StumbleBot

Play YouTube videos from the chat box and/or add custom commands to StumbleChat

目前為 2025-05-24 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         StumbleBot
// @namespace    StumbleBot
// @version      1.0.2
// @description  Play YouTube videos from the chat box and/or add custom commands to StumbleChat
// @author       Goji
// @match        https://stumblechat.com/room/*
// ==/UserScript==

// Track the last hour we sent the 4:20 message
let lastSentHour = -1;
// Flag to control sending message only once
let shouldSendMessage = false;

// Check every second if it's time to send the 4:20 message
setInterval(() => {
    const now = new Date();
    const currentHour = now.getHours();
    const currentMinute = now.getMinutes();
    const currentSecond = now.getSeconds();

    // If it's exactly HH:20:00 and we haven't sent it this hour
    if (currentMinute === 20 && currentSecond === 0 && lastSentHour !== currentHour && !shouldSendMessage) {
        lastSentHour = currentHour;     // Update the last hour
        shouldSendMessage = true;       // Set flag to trigger message
    }
}, 1000);

// Immediately invoked function expression to set up WebSocket behavior and bot logic
(function() {
    // Load saved user nicknames from localStorage or initialize empty object
    let userNicknames = JSON.parse(localStorage.getItem('userNicknames')) || {};

    // Save original WebSocket send method
    WebSocket.prototype._send = WebSocket.prototype.send;

    // Override WebSocket send method
    WebSocket.prototype.send = function(data) {
        this._send(data); // Send the original data as normal

        // Attach listener to handle incoming messages
        this.addEventListener('message', handleMessage.bind(this), false);

        // Redefine send to catch subscriptions separately
        this.send = function(data) {
            console.log('send:', data);
            const sendData = safeJSONParse(data);

            if (sendData && sendData['stumble'] === 'subscribe') {
                console.log('subscribe caught');
            } else {
                this._send(data);
            }
        };
    };

    // Handler for incoming WebSocket messages
    function handleMessage(msg) {
        console.log('<<', msg.data);
        const wsmsg = safeJSONParse(msg.data);
        console.log(wsmsg);

        // Track user info when they join the room
        if (wsmsg['stumble'] === 'join' && wsmsg['nick'] && wsmsg['username'] && wsmsg['handle']) {
            const username = wsmsg['username'];
            let nickname = wsmsg['nick'];
            const handle = wsmsg['handle'];

            // If name is "guest-####", use username instead
            if (/^guest-\d+$/i.test(nickname)) {
                nickname = username;
            }

            // Welcome back message or first-time message
            if (userNicknames[username]) {
                respondWithMessage.call(this, `Welcome back, ${nickname || username}!`);
            } else {
                respondWithMessage.call(this, `Welcome, ${nickname || username}!`);
            }

            // Store nickname and user data by username and handle
            userNicknames[username] = {
                handle: handle,
                username: username,
                nickname: nickname || username,
                modStatus: wsmsg['mod'] ? "Moderator" : "Regular"
            };
            userNicknames[handle] = {
                handle: handle,
                username: username,
                nickname: nickname || username,
                modStatus: wsmsg['mod'] ? "Moderator" : "Regular"
            };

            // Save updated nicknames back to localStorage
            localStorage.setItem('userNicknames', JSON.stringify(userNicknames));
        }

        // Send 4:20 message if the flag is set
        if (shouldSendMessage) {
            shouldSendMessage = false; // Reset flag immediately

            setTimeout(() => {
                this._send('{"stumble":"msg","text": "🌲 It\'s 4:20 somewhere! Smoke em if you got em! 💨"}');
            }, 1000); // Send message 1 second later
        }

        //------------------------------------------------------------------------------------------------------------------
        // Custom Bot Commands
        //------------------------------------------------------------------------------------------------------------------

        // .yt command: play YouTube video
        if (wsmsg['text'].startsWith(".yt ")) {
            const query = wsmsg['text'].slice(4).trim();
            if (query) {
                this._send(`{"stumble": "youtube","type": "add","id": "${query}","time": 0}`);
            }
        }

        // .me command: say something with your nickname
        if (wsmsg['text'].startsWith(".me ")) {
            const handle = wsmsg['handle'];
            const nickname = userNicknames[handle]?.nickname || "User";
            const message = wsmsg['text'].slice(4).trim();
            respondWithMessage.call(this, `${nickname} ${message}`);
        }

        // .commands command: list available bot commands
        if (wsmsg['text'] === ".commands") {
            const commandsList = [
                "- .yt [query] - Play a YouTube video",
                "- .me [message] - Send a message as yourself",
                "- .commands - List all commands"
            ];

            // Send each command one at a time
            commandsList.forEach((command, index) => {
                setTimeout(() => {
                    respondWithMessage.call(this, command);
                }, index * 1000);
            });
        }

        // ping command: respond with "PONG"
        if (wsmsg['text'] === "ping") {
            setTimeout(() => {
                respondWithMessage.call(this, "PONG");
            }, 1000);
        }
    }

    // Example placeholder for future message handling logic
    function handleChatMessage(wsmsg) {
        const { text, handle } = wsmsg;

        // Example bot command: .command (does nothing currently)
        if (text === ".command") {
            respondWithMessage.call(this, "result");
        }
    }

    // Helper to send a message using the bot
    function respondWithMessage(text) {
        this._send(JSON.stringify({
            stumble: 'msg',
            text
        }));
    }

    // Safe JSON parsing with error catching
    function safeJSONParse(jsonString) {
        try {
            return JSON.parse(jsonString); // Try to parse JSON
        } catch (error) {
            console.error('Error parsing JSON:', error); // Log parsing error
            return null; // Return null if invalid JSON
        }
    }
})();