On-demand themed Twitch chat overlay generator

  • By Ben Myers
  • Last update: Dec 6, 2022
  • Comments: 15

showmy.chat

All Contributors

On-Demand, Themeable Twitch Chat Overlays For Everyone

Join the Discord server!

This is the repo for showmy.chat, an on-demand themed Twitch chat overlay generator. These chat overlays can be used in broadcasting software such as OBS.

What does this mean? It means that anyone can go to showmy.chat, enter the name of their Twitch channel, and select a theme, and they'll get a URL they can use as a source in their broadcasting software of choice to display a live chat feed!

Goals

showmy.chat is intended to be a quick, easy, plug-and-play solution for getting themed chat overlays right out of the box. Because of this, it doesn't currently support any customization beyond selecting your theme. This could change later, so long as the user experience of generating your themed overlay remains quick and easy.

Contributing

showmy.chat is thrilled to accept contributions! Please read the contributing guide to see how you can contribute!

Colophon

showmy.chat is built with the Eleventy static site generator ā€” in particular, using the Eleventy Serverless functionality. It is hosted with gratitude on Netlify.

showmy.chat overlays leverage ComfyJS, which in turn is built on top of tmi.js, to receive and parse messages. Major thanks to everyone in these communities who have done the hard work to ensure that interacting with Twitch's chat API is as easy as possible.

Finally, showmy.chat is indebted to Style Stage, Stephanie Eckles's modern spiritual successor to CSS Zen Garden, for inspiration. Style Stage demonstrates how the same markup can be styled in a plethora of creative ways, and leverages community-contributed stylesheets. showmy.chat was initially conceived of as "What if Style Stage, but for Twitch chat overlays?"

Contributors āœØ

Thanks goes to these wonderful people (emoji key):


Ben Myers

šŸ’» šŸŽØ šŸš‡

Travis Waith-Mair

šŸŽØ

Michael Chan

šŸ–‹

Anthony Campolo

šŸ“– šŸ’»

Thomas Michael Semmler

šŸŒ

Alex Riviere

šŸ–‹ šŸŽØ šŸ’»

Ken aka Frosty

šŸ–‹ šŸ’» šŸš‡

Nicky Meuleman

šŸŽØ šŸ’» šŸ”§ šŸ“–

Mike Aparicio

šŸŽØ

Danial

šŸŽØ

Alex Trost

šŸ–‹ šŸŽØ

Kevin Powell

šŸ–‹

Jacob Bolda

šŸ–‹

This project follows the all-contributors specification. Contributions of any kind welcome!

Github

https://github.com/BenDMyers/showmy.chat

Comments(15)

  • 1

    Styleguide

    To keep the formatting/code style consistent as more people contribute to this project, I propose automating (part of) it.

    I asked if there was a styleguide on Discord, and @BenDMyers said:

    Not yet - but Iā€™d say ideally use tabs and semicolons.

    This (partial) automation can be done with tools like:

    Prettier for formatting.

    Possibly

    If linting tools are used, it is important to use the matching *-config-prettier (like stylelint-config-prettier for stylelint) to prevent conflicts between the linter and Prettier. Leaving all formatting concerns up to Prettier.

    I'd be happy to implement the tools while we discuss what rules to apply here.

  • 2

    set up linting and formatting

    rel #60

    Took the suggestions in #60 and the style patterns already in this codebase as a base.

    Prettier for formatting. - "useTabs": true, - "singleQuote": true, - "bracketSpacing": false

    ESLint for JavaScript linting. - The eslint:recommended rules as base. Those are the base rules that are listed at https://eslint.org/docs/rules/ and marked with a checkmark. - Added a "no-unused-vars": ["error", { "args": "none" }] because functions with named and unused arguments are used in the codebase. - Marked src/scripts/bttvIntegration.js and src/scripts/handleChat.js es ESM, the rest is CommonJS by default

    Stylelint for CSS linting - The styleline:recommended rules as base - Used the postcss-html syntax to also check the inline <style> tags in HTML files.

    All tools have a *ignore file based on .gitignore. Sadly, they don't support "take gitignore as a base for this file", so it's duplicated, but those files will very rarely be touched.

  • 3

    Configuration for showing the "n" most recent messages

    Need

    Some users would only like to display the n latest messages - especially in the case of large, obtrusive themes such as animalese and cards. This can be handled theme-level ā€” for instance, cards currently displays the five most recent messages using :nth-last-child:

    [data-twitch-message]:nth-last-child(n+5) {
    	opacity: 0;
    	transform: rotateX(-90deg);
    	transition:
    		opacity var(--animation-duration) ease-in,
    		transform var(--animation-duration) ease-in;
    }
    

    However, this isn't ideal, because it hardcodes the number of messages that can be displayed, and it does so in a way that requires some weird CSS overrides to change.

    showmy.chat should provide a query parameter through which users can specify how many of the most recent messages to display. If this query parameter is not provided, then the default behavior should be to show all messages ā€” this way, users strictly opt in to only showing a subset of their messages.

    Suggested API

    • Configuration should be strictly optional. If query parameter is not provided, all messages are shown.
      • In homepage URL builder, if user does not opt in to only displaying the n latest messages, don't add the query parameter to the built URL at all
    • Query parameter name could be showLatestMessages, and its expected value should be an integer greater than zero.
      • If value is not an integer greater than zero, query parameter is ignored.
    • Deletion should be handled in the JavaScript, and outdated messages should be completely deleted, rather than hidden. Rationale:
      • If outdated messages are hidden with CSS solutions, then when a mod deletes a message, previously-hidden outdated messages could become visible again
      • Some folks may be using the n latest feature to reduce the number of DOM elements on the page for perf reasons

    Steps

    • [ ] Ensure Eleventy Serverless query parameter data can be accessed inside the handleChat script
    • [ ] Ensure if showLatestMessages query parameter has been provided, and is an integer greater than 0, scripts delete older messages
    • [ ] Add configuration to homepage URL builder
    • [ ] Remove hardcoded CSS implementation in themes (currently just cards, I think?)
    • [ ] Notify known users of cards theme of changed behavior
  • 4

    Allow displaying multiple roles (frontend-horse)

    Added multiple roles display for frontend-horse.

    image

    founder ā†’ mod ā†’ vip ā†’ subscriber

    Generated from the following scss:
    $mod-badge: '<path d="M7.05 13.406l3.534 3.536-1.413 1.414 1.415 1.415-1.414 1.414-2.475-2.475-2.829 2.829-1.414-1.414 2.829-2.83-2.475-2.474 1.414-1.414 1.414 1.413 1.413-1.414zM3 3l3.546.003 11.817 11.818 1.415-1.414 1.414 1.414-2.474 2.475 2.828 2.829-1.414 1.414-2.829-2.829-2.475 2.475-1.414-1.414 1.414-1.415L3.003 6.531 3 3zm14.457 0L21 3.003l.002 3.523-4.053 4.052-3.536-3.535L17.457 3z"></path>';
    $vip-badge: '<path d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm8 5.5v7h2v-7h-2zm-.285 0H8.601l-1.497 4.113L5.607 8.5H3.493l2.611 6.964h2L10.715 8.5zm5.285 5h1.5a2.5 2.5 0 1 0 0-5H14v7h2v-2zm0-2v-1h1.5a.5.5 0 1 1 0 1H16z"></path>';
    $founder-badge: '<path style="transform: scale(0.045);" d="M509.8 332.5l-69.9-164.3c-14.9-41.2-50.4-71-93-79.2 18-10.6 46.3-35.9 34.2-82.3-1.3-5-7.1-7.9-12-6.1L166.9 76.3C35.9 123.4 0 238.9 0 398.8V480c0 17.7 14.3 32 32 32h236.2c23.8 0 39.3-25 28.6-46.3L256 384v-.7c-45.6-3.5-84.6-30.7-104.3-69.6-1.6-3.1-.9-6.9 1.6-9.3l12.1-12.1c3.9-3.9 10.6-2.7 12.9 2.4 14.8 33.7 48.2 57.4 87.4 57.4 17.2 0 33-5.1 46.8-13.2l46 63.9c6 8.4 15.7 13.3 26 13.3h50.3c8.5 0 16.6-3.4 22.6-9.4l45.3-39.8c8.9-9.1 11.7-22.6 7.1-34.4zM328 224c-13.3 0-24-10.7-24-24s10.7-24 24-24 24 10.7 24 24-10.7 24-24 24z"></path>';
    $sub-badge: '<path d="M12 18.26l-7.053 3.948 1.575-7.928L.587 8.792l8.027-.952L12 .5l3.386 7.34 8.027.952-5.935 5.488 1.575 7.928z"></path>';
    
    @mixin dualBadge($fill, $badge1, $badge2) {
      content: url('data:image/svg+xml; utf8, <svg fill="' + $fill + '" stroke="' + $fill + '" stroke-width="0" viewBox="0 0 54 24" height="1em" width="2.25em" xmlns="http://www.w3.org/2000/svg" style="display: inline;">' + $badge1 + '<g style="transform: translateX(30px)">' + $badge2 + '</g></svg>');
    }
    
    /* Multiple roles */
    // vip +
    [data-twitch-sender-roles~='vip'] {
        &[data-twitch-sender-roles~='subscriber'] .twitch-chat-sender::before {
            @include dualBadge('%23333333', $vip-badge, $sub-badge);
        }
    }
    
    // mod +
    [data-twitch-sender-roles~='mod'] {
        &[data-twitch-sender-roles~='subscriber'] .twitch-chat-sender::before {
            @include dualBadge('white', $mod-badge, $sub-badge);
        }
        &[data-twitch-sender-roles~='vip'] .twitch-chat-sender::before {
            @include dualBadge('white', $mod-badge, $vip-badge);
        }
    }
    
    // founder +
    [data-twitch-sender-roles~='founder'] {
        &[data-twitch-sender-roles~='subscriber'] .twitch-chat-sender::before {
            @include dualBadge('white', $founder-badge, $sub-badge);
        }
        &[data-twitch-sender-roles~='vip'] .twitch-chat-sender::before {
            @include dualBadge('white', $founder-badge, $vip-badge);
        }
        &[data-twitch-sender-roles~='mod'] .twitch-chat-sender::before {
            @include dualBadge('white', $founder-badge, $mod-badge);
        }
    }
    
    [data-twitch-sender-roles~=founder][data-twitch-sender-roles~=subscriber] .twitch-chat-sender::before,
    [data-twitch-sender-roles~=founder][data-twitch-sender-roles~=vip] .twitch-chat-sender::before,
    [data-twitch-sender-roles~=founder][data-twitch-sender-roles~=mod] .twitch-chat-sender::before,
    [data-twitch-sender-roles~=mod][data-twitch-sender-roles~=subscriber] .twitch-chat-sender::before,
    [data-twitch-sender-roles~=mod][data-twitch-sender-roles~=vip] .twitch-chat-sender::before,
    [data-twitch-sender-roles~=vip][data-twitch-sender-roles~=subscriber] .twitch-chat-sender::before {
      margin-left: -2.55em;
    }
    
    [data-twitch-sender-roles~=founder][data-twitch-sender-roles~=subscriber] .twitch-chat-sender,
    [data-twitch-sender-roles~=founder][data-twitch-sender-roles~=vip] .twitch-chat-sender,
    [data-twitch-sender-roles~=founder][data-twitch-sender-roles~=mod] .twitch-chat-sender,
    [data-twitch-sender-roles~=mod][data-twitch-sender-roles~=subscriber] .twitch-chat-sender,
    [data-twitch-sender-roles~=mod][data-twitch-sender-roles~=vip] .twitch-chat-sender,
    [data-twitch-sender-roles~=vip][data-twitch-sender-roles~=subscriber] .twitch-chat-sender {
      padding-left: 2.85em;
    }
    
  • 5

    Bump jsdom from 16.7.0 to 19.0.0

    Bumps jsdom from 16.7.0 to 19.0.0.

    Release notes

    Sourced from jsdom's releases.

    Version 19.0.0

    • Changed jsdom.nodeLocation() to return undefined when used on nodes that originate via fragment parsing (e.g., via innerHTML). Previously it would return based on the node location of the fragment string, which made node locations unreliable with respect to the original document source. This restores the behavior that was present in v14.0.0, and was accidentally broken in v14.1.0. (bakkot)
    • Fixed calling window.close() inside the Window's load event to no longer crash. (MattiasBuelens)

    Version 18.1.1

    • Fixed connectedCallback to fire in situations involving document fragments, which was broken in v18.0.1. (GrantGryczan)

    Version 18.1.0

    • Fixed headers.append() and headers.set() to normalize values. (MattiasBuelens)
    • Fixed pageshow events to have bubbles: true and cancelable: true. (MattiasBuelens)
    • Implemented the reason property on AbortSignals, along with the corresponding reason argument to abortSignal.abort() and AbortSignal.abort(). (MattiasBuelens)

    Version 18.0.1

    • Fixed live Ranges to update correctly after calling node.normalize(). (hgiesel)
    • Fixed live Ranges to update correctly after removing child nodes. (hgiesel)
    • Fixed setting inputEl.valueAsDate = null to no longer throw an exception, but instead set the value to the empty string. (simon-weimann)
    • Improved performance of node insertion and node.contains(). (GrantGryczan)

    Version 18.0.0

    Potentially-breaking bug fixes:

    • Fixed SSL certificate checking for WebSocket connections. Previously, invalid SSL certificates were always accepted; now, they properly respect the ResourceLoader's strictSSL option (which defaults to true).
    • Changed the global in which almost all Promise and TypeError instances are created to be the jsdom global, not the Node.js global. This could affect any code that uses instanceof.

    Other changes:

    • Fixed moving an element between HTML and XML documents to reset the tagName cache, allowing it to return a lowercase value once it's in the XML document. (LucasLefevre)
    • Fixed form submission to not happen when the form is invalid. (pozil)

    Version 17.0.0

    Breaking change: Node v12 is now the minimum supported version.

    Changelog

    Sourced from jsdom's changelog.

    19.0.0

    • Changed jsdom.nodeLocation() to return undefined when used on nodes that originate via fragment parsing (e.g., via innerHTML). Previously it would return based on the node location of the fragment string, which made node locations unreliable with respect to the original document source. This restores the behavior that was present in v14.0.0, and was accidentally broken in v14.1.0. (bakkot)
    • Fixed calling window.close() inside the Window's load event to no longer crash. (MattiasBuelens)

    18.1.1

    • Fixed connectedCallback to fire in situations involving document fragments, which was broken in v18.0.1. (GrantGryczan)

    18.1.0

    • Fixed headers.append() and headers.set() to normalize values. (MattiasBuelens)
    • Fixed pageshow events to have bubbles: true and cancelable: true. (MattiasBuelens)
    • Implemented the reason property on AbortSignals, along with the corresponding reason argument to abortSignal.abort() and AbortSignal.abort(). (MattiasBuelens)

    18.0.1

    • Fixed live Ranges to update correctly after calling node.normalize(). (hgiesel)
    • Fixed live Ranges to update correctly after removing child nodes. (hgiesel)
    • Fixed setting inputEl.valueAsDate = null to no longer throw an exception, but instead set the value to the empty string. (simon-weimann)
    • Improved performance of node insertion and node.contains(). (GrantGryczan)

    18.0.0

    Potentially-breaking bug fixes:

    • Fixed SSL certificate checking for WebSocket connections. Previously, invalid SSL certificates were always accepted; now, they properly respect the ResourceLoader's strictSSL option (which defaults to true).
    • Changed the global in which almost all Promise and TypeError instances are created to be the jsdom global, not the Node.js global. This could affect any code that uses instanceof.

    Other changes:

    • Fixed moving an element between HTML and XML documents to reset the tagName cache, allowing it to return a lowercase value once it's in the XML document. (LucasLefevre)
    • Fixed form submission to not happen when the form is invalid. (pozil)

    17.0.0

    Breaking change: Node v12 is now the minimum supported version.

    Commits
    • a604d67 Version 19.0.0
    • e46f76f Fix crash when calling window.close() inside load event listener
    • f9de3fd Do not track location information for fragment-parsed nodes
    • a61fdb8 Version 18.1.1
    • 15cbed6 Fix connectedCallback with document fragments
    • 79ff734 Version 18.1.0
    • a303721 Add AbortSignal's reason property
    • 158ada2 Update web platform tests + minor fixes
    • c98e0f5 Fix changelog username typo
    • b1ce1af Version 18.0.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
  • 6

    Allow users to bring their own styles

    Allow users to provide their own CSS. That way they can either tweak a theme to their liking, or even use it to bring their own custom theme. (maybe this can be used to build custom themes on a base of #44)

    This raises a few security questions. It would mean executing CSS on the user's device. While CSS is probably fine, executing any code that can be user-supplied warrants a closer look at how that is handled.

  • 7

    Demo page not working during local development

    Pages like http://localhost:8080/c/showmychat?theme=default&DEMO=true remain unpopulated. The CSS gets applied, as you can see the background change when you select a different theme, but messages never show up.

    The errors in the browser console: image

    I think what's happening is the errors in the console stop JS executions, and the script that should post faked messages never gets a chance to run.

    the console errors is textform: Access to fetch at 'https://api.betterttv.net/3/cached/users/twitch/762083631' from origin 'http://localhost:8080' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'https://localhost:8080' that is not equal to the supplied origin. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. bttvIntegration.js:80 GET https://api.betterttv.net/3/cached/users/twitch/762083631 net::ERR_FAILED 404 defaultFetch @ bttvIntegration.js:80 getBttvChannelEmoteDict @ bttvIntegration.js:67 init @ handleChat.js:228 await in init (async) (anonymous) @ handleChat.js:233 bttvIntegration.js:80

    Uncaught (in promise) TypeError: Failed to fetch at defaultFetch (bttvIntegration.js:80:25) at getBttvChannelEmoteDict (bttvIntegration.js:67:32) at init (handleChat.js:228:24) defaultFetch @ bttvIntegration.js:80 getBttvChannelEmoteDict @ bttvIntegration.js:67 init @ handleChat.js:228 await in init (async) (anonymous) @ handleChat.js:233

  • 8

    Deletion of messages after banning the author is not being reflected

    When a user first gets banned and then their message gets deleted, the chat view does update accordingly: the deleted message persists.

    Expected behavior

    A deleted message should be removed from the chat view, even if its author has been banned before.

    Additional info

    According to @BenDMyers, first deleting the message (and then banning the user) works fine. Observed during maxcellw's twitch stream.

  • 9

    Add HorseHorseHorse theme

    The newest horse-themed theme is ready get out there and mingle with some chats. Features include:

    • Colorful horse heads
    • Accessible handling of light names
    • Mild animations
    • Icons for Mods, VIPs, etc
  • 10

    document linting setup

    Adds extra details to CONTRIBUTING.md about the used linting tools, instructions on how to disable rules, instructions on how to ignore the pre-commit hook, editor extension information.

    Also adds a part to the colofon about them.

    Both files include a markdown footnote that enumerates the tools used, so this PR also adds support for markdown footnotes, by adding a markdown-it configuration that uses the footnotes package.

  • 11

    Allow users to specify how long a message should stay on screen

    Resolves #15 ā€” a fairly old issue.

    This pull request introduces a clearMessageAfter query parameter for the overlays, which accepts a positive integer. This is treated as number of milliseconds after which a message should be removed rom the overlay (assuming it hasn't already been deleted by, for instance, mods)

    Messages that are about to be deleted are now given the attribute data-twitch-message-status="deleting", as a style hook for themes to begin animating/transitioning for the message's departure. Because there's now a lot going on with message deletion, the approach has been standardized with a new removeMessage helper function.

    An example outbound animation can be seen in the default and animalese themes.

  • 12

    Create progress bar for fundraisers

    Now that Twitch has a fundraiser feature built in, I'm hoping they'll eventually expose it via API. It'd be great if we could get easy-to-use progress bars.

  • 13

    Allow config for buffering messages

    Inspired by @TheoBr's https://solidchat.vercel.app/

    In really fast chats, streamers might want to buffer their chat overlay so messages come up in bursts. We should consider having a system for buffering messages so that they can be shown in bursts.

    Suggested API:

    • Optional query parameter bufferMessages which, if provided, receives an integer count of milliseconds. Every n milliseconds, the buffer is flushed to the overlay display.
  • 14

    Document available css custom properties available for developers

    Currently, the custom properties available to developers for each message are only available when the DOM is inspected. it would be great to have some documentation surrounding these properties.

  • 15

    Support displaying pronouns from Alejo's Twitch Chat Pronouns API

    Alejo's Twitch Chat Pronouns service allows folks to authenticate with their Twitch accounts and set their pronouns. Anyone viewing a Twitch chat with the service's browser extension can see the pronouns alongside chatters' usernames if set. Pronouns are exposed via an API, which Alejo has specified in his server that he is comfortable with projects such as chat overlays so long as results are cached for "at least 10 to 15 minutes."

    This pull request introduces a showPronouns overlay configuration query parameter which, when enabled, queries Alejo's service and displays pronouns alongside the sender's username. Additionally, the message list item will be given a data-twitch-sender-pronouns attribute with the pronouns, to enable pronoun-specific styling within themes. If the user has not set their pronouns, no pronouns will be displayed for them. If someone sends a message at least 15 minutes after their first message while the overlay's active, the overlay will revalidate and update the cache if necessary.

    If the pronoun service is down, the overlay should (šŸ¤žšŸ») handle it gracefully and simply not display the pronoun.