2025-01-20 23:54:58 +01:00
|
|
|
// 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 volumeAdd(number) {
|
|
|
|
volumeSet(Math.min(1, Math.max(0, audio.volume + number)));
|
|
|
|
}
|
|
|
|
function updateVolumeLabel() {
|
2025-01-27 08:44:59 +01:00
|
|
|
const percentage = `${Math.round(audio.volume * 100)}%`;
|
|
|
|
const txtElm = document.querySelector('.volume-percentage');
|
2025-01-27 05:18:22 +01:00
|
|
|
if (txtElm)
|
2025-01-27 08:44:59 +01:00
|
|
|
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;
|
|
|
|
}
|
2025-01-20 23:54:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//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) => {
|
2025-01-26 02:31:23 +01:00
|
|
|
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)
|
2025-01-20 23:54:58 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
const ptp = pinnedPost.nextElementSibling.querySelector(".StatusBody");
|
|
|
|
if (!ptp)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const musicContainer = ptp.querySelector(".audio-container");
|
2025-01-23 01:35:26 +01:00
|
|
|
if (musicContainer) {
|
2025-01-20 23:54:58 +01:00
|
|
|
setMusic(rand(musicContainer.children).src);
|
2025-01-23 01:35:26 +01:00
|
|
|
return true;
|
|
|
|
} else {
|
2025-01-23 03:32:51 +01:00
|
|
|
const placeholderContainer = ptp.querySelector(".placeholder-container");
|
|
|
|
if (placeholderContainer && placeholderContainer.href.endsWith(".m4a")) {
|
|
|
|
setMusic(placeholderContainer.href);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
setMusic(null);
|
|
|
|
return false;
|
|
|
|
}
|
2025-01-23 01:35:26 +01:00
|
|
|
}
|
2025-01-20 23:54:58 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
//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")
|
2025-01-23 01:35:26 +01:00
|
|
|
if (musicFields.length > 0) {
|
2025-01-20 23:54:58 +01:00
|
|
|
setMusic(rand(musicFields).nextElementSibling.title);
|
2025-01-23 01:35:26 +01:00
|
|
|
return true;
|
|
|
|
} else {
|
2025-01-20 23:54:58 +01:00
|
|
|
setMusic(null);
|
2025-01-23 01:35:26 +01:00
|
|
|
return false;
|
|
|
|
}
|
2025-01-20 23:54:58 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//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":
|
2025-01-26 02:31:23 +01:00
|
|
|
applyMainTheme(); //Statuses
|
2025-01-20 23:54:58 +01:00
|
|
|
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);
|
2025-01-27 08:44:59 +01:00
|
|
|
|
|
|
|
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 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();
|
|
|
|
})
|
|
|
|
|
|
|
|
updateVolumeLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
createAudioControls();
|
|
|
|
window.addEventListener('resize', createAudioControls);
|
|
|
|
|
|
|
|
new MutationObserver((mutationRecords, observer) => {
|
|
|
|
createAudioControls();
|
|
|
|
}).observe(document.body, {
|
|
|
|
childList: true,
|
|
|
|
subtree: true
|
|
|
|
});
|
2025-01-20 23:54:58 +01:00
|
|
|
})();
|
|
|
|
|
|
|
|
console.log("rot.js loaded");
|