using Newtonsoft.Json; using Sony.Filtr.ApolloAPI.Models.Apple; using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using System.Web; namespace Sony.Filtr.ApolloAPI { public class ApolloAppleWebApi : ApolloWebApiBase, IApolloAppleWebApi { public const int TrackDefaultPaginationLimit = 100; public const int TrackMaxPaginationLimit = 300; public ApolloAppleWebApi(HttpClient httpClient) : base(httpClient) { } public Task GetPlaylistWithFullTracklistAsync(string storefront, string playlistId) { return this.GetPlaylistWithFullTracklistAsync(storefront, playlistId, NoCacheTime); } public async Task GetPlaylistWithFullTracklistAsync(string storefront, string playlistId, TimeSpan cacheExpiration) { var url = $"vendor-api/apple/v1/catalog/{storefront}/playlists/{playlistId}"; var response = await this.GetFromApollo(url, cacheExpiration); var playlist = response?.data?.FirstOrDefault(); if (playlist?.relationships?.tracks?.data?.Count >= TrackDefaultPaginationLimit) { playlist.relationships.tracks.data = await GetPlaylistTracksAsync(storefront, playlistId, cacheExpiration); } return playlist; } public Task> GetPlaylistsWithFullTracklistsAsync(string storefront, IEnumerable playlistIds) { return this.GetPlaylistsWithFullTracklistsAsync(storefront, playlistIds, NoCacheTime); } public async Task> GetPlaylistsWithFullTracklistsAsync(string storefront, IEnumerable playlistIds, TimeSpan cacheExpiration) { if (!playlistIds.Any()) { return new List(); } var url = $"vendor-api/apple/v1/catalog/{storefront}/playlists?ids={string.Join(",", playlistIds)}"; var playlistResponse = await base.GetFromApollo(url, r => JsonConvert.DeserializeObject(r), cacheExpiration); if (playlistResponse?.data != null && playlistResponse.data.Any(x => x.relationships?.tracks?.data?.Count >= TrackDefaultPaginationLimit)) { foreach (var playlistData in playlistResponse.data.Where(x => x?.relationships?.tracks?.data.Count >= TrackDefaultPaginationLimit)) { playlistData.relationships.tracks.data = await GetPlaylistTracksAsync(storefront, playlistData.id, cacheExpiration); } } return playlistResponse?.data; } public Task> GetPlaylistTracksAsync(string storefront, string playlistId) { return this.GetPlaylistTracksAsync(storefront, playlistId, NoCacheTime); } public async Task> GetPlaylistTracksAsync(string storefront, string playlistId, TimeSpan cacheExpiration) { var tracks = new List(); var url = $"vendor-api/apple/v1/catalog/{storefront}/playlists/{playlistId}/tracks?limit={TrackMaxPaginationLimit}"; do { var response = await this.GetFromApollo(url, cacheExpiration); tracks.AddRange(response.Data); url = null; if (response.Next != null) { var parameters = HttpUtility.ParseQueryString(response.Next); parameters.Add("limit", TrackMaxPaginationLimit.ToString()); url = $"vendor-api/apple/{parameters.ToString()}"; } } while (url != null); return tracks; } public Task> GetSongsAsync(string storefrontId, IEnumerable songIds) { return this.GetSongsAsync(storefrontId, songIds, NoCacheTime); } public async Task> GetSongsAsync(string storefrontId, IEnumerable songIds, TimeSpan cacheExpiration) { var url = $"vendor-api/apple/v1/catalog/{storefrontId}/songs?ids={String.Join(",", songIds)}"; var response = await this.GetFromApollo(url, cacheExpiration); return response.data; } public Task> GetArtistsAsync(string storefrontId, IEnumerable artistIds) { return this.GetArtistsAsync(storefrontId, artistIds, NoCacheTime); } public async Task> GetArtistsAsync(string storefrontId, IEnumerable artistIds, TimeSpan cacheExpiration) { var url = $"vendor-api/apple/v1/catalog/{storefrontId}/artists?ids={String.Join(",", artistIds)}"; var response = await this.GetFromApollo(url, cacheExpiration); return response.data; } public Task> GetAlbumsAsync(string storefrontId, IEnumerable albumIds) { return this.GetAlbumsAsync(storefrontId, albumIds, NoCacheTime); } public async Task> GetAlbumsAsync(string storefrontId, IEnumerable albumIds, TimeSpan cacheExpiration) { var url = $"vendor-api/apple/v1/catalog/{storefrontId}/albums?ids={String.Join(",", albumIds)}"; var response = await this.GetFromApollo(url, cacheExpiration); return response.data; } public Task GetAllStorefrontsAsync() { return this.GetAllStorefrontsAsync(NoCacheTime); } public async Task GetAllStorefrontsAsync(TimeSpan cacheExpiration) { var url = $"vendor-api/apple/v1/storefronts"; var response = await this.GetFromApollo(url, cacheExpiration); return response; } public Task> GetPaginatedSongChartAsync(string storeFront, string chartname, long? genreId = null, int? limit = 50) { return this.GetPaginatedSongChartAsync(storeFront, chartname, NoCacheTime, genreId, limit); } public async Task> GetPaginatedSongChartAsync(string storeFront, string chartname, TimeSpan cacheExpiration, long? genreId = null, int? limit = 50) { var url = $"vendor-api/apple/v1/catalog/{storeFront}/charts?types=songs&genre={genreId}"; if (limit.HasValue) { url += $"&limit={limit.Value}"; } List songData = new List(); var chart = await GetChartResponseAsync(url, cacheExpiration); var songs = chart.results.songs.SingleOrDefault(p => p.chart == chartname); if (songs != null && songs.data != null) { songData.AddRange(songs.data); string nextUrl = songs.next; while (nextUrl != null) { if (limit.HasValue) { nextUrl += $"&limit={limit.HasValue}"; //Force limit for requests since apple music defaults to lower number. } var moreData = await GetChartResponseAsync(nextUrl, cacheExpiration); var moreSongs = moreData?.results?.songs?.SingleOrDefault(p => p.chart == chartname); if (moreSongs?.data != null) { songData.AddRange(moreSongs.data); nextUrl = moreSongs.next; } else { nextUrl = null; } } } return songData; } public Task GetFullChartAsync(string storeFront, long? genreId = null, int? limit = 50) { return this.GetFullChartAsync(storeFront, NoCacheTime, genreId, limit); } public async Task GetFullChartAsync(string storeFront, TimeSpan cacheExpiration, long? genreId = null, int? limit = 50) { var initialResponse = await GetChartAsync(storeFront, cacheExpiration, chartType: null, genreId: genreId, limit: limit); var viewModel = new AppleFullChartViewModel(); if (initialResponse.results.musicVideos != null) { foreach (var resultsMusicVideos in initialResponse.results.musicVideos.Where(p => p.data != null)) { List musicVideos = new List(); musicVideos.AddRange(resultsMusicVideos.data); string nextUrl = resultsMusicVideos.next; while (nextUrl != null) { var moreData = await GetChartResponseAsync(nextUrl, cacheExpiration); if (moreData?.results.musicVideos?.FirstOrDefault()?.data != null) { var chartData = moreData.results.musicVideos.First(); //The next url should only specify this chart type. musicVideos.AddRange(chartData.data); nextUrl = chartData.next; } else { nextUrl = null; } } viewModel.MusicVideos.Add(resultsMusicVideos.chart, musicVideos); } } if (initialResponse.results.songs != null) { foreach (var resultsSong in initialResponse.results.songs.Where(p => p.data != null)) { List songs = new List(); songs.AddRange(resultsSong.data); string nextUrl = resultsSong.next; while (nextUrl != null) { var moreData = await GetChartResponseAsync(nextUrl, cacheExpiration); if (moreData?.results.songs?.FirstOrDefault()?.data != null) { var chartData = moreData.results.songs.First(); //The next url should only specify this chart type. songs.AddRange(chartData.data); nextUrl = chartData.next; } else { nextUrl = null; } } viewModel.Songs.Add(resultsSong.chart, songs); } } if (initialResponse.results.albums != null) { foreach (var resultsAlbum in initialResponse.results.albums.Where(p => p.data != null)) { List albums = new List(); albums.AddRange(resultsAlbum.data); string nextUrl = resultsAlbum.next; while (nextUrl != null) { var moreData = await GetChartResponseAsync(nextUrl, cacheExpiration); if (moreData?.results.albums?.FirstOrDefault()?.data != null) { var chartData = moreData.results.albums.First(); //The next url should only specify this chart type. albums.AddRange(chartData.data); nextUrl = chartData.next; } else { nextUrl = null; } } viewModel.Albums.Add(resultsAlbum.chart, albums); } } if (initialResponse.results.playlists != null) { foreach (var resultsPlaylist in initialResponse.results.playlists.Where(p => p.data != null)) { List playlists = new List(); playlists.AddRange(resultsPlaylist.data); string nextUrl = resultsPlaylist.next; while (nextUrl != null) { var moreData = await GetChartResponseAsync(nextUrl, cacheExpiration); if (moreData?.results.albums?.FirstOrDefault()?.data != null) { var chartData = moreData.results.playlists.First(); //The next url should only specify this chart type. playlists.AddRange(chartData.data); nextUrl = chartData.next; } else { nextUrl = null; } } viewModel.Playlists.Add(resultsPlaylist.chart, playlists); } } return viewModel; } public async Task GetChartAsync(string storeFront, TimeSpan cacheExpritaion, string chartType = null, long? genreId = null, int? limit = null) { if (string.IsNullOrWhiteSpace(chartType)) { chartType = "albums,songs,music-videos,playlists"; } var url = $"vendor-api/apple/v1/catalog/{storeFront}/charts?types={chartType}"; if (genreId != null) { url += $"&genre={genreId}"; } if (limit.HasValue) { url += $"&limit={limit.Value}"; } var chart = await GetChartResponseAsync(url, cacheExpritaion); return chart; } public Task> GetChartGenresAsync(string storeFront) { return this.GetChartGenresAsync(storeFront, NoCacheTime); } public async Task> GetChartGenresAsync(string storeFront, TimeSpan cacheExpiration) { var response = await this.GetFromApollo($"vendor-api/apple/v1/catalog/{storeFront.ToLowerInvariant()}/genres", cacheExpiration); return response.data; } public Task GetPlaylistAsync(string storeFront, string playlistId, string jobName = null) { return this.GetPlaylistAsync(storeFront, playlistId, NoCacheTime, jobName); } public async Task GetPlaylistAsync(string storeFront, string playlistId, TimeSpan cacheExpiration, string jobName = null) { var url = $"vendor-api/apple/v1/catalog/{storeFront}/playlists/{playlistId}"; var response = await this.GetFromApollo< ApplePlaylistResponse>(url, cacheExpiration); return response?.data?.FirstOrDefault(); } private async Task GetChartResponseAsync(string url, TimeSpan cacheExpiration) { return await this.GetFromApollo(url, cacheExpiration); } protected override string GetLoggerName() { return "ApolleAppleWebApi"; } } }