@sfmc-bds/db-server 0.2.0-beta.4 → 0.2.0-beta.6
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 +663 -0
- package/dist/domain/bridge.d.ts +57 -23
- package/dist/domain/bridge.d.ts.map +1 -1
- package/dist/domain/bridge.js +77 -6
- package/dist/domain/bridge.js.map +2 -2
- package/dist/domain/bridge.test.d.ts +2 -0
- package/dist/domain/bridge.test.d.ts.map +1 -0
- package/dist/domain/bridge.test.js +87 -0
- package/dist/domain/bridge.test.js.map +7 -0
- package/dist/domain/economy.d.ts +38 -7
- package/dist/domain/economy.d.ts.map +1 -1
- package/dist/domain/economy.js.map +2 -2
- package/dist/domain/messages-bridge-filter.test.d.ts +5 -0
- package/dist/domain/messages-bridge-filter.test.d.ts.map +1 -0
- package/dist/domain/messages-bridge-filter.test.js +20 -0
- package/dist/domain/messages-bridge-filter.test.js.map +7 -0
- package/dist/domain/qq-bind-status.test.d.ts +2 -0
- package/dist/domain/qq-bind-status.test.d.ts.map +1 -0
- package/dist/domain/qq-bind-status.test.js +211 -0
- package/dist/domain/qq-bind-status.test.js.map +7 -0
- package/dist/domain/qq-events.d.ts +84 -0
- package/dist/domain/qq-events.d.ts.map +1 -0
- package/dist/domain/qq-events.js +193 -0
- package/dist/domain/qq-events.js.map +7 -0
- package/dist/domain/qq-events.test.d.ts +5 -0
- package/dist/domain/qq-events.test.d.ts.map +1 -0
- package/dist/domain/qq-events.test.js +170 -0
- package/dist/domain/qq-events.test.js.map +7 -0
- package/dist/domain/qq-join.test.d.ts +2 -0
- package/dist/domain/qq-join.test.d.ts.map +1 -0
- package/dist/domain/qq-join.test.js +353 -0
- package/dist/domain/qq-join.test.js.map +7 -0
- package/dist/domain/schema.d.ts +17 -19
- package/dist/domain/schema.d.ts.map +1 -1
- package/dist/domain/schema.js +69 -0
- package/dist/domain/schema.js.map +2 -2
- package/dist/domain/system-status.d.ts +75 -0
- package/dist/domain/system-status.d.ts.map +1 -0
- package/dist/domain/system-status.js +181 -0
- package/dist/domain/system-status.js.map +7 -0
- package/dist/domain/system-status.test.d.ts +5 -0
- package/dist/domain/system-status.test.d.ts.map +1 -0
- package/dist/domain/system-status.test.js +19 -0
- package/dist/domain/system-status.test.js.map +7 -0
- package/dist/domain/transaction.d.ts +5 -4
- package/dist/domain/transaction.d.ts.map +1 -1
- package/dist/domain/transaction.js.map +2 -2
- package/dist/env.d.ts +7 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +55 -3
- package/dist/env.js.map +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -19
- package/dist/index.js.map +2 -2
- package/dist/lib/idempotency-store.d.ts +17 -11
- package/dist/lib/idempotency-store.d.ts.map +1 -1
- package/dist/lib/idempotency-store.js.map +2 -2
- package/dist/lib/idempotency.d.ts +3 -4
- package/dist/lib/idempotency.d.ts.map +1 -1
- package/dist/lib/idempotency.js.map +2 -2
- package/dist/lib/module-state.d.ts +38 -8
- package/dist/lib/module-state.d.ts.map +1 -1
- package/dist/lib/module-state.js.map +2 -2
- package/dist/lib/order-by.d.ts +8 -6
- package/dist/lib/order-by.d.ts.map +1 -1
- package/dist/lib/order-by.js.map +2 -2
- package/dist/lib/sql-helpers.d.ts +25 -13
- package/dist/lib/sql-helpers.d.ts.map +1 -1
- package/dist/lib/sql-helpers.js.map +2 -2
- package/dist/lib/sqlite.d.ts +14 -5
- package/dist/lib/sqlite.d.ts.map +1 -1
- package/dist/lib/sqlite.js.map +2 -2
- package/dist/manifest-loader.d.ts +5 -2
- package/dist/manifest-loader.d.ts.map +1 -1
- package/dist/manifest-loader.js +16 -6
- package/dist/manifest-loader.js.map +2 -2
- package/dist/manifest.d.ts +1 -2
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +13 -8
- package/dist/manifest.js.map +2 -2
- package/dist/project-root.d.ts +1 -1
- package/dist/project-root.d.ts.map +1 -1
- package/dist/project-root.js +2 -2
- package/dist/project-root.js.map +2 -2
- package/dist/routes/_shared.d.ts +14 -5
- package/dist/routes/_shared.d.ts.map +1 -1
- package/dist/routes/_shared.js.map +2 -2
- package/dist/routes/config.d.ts +8 -11
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +17 -23
- package/dist/routes/config.js.map +2 -2
- package/dist/routes/db-routes.d.ts +6 -0
- package/dist/routes/db-routes.d.ts.map +1 -1
- package/dist/routes/db-routes.js.map +2 -2
- package/dist/routes/messages.d.ts +6 -2
- package/dist/routes/messages.d.ts.map +1 -1
- package/dist/routes/messages.js +17 -3
- package/dist/routes/messages.js.map +2 -2
- package/dist/routes/module-config-routes.d.ts +13 -9
- package/dist/routes/module-config-routes.d.ts.map +1 -1
- package/dist/routes/module-config-routes.js.map +2 -2
- package/dist/routes/modules.d.ts +6 -0
- package/dist/routes/modules.d.ts.map +1 -1
- package/dist/routes/modules.js.map +2 -2
- package/dist/routes/qq-bind.d.ts +24 -0
- package/dist/routes/qq-bind.d.ts.map +1 -0
- package/dist/routes/qq-bind.js +179 -0
- package/dist/routes/qq-bind.js.map +7 -0
- package/dist/routes/qq-events.d.ts +20 -0
- package/dist/routes/qq-events.d.ts.map +1 -0
- package/dist/routes/qq-events.js +39 -0
- package/dist/routes/qq-events.js.map +7 -0
- package/dist/routes/qq-join.d.ts +46 -0
- package/dist/routes/qq-join.d.ts.map +1 -0
- package/dist/routes/qq-join.js +277 -0
- package/dist/routes/qq-join.js.map +7 -0
- package/dist/routes/service-routes.d.ts +6 -0
- package/dist/routes/service-routes.d.ts.map +1 -1
- package/dist/routes/service-routes.js.map +2 -2
- package/dist/routes/status.d.ts +19 -0
- package/dist/routes/status.d.ts.map +1 -0
- package/dist/routes/status.js +74 -0
- package/dist/routes/status.js.map +7 -0
- package/dist/schema-registry.d.ts +16 -15
- package/dist/schema-registry.d.ts.map +1 -1
- package/dist/schema-registry.js +7 -1
- package/dist/schema-registry.js.map +2 -2
- package/dist/server.d.ts +19 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js.map +2 -2
- package/dist/service-registry.d.ts +39 -9
- package/dist/service-registry.d.ts.map +1 -1
- package/dist/service-registry.js +30 -2
- package/dist/service-registry.js.map +2 -2
- package/dist/services/builtin-handlers.d.ts +27 -7
- package/dist/services/builtin-handlers.d.ts.map +1 -1
- package/dist/services/builtin-handlers.js.map +2 -2
- package/dist/tx-runner.d.ts +29 -3
- package/dist/tx-runner.d.ts.map +1 -1
- package/dist/tx-runner.js +29 -3
- package/dist/tx-runner.js.map +2 -2
- package/package.json +17 -15
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_QQ_EVENTS
|
|
3
|
+
} from "@sfmc-bds/sdk/node/config";
|
|
4
|
+
import { sendGroupOutbound } from "./bridge.js";
|
|
5
|
+
const WINDOW_TYPES = /* @__PURE__ */ new Set(["join", "leave", "death"]);
|
|
6
|
+
const IMMEDIATE_TYPES = /* @__PURE__ */ new Set(["crash", "start"]);
|
|
7
|
+
const MAX_WINDOW_EVENTS = 20;
|
|
8
|
+
const CAUSE_ZH = {
|
|
9
|
+
anvil: "\u94C1\u7827",
|
|
10
|
+
blockExplosion: "\u65B9\u5757\u7206\u70B8",
|
|
11
|
+
campfire: "\u8425\u706B",
|
|
12
|
+
drowning: "\u6EBA\u6C34",
|
|
13
|
+
entityAttack: "\u751F\u7269\u653B\u51FB",
|
|
14
|
+
entityExplosion: "\u5B9E\u4F53\u7206\u70B8",
|
|
15
|
+
fall: "\u5760\u843D",
|
|
16
|
+
fallingBlock: "\u843D\u77F3",
|
|
17
|
+
fire: "\u706B\u7130",
|
|
18
|
+
fireTick: "\u707C\u70E7",
|
|
19
|
+
flyIntoWall: "\u649E\u5899",
|
|
20
|
+
freezing: "\u51BB\u7ED3",
|
|
21
|
+
lava: "\u5CA9\u6D46",
|
|
22
|
+
lightning: "\u96F7\u51FB",
|
|
23
|
+
magic: "\u9B54\u6CD5",
|
|
24
|
+
magma: "\u5CA9\u6D46\u5757",
|
|
25
|
+
none: "\u672A\u77E5",
|
|
26
|
+
override: "\u5F3A\u5236",
|
|
27
|
+
piston: "\u6D3B\u585E",
|
|
28
|
+
projectile: "\u5F39\u5C04\u7269",
|
|
29
|
+
stalactite: "\u949F\u4E73\u77F3",
|
|
30
|
+
stalagmite: "\u77F3\u7B0B",
|
|
31
|
+
starve: "\u9965\u997F",
|
|
32
|
+
suffocation: "\u7A92\u606F",
|
|
33
|
+
suicide: "\u81EA\u6740",
|
|
34
|
+
thorns: "\u8346\u68D8",
|
|
35
|
+
void: "\u865A\u7A7A",
|
|
36
|
+
wither: "\u51CB\u96F6",
|
|
37
|
+
// 常见生物名(若上层将实体名填充至 cause 字段)
|
|
38
|
+
zombie: "\u50F5\u5C38",
|
|
39
|
+
skeleton: "\u9AB7\u9AC5",
|
|
40
|
+
creeper: "\u82E6\u529B\u6015",
|
|
41
|
+
player: "\u73A9\u5BB6"
|
|
42
|
+
};
|
|
43
|
+
function resolveQqEventsConfig(raw) {
|
|
44
|
+
const o = raw && typeof raw === "object" ? raw : {};
|
|
45
|
+
const windowSec = Number(o.window_sec ?? DEFAULT_QQ_EVENTS.window_sec);
|
|
46
|
+
return {
|
|
47
|
+
enabled: o.enabled === void 0 ? DEFAULT_QQ_EVENTS.enabled : o.enabled === true,
|
|
48
|
+
window_sec: Number.isFinite(windowSec) && windowSec >= 5 ? Math.min(600, Math.floor(windowSec)) : DEFAULT_QQ_EVENTS.window_sec,
|
|
49
|
+
join: o.join === void 0 ? DEFAULT_QQ_EVENTS.join : o.join === true,
|
|
50
|
+
leave: o.leave === void 0 ? DEFAULT_QQ_EVENTS.leave : o.leave === true,
|
|
51
|
+
death: o.death === void 0 ? DEFAULT_QQ_EVENTS.death : o.death === true,
|
|
52
|
+
crash: o.crash === void 0 ? DEFAULT_QQ_EVENTS.crash : o.crash === true,
|
|
53
|
+
start: o.start === void 0 ? DEFAULT_QQ_EVENTS.start : o.start === true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function localizeCause(cause) {
|
|
57
|
+
if (!cause) return "";
|
|
58
|
+
const key = cause.trim();
|
|
59
|
+
if (!key) return "";
|
|
60
|
+
return CAUSE_ZH[key] ?? CAUSE_ZH[key.toLowerCase()] ?? key;
|
|
61
|
+
}
|
|
62
|
+
function isEventTypeEnabled(cfg, type) {
|
|
63
|
+
if (!cfg.enabled) return false;
|
|
64
|
+
return cfg[type] === true;
|
|
65
|
+
}
|
|
66
|
+
function formatWindowBody(events) {
|
|
67
|
+
const joins = [];
|
|
68
|
+
const leaves = [];
|
|
69
|
+
const deaths = [];
|
|
70
|
+
for (const e of events) {
|
|
71
|
+
if (e.type === "join") joins.push(e.player);
|
|
72
|
+
else if (e.type === "leave") leaves.push(e.player);
|
|
73
|
+
else {
|
|
74
|
+
const c = localizeCause(e.cause);
|
|
75
|
+
deaths.push(c ? `${e.player}\uFF08${c}\uFF09` : e.player);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const lines = ["[MC\u4E8B\u4EF6]"];
|
|
79
|
+
if (joins.length) lines.push(`\u4E0A\u7EBF\uFF1A${joins.join("\u3001")}`);
|
|
80
|
+
if (leaves.length) lines.push(`\u4E0B\u7EBF\uFF1A${leaves.join("\u3001")}`);
|
|
81
|
+
if (deaths.length) lines.push(`\u6B7B\u4EA1\uFF1A${deaths.join("\u3001")}`);
|
|
82
|
+
return lines.join("\n");
|
|
83
|
+
}
|
|
84
|
+
function formatImmediateBody(ev) {
|
|
85
|
+
if (ev.type === "crash") {
|
|
86
|
+
const d = String(ev.detail ?? "").trim();
|
|
87
|
+
return d ? `[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (${d})` : "[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA";
|
|
88
|
+
}
|
|
89
|
+
if (ev.type === "start") {
|
|
90
|
+
const d = String(ev.detail ?? "").trim();
|
|
91
|
+
return d ? `[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (${d})` : "[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8";
|
|
92
|
+
}
|
|
93
|
+
return `[MC\u4E8B\u4EF6] ${ev.type}`;
|
|
94
|
+
}
|
|
95
|
+
function normalizeEventPayload(raw) {
|
|
96
|
+
if (!raw || typeof raw !== "object") return null;
|
|
97
|
+
const o = raw;
|
|
98
|
+
const type = String(o.type ?? "").trim();
|
|
99
|
+
if (!WINDOW_TYPES.has(type) && !IMMEDIATE_TYPES.has(type)) return null;
|
|
100
|
+
const out = { type };
|
|
101
|
+
if (o.player !== void 0) {
|
|
102
|
+
const player = String(o.player).trim();
|
|
103
|
+
if (player) out.player = player;
|
|
104
|
+
}
|
|
105
|
+
if (o.cause !== void 0) {
|
|
106
|
+
const cause = String(o.cause).trim();
|
|
107
|
+
if (cause) out.cause = cause;
|
|
108
|
+
}
|
|
109
|
+
if (o.detail !== void 0) {
|
|
110
|
+
const detail = String(o.detail).trim();
|
|
111
|
+
if (detail) out.detail = detail;
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
function createQqEventsAggregator(deps) {
|
|
116
|
+
const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;
|
|
117
|
+
const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;
|
|
118
|
+
const send = deps.send ?? ((text) => {
|
|
119
|
+
sendGroupOutbound(deps.getOutbound(), text);
|
|
120
|
+
});
|
|
121
|
+
let buffer = [];
|
|
122
|
+
let timer = null;
|
|
123
|
+
function clearTimer() {
|
|
124
|
+
if (timer != null) {
|
|
125
|
+
clearTimeoutFn(timer);
|
|
126
|
+
timer = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function flushWindow() {
|
|
130
|
+
clearTimer();
|
|
131
|
+
if (buffer.length === 0) return;
|
|
132
|
+
const snapshot = buffer;
|
|
133
|
+
buffer = [];
|
|
134
|
+
const text = formatWindowBody(snapshot);
|
|
135
|
+
if (text.split("\n").length > 1) send(text);
|
|
136
|
+
}
|
|
137
|
+
function scheduleFlush(windowSec) {
|
|
138
|
+
if (timer != null) return;
|
|
139
|
+
timer = setTimeoutFn(() => {
|
|
140
|
+
timer = null;
|
|
141
|
+
flushWindow();
|
|
142
|
+
}, windowSec * 1e3);
|
|
143
|
+
}
|
|
144
|
+
function ingestOne(ev) {
|
|
145
|
+
const cfg = deps.getConfig();
|
|
146
|
+
if (!isEventTypeEnabled(cfg, ev.type)) {
|
|
147
|
+
return { accepted: false, reason: "disabled" };
|
|
148
|
+
}
|
|
149
|
+
if (IMMEDIATE_TYPES.has(ev.type)) {
|
|
150
|
+
flushWindow();
|
|
151
|
+
send(formatImmediateBody(ev));
|
|
152
|
+
return { accepted: true };
|
|
153
|
+
}
|
|
154
|
+
const player = String(ev.player ?? "").trim();
|
|
155
|
+
if (!player) return { accepted: false, reason: "missing_player" };
|
|
156
|
+
buffer.push({
|
|
157
|
+
type: ev.type,
|
|
158
|
+
player,
|
|
159
|
+
...ev.cause ? { cause: ev.cause } : {}
|
|
160
|
+
});
|
|
161
|
+
scheduleFlush(cfg.window_sec);
|
|
162
|
+
if (buffer.length >= MAX_WINDOW_EVENTS) flushWindow();
|
|
163
|
+
return { accepted: true };
|
|
164
|
+
}
|
|
165
|
+
function ingestMany(items) {
|
|
166
|
+
let accepted = 0;
|
|
167
|
+
let rejected = 0;
|
|
168
|
+
for (const ev of items) {
|
|
169
|
+
const r = ingestOne(ev);
|
|
170
|
+
if (r.accepted) accepted += 1;
|
|
171
|
+
else rejected += 1;
|
|
172
|
+
}
|
|
173
|
+
return { accepted, rejected };
|
|
174
|
+
}
|
|
175
|
+
function dispose() {
|
|
176
|
+
flushWindow();
|
|
177
|
+
}
|
|
178
|
+
function pendingCount() {
|
|
179
|
+
return buffer.length;
|
|
180
|
+
}
|
|
181
|
+
return { ingestOne, ingestMany, flushWindow, dispose, pendingCount };
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
MAX_WINDOW_EVENTS,
|
|
185
|
+
createQqEventsAggregator,
|
|
186
|
+
formatImmediateBody,
|
|
187
|
+
formatWindowBody,
|
|
188
|
+
isEventTypeEnabled,
|
|
189
|
+
localizeCause,
|
|
190
|
+
normalizeEventPayload,
|
|
191
|
+
resolveQqEventsConfig
|
|
192
|
+
};
|
|
193
|
+
//# sourceMappingURL=qq-events.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/domain/qq-events.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * domain/qq-events.ts \u2014 Minecraft \u670D\u52A1\u5668\u4E8B\u4EF6\u7FA4\u63A8\u9001\u805A\u5408\u8282\u6D41\u5668\n *\n * \u805A\u5408\u7B56\u7565\uFF1A\n * - \u73A9\u5BB6\u8FDB\u9000\u4E0E\u6B7B\u4EA1\uFF08join / leave / death\uFF09\uFF1A\u5728\u65F6\u95F4\u7A97\u53E3\u5185\u6536\u96C6\u7F13\u51B2\uFF0C\u5408\u5E76\u4E3A\u5355\u6761\u6D88\u606F\u53D1\u9001\uFF0C\u907F\u514D\u5237\u5C4F\n * - \u5173\u952E\u8FD0\u884C\u6001\u4E8B\u4EF6\uFF08crash / start\uFF09\uFF1A\u5148\u7ACB\u5373\u51B2\u5237\uFF08flush\uFF09\u5F53\u524D\u7A97\u53E3\u5185\u7684\u5F85\u53D1\u9001\u4E8B\u4EF6\uFF0C\u968F\u540E\u7ACB\u5373\u5355\u72EC\u63A8\u9001\n */\n\nimport {\n DEFAULT_QQ_EVENTS,\n type QqEventsConfig,\n} from \"@sfmc-bds/sdk/node/config\";\n\nimport type { OutboundConfig } from \"./bridge.js\";\nimport { sendGroupOutbound } from \"./bridge.js\";\n\nexport type QqEventType = \"join\" | \"leave\" | \"death\" | \"crash\" | \"start\";\n\nexport type QqEventPayload = {\n type: QqEventType;\n player?: string;\n cause?: string;\n detail?: string;\n};\n\nexport type ResolvedQqEventsConfig = Required<QqEventsConfig>;\n\nconst WINDOW_TYPES = new Set<QqEventType>([\"join\", \"leave\", \"death\"]);\nconst IMMEDIATE_TYPES = new Set<QqEventType>([\"crash\", \"start\"]);\n\n/** \u7A97\u53E3\u5185\u6761\u6570\u4E0A\u9650\uFF1A\u8FBE\u5230\u4E0A\u9650\u65F6\u63D0\u524D\u89E6\u53D1 flush\uFF0C\u907F\u514D\u5355\u6B21\u79EF\u538B\u8FC7\u591A\u5185\u5BB9\u3002 */\nexport const MAX_WINDOW_EVENTS = 20;\n\n/** SAPI damageSource.cause \u2192 \u4E2D\u6587\u6620\u5C04\u5B57\u5178\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u6807\u8BC6\uFF09\u3002 */\nconst CAUSE_ZH: Record<string, string> = {\n anvil: \"\u94C1\u7827\",\n blockExplosion: \"\u65B9\u5757\u7206\u70B8\",\n campfire: \"\u8425\u706B\",\n drowning: \"\u6EBA\u6C34\",\n entityAttack: \"\u751F\u7269\u653B\u51FB\",\n entityExplosion: \"\u5B9E\u4F53\u7206\u70B8\",\n fall: \"\u5760\u843D\",\n fallingBlock: \"\u843D\u77F3\",\n fire: \"\u706B\u7130\",\n fireTick: \"\u707C\u70E7\",\n flyIntoWall: \"\u649E\u5899\",\n freezing: \"\u51BB\u7ED3\",\n lava: \"\u5CA9\u6D46\",\n lightning: \"\u96F7\u51FB\",\n magic: \"\u9B54\u6CD5\",\n magma: \"\u5CA9\u6D46\u5757\",\n none: \"\u672A\u77E5\",\n override: \"\u5F3A\u5236\",\n piston: \"\u6D3B\u585E\",\n projectile: \"\u5F39\u5C04\u7269\",\n stalactite: \"\u949F\u4E73\u77F3\",\n stalagmite: \"\u77F3\u7B0B\",\n starve: \"\u9965\u997F\",\n suffocation: \"\u7A92\u606F\",\n suicide: \"\u81EA\u6740\",\n thorns: \"\u8346\u68D8\",\n void: \"\u865A\u7A7A\",\n wither: \"\u51CB\u96F6\",\n // \u5E38\u89C1\u751F\u7269\u540D\uFF08\u82E5\u4E0A\u5C42\u5C06\u5B9E\u4F53\u540D\u586B\u5145\u81F3 cause \u5B57\u6BB5\uFF09\n zombie: \"\u50F5\u5C38\",\n skeleton: \"\u9AB7\u9AC5\",\n creeper: \"\u82E6\u529B\u6015\",\n player: \"\u73A9\u5BB6\",\n};\n\n/**\n * \u89E3\u6790\u5E76\u586B\u5145 QQ \u4E8B\u4EF6\u63A8\u9001\u914D\u7F6E\u7684\u9ED8\u8BA4\u503C\u3002\n *\n * @param raw \u539F\u59CB\u914D\u7F6E\u5BF9\u8C61\u3002\n * @returns \u89C4\u8303\u5316\u7684\u5B8C\u6574\u914D\u7F6E\u5BF9\u8C61\u3002\n */\nexport function resolveQqEventsConfig(raw: unknown): ResolvedQqEventsConfig {\n const o = raw && typeof raw === \"object\" ? (raw as Record<string, unknown>) : {};\n const windowSec = Number(o.window_sec ?? DEFAULT_QQ_EVENTS.window_sec);\n return {\n enabled: o.enabled === undefined ? DEFAULT_QQ_EVENTS.enabled : o.enabled === true,\n window_sec:\n Number.isFinite(windowSec) && windowSec >= 5\n ? Math.min(600, Math.floor(windowSec))\n : DEFAULT_QQ_EVENTS.window_sec,\n join: o.join === undefined ? DEFAULT_QQ_EVENTS.join : o.join === true,\n leave: o.leave === undefined ? DEFAULT_QQ_EVENTS.leave : o.leave === true,\n death: o.death === undefined ? DEFAULT_QQ_EVENTS.death : o.death === true,\n crash: o.crash === undefined ? DEFAULT_QQ_EVENTS.crash : o.crash === true,\n start: o.start === undefined ? DEFAULT_QQ_EVENTS.start : o.start === true,\n };\n}\n\n/**\n * \u5C06 SAPI \u539F\u751F\u4F24\u5BB3\u6765\u6E90\u4EE3\u7801\uFF08damageSource.cause\uFF09\u8F6C\u6362\u4E3A\u6613\u8BFB\u7684\u4E2D\u6587\u6587\u672C\u3002\n *\n * @param cause \u4F24\u5BB3\u6765\u6E90\u6807\u8BC6\u3002\n * @returns \u4E2D\u6587\u4F24\u5BB3\u63CF\u8FF0\uFF08\u672A\u6536\u5F55\u65F6\u4FDD\u7559\u539F\u82F1\u6587\u4EE3\u7801\uFF09\u3002\n */\nexport function localizeCause(cause: string | undefined): string {\n if (!cause) return \"\";\n const key = cause.trim();\n if (!key) return \"\";\n return CAUSE_ZH[key] ?? CAUSE_ZH[key.toLowerCase()] ?? key;\n}\n\nexport function isEventTypeEnabled(cfg: ResolvedQqEventsConfig, type: QqEventType): boolean {\n if (!cfg.enabled) return false;\n return cfg[type] === true;\n}\n\ntype Buffered = {\n type: \"join\" | \"leave\" | \"death\";\n player: string;\n cause?: string;\n};\n\nexport type QqEventsAggregatorDeps = {\n getConfig: () => ResolvedQqEventsConfig;\n getOutbound: () => OutboundConfig;\n /** \u53EF\u9009\u6CE8\u5165\uFF1A\u4F9B\u5355\u5143\u6D4B\u8BD5\u6A21\u62DF\u65F6\u949F\u4E0E\u5B9A\u65F6\u5668\u3002 */\n setTimeoutFn?: (fn: () => void, ms: number) => ReturnType<typeof setTimeout>;\n clearTimeoutFn?: (id: ReturnType<typeof setTimeout>) => void;\n send?: (text: string) => void;\n};\n\n/**\n * \u683C\u5F0F\u5316\u65F6\u95F4\u7A97\u53E3\u5185\u7684\u805A\u5408\u4E8B\u4EF6\u6587\u672C\u3002\n *\n * @param events \u7F13\u51B2\u533A\u5185\u7684\u4E8B\u4EF6\u5217\u8868\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u591A\u884C\u6D88\u606F\u5B57\u7B26\u4E32\u3002\n */\nexport function formatWindowBody(events: Buffered[]): string {\n const joins: string[] = [];\n const leaves: string[] = [];\n const deaths: string[] = [];\n for (const e of events) {\n if (e.type === \"join\") joins.push(e.player);\n else if (e.type === \"leave\") leaves.push(e.player);\n else {\n const c = localizeCause(e.cause);\n deaths.push(c ? `${e.player}\uFF08${c}\uFF09` : e.player);\n }\n }\n const lines: string[] = [\"[MC\u4E8B\u4EF6]\"];\n if (joins.length) lines.push(`\u4E0A\u7EBF\uFF1A${joins.join(\"\u3001\")}`);\n if (leaves.length) lines.push(`\u4E0B\u7EBF\uFF1A${leaves.join(\"\u3001\")}`);\n if (deaths.length) lines.push(`\u6B7B\u4EA1\uFF1A${deaths.join(\"\u3001\")}`);\n return lines.join(\"\\n\");\n}\n\n/**\n * \u683C\u5F0F\u5316\u9AD8\u4F18\u5148\u7EA7\u7684\u975E\u805A\u5408\u5373\u65F6\u4E8B\u4EF6\u6587\u672C\uFF08\u5982 BDS \u5D29\u6E83\u6216\u542F\u52A8\u5C31\u7EEA\uFF09\u3002\n *\n * @param ev \u5373\u65F6\u4E8B\u4EF6\u8F7D\u8377\u3002\n * @returns \u683C\u5F0F\u5316\u540E\u7684\u4E8B\u4EF6\u901A\u77E5\u6587\u672C\u3002\n */\nexport function formatImmediateBody(ev: QqEventPayload): string {\n if (ev.type === \"crash\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (${d})` : \"[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA\";\n }\n if (ev.type === \"start\") {\n const d = String(ev.detail ?? \"\").trim();\n return d ? `[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (${d})` : \"[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8\";\n }\n return `[MC\u4E8B\u4EF6] ${ev.type}`;\n}\n\nexport function normalizeEventPayload(raw: unknown): QqEventPayload | null {\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n const type = String(o.type ?? \"\").trim() as QqEventType;\n if (!WINDOW_TYPES.has(type) && !IMMEDIATE_TYPES.has(type)) return null;\n const out: QqEventPayload = { type };\n if (o.player !== undefined) {\n const player = String(o.player).trim();\n if (player) out.player = player;\n }\n if (o.cause !== undefined) {\n const cause = String(o.cause).trim();\n if (cause) out.cause = cause;\n }\n if (o.detail !== undefined) {\n const detail = String(o.detail).trim();\n if (detail) out.detail = detail;\n }\n return out;\n}\n\n/**\n * \u521B\u5EFA QQ \u670D\u52A1\u5668\u4E8B\u4EF6\u805A\u5408\u8282\u6D41\u5668\u5B9E\u4F8B\u3002\n *\n * @param deps \u4F9D\u8D56\u6CE8\u5165\u5BF9\u8C61\uFF08\u5305\u542B\u914D\u7F6E\u83B7\u53D6\u3001\u51FA\u7AD9\u53D1\u9001\u53CA\u5B9A\u65F6\u5668\u5B9E\u73B0\uFF09\u3002\n * @returns \u5305\u542B push, flush, cancel, getPendingCount \u7B49\u65B9\u6CD5\u7684\u805A\u5408\u5668\u63A7\u5236\u5668\u3002\n */\nexport function createQqEventsAggregator(deps: QqEventsAggregatorDeps) {\n\n const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;\n const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;\n const send =\n deps.send ??\n ((text: string) => {\n sendGroupOutbound(deps.getOutbound(), text);\n });\n\n let buffer: Buffered[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function clearTimer(): void {\n if (timer != null) {\n clearTimeoutFn(timer);\n timer = null;\n }\n }\n\n function flushWindow(): void {\n clearTimer();\n if (buffer.length === 0) return;\n const snapshot = buffer;\n buffer = [];\n const text = formatWindowBody(snapshot);\n if (text.split(\"\\n\").length > 1) send(text);\n }\n\n function scheduleFlush(windowSec: number): void {\n if (timer != null) return;\n timer = setTimeoutFn(() => {\n timer = null;\n flushWindow();\n }, windowSec * 1000);\n }\n\n function ingestOne(ev: QqEventPayload): { accepted: boolean; reason?: string } {\n const cfg = deps.getConfig();\n if (!isEventTypeEnabled(cfg, ev.type)) {\n return { accepted: false, reason: \"disabled\" };\n }\n\n if (IMMEDIATE_TYPES.has(ev.type)) {\n flushWindow();\n send(formatImmediateBody(ev));\n return { accepted: true };\n }\n\n const player = String(ev.player ?? \"\").trim();\n if (!player) return { accepted: false, reason: \"missing_player\" };\n\n buffer.push({\n type: ev.type as \"join\" | \"leave\" | \"death\",\n player,\n ...(ev.cause ? { cause: ev.cause } : {}),\n });\n scheduleFlush(cfg.window_sec);\n if (buffer.length >= MAX_WINDOW_EVENTS) flushWindow();\n return { accepted: true };\n }\n\n function ingestMany(items: QqEventPayload[]): {\n accepted: number;\n rejected: number;\n } {\n let accepted = 0;\n let rejected = 0;\n for (const ev of items) {\n const r = ingestOne(ev);\n if (r.accepted) accepted += 1;\n else rejected += 1;\n }\n return { accepted, rejected };\n }\n\n /** \u6D4B\u8BD5 / \u5173\u505C\uFF1A\u51B2\u5237\u5E76\u6E05\u5B9A\u65F6\u5668 */\n function dispose(): void {\n flushWindow();\n }\n\n /** \u4EC5\u6D4B\u7528\uFF1A\u5F53\u524D\u7F13\u51B2\u957F\u5EA6 */\n function pendingCount(): number {\n return buffer.length;\n }\n\n return { ingestOne, ingestMany, flushWindow, dispose, pendingCount };\n}\n\nexport type QqEventsAggregator = ReturnType<typeof createQqEventsAggregator>;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qq-events.test.d.ts","sourceRoot":"","sources":["../../src/domain/qq-events.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
createQqEventsAggregator,
|
|
5
|
+
formatImmediateBody,
|
|
6
|
+
formatWindowBody,
|
|
7
|
+
localizeCause,
|
|
8
|
+
MAX_WINDOW_EVENTS,
|
|
9
|
+
normalizeEventPayload,
|
|
10
|
+
resolveQqEventsConfig
|
|
11
|
+
} from "./qq-events.js";
|
|
12
|
+
const ALL_ON = {
|
|
13
|
+
enabled: true,
|
|
14
|
+
window_sec: 60,
|
|
15
|
+
join: true,
|
|
16
|
+
leave: true,
|
|
17
|
+
death: true,
|
|
18
|
+
crash: true,
|
|
19
|
+
start: true
|
|
20
|
+
};
|
|
21
|
+
const fakeOutbound = {
|
|
22
|
+
backend: "official",
|
|
23
|
+
creds: { appId: "a", appSecret: "s" },
|
|
24
|
+
groupOpenid: "G",
|
|
25
|
+
prefix: "[MC]"
|
|
26
|
+
};
|
|
27
|
+
test("resolveQqEventsConfig \u7F3A\u7701\u4E0E\u603B\u5F00\u5173", () => {
|
|
28
|
+
const d = resolveQqEventsConfig(void 0);
|
|
29
|
+
assert.equal(d.enabled, true);
|
|
30
|
+
assert.equal(d.window_sec, 60);
|
|
31
|
+
assert.equal(d.join, true);
|
|
32
|
+
const off = resolveQqEventsConfig({ enabled: false, join: false });
|
|
33
|
+
assert.equal(off.enabled, false);
|
|
34
|
+
assert.equal(off.join, false);
|
|
35
|
+
assert.equal(off.death, true);
|
|
36
|
+
});
|
|
37
|
+
test("localizeCause \u6620\u5C04\u4E0E\u539F\u6587\u56DE\u9000", () => {
|
|
38
|
+
assert.equal(localizeCause("fall"), "\u5760\u843D");
|
|
39
|
+
assert.equal(localizeCause("drowning"), "\u6EBA\u6C34");
|
|
40
|
+
assert.equal(localizeCause("custom_xyz"), "custom_xyz");
|
|
41
|
+
assert.equal(localizeCause(""), "");
|
|
42
|
+
});
|
|
43
|
+
test("normalizeEventPayload \u8FC7\u6EE4\u975E\u6CD5 type", () => {
|
|
44
|
+
assert.equal(normalizeEventPayload({ type: "chat" }), null);
|
|
45
|
+
assert.deepEqual(normalizeEventPayload({ type: "join", player: "Steve" }), {
|
|
46
|
+
type: "join",
|
|
47
|
+
player: "Steve"
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
test("formatWindowBody \u805A\u5408\u6587\u6848", () => {
|
|
51
|
+
const text = formatWindowBody([
|
|
52
|
+
{ type: "join", player: "Steve" },
|
|
53
|
+
{ type: "join", player: "Alex" },
|
|
54
|
+
{ type: "leave", player: "Bob" },
|
|
55
|
+
{ type: "death", player: "Steve", cause: "fall" }
|
|
56
|
+
]);
|
|
57
|
+
assert.match(text, /^\[MC事件\]/);
|
|
58
|
+
assert.match(text, /上线:Steve、Alex/);
|
|
59
|
+
assert.match(text, /下线:Bob/);
|
|
60
|
+
assert.match(text, /死亡:Steve(坠落)/);
|
|
61
|
+
});
|
|
62
|
+
test("formatImmediateBody crash/start", () => {
|
|
63
|
+
assert.equal(formatImmediateBody({ type: "crash", detail: "code=1" }), "[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (code=1)");
|
|
64
|
+
assert.equal(formatImmediateBody({ type: "start", detail: "pid=9" }), "[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (pid=9)");
|
|
65
|
+
});
|
|
66
|
+
test("\u7A97\u53E3\u5185 join+death \u5230\u671F flush \u6210\u4E00\u6761", () => {
|
|
67
|
+
const sent = [];
|
|
68
|
+
const timers = [];
|
|
69
|
+
const agg = createQqEventsAggregator({
|
|
70
|
+
getConfig: () => ({ ...ALL_ON, window_sec: 60 }),
|
|
71
|
+
getOutbound: () => fakeOutbound,
|
|
72
|
+
send: (t) => sent.push(t),
|
|
73
|
+
setTimeoutFn: (fn, ms) => {
|
|
74
|
+
timers.push({ fn, ms });
|
|
75
|
+
return timers.length;
|
|
76
|
+
},
|
|
77
|
+
clearTimeoutFn: () => {
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
agg.ingestOne({ type: "join", player: "A" });
|
|
81
|
+
agg.ingestOne({ type: "join", player: "B" });
|
|
82
|
+
agg.ingestOne({ type: "death", player: "A", cause: "fall" });
|
|
83
|
+
assert.equal(sent.length, 0);
|
|
84
|
+
assert.equal(agg.pendingCount(), 3);
|
|
85
|
+
assert.equal(timers.length, 1);
|
|
86
|
+
assert.equal(timers[0].ms, 6e4);
|
|
87
|
+
timers[0].fn();
|
|
88
|
+
assert.equal(sent.length, 1);
|
|
89
|
+
assert.match(sent[0], /上线:A、B/);
|
|
90
|
+
assert.match(sent[0], /死亡:A(坠落)/);
|
|
91
|
+
assert.equal(agg.pendingCount(), 0);
|
|
92
|
+
agg.dispose();
|
|
93
|
+
});
|
|
94
|
+
test("crash \u7ACB\u5373\u63A8\u4E14\u5148\u51B2\u5237\u7A97\u53E3", () => {
|
|
95
|
+
const sent = [];
|
|
96
|
+
const timers = [];
|
|
97
|
+
const agg = createQqEventsAggregator({
|
|
98
|
+
getConfig: () => ALL_ON,
|
|
99
|
+
getOutbound: () => fakeOutbound,
|
|
100
|
+
send: (t) => sent.push(t),
|
|
101
|
+
setTimeoutFn: (fn) => {
|
|
102
|
+
timers.push({ fn });
|
|
103
|
+
return 1;
|
|
104
|
+
},
|
|
105
|
+
clearTimeoutFn: () => {
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
agg.ingestOne({ type: "join", player: "Steve" });
|
|
109
|
+
agg.ingestOne({ type: "crash", detail: "code=1" });
|
|
110
|
+
assert.equal(sent.length, 2);
|
|
111
|
+
assert.match(sent[0], /上线:Steve/);
|
|
112
|
+
assert.match(sent[1], /BDS 意外退出 \(code=1\)/);
|
|
113
|
+
assert.equal(agg.pendingCount(), 0);
|
|
114
|
+
agg.dispose();
|
|
115
|
+
});
|
|
116
|
+
test("enabled:false \u5168\u4E22", () => {
|
|
117
|
+
const sent = [];
|
|
118
|
+
const agg = createQqEventsAggregator({
|
|
119
|
+
getConfig: () => ({ ...ALL_ON, enabled: false }),
|
|
120
|
+
getOutbound: () => fakeOutbound,
|
|
121
|
+
send: (t) => sent.push(t)
|
|
122
|
+
});
|
|
123
|
+
const r = agg.ingestMany([
|
|
124
|
+
{ type: "join", player: "A" },
|
|
125
|
+
{ type: "crash", detail: "x" }
|
|
126
|
+
]);
|
|
127
|
+
assert.equal(r.accepted, 0);
|
|
128
|
+
assert.equal(r.rejected, 2);
|
|
129
|
+
assert.equal(sent.length, 0);
|
|
130
|
+
agg.dispose();
|
|
131
|
+
});
|
|
132
|
+
test("\u5355\u7C7B\u5173\u95ED\uFF1Adeath \u5173\u5219\u4E22\u5F03", () => {
|
|
133
|
+
const sent = [];
|
|
134
|
+
const timers = [];
|
|
135
|
+
const agg = createQqEventsAggregator({
|
|
136
|
+
getConfig: () => ({ ...ALL_ON, death: false }),
|
|
137
|
+
getOutbound: () => fakeOutbound,
|
|
138
|
+
send: (t) => sent.push(t),
|
|
139
|
+
setTimeoutFn: (fn) => {
|
|
140
|
+
timers.push({ fn });
|
|
141
|
+
return 1;
|
|
142
|
+
},
|
|
143
|
+
clearTimeoutFn: () => {
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
assert.equal(agg.ingestOne({ type: "death", player: "A", cause: "fall" }).accepted, false);
|
|
147
|
+
assert.equal(agg.ingestOne({ type: "join", player: "A" }).accepted, true);
|
|
148
|
+
timers[0].fn();
|
|
149
|
+
assert.equal(sent.length, 1);
|
|
150
|
+
assert.doesNotMatch(sent[0], /死亡/);
|
|
151
|
+
agg.dispose();
|
|
152
|
+
});
|
|
153
|
+
test("\u7A97\u53E3\u6EE1 MAX_WINDOW_EVENTS \u63D0\u524D flush", () => {
|
|
154
|
+
const sent = [];
|
|
155
|
+
const agg = createQqEventsAggregator({
|
|
156
|
+
getConfig: () => ({ ...ALL_ON, window_sec: 600 }),
|
|
157
|
+
getOutbound: () => fakeOutbound,
|
|
158
|
+
send: (t) => sent.push(t),
|
|
159
|
+
setTimeoutFn: () => 1,
|
|
160
|
+
clearTimeoutFn: () => {
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
for (let i = 0; i < MAX_WINDOW_EVENTS; i++) {
|
|
164
|
+
agg.ingestOne({ type: "join", player: `P${i}` });
|
|
165
|
+
}
|
|
166
|
+
assert.equal(sent.length, 1);
|
|
167
|
+
assert.equal(agg.pendingCount(), 0);
|
|
168
|
+
agg.dispose();
|
|
169
|
+
});
|
|
170
|
+
//# sourceMappingURL=qq-events.test.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/domain/qq-events.test.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * qq-events \u805A\u5408\u5668\u5355\u6D4B\n */\n\nimport assert from \"node:assert/strict\";\nimport test from \"node:test\";\nimport {\n createQqEventsAggregator,\n formatImmediateBody,\n formatWindowBody,\n localizeCause,\n MAX_WINDOW_EVENTS,\n normalizeEventPayload,\n resolveQqEventsConfig,\n type ResolvedQqEventsConfig,\n} from \"./qq-events.js\";\nimport type { OutboundConfig } from \"./bridge.js\";\n\nconst ALL_ON: ResolvedQqEventsConfig = {\n enabled: true,\n window_sec: 60,\n join: true,\n leave: true,\n death: true,\n crash: true,\n start: true,\n};\n\nconst fakeOutbound: OutboundConfig = {\n backend: \"official\",\n creds: { appId: \"a\", appSecret: \"s\" },\n groupOpenid: \"G\",\n prefix: \"[MC]\",\n};\n\ntest(\"resolveQqEventsConfig \u7F3A\u7701\u4E0E\u603B\u5F00\u5173\", () => {\n const d = resolveQqEventsConfig(undefined);\n assert.equal(d.enabled, true);\n assert.equal(d.window_sec, 60);\n assert.equal(d.join, true);\n\n const off = resolveQqEventsConfig({ enabled: false, join: false });\n assert.equal(off.enabled, false);\n assert.equal(off.join, false);\n assert.equal(off.death, true);\n});\n\ntest(\"localizeCause \u6620\u5C04\u4E0E\u539F\u6587\u56DE\u9000\", () => {\n assert.equal(localizeCause(\"fall\"), \"\u5760\u843D\");\n assert.equal(localizeCause(\"drowning\"), \"\u6EBA\u6C34\");\n assert.equal(localizeCause(\"custom_xyz\"), \"custom_xyz\");\n assert.equal(localizeCause(\"\"), \"\");\n});\n\ntest(\"normalizeEventPayload \u8FC7\u6EE4\u975E\u6CD5 type\", () => {\n assert.equal(normalizeEventPayload({ type: \"chat\" }), null);\n assert.deepEqual(normalizeEventPayload({ type: \"join\", player: \"Steve\" }), {\n type: \"join\",\n player: \"Steve\",\n });\n});\n\ntest(\"formatWindowBody \u805A\u5408\u6587\u6848\", () => {\n const text = formatWindowBody([\n { type: \"join\", player: \"Steve\" },\n { type: \"join\", player: \"Alex\" },\n { type: \"leave\", player: \"Bob\" },\n { type: \"death\", player: \"Steve\", cause: \"fall\" },\n ]);\n assert.match(text, /^\\[MC\u4E8B\u4EF6\\]/);\n assert.match(text, /\u4E0A\u7EBF\uFF1ASteve\u3001Alex/);\n assert.match(text, /\u4E0B\u7EBF\uFF1ABob/);\n assert.match(text, /\u6B7B\u4EA1\uFF1ASteve\uFF08\u5760\u843D\uFF09/);\n});\n\ntest(\"formatImmediateBody crash/start\", () => {\n assert.equal(formatImmediateBody({ type: \"crash\", detail: \"code=1\" }), \"[MC\u4E8B\u4EF6] BDS \u610F\u5916\u9000\u51FA (code=1)\");\n assert.equal(formatImmediateBody({ type: \"start\", detail: \"pid=9\" }), \"[MC\u4E8B\u4EF6] BDS \u5DF2\u542F\u52A8 (pid=9)\");\n});\n\ntest(\"\u7A97\u53E3\u5185 join+death \u5230\u671F flush \u6210\u4E00\u6761\", () => {\n const sent: string[] = [];\n const timers: Array<{ fn: () => void; ms: number }> = [];\n const agg = createQqEventsAggregator({\n getConfig: () => ({ ...ALL_ON, window_sec: 60 }),\n getOutbound: () => fakeOutbound,\n send: (t) => sent.push(t),\n setTimeoutFn: (fn, ms) => {\n timers.push({ fn, ms });\n return timers.length as unknown as ReturnType<typeof setTimeout>;\n },\n clearTimeoutFn: () => {},\n });\n\n agg.ingestOne({ type: \"join\", player: \"A\" });\n agg.ingestOne({ type: \"join\", player: \"B\" });\n agg.ingestOne({ type: \"death\", player: \"A\", cause: \"fall\" });\n assert.equal(sent.length, 0);\n assert.equal(agg.pendingCount(), 3);\n assert.equal(timers.length, 1);\n assert.equal(timers[0]!.ms, 60_000);\n\n timers[0]!.fn();\n assert.equal(sent.length, 1);\n assert.match(sent[0]!, /\u4E0A\u7EBF\uFF1AA\u3001B/);\n assert.match(sent[0]!, /\u6B7B\u4EA1\uFF1AA\uFF08\u5760\u843D\uFF09/);\n assert.equal(agg.pendingCount(), 0);\n agg.dispose();\n});\n\ntest(\"crash \u7ACB\u5373\u63A8\u4E14\u5148\u51B2\u5237\u7A97\u53E3\", () => {\n const sent: string[] = [];\n const timers: Array<{ fn: () => void }> = [];\n const agg = createQqEventsAggregator({\n getConfig: () => ALL_ON,\n getOutbound: () => fakeOutbound,\n send: (t) => sent.push(t),\n setTimeoutFn: (fn) => {\n timers.push({ fn });\n return 1 as unknown as ReturnType<typeof setTimeout>;\n },\n clearTimeoutFn: () => {},\n });\n\n agg.ingestOne({ type: \"join\", player: \"Steve\" });\n agg.ingestOne({ type: \"crash\", detail: \"code=1\" });\n assert.equal(sent.length, 2);\n assert.match(sent[0]!, /\u4E0A\u7EBF\uFF1ASteve/);\n assert.match(sent[1]!, /BDS \u610F\u5916\u9000\u51FA \\(code=1\\)/);\n assert.equal(agg.pendingCount(), 0);\n agg.dispose();\n});\n\ntest(\"enabled:false \u5168\u4E22\", () => {\n const sent: string[] = [];\n const agg = createQqEventsAggregator({\n getConfig: () => ({ ...ALL_ON, enabled: false }),\n getOutbound: () => fakeOutbound,\n send: (t) => sent.push(t),\n });\n const r = agg.ingestMany([\n { type: \"join\", player: \"A\" },\n { type: \"crash\", detail: \"x\" },\n ]);\n assert.equal(r.accepted, 0);\n assert.equal(r.rejected, 2);\n assert.equal(sent.length, 0);\n agg.dispose();\n});\n\ntest(\"\u5355\u7C7B\u5173\u95ED\uFF1Adeath \u5173\u5219\u4E22\u5F03\", () => {\n const sent: string[] = [];\n const timers: Array<{ fn: () => void }> = [];\n const agg = createQqEventsAggregator({\n getConfig: () => ({ ...ALL_ON, death: false }),\n getOutbound: () => fakeOutbound,\n send: (t) => sent.push(t),\n setTimeoutFn: (fn) => {\n timers.push({ fn });\n return 1 as unknown as ReturnType<typeof setTimeout>;\n },\n clearTimeoutFn: () => {},\n });\n assert.equal(agg.ingestOne({ type: \"death\", player: \"A\", cause: \"fall\" }).accepted, false);\n assert.equal(agg.ingestOne({ type: \"join\", player: \"A\" }).accepted, true);\n timers[0]!.fn();\n assert.equal(sent.length, 1);\n assert.doesNotMatch(sent[0]!, /\u6B7B\u4EA1/);\n agg.dispose();\n});\n\ntest(\"\u7A97\u53E3\u6EE1 MAX_WINDOW_EVENTS \u63D0\u524D flush\", () => {\n const sent: string[] = [];\n const agg = createQqEventsAggregator({\n getConfig: () => ({ ...ALL_ON, window_sec: 600 }),\n getOutbound: () => fakeOutbound,\n send: (t) => sent.push(t),\n setTimeoutFn: () => 1 as unknown as ReturnType<typeof setTimeout>,\n clearTimeoutFn: () => {},\n });\n for (let i = 0; i < MAX_WINDOW_EVENTS; i++) {\n agg.ingestOne({ type: \"join\", player: `P${i}` });\n }\n assert.equal(sent.length, 1);\n assert.equal(agg.pendingCount(), 0);\n agg.dispose();\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qq-join.test.d.ts","sourceRoot":"","sources":["../../src/domain/qq-join.test.ts"],"names":[],"mappings":""}
|