@omelhorsite/sdk 0.12.1 → 0.13.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/README.md
CHANGED
|
@@ -100,7 +100,7 @@ const rows = await oms.http.get<{ id: string }[]>("/some/path");
|
|
|
100
100
|
|
|
101
101
|
## Namespaces
|
|
102
102
|
|
|
103
|
-
- `oms.auth` - OAuth: device grant, refresh, revoke, `whoami`.
|
|
103
|
+
- `oms.auth` - OAuth: device grant, refresh, revoke, `whoami`. `decodeIdToken(idToken).sub` is the stable identifier; `email` is a contact and `email_verified` is often `false`, so never find or merge users by email.
|
|
104
104
|
- `oms.sessions`, `oms.passkeys` - sign-in, sign-up, OTP, passkeys.
|
|
105
105
|
- `oms.account` - the signed-in user, profile, sessions, usage. `oms.account.notificationPreferences` decides, per notification kind, whether it shows in the inbox and whether it is emailed; the security kinds always email, unless the master switch is off.
|
|
106
106
|
- `oms.storage` - files and folders: upload, download, share.
|
package/dist/index.js
CHANGED
|
@@ -11076,6 +11076,12 @@ class AuthSessionsNamespace extends Resource {
|
|
|
11076
11076
|
async changeEmailStart(newEmail, options = {}) {
|
|
11077
11077
|
return this.http.post("/users/update_email_start", { email: newEmail }, options);
|
|
11078
11078
|
}
|
|
11079
|
+
async verifyEmailStart(options = {}) {
|
|
11080
|
+
return this.http.post("/users/verify_email_start", undefined, options);
|
|
11081
|
+
}
|
|
11082
|
+
async verifyEmailComplete(code, options = {}) {
|
|
11083
|
+
return this.http.post("/users/verify_email_end", { code }, options);
|
|
11084
|
+
}
|
|
11079
11085
|
async changeEmailComplete(input, options = {}) {
|
|
11080
11086
|
return this.http.post("/users/update_email_end", {
|
|
11081
11087
|
email: input.email,
|
|
@@ -13868,6 +13874,13 @@ class MusicSongsNamespace extends Resource {
|
|
|
13868
13874
|
async deleteSeparation(id, options = {}) {
|
|
13869
13875
|
await this.http.delete(`/songs/${encodeURIComponent(String(id))}/separation`, options);
|
|
13870
13876
|
}
|
|
13877
|
+
async matchCandidates(id, params = {}, options = {}) {
|
|
13878
|
+
const body = await this.http.get(`/songs/${encodeURIComponent(String(id))}/match_candidates`, { ...options, query: { limit: params.limit } });
|
|
13879
|
+
return body.items ?? [];
|
|
13880
|
+
}
|
|
13881
|
+
async rematch(id, input = {}, options = {}) {
|
|
13882
|
+
return this.http.post(`/songs/${encodeURIComponent(String(id))}/rematch`, input.sourceUrl === undefined ? {} : { source_url: input.sourceUrl }, options);
|
|
13883
|
+
}
|
|
13871
13884
|
async listLiked(params = {}, options = {}) {
|
|
13872
13885
|
const before = params.before instanceof Date ? params.before.toISOString() : params.before;
|
|
13873
13886
|
const rows = await this.http.get("/liked_songs", {
|
|
@@ -55,8 +55,22 @@ export interface IdentityClaims {
|
|
|
55
55
|
readonly iat?: number;
|
|
56
56
|
/** Present only when the `profile` scope was granted. Mutable, display only. */
|
|
57
57
|
readonly preferred_username?: string;
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* Present only when the `email` scope was granted. Mutable, display only.
|
|
60
|
+
*
|
|
61
|
+
* Never look a user up by this. An account can be created with any address
|
|
62
|
+
* and used without proving it, so the value may belong to someone else;
|
|
63
|
+
* matching on it lets whoever registers with a victim's address into the
|
|
64
|
+
* victim's account on your side. Key on `iss` + `sub`.
|
|
65
|
+
*/
|
|
59
66
|
readonly email?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Whether the server holds proof that `email` belongs to the user. Present
|
|
69
|
+
* whenever `email` is, and often `false`. `true` still only means the
|
|
70
|
+
* server checked it once; confirm it yourself before attaching the address
|
|
71
|
+
* to an account that already existed.
|
|
72
|
+
*/
|
|
73
|
+
readonly email_verified?: boolean;
|
|
60
74
|
readonly [claim: string]: unknown;
|
|
61
75
|
}
|
|
62
76
|
/**
|
|
@@ -30,6 +30,13 @@ export interface User extends BaseRecord {
|
|
|
30
30
|
readonly country_code?: string | null;
|
|
31
31
|
/** `en`, `pt` or `lv`: the language of the emails sent to this account. Own account only. */
|
|
32
32
|
readonly language?: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the address on the account was proven by an emailed code: the same
|
|
35
|
+
* fact the id token's `email_verified` carries. Own account only. `false`
|
|
36
|
+
* on accounts older than the emailed sign-up code until they confirm once
|
|
37
|
+
* with `sessions.verifyEmailStart` / `verifyEmailComplete`.
|
|
38
|
+
*/
|
|
39
|
+
readonly email_verified?: boolean;
|
|
33
40
|
readonly email_is_public?: boolean;
|
|
34
41
|
readonly gender_is_public?: boolean;
|
|
35
42
|
readonly library_public?: boolean;
|
|
@@ -692,6 +692,26 @@ export declare class AuthSessionsNamespace extends Resource {
|
|
|
692
692
|
* rejected at write time - already taken, malformed. Both codes are
|
|
693
693
|
* consumed by then and the flow restarts at {@link changeEmailStart}.
|
|
694
694
|
*/
|
|
695
|
+
/**
|
|
696
|
+
* `POST /users/verify_email_start` - emails a code to the address already on
|
|
697
|
+
* the account, so an account that never proved its mailbox (one older than
|
|
698
|
+
* the emailed sign-up code) can. Requires a live session; an account that is
|
|
699
|
+
* already verified answers `200` without sending anything.
|
|
700
|
+
*
|
|
701
|
+
* Same shared `*_start` throttle as the other code-issuing calls.
|
|
702
|
+
*
|
|
703
|
+
* @throws {OmsAuthError} 401 without a live session.
|
|
704
|
+
*/
|
|
705
|
+
verifyEmailStart(options?: RequestOptions): Promise<string>;
|
|
706
|
+
/**
|
|
707
|
+
* `POST /users/verify_email_end` - presents the code; answers `200` with the
|
|
708
|
+
* updated {@link User}, `email_verified` now `true`.
|
|
709
|
+
*
|
|
710
|
+
* @throws {OmsAuthError} 401 without a live session.
|
|
711
|
+
* @throws {OmsApiError} 404 `"Invalid Verification"` when the code is wrong,
|
|
712
|
+
* expired or burned.
|
|
713
|
+
*/
|
|
714
|
+
verifyEmailComplete(code: string, options?: RequestOptions): Promise<User>;
|
|
695
715
|
changeEmailComplete(input: ChangeEmailInput, options?: RequestOptions): Promise<User>;
|
|
696
716
|
/**
|
|
697
717
|
* `POST /users/destroy_start` - emails a deletion code to the address on the
|
|
@@ -60,6 +60,7 @@ import { Resource } from "../../http";
|
|
|
60
60
|
import type { ListParams } from "../../listing";
|
|
61
61
|
import { type BaseRecord, type FileInput, type FileOutput, type Id, type NativeFile, type Paginated, type RequestOptions, type Timestamp } from "../../types";
|
|
62
62
|
import type { VocalSeparation } from "../tools/vocalSeparation";
|
|
63
|
+
import type { SongImport } from "./imports";
|
|
63
64
|
/**
|
|
64
65
|
* Primary key of a song. An **integer**, unlike most ids in this API.
|
|
65
66
|
*
|
|
@@ -113,7 +114,7 @@ export interface SongArtistCredit extends Omit<BaseRecord, "id"> {
|
|
|
113
114
|
readonly compressed_image_fs_node_id?: Id | null;
|
|
114
115
|
}
|
|
115
116
|
/** Where a track came from. */
|
|
116
|
-
export type SongSourceKind = "upload" | "yt_dlp" | "spotify_sync";
|
|
117
|
+
export type SongSourceKind = "upload" | "yt_dlp" | "spotify_sync" | "rematch";
|
|
117
118
|
/**
|
|
118
119
|
* A track in the library.
|
|
119
120
|
*
|
|
@@ -254,6 +255,48 @@ export interface SongSeparationStatus {
|
|
|
254
255
|
*/
|
|
255
256
|
readonly job: VocalSeparation | null;
|
|
256
257
|
}
|
|
258
|
+
/** Providers a track can be matched against. */
|
|
259
|
+
export type SongMatchSource = "youtube" | "soundcloud" | "bandcamp";
|
|
260
|
+
/**
|
|
261
|
+
* One take the matcher found for a track, scored the way the import scores it.
|
|
262
|
+
*
|
|
263
|
+
* A candidate with `score: null` was REJECTED and `reject_reason` says why
|
|
264
|
+
* ("title says 'cover', query did not"); it is returned anyway, because the
|
|
265
|
+
* reason is often the whole answer to "why did I get this recording". Rejected
|
|
266
|
+
* candidates can still be passed to {@link MusicSongsNamespace.rematch}: a
|
|
267
|
+
* deliberate choice is not second-guessed.
|
|
268
|
+
*/
|
|
269
|
+
export interface SongMatchCandidate {
|
|
270
|
+
readonly source: SongMatchSource;
|
|
271
|
+
readonly title: string | null;
|
|
272
|
+
readonly uploader: string | null;
|
|
273
|
+
/** The page to import from. Feed it back as `sourceUrl` to take this one. */
|
|
274
|
+
readonly url: string | null;
|
|
275
|
+
/** Absent for providers whose search does not carry a runtime. */
|
|
276
|
+
readonly duration_s: number | null;
|
|
277
|
+
readonly thumbnail: string | null;
|
|
278
|
+
readonly source_id: string | null;
|
|
279
|
+
/** Higher is a better match; `null` means rejected. */
|
|
280
|
+
readonly score: number | null;
|
|
281
|
+
readonly reject_reason: string | null;
|
|
282
|
+
/** Where the provider's own search ranked it, before scoring. */
|
|
283
|
+
readonly rank: number;
|
|
284
|
+
/** True for the source the song's audio already came from. */
|
|
285
|
+
readonly current: boolean;
|
|
286
|
+
}
|
|
287
|
+
/** Arguments for {@link MusicSongsNamespace.matchCandidates}. */
|
|
288
|
+
export interface ListSongMatchCandidatesParams {
|
|
289
|
+
/** Candidates per provider. Clamped to 1..20; defaults to 8. */
|
|
290
|
+
readonly limit?: number;
|
|
291
|
+
}
|
|
292
|
+
/** Arguments for {@link MusicSongsNamespace.rematch}. */
|
|
293
|
+
export interface RematchSongInput {
|
|
294
|
+
/**
|
|
295
|
+
* The page to download from, normally a {@link SongMatchCandidate.url}.
|
|
296
|
+
* Omit to re-run the matcher over the track's original search terms.
|
|
297
|
+
*/
|
|
298
|
+
readonly sourceUrl?: string;
|
|
299
|
+
}
|
|
257
300
|
/** A liked track. The join row, with the whole song inlined. */
|
|
258
301
|
export interface LikedSong extends Omit<BaseRecord, "id"> {
|
|
259
302
|
/** Integer primary key of the like itself. NOT the song id. */
|
|
@@ -901,6 +944,49 @@ export declare class MusicSongsNamespace extends Resource {
|
|
|
901
944
|
* @throws {OmsAuthError} 401 when the song is not yours.
|
|
902
945
|
*/
|
|
903
946
|
deleteSeparation(id: SongId, options?: RequestOptions): Promise<void>;
|
|
947
|
+
/**
|
|
948
|
+
* `GET /songs/:id/match_candidates` - every take the matcher can find for
|
|
949
|
+
* this track, across all three providers, scored and ordered best first.
|
|
950
|
+
*
|
|
951
|
+
* This is the answer to "this is the wrong recording". A track imported by
|
|
952
|
+
* artist and title is matched, not looked up, and the classic wrong result is
|
|
953
|
+
* a cover: same runtime, the original artist's name in its title, and nothing
|
|
954
|
+
* about it that a duration check can catch. The pool shows what the matcher
|
|
955
|
+
* chose between, with the rejects kept and labelled.
|
|
956
|
+
*
|
|
957
|
+
* Candidates are searched live on three providers, so this is SLOW - seconds,
|
|
958
|
+
* not milliseconds - and worth its own timeout. Nothing is cached: two calls
|
|
959
|
+
* a minute apart can return different pools.
|
|
960
|
+
*
|
|
961
|
+
* Scores are comparable within one response and meaningless across two.
|
|
962
|
+
*
|
|
963
|
+
* @throws {OmsApiError} 404 for a song that is not yours, 502 when the
|
|
964
|
+
* downloader is unreachable.
|
|
965
|
+
*/
|
|
966
|
+
matchCandidates(id: SongId, params?: ListSongMatchCandidatesParams, options?: RequestOptions): Promise<SongMatchCandidate[]>;
|
|
967
|
+
/**
|
|
968
|
+
* `POST /songs/:id/rematch` - re-download this track onto the same record.
|
|
969
|
+
*
|
|
970
|
+
* The song row survives: its id, its credits, and every playlist entry, like
|
|
971
|
+
* and play event that points at it. Only the audio is replaced, along with
|
|
972
|
+
* the fields that describe the file (runtime, codec, source). Any vocal
|
|
973
|
+
* separation is thrown away, because the stems came out of audio the song no
|
|
974
|
+
* longer has.
|
|
975
|
+
*
|
|
976
|
+
* With a `sourceUrl` the pick is taken as given - no duration or cover check
|
|
977
|
+
* stands between you and the recording you named. Without one the matcher
|
|
978
|
+
* re-runs over the track's original search terms, which is the useful move
|
|
979
|
+
* after a bad match rather than a repeat of it.
|
|
980
|
+
*
|
|
981
|
+
* Returns the import to poll through `music.imports`; the swap lands when it
|
|
982
|
+
* reaches `complete`.
|
|
983
|
+
*
|
|
984
|
+
* @throws {OmsAuthError} 401 when the song is not yours.
|
|
985
|
+
* @throws {OmsApiError} 400 for a `sourceUrl` that is not http(s), or when no
|
|
986
|
+
* `sourceUrl` is given and the track has no search terms to re-run (an
|
|
987
|
+
* uploaded file, for one - there is nothing to match it against).
|
|
988
|
+
*/
|
|
989
|
+
rematch(id: SongId, input?: RematchSongInput, options?: RequestOptions): Promise<SongImport>;
|
|
904
990
|
/**
|
|
905
991
|
* `GET /liked_songs` - the caller's likes, newest first, with each song
|
|
906
992
|
* inlined in full.
|
package/package.json
CHANGED