Skip to content

The chat with emotes

A chat message arrives in parts: text, emotes and cheer images. Widget.dom.renderMessage turns them into a fragment ready to append, with emotes as <img> and text as text.

manifest.json
{
"events": ["Twitch.ChatMessage", "Twitch.ChatMessageDeleted"],
"strings": { "es": {}, "en": {} },
"menu": {
"sections": [
{
"id": "chat",
"title": { "es": "Chat", "en": "Chat" },
"fields": [
{
"type": "number",
"id": "maxMessages",
"param": "maxMessages",
"label": { "es": "Máximo de mensajes", "en": "Maximum messages" },
"default": 20,
"min": 1,
"max": 100,
"step": 1
}
]
}
]
}
}
index.html
<div id="chat"></div>
style.css
html,
body {
margin: 0;
background: transparent;
}
#chat {
position: absolute;
inset: auto 0 0 0;
padding: 16px;
font: 600 28px system-ui, sans-serif;
color: #ffffff;
}
.message {
margin-top: 8px;
}
.message img {
height: 1.2em;
vertical-align: middle;
}
script.js
const chat = document.querySelector("#chat");
const maxMessages = Widget.parameters.int("maxMessages", 20);
Widget.on("Twitch.ChatMessage", (message) => {
const row = document.createElement("div");
row.className = "message";
row.dataset.messageId = message.messageId;
const name = document.createElement("strong");
Widget.dom.text(name, message.user);
const text = document.createElement("span");
text.append(Widget.dom.renderMessage(message.parts));
row.append(name, " ", text);
chat.append(row);
while (chat.children.length > maxMessages) chat.firstElementChild.remove();
});
Widget.on("Twitch.ChatMessageDeleted", ({ messageId }) => {
for (const row of chat.querySelectorAll(".message")) {
if (row.dataset.messageId === messageId) row.remove();
}
});
  • message.parts already carries the message split up; there is nothing to parse by hand. Emote images only show if they are from an allowed CDN.
  • The name goes through Widget.dom.text, never innerHTML: a name with HTML cannot sneak elements in.
  • The while loop caps the number of messages. Without it, a long chat would pile up elements endlessly and run out of memory (runtime_memory).
  • messageId identifies each message: when a moderator deletes one, Twitch.ChatMessageDeleted arrives with the same id and you remove it.
  • More platforms: Kick.ChatMessage and Youtube.Message carry the same fields, so the same function works: call it from three Widget.on.
  • Name color: the event carries color (the user’s on their platform): name.style.color = message.color.
  • Badges: message.badges is a list with name and imageUrl; draw each one with Widget.dom.image(badge.imageUrl, { alt: badge.name }).
  • The Chat template in “New widget” carries all of this and a full parameter menu.