// ==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();
}
});
})();