diff --git a/internal/api/api_integration_test.go b/internal/api/api_integration_test.go index 141dcf64..9259b590 100644 --- a/internal/api/api_integration_test.go +++ b/internal/api/api_integration_test.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "math" "math/rand" "os" "strings" @@ -58,7 +57,7 @@ func (c *integrationTestConfig) isConfigured() bool { } func (c *integrationTestConfig) genRandomUsername() string { - return fmt.Sprintf("%s_%10d", c.testRegularUsername, rand.Intn(math.MaxInt64)) + return fmt.Sprintf("%s_%10d", c.testRegularUsername, rand.Int()) } func TestIncorrectEndpoint(t *testing.T) { diff --git a/internal/reader/rewrite/rules.go b/internal/reader/rewrite/rules.go index 89204e9d..063e4539 100644 --- a/internal/reader/rewrite/rules.go +++ b/internal/reader/rewrite/rules.go @@ -24,7 +24,7 @@ var predefinedRules = map[string]string{ "monkeyuser.com": "add_image_title", "mrlovenstein.com": "add_image_title", "nedroid.com": "add_image_title", - "oglaf.com": "add_image_title", + "oglaf.com": `replace("media.oglaf.com/story/tt(.+).gif"|"media.oglaf.com/comic/$1.jpg"),add_image_title`, "optipess.com": "add_image_title", "peebleslab.com": "add_image_title", "quantamagazine.org": `add_youtube_video_from_id, remove("h6:not(.byline,.post__title__kicker), #comments, .next-post__content, .footer__section, figure .outer--content, script")`, diff --git a/internal/reader/subscription/finder.go b/internal/reader/subscription/finder.go index a086136a..1b359a82 100644 --- a/internal/reader/subscription/finder.go +++ b/internal/reader/subscription/finder.go @@ -23,8 +23,9 @@ import ( ) var ( - youtubeChannelRegex = regexp.MustCompile(`youtube\.com/channel/(.*)$`) - youtubeVideoRegex = regexp.MustCompile(`youtube\.com/watch\?v=(.*)$`) + youtubeChannelRegex = regexp.MustCompile(`youtube\.com/channel/(.*)$`) + youtubeVideoRegex = regexp.MustCompile(`youtube\.com/watch\?v=(.*)$`) + youtubePlaylistRegex = regexp.MustCompile(`youtube\.com/playlist\?list=(.*)$`) ) type SubscriptionFinder struct { @@ -98,7 +99,19 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string) return subscriptions, nil } - // Step 4) Parse web page to find feeds from HTML meta tags. + // Step 4) Check if the website URL is a YouTube playlist. + slog.Debug("Try to detect feeds from YouTube playlist page", slog.String("website_url", websiteURL)) + subscriptions, localizedError = f.FindSubscriptionsFromYouTubePlaylistPage(websiteURL) + if localizedError != nil { + return nil, localizedError + } + + if len(subscriptions) > 0 { + slog.Debug("Subscriptions found from YouTube playlist page", slog.String("website_url", websiteURL), slog.Any("subscriptions", subscriptions)) + return subscriptions, nil + } + + // Step 5) Parse web page to find feeds from HTML meta tags. slog.Debug("Try to detect feeds from HTML meta tags", slog.String("website_url", websiteURL), slog.String("content_type", responseHandler.ContentType()), @@ -113,7 +126,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string) return subscriptions, nil } - // Step 5) Check if the website URL can use RSS-Bridge. + // Step 6) Check if the website URL can use RSS-Bridge. if rssBridgeURL != "" { slog.Debug("Try to detect feeds with RSS-Bridge", slog.String("website_url", websiteURL)) subscriptions, localizedError := f.FindSubscriptionsFromRSSBridge(websiteURL, rssBridgeURL) @@ -127,7 +140,7 @@ func (f *SubscriptionFinder) FindSubscriptions(websiteURL, rssBridgeURL string) } } - // Step 6) Check if the website has a known feed URL. + // Step 7) Check if the website has a known feed URL. slog.Debug("Try to detect feeds from well-known URLs", slog.String("website_url", websiteURL)) subscriptions, localizedError = f.FindSubscriptionsFromWellKnownURLs(websiteURL) if localizedError != nil { @@ -322,3 +335,16 @@ func (f *SubscriptionFinder) FindSubscriptionsFromYouTubeVideoPage(websiteURL st return nil, nil } + +func (f *SubscriptionFinder) FindSubscriptionsFromYouTubePlaylistPage(websiteURL string) (Subscriptions, *locale.LocalizedErrorWrapper) { + matches := youtubePlaylistRegex.FindStringSubmatch(websiteURL) + + if len(matches) == 2 { + feedURL := fmt.Sprintf(`https://www.youtube.com/feeds/videos.xml?playlist_id=%s`, matches[1]) + return Subscriptions{NewSubscription(websiteURL, feedURL, parser.FormatAtom)}, nil + } + + slog.Debug("This website is not a YouTube playlist page, the regex doesn't match", slog.String("website_url", websiteURL)) + + return nil, nil +} diff --git a/internal/reader/subscription/finder_test.go b/internal/reader/subscription/finder_test.go index 160b52cb..216d81cb 100644 --- a/internal/reader/subscription/finder_test.go +++ b/internal/reader/subscription/finder_test.go @@ -8,6 +8,28 @@ import ( "testing" ) +func TestFindYoutubePlaylistFeed(t *testing.T) { + scenarios := map[string]string{ + "https://www.youtube.com/playlist?list=PLOOwEPgFWm_NHcQd9aCi5JXWASHO_n5uR": "https://www.youtube.com/feeds/videos.xml?playlist_id=PLOOwEPgFWm_NHcQd9aCi5JXWASHO_n5uR", + "https://www.youtube.com/playlist?list=PLOOwEPgFWm_N42HlCLhqyJ0ZBWr5K1QDM": "https://www.youtube.com/feeds/videos.xml?playlist_id=PLOOwEPgFWm_N42HlCLhqyJ0ZBWr5K1QDM", + } + + for websiteURL, expectedFeedURL := range scenarios { + subscriptions, localizedError := NewSubscriptionFinder(nil).FindSubscriptionsFromYouTubePlaylistPage(websiteURL) + if localizedError != nil { + t.Fatalf(`Parsing a correctly formatted YouTube playlist page should not return any error: %v`, localizedError) + } + + if len(subscriptions) != 1 { + t.Fatal(`Incorrect number of subscriptions returned`) + } + + if subscriptions[0].URL != expectedFeedURL { + t.Errorf(`Unexpected Feed, got %s, instead of %s`, subscriptions[0].URL, expectedFeedURL) + } + } +} + func TestFindYoutubeChannelFeed(t *testing.T) { scenarios := map[string]string{ "https://www.youtube.com/channel/UC-Qj80avWItNRjkZ41rzHyw": "https://www.youtube.com/feeds/videos.xml?channel_id=UC-Qj80avWItNRjkZ41rzHyw",