@hieuzest/koishi-plugin-mahjongpub 0.1.22 → 0.2.1

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/lib/api.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Context, Dict, Quester } from 'koishi';
2
2
  export interface Team {
3
3
  pw?: string;
4
- tid: string;
5
- cid?: string;
4
+ tid: number;
5
+ cid?: number;
6
6
  name: string;
7
7
  players: string[];
8
8
  qq?: string[];
@@ -12,8 +12,8 @@ export declare class TeamAdmin implements Team {
12
12
  options: {
13
13
  endpoint: string;
14
14
  };
15
- tid: string;
16
- cid: string;
15
+ tid: number;
16
+ cid: number;
17
17
  name: string;
18
18
  description: string;
19
19
  players: string[];
@@ -28,16 +28,16 @@ export declare class TeamAdmin implements Team {
28
28
  }
29
29
  declare namespace TeamAdmin {
30
30
  interface Payload {
31
- tid: string;
31
+ tid: number;
32
32
  t_name: string;
33
33
  t_player?: string;
34
34
  t_sub?: string;
35
35
  t_pw: string;
36
36
  t_join_time?: string;
37
37
  t_join_ip?: string;
38
- t_type?: string;
38
+ t_type?: number;
39
39
  t_ps?: string;
40
- cid: string;
40
+ cid: number;
41
41
  qq?: string;
42
42
  ls_ip?: string;
43
43
  ls_time?: string;
@@ -45,19 +45,22 @@ declare namespace TeamAdmin {
45
45
  }
46
46
  }
47
47
  export declare class ContestAdmin {
48
- cid: string;
48
+ cid: number;
49
49
  pw: string;
50
50
  options: {
51
51
  endpoint: string;
52
52
  };
53
53
  name: string;
54
54
  teams: Dict<Team>;
55
+ round: number;
55
56
  http: Quester;
56
57
  raw?: ContestAdmin.Payload;
57
- constructor(ctx: Context, cid: string, pw: string, options: {
58
+ constructor(ctx: Context, cid: number, pw: string, options: {
58
59
  endpoint: string;
59
60
  });
60
61
  read(force?: boolean): Promise<void>;
62
+ getRounds(): Promise<Record<number, Record<number, Contest.Round>>>;
63
+ getRecords(round: number): Promise<Contest.Records>;
61
64
  getRoundRecord(round: number, cls: number): Promise<ContestAdmin.MatchRoundRecord>;
62
65
  updateRoundRecord(record: ContestAdmin.MatchRoundRecord): Promise<string>;
63
66
  }
@@ -67,13 +70,13 @@ declare namespace ContestAdmin {
67
70
  c_name: string;
68
71
  c_gonggao: string;
69
72
  c_pad: string;
70
- c_round: string;
71
- c_s_po: string;
72
- c_sub_t: string;
73
- cid: string;
74
- r_type: string;
75
- t_max: string;
76
- t_sub: string;
73
+ c_round: number;
74
+ c_s_po: number;
75
+ c_sub_t: number;
76
+ cid: number;
77
+ r_type: number;
78
+ t_max: number;
79
+ t_sub: number;
77
80
  t_type: string;
78
81
  };
79
82
  c_team: TeamAdmin.Payload[];
@@ -88,10 +91,85 @@ declare namespace ContestAdmin {
88
91
  logurl: string;
89
92
  }
90
93
  interface MatchRoundRecord {
91
- cid: string;
94
+ cid: number;
92
95
  cls: number;
93
96
  rnd: number;
94
97
  matches: MatchRecord[];
95
98
  }
99
+ interface RoundPayload {
100
+ clsmark: string;
101
+ code: string;
102
+ rid: number;
103
+ round: number;
104
+ t_class: number;
105
+ tid1: number;
106
+ tid2: number;
107
+ tid3: number;
108
+ tid4: number;
109
+ }
110
+ interface Round {
111
+ rid: number;
112
+ round: number;
113
+ cls: number;
114
+ code: string;
115
+ tids: number[];
116
+ }
117
+ }
118
+ declare namespace Contest {
119
+ interface RoundPayload {
120
+ clsmark: string;
121
+ code: string;
122
+ rid: number;
123
+ round: number;
124
+ t_class: number;
125
+ tid1: number;
126
+ tid2: number;
127
+ tid3: number;
128
+ tid4: number;
129
+ }
130
+ interface Round {
131
+ rid: number;
132
+ round: number;
133
+ cls: number;
134
+ code: string;
135
+ tids: number[];
136
+ }
137
+ interface MatchPayload {
138
+ inx: number;
139
+ cid: number;
140
+ rowi: number;
141
+ name1: string;
142
+ num1: number;
143
+ name2: string;
144
+ num2: number;
145
+ name3: string;
146
+ num3: number;
147
+ name4: string;
148
+ num4: number;
149
+ url: string;
150
+ pint1: number;
151
+ pint2: number;
152
+ pint3: number;
153
+ pint4: number;
154
+ rid: number;
155
+ round: number;
156
+ i_time: string;
157
+ }
158
+ interface Match {
159
+ inx: number;
160
+ cid: number;
161
+ rowi: number;
162
+ players: {
163
+ name: string;
164
+ num: number;
165
+ point: number;
166
+ }[];
167
+ url: string;
168
+ rid: number;
169
+ round: number;
170
+ time: string;
171
+ }
172
+ type RecordsPayload = Record<number, MatchPayload[]>;
173
+ type Records = Record<number, Match[]>;
96
174
  }
97
175
  export {};
package/lib/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- import { Context, Dict, Schema } from 'koishi';
1
+ import { Context, Dict, Schema, Service } from 'koishi';
2
2
  import { ContestAdmin, TeamAdmin } from './api';
3
3
  declare module 'koishi' {
4
+ interface Context {
5
+ mahjongpub: MahjongPub;
6
+ }
4
7
  interface User {
5
8
  'mahjongpub/bind-team': string;
6
9
  'mahjongpub/bind-teams': Dict<string>;
@@ -17,9 +20,9 @@ declare module '@koishijs/cache' {
17
20
  teams: TeamAdmin;
18
21
  }
19
22
  }
20
- export declare class MahjongPub {
21
- private ctx;
22
- private config;
23
+ export declare class MahjongPub extends Service {
24
+ protected ctx: Context;
25
+ config: MahjongPub.Config;
23
26
  teams: Dict<TeamAdmin>;
24
27
  contests: Dict<ContestAdmin>;
25
28
  constructor(ctx: Context, config: MahjongPub.Config);
package/lib/index.js CHANGED
@@ -23,18 +23,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
23
23
  // src/locales/zh-CN.yml
24
24
  var require_zh_CN = __commonJS({
25
25
  "src/locales/zh-CN.yml"(exports2, module2) {
26
- module2.exports = { commands: { "mahjongpub.team.bind": { description: "绑定队伍", messages: { failed: "绑定失败", success: "成功: [{cid}:{tid}] {name}" } }, "mahjongpub.team.unbind": { description: "解除绑定", messages: { notbind: "未绑定", success: "成功" } }, "mahjongpub.team.password": { description: "查看密码", messages: { notbind: "未绑定", output: "密码: {0}" } }, "mahjongpub.team.stats": { description: "查看队伍信息", messages: { notbind: "未绑定", failed: "失败", output: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.team.desc": { description: "队伍简介", messages: { notbind: "未绑定", failed: "失败", output: "- [{cid}:{tid}] {name}\n{description}\n" } }, "mahjongpub.team.logo": { description: "队伍头像", messages: { notbind: "未绑定", failed: "失败" } }, "mahjongpub.team.add": { description: "添加队员", messages: { notbind: "未绑定", failed: "失败", success: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.team.remove": { description: "删除队员", messages: { notbind: "未绑定", failed: "失败", success: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.team.swap": { description: "交换队员", messages: { notbind: "未绑定", failed: "失败", invalid: "参数错误", "wide-range": "范围过大", success: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.contest.bind": { description: "绑定比赛", messages: { failed: "绑定失败", success: "成功: [{cid}] {name}" } }, "mahjongpub.contest.record.get": { description: "查询比赛记录", messages: { failed: "查询失败" } }, "mahjongpub.contest.record.update": { description: "修改比赛记录", messages: { failed: "修改失败" } }, "mahjongpub.database.record.get": { description: "查询比赛记录", messages: { failed: "查询失败" } }, "mahjongpub.database.record.update": { description: "修改比赛记录", messages: { success: "修改成功", failed: "修改失败", unmodified: "未修改" } } } };
26
+ module2.exports = { commands: { "mahjongpub.team.bind": { description: "绑定队伍", messages: { failed: "绑定失败", success: "成功: [{cid}:{tid}] {name}" } }, "mahjongpub.team.unbind": { description: "解除绑定", messages: { notbind: "未绑定", success: "成功" } }, "mahjongpub.team.password": { description: "查看密码", messages: { notbind: "未绑定", output: "密码: {0}" } }, "mahjongpub.team.stats": { description: "查看队伍信息", messages: { notbind: "未绑定", failed: "失败", output: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.team.desc": { description: "队伍简介", messages: { notbind: "未绑定", failed: "失败", output: "- [{cid}:{tid}] {name}\n{description}\n" } }, "mahjongpub.team.logo": { description: "队伍头像", messages: { notbind: "未绑定", failed: "失败" } }, "mahjongpub.team.add": { description: "添加队员", messages: { notbind: "未绑定", failed: "失败", success: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.team.remove": { description: "删除队员", messages: { notbind: "未绑定", failed: "失败", success: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.team.swap": { description: "交换队员", messages: { notbind: "未绑定", failed: "失败", invalid: "参数错误", "wide-range": "范围过大", success: "- [{cid}:{tid}] {name}\n{players.map((p, i) => '' + (i+1) + ': ' + p).join('\\n')}\n" } }, "mahjongpub.contest.bind": { description: "绑定比赛", messages: { failed: "绑定失败", success: "成功: [{cid}] {name}" } }, "mahjongpub.contest.password": { description: "查看密码", messages: { failed: "未绑定", success: "[{0}] {1}\n" } }, "mahjongpub.contest.record.get": { description: "查询比赛记录", messages: { failed: "查询失败" } }, "mahjongpub.contest.record.update": { description: "修改比赛记录", messages: { failed: "修改失败" } }, "mahjongpub.database.record.get": { description: "查询比赛记录", messages: { failed: "查询失败" } }, "mahjongpub.database.record.update": { description: "修改比赛记录", messages: { success: "修改成功", failed: "修改失败", unmodified: "未修改" } } } };
27
27
  }
28
28
  });
29
29
 
30
30
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
33
- MahjongPub: () => MahjongPub,
34
- default: () => src_default
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ MahjongPub: () => MahjongPub2,
34
+ default: () => index_default
35
35
  });
36
- module.exports = __toCommonJS(src_exports);
37
- var import_koishi = require("koishi");
36
+ module.exports = __toCommonJS(index_exports);
37
+ var import_koishi3 = require("koishi");
38
38
 
39
39
  // src/api.ts
40
40
  var TeamAdmin = class {
@@ -79,7 +79,7 @@ var TeamAdmin = class {
79
79
  posttype: "update",
80
80
  t_pw: this.pw,
81
81
  t_ps: this.description,
82
- cid: this.cid,
82
+ cid: String(this.cid),
83
83
  linkurl: this.img,
84
84
  team_type: this.ready ? "1" : "0",
85
85
  postdata: this.players.join("\n")
@@ -103,13 +103,14 @@ var ContestAdmin = class {
103
103
  }
104
104
  name;
105
105
  teams;
106
+ round;
106
107
  http;
107
108
  raw;
108
109
  async read(force = false) {
109
110
  if (this.teams && !force) return;
110
111
  const payload = {
111
112
  posttype: "login",
112
- cid: this.cid,
113
+ cid: String(this.cid),
113
114
  c_pw: this.pw
114
115
  };
115
116
  const form = new FormData();
@@ -120,6 +121,7 @@ var ContestAdmin = class {
120
121
  if (!data?.c_admin?.cid) throw new Error("failed to read contest info");
121
122
  this.raw = data;
122
123
  this.name = data.c_admin.c_name;
124
+ this.round = data.c_admin.c_round;
123
125
  this.teams = Object.fromEntries(data.c_team.map((team) => [team.tid, {
124
126
  tid: team.tid,
125
127
  cid: this.cid,
@@ -129,10 +131,58 @@ var ContestAdmin = class {
129
131
  pw: team.t_pw
130
132
  }]));
131
133
  }
134
+ async getRounds() {
135
+ const data = await this.http.get("/api/data.php", {
136
+ params: {
137
+ t: "class",
138
+ cid: this.cid
139
+ }
140
+ });
141
+ const res = {};
142
+ data.forEach((round) => {
143
+ (res[round.round] ??= {})[round.t_class] = {
144
+ rid: round.rid,
145
+ round: round.round,
146
+ cls: round.t_class,
147
+ code: round.code,
148
+ tids: [round.tid1, round.tid2, round.tid3, round.tid4].filter(Boolean)
149
+ };
150
+ });
151
+ return res;
152
+ }
153
+ async getRecords(round) {
154
+ const data = await this.http.get("/api/data.php", {
155
+ params: {
156
+ t: "c_data",
157
+ cid: this.cid,
158
+ r: round
159
+ }
160
+ });
161
+ const res = {};
162
+ Object.entries(data).forEach(([rid, rs]) => {
163
+ if (!rs.length) return;
164
+ res[rid] = rs.map((x) => ({
165
+ inx: x.inx,
166
+ cid: x.cid,
167
+ rowi: x.rowi,
168
+ players: [
169
+ { name: x.name1, num: x.num1, point: x.pint1 },
170
+ { name: x.name2, num: x.num2, point: x.pint2 },
171
+ { name: x.name3, num: x.num3, point: x.pint3 },
172
+ { name: x.name4, num: x.num4, point: x.pint4 }
173
+ ],
174
+ url: x.url,
175
+ rid: x.rid,
176
+ round: x.round,
177
+ time: x.i_time
178
+ }));
179
+ });
180
+ return res;
181
+ }
132
182
  async getRoundRecord(round, cls) {
133
183
  const payload = {
134
184
  type: "login",
135
- cid: this.cid,
185
+ cid: String(this.cid),
136
186
  cls: String(cls),
137
187
  rnd: String(round)
138
188
  };
@@ -166,7 +216,7 @@ var ContestAdmin = class {
166
216
  async updateRoundRecord(record) {
167
217
  const payload = {
168
218
  type: "update",
169
- cid: this.cid,
219
+ cid: String(this.cid),
170
220
  cls: String(record.cls),
171
221
  rnd: String(record.rnd),
172
222
  pw: this.pw
@@ -189,6 +239,222 @@ var ContestAdmin = class {
189
239
  }
190
240
  };
191
241
 
242
+ // src/manager.ts
243
+ var import_koishi2 = require("koishi");
244
+ var import_lobby = require("@hieuzest/koishi-plugin-riichi-city/lobby");
245
+
246
+ // src/mahjongpub.ts
247
+ var import_koishi = require("koishi");
248
+ var import_cachetools = require("@hieuzest/cachetools");
249
+ var MahjongPub = class _MahjongPub {
250
+ constructor(ctx) {
251
+ this.ctx = ctx;
252
+ this.http = ctx.http.extend({});
253
+ }
254
+ static {
255
+ __name(this, "MahjongPub");
256
+ }
257
+ serverUri = "https://cdn.r-mj.com";
258
+ http;
259
+ async getContest(cid) {
260
+ return await this.http.get(`${this.serverUri}/api/data.php`, {
261
+ params: {
262
+ t: "admin",
263
+ cid
264
+ }
265
+ });
266
+ }
267
+ async getTeams(cid) {
268
+ return this.http.get(`${this.serverUri}/api/data.php`, {
269
+ params: {
270
+ t: "team",
271
+ cid
272
+ }
273
+ }).then((teams) => (0, import_koishi.mapValues)(teams, (team) => ({
274
+ ...team,
275
+ players: [...team.t_player?.split(/\s+/) ?? [], ...team.t_sub?.split(/\s+/) ?? []]
276
+ })));
277
+ }
278
+ async getRounds(cid) {
279
+ return Object.fromEntries(await this.http.get(`${this.serverUri}/api/data.php`, {
280
+ params: {
281
+ t: "class",
282
+ cid
283
+ }
284
+ }).then((rounds) => rounds.map((round) => [round.rid, {
285
+ ...round,
286
+ tids: [round.tid1, round.tid2, round.tid3, round.tid4]
287
+ }])));
288
+ }
289
+ async getMatches(cid, round) {
290
+ return await this.http.get(`${this.serverUri}/api/data.php`, {
291
+ params: {
292
+ t: "c_data",
293
+ cid,
294
+ r: round
295
+ }
296
+ });
297
+ }
298
+ async getAllMatches(cid) {
299
+ const contest = await this.getContest(cid);
300
+ return await this.http.get(`${this.serverUri}/api/data.php`, {
301
+ params: {
302
+ t: "multi_log",
303
+ cid,
304
+ r: [...Array(contest.c_round).keys()].map((x) => x + 1).map((x) => x.toString()).join(",")
305
+ }
306
+ });
307
+ }
308
+ static cacheOptions = {
309
+ "getContest": {
310
+ maxAge: 120
311
+ },
312
+ "getRounds": {
313
+ maxAge: 60
314
+ },
315
+ "getTeams": {
316
+ maxAge: 30
317
+ },
318
+ "getMatches": {
319
+ maxAge: 120
320
+ },
321
+ "getAllMatches": {
322
+ maxAge: 300
323
+ }
324
+ };
325
+ static new(ctx) {
326
+ return (0, import_cachetools.ttlcachedClass)(new _MahjongPub(ctx), _MahjongPub.cacheOptions);
327
+ }
328
+ };
329
+
330
+ // src/manager.ts
331
+ var ContestExtra = class {
332
+ constructor(ctx) {
333
+ this.ctx = ctx;
334
+ this.subscribers = /* @__PURE__ */ new Set();
335
+ this.stopCls = /* @__PURE__ */ new Set();
336
+ }
337
+ static {
338
+ __name(this, "ContestExtra");
339
+ }
340
+ subscribers;
341
+ stopCls;
342
+ async broadcast(msg) {
343
+ this.subscribers.forEach((channel) => this.ctx.sendMessage(channel, msg).catch(import_koishi2.noop));
344
+ }
345
+ };
346
+ var ContestManager = class {
347
+ constructor(ctx, config) {
348
+ this.ctx = ctx;
349
+ this.config = config;
350
+ this.mahjongpub = MahjongPub.new(ctx);
351
+ ctx.command("contest.manager", { authority: 3 }).action(import_koishi2.noop);
352
+ ctx.command("contest.manager.init").channelFields(["mahjongpub/bind-contest"]).option("clear", "-c").action(async ({ session, options }) => {
353
+ const cid = +(session.channel["mahjongpub/bind-contest"] || 0);
354
+ if (!cid) return "Unauthorized.";
355
+ this.extra[cid] ??= new ContestExtra(ctx);
356
+ this.extra[cid].subscribers.add(session.cid);
357
+ if (options.clear) this.extra[cid].stopCls.clear();
358
+ return "Finished.";
359
+ });
360
+ ctx.command("contest.manager.start [cls:number]").channelFields(["mahjongpub/bind-contest"]).action(async ({ session }, cls) => {
361
+ const cid = +(session.channel["mahjongpub/bind-contest"] || 0);
362
+ if (!cid) return "Unauthorized.";
363
+ this.extra[cid] ??= new ContestExtra(ctx);
364
+ this.extra[cid].subscribers.add(session.cid);
365
+ if (cls) {
366
+ this.extra[cid].stopCls.delete(cls);
367
+ await this.startMatch(cid, cls);
368
+ } else {
369
+ this.extra[cid].stopCls.clear();
370
+ const contest = await this.mahjongpub.getContest(cid);
371
+ const rounds = await this.mahjongpub.getRounds(cid);
372
+ for (const r of Object.values(rounds)) {
373
+ if (r.round !== contest.c_round) continue;
374
+ await this.startMatch(cid, r.t_class);
375
+ await (0, import_koishi2.sleep)(1e3);
376
+ }
377
+ }
378
+ });
379
+ ctx.command("contest.manager.stop [cls:number]").channelFields(["mahjongpub/bind-contest"]).action(async ({ session }, cls) => {
380
+ const cid = +(session.channel["mahjongpub/bind-contest"] || 0);
381
+ if (!cid) return "Unauthorized/";
382
+ this.extra[cid] ??= new ContestExtra(ctx);
383
+ if (cls) {
384
+ this.extra[cid].stopCls.add(cls);
385
+ } else {
386
+ const contest = await this.mahjongpub.getContest(cid);
387
+ const rounds = await this.mahjongpub.getRounds(cid);
388
+ for (const r of Object.values(rounds)) {
389
+ if (r.round !== contest.c_round) continue;
390
+ this.extra[cid].stopCls.add(r.t_class);
391
+ }
392
+ }
393
+ return "Finished.";
394
+ });
395
+ ctx.server.post("/contest-manager/finished", async (kCtx) => {
396
+ if (config.token && kCtx.request.headers.authorization !== `Bearer ${config.token}`) {
397
+ kCtx.status = 403;
398
+ return true;
399
+ }
400
+ this.onMatchFinished(kCtx.request.body);
401
+ kCtx.status = 200;
402
+ return true;
403
+ });
404
+ }
405
+ static {
406
+ __name(this, "ContestManager");
407
+ }
408
+ static inject = ["server", "mahjong", "mahjong.database", "mahjongpub", "sendMessage"];
409
+ mahjongpub;
410
+ extra = /* @__PURE__ */ Object.create(null);
411
+ async startMatch(cid, cls, timeout, tag) {
412
+ tag ||= `${cls}组`;
413
+ if (this.extra[cid]?.stopCls.has(cls)) {
414
+ this.ctx.logger.info(`try starting match ${cid}-${cls}, but stopped`);
415
+ return;
416
+ }
417
+ this.ctx.logger.info(`try starting match ${cid}-${cls}, timeout ${timeout}`);
418
+ const contest = await this.mahjongpub.getContest(cid);
419
+ const rounds = await this.mahjongpub.getRounds(cid);
420
+ const teams = await this.mahjongpub.getTeams(cid);
421
+ const [lastRecord] = await this.ctx.mahjong.database.db("scoreboard").collection("matches").find({
422
+ cid: `${cid}`,
423
+ round: +contest.c_round,
424
+ cls
425
+ }).sort({ rowi: -1 }).limit(1).toArray();
426
+ if (lastRecord && !lastRecord._finished) {
427
+ this.extra[cid]?.broadcast(`[${tag}] 发现进行中的比赛,已忽略`);
428
+ return;
429
+ }
430
+ const rowi = lastRecord ? lastRecord.rowi + 1 : 0;
431
+ const round = Object.values(rounds).find((x) => x.round === contest.c_round && x.t_class === cls);
432
+ const players = round.tids.map((tid) => teams[tid].players[rowi]);
433
+ try {
434
+ await this.ctx["zx-dhs"].startMatch(+round.code, players.map((x, i) => import_lobby.Player.fromPattern(x, lastRecord ? lastRecord.results?.[i].num : 1e5)));
435
+ this.extra[cid]?.broadcast(`[${tag}] 成功:` + players.join(","));
436
+ } catch (e) {
437
+ if (e instanceof import_lobby.PlayerNotFoundError) {
438
+ this.extra[cid]?.broadcast(`[${tag}] ` + e.message + ` <timeout=${timeout}>`);
439
+ if (timeout < 600) this.ctx.setTimeout(() => this.startMatch(cid, cls, timeout + 30), 30 * 1e3);
440
+ } else {
441
+ throw e;
442
+ }
443
+ }
444
+ }
445
+ async onMatchFinished(record) {
446
+ this.extra[record.cid]?.broadcast(`对局完成:
447
+ ` + record.results.map((x) => `${x.name} ${x.num}`).join("\n"));
448
+ await (0, import_koishi2.sleep)(2e3);
449
+ return this.startMatch(+record.cid, record.cls);
450
+ }
451
+ };
452
+ ((ContestManager2) => {
453
+ ContestManager2.Config = import_koishi2.Schema.object({
454
+ token: import_koishi2.Schema.string()
455
+ });
456
+ })(ContestManager || (ContestManager = {}));
457
+
192
458
  // src/index.ts
193
459
  function parsePlatform(target) {
194
460
  const index = target.startsWith("sandbox:") ? target.lastIndexOf(":") : target.indexOf(":");
@@ -197,10 +463,12 @@ function parsePlatform(target) {
197
463
  return [platform, id];
198
464
  }
199
465
  __name(parsePlatform, "parsePlatform");
200
- var MahjongPub = class {
466
+ var MahjongPub2 = class extends import_koishi3.Service {
201
467
  constructor(ctx, config) {
468
+ super(ctx, "mahjongpub", true);
202
469
  this.ctx = ctx;
203
470
  this.config = config;
471
+ ctx.plugin(ContestManager);
204
472
  ctx.i18n.define("zh-CN", require_zh_CN());
205
473
  ctx.model.extend("user", {
206
474
  "mahjongpub/bind-team": "string",
@@ -277,7 +545,7 @@ var MahjongPub = class {
277
545
  const team = await this.getTeam(pw);
278
546
  try {
279
547
  await team.read();
280
- if (!img) return import_koishi.h.image(team.img);
548
+ if (!img) return import_koishi3.h.image(team.img);
281
549
  const url = await ctx.assets.upload("https://koishi.chat/logo.png", `${session.id}-${session.timestamp}.png`);
282
550
  team.img = url;
283
551
  return await team.write();
@@ -376,7 +644,17 @@ var MahjongPub = class {
376
644
  return session.text(".failed");
377
645
  }
378
646
  });
379
- ctx.command("mahjongpub.contest.bind <cid:string> <cpw:string>", { authority: 4 }).option("channel", "-c <channel:channel>").channelFields(["mahjongpub/bind-contest", "mahjongpub/bind-contestpw"]).action(async ({ session, options }, cid, cpw) => {
647
+ ctx.command("mahjongpub.contest.bind <cid:string> [cpw:string]", { authority: 4 }).option("channel", "-c <channel:channel>").option("only", "-o").channelFields(["mahjongpub/bind-contest", "mahjongpub/bind-contestpw"]).action(async ({ session, options }, cid, cpw) => {
648
+ if (options.only) {
649
+ if (options.channel) {
650
+ ctx.database.setChannel(...parsePlatform(options.channel), {
651
+ "mahjongpub/bind-contest": cid
652
+ });
653
+ } else {
654
+ session.channel["mahjongpub/bind-contest"] = cid;
655
+ }
656
+ return "Finished.";
657
+ }
380
658
  const contest = await this.getContest(cid, cpw);
381
659
  try {
382
660
  await contest.read();
@@ -395,12 +673,32 @@ var MahjongPub = class {
395
673
  }
396
674
  return session.text(".success", contest);
397
675
  });
676
+ ctx.command("mahjongpub.contest.password", { authority: 4 }).option("channel", "-c <channel:channel>").channelFields(["mahjongpub/bind-contest", "mahjongpub/bind-contestpw"]).action(async ({ session }) => {
677
+ const [cid, cpw] = [session.channel["mahjongpub/bind-contest"], session.channel["mahjongpub/bind-contestpw"]];
678
+ if (cid) return session.text(".success", [cid, cpw]);
679
+ else return session.text(".failed");
680
+ });
681
+ ctx.command("mahjongpub.contest.round [round:natural]", { authority: 3 }).channelFields(["mahjongpub/bind-contest", "mahjongpub/bind-contestpw"]).action(async ({ session }, round) => {
682
+ const [cid, cpw] = [session.channel["mahjongpub/bind-contest"], session.channel["mahjongpub/bind-contestpw"]];
683
+ const contest = await this.getContest(cid, cpw);
684
+ try {
685
+ const rounds = await contest.getRounds();
686
+ const clss = rounds[round || contest.round];
687
+ return Object.entries(clss).map(([i, r]) => [
688
+ `[${i}]`,
689
+ r.tids.map((tid) => `${contest.teams[tid].name}`).join(" / ")
690
+ ].join(" ")).join("\n");
691
+ } catch (e) {
692
+ ctx.logger.warn(e);
693
+ return session.text(".failed");
694
+ }
695
+ });
398
696
  ctx.command("mahjongpub.contest.record.get <round:natural> <cls:natural>", { authority: 3 }).option("rowi", "-i <rowi:integer>").channelFields(["mahjongpub/bind-contest", "mahjongpub/bind-contestpw"]).action(async ({ session, options }, round, cls) => {
399
697
  const [cid, cpw] = [session.channel["mahjongpub/bind-contest"], session.channel["mahjongpub/bind-contestpw"]];
400
698
  const contest = await this.getContest(cid, cpw);
401
699
  try {
402
700
  const record = await contest.getRoundRecord(round, cls);
403
- if ((0, import_koishi.isNullable)(options.rowi)) {
701
+ if ((0, import_koishi3.isNullable)(options.rowi)) {
404
702
  return record.matches.map((match, i) => `[${i}] ` + match.players.map((x) => `${x.name} ${x.point}`).join(" / ")).join("\n");
405
703
  } else if (record.matches.at(options.rowi ?? -1)) {
406
704
  return [
@@ -523,29 +821,29 @@ var MahjongPub = class {
523
821
  if (this.ctx.get("cache")) {
524
822
  let res2 = await this.ctx.cache.get("contests", cid);
525
823
  if (res2) return res2;
526
- res2 = new ContestAdmin(this.ctx, cid, pw, this.config);
824
+ res2 = new ContestAdmin(this.ctx, +cid, pw, this.config);
527
825
  await res2.read();
528
826
  await this.ctx.cache.set("contests", cid, res2, 1e3 * 600);
529
827
  return res2;
530
828
  }
531
- const res = new ContestAdmin(this.ctx, cid, pw, this.config);
829
+ const res = new ContestAdmin(this.ctx, +cid, pw, this.config);
532
830
  await res.read();
533
831
  return res;
534
832
  }
535
833
  };
536
- ((MahjongPub2) => {
537
- MahjongPub2.inject = {
834
+ ((MahjongPub3) => {
835
+ MahjongPub3.inject = {
538
836
  required: ["database"],
539
837
  optional: ["assets", "cache", "mahjong", "mahjong.database"]
540
838
  };
541
- MahjongPub2.Config = import_koishi.Schema.object({
542
- informNotbind: import_koishi.Schema.boolean().default(false),
543
- endpoint: import_koishi.Schema.string().default("https://cdn.r-mj.com/"),
544
- batchInterval: import_koishi.Schema.natural().default(300),
545
- batchCount: import_koishi.Schema.natural().default(10)
839
+ MahjongPub3.Config = import_koishi3.Schema.object({
840
+ informNotbind: import_koishi3.Schema.boolean().default(false),
841
+ endpoint: import_koishi3.Schema.string().default("https://cdn.r-mj.com/"),
842
+ batchInterval: import_koishi3.Schema.natural().default(300),
843
+ batchCount: import_koishi3.Schema.natural().default(10)
546
844
  });
547
- })(MahjongPub || (MahjongPub = {}));
548
- var src_default = MahjongPub;
845
+ })(MahjongPub2 || (MahjongPub2 = {}));
846
+ var index_default = MahjongPub2;
549
847
  // Annotate the CommonJS export names for ESM import in node:
550
848
  0 && (module.exports = {
551
849
  MahjongPub
@@ -0,0 +1,87 @@
1
+ import { Context, Dict } from 'koishi';
2
+ export declare class MahjongPub {
3
+ ctx: Context;
4
+ private serverUri;
5
+ private http;
6
+ constructor(ctx: Context);
7
+ getContest(cid: number): Promise<MahjongPub.Contest>;
8
+ getTeams(cid: number): Promise<Dict<MahjongPub.Team>>;
9
+ getRounds(cid: number): Promise<Dict<MahjongPub.Round>>;
10
+ getMatches(cid: number, round: number): Promise<Dict<MahjongPub.Match[]>>;
11
+ getAllMatches(cid: number): Promise<Dict<MahjongPub.Match[]>>;
12
+ static cacheOptions: {
13
+ readonly getContest: {
14
+ readonly maxAge: 120;
15
+ };
16
+ readonly getRounds: {
17
+ readonly maxAge: 60;
18
+ };
19
+ readonly getTeams: {
20
+ readonly maxAge: 30;
21
+ };
22
+ readonly getMatches: {
23
+ readonly maxAge: 120;
24
+ };
25
+ readonly getAllMatches: {
26
+ readonly maxAge: 300;
27
+ };
28
+ };
29
+ static new(ctx: Context): import("@hieuzest/cachetools").CachedClass<MahjongPub, "getContest" | "getRounds" | "getTeams" | "getMatches" | "getAllMatches", "_">;
30
+ }
31
+ export declare namespace MahjongPub {
32
+ export interface Team {
33
+ tid: number;
34
+ cid: string;
35
+ t_name: string;
36
+ t_ps: string;
37
+ t_player?: string;
38
+ t_sub?: string;
39
+ img: string;
40
+ t_type: number;
41
+ players: string[];
42
+ }
43
+ export interface Contest {
44
+ cid: number;
45
+ c_name: string;
46
+ c_round: number;
47
+ c_gonggao: string;
48
+ c_pad: string;
49
+ t_type: string;
50
+ }
51
+ export interface Round {
52
+ rid: number;
53
+ round: number;
54
+ t_class: number;
55
+ code: string;
56
+ tid1: number;
57
+ tid2: number;
58
+ tid3: number;
59
+ tid4: number;
60
+ tids: number[];
61
+ }
62
+ export interface MatchBrief {
63
+ inx: number;
64
+ cid: number;
65
+ rid: number;
66
+ round: number;
67
+ rowi: number;
68
+ i_time: string;
69
+ url: string;
70
+ }
71
+ type Stringiable = string | number;
72
+ type Suffixed<K, P extends Stringiable> = K extends string ? `${K}${P}` : never;
73
+ type SuffixRemoved<PK, P extends Stringiable> = PK extends Suffixed<infer K, P> ? K : '';
74
+ type SuffixedValue<T extends object, PK extends string, P extends Stringiable> = T extends {
75
+ [K in SuffixRemoved<PK, P>]: infer TValue;
76
+ } ? TValue : never;
77
+ type SuffixedObject<T extends object, P extends Stringiable> = {
78
+ [K in Suffixed<keyof T, P>]: SuffixedValue<T, K, P>;
79
+ };
80
+ interface MatchPlayerTemplate {
81
+ name: string;
82
+ num: number;
83
+ }
84
+ type MatchPlayer<P extends Stringiable> = SuffixedObject<MatchPlayerTemplate, P>;
85
+ export type Match = MatchBrief & MatchPlayer<1> & MatchPlayer<2> & MatchPlayer<3> & MatchPlayer<4>;
86
+ export {};
87
+ }
@@ -0,0 +1,47 @@
1
+ import { Context, Dict, Schema } from 'koishi';
2
+ import { MahjongPub } from './mahjongpub';
3
+ interface PlayerResult {
4
+ name: string;
5
+ num: number;
6
+ pint: number;
7
+ }
8
+ interface MatchRecord {
9
+ _id: string;
10
+ inx?: string;
11
+ cid: string;
12
+ rid: string;
13
+ round: number;
14
+ rowi: number;
15
+ time?: string;
16
+ url?: string;
17
+ cls: number;
18
+ tids: number[];
19
+ players: string[];
20
+ ver?: number;
21
+ _finished?: boolean;
22
+ results?: PlayerResult[];
23
+ }
24
+ declare class ContestExtra {
25
+ ctx: Context;
26
+ subscribers: Set<string>;
27
+ stopCls: Set<number>;
28
+ constructor(ctx: Context);
29
+ broadcast(msg: string): Promise<void>;
30
+ }
31
+ export declare class ContestManager {
32
+ protected ctx: Context;
33
+ config: ContestManager.Config;
34
+ static inject: string[];
35
+ mahjongpub: MahjongPub;
36
+ extra: Dict<ContestExtra>;
37
+ constructor(ctx: Context, config: ContestManager.Config);
38
+ startMatch(cid: number, cls: number, timeout?: number, tag?: string): Promise<void>;
39
+ onMatchFinished(record: MatchRecord): Promise<void>;
40
+ }
41
+ export declare namespace ContestManager {
42
+ interface Config {
43
+ token?: string;
44
+ }
45
+ const Config: Schema<Config>;
46
+ }
47
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hieuzest/koishi-plugin-mahjongpub",
3
3
  "description": "Mahjong.pub API",
4
- "version": "0.1.22",
4
+ "version": "0.2.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -15,6 +15,10 @@
15
15
  "plugin"
16
16
  ],
17
17
  "peerDependencies": {
18
- "koishi": "^4.17.1"
18
+ "koishi": "^4.17.1",
19
+ "@hieuzest/koishi-plugin-riichi-city": "^0.5.2"
20
+ },
21
+ "dependencies": {
22
+ "@hieuzest/cachetools": "^0.1.5"
19
23
  }
20
24
  }