mirror of
https://codeberg.org/SafeTwitch/safetwitch-backend.git
synced 2024-12-22 05:02:58 -05:00
199 lines
4.6 KiB
Go
199 lines
4.6 KiB
Go
package chat
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"safetwitch-backend/extractor/chat/structs"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var u = url.URL{Scheme: "wss", Host: "irc-ws.chat.twitch.tv:443", Path: "/"}
|
|
var conn *websocket.Conn
|
|
var _ *http.Response
|
|
var err error
|
|
|
|
// If the connection is ready to subscribe to channels
|
|
var connectionReady = false
|
|
var streamersFollowing = map[string]bool{}
|
|
|
|
func BeginTwitchChatConnection() {
|
|
conn, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
|
log.Println("Connecting to Twitch IRC")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
defer conn.Close()
|
|
|
|
// Authenticate with server
|
|
err = sendMessage("CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands")
|
|
err = sendMessage("PASS none")
|
|
err = sendMessage("NICK justinfan333333333333")
|
|
if err != nil {
|
|
log.Printf("Failed to send message: %v", err)
|
|
}
|
|
|
|
// Follow streamers
|
|
for streamer := range streamersFollowing {
|
|
sendMessage("JOIN #" + streamer)
|
|
}
|
|
|
|
// Continue to listen for incoming messages from the server.
|
|
for {
|
|
_, msg, err := conn.ReadMessage()
|
|
if err != nil {
|
|
log.Printf("Failed to read message: %v", err)
|
|
connectionReady = false
|
|
expReconnect()
|
|
break
|
|
}
|
|
|
|
parseMessage(string(msg))
|
|
RemoveUnusedStreamers()
|
|
}
|
|
}
|
|
|
|
func expReconnect() {
|
|
// Attempt to reconnect with exponential backoff
|
|
waitTime := time.Second
|
|
maxWaitTime := 30 * time.Second
|
|
|
|
for {
|
|
log.Printf("Attempting to reconnect in %v...\n", waitTime)
|
|
time.Sleep(waitTime)
|
|
|
|
BeginTwitchChatConnection()
|
|
|
|
if waitTime < maxWaitTime {
|
|
waitTime *= 2
|
|
} else {
|
|
waitTime = maxWaitTime
|
|
}
|
|
}
|
|
}
|
|
|
|
func sendMessage(msg string) error {
|
|
return conn.WriteMessage(websocket.TextMessage, []byte(msg))
|
|
}
|
|
|
|
func parseMessage(msg string) error {
|
|
if !connectionReady && strings.Contains(msg, "001") {
|
|
connectionReady = true
|
|
log.Println("Authenticated with Twitch IRC!")
|
|
return nil
|
|
}
|
|
|
|
/*
|
|
example message
|
|
@badge-info=subscriber/2;badges=subscriber/0,premium/1;client-nonce=a245bea7f04bac06be7bd637144c3721;color=;display-name=EmbeddedMess;emotes=;first-msg=0;flags=16-19:A.3;id=3ad413d1-1ab1-4cd1-902e-020c29a7b45c;mod=0;returning-chatter=0;room-id=227217502;subscriber=1;tmi-sent-ts=1685535959939;turbo=0;user-id=693838516;user-type= :embeddedmess!embeddedmess@embeddedmess.tmi.twitch.tv PRIVMSG #dino_xx :Why doesn't the noob have loot keys on then?
|
|
*/
|
|
if !connectionReady {
|
|
return nil
|
|
}
|
|
|
|
// Three parts, metadata, middle, message SEE README.md
|
|
splitMsg := strings.Split(msg, " :")
|
|
if len(splitMsg) < 3 {
|
|
return nil
|
|
}
|
|
p1 := splitMsg[0]
|
|
p2 := splitMsg[1]
|
|
p3 := splitMsg[2]
|
|
|
|
parsedTags := parseTags(p1)
|
|
|
|
p2Split := strings.SplitAfter(p2, " ")
|
|
username := parseUsername(p2Split[0])
|
|
messageType := strings.Replace(p2Split[1], " ", "", 1)
|
|
channel := strings.Replace(p2Split[2], "#", "", 1)
|
|
|
|
parsedMessage := structs.TwitchMessageMetadata{
|
|
Username: username,
|
|
MessageType: messageType,
|
|
Channel: channel,
|
|
Message: p3,
|
|
Tags: parsedTags,
|
|
}
|
|
|
|
parsed, err := json.Marshal(parsedMessage)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
return err
|
|
}
|
|
|
|
clientsToSendTo := ClientHandler.FindClientsByStreamer(strings.Replace(channel, " ", "", 1))
|
|
for _, Client := range clientsToSendTo {
|
|
Client.send <- string(parsed)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseUsername(username string) string {
|
|
return strings.Split(username, "!")[0]
|
|
}
|
|
|
|
func parseTags(tags string) map[string]string {
|
|
// Split the data by semicolon separator
|
|
keyValues := strings.Split(tags, ";")
|
|
|
|
// Create an empty map to store the key-value pairs
|
|
var m = map[string]string{}
|
|
|
|
// Parse each key-value pair and add it to the map
|
|
for _, keyValue := range keyValues {
|
|
pair := strings.SplitN(keyValue, "=", 2)
|
|
if len(pair) == 2 {
|
|
m[pair[0]] = pair[1]
|
|
}
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func FollowStreamer(streamerName string) error {
|
|
err := sendMessage("JOIN #" + strings.ToLower(streamerName))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
streamersFollowing[streamerName] = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func SendMessage(msg string) error {
|
|
if connectionReady {
|
|
return conn.WriteMessage(websocket.TextMessage, []byte(msg))
|
|
}
|
|
|
|
return errors.New("connection not ready")
|
|
}
|
|
|
|
// function to check and remove unused streamers from server map
|
|
func RemoveUnusedStreamers() {
|
|
ClientHandler.Lock()
|
|
for streamer := range streamersFollowing {
|
|
found := false
|
|
// iterate over each client and their streamer map
|
|
|
|
for client, _ := range ClientHandler.Clients {
|
|
if client.FollowingStreamers[streamer] {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
// remove streamer from server map
|
|
delete(streamersFollowing, streamer)
|
|
sendMessage("PART #" + streamer)
|
|
}
|
|
}
|
|
ClientHandler.Unlock()
|
|
}
|