@omelhorsite/sdk 0.3.0 → 0.4.1
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 +113 -131
- package/dist/index.js +1049 -1246
- package/dist/types/auth/device.d.ts +1 -1
- package/dist/types/auth/index.d.ts +2 -2
- package/dist/types/auth/tokens.d.ts +15 -15
- package/dist/types/client.d.ts +10 -10
- package/dist/types/errors.d.ts +12 -15
- package/dist/types/http.d.ts +74 -118
- package/dist/types/index.d.ts +2 -2
- package/dist/types/internal/attachments.d.ts +17 -0
- package/dist/types/internal/helpers.d.ts +9 -0
- package/dist/types/listing.d.ts +90 -0
- package/dist/types/local/qr.d.ts +1 -1
- package/dist/types/local/wordlist.d.ts +2 -3
- package/dist/types/resources/account.d.ts +19 -19
- package/dist/types/resources/admin/authorizedApplications.d.ts +133 -0
- package/dist/types/resources/admin/chests.d.ts +51 -0
- package/dist/types/resources/admin/eventAlerts.d.ts +60 -0
- package/dist/types/resources/admin/identities.d.ts +99 -0
- package/dist/types/resources/admin/index.d.ts +77 -0
- package/dist/types/resources/admin/jobs.d.ts +97 -0
- package/dist/types/resources/admin/myOauthApplications.d.ts +282 -0
- package/dist/types/resources/admin/notepads.d.ts +49 -0
- package/dist/types/resources/admin/oauthApplications.d.ts +309 -0
- package/dist/types/resources/admin/quotas.d.ts +114 -0
- package/dist/types/resources/admin/shortLinks.d.ts +201 -0
- package/dist/types/resources/admin/types.d.ts +222 -0
- package/dist/types/resources/admin/vocalSeparations.d.ts +98 -0
- package/dist/types/resources/admin.d.ts +17 -13
- package/dist/types/resources/auth/index.d.ts +1 -1
- package/dist/types/resources/auth/passkeys.d.ts +127 -163
- package/dist/types/resources/auth/sessions.d.ts +116 -161
- package/dist/types/resources/chests.d.ts +27 -31
- package/dist/types/resources/content/analysis.d.ts +62 -0
- package/dist/types/resources/content/blogs.d.ts +471 -0
- package/dist/types/resources/content/feedbacks.d.ts +263 -0
- package/dist/types/resources/content/index.d.ts +52 -0
- package/dist/types/resources/content/intel/articles.d.ts +230 -0
- package/dist/types/resources/content/intel/config.d.ts +135 -0
- package/dist/types/resources/content/intel/index.d.ts +53 -0
- package/dist/types/resources/content/intel/items.d.ts +91 -0
- package/dist/types/resources/content/intel/reports.d.ts +108 -0
- package/dist/types/resources/content/intel/scripts.d.ts +152 -0
- package/dist/types/resources/content/intel/sources.d.ts +236 -0
- package/dist/types/resources/content/intel/stats.d.ts +105 -0
- package/dist/types/resources/content/intel/types.d.ts +86 -0
- package/dist/types/resources/content/jokes.d.ts +93 -0
- package/dist/types/resources/content/notifications.d.ts +147 -0
- package/dist/types/resources/content/serviceUsages.d.ts +73 -0
- package/dist/types/resources/content/servicesStatus.d.ts +194 -0
- package/dist/types/resources/content/siteConfig.d.ts +43 -0
- package/dist/types/resources/content/spaceInvaders.d.ts +154 -0
- package/dist/types/resources/content.d.ts +41 -236
- package/dist/types/resources/dynamicQrs.d.ts +29 -45
- package/dist/types/resources/forms.d.ts +37 -58
- package/dist/types/resources/jobs.d.ts +35 -44
- package/dist/types/resources/library/annotations.d.ts +162 -0
- package/dist/types/resources/library/books.d.ts +534 -0
- package/dist/types/resources/library/chat.d.ts +259 -0
- package/dist/types/resources/library/index.d.ts +39 -0
- package/dist/types/resources/library/shelves.d.ts +237 -0
- package/dist/types/resources/library/types.d.ts +120 -0
- package/dist/types/resources/library.d.ts +20 -22
- package/dist/types/resources/media.d.ts +48 -61
- package/dist/types/resources/movies/addons.d.ts +388 -0
- package/dist/types/resources/movies/collections.d.ts +352 -0
- package/dist/types/resources/movies/index.d.ts +29 -0
- package/dist/types/resources/movies/types.d.ts +50 -0
- package/dist/types/resources/movies/watchProgress.d.ts +277 -0
- package/dist/types/resources/movies.d.ts +27 -53
- package/dist/types/resources/music/artists.d.ts +182 -246
- package/dist/types/resources/music/imports.d.ts +190 -218
- package/dist/types/resources/music/index.d.ts +8 -7
- package/dist/types/resources/music/playlists.d.ts +85 -117
- package/dist/types/resources/music/social.d.ts +153 -228
- package/dist/types/resources/music/songs.d.ts +165 -227
- package/dist/types/resources/realtime.d.ts +75 -88
- package/dist/types/resources/shortLinks.d.ts +38 -47
- package/dist/types/resources/social/groupChats.d.ts +458 -0
- package/dist/types/resources/social/index.d.ts +19 -0
- package/dist/types/resources/social/messages.d.ts +424 -0
- package/dist/types/resources/social/relationships.d.ts +322 -0
- package/dist/types/resources/social/types.d.ts +37 -0
- package/dist/types/resources/social.d.ts +21 -3
- package/dist/types/resources/storage/upload.d.ts +42 -56
- package/dist/types/resources/storage.d.ts +94 -109
- package/dist/types/resources/tickets.d.ts +19 -10
- package/dist/types/resources/tools/backgroundRemoval.d.ts +11 -13
- package/dist/types/resources/tools/captions.d.ts +107 -135
- package/dist/types/resources/tools/upscale.d.ts +12 -16
- package/dist/types/types.d.ts +29 -38
- package/package.json +1 -1
|
@@ -2,31 +2,28 @@
|
|
|
2
2
|
* The `music.imports` namespace: getting audio INTO the library, and keeping a
|
|
3
3
|
* Spotify account mirrored into it.
|
|
4
4
|
*
|
|
5
|
-
* Four
|
|
5
|
+
* Four route families sit behind one namespace because they are the four
|
|
6
6
|
* doors a track can walk through:
|
|
7
7
|
*
|
|
8
8
|
* | Route family | What it is | Who may call it |
|
|
9
9
|
* |---|---|---|
|
|
10
|
-
* | `/song_imports` | one track, downloaded
|
|
10
|
+
* | `/song_imports` | one track, downloaded from a URL or found by search | any signed-in user |
|
|
11
11
|
* | `/playlist_imports/preview` | read a playlist URL without importing it | any signed-in user |
|
|
12
|
-
* | `/spotify_syncs/*` | mirror a Spotify account into playlists |
|
|
12
|
+
* | `/spotify_syncs/*` | mirror a Spotify account into playlists | Spotify-enabled accounts only |
|
|
13
13
|
* | `/s_r_machine/*` | raw fetch + transcode helpers | **admins only** |
|
|
14
14
|
*
|
|
15
15
|
* ## An import is not a `Job`
|
|
16
16
|
*
|
|
17
|
-
* `POST /song_imports` answers a
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
17
|
+
* `POST /song_imports` answers a song import, not a generic job, and there is
|
|
18
|
+
* no `job_id` anywhere on it. So `oms.jobs.get()` will never find it and
|
|
19
|
+
* `watch_token` means nothing here.
|
|
20
20
|
*
|
|
21
|
-
* What this namespace DOES
|
|
22
|
-
* MusicImportsNamespace.wait} and {@link MusicImportsNamespace.watch}
|
|
23
|
-
* `
|
|
24
|
-
*
|
|
25
|
-
* {@link
|
|
26
|
-
*
|
|
27
|
-
* polling engine in this SDK and this is not a second one. The same is true of
|
|
28
|
-
* {@link SpotifySyncNamespace.waitForSync}, which binds `poll` to
|
|
29
|
-
* `GET /spotify_syncs/status`.
|
|
21
|
+
* What this namespace DOES share with `oms.jobs` is the polling loop:
|
|
22
|
+
* {@link MusicImportsNamespace.wait} and {@link MusicImportsNamespace.watch}
|
|
23
|
+
* poll `GET /song_imports/:id` until {@link isSongImportTerminal}, with the
|
|
24
|
+
* same backoff, the same `waitTimeoutMs`, the same `signal` semantics and the
|
|
25
|
+
* same two `OmsTimeoutError` codes. {@link SpotifySyncNamespace.waitForSync}
|
|
26
|
+
* does the same against `GET /spotify_syncs/status`.
|
|
30
27
|
*
|
|
31
28
|
* Three scales, all called "progress", none of them the same number:
|
|
32
29
|
*
|
|
@@ -41,19 +38,17 @@
|
|
|
41
38
|
*
|
|
42
39
|
* Everything here sits under the general authenticated ceiling of 600/min
|
|
43
40
|
* except {@link MusicImportsNamespace.previewPlaylist}, which carries TWO
|
|
44
|
-
* budgets of its own
|
|
45
|
-
* been used to take the site down. Read its doc comment before writing a loop
|
|
46
|
-
* around it.
|
|
41
|
+
* budgets of its own. Read its doc comment before writing a loop around it.
|
|
47
42
|
*
|
|
48
43
|
* ## OAuth tokens cannot reach any of this
|
|
49
44
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* session cookie or a personal token, like the rest of music.
|
|
45
|
+
* An OAuth access token gets `403 {"error":"insufficient_scope"}` on every
|
|
46
|
+
* route here. Imports need a session cookie or a personal token, like the rest
|
|
47
|
+
* of music.
|
|
54
48
|
*/
|
|
55
49
|
import { type ApiClient, Resource } from "../../http";
|
|
56
|
-
import type {
|
|
50
|
+
import type { ListParams } from "../../listing";
|
|
51
|
+
import type { BaseRecord, FileInput, FileOutput, Id, NativeFile, Paginated, Progress, RequestOptions, Timestamp, WaitOptions } from "../../types";
|
|
57
52
|
import type { DownloaderPreview } from "../tools/downloader";
|
|
58
53
|
/**
|
|
59
54
|
* Primary key of a song import. An **integer**, like `songs` and `playlists`
|
|
@@ -72,7 +67,7 @@ export type SongImportId = number | string;
|
|
|
72
67
|
* jobs namespace has a dead branch here and a missing one there.
|
|
73
68
|
*/
|
|
74
69
|
export type SongImportState = "pending" | "processing" | "complete" | "failed";
|
|
75
|
-
/** The four states
|
|
70
|
+
/** The four states. */
|
|
76
71
|
export declare const SONG_IMPORT_STATES: Readonly<{
|
|
77
72
|
readonly pending: "pending";
|
|
78
73
|
readonly processing: "processing";
|
|
@@ -80,8 +75,8 @@ export declare const SONG_IMPORT_STATES: Readonly<{
|
|
|
80
75
|
readonly failed: "failed";
|
|
81
76
|
}>;
|
|
82
77
|
/**
|
|
83
|
-
* The states
|
|
84
|
-
*
|
|
78
|
+
* The states that are final, and the ones {@link MusicImportsNamespace.wait}
|
|
79
|
+
* stops on.
|
|
85
80
|
*
|
|
86
81
|
* `"pending"` is deliberately absent even though a row can go BACK to it: see
|
|
87
82
|
* {@link isSongImportTerminal}.
|
|
@@ -90,28 +85,26 @@ export declare const SONG_IMPORT_TERMINAL_STATES: readonly SongImportState[];
|
|
|
90
85
|
/**
|
|
91
86
|
* True once this state can never change again.
|
|
92
87
|
*
|
|
93
|
-
* The state machine is NOT monotonic on the way there.
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
88
|
+
* The state machine is NOT monotonic on the way there. A transient download
|
|
89
|
+
* error puts the row back to `"pending"` with `progress_pct: 0.0` before a
|
|
90
|
+
* retry, so a poller legitimately observes `processing -> pending ->
|
|
91
|
+
* processing -> complete` and a progress bar legitimately goes backwards.
|
|
92
|
+
* Never latch a UI on "it was processing, so it cannot be pending again"; read
|
|
93
|
+
* the state every time.
|
|
99
94
|
*
|
|
100
|
-
* `"failed"`, by contrast, is only ever written when the
|
|
101
|
-
* retry, so it really is the end.
|
|
95
|
+
* `"failed"`, by contrast, is only ever written when the server has decided
|
|
96
|
+
* NOT to retry, so it really is the end.
|
|
102
97
|
*/
|
|
103
98
|
export declare function isSongImportTerminal(state: string): boolean;
|
|
104
99
|
/**
|
|
105
|
-
* One
|
|
100
|
+
* One song import.
|
|
106
101
|
*
|
|
107
|
-
*
|
|
108
|
-
* `ApplicationBlueprint`, so every key below is present on every response;
|
|
109
|
-
* what varies is the value.
|
|
102
|
+
* Every key below is present on every response; what varies is the value.
|
|
110
103
|
*
|
|
111
|
-
* ## Five
|
|
104
|
+
* ## Five fields you can write but never read back
|
|
112
105
|
*
|
|
113
106
|
* `search_artist`, `search_title`, `search_album`, `isrc` and `artwork_url` are
|
|
114
|
-
* all accepted by `POST /song_imports` and NONE of them is
|
|
107
|
+
* all accepted by `POST /song_imports` and NONE of them is echoed back.
|
|
115
108
|
* `artwork_data_b64` likewise (and it is wiped from the row the moment the
|
|
116
109
|
* import settles, deduped or not). So an import created in search mode answers
|
|
117
110
|
* with `source_url: null` and no trace of what was searched for: if the caller
|
|
@@ -140,33 +133,32 @@ export interface SongImport extends Omit<BaseRecord, "id"> {
|
|
|
140
133
|
/** The provider's own id for the track. Half of the dedupe key. */
|
|
141
134
|
readonly source_id: string | null;
|
|
142
135
|
/**
|
|
143
|
-
* `"yt_dlp"` (the
|
|
144
|
-
* `"spotify_sync"` (written by
|
|
145
|
-
*
|
|
146
|
-
* these.
|
|
136
|
+
* `"yt_dlp"` (the default, and what every user-driven import is) or
|
|
137
|
+
* `"spotify_sync"` (written by the Spotify sync, one row per track per run).
|
|
138
|
+
* Never `"upload"` - that is a `Song.source_kind` value, not one of these.
|
|
147
139
|
*/
|
|
148
140
|
readonly source_kind: string;
|
|
149
141
|
readonly override_title: string | null;
|
|
150
142
|
readonly override_artist: string | null;
|
|
151
143
|
readonly override_album: string | null;
|
|
152
|
-
/** Seconds, as a float. A hint the
|
|
144
|
+
/** Seconds, as a float. A hint the downloader uses to pick between candidates. */
|
|
153
145
|
readonly expected_duration_s: number | null;
|
|
154
146
|
/** Requested position in {@link SongImport.playlist_id}. See the note on `create`. */
|
|
155
147
|
readonly position: number | null;
|
|
156
|
-
/** The
|
|
148
|
+
/** The downloader's own request id. Diagnostics only; nothing here takes it. */
|
|
157
149
|
readonly sidecar_request_id: string | null;
|
|
158
150
|
readonly state: SongImportState;
|
|
159
151
|
/**
|
|
160
|
-
* Free text from the
|
|
161
|
-
* whatever
|
|
152
|
+
* Free text from the downloader (`"starting"`, `"complete"`, `"a repetir"`,
|
|
153
|
+
* or whatever it last said). Human-facing, not a state: switch on
|
|
162
154
|
* {@link SongImport.state}.
|
|
163
155
|
*/
|
|
164
156
|
readonly progress_message: string | null;
|
|
165
157
|
/**
|
|
166
158
|
* A FLOAT between 0 and 1, not a percentage. `0.05` means 5%.
|
|
167
159
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
160
|
+
* It is a real number from the moment the row exists. It can go DOWN - a
|
|
161
|
+
* transient retry resets it to `0.0`.
|
|
170
162
|
* Multiply by 100 before showing it, or use {@link songImportProgress}.
|
|
171
163
|
*/
|
|
172
164
|
readonly progress_pct: number;
|
|
@@ -192,22 +184,21 @@ export interface SongImport extends Omit<BaseRecord, "id"> {
|
|
|
192
184
|
* Exactly one of the two modes must be satisfied, and the server checks it in
|
|
193
185
|
* this order:
|
|
194
186
|
*
|
|
195
|
-
* 1. **URL mode** - `sourceUrl` present. It must
|
|
196
|
-
*
|
|
197
|
-
*
|
|
187
|
+
* 1. **URL mode** - `sourceUrl` present. It must be a public http(s) URL:
|
|
188
|
+
* public DNS only, no redirect to a private address. Anything else is
|
|
189
|
+
* `400 "source_url is not allowed"`.
|
|
198
190
|
* 2. **Search mode** - `searchArtist` AND `searchTitle` both non-blank, with no
|
|
199
|
-
* `sourceUrl`. The
|
|
191
|
+
* `sourceUrl`. The server goes and finds the track itself.
|
|
200
192
|
*
|
|
201
193
|
* Neither one satisfied is `400 "source_url or (search_artist + search_title)
|
|
202
194
|
* required"`. One without the other counts as neither: `searchArtist` alone is
|
|
203
195
|
* not search mode.
|
|
204
196
|
*
|
|
205
|
-
* Every other field is optional and every unknown field is dropped in silence
|
|
206
|
-
*
|
|
207
|
-
* successful import that ignored what you asked for.
|
|
197
|
+
* Every other field is optional and every unknown field is dropped in silence,
|
|
198
|
+
* so a typo here is a successful import that ignored what you asked for.
|
|
208
199
|
*/
|
|
209
200
|
export interface CreateSongImportInput {
|
|
210
|
-
/** URL mode. Public http(s) only; see the
|
|
201
|
+
/** URL mode. Public http(s) only; see the note above. */
|
|
211
202
|
readonly sourceUrl?: string;
|
|
212
203
|
/** Search mode. Needs {@link CreateSongImportInput.searchTitle} beside it. */
|
|
213
204
|
readonly searchArtist?: string;
|
|
@@ -227,22 +218,21 @@ export interface CreateSongImportInput {
|
|
|
227
218
|
readonly sourceId?: string;
|
|
228
219
|
/**
|
|
229
220
|
* Defaults server-side to `"yt_dlp"`. Passing `"spotify_sync"` by hand is a
|
|
230
|
-
* bad idea: it
|
|
231
|
-
*
|
|
232
|
-
* playlist-position rewriting the sync does.
|
|
221
|
+
* bad idea: it moves the import onto the slow bulk lane and makes the row
|
|
222
|
+
* eligible for the playlist-position rewriting the sync does.
|
|
233
223
|
*/
|
|
234
224
|
readonly sourceKind?: string;
|
|
235
|
-
/** Tag overrides written onto the finished song instead of what
|
|
225
|
+
/** Tag overrides written onto the finished song instead of what the download carried. */
|
|
236
226
|
readonly overrideTitle?: string;
|
|
237
227
|
readonly overrideArtist?: string;
|
|
238
228
|
readonly overrideAlbum?: string;
|
|
239
|
-
/** Cover to embed.
|
|
229
|
+
/** Cover to embed. Must be a public http(s) URL, exactly like `sourceUrl`. */
|
|
240
230
|
readonly artworkUrl?: string;
|
|
241
231
|
/**
|
|
242
|
-
* Cover as base64, for a picture that has no public URL.
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
*
|
|
232
|
+
* Cover as base64, for a picture that has no public URL. Wiped as soon as
|
|
233
|
+
* the import settles. Keep it small: it is a JSON body, not a multipart
|
|
234
|
+
* upload, and the CDN in front of the API caps a request body at roughly
|
|
235
|
+
* 100 MB.
|
|
246
236
|
*/
|
|
247
237
|
readonly artworkDataB64?: string;
|
|
248
238
|
/** Seconds. A hint, not a constraint. */
|
|
@@ -265,51 +255,50 @@ export interface CreateSongImportInput {
|
|
|
265
255
|
/**
|
|
266
256
|
* Filters for {@link MusicImportsNamespace.list}.
|
|
267
257
|
*
|
|
268
|
-
* The
|
|
258
|
+
* The server allowlists six columns and nothing else. An unrecognised
|
|
269
259
|
* `search[...]` key is a `400` naming it, never a silently wider result.
|
|
270
260
|
*/
|
|
271
|
-
export interface ListSongImportsParams extends
|
|
272
|
-
readonly state?: SongImportState | SongImportState[];
|
|
273
|
-
readonly playlistId?: number | string |
|
|
274
|
-
/** Only ever your own id: the
|
|
261
|
+
export interface ListSongImportsParams extends ListParams<(typeof SONG_IMPORT_FILTER_COLUMNS)[number]> {
|
|
262
|
+
readonly state?: SongImportState | readonly SongImportState[];
|
|
263
|
+
readonly playlistId?: number | string | ReadonlyArray<number | string>;
|
|
264
|
+
/** Only ever your own id: the listing is scoped to the caller. */
|
|
275
265
|
readonly userId?: Id;
|
|
276
|
-
readonly id?: SongImportId | SongImportId[];
|
|
266
|
+
readonly id?: SongImportId | readonly SongImportId[];
|
|
277
267
|
}
|
|
278
|
-
/**
|
|
279
|
-
export declare const SONG_IMPORT_FILTER_COLUMNS: readonly
|
|
268
|
+
/** Filter columns of `GET /song_imports`. */
|
|
269
|
+
export declare const SONG_IMPORT_FILTER_COLUMNS: readonly ["id", "state", "playlist_id", "user_id", "created_at", "updated_at"];
|
|
280
270
|
/**
|
|
281
271
|
* What `POST /playlist_imports/preview` answers: the SAME `DownloaderPreview`
|
|
282
|
-
* shape `oms.tools.downloader.preview()` returns, because both
|
|
283
|
-
*
|
|
272
|
+
* shape `oms.tools.downloader.preview()` returns, because both read metadata
|
|
273
|
+
* the same way.
|
|
284
274
|
*
|
|
285
275
|
* Check `kind` first: a `"playlist"` carries `count` and `tracks` and none of
|
|
286
276
|
* the track fields, a `"track"` carries the track fields and neither of those.
|
|
287
|
-
* Note
|
|
288
|
-
*
|
|
277
|
+
* Note that `formats` is absent here even on a `"track"` - unlike the
|
|
278
|
+
* downloader's own preview.
|
|
289
279
|
*/
|
|
290
280
|
export type PlaylistImportPreview = DownloaderPreview;
|
|
291
281
|
/**
|
|
292
282
|
* Sustained ceiling on `POST /playlist_imports/preview`: 60 an hour, keyed by
|
|
293
|
-
* user id.
|
|
283
|
+
* user id.
|
|
294
284
|
*/
|
|
295
285
|
export declare const PLAYLIST_IMPORT_PREVIEW_HOURLY_LIMIT = 60;
|
|
296
286
|
/**
|
|
297
|
-
* Burst ceiling on the same route: 20 a minute, from
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
* `/
|
|
287
|
+
* Burst ceiling on the same route: 20 a minute, from a bucket SHARED with
|
|
288
|
+
* every other expensive tool (`/upscales`, `/vocal_separations`,
|
|
289
|
+
* `/transcriptions`, `/caption_jobs`, `/jumpstyle_jobs`, `/songs/:id/separate`,
|
|
290
|
+
* `/tools_downloader/*`).
|
|
301
291
|
*/
|
|
302
292
|
export declare const PLAYLIST_IMPORT_PREVIEW_BURST_LIMIT_PER_MINUTE = 20;
|
|
303
293
|
/**
|
|
304
|
-
* How long
|
|
294
|
+
* How long the server waits on a download before giving up on one import:
|
|
305
295
|
* ten minutes. A sensible floor for `waitTimeoutMs`, and the reason a shorter
|
|
306
296
|
* one is a client-side deadline rather than a cancellation.
|
|
307
297
|
*/
|
|
308
298
|
export declare const SONG_IMPORT_SERVER_TIMEOUT_MS = 600000;
|
|
309
299
|
/**
|
|
310
|
-
* Default pause between polls of a song import: 1.5s
|
|
311
|
-
*
|
|
312
|
-
* progress every 3 seconds anyway.
|
|
300
|
+
* Default pause between polls of a song import: 1.5s. Slower than the jobs
|
|
301
|
+
* default because progress is only written every 3 seconds anyway.
|
|
313
302
|
*/
|
|
314
303
|
export declare const SONG_IMPORT_POLL_INTERVAL_MS = 1500;
|
|
315
304
|
/**
|
|
@@ -323,7 +312,7 @@ export declare const SONG_IMPORT_POLL_INTERVAL_MS = 1500;
|
|
|
323
312
|
export declare function songImportProgress(record: SongImport): Progress;
|
|
324
313
|
/** The `music.imports` namespace, reachable as `oms.music.imports`. */
|
|
325
314
|
export declare class MusicImportsNamespace extends Resource {
|
|
326
|
-
/** Spotify account mirroring.
|
|
315
|
+
/** Spotify account mirroring. Spotify-enabled accounts only - see the class. */
|
|
327
316
|
readonly spotify: SpotifySyncNamespace;
|
|
328
317
|
/** Admin-only fetch and transcode helpers. */
|
|
329
318
|
readonly srMachine: SRMachineNamespace;
|
|
@@ -331,12 +320,12 @@ export declare class MusicImportsNamespace extends Resource {
|
|
|
331
320
|
/**
|
|
332
321
|
* `GET /song_imports` - your import history, newest first only if you ask.
|
|
333
322
|
*
|
|
334
|
-
* Scoped
|
|
335
|
-
*
|
|
323
|
+
* Scoped to the caller, so `userId` can only ever narrow to yourself and a
|
|
324
|
+
* foreign one answers an empty page rather than a 403.
|
|
336
325
|
*
|
|
337
|
-
* **The
|
|
338
|
-
*
|
|
339
|
-
*
|
|
326
|
+
* **The list is pruned.** `complete` and `deduped` rows older than 30 days
|
|
327
|
+
* and `failed` rows older than 90 are deleted, so this is a recent-activity
|
|
328
|
+
* feed, not an archive. It is also dominated by
|
|
340
329
|
* `spotify_sync` rows on any account with the sync on - one per track per
|
|
341
330
|
* daily run, thousands a day - so filter by `state` or `playlistId` unless
|
|
342
331
|
* you actually want that.
|
|
@@ -349,26 +338,26 @@ export declare class MusicImportsNamespace extends Resource {
|
|
|
349
338
|
* `GET /song_imports/:id` - one read, no waiting.
|
|
350
339
|
*
|
|
351
340
|
* @throws {OmsApiError} 404 `"song import not found"` for an id that is not
|
|
352
|
-
* yours, never a 403 - and for one
|
|
341
|
+
* yours, never a 403 - and for one that has already been pruned.
|
|
353
342
|
*/
|
|
354
343
|
get(id: SongImportId, options?: RequestOptions): Promise<SongImport>;
|
|
355
344
|
/**
|
|
356
345
|
* `POST /song_imports` - 201 with the row, before any downloading starts.
|
|
357
346
|
*
|
|
358
347
|
* Returns immediately in every case. Either the row is already terminal
|
|
359
|
-
* because dedupe hit ({@link SongImport.deduped}), or it is `"pending"` and
|
|
360
|
-
*
|
|
348
|
+
* because dedupe hit ({@link SongImport.deduped}), or it is `"pending"` and
|
|
349
|
+
* the download has been queued. Nothing about this call waits for audio.
|
|
361
350
|
*
|
|
362
|
-
* Which
|
|
363
|
-
* `"spotify_sync"` goes on the
|
|
364
|
-
* watching a spinner is not queued behind
|
|
365
|
-
*
|
|
351
|
+
* Which lane the download runs on depends on `sourceKind`: anything but
|
|
352
|
+
* `"spotify_sync"` goes on the interactive lane precisely so a person
|
|
353
|
+
* watching a spinner is not queued behind thousands of sync imports. Leave
|
|
354
|
+
* `sourceKind` alone and you get the good lane.
|
|
366
355
|
*
|
|
367
356
|
* ## The 401 that is really a 403
|
|
368
357
|
*
|
|
369
358
|
* `playlistId` pointing at a playlist you cannot update answers
|
|
370
|
-
* `401 "playlist not yours"
|
|
371
|
-
*
|
|
359
|
+
* `401 "playlist not yours"`, an authorization failure wearing an
|
|
360
|
+
* authentication status. Two consequences: it is an {@link OmsAuthError}, not an
|
|
372
361
|
* {@link OmsApiError}, so a `catch` sorting by class puts it in the wrong
|
|
373
362
|
* pile; and if the client was built with a token provider that implements
|
|
374
363
|
* `onUnauthorized`, the transport spends one pointless refresh on it before
|
|
@@ -376,8 +365,8 @@ export declare class MusicImportsNamespace extends Resource {
|
|
|
376
365
|
*
|
|
377
366
|
* @throws {TypeError} before any request when neither mode is satisfied.
|
|
378
367
|
* @throws {OmsApiError} 400 `"source_url is not allowed"` /
|
|
379
|
-
* `"artwork_url is not allowed"`
|
|
380
|
-
* missing mode, `404 "playlist not found"`.
|
|
368
|
+
* `"artwork_url is not allowed"` for a URL that is not public http(s),
|
|
369
|
+
* `400` naming the missing mode, `404 "playlist not found"`.
|
|
381
370
|
* @throws {OmsAuthError} 401 `"playlist not yours"`. See above.
|
|
382
371
|
*/
|
|
383
372
|
create(input: CreateSongImportInput, options?: RequestOptions): Promise<SongImport>;
|
|
@@ -390,12 +379,12 @@ export declare class MusicImportsNamespace extends Resource {
|
|
|
390
379
|
* semantics. `onProgress` is fed through {@link songImportProgress}, so its
|
|
391
380
|
* `loaded` is out of 100 even though the wire value is out of 1.
|
|
392
381
|
*
|
|
393
|
-
* Resolves for `"failed"` as well as `"complete"`: a download the
|
|
382
|
+
* Resolves for `"failed"` as well as `"complete"`: a download the server
|
|
394
383
|
* could not do is an ANSWER. Check `state` before reading `song_id`.
|
|
395
384
|
*
|
|
396
|
-
* `waitTimeoutMs` has no default here either.
|
|
397
|
-
*
|
|
398
|
-
*
|
|
385
|
+
* `waitTimeoutMs` has no default here either. The server gives a download
|
|
386
|
+
* ten minutes ({@link SONG_IMPORT_SERVER_TIMEOUT_MS}) and may then retry it,
|
|
387
|
+
* so a real import can outlive any deadline you pick; a deadline
|
|
399
388
|
* here abandons the WAIT, never the import, which keeps running and can be
|
|
400
389
|
* read later with {@link get}.
|
|
401
390
|
*
|
|
@@ -430,25 +419,20 @@ export declare class MusicImportsNamespace extends Resource {
|
|
|
430
419
|
* Nothing is written and nothing is enqueued, which is exactly why it is easy
|
|
431
420
|
* to mistake for cheap.
|
|
432
421
|
*
|
|
433
|
-
* ## It is not cheap
|
|
422
|
+
* ## It is not cheap
|
|
434
423
|
*
|
|
435
|
-
* Each call
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
* serves. On 2026-07-27 a load generator drove this endpoint at roughly 900
|
|
439
|
-
* requests a minute with each one parked 20-40 seconds, and the whole API -
|
|
440
|
-
* health check included - went down with it. At the time it was the one
|
|
441
|
-
* yt-dlp-backed route with neither a rack-attack rule nor a controller-level
|
|
442
|
-
* limit. It now has both:
|
|
424
|
+
* Each call fetches metadata for a URL the CALLER chose and holds a server
|
|
425
|
+
* thread for up to 60 seconds while it does. It therefore carries two
|
|
426
|
+
* budgets:
|
|
443
427
|
*
|
|
444
428
|
* - {@link PLAYLIST_IMPORT_PREVIEW_HOURLY_LIMIT} 60 an hour, keyed by user
|
|
445
|
-
* id
|
|
429
|
+
* id. This one does NOT set `Retry-After`;
|
|
446
430
|
* - {@link PLAYLIST_IMPORT_PREVIEW_BURST_LIMIT_PER_MINUTE} 20 a minute, from
|
|
447
|
-
*
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
*
|
|
431
|
+
* a bucket SHARED with upscale, background removal, transcription, vocal
|
|
432
|
+
* separation, captions, jumpstyle, `/songs/:id/separate` and the
|
|
433
|
+
* downloader. Importing a playlist while a separation is running spends
|
|
434
|
+
* the same budget. This one does set `Retry-After`, which the transport
|
|
435
|
+
* honours.
|
|
452
436
|
*
|
|
453
437
|
* So: one preview per user action, never one per row of a list, and never
|
|
454
438
|
* inside a retry loop. This method therefore does NOT retry by default -
|
|
@@ -456,11 +440,11 @@ export declare class MusicImportsNamespace extends Resource {
|
|
|
456
440
|
* no, which a replay will not change. Pass `retry: {}` to opt back in.
|
|
457
441
|
*
|
|
458
442
|
* @throws {OmsApiError} 400 `"url is required"` when blank; 400
|
|
459
|
-
* `"url is not allowed"`
|
|
460
|
-
* at `/account/dashboard` for ANY `open.spotify.com` or
|
|
461
|
-
* Spotify is never previewed here, it goes through
|
|
443
|
+
* `"url is not allowed"` for a URL that is not public http(s); 400 with a
|
|
444
|
+
* message pointing at `/account/dashboard` for ANY `open.spotify.com` or
|
|
445
|
+
* `spotify.com` URL - Spotify is never previewed here, it goes through
|
|
462
446
|
* {@link MusicImportsNamespace.spotify}; 502 carrying the first 200
|
|
463
|
-
* characters of
|
|
447
|
+
* characters of the downloader's own error.
|
|
464
448
|
* @throws {OmsQuotaError} 429 from either budget above.
|
|
465
449
|
*/
|
|
466
450
|
previewPlaylist(url: string, options?: RequestOptions): Promise<PlaylistImportPreview>;
|
|
@@ -482,8 +466,8 @@ export type SpotifySyncPlaylistState = "pending" | "running" | "complete" | "fai
|
|
|
482
466
|
*
|
|
483
467
|
* `queued` and `skipped` count TRACKS, and together they are the walk's
|
|
484
468
|
* progress against `total`. What they are not is downloads: `queued` means a
|
|
485
|
-
*
|
|
486
|
-
*
|
|
469
|
+
* song import was created on the slow bulk lane. The audio arrives minutes or
|
|
470
|
+
* hours later.
|
|
487
471
|
*/
|
|
488
472
|
export interface SpotifySyncPlaylistProgress {
|
|
489
473
|
/** Spotify's playlist id, or the literal `"liked"` for the liked-songs mirror. */
|
|
@@ -499,11 +483,10 @@ export interface SpotifySyncPlaylistProgress {
|
|
|
499
483
|
}
|
|
500
484
|
/**
|
|
501
485
|
* Progress of the last (or current) sync run, stored on the Spotify identity
|
|
502
|
-
* rather than
|
|
486
|
+
* rather than as a job.
|
|
503
487
|
*
|
|
504
|
-
* Every key is optional here because a freshly linked identity carries `{}`
|
|
505
|
-
* the
|
|
506
|
-
* sync starts. Treat a missing `state` as `"idle"`.
|
|
488
|
+
* Every key is optional here because a freshly linked identity carries `{}`
|
|
489
|
+
* until the first sync starts. Treat a missing `state` as `"idle"`.
|
|
507
490
|
*/
|
|
508
491
|
export interface SpotifySyncProgress {
|
|
509
492
|
readonly state?: SpotifySyncRunState;
|
|
@@ -514,7 +497,7 @@ export interface SpotifySyncProgress {
|
|
|
514
497
|
* their text: `"Token refresh failed - please relink Spotify."` means the
|
|
515
498
|
* refresh token is dead and the user must go through the link flow again,
|
|
516
499
|
* while a stale run rewritten by {@link SpotifySyncNamespace.status} says
|
|
517
|
-
* `"Sincronização interrompida"` (the
|
|
500
|
+
* `"Sincronização interrompida"` (the server writes that one in Portuguese).
|
|
518
501
|
*/
|
|
519
502
|
readonly error?: string | null;
|
|
520
503
|
readonly playlists?: SpotifySyncPlaylistProgress[];
|
|
@@ -525,7 +508,7 @@ export interface SpotifySyncProgress {
|
|
|
525
508
|
* All three keys are absent on an identity that has never been configured, and
|
|
526
509
|
* absent is NOT `false` for any of them - each defaults differently:
|
|
527
510
|
*
|
|
528
|
-
* - `sync_liked` defaults to `true
|
|
511
|
+
* - `sync_liked` defaults to `true`;
|
|
529
512
|
* - `enabled_playlists` absent or `null` means EVERY eligible playlist, not
|
|
530
513
|
* none. An empty array means none;
|
|
531
514
|
* - `auto_sync` absent means "on if this identity has ever synced before",
|
|
@@ -571,9 +554,8 @@ export interface SpotifyPlaylistOption {
|
|
|
571
554
|
readonly owner: string | null;
|
|
572
555
|
readonly cover_url: string | null;
|
|
573
556
|
/**
|
|
574
|
-
* Whether this playlist is currently selected.
|
|
575
|
-
* `enabled_playlists
|
|
576
|
-
* unconfigured identity EVERY row comes back `true`.
|
|
557
|
+
* Whether this playlist is currently selected. On an unconfigured identity
|
|
558
|
+
* (no `enabled_playlists` saved) EVERY row comes back `true`.
|
|
577
559
|
*/
|
|
578
560
|
readonly enabled: boolean;
|
|
579
561
|
}
|
|
@@ -582,9 +564,9 @@ export interface SpotifyPlaylistOption {
|
|
|
582
564
|
*
|
|
583
565
|
* The list is already filtered to what can actually be synced: playlists owned
|
|
584
566
|
* by `"spotify"` (the editorial ones) and other people's non-collaborative
|
|
585
|
-
* playlists are dropped, because
|
|
586
|
-
*
|
|
587
|
-
*
|
|
567
|
+
* playlists are dropped, because Spotify does not let this integration read
|
|
568
|
+
* their tracks. A playlist the user can see in the Spotify app and not here is
|
|
569
|
+
* that rule, not a bug.
|
|
588
570
|
*/
|
|
589
571
|
export interface SpotifySyncPreview {
|
|
590
572
|
readonly sync_liked: boolean;
|
|
@@ -593,17 +575,16 @@ export interface SpotifySyncPreview {
|
|
|
593
575
|
/**
|
|
594
576
|
* Body of `PATCH /spotify_syncs/settings`.
|
|
595
577
|
*
|
|
596
|
-
* **Presence-sensitive, and two of the three keys delete data.**
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
* delta.
|
|
578
|
+
* **Presence-sensitive, and two of the three keys delete data.** An omitted
|
|
579
|
+
* key is left alone and a present one is applied - which means you cannot
|
|
580
|
+
* express "leave enabled_playlists alone" by sending `null`, and you must send
|
|
581
|
+
* the WHOLE list every time rather than a delta.
|
|
601
582
|
*
|
|
602
583
|
* The destruction is immediate and synchronous, inside the PATCH:
|
|
603
584
|
*
|
|
604
585
|
* - `enabledPlaylists` **destroys the local copy** of every synced playlist
|
|
605
586
|
* whose Spotify id is not in the new list. Songs stay in the library; the
|
|
606
|
-
* playlist
|
|
587
|
+
* playlist and its rows do not. Re-enabling it later
|
|
607
588
|
* re-creates it from scratch on the next sync;
|
|
608
589
|
* - `syncLiked: false` **destroys the local "liked" mirror** the same way.
|
|
609
590
|
*
|
|
@@ -641,41 +622,36 @@ export interface StartSpotifySyncInput {
|
|
|
641
622
|
*/
|
|
642
623
|
readonly playlistIds?: string[];
|
|
643
624
|
}
|
|
644
|
-
/**
|
|
645
|
-
* A `"running"` sync older than this is treated as lost. Two hours, matching
|
|
646
|
-
* `SpotifySyncsController::STALE_RUNNING_AFTER`.
|
|
647
|
-
*/
|
|
625
|
+
/** A `"running"` sync older than this is treated as lost. Two hours. */
|
|
648
626
|
export declare const SPOTIFY_SYNC_STALE_AFTER_MS = 7200000;
|
|
649
627
|
/** True while a sync run is in flight. Anything else - `"idle"` included - is not. */
|
|
650
628
|
export declare function isSpotifySyncRunning(status: SpotifySyncStatus): boolean;
|
|
651
629
|
/**
|
|
652
630
|
* Spotify account mirroring, reachable as `oms.music.imports.spotify`.
|
|
653
631
|
*
|
|
654
|
-
* ## Every method here 403s unless
|
|
632
|
+
* ## Every method here 403s unless Spotify is enabled for the account
|
|
655
633
|
*
|
|
656
|
-
*
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
* of two, and the two fail in completely different places:
|
|
634
|
+
* Every route answers `403 "Spotify is not enabled for this account"` unless
|
|
635
|
+
* an administrator has enabled Spotify for the account. That is step one of
|
|
636
|
+
* two, and the two fail in completely different places:
|
|
660
637
|
*
|
|
661
|
-
* 1. **The
|
|
638
|
+
* 1. **The account flag.** Without it, `GET /auth/link/spotify` refuses before
|
|
662
639
|
* it ever redirects to Spotify: the user is bounced straight back with
|
|
663
640
|
* `?error=spotify_not_allowlisted`, having seen no Spotify screen at all.
|
|
664
641
|
* Every method in this class also 403s.
|
|
665
|
-
* 2. **
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
* the last moment: the redirect to Spotify happens, the user signs
|
|
669
|
-
* then Spotify refuses the authorization itself.
|
|
670
|
-
*
|
|
671
|
-
* is redirected back with the same `?error=spotify_not_allowlisted`, never
|
|
642
|
+
* 2. **Spotify's own allowlist.** The integration runs in Spotify's
|
|
643
|
+
* Development Mode, which admits at most 25 named users. With the flag set
|
|
644
|
+
* but the user not registered on Spotify's side, the link flow LOOKS right
|
|
645
|
+
* up to the last moment: the redirect to Spotify happens, the user signs
|
|
646
|
+
* in, and then Spotify refuses the authorization itself. The user is
|
|
647
|
+
* redirected back with the same `?error=spotify_not_allowlisted`, never
|
|
672
648
|
* linked, with nothing in this API having recorded an attempt. From the
|
|
673
|
-
* outside it looks like the login silently died
|
|
674
|
-
*
|
|
649
|
+
* outside it looks like the login silently died, and the fix is on
|
|
650
|
+
* Spotify's side.
|
|
675
651
|
*
|
|
676
|
-
* Because step 2 fails outside
|
|
677
|
-
* answering `{ connected: false }` on an
|
|
678
|
-
*
|
|
652
|
+
* Because step 2 fails outside this API, {@link SpotifySyncNamespace.status}
|
|
653
|
+
* answering `{ connected: false }` on an enabled account is the normal symptom
|
|
654
|
+
* of it. `connected: false` means "no linked identity", which covers both
|
|
679
655
|
* "never tried" and "tried and Spotify said no".
|
|
680
656
|
*
|
|
681
657
|
* ## There is no sync id
|
|
@@ -692,15 +668,15 @@ export declare class SpotifySyncNamespace extends Resource {
|
|
|
692
668
|
* `GET /spotify_syncs/status` - the whole state of the link in one call.
|
|
693
669
|
*
|
|
694
670
|
* **This GET writes.** If the stored progress says `"running"` and started
|
|
695
|
-
* more than {@link SPOTIFY_SYNC_STALE_AFTER_MS} ago, the
|
|
696
|
-
*
|
|
697
|
-
*
|
|
671
|
+
* more than {@link SPOTIFY_SYNC_STALE_AFTER_MS} ago, the server rewrites it
|
|
672
|
+
* to `"failed"` before answering, because a run that died leaves an eternal
|
|
673
|
+
* `"running"` behind and
|
|
698
674
|
* {@link SpotifySyncNamespace.start} refuses to queue another while one is
|
|
699
675
|
* "in flight". Calling this is therefore how a stuck account gets unstuck -
|
|
700
676
|
* which also means it is not safe to treat as a cacheable read.
|
|
701
677
|
*
|
|
702
678
|
* @throws {OmsApiError} 403 `"Spotify is not enabled for this account"` when
|
|
703
|
-
*
|
|
679
|
+
* Spotify is not enabled for the account. See the class comment.
|
|
704
680
|
*/
|
|
705
681
|
status(options?: RequestOptions): Promise<SpotifySyncStatus>;
|
|
706
682
|
/**
|
|
@@ -716,7 +692,7 @@ export declare class SpotifySyncNamespace extends Resource {
|
|
|
716
692
|
* @throws {OmsApiError} 404 `"link your Spotify account first"` when there is
|
|
717
693
|
* no identity; 502 `"Spotify auth failed: ..."` when the refresh token is
|
|
718
694
|
* dead (the user must relink); 502 with Spotify's own message for anything
|
|
719
|
-
* else upstream; 403
|
|
695
|
+
* else upstream; 403 when Spotify is not enabled for the account.
|
|
720
696
|
*/
|
|
721
697
|
preview(options?: RequestOptions): Promise<SpotifySyncPreview>;
|
|
722
698
|
/**
|
|
@@ -730,8 +706,8 @@ export declare class SpotifySyncNamespace extends Resource {
|
|
|
730
706
|
* never deselected. Read {@link SpotifySyncNamespace.preview} first and send
|
|
731
707
|
* back the ids you got from it.
|
|
732
708
|
*
|
|
733
|
-
* @throws {OmsApiError} 404 `"link your Spotify account first"`; 403
|
|
734
|
-
* the
|
|
709
|
+
* @throws {OmsApiError} 404 `"link your Spotify account first"`; 403 when
|
|
710
|
+
* Spotify is not enabled for the account.
|
|
735
711
|
*/
|
|
736
712
|
updateSettings(input: UpdateSpotifySyncSettingsInput, options?: RequestOptions): Promise<SpotifySyncSettingsResult>;
|
|
737
713
|
/**
|
|
@@ -743,22 +719,22 @@ export declare class SpotifySyncNamespace extends Resource {
|
|
|
743
719
|
* previous run. That is what makes {@link SpotifySyncNamespace.waitForSync}
|
|
744
720
|
* race-free when it follows a `start()`.
|
|
745
721
|
*
|
|
746
|
-
* A manual run is not the same as the nightly one. It
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
722
|
+
* A manual run is not the same as the nightly one. It re-walks every
|
|
723
|
+
* enabled playlist even when Spotify says nothing changed - it is the "I
|
|
724
|
+
* think something is out of step" button - which is why it is much more
|
|
725
|
+
* expensive than the automatic sync and why it should be user-initiated,
|
|
726
|
+
* never polled into.
|
|
751
727
|
*
|
|
752
|
-
* What finishing means: `"complete"` says the walk is done and a
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
728
|
+
* What finishing means: `"complete"` says the walk is done and a song import
|
|
729
|
+
* exists for every new track. The downloads themselves are still queued
|
|
730
|
+
* behind however many thousand imports the run just created. The library
|
|
731
|
+
* fills in for a long time afterwards.
|
|
756
732
|
*
|
|
757
733
|
* @throws {OmsApiError} 409 `"a sync is already running"` - unless the
|
|
758
734
|
* running one is over two hours old, in which case
|
|
759
735
|
* {@link SpotifySyncNamespace.status} rewrites it to failed first and this
|
|
760
|
-
* then succeeds; 404 `"link your Spotify account first"`; 403
|
|
761
|
-
*
|
|
736
|
+
* then succeeds; 404 `"link your Spotify account first"`; 403 when Spotify
|
|
737
|
+
* is not enabled for the account.
|
|
762
738
|
*/
|
|
763
739
|
start(input?: StartSpotifySyncInput, options?: RequestOptions): Promise<SpotifySyncQueued>;
|
|
764
740
|
/**
|
|
@@ -788,11 +764,12 @@ export declare class SpotifySyncNamespace extends Resource {
|
|
|
788
764
|
* count (the liked mirror always does until it finishes).
|
|
789
765
|
*
|
|
790
766
|
* Polling here is also what clears a lost `"running"`, since `status` rewrites
|
|
791
|
-
* a stale one - so a wait against a
|
|
792
|
-
* than never.
|
|
767
|
+
* a stale one - so a wait against a run that died ends after two hours
|
|
768
|
+
* rather than never.
|
|
793
769
|
*
|
|
794
770
|
* @throws {OmsTimeoutError} `code: "timeout"` / `"aborted"`, as everywhere.
|
|
795
|
-
* @throws {OmsApiError} 403
|
|
771
|
+
* @throws {OmsApiError} 403 when Spotify is not enabled for the account, on
|
|
772
|
+
* the first poll.
|
|
796
773
|
*/
|
|
797
774
|
waitForSync(options?: WaitOptions): Promise<SpotifySyncStatus>;
|
|
798
775
|
/**
|
|
@@ -821,9 +798,9 @@ export interface SRMachineMetadata {
|
|
|
821
798
|
*
|
|
822
799
|
* ## Admin only, and the check is blunt
|
|
823
800
|
*
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
801
|
+
* Every route answers `403 "You SHALL NOT use this resource"` to anyone who is
|
|
802
|
+
* not an administrator. There is no allowlist, no scope and no per-user
|
|
803
|
+
* quota.
|
|
827
804
|
*
|
|
828
805
|
* ## What it is
|
|
829
806
|
*
|
|
@@ -834,12 +811,11 @@ export interface SRMachineMetadata {
|
|
|
834
811
|
* the response body. If you want a track IN the library, use
|
|
835
812
|
* {@link MusicImportsNamespace.create} instead; this is the raw pipe.
|
|
836
813
|
*
|
|
837
|
-
* Three of the four
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
841
|
-
*
|
|
842
|
-
* stands in for a budget here. Do not build a batch loop on top of these.
|
|
814
|
+
* Three of the four fetch a caller-supplied URL and hold a server thread while
|
|
815
|
+
* they do it, exactly like {@link MusicImportsNamespace.previewPlaylist} - but
|
|
816
|
+
* they have no budget of their own, so the only ceiling is the general
|
|
817
|
+
* 600/min. The admin gate is what stands in for a budget here. Do not build a
|
|
818
|
+
* batch loop on top of these.
|
|
843
819
|
*
|
|
844
820
|
* A YouTube URL carrying `?list=` is truncated at the `?list=` before the fetch,
|
|
845
821
|
* so a link copied from inside a playlist resolves to the single video rather
|
|
@@ -851,20 +827,19 @@ export interface SRMachineMetadata {
|
|
|
851
827
|
*/
|
|
852
828
|
export declare class SRMachineNamespace extends Resource {
|
|
853
829
|
/**
|
|
854
|
-
* `GET /s_r_machine/metadata` - the title and artist
|
|
830
|
+
* `GET /s_r_machine/metadata` - the title and artist read off a URL.
|
|
855
831
|
*
|
|
856
832
|
* Both keys can be `null` for a source that carries no tags; it is a
|
|
857
833
|
* best-effort read, not a lookup.
|
|
858
834
|
*
|
|
859
|
-
* Not retried by default: it parks a thread for up to
|
|
860
|
-
*
|
|
835
|
+
* Not retried by default: it parks a server thread for up to 60 seconds,
|
|
836
|
+
* and a failure means the source refused.
|
|
861
837
|
*
|
|
862
838
|
* @throws {OmsAuthError} 403 `"You SHALL NOT use this resource"` for a
|
|
863
839
|
* non-admin.
|
|
864
|
-
* @throws {OmsApiError} 400 `"url is not allowed"`
|
|
865
|
-
* when
|
|
866
|
-
*
|
|
867
|
-
* Discord alert.
|
|
840
|
+
* @throws {OmsApiError} 400 `"url is not allowed"` for a URL that is not
|
|
841
|
+
* public http(s); 500 when the fetch itself fails - an upstream failure
|
|
842
|
+
* surfaces as a server error here, not as a 502.
|
|
868
843
|
*/
|
|
869
844
|
metadata(url: string, options?: RequestOptions): Promise<SRMachineMetadata>;
|
|
870
845
|
/**
|
|
@@ -900,27 +875,24 @@ export declare class SRMachineNamespace extends Resource {
|
|
|
900
875
|
* Multipart, field name `file`, and the only method in this namespace that
|
|
901
876
|
* uploads. The response is `audio/opus` bytes, not JSON.
|
|
902
877
|
*
|
|
903
|
-
* Works on all three
|
|
878
|
+
* Works on all three runtimes: a React Native `{ uri, name, type }` descriptor
|
|
904
879
|
* goes into the `FormData` verbatim and is streamed off disk by the native
|
|
905
880
|
* layer, while a browser or Bun caller passes a `FileInput` carrying a Blob
|
|
906
881
|
* or a `Uint8Array`. A `ReadableStream` is buffered first, because
|
|
907
882
|
* `FormData` has no streaming entry.
|
|
908
883
|
*
|
|
909
884
|
* The upload has no client-side size cap here because the server declares
|
|
910
|
-
* none - but
|
|
911
|
-
*
|
|
912
|
-
*
|
|
913
|
-
* it.
|
|
885
|
+
* none - but the CDN in front of the API rejects a request body over roughly
|
|
886
|
+
* 100 MB with a 413 that never reaches it. There is no chunked path for this
|
|
887
|
+
* route, so a file above that simply cannot go through it.
|
|
914
888
|
*
|
|
915
|
-
* The server reads the whole part into memory
|
|
916
|
-
*
|
|
917
|
-
* as well as on the caller.
|
|
889
|
+
* The server reads the whole part into memory before transcoding, so a large
|
|
890
|
+
* input is a large allocation on both sides.
|
|
918
891
|
*
|
|
919
892
|
* @throws {OmsAuthError} 403 `"You SHALL NOT use this resource"` for a
|
|
920
893
|
* non-admin.
|
|
921
|
-
* @throws {OmsApiError} 500 when no `file` part was sent
|
|
922
|
-
*
|
|
923
|
-
* graceful 400.
|
|
894
|
+
* @throws {OmsApiError} 500 when no `file` part was sent or when the input
|
|
895
|
+
* cannot be transcoded. Neither is a graceful 400.
|
|
924
896
|
* @throws {TypeError} when a React Native descriptor is passed on a runtime
|
|
925
897
|
* whose `FormData` is the web one, which would otherwise upload the literal
|
|
926
898
|
* text `"[object Object]"` and answer 500.
|