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) {
|
func TestParseConfigDumpOutput(t *testing.T) {
|
||||||
os.Clearenv()
|
os.Clearenv()
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ const (
|
||||||
defaultProxyMediaTypes = "image"
|
defaultProxyMediaTypes = "image"
|
||||||
defaultProxyUrl = ""
|
defaultProxyUrl = ""
|
||||||
defaultFetchYouTubeWatchTime = false
|
defaultFetchYouTubeWatchTime = false
|
||||||
|
defaultYouTubeEmbedUrlOverride = "https://www.youtube-nocookie.com/embed/"
|
||||||
defaultCreateAdmin = false
|
defaultCreateAdmin = false
|
||||||
defaultAdminUsername = ""
|
defaultAdminUsername = ""
|
||||||
defaultAdminPassword = ""
|
defaultAdminPassword = ""
|
||||||
|
@ -126,6 +127,7 @@ type Options struct {
|
||||||
proxyMediaTypes []string
|
proxyMediaTypes []string
|
||||||
proxyUrl string
|
proxyUrl string
|
||||||
fetchYouTubeWatchTime bool
|
fetchYouTubeWatchTime bool
|
||||||
|
youTubeEmbedUrlOverride string
|
||||||
oauth2UserCreationAllowed bool
|
oauth2UserCreationAllowed bool
|
||||||
oauth2ClientID string
|
oauth2ClientID string
|
||||||
oauth2ClientSecret string
|
oauth2ClientSecret string
|
||||||
|
@ -195,6 +197,7 @@ func NewOptions() *Options {
|
||||||
proxyMediaTypes: []string{defaultProxyMediaTypes},
|
proxyMediaTypes: []string{defaultProxyMediaTypes},
|
||||||
proxyUrl: defaultProxyUrl,
|
proxyUrl: defaultProxyUrl,
|
||||||
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
|
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
|
||||||
|
youTubeEmbedUrlOverride: defaultYouTubeEmbedUrlOverride,
|
||||||
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
|
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
|
||||||
oauth2ClientID: defaultOAuth2ClientID,
|
oauth2ClientID: defaultOAuth2ClientID,
|
||||||
oauth2ClientSecret: defaultOAuth2ClientSecret,
|
oauth2ClientSecret: defaultOAuth2ClientSecret,
|
||||||
|
@ -428,6 +431,11 @@ func (o *Options) FetchYouTubeWatchTime() bool {
|
||||||
return o.fetchYouTubeWatchTime
|
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.
|
// ProxyOption returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
|
||||||
func (o *Options) ProxyOption() string {
|
func (o *Options) ProxyOption() string {
|
||||||
return o.proxyOption
|
return o.proxyOption
|
||||||
|
@ -558,20 +566,20 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
"BATCH_SIZE": o.batchSize,
|
"BATCH_SIZE": o.batchSize,
|
||||||
"CERT_DOMAIN": o.certDomain,
|
"CERT_DOMAIN": o.certDomain,
|
||||||
"CERT_FILE": o.certFile,
|
"CERT_FILE": o.certFile,
|
||||||
|
"CLEANUP_ARCHIVE_BATCH_SIZE": o.cleanupArchiveBatchSize,
|
||||||
"CLEANUP_ARCHIVE_READ_DAYS": o.cleanupArchiveReadDays,
|
"CLEANUP_ARCHIVE_READ_DAYS": o.cleanupArchiveReadDays,
|
||||||
"CLEANUP_ARCHIVE_UNREAD_DAYS": o.cleanupArchiveUnreadDays,
|
"CLEANUP_ARCHIVE_UNREAD_DAYS": o.cleanupArchiveUnreadDays,
|
||||||
"CLEANUP_ARCHIVE_BATCH_SIZE": o.cleanupArchiveBatchSize,
|
|
||||||
"CLEANUP_FREQUENCY_HOURS": o.cleanupFrequencyHours,
|
"CLEANUP_FREQUENCY_HOURS": o.cleanupFrequencyHours,
|
||||||
"CLEANUP_REMOVE_SESSIONS_DAYS": o.cleanupRemoveSessionsDays,
|
"CLEANUP_REMOVE_SESSIONS_DAYS": o.cleanupRemoveSessionsDays,
|
||||||
"CREATE_ADMIN": o.createAdmin,
|
"CREATE_ADMIN": o.createAdmin,
|
||||||
|
"DATABASE_CONNECTION_LIFETIME": o.databaseConnectionLifetime,
|
||||||
"DATABASE_MAX_CONNS": o.databaseMaxConns,
|
"DATABASE_MAX_CONNS": o.databaseMaxConns,
|
||||||
"DATABASE_MIN_CONNS": o.databaseMinConns,
|
"DATABASE_MIN_CONNS": o.databaseMinConns,
|
||||||
"DATABASE_CONNECTION_LIFETIME": o.databaseConnectionLifetime,
|
|
||||||
"DATABASE_URL": redactSecretValue(o.databaseURL, redactSecret),
|
"DATABASE_URL": redactSecretValue(o.databaseURL, redactSecret),
|
||||||
"DEBUG": o.debug,
|
"DEBUG": o.debug,
|
||||||
"DISABLE_HSTS": !o.hsts,
|
"DISABLE_HSTS": !o.hsts,
|
||||||
"DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
|
|
||||||
"DISABLE_HTTP_SERVICE": !o.httpService,
|
"DISABLE_HTTP_SERVICE": !o.httpService,
|
||||||
|
"DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
|
||||||
"FETCH_YOUTUBE_WATCH_TIME": o.fetchYouTubeWatchTime,
|
"FETCH_YOUTUBE_WATCH_TIME": o.fetchYouTubeWatchTime,
|
||||||
"HTTPS": o.HTTPS,
|
"HTTPS": o.HTTPS,
|
||||||
"HTTP_CLIENT_MAX_BODY_SIZE": o.httpClientMaxBodySize,
|
"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_CLIENT_USER_AGENT": o.httpClientUserAgent,
|
||||||
"HTTP_SERVER_TIMEOUT": o.httpServerTimeout,
|
"HTTP_SERVER_TIMEOUT": o.httpServerTimeout,
|
||||||
"HTTP_SERVICE": o.httpService,
|
"HTTP_SERVICE": o.httpService,
|
||||||
"KEY_FILE": o.certKeyFile,
|
|
||||||
"INVIDIOUS_INSTANCE": o.invidiousInstance,
|
"INVIDIOUS_INSTANCE": o.invidiousInstance,
|
||||||
|
"KEY_FILE": o.certKeyFile,
|
||||||
"LISTEN_ADDR": o.listenAddr,
|
"LISTEN_ADDR": o.listenAddr,
|
||||||
"LOG_DATE_TIME": o.logDateTime,
|
"LOG_DATE_TIME": o.logDateTime,
|
||||||
"MAINTENANCE_MESSAGE": o.maintenanceMessage,
|
"MAINTENANCE_MESSAGE": o.maintenanceMessage,
|
||||||
"MAINTENANCE_MODE": o.maintenanceMode,
|
"MAINTENANCE_MODE": o.maintenanceMode,
|
||||||
"METRICS_ALLOWED_NETWORKS": strings.Join(o.metricsAllowedNetworks, ","),
|
"METRICS_ALLOWED_NETWORKS": strings.Join(o.metricsAllowedNetworks, ","),
|
||||||
"METRICS_COLLECTOR": o.metricsCollector,
|
"METRICS_COLLECTOR": o.metricsCollector,
|
||||||
|
"METRICS_PASSWORD": redactSecretValue(o.metricsPassword, redactSecret),
|
||||||
"METRICS_REFRESH_INTERVAL": o.metricsRefreshInterval,
|
"METRICS_REFRESH_INTERVAL": o.metricsRefreshInterval,
|
||||||
"METRICS_USERNAME": o.metricsUsername,
|
"METRICS_USERNAME": o.metricsUsername,
|
||||||
"METRICS_PASSWORD": redactSecretValue(o.metricsPassword, redactSecret),
|
|
||||||
"OAUTH2_CLIENT_ID": o.oauth2ClientID,
|
"OAUTH2_CLIENT_ID": o.oauth2ClientID,
|
||||||
"OAUTH2_CLIENT_SECRET": redactSecretValue(o.oauth2ClientSecret, redactSecret),
|
"OAUTH2_CLIENT_SECRET": redactSecretValue(o.oauth2ClientSecret, redactSecret),
|
||||||
"OAUTH2_OIDC_DISCOVERY_ENDPOINT": o.oauth2OidcDiscoveryEndpoint,
|
"OAUTH2_OIDC_DISCOVERY_ENDPOINT": o.oauth2OidcDiscoveryEndpoint,
|
||||||
|
@ -602,9 +610,9 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
"POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit,
|
"POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit,
|
||||||
"POLLING_SCHEDULER": o.pollingScheduler,
|
"POLLING_SCHEDULER": o.pollingScheduler,
|
||||||
"PROXY_HTTP_CLIENT_TIMEOUT": o.proxyHTTPClientTimeout,
|
"PROXY_HTTP_CLIENT_TIMEOUT": o.proxyHTTPClientTimeout,
|
||||||
"PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
|
|
||||||
"PROXY_MEDIA_TYPES": o.proxyMediaTypes,
|
"PROXY_MEDIA_TYPES": o.proxyMediaTypes,
|
||||||
"PROXY_OPTION": o.proxyOption,
|
"PROXY_OPTION": o.proxyOption,
|
||||||
|
"PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
|
||||||
"PROXY_URL": o.proxyUrl,
|
"PROXY_URL": o.proxyUrl,
|
||||||
"ROOT_URL": o.rootURL,
|
"ROOT_URL": o.rootURL,
|
||||||
"RUN_MIGRATIONS": o.runMigrations,
|
"RUN_MIGRATIONS": o.runMigrations,
|
||||||
|
@ -612,8 +620,9 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
|
||||||
"SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": o.schedulerEntryFrequencyMinInterval,
|
"SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": o.schedulerEntryFrequencyMinInterval,
|
||||||
"SCHEDULER_SERVICE": o.schedulerService,
|
"SCHEDULER_SERVICE": o.schedulerService,
|
||||||
"SERVER_TIMING_HEADER": o.serverTimingHeader,
|
"SERVER_TIMING_HEADER": o.serverTimingHeader,
|
||||||
"WORKER_POOL_SIZE": o.workerPoolSize,
|
|
||||||
"WATCHDOG": o.watchdog,
|
"WATCHDOG": o.watchdog,
|
||||||
|
"WORKER_POOL_SIZE": o.workerPoolSize,
|
||||||
|
"YOUTUBE_EMBED_URL_OVERRIDE": o.youTubeEmbedUrlOverride,
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := make([]string, 0, len(keyValues))
|
keys := make([]string, 0, len(keyValues))
|
||||||
|
|
|
@ -215,6 +215,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
|
||||||
p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword)
|
p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword)
|
||||||
case "FETCH_YOUTUBE_WATCH_TIME":
|
case "FETCH_YOUTUBE_WATCH_TIME":
|
||||||
p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime)
|
p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime)
|
||||||
|
case "YOUTUBE_EMBED_URL_OVERRIDE":
|
||||||
|
p.opts.youTubeEmbedUrlOverride = parseString(value, defaultYouTubeEmbedUrlOverride)
|
||||||
case "WATCHDOG":
|
case "WATCHDOG":
|
||||||
p.opts.watchdog = parseBool(value, defaultWatchdog)
|
p.opts.watchdog = parseBool(value, defaultWatchdog)
|
||||||
case "INVIDIOUS_INSTANCE":
|
case "INVIDIOUS_INSTANCE":
|
||||||
|
|
|
@ -124,6 +124,11 @@ use it as a reading time\&.
|
||||||
.br
|
.br
|
||||||
Disabled by default\&.
|
Disabled by default\&.
|
||||||
.TP
|
.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
|
.B SERVER_TIMING_HEADER
|
||||||
Set the value to 1 to enable server-timing headers\&.
|
Set the value to 1 to enable server-timing headers\&.
|
||||||
.br
|
.br
|
||||||
|
|
|
@ -208,7 +208,7 @@ func addYoutubeVideo(entryURL, entryContent string) string {
|
||||||
matches := youtubeRegex.FindStringSubmatch(entryURL)
|
matches := youtubeRegex.FindStringSubmatch(entryURL)
|
||||||
|
|
||||||
if len(matches) == 2 {
|
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 video + `<br>` + entryContent
|
||||||
}
|
}
|
||||||
return entryContent
|
return entryContent
|
||||||
|
@ -232,7 +232,8 @@ func addYoutubeVideoFromId(entryContent string) string {
|
||||||
sb := strings.Builder{}
|
sb := strings.Builder{}
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
if len(match) == 2 {
|
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(match[1])
|
||||||
sb.WriteString(`" allowfullscreen></iframe><br>`)
|
sb.WriteString(`" allowfullscreen></iframe><br>`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
package rewrite // import "miniflux.app/reader/rewrite"
|
package rewrite // import "miniflux.app/reader/rewrite"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"miniflux.app/config"
|
||||||
"miniflux.app/model"
|
"miniflux.app/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,6 +65,8 @@ func TestRewriteWithNoMatchingRule(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRewriteWithYoutubeLink(t *testing.T) {
|
func TestRewriteWithYoutubeLink(t *testing.T) {
|
||||||
|
config.Opts = config.NewOptions()
|
||||||
|
|
||||||
controlEntry := &model.Entry{
|
controlEntry := &model.Entry{
|
||||||
Title: `A title`,
|
Title: `A title`,
|
||||||
Content: `<iframe width="650" height="350" frameborder="0" src="https://www.youtube-nocookie.com/embed/1234" allowfullscreen></iframe><br>Video Description`,
|
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) {
|
func TestRewriteWithInexistingCustomRule(t *testing.T) {
|
||||||
controlEntry := &model.Entry{
|
controlEntry := &model.Entry{
|
||||||
Title: `A title`,
|
Title: `A title`,
|
||||||
|
|
|
@ -441,7 +441,7 @@ func inList(needle string, haystack []string) bool {
|
||||||
func rewriteIframeURL(link string) string {
|
func rewriteIframeURL(link string) string {
|
||||||
matches := youtubeEmbedRegex.FindStringSubmatch(link)
|
matches := youtubeEmbedRegex.FindStringSubmatch(link)
|
||||||
if len(matches) == 2 {
|
if len(matches) == 2 {
|
||||||
return `https://www.youtube-nocookie.com/embed/` + matches[1]
|
return config.Opts.YouTubeEmbedUrlOverride() + matches[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return link
|
return link
|
||||||
|
|
|
@ -3,7 +3,18 @@
|
||||||
|
|
||||||
package sanitizer // import "miniflux.app/reader/sanitizer"
|
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) {
|
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>`
|
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) {
|
func TestReplaceIframeURL(t *testing.T) {
|
||||||
input := `<iframe src="https://player.vimeo.com/video/123456?title=0&byline=0"></iframe>`
|
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>`
|
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