From 9796632c9877fe9945e6187b18651903dc46aa0a Mon Sep 17 00:00:00 2001 From: Jonas Dellinger Date: Tue, 4 Sep 2018 14:46:01 +0200 Subject: [PATCH 1/5] Update README.md --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d5e8a69a..f8b931f4 100644 --- a/README.md +++ b/README.md @@ -11,20 +11,20 @@ An API for the Spotify-Client and Spotify's Web API, written in .NET > Based on simple REST principles, our Web API endpoints return metadata in JSON format about artists, albums, and tracks directly from the Spotify catalogue. > The API also provides access to user-related data such as playlists and music saved in a “Your Music” library, subject to user’s authorization. -**Spotify's *unofficial* Local API** -> Do you ever wanted to control your local Spotify Client with some sort of API? Now you can! This API gives you full control over your spotify client. -> You can get infos about the currently playing song, get its Album-Art, skip/pause and much more. It also features multiple Event-Interfaces. - ### Docs and Usage More Information, Installation-Instructions, Examples and API-Reference can be found at [github.io/SpotifyAPI-Net/](http://johnnycrazy.github.io/SpotifyAPI-NET/) ### NuGet -You can add the API to your project via [nuget-package](https://www.nuget.org/packages/SpotifyAPI-NET/): +You can add the API to your project via [nuget-package](https://www.nuget.org/packages/SpotifyAPI.Web/): ``` -Install-Package SpotifyAPI-NET +Install-Package SpotifyAPI.Web +Install-Package SpotifyAPI.Web.Auth + //or -Install-Package SpotifyAPI-Net -pre + +Install-Package SpotifyAPI.Web -pre +Install-Package SpotifyAPI.Web.Auth -pre ``` ### Example From 5e134df50024173817867979c699cc1c5c91d11d Mon Sep 17 00:00:00 2001 From: Jonas Dellinger Date: Tue, 4 Sep 2018 14:46:22 +0200 Subject: [PATCH 2/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f8b931f4..e768a974 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ SpotifyAPI-NET [![Nuget](https://badge.fury.io/nu/SpotifyAPI-NET.svg)](https://www.nuget.org/packages/SpotifyAPI-NET/) [![Gitter](https://img.shields.io/gitter/room/SpotifyAPI-NET/Lobby.svg)](https://gitter.im/SpotifyAPI-NET/Lobby) -An API for the Spotify-Client and Spotify's Web API, written in .NET +A Wrapper for Spotify's Web API, written in .NET **Spotify's Web API** ([link](https://developer.spotify.com/web-api/)) > Based on simple REST principles, our Web API endpoints return metadata in JSON format about artists, albums, and tracks directly from the Spotify catalogue. From 81fe1763f4085969031a6ce72cab6778eb56b6ac Mon Sep 17 00:00:00 2001 From: kg73 Date: Mon, 17 Sep 2018 07:44:01 -0500 Subject: [PATCH 3/5] Added position_ms parameter to ResumePlayback. position_ms sets the seek time on the track, whereas offset deals with the song order in a playlist or list of tracks. (#288) --- SpotifyAPI.Web/SpotifyWebAPI.cs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/SpotifyAPI.Web/SpotifyWebAPI.cs b/SpotifyAPI.Web/SpotifyWebAPI.cs index e77af261..657f74f2 100644 --- a/SpotifyAPI.Web/SpotifyWebAPI.cs +++ b/SpotifyAPI.Web/SpotifyWebAPI.cs @@ -2001,11 +2001,12 @@ namespace SpotifyAPI.Web /// The id of the device this command is targeting. If not supplied, the user's currently active device is the target. /// Spotify URI of the context to play. /// A JSON array of the Spotify track URIs to play. - /// Indicates from where in the context playback should start. + /// Indicates from where in the context playback should start. + /// The starting time to seek the track to /// Only available when context_uri corresponds to an album or playlist object, or when the uris parameter is used. /// public ErrorResponse ResumePlayback(string deviceId = "", string contextUri = "", List uris = null, - int? offset = null) + int? offset = null, int positionMs = 0) { JObject ob = new JObject(); if(!string.IsNullOrEmpty(contextUri)) @@ -2014,6 +2015,8 @@ namespace SpotifyAPI.Web ob.Add("uris", new JArray(uris)); if(offset != null) ob.Add("offset", new JObject { { "position", offset } }); + if (positionMs > 0) + ob.Add("position_ms", positionMs); return UploadData(_builder.ResumePlayback(deviceId), ob.ToString(Formatting.None), "PUT"); } @@ -2023,11 +2026,12 @@ namespace SpotifyAPI.Web /// The id of the device this command is targeting. If not supplied, the user's currently active device is the target. /// Spotify URI of the context to play. /// A JSON array of the Spotify track URIs to play. - /// Indicates from where in the context playback should start. + /// Indicates from where in the context playback should start. + /// The starting time to seek the track to /// Only available when context_uri corresponds to an album or playlist object, or when the uris parameter is used. /// public Task ResumePlaybackAsync(string deviceId = "", string contextUri = "", List uris = null, - int? offset = null) + int? offset = null, int positionMs = 0) { JObject ob = new JObject(); if (!string.IsNullOrEmpty(contextUri)) @@ -2036,6 +2040,8 @@ namespace SpotifyAPI.Web ob.Add("uris", new JArray(uris)); if (offset != null) ob.Add("offset", new JObject { { "position", offset } }); + if (positionMs > 0) + ob.Add("position_ms", positionMs); return UploadDataAsync(_builder.ResumePlayback(deviceId), ob.ToString(Formatting.None), "PUT"); } @@ -2045,11 +2051,12 @@ namespace SpotifyAPI.Web /// The id of the device this command is targeting. If not supplied, the user's currently active device is the target. /// Spotify URI of the context to play. /// A JSON array of the Spotify track URIs to play. - /// Indicates from where in the context playback should start. + /// Indicates from where in the context playback should start. + /// The starting time to seek the track to /// Only available when context_uri corresponds to an album or playlist object, or when the uris parameter is used. /// public ErrorResponse ResumePlayback(string deviceId = "", string contextUri = "", List uris = null, - string offset = "") + string offset = "", int positionMs = 0) { JObject ob = new JObject(); if (!string.IsNullOrEmpty(contextUri)) @@ -2058,6 +2065,8 @@ namespace SpotifyAPI.Web ob.Add("uris", new JArray(uris)); if (!string.IsNullOrEmpty(offset)) ob.Add("offset", new JObject {{"uri", offset}}); + if (positionMs > 0) + ob.Add("position_ms", positionMs); return UploadData(_builder.ResumePlayback(deviceId), ob.ToString(Formatting.None), "PUT"); } @@ -2067,11 +2076,12 @@ namespace SpotifyAPI.Web /// The id of the device this command is targeting. If not supplied, the user's currently active device is the target. /// Spotify URI of the context to play. /// A JSON array of the Spotify track URIs to play. - /// Indicates from where in the context playback should start. + /// Indicates from where in the context playback should start. + /// The starting time to seek the track to /// Only available when context_uri corresponds to an album or playlist object, or when the uris parameter is used. /// public Task ResumePlaybackAsync(string deviceId = "", string contextUri = "", List uris = null, - string offset = "") + string offset = "", int positionMs = 0) { JObject ob = new JObject(); if (!string.IsNullOrEmpty(contextUri)) @@ -2080,6 +2090,8 @@ namespace SpotifyAPI.Web ob.Add("uris", new JArray(uris)); if (!string.IsNullOrEmpty(offset)) ob.Add("offset", new JObject { { "uri", offset } }); + if (positionMs > 0) + ob.Add("position_ms", positionMs); return UploadDataAsync(_builder.ResumePlayback(deviceId), ob.ToString(Formatting.None), "PUT"); } From fa0f3e2d9040b2a9ca826bb9330f8b861607dead Mon Sep 17 00:00:00 2001 From: kg73 Date: Mon, 17 Sep 2018 07:44:22 -0500 Subject: [PATCH 4/5] =?UTF-8?q?Modified=20SpotifyWebClient=20to=20use=20a?= =?UTF-8?q?=20single=20HttpClient=20per=20class=20lifeti=E2=80=A6=20(#287)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Modified SpotifyWebClient to use a single HttpClient per class lifetime. This will allow the HttpClient to properly pool connections. Reference: https://docs.microsoft.com/en-us/aspnet/web-api/overview/advanced/calling-a-web-api-from-a-net-client * Added suggested changes. Moved ProxyConfig to the constructor of SpotifyWebClient. --- SpotifyAPI.Web/SpotifyWebAPI.cs | 5 +- SpotifyAPI.Web/SpotifyWebClient.cs | 123 +++++++++++++---------------- 2 files changed, 58 insertions(+), 70 deletions(-) diff --git a/SpotifyAPI.Web/SpotifyWebAPI.cs b/SpotifyAPI.Web/SpotifyWebAPI.cs index 657f74f2..2e628dc2 100644 --- a/SpotifyAPI.Web/SpotifyWebAPI.cs +++ b/SpotifyAPI.Web/SpotifyWebAPI.cs @@ -23,15 +23,14 @@ namespace SpotifyAPI.Web { _builder = new SpotifyWebBuilder(); UseAuth = true; - WebClient = new SpotifyWebClient + WebClient = new SpotifyWebClient(proxyConfig) { JsonSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, TypeNameHandling = TypeNameHandling.All - }, - ProxyConfig = proxyConfig + } }; } diff --git a/SpotifyAPI.Web/SpotifyWebClient.cs b/SpotifyAPI.Web/SpotifyWebClient.cs index 3789aa50..b8c5c094 100644 --- a/SpotifyAPI.Web/SpotifyWebClient.cs +++ b/SpotifyAPI.Web/SpotifyWebClient.cs @@ -13,10 +13,14 @@ namespace SpotifyAPI.Web internal class SpotifyWebClient : IClient { public JsonSerializerSettings JsonSettings { get; set; } - - public ProxyConfig ProxyConfig { get; set; } - private readonly Encoding _encoding = Encoding.UTF8; + private readonly HttpClient _client; + + public SpotifyWebClient(ProxyConfig proxyConfig = null) + { + HttpClientHandler clientHandler = CreateClientHandler(proxyConfig); + _client = new HttpClient(clientHandler); + } public Tuple Download(string url, Dictionary headers = null) { @@ -32,47 +36,39 @@ namespace SpotifyAPI.Web public Tuple DownloadRaw(string url, Dictionary headers = null) { - HttpClientHandler clientHandler = CreateClientHandler(ProxyConfig); - using (HttpClient client = new HttpClient(clientHandler)) + if (headers != null) { - if (headers != null) + foreach (KeyValuePair headerPair in headers) { - foreach (KeyValuePair headerPair in headers) - { - client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); - } + _client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); } - using (HttpResponseMessage response = Task.Run(() => client.GetAsync(url)).Result) + } + using (HttpResponseMessage response = Task.Run(() => _client.GetAsync(url)).Result) + { + return new Tuple(new ResponseInfo { - return new Tuple(new ResponseInfo - { - StatusCode = response.StatusCode, - Headers = ConvertHeaders(response.Headers) - }, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result); - } + StatusCode = response.StatusCode, + Headers = ConvertHeaders(response.Headers) + }, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result); } } public async Task> DownloadRawAsync(string url, Dictionary headers = null) { - HttpClientHandler clientHandler = CreateClientHandler(ProxyConfig); - using (HttpClient client = new HttpClient(clientHandler)) + if (headers != null) { - if (headers != null) + foreach (KeyValuePair headerPair in headers) { - foreach (KeyValuePair headerPair in headers) - { - client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); - } + _client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); } - using (HttpResponseMessage response = await client.GetAsync(url).ConfigureAwait(false)) + } + using (HttpResponseMessage response = await _client.GetAsync(url).ConfigureAwait(false)) + { + return new Tuple(new ResponseInfo { - return new Tuple(new ResponseInfo - { - StatusCode = response.StatusCode, - Headers = ConvertHeaders(response.Headers) - }, await response.Content.ReadAsByteArrayAsync()); - } + StatusCode = response.StatusCode, + Headers = ConvertHeaders(response.Headers) + }, await response.Content.ReadAsByteArrayAsync()); } } @@ -102,57 +98,49 @@ namespace SpotifyAPI.Web public Tuple UploadRaw(string url, string body, string method, Dictionary headers = null) { - HttpClientHandler clientHandler = CreateClientHandler(ProxyConfig); - using (HttpClient client = new HttpClient(clientHandler)) + if (headers != null) { - if (headers != null) + foreach (KeyValuePair headerPair in headers) { - foreach (KeyValuePair headerPair in headers) - { - client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); - } + _client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); } + } - HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(method), url) + HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(method), url) + { + Content = new StringContent(body, _encoding) + }; + using (HttpResponseMessage response = Task.Run(() => _client.SendAsync(message)).Result) + { + return new Tuple(new ResponseInfo { - Content = new StringContent(body, _encoding) - }; - using (HttpResponseMessage response = Task.Run(() => client.SendAsync(message)).Result) - { - return new Tuple(new ResponseInfo - { - StatusCode = response.StatusCode, - Headers = ConvertHeaders(response.Headers) - }, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result); - } + StatusCode = response.StatusCode, + Headers = ConvertHeaders(response.Headers) + }, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result); } } public async Task> UploadRawAsync(string url, string body, string method, Dictionary headers = null) { - HttpClientHandler clientHandler = CreateClientHandler(ProxyConfig); - using (HttpClient client = new HttpClient(clientHandler)) + if (headers != null) { - if (headers != null) + foreach (KeyValuePair headerPair in headers) { - foreach (KeyValuePair headerPair in headers) - { - client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); - } + _client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); } + } - HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(method), url) + HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(method), url) + { + Content = new StringContent(body, _encoding) + }; + using (HttpResponseMessage response = await _client.SendAsync(message)) + { + return new Tuple(new ResponseInfo { - Content = new StringContent(body, _encoding) - }; - using (HttpResponseMessage response = await client.SendAsync(message)) - { - return new Tuple(new ResponseInfo - { - StatusCode = response.StatusCode, - Headers = ConvertHeaders(response.Headers) - }, await response.Content.ReadAsByteArrayAsync()); - } + StatusCode = response.StatusCode, + Headers = ConvertHeaders(response.Headers) + }, await response.Content.ReadAsByteArrayAsync()); } } @@ -170,6 +158,7 @@ namespace SpotifyAPI.Web public void Dispose() { + _client.Dispose(); GC.SuppressFinalize(this); } From 67cb3e42f1ee7c1eee7949c275fafbb2615794c6 Mon Sep 17 00:00:00 2001 From: Justin Swanson Date: Fri, 21 Sep 2018 07:42:52 -0500 Subject: [PATCH 5/5] TooManyRequests Handling (#282) * SpotifyHttpStatusCode A small supplementary enum for Spotify specific codes. Only contains TooManyRequests currently, but more can be added. * TooManyRequests Retry Handling Will now parse and wait the Spotify recommended wait time. Also has the option to not consume a retry attempt on this type of error * TryGetTooManyRequests refactored to GetTooManyRequests To keep the library consistent * Removed SpotifyHttpStatusCode --- SpotifyAPI.Web/Models/BasicModel.cs | 1 + SpotifyAPI.Web/Models/ResponseInfo.cs | 3 +- SpotifyAPI.Web/SpotifyWebAPI.cs | 48 ++++++++++++++++++++++++--- SpotifyAPI.Web/SpotifyWebClient.cs | 3 +- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/SpotifyAPI.Web/Models/BasicModel.cs b/SpotifyAPI.Web/Models/BasicModel.cs index d6f54697..04727628 100644 --- a/SpotifyAPI.Web/Models/BasicModel.cs +++ b/SpotifyAPI.Web/Models/BasicModel.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json; +using SpotifyAPI.Web.Enums; using System; using System.Net; diff --git a/SpotifyAPI.Web/Models/ResponseInfo.cs b/SpotifyAPI.Web/Models/ResponseInfo.cs index 1a8a12c2..7a794387 100644 --- a/SpotifyAPI.Web/Models/ResponseInfo.cs +++ b/SpotifyAPI.Web/Models/ResponseInfo.cs @@ -1,4 +1,5 @@ -using System.Net; +using SpotifyAPI.Web.Enums; +using System.Net; namespace SpotifyAPI.Web.Models { diff --git a/SpotifyAPI.Web/SpotifyWebAPI.cs b/SpotifyAPI.Web/SpotifyWebAPI.cs index 2e628dc2..57f66acd 100644 --- a/SpotifyAPI.Web/SpotifyWebAPI.cs +++ b/SpotifyAPI.Web/SpotifyWebAPI.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SpotifyAPI.Web.Enums; using SpotifyAPI.Web.Models; @@ -78,6 +78,11 @@ namespace SpotifyAPI.Web /// public int RetryTimes { get; set; } = 10; + /// + /// Whether a failure of type "Too Many Requests" should use up one of the allocated retry attempts. + /// + public bool TooManyRequestsConsumesARetry { get; set; } = false; + /// /// Error codes that will trigger auto-retry if is enabled. /// @@ -2445,6 +2450,26 @@ namespace SpotifyAPI.Web return response.Item2; } + /// + /// Retrieves whether request had a "TooManyRequests" error, and get the amount Spotify recommends waiting before another request. + /// + /// Info object to analyze. + /// Seconds to wait before making another request. -1 if no error. + /// AUTH NEEDED + private int GetTooManyRequests(ResponseInfo info) + { + // 429 is "TooManyRequests" value specified in Spotify API + if (429 != (int)info.StatusCode) + { + return -1; + } + if (!int.TryParse(info.Headers.Get("Retry-After"), out var secondsToWait)) + { + return -1; + } + return secondsToWait; + } + public async Task DownloadDataAsync(string url) where T : BasicModel { int triesLeft = RetryTimes + 1; @@ -2453,15 +2478,30 @@ namespace SpotifyAPI.Web Tuple response = null; do { - if (response != null) { await Task.Delay(RetryAfter).ConfigureAwait(false); } + if (response != null) + { + int msToWait = RetryAfter; + var secondsToWait = GetTooManyRequests(response.Item1); + if (secondsToWait > 0) + { + msToWait = secondsToWait * 1000; + } + await Task.Delay(msToWait).ConfigureAwait(false); + } response = await DownloadDataAltAsync(url).ConfigureAwait(false); response.Item2.AddResponseInfo(response.Item1); lastError = response.Item2.Error; - triesLeft -= 1; + if (TooManyRequestsConsumesARetry || GetTooManyRequests(response.Item1) == -1) + { + triesLeft -= 1; + } - } while (UseAutoRetry && triesLeft > 0 && lastError != null && RetryErrorCodes.Contains(lastError.Status)); + } while (UseAutoRetry + && triesLeft > 0 + && (GetTooManyRequests(response.Item1) != -1 + || (lastError != null && RetryErrorCodes.Contains(lastError.Status)))); return response.Item2; diff --git a/SpotifyAPI.Web/SpotifyWebClient.cs b/SpotifyAPI.Web/SpotifyWebClient.cs index b8c5c094..7c3a572b 100644 --- a/SpotifyAPI.Web/SpotifyWebClient.cs +++ b/SpotifyAPI.Web/SpotifyWebClient.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Net; @@ -7,6 +7,7 @@ using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; using SpotifyAPI.Web.Models; +using SpotifyAPI.Web.Enums; namespace SpotifyAPI.Web {