// ==UserScript==
// @name Camamba Chat Tweaks
// @namespace dannysaurus.camamba
// @version 0.5.14
// @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?version=1106047
// @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
// @require https://greasyfork.org/scripts/423662-camamba-chat-settings/code/Camamba%20Chat%20Settings.js?version=913122
// @require https://greasyfork.org/scripts/423665-camamba-hook-into-onmessage/code/Camamba%20Hook%20Into%20OnMessage.js?version=1180072
//
// @grant GM.getValue
// @grant GM.setValue
// ==/UserScript==
// https://greasyfork.org/de/scripts/419077-camamba-chat-tweaks
/* 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',
cbPrivateConvo: 'cb-privateConvo',
},
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: 3.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: 5.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'));
labelUnamePerma.type = 'text';
labelUnamePerma.for = "uname-perma";
labelUnamePerma.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).value;
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 });
}
});
}
const isGerman = location.hostname === "www.de.camamba.com";
let oldPrivateHandler = null;
// checkbox camslots on/off
const cbPrivateConvo = createCheckbox({
parentElement: container,
id: SELECTORS.ID.cbPrivateConvo,
initialChecked: true,
labelText: isGerman ? 'PN ablehnen ohne Freundschaft' : 'PM denie withouth friendship',
onValueChange: (value) => {
if (value) {
if (!oldPrivateHandler && onMessageHandlers.private) {
oldPrivateHandler = onMessageHandlers.private;
console.log("Alter Handler gesichert.", oldPrivateHandler.toString());
}
/**
* @param {{ id: number }} data
* @return {boolean} - true if further handling is required, false if action is fully handled
*/
onMessageHandlers.private = (data) => {
if (!data.id) {
return true;
}
const user = knownUsers[data.id];
if (!user) {
console.log(`Unknown user with id ${data.id} requesting ${"privConvo"}.`);
return false;
}
if (!user.friend) {
wsSend({ command: "control", target: data.id, request: "privReject" });
console.log(`PN von ${user.name} abgelehnt`)
return false;
}
console.log(`PN von ${user.name} erlaubt`)
console.log("Alter Handler ausgeführt.")
if (typeof oldPrivateHandler === 'function') {
return oldPrivateHandler(data);
}
return true;
};
} else {
if (oldPrivateHandler) {
onMessageHandlers.private = oldPrivateHandler;
console.log("Alter Handler wiederhergestellt.")
}
}
},
});
return {
cbCamslots,
spinnerUserlistFont,
spinnerChatFont,
cbPrivateConvo,
};
})();
const wait = async (ms) => new Promise(res => setTimeout(res, ms));
(async () => {
// wait until websocket has been connected
while (typeof initSettings !== 'function') {
await wait(100);
}
const original = initSettings;
initSettings = () => {
original();
// Breite von Userliste anpassen
layoutPatcher.patchSizes();
// weiterere Einstellungen überschreiben, bzw übernehmen
for (let control of [controls.cbCamslots, controls.spinnerUserlistFont, controls.spinnerChatFont, controls.cbPrivateConvo]) {
control.updateValue();
}
};
})();
(async () => {
let lastBanData = { userId: 0, text: '', time: 0, isPerma: false };
while (typeof adminExec !== 'function') {
await wait(100);
}
adminExec();
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 };
}
}
})();
(async () => {
while (document.getElementById(SELECTORS.ID.chatInput) === null) {
await wait(100);
}
document.getElementById(SELECTORS.ID.chatInput).setAttribute('autoComplete', 'on');
})();
console.log("running camamba chat tweaks")
})();