// ==UserScript==
// @name Camamba Chat Tweaks
// @namespace dannysaurus.camamba
// @version 0.5.9
// @description tweaks layout of the chat
// @license MIT License
//
// @include https://www.camamba.com/chat/
// @include https://www.de.camamba.com/chat/
//
// @connect camamba.com
// @grant GM_xmlhttpRequest
//
// @require https://greasyfork.org/scripts/405143-simplecache/code/SimpleCache.js
// @require https://greasyfork.org/scripts/405144-httprequest/code/HttpRequest.js
// @require https://greasyfork.org/scripts/391854-enum/code/Enum.js
// @require https://greasyfork.org/scripts/405699-camamba-user/code/Camamba%20User.js
//
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js
//
// @require https://greasyfork.org/scripts/423722-camamba-chat-helpers-library/code/Camamba%20Chat%20Helpers%20Library.js?version=960246
//
// @grant GM.getValue
// @grant GM.setValue
// ==/UserScript==
/* jslint esnext: true */
/* globals knownUsers, me */
(function() {
'use strict';
// --- initial sizes ---
const SIZES = {
FONT_EM: {
userList: 1.2,
chatBox: 1.8,
},
WIDTH_EM: {
sidebarLeft: 10,
sidebarRight: 14,
},
};
// --- HTML Selector Helpers ---
const SELECTORS = {
ID: {
// original
userList: 'userList',
chatBox: 'chatBox',
chatInput: 'chatInput',
chatWindow: 'chatWindow',
mediaContainer1 : 'mediaContainer1',
mediaContainer2 : 'mediaContainer2',
mediaContainer3 : 'mediaContainer3',
mediaContainer4 : 'mediaContainer4',
mediaContainer5 : 'mediaContainer5',
mediaContainer6 : 'mediaContainer6',
mediaContainer7 : 'mediaContainer7',
mediaContainer8 : 'mediaContainer8',
// script
cbCamslots: 'cb-camslots',
spinnerUserlistFont: 'spinner-userlist-font',
spinnerChatFont: 'spinner-chat-font',
unamePermaInput: 'uname-perma-input',
},
CLASS: {
noTextSelect: 'noTextSelect',
borderBox: 'borderBox',
camBox: 'camBox'
}
};
const containers = (() => {
let userList, chatBox, sidebars, camslots;
return {
get userList() {
if (typeof userList === "undefined") {
userList = document.getElementById(SELECTORS.ID.userList);
}
return userList;
},
get chatBox() {
if (typeof chatBox === "undefined") {
chatBox = document.getElementById(SELECTORS.ID.chatBox);
}
return chatBox;
},
get sidebars() {
if (typeof sidebars === "undefined") {
sidebars = document.getElementById(SELECTORS.ID.chatWindow).querySelectorAll(`.${SELECTORS.CLASS.noTextSelect}`);
}
return sidebars;
},
get sidebarLeft() {
return this.sidebars[0];
},
get sidebarTop() {
return this.sidebars[1];
},
get sidebarRight() {
return this.sidebars[2];
},
get camslots() {
if (typeof camslots === "undefined") {
const parentContainers = [
SELECTORS.ID.mediaContainer1,
SELECTORS.ID.mediaContainer2,
SELECTORS.ID.mediaContainer3,
SELECTORS.ID.mediaContainer4,
SELECTORS.ID.mediaContainer5,
SELECTORS.ID.mediaContainer6,
SELECTORS.ID.mediaContainer7,
SELECTORS.ID.mediaContainer8,
]
.map(id => document.getElementById(id))
.filter(el => el !== null)
.map(el => el.parentNode);
camslots = [ ...new Set(parentContainers)];
}
return camslots;
}
};
})();
const layoutPatcher = new class {
constructor() {
this.historyCamslotsRemoved = [];
}
patchSizes() {
// this.setWidthOfSidebarLeft(`${SIZES.WIDTH_EM.sidebarLeft}em`);
this.setWidthOfSidebarRight(`${SIZES.WIDTH_EM.sidebarRight}em`);
return this;
}
setFontSizeOfUserList(fontSize) {
containers.userList.style.fontSize = fontSize;
return this;
}
setFontSizeOfChat(fontSize) {
containers.chatBox.style.fontSize = fontSize;
return this;
}
setWidthOfSidebarLeft(width) {
containers.sidebarLeft.style.width = width;
return this;
}
setWidthOfSidebarRight(width) {
containers.sidebarLeft.style.width = width;
return this;
}
showCamslots() {
for (let i = 0; i < this.historyCamslotsRemoved.length; i++) {
const { parent, index, element } = this.historyCamslotsRemoved.pop();
parent.insertBefore(element, parent.children[index]);
}
return this;
}
hideCamslots() {
for (let element of containers.camslots) {
const parent = element.parentNode;
if (parent) {
let index = Array.from(parent.children).indexOf(element);
parent.removeChild(element);
this.historyCamslotsRemoved.push({ parent, index, element });
}
}
return this;
}
}();
const controls = (() => {
// --- HTML Create Element Helpers ---
const createInput = ({
id,
parentElement = null,
type = 'text',
defaultValue = '',
labelText = null,
onValueChange = null,
propertyNameValue = 'value',
eventNameValueChange = 'input',
}) => {
const div = document.createElement('div');
const input = div.appendChild(document.createElement('input'));
input.type = type;
input.id = id;
input.style.backgroundColor = 'rgba(39,62,77,1)';
if (labelText) {
const label = div.appendChild(document.createElement('label'));
label.htmlFor = id;
label.appendChild(document.createTextNode(labelText));
}
if (onValueChange) {
let oldValue;
input.addEventListener(eventNameValueChange, () => {
const newValue = input[propertyNameValue];
if (oldValue !== newValue) {
oldValue = newValue;
onValueChange(newValue);
}
});
}
if (parentElement) {
parentElement.appendChild(div);
}
return input;
};
const createInputPersistent = ({
id,
parentElement = null,
type = 'text',
defaultValue = '',
labelText = null,
onValueChange = null,
propertyNameValue = 'value',
eventNameValueChange = 'input',
}) => {
const input = createInput({
parentElement, type, id, defaultValue, labelText, propertyNameValue, eventNameValueChange,
onValueChange: value => {
GM.setValue(id, value);
if (onValueChange) {
onValueChange(value);
}
}
});
input.setValue = value => {
GM.setValue(id, value);
input[propertyNameValue] = value;
onValueChange(value);
};
input.updateValue = () => GM.getValue(id, defaultValue).then(value => {
input[propertyNameValue] = value;
if (onValueChange) {
onValueChange(value);
}
});
return input;
};
const createCheckbox = ({
id,
parentElement = null,
initialChecked = false,
labelText = null,
onValueChange = null,
}) => {
const checkbox = createInputPersistent({
parentElement, id, labelText, onValueChange,
defaultValue: !!initialChecked,
type: 'checkbox',
propertyNameValue: 'checked',
eventNameValueChange: 'click',
});
return checkbox;
};
const createSpinner = ({
id, min, max, step,
parentElement = null,
defaultValue = 0,
labelText = null,
onValueChange = null,
}) => {
const spinner = createInputPersistent({
parentElement, id, defaultValue, labelText, onValueChange,
type: 'number',
});
spinner.min = min;
spinner.max = max;
spinner.step = step;
const buttonDec = spinner.parentNode.insertBefore(document.createElement('button'), spinner);
buttonDec.type = 'button';
buttonDec.innerHTML = '-';
buttonDec.addEventListener('click', () => {
spinner.stepDown();
spinner.setValue(spinner.value);
});
const buttonInc = spinner.parentNode.insertBefore(document.createElement('button'), spinner.nextSibling);
buttonInc.type = 'button';
buttonInc.innerHTML = '+';
buttonInc.addEventListener('click', () => {
spinner.stepUp();
spinner.setValue(spinner.value);
});
return spinner;
};
const sidebarLeftCenter = containers.sidebarLeft.children[1];
sidebarLeftCenter.innerHTML = "";
const container = sidebarLeftCenter.appendChild(document.createElement('div'));
// checkbox camslots on/off
const cbCamslots = createCheckbox({
parentElement: container,
id: SELECTORS.ID.cbCamslots,
initialChecked: true,
labelText: 'camslots',
onValueChange: value => {
if (value) {
layoutPatcher.showCamslots();
} else {
layoutPatcher.hideCamslots();
}
},
});
// spinner userlist font
const spinnerUserlistFont = createSpinner({
parentElement: container,
id: SELECTORS.ID.spinnerUserlistFont,
defaultValue: SIZES.FONT_EM.userList,
min: 1.0,
max: 2.2,
step: 0.1,
labelText: 'users',
onValueChange: value => {
const fontSize = `${value}em`;
layoutPatcher.setFontSizeOfUserList(fontSize);
},
});
// spinner chat font
const spinnerChatFont = createSpinner({
parentElement: container,
id: SELECTORS.ID.spinnerChatFont,
defaultValue: SIZES.FONT_EM.chatBox,
min: 1.0,
max: 2.5,
step: 0.1,
labelText: 'chat',
onValueChange: value => {
const fontSize = `${value}em`;
layoutPatcher.setFontSizeOfChat(fontSize);
},
});
const buttonKickFromCam = container.appendChild(document.createElement('button'));
buttonKickFromCam.type = 'button';
buttonKickFromCam.innerHTML = 'Kick from cam';
buttonKickFromCam.addEventListener('click', () => {
knownUsers.bySelected().stopViewing();
});
if (me.admin) {
const labelUnamePerma = container.appendChild(document.createElement('label'));
inputPerma.type = 'text';
inputPerma.for = "uname-perma";
inputPerma.innerHTML = 'Username Perma';
const inputUnamePerma = container.appendChild(document.createElement('input'));
inputUnamePerma.type = 'text';
inputUnamePerma.id = SELECTORS.ID.unamePermaInput;
inputUnamePerma.name = 'uname-perma';
const buttonPerma = container.appendChild(document.createElement('button'));
buttonPerma.type = 'button';
buttonPerma.innerHTML = 'perma';
buttonPerma.addEventListener('click', () => {
const unamePerma = document.getElementById(SELECTORS.ID.unamePermaInput).textContent;
if (unamePerma) {
knownUsers.addExact(unamePerma).then(() => knownUsers.byName(unamePerma).banPermaFast(""));
} else {
knownUsers.bySelected().ban("You are permanently banned from Camamba. Please do not create any additional accounts!", 24, { isPublic: true, isPerma: true, suppressBanLog: false });
}
});
}
return {
cbCamslots,
spinnerUserlistFont,
spinnerChatFont,
};
})();
const patchObject = ({ getExpected, doPatch, confirmAvailable = null, timeOutRetryMillis = 200, maxPeriodTryMillis = 5000 }) => {
const expected = getExpected();
const isAvailable = confirmAvailable ? confirmAvailable(expected) : !!expected;
if (!isAvailable) {
if (timeOutRetryMillis <= maxPeriodTryMillis) {
setTimeout(() => {
maxPeriodTryMillis -= timeOutRetryMillis;
patchObject({ getExpected, doPatch, confirmAvailable, timeOutRetryMillis, maxPeriodTryMillis });
}, timeOutRetryMillis);
}
return;
}
doPatch(expected);
};
/* eslint-disable no-undef */
patchObject({
getExpected: () => initSettings,
doPatch: (original) => {
initSettings = () => {
original();
// Breite von Userliste anpassen
layoutPatcher.patchSizes();
// weiterere Einstellungen überschreiben, bzw übernehmen
for (let control of [ controls.cbCamslots, controls.spinnerUserlistFont, controls.spinnerChatFont ]) {
control.updateValue();
}
};
},
});
let lastBanData = { userId : 0, text: '', time: 0, isPerma: false };
patchObject({
getExpected: () => adminExec,
doPatch: (original) => {
original();
if (currentAdminAction == "ban") {
let userId, text, time, isPerma;
text = byId('adminMessageInput').value;
if (!text || text.length <= 3 && byId('adminMessageSelect').selectedIndex) {
text = adminMessages[currentAdminAction][byId('adminMessageSelect').value];
}
userId = currentAdminTarget;
time = parseInt(byId('banTime').value);
isPerma = byId('permaBan') && byId('permaBan').checked;
if (userId && text > 3 && time) {
lastBanData = { userId, text, time, isPerma };
}
}
}
});
patchObject({
getExpected: () => {
return document.getElementById(SELECTORS.ID.chatInput);
},
doPatch: (el) => {
el.setAttribute('autoComplete', 'on');
}
});
/* eslint-enable no-undef */
})();