VSCode Formatter - Also bumped tests to net core 3.1

This commit is contained in:
Jonas Dellinger 2020-03-09 20:47:39 +01:00
parent 307d69945e
commit 466e61523d
74 changed files with 2626 additions and 2642 deletions

View File

@ -1,30 +1,30 @@
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace SpotifyAPI.Web.Auth namespace SpotifyAPI.Web.Auth
{ {
internal static class AuthUtil internal static class AuthUtil
{
public static void OpenBrowser(string url)
{ {
public static void OpenBrowser(string url)
{
#if NETSTANDARD2_0 #if NETSTANDARD2_0
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{ {
url = url.Replace("&", "^&"); url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")); Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
} }
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{ {
Process.Start("xdg-open", url); Process.Start("xdg-open", url);
} }
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{ {
Process.Start("open", url); Process.Start("open", url);
} }
#else #else
url = url.Replace("&", "^&"); url = url.Replace("&", "^&");
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}")); Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
#endif #endif
}
} }
}
} }

View File

@ -12,125 +12,121 @@ using Unosquare.Labs.EmbedIO.Modules;
namespace SpotifyAPI.Web.Auth namespace SpotifyAPI.Web.Auth
{ {
public class AuthorizationCodeAuth : SpotifyAuthServer<AuthorizationCode> public class AuthorizationCodeAuth : SpotifyAuthServer<AuthorizationCode>
{
public string SecretId { get; set; }
public ProxyConfig ProxyConfig { get; set; }
public AuthorizationCodeAuth(string redirectUri, string serverUri, Scope scope = Scope.None, string state = "") : base("code", "AuthorizationCodeAuth", redirectUri, serverUri, scope, state)
{ }
public AuthorizationCodeAuth(string clientId, string secretId, string redirectUri, string serverUri, Scope scope = Scope.None, string state = "") : this(redirectUri, serverUri, scope, state)
{ {
public string SecretId { get; set; } ClientId = clientId;
SecretId = secretId;
public ProxyConfig ProxyConfig { get; set; }
public AuthorizationCodeAuth(string redirectUri, string serverUri, Scope scope = Scope.None, string state = "")
: base("code", "AuthorizationCodeAuth", redirectUri, serverUri, scope, state)
{
}
public AuthorizationCodeAuth(string clientId, string secretId, string redirectUri, string serverUri, Scope scope = Scope.None, string state = "")
: this(redirectUri, serverUri, scope, state)
{
ClientId = clientId;
SecretId = secretId;
}
private bool ShouldRegisterNewApp()
{
return string.IsNullOrEmpty(SecretId) || string.IsNullOrEmpty(ClientId);
}
public override string GetUri()
{
return ShouldRegisterNewApp() ? $"{RedirectUri}/start.html#{State}" : base.GetUri();
}
protected override void AdaptWebServer(WebServer webServer)
{
webServer.Module<WebApiModule>().RegisterController<AuthorizationCodeAuthController>();
}
private string GetAuthHeader() => $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(ClientId + ":" + SecretId))}";
public async Task<Token> RefreshToken(string refreshToken)
{
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("refresh_token", refreshToken)
};
return await GetToken(args);
}
public async Task<Token> ExchangeCode(string code)
{
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", RedirectUri)
};
return await GetToken(args);
}
private async Task<Token> GetToken(IEnumerable<KeyValuePair<string, string>> args)
{
HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Authorization", GetAuthHeader());
HttpContent content = new FormUrlEncodedContent(args);
HttpResponseMessage resp = await client.PostAsync("https://accounts.spotify.com/api/token", content);
string msg = await resp.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Token>(msg);
}
} }
public class AuthorizationCode private bool ShouldRegisterNewApp()
{ {
public string Code { get; set; } return string.IsNullOrEmpty(SecretId) || string.IsNullOrEmpty(ClientId);
public string Error { get; set; }
} }
internal class AuthorizationCodeAuthController : WebApiController public override string GetUri()
{ {
[WebApiHandler(HttpVerbs.Get, "/")] return ShouldRegisterNewApp() ? $"{RedirectUri}/start.html#{State}" : base.GetUri();
public Task<bool> GetEmpty()
{
string state = Request.QueryString["state"];
AuthorizationCodeAuth.Instances.TryGetValue(state, out SpotifyAuthServer<AuthorizationCode> auth);
string code = null;
string error = Request.QueryString["error"];
if (error == null)
code = Request.QueryString["code"];
Task.Factory.StartNew(() => auth?.TriggerAuth(new AuthorizationCode
{
Code = code,
Error = error
}));
return HttpContext.HtmlResponseAsync("<html><script type=\"text/javascript\">window.close();</script>OK - This window can be closed now</html>");
}
[WebApiHandler(HttpVerbs.Post, "/")]
public async Task<bool> PostValues()
{
Dictionary<string, object> formParams = await HttpContext.RequestFormDataDictionaryAsync();
string state = (string) formParams["state"];
AuthorizationCodeAuth.Instances.TryGetValue(state, out SpotifyAuthServer<AuthorizationCode> authServer);
AuthorizationCodeAuth auth = (AuthorizationCodeAuth) authServer;
auth.ClientId = (string) formParams["clientId"];
auth.SecretId = (string) formParams["secretId"];
string uri = auth.GetUri();
return HttpContext.Redirect(uri, false);
}
public AuthorizationCodeAuthController(IHttpContext context) : base(context)
{
}
} }
protected override void AdaptWebServer(WebServer webServer)
{
webServer.Module<WebApiModule>().RegisterController<AuthorizationCodeAuthController>();
}
private string GetAuthHeader() => $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(ClientId + ":" + SecretId))}";
public async Task<Token> RefreshToken(string refreshToken)
{
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("refresh_token", refreshToken)
};
return await GetToken(args);
}
public async Task<Token> ExchangeCode(string code)
{
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("redirect_uri", RedirectUri)
};
return await GetToken(args);
}
private async Task<Token> GetToken(IEnumerable<KeyValuePair<string, string>> args)
{
HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Authorization", GetAuthHeader());
HttpContent content = new FormUrlEncodedContent(args);
HttpResponseMessage resp = await client.PostAsync("https://accounts.spotify.com/api/token", content);
string msg = await resp.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Token>(msg);
}
}
public class AuthorizationCode
{
public string Code { get; set; }
public string Error { get; set; }
}
internal class AuthorizationCodeAuthController : WebApiController
{
[WebApiHandler(HttpVerbs.Get, "/")]
public Task<bool> GetEmpty()
{
string state = Request.QueryString["state"];
AuthorizationCodeAuth.Instances.TryGetValue(state, out SpotifyAuthServer<AuthorizationCode> auth);
string code = null;
string error = Request.QueryString["error"];
if (error == null)
code = Request.QueryString["code"];
Task.Factory.StartNew(() => auth?.TriggerAuth(new AuthorizationCode
{
Code = code,
Error = error
}));
return HttpContext.HtmlResponseAsync("<html><script type=\"text/javascript\">window.close();</script>OK - This window can be closed now</html>");
}
[WebApiHandler(HttpVerbs.Post, "/")]
public async Task<bool> PostValues()
{
Dictionary<string, object> formParams = await HttpContext.RequestFormDataDictionaryAsync();
string state = (string) formParams["state"];
AuthorizationCodeAuth.Instances.TryGetValue(state, out SpotifyAuthServer<AuthorizationCode> authServer);
AuthorizationCodeAuth auth = (AuthorizationCodeAuth) authServer;
auth.ClientId = (string) formParams["clientId"];
auth.SecretId = (string) formParams["secretId"];
string uri = auth.GetUri();
return HttpContext.Redirect(uri, false);
}
public AuthorizationCodeAuthController(IHttpContext context) : base(context)
{ }
}
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@ -8,38 +8,37 @@ using SpotifyAPI.Web.Models;
namespace SpotifyAPI.Web.Auth namespace SpotifyAPI.Web.Auth
{ {
public class CredentialsAuth public class CredentialsAuth
{
public string ClientSecret { get; set; }
public string ClientId { get; set; }
public ProxyConfig ProxyConfig { get; set; }
public CredentialsAuth(string clientId, string clientSecret)
{ {
public string ClientSecret { get; set; } ClientId = clientId;
ClientSecret = clientSecret;
public string ClientId { get; set; }
public ProxyConfig ProxyConfig { get; set; }
public CredentialsAuth(string clientId, string clientSecret)
{
ClientId = clientId;
ClientSecret = clientSecret;
}
public async Task<Token> GetToken()
{
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes(ClientId + ":" + ClientSecret));
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
HttpContent content = new FormUrlEncodedContent(args);
HttpResponseMessage resp = await client.PostAsync("https://accounts.spotify.com/api/token", content);
string msg = await resp.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Token>(msg);
}
} }
public async Task<Token> GetToken()
{
string auth = Convert.ToBase64String(Encoding.UTF8.GetBytes(ClientId + ":" + ClientSecret));
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
{new KeyValuePair<string, string>("grant_type", "client_credentials")
};
HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
HttpContent content = new FormUrlEncodedContent(args);
HttpResponseMessage resp = await client.PostAsync("https://accounts.spotify.com/api/token", content);
string msg = await resp.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Token>(msg);
}
}
} }

View File

@ -7,59 +7,57 @@ using Unosquare.Labs.EmbedIO.Modules;
namespace SpotifyAPI.Web.Auth namespace SpotifyAPI.Web.Auth
{ {
public class ImplicitGrantAuth : SpotifyAuthServer<Token> public class ImplicitGrantAuth : SpotifyAuthServer<Token>
{
public ImplicitGrantAuth(string clientId, string redirectUri, string serverUri, Scope scope = Scope.None, string state = "") : base("token", "ImplicitGrantAuth", redirectUri, serverUri, scope, state)
{ {
public ImplicitGrantAuth(string clientId, string redirectUri, string serverUri, Scope scope = Scope.None, string state = "") : ClientId = clientId;
base("token", "ImplicitGrantAuth", redirectUri, serverUri, scope, state)
{
ClientId = clientId;
}
protected override void AdaptWebServer(WebServer webServer)
{
webServer.Module<WebApiModule>().RegisterController<ImplicitGrantAuthController>();
}
} }
public class ImplicitGrantAuthController : WebApiController protected override void AdaptWebServer(WebServer webServer)
{ {
[WebApiHandler(HttpVerbs.Get, "/auth")] webServer.Module<WebApiModule>().RegisterController<ImplicitGrantAuthController>();
public Task<bool> GetAuth()
{
string state = Request.QueryString["state"];
SpotifyAuthServer<Token> auth = ImplicitGrantAuth.GetByState(state);
if (auth == null)
return HttpContext.StringResponseAsync(
$"Failed - Unable to find auth request with state \"{state}\" - Please retry");
Token token;
string error = Request.QueryString["error"];
if (error == null)
{
string accessToken = Request.QueryString["access_token"];
string tokenType = Request.QueryString["token_type"];
string expiresIn = Request.QueryString["expires_in"];
token = new Token
{
AccessToken = accessToken,
ExpiresIn = double.Parse(expiresIn),
TokenType = tokenType
};
}
else
{
token = new Token
{
Error = error
};
}
Task.Factory.StartNew(() => auth.TriggerAuth(token));
return HttpContext.HtmlResponseAsync("<html><script type=\"text/javascript\">window.close();</script>OK - This window can be closed now</html>");
}
public ImplicitGrantAuthController(IHttpContext context) : base(context)
{
}
} }
}
public class ImplicitGrantAuthController : WebApiController
{
[WebApiHandler(HttpVerbs.Get, "/auth")]
public Task<bool> GetAuth()
{
string state = Request.QueryString["state"];
SpotifyAuthServer<Token> auth = ImplicitGrantAuth.GetByState(state);
if (auth == null)
return HttpContext.StringResponseAsync(
$"Failed - Unable to find auth request with state \"{state}\" - Please retry");
Token token;
string error = Request.QueryString["error"];
if (error == null)
{
string accessToken = Request.QueryString["access_token"];
string tokenType = Request.QueryString["token_type"];
string expiresIn = Request.QueryString["expires_in"];
token = new Token
{
AccessToken = accessToken,
ExpiresIn = double.Parse(expiresIn),
TokenType = tokenType
};
}
else
{
token = new Token
{
Error = error
};
}
Task.Factory.StartNew(() => auth.TriggerAuth(token));
return HttpContext.HtmlResponseAsync("<html><script type=\"text/javascript\">window.close();</script>OK - This window can be closed now</html>");
}
public ImplicitGrantAuthController(IHttpContext context) : base(context)
{ }
}
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -10,84 +10,84 @@ using Unosquare.Labs.EmbedIO.Modules;
namespace SpotifyAPI.Web.Auth namespace SpotifyAPI.Web.Auth
{ {
public abstract class SpotifyAuthServer<T> public abstract class SpotifyAuthServer<T>
{
public string ClientId { get; set; }
public string ServerUri { get; set; }
public string RedirectUri { get; set; }
public string State { get; set; }
public Scope Scope { get; set; }
public bool ShowDialog { get; set; }
private readonly string _folder;
private readonly string _type;
private WebServer _server;
protected CancellationTokenSource _serverSource;
public delegate void OnAuthReceived(object sender, T payload);
public event OnAuthReceived AuthReceived;
internal static readonly Dictionary<string, SpotifyAuthServer<T>> Instances = new Dictionary<string, SpotifyAuthServer<T>>();
internal SpotifyAuthServer(string type, string folder, string redirectUri, string serverUri, Scope scope = Scope.None, string state = "")
{ {
public string ClientId { get; set; } _type = type;
public string ServerUri { get; set; } _folder = folder;
public string RedirectUri { get; set; } ServerUri = serverUri;
public string State { get; set; } RedirectUri = redirectUri;
public Scope Scope { get; set; } Scope = scope;
public bool ShowDialog { get; set; } State = string.IsNullOrEmpty(state) ? string.Join("", Guid.NewGuid().ToString("n").Take(8)) : state;
private readonly string _folder;
private readonly string _type;
private WebServer _server;
protected CancellationTokenSource _serverSource;
public delegate void OnAuthReceived(object sender, T payload);
public event OnAuthReceived AuthReceived;
internal static readonly Dictionary<string, SpotifyAuthServer<T>> Instances = new Dictionary<string, SpotifyAuthServer<T>>();
internal SpotifyAuthServer(string type, string folder, string redirectUri, string serverUri, Scope scope = Scope.None, string state = "")
{
_type = type;
_folder = folder;
ServerUri = serverUri;
RedirectUri = redirectUri;
Scope = scope;
State = string.IsNullOrEmpty(state) ? string.Join("", Guid.NewGuid().ToString("n").Take(8)) : state;
}
public void Start()
{
Instances.Add(State, this);
_serverSource = new CancellationTokenSource();
_server = WebServer.Create(ServerUri);
_server.RegisterModule(new WebApiModule());
AdaptWebServer(_server);
_server.RegisterModule(new ResourceFilesModule(Assembly.GetExecutingAssembly(), $"SpotifyAPI.Web.Auth.Resources.{_folder}"));
#pragma warning disable 4014
_server.RunAsync(_serverSource.Token);
#pragma warning restore 4014
}
public virtual string GetUri()
{
StringBuilder builder = new StringBuilder("https://accounts.spotify.com/authorize/?");
builder.Append("client_id=" + ClientId);
builder.Append($"&response_type={_type}");
builder.Append("&redirect_uri=" + RedirectUri);
builder.Append("&state=" + State);
builder.Append("&scope=" + Scope.GetStringAttribute(" "));
builder.Append("&show_dialog=" + ShowDialog);
return Uri.EscapeUriString(builder.ToString());
}
public void Stop(int delay = 2000)
{
if (_serverSource == null) return;
_serverSource.CancelAfter(delay);
Instances.Remove(State);
}
public void OpenBrowser()
{
string uri = GetUri();
AuthUtil.OpenBrowser(uri);
}
internal void TriggerAuth(T payload)
{
AuthReceived?.Invoke(this, payload);
}
internal static SpotifyAuthServer<T> GetByState(string state)
{
return Instances.TryGetValue(state, out SpotifyAuthServer<T> auth) ? auth : null;
}
protected abstract void AdaptWebServer(WebServer webServer);
} }
public void Start()
{
Instances.Add(State, this);
_serverSource = new CancellationTokenSource();
_server = WebServer.Create(ServerUri);
_server.RegisterModule(new WebApiModule());
AdaptWebServer(_server);
_server.RegisterModule(new ResourceFilesModule(Assembly.GetExecutingAssembly(), $"SpotifyAPI.Web.Auth.Resources.{_folder}"));
#pragma warning disable 4014
_server.RunAsync(_serverSource.Token);
#pragma warning restore 4014
}
public virtual string GetUri()
{
StringBuilder builder = new StringBuilder("https://accounts.spotify.com/authorize/?");
builder.Append("client_id=" + ClientId);
builder.Append($"&response_type={_type}");
builder.Append("&redirect_uri=" + RedirectUri);
builder.Append("&state=" + State);
builder.Append("&scope=" + Scope.GetStringAttribute(" "));
builder.Append("&show_dialog=" + ShowDialog);
return Uri.EscapeUriString(builder.ToString());
}
public void Stop(int delay = 2000)
{
if (_serverSource == null) return;
_serverSource.CancelAfter(delay);
Instances.Remove(State);
}
public void OpenBrowser()
{
string uri = GetUri();
AuthUtil.OpenBrowser(uri);
}
internal void TriggerAuth(T payload)
{
AuthReceived?.Invoke(this, payload);
}
internal static SpotifyAuthServer<T> GetByState(string state)
{
return Instances.TryGetValue(state, out SpotifyAuthServer<T> auth) ? auth : null;
}
protected abstract void AdaptWebServer(WebServer webServer);
}
} }

View File

@ -1,221 +1,218 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using SpotifyAPI.Web.Enums; using SpotifyAPI.Web.Enums;
using SpotifyAPI.Web.Models;
using Unosquare.Labs.EmbedIO; using Unosquare.Labs.EmbedIO;
using Unosquare.Labs.EmbedIO.Constants; using Unosquare.Labs.EmbedIO.Constants;
using Unosquare.Labs.EmbedIO.Modules; using Unosquare.Labs.EmbedIO.Modules;
using SpotifyAPI.Web.Models;
using Newtonsoft.Json;
using System.Net.Http;
namespace SpotifyAPI.Web.Auth namespace SpotifyAPI.Web.Auth
{ {
/// <summary>
/// <para>
/// A version of <see cref="AuthorizationCodeAuth"/> that does not store your client secret, client ID or redirect URI, enforcing a secure authorization flow. Requires an exchange server that will return the authorization code to its callback server via GET request.
/// </para>
/// <para>
/// It's recommended that you use <see cref="TokenSwapWebAPIFactory"/> if you would like to use the TokenSwap method.
/// </para>
/// </summary>
public class TokenSwapAuth : SpotifyAuthServer<AuthorizationCode>
{
readonly string _exchangeServerUri;
/// <summary> /// <summary>
/// The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival.
/// </summary>
public string HtmlResponse { get; set; } = "<script>window.close();</script>";
/// <summary>
/// If true, will time how long it takes for access to expire. On expiry, the <see cref="OnAccessTokenExpired"/> event fires.
/// </summary>
public bool TimeAccessExpiry { get; set; }
public ProxyConfig ProxyConfig { get; set; }
/// <param name="exchangeServerUri">The URI to an exchange server that will perform the key exchange.</param>
/// <param name="serverUri">The URI to host the server at that your exchange server should return the authorization code to by GET request. (e.g. http://localhost:4002)</param>
/// <param name="scope"></param>
/// <param name="state">Stating none will randomly generate a state parameter.</param>
/// <param name="htmlResponse">The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival.</param>
public TokenSwapAuth(string exchangeServerUri, string serverUri, Scope scope = Scope.None, string state = "",
string htmlResponse = "") : base("code", "", "", serverUri, scope, state)
{
if (!string.IsNullOrEmpty(htmlResponse))
{
HtmlResponse = htmlResponse;
}
_exchangeServerUri = exchangeServerUri;
}
protected override void AdaptWebServer(WebServer webServer)
{
webServer.Module<WebApiModule>().RegisterController<TokenSwapAuthController>();
}
public override string GetUri()
{
StringBuilder builder = new StringBuilder(_exchangeServerUri);
builder.Append("?");
builder.Append("response_type=code");
builder.Append("&state=" + State);
builder.Append("&scope=" + Scope.GetStringAttribute(" "));
builder.Append("&show_dialog=" + ShowDialog);
return Uri.EscapeUriString(builder.ToString());
}
/// <summary>
/// The maximum amount of times to retry getting a token.
/// <para/>
/// A token get is attempted every time you <see cref="RefreshAuthAsync(string)"/> and <see cref="ExchangeCodeAsync(string)"/>.
/// </summary>
public int MaxGetTokenRetries { get; set; } = 10;
/// <summary>
/// Creates a HTTP request to obtain a token object.<para/>
/// Parameter grantType can only be "refresh_token" or "authorization_code". authorizationCode and refreshToken are not mandatory, but at least one must be provided for your desired grant_type request otherwise an invalid response will be given and an exception is likely to be thrown.
/// <para> /// <para>
/// A version of <see cref="AuthorizationCodeAuth"/> that does not store your client secret, client ID or redirect URI, enforcing a secure authorization flow. Requires an exchange server that will return the authorization code to its callback server via GET request. /// Will re-attempt on error, on null or on no access token <see cref="MaxGetTokenRetries"/> times before finally returning null.
/// </para>
/// <para>
/// It's recommended that you use <see cref="TokenSwapWebAPIFactory"/> if you would like to use the TokenSwap method.
/// </para> /// </para>
/// </summary> /// </summary>
public class TokenSwapAuth : SpotifyAuthServer<AuthorizationCode> /// <param name="grantType">Can only be "refresh_token" or "authorization_code".</param>
/// <param name="authorizationCode">This needs to be defined if "grantType" is "authorization_code".</param>
/// <param name="refreshToken">This needs to be defined if "grantType" is "refresh_token".</param>
/// <param name="currentRetries">Does not need to be defined. Used internally for retry attempt recursion.</param>
/// <returns>Attempts to return a full <see cref="Token"/>, but after retry attempts, may return a <see cref="Token"/> with no <see cref="Token.AccessToken"/>, or null.</returns>
async Task<Token> GetToken(string grantType, string authorizationCode = "", string refreshToken = "",
int currentRetries = 0)
{ {
readonly string _exchangeServerUri; FormUrlEncodedContent content = new FormUrlEncodedContent(new Dictionary<string, string>
{ { "grant_type", grantType },
{ "code", authorizationCode },
{ "refresh_token", refreshToken }
});
/// <summary> try
/// The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival. {
/// </summary> HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
public string HtmlResponse { get; set; } = "<script>window.close();</script>"; HttpClient client = new HttpClient(handler);
HttpResponseMessage siteResponse = await client.PostAsync(_exchangeServerUri, content);
/// <summary> Token token = JsonConvert.DeserializeObject<Token>(await siteResponse.Content.ReadAsStringAsync());
/// If true, will time how long it takes for access to expire. On expiry, the <see cref="OnAccessTokenExpired"/> event fires. // Don't need to check if it was null - if it is, it will resort to the catch block.
/// </summary> if (!token.HasError() && !string.IsNullOrEmpty(token.AccessToken))
public bool TimeAccessExpiry { get; set; }
public ProxyConfig ProxyConfig { get; set; }
/// <param name="exchangeServerUri">The URI to an exchange server that will perform the key exchange.</param>
/// <param name="serverUri">The URI to host the server at that your exchange server should return the authorization code to by GET request. (e.g. http://localhost:4002)</param>
/// <param name="scope"></param>
/// <param name="state">Stating none will randomly generate a state parameter.</param>
/// <param name="htmlResponse">The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival.</param>
public TokenSwapAuth(string exchangeServerUri, string serverUri, Scope scope = Scope.None, string state = "",
string htmlResponse = "") : base("code", "", "", serverUri, scope, state)
{ {
if (!string.IsNullOrEmpty(htmlResponse)) return token;
{
HtmlResponse = htmlResponse;
}
_exchangeServerUri = exchangeServerUri;
} }
}
catch
{ }
protected override void AdaptWebServer(WebServer webServer) if (currentRetries >= MaxGetTokenRetries)
{ {
webServer.Module<WebApiModule>().RegisterController<TokenSwapAuthController>(); return null;
} }
else
public override string GetUri() {
{ currentRetries++;
StringBuilder builder = new StringBuilder(_exchangeServerUri); // The reason I chose to implement the retries system this way is because a static or instance
builder.Append("?"); // variable keeping track would inhibit parallelism i.e. using this function on multiple threads/tasks.
builder.Append("response_type=code"); // It's not clear why someone would like to do that, but it's better to cater for all kinds of uses.
builder.Append("&state=" + State); return await GetToken(grantType, authorizationCode, refreshToken, currentRetries);
builder.Append("&scope=" + Scope.GetStringAttribute(" ")); }
builder.Append("&show_dialog=" + ShowDialog);
return Uri.EscapeUriString(builder.ToString());
}
/// <summary>
/// The maximum amount of times to retry getting a token.
/// <para/>
/// A token get is attempted every time you <see cref="RefreshAuthAsync(string)"/> and <see cref="ExchangeCodeAsync(string)"/>.
/// </summary>
public int MaxGetTokenRetries { get; set; } = 10;
/// <summary>
/// Creates a HTTP request to obtain a token object.<para/>
/// Parameter grantType can only be "refresh_token" or "authorization_code". authorizationCode and refreshToken are not mandatory, but at least one must be provided for your desired grant_type request otherwise an invalid response will be given and an exception is likely to be thrown.
/// <para>
/// Will re-attempt on error, on null or on no access token <see cref="MaxGetTokenRetries"/> times before finally returning null.
/// </para>
/// </summary>
/// <param name="grantType">Can only be "refresh_token" or "authorization_code".</param>
/// <param name="authorizationCode">This needs to be defined if "grantType" is "authorization_code".</param>
/// <param name="refreshToken">This needs to be defined if "grantType" is "refresh_token".</param>
/// <param name="currentRetries">Does not need to be defined. Used internally for retry attempt recursion.</param>
/// <returns>Attempts to return a full <see cref="Token"/>, but after retry attempts, may return a <see cref="Token"/> with no <see cref="Token.AccessToken"/>, or null.</returns>
async Task<Token> GetToken(string grantType, string authorizationCode = "", string refreshToken = "",
int currentRetries = 0)
{
FormUrlEncodedContent content = new FormUrlEncodedContent(new Dictionary<string, string>
{
{"grant_type", grantType},
{"code", authorizationCode},
{"refresh_token", refreshToken}
});
try
{
HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
HttpResponseMessage siteResponse = await client.PostAsync(_exchangeServerUri, content);
Token token = JsonConvert.DeserializeObject<Token>(await siteResponse.Content.ReadAsStringAsync());
// Don't need to check if it was null - if it is, it will resort to the catch block.
if (!token.HasError() && !string.IsNullOrEmpty(token.AccessToken))
{
return token;
}
}
catch
{
}
if (currentRetries >= MaxGetTokenRetries)
{
return null;
}
else
{
currentRetries++;
// The reason I chose to implement the retries system this way is because a static or instance
// variable keeping track would inhibit parallelism i.e. using this function on multiple threads/tasks.
// It's not clear why someone would like to do that, but it's better to cater for all kinds of uses.
return await GetToken(grantType, authorizationCode, refreshToken, currentRetries);
}
}
System.Timers.Timer _accessTokenExpireTimer;
/// <summary>
/// When Spotify authorization has expired. Will only trigger if <see cref="TimeAccessExpiry"/> is true.
/// </summary>
public event EventHandler OnAccessTokenExpired;
/// <summary>
/// If <see cref="TimeAccessExpiry"/> is true, sets a timer for how long access will take to expire.
/// </summary>
/// <param name="token"></param>
void SetAccessExpireTimer(Token token)
{
if (!TimeAccessExpiry) return;
if (_accessTokenExpireTimer != null)
{
_accessTokenExpireTimer.Stop();
_accessTokenExpireTimer.Dispose();
}
_accessTokenExpireTimer = new System.Timers.Timer
{
Enabled = true,
Interval = token.ExpiresIn * 1000,
AutoReset = false
};
_accessTokenExpireTimer.Elapsed += (sender, e) => OnAccessTokenExpired?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Uses the authorization code to silently (doesn't open a browser) obtain both an access token and refresh token, where the refresh token would be required for you to use <see cref="RefreshAuthAsync(string)"/>.
/// </summary>
/// <param name="authorizationCode"></param>
/// <returns></returns>
public async Task<Token> ExchangeCodeAsync(string authorizationCode)
{
Token token = await GetToken("authorization_code", authorizationCode: authorizationCode);
if (token != null && !token.HasError() && !string.IsNullOrEmpty(token.AccessToken))
{
SetAccessExpireTimer(token);
}
return token;
}
/// <summary>
/// Uses the refresh token to silently (doesn't open a browser) obtain a fresh access token, no refresh token is given however (as it does not change).
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
public async Task<Token> RefreshAuthAsync(string refreshToken)
{
Token token = await GetToken("refresh_token", refreshToken: refreshToken);
if (token != null && !token.HasError() && !string.IsNullOrEmpty(token.AccessToken))
{
SetAccessExpireTimer(token);
}
return token;
}
} }
internal class TokenSwapAuthController : WebApiController System.Timers.Timer _accessTokenExpireTimer;
/// <summary>
/// When Spotify authorization has expired. Will only trigger if <see cref="TimeAccessExpiry"/> is true.
/// </summary>
public event EventHandler OnAccessTokenExpired;
/// <summary>
/// If <see cref="TimeAccessExpiry"/> is true, sets a timer for how long access will take to expire.
/// </summary>
/// <param name="token"></param>
void SetAccessExpireTimer(Token token)
{ {
public TokenSwapAuthController(IHttpContext context) : base(context) if (!TimeAccessExpiry) return;
{
}
[WebApiHandler(HttpVerbs.Get, "/auth")] if (_accessTokenExpireTimer != null)
public Task<bool> GetAuth() {
{ _accessTokenExpireTimer.Stop();
string state = Request.QueryString["state"]; _accessTokenExpireTimer.Dispose();
SpotifyAuthServer<AuthorizationCode> auth = TokenSwapAuth.GetByState(state); }
string code = null; _accessTokenExpireTimer = new System.Timers.Timer
string error = Request.QueryString["error"]; {
if (error == null) Enabled = true,
{ Interval = token.ExpiresIn * 1000,
code = Request.QueryString["code"]; AutoReset = false
} };
_accessTokenExpireTimer.Elapsed += (sender, e) => OnAccessTokenExpired?.Invoke(this, EventArgs.Empty);
Task.Factory.StartNew(() => auth?.TriggerAuth(new AuthorizationCode
{
Code = code,
Error = error
}));
return HttpContext.HtmlResponseAsync(((TokenSwapAuth) auth).HtmlResponse);
}
} }
/// <summary>
/// Uses the authorization code to silently (doesn't open a browser) obtain both an access token and refresh token, where the refresh token would be required for you to use <see cref="RefreshAuthAsync(string)"/>.
/// </summary>
/// <param name="authorizationCode"></param>
/// <returns></returns>
public async Task<Token> ExchangeCodeAsync(string authorizationCode)
{
Token token = await GetToken("authorization_code", authorizationCode : authorizationCode);
if (token != null && !token.HasError() && !string.IsNullOrEmpty(token.AccessToken))
{
SetAccessExpireTimer(token);
}
return token;
}
/// <summary>
/// Uses the refresh token to silently (doesn't open a browser) obtain a fresh access token, no refresh token is given however (as it does not change).
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
public async Task<Token> RefreshAuthAsync(string refreshToken)
{
Token token = await GetToken("refresh_token", refreshToken : refreshToken);
if (token != null && !token.HasError() && !string.IsNullOrEmpty(token.AccessToken))
{
SetAccessExpireTimer(token);
}
return token;
}
}
internal class TokenSwapAuthController : WebApiController
{
public TokenSwapAuthController(IHttpContext context) : base(context)
{ }
[WebApiHandler(HttpVerbs.Get, "/auth")]
public Task<bool> GetAuth()
{
string state = Request.QueryString["state"];
SpotifyAuthServer<AuthorizationCode> auth = TokenSwapAuth.GetByState(state);
string code = null;
string error = Request.QueryString["error"];
if (error == null)
{
code = Request.QueryString["code"];
}
Task.Factory.StartNew(() => auth?.TriggerAuth(new AuthorizationCode
{
Code = code,
Error = error
}));
return HttpContext.HtmlResponseAsync(((TokenSwapAuth) auth).HtmlResponse);
}
}
} }

View File

@ -1,282 +1,280 @@
using SpotifyAPI.Web.Enums;
using SpotifyAPI.Web.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using SpotifyAPI.Web.Enums;
using SpotifyAPI.Web.Models;
namespace SpotifyAPI.Web.Auth namespace SpotifyAPI.Web.Auth
{ {
/// <summary>
/// Returns a <see cref="SpotifyWebAPI"/> using the TokenSwapAuth process.
/// </summary>
public class TokenSwapWebAPIFactory
{
/// <summary> /// <summary>
/// Returns a <see cref="SpotifyWebAPI"/> using the TokenSwapAuth process. /// Access provided by Spotify expires after 1 hour. If true, <see cref="TokenSwapAuth"/> will time the access tokens, and access will attempt to be silently (without opening a browser) refreshed automatically. This will not make <see cref="OnAccessTokenExpired"/> fire, see <see cref="TimeAccessExpiry"/> for that.
/// </summary> /// </summary>
public class TokenSwapWebAPIFactory public bool AutoRefresh { get; set; }
/// <summary>
/// If true when calling <see cref="GetWebApiAsync"/>, will time how long it takes for access to Spotify to expire. The event <see cref="OnAccessTokenExpired"/> fires when the timer elapses.
/// </summary>
public bool TimeAccessExpiry { get; set; }
/// <summary>
/// The maximum time in seconds to wait for a SpotifyWebAPI to be returned. The timeout is cancelled early regardless if an auth success or failure occured.
/// </summary>
public int Timeout { get; set; }
public Scope Scope { get; set; }
/// <summary>
/// The URI (or URL) of the exchange server which exchanges the auth code for access and refresh tokens.
/// </summary>
public string ExchangeServerUri { get; set; }
/// <summary>
/// The URI (or URL) of where a callback server to receive the auth code will be hosted. e.g. http://localhost:4002
/// </summary>
public string HostServerUri { get; set; }
/// <summary>
/// Opens the user's browser and visits the exchange server for you, triggering the key exchange. This should be true unless you want to handle the key exchange in a nicer way.
/// </summary>
public bool OpenBrowser { get; set; }
/// <summary>
/// The HTML to respond with when the callback server has been reached. By default, it is set to close the window on arrival.
/// </summary>
public string HtmlResponse { get; set; }
/// <summary>
/// Whether or not to show a dialog saying "Is this you?" during the initial key exchange. It should be noted that this would allow a user the opportunity to change accounts.
/// </summary>
public bool ShowDialog { get; set; }
/// <summary>
/// The maximum amount of times to retry getting a token.
/// <para/>
/// A token get is attempted every time you <see cref="GetWebApiAsync"/> and <see cref="RefreshAuthAsync"/>. Increasing this may improve how often these actions succeed - although it won't solve any underlying problems causing a get token failure.
/// </summary>
public int MaxGetTokenRetries { get; set; } = 10;
/// <summary>
/// Returns a SpotifyWebAPI using the TokenSwapAuth process.
/// </summary>
/// <param name="exchangeServerUri">The URI (or URL) of the exchange server which exchanges the auth code for access and refresh tokens.</param>
/// <param name="scope"></param>
/// <param name="hostServerUri">The URI (or URL) of where a callback server to receive the auth code will be hosted. e.g. http://localhost:4002</param>
/// <param name="timeout">The maximum time in seconds to wait for a SpotifyWebAPI to be returned. The timeout is cancelled early regardless if an auth success or failure occured.</param>
/// <param name="autoRefresh">Access provided by Spotify expires after 1 hour. If true, access will attempt to be silently (without opening a browser) refreshed automatically.</param>
/// <param name="openBrowser">Opens the user's browser and visits the exchange server for you, triggering the key exchange. This should be true unless you want to handle the key exchange in a nicer way.</param>
public TokenSwapWebAPIFactory(string exchangeServerUri, Scope scope = Scope.None, string hostServerUri = "http://localhost:4002", int timeout = 10, bool autoRefresh = false, bool openBrowser = true)
{ {
/// <summary> AutoRefresh = autoRefresh;
/// Access provided by Spotify expires after 1 hour. If true, <see cref="TokenSwapAuth"/> will time the access tokens, and access will attempt to be silently (without opening a browser) refreshed automatically. This will not make <see cref="OnAccessTokenExpired"/> fire, see <see cref="TimeAccessExpiry"/> for that. Timeout = timeout;
/// </summary> Scope = scope;
public bool AutoRefresh { get; set; } ExchangeServerUri = exchangeServerUri;
/// <summary> HostServerUri = hostServerUri;
/// If true when calling <see cref="GetWebApiAsync"/>, will time how long it takes for access to Spotify to expire. The event <see cref="OnAccessTokenExpired"/> fires when the timer elapses. OpenBrowser = openBrowser;
/// </summary>
public bool TimeAccessExpiry { get; set; } OnAccessTokenExpired += async(sender, e) =>
/// <summary> {
/// The maximum time in seconds to wait for a SpotifyWebAPI to be returned. The timeout is cancelled early regardless if an auth success or failure occured. if (AutoRefresh)
/// </summary>
public int Timeout { get; set; }
public Scope Scope { get; set; }
/// <summary>
/// The URI (or URL) of the exchange server which exchanges the auth code for access and refresh tokens.
/// </summary>
public string ExchangeServerUri { get; set; }
/// <summary>
/// The URI (or URL) of where a callback server to receive the auth code will be hosted. e.g. http://localhost:4002
/// </summary>
public string HostServerUri { get; set; }
/// <summary>
/// Opens the user's browser and visits the exchange server for you, triggering the key exchange. This should be true unless you want to handle the key exchange in a nicer way.
/// </summary>
public bool OpenBrowser { get; set; }
/// <summary>
/// The HTML to respond with when the callback server has been reached. By default, it is set to close the window on arrival.
/// </summary>
public string HtmlResponse { get; set; }
/// <summary>
/// Whether or not to show a dialog saying "Is this you?" during the initial key exchange. It should be noted that this would allow a user the opportunity to change accounts.
/// </summary>
public bool ShowDialog { get; set; }
/// <summary>
/// The maximum amount of times to retry getting a token.
/// <para/>
/// A token get is attempted every time you <see cref="GetWebApiAsync"/> and <see cref="RefreshAuthAsync"/>. Increasing this may improve how often these actions succeed - although it won't solve any underlying problems causing a get token failure.
/// </summary>
public int MaxGetTokenRetries { get; set; } = 10;
/// <summary>
/// Returns a SpotifyWebAPI using the TokenSwapAuth process.
/// </summary>
/// <param name="exchangeServerUri">The URI (or URL) of the exchange server which exchanges the auth code for access and refresh tokens.</param>
/// <param name="scope"></param>
/// <param name="hostServerUri">The URI (or URL) of where a callback server to receive the auth code will be hosted. e.g. http://localhost:4002</param>
/// <param name="timeout">The maximum time in seconds to wait for a SpotifyWebAPI to be returned. The timeout is cancelled early regardless if an auth success or failure occured.</param>
/// <param name="autoRefresh">Access provided by Spotify expires after 1 hour. If true, access will attempt to be silently (without opening a browser) refreshed automatically.</param>
/// <param name="openBrowser">Opens the user's browser and visits the exchange server for you, triggering the key exchange. This should be true unless you want to handle the key exchange in a nicer way.</param>
public TokenSwapWebAPIFactory(string exchangeServerUri, Scope scope = Scope.None, string hostServerUri = "http://localhost:4002", int timeout = 10, bool autoRefresh = false, bool openBrowser = true)
{ {
AutoRefresh = autoRefresh; await RefreshAuthAsync();
Timeout = timeout;
Scope = scope;
ExchangeServerUri = exchangeServerUri;
HostServerUri = hostServerUri;
OpenBrowser = openBrowser;
OnAccessTokenExpired += async (sender, e) =>
{
if (AutoRefresh)
{
await RefreshAuthAsync();
}
};
}
Token lastToken;
SpotifyWebAPI lastWebApi;
TokenSwapAuth lastAuth;
public class ExchangeReadyEventArgs : EventArgs
{
public string ExchangeUri { get; set; }
}
/// <summary>
/// When the URI to get an authorization code is ready to be used to be visited. Not required if <see cref="OpenBrowser"/> is true as the exchange URI will automatically be visited for you.
/// </summary>
public event EventHandler<ExchangeReadyEventArgs> OnExchangeReady;
/// <summary>
/// Refreshes the access for a SpotifyWebAPI returned by this factory.
/// </summary>
/// <returns></returns>
public async Task RefreshAuthAsync()
{
Token token = await lastAuth.RefreshAuthAsync(lastToken.RefreshToken);
if (token == null)
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs($"Token not returned by server."));
}
else if (token.HasError())
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs($"{token.Error} {token.ErrorDescription}"));
}
else if (string.IsNullOrEmpty(token.AccessToken))
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs("Token had no access token attached."));
}
else
{
lastWebApi.AccessToken = token.AccessToken;
OnAuthSuccess?.Invoke(this, new AuthSuccessEventArgs());
}
}
// By defining empty EventArgs objects, you can specify additional information later on as you see fit and it won't
// be considered a breaking change to consumers of this API.
//
// They don't even need to be constructed for their associated events to be invoked - just pass the static Empty property.
public class AccessTokenExpiredEventArgs : EventArgs
{
public static new AccessTokenExpiredEventArgs Empty { get; } = new AccessTokenExpiredEventArgs();
public AccessTokenExpiredEventArgs()
{
}
}
/// <summary>
/// When the authorization from Spotify expires. This will only occur if <see cref="AutoRefresh"/> is true.
/// </summary>
public event EventHandler<AccessTokenExpiredEventArgs> OnAccessTokenExpired;
public class AuthSuccessEventArgs : EventArgs
{
public static new AuthSuccessEventArgs Empty { get; } = new AuthSuccessEventArgs();
public AuthSuccessEventArgs()
{
}
}
/// <summary>
/// When an authorization attempt succeeds and gains authorization.
/// </summary>
public event EventHandler<AuthSuccessEventArgs> OnAuthSuccess;
public class AuthFailureEventArgs : EventArgs
{
public static new AuthFailureEventArgs Empty { get; } = new AuthFailureEventArgs("");
public string Error { get; }
public AuthFailureEventArgs(string error)
{
Error = error;
}
}
/// <summary>
/// When an authorization attempt fails to gain authorization.
/// </summary>
public event EventHandler<AuthFailureEventArgs> OnAuthFailure;
/// <summary>
/// Manually triggers the timeout for any ongoing get web API request.
/// </summary>
public void CancelGetWebApiRequest()
{
if (webApiTimeoutTimer == null) return;
// The while loop in GetWebApiSync() will react and trigger the timeout.
webApiTimeoutTimer.Stop();
webApiTimeoutTimer.Dispose();
webApiTimeoutTimer = null;
}
System.Timers.Timer webApiTimeoutTimer;
/// <summary>
/// Gets an authorized and ready to use SpotifyWebAPI by following the SecureAuthorizationCodeAuth process with its current settings.
/// </summary>
/// <returns></returns>
public async Task<SpotifyWebAPI> GetWebApiAsync()
{
return await Task<SpotifyWebAPI>.Factory.StartNew(() =>
{
bool currentlyAuthorizing = true;
// Cancel any ongoing get web API requests
CancelGetWebApiRequest();
lastAuth = new TokenSwapAuth(
exchangeServerUri: ExchangeServerUri,
serverUri: HostServerUri,
scope: Scope,
htmlResponse: HtmlResponse)
{
ShowDialog = ShowDialog,
MaxGetTokenRetries = MaxGetTokenRetries,
TimeAccessExpiry = AutoRefresh || TimeAccessExpiry
};
lastAuth.AuthReceived += async (sender, response) =>
{
if (!string.IsNullOrEmpty(response.Error) || string.IsNullOrEmpty(response.Code))
{
// We only want one auth failure to be fired, if the request timed out then don't bother.
if (!webApiTimeoutTimer.Enabled) return;
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs(response.Error));
currentlyAuthorizing = false;
return;
}
lastToken = await lastAuth.ExchangeCodeAsync(response.Code);
if (lastToken == null || lastToken.HasError() || string.IsNullOrEmpty(lastToken.AccessToken))
{
// We only want one auth failure to be fired, if the request timed out then don't bother.
if (!webApiTimeoutTimer.Enabled) return;
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs("Exchange token not returned by server."));
currentlyAuthorizing = false;
return;
}
if (lastWebApi != null)
{
lastWebApi.Dispose();
}
lastWebApi = new SpotifyWebAPI()
{
TokenType = lastToken.TokenType,
AccessToken = lastToken.AccessToken
};
lastAuth.Stop();
OnAuthSuccess?.Invoke(this, AuthSuccessEventArgs.Empty);
currentlyAuthorizing = false;
};
lastAuth.OnAccessTokenExpired += async (sender, e) =>
{
if (TimeAccessExpiry)
{
OnAccessTokenExpired?.Invoke(sender, AccessTokenExpiredEventArgs.Empty);
}
if (AutoRefresh)
{
await RefreshAuthAsync();
}
};
lastAuth.Start();
OnExchangeReady?.Invoke(this, new ExchangeReadyEventArgs { ExchangeUri = lastAuth.GetUri() });
if (OpenBrowser)
{
lastAuth.OpenBrowser();
}
webApiTimeoutTimer = new System.Timers.Timer
{
AutoReset = false,
Enabled = true,
Interval = Timeout * 1000
};
while (currentlyAuthorizing && webApiTimeoutTimer.Enabled) ;
// If a timeout occurred
if (lastWebApi == null && currentlyAuthorizing)
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs("Authorization request has timed out."));
}
return lastWebApi;
});
} }
};
} }
Token lastToken;
SpotifyWebAPI lastWebApi;
TokenSwapAuth lastAuth;
public class ExchangeReadyEventArgs : EventArgs
{
public string ExchangeUri { get; set; }
}
/// <summary>
/// When the URI to get an authorization code is ready to be used to be visited. Not required if <see cref="OpenBrowser"/> is true as the exchange URI will automatically be visited for you.
/// </summary>
public event EventHandler<ExchangeReadyEventArgs> OnExchangeReady;
/// <summary>
/// Refreshes the access for a SpotifyWebAPI returned by this factory.
/// </summary>
/// <returns></returns>
public async Task RefreshAuthAsync()
{
Token token = await lastAuth.RefreshAuthAsync(lastToken.RefreshToken);
if (token == null)
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs($"Token not returned by server."));
}
else if (token.HasError())
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs($"{token.Error} {token.ErrorDescription}"));
}
else if (string.IsNullOrEmpty(token.AccessToken))
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs("Token had no access token attached."));
}
else
{
lastWebApi.AccessToken = token.AccessToken;
OnAuthSuccess?.Invoke(this, new AuthSuccessEventArgs());
}
}
// By defining empty EventArgs objects, you can specify additional information later on as you see fit and it won't
// be considered a breaking change to consumers of this API.
//
// They don't even need to be constructed for their associated events to be invoked - just pass the static Empty property.
public class AccessTokenExpiredEventArgs : EventArgs
{
public static new AccessTokenExpiredEventArgs Empty { get; } = new AccessTokenExpiredEventArgs();
public AccessTokenExpiredEventArgs()
{ }
}
/// <summary>
/// When the authorization from Spotify expires. This will only occur if <see cref="AutoRefresh"/> is true.
/// </summary>
public event EventHandler<AccessTokenExpiredEventArgs> OnAccessTokenExpired;
public class AuthSuccessEventArgs : EventArgs
{
public static new AuthSuccessEventArgs Empty { get; } = new AuthSuccessEventArgs();
public AuthSuccessEventArgs()
{ }
}
/// <summary>
/// When an authorization attempt succeeds and gains authorization.
/// </summary>
public event EventHandler<AuthSuccessEventArgs> OnAuthSuccess;
public class AuthFailureEventArgs : EventArgs
{
public static new AuthFailureEventArgs Empty { get; } = new AuthFailureEventArgs("");
public string Error { get; }
public AuthFailureEventArgs(string error)
{
Error = error;
}
}
/// <summary>
/// When an authorization attempt fails to gain authorization.
/// </summary>
public event EventHandler<AuthFailureEventArgs> OnAuthFailure;
/// <summary>
/// Manually triggers the timeout for any ongoing get web API request.
/// </summary>
public void CancelGetWebApiRequest()
{
if (webApiTimeoutTimer == null) return;
// The while loop in GetWebApiSync() will react and trigger the timeout.
webApiTimeoutTimer.Stop();
webApiTimeoutTimer.Dispose();
webApiTimeoutTimer = null;
}
System.Timers.Timer webApiTimeoutTimer;
/// <summary>
/// Gets an authorized and ready to use SpotifyWebAPI by following the SecureAuthorizationCodeAuth process with its current settings.
/// </summary>
/// <returns></returns>
public async Task<SpotifyWebAPI> GetWebApiAsync()
{
return await Task<SpotifyWebAPI>.Factory.StartNew(() =>
{
bool currentlyAuthorizing = true;
// Cancel any ongoing get web API requests
CancelGetWebApiRequest();
lastAuth = new TokenSwapAuth(
exchangeServerUri: ExchangeServerUri,
serverUri: HostServerUri,
scope: Scope,
htmlResponse: HtmlResponse)
{
ShowDialog = ShowDialog,
MaxGetTokenRetries = MaxGetTokenRetries,
TimeAccessExpiry = AutoRefresh || TimeAccessExpiry
};
lastAuth.AuthReceived += async(sender, response) =>
{
if (!string.IsNullOrEmpty(response.Error) || string.IsNullOrEmpty(response.Code))
{
// We only want one auth failure to be fired, if the request timed out then don't bother.
if (!webApiTimeoutTimer.Enabled) return;
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs(response.Error));
currentlyAuthorizing = false;
return;
}
lastToken = await lastAuth.ExchangeCodeAsync(response.Code);
if (lastToken == null || lastToken.HasError() || string.IsNullOrEmpty(lastToken.AccessToken))
{
// We only want one auth failure to be fired, if the request timed out then don't bother.
if (!webApiTimeoutTimer.Enabled) return;
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs("Exchange token not returned by server."));
currentlyAuthorizing = false;
return;
}
if (lastWebApi != null)
{
lastWebApi.Dispose();
}
lastWebApi = new SpotifyWebAPI()
{
TokenType = lastToken.TokenType,
AccessToken = lastToken.AccessToken
};
lastAuth.Stop();
OnAuthSuccess?.Invoke(this, AuthSuccessEventArgs.Empty);
currentlyAuthorizing = false;
};
lastAuth.OnAccessTokenExpired += async(sender, e) =>
{
if (TimeAccessExpiry)
{
OnAccessTokenExpired?.Invoke(sender, AccessTokenExpiredEventArgs.Empty);
}
if (AutoRefresh)
{
await RefreshAuthAsync();
}
};
lastAuth.Start();
OnExchangeReady?.Invoke(this, new ExchangeReadyEventArgs { ExchangeUri = lastAuth.GetUri() });
if (OpenBrowser)
{
lastAuth.OpenBrowser();
}
webApiTimeoutTimer = new System.Timers.Timer
{
AutoReset = false,
Enabled = true,
Interval = Timeout * 1000
};
while (currentlyAuthorizing && webApiTimeoutTimer.Enabled);
// If a timeout occurred
if (lastWebApi == null && currentlyAuthorizing)
{
OnAuthFailure?.Invoke(this, new AuthFailureEventArgs("Authorization request has timed out."));
}
return lastWebApi;
});
}
}
} }

View File

@ -1,4 +1,4 @@
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -7,27 +7,27 @@ using SpotifyAPI.Web.Examples.ASP.Models;
namespace SpotifyAPI.Web.Examples.ASP.Controllers namespace SpotifyAPI.Web.Examples.ASP.Controllers
{ {
[Authorize(AuthenticationSchemes = "Spotify")] [Authorize(AuthenticationSchemes = "Spotify")]
public class HomeController : Controller public class HomeController : Controller
{
public async Task<IActionResult> Index()
{ {
public async Task<IActionResult> Index() var accessToken = await HttpContext.GetTokenAsync("Spotify", "access_token");
{ SpotifyWebAPI api = new SpotifyWebAPI
var accessToken = await HttpContext.GetTokenAsync("Spotify", "access_token"); {
SpotifyWebAPI api = new SpotifyWebAPI AccessToken = accessToken,
{ TokenType = "Bearer"
AccessToken = accessToken, };
TokenType = "Bearer"
};
var savedTracks = await api.GetSavedTracksAsync(50); var savedTracks = await api.GetSavedTracksAsync(50);
return View(new IndexModel { SavedTracks = savedTracks }); return View(new IndexModel { SavedTracks = savedTracks });
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
} }
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
} }

View File

@ -2,10 +2,10 @@ using System;
namespace SpotifyAPI.Web.Examples.ASP.Models namespace SpotifyAPI.Web.Examples.ASP.Models
{ {
public class ErrorViewModel public class ErrorViewModel
{ {
public string RequestId { get; set; } public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
} }
} }

View File

@ -3,8 +3,8 @@ using SpotifyAPI.Web.Models;
namespace SpotifyAPI.Web.Examples.ASP.Models namespace SpotifyAPI.Web.Examples.ASP.Models
{ {
public class IndexModel public class IndexModel
{ {
public Paging<SavedTrack> SavedTracks; public Paging<SavedTrack> SavedTracks;
} }
} }

View File

@ -9,18 +9,18 @@ using Microsoft.Extensions.Logging;
namespace SpotifyAPI.Web.Examples.ASP namespace SpotifyAPI.Web.Examples.ASP
{ {
public class Program public class Program
{
public static void Main(string[] args)
{ {
public static void Main(string[] args) CreateHostBuilder(args).Build().Run();
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
} }
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
} }

View File

@ -8,61 +8,61 @@ using SpotifyAPI.Web.Enums;
namespace SpotifyAPI.Web.Examples.ASP namespace SpotifyAPI.Web.Examples.ASP
{ {
public class Startup public class Startup
{
public Startup(IConfiguration configuration)
{ {
public Startup(IConfiguration configuration) Configuration = configuration;
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(o => o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddSpotify(options =>
{
var scopes = Scope.UserLibraryRead;
options.Scope.Add(scopes.GetStringAttribute(","));
options.SaveTokens = true;
options.ClientId = Configuration["client_id"];
options.ClientSecret = Configuration["client_secret"];
options.CallbackPath = "/callback";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
} }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddAuthentication(o => o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddSpotify(options =>
{
var scopes = Scope.UserLibraryRead | Scope.UserModifyPlaybackState;
options.Scope.Add(scopes.GetStringAttribute(","));
options.SaveTokens = true;
options.ClientId = Configuration["client_id"];
options.ClientSecret = Configuration["client_secret"];
options.CallbackPath = "/callback";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
}
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using SpotifyAPI.Web.Auth; using SpotifyAPI.Web.Auth;
using SpotifyAPI.Web.Enums; using SpotifyAPI.Web.Enums;
@ -6,72 +6,70 @@ using SpotifyAPI.Web.Models;
namespace SpotifyAPI.Web.Examples.CLI namespace SpotifyAPI.Web.Examples.CLI
{ {
internal static class Program internal static class Program
{
private static string _clientId = ""; //"";
private static string _secretId = ""; //"";
// ReSharper disable once UnusedParameter.Local
public static void Main(string[] args)
{ {
private static string _clientId = ""; //""; _clientId = string.IsNullOrEmpty(_clientId) ?
private static string _secretId = ""; //""; Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID") :
_clientId;
// ReSharper disable once UnusedParameter.Local _secretId = string.IsNullOrEmpty(_secretId) ?
public static void Main(string[] args) Environment.GetEnvironmentVariable("SPOTIFY_SECRET_ID") :
{ _secretId;
_clientId = string.IsNullOrEmpty(_clientId)
? Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID")
: _clientId;
_secretId = string.IsNullOrEmpty(_secretId) Console.WriteLine("####### Spotify API Example #######");
? Environment.GetEnvironmentVariable("SPOTIFY_SECRET_ID") Console.WriteLine("This example uses AuthorizationCodeAuth.");
: _secretId; Console.WriteLine(
"Tip: If you want to supply your ClientID and SecretId beforehand, use env variables (SPOTIFY_CLIENT_ID and SPOTIFY_SECRET_ID)");
Console.WriteLine("####### Spotify API Example #######"); var auth =
Console.WriteLine("This example uses AuthorizationCodeAuth."); new AuthorizationCodeAuth(_clientId, _secretId, "http://localhost:4002", "http://localhost:4002",
Console.WriteLine( Scope.PlaylistReadPrivate | Scope.PlaylistReadCollaborative);
"Tip: If you want to supply your ClientID and SecretId beforehand, use env variables (SPOTIFY_CLIENT_ID and SPOTIFY_SECRET_ID)"); auth.AuthReceived += AuthOnAuthReceived;
auth.Start();
auth.OpenBrowser();
var auth = Console.ReadLine();
new AuthorizationCodeAuth(_clientId, _secretId, "http://localhost:4002", "http://localhost:4002", auth.Stop(0);
Scope.PlaylistReadPrivate | Scope.PlaylistReadCollaborative);
auth.AuthReceived += AuthOnAuthReceived;
auth.Start();
auth.OpenBrowser();
Console.ReadLine();
auth.Stop(0);
}
private static async void AuthOnAuthReceived(object sender, AuthorizationCode payload)
{
var auth = (AuthorizationCodeAuth) sender;
auth.Stop();
Token token = await auth.ExchangeCode(payload.Code);
var api = new SpotifyWebAPI
{
AccessToken = token.AccessToken,
TokenType = token.TokenType
};
await PrintUsefulData(api);
}
private static async Task PrintAllPlaylistTracks(SpotifyWebAPI api, Paging<SimplePlaylist> playlists)
{
if (playlists.Items == null) return;
playlists.Items.ForEach(playlist => Console.WriteLine($"- {playlist.Name}"));
if(playlists.HasNextPage())
await PrintAllPlaylistTracks(api, await api.GetNextPageAsync(playlists));
}
private static async Task PrintUsefulData(SpotifyWebAPI api)
{
PrivateProfile profile = await api.GetPrivateProfileAsync();
string name = string.IsNullOrEmpty(profile.DisplayName) ? profile.Id : profile.DisplayName;
Console.WriteLine($"Hello there, {name}!");
Console.WriteLine("Your playlists:");
await PrintAllPlaylistTracks(api, api.GetUserPlaylists(profile.Id));
}
} }
private static async void AuthOnAuthReceived(object sender, AuthorizationCode payload)
{
var auth = (AuthorizationCodeAuth) sender;
auth.Stop();
Token token = await auth.ExchangeCode(payload.Code);
var api = new SpotifyWebAPI
{
AccessToken = token.AccessToken,
TokenType = token.TokenType
};
await PrintUsefulData(api);
}
private static async Task PrintAllPlaylistTracks(SpotifyWebAPI api, Paging<SimplePlaylist> playlists)
{
if (playlists.Items == null) return;
playlists.Items.ForEach(playlist => Console.WriteLine($"- {playlist.Name}"));
if (playlists.HasNextPage())
await PrintAllPlaylistTracks(api, await api.GetNextPageAsync(playlists));
}
private static async Task PrintUsefulData(SpotifyWebAPI api)
{
PrivateProfile profile = await api.GetPrivateProfileAsync();
string name = string.IsNullOrEmpty(profile.DisplayName) ? profile.Id : profile.DisplayName;
Console.WriteLine($"Hello there, {name}!");
Console.WriteLine("Your playlists:");
await PrintAllPlaylistTracks(api, api.GetUserPlaylists(profile.Id));
}
}
} }

View File

@ -1,116 +1,116 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
namespace SpotifyAPI.Web.Tests namespace SpotifyAPI.Web.Tests
{ {
[TestFixture] [TestFixture]
public class ProxyConfigTest public class ProxyConfigTest
{
#region GetUri
[Test]
public void GetUri_HostNameWithScheme()
{ {
#region GetUri ProxyConfig config = new ProxyConfig { Host = "https://test-host.com" };
CheckUri(config.GetUri(), "https", "test-host.com", 80);
[Test]
public void GetUri_HostNameWithScheme()
{
ProxyConfig config = new ProxyConfig { Host = "https://test-host.com" };
CheckUri(config.GetUri(), "https", "test-host.com", 80);
}
[Test]
public void GetUri_HostNameWithoutScheme()
{
ProxyConfig config = new ProxyConfig { Host = "test-host.com" };
CheckUri(config.GetUri(), "http", "test-host.com", 80);
}
[Test]
public void GetUri_HostNameWithSchemeAndPort()
{
ProxyConfig config = new ProxyConfig
{
Host = "https://test-host.com",
Port = 8080
};
CheckUri(config.GetUri(), "https", "test-host.com", 8080);
}
[Test]
public void GetUri_HostAddressWithScheme()
{
ProxyConfig config = new ProxyConfig { Host = "https://192.168.0.1" };
CheckUri(config.GetUri(), "https", "192.168.0.1", 80);
}
[Test]
public void GetUri_HostAddressWithoutScheme()
{
ProxyConfig config = new ProxyConfig { Host = "192.168.0.1" };
CheckUri(config.GetUri(), "http", "192.168.0.1", 80);
}
[Test]
public void GetUri_HostAddressWithSchemeAndPort()
{
ProxyConfig config = new ProxyConfig
{
Host = "https://192.168.0.1",
Port = 8080
};
CheckUri(config.GetUri(), "https", "192.168.0.1", 8080);
}
[Test]
public void GetUri_NullHost()
{
ProxyConfig config = new ProxyConfig { Host = null };
Assert.Throws<ArgumentNullException>(() => config.GetUri());
}
[Test]
public void GetUri_EmptyHost()
{
ProxyConfig config = new ProxyConfig { Host = string.Empty };
Assert.Throws<UriFormatException>(() => config.GetUri());
}
[Test]
public void GetUri_NegativePort()
{
ProxyConfig config = new ProxyConfig
{
Host = "test-host.com",
Port = -10
};
Assert.Throws<ArgumentOutOfRangeException>(() => config.GetUri());
}
#endregion GetUri
[Test]
public void Set_Null()
{
ProxyConfig config = new ProxyConfig
{
Host = "https://test-host.com",
Port = 1234,
Username = "admin",
Password = "password",
BypassProxyOnLocal = true
};
config.Set(null);
Assert.NotNull(config);
Assert.Null(config.Host);
Assert.AreEqual(80, config.Port);
Assert.Null(config.Username);
Assert.Null(config.Password);
Assert.False(config.BypassProxyOnLocal);
}
private static void CheckUri(Uri uri, string expectedScheme, string expectedHost, int expectedPort)
{
Assert.AreEqual(expectedScheme, uri.Scheme);
Assert.AreEqual(expectedHost, uri.Host);
Assert.AreEqual(expectedPort, uri.Port);
}
} }
[Test]
public void GetUri_HostNameWithoutScheme()
{
ProxyConfig config = new ProxyConfig { Host = "test-host.com" };
CheckUri(config.GetUri(), "http", "test-host.com", 80);
}
[Test]
public void GetUri_HostNameWithSchemeAndPort()
{
ProxyConfig config = new ProxyConfig
{
Host = "https://test-host.com",
Port = 8080
};
CheckUri(config.GetUri(), "https", "test-host.com", 8080);
}
[Test]
public void GetUri_HostAddressWithScheme()
{
ProxyConfig config = new ProxyConfig { Host = "https://192.168.0.1" };
CheckUri(config.GetUri(), "https", "192.168.0.1", 80);
}
[Test]
public void GetUri_HostAddressWithoutScheme()
{
ProxyConfig config = new ProxyConfig { Host = "192.168.0.1" };
CheckUri(config.GetUri(), "http", "192.168.0.1", 80);
}
[Test]
public void GetUri_HostAddressWithSchemeAndPort()
{
ProxyConfig config = new ProxyConfig
{
Host = "https://192.168.0.1",
Port = 8080
};
CheckUri(config.GetUri(), "https", "192.168.0.1", 8080);
}
[Test]
public void GetUri_NullHost()
{
ProxyConfig config = new ProxyConfig { Host = null };
Assert.Throws<ArgumentNullException>(() => config.GetUri());
}
[Test]
public void GetUri_EmptyHost()
{
ProxyConfig config = new ProxyConfig { Host = string.Empty };
Assert.Throws<UriFormatException>(() => config.GetUri());
}
[Test]
public void GetUri_NegativePort()
{
ProxyConfig config = new ProxyConfig
{
Host = "test-host.com",
Port = -10
};
Assert.Throws<ArgumentOutOfRangeException>(() => config.GetUri());
}
#endregion GetUri
[Test]
public void Set_Null()
{
ProxyConfig config = new ProxyConfig
{
Host = "https://test-host.com",
Port = 1234,
Username = "admin",
Password = "password",
BypassProxyOnLocal = true
};
config.Set(null);
Assert.NotNull(config);
Assert.Null(config.Host);
Assert.AreEqual(80, config.Port);
Assert.Null(config.Username);
Assert.Null(config.Password);
Assert.False(config.BypassProxyOnLocal);
}
private static void CheckUri(Uri uri, string expectedScheme, string expectedHost, int expectedPort)
{
Assert.AreEqual(expectedScheme, uri.Scheme);
Assert.AreEqual(expectedHost, uri.Host);
Assert.AreEqual(expectedPort, uri.Port);
}
}
} }

View File

@ -1,8 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework> <TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
</PropertyGroup> </PropertyGroup>

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -9,72 +9,71 @@ using SpotifyAPI.Web.Models;
namespace SpotifyAPI.Web.Tests namespace SpotifyAPI.Web.Tests
{ {
[TestFixture] [TestFixture]
public class SpotifyWebAPITest public class SpotifyWebAPITest
{
private static readonly string FixtureDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../fixtures/");
private Mock<IClient> _mock;
private SpotifyWebAPI _spotify;
[SetUp]
public void SetUp()
{ {
private static readonly string FixtureDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../fixtures/"); _mock = new Mock<IClient>();
_spotify = new SpotifyWebAPI
private Mock<IClient> _mock; {
private SpotifyWebAPI _spotify; WebClient = _mock.Object,
UseAuth = false
[SetUp] };
public void SetUp()
{
_mock = new Mock<IClient>();
_spotify = new SpotifyWebAPI
{
WebClient = _mock.Object,
UseAuth = false
};
}
private static T GetFixture<T>(string file)
{
return JsonConvert.DeserializeObject<T>(File.ReadAllText(Path.Combine(FixtureDir, file)));
}
private static bool ContainsValues(string str, params string[] values)
{
return values.All(str.Contains);
}
[Test]
public void ShouldGetPrivateProfile_WithoutAuth()
{
_spotify.UseAuth = false;
Assert.Throws<InvalidOperationException>(() => _spotify.GetPrivateProfile());
}
[Test]
public void ShouldGetPrivateProfile_WithAuth()
{
PrivateProfile profile = GetFixture<PrivateProfile>("private-user.json");
_mock.Setup(client => client.DownloadJson<PrivateProfile>(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.Returns(new Tuple<ResponseInfo, PrivateProfile>(ResponseInfo.Empty, profile));
_spotify.UseAuth = true;
Assert.AreEqual(profile, _spotify.GetPrivateProfile());
_mock.Verify(client => client.DownloadJson<PrivateProfile>(
It.Is<string>(str => ContainsValues(str, "/me")),
It.IsNotNull<Dictionary<string, string>>()), Times.Exactly(1));
}
[Test]
public void ShouldGetPublicProfile()
{
PublicProfile profile = GetFixture<PublicProfile>("public-user.json");
_mock.Setup(client => client.DownloadJson<PublicProfile>(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.Returns(new Tuple<ResponseInfo, PublicProfile>(ResponseInfo.Empty, profile));
_spotify.UseAuth = false;
Assert.AreEqual(profile, _spotify.GetPublicProfile("wizzler"));
_mock.Verify(client => client.DownloadJson<PublicProfile>(
It.Is<string>(str => ContainsValues(str, "/users/wizzler")),
It.Is<Dictionary<string, string>>(headers => headers.Count == 0)), Times.Exactly(1));
}
//Will add more tests once I decided if this is worth the effort (propably not?)
} }
private static T GetFixture<T>(string file)
{
return JsonConvert.DeserializeObject<T>(File.ReadAllText(Path.Combine(FixtureDir, file)));
}
private static bool ContainsValues(string str, params string[] values)
{
return values.All(str.Contains);
}
[Test]
public void ShouldGetPrivateProfile_WithoutAuth()
{
_spotify.UseAuth = false;
Assert.Throws<InvalidOperationException>(() => _spotify.GetPrivateProfile());
}
[Test]
public void ShouldGetPrivateProfile_WithAuth()
{
PrivateProfile profile = GetFixture<PrivateProfile>("private-user.json");
_mock.Setup(client => client.DownloadJson<PrivateProfile>(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.Returns(new Tuple<ResponseInfo, PrivateProfile>(ResponseInfo.Empty, profile));
_spotify.UseAuth = true;
Assert.AreEqual(profile, _spotify.GetPrivateProfile());
_mock.Verify(client => client.DownloadJson<PrivateProfile>(
It.Is<string>(str => ContainsValues(str, "/me")),
It.IsNotNull<Dictionary<string, string>>()), Times.Exactly(1));
}
[Test]
public void ShouldGetPublicProfile()
{
PublicProfile profile = GetFixture<PublicProfile>("public-user.json");
_mock.Setup(client => client.DownloadJson<PublicProfile>(It.IsAny<string>(), It.IsAny<Dictionary<string, string>>()))
.Returns(new Tuple<ResponseInfo, PublicProfile>(ResponseInfo.Empty, profile));
_spotify.UseAuth = false;
Assert.AreEqual(profile, _spotify.GetPublicProfile("wizzler"));
_mock.Verify(client => client.DownloadJson<PublicProfile>(
It.Is<string>(str => ContainsValues(str, "/users/wizzler")),
It.Is<Dictionary<string, string>>(headers => headers.Count == 0)), Times.Exactly(1));
}
//Will add more tests once I decided if this is worth the effort (propably not?)
}
} }

View File

@ -1,18 +1,18 @@
using System; using System;
using NUnit.Framework; using NUnit.Framework;
namespace SpotifyAPI.Web.Tests namespace SpotifyAPI.Web.Tests
{ {
[TestFixture] [TestFixture]
public class UtilTest public class UtilTest
{
[Test]
public void TimestampShouldBeNoFloatingPoint()
{ {
[Test] string timestamp = DateTime.Now.ToUnixTimeMillisecondsPoly().ToString();
public void TimestampShouldBeNoFloatingPoint()
{
string timestamp = DateTime.Now.ToUnixTimeMillisecondsPoly().ToString();
StringAssert.DoesNotContain(".", timestamp); StringAssert.DoesNotContain(".", timestamp);
StringAssert.DoesNotContain(",", timestamp); StringAssert.DoesNotContain(",", timestamp);
}
} }
}
} }

View File

@ -1,23 +1,23 @@
using System; using System;
namespace SpotifyAPI.Web.Enums namespace SpotifyAPI.Web.Enums
{ {
[Flags] [Flags]
public enum AlbumType public enum AlbumType
{ {
[String("album")] [String("album")]
Album = 1, Album = 1,
[String("single")] [String("single")]
Single = 2, Single = 2,
[String("compilation")] [String("compilation")]
Compilation = 4, Compilation = 4,
[String("appears_on")] [String("appears_on")]
AppearsOn = 8, AppearsOn = 8,
[String("album,single,compilation,appears_on")] [String("album,single,compilation,appears_on")]
All = 16 All = 16
} }
} }

View File

@ -1,14 +1,14 @@
using System; using System;
namespace SpotifyAPI.Web.Enums namespace SpotifyAPI.Web.Enums
{ {
[Flags] [Flags]
public enum FollowType public enum FollowType
{ {
[String("artist")] [String("artist")]
Artist = 1, Artist = 1,
[String("user")] [String("user")]
User = 2 User = 2
} }
} }

View File

@ -1,17 +1,17 @@
using System; using System;
namespace SpotifyAPI.Web.Enums namespace SpotifyAPI.Web.Enums
{ {
[Flags] [Flags]
public enum RepeatState public enum RepeatState
{ {
[String("track")] [String("track")]
Track = 1, Track = 1,
[String("context")] [String("context")]
Context = 2, Context = 2,
[String("off")] [String("off")]
Off = 4 Off = 4
} }
} }

View File

@ -1,68 +1,68 @@
using System; using System;
namespace SpotifyAPI.Web.Enums namespace SpotifyAPI.Web.Enums
{ {
[Flags] [Flags]
public enum Scope public enum Scope
{ {
[String("")] [String("")]
None = 1, None = 1,
[String("playlist-modify-public")] [String("playlist-modify-public")]
PlaylistModifyPublic = 2, PlaylistModifyPublic = 2,
[String("playlist-modify-private")] [String("playlist-modify-private")]
PlaylistModifyPrivate = 4, PlaylistModifyPrivate = 4,
[String("playlist-read-private")] [String("playlist-read-private")]
PlaylistReadPrivate = 8, PlaylistReadPrivate = 8,
[String("streaming")] [String("streaming")]
Streaming = 16, Streaming = 16,
[String("user-read-private")] [String("user-read-private")]
UserReadPrivate = 32, UserReadPrivate = 32,
[String("user-read-email")] [String("user-read-email")]
UserReadEmail = 64, UserReadEmail = 64,
[String("user-library-read")] [String("user-library-read")]
UserLibraryRead = 128, UserLibraryRead = 128,
[String("user-library-modify")] [String("user-library-modify")]
UserLibraryModify = 256, UserLibraryModify = 256,
[String("user-follow-modify")] [String("user-follow-modify")]
UserFollowModify = 512, UserFollowModify = 512,
[String("user-follow-read")] [String("user-follow-read")]
UserFollowRead = 1024, UserFollowRead = 1024,
[String("user-read-birthdate")] [String("user-read-birthdate")]
UserReadBirthdate = 2048, UserReadBirthdate = 2048,
[String("user-top-read")] [String("user-top-read")]
UserTopRead = 4096, UserTopRead = 4096,
[String("playlist-read-collaborative")] [String("playlist-read-collaborative")]
PlaylistReadCollaborative = 8192, PlaylistReadCollaborative = 8192,
[String("user-read-recently-played")] [String("user-read-recently-played")]
UserReadRecentlyPlayed = 16384, UserReadRecentlyPlayed = 16384,
[String("user-read-playback-state")] [String("user-read-playback-state")]
UserReadPlaybackState = 32768, UserReadPlaybackState = 32768,
[String("user-modify-playback-state")] [String("user-modify-playback-state")]
UserModifyPlaybackState = 65536, UserModifyPlaybackState = 65536,
[String("user-read-currently-playing")] [String("user-read-currently-playing")]
UserReadCurrentlyPlaying = 131072, UserReadCurrentlyPlaying = 131072,
[String("app-remote-control")] [String("app-remote-control")]
AppRemoteControl = 262144, AppRemoteControl = 262144,
[String("ugc-image-upload")] [String("ugc-image-upload")]
UgcImageUpload = 524288 UgcImageUpload = 524288
} }
} }

View File

@ -1,23 +1,23 @@
using System; using System;
namespace SpotifyAPI.Web.Enums namespace SpotifyAPI.Web.Enums
{ {
[Flags] [Flags]
public enum SearchType public enum SearchType
{ {
[String("artist")] [String("artist")]
Artist = 1, Artist = 1,
[String("album")] [String("album")]
Album = 2, Album = 2,
[String("track")] [String("track")]
Track = 4, Track = 4,
[String("playlist")] [String("playlist")]
Playlist = 8, Playlist = 8,
[String("track,album,artist,playlist")] [String("track,album,artist,playlist")]
All = 16 All = 16
} }
} }

View File

@ -1,20 +1,20 @@
using System; using System;
namespace SpotifyAPI.Web.Enums namespace SpotifyAPI.Web.Enums
{ {
/// <summary> /// <summary>
/// Only one value allowed /// Only one value allowed
/// </summary> /// </summary>
[Flags] [Flags]
public enum TimeRangeType public enum TimeRangeType
{ {
[String("long_term")] [String("long_term")]
LongTerm = 1, LongTerm = 1,
[String("medium_term")] [String("medium_term")]
MediumTerm = 2, MediumTerm = 2,
[String("short_term")] [String("short_term")]
ShortTerm = 4 ShortTerm = 4
} }
} }

View File

@ -1,20 +1,20 @@
using System; using System;
namespace SpotifyAPI.Web.Enums namespace SpotifyAPI.Web.Enums
{ {
[Flags] [Flags]
public enum TrackType public enum TrackType
{ {
[String("track")] [String("track")]
Track = 1, Track = 1,
[String("episode")] [String("episode")]
Episode = 2, Episode = 2,
[String("ad")] [String("ad")]
Ad = 4, Ad = 4,
[String("unknown")] [String("unknown")]
Unknown = 8 Unknown = 8
} }
} }

View File

@ -1,125 +1,125 @@
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using SpotifyAPI.Web.Models; using SpotifyAPI.Web.Models;
namespace SpotifyAPI.Web namespace SpotifyAPI.Web
{ {
public interface IClient : IDisposable public interface IClient : IDisposable
{ {
JsonSerializerSettings JsonSettings { get; set; } JsonSerializerSettings JsonSettings { get; set; }
/// <summary> /// <summary>
/// Downloads data from an URL and returns it /// Downloads data from an URL and returns it
/// </summary> /// </summary>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Tuple<ResponseInfo, string> Download(string url, Dictionary<string, string> headers = null); Tuple<ResponseInfo, string> Download(string url, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Downloads data async from an URL and returns it /// Downloads data async from an URL and returns it
/// </summary> /// </summary>
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Task<Tuple<ResponseInfo, string>> DownloadAsync(string url, Dictionary<string, string> headers = null); Task<Tuple<ResponseInfo, string>> DownloadAsync(string url, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Downloads data from an URL and returns it /// Downloads data from an URL and returns it
/// </summary> /// </summary>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Tuple<ResponseInfo, byte[]> DownloadRaw(string url, Dictionary<string, string> headers = null); Tuple<ResponseInfo, byte[]> DownloadRaw(string url, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Downloads data async from an URL and returns it /// Downloads data async from an URL and returns it
/// </summary> /// </summary>
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Task<Tuple<ResponseInfo, byte[]>> DownloadRawAsync(string url, Dictionary<string, string> headers = null); Task<Tuple<ResponseInfo, byte[]>> DownloadRawAsync(string url, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Downloads data from an URL and converts it to an object /// Downloads data from an URL and converts it to an object
/// </summary> /// </summary>
/// <typeparam name="T">The Type which the object gets converted to</typeparam> /// <typeparam name="T">The Type which the object gets converted to</typeparam>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Tuple<ResponseInfo, T> DownloadJson<T>(string url, Dictionary<string, string> headers = null); Tuple<ResponseInfo, T> DownloadJson<T>(string url, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Downloads data async from an URL and converts it to an object /// Downloads data async from an URL and converts it to an object
/// </summary> /// </summary>
/// <typeparam name="T">The Type which the object gets converted to</typeparam> /// <typeparam name="T">The Type which the object gets converted to</typeparam>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Task<Tuple<ResponseInfo, T>> DownloadJsonAsync<T>(string url, Dictionary<string, string> headers = null); Task<Tuple<ResponseInfo, T>> DownloadJsonAsync<T>(string url, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Uploads data from an URL and returns the response /// Uploads data from an URL and returns the response
/// </summary> /// </summary>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="body">The Body-Data (most likely a JSON String)</param> /// <param name="body">The Body-Data (most likely a JSON String)</param>
/// <param name="method">The Upload-method (POST,DELETE,PUT)</param> /// <param name="method">The Upload-method (POST,DELETE,PUT)</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Tuple<ResponseInfo, string> Upload(string url, string body, string method, Dictionary<string, string> headers = null); Tuple<ResponseInfo, string> Upload(string url, string body, string method, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Uploads data async from an URL and returns the response /// Uploads data async from an URL and returns the response
/// </summary> /// </summary>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="body">The Body-Data (most likely a JSON String)</param> /// <param name="body">The Body-Data (most likely a JSON String)</param>
/// <param name="method">The Upload-method (POST,DELETE,PUT)</param> /// <param name="method">The Upload-method (POST,DELETE,PUT)</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Task<Tuple<ResponseInfo, string>> UploadAsync(string url, string body, string method, Dictionary<string, string> headers = null); Task<Tuple<ResponseInfo, string>> UploadAsync(string url, string body, string method, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Uploads data from an URL and returns the response /// Uploads data from an URL and returns the response
/// </summary> /// </summary>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="body">The Body-Data (most likely a JSON String)</param> /// <param name="body">The Body-Data (most likely a JSON String)</param>
/// <param name="method">The Upload-method (POST,DELETE,PUT)</param> /// <param name="method">The Upload-method (POST,DELETE,PUT)</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Tuple<ResponseInfo, byte[]> UploadRaw(string url, string body, string method, Dictionary<string, string> headers = null); Tuple<ResponseInfo, byte[]> UploadRaw(string url, string body, string method, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Uploads data async from an URL and returns the response /// Uploads data async from an URL and returns the response
/// </summary> /// </summary>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="body">The Body-Data (most likely a JSON String)</param> /// <param name="body">The Body-Data (most likely a JSON String)</param>
/// <param name="method">The Upload-method (POST,DELETE,PUT)</param> /// <param name="method">The Upload-method (POST,DELETE,PUT)</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Task<Tuple<ResponseInfo, byte[]>> UploadRawAsync(string url, string body, string method, Dictionary<string, string> headers = null); Task<Tuple<ResponseInfo, byte[]>> UploadRawAsync(string url, string body, string method, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Uploads data from an URL and converts the response to an object /// Uploads data from an URL and converts the response to an object
/// </summary> /// </summary>
/// <typeparam name="T">The Type which the object gets converted to</typeparam> /// <typeparam name="T">The Type which the object gets converted to</typeparam>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="body">The Body-Data (most likely a JSON String)</param> /// <param name="body">The Body-Data (most likely a JSON String)</param>
/// <param name="method">The Upload-method (POST,DELETE,PUT)</param> /// <param name="method">The Upload-method (POST,DELETE,PUT)</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Tuple<ResponseInfo, T> UploadJson<T>(string url, string body, string method, Dictionary<string, string> headers = null); Tuple<ResponseInfo, T> UploadJson<T>(string url, string body, string method, Dictionary<string, string> headers = null);
/// <summary> /// <summary>
/// Uploads data async from an URL and converts the response to an object /// Uploads data async from an URL and converts the response to an object
/// </summary> /// </summary>
/// <typeparam name="T">The Type which the object gets converted to</typeparam> /// <typeparam name="T">The Type which the object gets converted to</typeparam>
/// <param name="url">An URL</param> /// <param name="url">An URL</param>
/// <param name="body">The Body-Data (most likely a JSON String)</param> /// <param name="body">The Body-Data (most likely a JSON String)</param>
/// <param name="method">The Upload-method (POST,DELETE,PUT)</param> /// <param name="method">The Upload-method (POST,DELETE,PUT)</param>
/// <param name="headers"></param> /// <param name="headers"></param>
/// <returns></returns> /// <returns></returns>
Task<Tuple<ResponseInfo, T>> UploadJsonAsync<T>(string url, string body, string method, Dictionary<string, string> headers = null); Task<Tuple<ResponseInfo, T>> UploadJsonAsync<T>(string url, string body, string method, Dictionary<string, string> headers = null);
} }
} }

View File

@ -1,28 +1,28 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AnalysisMeta public class AnalysisMeta
{ {
[JsonProperty("analyzer_platform")] [JsonProperty("analyzer_platform")]
public string AnalyzerVersion { get; set; } public string AnalyzerVersion { get; set; }
[JsonProperty("platform")] [JsonProperty("platform")]
public string Platform { get; set; } public string Platform { get; set; }
[JsonProperty("status_code")] [JsonProperty("status_code")]
public int StatusCode { get; set; } public int StatusCode { get; set; }
[JsonProperty("detailed_status")] [JsonProperty("detailed_status")]
public string DetailedStatus { get; set; } public string DetailedStatus { get; set; }
[JsonProperty("timestamp")] [JsonProperty("timestamp")]
public long Timestamp { get; set; } public long Timestamp { get; set; }
[JsonProperty("analysis_time")] [JsonProperty("analysis_time")]
public double AnalysisTime { get; set; } public double AnalysisTime { get; set; }
[JsonProperty("input_process")] [JsonProperty("input_process")]
public string InputProcess { get; set; } public string InputProcess { get; set; }
} }
} }

View File

@ -1,43 +1,43 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AnalysisSection public class AnalysisSection
{ {
[JsonProperty("start")] [JsonProperty("start")]
public double Start { get; set; } public double Start { get; set; }
[JsonProperty("duration")] [JsonProperty("duration")]
public double Duration { get; set; } public double Duration { get; set; }
[JsonProperty("confidence")] [JsonProperty("confidence")]
public double Confidence { get; set; } public double Confidence { get; set; }
[JsonProperty("loudness")] [JsonProperty("loudness")]
public double Loudness { get; set; } public double Loudness { get; set; }
[JsonProperty("tempo")] [JsonProperty("tempo")]
public double Tempo { get; set; } public double Tempo { get; set; }
[JsonProperty("tempo_confidence")] [JsonProperty("tempo_confidence")]
public double TempoConfidence { get; set; } public double TempoConfidence { get; set; }
[JsonProperty("key")] [JsonProperty("key")]
public int Key { get; set; } public int Key { get; set; }
[JsonProperty("key_confidence")] [JsonProperty("key_confidence")]
public double KeyConfidence { get; set; } public double KeyConfidence { get; set; }
[JsonProperty("mode")] [JsonProperty("mode")]
public int Mode { get; set; } public int Mode { get; set; }
[JsonProperty("mode_confidence")] [JsonProperty("mode_confidence")]
public double ModeConfidence { get; set; } public double ModeConfidence { get; set; }
[JsonProperty("time_signature")] [JsonProperty("time_signature")]
public int TimeSignature { get; set; } public int TimeSignature { get; set; }
[JsonProperty("time_signature_confidence")] [JsonProperty("time_signature_confidence")]
public double TimeSignatureConfidence { get; set; } public double TimeSignatureConfidence { get; set; }
} }
} }

View File

@ -1,35 +1,35 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AnalysisSegment public class AnalysisSegment
{ {
[JsonProperty("start")] [JsonProperty("start")]
public double Start { get; set; } public double Start { get; set; }
[JsonProperty("duration")] [JsonProperty("duration")]
public double Duration { get; set; } public double Duration { get; set; }
[JsonProperty("confidence")] [JsonProperty("confidence")]
public double Confidence { get; set; } public double Confidence { get; set; }
[JsonProperty("loudness_start")] [JsonProperty("loudness_start")]
public double LoudnessStart { get; set; } public double LoudnessStart { get; set; }
[JsonProperty("loudness_max_time")] [JsonProperty("loudness_max_time")]
public double LoudnessMaxTime { get; set; } public double LoudnessMaxTime { get; set; }
[JsonProperty("loudness_max")] [JsonProperty("loudness_max")]
public double LoudnessMax { get; set; } public double LoudnessMax { get; set; }
[JsonProperty("loudness_end")] [JsonProperty("loudness_end")]
public double LoudnessEnd { get; set; } public double LoudnessEnd { get; set; }
[JsonProperty("pitches")] [JsonProperty("pitches")]
public List<double> Pitches { get; set; } public List<double> Pitches { get; set; }
[JsonProperty("timbre")] [JsonProperty("timbre")]
public List<double> Timbre { get; set; } public List<double> Timbre { get; set; }
} }
} }

View File

@ -1,16 +1,16 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AnalysisTimeSlice public class AnalysisTimeSlice
{ {
[JsonProperty("start")] [JsonProperty("start")]
public double Start { get; set; } public double Start { get; set; }
[JsonProperty("duration")] [JsonProperty("duration")]
public double Duration { get; set; } public double Duration { get; set; }
[JsonProperty("confidence")] [JsonProperty("confidence")]
public double Confidence { get; set; } public double Confidence { get; set; }
} }
} }

View File

@ -1,86 +1,86 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AnalysisTrack public class AnalysisTrack
{ {
[JsonProperty("num_samples")] [JsonProperty("num_samples")]
public int NumSamples { get; set; } public int NumSamples { get; set; }
[JsonProperty("duration")] [JsonProperty("duration")]
public double Duration { get; set; } public double Duration { get; set; }
[JsonProperty("sample_md5")] [JsonProperty("sample_md5")]
public string SampleMD5 { get; set; } public string SampleMD5 { get; set; }
[JsonProperty("offset_seconds")] [JsonProperty("offset_seconds")]
public double OffsetSeconds { get; set; } public double OffsetSeconds { get; set; }
[JsonProperty("window_seconds")] [JsonProperty("window_seconds")]
public double WindowSeconds { get; set; } public double WindowSeconds { get; set; }
[JsonProperty("analysis_sample_rate")] [JsonProperty("analysis_sample_rate")]
public int AnalysisSampleRate { get; set; } public int AnalysisSampleRate { get; set; }
[JsonProperty("analysis_channels")] [JsonProperty("analysis_channels")]
public int AnalysisChannels { get; set; } public int AnalysisChannels { get; set; }
[JsonProperty("end_of_fade_in")] [JsonProperty("end_of_fade_in")]
public double EndOfFadeIn { get; set; } public double EndOfFadeIn { get; set; }
[JsonProperty("start_of_fade_out")] [JsonProperty("start_of_fade_out")]
public double StartOfFadeOut { get; set; } public double StartOfFadeOut { get; set; }
[JsonProperty("loudness")] [JsonProperty("loudness")]
public double Loudness { get; set; } public double Loudness { get; set; }
[JsonProperty("tempo")] [JsonProperty("tempo")]
public double Tempo { get; set; } public double Tempo { get; set; }
[JsonProperty("tempo_confidence")] [JsonProperty("tempo_confidence")]
public double TempoConfidence { get; set; } public double TempoConfidence { get; set; }
[JsonProperty("time_signature")] [JsonProperty("time_signature")]
public double TimeSignature { get; set; } public double TimeSignature { get; set; }
[JsonProperty("time_signature_confidence")] [JsonProperty("time_signature_confidence")]
public double TimeSignatureConfidence { get; set; } public double TimeSignatureConfidence { get; set; }
[JsonProperty("key")] [JsonProperty("key")]
public int Key { get; set; } public int Key { get; set; }
[JsonProperty("key_confidence")] [JsonProperty("key_confidence")]
public double KeyConfidence { get; set; } public double KeyConfidence { get; set; }
[JsonProperty("mode")] [JsonProperty("mode")]
public int Mode { get; set; } public int Mode { get; set; }
[JsonProperty("mode_confidence")] [JsonProperty("mode_confidence")]
public double ModeConfidence { get; set; } public double ModeConfidence { get; set; }
[JsonProperty("codestring")] [JsonProperty("codestring")]
public string Codestring { get; set; } public string Codestring { get; set; }
[JsonProperty("code_version")] [JsonProperty("code_version")]
public double CodeVersion { get; set; } public double CodeVersion { get; set; }
[JsonProperty("echoprintstring")] [JsonProperty("echoprintstring")]
public string Echoprintstring { get; set; } public string Echoprintstring { get; set; }
[JsonProperty("echoprint_version")] [JsonProperty("echoprint_version")]
public double EchoprintVersion { get; set; } public double EchoprintVersion { get; set; }
[JsonProperty("synchstring")] [JsonProperty("synchstring")]
public string Synchstring { get; set; } public string Synchstring { get; set; }
[JsonProperty("synch_version")] [JsonProperty("synch_version")]
public double SynchVersion { get; set; } public double SynchVersion { get; set; }
[JsonProperty("rhythmstring")] [JsonProperty("rhythmstring")]
public string Rhythmstring { get; set; } public string Rhythmstring { get; set; }
[JsonProperty("rhythm_version")] [JsonProperty("rhythm_version")]
public double RhythmVersion { get; set; } public double RhythmVersion { get; set; }
} }
} }

View File

@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class ListResponse<T> : BasicModel public class ListResponse<T> : BasicModel
{ {
public List<T> List { get; set; } public List<T> List { get; set; }
} }
} }

View File

@ -1,29 +1,29 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AudioAnalysis : BasicModel public class AudioAnalysis : BasicModel
{ {
[JsonProperty("bars")] [JsonProperty("bars")]
public List<AnalysisTimeSlice> Bars { get; set; } public List<AnalysisTimeSlice> Bars { get; set; }
[JsonProperty("beats")] [JsonProperty("beats")]
public List<AnalysisTimeSlice> Beats { get; set; } public List<AnalysisTimeSlice> Beats { get; set; }
[JsonProperty("meta")] [JsonProperty("meta")]
public AnalysisMeta Meta { get; set; } public AnalysisMeta Meta { get; set; }
[JsonProperty("sections")] [JsonProperty("sections")]
public List<AnalysisSection> Sections { get; set; } public List<AnalysisSection> Sections { get; set; }
[JsonProperty("segments")] [JsonProperty("segments")]
public List<AnalysisSegment> Segments { get; set; } public List<AnalysisSegment> Segments { get; set; }
[JsonProperty("tatums")] [JsonProperty("tatums")]
public List<AnalysisTimeSlice> Tatums { get; set; } public List<AnalysisTimeSlice> Tatums { get; set; }
[JsonProperty("track")] [JsonProperty("track")]
public AnalysisTrack Track { get; set; } public AnalysisTrack Track { get; set; }
} }
} }

View File

@ -1,61 +1,61 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AudioFeatures : BasicModel public class AudioFeatures : BasicModel
{ {
[JsonProperty("acousticness")] [JsonProperty("acousticness")]
public float Acousticness { get; set; } public float Acousticness { get; set; }
[JsonProperty("analysis_url")] [JsonProperty("analysis_url")]
public string AnalysisUrl { get; set; } public string AnalysisUrl { get; set; }
[JsonProperty("danceability")] [JsonProperty("danceability")]
public float Danceability { get; set; } public float Danceability { get; set; }
[JsonProperty("duration_ms")] [JsonProperty("duration_ms")]
public int DurationMs { get; set; } public int DurationMs { get; set; }
[JsonProperty("energy")] [JsonProperty("energy")]
public float Energy { get; set; } public float Energy { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("instrumentalness")] [JsonProperty("instrumentalness")]
public float Instrumentalness { get; set; } public float Instrumentalness { get; set; }
[JsonProperty("key")] [JsonProperty("key")]
public int Key { get; set; } public int Key { get; set; }
[JsonProperty("liveness")] [JsonProperty("liveness")]
public float Liveness { get; set; } public float Liveness { get; set; }
[JsonProperty("loudness")] [JsonProperty("loudness")]
public float Loudness { get; set; } public float Loudness { get; set; }
[JsonProperty("mode")] [JsonProperty("mode")]
public int Mode { get; set; } public int Mode { get; set; }
[JsonProperty("speechiness")] [JsonProperty("speechiness")]
public float Speechiness { get; set; } public float Speechiness { get; set; }
[JsonProperty("tempo")] [JsonProperty("tempo")]
public float Tempo { get; set; } public float Tempo { get; set; }
[JsonProperty("time_signature")] [JsonProperty("time_signature")]
public int TimeSignature { get; set; } public int TimeSignature { get; set; }
[JsonProperty("track_href")] [JsonProperty("track_href")]
public string TrackHref { get; set; } public string TrackHref { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
[JsonProperty("valence")] [JsonProperty("valence")]
public float Valence { get; set; } public float Valence { get; set; }
} }
} }

View File

@ -1,11 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class AvailabeDevices : BasicModel public class AvailabeDevices : BasicModel
{ {
[JsonProperty("devices")] [JsonProperty("devices")]
public List<Device> Devices { get; set; } public List<Device> Devices { get; set; }
} }
} }

View File

@ -1,23 +1,23 @@
using Newtonsoft.Json;
using System.Net; using System.Net;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public abstract class BasicModel public abstract class BasicModel
{ {
[JsonProperty("error")] [JsonProperty("error")]
public Error Error { get; set; } public Error Error { get; set; }
private ResponseInfo _info; private ResponseInfo _info;
public bool HasError() => Error != null; public bool HasError() => Error != null;
internal void AddResponseInfo(ResponseInfo info) => _info = info; internal void AddResponseInfo(ResponseInfo info) => _info = info;
public string Header(string key) => _info.Headers?.Get(key); public string Header(string key) => _info.Headers?.Get(key);
public WebHeaderCollection Headers() => _info.Headers; public WebHeaderCollection Headers() => _info.Headers;
public HttpStatusCode StatusCode() => _info.StatusCode; public HttpStatusCode StatusCode() => _info.StatusCode;
} }
} }

View File

@ -1,20 +1,20 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class Category : BasicModel public class Category : BasicModel
{ {
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("icons")] [JsonProperty("icons")]
public List<Image> Icons { get; set; } public List<Image> Icons { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
} }
} }

View File

@ -1,10 +1,10 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class CategoryList : BasicModel public class CategoryList : BasicModel
{ {
[JsonProperty("categories")] [JsonProperty("categories")]
public Paging<Category> Categories { get; set; } public Paging<Category> Categories { get; set; }
} }
} }

View File

@ -1,10 +1,10 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class CategoryPlaylist : BasicModel public class CategoryPlaylist : BasicModel
{ {
[JsonProperty("playlists")] [JsonProperty("playlists")]
public Paging<SimplePlaylist> Playlists { get; set; } public Paging<SimplePlaylist> Playlists { get; set; }
} }
} }

View File

@ -1,31 +1,31 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class CursorPaging<T> : BasicModel public class CursorPaging<T> : BasicModel
{
[JsonProperty("href")]
public string Href { get; set; }
[JsonProperty("items")]
public List<T> Items { get; set; }
[JsonProperty("limit")]
public int Limit { get; set; }
[JsonProperty("next")]
public string Next { get; set; }
[JsonProperty("cursors")]
public Cursor Cursors { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
public bool HasNext()
{ {
[JsonProperty("href")] return !string.IsNullOrEmpty(Next);
public string Href { get; set; }
[JsonProperty("items")]
public List<T> Items { get; set; }
[JsonProperty("limit")]
public int Limit { get; set; }
[JsonProperty("next")]
public string Next { get; set; }
[JsonProperty("cursors")]
public Cursor Cursors { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
public bool HasNext()
{
return !string.IsNullOrEmpty(Next);
}
} }
}
} }

View File

@ -1,25 +1,25 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class Device public class Device
{ {
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("is_active")] [JsonProperty("is_active")]
public bool IsActive { get; set; } public bool IsActive { get; set; }
[JsonProperty("is_restricted")] [JsonProperty("is_restricted")]
public bool IsRestricted { get; set; } public bool IsRestricted { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("volume_percent")] [JsonProperty("volume_percent")]
public int VolumePercent { get; set; } public int VolumePercent { get; set; }
} }
} }

View File

@ -1,13 +1,13 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class FeaturedPlaylists : BasicModel public class FeaturedPlaylists : BasicModel
{ {
[JsonProperty("message")] [JsonProperty("message")]
public string Message { get; set; } public string Message { get; set; }
[JsonProperty("playlists")] [JsonProperty("playlists")]
public Paging<SimplePlaylist> Playlists { get; set; } public Paging<SimplePlaylist> Playlists { get; set; }
} }
} }

View File

@ -1,10 +1,10 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class FollowedArtists : BasicModel public class FollowedArtists : BasicModel
{ {
[JsonProperty("artists")] [JsonProperty("artists")]
public CursorPaging<FullArtist> Artists { get; set; } public CursorPaging<FullArtist> Artists { get; set; }
} }
} }

View File

@ -1,68 +1,68 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class FullAlbum : BasicModel public class FullAlbum : BasicModel
{ {
[JsonProperty("album_type")] [JsonProperty("album_type")]
public string AlbumType { get; set; } public string AlbumType { get; set; }
[JsonProperty("artists")] [JsonProperty("artists")]
public List<SimpleArtist> Artists { get; set; } public List<SimpleArtist> Artists { get; set; }
[JsonProperty("available_markets")] [JsonProperty("available_markets")]
public List<string> AvailableMarkets { get; set; } public List<string> AvailableMarkets { get; set; }
[JsonProperty("copyrights")] [JsonProperty("copyrights")]
public List<Copyright> Copyrights { get; set; } public List<Copyright> Copyrights { get; set; }
[JsonProperty("external_ids")] [JsonProperty("external_ids")]
public Dictionary<string, string> ExternalIds { get; set; } public Dictionary<string, string> ExternalIds { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("genres")] [JsonProperty("genres")]
public List<string> Genres { get; set; } public List<string> Genres { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("images")] [JsonProperty("images")]
public List<Image> Images { get; set; } public List<Image> Images { get; set; }
[JsonProperty("label")] [JsonProperty("label")]
public string Label { get; set; } public string Label { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("popularity")] [JsonProperty("popularity")]
public int Popularity { get; set; } public int Popularity { get; set; }
[JsonProperty("release_date")] [JsonProperty("release_date")]
public string ReleaseDate { get; set; } public string ReleaseDate { get; set; }
[JsonProperty("release_date_precision")] [JsonProperty("release_date_precision")]
public string ReleaseDatePrecision { get; set; } public string ReleaseDatePrecision { get; set; }
[JsonProperty("tracks")] [JsonProperty("tracks")]
public Paging<SimpleTrack> Tracks { get; set; } public Paging<SimpleTrack> Tracks { get; set; }
[JsonProperty("restrictions")] [JsonProperty("restrictions")]
public Dictionary<string, string> Restrictions { get; set; } public Dictionary<string, string> Restrictions { get; set; }
[JsonProperty("total_tracks")] [JsonProperty("total_tracks")]
public int TotalTracks { get; set; } public int TotalTracks { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,38 +1,38 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class FullArtist : BasicModel public class FullArtist : BasicModel
{ {
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("followers")] [JsonProperty("followers")]
public Followers Followers { get; set; } public Followers Followers { get; set; }
[JsonProperty("genres")] [JsonProperty("genres")]
public List<string> Genres { get; set; } public List<string> Genres { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("images")] [JsonProperty("images")]
public List<Image> Images { get; set; } public List<Image> Images { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("popularity")] [JsonProperty("popularity")]
public int Popularity { get; set; } public int Popularity { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,50 +1,50 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class FullPlaylist : BasicModel public class FullPlaylist : BasicModel
{ {
[JsonProperty("collaborative")] [JsonProperty("collaborative")]
public bool Collaborative { get; set; } public bool Collaborative { get; set; }
[JsonProperty("description")] [JsonProperty("description")]
public string Description { get; set; } public string Description { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("followers")] [JsonProperty("followers")]
public Followers Followers { get; set; } public Followers Followers { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("images")] [JsonProperty("images")]
public List<Image> Images { get; set; } public List<Image> Images { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("owner")] [JsonProperty("owner")]
public PublicProfile Owner { get; set; } public PublicProfile Owner { get; set; }
[JsonProperty("public")] [JsonProperty("public")]
public bool Public { get; set; } public bool Public { get; set; }
[JsonProperty("snapshot_id")] [JsonProperty("snapshot_id")]
public string SnapshotId { get; set; } public string SnapshotId { get; set; }
[JsonProperty("tracks")] [JsonProperty("tracks")]
public Paging<PlaylistTrack> Tracks { get; set; } public Paging<PlaylistTrack> Tracks { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,71 +1,71 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class FullTrack : BasicModel public class FullTrack : BasicModel
{ {
[JsonProperty("album")] [JsonProperty("album")]
public SimpleAlbum Album { get; set; } public SimpleAlbum Album { get; set; }
[JsonProperty("artists")] [JsonProperty("artists")]
public List<SimpleArtist> Artists { get; set; } public List<SimpleArtist> Artists { get; set; }
[JsonProperty("available_markets")] [JsonProperty("available_markets")]
public List<string> AvailableMarkets { get; set; } public List<string> AvailableMarkets { get; set; }
[JsonProperty("disc_number")] [JsonProperty("disc_number")]
public int DiscNumber { get; set; } public int DiscNumber { get; set; }
[JsonProperty("duration_ms")] [JsonProperty("duration_ms")]
public int DurationMs { get; set; } public int DurationMs { get; set; }
[JsonProperty("explicit")] [JsonProperty("explicit")]
public bool Explicit { get; set; } public bool Explicit { get; set; }
[JsonProperty("external_ids")] [JsonProperty("external_ids")]
public Dictionary<string, string> ExternalIds { get; set; } public Dictionary<string, string> ExternalIds { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternUrls { get; set; } public Dictionary<string, string> ExternUrls { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("popularity")] [JsonProperty("popularity")]
public int Popularity { get; set; } public int Popularity { get; set; }
[JsonProperty("preview_url")] [JsonProperty("preview_url")]
public string PreviewUrl { get; set; } public string PreviewUrl { get; set; }
[JsonProperty("track_number")] [JsonProperty("track_number")]
public int TrackNumber { get; set; } public int TrackNumber { get; set; }
[JsonProperty("restrictions")] [JsonProperty("restrictions")]
public Dictionary<string, string> Restrictions { get; set; } public Dictionary<string, string> Restrictions { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
/// <summary> /// <summary>
/// Only filled when the "market"-parameter was supplied! /// Only filled when the "market"-parameter was supplied!
/// </summary> /// </summary>
[JsonProperty("is_playable")] [JsonProperty("is_playable")]
public bool? IsPlayable { get; set; } public bool? IsPlayable { get; set; }
/// <summary> /// <summary>
/// Only filled when the "market"-parameter was supplied! /// Only filled when the "market"-parameter was supplied!
/// </summary> /// </summary>
[JsonProperty("linked_from")] [JsonProperty("linked_from")]
public LinkedFrom LinkedFrom { get; set; } public LinkedFrom LinkedFrom { get; set; }
} }
} }

View File

@ -1,159 +1,158 @@
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class Image public class Image
{
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("width")]
public int Width { get; set; }
[JsonProperty("height")]
public int Height { get; set; }
}
public class ErrorResponse : BasicModel
{ }
public class Error
{
[JsonProperty("status")]
public int Status { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
}
public class PlaylistTrackCollection
{
[JsonProperty("href")]
public string Href { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
}
public class Followers
{
[JsonProperty("href")]
public string Href { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
}
public class PlaylistTrack
{
[JsonProperty("added_at")]
public DateTime AddedAt { get; set; }
[JsonProperty("added_by")]
public PublicProfile AddedBy { get; set; }
[JsonProperty("track")]
public FullTrack Track { get; set; }
[JsonProperty("is_local")]
public bool IsLocal { get; set; }
}
public class DeleteTrackUri
{
/// <summary>
/// Delete-Track Wrapper
/// </summary>
/// <param name="uri">An Spotify-URI</param>
/// <param name="positions">Optional positions</param>
public DeleteTrackUri(string uri, params int[] positions)
{ {
[JsonProperty("url")] Positions = positions.ToList();
public string Url { get; set; } Uri = uri;
[JsonProperty("width")]
public int Width { get; set; }
[JsonProperty("height")]
public int Height { get; set; }
} }
public class ErrorResponse : BasicModel [JsonProperty("uri")]
public string Uri { get; set; }
[JsonProperty("positions")]
public List<int> Positions { get; set; }
public bool ShouldSerializePositions()
{ {
return Positions.Count > 0;
} }
}
public class Error public class Copyright
{ {
[JsonProperty("status")] [JsonProperty("text")]
public int Status { get; set; } public string Text { get; set; }
[JsonProperty("message")] [JsonProperty("type")]
public string Message { get; set; } public string Type { get; set; }
} }
public class PlaylistTrackCollection public class LinkedFrom
{ {
[JsonProperty("href")] [JsonProperty("external_urls")]
public string Href { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("total")] [JsonProperty("href")]
public int Total { get; set; } public string Href { get; set; }
}
public class Followers [JsonProperty("id")]
{ public string Id { get; set; }
[JsonProperty("href")]
public string Href { get; set; }
[JsonProperty("total")] [JsonProperty("type")]
public int Total { get; set; } public string Type { get; set; }
}
public class PlaylistTrack [JsonProperty("uri")]
{ public string Uri { get; set; }
[JsonProperty("added_at")] }
public DateTime AddedAt { get; set; }
[JsonProperty("added_by")] public class SavedTrack
public PublicProfile AddedBy { get; set; } {
[JsonProperty("added_at")]
public DateTime AddedAt { get; set; }
[JsonProperty("track")] [JsonProperty("track")]
public FullTrack Track { get; set; } public FullTrack Track { get; set; }
}
[JsonProperty("is_local")] public class SavedAlbum
public bool IsLocal { get; set; } {
} [JsonProperty("added_at")]
public DateTime AddedAt { get; set; }
public class DeleteTrackUri [JsonProperty("album")]
{ public FullAlbum Album { get; set; }
/// <summary> }
/// Delete-Track Wrapper
/// </summary>
/// <param name="uri">An Spotify-URI</param>
/// <param name="positions">Optional positions</param>
public DeleteTrackUri(string uri, params int[] positions)
{
Positions = positions.ToList();
Uri = uri;
}
[JsonProperty("uri")] public class Cursor
public string Uri { get; set; } {
[JsonProperty("after")]
public string After { get; set; }
[JsonProperty("positions")] [JsonProperty("before")]
public List<int> Positions { get; set; } public string Before { get; set; }
}
public bool ShouldSerializePositions() public class Context
{ {
return Positions.Count > 0; [JsonProperty("type")]
} public string Type { get; set; }
}
public class Copyright [JsonProperty("href")]
{ public string Href { get; set; }
[JsonProperty("text")]
public string Text { get; set; }
[JsonProperty("type")] [JsonProperty("external_urls")]
public string Type { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
}
public class LinkedFrom [JsonProperty("uri")]
{ public string Uri { get; set; }
[JsonProperty("external_urls")] }
public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("href")]
public string Href { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("uri")]
public string Uri { get; set; }
}
public class SavedTrack
{
[JsonProperty("added_at")]
public DateTime AddedAt { get; set; }
[JsonProperty("track")]
public FullTrack Track { get; set; }
}
public class SavedAlbum
{
[JsonProperty("added_at")]
public DateTime AddedAt { get; set; }
[JsonProperty("album")]
public FullAlbum Album { get; set; }
}
public class Cursor
{
[JsonProperty("after")]
public string After { get; set; }
[JsonProperty("before")]
public string Before { get; set; }
}
public class Context
{
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("href")]
public string Href { get; set; }
[JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("uri")]
public string Uri { get; set; }
}
} }

View File

@ -1,10 +1,10 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class NewAlbumReleases : BasicModel public class NewAlbumReleases : BasicModel
{ {
[JsonProperty("albums")] [JsonProperty("albums")]
public Paging<SimpleAlbum> Albums { get; set; } public Paging<SimpleAlbum> Albums { get; set; }
} }
} }

View File

@ -1,39 +1,39 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class Paging<T> : BasicModel public class Paging<T> : BasicModel
{
[JsonProperty("href")]
public string Href { get; set; }
[JsonProperty("items")]
public List<T> Items { get; set; }
[JsonProperty("limit")]
public int Limit { get; set; }
[JsonProperty("next")]
public string Next { get; set; }
[JsonProperty("offset")]
public int Offset { get; set; }
[JsonProperty("previous")]
public string Previous { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
public bool HasNextPage()
{ {
[JsonProperty("href")] return Next != null;
public string Href { get; set; }
[JsonProperty("items")]
public List<T> Items { get; set; }
[JsonProperty("limit")]
public int Limit { get; set; }
[JsonProperty("next")]
public string Next { get; set; }
[JsonProperty("offset")]
public int Offset { get; set; }
[JsonProperty("previous")]
public string Previous { get; set; }
[JsonProperty("total")]
public int Total { get; set; }
public bool HasNextPage()
{
return Next != null;
}
public bool HasPreviousPage()
{
return Previous != null;
}
} }
public bool HasPreviousPage()
{
return Previous != null;
}
}
} }

View File

@ -1,17 +1,17 @@
using System; using System;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class PlayHistory : BasicModel public class PlayHistory : BasicModel
{ {
[JsonProperty("track")] [JsonProperty("track")]
public SimpleTrack Track { get; set; } public SimpleTrack Track { get; set; }
[JsonProperty("played_at")] [JsonProperty("played_at")]
public DateTime PlayedAt { get; set; } public DateTime PlayedAt { get; set; }
[JsonProperty("context")] [JsonProperty("context")]
public Context Context { get; set; } public Context Context { get; set; }
} }
} }

View File

@ -1,38 +1,38 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using SpotifyAPI.Web.Enums; using SpotifyAPI.Web.Enums;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class PlaybackContext : BasicModel public class PlaybackContext : BasicModel
{ {
[JsonProperty("device")] [JsonProperty("device")]
public Device Device { get; set; } public Device Device { get; set; }
[JsonProperty("repeat_state")] [JsonProperty("repeat_state")]
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public RepeatState RepeatState { get; set; } public RepeatState RepeatState { get; set; }
[JsonProperty("shuffle_state")] [JsonProperty("shuffle_state")]
public bool ShuffleState { get; set; } public bool ShuffleState { get; set; }
[JsonProperty("context")] [JsonProperty("context")]
public Context Context { get; set; } public Context Context { get; set; }
[JsonProperty("timestamp")] [JsonProperty("timestamp")]
public long Timestamp { get; set; } public long Timestamp { get; set; }
[JsonProperty("progress_ms")] [JsonProperty("progress_ms")]
public int ProgressMs { get; set; } public int ProgressMs { get; set; }
[JsonProperty("is_playing")] [JsonProperty("is_playing")]
public bool IsPlaying { get; set; } public bool IsPlaying { get; set; }
[JsonProperty("item")] [JsonProperty("item")]
public FullTrack Item { get; set; } public FullTrack Item { get; set; }
[JsonProperty("currently_playing_type")] [JsonProperty("currently_playing_type")]
[JsonConverter(typeof(StringEnumConverter))] [JsonConverter(typeof(StringEnumConverter))]
public TrackType CurrentlyPlayingType { get; set; } public TrackType CurrentlyPlayingType { get; set; }
} }
} }

View File

@ -1,44 +1,44 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class PrivateProfile : BasicModel public class PrivateProfile : BasicModel
{ {
[JsonProperty("birthdate")] [JsonProperty("birthdate")]
public string Birthdate { get; set; } public string Birthdate { get; set; }
[JsonProperty("country")] [JsonProperty("country")]
public string Country { get; set; } public string Country { get; set; }
[JsonProperty("display_name")] [JsonProperty("display_name")]
public string DisplayName { get; set; } public string DisplayName { get; set; }
[JsonProperty("email")] [JsonProperty("email")]
public string Email { get; set; } public string Email { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("followers")] [JsonProperty("followers")]
public Followers Followers { get; set; } public Followers Followers { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("images")] [JsonProperty("images")]
public List<Image> Images { get; set; } public List<Image> Images { get; set; }
[JsonProperty("product")] [JsonProperty("product")]
public string Product { get; set; } public string Product { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,32 +1,32 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class PublicProfile : BasicModel public class PublicProfile : BasicModel
{ {
[JsonProperty("display_name")] [JsonProperty("display_name")]
public string DisplayName { get; set; } public string DisplayName { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("followers")] [JsonProperty("followers")]
public Followers Followers { get; set; } public Followers Followers { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("images")] [JsonProperty("images")]
public List<Image> Images { get; set; } public List<Image> Images { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,25 +1,25 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class RecommendationSeed public class RecommendationSeed
{ {
[JsonProperty("afterFilteringSize")] [JsonProperty("afterFilteringSize")]
public int AfterFilteringSize { get; set; } public int AfterFilteringSize { get; set; }
[JsonProperty("afterRelinkingSize")] [JsonProperty("afterRelinkingSize")]
public int AfterRelinkingSize { get; set; } public int AfterRelinkingSize { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("initialPoolSize")] [JsonProperty("initialPoolSize")]
public int InitialPoolSize { get; set; } public int InitialPoolSize { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
} }
} }

View File

@ -1,11 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class RecommendationSeedGenres : BasicModel public class RecommendationSeedGenres : BasicModel
{ {
[JsonProperty("genres")] [JsonProperty("genres")]
public List<string> Genres { get; set; } public List<string> Genres { get; set; }
} }
} }

View File

@ -1,14 +1,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class Recommendations : BasicModel public class Recommendations : BasicModel
{ {
[JsonProperty("seeds")] [JsonProperty("seeds")]
public List<RecommendationSeed> Seeds { get; set; } public List<RecommendationSeed> Seeds { get; set; }
[JsonProperty("tracks")] [JsonProperty("tracks")]
public List<SimpleTrack> Tracks { get; set; } public List<SimpleTrack> Tracks { get; set; }
} }
} }

View File

@ -1,13 +1,13 @@
using System.Net; using System.Net;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class ResponseInfo public class ResponseInfo
{ {
public WebHeaderCollection Headers { get; set; } public WebHeaderCollection Headers { get; set; }
public HttpStatusCode StatusCode { get; set; } public HttpStatusCode StatusCode { get; set; }
public static readonly ResponseInfo Empty = new ResponseInfo(); public static readonly ResponseInfo Empty = new ResponseInfo();
} }
} }

View File

@ -1,19 +1,19 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SearchItem : BasicModel public class SearchItem : BasicModel
{ {
[JsonProperty("artists")] [JsonProperty("artists")]
public Paging<FullArtist> Artists { get; set; } public Paging<FullArtist> Artists { get; set; }
[JsonProperty("albums")] [JsonProperty("albums")]
public Paging<SimpleAlbum> Albums { get; set; } public Paging<SimpleAlbum> Albums { get; set; }
[JsonProperty("tracks")] [JsonProperty("tracks")]
public Paging<FullTrack> Tracks { get; set; } public Paging<FullTrack> Tracks { get; set; }
[JsonProperty("playlists")] [JsonProperty("playlists")]
public Paging<SimplePlaylist> Playlists { get; set; } public Paging<SimplePlaylist> Playlists { get; set; }
} }
} }

View File

@ -1,11 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SeveralAlbums : BasicModel public class SeveralAlbums : BasicModel
{ {
[JsonProperty("albums")] [JsonProperty("albums")]
public List<FullAlbum> Albums { get; set; } public List<FullAlbum> Albums { get; set; }
} }
} }

View File

@ -3,9 +3,9 @@ using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SeveralArtists : BasicModel public class SeveralArtists : BasicModel
{ {
[JsonProperty("artists")] [JsonProperty("artists")]
public List<FullArtist> Artists { get; set; } public List<FullArtist> Artists { get; set; }
} }
} }

View File

@ -1,11 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SeveralAudioFeatures : BasicModel public class SeveralAudioFeatures : BasicModel
{ {
[JsonProperty("audio_features")] [JsonProperty("audio_features")]
public List<AudioFeatures> AudioFeatures { get; set; } public List<AudioFeatures> AudioFeatures { get; set; }
} }
} }

View File

@ -1,11 +1,11 @@
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SeveralTracks : BasicModel public class SeveralTracks : BasicModel
{ {
[JsonProperty("tracks")] [JsonProperty("tracks")]
public List<FullTrack> Tracks { get; set; } public List<FullTrack> Tracks { get; set; }
} }
} }

View File

@ -1,53 +1,53 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SimpleAlbum : BasicModel public class SimpleAlbum : BasicModel
{ {
[JsonProperty("album_group")] [JsonProperty("album_group")]
public string AlbumGroup { get; set; } public string AlbumGroup { get; set; }
[JsonProperty("album_type")] [JsonProperty("album_type")]
public string AlbumType { get; set; } public string AlbumType { get; set; }
[JsonProperty("artists")] [JsonProperty("artists")]
public List<SimpleArtist> Artists { get; set; } public List<SimpleArtist> Artists { get; set; }
[JsonProperty("available_markets")] [JsonProperty("available_markets")]
public List<string> AvailableMarkets { get; set; } public List<string> AvailableMarkets { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("images")] [JsonProperty("images")]
public List<Image> Images { get; set; } public List<Image> Images { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("release_date")] [JsonProperty("release_date")]
public string ReleaseDate { get; set; } public string ReleaseDate { get; set; }
[JsonProperty("release_date_precision")] [JsonProperty("release_date_precision")]
public string ReleaseDatePrecision { get; set; } public string ReleaseDatePrecision { get; set; }
[JsonProperty("restrictions")] [JsonProperty("restrictions")]
public Dictionary<string, string> Restrictions { get; set; } public Dictionary<string, string> Restrictions { get; set; }
[JsonProperty("total_tracks")] [JsonProperty("total_tracks")]
public int TotalTracks { get; set; } public int TotalTracks { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,26 +1,26 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SimpleArtist : BasicModel public class SimpleArtist : BasicModel
{ {
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,44 +1,44 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SimplePlaylist : BasicModel public class SimplePlaylist : BasicModel
{ {
[JsonProperty("collaborative")] [JsonProperty("collaborative")]
public bool Collaborative { get; set; } public bool Collaborative { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternalUrls { get; set; } public Dictionary<string, string> ExternalUrls { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("images")] [JsonProperty("images")]
public List<Image> Images { get; set; } public List<Image> Images { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("owner")] [JsonProperty("owner")]
public PublicProfile Owner { get; set; } public PublicProfile Owner { get; set; }
[JsonProperty("public")] [JsonProperty("public")]
public bool Public { get; set; } public bool Public { get; set; }
[JsonProperty("snapshot_id")] [JsonProperty("snapshot_id")]
public string SnapshotId { get; set; } public string SnapshotId { get; set; }
[JsonProperty("tracks")] [JsonProperty("tracks")]
public PlaylistTrackCollection Tracks { get; set; } public PlaylistTrackCollection Tracks { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,50 +1,50 @@
using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class SimpleTrack : BasicModel public class SimpleTrack : BasicModel
{ {
[JsonProperty("artists")] [JsonProperty("artists")]
public List<SimpleArtist> Artists { get; set; } public List<SimpleArtist> Artists { get; set; }
[JsonProperty("available_markets")] [JsonProperty("available_markets")]
public List<string> AvailableMarkets { get; set; } public List<string> AvailableMarkets { get; set; }
[JsonProperty("disc_number")] [JsonProperty("disc_number")]
public int DiscNumber { get; set; } public int DiscNumber { get; set; }
[JsonProperty("duration_ms")] [JsonProperty("duration_ms")]
public int DurationMs { get; set; } public int DurationMs { get; set; }
[JsonProperty("explicit")] [JsonProperty("explicit")]
public bool Explicit { get; set; } public bool Explicit { get; set; }
[JsonProperty("external_urls")] [JsonProperty("external_urls")]
public Dictionary<string, string> ExternUrls { get; set; } public Dictionary<string, string> ExternUrls { get; set; }
[JsonProperty("href")] [JsonProperty("href")]
public string Href { get; set; } public string Href { get; set; }
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("name")] [JsonProperty("name")]
public string Name { get; set; } public string Name { get; set; }
[JsonProperty("preview_url")] [JsonProperty("preview_url")]
public string PreviewUrl { get; set; } public string PreviewUrl { get; set; }
[JsonProperty("track_number")] [JsonProperty("track_number")]
public int TrackNumber { get; set; } public int TrackNumber { get; set; }
[JsonProperty("restrictions")] [JsonProperty("restrictions")]
public Dictionary<string, string> Restrictions { get; set; } public Dictionary<string, string> Restrictions { get; set; }
[JsonProperty("type")] [JsonProperty("type")]
public string Type { get; set; } public string Type { get; set; }
[JsonProperty("uri")] [JsonProperty("uri")]
public string Uri { get; set; } public string Uri { get; set; }
} }
} }

View File

@ -1,10 +1,10 @@
using Newtonsoft.Json; using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class Snapshot : BasicModel public class Snapshot : BasicModel
{ {
[JsonProperty("snapshot_id")] [JsonProperty("snapshot_id")]
public string SnapshotId { get; set; } public string SnapshotId { get; set; }
} }
} }

View File

@ -1,47 +1,47 @@
using Newtonsoft.Json;
using System; using System;
using Newtonsoft.Json;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class Token public class Token
{
public Token()
{ {
public Token() CreateDate = DateTime.Now;
{
CreateDate = DateTime.Now;
}
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public double ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
[JsonProperty("error")]
public string Error { get; set; }
[JsonProperty("error_description")]
public string ErrorDescription { get; set; }
public DateTime CreateDate { get; set; }
/// <summary>
/// Checks if the token has expired
/// </summary>
/// <returns></returns>
public bool IsExpired()
{
return CreateDate.Add(TimeSpan.FromSeconds(ExpiresIn)) <= DateTime.Now;
}
public bool HasError()
{
return Error != null;
}
} }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("expires_in")]
public double ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
[JsonProperty("error")]
public string Error { get; set; }
[JsonProperty("error_description")]
public string ErrorDescription { get; set; }
public DateTime CreateDate { get; set; }
/// <summary>
/// Checks if the token has expired
/// </summary>
/// <returns></returns>
public bool IsExpired()
{
return CreateDate.Add(TimeSpan.FromSeconds(ExpiresIn)) <= DateTime.Now;
}
public bool HasError()
{
return Error != null;
}
}
} }

View File

@ -1,69 +1,69 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Reflection; using System.Reflection;
namespace SpotifyAPI.Web.Models namespace SpotifyAPI.Web.Models
{ {
public class TuneableTrack public class TuneableTrack
{
[String("acousticness")]
public float? Acousticness { get; set; }
[String("danceability")]
public float? Danceability { get; set; }
[String("duration_ms")]
public int? DurationMs { get; set; }
[String("energy")]
public float? Energy { get; set; }
[String("instrumentalness")]
public float? Instrumentalness { get; set; }
[String("key")]
public int? Key { get; set; }
[String("liveness")]
public float? Liveness { get; set; }
[String("loudness")]
public float? Loudness { get; set; }
[String("mode")]
public int? Mode { get; set; }
[String("popularity")]
public int? Popularity { get; set; }
[String("speechiness")]
public float? Speechiness { get; set; }
[String("tempo")]
public float? Tempo { get; set; }
[String("time_signature")]
public int? TimeSignature { get; set; }
[String("valence")]
public float? Valence { get; set; }
public string BuildUrlParams(string prefix)
{ {
[String("acousticness")] List<string> urlParams = new List<string>();
public float? Acousticness { get; set; } foreach (PropertyInfo info in GetType().GetProperties())
{
[String("danceability")] object value = info.GetValue(this);
public float? Danceability { get; set; } string name = info.GetCustomAttribute<StringAttribute>()?.Text;
if (name == null || value == null)
[String("duration_ms")] continue;
public int? DurationMs { get; set; } urlParams.Add(value is float valueAsFloat ?
$"{prefix}_{name}={valueAsFloat.ToString(CultureInfo.InvariantCulture)}" :
[String("energy")] $"{prefix}_{name}={value}");
public float? Energy { get; set; } }
if (urlParams.Count > 0)
[String("instrumentalness")] return "&" + string.Join("&", urlParams);
public float? Instrumentalness { get; set; } return "";
[String("key")]
public int? Key { get; set; }
[String("liveness")]
public float? Liveness { get; set; }
[String("loudness")]
public float? Loudness { get; set; }
[String("mode")]
public int? Mode { get; set; }
[String("popularity")]
public int? Popularity { get; set; }
[String("speechiness")]
public float? Speechiness { get; set; }
[String("tempo")]
public float? Tempo { get; set; }
[String("time_signature")]
public int? TimeSignature { get; set; }
[String("valence")]
public float? Valence { get; set; }
public string BuildUrlParams(string prefix)
{
List<string> urlParams = new List<string>();
foreach (PropertyInfo info in GetType().GetProperties())
{
object value = info.GetValue(this);
string name = info.GetCustomAttribute<StringAttribute>()?.Text;
if(name == null || value == null)
continue;
urlParams.Add(value is float valueAsFloat
? $"{prefix}_{name}={valueAsFloat.ToString(CultureInfo.InvariantCulture)}"
: $"{prefix}_{name}={value}");
}
if (urlParams.Count > 0)
return "&" + string.Join("&", urlParams);
return "";
}
} }
}
} }

View File

@ -1,97 +1,97 @@
using System; using System;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
namespace SpotifyAPI.Web namespace SpotifyAPI.Web
{ {
public class ProxyConfig public class ProxyConfig
{
public string Host { get; set; }
public int Port { get; set; } = 80;
public string Username { get; set; }
public string Password { get; set; }
/// <summary>
/// Whether to bypass the proxy server for local addresses.
/// </summary>
public bool BypassProxyOnLocal { get; set; }
public void Set(ProxyConfig proxyConfig)
{ {
public string Host { get; set; } Host = proxyConfig?.Host;
Port = proxyConfig?.Port ?? 80;
public int Port { get; set; } = 80; Username = proxyConfig?.Username;
Password = proxyConfig?.Password;
public string Username { get; set; } BypassProxyOnLocal = proxyConfig?.BypassProxyOnLocal ?? false;
public string Password { get; set; }
/// <summary>
/// Whether to bypass the proxy server for local addresses.
/// </summary>
public bool BypassProxyOnLocal { get; set; }
public void Set(ProxyConfig proxyConfig)
{
Host = proxyConfig?.Host;
Port = proxyConfig?.Port ?? 80;
Username = proxyConfig?.Username;
Password = proxyConfig?.Password;
BypassProxyOnLocal = proxyConfig?.BypassProxyOnLocal ?? false;
}
/// <summary>
/// Whether both <see cref="Host"/> and <see cref="Port"/> have valid values.
/// </summary>
/// <returns></returns>
public bool IsValid()
{
return !string.IsNullOrWhiteSpace(Host) && Port > 0;
}
/// <summary>
/// Create a <see cref="Uri"/> from the host and port number
/// </summary>
/// <returns>A URI</returns>
public Uri GetUri()
{
UriBuilder uriBuilder = new UriBuilder(Host)
{
Port = Port
};
return uriBuilder.Uri;
}
/// <summary>
/// Creates a <see cref="WebProxy"/> from the proxy details of this object.
/// </summary>
/// <returns>A <see cref="WebProxy"/> or <code>null</code> if the proxy details are invalid.</returns>
public WebProxy CreateWebProxy()
{
if (!IsValid())
return null;
WebProxy proxy = new WebProxy
{
Address = GetUri(),
UseDefaultCredentials = true,
BypassProxyOnLocal = BypassProxyOnLocal
};
if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
return proxy;
proxy.UseDefaultCredentials = false;
proxy.Credentials = new NetworkCredential(Username, Password);
return proxy;
}
public static HttpClientHandler CreateClientHandler(ProxyConfig proxyConfig = null)
{
HttpClientHandler clientHandler = new HttpClientHandler
{
PreAuthenticate = false,
UseDefaultCredentials = true,
UseProxy = false
};
if (string.IsNullOrWhiteSpace(proxyConfig?.Host)) return clientHandler;
WebProxy proxy = proxyConfig.CreateWebProxy();
clientHandler.UseProxy = true;
clientHandler.Proxy = proxy;
clientHandler.UseDefaultCredentials = proxy.UseDefaultCredentials;
clientHandler.PreAuthenticate = proxy.UseDefaultCredentials;
return clientHandler;
}
} }
/// <summary>
/// Whether both <see cref="Host"/> and <see cref="Port"/> have valid values.
/// </summary>
/// <returns></returns>
public bool IsValid()
{
return !string.IsNullOrWhiteSpace(Host) && Port > 0;
}
/// <summary>
/// Create a <see cref="Uri"/> from the host and port number
/// </summary>
/// <returns>A URI</returns>
public Uri GetUri()
{
UriBuilder uriBuilder = new UriBuilder(Host)
{
Port = Port
};
return uriBuilder.Uri;
}
/// <summary>
/// Creates a <see cref="WebProxy"/> from the proxy details of this object.
/// </summary>
/// <returns>A <see cref="WebProxy"/> or <code>null</code> if the proxy details are invalid.</returns>
public WebProxy CreateWebProxy()
{
if (!IsValid())
return null;
WebProxy proxy = new WebProxy
{
Address = GetUri(),
UseDefaultCredentials = true,
BypassProxyOnLocal = BypassProxyOnLocal
};
if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
return proxy;
proxy.UseDefaultCredentials = false;
proxy.Credentials = new NetworkCredential(Username, Password);
return proxy;
}
public static HttpClientHandler CreateClientHandler(ProxyConfig proxyConfig = null)
{
HttpClientHandler clientHandler = new HttpClientHandler
{
PreAuthenticate = false,
UseDefaultCredentials = true,
UseProxy = false
};
if (string.IsNullOrWhiteSpace(proxyConfig?.Host)) return clientHandler;
WebProxy proxy = proxyConfig.CreateWebProxy();
clientHandler.UseProxy = true;
clientHandler.Proxy = proxy;
clientHandler.UseDefaultCredentials = proxy.UseDefaultCredentials;
clientHandler.PreAuthenticate = proxy.UseDefaultCredentials;
return clientHandler;
}
}
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;

View File

@ -1,4 +1,3 @@
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
@ -6,199 +5,201 @@ using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using SpotifyAPI.Web.Models; using SpotifyAPI.Web.Models;
namespace SpotifyAPI.Web namespace SpotifyAPI.Web
{ {
internal class SpotifyWebClient : IClient internal class SpotifyWebClient : IClient
{
public JsonSerializerSettings JsonSettings { get; set; }
private readonly Encoding _encoding = Encoding.UTF8;
private readonly HttpClient _client;
private const string UnknownErrorJson = "{\"error\": { \"status\": 0, \"message\": \"SpotifyAPI.Web - Unkown Spotify Error\" }}";
public SpotifyWebClient(ProxyConfig proxyConfig = null)
{ {
public JsonSerializerSettings JsonSettings { get; set; } HttpClientHandler clientHandler = ProxyConfig.CreateClientHandler(proxyConfig);
private readonly Encoding _encoding = Encoding.UTF8; _client = new HttpClient(clientHandler);
private readonly HttpClient _client;
private const string UnknownErrorJson = "{\"error\": { \"status\": 0, \"message\": \"SpotifyAPI.Web - Unkown Spotify Error\" }}";
public SpotifyWebClient(ProxyConfig proxyConfig = null)
{
HttpClientHandler clientHandler = ProxyConfig.CreateClientHandler(proxyConfig);
_client = new HttpClient(clientHandler);
}
public Tuple<ResponseInfo, string> Download(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> raw = DownloadRaw(url, headers);
return new Tuple<ResponseInfo, string>(raw.Item1, raw.Item2.Length > 0 ? _encoding.GetString(raw.Item2) : "{}");
}
public async Task<Tuple<ResponseInfo, string>> DownloadAsync(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> raw = await DownloadRawAsync(url, headers).ConfigureAwait(false);
return new Tuple<ResponseInfo, string>(raw.Item1, raw.Item2.Length > 0 ? _encoding.GetString(raw.Item2) : "{}");
}
public Tuple<ResponseInfo, byte[]> DownloadRaw(string url, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
using (HttpResponseMessage response = Task.Run(() => _client.GetAsync(url)).Result)
{
return new Tuple<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result);
}
}
public async Task<Tuple<ResponseInfo, byte[]>> DownloadRawAsync(string url, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
using (HttpResponseMessage response = await _client.GetAsync(url).ConfigureAwait(false))
{
return new Tuple<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, await response.Content.ReadAsByteArrayAsync());
}
}
public Tuple<ResponseInfo, T> DownloadJson<T>(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = Download(url, headers);
try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public async Task<Tuple<ResponseInfo, T>> DownloadJsonAsync<T>(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = await DownloadAsync(url, headers).ConfigureAwait(false);try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public Tuple<ResponseInfo, string> Upload(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> data = UploadRaw(url, body, method, headers);
return new Tuple<ResponseInfo, string>(data.Item1, data.Item2.Length > 0 ? _encoding.GetString(data.Item2) : "{}");
}
public async Task<Tuple<ResponseInfo, string>> UploadAsync(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> data = await UploadRawAsync(url, body, method, headers).ConfigureAwait(false);
return new Tuple<ResponseInfo, string>(data.Item1, data.Item2.Length > 0 ? _encoding.GetString(data.Item2) : "{}");
}
public Tuple<ResponseInfo, byte[]> UploadRaw(string url, string body, string method, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
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<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result);
}
}
public async Task<Tuple<ResponseInfo, byte[]>> UploadRawAsync(string url, string body, string method, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(method), url)
{
Content = new StringContent(body, _encoding)
};
using (HttpResponseMessage response = await _client.SendAsync(message))
{
return new Tuple<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, await response.Content.ReadAsByteArrayAsync());
}
}
public Tuple<ResponseInfo, T> UploadJson<T>(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = Upload(url, body, method, headers);
try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public async Task<Tuple<ResponseInfo, T>> UploadJsonAsync<T>(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = await UploadAsync(url, body, method, headers).ConfigureAwait(false);
try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public void Dispose()
{
_client.Dispose();
GC.SuppressFinalize(this);
}
private static WebHeaderCollection ConvertHeaders(HttpResponseHeaders headers)
{
WebHeaderCollection newHeaders = new WebHeaderCollection();
foreach (KeyValuePair<string, IEnumerable<string>> headerPair in headers)
{
foreach (string headerValue in headerPair.Value)
{
newHeaders.Add(headerPair.Key, headerValue);
}
}
return newHeaders;
}
private void AddHeaders(Dictionary<string,string> headers)
{
_client.DefaultRequestHeaders.Clear();
foreach (KeyValuePair<string, string> headerPair in headers)
{
_client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value);
}
}
} }
public Tuple<ResponseInfo, string> Download(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> raw = DownloadRaw(url, headers);
return new Tuple<ResponseInfo, string>(raw.Item1, raw.Item2.Length > 0 ? _encoding.GetString(raw.Item2) : "{}");
}
public async Task<Tuple<ResponseInfo, string>> DownloadAsync(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> raw = await DownloadRawAsync(url, headers).ConfigureAwait(false);
return new Tuple<ResponseInfo, string>(raw.Item1, raw.Item2.Length > 0 ? _encoding.GetString(raw.Item2) : "{}");
}
public Tuple<ResponseInfo, byte[]> DownloadRaw(string url, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
using(HttpResponseMessage response = Task.Run(() => _client.GetAsync(url)).Result)
{
return new Tuple<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result);
}
}
public async Task<Tuple<ResponseInfo, byte[]>> DownloadRawAsync(string url, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
using(HttpResponseMessage response = await _client.GetAsync(url).ConfigureAwait(false))
{
return new Tuple<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, await response.Content.ReadAsByteArrayAsync());
}
}
public Tuple<ResponseInfo, T> DownloadJson<T>(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = Download(url, headers);
try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public async Task<Tuple<ResponseInfo, T>> DownloadJsonAsync<T>(string url, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = await DownloadAsync(url, headers).ConfigureAwait(false);
try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public Tuple<ResponseInfo, string> Upload(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> data = UploadRaw(url, body, method, headers);
return new Tuple<ResponseInfo, string>(data.Item1, data.Item2.Length > 0 ? _encoding.GetString(data.Item2) : "{}");
}
public async Task<Tuple<ResponseInfo, string>> UploadAsync(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, byte[]> data = await UploadRawAsync(url, body, method, headers).ConfigureAwait(false);
return new Tuple<ResponseInfo, string>(data.Item1, data.Item2.Length > 0 ? _encoding.GetString(data.Item2) : "{}");
}
public Tuple<ResponseInfo, byte[]> UploadRaw(string url, string body, string method, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
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<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, Task.Run(() => response.Content.ReadAsByteArrayAsync()).Result);
}
}
public async Task<Tuple<ResponseInfo, byte[]>> UploadRawAsync(string url, string body, string method, Dictionary<string, string> headers = null)
{
if (headers != null)
{
AddHeaders(headers);
}
HttpRequestMessage message = new HttpRequestMessage(new HttpMethod(method), url)
{
Content = new StringContent(body, _encoding)
};
using(HttpResponseMessage response = await _client.SendAsync(message))
{
return new Tuple<ResponseInfo, byte[]>(new ResponseInfo
{
StatusCode = response.StatusCode,
Headers = ConvertHeaders(response.Headers)
}, await response.Content.ReadAsByteArrayAsync());
}
}
public Tuple<ResponseInfo, T> UploadJson<T>(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = Upload(url, body, method, headers);
try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public async Task<Tuple<ResponseInfo, T>> UploadJsonAsync<T>(string url, string body, string method, Dictionary<string, string> headers = null)
{
Tuple<ResponseInfo, string> response = await UploadAsync(url, body, method, headers).ConfigureAwait(false);
try
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(response.Item2, JsonSettings));
}
catch (JsonException)
{
return new Tuple<ResponseInfo, T>(response.Item1, JsonConvert.DeserializeObject<T>(UnknownErrorJson, JsonSettings));
}
}
public void Dispose()
{
_client.Dispose();
GC.SuppressFinalize(this);
}
private static WebHeaderCollection ConvertHeaders(HttpResponseHeaders headers)
{
WebHeaderCollection newHeaders = new WebHeaderCollection();
foreach (KeyValuePair<string, IEnumerable<string>> headerPair in headers)
{
foreach (string headerValue in headerPair.Value)
{
newHeaders.Add(headerPair.Key, headerValue);
}
}
return newHeaders;
}
private void AddHeaders(Dictionary<string, string> headers)
{
_client.DefaultRequestHeaders.Clear();
foreach (KeyValuePair<string, string> headerPair in headers)
{
_client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value);
}
}
}
} }

View File

@ -1,41 +1,41 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
namespace SpotifyAPI.Web namespace SpotifyAPI.Web
{ {
public static class Util public static class Util
{
public static string GetStringAttribute<T>(this T en, string separator = "") where T : struct, IConvertible
{ {
public static string GetStringAttribute<T>(this T en, string separator = "") where T : struct, IConvertible Enum e = (Enum) (object) en;
{ IEnumerable<StringAttribute> attributes =
Enum e = (Enum)(object)en; Enum.GetValues(typeof(T))
IEnumerable<StringAttribute> attributes = .Cast<T>()
Enum.GetValues(typeof(T)) .Where(v => e.HasFlag((Enum) (object) v))
.Cast<T>() .Select(v => typeof(T).GetField(v.ToString(CultureInfo.InvariantCulture)))
.Where(v => e.HasFlag((Enum)(object)v)) .Select(f => f.GetCustomAttributes(typeof(StringAttribute), false) [0])
.Select(v => typeof(T).GetField(v.ToString(CultureInfo.InvariantCulture))) .Cast<StringAttribute>();
.Select(f => f.GetCustomAttributes(typeof(StringAttribute), false)[0])
.Cast<StringAttribute>();
List<string> list = new List<string>(); List<string> list = new List<string>();
attributes.ToList().ForEach(element => list.Add(element.Text)); attributes.ToList().ForEach(element => list.Add(element.Text));
return string.Join(separator, list); return string.Join(separator, list);
}
public static long ToUnixTimeMillisecondsPoly(this DateTime time)
{
return (long)time.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
}
} }
public sealed class StringAttribute : Attribute public static long ToUnixTimeMillisecondsPoly(this DateTime time)
{ {
public string Text { get; set; } return (long) time.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds;
public StringAttribute(string text)
{
Text = text;
}
} }
}
public sealed class StringAttribute : Attribute
{
public string Text { get; set; }
public StringAttribute(string text)
{
Text = text;
}
}
} }