@owncast/plugin-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/bin/owncast-plugin.js +345 -0
- package/index.d.ts +459 -0
- package/index.js +443 -0
- package/package.json +47 -0
- package/scripts/postinstall.js +159 -0
package/index.d.ts
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
/** Built-in chat message payload. */
|
|
2
|
+
export interface ChatMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
user: string;
|
|
5
|
+
body: string;
|
|
6
|
+
timestamp: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** A chat user — payload of join/part/rename events. */
|
|
10
|
+
export interface ChatUser {
|
|
11
|
+
id: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
isBot?: boolean;
|
|
14
|
+
isAuthenticated?: boolean;
|
|
15
|
+
scopes?: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Payload of `chat.user.renamed` — the same user changing their name. */
|
|
19
|
+
export interface ChatUserRename {
|
|
20
|
+
user: ChatUser;
|
|
21
|
+
previousName: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Payload of `chat.message.moderated` — a message hidden/restored by a mod. */
|
|
25
|
+
export interface ChatMessageModeration {
|
|
26
|
+
messageId: string;
|
|
27
|
+
visible: boolean;
|
|
28
|
+
moderator?: ChatUser;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Stream-lifecycle payloads. */
|
|
32
|
+
export interface StreamLifecycleEvent {
|
|
33
|
+
startedAt?: string; // ISO-8601, set for stream.started
|
|
34
|
+
stoppedAt?: string; // ISO-8601, set for stream.stopped
|
|
35
|
+
title?: string;
|
|
36
|
+
summary?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface StreamTitleChange {
|
|
40
|
+
from: string;
|
|
41
|
+
to: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** What owncast.stream.current() returns. */
|
|
45
|
+
export interface StreamInfo {
|
|
46
|
+
online: boolean;
|
|
47
|
+
title?: string;
|
|
48
|
+
summary?: string;
|
|
49
|
+
viewers: number;
|
|
50
|
+
startedAt?: string;
|
|
51
|
+
latencyLevel?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** What owncast.server.info() returns. */
|
|
55
|
+
export interface ServerInfo {
|
|
56
|
+
name?: string;
|
|
57
|
+
url?: string;
|
|
58
|
+
summary?: string;
|
|
59
|
+
welcomeMessage?: string;
|
|
60
|
+
version?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** What owncast.stream.broadcaster() returns. Empty when offline. */
|
|
64
|
+
export interface StreamBroadcaster {
|
|
65
|
+
remoteAddr?: string;
|
|
66
|
+
codecs?: string[];
|
|
67
|
+
resolution?: string;
|
|
68
|
+
framerate?: number;
|
|
69
|
+
bitrates?: number[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One configured output rendition, part of VideoConfig (owncast.videoConfig). */
|
|
73
|
+
export interface StreamVariant {
|
|
74
|
+
width: number;
|
|
75
|
+
height: number;
|
|
76
|
+
framerate: number;
|
|
77
|
+
videoBitrate: number;
|
|
78
|
+
audioBitrate: number;
|
|
79
|
+
isPassthrough: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The current video/transcoding config returned by owncast.videoConfig.read(). */
|
|
83
|
+
export interface VideoConfig {
|
|
84
|
+
latencyLevel: number;
|
|
85
|
+
codec: string;
|
|
86
|
+
variants: StreamVariant[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Partial video config passed to owncast.videoConfig.write(). Omitted fields
|
|
90
|
+
* are left unchanged. */
|
|
91
|
+
export interface VideoConfigUpdate {
|
|
92
|
+
latencyLevel?: number;
|
|
93
|
+
codec?: string;
|
|
94
|
+
variants?: StreamVariant[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const FilterAction: {
|
|
98
|
+
readonly Pass: "pass";
|
|
99
|
+
readonly Modify: "modify";
|
|
100
|
+
readonly Drop: "drop";
|
|
101
|
+
};
|
|
102
|
+
export type FilterAction = (typeof FilterAction)[keyof typeof FilterAction];
|
|
103
|
+
|
|
104
|
+
export type FilterResult =
|
|
105
|
+
| { action: typeof FilterAction.Pass }
|
|
106
|
+
| { action: typeof FilterAction.Modify; payload: any }
|
|
107
|
+
| { action: typeof FilterAction.Drop; reason?: string };
|
|
108
|
+
|
|
109
|
+
export const Events: {
|
|
110
|
+
readonly ChatMessageReceived: "chat.message.received";
|
|
111
|
+
readonly ChatUserJoined: "chat.user.joined";
|
|
112
|
+
readonly ChatUserParted: "chat.user.parted";
|
|
113
|
+
readonly ChatUserRenamed: "chat.user.renamed";
|
|
114
|
+
readonly ChatMessageModerated: "chat.message.moderated";
|
|
115
|
+
readonly StreamStarted: "stream.started";
|
|
116
|
+
readonly StreamStopped: "stream.stopped";
|
|
117
|
+
readonly StreamTitleChanged: "stream.title.changed";
|
|
118
|
+
readonly FediverseFollow: "fediverse.follow";
|
|
119
|
+
readonly FediverseLike: "fediverse.like";
|
|
120
|
+
readonly FediverseRepost: "fediverse.repost";
|
|
121
|
+
readonly FediverseMention: "fediverse.mention";
|
|
122
|
+
readonly FediverseReply: "fediverse.reply";
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/** Payload shape for fediverse engagement events. */
|
|
126
|
+
export interface FediverseActor {
|
|
127
|
+
name: string;
|
|
128
|
+
handle: string; // e.g. "@alice@fediverse.example"
|
|
129
|
+
url?: string;
|
|
130
|
+
image?: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface FediverseEngagement {
|
|
134
|
+
actor: FediverseActor;
|
|
135
|
+
/** For likes and reposts: the target object URL. Not set for follows. */
|
|
136
|
+
target?: { url: string };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Inbound fediverse post — a mention or reply that contains content the
|
|
140
|
+
* plugin can act on. Carries both the rendered content (which has the
|
|
141
|
+
* source instance's HTML) and a plain-text version (HTML stripped). */
|
|
142
|
+
export interface FediverseInboundPost {
|
|
143
|
+
actor: FediverseActor;
|
|
144
|
+
content: string; // HTML from the source instance
|
|
145
|
+
contentText: string; // HTML stripped to plain text
|
|
146
|
+
url: string; // permalink to the post on its source
|
|
147
|
+
postedAt: string; // ISO-8601
|
|
148
|
+
inReplyTo?: string; // parent post URL, when this is a reply
|
|
149
|
+
attachments?: {
|
|
150
|
+
url: string;
|
|
151
|
+
mediaType: string;
|
|
152
|
+
alt?: string;
|
|
153
|
+
}[];
|
|
154
|
+
language?: string;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const Permissions: {
|
|
158
|
+
readonly ChatSend: "chat.send";
|
|
159
|
+
readonly ChatHistory: "chat.history";
|
|
160
|
+
readonly ChatModerate: "chat.moderate";
|
|
161
|
+
readonly StorageKV: "storage.kv";
|
|
162
|
+
readonly StorageUpload: "storage.upload";
|
|
163
|
+
readonly EventsEmit: "events.emit";
|
|
164
|
+
readonly NetworkFetch: "network.fetch";
|
|
165
|
+
readonly HttpServe: "http.serve";
|
|
166
|
+
readonly ServerRead: "server.read";
|
|
167
|
+
readonly NotificationsSend: "notifications.send";
|
|
168
|
+
readonly UsersRead: "users.read";
|
|
169
|
+
readonly UsersModerate: "users.moderate";
|
|
170
|
+
readonly FediversePost: "fediverse.post";
|
|
171
|
+
readonly HttpSSE: "http.sse";
|
|
172
|
+
readonly VideoConfigRead: "videoconfig.read";
|
|
173
|
+
readonly VideoConfigWrite: "videoconfig.write";
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export interface BrowserPushPayload {
|
|
177
|
+
title: string;
|
|
178
|
+
body?: string;
|
|
179
|
+
url?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface FediversePayload {
|
|
183
|
+
type: "follow" | "like" | "repost" | string;
|
|
184
|
+
body: string;
|
|
185
|
+
image?: string;
|
|
186
|
+
link?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface SocialHandle {
|
|
190
|
+
platform: string;
|
|
191
|
+
url: string;
|
|
192
|
+
icon?: string;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface FederationInfo {
|
|
196
|
+
enabled: boolean;
|
|
197
|
+
username?: string;
|
|
198
|
+
isPrivate?: boolean;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A user record from owncast.users.list() / .get(). */
|
|
202
|
+
export interface User {
|
|
203
|
+
id: string;
|
|
204
|
+
displayName: string;
|
|
205
|
+
previousNames?: string[];
|
|
206
|
+
createdAt?: string;
|
|
207
|
+
disabledAt?: string; // ISO-8601 if banned, omitted otherwise
|
|
208
|
+
scopes?: string[];
|
|
209
|
+
isBot?: boolean;
|
|
210
|
+
isAuthenticated?: boolean;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A connected chat client from owncast.chat.clients(). */
|
|
214
|
+
export interface ChatClient {
|
|
215
|
+
id: number;
|
|
216
|
+
userId?: string;
|
|
217
|
+
displayName?: string;
|
|
218
|
+
connectedAt?: string;
|
|
219
|
+
userAgent?: string;
|
|
220
|
+
ipAddress?: string;
|
|
221
|
+
messageCount: number;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Result of owncast.storage.upload(). */
|
|
225
|
+
export interface UploadResult {
|
|
226
|
+
url: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export const filter: {
|
|
230
|
+
pass(): FilterResult;
|
|
231
|
+
modify(payload: any): FilterResult;
|
|
232
|
+
drop(reason?: string): FilterResult;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/** Incoming HTTP request, paths are relative to the plugin's namespace
|
|
236
|
+
* (i.e. the leading /plugins/<name>/ has been stripped). */
|
|
237
|
+
export interface IncomingHttpRequest {
|
|
238
|
+
method: string;
|
|
239
|
+
path: string;
|
|
240
|
+
query: Record<string, string>;
|
|
241
|
+
headers: Record<string, string>;
|
|
242
|
+
body: string;
|
|
243
|
+
remoteAddr: string;
|
|
244
|
+
/** True when the request came with any form of Owncast auth (admin OR user). */
|
|
245
|
+
authenticated: boolean;
|
|
246
|
+
/** Identity of the user that made the request, when it came with a
|
|
247
|
+
* user-token. Undefined for anonymous or admin-only requests. */
|
|
248
|
+
user?: ChatUser;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface OutgoingHttpResponse {
|
|
252
|
+
status?: number;
|
|
253
|
+
headers?: Record<string, string>;
|
|
254
|
+
body?: string;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export interface PluginDef {
|
|
258
|
+
/** Notification handler for chat messages. Fire-and-forget. */
|
|
259
|
+
onChatMessage?(msg: ChatMessage): void | Promise<void>;
|
|
260
|
+
|
|
261
|
+
/** Filter handler for chat messages. Return filter.pass() / .modify() / .drop().
|
|
262
|
+
* Errors are treated as filter.pass() (fail-open). */
|
|
263
|
+
filterChatMessage?(msg: ChatMessage): FilterResult;
|
|
264
|
+
|
|
265
|
+
/** User connected to chat. */
|
|
266
|
+
onChatUserJoined?(user: ChatUser): void | Promise<void>;
|
|
267
|
+
/** User disconnected from chat. */
|
|
268
|
+
onChatUserParted?(user: ChatUser): void | Promise<void>;
|
|
269
|
+
/** User changed their display name. */
|
|
270
|
+
onChatUserRenamed?(change: ChatUserRename): void | Promise<void>;
|
|
271
|
+
/** A chat message was hidden or restored by a moderator. */
|
|
272
|
+
onMessageModerated?(event: ChatMessageModeration): void | Promise<void>;
|
|
273
|
+
|
|
274
|
+
/** Stream went live. */
|
|
275
|
+
onStreamStarted?(info: StreamLifecycleEvent): void | Promise<void>;
|
|
276
|
+
/** Stream stopped. */
|
|
277
|
+
onStreamStopped?(info: StreamLifecycleEvent): void | Promise<void>;
|
|
278
|
+
/** Stream title was updated. */
|
|
279
|
+
onStreamTitleChanged?(change: StreamTitleChange): void | Promise<void>;
|
|
280
|
+
|
|
281
|
+
/** Someone on the fediverse followed the streamer's account. */
|
|
282
|
+
onFediverseFollow?(event: FediverseEngagement): void | Promise<void>;
|
|
283
|
+
/** Someone on the fediverse liked a streamer post / federated stream announcement. */
|
|
284
|
+
onFediverseLike?(event: FediverseEngagement): void | Promise<void>;
|
|
285
|
+
/** Someone on the fediverse boosted (reposted) a streamer post. */
|
|
286
|
+
onFediverseRepost?(event: FediverseEngagement): void | Promise<void>;
|
|
287
|
+
/** Someone @-mentioned the streamer in a public post. */
|
|
288
|
+
onFediverseMention?(post: FediverseInboundPost): void | Promise<void>;
|
|
289
|
+
/** Someone replied to one of the streamer's federated posts. */
|
|
290
|
+
onFediverseReply?(post: FediverseInboundPost): void | Promise<void>;
|
|
291
|
+
|
|
292
|
+
/** HTTP request handler. Called for any path under /plugins/<name>/ that
|
|
293
|
+
* isn't served as a static asset. Default-public — gate admin features
|
|
294
|
+
* on `req.authenticated` yourself. Requires `http.serve` permission. */
|
|
295
|
+
onHttpRequest?(req: IncomingHttpRequest): OutgoingHttpResponse;
|
|
296
|
+
|
|
297
|
+
/** Handlers for plugin-emitted custom events. The key is the event type
|
|
298
|
+
* string (e.g. "announcement.broadcast"). Notifications only — to filter
|
|
299
|
+
* custom events, additional API will be needed. */
|
|
300
|
+
on?: { [eventType: string]: (payload: any) => void | Promise<void> };
|
|
301
|
+
|
|
302
|
+
/** Filter chain priority (lower = earlier). Applies to every filter*
|
|
303
|
+
* handler this plugin defines. Default 100. */
|
|
304
|
+
filterPriority?: number;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function definePlugin(def: PluginDef): PluginDef;
|
|
308
|
+
|
|
309
|
+
/** Typed wrappers around the Owncast host. Each method throws if the
|
|
310
|
+
* corresponding permission was not declared in plugin.manifest.json. */
|
|
311
|
+
export const owncast: {
|
|
312
|
+
chat: {
|
|
313
|
+
/** Post as the plugin's own chat bot (display name = the plugin's name). */
|
|
314
|
+
send(text: string): void;
|
|
315
|
+
/** Same identity, but in action style (italic, like IRC "/me"). */
|
|
316
|
+
sendAction(text: string): void;
|
|
317
|
+
/** Post a system message — no user identity, rendered as a server
|
|
318
|
+
* announcement. The body is rendered as HTML, so the plugin is
|
|
319
|
+
* responsible for escaping any untrusted content. Same `chat.send`
|
|
320
|
+
* permission as the other send variants. */
|
|
321
|
+
system(body: string): void;
|
|
322
|
+
/** Private message to one chat client. */
|
|
323
|
+
sendTo(clientId: number | bigint, text: string): void;
|
|
324
|
+
/** Recent chat history (most recent last). Requires `chat.history`.
|
|
325
|
+
* Default limit is 50; pass a smaller number to get fewer. */
|
|
326
|
+
history(limit?: number): ChatMessage[];
|
|
327
|
+
/** Hide a chat message by ID. Requires `chat.moderate`. */
|
|
328
|
+
deleteMessage(messageId: string): void;
|
|
329
|
+
/** Disconnect a chat client by its numeric ID. Requires `chat.moderate`. */
|
|
330
|
+
kick(clientId: number | bigint): void;
|
|
331
|
+
/** List currently-connected chat clients. Requires `chat.history`. */
|
|
332
|
+
clients(): ChatClient[];
|
|
333
|
+
};
|
|
334
|
+
/** User directory access. */
|
|
335
|
+
users: {
|
|
336
|
+
/** List all users (active + disabled). Requires `users.read`. */
|
|
337
|
+
list(): User[];
|
|
338
|
+
/** Fetch one user by ID. Requires `users.read`. */
|
|
339
|
+
get(id: string): User | null;
|
|
340
|
+
/** Enable/disable a user; reason is optional. Requires `users.moderate`. */
|
|
341
|
+
setEnabled(id: string, enabled: boolean, reason?: string): void;
|
|
342
|
+
/** Ban an IP address. Requires `users.moderate`. */
|
|
343
|
+
banIP(ip: string): void;
|
|
344
|
+
};
|
|
345
|
+
/** Upload bytes to Owncast's storage backend (local or S3); returns a
|
|
346
|
+
* public URL. Requires `storage.upload`. */
|
|
347
|
+
storage: {
|
|
348
|
+
upload(name: string, data: Uint8Array | string): UploadResult | null;
|
|
349
|
+
};
|
|
350
|
+
/** Post to the fediverse on the streamer's behalf. Requires `fediverse.post`,
|
|
351
|
+
* which is high-trust — admins should grant it sparingly. The host
|
|
352
|
+
* rate-limits at ~5 posts/hour per plugin. */
|
|
353
|
+
fediverse: {
|
|
354
|
+
/** Publish a public, text-only post. Returns { url } on success or null
|
|
355
|
+
* on rate-limit / disabled / other failure. */
|
|
356
|
+
post(text: string): { url: string } | null;
|
|
357
|
+
};
|
|
358
|
+
/** Send notifications via Owncast's configured channels.
|
|
359
|
+
* Requires `notifications.send`. */
|
|
360
|
+
notifications: {
|
|
361
|
+
/** Post via the Owncast-configured Discord webhook. */
|
|
362
|
+
discord(text: string): void;
|
|
363
|
+
/** Send a browser push notification to subscribed clients. */
|
|
364
|
+
browserPush(payload: string | BrowserPushPayload): void;
|
|
365
|
+
/** Broadcast a fediverse engagement event. */
|
|
366
|
+
fediverse(payload: FediversePayload): void;
|
|
367
|
+
};
|
|
368
|
+
kv: {
|
|
369
|
+
get(key: string): string | null;
|
|
370
|
+
set(key: string, value: string | number): void;
|
|
371
|
+
};
|
|
372
|
+
events: {
|
|
373
|
+
emit(eventType: string, payload: unknown): void;
|
|
374
|
+
};
|
|
375
|
+
sse: {
|
|
376
|
+
/** Push one Server-Sent-Event to every browser connected to this
|
|
377
|
+
* plugin's `/plugins/<name>/_sse/<channel>` stream. `event` is the SSE
|
|
378
|
+
* event name (`""` → the default "message" event); `data` is sent as-is
|
|
379
|
+
* if a string, otherwise JSON-stringified. Fire-and-forget; frames to a
|
|
380
|
+
* slow client are dropped rather than blocking the plugin. Requires the
|
|
381
|
+
* `http.sse` permission. */
|
|
382
|
+
send(channel: string, event: string, data: unknown): void;
|
|
383
|
+
};
|
|
384
|
+
http: {
|
|
385
|
+
fetch(url: string, opts?: HttpRequestOpts): HttpResponse;
|
|
386
|
+
};
|
|
387
|
+
/** Read live stream state + read-only broadcast telemetry. Requires `server.read`. */
|
|
388
|
+
stream: {
|
|
389
|
+
current(): StreamInfo;
|
|
390
|
+
broadcaster(): StreamBroadcaster;
|
|
391
|
+
};
|
|
392
|
+
/** Read server config. Requires `server.read` permission. */
|
|
393
|
+
server: {
|
|
394
|
+
info(): ServerInfo;
|
|
395
|
+
socials(): SocialHandle[];
|
|
396
|
+
federation(): FederationInfo;
|
|
397
|
+
tags(): string[];
|
|
398
|
+
};
|
|
399
|
+
/** Read/change video/transcoding configuration. read() requires
|
|
400
|
+
* `videoconfig.read`; write() requires `videoconfig.write`. */
|
|
401
|
+
videoConfig: {
|
|
402
|
+
read(): VideoConfig;
|
|
403
|
+
write(config: VideoConfigUpdate): void;
|
|
404
|
+
};
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export interface HttpRequestOpts {
|
|
408
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD";
|
|
409
|
+
headers?: Record<string, string>;
|
|
410
|
+
body?: string;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export interface HttpResponse {
|
|
414
|
+
status: number;
|
|
415
|
+
headers: Record<string, string>;
|
|
416
|
+
body: string;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** An entry in `manifest.actions` — declares an action button the Owncast
|
|
420
|
+
* UI surfaces while this plugin is enabled. Mirrors Owncast's existing
|
|
421
|
+
* ExternalAction shape; the host merges enabled-plugin buttons with the
|
|
422
|
+
* admin-configured list.
|
|
423
|
+
*
|
|
424
|
+
* Exactly one of `url` or `html` is required.
|
|
425
|
+
*
|
|
426
|
+
* URL ergonomics: if `url` starts with `/` but not `/plugins/`, the host
|
|
427
|
+
* rewrites it to `/plugins/<your-plugin-name>/<path>` at load time, so
|
|
428
|
+
* `"url": "/"` becomes `"/plugins/my-plugin/"`. Absolute http(s) URLs and
|
|
429
|
+
* explicit `/plugins/<your-name>/...` paths are accepted unchanged.
|
|
430
|
+
*
|
|
431
|
+
* When the resolved URL points back into this plugin, the manifest must
|
|
432
|
+
* declare `http.serve` — the host rejects the load otherwise. */
|
|
433
|
+
export interface ActionButton {
|
|
434
|
+
/** Button label. Required. */
|
|
435
|
+
title: string;
|
|
436
|
+
/** Load this URL when the button is pressed. Mutually exclusive with `html`. */
|
|
437
|
+
url?: string;
|
|
438
|
+
/** Render this raw HTML when the button is pressed. Mutually exclusive with `url`. */
|
|
439
|
+
html?: string;
|
|
440
|
+
/** Icon image URL — same path conventions as `url`. */
|
|
441
|
+
icon?: string;
|
|
442
|
+
/** Accent color, e.g. "#3b82f6". */
|
|
443
|
+
color?: string;
|
|
444
|
+
/** Tooltip / longer description. */
|
|
445
|
+
description?: string;
|
|
446
|
+
/** When true, open in a new tab instead of an in-page modal. */
|
|
447
|
+
openExternally?: boolean;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** `manifest.network` — narrows outbound HTTP scope for plugins that
|
|
451
|
+
* declare the `network.fetch` permission. Required when that permission
|
|
452
|
+
* is granted; the host rejects loads otherwise. */
|
|
453
|
+
export interface NetworkConfig {
|
|
454
|
+
/** Hostname globs the plugin can reach via `owncast.http.fetch`.
|
|
455
|
+
* Bare names match exactly (`"api.discord.com"`); `*` is a wildcard
|
|
456
|
+
* segment (`"*.weather.com"`). The bare wildcard `"*"` matches any
|
|
457
|
+
* host but must be written explicitly. */
|
|
458
|
+
allowedHosts: string[];
|
|
459
|
+
}
|