From e01bd2eb9bca9e878f4bf46d46f6fb858e9de49f Mon Sep 17 00:00:00 2001 From: zomo Date: Wed, 7 Jan 2026 00:16:15 -0600 Subject: [PATCH] added basic ws commands --- .env.example | 4 ++ api/main.go | 2 +- api/ws/main.go | 130 ++++++++++++++++++++++++++++++++++++------------- util/conf.go | 7 +++ 4 files changed, 107 insertions(+), 36 deletions(-) diff --git a/.env.example b/.env.example index 8cd880b..b7f9a47 100644 --- a/.env.example +++ b/.env.example @@ -5,5 +5,9 @@ CLIENT_SECRET= # Twitch Client Secret REDIR_URI= # Twitch OAuth Redirect URI # Required +WS_AUTHORIZATION= # the authorization code all websocket clients will need to connect + # Required + # TODO this is not the final version, this will not be a permanent config option + SQLITE_DB= # SQlite DB location # Default: ./db.sqlite diff --git a/api/main.go b/api/main.go index 8324eae..a17983c 100644 --- a/api/main.go +++ b/api/main.go @@ -10,7 +10,7 @@ import ( func InitApiServer(conf *util.Config, dbConn *db.DBConn, twitchConn *ttv.TwitchConn) (*ApiServer, error) { engine := gin.Default() - wsServer, err := ws.InitWSServer(dbConn, twitchConn) + wsServer, err := ws.InitWSServer(conf, dbConn, twitchConn) if err != nil { return nil, err } diff --git a/api/ws/main.go b/api/ws/main.go index 9a60603..07950db 100644 --- a/api/ws/main.go +++ b/api/ws/main.go @@ -1,24 +1,35 @@ package ws import ( + "errors" + "fmt" "log" + "net" "net/http" + "strings" "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" "zomo.dev/largehadroncollider/db" "zomo.dev/largehadroncollider/ttv" + "zomo.dev/largehadroncollider/util" ) -func InitWSServer(dbConn *db.DBConn, twitchConn *ttv.TwitchConn) (*WSServer, error) { - return &WSServer{}, nil +func InitWSServer(conf *util.Config, dbConn *db.DBConn, twitchConn *ttv.TwitchConn) (*WSServer, error) { + return &WSServer{conf, dbConn, twitchConn}, nil } type WSServer struct { + conf *util.Config + db *db.DBConn + twitch *ttv.TwitchConn } type WSConn struct { + conf *util.Config + c net.Conn events []string + authrorized bool } func (wsServer *WSServer) Handle(w http.ResponseWriter, r *http.Request) error { @@ -27,42 +38,91 @@ func (wsServer *WSServer) Handle(w http.ResponseWriter, r *http.Request) error { return err } - authrorization := "" - - go func() { - defer conn.Close() - - for { - msg, _, err := wsutil.ReadClientData(conn) - if err != nil { - // TODO handle error better, print more conn info - log.Printf("Error reading data from ws connection %v", err) - continue - } - - cmd, err := parseCommand(string(msg)) - if err != nil { - log.Printf("Error parsing command data from ws connection %v", err) - continue - } - - log.Printf("%+v", cmd) - - if authrorization == "" { - // TODO authorize connection - continue - } - - // TODO run with cmd - - // TODO errors will be responded to the client - // if the response errors, log and exit the loop - } - }() + wsconn := &WSConn{} + go wsconn.handleThread(wsServer.conf, conn) return nil } -func runCommand(cmd *Command) { +func (conn *WSConn) handleThread(conf *util.Config, c net.Conn) { + conn.conf = conf + conn.c = c + defer c.Close() + for { + msg, _, err := wsutil.ReadClientData(conn.c) + if err != nil { + // TODO handle error better, print more conn info + log.Printf("Error reading data from ws connection %v", err) + continue + } + + cmd, err := parseCommand(string(msg)) + if err != nil { + log.Printf("Error parsing command data from ws connection %v", err) + continue + } + + log.Printf("%+v", cmd) + + err = conn.runCommand(cmd) + + if err != nil { + _, err := fmt.Fprintf(conn.c, "error running command: %s: %v", cmd.Command, err) + if err != nil { + // TODO better print + log.Printf("ERROR: unable to send error to client: %v", err) + log.Printf("ERROR: client details: %+v", conn) + log.Printf("ERROR: command details: %+v", cmd) + return + } + } + } +} + +func (conn *WSConn) runCommand(cmd *Command) error { + switch strings.ToLower(cmd.Command) { + case "ping": + return conn.runCommandPing(cmd.Args) + case "authorization": + return conn.runCommandAuthorization(cmd.Args) + + case "events-add": + return conn.runCommandEventsAdd(cmd.Args) + + default: + return fmt.Errorf("invalid command: %s", cmd.Command) + } +} + +func (conn *WSConn) runCommandPing(_ []string) error { + _, err := fmt.Fprint(conn.c, "pong") + return err +} + +func (conn *WSConn) runCommandAuthorization(args []string) error { + if conn.authrorized { + return errors.New("connection already authorized") + } + if len(args) < 1 { + return errors.New("no authorization key given") + } + + key := args[0] + if key != conn.conf.WSAuthorization { + return errors.New("invalid authorization key") + } + + conn.authrorized = true + return nil +} + +func (conn *WSConn) runCommandEventsAdd(args []string) error { + if !conn.authrorized { + return errors.New("unauthorized") + } + + // TODO subscribe + + return nil } diff --git a/util/conf.go b/util/conf.go index 59ed491..cc11583 100644 --- a/util/conf.go +++ b/util/conf.go @@ -27,6 +27,7 @@ type Config struct { ClientSecret string RedirectURI string SQliteDB string + WSAuthorization string } func (c *Config) def() { @@ -51,6 +52,9 @@ func (c *Config) loadEnv() error { if str, found := os.LookupEnv("SQLITE_DB"); found { c.SQliteDB = strings.TrimSpace(str) } + if str, found := os.LookupEnv("WS_AUTHORIZATION"); found { + c.WSAuthorization = strings.TrimSpace(str) + } return nil } @@ -65,6 +69,9 @@ func (c *Config) verify() error { if c.RedirectURI == "" { return errors.New("unable to load a configured Redirect URI") } + if c.WSAuthorization == "" { + return errors.New("unable to load a configured WS Authorization code") + } return nil }