changeset 8:e9df3bb010f4

fix issues
author Dennis C. M. <dennis@denniscm.com>
date Thu, 13 Mar 2025 17:41:42 +0000
parents a8aab75f68c9
children 228ab74e8321
files auth/auth.go auth/config.go auth/types.go bot/bot.go cmd/auth.go cmd/config.go cmd/events.go cmd/socket.go event/events.go event/types.go socket/conn.go socket/types.go
diffstat 12 files changed, 714 insertions(+), 446 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auth/auth.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,195 @@
+package auth
+
+import (
+	"encoding/json"
+	"log"
+	"net/http"
+	"net/url"
+)
+
+// TODO: Change unmarshall to JSON DECODE
+
+func GetAuthUrl() string {
+	config := ReadConfig()
+
+	baseUrl := &url.URL{
+		Scheme: "https",
+		Host:   "id.twitch.tv",
+		Path:   "/oauth2/authorize",
+	}
+
+	params := url.Values{}
+	params.Add("client_id", config.ClientId)
+	params.Add("force_verify", "true")
+	params.Add("redirect_uri", "http://localhost:8080/twitch-auth-code-callback")
+	params.Add("response_type", "code")
+	params.Add("scope", "channel:bot user:read:chat")
+	params.Add("state", "c3ab8aa609ea11e793ae92361f002671")
+
+	baseUrl.RawQuery = params.Encode()
+
+	return baseUrl.String()
+}
+
+func GetAuthToken(authCode string) AuthRes {
+	config := ReadConfig()
+
+	baseUrl := &url.URL{
+		Scheme: "https",
+		Host:   "id.twitch.tv",
+		Path:   "/oauth2/token",
+	}
+
+	formData := url.Values{}
+	formData.Add("client_id", config.ClientId)
+	formData.Add("client_secret", config.ClientSecret)
+	formData.Add("code", authCode)
+	formData.Add("grant_type", "authorization_code")
+	formData.Add("redirect_uri", "http://localhost:8080/twitch-auth-code-callback")
+
+	res, err := http.PostForm(baseUrl.String(), formData)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer res.Body.Close()
+
+	if res.StatusCode != 200 {
+		log.Fatal("GetAuthToken")
+	}
+
+	var authRes AuthRes
+
+	err = json.NewDecoder(res.Body).Decode(&authRes)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	return authRes
+}
+
+func IsAuthTokenValid(authToken string) bool {
+	baseUrl := &url.URL{
+		Scheme: "https",
+		Host:   "id.twitch.tv",
+		Path:   "oauth2/validate",
+	}
+
+	req, err := http.NewRequest("GET", baseUrl.String(), nil)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	req.Header.Set("Authorization", "OAuth "+authToken)
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer resp.Body.Close()
+
+	return resp.StatusCode == 200
+}
+
+func RevokeAuthToken(authToken string) {
+	config := ReadConfig()
+
+	baseUrl := &url.URL{
+		Scheme: "https",
+		Host:   "id.twitch.tv",
+		Path:   "oauth2/revoke",
+	}
+
+	data := url.Values{}
+	data.Add("client_id", config.ClientId)
+	data.Add("token", authToken)
+
+	res, err := http.PostForm(baseUrl.String(), data)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer res.Body.Close()
+}
+
+func RefreshAuthToken(authToken, refreshToken string) AuthRes {
+	config := ReadConfig()
+
+	baseUrl := &url.URL{
+		Scheme: "https",
+		Host:   "id.twitch.tv",
+		Path:   "oauth2/token",
+	}
+
+	data := url.Values{}
+	data.Add("grant_type", "refresh_token")
+	data.Add("refresh_token", refreshToken)
+	data.Add("client_id", config.ClientId)
+	data.Add("client_secret", config.ClientSecret)
+
+	res, err := http.PostForm(baseUrl.String(), data)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer res.Body.Close()
+
+	var authRes AuthRes
+
+	err = json.NewDecoder(res.Body).Decode(&authRes)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	return authRes
+}
+
+func GetUser(userName, authToken string) UserRes {
+	config := ReadConfig()
+
+	baseUrl := &url.URL{
+		Scheme: "https",
+		Host:   "api.twitch.tv",
+		Path:   "helix/users",
+	}
+
+	params := url.Values{}
+	params.Add("login", userName)
+
+	req, err := http.NewRequest("GET", baseUrl.String(), nil)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	req.Header.Set("Client-ID", config.ClientId)
+	req.Header.Set("Authorization", "Bearer "+authToken)
+
+	client := &http.Client{}
+	res, err := client.Do(req)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer res.Body.Close()
+
+	var userRes UserRes
+
+	err = json.NewDecoder(res.Body).Decode(&userRes)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	return userRes
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auth/config.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,33 @@
+package auth
+
+import (
+	"encoding/json"
+	"io"
+	"log"
+	"os"
+)
+
+func ReadConfig() Config {
+	file, err := os.Open(".config.json")
+
+	if err != nil {
+		log.Fatalf("Error opening file: %v", err)
+	}
+
+	defer file.Close()
+
+	bytes, err := io.ReadAll(file)
+
+	if err != nil {
+		log.Fatalf("Error reading file: %v", err)
+	}
+
+	var config Config
+
+	err = json.Unmarshal(bytes, &config)
+	if err != nil {
+		log.Fatalf("Error decoding JSON: %v", err)
+	}
+
+	return config
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/auth/types.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,19 @@
+package auth
+
+type Config struct {
+	ClientId          string `json:"client_id"`
+	ClientSecret      string `json:"client_secret"`
+	BroadcasterUserId string `json:"broadcaster_user_id"`
+}
+
+type UserRes struct {
+	Data []struct {
+		Id string `json:"id"`
+	} `json:"data"`
+}
+
+type AuthRes struct {
+	AccessToken  string   `json:"access_token"`
+	RefreshToken string   `json:"refresh_token"`
+	Scope        []string `json:"scope"`
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bot/bot.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,88 @@
+package bot
+
+import (
+	"fmt"
+	"log"
+	"os"
+	"strconv"
+	"time"
+)
+
+var quitTimer chan bool
+
+func HandleCmd(cmd []string) {
+	cmdReceived := cmd[0]
+	log.Printf("bot: %s command received", cmdReceived)
+
+	switch cmdReceived {
+	case "timer":
+		seconds, err := strconv.Atoi(cmd[1])
+
+		if err != nil {
+			log.Fatal("err: invalid command arguments")
+		}
+
+		if quitTimer != nil {
+			quitTimer <- true
+
+		}
+
+		quitTimer = make(chan bool)
+
+		go func() {
+			filename := "F:/Media/Twitch/Bot/timer.txt"
+
+			file, err := os.Create(filename)
+
+			if err != nil {
+				log.Fatal(err)
+			}
+
+			defer file.Close()
+
+			countdown := time.Duration(seconds) * time.Second
+			ticker := time.NewTicker(time.Second)
+			defer ticker.Stop()
+
+			log.Printf("bot: timer started with duration %d seconds", seconds)
+
+			for countdown > 0 {
+				select {
+				case <-ticker.C:
+					totalSeconds := int(countdown.Seconds())
+					minutes := totalSeconds / 60
+					seconds := totalSeconds % 60
+					countdownMsg := fmt.Sprintf("%02d:%02d", minutes, seconds)
+
+					file.Seek(0, 0)
+					_, err = file.WriteString("")
+
+					if err != nil {
+						log.Fatal(err)
+					}
+
+					_, err = file.WriteString(countdownMsg)
+
+					if err != nil {
+						log.Fatal(err)
+					}
+
+					log.Printf("bot: timer updated to %s", countdownMsg)
+
+					countdown -= time.Second
+				case <-quitTimer:
+					file.Seek(0, 0)
+					_, err = file.WriteString("")
+
+					if err != nil {
+						log.Fatal(err)
+					}
+
+					log.Println("bot: timer stopped")
+
+					return
+				}
+			}
+		}()
+	}
+}
--- a/cmd/auth.go	Wed Mar 12 14:45:00 2025 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,207 +0,0 @@
-package cmd
-
-import (
-	"encoding/json"
-	"log"
-	"net/http"
-	"net/url"
-)
-
-// TODO: Change unmarshall to JSON DECODE
-
-func GetAuthUrl() string {
-	config := readConfig()
-
-	baseUrl := &url.URL{
-		Scheme: "https",
-		Host:   "id.twitch.tv",
-		Path:   "/oauth2/authorize",
-	}
-
-	params := url.Values{}
-	params.Add("client_id", config.ClientId)
-	params.Add("force_verify", "true")
-	params.Add("redirect_uri", "http://localhost:8080/twitch-auth-code-callback")
-	params.Add("response_type", "code")
-	params.Add("scope", "channel:bot user:read:chat")
-	params.Add("state", "c3ab8aa609ea11e793ae92361f002671")
-
-	baseUrl.RawQuery = params.Encode()
-
-	return baseUrl.String()
-}
-
-type AuthRes struct {
-	AccessToken  string   `json:"access_token"`
-	RefreshToken string   `json:"refresh_token"`
-	Scope        []string `json:"scope"`
-}
-
-func GetAuthToken(authCode string) AuthRes {
-	config := readConfig()
-
-	baseUrl := &url.URL{
-		Scheme: "https",
-		Host:   "id.twitch.tv",
-		Path:   "/oauth2/token",
-	}
-
-	formData := url.Values{}
-	formData.Add("client_id", config.ClientId)
-	formData.Add("client_secret", config.ClientSecret)
-	formData.Add("code", authCode)
-	formData.Add("grant_type", "authorization_code")
-	formData.Add("redirect_uri", "http://localhost:8080/twitch-auth-code-callback")
-
-	res, err := http.PostForm(baseUrl.String(), formData)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	defer res.Body.Close()
-
-	if res.StatusCode != 200 {
-		log.Fatal("GetAuthToken")
-	}
-
-	var authRes AuthRes
-
-	err = json.NewDecoder(res.Body).Decode(&authRes)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	return authRes
-}
-
-func IsAuthTokenValid(authToken string) bool {
-	baseUrl := &url.URL{
-		Scheme: "https",
-		Host:   "id.twitch.tv",
-		Path:   "oauth2/validate",
-	}
-
-	req, err := http.NewRequest("GET", baseUrl.String(), nil)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	req.Header.Set("Authorization", "OAuth "+authToken)
-
-	client := &http.Client{}
-	resp, err := client.Do(req)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	defer resp.Body.Close()
-
-	return resp.StatusCode == 200
-}
-
-func RevokeAuthToken(authToken string) {
-	config := readConfig()
-
-	baseUrl := &url.URL{
-		Scheme: "https",
-		Host:   "id.twitch.tv",
-		Path:   "oauth2/revoke",
-	}
-
-	data := url.Values{}
-	data.Add("client_id", config.ClientId)
-	data.Add("token", authToken)
-
-	res, err := http.PostForm(baseUrl.String(), data)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	defer res.Body.Close()
-}
-
-func RefreshAuthToken(authToken, refreshToken string) AuthRes {
-	config := readConfig()
-
-	baseUrl := &url.URL{
-		Scheme: "https",
-		Host:   "id.twitch.tv",
-		Path:   "oauth2/token",
-	}
-
-	data := url.Values{}
-	data.Add("grant_type", "refresh_token")
-	data.Add("refresh_token", refreshToken)
-	data.Add("client_id", config.ClientId)
-	data.Add("client_secret", config.ClientSecret)
-
-	res, err := http.PostForm(baseUrl.String(), data)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	defer res.Body.Close()
-
-	var authRes AuthRes
-
-	err = json.NewDecoder(res.Body).Decode(&authRes)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	return authRes
-}
-
-type UserRes struct {
-	Data []struct {
-		Id string `json:"id"`
-	} `json:"data"`
-}
-
-func GetUser(userName, authToken string) UserRes {
-	config := readConfig()
-
-	baseUrl := &url.URL{
-		Scheme: "https",
-		Host:   "api.twitch.tv",
-		Path:   "helix/users",
-	}
-
-	params := url.Values{}
-	params.Add("login", userName)
-
-	req, err := http.NewRequest("GET", baseUrl.String(), nil)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	req.Header.Set("Client-ID", config.ClientId)
-	req.Header.Set("Authorization", "Bearer "+authToken)
-
-	client := &http.Client{}
-	res, err := client.Do(req)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	defer res.Body.Close()
-
-	var userRes UserRes
-
-	err = json.NewDecoder(res.Body).Decode(&userRes)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	return userRes
-}
--- a/cmd/config.go	Wed Mar 12 14:45:00 2025 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-package cmd
-
-import (
-	"encoding/json"
-	"io"
-	"log"
-	"os"
-)
-
-type Config struct {
-	ClientId          string `json:"client_id"`
-	ClientSecret      string `json:"client_secret"`
-	BroadcasterUserId string `json:"broadcaster_user_id"`
-}
-
-func readConfig() Config {
-	file, err := os.Open(".config.json")
-
-	if err != nil {
-		log.Fatalf("Error opening file: %v", err)
-	}
-
-	defer file.Close()
-
-	bytes, err := io.ReadAll(file)
-
-	if err != nil {
-		log.Fatalf("Error reading file: %v", err)
-	}
-
-	var config Config
-
-	err = json.Unmarshal(bytes, &config)
-	if err != nil {
-		log.Fatalf("Error decoding JSON: %v", err)
-	}
-
-	return config
-}
--- a/cmd/events.go	Wed Mar 12 14:45:00 2025 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-package cmd
-
-import (
-	"bytes"
-	"encoding/json"
-	"io"
-	"log"
-	"net/http"
-	"net/url"
-)
-
-type ChannelChatMsgSubPayload struct {
-	Payload struct {
-		Event struct {
-			Msg struct {
-				Text string `json:"text"`
-			} `json:"message"`
-		} `json:"event"`
-	} `json:"payload"`
-}
-
-func ChannelChatMsgSub(authToken, session_id string) {
-	config := readConfig()
-
-	data := map[string]interface{}{
-		"type":    "channel.chat.message",
-		"version": "1",
-		"condition": map[string]string{
-			"broadcaster_user_id": config.BroadcasterUserId,
-			"user_id":             config.BroadcasterUserId,
-		},
-		"transport": map[string]string{
-			"method":     "websocket",
-			"session_id": session_id,
-		},
-	}
-
-	jsonData, err := json.Marshal(data)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	eventSub(authToken, jsonData)
-}
-
-func eventSub(authToken string, subData []byte) {
-	baseUrl := &url.URL{
-		Scheme: "https",
-		Host:   "api.twitch.tv",
-		Path:   "helix/eventsub/subscriptions",
-	}
-
-	req, err := http.NewRequest("POST", baseUrl.String(), bytes.NewBuffer(subData))
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	config := readConfig()
-
-	req.Header.Set("Authorization", "Bearer "+authToken)
-	req.Header.Set("Client-Id", config.ClientId)
-	req.Header.Set("Content-Type", "application/json")
-
-	client := &http.Client{}
-	res, err := client.Do(req)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	defer res.Body.Close()
-
-	body, err := io.ReadAll(res.Body)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	log.Println(string(body))
-}
--- a/cmd/socket.go	Wed Mar 12 14:45:00 2025 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-package cmd
-
-import (
-	"encoding/json"
-	"log"
-	"net/url"
-	"os"
-	"os/signal"
-	"time"
-
-	"github.com/gorilla/websocket"
-)
-
-type MetadataRes struct {
-	Metadata struct {
-		MsgType string `json:"message_type"`
-		SubType string `json:"subscription_type"`
-	} `json:"metadata"`
-}
-
-type WelcomeMsgPayload struct {
-	Payload struct {
-		Session struct {
-			Id string `json:"id"`
-		} `json:"session"`
-	} `json:"payload"`
-}
-
-func ConnSocket(authToken string) {
-	interrupt := make(chan os.Signal, 1)
-	signal.Notify(interrupt, os.Interrupt)
-
-	baseUrl := url.URL{Scheme: "wss", Host: "eventsub.wss.twitch.tv", Path: "/ws"}
-
-	conn, _, err := websocket.DefaultDialer.Dial(baseUrl.String(), nil)
-
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	defer conn.Close()
-
-	done := make(chan struct{})
-
-	ticker := time.NewTicker(time.Second * 15)
-	defer ticker.Stop()
-
-	go func() {
-		defer close(done)
-
-		for {
-			_, msg, err := conn.ReadMessage()
-
-			if err != nil {
-				log.Fatal(err)
-			}
-
-			var metadataRes MetadataRes
-
-			if err := json.Unmarshal(msg, &metadataRes); err != nil {
-				log.Fatal(err)
-			}
-
-			switch msgType := metadataRes.Metadata.MsgType; msgType {
-			case "session_welcome":
-				var welcomeMsgRes WelcomeMsgPayload
-
-				if err := json.Unmarshal(msg, &welcomeMsgRes); err != nil {
-					log.Fatal(err)
-				}
-
-				ChannelChatMsgSub(authToken, welcomeMsgRes.Payload.Session.Id)
-			case "session_keepalive":
-				ticker.Reset(time.Second * 15)
-			case "notification":
-				switch subType := metadataRes.Metadata.SubType; subType {
-				case "channel.chat.message":
-					var channelChatMsgSubPayload ChannelChatMsgSubPayload
-
-					if err := json.Unmarshal(msg, &channelChatMsgSubPayload); err != nil {
-						log.Fatal(err)
-					}
-
-					log.Println(string(msg))
-					log.Println(channelChatMsgSubPayload.Payload.Event.Msg.Text)
-
-				}
-			default:
-				log.Fatalf("%s: message type not implemented", msgType)
-			}
-
-		}
-	}()
-
-	for {
-		select {
-		case <-done:
-			return
-		case <-interrupt:
-			err := conn.WriteMessage(
-				websocket.CloseMessage,
-				websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
-
-			if err != nil {
-				log.Fatal(err)
-			}
-
-			select {
-			case <-done:
-			case <-time.After(time.Second):
-			}
-			return
-		case <-ticker.C:
-			// TODO: Replace this with logic to reconnect
-			log.Fatal("connection closed: timeout")
-		}
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/event/events.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,74 @@
+package event
+
+import (
+	"bytes"
+	"encoding/json"
+	"log"
+	"net/http"
+	"net/url"
+
+	"github.com/denniscmcom/pacobot/auth"
+)
+
+func ChannelChatMsgSub(authToken, session_id string) {
+	config := auth.ReadConfig()
+
+	data := map[string]any{
+		"type":    "channel.chat.message",
+		"version": "1",
+		"condition": map[string]string{
+			"broadcaster_user_id": config.BroadcasterUserId,
+			"user_id":             config.BroadcasterUserId,
+		},
+		"transport": map[string]string{
+			"method":     "websocket",
+			"session_id": session_id,
+		},
+	}
+
+	jsonData, err := json.Marshal(data)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	log.Printf("event: subscribing to %s", data["type"])
+	eventSub(authToken, jsonData)
+}
+
+func eventSub(authToken string, subData []byte) {
+	baseUrl := &url.URL{
+		Scheme: "https",
+		Host:   "api.twitch.tv",
+		Path:   "helix/eventsub/subscriptions",
+	}
+
+	req, err := http.NewRequest("POST", baseUrl.String(), bytes.NewBuffer(subData))
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	config := auth.ReadConfig()
+
+	req.Header.Set("Authorization", "Bearer "+authToken)
+	req.Header.Set("Client-Id", config.ClientId)
+	req.Header.Set("Content-Type", "application/json")
+
+	client := &http.Client{}
+	res, err := client.Do(req)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	log.Printf("status code: %d", res.StatusCode)
+
+	if res.StatusCode != 202 {
+		log.Fatal("event: failed to subscribe to event")
+	}
+
+	log.Println("event: subscribed")
+
+	defer res.Body.Close()
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/event/types.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,15 @@
+package event
+
+type ChannelChatMsgSubPayload struct {
+	Payload struct {
+		Event struct {
+			ChatterUserId    string `json:"chatter_user_id"`
+			ChatterUserLogin string `json:"chatter_user_login"`
+			ChatterUserName  string `json:"chatter_user_name"`
+			Msg              struct {
+				Type string `json:"type"`
+				Text string `json:"text"`
+			} `json:"message"`
+		} `json:"event"`
+	} `json:"payload"`
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/socket/conn.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,173 @@
+package socket
+
+import (
+	"encoding/json"
+	"fmt"
+	"log"
+	"net/url"
+	"os"
+	"os/signal"
+	"strings"
+	"time"
+
+	"github.com/denniscmcom/pacobot/bot"
+	"github.com/denniscmcom/pacobot/event"
+	"github.com/gorilla/websocket"
+)
+
+func Connect(authToken string) {
+	interrupt := make(chan os.Signal, 1)
+	signal.Notify(interrupt, os.Interrupt)
+
+	baseUrl := url.URL{Scheme: "wss", Host: "eventsub.wss.twitch.tv", Path: "/ws"}
+
+	log.Println("socket: connecting...")
+	conn, _, err := websocket.DefaultDialer.Dial(baseUrl.String(), nil)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	defer conn.Close()
+
+	log.Println("socket: connected")
+
+	var timeout time.Ticker
+	done := make(chan bool)
+
+	go readMsg(done, conn, &timeout, authToken)
+
+	for {
+		select {
+		case <-interrupt:
+			closeConn(conn)
+
+			select {
+			case <-done:
+			case <-time.After(time.Second):
+			}
+			return
+		case <-done:
+			log.Println("socket: connection closed by server")
+			Connect(authToken)
+		case <-timeout.C:
+			log.Println("socket: connection lost")
+			timeout.Stop()
+			Connect(authToken)
+		}
+	}
+}
+
+func readMsg(done chan bool, conn *websocket.Conn, timeout *time.Ticker, authToken string) {
+	var timeout_secs time.Duration
+
+	for {
+		log.Println("socket: waiting for msg...")
+		_, msg, err := conn.ReadMessage()
+
+		if err != nil {
+			break
+		}
+
+		var resMetadata Res_Metadata
+
+		if err := json.Unmarshal(msg, &resMetadata); err != nil {
+			log.Fatal(err)
+		}
+
+		msgType := resMetadata.Metadata.MsgType
+		log.Printf("socket: %s msg received", msgType)
+
+		switch msgType {
+		case "session_welcome":
+			var resWelcome Res_Welcome
+
+			if err := json.Unmarshal(msg, &resWelcome); err != nil {
+				log.Fatal(err)
+			}
+
+			timeout_secs = time.Duration(resWelcome.Payload.Session.KeepaliveTimeout+3) * time.Second
+			timeout = time.NewTicker(timeout_secs)
+			defer timeout.Stop()
+
+			event.ChannelChatMsgSub(authToken, resWelcome.Payload.Session.Id)
+
+		case "session_keepalive":
+			timeout.Reset(timeout_secs)
+			log.Println("socket: timeout resetted")
+
+		case "notification":
+			var resMetadataNotif Res_Metadata_Notif
+
+			if err := json.Unmarshal(msg, &resMetadataNotif); err != nil {
+				log.Fatal(err)
+			}
+
+			subType := resMetadataNotif.Metadata.SubType
+			log.Printf("socket: %s event received", subType)
+
+			switch subType {
+			case "channel.chat.message":
+				var resNotifChannelChatMsg Res_Notif_ChannelChatMsg
+
+				if err := json.Unmarshal(msg, &resNotifChannelChatMsg); err != nil {
+					log.Fatal(err)
+				}
+
+				// TODO: Add to a function
+				jsonFormatted, err := json.MarshalIndent(resNotifChannelChatMsg, "", " ")
+
+				if err != nil {
+					log.Fatalf("socket: error al formatear")
+				}
+
+				// log.Println(resNotifChannelChatMsg.Payload.Event)
+				fmt.Print(string(jsonFormatted))
+
+				chatMsg := resNotifChannelChatMsg.Payload.Event.Msg.Text
+				log.Println(chatMsg)
+
+				if strings.HasPrefix(chatMsg, "!") {
+					bot.HandleCmd(strings.Split(chatMsg[1:], " "))
+				}
+			}
+		default:
+			log.Fatalf("%s: message type not implemented", msgType)
+		}
+	}
+
+	done <- true
+}
+
+func closeConn(conn *websocket.Conn) {
+	err := conn.WriteMessage(
+		websocket.CloseMessage,
+		websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	log.Println("socket: connection closed")
+}
+
+// func test() {
+// 	var res Response
+
+// 	// Deserializas
+// 	err := json.Unmarshal([]byte(jsonData), &res)
+
+// 	if err != nil {
+// 		fmt.Println("Error al deserializar:", err)
+// 		return
+// 	}
+
+// 	// Conviertes la estructura nuevamente a JSON formateado
+
+// 	formattedJSON, err := json.MarshalIndent(res, "", " ")
+
+// 	if err != nil {
+// 		fmt.Println("Error al formatear JSON:", err)
+// 		return
+// 	}
+// }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/socket/types.go	Thu Mar 13 17:41:42 2025 +0000
@@ -0,0 +1,117 @@
+package socket
+
+type Res_Metadata struct {
+	Metadata Metadata `json:"metadata"`
+}
+
+type Res_Metadata_Notif struct {
+	Metadata Metadata_Notif `json:"metadata"`
+}
+
+type Res_Welcome struct {
+	Metadata Metadata        `json:"metadata"`
+	Payload  Payload_Welcome `json:"payload"`
+}
+
+type Res_Keepalive struct {
+	Metadata Metadata          `json:"metadata"`
+	Payload  Payload_Keepalive `json:"payload"`
+}
+
+type Res_Notif_ChannelChatMsg struct {
+	Metadata Metadata_Notif               `json:"metadata,omitempty"`
+	Payload  Payload_Notif_ChannelChatMsg `json:"payload,omitempty"`
+}
+
+type Metadata struct {
+	MsgId        string `json:"message_id"`
+	MsgType      string `json:"message_type"`
+	MsgTimestamp string `json:"message_timestamp"`
+}
+
+type Metadata_Notif struct {
+	Metadata
+	SubType    string `json:"subscription_type"`
+	SubVersion string `json:"subscription_version"`
+}
+
+type Payload_Welcome struct {
+	Session struct {
+		Id               string `json:"id"`
+		Status           string `json:"status"`
+		ConnectedAt      string `json:"connected_at"`
+		KeepaliveTimeout int    `json:"keepalive_timeout_seconds"`
+	} `json:"session"`
+}
+
+type Payload_Keepalive struct {
+}
+
+type Payload_Notif_ChannelChatMsg struct {
+	Subscription Payload_Sub_Notif_ChannelChatMsg   `json:"subscription"`
+	Event        Payload_Event_Notif_ChannelChatMsg `json:"event"`
+}
+
+type Payload_Sub_Notif struct {
+	Id      string `json:"id"`
+	Status  string `json:"status"`
+	Type    string `json:"type"`
+	Version string `json:"version"`
+	Cost    int    `json:"cost"`
+
+	Transport struct {
+		Method    string `json:"method"`
+		SessionId string `json:"session_id"`
+	} `json:"transport"`
+
+	CreatedAt string `json:"created_at"`
+}
+
+type Payload_Sub_Notif_ChannelChatMsg struct {
+	Payload_Sub_Notif
+
+	Condition struct {
+		BroadcasterUserId string `json:"broadcaster_user_id"`
+		UserId            string `json:"user_id"`
+	} `json:"condition"`
+}
+
+type Payload_Event_Notif_ChannelChatMsg struct {
+	BroadcasterUserId    string `json:"broadcaster_user_id"`
+	BroadcasterUserLogin string `json:"broadcaster_user_login"`
+	BroadcasterUserName  string `json:"broadcaster_user_name"`
+	ChatterUserId        string `json:"chatter_user_id"`
+	ChatterUserLogin     string `json:"chatter_user_login"`
+	ChatterUserName      string `json:"chatter_user_name"`
+	MsgId                string `json:"message_id"`
+
+	Msg struct {
+		Text string `json:"text"`
+
+		Fragments []struct {
+			Type      string `json:"type"`
+			Text      string `json:"text"`
+			Cheermote string `json:"cheermote"`
+			Emote     string `json:"emote"`
+			Mention   string `json:"mention"`
+		} `json:"fragments"`
+	} `json:"message"`
+
+	Color string `json:"color"`
+
+	Badges []struct {
+		SetId string `json:"set_id"`
+		Id    string `json:"id"`
+		Info  string `json:"info"`
+	} `json:"badges"`
+
+	MsgType                     string `json:"message_type"`
+	Cheer                       string `json:"cheer"`
+	Reply                       string `json:"reply"`
+	ChannelPointsCustomRewardId string `json:"channel_points_custom_reward_id"`
+	SourceBroadcasterUserId     string `json:"source_broadcaster_user_id"`
+	SourceBroadcasterUserLogin  string `json:"source_broadcaster_user_login"`
+	SourceBroadcasterUserName   string `json:"source_broadcaster_user_name"`
+	SourceMessageId             string `json:"source_message_id"`
+	SourceBadges                string `json:"source_badges"`
+}