Add BGG Hotness widget and associated doc entry
This commit is contained in:
parent
d60457afaf
commit
15db5899f8
7 changed files with 181 additions and 1 deletions
|
@ -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.
|
||||
|
|
|
@ -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{
|
||||
|
|
17
internal/assets/templates/bgghotness.html
Normal file
17
internal/assets/templates/bgghotness.html
Normal 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 }}
|
71
internal/feed/bgghotness.go
Normal file
71
internal/feed/bgghotness.go
Normal 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
|
||||
}
|
|
@ -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": "€",
|
||||
|
|
49
internal/widget/bgghotness.go
Normal file
49
internal/widget/bgghotness.go
Normal 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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue