koishi-plugin-prism 0.1.4 → 0.1.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/lib/index.js +48 -24
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  | `provider` | `string` | `"qq"` | 当前绑定的账号提供商平台名称(如 `"qq"`,`"discord"`)。 |
25
25
  | `autoRegister` | `boolean` | `true` | 当玩家未注册时,是否在首次操作(如入场/查钱包)时自动在 PRiSM 中创建新玩家。 |
26
26
  | `loginPricingConfigIds` | `string[]` | `[]` | 默认入场计费规则 ID 列表。 |
27
- | `loginSessionLabel` | `string` | - | 默认入场会话的备注文本(如 `"音游区间"`)。 |
27
+ | `loginSessionLabel` | `string` | `"音游区间"` | 默认入场会话的标签文本。后端会按该标签对同一玩家的活跃会话去重,重复入场会被拒绝并提示。留空则不启用去重。 |
28
28
  | `defaultDoorDeviceId` | `string` | - | 默认门锁设备的 ID,用于开门指令。 |
29
29
  | `defaultScanProvider` | `string` | `"aime"` | 默认模拟刷卡时的读卡器协议提供商(如 `"aime"`)。 |
30
30
  | `currencyName` | `string` | `"金币"` | 账户货币在显示时的自定义单位名称。 |
package/lib/index.js CHANGED
@@ -16,7 +16,10 @@ exports.Config = koishi_1.Schema.object({
16
16
  currencyName: koishi_1.Schema.string().default("猫粮").description("代币名称"),
17
17
  defaultDoorDeviceId: koishi_1.Schema.string().default("front-door").description("默认开门设备ID"),
18
18
  defaultScanProvider: koishi_1.Schema.string().default("aime").description("默认刷卡提供商"),
19
+ loginPricingConfigIds: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("默认入场绑定的计费策略ID"),
20
+ loginSessionLabel: koishi_1.Schema.string().default("音游区间").description("默认入场场次标签 (防重复入场)"),
19
21
  enableStaffCommands: koishi_1.Schema.boolean().default(false).description("是否启用管理员指令"),
22
+ staffUserIds: koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("允许执行管理员指令的平台用户ID列表"),
20
23
  powerOffInterval: koishi_1.Schema.number().default(0).description("无人自动关机等待秒数 (0为禁用)"),
21
24
  mahjongTables: koishi_1.Schema.string().description("麻将桌配置"),
22
25
  mahjongTableSize: koishi_1.Schema.number().default(4).description("麻将桌人数限制"),
@@ -709,6 +712,15 @@ class PrismKoishiService {
709
712
  return null;
710
713
  }
711
714
  }
715
+ async resolvePlayerDisplay(sender, playerId) {
716
+ if (!sender)
717
+ return playerId || "未知玩家";
718
+ const platformName = await this.resolvePlatformName(sender.id);
719
+ const name = platformName
720
+ || (sender.name && sender.name !== sender.id ? sender.name : playerId)
721
+ || sender.id;
722
+ return `玩家:${name}(${this.config.provider.toUpperCase()}:${sender.id})`;
723
+ }
712
724
  mahjongTableForPlayer(playerId) {
713
725
  for (const [tableId, state] of this.mahjongTables) {
714
726
  if (state.activeSessions[playerId])
@@ -752,7 +764,7 @@ class PrismKoishiService {
752
764
  return "权限不足";
753
765
  return null;
754
766
  }
755
- formatCheckoutPreview(result, sender, title = "【结算账单】") {
767
+ async formatCheckoutPreview(result, sender, title = "【结算账单】") {
756
768
  if (result?.billing && result?.session) {
757
769
  return formatLegacyBilling(result, this.config.currencyName);
758
770
  }
@@ -782,57 +794,57 @@ class PrismKoishiService {
782
794
  const adjustments = result?.adjustments ?? [];
783
795
  const assetHoldings = result?.assetHoldings ?? [];
784
796
  const lines = [];
785
- const headerParts = [title];
786
- if (playerId) {
787
- const identitySuffix = sender ? `(${this.config.provider.toUpperCase()}:${sender.id})` : "";
788
- headerParts.push(`玩家ID:${playerId}${identitySuffix}`);
789
- }
790
- else if (sender) {
791
- headerParts.push(`玩家:${sender.name || sender.id}(${this.config.provider.toUpperCase()}:${sender.id})`);
792
- }
793
- lines.push(headerParts.join("\n"));
797
+ lines.push(title);
798
+ lines.push(await this.resolvePlayerDisplay(sender, playerId));
794
799
  const validStarts = sessionPreviews.map((s) => parseDateTime(s?.startedAt)).filter(Boolean);
795
800
  const validEnds = sessionPreviews.map((s) => sessionDisplayEnd(s, previewedAt)).filter(Boolean);
796
801
  if (validStarts.length > 0) {
797
802
  const overallStart = minDate(validStarts);
798
803
  const overallEnd = validEnds.length > 0 ? maxDate(validEnds) : now(this.config);
799
- lines.push(`⏰全场到店时段:${formatHM(overallStart)}–${formatHM(overallEnd)}`);
804
+ lines.push(`⏰ 游玩时间:${formatHM(overallStart)}–${formatHM(overallEnd)}`);
800
805
  }
801
- lines.push("");
802
806
  for (const sPrev of sessionPreviews) {
803
807
  const label = sPrev?.label || "计时区间";
804
808
  const startDt = parseDateTime(sPrev?.startedAt);
805
809
  const endDt = sessionDisplayEnd(sPrev, previewedAt);
806
810
  const status = sPrev?.status ?? "active";
807
811
  const sTotal = toNumber(sPrev?.total ?? 0);
812
+ lines.push("");
808
813
  lines.push(label);
809
814
  if (startDt && endDt) {
815
+ const minutes = Math.floor((endDt.getTime() - startDt.getTime()) / 60_000);
810
816
  lines.push(`游玩时段:${formatHM(startDt)}-${formatHM(endDt)}`);
811
- lines.push(`游玩时长:${formatDurationValue(Math.floor((endDt.getTime() - startDt.getTime()) / 60_000))} | 消费:${formatNumber(sTotal)}${currency}`);
817
+ lines.push(`游玩时长:${formatDurationValue(minutes)}|消费:${formatNumber(sTotal)}${currency}`);
812
818
  }
813
819
  else if (startDt) {
814
820
  lines.push(`入场:${formatHM(startDt)} (${status === "active" ? "计费中" : "已关闭"})`);
815
821
  }
816
- lines.push("");
822
+ const sessionAdjustments = (sPrev?.adjustments ?? []);
823
+ for (const adj of sessionAdjustments) {
824
+ const amount = toNumber(firstDefined(adj ?? {}, "amount", "saved", 0));
825
+ if (amount === 0)
826
+ continue;
827
+ const adjLabel = firstDefined(adj ?? {}, "label", "name", "source") ?? "优惠";
828
+ lines.push(` └ ${adjLabel}:${formatNumber(amount)}${currency}`);
829
+ }
817
830
  }
818
- lines.push("————————————");
819
- const balanceParts = [];
831
+ let balance = 0;
832
+ let hasBalance = false;
820
833
  for (const holding of assetHoldings) {
821
- const qty = toNumber(holding?.quantity ?? 0);
822
834
  const code = String(holding?.assetCode ?? "").toLowerCase();
823
835
  if (code.includes("paid") || code.includes("free") || code.includes("currency")) {
824
- balanceParts.push(`${formatNumber(qty)}${currency}`);
836
+ balance += toNumber(holding?.quantity ?? 0);
837
+ hasBalance = true;
825
838
  }
826
839
  }
827
- if (balanceParts.length > 0)
828
- lines.push(`扣款后余额:${balanceParts.join("+")}`);
840
+ lines.push("");
829
841
  lines.push(`计费总价:${formatNumber(subtotal)}${currency}`);
830
- const hasDiscount = adjustments.some((adj) => {
831
- const amount = toNumber(firstDefined(adj ?? {}, "amount", "saved", 0));
832
- return amount !== 0;
833
- });
842
+ const hasDiscount = adjustments.some((adj) => toNumber(firstDefined(adj ?? {}, "amount", "saved", 0)) !== 0)
843
+ || sessionPreviews.some((sp) => (sp?.adjustments ?? []).some((adj) => toNumber(firstDefined(adj ?? {}, "amount", "saved", 0)) !== 0));
834
844
  if (hasDiscount)
835
845
  lines.push(`优惠后价格:${formatNumber(total)}${currency}`);
846
+ if (hasBalance)
847
+ lines.push(`扣款后余额:${formatNumber(balance)}${currency}`);
836
848
  return lines.join("\n");
837
849
  }
838
850
  handleCommandError(error) {
@@ -846,6 +858,18 @@ class PrismKoishiService {
846
858
  }
847
859
  }
848
860
  function humanReadableBotError(error) {
861
+ if (error.code === "DUPLICATE_SESSION_LABEL") {
862
+ return "❌ 您已经处于入场状态,请勿重复发送入场命令。";
863
+ }
864
+ if (error.code === "PLAYER_HAS_NO_UNSETTLED_SESSIONS") {
865
+ return "您当前没有未结算的账单,无需结账。";
866
+ }
867
+ if (error.code === "ACTIVE_SESSION_NOT_FOUND") {
868
+ return "您当前没有进行中的计费场次。";
869
+ }
870
+ if (error.code === "PLAYER_IDENTITY_NOT_FOUND") {
871
+ return "未找到您的玩家身份,请先注册或绑定账号。";
872
+ }
849
873
  if (error.code === "INSUFFICIENT_BALANCE") {
850
874
  return "余额不足,暂时不能结账。请先充值,或由店员在后台改价后再结账。";
851
875
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-prism",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "PRiSM Next 计费与设备管理系统的 Koishi 机器人集成插件",
5
5
  "main": "./lib/index.js",
6
6
  "exports": {
@@ -48,4 +48,4 @@
48
48
  "test": "bun test test",
49
49
  "typecheck": "tsc -p tsconfig.json"
50
50
  }
51
- }
51
+ }