@lumiastream/ui 0.2.8-alpha.22 → 0.2.8-alpha.24

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.
Files changed (3) hide show
  1. package/dist/index.js +266 -114
  2. package/dist/se-import.js +266 -114
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3114,7 +3114,7 @@ var resolveChatterProfileUrlWithResolvers = async ({
3114
3114
  };
3115
3115
 
3116
3116
  // src/se-import/index.ts
3117
- import { nanoid as nanoid3 } from "nanoid";
3117
+ import { nanoid as nanoid4 } from "nanoid";
3118
3118
 
3119
3119
  // src/se-import/translate.ts
3120
3120
  var SE_TO_LUMIA_PLACEHOLDER = {
@@ -3131,45 +3131,104 @@ var SE_TO_LUMIA_PLACEHOLDER = {
3131
3131
  total: "total",
3132
3132
  gifts: "giftAmount"
3133
3133
  };
3134
- function listenerPlaceholderContext(listener) {
3135
- if (listener === "follower-latest") return { name: "twitch_last_follower", username: "twitch_last_follower" };
3136
- if (listener === "subscriber-latest" || listener === "subscriber-new-latest" || listener === "subscriber-resub-latest") {
3137
- return { name: "twitch_last_subscriber", username: "twitch_last_subscriber" };
3138
- }
3139
- if (listener === "subscriber-gifted-latest") return { name: "twitch_last_gifter", username: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" };
3140
- if (listener === "subscriber-alltime-gifter") return { name: "twitch_alltime_top_gifter", username: "twitch_alltime_top_gifter", amount: "twitch_alltime_top_gifter_amount", count: "twitch_alltime_top_gifter_amount" };
3141
- if (listener === "cheer-latest") return { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" };
3142
- if (listener === "tip-latest" || listener === "latest_donator") {
3143
- return { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" };
3144
- }
3145
- if (listener === "raid-latest") return { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" };
3146
- if (listener === "follower-session") return { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" };
3147
- if (listener === "subscriber-session") return { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" };
3148
- if (listener === "cheer-session") return { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" };
3149
- if (listener === "tip-session") return { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" };
3150
- if (listener === "follower-total") return { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" };
3151
- if (listener === "subscriber-total") return { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" };
3152
- if (listener === "cheer-total") return { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" };
3153
- if (listener === "tip-total") return { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" };
3154
- if (listener === "tip-count") return { amount: "donation_count", count: "donation_count" };
3155
- if (listener === "cheer-session-top-donation") return { amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" };
3156
- if (listener === "cheer-session-top-donator") return { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheerer_amount", count: "twitch_session_top_cheerer_amount" };
3157
- if (listener === "tip-session-top-donation") return { amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" };
3158
- if (listener === "tip-session-top-donator") return { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" };
3159
- if (listener === "twitch_session_chat_count") return { wins: "twitch_session_chat_count", count: "twitch_session_chat_count", amount: "twitch_session_chat_count" };
3160
- if (listener === "merch-latest" || listener === "purchase-latest") {
3161
- return {
3162
- name: "fourthwall_last_order.username",
3163
- username: "fourthwall_last_order.username",
3164
- amount: "fourthwall_last_order.amount",
3165
- count: "fourthwall_last_order.itemsCount",
3166
- currency: "fourthwall_last_order.currency",
3167
- items: "fourthwall_last_order.items",
3168
- item: "fourthwall_last_order.firstItem.name",
3169
- message: "fourthwall_last_order.message"
3170
- };
3134
+ var TWITCH_CONTEXTS = {
3135
+ "follower-latest": { name: "twitch_last_follower", username: "twitch_last_follower" },
3136
+ "subscriber-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
3137
+ "subscriber-new-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
3138
+ "subscriber-resub-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
3139
+ "subscriber-gifted-latest": { name: "twitch_last_gifter", username: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
3140
+ "subscriber-alltime-gifter": { name: "twitch_alltime_top_gifter", username: "twitch_alltime_top_gifter", amount: "twitch_alltime_top_gifter_amount", count: "twitch_alltime_top_gifter_amount" },
3141
+ "cheer-latest": { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" },
3142
+ "tip-latest": { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
3143
+ latest_donator: { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
3144
+ "raid-latest": { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" },
3145
+ // Session counters: count/amount = the running session count.
3146
+ "follower-session": { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" },
3147
+ "subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
3148
+ "cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
3149
+ "tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
3150
+ // Totals (all-time): count/amount = total counter.
3151
+ "follower-total": { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" },
3152
+ "subscriber-total": { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" },
3153
+ "cheer-total": { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" },
3154
+ "tip-total": { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" },
3155
+ "tip-count": { amount: "donation_count", count: "donation_count" },
3156
+ // Top cheer / cheerer / donator session readouts.
3157
+ "cheer-session-top-donation": { amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
3158
+ "cheer-session-top-donator": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheerer_amount", count: "twitch_session_top_cheerer_amount" },
3159
+ "tip-session-top-donation": { amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
3160
+ "tip-session-top-donator": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
3161
+ // Botcounter — SE uses `{wins}` for the running chat count; the dispatcher's
3162
+ // fallbackVar for `se-widget-botcounter` is `twitch_session_chat_count`, so
3163
+ // when the listener arg lands as that var name we treat it as the wins source.
3164
+ twitch_session_chat_count: { wins: "twitch_session_chat_count", count: "twitch_session_chat_count", amount: "twitch_session_chat_count" },
3165
+ // Merch readouts — SE used to ship via their own shop, now most users route
3166
+ // through Fourthwall. Lumia exposes `fourthwall_last_order` as a full object:
3167
+ // { username, email, friendlyId, amount, currency, message, items,
3168
+ // itemsCount, itemsDetailed, orderItems, itemImages, firstItem }
3169
+ // (see fourthwall.manager.ts ORDER_PLACED handler). The template engine
3170
+ // supports dotted access (`dotNotationDig`) so we pluck the right field.
3171
+ "merch-latest": {
3172
+ name: "fourthwall_last_order.username",
3173
+ username: "fourthwall_last_order.username",
3174
+ amount: "fourthwall_last_order.amount",
3175
+ count: "fourthwall_last_order.itemsCount",
3176
+ currency: "fourthwall_last_order.currency",
3177
+ items: "fourthwall_last_order.items",
3178
+ item: "fourthwall_last_order.firstItem.name",
3179
+ message: "fourthwall_last_order.message"
3180
+ },
3181
+ "purchase-latest": {
3182
+ name: "fourthwall_last_order.username",
3183
+ username: "fourthwall_last_order.username",
3184
+ amount: "fourthwall_last_order.amount",
3185
+ count: "fourthwall_last_order.itemsCount",
3186
+ currency: "fourthwall_last_order.currency",
3187
+ items: "fourthwall_last_order.items",
3188
+ item: "fourthwall_last_order.firstItem.name",
3189
+ message: "fourthwall_last_order.message"
3171
3190
  }
3172
- return {};
3191
+ };
3192
+ var KICK_CONTEXTS = {
3193
+ "follower-latest": { name: "kick_last_follower", username: "kick_last_follower" },
3194
+ "subscriber-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
3195
+ "subscriber-new-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
3196
+ "subscriber-resub-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
3197
+ "subscriber-gifted-latest": { name: "kick_last_gifter", username: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
3198
+ "cheer-latest": { name: "kick_last_kicks", username: "kick_last_kicks", amount: "kick_last_kicks_amount", count: "kick_last_kicks_amount" },
3199
+ "raid-latest": { name: "kick_last_host", username: "kick_last_host", amount: "kick_last_host_amount", count: "kick_last_host_amount" },
3200
+ "follower-session": { amount: "kick_session_follower_count", count: "kick_session_follower_count" },
3201
+ "subscriber-session": { amount: "kick_session_subscriber_count", count: "kick_session_subscriber_count" },
3202
+ "cheer-session": { amount: "kick_session_kicks_count", count: "kick_session_kicks_count" },
3203
+ "subscriber-gifted-session": { amount: "kick_session_gifts_count", count: "kick_session_gifts_count" },
3204
+ "follower-total": { amount: "kick_total_follower_count", count: "kick_total_follower_count" },
3205
+ "subscriber-total": { amount: "kick_total_subscriber_count", count: "kick_total_subscriber_count" },
3206
+ "cheer-total": { amount: "kick_total_kicks_count", count: "kick_total_kicks_count" },
3207
+ "follower-week": { amount: "kick_week_follower_count", count: "kick_week_follower_count" },
3208
+ "follower-month": { amount: "kick_month_follower_count", count: "kick_month_follower_count" },
3209
+ "subscriber-week": { amount: "kick_week_subscriber_count", count: "kick_week_subscriber_count" },
3210
+ "subscriber-month": { amount: "kick_month_subscriber_count", count: "kick_month_subscriber_count" }
3211
+ };
3212
+ var YOUTUBE_CONTEXTS = {
3213
+ "follower-latest": { name: "youtube_last_subscriber", username: "youtube_last_subscriber" },
3214
+ "subscriber-latest": { name: "youtube_last_member", username: "youtube_last_member" },
3215
+ "subscriber-new-latest": { name: "youtube_last_member", username: "youtube_last_member" },
3216
+ "subscriber-resub-latest": { name: "youtube_last_member", username: "youtube_last_member" },
3217
+ "subscriber-gifted-latest": { name: "youtube_last_gift_member", username: "youtube_last_gift_member" },
3218
+ "cheer-latest": { name: "youtube_last_superchatter", username: "youtube_last_superchatter" },
3219
+ "follower-session": { amount: "youtube_session_subscriber_count", count: "youtube_session_subscriber_count" },
3220
+ "subscriber-session": { amount: "youtube_session_member_count", count: "youtube_session_member_count" },
3221
+ "cheer-session": { amount: "youtube_session_superchat_count", count: "youtube_session_superchat_count" },
3222
+ "follower-total": { amount: "youtube_total_subscriber_count", count: "youtube_total_subscriber_count" },
3223
+ "subscriber-total": { amount: "youtube_total_member_count", count: "youtube_total_member_count" },
3224
+ "follower-week": { amount: "youtube_week_subscriber_count", count: "youtube_week_subscriber_count" },
3225
+ "follower-month": { amount: "youtube_month_subscriber_count", count: "youtube_month_subscriber_count" },
3226
+ "subscriber-week": { amount: "youtube_week_member_count", count: "youtube_week_member_count" },
3227
+ "subscriber-month": { amount: "youtube_month_member_count", count: "youtube_month_member_count" }
3228
+ };
3229
+ function listenerPlaceholderContext(listener, provider = "twitch") {
3230
+ const platformTable = provider === "kick" ? KICK_CONTEXTS : provider === "youtube" ? YOUTUBE_CONTEXTS : null;
3231
+ return platformTable?.[listener] ?? TWITCH_CONTEXTS[listener] ?? {};
3173
3232
  }
3174
3233
  var SE_WIDGET_TYPE_LABELS = {
3175
3234
  "subscriber-latest": "Latest subscriber",
@@ -3205,17 +3264,57 @@ function defaultLabelForSEWidget(widget) {
3205
3264
  const tail = words.slice(1).join(" ");
3206
3265
  return tail ? `${head} ${tail}` : head;
3207
3266
  }
3208
- function translateSeText(input, listener, passthrough) {
3267
+ function translateSeText(input, listener, passthrough, provider) {
3209
3268
  if (!input) return "";
3210
- const ctx = listener ? listenerPlaceholderContext(listener) : {};
3269
+ const ctx = listener ? listenerPlaceholderContext(listener, provider) : {};
3211
3270
  return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
3212
3271
  if (passthrough?.has(raw)) return `{{${raw}}}`;
3213
3272
  const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
3214
3273
  return `{{${mapped}}}`;
3215
3274
  });
3216
3275
  }
3217
- function listenerToLumiaVariable(listener) {
3276
+ var KICK_LISTENER_VARS = {
3277
+ "follower-latest": "kick_last_follower",
3278
+ "subscriber-latest": "kick_last_subscriber",
3279
+ "subscriber-new-latest": "kick_last_subscriber",
3280
+ "subscriber-resub-latest": "kick_last_subscriber",
3281
+ "subscriber-gifted-latest": "kick_last_gifter",
3282
+ "cheer-latest": "kick_last_kicks",
3283
+ "raid-latest": "kick_last_host",
3284
+ "follower-session": "kick_session_follower_count",
3285
+ "follower-week": "kick_week_follower_count",
3286
+ "follower-month": "kick_month_follower_count",
3287
+ "follower-total": "kick_total_follower_count",
3288
+ "subscriber-session": "kick_session_subscriber_count",
3289
+ "subscriber-week": "kick_week_subscriber_count",
3290
+ "subscriber-month": "kick_month_subscriber_count",
3291
+ "subscriber-total": "kick_total_subscriber_count",
3292
+ "subscriber-gifted-session": "kick_session_gifts_count",
3293
+ "cheer-session": "kick_session_kicks_count",
3294
+ "cheer-total": "kick_total_kicks_count"
3295
+ };
3296
+ var YOUTUBE_LISTENER_VARS = {
3297
+ "follower-latest": "youtube_last_subscriber",
3298
+ "subscriber-latest": "youtube_last_member",
3299
+ "subscriber-new-latest": "youtube_last_member",
3300
+ "subscriber-resub-latest": "youtube_last_member",
3301
+ "subscriber-gifted-latest": "youtube_last_gift_member",
3302
+ "cheer-latest": "youtube_last_superchatter",
3303
+ "follower-session": "youtube_session_subscriber_count",
3304
+ "follower-week": "youtube_week_subscriber_count",
3305
+ "follower-month": "youtube_month_subscriber_count",
3306
+ "follower-total": "youtube_total_subscriber_count",
3307
+ "subscriber-session": "youtube_session_member_count",
3308
+ "subscriber-week": "youtube_week_member_count",
3309
+ "subscriber-month": "youtube_month_member_count",
3310
+ "subscriber-total": "youtube_total_member_count",
3311
+ "subscriber-gifted-session": "youtube_session_gift_members_count",
3312
+ "cheer-session": "youtube_session_superchat_count"
3313
+ };
3314
+ function listenerToLumiaVariable(listener, provider = "twitch") {
3218
3315
  if (typeof listener !== "string" || !listener) return "username";
3316
+ if (provider === "kick" && KICK_LISTENER_VARS[listener]) return KICK_LISTENER_VARS[listener];
3317
+ if (provider === "youtube" && YOUTUBE_LISTENER_VARS[listener]) return YOUTUBE_LISTENER_VARS[listener];
3219
3318
  const m = {
3220
3319
  // Latest events
3221
3320
  "follower-latest": "twitch_last_follower",
@@ -3513,10 +3612,10 @@ function mapReadout(widget, fallbackVar, ctx) {
3513
3612
  const explicit = typeof explicitListener === "string" ? explicitListener : null;
3514
3613
  const widgetListener = typeof widget.listener === "string" ? widget.listener : null;
3515
3614
  const listener = explicit ?? widgetListener ?? fallbackVar;
3516
- const variable = listenerToLumiaVariable(listener);
3615
+ const variable = listenerToLumiaVariable(listener, ctx?.provider);
3517
3616
  const userTemplate = widget.text?.value ?? "";
3518
3617
  const inheritsText = userTemplate.trim().length > 0 && /\{[a-zA-Z]/.test(userTemplate);
3519
- const value = inheritsText ? translateSeText(userTemplate, listener) : `{{${variable}}}`;
3618
+ const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider) : `{{${variable}}}`;
3520
3619
  const seCss = widget.text?.css ?? {};
3521
3620
  return buildUnit(widget, "text", {
3522
3621
  content: { value, highlightColor: "inherit" },
@@ -3534,34 +3633,99 @@ function mapReadout(widget, fallbackVar, ctx) {
3534
3633
  }, ctx);
3535
3634
  }
3536
3635
 
3537
- // src/se-import/mappers/alert.ts
3538
- var SE_EVENT_TO_LUMIA_ALERT = {
3539
- follower: "twitch-follower",
3540
- subscriber: "twitch-subscriber",
3541
- tip: "streamelements-donation",
3542
- cheer: "twitch-bits",
3543
- raid: "twitch-raid",
3544
- // Merch / shop-order — Lumia's Fourthwall integration is the data path most
3545
- // users now have wired up since SE deprecated their own merch flow in favor
3546
- // of Fourthwall. `fourthwall-shopOrder` is the alert key from
3547
- // LumiaAlertValues / activity.types.ts.
3548
- merch: "fourthwall-shopOrder",
3549
- purchase: "fourthwall-shopOrder",
3550
- charityCampaignDonation: "twitch-charityDonation"
3551
- // NOTE — two SE events intentionally NOT mapped:
3552
- // • `host` → previously `twitch-raid`. Twitch deprecated hosting in
3553
- // late 2022 so the event never fires on the platform anymore. Even
3554
- // if it did, host payloads don't carry a viewer count (raids do),
3555
- // so the rendered alert would read "{name} raided with ?? viewers".
3556
- // Drop the mapping; SE widgets configured with a `host` block fall
3557
- // through to the unsupported placeholder rather than producing
3558
- // misleading raid alerts.
3559
- // • `redemption` → SE's `redemption` event is the legacy SE Store
3560
- // redemption (their own loyalty-point store purchases), NOT Twitch
3561
- // channel-point redemptions. Different payload, different semantics.
3562
- // SE Store is mostly deprecated and seldom used; dropping the
3563
- // mapping prevents misleading "channel-point redemption" alerts.
3636
+ // src/se-import/mappers/provider-alerts.ts
3637
+ var ALERT_BOX_KEYS = {
3638
+ twitch: {
3639
+ follower: "twitch-follower",
3640
+ subscriber: "twitch-subscriber",
3641
+ tip: "streamelements-donation",
3642
+ cheer: "twitch-bits",
3643
+ raid: "twitch-raid",
3644
+ merch: "fourthwall-shopOrder",
3645
+ purchase: "fourthwall-shopOrder",
3646
+ charityCampaignDonation: "twitch-charityDonation"
3647
+ },
3648
+ kick: {
3649
+ follower: "kick-follower",
3650
+ subscriber: "kick-subscriber",
3651
+ tip: "streamelements-donation",
3652
+ cheer: "kick-kicks",
3653
+ raid: "kick-host",
3654
+ host: "kick-host",
3655
+ merch: "fourthwall-shopOrder",
3656
+ purchase: "fourthwall-shopOrder"
3657
+ },
3658
+ youtube: {
3659
+ follower: "youtube-subscriber",
3660
+ subscriber: "youtube-member",
3661
+ tip: "streamelements-donation",
3662
+ cheer: "youtube-superchat",
3663
+ merch: "fourthwall-shopOrder",
3664
+ purchase: "fourthwall-shopOrder"
3665
+ }
3666
+ };
3667
+ var KAPPAGEN_KEYS = {
3668
+ twitch: {
3669
+ follower: "twitch-follower",
3670
+ subscriber: "twitch-subscriber",
3671
+ cheer: "twitch-bits",
3672
+ raid: "twitch-raid",
3673
+ tip: "streamelements-donation"
3674
+ },
3675
+ kick: {
3676
+ follower: "kick-follower",
3677
+ subscriber: "kick-subscriber",
3678
+ cheer: "kick-kicks",
3679
+ raid: "kick-host",
3680
+ tip: "streamelements-donation"
3681
+ },
3682
+ youtube: {
3683
+ follower: "youtube-subscriber",
3684
+ subscriber: "youtube-member",
3685
+ cheer: "youtube-superchat",
3686
+ tip: "streamelements-donation"
3687
+ }
3688
+ };
3689
+ var STREAMBOSS_LISTENER_KEYS = {
3690
+ twitch: {
3691
+ "follower-latest": "twitch-follower",
3692
+ "subscriber-latest": "twitch-subscriber",
3693
+ "tip-latest": "lumiastream-donation",
3694
+ "cheer-latest": "twitch-bits",
3695
+ "raid-latest": "twitch-raid"
3696
+ },
3697
+ kick: {
3698
+ "follower-latest": "kick-follower",
3699
+ "subscriber-latest": "kick-subscriber",
3700
+ "tip-latest": "lumiastream-donation",
3701
+ "cheer-latest": "kick-kicks",
3702
+ "raid-latest": "kick-host"
3703
+ },
3704
+ youtube: {
3705
+ "follower-latest": "youtube-subscriber",
3706
+ "subscriber-latest": "youtube-member",
3707
+ "tip-latest": "lumiastream-donation",
3708
+ "cheer-latest": "youtube-superchat"
3709
+ }
3564
3710
  };
3711
+ function alertBoxKeyFor(seEvent, provider = "twitch") {
3712
+ return ALERT_BOX_KEYS[provider]?.[seEvent];
3713
+ }
3714
+ function kappagenKeyFor(seEvent, provider = "twitch") {
3715
+ return KAPPAGEN_KEYS[provider]?.[seEvent];
3716
+ }
3717
+ function streamBossListenerKeyFor(seListener, provider = "twitch") {
3718
+ return STREAMBOSS_LISTENER_KEYS[provider]?.[seListener];
3719
+ }
3720
+ function readBootstrapProvider(bootstrap) {
3721
+ const ch = bootstrap.channel;
3722
+ if (!ch || typeof ch !== "object") return "twitch";
3723
+ const p = ch.provider;
3724
+ if (p === "kick" || p === "youtube" || p === "twitch") return p;
3725
+ return "twitch";
3726
+ }
3727
+
3728
+ // src/se-import/mappers/alert.ts
3565
3729
  function pickGraphics(g) {
3566
3730
  const DEFAULT_GRAPHIC_VOLUME = 0.5;
3567
3731
  if (!g) return { type: "image", content: { src: "", loop: false, volume: DEFAULT_GRAPHIC_VOLUME } };
@@ -3744,12 +3908,12 @@ function pickBaseAlertFields(event) {
3744
3908
  function mapAlertBox(widget, opts = {}, ctx) {
3745
3909
  const v = widget.variables ?? {};
3746
3910
  const events = {};
3747
- const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "merch", "purchase", "charityCampaignDonation"];
3911
+ const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "host", "merch", "purchase", "charityCampaignDonation"];
3748
3912
  let firstEvent = null;
3749
3913
  for (const seKey of eventKeys) {
3750
3914
  const ev = v[seKey];
3751
3915
  if (!ev || typeof ev !== "object") continue;
3752
- const lumiaAlert = SE_EVENT_TO_LUMIA_ALERT[seKey];
3916
+ const lumiaAlert = alertBoxKeyFor(seKey, ctx?.provider);
3753
3917
  if (!lumiaAlert) continue;
3754
3918
  const built = buildAlertEvent(ev, seKey);
3755
3919
  events[lumiaAlert] = built;
@@ -3898,6 +4062,7 @@ function mapGoal(widget, ctx) {
3898
4062
  }
3899
4063
 
3900
4064
  // src/se-import/mappers/custom.ts
4065
+ import { nanoid as nanoid2 } from "nanoid";
3901
4066
  var SOURCE_TO_LUMIA_FIELD_TYPE = {
3902
4067
  text: "input",
3903
4068
  number: "number",
@@ -3957,15 +4122,28 @@ function mapCustom(widget, ctx) {
3957
4122
  };
3958
4123
  const fields = parse(v.fields) ?? v.fields;
3959
4124
  const fieldData = parse(v.fieldData) ?? v.fieldData;
4125
+ const widgetProvider = widget.provider;
4126
+ const provider = widgetProvider === "kick" || widgetProvider === "youtube" || widgetProvider === "twitch" ? widgetProvider : ctx?.provider ?? "twitch";
3960
4127
  return buildUnit(widget, "custom", {
3961
4128
  content: {
3962
- codeId: String(widget.id),
4129
+ // SE widget ids are integers unique only within a single SE overlay
4130
+ // (1, 2, 3…). Using them as Lumia codeIds would collide across
4131
+ // imports — codeId is the scoping key for variables, storage, and
4132
+ // event listeners. Mint a fresh nanoid so every imported widget
4133
+ // has its own scope.
4134
+ codeId: nanoid2(),
3963
4135
  html: v.html ?? "",
3964
4136
  css: v.css ?? "",
3965
4137
  js: v.js ?? "",
3966
4138
  configs: translateConfigs(fields),
3967
4139
  data: fieldData ?? {},
3968
- flavor: "streamelements"
4140
+ flavor: "streamelements",
4141
+ // Native field on the Custom module content. The compatibility
4142
+ // shim in Embed.tsx reads this to pick the right listener-name
4143
+ // mapping table when forwarding Lumia events into the iframe as
4144
+ // `onEventReceived` payloads. Default 'twitch' matches the shim's
4145
+ // historical default so legacy imports keep working.
4146
+ sourceProvider: provider
3969
4147
  }
3970
4148
  }, ctx);
3971
4149
  }
@@ -4597,27 +4775,6 @@ function mapMediaShare(widget, ctx) {
4597
4775
  }
4598
4776
  }, ctx);
4599
4777
  }
4600
- var SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT = {
4601
- follower: "twitch-follower",
4602
- subscriber: "twitch-subscriber",
4603
- cheer: "twitch-bits",
4604
- raid: "twitch-raid",
4605
- tip: "streamelements-donation"
4606
- // NOTE — three SE events intentionally NOT mapped:
4607
- // • `host` → previously `twitch-host`, which doesn't exist in
4608
- // LumiaAlertValues (verified). Twitch deprecated hosting
4609
- // in 2022 so the event never fires anyway.
4610
- // • `purchase` → previously `streamlabs-merch`, which is deprecated. The
4611
- // main Alert Box mapper routes merch/purchase to
4612
- // `fourthwall-shopOrder`; keep Kappagen consistent by
4613
- // dropping the legacy SL path rather than splitting
4614
- // emoji emotion between two competing alert keys.
4615
- // • `embers` → previously `youtube-superchat`, which is wrong on both
4616
- // ends: embers were a deprecated Twitch paid-emote
4617
- // feature unrelated to YouTube super chats.
4618
- // All three drop silently; the rest of the widget (size, spawn zone,
4619
- // per-event flags for the remaining events) still imports.
4620
- };
4621
4778
  function mapKappagen(widget, ctx) {
4622
4779
  const v = widget.variables ?? {};
4623
4780
  const eventsFromNested = v.eventsConfig ? {
@@ -4628,7 +4785,7 @@ function mapKappagen(widget, ctx) {
4628
4785
  tip: v.eventsConfig.tip?.enabled ?? true
4629
4786
  } : null;
4630
4787
  const events = v.events ?? eventsFromNested ?? { subscriber: true, follower: true, cheer: true, raid: true, tip: true };
4631
- const alertTriggers = Object.entries(events).filter(([, enabled]) => !!enabled).map(([seKey]) => SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT[seKey]).filter((x) => !!x);
4788
+ const alertTriggers = Object.entries(events).filter(([, enabled]) => !!enabled).map(([seKey]) => kappagenKeyFor(seKey, ctx?.provider)).filter((x) => !!x);
4632
4789
  const alertEmotes = ["Kappa", "<3", "imGlitch"];
4633
4790
  return buildUnit(
4634
4791
  widget,
@@ -5018,16 +5175,7 @@ function parsePx(raw) {
5018
5175
  }
5019
5176
 
5020
5177
  // src/se-import/mappers/streamboss.ts
5021
- var SE_LISTENER_TO_ALERT = {
5022
- "follower-latest": "twitch-follower",
5023
- "subscriber-latest": "twitch-subscriber",
5024
- "tip-latest": "lumiastream-donation",
5025
- "cheer-latest": "twitch-bits",
5026
- "raid-latest": "twitch-raid"
5027
- // `purchase-latest` and `subscriber-gifted-latest` are intentionally absent.
5028
- // SE rolled gifted subs into `subscriber-latest`; Lumia splits them via
5029
- // extraSettings.isGift, which the runtime already handles consistently.
5030
- };
5178
+ var SE_LISTENERS = ["follower-latest", "subscriber-latest", "tip-latest", "cheer-latest", "raid-latest"];
5031
5179
  var SE_LISTENER_MODE = {
5032
5180
  "follower-latest": "flat",
5033
5181
  "subscriber-latest": "flat",
@@ -5049,8 +5197,10 @@ function mapStreamBoss(widget, ctx) {
5049
5197
  const seDamage = v.damage ?? {};
5050
5198
  const damageMap = {};
5051
5199
  const selectedEvents = [];
5052
- for (const [seListener, alertKey] of Object.entries(SE_LISTENER_TO_ALERT)) {
5200
+ for (const seListener of SE_LISTENERS) {
5053
5201
  if (!listenerIsOn(widget.listeners, seListener)) continue;
5202
+ const alertKey = streamBossListenerKeyFor(seListener, ctx?.provider);
5203
+ if (!alertKey) continue;
5054
5204
  const raw = seDamage[seListener];
5055
5205
  const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
5056
5206
  if (value === null) continue;
@@ -5358,7 +5508,7 @@ function guessMime(name) {
5358
5508
  }
5359
5509
 
5360
5510
  // src/se-import/transplant.ts
5361
- import { nanoid as nanoid2 } from "nanoid";
5511
+ import { nanoid as nanoid3 } from "nanoid";
5362
5512
  function cloneModuleForTransplant(srcModule, newId) {
5363
5513
  const clonedContent = srcModule.content ? structuredClone(srcModule.content) : srcModule.content;
5364
5514
  if (srcModule.settings?.type === "custom" && clonedContent) {
@@ -5376,7 +5526,7 @@ function transplantLayer(source, sourceLayerId) {
5376
5526
  const srcLayer = layers.find((l) => l.id === sourceLayerId);
5377
5527
  const srcModule = modules[sourceLayerId];
5378
5528
  if (!srcLayer || !srcModule) return null;
5379
- const rootNewId = nanoid2();
5529
+ const rootNewId = nanoid3();
5380
5530
  if (srcLayer.type !== "group") {
5381
5531
  const layer = { ...srcLayer, id: rootNewId };
5382
5532
  const module = cloneModuleForTransplant(srcModule, rootNewId);
@@ -5394,7 +5544,7 @@ function transplantLayer(source, sourceLayerId) {
5394
5544
  if (seen.has(child.id)) continue;
5395
5545
  seen.add(child.id);
5396
5546
  descendants.push(child);
5397
- idRemap.set(child.id, nanoid2());
5547
+ idRemap.set(child.id, nanoid3());
5398
5548
  if (child.type === "group") queue.push(child.id);
5399
5549
  }
5400
5550
  }
@@ -8135,18 +8285,20 @@ function importSEBootstrap(bootstrap) {
8135
8285
  mappings: [],
8136
8286
  notes: []
8137
8287
  };
8288
+ const provider = readBootstrapProvider(bootstrap);
8138
8289
  console.log("[SE-IMPORT] bootstrap received", {
8139
8290
  overlayId: seOverlay?._id,
8140
8291
  name: seOverlay?.name,
8141
8292
  canvas: { width: seOverlay?.settings?.width, height: seOverlay?.settings?.height },
8142
8293
  widgetCount: widgets.length,
8143
- widgetTypes: widgets.map((w) => w.type)
8294
+ widgetTypes: widgets.map((w) => w.type),
8295
+ provider
8144
8296
  });
8145
8297
  const importCanvas = {
8146
8298
  width: seOverlay?.settings?.width ?? 1920,
8147
8299
  height: seOverlay?.settings?.height ?? 1080
8148
8300
  };
8149
- const ctx = { canvas: importCanvas };
8301
+ const ctx = { canvas: importCanvas, provider };
8150
8302
  const layers = [];
8151
8303
  const modules = {};
8152
8304
  const reviewItems = [];
@@ -8156,7 +8308,7 @@ function importSEBootstrap(bootstrap) {
8156
8308
  if (w.type !== "se-widget-group") continue;
8157
8309
  const uid = w.variables?.uid;
8158
8310
  if (typeof uid !== "string") continue;
8159
- const lumiaGroupId = nanoid3();
8311
+ const lumiaGroupId = nanoid4();
8160
8312
  seUidToLumiaGroupId[uid] = lumiaGroupId;
8161
8313
  groupWidgetByUid[uid] = w;
8162
8314
  }
package/dist/se-import.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/se-import/index.ts
2
- import { nanoid as nanoid3 } from "nanoid";
2
+ import { nanoid as nanoid4 } from "nanoid";
3
3
 
4
4
  // src/se-import/translate.ts
5
5
  var SE_TO_LUMIA_PLACEHOLDER = {
@@ -16,45 +16,104 @@ var SE_TO_LUMIA_PLACEHOLDER = {
16
16
  total: "total",
17
17
  gifts: "giftAmount"
18
18
  };
19
- function listenerPlaceholderContext(listener) {
20
- if (listener === "follower-latest") return { name: "twitch_last_follower", username: "twitch_last_follower" };
21
- if (listener === "subscriber-latest" || listener === "subscriber-new-latest" || listener === "subscriber-resub-latest") {
22
- return { name: "twitch_last_subscriber", username: "twitch_last_subscriber" };
23
- }
24
- if (listener === "subscriber-gifted-latest") return { name: "twitch_last_gifter", username: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" };
25
- if (listener === "subscriber-alltime-gifter") return { name: "twitch_alltime_top_gifter", username: "twitch_alltime_top_gifter", amount: "twitch_alltime_top_gifter_amount", count: "twitch_alltime_top_gifter_amount" };
26
- if (listener === "cheer-latest") return { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" };
27
- if (listener === "tip-latest" || listener === "latest_donator") {
28
- return { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" };
19
+ var TWITCH_CONTEXTS = {
20
+ "follower-latest": { name: "twitch_last_follower", username: "twitch_last_follower" },
21
+ "subscriber-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
22
+ "subscriber-new-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
23
+ "subscriber-resub-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
24
+ "subscriber-gifted-latest": { name: "twitch_last_gifter", username: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
25
+ "subscriber-alltime-gifter": { name: "twitch_alltime_top_gifter", username: "twitch_alltime_top_gifter", amount: "twitch_alltime_top_gifter_amount", count: "twitch_alltime_top_gifter_amount" },
26
+ "cheer-latest": { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" },
27
+ "tip-latest": { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
28
+ latest_donator: { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
29
+ "raid-latest": { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" },
30
+ // Session counters: count/amount = the running session count.
31
+ "follower-session": { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" },
32
+ "subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
33
+ "cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
34
+ "tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
35
+ // Totals (all-time): count/amount = total counter.
36
+ "follower-total": { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" },
37
+ "subscriber-total": { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" },
38
+ "cheer-total": { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" },
39
+ "tip-total": { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" },
40
+ "tip-count": { amount: "donation_count", count: "donation_count" },
41
+ // Top cheer / cheerer / donator session readouts.
42
+ "cheer-session-top-donation": { amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
43
+ "cheer-session-top-donator": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheerer_amount", count: "twitch_session_top_cheerer_amount" },
44
+ "tip-session-top-donation": { amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
45
+ "tip-session-top-donator": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
46
+ // Botcounter — SE uses `{wins}` for the running chat count; the dispatcher's
47
+ // fallbackVar for `se-widget-botcounter` is `twitch_session_chat_count`, so
48
+ // when the listener arg lands as that var name we treat it as the wins source.
49
+ twitch_session_chat_count: { wins: "twitch_session_chat_count", count: "twitch_session_chat_count", amount: "twitch_session_chat_count" },
50
+ // Merch readouts — SE used to ship via their own shop, now most users route
51
+ // through Fourthwall. Lumia exposes `fourthwall_last_order` as a full object:
52
+ // { username, email, friendlyId, amount, currency, message, items,
53
+ // itemsCount, itemsDetailed, orderItems, itemImages, firstItem }
54
+ // (see fourthwall.manager.ts ORDER_PLACED handler). The template engine
55
+ // supports dotted access (`dotNotationDig`) so we pluck the right field.
56
+ "merch-latest": {
57
+ name: "fourthwall_last_order.username",
58
+ username: "fourthwall_last_order.username",
59
+ amount: "fourthwall_last_order.amount",
60
+ count: "fourthwall_last_order.itemsCount",
61
+ currency: "fourthwall_last_order.currency",
62
+ items: "fourthwall_last_order.items",
63
+ item: "fourthwall_last_order.firstItem.name",
64
+ message: "fourthwall_last_order.message"
65
+ },
66
+ "purchase-latest": {
67
+ name: "fourthwall_last_order.username",
68
+ username: "fourthwall_last_order.username",
69
+ amount: "fourthwall_last_order.amount",
70
+ count: "fourthwall_last_order.itemsCount",
71
+ currency: "fourthwall_last_order.currency",
72
+ items: "fourthwall_last_order.items",
73
+ item: "fourthwall_last_order.firstItem.name",
74
+ message: "fourthwall_last_order.message"
29
75
  }
30
- if (listener === "raid-latest") return { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" };
31
- if (listener === "follower-session") return { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" };
32
- if (listener === "subscriber-session") return { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" };
33
- if (listener === "cheer-session") return { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" };
34
- if (listener === "tip-session") return { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" };
35
- if (listener === "follower-total") return { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" };
36
- if (listener === "subscriber-total") return { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" };
37
- if (listener === "cheer-total") return { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" };
38
- if (listener === "tip-total") return { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" };
39
- if (listener === "tip-count") return { amount: "donation_count", count: "donation_count" };
40
- if (listener === "cheer-session-top-donation") return { amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" };
41
- if (listener === "cheer-session-top-donator") return { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheerer_amount", count: "twitch_session_top_cheerer_amount" };
42
- if (listener === "tip-session-top-donation") return { amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" };
43
- if (listener === "tip-session-top-donator") return { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" };
44
- if (listener === "twitch_session_chat_count") return { wins: "twitch_session_chat_count", count: "twitch_session_chat_count", amount: "twitch_session_chat_count" };
45
- if (listener === "merch-latest" || listener === "purchase-latest") {
46
- return {
47
- name: "fourthwall_last_order.username",
48
- username: "fourthwall_last_order.username",
49
- amount: "fourthwall_last_order.amount",
50
- count: "fourthwall_last_order.itemsCount",
51
- currency: "fourthwall_last_order.currency",
52
- items: "fourthwall_last_order.items",
53
- item: "fourthwall_last_order.firstItem.name",
54
- message: "fourthwall_last_order.message"
55
- };
56
- }
57
- return {};
76
+ };
77
+ var KICK_CONTEXTS = {
78
+ "follower-latest": { name: "kick_last_follower", username: "kick_last_follower" },
79
+ "subscriber-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
80
+ "subscriber-new-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
81
+ "subscriber-resub-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
82
+ "subscriber-gifted-latest": { name: "kick_last_gifter", username: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
83
+ "cheer-latest": { name: "kick_last_kicks", username: "kick_last_kicks", amount: "kick_last_kicks_amount", count: "kick_last_kicks_amount" },
84
+ "raid-latest": { name: "kick_last_host", username: "kick_last_host", amount: "kick_last_host_amount", count: "kick_last_host_amount" },
85
+ "follower-session": { amount: "kick_session_follower_count", count: "kick_session_follower_count" },
86
+ "subscriber-session": { amount: "kick_session_subscriber_count", count: "kick_session_subscriber_count" },
87
+ "cheer-session": { amount: "kick_session_kicks_count", count: "kick_session_kicks_count" },
88
+ "subscriber-gifted-session": { amount: "kick_session_gifts_count", count: "kick_session_gifts_count" },
89
+ "follower-total": { amount: "kick_total_follower_count", count: "kick_total_follower_count" },
90
+ "subscriber-total": { amount: "kick_total_subscriber_count", count: "kick_total_subscriber_count" },
91
+ "cheer-total": { amount: "kick_total_kicks_count", count: "kick_total_kicks_count" },
92
+ "follower-week": { amount: "kick_week_follower_count", count: "kick_week_follower_count" },
93
+ "follower-month": { amount: "kick_month_follower_count", count: "kick_month_follower_count" },
94
+ "subscriber-week": { amount: "kick_week_subscriber_count", count: "kick_week_subscriber_count" },
95
+ "subscriber-month": { amount: "kick_month_subscriber_count", count: "kick_month_subscriber_count" }
96
+ };
97
+ var YOUTUBE_CONTEXTS = {
98
+ "follower-latest": { name: "youtube_last_subscriber", username: "youtube_last_subscriber" },
99
+ "subscriber-latest": { name: "youtube_last_member", username: "youtube_last_member" },
100
+ "subscriber-new-latest": { name: "youtube_last_member", username: "youtube_last_member" },
101
+ "subscriber-resub-latest": { name: "youtube_last_member", username: "youtube_last_member" },
102
+ "subscriber-gifted-latest": { name: "youtube_last_gift_member", username: "youtube_last_gift_member" },
103
+ "cheer-latest": { name: "youtube_last_superchatter", username: "youtube_last_superchatter" },
104
+ "follower-session": { amount: "youtube_session_subscriber_count", count: "youtube_session_subscriber_count" },
105
+ "subscriber-session": { amount: "youtube_session_member_count", count: "youtube_session_member_count" },
106
+ "cheer-session": { amount: "youtube_session_superchat_count", count: "youtube_session_superchat_count" },
107
+ "follower-total": { amount: "youtube_total_subscriber_count", count: "youtube_total_subscriber_count" },
108
+ "subscriber-total": { amount: "youtube_total_member_count", count: "youtube_total_member_count" },
109
+ "follower-week": { amount: "youtube_week_subscriber_count", count: "youtube_week_subscriber_count" },
110
+ "follower-month": { amount: "youtube_month_subscriber_count", count: "youtube_month_subscriber_count" },
111
+ "subscriber-week": { amount: "youtube_week_member_count", count: "youtube_week_member_count" },
112
+ "subscriber-month": { amount: "youtube_month_member_count", count: "youtube_month_member_count" }
113
+ };
114
+ function listenerPlaceholderContext(listener, provider = "twitch") {
115
+ const platformTable = provider === "kick" ? KICK_CONTEXTS : provider === "youtube" ? YOUTUBE_CONTEXTS : null;
116
+ return platformTable?.[listener] ?? TWITCH_CONTEXTS[listener] ?? {};
58
117
  }
59
118
  var SE_WIDGET_TYPE_LABELS = {
60
119
  "subscriber-latest": "Latest subscriber",
@@ -90,17 +149,57 @@ function defaultLabelForSEWidget(widget) {
90
149
  const tail = words.slice(1).join(" ");
91
150
  return tail ? `${head} ${tail}` : head;
92
151
  }
93
- function translateSeText(input, listener, passthrough) {
152
+ function translateSeText(input, listener, passthrough, provider) {
94
153
  if (!input) return "";
95
- const ctx = listener ? listenerPlaceholderContext(listener) : {};
154
+ const ctx = listener ? listenerPlaceholderContext(listener, provider) : {};
96
155
  return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
97
156
  if (passthrough?.has(raw)) return `{{${raw}}}`;
98
157
  const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
99
158
  return `{{${mapped}}}`;
100
159
  });
101
160
  }
102
- function listenerToLumiaVariable(listener) {
161
+ var KICK_LISTENER_VARS = {
162
+ "follower-latest": "kick_last_follower",
163
+ "subscriber-latest": "kick_last_subscriber",
164
+ "subscriber-new-latest": "kick_last_subscriber",
165
+ "subscriber-resub-latest": "kick_last_subscriber",
166
+ "subscriber-gifted-latest": "kick_last_gifter",
167
+ "cheer-latest": "kick_last_kicks",
168
+ "raid-latest": "kick_last_host",
169
+ "follower-session": "kick_session_follower_count",
170
+ "follower-week": "kick_week_follower_count",
171
+ "follower-month": "kick_month_follower_count",
172
+ "follower-total": "kick_total_follower_count",
173
+ "subscriber-session": "kick_session_subscriber_count",
174
+ "subscriber-week": "kick_week_subscriber_count",
175
+ "subscriber-month": "kick_month_subscriber_count",
176
+ "subscriber-total": "kick_total_subscriber_count",
177
+ "subscriber-gifted-session": "kick_session_gifts_count",
178
+ "cheer-session": "kick_session_kicks_count",
179
+ "cheer-total": "kick_total_kicks_count"
180
+ };
181
+ var YOUTUBE_LISTENER_VARS = {
182
+ "follower-latest": "youtube_last_subscriber",
183
+ "subscriber-latest": "youtube_last_member",
184
+ "subscriber-new-latest": "youtube_last_member",
185
+ "subscriber-resub-latest": "youtube_last_member",
186
+ "subscriber-gifted-latest": "youtube_last_gift_member",
187
+ "cheer-latest": "youtube_last_superchatter",
188
+ "follower-session": "youtube_session_subscriber_count",
189
+ "follower-week": "youtube_week_subscriber_count",
190
+ "follower-month": "youtube_month_subscriber_count",
191
+ "follower-total": "youtube_total_subscriber_count",
192
+ "subscriber-session": "youtube_session_member_count",
193
+ "subscriber-week": "youtube_week_member_count",
194
+ "subscriber-month": "youtube_month_member_count",
195
+ "subscriber-total": "youtube_total_member_count",
196
+ "subscriber-gifted-session": "youtube_session_gift_members_count",
197
+ "cheer-session": "youtube_session_superchat_count"
198
+ };
199
+ function listenerToLumiaVariable(listener, provider = "twitch") {
103
200
  if (typeof listener !== "string" || !listener) return "username";
201
+ if (provider === "kick" && KICK_LISTENER_VARS[listener]) return KICK_LISTENER_VARS[listener];
202
+ if (provider === "youtube" && YOUTUBE_LISTENER_VARS[listener]) return YOUTUBE_LISTENER_VARS[listener];
104
203
  const m = {
105
204
  // Latest events
106
205
  "follower-latest": "twitch_last_follower",
@@ -398,10 +497,10 @@ function mapReadout(widget, fallbackVar, ctx) {
398
497
  const explicit = typeof explicitListener === "string" ? explicitListener : null;
399
498
  const widgetListener = typeof widget.listener === "string" ? widget.listener : null;
400
499
  const listener = explicit ?? widgetListener ?? fallbackVar;
401
- const variable = listenerToLumiaVariable(listener);
500
+ const variable = listenerToLumiaVariable(listener, ctx?.provider);
402
501
  const userTemplate = widget.text?.value ?? "";
403
502
  const inheritsText = userTemplate.trim().length > 0 && /\{[a-zA-Z]/.test(userTemplate);
404
- const value = inheritsText ? translateSeText(userTemplate, listener) : `{{${variable}}}`;
503
+ const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider) : `{{${variable}}}`;
405
504
  const seCss = widget.text?.css ?? {};
406
505
  return buildUnit(widget, "text", {
407
506
  content: { value, highlightColor: "inherit" },
@@ -419,34 +518,99 @@ function mapReadout(widget, fallbackVar, ctx) {
419
518
  }, ctx);
420
519
  }
421
520
 
422
- // src/se-import/mappers/alert.ts
423
- var SE_EVENT_TO_LUMIA_ALERT = {
424
- follower: "twitch-follower",
425
- subscriber: "twitch-subscriber",
426
- tip: "streamelements-donation",
427
- cheer: "twitch-bits",
428
- raid: "twitch-raid",
429
- // Merch / shop-order — Lumia's Fourthwall integration is the data path most
430
- // users now have wired up since SE deprecated their own merch flow in favor
431
- // of Fourthwall. `fourthwall-shopOrder` is the alert key from
432
- // LumiaAlertValues / activity.types.ts.
433
- merch: "fourthwall-shopOrder",
434
- purchase: "fourthwall-shopOrder",
435
- charityCampaignDonation: "twitch-charityDonation"
436
- // NOTE — two SE events intentionally NOT mapped:
437
- // • `host` → previously `twitch-raid`. Twitch deprecated hosting in
438
- // late 2022 so the event never fires on the platform anymore. Even
439
- // if it did, host payloads don't carry a viewer count (raids do),
440
- // so the rendered alert would read "{name} raided with ?? viewers".
441
- // Drop the mapping; SE widgets configured with a `host` block fall
442
- // through to the unsupported placeholder rather than producing
443
- // misleading raid alerts.
444
- // • `redemption` → SE's `redemption` event is the legacy SE Store
445
- // redemption (their own loyalty-point store purchases), NOT Twitch
446
- // channel-point redemptions. Different payload, different semantics.
447
- // SE Store is mostly deprecated and seldom used; dropping the
448
- // mapping prevents misleading "channel-point redemption" alerts.
521
+ // src/se-import/mappers/provider-alerts.ts
522
+ var ALERT_BOX_KEYS = {
523
+ twitch: {
524
+ follower: "twitch-follower",
525
+ subscriber: "twitch-subscriber",
526
+ tip: "streamelements-donation",
527
+ cheer: "twitch-bits",
528
+ raid: "twitch-raid",
529
+ merch: "fourthwall-shopOrder",
530
+ purchase: "fourthwall-shopOrder",
531
+ charityCampaignDonation: "twitch-charityDonation"
532
+ },
533
+ kick: {
534
+ follower: "kick-follower",
535
+ subscriber: "kick-subscriber",
536
+ tip: "streamelements-donation",
537
+ cheer: "kick-kicks",
538
+ raid: "kick-host",
539
+ host: "kick-host",
540
+ merch: "fourthwall-shopOrder",
541
+ purchase: "fourthwall-shopOrder"
542
+ },
543
+ youtube: {
544
+ follower: "youtube-subscriber",
545
+ subscriber: "youtube-member",
546
+ tip: "streamelements-donation",
547
+ cheer: "youtube-superchat",
548
+ merch: "fourthwall-shopOrder",
549
+ purchase: "fourthwall-shopOrder"
550
+ }
551
+ };
552
+ var KAPPAGEN_KEYS = {
553
+ twitch: {
554
+ follower: "twitch-follower",
555
+ subscriber: "twitch-subscriber",
556
+ cheer: "twitch-bits",
557
+ raid: "twitch-raid",
558
+ tip: "streamelements-donation"
559
+ },
560
+ kick: {
561
+ follower: "kick-follower",
562
+ subscriber: "kick-subscriber",
563
+ cheer: "kick-kicks",
564
+ raid: "kick-host",
565
+ tip: "streamelements-donation"
566
+ },
567
+ youtube: {
568
+ follower: "youtube-subscriber",
569
+ subscriber: "youtube-member",
570
+ cheer: "youtube-superchat",
571
+ tip: "streamelements-donation"
572
+ }
449
573
  };
574
+ var STREAMBOSS_LISTENER_KEYS = {
575
+ twitch: {
576
+ "follower-latest": "twitch-follower",
577
+ "subscriber-latest": "twitch-subscriber",
578
+ "tip-latest": "lumiastream-donation",
579
+ "cheer-latest": "twitch-bits",
580
+ "raid-latest": "twitch-raid"
581
+ },
582
+ kick: {
583
+ "follower-latest": "kick-follower",
584
+ "subscriber-latest": "kick-subscriber",
585
+ "tip-latest": "lumiastream-donation",
586
+ "cheer-latest": "kick-kicks",
587
+ "raid-latest": "kick-host"
588
+ },
589
+ youtube: {
590
+ "follower-latest": "youtube-subscriber",
591
+ "subscriber-latest": "youtube-member",
592
+ "tip-latest": "lumiastream-donation",
593
+ "cheer-latest": "youtube-superchat"
594
+ }
595
+ };
596
+ function alertBoxKeyFor(seEvent, provider = "twitch") {
597
+ return ALERT_BOX_KEYS[provider]?.[seEvent];
598
+ }
599
+ function kappagenKeyFor(seEvent, provider = "twitch") {
600
+ return KAPPAGEN_KEYS[provider]?.[seEvent];
601
+ }
602
+ function streamBossListenerKeyFor(seListener, provider = "twitch") {
603
+ return STREAMBOSS_LISTENER_KEYS[provider]?.[seListener];
604
+ }
605
+ function readBootstrapProvider(bootstrap) {
606
+ const ch = bootstrap.channel;
607
+ if (!ch || typeof ch !== "object") return "twitch";
608
+ const p = ch.provider;
609
+ if (p === "kick" || p === "youtube" || p === "twitch") return p;
610
+ return "twitch";
611
+ }
612
+
613
+ // src/se-import/mappers/alert.ts
450
614
  function pickGraphics(g) {
451
615
  const DEFAULT_GRAPHIC_VOLUME = 0.5;
452
616
  if (!g) return { type: "image", content: { src: "", loop: false, volume: DEFAULT_GRAPHIC_VOLUME } };
@@ -629,12 +793,12 @@ function pickBaseAlertFields(event) {
629
793
  function mapAlertBox(widget, opts = {}, ctx) {
630
794
  const v = widget.variables ?? {};
631
795
  const events = {};
632
- const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "merch", "purchase", "charityCampaignDonation"];
796
+ const eventKeys = opts.onlyEvents ?? ["follower", "subscriber", "tip", "cheer", "raid", "host", "merch", "purchase", "charityCampaignDonation"];
633
797
  let firstEvent = null;
634
798
  for (const seKey of eventKeys) {
635
799
  const ev = v[seKey];
636
800
  if (!ev || typeof ev !== "object") continue;
637
- const lumiaAlert = SE_EVENT_TO_LUMIA_ALERT[seKey];
801
+ const lumiaAlert = alertBoxKeyFor(seKey, ctx?.provider);
638
802
  if (!lumiaAlert) continue;
639
803
  const built = buildAlertEvent(ev, seKey);
640
804
  events[lumiaAlert] = built;
@@ -783,6 +947,7 @@ function mapGoal(widget, ctx) {
783
947
  }
784
948
 
785
949
  // src/se-import/mappers/custom.ts
950
+ import { nanoid as nanoid2 } from "nanoid";
786
951
  var SOURCE_TO_LUMIA_FIELD_TYPE = {
787
952
  text: "input",
788
953
  number: "number",
@@ -842,15 +1007,28 @@ function mapCustom(widget, ctx) {
842
1007
  };
843
1008
  const fields = parse(v.fields) ?? v.fields;
844
1009
  const fieldData = parse(v.fieldData) ?? v.fieldData;
1010
+ const widgetProvider = widget.provider;
1011
+ const provider = widgetProvider === "kick" || widgetProvider === "youtube" || widgetProvider === "twitch" ? widgetProvider : ctx?.provider ?? "twitch";
845
1012
  return buildUnit(widget, "custom", {
846
1013
  content: {
847
- codeId: String(widget.id),
1014
+ // SE widget ids are integers unique only within a single SE overlay
1015
+ // (1, 2, 3…). Using them as Lumia codeIds would collide across
1016
+ // imports — codeId is the scoping key for variables, storage, and
1017
+ // event listeners. Mint a fresh nanoid so every imported widget
1018
+ // has its own scope.
1019
+ codeId: nanoid2(),
848
1020
  html: v.html ?? "",
849
1021
  css: v.css ?? "",
850
1022
  js: v.js ?? "",
851
1023
  configs: translateConfigs(fields),
852
1024
  data: fieldData ?? {},
853
- flavor: "streamelements"
1025
+ flavor: "streamelements",
1026
+ // Native field on the Custom module content. The compatibility
1027
+ // shim in Embed.tsx reads this to pick the right listener-name
1028
+ // mapping table when forwarding Lumia events into the iframe as
1029
+ // `onEventReceived` payloads. Default 'twitch' matches the shim's
1030
+ // historical default so legacy imports keep working.
1031
+ sourceProvider: provider
854
1032
  }
855
1033
  }, ctx);
856
1034
  }
@@ -1482,27 +1660,6 @@ function mapMediaShare(widget, ctx) {
1482
1660
  }
1483
1661
  }, ctx);
1484
1662
  }
1485
- var SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT = {
1486
- follower: "twitch-follower",
1487
- subscriber: "twitch-subscriber",
1488
- cheer: "twitch-bits",
1489
- raid: "twitch-raid",
1490
- tip: "streamelements-donation"
1491
- // NOTE — three SE events intentionally NOT mapped:
1492
- // • `host` → previously `twitch-host`, which doesn't exist in
1493
- // LumiaAlertValues (verified). Twitch deprecated hosting
1494
- // in 2022 so the event never fires anyway.
1495
- // • `purchase` → previously `streamlabs-merch`, which is deprecated. The
1496
- // main Alert Box mapper routes merch/purchase to
1497
- // `fourthwall-shopOrder`; keep Kappagen consistent by
1498
- // dropping the legacy SL path rather than splitting
1499
- // emoji emotion between two competing alert keys.
1500
- // • `embers` → previously `youtube-superchat`, which is wrong on both
1501
- // ends: embers were a deprecated Twitch paid-emote
1502
- // feature unrelated to YouTube super chats.
1503
- // All three drop silently; the rest of the widget (size, spawn zone,
1504
- // per-event flags for the remaining events) still imports.
1505
- };
1506
1663
  function mapKappagen(widget, ctx) {
1507
1664
  const v = widget.variables ?? {};
1508
1665
  const eventsFromNested = v.eventsConfig ? {
@@ -1513,7 +1670,7 @@ function mapKappagen(widget, ctx) {
1513
1670
  tip: v.eventsConfig.tip?.enabled ?? true
1514
1671
  } : null;
1515
1672
  const events = v.events ?? eventsFromNested ?? { subscriber: true, follower: true, cheer: true, raid: true, tip: true };
1516
- const alertTriggers = Object.entries(events).filter(([, enabled]) => !!enabled).map(([seKey]) => SE_KAPPAGEN_EVENT_TO_LUMIA_ALERT[seKey]).filter((x) => !!x);
1673
+ const alertTriggers = Object.entries(events).filter(([, enabled]) => !!enabled).map(([seKey]) => kappagenKeyFor(seKey, ctx?.provider)).filter((x) => !!x);
1517
1674
  const alertEmotes = ["Kappa", "<3", "imGlitch"];
1518
1675
  return buildUnit(
1519
1676
  widget,
@@ -1903,16 +2060,7 @@ function parsePx(raw) {
1903
2060
  }
1904
2061
 
1905
2062
  // src/se-import/mappers/streamboss.ts
1906
- var SE_LISTENER_TO_ALERT = {
1907
- "follower-latest": "twitch-follower",
1908
- "subscriber-latest": "twitch-subscriber",
1909
- "tip-latest": "lumiastream-donation",
1910
- "cheer-latest": "twitch-bits",
1911
- "raid-latest": "twitch-raid"
1912
- // `purchase-latest` and `subscriber-gifted-latest` are intentionally absent.
1913
- // SE rolled gifted subs into `subscriber-latest`; Lumia splits them via
1914
- // extraSettings.isGift, which the runtime already handles consistently.
1915
- };
2063
+ var SE_LISTENERS = ["follower-latest", "subscriber-latest", "tip-latest", "cheer-latest", "raid-latest"];
1916
2064
  var SE_LISTENER_MODE = {
1917
2065
  "follower-latest": "flat",
1918
2066
  "subscriber-latest": "flat",
@@ -1934,8 +2082,10 @@ function mapStreamBoss(widget, ctx) {
1934
2082
  const seDamage = v.damage ?? {};
1935
2083
  const damageMap = {};
1936
2084
  const selectedEvents = [];
1937
- for (const [seListener, alertKey] of Object.entries(SE_LISTENER_TO_ALERT)) {
2085
+ for (const seListener of SE_LISTENERS) {
1938
2086
  if (!listenerIsOn(widget.listeners, seListener)) continue;
2087
+ const alertKey = streamBossListenerKeyFor(seListener, ctx?.provider);
2088
+ if (!alertKey) continue;
1939
2089
  const raw = seDamage[seListener];
1940
2090
  const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
1941
2091
  if (value === null) continue;
@@ -2243,7 +2393,7 @@ function guessMime(name) {
2243
2393
  }
2244
2394
 
2245
2395
  // src/se-import/transplant.ts
2246
- import { nanoid as nanoid2 } from "nanoid";
2396
+ import { nanoid as nanoid3 } from "nanoid";
2247
2397
  function cloneModuleForTransplant(srcModule, newId) {
2248
2398
  const clonedContent = srcModule.content ? structuredClone(srcModule.content) : srcModule.content;
2249
2399
  if (srcModule.settings?.type === "custom" && clonedContent) {
@@ -2261,7 +2411,7 @@ function transplantLayer(source, sourceLayerId) {
2261
2411
  const srcLayer = layers.find((l) => l.id === sourceLayerId);
2262
2412
  const srcModule = modules[sourceLayerId];
2263
2413
  if (!srcLayer || !srcModule) return null;
2264
- const rootNewId = nanoid2();
2414
+ const rootNewId = nanoid3();
2265
2415
  if (srcLayer.type !== "group") {
2266
2416
  const layer = { ...srcLayer, id: rootNewId };
2267
2417
  const module = cloneModuleForTransplant(srcModule, rootNewId);
@@ -2279,7 +2429,7 @@ function transplantLayer(source, sourceLayerId) {
2279
2429
  if (seen.has(child.id)) continue;
2280
2430
  seen.add(child.id);
2281
2431
  descendants.push(child);
2282
- idRemap.set(child.id, nanoid2());
2432
+ idRemap.set(child.id, nanoid3());
2283
2433
  if (child.type === "group") queue.push(child.id);
2284
2434
  }
2285
2435
  }
@@ -7005,18 +7155,20 @@ function importSEBootstrap(bootstrap) {
7005
7155
  mappings: [],
7006
7156
  notes: []
7007
7157
  };
7158
+ const provider = readBootstrapProvider(bootstrap);
7008
7159
  console.log("[SE-IMPORT] bootstrap received", {
7009
7160
  overlayId: seOverlay?._id,
7010
7161
  name: seOverlay?.name,
7011
7162
  canvas: { width: seOverlay?.settings?.width, height: seOverlay?.settings?.height },
7012
7163
  widgetCount: widgets.length,
7013
- widgetTypes: widgets.map((w) => w.type)
7164
+ widgetTypes: widgets.map((w) => w.type),
7165
+ provider
7014
7166
  });
7015
7167
  const importCanvas = {
7016
7168
  width: seOverlay?.settings?.width ?? 1920,
7017
7169
  height: seOverlay?.settings?.height ?? 1080
7018
7170
  };
7019
- const ctx = { canvas: importCanvas };
7171
+ const ctx = { canvas: importCanvas, provider };
7020
7172
  const layers = [];
7021
7173
  const modules = {};
7022
7174
  const reviewItems = [];
@@ -7026,7 +7178,7 @@ function importSEBootstrap(bootstrap) {
7026
7178
  if (w.type !== "se-widget-group") continue;
7027
7179
  const uid = w.variables?.uid;
7028
7180
  if (typeof uid !== "string") continue;
7029
- const lumiaGroupId = nanoid3();
7181
+ const lumiaGroupId = nanoid4();
7030
7182
  seUidToLumiaGroupId[uid] = lumiaGroupId;
7031
7183
  groupWidgetByUid[uid] = w;
7032
7184
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumiastream/ui",
3
- "version": "0.2.8-alpha.22",
3
+ "version": "0.2.8-alpha.24",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",