diff --git a/api/cammie.go b/api/cammie.go index be2f72a..15cccf0 100644 --- a/api/cammie.go +++ b/api/cammie.go @@ -9,15 +9,15 @@ import ( gin "github.com/gin-gonic/gin" ) -// message struct -type message struct { +// messageCammie struct +type messageCammie struct { Message string `form:"message" json:"message" xml:"message" binding:"required"` } -// header struct -type header struct { +// headerCammie struct +type headerCammie struct { Name string `header:"X-Username"` - Ip string `header:"X-Real-IP"` + IP string `header:"X-Real-IP"` } var messages uint64 = 0 @@ -31,8 +31,8 @@ func getMessage(app *screen.ScreenApp, c *gin.Context) { func postMessage(app *screen.ScreenApp, c *gin.Context) { // Get structs - header := &header{} - message := &message{} + header := &headerCammie{} + message := &messageCammie{} // Check Header if err := c.ShouldBindHeader(header); err != nil { @@ -60,12 +60,12 @@ func postMessage(app *screen.ScreenApp, c *gin.Context) { return } newMessage = fmt.Sprintf("[%s[] %s", header.Name, message.Message) - } else if header.Ip != "" { - if slices.Contains(blockedIps, header.Ip) { + } else if header.IP != "" { + if slices.Contains(blockedIps, header.IP) { c.JSON(http.StatusOK, gin.H{"message": "Message received"}) return } - newMessage = fmt.Sprintf("<%s> %s", header.Ip, message.Message) + newMessage = fmt.Sprintf("<%s> %s", header.IP, message.Message) } else { newMessage = message.Message } diff --git a/api/spotify.go b/api/spotify.go index 89c0190..d2e3b08 100644 --- a/api/spotify.go +++ b/api/spotify.go @@ -1,10 +1,42 @@ package api import ( - "github.com/gin-gonic/gin" + "encoding/json" + "fmt" + "net/http" + "net/url" + "os" "scc/screen" + "strings" + "time" + + "github.com/gin-gonic/gin" ) +type spotifyMessage struct { + TrackID string `json:"track_id"` +} + +type spotifyTokenResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int64 `json:"expires_in"` +} + +type spotifyArtist struct { + Name string `json:"name"` +} + +type spotifyTrackResponse struct { + Name string `json:"name"` + Artists []spotifyArtist `json:"artists"` +} + +var accessToken = "" +var expiresOn int64 = 0 +var clientID = "d385173507a54bca93cc3327c0c2f5d9" +var clientSecret = "8e78977c1ba54b90b17f9dcd6b301c37" + func spotifyHandlerWrapper(app *screen.ScreenApp) func(*gin.Context) { return func(ctx *gin.Context) { spotifyHandler(app, ctx) @@ -12,6 +44,85 @@ func spotifyHandlerWrapper(app *screen.ScreenApp) func(*gin.Context) { } func spotifyHandler(app *screen.ScreenApp, ctx *gin.Context) { - b, _ := ctx.GetRawData() - app.Spotify.Update(string(b)) + message := &spotifyMessage{} + + if err := ctx.ShouldBindJSON(message); err != nil { + ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + ctx.JSON(http.StatusOK, gin.H{"track_id": "Track ID received"}) + + if expiresOn < time.Now().Unix() { + if err := setAccessToken(); err != nil { + fmt.Fprintf(os.Stderr, "Error: Unable to refresh spotify token: %s", err) + } + } + + track, err := getTrackTitle(message.TrackID) + + if err != nil { + fmt.Fprintf(os.Stderr, "Error: Unable to get track information: %s", err) + } + + app.Spotify.Update(track) +} + +func setAccessToken() error { + data := url.Values{} + data.Set("grant_type", "client_credentials") + data.Set("client_id", clientID) + data.Set("client_secret", clientSecret) + + // Send the POST request + resp, err := http.PostForm("https://accounts.spotify.com/api/token", data) + if err != nil { + return err + } + defer resp.Body.Close() + + // Check for a successful response + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("error: received non-200 status code %d", resp.StatusCode) + } + + message := &spotifyTokenResponse{} + if err := json.NewDecoder(resp.Body).Decode(message); err != nil { + return err + } + + accessToken = message.AccessToken + expiresOn = time.Now().Unix() + message.ExpiresIn + + return nil +} + +func getTrackTitle(trackID string) (string, error) { + url := fmt.Sprintf("https://api.spotify.com/v1/tracks/%s", trackID) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", err + } + req.Header.Set("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + trackResponse := &spotifyTrackResponse{} + if err := json.NewDecoder(resp.Body).Decode(trackResponse); err != nil { + return "", err + } + + trackTitle := trackResponse.Name + artistsNames := make([]string, len(trackResponse.Artists)) + for i, artist := range trackResponse.Artists { + artistsNames[i] = artist.Name + } + + return fmt.Sprintf("%s - %s", trackTitle, strings.Join(artistsNames, ", ")), nil } diff --git a/screen/cammie.go b/screen/cammie.go index 6d9bccf..83c962d 100644 --- a/screen/cammie.go +++ b/screen/cammie.go @@ -12,7 +12,7 @@ import ( var maxMessages = 20 // Available colors -var COLORS = [...]tcell.Color{ +var colors = [...]tcell.Color{ tcell.ColorViolet, tcell.ColorRed, tcell.ColorIndigo, @@ -52,21 +52,22 @@ func NewCammie(screenApp *ScreenApp) *Cammie { return &cammie } -// One-time setup +// Run one-time setup func (cammie *Cammie) Run() { // Wait for the view to be properly set up - time.Sleep(5 * time.Second) + time.Sleep(1 * time.Second) } // Updates the cammie chat // Gets called when a new message is received from the website func (cammie *Cammie) Update(message string) { - color := COLORS[lastColorIndex].String() - lastColorIndex = (lastColorIndex + 1) % len(COLORS) + color := colors[lastColorIndex].String() + lastColorIndex = (lastColorIndex + 1) % len(colors) - fmt.Fprintf(cammie.view, "\n[%s]%s", color, message) - - cammie.view.ScrollToEnd() + cammie.screenApp.execute(func() { + fmt.Fprintf(cammie.view, "\n[%s]%s", color, message) + cammie.view.ScrollToEnd() + }) } diff --git a/screen/graph1.go b/screen/graph1.go index c6fb98c..165dfab 100644 --- a/screen/graph1.go +++ b/screen/graph1.go @@ -4,13 +4,13 @@ import "github.com/rivo/tview" type Graph1 struct { ScreenApp *ScreenApp - view *tview.Box + view *tview.Box } func NewGraph1(screenApp *ScreenApp) *Graph1 { graph1 := Graph1{ ScreenApp: screenApp, - view: tview.NewBox().SetBorder(true).SetTitle("Graph 1"), + view: tview.NewBox().SetBorder(true).SetTitle(" Graph 1 "), } return &graph1 diff --git a/screen/spotify.go b/screen/spotify.go index 4c00875..1fa64f7 100644 --- a/screen/spotify.go +++ b/screen/spotify.go @@ -1,11 +1,12 @@ package screen import ( - "github.com/gdamore/tcell/v2" - "github.com/rivo/tview" "strings" "sync" "time" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" ) type Spotify struct { @@ -43,19 +44,17 @@ func (spotify *Spotify) Run() { if w != 0 { - spotify.mu.Lock() - - if len(spotify.buffer) != w { - if len(spotify.text) > w { - spotify.text = spotify.text[0 : w-4] - spotify.text += "..." + spotify.screenApp.execute(func() { + if len(spotify.buffer) != w { + if len(spotify.text) > w { + spotify.text = spotify.text[0 : w-4] + spotify.text += "..." + } + spotify.buffer = spotify.text + strings.Repeat(" ", w-len(spotify.text)) } - spotify.buffer = spotify.text + strings.Repeat(" ", w-len(spotify.text)) - } - - spotify.buffer = spotify.buffer[1:] + string(spotify.buffer[0]) - spotify.mu.Unlock() + spotify.buffer = spotify.buffer[1:] + string(spotify.buffer[0]) + }) spotify.screenApp.app.QueueUpdateDraw(func() { spotify.view.SetText(spotify.buffer) @@ -66,9 +65,8 @@ func (spotify *Spotify) Run() { } func (spotify *Spotify) Update(text string) { - spotify.mu.Lock() - defer spotify.mu.Unlock() - - spotify.text = text - spotify.buffer = "" + spotify.screenApp.execute(func() { + spotify.text = text + spotify.buffer = "" + }) } diff --git a/utils/queue.go b/utils/queue.go deleted file mode 100644 index a04ea58..0000000 --- a/utils/queue.go +++ /dev/null @@ -1,65 +0,0 @@ -package utils - -// Simple Queue implementation -type Queue[T any] struct { - maxSize int - Items []T -} - -// Create a new Queue with a maximum size -func NewQueue[T any](maxSize int) *Queue[T] { - return &Queue[T]{ - maxSize: maxSize, - Items: make([]T, 0, maxSize), - } -} - -// Add an item to the Queue -func (q *Queue[T]) Enqueue(item T) { - if len(q.Items) >= q.maxSize { - q.Items = q.Items[1:] - } - q.Items = append(q.Items, item) -} - -// Remove an item from the Queue -func (q *Queue[T]) Dequeue() (T, bool) { - if len(q.Items) == 0 { - var zero T - return zero, false - } - - item := q.Items[0] - q.Items = q.Items[1:] - return item, true -} - -// Get the first item in the Queue wtihout removing it -func (q *Queue[T]) Peek() (T, bool) { - if len(q.Items) == 0 { - var zero T - return zero, false - } - - return q.Items[0], true -} - -// Get all items in the Queue -func (q *Queue[T]) Get() []T { - return q.Items -} - -// Get the size of the Queue -func (q *Queue[T]) Size() int { - return len(q.Items) -} - -// Set the maximum size of the Queue -// If the new maximum size is smaller than the current size, the Queue will be truncated and items will potentially be lost -func (q *Queue[T]) SetMaxSize(maxSize int) { - q.maxSize = maxSize - - if len(q.Items) > maxSize { - q.Items = q.Items[:maxSize] - } -}