koishi-plugin-prism 0.1.10 → 0.1.13

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
@@ -31,10 +31,9 @@
31
31
  | `resolveDisplayName` | `function` | - | 可选。自定义用于获取群内昵称作为玩家注册名的异步逻辑。 |
32
32
  | `enableStaffCommands` | `boolean` | `false` | 是否开启管理员快捷指令。 |
33
33
  | `staffUserIds` | `string[]` | `[]` | 允许执行管理员快捷指令的平台用户 ID(如 QQ 号)白名单。空列表不授予目标用户操作权限。 |
34
- | `staffSessionToken` | `string` | - | 管理员写操作所需的 Staff 管理 Token。 |
35
34
  | `logoutNotifyUserIds` | `string[]` | `[]` | 结账成功后额外私聊完整账单的平台用户 ID。通知收件人为该列表与 `staffUserIds` 的去重并集。 |
36
35
 
37
- 管理员快捷指令必须同时配置 `enableStaffCommands: true`、`staffUserIds` `staffSessionToken`。目标用户参数使用 Koishi 的 `user` 选择器;只有白名单内的管理员可以操作其他用户。
36
+ 管理员快捷指令必须同时配置 `enableStaffCommands: true` `staffUserIds`。它们使用现有 `integrationToken` 调用受限的余额调整和立即结账接口;目标用户参数使用 Koishi 的 `user` 选择器,只有白名单内的管理员可以操作其他用户。
38
37
 
39
38
  ## 📝 机器人指令列表
40
39
 
package/lib/index.d.ts CHANGED
@@ -40,7 +40,6 @@ export type PrismKoishiPluginConfig = {
40
40
  now?: () => Date;
41
41
  baseUrl?: string;
42
42
  integrationToken?: string;
43
- staffSessionToken?: string;
44
43
  client?: any;
45
44
  };
46
45
  export type KoishiCommandRegistration = {
package/lib/index.js CHANGED
@@ -12,7 +12,6 @@ exports.Config = koishi_1.Schema.object({
12
12
  autoRegister: koishi_1.Schema.boolean().default(true).description("是否自动注册"),
13
13
  baseUrl: koishi_1.Schema.string().description("PRiSM 后端 API Base URL"),
14
14
  integrationToken: koishi_1.Schema.string().role("secret").description("集成 API Token"),
15
- staffSessionToken: koishi_1.Schema.string().role("secret").description("Staff 管理 Token (可选)"),
16
15
  currencyName: koishi_1.Schema.string().default("猫粮").description("代币名称"),
17
16
  defaultDoorDeviceId: koishi_1.Schema.string().default("front-door").description("默认开门设备ID"),
18
17
  defaultScanProvider: koishi_1.Schema.string().default("aime").description("默认刷卡提供商"),
@@ -67,27 +66,27 @@ function applyPrismKoishiPlugin(ctx, config) {
67
66
  }
68
67
  };
69
68
  ctx.command("register", "绑定或注册当前平台用户到 PRiSM").action(wrap(async (context) => service.register(await service.sender(context))));
70
- ctx.command("login [target:user]", "开启玩家计费场次").action(wrap(async (context, target) => service.loginForTarget(await service.sender(context), target)));
71
- ctx.command("入场 [target:user]", "入场").action(wrap(async (context, target) => service.loginForTarget(await service.sender(context), target)));
69
+ ctx.command("login [target:user]", "开启玩家计费场次").action(wrap(async (context, target) => service.loginForTarget(await service.sender(context), target, context.session?.bot)));
70
+ ctx.command("入场 [target:user]", "入场").action(wrap(async (context, target) => service.loginForTarget(await service.sender(context), target, context.session?.bot)));
72
71
  ctx.command("mahjong <tableId>", "加入指定麻将桌").action(wrap(async (context, tableId) => service.mahjongJoin(await service.sender(context), tableId)));
73
72
  ctx.command("上桌 <tableId>", "加入指定麻将桌").action(wrap(async (context, tableId) => service.mahjongJoin(await service.sender(context), tableId)));
74
73
  ctx.command("下桌 <tableId>", "离开指定麻将桌").action(wrap(async (context, tableId) => service.mahjongLeave(await service.sender(context), tableId)));
75
- ctx.command("logout [target:user]", "结算玩家计费场次").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.logout(sender, context.session?.bot))));
76
- ctx.command("billing [target:user]", "预览玩家结账费用").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.billing(sender))));
77
- ctx.command("wallet [target:user]", "查看玩家钱包").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.wallet(sender))));
78
- ctx.command("items [target:user]", "查看玩家资产").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.items(sender))));
74
+ ctx.command("logout [target:user]", "结算玩家计费场次").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.logout(sender, context.session?.bot), context.session?.bot)));
75
+ ctx.command("billing [target:user]", "预览玩家结账费用").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.billing(sender), context.session?.bot)));
76
+ ctx.command("wallet [target:user]", "查看玩家钱包").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.wallet(sender), context.session?.bot)));
77
+ ctx.command("items [target:user]", "查看玩家资产").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.items(sender), context.session?.bot)));
79
78
  ctx.command("list", "查看当前在线玩家列表").action(wrap(async (context) => service.listActiveSessions(await service.sender(context))));
80
79
  ctx.command("show [deviceId]", "查看设备电源状态").action(wrap(async (context, deviceId) => service.listDeviceStates(deviceId)));
81
- ctx.command("history [target:user]", "查看玩家历史").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.history(sender))));
80
+ ctx.command("history [target:user]", "查看玩家历史").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.history(sender), context.session?.bot)));
82
81
  ctx.command("lock", "向默认门锁设备发送开门指令").action(wrap(async (context) => service.lock(await service.sender(context))));
83
82
  ctx.command("on <deviceId>", "请求启动指定设备电源").action(wrap(async (context, deviceId) => service.powerOn(await service.sender(context), deviceId)));
84
83
  ctx.command("off <deviceId>", "请求关闭指定设备电源").action(wrap(async (context, deviceId) => service.powerOff(await service.sender(context), deviceId)));
85
84
  ctx.command("coin <deviceId> [count]", "请求向指定设备投币").action(wrap(async (context, deviceId, count) => service.coin(await service.sender(context), deviceId, count)));
86
85
  ctx.command("scan <deviceId> <subject>", "请求指定设备模拟刷卡").action(wrap(async (context, deviceId, subject) => service.scan(await service.sender(context), deviceId, subject)));
87
86
  ctx.command("redeem <code>", "兑换 PRiSM 礼物码").action(wrap(async (context, code) => service.redeem(await service.sender(context), code)));
88
- ctx.command("add <target:user> <amount:number>", "增加玩家余额").action(wrap(async (context, target, amount) => service.adjustTargetBalance(await service.sender(context), target, amount, 1)));
89
- ctx.command("del <target:user> <amount:number>", "扣除玩家余额").action(wrap(async (context, target, amount) => service.adjustTargetBalance(await service.sender(context), target, amount, -1)));
90
- ctx.command("overwrite <target:user> <amount:number> [reason:text]", "覆盖结账金额并立即结账").action(wrap(async (context, target, amount, reason) => service.overwriteTargetCheckout(await service.sender(context), target, amount, reason)));
87
+ ctx.command("add <target:user> <amount:number>", "增加玩家余额").action(wrap(async (context, target, amount) => service.adjustTargetBalance(await service.sender(context), target, amount, 1, context.session?.bot)));
88
+ ctx.command("del <target:user> <amount:number>", "扣除玩家余额").action(wrap(async (context, target, amount) => service.adjustTargetBalance(await service.sender(context), target, amount, -1, context.session?.bot)));
89
+ ctx.command("overwrite <target:user> <amount:number> [reason:text]", "覆盖结账金额并立即结账").action(wrap(async (context, target, amount, reason) => service.overwriteTargetCheckout(await service.sender(context), target, amount, reason, context.session?.bot)));
91
90
  const intervalMs = (config.powerOffInterval ?? 0) * 1000;
92
91
  if (intervalMs > 0 && typeof ctx.setInterval === "function") {
93
92
  ctx.setInterval(() => {
@@ -150,12 +149,6 @@ class PrismApiClient {
150
149
  throw new PrismBotClientError(error.message || "Network error", "NETWORK_ERROR", 500, {});
151
150
  }
152
151
  }
153
- requireStaffSessionToken() {
154
- if (!this.config.staffSessionToken) {
155
- throw new PrismBotClientError("Staff session token is required for this Bot shortcut.", "STAFF_TOKEN_REQUIRED", 0, {});
156
- }
157
- return this.config.staffSessionToken;
158
- }
159
152
  identityBody(identity) {
160
153
  return {
161
154
  identity: {
@@ -273,18 +266,16 @@ class PrismApiClient {
273
266
  token: this.config.integrationToken,
274
267
  });
275
268
  }
276
- async adjustStaffAssets(playerId, adjustments) {
277
- return this.request("POST", "/rpc/staff/players/:playerId/assets/adjustments", {
278
- token: this.requireStaffSessionToken(),
279
- params: { playerId },
280
- body: { adjustments },
269
+ async adjustAssetsByIdentity(identity, adjustments) {
270
+ return this.request("POST", "/rpc/integration/players/by-identity/assets/adjustments", {
271
+ token: this.config.integrationToken,
272
+ body: { ...this.identityBody(identity), adjustments },
281
273
  });
282
274
  }
283
- async checkoutWithOverride(playerId, total, reason) {
284
- return this.request("POST", "/rpc/staff/players/:playerId/checkout/override", {
285
- token: this.requireStaffSessionToken(),
286
- params: { playerId },
287
- body: { total, reason },
275
+ async checkoutWithOverrideByIdentity(identity, total, reason) {
276
+ return this.request("POST", "/rpc/integration/players/by-identity/checkout/override", {
277
+ token: this.config.integrationToken,
278
+ body: { ...this.identityBody(identity), total, reason },
288
279
  });
289
280
  }
290
281
  }
@@ -328,7 +319,6 @@ class PrismKoishiService {
328
319
  this.client = new PrismApiClient(http, {
329
320
  baseUrl: config.baseUrl,
330
321
  integrationToken: config.integrationToken,
331
- staffSessionToken: config.staffSessionToken,
332
322
  });
333
323
  }
334
324
  }
@@ -352,54 +342,46 @@ class PrismKoishiService {
352
342
  await this.client.resolveOrRegisterIdentity(this.identity(sender));
353
343
  return "注册成功";
354
344
  }
355
- async loginForTarget(actor, targetSubject) {
345
+ async loginForTarget(actor, targetSubject, bot) {
356
346
  return this.withTarget(actor, targetSubject, async (sender, isTargeted) => {
357
347
  await this.client.startSessionByIdentity(this.identity(sender), this.loginSessionBody());
358
- return isTargeted ? `✅ 已为用户 ${sender.id} 入场成功` : "✅ 入场成功";
359
- });
348
+ return isTargeted ? `✅ 已为用户 ${formatPlayerReference(sender)} 入场成功` : "✅ 入场成功";
349
+ }, bot);
360
350
  }
361
351
  async login(sender) {
362
352
  await this.client.startSessionByIdentity(this.identity(sender), this.loginSessionBody());
363
353
  return "✅ 入场成功";
364
354
  }
365
- async withTarget(actor, targetSubject, action) {
366
- const target = this.targetSender(actor, targetSubject);
355
+ async withTarget(actor, targetSubject, action, bot) {
356
+ const target = await this.targetSender(actor, targetSubject, bot);
367
357
  if (typeof target === "string")
368
358
  return target;
369
359
  return action(target, target !== actor);
370
360
  }
371
- async adjustTargetBalance(actor, targetSubject, rawAmount, direction) {
361
+ async adjustTargetBalance(actor, targetSubject, rawAmount, direction, bot) {
372
362
  return this.withTarget(actor, targetSubject, async (sender) => {
373
363
  const amount = Number(rawAmount);
374
364
  if (!Number.isFinite(amount) || amount <= 0)
375
365
  return "金额必须大于 0";
376
- const player = await this.resolvePlayer(sender);
377
- const playerId = String(player.id ?? "");
378
- if (!playerId)
379
- return "找不到玩家";
380
366
  const isAddition = direction === 1;
381
- await this.client.adjustStaffAssets(playerId, [{
367
+ await this.client.adjustAssetsByIdentity(this.identity(sender), [{
382
368
  assetType: "currency",
383
369
  assetCode: "paid",
384
370
  quantityDelta: amount * direction,
385
371
  reason: isAddition ? "Koishi 管理员增加余额" : "Koishi 管理员扣除余额",
386
372
  }]);
387
373
  return `✅ 已为用户 ${sender.id}${isAddition ? "增加" : "扣除"} ${formatNumber(amount)} ${this.config.currencyName}`;
388
- });
374
+ }, bot);
389
375
  }
390
- async overwriteTargetCheckout(actor, targetSubject, rawAmount, rawReason) {
376
+ async overwriteTargetCheckout(actor, targetSubject, rawAmount, rawReason, bot) {
391
377
  return this.withTarget(actor, targetSubject, async (sender) => {
392
378
  const total = Number(rawAmount);
393
379
  if (!Number.isFinite(total) || total < 0)
394
380
  return "金额必须为非负数";
395
- const player = await this.resolvePlayer(sender);
396
- const playerId = String(player.id ?? "");
397
- if (!playerId)
398
- return "找不到玩家";
399
381
  const reason = cleanText(rawReason) || "Koishi 管理员手动调价";
400
- await this.client.checkoutWithOverride(playerId, total, reason);
401
- return `✅ 已为用户 ${sender.id} 覆盖结账为 ${formatNumber(total)} ${this.config.currencyName}`;
402
- });
382
+ await this.client.checkoutWithOverrideByIdentity(this.identity(sender), total, reason);
383
+ return `✅ 已为用户 ${formatPlayerReference(sender)} 覆盖结账为 ${formatNumber(total)} ${this.config.currencyName}`;
384
+ }, bot);
403
385
  }
404
386
  async mahjongJoin(sender, rawTableId) {
405
387
  const tableId = cleanText(rawTableId);
@@ -744,14 +726,20 @@ class PrismKoishiService {
744
726
  displayName: sender.name || `${this.config.provider.toUpperCase()} ${sender.id}`,
745
727
  };
746
728
  }
747
- targetSender(actor, targetSubject) {
748
- const subject = cleanText(targetSubject);
729
+ async targetSender(actor, targetSubject, bot) {
730
+ const subject = normalizeTargetSubject(targetSubject);
749
731
  if (!subject)
750
732
  return actor;
751
733
  const denied = this.targetStaffDenied(actor);
752
734
  if (denied)
753
735
  return denied;
754
- return { id: subject, name: subject };
736
+ try {
737
+ const user = await bot?.getUser?.(subject);
738
+ return { id: subject, name: user?.name || subject };
739
+ }
740
+ catch {
741
+ return { id: subject, name: subject };
742
+ }
755
743
  }
756
744
  loginSessionBody() {
757
745
  const pricingConfigIds = (this.config.loginPricingConfigIds ?? [])
@@ -1012,6 +1000,16 @@ function parsePositiveInt(value, command, label, fallback) {
1012
1000
  function cleanText(value) {
1013
1001
  return value == null ? "" : String(value).trim();
1014
1002
  }
1003
+ function normalizeTargetSubject(value) {
1004
+ const subject = cleanText(value);
1005
+ const separator = subject.indexOf(":");
1006
+ return separator > 0 ? subject.slice(separator + 1) : subject;
1007
+ }
1008
+ function formatPlayerReference(sender) {
1009
+ return sender.name && sender.name !== sender.id
1010
+ ? `${sender.name}(QQ:${sender.id})`
1011
+ : sender.id;
1012
+ }
1015
1013
  function toNumber(value) {
1016
1014
  if (value == null)
1017
1015
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koishi-plugin-prism",
3
- "version": "0.1.10",
3
+ "version": "0.1.13",
4
4
  "description": "PRiSM Next 计费与设备管理系统的 Koishi 机器人集成插件",
5
5
  "main": "./lib/index.js",
6
6
  "exports": {