MVP completed: Hook into Playlistmanager to post updates to beatleader maps owned by the user
This commit is contained in:
parent
f9a0e1669f
commit
7879e03cc5
@ -62,6 +62,37 @@ namespace Setlist
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Reads sync URL, BeatLeader guid, and owner from playlist customData (same rules as startup scan).</summary>
|
||||||
|
internal static void TryReadBeatLeaderMetadata(
|
||||||
|
IPlaylist playlist,
|
||||||
|
out bool hasSyncUrl,
|
||||||
|
out string beatLeaderGuid,
|
||||||
|
out string ownerId)
|
||||||
|
{
|
||||||
|
hasSyncUrl = false;
|
||||||
|
beatLeaderGuid = null;
|
||||||
|
ownerId = null;
|
||||||
|
|
||||||
|
if (!playlist.TryGetCustomData("syncURL", out var syncObj) || !(syncObj is string syncUrl)
|
||||||
|
|| string.IsNullOrWhiteSpace(syncUrl))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSyncUrl = true;
|
||||||
|
|
||||||
|
if (playlist.TryGetCustomData("owner", out var ownerObj) && ownerObj is string o
|
||||||
|
&& !string.IsNullOrWhiteSpace(o))
|
||||||
|
{
|
||||||
|
ownerId = o.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryExtractBeatLeaderPlaylistGuid(syncUrl, out var g))
|
||||||
|
{
|
||||||
|
beatLeaderGuid = g;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spawns a hidden coroutine runner that resolves the platform user id, then logs per-playlist
|
/// Spawns a hidden coroutine runner that resolves the platform user id, then logs per-playlist
|
||||||
/// ownership from playlist JSON. Must be called from the Unity main thread (BSIPA <c>OnApplicationStart</c> is fine).
|
/// ownership from playlist JSON. Must be called from the Unity main thread (BSIPA <c>OnApplicationStart</c> is fine).
|
||||||
@ -77,6 +108,108 @@ namespace Setlist
|
|||||||
runner.Initialize(entries, log);
|
runner.Initialize(entries, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static IEnumerator FetchPlatformUserIdCoroutine(Action<string> onDone)
|
||||||
|
{
|
||||||
|
var waited = 0f;
|
||||||
|
var lastGetUserInfoAttempt = -PlatformUserGetUserInfoRetrySeconds;
|
||||||
|
while (waited < PlatformUserWaitTimeoutSeconds)
|
||||||
|
{
|
||||||
|
foreach (var plm in EnumerateLeaderboardModels())
|
||||||
|
{
|
||||||
|
var pid = plm.playerId;
|
||||||
|
if (!string.IsNullOrEmpty(pid))
|
||||||
|
{
|
||||||
|
onDone(pid.Trim());
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waited - lastGetUserInfoAttempt >= PlatformUserGetUserInfoRetrySeconds)
|
||||||
|
{
|
||||||
|
lastGetUserInfoAttempt = waited;
|
||||||
|
var model = ResolvePlatformUserModel();
|
||||||
|
if (model != null)
|
||||||
|
{
|
||||||
|
Task<UserInfo> task;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
task = model.GetUserInfo(CancellationToken.None);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
task = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task != null)
|
||||||
|
{
|
||||||
|
while (!task.IsCompleted)
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!task.IsFaulted)
|
||||||
|
{
|
||||||
|
var uid = task.Result?.platformUserId;
|
||||||
|
if (!string.IsNullOrEmpty(uid))
|
||||||
|
{
|
||||||
|
onDone(uid.Trim());
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waited += PlatformUserPollStepSeconds;
|
||||||
|
yield return new WaitForSeconds(PlatformUserPollStepSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDone(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="Resources.FindObjectsOfTypeAll{T}"/> can miss very early; <see cref="Object.FindObjectOfType{T}()"/> sometimes finds the scene instance sooner.
|
||||||
|
/// </summary>
|
||||||
|
private static IEnumerable<PlatformLeaderboardsModel> EnumerateLeaderboardModels()
|
||||||
|
{
|
||||||
|
var seen = new HashSet<PlatformLeaderboardsModel>();
|
||||||
|
foreach (var plm in Resources.FindObjectsOfTypeAll<PlatformLeaderboardsModel>())
|
||||||
|
{
|
||||||
|
if (plm != null && seen.Add(plm))
|
||||||
|
{
|
||||||
|
yield return plm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var active = UnityEngine.Object.FindObjectOfType<PlatformLeaderboardsModel>();
|
||||||
|
if (active != null && seen.Add(active))
|
||||||
|
{
|
||||||
|
yield return active;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IPlatformUserModel ResolvePlatformUserModel()
|
||||||
|
{
|
||||||
|
var field = typeof(PlatformLeaderboardsModel).GetField(
|
||||||
|
"_platformUserModel",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (field == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPlatformUserModel last = null;
|
||||||
|
foreach (var plm in EnumerateLeaderboardModels())
|
||||||
|
{
|
||||||
|
if (field.GetValue(plm) is IPlatformUserModel m)
|
||||||
|
{
|
||||||
|
last = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Component that drives the logging coroutine; self-destroys when finished.
|
/// Component that drives the logging coroutine; self-destroys when finished.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -97,7 +230,8 @@ namespace Setlist
|
|||||||
private IEnumerator Run()
|
private IEnumerator Run()
|
||||||
{
|
{
|
||||||
string platformUserId = null;
|
string platformUserId = null;
|
||||||
yield return StartCoroutine(FetchPlatformUserId(id => { platformUserId = id; }));
|
yield return StartCoroutine(FetchPlatformUserIdCoroutine(id => { platformUserId = id; }));
|
||||||
|
Plugin.CachedPlatformUserId = platformUserId;
|
||||||
_log.Info(string.IsNullOrEmpty(platformUserId)
|
_log.Info(string.IsNullOrEmpty(platformUserId)
|
||||||
? "platformUserId=(unknown)"
|
? "platformUserId=(unknown)"
|
||||||
: $"platformUserId={platformUserId}");
|
: $"platformUserId={platformUserId}");
|
||||||
@ -112,131 +246,8 @@ namespace Setlist
|
|||||||
platformUserId));
|
platformUserId));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var e in _entries)
|
|
||||||
{
|
|
||||||
if (!string.Equals(e.Title, BeatLeaderPlaylistUpdateTest.TargetPlaylistTitle, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return BeatLeaderPlaylistUpdateTest.RunIfTargetOwned(
|
|
||||||
e.Playlist,
|
|
||||||
e.HasSyncUrl,
|
|
||||||
e.BeatLeaderGuid,
|
|
||||||
e.OwnerId,
|
|
||||||
platformUserId,
|
|
||||||
_log);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// BSIPA runs us very early; <see cref="PlatformLeaderboardsModel"/> may not exist yet and its
|
|
||||||
/// async init may not have set <see cref="PlatformLeaderboardsModel.playerId"/>. Poll until ready.
|
|
||||||
/// </summary>
|
|
||||||
private static IEnumerator FetchPlatformUserId(Action<string> onDone)
|
|
||||||
{
|
|
||||||
var waited = 0f;
|
|
||||||
var lastGetUserInfoAttempt = -PlatformUserGetUserInfoRetrySeconds;
|
|
||||||
while (waited < PlatformUserWaitTimeoutSeconds)
|
|
||||||
{
|
|
||||||
foreach (var plm in EnumerateLeaderboardModels())
|
|
||||||
{
|
|
||||||
var pid = plm.playerId;
|
|
||||||
if (!string.IsNullOrEmpty(pid))
|
|
||||||
{
|
|
||||||
onDone(pid.Trim());
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (waited - lastGetUserInfoAttempt >= PlatformUserGetUserInfoRetrySeconds)
|
|
||||||
{
|
|
||||||
lastGetUserInfoAttempt = waited;
|
|
||||||
var model = ResolvePlatformUserModel();
|
|
||||||
if (model != null)
|
|
||||||
{
|
|
||||||
Task<UserInfo> task;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
task = model.GetUserInfo(CancellationToken.None);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
task = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (task != null)
|
|
||||||
{
|
|
||||||
while (!task.IsCompleted)
|
|
||||||
{
|
|
||||||
yield return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task.IsFaulted)
|
|
||||||
{
|
|
||||||
var uid = task.Result?.platformUserId;
|
|
||||||
if (!string.IsNullOrEmpty(uid))
|
|
||||||
{
|
|
||||||
onDone(uid.Trim());
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
waited += PlatformUserPollStepSeconds;
|
|
||||||
yield return new WaitForSeconds(PlatformUserPollStepSeconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDone(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="Resources.FindObjectsOfTypeAll{T}"/> can miss very early; <see cref="Object.FindObjectOfType{T}()"/> sometimes finds the scene instance sooner.
|
|
||||||
/// </summary>
|
|
||||||
private static IEnumerable<PlatformLeaderboardsModel> EnumerateLeaderboardModels()
|
|
||||||
{
|
|
||||||
var seen = new HashSet<PlatformLeaderboardsModel>();
|
|
||||||
foreach (var plm in Resources.FindObjectsOfTypeAll<PlatformLeaderboardsModel>())
|
|
||||||
{
|
|
||||||
if (plm != null && seen.Add(plm))
|
|
||||||
{
|
|
||||||
yield return plm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var active = UnityEngine.Object.FindObjectOfType<PlatformLeaderboardsModel>();
|
|
||||||
if (active != null && seen.Add(active))
|
|
||||||
{
|
|
||||||
yield return active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IPlatformUserModel ResolvePlatformUserModel()
|
|
||||||
{
|
|
||||||
var field = typeof(PlatformLeaderboardsModel).GetField(
|
|
||||||
"_platformUserModel",
|
|
||||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
|
||||||
if (field == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
IPlatformUserModel last = null;
|
|
||||||
foreach (var plm in EnumerateLeaderboardModels())
|
|
||||||
{
|
|
||||||
if (field.GetValue(plm) is IPlatformUserModel m)
|
|
||||||
{
|
|
||||||
last = m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return last;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,44 +13,42 @@ using IPALogger = IPA.Logging.Logger;
|
|||||||
namespace Setlist
|
namespace Setlist
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dev/test path: POST the on-disk playlist JSON to BeatLeader to prove authenticated updates work.
|
/// POSTs an updated playlist to BeatLeader when the local copy is owned by the player (playlist JSON
|
||||||
/// Requires BeatLeader to have finished sign-in (Unity cookie cache). See docs/beatleader-playlist-api.md.
|
/// <c>owner</c> vs platform user id). Uses Unity cookie session after BeatLeader sign-in.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static class BeatLeaderPlaylistUpdateTest
|
internal static class BeatLeaderPlaylistSync
|
||||||
{
|
{
|
||||||
/// <summary>When this playlist title is locally owned (owner matches platform id), run POST test on startup.</summary>
|
|
||||||
internal const string TargetPlaylistTitle = "2025-mapfilter-01";
|
|
||||||
|
|
||||||
private const string AuthenticationTypeName = "BeatLeader.API.Authentication";
|
private const string AuthenticationTypeName = "BeatLeader.API.Authentication";
|
||||||
private const string SignedInFieldName = "_signedIn";
|
private const string SignedInFieldName = "_signedIn";
|
||||||
private const float SignInWaitTimeoutSeconds = 90f;
|
private const float SignInWaitTimeoutSeconds = 90f;
|
||||||
private const int PostRequestTimeoutSeconds = 120;
|
private const int PostRequestTimeoutSeconds = 120;
|
||||||
|
|
||||||
/// <summary>POST <c>/user/playlist</c> with the serialized playlist body (same shape as the website sample).</summary>
|
/// <summary>POST <c>/user/playlist</c> with the serialized playlist body (same shape as the website sample).</summary>
|
||||||
internal static IEnumerator RunIfTargetOwned(
|
internal static IEnumerator CoPostOwnedPlaylistToBeatLeader(IPlaylist playlist, IPALogger log)
|
||||||
IPlaylist playlist,
|
|
||||||
bool hasSyncUrl,
|
|
||||||
string beatLeaderGuid,
|
|
||||||
string ownerId,
|
|
||||||
string platformUserId,
|
|
||||||
IPALogger log)
|
|
||||||
{
|
{
|
||||||
if (!string.Equals(playlist.Title, TargetPlaylistTitle, StringComparison.Ordinal))
|
BeatLeaderPlaylistOwnership.TryReadBeatLeaderMetadata(playlist, out var hasSyncUrl, out var beatLeaderGuid, out var ownerId);
|
||||||
|
|
||||||
|
var platformUserId = Plugin.CachedPlatformUserId;
|
||||||
|
if (string.IsNullOrEmpty(platformUserId))
|
||||||
{
|
{
|
||||||
yield break;
|
string resolved = null;
|
||||||
|
yield return BeatLeaderPlaylistOwnership.FetchPlatformUserIdCoroutine(id => { resolved = id; });
|
||||||
|
platformUserId = resolved;
|
||||||
|
if (!string.IsNullOrEmpty(platformUserId))
|
||||||
|
{
|
||||||
|
Plugin.CachedPlatformUserId = platformUserId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsLocallyOwnedBeatLeaderPlaylist(hasSyncUrl, beatLeaderGuid, ownerId, platformUserId))
|
if (!IsLocallyOwnedBeatLeaderPlaylist(hasSyncUrl, beatLeaderGuid, ownerId, platformUserId))
|
||||||
{
|
{
|
||||||
log.Info(
|
|
||||||
$"Setlist BeatLeader POST test: skip \"{TargetPlaylistTitle}\" (not locally owned or missing BeatLeader fields).");
|
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetBeatLeaderServerFields(playlist, out var serverPlaylistId, out var shared))
|
if (!TryGetBeatLeaderServerFields(playlist, out var serverPlaylistId, out var shared))
|
||||||
{
|
{
|
||||||
log.Info(
|
log.Info(
|
||||||
$"Setlist BeatLeader POST test: skip \"{TargetPlaylistTitle}\" (customData missing id or not parseable).");
|
$"Setlist BeatLeader sync: skip \"{playlist.Title}\" (customData id missing; cannot POST).");
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +64,7 @@ namespace Setlist
|
|||||||
if (waited >= SignInWaitTimeoutSeconds)
|
if (waited >= SignInWaitTimeoutSeconds)
|
||||||
{
|
{
|
||||||
log.Info(
|
log.Info(
|
||||||
$"Setlist BeatLeader POST test: BeatLeader login did not complete within {SignInWaitTimeoutSeconds:F0}s.");
|
$"Setlist BeatLeader sync: login did not complete within {SignInWaitTimeoutSeconds:F0}s (playlist \"{playlist.Title}\").");
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,22 +94,20 @@ namespace Setlist
|
|||||||
|
|
||||||
if (request.result == UnityWebRequest.Result.ConnectionError)
|
if (request.result == UnityWebRequest.Result.ConnectionError)
|
||||||
{
|
{
|
||||||
log.Info("Setlist BeatLeader POST test: network error: " + request.error);
|
log.Info("Setlist BeatLeader sync: network error: " + request.error);
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.result == UnityWebRequest.Result.ProtocolError)
|
if (request.result == UnityWebRequest.Result.ProtocolError)
|
||||||
{
|
{
|
||||||
log.Info(
|
log.Info(
|
||||||
$"Setlist BeatLeader POST test: HTTP {request.responseCode} body="
|
$"Setlist BeatLeader sync: HTTP {request.responseCode} playlist=\"{playlist.Title}\" body="
|
||||||
+ TruncateForLog(request.downloadHandler?.text));
|
+ TruncateForLog(request.downloadHandler?.text));
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(
|
log.Info(
|
||||||
"Setlist BeatLeader POST test: success HTTP "
|
$"Setlist BeatLeader sync: OK HTTP {request.responseCode} playlist=\"{playlist.Title}\" body="
|
||||||
+ request.responseCode
|
|
||||||
+ " body="
|
|
||||||
+ TruncateForLog(request.downloadHandler?.text));
|
+ TruncateForLog(request.downloadHandler?.text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +159,7 @@ namespace Setlist
|
|||||||
private static bool TrySerializePlaylist(IPlaylist playlist, IPALogger log, out string json)
|
private static bool TrySerializePlaylist(IPlaylist playlist, IPALogger log, out string json)
|
||||||
{
|
{
|
||||||
json = null;
|
json = null;
|
||||||
var mgr = PlaylistManager.DefaultManager;
|
var mgr = BeatSaberPlaylistsLib.PlaylistManager.DefaultManager;
|
||||||
IPlaylistHandler handler = null;
|
IPlaylistHandler handler = null;
|
||||||
if (!string.IsNullOrEmpty(playlist.SuggestedExtension))
|
if (!string.IsNullOrEmpty(playlist.SuggestedExtension))
|
||||||
{
|
{
|
||||||
@ -177,7 +173,7 @@ namespace Setlist
|
|||||||
|
|
||||||
if (handler == null)
|
if (handler == null)
|
||||||
{
|
{
|
||||||
log.Info("Setlist BeatLeader POST test: no IPlaylistHandler for playlist type.");
|
log.Info($"Setlist BeatLeader sync: no IPlaylistHandler for \"{playlist.Title}\".");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +189,7 @@ namespace Setlist
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
log.Info("Setlist BeatLeader POST test: serialize failed: " + ex.Message);
|
log.Info($"Setlist BeatLeader sync: serialize failed: {ex.Message}");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +200,7 @@ namespace Setlist
|
|||||||
var authType = beatLeaderAssembly.GetType(AuthenticationTypeName);
|
var authType = beatLeaderAssembly.GetType(AuthenticationTypeName);
|
||||||
if (authType == null)
|
if (authType == null)
|
||||||
{
|
{
|
||||||
log.Info($"Setlist BeatLeader POST test: assembly has no {AuthenticationTypeName}.");
|
log.Info($"Setlist BeatLeader sync: assembly has no {AuthenticationTypeName}.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,7 +223,7 @@ namespace Setlist
|
|||||||
|
|
||||||
private static string GetAssemblyVersion()
|
private static string GetAssemblyVersion()
|
||||||
{
|
{
|
||||||
var asm = typeof(BeatLeaderPlaylistUpdateTest).Assembly;
|
var asm = typeof(BeatLeaderPlaylistSync).Assembly;
|
||||||
return asm.GetName().Version?.ToString() ?? "0.0.0";
|
return asm.GetName().Version?.ToString() ?? "0.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using BeatSaberPlaylistsLib.Types;
|
using BeatSaberPlaylistsLib.Types;
|
||||||
using IPA;
|
using IPA;
|
||||||
|
using PlaylistManager.Utilities;
|
||||||
using IPALogger = IPA.Logging.Logger;
|
using IPALogger = IPA.Logging.Logger;
|
||||||
|
|
||||||
namespace Setlist
|
namespace Setlist
|
||||||
@ -16,6 +17,9 @@ namespace Setlist
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IPALogger Log { get; private set; }
|
internal static IPALogger Log { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>Set after the ownership scan resolves the platform user id; used when syncing after PlaylistManager adds a song.</summary>
|
||||||
|
internal static string CachedPlatformUserId { get; set; }
|
||||||
|
|
||||||
[Init]
|
[Init]
|
||||||
public Plugin(IPALogger logger)
|
public Plugin(IPALogger logger)
|
||||||
{
|
{
|
||||||
@ -28,6 +32,10 @@ namespace Setlist
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
SetlistSyncHost.Ensure();
|
||||||
|
|
||||||
|
Events.playlistSongAdded += OnPlaylistSongAdded;
|
||||||
|
|
||||||
var playlists = BeatSaberPlaylistsLib.PlaylistManager.DefaultManager.GetAllPlaylists(
|
var playlists = BeatSaberPlaylistsLib.PlaylistManager.DefaultManager.GetAllPlaylists(
|
||||||
includeChildren: true,
|
includeChildren: true,
|
||||||
out AggregateException loadErrors);
|
out AggregateException loadErrors);
|
||||||
@ -50,25 +58,11 @@ namespace Setlist
|
|||||||
var entries = new List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)>();
|
var entries = new List<(IPlaylist Playlist, string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)>();
|
||||||
foreach (var playlist in playlists)
|
foreach (var playlist in playlists)
|
||||||
{
|
{
|
||||||
var hasSyncUrl = false;
|
BeatLeaderPlaylistOwnership.TryReadBeatLeaderMetadata(
|
||||||
string syncUrl = null;
|
playlist,
|
||||||
if (playlist.TryGetCustomData("syncURL", out var syncObj) && syncObj is string url)
|
out var hasSyncUrl,
|
||||||
{
|
out var blGuid,
|
||||||
syncUrl = url;
|
out var ownerId);
|
||||||
hasSyncUrl = !string.IsNullOrWhiteSpace(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
string ownerId = null;
|
|
||||||
if (playlist.TryGetCustomData("owner", out var ownerObj) && ownerObj is string o)
|
|
||||||
{
|
|
||||||
ownerId = string.IsNullOrWhiteSpace(o) ? null : o.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
string blGuid = null;
|
|
||||||
if (hasSyncUrl && BeatLeaderPlaylistOwnership.TryExtractBeatLeaderPlaylistGuid(syncUrl, out var g))
|
|
||||||
{
|
|
||||||
blGuid = g;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.Add((playlist, playlist.Title, hasSyncUrl, blGuid, ownerId));
|
entries.Add((playlist, playlist.Title, hasSyncUrl, blGuid, ownerId));
|
||||||
}
|
}
|
||||||
@ -84,6 +78,12 @@ namespace Setlist
|
|||||||
[OnExit]
|
[OnExit]
|
||||||
public void OnApplicationQuit()
|
public void OnApplicationQuit()
|
||||||
{
|
{
|
||||||
|
Events.playlistSongAdded -= OnPlaylistSongAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlaylistSongAdded(IPlaylistSong song, IPlaylist playlist)
|
||||||
|
{
|
||||||
|
SetlistSyncHost.Instance?.StartPostOwnedBeatLeaderPlaylist(playlist, Log);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
[assembly: Guid("50F53E6E-21D5-4780-8E67-273877DAA28C")]
|
[assembly: Guid("50F53E6E-21D5-4780-8E67-273877DAA28C")]
|
||||||
[assembly: AssemblyVersion("0.0.8.0")]
|
[assembly: AssemblyVersion("0.1.0.0")]
|
||||||
[assembly: AssemblyFileVersion("0.0.8.0")]
|
[assembly: AssemblyFileVersion("0.1.0.0")]
|
||||||
|
|||||||
@ -115,11 +115,16 @@
|
|||||||
<HintPath>$(BeatSaberDir)\Plugins\BeatLeader.dll</HintPath>
|
<HintPath>$(BeatSaberDir)\Plugins\BeatLeader.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="PlaylistManager">
|
||||||
|
<HintPath>$(BeatSaberDir)\Plugins\PlaylistManager.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="BeatLeaderPlaylistOwnership.cs" />
|
<Compile Include="BeatLeaderPlaylistOwnership.cs" />
|
||||||
<Compile Include="BeatLeaderPlaylistUpdateTest.cs" />
|
<Compile Include="BeatLeaderPlaylistSync.cs" />
|
||||||
<Compile Include="Plugin.cs" />
|
<Compile Include="Plugin.cs" />
|
||||||
|
<Compile Include="SetlistSyncHost.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
32
Setlist/SetlistSyncHost.cs
Normal file
32
Setlist/SetlistSyncHost.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
using BeatSaberPlaylistsLib.Types;
|
||||||
|
using UnityEngine;
|
||||||
|
using IPALogger = IPA.Logging.Logger;
|
||||||
|
|
||||||
|
namespace Setlist
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Persists for <see cref="UnityEngine.MonoBehaviour.StartCoroutine"/> (e.g. BeatLeader POST after UI adds a song).
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class SetlistSyncHost : MonoBehaviour
|
||||||
|
{
|
||||||
|
internal static SetlistSyncHost Instance { get; private set; }
|
||||||
|
|
||||||
|
internal static void Ensure()
|
||||||
|
{
|
||||||
|
if (Instance != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var go = new GameObject("Setlist.SyncHost");
|
||||||
|
UnityEngine.Object.DontDestroyOnLoad(go);
|
||||||
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
Instance = go.AddComponent<SetlistSyncHost>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void StartPostOwnedBeatLeaderPlaylist(IPlaylist playlist, IPALogger log)
|
||||||
|
{
|
||||||
|
StartCoroutine(BeatLeaderPlaylistSync.CoPostOwnedPlaylistToBeatLeader(playlist, log));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,12 +3,13 @@
|
|||||||
"id": "Setlist",
|
"id": "Setlist",
|
||||||
"name": "Setlist",
|
"name": "Setlist",
|
||||||
"author": "",
|
"author": "",
|
||||||
"version": "0.0.8",
|
"version": "0.1.0",
|
||||||
"description": "Syncs playlists with external sources.",
|
"description": "Syncs playlists with external sources.",
|
||||||
"gameVersion": "1.40.8",
|
"gameVersion": "1.40.8",
|
||||||
"dependsOn": {
|
"dependsOn": {
|
||||||
"BSIPA": "^4.3.0",
|
"BSIPA": "^4.3.0",
|
||||||
"BeatSaberPlaylistsLib": "^1.7.0",
|
"BeatSaberPlaylistsLib": "^1.7.0",
|
||||||
"BeatLeader": "^0.9.0"
|
"BeatLeader": "^0.9.0",
|
||||||
|
"PlaylistManager": "^1.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user