0
Fork 0
mirror of https://codeberg.org/SafeTwitch/safetwitch-backend.git synced 2025-01-05 04:10:06 -05:00
safetwitch-backend/routes/chatIrcProxy.ts
2023-04-09 12:38:29 -04:00

184 lines
5.4 KiB
TypeScript

import { randomUUID } from 'crypto';
import ws from 'ws';
import { TwitchChat } from '../util/scraping/chat/chat';
interface ExtWebSocket extends ws {
id?: string;
}
export class TwitchChatServer {
private chat: TwitchChat;
private clients: { [k:string]: ExtWebSocket[] };
constructor() {
this.clients = {};
this.chat = new TwitchChat({
login: {
username: 'justinfan23423',
password: 'none'
},
channels: []
});
this.chat.connect()
this.chat.on('PRIVMSG', this.handlePrivateMessage.bind(this));
this.chat.on('CLEARCHAT', this.handleClearChat.bind(this));
this.chat.on('CLEARMSG', this.handleClearMessage.bind(this));
this.chat.on('GLOBALUSERSTATE', this.handleGlobalUserState.bind(this));
this.chat.on('HOSTTARGET', this.handleHostTarget.bind(this));
this.chat.on('NOTICE', this.handleNotice.bind(this));
this.chat.on('RECONNECT', this.handleReconnect.bind(this));
this.chat.on('ROOMSTATE', this.handleRoomState.bind(this));
this.chat.on('USERNOTICE', this.handleUserNotice.bind(this));
this.chat.on('USERSTATE', this.handleUserState.bind(this));
this.chat.on('WHISPER', this.handleWhisper.bind(this));
}
public async startWebSocketServer(server: ws.Server): Promise<void> {
server.on('connection', (ws: ws) => {
const socket = ws as ExtWebSocket
socket.on('message', this.handleWebSocketMessage.bind(this, socket));
socket.on('close', this.handleWebSocketClose.bind(this, socket));
});
}
private handlePrivateMessage = async(username: string, type: string, channel: string, message: string, tags: Record<string,string>) => {
const socketsToSend = await this.findClientsForStreamer(channel)
for(let socket of socketsToSend) {
let payload = {
type: 'PRIVMSG',
username,
channel,
message,
tags
}
socket.send(JSON.stringify(payload))
}
}
private handleClearChat = async(channel: string, username: string, tags: Record<string,string>) => {
const socketsToSend = await this.findClientsForStreamer(channel)
for(let socket of socketsToSend) {
let payload = {
type: 'CLEARCHAT',
username,
channel,
tags
}
socket.send(JSON.stringify(payload))
}
}
private handleClearMessage = async(channel: string, messageId: string, tags: Record<string,string>) => {
const socketsToSend = await this.findClientsForStreamer(channel)
for(let socket of socketsToSend) {
let payload = {
type: 'CLEARMSG',
channel,
tags
}
socket.send(JSON.stringify(payload))
}
}
private handleGlobalUserState = async() => {
// Do nothing for now
}
private handleHostTarget = async(channel: string, viewers: number, target: string, tags: Record<string,string>) => {
const socketsToSend = await this.findClientsForStreamer(channel)
for(let socket of socketsToSend) {
let payload = {
type: 'HOSTTARGET',
channel,
target,
viewers,
tags
}
socket.send(JSON.stringify(payload))
}
}
private handleNotice = async(channel: string, message: string) => {
// Do nothing for now
}
private handleReconnect = async() => {
// Do nothing for now
}
private handleRoomState = async(channel: string, isLive: boolean, tags: Record<string, string>) => {
// Do nothing for now
}
private handleUserNotice = async(channel: string, type: string, message: string, tags: { [id: string]: string }) => {
const socketsToSend = await this.findClientsForStreamer(channel)
for(let socket of socketsToSend) {
let payload = {
type: 'USERNOTICE',
channel,
message,
tags
}
socket.send(JSON.stringify(payload))
}
}
private handleUserState = async(channel: string, tags: { [id: string]: string }) => {
// Do nothing for now
}
private handleWhisper = async(username: string, message: string, tags: { [id: string]: string }) => {
// Do nothing for now
}
private async findClientsForStreamer(streamerName: string): Promise<ExtWebSocket[]> {
if(!this.clients[streamerName]) return []
return this.clients[streamerName];
}
private async handleWebSocketMessage(socket: ExtWebSocket, message: ws.Data) {
const data = message.toString()
const splitted = data.split(' ')
if (splitted.length < 2 || splitted[0] !== 'JOIN') {
socket.close();
return;
}
const streamersToJoin = splitted[1].split(',')
if (streamersToJoin.length > 1) {
socket.close();
return;
}
const id = randomUUID()
for (let streamer of streamersToJoin) {
this.chat.addStreamer(streamer)
if (this.clients[streamer]) {
this.clients[streamer].push(socket)
} else {
this.clients[streamer] = [socket]
}
}
socket.id = id
socket.send('OK')
}
private handleWebSocketClose(socket: ExtWebSocket) {
if (socket.id) {
// Remove the socket from the list of clients following each streamer
Object.entries(this.clients).forEach(([streamer, sockets]) => {
this.clients[streamer] = sockets.filter(s => s !== socket);
if (this.clients[streamer].length === 0) {
// No more clients following this streamer, remove it from the chat
this.chat.removeStreamer(streamer);
delete this.clients[streamer];
}
});
}
}
}