@lumiastream/lumia-types 3.8.0 → 3.8.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.
@@ -0,0 +1,140 @@
1
+ # Lumia Stream Chatbot Message Reference
2
+
3
+ This document is the reference for writing **chat bot messages** for Lumia Stream chat commands. A chat command runs when a viewer types a trigger (for example `!hello`) and the bot sends back a message. The message is a single line of text that can contain dynamic **variables** and **variable functions**.
4
+
5
+ ## Message syntax
6
+
7
+ Write the message as plain text. Insert dynamic values with double curly braces: `{{variable}}`. When the message is sent, each token is replaced with its live value.
8
+
9
+ - A plain variable: `Welcome {{username}}!` becomes `Welcome lumi!`.
10
+ - A default/fallback value if the variable is empty: `{{latest_subscriber=nobody yet}}`.
11
+ - Tokens can be nested inside a function: `{{math={{twitch_total_follower_count}}+1}}`.
12
+ - Anything that is not inside `{{ }}` is sent literally.
13
+
14
+ Keep messages to a single chat line unless the user asks for more. Do not wrap the message in quotes and do not use markdown. Only use variables and functions that appear in this reference — never invent new variable or function names.
15
+
16
+ ## Common variables
17
+
18
+ These resolve to live values about the viewer, the stream, and recent events.
19
+
20
+ ### Viewer / chatter
21
+
22
+ - `{{username}}` — the login name of the viewer who triggered the command.
23
+ - `{{displayname}}` — the viewer's display (cased) name.
24
+ - `{{message}}` — the full text the viewer typed after the command.
25
+ - `{{platform}}` — the viewer's platform: `twitch`, `youtube`, `kick`, `tiktok`, or `facebook`.
26
+ - `{{userLevels}}` — the viewer's roles (mod, vip, subscriber, follower, etc.).
27
+
28
+ ### The streamer / channel
29
+
30
+ - `{{streamer}}` — the streamer's name (works across any connected platform).
31
+ - `{{twitch_channel_title}}` — the current stream title.
32
+ - `{{twitch_category}}` — the current game or category.
33
+ - `{{twitch_current_viewer_count}}` — current live viewer count on Twitch.
34
+ - `{{twitch_live}}` — `true` or `false` for whether Twitch is live.
35
+
36
+ ### Followers, subscribers, donations (totals and sessions)
37
+
38
+ - `{{total_follower_count}}` — all-time follower count across platforms.
39
+ - `{{total_subscriber_count}}` — all-time subscriber count across platforms.
40
+ - `{{twitch_total_follower_count}}`, `{{kick_total_subscriber_count}}`, etc. — per-platform totals.
41
+ - `{{session_follower_count}}` — followers gained this stream session.
42
+ - `{{session_subscriber_count}}` — subscribers gained this session.
43
+ - `{{session_donation_amount}}` — total donations this session.
44
+
45
+ ### Most recent events
46
+
47
+ - `{{last_follower}}` — name of the most recent follower.
48
+ - `{{latest_subscriber}}` — name of the most recent subscriber.
49
+ - `{{last_cheer}}` — name of the last person to cheer bits.
50
+ - `{{last_raider}}` — name of the last raider, with `{{last_raider_amount}}` viewers.
51
+ - `{{latest_donator}}` — last donor, with `{{latest_donator_amount}}` and `{{latest_donator_currency_symbol}}`.
52
+
53
+ ### Time and uptime
54
+
55
+ - `{{today}}` — the current date.
56
+ - `{{time}}` — the current time.
57
+ - `{{lumia_uptime}}` — how long Lumia Stream has been running (e.g. `1d 5h`).
58
+ - `{{twitch_uptime}}` — how long the Twitch stream has been live.
59
+
60
+ ### Loyalty / points
61
+
62
+ - `{{loyalty_currency_name}}` — the custom name of the channel's points (e.g. "Lumipoints").
63
+
64
+ ## Variable functions
65
+
66
+ Variable functions perform logic. They are written `{{name=arguments}}`. Arguments are separated by commas. If an argument itself contains a comma (a URL, a regex, a date pattern), wrap that argument in quotes.
67
+
68
+ ### Randomness
69
+
70
+ - `{{random}}` — a random number from 1 to 100.
71
+ - `{{random=1,6}}` — a random number in a range (a dice roll).
72
+ - `{{random_input=a,b,c}}` — pick one item at random from the list.
73
+ - `{{random_inputs={{username}},{{last_subscriber}}}}` — pick at random between variables.
74
+ - `{{random_chatter=twitch}}` — pick a random recent chatter on a platform. Use `all` for any platform.
75
+
76
+ ### Math and numbers
77
+
78
+ - `{{math={{var1}}+{{var2}}}}` — evaluate a math expression. Operators: `+ - * / % ^`, with parentheses.
79
+ - `{{round={{math=10/3}},2}}` — round a number to N decimal places.
80
+ - `{{min={{var1}},{{var2}},100}}` / `{{max=...}}` — smallest / largest of the values.
81
+ - `{{sum_variables=twitch_total_follower_count,kick_total_follower_count}}` — add a list of numeric variables.
82
+ - `{{offset_count=twitch_total_follower_count,10}}` — add (or subtract) a fixed offset to a numeric variable.
83
+
84
+ ### Conditionals
85
+
86
+ - `{{compare={{var1}},>,{{var2}}}}` — returns `true` or `false`. Operators: `> >= < <= == !=`.
87
+ - `{{between={{twitch_current_viewer_count}},1,100}}` — returns `true` if the value is in the range.
88
+ - `{{if={{compare={{var1}},>,10}},high,low}}` — if the condition is truthy, output the second argument, otherwise the third.
89
+ - `{{coalesce={{displayname}},{{username}},Anonymous}}` — output the first argument that is not empty.
90
+
91
+ ### Text
92
+
93
+ - `{{arg=1}}` — the first word the viewer typed after the command. `{{arg=2}}` is the second word, and so on.
94
+ - `{{arg=1,word}}` — like `arg` but only accepts a plain word. Use `emote` to require an emote.
95
+ - `{{replace={{message}},badword,***}}` — replace text. The search term may be a `/pattern/flags` regex.
96
+ - `{{regex_extract={{message}},(\w+),1}}` — extract regex capture group N from the text.
97
+
98
+ ### Counters and saved values
99
+
100
+ - `{{counter=deaths}}` — increment a saved counter by 1 and output the new value.
101
+ - `{{counter=deaths,+5}}` — change a counter. Modifiers: `+N` add, `-N` subtract, `=N` set, `*N` multiply.
102
+ - `{{save_local=highscore,100}}` — save a value that persists between messages.
103
+ - `{{load_local=highscore,0}}` — load a saved value, with a fallback if it is unset.
104
+
105
+ ### Dates and durations
106
+
107
+ - `{{format_date={{session_start_date}},"MM/DD/YYYY hh:mm A"}}` — format a date (moment.js patterns; quote patterns containing commas).
108
+ - `{{time_since={{follow_time}}}}` — elapsed time since a date, like `2d 3h`.
109
+ - `{{time_until="2026-12-25T00:00:00Z"}}` — countdown to a future date.
110
+ - `{{twitch_followage}}` — how long the viewer has followed.
111
+ - `{{account_age}}` — how long ago the viewer's account was created.
112
+
113
+ ### Roles and user lookups
114
+
115
+ - `{{user_has_role=mod}}` — outputs the username if they have the role, otherwise empty. Roles: `mod`, `vip`, `subscriber`, `tier1`/`tier2`/`tier3`, `follower`, `broadcaster`.
116
+ - `{{user_has_role=subscriber,yesno}}` — outputs `Yes` or `No`.
117
+ - `{{user_top_role}}` — the viewer's highest role.
118
+ - `{{lookup_user=someuser,twitch,displayname}}` — look up another user's field. Fields: `displayname`, `avatar`, `channelDescription`, `channelViews`.
119
+ - `{{get_user_loyalty_points={{username}}}}` — the viewer's loyalty point balance.
120
+ - `{{user_watchtime={{username}}}}` — the viewer's total watch time.
121
+
122
+ ### External / advanced
123
+
124
+ - `{{read_url="https://api.example.com/user",data.name}}` — fetch JSON from a URL and read a field (quote URLs with commas/query strings).
125
+ - `{{weather=Seattle}}` — current weather for a location.
126
+ - `{{js=Math.floor(Math.random()*6)+1}}` — run a small sandboxed JavaScript expression (1 second limit). Wrap string variables in quotes, e.g. `{{js='{{username}}'.toUpperCase()}}`.
127
+
128
+ ## StreamElements compatibility
129
+
130
+ Lumia accepts StreamElements `$(...)` syntax and converts it automatically. Prefer native `{{ }}` tokens, but these are equivalent: `$(sender)` → `{{username}}`, `$(touser)` → `{{arg=1}}`, `$(count)` → `{{counter}}`, `$(random.pick a b c)` → `{{random_input=a,b,c}}`, `$(urlfetch url)` → `{{read_url=url}}`.
131
+
132
+ ## Examples
133
+
134
+ - Greeting: `Welcome to the stream, {{username}}! 🎉`
135
+ - Death counter: `{{streamer}} has died {{counter=deaths,+1}} times this stream.`
136
+ - Dice roll: `🎲 {{username}} rolled a {{random=1,6}}!`
137
+ - Shoutout: `Go check out {{arg=1}} at twitch.tv/{{arg=1}} — they were last seen playing {{lookup_user={{arg=1}},twitch,channelDescription}}!`
138
+ - Followage: `{{username}}, you have been following for {{twitch_followage}}.`
139
+ - Subs goal: `Sub count: {{twitch_total_subscriber_count}} / 100 — {{if={{compare={{twitch_total_subscriber_count}},>=,100}},goal hit!,keep going!}}`
140
+ - Points: `{{username}}, you have {{get_user_loyalty_points={{username}}}} {{loyalty_currency_name}}.`
@@ -1106,6 +1106,8 @@ export declare enum SystemVariables {
1106
1106
  RANDOM_INPUT = "random_input",
1107
1107
  /** Evaluate a math expression. Example: {{math={{var1}}+{{var2}}}}. Use as {{math}}. */
1108
1108
  MATH = "math",
1109
+ /** Evaluate a JavaScript expression in a sandbox. Example: {{js=10 * {{var1}}}}. Use as {{js}}. */
1110
+ JS = "js",
1109
1111
  /** Compare two values. Example: {{compare={{var1}},>,{{var2}}}}. Use as {{compare}}. */
1110
1112
  COMPARE = "compare",
1111
1113
  /** Round a value to decimal places. Example: {{round={{math={{var1}}/{{var2}}}},2}}. Use as {{round}}. */
@@ -1106,6 +1106,8 @@ export declare enum SystemVariables {
1106
1106
  RANDOM_INPUT = "random_input",
1107
1107
  /** Evaluate a math expression. Example: {{math={{var1}}+{{var2}}}}. Use as {{math}}. */
1108
1108
  MATH = "math",
1109
+ /** Evaluate a JavaScript expression in a sandbox. Example: {{js=10 * {{var1}}}}. Use as {{js}}. */
1110
+ JS = "js",
1109
1111
  /** Compare two values. Example: {{compare={{var1}},>,{{var2}}}}. Use as {{compare}}. */
1110
1112
  COMPARE = "compare",
1111
1113
  /** Round a value to decimal places. Example: {{round={{math={{var1}}/{{var2}}}},2}}. Use as {{round}}. */
package/dist/esm/index.js CHANGED
@@ -3,6 +3,7 @@ export { LumiaVariationConditions, LumiaVariationCurrency, VariationCurrencySymb
3
3
  export { LumiaIntegrations, LumiaEventTypes, } from './event.types.js';
4
4
  export { LumiaEventListTypes, LumiaMapAlertTypeToEventListType, AlertsToFilter, PlatformsToFilter, LumiaEventListTypeColors, getEventListCategoryColor } from './eventlist.types.js';
5
5
  export { SystemVariables, ReservedVariables, AllVariables, getAcceptedVariableName, getAcceptedVariableNames, } from './variables.types.js';
6
+ export { VARIABLE_EXAMPLES, getVariableExamples } from './variableExamples.js';
6
7
  export { formatCondition } from './helpers.js';
7
8
  export { EMULATE_EXAMPLE_AVATAR_URL, getExampleAlertVariableValue, buildExampleAlertVariables, coerceNumericAlertFields, aliasContentImageFromLegacy, isRedundantInputField, syncLinkedVariableFields, } from './emulate.helpers.js';
8
9
  export { KickKicksData, KickKicksImageSelections } from './kick_kicks.js';
@@ -0,0 +1,342 @@
1
+ // English-only example use-cases for function variables, surfaced when a
2
+ // function variable is expanded in a variable picker. Keyed by variable name;
3
+ // the snippet is the inner token (what goes between {{ }}). Shared across apps —
4
+ // the UI layer receives this map and renders it. Only list a variable here when
5
+ // it has 2+ genuinely distinct examples (different args/capability, or a
6
+ // discoverable option set) — a single example just duplicates the default
7
+ // insert.
8
+ //
9
+ // All snippets below are verified against the VariablesManager implementations.
10
+ // Two gotchas the resolver imposes: function args split on commas, so a regex
11
+ // or date pattern that contains a comma must be wrapped in quotes; and a literal
12
+ // backslash in a pattern is written `\\` in this source so the runtime string is
13
+ // a single backslash.
14
+ export const VARIABLE_EXAMPLES = {
15
+ read_url: [
16
+ { label: 'Fetch a plain-text endpoint', snippet: 'read_url=https://api.lumiastream.com/api/url-test' },
17
+ { label: 'Pull one field out of a JSON response', snippet: 'read_url=https://api.example.com/user,data.name' },
18
+ { label: 'URL containing commas (wrap in quotes)', snippet: 'read_url="https://api.example.com/?ids=1,2,3",result' },
19
+ ],
20
+ selection: [
21
+ { label: 'Validate a vote (chatter types one)', snippet: 'selection=red,blue,green' },
22
+ { label: 'Yes / no / maybe gate', snippet: 'selection=yes,no,maybe' },
23
+ { label: 'Pick a side', snippet: 'selection=heads,tails' },
24
+ ],
25
+ random: [
26
+ { label: 'Number between 1 and 100', snippet: 'random=1,100' },
27
+ { label: 'Dice roll (1–6)', snippet: 'random=1,6' },
28
+ { label: 'Allow negative numbers', snippet: 'random=-10,10' },
29
+ ],
30
+ random_input: [
31
+ { label: 'Magic 8-ball answer', snippet: 'random_input=Yes,No,Maybe,Ask again later,Definitely' },
32
+ { label: 'Decide what to play', snippet: 'random_input=Minecraft,Valorant,Just Chatting' },
33
+ { label: 'Coin flip', snippet: 'random_input=Heads,Tails' },
34
+ { label: 'Random hype message', snippet: "random_input=Let's go!,Insane!,GG" },
35
+ ],
36
+ random_inputs: [
37
+ { label: 'Random compliment', snippet: "random_inputs=You're awesome!,Great vibes!,Legend!" },
38
+ { label: 'Pick a random viewer to shout out', snippet: 'random_inputs={{username}},{{last_subscriber}},{{last_follower}}' },
39
+ { label: 'Random dare', snippet: 'random_inputs=No swearing for 5 min,Use a funny voice,Do a dance' },
40
+ ],
41
+ math: [
42
+ { label: 'Add two variables', snippet: 'math={{twitch_total_follower_count}}+{{kick_total_follower_count}}' },
43
+ { label: 'Percentage of a goal', snippet: 'math={{current}}/{{goal}}*100' },
44
+ { label: 'Group with parentheses', snippet: 'math=({{var1}}+{{var2}})/2' },
45
+ { label: 'Even/odd via modulo', snippet: 'math={{arg=1}}%2' },
46
+ { label: 'Exponent (square)', snippet: 'math={{arg=1}}^2' },
47
+ ],
48
+ js: [
49
+ { label: 'Celsius → Fahrenheit', snippet: 'js={{arg=1}} * 9 / 5 + 32' },
50
+ { label: 'Round to 2 decimals', snippet: 'js=({{arg=1}} / 3).toFixed(2)' },
51
+ { label: 'Adult / minor check (ternary)', snippet: "js={{arg=1}} >= 18 ? 'adult' : 'minor'" },
52
+ { label: 'Random dice roll 1–6', snippet: 'js=Math.floor(Math.random() * 6) + 1' },
53
+ { label: 'Clamp between 0 and 100', snippet: 'js=Math.min(100, Math.max(0, {{arg=1}}))' },
54
+ { label: "Uppercase the caller's name", snippet: "js='{{username}}'.toUpperCase()" },
55
+ { label: 'Comma-format a big number', snippet: 'js=Number({{twitch_total_follower_count}}).toLocaleString()' },
56
+ { label: 'First word of the message', snippet: "js='{{message}}'.split(' ')[0]" },
57
+ ],
58
+ compare: [
59
+ { label: 'Greater than', snippet: 'compare={{var1}},>,{{var2}}' },
60
+ { label: 'At least 100 viewers', snippet: 'compare={{twitch_current_viewer_count}},>=,100' },
61
+ { label: 'Equality check', snippet: 'compare={{platform}},==,twitch' },
62
+ { label: 'Not equal to a value', snippet: 'compare={{game}},!=,Just Chatting' },
63
+ ],
64
+ round: [
65
+ { label: 'Round a division to 2 decimals', snippet: 'round={{math={{var1}}/{{var2}}}},2' },
66
+ { label: 'Round to a whole number', snippet: 'round={{math={{var1}}/{{var2}}}},0' },
67
+ { label: 'Goal percentage to 1 decimal', snippet: 'round={{math={{current}}/{{goal}}*100}},1' },
68
+ ],
69
+ if: [
70
+ { label: 'High / low from a comparison', snippet: 'if={{compare={{var1}},>,10}},high,low' },
71
+ { label: 'Badge only for mods', snippet: 'if={{user_has_role=mod}},🛡️,' },
72
+ { label: 'Sub vs viewer', snippet: 'if={{user_has_role=subscriber}},sub,viewer' },
73
+ { label: 'Branch on a range', snippet: 'if={{between={{viewers}},1,10}},cozy,packed' },
74
+ ],
75
+ coalesce: [
76
+ { label: 'Display name with fallback', snippet: 'coalesce={{display_name}},{{username}},Anonymous' },
77
+ { label: "First connected platform's title", snippet: 'coalesce={{twitch_channel_title}},{{kick_channel_title}}' },
78
+ { label: 'Typed target, else the caller', snippet: 'coalesce={{arg=1}},{{username}}' },
79
+ ],
80
+ between: [
81
+ { label: 'Check a value is in range', snippet: 'between={{var1}},10,50' },
82
+ { label: 'Gate on viewer count', snippet: 'between={{twitch_current_viewer_count}},1,100' },
83
+ ],
84
+ min: [
85
+ { label: 'Smallest of several values', snippet: 'min={{v1}},{{v2}},100' },
86
+ { label: 'Cap a value at 100', snippet: 'min={{score}},100' },
87
+ ],
88
+ max: [
89
+ { label: 'Largest of several values', snippet: 'max={{v1}},{{v2}},0' },
90
+ { label: 'Floor a value at 0', snippet: 'max={{score}},0' },
91
+ ],
92
+ regex_extract: [
93
+ { label: 'First number in the message', snippet: 'regex_extract={{message}},([0-9]+),1' },
94
+ { label: 'First word in the message', snippet: 'regex_extract={{message}},(\\w+),1' },
95
+ { label: 'A @mention', snippet: 'regex_extract={{message}},@(\\w+),1' },
96
+ { label: 'A hashtag (whole match, group 0)', snippet: 'regex_extract={{message}},#\\w+,0' },
97
+ { label: 'Comma in pattern → quote it', snippet: 'regex_extract={{message}},"(\\d{2,4})",1' },
98
+ ],
99
+ replace: [
100
+ { label: 'Censor a word', snippet: 'replace={{message}},badword,***' },
101
+ { label: 'Case-insensitive regex censor', snippet: 'replace={{message}},/badword/gi,***' },
102
+ { label: 'Strip everything but digits', snippet: 'replace={{message}},/[^0-9]/g,' },
103
+ { label: 'Swap text', snippet: 'replace={{message}},hello,hi' },
104
+ { label: 'Remove a word', snippet: 'replace={{message}},spoiler,' },
105
+ ],
106
+ format_date: [
107
+ { label: 'Date and time (default)', snippet: 'format_date={{session_start_date}},MM/DD/YYYY hh:mm A' },
108
+ { label: 'ISO date', snippet: 'format_date={{session_start_date}},YYYY-MM-DD' },
109
+ { label: '12-hour clock', snippet: 'format_date={{session_start_date}},h:mm A' },
110
+ { label: '24-hour clock', snippet: 'format_date={{session_start_date}},HH:mm' },
111
+ { label: 'Weekday name', snippet: 'format_date={{session_start_date}},dddd' },
112
+ { label: 'Month and ordinal day', snippet: 'format_date={{session_start_date}},MMMM Do' },
113
+ { label: 'Short day + time', snippet: 'format_date={{session_start_date}},ddd h:mm A' },
114
+ { label: 'Friendly (quote patterns with commas)', snippet: 'format_date={{session_start_date}},"dddd, MMM Do YYYY"' },
115
+ { label: 'Full date with comma (quoted)', snippet: 'format_date={{session_start_date}},"MMM D, YYYY"' },
116
+ ],
117
+ time_since: [
118
+ { label: 'Since you went live', snippet: 'time_since={{session_start_date}}' },
119
+ { label: 'Since a follow', snippet: 'time_since={{follow_time}}' },
120
+ { label: 'Since a fixed date', snippet: 'time_since=2020-01-01' },
121
+ { label: 'Since a variable date', snippet: 'time_since={{anniversary_date}}' },
122
+ ],
123
+ time_until: [
124
+ { label: 'Countdown to a date', snippet: 'time_until=2026-12-25T00:00:00Z' },
125
+ { label: 'Until the next Twitch ad', snippet: 'time_until={{next_ad_starts_date}}' },
126
+ { label: 'Until the poll closes', snippet: 'time_until={{poll_ends_at}}' },
127
+ { label: 'Until the prediction closes', snippet: 'time_until={{prediction_ends_at}}' },
128
+ { label: 'Countdown to a variable date', snippet: 'time_until={{event_date}}' },
129
+ ],
130
+ counter: [
131
+ { label: 'Add one (e.g. deaths)', snippet: 'counter=mydeaths' },
132
+ { label: 'Add five', snippet: 'counter=mydeaths,+5' },
133
+ { label: 'Subtract one', snippet: 'counter=mydeaths,-1' },
134
+ { label: 'Set to a value', snippet: 'counter=mydeaths,=0' },
135
+ { label: 'Double it', snippet: 'counter=score,*2' },
136
+ ],
137
+ save_local: [
138
+ { label: 'Save a high score', snippet: 'save_local=highscore,100' },
139
+ { label: 'Save a flag', snippet: 'save_local=intro_done,true' },
140
+ ],
141
+ load_local: [
142
+ { label: 'Load a saved value', snippet: 'load_local=highscore' },
143
+ { label: 'Load with a fallback', snippet: 'load_local=highscore,0' },
144
+ ],
145
+ arg: [
146
+ { label: 'First word after the command', snippet: 'arg=1' },
147
+ { label: 'Second word', snippet: 'arg=2' },
148
+ { label: 'Require alphanumeric', snippet: 'arg=1,word' },
149
+ { label: 'Require an emote', snippet: 'arg=3,emote' },
150
+ ],
151
+ get_var_from_msg: [
152
+ { label: 'Read name= from the message', snippet: 'get_var_from_msg=name' },
153
+ { label: 'Read age= from the message', snippet: 'get_var_from_msg=age' },
154
+ { label: 'Read a quoted value (msg color="hot pink")', snippet: 'get_var_from_msg=color' },
155
+ ],
156
+ lookup_user: [
157
+ { label: "Another user's display name", snippet: 'lookup_user=lumi' },
158
+ { label: 'The target the chatter typed', snippet: 'lookup_user={{arg=1}}' },
159
+ { label: 'Avatar on a platform', snippet: 'lookup_user=lumi,twitch,avatar' },
160
+ ],
161
+ random_chatter: [
162
+ { label: 'Any recent chatter', snippet: 'random_chatter' },
163
+ { label: 'Twitch only', snippet: 'random_chatter=twitch' },
164
+ { label: 'Across all platforms', snippet: 'random_chatter=all' },
165
+ ],
166
+ user_has_role: [
167
+ { label: 'Mods only', snippet: 'user_has_role=mod' },
168
+ { label: 'VIPs only', snippet: 'user_has_role=vip' },
169
+ { label: 'Subscribers only', snippet: 'user_has_role=subscriber' },
170
+ ],
171
+ sum_variables: [
172
+ { label: 'Combined followers', snippet: 'sum_variables=twitch_total_follower_count,kick_total_follower_count' },
173
+ { label: 'Combined subscribers', snippet: 'sum_variables=twitch_total_subscriber_count,kick_total_subscriber_count' },
174
+ ],
175
+ offset_count: [
176
+ { label: 'Pad a follower count', snippet: 'offset_count=twitch_total_follower_count,10' },
177
+ { label: 'Subtract from a count', snippet: 'offset_count=twitch_total_follower_count,-5' },
178
+ ],
179
+ convert_color_to_hex: [
180
+ { label: 'Green to hex', snippet: 'convert_color_to_hex=green' },
181
+ { label: 'Hot pink to hex', snippet: 'convert_color_to_hex=hotpink' },
182
+ { label: 'From a chat color name', snippet: 'convert_color_to_hex={{message}}' },
183
+ ],
184
+ get_random_file_from_folder: [
185
+ { label: 'Random sound for a !sfx command', snippet: 'get_random_file_from_folder=C:/Sounds' },
186
+ { label: 'Random meme image', snippet: 'get_random_file_from_folder=C:/Memes' },
187
+ { label: 'Random clip', snippet: 'get_random_file_from_folder=C:/Clips' },
188
+ ],
189
+ get_latest_file_from_folder: [
190
+ { label: 'Latest clip', snippet: 'get_latest_file_from_folder=C:/Clips' },
191
+ { label: 'Latest screenshot', snippet: 'get_latest_file_from_folder=C:/Screenshots' },
192
+ { label: 'Latest recording', snippet: 'get_latest_file_from_folder=C:/Recordings' },
193
+ ],
194
+ screenshot: [
195
+ { label: 'Primary monitor', snippet: 'screenshot' },
196
+ { label: 'Second monitor', snippet: 'screenshot=2' },
197
+ ],
198
+ overlay_screenshot: [
199
+ { label: 'Capture an overlay', snippet: 'overlay_screenshot=Overlay 1' },
200
+ { label: 'Thermal filter', snippet: 'overlay_screenshot=Overlay 1,thermal' },
201
+ { label: 'Grayscale filter', snippet: 'overlay_screenshot=Overlay 1,grayscale' },
202
+ { label: 'Inverted colors', snippet: 'overlay_screenshot=Overlay 1,invert' },
203
+ { label: 'Black & white (binary)', snippet: 'overlay_screenshot=Overlay 1,binary' },
204
+ ],
205
+ obs_screenshot: [
206
+ { label: 'Current OBS scene', snippet: 'obs_screenshot' },
207
+ { label: 'A specific scene', snippet: 'obs_screenshot=Scene 1' },
208
+ ],
209
+ obs_replay: [
210
+ { label: 'Save the replay buffer', snippet: 'obs_replay' },
211
+ { label: 'Wait 5s before saving', snippet: 'obs_replay=5' },
212
+ ],
213
+ obs_vertical_replay: [
214
+ { label: 'Save the vertical buffer', snippet: 'obs_vertical_replay' },
215
+ { label: 'Wait 5s before saving', snippet: 'obs_vertical_replay=5' },
216
+ ],
217
+ twitch_followage: [
218
+ { label: 'Your own followage', snippet: 'twitch_followage' },
219
+ { label: "Another user's followage", snippet: 'twitch_followage={{username}}' },
220
+ ],
221
+ twitch_accountage: [
222
+ { label: 'Your account age', snippet: 'twitch_accountage' },
223
+ { label: "Another user's account age", snippet: 'twitch_accountage={{username}}' },
224
+ ],
225
+ account_age: [
226
+ { label: "Caller's account age", snippet: 'account_age={{username}}' },
227
+ { label: 'On a specific platform', snippet: 'account_age={{username}},twitch' },
228
+ ],
229
+ get_avatar: [
230
+ { label: "Caller's avatar", snippet: 'get_avatar={{username}}' },
231
+ { label: 'A named user', snippet: 'get_avatar=lumi' },
232
+ { label: 'On a specific platform', snippet: 'get_avatar=lumi,twitch' },
233
+ ],
234
+ vanish: [
235
+ { label: 'Clear your own messages', snippet: 'vanish={{username}}' },
236
+ { label: 'Clear the target user', snippet: 'vanish={{arg=1}}' },
237
+ ],
238
+ get_user_loyalty_points: [
239
+ { label: 'Points for a typed user', snippet: 'get_user_loyalty_points={{message}}' },
240
+ { label: 'User on a platform', snippet: 'get_user_loyalty_points={{username}},{{platform}}' },
241
+ ],
242
+ user_watchtime: [
243
+ { label: "Caller's watchtime", snippet: 'user_watchtime={{username}}' },
244
+ { label: 'A named user/platform', snippet: 'user_watchtime=lumi,twitch' },
245
+ ],
246
+ toggle_automation: [
247
+ { label: 'Enable an automation', snippet: 'toggle_automation=My Automation,true' },
248
+ { label: 'Disable an automation', snippet: 'toggle_automation=My Automation,false' },
249
+ ],
250
+ translate: [
251
+ { label: 'Auto-translate to your language', snippet: 'translate={{message}}' },
252
+ { label: 'To Spanish', snippet: 'translate={{message}}|es' },
253
+ { label: 'To French', snippet: 'translate={{message}}|fr' },
254
+ { label: 'To German', snippet: 'translate={{message}}|de' },
255
+ { label: 'To Japanese', snippet: 'translate={{message}}|ja' },
256
+ { label: 'To Korean', snippet: 'translate={{message}}|ko' },
257
+ { label: 'To Portuguese', snippet: 'translate={{message}}|pt' },
258
+ { label: 'To English', snippet: 'translate={{message}}|en' },
259
+ ],
260
+ ai_prompt: [
261
+ { label: "Answer the chatter's question", snippet: 'ai_prompt={{message}}' },
262
+ { label: 'Fun fact about a topic', snippet: 'ai_prompt=Give a short fun fact about {{message}}' },
263
+ { label: 'Playful roast', snippet: 'ai_prompt=Roast {{username}} in one playful sentence' },
264
+ { label: 'Summarize in one line', snippet: 'ai_prompt=Summarize this in one sentence: {{message}}' },
265
+ { label: 'Keep context with a thread', snippet: 'ai_prompt={{message}}|{{username}}' },
266
+ { label: 'With a thread and model', snippet: 'ai_prompt=Make a funny quote|thread_name|gpt-5-mini' },
267
+ ],
268
+ weather: [
269
+ { label: 'By city', snippet: 'weather=Seattle' },
270
+ { label: 'City, state', snippet: 'weather=New York, NY' },
271
+ { label: 'By coordinates', snippet: 'weather=47.6,-122.3' },
272
+ ],
273
+ time: [
274
+ { label: 'Your local time', snippet: 'time' },
275
+ { label: 'Eastern (New York)', snippet: 'time=America/New_York' },
276
+ { label: 'Central (Chicago)', snippet: 'time=America/Chicago' },
277
+ { label: 'Mountain (Denver)', snippet: 'time=America/Denver' },
278
+ { label: 'Pacific (Los Angeles)', snippet: 'time=America/Los_Angeles' },
279
+ { label: 'UK (London)', snippet: 'time=Europe/London' },
280
+ { label: 'Central Europe (Paris)', snippet: 'time=Europe/Paris' },
281
+ { label: 'Japan (Tokyo)', snippet: 'time=Asia/Tokyo' },
282
+ { label: 'Australia (Sydney)', snippet: 'time=Australia/Sydney' },
283
+ { label: 'UTC', snippet: 'time=Etc/UTC' },
284
+ ],
285
+ today: [
286
+ { label: "Today's date (local)", snippet: 'today' },
287
+ { label: 'Date in Eastern time', snippet: 'today=America/New_York' },
288
+ { label: 'Date in the UK', snippet: 'today=Europe/London' },
289
+ { label: 'Date in Japan', snippet: 'today=Asia/Tokyo' },
290
+ { label: 'Date in UTC', snippet: 'today=Etc/UTC' },
291
+ ],
292
+ lookup_user_game: [
293
+ { label: "A typed user's category", snippet: 'lookup_user_game={{arg=1}}' },
294
+ { label: 'On a specific platform', snippet: 'lookup_user_game=someuser,twitch' },
295
+ ],
296
+ lookup_user_title: [
297
+ { label: "A typed user's title", snippet: 'lookup_user_title={{arg=1}}' },
298
+ { label: 'On a specific platform', snippet: 'lookup_user_title=someuser,twitch' },
299
+ ],
300
+ channel_emotes: [
301
+ { label: "Caller's platform emotes", snippet: 'channel_emotes' },
302
+ { label: 'A specific platform', snippet: 'channel_emotes=twitch' },
303
+ ],
304
+ viewer_profile_summary: [
305
+ { label: "Caller's profile", snippet: 'viewer_profile_summary={{username}}' },
306
+ { label: 'A typed user', snippet: 'viewer_profile_summary={{arg=1}}' },
307
+ ],
308
+ filesay: [
309
+ { label: 'Post each line of a URL', snippet: 'filesay=https://example.com/list.txt' },
310
+ { label: 'Post each line of a local file', snippet: 'filesay=C:/path/list.txt' },
311
+ ],
312
+ set_alerts_paused: [
313
+ { label: 'Pause alerts', snippet: 'set_alerts_paused=true' },
314
+ { label: 'Resume alerts', snippet: 'set_alerts_paused=false' },
315
+ ],
316
+ set_sfx_muted: [
317
+ { label: 'Mute sound effects', snippet: 'set_sfx_muted=true' },
318
+ { label: 'Unmute sound effects', snippet: 'set_sfx_muted=false' },
319
+ ],
320
+ loyalty_top: [
321
+ { label: 'Top 3 users', snippet: 'loyalty_top=3' },
322
+ { label: 'Top 5 users', snippet: 'loyalty_top=5' },
323
+ { label: 'Top 10 users', snippet: 'loyalty_top=10' },
324
+ ],
325
+ add_points: [
326
+ { label: 'Give a user points', snippet: 'add_points={{username}},100' },
327
+ { label: 'Give the target points', snippet: 'add_points={{arg=1}},50' },
328
+ ],
329
+ set_points: [
330
+ { label: "Set a user's points", snippet: 'set_points={{username}},100' },
331
+ { label: 'Reset a user to zero', snippet: 'set_points={{arg=1}},0' },
332
+ ],
333
+ give_points: [
334
+ { label: 'Transfer to the typed user', snippet: 'give_points={{username}},{{touser}},100' },
335
+ { label: 'Transfer a fixed amount', snippet: 'give_points={{username}},{{arg=1}},50' },
336
+ ],
337
+ add_chatbot_command: [
338
+ { label: 'Create a static command', snippet: 'add_chatbot_command=hi,Hello there!' },
339
+ { label: 'Create one using a variable', snippet: 'add_chatbot_command=so,Check out {{arg=1}}' },
340
+ ],
341
+ };
342
+ export const getVariableExamples = (variableName) => { var _a; return (_a = VARIABLE_EXAMPLES[variableName]) !== null && _a !== void 0 ? _a : []; };
@@ -17,6 +17,8 @@ export var SystemVariables;
17
17
  SystemVariables["RANDOM_INPUT"] = "random_input";
18
18
  /** Evaluate a math expression. Example: {{math={{var1}}+{{var2}}}}. Use as {{math}}. */
19
19
  SystemVariables["MATH"] = "math";
20
+ /** Evaluate a JavaScript expression in a sandbox. Example: {{js=10 * {{var1}}}}. Use as {{js}}. */
21
+ SystemVariables["JS"] = "js";
20
22
  /** Compare two values. Example: {{compare={{var1}},>,{{var2}}}}. Use as {{compare}}. */
21
23
  SystemVariables["COMPARE"] = "compare";
22
24
  /** Round a value to decimal places. Example: {{round={{math={{var1}}/{{var2}}}},2}}. Use as {{round}}. */
@@ -1378,6 +1380,7 @@ export const AllVariables = {
1378
1380
  'random',
1379
1381
  'random_input',
1380
1382
  'math',
1383
+ 'js',
1381
1384
  'compare',
1382
1385
  'round',
1383
1386
  'if',
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export { LumiaVariationConditions, LumiaVariationCurrency, VariationCurrencySymb
3
3
  export { ILumiaSendPack, ILumiaEvent, ILumiaEventChatCommandBody, ILumiaEventChatBody, ILumiaEventAlertBody, ILumiaEventStateBody, ILumiaLight, LumiaIntegrations, LumiaEventTypes, } from './event.types';
4
4
  export { LumiaEventListTypes, LumiaMapAlertTypeToEventListType, AlertsToFilter, PlatformsToFilter, LumiaEventListTypeColors, getEventListCategoryColor } from './eventlist.types';
5
5
  export { SystemVariables, ReservedVariables, AllVariables, getAcceptedVariableName, getAcceptedVariableNames, type LumiaAcceptedVariable, type LumiaAcceptedVariableDefinition, type CountableVariableValue, type DonatorVariableValue, } from './variables.types';
6
+ export { VARIABLE_EXAMPLES, getVariableExamples, type VariableExample } from './variableExamples';
6
7
  export { formatCondition } from './helpers';
7
8
  export { EMULATE_EXAMPLE_AVATAR_URL, getExampleAlertVariableValue, buildExampleAlertVariables, coerceNumericAlertFields, aliasContentImageFromLegacy, isRedundantInputField, syncLinkedVariableFields, type InputFieldLike, } from './emulate.helpers';
8
9
  export { KickKicksData, KickKicksImageSelections } from './kick_kicks';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SongRequestPlaybackTarget = exports.SongRequestProvider = exports.SongRequestSource = exports.SongRequestStatus = exports.VIEWER_PROFILE_ACHIEVEMENTS = exports.YoutubeSuperstickerImageSelections = exports.YoutubeSuperstickersData = exports.TiktokGiftImageSelections = exports.TiktokGiftsData = exports.BLANK_OVERLAY_TEMPLATE_ID = exports.KickKicksImageSelections = exports.KickKicksData = exports.syncLinkedVariableFields = exports.isRedundantInputField = exports.aliasContentImageFromLegacy = exports.coerceNumericAlertFields = exports.buildExampleAlertVariables = exports.getExampleAlertVariableValue = exports.EMULATE_EXAMPLE_AVATAR_URL = exports.formatCondition = exports.getAcceptedVariableNames = exports.getAcceptedVariableName = exports.AllVariables = exports.ReservedVariables = exports.SystemVariables = exports.getEventListCategoryColor = exports.LumiaEventListTypeColors = exports.PlatformsToFilter = exports.AlertsToFilter = exports.LumiaMapAlertTypeToEventListType = exports.LumiaEventListTypes = exports.LumiaEventTypes = exports.LumiaIntegrations = exports.LumiaAlertConfigs = exports.LumiaRedemptionCurrencySymbol = exports.LumiaRedemptionCurrency = exports.VariationCurrencySymbol = exports.LumiaVariationCurrency = exports.LumiaVariationConditions = exports.LumiaActivityTestType = exports.LumiaActivityNoValueTypes = exports.LumiaActivityApiValueType = exports.LumiaActivityOriginTypes = exports.LumiaAlertFriendlyValues = exports.LumiaAlertValues = exports.LumiaExternalActivityCommandTypes = exports.LumiaActivityCommandTypes = exports.LumiaStreamingSites = void 0;
3
+ exports.SongRequestPlaybackTarget = exports.SongRequestProvider = exports.SongRequestSource = exports.SongRequestStatus = exports.VIEWER_PROFILE_ACHIEVEMENTS = exports.YoutubeSuperstickerImageSelections = exports.YoutubeSuperstickersData = exports.TiktokGiftImageSelections = exports.TiktokGiftsData = exports.BLANK_OVERLAY_TEMPLATE_ID = exports.KickKicksImageSelections = exports.KickKicksData = exports.syncLinkedVariableFields = exports.isRedundantInputField = exports.aliasContentImageFromLegacy = exports.coerceNumericAlertFields = exports.buildExampleAlertVariables = exports.getExampleAlertVariableValue = exports.EMULATE_EXAMPLE_AVATAR_URL = exports.formatCondition = exports.getVariableExamples = exports.VARIABLE_EXAMPLES = exports.getAcceptedVariableNames = exports.getAcceptedVariableName = exports.AllVariables = exports.ReservedVariables = exports.SystemVariables = exports.getEventListCategoryColor = exports.LumiaEventListTypeColors = exports.PlatformsToFilter = exports.AlertsToFilter = exports.LumiaMapAlertTypeToEventListType = exports.LumiaEventListTypes = exports.LumiaEventTypes = exports.LumiaIntegrations = exports.LumiaAlertConfigs = exports.LumiaRedemptionCurrencySymbol = exports.LumiaRedemptionCurrency = exports.VariationCurrencySymbol = exports.LumiaVariationCurrency = exports.LumiaVariationConditions = exports.LumiaActivityTestType = exports.LumiaActivityNoValueTypes = exports.LumiaActivityApiValueType = exports.LumiaActivityOriginTypes = exports.LumiaAlertFriendlyValues = exports.LumiaAlertValues = exports.LumiaExternalActivityCommandTypes = exports.LumiaActivityCommandTypes = exports.LumiaStreamingSites = void 0;
4
4
  var activity_types_1 = require("./activity.types");
5
5
  Object.defineProperty(exports, "LumiaStreamingSites", { enumerable: true, get: function () { return activity_types_1.LumiaStreamingSites; } });
6
6
  Object.defineProperty(exports, "LumiaActivityCommandTypes", { enumerable: true, get: function () { return activity_types_1.LumiaActivityCommandTypes; } });
@@ -34,6 +34,9 @@ Object.defineProperty(exports, "ReservedVariables", { enumerable: true, get: fun
34
34
  Object.defineProperty(exports, "AllVariables", { enumerable: true, get: function () { return variables_types_1.AllVariables; } });
35
35
  Object.defineProperty(exports, "getAcceptedVariableName", { enumerable: true, get: function () { return variables_types_1.getAcceptedVariableName; } });
36
36
  Object.defineProperty(exports, "getAcceptedVariableNames", { enumerable: true, get: function () { return variables_types_1.getAcceptedVariableNames; } });
37
+ var variableExamples_1 = require("./variableExamples");
38
+ Object.defineProperty(exports, "VARIABLE_EXAMPLES", { enumerable: true, get: function () { return variableExamples_1.VARIABLE_EXAMPLES; } });
39
+ Object.defineProperty(exports, "getVariableExamples", { enumerable: true, get: function () { return variableExamples_1.getVariableExamples; } });
37
40
  var helpers_1 = require("./helpers");
38
41
  Object.defineProperty(exports, "formatCondition", { enumerable: true, get: function () { return helpers_1.formatCondition; } });
39
42
  var emulate_helpers_1 = require("./emulate.helpers");
@@ -0,0 +1,6 @@
1
+ export type VariableExample = {
2
+ label: string;
3
+ snippet: string;
4
+ };
5
+ export declare const VARIABLE_EXAMPLES: Record<string, VariableExample[]>;
6
+ export declare const getVariableExamples: (variableName: string) => VariableExample[];
@@ -0,0 +1,346 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVariableExamples = exports.VARIABLE_EXAMPLES = void 0;
4
+ // English-only example use-cases for function variables, surfaced when a
5
+ // function variable is expanded in a variable picker. Keyed by variable name;
6
+ // the snippet is the inner token (what goes between {{ }}). Shared across apps —
7
+ // the UI layer receives this map and renders it. Only list a variable here when
8
+ // it has 2+ genuinely distinct examples (different args/capability, or a
9
+ // discoverable option set) — a single example just duplicates the default
10
+ // insert.
11
+ //
12
+ // All snippets below are verified against the VariablesManager implementations.
13
+ // Two gotchas the resolver imposes: function args split on commas, so a regex
14
+ // or date pattern that contains a comma must be wrapped in quotes; and a literal
15
+ // backslash in a pattern is written `\\` in this source so the runtime string is
16
+ // a single backslash.
17
+ exports.VARIABLE_EXAMPLES = {
18
+ read_url: [
19
+ { label: 'Fetch a plain-text endpoint', snippet: 'read_url=https://api.lumiastream.com/api/url-test' },
20
+ { label: 'Pull one field out of a JSON response', snippet: 'read_url=https://api.example.com/user,data.name' },
21
+ { label: 'URL containing commas (wrap in quotes)', snippet: 'read_url="https://api.example.com/?ids=1,2,3",result' },
22
+ ],
23
+ selection: [
24
+ { label: 'Validate a vote (chatter types one)', snippet: 'selection=red,blue,green' },
25
+ { label: 'Yes / no / maybe gate', snippet: 'selection=yes,no,maybe' },
26
+ { label: 'Pick a side', snippet: 'selection=heads,tails' },
27
+ ],
28
+ random: [
29
+ { label: 'Number between 1 and 100', snippet: 'random=1,100' },
30
+ { label: 'Dice roll (1–6)', snippet: 'random=1,6' },
31
+ { label: 'Allow negative numbers', snippet: 'random=-10,10' },
32
+ ],
33
+ random_input: [
34
+ { label: 'Magic 8-ball answer', snippet: 'random_input=Yes,No,Maybe,Ask again later,Definitely' },
35
+ { label: 'Decide what to play', snippet: 'random_input=Minecraft,Valorant,Just Chatting' },
36
+ { label: 'Coin flip', snippet: 'random_input=Heads,Tails' },
37
+ { label: 'Random hype message', snippet: "random_input=Let's go!,Insane!,GG" },
38
+ ],
39
+ random_inputs: [
40
+ { label: 'Random compliment', snippet: "random_inputs=You're awesome!,Great vibes!,Legend!" },
41
+ { label: 'Pick a random viewer to shout out', snippet: 'random_inputs={{username}},{{last_subscriber}},{{last_follower}}' },
42
+ { label: 'Random dare', snippet: 'random_inputs=No swearing for 5 min,Use a funny voice,Do a dance' },
43
+ ],
44
+ math: [
45
+ { label: 'Add two variables', snippet: 'math={{twitch_total_follower_count}}+{{kick_total_follower_count}}' },
46
+ { label: 'Percentage of a goal', snippet: 'math={{current}}/{{goal}}*100' },
47
+ { label: 'Group with parentheses', snippet: 'math=({{var1}}+{{var2}})/2' },
48
+ { label: 'Even/odd via modulo', snippet: 'math={{arg=1}}%2' },
49
+ { label: 'Exponent (square)', snippet: 'math={{arg=1}}^2' },
50
+ ],
51
+ js: [
52
+ { label: 'Celsius → Fahrenheit', snippet: 'js={{arg=1}} * 9 / 5 + 32' },
53
+ { label: 'Round to 2 decimals', snippet: 'js=({{arg=1}} / 3).toFixed(2)' },
54
+ { label: 'Adult / minor check (ternary)', snippet: "js={{arg=1}} >= 18 ? 'adult' : 'minor'" },
55
+ { label: 'Random dice roll 1–6', snippet: 'js=Math.floor(Math.random() * 6) + 1' },
56
+ { label: 'Clamp between 0 and 100', snippet: 'js=Math.min(100, Math.max(0, {{arg=1}}))' },
57
+ { label: "Uppercase the caller's name", snippet: "js='{{username}}'.toUpperCase()" },
58
+ { label: 'Comma-format a big number', snippet: 'js=Number({{twitch_total_follower_count}}).toLocaleString()' },
59
+ { label: 'First word of the message', snippet: "js='{{message}}'.split(' ')[0]" },
60
+ ],
61
+ compare: [
62
+ { label: 'Greater than', snippet: 'compare={{var1}},>,{{var2}}' },
63
+ { label: 'At least 100 viewers', snippet: 'compare={{twitch_current_viewer_count}},>=,100' },
64
+ { label: 'Equality check', snippet: 'compare={{platform}},==,twitch' },
65
+ { label: 'Not equal to a value', snippet: 'compare={{game}},!=,Just Chatting' },
66
+ ],
67
+ round: [
68
+ { label: 'Round a division to 2 decimals', snippet: 'round={{math={{var1}}/{{var2}}}},2' },
69
+ { label: 'Round to a whole number', snippet: 'round={{math={{var1}}/{{var2}}}},0' },
70
+ { label: 'Goal percentage to 1 decimal', snippet: 'round={{math={{current}}/{{goal}}*100}},1' },
71
+ ],
72
+ if: [
73
+ { label: 'High / low from a comparison', snippet: 'if={{compare={{var1}},>,10}},high,low' },
74
+ { label: 'Badge only for mods', snippet: 'if={{user_has_role=mod}},🛡️,' },
75
+ { label: 'Sub vs viewer', snippet: 'if={{user_has_role=subscriber}},sub,viewer' },
76
+ { label: 'Branch on a range', snippet: 'if={{between={{viewers}},1,10}},cozy,packed' },
77
+ ],
78
+ coalesce: [
79
+ { label: 'Display name with fallback', snippet: 'coalesce={{display_name}},{{username}},Anonymous' },
80
+ { label: "First connected platform's title", snippet: 'coalesce={{twitch_channel_title}},{{kick_channel_title}}' },
81
+ { label: 'Typed target, else the caller', snippet: 'coalesce={{arg=1}},{{username}}' },
82
+ ],
83
+ between: [
84
+ { label: 'Check a value is in range', snippet: 'between={{var1}},10,50' },
85
+ { label: 'Gate on viewer count', snippet: 'between={{twitch_current_viewer_count}},1,100' },
86
+ ],
87
+ min: [
88
+ { label: 'Smallest of several values', snippet: 'min={{v1}},{{v2}},100' },
89
+ { label: 'Cap a value at 100', snippet: 'min={{score}},100' },
90
+ ],
91
+ max: [
92
+ { label: 'Largest of several values', snippet: 'max={{v1}},{{v2}},0' },
93
+ { label: 'Floor a value at 0', snippet: 'max={{score}},0' },
94
+ ],
95
+ regex_extract: [
96
+ { label: 'First number in the message', snippet: 'regex_extract={{message}},([0-9]+),1' },
97
+ { label: 'First word in the message', snippet: 'regex_extract={{message}},(\\w+),1' },
98
+ { label: 'A @mention', snippet: 'regex_extract={{message}},@(\\w+),1' },
99
+ { label: 'A hashtag (whole match, group 0)', snippet: 'regex_extract={{message}},#\\w+,0' },
100
+ { label: 'Comma in pattern → quote it', snippet: 'regex_extract={{message}},"(\\d{2,4})",1' },
101
+ ],
102
+ replace: [
103
+ { label: 'Censor a word', snippet: 'replace={{message}},badword,***' },
104
+ { label: 'Case-insensitive regex censor', snippet: 'replace={{message}},/badword/gi,***' },
105
+ { label: 'Strip everything but digits', snippet: 'replace={{message}},/[^0-9]/g,' },
106
+ { label: 'Swap text', snippet: 'replace={{message}},hello,hi' },
107
+ { label: 'Remove a word', snippet: 'replace={{message}},spoiler,' },
108
+ ],
109
+ format_date: [
110
+ { label: 'Date and time (default)', snippet: 'format_date={{session_start_date}},MM/DD/YYYY hh:mm A' },
111
+ { label: 'ISO date', snippet: 'format_date={{session_start_date}},YYYY-MM-DD' },
112
+ { label: '12-hour clock', snippet: 'format_date={{session_start_date}},h:mm A' },
113
+ { label: '24-hour clock', snippet: 'format_date={{session_start_date}},HH:mm' },
114
+ { label: 'Weekday name', snippet: 'format_date={{session_start_date}},dddd' },
115
+ { label: 'Month and ordinal day', snippet: 'format_date={{session_start_date}},MMMM Do' },
116
+ { label: 'Short day + time', snippet: 'format_date={{session_start_date}},ddd h:mm A' },
117
+ { label: 'Friendly (quote patterns with commas)', snippet: 'format_date={{session_start_date}},"dddd, MMM Do YYYY"' },
118
+ { label: 'Full date with comma (quoted)', snippet: 'format_date={{session_start_date}},"MMM D, YYYY"' },
119
+ ],
120
+ time_since: [
121
+ { label: 'Since you went live', snippet: 'time_since={{session_start_date}}' },
122
+ { label: 'Since a follow', snippet: 'time_since={{follow_time}}' },
123
+ { label: 'Since a fixed date', snippet: 'time_since=2020-01-01' },
124
+ { label: 'Since a variable date', snippet: 'time_since={{anniversary_date}}' },
125
+ ],
126
+ time_until: [
127
+ { label: 'Countdown to a date', snippet: 'time_until=2026-12-25T00:00:00Z' },
128
+ { label: 'Until the next Twitch ad', snippet: 'time_until={{next_ad_starts_date}}' },
129
+ { label: 'Until the poll closes', snippet: 'time_until={{poll_ends_at}}' },
130
+ { label: 'Until the prediction closes', snippet: 'time_until={{prediction_ends_at}}' },
131
+ { label: 'Countdown to a variable date', snippet: 'time_until={{event_date}}' },
132
+ ],
133
+ counter: [
134
+ { label: 'Add one (e.g. deaths)', snippet: 'counter=mydeaths' },
135
+ { label: 'Add five', snippet: 'counter=mydeaths,+5' },
136
+ { label: 'Subtract one', snippet: 'counter=mydeaths,-1' },
137
+ { label: 'Set to a value', snippet: 'counter=mydeaths,=0' },
138
+ { label: 'Double it', snippet: 'counter=score,*2' },
139
+ ],
140
+ save_local: [
141
+ { label: 'Save a high score', snippet: 'save_local=highscore,100' },
142
+ { label: 'Save a flag', snippet: 'save_local=intro_done,true' },
143
+ ],
144
+ load_local: [
145
+ { label: 'Load a saved value', snippet: 'load_local=highscore' },
146
+ { label: 'Load with a fallback', snippet: 'load_local=highscore,0' },
147
+ ],
148
+ arg: [
149
+ { label: 'First word after the command', snippet: 'arg=1' },
150
+ { label: 'Second word', snippet: 'arg=2' },
151
+ { label: 'Require alphanumeric', snippet: 'arg=1,word' },
152
+ { label: 'Require an emote', snippet: 'arg=3,emote' },
153
+ ],
154
+ get_var_from_msg: [
155
+ { label: 'Read name= from the message', snippet: 'get_var_from_msg=name' },
156
+ { label: 'Read age= from the message', snippet: 'get_var_from_msg=age' },
157
+ { label: 'Read a quoted value (msg color="hot pink")', snippet: 'get_var_from_msg=color' },
158
+ ],
159
+ lookup_user: [
160
+ { label: "Another user's display name", snippet: 'lookup_user=lumi' },
161
+ { label: 'The target the chatter typed', snippet: 'lookup_user={{arg=1}}' },
162
+ { label: 'Avatar on a platform', snippet: 'lookup_user=lumi,twitch,avatar' },
163
+ ],
164
+ random_chatter: [
165
+ { label: 'Any recent chatter', snippet: 'random_chatter' },
166
+ { label: 'Twitch only', snippet: 'random_chatter=twitch' },
167
+ { label: 'Across all platforms', snippet: 'random_chatter=all' },
168
+ ],
169
+ user_has_role: [
170
+ { label: 'Mods only', snippet: 'user_has_role=mod' },
171
+ { label: 'VIPs only', snippet: 'user_has_role=vip' },
172
+ { label: 'Subscribers only', snippet: 'user_has_role=subscriber' },
173
+ ],
174
+ sum_variables: [
175
+ { label: 'Combined followers', snippet: 'sum_variables=twitch_total_follower_count,kick_total_follower_count' },
176
+ { label: 'Combined subscribers', snippet: 'sum_variables=twitch_total_subscriber_count,kick_total_subscriber_count' },
177
+ ],
178
+ offset_count: [
179
+ { label: 'Pad a follower count', snippet: 'offset_count=twitch_total_follower_count,10' },
180
+ { label: 'Subtract from a count', snippet: 'offset_count=twitch_total_follower_count,-5' },
181
+ ],
182
+ convert_color_to_hex: [
183
+ { label: 'Green to hex', snippet: 'convert_color_to_hex=green' },
184
+ { label: 'Hot pink to hex', snippet: 'convert_color_to_hex=hotpink' },
185
+ { label: 'From a chat color name', snippet: 'convert_color_to_hex={{message}}' },
186
+ ],
187
+ get_random_file_from_folder: [
188
+ { label: 'Random sound for a !sfx command', snippet: 'get_random_file_from_folder=C:/Sounds' },
189
+ { label: 'Random meme image', snippet: 'get_random_file_from_folder=C:/Memes' },
190
+ { label: 'Random clip', snippet: 'get_random_file_from_folder=C:/Clips' },
191
+ ],
192
+ get_latest_file_from_folder: [
193
+ { label: 'Latest clip', snippet: 'get_latest_file_from_folder=C:/Clips' },
194
+ { label: 'Latest screenshot', snippet: 'get_latest_file_from_folder=C:/Screenshots' },
195
+ { label: 'Latest recording', snippet: 'get_latest_file_from_folder=C:/Recordings' },
196
+ ],
197
+ screenshot: [
198
+ { label: 'Primary monitor', snippet: 'screenshot' },
199
+ { label: 'Second monitor', snippet: 'screenshot=2' },
200
+ ],
201
+ overlay_screenshot: [
202
+ { label: 'Capture an overlay', snippet: 'overlay_screenshot=Overlay 1' },
203
+ { label: 'Thermal filter', snippet: 'overlay_screenshot=Overlay 1,thermal' },
204
+ { label: 'Grayscale filter', snippet: 'overlay_screenshot=Overlay 1,grayscale' },
205
+ { label: 'Inverted colors', snippet: 'overlay_screenshot=Overlay 1,invert' },
206
+ { label: 'Black & white (binary)', snippet: 'overlay_screenshot=Overlay 1,binary' },
207
+ ],
208
+ obs_screenshot: [
209
+ { label: 'Current OBS scene', snippet: 'obs_screenshot' },
210
+ { label: 'A specific scene', snippet: 'obs_screenshot=Scene 1' },
211
+ ],
212
+ obs_replay: [
213
+ { label: 'Save the replay buffer', snippet: 'obs_replay' },
214
+ { label: 'Wait 5s before saving', snippet: 'obs_replay=5' },
215
+ ],
216
+ obs_vertical_replay: [
217
+ { label: 'Save the vertical buffer', snippet: 'obs_vertical_replay' },
218
+ { label: 'Wait 5s before saving', snippet: 'obs_vertical_replay=5' },
219
+ ],
220
+ twitch_followage: [
221
+ { label: 'Your own followage', snippet: 'twitch_followage' },
222
+ { label: "Another user's followage", snippet: 'twitch_followage={{username}}' },
223
+ ],
224
+ twitch_accountage: [
225
+ { label: 'Your account age', snippet: 'twitch_accountage' },
226
+ { label: "Another user's account age", snippet: 'twitch_accountage={{username}}' },
227
+ ],
228
+ account_age: [
229
+ { label: "Caller's account age", snippet: 'account_age={{username}}' },
230
+ { label: 'On a specific platform', snippet: 'account_age={{username}},twitch' },
231
+ ],
232
+ get_avatar: [
233
+ { label: "Caller's avatar", snippet: 'get_avatar={{username}}' },
234
+ { label: 'A named user', snippet: 'get_avatar=lumi' },
235
+ { label: 'On a specific platform', snippet: 'get_avatar=lumi,twitch' },
236
+ ],
237
+ vanish: [
238
+ { label: 'Clear your own messages', snippet: 'vanish={{username}}' },
239
+ { label: 'Clear the target user', snippet: 'vanish={{arg=1}}' },
240
+ ],
241
+ get_user_loyalty_points: [
242
+ { label: 'Points for a typed user', snippet: 'get_user_loyalty_points={{message}}' },
243
+ { label: 'User on a platform', snippet: 'get_user_loyalty_points={{username}},{{platform}}' },
244
+ ],
245
+ user_watchtime: [
246
+ { label: "Caller's watchtime", snippet: 'user_watchtime={{username}}' },
247
+ { label: 'A named user/platform', snippet: 'user_watchtime=lumi,twitch' },
248
+ ],
249
+ toggle_automation: [
250
+ { label: 'Enable an automation', snippet: 'toggle_automation=My Automation,true' },
251
+ { label: 'Disable an automation', snippet: 'toggle_automation=My Automation,false' },
252
+ ],
253
+ translate: [
254
+ { label: 'Auto-translate to your language', snippet: 'translate={{message}}' },
255
+ { label: 'To Spanish', snippet: 'translate={{message}}|es' },
256
+ { label: 'To French', snippet: 'translate={{message}}|fr' },
257
+ { label: 'To German', snippet: 'translate={{message}}|de' },
258
+ { label: 'To Japanese', snippet: 'translate={{message}}|ja' },
259
+ { label: 'To Korean', snippet: 'translate={{message}}|ko' },
260
+ { label: 'To Portuguese', snippet: 'translate={{message}}|pt' },
261
+ { label: 'To English', snippet: 'translate={{message}}|en' },
262
+ ],
263
+ ai_prompt: [
264
+ { label: "Answer the chatter's question", snippet: 'ai_prompt={{message}}' },
265
+ { label: 'Fun fact about a topic', snippet: 'ai_prompt=Give a short fun fact about {{message}}' },
266
+ { label: 'Playful roast', snippet: 'ai_prompt=Roast {{username}} in one playful sentence' },
267
+ { label: 'Summarize in one line', snippet: 'ai_prompt=Summarize this in one sentence: {{message}}' },
268
+ { label: 'Keep context with a thread', snippet: 'ai_prompt={{message}}|{{username}}' },
269
+ { label: 'With a thread and model', snippet: 'ai_prompt=Make a funny quote|thread_name|gpt-5-mini' },
270
+ ],
271
+ weather: [
272
+ { label: 'By city', snippet: 'weather=Seattle' },
273
+ { label: 'City, state', snippet: 'weather=New York, NY' },
274
+ { label: 'By coordinates', snippet: 'weather=47.6,-122.3' },
275
+ ],
276
+ time: [
277
+ { label: 'Your local time', snippet: 'time' },
278
+ { label: 'Eastern (New York)', snippet: 'time=America/New_York' },
279
+ { label: 'Central (Chicago)', snippet: 'time=America/Chicago' },
280
+ { label: 'Mountain (Denver)', snippet: 'time=America/Denver' },
281
+ { label: 'Pacific (Los Angeles)', snippet: 'time=America/Los_Angeles' },
282
+ { label: 'UK (London)', snippet: 'time=Europe/London' },
283
+ { label: 'Central Europe (Paris)', snippet: 'time=Europe/Paris' },
284
+ { label: 'Japan (Tokyo)', snippet: 'time=Asia/Tokyo' },
285
+ { label: 'Australia (Sydney)', snippet: 'time=Australia/Sydney' },
286
+ { label: 'UTC', snippet: 'time=Etc/UTC' },
287
+ ],
288
+ today: [
289
+ { label: "Today's date (local)", snippet: 'today' },
290
+ { label: 'Date in Eastern time', snippet: 'today=America/New_York' },
291
+ { label: 'Date in the UK', snippet: 'today=Europe/London' },
292
+ { label: 'Date in Japan', snippet: 'today=Asia/Tokyo' },
293
+ { label: 'Date in UTC', snippet: 'today=Etc/UTC' },
294
+ ],
295
+ lookup_user_game: [
296
+ { label: "A typed user's category", snippet: 'lookup_user_game={{arg=1}}' },
297
+ { label: 'On a specific platform', snippet: 'lookup_user_game=someuser,twitch' },
298
+ ],
299
+ lookup_user_title: [
300
+ { label: "A typed user's title", snippet: 'lookup_user_title={{arg=1}}' },
301
+ { label: 'On a specific platform', snippet: 'lookup_user_title=someuser,twitch' },
302
+ ],
303
+ channel_emotes: [
304
+ { label: "Caller's platform emotes", snippet: 'channel_emotes' },
305
+ { label: 'A specific platform', snippet: 'channel_emotes=twitch' },
306
+ ],
307
+ viewer_profile_summary: [
308
+ { label: "Caller's profile", snippet: 'viewer_profile_summary={{username}}' },
309
+ { label: 'A typed user', snippet: 'viewer_profile_summary={{arg=1}}' },
310
+ ],
311
+ filesay: [
312
+ { label: 'Post each line of a URL', snippet: 'filesay=https://example.com/list.txt' },
313
+ { label: 'Post each line of a local file', snippet: 'filesay=C:/path/list.txt' },
314
+ ],
315
+ set_alerts_paused: [
316
+ { label: 'Pause alerts', snippet: 'set_alerts_paused=true' },
317
+ { label: 'Resume alerts', snippet: 'set_alerts_paused=false' },
318
+ ],
319
+ set_sfx_muted: [
320
+ { label: 'Mute sound effects', snippet: 'set_sfx_muted=true' },
321
+ { label: 'Unmute sound effects', snippet: 'set_sfx_muted=false' },
322
+ ],
323
+ loyalty_top: [
324
+ { label: 'Top 3 users', snippet: 'loyalty_top=3' },
325
+ { label: 'Top 5 users', snippet: 'loyalty_top=5' },
326
+ { label: 'Top 10 users', snippet: 'loyalty_top=10' },
327
+ ],
328
+ add_points: [
329
+ { label: 'Give a user points', snippet: 'add_points={{username}},100' },
330
+ { label: 'Give the target points', snippet: 'add_points={{arg=1}},50' },
331
+ ],
332
+ set_points: [
333
+ { label: "Set a user's points", snippet: 'set_points={{username}},100' },
334
+ { label: 'Reset a user to zero', snippet: 'set_points={{arg=1}},0' },
335
+ ],
336
+ give_points: [
337
+ { label: 'Transfer to the typed user', snippet: 'give_points={{username}},{{touser}},100' },
338
+ { label: 'Transfer a fixed amount', snippet: 'give_points={{username}},{{arg=1}},50' },
339
+ ],
340
+ add_chatbot_command: [
341
+ { label: 'Create a static command', snippet: 'add_chatbot_command=hi,Hello there!' },
342
+ { label: 'Create one using a variable', snippet: 'add_chatbot_command=so,Check out {{arg=1}}' },
343
+ ],
344
+ };
345
+ const getVariableExamples = (variableName) => { var _a; return (_a = exports.VARIABLE_EXAMPLES[variableName]) !== null && _a !== void 0 ? _a : []; };
346
+ exports.getVariableExamples = getVariableExamples;
@@ -28,6 +28,8 @@ export declare enum SystemVariables {
28
28
  RANDOM_INPUT = "random_input",
29
29
  /** Evaluate a math expression. Example: {{math={{var1}}+{{var2}}}}. Use as {{math}}. */
30
30
  MATH = "math",
31
+ /** Evaluate a JavaScript expression in a sandbox. Example: {{js=10 * {{var1}}}}. Use as {{js}}. */
32
+ JS = "js",
31
33
  /** Compare two values. Example: {{compare={{var1}},>,{{var2}}}}. Use as {{compare}}. */
32
34
  COMPARE = "compare",
33
35
  /** Round a value to decimal places. Example: {{round={{math={{var1}}/{{var2}}}},2}}. Use as {{round}}. */
@@ -22,6 +22,8 @@ var SystemVariables;
22
22
  SystemVariables["RANDOM_INPUT"] = "random_input";
23
23
  /** Evaluate a math expression. Example: {{math={{var1}}+{{var2}}}}. Use as {{math}}. */
24
24
  SystemVariables["MATH"] = "math";
25
+ /** Evaluate a JavaScript expression in a sandbox. Example: {{js=10 * {{var1}}}}. Use as {{js}}. */
26
+ SystemVariables["JS"] = "js";
25
27
  /** Compare two values. Example: {{compare={{var1}},>,{{var2}}}}. Use as {{compare}}. */
26
28
  SystemVariables["COMPARE"] = "compare";
27
29
  /** Round a value to decimal places. Example: {{round={{math={{var1}}/{{var2}}}},2}}. Use as {{round}}. */
@@ -1383,6 +1385,7 @@ exports.AllVariables = {
1383
1385
  'random',
1384
1386
  'random_input',
1385
1387
  'math',
1388
+ 'js',
1386
1389
  'compare',
1387
1390
  'round',
1388
1391
  'if',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/lumia-types",
3
- "version": "3.8.0",
3
+ "version": "3.8.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -19,6 +19,7 @@
19
19
  "./dist/custom-overlays.d.ts": "./dist/custom-overlays.d.ts",
20
20
  "./dist/custom-overlays/*": "./dist/custom-overlays/*",
21
21
  "./dist/custom-code/*": "./dist/custom-code/*",
22
+ "./dist/chatbot/*": "./dist/chatbot/*",
22
23
  "./dist/*": {
23
24
  "types": "./dist/*.d.ts",
24
25
  "import": "./dist/esm/*.js",
@@ -30,7 +31,7 @@
30
31
  "/dist"
31
32
  ],
32
33
  "scripts": {
33
- "build": "npm run clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json && npm run sync:custom-overlays && npm run sync:custom-code && node ./scripts/finalize-esm-build.mjs",
34
+ "build": "npm run clean && tsc -p tsconfig.json && tsc -p tsconfig.esm.json && npm run sync:custom-overlays && npm run sync:custom-code && npm run sync:chatbot && node ./scripts/finalize-esm-build.mjs",
34
35
  "watch": "node ./scripts/watch.mjs",
35
36
  "test": "node ./scripts/check-eventlist-alert-invariant.mjs",
36
37
  "prepublishOnly": "npm run build && npm test",
@@ -39,6 +40,7 @@
39
40
  "clean": "rm -rf dist",
40
41
  "sync:custom-overlays": "node ./scripts/sync-custom-overlay-types.mjs",
41
42
  "sync:custom-code": "node ./scripts/sync-custom-code-docs.mjs",
43
+ "sync:chatbot": "node ./scripts/sync-chatbot-docs.mjs",
42
44
  "postpublish": "npm cache clean --force && node ./scripts/postpublish-install.mjs"
43
45
  },
44
46
  "keywords": [],