koishi-plugin-prism 0.1.18 → 0.1.20
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 +5 -3
- package/lib/index.js +46 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
* 🎮 **玩家入场与结算**:通过 `/login` 和 `/logout` 指令开启或结算计费场次,支持 `/入场` 别名;命令回复会引用触发消息,结账账单可私聊通知管理员与指定用户。
|
|
10
10
|
* 💳 **账户钱包与资产管理**:支持查询钱包余额(`/wallet`)和持有的道具资产(`/items`)。
|
|
11
|
-
* 🀄 **麻将桌位集成**:包含 `/mahjong <tableId
|
|
11
|
+
* 🀄 **麻将桌位集成**:包含 `/mahjong <tableId>`、`/上桌 <桌号>`、自动识别当前桌位的 `/下桌`,以及用于查看桌名、别名和状态的 `/麻将列表`。`上桌` 仅允许已通过 `login`/`入场` 开启默认入场会话的玩家使用。`list` 按 session 标签分组:有非音乐游戏 session 的玩家归入最新的非音乐标签,纯音乐玩家归入音乐标签;麻将桌额外显示当前人数和容量。已开局桌位会从后端活跃 session 自动恢复,未满桌候座仍由机器人进程暂存,重启后不会保留。
|
|
12
12
|
* 🔌 **硬件设备状态与电源管理**:可直接在聊天中查看设备状态(`/show`)、远程开启/关闭电源(`/on`、`/off`)、远程投币(`/coin`)和模拟刷卡(`/scan`)。
|
|
13
13
|
* 🎟️ **礼物兑换码**:使用 `/redeem <code>` 兑换系统发放的福利礼包。
|
|
14
14
|
* 🛠️ **管理员快捷指令**:允许管理员为指定平台用户增加或扣除余额,并覆盖结账金额后立即结账。
|
|
@@ -58,6 +58,7 @@ pricingConfigIds: [pricing-mahjong-a]
|
|
|
58
58
|
玩家在未产生任何费用时退场,机器人会简洁显示“本次未产生费用”和当前余额;存在收费或优惠明细时仍显示完整结算账单。
|
|
59
59
|
结账成功后,机器人会向 `staffUserIds` 与 `logoutNotifyUserIds` 中的用户私聊同一份账单,账单会明确显示结账玩家身份。
|
|
60
60
|
账单和管理员代操作回执均使用“平台昵称(QQ:号码)”称呼玩家;平台暂时无法提供昵称时显示“未知昵称(QQ:号码)”,不会显示内部玩家 ID。
|
|
61
|
+
管理员 `/add` 增加免费余额;`/del` 按结账相同的顺序从可用免费余额、再从付费余额扣除,余额不足时会返回余额不足提示。
|
|
61
62
|
* `billing` - 预览当前玩家本场计费的消费费用。
|
|
62
63
|
* `wallet` - 查看当前玩家的钱包余额。
|
|
63
64
|
* `items` - 查看当前玩家持有的道具或资产。
|
|
@@ -70,8 +71,9 @@ pricingConfigIds: [pricing-mahjong-a]
|
|
|
70
71
|
* `coin <deviceId> [count]` - 请求向指定设备投币指定枚数。
|
|
71
72
|
* `scan <deviceId> <subject>` - 请求向设备发送模拟刷卡。
|
|
72
73
|
* `redeem <code>` - 兑换礼物码。
|
|
73
|
-
* `mahjong <tableId>` / `上桌
|
|
74
|
-
*
|
|
74
|
+
* `mahjong <tableId>` / `上桌 [tableId]` - 加入指定麻将桌;`/上桌` 未提供桌号时会引导查看 `/麻将列表`。仅允许已通过 `login`/`入场` 开启默认入场会话的玩家使用。
|
|
75
|
+
* `下桌` - 自动离开当前所在麻将桌。
|
|
76
|
+
* `麻将列表` - 查看已配置机器的桌名、命令别名,以及空闲、等位或游玩中状态。
|
|
75
77
|
|
|
76
78
|
### 管理员快捷指令
|
|
77
79
|
启用 `enableStaffCommands`、配置 `staffUserIds` 白名单与 `staffSessionToken` 后可使用:
|
package/lib/index.js
CHANGED
|
@@ -50,7 +50,8 @@ exports.PrismBotClientError = PrismBotClientError;
|
|
|
50
50
|
const LOCAL_TZ_OFFSET_MINUTES = 8 * 60;
|
|
51
51
|
const USAGE = {
|
|
52
52
|
mahjong_join: "/上桌 <桌号>",
|
|
53
|
-
mahjong_leave: "/下桌
|
|
53
|
+
mahjong_leave: "/下桌",
|
|
54
|
+
mahjong_list: "/麻将列表",
|
|
54
55
|
prism_on: "/prism on <设备ID>",
|
|
55
56
|
prism_off: "/prism off <设备ID|all>",
|
|
56
57
|
prism_coin: "/prism coin <设备ID> [数量]",
|
|
@@ -75,8 +76,9 @@ function applyPrismKoishiPlugin(ctx, config) {
|
|
|
75
76
|
ctx.command("login [target:user]", "开启玩家计费场次").action(wrap(async (context, target) => service.loginForTarget(await service.sender(context), target, context.session?.bot)));
|
|
76
77
|
ctx.command("入场 [target:user]", "入场").action(wrap(async (context, target) => service.loginForTarget(await service.sender(context), target, context.session?.bot)));
|
|
77
78
|
ctx.command("mahjong <tableId>", "加入指定麻将桌").action(wrap(async (context, tableId) => service.mahjongJoin(await service.sender(context), tableId)));
|
|
78
|
-
ctx.command("上桌
|
|
79
|
-
ctx.command("下桌
|
|
79
|
+
ctx.command("上桌 [tableId]", "加入指定麻将桌").action(wrap(async (context, tableId) => service.mahjongJoin(await service.sender(context), tableId)));
|
|
80
|
+
ctx.command("下桌", "离开当前所在麻将桌").action(wrap(async (context) => service.mahjongLeave(await service.sender(context))));
|
|
81
|
+
ctx.command("麻将列表", "查看麻将机状态与别名").action(wrap(async () => service.listMahjongTables()));
|
|
80
82
|
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)));
|
|
81
83
|
ctx.command("billing [target:user]", "预览玩家结账费用").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.billing(sender), context.session?.bot)));
|
|
82
84
|
ctx.command("wallet [target:user]", "查看玩家钱包").action(wrap(async (context, target) => service.withTarget(await service.sender(context), target, (sender) => service.wallet(sender), context.session?.bot)));
|
|
@@ -278,6 +280,12 @@ class PrismApiClient {
|
|
|
278
280
|
body: { ...this.identityBody(identity), adjustments },
|
|
279
281
|
});
|
|
280
282
|
}
|
|
283
|
+
async adjustWalletByIdentity(identity, amount, reason) {
|
|
284
|
+
return this.request("POST", "/rpc/integration/players/by-identity/wallet/adjustment", {
|
|
285
|
+
token: this.config.integrationToken,
|
|
286
|
+
body: { ...this.identityBody(identity), amount, reason },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
281
289
|
async checkoutWithOverrideByIdentity(identity, total, reason) {
|
|
282
290
|
return this.request("POST", "/rpc/integration/players/by-identity/checkout/override", {
|
|
283
291
|
token: this.config.integrationToken,
|
|
@@ -368,12 +376,7 @@ class PrismKoishiService {
|
|
|
368
376
|
if (!Number.isFinite(amount) || amount <= 0)
|
|
369
377
|
return "金额必须大于 0";
|
|
370
378
|
const isAddition = direction === 1;
|
|
371
|
-
await this.client.
|
|
372
|
-
assetType: "currency",
|
|
373
|
-
assetCode: "paid",
|
|
374
|
-
quantityDelta: amount * direction,
|
|
375
|
-
reason: isAddition ? "Koishi 管理员增加余额" : "Koishi 管理员扣除余额",
|
|
376
|
-
}]);
|
|
379
|
+
await this.client.adjustWalletByIdentity(this.identity(sender), amount * direction, isAddition ? "Koishi 管理员增加余额" : "Koishi 管理员扣除余额");
|
|
377
380
|
return `✅ 已为用户 ${formatPlayerReference(sender, this.config.provider)}${isAddition ? "增加" : "扣除"} ${formatNumber(amount)} ${this.config.currencyName}`;
|
|
378
381
|
}, bot);
|
|
379
382
|
}
|
|
@@ -390,10 +393,10 @@ class PrismKoishiService {
|
|
|
390
393
|
async mahjongJoin(sender, rawTableId) {
|
|
391
394
|
const tableId = cleanText(rawTableId);
|
|
392
395
|
if (!tableId)
|
|
393
|
-
return
|
|
396
|
+
return "请指定麻将桌,例如 /上桌 a;可先使用 /麻将列表 查看可用桌位。";
|
|
394
397
|
const tableConfig = this.mahjongTableConfigs().get(tableId);
|
|
395
398
|
if (!tableConfig)
|
|
396
|
-
return
|
|
399
|
+
return `未找到麻将桌「${tableId}」。可先使用 /麻将列表 查看可用桌位。`;
|
|
397
400
|
const tableKey = tableConfig.tableId;
|
|
398
401
|
const tableSubject = tableConfig.displayName || `${tableKey} 桌`;
|
|
399
402
|
const player = await this.resolvePlayer(sender);
|
|
@@ -406,11 +409,11 @@ class PrismKoishiService {
|
|
|
406
409
|
}
|
|
407
410
|
const existing = this.mahjongTableForPlayer(playerId);
|
|
408
411
|
if (existing)
|
|
409
|
-
return `你已经在 ${existing}
|
|
412
|
+
return `你已经在 ${existing},无需重复上桌。`;
|
|
410
413
|
const state = this.mahjongTables.get(tableKey) ?? { waiting: [], activeSessions: {} };
|
|
411
414
|
this.mahjongTables.set(tableKey, state);
|
|
412
415
|
if (Object.keys(state.activeSessions).length > 0) {
|
|
413
|
-
return
|
|
416
|
+
return `「${tableSubject}」正在游玩中(${Object.keys(state.activeSessions).length}/${this.config.mahjongTableSize ?? 4}),请等待本局结束后再上桌。`;
|
|
414
417
|
}
|
|
415
418
|
state.waiting.push({
|
|
416
419
|
playerId,
|
|
@@ -437,20 +440,19 @@ class PrismKoishiService {
|
|
|
437
440
|
}
|
|
438
441
|
return `${tableSubject}已满,麻将计费已开始。`;
|
|
439
442
|
}
|
|
440
|
-
async mahjongLeave(sender
|
|
441
|
-
const tableId = cleanText(rawTableId);
|
|
442
|
-
if (!tableId)
|
|
443
|
-
return commandUsage("mahjong_leave");
|
|
444
|
-
const tableConfig = this.mahjongTableConfigs().get(tableId);
|
|
445
|
-
const tableKey = tableConfig?.tableId ?? tableId;
|
|
446
|
-
const tableSubject = tableConfig?.displayName || `${tableId} 桌`;
|
|
443
|
+
async mahjongLeave(sender) {
|
|
447
444
|
const activeResult = (await this.client.listActiveSessions());
|
|
448
445
|
this.syncMahjongTableStates((activeResult.sessions ?? []));
|
|
449
|
-
const state = this.mahjongTables.get(tableKey);
|
|
450
|
-
if (!state)
|
|
451
|
-
return `你不在 ${tableSubject}。`;
|
|
452
446
|
const player = await this.resolvePlayer(sender);
|
|
453
447
|
const playerId = String(player.id ?? "");
|
|
448
|
+
const tableKey = this.mahjongTableForPlayer(playerId);
|
|
449
|
+
if (!tableKey)
|
|
450
|
+
return "你当前未在任何麻将桌上。";
|
|
451
|
+
const tableConfig = uniqueMahjongConfigs(this.mahjongTableConfigs()).find((table) => table.tableId === tableKey);
|
|
452
|
+
const tableSubject = tableConfig?.displayName || tableKey;
|
|
453
|
+
const state = this.mahjongTables.get(tableKey);
|
|
454
|
+
if (!state)
|
|
455
|
+
return "你当前未在任何麻将桌上。";
|
|
454
456
|
const waitingBefore = state.waiting.length;
|
|
455
457
|
state.waiting = state.waiting.filter((seat) => seat.playerId !== playerId);
|
|
456
458
|
if (state.waiting.length !== waitingBefore) {
|
|
@@ -458,11 +460,31 @@ class PrismKoishiService {
|
|
|
458
460
|
}
|
|
459
461
|
const sessionId = state.activeSessions[playerId];
|
|
460
462
|
if (!sessionId)
|
|
461
|
-
return
|
|
463
|
+
return "你当前未在任何麻将桌上。";
|
|
462
464
|
delete state.activeSessions[playerId];
|
|
463
465
|
await this.client.stopSessionByIdentity(this.identity(sender), sessionId);
|
|
464
466
|
return `已离开 ${tableSubject},麻将计费已停止。`;
|
|
465
467
|
}
|
|
468
|
+
async listMahjongTables() {
|
|
469
|
+
const activeResult = (await this.client.listActiveSessions());
|
|
470
|
+
this.syncMahjongTableStates((activeResult.sessions ?? []));
|
|
471
|
+
const tableSize = this.config.mahjongTableSize ?? 4;
|
|
472
|
+
const tables = uniqueMahjongConfigs(this.mahjongTableConfigs());
|
|
473
|
+
if (tables.length === 0)
|
|
474
|
+
return "当前没有配置任何麻将机。";
|
|
475
|
+
const lines = [`🀄️ 麻将机列表(${tables.length} 台)`];
|
|
476
|
+
for (const table of tables) {
|
|
477
|
+
const state = this.mahjongTables.get(table.tableId) ?? { waiting: [], activeSessions: {} };
|
|
478
|
+
const activeCount = Object.keys(state.activeSessions).length;
|
|
479
|
+
const waitingCount = state.waiting.length;
|
|
480
|
+
const statuses = [
|
|
481
|
+
...(activeCount > 0 ? [`游玩中 ${activeCount}/${tableSize}`] : []),
|
|
482
|
+
...(waitingCount > 0 ? [`等位 ${waitingCount}/${tableSize}`] : []),
|
|
483
|
+
];
|
|
484
|
+
lines.push(`- ${table.displayName}|别名:${table.aliases.length > 0 ? table.aliases.join("、") : "无"}|${statuses.join(";") || "空闲"}`);
|
|
485
|
+
}
|
|
486
|
+
return lines.join("\n");
|
|
487
|
+
}
|
|
466
488
|
async billing(sender) {
|
|
467
489
|
const result = (await this.client.previewCheckoutByIdentity(this.identity(sender)));
|
|
468
490
|
return this.formatCheckoutPreview(result, sender);
|