heywey

heywey

Student
Aug 28, 2025
108
There have been a few threads in the suggestions subforum that can be done client-side, plus some other things that annoyed me about the site ui, so I made a little userscript to fix them. It can:
  • hide those useless chat notification bubbles
  • hide the word "suicide" in headers and page titles
  • set maximum signature height
  • prevent gifs in signatures from autoplaying
  • automatically set dark/light mode based on browser/system preference
To use it you'll need a browser extension like Tampermonkey (Chrom[e|ium]) or Violentmonkey (Firefox), then add a new script and copy-paste this in. I'd just upload it to greasyfork but don't really want my account there associated with this site so here we are.

Also this should go without saying but running code that you don't understand from some random guy you don't know is generally a pretty bad idea. It's simple and tiny so it should be pretty easy to follow if you have any programming knowledge, but if you don't, idk, ask AI to walk you through it or something.

JavaScript:
// ==UserScript==
// @name       sasu aio
// @namespace  Violentmonkey Scripts
// @match      https://sanctioned-suicide.net/*
// @grant      none
// @version    3.1
// @author     heywey
// @description
// ==/UserScript==

(function() {
    'use strict';

    //// SETTINGS
    // privacy
    const REPLACEMENT_WORD = "suicide"; // 'suicide' in headers (forum names, etc) gets replaced w this. just set to 'suicide' to disable
    const REMOVE_SITE_NAME = false; // removes " | Sanctioned Suicide" from browser tab title
    // signatures
    const MAX_SIGNATURE_HEIGHT = 150; // max signature height in pixels (overflow gets a scrollbar)
    const PREVENT_GIF_AUTOPLAY = true; // replaces gifs in signatures with black box until hovered with cursor
    // visual tweaks
    const MATCH_BROWSER_THEME = true; // automatically sets site theme to match browser preference (dark/light)
    const HIDE_BADGE_INDICATOR = true; // hides chat notification bubble

    //// UTIL

    function injectCss(css) {
        if (!css) return;
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    function capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    //// MODULES

    function applyReplacementWord() {
        if (REPLACEMENT_WORD.toLowerCase() === 'suicide') return;

        const replacerLower = REPLACEMENT_WORD.toLowerCase();
        const replacerTitle = capitalize(replacerLower);

        // helper to replace text in nodes
        function replaceTextInNode(node) {
            // skip script, style, form elements
            if (['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT'].includes(node.tagName)) return;

            if (node.nodeType === Node.TEXT_NODE) {
                const originalText = node.textContent;
                const newText = originalText
                    .replace(/Suicide/g, replacerTitle)
                    .replace(/suicide/g, replacerLower);

                if (newText !== originalText) {
                    node.textContent = newText;
                }
                return;
            }

            if (node.nodeType === Node.ELEMENT_NODE && !node.isContentEditable) {
                for (let i = 0; i < node.childNodes.length; i++) {
                    replaceTextInNode(node.childNodes[i]);
                }
            }
        }

        // replace in headers
        const headers = document.querySelectorAll('h1, h2, h3, h4, h5');
        headers.forEach(header => replaceTextInNode(header));

        // replace in title
        let newTitle = document.title;
        newTitle = newTitle
            .replace(/Suicide/g, replacerTitle)
            .replace(/suicide/g, replacerLower);

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyRemoveSiteName() {
        if (!REMOVE_SITE_NAME) return;

        // removes " | Sanctioned Suicide" and any surrounding whitespace/pipes
        const newTitle = document.title.replace(/[\s|]*Sanctioned Suicide/gi, '');

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyMaxSignatureHeight() {
        if (MAX_SIGNATURE_HEIGHT <= 0) return;

        injectCss(`
            .message-signature {
                max-height: ${MAX_SIGNATURE_HEIGHT}px !important;
                overflow-y: auto !important;
                overflow-x: hidden !important;
            }
        `);
    }

    function applyPreventGifAutoplay() {
        if (!PREVENT_GIF_AUTOPLAY) return;

        // target images that look like gifs based on src, data-url, alt attributes
        const gifSelectors = [
            '.message-signature img[src*=".gif"]',
            '.message-signature img[data-url*=".gif"]',
            '.message-signature img[alt*=".gif"]'
        ].join(', ');

        const hoverSelectors = gifSelectors.split(', ').map(s => s + ':hover').join(', ');

        // set wrapper background to black and handle opacity
        injectCss(`
            .message-signature .bbImageWrapper {
                background-color: #000 !important;
                display: inline-block !important;
            }
            ${gifSelectors} {
                opacity: 0 !important;
            }
            /* show on hover */
            ${hoverSelectors} {
                opacity: 1 !important;
            }
        `);
    }

    function applyMatchBrowserTheme() {
        if (!MATCH_BROWSER_THEME) return;

        // check if browser supports media queries and site object exists
        if (window.matchMedia && typeof themehouse !== 'undefined' && themehouse.styleSwitch) {
            const isOsDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
            const targetMode = isOsDark ? 'dark' : 'light';

            // attempt to detect current site mode to avoid redundant switches
            let currentMode;

            // check internal property if available
            if (themehouse.styleSwitch.mode) {
                currentMode = themehouse.styleSwitch.mode;
            }
            // fallback: check html classes/attributes
            else {
                const html = document.documentElement;
                const isSiteDark = html.classList.contains('theme--dark') ||
                                   html.classList.contains('uix_dark') ||
                                   html.dataset.theme === 'dark';
                currentMode = isSiteDark ? 'dark' : 'light';
            }

            // only switch if the modes don't match
            if (currentMode !== targetMode) {
                themehouse.styleSwitch.switchStyle(targetMode);
            }
        }
    }

    function applyHideBadgeIndicator() {
        if (!HIDE_BADGE_INDICATOR) return;

        injectCss(`
            .badgeContainer::after {
                content: none !important;
            }
            .badge {
              display: none !important;
            }
        `);
    }

    //// INIT

    function init() {
        // privacy
        applyRemoveSiteName();
        applyReplacementWord();

        // signatures
        applyMaxSignatureHeight();
        applyPreventGifAutoplay();

        // visual tweaks
        applyMatchBrowserTheme();
        applyHideBadgeIndicator();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
 
Last edited:
  • Like
  • Informative
  • Love
Reactions: boddibo, Rust, gunmetalblue and 9 others
H

Hvergelmir

Wizard
May 5, 2024
687
Thanks for sharing this.

The script is safe. There's really no room for hidden malicious behavior.
 
  • Love
Reactions: heywey
heywey

heywey

Student
Aug 28, 2025
108
Added setting to show the "jump to new" at the top of every thread. Also fixed the overly broad notification bubble selector, whoops.

JavaScript:
// ==UserScript==
// @name       sasu aio
// @namespace  Violentmonkey Scripts
// @match      https://sanctioned-suicide.net/*
// @grant      none
// @version    4
// @author     heywey
// @description
// ==/UserScript==

(function() {
    'use strict';

    //// SETTINGS
    // privacy
    const REPLACEMENT_WORD = "suicide"; // 'suicide' in headers (forum names, etc) gets replaced w this. just set to 'suicide' to disable
    const REMOVE_SITE_NAME = false; // removes " | Sanctioned Suicide" from browser tab title
    // signatures
    const MAX_SIGNATURE_HEIGHT = 150; // max signature height in pixels (overflow gets a scrollbar)
    const PREVENT_GIF_AUTOPLAY = true; // replaces gifs in signatures with black box until hovered with cursor
    // visual tweaks
    const MATCH_BROWSER_THEME = true; // automatically sets site theme to match browser preference (dark/light)
    const HIDE_BADGE_INDICATOR = true; // hides chat notification bubble
    // navigation
    const ADD_JUMP_TO_NEW = true; // adds "Jump to new" button to threads where it is missing (links to latest post)

    //// UTIL

    function injectCss(css) {
        if (!css) return;
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    function capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    //// MODULES

    function applyReplacementWord() {
        if (REPLACEMENT_WORD.toLowerCase() === 'suicide') return;

        const replacerLower = REPLACEMENT_WORD.toLowerCase();
        const replacerTitle = capitalize(replacerLower);

        // helper to replace text in nodes
        function replaceTextInNode(node) {
            // skip script, style, form elements
            if (['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT'].includes(node.tagName)) return;

            if (node.nodeType === Node.TEXT_NODE) {
                const originalText = node.textContent;
                const newText = originalText
                    .replace(/Suicide/g, replacerTitle)
                    .replace(/suicide/g, replacerLower);

                if (newText !== originalText) {
                    node.textContent = newText;
                }
                return;
            }

            if (node.nodeType === Node.ELEMENT_NODE && !node.isContentEditable) {
                for (let i = 0; i < node.childNodes.length; i++) {
                    replaceTextInNode(node.childNodes[i]);
                }
            }
        }

        // replace in headers
        const headers = document.querySelectorAll('h1, h2, h3, h4, h5');
        headers.forEach(header => replaceTextInNode(header));

        // replace in title
        let newTitle = document.title;
        newTitle = newTitle
            .replace(/Suicide/g, replacerTitle)
            .replace(/suicide/g, replacerLower);

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyRemoveSiteName() {
        if (!REMOVE_SITE_NAME) return;

        // removes " | Sanctioned Suicide" and any surrounding whitespace/pipes
        const newTitle = document.title.replace(/[\s|]*Sanctioned Suicide/gi, '');

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyMaxSignatureHeight() {
        if (MAX_SIGNATURE_HEIGHT <= 0) return;

        injectCss(`
            .message-signature {
                max-height: ${MAX_SIGNATURE_HEIGHT}px !important;
                overflow-y: auto !important;
                overflow-x: hidden !important;
            }
        `);
    }

    function applyPreventGifAutoplay() {
        if (!PREVENT_GIF_AUTOPLAY) return;

        // target images that look like gifs based on src, data-url, alt attributes
        const gifSelectors = [
            '.message-signature img[src*=".gif"]',
            '.message-signature img[data-url*=".gif"]',
            '.message-signature img[alt*=".gif"]'
        ].join(', ');

        const hoverSelectors = gifSelectors.split(', ').map(s => s + ':hover').join(', ');

        // set wrapper background to black and handle opacity
        injectCss(`
            .message-signature .bbImageWrapper {
                background-color: #000 !important;
                display: inline-block !important;
            }
            ${gifSelectors} {
                opacity: 0 !important;
            }
            /* show on hover */
            ${hoverSelectors} {
                opacity: 1 !important;
            }
        `);
    }

    function applyMatchBrowserTheme() {
        if (!MATCH_BROWSER_THEME) return;

        // check if browser supports media queries and site object exists
        if (window.matchMedia && typeof themehouse !== 'undefined' && themehouse.styleSwitch) {
            const isOsDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
            const targetMode = isOsDark ? 'dark' : 'light';

            // attempt to detect current site mode to avoid redundant switches
            let currentMode;

            // check internal property if available
            if (themehouse.styleSwitch.mode) {
                currentMode = themehouse.styleSwitch.mode;
            }
            // fallback: check html classes/attributes
            else {
                const html = document.documentElement;
                const isSiteDark = html.classList.contains('theme--dark') ||
                                   html.classList.contains('uix_dark') ||
                                   html.dataset.theme === 'dark';
                currentMode = isSiteDark ? 'dark' : 'light';
            }

            // only switch if the modes don't match
            if (currentMode !== targetMode) {
                themehouse.styleSwitch.switchStyle(targetMode);
            }
        }
    }

    function applyHideBadgeIndicator() {
        if (!HIDE_BADGE_INDICATOR) return;

        injectCss(`
            a.p-navgroup-link--chat.badgeContainer.badgeContainer--highlighted::after {
                content: none !important;
            }
            .badge {
              display: none !important;
            }
        `);
    }

    function applyJumpToNew() {
        if (!ADD_JUMP_TO_NEW) return;

        // only run if we are in a thread
        if (!window.location.pathname.includes('/threads/')) return;

        // extract the canonical base url of the thread (e.g., /threads/name.123/)
        // we look for the pattern /threads/title.id/
        const match = window.location.pathname.match(/(\/threads\/[^/]+\.\d+\/)/);
        if (!match) return;

        const threadBaseUrl = match[1];
        // appending 'latest' to a xenforo thread url automatically redirects to the last post
        const targetUrl = threadBaseUrl + 'latest';

        // find all button groups in the outer blocks (usually one at top, one at bottom)
        const buttonGroups = document.querySelectorAll('.block-outer-opposite .buttonGroup');

        buttonGroups.forEach(group => {
            // check if a "jump to new" button already exists here
            // the native button usually has 'unread' in the href or the specific text
            const hasButton = Array.from(group.children).some(child =>
                child.textContent.trim() === 'Jump to new' ||
                (child.tagName === 'A' && child.href.includes('unread'))
            );

            if (!hasButton) {
                // create the button
                const btn = document.createElement('a');
                btn.className = 'button--link button rippleButton';
                btn.href = targetUrl;
                btn.innerHTML = '<span class="button-text">Jump to new</span>';

                // insert at the beginning of the group (standard position)
                group.insertBefore(btn, group.firstChild);
            }
        });
    }

    //// INIT

    function init() {
        // privacy
        applyRemoveSiteName();
        applyReplacementWord();

        // signatures
        applyMaxSignatureHeight();
        applyPreventGifAutoplay();

        // visual tweaks
        applyMatchBrowserTheme();
        applyHideBadgeIndicator();

        // navigation
        applyJumpToNew();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
 
Last edited:
  • Informative
  • Like
Reactions: Surek and Forveleth
heywey

heywey

Student
Aug 28, 2025
108
Added a filter for 'similar threads' per https://sanctioned-suicide.net/thre...forum-games-to-only-other-forum-games.223621/

I can't rework the recommendation algorithm of course, so it just removes any posts that aren't from the current subforum.

Oh yeah, also removed the auto light/dark mode because it didn't work quite right and I can't figure out how to fix it.

JavaScript:
// ==UserScript==
// @name       sasu aio
// @namespace  Violentmonkey Scripts
// @match      https://sanctioned-suicide.net/*
// @grant      none
// @version    5
// @author     heywey
// @description
// ==/UserScript==

(function() {
    'use strict';

    //// SETTINGS
    // privacy
    const REPLACEMENT_WORD = "suicide"; // 'suicide' in headers (forum names, etc) gets replaced w this. just set to 'suicide' to disable
    const REMOVE_SITE_NAME = false; // removes " | Sanctioned Suicide" from browser tab title
    // signatures
    const MAX_SIGNATURE_HEIGHT = 150; // max signature height in pixels (overflow gets a scrollbar)
    const PREVENT_GIF_AUTOPLAY = true; // replaces gifs in signatures with black box until hovered with cursor
    // visual tweaks
    const HIDE_BADGE_INDICATOR = true; // hides chat notification bubble
    const HIDE_FOREIGN_SUGGESTIONS = true; // hides suggested threads that are not from the current subforum
    // navigation
    const ADD_JUMP_TO_NEW = true; // adds "Jump to new" button to threads where it is missing (links to latest post)

    //// UTIL

    function injectCss(css) {
        if (!css) return;
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    function capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    //// MODULES

    function applyReplacementWord() {
        if (REPLACEMENT_WORD.toLowerCase() === 'suicide') return;

        const replacerLower = REPLACEMENT_WORD.toLowerCase();
        const replacerTitle = capitalize(replacerLower);

        // helper to replace text in nodes
        function replaceTextInNode(node) {
            // skip script, style, form elements
            if (['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT'].includes(node.tagName)) return;

            if (node.nodeType === Node.TEXT_NODE) {
                const originalText = node.textContent;
                const newText = originalText
                    .replace(/Suicide/g, replacerTitle)
                    .replace(/suicide/g, replacerLower);

                if (newText !== originalText) {
                    node.textContent = newText;
                }
                return;
            }

            if (node.nodeType === Node.ELEMENT_NODE && !node.isContentEditable) {
                for (let i = 0; i < node.childNodes.length; i++) {
                    replaceTextInNode(node.childNodes[i]);
                }
            }
        }

        // replace in headers
        const headers = document.querySelectorAll('h1, h2, h3, h4, h5');
        headers.forEach(header => replaceTextInNode(header));

        // replace in title
        let newTitle = document.title;
        newTitle = newTitle
            .replace(/Suicide/g, replacerTitle)
            .replace(/suicide/g, replacerLower);

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyRemoveSiteName() {
        if (!REMOVE_SITE_NAME) return;

        // removes " | Sanctioned Suicide" and any surrounding whitespace/pipes
        const newTitle = document.title.replace(/[\s|]*Sanctioned Suicide/gi, '');

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyMaxSignatureHeight() {
        if (MAX_SIGNATURE_HEIGHT <= 0) return;

        injectCss(`
            .message-signature {
                max-height: ${MAX_SIGNATURE_HEIGHT}px !important;
                overflow-y: auto !important;
                overflow-x: hidden !important;
            }
        `);
    }

    function applyPreventGifAutoplay() {
        if (!PREVENT_GIF_AUTOPLAY) return;

        // target images that look like gifs based on src, data-url, alt attributes
        const gifSelectors = [
            '.message-signature img[src*=".gif"]',
            '.message-signature img[data-url*=".gif"]',
            '.message-signature img[alt*=".gif"]'
        ].join(', ');

        const hoverSelectors = gifSelectors.split(', ').map(s => s + ':hover').join(', ');

        // set wrapper background to black and handle opacity
        injectCss(`
            .message-signature .bbImageWrapper {
                background-color: #000 !important;
                display: inline-block !important;
            }
            ${gifSelectors} {
                opacity: 0 !important;
            }
            /* show on hover */
            ${hoverSelectors} {
                opacity: 1 !important;
            }
        `);
    }

    function applyHideBadgeIndicator() {
        if (!HIDE_BADGE_INDICATOR) return;

        injectCss(`
            a.p-navgroup-link--chat.badgeContainer.badgeContainer--highlighted::after {
                content: none !important;
            }
            .badge {
              display: none !important;
            }
        `);
    }

    function applyHideForeignSuggestions() {
        if (!HIDE_FOREIGN_SUGGESTIONS) return;

        // find similar threads widget
        const widget = document.querySelector('[data-widget-key="xfes_thread_view_below_quick_reply_similar_threads"]');
        if (!widget) return;

        // get current subforum by finding last link in the breadcrumb that points to a forum
        const breadcrumbLinks = Array.from(document.querySelectorAll('.p-breadcrumbs a'));
        const forumLinks = breadcrumbLinks.filter(a => a.href.includes('/forums/'));

        if (forumLinks.length === 0) return;

        // the last forum link in the chain is the current subforum
        const currentSubforumName = forumLinks[forumLinks.length - 1].textContent.trim();

        // iterate over suggested threads
        const threads = widget.querySelectorAll('.structItem--thread');

        threads.forEach(thread => {
            let threadSubforumName = null;

            // try to find the subforum link in the meta parts (standard location)
            const metaLinks = thread.querySelectorAll('.structItem-parts > li > a');
            for (const link of metaLinks) {
                // checks if href points to a forum
                if (link.href.includes('/forums/')) {
                    threadSubforumName = link.textContent.trim();
                    break;
                }
            }

            // if we found a subforum name and it doesn't match the current breadcrumb, remove it
            if (threadSubforumName && threadSubforumName !== currentSubforumName) {
                thread.remove();
            }
        });
    }

    function applyJumpToNew() {
        if (!ADD_JUMP_TO_NEW) return;

        // only run if we are in a thread
        if (!window.location.pathname.includes('/threads/')) return;

        // extract the canonical base url of the thread (e.g., /threads/name.123/)
        // we look for the pattern /threads/title.id/
        const match = window.location.pathname.match(/(\/threads\/[^/]+\.\d+\/)/);
        if (!match) return;

        const threadBaseUrl = match[1];
        // appending 'latest' to a xenforo thread url automatically redirects to the last post
        const targetUrl = threadBaseUrl + 'latest';

        // find all button groups in the outer blocks (usually one at top, one at bottom)
        const buttonGroups = document.querySelectorAll('.block-outer-opposite .buttonGroup');

        buttonGroups.forEach(group => {
            // check if a "jump to new" button already exists here
            // the native button usually has 'unread' in the href or the specific text
            const hasButton = Array.from(group.children).some(child =>
                child.textContent.trim() === 'Jump to new' ||
                (child.tagName === 'A' && child.href.includes('unread'))
            );

            if (!hasButton) {
                // create the button
                const btn = document.createElement('a');
                btn.className = 'button--link button rippleButton';
                btn.href = targetUrl;
                btn.innerHTML = '<span class="button-text">Jump to new</span>';

                // insert at the beginning of the group (standard position)
                group.insertBefore(btn, group.firstChild);
            }
        });
    }

    //// INIT

    function init() {
        // privacy
        applyRemoveSiteName();
        applyReplacementWord();

        // signatures
        applyMaxSignatureHeight();
        applyPreventGifAutoplay();

        // visual tweaks
        applyHideBadgeIndicator();
        applyHideForeignSuggestions();

        // navigation
        applyJumpToNew();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
 
  • Like
Reactions: Surek and EmptyBottle
U. A.

U. A.

"Ultra Based Gigachad"
Aug 8, 2022
2,323
You are doing the admin's lord's work.
 
  • Love
Reactions: heywey
heywey

heywey

Student
Aug 28, 2025
108
Added normalize chat colors from https://sanctioned-suicide.net/threads/option-to-view-all-chat-messages-in-a-default-color.223753/
does what it says on the tin, chat message contents are always normal color

Also keyword filter as suggested in https://sanctioned-suicide.net/threads/keyword-list-that-auto-hides-content-containing-them.218025/

Feel free to @ with issues or whatever.

JavaScript:
// ==UserScript==
// @name       sasu aio
// @namespace  Violentmonkey Scripts
// @match      https://sanctioned-suicide.net/*
// @grant      none
// @version    7
// @author     heywey
// @description
// ==/UserScript==

(function() {
    'use strict';

    //// SETTINGS
    // privacy
    const REPLACEMENT_WORD = "suicide"; // 'suicide' in headers (forum names, etc) gets replaced w this. just set to 'suicide' to disable
    const REMOVE_SITE_NAME = false; // removes " | Sanctioned Suicide" from browser tab title
    // signatures
    const MAX_SIGNATURE_HEIGHT = 150; // max signature height in pixels (overflow gets a scrollbar)
    const PREVENT_GIF_AUTOPLAY = true; // replaces gifs in signatures with black box until hovered with cursor
    // visual tweaks
    const HIDE_BADGE_INDICATOR = true; // hides chat notification bubble
    const HIDE_FOREIGN_SUGGESTIONS = true; // hides suggested threads that are not from the current subforum
    const NORMALIZE_CHAT_COLOR = true; // removes cutom colors in chat. all message text becomes default color
    // navigation
    const ADD_JUMP_TO_NEW = true; // adds "Jump to new" button to threads where it is missing (links to latest post)
    // content filtering
    const FILTER_KEYWORDS = []; // example: ["keyword1", "keyword2"]
    const FILTER_THREADS = true; // remove threads containing keywords in title
    const FILTER_POSTS = true; // remove individual posts containing keywords

    //// UTIL

    function injectCss(css) {
        if (!css) return;
        const style = document.createElement('style');
        style.textContent = css;
        document.head.appendChild(style);
    }

    function capitalize(str) {
        return str.charAt(0).toUpperCase() + str.slice(1);
    }

    //// MODULES

    function applyReplacementWord() {
        if (REPLACEMENT_WORD.toLowerCase() === 'suicide') return;

        const replacerLower = REPLACEMENT_WORD.toLowerCase();
        const replacerTitle = capitalize(replacerLower);

        // helper to replace text in nodes
        function replaceTextInNode(node) {
            // skip script, style, form elements
            if (['SCRIPT', 'STYLE', 'TEXTAREA', 'INPUT'].includes(node.tagName)) return;

            if (node.nodeType === Node.TEXT_NODE) {
                const originalText = node.textContent;
                const newText = originalText
                    .replace(/Suicide/g, replacerTitle)
                    .replace(/suicide/g, replacerLower);

                if (newText !== originalText) {
                    node.textContent = newText;
                }
                return;
            }

            if (node.nodeType === Node.ELEMENT_NODE && !node.isContentEditable) {
                for (let i = 0; i < node.childNodes.length; i++) {
                    replaceTextInNode(node.childNodes[i]);
                }
            }
        }

        // replace in headers
        const headers = document.querySelectorAll('h1, h2, h3, h4, h5');
        headers.forEach(header => replaceTextInNode(header));

        // replace in title
        let newTitle = document.title;
        newTitle = newTitle
            .replace(/Suicide/g, replacerTitle)
            .replace(/suicide/g, replacerLower);

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyRemoveSiteName() {
        if (!REMOVE_SITE_NAME) return;

        // removes " | Sanctioned Suicide" and any surrounding whitespace/pipes
        const newTitle = document.title.replace(/[\s|]*Sanctioned Suicide/gi, '');

        if (newTitle !== document.title) {
            document.title = newTitle;
        }
    }

    function applyMaxSignatureHeight() {
        if (MAX_SIGNATURE_HEIGHT <= 0) return;

        injectCss(`
            .message-signature {
                max-height: ${MAX_SIGNATURE_HEIGHT}px !important;
                overflow-y: auto !important;
                overflow-x: hidden !important;
            }
        `);
    }

    function applyPreventGifAutoplay() {
        if (!PREVENT_GIF_AUTOPLAY) return;

        // target images that look like gifs based on src, data-url, alt attributes
        const gifSelectors = [
            '.message-signature img[src*=".gif"]',
            '.message-signature img[data-url*=".gif"]',
            '.message-signature img[alt*=".gif"]'
        ].join(', ');

        const hoverSelectors = gifSelectors.split(', ').map(s => s + ':hover').join(', ');

        // set wrapper background to black and handle opacity
        injectCss(`
            .message-signature .bbImageWrapper {
                background-color: #000 !important;
                display: inline-block !important;
            }
            ${gifSelectors} {
                opacity: 0 !important;
            }
            /* show on hover */
            ${hoverSelectors} {
                opacity: 1 !important;
            }
        `);
    }

    function applyHideBadgeIndicator() {
        if (!HIDE_BADGE_INDICATOR) return;

        injectCss(`
            a.p-navgroup-link--chat.badgeContainer.badgeContainer--highlighted::after {
                content: none !important;
            }
            .badge {
              display: none !important;
            }
        `);
    }

    function applyHideForeignSuggestions() {
        if (!HIDE_FOREIGN_SUGGESTIONS) return;

        // find similar threads widget
        const widget = document.querySelector('[data-widget-key="xfes_thread_view_below_quick_reply_similar_threads"]');
        if (!widget) return;

        // get current subforum by finding last link in the breadcrumb that points to a forum
        const breadcrumbLinks = Array.from(document.querySelectorAll('.p-breadcrumbs a'));
        const forumLinks = breadcrumbLinks.filter(a => a.href.includes('/forums/'));

        if (forumLinks.length === 0) return;

        // the last forum link in the chain is the current subforum
        const currentSubforumName = forumLinks[forumLinks.length - 1].textContent.trim();

        // iterate over suggested threads
        const threads = widget.querySelectorAll('.structItem--thread');

        threads.forEach(thread => {
            let threadSubforumName = null;

            // try to find the subforum link in the meta parts (standard location)
            const metaLinks = thread.querySelectorAll('.structItem-parts > li > a');
            for (const link of metaLinks) {
                // checks if href points to a forum
                if (link.href.includes('/forums/')) {
                    threadSubforumName = link.textContent.trim();
                    break;
                }
            }

            // if we found a subforum name and it doesn't match the current breadcrumb, remove it
            if (threadSubforumName && threadSubforumName !== currentSubforumName) {
                thread.remove();
            }
        });
    }

    function applyNormalizeChatColor() {
      if (!NORMALIZE_CHAT_COLOR) return;

      injectCss(`
          .siropuChatMessageText .fixed-color {
              color: inherit !important;
          }
      `);
    }

    function applyJumpToNew() {
        if (!ADD_JUMP_TO_NEW) return;

        // only run if we are in a thread
        if (!window.location.pathname.includes('/threads/')) return;

        // extract the canonical base url of the thread (e.g., /threads/name.123/)
        // we look for the pattern /threads/title.id/
        const match = window.location.pathname.match(/(\/threads\/[^/]+\.\d+\/)/);
        if (!match) return;

        const threadBaseUrl = match[1];
        // appending 'latest' to a xenforo thread url automatically redirects to the last post
        const targetUrl = threadBaseUrl + 'latest';

        // find all button groups in the outer blocks (usually one at top, one at bottom)
        const buttonGroups = document.querySelectorAll('.block-outer-opposite .buttonGroup');

        buttonGroups.forEach(group => {
            // check if a "jump to new" button already exists here
            // the native button usually has 'unread' in the href or the specific text
            const hasButton = Array.from(group.children).some(child =>
                child.textContent.trim() === 'Jump to new' ||
                (child.tagName === 'A' && child.href.includes('unread'))
            );

            if (!hasButton) {
                // create the button
                const btn = document.createElement('a');
                btn.className = 'button--link button rippleButton';
                btn.href = targetUrl;
                btn.innerHTML = '<span class="button-text">Jump to new</span>';

                // insert at the beginning of the group (standard position)
                group.insertBefore(btn, group.firstChild);
            }
        });
    }

    function applyKeywordFilter() {
        if (!FILTER_KEYWORDS.length) return;

        // create regex from keywords, escaping special chars
        const pattern = new RegExp(FILTER_KEYWORDS.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'i');

        if (FILTER_THREADS) {
            // standard thread list items
            const threads = document.querySelectorAll('.structItem--thread');
            threads.forEach(thread => {
                const title = thread.querySelector('.structItem-title');
                if (title && pattern.test(title.textContent)) {
                    thread.remove();
                }
            });

            // forum previews
            const extras = document.querySelectorAll('.node-extra');
            extras.forEach(extra => {
                const title = extra.querySelector('.node-extra-title');
                if (title && pattern.test(title.textContent)) {
                    extra.remove();
                }
            });
        }

        if (FILTER_POSTS) {
            const posts = document.querySelectorAll('.message--post');
            posts.forEach(post => {
                const content = post.querySelector('.bbWrapper');
                if (content && pattern.test(content.textContent)) {
                    post.remove();
                }
            });
        }
    }

    //// INIT

    function init() {
        // privacy
        applyRemoveSiteName();
        applyReplacementWord();

        // signatures
        applyMaxSignatureHeight();
        applyPreventGifAutoplay();

        // visual tweaks
        applyHideBadgeIndicator();
        applyHideForeignSuggestions();
        applyNormalizeChatColor();

        // navigation
        applyJumpToNew();

        // filtering
        applyKeywordFilter();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();
 
Last edited:
  • Love
  • Informative
  • Like
Reactions: boddibo, Surek and U. A.
U. A.

U. A.

"Ultra Based Gigachad"
Aug 8, 2022
2,323
Can a mod pin this or something? If admin isn't gonna even give us the changes that would be fucking options then ffs the least they can do is help people who want to make them now and in the future.
 
  • Like
Reactions: nobodycaresaboutme
Pluto

Pluto

Cat Extremist
Dec 27, 2020
6,143
images
 
  • Love
Reactions: heywey, Surek and Praestat_Mori
cme-dme

cme-dme

wants to sleep forever
Feb 1, 2025
532
Holy crap you're incredible. This as well as the Load Chat History userscript are absolute must haves for this forum!
PS: You should add an @match for the site mirror sanctionedsuicide.site
 
Last edited:
  • Love
Reactions: U. A.

Similar threads

DarkRange55
Replies
0
Views
530
Offtopic
DarkRange55
DarkRange55
leloyon
Replies
26
Views
3K
Offtopic
EmptyBottle
EmptyBottle
goodoldnoname923
Replies
45
Views
6K
Suicide Discussion
Eternal Eyes
Eternal Eyes
bed
Replies
13
Views
5K
Offtopic
AreWeWinning
AreWeWinning
Oathkeeper
Replies
13
Views
2K
Suicide Discussion
Umacon
U