✨ Spotify OAuth & Presence
This commit is contained in:
209
DysonNetwork.Pass/Account/Presences/SpotifyPresenceService.cs
Normal file
209
DysonNetwork.Pass/Account/Presences/SpotifyPresenceService.cs
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using DysonNetwork.Shared.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Account.Presences;
|
||||||
|
|
||||||
|
public class SpotifyPresenceService(
|
||||||
|
AppDatabase db,
|
||||||
|
Auth.OpenId.SpotifyOidcService spotifyService,
|
||||||
|
AccountEventService accountEventService,
|
||||||
|
ILogger<SpotifyPresenceService> logger
|
||||||
|
)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Updates presence activities for users who have Spotify connections and are currently playing music
|
||||||
|
/// </summary>
|
||||||
|
public async Task UpdateAllSpotifyPresencesAsync()
|
||||||
|
{
|
||||||
|
var userConnections = await db.AccountConnections
|
||||||
|
.Where(c => c.Provider == "spotify" && c.AccessToken != null && c.RefreshToken != null)
|
||||||
|
.Include(c => c.Account)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var connection in userConnections)
|
||||||
|
{
|
||||||
|
await UpdateSpotifyPresenceAsync(connection.Account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the Spotify presence activity for a specific user
|
||||||
|
/// </summary>
|
||||||
|
public async Task UpdateSpotifyPresenceAsync(SnAccount account)
|
||||||
|
{
|
||||||
|
var connection = await db.AccountConnections
|
||||||
|
.FirstOrDefaultAsync(c => c.AccountId == account.Id && c.Provider == "spotify");
|
||||||
|
|
||||||
|
if (connection?.RefreshToken == null)
|
||||||
|
{
|
||||||
|
// No Spotify connection, remove any existing Spotify presence
|
||||||
|
await RemoveSpotifyPresenceAsync(account.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var currentlyPlayingJson = await spotifyService.GetCurrentlyPlayingAsync(
|
||||||
|
connection.RefreshToken,
|
||||||
|
connection.AccessToken
|
||||||
|
);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(currentlyPlayingJson) || currentlyPlayingJson == "{}")
|
||||||
|
{
|
||||||
|
// Nothing playing, remove the presence
|
||||||
|
await RemoveSpotifyPresenceAsync(account.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var presenceActivity = await ParseAndCreatePresenceActivityAsync(account.Id, currentlyPlayingJson);
|
||||||
|
|
||||||
|
// Update or create the presence activity
|
||||||
|
await accountEventService.UpdateActivityByManualId(
|
||||||
|
"spotify",
|
||||||
|
account.Id,
|
||||||
|
activity =>
|
||||||
|
{
|
||||||
|
activity.Type = PresenceType.Music;
|
||||||
|
activity.Title = presenceActivity.Title;
|
||||||
|
activity.Subtitle = presenceActivity.Subtitle;
|
||||||
|
activity.Caption = presenceActivity.Caption;
|
||||||
|
activity.LargeImage = presenceActivity.LargeImage;
|
||||||
|
activity.SmallImage = presenceActivity.SmallImage;
|
||||||
|
activity.TitleUrl = presenceActivity.TitleUrl;
|
||||||
|
activity.SubtitleUrl = presenceActivity.SubtitleUrl;
|
||||||
|
activity.Meta = presenceActivity.Meta;
|
||||||
|
},
|
||||||
|
10 // 10 minute lease
|
||||||
|
);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// On error, remove the presence to avoid stale data
|
||||||
|
await RemoveSpotifyPresenceAsync(account.Id);
|
||||||
|
|
||||||
|
// In a real implementation, you might want to log the error
|
||||||
|
logger.LogError(ex, "Failed to update Spotify presence for user {UserId}", account.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the Spotify presence activity for a user
|
||||||
|
/// </summary>
|
||||||
|
private async Task RemoveSpotifyPresenceAsync(Guid accountId)
|
||||||
|
{
|
||||||
|
await accountEventService.UpdateActivityByManualId(
|
||||||
|
"spotify",
|
||||||
|
accountId,
|
||||||
|
activity =>
|
||||||
|
{
|
||||||
|
// Mark it for immediate expiration
|
||||||
|
activity.LeaseExpiresAt = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<SnPresenceActivity> ParseAndCreatePresenceActivityAsync(Guid accountId, string currentlyPlayingJson)
|
||||||
|
{
|
||||||
|
var document = JsonDocument.Parse(currentlyPlayingJson);
|
||||||
|
var root = document.RootElement;
|
||||||
|
|
||||||
|
// Extract track information
|
||||||
|
var item = root.GetProperty("item");
|
||||||
|
var trackName = item.GetProperty("name").GetString() ?? "";
|
||||||
|
var isPlaying = root.GetProperty("is_playing").GetBoolean();
|
||||||
|
|
||||||
|
// Only create presence if actually playing
|
||||||
|
if (!isPlaying)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Track is not currently playing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get artists
|
||||||
|
var artists = item.GetProperty("artists");
|
||||||
|
var artistNames = artists.EnumerateArray()
|
||||||
|
.Select(a => a.GetProperty("name").GetString() ?? "")
|
||||||
|
.Where(name => !string.IsNullOrEmpty(name))
|
||||||
|
.ToArray();
|
||||||
|
var artistsString = string.Join(", ", artistNames);
|
||||||
|
|
||||||
|
// Get album
|
||||||
|
var album = item.GetProperty("album");
|
||||||
|
var albumName = album.GetProperty("name").GetString() ?? "";
|
||||||
|
|
||||||
|
// Get album images (artwork)
|
||||||
|
string? albumImageUrl = null;
|
||||||
|
if (album.TryGetProperty("images", out var images) && images.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
var albumImages = images.EnumerateArray().ToList();
|
||||||
|
// Take the largest image (usually last in the array)
|
||||||
|
if (albumImages.Count > 0)
|
||||||
|
{
|
||||||
|
albumImageUrl = albumImages[0].GetProperty("url").GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get external URLs
|
||||||
|
var externalUrls = item.GetProperty("external_urls");
|
||||||
|
var trackUrl = externalUrls.GetProperty("spotify").GetString();
|
||||||
|
|
||||||
|
var artistsUrls = new List<string>();
|
||||||
|
foreach (var artist in artists.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (artist.TryGetProperty("external_urls", out var artistUrls))
|
||||||
|
{
|
||||||
|
var spotifyUrl = artistUrls.GetProperty("spotify").GetString();
|
||||||
|
if (!string.IsNullOrEmpty(spotifyUrl))
|
||||||
|
{
|
||||||
|
artistsUrls.Add(spotifyUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get progress and duration for metadata
|
||||||
|
var progressMs = root.GetProperty("progress_ms").GetInt32();
|
||||||
|
var durationMs = item.GetProperty("duration_ms").GetInt32();
|
||||||
|
|
||||||
|
// Calculate progress percentage
|
||||||
|
var progressPercent = durationMs > 0 ? (double)progressMs / durationMs * 100 : 0;
|
||||||
|
|
||||||
|
// Get context info (playlist, album, etc.)
|
||||||
|
string? contextType = null;
|
||||||
|
string? contextUrl = null;
|
||||||
|
if (root.TryGetProperty("context", out var context))
|
||||||
|
{
|
||||||
|
contextType = context.GetProperty("type").GetString();
|
||||||
|
var contextExternalUrls = context.GetProperty("external_urls");
|
||||||
|
contextUrl = contextExternalUrls.GetProperty("spotify").GetString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SnPresenceActivity
|
||||||
|
{
|
||||||
|
AccountId = accountId,
|
||||||
|
Type = PresenceType.Music,
|
||||||
|
ManualId = "spotify",
|
||||||
|
Title = trackName,
|
||||||
|
Subtitle = artistsString,
|
||||||
|
Caption = albumName,
|
||||||
|
LargeImage = albumImageUrl,
|
||||||
|
TitleUrl = trackUrl,
|
||||||
|
SubtitleUrl = artistsUrls.FirstOrDefault(),
|
||||||
|
Meta = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["track_duration_ms"] = durationMs,
|
||||||
|
["progress_ms"] = progressMs,
|
||||||
|
["progress_percent"] = progressPercent,
|
||||||
|
["track_id"] = item.GetProperty("id").GetString() ?? "",
|
||||||
|
["album_id"] = album.GetProperty("id").GetString() ?? "",
|
||||||
|
["artist_ids"] = artists.EnumerateArray().Select(a => a.GetProperty("id").GetString() ?? "").ToArray(),
|
||||||
|
["context_type"] = contextType,
|
||||||
|
["context_url"] = contextUrl,
|
||||||
|
["is_explicit"] = item.GetProperty("explicit").GetBoolean(),
|
||||||
|
["popularity"] = item.GetProperty("popularity").GetInt32(),
|
||||||
|
["spotify_track_url"] = trackUrl,
|
||||||
|
["updated_at"] = SystemClock.Instance.GetCurrentInstant()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Quartz;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Account.Presences;
|
||||||
|
|
||||||
|
public class SpotifyPresenceUpdateJob(SpotifyPresenceService spotifyPresenceService, ILogger<SpotifyPresenceUpdateJob> logger) : IJob
|
||||||
|
{
|
||||||
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Starting Spotify presence updates...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await spotifyPresenceService.UpdateAllSpotifyPresencesAsync();
|
||||||
|
logger.LogInformation("Spotify presence updates completed successfully.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error occurred during Spotify presence updates.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -141,4 +141,4 @@ public class MicrosoftOidcService(
|
|||||||
Provider = ProviderName
|
Provider = ProviderName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,6 +123,7 @@ public class OidcController(
|
|||||||
"microsoft" => serviceProvider.GetRequiredService<MicrosoftOidcService>(),
|
"microsoft" => serviceProvider.GetRequiredService<MicrosoftOidcService>(),
|
||||||
"discord" => serviceProvider.GetRequiredService<DiscordOidcService>(),
|
"discord" => serviceProvider.GetRequiredService<DiscordOidcService>(),
|
||||||
"github" => serviceProvider.GetRequiredService<GitHubOidcService>(),
|
"github" => serviceProvider.GetRequiredService<GitHubOidcService>(),
|
||||||
|
"spotify" => serviceProvider.GetRequiredService<SpotifyOidcService>(),
|
||||||
"afdian" => serviceProvider.GetRequiredService<AfdianOidcService>(),
|
"afdian" => serviceProvider.GetRequiredService<AfdianOidcService>(),
|
||||||
_ => throw new ArgumentException($"Unsupported provider: {provider}")
|
_ => throw new ArgumentException($"Unsupported provider: {provider}")
|
||||||
};
|
};
|
||||||
|
|||||||
255
DysonNetwork.Pass/Auth/OpenId/SpotifyOidcService.cs
Normal file
255
DysonNetwork.Pass/Auth/OpenId/SpotifyOidcService.cs
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using DysonNetwork.Shared.Cache;
|
||||||
|
|
||||||
|
namespace DysonNetwork.Pass.Auth.OpenId;
|
||||||
|
|
||||||
|
public class SpotifyOidcService(
|
||||||
|
IConfiguration configuration,
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
AppDatabase db,
|
||||||
|
AuthService auth,
|
||||||
|
ICacheService cache
|
||||||
|
)
|
||||||
|
: OidcService(configuration, httpClientFactory, db, auth, cache)
|
||||||
|
{
|
||||||
|
public override string ProviderName => "Spotify";
|
||||||
|
protected override string DiscoveryEndpoint => ""; // Spotify doesn't have a standard OIDC discovery endpoint
|
||||||
|
protected override string ConfigSectionName => "Spotify";
|
||||||
|
|
||||||
|
public override Task<string> GetAuthorizationUrlAsync(string state, string nonce)
|
||||||
|
{
|
||||||
|
return Task.FromResult(GetAuthorizationUrl(state, nonce));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string GetAuthorizationUrl(string state, string nonce)
|
||||||
|
{
|
||||||
|
var config = GetProviderConfig();
|
||||||
|
var queryParams = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "client_id", config.ClientId },
|
||||||
|
{ "redirect_uri", config.RedirectUri },
|
||||||
|
{ "response_type", "code" },
|
||||||
|
{ "scope", "user-read-private user-read-current-playing user-read-email" },
|
||||||
|
{ "state", state },
|
||||||
|
};
|
||||||
|
|
||||||
|
var queryString = string.Join("&", queryParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
|
||||||
|
return $"https://accounts.spotify.com/authorize?{queryString}";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task<OidcDiscoveryDocument?> GetDiscoveryDocumentAsync()
|
||||||
|
{
|
||||||
|
return Task.FromResult(new OidcDiscoveryDocument
|
||||||
|
{
|
||||||
|
AuthorizationEndpoint = "https://accounts.spotify.com/authorize",
|
||||||
|
TokenEndpoint = "https://accounts.spotify.com/api/token",
|
||||||
|
UserinfoEndpoint = "https://api.spotify.com/v1/me",
|
||||||
|
JwksUri = null
|
||||||
|
})!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<OidcUserInfo> ProcessCallbackAsync(OidcCallbackData callbackData)
|
||||||
|
{
|
||||||
|
var tokenResponse = await ExchangeCodeForTokensAsync(callbackData.Code);
|
||||||
|
if (tokenResponse?.AccessToken == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to obtain access token from Spotify");
|
||||||
|
}
|
||||||
|
|
||||||
|
var userInfo = await GetUserInfoAsync(tokenResponse.AccessToken);
|
||||||
|
|
||||||
|
userInfo.AccessToken = tokenResponse.AccessToken;
|
||||||
|
userInfo.RefreshToken = tokenResponse.RefreshToken;
|
||||||
|
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task<OidcTokenResponse?> ExchangeCodeForTokensAsync(string code,
|
||||||
|
string? codeVerifier = null)
|
||||||
|
{
|
||||||
|
var config = GetProviderConfig();
|
||||||
|
var client = HttpClientFactory.CreateClient();
|
||||||
|
|
||||||
|
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "client_id", config.ClientId },
|
||||||
|
{ "client_secret", config.ClientSecret },
|
||||||
|
{ "grant_type", "authorization_code" },
|
||||||
|
{ "code", code },
|
||||||
|
{ "redirect_uri", config.RedirectUri },
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await client.PostAsync("https://accounts.spotify.com/api/token", content);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
return await response.Content.ReadFromJsonAsync<OidcTokenResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes an access token using the refresh token
|
||||||
|
/// </summary>
|
||||||
|
public async Task<OidcTokenResponse?> RefreshTokenAsync(string refreshToken)
|
||||||
|
{
|
||||||
|
var config = GetProviderConfig();
|
||||||
|
var client = HttpClientFactory.CreateClient();
|
||||||
|
|
||||||
|
var content = new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "client_id", config.ClientId },
|
||||||
|
{ "client_secret", config.ClientSecret },
|
||||||
|
{ "grant_type", "refresh_token" },
|
||||||
|
{ "refresh_token", refreshToken },
|
||||||
|
});
|
||||||
|
|
||||||
|
var response = await client.PostAsync("https://accounts.spotify.com/api/token", content);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return null; // Refresh failed
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.Content.ReadFromJsonAsync<OidcTokenResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a valid access token, refreshing if necessary
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string?> GetValidAccessTokenAsync(string refreshToken, string? currentAccessToken = null)
|
||||||
|
{
|
||||||
|
// If we don't have a current token, we need to refresh
|
||||||
|
if (string.IsNullOrEmpty(currentAccessToken))
|
||||||
|
{
|
||||||
|
var refreshedTokens = await RefreshTokenAsync(refreshToken);
|
||||||
|
return refreshedTokens?.AccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test if the current token is still valid by making a lightweight API call
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var client = HttpClientFactory.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.spotify.com/v1/me");
|
||||||
|
request.Headers.Add("Authorization", $"Bearer {currentAccessToken}");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
// Token is still valid
|
||||||
|
return currentAccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If unauthorized, try to refresh
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var refreshedTokens = await RefreshTokenAsync(refreshToken);
|
||||||
|
return refreshedTokens?.AccessToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// On any error, try to refresh
|
||||||
|
var refreshedTokens = await RefreshTokenAsync(refreshToken);
|
||||||
|
return refreshedTokens?.AccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current token is invalid and refresh failed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<OidcUserInfo> GetUserInfoAsync(string accessToken)
|
||||||
|
{
|
||||||
|
var client = HttpClientFactory.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.spotify.com/v1/me");
|
||||||
|
request.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var json = await response.Content.ReadAsStringAsync();
|
||||||
|
var spotifyUser = JsonDocument.Parse(json).RootElement;
|
||||||
|
|
||||||
|
var userId = spotifyUser.GetProperty("id").GetString() ?? "";
|
||||||
|
var email = spotifyUser.TryGetProperty("email", out var emailElement) ? emailElement.GetString() : null;
|
||||||
|
|
||||||
|
// Get display name - prefer display_name, then fallback to id
|
||||||
|
var displayName = spotifyUser.TryGetProperty("display_name", out var displayNameElement)
|
||||||
|
? displayNameElement.GetString() ?? ""
|
||||||
|
: userId;
|
||||||
|
|
||||||
|
// Get profile picture - take the first image URL if available
|
||||||
|
string? profilePictureUrl = null;
|
||||||
|
if (spotifyUser.TryGetProperty("images", out var imagesElement) && imagesElement.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
var images = imagesElement.EnumerateArray().ToList();
|
||||||
|
if (images.Count > 0)
|
||||||
|
{
|
||||||
|
profilePictureUrl = images[0].TryGetProperty("url", out var urlElement)
|
||||||
|
? urlElement.GetString()
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OidcUserInfo
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Email = email,
|
||||||
|
DisplayName = displayName,
|
||||||
|
PreferredUsername = userId, // Spotify doesn't have a separate username field like some platforms
|
||||||
|
ProfilePictureUrl = profilePictureUrl,
|
||||||
|
Provider = ProviderName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the user's currently playing track
|
||||||
|
/// </summary>
|
||||||
|
public async Task<string?> GetCurrentlyPlayingAsync(string refreshToken, string? currentAccessToken = null)
|
||||||
|
{
|
||||||
|
var validToken = await GetValidAccessTokenAsync(refreshToken, currentAccessToken);
|
||||||
|
if (string.IsNullOrEmpty(validToken))
|
||||||
|
{
|
||||||
|
return null; // Couldn't get a valid token
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = HttpClientFactory.CreateClient();
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.spotify.com/v1/me/player/currently-playing");
|
||||||
|
request.Headers.Add("Authorization", $"Bearer {validToken}");
|
||||||
|
|
||||||
|
var response = await client.SendAsync(request);
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
|
||||||
|
{
|
||||||
|
// 204 No Content means nothing is currently playing
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try one more time with a fresh token if it failed with unauthorized
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
var freshToken = await RefreshTokenAsync(refreshToken);
|
||||||
|
if (freshToken?.AccessToken != null)
|
||||||
|
{
|
||||||
|
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", freshToken.AccessToken);
|
||||||
|
response = await client.SendAsync(request);
|
||||||
|
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
|
||||||
|
{
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.Content.ReadAsStringAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using DysonNetwork.Pass.Account;
|
||||||
|
using DysonNetwork.Pass.Account.Presences;
|
||||||
using DysonNetwork.Pass.Credit;
|
using DysonNetwork.Pass.Credit;
|
||||||
using DysonNetwork.Pass.Handlers;
|
using DysonNetwork.Pass.Handlers;
|
||||||
using DysonNetwork.Pass.Wallet;
|
using DysonNetwork.Pass.Wallet;
|
||||||
@@ -81,6 +83,16 @@ public static class ScheduledJobsConfiguration
|
|||||||
.ForJob(socialCreditValidationJob)
|
.ForJob(socialCreditValidationJob)
|
||||||
.WithIdentity("SocialCreditValidationTrigger")
|
.WithIdentity("SocialCreditValidationTrigger")
|
||||||
.WithCronSchedule("0 0 0 * * ?"));
|
.WithCronSchedule("0 0 0 * * ?"));
|
||||||
|
|
||||||
|
var spotifyPresenceUpdateJob = new JobKey("SpotifyPresenceUpdate");
|
||||||
|
q.AddJob<SpotifyPresenceUpdateJob>(opts => opts.WithIdentity(spotifyPresenceUpdateJob));
|
||||||
|
q.AddTrigger(opts => opts
|
||||||
|
.ForJob(spotifyPresenceUpdateJob)
|
||||||
|
.WithIdentity("SpotifyPresenceUpdateTrigger")
|
||||||
|
.WithSimpleSchedule(o => o
|
||||||
|
.WithIntervalInMinutes(2)
|
||||||
|
.RepeatForever())
|
||||||
|
);
|
||||||
});
|
});
|
||||||
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
||||||
|
|
||||||
|
|||||||
@@ -57,12 +57,14 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<OidcService, MicrosoftOidcService>();
|
services.AddScoped<OidcService, MicrosoftOidcService>();
|
||||||
services.AddScoped<OidcService, DiscordOidcService>();
|
services.AddScoped<OidcService, DiscordOidcService>();
|
||||||
services.AddScoped<OidcService, AfdianOidcService>();
|
services.AddScoped<OidcService, AfdianOidcService>();
|
||||||
|
services.AddScoped<OidcService, SpotifyOidcService>();
|
||||||
services.AddScoped<GoogleOidcService>();
|
services.AddScoped<GoogleOidcService>();
|
||||||
services.AddScoped<AppleOidcService>();
|
services.AddScoped<AppleOidcService>();
|
||||||
services.AddScoped<GitHubOidcService>();
|
services.AddScoped<GitHubOidcService>();
|
||||||
services.AddScoped<MicrosoftOidcService>();
|
services.AddScoped<MicrosoftOidcService>();
|
||||||
services.AddScoped<DiscordOidcService>();
|
services.AddScoped<DiscordOidcService>();
|
||||||
services.AddScoped<AfdianOidcService>();
|
services.AddScoped<AfdianOidcService>();
|
||||||
|
services.AddScoped<SpotifyOidcService>();
|
||||||
|
|
||||||
services.AddControllers().AddJsonOptions(options =>
|
services.AddControllers().AddJsonOptions(options =>
|
||||||
{
|
{
|
||||||
@@ -142,6 +144,7 @@ public static class ServiceCollectionExtensions
|
|||||||
services.AddScoped<ActionLogService>();
|
services.AddScoped<ActionLogService>();
|
||||||
services.AddScoped<RelationshipService>();
|
services.AddScoped<RelationshipService>();
|
||||||
services.AddScoped<MagicSpellService>();
|
services.AddScoped<MagicSpellService>();
|
||||||
|
services.AddScoped<DysonNetwork.Pass.Account.Presences.SpotifyPresenceService>();
|
||||||
services.AddScoped<AuthService>();
|
services.AddScoped<AuthService>();
|
||||||
services.AddScoped<TokenAuthService>();
|
services.AddScoped<TokenAuthService>();
|
||||||
services.AddScoped<AccountUsernameService>();
|
services.AddScoped<AccountUsernameService>();
|
||||||
|
|||||||
Reference in New Issue
Block a user