koishi-plugin-prism 0.1.15 → 0.1.18

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/README.md CHANGED
@@ -32,6 +32,20 @@
32
32
  | `enableStaffCommands` | `boolean` | `false` | 是否开启管理员快捷指令。 |
33
33
  | `staffUserIds` | `string[]` | `[]` | 允许执行管理员快捷指令的平台用户 ID(如 QQ 号)白名单。空列表不授予目标用户操作权限。 |
34
34
  | `logoutNotifyUserIds` | `string[]` | `[]` | 结账成功后额外私聊完整账单的平台用户 ID。通知收件人为该列表与 `staffUserIds` 的去重并集。 |
35
+ | `mahjongTableConfigs` | `object[]` | `[]` | 推荐的结构化麻将桌列表。每项填写显示名称、命令别名列表与计费方案 ID 列表。显示名称同时作为内部桌位锚点与 session 标签。 |
36
+ | `mahjongTables` | `string` | - | 旧版文本配置,仅在 `mahjongTableConfigs` 为空时读取;建议迁移到结构化列表。 |
37
+
38
+ ### 麻将桌配置
39
+
40
+ 在 Koishi 配置页的 `mahjongTableConfigs` 中新增桌位,每一项填写:
41
+
42
+ ```yaml
43
+ displayName: "🀄️ M.LEAGUE联名比赛专用机"
44
+ aliases: [a, 四麻A, 比赛机]
45
+ pricingConfigIds: [pricing-mahjong-a]
46
+ ```
47
+
48
+ `displayName` 是该桌的稳定锚点和 session 标签;玩家输入的桌号、简称等均写入 `aliases`(至少一个)。结构化配置有内容时优先于旧 `mahjongTables` 文本;已有旧配置不会失效,可逐桌迁移。
35
49
 
36
50
  管理员快捷指令必须同时配置 `enableStaffCommands: true` 与 `staffUserIds`。它们使用现有 `integrationToken` 调用受限的余额调整和立即结账接口;目标用户参数使用 Koishi 的 `user` 选择器,只有白名单内的管理员可以操作其他用户。
37
51
 
@@ -43,6 +57,7 @@
43
57
  * `logout` - 结算当前玩家的计费场次。
44
58
  玩家在未产生任何费用时退场,机器人会简洁显示“本次未产生费用”和当前余额;存在收费或优惠明细时仍显示完整结算账单。
45
59
  结账成功后,机器人会向 `staffUserIds` 与 `logoutNotifyUserIds` 中的用户私聊同一份账单,账单会明确显示结账玩家身份。
60
+ 账单和管理员代操作回执均使用“平台昵称(QQ:号码)”称呼玩家;平台暂时无法提供昵称时显示“未知昵称(QQ:号码)”,不会显示内部玩家 ID。
46
61
  * `billing` - 预览当前玩家本场计费的消费费用。
47
62
  * `wallet` - 查看当前玩家的钱包余额。
48
63
  * `items` - 查看当前玩家持有的道具或资产。
package/lib/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export type MahjongTableConfig = {
14
14
  aliases: string[];
15
15
  pricingConfigIds: string[];
16
16
  };
17
+ export type MahjongTableConfigInput = Omit<MahjongTableConfig, "tableId">;
17
18
  export type PrismKoishiPluginConfig = {
18
19
  provider: string;
19
20
  autoRegister: boolean;
@@ -25,6 +26,7 @@ export type PrismKoishiPluginConfig = {
25
26
  enableStaffCommands?: boolean;
26
27
  staffUserIds?: string[];
27
28
  logoutNotifyUserIds?: string[];
29
+ mahjongTableConfigs?: MahjongTableConfigInput[];
28
30
  mahjongTables?: string;
29
31
  mahjongTableSize?: number;
30
32
  mahjongLabelPrefix?: string;
@@ -75,6 +77,7 @@ export type Sender = {
75
77
  };
76
78
  export declare function humanReadableBotError(error: PrismBotClientError): string;
77
79
  export declare function parseMahjongTables(value: string, labelPrefix: string): Map<string, MahjongTableConfig>;
80
+ export declare function resolveMahjongTableConfigs(structured: readonly MahjongTableConfigInput[], legacyValue: string, labelPrefix: string): Map<string, MahjongTableConfig>;
78
81
  declare const _default: {
79
82
  name: string;
80
83
  Config: Schema<PrismKoishiPluginConfig>;
package/lib/index.js CHANGED
@@ -5,6 +5,7 @@ exports.apply = apply;
5
5
  exports.applyPrismKoishiPlugin = applyPrismKoishiPlugin;
6
6
  exports.humanReadableBotError = humanReadableBotError;
7
7
  exports.parseMahjongTables = parseMahjongTables;
8
+ exports.resolveMahjongTableConfigs = resolveMahjongTableConfigs;
8
9
  const koishi_1 = require("koishi");
9
10
  exports.name = "prism";
10
11
  exports.Config = koishi_1.Schema.object({
@@ -21,7 +22,12 @@ exports.Config = koishi_1.Schema.object({
21
22
  staffUserIds: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("允许执行管理员指令的平台用户ID列表"),
22
23
  logoutNotifyUserIds: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("结账账单私聊通知的平台用户ID列表"),
23
24
  powerOffInterval: koishi_1.Schema.number().default(0).description("无人自动关机等待秒数 (0为禁用)"),
24
- mahjongTables: koishi_1.Schema.string().description("麻将桌配置"),
25
+ mahjongTableConfigs: koishi_1.Schema.array(koishi_1.Schema.object({
26
+ displayName: koishi_1.Schema.string().required().description("桌位显示名称和 session 标签"),
27
+ aliases: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("命令别名(至少一个),例如 a、四麻A"),
28
+ pricingConfigIds: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("开局时绑定的计费方案 ID"),
29
+ })).default([]).description("麻将桌配置"),
30
+ mahjongTables: koishi_1.Schema.string().description("旧版麻将桌文本配置(已废弃;仅在结构化配置为空时使用)"),
25
31
  mahjongTableSize: koishi_1.Schema.number().default(4).description("麻将桌人数限制"),
26
32
  mahjongLabelPrefix: koishi_1.Schema.string().default("麻将桌").description("麻将账单前缀"),
27
33
  });
@@ -324,7 +330,7 @@ class PrismKoishiService {
324
330
  }
325
331
  async sender(context) {
326
332
  const id = context.session?.senderId || context.session?.userId || "";
327
- let name = id;
333
+ let name = context.session?.username || context.session?.senderName || id;
328
334
  try {
329
335
  if (context.session?.bot?.getUser) {
330
336
  const user = await context.session.bot.getUser(id);
@@ -333,9 +339,7 @@ class PrismKoishiService {
333
339
  }
334
340
  }
335
341
  }
336
- catch {
337
- name = context.session?.username || context.session?.senderName || id;
338
- }
342
+ catch { }
339
343
  return { id, name };
340
344
  }
341
345
  async register(sender) {
@@ -345,7 +349,7 @@ class PrismKoishiService {
345
349
  async loginForTarget(actor, targetSubject, bot) {
346
350
  return this.withTarget(actor, targetSubject, async (sender, isTargeted) => {
347
351
  await this.client.startSessionByIdentity(this.identity(sender), this.loginSessionBody());
348
- return isTargeted ? `✅ 已为用户 ${formatPlayerReference(sender)} 入场成功` : "✅ 入场成功";
352
+ return isTargeted ? `✅ 已为用户 ${formatPlayerReference(sender, this.config.provider)} 入场成功` : "✅ 入场成功";
349
353
  }, bot);
350
354
  }
351
355
  async login(sender) {
@@ -370,7 +374,7 @@ class PrismKoishiService {
370
374
  quantityDelta: amount * direction,
371
375
  reason: isAddition ? "Koishi 管理员增加余额" : "Koishi 管理员扣除余额",
372
376
  }]);
373
- return `✅ 已为用户 ${sender.id}${isAddition ? "增加" : "扣除"} ${formatNumber(amount)} ${this.config.currencyName}`;
377
+ return `✅ 已为用户 ${formatPlayerReference(sender, this.config.provider)}${isAddition ? "增加" : "扣除"} ${formatNumber(amount)} ${this.config.currencyName}`;
374
378
  }, bot);
375
379
  }
376
380
  async overwriteTargetCheckout(actor, targetSubject, rawAmount, rawReason, bot) {
@@ -380,7 +384,7 @@ class PrismKoishiService {
380
384
  return "金额必须为非负数";
381
385
  const reason = cleanText(rawReason) || "Koishi 管理员手动调价";
382
386
  await this.client.checkoutWithOverrideByIdentity(this.identity(sender), total, reason);
383
- return `✅ 已为用户 ${formatPlayerReference(sender)} 覆盖结账为 ${formatNumber(total)} ${this.config.currencyName}`;
387
+ return `✅ 已为用户 ${formatPlayerReference(sender, this.config.provider)} 覆盖结账为 ${formatNumber(total)} ${this.config.currencyName}`;
384
388
  }, bot);
385
389
  }
386
390
  async mahjongJoin(sender, rawTableId) {
@@ -675,7 +679,7 @@ class PrismKoishiService {
675
679
  }
676
680
  mergeWaitingSeats(groups) {
677
681
  for (const [tableId, state] of this.mahjongTables) {
678
- const table = this.mahjongTableConfigs().get(tableId);
682
+ const table = uniqueMahjongConfigs(this.mahjongTableConfigs()).find((candidate) => candidate.tableId === tableId);
679
683
  if (!table)
680
684
  continue;
681
685
  const label = mahjongSessionLabel(table, this.config.mahjongLabelPrefix ?? "麻将桌");
@@ -702,8 +706,8 @@ class PrismKoishiService {
702
706
  return playerId || "未知玩家";
703
707
  const platformName = await this.resolvePlatformName(sender.id);
704
708
  const name = platformName
705
- || (sender.name && sender.name !== sender.id ? sender.name : playerId)
706
- || sender.id;
709
+ || (sender.name && sender.name !== sender.id ? sender.name : "")
710
+ || "未知昵称";
707
711
  return `玩家:${name}(${this.config.provider.toUpperCase()}:${sender.id})`;
708
712
  }
709
713
  mahjongTableForPlayer(playerId) {
@@ -716,7 +720,7 @@ class PrismKoishiService {
716
720
  return null;
717
721
  }
718
722
  mahjongTableConfigs() {
719
- return parseMahjongTables(this.config.mahjongTables ?? "", this.config.mahjongLabelPrefix ?? "麻将桌");
723
+ return resolveMahjongTableConfigs(this.config.mahjongTableConfigs ?? [], this.config.mahjongTables ?? "", this.config.mahjongLabelPrefix ?? "麻将桌");
720
724
  }
721
725
  syncMahjongTableStates(sessions) {
722
726
  const tables = uniqueMahjongConfigs(this.mahjongTableConfigs());
@@ -969,6 +973,23 @@ function parseMahjongTables(value, labelPrefix) {
969
973
  }
970
974
  return tables;
971
975
  }
976
+ function resolveMahjongTableConfigs(structured, legacyValue, labelPrefix) {
977
+ if (structured.length === 0)
978
+ return parseMahjongTables(legacyValue, labelPrefix);
979
+ const tables = new Map();
980
+ for (const input of structured) {
981
+ const displayName = cleanText(input.displayName);
982
+ const tableId = displayName;
983
+ const aliases = [...new Set((input.aliases ?? []).map(cleanText).filter(Boolean))];
984
+ const pricingConfigIds = (input.pricingConfigIds ?? []).map(cleanText).filter(Boolean);
985
+ if (!tableId || !displayName || pricingConfigIds.length === 0)
986
+ continue;
987
+ const table = { tableId, displayName, aliases, pricingConfigIds };
988
+ for (const alias of aliases)
989
+ tables.set(alias, table);
990
+ }
991
+ return tables;
992
+ }
972
993
  function uniqueMahjongConfigs(tables) {
973
994
  return [...new Map([...tables.values()].map((table) => [table.tableId, table])).values()];
974
995
  }
@@ -1001,7 +1022,7 @@ function formatPlayerGroups(groups, tableSize, mahjongLabelPrefix) {
1001
1022
  const heading = group.table
1002
1023
  ? `${formatMahjongTableLabel(group.table, mahjongLabelPrefix)} ( ${group.players.length}/${tableSize} )`
1003
1024
  : `${group.label} ( ${group.players.length}人 )`;
1004
- lines.push(`${heading}:\n${formatPlayerNames(group.players)}`);
1025
+ lines.push(`\n${heading}:\n${formatPlayerNames(group.players)}`);
1005
1026
  }
1006
1027
  return lines.join("\n");
1007
1028
  }
@@ -1042,10 +1063,9 @@ function normalizeTargetSubject(value) {
1042
1063
  const separator = subject.indexOf(":");
1043
1064
  return separator > 0 ? subject.slice(separator + 1) : subject;
1044
1065
  }
1045
- function formatPlayerReference(sender) {
1046
- return sender.name && sender.name !== sender.id
1047
- ? `${sender.name}(QQ:${sender.id})`
1048
- : sender.id;
1066
+ function formatPlayerReference(sender, provider = "qq") {
1067
+ const name = sender.name && sender.name !== sender.id ? sender.name : "未知昵称";
1068
+ return `${name}(${provider.toUpperCase()}:${sender.id})`;
1049
1069
  }
1050
1070
  function toNumber(value) {
1051
1071
  if (value == null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-prism",
3
- "version": "0.1.15",
3
+ "version": "0.1.18",
4
4
  "description": "PRiSM Next 计费与设备管理系统的 Koishi 机器人集成插件",
5
5
  "main": "./lib/index.js",
6
6
  "exports": {