changeset 6:4deabe76bd7f

cmd: add CMD package
author Dennis C. M. <dennis@denniscm.com>
date Wed, 12 Mar 2025 14:13:24 +0000
parents 8cf3011fc949
children a8aab75f68c9
files cmd/auth.go cmd/config.go cmd/events.go cmd/socket.go go.mod go.sum main.go
diffstat 7 files changed, 639 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/auth.go	Wed Mar 12 14:13:24 2025 +0000
@@ -0,0 +1,203 @@
+package cmd
+
+import (
+	"bytes"
+	"encoding/json"
+	"io"
+	"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)
+	}
+
+	log.Println(authRes.Scope)
+
+	return authRes
+}
+
+func IsAuthTokenValid(authToken string) bool {
+	endpoint := "https://id.twitch.tv/oauth2/validate"
+	req, err := http.NewRequest("GET", endpoint, nil)
+
+	if err != nil {
+		log.Fatalf("Error creating request: %v\n", err)
+	}
+
+	req.Header.Set("Authorization", "OAuth "+authToken)
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+
+	if err != nil {
+		log.Fatalf("Error sending request: %v\n", err)
+	}
+
+	defer resp.Body.Close()
+
+	return resp.StatusCode == 200
+}
+
+func RevokeAuthToken(authToken string) {
+	config := readConfig()
+
+	data := url.Values{}
+	data.Set("client_id", config.ClientId)
+	data.Set("token", authToken)
+
+	endpoint := "https://id.twitch.tv/oauth2/revoke"
+	req, err := http.NewRequest("POST", endpoint, bytes.NewBufferString(data.Encode()))
+
+	if err != nil {
+		log.Fatalf("Error creating request: %v\n", err)
+	}
+
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+
+	if err != nil {
+		log.Fatalf("Error sending request: %v\n", err)
+	}
+
+	defer resp.Body.Close()
+}
+
+func RefreshAuthToken(authToken, refreshToken string) AuthRes {
+	config := readConfig()
+
+	data := url.Values{}
+	data.Set("grant_type", "refresh_token")
+	data.Set("refresh_token", refreshToken)
+	data.Set("client_id", config.ClientId)
+	data.Set("client_secret", config.ClientSecret)
+
+	endpoint := "https://id.twitch.tv/oauth2/token"
+	req, err := http.NewRequest("POST", endpoint, bytes.NewBufferString(data.Encode()))
+
+	if err != nil {
+		log.Fatalf("Error creating request: %v\n", err)
+	}
+
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+
+	if err != nil {
+		log.Fatalf("Error sending request: %v\n", err)
+	}
+
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+
+	if err != nil {
+		log.Fatalf("Error reading response body: %v", err)
+	}
+
+	var authResponse AuthRes
+
+	if err := json.Unmarshal(body, &authResponse); err != nil {
+		log.Fatalf("Error parsing JSON: %v", err)
+	}
+
+	return authResponse
+}
+
+// TODO: Return broadcaste user id
+func GetBroadcasterUserId(userName, authToken string) {
+	config := readConfig()
+
+	endpoint := "https://api.twitch.tv/helix/users?login=" + userName
+	req, err := http.NewRequest("GET", endpoint, nil)
+
+	if err != nil {
+		log.Fatalf("Error creating request: %v\n", err)
+	}
+
+	req.Header.Set("Client-ID", config.ClientId)
+	req.Header.Set("Authorization", "Bearer "+authToken)
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+
+	if err != nil {
+		log.Fatalf("Error sending request: %v\n", err)
+	}
+
+	defer resp.Body.Close()
+
+	body, err := io.ReadAll(resp.Body)
+
+	if err != nil {
+		log.Fatalf("Error reading response body: %v", err)
+	}
+
+	log.Println(string(body))
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/config.go	Wed Mar 12 14:13:24 2025 +0000
@@ -0,0 +1,39 @@
+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
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/events.go	Wed Mar 12 14:13:24 2025 +0000
@@ -0,0 +1,82 @@
+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))
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/socket.go	Wed Mar 12 14:13:24 2025 +0000
@@ -0,0 +1,118 @@
+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/go.mod	Wed Mar 12 14:13:24 2025 +0000
@@ -0,0 +1,34 @@
+module github.com/denniscmcom/pacobot
+
+go 1.23.5
+
+require (
+	github.com/bytedance/sonic v1.13.1 // indirect
+	github.com/bytedance/sonic/loader v0.2.4 // indirect
+	github.com/cloudwego/base64x v0.1.5 // indirect
+	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.8 // indirect
+	github.com/gin-contrib/sse v1.0.0 // indirect
+	github.com/gin-gonic/gin v1.10.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.25.0 // indirect
+	github.com/goccy/go-json v0.10.5 // indirect
+	github.com/gorilla/websocket v1.5.3
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.10 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.3 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.12 // indirect
+	golang.org/x/arch v0.15.0 // indirect
+	golang.org/x/crypto v0.36.0 // indirect
+	golang.org/x/net v0.37.0 // indirect
+	golang.org/x/sys v0.31.0 // indirect
+	golang.org/x/text v0.23.0 // indirect
+	google.golang.org/protobuf v1.36.5 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/go.sum	Wed Mar 12 14:13:24 2025 +0000
@@ -0,0 +1,80 @@
+github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
+github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
+github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
+github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
+github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
+github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
+github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
+github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
+github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
+github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
+github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
+golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
+google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.go	Wed Mar 12 14:13:24 2025 +0000
@@ -0,0 +1,83 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/denniscmcom/pacobot/cmd"
+	"github.com/gin-gonic/gin"
+)
+
+func main() {
+	gin.SetMode(gin.DebugMode)
+	r := gin.Default()
+
+	var authRes cmd.AuthRes
+
+	r.GET("/", func(c *gin.Context) {
+		c.JSON(http.StatusOK, gin.H{
+			"message": "Hello world",
+		})
+	})
+
+	// TODO: Pass username in parameters
+	r.GET("/id", func(c *gin.Context) {
+		cmd.GetBroadcasterUserId("denniscmartin", authRes.AccessToken)
+
+		c.JSON(http.StatusOK, gin.H{
+			"message": "ok",
+		})
+	})
+
+	r.GET("/auth", func(c *gin.Context) {
+		authUrl := cmd.GetAuthUrl()
+
+		c.Redirect(http.StatusMovedPermanently, authUrl)
+	})
+
+	r.GET("/auth-validate", func(c *gin.Context) {
+		msg := "failed"
+
+		if cmd.IsAuthTokenValid(authRes.AccessToken) {
+			msg = "ok"
+		}
+
+		c.JSON(http.StatusOK, gin.H{
+			"message": msg,
+		})
+	})
+
+	r.GET("/auth-refresh", func(c *gin.Context) {
+		authRes = cmd.RefreshAuthToken(authRes.AccessToken, authRes.RefreshToken)
+
+		c.JSON(http.StatusOK, gin.H{
+			"message": "ok",
+		})
+	})
+
+	r.GET("/auth-revoke", func(c *gin.Context) {
+		cmd.RevokeAuthToken(authRes.AccessToken)
+
+		c.JSON(http.StatusOK, gin.H{
+			"message": "ok",
+		})
+	})
+
+	r.GET("/twitch-auth-code-callback", func(c *gin.Context) {
+		authCode := c.Query("code")
+		authRes = cmd.GetAuthToken(authCode)
+
+		c.JSON(http.StatusOK, gin.H{
+			"message": "ok",
+		})
+	})
+
+	r.GET("/connect", func(c *gin.Context) {
+		go cmd.ConnSocket(authRes.AccessToken)
+
+		c.JSON(http.StatusOK, gin.H{
+			"message": "ok",
+		})
+	})
+
+	r.Run()
+}