summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--index.html104
-rw-r--r--scripts/chat.js125
-rw-r--r--style.css24
3 files changed, 139 insertions, 114 deletions
diff --git a/index.html b/index.html
index 32f47d7..2d528ec 100644
--- a/index.html
+++ b/index.html
@@ -1,67 +1,63 @@
<!DOCTYPE html>
<html>
- <head>
- <title>ilotterytea&apos;s chat widget</title>
- <link rel="stylesheet" href="/style.css">
- </head>
- <body class="center">
- <h1><img src="/logo.png" alt> ilotterytea&apos;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&apos;s chat widget</title>
+ <link rel="stylesheet" href="/style.css">
+</head>
+
+<body class="center">
+ <h1><img src="/logo.png" alt> ilotterytea&apos;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>&lpar;FFZ, BTTV, 7TV, TinyEmotes&rpar;</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>&lpar;FFZ, BTTV, 7TV, TinyEmotes&rpar;</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));
}
diff --git a/style.css b/style.css
index bec003a..c0053d1 100644
--- a/style.css
+++ b/style.css
@@ -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