From 7adf624e95909566209116c50a9bc3a5700119a6 Mon Sep 17 00:00:00 2001 From: Shashank S Date: Wed, 8 May 2024 11:47:25 +0200 Subject: [PATCH 01/80] feat: add change detection module --- internal/assets/templates.go | 1 + internal/assets/templates/changes.html | 17 ++++++ internal/feed/changedetection.go | 79 ++++++++++++++++++++++++++ internal/feed/primitives.go | 16 ++++++ internal/widget/changedetection.go | 51 +++++++++++++++++ internal/widget/widget.go | 4 +- 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 internal/assets/templates/changes.html create mode 100644 internal/feed/changedetection.go create mode 100644 internal/widget/changedetection.go diff --git a/internal/assets/templates.go b/internal/assets/templates.go index fe82ed5..3c9f691 100644 --- a/internal/assets/templates.go +++ b/internal/assets/templates.go @@ -22,6 +22,7 @@ var ( RedditCardsHorizontalTemplate = compileTemplate("reddit-horizontal-cards.html", "widget-base.html") RedditCardsVerticalTemplate = compileTemplate("reddit-vertical-cards.html", "widget-base.html") ReleasesTemplate = compileTemplate("releases.html", "widget-base.html") + ChangesTemplate = compileTemplate("changes.html", "widget-base.html") VideosTemplate = compileTemplate("videos.html", "widget-base.html") StocksTemplate = compileTemplate("stocks.html", "widget-base.html") RSSListTemplate = compileTemplate("rss-list.html", "widget-base.html") diff --git a/internal/assets/templates/changes.html b/internal/assets/templates/changes.html new file mode 100644 index 0000000..d8eee38 --- /dev/null +++ b/internal/assets/templates/changes.html @@ -0,0 +1,17 @@ +{{ template "widget-base.html" . }} + +{{ define "widget-content" }} + +{{ if gt (len .ChangeDetections) $.CollapseAfter }} + +{{ end }} +{{ end }} diff --git a/internal/feed/changedetection.go b/internal/feed/changedetection.go new file mode 100644 index 0000000..6bfd119 --- /dev/null +++ b/internal/feed/changedetection.go @@ -0,0 +1,79 @@ +package feed + +import ( + "fmt" + "log/slog" + "net/http" + "time" +) + +type changeDetectionResponseJson struct { + Name string `json:"title"` + Url string `json:"url"` + LastChanged int `json:"last_changed"` +} + + +func parseLastChangeTime(t int) time.Time { + parsedTime := time.Unix(int64(t), 0) + return parsedTime +} + + +func FetchLatestDetectedChanges(watches []string, token string) (ChangeWatches, error) { + changeWatches := make(ChangeWatches, 0, len(watches)) + + if len(watches) == 0 { + return changeWatches, nil + } + + requests := make([]*http.Request, len(watches)) + + for i, repository := range watches { + request, _ := http.NewRequest("GET", fmt.Sprintf("https://changedetection.knhash.in/api/v1/watch/%s", repository), nil) + + if token != "" { + request.Header.Add("x-api-key", token) + } + + requests[i] = request + } + + task := decodeJsonFromRequestTask[changeDetectionResponseJson](defaultClient) + job := newJob(task, requests).withWorkers(15) + responses, errs, err := workerPoolDo(job) + + if err != nil { + return nil, err + } + + var failed int + + for i := range responses { + if errs[i] != nil { + failed++ + slog.Error("Failed to fetch or parse change detections", "error", errs[i], "url", requests[i].URL) + continue + } + + watch := responses[i] + + changeWatches = append(changeWatches, ChangeWatch{ + Name: watch.Name, + Url: watch.Url, + LastChanged: parseLastChangeTime(watch.LastChanged), + }) + } + + if len(changeWatches) == 0 { + return nil, ErrNoContent + } + + changeWatches.SortByNewest() + + if failed > 0 { + return changeWatches, fmt.Errorf("%w: could not get %d watches", ErrPartialContent, failed) + } + + return changeWatches, nil +} diff --git a/internal/feed/primitives.go b/internal/feed/primitives.go index 99d6763..70e54a1 100644 --- a/internal/feed/primitives.go +++ b/internal/feed/primitives.go @@ -48,6 +48,14 @@ type AppRelease struct { type AppReleases []AppRelease +type ChangeWatch struct { + Name string + Url string + LastChanged time.Time +} + +type ChangeWatches []ChangeWatch + type Video struct { ThumbnailUrl string Title string @@ -200,6 +208,14 @@ func (r AppReleases) SortByNewest() AppReleases { return r } +func (r ChangeWatches) SortByNewest() ChangeWatches { + sort.Slice(r, func(i, j int) bool { + return r[i].LastChanged.After(r[j].LastChanged) + }) + + return r +} + func (v Videos) SortByNewest() Videos { sort.Slice(v, func(i, j int) bool { return v[i].TimePosted.After(v[j].TimePosted) diff --git a/internal/widget/changedetection.go b/internal/widget/changedetection.go new file mode 100644 index 0000000..589147e --- /dev/null +++ b/internal/widget/changedetection.go @@ -0,0 +1,51 @@ +package widget + +import ( + "context" + "html/template" + "time" + + "github.com/glanceapp/glance/internal/assets" + "github.com/glanceapp/glance/internal/feed" +) + +type ChangeDetections struct { + widgetBase `yaml:",inline"` + ChangeDetections feed.ChangeWatches `yaml:"-"` + Watches []string `yaml:"watches"` + Token OptionalEnvString `yaml:"token"` + Limit int `yaml:"limit"` + CollapseAfter int `yaml:"collapse-after"` +} + +func (widget *ChangeDetections) Initialize() error { + widget.withTitle("Changes").withCacheDuration(2 * time.Hour) + + if widget.Limit <= 0 { + widget.Limit = 10 + } + + if widget.CollapseAfter == 0 || widget.CollapseAfter < -1 { + widget.CollapseAfter = 5 + } + + return nil +} + +func (widget *ChangeDetections) Update(ctx context.Context) { + watches, err := feed.FetchLatestDetectedChanges(widget.Watches, string(widget.Token)) + + if !widget.canContinueUpdateAfterHandlingErr(err) { + return + } + + if len(watches) > widget.Limit { + watches = watches[:widget.Limit] + } + + widget.ChangeDetections = watches +} + +func (widget *ChangeDetections) Render() template.HTML { + return widget.render(widget, assets.ChangesTemplate) +} diff --git a/internal/widget/widget.go b/internal/widget/widget.go index 367d822..e7ec293 100644 --- a/internal/widget/widget.go +++ b/internal/widget/widget.go @@ -43,8 +43,10 @@ func New(widgetType string) (Widget, error) { return &TwitchGames{}, nil case "twitch-channels": return &TwitchChannels{}, nil + case "changes": + return &ChangeDetections{}, nil default: - return nil, fmt.Errorf("unknown widget type: %s", widgetType) + return nil, fmt.Errorf("unknown widget type: %s found", widgetType) } } From ecbad7ce6ca757f9d1f7750c2295fe4af0b6a5ed Mon Sep 17 00:00:00 2001 From: Carlos Medina Date: Wed, 8 May 2024 16:51:04 -0600 Subject: [PATCH 02/80] Add mobile web app support --- internal/assets/static/app-icon.png | Bin 0 -> 7946 bytes internal/assets/static/main.css | 8 ++++++++ internal/assets/static/manifest.json | 13 +++++++++++++ internal/assets/templates/document.html | 8 ++++++++ 4 files changed, 29 insertions(+) create mode 100644 internal/assets/static/app-icon.png create mode 100644 internal/assets/static/manifest.json diff --git a/internal/assets/static/app-icon.png b/internal/assets/static/app-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..54fc4131137d09784f3057b58cc18afdaab13ba4 GIT binary patch literal 7946 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 + + + + + + + + From 461c984317c86d6e32cd289853a892f634a7d978 Mon Sep 17 00:00:00 2001 From: Svilen Markov <7613769+svilenmarkov@users.noreply.github.com> Date: Thu, 9 May 2024 04:05:25 +0100 Subject: [PATCH 03/80] Check if background color is nil before setting --- internal/assets/templates/document.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/assets/templates/document.html b/internal/assets/templates/document.html index 8929579..dbc19e3 100644 --- a/internal/assets/templates/document.html +++ b/internal/assets/templates/document.html @@ -10,7 +10,7 @@ - + From aa04904a727147f0c0e5ae7f43e5694fa370f3b7 Mon Sep 17 00:00:00 2001 From: Shashank S Date: Thu, 9 May 2024 08:32:27 +0200 Subject: [PATCH 04/80] feat: add diff, fix: configurable url --- internal/assets/templates/changes.html | 3 ++- internal/feed/changedetection.go | 18 ++++++++++++------ internal/feed/primitives.go | 4 +++- internal/widget/changedetection.go | 13 +++++++------ 4 files changed, 24 insertions(+), 14 deletions(-) diff --git a/internal/assets/templates/changes.html b/internal/assets/templates/changes.html index d8eee38..2b4c53a 100644 --- a/internal/assets/templates/changes.html +++ b/internal/assets/templates/changes.html @@ -4,9 +4,10 @@