Merge branch 'v6'

This commit is contained in:
Jonas Dellinger 2020-06-04 15:43:25 +02:00
commit e69af64ecd
535 changed files with 20438 additions and 49969 deletions

31
.editorconfig Normal file
View File

@ -0,0 +1,31 @@
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
charset = utf-8
[*.{cs,vb}]
dotnet_diagnostic.CA1303.severity = none
dotnet_diagnostic.CA1056.severity = none
dotnet_diagnostic.CA1034.severity = none
dotnet_diagnostic.CA1054.severity = none
dotnet_diagnostic.CA2227.severity = none
dotnet_diagnostic.CA1308.severity = none
# 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
# Custom disabled

4
.gitignore vendored
View File

@ -114,3 +114,7 @@ UpgradeLog*.XML
# Idea jetbrains
.idea/
.env
SpotifyAPI.Web.Tests/UtilTests/Test.cs
launchSettings.json

51
.vscode/csharp.code-snippets vendored Normal file
View File

@ -0,0 +1,51 @@
{
"class-model": {
"scope": "csharp",
"prefix": "class-model",
"body": [
"namespace SpotifyAPI.Web",
"{",
" public class $TM_FILENAME_BASE",
" {",
" public ${2:string} ${3:Name} { get; set; }",
"",
" $4",
" }",
"}"
],
"description": "Creates a new model"
},
"class-request": {
"scope": "csharp",
"prefix": "class-request",
"body": [
"namespace SpotifyAPI.Web",
"{",
" public class $TM_FILENAME_BASE : RequestParams",
" {",
" [QueryParam(\"${3:Name}\")]",
" public ${2:string} ${3:Name} { get; set; }",
"",
" $4",
" }",
"}"
],
"description": "Creates a new request"
},
"reqcomment": {
"scope": "csharp",
"prefix": "reqcomment",
"body": "The request-model which contains required and optional parameters.",
"description": "Creates a new request comment for XAML"
},
"remark": {
"scope": "csharp",
"prefix": "remark",
"body": [
"<remarks>",
"/// $1",
"/// </remarks>"
],
"description": "Creates a new request comment for XAML"
}
}

27
.vscode/launch.json vendored
View File

@ -5,36 +5,23 @@
"version": "0.2.0",
"configurations": [
{
"name": "Launch SpotifyAPI.Web.Examples.ASP",
"name": "Launch Example.ASP",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-web-example-asp",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/SpotifyAPI.Web.Examples.ASP/bin/Debug/netcoreapp3.0/SpotifyAPI.Web.Examples.ASP.dll",
"preLaunchTask": "Example.ASP-build",
"program": "${workspaceFolder}/SpotifyAPI.Web.Examples/Example.ASP/bin/Debug/netcoreapp3.1/Example.ASP.dll",
"args": [],
"cwd": "${workspaceFolder}/SpotifyAPI.Web.Examples.ASP",
"cwd": "${workspaceFolder}/SpotifyAPI.Web.Examples/Example.ASP",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"
},
// "serverReadyAction": {
// "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)"
// },
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": "Launch SpotifyAPI.Web.Examples.CLI",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build-web-example-cli",
"program": "${workspaceFolder}/SpotifyAPI.Web.Examples.CLI/bin/Debug/netcoreapp3.0/SpotifyAPI.Web.Examples.CLI.dll",
"args": [],
"cwd": "${workspaceFolder}/SpotifyAPI.Web.Examples.CLI",
"stopAtEntry": false
}
]
}

View File

@ -2,6 +2,4 @@
"editor.detectIndentation": false,
"editor.insertSpaces": true,
"editor.tabSize": 2,
"csharpfixformat.style.braces.onSameLine": false,
"csharpfixformat.style.spaces.beforeParenthesis": false,
}

18
.vscode/tasks.json vendored
View File

@ -2,28 +2,16 @@
"version": "2.0.0",
"tasks": [
{
"label": "build-web-example-asp",
"label": "Example.ASP-build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/SpotifyAPI.Web.Examples.ASP/SpotifyAPI.Web.Examples.ASP.csproj",
"${workspaceFolder}/SpotifyAPI.Web.Examples/Example.ASP/Example.ASP.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "build-web-example-cli",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/SpotifyAPI.Web.Examples.CLI/SpotifyAPI.Web.Examples.CLI.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
}
]
}

166
LICENSE
View File

@ -1,165 +1,7 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright 2020 Jonas Dellinger
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,2 +1,20 @@
docs/.vuepress/dist
node_modules/
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

33
SpotifyAPI.Docs/README.md Normal file
View File

@ -0,0 +1,33 @@
# Website
This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
### Deployment
```
$ GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@ -1,28 +0,0 @@
if "%APPVEYOR_PULL_REQUEST_NUMBER%" == "" (
if "%APPVEYOR_REPO_BRANCH%" == "master" (
echo Building docs...
powershell Install-Product node $env:NODEJS_VERSION
cd ./SpotifyAPI.Docs
yarn
yarn run build
mkdir deploy
cd deploy
git config --global user.email "johnny@johnnycrazy.de"
git config --global user.name "AppVeyor Doc Generation"
git clone --quiet --branch=gh-pages https://%GH_TOKEN%@github.com/JohnnyCrazy/SpotifyAPI-NET gh-pages
cd gh-pages
git rm -qrf .
xcopy ..\..\docs\.vuepress\dist .\ /s /e /y
git add -A
git commit -m "Built docs | AppVeyor Build %APPVEYOR_BUILD_NUMBER%"
git push -fq origin gh-pages
cd ../../../
)
) else (
echo Skipping doc build
)

View File

@ -1,51 +0,0 @@
module.exports = {
title: 'SpotifyAPI-NET',
base: '/SpotifyAPI-NET/',
description: '🔉 An API for the Spotify-Client and the Spotify Web API, written in C#/.NET',
themeConfig: {
repo: 'JohnnyCrazy/SpotifyAPI-NET',
repoLabel: '🚀 GitHub',
docsDir: 'SpotifyAPI.Docs/docs',
editLinks: true,
editLinkText: 'Help us improve this page!',
sidebar: 'auto',
sidebarDepth: 0,
lastUpdated: 'Last Updated',
serviceWorker: {
updatePopup: true
},
nav: [
{ text: 'Home', link: '/' },
{
text: 'SpotifyAPI.Web',
items: [
{ text: 'Getting Started', link: '/web/getting_started' },
{ text: 'Examples', link: '/web/examples' },
{ text: 'Proxy', link: '/web/proxy' },
{ text: '- Albums', link: '/web/albums' },
{ text: '- Artists', link: '/web/artists' },
{ text: '- Browse', link: '/web/browse' },
{ text: '- Follow', link: '/web/follow' },
{ text: '- Library', link: '/web/library' },
{ text: '- Personalization', link: '/web/personalization' },
{ text: '- Player', link: '/web/player' },
{ text: '- Playlists', link: '/web/playlists' },
{ text: '- Profiles', link: '/web/profiles' },
{ text: '- Search', link: '/web/search' },
{ text: '- Tracks', link: '/web/tracks' },
{ text: 'Utilities', link: '/web/utils' },
]
},
{
text: 'SpotifyAPI.Auth',
items: [
{ text: 'Getting Started', link: '/auth/getting_started' },
{ text: '- ImplicitGrantAuth', link: '/auth/implicit_grant' },
{ text: '- TokenSwapAuth', link: '/auth/token_swap' },
{ text: '- AutorizationCodeAuth', link: '/auth/authorization_code' },
{ text: '- ClientCredentialsAuth', link: '/auth/client_credentials' },
]
},
]
}
}

View File

@ -0,0 +1,20 @@
---
id: auth_introduction
title: Introduction
---
Spotify does not allow unauthorized access to the api. Thus, you need an access token to make requets. This access token can be gathered via multiple schemes, all following the OAuth2 spec. Since it's important to choose the correct scheme for your usecase, make sure you have a grasp of the following terminology/docs:
* OAuth2
* [Spotify Authorization Flows](https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow)
Since every auth flow also needs an application in the [spotify dashboard](https://developer.spotify.com/dashboard/), make sure you have the necessary values (like `Client Id` and `Client Secret`).
Then, continue with the docs of the specific auth flows:
* [Client Credentials](client_credentials.md)
* [Implicit Grant](implicit_grant.md)
* [Authorization Code](authorization_code.md)
* [Token Swap](token_swap.md)
![auth comparison](/img/auth_comparison.png)

View File

@ -0,0 +1,108 @@
---
id: authorization_code
title: Authorization Code
---
> This flow is suitable for long-running applications in which the user grants permission only once. It provides an access token that can be refreshed. Since the token exchange involves sending your secret key, perform this on a secure location, like a backend service, and not from a client such as a browser or from a mobile app.
## Existing Web-Server
If you are already in control of a Web-Server (like `ASP.NET`), you can start the flow by generating a login uri
```csharp
// Make sure "http://localhost:5000" is in your applications redirect URIs!
var loginRequest = new LoginRequest(
new Uri("http://localhost:5000"),
"ClientId",
LoginRequest.ResponseType.Code
)
{
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
};
var uri = loginRequest.ToUri();
// Redirect user to uri via your favorite web-server
```
When the user is redirected to the generated uri, he will have to login with his spotify account and confirm, that your application wants to access his user data. Once confirmed, he will be redirect to `http://localhost:5000` and a `code` parameter is attached to the query. This `code` has to be exchanged for an `access_token` and `refresh_token`:
```csharp
// This method should be called from your web-server when the user visits "http://localhost:5000"
public Task GetCallback(string code)
{
var response = await new OAuthClient().RequestToken(
new AuthorizationCodeTokenRequest("ClientId", "ClientSecret", code, "http://localhost:5000")
);
var spotify = new SpotifyClient(response.AccessToken);
// Also important for later: response.RefreshToken
}
```
If the token expires at some point (check via `response.IsExpired`), you can refresh it:
```csharp
var newResponse = await new OAuthClient().RequestToken(
new AuthorizationCodeRefreshRequest("ClientId", "ClientSecret", response.RefreshToken)
);
var spotify = new SpotifyClient(newResponse.AccessToken);
```
You can also let the `AuthorizationCodeAuthenticator` take care of the refresh part:
```csharp
var response = await new OAuthClient().RequestToken(
new AuthorizationCodeTokenRequest("ClientId", "ClientSecret", code, "http://localhost:5000")
);
var config = SpotifyClientConfig
.CreateDefault()
.WithAuthenticator(new AuthorizationCodeAuthenticator("ClientId", "ClientSecret", response));
var spotify = new SpotifyClient(config);
```
For a real example, have a look at [Example.ASP](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.ASP). This also uses the great package `AspNet.Security.OAuth.Spotify` which takes care of the OAuth flow inside of `ASP.NET`.
## Using Spotify.Web.Auth
For cross-platform CLI and desktop apps (non `UWP` apps), `Spotify.Web.Auth` can be used to supply a small embedded Web Server for the code retrieval.
:::warning
You're client secret will be exposed when embedded in a desktop/cli app. This can be abused and is not preffered. If possible, let the user create an application in spotify dashboard or let a server handle the spotify communication.
:::
```csharp
private static EmbedIOAuthServer _server;
public static async Task Main()
{
// Make sure "http://localhost:5000/callback" is in your spotify application as redirect uri!
_server = new EmbedIOAuthServer(new Uri("http://localhost:5000/callback"), 5000);
await _server.Start();
_server.AuthorizationCodeReceived += OnAuthorizationCodeReceived;
var request = new LoginRequest(_server.BaseUri, "ClientId", LoginRequest.ResponseType.Code)
{
Scope = new List<string> { Scopes.UserReadEmail }
};
BrowserUtil.Open(uri);
}
private static async Task OnAuthorizationCodeReceived(object sender, AuthorizationCodeResponse response)
{
await _server.Stop();
var config = SpotifyClientConfig.CreateDefault();
var tokenResponse = await new OAuthClient(config).RequestToken(
new AuthorizationCodeTokenRequest(
"ClientId", "ClientSecret", response.Code, "http://localhost:5000/callback"
)
);
var spotify = new SpotifyClient(tokenResponse.AccessToken);
// do calls with spotify and save token?
}
```
For real examples, have a look at [Example.CLI.PersistentConfig](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig) and [Example.CLI.CustomHTML](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.CLI.CustomHTML)

View File

@ -0,0 +1,45 @@
---
id: client_credentials
title: Client Credentials
---
> The Client Credentials flow is used in server-to-server authentication.
> Only endpoints that do not access user information can be accessed.
By supplying your `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET`, you get an access token.
## Request token once
To request an access token, build a `ClientCredentialsRequest` and send it via `OAuthClient`. This access token will expire after some time and you need to repeat the process.
```csharp
public static async Task Main()
{
var config = SpotifyClientConfig.CreateDefault();
var request = new ClientCredentialsRequest("CLIENT_ID", "CLIENT_SECRET");
var response = await new OAuthClient(config).RequestToken(request);
var spotify = new SpotifyClient(config.WithToken(response.AccessToken));
}
```
## Request Token On-Demand
You can also use `CredentialsAuthenticator`, which will make sure the spotify instance will always have an up-to-date access token by automatically refreshing the token on-demand.
```csharp
public static async Task Main()
{
var config = SpotifyClientConfig
.CreateDefault()
.WithAuthenticator(new CredentialsAuthenticator("CLIENT_ID", "CLIENT_SECRET"));
var spotify = new SpotifyClient(config);
}
```
:::info
There is no thread safety guaranteed when using `CredentialsAuthenticator`.
:::

View File

@ -0,0 +1,48 @@
---
id: configuration
title: Configuration
---
To configure the spotify client functionality, the `SpotifyClientConfig` class exists.
```csharp
var config = SpotifyClientConfig.CreateDefault("YourAccessToken");
var spotify = new SpotifyClient(config);
// is the same as
var spotify = new SpotifyClient("YourAccessToken");
```
We won't cover every possible configuration in this part, head over to the specific guides for that:
* ...
## HTTPClient Notes
One important part of the configuration is the used HTTPClient. By default, every time when a `SpotifyClientConfig` is instantiated, a new `HTTPClient` is created in the background. For Web Applications which require a lot of different configs due to user based access tokens, it is **not** advised to create a new config from scratch with every HTTP call. Instead, a default (static) config should be used to create a new config with a new access token.
Consider the following HTTP Endpoint:
```csharp
public HttpResult Get()
{
var config = SpotifyClientConfig.CreateDefault("YourAccessToken")
var spotify = new SpotifyClient(config);
}
```
This creates a new `HTTPClient` every time a request is made, which can be quite bad for the performance. Instead we should use a base config and use `WithToken`:
```csharp
// somewhere global/static
public static SpotifyClientConfig DefaultConfig = SpotifyClientConfig.CreateDefault();
public HttpResult Get()
{
var config = DefaultConfig.WithToken("YourAccessToken");
var spotify = new SpotifyClient(config);
}
```
This way, a single `HTTPClient` will be used. For a real example, checkout the [ASP.NET Example](example_asp.md)

View File

@ -0,0 +1,46 @@
---
id: error_handling
title: Error Handling
---
API calls can fail when input data is malformed or the server detects issues with the request. As an example, the following request obviously fails:
```csharp
var track = await spotify.Tracks.Get("NotExistingTrackId");
Console.WriteLine(track.Name);
```
When a request fails an `APIException` is thrown. Specific errors may throw a child exception of `APIException`.
## APIException
A very general API error. The message is parsed from the API response JSON body and the response is available as public property.
```csharp
try {
var track = await spotify.Tracks.Get("NotExistingTrackId");
} catch(APIException e) {
// Prints: invalid id
Console.WriteLine(e.Message);
// Prints: BadRequest
Console.WriteLine(e.Response?.StatusCode);
}
```
## APIUnauthorizedException
Provides the same properties as `APIException` and occurs, when the access token is expired or not provided. Notice that an access token has to be included in **every** request. Spotify does not allow unauthorized API access.
## APITooManyRequestsException
Provides the same properties as `APIException` and occurs, when too many requests has been sent by your application. It also provides the property `TimeSpan RetryAfter`, which maps to the received `Retry-After` Header.
```csharp
try {
// call it very often?
var track = await spotify.Tracks.Get("1s6ux0lNiTziSrd7iUAADH");
} catch(APITooManyRequestsException e) {
// Prints: seconds to wait, often 1 or 2
Console.WriteLine(e.RetryAfter);
}
```

View File

@ -0,0 +1,28 @@
---
id: example_asp
title: ASP.NET
---
## Description
This example is based on ASP .NET Core. It uses `Authorization Code` under the hood with the help of [`AspNet.Security.OAuth.Spotify`](https://www.nuget.org/packages/AspNet.Security.OAuth.Spotify/). It stores the access token in the current user session (cookie-based) and allows to refresh tokens when they expire. Two pages are implemented:
* Home shows your current playlists via pagination
* Profile shows your current profile information
![ASP Example - Home](/img/asp_example_home.png)
![ASP Example - Profile](/img/asp_example_profile.png)
## Run it
Before running it, make sure you created an app in your [spotify dashboard](https://developer.spotify.com/dashboard/) and `https://localhost:5001` is a redirect uri of it.
```bash
# Assumes linux and current working directory is the cloned repository
cd SpotifyAPI.Web.Examples/Example.ASP
dotnet restore
SPOTIFY_CLIENT_ID=YourClientId SPOTIFY_CLIENT_SECRET=YourClientSecret dotnet run
# Visit https://localhost:5001
```

View File

@ -0,0 +1,24 @@
---
id: example_blazor
title: Blazor ServerSide
---
## Description
Very similar to the [Blazor WASM Example](example_blazor_wasm.md), but runs code on the server side and pushes view updates to the client.
![ASP Blazor Example - Home](/img/asp_blazor_example_home.png)
## Run it
Before running it, make sure you created an app in your [spotify dashboard](https://developer.spotify.com/dashboard/) and `https://localhost:5001` is a redirect uri of it.
```bash
# Assumes linux and current working directory is the cloned repository
cd SpotifyAPI.Web.Examples/Example.ASPBlazor
dotnet restore
SPOTIFY_CLIENT_ID=YourClientId SPOTIFY_CLIENT_SECRET=YourClientSecret dotnet run
# Visit https://localhost:5001
```

View File

@ -0,0 +1,29 @@
---
id: example_blazor_wasm
title: Blazor WASM
---
## Description
This small cross-platform web app runs on `Blazor WebAssembly`, which was released on 19. May 2020. It allows to run C# code in any browser which supports WebAssembly. This allows to create .NET full-stack web projects without writing any JavaScript. Find more about [Blazor WebAssembly here](https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-now-available/)
Since this library is compatible with `.NET Standard 2.1`, you can use all features of `SpotifyAPI.Web` in your blazor wasm app. The example logs the user in via `Implicit Grant` and does 2 user-related API requests from the browser. You can observe the requests from your browsers network tools.
![BlazorWASM Spotify Example](/img/blazorwasm_homepage.png)
![BlazorWASM Spotify Example - network tools](/img/blazorwasm_network_tools.png)
## Run it
Before running it, make sure you created an app in your [spotify dashboard](https://developer.spotify.com/dashboard/) and `https://localhost:5001` is a redirect uri of it.
```bash
# Assumes linux and current working directory is the cloned repository
cd SpotifyAPI.Web.Examples/Example.BlazorWASM
dotnet restore
echo "{ \"SPOTIFY_CLIENT_ID\": \"YourSpotifyClientId\" }" > wwwroot/appsettings.json
dotnet run
# Visit https://localhost:5001
```

View File

@ -0,0 +1,4 @@
---
id: example_cli_custom_html
title: CLI - Custom HTML
---

View File

@ -0,0 +1,4 @@
---
id: example_cli_persistent_config
title: CLI - Persistent Config
---

View File

@ -0,0 +1,4 @@
---
id: example_token_swap
title: Token Swap
---

View File

@ -0,0 +1,4 @@
---
id: example_uwp
title: UWP
---

View File

@ -0,0 +1,108 @@
---
id: getting_started
title: Getting Started
---
import InstallInstructions from '../src/install_instructions'
## Adding SpotifyAPI-NET to your project
The library can be added to your project via the following methods:
### Package Managers
<InstallInstructions />
### Add DLL Manually
You can also grab the latest compiled DLL from our [GitHub Releases Page](https://github.com/johnnycrazy/spotifyapi-net/releases). It can be added to your project via Visual Studio or directly in your `.csproj`:
```xml
<ItemGroup>
<Reference Include="SpotifyAPI.Web">
<HintPath>..\Dlls\SpotifyAPI.Web.dll</HintPath>
</Reference>
</ItemGroup>
```
### Compile Yourself
```sh
git clone https://github.com/JohnnyCrazy/SpotifyAPI-NET.git
cd SpotifyAPI-NET
dotnet restore
dotnet build
ls -la SpotifyAPI.Web/bin/Debug/netstandard2.1/SpotifyAPI.Web.dll
```
## First API Calls
You're now ready to issue your first calls to the Spotify API, a small console example:
```csharp
using System;
using SpotifyAPI.Web;
class Program
{
static async Task Main()
{
var spotify = new SpotifyClient("YourAccessToken");
var track = await spotify.Tracks.Get("1s6ux0lNiTziSrd7iUAADH");
Console.WriteLine(track.Name);
}
}
```
:::tip
Notice that the spotify api does not allow unauthorized API access. Wondering where you should get an access token from? For a quick test, head over to the [Spotify Developer Console](https://developer.spotify.com/console/get-album/) and generate an access token with the required scopes! For a permanent solution, head over to the [authentication guides](auth_introduction.md).
:::
There is no online documentation for every available API call, but XML inline docs are available:
* [UserProfile](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IUserProfileClient.cs)
* [Browse](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IBrowseClient.cs)
* [Shows](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IShowsClient.cs)
* [Playlists](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IPlaylistsClient.cs)
* [Search](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/ISearchClient.cs)
* [Follow](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IFollowClient.cs)
* [Tracks](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/ITracksClient.cs)
* [Player](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IPlayerClient.cs)
* [Albums](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IAlbumsClient.cs)
* [Artists](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IArtistsClient.cs)
* [Personalization](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IPersonalizationClient.cs)
* [Episodes](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IEpisodesClient.cs)
* [Library](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/ILibraryClient.cs)
All calls have the [Spotify Web API documentation reference](https://developer.spotify.com/documentation/web-api/reference-beta/) attached as a remark.
## Query/Body Parameters
If an API endpoint has query or body parameters, a request model can be supplied to the method
```csharp
// No optional or required query/body parameters
// The track ID is part of the request path --> it's not treated as query/body parameter
var track = await spotify.Tracks.Get("1s6ux0lNiTziSrd7iUAADH");
// Optional query/body parameter
var track = await spotify.Tracks.Get("1s6ux0lNiTziSrd7iUAADH", new TrackRequest{
Market = "DE"
});
// Sometimes, query/body parameters are also required!
var tracks = await spotify.Tracks.GetSeveral(new TracksRequest(new List<string> {
"1s6ux0lNiTziSrd7iUAADH",
"6YlOxoHWLjH6uVQvxUIUug"
}));
```
If a query/body parameter is required, it has to be supplied in the constructor of the request model. In the background, empty/null checks are also performed to make sure required parameters are not empty/null. If it is optional, it can be supplied as a property to the request model.
## Guides
All other relevant topics are covered in the "Guides" and [Authentication Guides](auth_introduction.md) section in the sidebar!

View File

@ -0,0 +1,106 @@
---
id: implicit_grant
title: Implicit Grant
---
> Implicit grant flow is for clients that are implemented entirely using JavaScript and running in the resource owners browser. You do not need any server-side code to use it. Rate limits for requests are improved but there is no refresh token provided. This flow is described in RFC-6749.
This flow is useful for getting a user access token for a short timespan
## Existing Web-Server
If you are already in control of a Web-Server (like `ASP.NET`), you can start the flow by generating a login uri
```csharp
// Make sure "http://localhost:5000" is in your applications redirect URIs!
var loginRequest = new LoginRequest(
new Uri("http://localhost:5000"),
"ClientId",
LoginRequest.ResponseType.Token
)
{
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
};
var uri = loginRequest.ToUri();
// Redirect user to uri via your favorite web-server
```
When the user is redirected to the generated uri, he will have to login with his spotify account and confirm, that your application wants to access his user data. Once confirmed, he will be redirect to `http://localhost:5000` and the fragment identifier (`#` part of URI) will contain an access token.
:::warning
Note, this parameter is not sent to the server! You need JavaScript to access it.
:::
## Using custom Protocols
This flow can also be used with custom protocols instead of `http`/`https`. This is especially interesting for `UWP` apps, since your able to register custom protocol handlers quite easily.
![protocol handlers](/img/auth_protocol_handlers.png)
The process is very similar, you generate a uri and open it for the user
```csharp
// Make sure "spotifyapi.web.oauth://token" is in your applications redirect URIs!
var loginRequest = new LoginRequest(
new Uri("spotifyapi.web.oauth://token"),
"ClientId",
LoginRequest.ResponseType.Token
)
{
Scope = new[] { Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative }
};
var uri = loginRequest.ToUri();
// This call requires Spotify.Web.Auth
BrowserUtil.Open(uri);
```
After the user logged in and consented your app, your `UWP` app will receive a callback:
```csharp
protected override void OnActivated(IActivatedEventArgs args)
{
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs;
var publisher = Mvx.IoCProvider.Resolve<ITokenPublisherService>();
// This Uri contains your access token in the Fragment part
Console.WriteLine(eventArgs.Uri);
}
}
```
For a real example, have a look at the [Example.UWP](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.UWP), [Example.ASP](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.ASP) or [Example.ASPBlazor](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.ASPBlazor)
# Using Spotify.Web.Auth
For cross-platform CLI and desktop apps (non `UWP` apps), custom protocol handlers are sometimes not an option. The fallback here is a small cross-platform embedded web server running on `http://localhost:5000` serving javascript. The javscript will parse the fragment part of the URI and sends a request to the web server in the background. The web server then notifies your appliciation via event.
```csharp
private static EmbedIOAuthServer _server;
public static async Task Main()
{
// Make sure "http://localhost:5000/callback" is in your spotify application as redirect uri!
_server = new EmbedIOAuthServer(new Uri("http://localhost:5000/callback"), 5000);
await _server.Start();
_server.ImplictGrantReceived += OnImplictGrantReceived;
var request = new LoginRequest(_server.BaseUri, "ClientId", LoginRequest.ResponseType.Code)
{
Scope = new List<string> { Scopes.UserReadEmail }
};
BrowserUtil.Open(uri);
}
private static async Task OnImplictGrantReceived(object sender, ImplictGrantResponse response)
{
await _server.Stop();
var spotify = new SpotifyClient(response.AccessToken);
// do calls with spotify
}
```
For real examples, have a look at [Example.CLI.PersistentConfig](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.CLI.PersistentConfig) and [Example.CLI.CustomHTML](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.CLI.CustomHTML)

View File

@ -0,0 +1,24 @@
---
id: introduction
title: Introduction
---
This open source library for the Spotify Web API provides an easy to use interface for .NET based languages, like C# and VisualBasic .NET. By using it you can query general spotify catalog information (tracks, albums and playlists), manage user-related content ("My Library", create and edit playlists) and control the users music players (play, stop, transfer playback, play specific track).
## Features
From version 6 onwards, the library was built with the following features included:
* ✅ Typed responses and requests to over 74 endpoints. Complete and always up to date.
* ✅ Supports `.NET Standard 2.X`, which includes all major platforms, including mobile:
* `.NET Framework`
* `UWP`
* `.NET Core`
* `Xamarin.Forms`
* ✅ Included `HTTPClient`, but feel free to bring your own!
* ✅ Logging supported
* ✅ Retry Handlers supported
* ✅ Proxy support
* ✅ Pagination support
* ✅ All OAuth2 Authentications supported for use in `ASP .NET` **and** `CLI` apps
* ✅ Modular structure, for easy unit testing

View File

@ -0,0 +1,38 @@
---
id: logging
title: Logging
---
The library provides a way to inject your own, custom HTTP Logger. By default, no logging is performed.
```csharp
var config = SpotifyClientConfig
.CreateDefault("YourAccessToken")
.WithHTTPLogger(new YourHTTPLogger());
var spotify = new SpotifyClient(config);
```
The `IHTTPLogger` interface can be found [here](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Http/Interfaces/IHTTPLogger.cs).
## SimpleConsoleHTTPLogger
The library ships with a simple console-based logger
```csharp
var config = SpotifyClientConfig
.CreateDefault("YourAccessToken")
.WithHTTPLogger(new SimpleConsoleHTTPLogger());
var spotify = new SpotifyClient(config);
```
This logger produces a simple console output for debugging purposes:
```text
GET tracks/NotAnid []
--> BadRequest application/json { "error" : { "status" : 400, "message" : "
GET tracks/6YlOxoHWLjH6uVQvxUIUug []
--> OK application/json { "album" : { "album_type" : "album", "arti
```

View File

@ -0,0 +1,80 @@
---
id: pagination
title: Pagination
---
When working with spotify responses, you will often encounter the `Paging<T>` type.
> The offset-based paging object is a container for a set of objects. It contains a key called items (whose value is an array of the requested objects) along with other keys like previous, next and limit that can be useful in future calls.
It allows to receive only a subset of all available data and dynamically check if more requests are required. The library supports `Paging<T>` responses in two ways:
## PaginateAll
`PaginateAll` will query all remaining elements based on a first page and return all of them in a `IList`. This method should not be used for a huge amount of pages (e.g `Search` Endpoint), since it stores every response in memory.
```csharp
// we need the first page, every syntax can be used for pagination
var page = await spotify.Playlists.CurrentUsers();
var page = spotify.Playlists.CurrentUsers();
var page = () => spotify.Playlists.CurrentUsers();
// allPages will include the first page retrived before
var allPages = await spotify.PaginateAll(page);
```
## Paginate
:::info .NET Standard >= 2.1 required
:::
`Paginate` is based on `IAsyncEnumerable` and streams pages instead of returning them all in one list. This allows to break the fetching early and keeps only 1 page in memory at a time. This method should always be preferred to `PaginateAll`
```csharp
// we need the first page, every syntax can be used for pagination
var page = await spotify.Playlists.CurrentUsers();
var page = spotify.Playlists.CurrentUsers();
var page = () => spotify.Playlists.CurrentUsers();
await foreach(var item in spotify.Paginate(page))
{
Console.WriteLine(item.Name);
// you can use "break" here!
}
```
Some endpoints have nested and/or multiple paginations objects. When requesting the next page, it will not return the actual paging object but rather the root level endpoint object. A good example is the `Search` endpoint, which contains up to 5 Paging objects. Requesting the next page of the nested `Artists` paging object will return another `Search` response, instead of just `Artists`. You will need to supply a mapper function to the `Paginate` call, which returns the correct paging object:
```csharp
var search = await spotify.Search.Item(new SearchRequest(
SearchRequest.Types.All, "Jake"
));
await foreach(var item in spotify.Paginate(search.Albums, (s) => s.Albums))
{
Console.WriteLine(item.Name);
// you can use "break" here!
}
```
## Paginators
Via the interface [`IPaginator`](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/Interfaces/IPaginator.cs), it can be configured how pages are fetched. It can be configured on a global level:
```csharp
var config = SpotifyClientConfig
.CreateDefault()
.WithPaginator(new YourCustomPaginator());
```
or on method level:
```csharp
await foreach(var item in spotify.Paginate(page, new YourCustomPaginator()))
{
Console.WriteLine(item.Name);
// you can use "break" here!
}
```
By default, [`SimplePaginator`](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Clients/SimplePaginator.cs) is used. It fetches pages without any delay.

View File

@ -0,0 +1,25 @@
---
id: proxy
title: Proxy
---
The included `HTTPClient` has full proxy configuration support:
```csharp
var httpClient = new NetHttpClient(new ProxyConfig("localhost", 8080)
{
User = "",
Password = "",
SkipSSLCheck = false,
});
var config = SpotifyClientConfig
.CreateDefault()
.WithHTTPClient(httpClient);
var spotify = new SpotifyClient(config);
```
As an example, [mitmproxy](https://mitmproxy.org/) can be used to inspect the requests and responses:
![mitmproxy](/img/mitmproxy.png)

View File

@ -0,0 +1,50 @@
---
id: retry_handling
title: Retry Handling
---
In [Error Handling](error_handling.md) we already found out that requests can fail. We provide a way to automatically retry requests via retry handlers. Note, by default no retries are performed.
```csharp
var config = SpotifyClientConfig
.CreateDefault()
.WithRetryHandler(new YourCustomRetryHandler())
```
[`IRetryHandler`](https://github.com/JohnnyCrazy/SpotifyAPI-NET/blob/master/SpotifyAPI.Web/Http/Interfaces/IRetryHandler.cs) only needs one function:
```csharp
public class YourCustomRetryHandler : IRetryHandler
{
public Task<IResponse> HandleRetry(IRequest request, IResponse response, IRetryHandler.RetryFunc retry)
{
// request is the sent request and response the received response, obviously?
// don't retry:
return response;
// retry once
var newResponse = retry(request);
return newResponse;
// use retry as often as you want, make sure to return a response
}
}
```
## SimpleRetryHandler
A `SimpleRetryHandler` is included, which contains the following retry logic:
* Retries the (configurable) status codes: 500, 502, 503 and 429
* `RetryAfter` - specifies the delay between retried calls
* `RetryTimes` - specifies the maxiumum amount of performed retries per call
* `TooManyRequestsConsumesARetry` - Whether a failure of type "Too Many Requests" should use up one of the retry attempts.
```csharp
var config = SpotifyClientConfig
.CreateDefault()
.WithRetryHandler(new SimpleRetryHandler() { RetryAfter = TimeSpan.FromSeconds(1) });
var spotify = new SpotifyClient(config);
```

View File

@ -0,0 +1,56 @@
---
id: showcase
title: Showcase
---
:::info
Are you using `SpotifyAPI-NET` and would like to include your project in this list?
Send a PR via the "Edit this page" link at the end of the page!
:::
### [lidarr](https://github.com/lidarr/Lidarr) by [@lidarr](https://github.com/lidarr)
> Looks and smells like Sonarr but made for music.
### [botframework-solutions](https://github.com/microsoft/botframework-solutions) by [@microsoft](https://github.com/microsoft)
> home for a set of templates and solutions to help build advanced conversational experiences using Azure Bot Service and Bot Framework
### [Spytify](https://github.com/jwallet/spy-spotify) by [@jwallet](https://github.com/jwallet)
> Records Spotify to mp3 without ads while it plays and includes media tags to the recorded files
### [audio-band](https://github.com/dsafa/audio-band) by [@dsafa](https://github.com/dsafa)
> Display and control songs from the Windows taskbar
### [rocksmith-custom-song-toolkit](https://github.com/catara/rocksmith-custom-song-toolkit) by [@catara](https://github.com/catara)
> MASS Manipulation of Rocksmith DLC Library
### [Spofy](https://github.com/eltoncezar/Spofy) by [@eltoncezar](https://github.com/eltoncezar)
> A Spotify mini player and notifier for Windows
### [Toastify](https://github.com/aleab/toastify) by [@aleab](https://github.com/aleab)
> Toastify adds global hotkeys and toast notifications to Spotify
>
> *Forked from [nachmore/toastify](https://github.com/nachmore/toastify)*
### [Spotify Oculus](https://github.com/CaptainMorgs/spotify-oculus-release) by [@CaptainMorgs](https://github.com/CaptainMorgs)
> Unity project for interacting with Spotify in virtual reality for the Oculus Rift.
### [Songify](https://github.com/Inzaniity/Songify) by [@Inzaniity](https://github.com/Inzaniity)
> A simple tool that gets the current track from Spotify, YouTube and Nightbot.
### [Elite G19s Companion app](https://forums.frontier.co.uk/threads/elite-g19s-companion-app-with-simulated-space-traffic-control.226782/) by [@MagicMau](https://github.com/MagicMau)
> Main features include: system and station overview, play radio and podcast with audio visualizations, simulated Space Traffic Control, GPS functionality (including planetary races), an orrery view, a screenshot converter, and a news ticker.
### [ARDUINO-Spotify-Remote-Control](https://github.com/NADER11NDEU/ARDUINO-Spotify-Remote-Control) by [@NADER11NDEU](https://github.com/NADER11NDEU)
> Well, with this project we will be able to control active spotify devices with Arduino. How we gonna do that ? We will use serial communication.

View File

@ -0,0 +1,93 @@
---
id: token_swap
title: Token Swap
---
Token Swap provides an authenticatiow flow where client-side apps (like cli/desktop/mobile apps) are still able to use long-living tokens and the oppurtunity to refresh them without exposing your application's secret. This however requires a server-side part to work.
It is based on the [Authorization Code](authorization_code.md) flow and is also documented by spotify: [Token Swap and Refresh ](https://developer.spotify.com/documentation/ios/guides/token-swap-and-refresh/).
## Flow
The client uses the first part of the `Authorization Code` flow and redirects the user to spotify's login page. In this part, only the client id is required. Once the user logged in and confirmed the usage of your app, he will be redirect to a `http://localhost` server which grabs the `code` from the query parameters.
```csharp
var request = new LoginRequest("http://localhost", "ClientId", LoginRequest.ResponseType.Code)
{
Scope = new List<string> { Scopes.UserReadEmail }
};
BrowserUtil.Open(uri);
```
Now, swapping out this `code` for an `access_token` would require the app's client secret. We don't have this on the client-side. Instead, we send a request to our server, which takes care of the code swap:
```csharp
public Task GetCallback(string code)
{
var response = await new OAuthClient().RequestToken(
new TokenSwapTokenRequest("https://your-swap-server.com/swap", code)
);
var spotify = new SpotifyClient(response.AccessToken);
// Also important for later: response.RefreshToken
}
```
The server swapped out the `code` for an `access_token` and `refresh_token`. Once we realize the `access_token` expired, we can also ask the server to refresh it:
```csharp
// if response.IsExpired is true
var newResponse = await new OAuthClient().RequestToken(
new TokenSwapTokenRequest("https://your-swap-server.com/refresh", response.RefreshToken)
);
var spotify = new SpotifyClient(newResponse.AccessToken);
```
## Server Implementation
The server needs to support two endpoints, `/swap` and `/refresh` (endpoints can be named differently of course)
### Swap
The client sends a body via `application/x-www-form-urlencoded` where the received `code` is included. In cURL:
```bash
curl -X POST "https://example.com/v1/swap"\
-H "Content-Type: application/x-www-form-urlencoded"\
--data "code=AQDy8...xMhKNA"
```
The server needs to respond with content-type `application/json` and the at least the following body:
```json
{
"access_token" : "NgAagA...Um_SHo",
"expires_in" : "3600",
"refresh_token" : "NgCXRK...MzYjw"
}
```
### Refresh
The client sends a body via `application/x-www-form-urlencoded` where the received `refresh_token` is included. In cURL:
```bash
curl -X POST "https://example.com/v1/refresh"\
-H "Content-Type: application/x-www-form-urlencoded"\
--data "refresh_token=NgCXRK...MzYjw"
```
The server needs to respond with content-type `application/json` and the at least the following body:
```json
{
"access_token" : "NgAagA...Um_SHo",
"expires_in" : "3600"
}
```
## Example
An example server has been implemented in NodeJS with a .NET CLI client, located at [Example.TokenSwap](https://github.com/JohnnyCrazy/SpotifyAPI-NET/tree/master/SpotifyAPI.Web.Examples/Example.TokenSwap)

View File

@ -0,0 +1,35 @@
---
id: unit_testing
title: Unit Testing
---
The modular structure of the library makes it easy to mock the API when unit testing. Consider the following method:
```csharp
public static async Task<bool> IsAdmin(IUserProfileClient userProfileClient)
{
// get loggedin user
var user = await userProfileClient.Current();
// only my user id is an admin
return user.Id == "1122095781";
}
```
Using `Moq`, this can be tested without doing any network requests:
```csharp
[Test]
public async Task IsAdmin_SuccessTest()
{
var userProfileClient = new Mock<IUserProfileClient>();
userProfileClient.Setup(u => u.Current()).Returns(
Task.FromResult(new PrivateUser
{
Id = "1122095781"
})
);
Assert.AreEqual(true, await IsAdmin(userProfileClient.Object));
}
```

View File

@ -1,7 +0,0 @@
---
sidebar: false
---
# Examples
This page is currently empty. Do you have useful examples? Please open a PR

View File

@ -0,0 +1,85 @@
const versions = require('./versions.json');
module.exports = {
title: 'SpotifyAPI-NET',
tagline: '🔊 A Client for the Spotify Web API, written in C#/.NET',
url: 'https://johnnycrazy.github.io/SpotifyAPI-NET',
baseUrl: '/',
favicon: 'img/favicon.ico',
organizationName: 'JohnnyCrazy', // Usually your GitHub org/user name.
projectName: 'SpotifyAPI-NET', // Usually your repo name.
themeConfig: {
sidebarCollapsible: true,
prism: {
additionalLanguages: ['csharp'],
},
navbar: {
title: 'SpotifyAPI-NET',
logo: {
alt: 'SpotifyAPI-NET',
src: 'img/logo.svg',
},
links: [
{
activeBasePath: 'docs',
label: 'Docs',
position: 'left',
items: [
{
label: 'Latest/Next',
to: 'docs/next/introduction',
},
{
label: versions[0],
to: 'docs/home',
},
...versions.slice(1).map((version) => ({
label: version,
to: `docs/${version}/home`,
}))
]
},
{ to: 'news', label: 'News', position: 'left' },
{
href: 'https://github.com/JohnnyCrazy/SpotifyAPI-NET',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
copyright: `Copyright © ${new Date().getFullYear()} Jonas Dellinger. Built with Docusaurus.`,
},
},
presets: [
[
'@docusaurus/preset-classic',
{
docs: {
sidebarPath: require.resolve('./sidebars.js'),
// Please change this to your repo.
editUrl:
'https://github.com/JohnnyCrazy/SpotifyAPI-NET/edit/master/SpotifyAPI.Docs/',
showLastUpdateAuthor: true,
showLastUpdateTime: true,
},
blog: {
path: 'news',
routeBasePath: 'news',
showReadingTime: true,
feedOptions: {
type: 'all',
copyright: `Copyright © ${new Date().getFullYear()} Jonas Dellinger.`,
},
// Please change this to your repo.
editUrl:
'https://github.com/JohnnyCrazy/SpotifyAPI-NET/edit/master/SpotifyAPI.Docs/blog/',
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
},
],
],
};

View File

@ -1,16 +1,31 @@
{
"name": "SpotifyAPI.Docs",
"version": "0.0.1",
"main": "index.js",
"repository": "https://github.com/JohnnyCrazy/SpotifyAPI-NET",
"author": "Jonas Dellinger",
"license": "CUSTOM",
"name": "spotify-api-docs",
"version": "0.0.0",
"private": true,
"devDependencies": {
"vuepress": "^1.4.0"
},
"scripts": {
"dev": "vuepress dev docs",
"build": "vuepress build docs"
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy"
},
"dependencies": {
"@docusaurus/core": "^2.0.0-alpha.56",
"@docusaurus/preset-classic": "^2.0.0-alpha.56",
"classnames": "^2.2.6",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-github-btn": "^1.2.0"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@ -0,0 +1,196 @@
You can write content using [GitHub-flavored Markdown syntax](https://github.github.com/gfm/).
## Markdown Syntax
To serve as an example page when styling markdown based Docusaurus sites.
## Headers
# H1 - Create the best documentation
## H2 - Create the best documentation
### H3 - Create the best documentation
#### H4 - Create the best documentation
##### H5 - Create the best documentation
###### H6 - Create the best documentation
---
## Emphasis
Emphasis, aka italics, with _asterisks_ or _underscores_.
Strong emphasis, aka bold, with **asterisks** or **underscores**.
Combined emphasis with **asterisks and _underscores_**.
Strikethrough uses two tildes. ~~Scratch this.~~
---
## Lists
1. First ordered list item
1. Another item ⋅⋅\* Unordered sub-list.
1. Actual numbers don't matter, just that it's a number ⋅⋅1. Ordered sub-list
1. And another item.
⋅⋅⋅You can have properly indented paragraphs within list items. Notice the blank line above, and the leading spaces (at least one, but we'll use three here to also align the raw Markdown).
⋅⋅⋅To have a line break without a paragraph, you will need to use two trailing spaces.⋅⋅ ⋅⋅⋅Note that this line is separate, but within the same paragraph.⋅⋅ ⋅⋅⋅(This is contrary to the typical GFM line break behaviour, where trailing spaces are not required.)
- Unordered list can use asterisks
* Or minuses
- Or pluses
---
## Links
[I'm an inline-style link](https://www.google.com)
[I'm an inline-style link with title](https://www.google.com "Google's Homepage")
[I'm a reference-style link][arbitrary case-insensitive reference text]
[I'm a relative reference to a repository file](../blob/master/LICENSE)
[You can use numbers for reference-style link definitions][1]
Or leave it empty and use the [link text itself].
URLs and URLs in angle brackets will automatically get turned into links. http://www.example.com or <http://www.example.com> and sometimes example.com (but not on Github, for example).
Some text to show that the reference links can follow later.
[arbitrary case-insensitive reference text]: https://www.mozilla.org
[1]: http://slashdot.org
[link text itself]: http://www.reddit.com
---
## Images
Here's our logo (hover to see the title text):
Inline-style: ![alt text](https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 1')
Reference-style: ![alt text][logo]
[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png 'Logo Title Text 2'
---
## Code
```javascript
var s = 'JavaScript syntax highlighting';
alert(s);
```
```python
s = "Python syntax highlighting"
print(s)
```
```
No language indicated, so no syntax highlighting.
But let's throw in a <b>tag</b>.
```
```js {2}
function highlightMe() {
console.log('This line can be highlighted!');
}
```
---
## Tables
Colons can be used to align columns.
| Tables | Are | Cool |
| ------------- | :-----------: | -----: |
| col 3 is | right-aligned | \$1600 |
| col 2 is | centered | \$12 |
| zebra stripes | are neat | \$1 |
There must be at least 3 dashes separating each header cell. The outer pipes (|) are optional, and you don't need to make the raw Markdown line up prettily. You can also use inline Markdown.
| Markdown | Less | Pretty |
| -------- | --------- | ---------- |
| _Still_ | `renders` | **nicely** |
| 1 | 2 | 3 |
---
## Blockquotes
> Blockquotes are very handy in email to emulate reply text. This line is part of the same quote.
Quote break.
> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can _put_ **Markdown** into a blockquote.
---
## Inline HTML
<dl>
<dt>Definition list</dt>
<dd>Is something people use sometimes.</dd>
<dt>Markdown in HTML</dt>
<dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd>
</dl>
---
## Line Breaks
Here's a line for us to start with.
This line is separated from the one above by two newlines, so it will be a _separate paragraph_.
This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the _same paragraph_.
---
## Admonitions
:::note
This is a note
:::
:::tip
This is a tip
:::
:::important
This is important
:::
:::caution
This is a caution
:::
:::warning
This is a warning
:::

View File

@ -0,0 +1,46 @@
module.exports = {
docs: {
'SpotifyAPI-NET': [
'introduction',
'getting_started',
{
type: 'category',
label: 'Guides',
items: [
'error_handling',
'configuration',
'logging',
'proxy',
'pagination',
'retry_handling',
'unit_testing'
]
},
{
type: 'category',
label: 'Authentication Guides',
items: [
'auth_introduction',
'client_credentials',
'implicit_grant',
'authorization_code',
'token_swap'
]
},
'showcase',
{
type: 'category',
label: 'Examples',
items: [
'example_asp',
'example_blazor_wasm',
'example_blazor',
'example_cli_custom_html',
'example_cli_persistent_config',
'example_token_swap',
'example_uwp'
]
},
]
}
};

View File

@ -0,0 +1,25 @@
/* stylelint-disable docusaurus/copyright-header */
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #1db954;
--ifm-color-primary-dark: #1aa74c;
--ifm-color-primary-darker: #199d47;
--ifm-color-primary-darkest: #14823b;
--ifm-color-primary-light: #20cb5c;
--ifm-color-primary-lighter: #21d561;
--ifm-color-primary-lightest: #37e072;
--ifm-code-font-size: 95%;
}
.docusaurus-highlight-code-line {
background-color: rgb(72, 77, 91);
display: block;
margin: 0 calc(-1 * var(--ifm-pre-padding));
padding: 0 var(--ifm-pre-padding);
}

View File

@ -0,0 +1,52 @@
import React from "react";
import CodeBlock from '@theme/CodeBlock'
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
const installCodeNuget =
`Install-Package SpotifyAPI.Web -Version 6.0.0-beta.1
# Optional Auth module, which includes an embedded HTTP Server for OAuth2
Install-Package SpotifyAPI.Web.Auth -Version 6.0.0-beta.1
`;
const installReference =
`<PackageReference Include="SpotifyAPI.Web" Version="6.0.0-beta.1" />
<!-- Optional Auth module, which includes an embedded HTTP Server for OAuth2 -->
<PackageReference Include="SpotifyAPI.Web.Auth" Version="6.0.0-beta.1" />
`;
const installCodeCLI =
`dotnet add package SpotifyAPI.Web --version 6.0.0-beta.1
# Optional Auth module, which includes an embedded HTTP Server for OAuth2
dotnet add package SpotifyAPI.Web.Auth --version 6.0.0-beta.1
`;
const InstallInstructions = () => {
return (<div style={{ padding: '30px' }}>
<Tabs
defaultValue="cli"
values={[
{ label: '.NET CLI', value: 'cli' },
{ label: 'Package Manager', value: 'nuget' },
{ label: 'Package Reference', value: 'reference' }
]}>
<TabItem value="cli">
<CodeBlock metastring="shell" className="shell">
{installCodeCLI}
</CodeBlock>
</TabItem>
<TabItem value="nuget">
<CodeBlock metastring="shell" className="shell">
{installCodeNuget}
</CodeBlock>
</TabItem>
<TabItem value="reference">
<CodeBlock metastring="xml" className="xml">
{installReference}
</CodeBlock>
</TabItem>
</Tabs>
</div>);
}
export default InstallInstructions;

View File

@ -0,0 +1,157 @@
import React from 'react';
import classnames from 'classnames';
import Layout from '@theme/Layout';
import CodeBlock from '@theme/CodeBlock'
import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl';
import styles from './styles.module.css';
import GitHubButton from 'react-github-btn'
import InstallInstructions from '../install_instructions';
const exampleCode =
`var spotify = new SpotifyClient("YourAccessToken");
var me = await spotify.UserProfile.Current();
Console.WriteLine($"Hello there {me.DisplayName}");
await foreach(
var playlist in spotify.Paginate(spotify.Playlists.CurrentUsers())
)
{
Console.WriteLine(playlist.Name);
}`;
const features = [
{
title: <>Sane Defaults - Easy To Configure</>,
imageUrl: 'img/undraw_preferences_uuo2.svg',
description: () => (
<>
<code>SpotifyAPI-NET</code> allows you to quickly integrate with Spotify's Web API by supplying sane configuration defaults from the start. Later on, behaviour can be customized using extensive configuration possibilities.
</>
),
},
{
title: <>All API Calls Integrated</>,
imageUrl: 'img/undraw_project_completed_w0oq.svg',
description: () => (
<>
The Spotify Web API consists of over 74 API calls. <code>SpotifyAPI-NET</code> provides fully typed requests/responses for all of them.
</>
),
},
{
title: <>.NET Standard 2.X</>,
imageUrl: 'img/undraw_Devices_e67q.svg',
description: () => (
<>
With the support of .NET Standard 2.X, <code>SpotifyAPI-NET</code> runs on many platforms, including .NET Core, UWP and Xamarin.Forms (Windows, Android, iOS and Mac)
</>
),
},
{
title: <>Testable</>,
imageUrl: 'img/undraw_QA_engineers_dg5p.svg',
description: () => (
<>
<code>SpotifyAPI-NET</code> is built on a modular structure, which allows easy testing through mocks and stubs. Learn more by visiting the <Link to={useBaseUrl('docs/next/testing')}>Testing Guide</Link>
</>
),
},
];
function Feature({ imageUrl, title, description }) {
const imgUrl = useBaseUrl(imageUrl);
return (
<div className={classnames('col col--4', styles.feature)}>
{imgUrl && (
<div className="text--center">
<img className={styles.featureImage} src={imgUrl} alt={title} />
</div>
)}
<h3>{title}</h3>
<p>{description()}</p>
</div>
);
}
function Home() {
const context = useDocusaurusContext();
const { siteConfig = {} } = context;
return (
<Layout
title={`${siteConfig.title}`}
description="Documentation for the C# .NET SpotifyAPI-NET Library">
<header className={classnames('hero hero--primary', styles.heroBanner)}>
<div className="container">
<div className="row">
<div className="col col--5">
<img src="img/logo.svg" width="120" height="120" />
<h1 className="hero__title">
{siteConfig.title}
<span style={{ marginLeft: '50px' }} />
<GitHubButton
href="https://github.com/JohnnyCrazy/SpotifyAPI-NET"
data-icon="octicon-star"
data-size="large"
data-show-count="true"
aria-label="Star JohnnyCrazy/SpotifyAPI-NET on GitHub">Star</GitHubButton>
<br />
<a href="https://www.nuget.org/packages/SpotifyAPI.Web/" rel="noopener noreferrer">
<img
alt="Nuget"
src="https://img.shields.io/nuget/v/SpotifyAPI.Web?label=SpotifyAPI.Web&style=flat-square">
</img>{' '}
</a>
<a href="https://www.nuget.org/packages/SpotifyAPI.Web.Auth/" rel="noopener noreferrer">
<img
alt="Nuget"
src="https://img.shields.io/nuget/v/SpotifyAPI.Web.Auth?label=SpotifyAPI.Web.Auth&style=flat-square">
</img>
</a>
</h1>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className={classnames(
'button button--outline button--secondary button--lg',
styles.getStarted,
)}
to={useBaseUrl('docs/next/introduction')}>
Get Started
</Link>
</div>
</div>
<div className={classnames('col col--7', styles.exampleCode)}>
<CodeBlock metastring="csharp" className="csharp">
{exampleCode}
</CodeBlock>
</div>
</div>
</div>
</header>
<main>
<div className="container">
<h2 style={{ textAlign: 'center', marginTop: '30px' }}>Try it out now</h2>
<InstallInstructions />
</div>
{features && features.length && (
<section className={styles.features}>
<div className="container">
<div className="row">
{features.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
)}
</main>
</Layout>
);
}
export default Home;

View File

@ -0,0 +1,41 @@
/* stylelint-disable docusaurus/copyright-header */
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding : 4rem 0;
text-align: center;
position : relative;
overflow : hidden;
}
.exampleCode {
margin-top: 20px;
text-align: left;
}
@media screen and (max-width: 966px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display : flex;
align-items : center;
justify-content: center;
}
.features {
display : flex;
align-items: center;
padding : 2rem 0;
width : 100%;
}
.featureImage {
height: 200px;
width : 200px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 950 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1 @@
<svg id="d2ec24bc-8034-4145-bbaa-ac0081ffc938" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="814.27" height="615.44" viewBox="0 0 814.27 615.44"><defs><linearGradient id="5868a8ae-9641-4bfb-a3e0-87c9707ff455" x1="684.96" y1="708.77" x2="684.96" y2="685.5" gradientTransform="translate(0.36 -0.19)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient><linearGradient id="2dc439d3-206c-40ba-88b3-0b09748d0dcc" x1="685.09" y1="578.57" x2="685.09" y2="157.81" gradientTransform="matrix(1, 0, 0, 1.04, -0.63, -19.87)" xlink:href="#5868a8ae-9641-4bfb-a3e0-87c9707ff455"/><linearGradient id="33b509d6-5684-4d10-a2d7-992d23792513" x1="439.04" y1="749.29" x2="439.04" y2="297.39" gradientTransform="matrix(1, 0, 0, 1.02, 0.11, -11.28)" xlink:href="#5868a8ae-9641-4bfb-a3e0-87c9707ff455"/><linearGradient id="c7762558-8ab5-4e43-8750-831a04cd0054" x1="439.56" y1="710.94" x2="439.56" y2="331.91" gradientTransform="matrix(1, 0, 0, 1.02, 0.11, -11.28)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-opacity="0.09"/><stop offset="0.55" stop-opacity="0.07"/><stop offset="1" stop-opacity="0.02"/></linearGradient><linearGradient id="fd9cbc55-269a-4233-8dd0-3aded2a6d9e7" x1="290.89" y1="757.14" x2="290.89" y2="406.12" gradientTransform="matrix(1, 0, 0, 1.02, 0.19, -12.23)" xlink:href="#5868a8ae-9641-4bfb-a3e0-87c9707ff455"/></defs><title>Devices</title><path d="M765.64,538.41S781.71,648.5,875.73,685.08l-190.34.26-190.34.26c93.92-36.83,109.71-147,109.71-147Z" transform="translate(-192.86 -142.28)" fill="#e0e0e0"/><rect x="493.16" y="685.34" width="384.26" height="23.26" transform="translate(-193.82 -141.33) rotate(-0.08)" fill="url(#5868a8ae-9641-4bfb-a3e0-87c9707ff455)"/><rect x="495.07" y="685.34" width="380.67" height="17.94" transform="translate(-193.82 -141.33) rotate(-0.08)" fill="#f5f5f5"/><path d="M994.22,142.28l-619.08.85c-6.85,0-12.4,5.83-12.39,13l.48,361.19,0,12c0,27.44,21.33,49.66,47.55,49.62l548.93-.76c26.23,0,47.46-22.31,47.42-49.75l0-12-.48-361.19C1006.64,148.07,1001.07,142.27,994.22,142.28Z" transform="translate(-192.86 -142.28)" fill="url(#2dc439d3-206c-40ba-88b3-0b09748d0dcc)"/><path d="M381.51,153.13H988.35a10.75,10.75,0,0,1,10.75,10.75V509.16a0,0,0,0,1,0,0H370.75a0,0,0,0,1,0,0V163.88a10.75,10.75,0,0,1,10.75-10.75Z" transform="matrix(1, 0, 0, 1, -193.32, -141.33)" fill="#fff"/><path d="M958.26,567.51l-546,.75A41.15,41.15,0,0,1,371,527.17l0-17.57,628.35-.87,0,17.58A41.15,41.15,0,0,1,958.26,567.51Z" transform="translate(-192.86 -142.28)" fill="#f5f5f5"/><rect x="401.81" y="181.96" width="568.62" height="287.41" transform="translate(-193.31 -141.33) rotate(-0.08)" fill="#6c63ff"/><rect x="291.13" y="290.71" width="296" height="458.84" rx="17.17" ry="17.17" transform="translate(-193.58 -141.67) rotate(-0.08)" fill="url(#33b509d6-5684-4d10-a2d7-992d23792513)"/><rect x="311.85" y="325.76" width="255.62" height="384.84" transform="translate(-193.58 -141.67) rotate(-0.08)" fill="url(#c7762558-8ab5-4e43-8750-831a04cd0054)"/><rect x="300.13" y="297.65" width="277.99" height="437.71" rx="17.17" ry="17.17" transform="translate(-193.57 -141.67) rotate(-0.08)" fill="#fff"/><rect x="319.59" y="331.09" width="240.06" height="367.12" transform="matrix(1, 0, 0, 1, -193.57, -141.67)" fill="#6c63ff"/><ellipse cx="440.54" cy="716.44" rx="9.89" ry="10.13" transform="matrix(1, 0, 0, 1, -193.85, -141.67)" fill="#dbdbdb"/><ellipse cx="397.81" cy="315.27" rx="3.3" ry="3.38" transform="translate(-193.3 -141.73) rotate(-0.08)" fill="#dbdbdb"/><rect x="414.29" y="313.19" width="42.87" height="3.38" rx="1.43" ry="1.43" transform="translate(-193.3 -141.68) rotate(-0.08)" fill="#dbdbdb"/><rect x="193.08" y="400.71" width="195.96" height="356.89" rx="10" ry="10" transform="translate(-193.66 -141.88) rotate(-0.08)" fill="url(#fd9cbc55-269a-4233-8dd0-3aded2a6d9e7)"/><rect x="197.6" y="406.59" width="186.94" height="342.74" rx="10" ry="10" transform="translate(-193.66 -141.88) rotate(-0.08)" fill="#fff"/><rect x="218.69" y="430.82" width="144.73" height="278.92" transform="translate(-193.65 -141.88) rotate(-0.08)" fill="#6c63ff"/><ellipse cx="291.26" cy="729.83" rx="11.54" ry="11.82" transform="matrix(1, 0, 0, 1, -193.87, -141.88)" fill="#dbdbdb"/><ellipse cx="268.93" cy="417.85" rx="2.31" ry="2.36" transform="translate(-193.44 -141.91) rotate(-0.08)" fill="#dbdbdb"/><rect x="279.31" y="416.03" width="35.77" height="3.55" rx="1.5" ry="1.5" transform="translate(-193.44 -141.87) rotate(-0.08)" fill="#dbdbdb"/></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 36 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -1,9 +1,8 @@
---
sidebar: false
id: authorization_code
title: Authorization Code
---
# AutorizationCodeAuth
This way is **not recommended** for client-side apps and requires server-side code to run securely.
With this approach, you first get a code which you need to trade against the access-token.
In this exchange you need to provide your Client-Secret and because of that it's not recommended.
@ -40,7 +39,7 @@ static async void Main(string[] args)
## Token Refresh
Once the `AccessToken` is expired, you can use your `RefreshToken` to get a new one.
Once the `AccessToken` is expired, you can use your `RefreshToken` to get a new one.
In this procedure, no HTTP Server is needed in the background and a single HTTP Request is made.
```csharp
@ -52,4 +51,4 @@ if(token.IsExpired())
api.AccessToken = newToken.AccessToken
api.TokenType = newToken.TokenType
}
```
```

View File

@ -1,9 +1,8 @@
---
sidebar: false
id: client_credentials
title: Client Credentials
---
# ClientCredentialsAuth
With this approach, you make a POST Request with a base64 encoded string (consists of ClientId + ClientSecret). You will directly get the token (Without a local HTTP Server), but it will expire and can't be refreshed.
If you want to use it securely, you would need to do it all server-side.
**NOTE:** You will only be able to query non-user-related information e.g search for a Track.

View File

@ -1,4 +1,7 @@
# Getting Started
---
id: getting_started
title: Getting Started
---
## Auth-Methods
@ -9,8 +12,8 @@ Before you start, install `SpotifyAPI.Web.Auth` and create an application at Spo
After you have created your Application, you will have following important values:
| Name | Description |
| -------------- |------------------------- |
|Name|Description|
|--------------|-------------------------|-------------------------|
| **Client_Id** | This is your client_id, you don't have to hide it|
| **Client_Secret** | Never use this in one of your client-side apps! Keep it secret! |
| **Redirect URIs** | Some of auth flows require that you set the correct redirect URI |
@ -19,11 +22,12 @@ Now you can start with the user-authentication. Spotify provides 3 ways (4 if yo
* [ImplicitGrantAuth](implicit_grant.md)
* [TokenSwapAuth](token_swap.md) (**Recommended**, server-side code mandatory, most secure method. The necessary code is shown here so you do not have to code it yourself.)
* [AutorizationCodeAuth](authorization_code.md)
* [AutorizationCodeAuth](authorization_code.md) (Not recommended, server-side code needed, else it's unsecure)
* [ClientCredentialsAuth](client_credentials.md)
* [TokenSwapAuth](token_swap.md)
* [ClientCredentialsAuth](client_credentials.md) (Not recommended, server-side code needed, else it's unsecure)
Overview:
![Overview](http://i.imgur.com/uf3ahTl.png)

View File

@ -1,9 +1,9 @@
---
sidebar: false
id: implicit_grant
title: Implicit Grant
sidebar_label: Implicit Grant
---
# ImplicitGrantAuth
This way is **recommended** and the only auth-process which does not need a server-side exchange of keys. With this approach, you directly get a Token object after the user authed your application.
You won't be able to refresh the token. If you want to use the internal Http server, please add "http://localhost:YOURPORT" to your application redirect URIs.

View File

@ -1,4 +1,7 @@
# TokenSwapAuth
---
id: token_swap
title: Token Swap
---
This way uses server-side code or at least access to an exchange server, otherwise, compared to other
methods, it is impossible to use.

View File

@ -1,6 +1,8 @@
# SpotifyAPI-NET Docs
## About
---
id: home
title: SpotifyAPI-NET
sidebar_label: Home
---
This project, written in C#/.NET, provides 2 libraries for an easier usage of the Spotify Web API
@ -20,8 +22,8 @@ This project, written in C#/.NET, provides 2 libraries for an easier usage of th
* Via NuGet Package:
```bash
Install-Package SpotifyAPI.Web
Install-Package SpotifyAPI.Web.Auth
Install-Package SpotifyAPI.Web -Version 5.1.1
Install-Package SpotifyAPI.Web.Auth -Version 5.1.1
```
* Download the latest binaries on the [GitHub Release Page](https://github.com/JohnnyCrazy/SpotifyAPI-NET/releases) and add it to your Project
* Clone the Repo and build the project yourself.
@ -30,8 +32,8 @@ Install-Package SpotifyAPI.Web.Auth
## Getting Started
* [SpotifyAPI.Web](/web/getting_started/)
* [SpotifyAPI.Web.Auth](/auth/getting_started/)
* [SpotifyAPI.Web](web/getting_started.md)
* [SpotifyAPI.Web.Auth](auth/getting_started)
## Projects
@ -72,7 +74,7 @@ Install-Package SpotifyAPI.Web.Auth
### [Songify](https://github.com/Inzaniity/Songify) by [@Inzaniity](https://github.com/Inzaniity)
> A simple tool that gets the current track from Spotify, YouTube and Nightbot.
> A simple tool that gets the current track from Spotify, YouTube and Nightbot.
### [Elite G19s Companion app](https://forums.frontier.co.uk/threads/elite-g19s-companion-app-with-simulated-space-traffic-control.226782/) by [@MagicMau](https://github.com/MagicMau)

View File

@ -1,4 +1,8 @@
# Albums
---
id: albums
title: Albums
sidebar_label: Albums
---
## GetAlbumTracks
> Get Spotify catalog information about an album's tracks. Optional parameters can be used to limit the number of tracks returned.

View File

@ -1,4 +1,8 @@
# Artists
---
id: artists
title: Artists
sidebar_label: Artists
---
## GetArtist

View File

@ -1,4 +1,8 @@
# Browse
---
id: browse
title: Browse
sidebar_label: Browse
---
## GetFeaturedPlaylists

View File

@ -1,4 +1,8 @@
# Follow
---
id: follow
title: Follow
sidebar_label: Follow
---
## Follow

View File

@ -1,9 +1,9 @@
---
sidebarDepth: 2
id: getting_started
title: Getting Started
sidebar_label: Getting Started
---
# Getting Started
This API provides full access to the new SpotifyWebAPI introduced [here](https://developer.spotify.com/web-api/).
With it, you can search for Tracks/Albums/Artists and also get User-based information.
It's also possible to create new playlists and add tracks to it.
@ -52,12 +52,6 @@ _spotify = new SpotifyWebAPI()
For more info, visit the [Getting Started of SpotifyAPI.Web.Auth](/auth/getting_started/)
---
## Examples
A list of small examples can be found [here](/web/examples/). Do you think a specific example is missing? Feel free to open a PR.mdIssue!
---
## Error-Handling
Every API-Call returns a reponse-model which consists of base-error model. To check if a specific API-Call was successful, use the following approach:
@ -85,8 +79,6 @@ public async void Test()
Note that the `synchronous` call will block the current Thread!
---
## API-Reference
### Albums

View File

@ -1,4 +1,8 @@
# Library
---
id: library
title: Library
sidebar_label: Library
---
## SaveTracks

View File

@ -1,4 +1,8 @@
# Personalization
---
id: personalization
title: Personalization
sidebar_label: Personalization
---
## GetUsersTopTracks

View File

@ -1,4 +1,8 @@
# Player
---
id: player
title: Player
sidebar_label: Player
---
## GetDevices
> Get information about a users available devices.

View File

@ -1,4 +1,8 @@
# Playlists
---
id: playlists
title: Playlists
sidebar_label: Playlists
---
## GetUserPlaylists

View File

@ -1,4 +1,8 @@
# Profiles
---
id: profiles
title: Profiles
sidebar_label: Profiles
---
## GetPrivateProfile

View File

@ -1,9 +1,9 @@
---
sidebar: false
id: proxy
title: Proxy Settings
sidebar_label: Proxy Settings
---
# Proxy Settings
You can forward your proxy settings to the web api by using a field in the `SpotifyLocalAPIConfig`.
```csharp

View File

@ -1,4 +1,8 @@
# Search
---
id: search
title: Search
sidebar_label: Search
---
## SearchItems

View File

@ -1,4 +1,8 @@
# Tracks
---
id: tracks
title: Tracks
sidebar_label: Tracks
---
## GetSeveralTracks

View File

@ -1,4 +1,8 @@
# Utilities
---
id: utilities
title: Utilities
sidebar_label: Utilities
---
## Paging-Methods

View File

@ -0,0 +1,102 @@
{
"version-5.1.1/someSidebar": [
{
"type": "category",
"label": "SpotifyAPI-NET",
"items": [
{
"type": "doc",
"id": "version-5.1.1/home"
}
]
},
{
"type": "category",
"label": "SpotifyAPI-NET.Web",
"items": [
{
"type": "doc",
"id": "version-5.1.1/web/getting_started"
},
{
"type": "doc",
"id": "version-5.1.1/web/albums"
},
{
"type": "doc",
"id": "version-5.1.1/web/artists"
},
{
"type": "doc",
"id": "version-5.1.1/web/browse"
},
{
"type": "doc",
"id": "version-5.1.1/web/follow"
},
{
"type": "doc",
"id": "version-5.1.1/web/library"
},
{
"type": "doc",
"id": "version-5.1.1/web/personalization"
},
{
"type": "doc",
"id": "version-5.1.1/web/player"
},
{
"type": "doc",
"id": "version-5.1.1/web/playlists"
},
{
"type": "doc",
"id": "version-5.1.1/web/profiles"
},
{
"type": "doc",
"id": "version-5.1.1/web/proxy"
},
{
"type": "doc",
"id": "version-5.1.1/web/search"
},
{
"type": "doc",
"id": "version-5.1.1/web/tracks"
},
{
"type": "doc",
"id": "version-5.1.1/web/utilities"
}
]
},
{
"type": "category",
"label": "SpotifyAPI-NET.Auth",
"items": [
{
"type": "doc",
"id": "version-5.1.1/auth/getting_started"
},
{
"type": "doc",
"id": "version-5.1.1/auth/implicit_grant"
},
{
"type": "doc",
"id": "version-5.1.1/auth/authorization_code"
},
{
"type": "doc",
"id": "version-5.1.1/auth/client_credentials"
},
{
"type": "doc",
"id": "version-5.1.1/auth/token_swap"
}
]
}
]
}

View File

@ -0,0 +1,3 @@
[
"5.1.1"
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
namespace SpotifyAPI.Web.Auth
{
[System.Serializable]
public class AuthException : System.Exception
{
public AuthException(string error, string state)
{
Error = error;
State = state;
}
public AuthException(string message) : base(message) { }
public AuthException(string message, System.Exception inner) : base(message, inner) { }
protected AuthException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
public string? Error { get; set; }
public string? State { get; set; }
}
}

View File

@ -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;
}
}
}
}

View File

@ -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)
{ }
}
}

View File

@ -0,0 +1,26 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System;
namespace SpotifyAPI.Web.Auth
{
public static class BrowserUtil
{
public static void Open(Uri uri)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
var uriStr = uri.ToString().Replace("&", "^&");
Process.Start(new ProcessStartInfo($"cmd", $"/c start {uriStr}"));
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", uri.ToString());
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", uri.ToString());
}
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,97 @@
using System.Reflection;
using System.Threading;
using System.Web;
using System.Globalization;
using System.Text;
using System;
using System.Threading.Tasks;
using EmbedIO;
using EmbedIO.Actions;
namespace SpotifyAPI.Web.Auth
{
public class EmbedIOAuthServer : IAuthServer
{
public event Func<object, AuthorizationCodeResponse, Task>? AuthorizationCodeReceived;
public event Func<object, ImplictGrantResponse, Task>? ImplictGrantReceived;
private const string AssetsResourcePath = "SpotifyAPI.Web.Auth.Resources.auth_assets";
private const string DefaultResourcePath = "SpotifyAPI.Web.Auth.Resources.default_site";
private CancellationTokenSource? _cancelTokenSource;
private readonly WebServer _webServer;
public EmbedIOAuthServer(Uri baseUri, int port)
: this(baseUri, port, Assembly.GetExecutingAssembly(), DefaultResourcePath) { }
public EmbedIOAuthServer(Uri baseUri, int port, Assembly resourceAssembly, string resourcePath)
{
Ensure.ArgumentNotNull(baseUri, nameof(baseUri));
BaseUri = baseUri;
Port = port;
_webServer = new WebServer(port)
.WithModule(new ActionModule("/", HttpVerbs.Post, (ctx) =>
{
var query = ctx.Request.QueryString;
if (query["error"] != null)
{
throw new AuthException(query["error"], query["state"]);
}
var requestType = query.Get("request_type");
if (requestType == "token")
{
ImplictGrantReceived?.Invoke(this, new ImplictGrantResponse(
query["access_token"], query["token_type"], int.Parse(query["expires_in"])
)
{
State = query["state"]
});
}
if (requestType == "code")
{
AuthorizationCodeReceived?.Invoke(this, new AuthorizationCodeResponse(query["code"])
{
State = query["state"]
});
}
return ctx.SendStringAsync("OK", "text/plain", Encoding.UTF8);
}))
.WithEmbeddedResources("/auth_assets", Assembly.GetExecutingAssembly(), AssetsResourcePath)
.WithEmbeddedResources(baseUri.AbsolutePath, resourceAssembly, resourcePath);
}
public Uri BaseUri { get; }
public int Port { get; }
public Task Start()
{
_cancelTokenSource = new CancellationTokenSource();
_webServer.Start(_cancelTokenSource.Token);
return Task.CompletedTask;
}
public Task Stop()
{
_cancelTokenSource?.Cancel();
return Task.CompletedTask;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_webServer?.Dispose();
}
}
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Threading.Tasks;
namespace SpotifyAPI.Web.Auth
{
public interface IAuthServer : IDisposable
{
event Func<object, AuthorizationCodeResponse, Task> AuthorizationCodeReceived;
event Func<object, ImplictGrantResponse, Task> ImplictGrantReceived;
Task Start();
Task Stop();
Uri BaseUri { get; }
}
}

View File

@ -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)
{ }
}
}

View File

@ -0,0 +1,15 @@
namespace SpotifyAPI.Web.Auth
{
public class AuthorizationCodeResponse
{
public AuthorizationCodeResponse(string code)
{
Ensure.ArgumentNotNullOrEmptyString(code, nameof(code));
Code = code;
}
public string Code { get; set; } = default!;
public string State { get; set; } = default!;
}
}

View File

@ -0,0 +1,30 @@
using System;
namespace SpotifyAPI.Web.Auth
{
public class ImplictGrantResponse
{
public ImplictGrantResponse(string accessToken, string tokenType, int expiresIn)
{
Ensure.ArgumentNotNullOrEmptyString(accessToken, nameof(accessToken));
Ensure.ArgumentNotNullOrEmptyString(tokenType, nameof(tokenType));
AccessToken = accessToken;
TokenType = tokenType;
ExpiresIn = expiresIn;
}
public string AccessToken { get; set; } = default!;
public string TokenType { get; set; } = default!;
public int ExpiresIn { get; set; }
public string State { get; set; } = default!;
/// <summary>
/// Auto-Initalized to UTC Now
/// </summary>
/// <value></value>
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public bool IsExpired { get => CreatedAt.AddSeconds(ExpiresIn) <= DateTime.UtcNow; }
}
}

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -1,77 +0,0 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<link rel="stylesheet" href="css/bulma.min.css"/>
<style>
p {
font-size: 1.0rem;
}
.demo-image {
margin: 30px;
border: 20px solid #31BD5B;
}
</style>
</head>
<body>
<div class="container">
<div class="notification has-text-centered">
<h1 class="title is-1">Spotify Authentication</h1>
</div>
<div class="content">
<h2 class="title is-2">Introduction</h2>
<!-- <p>
In order to use this app, you will need to follow the steps below.
You will create a Spotify Developer App, which allows applications like this to securely access your spotify data, like playlists or your currently playing track.
<p class="notification is-warning">
If this page looks similar, you may already have a Spotify Developer App created. In this case, you can skip to the bottom and input your <code>client_id</code> and <code>client_secret</code>
</p>
</p>
<h2 class="title is-2">1. Login at your Developer Dashboard</h2>
<p>
Visit <a href="https://developer.spotify.com/dashboard/" rel="nofollow noreferer" target="_blank">https://developer.spotify.com/dashboard/</a> and login with your Spotify Account.
<img class="demo-image" src="images/1.png" width="700" alt=""/>
</p>
<h2 class="title is-2">2. Create a new ClientID</h2>
<p>
Visit <a href="https://developer.spotify.com/dashboard/" rel="nofollow noreferer" target="_blank">https://developer.spotify.com/dashboard/</a> and login with your Spotify Account.
<img class="demo-image" src="images/2.png" width="700" alt=""/>
</p>-->
<form action="/" method="post" style="margin-bottom: 20px">
<div class="field">
<label class="label" for="clientId">Client ID</label>
<div class="control">
<input class="input" type="text" placeholder="Client ID" name="clientId" id="clientId">
</div>
</div>
<div class="field">
<label class="label" for="secretId">Secret ID</label>
<div class="control">
<input class="input" type="password" placeholder="Secret ID" name="secretId" id="secretId">
</div>
</div>
<input class="input is-hidden" hidden name="state" id="state"/>
<div class="field is-grouped">
<div class="control">
<button class="button is-link">Submit</button>
</div>
</div>
</form>
</div>
</div>
<script type="text/javascript">
const hash = window.location.hash.split("#")[1];
document.addEventListener("DOMContentLoaded", () => {
const input = document.getElementById("state");
input.value = hash;
});
</script>
</body>
</html>

View File

@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<script type="text/javascript">
const serialize = (obj) => {
var str = [];
for (let p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
document.addEventListener("DOMContentLoaded",
() => {
const hash = window.location.hash.substr(1);
let result;
if (hash === "") {
const params = (new URL(document.location)).searchParams;
result = {
error: params.get("error"),
state: params.get("state")
}
} else {
result = hash.split('&').reduce(function(res, item) {
const parts = item.split('=');
res[parts[0]] = parts[1];
return res;
},
{});
}
window.location = `/auth?${serialize(result)}`;
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

Some files were not shown because too many files have changed in this diff Show More