您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds customizable buttons to backloggd.
// ==UserScript== // @name Backloggd+ (Buttons & Tweaks) // @namespace http://vers.works/ // @version 2.7 // @icon https://pbs.twimg.com/profile_images/1541908760607821824/3Am5dmsx_400x400.jpg // @description Adds customizable buttons to backloggd. // @author VERS // @match https://www.backloggd.com/* // @match *://backloggd.com/* // @match *://*.backloggd.com/* // @grant GM_setValue // @grant GM_getValue // @license MIT // ==/UserScript== (function() { 'use strict'; const whitelist = ['Steam', 'SteamDB', 'SteamGridDB', 'Epic Games Store', 'GOG', 'Twitch', 'Youtube', 'XBOX', 'Playstation', 'Nintendo', "Retroachievements",]; let buttons = GM_getValue('buttons', [ { name: 'Steam', iconUrl: 'https://i.imgur.com/9NFBdz4.png', searchUrl: 'https://store.steampowered.com/search/?term=', color: '#1a9fff', status: true }, { name: 'SteamDB', iconUrl: 'https://i.imgur.com/t6rV9xA.png', searchUrl: 'https://steamdb.info/search/?a=all&q=', color: '#3e76cb', status: true }, { name: 'SteamGridDB', iconUrl: 'https://i.imgur.com/oluhFHS.png', searchUrl: 'https://www.steamgriddb.com/search/grids?term=', color: '#3a6e92', status: true }, { name: 'Epic Games Store', iconUrl: 'https://cdn2.unrealengine.com/Unreal+Engine%2Feg-logo-filled-1255x1272-0eb9d144a0f981d1cbaaa1eb957de7a3207b31bb.png', searchUrl: 'https://store.epicgames.com/en-US/expanded-search-results?q=', color: '#000000', status: true }, { name: 'GOG', iconUrl: 'https://i.imgur.com/3tL2SKY.png', searchUrl: 'https://www.gog.com/en/games?query=', color: '#9548a6', status: true }, { name: 'Twitch', iconUrl: 'https://i.imgur.com/UVuf0iF.png', searchUrl: 'https://www.twitch.tv/search?term=', color: '#9046fd', status: true }, { name: 'Youtube', iconUrl: 'https://i.imgur.com/C0Ux2Y3.png', searchUrl: 'https://www.youtube.com/results?search_query=', color: '#ff0000', status: true }, { name: 'XBOX', iconUrl: 'https://i.imgur.com/jrItCUM.png', searchUrl: 'https://www.xbox.com/Search/Results?q=', color: '#107b10', status: true }, { name: 'Playstation', iconUrl: 'https://i.imgur.com/wvB5DF8.png', searchUrl: 'https://www.playstation.com/search/?q=', color: '#0070d1', status: true }, { name: 'Nintendo', iconUrl: 'https://i.imgur.com/7cGs7D6.png', searchUrl: 'https://www.nintendo.com/us/search/#q=', color: '#e70819', status: true }, { name: 'Retroachievements', iconUrl: 'https://i.imgur.com/wp8wsD2.png', searchUrl: 'https://retroachievements.org/games?filter%5Btitle%5D=', color: '#ce9833', status: true }, { name: 'Settings', iconUrl: 'https://i.imgur.com/WvM8EHQ.png', color: '#16181c', status: true, isSettings: true } ]); function createButtonElement(buttonData) { const { iconUrl, searchUrl, color, name } = buttonData; const col = document.createElement('div'); col.className = 'custom-btn-container col px-0 mt-auto'; col.setAttribute('data-name', name); const button = document.createElement('button'); button.className = 'button-link btn-play mx-auto custom-button'; button.style.backgroundColor = color; button.style.width = '40px'; button.style.height = '40px'; button.style.borderRadius = '7px'; button.style.display = 'flex'; button.style.justifyContent = 'center'; button.style.alignItems = 'center'; button.style.boxShadow = 'var(--lt-shadowDefault)'; button.style.transition = 'transform 0.1s ease-in-out, box-shadow 0.1s ease-in-out'; button.addEventListener('mouseenter', () => { button.style.boxShadow = 'var(--lt-shadowActive)'; button.style.transform = 'translateY(-1px)'; }); button.addEventListener('mouseleave', () => { button.style.boxShadow = 'var(--lt-shadowDefault)'; button.style.transform = 'none'; }); const icon = document.createElement('img'); icon.src = iconUrl; icon.alt = name; icon.style.width = '24px'; icon.style.height = '24px'; icon.style.pointerEvents = 'none'; button.appendChild(icon); button.addEventListener('click', function() { if (name === 'Settings') { openSettingsModal(); } else { let gameTitleElement = document.querySelector('#title h1'); if (!gameTitleElement) { gameTitleElement = document.querySelector('h1[itemprop="name"]'); } if (!gameTitleElement) { gameTitleElement = document.querySelector('.game-title h1'); } if (!gameTitleElement) { gameTitleElement = document.querySelector('h1'); } if (gameTitleElement) { const gameTitle = encodeURIComponent(gameTitleElement.textContent.trim()); console.log('Game title found:', gameTitle); const searchLink = searchUrl.includes('%s') ? searchUrl.replace('%s', gameTitle) : searchUrl + gameTitle; console.log('Opening URL:', searchLink); window.open(searchLink, '_blank'); } else { console.error('Game title element not found on this page'); alert('Could not find game title on this page. Make sure you are on a game page.'); } } }); button.setAttribute('data-search', searchUrl); button.setAttribute('data-name', name); col.appendChild(button); return col; } function updateButtons() { const targetHr = document.querySelector('hr.mb-3.mt-2.d-none.d-md-flex'); if (!targetHr) return; document.querySelectorAll('.custom-btn-row').forEach(row => row.remove()); const activeButtons = buttons.filter(btn => btn.status); const whitelistButtons = activeButtons .filter(btn => whitelist.includes(btn.name)) .sort((a, b) => whitelist.indexOf(a.name) - whitelist.indexOf(b.name)); const customButtons = activeButtons.filter(btn => !whitelist.includes(btn.name) && !btn.isSettings); const settingsButtons = activeButtons.filter(btn => btn.isSettings); const allButtons = [...whitelistButtons, ...customButtons, ...settingsButtons]; let insertAfter = targetHr; for (let i = 0; i < allButtons.length; i += 4) { const rowButtons = allButtons.slice(i, i + 4); const buttonRow = document.createElement('div'); buttonRow.className = 'row mx-0 d-none d-md-flex logging-btns small-log-btns custom-btn-row flex-wrap'; rowButtons.forEach(btn => buttonRow.appendChild(createButtonElement(btn))); insertAfter.parentNode.insertBefore(buttonRow, insertAfter.nextSibling); insertAfter = buttonRow; } } function addSpacer() { const targetHr = document.querySelector('hr.mb-3.mt-2.d-none.d-md-flex'); if (!targetHr) return; const spacerParent = targetHr.parentNode.querySelector('.row.mt-2.d-none.d-md-flex'); if (spacerParent && !spacerParent.querySelector('.spacer')) { const spacer = document.createElement('div'); spacer.className = 'spacer'; spacer.style.height = '10px'; spacerParent.appendChild(spacer); } } function openSettingsModal() { const settingsModal = document.createElement('div'); settingsModal.className = 'settings-modal'; settingsModal.style.position = 'fixed'; settingsModal.style.top = '50%'; settingsModal.style.left = '50%'; settingsModal.style.transform = 'translate(-50%, -50%)'; settingsModal.style.backgroundColor = 'rgba(22, 24, 28)'; settingsModal.style.borderRadius = '10px'; settingsModal.style.padding = '20px'; settingsModal.style.zIndex = '9999'; settingsModal.style.maxHeight = 'auto'; settingsModal.style.overflowY = 'visible'; settingsModal.innerHTML = ` <h1>BACKLOGGD BUTTONS</h1> <p style="display: flex; align-items: center;">Script made by VERS. <a href="https://www.backloggd.com/u/VERS/" target="_blank"> <img src="https://i.imgur.com/4FLnRC9.png" alt="Icon 1" style="margin-left: 10px; width: 19px; height: 19px;"> </a> <a href="https://bento.me/thevers" target="_blank"> <img src="https://i.imgur.com/oePEdTt.png" alt="Icon 3" style="margin-left: 5px; width: 19px; height: 19px;"> </a> </p> <h2>Button Settings</h2> <div style="max-height: 300px; overflow-y: auto;"> <!-- Only this container scrolls --> <ul> ${buttons .filter(button => !button.isSettings && button.name !== 'Remove Button') .map( button => `<li> <label> <input type="checkbox" id="${button.name}-checkbox" ${ button.status ? 'checked' : '' }> ${button.name} </label> </li>` ) .join('')} </ul> </div> <button id="add-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem; display: block; width: 100%; margin-bottom: 6px;"> <img src="https://i.imgur.com/i5OSWDm.png" alt="Add" style="width:20px; height:20px; margin-right:8px; vertical-align:middle;">Add Custom Button </button> <button id="remove-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem; display: block; width: 100%; margin-bottom: 6px;"> <img src="https://i.imgur.com/mffJ4qZ.png" alt="Remove" style="width:20px; height:20px; margin-right:8px; vertical-align:middle;">Remove Custom Button </button> <div style="display: flex; gap: 6px; margin-bottom: 6px;"> <button id="import-json-btn" style="flex:1; background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;"> <img src="https://i.imgur.com/uLKJYct.png" alt="Import" style="width:20px; height:20px; margin-right:6px; vertical-align:middle;">Import JSON </button> <button id="export-json-btn" style="flex:1; background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;"> <img src="https://i.imgur.com/riaa4f2.png" alt="Export" style="width:20px; height:20px; margin-right:6px; vertical-align:middle;">Export JSON </button> </div> <button id="flush-memory-btn" style="background-color: #ff4444; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem; display: block; width: 100%; margin-bottom: 6px;"> <img src="https://i.imgur.com/zVry85k.png" alt="Flush" style="width:20px; height:20px; margin-right:8px; vertical-align:middle;">Flush Memory </button> <a href="https://discord.gg/eyDknctyUA" target="_blank" style="text-decoration: none;"> <button id="discord-btn" style="background-color: #7289DA; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem; display: block; width: 100%; margin-bottom: 6px;"> <img src="https://uxwing.com/wp-content/themes/uxwing/download/brands-and-social-media/discord-white-icon.png" alt="Discord" style="width:20px; height:20px; margin-right:8px; vertical-align:middle;"> Find more buttons </button> </a> <button id="save-settings-btn" style="background-color: #fc6399; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem; display: block; width: 100%; margin-bottom: 0px;"> <img src="https://i.imgur.com/IpDGyvR.png" alt="Save" style="width:20px; height:20px; margin-right:8px; vertical-align:middle;">Save Settings </button> `; document.body.appendChild(settingsModal); document.getElementById('import-json-btn').addEventListener('click', () => { const importModal = document.createElement('div'); importModal.style.position = 'fixed'; importModal.style.top = '50%'; importModal.style.left = '50%'; importModal.style.transform = 'translate(-50%, -50%)'; importModal.style.backgroundColor = '#242832'; importModal.style.borderRadius = '10px'; importModal.style.padding = '20px'; importModal.style.zIndex = '10000'; importModal.style.width = '400px'; importModal.innerHTML = ` <h2>Import Buttons from JSON</h2> <p style="font-size: 12px;">Paste your JSON below and click <strong>Import</strong>:</p> <textarea id="json-import-input" style="width: 100%; height: 150px; background-color: #1a1d25; color: #fff; border: 1px solid #555; border-radius: 5px; padding: 8px;"></textarea> <div style="margin-top: 10px; display: flex; justify-content: space-between;"> <button id="import-json-confirm" style="background-color: #fc6399; border: none; color: #fff; padding: 6px 12px; border-radius: 4px; cursor: pointer;">Import</button> <button id="close-json-import" style="background-color: #4a5e8d; border: none; color: #fff; padding: 6px 12px; border-radius: 4px; cursor: pointer;">Cancel</button> </div> `; document.body.appendChild(importModal); document.getElementById('close-json-import').addEventListener('click', () => importModal.remove()); document.getElementById('import-json-confirm').addEventListener('click', () => { try { const text = document.getElementById('json-import-input').value.trim(); if (!text) return alert('Please paste JSON first.'); const arr = JSON.parse(text); if (!Array.isArray(arr)) throw new Error('Expected array.'); let added = 0; arr.forEach(entry => { if (entry.name && entry.iconUrl && entry.searchUrl && entry.color) { if (!buttons.some(b => b.name.toLowerCase() === entry.name.toLowerCase())) { buttons.push({ ...entry, status: true }); added++; } } }); if (added > 0) { GM_setValue('buttons', buttons); alert(`✅ Imported ${added} new button(s)!`); window.location.reload(); } else alert('No new buttons found.'); } catch (err) { alert('❌ Invalid JSON: ' + err.message); } }); }); document.getElementById('export-json-btn').addEventListener('click', () => { const exportModal = document.createElement('div'); exportModal.style.position = 'fixed'; exportModal.style.top = '50%'; exportModal.style.left = '50%'; exportModal.style.transform = 'translate(-50%, -50%)'; exportModal.style.backgroundColor = '#242832'; exportModal.style.borderRadius = '10px'; exportModal.style.padding = '20px'; exportModal.style.zIndex = '10000'; exportModal.style.width = '400px'; const customBtns = buttons.filter(b => !whitelist.includes(b.name) && !b.isSettings); exportModal.innerHTML = ` <h2>Export Custom Buttons</h2> <p style="font-size: 12px;">Select which buttons to export:</p> <div id="export-checkboxes" style="max-height: 150px; overflow-y: auto; text-align: left; margin-bottom: 10px;"> ${customBtns.length > 0 ? customBtns.map(b => ` <label style="display: block; margin-bottom: 4px;"> <input type="checkbox" value="${b.name}" checked> ${b.name} </label>`).join('') : '<p style="font-size: 12px; color: #ccc;">No custom buttons found.</p>'} </div> <textarea id="json-export-output" style="width:100%; height:120px; background:#1a1d25; color:#fff; border:1px solid #555; border-radius:5px; padding:6px;" readonly></textarea> <div style="margin-top: 10px; display: flex; justify-content: space-between;"> <button id="export-json-generate" style="background-color: #fc6399; border: none; color: #fff; padding: 6px 12px; border-radius: 4px; cursor: pointer;">Generate</button> <button id="export-json-close" style="background-color: #4a5e8d; border: none; color: #fff; padding: 6px 12px; border-radius: 4px; cursor: pointer;">Close</button> </div> `; document.body.appendChild(exportModal); document.getElementById('export-json-close').addEventListener('click', () => exportModal.remove()); document.getElementById('export-json-generate').addEventListener('click', () => { const selected = Array.from(exportModal.querySelectorAll('input[type="checkbox"]:checked')).map(cb => cb.value); const exportData = buttons .filter(b => selected.includes(b.name)) .map(({ name, iconUrl, searchUrl, color }) => ({ name, iconUrl, searchUrl, color })); document.getElementById('json-export-output').value = JSON.stringify(exportData, null, 2); }); }); const addButtonBtn = document.getElementById('add-button'); addButtonBtn.addEventListener('click', () => { openAddButtonModal(); }); const removeButtonBtn = document.getElementById('remove-button'); removeButtonBtn.addEventListener('click', () => { openRemoveButtonModal(); }); const saveSettingsBtn = document.getElementById('save-settings-btn'); saveSettingsBtn.addEventListener('click', () => { buttons.forEach(button => { if (!button.isSettings) { const checkbox = document.getElementById(`${button.name}-checkbox`); button.status = checkbox.checked; } }); GM_setValue('buttons', buttons); window.location.reload(); }); } function openRemoveButtonModal() { const removeButtonModal = document.createElement('div'); removeButtonModal.className = 'remove-button-modal'; removeButtonModal.style.position = 'fixed'; removeButtonModal.style.top = '50%'; removeButtonModal.style.borderRadius = '10px'; removeButtonModal.style.left = '50%'; removeButtonModal.style.transform = 'translate(-50%, -50%)'; removeButtonModal.style.backgroundColor = '#242832'; removeButtonModal.style.padding = '20px'; removeButtonModal.style.zIndex = '9999'; removeButtonModal.innerHTML = ` <h2>Remove Button</h2> <ul> ${buttons .filter(button => !button.isSettings && !whitelist.includes(button.name)) .map( button => `<li> <button id="remove-${button.name}" style="background-color: #fc6399; border: none; color: #ffffff; padding: 5px 5; cursor: pointer;">✖</button> <span style="margin-left: 10px;">${button.name}</span> </li>` ) .join('')} </ul> <button id="close-remove-modal" style="background-color: #4a5e8d; border: none; color: #ffffff; padding: 8px 16px; cursor: pointer; margin-top: 10px;">Close</button> `; document.body.appendChild(removeButtonModal); buttons .filter(button => !button.isSettings && !whitelist.includes(button.name)) .forEach(button => { const removeButton = document.getElementById(`remove-${button.name}`); removeButton.addEventListener('click', () => { removeButtonFromList(button.name); removeButton.parentElement.remove(); }); }); const closeModalBtn = document.getElementById('close-remove-modal'); closeModalBtn.addEventListener('click', () => { removeButtonModal.remove(); }); } function removeButtonFromList(buttonName) { buttons = buttons.filter(button => button.name !== buttonName); GM_setValue('buttons', buttons); const existingRows = document.querySelectorAll('.custom-btn-row'); existingRows.forEach(row => row.remove()); addSpacer(); updateButtons(); } function openAddButtonModal() { const addButtonModal = document.createElement('div'); addButtonModal.className = 'add-button-modal'; Object.assign(addButtonModal.style, { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#242832', borderRadius: '10px', padding: '20px', zIndex: '9999', color: '#fff', textAlign: 'left', width: '420px', maxWidth: '90%', boxShadow: '0 0 12px rgba(0,0,0,0.5)', fontFamily: 'Inter, sans-serif' }); addButtonModal.innerHTML = ` <h2 style="text-align:center; margin-bottom: 12px;">Add Custom Button</h2> <div style="margin-bottom: 8px;"> <label for="website-name" style="display:inline-block;width:120px;">Website Name:</label> <input type="text" id="website-name" style="width:220px; border:none; border-radius:4px; padding:4px;" required> <button id="info-website-name" class="info-button" style="margin-left:5px; background:none; border:none; cursor:pointer;"> <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width:15px;height:15px;"> </button> </div> <div style="margin-bottom: 8px;"> <label for="icon-url" style="display:inline-block;width:120px;">Icon URL:</label> <input type="text" id="icon-url" style="width:220px; border:none; border-radius:4px; padding:4px;" required> <button id="info-icon-url" class="info-button" style="margin-left:5px; background:none; border:none; cursor:pointer;"> <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width:15px;height:15px;"> </button> </div> <div style="margin-bottom: 8px;"> <label for="search-url" style="display:inline-block;width:120px;">Search URL:</label> <input type="text" id="search-url" style="width:220px; border:none; border-radius:4px; padding:4px;" required> <button id="info-search-url" class="info-button" style="margin-left:5px; background:none; border:none; cursor:pointer;"> <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width:15px;height:15px;"> </button> </div> <div style="margin-bottom: 8px;"> <label for="color" style="display:inline-block;width:120px;">Button Color:</label> <input type="color" id="color" value="#fc6399" style="border:none; border-radius:4px; padding:2px;"> <button id="info-color" class="info-button" style="margin-left:5px; background:none; border:none; cursor:pointer;"> <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width:15px;height:15px;"> </button> </div> <div style="display:flex; justify-content:space-between; margin-top:14px;"> <button id="save-new-button" style="background-color:#fc6399; border:none; color:#fff; padding:8px 16px; border-radius:4px; cursor:pointer;">Add Button</button> <button id="close-button" style="background-color:#555; border:none; color:#fff; padding:8px 16px; border-radius:4px; cursor:pointer;">Close</button> </div> `; document.body.appendChild(addButtonModal); const infoTexts = { 'website-name': `This is the button name shown in settings. It doesn't have to match the site exactly.`, 'icon-url': `Direct link to an image (PNG/JPEG) used as the button icon. Example: https://i.imgur.com/abcd123.png`, 'search-url': `The base URL for a search on the site, WITHOUT the search term. Example: • Google: https://www.google.com/search?q= • Or use %s placeholder if not at the end (e.g., https://example.com/search?query=%s&type=games)`, 'color': `The background color for the button.` }; Object.keys(infoTexts).forEach(id => { const btn = document.getElementById(`info-${id}`); btn.addEventListener('click', () => alert(infoTexts[id])); }); document.getElementById('save-new-button').addEventListener('click', () => { const name = document.getElementById('website-name').value.trim(); const iconUrl = document.getElementById('icon-url').value.trim(); const searchUrl = document.getElementById('search-url').value.trim(); const color = document.getElementById('color').value.trim(); if (!name || !iconUrl || !searchUrl || !color) { alert('Please fill in all fields.'); return; } if (buttons.some(b => b.name.toLowerCase() === name.toLowerCase())) { alert('A button with this name already exists.'); return; } buttons.push({ name, iconUrl, searchUrl, color, status: true }); GM_setValue('buttons', buttons); alert(`✅ "${name}" button added successfully!`); window.location.reload(); }); document.getElementById('close-button').addEventListener('click', () => addButtonModal.remove()); document.getElementById('import-json').addEventListener('click', () => { const importModal = document.createElement('div'); Object.assign(importModal.style, { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: '#242832', borderRadius: '10px', padding: '20px', zIndex: '10000', width: '420px', color: '#fff', textAlign: 'left', boxShadow: '0 0 12px rgba(0,0,0,0.5)' }); importModal.innerHTML = ` <h3>Import Buttons from JSON</h3> <p style="font-size:12px;">Paste your JSON below and click Import:</p> <textarea id="json-input" placeholder='[{"Website Name":"Example","Icon URL":"https://i.imgur.com/abcd.png","Search URL":"https://example.com/search?q=","Button Color":"#123456"}]' style="width:100%;height:150px;background:#1a1d25;color:#fff;border:1px solid #555;border-radius:5px;padding:8px;resize:none;"></textarea> <div style="display:flex;justify-content:space-between;margin-top:10px;"> <button id="import-confirm" style="background-color:#fc6399;border:none;color:#fff;padding:6px 12px;border-radius:4px;cursor:pointer;">Import</button> <button id="import-cancel" style="background-color:#4a5e8d;border:none;color:#fff;padding:6px 12px;border-radius:4px;cursor:pointer;">Cancel</button> </div> `; document.body.appendChild(importModal); document.getElementById('import-cancel').addEventListener('click', () => importModal.remove()); document.getElementById('import-confirm').addEventListener('click', () => { const raw = document.getElementById('json-input').value.trim(); if (!raw) return alert('Please paste JSON first.'); try { const arr = JSON.parse(raw); if (!Array.isArray(arr)) throw new Error('Expected an array of objects.'); let added = 0; arr.forEach(entry => { const name = entry['Website Name']; const iconUrl = entry['Icon URL']; const searchUrl = entry['Search URL']; const color = entry['Button Color']; if (name && iconUrl && searchUrl && color && !buttons.some(b => b.name.toLowerCase() === name.toLowerCase())) { buttons.push({ name, iconUrl, searchUrl, color, status: true }); added++; } }); if (added) { GM_setValue('buttons', buttons); alert(`✅ Imported ${added} new button(s)!`); window.location.reload(); } else { alert('No valid or new buttons found.'); } } catch (e) { alert('❌ Invalid JSON: ' + e.message); } }); }); } function waitForElement(selector, callback, maxAttempts = 50) { let attempts = 0; const interval = setInterval(() => { const element = document.querySelector(selector); if (element) { clearInterval(interval); callback(element); } else if (++attempts >= maxAttempts) { clearInterval(interval); console.log(`Element ${selector} not found after ${maxAttempts} attempts`); } }, 100); } function addAtButton() { const commentRows = document.querySelectorAll('.row.mb-2'); commentRows.forEach(row => { if (!row.querySelector('.at-button')) { const atButton = document.createElement('div'); atButton.className = 'col-auto mt-auto pl-0 pr-1 at-button'; atButton.innerHTML = '<button class="button-link secondary-link" style="cursor: pointer;">@</button>'; const usernameDiv = row.querySelector('.col-auto.mt-auto.px-2'); if (usernameDiv) { const usernameLink = usernameDiv.querySelector('a.secondary-link'); if (usernameLink) { const username = usernameLink.textContent.trim(); atButton.querySelector('button').addEventListener('click', () => { const commentBox = document.getElementById('comment_body'); if (commentBox) { const currentText = commentBox.value; const cursorPosition = commentBox.selectionStart; const textBefore = currentText.substring(0, cursorPosition); const textAfter = currentText.substring(cursorPosition); const newText = `${textBefore}@${username} ${textAfter}`; commentBox.value = newText; commentBox.focus(); const newCursorPosition = cursorPosition + username.length + 2; commentBox.setSelectionRange(newCursorPosition, newCursorPosition); } }); row.appendChild(atButton); } } } }); } function processElements() { addAtButton(); } const style = document.createElement('style'); style.textContent = ` .settings-modal button:hover { filter: brightness(85%); } /* Custom main buttons hover effect */ .custom-button { transition: transform 0.15s ease, box-shadow 0.15s ease, border 0.15s ease; border: 2px solid transparent; /* default border */ } .custom-button:hover { transform: translateY(-3px) scale(1.05); border: 2px solid rgba(255, 255, 255, 0.35); /* slightly lighter than button */ box-shadow: 0 4px 8px rgba(0,0,0,0.3); } .custom-btn-row { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: flex-start; /* changed from space-between */ margin-top: 6px; } .custom-btn-container { flex: 0 0 25%; /* keeps each button container 1/4 of the row */ max-width: 25%; display: flex; justify-content: center; } .custom-button { color: #fff; cursor: pointer; } .custom-button img { filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)); } `; document.head.appendChild(style); const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { if (mutation.type === 'childList') { const addedNodes = Array.from(mutation.addedNodes); addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.classList && (node.classList.contains('comments-section') || node.closest('.comments-section'))) { processElements(); } } }); } }); }); observer.observe(document.body, { childList: true, subtree: true }); function initializeButtons() { waitForElement('hr.mb-3.mt-2.d-none.d-md-flex', () => { addSpacer(); updateButtons(); processElements(); }); } initializeButtons(); let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; console.log('URL changed, reinitializing buttons...'); setTimeout(() => { initializeButtons(); }, 300); } }).observe(document, { subtree: true, childList: true }); window.addEventListener('hashchange', () => { console.log('Hash changed, reinitializing buttons...'); setTimeout(() => { initializeButtons(); }, 300); }); const CURRENT_NOTICE_VERSION = 'debug'; const hasSeenNotice = GM_getValue(CURRENT_NOTICE_VERSION, false); if (!hasSeenNotice) { const noticeModal = document.createElement('div'); noticeModal.style.position = 'fixed'; noticeModal.style.top = '50%'; noticeModal.style.left = '50%'; noticeModal.style.transform = 'translate(-50%, -50%)'; noticeModal.style.backgroundColor = '#242831'; noticeModal.style.border = '1px solid #444'; noticeModal.style.borderRadius = '10px'; noticeModal.style.padding = '20px'; noticeModal.style.zIndex = '99999'; noticeModal.style.color = '#fff'; noticeModal.style.textAlign = 'center'; noticeModal.style.boxShadow = '0 0 15px rgba(0,0,0,0.6)'; noticeModal.innerHTML = ` <h1 style="margin-bottom: 20px;">BACKLOGGD BUTTONS - 2.6</h1> <p style="font-size: 19px; margin-bottom: 20px;"> If you are updating please flush the memory. (This will reset all of your settings!) You can also export your buttons in the settings. </p> <div style="display:flex; gap:8px; justify-content:center;"> <button id="notice-flush-btn" style=" background-color: #ff4444; border: none; color: white; padding: 8px 16px; border-radius: 6px; cursor: pointer; ">Flush Memory</button> <button id="notice-ok-btn" style=" background-color: #fc6399; border: none; color: white; padding: 8px 16px; border-radius: 6px; cursor: pointer; ">Got it</button> </div> `; document.body.appendChild(noticeModal); const originalFlushMemory = () => { if (confirm('Are you sure you want to flush all saved buttons and reset the script?')) { const preserveNotice = GM_getValue(CURRENT_NOTICE_VERSION, true); GM_setValue('buttons', undefined); GM_setValue(CURRENT_NOTICE_VERSION, preserveNotice); window.location.reload(); } }; document.getElementById('notice-ok-btn').addEventListener('click', () => { GM_setValue(CURRENT_NOTICE_VERSION, true); noticeModal.remove(); }); document.getElementById('notice-flush-btn').addEventListener('click', () => { originalFlushMemory(); }); } const originalFlushMemory = () => { if (confirm('Are you sure you want to flush all saved buttons and reset the script?')) { const preserveNotice = GM_getValue(CURRENT_NOTICE_VERSION, true); GM_setValue('buttons', undefined); GM_setValue(CURRENT_NOTICE_VERSION, preserveNotice); window.location.reload(); } }; document.addEventListener('click', (e) => { if (e.target && e.target.id === 'flush-memory-btn') { e.preventDefault(); originalFlushMemory(); } }); })();