@owncast/plugin-sdk 0.6.0 → 0.10.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 +6 -6
- package/bin/owncast-plugin.js +39 -37
- package/index.d.ts +172 -89
- package/index.js +212 -279
- package/package.json +2 -1
- package/scripts/postinstall.js +5 -5
- package/slug.js +26 -0
- package/testing.js +15 -48
package/index.d.ts
CHANGED
|
@@ -1,34 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Built-in chat message payload (`chat.message.received` and the chat filter).
|
|
3
3
|
*
|
|
4
|
-
* `user` carries the full sender identity
|
|
4
|
+
* `user` carries the full sender identity. Use `user.id` for stable per-user
|
|
5
5
|
* state and `user.scopes` (e.g. `"MODERATOR"`) for reliable, non-spoofable
|
|
6
6
|
* moderation gating rather than matching on the display name. `clientId`
|
|
7
|
-
* identifies the originating connection
|
|
7
|
+
* identifies the originating connection. Pass it to `owncast.chat.sendTo` (or
|
|
8
8
|
* `owncast.chat.replyTo(msg, …)`) to whisper a reply back to the sender.
|
|
9
9
|
*
|
|
10
10
|
* `user` is undefined for the rare message with no associated account.
|
|
11
11
|
*/
|
|
12
12
|
export interface ChatMessage {
|
|
13
13
|
id: string;
|
|
14
|
-
user?:
|
|
14
|
+
user?: User;
|
|
15
15
|
clientId?: number;
|
|
16
16
|
body: string;
|
|
17
17
|
timestamp: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** A chat user, payload of join/part/rename events. */
|
|
21
|
-
export interface ChatUser {
|
|
22
|
-
id: string;
|
|
23
|
-
displayName: string;
|
|
24
|
-
isBot?: boolean;
|
|
25
|
-
isAuthenticated?: boolean;
|
|
26
|
-
scopes?: string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
20
|
/** Payload of `chat.user.renamed`, the same user changing their name. */
|
|
30
21
|
export interface ChatUserRename {
|
|
31
|
-
user:
|
|
22
|
+
user: User;
|
|
32
23
|
previousName: string;
|
|
33
24
|
}
|
|
34
25
|
|
|
@@ -36,7 +27,7 @@ export interface ChatUserRename {
|
|
|
36
27
|
export interface ChatMessageModeration {
|
|
37
28
|
messageId: string;
|
|
38
29
|
visible: boolean;
|
|
39
|
-
moderator?:
|
|
30
|
+
moderator?: User;
|
|
40
31
|
}
|
|
41
32
|
|
|
42
33
|
/** Stream-lifecycle payloads. */
|
|
@@ -129,9 +120,11 @@ export const Events: {
|
|
|
129
120
|
readonly SseConnect: "sse.connect";
|
|
130
121
|
readonly SseDisconnect: "sse.disconnect";
|
|
131
122
|
readonly Tick: "tick";
|
|
123
|
+
readonly FediverseActivity: "fediverse.activity";
|
|
132
124
|
readonly FediverseFollow: "fediverse.follow";
|
|
133
125
|
readonly FediverseLike: "fediverse.like";
|
|
134
126
|
readonly FediverseRepost: "fediverse.repost";
|
|
127
|
+
readonly FediverseQuote: "fediverse.quote";
|
|
135
128
|
readonly FediverseMention: "fediverse.mention";
|
|
136
129
|
readonly FediverseReply: "fediverse.reply";
|
|
137
130
|
};
|
|
@@ -146,10 +139,14 @@ export interface FediverseActor {
|
|
|
146
139
|
|
|
147
140
|
export interface FediverseEngagement {
|
|
148
141
|
actor: FediverseActor;
|
|
149
|
-
/** For likes and
|
|
142
|
+
/** For likes, reposts, and quotes: the target object URL. Not set for follows. */
|
|
150
143
|
target?: { url: string };
|
|
151
144
|
}
|
|
152
145
|
|
|
146
|
+
export interface FediverseTargetedEngagement extends FediverseEngagement {
|
|
147
|
+
target: { url: string };
|
|
148
|
+
}
|
|
149
|
+
|
|
153
150
|
/** Inbound fediverse post, a mention or reply that contains content the
|
|
154
151
|
* plugin can act on. Carries both the rendered content (which has the
|
|
155
152
|
* source instance's HTML) and a plain-text version (HTML stripped). */
|
|
@@ -183,13 +180,40 @@ export const Permissions: {
|
|
|
183
180
|
readonly NotificationsSend: "notifications.send";
|
|
184
181
|
readonly UsersRead: "users.read";
|
|
185
182
|
readonly UsersModerate: "users.moderate";
|
|
183
|
+
readonly UsersRegister: "users.register";
|
|
184
|
+
readonly AuthGate: "auth.gate";
|
|
186
185
|
readonly FediversePost: "fediverse.post";
|
|
186
|
+
readonly FediverseInbound: "fediverse.inbound";
|
|
187
187
|
readonly HttpSSE: "http.sse";
|
|
188
188
|
readonly VideoConfigRead: "videoconfig.read";
|
|
189
189
|
readonly VideoConfigWrite: "videoconfig.write";
|
|
190
190
|
readonly UIModify: "ui.modify";
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
+
/** Request for `owncast.users.register`. */
|
|
194
|
+
export interface UserRegisterRequest {
|
|
195
|
+
/** Stable, provider-scoped external identity (e.g. `"github:583231"`). The
|
|
196
|
+
* host namespaces it by the calling plugin's slug. */
|
|
197
|
+
authId: string;
|
|
198
|
+
/** Optional display name to seed on the user. */
|
|
199
|
+
displayName?: string;
|
|
200
|
+
/** Optional scopes to grant the user (e.g. `["MODERATOR"]`). */
|
|
201
|
+
scopes?: string[];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Result of `owncast.users.register`: the resolved Owncast user ID. */
|
|
205
|
+
export interface UserRegisterResult {
|
|
206
|
+
userId: string;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Request for `owncast.auth.grantSession`. */
|
|
210
|
+
export interface GrantSessionRequest {
|
|
211
|
+
/** The Owncast user ID returned by `owncast.users.register`. */
|
|
212
|
+
userId: string;
|
|
213
|
+
/** Optional session lifetime in seconds. 0/omitted uses the host default. */
|
|
214
|
+
ttl?: number;
|
|
215
|
+
}
|
|
216
|
+
|
|
193
217
|
export interface BrowserPushPayload {
|
|
194
218
|
title: string;
|
|
195
219
|
body?: string;
|
|
@@ -221,10 +245,14 @@ export interface FederationInfo {
|
|
|
221
245
|
isPrivate?: boolean;
|
|
222
246
|
}
|
|
223
247
|
|
|
224
|
-
/** A user
|
|
248
|
+
/** A user. The sender identity carried by every chat payload
|
|
249
|
+
* (chat.message.received, join/part/rename, moderation) and the record
|
|
250
|
+
* returned by owncast.users.list() / .get(). `displayColor` is an index into
|
|
251
|
+
* the instance's configured user-color palette, not a literal color. */
|
|
225
252
|
export interface User {
|
|
226
253
|
id: string;
|
|
227
254
|
displayName: string;
|
|
255
|
+
displayColor: number;
|
|
228
256
|
previousNames?: string[];
|
|
229
257
|
createdAt?: string;
|
|
230
258
|
disabledAt?: string; // ISO-8601 if banned, omitted otherwise
|
|
@@ -262,6 +290,30 @@ export const filter: {
|
|
|
262
290
|
drop(reason?: string): FilterResult;
|
|
263
291
|
};
|
|
264
292
|
|
|
293
|
+
/** Request passed to `onAuthCheck`: the host-resolved identity of the viewer
|
|
294
|
+
* whose session is being re-validated (same `user` shape `onHttpRequest`
|
|
295
|
+
* receives, and the plugin never re-resolves it). */
|
|
296
|
+
export interface AuthCheckRequest {
|
|
297
|
+
user: User;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Verdict returned from `onAuthCheck`:
|
|
301
|
+
* - `ok` keep the session as-is
|
|
302
|
+
* - `refresh` keep it and re-issue the cookie (optionally with a new `ttl`
|
|
303
|
+
* in seconds) for sliding-expiry
|
|
304
|
+
* - `deny` end the session and bounce the viewer back to the login screen */
|
|
305
|
+
export type AuthCheckResult =
|
|
306
|
+
| { action: "ok" }
|
|
307
|
+
| { action: "refresh"; ttl?: number }
|
|
308
|
+
| { action: "deny"; reason?: string };
|
|
309
|
+
|
|
310
|
+
/** Verdict helpers for `onAuthCheck`. */
|
|
311
|
+
export const authCheck: {
|
|
312
|
+
ok(): AuthCheckResult;
|
|
313
|
+
refresh(opts?: { ttl?: number }): AuthCheckResult;
|
|
314
|
+
deny(reason?: string): AuthCheckResult;
|
|
315
|
+
};
|
|
316
|
+
|
|
265
317
|
/** Incoming HTTP request, paths are relative to the plugin's namespace
|
|
266
318
|
* (i.e. the leading /plugins/<name>/ has been stripped). */
|
|
267
319
|
export interface IncomingHttpRequest {
|
|
@@ -275,7 +327,7 @@ export interface IncomingHttpRequest {
|
|
|
275
327
|
authenticated: boolean;
|
|
276
328
|
/** Identity of the user that made the request, when it came with a
|
|
277
329
|
* user-token. Undefined for anonymous or admin-only requests. */
|
|
278
|
-
user?:
|
|
330
|
+
user?: User;
|
|
279
331
|
}
|
|
280
332
|
|
|
281
333
|
export interface OutgoingHttpResponse {
|
|
@@ -290,7 +342,7 @@ export interface ContentRequest {
|
|
|
290
342
|
slug: string;
|
|
291
343
|
/** The viewing user's chat identity, when available. Undefined for
|
|
292
344
|
* anonymous viewers or when the host cannot resolve an identity. */
|
|
293
|
-
user?:
|
|
345
|
+
user?: User;
|
|
294
346
|
}
|
|
295
347
|
|
|
296
348
|
/** Payload for the sse.connect / sse.disconnect events. Fired when a browser
|
|
@@ -302,7 +354,7 @@ export interface ContentRequest {
|
|
|
302
354
|
export interface SSEConnectionEvent {
|
|
303
355
|
channel: string;
|
|
304
356
|
connectionId: number;
|
|
305
|
-
user?:
|
|
357
|
+
user?: User;
|
|
306
358
|
}
|
|
307
359
|
|
|
308
360
|
/** Payload for the once-a-second tick event (onTick). `now` is the host
|
|
@@ -312,21 +364,13 @@ export interface TickEvent {
|
|
|
312
364
|
}
|
|
313
365
|
|
|
314
366
|
export interface PluginDef {
|
|
315
|
-
/** Declarative chat
|
|
316
|
-
*
|
|
317
|
-
* canonical command name → definition (run/description/usage/aliases/
|
|
318
|
-
* modOnly/cooldownMs/...); see {@link CommandDefinition}. For advanced
|
|
319
|
-
* composition (e.g. dropping command messages via a filter) use the
|
|
320
|
-
* lower-level {@link defineCommands} router instead. If you also provide
|
|
321
|
-
* onChatMessage, the router runs first and then onChatMessage runs for every
|
|
322
|
-
* message. */
|
|
367
|
+
/** Declarative chat commands with aliases, moderator gates, and per-user
|
|
368
|
+
* cooldowns. Command messages also remain available to onChatMessage. */
|
|
323
369
|
commands?: Record<string, CommandDefinition>;
|
|
324
370
|
/** Command prefix for the `commands` table. Default "!". */
|
|
325
371
|
commandPrefix?: string;
|
|
326
372
|
/** Match command names case-sensitively. Default false. */
|
|
327
373
|
commandsCaseSensitive?: boolean;
|
|
328
|
-
/** Called when a prefixed message matched no command in `commands`. */
|
|
329
|
-
onUnknownCommand?(ctx: CommandContext): void;
|
|
330
374
|
|
|
331
375
|
/** Notification handler for chat messages. Fire-and-forget. */
|
|
332
376
|
onChatMessage?(msg: ChatMessage): void | Promise<void>;
|
|
@@ -336,9 +380,9 @@ export interface PluginDef {
|
|
|
336
380
|
filterChatMessage?(msg: ChatMessage): FilterResult;
|
|
337
381
|
|
|
338
382
|
/** User connected to chat. */
|
|
339
|
-
onChatUserJoined?(user:
|
|
383
|
+
onChatUserJoined?(user: User): void | Promise<void>;
|
|
340
384
|
/** User disconnected from chat. */
|
|
341
|
-
onChatUserParted?(user:
|
|
385
|
+
onChatUserParted?(user: User): void | Promise<void>;
|
|
342
386
|
/** User changed their display name. */
|
|
343
387
|
onChatUserRenamed?(change: ChatUserRename): void | Promise<void>;
|
|
344
388
|
/** A chat message was hidden or restored by a moderator. */
|
|
@@ -362,15 +406,20 @@ export interface PluginDef {
|
|
|
362
406
|
* in unix milliseconds. Defining this opts the plugin into the tick. */
|
|
363
407
|
onTick?(event: TickEvent): void | Promise<void>;
|
|
364
408
|
|
|
365
|
-
/**
|
|
409
|
+
/** A verified inbound ActivityPub activity as its raw JSON object. Requires `fediverse.inbound`. */
|
|
410
|
+
onFediverse?(activity: Record<string, unknown>): void | Promise<void>;
|
|
411
|
+
|
|
412
|
+
/** Someone on the fediverse followed the streamer's account. Requires `fediverse.inbound`. */
|
|
366
413
|
onFediverseFollow?(event: FediverseEngagement): void | Promise<void>;
|
|
367
|
-
/** Someone on the fediverse liked a streamer post / federated stream announcement. */
|
|
368
|
-
onFediverseLike?(event:
|
|
369
|
-
/** Someone on the fediverse boosted (reposted) a streamer post. */
|
|
370
|
-
onFediverseRepost?(event:
|
|
371
|
-
/** Someone
|
|
414
|
+
/** Someone on the fediverse liked a streamer post / federated stream announcement. Requires `fediverse.inbound`. */
|
|
415
|
+
onFediverseLike?(event: FediverseTargetedEngagement): void | Promise<void>;
|
|
416
|
+
/** Someone on the fediverse boosted (reposted) a streamer post. Requires `fediverse.inbound`. */
|
|
417
|
+
onFediverseRepost?(event: FediverseTargetedEngagement): void | Promise<void>;
|
|
418
|
+
/** Someone on the fediverse quoted a locally authored post. `target.url` identifies that quoted post. Requires `fediverse.inbound`. */
|
|
419
|
+
onFediverseQuote?(event: FediverseTargetedEngagement): void | Promise<void>;
|
|
420
|
+
/** Someone @-mentioned the streamer in a public post. Requires `fediverse.inbound`. */
|
|
372
421
|
onFediverseMention?(post: FediverseInboundPost): void | Promise<void>;
|
|
373
|
-
/** Someone replied to one of the streamer's federated posts. */
|
|
422
|
+
/** Someone replied to one of the streamer's federated posts. Requires `fediverse.inbound`. */
|
|
374
423
|
onFediverseReply?(post: FediverseInboundPost): void | Promise<void>;
|
|
375
424
|
|
|
376
425
|
/** HTTP request handler. Called for any path under /plugins/<name>/ that
|
|
@@ -378,6 +427,15 @@ export interface PluginDef {
|
|
|
378
427
|
* on `req.authenticated` yourself. Requires `http.serve` permission. */
|
|
379
428
|
onHttpRequest?(req: IncomingHttpRequest): OutgoingHttpResponse;
|
|
380
429
|
|
|
430
|
+
/** Re-validate a viewer's gate session on page load. Only meaningful for the
|
|
431
|
+
* active `auth.gate` plugin: the host calls it on the viewer's `/` request
|
|
432
|
+
* with the resolved `req.user`, and acts on the verdict: `ok` to continue,
|
|
433
|
+
* `refresh` to extend the session, `deny` to revoke it and force re-login.
|
|
434
|
+
* Optional. Without it a granted session lasts until its cookie
|
|
435
|
+
* expires (no mid-session revocation). This is the revocation hook: return
|
|
436
|
+
* `deny` for users your provider has banned/deleted. Requires `auth.gate`. */
|
|
437
|
+
onAuthCheck?(req: AuthCheckRequest): AuthCheckResult;
|
|
438
|
+
|
|
381
439
|
/** Render HTML for a dynamic tab. Called by the host when the tab was
|
|
382
440
|
* declared in the manifest without a static `content` file. Return the
|
|
383
441
|
* full HTML string to inline as the tab body. `req.user` is the viewer's
|
|
@@ -390,6 +448,28 @@ export interface PluginDef {
|
|
|
390
448
|
* `req.user` is the viewer's chat identity when available. */
|
|
391
449
|
onPageContent?(req: ContentRequest): string;
|
|
392
450
|
|
|
451
|
+
/** Return CSS to inline into the viewer page at request time, the dynamic
|
|
452
|
+
* counterpart to `manifest.styles`, applied to the whole UI. Called once
|
|
453
|
+
* per `/api/config` for any plugin holding `ui.modify`. No manifest field
|
|
454
|
+
* is needed, just export this handler. Return nothing (a bare `return`, or
|
|
455
|
+
* `""`) to contribute nothing. The output is appended after any static
|
|
456
|
+
* `manifest.styles` files, so returning only the active override wins
|
|
457
|
+
* within your plugin's own styles. Plugin styles sit below the admin's
|
|
458
|
+
* appearance settings, so an admin's explicit colors override yours.
|
|
459
|
+
* Global (no per-viewer argument) so `/api/config` stays cacheable.
|
|
460
|
+
* Requires `ui.modify`. */
|
|
461
|
+
onPageStyles?(): string | null | void;
|
|
462
|
+
|
|
463
|
+
/** Return JavaScript to append to the viewer page at request time, the
|
|
464
|
+
* dynamic counterpart to `manifest.scripts`. Called once per `/api/config`
|
|
465
|
+
* for any plugin holding `ui.modify`. The host wraps each plugin's script
|
|
466
|
+
* (static and dynamic) in a try/catch so a runtime error can't break other
|
|
467
|
+
* plugins, but it runs in the shared viewer `window`: wrap your code in an
|
|
468
|
+
* IIFE to avoid global collisions, and escape any untrusted strings you
|
|
469
|
+
* embed. Return nothing (a bare `return`, or `""`) to contribute nothing.
|
|
470
|
+
* Requires `ui.modify`. */
|
|
471
|
+
onPageScripts?(): string | null | void;
|
|
472
|
+
|
|
393
473
|
/** Handlers for plugin-emitted custom events. The key is the event type
|
|
394
474
|
* string (e.g. "announcement.broadcast"). Notifications only, to filter
|
|
395
475
|
* custom events, additional API will be needed. */
|
|
@@ -407,61 +487,46 @@ export interface CommandContext {
|
|
|
407
487
|
/** The originating chat message. */
|
|
408
488
|
msg: ChatMessage;
|
|
409
489
|
/** The sender (same as `msg.user`). */
|
|
410
|
-
user?:
|
|
490
|
+
user?: User;
|
|
411
491
|
/** The canonical command name that matched (not the alias used). */
|
|
412
492
|
command: string;
|
|
493
|
+
/** The command name or alias exactly as the sender typed it. */
|
|
494
|
+
invokedAs: string;
|
|
413
495
|
/** Whitespace-split arguments after the command word. */
|
|
414
496
|
args: string[];
|
|
415
497
|
/** The raw argument string (everything after the command word, trimmed). */
|
|
416
498
|
argString: string;
|
|
417
499
|
/** Post a public reply as the plugin's chat bot. */
|
|
418
500
|
reply(text: string): void;
|
|
419
|
-
/** Whisper a reply to the sender
|
|
501
|
+
/** Whisper a reply to the sender, falling back to a public post if their
|
|
420
502
|
* connection is unknown. */
|
|
421
503
|
replyPrivately(text: string): void;
|
|
422
504
|
}
|
|
423
505
|
|
|
424
|
-
/** One command in a
|
|
506
|
+
/** One command in a declarative command table. */
|
|
425
507
|
export interface CommandDefinition {
|
|
426
|
-
/** Short, human-readable summary
|
|
427
|
-
* command listings (e.g. a future `!help`); ignored by the router itself. */
|
|
508
|
+
/** Short, human-readable summary shown in command listings. */
|
|
428
509
|
description?: string;
|
|
429
510
|
/** Optional usage/example string, e.g. "!latency <0-4>". */
|
|
430
511
|
usage?: string;
|
|
431
512
|
/** Alternate names that invoke this command. */
|
|
432
513
|
aliases?: string[];
|
|
433
|
-
/**
|
|
514
|
+
/** Dispatch only for senders whose scopes include "MODERATOR". */
|
|
434
515
|
modOnly?: boolean;
|
|
435
|
-
/**
|
|
436
|
-
* `msg.timestamp`). */
|
|
516
|
+
/** Non-negative integer milliseconds between invocations per user. */
|
|
437
517
|
cooldownMs?: number;
|
|
438
518
|
/** Invoked when the command runs. */
|
|
439
519
|
run(ctx: CommandContext): void;
|
|
440
|
-
/** Invoked instead of `run` when a non-moderator calls a `modOnly` command. */
|
|
441
|
-
onDenied?(ctx: CommandContext): void;
|
|
442
|
-
/** Invoked instead of `run` when the per-user cooldown hasn't elapsed. */
|
|
443
|
-
onCooldown?(ctx: CommandContext): void;
|
|
444
520
|
}
|
|
445
521
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
/** Default denied/cooldown handlers, used when a command omits its own. */
|
|
455
|
-
onDenied?(ctx: CommandContext): void;
|
|
456
|
-
onCooldown?(ctx: CommandContext): void;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/** Build a chat-command router (prefix parsing, aliases, per-user cooldowns,
|
|
460
|
-
* moderator gating). Feed the returned function a `ChatMessage`; it returns
|
|
461
|
-
* true when the message was a command (even if gated), false otherwise. */
|
|
462
|
-
export function defineCommands(
|
|
463
|
-
config: CommandsConfig,
|
|
464
|
-
): (msg: ChatMessage) => boolean;
|
|
522
|
+
/** Internal payload for a matched command declaration. */
|
|
523
|
+
export interface CommandEvent {
|
|
524
|
+
message: ChatMessage;
|
|
525
|
+
command: string;
|
|
526
|
+
invokedAs: string;
|
|
527
|
+
args: string[];
|
|
528
|
+
argString: string;
|
|
529
|
+
}
|
|
465
530
|
|
|
466
531
|
/** Typed wrappers around the Owncast host. Each method throws if the
|
|
467
532
|
* corresponding permission was not declared in plugin.manifest.json. */
|
|
@@ -485,7 +550,7 @@ export const owncast: {
|
|
|
485
550
|
* `chat.send`. */
|
|
486
551
|
replyTo(msg: ChatMessage | number | bigint, text: string): boolean;
|
|
487
552
|
/** Recent chat history (most recent last). Requires `chat.history`.
|
|
488
|
-
* Default limit is 50
|
|
553
|
+
* Default limit is 50. Pass a smaller number to get fewer. */
|
|
489
554
|
history(limit?: number): ChatMessage[];
|
|
490
555
|
/** Hide a chat message by ID. Requires `chat.moderate`. */
|
|
491
556
|
deleteMessage(messageId: string): void;
|
|
@@ -500,12 +565,30 @@ export const owncast: {
|
|
|
500
565
|
list(): User[];
|
|
501
566
|
/** Fetch one user by ID. Requires `users.read`. */
|
|
502
567
|
get(id: string): User | null;
|
|
503
|
-
/** Enable/disable a user
|
|
568
|
+
/** Enable/disable a user, with an optional reason. Requires `users.moderate`. */
|
|
504
569
|
setEnabled(id: string, enabled: boolean, reason?: string): void;
|
|
505
570
|
/** Ban an IP address. Requires `users.moderate`. */
|
|
506
571
|
banIP(ip: string): void;
|
|
572
|
+
/** Find-or-create an authenticated user for an external identity. `authId`
|
|
573
|
+
* is the stable provider-scoped id (e.g. `"github:583231"`). The host
|
|
574
|
+
* namespaces it by this plugin's slug so plugins can't collide on or spoof
|
|
575
|
+
* each other's users. Returns `{ userId }`. Throws on host error.
|
|
576
|
+
* Requires `users.register`. */
|
|
577
|
+
register(opts: UserRegisterRequest | string): UserRegisterResult;
|
|
578
|
+
};
|
|
579
|
+
/** Viewer-authentication gate. Only a plugin holding `auth.gate` (and enabled
|
|
580
|
+
* by an admin) can issue sessions, and only inside `onHttpRequest`, where the
|
|
581
|
+
* host attaches or clears the signed session cookie on the response. */
|
|
582
|
+
auth: {
|
|
583
|
+
/** Issue a gate session for an already-registered user (see
|
|
584
|
+
* `users.register`). `ttl` is optional seconds (0/omitted = host default).
|
|
585
|
+
* Throws on host error. Requires `auth.gate`. */
|
|
586
|
+
grantSession(opts: GrantSessionRequest | string): void;
|
|
587
|
+
/** Clear the current viewer's gate session (logout). The plugin still owns
|
|
588
|
+
* the response/redirect. Requires `auth.gate`. */
|
|
589
|
+
endSession(): void;
|
|
507
590
|
};
|
|
508
|
-
/** Upload bytes to Owncast's storage backend (local or S3)
|
|
591
|
+
/** Upload bytes to Owncast's storage backend (local or S3). Returns a
|
|
509
592
|
* public URL. Requires `storage.upload`. */
|
|
510
593
|
storage: {
|
|
511
594
|
upload(name: string, data: Uint8Array | string): UploadResult | null;
|
|
@@ -520,7 +603,7 @@ export const owncast: {
|
|
|
520
603
|
readText(path: string): string | null;
|
|
521
604
|
/** Write bytes or a string, creating parent directories as needed. */
|
|
522
605
|
write(path: string, data: Uint8Array | string): FsResult;
|
|
523
|
-
/** List entry names directly inside dir
|
|
606
|
+
/** List entry names directly inside dir. A missing dir lists as empty. */
|
|
524
607
|
list(dir: string): string[];
|
|
525
608
|
/** Remove a single file or empty directory. */
|
|
526
609
|
delete(path: string): FsResult;
|
|
@@ -528,7 +611,7 @@ export const owncast: {
|
|
|
528
611
|
exists(path: string): boolean;
|
|
529
612
|
};
|
|
530
613
|
/** Post to the fediverse on the streamer's behalf. Requires `fediverse.post`,
|
|
531
|
-
* which is high-trust (posts go out under the streamer's own handle)
|
|
614
|
+
* which is high-trust (posts go out under the streamer's own handle), so
|
|
532
615
|
* admins should grant it sparingly. */
|
|
533
616
|
fediverse: {
|
|
534
617
|
/** Publish a public, text-only post. Returns `{ url }` (currently empty
|
|
@@ -557,7 +640,7 @@ export const owncast: {
|
|
|
557
640
|
setJSON(key: string, value: unknown): void;
|
|
558
641
|
};
|
|
559
642
|
/** Read this plugin's admin-configurable settings, declared under
|
|
560
|
-
* `config` in the manifest. Ambient
|
|
643
|
+
* `config` in the manifest. Ambient, so no permission is required. */
|
|
561
644
|
config: {
|
|
562
645
|
/** The effective value of a manifest-declared config key (admin override,
|
|
563
646
|
* else the declared default), parsed to its declared type. Returns
|
|
@@ -565,10 +648,10 @@ export const owncast: {
|
|
|
565
648
|
* value. */
|
|
566
649
|
get<T = unknown>(key: string, fallback?: T): T;
|
|
567
650
|
};
|
|
568
|
-
/** Read files the plugin bundled in its own `assets/` directory
|
|
651
|
+
/** Read files the plugin bundled in its own `assets/` directory: templates,
|
|
569
652
|
* data files, and other bundled resources loaded at request time. Path is
|
|
570
|
-
* relative to `assets/` and must not contain `..`. Ambient
|
|
571
|
-
* required. */
|
|
653
|
+
* relative to `assets/` and must not contain `..`. Ambient, so no permission
|
|
654
|
+
* is required. */
|
|
572
655
|
assets: {
|
|
573
656
|
/** Raw bytes of the file, or `null` if not found. */
|
|
574
657
|
read(path: string): Uint8Array | null;
|
|
@@ -585,30 +668,30 @@ export const owncast: {
|
|
|
585
668
|
actions: {
|
|
586
669
|
/** Append one or more buttons to the plugin's runtime list. Each
|
|
587
670
|
* entry is validated with the same rules as `manifest.actions`
|
|
588
|
-
* (title required
|
|
589
|
-
* rewritten into this plugin's namespace
|
|
671
|
+
* (title required, exactly one of `url` or `html`, relative URLs
|
|
672
|
+
* rewritten into this plugin's namespace, cross-plugin URLs
|
|
590
673
|
* rejected). The next viewer `/api/config` request returns
|
|
591
674
|
* `manifest.actions` ++ the runtime list. */
|
|
592
675
|
add(actions: ActionButton | ActionButton[]): void;
|
|
593
|
-
/** Drop the runtime additions
|
|
676
|
+
/** Drop the runtime additions, so only `manifest.actions` remain on
|
|
594
677
|
* the next viewer `/api/config` request. */
|
|
595
678
|
clear(): void;
|
|
596
679
|
};
|
|
597
680
|
sse: {
|
|
598
681
|
/** Push one Server-Sent-Event to every browser connected to this
|
|
599
682
|
* plugin's `/plugins/<name>/_sse/<channel>` stream. `event` is the SSE
|
|
600
|
-
* event name (`""` → the default "message" event)
|
|
601
|
-
* if a string, otherwise JSON-stringified. Fire-and-forget
|
|
683
|
+
* event name (`""` → the default "message" event). `data` is sent as-is
|
|
684
|
+
* if a string, otherwise JSON-stringified. Fire-and-forget, and frames to a
|
|
602
685
|
* slow client are dropped rather than blocking the plugin. Requires the
|
|
603
686
|
* `http.sse` permission. */
|
|
604
687
|
send(channel: string, event: string, data: unknown): void;
|
|
605
688
|
};
|
|
606
|
-
/** Host-driven timers. The sandbox has no setTimeout
|
|
689
|
+
/** Host-driven timers. The sandbox has no setTimeout, so these ask the host to
|
|
607
690
|
* call your callback back later (in this instance). No permission required.
|
|
608
691
|
* Timers do not survive a plugin reload or host restart. */
|
|
609
692
|
timer: {
|
|
610
693
|
/** Run `fn` once after ~`ms` milliseconds. Returns an id for `clear()`.
|
|
611
|
-
* Very small delays are clamped up by the host
|
|
694
|
+
* Very small delays are clamped up by the host. Throws past the
|
|
612
695
|
* per-plugin pending-timer cap. */
|
|
613
696
|
setTimeout(fn: () => void, ms: number): number;
|
|
614
697
|
/** Run `fn` every ~`ms` milliseconds until `clear()`. The next run is
|
|
@@ -635,7 +718,7 @@ export const owncast: {
|
|
|
635
718
|
tags(): string[];
|
|
636
719
|
};
|
|
637
720
|
/** Read/change video/transcoding configuration. read() requires
|
|
638
|
-
* `videoconfig.read
|
|
721
|
+
* `videoconfig.read`, and write() requires `videoconfig.write`. */
|
|
639
722
|
videoConfig: {
|
|
640
723
|
read(): VideoConfig;
|
|
641
724
|
write(config: VideoConfigUpdate): void;
|
|
@@ -656,7 +739,7 @@ export interface HttpResponse {
|
|
|
656
739
|
|
|
657
740
|
/** An entry in `manifest.actions`, declares an action button the Owncast
|
|
658
741
|
* UI surfaces while this plugin is enabled. Mirrors Owncast's existing
|
|
659
|
-
* ExternalAction shape
|
|
742
|
+
* ExternalAction shape. The host merges enabled-plugin buttons with the
|
|
660
743
|
* admin-configured list.
|
|
661
744
|
*
|
|
662
745
|
* Exactly one of `url` or `html` is required.
|
|
@@ -687,10 +770,10 @@ export interface ActionButton {
|
|
|
687
770
|
|
|
688
771
|
/** `manifest.network`, narrows outbound HTTP scope for plugins that
|
|
689
772
|
* declare the `network.fetch` permission. Required when that permission
|
|
690
|
-
* is granted
|
|
773
|
+
* is granted. The host rejects loads otherwise. */
|
|
691
774
|
export interface NetworkConfig {
|
|
692
775
|
/** Hostname globs the plugin can reach via `owncast.http.fetch`.
|
|
693
|
-
* Bare names match exactly (`"api.discord.com"`)
|
|
776
|
+
* Bare names match exactly (`"api.discord.com"`), and `*` is a wildcard
|
|
694
777
|
* segment (`"*.weather.com"`). The bare wildcard `"*"` matches any
|
|
695
778
|
* host but must be written explicitly. */
|
|
696
779
|
allowedHosts: string[];
|