@vocalstack/js-sdk 2.0.0-alpha.0 → 2.0.0-alpha.2
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 +7 -2
- package/SKILL.md +13 -6
- package/dist/errors.d.ts +0 -18
- package/dist/errors.js +0 -18
- package/dist/http.d.ts +0 -32
- package/dist/http.js +26 -24
- package/dist/index.d.ts +5 -19
- package/dist/index.js +4 -18
- package/dist/mic.d.ts +1 -49
- package/dist/mic.js +0 -22
- package/dist/realtime.d.ts +4 -59
- package/dist/realtime.js +192 -152
- package/dist/resources.d.ts +86 -211
- package/dist/resources.js +130 -178
- package/dist/subscription.d.ts +0 -17
- package/dist/subscription.js +1 -12
- package/dist/ws.d.ts +0 -3
- package/dist/ws.js +0 -3
- package/package.json +3 -7
- package/skills/vocalstack/SKILL.md +13 -6
package/dist/resources.js
CHANGED
|
@@ -3,6 +3,10 @@ import { listenerQuery, seg } from './http.js';
|
|
|
3
3
|
import { RealtimeSession } from './realtime.js';
|
|
4
4
|
import { Subscription } from './subscription.js';
|
|
5
5
|
import { openWebSocket } from './ws.js';
|
|
6
|
+
function filterQuery(f) {
|
|
7
|
+
const at = (d) => (d instanceof Date ? d.toISOString() : d);
|
|
8
|
+
return { lifecycle: f.lifecycle, q: f.q, site: f.site, language: f.language, kind: f.kind, origin: f.origin, tag: f.tag, broadcast: f.broadcast, createdAfter: at(f.createdAfter), createdBefore: at(f.createdBefore), shared: f.shared };
|
|
9
|
+
}
|
|
6
10
|
async function toBlob(file) {
|
|
7
11
|
if (typeof file === 'string') {
|
|
8
12
|
const fsSpecifier = 'node:' + 'fs';
|
|
@@ -14,8 +18,6 @@ async function toBlob(file) {
|
|
|
14
18
|
}
|
|
15
19
|
return { blob: new Blob([file]) };
|
|
16
20
|
}
|
|
17
|
-
/** Every item of a paged list: each page asked for with the previous
|
|
18
|
-
* page's `nextCursor` until one comes back without it. */
|
|
19
21
|
async function allPages(page) {
|
|
20
22
|
const out = [];
|
|
21
23
|
let cursor;
|
|
@@ -27,8 +29,6 @@ async function allPages(page) {
|
|
|
27
29
|
cursor = r.nextCursor;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
|
-
/** What broadcasts and transcriptions have in common: both hold
|
|
31
|
-
* recordings and share links. */
|
|
32
32
|
class ParentResource {
|
|
33
33
|
t;
|
|
34
34
|
constructor(t) {
|
|
@@ -37,8 +37,6 @@ class ParentResource {
|
|
|
37
37
|
p(id, rest = '') {
|
|
38
38
|
return `${this.base}/${seg(id)}${rest}`;
|
|
39
39
|
}
|
|
40
|
-
/** Every one, newest first: a paged list is read to its last page
|
|
41
|
-
* (`transcriptions.listPage` reads one). */
|
|
42
40
|
list(lifecycle = 'active') {
|
|
43
41
|
return allPages(async (cursor) => {
|
|
44
42
|
const r = await this.t.json('GET', this.base, { query: { lifecycle, cursor } });
|
|
@@ -48,36 +46,36 @@ class ParentResource {
|
|
|
48
46
|
get(id) {
|
|
49
47
|
return this.t.json('GET', this.p(id));
|
|
50
48
|
}
|
|
51
|
-
/** Rename, change the default capture settings (a broadcast: also its access, password or link). */
|
|
52
49
|
update(id, req) {
|
|
53
50
|
return this.t.json('PATCH', this.p(id), { body: req });
|
|
54
51
|
}
|
|
55
|
-
/** Hidden, its links dead, its data kept until `restore` or `purge`. */
|
|
56
52
|
archive(id) {
|
|
57
53
|
return this.t.json('POST', this.p(id, '/archive'));
|
|
58
54
|
}
|
|
59
55
|
restore(id) {
|
|
60
56
|
return this.t.json('POST', this.p(id, '/restore'));
|
|
61
57
|
}
|
|
62
|
-
/** Delete an archived one and everything in it, for good. */
|
|
63
58
|
async purge(id) {
|
|
64
59
|
await this.t.json('POST', this.p(id, '/purge'));
|
|
65
60
|
}
|
|
66
|
-
|
|
61
|
+
archiveMany(ids) {
|
|
62
|
+
return this.t.json('POST', `${this.base}/archive`, { body: { ids } });
|
|
63
|
+
}
|
|
64
|
+
restoreMany(ids) {
|
|
65
|
+
return this.t.json('POST', `${this.base}/restore`, { body: { ids } });
|
|
66
|
+
}
|
|
67
|
+
purgeMany(ids) {
|
|
68
|
+
return this.t.json('POST', `${this.base}/purge`, { body: { ids } });
|
|
69
|
+
}
|
|
67
70
|
listRecordings(id, lifecycle = 'active') {
|
|
68
71
|
return allPages(async (cursor) => {
|
|
69
72
|
const r = await this.listRecordingsPage(id, { lifecycle, cursor });
|
|
70
73
|
return { items: r.recordings, nextCursor: r.nextCursor };
|
|
71
74
|
});
|
|
72
75
|
}
|
|
73
|
-
/** One page of recordings, newest first: pass its `nextCursor` as `cursor` for the next. */
|
|
74
76
|
listRecordingsPage(id, opts = {}) {
|
|
75
77
|
return this.t.json('GET', this.p(id, '/recordings'), { query: { lifecycle: opts.lifecycle, cursor: opts.cursor, limit: opts.limit } });
|
|
76
78
|
}
|
|
77
|
-
/** Start a live recording. The grant's `streamUrl` is the publisher
|
|
78
|
-
* socket (`vs.recordings.publish(grant)`). A 409 `conflict` with
|
|
79
|
-
* `details.takeover` means another device is recording: retry with
|
|
80
|
-
* `{ takeover: true }` to record here instead. */
|
|
81
79
|
startRecording(id, req = {}, call = {}) {
|
|
82
80
|
return this.t.json('POST', this.p(id, '/recordings'), { body: req, ...call });
|
|
83
81
|
}
|
|
@@ -88,52 +86,43 @@ class ParentResource {
|
|
|
88
86
|
createShareLink(id, req) {
|
|
89
87
|
return this.t.json('POST', this.p(id, '/share-links'), { body: req });
|
|
90
88
|
}
|
|
91
|
-
/** The events socket a waiting screen needs: `recording.started` (it is
|
|
92
|
-
* live), `recording.ended`, `job.progress`, `stream.ingest`.
|
|
93
|
-
* Each names the transcription the recording is made in (`transcriptionId`). */
|
|
94
89
|
async events(id, as = {}) {
|
|
95
90
|
const token = as.asOwner === false ? undefined : await this.t.bearer();
|
|
96
91
|
return openWebSocket(this.t.wsUrl(this.p(id, '/events'), listenerQuery(as)), token);
|
|
97
92
|
}
|
|
98
93
|
}
|
|
99
|
-
/** `vs.broadcasts`: the links listeners open (design 77). */
|
|
100
94
|
export class Broadcasts extends ParentResource {
|
|
101
95
|
base = '/v1/broadcasts';
|
|
102
96
|
key = 'broadcasts';
|
|
103
97
|
create(req, call = {}) {
|
|
104
98
|
return this.t.json('POST', this.base, { body: req, ...call });
|
|
105
99
|
}
|
|
106
|
-
/** Give a broadcast a stream it can record from (a YouTube live, an
|
|
107
|
-
* internet radio address). Returns the broadcast. */
|
|
108
100
|
addAudioSource(id, req) {
|
|
109
101
|
return this.t.json('POST', this.p(id, '/audio-sources'), { body: req });
|
|
110
102
|
}
|
|
111
103
|
removeAudioSource(id, sourceId) {
|
|
112
104
|
return this.t.json('DELETE', this.p(id, `/audio-sources/${seg(sourceId)}`));
|
|
113
105
|
}
|
|
114
|
-
/** Whether that source can be recorded right now; `fresh` skips the short cache. */
|
|
115
106
|
checkAudioSource(id, sourceId, opts = {}) {
|
|
116
107
|
return this.t.json('GET', this.p(id, `/audio-sources/${seg(sourceId)}/check`), { query: { fresh: opts.fresh || undefined } });
|
|
117
108
|
}
|
|
118
109
|
}
|
|
119
|
-
/** `vs.transcriptions`: a file, a link, a microphone or a stream, transcribed and stored. */
|
|
120
110
|
export class Transcriptions extends ParentResource {
|
|
121
111
|
base = '/v1/transcriptions';
|
|
122
112
|
key = 'transcriptions';
|
|
123
|
-
|
|
124
|
-
|
|
113
|
+
list(opts = 'active') {
|
|
114
|
+
const f = typeof opts === 'string' ? { lifecycle: opts } : opts;
|
|
115
|
+
return allPages(async (cursor) => {
|
|
116
|
+
const r = await this.listPage({ ...f, cursor });
|
|
117
|
+
return { items: r.transcriptions, nextCursor: r.nextCursor };
|
|
118
|
+
});
|
|
119
|
+
}
|
|
125
120
|
listPage(opts = {}) {
|
|
126
|
-
return this.t.json('GET', this.base, { query: {
|
|
121
|
+
return this.t.json('GET', this.base, { query: { ...filterQuery(opts), cursor: opts.cursor, limit: opts.limit } });
|
|
127
122
|
}
|
|
128
|
-
/** A file or link recording in this transcription: no live phase, transcribed at full speed.
|
|
129
|
-
* It has no job: open the grant's socket with `vs.recordings.publish(grant)` (with `url`
|
|
130
|
-
* the server fetches the link) and read it until the session is done. */
|
|
131
123
|
uploadRecording(id, req = {}, call = {}) {
|
|
132
124
|
return this.t.json('POST', this.p(id, '/recordings/upload'), { body: req, ...call });
|
|
133
125
|
}
|
|
134
|
-
/** From a file (`file`) or a public link (`url`). The answer's
|
|
135
|
-
* `job.recordingId` is the job: `vs.jobs.wait(recordingId)`,
|
|
136
|
-
* then `vs.recordings.transcript(recordingId)`. */
|
|
137
126
|
async create(req, call = {}) {
|
|
138
127
|
const { file, fileName, ...options } = req;
|
|
139
128
|
if (file === undefined)
|
|
@@ -144,20 +133,19 @@ export class Transcriptions extends ParentResource {
|
|
|
144
133
|
fd.append('file', blob, fileName ?? name ?? 'upload');
|
|
145
134
|
return this.t.json('POST', this.base, { body: fd, ...call });
|
|
146
135
|
}
|
|
147
|
-
/** `create({ url, ...opts })`. */
|
|
148
136
|
createFromUrl(url, opts = {}, call = {}) {
|
|
149
137
|
return this.create({ ...opts, url }, call);
|
|
150
138
|
}
|
|
151
|
-
/** A transcription captured as it is spoken: a microphone, or a stream
|
|
152
|
-
* the server pulls. Created idle: `startRecording(id)` starts it. */
|
|
153
139
|
createLive(req, call = {}) {
|
|
154
140
|
return this.t.json('POST', `${this.base}/live`, { body: req, ...call });
|
|
155
141
|
}
|
|
156
|
-
|
|
142
|
+
async lookup(url) {
|
|
143
|
+
const r = await this.t.json('GET', `${this.base}/lookup`, { query: { url } });
|
|
144
|
+
return r.transcription ?? null;
|
|
145
|
+
}
|
|
157
146
|
probe(url) {
|
|
158
147
|
return this.t.json('GET', `${this.base}/probe`, { query: { url } });
|
|
159
148
|
}
|
|
160
|
-
/** Feed a broadcast from a live transcription. Returns the transcription. */
|
|
161
149
|
attachBroadcast(id, broadcastId) {
|
|
162
150
|
return this.t.json('PUT', this.p(id, `/broadcasts/${seg(broadcastId)}`));
|
|
163
151
|
}
|
|
@@ -165,11 +153,6 @@ export class Transcriptions extends ParentResource {
|
|
|
165
153
|
return this.t.json('DELETE', this.p(id, `/broadcasts/${seg(broadcastId)}`));
|
|
166
154
|
}
|
|
167
155
|
}
|
|
168
|
-
/** `vs.batches`: several files transcribed together, for less than a
|
|
169
|
-
* transcription each (one create, one minimum charge). Create a batch, add
|
|
170
|
-
* each file or link with `addFile`, then `start` it; `get` shows its files
|
|
171
|
-
* (each a transcription with its job) and their counts. A batch's files get
|
|
172
|
-
* no summary, title, share link or spoken translations. */
|
|
173
156
|
export class Batches {
|
|
174
157
|
#t;
|
|
175
158
|
constructor(t) {
|
|
@@ -178,31 +161,24 @@ export class Batches {
|
|
|
178
161
|
#p(id, rest = '') {
|
|
179
162
|
return `/v1/batches/${seg(id)}${rest}`;
|
|
180
163
|
}
|
|
181
|
-
/** An open batch, ready for files (`name` defaults to "Batch of <date>"). */
|
|
182
164
|
create(req = {}, call = {}) {
|
|
183
165
|
return this.#t.json('POST', '/v1/batches', { body: req, ...call });
|
|
184
166
|
}
|
|
185
|
-
/** Every batch, newest first (every page of `listPage`); each with its counts, not its files. */
|
|
186
167
|
list() {
|
|
187
168
|
return allPages(async (cursor) => {
|
|
188
169
|
const r = await this.listPage({ cursor });
|
|
189
170
|
return { items: r.batches, nextCursor: r.nextCursor };
|
|
190
171
|
});
|
|
191
172
|
}
|
|
192
|
-
/** One page of batches, newest first: pass its `nextCursor` as `cursor` for the next. */
|
|
193
173
|
listPage(opts = {}) {
|
|
194
174
|
return this.#t.json('GET', '/v1/batches', { query: { cursor: opts.cursor, limit: opts.limit } });
|
|
195
175
|
}
|
|
196
|
-
/** A batch with its files (`files`, each a transcription with its job), in the order they were added. */
|
|
197
176
|
get(id) {
|
|
198
177
|
return this.#t.json('GET', this.#p(id));
|
|
199
178
|
}
|
|
200
|
-
/** Discard a batch that was never started, with its files (nothing was charged). */
|
|
201
179
|
async delete(id) {
|
|
202
180
|
await this.#t.json('DELETE', this.#p(id));
|
|
203
181
|
}
|
|
204
|
-
/** Add one file (`file`) or link (`url`) to an open batch: a transcription
|
|
205
|
-
* of its own (`batchId`), waiting for the batch's `start`. */
|
|
206
182
|
async addFile(id, req, call = {}) {
|
|
207
183
|
const { file, fileName, ...options } = req;
|
|
208
184
|
if (file === undefined)
|
|
@@ -213,36 +189,24 @@ export class Batches {
|
|
|
213
189
|
fd.append('file', blob, fileName ?? name ?? 'upload');
|
|
214
190
|
return this.#t.json('POST', this.#p(id, '/files'), { body: fd, ...call });
|
|
215
191
|
}
|
|
216
|
-
/** `addFile(id, { url, ...opts })`. */
|
|
217
192
|
addUrl(id, url, opts = {}, call = {}) {
|
|
218
193
|
return this.addFile(id, { ...opts, url }, call);
|
|
219
194
|
}
|
|
220
|
-
/** Seal the batch and queue its files, longest first; `wait` follows it to the end. */
|
|
221
195
|
start(id, call = {}) {
|
|
222
196
|
return this.#t.json('POST', this.#p(id, '/start'), call);
|
|
223
197
|
}
|
|
224
|
-
/** Every finished file's transcript in one `format` (as `recordings.export`
|
|
225
|
-
* makes one), in one zip, as a `Response`: `.arrayBuffer()` it. Files
|
|
226
|
-
* still running or failed are left out. */
|
|
227
198
|
export(id, format) {
|
|
228
199
|
return this.#t.raw('GET', this.#p(id, '/export'), { headers: { accept: '*/*' }, query: { format } });
|
|
229
200
|
}
|
|
230
|
-
/** Stop every file not finished yet: queued files never start, a running
|
|
231
|
-
* one is cancelled and billed for what it transcribed. */
|
|
232
201
|
stop(id) {
|
|
233
202
|
return this.#t.json('POST', this.#p(id, '/stop'));
|
|
234
203
|
}
|
|
235
|
-
/** Poll a started batch until it is `done` (every file finished or
|
|
236
|
-
* failed) or `stopped`, and return it with its files. It does not throw
|
|
237
|
-
* on a failed file: check `counts.failed` and each file's `job.state`.
|
|
238
|
-
* `onProgress` sees every read, the last one included. */
|
|
239
204
|
async wait(id, opts = {}) {
|
|
240
205
|
const interval = opts.intervalMs ?? 5000;
|
|
241
206
|
const deadline = opts.timeoutMs ? Date.now() + opts.timeoutMs : Infinity;
|
|
242
207
|
for (;;) {
|
|
243
208
|
const batch = await this.get(id);
|
|
244
209
|
opts.onProgress?.(batch);
|
|
245
|
-
// Closed: every file over (a stopped batch's running file too).
|
|
246
210
|
if ((batch.state === 'done' || batch.state === 'stopped') && batch.finishedAt)
|
|
247
211
|
return batch;
|
|
248
212
|
if (batch.state === 'open')
|
|
@@ -255,7 +219,6 @@ export class Batches {
|
|
|
255
219
|
}
|
|
256
220
|
}
|
|
257
221
|
}
|
|
258
|
-
/** `vs.recordings`: one take, live or uploaded. */
|
|
259
222
|
export class Recordings {
|
|
260
223
|
#t;
|
|
261
224
|
constructor(t) {
|
|
@@ -273,19 +236,15 @@ export class Recordings {
|
|
|
273
236
|
resume(id) {
|
|
274
237
|
return this.#t.json('POST', this.#p(id, '/resume'));
|
|
275
238
|
}
|
|
276
|
-
/** Only from paused. */
|
|
277
239
|
stop(id, reason) {
|
|
278
240
|
return this.#t.json('POST', this.#p(id, '/stop'), { body: { reason } });
|
|
279
241
|
}
|
|
280
242
|
settings(id, patch) {
|
|
281
243
|
return this.#t.json('PATCH', this.#p(id, '/settings'), { body: patch });
|
|
282
244
|
}
|
|
283
|
-
/** A new publisher grant for a recording that is already live. */
|
|
284
245
|
publishToken(id, opts = {}) {
|
|
285
246
|
return this.#t.json('POST', this.#p(id, '/publish-token'), { body: { takeover: opts.takeover ?? false } });
|
|
286
247
|
}
|
|
287
|
-
/** Open the publisher socket a grant names and wait until it is ready:
|
|
288
|
-
* send raw PCM (f32le, 16 kHz, mono) with `sendAudio`, end with `eof()`. */
|
|
289
248
|
publish(grant, opts = {}) {
|
|
290
249
|
if (!grant.streamUrl)
|
|
291
250
|
throw new VocalStackError('invalid_state', 'this grant has no streamUrl (an upload recording has no publisher socket)');
|
|
@@ -306,42 +265,38 @@ export class Recordings {
|
|
|
306
265
|
async purge(id) {
|
|
307
266
|
await this.#t.json('POST', this.#p(id, '/purge'));
|
|
308
267
|
}
|
|
309
|
-
|
|
310
|
-
|
|
268
|
+
archiveMany(ids) {
|
|
269
|
+
return this.#t.json('POST', '/v1/recordings/archive', { body: { ids } });
|
|
270
|
+
}
|
|
271
|
+
restoreMany(ids) {
|
|
272
|
+
return this.#t.json('POST', '/v1/recordings/restore', { body: { ids } });
|
|
273
|
+
}
|
|
274
|
+
purgeMany(ids) {
|
|
275
|
+
return this.#t.json('POST', '/v1/recordings/purge', { body: { ids } });
|
|
276
|
+
}
|
|
311
277
|
transcript(id, opts = {}) {
|
|
312
278
|
return this.#t.json('GET', this.#p(id, '/transcript'), {
|
|
313
279
|
query: { ...listenerQuery(opts), after: opts.after, lang: opts.lang, role: opts.asListener ? 'listener' : undefined },
|
|
314
280
|
});
|
|
315
281
|
}
|
|
316
|
-
/** Translate the given sentences (at most 40 ids per call); the answer
|
|
317
|
-
* carries only what this call translated. */
|
|
318
282
|
translation(id, lang, sentences = [], as) {
|
|
319
283
|
return this.#t.json('GET', this.#p(id, '/translation'), {
|
|
320
284
|
query: { ...listenerQuery(as), lang, sentences: sentences.length ? sentences.join(',') : undefined },
|
|
321
285
|
});
|
|
322
286
|
}
|
|
323
|
-
/** Queue the AI title and summary of a finished recording. */
|
|
324
287
|
summarize(id, call = {}) {
|
|
325
288
|
return this.#t.json('POST', this.#p(id, '/summarize'), call);
|
|
326
289
|
}
|
|
327
|
-
/** The title and summary as they stand, in `lang` when given. */
|
|
328
290
|
summary(id, opts = {}) {
|
|
329
291
|
return this.#t.json('GET', this.#p(id, '/summary'), { query: { ...listenerQuery(opts), lang: opts.lang } });
|
|
330
292
|
}
|
|
331
|
-
/** An export file (JSON, SRT, XLSX, PDF) as a `Response`: `.text()` or
|
|
332
|
-
* `.arrayBuffer()` it. `lang` makes it read in that language. */
|
|
333
293
|
export(id, format, opts = {}) {
|
|
334
294
|
return this.#t.raw('GET', this.#p(id, '/export'), { headers: { accept: '*/*' }, query: { ...listenerQuery(opts), format, lang: opts.lang } });
|
|
335
295
|
}
|
|
336
|
-
/** The export as a plain link, for a browser download with a browser
|
|
337
|
-
* access token (it rides in the query). Never use it with an API key:
|
|
338
|
-
* the API refuses a key in a URL. */
|
|
339
296
|
async exportUrl(id, format, opts = {}) {
|
|
340
297
|
const token = opts.shareCode || opts.listenerToken ? undefined : await this.#t.bearer();
|
|
341
298
|
return this.#t.url(this.#p(id, '/export'), { format, lang: opts.lang, shareCode: opts.shareCode, listenerToken: opts.listenerToken, access_token: token });
|
|
342
299
|
}
|
|
343
|
-
/** The signed media URL the server redirects to. `kind`: `delivery`
|
|
344
|
-
* (default), `master`, or `source` (a transcription's original upload). */
|
|
345
300
|
async audioUrl(id, opts = {}) {
|
|
346
301
|
const res = await this.#t.raw('GET', this.#p(id, '/audio'), {
|
|
347
302
|
query: { ...listenerQuery(opts), kind: opts.kind, lang: opts.lang, format: opts.format },
|
|
@@ -351,44 +306,55 @@ export class Recordings {
|
|
|
351
306
|
throw new VocalStackError('not_found', 'no signed URL in the redirect');
|
|
352
307
|
return location;
|
|
353
308
|
}
|
|
354
|
-
|
|
309
|
+
audioSrc(id, opts = {}) {
|
|
310
|
+
return this.#t.url(this.#p(id, '/audio'), { kind: opts.kind ?? 'delivery', lang: opts.lang, format: opts.format, voices: opts.voices, voice: opts.voice, ...mediaQuery(opts) });
|
|
311
|
+
}
|
|
312
|
+
clipSrc(id, startS, endS, opts = {}) {
|
|
313
|
+
return this.#t.url(this.#p(id, '/audio/clip'), { startS: startS.toFixed(2), endS: endS.toFixed(2), clean: opts.clean ? 'true' : undefined, ...mediaQuery(opts) });
|
|
314
|
+
}
|
|
315
|
+
voiceClipSrc(id, sentence, lang, opts = {}) {
|
|
316
|
+
return this.#t.url(this.#p(id, `/voice/${seg(sentence)}`), {
|
|
317
|
+
lang,
|
|
318
|
+
voices: opts.voices,
|
|
319
|
+
voice: opts.voice,
|
|
320
|
+
format: opts.format,
|
|
321
|
+
part: opts.part ? opts.part : undefined,
|
|
322
|
+
v: opts.v,
|
|
323
|
+
...mediaQuery(opts),
|
|
324
|
+
});
|
|
325
|
+
}
|
|
355
326
|
clip(id, startS, endS, as) {
|
|
356
327
|
return this.#t.raw('GET', this.#p(id, '/audio/clip'), { headers: { accept: '*/*' }, query: { ...listenerQuery(as), startS, endS } });
|
|
357
328
|
}
|
|
358
|
-
/** The synthesized voice tracks of a recording (per language). */
|
|
359
329
|
voice(id, lang, as) {
|
|
360
330
|
return this.#t.json('GET', this.#p(id, '/voice'), { query: { ...listenerQuery(as), lang } });
|
|
361
331
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return this.#t.json('POST', this.#p(id, '/voice'), {
|
|
332
|
+
renderVoice(id, req, call = {}) {
|
|
333
|
+
const r = Array.isArray(req) ? { langs: req } : req;
|
|
334
|
+
return this.#t.json('POST', this.#p(id, '/voice'), {
|
|
335
|
+
query: listenerQuery(r),
|
|
336
|
+
body: { langs: r.langs, voices: r.voices, voice: r.voice, voiceBySpeaker: r.voiceBySpeaker, near: r.near, consent: r.consent },
|
|
337
|
+
...call,
|
|
338
|
+
});
|
|
365
339
|
}
|
|
366
|
-
/** One sentence spoken in `lang`. */
|
|
367
340
|
voiceClip(id, sentence, lang, opts = {}) {
|
|
368
|
-
return this.#t.raw('GET', this.#p(id, `/voice/${seg(sentence)}`), { headers: { accept: '*/*' }, query: { ...listenerQuery(opts), lang, format: opts.format } });
|
|
341
|
+
return this.#t.raw('GET', this.#p(id, `/voice/${seg(sentence)}`), { headers: { accept: '*/*' }, query: { ...listenerQuery(opts), lang, format: opts.format, voices: opts.voices } });
|
|
369
342
|
}
|
|
370
|
-
/** A short-lived token for the recording's media URLs. */
|
|
371
343
|
mediaToken(id) {
|
|
372
344
|
return this.#t.json('GET', this.#p(id, '/media-token'));
|
|
373
345
|
}
|
|
374
|
-
/** Name a speaker (the diarizer's index); `null` clears the name. */
|
|
375
346
|
async nameSpeaker(id, speaker, name) {
|
|
376
347
|
await this.#t.json('PUT', this.#p(id, `/speakers/${seg(speaker)}`), { body: { name } });
|
|
377
348
|
}
|
|
378
|
-
/** The durable event log with wall times (owner only): every event
|
|
379
|
-
* after `after` (a `seq`; the whole log without it), read `limit` a page. */
|
|
380
349
|
events(id, opts = {}) {
|
|
381
350
|
return allPages(async (cursor) => {
|
|
382
351
|
const r = await this.eventsPage(id, cursor ? { cursor, limit: opts.limit } : { after: opts.after, limit: opts.limit });
|
|
383
352
|
return { items: r.events, nextCursor: r.nextCursor };
|
|
384
353
|
});
|
|
385
354
|
}
|
|
386
|
-
/** One page of the event log (500 by default, 1000 at most): from `after`
|
|
387
|
-
* a `seq`, or the previous page's `nextCursor` as `cursor`. */
|
|
388
355
|
eventsPage(id, opts = {}) {
|
|
389
356
|
return this.#t.json('GET', this.#p(id, '/events'), { query: { cursor: opts.cursor, after: opts.after, limit: opts.limit } });
|
|
390
357
|
}
|
|
391
|
-
/** Attach to the live fan-out; resolves once the backfill snapshot is in place. */
|
|
392
358
|
async subscribe(id, opts = {}) {
|
|
393
359
|
const token = opts.asOwner === false ? undefined : await this.#t.bearer();
|
|
394
360
|
const url = this.#t.wsUrl(this.#p(id, '/subscribe'), {
|
|
@@ -407,10 +373,19 @@ export class Recordings {
|
|
|
407
373
|
}));
|
|
408
374
|
}
|
|
409
375
|
}
|
|
376
|
+
function mediaQuery(a) {
|
|
377
|
+
return { media: a.media, access_token: a.media ? undefined : a.token, listenerToken: a.listenerToken, shareCode: a.shareCode };
|
|
378
|
+
}
|
|
379
|
+
export function playableFormat() {
|
|
380
|
+
try {
|
|
381
|
+
const doc = globalThis.document;
|
|
382
|
+
return doc?.createElement('audio').canPlayType?.('audio/x-caf; codecs=opus') === 'probably' ? 'caf' : 'ogg';
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return 'ogg';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
410
388
|
const TERMINAL = new Set(['done', 'failed', 'stopped', 'cropping']);
|
|
411
|
-
/** `vs.jobs`: the transcription job of a file or link recording
|
|
412
|
-
* (`/v1/recordings/{id}/job`). States: queued, fetching, transcribing,
|
|
413
|
-
* done, failed, and cropping (waiting for `start`). */
|
|
414
389
|
export class Jobs {
|
|
415
390
|
#t;
|
|
416
391
|
constructor(t) {
|
|
@@ -419,22 +394,15 @@ export class Jobs {
|
|
|
419
394
|
get(recordingId) {
|
|
420
395
|
return this.#t.json('GET', `/v1/recordings/${seg(recordingId)}/job`);
|
|
421
396
|
}
|
|
422
|
-
/** Release a job waiting in `cropping`: the window to transcribe, in seconds. */
|
|
423
397
|
start(recordingId, window = {}) {
|
|
424
398
|
return this.#t.json('POST', `/v1/recordings/${seg(recordingId)}/job/start`, { body: window });
|
|
425
399
|
}
|
|
426
|
-
/** Re-queue a failed job from its stored source. */
|
|
427
400
|
retry(recordingId) {
|
|
428
401
|
return this.#t.json('POST', `/v1/recordings/${seg(recordingId)}/job/retry`);
|
|
429
402
|
}
|
|
430
|
-
/** The file the server fetched for a link job waiting in `cropping`. */
|
|
431
403
|
preview(recordingId) {
|
|
432
404
|
return this.#t.raw('GET', `/v1/recordings/${seg(recordingId)}/job/preview`, { headers: { accept: '*/*' } });
|
|
433
405
|
}
|
|
434
|
-
/** Poll until the job is `done`, `failed` or `cropping`, and return it.
|
|
435
|
-
* It does not throw on `failed`: check `job.state` (and `job.error`).
|
|
436
|
-
* `onProgress` sees every job read, the last one included: its `state`
|
|
437
|
-
* and `progress` (`pct`, `processedS`, the fetch phase). */
|
|
438
406
|
async wait(recordingId, opts = {}) {
|
|
439
407
|
const interval = opts.intervalMs ?? 2000;
|
|
440
408
|
const deadline = opts.timeoutMs ? Date.now() + opts.timeoutMs : Infinity;
|
|
@@ -451,29 +419,24 @@ export class Jobs {
|
|
|
451
419
|
}
|
|
452
420
|
}
|
|
453
421
|
}
|
|
454
|
-
/** `vs.corrections`: edits to a finished transcript, each revertible. */
|
|
455
422
|
export class Corrections {
|
|
456
423
|
#t;
|
|
457
424
|
constructor(t) {
|
|
458
425
|
this.#t = t;
|
|
459
426
|
}
|
|
460
|
-
/** Every correction, newest first (every page of `listPage`). */
|
|
461
427
|
list(recordingId) {
|
|
462
428
|
return allPages(async (cursor) => {
|
|
463
429
|
const r = await this.listPage(recordingId, { cursor });
|
|
464
430
|
return { items: r.corrections, nextCursor: r.nextCursor };
|
|
465
431
|
});
|
|
466
432
|
}
|
|
467
|
-
/** One page of corrections, newest first: pass its `nextCursor` as `cursor` for the next. */
|
|
468
433
|
listPage(recordingId, opts = {}) {
|
|
469
434
|
return this.#t.json('GET', `/v1/recordings/${seg(recordingId)}/corrections`, { query: { cursor: opts.cursor, limit: opts.limit } });
|
|
470
435
|
}
|
|
471
|
-
/** Apply a staged batch atomically; every target must be settled. */
|
|
472
436
|
async apply(recordingId, batch) {
|
|
473
437
|
const r = await this.#t.json('POST', `/v1/recordings/${seg(recordingId)}/corrections`, { body: batch });
|
|
474
438
|
return r.corrections ?? [];
|
|
475
439
|
}
|
|
476
|
-
/** Read lines again with the language forced; lands as `line` corrections. */
|
|
477
440
|
async retranscribe(recordingId, req) {
|
|
478
441
|
const r = await this.#t.json('POST', `/v1/recordings/${seg(recordingId)}/retranscribe`, { body: req });
|
|
479
442
|
return r.corrections ?? [];
|
|
@@ -482,13 +445,14 @@ export class Corrections {
|
|
|
482
445
|
return this.#t.json('POST', `/v1/corrections/${seg(correctionId)}/revert`);
|
|
483
446
|
}
|
|
484
447
|
}
|
|
485
|
-
/** `vs.shareLinks`: named public links to a transcription or broadcast
|
|
486
|
-
* (create and list them on their owner). */
|
|
487
448
|
export class ShareLinks {
|
|
488
449
|
#t;
|
|
489
450
|
constructor(t) {
|
|
490
451
|
this.#t = t;
|
|
491
452
|
}
|
|
453
|
+
open(code) {
|
|
454
|
+
return this.#t.json('GET', `/v1/shares/${seg(code)}`, { anonymous: true });
|
|
455
|
+
}
|
|
492
456
|
update(id, req) {
|
|
493
457
|
return this.#t.json('PATCH', `/v1/share-links/${seg(id)}`, { body: req });
|
|
494
458
|
}
|
|
@@ -496,22 +460,55 @@ export class ShareLinks {
|
|
|
496
460
|
return this.#t.json('DELETE', `/v1/share-links/${seg(id)}`);
|
|
497
461
|
}
|
|
498
462
|
}
|
|
499
|
-
|
|
500
|
-
export class Voices {
|
|
463
|
+
export class Tags {
|
|
501
464
|
#t;
|
|
502
465
|
constructor(t) {
|
|
503
466
|
this.#t = t;
|
|
504
467
|
}
|
|
505
468
|
async list() {
|
|
506
|
-
const r = await this.#t.json('GET', '/v1/
|
|
469
|
+
const r = await this.#t.json('GET', '/v1/tags');
|
|
470
|
+
return r.tags ?? [];
|
|
471
|
+
}
|
|
472
|
+
create(req) {
|
|
473
|
+
return this.#t.json('POST', '/v1/tags', { body: req });
|
|
474
|
+
}
|
|
475
|
+
update(id, req) {
|
|
476
|
+
return this.#t.json('PATCH', `/v1/tags/${seg(id)}`, { body: req });
|
|
477
|
+
}
|
|
478
|
+
async delete(id) {
|
|
479
|
+
await this.#t.json('DELETE', `/v1/tags/${seg(id)}`);
|
|
480
|
+
}
|
|
481
|
+
tagTranscriptions(id, req) {
|
|
482
|
+
return this.#t.json('POST', `/v1/tags/${seg(id)}/transcriptions`, { body: req });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
export class Voices {
|
|
486
|
+
#t;
|
|
487
|
+
constructor(t) {
|
|
488
|
+
this.#t = t;
|
|
489
|
+
}
|
|
490
|
+
async list(opts = {}) {
|
|
491
|
+
const r = await this.#t.json('GET', '/v1/voices', { query: { lifecycle: opts.lifecycle } });
|
|
507
492
|
return r.voices ?? [];
|
|
508
493
|
}
|
|
509
494
|
get(id) {
|
|
510
495
|
return this.#t.json('GET', `/v1/voices/${seg(id)}`);
|
|
511
496
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
497
|
+
archive(id) {
|
|
498
|
+
return this.#t.json('POST', `/v1/voices/${seg(id)}/archive`);
|
|
499
|
+
}
|
|
500
|
+
restore(id) {
|
|
501
|
+
return this.#t.json('POST', `/v1/voices/${seg(id)}/restore`);
|
|
502
|
+
}
|
|
503
|
+
archiveMany(ids) {
|
|
504
|
+
return this.#t.json('POST', '/v1/voices/archive', { body: { ids } });
|
|
505
|
+
}
|
|
506
|
+
restoreMany(ids) {
|
|
507
|
+
return this.#t.json('POST', '/v1/voices/restore', { body: { ids } });
|
|
508
|
+
}
|
|
509
|
+
deleteMany(ids) {
|
|
510
|
+
return this.#t.json('POST', '/v1/voices/delete', { body: { ids } });
|
|
511
|
+
}
|
|
515
512
|
async create(req, call = {}) {
|
|
516
513
|
const { sample, fileName, ...meta } = req;
|
|
517
514
|
const form = new FormData();
|
|
@@ -522,34 +519,27 @@ export class Voices {
|
|
|
522
519
|
}
|
|
523
520
|
return this.#t.json('POST', '/v1/voices', { body: form, ...call });
|
|
524
521
|
}
|
|
525
|
-
/** Build a voice from one speaker of a recording (asserts permission). */
|
|
526
522
|
createFromSpeaker(req, call = {}) {
|
|
527
523
|
return this.#t.json('POST', '/v1/voices/from-speaker', { body: { consent: true, ...req }, ...call });
|
|
528
524
|
}
|
|
529
|
-
|
|
525
|
+
setConsent(id, consent, opts = {}) {
|
|
526
|
+
return this.#t.json('POST', `/v1/voices/${seg(id)}/consent`, { body: { consent, ...opts } });
|
|
527
|
+
}
|
|
530
528
|
update(id, patch) {
|
|
531
529
|
return this.#t.json('PATCH', `/v1/voices/${seg(id)}`, { body: patch });
|
|
532
530
|
}
|
|
533
531
|
async delete(id) {
|
|
534
532
|
await this.#t.json('DELETE', `/v1/voices/${seg(id)}`);
|
|
535
533
|
}
|
|
536
|
-
/** The voices that can speak `lang`, best first (with a token, also the
|
|
537
|
-
* account's ready clones). `recording` also offers the cloned voices that
|
|
538
|
-
* recording uses, to whoever may read it: its owner, or a listener with
|
|
539
|
-
* `listenerToken` or `shareCode`. */
|
|
540
534
|
available(lang, opts = {}) {
|
|
541
535
|
return this.#t.json('GET', '/v1/voices/available', {
|
|
542
536
|
query: { ...listenerQuery(opts), lang, recording: opts.recording },
|
|
543
537
|
anonymous: opts.asOwner === false,
|
|
544
538
|
});
|
|
545
539
|
}
|
|
546
|
-
/** A catalogue voice speaking `lang` (Ogg/Opus; `caf` for Apple). 202 while it is prepared. */
|
|
547
540
|
preview(voice, lang, opts = {}) {
|
|
548
541
|
return this.#t.raw('GET', '/v1/voices/preview', { headers: { accept: '*/*' }, query: { voice, lang, format: opts.format } });
|
|
549
542
|
}
|
|
550
|
-
/** A cloned voice speaking `lang`; 409 while it is not ready. With
|
|
551
|
-
* `recording`, as it speaks in that recording, for whoever may read it
|
|
552
|
-
* (its owner, or a listener with `listenerToken` or `shareCode`). */
|
|
553
543
|
clonePreview(id, opts = {}) {
|
|
554
544
|
return this.#t.raw('GET', `/v1/voices/${seg(id)}/preview`, {
|
|
555
545
|
headers: { accept: '*/*' },
|
|
@@ -557,31 +547,38 @@ export class Voices {
|
|
|
557
547
|
anonymous: opts.asOwner === false,
|
|
558
548
|
});
|
|
559
549
|
}
|
|
560
|
-
/** The audio a voice was made from. */
|
|
561
550
|
sample(id) {
|
|
562
551
|
return this.#t.raw('GET', `/v1/voices/${seg(id)}/sample`, { headers: { accept: '*/*' } });
|
|
563
552
|
}
|
|
564
553
|
}
|
|
565
|
-
/** `vs.search`: full-text search over the caller's transcripts. */
|
|
566
554
|
export class Search {
|
|
567
555
|
#t;
|
|
568
556
|
constructor(t) {
|
|
569
557
|
this.#t = t;
|
|
570
558
|
}
|
|
571
|
-
/** `q` is 2 or more characters. */
|
|
572
559
|
query(q, opts = {}) {
|
|
573
560
|
const query = {
|
|
561
|
+
...filterQuery({ site: opts.site, language: opts.language, kind: opts.kind, origin: opts.origin, tag: opts.tag, createdAfter: opts.createdAfter, createdBefore: opts.createdBefore, shared: opts.shared }),
|
|
574
562
|
q,
|
|
575
563
|
broadcasts: opts.broadcasts?.join(','),
|
|
576
564
|
listenerTokens: opts.listenerTokens?.join(','),
|
|
577
565
|
recording: opts.recording,
|
|
566
|
+
transcription: opts.transcription,
|
|
578
567
|
lang: opts.lang,
|
|
579
568
|
limit: opts.limit,
|
|
580
569
|
};
|
|
581
570
|
return this.#t.json('GET', '/v1/search', { query });
|
|
582
571
|
}
|
|
572
|
+
vector(q, opts = {}) {
|
|
573
|
+
const query = {
|
|
574
|
+
...filterQuery({ site: opts.site, language: opts.language, kind: opts.kind, origin: opts.origin, tag: opts.tag, createdAfter: opts.createdAfter, createdBefore: opts.createdBefore, shared: opts.shared }),
|
|
575
|
+
q,
|
|
576
|
+
recording: opts.recording,
|
|
577
|
+
transcription: opts.transcription,
|
|
578
|
+
};
|
|
579
|
+
return this.#t.json('GET', '/v1/search/vector', { query });
|
|
580
|
+
}
|
|
583
581
|
}
|
|
584
|
-
/** `vs.me`: who the credential is. */
|
|
585
582
|
export class MeApi {
|
|
586
583
|
#t;
|
|
587
584
|
constructor(t) {
|
|
@@ -591,18 +588,11 @@ export class MeApi {
|
|
|
591
588
|
return this.#t.json('GET', '/v1/me');
|
|
592
589
|
}
|
|
593
590
|
}
|
|
594
|
-
/** `vs.connect`: one-click Connect, on the app's server. */
|
|
595
591
|
export class Connect {
|
|
596
592
|
#t;
|
|
597
593
|
constructor(t) {
|
|
598
594
|
this.#t = t;
|
|
599
595
|
}
|
|
600
|
-
/** Redeem the `code` the dashboard sent to the app's `redirect_uri` for
|
|
601
|
-
* an API key (OAuth 2.0 authorization code + PKCE). No credential is
|
|
602
|
-
* sent, even when the client has one, and the call is never retried:
|
|
603
|
-
* the code works once. A refusal is a `VocalStackError` whose `code` is
|
|
604
|
-
* OAuth's `error` (`invalid_grant`: unknown, spent or expired code, or a
|
|
605
|
-
* verifier or `redirect_uri` that does not match). */
|
|
606
596
|
token(input, opts = {}) {
|
|
607
597
|
const body = new URLSearchParams({
|
|
608
598
|
grant_type: 'authorization_code',
|
|
@@ -613,165 +603,125 @@ export class Connect {
|
|
|
613
603
|
return this.#t.json('POST', '/v1/connect/token', { body, anonymous: true, noRetry: true, oauthErrors: true, signal: opts.signal });
|
|
614
604
|
}
|
|
615
605
|
}
|
|
616
|
-
/** `vs.billing`: the account's plan, credit balance and limits, the price
|
|
617
|
-
* list, the ledger, the per-recording charges, the usage report, the
|
|
618
|
-
* payments history, payment methods, invoice details and promo codes. */
|
|
619
606
|
export class Billing {
|
|
620
607
|
#t;
|
|
621
608
|
constructor(t) {
|
|
622
609
|
this.#t = t;
|
|
623
610
|
}
|
|
624
|
-
/** The account: plan, rates, balance, limits, subscription, top-up URL. */
|
|
625
611
|
get() {
|
|
626
612
|
return this.#t.json('GET', '/v1/billing');
|
|
627
613
|
}
|
|
628
|
-
/** The visible plans with their current rates and limits (no credential needed). */
|
|
629
614
|
plans() {
|
|
630
615
|
return this.#t.json('GET', '/v1/billing/plans');
|
|
631
616
|
}
|
|
632
|
-
/** The ledger, newest first. */
|
|
633
617
|
ledger(opts = {}) {
|
|
634
618
|
return this.#t.json('GET', '/v1/billing/ledger', { query: { cursor: opts.cursor, limit: opts.limit } });
|
|
635
619
|
}
|
|
636
|
-
/** The per-recording charges, newest first. */
|
|
637
620
|
charges(opts = {}) {
|
|
638
621
|
return this.#t.json('GET', '/v1/billing/charges', { query: { cursor: opts.cursor, limit: opts.limit } });
|
|
639
622
|
}
|
|
640
|
-
/** Open a checkout at the payment provider (a top-up or a subscription); send the customer to `url`.
|
|
641
|
-
* A signed-in person only, never an API key. */
|
|
642
623
|
createCheckout(req, opts = {}) {
|
|
643
624
|
return this.#t.json('POST', '/v1/billing/checkouts', { body: req, ...opts });
|
|
644
625
|
}
|
|
645
|
-
/** A checkout's status (by its id or the provider's). */
|
|
646
626
|
getCheckout(id) {
|
|
647
627
|
return this.#t.json('GET', `/v1/billing/checkouts/${seg(id)}`);
|
|
648
628
|
}
|
|
649
|
-
/** Cancel the subscription at the end of its period. */
|
|
650
629
|
cancelSubscription() {
|
|
651
630
|
return this.#t.json('POST', '/v1/billing/subscription/cancel');
|
|
652
631
|
}
|
|
653
|
-
/** Take back a scheduled cancellation. */
|
|
654
632
|
uncancelSubscription() {
|
|
655
633
|
return this.#t.json('POST', '/v1/billing/subscription/uncancel');
|
|
656
634
|
}
|
|
657
|
-
/** Turn auto top-up on (threshold and amount from `plans().autoTopUp`) or off. */
|
|
658
635
|
setAutoTopUp(req) {
|
|
659
636
|
return this.#t.json('PUT', '/v1/billing/auto-top-up', { body: req });
|
|
660
637
|
}
|
|
661
|
-
/** A link to the provider's customer portal (payment methods, invoices, receipts). */
|
|
662
638
|
portal() {
|
|
663
639
|
return this.#t.json('GET', '/v1/billing/portal');
|
|
664
640
|
}
|
|
665
|
-
/** Charge the configured auto top-up amount now (under the day's cap). */
|
|
666
641
|
retryAutoTopUp() {
|
|
667
642
|
return this.#t.json('POST', '/v1/billing/auto-top-up/retry');
|
|
668
643
|
}
|
|
669
|
-
/** Turn the low-balance email on or off and set its line. */
|
|
670
644
|
setLowBalanceAlert(req) {
|
|
671
645
|
return this.#t.json('PUT', '/v1/billing/low-balance-alert', { body: req });
|
|
672
646
|
}
|
|
673
|
-
/** Renew `month`ly or `year`ly from the next period, at `subscription.switchPrices` (needs `acceptTerms` and `noNewWithdrawalPeriodShown`). */
|
|
674
647
|
setInterval(req) {
|
|
675
648
|
return this.#t.json('POST', '/v1/billing/subscription/interval', { body: req });
|
|
676
649
|
}
|
|
677
|
-
/** Keep the subscription by switching to yearly when monthly billing is withdrawn (`subscription.yearlyOptIn`). */
|
|
678
650
|
optInYearly(req) {
|
|
679
651
|
return this.#t.json('POST', '/v1/billing/subscription/yearly-opt-in', { body: req });
|
|
680
652
|
}
|
|
681
|
-
/** Take back a scheduled interval switch. */
|
|
682
653
|
unsetInterval() {
|
|
683
654
|
return this.#t.json('DELETE', '/v1/billing/subscription/interval');
|
|
684
655
|
}
|
|
685
|
-
/** What the account spent per day, source (API key or Dashboard) and kind. */
|
|
686
656
|
usage(opts = {}) {
|
|
687
657
|
return this.#t.json('GET', '/v1/billing/usage', { query: { startDate: opts.startDate, endDate: opts.endDate, tz: opts.tz } });
|
|
688
658
|
}
|
|
689
|
-
/** The usage report's charges one by one, newest first, with how many match and their total. */
|
|
690
659
|
usageCharges(opts = {}) {
|
|
691
660
|
const query = { startDate: opts.startDate, endDate: opts.endDate, tz: opts.tz, kind: opts.kind, source: opts.source, cursor: opts.cursor, limit: opts.limit };
|
|
692
661
|
return this.#t.json('GET', '/v1/billing/usage/charges', { query });
|
|
693
662
|
}
|
|
694
|
-
/** The payments history, newest first. */
|
|
695
663
|
payments(opts = {}) {
|
|
696
664
|
return this.#t.json('GET', '/v1/billing/payments', { query: { cursor: opts.cursor, limit: opts.limit } });
|
|
697
665
|
}
|
|
698
|
-
/** A payment's invoice or receipt: `{url}`, or `{pending: true}` while the provider makes it. */
|
|
699
666
|
paymentDocument(id) {
|
|
700
667
|
return this.#t.json('GET', `/v1/billing/payments/${seg(id)}/document`);
|
|
701
668
|
}
|
|
702
|
-
/** The saved payment methods. */
|
|
703
669
|
paymentMethods() {
|
|
704
670
|
return this.#t.json('GET', '/v1/billing/payment-methods');
|
|
705
671
|
}
|
|
706
|
-
/** Remove a saved payment method. */
|
|
707
672
|
async deletePaymentMethod(id) {
|
|
708
673
|
await this.#t.json('DELETE', `/v1/billing/payment-methods/${seg(id)}`);
|
|
709
674
|
}
|
|
710
|
-
/** A link to the provider's portal to add a card or choose the default. */
|
|
711
675
|
managePaymentMethods() {
|
|
712
676
|
return this.#t.json('POST', '/v1/billing/payment-methods/manage');
|
|
713
677
|
}
|
|
714
|
-
/** The invoice details. */
|
|
715
678
|
details() {
|
|
716
679
|
return this.#t.json('GET', '/v1/billing/details');
|
|
717
680
|
}
|
|
718
|
-
/** Save the invoice details. */
|
|
719
681
|
setDetails(req) {
|
|
720
682
|
return this.#t.json('PUT', '/v1/billing/details', { body: req });
|
|
721
683
|
}
|
|
722
|
-
/** What a promo code gives for a top-up or a subscription. */
|
|
723
684
|
checkPromoCode(req) {
|
|
724
685
|
return this.#t.json('POST', '/v1/billing/promo-codes/check', { body: req });
|
|
725
686
|
}
|
|
726
687
|
}
|
|
727
|
-
/** `vs.languages`. */
|
|
728
688
|
export class Languages {
|
|
729
689
|
#t;
|
|
730
690
|
constructor(t) {
|
|
731
691
|
this.#t = t;
|
|
732
692
|
}
|
|
733
|
-
/** Spoken and translation languages. */
|
|
734
693
|
list() {
|
|
735
694
|
return this.#t.json('GET', '/v1/languages');
|
|
736
695
|
}
|
|
737
696
|
}
|
|
738
|
-
/** `vs.rtmp`. */
|
|
739
697
|
export class Rtmp {
|
|
740
698
|
#t;
|
|
741
699
|
constructor(t) {
|
|
742
700
|
this.#t = t;
|
|
743
701
|
}
|
|
744
|
-
/** The account's RTMP ingest: server URL (RTMPS where available), the stream key (in full only in the answer that made it, `maskedStreamKey` after), and whether an encoder is sending. */
|
|
745
702
|
status() {
|
|
746
703
|
return this.#t.json('GET', '/v1/rtmp');
|
|
747
704
|
}
|
|
748
|
-
/** A stream key of the app's own, beside the account's: `streamKey` is in full in this answer only. It works while the API key that made it does. */
|
|
749
705
|
createKey(req) {
|
|
750
706
|
return this.#t.json('POST', '/v1/rtmp/keys', { body: req });
|
|
751
707
|
}
|
|
752
|
-
/** The account's app stream keys, oldest first: masked, each with whether an encoder is sending on it (`live`, `audio`). */
|
|
753
708
|
async listKeys() {
|
|
754
709
|
const r = await this.#t.json('GET', '/v1/rtmp/keys');
|
|
755
710
|
return r.keys ?? [];
|
|
756
711
|
}
|
|
757
|
-
/** Delete an app stream key: an encoder publishing on it is dropped within seconds. */
|
|
758
712
|
async deleteKey(id) {
|
|
759
713
|
await this.#t.json('DELETE', `/v1/rtmp/keys/${seg(id)}`);
|
|
760
714
|
}
|
|
761
715
|
}
|
|
762
|
-
/** `vs.events`: the account socket. */
|
|
763
716
|
export class Events {
|
|
764
717
|
#t;
|
|
765
718
|
constructor(t) {
|
|
766
719
|
this.#t = t;
|
|
767
720
|
}
|
|
768
|
-
/** `events.ready`, then `lists.changed` whenever one of the owner's
|
|
769
|
-
* lists of broadcasts or transcriptions is out of date: re-read them. */
|
|
770
721
|
async account() {
|
|
771
722
|
return openWebSocket(this.#t.wsUrl('/v1/events'), await this.#t.bearer());
|
|
772
723
|
}
|
|
773
724
|
}
|
|
774
|
-
/** `vs.meta`. */
|
|
775
725
|
export class Meta {
|
|
776
726
|
#t;
|
|
777
727
|
constructor(t) {
|
|
@@ -780,8 +730,10 @@ export class Meta {
|
|
|
780
730
|
health() {
|
|
781
731
|
return this.#t.json('GET', '/v1/health');
|
|
782
732
|
}
|
|
783
|
-
/** The OpenAPI document this SDK is generated from. */
|
|
784
733
|
openapi() {
|
|
785
734
|
return this.#t.json('GET', '/v1/openapi.json');
|
|
786
735
|
}
|
|
736
|
+
clientPolicy(client) {
|
|
737
|
+
return this.#t.json('GET', `/v1/clients/${encodeURIComponent(client)}`);
|
|
738
|
+
}
|
|
787
739
|
}
|