Auto Join Jackbox

Automatically connects to Jackbox game if twitch streamer sends message with code to chat

// ==UserScript==
// @name         Auto Join Jackbox
// @description  Automatically connects to Jackbox game if twitch streamer sends message with code to chat
// @version      1.0.0
// @namespace    http://tampermonkey.net
// @icon         https://jackbox.fun/favicon.ico
// @author       onejeuu
// @license      MIT
// @match        *://*.twitch.tv/*
// @match        *://*.jackbox.fun/*
// ==/UserScript==

(function() {
    'use strict';

    // !!! ВАЖНО !!!
    // Вы должны быть УЖЕ залогинены на jackbox.fun через Twitch

    // !!! ВАЖНО !!!
    // Поставить true если установлен 7TV
    // Поставить false если НЕ установлен 7TV
    const SEVENTV = true;

    const JACKBOX = {
        URL: 'jackbox.fun',
        NAME: 'BEBRANUH'
    }

    const LOG = {
        ENBALED: true,
        PREFIX: '%c[AUTOJACKBOX]',
        STYLE: 'color: cyan'
    }

    const SELECTOR = {
        ROOT:       'div[id="root"]',
        CHAT:       SEVENTV ? 'main[class="seventv-chat-list"]' : 'div[class="simplebar-content"]',
        MESSAGES:   SEVENTV ? 'span[class="seventv-user-message"]' : 'div[class="chat-line__message"]',
        USERNAME:   SEVENTV ? 'span[class="seventv-chat-user-username"]' : 'span[class="chat-author__display-name"]',
        TEXT:       SEVENTV ? 'span[class="text-token"]' : 'span[class="text-fragment"]',

        JACKBOX: {
            BUTTON:     'button[id="button-join"]',
            INPUT:      'input[id="username"]'
        }
    }

    const REGEX = /(?:(?<=^)|(?<=\s))[A-Za-z]{4}(?=\s|$)/g;

    const TwitchMessagesObserver = class {
        constructor() {
        	this.channelName = window.location.pathname.split('/')[1];

            this.chat = null;
            this.checkedMessages = new Set();

            this.checkChat();

            this.observer = new MutationObserver(this.handleMutations.bind(this));
            this.observer.observe(document.querySelector(SELECTOR.ROOT), {childList: true, subtree: true, attributes: true});

            if (LOG.ENBALED) {
                console.log(LOG.PREFIX, LOG.STYLE, 'NEW OBSERVER:', 'Twitch');
            }
        }

        checkChat() {
            this.chat = document.querySelector(SELECTOR.CHAT);
            this.checkMessages();
        }

        checkMessages() {
        	if (!this.chat) return;

        	const messages = this.chat.querySelectorAll(SELECTOR.MESSAGES);
            if (!messages) return;

            messages.forEach(message => this.checkMessage(message));
        }

        checkMessage(message) {
            if (!message) return;

            const username = message.querySelector(SELECTOR.USERNAME)?.textContent || null;
            if (!username) return;

            if (username !== this.channelName) return;

            const text = message.querySelector(SELECTOR.TEXT)?.textContent || null;
            if (!text) return;

            if (this.checkedMessages.has(text)) return;

            this.checkedMessages.add(text);

            if (LOG.ENBALED) {
                console.log(LOG.PREFIX, LOG.STYLE, `Message from ${username}: ${text}`);
            }

            this.checkCode(text);
        }

        validCode(text) {
            return text.match(REGEX)?.[0]?.toUpperCase().trim() || false;
        }

        checkCode(text) {
            const code = this.validCode(text);
	        if (!code) return;

            if (LOG.ENBALED) {
                console.log(LOG.PREFIX, LOG.STYLE, `Code: ${code}. (${text})`);
            }

	        window.open(`https://${JACKBOX.URL}/${code}`, '_blank');
	    }

        handleMutations(mutationsList) {
            mutationsList.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                	this.checkChat();
                });
            });
        }
    }

    const JackboxButtonObserver = class {
    	constructor() {
            this.input = null;
    		this.button = null;

            this.checkInput();
            this.checkButton();

            this.observer = new MutationObserver(this.handleMutations.bind(this));
            this.observer.observe(document.documentElement, {childList: true, subtree: true, attributes: true});

            if (LOG.ENBALED) {
                console.log(LOG.PREFIX, LOG.STYLE, 'NEW OBSERVER:', 'Jackbox');
            }
        }

        checkInput() {
            this.input = document.querySelector(SELECTOR.JACKBOX.INPUT);
            this.checkName();
        }

        checkName() {
            if (!this.input) return;

            if (!this.input.value) {
                this.changeName();
            }
        }

        changeName() {
            if (!this.input) return;

            this.input.value = JACKBOX.NAME;
        }

        checkButton() {
        	this.button = document.querySelector(SELECTOR.JACKBOX.BUTTON);
            this.checkClick();
        }

        checkClick() {
        	if (!this.button) return;

        	if (this.button.disabled) return;

        	this.button.click();
        }

        handleMutations(mutationsList) {
            mutationsList.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                	this.checkButton();
                });
            });
        }
    }

    const start = () => {
        const url = window.location.href;

		if (url.includes('twitch')) {
			new TwitchMessagesObserver();
			return;
		}

		if (url.includes('jackbox')) {
			new JackboxButtonObserver();
			return;
		}
    }

    window.addEventListener('load', start);
})();