amputoma-fe/static/rot/rot.js
2025-01-29 04:15:26 +00:00

404 lines
No EOL
14 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);
});
}
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 volumeSet(number) {
audio.volume = number; //Set
localStorage.audiovolume = number; //Save
updateVolumeLabel();
}
function volumeInc() {
//We would not need to multiply by 10 and truncate if
//not for javascript constantly spewing rounding errors.
//But we do not live in an ideal world.
volumeSet(Math.min(1, Math.floor((Math.trunc(audio.volume * 1000) / 100) + 1) * 0.1));
}
function volumeDec() {
volumeSet(Math.max(0, Math.ceil((Math.trunc(audio.volume * 1000) / 100) - 1) * 0.1));
}
function updateVolumeLabel() {
const percentage = `${Math.round(audio.volume * 100)}%`;
const txtElm = document.querySelector('.volume-percentage');
if (txtElm)
txtElm.innerHTML = percentage;
const slider = document.querySelector('.volume-slider');
if (slider) {
const thumb = slider.querySelector('.volume-thumb');
const fill = slider.querySelector('.volume-fill');
if (thumb) thumb.style.left = percentage;
if (fill) fill.style.width = percentage;
}
}
//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) => {
let p = pinnedPost.nextElementSibling;
if (!p)
return false;
let statusBody = p.querySelector(".StatusBody");
if (!statusBody)
return false;
let txtElm = statusBody.querySelector(".text");
if (!txtElm)
return false;
if (txtElm.innerHTML.replace(/(<([^>]+)>)/ig, '').search(/profile them/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);
return true;
} else {
const placeholderContainer = ptp.querySelector(".placeholder-container");
if (placeholderContainer && placeholderContainer.href.endsWith(".m4a")) {
setMusic(placeholderContainer.href);
return true;
} else {
setMusic(null);
return false;
}
}
});
//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);
return true;
} else {
setMusic(null);
return false;
}
});
}
//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":
applyMainTheme(); //Statuses
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
//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);
function createAudioControls() {
let NavPanel = document.querySelector('.NavPanel');
let sideDrawer = document.querySelector('.side-drawer');
if (!NavPanel && !sideDrawer)
return;
if (document.querySelector('.audioControl'))
return;
//Initialize audio controls and event listeners
console.log("Adding music controls");
const panel = document.createElement("div");
panel.className = "panel panel-default";
const panelHeading = document.createElement("div");
panelHeading.className = "panel-heading";
panel.appendChild(panelHeading);
const title = document.createElement("div");
title.className = "title";
title.innerText = "Music Controls";
panelHeading.appendChild(title);
const panelBody = document.createElement("div");
panelBody.className = "panel-body";
panel.appendChild(panelBody);
const audioControl = document.createElement("div");
audioControl.className = "audioControl";
panelBody.appendChild(audioControl);
const mutebutton = document.createElement("label");
mutebutton.className = "mutebutton";
audioControl.appendChild(mutebutton);
const musicmute = document.createElement("input");
musicmute.className = "music-mute";
musicmute.setAttribute("type", "checkbox");
musicmute.checked = true;
mutebutton.appendChild(musicmute);
const mutecheck = document.createElement("span");
mutecheck.className = "mutecheck";
mutecheck.title = "Mute music";
mutebutton.appendChild(mutecheck);
const volumeSlider = document.createElement("div");
volumeSlider.className = "volume-slider";
audioControl.appendChild(volumeSlider);
const volumeTrack = document.createElement("div");
volumeTrack.className = "volume-track";
volumeSlider.appendChild(volumeTrack);
const volumeFill = document.createElement("div");
volumeFill.className = "volume-fill";
volumeTrack.appendChild(volumeFill);
const volumeThumb = document.createElement("div");
volumeThumb.className = "volume-thumb";
volumeSlider.appendChild(volumeThumb);
const volumeStepDwn = document.createElement("button");
volumeStepDwn.className = "volume-button-dwn button-default";
volumeStepDwn.innerText = "";
audioControl.appendChild(volumeStepDwn);
const volumeStepUp = document.createElement("button");
volumeStepUp.className = "volume-button-up button-default";
volumeStepUp.innerText = "";
audioControl.appendChild(volumeStepUp);
const volumePercentage = document.createElement("div");
volumePercentage.className = "volume-percentage";
audioControl.appendChild(volumePercentage);
if (NavPanel)
NavPanel.insertAdjacentElement('afterend', panel);
else if (sideDrawer)
sideDrawer.insertAdjacentElement('beforeend', audioControl);
{
let isDragging = false;
function updateVolume(e) {
const rect = volumeSlider.getBoundingClientRect();
let x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
let percentage = (x / rect.width) * 100;
// Update actual volume
volumeSet(percentage / 100);
}
function onMouseDown(e) {
isDragging = true;
updateVolume(e);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
function onMouseMove(e) {
if (isDragging) {
updateVolume(e);
}
}
function onMouseUp() {
isDragging = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
volumeSlider.addEventListener('mousedown', onMouseDown);
}
audio.muted = musicmute.checked = localStorage.audiomuted === "true";
musicmute.addEventListener('click', () => {
localStorage.audiomuted = audio.muted = musicmute.checked;
playMusic();
})
volumeStepDwn.addEventListener('click', volumeDec);
volumeStepUp.addEventListener('click', volumeInc);
updateVolumeLabel();
}
createAudioControls();
window.addEventListener('resize', createAudioControls);
new MutationObserver((mutationRecords, observer) => {
createAudioControls();
}).observe(document.body, {
childList: true,
subtree: true
});
})();
console.log("rot.js loaded");