Merge pull request #56 from jonasknobloch/lobsters-widget

Add Lobsters widget
This commit is contained in:
Svilen Markov 2024-06-02 18:20:36 +01:00 committed by GitHub
commit 8dc34ddfa7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 203 additions and 8 deletions

10
.dockerignore Normal file
View File

@ -0,0 +1,10 @@
# https://docs.docker.com/build/building/context/#dockerignore-files
# Ignore all files by default
*
# Only add necessary files to the Docker build context (Dockerfiles are always included implicitly)
!/build/
!/internal/
!/go.mod
!/go.sum
!main.go

View File

@ -1,7 +1,14 @@
FROM golang:1.22.3-alpine3.19 AS builder
WORKDIR /app
COPY . /app
RUN CGO_ENABLED=0 go build .
FROM alpine:3.19
WORKDIR /app
COPY build/glance /app/glance
COPY --from=builder /app/glance .
EXPOSE 8080/tcp
ENTRYPOINT ["/app/glance"]

View File

@ -94,12 +94,6 @@ go run .
### Building Docker image
Build Glance with CGO disabled:
```bash
CGO_ENABLED=0 go build -o build/glance .
```
Build the image:
**Make sure to replace "owner" with your name or organization.**

View File

@ -10,6 +10,7 @@
- [RSS](#rss)
- [Videos](#videos)
- [Hacker News](#hacker-news)
- [Lobsters](#lobsters)
- [Reddit](#reddit)
- [Search](#search-widget)
- [Weather](#weather)
@ -533,6 +534,49 @@ Can be used to specify an additional sort which will be applied on top of the al
The `engagement` sort tries to place the posts with the most points and comments on top, also prioritizing recent over old posts.
### Lobsters
Display a list of posts from [Lobsters](https://lobste.rs).
Example:
```yaml
- type: lobsters
sort-by: hot
tags:
- go
- security
- linux
limit: 15
collapse-after: 5
```
<!--
TODO: add preview
Preview:
![](images/lobsters-widget-preview.png)
-->
#### Properties
| Name | Type | Required | Default |
| ---- | ---- | -------- | ------- |
| limit | integer | no | 15 |
| collapse-after | integer | no | 5 |
| sort-by | string | no | hot |
| tags | array | no | |
##### `limit`
The maximum number of posts to show.
##### `collapse-after`
How many posts are visible before the "SHOW MORE" button appears. Set to `-1` to never collapse.
##### `sort-by`
The sort order in which posts are returned. Possible options are `hot` and `new`.
##### `tags`
Limit to posts containing one of the given tags. **You cannot specify a sort order when filtering by tags, it will default to `hot`.**
### Reddit
Display a list of posts from a specific subreddit.
@ -629,7 +673,7 @@ https://your.proxy/?url={REQUEST-URL}
##### `sort-by`
Can be used to specify the order in which the posts should get returned. Possible values are `hot`, `new`, `top` and `rising`.
##### `top-perid`
##### `top-period`
Available only when `sort-by` is set to `top`. Possible values are `hour`, `day`, `week`, `month`, `year` and `all`.
##### `search`

81
internal/feed/lobsters.go Normal file
View File

@ -0,0 +1,81 @@
package feed
import (
"net/http"
"strings"
"time"
)
type lobstersPostResponseJson struct {
CreatedAt string `json:"created_at"`
Title string `json:"title"`
URL string `json:"url"`
Score int `json:"score"`
CommentCount int `json:"comment_count"`
CommentsURL string `json:"comments_url"`
Tags []string `json:"tags"`
}
type lobstersFeedResponseJson []lobstersPostResponseJson
func getLobstersPostsFromFeed(feedUrl string) (ForumPosts, error) {
request, err := http.NewRequest("GET", feedUrl, nil)
if err != nil {
return nil, err
}
feed, err := decodeJsonFromRequest[lobstersFeedResponseJson](defaultClient, request)
if err != nil {
return nil, err
}
posts := make(ForumPosts, 0, len(feed))
for i := range feed {
createdAt, _ := time.Parse(time.RFC3339, feed[i].CreatedAt)
posts = append(posts, ForumPost{
Title: feed[i].Title,
DiscussionUrl: feed[i].CommentsURL,
TargetUrl: feed[i].URL,
TargetUrlDomain: extractDomainFromUrl(feed[i].URL),
CommentCount: feed[i].CommentCount,
Score: feed[i].Score,
TimePosted: createdAt,
Tags: feed[i].Tags,
})
}
if len(posts) == 0 {
return nil, ErrNoContent
}
return posts, nil
}
func FetchLobstersPosts(sortBy string, tags []string) (ForumPosts, error) {
var feedUrl string
if sortBy == "hot" {
sortBy = "hottest"
} else if sortBy == "new" {
sortBy = "newest"
}
if len(tags) == 0 {
feedUrl = "https://lobste.rs/" + sortBy + ".json"
} else {
tags := strings.Join(tags, ",")
feedUrl = "https://lobste.rs/t/" + tags + ".json"
}
posts, err := getLobstersPostsFromFeed(feedUrl)
if err != nil {
return nil, err
}
return posts, nil
}

View File

@ -16,6 +16,7 @@ type ForumPost struct {
Score int
Engagement float64
TimePosted time.Time
Tags []string
}
type ForumPosts []ForumPost

View File

@ -0,0 +1,56 @@
package widget
import (
"context"
"html/template"
"time"
"github.com/glanceapp/glance/internal/assets"
"github.com/glanceapp/glance/internal/feed"
)
type Lobsters struct {
widgetBase `yaml:",inline"`
Posts feed.ForumPosts `yaml:"-"`
Limit int `yaml:"limit"`
CollapseAfter int `yaml:"collapse-after"`
SortBy string `yaml:"sort-by"`
Tags []string `yaml:"tags"`
ShowThumbnails bool `yaml:"-"`
}
func (widget *Lobsters) Initialize() error {
widget.withTitle("Lobsters").withCacheDuration(30 * time.Minute)
if widget.SortBy == "" || (widget.SortBy != "hot" && widget.SortBy != "new") {
widget.SortBy = "hot"
}
if widget.Limit <= 0 {
widget.Limit = 15
}
if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 {
widget.CollapseAfter = 5
}
return nil
}
func (widget *Lobsters) Update(ctx context.Context) {
posts, err := feed.FetchLobstersPosts(widget.SortBy, widget.Tags)
if !widget.canContinueUpdateAfterHandlingErr(err) {
return
}
if widget.Limit < len(posts) {
posts = posts[:widget.Limit]
}
widget.Posts = posts
}
func (widget *Lobsters) Render() template.HTML {
return widget.render(widget, assets.ForumPostsTemplate)
}

View File

@ -45,6 +45,8 @@ func New(widgetType string) (Widget, error) {
return &TwitchGames{}, nil
case "twitch-channels":
return &TwitchChannels{}, nil
case "lobsters":
return &Lobsters{}, nil
case "change-detection":
return &ChangeDetection{}, nil
case "repository":