4chan Session ID Unbreaker

Tries to detect and un-break Session IDs posted on 4chan

// ==UserScript==
// @name         4chan Session ID Unbreaker
// @license      GPLv3
// @namespace    https://boards.4chan.org/
// @version      1.7
// @description  Tries to detect and un-break Session IDs posted on 4chan
// @author       ceodoe
// @match        https://boards.4chan.org/*/thread/*
// @match        https://boards.4chan.org/*/res/*
// @match        https://archived.moe/*/thread/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=4chan.org
// @grant        none
// ==/UserScript==

function parsePosts() {
    let posts = document.querySelectorAll("blockquote.postMessage");

    if(location.href.startsWith("https://archived.moe/")) {
        posts = document.querySelectorAll("article > div.text, div.post_wrapper > div.text");
    }

    for(let i = 0; i < posts.length; i++) {
        if(posts[i].getAttribute("data-4sidu-parsed") !== "1") {
            posts[i].setAttribute("data-4sidu-parsed", "1");

            // Strip all backlinks as they are likely to contain the magic number 05 that all Session IDs start with
            let postText = posts[i].innerText.replace(/\>\>\b[0-9]+\b/g, "");
            let idStartIndex = postText.indexOf("05");

            if(idStartIndex > -1) {
                let id = postText.substring(idStartIndex).replace(/[^A-Fa-f0-9]/g, "").substring(0, 66);

                if(id.length == 66) { // All Session IDs are 66 characters long; if we didn't get that many characters, the ID is invalid
                    let opPost = "";
                    if(posts[i].parentNode.classList.contains("op")) {
                        opPost = "overflow: auto;";
                    }

                    let archivePost = "border-top: 1px solid; width: fit-content;";
                    if(posts[i].parentNode.tagName.toLowerCase() == "article") {
                        archivePost = "";
                    }

                    let html = `
                        <div style="margin-top: 1em; padding: 0.5em; ${archivePost} ${opPost}">
                            <span style="color: #66cc33; font-weight: bold;">Session ID:</span> ${id}
                            <input type="button" class="4sidu-copy-btn" id="4sidu-copy-btn-${i}" data-4sidu-session-id="${id}" style="margin-left: 0.5em;" value="Copy">
                        </div>
                    `;

                    posts[i].insertAdjacentHTML("beforeend", html);

                    document.getElementById(`4sidu-copy-btn-${i}`).addEventListener("click", async function() {
                        let tempInput = document.createElement("input");
                        tempInput.value = this.getAttribute("data-4sidu-session-id");
                        tempInput.select();
                        tempInput.setSelectionRange(0,66);

                        try {
                            await navigator.clipboard.writeText(tempInput.value);
                            this.value = "✓";

                            window.setTimeout(function() {
                                document.getElementById(`4sidu-copy-btn-${i}`).value = "Copy";
                            }, 3000)
                        } catch (err) {
                            alert("Failed to copy to clipboard: " + err);
                        }
                    });
                }
            }
        }
    }
}

new MutationObserver(function(event) { parsePosts(); }).observe(document.querySelector("div.thread"), {childList: true});
parsePosts();