├── .deployment ├── .gitignore ├── CONTRIB.md ├── LICENSE ├── README.md ├── build └── azure-pipelines.yaml ├── docs └── links.md └── src ├── SpotifyApi.NetCore.Tests ├── .vscode │ ├── launch.json │ └── tasks.json ├── AlbumsApiTests.cs ├── ArtistsApiTests.cs ├── Authorization │ ├── AccountsServiceTests.cs │ ├── BearerAccessTokenTests.cs │ └── UserAccountsServiceTests.cs ├── BrowseApiTests.cs ├── EpisodeApiTests.cs ├── Extensions │ ├── TracksApiExtensionsTests.cs │ └── UriBuilderExtensionTests.cs ├── FollowApiTests.cs ├── Helpers │ ├── SpotifyUriHelperTests.cs │ └── TimeRangeHelperTests.cs ├── Http │ └── RestHttpClientTests.cs ├── Integration │ ├── AuthorizationCodeFlowTests.cs │ ├── CompetingAccessTokenRequests.cs │ └── UsageTests.cs ├── LibraryApiTests.cs ├── Mocks │ ├── MockAccountsService.cs │ ├── MockConfiguration.cs │ ├── MockHttpClient.cs │ └── MockRefreshTokenStore.cs ├── Models │ └── PlayHistoryTests.cs ├── PersonalizationApiTests.cs ├── PlayerApiTests.cs ├── PlaylistsTests.cs ├── README.md ├── SearchApiTests.cs ├── ShowsApiTests.cs ├── SpotifyApi.NetCore.Tests.csproj ├── SpotifyApiErrorExceptionTests.cs ├── SpotifyWebApiTests.cs ├── TestsHelper.cs ├── TracksApiTests.cs ├── UsersProfileApiTests.cs └── appsettings-template.json ├── SpotifyApi.NetCore.sln └── SpotifyApi.NetCore ├── .vscode └── tasks.json ├── AlbumsApi.cs ├── ArtistsApi.cs ├── AssemblyInfo.cs ├── Authorization ├── AccountsService.cs ├── BearerAccessRefreshToken.cs ├── IAccessTokenProvider.cs ├── IAccountsService.cs ├── IBearerTokenStore.cs ├── MemoryBearerTokenStore.cs └── UserAccountsService.cs ├── BrowseApi.cs ├── EpisodesApi.cs ├── Extensions ├── TracksApiExtensions.cs └── UriBuilderExtensions.cs ├── FollowApi.cs ├── Helpers ├── SpotifyUriHelper.cs └── TimeRangeHelper.cs ├── Http └── RestHttpClient.cs ├── IAlbumsApi.cs ├── IArtistsApi.cs ├── IBrowseApi.cs ├── IEpisodesApi.cs ├── IFollowApi.cs ├── ILibraryApi.cs ├── IPersonalizationApi.cs ├── IPlayerApi.cs ├── IPlaylistsApi.cs ├── ISearchApi.cs ├── IShowsApi.cs ├── ITracksApi.cs ├── IUsersProfileApi.cs ├── LibraryApi.cs ├── Logger └── Logger.cs ├── Models ├── Actions.cs ├── Album.cs ├── Artist.cs ├── Category.cs ├── Common.cs ├── CurrentEpisodePlaybackContext.cs ├── CurrentPlaybackContext.cs ├── CurrentTrackPlaybackContext.cs ├── Cursors.cs ├── Device.cs ├── Episode.cs ├── FeaturedPlaylists.cs ├── ModifyPlaylistResponse.cs ├── PagedAlbums.cs ├── PagedArtists.cs ├── PagedCategories.cs ├── PagedEpisodes.cs ├── PagedPlayHistory.cs ├── PagedPlaylists.cs ├── PagedShows.cs ├── PagedT.cs ├── PagedTracks.cs ├── PlayHistory.cs ├── Playlist.cs ├── PlaylistDetails.cs ├── RecommendationsResult.cs ├── RepeatStates.cs ├── SearchResult.cs ├── Show.cs ├── SpotifyArtistAlbumGroups.cs ├── SpotifyCountryCodes.cs ├── SpotifyResponse.cs ├── SpotifySearchTypes.cs ├── SpotifyUri.cs ├── TimeRange.cs ├── Track.cs ├── TrackAudioAnalysis.cs └── TrackAudioFeatures.cs ├── PersonalizationApi.cs ├── PlayerApi.cs ├── PlaylistsApi.cs ├── SearchApi.cs ├── ShowsApi.cs ├── SpotifyApi.NetCore.csproj ├── SpotifyApiErrorException.cs ├── SpotifyWebApi.cs ├── TracksApi.cs └── UsersProfileApi.cs /.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | project = src/samples/SpotifyVue 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .vscode 4 | appsettings.*.json 5 | _* 6 | node_modules 7 | .vs 8 | *.user 9 | TestResults -------------------------------------------------------------------------------- /CONTRIB.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * Feature requests welcomed (log an issue) 4 | * Bug reports welcomed (log an issue) 5 | * Pull requests welcomed 6 | 7 | ## Pull requests 8 | 9 | * Open a separate branch _and_ Pull Request (PR) for each family of endpoints (Artists, Users, Player, etc), or for 10 | each bug. Follow [GitHub Flow](https://guides.github.com/introduction/flow/) and don't PR from `master`. 11 | * Keep PR's small and confined to only the files that are related to the API / Bug / Change you are 12 | working on. Don't be tempted to refactor the whole library while you are at it, this is production 13 | code used by thousands of users; changes need to be small. 14 | * Open a Pull Request early so that we can collaborate on the code as you write it. The PR won't be 15 | merged until reviews are completed and merge conflicts are resolved. Consider the reviewer (who is 16 | a volunteer too) by keeping changes small and relevant. 17 | 18 | > See [Proposing changes to your work with pull requests](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests) 19 | > to learn about Pull Requests. 20 | 21 | ## Coding guide, style and conventions 22 | 23 | * Study existing code for guidance on style and conventions 24 | * All public methods must be fully documented with XML Comments 25 | * Copy and paste text from Spotify Api Reference documentation for all methods and params so that docs 26 | match as closely as possible 27 | * Include a link to the original Spotify docs in the `` XML comment tag 28 | * Provide generic versions of all API Methods so that users can provide their own types for deserialization 29 | * Name API methods to closely match the name of the Spotify endpoint. 30 | * In public methods, required params should be validated, e.g. 31 | 32 | ```csharp 33 | if (string.IsNullOrEmpty(username)) throw new ArgumentNullException("username"); 34 | ``` 35 | 36 | * The convention in this project is to use `var` whenever the type is obvious, e.g. 37 | 38 | ```csharp 39 | var http = new HttpClient(); 40 | ``` 41 | 42 | * This library prefers simple types wherever possible. For example, use `string[]` instead of `List` 43 | unless the implementation could truly benefit from a List. 44 | * Currently, `null` is meaningful and is used to indicate when a param has not been set. This may change 45 | (at least internally) in a future major version. 46 | * We have opted to not use DI to resolve `SearchApi` which is proxied by several API classes. This is 47 | purely for simplicity; SearchApi can still be tested on its own. This may be reviewed and changed 48 | in a future release. 49 | 50 | ## Writing tests 51 | 52 | We prefer valuable tests over extensive code coverage. However all public methods must be exercised 53 | by at least one test. Generally a few Integration tests are sufficient for testing an Endpoint method. 54 | For business and helper logic, Unit tests are good. If you are not sure what to test, ask for a review. 55 | 56 | ### Configuring environment for testing 57 | 58 | Running Integration Tests requires your Application to be registered in your Spotify Developer Account. 59 | [See this page for instructions](https://developer.spotify.com/documentation/general/guides/app-settings/). 60 | 61 | 1. Copy `src\SpotifyApi.NetCore.Tests\appsettings-template.json` to `src\SpotifyApi.NetCore.Tests\appsettings.local.json` 62 | 2. Copy and paste the Spotify API Client Id and Client Secret into the `appsettings.local.json` file. 63 | This file is git-ignored and should never be committed. 64 | 3. Run all tests using the Visual Studio Test Runner, VS Code Test runner or by running `dotnet test` 65 | from the command line. 66 | 67 | ### User profile and Current user tests 68 | 69 | User Profile and Current User tests require a current Spotify User Bearer Access Token. The easiest 70 | way to generate an access token is to use the "Try It" function in the Spotify Developer Reference, 71 | e.g. [Get Current User's Profile](https://developer.spotify.com/console/get-current-user/). 72 | 73 | 1. Click the "Get Token" button to generate a new token that is good for one hour. 74 | 1. Copy this token into the `SpotifyUserBearerAccessToken` app setting in the `appsettings.local.json` 75 | file. 76 | 77 | You can now run the User profile tests. 78 | 79 | ## Publishing 80 | 81 | Push the Version number in `SpotifyApi.NetCore.csproj` 82 | 83 | ```xml 84 | 2.5.0 85 | ``` 86 | 87 | Commit and push 88 | 89 | git commit -a -m "Packing v2.5.0" 90 | git push 91 | 92 | And then create a Release in Github. 93 | 94 | Tag = 2.5.0 (no "v" - this must match .csproj to work with Nuget packaging) 95 | Title = v2.5.0 96 | Description = release notes 97 | 98 | Or for pre-release 99 | 100 | git tag -a -m "v2.5.0-beta" 2.5.0-beta 101 | git push origin 2.5.0-beta 102 | 103 | Creating a Release in Github, or pushing a tag, _should_ trigger a build that will pack and publish 104 | the Nuget package. If the trigger does not fire you can queue manually using the tag ref, e.g. `refs/tags/2.5.0` 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Daniel Larsen and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spotify API .NET Core 2 | 3 | Lightweight .NET Core wrapper for the Spotify Web API. 4 | 5 | ### Build status 6 | 7 | [![Build Status](https://dev.azure.com/daniellarsennz/SpotifyApi.NetCore/_apis/build/status/SpotifyApi.NetCore-Build)](https://dev.azure.com/daniellarsennz/SpotifyApi.NetCore/_build/latest?definitionId=9) 8 | 9 | ## Features 10 | 11 | * Targets .NET Standard 2.0 12 | * `async` by default 13 | * BYO `HttpClient` 14 | * Authorization support (App and User flows) 15 | * MIT license 16 | * Fully XML documented 17 | 18 | ## Installation 19 | 20 | Install the latest version using dotnet CLI: 21 | 22 | > dotnet add package SpotifyApi.NetCore 23 | 24 | Install using Package Manager Console: 25 | 26 | > Install-Package SpotifyApi.NetCore 27 | 28 | ## Version 3 29 | 30 | Version 3 of `SpotifyApi.NetCore` is a major version overhaul with many improvements including: 31 | 32 | * Removal of multi-user authentication in favour of bring-your-own auth 33 | * Simplification of Authentication services 34 | * Consistent approach to paging and auth params throughout the library 35 | * Removal of many overloads in favour of optional params 36 | * Complete XML comment documentation of public methods including links to Spotify reference docs 37 | * Separate [SpotifyApi.NetCore.Samples] repo 38 | 39 | > It is highly recommended that users upgrade to `SpotifyApi.NetCore` >= v3.0.1 as soon as possible. 40 | > Version >= 2.4.7 will be supported until the next major version ships. 41 | 42 | ### Upgrading from v2 to v3 43 | 44 | There are breaking changes in v3 but, for most users, upgrading should be straight-forward. Some minor 45 | refactoring may be required, e.g. 46 | 47 | * Most Authorization objects have moved from namespace `SpotifyApi.NetCore` to `SpotifyApi.NetCore.Authorization` 48 | * `Models.Image.Height` and `Models.Image.Width` have changed from `int` to `int?` 49 | * `Models.CurrentPlaybackContext.ProgressMs` has changed from `long` to `long?` 50 | 51 | ## Basic usage 52 | 53 | Set Environment variables: 54 | 55 | SpotifyApiClientId=(SpotifyApiClientId) 56 | SpotifyApiClientSecret=(SpotifyApiClientSecret) 57 | 58 | ```csharp 59 | // HttpClient and AccountsService can be reused. 60 | // Tokens are automatically cached and refreshed 61 | var http = new HttpClient(); 62 | var accounts = new AccountsService(http); 63 | 64 | // Get an artist by Spotify Artist Id 65 | var artists = new ArtistsApi(http, accounts); 66 | Artist artist = await artists.GetArtist("1tpXaFf2F55E7kVJON4j4G"); 67 | string artistName = artist.Name; 68 | Trace.WriteLine($"Artist.Name = {artistName}"); 69 | 70 | // Get recommendations based on seed Artist Ids 71 | var browse = new BrowseApi(http, accounts); 72 | RecommendationsResult result = await browse.GetRecommendations(new[] { "1tpXaFf2F55E7kVJON4j4G", "4Z8W4fKeB5YxbusRsdQVPb" }, null, null); 73 | string firstTrackName = result.Tracks[0].Name; 74 | Trace.WriteLine($"First recommendation = {firstTrackName}"); 75 | 76 | // Page through a list of tracks in a Playlist 77 | var playlists = new PlaylistsApi(http, accounts); 78 | int limit = 100; 79 | PlaylistPaged playlist = await playlists.GetTracks("4h4urfIy5cyCdFOc1Ff4iN", limit: limit); 80 | int offset = 0; 81 | int j = 0; 82 | // using System.Linq 83 | while (playlist.Items.Any()) 84 | { 85 | for (int i = 0; i < playlist.Items.Length; i++) 86 | { 87 | Trace.WriteLine($"Track #{j += 1}: {playlist.Items[i].Track.Artists[0].Name} / {playlist.Items[i].Track.Name}"); 88 | } 89 | offset += limit; 90 | playlist = await playlists.GetTracks("4h4urfIy5cyCdFOc1Ff4iN", limit: limit, offset: offset); 91 | } 92 | ``` 93 | 94 | ### User Authorization 95 | 96 | ```csharp 97 | // Get a list of a User's devices 98 | // This requires User authentication and authorization. 99 | // A `UserAccountsService` is provided to help with this. 100 | 101 | // HttpClient and UserAccountsService can be reused. 102 | // Tokens can be cached by your code 103 | var http = new HttpClient(); 104 | var accounts = new UserAccountsService(http); 105 | 106 | // See https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow 107 | // for an explanation of the Authorization code flow 108 | 109 | // Generate a random state value to use in the Auth request 110 | string state = Guid.NewGuid().ToString("N"); 111 | // Accounts service will derive the Auth URL for you 112 | string url = accounts.AuthorizeUrl(state, new[] { "user-read-playback-state" }); 113 | 114 | /* 115 | Redirect the user to `url` and when they have auth'ed Spotify will redirect to your reply URL 116 | The response will include two query parameters: `state` and `code`. 117 | For a full working example see `SpotifyApi.NetCore.Samples`. 118 | */ 119 | 120 | // Check that the request has not been tampered with by checking the `state` value matches 121 | if (state != query["state"]) throw new ArgumentException(); 122 | 123 | // Use the User accounts service to swap `code` for a Refresh token 124 | BearerAccessRefreshToken token = await accounts.RequestAccessRefreshToken(query["code"]); 125 | 126 | // Use the Bearer (Access) Token to call the Player API 127 | var player = new PlayerApi(http, accounts); 128 | Device[] devices = await player.GetDevices(accessToken: token.AccessToken); 129 | 130 | foreach(Device device in devices) 131 | { 132 | Trace.WriteLine($"Device {device.Name} Status = {device.Type} Active = {device.IsActive}"); 133 | } 134 | 135 | ``` 136 | 137 | See tests and [SpotifyApi.NetCore.Samples] for more usage examples. 138 | 139 | ## Spotify Web API Coverage 140 | 141 | | Spotify API | Endpoints | Implemented | % | | 142 | | :---------- | --------: | ----------: | -: | - | 143 | | Albums | 3 | 3 | 100% | ✅ | 144 | | Artists | 5 | 5 | 100% | ✅ | 145 | | Browse | 7 | 7 | 100% | ✅ | 146 | | Episodes | 2 | 2 | 100% | ✅ | 147 | | Follow | 7 | 7 | 100% | ✅ | 148 | | Library | 8 | 8 | 100% | ✅ | 149 | | Personalization | 1 | 1 | 100% | ✅ | 150 | | Player | 14 | 14 | 100% | ✅ | 151 | | Playlists | 12 | 6 | 50% | | 152 | | Search | 1 | 1 | 100% | ✅ | 153 | | Shows | 3 | 3 | 100% | ✅ | 154 | | Tracks | 5 | 5 | 100% | ✅ | 155 | | Users Profile | 2 | 2 | 100% | ✅ | 156 | | **Total** | **70** | **64** | **91%** | 157 | 158 | Feature requests welcomed! (create an issue) 159 | 160 | ## Maintainer 161 | 162 | This project is actively maintained by @DanielLarsenNZ. The easiest way to get in touch is to create an issue. But you can also email daniel@larsen.nz. 163 | 164 | ## Contributors 165 | 166 | Huge thanks to **@aevansme**, **@brandongregoryscott** and **@akshays2112** for their contributions! 167 | 168 | Contributions welcomed. Read [CONTRIB.md](./CONTRIB.md) 169 | 170 | [SpotifyApi.NetCore.Samples]:https://github.com/Ringobot/SpotifyApi.NetCore.Samples 171 | -------------------------------------------------------------------------------- /build/azure-pipelines.yaml: -------------------------------------------------------------------------------- 1 | # Build ASP.NET Core project using Azure Pipelines 2 | # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core?view=vsts 3 | 4 | trigger: 5 | branches: 6 | include: 7 | - master 8 | - refs/tags/* 9 | paths: 10 | include: 11 | - src/SpotifyApi.NetCore/* 12 | - src/SpotifyApi.NetCore.Tests/* 13 | 14 | pool: 15 | vmImage: 'ubuntu-16.04' 16 | 17 | variables: 18 | buildConfiguration: 'Release' 19 | 20 | steps: 21 | - script: | 22 | dotnet build src/SpotifyApi.NetCore --configuration $(buildConfiguration) 23 | dotnet test src/SpotifyApi.NetCore.Tests --configuration $(buildConfiguration) --logger trx --filter "TestCategory!=Integration" 24 | dotnet publish src/SpotifyApi.NetCore --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory) 25 | 26 | - task: PublishTestResults@2 27 | inputs: 28 | testRunner: VSTest 29 | testResultsFiles: '**/*.trx' 30 | 31 | - task: PublishBuildArtifacts@1 32 | inputs: 33 | pathtoPublish: $(Build.ArtifactStagingDirectory) 34 | 35 | - script: | 36 | LATEST_TAG=$(git describe --abbrev=0 --tags) 37 | echo NUGET PUSH TAG $LATEST_TAG - tag must match .csproj version 38 | dotnet pack src/SpotifyApi.NetCore -c $(buildConfiguration) 39 | dotnet nuget push src/SpotifyApi.NetCore/bin/Release/SpotifyApi.NetCore.$LATEST_TAG.nupkg -k $(nugetApiKey) -s https://api.nuget.org/v3/index.json 40 | condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/')) 41 | -------------------------------------------------------------------------------- /docs/links.md: -------------------------------------------------------------------------------- 1 | # Links 2 | 3 | ### Spotify 4 | 5 | Auth guide 6 | 7 | ### .NET Core 8 | 9 | Concurrent Dictionary 10 | 11 | Additions to the csproj format for .NET Core: 12 | 13 | ### HttpClient factory 14 | 15 | 16 | 17 | ### Configuration in .NET Core 18 | 19 | Setting up .NET Core Configuration Providers: 20 | 21 | Easy Configuration Binding in ASP.NET Core: 22 | 23 | Configuration in ASP.NET Core: 24 | 25 | ### Nuget 26 | 27 | Create and publish a Nuget package: 28 | 29 | Nuget package versioning: 30 | 31 | Nuget metadata properties: 32 | 33 | Detecting git tag pushes in Azure DevOps: 34 | 35 | 36 | 37 | ## Blazor 38 | 39 | `HttpClientJsonExtensions` 40 | 41 | Learn Blazor 42 | 43 | ## JSON 44 | 45 | Serializing JSON fragments: -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp2.0/SpotifyApi.NetCore.Tests.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "internalConsole", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | }, 21 | { 22 | "name": ".NET Core Attach", 23 | "type": "coreclr", 24 | "request": "attach", 25 | "processId": "${command:pickProcess}" 26 | } 27 | ,] 28 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "test", 8 | "command": "dotnet test", 9 | "type": "shell", 10 | "group": "build", 11 | "presentation": { 12 | "reveal": "silent" 13 | }, 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "build", 18 | "command": "dotnet build", 19 | "type": "shell", 20 | "group": "build", 21 | "presentation": { 22 | "reveal": "silent" 23 | }, 24 | "problemMatcher": "$msCompile" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/AlbumsApiTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using SpotifyApi.NetCore.Authorization; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace SpotifyApi.NetCore.Tests 7 | { 8 | [TestClass] 9 | public class AlbumsApiTests 10 | { 11 | [TestCategory("Integration")] 12 | [TestMethod] 13 | public async Task GetAlbum_AlbumsId_CorrectAlbumName() 14 | { 15 | // arrange 16 | // spotify:album:5ObHI23lQY2S5FGizlNrob 17 | const string albumId = "5ObHI23lQY2S5FGizlNrob"; 18 | 19 | var http = new HttpClient(); 20 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 21 | 22 | var api = new AlbumsApi(http, accounts); 23 | 24 | // act 25 | var response = await api.GetAlbum(albumId); 26 | 27 | // assert 28 | Assert.AreEqual("Trojan Presents: Dub", response.Name); 29 | } 30 | 31 | [TestCategory("Integration")] 32 | [TestMethod] 33 | public async Task SearchAlbums_AlbumName_FirstAlbumNameMatches() 34 | { 35 | // arrange 36 | const string albumName = "Trojan Presents: Dub"; 37 | var http = new HttpClient(); 38 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 39 | 40 | var albums = new AlbumsApi(http, accounts); 41 | 42 | // act 43 | var result = await albums.SearchAlbums(albumName); 44 | 45 | // assert 46 | Assert.AreEqual(albumName, result.Albums.Items[0].Name); 47 | } 48 | 49 | [TestMethod] 50 | [TestCategory("Integration")] 51 | public async Task GetAlbums_2ValidAlbums_AlbumIdsMatch() 52 | { 53 | // arrange 54 | // spotify:album:49PXnWG6cuZbCZSolJWrYa 55 | string[] albumIds = new[] { "5ObHI23lQY2S5FGizlNrob", "49PXnWG6cuZbCZSolJWrYa" }; 56 | 57 | var http = new HttpClient(); 58 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 59 | var albums = new AlbumsApi(http, accounts); 60 | 61 | // act 62 | var result = await albums.GetAlbums(albumIds); 63 | 64 | // assert 65 | Assert.AreEqual(albumIds[0], result[0].Id); 66 | Assert.AreEqual(albumIds[1], result[1].Id); 67 | } 68 | 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/ArtistsApiTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using SpotifyApi.NetCore.Authorization; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace SpotifyApi.NetCore.Tests 8 | { 9 | [TestClass] 10 | public class ArtistsApiTests 11 | { 12 | [TestCategory("Integration")] 13 | [TestMethod] 14 | public async Task GetArtist_ArtistsId_CorrectArtistName() 15 | { 16 | // arrange 17 | const string artistId = "1tpXaFf2F55E7kVJON4j4G"; 18 | 19 | var http = new HttpClient(); 20 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 21 | 22 | var api = new ArtistsApi(http, accounts); 23 | 24 | // act 25 | var response = await api.GetArtist(artistId); 26 | 27 | // assert 28 | Assert.AreEqual("Black Rebel Motorcycle Club", response.Name); 29 | } 30 | 31 | [TestCategory("Integration")] 32 | [TestMethod] 33 | public async Task SearchArtists_ArtistName_FirstArtistNameMatches() 34 | { 35 | // arrange 36 | const string artistName = "Radiohead"; 37 | var http = new HttpClient(); 38 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 39 | 40 | var artists = new ArtistsApi(http, accounts); 41 | 42 | // act 43 | var result = await artists.SearchArtists(artistName, 3); 44 | 45 | // assert 46 | Assert.AreEqual(artistName, result.Artists.Items[0].Name); 47 | } 48 | 49 | [TestMethod] 50 | [TestCategory("Integration")] 51 | public async Task GetArtists_2ValidArtists_ArtistIdsMatch() 52 | { 53 | // arrange 54 | string[] artistIds = new[] { "1tpXaFf2F55E7kVJON4j4G", "0oSGxfWSnnOXhD2fKuz2Gy" }; 55 | 56 | var http = new HttpClient(); 57 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 58 | var artists = new ArtistsApi(http, accounts); 59 | 60 | // act 61 | var result = await artists.GetArtists(artistIds); 62 | 63 | // assert 64 | Assert.AreEqual(artistIds[0], result[0].Id); 65 | Assert.AreEqual(artistIds[1], result[1].Id); 66 | } 67 | 68 | [TestMethod] 69 | [TestCategory("Integration")] 70 | public async Task GetArtistsTopTracks_NZCountryCode_ArtistIdMatches() 71 | { 72 | // arrange 73 | const string artistId = "1tpXaFf2F55E7kVJON4j4G"; 74 | const string market = SpotifyCountryCodes.New_Zealand; 75 | 76 | var http = new HttpClient(); 77 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 78 | var artists = new ArtistsApi(http, accounts); 79 | 80 | // act 81 | var result = await artists.GetArtistsTopTracks(artistId, market); 82 | 83 | // assert 84 | Assert.AreEqual(artistId, result[0].Artists[0].Id); 85 | } 86 | 87 | [TestMethod] 88 | [ExpectedException(typeof(SpotifyApiErrorException))] 89 | [TestCategory("Integration")] 90 | public async Task GetArtistsTopTracks_FromAppToken_SpotifyErrorException() 91 | { 92 | // arrange 93 | const string artistId = "1tpXaFf2F55E7kVJON4j4G"; 94 | const string market = SpotifyCountryCodes._From_Token; 95 | 96 | var http = new HttpClient(); 97 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 98 | var artists = new ArtistsApi(http, accounts); 99 | 100 | // act 101 | var result = await artists.GetArtistsTopTracks(artistId, market); 102 | } 103 | 104 | [TestMethod] 105 | [TestCategory("Integration")] 106 | public async Task GetRelatedArtists_When_Given_Valid_ArtistId_Returns_Artists() 107 | { 108 | // arrange 109 | const string artistId = "6lcwlkAjBPSKnFBZjjZFJs"; 110 | 111 | var http = new HttpClient(); 112 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 113 | var artists = new ArtistsApi(http, accounts); 114 | 115 | // act 116 | var result = await artists.GetRelatedArtists(artistId); 117 | 118 | // assert 119 | Assert.AreNotSame(result.Length, 0); 120 | } 121 | 122 | [TestMethod] 123 | [TestCategory("Integration")] 124 | public async Task GetArtistsAlbums_Limit2_ItemsLength2() 125 | { 126 | // arrange 127 | const string artistId = "1tpXaFf2F55E7kVJON4j4G"; 128 | 129 | var http = new HttpClient(); 130 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 131 | var artists = new ArtistsApi(http, accounts); 132 | 133 | // act 134 | var result = await artists.GetArtistsAlbums(artistId, country: SpotifyCountryCodes.New_Zealand, limit: 2); 135 | 136 | // assert 137 | Assert.AreEqual(2, result.Items.Length); 138 | } 139 | 140 | [TestMethod] 141 | [TestCategory("Integration")] 142 | public async Task GetArtistsAlbums_IncludeGroupsSingles_ItemsAllSingles() 143 | { 144 | // arrange 145 | const string artistId = "1tpXaFf2F55E7kVJON4j4G"; 146 | 147 | var http = new HttpClient(); 148 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 149 | var artists = new ArtistsApi(http, accounts); 150 | 151 | // act 152 | var result = await artists.GetArtistsAlbums( 153 | artistId, 154 | country: SpotifyCountryCodes.New_Zealand, 155 | includeGroups: new[] { SpotifyArtistAlbumGroups.Single }); 156 | 157 | // assert 158 | Assert.IsTrue(result.Items.All(a => a.AlbumType == SpotifyArtistAlbumGroups.Single)); 159 | } 160 | } 161 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Authorization/AccountsServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Moq; 8 | using SpotifyApi.NetCore.Authorization; 9 | using SpotifyApi.NetCore.Tests.Mocks; 10 | 11 | namespace SpotifyApi.NetCore.Tests 12 | { 13 | [TestClass] 14 | public class AccountsServiceTests 15 | { 16 | //const string UserHash = "E11AC28538A7C0A827A726DD9B30B710FC1FCAFFFE2E86FCA853AB90E7C710D2"; 17 | 18 | [TestMethod] 19 | public void AuthorizeUrl_StateParam_UrlContainsState() 20 | { 21 | // arrange 22 | const string state = "abc123"; 23 | var http = new MockHttpClient().HttpClient; 24 | var config = new MockConfiguration().Object; 25 | //var tokenStore = new MockRefreshTokenStore(UserHash, config).Object; 26 | var service = new UserAccountsService(http, config); 27 | 28 | // act 29 | string url = service.AuthorizeUrl(state, null); 30 | 31 | // assert 32 | Assert.IsTrue(url.Contains(state), "url result should contain state param"); 33 | } 34 | 35 | [TestMethod] 36 | public void RequestAuthorizationUrl_Scopes_UrlContainsSpaceDelimitedScopes() 37 | { 38 | // arrange 39 | const string state = "abc123"; 40 | 41 | string[] scopes = new[] 42 | { 43 | "user-modify-playback-state", 44 | "user-read-playback-state", 45 | "playlist-read-collaborative", 46 | "playlist-modify-public", 47 | "playlist-modify-private", 48 | "playlist-read-private", 49 | "user-read-email" 50 | }; 51 | 52 | var config = new MockConfiguration().Object; 53 | var http = new HttpClient(); 54 | //var tokenStore = new MockRefreshTokenStore(UserHash, config).Object; 55 | var service = new UserAccountsService(http, config); 56 | 57 | // act 58 | string url = service.AuthorizeUrl(state, scopes); 59 | 60 | // assert 61 | Assert.IsTrue(url.Contains(string.Join("%20", scopes)), "url should contain %20 (space) delimited user scopes"); 62 | Trace.WriteLine("RequestAuthorizationUrl_Scopes_UrlContainsSpaceDelimitedScopes url ="); 63 | Trace.WriteLine(url); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Authorization/BearerAccessTokenTests.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | using SpotifyApi.NetCore.Authorization; 6 | 7 | namespace SpotifyApi.NetCore.Tests.Authorization 8 | { 9 | [TestClass] 10 | public class BearerAccessTokenTests 11 | { 12 | [TestMethod] 13 | public void SetExpires_NowIsNotUtcDate_ReturnsUtcDate() 14 | { 15 | // arrange 16 | var token = new BearerAccessToken{ExpiresIn = 3600}; 17 | var now = DateTime.Now; 18 | 19 | // act 20 | token.SetExpires(now); 21 | 22 | // assert 23 | Assert.AreEqual(token.Expires.Value.Kind, DateTimeKind.Utc); 24 | } 25 | 26 | [TestMethod] 27 | public void SetExpires_NowIsUtcDate_ReturnsUtcDate() 28 | { 29 | // arrange 30 | var token = new BearerAccessToken{ExpiresIn = 3600}; 31 | var now = DateTime.UtcNow; 32 | 33 | // act 34 | token.SetExpires(now); 35 | 36 | // assert 37 | Assert.AreEqual(token.Expires.Value.Kind, DateTimeKind.Utc); 38 | } 39 | 40 | [TestMethod] 41 | [ExpectedException(typeof(InvalidOperationException))] 42 | public void EnforceInvariants_RefreshTokenNotSet_Throws() 43 | { 44 | var token = new BearerAccessRefreshToken { ExpiresIn = 3600 }; 45 | token.SetExpires(DateTime.UtcNow); 46 | token.EnforceInvariants(); 47 | } 48 | 49 | [TestMethod] 50 | [ExpectedException(typeof(InvalidOperationException))] 51 | public void BearerAccessTokenEnforceInvariants_ExpiresNotSet_Throws() 52 | { 53 | var token = new BearerAccessToken(); 54 | token.EnforceInvariants(); 55 | } 56 | 57 | [TestMethod] 58 | [ExpectedException(typeof(InvalidOperationException))] 59 | public void BearerAccessRefreshTokenEnforceInvariants_RefreshTokenSetExpiresNotSet_Throws() 60 | { 61 | var token = new BearerAccessRefreshToken { RefreshToken = "abc" }; 62 | token.EnforceInvariants(); 63 | } 64 | 65 | [TestMethod] 66 | public void EnforceInvariants_RefreshTokenAndExpiresSet_DoesNotThrow() 67 | { 68 | var token = new BearerAccessRefreshToken { Expires = DateTime.UtcNow, RefreshToken = "abc" }; 69 | token.EnforceInvariants(); 70 | } 71 | 72 | [TestMethod] 73 | public void SetExpires_ExpiresIn3600_ExpiryIs1HourGreaterThanNow() 74 | { 75 | // arrange 76 | var token = new BearerAccessToken { ExpiresIn = 3600 }; 77 | var now = DateTime.UtcNow; 78 | 79 | // act 80 | token.SetExpires(now); 81 | 82 | // assert 83 | Assert.AreEqual(now.AddSeconds(3600), token.Expires.Value); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Authorization/UserAccountsServiceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | using SpotifyApi.NetCore.Authorization; 6 | using SpotifyApi.NetCore.Tests.Mocks; 7 | 8 | namespace SpotifyApi.NetCore.Tests 9 | { 10 | [TestClass] 11 | public class UserAccountsServiceTests 12 | { 13 | //TODO 14 | } 15 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/EpisodeApiTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using SpotifyApi.NetCore.Authorization; 6 | using SpotifyApi.NetCore.Models; 7 | 8 | namespace SpotifyApi.NetCore.Tests 9 | { 10 | [TestClass] 11 | public class EpisodeApiTests 12 | { 13 | EpisodesApi api; 14 | string bearerAccessToken; 15 | 16 | [TestInitialize] 17 | public void Initialize() 18 | { 19 | var http = new HttpClient(); 20 | IConfiguration testConfig = TestsHelper.GetLocalConfig(); 21 | bearerAccessToken = testConfig.GetValue(typeof(string), 22 | "SpotifyUserBearerAccessToken").ToString(); 23 | var accounts = new AccountsService(http, testConfig); 24 | api = new EpisodesApi(http, accounts); 25 | } 26 | 27 | [TestCategory("Integration")] 28 | [TestMethod] 29 | public async Task GetEpisode_EpisodeId_IsNotNull() 30 | { 31 | // assert 32 | Assert.IsNotNull(value: await api.GetEpisode("512ojhOuo1ktJprKbVcKyQ", "ES", 33 | bearerAccessToken)); 34 | } 35 | 36 | [TestCategory("Integration")] 37 | [TestMethod] 38 | public async Task GetSeveralEpisodes_EpisodeId_IsNotNull() 39 | { 40 | // assert 41 | Assert.IsNotNull(value: await api.GetSeveralEpisodes( 42 | new string[] { "77o6BIVlYM3msb4MMIL1jH" }, 43 | "ES", 44 | bearerAccessToken)); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Extensions/TracksApiExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System.Net.Http; 5 | using SpotifyApi.NetCore.Extensions; 6 | using Moq; 7 | using SpotifyApi.NetCore.Authorization; 8 | 9 | namespace SpotifyApi.NetCore.Tests.Extensions 10 | { 11 | [TestClass] 12 | public class TracksApiExtensionsTests 13 | { 14 | [TestMethod] 15 | public async Task GetTrackByIsrcCode_ValidIsrc_ReturnsFirstItem() 16 | { 17 | // arrange 18 | const string isrc = "GB0409700200"; 19 | var tracks = new[] { new Track { ExternalIds = new ExternalIds { Isrc = "GB0409700200" } } }; 20 | var mockTracksApi = new Mock(); 21 | mockTracksApi 22 | .Setup(a => a.SearchTracks(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) 23 | .ReturnsAsync(new TracksSearchResult { Items = tracks }); 24 | 25 | // act 26 | var track = await TracksApiExtensions.GetTrackByIsrcCode(mockTracksApi.Object, isrc); 27 | 28 | // assert 29 | Assert.AreEqual(tracks[0], track); 30 | } 31 | 32 | [TestMethod] 33 | [ExpectedException(typeof(ArgumentException))] 34 | public async Task GetTrackByIsrcCode_InvalidIsrc_Exception() 35 | { 36 | // arrange 37 | const string isrc = "NOPE"; 38 | var mockTracksApi = new Mock(); 39 | 40 | // act 41 | var track = await TracksApiExtensions.GetTrackByIsrcCode(mockTracksApi.Object, isrc); 42 | } 43 | 44 | [TestCategory("Integration")] 45 | [TestMethod] 46 | public async Task GetTrackByIsrcCode_Isrc_CorrectResult() 47 | { 48 | // arrange 49 | const string isrc = "GBBKS1700108"; 50 | 51 | var http = new HttpClient(); 52 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 53 | 54 | var api = new TracksApi(http, accounts); 55 | 56 | // act 57 | var track = await api.GetTrackByIsrcCode(isrc); 58 | 59 | // assert 60 | Assert.AreEqual(isrc, track.ExternalIds.Isrc); 61 | } 62 | 63 | [TestCategory("Integration")] 64 | [TestMethod] 65 | public async Task GetTrackByIsrcCode_NonExistentIsrc_ResultIsNull() 66 | { 67 | // arrange 68 | const string isrc = "NOPE12345678"; 69 | 70 | var http = new HttpClient(); 71 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 72 | 73 | var api = new TracksApi(http, accounts); 74 | 75 | // act 76 | var track = await api.GetTrackByIsrcCode(isrc); 77 | 78 | // assert 79 | Assert.IsNull(track); 80 | } 81 | 82 | } 83 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Extensions/UriBuilderExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | 4 | namespace SpotifyApi.NetCore.Tests.Extensions 5 | { 6 | [TestClass] 7 | public class UriBuilderExtensionsTests 8 | { 9 | [TestMethod] 10 | public void AppendToQuery_OneParam_WellFormed() 11 | { 12 | // arrange 13 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 14 | 15 | // act 16 | builder.AppendToQuery("country", "nz"); 17 | 18 | // assert 19 | Assert.AreEqual("?country=nz", builder.Uri.Query); 20 | } 21 | 22 | [TestMethod] 23 | public void AppendToQuery_TwoParams_WellFormed() 24 | { 25 | // arrange 26 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 27 | 28 | // act 29 | builder.AppendToQuery("country", "nz"); 30 | builder.AppendToQuery("limit", 10); 31 | 32 | // assert 33 | Assert.AreEqual("?country=nz&limit=10", builder.Uri.Query); 34 | } 35 | 36 | [TestMethod] 37 | public void AppendToQuery_ParamInOriginalUrlString_WellFormed() 38 | { 39 | // arrange 40 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases?local=en-nz"); 41 | 42 | // act 43 | builder.AppendToQuery("country", "nz"); 44 | builder.AppendToQuery("limit", 10); 45 | 46 | // assert 47 | Assert.AreEqual("?local=en-nz&country=nz&limit=10", builder.Uri.Query); 48 | } 49 | 50 | [TestMethod] 51 | public void AppendToQuery_TwoParamsDifferentValues_WellFormed() 52 | { 53 | // arrange 54 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 55 | 56 | // act 57 | builder.AppendToQuery("artist", "radiohead"); 58 | builder.AppendToQuery("artist", "metallica"); 59 | 60 | // assert 61 | Assert.AreEqual("?artist=radiohead&artist=metallica", builder.Uri.Query); 62 | } 63 | 64 | [TestMethod] 65 | public void AppendToQuery_SpaceInParamValue_EscapedAndWellFormed() 66 | { 67 | // arrange 68 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 69 | 70 | // act 71 | builder.AppendToQuery("artist", "Massive Attack"); 72 | 73 | // assert 74 | Assert.AreEqual("?artist=Massive%20Attack", builder.Uri.Query); 75 | } 76 | 77 | [TestMethod] 78 | public void AppendToQueryIfValueNotNullOrWhiteSpace_QueryNotSetAndValueIsNull_QueryIsNullEmpty() 79 | { 80 | // arrange 81 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 82 | 83 | // act 84 | builder.AppendToQueryIfValueNotNullOrWhiteSpace("artist", null); 85 | 86 | // assert 87 | Assert.IsTrue(string.IsNullOrEmpty(builder.Uri.Query)); 88 | } 89 | 90 | [TestMethod] 91 | public void AppendToQueryIfValueNotNullOrWhiteSpace_QuerySetAndValueIsNull_QueryNotAppended() 92 | { 93 | // arrange 94 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 95 | builder.AppendToQuery("1", "1"); 96 | 97 | // act 98 | builder.AppendToQueryIfValueNotNullOrWhiteSpace("2", null); 99 | 100 | // assert 101 | Assert.AreEqual("?1=1", builder.Uri.Query); 102 | } 103 | 104 | [TestMethod] 105 | public void AppendToQueryIfValueGreaterThan0_QuerySetAndValueIsNull_QueryNotAppended() 106 | { 107 | // arrange 108 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 109 | builder.AppendToQuery("1", "1"); 110 | 111 | // act 112 | builder.AppendToQueryIfValueGreaterThan0("2", null); 113 | 114 | // assert 115 | Assert.AreEqual("?1=1", builder.Uri.Query); 116 | } 117 | 118 | [TestMethod] 119 | public void AppendToQueryIfValueGreaterThan0_QueryNotSetAndValueIs0_QueryIsNullOrEmpty() 120 | { 121 | // arrange 122 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 123 | 124 | // act 125 | builder.AppendToQueryIfValueGreaterThan0("1", 0); 126 | 127 | // assert 128 | Assert.IsTrue(string.IsNullOrEmpty(builder.Uri.Query)); 129 | } 130 | 131 | [TestMethod] 132 | public void AppendToQueryAsCsv_ThreeValues_WellFormed() 133 | { 134 | // arrange 135 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/new-releases"); 136 | 137 | // act 138 | builder.AppendToQueryAsCsv("numbers", new[] { "1", "2", "3" }); 139 | 140 | // assert 141 | Assert.AreEqual("?numbers=1,2,3", builder.Uri.Query); 142 | } 143 | 144 | [TestMethod] 145 | public void AppendToQueryAsTimestampIso8601_DateTime_ExpectedFormat() 146 | { 147 | // arrange 148 | var builder = new UriBuilder("https://api.spotify.com/v1/browse/featured-playlists"); 149 | 150 | // act 151 | builder.AppendToQueryAsTimestampIso8601("timestamp", new DateTime(2014, 10, 23, 9, 0, 0)); 152 | 153 | // assert 154 | Assert.AreEqual("?timestamp=2014-10-23T09:00:00", builder.Uri.Query); 155 | } 156 | 157 | [TestMethod] 158 | public void AppendToQueryIfValueGreaterThan0_LongValue_WellFormed() 159 | { 160 | // arrange 161 | var builder = new UriBuilder("https://api.spotify.com/v1/me/player/recently-played"); 162 | long after = 1484811043508; 163 | 164 | // act 165 | builder.AppendToQueryIfValueGreaterThan0("after", after); 166 | 167 | // assert 168 | Assert.AreEqual($"?after={after}", builder.Uri.Query); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Helpers/TimeRangeHelperTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using SpotifyApi.NetCore.Helpers; 4 | using SpotifyApi.NetCore.Models; 5 | 6 | namespace SpotifyApi.NetCore.Tests.Helpers 7 | { 8 | [TestClass] 9 | public class TimeRangeHelperTests 10 | { 11 | [TestMethod] 12 | public void TimeRangeString_ShortTerm_AreEqual() 13 | { 14 | // assert 15 | Assert.AreEqual("short_term", TimeRangeHelper.TimeRangeString(TimeRange.ShortTerm)); 16 | } 17 | 18 | [TestMethod] 19 | public void TimeRangeString_MediumTerm_AreEqual() 20 | { 21 | // assert 22 | Assert.AreEqual("medium_term", TimeRangeHelper.TimeRangeString(TimeRange.MediumTerm)); 23 | } 24 | 25 | [TestMethod] 26 | public void TimeRangeString_LongTerm_AreEqual() 27 | { 28 | // assert 29 | Assert.AreEqual("long_term", TimeRangeHelper.TimeRangeString(TimeRange.LongTerm)); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Integration/AuthorizationCodeFlowTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Diagnostics; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Configuration; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | using Moq; 10 | using SpotifyApi.NetCore.Authorization; 11 | using SpotifyApi.NetCore.Tests.Mocks; 12 | 13 | namespace SpotifyApi.NetCore.Tests.Integration 14 | { 15 | [TestClass] 16 | public class AuthorizationCodeFlowTests 17 | { 18 | UserAccountsService _accounts; 19 | //IRefreshTokenProvider _refreshTokenProvider; 20 | 21 | //const string UserHash = "E11AC28538A7C0A827A726DD9B30B710FC1FCAFFFE2E86FCA853AB90E7C710D2"; 22 | 23 | [TestInitialize] 24 | public void Initialize() 25 | { 26 | //_refreshTokenProvider = new MockRefreshTokenStore(UserHash).Object; 27 | _accounts = new UserAccountsService(new HttpClient(), TestsHelper.GetLocalConfig()); 28 | } 29 | 30 | //[TestMethod] // only used for manual debugging 31 | public void ControllerAuthorize1() 32 | { 33 | // controller creates state, saves a hash (userHash, state) 34 | string state = Guid.NewGuid().ToString("N"); 35 | 36 | // controller encodes userHash and state (this is optional) 37 | // controller calls Helper to get Auth URL (userHash, state) 38 | string url = _accounts.AuthorizeUrl(state, new[] 39 | { 40 | "user-modify-playback-state", 41 | "user-read-playback-state", 42 | "user-read-currently-playing", 43 | 44 | "user-library-modify", 45 | "user-library-read", 46 | "user-top-read", 47 | "user-read-playback-position", 48 | "user-read-recently-played", 49 | "user-follow-read", 50 | "user-follow-modify" 51 | 52 | //"playlist-read-collaborative", 53 | //"playlist-modify-public", 54 | //"playlist-modify-private", 55 | //"playlist-read-private", 56 | //"user-read-email", 57 | //"user-read-private", 58 | 59 | }); 60 | 61 | Trace.WriteLine(url); 62 | 63 | // controller redirects to URL 64 | } 65 | 66 | //[TestMethod] // only used for manual debugging 67 | public async Task ControllerAuthorize2() 68 | { 69 | // spotify calls back to localhost /authorize/spotify 70 | const string codeParam = "AQArbSU1WPqZfcCiwsUUihPDqC4fc2_UPA-BZ3vgdMuJ6005YFgQsJjEaUS28g9Cq8ifBo9K-EaV2uL7APHgzrFPNxTO6hkWQFKay9YoFxXyNmhRZx1wQ9u7WsWLXyur-GrYxYLrz6mMfPI0zEy2frLV8a9uoCfZXcZqpVGLseWUYSltraikCX78VlsMyxs0oqp_h8-MdWk0-GjzOdUMKRUs5PAQArpE81GCGhsyyT4J28PUv50dPzGbb5dWnI0oQc2xEpFdRVhMdai0-uWQO9Ej9vUN3wp7YBjC_LlOuYsw8PMAPpEhaAHSvZOjOpRgt7DMdvLOCJr7Fc44mgHe26eeOuqHpVIKNLSYjCOMjpJecblnEbHE5uly4XimJ-ZuYzns5tUs-swmMi2zWl0RADEfqwL9C9K-TzBgMR8umTKcV_rMrGMNbLtGvODUtDKg51Uq8TqrzMp_b84aQDdzbgeXecJez7N1vRGHPLPhtLln6s8&state=000ad7aba70a453490ecb0228bbf39e9"; 71 | 72 | // controller calls Accounts Service to get access and refresh tokens 73 | // account service updates store 74 | var token = await _accounts.RequestAccessRefreshToken(codeParam); 75 | Trace.WriteLine(token.AccessToken); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Integration/CompetingAccessTokenRequests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using SpotifyApi.NetCore.Authorization; 7 | using SpotifyApi.NetCore.Tests.Mocks; 8 | 9 | namespace SpotifyApi.NetCore.Tests.Integration 10 | { 11 | [TestClass] 12 | [TestCategory("Integration")] 13 | public class CompetingAccessTokenRequests 14 | { 15 | [TestMethod] 16 | public async Task TwoCompetingAccessTokenRequestsGetConsistentResults() 17 | { 18 | const string artistId = "1tpXaFf2F55E7kVJON4j4G"; 19 | 20 | var http1 = new HttpClient(); 21 | var accounts1 = new AccountsService(http1, TestsHelper.GetLocalConfig()); 22 | var artists1 = new ArtistsApi(http1, accounts1); 23 | 24 | var http2 = new HttpClient(); 25 | var accounts2 = new AccountsService(http2, TestsHelper.GetLocalConfig()); 26 | var artists2 = new ArtistsApi(http2, accounts2); 27 | 28 | // act 29 | await artists1.GetArtist(artistId); 30 | await artists2.GetArtist(artistId); 31 | 32 | // assert 33 | // no error 34 | } 35 | 36 | [TestMethod] 37 | public async Task TwoCompetingAccessTokenRequestsSameHttpClientGetConsistentResults() 38 | { 39 | const string artistId = "1tpXaFf2F55E7kVJON4j4G"; 40 | 41 | var http1 = new HttpClient(); 42 | var accounts1 = new AccountsService(http1, TestsHelper.GetLocalConfig()); 43 | var artists1 = new ArtistsApi(http1, accounts1); 44 | 45 | var accounts2 = new AccountsService(http1, TestsHelper.GetLocalConfig()); 46 | var artists2 = new ArtistsApi(http1, accounts2); 47 | 48 | // act 49 | await artists1.GetArtist(artistId); 50 | await artists2.GetArtist(artistId); 51 | 52 | // assert 53 | // no error 54 | } 55 | 56 | //TODO: Something is up - the first error is not being thrown to the test runner... 57 | //[TestMethod] 58 | public async Task TwoCompetingUserAccessTokenRequestsGetConsistentResults() 59 | { 60 | //const string userHash = "E11AC28538A7C0A827A726DD9B30B710FC1FCAFFFE2E86FCA853AB90E7C710D2"; 61 | //const string spotifyUri = "spotify:user:palsvensson:playlist:2iL5fr6OmN8f4yoQvvuWSf"; 62 | 63 | //var store = new MockRefreshTokenStore(userHash).Object; 64 | 65 | var http1 = new HttpClient(); 66 | var accounts1 = new UserAccountsService(http1, TestsHelper.GetLocalConfig()); 67 | var player1 = new PlayerApi(http1, accounts1); 68 | 69 | var http2 = new HttpClient(); 70 | var accounts2 = new UserAccountsService(http2, TestsHelper.GetLocalConfig()); 71 | var player2 = new PlayerApi(http2, accounts2); 72 | 73 | // act 74 | //try 75 | //{ 76 | //TODO: Call Device method instead 77 | //await player1.PlayContext(userHash, spotifyUri); 78 | //} 79 | //catch (SpotifyApiErrorException ex) 80 | //{ 81 | //Trace.WriteLine(ex.Message); 82 | //} 83 | 84 | //try 85 | //{ 86 | //await player2.PlayContext(userHash, spotifyUri); 87 | //} 88 | //catch (SpotifyApiErrorException ex) 89 | //{ 90 | // Trace.WriteLine(ex.Message); 91 | //} 92 | 93 | // assert 94 | // no error 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Integration/UsageTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using SpotifyApi.NetCore.Authorization; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Security; 9 | using System.Threading.Tasks; 10 | 11 | namespace SpotifyApi.NetCore.Tests.Integration 12 | { 13 | [TestClass] 14 | [TestCategory("Integration")] 15 | public class UsageTests 16 | { 17 | /// The Usage examples for README.md 18 | [TestMethod] 19 | public async Task Usage1() 20 | { 21 | // HttpClient and AccountsService can be reused. 22 | // Tokens are automatically cached and refreshed 23 | var http = new HttpClient(); 24 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 25 | 26 | // Get an artist by Spotify Artist Id 27 | var artists = new ArtistsApi(http, accounts); 28 | Artist artist = await artists.GetArtist("1tpXaFf2F55E7kVJON4j4G"); 29 | string artistName = artist.Name; 30 | Trace.WriteLine($"Artist.Name = {artistName}"); 31 | 32 | // Get recommendations based on seed Artist Ids 33 | var browse = new BrowseApi(http, accounts); 34 | RecommendationsResult result = await browse.GetRecommendations(new[] { "1tpXaFf2F55E7kVJON4j4G", "4Z8W4fKeB5YxbusRsdQVPb" }, null, null); 35 | string firstTrackName = result.Tracks[0].Name; 36 | Trace.WriteLine($"First recommendation = {firstTrackName}"); 37 | 38 | // Page through a list of tracks in a Playlist 39 | var playlists = new PlaylistsApi(http, accounts); 40 | int limit = 100; 41 | PlaylistPaged playlist = await playlists.GetTracks("4h4urfIy5cyCdFOc1Ff4iN", limit: limit); 42 | int offset = 0; 43 | int j = 0; 44 | // using System.Linq 45 | while (playlist.Items.Any()) 46 | { 47 | for (int i = 0; i < playlist.Items.Length; i++) 48 | { 49 | Trace.WriteLine($"Track #{j += 1}: {playlist.Items[i].Track.Artists[0].Name} / {playlist.Items[i].Track.Name}"); 50 | } 51 | offset += limit; 52 | playlist = await playlists.GetTracks("4h4urfIy5cyCdFOc1Ff4iN", limit: limit, offset: offset); 53 | } 54 | } 55 | 56 | //[TestMethod] 57 | public async Task Usage2() 58 | { 59 | // Get a list of a User's devices 60 | // This requires User authentication and authorization. 61 | // A `UserAccountsService` is provided to help with this. 62 | 63 | // HttpClient and UserAccountsService can be reused. 64 | // Tokens can be cached by your code 65 | var http = new HttpClient(); 66 | var accounts = new UserAccountsService(http, TestsHelper.GetLocalConfig()); 67 | 68 | // See https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow 69 | // for an explanation of the Authorization code flow 70 | 71 | // Generate a random state value to use in the Auth request 72 | string state = Guid.NewGuid().ToString("N"); 73 | // Accounts service will derive the Auth URL for you 74 | string url = accounts.AuthorizeUrl(state, new[] { "user-read-playback-state" }); 75 | 76 | /* 77 | Redirect the user to `url` and when they have auth'ed Spotify will redirect to your reply URL 78 | The response will include two query parameters: `state` and `code`. 79 | For a full working example see `SpotifyApi.NetCore.Samples`. 80 | */ 81 | var query = new Dictionary(); 82 | 83 | // Check that the request has not been tampered with by checking the `state` value matches 84 | if (state != query["state"]) throw new ArgumentException(); 85 | 86 | // Use the User accounts service to swap `code` for a Refresh token 87 | BearerAccessRefreshToken token = await accounts.RequestAccessRefreshToken(query["code"]); 88 | 89 | // Use the Bearer (Access) Token to call the Player API 90 | var player = new PlayerApi(http, accounts); 91 | Device[] devices = await player.GetDevices(accessToken: token.AccessToken); 92 | 93 | foreach(Device device in devices) 94 | { 95 | Trace.WriteLine($"Device {device.Name} Status = {device.Type} Active = {device.IsActive}"); 96 | } 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/LibraryApiTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using SpotifyApi.NetCore.Authorization; 7 | using SpotifyApi.NetCore.Models; 8 | 9 | namespace SpotifyApi.NetCore.Tests 10 | { 11 | [TestClass] 12 | public class LibraryApiTests 13 | { 14 | LibraryApi api; 15 | string bearerAccessToken; 16 | 17 | [TestInitialize] 18 | public void Initialize() 19 | { 20 | var http = new HttpClient(); 21 | IConfiguration testConfig = TestsHelper.GetLocalConfig(); 22 | bearerAccessToken = testConfig.GetValue(typeof(string), 23 | "SpotifyUserBearerAccessToken").ToString(); 24 | var accounts = new AccountsService(http, testConfig); 25 | api = new LibraryApi(http, accounts); 26 | } 27 | 28 | [TestCategory("Integration")] 29 | [TestCategory("User")] 30 | [TestMethod] 31 | public async Task CheckUserSavedAlbums_AlbumIds_AnyItems() 32 | { 33 | // assert 34 | Assert.IsTrue((await api.CheckUsersSavedAlbums( 35 | new string[] { "07bYtmE3bPsLB6ZbmmFi8d" }, 36 | bearerAccessToken)).Any()); 37 | } 38 | 39 | [TestCategory("Integration")] 40 | [TestMethod] 41 | public async Task CheckUserSavedShows_ShowIds_AnyItems() 42 | { 43 | // assert 44 | Assert.IsTrue((await api.CheckUsersSavedShows( 45 | new string[] { "5AvwZVawapvyhJUIx71pdJ" }, 46 | bearerAccessToken)).Any()); 47 | } 48 | 49 | [TestCategory("Integration")] 50 | [TestMethod] 51 | public async Task CheckUserSavedTracks_TrackIds_AnyItems() 52 | { 53 | // assert 54 | Assert.IsTrue((await api.CheckUsersSavedTracks( 55 | new string[] { "0udZHhCi7p1YzMlvI4fXoK" }, 56 | bearerAccessToken)).Any()); 57 | } 58 | 59 | [TestCategory("Integration")] 60 | [TestMethod] 61 | public async Task GetUserSavedAlbums_IsNotNull() 62 | { 63 | // assert 64 | Assert.IsNotNull(await api.GetCurrentUsersSavedAlbums(market: "", 65 | accessToken: bearerAccessToken)); 66 | } 67 | 68 | [TestCategory("Integration")] 69 | [TestMethod] 70 | public async Task GetUserSavedShows_IsNotNull() 71 | { 72 | // assert 73 | Assert.IsNotNull(await api.GetUsersSavedShows( 74 | accessToken: bearerAccessToken)); 75 | } 76 | 77 | [TestCategory("Integration")] 78 | [TestMethod] 79 | public async Task GetUserSavedTracks_IsNotNull() 80 | { 81 | // assert 82 | Assert.IsNotNull(await api.GetUsersSavedTracks( 83 | accessToken: bearerAccessToken)); 84 | } 85 | 86 | [TestCategory("Integration")] 87 | [TestMethod] 88 | public async Task RemoveAlbumsForCurrentUser_AlbumIds_IsFalse() 89 | { 90 | // act 91 | await api.RemoveAlbumsForCurrentUser( 92 | new string[] { "07bYtmE3bPsLB6ZbmmFi8d" }, 93 | bearerAccessToken); 94 | 95 | // assert 96 | // checking if album was removed for the current user. 97 | Assert.IsFalse((await api.CheckUsersSavedAlbums( 98 | new string[] { "07bYtmE3bPsLB6ZbmmFi8d" }, 99 | bearerAccessToken)).FirstOrDefault()); 100 | } 101 | 102 | [TestCategory("Integration")] 103 | [TestMethod] 104 | public async Task RemoveUserSavedShows_ShowIds_IsFalse() 105 | { 106 | // act 107 | await api.RemoveUsersSavedShows( 108 | new string[] { "5AvwZVawapvyhJUIx71pdJ" }, "ES", 109 | bearerAccessToken); 110 | 111 | // assert 112 | // checking if show was removed for the current user 113 | Assert.IsFalse((await api.CheckUsersSavedShows( 114 | new string[] { "5AvwZVawapvyhJUIx71pdJ" }, 115 | bearerAccessToken)).FirstOrDefault()); 116 | } 117 | 118 | [TestCategory("Integration")] 119 | [TestMethod] 120 | public async Task RemoveUserSavedTracks_TrackIds_IsFalse() 121 | { 122 | // act 123 | await api.RemoveUsersSavedTracks(new string[] { "4iV5W9uYEdYUVa79Axb7Rh" }, 124 | bearerAccessToken); 125 | 126 | // assert 127 | // checking if track was removed for the current user 128 | Assert.IsFalse((await api.CheckUsersSavedTracks( 129 | new string[] { "4iV5W9uYEdYUVa79Axb7Rh" }, 130 | bearerAccessToken)).FirstOrDefault()); 131 | } 132 | 133 | [TestCategory("Integration")] 134 | [TestMethod] 135 | public async Task SaveAlbumsForCurrentUser_AlbumIds_IsTrue() 136 | { 137 | // act 138 | await api.SaveAlbumsForCurrentUser( 139 | new string[] { "07bYtmE3bPsLB6ZbmmFi8d" }, 140 | bearerAccessToken); 141 | 142 | // assert 143 | // checking if album was saved for the current user 144 | Assert.IsTrue((await api.CheckUsersSavedAlbums( 145 | new string[] { "07bYtmE3bPsLB6ZbmmFi8d" }, 146 | bearerAccessToken)).FirstOrDefault()); 147 | } 148 | 149 | [TestCategory("Integration")] 150 | [TestMethod] 151 | public async Task SaveShowsForCurrentUser_ShowIds_IsTrue() 152 | { 153 | // act 154 | await api.SaveShowsForCurrentUser( 155 | new string[] { "5AvwZVawapvyhJUIx71pdJ" }, 156 | bearerAccessToken); 157 | 158 | // assert 159 | // checking if show was saved for the current user 160 | Assert.IsTrue((await api.CheckUsersSavedShows( 161 | new string[] { "5AvwZVawapvyhJUIx71pdJ" }, 162 | bearerAccessToken)).FirstOrDefault()); 163 | } 164 | 165 | [TestCategory("Integration")] 166 | [TestMethod] 167 | public async Task SaveTracksForCurrentUser_TrackIds_IsTrue() 168 | { 169 | // act 170 | await api.SaveTracksForUser( 171 | new string[] { "7ouMYWpwJ422jRcDASZB7P" }, 172 | bearerAccessToken); 173 | 174 | // assert 175 | // checking if track was saved for the current user 176 | Assert.IsTrue((await api.CheckUsersSavedTracks( 177 | new string[] { "7ouMYWpwJ422jRcDASZB7P" }, 178 | bearerAccessToken)).FirstOrDefault()); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Mocks/MockAccountsService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Moq; 3 | using SpotifyApi.NetCore.Authorization; 4 | 5 | namespace SpotifyApi.NetCore.Tests.Mocks 6 | { 7 | public class MockAccountsService : Mock 8 | { 9 | public MockAccountsService() 10 | { 11 | const string token = "abcd1234"; 12 | Setup(s => s.GetAccessToken()).ReturnsAsync(token); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Mocks/MockConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Moq; 4 | 5 | namespace SpotifyApi.NetCore.Tests.Mocks 6 | { 7 | public class MockConfiguration : Mock 8 | { 9 | public MockConfiguration() 10 | { 11 | SetupGet(c=>c["SpotifyApiClientId"]).Returns("(SpotifyApiClientId)"); 12 | SetupGet(c=>c["SpotifyApiClientSecret"]).Returns("(SpotifyApiClientSecret)"); 13 | SetupGet(c=>c["SpotifyAuthRedirectUri"]).Returns("(SpotifyAuthRedirectUri)"); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Mocks/MockHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Net.Http; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Moq; 6 | using Moq.Protected; 7 | 8 | namespace SpotifyApi.NetCore.Tests.Mocks 9 | { 10 | /// 11 | /// A Mock HttpClient helper 12 | /// 13 | internal class MockHttpClient 14 | { 15 | private Mock _mockHttpMessageHandler = new Mock(); 16 | public HttpClient HttpClient { get; private set; } 17 | 18 | public Mock MockHttpMessageHandler { get { return _mockHttpMessageHandler; } } 19 | 20 | public MockHttpClient() 21 | { 22 | _mockHttpMessageHandler = new Mock(); 23 | HttpClient = new HttpClient(_mockHttpMessageHandler.Object); 24 | } 25 | 26 | /// 27 | /// Sets up the mock message handler to return the given `responseContent` and status 200 OK. 28 | /// 29 | /// The response as string 30 | /// Mock handler for further setup and validation if required 31 | internal Moq.Language.Flow.IReturnsResult SetupSendAsync(string responseContent) 32 | { 33 | return _mockHttpMessageHandler.Protected() 34 | .Setup>("SendAsync", ItExpr.IsAny(), 35 | ItExpr.IsAny()) 36 | .Returns(Task.Factory.StartNew(() => 37 | new HttpResponseMessage(HttpStatusCode.OK) 38 | { 39 | Content = new StringContent(responseContent) 40 | })); 41 | } 42 | 43 | /// 44 | /// Sets up the mock message handler to return status 200 OK with no content. 45 | /// 46 | /// Mock handler for further setup and validation if required 47 | internal Moq.Language.Flow.IReturnsResult SetupSendAsync() 48 | { 49 | return _mockHttpMessageHandler.Protected() 50 | .Setup>("SendAsync", ItExpr.IsAny(), 51 | ItExpr.IsAny()) 52 | .Returns(Task.Factory.StartNew(() => 53 | new HttpResponseMessage(HttpStatusCode.OK))); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Mocks/MockRefreshTokenStore.cs: -------------------------------------------------------------------------------- 1 | //using System; 2 | //using Microsoft.Extensions.Configuration; 3 | //using Moq; 4 | //using SpotifyApi.NetCore.Authorization; 5 | 6 | //namespace SpotifyApi.NetCore.Tests.Mocks 7 | //{ 8 | // public class MockRefreshTokenStore : Mock 9 | // { 10 | // public MockRefreshTokenStore(string userHash, IConfiguration config = null) 11 | // { 12 | // // setup a mock to return the refresh token 13 | // config = config ?? TestsHelper.GetLocalConfig(); 14 | // Setup(s=>s.GetRefreshToken(userHash)).ReturnsAsync(config["SpotifyAuthRefreshToken"]); 15 | // } 16 | // } 17 | //} -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/Models/PlayHistoryTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Diagnostics; 4 | 5 | namespace SpotifyApi.NetCore.Tests.Models 6 | { 7 | [TestClass] 8 | public class PlayHistoryTests 9 | { 10 | [TestMethod] 11 | public void PlayedAtDateTime_IsoDateTime_ReturnsEquivalentDateTimeOffsetValue() 12 | { 13 | // arrange 14 | var playHistory = new PlayHistory { PlayedAt = "2020-07-14T07:43:21.064Z" }; 15 | 16 | // act & assert 17 | Assert.AreEqual( 18 | new DateTimeOffset(2020, 7, 14, 7, 43, 21, 64, TimeSpan.FromHours(0)), 19 | playHistory.PlayedAtDateTime()); 20 | } 21 | 22 | [TestMethod] 23 | public void MyTestMethod() 24 | { 25 | var timestamp = DateTimeOffset.TryParse("2020-06-30T17:23:18.492Z", out var result); 26 | Debug.WriteLine(result.ToUnixTimeMilliseconds()); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/PersonalizationApiTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using SpotifyApi.NetCore.Authorization; 4 | using SpotifyApi.NetCore.Models; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | 8 | namespace SpotifyApi.NetCore.Tests 9 | { 10 | [TestClass] 11 | public class PersonalizationApiTests 12 | { 13 | PersonalizationApi api; 14 | string bearerAccessToken; 15 | 16 | [TestInitialize] 17 | public void Initialize() 18 | { 19 | var http = new HttpClient(); 20 | IConfiguration testConfig = TestsHelper.GetLocalConfig(); 21 | bearerAccessToken = testConfig.GetValue(typeof(string), 22 | "SpotifyUserBearerAccessToken").ToString(); 23 | var accounts = new AccountsService(http, testConfig); 24 | api = new PersonalizationApi(http, accounts); 25 | } 26 | 27 | [TestCategory("Integration")] 28 | [TestCategory("User")] 29 | [TestMethod] 30 | public async Task GetUserTopArtists_TimeRange_LongTerm_IsNotNull() 31 | { 32 | // assert 33 | Assert.IsNotNull(value: await api.GetUsersTopArtists( 34 | timeRange: TimeRange.LongTerm, 35 | accessToken: bearerAccessToken)); 36 | } 37 | 38 | [TestCategory("Integration")] 39 | [TestCategory("User")] 40 | [TestMethod] 41 | public async Task GetUserTopArtists_TimeRange_MediumTerm_IsNotNull() 42 | { 43 | // assert 44 | Assert.IsNotNull(value: await api.GetUsersTopArtists( 45 | timeRange: TimeRange.MediumTerm, 46 | accessToken: bearerAccessToken)); 47 | } 48 | 49 | [TestCategory("Integration")] 50 | [TestCategory("User")] 51 | [TestMethod] 52 | public async Task GetUserTopArtists_TimeRange_ShortTerm_IsNotNull() 53 | { 54 | // assert 55 | Assert.IsNotNull(value: await api.GetUsersTopArtists( 56 | timeRange: TimeRange.ShortTerm, 57 | accessToken: bearerAccessToken)); 58 | } 59 | 60 | [TestCategory("Integration")] 61 | [TestCategory("User")] 62 | [TestMethod] 63 | public async Task GetUserTopTracks_TimeRange_LongTerm_IsNotNull() 64 | { 65 | // assert 66 | Assert.IsNotNull(value: await api.GetUsersTopTracks( 67 | timeRange: TimeRange.LongTerm, 68 | accessToken: bearerAccessToken)); 69 | } 70 | 71 | [TestCategory("Integration")] 72 | [TestCategory("User")] 73 | [TestMethod] 74 | public async Task GetUserTopTracks_TimeRange_MediumTerm_IsNotNull() 75 | { 76 | // assert 77 | Assert.IsNotNull(value: await api.GetUsersTopTracks( 78 | timeRange: TimeRange.MediumTerm, 79 | accessToken: bearerAccessToken)); 80 | } 81 | 82 | [TestCategory("Integration")] 83 | [TestCategory("User")] 84 | [TestMethod] 85 | public async Task GetUserTopTracks_TimeRange_ShortTerm_IsNotNull() 86 | { 87 | // assert 88 | Assert.IsNotNull(value: await api.GetUsersTopTracks( 89 | timeRange: TimeRange.ShortTerm, 90 | accessToken: bearerAccessToken)); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/README.md: -------------------------------------------------------------------------------- 1 | ## Basic usage 2 | 3 | - Add "appsettings.local.json" to this directory. 4 | - Add "SpotifyClientId" and "SpotifyClientSecret" inside appsettings.local.json 5 | - run 'dotnet test' 6 | 7 | See also: /CONTRIB.md -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/ShowsApiTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Net.Http; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using SpotifyApi.NetCore.Authorization; 7 | using SpotifyApi.NetCore.Models; 8 | 9 | namespace SpotifyApi.NetCore.Tests 10 | { 11 | [TestClass] 12 | public class ShowsApiTests 13 | { 14 | ShowsApi api; 15 | string bearerAccessToken; 16 | 17 | [TestInitialize] 18 | public void Initialize() 19 | { 20 | var http = new HttpClient(); 21 | IConfiguration testConfig = TestsHelper.GetLocalConfig(); 22 | bearerAccessToken = testConfig.GetValue(typeof(string), 23 | "SpotifyUserBearerAccessToken").ToString(); 24 | var accounts = new AccountsService(http, testConfig); 25 | api = new ShowsApi(http, accounts); 26 | } 27 | 28 | [TestCategory("Integration")] 29 | [TestMethod] 30 | public async Task GetShow_ShowId_IsNotNull() 31 | { 32 | // assert 33 | Assert.IsNotNull(await api.GetShow("38bS44xjbVVZ3No3ByF1dJ", 34 | accessToken: bearerAccessToken)); 35 | } 36 | 37 | [TestCategory("Integration")] 38 | [TestMethod] 39 | public async Task GetSeveralShows_ShowIds_IsNotNull() 40 | { 41 | // assert 42 | Assert.IsNotNull(await api.GetSeveralShows(new string[] { "5CfCWKI5pZ28U0uOzXkDHe" }, 43 | accessToken: bearerAccessToken)); 44 | } 45 | 46 | [TestCategory("Integration")] 47 | [TestMethod] 48 | public async Task GetShowEpisodes_ShowId_IsNotNull() 49 | { 50 | // assert 51 | Assert.IsNotNull(await api.GetShowEpisodes("38bS44xjbVVZ3No3ByF1dJ", 52 | accessToken: bearerAccessToken)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/SpotifyApi.NetCore.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Always 27 | 28 | 29 | Always 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/SpotifyApiErrorExceptionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using Moq; 9 | using Moq.Protected; 10 | using Newtonsoft.Json; 11 | using Newtonsoft.Json.Linq; 12 | using SpotifyApi.NetCore.Http; 13 | using SpotifyApi.NetCore.Tests.Http; 14 | 15 | namespace SpotifyApi.NetCore.Tests 16 | { 17 | [TestClass] 18 | public class SpotifyApiErrorExceptionTests 19 | { 20 | [TestMethod] 21 | public async Task ReadErrorResponse_EmptyPlainText_ReturnsNull() 22 | { 23 | // arrange 24 | var response = new HttpResponseMessage(HttpStatusCode.NotFound) 25 | { 26 | Content = new StringContent("", Encoding.Unicode,"text/plain") 27 | }; 28 | 29 | // act 30 | var error = await SpotifyApiErrorException.ReadErrorResponse(response); 31 | 32 | // assert 33 | Assert.IsNull(error); 34 | } 35 | 36 | [TestMethod] 37 | public async Task ReadErrorResponse_NoContent_ReturnsNull() 38 | { 39 | // arrange 40 | var response = new HttpResponseMessage(HttpStatusCode.NotFound); 41 | 42 | // act 43 | var error = await SpotifyApiErrorException.ReadErrorResponse(response); 44 | 45 | // assert 46 | Assert.IsNull(error); 47 | } 48 | 49 | [TestMethod] 50 | public async Task ReadErrorResponse_ValidJson_ReturnsNotNull() 51 | { 52 | // arrange 53 | 54 | const string content = @"{ 55 | ""error"": { 56 | ""status"": 404, 57 | ""message"": ""No active device found"" 58 | } 59 | }"; 60 | 61 | var response = new HttpResponseMessage(HttpStatusCode.NotFound) 62 | { 63 | Content = new StringContent(content, Encoding.Unicode,"application/json") 64 | }; 65 | 66 | var j = JsonConvert.DeserializeObject(content) as JObject; 67 | var t = j["error"].Type; 68 | Debug.WriteLine($"error type = {t}"); 69 | Debug.WriteLine($"error.message = {j["error"].Value("message")}"); 70 | Debug.WriteLine($"error.foo = {j["error"].Value("foo")}"); 71 | 72 | // act 73 | var error = await SpotifyApiErrorException.ReadErrorResponse(response); 74 | 75 | // assert 76 | Assert.IsNotNull(error); 77 | } 78 | 79 | [TestMethod] 80 | public async Task ReadErrorResponse_AlternateErrorFormat_ReturnsNotNull() 81 | { 82 | // arrange 83 | const string content = "{\"error\":\"invalid_grant\",\"error_description\":\"Invalid authorization code\"}"; 84 | 85 | var response = new HttpResponseMessage(HttpStatusCode.BadRequest) 86 | { 87 | Content = new StringContent(content, Encoding.Unicode,"application/json") 88 | }; 89 | 90 | var j = JsonConvert.DeserializeObject(content) as JObject; 91 | var t = j["error"].Type; 92 | Debug.WriteLine($"error type = {t}"); 93 | Debug.WriteLine($"error_description = {j["error_description"].Value()}"); 94 | 95 | if (j.ContainsKey("foo")) 96 | { 97 | var t2 = j["foo"].Type; 98 | Debug.WriteLine($"foo type = {t2}"); 99 | } 100 | 101 | // act 102 | var error = await SpotifyApiErrorException.ReadErrorResponse(response); 103 | 104 | // assert 105 | Assert.IsNotNull(error); 106 | } 107 | 108 | // 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/SpotifyWebApiTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System; 3 | using System.Threading.Tasks; 4 | using Moq; 5 | using System.Net.Http; 6 | using SpotifyApi.NetCore.Authorization; 7 | 8 | namespace SpotifyApi.NetCore.Tests 9 | { 10 | [TestClass] 11 | public class SpotifyWebApiTests 12 | { 13 | [TestMethod] 14 | public async Task GetAccessToken_ParamNullFieldNotNull_ReturnsField() 15 | { 16 | // arrange 17 | const string accessToken = "abc123"; 18 | var mockSpotifyWebApi = new Mock(new Mock().Object, accessToken) 19 | { CallBase = true }; 20 | 21 | // act 22 | string token = await mockSpotifyWebApi.Object.GetAccessToken(null); 23 | 24 | // assert 25 | Assert.AreEqual(accessToken, token); 26 | } 27 | 28 | [TestMethod] 29 | [ExpectedException(typeof(InvalidOperationException))] 30 | public async Task GetAccessToken_ParamNullFieldNullTokenProviderNullAccountsServiceNull_ThrowsInvalidOperationException() 31 | { 32 | // arrange 33 | const string accessToken = null; 34 | 35 | var mockSpotifyWebApi = new Mock(new Mock().Object) 36 | { CallBase = true }; 37 | 38 | // act 39 | string token = await mockSpotifyWebApi.Object.GetAccessToken(accessToken); 40 | } 41 | 42 | [TestMethod] 43 | [ExpectedException(typeof(InvalidOperationException))] 44 | public async Task GetAccessToken_ParamNullTokenProviderGetAccessTokenReturnsNull_ThrowsInvalidOperationException() 45 | { 46 | // arrange 47 | const string accessToken = null; 48 | 49 | var mockTokenProvider = new Mock(); 50 | 51 | var mockSpotifyWebApi = new Mock(new Mock().Object, mockTokenProvider.Object) 52 | { CallBase = true }; 53 | 54 | // act 55 | string token = await mockSpotifyWebApi.Object.GetAccessToken(accessToken); 56 | } 57 | 58 | [TestMethod] 59 | public async Task GetAccessToken_ParamNullTokenProviderGetAccessTokenReturnsToken_ReturnsTokenProviderToken() 60 | { 61 | // arrange 62 | const string accessToken = null; 63 | const string tokenProviderToken = "abc123"; 64 | 65 | var mockTokenProvider = new Mock(); 66 | mockTokenProvider.Setup(p => p.GetAccessToken()).ReturnsAsync(tokenProviderToken); 67 | 68 | var mockSpotifyWebApi = new Mock(new Mock().Object, mockTokenProvider.Object) 69 | { CallBase = true }; 70 | 71 | // act 72 | string token = await mockSpotifyWebApi.Object.GetAccessToken(accessToken); 73 | 74 | // assert 75 | Assert.AreEqual(tokenProviderToken, token); 76 | } 77 | 78 | [TestMethod] 79 | public async Task GetAccessToken_ParamNullTokenProviderNullAccountsServiceGetAppAccessTokenReturnsToken_ReturnsAccountsServiceToken() 80 | { 81 | // arrange 82 | const string accessToken = null; 83 | const string accountsServiceToken = "ghi789"; 84 | 85 | var mockAccountsService = new Mock(); 86 | mockAccountsService.Setup(s => s.GetAccessToken()).ReturnsAsync(accountsServiceToken); 87 | 88 | var mockSpotifyWebApi = new Mock(new Mock().Object, mockAccountsService.Object) 89 | { CallBase = true }; 90 | 91 | // act 92 | string token = await mockSpotifyWebApi.Object.GetAccessToken(accessToken); 93 | 94 | // assert 95 | Assert.AreEqual(accountsServiceToken, token); 96 | } 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/TestsHelper.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Configuration; 4 | using SpotifyApi.NetCore.Authorization; 5 | 6 | namespace SpotifyApi.NetCore.Tests 7 | { 8 | /// 9 | /// Helpers for tests 10 | /// 11 | /// Not thread safe. Will need to be refactored if tests runs are parallelized. 12 | internal static class TestsHelper 13 | { 14 | private static UserAccountsService _accounts = new UserAccountsService(GetLocalConfig()); 15 | private static string _accessToken; 16 | private static IConfiguration _config; 17 | 18 | internal static IConfiguration GetLocalConfig() 19 | { 20 | if (_config == null) 21 | { 22 | _config = new ConfigurationBuilder() 23 | // Using "..", "..", ".." vs. "..\\..\\.." for system-agnostic path resolution 24 | // Reference: https://stackoverflow.com/questions/14899422/how-to-navigate-a-few-folders-up#comment97806320_30902714 25 | .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..")) 26 | .AddJsonFile("appsettings.local.json", false) 27 | .Build(); 28 | } 29 | 30 | return _config; 31 | } 32 | 33 | /// 34 | /// Gets and caches a user access token. App setting "SpotifyUserRefreshToken" must contain a valid 35 | /// user refresh token for this to work. Does not refesh token when expired. 36 | /// 37 | internal static async Task GetUserAccessToken() 38 | { 39 | // not thread safe (last one wins) 40 | if (_accessToken == null) 41 | { 42 | var tokens = await _accounts.RefreshUserAccessToken(GetLocalConfig()["SpotifyUserRefreshToken"]); 43 | _accessToken = tokens.AccessToken; 44 | } 45 | 46 | return _accessToken; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/UsersProfileApiTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using SpotifyApi.NetCore.Authorization; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace SpotifyApi.NetCore.Tests 7 | { 8 | [TestClass] 9 | public class UsersProfileApiTests 10 | { 11 | [TestMethod] 12 | [TestCategory("Integration")] 13 | [TestCategory("User")] 14 | public async Task GetUsersProfile_NoUserId_DeserializedResponse() 15 | { 16 | // arrange 17 | var config = TestsHelper.GetLocalConfig(); 18 | var accessToken = config["SpotifyUserBearerAccessToken"]; 19 | var http = new HttpClient(); 20 | var accounts = new UserAccountsService(http, config); 21 | 22 | var api = new UsersProfileApi(http, accounts); 23 | 24 | // act 25 | // must use a User Access Token for this call 26 | var response = await api.GetCurrentUsersProfile(accessToken: accessToken); 27 | 28 | Assert.IsNotNull(response); 29 | } 30 | 31 | [TestMethod] 32 | [TestCategory("Integration")] 33 | public async Task GetUsersProfile_UserId_IdEqualsUserId() 34 | { 35 | // arrange 36 | var http = new HttpClient(); 37 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 38 | const string userId = "daniellarsennz"; 39 | 40 | var api = new UsersProfileApi(http, accounts); 41 | 42 | // act 43 | var response = await api.GetUsersProfile(userId); 44 | 45 | Assert.AreEqual(userId.ToLower(), response.Id.ToLower()); 46 | } 47 | 48 | [TestMethod] 49 | [TestCategory("Integration")] 50 | public async Task GetUsersProfile_UserId_EmailIsNull() 51 | { 52 | // arrange 53 | var http = new HttpClient(); 54 | var accounts = new AccountsService(http, TestsHelper.GetLocalConfig()); 55 | const string userId = "daniellarsennz"; 56 | 57 | var api = new UsersProfileApi(http, accounts); 58 | 59 | // act 60 | var response = await api.GetUsersProfile(userId); 61 | 62 | Assert.IsNull(response.Email); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.Tests/appsettings-template.json: -------------------------------------------------------------------------------- 1 | { 2 | "SpotifyApiClientId": "", 3 | "SpotifyApiClientSecret": "", 4 | "SpotifyAuthRedirectUri": "http://localhost:3978/authorize/spotify", 5 | "SpotifyAuthRefreshToken": "", 6 | "SpotifyUserBearerAccessToken": "" 7 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29009.5 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpotifyApi.NetCore", "SpotifyApi.NetCore\SpotifyApi.NetCore.csproj", "{D835F618-4B23-4DAD-842D-57D308499BA8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpotifyApi.NetCore.Tests", "SpotifyApi.NetCore.Tests\SpotifyApi.NetCore.Tests.csproj", "{86644469-0957-4363-A185-32E6ADEB0C3E}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D835F618-4B23-4DAD-842D-57D308499BA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D835F618-4B23-4DAD-842D-57D308499BA8}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D835F618-4B23-4DAD-842D-57D308499BA8}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D835F618-4B23-4DAD-842D-57D308499BA8}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {86644469-0957-4363-A185-32E6ADEB0C3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {86644469-0957-4363-A185-32E6ADEB0C3E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {86644469-0957-4363-A185-32E6ADEB0C3E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {86644469-0957-4363-A185-32E6ADEB0C3E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {45AA36B6-8CC8-463C-9277-2D504217A6D6} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet build", 9 | "type": "shell", 10 | "group": "build", 11 | "presentation": { 12 | "reveal": "always" 13 | }, 14 | "problemMatcher": "$msCompile" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly:InternalsVisibleTo("SpotifyApi.NetCore.Tests")] -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Authorization/AccountsService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Newtonsoft.Json; 3 | using SpotifyApi.NetCore.Http; 4 | using System; 5 | using System.Net.Http; 6 | using System.Net.Http.Headers; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SpotifyApi.NetCore.Authorization 11 | { 12 | /// 13 | /// Provides Spotify Accounts Service functionality as described in 14 | /// 15 | public class AccountsService : IAccountsService 16 | { 17 | protected const string TokenUrl = "https://accounts.spotify.com/api/token"; 18 | 19 | protected readonly HttpClient _http; 20 | protected readonly IConfiguration _config; 21 | protected readonly IBearerTokenStore _bearerTokenStore; 22 | 23 | #region constructors 24 | 25 | /// 26 | /// Instantiates an AccountsService class. 27 | /// 28 | /// An instance of for making HTTP calls to the Spotify Accounts Service. 29 | /// An instance of for providing Configuration. 30 | /// An instance of for storing cached Access (Bearer) tokens. 31 | public AccountsService(HttpClient httpClient, IConfiguration configuration, IBearerTokenStore bearerTokenStore) 32 | { 33 | if (httpClient == null) throw new ArgumentNullException("httpClient"); 34 | _http = httpClient; 35 | 36 | // if configuration is not provided, read from environment variables 37 | _config = configuration ?? new ConfigurationBuilder() 38 | .AddEnvironmentVariables() 39 | .Build(); 40 | 41 | ValidateConfig(); 42 | 43 | _bearerTokenStore = bearerTokenStore ?? new MemoryBearerTokenStore(); 44 | } 45 | 46 | /// 47 | /// Instantiates an AccountsService class. 48 | /// 49 | public AccountsService() : this(new HttpClient(), null, null) { } 50 | 51 | /// 52 | /// Instantiates an AccountsService class. 53 | /// 54 | /// An instance of for making HTTP calls to the Spotify Accounts Service. 55 | public AccountsService(HttpClient httpClient) : this(httpClient, null, null) { } 56 | 57 | /// 58 | /// Instantiates an AccountsService class. 59 | /// 60 | /// An instance of for making HTTP calls to the Spotify Accounts Service. 61 | /// An instance of for providing Configuration. 62 | public AccountsService(HttpClient httpClient, IConfiguration configuration) : this(httpClient, configuration, null) { } 63 | 64 | #endregion 65 | 66 | /// 67 | /// Returns a valid access token for the Spotify Service. 68 | /// 69 | /// The token as string. 70 | public async Task GetAccessToken() 71 | { 72 | var token = await GetAccessToken(_config["SpotifyApiClientId"], "grant_type=client_credentials"); 73 | return token.AccessToken; 74 | } 75 | 76 | protected async Task GetAccessToken(string tokenKey, string body) 77 | { 78 | var token = await _bearerTokenStore.Get(tokenKey); 79 | 80 | // if token current, return it 81 | var now = DateTime.UtcNow; 82 | if (token != null && token.Expires != null && token.Expires > now) return token; 83 | 84 | string json = await _http.Post(new Uri(TokenUrl), body, GetHeader(_config)); 85 | 86 | // deserialise the token 87 | var newToken = JsonConvert.DeserializeObject(json); 88 | // set absolute expiry 89 | newToken.SetExpires(now); 90 | 91 | // add to store 92 | newToken.EnforceInvariants(); 93 | await _bearerTokenStore.InsertOrReplace(tokenKey, newToken); 94 | return newToken; 95 | } 96 | 97 | protected async Task RefreshAccessToken(string body) 98 | { 99 | var now = DateTime.UtcNow; 100 | string json = await _http.Post(new Uri(TokenUrl), body, GetHeader(_config)); 101 | // deserialise the token 102 | var newToken = JsonConvert.DeserializeObject(json); 103 | // set absolute expiry 104 | newToken.SetExpires(now); 105 | return newToken; 106 | } 107 | 108 | protected static AuthenticationHeaderValue GetHeader(IConfiguration configuration) 109 | { 110 | return new AuthenticationHeaderValue("Basic", 111 | Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", 112 | configuration["SpotifyApiClientId"], configuration["SpotifyApiClientSecret"]))) 113 | ); 114 | } 115 | 116 | private void ValidateConfig() 117 | { 118 | if (string.IsNullOrEmpty(_config["SpotifyApiClientId"])) 119 | throw new ArgumentNullException("SpotifyApiClientId", "Expecting configuration value for `SpotifyApiClientId`"); 120 | if (string.IsNullOrEmpty(_config["SpotifyApiClientSecret"])) 121 | throw new ArgumentNullException("SpotifyApiClientSecret", "Expecting configuration value for `SpotifyApiClientSecret`"); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Authorization/BearerAccessRefreshToken.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace SpotifyApi.NetCore.Authorization 5 | { 6 | /// 7 | /// A Bearer plus Refresh Token DTO. 8 | /// 9 | public class BearerAccessRefreshToken : BearerAccessToken 10 | { 11 | /* 12 | { 13 | "access_token": "NgCXRK...MzYjw", 14 | "token_type": "Bearer", 15 | "scope": "user-read-private user-read-email", 16 | "expires_in": 3600, 17 | "refresh_token": "NgAagA...Um_SHo" 18 | } 19 | */ 20 | 21 | /// 22 | /// A Refresh token. 23 | /// 24 | [JsonProperty("refresh_token")] 25 | public string RefreshToken { get; set; } 26 | } 27 | 28 | /// 29 | /// A Bearer (Access) token DTO. 30 | /// 31 | public class BearerAccessToken 32 | { 33 | /* 34 | { 35 | "access_token": "NgCXRKc...MzYjw", 36 | "token_type": "bearer", 37 | "expires_in": 3600, 38 | } 39 | */ 40 | 41 | /// 42 | /// Access (bearer) token. 43 | /// 44 | [JsonProperty("access_token")] 45 | public string AccessToken { get; set; } 46 | 47 | /// 48 | /// Number of seconds after issue that the token expres. 49 | /// 50 | [JsonProperty("expires_in")] 51 | public int ExpiresIn { get; set; } 52 | 53 | /// 54 | /// List of scopes that the token is valid for. Can be null. 55 | /// 56 | [JsonProperty("scope")] 57 | public string Scope { get; set; } 58 | 59 | /// 60 | /// The approximate Date and Time that the token expires. 61 | /// 62 | public DateTime? Expires { get; internal set; } 63 | } 64 | 65 | /// 66 | /// Static extensions for and . 67 | /// 68 | public static class BearerAccessTokenExtensions 69 | { 70 | /// 71 | /// Derive and set the Expires property on a . 72 | /// 73 | /// A instance of . 74 | /// Approximate date and time that the token was issued. 75 | /// The derived Expires value. 76 | public static DateTime SetExpires(this BearerAccessToken token, DateTime issuedDateTime) 77 | { 78 | token.Expires = issuedDateTime.ToUniversalTime().AddSeconds(token.ExpiresIn); 79 | return token.Expires.Value; 80 | } 81 | 82 | /// 83 | /// Enforces invariants on a . 84 | /// 85 | /// An instance of 86 | public static void EnforceInvariants(this BearerAccessToken token) 87 | { 88 | if (token.Expires == null) throw new InvalidOperationException("Expires must not be null. Call SetExpires() to set"); 89 | } 90 | 91 | /// 92 | /// Enforces invariants on a . 93 | /// 94 | /// An instance of 95 | public static void EnforceInvariants(this BearerAccessRefreshToken token) 96 | { 97 | if (token.RefreshToken == null) throw new InvalidOperationException("RefreshToken must not be null."); 98 | // enforce the Base invariants (am I doing this right?) 99 | ((BearerAccessToken)token).EnforceInvariants(); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Authorization/IAccessTokenProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SpotifyApi.NetCore.Authorization 5 | { 6 | /// 7 | /// Defines a provider of Bearer (Access) Tokens for the Spotify Service 8 | /// 9 | public interface IAccessTokenProvider 10 | { 11 | /// 12 | /// Returns a valid access token for the Spotify Service 13 | /// 14 | /// 15 | Task GetAccessToken(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Authorization/IAccountsService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace SpotifyApi.NetCore.Authorization 5 | { 6 | /// 7 | /// Defines a Spotify Accounts Service for the User (Authorization Code) Flow. 8 | /// 9 | /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow 10 | public interface IUserAccountsService : IAccountsService 11 | { 12 | /// 13 | /// Derives and returns a URL for a webpage where a user can choose to grant your app access to their data. 14 | /// 15 | /// Optional, but strongly recommended. Random state value to provides protection against 16 | /// attacks such as cross-site request forgery. See important notes in 17 | /// Optional. A space-separated list of scopes. 18 | /// A fully qualified URL. 19 | string AuthorizeUrl(string state, string[] scopes); 20 | 21 | /// 22 | /// Exchange the authorization code returned by the `/authorize` endpoint for a . 23 | /// 24 | /// The authorization code returned from the initial request to the Account /authorize endpoint. 25 | /// An instance of 26 | Task RequestAccessRefreshToken(string code); 27 | 28 | /// 29 | /// Refresh a Bearer (Access) token when it has expired / is about to expire. 30 | /// 31 | /// The refresh token returned from the authorization code exchange. 32 | /// An instance of . 33 | Task RefreshUserAccessToken(string refreshToken); 34 | } 35 | 36 | /// 37 | /// Defines a Spotify Accounts Service for the Client Credentials (App) Flow. 38 | /// 39 | /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow 40 | public interface IAccountsService : IAccessTokenProvider 41 | { 42 | } 43 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Authorization/IBearerTokenStore.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SpotifyApi.NetCore.Authorization 4 | { 5 | /// 6 | /// Defines a Bearer Token cache store for use with . 7 | /// 8 | public interface IBearerTokenStore 9 | { 10 | /// 11 | /// Inserts or replaces a . 12 | /// 13 | /// The identifier key for the token. 14 | /// The token as . 15 | Task InsertOrReplace(string key, BearerAccessToken token); 16 | 17 | /// 18 | /// Get a by its key. 19 | /// 20 | Task Get(string key); 21 | } 22 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Authorization/MemoryBearerTokenStore.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Threading.Tasks; 3 | 4 | namespace SpotifyApi.NetCore.Authorization 5 | { 6 | // https://docs.microsoft.com/en-nz/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=netframework-4.7.1#remarks 7 | 8 | /// 9 | /// An internal, in-memory Bearer Token cache store for use with . 10 | /// 11 | internal class MemoryBearerTokenStore : IBearerTokenStore 12 | { 13 | private readonly ConcurrentDictionary _store = new ConcurrentDictionary(); 14 | 15 | /// 16 | /// Inserts or replaces a . 17 | /// 18 | /// The identifier key for the token. 19 | /// The token as . 20 | public Task InsertOrReplace(string key, BearerAccessToken token) 21 | { 22 | _store[key] = token; 23 | return Task.CompletedTask; 24 | } 25 | 26 | /// 27 | /// Get a by its key. 28 | /// 29 | public Task Get(string key) 30 | { 31 | _store.TryGetValue(key, out BearerAccessToken value); 32 | return Task.FromResult(value); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Authorization/UserAccountsService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Newtonsoft.Json; 3 | using SpotifyApi.NetCore.Http; 4 | using System; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | 8 | namespace SpotifyApi.NetCore.Authorization 9 | { 10 | /// 11 | /// Spotify Accounts Service for the User (Authorization Code) Flow. 12 | /// 13 | /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow 14 | public class UserAccountsService : AccountsService, IUserAccountsService 15 | { 16 | private const string AccountsAuthorizeUrl = "https://accounts.spotify.com/authorize"; 17 | 18 | #region constructors 19 | 20 | /// 21 | /// Instantiate a new . 22 | /// 23 | /// An instance of . 24 | /// An instance of . 25 | public UserAccountsService(HttpClient httpClient, IConfiguration configuration) 26 | : base(httpClient, configuration) 27 | { 28 | ValidateConfig(); 29 | } 30 | 31 | /// 32 | /// Instantiate a new . 33 | /// 34 | /// An instance of . 35 | public UserAccountsService(IConfiguration configuration) 36 | : base(new HttpClient(), configuration) 37 | { 38 | ValidateConfig(); 39 | } 40 | 41 | #endregion 42 | 43 | /// 44 | /// Refresh a Bearer (Access) token when it has expired / is about to expire. 45 | /// 46 | /// The refresh token returned from the authorization code exchange. 47 | /// An instance of . 48 | public async Task RefreshUserAccessToken(string refreshToken) 49 | { 50 | if (string.IsNullOrEmpty(refreshToken)) throw new ArgumentNullException(nameof(refreshToken)); 51 | 52 | return await RefreshAccessToken( 53 | $"grant_type=refresh_token&refresh_token={refreshToken}&redirect_uri={_config["SpotifyAuthRedirectUri"]}"); 54 | } 55 | 56 | /// 57 | /// Derives and returns a URL for a webpage where a user can choose to grant your app access to their data. 58 | /// 59 | /// Optional, but strongly recommended. Random state value to provides protection against 60 | /// attacks such as cross-site request forgery. See important notes in 61 | /// Optional. A space-separated list of scopes. 62 | /// A fully qualified URL. 63 | public string AuthorizeUrl(string state, string[] scopes) 64 | { 65 | return AuthorizeUrl(state, scopes, _config["SpotifyApiClientId"], _config["SpotifyAuthRedirectUri"]); 66 | } 67 | 68 | /// 69 | /// Derives and returns a URL for a webpage where a user can choose to grant your app access to their data. 70 | /// 71 | /// Optional, but strongly recommended. Random state value to provides protection against 72 | /// attacks such as cross-site request forgery. See important notes in 73 | /// Optional. A space-separated list of scopes. 74 | /// A valid Spotify API Client Id. 75 | /// A valid Spotify Auth Redirect URI. 76 | /// A fully qualified URL. 77 | public static string AuthorizeUrl(string state, string[] scopes, string spotifyApiClientId, string spotifyAuthRedirectUri) 78 | { 79 | if (string.IsNullOrEmpty(spotifyApiClientId)) throw new ArgumentNullException(nameof(spotifyApiClientId)); 80 | if (string.IsNullOrEmpty(spotifyAuthRedirectUri)) throw new ArgumentNullException(nameof(spotifyAuthRedirectUri)); 81 | 82 | string scope = scopes == null || scopes.Length == 0 ? "" : string.Join("%20", scopes); 83 | return $"{AccountsAuthorizeUrl}/?client_id={spotifyApiClientId}&response_type=code&redirect_uri={spotifyAuthRedirectUri}&scope={scope}&state={state}"; 84 | } 85 | 86 | /// 87 | /// Exchange the authorization code returned by the `/authorize` endpoint for a . 88 | /// 89 | /// The authorization code returned from the initial request to the Account /authorize endpoint. 90 | /// An instance of 91 | public async Task RequestAccessRefreshToken(string code) 92 | { 93 | var now = DateTime.UtcNow; 94 | // POST the code to get the tokens 95 | var token = await GetAuthorizationTokens(code); 96 | // set absolute expiry 97 | token.SetExpires(now); 98 | token.EnforceInvariants(); 99 | return token; 100 | } 101 | 102 | protected internal virtual async Task GetAuthorizationTokens(string code) 103 | { 104 | var result = await _http.Post(new Uri(TokenUrl), 105 | $"grant_type=authorization_code&code={code}&redirect_uri={_config["SpotifyAuthRedirectUri"]}", 106 | GetHeader(_config)); 107 | return JsonConvert.DeserializeObject(result); 108 | } 109 | 110 | private void ValidateConfig() 111 | { 112 | if (string.IsNullOrEmpty(_config["SpotifyAuthRedirectUri"])) 113 | throw new ArgumentNullException("SpotifyAuthRedirectUri", "Expecting configuration value for `SpotifyAuthRedirectUri`"); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/BrowseApi.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ringobot/SpotifyApi.NetCore/87cdccd2903d71514e2a7c54a7165437d9a0e197/src/SpotifyApi.NetCore/BrowseApi.cs -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/EpisodesApi.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Authorization; 2 | using SpotifyApi.NetCore.Models; 3 | using System; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | 7 | namespace SpotifyApi.NetCore 8 | { 9 | /// 10 | /// An implementation of the API Wrapper for the Spotify Web API Episodes endpoints. 11 | /// 12 | public class EpisodesApi : SpotifyWebApi, IEpisodesApi 13 | { 14 | #region constructors 15 | public EpisodesApi(HttpClient httpClient, IAccessTokenProvider accessTokenProvider) : base(httpClient, accessTokenProvider) 16 | { 17 | } 18 | 19 | public EpisodesApi(HttpClient httpClient, string accessToken) : base(httpClient, accessToken) 20 | { 21 | } 22 | 23 | public EpisodesApi(HttpClient httpClient) : base(httpClient) 24 | { 25 | } 26 | #endregion 27 | 28 | #region GetEpisode 29 | /// 30 | /// Get Spotify catalog information for a single episode identified by its unique Spotify ID. 31 | /// 32 | /// The Spotify ID for the episode. 33 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 34 | /// A Task that, once successfully completed, returns a full object. 35 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-an-episode/ 36 | public Task GetEpisode(string episodeId, string market = null, string accessToken = null) 37 | => GetEpisode(episodeId, market, accessToken: accessToken); 38 | 39 | /// 40 | /// 41 | /// Get Spotify catalog information for a single episode identified by its unique Spotify ID. 42 | /// 43 | /// The Spotify ID for the episode. 44 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 45 | /// A Task that, once successfully completed, returns a full object. 46 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-an-episode/ 47 | public async Task GetEpisode(string episodeId, string market = null, string accessToken = null) 48 | { 49 | var builder = new UriBuilder($"{BaseUrl}/episodes/{episodeId}"); 50 | builder.AppendToQueryIfValueNotNullOrWhiteSpace("market", market); 51 | return await GetModel(builder.Uri, accessToken); 52 | } 53 | #endregion 54 | 55 | #region GetSeveralEpisodes 56 | /// 57 | /// Get Spotify catalog information for multiple episodes based on their Spotify IDs. 58 | /// 59 | /// Required. A comma-separated list of the episode Spotify IDs. A maximum of 50 episode IDs can be sent in one request. A minimum of 1 user id is required. 60 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 61 | /// A Task that, once successfully completed, returns a full object. 62 | /// 63 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-several-episodes/ 64 | /// 65 | public Task GetSeveralEpisodes( 66 | string[] episodeIds, 67 | string market = null, 68 | string accessToken = null 69 | ) => GetSeveralEpisodes(episodeIds, market, accessToken); 70 | 71 | /// 72 | /// Get Spotify catalog information for multiple episodes based on their Spotify IDs. 73 | /// 74 | /// Required. A comma-separated list of the episode Spotify IDs. A maximum of 50 episode IDs can be sent in one request. A minimum of 1 user id is required. 75 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 76 | /// A Task that, once successfully completed, returns a full object. 77 | /// 78 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-several-episodes/ 79 | /// 80 | public async Task GetSeveralEpisodes( 81 | string[] episodeIds, 82 | string market = null, 83 | string accessToken = null 84 | ) 85 | { 86 | if (episodeIds?.Length < 1 || episodeIds?.Length > 50) throw new 87 | ArgumentException("A minimum of 1 and a maximum of 50 episode ids can be sent."); 88 | 89 | var builder = new UriBuilder($"{BaseUrl}/episodes"); 90 | builder.AppendToQueryAsCsv("ids", episodeIds); 91 | builder.AppendToQueryIfValueNotNullOrWhiteSpace("market", market); 92 | return await GetModel(builder.Uri, accessToken); 93 | } 94 | #endregion 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Extensions/TracksApiExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace SpotifyApi.NetCore.Extensions 6 | { 7 | /// 8 | /// Helper extension methods for 9 | /// 10 | public static class TracksApiExtensions 11 | { 12 | /// 13 | /// Get a single track by its ISRC code. 14 | /// 15 | /// This instance of . 16 | /// A valid 12 digit ISRC code. 17 | /// 18 | public static async Task GetTrackByIsrcCode(this ITracksApi tracksApi, string isrc) 19 | { 20 | if (isrc == null || isrc.Length != 12) throw new ArgumentException("12 digit ISRC code expected."); 21 | return (await tracksApi.SearchTracks($"isrc:{isrc}", limit: 1))?.Items.FirstOrDefault(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Extensions/UriBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | internal static class UriBuilderExtensions 7 | { 8 | public static void AppendToQuery(this UriBuilder builder, string name, int value) 9 | => AppendToQuery(builder, name, value.ToString()); 10 | 11 | public static void AppendToQuery(this UriBuilder builder, string name, long value) 12 | => AppendToQuery(builder, name, value.ToString()); 13 | 14 | public static void AppendToQuery(this UriBuilder builder, string name, string value) 15 | { 16 | if (string.IsNullOrEmpty(builder.Query)) builder.Query = $"{name}={value}"; 17 | else builder.Query = $"{builder.Query.Substring(1)}&{name}={value}"; 18 | } 19 | 20 | public static void AppendToQueryAsCsv(this UriBuilder builder, string name, string[] values) 21 | { 22 | if (values != null && values.Length > 0) 23 | AppendToQuery(builder, name, string.Join(",", values)); 24 | } 25 | 26 | public static void AppendToQueryIfValueGreaterThan0( 27 | this UriBuilder builder, 28 | string name, 29 | long? value) 30 | { 31 | if (value.HasValue) AppendToQueryIfValueGreaterThan0(builder, name, value.Value); 32 | } 33 | 34 | public static void AppendToQueryIfValueGreaterThan0( 35 | this UriBuilder builder, 36 | string name, 37 | long value) 38 | { 39 | if (value > 0) AppendToQuery(builder, name, value); 40 | } 41 | 42 | public static void AppendToQueryIfValueGreaterThan0( 43 | this UriBuilder builder, 44 | string name, 45 | int? value) 46 | { 47 | if (value.HasValue) AppendToQueryIfValueGreaterThan0(builder, name, value.Value); 48 | } 49 | 50 | public static void AppendToQueryIfValueGreaterThan0( 51 | this UriBuilder builder, 52 | string name, 53 | int value) 54 | { 55 | if (value > 0) AppendToQuery(builder, name, value); 56 | } 57 | 58 | public static void AppendToQueryIfValueNotNullOrWhiteSpace( 59 | this UriBuilder builder, 60 | string name, 61 | string value) 62 | { 63 | if (!string.IsNullOrWhiteSpace(value)) AppendToQuery(builder, name, value); 64 | } 65 | 66 | public static void AppendToQueryAsTimestampIso8601(this UriBuilder builder, string name, DateTime? timestamp) 67 | { 68 | if (timestamp.HasValue) AppendToQuery(builder, name, timestamp.Value.ToString("s", CultureInfo.InvariantCulture)); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Helpers/SpotifyUriHelper.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Models; 2 | using System; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace SpotifyApi.NetCore.Helpers 6 | { 7 | /// 8 | /// Helper for Spotify URI's and Id's 9 | /// 10 | /// https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids 11 | public static class SpotifyUriHelper 12 | { 13 | public static readonly Regex SpotifyUriRegEx = SpotifyUri.SpotifyIdRegEx; 14 | public static readonly Regex SpotifyUserPlaylistUriRegEx = SpotifyUri.SpotifyUserPlaylistUriRegEx; 15 | public static readonly Regex SpotifyIdRegEx = SpotifyUri.SpotifyIdRegEx; 16 | 17 | /// 18 | /// Converts a Spotify Track Id or URI into a Spotify URI 19 | /// 20 | public static string TrackUri(string trackId) => ToUri("track", trackId); 21 | 22 | /// 23 | /// Converts a Spotify Album Id or URI into a Spotify URI 24 | /// 25 | public static string AlbumUri(string albumId) => ToUri("album", albumId); 26 | 27 | /// 28 | /// Converts a Spotify Artist Id or URI into a Spotify URI 29 | /// 30 | public static string ArtistUri(string artistId) => ToUri("artist", artistId); 31 | 32 | /// 33 | /// Converts a Spotify Playlist Id or URI into a Spotify URI 34 | /// 35 | public static string PlaylistUri(string playlistId) => ToUri("playlist", playlistId); 36 | 37 | /// 38 | /// Converts a Spotify Track Id or URI into a Spotify Id 39 | /// 40 | public static string TrackId(string trackId) => ToId("track", trackId); 41 | 42 | /// 43 | /// Converts a Spotify Album Id or URI into a Spotify Id 44 | /// 45 | public static string AlbumId(string albumId) => ToId("album", albumId); 46 | 47 | /// 48 | /// Converts a Spotify Artist Id or URI into a Spotify Id 49 | /// 50 | public static string ArtistId(string artistId) => ToId("artist", artistId); 51 | 52 | /// 53 | /// Converts a Spotify Playlist Id or URI into a Spotify Id 54 | /// 55 | public static string PlaylistId(string playlistId) => ToId("playlist", playlistId); 56 | 57 | private static string ToUri(string type, string id) 58 | { 59 | var uri = new SpotifyUri(id, type); 60 | if (!uri.IsValid) throw new ArgumentException($"\"{id}\" is not a valid Spotify identifier."); 61 | return uri.FullUri; 62 | } 63 | 64 | /// 65 | /// Converts any valid spotify URI to the standard Spotify URI format, i.e. spotify:(type):(id) 66 | /// 67 | /// Any Spotify URI 68 | /// The normalized Spotify URI 69 | public static string NormalizeUri(string uri) 70 | { 71 | var spotifyUri = new SpotifyUri(uri); 72 | if (!spotifyUri.IsValid || !spotifyUri.IsSpotifyUri) 73 | throw new ArgumentException($"\"{uri}\" is not a valid Spotify URI."); 74 | return spotifyUri.Uri; 75 | } 76 | 77 | internal static string ToId(string type, string idOrUri, bool throwIfNotValid = true) 78 | { 79 | var uri = new SpotifyUri(idOrUri, type); 80 | if (throwIfNotValid && !uri.IsValid) 81 | throw new ArgumentException($"\"{idOrUri}\" is not a valid Spotify {type} identifier"); 82 | 83 | return uri.Id; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Helpers/TimeRangeHelper.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Models; 2 | 3 | namespace SpotifyApi.NetCore.Helpers 4 | { 5 | /// 6 | /// Helper for TimeRange 7 | /// 8 | /// https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids 9 | public static class TimeRangeHelper 10 | { 11 | /// 12 | /// Return the string value for the time range. 13 | /// 14 | /// Required. The enum time range to be resolved to the string value. 15 | /// string Over what time frame the affinities are computed. Valid values: long_term (calculated from several years of data and including all new data as it becomes available), medium_term (approximately last 6 months), short_term (approximately last 4 weeks). Default: medium_term. 16 | internal static string TimeRangeString(TimeRange timeRange) => timeRange == TimeRange.ShortTerm ? "short_term" : 17 | (timeRange == TimeRange.LongTerm ? "long_term" : "medium_term"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Http/RestHttpClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Net.Http.Headers; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SpotifyApi.NetCore.Http 8 | { 9 | /// 10 | /// Static helper extensions on HttpClient for communicating with the Spotify Web API. 11 | /// 12 | /// See https://developer.spotify.com/web-api/ 13 | internal static class RestHttpClient 14 | { 15 | /// 16 | /// Makes an HTTP(S) GET request to and returns the result as (awaitable Task of) . 17 | /// 18 | /// The entire request URL as . 19 | /// An (awaitable Task of) 20 | /// Will Authorise using the values of the SpotifyApiClientId and SpotifyApiClientSecret appSettings. See 21 | /// https://developer.spotify.com/web-api/authorization-guide/#client-credentials-flow 22 | public static async Task Get(this HttpClient http, Uri requestUri) 23 | { 24 | return await Get(http, requestUri, null); 25 | } 26 | 27 | /// 28 | /// Makes an HTTP(S) GET request to and returns the result as (awaitable Task of) . 29 | /// 30 | /// The entire request URL as . 31 | /// 32 | /// An (awaitable Task of) 33 | /// Will Authorise using the values of the SpotifyApiClientId and SpotifyApiClientSecret appSettings. See 34 | /// https://developer.spotify.com/web-api/authorization-guide/#client-credentials-flow 35 | public static async Task Get( 36 | this HttpClient http, 37 | Uri requestUri, 38 | AuthenticationHeaderValue authenticationHeader) 39 | { 40 | //TODO: Implement if-modified-since support, serving from cache if response = 304 41 | 42 | if (requestUri == null) throw new ArgumentNullException(nameof(requestUri)); 43 | 44 | Logger.Debug( 45 | $"GET {requestUri}. Token = {authenticationHeader?.ToString()?.Substring(0, 11)}...", 46 | nameof(RestHttpClient)); 47 | 48 | http.DefaultRequestHeaders.Authorization = authenticationHeader; 49 | var response = await http.GetAsync(requestUri); 50 | 51 | Logger.Information($"Got {requestUri} {response.StatusCode}", nameof(RestHttpClient)); 52 | 53 | await CheckForErrors(response); 54 | 55 | return await response.Content.ReadAsStringAsync(); 56 | } 57 | 58 | /// 59 | /// Makes an HTTP(S) POST request to with as the request body. Returns the result as (awaitable Task of) . 60 | /// 61 | /// The entire request URL as . 62 | /// The URL encoded formData in format "key1=value1&key2=value2" 63 | /// An (awaitable Task of) 64 | /// Will Authorise using the values of the SpotifyApiClientId and SpotifyApiClientSecret appSettings. See 65 | /// https://developer.spotify.com/web-api/authorization-guide/#client-credentials-flow 66 | public static async Task Post(this HttpClient http, Uri requestUri, string formData) 67 | { 68 | return await Post(http, requestUri, formData, null); 69 | } 70 | 71 | /// 72 | /// Makes an HTTP(S) POST request to with as the request body. Returns the result as (awaitable Task of) . 73 | /// 74 | /// The entire request URI as . 75 | /// The URL encoded formData in format "key1=value1&key2=value2" 76 | /// An authentication header for the request. 77 | /// An (awaitable Task of) 78 | /// Will Authorise using the values of the SpotifyApiClientId and SpotifyApiClientSecret appSettings. See 79 | /// https://developer.spotify.com/web-api/authorization-guide/#client-credentials-flow 80 | public static async Task Post( 81 | this HttpClient http, 82 | Uri requestUri, 83 | string formData, 84 | AuthenticationHeaderValue headerValue) 85 | { 86 | if (requestUri == null) throw new ArgumentNullException(nameof(requestUri)); 87 | if (string.IsNullOrEmpty(formData)) throw new ArgumentNullException(formData); 88 | 89 | Logger.Debug( 90 | $"POST {requestUri}. Token = {headerValue?.ToString()?.Substring(0, 11)}...", 91 | nameof(RestHttpClient)); 92 | 93 | http.DefaultRequestHeaders.Authorization = headerValue; 94 | var response = 95 | await 96 | http.PostAsync(requestUri, 97 | new StringContent(formData, Encoding.UTF8, "application/x-www-form-urlencoded")); 98 | 99 | Logger.Information($"Posted {requestUri} {response.StatusCode}", nameof(RestHttpClient)); 100 | 101 | await CheckForErrors(response); 102 | 103 | return await response.Content.ReadAsStringAsync(); 104 | } 105 | 106 | /// 107 | /// Checks the reponse from the Spotify Server for an error. 108 | /// 109 | /// 110 | /// If a Spotify API Error message is parsed, a is thrown. 111 | /// If any other error is returned a if thrown. If no error 112 | /// the method returns void. 113 | public static async Task CheckForErrors(HttpResponseMessage response) 114 | { 115 | if (!response.IsSuccessStatusCode) 116 | { 117 | var error = await SpotifyApiErrorException.ReadErrorResponse(response); 118 | if (error != null) throw new SpotifyApiErrorException(response.StatusCode, error); 119 | response.EnsureSuccessStatusCode(); // not a Spotify API Error so throw HttpResponseMessageException 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/IBrowseApi.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ringobot/SpotifyApi.NetCore/87cdccd2903d71514e2a7c54a7165437d9a0e197/src/SpotifyApi.NetCore/IBrowseApi.cs -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/IEpisodesApi.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Models; 2 | using System.Threading.Tasks; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | /// 7 | /// Defines an interface for a wrapper for the Spotify Web Episodes API. 8 | /// 9 | public interface IEpisodesApi 10 | { 11 | #region GetEpisode 12 | /// 13 | /// Get Spotify catalog information for a single episode identified by its unique Spotify ID. 14 | /// 15 | /// The Spotify ID for the episode. 16 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 17 | /// A Task that, once successfully completed, returns a full object. 18 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-an-episode/ 19 | Task GetEpisode(string episodeId, string market = null, string accessToken = null); 20 | 21 | /// 22 | /// 23 | /// Get Spotify catalog information for a single episode identified by its unique Spotify ID. 24 | /// 25 | /// The Spotify ID for the episode. 26 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 27 | /// A Task that, once successfully completed, returns a full object. 28 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-an-episode/ 29 | Task GetEpisode(string episodeId, string market = null, string accessToken = null); 30 | #endregion 31 | 32 | #region GetSeveralEpisodes 33 | /// 34 | /// Get Spotify catalog information for multiple episodes based on their Spotify IDs. 35 | /// 36 | /// Required. A comma-separated list of the episode Spotify IDs. A maximum of 50 episode IDs can be sent in one request. A minimum of 1 user id is required. 37 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 38 | /// A Task that, once successfully completed, returns a full object. 39 | /// 40 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-several-episodes/ 41 | /// 42 | Task GetSeveralEpisodes( 43 | string[] episodeIds, 44 | string market = null, 45 | string accessToken = null 46 | ); 47 | 48 | /// 49 | /// Get Spotify catalog information for multiple episodes based on their Spotify IDs. 50 | /// 51 | /// Required. A comma-separated list of the episode Spotify IDs. A maximum of 50 episode IDs can be sent in one request. A minimum of 1 user id is required. 52 | /// Optional. An ISO 3166-1 alpha-2 country code or the string from_token . If a country code is specified, only shows and episodes that are available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. Note: If neither market or user country are provided, the content is considered unavailable for the client. Users can view the country that is associated with their account in the account settings. 53 | /// A Task that, once successfully completed, returns a full object. 54 | /// 55 | /// https://developer.spotify.com/documentation/web-api/reference/episodes/get-several-episodes/ 56 | /// 57 | Task GetSeveralEpisodes( 58 | string[] episodeIds, 59 | string market = null, 60 | string accessToken = null 61 | ); 62 | #endregion 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/IPersonalizationApi.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Models; 2 | using System.Threading.Tasks; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | /// 7 | /// Defines a wrapper for the Spotify Web Personalization API. 8 | /// 9 | public interface IPersonalizationApi 10 | { 11 | #region GetUsersTopArtistsOrTracks 12 | 13 | /// 14 | /// Get the current user’s top artists based on calculated affinity. 15 | /// 16 | /// Optional. The maximum number of entities to return. Default: 20. Minimum: 1. Maximum: 50. 17 | /// Optional. The index of the first object to return. Default: 0 (i.e., the first object). Use with limit to get the next set of objects. 18 | /// Optional. Over what time frame the affinities are computed. Valid values: long_term (calculated from several years of data and including all new data as it becomes available), medium_term (approximately last 6 months), short_term (approximately last 4 weeks). Default: medium_term. 19 | /// A Task that, once successfully completed, returns a full object. 20 | /// 21 | /// https://developer.spotify.com/documentation/web-api/reference/personalization/get-users-top-artists-and-tracks/ 22 | /// 23 | Task GetUsersTopArtists( 24 | int limit = 20, 25 | int offset = 0, 26 | TimeRange timeRange = TimeRange.MediumTerm, 27 | string accessToken = null 28 | ); 29 | 30 | /// 31 | /// Get the current user’s top artists based on calculated affinity. 32 | /// 33 | /// Optional. The maximum number of entities to return. Default: 20. Minimum: 1. Maximum: 50. 34 | /// Optional. The index of the first object to return. Default: 0 (i.e., the first object). Use with limit to get the next set of objects. 35 | /// Optional. Over what time frame the affinities are computed. Valid values: long_term (calculated from several years of data and including all new data as it becomes available), medium_term (approximately last 6 months), short_term (approximately last 4 weeks). Default: medium_term. 36 | /// A Task that, once successfully completed, returns a instance of `T`. 37 | /// 38 | /// https://developer.spotify.com/documentation/web-api/reference/personalization/get-users-top-artists-and-tracks/ 39 | /// 40 | Task GetUsersTopArtists( 41 | int limit = 20, 42 | int offset = 0, 43 | TimeRange timeRange = TimeRange.MediumTerm, 44 | string accessToken = null 45 | ); 46 | 47 | /// 48 | /// Get the current user’s top tracks based on calculated affinity. 49 | /// 50 | /// Optional. The maximum number of entities to return. Default: 20. Minimum: 1. Maximum: 50. 51 | /// Optional. The index of the first object to return. Default: 0 (i.e., the first object). Use with limit to get the next set of objects. 52 | /// Optional. Over what time frame the affinities are computed. Valid values: long_term (calculated from several years of data and including all new data as it becomes available), medium_term (approximately last 6 months), short_term (approximately last 4 weeks). Default: medium_term. 53 | /// A Task that, once successfully completed, returns a full object. 54 | /// 55 | /// https://developer.spotify.com/documentation/web-api/reference/personalization/get-users-top-artists-and-tracks/ 56 | /// 57 | Task GetUsersTopTracks( 58 | int limit = 20, 59 | int offset = 0, 60 | TimeRange timeRange = TimeRange.MediumTerm, 61 | string accessToken = null 62 | ); 63 | 64 | /// 65 | /// Get the current user’s top tracks based on calculated affinity. 66 | /// 67 | /// Optional. The maximum number of entities to return. Default: 20. Minimum: 1. Maximum: 50. 68 | /// Optional. The index of the first object to return. Default: 0 (i.e., the first object). Use with limit to get the next set of objects. 69 | /// Optional. Over what time frame the affinities are computed. Valid values: long_term (calculated from several years of data and including all new data as it becomes available), medium_term (approximately last 6 months), short_term (approximately last 4 weeks). Default: medium_term. 70 | /// A Task that, once successfully completed, returns a instance of `T`. 71 | /// 72 | /// https://developer.spotify.com/documentation/web-api/reference/personalization/get-users-top-artists-and-tracks/ 73 | /// 74 | Task GetUsersTopTracks( 75 | int limit = 20, 76 | int offset = 0, 77 | TimeRange timeRange = TimeRange.MediumTerm, 78 | string accessToken = null 79 | ); 80 | 81 | #endregion 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/ISearchApi.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | /// 6 | /// Get Spotify Catalog information about artists, albums, tracks or playlists that match a keyword string. 7 | /// 8 | public interface ISearchApi 9 | { 10 | /// 11 | /// Get Spotify Catalog information about artists, albums, tracks or playlists that match a 12 | /// keyword string. 13 | /// 14 | /// Search query keywords and optional field filters and operators. For 15 | /// example: `q=roadhouse%20blues`. See also https://developer.spotify.com/documentation/web-api/reference/search/search/#writing-a-query---guidelines 16 | /// Specify one of . 17 | /// Optional. Choose a . If a country code 18 | /// is specified, only artists, albums, and tracks with content that is playable in that market 19 | /// is returned. Note: Playlist results are not affected by the market parameter. 20 | /// Optional. Maximum number of results to return. Default: 20, Minimum: 1, 21 | /// Maximum: 50. 22 | /// Optional. The index of the first result to return. Default: 0 (the 23 | /// first result). Maximum offset (including limit): 10,000. Use with limit to get the next 24 | /// page of search results. 25 | /// Optional. A valid access token from the Spotify Accounts service, 26 | /// used for this call only. See constructors for more ways to provide access tokens. 27 | /// Task of 28 | Task Search( 29 | string query, 30 | string type, 31 | string market = null, 32 | int? limit = null, 33 | int offset = 0, 34 | string accessToken = null); 35 | 36 | /// 37 | /// Get Spotify Catalog information about artists, albums, tracks or playlists that match a 38 | /// keyword string. 39 | /// 40 | /// Search query keywords and optional field filters and operators. For 41 | /// example: `q=roadhouse%20blues`. See also https://developer.spotify.com/documentation/web-api/reference/search/search/#writing-a-query---guidelines 42 | /// Specify multiple to search across. 43 | /// Optional. Choose a . If a country code 44 | /// is specified, only artists, albums, and tracks with content that is playable in that market 45 | /// is returned. Note: Playlist results are not affected by the market parameter. 46 | /// Optional. Maximum number of results to return. Default: 20, Minimum: 1, 47 | /// Maximum: 50. Note: The limit is applied within each type, not on the total response. For 48 | /// example, if the limit value is 3 and the type is `artist,album`, the response contains 3 49 | /// artists and 3 albums. 50 | /// Optional. The index of the first result to return. Default: 0 (the 51 | /// first result). Maximum offset (including limit): 10,000. Use with limit to get the next 52 | /// page of search results. 53 | /// Optional. A valid access token from the Spotify Accounts service, 54 | /// used for this call only. See constructors for more ways to provide access tokens. 55 | /// Task of 56 | Task Search( 57 | string query, 58 | string[] types, 59 | string market = null, 60 | int? limit = null, 61 | int offset = 0, 62 | string accessToken = null); 63 | 64 | /// 65 | /// Get Spotify Catalog information about artists, albums, tracks or playlists that match a 66 | /// keyword string. 67 | /// 68 | /// Search query keywords and optional field filters and operators. For 69 | /// example: `q=roadhouse%20blues`. See also https://developer.spotify.com/documentation/web-api/reference/search/search/#writing-a-query---guidelines 70 | /// Specify multiple to search across. 71 | /// Optional. Choose a . If a country code 72 | /// is specified, only artists, albums, and tracks with content that is playable in that market 73 | /// is returned. Note: Playlist results are not affected by the market parameter. 74 | /// Optional. Maximum number of results to return. Default: 20, Minimum: 1, 75 | /// Maximum: 50. Note: The limit is applied within each type, not on the total response. For 76 | /// example, if the limit value is 3 and the type is `artist,album`, the response contains 3 77 | /// artists and 3 albums. 78 | /// Optional. The index of the first result to return. Default: 0 (the 79 | /// first result). Maximum offset (including limit): 10,000. Use with limit to get the next 80 | /// page of search results. 81 | /// Optional. A valid access token from the Spotify Accounts service, 82 | /// used for this call only. See constructors for more ways to provide access tokens. 83 | /// Optionally provide your own type to deserialise Spotify's response to. 84 | /// Task of T. The Spotify response is deserialised as T. 85 | Task Search( 86 | string query, 87 | string[] types, 88 | string market = null, 89 | int? limit = null, 90 | int offset = 0, 91 | string accessToken = null); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/IUsersProfileApi.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public interface IUsersProfileApi 6 | { 7 | /// 8 | /// Get detailed profile information about the current user (including the current user’s username). 9 | /// 10 | /// Optional. A valid access token from the Spotify Accounts service. 11 | /// The access token must have been issued on behalf of the current user. Reading the user's 12 | /// email address requires the `user-read-email` scope; reading country and product subscription 13 | /// level requires the `user-read-private` scope. 14 | /// 15 | /// See 16 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/ 17 | Task GetCurrentUsersProfile(string accessToken = null); 18 | 19 | /// 20 | /// Get detailed profile information about the current user (including the current user’s username). 21 | /// 22 | /// Optional. A valid access token from the Spotify Accounts service. 23 | /// The access token must have been issued on behalf of the current user. Reading the user's 24 | /// email address requires the `user-read-email` scope; reading country and product subscription 25 | /// level requires the `user-read-private` scope. 26 | /// 27 | /// Optionally provide your own type to deserialise Spotify's response to. 28 | /// Task of T. The Spotify response is deserialised as T. 29 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/ 30 | Task GetCurrentUsersProfile(string accessToken = null); 31 | 32 | /// 33 | /// Get public profile information about a Spotify user. 34 | /// 35 | /// The user's Spotify user ID. 36 | /// Optional. A valid access token from the Spotify Accounts service. 37 | /// The access token must have been issued on behalf of the current user. Reading the user's 38 | /// email address requires the `user-read-email` scope; reading country and product subscription 39 | /// level requires the `user-read-private` scope. 40 | /// 41 | /// See 42 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-users-profile/ 43 | Task GetUsersProfile(string userId, string accessToken = null); 44 | 45 | /// 46 | /// Get public profile information about a Spotify user. 47 | /// 48 | /// The user's Spotify user ID. 49 | /// Optional. A valid access token from the Spotify Accounts service. 50 | /// The access token must have been issued on behalf of the current user. Reading the user's 51 | /// email address requires the `user-read-email` scope; reading country and product subscription 52 | /// level requires the `user-read-private` scope. 53 | /// 54 | /// Optionally provide your own type to deserialise Spotify's response to. 55 | /// Task of T. The Spotify response is deserialised as T. 56 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-users-profile/ 57 | Task GetUsersProfile(string userId, string accessToken = null); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Logger/Logger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Runtime.CompilerServices; 5 | 6 | namespace SpotifyApi.NetCore 7 | { 8 | /// 9 | /// Simple Class Library Logger 10 | /// 11 | public static class Logger 12 | { 13 | private static ILoggerFactory _Factory = null; 14 | 15 | /// 16 | /// Instance of . 17 | /// 18 | public static ILoggerFactory LoggerFactory 19 | { 20 | get 21 | { 22 | if (_Factory == null) 23 | { 24 | _Factory = new LoggerFactory(); 25 | } 26 | return _Factory; 27 | } 28 | set { _Factory = value; } 29 | } 30 | 31 | /// 32 | /// Create an instance of with the given category. 33 | /// 34 | /// The category description for this instance of the logger. 35 | /// Instance of 36 | public static ILogger CreateLogger(string category) => LoggerFactory.CreateLogger(category); 37 | 38 | private static string Category(string className, string memberName) => $"SpotifyApi.NetCore:{className}.{memberName}"; 39 | 40 | /// 41 | /// Log a message at Debug level using a category name derived from className and Member name 42 | /// 43 | /// The log message. 44 | /// Optional. The name of the class emiting the log message. e.g. `nameof(RestHttpClient)`. 45 | /// The compiler will set the caller member name. Can be overidden. 46 | /// The compiler will set the source file path. Can be overidden. 47 | /// The compiler will set the source line number. Can be overidden. 48 | public static void Debug( 49 | string message, 50 | string className = null, 51 | [CallerMemberName] string memberName = null, 52 | [CallerFilePath] string sourceFilePath = null, 53 | [CallerLineNumber] int sourceLineNumber = 0) 54 | { 55 | //SpotifyWebApi.Get: This is the message. c:\path\file.cs:10 56 | //.Get: This is the message 57 | //.: This is the message 58 | 59 | string fullMessage = $"{message}\r\n{sourceFilePath}:{sourceLineNumber}"; 60 | string category = Category(className, memberName); 61 | Trace.WriteLine(fullMessage, category); 62 | CreateLogger(category).LogDebug(fullMessage); 63 | } 64 | 65 | /// 66 | /// Log a message at Information level using a category name derived from className and Member name 67 | /// 68 | /// The log message. 69 | /// Optional. The name of the class emiting the log message. e.g. `nameof(RestHttpClient)`. 70 | /// The compiler will set the caller member name. Can be overidden. 71 | /// The compiler will set the source file path. Can be overidden. 72 | /// The compiler will set the source line number. Can be overidden. 73 | public static void Information(string message, string className = null, [CallerMemberName] string memberName = "") 74 | { 75 | string category = Category(className, memberName); 76 | Trace.TraceInformation($"{category}: {message}"); 77 | CreateLogger(category).LogInformation(message); 78 | } 79 | 80 | /// 81 | /// Log a message at Warning level using a category name derived from className and Member name 82 | /// 83 | /// The log message. 84 | /// Optional. The name of the class emiting the log message. e.g. `nameof(RestHttpClient)`. 85 | /// The compiler will set the caller member name. Can be overidden. 86 | /// The compiler will set the source file path. Can be overidden. 87 | /// The compiler will set the source line number. Can be overidden. 88 | public static void Warning(string message, string className = null, [CallerMemberName] string memberName = "") 89 | { 90 | string category = Category(className, memberName); 91 | Trace.TraceWarning($"{category}: {message}"); 92 | CreateLogger(category).LogWarning(message); 93 | } 94 | 95 | /// 96 | /// Log a message at Error level using a category name derived from className and Member name 97 | /// 98 | /// The log message. 99 | /// Optional. An Exception to log. 100 | /// Optional. The name of the class emiting the log message. e.g. `nameof(RestHttpClient)`. 101 | /// The compiler will set the caller member name. Can be overidden. 102 | /// The compiler will set the source file path. Can be overidden. 103 | /// The compiler will set the source line number. Can be overidden. 104 | public static void Error( 105 | string message, 106 | Exception exception = null, 107 | string className = null, 108 | [CallerMemberName] string memberName = "", 109 | [CallerFilePath] string sourceFilePath = "", 110 | [CallerLineNumber] int sourceLineNumber = 0) 111 | { 112 | string category = Category(className, memberName); 113 | string fullMessage = $"{category}: {message}\r\n{sourceFilePath}:{sourceLineNumber}"; 114 | 115 | if (exception == null) 116 | { 117 | Trace.TraceError(fullMessage); 118 | CreateLogger(category).LogError(message); 119 | } 120 | else 121 | { 122 | Trace.TraceError(fullMessage); 123 | CreateLogger(category).LogError(exception, message); 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Actions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class Actions 6 | { 7 | [JsonProperty("disallows")] 8 | public Disallows Disallows { get; set; } 9 | } 10 | 11 | public class Disallows 12 | { 13 | [JsonProperty("interrupting_playback")] 14 | public bool InterruptingPlayback { get; set; } 15 | 16 | [JsonProperty("pausing")] 17 | public bool Pausing { get; set; } 18 | 19 | [JsonProperty("resuming")] 20 | public bool Resuming { get; set; } 21 | 22 | [JsonProperty("seeking")] 23 | public bool Seeking { get; set; } 24 | 25 | [JsonProperty("skipping_next")] 26 | public bool SkippingNext { get; set; } 27 | 28 | [JsonProperty("skipping_prev")] 29 | public bool SkippingPrev { get; set; } 30 | 31 | [JsonProperty("toggling_repeat_context")] 32 | public bool TogglingRepeatContext { get; set; } 33 | 34 | [JsonProperty("toggling_shuffle")] 35 | public bool TogglingShuffle { get; set; } 36 | 37 | [JsonProperty("toggling_repeat_track")] 38 | public bool TogglingRepeatTrack { get; set; } 39 | 40 | [JsonProperty("transferring_playback")] 41 | public bool TransferringPlayback { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Album.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ringobot/SpotifyApi.NetCore/87cdccd2903d71514e2a7c54a7165437d9a0e197/src/SpotifyApi.NetCore/Models/Album.cs -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Artist.cs: -------------------------------------------------------------------------------- 1 | // Thanks @quicktype ! 2 | using Newtonsoft.Json; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | /// 7 | /// Artist object (full). 8 | /// 9 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/ 10 | public partial class Artist 11 | { 12 | /// 13 | /// Known external URLs for this artist. 14 | /// 15 | [JsonProperty("external_urls")] 16 | public ExternalUrls ExternalUrls { get; set; } 17 | 18 | /// 19 | /// Information about the followers of the artist. 20 | /// 21 | [JsonProperty("followers")] 22 | public Followers Followers { get; set; } 23 | 24 | /// 25 | /// A list of the genres the artist is associated with. For example: "Prog Rock" , "Post-Grunge". 26 | /// 27 | [JsonProperty("genres")] 28 | public string[] Genres { get; set; } 29 | 30 | /// 31 | /// A link to the Web API endpoint providing full details of the artist. 32 | /// 33 | [JsonProperty("href")] 34 | public string Href { get; set; } 35 | 36 | /// 37 | /// The Spotify ID for the artist. 38 | /// 39 | [JsonProperty("id")] 40 | public string Id { get; set; } 41 | 42 | /// 43 | /// Images of the artist in various sizes, widest first. 44 | /// 45 | [JsonProperty("images")] 46 | public Image[] Images { get; set; } 47 | 48 | /// 49 | /// The name of the artist 50 | /// 51 | [JsonProperty("name")] 52 | public string Name { get; set; } 53 | 54 | /// 55 | /// The popularity of the artist. The value will be between 0 and 100, with 100 being the most 56 | /// popular. 57 | /// 58 | [JsonProperty("popularity")] 59 | public int Popularity { get; set; } 60 | 61 | /// 62 | /// The object type: "artist" 63 | /// 64 | [JsonProperty("type")] 65 | public string Type { get; set; } 66 | 67 | /// 68 | /// The Spotify URI for the artist. 69 | /// 70 | [JsonProperty("uri")] 71 | public string Uri { get; set; } 72 | } 73 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Category.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | /// 6 | /// Category object (full) 7 | /// 8 | /// https://developer.spotify.com/documentation/web-api/reference/browse/get-category/#categoryobject 9 | public partial class Category 10 | { 11 | /// 12 | /// A link to the Web API endpoint returning full details of the category. 13 | /// 14 | [JsonProperty("href")] 15 | public string Href { get; set; } 16 | 17 | /// 18 | /// The category icon, in various sizes. 19 | /// 20 | [JsonProperty("icons")] 21 | public Image[] Icons { get; set; } 22 | 23 | /// 24 | /// The Spotify category ID of the category. 25 | /// 26 | [JsonProperty("id")] 27 | public string Id { get; set; } 28 | 29 | /// 30 | /// The name of the category. 31 | /// 32 | [JsonProperty("name")] 33 | public string Name { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Common.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ringobot/SpotifyApi.NetCore/87cdccd2903d71514e2a7c54a7165437d9a0e197/src/SpotifyApi.NetCore/Models/Common.cs -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/CurrentEpisodePlaybackContext.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public partial class CurrentEpisodePlaybackContext : CurrentPlaybackContext 6 | { 7 | /// 8 | /// The currently playing Episode. Can be null. 9 | /// 10 | [JsonProperty("item")] 11 | public Episode Item { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/CurrentPlaybackContext.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ringobot/SpotifyApi.NetCore/87cdccd2903d71514e2a7c54a7165437d9a0e197/src/SpotifyApi.NetCore/Models/CurrentPlaybackContext.cs -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/CurrentTrackPlaybackContext.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public partial class CurrentTrackPlaybackContext : CurrentPlaybackContext 6 | { 7 | /// 8 | /// The currently playing track. Can be null. 9 | /// 10 | [JsonProperty("item")] 11 | public Track Item { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Cursors.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class Cursors 6 | { 7 | /// 8 | /// The cursor to use as key to find the next page of items. 9 | /// 10 | [JsonProperty("after")] 11 | public string After { get; set; } 12 | 13 | /// 14 | /// The cursor to use as key to find the previous page of items. 15 | /// 16 | [JsonProperty("before")] 17 | public string Before { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Device.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ringobot/SpotifyApi.NetCore/87cdccd2903d71514e2a7c54a7165437d9a0e197/src/SpotifyApi.NetCore/Models/Device.cs -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Episode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | /// 7 | /// Episode object (full) (public) 8 | /// 9 | /// 10 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/#episode-object-full 11 | /// 12 | public partial class Episode 13 | { 14 | /// 15 | /// A URL to a 30 second preview (MP3 format) of the episode. null if not available. 16 | /// 17 | [JsonProperty("audio_preview_url")] 18 | public Uri AudioPreviewUrl { get; set; } 19 | 20 | /// 21 | /// A description of the episode. 22 | /// 23 | [JsonProperty("description")] 24 | public string Description { get; set; } 25 | 26 | /// 27 | /// The episode length in milliseconds. 28 | /// 29 | [JsonProperty("duration_ms")] 30 | public long DurationMs { get; set; } 31 | 32 | /// 33 | /// Whether or not the episode has explicit content (true = yes it does; false = no it does not OR unknown). 34 | /// 35 | [JsonProperty("explicit")] 36 | public bool Explicit { get; set; } 37 | 38 | /// 39 | /// External URLs for this episode. 40 | /// 41 | [JsonProperty("external_urls")] 42 | public ExternalUrls ExternalUrls { get; set; } 43 | 44 | /// 45 | /// A link to the Web API endpoint providing full details of the episode. 46 | /// 47 | [JsonProperty("href")] 48 | public Uri Href { get; set; } 49 | 50 | /// 51 | /// The Spotify ID for the episode. 52 | /// 53 | [JsonProperty("id")] 54 | public string Id { get; set; } 55 | 56 | /// 57 | /// The cover art for the episode in various sizes, widest first. 58 | /// 59 | [JsonProperty("images")] 60 | public Image[] Images { get; set; } 61 | 62 | /// 63 | /// True if the episode is hosted outside of Spotify’s CDN. 64 | /// 65 | [JsonProperty("is_externally_hosted")] 66 | public bool IsExternallyHosted { get; set; } 67 | 68 | /// 69 | /// True if the episode is playable in the given market. Otherwise false. 70 | /// 71 | [JsonProperty("is_playable")] 72 | public bool IsPlayable { get; set; } 73 | 74 | /// 75 | /// Note: This field is deprecated and might be removed in the future. Please use the languages field instead. The language used in the episode, identified by a ISO 639 code. 76 | /// 77 | [JsonProperty("language")] 78 | public string Language { get; set; } 79 | 80 | /// 81 | /// A list of the languages used in the episode, identified by their ISO 639 code. 82 | /// 83 | [JsonProperty("languages")] 84 | public string[] Languages { get; set; } 85 | 86 | /// 87 | /// The name of the episode. 88 | /// 89 | [JsonProperty("name")] 90 | public string Name { get; set; } 91 | 92 | /// 93 | /// The date the episode was first released, for example "1981-12-15". Depending on the precision, it might be shown as "1981" or "1981-12". 94 | /// 95 | [JsonProperty("release_date")] 96 | public DateTimeOffset ReleaseDate { get; set; } 97 | 98 | /// 99 | /// The precision with which release_date value is known: "year", "month", or "day". 100 | /// 101 | [JsonProperty("release_date_precision")] 102 | public string ReleaseDatePrecision { get; set; } 103 | 104 | /// 105 | /// The user’s most recent position in the episode. Set if the supplied access token is a user token and has the scope user-read-playback-position. 106 | /// 107 | [JsonProperty("resume_point")] 108 | public ResumePoint ResumePoint { get; set; } 109 | 110 | /// 111 | /// The show on which the episode belongs. 112 | /// 113 | [JsonProperty("show")] 114 | public Show Show { get; set; } 115 | 116 | /// 117 | /// The object type: "episode". 118 | /// 119 | [JsonProperty("type")] 120 | public string Type { get; set; } 121 | 122 | /// 123 | /// The Spotify URI for the episode. 124 | /// 125 | [JsonProperty("uri")] 126 | public string Uri { get; set; } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/FeaturedPlaylists.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class FeaturedPlaylists : PagedPlaylists 6 | { 7 | [JsonProperty("message")] 8 | public string Message { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/ModifyPlaylistResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class ModifyPlaylistResponse 6 | { 7 | /// 8 | /// The snapshot_id can be used to identify your playlist version in future requests. 9 | /// 10 | [JsonProperty("snapshot_id")] 11 | public string SnapshotId { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedAlbums.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Models; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class PagedAlbums : Paged 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedArtists.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.Dynamic; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | /// 7 | /// Paged Full Artist Objects. 8 | /// 9 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/ 10 | public class PagedArtists : Paged 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedCategories.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Models; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class PagedCategories : Paged 6 | { 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedEpisodes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SpotifyApi.NetCore 6 | { 7 | /// 8 | /// Paged Full Episodes Object. 9 | /// 10 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/ 11 | public class PagedEpisodes : Paged 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedPlayHistory.cs: -------------------------------------------------------------------------------- 1 | namespace SpotifyApi.NetCore 2 | { 3 | public class PagedPlayHistory : Paged 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedPlaylists.cs: -------------------------------------------------------------------------------- 1 | namespace SpotifyApi.NetCore 2 | { 3 | public class PagedPlaylists : Paged 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedShows.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SpotifyApi.NetCore 6 | { 7 | /// 8 | /// Paged Full Show Objects. 9 | /// 10 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/ 11 | public class PagedShows : Paged 12 | { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedT.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class Paged 6 | { 7 | [JsonProperty("href")] 8 | public string Href { get; set; } 9 | 10 | [JsonProperty("items")] 11 | public T[] Items { get; set; } 12 | 13 | [JsonProperty("limit")] 14 | public int Limit { get; set; } 15 | 16 | [JsonProperty("next")] 17 | public string Next { get; set; } 18 | 19 | [JsonProperty("offset", NullValueHandling = NullValueHandling.Ignore)] 20 | public int Offset { get; set; } 21 | 22 | [JsonProperty("previous", NullValueHandling = NullValueHandling.Ignore)] 23 | public string Previous { get; set; } 24 | 25 | /// 26 | /// The cursors used to find the next set of items. 27 | /// 28 | [JsonProperty("cursors", NullValueHandling = NullValueHandling.Ignore)] 29 | public Cursors Cursors { get; set; } 30 | 31 | [JsonProperty("total", NullValueHandling = NullValueHandling.Ignore)] 32 | public int Total { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PagedTracks.cs: -------------------------------------------------------------------------------- 1 | namespace SpotifyApi.NetCore 2 | { 3 | /// 4 | /// Paged Full Track Objects. 5 | /// 6 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/ 7 | public class PagedTracks : Paged 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PlayHistory.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | public class PlayHistory 7 | { 8 | /// 9 | /// The track the user listened to. 10 | /// 11 | [JsonProperty("track")] 12 | public Track Track { get; set; } 13 | 14 | /// 15 | /// The date and time the track was played. 16 | /// 17 | [JsonProperty("played_at")] 18 | public string PlayedAt { get; set; } 19 | 20 | /// 21 | /// The context the track was played from. 22 | /// 23 | [JsonProperty("context")] 24 | public Context Context { get; set; } 25 | 26 | /// 27 | /// Converts the date and time the track was played into . 28 | /// 29 | public DateTimeOffset? PlayedAtDateTime() 30 | { 31 | if (PlayedAt == null) return null; 32 | 33 | if (DateTimeOffset.TryParse(PlayedAt, out var result)) 34 | { 35 | return result; 36 | } 37 | 38 | return null; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/PlaylistDetails.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | public class PlaylistDetails 6 | { 7 | /// 8 | /// The name for the new playlist, for example "Your Coolest Playlist". This name does not 9 | /// need to be unique; a user may have several playlists with the same name. 10 | /// 11 | [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] 12 | public string Name { get; set; } 13 | 14 | /// 15 | /// Defaults to true. If true the playlist will be public, if false it will be private. To 16 | /// be able to create private playlists, the user must have granted the playlist-modify-private scope . 17 | /// 18 | [JsonProperty("public", NullValueHandling = NullValueHandling.Ignore)] 19 | public bool? Public { get; set; } 20 | 21 | /// 22 | /// Defaults to false. If true the playlist will be collaborative. Note that to create a collaborative 23 | /// playlist you must also set public to false . To create collaborative playlists you must have 24 | /// granted playlist-modify-private and playlist-modify-public scopes. 25 | /// 26 | [JsonProperty("collaborative", NullValueHandling = NullValueHandling.Ignore)] 27 | public bool? Collaborative { get; set; } 28 | 29 | /// 30 | /// Value for playlist description as displayed in Spotify Clients and in the Web API. 31 | /// 32 | [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] 33 | public string Description { get; set; } 34 | } 35 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/RecommendationsResult.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | /// 6 | /// recommendations object 7 | /// 8 | public partial class RecommendationsResult 9 | { 10 | /// 11 | /// An array of track object (simplified) ordered according to the parameters supplied. 12 | /// 13 | [JsonProperty("tracks")] 14 | public Track[] Tracks { get; set; } 15 | 16 | /// 17 | /// An array of recommendation seed objects. 18 | /// 19 | [JsonProperty("seeds")] 20 | public Seed[] Seeds { get; set; } 21 | } 22 | 23 | /// 24 | /// recommendations seed object 25 | /// 26 | public partial class Seed 27 | { 28 | /// 29 | /// The number of recommended tracks available for this seed. 30 | /// 31 | [JsonProperty("initialPoolSize")] 32 | public int InitialPoolSize { get; set; } 33 | 34 | /// 35 | /// The number of tracks available after min_* and max_* filters have been applied. 36 | /// 37 | [JsonProperty("afterFilteringSize")] 38 | public int AfterFilteringSize { get; set; } 39 | 40 | /// 41 | /// The number of tracks available after relinking for regional availability. 42 | /// 43 | [JsonProperty("afterRelinkingSize")] 44 | public int AfterRelinkingSize { get; set; } 45 | 46 | /// 47 | /// The id used to select this seed. This will be the same as the string used in the seed_artists, 48 | /// seed_tracks or seed_genres parameter. 49 | /// 50 | [JsonProperty("id")] 51 | public string Id { get; set; } 52 | 53 | /// 54 | /// The entity type of this seed. One of artist , track or genre. 55 | /// 56 | [JsonProperty("type")] 57 | public string Type { get; set; } 58 | 59 | /// 60 | /// A link to the full track or artist data for this seed. For tracks this will be a link to 61 | /// a Track Object. For artists a link to an Artist Object. For genre seeds, this value will 62 | /// be null. 63 | /// 64 | [JsonProperty("href")] 65 | public string Href { get; set; } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/RepeatStates.cs: -------------------------------------------------------------------------------- 1 | namespace SpotifyApi.NetCore 2 | { 3 | /// 4 | /// Repeat States constants 5 | /// 6 | public static class RepeatStates 7 | { 8 | public const string Track = "track"; 9 | 10 | public const string Context = "context"; 11 | 12 | public const string Off = "off"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/SearchResult.cs: -------------------------------------------------------------------------------- 1 | // Generated by https://quicktype.io 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | using Newtonsoft.Json; 6 | using SpotifyApi.NetCore.Models; 7 | using System; 8 | 9 | /// 10 | /// Search Result. 11 | /// 12 | /// https://developer.spotify.com/documentation/web-api/reference/search/search/ 13 | public partial class SearchResult 14 | { 15 | [JsonProperty("artists")] 16 | public ArtistsSearchResult Artists { get; set; } 17 | 18 | [JsonProperty("albums")] 19 | public AlbumsSearchResult Albums { get; set; } 20 | 21 | [JsonProperty("tracks")] 22 | public TracksSearchResult Tracks { get; set; } 23 | 24 | [JsonProperty("playlists")] 25 | public PlaylistsSearchResult Playlists { get; set; } 26 | } 27 | 28 | /// 29 | /// Artists Search Result. 30 | /// 31 | public partial class ArtistsSearchResult : Paged 32 | { 33 | } 34 | 35 | /// 36 | /// Albums Search Result. 37 | /// 38 | public partial class AlbumsSearchResult : Paged 39 | { 40 | } 41 | 42 | /// 43 | /// Tracks Search Result. 44 | /// 45 | public partial class TracksSearchResult : Paged 46 | { 47 | } 48 | 49 | /// 50 | /// Playlists Search Result 51 | /// 52 | public partial class PlaylistsSearchResult : Paged 53 | { 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Show.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace SpotifyApi.NetCore 5 | { 6 | /// 7 | /// Show object (full) (public) 8 | /// 9 | /// 10 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/#show-object-full 11 | /// 12 | public partial class Show 13 | { 14 | /// 15 | /// A list of the countries in which the show can be played, identified by their ISO 3166-1 alpha-2 code. 16 | /// 17 | [JsonProperty("available_markets")] 18 | public string[] AvailableMarkets { get; set; } 19 | 20 | /// 21 | /// The copyright statements of the show. 22 | /// 23 | [JsonProperty("copyrights")] 24 | public object[] Copyrights { get; set; } 25 | 26 | /// 27 | /// A description of the show. 28 | /// 29 | [JsonProperty("description")] 30 | public string Description { get; set; } 31 | 32 | /// 33 | /// Whether or not the show has explicit content (true = yes it does; false = no it does not OR unknown). 34 | /// 35 | [JsonProperty("explicit")] 36 | public bool Explicit { get; set; } 37 | 38 | /// 39 | /// A list of the show’s episodes. 40 | /// 41 | [JsonProperty("episodes")] 42 | public PagedShows Episodes { get; set; } 43 | 44 | /// 45 | /// Known external URLs for this show. 46 | /// 47 | [JsonProperty("external_urls")] 48 | public ExternalUrls ExternalUrls { get; set; } 49 | 50 | /// 51 | /// A link to the Web API endpoint providing full details of the show. 52 | /// 53 | [JsonProperty("href")] 54 | public Uri Href { get; set; } 55 | 56 | /// 57 | /// The Spotify ID for the show. 58 | /// 59 | [JsonProperty("id")] 60 | public string Id { get; set; } 61 | 62 | /// 63 | /// The cover art for the show in various sizes, widest first. 64 | /// 65 | [JsonProperty("images")] 66 | public Image[] Images { get; set; } 67 | 68 | /// 69 | /// True if all of the show’s episodes are hosted outside of Spotify’s CDN. This field might be null in some cases. 70 | /// 71 | [JsonProperty("is_externally_hosted")] 72 | public bool IsExternallyHosted { get; set; } 73 | 74 | /// 75 | /// A list of the languages used in the show, identified by their ISO 639 code. 76 | /// 77 | [JsonProperty("languages")] 78 | public string[] Languages { get; set; } 79 | 80 | /// 81 | /// The media type of the show. 82 | /// 83 | [JsonProperty("media_type")] 84 | public string MediaType { get; set; } 85 | 86 | /// 87 | /// The name of the show. 88 | /// 89 | [JsonProperty("name")] 90 | public string Name { get; set; } 91 | 92 | /// 93 | /// The publisher of the show. 94 | /// 95 | [JsonProperty("publisher")] 96 | public string Publisher { get; set; } 97 | 98 | /// 99 | /// The object type: “show”. 100 | /// 101 | [JsonProperty("type")] 102 | public string Type { get; set; } 103 | 104 | /// 105 | /// The Spotify URI for the show. 106 | /// 107 | [JsonProperty("uri")] 108 | public string Uri { get; set; } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/SpotifyArtistAlbumGroups.cs: -------------------------------------------------------------------------------- 1 | namespace SpotifyApi.NetCore 2 | { 3 | public static class SpotifyArtistAlbumGroups 4 | { 5 | public const string Album = "album"; 6 | public const string Single = "single"; 7 | public const string AppearsOn = "appears_on"; 8 | public const string Compilation = "compilation"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/SpotifyResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace SpotifyApi.NetCore 4 | { 5 | /// 6 | /// A response from the Spotify API 7 | /// 8 | /// 9 | public class SpotifyResponse 10 | { 11 | public HttpStatusCode StatusCode { get; set; } 12 | 13 | public string ReasonPhrase { get; set; } 14 | } 15 | 16 | /// 17 | /// A generic response from the Spotify API 18 | /// 19 | /// 20 | public class SpotifyResponse : SpotifyResponse 21 | { 22 | public T Data { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/SpotifySearchTypes.cs: -------------------------------------------------------------------------------- 1 | namespace SpotifyApi.NetCore 2 | { 3 | /// 4 | /// Spotify Search Type constants 5 | /// 6 | public static class SpotifySearchTypes 7 | { 8 | public const string Album = "album"; 9 | 10 | public const string Artist = "artist"; 11 | 12 | public const string Playlist = "playlist"; 13 | 14 | public const string Track = "track"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/SpotifyUri.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace SpotifyApi.NetCore.Models 6 | { 7 | /// 8 | /// Domain Model Object that parses and describes a Spotify URI or Id. 9 | /// 10 | public class SpotifyUri 11 | { 12 | internal static readonly Regex SpotifyUriRegEx = new Regex("spotify:[a-z]+:[a-zA-Z0-9]+$"); 13 | internal static readonly Regex SpotifyUserPlaylistUriRegEx = new Regex("^spotify:user:[a-z0-9_-]+:playlist:[a-zA-Z0-9]+$"); 14 | internal static readonly Regex SpotifyUserCollectionUriRegEx = new Regex("^spotify:user:[a-z0-9_-]+:collection:[a-z]+:[a-zA-Z0-9]+$"); 15 | internal static readonly Regex SpotifyIdRegEx = new Regex("^[a-zA-Z0-9]+$"); 16 | 17 | /// 18 | /// Instantiates a new . 19 | /// 20 | /// A Spotify Id or URI. 21 | /// The item type, e.g. "album", "artist", "track", "playlist". 22 | public SpotifyUri(string inputValue, string type = null) 23 | { 24 | if (string.IsNullOrWhiteSpace(inputValue)) throw new ArgumentNullException(nameof(inputValue)); 25 | 26 | InputValue = inputValue; 27 | string trimUri = inputValue.Trim(); 28 | string[] uriParts = trimUri.Split(':'); 29 | 30 | // Spotify URI 31 | MatchCollection matchesUri = SpotifyUriRegEx.Matches(trimUri); 32 | if (matchesUri.Count > 0) 33 | { 34 | // spotify:playlist:0TnOYISbd1XYRBk9myaseg 35 | Uri = FullUri = matchesUri[0].Value; 36 | ItemType = TypeFromUri(Uri); 37 | Id = FromUriToId(Uri); 38 | IsSpotifyUri = true; 39 | IsValid = type == null || type == ItemType; 40 | return; 41 | } 42 | 43 | // if a Spotify Id 44 | if (SpotifyIdRegEx.IsMatch(trimUri)) 45 | { 46 | if (type != null && new[] { "album", "artist", "track", "playlist" }.Contains(type)) 47 | { 48 | FullUri = Uri = $"spotify:{type}:{trimUri}"; 49 | IsSpotifyUri = true; 50 | } 51 | 52 | Id = trimUri; 53 | IsValid = true; 54 | } 55 | 56 | // Spotify User Collection URI 57 | if (SpotifyUserCollectionUriRegEx.IsMatch(trimUri)) 58 | { 59 | // 0 1 2 3 4 5 60 | // spotify:user:daniellarsennz:collection:artist:65XA3lk0aG9XejO8y37jjD 61 | 62 | Uri = $"spotify:{uriParts[4]}:{uriParts[5]}"; 63 | FullUri = trimUri; 64 | Id = FromUriToId(Uri); 65 | ItemType = TypeFromUri(Uri); 66 | IsSpotifyUri = true; 67 | IsUserCollectionUri = true; 68 | IsValid = type == null || type == ItemType; 69 | return; 70 | } 71 | 72 | // Spotify User Playlist URI 73 | if (SpotifyUserPlaylistUriRegEx.IsMatch(trimUri)) 74 | { 75 | // 0 1 2 3 4 76 | // spotify:user:1298341199:playlist:6RTNx0BJWjbmJuEfvMau3r 77 | 78 | Uri = $"spotify:{uriParts[3]}:{uriParts[4]}"; 79 | FullUri = trimUri; 80 | Id = FromUriToId(Uri); 81 | ItemType = TypeFromUri(Uri); 82 | IsSpotifyUri = true; 83 | IsUserPlaylistUri = true; 84 | IsValid = type == null || type == ItemType; 85 | return; 86 | } 87 | } 88 | 89 | /// 90 | /// True when contains a Spotify URI. 91 | /// 92 | public bool IsSpotifyUri { get; private set; } 93 | 94 | /// 95 | /// True when contains a User Playlist URI 96 | /// 97 | public bool IsUserPlaylistUri { get; private set; } 98 | 99 | /// 100 | /// True when contains a User Collection URI 101 | /// 102 | public bool IsUserCollectionUri { get; private set; } 103 | 104 | /// 105 | /// True when contains a valid Spotify URI or Id 106 | /// 107 | public bool IsValid { get; private set; } 108 | 109 | /// 110 | /// The raw input value. 111 | /// 112 | public string InputValue { get; private set; } 113 | 114 | /// 115 | /// The full (extended) Spotify URI including user, collection and type (if provided). 116 | /// 117 | public string FullUri { get; private set; } 118 | 119 | /// 120 | /// The Spotify URI derived (or copied) from input value. 121 | /// 122 | public string Uri { get; private set; } 123 | 124 | /// 125 | /// The item type, e.g. "album", "artist", "track", "playlist". 126 | /// 127 | public string ItemType { get; private set; } 128 | 129 | /// 130 | /// The Id only. 131 | /// 132 | public string Id { get; private set; } 133 | 134 | private static string TypeFromUri(string uri) => uri.Split(':')[1]; 135 | 136 | private static string FromUriToId(string uri) => uri.Split(':').LastOrDefault(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/TimeRange.cs: -------------------------------------------------------------------------------- 1 | namespace SpotifyApi.NetCore 2 | { 3 | public enum TimeRange 4 | { 5 | LongTerm, 6 | MediumTerm, 7 | ShortTerm 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/Track.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ringobot/SpotifyApi.NetCore/87cdccd2903d71514e2a7c54a7165437d9a0e197/src/SpotifyApi.NetCore/Models/Track.cs -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/TrackAudioAnalysis.cs: -------------------------------------------------------------------------------- 1 | // Generated by https://quicktype.io 2 | using Newtonsoft.Json; 3 | using System.Collections.Generic; 4 | 5 | namespace SpotifyApi.NetCore 6 | { 7 | /// 8 | /// Track Audio Analysis 9 | /// 10 | public partial class TrackAudioAnalysis 11 | { 12 | [JsonProperty("bars")] 13 | public Bar[] Bars { get; set; } 14 | 15 | [JsonProperty("beats")] 16 | public Bar[] Beats { get; set; } 17 | 18 | [JsonProperty("meta")] 19 | public Meta Meta { get; set; } 20 | 21 | [JsonProperty("sections")] 22 | public Dictionary[] Sections { get; set; } 23 | 24 | [JsonProperty("segments")] 25 | public Segment[] Segments { get; set; } 26 | 27 | [JsonProperty("tatums")] 28 | public Bar[] Tatums { get; set; } 29 | 30 | [JsonProperty("track")] 31 | public AudioAnalysisTrack Track { get; set; } 32 | } 33 | 34 | /// 35 | /// Bar object 36 | /// 37 | public partial class Bar 38 | { 39 | [JsonProperty("start")] 40 | public double Start { get; set; } 41 | 42 | [JsonProperty("duration")] 43 | public double Duration { get; set; } 44 | 45 | [JsonProperty("confidence")] 46 | public double Confidence { get; set; } 47 | } 48 | 49 | /// 50 | /// Meta object 51 | /// 52 | public partial class Meta 53 | { 54 | [JsonProperty("analyzer_version")] 55 | public string AnalyzerVersion { get; set; } 56 | 57 | [JsonProperty("platform")] 58 | public string Platform { get; set; } 59 | 60 | [JsonProperty("detailed_status")] 61 | public string DetailedStatus { get; set; } 62 | 63 | [JsonProperty("status_code")] 64 | public long StatusCode { get; set; } 65 | 66 | [JsonProperty("timestamp")] 67 | public long Timestamp { get; set; } 68 | 69 | [JsonProperty("analysis_time")] 70 | public double AnalysisTime { get; set; } 71 | 72 | [JsonProperty("input_process")] 73 | public string InputProcess { get; set; } 74 | } 75 | 76 | /// 77 | /// Segment object 78 | /// 79 | public partial class Segment 80 | { 81 | [JsonProperty("start")] 82 | public double Start { get; set; } 83 | 84 | [JsonProperty("duration")] 85 | public double Duration { get; set; } 86 | 87 | [JsonProperty("confidence")] 88 | public double Confidence { get; set; } 89 | 90 | [JsonProperty("loudness_start")] 91 | public double LoudnessStart { get; set; } 92 | 93 | [JsonProperty("loudness_max_time")] 94 | public double LoudnessMaxTime { get; set; } 95 | 96 | [JsonProperty("loudness_max")] 97 | public double LoudnessMax { get; set; } 98 | 99 | [JsonProperty("loudness_end")] 100 | public long LoudnessEnd { get; set; } 101 | 102 | [JsonProperty("pitches")] 103 | public double[] Pitches { get; set; } 104 | 105 | [JsonProperty("timbre")] 106 | public double[] Timbre { get; set; } 107 | } 108 | 109 | /// 110 | /// Audio analysis 111 | /// 112 | public partial class AudioAnalysisTrack 113 | { 114 | [JsonProperty("duration")] 115 | public double Duration { get; set; } 116 | 117 | [JsonProperty("sample_md5")] 118 | public string SampleMd5 { get; set; } 119 | 120 | [JsonProperty("offset_seconds")] 121 | public long OffsetSeconds { get; set; } 122 | 123 | [JsonProperty("window_seconds")] 124 | public long WindowSeconds { get; set; } 125 | 126 | [JsonProperty("analysis_sample_rate")] 127 | public long AnalysisSampleRate { get; set; } 128 | 129 | [JsonProperty("analysis_channels")] 130 | public long AnalysisChannels { get; set; } 131 | 132 | [JsonProperty("end_of_fade_in")] 133 | public long EndOfFadeIn { get; set; } 134 | 135 | [JsonProperty("start_of_fade_out")] 136 | public double StartOfFadeOut { get; set; } 137 | 138 | [JsonProperty("loudness")] 139 | public double Loudness { get; set; } 140 | 141 | [JsonProperty("tempo")] 142 | public double Tempo { get; set; } 143 | 144 | [JsonProperty("tempo_confidence")] 145 | public double TempoConfidence { get; set; } 146 | 147 | [JsonProperty("time_signature")] 148 | public long TimeSignature { get; set; } 149 | 150 | [JsonProperty("time_signature_confidence")] 151 | public long TimeSignatureConfidence { get; set; } 152 | 153 | [JsonProperty("key")] 154 | public long Key { get; set; } 155 | 156 | [JsonProperty("key_confidence")] 157 | public double KeyConfidence { get; set; } 158 | 159 | [JsonProperty("mode")] 160 | public long Mode { get; set; } 161 | 162 | [JsonProperty("mode_confidence")] 163 | public double ModeConfidence { get; set; } 164 | 165 | [JsonProperty("codestring")] 166 | public string Codestring { get; set; } 167 | 168 | [JsonProperty("code_version")] 169 | public double CodeVersion { get; set; } 170 | 171 | [JsonProperty("echoprintstring")] 172 | public string Echoprintstring { get; set; } 173 | 174 | [JsonProperty("echoprint_version")] 175 | public double EchoprintVersion { get; set; } 176 | 177 | [JsonProperty("synchstring")] 178 | public string Synchstring { get; set; } 179 | 180 | [JsonProperty("synch_version")] 181 | public long SynchVersion { get; set; } 182 | 183 | [JsonProperty("rhythmstring")] 184 | public string Rhythmstring { get; set; } 185 | 186 | [JsonProperty("rhythm_version")] 187 | public long RhythmVersion { get; set; } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/Models/TrackAudioFeatures.cs: -------------------------------------------------------------------------------- 1 | // Generated by https://quicktype.io 2 | using Newtonsoft.Json; 3 | using System; 4 | 5 | namespace SpotifyApi.NetCore 6 | { 7 | /// 8 | /// Audio features object 9 | /// 10 | /// https://developer.spotify.com/documentation/web-api/reference/object-model/ 11 | public partial class TrackAudioFeatures 12 | { 13 | [JsonProperty("danceability")] 14 | public double Danceability { get; set; } 15 | 16 | [JsonProperty("energy")] 17 | public double Energy { get; set; } 18 | 19 | [JsonProperty("key")] 20 | public long Key { get; set; } 21 | 22 | [JsonProperty("loudness")] 23 | public double Loudness { get; set; } 24 | 25 | [JsonProperty("mode")] 26 | public long Mode { get; set; } 27 | 28 | [JsonProperty("speechiness")] 29 | public double Speechiness { get; set; } 30 | 31 | [JsonProperty("acousticness")] 32 | public double Acousticness { get; set; } 33 | 34 | [JsonProperty("instrumentalness")] 35 | public double Instrumentalness { get; set; } 36 | 37 | [JsonProperty("liveness")] 38 | public double Liveness { get; set; } 39 | 40 | [JsonProperty("valence")] 41 | public double Valence { get; set; } 42 | 43 | [JsonProperty("tempo")] 44 | public double Tempo { get; set; } 45 | 46 | [JsonProperty("type")] 47 | public string Type { get; set; } 48 | 49 | [JsonProperty("id")] 50 | public string Id { get; set; } 51 | 52 | [JsonProperty("uri")] 53 | public string Uri { get; set; } 54 | 55 | [JsonProperty("track_href")] 56 | public Uri TrackHref { get; set; } 57 | 58 | [JsonProperty("analysis_url")] 59 | public Uri AnalysisUrl { get; set; } 60 | 61 | [JsonProperty("duration_ms")] 62 | public long DurationMs { get; set; } 63 | 64 | [JsonProperty("time_signature")] 65 | public long TimeSignature { get; set; } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/SpotifyApi.NetCore.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | SpotifyApi.NetCore 5 | Spotify Web API .NET Core 6 | Lightweight .NET Core client wrapper for the Spotify Web API 7 | 3.5.0 8 | Daniel Larsen and contributors 9 | Ringobot 10 | Copyright 2020 Daniel Larsen and contributors 11 | MIT 12 | https://github.com/Ringobot/SpotifyApi.NetCore 13 | https://github.com/Ringobot/SpotifyApi.NetCore 14 | spotify;.net core;dotnet core;api;client;wrapper 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/SpotifyApiErrorException.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Net; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | 8 | namespace SpotifyApi.NetCore 9 | { 10 | /// 11 | /// A Spotify API Error Exception. 12 | /// 13 | public class SpotifyApiErrorException : Exception 14 | { 15 | /// 16 | /// Instantiates a SpotifyApiErrorException with a message. 17 | /// 18 | /// 19 | public SpotifyApiErrorException(string message) : base(message) { } 20 | 21 | /// 22 | /// Instantiates a SpotifyApiErrorException with a status code and a . 23 | /// 24 | /// 25 | /// 26 | public SpotifyApiErrorException(HttpStatusCode statusCode, SpotifyApiError spotifyApiError) : base(spotifyApiError?.Message) 27 | { 28 | HttpStatusCode = statusCode; 29 | SpotifyApiError = spotifyApiError; 30 | } 31 | 32 | /// 33 | /// The HTTP Status Code returned from the Spotify API 34 | /// 35 | public HttpStatusCode HttpStatusCode { get; private set; } 36 | 37 | /// 38 | /// The derived returned from the Spotify API 39 | /// 40 | public SpotifyApiError SpotifyApiError { get; private set; } 41 | 42 | /// 43 | /// Reads an to parse a . 44 | /// 45 | /// The . 46 | /// An instance of . 47 | public static async Task ReadErrorResponse(HttpResponseMessage response) 48 | { 49 | // if no content 50 | if (response.Content == null) return null; 51 | 52 | // if not JSON content type 53 | if (response.Content.Headers.ContentType?.MediaType != "application/json") return null; 54 | 55 | var content = await response.Content.ReadAsStringAsync(); 56 | Logger.Debug(content, nameof(SpotifyApiErrorException)); 57 | 58 | // if empty body 59 | if (string.IsNullOrWhiteSpace(content)) return null; 60 | 61 | var error = new SpotifyApiError { Json = content }; 62 | 63 | // interrogate properties to detect error json type 64 | var deserialized = JsonConvert.DeserializeObject(content) as JObject; 65 | 66 | // if no error property 67 | if (!deserialized.ContainsKey("error")) return error; 68 | 69 | switch (deserialized["error"].Type) 70 | { 71 | case JTokenType.Object: 72 | error.Message = deserialized["error"].Value("message"); 73 | break; 74 | case JTokenType.String: 75 | error.Message = deserialized["error_description"].Value(); 76 | break; 77 | } 78 | 79 | return error; 80 | } 81 | } 82 | 83 | /// 84 | /// A model for a Spotify API Error. 85 | /// 86 | public class SpotifyApiError 87 | { 88 | /// 89 | /// The message returned by the API 90 | /// 91 | public string Message { get; set; } 92 | 93 | /// 94 | /// The raw JSON string returned by the API 95 | /// 96 | public string Json { get; set; } 97 | } 98 | } -------------------------------------------------------------------------------- /src/SpotifyApi.NetCore/UsersProfileApi.cs: -------------------------------------------------------------------------------- 1 | using SpotifyApi.NetCore.Authorization; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace SpotifyApi.NetCore 9 | { 10 | public class UsersProfileApi : SpotifyWebApi, IUsersProfileApi 11 | { 12 | #region Constructors 13 | 14 | /// 15 | /// Instantiates a new . 16 | /// 17 | /// 18 | /// Use this constructor when an accessToken will be provided using the `accessToken` parameter 19 | /// on each method 20 | /// 21 | /// An instance of 22 | public UsersProfileApi(HttpClient httpClient) : base(httpClient) 23 | { 24 | } 25 | 26 | /// 27 | /// Instantiates a new . 28 | /// 29 | /// 30 | /// This constructor accepts a Spotify access token that will be used for all calls to the API 31 | /// (except when an accessToken is provided using the optional `accessToken` parameter on each method). 32 | /// 33 | /// An instance of 34 | /// A valid access token from the Spotify Accounts service 35 | public UsersProfileApi(HttpClient httpClient, string accessToken) : base(httpClient, accessToken) 36 | { 37 | } 38 | 39 | /// 40 | /// Instantiates a new . 41 | /// 42 | /// An instance of 43 | /// An instance of , e.g. . 44 | public UsersProfileApi(HttpClient httpClient, IAccessTokenProvider accessTokenProvider) 45 | : base(httpClient, accessTokenProvider) 46 | { 47 | } 48 | 49 | #endregion 50 | 51 | /// 52 | /// Get detailed profile information about the current user (including the current user’s username). 53 | /// 54 | /// Optional. A valid access token from the Spotify Accounts service. 55 | /// The access token must have been issued on behalf of the current user. Reading the user's 56 | /// email address requires the `user-read-email` scope; reading country and product subscription 57 | /// level requires the `user-read-private` scope. 58 | /// 59 | /// See 60 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/ 61 | public Task GetCurrentUsersProfile(string accessToken = null) => GetCurrentUsersProfile(accessToken: accessToken); 62 | 63 | /// 64 | /// Get detailed profile information about the current user (including the current user’s username). 65 | /// 66 | /// Optional. A valid access token from the Spotify Accounts service. 67 | /// The access token must have been issued on behalf of the current user. Reading the user's 68 | /// email address requires the `user-read-email` scope; reading country and product subscription 69 | /// level requires the `user-read-private` scope. 70 | /// 71 | /// Optionally provide your own type to deserialise Spotify's response to. 72 | /// Task of T. The Spotify response is deserialised as T. 73 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-current-users-profile/ 74 | public async Task GetCurrentUsersProfile(string accessToken = null) 75 | { 76 | var builder = new UriBuilder($"{BaseUrl}/me"); 77 | return await GetModel(builder.Uri, accessToken: accessToken); 78 | } 79 | 80 | /// 81 | /// Get public profile information about a Spotify user. 82 | /// 83 | /// The user's Spotify user ID. 84 | /// Optional. A valid access token from the Spotify Accounts service. 85 | /// The access token must have been issued on behalf of the current user. Reading the user's 86 | /// email address requires the `user-read-email` scope; reading country and product subscription 87 | /// level requires the `user-read-private` scope. 88 | /// 89 | /// See 90 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-users-profile/ 91 | public Task GetUsersProfile(string userId, string accessToken = null) 92 | => GetUsersProfile(userId, accessToken: accessToken); 93 | 94 | /// 95 | /// Get public profile information about a Spotify user. 96 | /// 97 | /// The user's Spotify user ID. 98 | /// Optional. A valid access token from the Spotify Accounts service. 99 | /// The access token must have been issued on behalf of the current user. Reading the user's 100 | /// email address requires the `user-read-email` scope; reading country and product subscription 101 | /// level requires the `user-read-private` scope. 102 | /// 103 | /// Optionally provide your own type to deserialise Spotify's response to. 104 | /// Task of T. The Spotify response is deserialised as T. 105 | /// https://developer.spotify.com/documentation/web-api/reference/users-profile/get-users-profile/ 106 | public async Task GetUsersProfile(string userId, string accessToken = null) 107 | { 108 | var builder = new UriBuilder($"{BaseUrl}/users/{userId}"); 109 | return await GetModel(builder.Uri, accessToken: accessToken); 110 | } 111 | } 112 | } 113 | --------------------------------------------------------------------------------