1
0
Fork 0
mirror of https://github.com/fiso64/slsk-batchdl.git synced 2024-12-22 14:32:40 +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:
FoxxMD 2024-08-27 14:50:25 -04:00
parent 6e7b8d5d67
commit 5357911054
3 changed files with 125 additions and 19 deletions

View file

@ -126,6 +126,8 @@ Usage: sldl <input> [OPTIONS]
Spotify
--spotify-id <id> spotify client ID
--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
```
```
@ -243,10 +245,26 @@ Tip: For playlists containing music videos, it may be better to remove all text
### Spotify
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
a private playlist or liked music.
The id and secret can be obtained at https://developer.spotify.com/dashboard/applications.
Create an app and add http://localhost:48721/callback as a redirect url in its settings.
liked songs. Credentials are required when downloading a private playlist or liked music.
#### Using Credential/Application
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
An bandcamp url: Download a single track, and album, or an artist's entire discography.

View file

@ -30,6 +30,8 @@ static class Config
public static string defaultFolderName = "";
public static string spotifyId = "";
public static string spotifySecret = "";
public static string spotifyToken = "";
public static string spotifyRefresh = "";
public static string ytKey = "";
public static string username = "";
public static string password = "";
@ -642,6 +644,14 @@ static class Config
case "--spotify-secret":
spotifySecret = args[++i];
break;
case "--stk":
case "--spotify-token":
spotifyToken = args[++i];
break;
case "--str":
case "--spotify-refresh":
spotifyRefresh = args[++i];
break;
case "--yk":
case "--youtube-key":
ytKey = args[++i];

View file

@ -4,6 +4,7 @@ using Swan;
using Data;
using Enums;
using System.Security;
namespace Extractors
{
@ -34,15 +35,19 @@ namespace Extractors
Config.spotifyId = Console.ReadLine();
Console.Write("Spotify client secret:");
Config.spotifySecret = Console.ReadLine();
Console.Write("Spotify token:");
Config.spotifyToken = Console.ReadLine();
Console.Write("Spotify refresh token:");
Config.spotifyRefresh = Console.ReadLine();
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();
}
spotifyClient = new Spotify(Config.spotifyId, Config.spotifySecret);
spotifyClient = new Spotify(Config.spotifyId, Config.spotifySecret, Config.spotifyToken, Config.spotifyRefresh);
await spotifyClient.Authorize(needLogin, Config.removeTracksFromSource);
if (Config.input == "spotify-likes")
@ -132,6 +137,8 @@ namespace Extractors
private EmbedIOAuthServer _server;
private readonly string _clientId;
private readonly string _clientSecret;
private string _clientToken;
private string _clientRefreshToken;
private SpotifyClient? _client;
private bool loggedIn = false;
@ -140,12 +147,14 @@ namespace Extractors
public const string encodedSpotifySecret = "Y2JlM2QxYTE5MzJkNDQ2MmFiOGUy3shTuf4Y2JhY2M3ZDdjYWU=";
public bool UsedDefaultCredentials { get; private set; }
public Spotify(string clientId = "", string clientSecret = "")
public Spotify(string clientId = "", string clientSecret = "", string token = "", string refreshToken = "")
{
_clientId = clientId;
_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", "")));
_clientSecret = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encodedSpotifySecret.Replace("3shTuf4", "")));
@ -172,27 +181,91 @@ namespace Extractors
_server = new EmbedIOAuthServer(new Uri("http://localhost:48721/callback"), 48721);
await _server.Start();
_server.AuthorizationCodeReceived += OnAuthorizationCodeReceived;
_server.ErrorReceived += OnErrorReceived;
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.ErrorReceived += OnErrorReceived;
var scope = new List<string> {
var scope = new List<string> {
Scopes.UserLibraryRead, Scopes.PlaylistReadPrivate, Scopes.PlaylistReadCollaborative
};
if (needModify)
{
scope.Add(Scopes.PlaylistModifyPublic);
scope.Add(Scopes.PlaylistModifyPrivate);
if (needModify)
{
scope.Add(Scopes.PlaylistModifyPublic);
scope.Add(Scopes.PlaylistModifyPrivate);
}
var request = new LoginRequest(_server.BaseUri, _clientId, LoginRequest.ResponseType.Code) { Scope = scope };
try
{
BrowserUtil.Open(request.ToUri());
}
catch (Exception)
{
Console.WriteLine("Unable to open URL, manually open: {0}", request.ToUri());
}
}
var request = new LoginRequest(_server.BaseUri, _clientId, LoginRequest.ResponseType.Code) { Scope = scope };
BrowserUtil.Open(request.ToUri());
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)
{
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);
loggedIn = true;
}