Skip to content

Instantly share code, notes, and snippets.

@Chaosmeister
Last active September 25, 2024 10:32
Show Gist options
  • Save Chaosmeister/918a8b75b1c43e2ef9daa5cab4d347b7 to your computer and use it in GitHub Desktop.
Save Chaosmeister/918a8b75b1c43e2ef9daa5cab4d347b7 to your computer and use it in GitHub Desktop.
more efficient rvrb layout
// ==UserScript==
// @name RVRB Relayout
// @namespace https://app.rvrb.one/
// @version 1.12
// @description RVRB relayout
// @author Tomas Dittmann, Nicola Bosco
// @match https://app.rvrb.one/*
// @icon https://app.rvrb.one/favicon-32x32.png
// @grant none
// ==/UserScript==
///// CHANGELOG
//// 1.12
// - add sound notifications when user's get tagged or mentioned
// - add DJ auto step-up
//// 1.11
// autodope after 1 and song-length/2 seconds have passed
//// 1.10
// use mutationobserver for autodope
//// 1.9
// fix autodope loop
//// 1.8
// adjust to rvrb v1.10
// move menu text below buttons
// use visibility attribute instead of display to fix menu button positions
// add setting to hide menutitles
// fix custommenu position
// use rvrb icon
// use rvrb styles
//// 1.7
// adjust to rvrb v1.9
//// 1.6
// call colorizeBackground on start-up
//// 1.5
// cleanup
// refactor menuitem code into separate function
// add background styling by Kino
//// 1.4
// add Changelog
// move checkboxes into separate menu
// this will allow adding features more easily
(function() {
'use strict';
// INTERNAL DATA / FUNCTIONS //
var rvrbAccount;
var Style1;
var Style2;
var Style3;
var pingSound = new Audio("data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=");
var tagObserver;
var replyObserver;
var djStepUpObserver;
function convert(input) {
var parts = input.split(':'),
minutes = +parts[0],
seconds = +parts[1];
return (minutes * 60 + seconds);
}
function waitForEl(selector) {
return new Promise((resolve) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(() => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
});
});
}
function RegisterTrackChange() {
const observer = new MutationObserver(() => {
dope(false);
});
observer.observe(document.body.querySelector("#trackTitle"), {
childList: true,
subtree: true,
attributes: true,
});
}
function dope(immediately)
{
if (typeof (Storage) !== "undefined") {
if (localStorage.getItem("autodope")) {
const btn = document.body.querySelector(".section-content.floating.reactions > .section-actions > .action");
if (immediately)
{
if (btn.className.indexOf("active") < 0){
btn.click();
}
}
else
{
// dope sometime after half the tracklength + 1 seconds
var halfLengthInMilliseconds = ((convert(document.body.querySelector(".trackLength").innerText)/2)+1)*1000;
setTimeout(() => {
if (btn.className.indexOf("active") < 0){
btn.click();
}
}, Math.floor(Math.random() * halfLengthInMilliseconds));
}
}
}
}
function colorizeScrollbar()
{
if (typeof (Storage) !== "undefined") {
if (localStorage.getItem("colorizescrollbar")) {
Style1 = addGlobalStyle(`
div ::-webkit-scrollbar-thumb {
background: linear-gradient(to bottom, var(--secondary-album-palette), var(--primary-album-palette))
}`);
Style2 = addGlobalStyle(`
div ::-webkit-scrollbar-thumb:window-inactive {
background: linear-gradient(to bottom, var(--secondary-album-palette), var(--primary-album-palette))
}`);
} else {
if (Style1)
{
Style1.remove();
}
if (Style2)
{
Style2.remove();
}
}
}
}
function colorizeBackground()
{
if (typeof (Storage) !== "undefined") {
if (localStorage.getItem("colorizebackground")) {
Style3 = addGlobalStyle(`
.section-content.fixed:hover, .section-content.scrollable:hover {
background-color: rgba(0,0,0,.4)!important;
}
@property --albumColor {
syntax: '<color>';
initial-value: black;
inherits: false;
}
@property --albumColorSecondary {
syntax: '<color>';
initial-value: black;
inherits: false;
}
@property --black {
syntax: '<color>';
initial-value: black;
inherits: false;
}
#App, #MobileApp {
--albumColorSecondary: var(--secondary-album-palette);
background-image: linear-gradient(-45deg, var(--albumColorSecondary), var(--black) 85%)!important;
transition: --albumColorSecondary 1s;
}`);
} else {
if (Style3)
{
Style3.remove();
}
}
}
}
function hidemenutitles()
{
if (typeof (Storage) !== "undefined") {
var SideMenu = document.querySelector('#SideMenu');
if (localStorage.getItem("hidemenutitles")) {
SideMenu.querySelectorAll(".nav-label").forEach(item=>{
item.style.visibility = "hidden";
});
UpdateHeight(50);
}
else
{
SideMenu.querySelectorAll(".nav-label").forEach(item=>{
item.style.visibility = "visible";
});
UpdateHeight(65);
}
}
}
function beep() {
pingSound.play();
}
function pingUserOnTag() {
if (typeof (Storage) !== "undefined" && rvrbAccount !== undefined) {
if (localStorage.getItem("pingUserOnTag")) {
var handler = function ([record]) {
if (record.type === "childList") {
if (record.addedNodes[0].querySelector(".username." + rvrbAccount.userName)) return beep();
}
};
tagObserver = new MutationObserver((...r) => handler(...r));
tagObserver.observe(document.getElementById("ChatHistory"), {childList: true, subtree: true});
} else {
if (tagObserver !== undefined) {
tagObserver.disconnect();
}
}
}
}
function pingUserOnReply() {
if (typeof (Storage) !== "undefined" && rvrbAccount !== undefined) {
if (localStorage.getItem("pingUserOnReply")) {
var handler = function ([record]) {
if (record.type === "childList") {
const replyNode = record.addedNodes[0].querySelector(".replyTo");
if (replyNode && replyNode.textContent.includes(rvrbAccount.displayName)) {
beep();
}
}
};
replyObserver = new MutationObserver((...r) => handler(...r));
replyObserver.observe(document.getElementById("ChatHistory"), {childList: true, subtree: true});
} else {
if (replyObserver !== undefined) {
replyObserver.disconnect();
}
}
}
}
function autoStepUp() {
if (typeof (Storage) !== "undefined") {
if (localStorage.getItem("autoStepUp")) {
var tryToStepUp = function () {
const stepUpButton = document.querySelector(".rvrb-dj-step-up");
if (stepUpButton) {
stepUpButton.click();
}
}
var handler = function ([record]) {
if (record.type === "childList") {
tryToStepUp();
}
};
djStepUpObserver = new MutationObserver((...r) => handler(...r));
var djListActionSection = document.querySelector("#DjList .section-actions");
if (djListActionSection !== undefined) {
djStepUpObserver.observe(djListActionSection, {childList: true, subtree: true});
}
tryToStepUp();
} else {
if (djStepUpObserver !== undefined) {
djStepUpObserver.disconnect();
}
}
}
}
function addGlobalStyle(css)
{
var node = document.createElement("style");
node.type = "text/css";
node.appendChild(document.createTextNode(css));
var heads = document.getElementsByTagName("head");
if (heads.length > 0) {
heads[0].appendChild(node);
} else {
// no head yet, stick it whereever
document.documentElement.appendChild(node);
}
return node;
}
function addCheckbox(Title, Id, OnChange)
{
var Element = document.createElement("div");
Element.classList.add("nav-button");
Element.style.display = "flex";
Element.style.justifyContent = "space-between";
Element.style.flexDirection = "row";
Element.innerHTML = '<label for="ID' + Id + '">' + Title + '</label>';
var CheckBox = document.createElement("input");
CheckBox.type = "checkbox";
CheckBox.id = "ID" + Id;
CheckBox.style.marginLeft = "5px"
if (typeof (Storage) !== "undefined") {
if (localStorage.getItem(Id)) {
CheckBox.checked = true;
}
}
CheckBox.addEventListener('change', (event) => {
if (typeof (Storage) !== "undefined") {
if (event.currentTarget.checked) {
localStorage.setItem(Id, 'on');
} else {
localStorage.removeItem(Id);
}
}
});
CheckBox.addEventListener('change', OnChange);
Element.appendChild(CheckBox);
return Element;
}
function UpdateHeight(Height)
{
var App = document.querySelector('#App');
App.style.height = 'calc(100% - ' + Height + 'px)';
var SideMenu = document.querySelector('#SideMenu');
SideMenu.style.height = Height + "px";
var CustomMenu = SideMenu.querySelector('#CustomMenu');
CustomMenu.style.marginTop = Height - CustomMenu.previousElementSibling.clientHeight + "px";
}
// Main functions
function updateLayout() {
///// Re-Layout /////
// remove the SideMenu open button
document.querySelector('.toggle-button').remove()
// move SideMenu to top
var App = document.querySelector('#App');
var SideMenu = document.querySelector('#SideMenu');
App.before(SideMenu);
// fix SideMenu Layout
SideMenu.style.flexDirection = 'row';
SideMenu.style.width = 'auto';
// remove Account button margins and fix to the right border
var Account = SideMenu.querySelector('[title="Account"]');
Account.style.marginTop = 'unset';
Account.style.marginLeft = 'auto';
// show complete text in SideMenu;
document.querySelectorAll('.ellipses').forEach(item=>{
item.style.overflow = 'unset';
});
SideMenu.querySelectorAll(".nav-button").forEach(item=>{
item.style.flexDirection = 'column';
item.style.alignItems = 'center';
});
// move the ChatView to center
var Chatview = document.querySelector('#ChatView');
var UserView = document.querySelector('#UserView');
Chatview.after(UserView);
}
function CreateCustomMenu(){
var Menu = document.createElement("div");
Menu.style.visibility = "hidden";
Menu.style.background = "#000"
Menu.style.zIndex = "100";
Menu.style.flexDirection = "column";
Menu.id = "CustomMenu";
var MenuButtonText = document.createElement("div");
MenuButtonText.innerHTML = "RE-LAYOUT"
MenuButtonText.classList.add("nav-label");
var MenuButtonBox = document.createElement("div");
MenuButtonBox.classList.add("nav-icon");
var MenuButtonIcon = document.createElement("i");
MenuButtonIcon.classList.add("icon");
MenuButtonIcon.classList.add("rvrb-settings");
MenuButtonBox.appendChild(MenuButtonIcon);
var MenuButton = document.createElement("div");
MenuButton.appendChild(MenuButtonBox);
MenuButton.appendChild(MenuButtonText);
MenuButton.classList.add("nav-button");
MenuButton.style.flexDirection = "column";
MenuButton.style.alignItems = "center";
MenuButton.addEventListener("click", function() {
if (event.currentTarget !== event.target) {
if (Menu.style.visibility === "hidden") {
Menu.style.visibility = "visible";
}
else
{
Menu.style.visibility = "hidden";
}
}
});
var CustomMenu = document.createElement("div");
CustomMenu.style.display = "flex";
CustomMenu.style.flexDirection = "column";
CustomMenu.appendChild(MenuButton);
CustomMenu.appendChild(Menu);
var SideMenu = document.querySelector('#SideMenu');
var Account = SideMenu.querySelector('[title="Account"]');
Account.before(CustomMenu);
Menu.appendChild(addCheckbox("Autodope", "autodope", (event) => {
dope(true);
}));
Menu.appendChild(addCheckbox("Colorize scrollbar","colorizescrollbar", (event) => {
colorizeScrollbar();
}));
Menu.appendChild(addCheckbox("Colorize background","colorizebackground", (event) => {
colorizeBackground();
}));
Menu.appendChild(addCheckbox("Hide menu titles","hidemenutitles", (event) => {
hidemenutitles();
}));
Menu.appendChild(addCheckbox("Ping user on tag","pingUserOnTag", (event) => {
pingUserOnTag();
}));
Menu.appendChild(addCheckbox("Ping user on reply","pingUserOnReply", (event) => {
pingUserOnReply();
}));
Menu.appendChild(addCheckbox("Enable DJ auto step-up","autoStepUp", (event) => {
autoStepUp();
}));
}
function loadRvrbAccout () {
if (typeof (Storage) !== "undefined") {
const rvrbAccountRaw = localStorage.getItem("rvrb-account");
rvrbAccount = JSON.parse(rvrbAccountRaw);
}
}
loadRvrbAccout();
// wait for all elements to load before applying all the patches
waitForEl("#ChatView").then(() => {
updateLayout();
CreateCustomMenu();
// apply custom features
dope(true);
colorizeScrollbar();
colorizeBackground();
hidemenutitles();
pingUserOnTag();
pingUserOnReply();
autoStepUp();
// register autodope on trackchange
RegisterTrackChange();
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment