koishi-plugin-prism 0.1.13 → 0.1.15
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 +2 -2
- package/lib/index.js +65 -28
- 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>` 以及便捷的 `/上桌` / `/下桌` 控制。`上桌` 仅允许已通过 `login`/`入场` 开启默认入场会话的玩家使用。`list`
|
|
11
|
+
* 🀄 **麻将桌位集成**:包含 `/mahjong <tableId>` 以及便捷的 `/上桌` / `/下桌` 控制。`上桌` 仅允许已通过 `login`/`入场` 开启默认入场会话的玩家使用。`list` 按 session 标签分组:有非音乐游戏 session 的玩家归入最新的非音乐标签,纯音乐玩家归入音乐标签;麻将桌额外显示当前人数和容量。已开局桌位会从后端活跃 session 自动恢复,未满桌候座仍由机器人进程暂存,重启后不会保留。
|
|
12
12
|
* 🔌 **硬件设备状态与电源管理**:可直接在聊天中查看设备状态(`/show`)、远程开启/关闭电源(`/on`、`/off`)、远程投币(`/coin`)和模拟刷卡(`/scan`)。
|
|
13
13
|
* 🎟️ **礼物兑换码**:使用 `/redeem <code>` 兑换系统发放的福利礼包。
|
|
14
14
|
* 🛠️ **管理员快捷指令**:允许管理员为指定平台用户增加或扣除余额,并覆盖结账金额后立即结账。
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* `billing` - 预览当前玩家本场计费的消费费用。
|
|
47
47
|
* `wallet` - 查看当前玩家的钱包余额。
|
|
48
48
|
* `items` - 查看当前玩家持有的道具或资产。
|
|
49
|
-
* `list` -
|
|
49
|
+
* `list` - 查看当前在线/在店游玩玩家的列表,按 session 标签分组并对同一玩家去重;存在非音乐游戏 session 时取最新的非音乐标签,麻将桌显示当前人数和容量。已开局桌位由后端 session 恢复;未满桌候座由机器人进程暂存,机器人重启后不会保留。
|
|
50
50
|
* `show [deviceId]` - 查看设备电源与连接状态。
|
|
51
51
|
* `history` - 查看自己的历史游玩记录。
|
|
52
52
|
* `lock` - 发送开门指令。
|
package/lib/index.js
CHANGED
|
@@ -396,6 +396,7 @@ class PrismKoishiService {
|
|
|
396
396
|
const playerId = String(player.id ?? "");
|
|
397
397
|
const activeResult = (await this.client.listActiveSessions());
|
|
398
398
|
const activeSessions = (activeResult.sessions ?? []);
|
|
399
|
+
this.syncMahjongTableStates(activeSessions);
|
|
399
400
|
if (!this.hasEntrySession(playerId, activeSessions)) {
|
|
400
401
|
return "请先入场后再上桌。";
|
|
401
402
|
}
|
|
@@ -439,6 +440,8 @@ class PrismKoishiService {
|
|
|
439
440
|
const tableConfig = this.mahjongTableConfigs().get(tableId);
|
|
440
441
|
const tableKey = tableConfig?.tableId ?? tableId;
|
|
441
442
|
const tableSubject = tableConfig?.displayName || `${tableId} 桌`;
|
|
443
|
+
const activeResult = (await this.client.listActiveSessions());
|
|
444
|
+
this.syncMahjongTableStates((activeResult.sessions ?? []));
|
|
442
445
|
const state = this.mahjongTables.get(tableKey);
|
|
443
446
|
if (!state)
|
|
444
447
|
return `你不在 ${tableSubject}。`;
|
|
@@ -567,6 +570,7 @@ class PrismKoishiService {
|
|
|
567
570
|
async listActiveSessions(sender) {
|
|
568
571
|
const result = (await this.client.listActiveSessions());
|
|
569
572
|
const sessions = (result?.sessions ?? []);
|
|
573
|
+
this.syncMahjongTableStates(sessions);
|
|
570
574
|
const tableByLabel = new Map(uniqueMahjongConfigs(this.mahjongTableConfigs()).map((table) => [
|
|
571
575
|
mahjongSessionLabel(table, this.config.mahjongLabelPrefix ?? "麻将桌"),
|
|
572
576
|
table,
|
|
@@ -650,41 +654,44 @@ class PrismKoishiService {
|
|
|
650
654
|
|| "未知玩家";
|
|
651
655
|
}
|
|
652
656
|
async buildPlayerGroups(players, tableByLabel) {
|
|
653
|
-
const groups = {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
};
|
|
657
|
-
const groupForTable = new Map(groups.mahjong.map((group) => [group.table.tableId, group]));
|
|
657
|
+
const groups = { groups: [] };
|
|
658
|
+
const groupByLabel = new Map();
|
|
659
|
+
const musicLabel = this.config.loginSessionLabel?.trim() || "音游区间";
|
|
658
660
|
for (const player of players.values()) {
|
|
659
661
|
player.displayName = await this.displayNameForPlayer(player);
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
662
|
+
const nonMusic = player.sessions.filter((session) => Boolean(session.label) && session.label !== musicLabel);
|
|
663
|
+
const source = nonMusic.length > 0 ? nonMusic : player.sessions;
|
|
664
|
+
const selected = source.reduce((latest, session) => !latest || sessionStartedAt(session) > sessionStartedAt(latest) ? session : latest, undefined);
|
|
665
|
+
const label = selected?.label || musicLabel;
|
|
666
|
+
let group = groupByLabel.get(label);
|
|
667
|
+
if (!group) {
|
|
668
|
+
group = { label, table: tableByLabel.get(label), players: [] };
|
|
669
|
+
groupByLabel.set(label, group);
|
|
670
|
+
groups.groups.push(group);
|
|
668
671
|
}
|
|
672
|
+
group.players.push(player);
|
|
669
673
|
}
|
|
670
674
|
return groups;
|
|
671
675
|
}
|
|
672
676
|
mergeWaitingSeats(groups) {
|
|
673
677
|
for (const [tableId, state] of this.mahjongTables) {
|
|
674
|
-
const
|
|
675
|
-
if (!
|
|
678
|
+
const table = this.mahjongTableConfigs().get(tableId);
|
|
679
|
+
if (!table)
|
|
676
680
|
continue;
|
|
681
|
+
const label = mahjongSessionLabel(table, this.config.mahjongLabelPrefix ?? "麻将桌");
|
|
682
|
+
let group = groups.groups.find((candidate) => candidate.label === label);
|
|
683
|
+
if (!group) {
|
|
684
|
+
group = { label, table, players: [] };
|
|
685
|
+
groups.groups.push(group);
|
|
686
|
+
}
|
|
677
687
|
for (const seat of state.waiting) {
|
|
678
688
|
if (group.players.some((player) => player.playerId === seat.playerId))
|
|
679
689
|
continue;
|
|
680
|
-
const existing =
|
|
681
|
-
.flat()
|
|
690
|
+
const existing = groups.groups.flatMap((candidate) => candidate.players)
|
|
682
691
|
.find((player) => player.playerId === seat.playerId);
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (candidate !== group) {
|
|
692
|
+
for (const candidate of groups.groups) {
|
|
693
|
+
if (candidate !== group)
|
|
686
694
|
candidate.players = candidate.players.filter((player) => player.playerId !== seat.playerId);
|
|
687
|
-
}
|
|
688
695
|
}
|
|
689
696
|
group.players.push(existing ?? { playerId: seat.playerId, sessions: [], displayName: seat.displayName });
|
|
690
697
|
}
|
|
@@ -711,6 +718,32 @@ class PrismKoishiService {
|
|
|
711
718
|
mahjongTableConfigs() {
|
|
712
719
|
return parseMahjongTables(this.config.mahjongTables ?? "", this.config.mahjongLabelPrefix ?? "麻将桌");
|
|
713
720
|
}
|
|
721
|
+
syncMahjongTableStates(sessions) {
|
|
722
|
+
const tables = uniqueMahjongConfigs(this.mahjongTableConfigs());
|
|
723
|
+
const tableByLabel = new Map(tables.map((table) => [
|
|
724
|
+
mahjongSessionLabel(table, this.config.mahjongLabelPrefix ?? "麻将桌"),
|
|
725
|
+
table,
|
|
726
|
+
]));
|
|
727
|
+
const activeByTable = new Map();
|
|
728
|
+
for (const session of sessions) {
|
|
729
|
+
const table = tableByLabel.get(session.label ?? "");
|
|
730
|
+
const playerId = String(session.playerId ?? "");
|
|
731
|
+
const sessionId = String(session.id ?? "");
|
|
732
|
+
if (!table || !playerId || !sessionId)
|
|
733
|
+
continue;
|
|
734
|
+
const active = activeByTable.get(table.tableId) ?? {};
|
|
735
|
+
active[playerId] = sessionId;
|
|
736
|
+
activeByTable.set(table.tableId, active);
|
|
737
|
+
}
|
|
738
|
+
for (const table of tables) {
|
|
739
|
+
const state = this.mahjongTables.get(table.tableId) ?? { waiting: [], activeSessions: {} };
|
|
740
|
+
state.activeSessions = activeByTable.get(table.tableId) ?? {};
|
|
741
|
+
state.waiting = state.waiting.filter((seat) => !state.activeSessions[seat.playerId]);
|
|
742
|
+
if (state.waiting.length > 0 || Object.keys(state.activeSessions).length > 0 || this.mahjongTables.has(table.tableId)) {
|
|
743
|
+
this.mahjongTables.set(table.tableId, state);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
714
747
|
async resolvePlayer(sender) {
|
|
715
748
|
return (await this.client.resolveOrRegisterIdentity(this.identity(sender)));
|
|
716
749
|
}
|
|
@@ -954,17 +987,21 @@ function groupSessionsByPlayer(sessions) {
|
|
|
954
987
|
}
|
|
955
988
|
return players;
|
|
956
989
|
}
|
|
990
|
+
function sessionStartedAt(session) {
|
|
991
|
+
const value = Date.parse(session.startedAt ?? "");
|
|
992
|
+
return Number.isFinite(value) ? value : 0;
|
|
993
|
+
}
|
|
957
994
|
function formatPlayerGroups(groups, tableSize, mahjongLabelPrefix) {
|
|
958
|
-
const
|
|
959
|
-
const total =
|
|
995
|
+
const populatedGroups = groups.groups.filter((group) => group.players.length > 0);
|
|
996
|
+
const total = new Set(populatedGroups.flatMap((group) => group.players.map((player) => player.playerId))).size;
|
|
960
997
|
if (total === 0)
|
|
961
998
|
return "🫥 窝里目前没有玩家呢";
|
|
962
999
|
const lines = [`[总计 ${total} 人]`];
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
lines.push(`${
|
|
1000
|
+
for (const group of populatedGroups) {
|
|
1001
|
+
const heading = group.table
|
|
1002
|
+
? `${formatMahjongTableLabel(group.table, mahjongLabelPrefix)} ( ${group.players.length}/${tableSize} )`
|
|
1003
|
+
: `${group.label} ( ${group.players.length}人 )`;
|
|
1004
|
+
lines.push(`${heading}:\n${formatPlayerNames(group.players)}`);
|
|
968
1005
|
}
|
|
969
1006
|
return lines.join("\n");
|
|
970
1007
|
}
|