diff --git a/pkg/builders/common.go b/pkg/builders/common.go index 5a6c5db..5af9df3 100644 --- a/pkg/builders/common.go +++ b/pkg/builders/common.go @@ -24,3 +24,7 @@ func makeEnclosure(feed *model.Feed, id string, lengthInBytes int64) (string, it url := fmt.Sprintf("http://podsync.net/download/%s/%s.%s", feed.HashID, id, ext) return url, contentType, lengthInBytes } + +type VideoCounter interface { + GetVideoCount(feed *model.Feed) (uint64, error) +} \ No newline at end of file diff --git a/pkg/builders/youtube.go b/pkg/builders/youtube.go index 01e8cfa..8e27b53 100644 --- a/pkg/builders/youtube.go +++ b/pkg/builders/youtube.go @@ -25,6 +25,8 @@ const ( highAudioBytesPerSecond = 128000 / 8 ) +var _ VideoCounter = (*YouTubeBuilder)(nil) + type apiKey string func (key apiKey) Get() (string, string) { @@ -38,8 +40,8 @@ type YouTubeBuilder struct { // Cost: 5 units (call method: 1, snippet: 2, contentDetails: 2) // See https://developers.google.com/youtube/v3/docs/channels/list#part -func (yt *YouTubeBuilder) listChannels(linkType api.LinkType, id string) (*youtube.Channel, error) { - req := yt.client.Channels.List("id,snippet,contentDetails") +func (yt *YouTubeBuilder) listChannels(linkType api.LinkType, id string, parts string) (*youtube.Channel, error) { + req := yt.client.Channels.List(parts) switch linkType { case api.LinkTypeChannel: @@ -65,8 +67,8 @@ func (yt *YouTubeBuilder) listChannels(linkType api.LinkType, id string) (*youtu // Cost: 3 units (call method: 1, snippet: 2) // See https://developers.google.com/youtube/v3/docs/playlists/list#part -func (yt *YouTubeBuilder) listPlaylists(id, channelID string) (*youtube.Playlist, error) { - req := yt.client.Playlists.List("id,snippet") +func (yt *YouTubeBuilder) listPlaylists(id, channelID string, parts string) (*youtube.Playlist, error) { + req := yt.client.Playlists.List(parts) if id != "" { req = req.Id(id) @@ -132,9 +134,29 @@ func (yt *YouTubeBuilder) selectThumbnail(snippet *youtube.ThumbnailDetails, qua return snippet.Default.Url } -// Cost: -// - 5 units for channel or user -// - 3 units for playlist +func (yt *YouTubeBuilder) GetVideoCount(feed *model.Feed) (uint64, error) { + switch feed.LinkType { + case api.LinkTypeChannel, api.LinkTypeUser: + // Cost: 3 units + if channel, err := yt.listChannels(feed.LinkType, feed.ItemID, "id,statistics"); err != nil { + return 0, err + } else { + return channel.Statistics.VideoCount, nil + } + + case api.LinkTypePlaylist: + // Cost: 3 units + if playlist, err := yt.listPlaylists(feed.ItemID, "", "id,contentDetails"); err != nil { + return 0, err + } else { + return uint64(playlist.ContentDetails.ItemCount), nil + } + + default: + return 0, errors.New("unsupported link format") + } +} + func (yt *YouTubeBuilder) queryFeed(feed *model.Feed) (*itunes.Podcast, string, error) { var ( title string @@ -147,7 +169,8 @@ func (yt *YouTubeBuilder) queryFeed(feed *model.Feed) (*itunes.Podcast, string, switch feed.LinkType { case api.LinkTypeChannel, api.LinkTypeUser: - channel, err := yt.listChannels(feed.LinkType, feed.ItemID) + // Cost: 5 units for channel or user + channel, err := yt.listChannels(feed.LinkType, feed.ItemID, "id,snippet,contentDetails") if err != nil { return nil, "", err } @@ -172,7 +195,8 @@ func (yt *YouTubeBuilder) queryFeed(feed *model.Feed) (*itunes.Podcast, string, thumbnails = channel.Snippet.Thumbnails case api.LinkTypePlaylist: - playlist, err := yt.listPlaylists(feed.ItemID, "") + // Cost: 3 units for playlist + playlist, err := yt.listPlaylists(feed.ItemID, "", "id,snippet") if err != nil { return nil, "", err } diff --git a/pkg/builders/youtube_test.go b/pkg/builders/youtube_test.go index bcc1677..a09cdca 100644 --- a/pkg/builders/youtube_test.go +++ b/pkg/builders/youtube_test.go @@ -13,7 +13,7 @@ import ( var ytKey = os.Getenv("YOUTUBE_TEST_API_KEY") -func TestQueryYTChannel(t *testing.T) { +func TestYT_QueryChannel(t *testing.T) { if ytKey == "" { t.Skip("YouTube API key is not provided") } @@ -21,16 +21,16 @@ func TestQueryYTChannel(t *testing.T) { builder, err := NewYouTubeBuilder(ytKey) require.NoError(t, err) - channel, err := builder.listChannels(api.LinkTypeChannel, "UC2yTVSttx7lxAOAzx1opjoA") + channel, err := builder.listChannels(api.LinkTypeChannel, "UC2yTVSttx7lxAOAzx1opjoA", "id") require.NoError(t, err) require.Equal(t, "UC2yTVSttx7lxAOAzx1opjoA", channel.Id) - channel, err = builder.listChannels(api.LinkTypeUser, "fxigr1") + channel, err = builder.listChannels(api.LinkTypeUser, "fxigr1", "id") require.NoError(t, err) require.Equal(t, "UCr_fwF-n-2_olTYd-m3n32g", channel.Id) } -func TestBuildYTFeed(t *testing.T) { +func TestYT_BuildFeed(t *testing.T) { if ytKey == "" { t.Skip("YouTube API key is not provided") } @@ -82,3 +82,29 @@ func TestBuildYTFeed(t *testing.T) { }) } } + +func TestYT_GetVideoCount(t *testing.T) { + if ytKey == "" { + t.Skip("YouTube API key is not provided") + } + + builder, err := NewYouTubeBuilder(ytKey) + require.NoError(t, err) + + feeds := []*model.Feed{ + {Provider: api.ProviderYoutube, LinkType: api.LinkTypeUser, ItemID: "fxigr1"}, + {Provider: api.ProviderYoutube, LinkType: api.LinkTypeChannel, ItemID: "UCupvZG-5ko_eiXAupbDfxWw"}, + {Provider: api.ProviderYoutube, LinkType: api.LinkTypePlaylist, ItemID: "PLfVk3KMh3VX1yJShGRsJmsqAjvMIviJYQ"}, + {Provider: api.ProviderYoutube, LinkType: api.LinkTypeChannel, ItemID: "UCK9lZ2lHRBgx2LOcqPifukA"}, + {Provider: api.ProviderYoutube, LinkType: api.LinkTypeUser, ItemID: "WylsaLive"}, + {Provider: api.ProviderYoutube, LinkType: api.LinkTypePlaylist, ItemID: "PLUVl5pafUrBydT_gsCjRGeCy0hFHloec8"}, + } + + for _, f := range feeds { + t.Run(f.ItemID, func(t *testing.T) { + count, err := builder.GetVideoCount(f) + assert.NoError(t, err) + assert.NotZero(t, count) + }) + } +}