// Rot.js // one rotten apple spoils the bunch (() => { //Helper functions function getPromiseFromEvent(item, event) { return new Promise((resolve) => { const listener = () => { item.removeEventListener(event, listener); resolve(); } item.addEventListener(event, listener); }); } // MIT Licensed // Author: jwilson8767 function waitUntil(selector) { return new Promise((resolve, reject) => { const el = document.querySelector(selector); if (el) { resolve(el); return; } new MutationObserver((mutationRecords, observer) => { // Query for elements matching the specified selector Array.from(document.querySelectorAll(selector)).forEach((element) => { resolve(element); //Once we have resolved we don't need the observer anymore. observer.disconnect(); }); }).observe(document.documentElement, { childList: true, subtree: true }); }); } function sex(sex) { return sex // Sex } function rand(list) { return list ? list[list.length * Math.random() | 0] : null; } //Music functions function setMusic(url) { if (audio.src == url || (!url && audio.src === window.location.toString())) return; audio.pause(); if (url) { console.log("Setting music: " + url); audio.src = url; playMusic(); } else { // This line will cause a lot of errors. // Setting src to "" will cause any pending .play()s to fail. audio.src = ""; } } function playMusic() { //Starts playing the music if it isn't muted and isn't already playing if (audio.src && audio.src != "" && audio.paused && !audio.muted) audio.play().catch(() => getPromiseFromEvent(window, 'click').then(audio.play)); } function setImage(url) { if (url) { console.log("Setting background: " + url); let apploaded = document.getElementById("app-loaded"); if (apploaded) apploaded.style.setProperty("--body-background-image", "url(" + url + ")"); else console.log("Couldn't set background, app not yet loaded"); } } function volumeSet(number) { audio.volume = number; //Set localStorage.audiovolume = number; //Save updateVolumeLabel(); } function volumeAdd(number) { volumeSet(Math.min(1, Math.max(0, audio.volume + number))); } function updateVolumeLabel() { document.getElementById("user-audio-percentage").innerHTML = Math.round(audio.volume * 100) + "%"; } //Registers a mutation observer for an element. Upon triggering and the callback //returning true, observation ceases. All observers are disconnected and the array is cleared. const observers = []; function waitUntilSpecial(selector, callback) { const newObserver = new MutationObserver((mutationRecords, observer) => { // Query for elements matching the specified selector Array.from(document.querySelectorAll(selector)).forEach((element) => { //Callback if (!callback(element)) return; //Clean up console.log("Cleaning up"); for (const o of observers) o.disconnect(); observers.length = 0; }); }); observers.push(newObserver); newObserver.observe(document.documentElement, { childList: true, subtree: true }); } //Theme application function applyMainTheme() { console.log("Applying main theme"); setMusic(null); // waitUntilSpecial('meta[name="pageMusic"]', (pageMusic) => { setMusic(pageMusic.content); playMusic(); return true; }); // waitUntilSpecial("#pageMusic", (pageMusic) => { setMusic(pageMusic.getAttribute("href")); playMusic(); return true; }); } function applyUserTheme() { console.log("Applying user theme"); setMusic(null); //Configure by post waitUntilSpecial(".pin", (pinnedPost) => { if (pinnedPost.nextElementSibling .querySelector(".StatusBody") .querySelector(".text") .innerHTML .replace(/(<([^>]+)>)/ig, '') .search(/profile theming post/ig) == -1) return false; const ptp = pinnedPost.nextElementSibling.querySelector(".StatusBody"); if (!ptp) return false; const musicContainer = ptp.querySelector(".audio-container"); if (musicContainer) setMusic(rand(musicContainer.children).src); else setMusic(null); const imageContainer = ptp.querySelector(".image-container"); if (imageContainer) setImage(rand(imageContainer.getElementsByTagName("img")).src); else setImage(null); return musicContainer || imageContainer; }); //Configure by fields waitUntilSpecial(".user-profile-field-name", () => { const fields = [...document.getElementsByClassName("user-profile-field-name")] if (fields.length == 0) return false; const musicFields = fields.filter((x) => x.title.toLowerCase().replace(/\s+/g, '') == "music") if (musicFields.length > 0) setMusic(rand(musicFields).nextElementSibling.title); else setMusic(null); const imageFields = fields.filter((x) => x.title.toLowerCase().replace(/\s+/g, '') == "image") if (imageFields.length > 0) setImage(rand(imageFields).nextElementSibling.title); else setImage(null); return musicFields.length > 0 || imageFields.length > 0; }); } //Switch-based monkey patching router bullshit let lastPath = null; function updateRot() { try { let newPath = window.location.pathname; if (lastPath == newPath) return; lastPath = newPath; let pathSpl = newPath.split("/"); switch (pathSpl.length) { case 1: applyMainTheme(); //Root break; case 2: switch (pathSpl[1]) { case "about": case "announcements": case "lists": case "bookmarks": applyMainTheme(); break; default: applyUserTheme(); break; } break; case 3: switch (pathSpl[1]) { case "main": applyMainTheme(); //Main timelines break; case "users": applyUserTheme(); break; case "notice": //Continue playing break; default: applyMainTheme(); break; } break; default: applyMainTheme(); break; } } catch (e) { console.error(e); } } //Rot music player const audio = document.createElement("audio"); audio.loop = true; audio.id = "user-music"; audio.style = "display:none;"; if (localStorage.audiovolume && localStorage.audiovolume >= 0 && localStorage.audiovolume <= 1) audio.volume = localStorage.audiovolume; //Load volume else audio.volume = 0.2; //Default volume //Initialize audio controls and event listeners waitUntil("#music-controls").then((controls) => { updateVolumeLabel(); controls.querySelector("#music-up").onclick = () => volumeAdd(0.05); controls.querySelector("#music-down").onclick = () => volumeAdd(-0.05); }); waitUntil("#music-slider").then((slider) => { updateVolumeLabel(); slider.oninput = () => volumeSet(slider.value / 100); }); waitUntil("#music-mute").then((box) => { audio.muted = box.checked = localStorage.audiomuted === "true"; box.addEventListener('click', () => { localStorage.audiomuted = audio.muted = box.checked; playMusic(); }) }); //Monkey patches and event listeners const oldPushState = history.pushState; history.pushState = function pushState() { const ret = oldPushState.apply(this, arguments); updateRot(); return ret; }; const oldReplaceState = history.replaceState; history.replaceState = function replaceState() { const ret = oldReplaceState.apply(this, arguments); updateRot(); return ret; }; addEventListener('locationchange', updateRot); addEventListener('popstate', updateRot); })(); console.log("rot.js loaded");