From a74fca8ce7d6a81f9fec439b3b445cc6712a9729 Mon Sep 17 00:00:00 2001 From: OpenWaygate <27287694+OpenWaygate@users.noreply.github.com> Date: Sun, 14 Sep 2025 19:24:31 +0800 Subject: [PATCH] :sparkles: Support pagination when list --- pkg/activity/activity.go | 37 +++++++---- pkg/caption/caption.go | 24 ++++--- pkg/channel/channel.go | 43 +++++++------ pkg/channelSection/channelSection.go | 6 +- pkg/comment/comment.go | 39 +++++++----- pkg/commentThread/commentThread.go | 44 +++++++------ pkg/i18nLanguage/i18nLanguage.go | 10 +-- pkg/i18nRegion/i18nRegion.go | 6 +- pkg/interface.go | 4 ++ pkg/member/member.go | 35 ++++++++--- pkg/membershipsLevel/membershipsLevel.go | 6 +- pkg/playlist/playlist.go | 35 ++++++++--- pkg/playlistImage/playlistImage.go | 36 +++++++---- pkg/playlistItem/playlistItem.go | 36 ++++++++--- pkg/search/search.go | 63 ++++++++----------- pkg/subscription/subscription.go | 39 ++++++++---- pkg/superChatEvent/superChatEvent.go | 37 +++++++---- pkg/vars.go | 2 +- pkg/video/video.go | 35 ++++++++--- .../videoAbuseReportReason.go | 6 +- pkg/videoCategory/videoCategory.go | 6 +- 21 files changed, 343 insertions(+), 206 deletions(-) diff --git a/pkg/activity/activity.go b/pkg/activity/activity.go index ed15cde..b8794be 100644 --- a/pkg/activity/activity.go +++ b/pkg/activity/activity.go @@ -27,14 +27,14 @@ type activity struct { RegionCode string `yaml:"region_code" json:"region_code"` } -type Activity interface { +type Activity[T any] interface { List([]string, string, string, io.Writer) error - Get([]string) ([]*youtube.Activity, error) + Get([]string) ([]*T, error) } type Option func(*activity) -func NewActivity(opts ...Option) Activity { +func NewActivity(opts ...Option) Activity[youtube.Activity] { a := &activity{} for _, opt := range opts { @@ -58,9 +58,8 @@ func (a *activity) Get(parts []string) ([]*youtube.Activity, error) { call = call.Mine(*a.Mine) } - call.MaxResults(a.MaxResults) - if a.PublishedAfter != "" { + // TODO: update call call.PublishedAfter(a.PublishedAfter) } @@ -72,19 +71,35 @@ func (a *activity) Get(parts []string) ([]*youtube.Activity, error) { call.RegionCode(a.RegionCode) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetActivity, err) + var items []*youtube.Activity + pageToken := "" + for a.MaxResults > 0 { + call = call.MaxResults(min(a.MaxResults, pkg.PerPage)) + a.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetActivity, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (a *activity) List( parts []string, output string, jpath string, writer io.Writer, ) error { activities, err := a.Get(parts) - if err != nil { + if err != nil && activities == nil { return err } @@ -109,7 +124,7 @@ func (a *activity) List( ) } } - return nil + return err } func WithChannelId(channelId string) Option { diff --git a/pkg/caption/caption.go b/pkg/caption/caption.go index b72c1e5..3c31e4c 100644 --- a/pkg/caption/caption.go +++ b/pkg/caption/caption.go @@ -41,18 +41,18 @@ type caption struct { Tlang string `yaml:"tlang" json:"tlang"` } -type Caption interface { - Get(parts []string) ([]*youtube.Caption, error) - List(parts []string, output string, s string, writer io.Writer) error - Insert(output string, s string, writer io.Writer) error - Update(output string, s string, writer io.Writer) error - Delete(writer io.Writer) error - Download(writer io.Writer) error +type Caption[T youtube.Caption] interface { + Get([]string) ([]*T, error) + List([]string, string, string, io.Writer) error + Insert(string, string, io.Writer) error + Update(string, string, io.Writer) error + Delete(io.Writer) error + Download(io.Writer) error } type Option func(*caption) -func NewCation(opts ...Option) Caption { +func NewCation(opts ...Option) Caption[youtube.Caption] { c := &caption{} for _, opt := range opts { opt(c) @@ -112,7 +112,9 @@ func (c *caption) List( return nil } -func (c *caption) Insert(output string, jpath string, writer io.Writer) error { +func (c *caption) Insert( + output string, jpath string, writer io.Writer, +) error { file, err := pkg.Root.Open(c.File) if err != nil { return errors.Join(errInsertCaption, err) @@ -159,7 +161,9 @@ func (c *caption) Insert(output string, jpath string, writer io.Writer) error { return nil } -func (c *caption) Update(output string, jpath string, writer io.Writer) error { +func (c *caption) Update( + output string, jpath string, writer io.Writer, +) error { captions, err := c.Get([]string{"snippet"}) if err != nil { return errors.Join(errUpdateCaption, err) diff --git a/pkg/channel/channel.go b/pkg/channel/channel.go index 1b67576..bbb7b62 100644 --- a/pkg/channel/channel.go +++ b/pkg/channel/channel.go @@ -39,15 +39,15 @@ type channel struct { Title string `yaml:"title" json:"title"` } -type Channel interface { +type Channel[T youtube.Channel] interface { List([]string, string, string, io.Writer) error Update(string, string, io.Writer) error - Get([]string) ([]*youtube.Channel, error) + Get([]string) ([]*T, error) } type Option func(*channel) -func NewChannel(opts ...Option) Channel { +func NewChannel(opts ...Option) Channel[youtube.Channel] { c := &channel{} for _, opt := range opts { @@ -62,54 +62,59 @@ func (c *channel) Get(parts []string) ([]*youtube.Channel, error) { if c.CategoryId != "" { call = call.CategoryId(c.CategoryId) } - if c.ForHandle != "" { call = call.ForHandle(c.ForHandle) } - if c.ForUsername != "" { call = call.ForUsername(c.ForUsername) } - if c.Hl != "" { call = call.Hl(c.Hl) } - if len(c.IDs) > 0 { call = call.Id(c.IDs...) } - if c.ManagedByMe != nil { call = call.ManagedByMe(*c.ManagedByMe) } - - call = call.MaxResults(c.MaxResults) - if c.Mine != nil { call = call.Mine(*c.Mine) } - if c.MySubscribers != nil { call = call.MySubscribers(*c.MySubscribers) } - if c.OnBehalfOfContentOwner != "" { call = call.OnBehalfOfContentOwner(c.OnBehalfOfContentOwner) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetChannel, err) + var items []*youtube.Channel + pageToken := "" + for c.MaxResults > 0 { + call = call.MaxResults(min(c.MaxResults, pkg.PerPage)) + c.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetChannel, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (c *channel) List( parts []string, output string, jpath string, writer io.Writer, ) error { channels, err := c.Get(parts) - if err != nil { + if err != nil && channels == nil { return err } @@ -131,7 +136,7 @@ func (c *channel) List( ) } } - return nil + return err } func (c *channel) Update(output string, jpath string, writer io.Writer) error { diff --git a/pkg/channelSection/channelSection.go b/pkg/channelSection/channelSection.go index ff49e1d..0817012 100644 --- a/pkg/channelSection/channelSection.go +++ b/pkg/channelSection/channelSection.go @@ -26,8 +26,8 @@ type channelSection struct { OnBehalfOfContentOwner string `yaml:"on_behalf_of_content_owner" json:"on_behalf_of_content_owner"` } -type ChannelSection interface { - Get([]string) ([]*youtube.ChannelSection, error) +type ChannelSection[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error Delete(writer io.Writer) error // Update() @@ -36,7 +36,7 @@ type ChannelSection interface { type Option func(*channelSection) -func NewChannelSection(opts ...Option) ChannelSection { +func NewChannelSection(opts ...Option) ChannelSection[youtube.ChannelSection] { cs := &channelSection{} for _, opt := range opts { diff --git a/pkg/comment/comment.go b/pkg/comment/comment.go index 812cbbd..7b0fa3f 100644 --- a/pkg/comment/comment.go +++ b/pkg/comment/comment.go @@ -38,8 +38,8 @@ type comment struct { ViewerRating string `yaml:"viewer_rating" json:"viewer_rating"` } -type Comment interface { - Get([]string) ([]*youtube.Comment, error) +type Comment[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error Insert(string, string, io.Writer) error Update(string, string, io.Writer) error @@ -50,7 +50,7 @@ type Comment interface { type Option func(*comment) -func NewComment(opts ...Option) Comment { +func NewComment(opts ...Option) Comment[youtube.Comment] { c := &comment{} for _, opt := range opts { @@ -62,34 +62,45 @@ func NewComment(opts ...Option) Comment { func (c *comment) Get(parts []string) ([]*youtube.Comment, error) { call := service.Comments.List(parts) - if c.IDs[0] != "" { call = call.Id(c.IDs...) } - - call = call.MaxResults(c.MaxResults) - if c.ParentId != "" { call = call.ParentId(c.ParentId) } - if c.TextFormat != "" { call = call.TextFormat(c.TextFormat) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetComment, err) + var items []*youtube.Comment + pageToken := "" + for c.MaxResults > 0 { + call = call.MaxResults(min(c.MaxResults, pkg.PerPage)) + c.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetComment, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (c *comment) List( parts []string, output string, jpath string, writer io.Writer, ) error { comments, err := c.Get(parts) - if err != nil { + if err != nil && comments == nil { return err } @@ -114,7 +125,7 @@ func (c *comment) List( ) } } - return nil + return err } func (c *comment) Insert(output string, jpath string, writer io.Writer) error { diff --git a/pkg/commentThread/commentThread.go b/pkg/commentThread/commentThread.go index 1e9d5af..dcb42ca 100644 --- a/pkg/commentThread/commentThread.go +++ b/pkg/commentThread/commentThread.go @@ -33,15 +33,15 @@ type commentThread struct { VideoId string `yaml:"video_id" json:"video_id"` } -type CommentThread interface { - Get([]string) ([]*youtube.CommentThread, error) +type CommentThread[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error Insert(output string, s string, writer io.Writer) error } type Option func(*commentThread) -func NewCommentThread(opts ...Option) CommentThread { +func NewCommentThread(opts ...Option) CommentThread[youtube.CommentThread] { c := &commentThread{} for _, opt := range opts { @@ -53,54 +53,60 @@ func NewCommentThread(opts ...Option) CommentThread { func (c *commentThread) Get(parts []string) ([]*youtube.CommentThread, error) { call := service.CommentThreads.List(parts) - if len(c.IDs) > 0 { call = call.Id(c.IDs...) } - if c.AllThreadsRelatedToChannelId != "" { call = call.AllThreadsRelatedToChannelId(c.AllThreadsRelatedToChannelId) } - if c.ChannelId != "" { call = call.ChannelId(c.ChannelId) } - - call = call.MaxResults(c.MaxResults) - if c.ModerationStatus != "" { call = call.ModerationStatus(c.ModerationStatus) } - if c.Order != "" { call = call.Order(c.Order) } - if c.SearchTerms != "" { call = call.SearchTerms(c.SearchTerms) } - if c.TextFormat != "" { call = call.TextFormat(c.TextFormat) } - if c.VideoId != "" { call = call.VideoId(c.VideoId) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetCommentThread, err) + var items []*youtube.CommentThread + pageToken := "" + for c.MaxResults > 0 { + call = call.MaxResults(min(c.MaxResults, pkg.PerPage)) + c.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetCommentThread, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (c *commentThread) List( parts []string, output string, jpath string, writer io.Writer, ) error { commentThreads, err := c.Get(parts) - if err != nil { + if err != nil && commentThreads == nil { return err } @@ -126,7 +132,7 @@ func (c *commentThread) List( ) } } - return nil + return err } func (c *commentThread) Insert( diff --git a/pkg/i18nLanguage/i18nLanguage.go b/pkg/i18nLanguage/i18nLanguage.go index 97c43f8..e10eb69 100644 --- a/pkg/i18nLanguage/i18nLanguage.go +++ b/pkg/i18nLanguage/i18nLanguage.go @@ -20,14 +20,14 @@ type i18nLanguage struct { Hl string `yaml:"hl" json:"hl"` } -type I18nLanguage interface { - Get([]string) ([]*youtube.I18nLanguage, error) +type I18nLanguage[T youtube.I18nLanguage] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error } type Option func(*i18nLanguage) -func NewI18nLanguage(opts ...Option) I18nLanguage { +func NewI18nLanguage(opts ...Option) I18nLanguage[youtube.I18nLanguage] { i := &i18nLanguage{} for _, opt := range opts { @@ -37,7 +37,9 @@ func NewI18nLanguage(opts ...Option) I18nLanguage { return i } -func (i *i18nLanguage) Get(parts []string) ([]*youtube.I18nLanguage, error) { +func (i *i18nLanguage) Get(parts []string) ( + []*youtube.I18nLanguage, error, +) { call := service.I18nLanguages.List(parts) if i.Hl != "" { call = call.Hl(i.Hl) diff --git a/pkg/i18nRegion/i18nRegion.go b/pkg/i18nRegion/i18nRegion.go index 968d0c8..4326134 100644 --- a/pkg/i18nRegion/i18nRegion.go +++ b/pkg/i18nRegion/i18nRegion.go @@ -20,14 +20,14 @@ type i18nRegion struct { Hl string `yaml:"hl" json:"hl"` } -type I18nRegion interface { - Get([]string) ([]*youtube.I18nRegion, error) +type I18nRegion[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error } type Option func(*i18nRegion) -func NewI18nRegion(opts ...Option) I18nRegion { +func NewI18nRegion(opts ...Option) I18nRegion[youtube.I18nRegion] { i := &i18nRegion{} for _, opt := range opts { diff --git a/pkg/interface.go b/pkg/interface.go index 038868a..89bba23 100644 --- a/pkg/interface.go +++ b/pkg/interface.go @@ -4,6 +4,10 @@ import ( "io" ) +type Getter[T any] interface { + Get([]string) ([]*T, error) +} + type Lister interface { List([]string, string, string, io.Writer) error } diff --git a/pkg/member/member.go b/pkg/member/member.go index b99dafc..fd180d1 100644 --- a/pkg/member/member.go +++ b/pkg/member/member.go @@ -24,14 +24,14 @@ type member struct { Mode string `yaml:"mode" json:"mode"` } -type Member interface { +type Member[T any] interface { List([]string, string, string, io.Writer) error - Get([]string) ([]*youtube.Member, error) + Get([]string) ([]*T, error) } type Option func(*member) -func NewMember(opts ...Option) Member { +func NewMember(opts ...Option) Member[youtube.Member] { m := &member{} for _, opt := range opts { @@ -49,24 +49,39 @@ func (m *member) Get(parts []string) ([]*youtube.Member, error) { if m.HasAccessToLevel != "" { call = call.HasAccessToLevel(m.HasAccessToLevel) } - call = call.MaxResults(m.MaxResults) if m.Mode != "" { call = call.Mode(m.Mode) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetMember, err) + var items []*youtube.Member + pageToken := "" + for m.MaxResults > 0 { + call = call.MaxResults(min(m.MaxResults, pkg.PerPage)) + m.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetMember, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (m *member) List( parts []string, output string, jpath string, writer io.Writer, ) error { members, err := m.Get(parts) - if err != nil { + if err != nil && members == nil { return err } @@ -91,7 +106,7 @@ func (m *member) List( ) } } - return nil + return err } func WithMemberChannelId(channelId string) Option { diff --git a/pkg/membershipsLevel/membershipsLevel.go b/pkg/membershipsLevel/membershipsLevel.go index 2661541..08c3bb4 100644 --- a/pkg/membershipsLevel/membershipsLevel.go +++ b/pkg/membershipsLevel/membershipsLevel.go @@ -18,14 +18,14 @@ var ( type membershipsLevel struct{} -type MembershipsLevel interface { +type MembershipsLevel[T any] interface { List([]string, string, string, io.Writer) error - Get([]string) ([]*youtube.MembershipsLevel, error) + Get([]string) ([]*T, error) } type Option func(*membershipsLevel) -func NewMembershipsLevel(opts ...Option) MembershipsLevel { +func NewMembershipsLevel(opts ...Option) MembershipsLevel[youtube.MembershipsLevel] { m := &membershipsLevel{} for _, opt := range opts { diff --git a/pkg/playlist/playlist.go b/pkg/playlist/playlist.go index 4ea077e..f9f34ea 100644 --- a/pkg/playlist/playlist.go +++ b/pkg/playlist/playlist.go @@ -37,17 +37,17 @@ type playlist struct { OnBehalfOfContentOwnerChannel string `yaml:"on_behalf_of_content_owner_channel" json:"on_behalf_of_content_owner_channel"` } -type Playlist interface { +type Playlist[T any] interface { List([]string, string, string, io.Writer) error Insert(string, string, io.Writer) error Update(string, string, io.Writer) error Delete(io.Writer) error - Get([]string) ([]*youtube.Playlist, error) + Get([]string) ([]*T, error) } type Option func(*playlist) -func NewPlaylist(opts ...Option) Playlist { +func NewPlaylist(opts ...Option) Playlist[youtube.Playlist] { p := &playlist{} for _, opt := range opts { @@ -69,7 +69,6 @@ func (p *playlist) Get(parts []string) ([]*youtube.Playlist, error) { if p.Mine != nil { call = call.Mine(*p.Mine) } - call = call.MaxResults(p.MaxResults) if p.OnBehalfOfContentOwner != "" { call = call.OnBehalfOfContentOwner(p.OnBehalfOfContentOwner) } @@ -77,19 +76,35 @@ func (p *playlist) Get(parts []string) ([]*youtube.Playlist, error) { call = call.OnBehalfOfContentOwnerChannel(p.OnBehalfOfContentOwnerChannel) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetPlaylist, err) + var items []*youtube.Playlist + pageToken := "" + for p.MaxResults > 0 { + call = call.MaxResults(min(p.MaxResults, pkg.PerPage)) + p.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetPlaylist, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (p *playlist) List( parts []string, output string, jpath string, writer io.Writer, ) error { playlists, err := p.Get(parts) - if err != nil { + if err != nil && playlists == nil { return err } @@ -109,7 +124,7 @@ func (p *playlist) List( tb.AppendRow(table.Row{pl.Id, pl.Snippet.ChannelId, pl.Snippet.Title}) } } - return nil + return err } func (p *playlist) Insert(output string, jpath string, writer io.Writer) error { diff --git a/pkg/playlistImage/playlistImage.go b/pkg/playlistImage/playlistImage.go index 602867d..fb6cf5c 100644 --- a/pkg/playlistImage/playlistImage.go +++ b/pkg/playlistImage/playlistImage.go @@ -36,8 +36,8 @@ type playlistImage struct { OnBehalfOfContentOwnerChannel string `yaml:"on_behalf_of_content_owner_channel" json:"on_behalf_of_content_owner_channel"` } -type PlaylistImage interface { - Get([]string) ([]*youtube.PlaylistImage, error) +type PlaylistImage[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error Insert(string, string, io.Writer) error Update(string, string, io.Writer) error @@ -46,7 +46,7 @@ type PlaylistImage interface { type Option func(*playlistImage) -func NewPlaylistImage(opts ...Option) PlaylistImage { +func NewPlaylistImage(opts ...Option) PlaylistImage[youtube.PlaylistImage] { pi := &playlistImage{} for _, opt := range opts { opt(pi) @@ -57,7 +57,6 @@ func NewPlaylistImage(opts ...Option) PlaylistImage { func (pi *playlistImage) Get(parts []string) ([]*youtube.PlaylistImage, error) { call := service.PlaylistImages.List() call = call.Part(parts...) - if pi.Parent != "" { call = call.Parent(pi.Parent) } @@ -67,21 +66,36 @@ func (pi *playlistImage) Get(parts []string) ([]*youtube.PlaylistImage, error) { if pi.OnBehalfOfContentOwnerChannel != "" { call = call.OnBehalfOfContentOwnerChannel(pi.OnBehalfOfContentOwnerChannel) } - call = call.MaxResults(pi.MaxResults) - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetPlaylistImage, err) + var items []*youtube.PlaylistImage + pageToken := "" + for pi.MaxResults > 0 { + call = call.MaxResults(min(pi.MaxResults, pkg.PerPage)) + pi.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetPlaylistImage, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (pi *playlistImage) List( parts []string, output string, jpath string, writer io.Writer, ) error { playlistImages, err := pi.Get(parts) - if err != nil { + if err != nil && playlistImages == nil { return err } @@ -103,7 +117,7 @@ func (pi *playlistImage) List( ) } } - return nil + return err } func (pi *playlistImage) Insert( diff --git a/pkg/playlistItem/playlistItem.go b/pkg/playlistItem/playlistItem.go index 79b97fd..a0b682c 100644 --- a/pkg/playlistItem/playlistItem.go +++ b/pkg/playlistItem/playlistItem.go @@ -38,17 +38,17 @@ type playlistItem struct { OnBehalfOfContentOwner string `yaml:"on_behalf_of_content_owner" json:"on_behalf_of_content_owner"` } -type PlaylistItem interface { +type PlaylistItem[T any] interface { List([]string, string, string, io.Writer) error Insert(string, string, io.Writer) error Update(string, string, io.Writer) error Delete(io.Writer) error - Get([]string) ([]*youtube.PlaylistItem, error) + Get([]string) ([]*T, error) } type Option func(*playlistItem) -func NewPlaylistItem(opts ...Option) PlaylistItem { +func NewPlaylistItem(opts ...Option) PlaylistItem[youtube.PlaylistItem] { p := &playlistItem{} for _, opt := range opts { @@ -72,20 +72,36 @@ func (pi *playlistItem) Get(parts []string) ([]*youtube.PlaylistItem, error) { if pi.VideoId != "" { call = call.VideoId(pi.VideoId) } - call = call.MaxResults(pi.MaxResults) - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetPlaylistItem, err) + + var items []*youtube.PlaylistItem + pageToken := "" + for pi.MaxResults > 0 { + call = call.MaxResults(min(pi.MaxResults, pkg.PerPage)) + pi.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetPlaylistItem, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (pi *playlistItem) List( parts []string, output string, jpath string, writer io.Writer, ) error { playlistItems, err := pi.Get(parts) - if err != nil { + if err != nil && playlistItems == nil { return err } @@ -118,7 +134,7 @@ func (pi *playlistItem) List( ) } } - return nil + return err } func (pi *playlistItem) Insert( diff --git a/pkg/search/search.go b/pkg/search/search.go index 86278e8..c711e70 100644 --- a/pkg/search/search.go +++ b/pkg/search/search.go @@ -49,14 +49,14 @@ type search struct { VideoType string `yaml:"video_type" json:"video_type"` } -type Search interface { - Get([]string) ([]*youtube.SearchResult, error) +type Search[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error } type Option func(*search) -func NewSearch(opts ...Option) Search { +func NewSearch(opts ...Option) Search[youtube.SearchResult] { s := &search{} for _, opt := range opts { @@ -71,130 +71,117 @@ func (s *search) Get(parts []string) ([]*youtube.SearchResult, error) { if s.ChannelId != "" { call.ChannelId(s.ChannelId) } - if s.ChannelType != "" { call.ChannelType(s.ChannelType) } - if s.EventType != "" { call.EventType(s.EventType) } - if s.ForContentOwner != nil { call.ForContentOwner(*s.ForContentOwner) } - if s.ForDeveloper != nil { call.ForDeveloper(*s.ForDeveloper) } - if s.ForMine != nil { call.ForMine(*s.ForMine) } - if s.Location != "" { call.Location(s.Location) } - if s.LocationRadius != "" { call.LocationRadius(s.LocationRadius) } - - call.MaxResults(s.MaxResults) - if s.OnBehalfOfContentOwner != "" { call.OnBehalfOfContentOwner(s.OnBehalfOfContentOwner) } - if s.Order != "" { call.Order(s.Order) } - if s.PublishedAfter != "" { call.PublishedAfter(s.PublishedAfter) } - if s.PublishedBefore != "" { call.PublishedBefore(s.PublishedBefore) } - if s.Q != "" { call.Q(s.Q) } - if s.RegionCode != "" { call.RegionCode(s.RegionCode) } - if s.RelevanceLanguage != "" { call.RelevanceLanguage(s.RelevanceLanguage) } - if s.SafeSearch != "" { call.SafeSearch(s.SafeSearch) } - if s.TopicId != "" { call.TopicId(s.TopicId) } - if len(s.Types) > 0 { call.Type(s.Types...) } - if s.VideoCaption != "" { call.VideoCaption(s.VideoCaption) } - if s.VideoCategoryId != "" { call.VideoCategoryId(s.VideoCategoryId) } - if s.VideoDefinition != "" { call.VideoDefinition(s.VideoDefinition) } - if s.VideoDimension != "" { call.VideoDimension(s.VideoDimension) } - if s.VideoDuration != "" { call.VideoDuration(s.VideoDuration) } - if s.VideoEmbeddable != "" { call.VideoEmbeddable(s.VideoEmbeddable) } - if s.VideoLicense != "" { call.VideoLicense(s.VideoLicense) } - if s.VideoPaidProductPlacement != "" { call.VideoPaidProductPlacement(s.VideoPaidProductPlacement) } - if s.VideoSyndicated != "" { call.VideoSyndicated(s.VideoSyndicated) } - if s.VideoType != "" { call.VideoType(s.VideoType) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetSearch, err) + var items []*youtube.SearchResult + pageToken := "" + for s.MaxResults > 0 { + call = call.MaxResults(min(s.MaxResults, pkg.PerPage)) + s.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetSearch, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (s *search) List( parts []string, output string, jpath string, writer io.Writer, ) error { results, err := s.Get(parts) - if err != nil { + if err != nil && results == nil { return err } @@ -225,7 +212,7 @@ func (s *search) List( ) } } - return nil + return err } func WithChannelId(channelId string) Option { diff --git a/pkg/subscription/subscription.go b/pkg/subscription/subscription.go index a7681f8..6d31bea 100644 --- a/pkg/subscription/subscription.go +++ b/pkg/subscription/subscription.go @@ -36,8 +36,8 @@ type subscription struct { Title string `yaml:"title" json:"title"` } -type Subscription interface { - Get([]string) ([]*youtube.Subscription, error) +type Subscription[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error Insert(string, string, io.Writer) error Delete(io.Writer) error @@ -45,7 +45,7 @@ type Subscription interface { type Option func(*subscription) -func NewSubscription(opts ...Option) Subscription { +func NewSubscription(opts ...Option) Subscription[youtube.Subscription] { s := &subscription{} for _, opt := range opts { @@ -66,8 +66,6 @@ func (s *subscription) Get(parts []string) ([]*youtube.Subscription, error) { if s.ForChannelId != "" { call = call.ForChannelId(s.ForChannelId) } - call = call.MaxResults(s.MaxResults) - if s.Mine != nil { call = call.Mine(*s.Mine) } @@ -77,7 +75,6 @@ func (s *subscription) Get(parts []string) ([]*youtube.Subscription, error) { if s.MySubscribers != nil { call = call.MySubscribers(*s.MySubscribers) } - if s.OnBehalfOfContentOwner != "" { call = call.OnBehalfOfContentOwner(s.OnBehalfOfContentOwner) } @@ -88,20 +85,36 @@ func (s *subscription) Get(parts []string) ([]*youtube.Subscription, error) { call = call.Order(s.Order) } - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetSubscription, err) + var items []*youtube.Subscription + pageToken := "" + for s.MaxResults > 0 { + call = call.MaxResults(min(s.MaxResults, pkg.PerPage)) + s.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetSubscription, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (s *subscription) List( parts []string, output string, jpath string, writer io.Writer, ) error { subscriptions, err := s.Get(parts) - if err != nil { - return errors.Join(errGetSubscription, err) + if err != nil && subscriptions == nil { + return err } switch output { @@ -133,7 +146,7 @@ func (s *subscription) List( ) } } - return nil + return err } func (s *subscription) Insert( diff --git a/pkg/superChatEvent/superChatEvent.go b/pkg/superChatEvent/superChatEvent.go index 4f7a0f8..faa7847 100644 --- a/pkg/superChatEvent/superChatEvent.go +++ b/pkg/superChatEvent/superChatEvent.go @@ -22,14 +22,14 @@ type superChatEvent struct { MaxResults int64 `yaml:"max_results" json:"max_results"` } -type SuperChatEvent interface { - Get([]string) ([]*youtube.SuperChatEvent, error) +type SuperChatEvent[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error } type Option func(*superChatEvent) -func NewSuperChatEvent(opts ...Option) SuperChatEvent { +func NewSuperChatEvent(opts ...Option) SuperChatEvent[youtube.SuperChatEvent] { s := &superChatEvent{} for _, opt := range opts { @@ -44,22 +44,37 @@ func (s *superChatEvent) Get(parts []string) ([]*youtube.SuperChatEvent, error) if s.Hl != "" { call = call.Hl(s.Hl) } - call = call.MaxResults(s.MaxResults) - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetSuperChatEvent, err) + var items []*youtube.SuperChatEvent + pageToken := "" + for s.MaxResults > 0 { + call = call.MaxResults(min(s.MaxResults, pkg.PerPage)) + s.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetSuperChatEvent, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (s *superChatEvent) List( parts []string, output string, jpath string, writer io.Writer, ) error { events, err := s.Get(parts) - if err != nil { - return errors.Join(errGetSuperChatEvent, err) + if err != nil && events == nil { + return err } switch output { @@ -83,7 +98,7 @@ func (s *superChatEvent) List( ) } } - return nil + return err } func WithHl(hl string) Option { diff --git a/pkg/vars.go b/pkg/vars.go index 1afc93a..843741a 100644 --- a/pkg/vars.go +++ b/pkg/vars.go @@ -7,7 +7,7 @@ import ( const ( PartsUsage = "Comma separated parts" - MRUsage = "The maximum number of items that should be returned" + MRUsage = "The maximum number of items that should be returned, 0 for no limit" TableUsage = "json, yaml, or table" SilentUsage = "json, yaml, or silent" JPUsage = "JSONPath expression to filter the output" diff --git a/pkg/video/video.go b/pkg/video/video.go index 56a7266..e80991e 100644 --- a/pkg/video/video.go +++ b/pkg/video/video.go @@ -64,7 +64,7 @@ type video struct { OnBehalfOfContentOwnerChannel string `yaml:"on_behalf_of_content_owner_channel" json:"on_behalf_of_content_owner_channel"` } -type Video interface { +type Video[T any] interface { List([]string, string, string, io.Writer) error Insert(string, string, io.Writer) error Update(string, string, io.Writer) error @@ -72,12 +72,12 @@ type Video interface { GetRating(string, string, io.Writer) error Delete(io.Writer) error ReportAbuse(io.Writer) error - Get([]string) ([]*youtube.Video, error) + Get([]string) ([]*T, error) } type Option func(*video) -func NewVideo(opts ...Option) Video { +func NewVideo(opts ...Option) Video[youtube.Video] { v := &video{} for _, opt := range opts { @@ -119,21 +119,36 @@ func (v *video) Get(parts []string) ([]*youtube.Video, error) { if v.OnBehalfOfContentOwner != "" { call = call.OnBehalfOfContentOwner(v.OnBehalfOfContentOwner) } - call = call.MaxResults(v.MaxResults) - res, err := call.Do() - if err != nil { - return nil, errors.Join(errGetVideo, err) + var items []*youtube.Video + pageToken := "" + for v.MaxResults > 0 { + call = call.MaxResults(min(v.MaxResults, pkg.PerPage)) + v.MaxResults -= pkg.PerPage + if pageToken != "" { + call = call.PageToken(pageToken) + } + + res, err := call.Do() + if err != nil { + return items, errors.Join(errGetVideo, err) + } + + items = append(items, res.Items...) + pageToken = res.NextPageToken + if pageToken == "" || len(res.Items) == 0 { + break + } } - return res.Items, nil + return items, nil } func (v *video) List( parts []string, output string, jpath string, writer io.Writer, ) error { videos, err := v.Get(parts) - if err != nil { + if err != nil && videos == nil { return err } @@ -159,7 +174,7 @@ func (v *video) List( ) } } - return nil + return err } func (v *video) Insert(output string, jpath string, writer io.Writer) error { diff --git a/pkg/videoAbuseReportReason/videoAbuseReportReason.go b/pkg/videoAbuseReportReason/videoAbuseReportReason.go index 7b4b287..55cdb3c 100644 --- a/pkg/videoAbuseReportReason/videoAbuseReportReason.go +++ b/pkg/videoAbuseReportReason/videoAbuseReportReason.go @@ -20,14 +20,14 @@ type videoAbuseReportReason struct { Hl string `yaml:"hl" json:"hl"` } -type VideoAbuseReportReason interface { - Get([]string) ([]*youtube.VideoAbuseReportReason, error) +type VideoAbuseReportReason[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error } type Option func(*videoAbuseReportReason) -func NewVideoAbuseReportReason(opt ...Option) VideoAbuseReportReason { +func NewVideoAbuseReportReason(opt ...Option) VideoAbuseReportReason[youtube.VideoAbuseReportReason] { va := &videoAbuseReportReason{} for _, o := range opt { o(va) diff --git a/pkg/videoCategory/videoCategory.go b/pkg/videoCategory/videoCategory.go index 6df8ef9..82bf151 100644 --- a/pkg/videoCategory/videoCategory.go +++ b/pkg/videoCategory/videoCategory.go @@ -22,14 +22,14 @@ type videoCategory struct { RegionCode string `yaml:"region_code" json:"region_code"` } -type VideoCategory interface { - Get([]string) ([]*youtube.VideoCategory, error) +type VideoCategory[T any] interface { + Get([]string) ([]*T, error) List([]string, string, string, io.Writer) error } type Option func(*videoCategory) -func NewVideoCategory(opt ...Option) VideoCategory { +func NewVideoCategory(opt ...Option) VideoCategory[youtube.VideoCategory] { vc := &videoCategory{} for _, o := range opt { o(vc)