diff options
| -rw-r--r-- | index.html | 104 | ||||
| -rw-r--r-- | scripts/chat.js | 125 | ||||
| -rw-r--r-- | style.css | 24 |
3 files changed, 139 insertions, 114 deletions
@@ -1,67 +1,63 @@ <!DOCTYPE html> <html> - <head> - <title>ilotterytea's chat widget</title> - <link rel="stylesheet" href="/style.css"> - </head> - <body class="center"> - <h1><img src="/logo.png" alt> ilotterytea's chat widget</h1> - <p>a simple, client-side, lightweight Twitch chat for your streams.</p> - <form action="/twitch.html" method="get"> + +<head> + <title>ilotterytea's chat widget</title> + <link rel="stylesheet" href="/style.css"> +</head> + +<body class="center"> + <h1><img src="/logo.png" alt> ilotterytea's chat widget</h1> + <p>a simple, client-side, lightweight Twitch chat for your streams.</p> + <form action="/twitch.html" method="get"> + <div> + <label for="channel">Channel name:</label> + <input type="text" name="channel" id="channel" placeholder="forsen"> + </div> + <div> + <label for="fetchrecentmessages">Fetch recent messages:</label> + <input type="checkbox" name="fetchrecentmessages" id="fetchrecentmessages" value="1"> + </div> + <div> + <label for="thirdpartyemotes">Enable Third-party emotes + <i>(FFZ, BTTV, 7TV, TinyEmotes)</i>:</label> + <input type="checkbox" name="thirdpartyemotes" id="thirdpartyemotes" value="1" checked> + </div> + <div> + <label for="membership">Show joined/parted chatters:</label> + <input type="checkbox" name="membership" id="membership" value="1"> + </div> + <div> + <label for="perplex">Perplex chat:</label> + <input type="checkbox" name="perplex" id="perplex" value="1"> + </div> + <details> + <summary>More...</summary> + <div> - <label for="channel">Channel name:</label> - <input type="text" name="channel" id="channel" - placeholder="forsen"> + <label for="ircserver">IRC server:</label> + <input type="url" name="ircserver" id="ircserver" placeholder="wss://irc-ws.chat.twitch.tv"> </div> + <div> - <label for="fetchrecentmessages">Fetch recent messages:</label> - <input type="checkbox" name="fetchrecentmessages" - id="fetchrecentmessages" - value="1"> + <label for="ircuser">IRC username:</label> + <input type="name" name="ircuser" id="ircuser" placeholder="justinfan12345"> </div> + <div> - <label for="thirdpartyemotes">Enable Third-party emotes - <i>(FFZ, BTTV, 7TV, TinyEmotes)</i>:</label> - <input type="checkbox" name="thirdpartyemotes" - id="thirdpartyemotes" - value="1" checked> + <label for="ircpass">IRC password:</label> + <input type="password" name="ircpass" id="ircpass" placeholder="65432"> </div> + <div> - <label for="membership">Show joined/parted chatters:</label> - <input type="checkbox" name="membership" id="membership" - value="1"> + <label for="tinyemotesinstances">TinyEmotes + instances:</label> + <textarea name="tinyemotesinstances" id="tinyemotesinstances" placeholder="1 URL per line"></textarea> </div> - <details> - <summary>More...</summary> - - <div> - <label for="ircserver">IRC server:</label> - <input type="url" name="ircserver" id="ircserver" - placeholder="wss://irc-ws.chat.twitch.tv"> - </div> - - <div> - <label for="ircuser">IRC username:</label> - <input type="name" name="ircuser" id="ircuser" - placeholder="justinfan12345"> - </div> - - <div> - <label for="ircpass">IRC password:</label> - <input type="password" name="ircpass" id="ircpass" - placeholder="65432"> - </div> - <div> - <label for="tinyemotesinstances">TinyEmotes - instances:</label> - <textarea name="tinyemotesinstances" - id="tinyemotesinstances" - placeholder="1 URL per line"></textarea> - </div> + </details> + <button type="submit">Generate</button> + </form> +</body> - </details> - <button type="submit">Generate</button> - </form> - </body> </html>
\ No newline at end of file diff --git a/scripts/chat.js b/scripts/chat.js index 7c23eeb..6183c2a 100644 --- a/scripts/chat.js +++ b/scripts/chat.js @@ -1,43 +1,43 @@ function parseIRCMessage(line) { - line = line.replace(/\r?\n$/, ''); - let tags = {}; - let prefix = null; - let nick = null; - let command = null; - const params = []; - - // tags - if (line[0] === '@') { - const end = line.indexOf(' '); - const rawTags = line.slice(1, end); - for (const tag of rawTags.split(';')) { - const [k, v = true] = tag.split('='); - tags[k] = v === true ? true : v; + line = line.replace(/\r?\n$/, ''); + let tags = {}; + let prefix = null; + let nick = null; + let command = null; + const params = []; + + // tags + if (line[0] === '@') { + const end = line.indexOf(' '); + const rawTags = line.slice(1, end); + for (const tag of rawTags.split(';')) { + const [k, v = true] = tag.split('='); + tags[k] = v === true ? true : v; + } + line = line.slice(end + 1); + } + + // prefix + if (line[0] === ':') { + const end = line.indexOf(' '); + prefix = line.slice(1, end); + const bang = prefix.indexOf('!'); + if (bang !== -1) nick = prefix.slice(0, bang); + line = line.slice(end + 1); } - line = line.slice(end + 1); - } - - // prefix - if (line[0] === ':') { - const end = line.indexOf(' '); - prefix = line.slice(1, end); - const bang = prefix.indexOf('!'); - if (bang !== -1) nick = prefix.slice(0, bang); - line = line.slice(end + 1); - } - - // command and params - const parts = line.split(' '); - command = parts.shift(); - for (let i = 0; i < parts.length; i++) { - if (parts[i][0] === ':') { - params.push(parts.slice(i).join(' ').slice(1)); - break; + + // command and params + const parts = line.split(' '); + command = parts.shift(); + for (let i = 0; i < parts.length; i++) { + if (parts[i][0] === ':') { + params.push(parts.slice(i).join(' ').slice(1)); + break; + } + if (parts[i].length) params.push(parts[i]); } - if (parts[i].length) params.push(parts[i]); - } - return { tags, prefix, nick, command, params }; + return { tags, prefix, nick, command, params }; } function addMessage(message) { @@ -45,30 +45,35 @@ function addMessage(message) { if (!messages || message.command != "PRIVMSG") { return; } - + // msg base const elem = document.createElement("p"); elem.classList.add("message"); - + + if (params.perplex) { + elem.classList.add("perplex"); + elem.style.top = `${Math.random() * 81}%`; + } + if ("id" in message.tags) { elem.setAttribute("msg-id", message.tags["id"]); } - + if ("reply-parent-msg-id" in message.tags && "reply-parent-user-login" in message.tags) { const replyMessage = document.querySelector(`.message[msg-id='${message.tags["reply-parent-msg-id"]}']>.content`); - + if (replyMessage) { const username = message.tags["reply-parent-user-login"]; message.params[1] = message.params[1].substring(username.length + 1).trim(); - + const replyThread = document.createElement("p"); replyThread.classList.add("message-thread"); replyThread.innerHTML = `Replying to @${username}: ${replyMessage.innerHTML}`; - + elem.append(replyThread); } - } - + } + // badges if ("badges" in message.tags && message.tags["badges"].length > 0) { for (const b of message.tags["badges"].split(",")) { @@ -81,7 +86,7 @@ function addMessage(message) { } } } - + // parsing twitch emotes if ("emotes" in message.tags && message.tags["emotes"].length > 0) { const parts = message.tags["emotes"].split("/"); @@ -89,7 +94,7 @@ function addMessage(message) { const p = part.split(":"); const id = p[0]; const positions = p[1].split(","); - + for (const pos of positions) { const p = pos.split("-"); const start = p[0]; @@ -99,7 +104,7 @@ function addMessage(message) { } } } - + // username if (message.nick != null && message.nick.length > 0) { const usernameElem = document.createElement("span"); @@ -110,7 +115,7 @@ function addMessage(message) { usernameElem.classList.add("author"); usernameElem.textContent = `${message.nick}:`; } - + // message text let msgWords = message.params[1].split(" "); for (let i = 0; i < msgWords.length; i++) { @@ -118,16 +123,16 @@ function addMessage(message) { msgWords[i] = `<img src="${emotes[msgWords[i]]}" loading="lazy" alt="${msgWords[i]}" class="emote" />`; } } - + const msgContentElem = document.createElement("span"); msgContentElem.classList.add("content"); elem.append(msgContentElem); msgContentElem.innerHTML += ' ' + msgWords.join(" "); - + messages.append(elem); - + window.scrollTo(0, document.body.scrollHeight); - + // remove old messages if (messages.childElementCount > 100) { for (let i = 0; i < messages.childElementCount - 100; i++) { @@ -152,19 +157,19 @@ function connectToChat(host, nick, password, room) { "joined": [], "parted": [] }; - + const membershipInterval = setInterval(() => { if (membership.joined.length > 0) { addSystemMessage(`Users joined: ${membership.joined.join(", ")}`, null); membership.joined = []; } - + if (membership.parted.length > 0) { addSystemMessage(`Users parted: ${membership.parted.join(", ")}`, null); membership.parted = []; } }, 10000); - + socket.addEventListener("open", () => { socket.send(`NICK ${nick}`); socket.send(`PASS ${password}`); @@ -175,10 +180,10 @@ function connectToChat(host, nick, password, room) { socket.send(`JOIN #${room}`); addSystemMessage("Connected to the chat!"); }); - + socket.addEventListener("message", (e) => { const lines = e.data.split("\r\n"); - + for (const line of lines) { const l = line.trim(); if (l.length == 0) { @@ -187,7 +192,7 @@ function connectToChat(host, nick, password, room) { let m = parseIRCMessage(l); console.log(m); addMessage(m); - + if (m.command == "JOIN" && m.nick != "justinfan12345") { membership.joined.push(m.nick); } else if (m.command == "PART" && m.nick != "justinfan12345") { @@ -197,7 +202,7 @@ function connectToChat(host, nick, password, room) { } } }); - + socket.addEventListener("close", (e) => { addMessage("Chat", `Disconnected from the chat: ${e.reason}`); addMessage("Chat", "Reconnecting to the chat in 5 seconds..."); @@ -214,7 +219,7 @@ function getRecentMessages(room) { addSystemMessage(`${json["error"]} (Recent messages)`); return; } - + for (const message of json["messages"]) { addMessage(parseIRCMessage(message)); } @@ -41,6 +41,20 @@ form div { color: gray; } +.perplex { + position: absolute; + top: 0; + left: 0; + animation: perplex-animation 30s forwards; + white-space: nowrap; +} + +.message.perplex, +.message.perplex:nth-child(even) { + background: unset; + border-bottom: unset; +} + .message-thread { color: gray; font-size: 12px; @@ -78,4 +92,14 @@ form div { .message:last-child { border-bottom: none; +} + +@keyframes perplex-animation { + from { + left: 100%; + } + + to { + left: -100%; + } }
\ No newline at end of file |
