Backloggd Button Utility

Adds customizable buttons to backloggd.

ของเมื่อวันที่ 11-04-2025 ดู เวอร์ชันล่าสุด

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Backloggd Button Utility
// @namespace    http://vers.works/
// @version      2.54
// @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==

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