cortico 0.1.0
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/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { ConfigGroup } from '../../core/types.ts';
|
|
2
|
+
import { cloneOverlayConfig } from './overlay/model.ts';
|
|
3
|
+
|
|
4
|
+
export const BILIBILI_DEFAULTS = {
|
|
5
|
+
// enabled 由Persona的装配层显式开启。
|
|
6
|
+
enabled: false,
|
|
7
|
+
/** 直播间号;短号与真实房间号都行,World 自己换算 */
|
|
8
|
+
roomId: 0,
|
|
9
|
+
/** 登录 cookie 的 SESSDATA;空值使用匿名接入。 */
|
|
10
|
+
sessdata: '',
|
|
11
|
+
/** 礼物使用 flush 投递的金额门槛,单位元。 */
|
|
12
|
+
giftFlushYuan: 1,
|
|
13
|
+
/** 相同弹幕与常规礼物进入总线前的固定归并窗 */
|
|
14
|
+
coalesceWindowMs: 300,
|
|
15
|
+
/** 固定窗内累计到此数量时立即冲刷 */
|
|
16
|
+
coalesceMaxItems: 64,
|
|
17
|
+
/** 高能榜拥挤代理的开启/退出水位 */
|
|
18
|
+
audienceOnlineRankOn: 200,
|
|
19
|
+
audienceOnlineRankOff: 170,
|
|
20
|
+
/** 人数持续不高于退出线达到此时长后解除拥挤态,单位秒。 */
|
|
21
|
+
audienceReleaseHoldSec: 120,
|
|
22
|
+
/** 人数信号新鲜度与已拥挤时的最长陈旧保持 */
|
|
23
|
+
audienceSignalFreshSec: 90,
|
|
24
|
+
audienceActiveStaleHoldSec: 300,
|
|
25
|
+
audienceEventLineBudget: 114,
|
|
26
|
+
audienceEventTokenBudget: 1061,
|
|
27
|
+
/** 重要观众常规互动占总批预算的上限比例,同时受剩余额度限制。 */
|
|
28
|
+
audienceImportantShare: 0.5,
|
|
29
|
+
overlay: cloneOverlayConfig(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const BILIBILI_CONFIG_GROUP: ConfigGroup = {
|
|
33
|
+
id: 'world:bilibili',
|
|
34
|
+
owner: 'world:bilibili',
|
|
35
|
+
schema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
title: 'B 站直播间 · 接入',
|
|
38
|
+
description: '只读接入:弹幕、礼物、醒目留言、上舰、进场与人流读数。',
|
|
39
|
+
properties: {
|
|
40
|
+
'worlds.bilibili.roomId': {
|
|
41
|
+
type: 'integer',
|
|
42
|
+
title: '直播间号',
|
|
43
|
+
minimum: 0,
|
|
44
|
+
maximum: 4294967295,
|
|
45
|
+
'x-hot': false,
|
|
46
|
+
description: '短号或真实房间号皆可。改动需重启后生效。',
|
|
47
|
+
},
|
|
48
|
+
'worlds.bilibili.sessdata': {
|
|
49
|
+
type: 'string',
|
|
50
|
+
title: '登录凭证 SESSDATA',
|
|
51
|
+
'x-hot': false,
|
|
52
|
+
description:
|
|
53
|
+
'浏览器登录 bilibili 后的 SESSDATA cookie。留空使用匿名接入,观众身份可能被脱敏。修改后需重启。',
|
|
54
|
+
},
|
|
55
|
+
'worlds.bilibili.giftFlushYuan': {
|
|
56
|
+
type: 'number',
|
|
57
|
+
title: '礼物即时投递门槛',
|
|
58
|
+
minimum: 0,
|
|
59
|
+
maximum: 1000,
|
|
60
|
+
'x-suffix': '元',
|
|
61
|
+
'x-hot': true,
|
|
62
|
+
description: '达到此金额的礼物立即投递;其余礼物参与常规合批。',
|
|
63
|
+
},
|
|
64
|
+
'worlds.bilibili.coalesceWindowMs': {
|
|
65
|
+
type: 'integer',
|
|
66
|
+
title: '相同消息归并窗',
|
|
67
|
+
minimum: 0,
|
|
68
|
+
maximum: 2000,
|
|
69
|
+
multipleOf: 50,
|
|
70
|
+
'x-scale': 1000,
|
|
71
|
+
'x-suffix': 's',
|
|
72
|
+
'x-hot': true,
|
|
73
|
+
description: '相同弹幕与常规礼物在进入事件总线前合并;0 关闭。窗口固定且不续期,flush 事件不等待。',
|
|
74
|
+
},
|
|
75
|
+
'worlds.bilibili.coalesceMaxItems': {
|
|
76
|
+
type: 'integer',
|
|
77
|
+
title: '归并池硬上限',
|
|
78
|
+
minimum: 1,
|
|
79
|
+
maximum: 1000,
|
|
80
|
+
'x-suffix': '条',
|
|
81
|
+
'x-hot': true,
|
|
82
|
+
description: '固定窗内累计到此数量时立即输出归并结果,交给事件总线调度。',
|
|
83
|
+
},
|
|
84
|
+
'worlds.bilibili.audienceOnlineRankOn': {
|
|
85
|
+
type: 'integer',
|
|
86
|
+
title: '高能榜拥挤开启线',
|
|
87
|
+
minimum: 1,
|
|
88
|
+
maximum: 100000,
|
|
89
|
+
'x-suffix': '人',
|
|
90
|
+
'x-hot': true,
|
|
91
|
+
description: '高能榜人数达到此值时进入拥挤态;只有同批上下文也超预算才会开始筛选。',
|
|
92
|
+
},
|
|
93
|
+
'worlds.bilibili.audienceOnlineRankOff': {
|
|
94
|
+
type: 'integer',
|
|
95
|
+
title: '高能榜拥挤退出线',
|
|
96
|
+
minimum: 0,
|
|
97
|
+
maximum: 100000,
|
|
98
|
+
'x-suffix': '人',
|
|
99
|
+
'x-hot': true,
|
|
100
|
+
description: '人数持续不高于此值,达到退出等待时间后解除拥挤态。',
|
|
101
|
+
},
|
|
102
|
+
'worlds.bilibili.audienceReleaseHoldSec': {
|
|
103
|
+
type: 'integer',
|
|
104
|
+
title: '拥挤退出等待',
|
|
105
|
+
minimum: 1,
|
|
106
|
+
maximum: 1800,
|
|
107
|
+
'x-suffix': 's',
|
|
108
|
+
'x-hot': true,
|
|
109
|
+
description: '高能榜人数需持续不高于退出线的时长。',
|
|
110
|
+
},
|
|
111
|
+
'worlds.bilibili.audienceSignalFreshSec': {
|
|
112
|
+
type: 'integer',
|
|
113
|
+
title: '高能榜新鲜时间',
|
|
114
|
+
minimum: 1,
|
|
115
|
+
maximum: 3600,
|
|
116
|
+
'x-suffix': 's',
|
|
117
|
+
'x-hot': true,
|
|
118
|
+
description: '超过此时间未更新时标记为陈旧,但已拥挤状态可在最长保持期内继续生效。',
|
|
119
|
+
},
|
|
120
|
+
'worlds.bilibili.audienceActiveStaleHoldSec': {
|
|
121
|
+
type: 'integer',
|
|
122
|
+
title: '拥挤陈旧保持上限',
|
|
123
|
+
minimum: 1,
|
|
124
|
+
maximum: 3600,
|
|
125
|
+
'x-suffix': 's',
|
|
126
|
+
'x-hot': true,
|
|
127
|
+
description: '最后一次高能榜读数过去这么久后,旧的拥挤判定不再启用限流。',
|
|
128
|
+
},
|
|
129
|
+
'worlds.bilibili.audienceEventLineBudget': {
|
|
130
|
+
type: 'integer',
|
|
131
|
+
title: '观众事件行预算',
|
|
132
|
+
minimum: 1,
|
|
133
|
+
maximum: 5000,
|
|
134
|
+
'x-suffix': '行/批',
|
|
135
|
+
'x-hot': true,
|
|
136
|
+
description: '拥挤态下同批归并候选超过此行数才启用筛选。',
|
|
137
|
+
},
|
|
138
|
+
'worlds.bilibili.audienceEventTokenBudget': {
|
|
139
|
+
type: 'integer',
|
|
140
|
+
title: '观众事件 token 预算',
|
|
141
|
+
minimum: 1,
|
|
142
|
+
maximum: 100000,
|
|
143
|
+
'x-suffix': 'token/批',
|
|
144
|
+
'x-hot': true,
|
|
145
|
+
description: '拥挤态下同批归并候选超过此估算量才启用筛选。',
|
|
146
|
+
},
|
|
147
|
+
'worlds.bilibili.audienceImportantShare': {
|
|
148
|
+
type: 'number',
|
|
149
|
+
title: '重要观众常规互动预算比例',
|
|
150
|
+
minimum: 0.01,
|
|
151
|
+
maximum: 1,
|
|
152
|
+
'x-scale': 100,
|
|
153
|
+
'x-suffix': '%',
|
|
154
|
+
'x-hot': true,
|
|
155
|
+
description: '重要观众常规互动占总批预算的上限比例,同时受批次剩余额度限制。',
|
|
156
|
+
},
|
|
157
|
+
'worlds.bilibili.overlay.enabled': {
|
|
158
|
+
type: 'boolean',
|
|
159
|
+
title: '启用 Overlay',
|
|
160
|
+
'x-hot': false,
|
|
161
|
+
description: '在本机启动透明 Overlay 页面。改动需重启后生效。',
|
|
162
|
+
},
|
|
163
|
+
'worlds.bilibili.overlay.port': {
|
|
164
|
+
type: 'integer',
|
|
165
|
+
title: 'Overlay 端口',
|
|
166
|
+
minimum: 0,
|
|
167
|
+
maximum: 65535,
|
|
168
|
+
'x-hot': false,
|
|
169
|
+
description: '默认 7795;被占用时会自动顺延。0 表示随机空闲端口。',
|
|
170
|
+
},
|
|
171
|
+
'worlds.bilibili.overlay.agentNoticeMaxChars': {
|
|
172
|
+
type: 'integer',
|
|
173
|
+
title: 'Agent 公告字数上限',
|
|
174
|
+
minimum: 1,
|
|
175
|
+
maximum: 5000,
|
|
176
|
+
'x-suffix': '字',
|
|
177
|
+
'x-hot': true,
|
|
178
|
+
description: 'Agent 公告工具写入纯文本时的 Unicode 字符上限。',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/** config.json 的 `worlds.bilibili` 节。 */
|
|
185
|
+
export type BilibiliConfigSection = typeof BILIBILI_DEFAULTS;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* B 站直播间 World 的浏览器扩展。
|
|
3
|
+
*
|
|
4
|
+
* `log` 保留直播间入站诊断。Overlay 编辑器由 World 的回环服务独立承载,
|
|
5
|
+
* 控制台只暴露入口链接。
|
|
6
|
+
*
|
|
7
|
+
* 轮询 state,按 total 差值追加事件。服务端响应类型在浏览器端声明,避免导入 Node 模块。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ConsoleClientBundle, ConsolePanelContext, ConsolePanel } from '../../../web/shared/client-panel.ts';
|
|
11
|
+
|
|
12
|
+
export function errText(err: unknown): string {
|
|
13
|
+
return err instanceof Error ? err.message : String(err);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// 服务端 `BilibiliWorld.console().invoke('log', 'state')` 的返回形状
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
interface BilibiliLiveStatus {
|
|
21
|
+
/** `stopped` / `connecting` / `connected` / `retrying` */
|
|
22
|
+
phase: string;
|
|
23
|
+
/** 配置里填的房间号(可能是短号) */
|
|
24
|
+
roomId: number;
|
|
25
|
+
/** 换算出的真实房间号;还没握手完就是 null */
|
|
26
|
+
realRoomId: number | null;
|
|
27
|
+
title: string | null;
|
|
28
|
+
living: boolean;
|
|
29
|
+
/** 登录凭证对应的自己的 uid;匿名接入时为 0 */
|
|
30
|
+
selfUid: number;
|
|
31
|
+
lastError: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 待投递的观众统计;计数项累加,其余字段保留最新值。 */
|
|
35
|
+
interface BilibiliAggregate {
|
|
36
|
+
enter: number;
|
|
37
|
+
like: number;
|
|
38
|
+
freeGift: number;
|
|
39
|
+
watched: number | null;
|
|
40
|
+
online: number | null;
|
|
41
|
+
popularity: number | null;
|
|
42
|
+
fans: number | null;
|
|
43
|
+
likeTotal: number | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface BilibiliLogState {
|
|
47
|
+
status: BilibiliLiveStatus | null;
|
|
48
|
+
/** 最近一窗弹幕均缺少有效 uid。 */
|
|
49
|
+
desensitized: boolean;
|
|
50
|
+
aggregate: BilibiliAggregate;
|
|
51
|
+
/** 最近事件的文本,按时间倒序排列。 */
|
|
52
|
+
recent: string[];
|
|
53
|
+
/** 记过的总条数(含已被上限挤掉的) */
|
|
54
|
+
total: number;
|
|
55
|
+
/** cmd → 条数,按条数降序;没进白名单也没进黑名单的 cmd 也在里面 */
|
|
56
|
+
counts: Array<[string, number]>;
|
|
57
|
+
audienceAdmission: {
|
|
58
|
+
trackedViewers: number;
|
|
59
|
+
qualifiedViewers: number;
|
|
60
|
+
crowd: { onlineRankCount: number | null; signalFresh: boolean; active: boolean };
|
|
61
|
+
totals: { input: number; selected: number; dropped: number; limitedBatches: number };
|
|
62
|
+
lastBatch: { limitingActive: boolean; input: number; selected: number } | null;
|
|
63
|
+
persistenceError: string | null;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DESC = '直播间事件与各 cmd 计数。';
|
|
68
|
+
|
|
69
|
+
const PHASE_TEXT: Record<string, string> = {
|
|
70
|
+
stopped: '未接入',
|
|
71
|
+
connecting: '连接中',
|
|
72
|
+
connected: '已接入',
|
|
73
|
+
retrying: '重连中',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const POLL_MS = 2000;
|
|
77
|
+
/** 与服务端 RECENT_CAP 一致。 */
|
|
78
|
+
const KEEP = 200;
|
|
79
|
+
|
|
80
|
+
const logPanel: ConsolePanel = {
|
|
81
|
+
mount(ctx: ConsolePanelContext) {
|
|
82
|
+
const { ui, root } = ctx;
|
|
83
|
+
const card = ui.sheet({ title: '直播间事件', en: 'bilibili live', desc: DESC });
|
|
84
|
+
root.appendChild(card.el);
|
|
85
|
+
|
|
86
|
+
const err = ui.msgline();
|
|
87
|
+
const info = ui.h('div');
|
|
88
|
+
const gauges = ui.statgrid();
|
|
89
|
+
const view = ui.log({ max: KEEP, maxHeight: '320px', empty: '(等待直播间消息…)' });
|
|
90
|
+
const counts = ui.table({ head: ['cmd', '条数'] });
|
|
91
|
+
const btnEnd = ui.button('回到底部', { size: 'sm', onClick: () => view.scrollToEnd() });
|
|
92
|
+
const bar = ui.rowbar();
|
|
93
|
+
bar.append(ui.h('span', 'grow'), btnEnd);
|
|
94
|
+
card.body.append(err, info, gauges, bar, view.el, counts.el);
|
|
95
|
+
|
|
96
|
+
let shown = 0;
|
|
97
|
+
|
|
98
|
+
const setError = (text: string | null): void => {
|
|
99
|
+
err.textContent = text ?? '';
|
|
100
|
+
err.className = text ? 'msgline bad' : 'msgline';
|
|
101
|
+
err.hidden = !text;
|
|
102
|
+
};
|
|
103
|
+
setError(null);
|
|
104
|
+
|
|
105
|
+
const drawInfo = (st: BilibiliLogState): void => {
|
|
106
|
+
const s = st.status;
|
|
107
|
+
const room = s?.realRoomId
|
|
108
|
+
? `${s.realRoomId}${s.roomId && s.roomId !== s.realRoomId ? `(短号 ${s.roomId})` : ''}`
|
|
109
|
+
: s?.roomId
|
|
110
|
+
? `${s.roomId}(还没换算出真实房间号)`
|
|
111
|
+
: '未配置';
|
|
112
|
+
info.replaceChildren(ui.kv([
|
|
113
|
+
{ k: '接入', v: PHASE_TEXT[s?.phase ?? 'stopped'] ?? s?.phase ?? '未接入' },
|
|
114
|
+
{ k: '直播间', v: room },
|
|
115
|
+
{ k: '标题', v: s?.title || '—' },
|
|
116
|
+
{ k: '开播', v: ui.pill(s?.living ? '直播中' : '未开播', s?.living ? 'on' : 'off') },
|
|
117
|
+
{
|
|
118
|
+
k: '身份',
|
|
119
|
+
v: st.desensitized
|
|
120
|
+
? ui.pill('近期弹幕缺少观众 uid', 'off')
|
|
121
|
+
: ui.pill(s?.selfUid ? `已登录(uid ${s.selfUid})` : '待观察', s?.selfUid ? 'on' : 'plain'),
|
|
122
|
+
},
|
|
123
|
+
...(s?.lastError ? [{ k: '最近错误', v: s.lastError }] : []),
|
|
124
|
+
]));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** 服务端在生成投递正文后清零聚合计数。 */
|
|
128
|
+
const drawGauges = (agg: BilibiliAggregate, admission: BilibiliLogState['audienceAdmission']): void => {
|
|
129
|
+
const num = (v: number | null): string => (v === null ? '—' : ui.fmt.count(v));
|
|
130
|
+
const last = admission.lastBatch;
|
|
131
|
+
const limiter = admission.crowd.active
|
|
132
|
+
? last?.limitingActive
|
|
133
|
+
? `拥挤(上批保留 ${last.selected}/${last.input})`
|
|
134
|
+
: '拥挤'
|
|
135
|
+
: last?.limitingActive
|
|
136
|
+
? `未限流(上批保留 ${last.selected}/${last.input})`
|
|
137
|
+
: '未限流';
|
|
138
|
+
gauges.replaceChildren(
|
|
139
|
+
ui.stat({ k: '待投递 · 进场', v: agg.enter, unit: '人' }),
|
|
140
|
+
ui.stat({ k: '待投递 · 点赞', v: agg.like, unit: '次' }),
|
|
141
|
+
ui.stat({ k: '待投递 · 免费礼物', v: agg.freeGift, unit: '个' }),
|
|
142
|
+
ui.stat({ k: '高能榜', v: num(admission.crowd.onlineRankCount ?? agg.online), unit: '人' }),
|
|
143
|
+
ui.stat({ k: '事件筛选', v: limiter }),
|
|
144
|
+
ui.stat({ k: '重要观众', v: admission.qualifiedViewers, unit: '人' }),
|
|
145
|
+
ui.stat({ k: '累计过滤', v: admission.totals.dropped, unit: '条' }),
|
|
146
|
+
ui.stat({ k: '看过', v: num(agg.watched) }),
|
|
147
|
+
ui.stat({ k: '人气', v: num(agg.popularity) }),
|
|
148
|
+
ui.stat({ k: '粉丝', v: num(agg.fans) }),
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const drawCounts = (rows: BilibiliLogState['counts']): void => {
|
|
153
|
+
counts.clear(rows.length ? undefined : '(还没收到任何 cmd)');
|
|
154
|
+
for (const [cmd, n] of rows) counts.addRow([cmd, n]);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const appendNew = (st: BilibiliLogState): void => {
|
|
158
|
+
const fresh = Math.min(st.total - shown, st.recent.length);
|
|
159
|
+
if (fresh <= 0) {
|
|
160
|
+
shown = st.total;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const chronological = [...st.recent].reverse();
|
|
164
|
+
for (const line of chronological.slice(chronological.length - fresh)) view.append(line);
|
|
165
|
+
shown = st.total;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
let polling = false;
|
|
169
|
+
const poll = (): void => {
|
|
170
|
+
if (polling) return;
|
|
171
|
+
polling = true;
|
|
172
|
+
void ctx.invoke<BilibiliLogState>('state').then(
|
|
173
|
+
(st) => {
|
|
174
|
+
polling = false;
|
|
175
|
+
if (ctx.signal.aborted) return;
|
|
176
|
+
setError(null);
|
|
177
|
+
drawInfo(st);
|
|
178
|
+
drawGauges(st.aggregate, st.audienceAdmission);
|
|
179
|
+
drawCounts(st.counts);
|
|
180
|
+
appendNew(st);
|
|
181
|
+
},
|
|
182
|
+
(e: unknown) => {
|
|
183
|
+
polling = false;
|
|
184
|
+
if (ctx.signal.aborted) return;
|
|
185
|
+
setError(`读取直播间状态失败: ${errText(e)}`);
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
ctx.interval(poll, POLL_MS);
|
|
191
|
+
poll();
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const bundle: ConsoleClientBundle = {
|
|
196
|
+
// 键是**局部** panel id,与服务端 `console().panels[].id` 一一对应。
|
|
197
|
+
panels: {
|
|
198
|
+
log: logPanel,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export default bundle;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import type { WorldDefinition } from '../../world.ts';
|
|
3
|
+
import { type BilibiliConfigSection, BILIBILI_DEFAULTS } from './config.ts';
|
|
4
|
+
import { BilibiliWorld } from './world.ts';
|
|
5
|
+
|
|
6
|
+
export const BILIBILI: WorldDefinition<BilibiliConfigSection> = {
|
|
7
|
+
id: 'bilibili',
|
|
8
|
+
label: 'B 站直播间',
|
|
9
|
+
defaults: () => structuredClone(BILIBILI_DEFAULTS),
|
|
10
|
+
create: (ctx) => {
|
|
11
|
+
const { cfg } = ctx;
|
|
12
|
+
return new BilibiliWorld({
|
|
13
|
+
roomId: cfg.roomId,
|
|
14
|
+
sessdata: cfg.sessdata,
|
|
15
|
+
giftFlushYuan: () => cfg.giftFlushYuan,
|
|
16
|
+
coalesceWindowMs: () => cfg.coalesceWindowMs,
|
|
17
|
+
coalesceMaxItems: () => cfg.coalesceMaxItems,
|
|
18
|
+
audienceOnlineRankOn: () => cfg.audienceOnlineRankOn,
|
|
19
|
+
audienceOnlineRankOff: () => cfg.audienceOnlineRankOff,
|
|
20
|
+
audienceReleaseHoldSec: () => cfg.audienceReleaseHoldSec,
|
|
21
|
+
audienceSignalFreshSec: () => cfg.audienceSignalFreshSec,
|
|
22
|
+
audienceActiveStaleHoldSec: () => cfg.audienceActiveStaleHoldSec,
|
|
23
|
+
audienceEventLineBudget: () => cfg.audienceEventLineBudget,
|
|
24
|
+
audienceEventTokenBudget: () => cfg.audienceEventTokenBudget,
|
|
25
|
+
audienceImportantShare: () => cfg.audienceImportantShare,
|
|
26
|
+
audienceLedgerFile: join(ctx.dataDir, 'bilibili-audience', 'ledger.json'),
|
|
27
|
+
overlay: cfg.overlay,
|
|
28
|
+
agentNoticeMaxChars: () => cfg.overlay.agentNoticeMaxChars,
|
|
29
|
+
agentNoticeFile: join(ctx.dataDir, 'bilibili-overlay', 'agent-notice.json'),
|
|
30
|
+
overlayAssetDir: join(ctx.dataDir, 'bilibili-overlay', 'assets'),
|
|
31
|
+
onOverlayConfig: (overlay) => ctx.persist({ overlay: { design: overlay.design } }),
|
|
32
|
+
timezone: ctx.timezone,
|
|
33
|
+
});
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `SEND_GIFT_V2` 的 protobuf 帧 → V1 的 JSON 字段形状。
|
|
3
|
+
*
|
|
4
|
+
* 此层将 protobuf 字段映射到 V1 的 JSON 名称,使 normalize、overlay/project 共用处理路径,并支持 V1/V2 共存及帧回放。
|
|
5
|
+
*
|
|
6
|
+
* 字段号依据实采帧推断,未有官方 schema;映射如下:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* 顶层
|
|
10
|
+
* 1 uid 送礼人
|
|
11
|
+
* 2 uname
|
|
12
|
+
* 3 face 头像 URL
|
|
13
|
+
* 8 medal 粉丝牌:5=等级 6=牌名。**可能是别的房间的牌子**
|
|
14
|
+
* 10 gift 礼物体,见下
|
|
15
|
+
* 15 sender 送礼人完整档案(1=uid,2={1=名字,2=头像})
|
|
16
|
+
* 礼物体(顶层 10)
|
|
17
|
+
* 1 giftId 2 giftName 3 num 4 giftType(动画礼物=2)
|
|
18
|
+
* 5/6/7 金瓜子三槽,见 coinTotal
|
|
19
|
+
* 8 coin_type("gold"=付费,"silver"=免费)
|
|
20
|
+
* 9 tid(这一笔的交易号,V1 同名字段同形)
|
|
21
|
+
* 12 combo_id 14 连击累计金瓜子(100/200/400/600 这样递增,**不是单笔**)
|
|
22
|
+
* 18 动作词("投喂")
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* 以下字段不填:
|
|
26
|
+
*
|
|
27
|
+
* - `guard_level`:粉丝牌里那个档位(medal.12)是**牌子所属房间**的舰长等级,
|
|
28
|
+
* 不能作为本房间的舰长等级。
|
|
29
|
+
* - `is_admin`:尚未确认对应的 protobuf 字段。
|
|
30
|
+
*/
|
|
31
|
+
import { pbFromBase64, pbInt, pbSub, pbText, type PbField } from './protobuf.ts';
|
|
32
|
+
|
|
33
|
+
/** 由调用方记录解析告警。 */
|
|
34
|
+
type GiftFrameWarn = (message: string, data?: Record<string, unknown>) => void;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* V1 data 原样返回;V2 返回补齐 V1 字段名的副本。V2 缺少礼物体或名称时返回原 data。
|
|
38
|
+
*/
|
|
39
|
+
export function giftFrameData(
|
|
40
|
+
data: Record<string, unknown>,
|
|
41
|
+
warn?: GiftFrameWarn,
|
|
42
|
+
): Record<string, unknown> {
|
|
43
|
+
if (text(data.giftName) || text(data.gift_name)) return data;
|
|
44
|
+
const top = pbFromBase64(data.pb);
|
|
45
|
+
const gift = pbSub(top, 10);
|
|
46
|
+
const giftName = pbText(gift, 2);
|
|
47
|
+
if (!gift || !giftName) {
|
|
48
|
+
if (data.pb !== undefined) {
|
|
49
|
+
warn?.('SEND_GIFT_V2 缺少有效礼物体或礼物名', {
|
|
50
|
+
decoded: top !== null,
|
|
51
|
+
giftBody: gift !== null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
const sender = pbSub(top, 15);
|
|
57
|
+
const senderBase = pbSub(sender, 2);
|
|
58
|
+
const uid = pbInt(top, 1) ?? pbInt(sender, 1);
|
|
59
|
+
const uname = pbText(top, 2) || pbText(senderBase, 1);
|
|
60
|
+
const face = pbText(top, 3) || pbText(senderBase, 2);
|
|
61
|
+
const num = pbInt(gift, 3);
|
|
62
|
+
const coinType = pbText(gift, 8);
|
|
63
|
+
const totalCoin = coinTotal(gift, num, giftName, warn);
|
|
64
|
+
const giftId = pbInt(gift, 1);
|
|
65
|
+
const tid = pbText(gift, 9);
|
|
66
|
+
return {
|
|
67
|
+
...data,
|
|
68
|
+
...(uid !== undefined ? { uid } : {}),
|
|
69
|
+
...(uname ? { uname } : {}),
|
|
70
|
+
giftName,
|
|
71
|
+
...(giftId !== undefined ? { giftId } : {}),
|
|
72
|
+
...(num !== undefined ? { num } : {}),
|
|
73
|
+
...(coinType ? { coin_type: coinType } : {}),
|
|
74
|
+
...(totalCoin !== undefined ? { total_coin: totalCoin } : {}),
|
|
75
|
+
...(tid ? { tid } : {}),
|
|
76
|
+
sender_uinfo: senderUinfo(top, uid, uname, face),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function senderUinfo(
|
|
81
|
+
top: readonly PbField[] | null,
|
|
82
|
+
uid: number | undefined,
|
|
83
|
+
uname: string,
|
|
84
|
+
face: string,
|
|
85
|
+
): Record<string, unknown> {
|
|
86
|
+
const medal = pbSub(top, 8);
|
|
87
|
+
const medalLevel = pbInt(medal, 5);
|
|
88
|
+
const medalName = pbText(medal, 6);
|
|
89
|
+
const hasMedal = medalLevel !== undefined || medalName !== '';
|
|
90
|
+
return {
|
|
91
|
+
...(uid !== undefined ? { uid } : {}),
|
|
92
|
+
base: {
|
|
93
|
+
...(uname ? { name: uname } : {}),
|
|
94
|
+
...(face ? { face } : {}),
|
|
95
|
+
},
|
|
96
|
+
...(hasMedal
|
|
97
|
+
? {
|
|
98
|
+
medal: {
|
|
99
|
+
...(medalLevel !== undefined ? { level: medalLevel } : {}),
|
|
100
|
+
...(medalName ? { name: medalName } : {}),
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
: {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 金瓜子字段映射尚未确认,暂取字段 5/6/7 中正数的最大值作为 fallback。
|
|
109
|
+
* 有效值不相等或已知 num 不为 1 时告警。
|
|
110
|
+
*/
|
|
111
|
+
function coinTotal(
|
|
112
|
+
gift: readonly PbField[],
|
|
113
|
+
num: number | undefined,
|
|
114
|
+
giftName: string,
|
|
115
|
+
warn?: GiftFrameWarn,
|
|
116
|
+
): number | undefined {
|
|
117
|
+
const slots = [5, 6, 7]
|
|
118
|
+
.map((field) => pbInt(gift, field))
|
|
119
|
+
.filter((value): value is number => value !== undefined && value > 0);
|
|
120
|
+
if (slots.length === 0) return undefined;
|
|
121
|
+
const total = Math.max(...slots);
|
|
122
|
+
const settled = slots.every((value) => value === total) && (num ?? 1) === 1;
|
|
123
|
+
if (!settled) {
|
|
124
|
+
warn?.('SEND_GIFT_V2 金瓜子字段映射未确认,暂取最大值', { gift: giftName, slots, num });
|
|
125
|
+
}
|
|
126
|
+
return total;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function text(value: unknown): string {
|
|
130
|
+
return typeof value === 'string' ? value : '';
|
|
131
|
+
}
|