// ==UserScript== // @name 4chan - YouTube Playlist // @description Wraps all YouTube videos inside a thread into a playlist // @namespace 4chan-yt-playlist // @version 2.4.9 // @include https://boards.4chan.org/*/thread/* // @include https://warosu.org/*/thread/* // @run-at document-start // @grant GM_setValue // @grant GM_getValue // ==/UserScript== const C = new class Conf { constructor() { this.initFinished = false; this.fourchan = location.hostname === "boards.4chan.org"; this.warosu = location.hostname === "warosu.org"; this.native = true; this.fixedNav = false; this.classicNav = false; this.autohideNav = false; this.fourchanX = false; const pathname = location.pathname.slice(1).split("/thread/"); this.board = pathname[0]; this.thread = pathname[1]; if (this.warosu) this.initFinished = true; document.addEventListener("4chanMainInit", () => { this.native = !unsafeWindow.Config.disableAll; this.fixedNav = unsafeWindow.Config.dropDownNav; this.classicNav = unsafeWindow.Config.classicNav; this.autohideNav = unsafeWindow.Config.autoHideNav; this.initFinished = true; }); document.addEventListener("4chanXInitFinished", () => { this.fourchanX = true; }); } get fixedHeader() { if (!this.fourchanX) return false; return document.documentElement.classList.contains("fixed"); } get autohideHeader() { if (!this.fourchanX) return false; return document.documentElement.classList.contains("autohide"); } }; class Dialog { constructor(playlist) { this.dragging = false; this.snapshot = { size: [0, 0], cursor: [0, 0], topbar: [0, 0, false, false] }; document.addEventListener("DOMContentLoaded", () => { document.body.insertAdjacentHTML("beforeend", "<div id=\"playlist-embed\" class=\"dialog hide\"><div><a class=\"reload\" href=\"javascript:;\" title=\"Reload playlist\"><svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><path d=\"M370.7 133.3C339.5 104 298.9 88 255.8 88c-77.5 .1-144.3 53.2-162.8 126.9-1.3 5.4-6.1 9.2-11.7 9.2H24.1c-7.5 0-13.2-6.8-11.8-14.2C33.9 94.9 134.8 8 256 8c66.4 0 126.8 26.1 171.3 68.7L463 41C478.1 25.9 504 36.6 504 57.9V192c0 13.3-10.7 24-24 24H345.9c-21.4 0-32.1-25.9-17-41l41.8-41.7zM32 296h134.1c21.4 0 32.1 25.9 17 41l-41.8 41.8c31.3 29.3 71.8 45.3 114.9 45.3 77.4-.1 144.3-53.1 162.8-126.8 1.3-5.4 6.1-9.2 11.7-9.2h57.3c7.5 0 13.2 6.8 11.8 14.2C478.1 417.1 377.2 504 256 504c-66.4 0-126.8-26.1-171.3-68.7L49 471C33.9 486.1 8 475.4 8 454.1V320c0-13.3 10.7-24 24-24z\" fill=\"currentColor\" /></svg></a><ul class=\"tabs\"></ul><div class=\"move\"></div><a class=\"jump\" href=\"javascript:;\" title=\"Jump to post\">→</a><a class=\"close\" href=\"javascript:;\" title=\"Close\">×</a></div><div id=\"playlist\"></div></div>"); const tabs = this.self?.querySelector("ul"); const reload = this.self?.querySelector(".reload"); const move = this.self?.querySelector(".move"); const jump = this.self?.querySelector(".jump"); const close = this.self?.querySelector(".close"); tabs.addEventListener("click", switchTab); reload.addEventListener("click", reloadPlaylist); move.addEventListener("mousedown", toggleDrag); jump.addEventListener("click", () => { playlist.jumpTo(playlist.track); }); close.addEventListener("click", () => { this.toggle(true); }); document.addEventListener("mouseup", toggleDrag); switch (true) { case C.fourchan: setPos(GM_getValue("4chan Dialog Coordinates", ["10%", "5%", null, null])); break; case C.warosu: setPos(GM_getValue("Warosu Dialog Coordinates", ["10%", "5%", null, null])); break; } function reloadPlaylist(e) { const icon = e.currentTarget?.firstElementChild; icon?.classList.add("spin"); return playlist.reload(); } }); document.addEventListener("DOMContentLoaded", () => { switch (true) { case C.fourchan: document.getElementById("navtopright")?.insertAdjacentHTML("afterbegin", "[<a class=\"playlist-toggle native\" href=\"javascript:;\" title=\"Toggle YouTube playlist\">Playlist</a>] "); document.getElementById("navbotright")?.insertAdjacentHTML("afterbegin", "[<a class=\"playlist-toggle native\" href=\"javascript:;\" title=\"Toggle YouTube playlist\">Playlist</a>] "); document.addEventListener("4chanParsingDone", () => { document.getElementById("settingsWindowLinkClassic")?.insertAdjacentHTML("beforebegin", "<a class=\"playlist-toggle native\" href=\"javascript:;\" title=\"Toggle YouTube playlist\">Playlist</a>"); document.getElementById("settingsWindowLinkMobile")?.insertAdjacentHTML("beforebegin", "<a class=\"playlist-toggle native\" href=\"javascript:;\" title=\"Toggle YouTube playlist\">Playlist</a> "); ["settingsWindowLinkClassic", "settingsWindowLinkMobile"].forEach((id) => { (document.getElementById(id)?.querySelector(".playlist-toggle")).onclick = initOrToggle; }); }); ["navtopright", "navbotright"].forEach((id) => { (document.getElementById(id)?.querySelector(".playlist-toggle")).onclick = initOrToggle; }); break; case C.warosu: const lastLink = document.getElementById("p" + C.thread)?.querySelector("a:last-of-type"); lastLink?.nextElementSibling?.insertAdjacentHTML("beforebegin", "[<a class=\"playlist-toggle\" href=\"javascript:;\" title=\"Toggle YouTube playlist\">Playlist</a>]"); document.querySelector(".playlist-toggle").onclick = initOrToggle; break; } }); document.addEventListener("4chanXInitFinished", () => { document.getElementById("shortcut-qr")?.insertAdjacentHTML("beforebegin", "<span id=\"shortcut-playlist\" class=\"shortcut brackets-wrap\"><a class=\"playlist-toggle disabled\" title=\"Toggle YouTube playlist\" href=\"javascript:;\"><span class=\"icon--alt-text\">YouTube Playlist</span><svg class=\"icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><path d=\"M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 394c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V192h416v234z\" fill=\"currentColor\" /></svg></a></span>"); document.querySelector(".playlist-toggle:not(.native)").onclick = initOrToggle; }); function switchTab(e) { if (!playlist.player) return; if (!e.target?.dataset.page) return; const index = e.target.dataset.page || "0"; playlist.player.cuePlaylist(playlist.toPages()[parseInt(index)]); } function initOrToggle() { if (playlist.checking || playlist.isEmpty()) return; if (!playlist.player) return initAPI(); return playlist.dialog?.toggle(); } function toggleDrag(e) { if (!playlist.dialog.self) return; switch (e.type) { case "mouseup": if (!playlist.dialog.dragging) return; playlist.dialog.dragging = false; document.removeEventListener("mousemove", moveDialog); GM_setValue((C.fourchan ? "4chan" : "Warosu") + " Dialog Coordinates", [ playlist.dialog.self.style.top, playlist.dialog.self.style.right, playlist.dialog.self.style.bottom, playlist.dialog.self.style.left ]); break; case "mousedown": if (e.button !== 0) return; e.preventDefault(); const rect = playlist.dialog.self.getBoundingClientRect(); playlist.dialog.snapshot.size[0] = rect.width; playlist.dialog.snapshot.size[1] = rect.height; playlist.dialog.snapshot.cursor[0] = e.x - rect.x; playlist.dialog.snapshot.cursor[1] = e.y - rect.y; if (C.fixedNav || C.fixedHeader) { const topbar = document.getElementById(C.fourchanX ? "header-bar" : C.classicNav ? "boardNavDesktop" : "boardNavMobile"); if (topbar) { playlist.dialog.snapshot.topbar[0] = topbar.getBoundingClientRect().y || 0; playlist.dialog.snapshot.topbar[1] = topbar.offsetHeight || 0; playlist.dialog.snapshot.topbar[2] = C.fourchanX ? C.fixedHeader : C.fixedNav; playlist.dialog.snapshot.topbar[3] = C.fourchanX ? C.autohideHeader : C.autohideNav; } } playlist.dialog.dragging = true; document.addEventListener("mousemove", moveDialog); break; } } function moveDialog(e) { if (!playlist.dialog.self) return; e.preventDefault(); const sW = document.documentElement.clientWidth, sH = document.documentElement.clientHeight, x = e.x - playlist.dialog.snapshot.cursor[0], y = e.y - playlist.dialog.snapshot.cursor[1], w = playlist.dialog.snapshot.size[0], h = playlist.dialog.snapshot.size[1], maxX = sW - w; let minY = 0, maxY = sH - h; if (playlist.dialog.snapshot.topbar[2]) { const [dialogPos, dialogHeight, fixed, autohide] = playlist.dialog.snapshot.topbar; if (fixed && !autohide) { if (dialogPos < dialogHeight) { minY += dialogHeight; } else { maxY -= dialogHeight; } } } if (y > maxY) { playlist.dialog.self.style.bottom = 0 + "px"; playlist.dialog.self.style.removeProperty("top"); } else { playlist.dialog.self.style.top = y > minY ? ((y * 100) / sH) + "%" : minY + "px"; playlist.dialog.self.style.removeProperty("bottom"); } if (x > maxX) { playlist.dialog.self.style.right = 0 + "px"; playlist.dialog.self.style.removeProperty("left"); } else { playlist.dialog.self.style.left = x > 0 ? ((x * 100) / sW) + "%" : 0 + "px"; playlist.dialog.self.style.removeProperty("right"); } } function initAPI() { if (playlist.state > 0 || playlist.player) return; if (playlist.state < 0) return failedLoad(); playlist.state = 1; const script = document.createElement("script"); script.src = "https://www.youtube.com/iframe_api"; document.head.appendChild(script); setTimeout(() => { if (playlist.state < 1) return; playlist.state = -1; failedLoad(); }, 5000); script.onerror = () => { failedLoad(); playlist.state = -1; }; function failedLoad() { const msg = "Unable to load YouTube Iframe API.\nPress F12 and follow the instructions in the console."; if (!C.fourchanX) alert(msg); document.dispatchEvent(new CustomEvent("CreateNotification", { detail: { type: "error", content: msg } })); console.info("Unable to load YouTube Iframe API\n\n" + "4chanX's Settings > Advanced > Javascript Whitelist\n\n" + " https://www.youtube.com/iframe_api\n" + " https://www.youtube.com/s/player/" + "\n\nFilters in your AdBlock extension\n\n" + " @@||www.youtube.com/iframe_api$script,domain=4chan.org\n" + " @@||www.youtube.com/s/player/*$script,domain=4chan.org\n"); } } function setPos(coordinates) { coordinates.forEach((pos, index) => { if (!pos || !playlist.dialog?.self) return; switch (index) { case 0: playlist.dialog.self.style.top = pos; break; case 1: playlist.dialog.self.style.right = pos; break; case 2: playlist.dialog.self.style.bottom = pos; break; case 3: playlist.dialog.self.style.left = pos; break; } }); } } get self() { return document.getElementById("playlist-embed"); } get toggleBtn() { return document.querySelector(".playlist-toggle:not(.native)"); } toggle(close) { if (close || !this.self?.classList.contains("hide")) { this.self?.classList.add("hide"); if (!C.fourchanX) return; const button = document.getElementById("shortcut-playlist")?.firstElementChild; button?.classList.add("disabled"); } else { this.self?.classList.remove("hide"); if (!C.fourchanX) return; const button = document.getElementById("shortcut-playlist")?.firstElementChild; button?.classList.remove("disabled"); } } updateTabs(amount) { const tabs = this.self?.querySelector("ul"); if (!tabs) return console.error("No <ul> present in dialog."); while (tabs?.lastChild) tabs.removeChild(tabs.lastChild); for (let i = 0; i < amount; i++) { tabs.insertAdjacentHTML("beforeend", `<li><a href="javascript:;" data-page="${i}">${i + 1}</a></li>`); } } } class Playlist { constructor() { this.checking = false; this.mutated = false; this.playing = false; this.state = 0; this.posts = {}; this.dialog = new Dialog(this); this.player = null; this.track = ""; this.regex = /(?:https?:\/\/)?(?:www\.)?youtu(?:\.be\/|be.com\/\S*?(?:watch|embed)(?:(?:(?=\/[-a-zA-Z0-9_]{11,}(?!\S))\/)|(?:\S*?v=|v\/)))([-a-zA-Z0-9_]{11,})/g; if (!C.fourchan && !C.warosu) throw new Error("Website not supported.."); document.addEventListener("DOMContentLoaded", async () => { this.checking = true; const posts = await fetchThread(); if (posts.length < 1) throw new Error("No post was found."); this.parsePosts(posts).finally(() => this.checking = false); }); document.addEventListener("ThreadUpdate", ((e) => { if (!C.fourchanX) return; if (e.detail[404]) return; const newPosts = e.detail.newPosts.map((fullId) => { const no = fullId.split(".").pop(); return [no, document.getElementById("m" + no)?.innerHTML || ""]; }); parseReplies(this, newPosts); })); document.addEventListener("4chanThreadUpdated", ((e) => { if (C.fourchanX) return; if (unsafeWindow.thread_archived) return; const thread = document.querySelector(".board > .thread"); const newPosts = [...thread.children].slice(-e.detail.count).map((p) => { const no = p.id.split("pc").pop(); return [no, document.getElementById("m" + no)?.innerHTML || ""]; }); parseReplies(this, newPosts); })); document.addEventListener("4chanQRPostSuccess", ((e) => { if (C.fourchanX) return; const no = e.detail.postId.toString(); const newPost = [no, document.getElementById("m" + no)?.innerHTML || ""]; parseReplies(this, [newPost]); })); async function fetchThread() { switch (true) { case C.fourchan: const response = await fetch("https://a.4cdn.org/" + C.board + "/thread/" + C.thread + ".json"); const { posts } = await response.json(); return posts.map((post) => [post.no.toString(), post.com || ""]); case C.warosu: return [...document.querySelectorAll(".comment:not(:has(img[alt='[DELETED]'])) blockquote")] .map((post) => [post.parentElement?.id.slice(1) ?? "", post.innerHTML]); default: return []; } } async function parseReplies(playlist, newPosts) { playlist.checking = true; playlist.parsePosts(newPosts).then((addedPosts) => { if (!playlist.player) return; if (addedPosts > 0) playlist.mutated = true; }).finally(() => { playlist.checking = false; if (!playlist.mutated || playlist.playing) return; playlist.updatePlayer(); }); } } async parsePosts(posts) { const validPosts = posts.reduce((array, post) => { const comment = post[1].replace(/<wbr>/g, ""); const ids = Array.from(new Set([...comment.matchAll(this.regex)].map((m) => m[1]))); if (ids.length > 0) array.push([post[0], ids]); return array; }, []); if (validPosts.length < 1) return Promise.resolve(0); const parsedPosts = validPosts.map(async (post) => { return Promise.allSettled(post[1].map((id) => this.checkLink(id))) .then((responses) => [post[0], responses.reduce((output, res) => { if (res.status === "fulfilled") output.push(res.value); return output; }, [])]); }); return Promise.allSettled(parsedPosts).then((responses) => { return responses.reduce((output, res) => { if (res.status === "fulfilled") output.push(res.value); return output; }, []); }).then((posts) => { for (const post of posts) this.posts[post[0]] = post[1]; return posts.length; }); } ; async checkLink(id) { const url = "https://www.youtube.com/oembed?url=https://youtu.be/" + id + "&format=json"; return fetch(url, { method: "HEAD" }).then(res => [id, res.ok]); } ; updatePlayer() { if (!this.player) return; const pages = this.toPages(); const page = pages.findIndex(p => p.includes(this.track)); if (page > -1) { const index = pages[page].indexOf(this.track), trackLength = this.player.getDuration(), trackTime = this.player.getCurrentTime(), endTrack = trackTime === trackLength && trackLength > 0, endPlaylist = index === 199; const nextPage = endPlaylist && endTrack && page < pages.length - 1 ? page + 1 : page, nextIndex = !endPlaylist && endTrack ? index + 1 : index; if (this.playing) { this.player.loadPlaylist(pages[nextPage], nextIndex); console.debug("loadPlaylist()"); } else { const start = trackTime < trackLength ? trackTime : undefined; this.player.cuePlaylist(pages[nextPage], nextIndex, start); console.debug("cuePlaylist()"); } } else { console.debug("Can't find last played track in the playlist. Resetting to first page.."); if (this.playing) { this.player.loadPlaylist(pages[0]); } else { this.player.cuePlaylist(pages[0]); } } this.mutated = false; const tabs = document.getElementById("playlist-embed")?.querySelector(".tabs"); if (tabs && tabs.children.length !== pages.length) this.dialog?.updateTabs(pages.length); } ; toPages() { const output = []; const ids = Object.values(this.posts).flat() .reduce((validVideos, video) => { const [id, status] = video; if (status && validVideos.indexOf(id) < 0) { validVideos.push(id); } return validVideos; }, []); for (let i = 0; i < ids.length; i += 200) { const chunk = ids.slice(i, i + 200); output.push(chunk); } return output; } ; jumpTo(track) { if (!track) return; const postId = findKey(this.posts, track); return document.getElementById((C.fourchan ? "pc" : "p") + postId)?.scrollIntoView(); function findKey(object, key) { for (const k in object) { for (const a of object[k]) { if (a.includes(key)) return k; } } return null; } } ; isEmpty() { for (const prop in this.posts) { if (Object.prototype.hasOwnProperty.call(this.posts, prop)) { return false; } } return true; } ; reload() { if (!this.player) return; const pages = this.toPages(); const page = pages.findIndex(p => p.includes(this.track)); if (page > -1) { const index = pages[page].indexOf(this.track), trackTime = this.player.getCurrentTime(); if (this.playing) { this.player.loadPlaylist(pages[page], index, trackTime); } else { this.player.cuePlaylist(pages[page], index, trackTime); } } else { if (this.playing) { this.player.loadPlaylist(pages[0]); } else { this.player.cuePlaylist(pages[0]); } } } } const playlist = new Playlist(); unsafeWindow.onYouTubeIframeAPIReady = () => { playlist.player = new YT.Player("playlist", { width: '512', height: '288', playerVars: { 'fs': 0, 'disablekb': 1, 'rel': 0 }, events: { "onReady": initPlaylist, "onStateChange": updateTrack, "onError": playbackError } }); function initPlaylist(e) { playlist.state = 0; const pages = playlist.toPages(); playlist.dialog.updateTabs(pages.length); let history = {}; try { history = JSON.parse(localStorage.getItem("4chan-yt-playlist-history") || "{}"); } catch (err) { console.error("Failed to parse playlist history from local storage"); console.error(err); } const lastPlayedTrack = history[C.board + "." + C.thread] || null; const lastPage = lastPlayedTrack ? pages.find(p => p.includes(lastPlayedTrack)) : null; if (lastPlayedTrack && lastPage) { e.target.cuePlaylist(lastPage, lastPage.indexOf(lastPlayedTrack)); } else { e.target.cuePlaylist(pages[0]); } playlist.dialog.toggle(); } function updateTrack(e) { if (e.data == 0) { const pages = playlist.toPages(); const page = pages.findIndex(p => p.includes(playlist.track)); if (page > -1 && page < pages.length - 1) e.target.loadPlaylist(pages[page + 1]); } else if (e.data == -1) { playlist.track = e.target.getVideoUrl().split("=").pop() || ""; let history = {}; try { history = JSON.parse(localStorage.getItem("4chan-yt-playlist-history") || "{}"); } catch (err) { console.error("Failed to parse playlist history from local storage."); console.error(err); } history[C.board + "." + C.thread] = playlist.track; localStorage.setItem("4chan-yt-playlist-history", JSON.stringify(history)); if (playlist.mutated) playlist.updatePlayer(); const icon = playlist.dialog.self?.querySelector(".reload .icon"); icon?.classList.remove("spin"); playlist.player?.getIframe().blur(); } playlist.playing = (e.data == 1 || e.data == 3) ? true : false; } function playbackError(e) { let errLvl = "", errMsg = ""; const index = e.target.getPlaylistIndex(); const length = e.target.getPlaylist().length; switch (+e.data) { case 5: case 100: case 101: case 150: if (index + 1 < length) { e.target.nextVideo(); } case 101: case 150: errLvl = "warning"; errMsg = "The owner of the requested video does not allow it to be played in embedded players."; break; case 2: errLvl = "error"; errMsg = "The request contains an invalid parameter value."; break; case 5: errLvl = "error"; errMsg = "The requested content cannot be played in an HTML5 player."; break; case 100: errLvl = "warning"; errMsg = "The video has been removed or marked as private."; break; } const output = "Error - Video #" + (index + 1) + "\n" + errMsg; if (C.fourchanX) { document.dispatchEvent(new CustomEvent("CreateNotification", { detail: { type: errLvl, content: output, lifetime: 10 } })); } else { switch (errLvl) { case "error": console.error(output); break; case "warning": console.warn(output); break; default: console.info(output); break; } } } }; document.addEventListener("DOMContentLoaded", () => { if (C.fourchan) document.documentElement.classList.add("fourchan"); if (C.warosu) document.documentElement.classList.add("warosu"); let css = ":root.yotsuba{--fourchan-native-background-color:#f0e0d6;--fourchan-native-border-color:#d9bfb7}:root.yotsuba-b{--fourchan-native-background-color:#d6daf0;--fourchan-native-border-color:#b7c5d9}:root.futaba{--fourchan-native-background-color:#f0e0d6;--fourchan-native-border-color:#d9bfb7}:root.burichan{--fourchan-native-background-color:#d6daf0;--fourchan-native-border-color:#b7c5d9}:root.tomorrow{--fourchan-native-background-color:#282a2e;--fourchan-native-border-color:#282a2e}:root.photon{--fourchan-native-background-color:#ddd;--fourchan-native-border-color:#ccc}#playlist-embed{position:fixed;padding:1px 4px}:root:not(.fourchan-x) #playlist-embed{border-width:1px;border-style:solid;box-shadow:0 1px 2px rgba(0,0,0,.15)}:root.fourchan:not(.fourchan-x) #playlist-embed{background-color:var(--fourchan-native-background-color);border-color:var(--fourchan-native-border-color)}:root.warosu #playlist-embed{background-color:var(--darker-background-color);border-color:var(--even-darker-background-color)}:root.warosu #playlist-embed a{text-decoration:none}#playlist-embed.hide{display:none}#playlist-embed>div:first-child{display:flex}#playlist-embed .icon{height:12px}#playlist-embed .icon.spin{transform:rotate(360deg);-webkit-transition:transform .25s ease-in;-moz-transition:transform .25s ease-in;-ms-transition:transform .25s ease-in;-o-transition:transform .25s ease-in;transition:transform .25s ease-in}:root.fourchan #playlist-embed .reload{transform:translate(0,1px)}#playlist-embed .reload{margin-right:.25em}#playlist-embed ul,#playlist-embed li{margin:0;padding:0}#playlist-embed li{display:inline-block;list-style-type:none}#playlist-embed li:only-of-type{display:none}#playlist-embed li:not(:only-of-type):not(:last-of-type)::after{content:'•';margin:0 .25em}#playlist-embed .move{flex:1;cursor:move}#playlist-embed .jump{margin-top:-1px}#playlist-embed .close{margin-left:4px}:root.shortcut-icons:not(.fourchan-xt) #shortcut-playlist .icon--alt-text{font-size:0;visibility:hidden}:root:not(.fourchan-xt) #shortcut-playlist .icon{height:15px;width:16px;margin-bottom:-3px}"; const styles = [ ["yotsuba", "yotsubanew"], ["yotsuba-b", "yotsubabnew"], ["futaba", "futabanew"], ["burichan", "burichannew"], ["photon", "photon"], ["tomorrow", "tomorrow"] ]; document.head.insertAdjacentHTML("beforeend", "<style>" + css + "</style>"); if (!C.fourchan) return; changeStylingClass(unsafeWindow.Main.stylesheet.replace(/_/g, "")); const ob = new MutationObserver(() => { changeStylingClass(); }); ob.observe(document.querySelector("link[rel='stylesheet']"), { attributes: true }); function changeStylingClass(cookie) { if (C.fourchanX) return; const currentStyleName = document.styleSheets[0].href?.match(/css\/(.+)\..+\.css$/)?.[1] || ""; const index = styles.map((s) => s[1]).indexOf(cookie ? cookie : currentStyleName); if (index < 0 || document.documentElement.classList.contains(styles[index][0])) return; styles.forEach((c) => document.documentElement.classList.remove(c[0])); document.documentElement.classList.add(styles[index][0]); } });