mirror of
https://github.com/Sarsoo/Spotify.NET.git
synced 2024-12-23 14:46:26 +00:00
Started with Unit Tests and added .editorconfig for syntax settings
Disabled examples for now
This commit is contained in:
parent
2c4463529b
commit
9f6729ad60
28
.editorconfig
Normal file
28
.editorconfig
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
|
||||||
|
[*.{cs,csx,vb,vbx}]]
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8-bom
|
||||||
|
|
||||||
|
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{cs,vb}]
|
||||||
|
# Sort using and Import directives with System.* appearing first
|
||||||
|
dotnet_sort_system_directives_first = true
|
||||||
|
dotnet_style_require_accessibility_modifiers = always:warning
|
||||||
|
|
||||||
|
# Avoid "this." and "Me." if not necessary
|
||||||
|
dotnet_style_qualification_for_field = false:warning
|
||||||
|
dotnet_style_qualification_for_property = false:warning
|
||||||
|
dotnet_style_qualification_for_method = false:warning
|
||||||
|
dotnet_style_qualification_for_event = false:warning
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_prefer_braces = true:warning
|
||||||
|
csharp_prefer_simple_using_statement = true:warning
|
@ -1,39 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace SpotifyAPI.Web.Auth
|
|
||||||
{
|
|
||||||
internal static class AuthUtil
|
|
||||||
{
|
|
||||||
public static bool OpenBrowser(string url)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
#if NETSTANDARD2_0
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
url = url.Replace("&", "^&");
|
|
||||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
|
|
||||||
}
|
|
||||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
||||||
{
|
|
||||||
Process.Start("xdg-open", url);
|
|
||||||
}
|
|
||||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
||||||
{
|
|
||||||
Process.Start("open", url);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
url = url.Replace("&", "^&");
|
|
||||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
|
|
||||||
#endif
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SpotifyAPI.Web.Enums;
|
|
||||||
using SpotifyAPI.Web.Models;
|
|
||||||
using Unosquare.Labs.EmbedIO;
|
|
||||||
using Unosquare.Labs.EmbedIO.Constants;
|
|
||||||
using Unosquare.Labs.EmbedIO.Modules;
|
|
||||||
|
|
||||||
namespace SpotifyAPI.Web.Auth
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SpotifyAPI.Web.Models;
|
|
||||||
|
|
||||||
namespace SpotifyAPI.Web.Auth
|
|
||||||
{
|
|
||||||
public class CredentialsAuth
|
|
||||||
{
|
|
||||||
public string ClientSecret { get; set; }
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using SpotifyAPI.Web.Enums;
|
|
||||||
using SpotifyAPI.Web.Models;
|
|
||||||
using Unosquare.Labs.EmbedIO;
|
|
||||||
using Unosquare.Labs.EmbedIO.Constants;
|
|
||||||
using Unosquare.Labs.EmbedIO.Modules;
|
|
||||||
|
|
||||||
namespace SpotifyAPI.Web.Auth
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
ClientId = clientId;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void AdaptWebServer(WebServer webServer)
|
|
||||||
{
|
|
||||||
webServer.Module<WebApiModule>().RegisterController<ImplicitGrantAuthController>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{ }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using SpotifyAPI.Web.Enums;
|
|
||||||
using Unosquare.Labs.EmbedIO;
|
|
||||||
using Unosquare.Labs.EmbedIO.Modules;
|
|
||||||
|
|
||||||
namespace SpotifyAPI.Web.Auth
|
|
||||||
{
|
|
||||||
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 = "")
|
|
||||||
{
|
|
||||||
_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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,218 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SpotifyAPI.Web.Enums;
|
|
||||||
using SpotifyAPI.Web.Models;
|
|
||||||
using Unosquare.Labs.EmbedIO;
|
|
||||||
using Unosquare.Labs.EmbedIO.Constants;
|
|
||||||
using Unosquare.Labs.EmbedIO.Modules;
|
|
||||||
|
|
||||||
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>
|
|
||||||
/// 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>
|
|
||||||
/// 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
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,280 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using SpotifyAPI.Web.Enums;
|
|
||||||
using SpotifyAPI.Web.Models;
|
|
||||||
|
|
||||||
namespace SpotifyAPI.Web.Auth
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a <see cref="SpotifyWebAPI"/> using the TokenSwapAuth process.
|
|
||||||
/// </summary>
|
|
||||||
public class TokenSwapWebAPIFactory
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 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>
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
AutoRefresh = autoRefresh;
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,18 +10,18 @@ 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 IActionResult Index()
|
||||||
{
|
{
|
||||||
var accessToken = await HttpContext.GetTokenAsync("Spotify", "access_token");
|
// var accessToken = await HttpContext.GetTokenAsync("Spotify", "access_token");
|
||||||
SpotifyWebAPI api = new SpotifyWebAPI
|
// SpotifyWebAPI api = new SpotifyWebAPI
|
||||||
{
|
// {
|
||||||
AccessToken = accessToken,
|
// AccessToken = accessToken,
|
||||||
TokenType = "Bearer"
|
// 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 = null });
|
||||||
}
|
}
|
||||||
|
|
||||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||||
@ -30,4 +30,4 @@ namespace SpotifyAPI.Web.Examples.ASP.Controllers
|
|||||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,6 @@ namespace SpotifyAPI.Web.Examples.ASP.Models
|
|||||||
{
|
{
|
||||||
public class IndexModel
|
public class IndexModel
|
||||||
{
|
{
|
||||||
public Paging<SavedTrack> SavedTracks;
|
public Paging<object> SavedTracks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,10 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
You have @Model.SavedTracks.Total saved tracks in your library! Here are 50 of them:
|
You have @Model.SavedTracks.Total saved tracks in your library! Here are 50 of them:
|
||||||
|
|
||||||
<ul>
|
@* <ul>
|
||||||
@foreach (var item in Model.SavedTracks.Items)
|
@foreach (var item in Model.SavedTracks.Items)
|
||||||
{
|
{
|
||||||
<li>@item.Track.Name</li>
|
<li>@item.Track.Name</li>
|
||||||
}
|
}
|
||||||
</ul>
|
</ul> *@
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,75 +1,72 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpotifyAPI.Web.Auth;
|
|
||||||
using SpotifyAPI.Web.Enums;
|
|
||||||
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 _clientId = ""; //"";
|
||||||
private static string _secretId = ""; //"";
|
// private static string _secretId = ""; //"";
|
||||||
|
|
||||||
// ReSharper disable once UnusedParameter.Local
|
// ReSharper disable once UnusedParameter.Local
|
||||||
public static void Main(string[] args)
|
// public static void Main(string[] args)
|
||||||
{
|
// {
|
||||||
_clientId = string.IsNullOrEmpty(_clientId) ?
|
// _clientId = string.IsNullOrEmpty(_clientId) ?
|
||||||
Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID") :
|
// Environment.GetEnvironmentVariable("SPOTIFY_CLIENT_ID") :
|
||||||
_clientId;
|
// _clientId;
|
||||||
|
|
||||||
_secretId = string.IsNullOrEmpty(_secretId) ?
|
// _secretId = string.IsNullOrEmpty(_secretId) ?
|
||||||
Environment.GetEnvironmentVariable("SPOTIFY_SECRET_ID") :
|
// Environment.GetEnvironmentVariable("SPOTIFY_SECRET_ID") :
|
||||||
_secretId;
|
// _secretId;
|
||||||
|
|
||||||
Console.WriteLine("####### Spotify API Example #######");
|
// Console.WriteLine("####### Spotify API Example #######");
|
||||||
Console.WriteLine("This example uses AuthorizationCodeAuth.");
|
// Console.WriteLine("This example uses AuthorizationCodeAuth.");
|
||||||
Console.WriteLine(
|
// Console.WriteLine(
|
||||||
"Tip: If you want to supply your ClientID and SecretId beforehand, use env variables (SPOTIFY_CLIENT_ID and SPOTIFY_SECRET_ID)");
|
// "Tip: If you want to supply your ClientID and SecretId beforehand, use env variables (SPOTIFY_CLIENT_ID and SPOTIFY_SECRET_ID)");
|
||||||
|
|
||||||
var auth =
|
// var auth =
|
||||||
new AuthorizationCodeAuth(_clientId, _secretId, "http://localhost:4002", "http://localhost:4002",
|
// new AuthorizationCodeAuth(_clientId, _secretId, "http://localhost:4002", "http://localhost:4002",
|
||||||
Scope.PlaylistReadPrivate | Scope.PlaylistReadCollaborative);
|
// Scope.PlaylistReadPrivate | Scope.PlaylistReadCollaborative);
|
||||||
auth.AuthReceived += AuthOnAuthReceived;
|
// auth.AuthReceived += AuthOnAuthReceived;
|
||||||
auth.Start();
|
// auth.Start();
|
||||||
auth.OpenBrowser();
|
// auth.OpenBrowser();
|
||||||
|
|
||||||
Console.ReadLine();
|
// Console.ReadLine();
|
||||||
auth.Stop(0);
|
// auth.Stop(0);
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
private static async void AuthOnAuthReceived(object sender, AuthorizationCode payload)
|
// private static async void AuthOnAuthReceived(object sender, AuthorizationCode payload)
|
||||||
{
|
// {
|
||||||
var auth = (AuthorizationCodeAuth) sender;
|
// var auth = (AuthorizationCodeAuth)sender;
|
||||||
auth.Stop();
|
// auth.Stop();
|
||||||
|
|
||||||
Token token = await auth.ExchangeCode(payload.Code);
|
// Token token = await auth.ExchangeCode(payload.Code);
|
||||||
var api = new SpotifyWebAPI
|
// var api = new SpotifyWebAPI
|
||||||
{
|
// {
|
||||||
AccessToken = token.AccessToken,
|
// AccessToken = token.AccessToken,
|
||||||
TokenType = token.TokenType
|
// TokenType = token.TokenType
|
||||||
};
|
// };
|
||||||
await PrintUsefulData(api);
|
// await PrintUsefulData(api);
|
||||||
}
|
// }
|
||||||
|
|
||||||
private static async Task PrintAllPlaylistTracks(SpotifyWebAPI api, Paging<SimplePlaylist> playlists)
|
// private static async Task PrintAllPlaylistTracks(SpotifyWebAPI api, Paging<SimplePlaylist> playlists)
|
||||||
{
|
// {
|
||||||
if (playlists.Items == null) return;
|
// if (playlists.Items == null) return;
|
||||||
|
|
||||||
playlists.Items.ForEach(playlist => Console.WriteLine($"- {playlist.Name}"));
|
// playlists.Items.ForEach(playlist => Console.WriteLine($"- {playlist.Name}"));
|
||||||
if (playlists.HasNextPage())
|
// if (playlists.HasNextPage())
|
||||||
await PrintAllPlaylistTracks(api, await api.GetNextPageAsync(playlists));
|
// await PrintAllPlaylistTracks(api, await api.GetNextPageAsync(playlists));
|
||||||
}
|
// }
|
||||||
|
|
||||||
private static async Task PrintUsefulData(SpotifyWebAPI api)
|
// private static async Task PrintUsefulData(SpotifyWebAPI api)
|
||||||
{
|
// {
|
||||||
PrivateProfile profile = await api.GetPrivateProfileAsync();
|
// PrivateProfile profile = await api.GetPrivateProfileAsync();
|
||||||
string name = string.IsNullOrEmpty(profile.DisplayName) ? profile.Id : profile.DisplayName;
|
// string name = string.IsNullOrEmpty(profile.DisplayName) ? profile.Id : profile.DisplayName;
|
||||||
Console.WriteLine($"Hello there, {name}!");
|
// Console.WriteLine($"Hello there, {name}!");
|
||||||
|
|
||||||
Console.WriteLine("Your playlists:");
|
// Console.WriteLine("Your playlists:");
|
||||||
await PrintAllPlaylistTracks(api, api.GetUserPlaylists(profile.Id));
|
// await PrintAllPlaylistTracks(api, api.GetUserPlaylists(profile.Id));
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
SpotifyAPI.Web.Tests/Http/NetHTTPClientTest.cs
Normal file
20
SpotifyAPI.Web.Tests/Http/NetHTTPClientTest.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class NetHTTPClientTest
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class BuildRequestsMethod
|
||||||
|
{
|
||||||
|
public void AddsHeaders()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
SpotifyAPI.Web.Tests/Http/NewtonsoftJSONSerializerTest.cs
Normal file
90
SpotifyAPI.Web.Tests/Http/NewtonsoftJSONSerializerTest.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using SpotifyAPI.Web.Http;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Tests
|
||||||
|
{
|
||||||
|
public class NewtonsoftJSONSerializerTest
|
||||||
|
{
|
||||||
|
|
||||||
|
public static IEnumerable<object> DontSerializeTestSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new TestCaseData(null);
|
||||||
|
yield return new TestCaseData("string");
|
||||||
|
yield return new TestCaseData(new MemoryStream(Encoding.UTF8.GetBytes("string")));
|
||||||
|
yield return new TestCaseData(new StringContent("string"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(DontSerializeTestSource))]
|
||||||
|
public void SerializeRequest_SkipsAlreadySerialized(object input)
|
||||||
|
{
|
||||||
|
var serializer = new NewtonsoftJSONSerializer();
|
||||||
|
var request = new Mock<IRequest>();
|
||||||
|
request.SetupGet(r => r.Body).Returns(input);
|
||||||
|
|
||||||
|
serializer.SerializeRequest(request.Object);
|
||||||
|
|
||||||
|
Assert.AreEqual(input, request.Object.Body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object> SerializeTestSource
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
yield return new TestCaseData(new { Uppercase = true }, "{\"uppercase\":true}");
|
||||||
|
yield return new TestCaseData(new { CamelCase = true }, "{\"camel_case\":true}");
|
||||||
|
yield return new TestCaseData(new { CamelCase = true, UPPER = true }, "{\"camel_case\":true,\"upper\":true}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(SerializeTestSource))]
|
||||||
|
public void SerializeRequest_CorrectNaming(object input, string result)
|
||||||
|
{
|
||||||
|
var serializer = new NewtonsoftJSONSerializer();
|
||||||
|
var request = new Mock<IRequest>();
|
||||||
|
request.SetupGet(r => r.Body).Returns(input);
|
||||||
|
|
||||||
|
serializer.SerializeRequest(request.Object);
|
||||||
|
|
||||||
|
request.VerifySet(r => r.Body = result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase]
|
||||||
|
public void DeserializeResponse_SkipsNonJson()
|
||||||
|
{
|
||||||
|
var serializer = new NewtonsoftJSONSerializer();
|
||||||
|
var response = new Mock<IResponse>();
|
||||||
|
response.SetupGet(r => r.Body).Returns("hello");
|
||||||
|
response.SetupGet(r => r.ContentType).Returns("media/mp4");
|
||||||
|
|
||||||
|
IAPIResponse<object> apiResonse = serializer.DeserializeResponse<object>(response.Object);
|
||||||
|
Assert.AreEqual(apiResonse.Body, null);
|
||||||
|
Assert.AreEqual(apiResonse.Response, response.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase]
|
||||||
|
public void DeserializeResponse_HandlesJson()
|
||||||
|
{
|
||||||
|
var serializer = new NewtonsoftJSONSerializer();
|
||||||
|
var response = new Mock<IResponse>();
|
||||||
|
response.SetupGet(r => r.Body).Returns("{\"hello_world\": false}");
|
||||||
|
response.SetupGet(r => r.ContentType).Returns("application/json");
|
||||||
|
|
||||||
|
IAPIResponse<TestResponseObject> apiResonse = serializer.DeserializeResponse<TestResponseObject>(response.Object);
|
||||||
|
Assert.AreEqual(apiResonse.Body?.HelloWorld, false);
|
||||||
|
Assert.AreEqual(apiResonse.Response, response.Object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestResponseObject
|
||||||
|
{
|
||||||
|
public bool HelloWorld { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
SpotifyAPI.Web.Tests/Http/TokenHeaderAuthenticatorTest.cs
Normal file
22
SpotifyAPI.Web.Tests/Http/TokenHeaderAuthenticatorTest.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Moq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using SpotifyAPI.Web.Http;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TokenHeaderAuthenticatorTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void Apply_AddsCorrectHeader()
|
||||||
|
{
|
||||||
|
var authenticator = new TokenHeaderAuthenticator("MyToken", "Bearer");
|
||||||
|
var request = new Mock<IRequest>();
|
||||||
|
request.SetupGet(r => r.Headers).Returns(new Dictionary<string, string>());
|
||||||
|
|
||||||
|
authenticator.Apply(request.Object);
|
||||||
|
Assert.AreEqual(request.Object.Headers["Authorization"], "Bearer MyToken");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,20 +2,20 @@ using System;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
namespace SpotifyAPI.Web.Tests
|
namespace SpotifyAPI.Web
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class Test
|
public class Testing
|
||||||
{
|
{
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Testing()
|
public async Task TestingYo()
|
||||||
{
|
{
|
||||||
var token = "";
|
var config = SpotifyClientConfig.CreateDefault("BQAODnY4uqYj_KCddlDm10KLPDZSpZhVUtMDjdh1zfG-xd5pAV3htRjnaGfO7ob92HHzNP05a-4mDnts337gdnZlRtjrDPnuWNFx75diY540H0cD1bS9UzI5cfO27N2O6lmzKb_jAYTaRoqPKHoG93KGiXxwg4vblGKSBY1vIloP");
|
||||||
|
var spotify = new SpotifyClient(config);
|
||||||
|
|
||||||
var spotify = new SpotifyClient(token);
|
var playlists = await spotify.Browse.GetCategoryPlaylists("toplists", new CategoriesPlaylistsRequest() { Offset = 1 });
|
||||||
|
Console.WriteLine(playlists.Playlists.Items[0].Name);
|
||||||
var categories = await spotify.Browse.GetCategories();
|
|
||||||
var playlists = await spotify.Browse.GetCategoryPlaylists(categories.Categories.Items[0].Id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace SpotifyAPI.Web.Tests
|
|||||||
|
|
||||||
var user = "wizzler";
|
var user = "wizzler";
|
||||||
var formatter = new URIParameterFormatProvider();
|
var formatter = new URIParameterFormatProvider();
|
||||||
Func<FormattableString, string> func = (FormattableString str) => str.ToString(formatter);
|
string func(FormattableString str) => str.ToString(formatter);
|
||||||
|
|
||||||
Assert.AreEqual(expected, func($"/users/{user}"));
|
Assert.AreEqual(expected, func($"/users/{user}"));
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ namespace SpotifyAPI.Web.Tests
|
|||||||
|
|
||||||
var user = " wizzler";
|
var user = " wizzler";
|
||||||
var formatter = new URIParameterFormatProvider();
|
var formatter = new URIParameterFormatProvider();
|
||||||
Func<FormattableString, string> func = (FormattableString str) => str.ToString(formatter);
|
string func(FormattableString str) => str.ToString(formatter);
|
||||||
|
|
||||||
Assert.AreEqual(expected, func($"/users/{user}"));
|
Assert.AreEqual(expected, func($"/users/{user}"));
|
||||||
}
|
}
|
||||||
|
@ -51,5 +51,12 @@ namespace SpotifyAPI.Web
|
|||||||
|
|
||||||
return API.Get<CategoryPlaylistsResponse>(URLs.CategoryPlaylists(categoryId), request.BuildQueryParams());
|
return API.Get<CategoryPlaylistsResponse>(URLs.CategoryPlaylists(categoryId), request.BuildQueryParams());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task<RecommendationsResponse> GetRecommendations(RecommendationsRequest request)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(request, nameof(request));
|
||||||
|
|
||||||
|
return API.Get<RecommendationsResponse>(URLs.Recommendations(), request.BuildQueryParams());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,5 +12,7 @@ namespace SpotifyAPI.Web
|
|||||||
|
|
||||||
Task<CategoryPlaylistsResponse> GetCategoryPlaylists(string categoryId);
|
Task<CategoryPlaylistsResponse> GetCategoryPlaylists(string categoryId);
|
||||||
Task<CategoryPlaylistsResponse> GetCategoryPlaylists(string categoryId, CategoriesPlaylistsRequest request);
|
Task<CategoryPlaylistsResponse> GetCategoryPlaylists(string categoryId, CategoriesPlaylistsRequest request);
|
||||||
|
|
||||||
|
Task<RecommendationsResponse> GetRecommendations(RecommendationsRequest request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
namespace SpotifyAPI.Web
|
namespace SpotifyAPI.Web
|
||||||
{
|
{
|
||||||
interface ISpotifyClient
|
public interface ISpotifyClient
|
||||||
{
|
{
|
||||||
IUserProfileClient UserProfile { get; }
|
IUserProfileClient UserProfile { get; }
|
||||||
|
|
||||||
|
@ -4,21 +4,17 @@ namespace SpotifyAPI.Web
|
|||||||
{
|
{
|
||||||
public class SpotifyClient : ISpotifyClient
|
public class SpotifyClient : ISpotifyClient
|
||||||
{
|
{
|
||||||
private IAPIConnector _apiConnector;
|
private readonly IAPIConnector _apiConnector;
|
||||||
|
|
||||||
public SpotifyClient(string token, string tokenType = "Bearer") :
|
public SpotifyClient(string token, string tokenType = "Bearer") :
|
||||||
this(new TokenHeaderAuthenticator(token, tokenType))
|
this(SpotifyClientConfig.CreateDefault(token, tokenType))
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public SpotifyClient(IAuthenticator authenticator) :
|
public SpotifyClient(SpotifyClientConfig config)
|
||||||
this(new APIConnector(SpotifyUrls.API_V1, authenticator))
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public SpotifyClient(IAPIConnector apiConnector)
|
|
||||||
{
|
{
|
||||||
Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector));
|
Ensure.ArgumentNotNull(config, nameof(config));
|
||||||
|
|
||||||
_apiConnector = apiConnector;
|
_apiConnector = config.CreateAPIConnector();
|
||||||
UserProfile = new UserProfileClient(_apiConnector);
|
UserProfile = new UserProfileClient(_apiConnector);
|
||||||
Browse = new BrowseClient(_apiConnector);
|
Browse = new BrowseClient(_apiConnector);
|
||||||
}
|
}
|
||||||
|
86
SpotifyAPI.Web/Clients/SpotifyClientConfig.cs
Normal file
86
SpotifyAPI.Web/Clients/SpotifyClientConfig.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using SpotifyAPI.Web.Http;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public class SpotifyClientConfig
|
||||||
|
{
|
||||||
|
public Uri BaseAddress { get; }
|
||||||
|
public IAuthenticator Authenticator { get; }
|
||||||
|
public IJSONSerializer JSONSerializer { get; }
|
||||||
|
public IHTTPClient HTTPClient { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This config spefies the internal parts of the SpotifyClient.
|
||||||
|
/// In apps where multiple different access tokens are used, one should create a default config and then use
|
||||||
|
/// <see cref="WithToken" /> or <see cref="WithAuthenticator" /> to specify the auth details.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="baseAddress"></param>
|
||||||
|
/// <param name="authenticator"></param>
|
||||||
|
/// <param name="jsonSerializer"></param>
|
||||||
|
/// <param name="httpClient"></param>
|
||||||
|
public SpotifyClientConfig(
|
||||||
|
Uri baseAddress,
|
||||||
|
IAuthenticator authenticator,
|
||||||
|
IJSONSerializer jsonSerializer,
|
||||||
|
IHTTPClient httpClient
|
||||||
|
)
|
||||||
|
{
|
||||||
|
BaseAddress = baseAddress;
|
||||||
|
Authenticator = authenticator;
|
||||||
|
JSONSerializer = jsonSerializer;
|
||||||
|
HTTPClient = httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal IAPIConnector CreateAPIConnector()
|
||||||
|
{
|
||||||
|
Ensure.PropertyNotNull(BaseAddress, nameof(BaseAddress));
|
||||||
|
Ensure.PropertyNotNull(Authenticator, nameof(Authenticator),
|
||||||
|
". Use WithToken or WithAuthenticator to specify a authentication");
|
||||||
|
Ensure.PropertyNotNull(JSONSerializer, nameof(JSONSerializer));
|
||||||
|
Ensure.PropertyNotNull(HTTPClient, nameof(HTTPClient));
|
||||||
|
|
||||||
|
return new APIConnector(BaseAddress, Authenticator, JSONSerializer, HTTPClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpotifyClientConfig WithToken(string token, string tokenType = "Bearer")
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(token, nameof(token));
|
||||||
|
|
||||||
|
return WithAuthenticator(new TokenHeaderAuthenticator(token, tokenType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpotifyClientConfig WithAuthenticator(IAuthenticator authenticator)
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(authenticator, nameof(authenticator));
|
||||||
|
|
||||||
|
return new SpotifyClientConfig(BaseAddress, Authenticator, JSONSerializer, HTTPClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SpotifyClientConfig CreateDefault(string token, string tokenType = "Bearer")
|
||||||
|
{
|
||||||
|
Ensure.ArgumentNotNull(token, nameof(token));
|
||||||
|
|
||||||
|
return CreateDefault(new TokenHeaderAuthenticator(token, tokenType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a default configuration, which is not useable without calling <see cref="WithToken" /> or
|
||||||
|
/// <see cref="WithAuthenticator" />
|
||||||
|
/// </summary>
|
||||||
|
public static SpotifyClientConfig CreateDefault()
|
||||||
|
{
|
||||||
|
return CreateDefault(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SpotifyClientConfig CreateDefault(IAuthenticator authenticator)
|
||||||
|
{
|
||||||
|
return new SpotifyClientConfig(
|
||||||
|
SpotifyUrls.API_V1,
|
||||||
|
authenticator,
|
||||||
|
new NewtonsoftJSONSerializer(),
|
||||||
|
new NetHttpClient()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,4 +20,4 @@ namespace SpotifyAPI.Web.Enums
|
|||||||
[String("album,single,compilation,appears_on")]
|
[String("album,single,compilation,appears_on")]
|
||||||
All = 16
|
All = 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,10 @@ namespace SpotifyAPI.Web.Http
|
|||||||
{
|
{
|
||||||
public class APIConnector : IAPIConnector
|
public class APIConnector : IAPIConnector
|
||||||
{
|
{
|
||||||
private Uri _baseAddress;
|
private readonly Uri _baseAddress;
|
||||||
private IAuthenticator _authenticator;
|
private readonly IAuthenticator _authenticator;
|
||||||
private IJSONSerializer _jsonSerializer;
|
private readonly IJSONSerializer _jsonSerializer;
|
||||||
private IHTTPClient _httpClient;
|
private readonly IHTTPClient _httpClient;
|
||||||
|
|
||||||
public APIConnector(Uri baseAddress, IAuthenticator authenticator) :
|
public APIConnector(Uri baseAddress, IAuthenticator authenticator) :
|
||||||
this(baseAddress, authenticator, new NewtonsoftJSONSerializer(), new NetHttpClient())
|
this(baseAddress, authenticator, new NewtonsoftJSONSerializer(), new NetHttpClient())
|
||||||
@ -119,10 +119,10 @@ namespace SpotifyAPI.Web.Http
|
|||||||
var request = new Request
|
var request = new Request
|
||||||
{
|
{
|
||||||
BaseAddress = _baseAddress,
|
BaseAddress = _baseAddress,
|
||||||
ContentType = "application/json",
|
|
||||||
Parameters = parameters,
|
Parameters = parameters,
|
||||||
Endpoint = uri,
|
Endpoint = uri,
|
||||||
Method = method
|
Method = method,
|
||||||
|
Body = body
|
||||||
};
|
};
|
||||||
|
|
||||||
_jsonSerializer.SerializeRequest(request);
|
_jsonSerializer.SerializeRequest(request);
|
||||||
@ -143,13 +143,11 @@ namespace SpotifyAPI.Web.Http
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (response.StatusCode)
|
throw response.StatusCode switch
|
||||||
{
|
{
|
||||||
case HttpStatusCode.Unauthorized:
|
HttpStatusCode.Unauthorized => new APIUnauthorizedException(response),
|
||||||
throw new APIUnauthorizedException(response);
|
_ => new APIException(response),
|
||||||
default:
|
};
|
||||||
throw new APIException(response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ namespace SpotifyAPI.Web.Http
|
|||||||
{
|
{
|
||||||
public class APIResponse<T> : IAPIResponse<T>
|
public class APIResponse<T> : IAPIResponse<T>
|
||||||
{
|
{
|
||||||
public APIResponse(IResponse response, T body = default(T))
|
public APIResponse(IResponse response, T body = default)
|
||||||
{
|
{
|
||||||
Ensure.ArgumentNotNull(response, nameof(response));
|
Ensure.ArgumentNotNull(response, nameof(response));
|
||||||
|
|
||||||
|
@ -16,8 +16,6 @@ namespace SpotifyAPI.Web.Http
|
|||||||
|
|
||||||
HttpMethod Method { get; }
|
HttpMethod Method { get; }
|
||||||
|
|
||||||
string ContentType { get; }
|
|
||||||
|
|
||||||
object Body { get; set; }
|
object Body { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace SpotifyAPI.Web.Http
|
|||||||
{
|
{
|
||||||
public class NetHttpClient : IHTTPClient
|
public class NetHttpClient : IHTTPClient
|
||||||
{
|
{
|
||||||
private HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
public NetHttpClient()
|
public NetHttpClient()
|
||||||
{
|
{
|
||||||
@ -19,14 +19,12 @@ namespace SpotifyAPI.Web.Http
|
|||||||
{
|
{
|
||||||
Ensure.ArgumentNotNull(request, nameof(request));
|
Ensure.ArgumentNotNull(request, nameof(request));
|
||||||
|
|
||||||
using (HttpRequestMessage requestMsg = BuildRequestMessage(request))
|
using HttpRequestMessage requestMsg = BuildRequestMessage(request);
|
||||||
{
|
var responseMsg = await _httpClient
|
||||||
var responseMsg = await _httpClient
|
.SendAsync(requestMsg, HttpCompletionOption.ResponseContentRead)
|
||||||
.SendAsync(requestMsg, HttpCompletionOption.ResponseContentRead)
|
.ConfigureAwait(false);
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
return await BuildResponse(responseMsg).ConfigureAwait(false);
|
return await BuildResponse(responseMsg).ConfigureAwait(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IResponse> BuildResponse(HttpResponseMessage responseMsg)
|
private async Task<IResponse> BuildResponse(HttpResponseMessage responseMsg)
|
||||||
@ -34,19 +32,17 @@ namespace SpotifyAPI.Web.Http
|
|||||||
Ensure.ArgumentNotNull(responseMsg, nameof(responseMsg));
|
Ensure.ArgumentNotNull(responseMsg, nameof(responseMsg));
|
||||||
|
|
||||||
// We only support text stuff for now
|
// We only support text stuff for now
|
||||||
using (var content = responseMsg.Content)
|
using var content = responseMsg.Content;
|
||||||
{
|
var headers = responseMsg.Headers.ToDictionary(header => header.Key, header => header.Value.First());
|
||||||
var headers = responseMsg.Headers.ToDictionary(header => header.Key, header => header.Value.First());
|
var body = await responseMsg.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||||
var body = await responseMsg.Content.ReadAsStringAsync().ConfigureAwait(false);
|
var contentType = content.Headers?.ContentType?.MediaType;
|
||||||
var contentType = content.Headers?.ContentType?.MediaType;
|
|
||||||
|
|
||||||
return new Response(headers)
|
return new Response(headers)
|
||||||
{
|
{
|
||||||
ContentType = contentType,
|
ContentType = contentType,
|
||||||
StatusCode = responseMsg.StatusCode,
|
StatusCode = responseMsg.StatusCode,
|
||||||
Body = body
|
Body = body
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpRequestMessage BuildRequestMessage(IRequest request)
|
private HttpRequestMessage BuildRequestMessage(IRequest request)
|
||||||
|
@ -9,7 +9,7 @@ namespace SpotifyAPI.Web.Http
|
|||||||
{
|
{
|
||||||
public class NewtonsoftJSONSerializer : IJSONSerializer
|
public class NewtonsoftJSONSerializer : IJSONSerializer
|
||||||
{
|
{
|
||||||
JsonSerializerSettings _serializerSettings;
|
private readonly JsonSerializerSettings _serializerSettings;
|
||||||
|
|
||||||
public NewtonsoftJSONSerializer()
|
public NewtonsoftJSONSerializer()
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,7 @@ using System.Net.Http;
|
|||||||
|
|
||||||
namespace SpotifyAPI.Web.Http
|
namespace SpotifyAPI.Web.Http
|
||||||
{
|
{
|
||||||
class Request : IRequest
|
public class Request : IRequest
|
||||||
{
|
{
|
||||||
public Request()
|
public Request()
|
||||||
{
|
{
|
||||||
@ -22,8 +22,6 @@ namespace SpotifyAPI.Web.Http
|
|||||||
|
|
||||||
public HttpMethod Method { get; set; }
|
public HttpMethod Method { get; set; }
|
||||||
|
|
||||||
public string ContentType { get; set; }
|
|
||||||
|
|
||||||
public object Body { get; set; }
|
public object Body { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace SpotifyAPI.Web.Http
|
namespace SpotifyAPI.Web.Http
|
||||||
{
|
{
|
||||||
class TokenHeaderAuthenticator : IAuthenticator
|
public class TokenHeaderAuthenticator : IAuthenticator
|
||||||
{
|
{
|
||||||
public TokenHeaderAuthenticator(string token, string tokenType)
|
public TokenHeaderAuthenticator(string token, string tokenType)
|
||||||
{
|
{
|
||||||
|
58
SpotifyAPI.Web/Models/Request/RecommendationsRequest.cs
Normal file
58
SpotifyAPI.Web/Models/Request/RecommendationsRequest.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public class RecommendationsRequest : RequestParams
|
||||||
|
{
|
||||||
|
public RecommendationsRequest()
|
||||||
|
{
|
||||||
|
Min = new Dictionary<string, string>();
|
||||||
|
Max = new Dictionary<string, string>();
|
||||||
|
Target = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[QueryParam("seed_artists")]
|
||||||
|
public string SeedArtists { get; set; }
|
||||||
|
|
||||||
|
[QueryParam("seed_genres")]
|
||||||
|
public string SeedGenres { get; set; }
|
||||||
|
|
||||||
|
[QueryParam("seed_tracks")]
|
||||||
|
public string SeedTracks { get; set; }
|
||||||
|
|
||||||
|
[QueryParam("limit")]
|
||||||
|
public int? Limit { get; set; }
|
||||||
|
|
||||||
|
[QueryParam("market")]
|
||||||
|
public string Market { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> Min { get; set; }
|
||||||
|
public Dictionary<string, string> Max { get; set; }
|
||||||
|
public Dictionary<string, string> Target { get; set; }
|
||||||
|
|
||||||
|
protected override void Ensure()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(SeedTracks) && string.IsNullOrEmpty(SeedGenres) && string.IsNullOrEmpty(SeedArtists))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("At least one of the seeds has to be non-empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddCustomQueryParams(System.Collections.Generic.Dictionary<string, string> queryParams)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<string, string> pair in Min)
|
||||||
|
{
|
||||||
|
queryParams.Add($"min_{pair.Key}", pair.Value);
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<string, string> pair in Min)
|
||||||
|
{
|
||||||
|
queryParams.Add($"max_{pair.Key}", pair.Value);
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<string, string> pair in Min)
|
||||||
|
{
|
||||||
|
queryParams.Add($"target_{pair.Key}", pair.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,22 +8,30 @@ namespace SpotifyAPI.Web
|
|||||||
{
|
{
|
||||||
public Dictionary<string, string> BuildQueryParams()
|
public Dictionary<string, string> BuildQueryParams()
|
||||||
{
|
{
|
||||||
var queryProps = this.GetType().GetProperties()
|
// Make sure everything is okay before building query params
|
||||||
|
Ensure();
|
||||||
|
|
||||||
|
var queryProps = GetType().GetProperties()
|
||||||
.Where(prop => prop.GetCustomAttributes(typeof(QueryParamAttribute), true).Length > 0);
|
.Where(prop => prop.GetCustomAttributes(typeof(QueryParamAttribute), true).Length > 0);
|
||||||
|
|
||||||
var queryParams = new Dictionary<string, string>();
|
var queryParams = new Dictionary<string, string>();
|
||||||
foreach (var prop in queryProps)
|
foreach (var prop in queryProps)
|
||||||
{
|
{
|
||||||
var attribute = prop.GetCustomAttribute(typeof(QueryParamAttribute)) as QueryParamAttribute;
|
var attribute = prop.GetCustomAttribute(typeof(QueryParamAttribute)) as QueryParamAttribute;
|
||||||
var value = prop.GetValue(this);
|
object value = prop.GetValue(this);
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
queryParams.Add(attribute.Key ?? prop.Name, value.ToString());
|
queryParams.Add(attribute.Key ?? prop.Name, value.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddCustomQueryParams(queryParams);
|
||||||
|
|
||||||
return queryParams;
|
return queryParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void Ensure() { }
|
||||||
|
protected virtual void AddCustomQueryParams(Dictionary<string, string> queryParams) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QueryParamAttribute : Attribute
|
public class QueryParamAttribute : Attribute
|
||||||
|
@ -19,7 +19,7 @@ namespace SpotifyAPI.Web
|
|||||||
public string ReleaseDatePrecision { get; set; }
|
public string ReleaseDatePrecision { get; set; }
|
||||||
public ResumePoint ResumePoint { get; set; }
|
public ResumePoint ResumePoint { get; set; }
|
||||||
public SimpleShow Show { get; set; }
|
public SimpleShow Show { get; set; }
|
||||||
public PlaylistElementType Type { get; set; }
|
public ElementType Type { get; set; }
|
||||||
public string Uri { get; set; }
|
public string Uri { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ namespace SpotifyAPI.Web
|
|||||||
public int Popularity { get; set; }
|
public int Popularity { get; set; }
|
||||||
public string PreviewUrl { get; set; }
|
public string PreviewUrl { get; set; }
|
||||||
public int TrackNumber { get; set; }
|
public int TrackNumber { get; set; }
|
||||||
public PlaylistElementType Type { get; set; }
|
public ElementType Type { get; set; }
|
||||||
public string Uri { get; set; }
|
public string Uri { get; set; }
|
||||||
public bool IsLocal { get; set; }
|
public bool IsLocal { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,15 @@ using Newtonsoft.Json.Converters;
|
|||||||
|
|
||||||
namespace SpotifyAPI.Web
|
namespace SpotifyAPI.Web
|
||||||
{
|
{
|
||||||
public enum PlaylistElementType
|
public enum ElementType
|
||||||
{
|
{
|
||||||
Track,
|
Track,
|
||||||
Episode
|
Episode
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPlaylistElement
|
public interface IPlaylistElement
|
||||||
{
|
{
|
||||||
[JsonConverter(typeof(StringEnumConverter))]
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
public PlaylistElementType Type { get; set; }
|
public ElementType Type { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,6 @@ namespace SpotifyAPI.Web
|
|||||||
public string Href { get; set; }
|
public string Href { get; set; }
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
public string uri { get; set; }
|
public string Uri { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace SpotifyAPI.Web
|
namespace SpotifyAPI.Web
|
||||||
{
|
{
|
||||||
public class PlaylistTrack
|
public class PlaylistTrack
|
||||||
@ -6,6 +8,7 @@ namespace SpotifyAPI.Web
|
|||||||
public DateTime? AddedAt { get; set; }
|
public DateTime? AddedAt { get; set; }
|
||||||
public PublicUser AddedBy { get; set; }
|
public PublicUser AddedBy { get; set; }
|
||||||
public bool IsLocal { get; set; }
|
public bool IsLocal { get; set; }
|
||||||
|
[JsonConverter(typeof(PlaylistElementConverter))]
|
||||||
public IPlaylistElement Track { get; set; }
|
public IPlaylistElement Track { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
SpotifyAPI.Web/Models/Response/RecommendationSeed.cs
Normal file
18
SpotifyAPI.Web/Models/Response/RecommendationSeed.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public class RecommendationSeed
|
||||||
|
{
|
||||||
|
[JsonProperty("afterFilteringSize")]
|
||||||
|
public int AfterFiliteringSize { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("afterRelinkingSize")]
|
||||||
|
public int AfterRelinkingSize { get; set; }
|
||||||
|
public string Href { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
[JsonProperty("initialPoolSize")]
|
||||||
|
public int InitialPoolSize { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
10
SpotifyAPI.Web/Models/Response/RecommendationsResponse.cs
Normal file
10
SpotifyAPI.Web/Models/Response/RecommendationsResponse.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public class RecommendationsResponse
|
||||||
|
{
|
||||||
|
public List<RecommendationSeed> Seeds { get; set; }
|
||||||
|
public List<SimpleTrack> Tracks { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@ namespace SpotifyAPI.Web
|
|||||||
public string MediaType { get; set; }
|
public string MediaType { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Publisher { get; set; }
|
public string Publisher { get; set; }
|
||||||
public string Type { get; set; }
|
public ElementType Type { get; set; }
|
||||||
public string Uri { get; set; }
|
public string Uri { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
SpotifyAPI.Web/Models/Response/SimpleTrack.cs
Normal file
23
SpotifyAPI.Web/Models/Response/SimpleTrack.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SpotifyAPI.Web
|
||||||
|
{
|
||||||
|
public class SimpleTrack
|
||||||
|
{
|
||||||
|
public List<SimpleArtist> Artists { get; set; }
|
||||||
|
public List<string> AvailableMarkets { get; set; }
|
||||||
|
public int DiscNumber { get; set; }
|
||||||
|
public int DurationMs { get; set; }
|
||||||
|
public bool Explicit { get; set; }
|
||||||
|
public Dictionary<string, string> ExternalUrls { get; set; }
|
||||||
|
public string Href { get; set; }
|
||||||
|
public string Id { get; set; }
|
||||||
|
public bool IsPlayable { get; set; }
|
||||||
|
public LinkedTrack LinkedFrom { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string PreviewUrl { get; set; }
|
||||||
|
public int TrackNumber { get; set; }
|
||||||
|
public ElementType Type { get; set; }
|
||||||
|
public string Uri { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,9 @@ namespace SpotifyAPI.Web
|
|||||||
public WebProxy CreateWebProxy()
|
public WebProxy CreateWebProxy()
|
||||||
{
|
{
|
||||||
if (!IsValid())
|
if (!IsValid())
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
WebProxy proxy = new WebProxy
|
WebProxy proxy = new WebProxy
|
||||||
{
|
{
|
||||||
@ -67,7 +69,9 @@ namespace SpotifyAPI.Web
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
|
if (string.IsNullOrEmpty(Username) || string.IsNullOrEmpty(Password))
|
||||||
|
{
|
||||||
return proxy;
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
proxy.UseDefaultCredentials = false;
|
proxy.UseDefaultCredentials = false;
|
||||||
proxy.Credentials = new NetworkCredential(Username, Password);
|
proxy.Credentials = new NetworkCredential(Username, Password);
|
||||||
@ -79,12 +83,16 @@ namespace SpotifyAPI.Web
|
|||||||
{
|
{
|
||||||
HttpClientHandler clientHandler = new HttpClientHandler
|
HttpClientHandler clientHandler = new HttpClientHandler
|
||||||
{
|
{
|
||||||
PreAuthenticate = false,
|
PreAuthenticate = false,
|
||||||
UseDefaultCredentials = true,
|
UseDefaultCredentials = true,
|
||||||
UseProxy = false
|
UseProxy = false
|
||||||
};
|
};
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(proxyConfig?.Host)) return clientHandler;
|
if (string.IsNullOrWhiteSpace(proxyConfig?.Host))
|
||||||
|
{
|
||||||
|
return clientHandler;
|
||||||
|
}
|
||||||
|
|
||||||
WebProxy proxy = proxyConfig.CreateWebProxy();
|
WebProxy proxy = proxyConfig.CreateWebProxy();
|
||||||
clientHandler.UseProxy = true;
|
clientHandler.UseProxy = true;
|
||||||
clientHandler.Proxy = proxy;
|
clientHandler.Proxy = proxy;
|
||||||
@ -94,4 +102,4 @@ namespace SpotifyAPI.Web
|
|||||||
return clientHandler;
|
return clientHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,21 +3,22 @@ namespace SpotifyAPI.Web
|
|||||||
{
|
{
|
||||||
public static class SpotifyUrls
|
public static class SpotifyUrls
|
||||||
{
|
{
|
||||||
static URIParameterFormatProvider _provider = new URIParameterFormatProvider();
|
static private readonly URIParameterFormatProvider _provider = new URIParameterFormatProvider();
|
||||||
|
|
||||||
public static Uri API_V1 = new Uri("https://api.spotify.com/v1/");
|
public static Uri API_V1 = new Uri("https://api.spotify.com/v1/");
|
||||||
|
|
||||||
public static Uri Me() => _Uri("me");
|
public static Uri Me() => EUri($"me");
|
||||||
|
|
||||||
public static Uri User(string userId) => _Uri($"users/{userId}");
|
public static Uri User(string userId) => EUri($"users/{userId}");
|
||||||
|
|
||||||
public static Uri Categories() => _Uri("browse/categories");
|
public static Uri Categories() => EUri($"browse/categories");
|
||||||
|
|
||||||
public static Uri Category(string categoryId) => _Uri($"browse/categories/{categoryId}");
|
public static Uri Category(string categoryId) => EUri($"browse/categories/{categoryId}");
|
||||||
|
|
||||||
public static Uri CategoryPlaylists(string categoryId) => _Uri($"browse/categories/{categoryId}/playlists");
|
public static Uri CategoryPlaylists(string categoryId) => EUri($"browse/categories/{categoryId}/playlists");
|
||||||
|
|
||||||
private static Uri _Uri(FormattableString path) => new Uri(path.ToString(_provider), UriKind.Relative);
|
public static Uri Recommendations() => EUri($"recommendations");
|
||||||
private static Uri _Uri(string path) => new Uri(path, UriKind.Relative);
|
|
||||||
|
private static Uri EUri(FormattableString path) => new Uri(path.ToString(_provider), UriKind.Relative);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,10 @@ namespace SpotifyAPI.Web
|
|||||||
/// <param name = "name">The name of the argument</param>
|
/// <param name = "name">The name of the argument</param>
|
||||||
public static void ArgumentNotNull(object value, string name)
|
public static void ArgumentNotNull(object value, string name)
|
||||||
{
|
{
|
||||||
if (value != null) return;
|
if (value != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
throw new ArgumentNullException(name);
|
throw new ArgumentNullException(name);
|
||||||
}
|
}
|
||||||
@ -26,9 +29,22 @@ namespace SpotifyAPI.Web
|
|||||||
/// <param name = "name">The name of the argument</param>
|
/// <param name = "name">The name of the argument</param>
|
||||||
public static void ArgumentNotNullOrEmptyString(string value, string name)
|
public static void ArgumentNotNullOrEmptyString(string value, string name)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(value)) return;
|
if (!string.IsNullOrEmpty(value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
throw new ArgumentException("String is empty or null", name);
|
throw new ArgumentException("String is empty or null", name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void PropertyNotNull(object value, string name, string additional = null)
|
||||||
|
{
|
||||||
|
if (value != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"The property {name} is null{additional}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,10 @@ namespace SpotifyAPI.Web
|
|||||||
var queryString = String.Join("&", newParameters.Select((parameter) => $"{parameter.Key}={parameter.Value}"));
|
var queryString = String.Join("&", newParameters.Select((parameter) => $"{parameter.Key}={parameter.Value}"));
|
||||||
var query = string.IsNullOrEmpty(queryString) ? null : $"?{queryString}";
|
var query = string.IsNullOrEmpty(queryString) ? null : $"?{queryString}";
|
||||||
|
|
||||||
var uriBuilder = new UriBuilder(uri);
|
var uriBuilder = new UriBuilder(uri)
|
||||||
uriBuilder.Query = query;
|
{
|
||||||
|
Query = query
|
||||||
|
};
|
||||||
|
|
||||||
return uriBuilder.Uri;
|
return uriBuilder.Uri;
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,10 @@ namespace SpotifyAPI.Web
|
|||||||
|
|
||||||
public object GetFormat(Type formatType)
|
public object GetFormat(Type formatType)
|
||||||
{
|
{
|
||||||
if (formatType == typeof(ICustomFormatter))
|
return formatType == typeof(ICustomFormatter) ? _formatter : null;
|
||||||
return _formatter;
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class URIParameterFormatter : ICustomFormatter
|
public class URIParameterFormatter : ICustomFormatter
|
||||||
{
|
{
|
||||||
public string Format(string format, object arg, IFormatProvider formatProvider)
|
public string Format(string format, object arg, IFormatProvider formatProvider)
|
||||||
{
|
{
|
||||||
|
8
omnisharp.json
Normal file
8
omnisharp.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"RoslynExtensionsOptions": {
|
||||||
|
"enableAnalyzersSupport": true
|
||||||
|
},
|
||||||
|
"FormattingOptions": {
|
||||||
|
"enableEditorConfigSupport": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user