This commit is contained in:
Forrest 2026-07-29 22:41:38 -04:00
parent 981b4836f9
commit ed910748cb
34 changed files with 2054 additions and 153 deletions

View file

@ -8,7 +8,7 @@ import 'json_helpers.dart';
import 'models.dart';
/// A Subsonic API error (server returned `status: "failed"`), ported from
/// `ratune-subsonic/src/error.rs`. Code 40 is the auth-failure code.
/// `timbre-subsonic/src/error.rs`. Code 40 is the auth-failure code.
class SubsonicError implements Exception {
SubsonicError(this.code, this.message);
@ -22,7 +22,7 @@ class SubsonicError implements Exception {
String toString() => 'SubsonicError($code): $message';
}
/// Subsonic HTTP client, ported from `ratune-subsonic/src/client.rs`.
/// Subsonic HTTP client, ported from `timbre-subsonic/src/client.rs`.
///
/// Auth is the classic token scheme: a fresh random salt per request and
/// `token = MD5(password + salt)`, sent as query params
@ -57,7 +57,7 @@ class SubsonicClient {
url.endsWith('/') ? url.substring(0, url.length - 1) : url;
/// Cryptographically-random 12-char salt (Dart's `Random.secure()` stands in
/// for the platform CSPRNG; Ratune uses a weaker LCG here).
/// for the platform CSPRNG; Timbre uses a weaker LCG here).
String _makeSalt([int length = 12]) {
final rng = Random.secure();
return List.generate(
@ -190,7 +190,7 @@ class SubsonicClient {
}
/// `star` / `unstar`. Dispatches to the right param (id / albumId / artistId)
/// exactly like Ratune's `set_starred` (`client.rs`).
/// exactly like Timbre's `set_starred` (`client.rs`).
Future<void> setStarred({
required bool starred,
String? songId,
@ -218,7 +218,7 @@ class SubsonicClient {
}
// ---- Playlists ----------------------------------------------------------
// Ported from `ratune-subsonic/src/client.rs`. Track mutations all go through
// Ported from `timbre-subsonic/src/client.rs`. Track mutations all go through
// `updatePlaylist` with `songIdToAdd` / `songIndexToRemove` / `name`.
/// `getPlaylists` — every playlist visible to the authenticated user.
@ -266,6 +266,13 @@ class SubsonicClient {
await _get('updatePlaylist', {'playlistId': playlistId, 'name': name});
}
/// `updatePlaylist` + `comment` — set a playlist's comment. Timbre uses this
/// to mark a playlist as a tag (`createPlaylist` can't set a comment inline).
Future<void> setPlaylistComment(String playlistId, String comment) async {
await _get(
'updatePlaylist', {'playlistId': playlistId, 'comment': comment});
}
/// `deletePlaylist` — delete a playlist by id.
Future<void> deletePlaylist(String id) async {
await _get('deletePlaylist', {'id': id});