StoryGraph Plus: Search MAM Buttons

Add "Search MAM" buttons to TheStoryGraph book and series pages (Title/Series and Title/Series + Author)

// ==UserScript==
// @name         StoryGraph Plus: Search MAM Buttons
// @namespace    https://greasyfork.org/en/users/1457912
// @version      0.2.1
// @description  Add "Search MAM" buttons to TheStoryGraph book and series pages (Title/Series and Title/Series + Author)
// @author       WilliestWonka
// @match        https://app.thestorygraph.com/books/*
// @match        https://app.thestorygraph.com/series/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const maxRetries = 5;
    let retryCount = 0;
    let retryIntervalId = null;

    function createMamButtons(title, author, isSeries = false) {
        const container = document.createElement("div");
        container.className = "mam-button-container flex mt-2 mb-2 space-x-2 w-full";

        const createButton = (text, url) => {
            const button = document.createElement("a");
            button.href = url;
            button.target = "_blank";
            button.textContent = text;
            button.className =
                "py-2 px-2 border-x-2 border-x-darkGrey dark:border-x-darkerGrey " +
                "border-y border-y-darkGrey dark:border-y-darkerGrey border-b-2 " +
                "bg-grey dark:bg-darkestGrey hover:bg-darkGrey dark:hover:bg-darkerGrey " +
                "inline-block w-full text-center text-xs text-darkerGrey dark:text-lightGrey";
            return button;
        };

        const searchUrl = (query) =>
            `https://www.myanonamouse.net/tor/browse.php?tor[text]=${encodeURIComponent(query)}`;

        if (isSeries) {
            container.appendChild(createButton("Search MAM Series", searchUrl(title)));
            container.appendChild(createButton("Search MAM Series + Author", searchUrl(`${title} ${author}`)));
        } else {
            container.appendChild(createButton("Search MAM Title", searchUrl(title)));
            container.appendChild(createButton("Search MAM Title + Author", searchUrl(`${title} ${author}`)));
        }

        return container;
    }

    function addButtonsIfReady() {
        const pathParts = location.pathname.split('/').filter(Boolean);
        let titleElement, authorElement, isSeries = false;

        if (pathParts[0] === "books") {
            titleElement = document.querySelector("h3.font-serif");
            authorElement = document.querySelector("a[href^='/authors/']");
        } else if (pathParts[0] === "series") {
            isSeries = true;
            titleElement = document.querySelector("h4.page-heading");
            authorElement = document.querySelector("p.font-body a[href^='/authors/']");
        } else {
            return false;
        }

        if (!titleElement || !authorElement) {
            return false;
        }

        const rawTitle = titleElement?.childNodes[0]?.textContent?.trim();
        const author = authorElement?.textContent?.trim();

        if (!rawTitle || !author) return false;

        if (document.querySelector(".mam-button-container")) return true;

        const buttonContainer = createMamButtons(rawTitle, author, isSeries);

        if (isSeries) {
            const flexRow = titleElement.closest("div.flex.justify-between.items-center.px-1");
            if (flexRow && flexRow.parentElement) {
                flexRow.parentElement.insertBefore(buttonContainer, flexRow.nextSibling);
            }
        } else {
            const bookBlock = document.querySelector("div.book-title-author-and-series");
            bookBlock?.appendChild(buttonContainer);
        }

        console.log("[SG+] 'Search MAM' buttons added!");
        return true;
    }

    function startUnifiedRetryLoop() {
        clearInterval(retryIntervalId);
        retryCount = 0;

        retryIntervalId = setInterval(() => {
            if (retryCount >= maxRetries) {
                clearInterval(retryIntervalId);
                console.log("[SG+] Max retries reached, stopping.");
                return;
            }

            if (!document.querySelector(".mam-button-container")) {
                console.log(`[SG+] Buttons removed or missing, re-adding... Retry ${retryCount}`);
                const success = addButtonsIfReady();
                retryCount++;
                if (success) {
                    clearInterval(retryIntervalId);
                }
            }
        }, 1000);
    }

    function setupNavigationListener() {
        const originalPushState = history.pushState;
        history.pushState = function (...args) {
            originalPushState.apply(this, args);
            window.dispatchEvent(new Event("locationchange"));
        };

        const originalReplaceState = history.replaceState;
        history.replaceState = function (...args) {
            originalReplaceState.apply(this, args);
            window.dispatchEvent(new Event("locationchange"));
        };

        window.addEventListener("popstate", () => {
            window.dispatchEvent(new Event("locationchange"));
        });

        window.addEventListener("locationchange", () => {
            setTimeout(() => {
                startUnifiedRetryLoop();
            }, 300); // slight delay after navigation
        });
    }

    window.addEventListener("load", () => {
        console.log("[SG+] Script loaded.");
        startUnifiedRetryLoop();
        setupNavigationListener();
    });
})();