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

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 +245 -107
  2. package/dist/se-import.js +245 -107
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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
+ }
3564
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
+ }
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;
@@ -4597,27 +4761,6 @@ function mapMediaShare(widget, ctx) {
4597
4761
  }
4598
4762
  }, ctx);
4599
4763
  }
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
4764
  function mapKappagen(widget, ctx) {
4622
4765
  const v = widget.variables ?? {};
4623
4766
  const eventsFromNested = v.eventsConfig ? {
@@ -4628,7 +4771,7 @@ function mapKappagen(widget, ctx) {
4628
4771
  tip: v.eventsConfig.tip?.enabled ?? true
4629
4772
  } : null;
4630
4773
  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);
4774
+ const alertTriggers = Object.entries(events).filter(([, enabled]) => !!enabled).map(([seKey]) => kappagenKeyFor(seKey, ctx?.provider)).filter((x) => !!x);
4632
4775
  const alertEmotes = ["Kappa", "<3", "imGlitch"];
4633
4776
  return buildUnit(
4634
4777
  widget,
@@ -5018,16 +5161,7 @@ function parsePx(raw) {
5018
5161
  }
5019
5162
 
5020
5163
  // 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
- };
5164
+ var SE_LISTENERS = ["follower-latest", "subscriber-latest", "tip-latest", "cheer-latest", "raid-latest"];
5031
5165
  var SE_LISTENER_MODE = {
5032
5166
  "follower-latest": "flat",
5033
5167
  "subscriber-latest": "flat",
@@ -5049,8 +5183,10 @@ function mapStreamBoss(widget, ctx) {
5049
5183
  const seDamage = v.damage ?? {};
5050
5184
  const damageMap = {};
5051
5185
  const selectedEvents = [];
5052
- for (const [seListener, alertKey] of Object.entries(SE_LISTENER_TO_ALERT)) {
5186
+ for (const seListener of SE_LISTENERS) {
5053
5187
  if (!listenerIsOn(widget.listeners, seListener)) continue;
5188
+ const alertKey = streamBossListenerKeyFor(seListener, ctx?.provider);
5189
+ if (!alertKey) continue;
5054
5190
  const raw = seDamage[seListener];
5055
5191
  const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
5056
5192
  if (value === null) continue;
@@ -8135,18 +8271,20 @@ function importSEBootstrap(bootstrap) {
8135
8271
  mappings: [],
8136
8272
  notes: []
8137
8273
  };
8274
+ const provider = readBootstrapProvider(bootstrap);
8138
8275
  console.log("[SE-IMPORT] bootstrap received", {
8139
8276
  overlayId: seOverlay?._id,
8140
8277
  name: seOverlay?.name,
8141
8278
  canvas: { width: seOverlay?.settings?.width, height: seOverlay?.settings?.height },
8142
8279
  widgetCount: widgets.length,
8143
- widgetTypes: widgets.map((w) => w.type)
8280
+ widgetTypes: widgets.map((w) => w.type),
8281
+ provider
8144
8282
  });
8145
8283
  const importCanvas = {
8146
8284
  width: seOverlay?.settings?.width ?? 1920,
8147
8285
  height: seOverlay?.settings?.height ?? 1080
8148
8286
  };
8149
- const ctx = { canvas: importCanvas };
8287
+ const ctx = { canvas: importCanvas, provider };
8150
8288
  const layers = [];
8151
8289
  const modules = {};
8152
8290
  const reviewItems = [];
package/dist/se-import.js CHANGED
@@ -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" };
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"
23
75
  }
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" };
29
- }
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
+ }
449
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
+ }
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;
@@ -1482,27 +1646,6 @@ function mapMediaShare(widget, ctx) {
1482
1646
  }
1483
1647
  }, ctx);
1484
1648
  }
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
1649
  function mapKappagen(widget, ctx) {
1507
1650
  const v = widget.variables ?? {};
1508
1651
  const eventsFromNested = v.eventsConfig ? {
@@ -1513,7 +1656,7 @@ function mapKappagen(widget, ctx) {
1513
1656
  tip: v.eventsConfig.tip?.enabled ?? true
1514
1657
  } : null;
1515
1658
  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);
1659
+ const alertTriggers = Object.entries(events).filter(([, enabled]) => !!enabled).map(([seKey]) => kappagenKeyFor(seKey, ctx?.provider)).filter((x) => !!x);
1517
1660
  const alertEmotes = ["Kappa", "<3", "imGlitch"];
1518
1661
  return buildUnit(
1519
1662
  widget,
@@ -1903,16 +2046,7 @@ function parsePx(raw) {
1903
2046
  }
1904
2047
 
1905
2048
  // 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
- };
2049
+ var SE_LISTENERS = ["follower-latest", "subscriber-latest", "tip-latest", "cheer-latest", "raid-latest"];
1916
2050
  var SE_LISTENER_MODE = {
1917
2051
  "follower-latest": "flat",
1918
2052
  "subscriber-latest": "flat",
@@ -1934,8 +2068,10 @@ function mapStreamBoss(widget, ctx) {
1934
2068
  const seDamage = v.damage ?? {};
1935
2069
  const damageMap = {};
1936
2070
  const selectedEvents = [];
1937
- for (const [seListener, alertKey] of Object.entries(SE_LISTENER_TO_ALERT)) {
2071
+ for (const seListener of SE_LISTENERS) {
1938
2072
  if (!listenerIsOn(widget.listeners, seListener)) continue;
2073
+ const alertKey = streamBossListenerKeyFor(seListener, ctx?.provider);
2074
+ if (!alertKey) continue;
1939
2075
  const raw = seDamage[seListener];
1940
2076
  const value = typeof raw === "number" && Number.isFinite(raw) && raw > 0 ? raw : null;
1941
2077
  if (value === null) continue;
@@ -7005,18 +7141,20 @@ function importSEBootstrap(bootstrap) {
7005
7141
  mappings: [],
7006
7142
  notes: []
7007
7143
  };
7144
+ const provider = readBootstrapProvider(bootstrap);
7008
7145
  console.log("[SE-IMPORT] bootstrap received", {
7009
7146
  overlayId: seOverlay?._id,
7010
7147
  name: seOverlay?.name,
7011
7148
  canvas: { width: seOverlay?.settings?.width, height: seOverlay?.settings?.height },
7012
7149
  widgetCount: widgets.length,
7013
- widgetTypes: widgets.map((w) => w.type)
7150
+ widgetTypes: widgets.map((w) => w.type),
7151
+ provider
7014
7152
  });
7015
7153
  const importCanvas = {
7016
7154
  width: seOverlay?.settings?.width ?? 1920,
7017
7155
  height: seOverlay?.settings?.height ?? 1080
7018
7156
  };
7019
- const ctx = { canvas: importCanvas };
7157
+ const ctx = { canvas: importCanvas, provider };
7020
7158
  const layers = [];
7021
7159
  const modules = {};
7022
7160
  const reviewItems = [];
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.23",
4
4
  "author": "Lumia Stream",
5
5
  "license": "ISC",
6
6
  "description": "Lumia UI Kit",