feat: support for custom youtube embed URL
This commit is contained in:
parent
f286c3c1c9
commit
9b42d0e25e
8 changed files with 109 additions and 11 deletions
|
@ -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()
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -208,7 +208,7 @@ func addYoutubeVideo(entryURL, entryContent string) string {
|
|||
matches := youtubeRegex.FindStringSubmatch(entryURL)
|
||||
|
||||
if len(matches) == 2 {
|
||||
video := `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/` + matches[1] + `" allowfullscreen></iframe>`
|
||||
video := `<iframe width="650" height="350" frameborder="0" src="` + config.Opts.YouTubeEmbedUrlOverride() + matches[1] + `" allowfullscreen></iframe>`
|
||||
return video + `<br>` + 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(`<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/`)
|
||||
sb.WriteString(`<iframe width="650" height="350" frameborder="0" src="`)
|
||||
sb.WriteString(config.Opts.YouTubeEmbedUrlOverride())
|
||||
sb.WriteString(match[1])
|
||||
sb.WriteString(`" allowfullscreen></iframe><br>`)
|
||||
}
|
||||
|
|
|
@ -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: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1234" allowfullscreen></iframe><br>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: `<iframe width="650" height="350" frameborder="0" src="https://invidious.custom/embed/1234" allowfullscreen></iframe><br>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`,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 := `<p>This is a <strong>text</strong> with an image: <img src="http://example.org/" alt="Test" loading="lazy">.</p>`
|
||||
|
@ -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 := `<iframe src="https://www.youtube.com/embed/test123?version=3&rel=1&fs=1&autohide=2&showsearch=0&showinfo=1&iv_load_policy=1&wmode=transparent"></iframe>`
|
||||
expected := `<iframe src="https://invidious.custom/embed/test123?version=3&rel=1&fs=1&autohide=2&showsearch=0&showinfo=1&iv_load_policy=1&wmode=transparent" sandbox="allow-scripts allow-same-origin allow-popups" loading="lazy"></iframe>`
|
||||
output := Sanitize("http://example.org/", input)
|
||||
|
||||
if expected != output {
|
||||
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceIframeURL(t *testing.T) {
|
||||
input := `<iframe src="https://player.vimeo.com/video/123456?title=0&byline=0"></iframe>`
|
||||
expected := `<iframe src="https://player.vimeo.com/video/123456?title=0&byline=0" sandbox="allow-scripts allow-same-origin allow-popups" loading="lazy"></iframe>`
|
||||
|
|
Loading…
Reference in a new issue