Merge branch 'v6'
31
.editorconfig
Normal 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
@ -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
@ -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
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
2
.vscode/settings.json
vendored
@ -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
@ -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
@ -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.
|
||||
|
22
SpotifyAPI.Docs/.gitignore
vendored
@ -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
@ -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.
|
@ -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
|
||||
)
|
@ -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' },
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
20
SpotifyAPI.Docs/docs/auth_introduction.md
Normal 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)
|
108
SpotifyAPI.Docs/docs/authorization_code.md
Normal 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)
|
45
SpotifyAPI.Docs/docs/client_credentials.md
Normal 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`.
|
||||
:::
|
||||
|
48
SpotifyAPI.Docs/docs/configuration.md
Normal 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)
|
46
SpotifyAPI.Docs/docs/error_handling.md
Normal 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);
|
||||
}
|
||||
```
|
28
SpotifyAPI.Docs/docs/example_asp.md
Normal 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
|
||||
```
|
24
SpotifyAPI.Docs/docs/example_blazor.md
Normal 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
|
||||
```
|
29
SpotifyAPI.Docs/docs/example_blazor_wasm.md
Normal 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
|
||||
```
|
4
SpotifyAPI.Docs/docs/example_cli_custom_html.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
id: example_cli_custom_html
|
||||
title: CLI - Custom HTML
|
||||
---
|
4
SpotifyAPI.Docs/docs/example_cli_persistent_config.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
id: example_cli_persistent_config
|
||||
title: CLI - Persistent Config
|
||||
---
|
4
SpotifyAPI.Docs/docs/example_token_swap.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
id: example_token_swap
|
||||
title: Token Swap
|
||||
---
|
4
SpotifyAPI.Docs/docs/example_uwp.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
id: example_uwp
|
||||
title: UWP
|
||||
---
|
108
SpotifyAPI.Docs/docs/getting_started.md
Normal 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!
|
106
SpotifyAPI.Docs/docs/implicit_grant.md
Normal 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 owner’s 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)
|
24
SpotifyAPI.Docs/docs/introduction.md
Normal 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
|
38
SpotifyAPI.Docs/docs/logging.md
Normal 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
|
||||
```
|
80
SpotifyAPI.Docs/docs/pagination.md
Normal 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.
|
25
SpotifyAPI.Docs/docs/proxy.md
Normal 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)
|
||||
|
50
SpotifyAPI.Docs/docs/retry_handling.md
Normal 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);
|
||||
```
|
56
SpotifyAPI.Docs/docs/showcase.md
Normal 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.
|
93
SpotifyAPI.Docs/docs/token_swap.md
Normal 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)
|
35
SpotifyAPI.Docs/docs/unit_testing.md
Normal 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));
|
||||
}
|
||||
```
|
@ -1,7 +0,0 @@
|
||||
---
|
||||
sidebar: false
|
||||
---
|
||||
|
||||
# Examples
|
||||
|
||||
This page is currently empty. Do you have useful examples? Please open a PR
|
85
SpotifyAPI.Docs/docusaurus.config.js
Normal 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'),
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
};
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
196
SpotifyAPI.Docs/reference.md
Normal 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
|
||||
|
||||
:::
|
46
SpotifyAPI.Docs/sidebars.js
Normal 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'
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
};
|
25
SpotifyAPI.Docs/src/css/custom.css
Normal 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);
|
||||
}
|
52
SpotifyAPI.Docs/src/install_instructions.js
Normal 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;
|
157
SpotifyAPI.Docs/src/pages/index.js
Normal 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;
|
41
SpotifyAPI.Docs/src/pages/styles.module.css
Normal 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;
|
||||
}
|
BIN
SpotifyAPI.Docs/static/img/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
SpotifyAPI.Docs/static/img/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
SpotifyAPI.Docs/static/img/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
SpotifyAPI.Docs/static/img/asp_blazor_example_home.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
SpotifyAPI.Docs/static/img/asp_example_home.png
Normal file
After Width: | Height: | Size: 129 KiB |
BIN
SpotifyAPI.Docs/static/img/asp_example_profile.png
Normal file
After Width: | Height: | Size: 44 KiB |
BIN
SpotifyAPI.Docs/static/img/auth_comparison.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
SpotifyAPI.Docs/static/img/auth_protocol_handlers.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
SpotifyAPI.Docs/static/img/blazorwasm_homepage.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
SpotifyAPI.Docs/static/img/blazorwasm_network_tools.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
SpotifyAPI.Docs/static/img/favicon-16x16.png
Normal file
After Width: | Height: | Size: 424 B |
BIN
SpotifyAPI.Docs/static/img/favicon-32x32.png
Normal file
After Width: | Height: | Size: 950 B |
BIN
SpotifyAPI.Docs/static/img/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
9
SpotifyAPI.Docs/static/img/logo.svg
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
SpotifyAPI.Docs/static/img/mitmproxy.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
1
SpotifyAPI.Docs/static/img/undraw_Devices_e67q.svg
Normal 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 |
1
SpotifyAPI.Docs/static/img/undraw_QA_engineers_dg5p.svg
Normal file
After Width: | Height: | Size: 40 KiB |
1
SpotifyAPI.Docs/static/img/undraw_preferences_uuo2.svg
Normal file
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 6.6 KiB |
@ -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
|
||||
}
|
||||
```
|
||||
```
|
@ -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.
|
@ -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)
|
@ -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.
|
||||
|
@ -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.
|
@ -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)
|
||||
|
@ -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.
|
@ -1,4 +1,8 @@
|
||||
# Artists
|
||||
---
|
||||
id: artists
|
||||
title: Artists
|
||||
sidebar_label: Artists
|
||||
---
|
||||
|
||||
## GetArtist
|
||||
|
@ -1,4 +1,8 @@
|
||||
# Browse
|
||||
---
|
||||
id: browse
|
||||
title: Browse
|
||||
sidebar_label: Browse
|
||||
---
|
||||
|
||||
## GetFeaturedPlaylists
|
||||
|
@ -1,4 +1,8 @@
|
||||
# Follow
|
||||
---
|
||||
id: follow
|
||||
title: Follow
|
||||
sidebar_label: Follow
|
||||
---
|
||||
|
||||
## Follow
|
||||
|
@ -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
|
@ -1,4 +1,8 @@
|
||||
# Library
|
||||
---
|
||||
id: library
|
||||
title: Library
|
||||
sidebar_label: Library
|
||||
---
|
||||
|
||||
## SaveTracks
|
||||
|
@ -1,4 +1,8 @@
|
||||
# Personalization
|
||||
---
|
||||
id: personalization
|
||||
title: Personalization
|
||||
sidebar_label: Personalization
|
||||
---
|
||||
|
||||
## GetUsersTopTracks
|
||||
|
@ -1,4 +1,8 @@
|
||||
# Player
|
||||
---
|
||||
id: player
|
||||
title: Player
|
||||
sidebar_label: Player
|
||||
---
|
||||
|
||||
## GetDevices
|
||||
> Get information about a user’s available devices.
|
@ -1,4 +1,8 @@
|
||||
# Playlists
|
||||
---
|
||||
id: playlists
|
||||
title: Playlists
|
||||
sidebar_label: Playlists
|
||||
---
|
||||
|
||||
## GetUserPlaylists
|
||||
|
@ -1,4 +1,8 @@
|
||||
# Profiles
|
||||
---
|
||||
id: profiles
|
||||
title: Profiles
|
||||
sidebar_label: Profiles
|
||||
---
|
||||
|
||||
## GetPrivateProfile
|
||||
|
@ -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
|
@ -1,4 +1,8 @@
|
||||
# Search
|
||||
---
|
||||
id: search
|
||||
title: Search
|
||||
sidebar_label: Search
|
||||
---
|
||||
|
||||
## SearchItems
|
||||
|
@ -1,4 +1,8 @@
|
||||
# Tracks
|
||||
---
|
||||
id: tracks
|
||||
title: Tracks
|
||||
sidebar_label: Tracks
|
||||
---
|
||||
|
||||
## GetSeveralTracks
|
||||
|
@ -1,4 +1,8 @@
|
||||
# Utilities
|
||||
---
|
||||
id: utilities
|
||||
title: Utilities
|
||||
sidebar_label: Utilities
|
||||
---
|
||||
|
||||
## Paging-Methods
|
||||
|
102
SpotifyAPI.Docs/versioned_sidebars/version-5.1.1-sidebars.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
3
SpotifyAPI.Docs/versions.json
Normal file
@ -0,0 +1,3 @@
|
||||
[
|
||||
"5.1.1"
|
||||
]
|
20
SpotifyAPI.Web.Auth/AuthException.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SpotifyAPI.Web.Auth
|
||||
{
|
||||
internal static class AuthUtil
|
||||
{
|
||||
public static bool OpenBrowser(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if NETSTANDARD2_0
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
url = url.Replace("&", "^&");
|
||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Process.Start("xdg-open", url);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
Process.Start("open", url);
|
||||
}
|
||||
#else
|
||||
url = url.Replace("&", "^&");
|
||||
Process.Start(new ProcessStartInfo("cmd", $"/c start {url}"));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using SpotifyAPI.Web.Enums;
|
||||
using SpotifyAPI.Web.Models;
|
||||
using Unosquare.Labs.EmbedIO;
|
||||
using Unosquare.Labs.EmbedIO.Constants;
|
||||
using Unosquare.Labs.EmbedIO.Modules;
|
||||
|
||||
namespace SpotifyAPI.Web.Auth
|
||||
{
|
||||
public class AuthorizationCodeAuth : SpotifyAuthServer<AuthorizationCode>
|
||||
{
|
||||
public string SecretId { get; set; }
|
||||
|
||||
public ProxyConfig ProxyConfig { get; set; }
|
||||
|
||||
public AuthorizationCodeAuth(string redirectUri, string serverUri, Scope scope = Scope.None, string state = "") : base("code", "AuthorizationCodeAuth", redirectUri, serverUri, scope, state)
|
||||
{ }
|
||||
|
||||
public AuthorizationCodeAuth(string clientId, string secretId, string redirectUri, string serverUri, Scope scope = Scope.None, string state = "") : this(redirectUri, serverUri, scope, state)
|
||||
{
|
||||
ClientId = clientId;
|
||||
SecretId = secretId;
|
||||
}
|
||||
|
||||
private bool ShouldRegisterNewApp()
|
||||
{
|
||||
return string.IsNullOrEmpty(SecretId) || string.IsNullOrEmpty(ClientId);
|
||||
}
|
||||
|
||||
public override string GetUri()
|
||||
{
|
||||
return ShouldRegisterNewApp() ? $"{RedirectUri}/start.html#{State}" : base.GetUri();
|
||||
}
|
||||
|
||||
protected override void AdaptWebServer(WebServer webServer)
|
||||
{
|
||||
webServer.Module<WebApiModule>().RegisterController<AuthorizationCodeAuthController>();
|
||||
}
|
||||
|
||||
private string GetAuthHeader() => $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(ClientId + ":" + SecretId))}";
|
||||
|
||||
public async Task<Token> RefreshToken(string refreshToken)
|
||||
{
|
||||
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type", "refresh_token"),
|
||||
new KeyValuePair<string, string>("refresh_token", refreshToken)
|
||||
};
|
||||
|
||||
return await GetToken(args);
|
||||
}
|
||||
|
||||
public async Task<Token> ExchangeCode(string code)
|
||||
{
|
||||
List<KeyValuePair<string, string>> args = new List<KeyValuePair<string, string>>
|
||||
{
|
||||
new KeyValuePair<string, string>("grant_type", "authorization_code"),
|
||||
new KeyValuePair<string, string>("code", code),
|
||||
new KeyValuePair<string, string>("redirect_uri", RedirectUri)
|
||||
};
|
||||
|
||||
return await GetToken(args);
|
||||
}
|
||||
|
||||
private async Task<Token> GetToken(IEnumerable<KeyValuePair<string, string>> args)
|
||||
{
|
||||
HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
|
||||
HttpClient client = new HttpClient(handler);
|
||||
client.DefaultRequestHeaders.Add("Authorization", GetAuthHeader());
|
||||
HttpContent content = new FormUrlEncodedContent(args);
|
||||
|
||||
HttpResponseMessage resp = await client.PostAsync("https://accounts.spotify.com/api/token", content);
|
||||
string msg = await resp.Content.ReadAsStringAsync();
|
||||
|
||||
return JsonConvert.DeserializeObject<Token>(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthorizationCode
|
||||
{
|
||||
public string Code { get; set; }
|
||||
|
||||
public string Error { get; set; }
|
||||
}
|
||||
|
||||
internal class AuthorizationCodeAuthController : WebApiController
|
||||
{
|
||||
[WebApiHandler(HttpVerbs.Get, "/")]
|
||||
public Task<bool> GetEmpty()
|
||||
{
|
||||
string state = Request.QueryString["state"];
|
||||
AuthorizationCodeAuth.Instances.TryGetValue(state, out SpotifyAuthServer<AuthorizationCode> auth);
|
||||
|
||||
string code = null;
|
||||
string error = Request.QueryString["error"];
|
||||
if (error == null)
|
||||
code = Request.QueryString["code"];
|
||||
|
||||
Task.Factory.StartNew(() => auth?.TriggerAuth(new AuthorizationCode
|
||||
{
|
||||
Code = code,
|
||||
Error = error
|
||||
}));
|
||||
|
||||
return HttpContext.HtmlResponseAsync("<html><script type=\"text/javascript\">window.close();</script>OK - This window can be closed now</html>");
|
||||
}
|
||||
|
||||
[WebApiHandler(HttpVerbs.Post, "/")]
|
||||
public async Task<bool> PostValues()
|
||||
{
|
||||
Dictionary<string, object> formParams = await HttpContext.RequestFormDataDictionaryAsync();
|
||||
|
||||
string state = (string) formParams["state"];
|
||||
AuthorizationCodeAuth.Instances.TryGetValue(state, out SpotifyAuthServer<AuthorizationCode> authServer);
|
||||
|
||||
AuthorizationCodeAuth auth = (AuthorizationCodeAuth) authServer;
|
||||
auth.ClientId = (string) formParams["clientId"];
|
||||
auth.SecretId = (string) formParams["secretId"];
|
||||
|
||||
string uri = auth.GetUri();
|
||||
return HttpContext.Redirect(uri, false);
|
||||
}
|
||||
|
||||
public AuthorizationCodeAuthController(IHttpContext context) : base(context)
|
||||
{ }
|
||||
}
|
||||
}
|
26
SpotifyAPI.Web.Auth/BrowserUtil.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
97
SpotifyAPI.Web.Auth/EmbedIOAuthServer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
SpotifyAPI.Web.Auth/IAuthServer.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -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!;
|
||||
}
|
||||
}
|
30
SpotifyAPI.Web.Auth/Models/Response/ImplicitGrantResponse.cs
Normal 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; }
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 16 KiB |
@ -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>
|
@ -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>
|
9
SpotifyAPI.Web.Auth/Resources/auth_assets/logo.svg
Normal file
After Width: | Height: | Size: 51 KiB |