From d5d6103327a71c0f96b86b3b1f5fa8a906383e39 Mon Sep 17 00:00:00 2001
From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com>
Date: Mon, 29 Apr 2024 22:32:46 +0100
Subject: [PATCH 01/15] Fix currency symbol being hardcoded to $
---
internal/assets/templates/stocks.html | 2 +-
internal/feed/primitives.go | 26 ++++++++++++++++++++++++++
internal/feed/yahoo.go | 14 +++++++++++---
3 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/internal/assets/templates/stocks.html b/internal/assets/templates/stocks.html
index 2bdb02e1..f8ea9b2a 100644
--- a/internal/assets/templates/stocks.html
+++ b/internal/assets/templates/stocks.html
@@ -15,7 +15,7 @@
{{ printf "%+.2f" .PercentChange }}%
-
${{ .Price | formatPrice }}
+
{{ .Currency }}{{ .Price | formatPrice }}
{{ end }}
diff --git a/internal/feed/primitives.go b/internal/feed/primitives.go
index 7f4f4642..99d67634 100644
--- a/internal/feed/primitives.go
+++ b/internal/feed/primitives.go
@@ -59,9 +59,35 @@ type Video struct {
type Videos []Video
+var currencyToSymbol = map[string]string{
+ "USD": "$",
+ "EUR": "€",
+ "JPY": "¥",
+ "CAD": "C$",
+ "AUD": "A$",
+ "GBP": "£",
+ "CHF": "Fr",
+ "NZD": "N$",
+ "INR": "₹",
+ "BRL": "R$",
+ "RUB": "₽",
+ "TRY": "₺",
+ "ZAR": "R",
+ "CNY": "¥",
+ "KRW": "₩",
+ "HKD": "HK$",
+ "SGD": "S$",
+ "SEK": "kr",
+ "NOK": "kr",
+ "DKK": "kr",
+ "PLN": "zł",
+ "PHP": "₱",
+}
+
type Stock struct {
Name string
Symbol string
+ Currency string
Price float64
PercentChange float64
SvgChartPoints string
diff --git a/internal/feed/yahoo.go b/internal/feed/yahoo.go
index 94126655..a8106d74 100644
--- a/internal/feed/yahoo.go
+++ b/internal/feed/yahoo.go
@@ -10,6 +10,7 @@ type stockResponseJson struct {
Chart struct {
Result []struct {
Meta struct {
+ Currency string `json:"currency"`
Symbol string `json:"symbol"`
RegularMarketPrice float64 `json:"regularMarketPrice"`
ChartPreviousClose float64 `json:"chartPreviousClose"`
@@ -78,10 +79,17 @@ func FetchStocksDataFromYahoo(stockRequests []StockRequest) (Stocks, error) {
points := SvgPolylineCoordsFromYValues(100, 50, maybeCopySliceWithoutZeroValues(prices))
+ currency, exists := currencyToSymbol[response.Chart.Result[0].Meta.Currency]
+
+ if !exists {
+ currency = response.Chart.Result[0].Meta.Currency
+ }
+
stocks = append(stocks, Stock{
- Name: stockRequests[i].Name,
- Symbol: response.Chart.Result[0].Meta.Symbol,
- Price: response.Chart.Result[0].Meta.RegularMarketPrice,
+ Name: stockRequests[i].Name,
+ Symbol: response.Chart.Result[0].Meta.Symbol,
+ Price: response.Chart.Result[0].Meta.RegularMarketPrice,
+ Currency: currency,
PercentChange: percentChange(
response.Chart.Result[0].Meta.RegularMarketPrice,
previous,
From 44a153d30abd1553d68c6977eb5d799bcac23e14 Mon Sep 17 00:00:00 2001
From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com>
Date: Wed, 1 May 2024 19:08:25 +0100
Subject: [PATCH 02/15] Allow using alternative links for YT, HN and reddit
---
docs/configuration.md | 45 ++++++++++++++++++++++++++++++++++
internal/feed/hacker-news.go | 17 ++++++++++---
internal/feed/reddit.go | 16 ++++++++++--
internal/feed/youtube.go | 19 ++++++++++++--
internal/widget/hacker-news.go | 11 +++++----
internal/widget/reddit.go | 15 ++++++------
internal/widget/videos.go | 11 +++++----
7 files changed, 109 insertions(+), 25 deletions(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index 0c14525b..a46569a3 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -434,6 +434,7 @@ Preview:
| ---- | ---- | -------- | ------- |
| channels | array | yes | |
| limit | integer | no | 25 |
+| video-url-template | string | no | https://www.youtube.com/watch?v={VIDEO-ID} |
##### `channels`
A list of channel IDs. One way of getting the ID of a channel is going to the channel's page and clicking on its description:
@@ -447,6 +448,17 @@ Then scroll down and click on "Share channel", then "Copy channel ID":
##### `limit`
The maximum number of videos to show.
+##### `video-url-template`
+Used to replace the default link for videos. Useful when you're running your own YouTube front-end. Example:
+
+```yaml
+video-url-template: https://invidious.your-domain.com/watch?v={VIDEO-ID}
+```
+
+Placeholders:
+
+`{VIDEO-ID}` - the ID of the video
+
### Hacker News
Display a list of posts from [Hacker News](https://news.ycombinator.com/).
@@ -466,6 +478,19 @@ Preview:
| ---- | ---- | -------- | ------- |
| limit | integer | no | 15 |
| collapse-after | integer | no | 5 |
+| comments-url-template | string | no | https://news.ycombinator.com/item?id={POST-ID} |
+
+##### `comments-url-template`
+Used to replace the default link for post comments. Useful if you want to use an alternative front-end. Example:
+
+```yaml
+comments-url-template: https://www.hckrnws.com/stories/{POST-PATH}
+```
+
+Placeholders:
+
+`{POST-ID}` - the ID of the post
+
### Reddit
Display a list of posts from a specific subreddit.
@@ -488,6 +513,7 @@ Example:
| style | string | no | vertical-list |
| limit | integer | no | 15 |
| collapse-after | integer | no | 5 |
+| comments-url-template | string | no | https://www.reddit.com/{POST-PATH} |
##### `subreddit`
The subreddit for which to fetch the posts from.
@@ -513,6 +539,25 @@ 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. Not available when using the `vertical-cards` and `horizontal-cards` styles.
+##### `comments-url-template`
+Used to replace the default link for post comments. Useful if you want to use the old Reddit design or any other 3rd party front-end. Example:
+
+```yaml
+comments-url-template: https://old.reddit.com/{POST-PATH}
+```
+
+Placeholders:
+
+`{POST-PATH}` - the full path to the post, such as:
+
+```
+r/selfhosted/comments/bsp01i/welcome_to_rselfhosted_please_read_this_first/
+```
+
+`{POST-ID}` - the ID that comes after `/comments/`
+
+`{SUBREDDIT}` - the subreddit name
+
### Weather
Display weather information for a specific location. The data is provided by https://open-meteo.com/.
diff --git a/internal/feed/hacker-news.go b/internal/feed/hacker-news.go
index 8e7ad736..baacdf27 100644
--- a/internal/feed/hacker-news.go
+++ b/internal/feed/hacker-news.go
@@ -5,6 +5,7 @@ import (
"log/slog"
"net/http"
"strconv"
+ "strings"
"time"
)
@@ -28,7 +29,7 @@ func getHackerNewsTopPostIds() ([]int, error) {
return response, nil
}
-func getHackerNewsPostsFromIds(postIds []int) (ForumPosts, error) {
+func getHackerNewsPostsFromIds(postIds []int, commentsUrlTemplate string) (ForumPosts, error) {
requests := make([]*http.Request, len(postIds))
for i, id := range postIds {
@@ -52,9 +53,17 @@ func getHackerNewsPostsFromIds(postIds []int) (ForumPosts, error) {
continue
}
+ var commentsUrl string
+
+ if commentsUrlTemplate == "" {
+ commentsUrl = "https://news.ycombinator.com/item?id=" + strconv.Itoa(results[i].Id)
+ } else {
+ commentsUrl = strings.ReplaceAll(commentsUrlTemplate, "{POST-ID}", strconv.Itoa(results[i].Id))
+ }
+
posts = append(posts, ForumPost{
Title: results[i].Title,
- DiscussionUrl: "https://news.ycombinator.com/item?id=" + strconv.Itoa(results[i].Id),
+ DiscussionUrl: commentsUrl,
TargetUrl: results[i].TargetUrl,
TargetUrlDomain: extractDomainFromUrl(results[i].TargetUrl),
CommentCount: results[i].CommentCount,
@@ -74,7 +83,7 @@ func getHackerNewsPostsFromIds(postIds []int) (ForumPosts, error) {
return posts, nil
}
-func FetchHackerNewsTopPosts(limit int) (ForumPosts, error) {
+func FetchHackerNewsTopPosts(limit int, commentsUrlTemplate string) (ForumPosts, error) {
postIds, err := getHackerNewsTopPostIds()
if err != nil {
@@ -85,5 +94,5 @@ func FetchHackerNewsTopPosts(limit int) (ForumPosts, error) {
postIds = postIds[:limit]
}
- return getHackerNewsPostsFromIds(postIds)
+ return getHackerNewsPostsFromIds(postIds, commentsUrlTemplate)
}
diff --git a/internal/feed/reddit.go b/internal/feed/reddit.go
index b8449f90..c8ff4c63 100644
--- a/internal/feed/reddit.go
+++ b/internal/feed/reddit.go
@@ -5,6 +5,7 @@ import (
"html"
"net/http"
"net/url"
+ "strings"
"time"
)
@@ -12,6 +13,7 @@ type subredditResponseJson struct {
Data struct {
Children []struct {
Data struct {
+ Id string `json:"id"`
Title string `json:"title"`
Upvotes int `json:"ups"`
Url string `json:"url"`
@@ -28,7 +30,7 @@ type subredditResponseJson struct {
} `json:"data"`
}
-func FetchSubredditPosts(subreddit string) (ForumPosts, error) {
+func FetchSubredditPosts(subreddit string, commentsUrlTemplate string) (ForumPosts, error) {
requestUrl := fmt.Sprintf("https://www.reddit.com/r/%s/hot.json", url.QueryEscape(subreddit))
request, err := http.NewRequest("GET", requestUrl, nil)
@@ -57,9 +59,19 @@ func FetchSubredditPosts(subreddit string) (ForumPosts, error) {
continue
}
+ var commentsUrl string
+
+ if commentsUrlTemplate == "" {
+ commentsUrl = "https://www.reddit.com" + post.Permalink
+ } else {
+ commentsUrl = strings.ReplaceAll(commentsUrlTemplate, "{SUBREDDIT}", subreddit)
+ commentsUrl = strings.ReplaceAll(commentsUrl, "{POST-ID}", post.Id)
+ commentsUrl = strings.ReplaceAll(commentsUrl, "{POST-PATH}", strings.TrimLeft(post.Permalink, "/"))
+ }
+
forumPost := ForumPost{
Title: html.UnescapeString(post.Title),
- DiscussionUrl: "https://www.reddit.com" + post.Permalink,
+ DiscussionUrl: commentsUrl,
TargetUrlDomain: post.Domain,
CommentCount: post.CommentsCount,
Score: post.Upvotes,
diff --git a/internal/feed/youtube.go b/internal/feed/youtube.go
index e22b8bb1..e478259d 100644
--- a/internal/feed/youtube.go
+++ b/internal/feed/youtube.go
@@ -4,6 +4,7 @@ import (
"fmt"
"log/slog"
"net/http"
+ "net/url"
"strings"
"time"
)
@@ -38,7 +39,7 @@ func parseYoutubeFeedTime(t string) time.Time {
return parsedTime
}
-func FetchYoutubeChannelUploads(channelIds []string) (Videos, error) {
+func FetchYoutubeChannelUploads(channelIds []string, videoUrlTemplate string) (Videos, error) {
requests := make([]*http.Request, 0, len(channelIds))
for i := range channelIds {
@@ -75,10 +76,24 @@ func FetchYoutubeChannelUploads(channelIds []string) (Videos, error) {
continue
}
+ var videoUrl string
+
+ if videoUrlTemplate == "" {
+ videoUrl = video.Link.Href
+ } else {
+ parsedUrl, err := url.Parse(video.Link.Href)
+
+ if err == nil {
+ videoUrl = strings.ReplaceAll(videoUrlTemplate, "{VIDEO-ID}", parsedUrl.Query().Get("v"))
+ } else {
+ videoUrl = "#"
+ }
+ }
+
videos = append(videos, Video{
ThumbnailUrl: video.Group.Thumbnail.Url,
Title: video.Title,
- Url: video.Link.Href,
+ Url: videoUrl,
Author: response.Channel,
AuthorUrl: response.ChannelLink.Href + "/videos",
TimePosted: parseYoutubeFeedTime(video.Published),
diff --git a/internal/widget/hacker-news.go b/internal/widget/hacker-news.go
index 9870e742..1025d061 100644
--- a/internal/widget/hacker-news.go
+++ b/internal/widget/hacker-news.go
@@ -10,10 +10,11 @@ import (
)
type HackerNews struct {
- widgetBase `yaml:",inline"`
- Posts feed.ForumPosts `yaml:"-"`
- Limit int `yaml:"limit"`
- CollapseAfter int `yaml:"collapse-after"`
+ widgetBase `yaml:",inline"`
+ Posts feed.ForumPosts `yaml:"-"`
+ Limit int `yaml:"limit"`
+ CollapseAfter int `yaml:"collapse-after"`
+ CommentsUrlTemplate string `yaml:"comments-url-template"`
}
func (widget *HackerNews) Initialize() error {
@@ -31,7 +32,7 @@ func (widget *HackerNews) Initialize() error {
}
func (widget *HackerNews) Update(ctx context.Context) {
- posts, err := feed.FetchHackerNewsTopPosts(40)
+ posts, err := feed.FetchHackerNewsTopPosts(40, widget.CommentsUrlTemplate)
if !widget.canContinueUpdateAfterHandlingErr(err) {
return
diff --git a/internal/widget/reddit.go b/internal/widget/reddit.go
index 3287fcf6..b884ac6e 100644
--- a/internal/widget/reddit.go
+++ b/internal/widget/reddit.go
@@ -11,12 +11,13 @@ import (
)
type Reddit struct {
- widgetBase `yaml:",inline"`
- Posts feed.ForumPosts `yaml:"-"`
- Subreddit string `yaml:"subreddit"`
- Style string `yaml:"style"`
- Limit int `yaml:"limit"`
- CollapseAfter int `yaml:"collapse-after"`
+ widgetBase `yaml:",inline"`
+ Posts feed.ForumPosts `yaml:"-"`
+ Subreddit string `yaml:"subreddit"`
+ Style string `yaml:"style"`
+ CommentsUrlTemplate string `yaml:"comments-url-template"`
+ Limit int `yaml:"limit"`
+ CollapseAfter int `yaml:"collapse-after"`
}
func (widget *Reddit) Initialize() error {
@@ -38,7 +39,7 @@ func (widget *Reddit) Initialize() error {
}
func (widget *Reddit) Update(ctx context.Context) {
- posts, err := feed.FetchSubredditPosts(widget.Subreddit)
+ posts, err := feed.FetchSubredditPosts(widget.Subreddit, widget.CommentsUrlTemplate)
if !widget.canContinueUpdateAfterHandlingErr(err) {
return
diff --git a/internal/widget/videos.go b/internal/widget/videos.go
index 26527685..a472f0ed 100644
--- a/internal/widget/videos.go
+++ b/internal/widget/videos.go
@@ -10,10 +10,11 @@ import (
)
type Videos struct {
- widgetBase `yaml:",inline"`
- Videos feed.Videos `yaml:"-"`
- Channels []string `yaml:"channels"`
- Limit int `yaml:"limit"`
+ widgetBase `yaml:",inline"`
+ Videos feed.Videos `yaml:"-"`
+ VideoUrlTemplate string `yaml:"video-url-template"`
+ Channels []string `yaml:"channels"`
+ Limit int `yaml:"limit"`
}
func (widget *Videos) Initialize() error {
@@ -27,7 +28,7 @@ func (widget *Videos) Initialize() error {
}
func (widget *Videos) Update(ctx context.Context) {
- videos, err := feed.FetchYoutubeChannelUploads(widget.Channels)
+ videos, err := feed.FetchYoutubeChannelUploads(widget.Channels, widget.VideoUrlTemplate)
if !widget.canContinueUpdateAfterHandlingErr(err) {
return
From 58967ab758352be8f62c640cd60822120a87f11e Mon Sep 17 00:00:00 2001
From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com>
Date: Wed, 1 May 2024 22:06:40 +0100
Subject: [PATCH 03/15] Update doc
---
docs/configuration.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index a46569a3..3b0ebf98 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -484,7 +484,7 @@ Preview:
Used to replace the default link for post comments. Useful if you want to use an alternative front-end. Example:
```yaml
-comments-url-template: https://www.hckrnws.com/stories/{POST-PATH}
+comments-url-template: https://www.hckrnws.com/stories/{POST-ID}
```
Placeholders:
From 948c117f5b067e33e15a42a9d6791e98443665e3 Mon Sep 17 00:00:00 2001
From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com>
Date: Wed, 1 May 2024 22:28:59 +0100
Subject: [PATCH 04/15] Retain stocks order by default
---
docs/configuration.md | 4 ++++
internal/widget/stocks.go | 6 +++++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index 3b0ebf98..77da5b56 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -831,6 +831,7 @@ Preview:
| Name | Type | Required |
| ---- | ---- | -------- |
| stocks | array | yes |
+| sort | string | no |
##### `stocks`
An array of stocks for which to display information about.
@@ -849,6 +850,9 @@ The symbol, as seen in Yahoo Finance.
The name that will be displayed under the symbol.
+##### `sort-by`
+By default the stocks are displayed in the order they were defined. You can customize their ordering by setting the `sort-by` property to `absolute-change` for descending order based on the stock's absolute price change.
+
### Twitch Channels
Display a list of channels from Twitch.
diff --git a/internal/widget/stocks.go b/internal/widget/stocks.go
index afba0e41..18638757 100644
--- a/internal/widget/stocks.go
+++ b/internal/widget/stocks.go
@@ -12,6 +12,7 @@ import (
type Stocks struct {
widgetBase `yaml:",inline"`
Stocks feed.Stocks `yaml:"-"`
+ Sort string `yaml:"sort-by"`
Tickers []feed.StockRequest `yaml:"stocks"`
}
@@ -28,7 +29,10 @@ func (widget *Stocks) Update(ctx context.Context) {
return
}
- stocks.SortByAbsChange()
+ if widget.Sort == "absolute-change" {
+ stocks.SortByAbsChange()
+ }
+
widget.Stocks = stocks
}
From f8af2025e895c7e07e1e9dac65bf3ea731b245c0 Mon Sep 17 00:00:00 2001
From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com>
Date: Wed, 1 May 2024 23:47:14 +0100
Subject: [PATCH 05/15] Rename flex-grow to grow
---
internal/assets/static/main.css | 2 +-
internal/assets/templates/page.html | 2 +-
internal/assets/templates/reddit-horizontal-cards.html | 2 +-
internal/assets/templates/rss-cards.html | 2 +-
internal/assets/templates/videos.html | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css
index 4824536c..31c49c05 100644
--- a/internal/assets/static/main.css
+++ b/internal/assets/static/main.css
@@ -1011,7 +1011,7 @@ body {
.justify-center { justify-content: center; }
.justify-end { justify-content: end; }
.uppercase { text-transform: uppercase; }
-.flex-grow { flex-grow: 1; }
+.grow { flex-grow: 1; }
.flex-column { flex-direction: column; }
.items-center { align-items: center; }
.items-start { align-items: start; }
diff --git a/internal/assets/templates/page.html b/internal/assets/templates/page.html
index e2a6dd91..61fad9e3 100644
--- a/internal/assets/templates/page.html
+++ b/internal/assets/templates/page.html
@@ -29,7 +29,7 @@
{{ end }}
-