Grundo's Cafe Dynamic Guide Links

Adds a dropdown with quick access to guides and resources relevant to the current page (such as training links for battledome, game-specific guides, and more). Compatible with both Firefox and Chrome.

// ==UserScript==
// @name         Grundo's Cafe Dynamic Guide Links
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  Adds a dropdown with quick access to guides and resources relevant to the current page (such as training links for battledome, game-specific guides, and more). Compatible with both Firefox and Chrome.
// @author       Thornruler
// @match        https://grundos.cafe/*
// @match        https://www.grundos.cafe/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=grundos.cafe
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
    'use strict';
    // Toggle for info question mark. Set to false to remove it.
    const showQuestionMark = true;

    // Define the mapping between the website and the corresponding link with custom text
    const linkMapping = [
        //----------------Page url guides
        // Overall Links
        { pattern: '^/$', url: 'https://www.grundos.cafe/~Bawk/', text: 'Overall Guide' },
        { pattern: '/explore/', url: 'https://www.grundos.cafe/~Bawk/', text: 'Overall Guide' },

        // Shop Links
        { pattern: '/viewshop/*', url: 'https://www.grundos.cafe/~Bawk/#Market', text: 'Shops/Restocking' },
        { pattern: '/viewshop/\\?shop_id=32', url: 'https://www.grundos.cafe/~toadstool/', text: 'Mushroom Effects' },
        { pattern: '/viewshop/\\?shop_id=12', url: 'https://www.grundos.cafe/~Plant/', text: 'Gardening' },
        { pattern: '/viewshop/\\?shop_id=14', url: 'https://www.grundos.cafe/~Birdy', text: 'Gourmet Foods' },
        { pattern: '/viewshop/\\?shop_id=16', url: 'https://www.grundos.cafe/~Birdy', text: 'Gourmet Foods' },

        // Battle Dome and Training links
        { pattern: '/dome/*', url: 'https://www.grundos.cafe/~Bawk/#Training', text: 'Training' },
        { pattern: '/dome/*', url: 'https://www.grundos.cafe/~B/', text: 'Challenger Unlocks' },
        { pattern: '/dome/*', url: 'https://www.grundos.cafe/~toadstool/', text: 'Mushroom Effects' },
        { pattern: '/dome/*', url: 'https://www.grundos.cafe/~Jack/', text: 'Negg Effects' },
        { pattern: '/island/training/*', url: 'https://www.grundos.cafe/~Bawk/#Training', text: 'Training' },
        { pattern: '/pirates/academy/*', url: 'https://www.grundos.cafe/~Bawk/#Training', text: 'Training' },

        // Guild
        { pattern: '/guilds/*', url: 'https://www.grundos.cafe/~Anemone/', text: 'Guild Directory' },
        { pattern: '/guilds/guild/.*/garden/*', url: 'https://www.grundos.cafe/~Plant/', text: 'Gardening' },

        // Neoschool
        { pattern: '/neoschool/*', url: 'https://www.grundos.cafe/~Nerd/', text: 'NeoSchool' },

        // Games and dailies
        { pattern: '/games/*', url: 'https://www.grundos.cafe/~Mitya/', text: 'Game Rewards' },
        { pattern: '/games/cliffhanger/*', url: 'https://www.jellyneo.net/\\?go=cliffhanger', text: 'Cliffhanger' },
        { pattern: '/games/invasionofmeridell/*', url: 'https://docs.google.com/document/d/e/2PACX-1vQOm_faNyIRAPWZ4VUb-lvzCqSm7QHzu0Ltz00FIlM8n7kD_mbBzKsea5tYE9WwKGJlytvRjhnpb23K/pub', text: 'Invasion of Meridell' },
        { pattern: '/medieval/turmaculus/*', url: 'https://docs.google.com/spreadsheets/d/1Ij3I_YYl3z0SW_8bca_-bcpfGjhrvDa_1bku-52pGcI/edit#gid=336283101', text: 'Turmy Tracking' },

        // Misc Links
        { pattern: '/winter/neggery/*', url: 'https://www.grundos.cafe/~Jack/', text: 'Negg Effects' },
        { pattern: '/space/warehouse/*', url: 'https://www.grundos.cafe/~Rune/', text: 'Relics' },
        { pattern: '/games/avatar_stats/*', url: 'https://www.grundos.cafe/~Milk/', text: 'Avatar Collection' },
        { pattern: '/island/cookingpot/*', url: 'https://www.grundos.cafe/~Mix/', text: 'Potion Mixing' },
        { pattern: '/island/cookingpot/*', url: 'https://greasyfork.org/en/scripts/468004-grundo-s-cafe-cooking-pot-enhancer', text: 'Ingredient Display Script' },
        { pattern: '/island/tradingpost/*', url: 'https://www.grundos.cafe/~Bawk/#TradingPost', text: 'Trading Post' },
        { pattern: '/halloween/*', url: 'https://www.grundos.cafe/~Luigi', text: 'Spooky Toilet' },
        { pattern: '/help/siteprefs/*', url: 'https://www.grundos.cafe/~Brand/', text: 'Site Themes' },

        //----------------Item scan guides
        { pattern: '/inventory/*', condition: () => Array.from(document.querySelectorAll('.item-info > span')).some(span => span.textContent.includes('shroom')), url: 'https://www.grundos.cafe/~toadstool/', text: 'Mushroom Effects'},
        { pattern: '/inventory/*', condition: () => Array.from(document.querySelectorAll('.item-info > span')).some(span => span.textContent.includes('Negg')), url: 'https://www.grundos.cafe/~Jack/', text: 'Negg Effects'},
        { pattern: '/market/*', condition: () => Array.from(document.querySelectorAll('strong')).some(strong => strong.textContent.includes('shroom')), url: 'https://www.grundos.cafe/~toadstool/', text: 'Mushroom Effects'},
        { pattern: '/market/*', condition: () => Array.from(document.querySelectorAll('strong')).some(strong => strong.textContent.includes('Negg')), url: 'https://www.grundos.cafe/~Jack/', text: 'Negg Effects'},
        { pattern: '/safetydeposit*', condition: () => Array.from(document.querySelectorAll('strong')).some(strong => strong.textContent.includes('shroom')), url: 'https://www.grundos.cafe/~toadstool/', text: 'Mushroom Effects'},
        { pattern: '/safetydeposit*', condition: () => Array.from(document.querySelectorAll('strong')).some(strong => strong.textContent.includes('Negg')), url: 'https://www.grundos.cafe/~Jack/', text: 'Negg Effects'},

    ];

    // Ensure consistent domain (www or non-www) for grundos.cafe links. This prevents having to log in again.
    function prependWWWIfNeeded(url) {
        const grundosPattern = /https?:\/\/(www\.)?grundos\.cafe/;
        const isGrundosLink = grundosPattern.test(url);

        if (!isGrundosLink) return url;

        const isOnWWW = window.location.hostname.startsWith('www.');
        const hasWWW = url.startsWith('https://www.');

        if (isOnWWW && !hasWWW) {
            return url.replace('https://', 'https://www.');
        } else if (!isOnWWW && hasWWW) {
            return url.replace('https://www.', 'https://');
        }

        return url;
    }

    function createModalContent(links) {
        const uniqueLinks = links.reduce((accumulator, link) => {
            const fullUrl = prependWWWIfNeeded(link.url);
            const existingLink = accumulator.find(item => item.url === fullUrl && item.text === link.text);

            if (!existingLink) {
                accumulator.push({ url: fullUrl, text: link.text });
            }

            return accumulator;
        }, []);

        const container = document.createElement('div');

        const list = document.createElement('ul');
        list.style.listStyleType = 'none';
        list.style.padding = '0';

        for (const link of uniqueLinks) {
            const listItem = document.createElement('li');
            const anchor = document.createElement('a');
            anchor.href = link.url;
            anchor.target = '_blank';
            anchor.textContent = link.text;
            listItem.appendChild(anchor);
            list.appendChild(listItem);
        }

        container.appendChild(list);

        // Create expandable section
        const aboutSection = document.createElement('details');
        const summary = document.createElement('summary');
        summary.textContent = 'About this script';
        aboutSection.appendChild(summary);

        const alertText = document.createElement('p');
        alertText.textContent = "If you have suggestions for more links to add or have bugs to report, send me a Neomail:\nhttps://www.grundos.cafe/userlookup/?user=Thornruler\n\nThank you!";
        alertText.style.whiteSpace = 'pre-wrap';
        aboutSection.appendChild(alertText);

        container.appendChild(aboutSection);

        return container;
    }


    function showModal(links) {
        const modal = document.createElement('div');
        modal.style.position = 'fixed';
        modal.style.left = '0';
        modal.style.top = '0';
        modal.style.width = '100%';
        modal.style.height = '100%';
        modal.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
        modal.style.display = 'flex';
        modal.style.justifyContent = 'center';
        modal.style.alignItems = 'center';
        modal.style.zIndex = '1000';

        const modalContent = document.createElement('div');
        modalContent.style.backgroundColor = '#fff';
        modalContent.style.padding = '20px';
        modalContent.style.borderRadius = '5px';
        modalContent.style.maxWidth = '80%';
        modalContent.style.maxHeight = '80%';
        modalContent.style.overflowY = 'auto';

        modalContent.appendChild(createModalContent(links));
        modal.appendChild(modalContent);

        modal.addEventListener('click', (event) => {
            if (event.target === modal) {
                modal.remove();
            }
        });


        document.body.appendChild(modal);
    }


    // Get the current URL
    const currentURL = window.location.pathname + window.location.search;

    // Find the associated links for the current URL
    const matchingLinks = linkMapping.filter((entry) => {
        const regex = new RegExp(entry.pattern);
        const urlMatch = regex.test(currentURL);
        if (!urlMatch) {
            return false;
        }
        if (entry.hasOwnProperty('condition')) {
            return entry.condition();
        }
        return true;
    });


    if (!matchingLinks.length) {
        return;
    }

    // Find the userinfo element
    const userinfo = document.getElementById('userinfo');

    // Find the moon element
    const moonElement = userinfo.querySelector('#howlatthe');

    // Find the moon element parent
    const moonParent = moonElement.parentNode;

    // Insert a dropdown menu
    const dropdown = document.createElement('select');
    dropdown.id = 'custom-link-dropdown';
    dropdown.style.marginRight = '5px';

    // Add a default option
    const defaultOption = document.createElement('option');
    defaultOption.value = '';
    defaultOption.textContent = `${matchingLinks.length} ${matchingLinks.length === 1 ? 'Guide' : 'Guides'}`;
    defaultOption.selected = true;
    defaultOption.disabled = true;
    defaultOption.hidden = true;
    defaultOption.setAttribute('style', 'display: none;');
    dropdown.appendChild(defaultOption);



    // Add an option for each link
    for (const linkData of matchingLinks) {
        const option = document.createElement('option');
        option.value = prependWWWIfNeeded(linkData.url);
        option.textContent = linkData.text;
        dropdown.appendChild(option);
    }

    // Add an event listener to navigate to the selected link
    dropdown.addEventListener('change', (event) => {
        if (event.target.value) {
            window.open(event.target.value, '_blank');
            event.target.selectedIndex = 0;
        }
    });

    // Insert the dropdown before the moon parent
    userinfo.insertBefore(dropdown, moonParent);

    // Add a separator (|) after the new link or dropdown
    const separator = document.createTextNode(' | ');
    userinfo.insertBefore(separator, moonParent);

    // Add custom CSS for the question mark icon
    const customStyle = `
    .question-mark {
        cursor: help;
        margin-right: 5px;
        position: relative;
        display: inline-block;
    }
`;

    // Add the custom style
    const style = document.createElement('style');
    style.textContent = customStyle;
    document.head.appendChild(style);

    // Create the question mark icon
    const questionMark = document.createElement('span');
    questionMark.classList.add('question-mark');
    questionMark.textContent = '?';

    // Add an event listener to trigger an alert box with an explanation when clicked
    questionMark.addEventListener('click', () => {
        showModal(linkMapping);
    });



    // Insert the question mark icon before the dropdown
    if (showQuestionMark) {
        userinfo.insertBefore(questionMark, dropdown);
    }

})();