@lumiastream/ui 0.2.8-alpha.27 → 0.2.8-alpha.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +195 -55
- package/dist/se-import.js +195 -55
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3133,31 +3133,86 @@ var SE_TO_LUMIA_PLACEHOLDER = {
|
|
|
3133
3133
|
};
|
|
3134
3134
|
var TWITCH_CONTEXTS = {
|
|
3135
3135
|
"follower-latest": { name: "twitch_last_follower", username: "twitch_last_follower" },
|
|
3136
|
+
// Three SE listeners with genuinely distinct semantics — preserve the
|
|
3137
|
+
// distinction in the variable mapping so a widget configured for "Latest
|
|
3138
|
+
// new subscriber" doesn't render the latest *resub* (and vice versa).
|
|
3139
|
+
// `subscriber-latest` is the umbrella (anyone subscribing); the other two
|
|
3140
|
+
// are subtype-specific. The dedicated TWITCH_LAST_{NEW_SUBSCRIBER,
|
|
3141
|
+
// RESUBSCRIBER} variables are written by the Twitch event pipeline on
|
|
3142
|
+
// the matching sub.type event.
|
|
3136
3143
|
"subscriber-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
|
|
3137
|
-
"subscriber-new-latest": { name: "
|
|
3138
|
-
"subscriber-resub-latest": { name: "
|
|
3139
|
-
|
|
3144
|
+
"subscriber-new-latest": { name: "twitch_last_new_subscriber", username: "twitch_last_new_subscriber" },
|
|
3145
|
+
"subscriber-resub-latest": { name: "twitch_last_resubscriber", username: "twitch_last_resubscriber" },
|
|
3146
|
+
// `subscriber-gifted-latest`: SE uses `{sender}` for the giver and `{name}` for
|
|
3147
|
+
// the recipient (e.g. "{sender} gifted to {name}"). The two roles must point
|
|
3148
|
+
// at distinct Lumia variables — collapsing both onto `twitch_last_gifter`
|
|
3149
|
+
// rendered "Mr.Beast gifted to Mr.Beast" while the actual recipient was
|
|
3150
|
+
// nowhere. `sender` keeps the gifter; `name` / `username` switch to the
|
|
3151
|
+
// dedicated recipient variable `twitch_last_gifted`. Amount stays on the
|
|
3152
|
+
// gifter (it's the count of subs in that drop, owned by the gifter).
|
|
3153
|
+
"subscriber-gifted-latest": { name: "twitch_last_gifted", username: "twitch_last_gifted", sender: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
|
|
3140
3154
|
"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
3155
|
"cheer-latest": { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" },
|
|
3142
3156
|
"tip-latest": { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
|
|
3143
3157
|
latest_donator: { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
|
|
3144
3158
|
"raid-latest": { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" },
|
|
3145
|
-
//
|
|
3159
|
+
// Period counters: count/amount = the running counter for that window.
|
|
3160
|
+
// SE lets the streamer pick session / week / month / total on the same widget
|
|
3161
|
+
// type; the chosen period lands in `widget.listener` (e.g. `cheer-week`).
|
|
3162
|
+
// Each entry below mirrors a row in the Lumia variables table — the previous
|
|
3163
|
+
// table only carried session+total and broke any widget configured for
|
|
3164
|
+
// week/month because `{amount}` fell through to the unscoped placeholder
|
|
3165
|
+
// and rendered blank.
|
|
3146
3166
|
"follower-session": { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" },
|
|
3147
|
-
"
|
|
3148
|
-
"
|
|
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.
|
|
3167
|
+
"follower-week": { amount: "twitch_week_follower_count", count: "twitch_week_follower_count" },
|
|
3168
|
+
"follower-month": { amount: "twitch_month_follower_count", count: "twitch_month_follower_count" },
|
|
3151
3169
|
"follower-total": { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" },
|
|
3170
|
+
"subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
|
|
3171
|
+
"subscriber-week": { amount: "twitch_week_subscriber_count", count: "twitch_week_subscriber_count" },
|
|
3172
|
+
"subscriber-month": { amount: "twitch_month_subscriber_count", count: "twitch_month_subscriber_count" },
|
|
3152
3173
|
"subscriber-total": { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" },
|
|
3174
|
+
"subscriber-new-session": { amount: "twitch_session_new_subscribers_count", count: "twitch_session_new_subscribers_count" },
|
|
3175
|
+
"subscriber-resub-session": { amount: "twitch_session_resub_subscribers_count", count: "twitch_session_resub_subscribers_count" },
|
|
3176
|
+
"subscriber-gifted-session": { amount: "twitch_session_gifted_subscribers_count", count: "twitch_session_gifted_subscribers_count" },
|
|
3177
|
+
"cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
|
|
3178
|
+
"cheer-week": { amount: "twitch_week_bits_count", count: "twitch_week_bits_count" },
|
|
3179
|
+
"cheer-month": { amount: "twitch_month_bits_count", count: "twitch_month_bits_count" },
|
|
3153
3180
|
"cheer-total": { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" },
|
|
3181
|
+
"tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
|
|
3182
|
+
"tip-week": { amount: "week_donation_amount", count: "week_donation_count", currency: "week_donation_amount_currency_symbol" },
|
|
3183
|
+
"tip-month": { amount: "month_donation_amount", count: "month_donation_count", currency: "month_donation_amount_currency_symbol" },
|
|
3154
3184
|
"tip-total": { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" },
|
|
3155
3185
|
"tip-count": { amount: "donation_count", count: "donation_count" },
|
|
3156
|
-
// Top cheer / cheerer / donator
|
|
3157
|
-
|
|
3186
|
+
// Top cheer / cheerer / donator readouts — per period. SE's `se-widget-top-*`
|
|
3187
|
+
// widgets encode the chosen period (session / weekly / monthly / alltime) in
|
|
3188
|
+
// the widget's `listener` field. Without these entries the user's `{amount}`
|
|
3189
|
+
// / `{name}` template tokens fall through to the unscoped placeholder map
|
|
3190
|
+
// (`{{amount}}` only resolves inside alert variations) and render blank.
|
|
3191
|
+
//
|
|
3192
|
+
// `*-top-donation` and `*-top-donator` are paired: SE nominally treats them
|
|
3193
|
+
// as "top single event" vs "top cumulative contributor", but the data
|
|
3194
|
+
// rendered for both lives in the same Lumia variable pair (`*_top_donator`
|
|
3195
|
+
// / `*_top_donator_amount`, `twitch_*_top_cheerer` / `..._amount`). SE
|
|
3196
|
+
// widgets routinely use `{name}` against the `-donation` listener to mean
|
|
3197
|
+
// "the user who made the top donation", so the two rows below carry the
|
|
3198
|
+
// same name/username mapping — anything else falls through to the unscoped
|
|
3199
|
+
// `{{username}}` and renders blank.
|
|
3200
|
+
"cheer-session-top-donation": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
|
|
3158
3201
|
"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
|
-
"
|
|
3202
|
+
"cheer-weekly-top-donation": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
|
|
3203
|
+
"cheer-weekly-top-donator": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
|
|
3204
|
+
"cheer-monthly-top-donation": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
|
|
3205
|
+
"cheer-monthly-top-donator": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
|
|
3206
|
+
"cheer-alltime-top-donation": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
|
|
3207
|
+
"cheer-alltime-top-donator": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
|
|
3208
|
+
"tip-session-top-donation": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
|
|
3160
3209
|
"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" },
|
|
3210
|
+
"tip-weekly-top-donation": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
|
|
3211
|
+
"tip-weekly-top-donator": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
|
|
3212
|
+
"tip-monthly-top-donation": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
|
|
3213
|
+
"tip-monthly-top-donator": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
|
|
3214
|
+
"tip-alltime-top-donation": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
|
|
3215
|
+
"tip-alltime-top-donator": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
|
|
3161
3216
|
// Botcounter — SE uses `{wins}` for the running chat count; the dispatcher's
|
|
3162
3217
|
// fallbackVar for `se-widget-botcounter` is `twitch_session_chat_count`, so
|
|
3163
3218
|
// when the listener arg lands as that var name we treat it as the wins source.
|
|
@@ -3194,7 +3249,11 @@ var KICK_CONTEXTS = {
|
|
|
3194
3249
|
"subscriber-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
|
|
3195
3250
|
"subscriber-new-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
|
|
3196
3251
|
"subscriber-resub-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
|
|
3197
|
-
|
|
3252
|
+
// Mirror the Twitch split: `{sender}` = giver, `{name}` = recipient.
|
|
3253
|
+
// `kick_last_gifted` was added alongside this mapping; see twitch context
|
|
3254
|
+
// above for the rationale (without the split, SE's "{sender} gifted to
|
|
3255
|
+
// {name}" renders the gifter on both sides).
|
|
3256
|
+
"subscriber-gifted-latest": { name: "kick_last_gifted", username: "kick_last_gifted", sender: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
|
|
3198
3257
|
"cheer-latest": { name: "kick_last_kicks", username: "kick_last_kicks", amount: "kick_last_kicks_amount", count: "kick_last_kicks_amount" },
|
|
3199
3258
|
"raid-latest": { name: "kick_last_host", username: "kick_last_host", amount: "kick_last_host_amount", count: "kick_last_host_amount" },
|
|
3200
3259
|
"follower-session": { amount: "kick_session_follower_count", count: "kick_session_follower_count" },
|
|
@@ -3272,12 +3331,13 @@ function defaultLabelForSEWidget(widget) {
|
|
|
3272
3331
|
const tail = words.slice(1).join(" ");
|
|
3273
3332
|
return tail ? `${head} ${tail}` : head;
|
|
3274
3333
|
}
|
|
3275
|
-
function translateSeText(input, listener, passthrough, provider) {
|
|
3334
|
+
function translateSeText(input, listener, passthrough, provider, options) {
|
|
3276
3335
|
if (!input) return "";
|
|
3277
3336
|
const ctx = listener ? listenerPlaceholderContext(listener, provider) : {};
|
|
3337
|
+
const useAlertFallback = (options?.context ?? "alert") === "alert";
|
|
3278
3338
|
return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
|
|
3279
3339
|
if (passthrough?.has(raw)) return `{{${raw}}}`;
|
|
3280
|
-
const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
|
|
3340
|
+
const mapped = ctx[raw] ?? (useAlertFallback ? SE_TO_LUMIA_PLACEHOLDER[raw] : void 0) ?? raw;
|
|
3281
3341
|
return `{{${mapped}}}`;
|
|
3282
3342
|
});
|
|
3283
3343
|
}
|
|
@@ -3328,8 +3388,8 @@ function listenerToLumiaVariable(listener, provider = "twitch") {
|
|
|
3328
3388
|
// Latest events
|
|
3329
3389
|
"follower-latest": "twitch_last_follower",
|
|
3330
3390
|
"subscriber-latest": "twitch_last_subscriber",
|
|
3331
|
-
"subscriber-new-latest": "
|
|
3332
|
-
"subscriber-resub-latest": "
|
|
3391
|
+
"subscriber-new-latest": "twitch_last_new_subscriber",
|
|
3392
|
+
"subscriber-resub-latest": "twitch_last_resubscriber",
|
|
3333
3393
|
"subscriber-gifted-latest": "twitch_last_gifter",
|
|
3334
3394
|
"subscriber-alltime-gifter": "twitch_alltime_top_gifter",
|
|
3335
3395
|
"tip-latest": "latest_donator",
|
|
@@ -3484,9 +3544,16 @@ var LUMIA_DEFAULT_SIZES = {
|
|
|
3484
3544
|
// just the card if they want SE's exact footprint.
|
|
3485
3545
|
songrequest: { width: 480, height: 380 }
|
|
3486
3546
|
};
|
|
3487
|
-
function translateVariationCondition(seType, seCondition, requirement) {
|
|
3547
|
+
function translateVariationCondition(seType, seCondition, requirement, seEventType) {
|
|
3488
3548
|
if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
|
|
3489
3549
|
if (seType === "communityGift") return { conditionType: "GIFT_SUB_GREATER", condition: requirement ?? 0 };
|
|
3550
|
+
const isSubscriberEvent = seEventType === "subscriber";
|
|
3551
|
+
if (isSubscriberEvent && seCondition === "ATLEAST" && requirement != null) {
|
|
3552
|
+
return { conditionType: "SUBSCRIBED_MONTHS_GREATER", condition: requirement };
|
|
3553
|
+
}
|
|
3554
|
+
if (isSubscriberEvent && seCondition === "EXACT" && requirement != null) {
|
|
3555
|
+
return { conditionType: "SUBSCRIBED_MONTHS_EQUAL", condition: requirement };
|
|
3556
|
+
}
|
|
3490
3557
|
if (seCondition === "EXACT" && requirement != null) return { conditionType: "EQUAL_NUMBER", condition: requirement };
|
|
3491
3558
|
if (seCondition === "ATLEAST" && requirement != null) {
|
|
3492
3559
|
return { conditionType: "GREATER_NUMBER", condition: requirement };
|
|
@@ -3530,9 +3597,10 @@ function mapSeTextCssToModuleCss(widget, defaults = {}) {
|
|
|
3530
3597
|
background: defaults.background ?? "transparent"
|
|
3531
3598
|
};
|
|
3532
3599
|
}
|
|
3533
|
-
|
|
3600
|
+
var READOUT_AUTO_FALLBACK_SIZE = { width: 400, height: 50 };
|
|
3601
|
+
function buildUnit(widget, lumiaType, moduleExtras, ctx, sizeOverride) {
|
|
3534
3602
|
const id = nanoid();
|
|
3535
|
-
const defaults = LUMIA_DEFAULT_SIZES[lumiaType] ?? { width: 400, height: 400 };
|
|
3603
|
+
const defaults = sizeOverride ?? LUMIA_DEFAULT_SIZES[lumiaType] ?? { width: 400, height: 400 };
|
|
3536
3604
|
const bounds = seCssToBounds(widget.css, defaults, ctx?.canvas);
|
|
3537
3605
|
const layer = {
|
|
3538
3606
|
id,
|
|
@@ -3586,7 +3654,7 @@ function seExtraTextCss(seCss, scrolling) {
|
|
|
3586
3654
|
return extra;
|
|
3587
3655
|
}
|
|
3588
3656
|
function mapText(widget, ctx) {
|
|
3589
|
-
const value = translateSeText(widget.text?.value ?? "");
|
|
3657
|
+
const value = translateSeText(widget.text?.value ?? "", null, void 0, ctx?.provider, { context: "static" });
|
|
3590
3658
|
const seCss = widget.text?.css ?? {};
|
|
3591
3659
|
return buildUnit(widget, "text", {
|
|
3592
3660
|
content: {
|
|
@@ -3643,22 +3711,32 @@ function mapReadout(widget, fallbackVar, ctx) {
|
|
|
3643
3711
|
const variable = listenerToLumiaVariable(listener, ctx?.provider);
|
|
3644
3712
|
const userTemplate = widget.text?.value ?? "";
|
|
3645
3713
|
const inheritsText = userTemplate.trim().length > 0 && /\{[a-zA-Z]/.test(userTemplate);
|
|
3646
|
-
const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider) : `{{${variable}}}`;
|
|
3714
|
+
const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider, { context: "static" }) : `{{${variable}}}`;
|
|
3647
3715
|
const seCss = widget.text?.css ?? {};
|
|
3648
|
-
return buildUnit(
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3716
|
+
return buildUnit(
|
|
3717
|
+
widget,
|
|
3718
|
+
"text",
|
|
3719
|
+
{
|
|
3720
|
+
content: { value, highlightColor: "inherit" },
|
|
3721
|
+
css: {
|
|
3722
|
+
fontSize: seCss["font-size"] ?? 28,
|
|
3723
|
+
textAlign: seCss["text-align"] ?? "center",
|
|
3724
|
+
fontFamily: seCss["font-family"] ?? "Roboto",
|
|
3725
|
+
fontWeight: seCss["font-weight"] ?? "bold",
|
|
3726
|
+
color: seCss["color"] ?? "#ffffff",
|
|
3727
|
+
textShadow: seCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
|
|
3728
|
+
lineHeight: seCss["line-height"] ?? 1,
|
|
3729
|
+
background: "transparent",
|
|
3730
|
+
...seExtraTextCss(seCss, widget.text?.scrolling)
|
|
3731
|
+
}
|
|
3732
|
+
},
|
|
3733
|
+
ctx,
|
|
3734
|
+
// SE readouts ship `width:auto; height:auto`. Override the generic
|
|
3735
|
+
// `text` 600×200 fallback with a single-line-friendly size so the
|
|
3736
|
+
// imported layer doesn't render as a half-canvas rectangle when the
|
|
3737
|
+
// rendered content is one line of 24px text.
|
|
3738
|
+
READOUT_AUTO_FALLBACK_SIZE
|
|
3739
|
+
);
|
|
3662
3740
|
}
|
|
3663
3741
|
|
|
3664
3742
|
// src/se-import/mappers/provider-alerts.ts
|
|
@@ -3898,7 +3976,7 @@ function buildAlertEvent(seEvent, seEventType) {
|
|
|
3898
3976
|
};
|
|
3899
3977
|
}
|
|
3900
3978
|
function buildAlertVariation(v, seEventType, parentSettings) {
|
|
3901
|
-
const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null);
|
|
3979
|
+
const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null, seEventType);
|
|
3902
3980
|
if (!translated) return null;
|
|
3903
3981
|
const { variations: _parentVariations, ...parentInheritable } = parentSettings ?? {};
|
|
3904
3982
|
const mergedSettings = deepMergeSeSettings(parentSettings ? parentInheritable : void 0, v.settings);
|
|
@@ -4034,7 +4112,8 @@ function mapGoal(widget, ctx) {
|
|
|
4034
4112
|
// live `current` floating at the percentage position. See
|
|
4035
4113
|
// components/Modules/Views/Goal/types.ts:GoalTypes.SLIM.
|
|
4036
4114
|
type: "slim",
|
|
4037
|
-
label
|
|
4115
|
+
// Static label rendered above the goal bar — not an alert context.
|
|
4116
|
+
label: translateSeText(v.title ?? "Goal", null, void 0, void 0, { context: "static" }),
|
|
4038
4117
|
subLabel: "",
|
|
4039
4118
|
// The Goal module renders this template literal with {{current}} + {{goal}} substituted.
|
|
4040
4119
|
// Charity goals track donation amounts — prefix `$` so the rendered bar reads
|
|
@@ -4637,7 +4716,7 @@ function mapTimer(widget, ctx) {
|
|
|
4637
4716
|
const v = widget.variables ?? {};
|
|
4638
4717
|
const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
|
|
4639
4718
|
const TIMER_PASSTHROUGH = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
|
|
4640
|
-
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH);
|
|
4719
|
+
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, { context: "static" });
|
|
4641
4720
|
const absoluteDate = v.date ?? v.endsAt ?? v.endDate;
|
|
4642
4721
|
const target = absoluteDate ? Date.parse(absoluteDate) : NaN;
|
|
4643
4722
|
const hasValidAbsoluteDate = Number.isFinite(target);
|
|
@@ -4709,7 +4788,7 @@ function mapTimer(widget, ctx) {
|
|
|
4709
4788
|
endAnimation: "",
|
|
4710
4789
|
hideAfter: 0,
|
|
4711
4790
|
highlightColor: "inherit",
|
|
4712
|
-
messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? ""),
|
|
4791
|
+
messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? "", null, void 0, void 0, { context: "static" }),
|
|
4713
4792
|
changeMessageAfter: !!v.changeMessageAfterCountdown,
|
|
4714
4793
|
countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
|
|
4715
4794
|
date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
|
|
@@ -4852,28 +4931,47 @@ function mapKappagen(widget, ctx) {
|
|
|
4852
4931
|
ctx
|
|
4853
4932
|
);
|
|
4854
4933
|
}
|
|
4855
|
-
|
|
4934
|
+
var TOP_LIST_SORT_BY = {
|
|
4935
|
+
tips: "higherDonations",
|
|
4936
|
+
cheers: "higherBits",
|
|
4937
|
+
gifts: "higherGifts"
|
|
4938
|
+
};
|
|
4939
|
+
var TOP_LIST_TITLE = {
|
|
4940
|
+
tips: "Top Tippers",
|
|
4941
|
+
cheers: "Top Cheerers",
|
|
4942
|
+
gifts: "Top Gifters"
|
|
4943
|
+
};
|
|
4944
|
+
function mapViewerProfilesLeaderboard(widget, currency, ctx) {
|
|
4856
4945
|
const v = widget.variables ?? {};
|
|
4857
4946
|
const count = v.limit ?? v.visibleItems ?? 10;
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
return buildUnit(widget, "loyaltyleaderboard", {
|
|
4861
|
-
// Carry SE text styling so the leaderboard list keeps the streamer's
|
|
4947
|
+
return buildUnit(widget, "viewerprofiles", {
|
|
4948
|
+
// Carry SE text styling so the leaderboard keeps the streamer's
|
|
4862
4949
|
// chosen font / size / color.
|
|
4863
4950
|
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
|
|
4864
4951
|
content: {
|
|
4865
|
-
|
|
4866
|
-
currency,
|
|
4867
|
-
window,
|
|
4952
|
+
sortBy: TOP_LIST_SORT_BY[currency],
|
|
4868
4953
|
count,
|
|
4869
|
-
title,
|
|
4954
|
+
title: TOP_LIST_TITLE[currency],
|
|
4870
4955
|
showTitle: true,
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4956
|
+
showAvatar: true,
|
|
4957
|
+
showPlatform: true,
|
|
4958
|
+
// Hide noisy columns that aren't relevant for a single-metric
|
|
4959
|
+
// top-X list — the streamer picked "top by gifts/tips/cheers"
|
|
4960
|
+
// for a reason. They can re-enable in Settings if they want.
|
|
4961
|
+
showCommands: false,
|
|
4962
|
+
showAlerts: false,
|
|
4963
|
+
showBits: currency === "cheers",
|
|
4964
|
+
showLastSeen: false,
|
|
4965
|
+
showTopActivity: false,
|
|
4966
|
+
showAchievements: false,
|
|
4967
|
+
showAchievementProgress: false,
|
|
4968
|
+
refreshIntervalSeconds: 30,
|
|
4874
4969
|
itemGap: 8,
|
|
4970
|
+
autoScroll: false,
|
|
4875
4971
|
// Source-side knobs that don't map cleanly survive under the
|
|
4876
|
-
// generic importMeta envelope so the user can re-introspect.
|
|
4972
|
+
// generic importMeta envelope so the user can re-introspect later.
|
|
4973
|
+
// `period` is interesting because viewerprofiles doesn't yet have
|
|
4974
|
+
// a windowed mode — if/when it does, we can promote this.
|
|
4877
4975
|
importMeta: buildImportMeta(widget, {
|
|
4878
4976
|
period: v.period,
|
|
4879
4977
|
offset: v.offset,
|
|
@@ -5019,6 +5117,47 @@ function mapHypetrain(widget, ctx) {
|
|
|
5019
5117
|
}
|
|
5020
5118
|
}, ctx);
|
|
5021
5119
|
}
|
|
5120
|
+
function mapEmoteWall(widget, ctx) {
|
|
5121
|
+
const v = widget.variables ?? {};
|
|
5122
|
+
const imageSize = typeof v.emoteSize === "number" ? v.emoteSize : typeof v.size === "number" ? v.size : 40;
|
|
5123
|
+
const maxItemsToShow = typeof v.maxEmotes === "number" && v.maxEmotes > 0 ? v.maxEmotes : typeof v.maxItems === "number" && v.maxItems > 0 ? v.maxItems : 40;
|
|
5124
|
+
const duration = typeof v.duration === "number" && v.duration > 0 ? v.duration : typeof v.decay === "number" && v.decay > 0 ? v.decay : typeof v.decayTime === "number" && v.decayTime > 0 ? v.decayTime : 5;
|
|
5125
|
+
const sites = [];
|
|
5126
|
+
if (v.enableTwitch !== false && v.twitch !== false) sites.push("twitch");
|
|
5127
|
+
if (v.enableBttv !== false && v.bttv !== false) sites.push("bttv");
|
|
5128
|
+
if (v.enableFfz !== false && v.ffz !== false) sites.push("ffz");
|
|
5129
|
+
if (v.enable7tv !== false && v.sevenTv !== false) sites.push("seventv");
|
|
5130
|
+
const animationType = v.direction === "up" ? "rise" : v.direction === "down" ? "fall" : v.direction === "left" || v.direction === "right" ? "slide" : "random";
|
|
5131
|
+
return buildUnit(widget, "emotebox", {
|
|
5132
|
+
content: {
|
|
5133
|
+
version: 1,
|
|
5134
|
+
type: animationType,
|
|
5135
|
+
imageSize,
|
|
5136
|
+
animationTime: duration,
|
|
5137
|
+
duration,
|
|
5138
|
+
maxItemsToShow,
|
|
5139
|
+
delay: typeof v.delay === "number" ? v.delay : 0,
|
|
5140
|
+
sites,
|
|
5141
|
+
background: "transparent",
|
|
5142
|
+
theme: "dark",
|
|
5143
|
+
userLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
5144
|
+
ignoredList: [],
|
|
5145
|
+
excludedAnimations: [],
|
|
5146
|
+
// Emote-wall is chat-driven, not alert-driven — leave alert toggles
|
|
5147
|
+
// off so the widget doesn't double-fire on subscribe/follower events.
|
|
5148
|
+
alertOn: false,
|
|
5149
|
+
alertTriggers: [],
|
|
5150
|
+
alertEmotes: [],
|
|
5151
|
+
alertNumberEmotes: 30,
|
|
5152
|
+
useAnimatedEmotes: true,
|
|
5153
|
+
importMeta: buildImportMeta(widget, {
|
|
5154
|
+
fullscreen: !!v.fullscreen,
|
|
5155
|
+
direction: v.direction
|
|
5156
|
+
})
|
|
5157
|
+
},
|
|
5158
|
+
css: { background: "transparent" }
|
|
5159
|
+
}, ctx);
|
|
5160
|
+
}
|
|
5022
5161
|
function mapUnsupportedAsText(widget, ctx) {
|
|
5023
5162
|
return buildUnit(widget, "text", {
|
|
5024
5163
|
content: {
|
|
@@ -5418,15 +5557,16 @@ function dispatch(widget, ctx) {
|
|
|
5418
5557
|
return { unit: mapReadout(widget, fallbackVar, ctx), status: "direct", lumiaType: "text" };
|
|
5419
5558
|
}
|
|
5420
5559
|
if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget, ctx), status: "direct", lumiaType: "eventlist" };
|
|
5421
|
-
if (t === "se-widget-top-tippers-list") return { unit:
|
|
5422
|
-
if (t === "se-widget-top-cheerers-list") return { unit:
|
|
5423
|
-
if (t === "se-widget-top-gifters-list") return { unit:
|
|
5560
|
+
if (t === "se-widget-top-tippers-list") return { unit: mapViewerProfilesLeaderboard(widget, "tips", ctx), status: "direct", lumiaType: "viewerprofiles" };
|
|
5561
|
+
if (t === "se-widget-top-cheerers-list") return { unit: mapViewerProfilesLeaderboard(widget, "cheers", ctx), status: "direct", lumiaType: "viewerprofiles" };
|
|
5562
|
+
if (t === "se-widget-top-gifters-list") return { unit: mapViewerProfilesLeaderboard(widget, "gifts", ctx), status: "direct", lumiaType: "viewerprofiles" };
|
|
5424
5563
|
if (t === "se-widget-credit-roll") return { unit: mapCredits(widget, ctx), status: "partial", lumiaType: "credits" };
|
|
5425
5564
|
if (t === "se-widget-twitch-chat") return { unit: mapChatbox(widget, ctx), status: "partial", lumiaType: "chatbox" };
|
|
5426
5565
|
if (t === "se-widget-image-rotator" || t === "se-widget-merch-products-rotator") return { unit: mapSlideshow(widget, ctx), status: t === "se-widget-merch-products-rotator" ? "partial" : "direct", lumiaType: "slideshow" };
|
|
5427
5566
|
if (t === "se-widget-countdown") return { unit: mapTimer(widget, ctx), status: "partial", lumiaType: "timer" };
|
|
5428
5567
|
if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget, ctx), status: "partial", lumiaType: "nowplaying" };
|
|
5429
5568
|
if (t === "se-widget-kappagen") return { unit: mapKappagen(widget, ctx), status: "partial", lumiaType: "emotealert" };
|
|
5569
|
+
if (t === "se-widget-emote-wall") return { unit: mapEmoteWall(widget, ctx), status: "partial", lumiaType: "emotebox" };
|
|
5430
5570
|
if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget, ctx), status: "direct", lumiaType: "raffle" };
|
|
5431
5571
|
if (t === "se-widget-media-share") return { unit: mapMediaShare(widget, ctx), status: "partial", lumiaType: "songrequest" };
|
|
5432
5572
|
if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget, ctx), status: "partial", lumiaType: "tipjar" };
|
package/dist/se-import.js
CHANGED
|
@@ -18,31 +18,86 @@ var SE_TO_LUMIA_PLACEHOLDER = {
|
|
|
18
18
|
};
|
|
19
19
|
var TWITCH_CONTEXTS = {
|
|
20
20
|
"follower-latest": { name: "twitch_last_follower", username: "twitch_last_follower" },
|
|
21
|
+
// Three SE listeners with genuinely distinct semantics — preserve the
|
|
22
|
+
// distinction in the variable mapping so a widget configured for "Latest
|
|
23
|
+
// new subscriber" doesn't render the latest *resub* (and vice versa).
|
|
24
|
+
// `subscriber-latest` is the umbrella (anyone subscribing); the other two
|
|
25
|
+
// are subtype-specific. The dedicated TWITCH_LAST_{NEW_SUBSCRIBER,
|
|
26
|
+
// RESUBSCRIBER} variables are written by the Twitch event pipeline on
|
|
27
|
+
// the matching sub.type event.
|
|
21
28
|
"subscriber-latest": { name: "twitch_last_subscriber", username: "twitch_last_subscriber" },
|
|
22
|
-
"subscriber-new-latest": { name: "
|
|
23
|
-
"subscriber-resub-latest": { name: "
|
|
24
|
-
|
|
29
|
+
"subscriber-new-latest": { name: "twitch_last_new_subscriber", username: "twitch_last_new_subscriber" },
|
|
30
|
+
"subscriber-resub-latest": { name: "twitch_last_resubscriber", username: "twitch_last_resubscriber" },
|
|
31
|
+
// `subscriber-gifted-latest`: SE uses `{sender}` for the giver and `{name}` for
|
|
32
|
+
// the recipient (e.g. "{sender} gifted to {name}"). The two roles must point
|
|
33
|
+
// at distinct Lumia variables — collapsing both onto `twitch_last_gifter`
|
|
34
|
+
// rendered "Mr.Beast gifted to Mr.Beast" while the actual recipient was
|
|
35
|
+
// nowhere. `sender` keeps the gifter; `name` / `username` switch to the
|
|
36
|
+
// dedicated recipient variable `twitch_last_gifted`. Amount stays on the
|
|
37
|
+
// gifter (it's the count of subs in that drop, owned by the gifter).
|
|
38
|
+
"subscriber-gifted-latest": { name: "twitch_last_gifted", username: "twitch_last_gifted", sender: "twitch_last_gifter", amount: "twitch_last_gifter_amount", count: "twitch_last_gifter_amount" },
|
|
25
39
|
"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
40
|
"cheer-latest": { name: "twitch_last_bit", username: "twitch_last_bit", amount: "twitch_last_bit_amount", count: "twitch_last_bit_amount" },
|
|
27
41
|
"tip-latest": { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
|
|
28
42
|
latest_donator: { name: "latest_donator", username: "latest_donator", amount: "latest_donator_amount", count: "latest_donator_amount", currency: "latest_donator_currency_symbol" },
|
|
29
43
|
"raid-latest": { name: "twitch_last_raider", username: "twitch_last_raider", amount: "twitch_last_raid_amount", count: "twitch_last_raid_amount" },
|
|
30
|
-
//
|
|
44
|
+
// Period counters: count/amount = the running counter for that window.
|
|
45
|
+
// SE lets the streamer pick session / week / month / total on the same widget
|
|
46
|
+
// type; the chosen period lands in `widget.listener` (e.g. `cheer-week`).
|
|
47
|
+
// Each entry below mirrors a row in the Lumia variables table — the previous
|
|
48
|
+
// table only carried session+total and broke any widget configured for
|
|
49
|
+
// week/month because `{amount}` fell through to the unscoped placeholder
|
|
50
|
+
// and rendered blank.
|
|
31
51
|
"follower-session": { amount: "twitch_session_follower_count", count: "twitch_session_follower_count" },
|
|
32
|
-
"
|
|
33
|
-
"
|
|
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.
|
|
52
|
+
"follower-week": { amount: "twitch_week_follower_count", count: "twitch_week_follower_count" },
|
|
53
|
+
"follower-month": { amount: "twitch_month_follower_count", count: "twitch_month_follower_count" },
|
|
36
54
|
"follower-total": { amount: "twitch_total_follower_count", count: "twitch_total_follower_count" },
|
|
55
|
+
"subscriber-session": { amount: "twitch_session_subscribers_count", count: "twitch_session_subscribers_count" },
|
|
56
|
+
"subscriber-week": { amount: "twitch_week_subscriber_count", count: "twitch_week_subscriber_count" },
|
|
57
|
+
"subscriber-month": { amount: "twitch_month_subscriber_count", count: "twitch_month_subscriber_count" },
|
|
37
58
|
"subscriber-total": { amount: "twitch_total_subscriber_count", count: "twitch_total_subscriber_count" },
|
|
59
|
+
"subscriber-new-session": { amount: "twitch_session_new_subscribers_count", count: "twitch_session_new_subscribers_count" },
|
|
60
|
+
"subscriber-resub-session": { amount: "twitch_session_resub_subscribers_count", count: "twitch_session_resub_subscribers_count" },
|
|
61
|
+
"subscriber-gifted-session": { amount: "twitch_session_gifted_subscribers_count", count: "twitch_session_gifted_subscribers_count" },
|
|
62
|
+
"cheer-session": { amount: "twitch_session_bits_count", count: "twitch_session_bits_count" },
|
|
63
|
+
"cheer-week": { amount: "twitch_week_bits_count", count: "twitch_week_bits_count" },
|
|
64
|
+
"cheer-month": { amount: "twitch_month_bits_count", count: "twitch_month_bits_count" },
|
|
38
65
|
"cheer-total": { amount: "twitch_total_bits_count", count: "twitch_total_bits_count" },
|
|
66
|
+
"tip-session": { amount: "session_donation_amount", count: "session_donation_count", currency: "session_donation_amount_currency_symbol" },
|
|
67
|
+
"tip-week": { amount: "week_donation_amount", count: "week_donation_count", currency: "week_donation_amount_currency_symbol" },
|
|
68
|
+
"tip-month": { amount: "month_donation_amount", count: "month_donation_count", currency: "month_donation_amount_currency_symbol" },
|
|
39
69
|
"tip-total": { amount: "total_donation_amount", count: "total_donation_amount", currency: "total_donation_amount_currency_symbol" },
|
|
40
70
|
"tip-count": { amount: "donation_count", count: "donation_count" },
|
|
41
|
-
// Top cheer / cheerer / donator
|
|
42
|
-
|
|
71
|
+
// Top cheer / cheerer / donator readouts — per period. SE's `se-widget-top-*`
|
|
72
|
+
// widgets encode the chosen period (session / weekly / monthly / alltime) in
|
|
73
|
+
// the widget's `listener` field. Without these entries the user's `{amount}`
|
|
74
|
+
// / `{name}` template tokens fall through to the unscoped placeholder map
|
|
75
|
+
// (`{{amount}}` only resolves inside alert variations) and render blank.
|
|
76
|
+
//
|
|
77
|
+
// `*-top-donation` and `*-top-donator` are paired: SE nominally treats them
|
|
78
|
+
// as "top single event" vs "top cumulative contributor", but the data
|
|
79
|
+
// rendered for both lives in the same Lumia variable pair (`*_top_donator`
|
|
80
|
+
// / `*_top_donator_amount`, `twitch_*_top_cheerer` / `..._amount`). SE
|
|
81
|
+
// widgets routinely use `{name}` against the `-donation` listener to mean
|
|
82
|
+
// "the user who made the top donation", so the two rows below carry the
|
|
83
|
+
// same name/username mapping — anything else falls through to the unscoped
|
|
84
|
+
// `{{username}}` and renders blank.
|
|
85
|
+
"cheer-session-top-donation": { name: "twitch_session_top_cheerer", username: "twitch_session_top_cheerer", amount: "twitch_session_top_cheer_amount", count: "twitch_session_top_cheer_amount" },
|
|
43
86
|
"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
|
-
"
|
|
87
|
+
"cheer-weekly-top-donation": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
|
|
88
|
+
"cheer-weekly-top-donator": { name: "twitch_week_top_cheerer", username: "twitch_week_top_cheerer", amount: "twitch_week_top_cheerer_amount", count: "twitch_week_top_cheerer_amount" },
|
|
89
|
+
"cheer-monthly-top-donation": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
|
|
90
|
+
"cheer-monthly-top-donator": { name: "twitch_month_top_cheerer", username: "twitch_month_top_cheerer", amount: "twitch_month_top_cheerer_amount", count: "twitch_month_top_cheerer_amount" },
|
|
91
|
+
"cheer-alltime-top-donation": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
|
|
92
|
+
"cheer-alltime-top-donator": { name: "twitch_alltime_top_cheerer", username: "twitch_alltime_top_cheerer", amount: "twitch_alltime_top_cheerer_amount", count: "twitch_alltime_top_cheerer_amount" },
|
|
93
|
+
"tip-session-top-donation": { name: "session_top_donator", username: "session_top_donator", amount: "session_top_donator_amount", count: "session_top_donator_amount", currency: "session_top_donator_currency_symbol" },
|
|
45
94
|
"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" },
|
|
95
|
+
"tip-weekly-top-donation": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
|
|
96
|
+
"tip-weekly-top-donator": { name: "week_top_donator", username: "week_top_donator", amount: "week_top_donator_amount", count: "week_top_donator_amount", currency: "week_top_donator_currency_symbol" },
|
|
97
|
+
"tip-monthly-top-donation": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
|
|
98
|
+
"tip-monthly-top-donator": { name: "month_top_donator", username: "month_top_donator", amount: "month_top_donator_amount", count: "month_top_donator_amount", currency: "month_top_donator_currency_symbol" },
|
|
99
|
+
"tip-alltime-top-donation": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
|
|
100
|
+
"tip-alltime-top-donator": { name: "top_donator", username: "top_donator", amount: "top_donator_amount", count: "top_donator_amount", currency: "top_donator_currency_symbol" },
|
|
46
101
|
// Botcounter — SE uses `{wins}` for the running chat count; the dispatcher's
|
|
47
102
|
// fallbackVar for `se-widget-botcounter` is `twitch_session_chat_count`, so
|
|
48
103
|
// when the listener arg lands as that var name we treat it as the wins source.
|
|
@@ -79,7 +134,11 @@ var KICK_CONTEXTS = {
|
|
|
79
134
|
"subscriber-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
|
|
80
135
|
"subscriber-new-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
|
|
81
136
|
"subscriber-resub-latest": { name: "kick_last_subscriber", username: "kick_last_subscriber" },
|
|
82
|
-
|
|
137
|
+
// Mirror the Twitch split: `{sender}` = giver, `{name}` = recipient.
|
|
138
|
+
// `kick_last_gifted` was added alongside this mapping; see twitch context
|
|
139
|
+
// above for the rationale (without the split, SE's "{sender} gifted to
|
|
140
|
+
// {name}" renders the gifter on both sides).
|
|
141
|
+
"subscriber-gifted-latest": { name: "kick_last_gifted", username: "kick_last_gifted", sender: "kick_last_gifter", amount: "kick_last_gifter_amount", count: "kick_last_gifter_amount" },
|
|
83
142
|
"cheer-latest": { name: "kick_last_kicks", username: "kick_last_kicks", amount: "kick_last_kicks_amount", count: "kick_last_kicks_amount" },
|
|
84
143
|
"raid-latest": { name: "kick_last_host", username: "kick_last_host", amount: "kick_last_host_amount", count: "kick_last_host_amount" },
|
|
85
144
|
"follower-session": { amount: "kick_session_follower_count", count: "kick_session_follower_count" },
|
|
@@ -157,12 +216,13 @@ function defaultLabelForSEWidget(widget) {
|
|
|
157
216
|
const tail = words.slice(1).join(" ");
|
|
158
217
|
return tail ? `${head} ${tail}` : head;
|
|
159
218
|
}
|
|
160
|
-
function translateSeText(input, listener, passthrough, provider) {
|
|
219
|
+
function translateSeText(input, listener, passthrough, provider, options) {
|
|
161
220
|
if (!input) return "";
|
|
162
221
|
const ctx = listener ? listenerPlaceholderContext(listener, provider) : {};
|
|
222
|
+
const useAlertFallback = (options?.context ?? "alert") === "alert";
|
|
163
223
|
return input.replace(/\{([a-zA-Z][a-zA-Z0-9_.]*)\}/g, (_match, raw) => {
|
|
164
224
|
if (passthrough?.has(raw)) return `{{${raw}}}`;
|
|
165
|
-
const mapped = ctx[raw] ?? SE_TO_LUMIA_PLACEHOLDER[raw] ?? raw;
|
|
225
|
+
const mapped = ctx[raw] ?? (useAlertFallback ? SE_TO_LUMIA_PLACEHOLDER[raw] : void 0) ?? raw;
|
|
166
226
|
return `{{${mapped}}}`;
|
|
167
227
|
});
|
|
168
228
|
}
|
|
@@ -213,8 +273,8 @@ function listenerToLumiaVariable(listener, provider = "twitch") {
|
|
|
213
273
|
// Latest events
|
|
214
274
|
"follower-latest": "twitch_last_follower",
|
|
215
275
|
"subscriber-latest": "twitch_last_subscriber",
|
|
216
|
-
"subscriber-new-latest": "
|
|
217
|
-
"subscriber-resub-latest": "
|
|
276
|
+
"subscriber-new-latest": "twitch_last_new_subscriber",
|
|
277
|
+
"subscriber-resub-latest": "twitch_last_resubscriber",
|
|
218
278
|
"subscriber-gifted-latest": "twitch_last_gifter",
|
|
219
279
|
"subscriber-alltime-gifter": "twitch_alltime_top_gifter",
|
|
220
280
|
"tip-latest": "latest_donator",
|
|
@@ -369,9 +429,16 @@ var LUMIA_DEFAULT_SIZES = {
|
|
|
369
429
|
// just the card if they want SE's exact footprint.
|
|
370
430
|
songrequest: { width: 480, height: 380 }
|
|
371
431
|
};
|
|
372
|
-
function translateVariationCondition(seType, seCondition, requirement) {
|
|
432
|
+
function translateVariationCondition(seType, seCondition, requirement, seEventType) {
|
|
373
433
|
if (seType === "gift") return { conditionType: "IS_GIFT", condition: 1 };
|
|
374
434
|
if (seType === "communityGift") return { conditionType: "GIFT_SUB_GREATER", condition: requirement ?? 0 };
|
|
435
|
+
const isSubscriberEvent = seEventType === "subscriber";
|
|
436
|
+
if (isSubscriberEvent && seCondition === "ATLEAST" && requirement != null) {
|
|
437
|
+
return { conditionType: "SUBSCRIBED_MONTHS_GREATER", condition: requirement };
|
|
438
|
+
}
|
|
439
|
+
if (isSubscriberEvent && seCondition === "EXACT" && requirement != null) {
|
|
440
|
+
return { conditionType: "SUBSCRIBED_MONTHS_EQUAL", condition: requirement };
|
|
441
|
+
}
|
|
375
442
|
if (seCondition === "EXACT" && requirement != null) return { conditionType: "EQUAL_NUMBER", condition: requirement };
|
|
376
443
|
if (seCondition === "ATLEAST" && requirement != null) {
|
|
377
444
|
return { conditionType: "GREATER_NUMBER", condition: requirement };
|
|
@@ -415,9 +482,10 @@ function mapSeTextCssToModuleCss(widget, defaults = {}) {
|
|
|
415
482
|
background: defaults.background ?? "transparent"
|
|
416
483
|
};
|
|
417
484
|
}
|
|
418
|
-
|
|
485
|
+
var READOUT_AUTO_FALLBACK_SIZE = { width: 400, height: 50 };
|
|
486
|
+
function buildUnit(widget, lumiaType, moduleExtras, ctx, sizeOverride) {
|
|
419
487
|
const id = nanoid();
|
|
420
|
-
const defaults = LUMIA_DEFAULT_SIZES[lumiaType] ?? { width: 400, height: 400 };
|
|
488
|
+
const defaults = sizeOverride ?? LUMIA_DEFAULT_SIZES[lumiaType] ?? { width: 400, height: 400 };
|
|
421
489
|
const bounds = seCssToBounds(widget.css, defaults, ctx?.canvas);
|
|
422
490
|
const layer = {
|
|
423
491
|
id,
|
|
@@ -471,7 +539,7 @@ function seExtraTextCss(seCss, scrolling) {
|
|
|
471
539
|
return extra;
|
|
472
540
|
}
|
|
473
541
|
function mapText(widget, ctx) {
|
|
474
|
-
const value = translateSeText(widget.text?.value ?? "");
|
|
542
|
+
const value = translateSeText(widget.text?.value ?? "", null, void 0, ctx?.provider, { context: "static" });
|
|
475
543
|
const seCss = widget.text?.css ?? {};
|
|
476
544
|
return buildUnit(widget, "text", {
|
|
477
545
|
content: {
|
|
@@ -528,22 +596,32 @@ function mapReadout(widget, fallbackVar, ctx) {
|
|
|
528
596
|
const variable = listenerToLumiaVariable(listener, ctx?.provider);
|
|
529
597
|
const userTemplate = widget.text?.value ?? "";
|
|
530
598
|
const inheritsText = userTemplate.trim().length > 0 && /\{[a-zA-Z]/.test(userTemplate);
|
|
531
|
-
const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider) : `{{${variable}}}`;
|
|
599
|
+
const value = inheritsText ? translateSeText(userTemplate, listener, void 0, ctx?.provider, { context: "static" }) : `{{${variable}}}`;
|
|
532
600
|
const seCss = widget.text?.css ?? {};
|
|
533
|
-
return buildUnit(
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
601
|
+
return buildUnit(
|
|
602
|
+
widget,
|
|
603
|
+
"text",
|
|
604
|
+
{
|
|
605
|
+
content: { value, highlightColor: "inherit" },
|
|
606
|
+
css: {
|
|
607
|
+
fontSize: seCss["font-size"] ?? 28,
|
|
608
|
+
textAlign: seCss["text-align"] ?? "center",
|
|
609
|
+
fontFamily: seCss["font-family"] ?? "Roboto",
|
|
610
|
+
fontWeight: seCss["font-weight"] ?? "bold",
|
|
611
|
+
color: seCss["color"] ?? "#ffffff",
|
|
612
|
+
textShadow: seCss["text-shadow"] ?? "rgb(0, 0, 0) 1px 1px 1px",
|
|
613
|
+
lineHeight: seCss["line-height"] ?? 1,
|
|
614
|
+
background: "transparent",
|
|
615
|
+
...seExtraTextCss(seCss, widget.text?.scrolling)
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
ctx,
|
|
619
|
+
// SE readouts ship `width:auto; height:auto`. Override the generic
|
|
620
|
+
// `text` 600×200 fallback with a single-line-friendly size so the
|
|
621
|
+
// imported layer doesn't render as a half-canvas rectangle when the
|
|
622
|
+
// rendered content is one line of 24px text.
|
|
623
|
+
READOUT_AUTO_FALLBACK_SIZE
|
|
624
|
+
);
|
|
547
625
|
}
|
|
548
626
|
|
|
549
627
|
// src/se-import/mappers/provider-alerts.ts
|
|
@@ -783,7 +861,7 @@ function buildAlertEvent(seEvent, seEventType) {
|
|
|
783
861
|
};
|
|
784
862
|
}
|
|
785
863
|
function buildAlertVariation(v, seEventType, parentSettings) {
|
|
786
|
-
const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null);
|
|
864
|
+
const translated = translateVariationCondition(v.type ?? null, v.condition ?? null, v.requirement ?? null, seEventType);
|
|
787
865
|
if (!translated) return null;
|
|
788
866
|
const { variations: _parentVariations, ...parentInheritable } = parentSettings ?? {};
|
|
789
867
|
const mergedSettings = deepMergeSeSettings(parentSettings ? parentInheritable : void 0, v.settings);
|
|
@@ -919,7 +997,8 @@ function mapGoal(widget, ctx) {
|
|
|
919
997
|
// live `current` floating at the percentage position. See
|
|
920
998
|
// components/Modules/Views/Goal/types.ts:GoalTypes.SLIM.
|
|
921
999
|
type: "slim",
|
|
922
|
-
label
|
|
1000
|
+
// Static label rendered above the goal bar — not an alert context.
|
|
1001
|
+
label: translateSeText(v.title ?? "Goal", null, void 0, void 0, { context: "static" }),
|
|
923
1002
|
subLabel: "",
|
|
924
1003
|
// The Goal module renders this template literal with {{current}} + {{goal}} substituted.
|
|
925
1004
|
// Charity goals track donation amounts — prefix `$` so the rendered bar reads
|
|
@@ -1522,7 +1601,7 @@ function mapTimer(widget, ctx) {
|
|
|
1522
1601
|
const v = widget.variables ?? {};
|
|
1523
1602
|
const textValue = typeof widget.text?.value === "string" ? widget.text.value : void 0;
|
|
1524
1603
|
const TIMER_PASSTHROUGH = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
|
|
1525
|
-
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH);
|
|
1604
|
+
const mapTemplate = (template) => translateSeText(template, null, TIMER_PASSTHROUGH, void 0, { context: "static" });
|
|
1526
1605
|
const absoluteDate = v.date ?? v.endsAt ?? v.endDate;
|
|
1527
1606
|
const target = absoluteDate ? Date.parse(absoluteDate) : NaN;
|
|
1528
1607
|
const hasValidAbsoluteDate = Number.isFinite(target);
|
|
@@ -1594,7 +1673,7 @@ function mapTimer(widget, ctx) {
|
|
|
1594
1673
|
endAnimation: "",
|
|
1595
1674
|
hideAfter: 0,
|
|
1596
1675
|
highlightColor: "inherit",
|
|
1597
|
-
messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? ""),
|
|
1676
|
+
messageAfterCountdown: translateSeText(v.messageAfterCountdown ?? "", null, void 0, void 0, { context: "static" }),
|
|
1598
1677
|
changeMessageAfter: !!v.changeMessageAfterCountdown,
|
|
1599
1678
|
countDate: hasValidAbsoluteDate && (!!v.exactTime || !!v.datePicker || !!absoluteDate),
|
|
1600
1679
|
date: hasValidAbsoluteDate ? new Date(target).toISOString() : "",
|
|
@@ -1737,28 +1816,47 @@ function mapKappagen(widget, ctx) {
|
|
|
1737
1816
|
ctx
|
|
1738
1817
|
);
|
|
1739
1818
|
}
|
|
1740
|
-
|
|
1819
|
+
var TOP_LIST_SORT_BY = {
|
|
1820
|
+
tips: "higherDonations",
|
|
1821
|
+
cheers: "higherBits",
|
|
1822
|
+
gifts: "higherGifts"
|
|
1823
|
+
};
|
|
1824
|
+
var TOP_LIST_TITLE = {
|
|
1825
|
+
tips: "Top Tippers",
|
|
1826
|
+
cheers: "Top Cheerers",
|
|
1827
|
+
gifts: "Top Gifters"
|
|
1828
|
+
};
|
|
1829
|
+
function mapViewerProfilesLeaderboard(widget, currency, ctx) {
|
|
1741
1830
|
const v = widget.variables ?? {};
|
|
1742
1831
|
const count = v.limit ?? v.visibleItems ?? 10;
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
return buildUnit(widget, "loyaltyleaderboard", {
|
|
1746
|
-
// Carry SE text styling so the leaderboard list keeps the streamer's
|
|
1832
|
+
return buildUnit(widget, "viewerprofiles", {
|
|
1833
|
+
// Carry SE text styling so the leaderboard keeps the streamer's
|
|
1747
1834
|
// chosen font / size / color.
|
|
1748
1835
|
css: mapSeTextCssToModuleCss(widget, { fontSize: 18 }),
|
|
1749
1836
|
content: {
|
|
1750
|
-
|
|
1751
|
-
currency,
|
|
1752
|
-
window,
|
|
1837
|
+
sortBy: TOP_LIST_SORT_BY[currency],
|
|
1753
1838
|
count,
|
|
1754
|
-
title,
|
|
1839
|
+
title: TOP_LIST_TITLE[currency],
|
|
1755
1840
|
showTitle: true,
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1841
|
+
showAvatar: true,
|
|
1842
|
+
showPlatform: true,
|
|
1843
|
+
// Hide noisy columns that aren't relevant for a single-metric
|
|
1844
|
+
// top-X list — the streamer picked "top by gifts/tips/cheers"
|
|
1845
|
+
// for a reason. They can re-enable in Settings if they want.
|
|
1846
|
+
showCommands: false,
|
|
1847
|
+
showAlerts: false,
|
|
1848
|
+
showBits: currency === "cheers",
|
|
1849
|
+
showLastSeen: false,
|
|
1850
|
+
showTopActivity: false,
|
|
1851
|
+
showAchievements: false,
|
|
1852
|
+
showAchievementProgress: false,
|
|
1853
|
+
refreshIntervalSeconds: 30,
|
|
1759
1854
|
itemGap: 8,
|
|
1855
|
+
autoScroll: false,
|
|
1760
1856
|
// Source-side knobs that don't map cleanly survive under the
|
|
1761
|
-
// generic importMeta envelope so the user can re-introspect.
|
|
1857
|
+
// generic importMeta envelope so the user can re-introspect later.
|
|
1858
|
+
// `period` is interesting because viewerprofiles doesn't yet have
|
|
1859
|
+
// a windowed mode — if/when it does, we can promote this.
|
|
1762
1860
|
importMeta: buildImportMeta(widget, {
|
|
1763
1861
|
period: v.period,
|
|
1764
1862
|
offset: v.offset,
|
|
@@ -1904,6 +2002,47 @@ function mapHypetrain(widget, ctx) {
|
|
|
1904
2002
|
}
|
|
1905
2003
|
}, ctx);
|
|
1906
2004
|
}
|
|
2005
|
+
function mapEmoteWall(widget, ctx) {
|
|
2006
|
+
const v = widget.variables ?? {};
|
|
2007
|
+
const imageSize = typeof v.emoteSize === "number" ? v.emoteSize : typeof v.size === "number" ? v.size : 40;
|
|
2008
|
+
const maxItemsToShow = typeof v.maxEmotes === "number" && v.maxEmotes > 0 ? v.maxEmotes : typeof v.maxItems === "number" && v.maxItems > 0 ? v.maxItems : 40;
|
|
2009
|
+
const duration = typeof v.duration === "number" && v.duration > 0 ? v.duration : typeof v.decay === "number" && v.decay > 0 ? v.decay : typeof v.decayTime === "number" && v.decayTime > 0 ? v.decayTime : 5;
|
|
2010
|
+
const sites = [];
|
|
2011
|
+
if (v.enableTwitch !== false && v.twitch !== false) sites.push("twitch");
|
|
2012
|
+
if (v.enableBttv !== false && v.bttv !== false) sites.push("bttv");
|
|
2013
|
+
if (v.enableFfz !== false && v.ffz !== false) sites.push("ffz");
|
|
2014
|
+
if (v.enable7tv !== false && v.sevenTv !== false) sites.push("seventv");
|
|
2015
|
+
const animationType = v.direction === "up" ? "rise" : v.direction === "down" ? "fall" : v.direction === "left" || v.direction === "right" ? "slide" : "random";
|
|
2016
|
+
return buildUnit(widget, "emotebox", {
|
|
2017
|
+
content: {
|
|
2018
|
+
version: 1,
|
|
2019
|
+
type: animationType,
|
|
2020
|
+
imageSize,
|
|
2021
|
+
animationTime: duration,
|
|
2022
|
+
duration,
|
|
2023
|
+
maxItemsToShow,
|
|
2024
|
+
delay: typeof v.delay === "number" ? v.delay : 0,
|
|
2025
|
+
sites,
|
|
2026
|
+
background: "transparent",
|
|
2027
|
+
theme: "dark",
|
|
2028
|
+
userLevels: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
|
2029
|
+
ignoredList: [],
|
|
2030
|
+
excludedAnimations: [],
|
|
2031
|
+
// Emote-wall is chat-driven, not alert-driven — leave alert toggles
|
|
2032
|
+
// off so the widget doesn't double-fire on subscribe/follower events.
|
|
2033
|
+
alertOn: false,
|
|
2034
|
+
alertTriggers: [],
|
|
2035
|
+
alertEmotes: [],
|
|
2036
|
+
alertNumberEmotes: 30,
|
|
2037
|
+
useAnimatedEmotes: true,
|
|
2038
|
+
importMeta: buildImportMeta(widget, {
|
|
2039
|
+
fullscreen: !!v.fullscreen,
|
|
2040
|
+
direction: v.direction
|
|
2041
|
+
})
|
|
2042
|
+
},
|
|
2043
|
+
css: { background: "transparent" }
|
|
2044
|
+
}, ctx);
|
|
2045
|
+
}
|
|
1907
2046
|
function mapUnsupportedAsText(widget, ctx) {
|
|
1908
2047
|
return buildUnit(widget, "text", {
|
|
1909
2048
|
content: {
|
|
@@ -2303,15 +2442,16 @@ function dispatch(widget, ctx) {
|
|
|
2303
2442
|
return { unit: mapReadout(widget, fallbackVar, ctx), status: "direct", lumiaType: "text" };
|
|
2304
2443
|
}
|
|
2305
2444
|
if (EVENTLIST_TYPES.has(t)) return { unit: mapEventList(widget, ctx), status: "direct", lumiaType: "eventlist" };
|
|
2306
|
-
if (t === "se-widget-top-tippers-list") return { unit:
|
|
2307
|
-
if (t === "se-widget-top-cheerers-list") return { unit:
|
|
2308
|
-
if (t === "se-widget-top-gifters-list") return { unit:
|
|
2445
|
+
if (t === "se-widget-top-tippers-list") return { unit: mapViewerProfilesLeaderboard(widget, "tips", ctx), status: "direct", lumiaType: "viewerprofiles" };
|
|
2446
|
+
if (t === "se-widget-top-cheerers-list") return { unit: mapViewerProfilesLeaderboard(widget, "cheers", ctx), status: "direct", lumiaType: "viewerprofiles" };
|
|
2447
|
+
if (t === "se-widget-top-gifters-list") return { unit: mapViewerProfilesLeaderboard(widget, "gifts", ctx), status: "direct", lumiaType: "viewerprofiles" };
|
|
2309
2448
|
if (t === "se-widget-credit-roll") return { unit: mapCredits(widget, ctx), status: "partial", lumiaType: "credits" };
|
|
2310
2449
|
if (t === "se-widget-twitch-chat") return { unit: mapChatbox(widget, ctx), status: "partial", lumiaType: "chatbox" };
|
|
2311
2450
|
if (t === "se-widget-image-rotator" || t === "se-widget-merch-products-rotator") return { unit: mapSlideshow(widget, ctx), status: t === "se-widget-merch-products-rotator" ? "partial" : "direct", lumiaType: "slideshow" };
|
|
2312
2451
|
if (t === "se-widget-countdown") return { unit: mapTimer(widget, ctx), status: "partial", lumiaType: "timer" };
|
|
2313
2452
|
if (t === "se-widget-current-song") return { unit: mapNowPlaying(widget, ctx), status: "partial", lumiaType: "nowplaying" };
|
|
2314
2453
|
if (t === "se-widget-kappagen") return { unit: mapKappagen(widget, ctx), status: "partial", lumiaType: "emotealert" };
|
|
2454
|
+
if (t === "se-widget-emote-wall") return { unit: mapEmoteWall(widget, ctx), status: "partial", lumiaType: "emotebox" };
|
|
2315
2455
|
if (t === "se-widget-giveaway") return { unit: mapGiveaway(widget, ctx), status: "direct", lumiaType: "raffle" };
|
|
2316
2456
|
if (t === "se-widget-media-share") return { unit: mapMediaShare(widget, ctx), status: "partial", lumiaType: "songrequest" };
|
|
2317
2457
|
if (t === "se-widget-hype-cup") return { unit: mapHypeCup(widget, ctx), status: "partial", lumiaType: "tipjar" };
|