rot.js/rot.js
2025-01-17 10:29:02 +00:00

302 lines
No EOL
9.7 KiB
JavaScript

// 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);
//<meta name="pageMusic" content="/assets/music.ogg">
waitUntilSpecial('meta[name="pageMusic"]', (pageMusic) => {
setMusic(pageMusic.content);
playMusic();
return true;
});
//<a id="pageMusic" href="/assets/music.ogg"></a>
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");