@zhin.js/plugin-idiom-chain 1.1.14 → 1.1.16
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/commands/chain/[[action]]/index.js +4 -4
- package/commands/chain/[[action]]/index.ts +4 -4
- package/lib/chain-command.d.ts +1 -0
- package/lib/chain-command.js +27 -20
- package/lib/chain-command.js.map +1 -1
- package/lib/game-flow.js +52 -23
- package/lib/game-flow.js.map +1 -1
- package/lib/idiom-provider.d.ts +4 -3
- package/lib/idiom-provider.js +23 -11
- package/lib/idiom-provider.js.map +1 -1
- package/lib/models.d.ts +4 -1
- package/lib/models.js +2 -1
- package/lib/models.js.map +1 -1
- package/lib/session-service.js +1 -0
- package/lib/session-service.js.map +1 -1
- package/lib/view.js +28 -13
- package/lib/view.js.map +1 -1
- package/middlewares/chain-alias/index.js +5 -3
- package/middlewares/chain-alias/index.ts +5 -3
- package/package.json +7 -7
- package/plugin.js +4 -0
- package/src/chain-command.ts +29 -24
- package/src/game-flow.ts +53 -23
- package/src/idiom-provider.ts +25 -11
- package/src/models.ts +4 -2
- package/src/session-service.ts +1 -0
- package/src/view.ts +29 -13
package/lib/view.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
1
2
|
import { buildChoiceKeyboard } from '@zhin.js/game-kit';
|
|
2
3
|
import { CHAIN_PREFIX, getGloss, idiomCount, modeLabel, promptLine } from './engine.js';
|
|
3
4
|
const MAX_WRONG = 3;
|
|
@@ -8,17 +9,17 @@ export function buildChainView(session, eventLines = [], channelType) {
|
|
|
8
9
|
const terminal = session.status !== 'active';
|
|
9
10
|
const mode = sessionMode(session);
|
|
10
11
|
const lines = [
|
|
11
|
-
|
|
12
|
+
messages(session.locale ?? undefined, 'text1', { p0: modeLabel(mode, session.locale ?? undefined) }),
|
|
12
13
|
'',
|
|
13
|
-
|
|
14
|
+
messages(session.locale ?? undefined, 'text2', { p0: idiomCount(), p1: session.player_score, p2: session.bot_score, p3: session.streak, p4: session.best_streak }),
|
|
14
15
|
];
|
|
15
16
|
if (session.last_idiom) {
|
|
16
17
|
const gloss = getGloss(session.last_idiom);
|
|
17
|
-
lines.push('',
|
|
18
|
-
lines.push(promptLine(session.last_idiom, mode));
|
|
18
|
+
lines.push('', messages(session.locale ?? undefined, 'text3', { p0: session.last_idiom }) + (gloss ? `(${gloss})` : ''));
|
|
19
|
+
lines.push(promptLine(session.last_idiom, mode, session.locale ?? undefined));
|
|
19
20
|
}
|
|
20
21
|
if (session.wrong_count > 0 && session.status === 'active') {
|
|
21
|
-
lines.push(
|
|
22
|
+
lines.push(messages(session.locale ?? undefined, 'text4', { p0: session.wrong_count, p1: MAX_WRONG }));
|
|
22
23
|
}
|
|
23
24
|
if (eventLines.length) {
|
|
24
25
|
lines.push('', ...eventLines);
|
|
@@ -26,19 +27,19 @@ export function buildChainView(session, eventLines = [], channelType) {
|
|
|
26
27
|
if (terminal) {
|
|
27
28
|
lines.push('');
|
|
28
29
|
if (session.status === 'won')
|
|
29
|
-
lines.push(
|
|
30
|
+
lines.push(messages(session.locale ?? undefined, 'text5'));
|
|
30
31
|
else if (session.status === 'lost')
|
|
31
|
-
lines.push(
|
|
32
|
+
lines.push(messages(session.locale ?? undefined, 'text6'));
|
|
32
33
|
}
|
|
33
34
|
else {
|
|
34
|
-
lines.push('', '
|
|
35
|
+
lines.push('', messages(session.locale ?? undefined, 'text7'));
|
|
35
36
|
}
|
|
36
37
|
const choices = terminal
|
|
37
|
-
? [{ id: 'restart', label:
|
|
38
|
+
? [{ id: 'restart', label: messages(session.locale ?? undefined, 'text8'), style: 'primary', keepEnabledWhenTerminal: true }]
|
|
38
39
|
: [
|
|
39
|
-
{ id: 'hint', label:
|
|
40
|
-
{ id: 'skip', label:
|
|
41
|
-
{ id: 'quit', label:
|
|
40
|
+
{ id: 'hint', label: messages(session.locale ?? undefined, 'text9'), style: 'secondary' },
|
|
41
|
+
{ id: 'skip', label: messages(session.locale ?? undefined, 'text10'), style: 'secondary' },
|
|
42
|
+
{ id: 'quit', label: messages(session.locale ?? undefined, 'text11'), style: 'danger' },
|
|
42
43
|
];
|
|
43
44
|
return buildChoiceKeyboard({
|
|
44
45
|
gamePrefix: CHAIN_PREFIX,
|
|
@@ -47,10 +48,24 @@ export function buildChainView(session, eventLines = [], channelType) {
|
|
|
47
48
|
choices,
|
|
48
49
|
terminal,
|
|
49
50
|
buttonsPerRow: 3,
|
|
50
|
-
fallbackHint:
|
|
51
|
+
fallbackHint: messages(session.locale ?? undefined, 'text12'),
|
|
51
52
|
interactionProfile: terminal ? 'terminal' : 'gameplay',
|
|
52
53
|
channelType,
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
export { MAX_WRONG };
|
|
57
|
+
const messages = createMessages({
|
|
58
|
+
text1: { 'zh-CN': "📜 **成语接龙** · {p0}", en: "📜 **Chinese Idiom Chain** · {p0}" },
|
|
59
|
+
text2: { 'zh-CN': "词库 **{p0}** 条 · 局分 {p1}:{p2} · 连击 **{p3}**(最佳 {p4})", en: "Dictionary **{p0}** entries · Score {p1}:{p2} · Streak **{p3}** (best {p4})" },
|
|
60
|
+
text3: { 'zh-CN': "上一句:**{p0}**", en: "Last idiom: **{p0}**" },
|
|
61
|
+
text4: { 'zh-CN': "⚠️ 失误 {p0}/{p1}", en: "⚠️ Mistakes: {p0}/{p1}" },
|
|
62
|
+
text5: { 'zh-CN': "🏆 **你赢了本局!** 机器人接不上啦。", en: "🏆 **You won!** The bot has no next idiom." },
|
|
63
|
+
text6: { 'zh-CN': "💀 **机器人获胜。** 点击「再来一局」重新开始。", en: "💀 **The bot won.** Choose “Play again” to start again." },
|
|
64
|
+
text7: { 'zh-CN': "直接回复四字成语,或点下方按钮:", en: "Reply with a four-character Chinese idiom, or choose an option:" },
|
|
65
|
+
text8: { 'zh-CN': "🔄 再来一局", en: "🔄 Play again" },
|
|
66
|
+
text9: { 'zh-CN': "💡 提示", en: "💡 Hint" },
|
|
67
|
+
text10: { 'zh-CN': "⏭️ 跳过", en: "⏭️ Skip" },
|
|
68
|
+
text11: { 'zh-CN': "🏳️ 认输", en: "🏳️ Concede" },
|
|
69
|
+
text12: { 'zh-CN': "回复成语,或 1提示 2跳过 3认输", en: "Reply with an idiom, or 1 Hint · 2 Skip · 3 Concede" },
|
|
70
|
+
});
|
|
56
71
|
//# sourceMappingURL=view.js.map
|
package/lib/view.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAExF,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,SAAS,WAAW,CAAC,OAAwB;IAC3C,OAAO,OAAO,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAAwB,EACxB,aAAuB,EAAE,EACzB,WAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;IAC7C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG;QACZ,
|
|
1
|
+
{"version":3,"file":"view.js","sourceRoot":"","sources":["../src/view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAExF,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,SAAS,WAAW,CAAC,OAAwB;IAC3C,OAAO,OAAO,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,OAAwB,EACxB,aAAuB,EAAE,EACzB,WAAoB;IAEpB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC;IAC7C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG;QACZ,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,EAAE,CAAC;QACpG,EAAE;QACF,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,YAAY,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC;KACnK,CAAC;IAEF,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3C,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzH,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,SAAS,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACzG,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,UAAU,CAAC,CAAC;IAChC,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;aACpF,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IACjG,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ;QACtB,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,SAAkB,EAAE,uBAAuB,EAAE,IAAI,EAAE,CAAC;QACtI,CAAC,CAAC;YACE,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,WAAoB,EAAE;YAClG,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,WAAoB,EAAE;YACnG,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,QAAiB,EAAE;SACjG,CAAC;IAEN,OAAO,mBAAmB,CAAC;QACzB,UAAU,EAAE,YAAY;QACxB,SAAS,EAAE,OAAO,CAAC,EAAE;QACrB,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3B,OAAO;QACP,QAAQ;QACR,aAAa,EAAE,CAAC;QAChB,YAAY,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,QAAQ,CAAC;QAC7D,kBAAkB,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;QACtD,WAAW;KACZ,CAAC,CAAC;AACL,CAAC;AAED,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,KAAK,EAAE,EAAE,OAAO,EAAE,oBAAoB,EAAE,EAAE,EAAE,mCAAmC,EAAE;IACjF,KAAK,EAAE,EAAE,OAAO,EAAE,qDAAqD,EAAE,EAAE,EAAE,6EAA6E,EAAE;IAC5J,KAAK,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,sBAAsB,EAAE;IAC9D,KAAK,EAAE,EAAE,OAAO,EAAE,iBAAiB,EAAE,EAAE,EAAE,wBAAwB,EAAE;IACnE,KAAK,EAAE,EAAE,OAAO,EAAE,wBAAwB,EAAE,EAAE,EAAE,4CAA4C,EAAE;IAC9F,KAAK,EAAE,EAAE,OAAO,EAAE,6BAA6B,EAAE,EAAE,EAAE,yDAAyD,EAAE;IAChH,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,EAAE,iEAAiE,EAAE;IAC7G,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,eAAe,EAAE;IAClD,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE;IAC1C,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE;IAC3C,MAAM,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE;IAChD,MAAM,EAAE,EAAE,OAAO,EAAE,oBAAoB,EAAE,EAAE,EAAE,qDAAqD,EAAE;CACrG,CAAC,CAAC"}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { defineGameCommandAliasMiddleware, isChainAction, messageFromCommandInput, normalizeChainAction, } from '@zhin.js/game-kit';
|
|
3
|
-
import {
|
|
3
|
+
import { getChainHelp, runChainCommand } from "../../lib/chain-command.js";
|
|
4
4
|
import { resolveGameServices } from "../../lib/runtime-store.js";
|
|
5
5
|
export default defineGameCommandAliasMiddleware({
|
|
6
6
|
aliases: ['接龙', 'chain'],
|
|
7
7
|
async run(action, input, context) {
|
|
8
|
+
const message = messageFromCommandInput(input, {
|
|
9
|
+
formatCommand: (path) => context.input.formatCommand?.(path, context.owner.id),
|
|
10
|
+
});
|
|
8
11
|
const normalized = normalizeChainAction(String(action ?? ''));
|
|
9
12
|
if (normalized === 'help')
|
|
10
|
-
return
|
|
13
|
+
return getChainHelp(message);
|
|
11
14
|
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
12
15
|
if (!isChainAction(normalized))
|
|
13
16
|
return null;
|
|
14
17
|
const services = resolveGameServices(context);
|
|
15
|
-
const message = messageFromCommandInput(input);
|
|
16
18
|
return runChainCommand(services, message, normalized);
|
|
17
19
|
},
|
|
18
20
|
});
|
|
@@ -4,18 +4,20 @@ import {
|
|
|
4
4
|
messageFromCommandInput,
|
|
5
5
|
normalizeChainAction,
|
|
6
6
|
} from '@zhin.js/game-kit';
|
|
7
|
-
import {
|
|
7
|
+
import { getChainHelp, runChainCommand } from '../../src/chain-command.js';
|
|
8
8
|
import { resolveGameServices } from '../../src/runtime-store.js';
|
|
9
9
|
|
|
10
10
|
export default defineGameCommandAliasMiddleware({
|
|
11
11
|
aliases: ['接龙', 'chain'],
|
|
12
12
|
async run(action, input, context) {
|
|
13
|
+
const message = messageFromCommandInput(input, {
|
|
14
|
+
formatCommand: (path) => context.input.formatCommand?.(path, context.owner.id),
|
|
15
|
+
});
|
|
13
16
|
const normalized = normalizeChainAction(String(action ?? ''));
|
|
14
|
-
if (normalized === 'help') return
|
|
17
|
+
if (normalized === 'help') return getChainHelp(message);
|
|
15
18
|
// action 无法识别:放行给后续中间件,避免劫持普通聊天
|
|
16
19
|
if (!isChainAction(normalized)) return null;
|
|
17
20
|
const services = resolveGameServices(context);
|
|
18
|
-
const message = messageFromCommandInput(input);
|
|
19
21
|
return runChainCommand(services, message, normalized);
|
|
20
22
|
},
|
|
21
23
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-idiom-chain",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.16",
|
|
4
4
|
"description": "Zhin.js 成语接龙 — 四字成语文字接龙 (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"chinese-idiom-chengyu": "^1.1.0",
|
|
27
|
-
"@zhin.js/core": "1.1.
|
|
28
|
-
"@zhin.js/game-kit": "1.1.
|
|
27
|
+
"@zhin.js/core": "1.1.51",
|
|
28
|
+
"@zhin.js/game-kit": "1.1.16"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"typescript": "^6.0.3",
|
|
32
32
|
"vitest": "^4.1.10",
|
|
33
|
-
"zhin.js": "1.1.
|
|
33
|
+
"zhin.js": "1.1.16"
|
|
34
34
|
},
|
|
35
35
|
"repository": {
|
|
36
36
|
"type": "git",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"plugins": []
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@zhin.js/command": "^1.1.
|
|
71
|
-
"@zhin.js/middleware": "^1.1.
|
|
72
|
-
"zhin.js": "^1.1.
|
|
70
|
+
"@zhin.js/command": "^1.1.11",
|
|
71
|
+
"@zhin.js/middleware": "^1.1.10",
|
|
72
|
+
"zhin.js": "^1.1.16"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|
|
75
75
|
"@zhin.js/command": {
|
package/plugin.js
CHANGED
|
@@ -15,6 +15,10 @@ export default defineGamePlugin({
|
|
|
15
15
|
displayName: 'Idiom Chain',
|
|
16
16
|
},
|
|
17
17
|
game: {
|
|
18
|
+
commandPath: 'chain',
|
|
19
|
+
titleI18n: { en: 'Chinese Idiom Chain' },
|
|
20
|
+
descriptionI18n: { en: 'Connect Chinese idioms by sound or character' },
|
|
21
|
+
quickStartI18n: { en: 'start_pinyin' },
|
|
18
22
|
id: 'chain',
|
|
19
23
|
title: '成语接龙',
|
|
20
24
|
icon: '📜',
|
package/src/chain-command.ts
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type GameMessageLike,
|
|
4
|
-
type GameReply,
|
|
5
|
-
} from '@zhin.js/game-kit';
|
|
1
|
+
import { formatGameHelp, gameCommandFeedback, gameCommandHint, channelKey, type GameMessageLike, type GameReply } from '@zhin.js/game-kit';
|
|
2
|
+
import { createMessages } from 'zhin.js/i18n';
|
|
6
3
|
import { continueGame, startGame } from './game-flow.js';
|
|
7
4
|
import { idiomCount, modeLabel, promptLine, type MatchMode } from './engine.js';
|
|
8
5
|
|
|
9
6
|
import type { SessionService } from './session-service.js';
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
|
|
9
|
+
const messages = createMessages({
|
|
10
|
+
dictionary: { 'zh-CN': '开源词库 {count} 条 · 机器人先出第一句', en: 'Dictionary: {count} entries · The bot plays first' },
|
|
11
|
+
pinyin: { 'zh-CN': '{command} — 同音接龙', en: '{command} — Match the pronunciation' },
|
|
12
|
+
char: { 'zh-CN': '{command} — 严格同字接龙', en: '{command} — Match the character' },
|
|
13
|
+
instructions: { 'zh-CN': '进行中直接回复四字成语。按钮:提示 · 跳过 · 认输(失误 3 次判负)。', en: 'Reply with four-character Chinese idioms. Options: Hint · Skip · Concede. Three mistakes lose the game.' },
|
|
14
|
+
active: { 'zh-CN': '进行中:{player} · {mode} · 局分 {playerScore}:{botScore} · 连击 {streak}', en: 'Playing: {player} · {mode} · Score {playerScore}:{botScore} · Streak {streak}' },
|
|
15
|
+
quit: { 'zh-CN': '已放弃成语接龙。', en: 'You quit the idiom chain.' },
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export function getChainHelp(message: Pick<GameMessageLike, 'locale' | 'formatCommand'> = {}): string {
|
|
19
|
+
const locale = message.locale ?? 'zh-CN';
|
|
20
|
+
const command = message.formatCommand?.('chain');
|
|
21
|
+
return [formatGameHelp(message, { 'zh-CN': '📜 成语接龙(文字接龙 · 四字成语)', en: '📜 Chinese Idiom Chain' }, 'chain', ['continue', 'quit']),
|
|
22
|
+
messages(locale, 'dictionary', { count: idiomCount() }),
|
|
23
|
+
...(command ? [messages(locale, 'pinyin', { command: command + ' start_pinyin' }), messages(locale, 'char', { command: command + ' start_char' })] : []),
|
|
24
|
+
messages(locale, 'instructions'),
|
|
25
|
+
].join('\n');
|
|
26
|
+
}
|
|
27
|
+
export const CHAIN_HELP = getChainHelp();
|
|
23
28
|
|
|
24
29
|
export async function runChainCommand(
|
|
25
30
|
services: SessionService,
|
|
@@ -31,15 +36,15 @@ export async function runChainCommand(
|
|
|
31
36
|
|
|
32
37
|
if (!action || action === 'help') {
|
|
33
38
|
const active = await services.getActiveByChannel(ch);
|
|
34
|
-
const lines = [
|
|
39
|
+
const lines = [getChainHelp(message), ''];
|
|
35
40
|
if (active) {
|
|
36
41
|
const mode: MatchMode = active.match_mode === 'char' ? 'char' : 'pinyin';
|
|
37
42
|
lines.push(
|
|
38
|
-
|
|
43
|
+
messages(active.locale ?? undefined, 'active', { player: active.player_name, mode: modeLabel(mode, active.locale ?? undefined), playerScore: active.player_score, botScore: active.bot_score, streak: active.streak }),
|
|
39
44
|
);
|
|
40
|
-
if (active.last_idiom) lines.push(promptLine(active.last_idiom, mode));
|
|
45
|
+
if (active.last_idiom) lines.push(promptLine(active.last_idiom, mode, active.locale ?? undefined));
|
|
41
46
|
} else {
|
|
42
|
-
lines.push('
|
|
47
|
+
lines.push(gameCommandFeedback(message, 'noGame'), gameCommandHint(message, 'chain', 'start_pinyin'));
|
|
43
48
|
}
|
|
44
49
|
return lines.join('\n');
|
|
45
50
|
}
|
|
@@ -50,10 +55,10 @@ export async function runChainCommand(
|
|
|
50
55
|
|
|
51
56
|
if (action === 'quit') {
|
|
52
57
|
const row = await services.getActiveForUser(ch, userId);
|
|
53
|
-
if (!row) return '
|
|
58
|
+
if (!row) return gameCommandFeedback(message, 'noGame');
|
|
54
59
|
await services.updateSession(row.id, { status: 'aborted' });
|
|
55
|
-
return '
|
|
60
|
+
return messages(row.locale ?? undefined, 'quit');
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
return
|
|
63
|
+
return gameCommandFeedback(message, 'unknown', action) + '\n\n' + getChainHelp(message);
|
|
59
64
|
}
|
package/src/game-flow.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMessages, resolveLocale } from 'zhin.js/i18n';
|
|
1
2
|
import type { GameMessageLike, GameReply } from '@zhin.js/game-kit';
|
|
2
3
|
import {
|
|
3
4
|
CHAIN_PREFIX,
|
|
@@ -35,6 +36,7 @@ async function botTurn(
|
|
|
35
36
|
session: ChainSessionRow,
|
|
36
37
|
used: Set<string>,
|
|
37
38
|
): Promise<{ session: ChainSessionRow; lines: string[]; playerWon: boolean }> {
|
|
39
|
+
const locale = resolveLocale(session.locale);
|
|
38
40
|
const mode = sessionMode(session);
|
|
39
41
|
const bot = pickBotIdiom(session.last_idiom, mode, used);
|
|
40
42
|
if (!bot) {
|
|
@@ -43,7 +45,7 @@ async function botTurn(
|
|
|
43
45
|
player_score: session.player_score + 1,
|
|
44
46
|
});
|
|
45
47
|
const updated = (await services.getById(session.id))!;
|
|
46
|
-
return { session: updated, lines: [
|
|
48
|
+
return { session: updated, lines: [messages(locale, 'text1')], playerWon: true };
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
used.add(bot.text);
|
|
@@ -57,8 +59,8 @@ async function botTurn(
|
|
|
57
59
|
});
|
|
58
60
|
const updated = (await services.getById(session.id))!;
|
|
59
61
|
const lines = [
|
|
60
|
-
|
|
61
|
-
promptLine(bot.text, mode),
|
|
62
|
+
messages(locale, 'text2', { p0: bot.text }) + (gloss ? `(${gloss})` : ''),
|
|
63
|
+
promptLine(bot.text, mode, locale),
|
|
62
64
|
];
|
|
63
65
|
return { session: updated, lines, playerWon: false };
|
|
64
66
|
}
|
|
@@ -70,11 +72,12 @@ export async function startGame(
|
|
|
70
72
|
): Promise<GameReply | undefined> {
|
|
71
73
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
72
74
|
const active = await services.getActiveByChannel(ch);
|
|
75
|
+
const locale = resolveLocale(active ? active.locale : message.locale);
|
|
73
76
|
if (active) {
|
|
74
77
|
if (active.player_id === message.$sender.id) {
|
|
75
|
-
return
|
|
78
|
+
return messages(locale, 'text3');
|
|
76
79
|
}
|
|
77
|
-
return
|
|
80
|
+
return messages(locale, 'text4', { p0: active.player_name });
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
const used = new Set<string>();
|
|
@@ -89,8 +92,8 @@ export async function startGame(
|
|
|
89
92
|
|
|
90
93
|
const gloss = starter.gloss ?? getGloss(starter.text);
|
|
91
94
|
return sendOrEditView(services, message, session, [
|
|
92
|
-
|
|
93
|
-
promptLine(starter.text, matchMode),
|
|
95
|
+
messages(locale, 'text5', { p0: modeLabel(matchMode, locale), p1: starter.text }) + (gloss ? `(${gloss})` : ''),
|
|
96
|
+
promptLine(starter.text, matchMode, locale),
|
|
94
97
|
]);
|
|
95
98
|
}
|
|
96
99
|
|
|
@@ -102,7 +105,8 @@ export async function continueGame(
|
|
|
102
105
|
`${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`,
|
|
103
106
|
message.$sender.id,
|
|
104
107
|
);
|
|
105
|
-
|
|
108
|
+
const locale = resolveLocale(session ? session.locale : message.locale);
|
|
109
|
+
if (!session) return messages(locale, 'text6');
|
|
106
110
|
return sendOrEditView(services, message, session);
|
|
107
111
|
}
|
|
108
112
|
|
|
@@ -113,6 +117,7 @@ export async function processIdiomText(
|
|
|
113
117
|
): Promise<GameReply | null> {
|
|
114
118
|
const ch = `${message.$adapter}-${message.$endpoint}-${message.$channel.type}:${message.$channel.id}`;
|
|
115
119
|
const session = await services.getActiveForUser(ch, message.$sender.id);
|
|
120
|
+
const locale = resolveLocale(session ? session.locale : message.locale);
|
|
116
121
|
if (!session || session.status !== 'active') return null;
|
|
117
122
|
|
|
118
123
|
const mode = sessionMode(session);
|
|
@@ -120,9 +125,9 @@ export async function processIdiomText(
|
|
|
120
125
|
const idiom = normalizeInput(raw);
|
|
121
126
|
// 格式不合规(非四字)只提示、不扣失误:避免闲聊(「好的」「谢谢」)被累计判负
|
|
122
127
|
if (!/^[\u4e00-\u9fff]{4}$/.test(idiom)) {
|
|
123
|
-
return '
|
|
128
|
+
return messages(locale, 'text7');
|
|
124
129
|
}
|
|
125
|
-
const check = validatePlayerIdiom(idiom, session.last_idiom, mode, used);
|
|
130
|
+
const check = validatePlayerIdiom(idiom, session.last_idiom, mode, used, locale);
|
|
126
131
|
if (!check.ok) {
|
|
127
132
|
const wrong = session.wrong_count + 1;
|
|
128
133
|
if (wrong >= MAX_WRONG) {
|
|
@@ -135,7 +140,7 @@ export async function processIdiomText(
|
|
|
135
140
|
const updated = (await services.getById(session.id))!;
|
|
136
141
|
return (await sendOrEditView(services, message, updated, [
|
|
137
142
|
check.reason!,
|
|
138
|
-
'
|
|
143
|
+
messages(locale, 'text8'),
|
|
139
144
|
])) ?? null;
|
|
140
145
|
}
|
|
141
146
|
await services.updateSession(session.id, { wrong_count: wrong, streak: 0 });
|
|
@@ -159,7 +164,7 @@ export async function processIdiomText(
|
|
|
159
164
|
});
|
|
160
165
|
|
|
161
166
|
const current = (await services.getById(session.id))!;
|
|
162
|
-
const userLine =
|
|
167
|
+
const userLine = messages(locale, 'text9', { p0: idiom }) + (gloss ? `(${gloss})` : '');
|
|
163
168
|
|
|
164
169
|
const botResult = await botTurn(services, current, used);
|
|
165
170
|
return (await sendOrEditView(services, message, botResult.session, [
|
|
@@ -175,16 +180,19 @@ export async function handleChoice(
|
|
|
175
180
|
choiceId: string,
|
|
176
181
|
): Promise<GameReply | null> {
|
|
177
182
|
const session = await services.getById(sessionId);
|
|
178
|
-
|
|
179
|
-
if (session
|
|
183
|
+
const locale = resolveLocale(session ? session.locale : message.locale);
|
|
184
|
+
if (!session) return messages(locale, 'text10');
|
|
185
|
+
if (session.player_id !== message.$sender.id) return messages(locale, 'text11');
|
|
180
186
|
|
|
181
187
|
const mode = sessionMode(session);
|
|
182
188
|
|
|
183
189
|
if (choiceId === 'restart') {
|
|
190
|
+
const locale = resolveLocale(message.locale);
|
|
184
191
|
const used = new Set<string>();
|
|
185
192
|
const starter = pickStarterIdiom(used);
|
|
186
193
|
used.add(starter.text);
|
|
187
194
|
await services.updateSession(session.id, {
|
|
195
|
+
locale,
|
|
188
196
|
last_idiom: starter.text,
|
|
189
197
|
next_char: lastChar(starter.text),
|
|
190
198
|
used_idioms: JSON.stringify([...used]),
|
|
@@ -196,13 +204,13 @@ export async function handleChoice(
|
|
|
196
204
|
const updated = (await services.getById(session.id))!;
|
|
197
205
|
const gloss = starter.gloss ?? getGloss(starter.text);
|
|
198
206
|
return (await sendOrEditView(services, message, updated, [
|
|
199
|
-
|
|
200
|
-
promptLine(starter.text, mode),
|
|
207
|
+
messages(locale, 'text12', { p0: starter.text }) + (gloss ? `(${gloss})` : ''),
|
|
208
|
+
promptLine(starter.text, mode, locale),
|
|
201
209
|
])) ?? null;
|
|
202
210
|
}
|
|
203
211
|
|
|
204
212
|
if (session.status !== 'active') {
|
|
205
|
-
return '
|
|
213
|
+
return messages(locale, 'text13');
|
|
206
214
|
}
|
|
207
215
|
|
|
208
216
|
const used = parseUsed(session.used_idioms);
|
|
@@ -214,15 +222,15 @@ export async function handleChoice(
|
|
|
214
222
|
await services.updateSession(session.id, { status: 'won', player_score: session.player_score + 1 });
|
|
215
223
|
const updated = (await services.getById(session.id))!;
|
|
216
224
|
return (await sendOrEditView(services, message, updated, [
|
|
217
|
-
|
|
225
|
+
messages(locale, 'text14'),
|
|
218
226
|
])) ?? null;
|
|
219
227
|
}
|
|
220
228
|
const gloss = getGloss(hint);
|
|
221
229
|
await services.updateSession(session.id, { hints_used: session.hints_used + 1, streak: 0 });
|
|
222
230
|
const updated = (await services.getById(session.id))!;
|
|
223
231
|
return (await sendOrEditView(services, message, updated, [
|
|
224
|
-
|
|
225
|
-
'
|
|
232
|
+
messages(locale, 'text15', { p0: hint }) + (gloss ? `(${gloss})` : ''),
|
|
233
|
+
messages(locale, 'text16'),
|
|
226
234
|
])) ?? null;
|
|
227
235
|
}
|
|
228
236
|
|
|
@@ -234,17 +242,39 @@ export async function handleChoice(
|
|
|
234
242
|
});
|
|
235
243
|
const updated = (await services.getById(session.id))!;
|
|
236
244
|
return (await sendOrEditView(services, message, updated, [
|
|
237
|
-
|
|
245
|
+
messages(locale, 'text17'),
|
|
238
246
|
])) ?? null;
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
if (choiceId === 'quit') {
|
|
242
250
|
await services.updateSession(session.id, { status: 'lost', bot_score: session.bot_score + 1 });
|
|
243
251
|
const updated = (await services.getById(session.id))!;
|
|
244
|
-
return (await sendOrEditView(services, message, updated, [
|
|
252
|
+
return (await sendOrEditView(services, message, updated, [messages(locale, 'text18')])) ?? null;
|
|
245
253
|
}
|
|
246
254
|
|
|
247
|
-
return '
|
|
255
|
+
return messages(locale, 'text19');
|
|
248
256
|
}
|
|
249
257
|
|
|
250
258
|
export { CHAIN_PREFIX };
|
|
259
|
+
|
|
260
|
+
const messages = createMessages({
|
|
261
|
+
text1: { 'zh-CN': "🎉 机器人接不上,你赢本局!", en: "🎉 The bot has no next idiom. You win!" },
|
|
262
|
+
text2: { 'zh-CN': "🤖 机器人:{p0}", en: "🤖 Bot: {p0}" },
|
|
263
|
+
text3: { 'zh-CN': "你已有进行中的接龙,请使用当前对局的操作入口继续。", en: "You already have an active idiom chain. Continue using its controls." },
|
|
264
|
+
text4: { 'zh-CN': "本频道 {p0} 正在成语接龙。", en: "{p0} is playing an idiom chain in this channel." },
|
|
265
|
+
text5: { 'zh-CN': "🎬 {p0}开局!我先出:**{p1}**", en: "🎬 Starting {p0}! My first idiom: **{p1}**" },
|
|
266
|
+
text6: { 'zh-CN': "你没有进行中的接龙。", en: "You have no active idiom chain." },
|
|
267
|
+
text7: { 'zh-CN': "接龙请输入**四字成语**(这条不算失误)。", en: "Enter a **four-character Chinese idiom** (this does not count as a mistake)." },
|
|
268
|
+
text8: { 'zh-CN': "失误过多,本局结束。", en: "Too many mistakes. This game has ended." },
|
|
269
|
+
text9: { 'zh-CN': "✅ 你:{p0}", en: "✅ You: {p0}" },
|
|
270
|
+
text10: { 'zh-CN': "对局不存在。", en: "This game no longer exists." },
|
|
271
|
+
text11: { 'zh-CN': "这是别人的接龙。", en: "This idiom chain belongs to another player." },
|
|
272
|
+
text12: { 'zh-CN': "🎬 新一局!我先出:**{p0}**", en: "🎬 New game! My first idiom: **{p0}**" },
|
|
273
|
+
text13: { 'zh-CN': "本局已结束,请点击再来一局。", en: "This game has ended. Choose Play again." },
|
|
274
|
+
text14: { 'zh-CN': "💡 词库中暂无可用提示,你赢了!", en: "💡 No next idiom is available in the dictionary. You win!" },
|
|
275
|
+
text15: { 'zh-CN': "💡 提示:可试 **{p0}**", en: "💡 Hint: try **{p0}**" },
|
|
276
|
+
text16: { 'zh-CN': "(使用提示会清零连击)", en: "(Using a hint resets your streak)" },
|
|
277
|
+
text17: { 'zh-CN': "⏭️ 你选择跳过,机器人得一分。", en: "⏭️ You skipped. The bot gets a point." },
|
|
278
|
+
text18: { 'zh-CN': "🏳️ 你认输了。", en: "🏳️ You conceded." },
|
|
279
|
+
text19: { 'zh-CN': "未知操作。", en: "Unknown action." },
|
|
280
|
+
});
|
package/src/idiom-provider.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMessages, type Locale } from 'zhin.js/i18n';
|
|
1
2
|
/**
|
|
2
3
|
* 成语词库适配层 — 基于 npm `chinese-idiom-chengyu`(MIT,~3 万成语)
|
|
3
4
|
* @see https://www.npmjs.com/package/chinese-idiom-chengyu
|
|
@@ -74,8 +75,8 @@ export function lastSyllableNoTone(idiom: string): string {
|
|
|
74
75
|
return Util.pinyinToLetters(tail);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
export function modeLabel(mode: MatchMode): string {
|
|
78
|
-
return mode === 'char' ? '
|
|
78
|
+
export function modeLabel(mode: MatchMode, locale?: Locale): string {
|
|
79
|
+
return mode === 'char' ? messages(locale, 'text1') : messages(locale, 'text2');
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
export function getValidNext(
|
|
@@ -129,16 +130,17 @@ export function validatePlayerIdiom(
|
|
|
129
130
|
prevIdiom: string,
|
|
130
131
|
mode: MatchMode,
|
|
131
132
|
used: Set<string>,
|
|
133
|
+
locale?: Locale,
|
|
132
134
|
): ValidateResult {
|
|
133
135
|
const idiom = normalizeInput(text);
|
|
134
136
|
if (!FOUR_CHAR.test(idiom)) {
|
|
135
|
-
return { ok: false, reason: '
|
|
137
|
+
return { ok: false, reason: messages(locale, 'text3') };
|
|
136
138
|
}
|
|
137
139
|
if (!isKnownIdiom(idiom)) {
|
|
138
|
-
return { ok: false, reason: '
|
|
140
|
+
return { ok: false, reason: messages(locale, 'text4') };
|
|
139
141
|
}
|
|
140
142
|
if (used.has(idiom)) {
|
|
141
|
-
return { ok: false, reason: '
|
|
143
|
+
return { ok: false, reason: messages(locale, 'text5') };
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
const valid = getValidNext(prevIdiom, mode, used);
|
|
@@ -146,21 +148,33 @@ export function validatePlayerIdiom(
|
|
|
146
148
|
|
|
147
149
|
const tail = lastChar(prevIdiom);
|
|
148
150
|
if (mode === 'char') {
|
|
149
|
-
return { ok: false, reason:
|
|
151
|
+
return { ok: false, reason: messages(locale, 'text6', { p0: tail }) };
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
const py = lastSyllableNoTone(prevIdiom);
|
|
153
155
|
return {
|
|
154
156
|
ok: false,
|
|
155
|
-
reason:
|
|
157
|
+
reason: messages(locale, 'text7', { p0: py, p1: tail, p2: tail }),
|
|
156
158
|
};
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
export function promptLine(prevIdiom: string, mode: MatchMode): string {
|
|
161
|
+
export function promptLine(prevIdiom: string, mode: MatchMode, locale?: Locale): string {
|
|
160
162
|
const tail = lastChar(prevIdiom);
|
|
161
163
|
if (mode === 'char') {
|
|
162
|
-
return
|
|
164
|
+
return messages(locale, 'text8', { p0: tail });
|
|
163
165
|
}
|
|
164
166
|
const py = lastSyllableNoTone(prevIdiom);
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
+
return messages(locale, 'text9', { p0: py, p1: tail });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const messages = createMessages({
|
|
171
|
+
text1: { 'zh-CN': "同字接龙", en: "Matching character" },
|
|
172
|
+
text2: { 'zh-CN': "同音接龙", en: "Matching pronunciation" },
|
|
173
|
+
text3: { 'zh-CN': "请输入**四字成语**(不要空格或标点)。", en: "Enter a **four-character Chinese idiom** without spaces or punctuation." },
|
|
174
|
+
text4: { 'zh-CN': "开源词库中未收录该成语,请换一个常见四字成语。", en: "This idiom is not in the dictionary. Try a common four-character idiom." },
|
|
175
|
+
text5: { 'zh-CN': "这个成语本局已经用过了。", en: "This idiom has already been used in this game." },
|
|
176
|
+
text6: { 'zh-CN': "必须以「**{p0}**」开头(上一句尾字)。", en: "The idiom must start with **{p0}**, the last character of the previous idiom." },
|
|
177
|
+
text7: { 'zh-CN': "首字需**同音**({p0}),如「{p1}」音;也可同字「{p2}」开头。", en: "The first character must have the **same pronunciation** ({p0}) as “{p1}”, or be the character “{p2}”." },
|
|
178
|
+
text8: { 'zh-CN': "请接「**{p0}**」字开头的四字成语", en: "Enter a four-character idiom starting with **{p0}**" },
|
|
179
|
+
text9: { 'zh-CN': "请接**同音**「{p0}」(尾字「{p1}」)开头的四字成语", en: "Enter a four-character idiom whose first character sounds like **{p0}** (last character: “{p1}”)" },
|
|
180
|
+
});
|
package/src/models.ts
CHANGED
|
@@ -7,6 +7,7 @@ declare module '@zhin.js/core' {
|
|
|
7
7
|
interface Models {
|
|
8
8
|
idiom_chain_sessions: {
|
|
9
9
|
id: string;
|
|
10
|
+
locale?: 'zh-CN' | 'en' | null;
|
|
10
11
|
adapter: string;
|
|
11
12
|
endpoint: string;
|
|
12
13
|
channel_type: string;
|
|
@@ -36,10 +37,11 @@ export type ChainSessionRow = Models['idiom_chain_sessions'];
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
export function defineHostTables(
|
|
39
|
-
db: { define: (name: string, definition: Record<string, unknown
|
|
40
|
+
db: { define: (name: string, definition: Record<string, unknown>, options?: { readonly addColumns?: readonly string[] }) => void },
|
|
40
41
|
): void {
|
|
41
42
|
db.define('idiom_chain_sessions', {
|
|
42
43
|
id: { type: 'text', primary: true },
|
|
44
|
+
locale: { type: 'text', nullable: true },
|
|
43
45
|
adapter: { type: 'text', nullable: false },
|
|
44
46
|
endpoint: { type: 'text', nullable: false },
|
|
45
47
|
channel_type: { type: 'text', nullable: false },
|
|
@@ -61,5 +63,5 @@ export function defineHostTables(
|
|
|
61
63
|
status: { type: 'text', default: 'active' },
|
|
62
64
|
updated_at: { type: 'bigint', default: 0 },
|
|
63
65
|
created_at: { type: 'bigint', default: 0 },
|
|
64
|
-
});
|
|
66
|
+
}, { addColumns: ['locale'] });
|
|
65
67
|
}
|
package/src/session-service.ts
CHANGED
|
@@ -42,6 +42,7 @@ export class SessionService extends BaseSessionService<ChainSessionRow> {
|
|
|
42
42
|
const now = Date.now();
|
|
43
43
|
const row: ChainSessionRow = {
|
|
44
44
|
id: generateSessionId(),
|
|
45
|
+
locale: message.locale ?? 'zh-CN',
|
|
45
46
|
adapter: String(message.$adapter),
|
|
46
47
|
endpoint: message.$endpoint,
|
|
47
48
|
channel_type: message.$channel.type,
|