0
Fork 0
mirror of https://codeberg.org/SafeTwitch/safetwitch-backend.git synced 2024-12-22 13:13:00 -05:00
safetwitch-backend/extractor/chat/twitchChat.go
2023-06-08 07:50:34 -04:00

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()
}