Add research on beatleader playlist CRUD actions
This commit is contained in:
commit
216cd9564b
45
docs/beatleader-api-samples.md
Normal file
45
docs/beatleader-api-samples.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# BeatLeader API Samples
|
||||||
|
|
||||||
|
Creating a playlist via beatleader-website
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch("https://api.beatleader.com/user/playlist?id=&shared=false", {
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/plain;charset=UTF-8",
|
||||||
|
"sec-ch-ua": "\"Chromium\";v=\"147\", \"Not.A/Brand\";v=\"8\"",
|
||||||
|
"sec-ch-ua-mobile": "?0",
|
||||||
|
"sec-ch-ua-platform": "\"Linux\""
|
||||||
|
},
|
||||||
|
"referrer": "https://beatleader.com/",
|
||||||
|
"body": "{\"playlistTitle\":\"New playlist\",\"playlistAuthor\":\"BeatLeader\",\"songs\":[],\"image\":\"data:image/png;base64,..."}",
|
||||||
|
"method": "POST",
|
||||||
|
"mode": "cors",
|
||||||
|
"credentials": "omit"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
beatleader-server response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"syncURL":"https://api.beatleader.com/playlist/guid/e5e809799274440c9fde81dc15ff4ac0","owner":"76561199407393962","id":"105647","hash":"60faee74c5fac44fa6c1a47baa288f852ae25accf921a1f5d8a2309e57987ded","shared":false}
|
||||||
|
```
|
||||||
|
|
||||||
|
Updating a playlist (basically send the whole thing)
|
||||||
|
|
||||||
|
```js
|
||||||
|
fetch("https://api.beatleader.com/user/playlist?id=105643&shared=false", {
|
||||||
|
"headers": {
|
||||||
|
"content-type": "text/plain;charset=UTF-8",
|
||||||
|
"sec-ch-ua": "\"Chromium\";v=\"147\", \"Not.A/Brand\";v=\"8\"",
|
||||||
|
"sec-ch-ua-mobile": "?0",
|
||||||
|
"sec-ch-ua-platform": "\"Linux\""
|
||||||
|
},
|
||||||
|
"referrer": "https://beatleader.com/",
|
||||||
|
"body": "{\"playlistTitle\":\"New playlist\",\"playlistAuthor\":\"BeatLeader\",\"songs\":[{\"hash\":\"938cd22b78f603c1fb83725ce16a2c9ca337e1be\",\"songName\":\"Death Waltz\",\"difficulties\":[{\"name\":\"expertPlus\",\"characteristic\":\"Standard\"}],\"levelAuthorName\":\"nitronik.exe\"},{\"hash\":\"ba3175097c8341585a5e9ce5cdecdca4af0f97e5\",\"songName\":\"Thunder\",\"difficulties\":[{\"name\":\"expertPlus\",\"characteristic\":\"Standard\"}],\"levelAuthorName\":\"Undeceiver\"}],\"image\":\"data:image/png;base64,...\",\"customData\":{\"syncURL\":\"https://api.beatleader.com/playlist/guid/9f2660d1167849b3874bee8abb9c3828\",\"owner\":\"76561199407393962\",\"id\":\"105643\",\"hash\":\"3f90d04e0fc122f6a8a0e4e74f0a97bbe886bdd73c67b58a29f6d0b59bacf8e5\",\"shared\":false}}",
|
||||||
|
"method": "POST",
|
||||||
|
"mode": "cors",
|
||||||
|
"credentials": "omit"
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The beatleader-server response is the same shape as creating the playlist.
|
||||||
90
docs/beatleader-playlist-api.md
Normal file
90
docs/beatleader-playlist-api.md
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# BeatLeader Playlist API
|
||||||
|
|
||||||
|
Here is the practical picture for a **second plugin** in the same Beat Saber process.
|
||||||
|
|
||||||
|
## What “authorization state” actually is
|
||||||
|
|
||||||
|
For `/user/playlist*`, the server cares about the **ASP.NET cookie auth session**: the browser-like **cookies** your client got after ticket sign-in, which build a principal with **`NameIdentifier` + `Issued`**. There is **no separate API key or Bearer token** your plugin can copy out of BeatLeader and paste on its own requests unless the server also accepts that (for these routes, you have been told it does not).
|
||||||
|
|
||||||
|
## Reference BeatLeader and use its `WebRequestFactory` (real sharing)
|
||||||
|
|
||||||
|
In beatleader-mod, session cookies live on a **single static** `HttpClient` whose handler uses **`WebRequestFactory`’s `CookieContainer`**. Login is **`Authentication.Login()`** (internal), which fills that container; **`WebRequestFactory.Send` / `Send<T>`** are **public** and, with default **`waitForLogin: true`**, call **`Authentication.WaitLogin()`** before sending.
|
||||||
|
|
||||||
|
So **another assembly** can:
|
||||||
|
|
||||||
|
- Add a **project reference** (or reference the shipped **BeatLeader.dll** in `Plugins`).
|
||||||
|
- Build `HttpRequestMessage` for `GET https://api.beatleader.com/user/playlists`
|
||||||
|
- Call **`WebRequestFactory.Send(...)`** or **`WebRequestFactory.Send<T>(..., parser, waitForLogin: true)`**.
|
||||||
|
|
||||||
|
That runs on the **same `HttpClient` and `CookieContainer`** as BeatLeader, and waits on the **same** login gate, so you inherit the session **without** BeatLeader exposing a special “auth API.”
|
||||||
|
|
||||||
|
**Requirements / caveats**
|
||||||
|
|
||||||
|
- **BeatLeader must be installed and actually complete login** (its menu init calls `Authentication.Login()`). If login never succeeds, `WaitLogin()` never completes happily and you get no cookies.
|
||||||
|
- **Hard dependency**: no BeatLeader assembly → no compile; disabled/removed mod → your auth path breaks unless you add a fallback.
|
||||||
|
- **API stability**: you depend on BeatLeader’s public surface (`WebRequestFactory`, parsers, etc.) staying compatible across versions.
|
||||||
|
|
||||||
|
You do **not** need to “obtain” cookies as strings; you only need to **route requests through that factory** (or duplicate sign-in below).
|
||||||
|
|
||||||
|
## Playlists API
|
||||||
|
|
||||||
|
Here’s a concrete shape that matches how the beatleader mod already does authenticated GETs (`UserRequest`, `ContextsRequest`, `PlatformEventsRequest`): same `HttpClient` + `CookieContainer`, default **`waitForLogin: true`**, so **`GET /user/playlists`** runs after sign-in and sends session cookies automatically.
|
||||||
|
|
||||||
|
### 1. Response model
|
||||||
|
|
||||||
|
Align property names with your JSON (camelCase matches typical ASP.NET JSON defaults):
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// e.g. Source/2_Core/Models/API/BeatLeader/UserPlaylistSummary.cs
|
||||||
|
namespace BeatLeader.Models {
|
||||||
|
public sealed class UserPlaylistSummary {
|
||||||
|
public int id { get; set; }
|
||||||
|
public bool isShared { get; set; }
|
||||||
|
public string? link { get; set; }
|
||||||
|
public string? ownerId { get; set; }
|
||||||
|
public string? hash { get; set; }
|
||||||
|
public string? guid { get; set; }
|
||||||
|
public bool deleted { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Request type (mirror `PlatformEventsRequest` / `PlaylistRequest`)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using BeatLeader.Models;
|
||||||
|
using BeatLeader.Utils;
|
||||||
|
using BeatLeader.WebRequests;
|
||||||
|
|
||||||
|
namespace BeatLeader.API {
|
||||||
|
internal sealed class UserPlaylistsRequest
|
||||||
|
: PersistentWebRequestBase<List<UserPlaylistSummary>, JsonResponseParser<List<UserPlaylistSummary>>> {
|
||||||
|
|
||||||
|
private static string Endpoint => BLConstants.BEATLEADER_API_URL + "/user/playlists";
|
||||||
|
|
||||||
|
public static IWebRequest<List<UserPlaylistSummary>> Send() {
|
||||||
|
return SendRet(Endpoint, HttpMethod.Get);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Call site (same as `PlaylistRequest` / `PlatformEventsRequest`)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
var result = await UserPlaylistsRequest.Send().Join();
|
||||||
|
|
||||||
|
if (result.RequestState == WebRequests.RequestState.Finished && result.Result != null) {
|
||||||
|
foreach (var p in result.Result) {
|
||||||
|
// use p.id, p.guid, p.deleted, etc.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Plugin.Log.Debug($"User playlists failed: {result.FailReason}");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That’s all that’s required on the **client** side for “fetch my playlists”: **GET + existing cookie session**. No extra headers beyond what `WebRequestFactory` already applies (`User-Agent` + cookies).
|
||||||
|
|
||||||
|
**Troubleshooting:** right after implementing, compare behavior to `UserRequest` (`GET /user/modinterface`): if one returns `Finished` with data and the other gets `401`, the issue is session/host/HTTPS, not playlist-specific logic.
|
||||||
Loading…
x
Reference in New Issue
Block a user