diff --git a/README.md b/README.md index 35f2c4f..2851c97 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ vimeo = [ # Multiple keys will be rotated. update_period = "12h" # How often query for updates, examples: "60m", "4h", "2h45m" quality = "high" # or "low" format = "video" # or "audio" + playlist_sort = "asc" # or "desc", which will fetch playlist items from the end # custom.cover_art_quality use "high" or "low" to special cover image quality from channel cover default is equal with "quality" and disable when custom.cover_art was set. # custom = { title = "Level1News", description = "News sections of Level1Techs, in a podcast feed!", author = "Level1Tech", cover_art = "{IMAGE_URL}", cover_art_quality = "high", category = "TV", subcategories = ["Documentary", "Tech News"], explicit = true, lang = "en" } # Optional feed customizations # max_height = 720 # Optional maximal height of video, example: 720, 1080, 1440, 2160, ... diff --git a/pkg/builder/youtube.go b/pkg/builder/youtube.go index 8e5d491..a0a0040 100644 --- a/pkg/builder/youtube.go +++ b/pkg/builder/youtube.go @@ -330,11 +330,14 @@ func (yt *YouTubeBuilder) queryVideoDescriptions(ctx context.Context, playlist m return nil } -// Cost: (3 units + 5 units) * X pages = 8 units per page +// Cost: +// ASC mode = (3 units + 5 units) * X pages = 8 units per page +// DESC mode = 3 units * (number of pages in the entire playlist) + 5 units func (yt *YouTubeBuilder) queryItems(ctx context.Context, feed *model.Feed) error { var ( - token string - count int + token string + count int + allSnippets []*youtube.PlaylistItemSnippet ) for { @@ -346,25 +349,39 @@ func (yt *YouTubeBuilder) queryItems(ctx context.Context, feed *model.Feed) erro token = pageToken if len(items) == 0 { - return nil + break } // Extract playlist snippets - snippets := map[string]*youtube.PlaylistItemSnippet{} for _, item := range items { - snippets[item.Snippet.ResourceId.VideoId] = item.Snippet + allSnippets = append(allSnippets, item.Snippet) count++ } - // Query video descriptions from the list of ids - if err := yt.queryVideoDescriptions(ctx, snippets, feed); err != nil { - return err - } - - if count >= feed.PageSize || token == "" { - return nil + if (feed.PlaylistSort != model.SortingDesc && count >= feed.PageSize) || token == "" { + break } } + + if len(allSnippets) > feed.PageSize { + if feed.PlaylistSort != model.SortingDesc { + allSnippets = allSnippets[:feed.PageSize] + } else { + allSnippets = allSnippets[len(allSnippets)-feed.PageSize:] + } + } + + snippets := map[string]*youtube.PlaylistItemSnippet{} + for _, snippet := range allSnippets { + snippets[snippet.ResourceId.VideoId] = snippet + } + + // Query video descriptions from the list of ids + if err := yt.queryVideoDescriptions(ctx, snippets, feed); err != nil { + return err + } + + return nil } func (yt *YouTubeBuilder) Build(ctx context.Context, cfg *config.Feed) (*model.Feed, error) { @@ -374,13 +391,14 @@ func (yt *YouTubeBuilder) Build(ctx context.Context, cfg *config.Feed) (*model.F } feed := &model.Feed{ - ItemID: info.ItemID, - Provider: info.Provider, - LinkType: info.LinkType, - Format: cfg.Format, - Quality: cfg.Quality, - PageSize: cfg.PageSize, - UpdatedAt: time.Now().UTC(), + ItemID: info.ItemID, + Provider: info.Provider, + LinkType: info.LinkType, + Format: cfg.Format, + Quality: cfg.Quality, + PageSize: cfg.PageSize, + PlaylistSort: cfg.PlaylistSort, + UpdatedAt: time.Now().UTC(), } if feed.PageSize == 0 { diff --git a/pkg/config/config.go b/pkg/config/config.go index 6930f1d..dcb0197 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -45,6 +45,8 @@ type Feed struct { YouTubeDLArgs []string `toml:"youtube_dl_args"` // Included in OPML file OPML bool `toml:"opml"` + // Playlist sort + PlaylistSort model.Sorting `toml:"playlist_sort"` } type Filters struct { @@ -237,5 +239,9 @@ func (c *Config) applyDefaults(configPath string) { if feed.PageSize == 0 { feed.PageSize = model.DefaultPageSize } + + if feed.PlaylistSort == "" { + feed.PlaylistSort = model.SortingAsc + } } } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 8a1694e..cc79a3e 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -40,6 +40,7 @@ timeout = 15 format = "audio" quality = "low" filters = { title = "regex for title here" } + playlist_sort = "desc" clean = { keep_last = 10 } [feeds.XYZ.custom] cover_art = "http://img" @@ -80,6 +81,7 @@ timeout = 15 assert.EqualValues(t, "low", feed.Quality) assert.EqualValues(t, "regex for title here", feed.Filters.Title) assert.EqualValues(t, 10, feed.Clean.KeepLast) + assert.EqualValues(t, model.SortingDesc, feed.PlaylistSort) assert.EqualValues(t, "http://img", feed.Custom.CoverArt) assert.EqualValues(t, "high", feed.Custom.CoverArtQuality) diff --git a/pkg/model/feed.go b/pkg/model/feed.go index e2c0cab..6df79e0 100644 --- a/pkg/model/feed.go +++ b/pkg/model/feed.go @@ -20,6 +20,14 @@ const ( FormatVideo = Format("video") ) +// Playlist sorting style +type Sorting string + +const ( + SortingDesc = Sorting("desc") + SortingAsc = Sorting("asc") +) + type Episode struct { // ID of episode ID string `json:"id"` @@ -54,6 +62,7 @@ type Feed struct { ItemURL string `json:"item_url"` // Platform specific URL Episodes []*Episode `json:"-"` // Array of episodes UpdatedAt time.Time `json:"updated_at"` + PlaylistSort Sorting `json:"playlist_sort"` } type EpisodeStatus string