- // ==UserScript==
- // @name Backloggd Button Utility
- // @namespace http://vers.works/
- // @version 2.56
- // @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/*
- // @exclude https://backloggd.com/search/*
- // @grant GM_setValue
- // @grant GM_getValue
- // @license MIT
- // ==/UserScript==
-
- // Version 2.53:
- // Fixed loading issues and GOG logo, thank you to DarkH15.
-
-
- (function() {
- 'use strict';
-
- const whitelist = ['Steam', 'SteamDB', 'SteamGridDB', 'Epic Games Store', 'GOG', 'Twitch', 'Youtube', 'XBOX', 'Playstation', 'Nintendo',];
-
- 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://yt3.googleusercontent.com/7fRS7D_McYJmwXG4hrzJsZyKgAcVU7WmZrGkYaAyadqMj5LGD-lQXkCZ5ybWAklQSKDSiVbUIA=s900-c-k-c0x00ffffff-no-rj',
- searchUrl: 'https://www.gog.com/en/games?query=',
- color: '#fff',
- 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: 'Settings',
- iconUrl: 'https://i.imgur.com/WvM8EHQ.png',
- color: '#16181c',
- status: true,
- isSettings: true
- },
- {
- name: 'Remove Button',
- iconUrl: 'https://via.placeholder.com/24',
- color: '#ff0000',
- status: true,
- isSettings: true,
- isRemovable: false
- }
- ]);
-
- function addButton(iconUrl, searchUrl, color, status, name) {
- if (!status) {
- return;
- }
-
- const button = document.createElement('button');
- button.className = 'btn btn-main journal-btn custom-button';
- button.style.width = '34px';
- button.style.height = '34px';
- button.style.margin = '7px 4px 3px 4px';
- button.style.backgroundColor = color;
- button.style.border = 'none';
- button.style.display = 'inline-flex';
- button.style.justifyContent = 'center';
- button.style.alignItems = 'center';
-
- const icon = document.createElement('img');
- icon.src = iconUrl;
- icon.alt = 'Search';
- icon.style.width = '24px';
- icon.style.height = '24px';
- button.appendChild(icon);
-
- button.addEventListener('click', function() {
- if (name === 'Settings') {
- openSettingsModal();
- } else {
- const gameTitleElement = document.querySelector('#title h1');
- if (gameTitleElement) {
- const gameTitle = encodeURIComponent(gameTitleElement.textContent.trim());
- const searchLink = searchUrl.includes('%s') ? searchUrl.replace('%s', gameTitle) : searchUrl + gameTitle;
- window.open(searchLink, '_blank');
- } else {
- console.error('Game title element not found');
- }
- }
- });
-
- const journalButtonContainer = document.querySelector('.journal-button-container');
- if (journalButtonContainer) {
- const settingsButton = journalButtonContainer.querySelector('button[data-name="Settings"]');
- const existingButton = journalButtonContainer.querySelector(`.custom-button[data-search="${searchUrl}"]`);
-
- if (!existingButton) {
- button.setAttribute('data-search', searchUrl);
- button.setAttribute('data-name', name);
- if (settingsButton) {
- journalButtonContainer.insertBefore(button, settingsButton); // Insert before Settings button
- } else {
- journalButtonContainer.appendChild(button); // Fallback in case settings button is not found
- }
- }
- } else {
- console.error('Journal button container not found');
- }
- }
-
- function addSpacer() {
- const existingSpacer = document.querySelector('.journal-button-container > .spacer');
- if (!existingSpacer) {
- const spacer = document.createElement('div');
- spacer.className = 'spacer'; // Add a class to identify the spacer
- spacer.style.height = '10px'; // Adjust the height as needed
- const journalButtonContainer = document.querySelector('.journal-button-container');
- if (journalButtonContainer) {
- const firstButton = journalButtonContainer.querySelector('.custom-button');
- if (firstButton) {
- journalButtonContainer.insertBefore(spacer, firstButton);
- } else {
- journalButtonContainer.appendChild(spacer); // If no buttons exist yet, just append the spacer
- }
- } else {
- console.error('Journal button container not found');
- }
- }
- }
-
-
- function updateButtons() {
- buttons.forEach(button => {
- addButton(button.iconUrl, button.searchUrl, button.color, button.status, button.name);
- });
- }
-
- 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.innerHTML = `
- <h1>Backloggd Button Utility</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://twitter.com/versworks" target="_blank">
- <img src="https://i.imgur.com/yXPQwtv.png" alt="Icon 2" style="margin-left: 5px;; width: 19px; height: 19px;">
- </a>
- <a href="https://vers.works/" target="_blank">
- <img src="https://i.imgur.com/oePEdTt.png" alt="Icon 3" style="margin-left: 5px;; width: 19px; height: 19px;">
- </a>
- </p>
- <p style="font-size: 12px; font-style: italic; text-align: center;">
- If you have any issues or feedback you can contact me on discord (The_Vers) <br>or in this <a href="https://www.reddit.com/r/backloggd/comments/1bopg7t/introducing_button_utility_my_script_that_adds/" target="_blank">reddit post</a>.
- </p>
- <h2>Button Settings</h2>
- <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>
- <button id="add-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Add Custom Button</button>
- <button id="remove-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Remove Custom Button</button>
- <button id="save-settings-btn" style="background-color: #fc6399; border: none; color: #fff; padding: 8px 16px; cursor: pointer; border-radius: .25rem;">Save Settings</button>
- `;
-
- document.body.appendChild(settingsModal);
-
- 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 existingButtons = document.querySelectorAll('.custom-button');
- existingButtons.forEach(button => {
- button.remove();
- });
-
- addSpacer();
- updateButtons();
- }
-
- function openAddButtonModal() {
- const addButtonModal = document.createElement('div');
- addButtonModal.className = 'add-button-modal';
- addButtonModal.style.position = 'fixed';
- addButtonModal.style.top = '50%';
- addButtonModal.style.left = '50%';
- addButtonModal.style.borderRadius = '10px';
- addButtonModal.style.transform = 'translate(-50%, -50%)';
- addButtonModal.style.backgroundColor = '#242832';
- addButtonModal.style.padding = '20px';
- addButtonModal.style.zIndex = '9999';
- addButtonModal.innerHTML = `
- <h2>Add Custom Button</h2>
- <div>
- <label for="website-name" style="width: 120px;">Website Name:</label>
- <button id="info-website-name" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
- <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
- </button>
- <input type="text" id="website-name" style="width: 200px;" required>
- </div>
- <div>
- <label for="icon-url" style="width: 120px;">Icon URL:</label>
- <button id="info-icon-url" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
- <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
- </button>
- <input type="text" id="icon-url" style="width: 200px;" required>
- </div>
- <div>
- <label for="search-url" style="width: 120px;">Search URL:</label>
- <button id="info-search-url" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
- <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
- </button>
- <input type="text" id="search-url" style="width: 200px;" required>
- </div>
- <div>
- <label for="color" style="width: 120px;">Button Color:</label>
- <button id="info-color" class="info-button" style="background-color: #384361; border: none; color: #fff; padding: 3px; cursor: pointer; border-radius: 3px;">
- <img src="https://i.imgur.com/jEbi3Oy.png" alt="Info" style="width: 15px; height: 15px;">
- </button>
- <input type="color" id="color" value="#fc6399">
- </div>
- <button id="save-new-button" style="background-color: #fc6399; border: none; color: #fff; padding: 8px 16px; cursor: pointer; margin-top: 10px; border-radius: 3px;">Add Button</button>
- <button id="close-button" style="background-color: #4a5e8d; border: none; color: #fff; padding: 8px 16px; cursor: pointer; margin-top: 10px; border-radius: 3px;">Close</button>
- `;
-
- document.body.appendChild(addButtonModal);
-
- const saveNewButtonBtn = document.getElementById('save-new-button');
- const closeButton = document.getElementById('close-button');
-
- function openInfoPopup(inputId, infoText) {
- const inputElement = document.getElementById(inputId);
- const infoButton = document.getElementById(`info-${inputId}`);
-
- infoButton.addEventListener('click', () => {
- alert(infoText);
- inputElement.focus();
- });
- }
-
- const infoTexts = {
- 'website-name': 'Used for enabling/disabling in the settings (Doesnt have to be exact)',
- 'icon-url': `Full URL path to a (preferably transparent) PNG.
- ✅ Correct: https://i.imgur.com/n04Muj1.jpeg
- ❌ Wrong: https://cdn-icons-png.flaticon.com/512/2815/2815428`,
- 'search-url': `This is a URL when you search something without the actual thing you searched.
-
- Simple:
- Add the full URL without the thing you searched (Good if the searched term is at the end of the URL)
-
- Example (On google.com):
- If you google test you would get: https://www.google.com/search?q=test
- Then you remove 'test' and you get this, which you put into this box: https://www.google.com/search?q=
-
- Advanced:
- In rare instances, when the searched term is not at the end of the URLm you can replace the searched term with %s, which is where the game name will be put into.
-
- Example (Made-up case on Gamebanana):
- If the URL is:
- https://gamebanana.com/search?__sSearchString=TESTOrder=best_match&_idGameRow=18557
-
- We replace the 'TEST' with %s, which we will get:
- https://gamebanana.com/search?__sSearchString=%sOrder=best_match&_idGameRow=18557`,
- 'color': 'Color for the button.'
- };
-
- Object.keys(infoTexts).forEach(inputId => {
- openInfoPopup(inputId, infoTexts[inputId]);
- });
-
- saveNewButtonBtn.addEventListener('click', () => {
- const websiteNameInput = document.getElementById('website-name').value;
- const iconUrlInput = document.getElementById('icon-url').value;
- const searchUrlInput = document.getElementById('search-url').value;
- const colorInput = document.getElementById('color').value;
-
- if (buttons.some(button => button.name.toLowerCase() === websiteNameInput.toLowerCase())) {
- alert('A button with this name already exists. Please choose a different name.');
- return;
- }
-
- const newButton = {
- name: websiteNameInput,
- iconUrl: iconUrlInput,
- searchUrl: searchUrlInput,
- color: colorInput,
- status: true
- };
-
- buttons.push(newButton);
- GM_setValue('buttons', buttons);
-
- addButton(newButton.iconUrl, newButton.searchUrl, newButton.color, newButton.status, newButton.name);
-
- window.location.reload();
- });
-
- closeButton.addEventListener('click', () => {
- addButtonModal.remove();
- });
- }
-
-
- function waitForElement(selector, callback) {
- const interval = setInterval(() => {
- const element = document.querySelector(selector);
- if (element) {
- clearInterval(interval);
- callback(element);
- }
- }, 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();
- // Add any other element processing functions here
- }
-
- 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) {
- // Check if the added node is part of the comment section
- if (node.classList && (node.classList.contains('comments-section') || node.closest('.comments-section'))) {
- processElements();
- } else {
- // Process any other elements
- processElements();
- }
- }
- });
- }
- });
- });
-
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
-
- // Initial setup
- addSpacer();
- updateButtons();
- processElements();
-
- waitForElement('#title h1', (element) => {
- const titleObserver = new MutationObserver(function(mutationsList) {
- for (let mutation of mutationsList) {
- if (mutation.type === 'childList' && mutation.target.nodeName === 'HTML') {
- const existingButtons = document.querySelectorAll('.custom-button');
- existingButtons.forEach(button => {
- button.remove();
- });
- addSpacer();
- updateButtons();
- processElements();
- }
- }
- });
-
- titleObserver.observe(document, { childList: true, subtree: true });
- });
-
- window.onhashchange = function() {
- const existingButtons = document.querySelectorAll('.custom-button');
- existingButtons.forEach(button => {
- button.remove();
- });
- updateButtons();
- processElements();
- };
- })();