Add BGG Hotness widget and associated doc entry

This commit is contained in:
Taffaz 2024-09-20 07:54:01 +00:00
parent d60457afaf
commit 15db5899f8
7 changed files with 181 additions and 1 deletions

View file

@ -30,6 +30,7 @@
- [Twitch Top Games](#twitch-top-games)
- [iframe](#iframe)
- [HTML](#html)
- [BGG Hotness](#bgghotness)
## 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.
@ -1665,3 +1666,26 @@ Example:
```
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.

View file

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

View file

@ -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 }}

View file

@ -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
}

View file

@ -63,6 +63,22 @@ type Video struct {
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{
"USD": "$",
"EUR": "€",

View file

@ -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)
}

View file

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