gerdur-core 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +43 -1
- package/dist/api/favorites.d.ts +59 -0
- package/dist/api/favorites.js +99 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.18.0 - 2026-08-31
|
|
4
|
+
|
|
5
|
+
Write operations — the first part of this package that changes an account.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`src/api/favorites.ts`** — mutating library operations, in their own module
|
|
10
|
+
and wired into no download path, so they only run when called deliberately:
|
|
11
|
+
- `addFavoriteTracks(ids)` / `removeFavoriteTracks(ids)` (`song.addFavorites` /
|
|
12
|
+
`song.removeFavorites` — note the plural)
|
|
13
|
+
- `addFavoriteAlbum` / `removeFavoriteAlbum`, `addFavoriteArtist` /
|
|
14
|
+
`removeFavoriteArtist`
|
|
15
|
+
- `followPlaylist` / `unfollowPlaylist` (the gateway wants
|
|
16
|
+
`parent_playlist_id` to follow but `playlist_id` to unfollow)
|
|
17
|
+
- `addFavoriteShow`, `createPlaylist`, `addTracksToPlaylist`,
|
|
18
|
+
`removeTracksFromPlaylist`
|
|
19
|
+
|
|
20
|
+
The surface was mapped by calling each candidate with **incomplete
|
|
21
|
+
parameters** — an existing method answers `MISSING_PARAMETER_*`, an absent one
|
|
22
|
+
`GATEWAY_ERROR` — so method names and their parameter names were established
|
|
23
|
+
without creating or changing anything.
|
|
24
|
+
|
|
25
|
+
### Verification status
|
|
26
|
+
|
|
27
|
+
- Method existence and parameter names: **confirmed by probe**.
|
|
28
|
+
- End-to-end execution: **not run.** `__tests__/favorites.ts` contains
|
|
29
|
+
snapshot → mutate → assert → revert → assert-restored round trips, gated
|
|
30
|
+
behind `GERDUR_ALLOW_WRITE_TESTS=1` so they never fire by accident.
|
|
31
|
+
|
|
32
|
+
### Known gaps
|
|
33
|
+
|
|
34
|
+
- **Deezer's gateway has no playlist delete.** `playlist.delete`, `.remove`,
|
|
35
|
+
`.destroy`, `.deletePlaylist` and eight other spellings all answer
|
|
36
|
+
`GATEWAY_ERROR`, so `createPlaylist` is one-way — remove what it makes from a
|
|
37
|
+
Deezer client. Everything else here has a confirmed inverse.
|
|
38
|
+
- No `show.deleteFavorite` was found, so `addFavoriteShow` is one-way too.
|
|
39
|
+
- **`log.listen` (scrobbling) is not implemented.** It never answered the probe,
|
|
40
|
+
so its shape is unknown, and it writes to listening history that cannot be
|
|
41
|
+
undone — not something to ship on a guess.
|
|
42
|
+
|
|
3
43
|
## 2.17.0 - 2026-08-31
|
|
4
44
|
|
|
5
45
|
The account's own library, over the authenticated gateway.
|
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ the CLI and the file-writing layer on top.
|
|
|
47
47
|
- [Browse and discover](#browse-and-discover)
|
|
48
48
|
- [Flow, radios and a user's library](#flow-radios-and-a-users-library)
|
|
49
49
|
- [Your own library](#your-own-library)
|
|
50
|
+
- [Changing the account (write operations)](#changing-the-account-write-operations)
|
|
50
51
|
- [Podcasts](#podcasts)
|
|
51
52
|
- [Preview clips](#preview-clips)
|
|
52
53
|
- [Resolve a download URL](#resolve-a-download-url)
|
|
@@ -409,6 +410,43 @@ const urls = await resolveDownloadUrls(mix, [9, 3, 1]); // straight to download
|
|
|
409
410
|
`userId` defaults to the logged-in account. These are account-scoped, so they
|
|
410
411
|
never enter the shared cross-session cache.
|
|
411
412
|
|
|
413
|
+
### Changing the account (write operations)
|
|
414
|
+
|
|
415
|
+
Everything above reads. These change the logged-in account's library for real —
|
|
416
|
+
they are in their own module, wired into no download path, and only run when you
|
|
417
|
+
call them.
|
|
418
|
+
|
|
419
|
+
| Function | Inverse |
|
|
420
|
+
| :--- | :--- |
|
|
421
|
+
| `addFavoriteTracks(ids)` | `removeFavoriteTracks(ids)` |
|
|
422
|
+
| `addFavoriteAlbum(id)` | `removeFavoriteAlbum(id)` |
|
|
423
|
+
| `addFavoriteArtist(id)` | `removeFavoriteArtist(id)` |
|
|
424
|
+
| `followPlaylist(id)` | `unfollowPlaylist(id)` |
|
|
425
|
+
| `addFavoriteShow(id)` | — none found |
|
|
426
|
+
| `createPlaylist(title, opts?)` | **— none exists, see below** |
|
|
427
|
+
| `addTracksToPlaylist(id, sngIds)` | `removeTracksFromPlaylist(id, sngIds)` |
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import {addFavoriteTracks, removeFavoriteTracks} from 'gerdur-core';
|
|
431
|
+
|
|
432
|
+
await addFavoriteTracks(['3135556']); // love it
|
|
433
|
+
await removeFavoriteTracks(['3135556']); // and back
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
> **`createPlaylist` is a one-way door.** Deezer's gateway exposes no delete —
|
|
437
|
+
> a dozen spellings of `playlist.delete` all answer `GATEWAY_ERROR` — so a
|
|
438
|
+
> playlist made here has to be removed from a Deezer client. It is also the one
|
|
439
|
+
> function in this module that has not been exercised against a live account,
|
|
440
|
+
> for exactly that reason.
|
|
441
|
+
>
|
|
442
|
+
> Method names and parameters were established by probing with incomplete
|
|
443
|
+
> arguments (an existing method answers `MISSING_PARAMETER_*`), so the surface is
|
|
444
|
+
> real; the round trips live in `__tests__/favorites.ts` behind
|
|
445
|
+
> `GERDUR_ALLOW_WRITE_TESTS=1`.
|
|
446
|
+
|
|
447
|
+
Scrobbling (`log.listen`) is deliberately absent: it never answered the probe and
|
|
448
|
+
it writes history that cannot be undone.
|
|
449
|
+
|
|
412
450
|
### Podcasts
|
|
413
451
|
|
|
414
452
|
```ts
|
|
@@ -818,7 +856,11 @@ import type {
|
|
|
818
856
|
`getUserChartTracks` · `getRadios` · `getRadioTracks` · `getRadioGenres` ·
|
|
819
857
|
`getMyPlaylists` · `getMyFavoriteTracks` · `getMyFavoriteTrackIds` ·
|
|
820
858
|
`getMyFavoriteAlbums` · `getMyFavoriteArtists` · `getMyFavoritePlaylists` ·
|
|
821
|
-
`getMyFavoriteRadios` · `getMyFavoriteShows` · `getTrackMix`
|
|
859
|
+
`getMyFavoriteRadios` · `getMyFavoriteShows` · `getTrackMix` ·
|
|
860
|
+
`addFavoriteTracks` · `removeFavoriteTracks` · `addFavoriteAlbum` ·
|
|
861
|
+
`removeFavoriteAlbum` · `addFavoriteArtist` · `removeFavoriteArtist` ·
|
|
862
|
+
`followPlaylist` · `unfollowPlaylist` · `addFavoriteShow` · `createPlaylist` ·
|
|
863
|
+
`addTracksToPlaylist` · `removeTracksFromPlaylist`
|
|
822
864
|
</details>
|
|
823
865
|
|
|
824
866
|
<details>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** What the gateway returns for a mutation — usually `true`, or the new id. */
|
|
2
|
+
export type WriteResult = boolean | string | number | Record<string, any>;
|
|
3
|
+
/**
|
|
4
|
+
* Love one or more tracks (`song.addFavorites`). Reversible with
|
|
5
|
+
* {@link removeFavoriteTracks}.
|
|
6
|
+
*
|
|
7
|
+
* @param sngIds `SNG_ID`s
|
|
8
|
+
*/
|
|
9
|
+
export declare const addFavoriteTracks: (sngIds: (string | number)[]) => Promise<WriteResult>;
|
|
10
|
+
/** Un-love one or more tracks (`song.removeFavorites`). */
|
|
11
|
+
export declare const removeFavoriteTracks: (sngIds: (string | number)[]) => Promise<WriteResult>;
|
|
12
|
+
/** Add an album to favourites (`album.addFavorite`). */
|
|
13
|
+
export declare const addFavoriteAlbum: (albId: string | number) => Promise<WriteResult>;
|
|
14
|
+
/** Remove an album from favourites (`album.deleteFavorite`). */
|
|
15
|
+
export declare const removeFavoriteAlbum: (albId: string | number) => Promise<WriteResult>;
|
|
16
|
+
/** Follow an artist (`artist.addFavorite`). */
|
|
17
|
+
export declare const addFavoriteArtist: (artId: string | number) => Promise<WriteResult>;
|
|
18
|
+
/** Unfollow an artist (`artist.deleteFavorite`). */
|
|
19
|
+
export declare const removeFavoriteArtist: (artId: string | number) => Promise<WriteResult>;
|
|
20
|
+
/**
|
|
21
|
+
* Follow someone else's playlist (`playlist.addFavorite`). Note the gateway
|
|
22
|
+
* wants `parent_playlist_id` here but plain `playlist_id` to unfollow.
|
|
23
|
+
*/
|
|
24
|
+
export declare const followPlaylist: (playlistId: string | number) => Promise<WriteResult>;
|
|
25
|
+
/** Unfollow a playlist (`playlist.deleteFavorite`). */
|
|
26
|
+
export declare const unfollowPlaylist: (playlistId: string | number) => Promise<WriteResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Follow a podcast show (`show.addFavorite`).
|
|
29
|
+
*
|
|
30
|
+
* No `show.deleteFavorite` answered the probe, so treat this as one-way until
|
|
31
|
+
* the inverse is found.
|
|
32
|
+
*/
|
|
33
|
+
export declare const addFavoriteShow: (showId: string | number) => Promise<WriteResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Create a playlist (`playlist.create`). Resolves to the new `PLAYLIST_ID`.
|
|
36
|
+
*
|
|
37
|
+
* **One-way.** The gateway has no delete counterpart (see the module note), so
|
|
38
|
+
* anything created here has to be removed from a Deezer client. For the same
|
|
39
|
+
* reason this is the one function on this page that has not been exercised
|
|
40
|
+
* against a live account.
|
|
41
|
+
*
|
|
42
|
+
* @param title playlist title
|
|
43
|
+
* @param options `description`, `status` (0 public / 1 private / 2 collaborative),
|
|
44
|
+
* and `songs` to seed it with `SNG_ID`s
|
|
45
|
+
*/
|
|
46
|
+
export declare const createPlaylist: (title: string, options?: {
|
|
47
|
+
description?: string;
|
|
48
|
+
status?: 0 | 1 | 2;
|
|
49
|
+
songs?: (string | number)[];
|
|
50
|
+
}) => Promise<WriteResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Append tracks to a playlist (`playlist.addSongs`). Reversible with
|
|
53
|
+
* {@link removeTracksFromPlaylist}.
|
|
54
|
+
*
|
|
55
|
+
* The gateway takes `songs` as `[[sngId, offset], …]`; this wraps that for you.
|
|
56
|
+
*/
|
|
57
|
+
export declare const addTracksToPlaylist: (playlistId: string | number, sngIds: (string | number)[]) => Promise<WriteResult>;
|
|
58
|
+
/** Remove tracks from a playlist (`playlist.deleteSongs`). */
|
|
59
|
+
export declare const removeTracksFromPlaylist: (playlistId: string | number, sngIds: (string | number)[]) => Promise<WriteResult>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.removeTracksFromPlaylist = exports.addTracksToPlaylist = exports.createPlaylist = exports.addFavoriteShow = exports.unfollowPlaylist = exports.followPlaylist = exports.removeFavoriteArtist = exports.addFavoriteArtist = exports.removeFavoriteAlbum = exports.addFavoriteAlbum = exports.removeFavoriteTracks = exports.addFavoriteTracks = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* **Mutating** operations on the logged-in account's library.
|
|
6
|
+
*
|
|
7
|
+
* Everything else in this package reads. These change the account, so they are
|
|
8
|
+
* kept in their own module and none of them are wired into any download path —
|
|
9
|
+
* you have to call them deliberately.
|
|
10
|
+
*
|
|
11
|
+
* The method names here were established by probing the gateway with incomplete
|
|
12
|
+
* parameters, which makes an existing method answer `MISSING_PARAMETER_*` while
|
|
13
|
+
* an absent one answers `GATEWAY_ERROR` — so the surface was mapped without
|
|
14
|
+
* creating or changing anything.
|
|
15
|
+
*
|
|
16
|
+
* **Deezer's gateway exposes no way to delete a playlist.** `playlist.delete`,
|
|
17
|
+
* `.remove`, `.destroy` and nine other spellings all answer `GATEWAY_ERROR`.
|
|
18
|
+
* {@link createPlaylist} is therefore a one-way door: whatever it makes, you
|
|
19
|
+
* remove from a Deezer client, not from here. Everything else on this page has a
|
|
20
|
+
* confirmed inverse and is safe to undo.
|
|
21
|
+
*/
|
|
22
|
+
const request_1 = require("./request");
|
|
23
|
+
// ─── Loved tracks ────────────────────────────────────────────────────────────
|
|
24
|
+
/**
|
|
25
|
+
* Love one or more tracks (`song.addFavorites`). Reversible with
|
|
26
|
+
* {@link removeFavoriteTracks}.
|
|
27
|
+
*
|
|
28
|
+
* @param sngIds `SNG_ID`s
|
|
29
|
+
*/
|
|
30
|
+
const addFavoriteTracks = (sngIds) => (0, request_1.request)({ ids: sngIds.map(String) }, 'song.addFavorites');
|
|
31
|
+
exports.addFavoriteTracks = addFavoriteTracks;
|
|
32
|
+
/** Un-love one or more tracks (`song.removeFavorites`). */
|
|
33
|
+
const removeFavoriteTracks = (sngIds) => (0, request_1.request)({ ids: sngIds.map(String) }, 'song.removeFavorites');
|
|
34
|
+
exports.removeFavoriteTracks = removeFavoriteTracks;
|
|
35
|
+
// ─── Albums / artists ────────────────────────────────────────────────────────
|
|
36
|
+
/** Add an album to favourites (`album.addFavorite`). */
|
|
37
|
+
const addFavoriteAlbum = (albId) => (0, request_1.request)({ alb_id: String(albId) }, 'album.addFavorite');
|
|
38
|
+
exports.addFavoriteAlbum = addFavoriteAlbum;
|
|
39
|
+
/** Remove an album from favourites (`album.deleteFavorite`). */
|
|
40
|
+
const removeFavoriteAlbum = (albId) => (0, request_1.request)({ alb_id: String(albId) }, 'album.deleteFavorite');
|
|
41
|
+
exports.removeFavoriteAlbum = removeFavoriteAlbum;
|
|
42
|
+
/** Follow an artist (`artist.addFavorite`). */
|
|
43
|
+
const addFavoriteArtist = (artId) => (0, request_1.request)({ art_id: String(artId) }, 'artist.addFavorite');
|
|
44
|
+
exports.addFavoriteArtist = addFavoriteArtist;
|
|
45
|
+
/** Unfollow an artist (`artist.deleteFavorite`). */
|
|
46
|
+
const removeFavoriteArtist = (artId) => (0, request_1.request)({ art_id: String(artId) }, 'artist.deleteFavorite');
|
|
47
|
+
exports.removeFavoriteArtist = removeFavoriteArtist;
|
|
48
|
+
// ─── Playlists & shows ───────────────────────────────────────────────────────
|
|
49
|
+
/**
|
|
50
|
+
* Follow someone else's playlist (`playlist.addFavorite`). Note the gateway
|
|
51
|
+
* wants `parent_playlist_id` here but plain `playlist_id` to unfollow.
|
|
52
|
+
*/
|
|
53
|
+
const followPlaylist = (playlistId) => (0, request_1.request)({ parent_playlist_id: String(playlistId) }, 'playlist.addFavorite');
|
|
54
|
+
exports.followPlaylist = followPlaylist;
|
|
55
|
+
/** Unfollow a playlist (`playlist.deleteFavorite`). */
|
|
56
|
+
const unfollowPlaylist = (playlistId) => (0, request_1.request)({ playlist_id: String(playlistId) }, 'playlist.deleteFavorite');
|
|
57
|
+
exports.unfollowPlaylist = unfollowPlaylist;
|
|
58
|
+
/**
|
|
59
|
+
* Follow a podcast show (`show.addFavorite`).
|
|
60
|
+
*
|
|
61
|
+
* No `show.deleteFavorite` answered the probe, so treat this as one-way until
|
|
62
|
+
* the inverse is found.
|
|
63
|
+
*/
|
|
64
|
+
const addFavoriteShow = (showId) => (0, request_1.request)({ show_id: String(showId) }, 'show.addFavorite');
|
|
65
|
+
exports.addFavoriteShow = addFavoriteShow;
|
|
66
|
+
// ─── Playlist contents ───────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Create a playlist (`playlist.create`). Resolves to the new `PLAYLIST_ID`.
|
|
69
|
+
*
|
|
70
|
+
* **One-way.** The gateway has no delete counterpart (see the module note), so
|
|
71
|
+
* anything created here has to be removed from a Deezer client. For the same
|
|
72
|
+
* reason this is the one function on this page that has not been exercised
|
|
73
|
+
* against a live account.
|
|
74
|
+
*
|
|
75
|
+
* @param title playlist title
|
|
76
|
+
* @param options `description`, `status` (0 public / 1 private / 2 collaborative),
|
|
77
|
+
* and `songs` to seed it with `SNG_ID`s
|
|
78
|
+
*/
|
|
79
|
+
const createPlaylist = (title, options = {}) => {
|
|
80
|
+
var _a, _b, _c;
|
|
81
|
+
return (0, request_1.request)({
|
|
82
|
+
title,
|
|
83
|
+
description: (_a = options.description) !== null && _a !== void 0 ? _a : '',
|
|
84
|
+
status: (_b = options.status) !== null && _b !== void 0 ? _b : 1,
|
|
85
|
+
songs: ((_c = options.songs) !== null && _c !== void 0 ? _c : []).map((id) => [String(id), 0]),
|
|
86
|
+
}, 'playlist.create');
|
|
87
|
+
};
|
|
88
|
+
exports.createPlaylist = createPlaylist;
|
|
89
|
+
/**
|
|
90
|
+
* Append tracks to a playlist (`playlist.addSongs`). Reversible with
|
|
91
|
+
* {@link removeTracksFromPlaylist}.
|
|
92
|
+
*
|
|
93
|
+
* The gateway takes `songs` as `[[sngId, offset], …]`; this wraps that for you.
|
|
94
|
+
*/
|
|
95
|
+
const addTracksToPlaylist = (playlistId, sngIds) => (0, request_1.request)({ playlist_id: String(playlistId), songs: sngIds.map((id) => [String(id), 0]) }, 'playlist.addSongs');
|
|
96
|
+
exports.addTracksToPlaylist = addTracksToPlaylist;
|
|
97
|
+
/** Remove tracks from a playlist (`playlist.deleteSongs`). */
|
|
98
|
+
const removeTracksFromPlaylist = (playlistId, sngIds) => (0, request_1.request)({ playlist_id: String(playlistId), songs: sngIds.map((id) => [String(id), 0]) }, 'playlist.deleteSongs');
|
|
99
|
+
exports.removeTracksFromPlaylist = removeTracksFromPlaylist;
|
package/dist/api/index.d.ts
CHANGED
package/dist/api/index.js
CHANGED
package/package.json
CHANGED