Twitch chat message to little image guy


I stream over on Twitch quite often, and I like making little widgets and stuff for my streams. A little guy I made a ways back would listen for certain words in chat and swap a displayed image if it sees any of those messages. Pretty nice for having a recently posted emote show up in the corner of the stream!

It’s super primitive and I made it in about 20 minutes, but I figured someone else might want to use or tweak it a bit, so here ya go!

To use it like I did make a folder named ‘images’ and copy your emote images in there. Add the emote word (like ‘aezaliRoll’) and its path (like ‘images/aezaliRoll.gif’) to the config dictionary. Change the defaults of the “main image” html element and the current to whatever you want it to start as, and set your channel name.

Have fun!

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Twitch Chat Image Changer</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            height: 100vh;
            display: flex;
            justify-content: flex-end;
            align-items: flex-end;
        }
        #image-container {
            position: fixed;
            bottom: -4px;
            right: -3px;
            max-width: 150px;
            height: auto;
            transition: transform 0.15s ease-in-out;
            /*max-width: 100%;
            height: 150px;*/
        }
        #image-container.slide-out {
            transform: translateX(100%)
        }
        @keyframes wobble {
          0% {
            transform: rotate(0deg);
          }
          25% {
            transform: rotate(-3deg);
          }
          50% {
            transform: rotate(3deg);
          }
          75% {
            transform: rotate(-3deg);
          }
          100% {
            transform: rotate(0deg);
          }
        }
        .wobble-image {
          display: inline-block;
          animation: wobble 3s infinite ease-in-out;
        }
        #main-image {
            width: 100%;
            height: auto;
        }
    </style>
</head>
<body>
    <div id="image-container">
        <img id="main-image" src="images/image.png" alt="Default Image">
    </div>

    <script src="https://github.com/tmijs/tmi.js/releases/download/v1.8.5/tmi.min.js"></script>
    <script>
        // Configuration data embedded directly in the HTML
        const config = {
            "messageToImage": {
                "messageWord": "images/image.png",
                "otherMessageWord": "images/second.gif"
            },
            "timeWindow": 2000,
            "messageThreshold": 1
        };

        var current = "whateverTheDefaultWordIs"

        const imageContainer = document.getElementById('image-container');
        const mainImage = document.getElementById('main-image');

        const messageCount = {};


        // Initialize Twitch client and message handling
        const client = new tmi.Client({
            channels: ['your_channel_name_here'] // Replace with your Twitch channel
        });

        client.connect();

        client.on('disconnected', (reason) => {
            console.error("Disconnected from Twitch:", reason);
            // Attempt to reconnect
            client.connect();
        });

        client.on('message', (channel, tags, message, self) => {
            if (self) return;

            const words = message.split(' ');
            let matchedKeyword = null;

            for (let word of words) {
                console.log(`trying ${word}`)
                if (config.messageToImage.hasOwnProperty(word)) {
                    matchedKeyword = word;
                    break; // Exit the loop once a match is found
                }
            }

            if(matchedKeyword){
                console.log(`found ${matchedKeyword}`)
                if (config.messageToImage[matchedKeyword]) { // now it needs to update the image
                    if (!messageCount[matchedKeyword]) {
                        messageCount[matchedKeyword] = 0;
                    }
                    messageCount[matchedKeyword]++;

                    setTimeout(() => {
                        messageCount[matchedKeyword]--;
                        if (messageCount[matchedKeyword] === 0) {
                            delete messageCount[matchedKeyword];
                        }
                    }, config.timeWindow);

                    if (messageCount[matchedKeyword] >= config.messageThreshold) {
                        if(matchedKeyword == current){
                            messageCount[matchedKeyword] = 0; // Reset count after change
                            return;
                        }
                        imageContainer.classList.add('slide-out');
                        setTimeout(() => {
                            //mainImage.src == config.messageToImage[matchedKeyword];
                            var path = config.messageToImage[matchedKeyword]
                            document.getElementById('main-image').src = path//config.messageToImage[matchedKeyword];
                            /*if(path.endsWith(".png") && !imageContainer.classList.contains('wobble-image')){
                                imageContainer.classList.add('wobble-image');
                            }else if(imageContainer.classList.contains('wobble-image')){
                                imageContainer.classList.remove('wobble-image');
                            }*/
                            imageContainer.classList.remove('slide-out')
                        }, 150)
                        //document.getElementById('main-image').src = config.messageToImage[matchedKeyword];
                        messageCount[matchedKeyword] = 0; // Reset count after change
                        current = matchedKeyword;
                    }
                }
            }
        });
    </script>
</body>
</html>