add configurable gift menu overlays
Add tenant-scoped gift menu settings, catalog-backed triggers, infinite OBS rendering, and guard assets. Normalize legacy and protobuf gift values for blind-box, battery-tier, and transaction-aware matching.
This commit is contained in:
@@ -12,6 +12,9 @@ import type {
|
||||
ComponentSummary,
|
||||
CookieCloudSource,
|
||||
GiftEffectSettings,
|
||||
GiftCatalogItem,
|
||||
GiftMenuItem,
|
||||
GiftMenuSettings,
|
||||
Invitation,
|
||||
OverlaySettings,
|
||||
Session,
|
||||
@@ -22,8 +25,10 @@ import type {
|
||||
} from './types'
|
||||
import { normalizeThemeId } from './themes'
|
||||
import { normalizeGiftEffectThemeId } from './giftThemes'
|
||||
import { normalizeGiftMenuThemeId } from './giftMenuThemes'
|
||||
import {
|
||||
defaultGiftEffectSettings,
|
||||
defaultGiftMenuSettings,
|
||||
defaultOverlaySettings,
|
||||
defaultSongRequestSettings,
|
||||
} from './types'
|
||||
@@ -236,6 +241,90 @@ export function normalizeGiftEffectSettings(value: unknown): GiftEffectSettings
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeGiftMenuItem(value: unknown): GiftMenuItem | undefined {
|
||||
const item = object(value)
|
||||
const trigger = object(item.trigger)
|
||||
if (typeof item.id !== 'string' || typeof item.description !== 'string') return undefined
|
||||
if (trigger.kind === 'gift') {
|
||||
const giftId = Number(trigger.giftId)
|
||||
if (!Number.isSafeInteger(giftId) || giftId <= 0 || typeof trigger.giftName !== 'string')
|
||||
return undefined
|
||||
return {
|
||||
id: item.id,
|
||||
description: item.description,
|
||||
trigger: {
|
||||
kind: 'gift',
|
||||
giftId,
|
||||
giftName: trigger.giftName,
|
||||
unitPrice: Number(trigger.unitPrice ?? 0),
|
||||
imageUrl: typeof trigger.imageUrl === 'string' ? trigger.imageUrl : undefined,
|
||||
},
|
||||
}
|
||||
}
|
||||
if (
|
||||
trigger.kind === 'guard' &&
|
||||
['captain', 'admiral', 'governor'].includes(String(trigger.level))
|
||||
) {
|
||||
return {
|
||||
id: item.id,
|
||||
description: item.description,
|
||||
trigger: {
|
||||
kind: 'guard',
|
||||
level: trigger.level as 'captain' | 'admiral' | 'governor',
|
||||
},
|
||||
}
|
||||
}
|
||||
const amount = Number(trigger.amount)
|
||||
if (trigger.kind === 'battery' && Number.isSafeInteger(amount) && amount > 0) {
|
||||
return {
|
||||
id: item.id,
|
||||
description: item.description,
|
||||
trigger: { kind: 'battery', amount },
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function normalizeGiftMenuSettings(value: unknown): GiftMenuSettings {
|
||||
const root = object(value)
|
||||
const settings = object(root.settings ?? value)
|
||||
const items = Array.isArray(settings.items)
|
||||
? settings.items.map(normalizeGiftMenuItem).filter(item => item !== undefined)
|
||||
: []
|
||||
return {
|
||||
...defaultGiftMenuSettings,
|
||||
...(settings as Partial<GiftMenuSettings>),
|
||||
themeId: normalizeGiftMenuThemeId(settings.themeId),
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeGiftCatalog(value: unknown): GiftCatalogItem[] {
|
||||
const root = object(value)
|
||||
if (!Array.isArray(root.gifts)) return []
|
||||
return root.gifts.flatMap(candidate => {
|
||||
const gift = object(candidate)
|
||||
const id = Number(gift.id)
|
||||
if (!Number.isSafeInteger(id) || id <= 0 || typeof gift.name !== 'string') return []
|
||||
return [
|
||||
{
|
||||
id,
|
||||
name: gift.name,
|
||||
coinType: String(gift.coinType ?? 'gold'),
|
||||
batteryValue: Number(gift.batteryValue ?? Number(gift.unitPrice ?? 0) / 100),
|
||||
unitPrice: Number(gift.unitPrice ?? 0),
|
||||
imageUrl:
|
||||
typeof gift.imageUrl === 'string'
|
||||
? gift.imageUrl
|
||||
: typeof gift.animationUrl === 'string'
|
||||
? gift.animationUrl
|
||||
: undefined,
|
||||
animationUrl: typeof gift.animationUrl === 'string' ? gift.animationUrl : undefined,
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export function normalizeSongRequestItem(value: unknown): SongRequestItem | undefined {
|
||||
const item = object(value)
|
||||
const requester = object(item.requester)
|
||||
|
||||
Reference in New Issue
Block a user