aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.devcontainer/docker-compose.yml4
-rw-r--r--internal/config/options.go9
-rw-r--r--internal/config/parser.go2
-rw-r--r--internal/reader/processor/processor.go10
-rw-r--r--internal/reader/processor/processor_test.go25
-rw-r--r--miniflux.17
6 files changed, 53 insertions, 4 deletions
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
index f65edf15..ac5d01d3 100644
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/docker-compose.yml
@@ -1,7 +1,7 @@
version: '3.8'
services:
app:
- image: mcr.microsoft.com/devcontainers/go
+ image: mcr.microsoft.com/devcontainers/go:1.22
volumes:
- ..:/workspace:cached
command: sleep infinity
@@ -24,7 +24,7 @@ services:
ports:
- 5432:5432
apprise:
- image: caronc/apprise:latest
+ image: caronc/apprise:1.0
restart: unless-stopped
hostname: apprise
volumes:
diff --git a/internal/config/options.go b/internal/config/options.go
index 2169504f..57696e63 100644
--- a/internal/config/options.go
+++ b/internal/config/options.go
@@ -55,6 +55,7 @@ const (
defaultProxyOption = "http-only"
defaultProxyMediaTypes = "image"
defaultProxyUrl = ""
+ defaultFilterEntryMaxAgeDays = 0
defaultFetchOdyseeWatchTime = false
defaultFetchYouTubeWatchTime = false
defaultYouTubeEmbedUrlOverride = "https://www.youtube-nocookie.com/embed/"
@@ -141,6 +142,7 @@ type Options struct {
proxyUrl string
fetchOdyseeWatchTime bool
fetchYouTubeWatchTime bool
+ filterEntryMaxAgeDays int
youTubeEmbedUrlOverride string
oauth2UserCreationAllowed bool
oauth2ClientID string
@@ -213,6 +215,7 @@ func NewOptions() *Options {
proxyOption: defaultProxyOption,
proxyMediaTypes: []string{defaultProxyMediaTypes},
proxyUrl: defaultProxyUrl,
+ filterEntryMaxAgeDays: defaultFilterEntryMaxAgeDays,
fetchOdyseeWatchTime: defaultFetchOdyseeWatchTime,
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
youTubeEmbedUrlOverride: defaultYouTubeEmbedUrlOverride,
@@ -612,6 +615,11 @@ func (o *Options) WebAuthn() bool {
return o.webAuthn
}
+// FilterEntryMaxAgeDays returns the number of days after which entries should be retained.
+func (o *Options) FilterEntryMaxAgeDays() int {
+ return o.filterEntryMaxAgeDays
+}
+
// SortedOptions returns options as a list of key value pairs, sorted by keys.
func (o *Options) SortedOptions(redactSecret bool) []*Option {
var keyValues = map[string]interface{}{
@@ -637,6 +645,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
"DISABLE_HSTS": !o.hsts,
"DISABLE_HTTP_SERVICE": !o.httpService,
"DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
+ "FILTER_ENTRY_MAX_AGE_DAYS": o.filterEntryMaxAgeDays,
"FETCH_YOUTUBE_WATCH_TIME": o.fetchYouTubeWatchTime,
"FETCH_ODYSEE_WATCH_TIME": o.fetchOdyseeWatchTime,
"HTTPS": o.HTTPS,
diff --git a/internal/config/parser.go b/internal/config/parser.go
index ea419f30..c08abca8 100644
--- a/internal/config/parser.go
+++ b/internal/config/parser.go
@@ -112,6 +112,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns)
case "DATABASE_CONNECTION_LIFETIME":
p.opts.databaseConnectionLifetime = parseInt(value, defaultDatabaseConnectionLifetime)
+ case "FILTER_ENTRY_MAX_AGE_DAYS":
+ p.opts.filterEntryMaxAgeDays = parseInt(value, defaultFilterEntryMaxAgeDays)
case "RUN_MIGRATIONS":
p.opts.runMigrations = parseBool(value, defaultRunMigrations)
case "DISABLE_HSTS":
diff --git a/internal/reader/processor/processor.go b/internal/reader/processor/processor.go
index e36ac811..913ae0b3 100644
--- a/internal/reader/processor/processor.go
+++ b/internal/reader/processor/processor.go
@@ -47,8 +47,7 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.Us
slog.Int64("feed_id", feed.ID),
slog.String("feed_url", feed.FeedURL),
)
-
- if isBlockedEntry(feed, entry) || !isAllowedEntry(feed, entry) {
+ if isBlockedEntry(feed, entry) || !isAllowedEntry(feed, entry) || !isRecentEntry(entry) {
continue
}
@@ -413,3 +412,10 @@ func parseISO8601(from string) (time.Duration, error) {
return d, nil
}
+
+func isRecentEntry(entry *model.Entry) bool {
+ if config.Opts.FilterEntryMaxAgeDays() == 0 || entry.Date.After(time.Now().AddDate(0, 0, -config.Opts.FilterEntryMaxAgeDays())) {
+ return true
+ }
+ return false
+}
diff --git a/internal/reader/processor/processor_test.go b/internal/reader/processor/processor_test.go
index a0d5f6f5..e99a566a 100644
--- a/internal/reader/processor/processor_test.go
+++ b/internal/reader/processor/processor_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"time"
+ "miniflux.app/v2/internal/config"
"miniflux.app/v2/internal/model"
)
@@ -92,3 +93,27 @@ func TestParseISO8601(t *testing.T) {
}
}
}
+
+func TestIsRecentEntry(t *testing.T) {
+ parser := config.NewParser()
+ var err error
+ config.Opts, err = parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+ var scenarios = []struct {
+ entry *model.Entry
+ expected bool
+ }{
+ {&model.Entry{Title: "Example1", Date: time.Date(2005, 5, 1, 05, 05, 05, 05, time.UTC)}, true},
+ {&model.Entry{Title: "Example2", Date: time.Date(2010, 5, 1, 05, 05, 05, 05, time.UTC)}, true},
+ {&model.Entry{Title: "Example3", Date: time.Date(2020, 5, 1, 05, 05, 05, 05, time.UTC)}, true},
+ {&model.Entry{Title: "Example4", Date: time.Date(2024, 3, 15, 05, 05, 05, 05, time.UTC)}, true},
+ }
+ for _, tc := range scenarios {
+ result := isRecentEntry(tc.entry)
+ if tc.expected != result {
+ t.Errorf(`Unexpected result, got %v for entry %q`, result, tc.entry.Title)
+ }
+ }
+}
diff --git a/miniflux.1 b/miniflux.1
index a07c678e..58130c2b 100644
--- a/miniflux.1
+++ b/miniflux.1
@@ -307,6 +307,13 @@ Set the value to 1 to disable the internal scheduler service\&.
.br
Default is false (The internal scheduler service is enabled)\&.
.TP
+.B FILTER_ENTRY_MAX_AGE_DAYS
+Number of days after which new entries should be retained.\&.
+.br
+Set 7 to fetch only entries 7 days old.\&.
+.br
+Default is 0\&.
+.TP
.B CERT_FILE
Path to SSL certificate\&.
.br