Mercurial > public > pacobot
changeset 12:aaf85ae1f942
add very simple html template
author | Dennis C. M. <dennis@denniscm.com> |
---|---|
date | Thu, 20 Mar 2025 11:12:21 +0000 |
parents | 6d91c612310a |
children | e7ab74d2ad88 |
files | README.md api/handlers.go api/store.go app/handlers.go auth/auth.go auth/types.go bot/timer.go event/events.go event/types.go main.go socket/conn.go socket/types.go store/store.go www/blocks/head.html www/pages/index.html www/static/style.css |
diffstat | 16 files changed, 298 insertions(+), 226 deletions(-) [+] |
line wrap: on
line diff
--- a/README.md Sat Mar 15 17:08:03 2025 +0000 +++ b/README.md Thu Mar 20 11:12:21 2025 +0000 @@ -1,13 +1,1 @@ # pacobot - -## Instructions - -### Create a `.config.json` file with the following fields. - -```json -{ - "client_id": "", - "client_secret": "", - "broadcaster_user_id": "" -} -```
--- a/api/handlers.go Sat Mar 15 17:08:03 2025 +0000 +++ b/api/handlers.go Thu Mar 20 11:12:21 2025 +0000 @@ -5,27 +5,28 @@ "github.com/denniscmcom/pacobot/auth" "github.com/denniscmcom/pacobot/socket" + "github.com/denniscmcom/pacobot/store" "github.com/gin-gonic/gin" ) -func GetUserHandler(c *gin.Context) { +func GetUser(c *gin.Context) { userName := c.Query("username") - user := auth.GetUser(userName, getAccessToken()) + user := auth.GetUser(userName, store.GetAccessToken()) c.JSON(http.StatusOK, gin.H{ "message": user.Data[len(user.Data)-1].Id, }) } -func AuthHandler(c *gin.Context) { +func Auth(c *gin.Context) { authUrl := auth.GetAuthUrl() c.Redirect(http.StatusMovedPermanently, authUrl) } -func AuthValidateHandler(c *gin.Context) { +func AuthValidate(c *gin.Context) { msg := "failed" - if auth.IsAuthTokenValid(getAccessToken()) { + if auth.IsAuthTokenValid(store.GetAccessToken()) { msg = "ok" } @@ -34,37 +35,34 @@ }) } -func AuthRefreshHandler(c *gin.Context) { - authRes := auth.RefreshAuthToken(getAccessToken(), getRefreshToken()) - setAccessToken(authRes.AccessToken) - setRefreshToken(authRes.RefreshToken) - - c.JSON(http.StatusOK, gin.H{ - "message": "ok", - }) -} - -func AuthRevokeHandler(c *gin.Context) { - auth.RevokeAuthToken(getAccessToken()) +func AuthRefresh(c *gin.Context) { + authRes := auth.RefreshAuthToken(store.GetAccessToken(), store.GetRefreshToken()) + store.SetAccessToken(authRes.AccessToken) + store.SetRefreshToken(authRes.RefreshToken) c.JSON(http.StatusOK, gin.H{ "message": "ok", }) } -func TwitchCallbackHandler(c *gin.Context) { - authCode := c.Query("code") - authRes := auth.GetAuthToken(authCode) - authStore.Store("accessToken", authRes.AccessToken) - authStore.Store("refreshToken", authRes.RefreshToken) +func AuthRevoke(c *gin.Context) { + auth.RevokeAuthToken(store.GetAccessToken()) c.JSON(http.StatusOK, gin.H{ "message": "ok", }) } -func ConnectHandler(c *gin.Context) { - go socket.Connect(getAccessToken()) +func Twitch(c *gin.Context) { + authCode := c.Query("code") + authRes := auth.GetAuthToken(authCode) + store.SetAccessToken(authRes.AccessToken) + store.SetRefreshToken(authRes.RefreshToken) + c.Redirect(http.StatusFound, "/") +} + +func Connect(c *gin.Context) { + go socket.Connect(store.GetAccessToken()) c.JSON(http.StatusOK, gin.H{ "message": "ok",
--- a/api/store.go Sat Mar 15 17:08:03 2025 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -package api - -import ( - "log" - "sync" -) - -var authStore sync.Map - -func setAccessToken(accessToken string) { - authStore.Store("accessToken", accessToken) -} - -func setRefreshToken(refreshToken string) { - authStore.Store("refreshToken", refreshToken) -} - -func getAccessToken() string { - value, exists := authStore.Load("accessToken") - - if !exists { - log.Fatal("api: access token not found") - } - - return value.(string) -} - -func getRefreshToken() string { - value, exists := authStore.Load("refreshToken") - - if !exists { - log.Fatal("api: refresh token not found") - } - - return value.(string) -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/handlers.go Thu Mar 20 11:12:21 2025 +0000 @@ -0,0 +1,15 @@ +package app + +import ( + "net/http" + + "github.com/denniscmcom/pacobot/store" + "github.com/gin-gonic/gin" +) + +func Index(c *gin.Context) { + c.HTML(http.StatusOK, "index.html", gin.H{ + "title": "Pacobot", + "isLogged": store.IsAccessTokenSet(), + }) +}
--- a/auth/auth.go Sat Mar 15 17:08:03 2025 +0000 +++ b/auth/auth.go Thu Mar 20 11:12:21 2025 +0000 @@ -7,8 +7,6 @@ "net/url" ) -// TODO: Change unmarshall to JSON DECODE - func GetAuthUrl() string { config := ReadConfig() @@ -21,7 +19,7 @@ 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("redirect_uri", "http://localhost:8080/api/auth/twitch") params.Add("response_type", "code") params.Add("scope", "channel:bot user:read:chat") params.Add("state", "c3ab8aa609ea11e793ae92361f002671") @@ -31,7 +29,7 @@ return baseUrl.String() } -func GetAuthToken(authCode string) AuthRes { +func GetAuthToken(authCode string) Auth { config := ReadConfig() baseUrl := &url.URL{ @@ -45,7 +43,7 @@ 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") + formData.Add("redirect_uri", "http://localhost:8080/api/auth/twitch") res, err := http.PostForm(baseUrl.String(), formData) @@ -59,7 +57,7 @@ log.Fatal("GetAuthToken") } - var authRes AuthRes + var authRes Auth err = json.NewDecoder(res.Body).Decode(&authRes) @@ -119,7 +117,7 @@ defer res.Body.Close() } -func RefreshAuthToken(authToken, refreshToken string) AuthRes { +func RefreshAuthToken(authToken, refreshToken string) Auth { config := ReadConfig() baseUrl := &url.URL{ @@ -142,7 +140,7 @@ defer res.Body.Close() - var authRes AuthRes + var authRes Auth err = json.NewDecoder(res.Body).Decode(&authRes) @@ -153,7 +151,7 @@ return authRes } -func GetUser(userName, authToken string) UserRes { +func GetUser(userName, authToken string) User { config := ReadConfig() baseUrl := &url.URL{ @@ -183,7 +181,7 @@ defer res.Body.Close() - var userRes UserRes + var userRes User err = json.NewDecoder(res.Body).Decode(&userRes)
--- a/auth/types.go Sat Mar 15 17:08:03 2025 +0000 +++ b/auth/types.go Thu Mar 20 11:12:21 2025 +0000 @@ -6,13 +6,13 @@ BroadcasterUserId string `json:"broadcaster_user_id"` } -type UserRes struct { +type User struct { Data []struct { Id string `json:"id"` } `json:"data"` } -type AuthRes struct { +type Auth struct { AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` Scope []string `json:"scope"`
--- a/bot/timer.go Sat Mar 15 17:08:03 2025 +0000 +++ b/bot/timer.go Thu Mar 20 11:12:21 2025 +0000 @@ -17,7 +17,7 @@ quit = make(chan struct{}) go func() { - filename := "F:/Media/Twitch/Bot/timer.txt" + filename := "timer.txt" file, err := os.Create(filename) if err != nil {
--- a/event/events.go Sat Mar 15 17:08:03 2025 +0000 +++ b/event/events.go Thu Mar 20 11:12:21 2025 +0000 @@ -10,7 +10,7 @@ "github.com/denniscmcom/pacobot/auth" ) -func ChannelChatMsgSub(authToken, session_id string) { +func SubChannelChatMsg(authToken, session_id string) { config := auth.ReadConfig() data := map[string]any{ @@ -33,10 +33,10 @@ } log.Printf("event: subscribing to %s", data["type"]) - eventSub(authToken, jsonData) + subEvent(authToken, jsonData) } -func eventSub(authToken string, subData []byte) { +func subEvent(authToken string, subData []byte) { baseUrl := &url.URL{ Scheme: "https", Host: "api.twitch.tv",
--- a/event/types.go Sat Mar 15 17:08:03 2025 +0000 +++ b/event/types.go Thu Mar 20 11:12:21 2025 +0000 @@ -1,6 +1,6 @@ package event -type ChannelChatMsgSubPayload struct { +type ChannelChatMsgSub struct { Payload struct { Event struct { ChatterUserId string `json:"chatter_user_id"`
--- a/main.go Sat Mar 15 17:08:03 2025 +0000 +++ b/main.go Thu Mar 20 11:12:21 2025 +0000 @@ -2,6 +2,7 @@ import ( "github.com/denniscmcom/pacobot/api" + "github.com/denniscmcom/pacobot/app" "github.com/gin-gonic/gin" ) @@ -9,14 +10,26 @@ gin.SetMode(gin.DebugMode) r := gin.Default() + r.LoadHTMLGlob("./www/**/*.html") + r.Static("/static", "./www/static") - r.GET("/user", api.GetUserHandler) - r.GET("/auth", api.AuthHandler) - r.GET("/auth-validate", api.AuthValidateHandler) - r.GET("/auth-refresh", api.AuthRefreshHandler) - r.GET("/auth-revoke", api.AuthRevokeHandler) - r.GET("/twitch-auth-code-callback", api.TwitchCallbackHandler) - r.GET("/connect", api.ConnectHandler) + r.GET("/", app.Index) + + { + apiG := r.Group("/api") + apiG.GET("/user", api.GetUser) + apiG.GET("/connect", api.Connect) + + { + authG := apiG.Group("/auth") + authG.GET("/", api.Auth) + authG.GET("/validate", api.AuthValidate) + authG.GET("/refresh", api.AuthRefresh) + authG.GET("/revoke", api.AuthRevoke) + authG.GET("/twitch", api.Twitch) + } + + } r.Run() }
--- a/socket/conn.go Sat Mar 15 17:08:03 2025 +0000 +++ b/socket/conn.go Thu Mar 20 11:12:21 2025 +0000 @@ -69,59 +69,60 @@ break } - var resMetadata Res_Metadata + var metadataMsg MetadataMsg - if err := json.Unmarshal(msg, &resMetadata); err != nil { + if err := json.Unmarshal(msg, &metadataMsg); err != nil { log.Fatal(err) } - msgType := resMetadata.Metadata.MsgType + msgType := metadataMsg.Metadata.MsgType log.Printf("socket: %s msg received", msgType) switch msgType { case "session_welcome": - var resWelcome Res_Welcome + var welcomeMsg WelcomeMsg - if err := json.Unmarshal(msg, &resWelcome); err != nil { + if err := json.Unmarshal(msg, &welcomeMsg); err != nil { log.Fatal(err) } - timeout_secs = time.Duration(resWelcome.Payload.Session.KeepaliveTimeout+3) * time.Second + timeout_secs = time.Duration(welcomeMsg.Payload.Session.KeepaliveTimeoutSecs+3) * time.Second timeout = time.NewTicker(timeout_secs) defer timeout.Stop() - event.ChannelChatMsgSub(authToken, resWelcome.Payload.Session.Id) + event.SubChannelChatMsg(authToken, welcomeMsg.Payload.Session.Id) case "session_keepalive": timeout.Reset(timeout_secs) log.Println("socket: timeout resetted") case "notification": - var resMetadataNotif Res_Metadata_Notif + var metadataEvent MetadataEvent - if err := json.Unmarshal(msg, &resMetadataNotif); err != nil { + if err := json.Unmarshal(msg, &metadataEvent); err != nil { log.Fatal(err) } - subType := resMetadataNotif.Metadata.SubType + subType := metadataEvent.Metadata.SubType log.Printf("socket: %s event received", subType) switch subType { case "channel.chat.message": - var resNotifChannelChatMsg Res_Notif_ChannelChatMsg + var channelChatMsgEvent ChannelChatMsgEvent - if err := json.Unmarshal(msg, &resNotifChannelChatMsg); err != nil { + if err := json.Unmarshal(msg, &channelChatMsgEvent); err != nil { log.Fatal(err) } - chatMsg := resNotifChannelChatMsg.Payload.Event.Msg.Text + chatMsg := channelChatMsgEvent.Payload.Event.Msg.Text if strings.HasPrefix(chatMsg, "!") { - if resNotifChannelChatMsg.Payload.Event.ChatterUserName == "denniscmartin" { + if channelChatMsgEvent.Payload.Event.ChatterUserName == "denniscmartin" { go bot.HandleCmd(strings.Split(chatMsg[1:], " ")) } } } + default: log.Fatalf("socket: %s message type not implemented", msgType) } @@ -139,24 +140,3 @@ 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 -// } -// }
--- a/socket/types.go Sat Mar 15 17:08:03 2025 +0000 +++ b/socket/types.go Thu Mar 20 11:12:21 2025 +0000 @@ -1,117 +1,118 @@ 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 MetadataMsg struct { + Metadata struct { + MsgId string `json:"message_id"` + MsgType string `json:"message_type"` + MsgTimeStamp string `json:"message_timestamp"` + } `json:"metadata"` } -type Metadata struct { - MsgId string `json:"message_id"` - MsgType string `json:"message_type"` - MsgTimestamp string `json:"message_timestamp"` -} +type WelcomeMsg struct { + Metadata struct { + MsgId string `json:"message_id"` + MsgType string `json:"message_type"` + MsgTimeStamp string `json:"message_timestamp"` + } `json:"metadata"` -type Metadata_Notif struct { - Metadata - SubType string `json:"subscription_type"` - SubVersion string `json:"subscription_version"` + Payload struct { + Session struct { + Id string `json:"id"` + Status string `json:"status"` + ConnectedAt string `json:"connected_at"` + KeepaliveTimeoutSecs int `json:"keepalive_timeout_seconds"` + ReconnectUrl string `json:"reconnect_url"` + } `json:"session"` + } `json:"payload"` } -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 KeealiveMsg struct { + Metadata struct { + MsgId string `json:"message_id"` + MsgType string `json:"message_type"` + MsgTimeStamp string `json:"message_timestamp"` + } `json:"metadata"` + + Payload struct { + } `json:"payload"` } -type Payload_Keepalive struct { +type ReconnectMsg struct { + WelcomeMsg } -type Payload_Notif_ChannelChatMsg struct { - Subscription Payload_Sub_Notif_ChannelChatMsg `json:"subscription"` - Event Payload_Event_Notif_ChannelChatMsg `json:"event"` +type MetadataEvent struct { + Metadata struct { + MsgId string `json:"message_id"` + MsgType string `json:"message_type"` + MsgTimeStamp string `json:"message_timestamp"` + SubType string `json:"subscription_type"` + SubVersion string `json:"subscription_version"` + } `json:"metadata"` } -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"` +type ChannelChatMsgEvent struct { + Metadata MetadataEvent `json:"metadata"` - Transport struct { - Method string `json:"method"` - SessionId string `json:"session_id"` - } `json:"transport"` + Payload struct { + Sub struct { + Id string `json:"id"` + Status string `json:"status"` + Type string `json:"type"` + Version string `json:"version"` + + Condition struct { + BroadcasterUserId string `json:"broadcaster_user_id"` + UserId string `json:"user_id"` + } `json:"condition"` - CreatedAt string `json:"created_at"` -} + Transport struct { + Method string `json:"method"` + SessionId string `json:"session_id"` + } `json:"transport"` -type Payload_Sub_Notif_ChannelChatMsg struct { - Payload_Sub_Notif + CreatedAt string `json:"created_at"` + Cost int `json:"cost"` + } `json:"subscription"` - Condition struct { - BroadcasterUserId string `json:"broadcaster_user_id"` - UserId string `json:"user_id"` - } `json:"condition"` -} + Event 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"` -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"` - 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"` - 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"` - 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"` + 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"` + } `json:"event"` + } `json:"payload"` }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/store/store.go Thu Mar 20 11:12:21 2025 +0000 @@ -0,0 +1,42 @@ +package store + +import ( + "log" + "sync" +) + +var authStore sync.Map + +func SetAccessToken(accessToken string) { + authStore.Store("accessToken", accessToken) +} + +func SetRefreshToken(refreshToken string) { + authStore.Store("refreshToken", refreshToken) +} + +func GetAccessToken() string { + value, exists := authStore.Load("accessToken") + + if !exists { + log.Fatal("api: access token not found") + } + + return value.(string) +} + +func GetRefreshToken() string { + value, exists := authStore.Load("refreshToken") + + if !exists { + log.Fatal("api: refresh token not found") + } + + return value.(string) +} + +func IsAccessTokenSet() bool { + _, exists := authStore.Load("accessToken") + + return exists +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/blocks/head.html Thu Mar 20 11:12:21 2025 +0000 @@ -0,0 +1,6 @@ +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link href="/static/style.css" rel="stylesheet"> + <title>{{ .title }}</title> +</head> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/pages/index.html Thu Mar 20 11:12:21 2025 +0000 @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + {{ template "head.html" .}} + <style> + .container { + display: flex; + align-items: center; + gap: 10px; + } + </style> +</head> + +<body> + <h1>Hola, soy Pacobot, tu bot de Twitch.</h1> + <div class="col"> + <div class="container"> + <a href="api/auth"><button id="loginBtn">Login</button></a> + <div id="loginOkMsg">{{ if .isLogged}} ✅ Logged in {{ end }}</div> + </div> + <div class="container"> + <button id="connectBtn">Connect</button> + <div id="connectOkMsg" hidden>✅ Connected</div> + <div id="connectErrorMsg" hidden>❌ Connection failed</div> + </div> + </div> +</body> + +<script lang="js"> + const connectBtn = document.getElementById('connectBtn'); + const connectOkMsg = document.getElementById("connectOkMsg") + const connectErrorMsg = document.getElementById("connectErrorMsg") + + connectBtn.addEventListener('click', async () => { + try { + const response = await fetch('/api/connect'); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + connectOkMsg.hidden = false; + connectErrorMsg.hidden = true; + } catch (error) { + console.error('Login failed:', error); + connectOkMsg.hidden = true; + connectErrorMsg.hidden = false; + } + }); +</script> + +</html> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/www/static/style.css Thu Mar 20 11:12:21 2025 +0000 @@ -0,0 +1,14 @@ +body { + margin: auto; + width: 60%; + border: 3px solid gray; + padding: 20px; + font-family: Arial, sans-serif; + line-height: 1.6; +} + +.col { + display: flex; + flex-direction: column; + gap: 10px; +}