using Roadie.Library.Caching; using RestSharp; using Roadie.Library.Extensions; using Roadie.Library.MetaData; using Roadie.Library.Utility; using Roadie.Library.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Authentication; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; namespace Roadie.Library.SearchEngines.MetaData.Spotify { public class SpotifyHelper : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine { public override bool IsEnabled { get { return this.Configuration.GetValue("Integrations:SpotifyProviderEnabled", true); } } public SpotifyHelper(IConfiguration configuration, ICacheManager cacheManager, ILogger loggingService) : base(configuration, cacheManager, loggingService) { } private RestRequest BuildSearchRequest(string query, int resultsCount, string entityType) { var request = new RestRequest { Resource = "search", Method = Method.GET, RequestFormat = DataFormat.Json }; request.AddParameter(new Parameter { Name = "type", Value = entityType, Type = ParameterType.GetOrPost }); request.AddParameter(new Parameter { Name = "q", Value = string.Format("{0}", query.Trim()), Type = ParameterType.GetOrPost }); request.AddParameter(new Parameter { Name = "market", Value = "US", Type = ParameterType.GetOrPost }); return request; } public async Task>> PerformArtistSearch(string query, int resultsCount) { ArtistSearchResult data = null; try { this.Logger.Trace("SpotifyHelper:PerformArtistSearch:{0}", query); var request = this.BuildSearchRequest(query, 1, "artist"); var client = new RestClient("http://api.spotify.com/v1"); client.UserAgent = WebHelper.UserAgent; var response = await client.ExecuteTaskAsync(request); if (response.ResponseStatus == ResponseStatus.Error) { if (response.StatusCode == HttpStatusCode.Unauthorized) { throw new AuthenticationException("Unauthorized"); } throw new Exception(string.Format("Request Error Message: {0}. Content: {1}.", response.ErrorMessage, response.Content)); } Item spotifyArtist = null; if (response.Data != null && response.Data.artists != null && response.Data.artists.items != null) { spotifyArtist = response.Data.artists.items.FirstOrDefault(x => x.name.Equals(query, StringComparison.OrdinalIgnoreCase)); } if (spotifyArtist == null) { return new OperationResult>(); } data = new ArtistSearchResult { ArtistName = spotifyArtist.name, ArtistType = spotifyArtist.type, SpotifyId = spotifyArtist.id }; if (spotifyArtist.images != null) { data.ImageUrls = spotifyArtist.images.OrderByDescending(x => x.height).Take(1).Select(x => x.url).ToList(); } if (spotifyArtist.genres != null) { data.ArtistGenres = spotifyArtist.genres.Select(x => x).ToList(); } if (spotifyArtist.external_urls != null) { data.Urls = new string[] { spotifyArtist.external_urls.spotify }; } } catch (Exception ex) { this.Logger.Error(ex); } return new OperationResult> { IsSuccess = data != null, Data = new ArtistSearchResult[] { data } }; } private async Task AlbumsForArtist(string spotifyId) { var cacheKey = string.Format("uri:spotify:AlbumsForArtist:{0}", spotifyId); var result = this.CacheManager.Get(cacheKey); if (result == null) { var request = new RestRequest(Method.GET); var client = new RestClient(string.Format("http://api.spotify.com/v1/artists/{0}/albums?offset=0&limit=25&album_type=album&market=US", spotifyId)); var artistAlbumsResponse = await client.ExecuteTaskAsync(request); result = artistAlbumsResponse != null && artistAlbumsResponse.Data != null ? artistAlbumsResponse.Data : null; if (result != null) { this.CacheManager.Add(cacheKey, result); } } return result; } public async Task>> PerformReleaseSearch(string artistName, string query, int resultsCount) { var artistResult = await this.PerformArtistSearch(artistName, resultsCount); if (!artistResult.IsSuccess) { return new OperationResult>(); } try { var tcs = new TaskCompletionSource>>(); var request = new RestRequest(Method.GET); ReleaseSearchResult result = null; var response = await this.AlbumsForArtist(artistResult.Data.First().SpotifyId); if (response != null && response.items != null) { string foundByAlternateName = null; var spotifyAlbum = response.items.FirstOrDefault(x => x.name.Trim().Equals(query.Trim(), StringComparison.OrdinalIgnoreCase)); if (spotifyAlbum == null) { // No Exact match see if one starts with and use the first one spotifyAlbum = response.items.FirstOrDefault(x => x.name.Trim().StartsWith(query.Trim(), StringComparison.OrdinalIgnoreCase)); if (spotifyAlbum == null) { return new OperationResult>(); } foundByAlternateName = spotifyAlbum.name; } var client = new RestClient(string.Format("http://api.spotify.com/v1/albums/{0}", spotifyAlbum.id)); var albumResult = await client.ExecuteTaskAsync(request); if (albumResult != null && albumResult.Data != null) { var sa = albumResult.Data; var resultTags = new List(); var resultUrls = new List(); if (sa.external_ids != null) { resultTags.Add("upc:" + sa.external_ids.upc); } result = new ReleaseSearchResult { ReleaseTitle = !string.IsNullOrEmpty(foundByAlternateName) ? query.ToTitleCase(false) : sa.name, ReleaseDate = SafeParser.ToDateTime(sa.release_date), ReleaseType = sa.album_type, SpotifyId = sa.id, Tags = resultTags, AlternateNames = !string.IsNullOrEmpty(foundByAlternateName) ? new string[] { foundByAlternateName } : null }; if (sa.artists != null && sa.artists.FirstOrDefault() != null) { var saArtist = sa.artists.First(); result.Artist = new ArtistSearchResult { ArtistName = saArtist.name, ArtistType = saArtist.type, SpotifyId = saArtist.id }; } if (sa.genres != null) { result.ReleaseGenres = sa.genres.ToList(); } if (sa.external_urls != null) { resultUrls.Add(sa.external_urls.spotify); } if (sa.images != null) { result.ImageUrls = sa.images.OrderBy(x => x.width).Take(1).Select(x => x.url).ToList(); } if (sa.tracks != null) { var releaseMediaCount = sa.tracks.items.Max(x => x.disc_number ?? 0); var releaseMedias = new List(); for (short? i = 1; i <= releaseMediaCount; i++) { var releaseTracks = new List(); foreach (var saTrack in sa.tracks.items) { ArtistSearchResult trackArtist = null; if (saTrack.artists != null && saTrack.artists.FirstOrDefault() != null) { var saTrackArtist = saTrack.artists.First(); trackArtist = new ArtistSearchResult { ArtistName = saTrackArtist.name, SpotifyId = saTrackArtist.id, ArtistType = saTrackArtist.type }; } releaseTracks.Add(new TrackSearchResult { Artist = !trackArtist.SpotifyId.Equals(trackArtist.SpotifyId) ? trackArtist : null, TrackNumber = saTrack.track_number, Title = saTrack.name, SpotifyId = saTrack.id, Urls = new List { saTrack.external_urls.spotify, saTrack.preview_url }, Duration = saTrack.duration_ms, TrackType = saTrack.type }); } releaseMedias.Add(new ReleaseMediaSearchResult { ReleaseMediaNumber = i, TrackCount = (short)sa.tracks.items.Count(x => x.disc_number == i), Tracks = releaseTracks }); } result.ReleaseMedia = releaseMedias; } result.Urls = resultUrls; } } return new OperationResult> { IsSuccess = result != null, Data = new List { result } }; } catch (Exception ex) { this.Logger.Error(ex, ex.Serialize()); } return new OperationResult>(); } } }