diff --git a/config/config_test.go b/config/config_test.go
index 8eb0e88d..f492f4f5 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -1616,6 +1616,24 @@ func TestFetchYouTubeWatchTime(t *testing.T) {
}
}
+func TestYouTubeEmbedUrlOverride(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
+
+ parser := NewParser()
+ opts, err := parser.ParseEnvironmentVariables()
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ expected := "https://invidious.custom/embed/"
+ result := opts.YouTubeEmbedUrlOverride()
+
+ if result != expected {
+ t.Fatalf(`Unexpected YOUTUBE_EMBED_URL_OVERRIDE value, got %v instead of %v`, result, expected)
+ }
+}
+
func TestParseConfigDumpOutput(t *testing.T) {
os.Clearenv()
diff --git a/config/options.go b/config/options.go
index f7448578..52a6ef10 100644
--- a/config/options.go
+++ b/config/options.go
@@ -50,6 +50,7 @@ const (
defaultProxyMediaTypes = "image"
defaultProxyUrl = ""
defaultFetchYouTubeWatchTime = false
+ defaultYouTubeEmbedUrlOverride = "https://www.youtube-nocookie.com/embed/"
defaultCreateAdmin = false
defaultAdminUsername = ""
defaultAdminPassword = ""
@@ -126,6 +127,7 @@ type Options struct {
proxyMediaTypes []string
proxyUrl string
fetchYouTubeWatchTime bool
+ youTubeEmbedUrlOverride string
oauth2UserCreationAllowed bool
oauth2ClientID string
oauth2ClientSecret string
@@ -195,6 +197,7 @@ func NewOptions() *Options {
proxyMediaTypes: []string{defaultProxyMediaTypes},
proxyUrl: defaultProxyUrl,
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
+ youTubeEmbedUrlOverride: defaultYouTubeEmbedUrlOverride,
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
oauth2ClientID: defaultOAuth2ClientID,
oauth2ClientSecret: defaultOAuth2ClientSecret,
@@ -428,6 +431,11 @@ func (o *Options) FetchYouTubeWatchTime() bool {
return o.fetchYouTubeWatchTime
}
+// YouTubeEmbedUrlOverride returns YouTube URL which will be used for embeds
+func (o *Options) YouTubeEmbedUrlOverride() string {
+ return o.youTubeEmbedUrlOverride
+}
+
// ProxyOption returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
func (o *Options) ProxyOption() string {
return o.proxyOption
@@ -558,20 +566,20 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
"BATCH_SIZE": o.batchSize,
"CERT_DOMAIN": o.certDomain,
"CERT_FILE": o.certFile,
+ "CLEANUP_ARCHIVE_BATCH_SIZE": o.cleanupArchiveBatchSize,
"CLEANUP_ARCHIVE_READ_DAYS": o.cleanupArchiveReadDays,
"CLEANUP_ARCHIVE_UNREAD_DAYS": o.cleanupArchiveUnreadDays,
- "CLEANUP_ARCHIVE_BATCH_SIZE": o.cleanupArchiveBatchSize,
"CLEANUP_FREQUENCY_HOURS": o.cleanupFrequencyHours,
"CLEANUP_REMOVE_SESSIONS_DAYS": o.cleanupRemoveSessionsDays,
"CREATE_ADMIN": o.createAdmin,
+ "DATABASE_CONNECTION_LIFETIME": o.databaseConnectionLifetime,
"DATABASE_MAX_CONNS": o.databaseMaxConns,
"DATABASE_MIN_CONNS": o.databaseMinConns,
- "DATABASE_CONNECTION_LIFETIME": o.databaseConnectionLifetime,
"DATABASE_URL": redactSecretValue(o.databaseURL, redactSecret),
"DEBUG": o.debug,
"DISABLE_HSTS": !o.hsts,
- "DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
"DISABLE_HTTP_SERVICE": !o.httpService,
+ "DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
"FETCH_YOUTUBE_WATCH_TIME": o.fetchYouTubeWatchTime,
"HTTPS": o.HTTPS,
"HTTP_CLIENT_MAX_BODY_SIZE": o.httpClientMaxBodySize,
@@ -580,17 +588,17 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
"HTTP_CLIENT_USER_AGENT": o.httpClientUserAgent,
"HTTP_SERVER_TIMEOUT": o.httpServerTimeout,
"HTTP_SERVICE": o.httpService,
- "KEY_FILE": o.certKeyFile,
"INVIDIOUS_INSTANCE": o.invidiousInstance,
+ "KEY_FILE": o.certKeyFile,
"LISTEN_ADDR": o.listenAddr,
"LOG_DATE_TIME": o.logDateTime,
"MAINTENANCE_MESSAGE": o.maintenanceMessage,
"MAINTENANCE_MODE": o.maintenanceMode,
"METRICS_ALLOWED_NETWORKS": strings.Join(o.metricsAllowedNetworks, ","),
"METRICS_COLLECTOR": o.metricsCollector,
+ "METRICS_PASSWORD": redactSecretValue(o.metricsPassword, redactSecret),
"METRICS_REFRESH_INTERVAL": o.metricsRefreshInterval,
"METRICS_USERNAME": o.metricsUsername,
- "METRICS_PASSWORD": redactSecretValue(o.metricsPassword, redactSecret),
"OAUTH2_CLIENT_ID": o.oauth2ClientID,
"OAUTH2_CLIENT_SECRET": redactSecretValue(o.oauth2ClientSecret, redactSecret),
"OAUTH2_OIDC_DISCOVERY_ENDPOINT": o.oauth2OidcDiscoveryEndpoint,
@@ -602,9 +610,9 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
"POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit,
"POLLING_SCHEDULER": o.pollingScheduler,
"PROXY_HTTP_CLIENT_TIMEOUT": o.proxyHTTPClientTimeout,
- "PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
"PROXY_MEDIA_TYPES": o.proxyMediaTypes,
"PROXY_OPTION": o.proxyOption,
+ "PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
"PROXY_URL": o.proxyUrl,
"ROOT_URL": o.rootURL,
"RUN_MIGRATIONS": o.runMigrations,
@@ -612,8 +620,9 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
"SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": o.schedulerEntryFrequencyMinInterval,
"SCHEDULER_SERVICE": o.schedulerService,
"SERVER_TIMING_HEADER": o.serverTimingHeader,
- "WORKER_POOL_SIZE": o.workerPoolSize,
"WATCHDOG": o.watchdog,
+ "WORKER_POOL_SIZE": o.workerPoolSize,
+ "YOUTUBE_EMBED_URL_OVERRIDE": o.youTubeEmbedUrlOverride,
}
keys := make([]string, 0, len(keyValues))
diff --git a/config/parser.go b/config/parser.go
index d8c3c7db..a76e02c0 100644
--- a/config/parser.go
+++ b/config/parser.go
@@ -215,6 +215,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword)
case "FETCH_YOUTUBE_WATCH_TIME":
p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime)
+ case "YOUTUBE_EMBED_URL_OVERRIDE":
+ p.opts.youTubeEmbedUrlOverride = parseString(value, defaultYouTubeEmbedUrlOverride)
case "WATCHDOG":
p.opts.watchdog = parseBool(value, defaultWatchdog)
case "INVIDIOUS_INSTANCE":
diff --git a/miniflux.1 b/miniflux.1
index d4d2d8d5..8d834d1c 100644
--- a/miniflux.1
+++ b/miniflux.1
@@ -124,6 +124,11 @@ use it as a reading time\&.
.br
Disabled by default\&.
.TP
+.B YOUTUBE_EMBED_URL_OVERRIDE
+YouTube URL which will be used for embeds.\&.
+.br
+Default is https://www.youtube-nocookie.com/embed/\&
+.TP
.B SERVER_TIMING_HEADER
Set the value to 1 to enable server-timing headers\&.
.br
diff --git a/reader/rewrite/rewrite_functions.go b/reader/rewrite/rewrite_functions.go
index 20ba28cf..9443230e 100644
--- a/reader/rewrite/rewrite_functions.go
+++ b/reader/rewrite/rewrite_functions.go
@@ -208,7 +208,7 @@ func addYoutubeVideo(entryURL, entryContent string) string {
matches := youtubeRegex.FindStringSubmatch(entryURL)
if len(matches) == 2 {
- video := ``
+ video := ``
return video + `
` + entryContent
}
return entryContent
@@ -232,7 +232,8 @@ func addYoutubeVideoFromId(entryContent string) string {
sb := strings.Builder{}
for _, match := range matches {
if len(match) == 2 {
- sb.WriteString(`
`)
}
diff --git a/reader/rewrite/rewriter_test.go b/reader/rewrite/rewriter_test.go
index b7e04e90..2781a2a9 100644
--- a/reader/rewrite/rewriter_test.go
+++ b/reader/rewrite/rewriter_test.go
@@ -4,10 +4,12 @@
package rewrite // import "miniflux.app/reader/rewrite"
import (
+ "os"
"reflect"
"strings"
"testing"
+ "miniflux.app/config"
"miniflux.app/model"
)
@@ -63,6 +65,8 @@ func TestRewriteWithNoMatchingRule(t *testing.T) {
}
func TestRewriteWithYoutubeLink(t *testing.T) {
+ config.Opts = config.NewOptions()
+
controlEntry := &model.Entry{
Title: `A title`,
Content: `
Video Description`,
@@ -78,6 +82,33 @@ func TestRewriteWithYoutubeLink(t *testing.T) {
}
}
+func TestRewriteWithYoutubeLinkAndCustomEmbedURL(t *testing.T) {
+ os.Clearenv()
+ os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
+
+ var err error
+ parser := config.NewParser()
+ config.Opts, err = parser.ParseEnvironmentVariables()
+
+ if err != nil {
+ t.Fatalf(`Parsing failure: %v`, err)
+ }
+
+ controlEntry := &model.Entry{
+ Title: `A title`,
+ Content: `
Video Description`,
+ }
+ testEntry := &model.Entry{
+ Title: `A title`,
+ Content: `Video Description`,
+ }
+ Rewriter("https://www.youtube.com/watch?v=1234", testEntry, ``)
+
+ if !reflect.DeepEqual(testEntry, controlEntry) {
+ t.Errorf(`Not expected output: got "%+v" instead of "%+v"`, testEntry, controlEntry)
+ }
+}
+
func TestRewriteWithInexistingCustomRule(t *testing.T) {
controlEntry := &model.Entry{
Title: `A title`,
diff --git a/reader/sanitizer/sanitizer.go b/reader/sanitizer/sanitizer.go
index 9d1154b9..e811dd91 100644
--- a/reader/sanitizer/sanitizer.go
+++ b/reader/sanitizer/sanitizer.go
@@ -441,7 +441,7 @@ func inList(needle string, haystack []string) bool {
func rewriteIframeURL(link string) string {
matches := youtubeEmbedRegex.FindStringSubmatch(link)
if len(matches) == 2 {
- return `https://www.youtube-nocookie.com/embed/` + matches[1]
+ return config.Opts.YouTubeEmbedUrlOverride() + matches[1]
}
return link
diff --git a/reader/sanitizer/sanitizer_test.go b/reader/sanitizer/sanitizer_test.go
index 4b2a5cb7..100bf684 100644
--- a/reader/sanitizer/sanitizer_test.go
+++ b/reader/sanitizer/sanitizer_test.go
@@ -3,7 +3,18 @@
package sanitizer // import "miniflux.app/reader/sanitizer"
-import "testing"
+import (
+ "os"
+ "testing"
+
+ "miniflux.app/config"
+)
+
+func TestMain(m *testing.M) {
+ config.Opts = config.NewOptions()
+ exitCode := m.Run()
+ os.Exit(exitCode)
+}
func TestValidInput(t *testing.T) {
input := `
This is a text with an image: .
` @@ -540,6 +551,27 @@ func TestReplaceProtocolRelativeYoutubeURL(t *testing.T) { } } +func TestReplaceYoutubeURLWithCustomURL(t *testing.T) { + os.Clearenv() + os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/") + + var err error + parser := config.NewParser() + config.Opts, err = parser.ParseEnvironmentVariables() + + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } + + input := `` + expected := `` + output := Sanitize("http://example.org/", input) + + if expected != output { + t.Errorf(`Wrong output: "%s" != "%s"`, expected, output) + } +} + func TestReplaceIframeURL(t *testing.T) { input := `` expected := ``