Backloggd Button Utility

Adds customizable buttons to backloggd.

目前为 2024-03-27 提交的版本。查看 最新版本

// ==UserScript==
// @name         Backloggd Button Utility
// @namespace    http://vers.works/
// @version      1.1
// @icon         https://pbs.twimg.com/profile_images/1541908760607821824/3Am5dmsx_400x400.jpg
// @description  Adds customizable buttons to backloggd.
// @author       VERS
// @match        https://www.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',];

    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/wr5HXJvdt0_B0bFgxaO5Fczjl3wsofggnPGKRuLe8AWkvyRUnedkjJIKQGmztxD-Xue8qiBC=s900-c-k-c0x00ffffff-no-rj',
            searchUrl: 'https://www.gog.com/en/games?query=',
            color: '#ffffff',
            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 + gameTitle;
                    window.open(searchLink, '_blank');
                } else {
                    console.error('Game title element not found');
                }
            }
        });

        const journalButtonContainer = document.querySelector('.journal-button-container');
        if (journalButtonContainer) {
            const existingButton = journalButtonContainer.querySelector(`.custom-button[data-search="${searchUrl}"]`);
            if (!existingButton) {
                button.setAttribute('data-search', searchUrl);
                button.setAttribute('data-name', name);
                journalButtonContainer.appendChild(button);
            }
        } else {
            console.error('Journal button container not found');
        }
    }

    function addSpacer() {
        const spacer = document.createElement('div');
        spacer.style.height = '6px';
        const journalButtonContainer = document.querySelector('.journal-button-container');
        if (journalButtonContainer) {
            journalButtonContainer.insertBefore(spacer, journalButtonContainer.firstChild);
        }
    }

    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 = '#16181c';
        settingsModal.style.padding = '20px';
        settingsModal.style.zIndex = '9999';
        settingsModal.innerHTML = `
            <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: #badefc; border: none; color: #000; padding: 8px 16px; cursor: pointer;">Add Custom Button</button>
            <button id="remove-button" style="background-color: #ffcccc; border: none; color: #000; padding: 8px 16px; cursor: pointer;">Remove Custom Button</button>
            <button id="save-settings-btn" style="background-color: #badefc; border: none; color: #000; padding: 8px 16px; cursor: pointer;">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.left = '50%';
        removeButtonModal.style.transform = 'translate(-50%, -50%)';
        removeButtonModal.style.backgroundColor = '#010101';
        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.name}
                                <button id="remove-${button.name}" style="margin-left: 10px; background-color: #ffcccc; border: none; color: #000; padding: 5px 10px; cursor: pointer;">x</button>
                            </li>`
                    )
                    .join('')}
            </ul>
            <button id="close-remove-modal" style="background-color: #badefc; border: none; color: #000; 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', () => {
                    // Remove button logic
                    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.transform = 'translate(-50%, -50%)';
        addButtonModal.style.backgroundColor = '#010101';
        addButtonModal.style.padding = '20px';
        addButtonModal.style.zIndex = '9999';
        addButtonModal.innerHTML = `
            <h2>Add New Button</h2>
            <div>
                <label for="website-name">Website Name:</label>
                <input type="text" id="website-name" required>
            </div>
            <div>
                <label for="icon-url">Icon URL:</label>
                <input type="text" id="icon-url" required>
            </div>
            <div>
                <label for="search-url">Search URL:</label>
                <input type="text" id="search-url" required>
            </div>
            <div>
                <label for="color">Button Color:</label>
                <input type="color" id="color" value="#1a9fff">
            </div>
            <button id="save-new-button" style="background-color: #badefc; border: none; color: #000; padding: 8px 16px; cursor: pointer; margin-top: 10px;">Save Button</button>
        `;

        document.body.appendChild(addButtonModal);

        const saveNewButtonBtn = document.getElementById('save-new-button');
        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;

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

    function waitForElement(selector, callback) {
        const interval = setInterval(() => {
            const element = document.querySelector(selector);
            if (element) {
                clearInterval(interval);
                callback(element);
            }
        }, 100);
    }


    function addBadgeToElement(element) {
        const badge = document.createElement('span');
        badge.className = 'vers-badge';
        badge.textContent = 'SCRIPT\nDEV'; // Two words split into two lines
        badge.style.backgroundColor = '#1aba7c';
        badge.style.color = '#16181c';
        badge.style.fontSize = '.7rem';
        badge.style.borderRadius = '4px';
        badge.style.fontWeight = '600';
        badge.style.padding = '3px';
        badge.style.marginLeft = '5px'; // Adjust margin as needed

        // Check if the element is a <p>, set display to inline-block
        if (element.tagName.toLowerCase() === 'p') {
            badge.textContent = 'SCRIPTDEV'; // Two words split into two lines
        }

        // Insert badge after the text node
        const textNode = element.firstChild;
        element.insertBefore(badge, textNode.nextSibling);
    }

    function processElements() {
        console.log('Processing elements...');
        const h3Elements = document.querySelectorAll('div.row > div.col-auto > h3.main-header');
        const pElements = document.querySelectorAll('div.col > div.row.mb-1 > a > div.col-auto > p.mb-0');

        h3Elements.forEach(element => {
            if (element.textContent.trim() === 'VERS' && !element.classList.contains('vers-badge')) {
                console.log('Adding badge to h3 element:', element);
                addBadgeToElement(element);
            }
        });

        pElements.forEach(element => {
            if (element.textContent.trim() === 'VERS' && !element.classList.contains('vers-badge')) {
                console.log('Adding badge to p element:', element);
                addBadgeToElement(element);
            }
        });
    }

    // Initial processing on page load
    processElements();

    // Mutation Observer to handle dynamically added content
    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) {
                        processElements();
                    }
                });
            }
        });
    });

    // Observe changes to the body and its subtree
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    addSpacer();
    updateButtons();

    waitForElement('#title h1', (element) => {
        const observer = 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();
                }
            }
        });

        observer.observe(document, { childList: true, subtree: true });
    });

    window.onhashchange = function() {
        const existingButtons = document.querySelectorAll('.custom-button');
        existingButtons.forEach(button => {
            button.remove();
        });
        updateButtons();
    };
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址