@lumiastream/ui 0.2.8-alpha.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/utils.js ADDED
@@ -0,0 +1,830 @@
1
+ // src/utils/variableCompletionOptions.ts
2
+ function variableCompletionOptions(context, variables) {
3
+ const before = context.state.sliceDoc(0, context.pos);
4
+ const after = context.state.sliceDoc(context.pos, context.pos + 2);
5
+ const wordMatch = /(\w*)$/.exec(before);
6
+ if (!wordMatch) return null;
7
+ const word = wordMatch[1];
8
+ const from = context.pos - word.length;
9
+ const openCount = before.match(/({+)\s*\w*$/)?.[1].length ?? 0;
10
+ const closeCount = after.startsWith("}}") ? 2 : after.startsWith("}") ? 1 : 0;
11
+ const openNeeded = Math.max(0, 2 - openCount);
12
+ const closeNeeded = Math.max(0, 2 - closeCount);
13
+ const braceWrap = (v) => `${"{".repeat(openNeeded)}${v}${"}".repeat(closeNeeded)}`;
14
+ const options = [
15
+ ...variables.map((variable) => ({
16
+ label: variable.shouldWrap ? braceWrap(variable.label) : variable.label,
17
+ displayLabel: variable.displayLabel,
18
+ type: variable.type,
19
+ info: variable.info
20
+ }))
21
+ ];
22
+ return {
23
+ from,
24
+ options,
25
+ validFor: /^[\w$]*$/
26
+ // keep list open while typing identifiers
27
+ };
28
+ }
29
+
30
+ // src/utils/codeMirrorlinterOptions.ts
31
+ import globals from "globals";
32
+ var browserGlobals = Object.fromEntries(
33
+ Object.entries(globals.browser).map(([key, value]) => [
34
+ key,
35
+ value ? "writable" : "readonly"
36
+ ])
37
+ );
38
+ var jsOptions = {
39
+ languageOptions: {
40
+ ecmaVersion: 2025,
41
+ sourceType: "module",
42
+ globals: {
43
+ ...browserGlobals,
44
+ // DOM / utility
45
+ _: "readonly",
46
+ moment: "readonly",
47
+ dayjs: "readonly",
48
+ axios: "readonly",
49
+ // Data-viz / animation
50
+ d3: "readonly",
51
+ Chart: "readonly",
52
+ anime: "readonly",
53
+ gsap: "readonly",
54
+ TweenMax: "readonly",
55
+ TweenLite: "readonly",
56
+ // UI frameworks
57
+ Vue: "readonly",
58
+ angular: "readonly",
59
+ ReactDOM: "readonly",
60
+ $: "readonly",
61
+ jQuery: "readonly",
62
+ React: "readonly",
63
+ // Canvas / WebGL
64
+ BABYLON: "readonly",
65
+ createjs: "readonly",
66
+ p5: "readonly",
67
+ fabric: "readonly",
68
+ THREE: "readonly",
69
+ Phaser: "readonly",
70
+ Pixi: "readonly",
71
+ PixiJS: "readonly",
72
+ // Maps
73
+ L: "readonly",
74
+ mapboxgl: "readonly",
75
+ // Realtime / networking
76
+ io: "readonly",
77
+ Pusher: "readonly",
78
+ Echo: "readonly"
79
+ }
80
+ },
81
+ rules: {
82
+ // Variables
83
+ "no-unused-vars": "warn",
84
+ "no-undef": "error",
85
+ "no-use-before-define": ["error", { functions: false, classes: true }],
86
+ "no-shadow": "warn",
87
+ "no-redeclare": "error",
88
+ // Best Practices
89
+ eqeqeq: ["warn", "always", { null: "ignore" }],
90
+ "no-eval": "error",
91
+ "no-implied-eval": "error",
92
+ "no-new-func": "error",
93
+ "no-return-await": "warn",
94
+ "require-await": "warn",
95
+ "no-throw-literal": "error",
96
+ "no-unmodified-loop-condition": "error",
97
+ "no-constant-condition": "warn",
98
+ "no-debugger": "error",
99
+ // Code Quality
100
+ "no-duplicate-case": "error",
101
+ "no-empty": ["error", { allowEmptyCatch: true }],
102
+ "no-empty-pattern": "error",
103
+ "no-fallthrough": "error",
104
+ "no-sparse-arrays": "error",
105
+ "no-unreachable": "error",
106
+ "use-isnan": "error",
107
+ "valid-typeof": "error",
108
+ // ES6+ Features
109
+ "prefer-const": "warn",
110
+ "no-var": "warn",
111
+ "prefer-template": "warn",
112
+ "no-useless-concat": "warn",
113
+ // Error Prevention
114
+ "no-cond-assign": "error",
115
+ "no-constant-binary-expression": "error",
116
+ "no-dupe-args": "error",
117
+ "no-dupe-keys": "error",
118
+ "no-duplicate-imports": "error",
119
+ "no-ex-assign": "error",
120
+ "no-func-assign": "error",
121
+ "no-import-assign": "error",
122
+ "no-irregular-whitespace": "error",
123
+ "no-misleading-character-class": "error",
124
+ "no-prototype-builtins": "warn",
125
+ "no-unexpected-multiline": "error",
126
+ // Security
127
+ "no-script-url": "error",
128
+ "no-unsafe-optional-chaining": "error",
129
+ // Performance
130
+ "no-loop-func": "warn",
131
+ "no-await-in-loop": "warn"
132
+ }
133
+ };
134
+ var globalOptions = {
135
+ overlay: {
136
+ Overlay: "readonly",
137
+ toast: "readonly"
138
+ },
139
+ code: {
140
+ done: "readonly",
141
+ log: "readonly",
142
+ addLog: "readonly",
143
+ showToast: "readonly",
144
+ getVariable: "readonly",
145
+ getAllVariables: "readonly",
146
+ setVariable: "readonly",
147
+ deleteVariable: "readonly",
148
+ getStore: "readonly",
149
+ getStoreItem: "readonly",
150
+ setStore: "readonly",
151
+ resetStore: "readonly",
152
+ getLights: "readonly",
153
+ sendColor: "readonly",
154
+ getCommands: "readonly",
155
+ getAllCommands: "readonly",
156
+ getApiOptions: "readonly",
157
+ callAlert: "readonly",
158
+ callCommand: "readonly",
159
+ callTwitchPoint: "readonly",
160
+ callTwitchExtension: "readonly",
161
+ callTrovoSpell: "readonly",
162
+ readFile: "readonly",
163
+ writeFile: "readonly",
164
+ hexToRgb: "readonly",
165
+ tts: "readonly",
166
+ chatbot: "readonly",
167
+ execShellCommand: "readonly",
168
+ playAudio: "readonly",
169
+ sendRawObsJson: "readonly",
170
+ setObsSceneItem: "readonly",
171
+ actions: "readonly",
172
+ delay: "readonly",
173
+ getToken: "readonly",
174
+ getClientId: "readonly",
175
+ overlayAlertTrigger: "readonly",
176
+ overlaySetVisibility: "readonly",
177
+ overlaySetLayerVisibility: "readonly",
178
+ overlaySetLayerPosition: "readonly",
179
+ overlaySetTextContent: "readonly",
180
+ overlaySetImageContent: "readonly",
181
+ overlaySetVideoContent: "readonly",
182
+ overlaySetAudioContent: "readonly",
183
+ overlaySetVolume: "readonly",
184
+ overlayPlayPauseMedia: "readonly",
185
+ overlaySendHfx: "readonly",
186
+ overlayTimer: "readonly",
187
+ overlayShoutout: "readonly",
188
+ overlaySendCustomContent: "readonly"
189
+ }
190
+ };
191
+ var codeMirrorlinterOptions = (type, language = "js") => {
192
+ if (type === "overlay") {
193
+ return {
194
+ ...jsOptions,
195
+ languageOptions: {
196
+ ...jsOptions.languageOptions,
197
+ globals: {
198
+ ...jsOptions.languageOptions.globals,
199
+ ...globalOptions.overlay
200
+ }
201
+ }
202
+ };
203
+ } else if (type === "code") {
204
+ return {
205
+ ...jsOptions,
206
+ languageOptions: {
207
+ ...jsOptions.languageOptions,
208
+ globals: {
209
+ ...jsOptions.languageOptions.globals,
210
+ ...globalOptions.code
211
+ }
212
+ }
213
+ };
214
+ }
215
+ return jsOptions;
216
+ };
217
+
218
+ // src/utils/chatMedia.ts
219
+ var URL_TOKEN_REGEX = /(?:https?:\/\/|www\.)[^\s<]+/gi;
220
+ var TRAILING_LINK_PUNCTUATION_REGEX = /[),.!?;:'"]+$/;
221
+ var MEDIA_IMAGE_REGEX = /\.(gif|png|jpe?g|webp|bmp|avif)(?:[?#].*)?$/i;
222
+ var MEDIA_VIDEO_REGEX = /\.(mp4|webm|mov|m4v)(?:[?#].*)?$/i;
223
+ var MEDIA_AUDIO_REGEX = /\.(mp3|wav|ogg|oga|aac|m4a|flac|opus)(?:[?#].*)?$/i;
224
+ var YOUTUBE_ID_REGEX = /^[a-zA-Z0-9_-]{6,}$/;
225
+ var SPOTIFY_EMBED_TYPES = /* @__PURE__ */ new Set(["track", "album", "playlist", "episode", "show", "artist"]);
226
+ var MEDIA_PREVIEW_USER_LEVEL_VALUES = ["streamer", "moderators", "vips", "tier3", "tier2", "subscribers", "regular", "follower", "anyone"];
227
+ var parseMessageLinks = (value) => {
228
+ if (!value) {
229
+ return [{ text: "" }];
230
+ }
231
+ const parts = [];
232
+ let cursor = 0;
233
+ for (const match of value.matchAll(URL_TOKEN_REGEX)) {
234
+ const rawToken = match[0];
235
+ if (!rawToken) {
236
+ continue;
237
+ }
238
+ const start = match.index ?? 0;
239
+ const end = start + rawToken.length;
240
+ if (start > cursor) {
241
+ parts.push({ text: value.slice(cursor, start) });
242
+ }
243
+ const trailing = rawToken.match(TRAILING_LINK_PUNCTUATION_REGEX)?.[0] ?? "";
244
+ const cleaned = trailing ? rawToken.slice(0, -trailing.length) : rawToken;
245
+ if (cleaned) {
246
+ const href = cleaned.startsWith("http://") || cleaned.startsWith("https://") ? cleaned : `https://${cleaned}`;
247
+ parts.push({ text: cleaned, url: href });
248
+ } else {
249
+ parts.push({ text: rawToken });
250
+ }
251
+ if (trailing) {
252
+ parts.push({ text: trailing });
253
+ }
254
+ cursor = end;
255
+ }
256
+ if (cursor < value.length) {
257
+ parts.push({ text: value.slice(cursor) });
258
+ }
259
+ return parts.length ? parts : [{ text: value }];
260
+ };
261
+ var tokenizeChatMessage = (value, options) => {
262
+ const hyperClickableLinks = options?.hyperClickableLinks !== false;
263
+ const previewMediaInChat = options?.previewMediaInChat !== false;
264
+ if (!value) {
265
+ return [{ type: "text", text: "" }];
266
+ }
267
+ if (!hyperClickableLinks && !previewMediaInChat) {
268
+ return [{ type: "text", text: value }];
269
+ }
270
+ const parts = parseMessageLinks(value);
271
+ const tokens = [];
272
+ for (const part of parts) {
273
+ if (!part.url) {
274
+ tokens.push({ type: "text", text: part.text });
275
+ continue;
276
+ }
277
+ const media = previewMediaInChat ? getMediaPreviewFromUrl(part.url) : null;
278
+ if (media) {
279
+ tokens.push({
280
+ type: "media",
281
+ text: part.text,
282
+ url: part.url,
283
+ media
284
+ });
285
+ continue;
286
+ }
287
+ if (hyperClickableLinks) {
288
+ tokens.push({
289
+ type: "link",
290
+ text: part.text,
291
+ url: part.url
292
+ });
293
+ continue;
294
+ }
295
+ tokens.push({ type: "text", text: part.text });
296
+ }
297
+ return tokens.length ? tokens : [{ type: "text", text: value }];
298
+ };
299
+ var buildChatMessageContent = ({
300
+ message,
301
+ replaceWord,
302
+ filteredWordsRegex,
303
+ emotesRaw,
304
+ emotesPack,
305
+ origin,
306
+ emoteParserType,
307
+ isCheer,
308
+ storeEmotes,
309
+ youtubeEmotes,
310
+ allowParserTypes
311
+ }) => {
312
+ let normalizedMessage = message ?? "";
313
+ if (filteredWordsRegex && typeof replaceWord === "string") {
314
+ normalizedMessage = normalizedMessage.replaceAll(filteredWordsRegex, replaceWord);
315
+ }
316
+ const arrFromMsg = Array.from(normalizedMessage);
317
+ const parserType = emoteParserType || origin;
318
+ const allowParserType = !allowParserTypes || allowParserTypes.includes(parserType ?? "");
319
+ const emotes = [];
320
+ const parsePluginEmotesRaw = (raw) => {
321
+ const text = raw?.trim?.();
322
+ if (!text || text[0] !== "[" && text[0] !== "{") {
323
+ return [];
324
+ }
325
+ let parsed;
326
+ try {
327
+ parsed = JSON.parse(text);
328
+ } catch {
329
+ return [];
330
+ }
331
+ const items = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.emotes) ? parsed.emotes : [];
332
+ if (!items.length) {
333
+ return [];
334
+ }
335
+ return items.map((item) => {
336
+ const rawStart = Number(item?.start);
337
+ const rawEnd = Number(item?.end);
338
+ const directUrls = Array.isArray(item?.urls) ? item.urls.filter((url2) => typeof url2 === "string" && url2) : [];
339
+ const url = typeof item?.url === "string" ? item.url : "";
340
+ const urls = directUrls.length ? directUrls : url ? [url] : [];
341
+ if (!urls.length || !Number.isFinite(rawStart) || !Number.isFinite(rawEnd)) {
342
+ return null;
343
+ }
344
+ let start = Math.max(0, Math.floor(rawStart));
345
+ let end = Math.min(Math.floor(rawEnd) + 1, arrFromMsg.length);
346
+ if (start > 0 && arrFromMsg[start - 1] === ":") {
347
+ start -= 1;
348
+ }
349
+ if (end < arrFromMsg.length && arrFromMsg[end] === ":") {
350
+ end += 1;
351
+ }
352
+ if (end <= start) {
353
+ return null;
354
+ }
355
+ return {
356
+ id: item?.id || `${start}-${end}`,
357
+ urls,
358
+ start,
359
+ end
360
+ };
361
+ }).filter(Boolean);
362
+ };
363
+ const pluginRawEmotes = emotesRaw ? parsePluginEmotesRaw(emotesRaw) : [];
364
+ if (pluginRawEmotes.length) {
365
+ emotes.push(...pluginRawEmotes);
366
+ }
367
+ if (allowParserType && emotesRaw && !pluginRawEmotes.length && parserType === "twitch") {
368
+ for (const emote of emotesRaw.split("/")) {
369
+ if (!emote) continue;
370
+ const [emoteId, indicies] = emote.split(":");
371
+ if (!emoteId || !indicies) continue;
372
+ for (const indexSet of indicies.split(",")) {
373
+ const [start, end] = indexSet.split("-");
374
+ if (start === void 0 || end === void 0) continue;
375
+ emotes.push({
376
+ id: emoteId,
377
+ urls: [
378
+ `https://static-cdn.jtvnw.net/emoticons/v2/${emoteId}/default/dark/1.0`,
379
+ `https://static-cdn.jtvnw.net/emoticons/v2/${emoteId}/default/dark/2.0`,
380
+ `https://static-cdn.jtvnw.net/emoticons/v2/${emoteId}/default/dark/3.0`
381
+ ],
382
+ start: Math.max(+start, 0),
383
+ end: Math.min(+end + 1, normalizedMessage.length)
384
+ });
385
+ }
386
+ }
387
+ } else if (allowParserType && emotesPack && parserType === "kick") {
388
+ const emotesPackRecord = emotesPack;
389
+ Object.keys(emotesPackRecord).forEach((emoteId) => {
390
+ emotesPackRecord[emoteId]?.locations?.forEach((location) => {
391
+ const [start, end] = location.split("-");
392
+ if (start === void 0 || end === void 0) return;
393
+ let url;
394
+ if (emotesPackRecord[emoteId]?.type === "emote") {
395
+ url = `https://files.kick.com/emotes/${emoteId}/fullsize`;
396
+ } else {
397
+ url = `https://dbxmjjzl5pc1g.cloudfront.net/1065f255-473b-4a4d-a383-1282aa1ab9d5/images/emojis/${emoteId}.png`;
398
+ }
399
+ if (url) {
400
+ emotes.push({
401
+ id: emoteId,
402
+ urls: [url],
403
+ start: Math.max(+start, 0),
404
+ end: Math.min(+end + 1, normalizedMessage.length)
405
+ });
406
+ }
407
+ });
408
+ });
409
+ } else if (allowParserType && emotesPack && parserType === "discord") {
410
+ const emotesPackRecord = emotesPack;
411
+ Object.keys(emotesPackRecord).forEach((emoteId) => {
412
+ emotesPackRecord[emoteId]?.locations?.forEach((location) => {
413
+ const [start, end] = location.split("-");
414
+ if (start === void 0 || end === void 0) return;
415
+ const url = `https://cdn.discordapp.com/emojis/${emoteId}`;
416
+ emotes.push({
417
+ id: emoteId,
418
+ urls: [url],
419
+ start: Math.max(+start, 0),
420
+ end: Math.min(+end + 1, normalizedMessage.length)
421
+ });
422
+ });
423
+ });
424
+ }
425
+ let idx = -1;
426
+ let nextIdx = 0;
427
+ const separator = origin === "youtube" ? ":" : " ";
428
+ do {
429
+ nextIdx = arrFromMsg.indexOf(separator, idx + 1);
430
+ if (nextIdx === -1) {
431
+ nextIdx = arrFromMsg.length;
432
+ }
433
+ const emote = arrFromMsg.slice(idx + 1, nextIdx).join("");
434
+ const start = idx + 1;
435
+ const end = nextIdx;
436
+ if (emote) {
437
+ if (storeEmotes?.ffz?.[emote]) {
438
+ emotes.push({
439
+ id: emote,
440
+ urls: [storeEmotes.ffz[emote]],
441
+ start,
442
+ end
443
+ });
444
+ } else if (storeEmotes?.bttv?.[emote]) {
445
+ emotes.push({
446
+ id: emote,
447
+ urls: [storeEmotes.bttv[emote]],
448
+ start,
449
+ end
450
+ });
451
+ } else if (storeEmotes?.seventv?.[emote]) {
452
+ emotes.push({
453
+ id: emote,
454
+ urls: [storeEmotes.seventv[emote]],
455
+ start,
456
+ end
457
+ });
458
+ } else if (youtubeEmotes?.[emote]) {
459
+ emotes.push({
460
+ id: emote,
461
+ urls: [youtubeEmotes[emote]],
462
+ start,
463
+ end
464
+ });
465
+ } else if (isCheer && /[a-z]+\d+/gi.exec(emote)) {
466
+ const cheerAmount = parseInt(emote.replace(/^\D+/g, ""));
467
+ let roundedAmount = 1;
468
+ let bitColor = "#979797";
469
+ if (cheerAmount >= 1e4) {
470
+ roundedAmount = 1e4;
471
+ bitColor = "#f43021";
472
+ } else if (cheerAmount >= 5e3) {
473
+ roundedAmount = 5e3;
474
+ bitColor = "#0099fe";
475
+ } else if (cheerAmount >= 1e3) {
476
+ roundedAmount = 1e3;
477
+ bitColor = "#1db2a5";
478
+ } else if (cheerAmount >= 100) {
479
+ roundedAmount = 100;
480
+ bitColor = "#9c3ee8";
481
+ }
482
+ emotes.push({
483
+ id: emote,
484
+ type: "cheer",
485
+ roundedAmount,
486
+ cheerAmount,
487
+ bitColor,
488
+ start,
489
+ end
490
+ });
491
+ }
492
+ }
493
+ } while ((idx = arrFromMsg.indexOf(separator, nextIdx)) !== -1);
494
+ emotes.sort((x, y) => x.start - y.start);
495
+ let index = 0;
496
+ const content = [];
497
+ for (const emote of emotes) {
498
+ const slicedMsg = arrFromMsg.slice(index, emote.start).join("");
499
+ content.push(origin === "youtube" ? slicedMsg?.replaceAll(":", "") : slicedMsg);
500
+ if ("type" in emote && emote.type === "cheer") {
501
+ content.push({
502
+ id: emote.id,
503
+ type: "cheer",
504
+ roundedAmount: emote.roundedAmount,
505
+ cheerAmount: emote.cheerAmount,
506
+ bitColor: emote.bitColor,
507
+ key: `${emote.start}-${emote.end}`
508
+ });
509
+ } else if ("urls" in emote) {
510
+ content.push({
511
+ id: emote.id,
512
+ urls: emote.urls,
513
+ key: `${emote.start}-${emote.end}`
514
+ });
515
+ }
516
+ index = emote.end;
517
+ }
518
+ const subStrMsg = arrFromMsg.slice(index, arrFromMsg.length).join("");
519
+ content.push(origin === "youtube" ? subStrMsg?.replaceAll(":", "") : subStrMsg);
520
+ return content;
521
+ };
522
+ var resolveMediaPreviewSetting = (value, fallback) => {
523
+ return typeof value === "boolean" ? value : fallback;
524
+ };
525
+ var normalizeMediaPreviewUserLevels = (value) => {
526
+ if (Array.isArray(value)) {
527
+ const normalized = value.map((level) => String(level).trim().toLowerCase()).filter((level) => MEDIA_PREVIEW_USER_LEVEL_VALUES.includes(level));
528
+ return [...new Set(normalized)];
529
+ }
530
+ if (typeof value === "string") {
531
+ const normalized = value.split(",").map((level) => level.trim().toLowerCase()).filter((level) => MEDIA_PREVIEW_USER_LEVEL_VALUES.includes(level));
532
+ return [...new Set(normalized)];
533
+ }
534
+ return [];
535
+ };
536
+ var resolveMediaPreviewRoleSettings = (settings) => {
537
+ if (Array.isArray(settings.previewMediaUserLevels) || typeof settings.previewMediaUserLevels === "string") {
538
+ const selected = new Set(normalizeMediaPreviewUserLevels(settings.previewMediaUserLevels));
539
+ return {
540
+ previewMediaForViewers: selected.has("regular") || selected.has("follower") || selected.has("anyone"),
541
+ previewMediaForSubscribers: selected.has("subscribers"),
542
+ previewMediaForVips: selected.has("vips"),
543
+ previewMediaForModerators: selected.has("moderators"),
544
+ previewMediaForStreamer: selected.has("streamer"),
545
+ previewMediaForTier3: selected.has("tier3"),
546
+ previewMediaForTier2: selected.has("tier2"),
547
+ previewMediaForRegular: selected.has("regular"),
548
+ previewMediaForFollower: selected.has("follower"),
549
+ previewMediaForAnyone: selected.has("anyone")
550
+ };
551
+ }
552
+ const previewMediaForViewers = resolveMediaPreviewSetting(settings.previewMediaForViewers, true);
553
+ const previewMediaForSubscribers = resolveMediaPreviewSetting(settings.previewMediaForSubscribers, true);
554
+ return {
555
+ previewMediaForViewers,
556
+ previewMediaForSubscribers,
557
+ previewMediaForVips: resolveMediaPreviewSetting(settings.previewMediaForVips, true),
558
+ previewMediaForModerators: resolveMediaPreviewSetting(settings.previewMediaForModerators, true),
559
+ previewMediaForStreamer: resolveMediaPreviewSetting(settings.previewMediaForStreamer, true),
560
+ previewMediaForTier3: resolveMediaPreviewSetting(settings.previewMediaForTier3, previewMediaForSubscribers),
561
+ previewMediaForTier2: resolveMediaPreviewSetting(settings.previewMediaForTier2, previewMediaForSubscribers),
562
+ previewMediaForRegular: resolveMediaPreviewSetting(settings.previewMediaForRegular, previewMediaForViewers),
563
+ previewMediaForFollower: resolveMediaPreviewSetting(settings.previewMediaForFollower, previewMediaForViewers),
564
+ previewMediaForAnyone: resolveMediaPreviewSetting(settings.previewMediaForAnyone, previewMediaForViewers)
565
+ };
566
+ };
567
+ var getNormalizedUserLevels = (value) => {
568
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
569
+ return {};
570
+ }
571
+ const normalized = {};
572
+ for (const [key, levelValue] of Object.entries(value)) {
573
+ normalized[key.toLowerCase()] = Boolean(levelValue);
574
+ }
575
+ return normalized;
576
+ };
577
+ var isMediaPreviewAllowedForUser = (userLevelsRaw, roleSettings) => {
578
+ const levels = getNormalizedUserLevels(userLevelsRaw);
579
+ const isStreamer = levels.isself || levels.self || levels.streamer || levels.broadcaster || levels.owner;
580
+ const isModerator = levels.mod || levels.moderator;
581
+ const isVip = levels.vip || levels.vips;
582
+ const isTier3 = levels.tier3 || levels.tier_3;
583
+ const isTier2 = levels.tier2 || levels.tier_2;
584
+ const isSubscriber = levels.subscriber || levels.sub || levels.member || levels.members || levels.tier1 || levels.tier_1;
585
+ const isRegular = levels.regular;
586
+ const isFollower = levels.follower || levels.followers;
587
+ return isStreamer && roleSettings.previewMediaForStreamer || isModerator && roleSettings.previewMediaForModerators || isVip && roleSettings.previewMediaForVips || isTier3 && roleSettings.previewMediaForTier3 || isTier2 && roleSettings.previewMediaForTier2 || isSubscriber && roleSettings.previewMediaForSubscribers || isRegular && roleSettings.previewMediaForRegular || isFollower && roleSettings.previewMediaForFollower || roleSettings.previewMediaForAnyone;
588
+ };
589
+ var parseExternalUrl = (url) => {
590
+ try {
591
+ return new URL(url);
592
+ } catch {
593
+ return null;
594
+ }
595
+ };
596
+ var decodeURIComponentSafe = (value) => {
597
+ try {
598
+ return decodeURIComponent(value);
599
+ } catch {
600
+ return value;
601
+ }
602
+ };
603
+ var sanitizeMediaTitle = (value) => {
604
+ return decodeURIComponentSafe(value.replace(/\+/g, " ")).replace(/\.[^./?#]+$/, "").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
605
+ };
606
+ var tryExtractFilename = (value) => {
607
+ const trimmed = value.trim();
608
+ if (!trimmed) {
609
+ return "";
610
+ }
611
+ const normalizedUrl = /^https?:\/\//i.test(trimmed) ? trimmed : trimmed.startsWith("www.") ? `https://${trimmed}` : "";
612
+ if (normalizedUrl) {
613
+ const parsed = parseExternalUrl(normalizedUrl);
614
+ if (parsed) {
615
+ const segment2 = parsed.pathname.split("/").filter(Boolean).pop() ?? "";
616
+ const decoded = decodeURIComponentSafe(segment2).trim();
617
+ if (decoded) {
618
+ return decoded;
619
+ }
620
+ }
621
+ }
622
+ const withoutQueryAndHash = trimmed.split(/[?#]/)[0] ?? "";
623
+ const segment = withoutQueryAndHash.split("/").filter(Boolean).pop() ?? "";
624
+ return decodeURIComponentSafe(segment).trim();
625
+ };
626
+ var getAudioTitleFromUrl = (url) => {
627
+ const parsed = parseExternalUrl(url);
628
+ if (!parsed) {
629
+ return "Audio";
630
+ }
631
+ for (const queryKey of ["title", "name", "filename", "file"]) {
632
+ const rawValue = parsed.searchParams.get(queryKey);
633
+ const filename = rawValue ? tryExtractFilename(rawValue) : "";
634
+ if (filename) {
635
+ return filename;
636
+ }
637
+ }
638
+ const pathFilename = tryExtractFilename(parsed.pathname);
639
+ if (pathFilename) {
640
+ return pathFilename;
641
+ }
642
+ for (const queryKey of ["title", "name", "filename", "file"]) {
643
+ const rawValue = parsed.searchParams.get(queryKey);
644
+ const normalized = rawValue ? sanitizeMediaTitle(rawValue) : "";
645
+ if (normalized) {
646
+ return normalized;
647
+ }
648
+ }
649
+ const pathSegment = parsed.pathname.split("/").filter(Boolean).pop() ?? "";
650
+ const normalizedPathTitle = sanitizeMediaTitle(pathSegment);
651
+ return normalizedPathTitle || "Audio";
652
+ };
653
+ var extractYoutubeEmbedInfo = (url) => {
654
+ const parsed = parseExternalUrl(url);
655
+ if (!parsed) {
656
+ return null;
657
+ }
658
+ const hostname = parsed.hostname.replace(/^www\./i, "").toLowerCase();
659
+ let videoId = "";
660
+ let isShort = false;
661
+ if (hostname === "youtu.be") {
662
+ videoId = parsed.pathname.split("/").filter(Boolean)[0] ?? "";
663
+ } else if (hostname.endsWith("youtube.com") || hostname === "youtube-nocookie.com") {
664
+ const segments = parsed.pathname.split("/").filter(Boolean);
665
+ if (segments[0] === "watch") {
666
+ videoId = parsed.searchParams.get("v") ?? "";
667
+ } else if (segments[0] === "shorts") {
668
+ videoId = segments[1] ?? "";
669
+ isShort = true;
670
+ } else if (["embed", "live", "v"].includes(segments[0] ?? "")) {
671
+ videoId = segments[1] ?? "";
672
+ }
673
+ }
674
+ const cleaned = videoId.trim();
675
+ return YOUTUBE_ID_REGEX.test(cleaned) ? { id: cleaned, isShort } : null;
676
+ };
677
+ var extractSpotifyEmbedUrl = (url) => {
678
+ const parsed = parseExternalUrl(url);
679
+ if (!parsed) {
680
+ return null;
681
+ }
682
+ const hostname = parsed.hostname.replace(/^www\./i, "").toLowerCase();
683
+ if (hostname !== "open.spotify.com" && hostname !== "play.spotify.com") {
684
+ return null;
685
+ }
686
+ const segments = parsed.pathname.split("/").filter(Boolean);
687
+ let type = segments[0] ?? "";
688
+ let id = segments[1] ?? "";
689
+ if (type === "intl" && segments.length >= 4) {
690
+ type = segments[2] ?? "";
691
+ id = segments[3] ?? "";
692
+ }
693
+ if (!SPOTIFY_EMBED_TYPES.has(type) || !id) {
694
+ return null;
695
+ }
696
+ return `https://open.spotify.com/embed/${type}/${id}`;
697
+ };
698
+ var getMediaPreviewFromUrl = (url) => {
699
+ if (MEDIA_IMAGE_REGEX.test(url)) {
700
+ return { kind: "image", src: url };
701
+ }
702
+ if (MEDIA_AUDIO_REGEX.test(url)) {
703
+ return { kind: "audio", src: url, title: getAudioTitleFromUrl(url) };
704
+ }
705
+ if (MEDIA_VIDEO_REGEX.test(url)) {
706
+ return { kind: "video", src: url };
707
+ }
708
+ const youtube = extractYoutubeEmbedInfo(url);
709
+ if (youtube) {
710
+ return { kind: youtube.isShort ? "youtubeShort" : "youtube", src: `https://www.youtube.com/embed/${youtube.id}` };
711
+ }
712
+ const spotifyEmbed = extractSpotifyEmbedUrl(url);
713
+ if (spotifyEmbed) {
714
+ return { kind: "spotify", src: spotifyEmbed };
715
+ }
716
+ return null;
717
+ };
718
+ var normalizeProfilePlatform = (value) => {
719
+ if (value === null || value === void 0) {
720
+ return "";
721
+ }
722
+ return String(value).trim().toLowerCase();
723
+ };
724
+ var normalizeProfileHandle = (value) => {
725
+ const text = typeof value === "string" || typeof value === "number" ? String(value).trim() : "";
726
+ return text.replace(/^@+/, "");
727
+ };
728
+ var resolvePlatformChatterProfileUrl = ({ platform, username, displayname, userId }) => {
729
+ const normalizedPlatform = normalizeProfilePlatform(platform);
730
+ if (!normalizedPlatform) {
731
+ return null;
732
+ }
733
+ const normalizedUsername = normalizeProfileHandle(username) || normalizeProfileHandle(displayname);
734
+ const normalizedUserId = typeof userId === "string" || typeof userId === "number" ? String(userId).trim() : "";
735
+ switch (normalizedPlatform) {
736
+ case "twitch":
737
+ return normalizedUsername ? `https://www.twitch.tv/${encodeURIComponent(normalizedUsername)}` : null;
738
+ case "youtube":
739
+ case "youtubegaming":
740
+ if (/^UC[\w-]+$/i.test(normalizedUserId)) {
741
+ return `https://www.youtube.com/channel/${encodeURIComponent(normalizedUserId)}`;
742
+ }
743
+ if (normalizedUsername) {
744
+ return `https://www.youtube.com/@${encodeURIComponent(normalizedUsername)}`;
745
+ }
746
+ return normalizedUserId ? `https://www.youtube.com/channel/${encodeURIComponent(normalizedUserId)}` : null;
747
+ case "facebook":
748
+ if (normalizedUserId) {
749
+ return `https://www.facebook.com/${encodeURIComponent(normalizedUserId)}`;
750
+ }
751
+ return normalizedUsername ? `https://www.facebook.com/${encodeURIComponent(normalizedUsername)}` : null;
752
+ case "tiktok":
753
+ return normalizedUsername ? `https://www.tiktok.com/@${encodeURIComponent(normalizedUsername)}` : null;
754
+ case "kick":
755
+ return normalizedUsername ? `https://kick.com/${encodeURIComponent(normalizedUsername)}` : null;
756
+ case "discord":
757
+ return normalizedUserId ? `https://discord.com/users/${encodeURIComponent(normalizedUserId)}` : null;
758
+ case "trovo":
759
+ return normalizedUsername ? `https://trovo.live/${encodeURIComponent(normalizedUsername)}` : null;
760
+ default:
761
+ return null;
762
+ }
763
+ };
764
+ var normalizeHttpUrl = (value) => {
765
+ const candidate = typeof value === "string" ? value.trim() : "";
766
+ if (!candidate) {
767
+ return null;
768
+ }
769
+ const normalized = candidate.startsWith("http://") || candidate.startsWith("https://") ? candidate : candidate.startsWith("www.") ? `https://${candidate}` : candidate;
770
+ const parsed = parseExternalUrl(normalized);
771
+ if (!parsed || !/^https?:$/i.test(parsed.protocol)) {
772
+ return null;
773
+ }
774
+ return normalized;
775
+ };
776
+ var resolveChatterProfileUrlWithResolvers = async ({
777
+ platform,
778
+ username,
779
+ displayname,
780
+ userId,
781
+ extraSettings,
782
+ resolvers
783
+ }) => {
784
+ const normalizedPlatform = normalizeProfilePlatform(platform);
785
+ const normalizedUsername = normalizeProfileHandle(username);
786
+ const normalizedDisplayName = normalizeProfileHandle(displayname);
787
+ const normalizedUserId = typeof userId === "string" || typeof userId === "number" ? String(userId).trim() : "";
788
+ const payload = {
789
+ username: normalizedUsername || void 0,
790
+ displayname: normalizedDisplayName || void 0,
791
+ userId: normalizedUserId || void 0,
792
+ platform: normalizedPlatform || void 0,
793
+ extraSettings
794
+ };
795
+ for (const resolver of resolvers ?? []) {
796
+ if (typeof resolver !== "function") {
797
+ continue;
798
+ }
799
+ try {
800
+ const resolved = await resolver(payload);
801
+ const normalizedResolvedUrl = normalizeHttpUrl(resolved);
802
+ if (normalizedResolvedUrl) {
803
+ return normalizedResolvedUrl;
804
+ }
805
+ } catch {
806
+ }
807
+ }
808
+ return resolvePlatformChatterProfileUrl({
809
+ platform: normalizedPlatform || platform,
810
+ username: normalizedUsername || normalizedDisplayName,
811
+ userId: normalizedUserId || void 0
812
+ });
813
+ };
814
+ export {
815
+ MEDIA_PREVIEW_USER_LEVEL_VALUES,
816
+ buildChatMessageContent,
817
+ codeMirrorlinterOptions,
818
+ getMediaPreviewFromUrl,
819
+ getNormalizedUserLevels,
820
+ isMediaPreviewAllowedForUser,
821
+ normalizeHttpUrl,
822
+ normalizeMediaPreviewUserLevels,
823
+ parseMessageLinks,
824
+ resolveChatterProfileUrlWithResolvers,
825
+ resolveMediaPreviewRoleSettings,
826
+ resolveMediaPreviewSetting,
827
+ resolvePlatformChatterProfileUrl,
828
+ tokenizeChatMessage,
829
+ variableCompletionOptions
830
+ };