diff --git a/Setlist/BeatLeaderPlaylistOwnership.cs b/Setlist/BeatLeaderPlaylistOwnership.cs
index d89dde0..ca64b43 100644
--- a/Setlist/BeatLeaderPlaylistOwnership.cs
+++ b/Setlist/BeatLeaderPlaylistOwnership.cs
@@ -5,38 +5,26 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
+using UnityEngine;
+#if false
using BeatLeader.Utils;
using Newtonsoft.Json;
-using UnityEngine;
using UnityEngine.Networking;
+#endif
using IPALogger = IPA.Logging.Logger;
namespace Setlist
{
///
- /// Parses BeatLeader sync URLs and checks ownership via GET /user/playlists.
- /// Reuses BeatLeader's sign-in by piggy-backing on Unity's process-wide
- /// cookie cache (the shipped 0.9.x BeatLeader
- /// signs in with ; cookies are global per host).
- /// We block on BeatLeader's Authentication._signedIn via reflection so
- /// we don't fire the request before the cookie is in the cache.
+ /// Parses BeatLeader-style sync URLs for logging. Ownership is determined in
+ /// from playlist JSON only (no network).
///
internal static class BeatLeaderPlaylistOwnership
{
- private const string AuthenticationTypeName = "BeatLeader.API.Authentication";
- private const string SignedInFieldName = "_signedIn";
- private const float LoginWaitTimeoutSeconds = 90f;
private const float PlatformUserPollStepSeconds = 0.5f;
/// How long to wait for to appear and populate playerId (plugin runs before menu init).
private const float PlatformUserWaitTimeoutSeconds = 30f;
private const float PlatformUserGetUserInfoRetrySeconds = 3f;
- private const int RequestTimeoutSeconds = 30;
-
- private sealed class UserPlaylistSummary
- {
- [JsonProperty("guid")]
- public string Guid { get; set; }
- }
internal static bool TryExtractBeatLeaderPlaylistGuid(string syncUrl, out string guid)
{
@@ -79,9 +67,8 @@ namespace Setlist
}
///
- /// Spawns a hidden coroutine runner that waits for BeatLeader sign-in,
- /// fetches /user/playlists, then logs the per-playlist ownership.
- /// Must be called from the Unity main thread (BSIPA's OnApplicationStart is fine).
+ /// 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 OnApplicationStart is fine).
///
internal static void ScheduleVerifyAndLog(
List<(string Title, bool HasSyncUrl, string BeatLeaderGuid, string OwnerId)> entries,
@@ -95,7 +82,7 @@ namespace Setlist
}
///
- /// Component that drives the verification coroutine; self-destroys when finished.
+ /// Component that drives the logging coroutine; self-destroys when finished.
///
private sealed class OwnershipRunner : MonoBehaviour
{
@@ -119,54 +106,6 @@ namespace Setlist
? "platformUserId=(unknown)"
: $"platformUserId={platformUserId}");
- HashSet owned = null;
- string failure = null;
-
- if (_entries.Any(e => e.BeatLeaderGuid != null))
- {
- FieldInfo signedInField;
- try
- {
- signedInField = ResolveSignedInField(_log);
- }
- catch (Exception ex)
- {
- signedInField = null;
- failure = "reflecting BeatLeader Authentication failed: " + ex.Message;
- }
-
- if (signedInField != null)
- {
- var waitedSeconds = 0f;
- while (!IsSignedIn(signedInField))
- {
- if (waitedSeconds >= LoginWaitTimeoutSeconds)
- {
- failure = $"BeatLeader login did not complete within {LoginWaitTimeoutSeconds:F0}s; "
- + "is the BeatLeader mod actually signing in (check BeatLeader log lines)?";
- break;
- }
- yield return new WaitForSeconds(1f);
- waitedSeconds += 1f;
- }
-
- if (failure == null)
- {
- var fetchEnumerator = FetchOwnedGuids(result =>
- {
- owned = result.OwnedGuids;
- failure = result.Failure;
- });
- yield return StartCoroutine(fetchEnumerator);
- }
- }
- }
-
- if (owned == null && failure != null)
- {
- _log.Info("BeatLeader /user/playlists: " + failure);
- }
-
foreach (var e in _entries)
{
_log.Info(Plugin.FormatPlaylistLogLine(
@@ -174,8 +113,7 @@ namespace Setlist
e.HasSyncUrl,
e.BeatLeaderGuid,
e.OwnerId,
- platformUserId,
- owned));
+ platformUserId));
}
Destroy(gameObject);
@@ -287,6 +225,21 @@ namespace Setlist
return last;
}
+ // SETLIST: Remove the entire #if false region below once we no longer need the old
+ // BeatLeader GET /user/playlists + login-wait verification path for reference.
+
+#if false
+ private const string AuthenticationTypeName = "BeatLeader.API.Authentication";
+ private const string SignedInFieldName = "_signedIn";
+ private const float LoginWaitTimeoutSeconds = 90f;
+ private const int RequestTimeoutSeconds = 30;
+
+ private sealed class UserPlaylistSummary
+ {
+ [JsonProperty("guid")]
+ public string Guid { get; set; }
+ }
+
private struct FetchResult
{
public HashSet OwnedGuids;
@@ -384,6 +337,7 @@ namespace Setlist
var asm = typeof(BeatLeaderPlaylistOwnership).Assembly;
return asm.GetName().Version?.ToString() ?? "0.0.0";
}
+#endif
}
}
}
diff --git a/Setlist/Plugin.cs b/Setlist/Plugin.cs
index 7415c3d..e8be6f1 100644
--- a/Setlist/Plugin.cs
+++ b/Setlist/Plugin.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using IPA;
using IPALogger = IPA.Logging.Logger;
@@ -86,45 +85,50 @@ namespace Setlist
{
}
+ ///
+ /// One-line log for a playlist. Ownership is inferred only from playlist JSON customData
+ /// (owner vs the game's platform user id). No network verification.
+ ///
internal static string FormatPlaylistLogLine(
string title,
bool hasSyncUrl,
- string beatLeaderGuid,
+ string beatLeaderPlaylistGuid,
string ownerId,
- string platformUserId,
- HashSet ownedGuids)
+ string platformUserId)
{
var ownerToken = string.IsNullOrEmpty(ownerId) ? "owner=n/a" : $"owner={ownerId}";
- string confirmPart;
+ string ownerMatchesPlatformPart;
if (!hasSyncUrl)
{
- confirmPart = "beatLeaderOwnerConfirmed=n/a (no sync URL)";
+ ownerMatchesPlatformPart = "ownerMatchesPlatform=n/a (no sync URL)";
}
- else if (string.IsNullOrEmpty(beatLeaderGuid))
+ else if (string.IsNullOrEmpty(beatLeaderPlaylistGuid))
{
- confirmPart = "beatLeaderOwnerConfirmed=n/a (sync URL is not a BeatLeader playlist)";
+ ownerMatchesPlatformPart =
+ "ownerMatchesPlatform=n/a (sync URL is not an api.beatleader.com /playlist/guid/… URL)";
}
- else if (!string.IsNullOrEmpty(ownerId)
- && !string.IsNullOrEmpty(platformUserId)
- && string.Equals(ownerId, platformUserId, StringComparison.Ordinal))
+ else if (string.IsNullOrEmpty(platformUserId))
{
- confirmPart = "beatLeaderOwnerConfirmed=true (owner matches platform user id)";
+ ownerMatchesPlatformPart =
+ "ownerMatchesPlatform=unknown (platform user id not available yet; cannot compare to owner)";
}
- else if (ownedGuids == null)
+ else if (string.IsNullOrEmpty(ownerId))
{
- confirmPart = "beatLeaderOwnerConfirmed=unknown (BeatLeader /user/playlists did not succeed; see prior log line)";
+ ownerMatchesPlatformPart =
+ "ownerMatchesPlatform=false (BeatLeader playlist URL but no owner field in playlist JSON)";
}
- else if (ownedGuids.Contains(beatLeaderGuid))
+ else if (string.Equals(ownerId, platformUserId, StringComparison.Ordinal))
{
- confirmPart = "beatLeaderOwnerConfirmed=true";
+ ownerMatchesPlatformPart = "ownerMatchesPlatform=true (playlist owner field equals platform user id)";
}
else
{
- confirmPart = "beatLeaderOwnerConfirmed=false";
+ ownerMatchesPlatformPart =
+ "ownerMatchesPlatform=false (playlist owner field differs from platform user id)";
}
- return $"Playlist \"{title}\": hasSyncUrl={hasSyncUrl}, {ownerToken}, {confirmPart}";
+ return $"Playlist \"{title}\": hasSyncUrl={hasSyncUrl}, {ownerToken}, {ownerMatchesPlatformPart}";
}
}
}
diff --git a/Setlist/Properties/AssemblyInfo.cs b/Setlist/Properties/AssemblyInfo.cs
index 537c989..de422bc 100644
--- a/Setlist/Properties/AssemblyInfo.cs
+++ b/Setlist/Properties/AssemblyInfo.cs
@@ -11,5 +11,5 @@ using System.Runtime.InteropServices;
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("50F53E6E-21D5-4780-8E67-273877DAA28C")]
-[assembly: AssemblyVersion("0.0.5.0")]
-[assembly: AssemblyFileVersion("0.0.5.0")]
+[assembly: AssemblyVersion("0.0.6.0")]
+[assembly: AssemblyFileVersion("0.0.6.0")]
diff --git a/Setlist/Setlist.csproj b/Setlist/Setlist.csproj
index f36a260..5628bb7 100644
--- a/Setlist/Setlist.csproj
+++ b/Setlist/Setlist.csproj
@@ -10,6 +10,7 @@
Properties
Setlist
Setlist
+
v4.8
512
true
diff --git a/Setlist/manifest.json b/Setlist/manifest.json
index ce5576d..28faac5 100644
--- a/Setlist/manifest.json
+++ b/Setlist/manifest.json
@@ -3,7 +3,7 @@
"id": "Setlist",
"name": "Setlist",
"author": "",
- "version": "0.0.5",
+ "version": "0.0.6",
"description": "Syncs playlists with external sources.",
"gameVersion": "1.40.8",
"dependsOn": {
diff --git a/docs/playlistmanager-add-map.md b/docs/playlistmanager-add-map.md
index bd9fdf6..72f5446 100644
--- a/docs/playlistmanager-add-map.md
+++ b/docs/playlistmanager-add-map.md
@@ -79,4 +79,39 @@ So:
- **PlaylistManager code**: `AddPlaylistModalController.OnCellSelect` (orchestration, UI, `StorePlaylist`, `Events.RaisePlaylistSongAdded`).
- **Library code**: `selectedPlaylist.Add(...)` — implementation of how the entry is stored lives in **BeatSaberPlaylistsLib** (`IPlaylist`), not in this repository.
-There is no other `.Add(` on a playlist for this flow in the grep results; removing a song is the parallel path in `LevelDetailButtonsViewController.RemoveSong()`.
\ No newline at end of file
+There is no other `.Add(` on a playlist for this flow in the grep results; removing a song is the parallel path in `LevelDetailButtonsViewController.RemoveSong()`.
+
+## hooking into the process
+
+### 1. Subscribe to PlaylistManager’s public event (simplest)
+
+After a successful add from the **Add to playlist** UI, PlaylistManager raises a **public static** event:
+
+```16:18:PlaylistManager/Utilities/Events.cs
+ ///
+ /// Raised when an is added to an
+ ///
+ public static event Action playlistSongAdded;
+```
+
+It is invoked **after** `RaisePlaylistChanged()` and `StorePlaylist()` succeed:
+
+```187:194:PlaylistManager/UI/ViewControllers/AddPlaylistModalController.cs
+ try
+ {
+ selectedPlaylist.RaisePlaylistChanged();
+ parentManager.StorePlaylist(selectedPlaylist);
+ popupModalsController.ShowOkModal(modalTransform, string.Format("Song successfully added to {0}", selectedPlaylist.Title), null, animateParentCanvas: false);
+ // TODO: Doesn't refresh the sprite.
+ Events.RaisePlaylistSongAdded(playlistSong, selectedPlaylist);
+ }
+```
+
+In your plugin: add a **reference to `PlaylistManager.dll`**, a **manifest dependency** on PlaylistManager, then subscribe in `OnEnable` (or menu init) and unsubscribe in `OnDisable`:
+
+- Namespace: `PlaylistManager.Utilities`
+- Type: `Events`
+- Event: `playlistSongAdded`
+- Handler signature: `(IPlaylistSong song, IPlaylist playlist)` from `BeatSaberPlaylistsLib.Types`
+
+**Caveat:** This is only raised for adds that go through **this** code path. It is the **only** `RaisePlaylistSongAdded` call site in the repo, so adds done only via BeatSaberPlaylistsLib (or another mod) will **not** fire this event.