mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
129 lines
3.6 KiB
JavaScript
129 lines
3.6 KiB
JavaScript
const TWITCH_IRC_URL = "wss://irc-ws.chat.twitch.tv:443";
|
|
const ANON_PREFIX = "justinfan";
|
|
const ANON_PASSWORD = "SCHMOOPIIE";
|
|
|
|
const buildAnonymousNick = () => {
|
|
const suffix = Math.floor(Math.random() * 100000)
|
|
.toString()
|
|
.padStart(5, "0");
|
|
return `${ANON_PREFIX}${suffix}`;
|
|
};
|
|
|
|
const parseTags = (rawTags) => {
|
|
if (!rawTags) {
|
|
return {};
|
|
}
|
|
|
|
return rawTags.split(";").reduce((acc, entry) => {
|
|
const [key, value = ""] = entry.split("=");
|
|
acc[key] = value;
|
|
return acc;
|
|
}, {});
|
|
};
|
|
|
|
const parseIrcMessage = (rawLine) => {
|
|
let line = rawLine;
|
|
let tags = {};
|
|
let prefix = "";
|
|
|
|
if (line.startsWith("@")) {
|
|
const spaceIndex = line.indexOf(" ");
|
|
tags = parseTags(line.slice(1, spaceIndex));
|
|
line = line.slice(spaceIndex + 1);
|
|
}
|
|
|
|
if (line.startsWith(":")) {
|
|
const spaceIndex = line.indexOf(" ");
|
|
prefix = line.slice(1, spaceIndex);
|
|
line = line.slice(spaceIndex + 1);
|
|
}
|
|
|
|
const commandEnd = line.indexOf(" ");
|
|
const command = commandEnd === -1 ? line : line.slice(0, commandEnd);
|
|
const params = commandEnd === -1 ? "" : line.slice(commandEnd + 1);
|
|
|
|
return {
|
|
command,
|
|
params,
|
|
prefix,
|
|
tags,
|
|
raw: rawLine,
|
|
};
|
|
};
|
|
|
|
const extractChatMessage = (ircMessage) => {
|
|
if (ircMessage.command !== "PRIVMSG") {
|
|
return null;
|
|
}
|
|
|
|
const messageSplit = ircMessage.params.split(" :");
|
|
const channel = messageSplit[0]?.trim() || "";
|
|
const message = messageSplit.slice(1).join(" :");
|
|
const displayName = ircMessage.tags["display-name"] || ircMessage.prefix.split("!")[0];
|
|
|
|
return {
|
|
channel,
|
|
displayName,
|
|
message,
|
|
tags: ircMessage.tags,
|
|
prefix: ircMessage.prefix,
|
|
raw: ircMessage.raw,
|
|
};
|
|
};
|
|
|
|
export const connectTwitchChat = (channelName, onMessage = console.log) => {
|
|
if (!channelName) {
|
|
console.warn("Twitch chat connection skipped: missing channel name");
|
|
return () => {};
|
|
}
|
|
|
|
const normalizedChannel = channelName.toLowerCase();
|
|
const nick = buildAnonymousNick();
|
|
const socket = new WebSocket(TWITCH_IRC_URL);
|
|
|
|
socket.addEventListener("open", () => {
|
|
socket.send(`PASS ${ANON_PASSWORD}`);
|
|
socket.send(`NICK ${nick}`);
|
|
socket.send("CAP REQ :twitch.tv/tags twitch.tv/commands twitch.tv/membership");
|
|
socket.send(`JOIN #${normalizedChannel}`);
|
|
});
|
|
|
|
socket.addEventListener("message", (event) => {
|
|
const lines = String(event.data).split("\r\n").filter(Boolean);
|
|
|
|
lines.forEach((line) => {
|
|
if (line.startsWith("PING")) {
|
|
const payload = line.split(" ")[1] || ":tmi.twitch.tv";
|
|
socket.send(`PONG ${payload}`);
|
|
return;
|
|
}
|
|
|
|
const parsed = parseIrcMessage(line);
|
|
const chatMessage = extractChatMessage(parsed);
|
|
|
|
if (chatMessage) {
|
|
onMessage({
|
|
channel: chatMessage.channel,
|
|
displayName: chatMessage.displayName,
|
|
message: chatMessage.message,
|
|
tags: chatMessage.tags,
|
|
prefix: chatMessage.prefix,
|
|
raw: chatMessage.raw,
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
socket.addEventListener("close", () => {
|
|
console.info(`Twitch chat connection closed for #${normalizedChannel}`);
|
|
});
|
|
|
|
socket.addEventListener("error", (event) => {
|
|
console.error("Twitch chat connection error", event);
|
|
});
|
|
|
|
return () => {
|
|
socket.close();
|
|
};
|
|
};
|