Skip to content

Commit

Permalink
[WIP] Adding Tidal API auth workflow.
Browse files Browse the repository at this point in the history
Added OVR Toolkit connection status to HomePage.
  • Loading branch information
Soapwood committed Dec 2, 2024
1 parent 52a2036 commit ef9b700
Show file tree
Hide file tree
Showing 12 changed files with 362 additions and 34 deletions.
10 changes: 4 additions & 6 deletions VXMusic/Connections/Spotify/SpotifyClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@
using SpotifyAPI.Web.Auth;
using VXMusic.Spotify.Authentication;
using VXMusic.Spotify.Model;

namespace VXMusic.Spotify;

using System.Net;
using System.Text;
using Newtonsoft.Json;

namespace VXMusic.Spotify;


public class SpotifyClientBuilder
{
private static readonly Lazy<Task<SpotifyClient>> _spotifyClient =
new Lazy<Task<SpotifyClient>>(() => CreateSpotifyClient());

public static Task<SpotifyClient> Instance => _spotifyClient.Value;

private readonly string _spotifyAccountEndpoint = "https://accounts.spotify.com/api/token";


public static async Task<SpotifyClient> CreateSpotifyClient()
{
await SpotifyAuthentication.GetSpotifyUserAuthentication();
Expand Down
90 changes: 90 additions & 0 deletions VXMusic/Connections/Tidal/Authentication/TidalOAuthClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Text;
using SpotifyAPI.Web;
using SpotifyAPI.Web.Http;
using VXMusic.Connections.Tidal;

namespace VXMusic.Tidal.Authentication;

public class TidalOAuthClient : APIClient, IOAuthClient
{
public Task<PKCETokenResponse> RequestToken(PKCETokenRequest request, CancellationToken cancel = default (CancellationToken)) => RequestToken(request, this.API, cancel);

public static Task<PKCETokenResponse> RequestToken(
PKCETokenRequest request,
IAPIConnector apiConnector,
CancellationToken cancel = default (CancellationToken))
{
TidalUtil.Ensure.ArgumentNotNull((object) request, nameof (request));
TidalUtil.Ensure.ArgumentNotNull((object) apiConnector, nameof (apiConnector));
List<KeyValuePair<string, string>> form = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("client_id", request.ClientId),
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", request.Code),
new KeyValuePair<string, string>("redirect_uri", request.RedirectUri.ToString()),
new KeyValuePair<string, string>("code_verifier", request.CodeVerifier)
};
return SendOAuthRequest<PKCETokenResponse>(apiConnector, form, (string) null, (string) null, cancel);
}

private static Task<T> SendOAuthRequest<T>(
IAPIConnector apiConnector,
List<KeyValuePair<string?, string?>> form,
string? clientId,
string? clientSecret,
CancellationToken cancel = default (CancellationToken))
{
// TODO Inject creds here
Dictionary<string, string> headers = BuildAuthHeader(clientId, clientSecret);
return apiConnector.Post<T>(TidalAuthentication.TidalAuthApiUrl, (IDictionary<string, string>) null, (object) new FormUrlEncodedContent((IEnumerable<KeyValuePair<string, string>>) form), headers, cancel);

Check failure on line 39 in VXMusic/Connections/Tidal/Authentication/TidalOAuthClient.cs

View workflow job for this annotation

GitHub Actions / build

The name 'TidalAuthentication' does not exist in the current context

Check failure on line 39 in VXMusic/Connections/Tidal/Authentication/TidalOAuthClient.cs

View workflow job for this annotation

GitHub Actions / build

The name 'TidalAuthentication' does not exist in the current context
}

private static Dictionary<string, string> BuildAuthHeader(string? clientId, string? clientSecret)
{
if (clientId == null || clientSecret == null)
return new Dictionary<string, string>();
return new Dictionary<string, string>()
{
{
"Authorization",
"Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(clientId + ":" + clientSecret))
}
};
}

public TidalOAuthClient(SpotifyClientConfig config)
: base((IAPIConnector) ValidateConfig(config))
{
}

private static APIConnector ValidateConfig(SpotifyClientConfig config)
{
TidalUtil.Ensure.ArgumentNotNull((object) config, nameof (config));
return new APIConnector(config.BaseAddress, config.Authenticator, config.JSONSerializer, config.HTTPClient, config.RetryHandler, config.HTTPLogger);
}

public Task<ClientCredentialsTokenResponse> RequestToken(ClientCredentialsRequest request, CancellationToken cancel = new CancellationToken())
{
throw new NotImplementedException();
}

public Task<AuthorizationCodeRefreshResponse> RequestToken(AuthorizationCodeRefreshRequest request, CancellationToken cancel = new CancellationToken())
{
throw new NotImplementedException();
}

public Task<AuthorizationCodeTokenResponse> RequestToken(AuthorizationCodeTokenRequest request, CancellationToken cancel = new CancellationToken())
{
throw new NotImplementedException();
}

public Task<AuthorizationCodeTokenResponse> RequestToken(TokenSwapTokenRequest request, CancellationToken cancel = new CancellationToken())
{
throw new NotImplementedException();
}

public Task<AuthorizationCodeRefreshResponse> RequestToken(TokenSwapRefreshRequest request, CancellationToken cancel = new CancellationToken())
{
throw new NotImplementedException();
}
}
6 changes: 6 additions & 0 deletions VXMusic/Connections/Tidal/Model/TidalClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace VXMusic.Tidal.Model;

public class TidalClient
{

}
7 changes: 7 additions & 0 deletions VXMusic/Connections/Tidal/Model/TidalScopes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace VXMusic.Tidal.Model;

public static class TidalScopes
{
public const string PlaylistRead = "playlists.read";
public const string PlaylistWrite = "playlists.write";
}
33 changes: 33 additions & 0 deletions VXMusic/Connections/Tidal/TidalUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace VXMusic.Connections.Tidal;

public class TidalUtil
{
internal static class Ensure
{
/// <summary>Checks an argument to ensure it isn't null.</summary>
/// <param name="value">The argument value to check</param>
/// <param name="name">The name of the argument</param>
public static void ArgumentNotNull(object value, string name)
{
if (value == null)
throw new ArgumentNullException(name);
}

/// <summary>
/// Checks an argument to ensure it isn't null or an empty string
/// </summary>
/// <param name="value">The argument value to check</param>
/// <param name="name">The name of the argument</param>
public static void ArgumentNotNullOrEmptyString(string value, string name)
{
if (string.IsNullOrEmpty(value))
throw new ArgumentException("String is empty or null", name);
}

public static void ArgumentNotNullOrEmptyList<T>(IEnumerable<T> value, string name)
{
if (value == null || !value.Any<T>())
throw new ArgumentException("List is empty or null", name);
}
}
}
6 changes: 6 additions & 0 deletions VXMusicDesktop/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ public App() : base()
ClientId = configuration["Connections:Spotify:ClientId"],
PlaylistSavingSaveSetting = VXUserSettings.Connections.GetPlaylistSaveSetting()
},

TidalSettings = new TidalSettings()
{
ClientId = configuration["Connections:Tidal:ClientId"],
PlaylistSavingSaveSetting = VXUserSettings.Connections.GetPlaylistSaveSetting()
},

LastfmSettings = new LastfmSettings()
{
Expand Down
104 changes: 101 additions & 3 deletions VXMusicDesktop/MVVM/View/ConnectionsView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
<UserControl.Resources>
<BitmapImage x:Key="SpotifyLogo"
UriSource="{Binding Source={x:Static custom:UIImageManager.SpotifyLogo}, Path=UriSource}" />

<BitmapImage x:Key="TidalLogo"
UriSource="{Binding Source={x:Static custom:UIImageManager.TidalLogo}, Path=UriSource}" />

<BitmapImage x:Key="LastfmLogo"
UriSource="{Binding Source={x:Static custom:UIImageManager.LastfmLogo}, Path=UriSource}" />
Expand All @@ -29,6 +32,8 @@

<SolidColorBrush x:Key="SpotifyGreen"
Color="{Binding Source={x:Static custom:ColourSchemeManager.SpotifyGreen}, Path=Color}" />
<SolidColorBrush x:Key="TidalBlack"
Color="{Binding Source={x:Static custom:ColourSchemeManager.TidalBlack}, Path=Color}" />
<SolidColorBrush x:Key="LastFmRed"
Color="{Binding Source={x:Static custom:ColourSchemeManager.LastFmRed}, Path=Color}" />

Expand Down Expand Up @@ -265,12 +270,105 @@
</StackPanel>
</Grid>
</Border>

</StackPanel>

<StackPanel Orientation="Horizontal"
Margin="0,0,0,10">
</StackPanel>
Margin="0,0,0,10">

<Border Width="300"
Height="200"
CornerRadius="10"
Margin="20,10,0,0">

<Border.Background>
<LinearGradientBrush x:Name="ConnectionsIntegration3BoxBorderGradientBrush" StartPoint="0,0"
EndPoint="1,2">
<GradientStop Offset="0.0">
<GradientStop.Color>
<Binding Source="{StaticResource SecondaryColor}"
Converter="{StaticResource SolidColorBrushToColorConverter}" />
</GradientStop.Color>
</GradientStop>

<GradientStop Offset="0.7">
<GradientStop.Color>
<Binding Source="{StaticResource Accent1Colour}"
Converter="{StaticResource SolidColorBrushToColorConverter}" />
</GradientStop.Color>
</GradientStop>
</LinearGradientBrush>
</Border.Background>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!-- First TextBlock -->
<RowDefinition Height="Auto" />
<!-- Second TextBlock -->
<RowDefinition Height="Auto" />
<!-- Second TextBlock -->
</Grid.RowDefinitions>

<StackPanel >
<Image Height="60"
Source="{StaticResource TidalLogo}"
RenderTransformOrigin="0,0"
RenderOptions.BitmapScalingMode="HighQuality"
Margin="0,20,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center" />

<TextBlock Grid.Row="1"
Name="ConnectionService3TextHeader"
Text="Link VXMusic to Tidal"
Foreground="{StaticResource TextBasic}"
FontSize="18"
Margin="0,10,0,0"
HorizontalAlignment="Center"
VerticalAlignment="Center" />

<TextBlock Name="ConnectionService3TextParagraph"
Text="Automatically add recognised songs to playlists!"
Foreground="{StaticResource TextBasic}"
FontSize="12"
Margin="0,0,0,0"
HorizontalAlignment="Center" />


<TextBlock Name="ConnectionService3TextParagraph2"
Text="Coming Soon"
Foreground="{StaticResource TextBasic}"
FontSize="16"
Margin="0,20,0,0"
HorizontalAlignment="Center" />

<!-- <Button Grid.Row="2" x:Name="TidalLoginButton" -->
<!-- Content="{Binding TidalLinkButtonText}" -->
<!-- Command="{Binding LinkTidalButtonClick}" -->
<!-- Cursor="Hand" -->
<!-- Padding="0" -->
<!-- Margin="0,15,0,0" -->
<!-- Background="{StaticResource TidalBlack}" -->
<!-- Foreground="White" -->
<!-- HorizontalAlignment="Center" -->
<!-- VerticalAlignment="Center" -->
<!-- Width="150" Height="40"> -->
<!-- <Button.Resources> -->
<!-- <Style TargetType="Border"> -->
<!-- <Setter Property="CornerRadius" Value="5" /> -->
<!-- -->
<!-- <Style.Triggers> -->
<!-- <Trigger Property="IsEnabled" Value="False"> -->
<!-- <Setter Property="Background" Value="Black" /> -->
<!-- </Trigger> -->
<!-- </Style.Triggers> -->
<!-- </Style> -->
<!-- </Button.Resources> -->
<!-- </Button> -->
</StackPanel>
</Grid>
</Border>
</StackPanel>
</StackPanel>

</UserControl>
Loading

0 comments on commit ef9b700

Please sign in to comment.