diff --git a/index.html b/index.html index a0e1e947..7dda8f51 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - + diff --git a/static/js/rot.js b/static/js/rot.js new file mode 100644 index 00000000..c3bdf431 --- /dev/null +++ b/static/js/rot.js @@ -0,0 +1,302 @@ +// 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"); \ No newline at end of file