Files
glance/internal/glance/widget-twitch-top-games.go
2024-11-30 14:24:19 +00:00

126 lines
3.3 KiB
Go

package glance
import (
"context"
"errors"
"fmt"
"html/template"
"net/http"
"slices"
"strings"
"time"
)
var twitchGamesWidgetTemplate = mustParseTemplate("twitch-games-list.html", "widget-base.html")
type twitchGamesWidget struct {
widgetBase `yaml:",inline"`
Categories []twitchCategory `yaml:"-"`
Exclude []string `yaml:"exclude"`
Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"`
}
func (widget *twitchGamesWidget) initialize() error {
widget.
withTitle("Top games on Twitch").
withTitleURL("https://www.twitch.tv/directory?sort=VIEWER_COUNT").
withCacheDuration(time.Minute * 10)
if widget.Limit <= 0 {
widget.Limit = 10
}
if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 {
widget.CollapseAfter = 5
}
return nil
}
func (widget *twitchGamesWidget) update(ctx context.Context) {
categories, err := fetchTopGamesFromTwitch(widget.Exclude, widget.Limit)
if !widget.canContinueUpdateAfterHandlingErr(err) {
return
}
widget.Categories = categories
}
func (widget *twitchGamesWidget) Render() template.HTML {
return widget.renderTemplate(widget, twitchGamesWidgetTemplate)
}
type twitchCategory struct {
Slug string `json:"slug"`
Name string `json:"name"`
AvatarUrl string `json:"avatarURL"`
ViewersCount int `json:"viewersCount"`
Tags []struct {
Name string `json:"tagName"`
} `json:"tags"`
GameReleaseDate string `json:"originalReleaseDate"`
IsNew bool `json:"-"`
}
type twitchDirectoriesOperationResponse struct {
Data struct {
DirectoriesWithTags struct {
Edges []struct {
Node twitchCategory `json:"node"`
} `json:"edges"`
} `json:"directoriesWithTags"`
} `json:"data"`
}
const twitchDirectoriesOperationRequestBody = `[
{"operationName": "BrowsePage_AllDirectories","variables": {"limit": %d,"options": {"sort": "VIEWER_COUNT","tags": []}},"extensions": {"persistedQuery": {"version": 1,"sha256Hash": "2f67f71ba89f3c0ed26a141ec00da1defecb2303595f5cda4298169549783d9e"}}}
]`
func fetchTopGamesFromTwitch(exclude []string, limit int) ([]twitchCategory, error) {
reader := strings.NewReader(fmt.Sprintf(twitchDirectoriesOperationRequestBody, len(exclude)+limit))
request, _ := http.NewRequest("POST", twitchGqlEndpoint, reader)
request.Header.Add("Client-ID", twitchGqlClientId)
response, err := decodeJsonFromRequest[[]twitchDirectoriesOperationResponse](defaultHTTPClient, request)
if err != nil {
return nil, err
}
if len(response) == 0 {
return nil, errors.New("no categories could be retrieved")
}
edges := (response)[0].Data.DirectoriesWithTags.Edges
categories := make([]twitchCategory, 0, len(edges))
for i := range edges {
if slices.Contains(exclude, edges[i].Node.Slug) {
continue
}
category := &edges[i].Node
category.AvatarUrl = strings.Replace(category.AvatarUrl, "285x380", "144x192", 1)
if len(category.Tags) > 2 {
category.Tags = category.Tags[:2]
}
gameReleasedDate, err := time.Parse("2006-01-02T15:04:05Z", category.GameReleaseDate)
if err == nil {
if time.Since(gameReleasedDate) < 14*24*time.Hour {
category.IsNew = true
}
}
categories = append(categories, *category)
}
if len(categories) > limit {
categories = categories[:limit]
}
return categories, nil
}