Quellcode durchsuchen

Add BGG Hotness widget and associated doc entry

Taffaz vor 9 Monaten
Ursprung
Commit
15db5899f8

+ 24 - 0
docs/configuration.md

@@ -30,6 +30,7 @@
   - [Twitch Top Games](#twitch-top-games)
   - [Twitch Top Games](#twitch-top-games)
   - [iframe](#iframe)
   - [iframe](#iframe)
   - [HTML](#html)
   - [HTML](#html)
+  - [BGG Hotness](#bgghotness)
 
 
 ## Intro
 ## Intro
 Configuration is done via a single YAML file and a server restart is required in order for any changes to take effect. Trying to start the server with an invalid config file will result in an error.
 Configuration is done via a single YAML file and a server restart is required in order for any changes to take effect. Trying to start the server with an invalid config file will result in an error.
@@ -1665,3 +1666,26 @@ Example:
 ```
 ```
 
 
 Note the use of `|` after `source:`, this allows you to insert a multi-line string.
 Note the use of `|` after `source:`, this allows you to insert a multi-line string.
+
+### bgghotness
+Display the current games listed in the Board Game Geek Hotness list
+
+Example:
+
+```yaml
+- type: bgghotness
+  limit: 20
+  collapse-after-rows: 4
+```
+
+#### Properties
+| Name | Type | Required | Default |
+| ---- | ---- | -------- | ------- |
+| limit | integer | no | 20 |
+| collapse-after-rows| integer | no | 4 |
+
+##### `limit`
+The number of games to show. The BGG hotness has a maximum of 50 so any number greater than that will be clamped at 50
+
+##### `collapse-after-rows`
+How many rows of games are shown before the "SHOW MORE" button appears. Set to `-1` to never collapse.

+ 1 - 0
internal/assets/templates.go

@@ -39,6 +39,7 @@ var (
 	ExtensionTemplate             = compileTemplate("extension.html", "widget-base.html")
 	ExtensionTemplate             = compileTemplate("extension.html", "widget-base.html")
 	GroupTemplate                 = compileTemplate("group.html", "widget-base.html")
 	GroupTemplate                 = compileTemplate("group.html", "widget-base.html")
 	DNSStatsTemplate              = compileTemplate("dns-stats.html", "widget-base.html")
 	DNSStatsTemplate              = compileTemplate("dns-stats.html", "widget-base.html")
+    BGGHotnessTemplate            = compileTemplate("bgghotness.html", "widget-base.html")
 )
 )
 
 
 var globalTemplateFunctions = template.FuncMap{
 var globalTemplateFunctions = template.FuncMap{

+ 17 - 0
internal/assets/templates/bgghotness.html

@@ -0,0 +1,17 @@
+{{ template "widget-base.html" . }}
+
+{{ define "widget-content" }}
+<div class="cards-grid collapsible-container" data-collapse-after-rows="{{ .CollapseAfterRows }}">
+    {{ range .Games }}
+    <div class="card widget-content-frame thumbnail-parent">
+        <img class="thumbnail" loading="lazy" src="{{ .ThumbnailUrl.PreFilter }}filters:strip_icc(){{ .ThumbnailUrl.PostFilter}}" alt="">
+        <div class="margin-top-10 margin-bottom-widget flex flex-column grow padding-inline-widget">
+            <a class="video-title color-primary-if-not-visited" href="{{ .BGGGameLink }}">{{ .Name }}</a>
+            <ul class="list-horizontal-text flex-nowrap margin-top-7">
+                <li class="min-width-0">Rank: {{ .Rank }}</li>
+            </ul>
+        </div>
+    </div>
+    {{ end }}
+</div>
+{{ end }}

+ 71 - 0
internal/feed/bgghotness.go

@@ -0,0 +1,71 @@
+package feed
+
+import (
+    "encoding/xml"
+    "io"
+    "net/http"
+    "strings"
+)
+
+type BGGFeedResponseXML struct {
+    XMLName xml.Name    `xml:"items"`
+    Items   []struct {
+        XMLName xml.Name    `xml:"item"`
+        ID string   `xml:"id,attr"`
+        Thumbnail   struct {
+            Value   string  `xml:"value,attr"`
+        }   `xml:"thumbnail"`
+        Name   struct {
+            Value   string  `xml:"value,attr"`
+        }   `xml:"name"`
+        YearPublished   struct {
+            Value   string  `xml:"value,attr"`
+        }   `xml:"yearpublished"`
+        Rank    string  `xml:"rank,attr"`
+    }   `xml:"item"`
+}
+
+func FetchBGGHotnessList() (BggBoardGames, error){
+    resp, err := http.Get("https://boardgamegeek.com/xmlapi2/hot?boardgame")
+    if err != nil {
+        return BggBoardGames{}, err
+    }
+
+    defer resp.Body.Close()
+
+    data, err := io.ReadAll(resp.Body)
+    if err != nil {
+        return BggBoardGames{}, err
+    }
+
+    var hotnessFeed BGGFeedResponseXML
+    err = xml.Unmarshal(data, &hotnessFeed)
+    if err != nil {
+        return BggBoardGames{}, err
+    }
+
+    bggGames := getItemsFromBGGFeedTask(hotnessFeed)
+    
+    return bggGames, nil
+}
+
+func getItemsFromBGGFeedTask(response BGGFeedResponseXML) (BggBoardGames) {
+    games := make(BggBoardGames, 0, len(response.Items))
+
+    for _, item := range response.Items {
+        splitUrl :=  strings.Split(item.Thumbnail.Value, "filters:strip_icc()")
+        thumbUrl := ThumbnailUrl { splitUrl[0], splitUrl[1]}
+        bggBoardGame := BggBoardGame {
+            ID:             item.ID,
+            ThumbnailUrl:   thumbUrl, 
+            Name:           item.Name.Value,
+            YearPublished:  item.YearPublished.Value,
+            Rank:           item.Rank,
+            BGGGameLink:    "https://boardgamegeek.com/boardgame/" + item.ID,
+        }
+
+        games = append(games, bggBoardGame)
+    }
+
+    return games
+}

+ 16 - 0
internal/feed/primitives.go

@@ -63,6 +63,22 @@ type Video struct {
 
 
 type Videos []Video
 type Videos []Video
 
 
+type BggBoardGame struct {
+    ID              string
+    ThumbnailUrl    BggThumbnailUrl
+    Name            string
+    YearPublished   string
+    Rank            string
+    BGGGameLink     string
+}
+
+type BggBoardGames []BggBoardGame
+
+type BggThumbnailUrl struct {
+    PreFilter       string
+    PostFilter      string
+}
+
 var currencyToSymbol = map[string]string{
 var currencyToSymbol = map[string]string{
 	"USD": "$",
 	"USD": "$",
 	"EUR": "€",
 	"EUR": "€",

+ 49 - 0
internal/widget/bgghotness.go

@@ -0,0 +1,49 @@
+package widget
+
+import (
+	"context"
+	"html/template"
+	"time"
+
+	"github.com/glanceapp/glance/internal/assets"
+	"github.com/glanceapp/glance/internal/feed"
+)
+
+type BGGHotness struct {
+    widgetBase  `yaml:",inline"`
+    Games       feed.BggBoardGames  `yaml:"`
+    CollapseAfterRows int         `yaml:"collapse-after-rows"`
+	Limit             int         `yaml:"limit"`
+}
+
+func (widget *BGGHotness) Initialize() error {
+    widget.withTitle("BGG Hotness").withCacheDuration(time.Hour)
+
+    if widget.Limit <= 0 {
+        widget.Limit = 20
+    }
+
+    if widget.CollapseAfterRows == 0 || widget.CollapseAfterRows < -1 {
+        widget.CollapseAfterRows = 4
+    }
+    return nil
+}
+
+func (widget *BGGHotness) Update(ctx context.Context) {
+    games, err := feed.FetchBGGHotnessList()
+
+    if !widget.canContinueUpdateAfterHandlingErr(err) {
+        return
+    }
+
+    if len(games) > widget.Limit {
+        games = games[:widget.Limit]
+    }
+
+    widget.Games = games
+}
+
+func (widget *BGGHotness) Render() template.HTML {
+    
+    return widget.render(widget, assets.BGGHotnessTemplate) 
+}

+ 3 - 1
internal/widget/widget.go

@@ -63,10 +63,12 @@ func New(widgetType string) (Widget, error) {
 		widget = &Search{}
 		widget = &Search{}
 	case "extension":
 	case "extension":
 		widget = &Extension{}
 		widget = &Extension{}
-	case "group":
+    case "group":
 		widget = &Group{}
 		widget = &Group{}
 	case "dns-stats":
 	case "dns-stats":
 		widget = &DNSStats{}
 		widget = &DNSStats{}
+    case "bgghotness":
+        widget = &BGGHotness{}
 	default:
 	default:
 		return nil, fmt.Errorf("unknown widget type: %s", widgetType)
 		return nil, fmt.Errorf("unknown widget type: %s", widgetType)
 	}
 	}