Camamba Chat Tweaks

tweaks layout of the chat

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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")
})();