@hieuzest/koishi-plugin-mahjongpub 0.2.0 → 0.2.2

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/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Context, Dict, Schema, Service } from 'koishi';
2
2
  import { ContestAdmin, TeamAdmin } from './api';
3
+ import { ContestManager } from './manager';
3
4
  declare module 'koishi' {
4
5
  interface Context {
5
6
  mahjongpub: MahjongPub;
@@ -39,6 +40,7 @@ export declare namespace MahjongPub {
39
40
  endpoint: string;
40
41
  batchInterval: number;
41
42
  batchCount: number;
43
+ manager: ContestManager.Config;
42
44
  }
43
45
  const Config: Schema<Config>;
44
46
  }
package/lib/index.js CHANGED
@@ -28,13 +28,13 @@ var require_zh_CN = __commonJS({
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,229 @@ 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.deinit").channelFields(["mahjongpub/bind-contest"]).option("clear", "-c").action(async ({ session, options }) => {
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.delete(session.cid);
365
+ return "Finished.";
366
+ });
367
+ ctx.command("contest.manager.start [cls:number]").channelFields(["mahjongpub/bind-contest"]).action(async ({ session }, cls) => {
368
+ const cid = +(session.channel["mahjongpub/bind-contest"] || 0);
369
+ if (!cid) return "Unauthorized.";
370
+ this.extra[cid] ??= new ContestExtra(ctx);
371
+ this.extra[cid].subscribers.add(session.cid);
372
+ if (cls) {
373
+ this.extra[cid].stopCls.delete(cls);
374
+ await this.startMatch(cid, cls);
375
+ } else {
376
+ this.extra[cid].stopCls.clear();
377
+ const contest = await this.mahjongpub.getContest(cid);
378
+ const rounds = await this.mahjongpub.getRounds(cid);
379
+ for (const r of Object.values(rounds)) {
380
+ if (r.round !== contest.c_round) continue;
381
+ await this.startMatch(cid, r.t_class);
382
+ await (0, import_koishi2.sleep)(1e3);
383
+ }
384
+ }
385
+ });
386
+ ctx.command("contest.manager.stop [cls:number]").channelFields(["mahjongpub/bind-contest"]).action(async ({ session }, cls) => {
387
+ const cid = +(session.channel["mahjongpub/bind-contest"] || 0);
388
+ if (!cid) return "Unauthorized/";
389
+ this.extra[cid] ??= new ContestExtra(ctx);
390
+ if (cls) {
391
+ this.extra[cid].stopCls.add(cls);
392
+ } else {
393
+ const contest = await this.mahjongpub.getContest(cid);
394
+ const rounds = await this.mahjongpub.getRounds(cid);
395
+ for (const r of Object.values(rounds)) {
396
+ if (r.round !== contest.c_round) continue;
397
+ this.extra[cid].stopCls.add(r.t_class);
398
+ }
399
+ }
400
+ return "Finished.";
401
+ });
402
+ ctx.server.post("/contest-manager/finished", async (kCtx) => {
403
+ if (config.token && kCtx.request.headers.authorization !== `Bearer ${config.token}`) {
404
+ kCtx.status = 403;
405
+ return true;
406
+ }
407
+ this.onMatchFinished(kCtx.request.body);
408
+ kCtx.status = 200;
409
+ return true;
410
+ });
411
+ }
412
+ static {
413
+ __name(this, "ContestManager");
414
+ }
415
+ static inject = ["server", "mahjong", "mahjong.database", "mahjongpub", "sendMessage"];
416
+ mahjongpub;
417
+ extra = /* @__PURE__ */ Object.create(null);
418
+ async startMatch(cid, cls, timeout, tag) {
419
+ tag ||= `${cls}组`;
420
+ if (this.extra[cid]?.stopCls.has(cls)) {
421
+ this.ctx.logger.info(`try starting match ${cid}-${cls}, but stopped`);
422
+ return;
423
+ }
424
+ this.ctx.logger.info(`try starting match ${cid}-${cls}, timeout ${timeout}`);
425
+ const contest = await this.mahjongpub.getContest(cid);
426
+ const rounds = await this.mahjongpub.getRounds(cid);
427
+ const teams = await this.mahjongpub.getTeams(cid);
428
+ const [lastRecord] = await this.ctx.mahjong.database.db("scoreboard").collection("matches").find({
429
+ cid: `${cid}`,
430
+ round: +contest.c_round,
431
+ cls
432
+ }).sort({ rowi: -1 }).limit(1).toArray();
433
+ if (lastRecord && !lastRecord._finished) {
434
+ this.extra[cid]?.broadcast(`[${tag}] 发现进行中的比赛,已忽略`);
435
+ return;
436
+ }
437
+ const rowi = lastRecord ? lastRecord.rowi + 1 : 0;
438
+ const round = Object.values(rounds).find((x) => x.round === contest.c_round && x.t_class === cls);
439
+ const players = round.tids.map((tid) => teams[tid].players[rowi]);
440
+ try {
441
+ await this.ctx["zx-dhs"].startMatch(+round.code, players.map((x, i) => import_lobby.Player.fromPattern(x, lastRecord ? lastRecord.results?.[i].num : 1e5)));
442
+ this.extra[cid]?.broadcast(`[${tag}] 成功:` + players.join(","));
443
+ } catch (e) {
444
+ if (e instanceof import_lobby.PlayerNotFoundError) {
445
+ this.extra[cid]?.broadcast(`[${tag}] ` + e.message + ` <timeout=${timeout}>`);
446
+ if (timeout < 600) this.ctx.setTimeout(() => this.startMatch(cid, cls, timeout + 30), 30 * 1e3);
447
+ } else {
448
+ throw e;
449
+ }
450
+ }
451
+ }
452
+ async onMatchFinished(record) {
453
+ this.extra[record.cid]?.broadcast(`对局完成:
454
+ ` + record.results.map((x) => `${x.name} ${x.num}`).join("\n"));
455
+ await (0, import_koishi2.sleep)(2e3);
456
+ return this.startMatch(+record.cid, record.cls);
457
+ }
458
+ };
459
+ ((ContestManager2) => {
460
+ ContestManager2.Config = import_koishi2.Schema.object({
461
+ token: import_koishi2.Schema.string()
462
+ });
463
+ })(ContestManager || (ContestManager = {}));
464
+
192
465
  // src/index.ts
193
466
  function parsePlatform(target) {
194
467
  const index = target.startsWith("sandbox:") ? target.lastIndexOf(":") : target.indexOf(":");
@@ -197,10 +470,12 @@ function parsePlatform(target) {
197
470
  return [platform, id];
198
471
  }
199
472
  __name(parsePlatform, "parsePlatform");
200
- var MahjongPub = class {
473
+ var MahjongPub2 = class extends import_koishi3.Service {
201
474
  constructor(ctx, config) {
475
+ super(ctx, "mahjongpub", true);
202
476
  this.ctx = ctx;
203
477
  this.config = config;
478
+ ctx.plugin(ContestManager, config.manager);
204
479
  ctx.i18n.define("zh-CN", require_zh_CN());
205
480
  ctx.model.extend("user", {
206
481
  "mahjongpub/bind-team": "string",
@@ -277,7 +552,7 @@ var MahjongPub = class {
277
552
  const team = await this.getTeam(pw);
278
553
  try {
279
554
  await team.read();
280
- if (!img) return import_koishi.h.image(team.img);
555
+ if (!img) return import_koishi3.h.image(team.img);
281
556
  const url = await ctx.assets.upload("https://koishi.chat/logo.png", `${session.id}-${session.timestamp}.png`);
282
557
  team.img = url;
283
558
  return await team.write();
@@ -376,7 +651,17 @@ var MahjongPub = class {
376
651
  return session.text(".failed");
377
652
  }
378
653
  });
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) => {
654
+ 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) => {
655
+ if (options.only) {
656
+ if (options.channel) {
657
+ ctx.database.setChannel(...parsePlatform(options.channel), {
658
+ "mahjongpub/bind-contest": cid
659
+ });
660
+ } else {
661
+ session.channel["mahjongpub/bind-contest"] = cid;
662
+ }
663
+ return "Finished.";
664
+ }
380
665
  const contest = await this.getContest(cid, cpw);
381
666
  try {
382
667
  await contest.read();
@@ -400,12 +685,27 @@ var MahjongPub = class {
400
685
  if (cid) return session.text(".success", [cid, cpw]);
401
686
  else return session.text(".failed");
402
687
  });
688
+ ctx.command("mahjongpub.contest.round [round:natural]", { authority: 3 }).channelFields(["mahjongpub/bind-contest", "mahjongpub/bind-contestpw"]).action(async ({ session }, round) => {
689
+ const [cid, cpw] = [session.channel["mahjongpub/bind-contest"], session.channel["mahjongpub/bind-contestpw"]];
690
+ const contest = await this.getContest(cid, cpw);
691
+ try {
692
+ const rounds = await contest.getRounds();
693
+ const clss = rounds[round || contest.round];
694
+ return Object.entries(clss).map(([i, r]) => [
695
+ `[${i}]`,
696
+ r.tids.map((tid) => `${contest.teams[tid].name}`).join(" / ")
697
+ ].join(" ")).join("\n");
698
+ } catch (e) {
699
+ ctx.logger.warn(e);
700
+ return session.text(".failed");
701
+ }
702
+ });
403
703
  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) => {
404
704
  const [cid, cpw] = [session.channel["mahjongpub/bind-contest"], session.channel["mahjongpub/bind-contestpw"]];
405
705
  const contest = await this.getContest(cid, cpw);
406
706
  try {
407
707
  const record = await contest.getRoundRecord(round, cls);
408
- if ((0, import_koishi.isNullable)(options.rowi)) {
708
+ if ((0, import_koishi3.isNullable)(options.rowi)) {
409
709
  return record.matches.map((match, i) => `[${i}] ` + match.players.map((x) => `${x.name} ${x.point}`).join(" / ")).join("\n");
410
710
  } else if (record.matches.at(options.rowi ?? -1)) {
411
711
  return [
@@ -528,29 +828,30 @@ var MahjongPub = class {
528
828
  if (this.ctx.get("cache")) {
529
829
  let res2 = await this.ctx.cache.get("contests", cid);
530
830
  if (res2) return res2;
531
- res2 = new ContestAdmin(this.ctx, cid, pw, this.config);
831
+ res2 = new ContestAdmin(this.ctx, +cid, pw, this.config);
532
832
  await res2.read();
533
833
  await this.ctx.cache.set("contests", cid, res2, 1e3 * 600);
534
834
  return res2;
535
835
  }
536
- const res = new ContestAdmin(this.ctx, cid, pw, this.config);
836
+ const res = new ContestAdmin(this.ctx, +cid, pw, this.config);
537
837
  await res.read();
538
838
  return res;
539
839
  }
540
840
  };
541
- ((MahjongPub2) => {
542
- MahjongPub2.inject = {
841
+ ((MahjongPub3) => {
842
+ MahjongPub3.inject = {
543
843
  required: ["database"],
544
844
  optional: ["assets", "cache", "mahjong", "mahjong.database"]
545
845
  };
546
- MahjongPub2.Config = import_koishi.Schema.object({
547
- informNotbind: import_koishi.Schema.boolean().default(false),
548
- endpoint: import_koishi.Schema.string().default("https://cdn.r-mj.com/"),
549
- batchInterval: import_koishi.Schema.natural().default(300),
550
- batchCount: import_koishi.Schema.natural().default(10)
846
+ MahjongPub3.Config = import_koishi3.Schema.object({
847
+ informNotbind: import_koishi3.Schema.boolean().default(false),
848
+ endpoint: import_koishi3.Schema.string().default("https://cdn.r-mj.com/"),
849
+ batchInterval: import_koishi3.Schema.natural().default(300),
850
+ batchCount: import_koishi3.Schema.natural().default(10),
851
+ manager: ContestManager.Config
551
852
  });
552
- })(MahjongPub || (MahjongPub = {}));
553
- var src_default = MahjongPub;
853
+ })(MahjongPub2 || (MahjongPub2 = {}));
854
+ var index_default = MahjongPub2;
554
855
  // Annotate the CommonJS export names for ESM import in node:
555
856
  0 && (module.exports = {
556
857
  MahjongPub
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.2.0",
4
+ "version": "0.2.2",
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
  }