@lumiastream/lumia-types 3.9.1 → 3.9.3

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.
@@ -685,7 +685,7 @@ exports.LumiaAlertFriendlyValues = {
685
685
  [LumiaAlertValues.YOUTUBE_SUBSCRIBER]: 'Youtube Subscriber',
686
686
  [LumiaAlertValues.YOUTUBE_SUPERCHAT]: 'Youtube Superchat',
687
687
  [LumiaAlertValues.YOUTUBE_SUPERSTICKER]: 'Youtube Supersticker',
688
- [LumiaAlertValues.YOUTUBE_GIFTS]: 'Youtube Gifts',
688
+ [LumiaAlertValues.YOUTUBE_GIFTS]: 'Youtube Gifts (Jewels)',
689
689
  [LumiaAlertValues.YOUTUBE_SESSION_GIFTS]: 'Youtube Session Gifts',
690
690
  [LumiaAlertValues.YOUTUBE_ENTRANCE]: 'Youtube Entrance',
691
691
  [LumiaAlertValues.YOUTUBE_LIKE]: 'Youtube Like',
@@ -95,6 +95,242 @@ async function() {
95
95
  }
96
96
  ```
97
97
 
98
+ ### Engagement & system `lumia` actions
99
+
100
+ These `lumia` actions drive Lumia's engagement features (raffles, tournaments, viewer queue, song requests) and system controls. None of them has a dedicated helper, so call them through `actions()`. The action shape is `{ base: "lumia", type: "<type>", value: { ... } }` — the fields in the tables below go **inside the inner `value` object**. Text fields accept template tokens like `{{username}}`.
101
+
102
+ #### Enable / disable commands, alerts, folders & automations
103
+
104
+ `on: true` enables, `on: false` disables.
105
+
106
+ | type | `value` | targets |
107
+ | --- | --- | --- |
108
+ | `setCommand` | `{ value: "<command name>", on: true }` | a chat command |
109
+ | `setChatbotCommand` | `{ value: "<command name>", on: true }` | a chatbot command |
110
+ | `setTwitchPointsCommand` | `{ value: "<command name>", on: true }` | a Twitch channel-points reward command |
111
+ | `setTwitchExtensionCommand` | `{ value: "<command name>", on: true }` | a Twitch extension command |
112
+ | `setKickPointsCommand` | `{ value: "<command name>", on: true }` | a Kick points command |
113
+ | `setChatMatchCommand` | `{ value: "<chat-match id>", on: true }` | a chat-match command (by id) |
114
+ | `setAlert` | `{ value: "<alert key>", on: true }` | an alert (its `pathKey`) |
115
+ | `setAlertVariation` | `{ value: "<alert key>", variation: "<variation id>", on: true }` | one variation of an alert |
116
+ | `setFolder` | `{ value: "<folder id>", on: true }` | a command/alert folder |
117
+ | `setAutomation` | `{ value: "<automation name>", on: true }` | an automation/timer (matched by name) |
118
+ | `setVoicecommands` | `{ on: true }` | the voice-commands input as a whole |
119
+ | `setFuzeAudioSensitivity` | `{ value: 50 }` | Fuze audio sensitivity (number) |
120
+
121
+ #### Variables, local storage & files
122
+
123
+ | type | `value` | notes |
124
+ | --- | --- | --- |
125
+ | `appendToVariable` | `{ value: "<variable name>", message: "<item>", unique: true }` | append an item to a list variable; `unique` skips it if already present |
126
+ | `unappendFromVariable` | `{ value: "<variable name>", message: "<item>" }` | remove an item from a list variable |
127
+ | `saveLocal` | `{ value: "<key>", message: "<value>" }` | persist a value (same store as the `{{save_local}}` / `{{load_local}}` chat functions) |
128
+ | `writeToFile` | `{ value: "<file path>", message: "<content>", on: true }` | write text to a file; `on: true` appends instead of overwriting |
129
+
130
+ > For plain variables prefer the `setVariable` / `getVariable` / `deleteVariable` helpers in `helper-functions.md`.
131
+
132
+ #### Userlevels & restrictions
133
+
134
+ | type | `value` | notes |
135
+ | --- | --- | --- |
136
+ | `addToUserlevel` | `{ value: "<userlevel id>", message: "<username>" }` | add a user to a userlevel |
137
+ | `removeFromUserlevel` | `{ value: "<userlevel id>", message: "<username>" }` | remove a user from a userlevel |
138
+ | `addToRestrictionsList` | `{ message: "<username>" }` | restrict a user (no `value`) |
139
+ | `removeFromRestrictionsList` | `{ message: "<username>" }` | unrestrict a user |
140
+
141
+ #### Raffles
142
+
143
+ | type | `value` | notes |
144
+ | --- | --- | --- |
145
+ | `raffleStart` | `{ title: "<title>", preset: "<raffle id>", auto_end: true, ends_after: 5 }` | all optional; `preset` seeds from a saved raffle, `ends_after` is in **minutes** (default 30), `auto_end` turns on the timer |
146
+ | `raffleEntry` | `{ value: "<username>" }` | needs a started raffle |
147
+ | `raffleRemoveEntry` | `{ value: "<username>" }` | |
148
+ | `raffleStop` | `{}` | stop accepting entries |
149
+ | `raffleGetWinner` | `{}` | pick a winner (raffle must be stopped first) |
150
+ | `raffleEnd` | `{}` | end the raffle |
151
+
152
+ #### Tournaments
153
+
154
+ | type | `value` | notes |
155
+ | --- | --- | --- |
156
+ | `tournamentStart` | `{ preset: "<tournament id>" }` | start from a preset, or a new tournament if omitted |
157
+ | `tournamentEntry` | `{ message: "<username>", value: "<avatar url>", extra: "<team>", platform: "twitch" }` | `message` is the username; needs a running tournament with signups open |
158
+ | `tournamentRemoveEntry` | `{ message: "<username>", platform: "twitch" }` | |
159
+ | `tournamentUpdatePoints` | `{ message: "<username>", points: "+100" }` | `points` uses the modifier syntax (`+ - * / =`); defaults to `=` (set) when no modifier |
160
+ | `tournamentEnd` | `{}` | |
161
+
162
+ #### Viewer queue
163
+
164
+ | type | `value` | notes |
165
+ | --- | --- | --- |
166
+ | `viewerQueueEntry` | `{ value: "<username>" }` | needs a started queue |
167
+ | `viewerQueueLeave` | `{ value: "<username>" }` | |
168
+ | `viewerQueuePickPlayer` | `{ value: 1, mode: "single" }` | pick N players; `mode` is `single`, `bulkfirst`, or `bulkrandom` (default `single`, 1 player) |
169
+ | `viewerQueuePlayPause` | `{ on: true }` | `true` plays, `false` pauses |
170
+ | `viewerQueueEndQueue` | `{}` | |
171
+
172
+ #### Song requests
173
+
174
+ | type | `value` | notes |
175
+ | --- | --- | --- |
176
+ | `addSongRequest` | `{ value: "<search term or url>", requesterUsername: "{{username}}", skipApproval: true }` | `skipApproval` (or `forceApprove`) bypasses the approval queue |
177
+ | `skipSongRequest` | `{}` | |
178
+ | `clearSongRequestQueue` | `{}` | |
179
+
180
+ #### Queue, cooldowns & session (no `value` needed)
181
+
182
+ | type | what it does |
183
+ | --- | --- |
184
+ | `pauseQueue` / `resumeQueue` | pause / resume the action queue |
185
+ | `removeCurrentQueueItem` | drop the item currently running |
186
+ | `runLastQueueItem` | re-run the last completed queue item |
187
+ | `clearQueue` | empty the queue |
188
+ | `clearCooldowns` | clear all command cooldowns |
189
+ | `resetSession` | reset session variables (session counts) |
190
+ | `cleanAll` | clear queue + cooldowns and send lights to default |
191
+ | `backToDefault` | send lights back to their default |
192
+ | `refreshSettings` | hard-refresh Lumia's settings |
193
+ | `replayLastEventListEvent` | replay the most recent event-list event |
194
+
195
+ Call these with no inner value, e.g. `await actions([{ base: "lumia", type: "clearQueue" }]);`
196
+
197
+ #### Other command calls & webhooks
198
+
199
+ | type | `value` | notes |
200
+ | --- | --- | --- |
201
+ | `callRandomCommand` | `{ options: ["cmdA", "cmdB", "cmdC"], addToEndOfQueue: false }` | run one of the listed commands at random |
202
+ | `toggleStreamMode` | `{}` | flip stream mode on/off (use `setStreamMode { on }` to set it explicitly) |
203
+ | `sendToWebhook` | `{ value: "<url>", message: "<json string>" }` | `message` must be valid JSON — it is parsed and POSTed as the request body |
204
+ | `sendToDiscordWebhook` | `{ value: "<discord webhook url>", message: "<text>" }` | posts text to a Discord webhook |
205
+ | `sendToDiscordWithMediaWebhook` | `{ value: "<discord webhook url>", message: "<text>", file: "<local file path>" }` | posts text + an uploaded file |
206
+ | `sendToPrinter` | `{ value: "<IP:port or usb://vendor:product>", type: "image", message: "<image path>" }` | `type` is `image` (default) or `text`; for `text`, `message` is the text to print |
207
+
208
+ ```js
209
+ async function() {
210
+ // Start a 5-minute raffle, then enter the running viewer
211
+ await actions([
212
+ { base: "lumia", type: "raffleStart", value: { title: "Giveaway", auto_end: true, ends_after: 5 } },
213
+ { base: "lumia", type: "raffleEntry", value: { value: "{{username}}" } },
214
+ ]);
215
+
216
+ // Disable a command and clear the queue
217
+ await actions([
218
+ { base: "lumia", type: "setCommand", value: { value: "!hug", on: false } },
219
+ { base: "lumia", type: "clearQueue" },
220
+ ]);
221
+ done();
222
+ }
223
+ ```
224
+
225
+ ### Points, loyalty & currency actions
226
+
227
+ Several `lumia` actions change a point value, and they all share the same **modifier** syntax on the amount you pass in:
228
+
229
+ | You pass | Result |
230
+ | --- | --- |
231
+ | `+100` | add 100 |
232
+ | `-100` | subtract 100 |
233
+ | `*2` | multiply by 2 |
234
+ | `/2` | divide by 2 |
235
+ | `=300` | set the exact value to 300 |
236
+
237
+ A bare number with **no** modifier falls back to each action's own default (noted per action below). The same `+ - * / =` modifiers also work on the `updateCounter` and `updateVariable` actions.
238
+
239
+ #### `setUserLoyaltyPoint` — change a viewer's loyalty balance
240
+
241
+ | Field | Meaning |
242
+ | --- | --- |
243
+ | `value.value` | the amount (with optional modifier) |
244
+ | `value.message` | the username to target — defaults to the command's own `{{username}}` |
245
+ | `value.platform` | optional; defaults to the event's platform, then `twitch` |
246
+
247
+ **Default modifier: `+`** — a bare number *adds* points.
248
+
249
+ ```js
250
+ async function() {
251
+ // Give the viewer who triggered this 100 points
252
+ await actions([{ base: "lumia", type: "setUserLoyaltyPoint", value: { value: "100", message: "{{username}}" } }]);
253
+
254
+ // Take 50 points from a specific user on YouTube
255
+ await actions([{ base: "lumia", type: "setUserLoyaltyPoint", value: { value: "-50", message: "someviewer", platform: "youtube" } }]);
256
+
257
+ // Set an exact balance of 500
258
+ await actions([{ base: "lumia", type: "setUserLoyaltyPoint", value: { value: "=500", message: "{{username}}" } }]);
259
+ done();
260
+ }
261
+ ```
262
+
263
+ #### `setLoyaltyPointValue` — change the loyalty-points **cost** of one of your commands
264
+
265
+ | Field | Meaning |
266
+ | --- | --- |
267
+ | `value.value` | the command name whose cost you are changing |
268
+ | `value.points` | the new cost (with optional modifier) |
269
+
270
+ **Default modifier: `=`** — a bare number *sets* the cost.
271
+
272
+ ```js
273
+ async function() {
274
+ await actions([{ base: "lumia", type: "setLoyaltyPointValue", value: { value: "myredeem", points: "250" } }]); // cost becomes 250
275
+ await actions([{ base: "lumia", type: "setLoyaltyPointValue", value: { value: "myredeem", points: "+50" } }]); // cost goes up by 50
276
+ done();
277
+ }
278
+ ```
279
+
280
+ #### `setTwitchPointValue` / `setTwitchExtensionBitsValue` — change a Twitch reward's cost
281
+
282
+ Both take the same `{ value: "<commandName>", points: "<amount>" }` shape as `setLoyaltyPointValue` and also **default to `=`**. `setTwitchPointValue` updates a channel-points reward's cost; `setTwitchExtensionBitsValue` updates a Twitch-extension command's Bits cost.
283
+
284
+ ```js
285
+ async function() {
286
+ await actions([{ base: "lumia", type: "setTwitchPointValue", value: { value: "myreward", points: "1000" } }]);
287
+ await actions([{ base: "lumia", type: "setTwitchExtensionBitsValue", value: { value: "myextcommand", points: "100" } }]);
288
+ done();
289
+ }
290
+ ```
291
+
292
+ ### Overlay actions without a helper
293
+
294
+ These `base: "overlay"` actions don't have a dedicated helper yet. Their inner `value` targets a layer with `layer` (add `overlay: "<overlay name>"` to disambiguate which overlay the layer belongs to). `content` fields accept template tokens.
295
+
296
+ #### Spin wheel
297
+
298
+ | type | `value` | notes |
299
+ | --- | --- | --- |
300
+ | `spinwheelAddItem` | `{ layer: "<layer>", content: "<item title>", image: "<image url>" }` | adds an item (max 60); `image` is optional |
301
+ | `spinwheelRemoveItem` | `{ layer: "<layer>", content: "<item title>" }` | removes a matching item |
302
+ | `spinwheelReset` | `{ layer: "<layer>", removeItems: true }` | `removeItems: true` wipes all items; otherwise it just resets the wheel |
303
+
304
+ #### Polls / trigger games
305
+
306
+ | type | `value` | notes |
307
+ | --- | --- | --- |
308
+ | `pollStart` | `{ layer: "<layer>" }` | start the poll |
309
+ | `pollTrigger` | `{ layer: "<layer>" }` | trigger using the caller's username/message |
310
+ | `pollAddItem` | `{ layer: "<layer>", content: "<option title>", trigger: "<keyword>" }` | `trigger` defaults to the next option number |
311
+ | `pollRemoveItem` | `{ layer: "<layer>", content: "<option title>" }` | |
312
+ | `pollResetVotes` | `{ layer: "<layer>" }` | |
313
+ | `pollSetTimer` | `{ layer: "<layer>", content: "30" }` | `content` is the timer duration in seconds |
314
+
315
+ #### Game overlays, content & screenshots
316
+
317
+ | type | `value` | notes |
318
+ | --- | --- | --- |
319
+ | `sendGameTrigger` | `{ layer: "<layer>", content: "<value>" }` | targets the player from the caller's context |
320
+ | `sendGameUpdate` | `{ layer: "<layer>", content: "<value>" }` | |
321
+ | `setContent` | `{ layer: "<layer>", content: "<text>" }` | same as the `overlaySetTextContent` helper |
322
+ | `alertEvent` | `{ value: "<alert id>", variation: "<variation>", duration: 5000 }` | fire an overlay alert event; `duration` in ms |
323
+ | `takeScreenshot` | `{ value: "<overlay id>", format: "png", conversion: "original" }` | `format` is `png` or `jpeg`; `conversion` is `original`, `thermal`, `grayscale`, `invert`, or `binary`; resolves to the saved image path |
324
+
325
+ #### HUD
326
+
327
+ | type | `value` | notes |
328
+ | --- | --- | --- |
329
+ | `hudToggle` | `{}` | toggle the HUD |
330
+ | `hudOverlayChange` | `{ value: "<overlay>" }` | switch the HUD overlay |
331
+ | `hudVolumeSet` | `{ volume: 50 }` | set HUD volume |
332
+ | `hudOpacitySet` | `{ volume: 80 }` | set HUD opacity — note the value goes in the `volume` field |
333
+
98
334
  ### OBS (v5) actions
99
335
 
100
336
  OBS has a dedicated helper, so you do **not** need `actions()` for it: pass any OBS websocket v5 request to `sendRawObsJson({ "request-type": "...", ...params })` (see `helper-functions.md`). The `request-type` and fields are exactly the same ones Lumia's OBS action editor uses. Common ones:
@@ -132,4 +368,325 @@ async function() {
132
368
 
133
369
  Other available request-types include `SetCurrentProfile`, `SetCurrentSceneCollection`, `CreateInput`, `RemoveInput`, `SetSceneItemTransform`, `SetSceneItemBlendMode`, `SetInputAudioMonitorType`, `TriggerHotkeyByName`, `SetStudioModeEnabled`, `SendStreamCaption`, and the `Start/Stop/Toggle` variants for recording, streaming, replay buffer and virtual cam — the same list as the OBS action editor.
134
370
 
371
+ ### Integration actions
372
+
373
+ Every connected integration is also a valid `base` (`spotify`, `discord`, `vtubestudio`, `midi`, `osc`, and the rest listed at the top). An integration action only runs if that integration is **connected and enabled** — otherwise it is silently skipped. The exact `type` and `value` vary per integration; the common ones are below. To find anything not listed, set the action up once in the normal action editor and read off its fields.
374
+
375
+ **Wrapper styles differ per integration** — each subsection states which it uses:
376
+
377
+ - **`value` object** — `{ base, type, value: { ... } }`: Discord, VTube Studio, Twitch, YouTube, Kick, TikTok, Meld, SwitchBot, Wave Link, Camera Hub, StreamFog.
378
+ - **scalar `value`** — `{ base, type, value: "..." }`: Spotify, VLC, YouTube Music, Voicemod, Streamer.bot.
379
+ - **flat fields** — `{ base, type, ...fields }` with no `value` wrapper: MIDI, OSC, OBS, MQTT, WebSocket, Serial, Art-Net, Broadlink (SLOBS is a special case, noted below).
380
+
381
+ Every integration also supports a delay step: `{ base: "<integration>", type: "delay", delay: <ms> }`. String fields generally resolve `{{template}}` tokens (Art-Net is the exception).
382
+
383
+ > **Lights** (Hue, LIFX, Govee, Nanoleaf, WLED, Elgato key lights, etc.) have **no** action `base` — control them with the `sendColor` helper (see `helper-functions.md`), not through `actions()`.
384
+
385
+ #### Spotify (`base: "spotify"`)
386
+
387
+ Spotify actions take a single scalar `value` (not an object). Playback control needs an active Spotify device.
388
+
389
+ | type | `value` | notes |
390
+ | --- | --- | --- |
391
+ | `setPlayState` | `"play"` / `"pause"` / `"toggle"` | |
392
+ | `skipToNext` / `skipToPrevious` | — | no value needed |
393
+ | `setVolume` | `0`–`100` | percent |
394
+ | `seek` | `<milliseconds>` | |
395
+ | `setShuffle` | `"true"` / `"false"` / `"toggle"` | |
396
+ | `setRepeat` | `"track"` / `"context"` / `"off"` | |
397
+ | `searchAndPlay` | `"<query or track url>"` | queues the match then skips to it |
398
+ | `searchAndPlayImmediately` | `"<query or track url>"` | plays it right away |
399
+ | `searchAndAddToQueue` | `"<query or track url>"` | adds to the queue |
400
+ | `searchAndAddToPlaylist` | `"<query or track url>"` + top-level `target: "<playlist id>"` | |
401
+
402
+ ```js
403
+ async function() {
404
+ await actions([{ base: "spotify", type: "setVolume", value: 40 }]);
405
+ await actions([{ base: "spotify", type: "searchAndAddToQueue", value: "never gonna give you up" }]);
406
+ await actions([{ base: "spotify", type: "searchAndAddToPlaylist", value: "yesterday", target: "37i9dQZF1DXcBWIGoYBM5M" }]);
407
+ done();
408
+ }
409
+ ```
410
+
411
+ #### Discord (`base: "discord"`)
412
+
413
+ Discord actions take a `value` object and post through your connected Discord.
414
+
415
+ | type | `value` | notes |
416
+ | --- | --- | --- |
417
+ | `sendMessage` | `{ value: "<message>", target: "<channel id>", file: "<local path>", embeds: "<json string>", on: false }` | the message text is `value.value`; `target` defaults to your primary channel; `on: true` sends it as TTS; `embeds` is a JSON-array string |
418
+ | `addMemberRole` | `{ value: "<username>", target: "<role id>" }` | |
419
+ | `removeMemberRole` | `{ value: "<username>", target: "<role id>" }` | |
420
+ | `kickMember` | `{ value: "<username>" }` | |
421
+
422
+ ```js
423
+ async function() {
424
+ await actions([{ base: "discord", type: "sendMessage", value: { value: "Stream is live! {{twitch_channel_title}}" } }]);
425
+ done();
426
+ }
427
+ ```
428
+
429
+ #### VTube Studio (`base: "vtubestudio"`)
430
+
431
+ VTube Studio actions take a `value` object; the identifier (model / hotkey / expression) goes in the inner `value.value`.
432
+
433
+ | type | `value` | notes |
434
+ | --- | --- | --- |
435
+ | `loadModel` | `{ value: "<model name or id>" }` | |
436
+ | `hotkeyTrigger` | `{ value: "<hotkey id>" }` | |
437
+ | `expressionTrigger` | `{ value: "<expression name>", on: true }` | `on` activates / deactivates |
438
+ | `moveModel` | `{ positionX: "0", positionY: "0.5", rotation: "0", timeInSeconds: 1, valuesAreRelativeToModel: false }` | position/rotation are numbers or numeric strings |
439
+ | `resizeModel` | `{ size: "-22.5", timeInSeconds: 1, valuesAreRelativeToModel: false }` | `size` ranges -100 (smallest) to 100 (biggest); applied through MoveModelRequest |
440
+
441
+ ```js
442
+ async function() {
443
+ await actions([{ base: "vtubestudio", type: "hotkeyTrigger", value: { value: "MyHotkey" } }]);
444
+ done();
445
+ }
446
+ ```
447
+
448
+ #### MIDI (`base: "midi"`) and OSC (`base: "osc"`)
449
+
450
+ MIDI and OSC read their fields **flat on the action object** — there is no `value` wrapper.
451
+
452
+ ```js
453
+ async function() {
454
+ // MIDI: send a note (channel 1-16, note name like "C3", velocity 0-127)
455
+ await actions([{ base: "midi", type: "note-on", port: 0, channel: 1, note: "C3", velocity: 127 }]);
456
+
457
+ // OSC: send a typed message. type is "s" (string), "f" (float), or "i" (int).
458
+ await actions([{ base: "osc", type: "f", name: "/track/1/volume", sendPort: "9000", value: 0.8 }]);
459
+ done();
460
+ }
461
+ ```
462
+
463
+ #### Twitch (`base: "twitch"`)
464
+
465
+ Twitch actions take a `value` object. Most string inputs — usernames, message ids, comma-separated option lists — go in `value.message`. Each action needs the matching Twitch permission to be granted on your connection.
466
+
467
+ Stream & engagement:
468
+
469
+ | type | `value` | notes |
470
+ | --- | --- | --- |
471
+ | `changeStreamTitle` | `{ message: "<title>" }` | |
472
+ | `changeCurrentCategory` | `{ message: "<game/category>" }` | matched by name |
473
+ | `clip` | `{ title: "<title>", duration: 30 }` | `duration` 5–60s; sets `{{twitch_last_clip_url}}` |
474
+ | `createStreamMarker` | `{ message: "<description>" }` | |
475
+ | `runCommercial` | `{ duration: 60 }` | one of 30/60/90/120/150/180 |
476
+ | `changeUserColor` | `{ color: "blue" }` | named color (Prime/Turbo can use hex) |
477
+ | `shoutout` | `{ message: "<username>" }` | runs your shoutout command |
478
+ | `raid` | `{ message: "<channel>" }` | |
479
+ | `sendAnnouncement` | `{ message: "<text>", color: "primary" }` | color: primary/blue/green/orange/purple |
480
+ | `createPoll` | `{ title: "<title>", message: "opt1,opt2", duration: 60 }` | options comma-separated; `duration` seconds |
481
+ | `endPoll` | `{ reason: "ARCHIVED" }` | `ARCHIVED` or `TERMINATED` |
482
+ | `createPrediction` | `{ title: "<title>", message: "outcome1,outcome2", duration: 60 }` | |
483
+ | `endPrediction` | `{ reason: "RESOLVED", message: "<winning outcome>" }` | `RESOLVED`/`CANCELED`/`LOCKED`; `message` is the winning outcome text |
484
+ | `updateRedemptionStatus` | `{ status: "FULFILLED" }` | `FULFILLED` or `CANCELED`; applies to pending redemptions |
485
+
486
+ Chat modes (`on: true` enables):
487
+
488
+ | type | `value` |
489
+ | --- | --- |
490
+ | `setSubscriberMode` / `setEmotesMode` / `setUniqueChatMode` | `{ on: true }` |
491
+ | `setFollowMode` | `{ on: true, duration: 0 }` (`duration` in **minutes**) |
492
+ | `setSlowMode` | `{ on: true, duration: 30 }` (`duration` in **seconds**) |
493
+ | `clearChat` | `{}` |
494
+
495
+ Moderation:
496
+
497
+ | type | `value` | notes |
498
+ | --- | --- | --- |
499
+ | `banUser` | `{ message: "<username>", reason: "" }` | |
500
+ | `unbanUser` | `{ message: "<username>" }` | |
501
+ | `timeoutUser` | `{ message: "<username>", duration: 600, reason: "" }` | `duration` in seconds |
502
+ | `deleteMessage` | `{ message: "<message id>" }` | |
503
+ | `pinMessage` | `{ message: "<text>", duration: 0 }` | sends then pins; `duration` 0 = no expiry, else 30–1800s |
504
+ | `unpinMessage` | `{}` | |
505
+ | `addModerator` / `removeModerator` / `addVip` / `removeVip` | `{ message: "<username>" }` | |
506
+ | `warnUser` | `{ message: "<username>", reason: "" }` | |
507
+ | `blockUser` / `unblockUser` | `{ message: "<username>" }` | |
508
+ | `addBlockedWord` / `removeBlockedWord` | `{ message: "<word>" }` | |
509
+ | `shieldModeOn` / `shieldModeOff` | `{}` | |
510
+ | `setSuspiciousUserStatus` | `{ message: "<username>", status: "ACTIVE_MONITORING" }` | `ACTIVE_MONITORING` or `RESTRICTED` |
511
+ | `removeSuspiciousUserStatus` | `{ message: "<username>" }` | |
512
+
513
+ ```js
514
+ async function() {
515
+ await actions([{ base: "twitch", type: "clip", value: { title: "Clutch!", duration: 30 } }]);
516
+ await actions([{ base: "twitch", type: "sendAnnouncement", value: { message: "Raid incoming!", color: "purple" } }]);
517
+ done();
518
+ }
519
+ ```
520
+
521
+ #### YouTube (`base: "youtube"`)
522
+
523
+ `value` object. Usernames go in `value.message`.
524
+
525
+ | type | `value` | notes |
526
+ | --- | --- | --- |
527
+ | `changeStreamTitle` | `{ title: "<title>", message: "<description>" }` | note: `message` is the **description** |
528
+ | `banUser` | `{ message: "<username>" }` | permanent |
529
+ | `unbanUser` | `{ message: "<username>" }` | |
530
+ | `timeoutUser` | `{ message: "<username>", duration: 300 }` | `duration` in seconds |
531
+ | `deleteMessage` | `{ message: "<message id>" }` | |
532
+ | `shoutout` | `{ message: "<username>" }` | |
533
+
534
+ #### Kick (`base: "kick"`)
535
+
536
+ `value` object. Usernames and the stream title both go in `value.message`. No poll or send-message action (chat goes through the chatbot).
537
+
538
+ | type | `value` | notes |
539
+ | --- | --- | --- |
540
+ | `changeStreamTitle` | `{ message: "<title>" }` | |
541
+ | `changeCurrentCategory` | `{ message: "<category>" }` | matched by name |
542
+ | `shoutout` | `{ message: "<username>" }` | |
543
+ | `banUser` | `{ message: "<username>", reason: "" }` | |
544
+ | `unbanUser` | `{ message: "<username>" }` | |
545
+ | `timeoutUser` | `{ message: "<username>", duration: 600, reason: "" }` | `duration` in seconds |
546
+
547
+ #### TikTok (`base: "tiktok"`)
548
+
549
+ `value` object. TikTok actions are **video upload only** (everything else is inbound events).
550
+
551
+ | type | `value` | notes |
552
+ | --- | --- | --- |
553
+ | `uploadDraftVideo` | `{ media: "<local file path>" }` | uploads to your TikTok inbox to finish/publish manually |
554
+ | `uploadAndPublishVideo` | `{ media: "<local file path>", title: "<title>", privacy_level: "PUBLIC_TO_EVERYONE", disable_comment: false, disable_duet: false, disable_stitch: false }` | `privacy_level`: `PUBLIC_TO_EVERYONE` / `MUTUAL_FOLLOW_FRIENDS` / `SELF_ONLY` |
555
+
556
+ #### OBS (`base: "obs"`)
557
+
558
+ For OBS, prefer the **`sendRawObsJson`** helper documented above — the `actions()` OBS path uses the same OBS-websocket-v5 `request-type` values and fields. Reach for `base: "obs"` only to batch OBS in one ordered `actions([...])` array with other bases, or to use Lumia's convenience source types.
559
+
560
+ OBS fields are **flat** on the action object (the type key is `request-type`, hyphenated):
561
+
562
+ ```js
563
+ async function() {
564
+ await actions([
565
+ { base: "obs", "request-type": "SetCurrentProgramScene", sceneName: "Starting Soon" },
566
+ { base: "obs", "request-type": "SetInputMute", inputName: "Mic/Aux", inputMuted: true },
567
+ ]);
568
+ done();
569
+ }
570
+ ```
571
+
572
+ Convenience source types (Lumia builds the `inputSettings` for you): `SetSourceText` (`{ inputName, text }`), `SetGenericUrlSource` / `SetSourceUrl` (`{ inputName, url }`), `SetGenericFileSource` / `SetImageFileSource` / `SetMediaFileSource` (`{ inputName, file }`). All other request-types match the OBS v5 API exactly.
573
+
574
+ #### Streamlabs Desktop (`base: "slobs"`)
575
+
576
+ SLOBS uses JSON-RPC, so an action carries both a `type` (for delay routing) and an RPC `method` + `params`. Only a few types are exposed:
577
+
578
+ | type | shape | notes |
579
+ | --- | --- | --- |
580
+ | `SetCurrentScene` | `{ base: "slobs", type: "SetCurrentScene", method: "makeSceneActive", params: { resource: "ScenesService", args: ["<sceneId>"] } }` | switch scene |
581
+ | `SetCurrentSceneCollection` | `{ ..., type: "SetCurrentSceneCollection", method: "load", params: { resource: "SceneCollectionsService", args: ["<collectionId>"] } }` | |
582
+ | `SetSourceRender` | `{ ..., type: "SetSourceRender", method: "setVisibility", params: { resource: "<sceneItemId>", args: [true] } }` | scene-item visibility |
583
+
584
+ #### Meld Studio (`base: "meld"`)
585
+
586
+ `value` object; scene/layer/track/effect targets accept the friendly name or id.
587
+
588
+ | type | `value` | notes |
589
+ | --- | --- | --- |
590
+ | `SetScene` | `{ value: "<scene>" }` | show a scene |
591
+ | `SetStagedScene` | `{ value: "<scene>" }` | set the staged (preview) scene |
592
+ | `ShowStagedScene` | `{}` | promote staged to live |
593
+ | `SetLayerVisibility` | `{ value: "<layer>", on: true }` | |
594
+ | `SetLayerEffect` | `{ parent: "<layer>", value: "<effect>", on: true }` | |
595
+ | `SetAudioTrackMute` | `{ value: "<track>", on: true }` | `on: true` = muted |
596
+ | `StartRecord` / `StopRecord` / `StartStream` / `StopStream` | `{}` | |
597
+ | `Clip` / `Screenshot` | `{}` | |
598
+ | `SendCommand` | `{ value: "meld.<command>" }` | raw command, e.g. `meld.toggleStreamingAction` |
599
+
600
+ #### VLC (`base: "vlc"`)
601
+
602
+ Scalar `value`.
603
+
604
+ | type | `value` | notes |
605
+ | --- | --- | --- |
606
+ | `setPlayState` | `"play"` / `"pause"` / `"toggle"` | |
607
+ | `playlistNext` / `playlistPrevious` | — | |
608
+ | `setVolume` | `0`–`125` | percent |
609
+ | `seek` | `"+30"` / `"-30"` / `"50%"` / `"120"` | VLC seek syntax |
610
+ | `setShuffle` / `setRepeat` / `setLoop` | `"true"` / `"false"` / `"toggle"` | |
611
+ | `addToQueueAndPlay` | `"<uri or path>"` | |
612
+ | `addToQueue` | `"<uri or path>"` | |
613
+ | `playlistEmpty` | — | clears the playlist |
614
+
615
+ #### YouTube Music (`base: "youtubemusic"`)
616
+
617
+ Scalar `value`; most types take no value.
618
+
619
+ | type | `value` | notes |
620
+ | --- | --- | --- |
621
+ | `setPlayState` | `"play"` / `"pause"` / `"toggle"` | |
622
+ | `skipToNext` / `skipToPrevious` | — | |
623
+ | `toggleShuffle` / `toggleRepeat` | — | |
624
+ | `thumbsUp` / `thumbsDown` | — | |
625
+ | `setVolumeUp` / `setVolumeDown` | — | |
626
+ | `searchAndPlayImmediately` | `"<url, videoId, or playlistId>"` | YTMDesktop app only |
627
+
628
+ #### Voicemod (`base: "voicemod"`)
629
+
630
+ Scalar `value` (voice/sound name) for the lookups; the on/off toggles take no value.
631
+
632
+ | type | `value` | notes |
633
+ | --- | --- | --- |
634
+ | `voice` | `"<voice name>"` | load a specific voice |
635
+ | `random_voice` / `random_voice_favorite` | — | random voice (favorites only for the latter) |
636
+ | `sound` / `soundboard` | `"<sound name>"` | play a meme sound |
637
+ | `random_sound` / `random_soundboard` | — | random sound |
638
+ | `stop_all_sounds` | — | |
639
+ | `on` / `off` / `on_off` | — | voice changer on / off / toggle |
640
+ | `hear_my_voice_on` / `hear_my_voice_off` / `hear_my_voice_on_off` | — | |
641
+ | `background_on_off` / `mic_on_off` | — | toggle background effects / mute mic |
642
+
643
+ #### Streamer.bot (`base: "streamerbot"`)
644
+
645
+ Scalar `value` is the action name; `args` is a flat top-level JSON string.
646
+
647
+ ```js
648
+ async function() {
649
+ await actions([{ base: "streamerbot", type: "action", value: "My SB Action", args: '{"user":"{{username}}","amount":5}' }]);
650
+ done();
651
+ }
652
+ ```
653
+
654
+ #### Protocols — MQTT, WebSocket, Serial, Art-Net, Broadlink
655
+
656
+ These read their fields **flat** on the action object (no `value` wrapper).
657
+
658
+ ```js
659
+ async function() {
660
+ // MQTT: publish to a topic on a configured broker (host = the broker's host)
661
+ await actions([{ base: "mqtt", type: "send", host: "192.168.1.50", topic: "home/light", value: "ON" }]);
662
+
663
+ // WebSocket: send to one connection by its id
664
+ await actions([{ base: "websocket", type: "data", device: "<websocketId>", value: "hello" }]);
665
+
666
+ // Serial: write raw data to a port (9600 baud)
667
+ await actions([{ base: "serial", type: "write", port: "COM3", value: "L1\n" }]);
668
+
669
+ // Art-Net: set DMX channels on a universe (values 0-255; no template tokens here)
670
+ await actions([{ base: "artnet", type: "artnet", universe: 0, values: [{ channel: 1, value: 255 }, { channel: 2, value: 128 }] }]);
671
+
672
+ // Broadlink: send a learned IR/RF code by its library id or name to a device
673
+ await actions([{ base: "broadlink", type: "ir", device: "<deviceId>", value: "TV Power" }]);
674
+ done();
675
+ }
676
+ ```
677
+
678
+ #### Devices — SwitchBot, Wave Link, Camera Hub, StreamFog
679
+
680
+ All take a `value` object.
681
+
682
+ | base | type | `value` | notes |
683
+ | --- | --- | --- | --- |
684
+ | `switchbot` | `turnOn` / `turnOff` / `toggle` | `{ deviceId: "<id>" }` | |
685
+ | `wavelink` | `SET_INPUT_VOLUME` | `{ inputId: "<id>", volume: 50, mixerID: "com.elgato.mix.local" }` | volume 0–100; mixer `local`/`stream`/`all` |
686
+ | `wavelink` | `MUTE_INPUT` | `{ inputId: "<id>", muted: true, mixerID: "com.elgato.mix.local" }` | |
687
+ | `wavelink` | `SET_OUTPUT_VOLUME` / `MUTE_OUTPUT` | `{ volume: 50, mixerID: "com.elgato.mix.local" }` | |
688
+ | `wavelink3` | same types as `wavelink` | same shapes | Wave Link 3.0 uses base `wavelink3`; some legacy filter/mic types are unsupported on v3 |
689
+ | `camerahub` | `SET_WEBCAM_PROPERTY` | `{ propertyID: "Brightness", propertyValue: 60 }` | also `SELECT_DEVICE`, `SET_NVIDIA_VIDEO_EFFECT`, `SET_LUT_EFFECT`, `SET_PROMPTER_PROPERTY` |
690
+ | `streamfog` | `activateLens` | `{ lensId: "<id or name>", duration: 10 }` | `lensId: "random"` picks one; also `activateOutfit` (`{ outfit, duration }`) and `disableLens` (`{}`) |
691
+
135
692
  This documentation can get extremely broad for every integration, so if you get stuck please visit our [**Discord**](https://discord.gg/R8rCaKb) to ask us any questions.
@@ -93,7 +93,7 @@ async function() {
93
93
 
94
94
  ### Get Variable
95
95
 
96
- `getVariable(name: string)`: Retrieve a variables value based on it's name
96
+ `getVariable(name: string)`: Retrieve a variables value based on it's name. Returns the raw stored value (string / number / …), or `undefined` if it isn't set. A command's runtime variable takes priority over the saved one of the same name.
97
97
 
98
98
  ```js
99
99
  async function() {
@@ -127,7 +127,7 @@ async function() {
127
127
 
128
128
  ### Get All Variables
129
129
 
130
- `getAllVariables()`: Ability to get all local and global variables with one easy call
130
+ `getAllVariables()`: Ability to get all local and global variables with one easy call. Returns a flat `{ name: value }` map of every saved variable merged with the command's runtime variables (runtime values win on a name clash).
131
131
 
132
132
  ```js
133
133
  async function() {
@@ -197,6 +197,8 @@ async function() {
197
197
 
198
198
  `getLights()`: Get the list of lights the streamer has along with it's type and id. The type and id is required to send color or power to specific lights
199
199
 
200
+ Returns an array of `{ id, name, alias, type }`. `type` is the integration key (e.g. `hue`, `govee`, `wled`, `elgato`, `virtuallights`), `alias` is your custom label, and `name` is the device's original name. Pass `{ id, type }` objects to `sendColor`'s `lights` array to target specific lights. Disconnected or disabled integrations are omitted, so an empty array means nothing is connected.
201
+
200
202
  ```js
201
203
  async function() {
202
204
  const lights = await getLights()
@@ -246,6 +248,8 @@ async function() {
246
248
 
247
249
  `getApiOptions()`: Contains information like commands, types, connections, and more
248
250
 
251
+ Returns `{ types, options }`. `types` is the list of every API command type (e.g. `setColor`, `setBrightness`, `alert`, `tts`, `chatCommand`, `chatbotCommand`, `twitchPoints`, `kickPoints`, studio scene/theme/animation, plus value-less system ops). `options` is keyed by those same types: value-less ops are `null`, while the command/alert/tts/studio types carry a `{ values: [...] }` list of the names you can use.
252
+
249
253
  ```js
250
254
  async function() {
251
255
  const lights = await getApiOptions()
@@ -254,7 +258,7 @@ async function() {
254
258
 
255
259
  ### Get Commands
256
260
 
257
- `getCommands({ formatted?: boolean; onlyOn?: boolean; onlyUser?: boolean })`: Returns the list of chat command names. Pass `onlyOn: true` to only include commands that are enabled and shown in the commands list, `onlyUser: true` to only include commands the current user has access to (based on their user levels), and `formatted: true` to get a single comma separated string instead of an array
261
+ `getCommands({ formatted?: boolean; onlyOn?: boolean; onlyUser?: boolean })`: Returns the list of chat and chatbot command names. Pass `onlyOn: true` to only include commands that are enabled and shown in the commands list, `onlyUser: true` to only include commands the current user has access to (based on their user levels), and `formatted: true` to get a single comma separated string instead of an array
258
262
 
259
263
  ```js
260
264
  async function() {
@@ -486,7 +490,7 @@ async function() {
486
490
 
487
491
  ### Get Token
488
492
 
489
- `getToken(connection: "twitch" | "twitchChatbot" | "youtube" | "facebook" | "streamlabs" | "streamelements" | "treatstream" | "tipeeestream" | "tiltify" | "patreon" | "woocommerce" | "discord" | "twitter" | "spotify" | "pulsoid" | "wyze" | "homeassistant" | "govee" | "wled" )`: When you need to call a request that we don't directly support you can get the access token from Lumia before making the call. This is helpful for things where you need to call for instance the Twitch API, but you don't want to handle tokens and refreshing inside of your scripts. More examples of this below
493
+ `getToken(connection: "twitch" | "twitchChatbot" | "youtube" | "facebook" | "streamlabs" | "streamelements" | "treatstream" | "tipeeestream" | "tiltify" | "patreon" | "woocommerce" | "discord" | "twitter" | "spotify" | "pulsoid" | "wyze" | "homeassistant" | "govee" | "wled" )`: When you need to call a request that we don't directly support you can get the access token from Lumia before making the call. This is helpful for things where you need to call for instance the Twitch API, but you don't want to handle tokens and refreshing inside of your scripts. More examples of this below. Returns the access-token string, or `null` / `undefined` if that connection isn't authorized.
490
494
 
491
495
  ```js
492
496
  async function() {
@@ -497,7 +501,7 @@ async function() {
497
501
 
498
502
  ### Get Client ID For Twitch
499
503
 
500
- `getClientId(connection: "twitch")`: When calling requests with Twitch's API you will need to pass in a Client-ID to the headers. We provide a Client ID that you can use to call the different api's with the permissions the user has selected. Check out [Twitch's developers docs](https://dev.twitch.tv/docs/api/reference) to learn what you can do
504
+ `getClientId(connection: "twitch")`: When calling requests with Twitch's API you will need to pass in a Client-ID to the headers. We provide a Client ID that you can use to call the different api's with the permissions the user has selected. Check out [Twitch's developers docs](https://dev.twitch.tv/docs/api/reference) to learn what you can do. Returns the Twitch Client-ID string; only `"twitch"` is supported and any other value returns `null`.
501
505
 
502
506
  ```js
503
507
  async function() {
@@ -682,7 +682,7 @@ export const LumiaAlertFriendlyValues = {
682
682
  [LumiaAlertValues.YOUTUBE_SUBSCRIBER]: 'Youtube Subscriber',
683
683
  [LumiaAlertValues.YOUTUBE_SUPERCHAT]: 'Youtube Superchat',
684
684
  [LumiaAlertValues.YOUTUBE_SUPERSTICKER]: 'Youtube Supersticker',
685
- [LumiaAlertValues.YOUTUBE_GIFTS]: 'Youtube Gifts',
685
+ [LumiaAlertValues.YOUTUBE_GIFTS]: 'Youtube Gifts (Jewels)',
686
686
  [LumiaAlertValues.YOUTUBE_SESSION_GIFTS]: 'Youtube Session Gifts',
687
687
  [LumiaAlertValues.YOUTUBE_ENTRANCE]: 'Youtube Entrance',
688
688
  [LumiaAlertValues.YOUTUBE_LIKE]: 'Youtube Like',