mirror of
https://github.com/fiso64/slsk-batchdl.git
synced 2024-12-22 22:42:41 +00:00
Implement spotify token and refresh flow from configuration
* makes headless usage of sldl easier #42 * fallback to outputting login flow URL if opening a browser fails (in headless environment) * output token and refresh token on login complete * parse token and refresh token from config and attempt to use before invoking login flow
This commit is contained in:
parent
6e7b8d5d67
commit
5357911054
3 changed files with 125 additions and 19 deletions
26
README.md
26
README.md
|
@ -126,6 +126,8 @@ Usage: sldl <input> [OPTIONS]
|
||||||
Spotify
|
Spotify
|
||||||
--spotify-id <id> spotify client ID
|
--spotify-id <id> spotify client ID
|
||||||
--spotify-secret <secret> spotify client secret
|
--spotify-secret <secret> spotify client secret
|
||||||
|
--spotify-token <token> spotify access token
|
||||||
|
--spotify-refresh <token> spotify refresh token
|
||||||
--remove-from-source Remove downloaded tracks from source playlist
|
--remove-from-source Remove downloaded tracks from source playlist
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
|
@ -243,10 +245,26 @@ Tip: For playlists containing music videos, it may be better to remove all text
|
||||||
|
|
||||||
### Spotify
|
### Spotify
|
||||||
A playlist/album url or 'spotify-likes': Download a spotify playlist, album, or your
|
A playlist/album url or 'spotify-likes': Download a spotify playlist, album, or your
|
||||||
liked songs. --spotify-id and --spotify-secret are required in addition when downloading
|
liked songs. Credentials are required when downloading a private playlist or liked music.
|
||||||
a private playlist or liked music.
|
|
||||||
The id and secret can be obtained at https://developer.spotify.com/dashboard/applications.
|
#### Using Credential/Application
|
||||||
Create an app and add http://localhost:48721/callback as a redirect url in its settings.
|
|
||||||
|
Create a [Spotify application](https://developer.spotify.com/dashboard/applications) with a redirect url of `http://localhost:48721/callback`. Obtain an application **ID** and **Secret** from the created application dashboard.
|
||||||
|
|
||||||
|
Start sldl with the obtained credentials and an authorized action to trigger the Spotify app login flow:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sldl spotify-likes --number 1 --spotify-id 123456 --spotify-secret 123456 ...
|
||||||
|
```
|
||||||
|
sldl will try to open a browser automatically but will fallback to logging the login flow URL to output. After login flow is complete sldl will output a **Token** and **Refresh Token** and finish running the current command.
|
||||||
|
|
||||||
|
To skip requiring login flow every time `sldl` is used the **Token** and **Refresh Token** can be provided to sldl (hint: use `--config` and store this info in the config file to make commands less verbose):
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sldl spotify-likes --number 1 --spotify-id 123456 --spotify-secret 123456 --spotify-refresh 123456 --spotify-token 123456 ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`spotify-token` access is only valid for 1 hour. `spotify-refresh` will enable sldl to renew access every time it is run (and can be used without including `spotify-token`)
|
||||||
|
|
||||||
### Bandcamp
|
### Bandcamp
|
||||||
An bandcamp url: Download a single track, and album, or an artist's entire discography.
|
An bandcamp url: Download a single track, and album, or an artist's entire discography.
|
||||||
|
|
|
@ -30,6 +30,8 @@ static class Config
|
||||||
public static string defaultFolderName = "";
|
public static string defaultFolderName = "";
|
||||||
public static string spotifyId = "";
|
public static string spotifyId = "";
|
||||||
public static string spotifySecret = "";
|
public static string spotifySecret = "";
|
||||||
|
public static string spotifyToken = "";
|
||||||
|
public static string spotifyRefresh = "";
|
||||||
public static string ytKey = "";
|
public static string ytKey = "";
|
||||||
public static string username = "";
|
public static string username = "";
|
||||||
public static string password = "";
|
public static string password = "";
|
||||||
|
@ -642,6 +644,14 @@ static class Config
|
||||||
case "--spotify-secret":
|
case "--spotify-secret":
|
||||||
spotifySecret = args[++i];
|
spotifySecret = args[++i];
|
||||||
break;
|
break;
|
||||||
|
case "--stk":
|
||||||
|
case "--spotify-token":
|
||||||
|
spotifyToken = args[++i];
|
||||||
|
break;
|
||||||
|
case "--str":
|
||||||
|
case "--spotify-refresh":
|
||||||
|
spotifyRefresh = args[++i];
|
||||||
|
break;
|
||||||
case "--yk":
|
case "--yk":
|
||||||
case "--youtube-key":
|
case "--youtube-key":
|
||||||
ytKey = args[++i];
|
ytKey = args[++i];
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Swan;
|
||||||
|
|
||||||
using Data;
|
using Data;
|
||||||
using Enums;
|
using Enums;
|
||||||
|
using System.Security;
|
||||||
|
|
||||||
namespace Extractors
|
namespace Extractors
|
||||||
{
|
{
|
||||||
|
@ -34,15 +35,19 @@ namespace Extractors
|
||||||
Config.spotifyId = Console.ReadLine();
|
Config.spotifyId = Console.ReadLine();
|
||||||
Console.Write("Spotify client secret:");
|
Console.Write("Spotify client secret:");
|
||||||
Config.spotifySecret = Console.ReadLine();
|
Config.spotifySecret = Console.ReadLine();
|
||||||
|
Console.Write("Spotify token:");
|
||||||
|
Config.spotifyToken = Console.ReadLine();
|
||||||
|
Console.Write("Spotify refresh token:");
|
||||||
|
Config.spotifyRefresh = Console.ReadLine();
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needLogin && (Config.spotifyId.Length == 0 || Config.spotifySecret.Length == 0))
|
if (needLogin && Config.spotifyToken.Length == 0 && (Config.spotifyId.Length == 0 || Config.spotifySecret.Length == 0))
|
||||||
{
|
{
|
||||||
readSpotifyCreds();
|
readSpotifyCreds();
|
||||||
}
|
}
|
||||||
|
|
||||||
spotifyClient = new Spotify(Config.spotifyId, Config.spotifySecret);
|
spotifyClient = new Spotify(Config.spotifyId, Config.spotifySecret, Config.spotifyToken, Config.spotifyRefresh);
|
||||||
await spotifyClient.Authorize(needLogin, Config.removeTracksFromSource);
|
await spotifyClient.Authorize(needLogin, Config.removeTracksFromSource);
|
||||||
|
|
||||||
if (Config.input == "spotify-likes")
|
if (Config.input == "spotify-likes")
|
||||||
|
@ -132,6 +137,8 @@ namespace Extractors
|
||||||
private EmbedIOAuthServer _server;
|
private EmbedIOAuthServer _server;
|
||||||
private readonly string _clientId;
|
private readonly string _clientId;
|
||||||
private readonly string _clientSecret;
|
private readonly string _clientSecret;
|
||||||
|
private string _clientToken;
|
||||||
|
private string _clientRefreshToken;
|
||||||
private SpotifyClient? _client;
|
private SpotifyClient? _client;
|
||||||
private bool loggedIn = false;
|
private bool loggedIn = false;
|
||||||
|
|
||||||
|
@ -140,12 +147,14 @@ namespace Extractors
|
||||||
public const string encodedSpotifySecret = "Y2JlM2QxYTE5MzJkNDQ2MmFiOGUy3shTuf4Y2JhY2M3ZDdjYWU=";
|
public const string encodedSpotifySecret = "Y2JlM2QxYTE5MzJkNDQ2MmFiOGUy3shTuf4Y2JhY2M3ZDdjYWU=";
|
||||||
public bool UsedDefaultCredentials { get; private set; }
|
public bool UsedDefaultCredentials { get; private set; }
|
||||||
|
|
||||||
public Spotify(string clientId = "", string clientSecret = "")
|
public Spotify(string clientId = "", string clientSecret = "", string token = "", string refreshToken = "")
|
||||||
{
|
{
|
||||||
_clientId = clientId;
|
_clientId = clientId;
|
||||||
_clientSecret = clientSecret;
|
_clientSecret = clientSecret;
|
||||||
|
_clientToken = token;
|
||||||
|
_clientRefreshToken = refreshToken;
|
||||||
|
|
||||||
if (_clientId.Length == 0 || _clientSecret.Length == 0)
|
if (_clientToken.Length == 0 && (_clientId.Length == 0 || _clientSecret.Length == 0))
|
||||||
{
|
{
|
||||||
_clientId = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encodedSpotifyId.Replace("1bLaH9", "")));
|
_clientId = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encodedSpotifyId.Replace("1bLaH9", "")));
|
||||||
_clientSecret = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encodedSpotifySecret.Replace("3shTuf4", "")));
|
_clientSecret = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encodedSpotifySecret.Replace("3shTuf4", "")));
|
||||||
|
@ -172,6 +181,15 @@ namespace Extractors
|
||||||
_server = new EmbedIOAuthServer(new Uri("http://localhost:48721/callback"), 48721);
|
_server = new EmbedIOAuthServer(new Uri("http://localhost:48721/callback"), 48721);
|
||||||
await _server.Start();
|
await _server.Start();
|
||||||
|
|
||||||
|
var existingOk = false;
|
||||||
|
if (_clientToken.Length != 0 || _clientRefreshToken.Length != 0)
|
||||||
|
{
|
||||||
|
existingOk = await this.TryExistingToken();
|
||||||
|
loggedIn = true;
|
||||||
|
//new OAuthClient(config).RequestToken()
|
||||||
|
}
|
||||||
|
if (!existingOk)
|
||||||
|
{
|
||||||
_server.AuthorizationCodeReceived += OnAuthorizationCodeReceived;
|
_server.AuthorizationCodeReceived += OnAuthorizationCodeReceived;
|
||||||
_server.ErrorReceived += OnErrorReceived;
|
_server.ErrorReceived += OnErrorReceived;
|
||||||
|
|
||||||
|
@ -187,12 +205,67 @@ namespace Extractors
|
||||||
|
|
||||||
var request = new LoginRequest(_server.BaseUri, _clientId, LoginRequest.ResponseType.Code) { Scope = scope };
|
var request = new LoginRequest(_server.BaseUri, _clientId, LoginRequest.ResponseType.Code) { Scope = scope };
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
BrowserUtil.Open(request.ToUri());
|
BrowserUtil.Open(request.ToUri());
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Unable to open URL, manually open: {0}", request.ToUri());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await IsClientReady();
|
await IsClientReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> TryExistingToken()
|
||||||
|
{
|
||||||
|
if (_clientToken.Length != 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Testing Spotify access with existing token...");
|
||||||
|
var client = new SpotifyClient(_clientToken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var me = await client.UserProfile.Current();
|
||||||
|
Console.WriteLine("Spotify access is good!");
|
||||||
|
_client = client;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Could not make an API call with existing token: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_clientRefreshToken.Length != 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Trying to renew access with refresh token...");
|
||||||
|
// var refreshRequest = new TokenSwapRefreshRequest(
|
||||||
|
// new Uri("http://localhost:48721/refresh"),
|
||||||
|
// _clientRefreshToken
|
||||||
|
// );
|
||||||
|
var refreshRequest = new AuthorizationCodeRefreshRequest(_clientId, _clientSecret, _clientRefreshToken);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var oauthClient = new OAuthClient();
|
||||||
|
var refreshResponse = await oauthClient.RequestToken(refreshRequest);
|
||||||
|
Console.WriteLine($"We got a new refreshed access token from server: {refreshResponse.AccessToken}");
|
||||||
|
_clientToken = refreshResponse.AccessToken;
|
||||||
|
_client = new SpotifyClient(_clientToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Could not refresh access token with refresh token: {ex}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Console.WriteLine("No refresh token present, cannot refresh existing access");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Not possible to access Spotify API without login! Falling back to login flow...");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnAuthorizationCodeReceived(object sender, AuthorizationCodeResponse response)
|
private async Task OnAuthorizationCodeReceived(object sender, AuthorizationCodeResponse response)
|
||||||
{
|
{
|
||||||
await _server.Stop();
|
await _server.Stop();
|
||||||
|
@ -204,6 +277,11 @@ namespace Extractors
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Console.WriteLine("Spotify token: " + tokenResponse.AccessToken);
|
||||||
|
_clientToken = tokenResponse.AccessToken;
|
||||||
|
Console.WriteLine("Spotify refresh token: " + tokenResponse.RefreshToken);
|
||||||
|
_clientRefreshToken = tokenResponse.RefreshToken;
|
||||||
|
|
||||||
_client = new SpotifyClient(tokenResponse.AccessToken);
|
_client = new SpotifyClient(tokenResponse.AccessToken);
|
||||||
loggedIn = true;
|
loggedIn = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue