@hieuzest/koishi-plugin-mahjongpub 0.2.0 → 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.
Files changed (2) hide show
  1. package/lib/index.js +318 -25
  2. package/package.json +6 -2
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,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();
@@ -400,12 +678,27 @@ var MahjongPub = class {
400
678
  if (cid) return session.text(".success", [cid, cpw]);
401
679
  else return session.text(".failed");
402
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
+ });
403
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) => {
404
697
  const [cid, cpw] = [session.channel["mahjongpub/bind-contest"], session.channel["mahjongpub/bind-contestpw"]];
405
698
  const contest = await this.getContest(cid, cpw);
406
699
  try {
407
700
  const record = await contest.getRoundRecord(round, cls);
408
- if ((0, import_koishi.isNullable)(options.rowi)) {
701
+ if ((0, import_koishi3.isNullable)(options.rowi)) {
409
702
  return record.matches.map((match, i) => `[${i}] ` + match.players.map((x) => `${x.name} ${x.point}`).join(" / ")).join("\n");
410
703
  } else if (record.matches.at(options.rowi ?? -1)) {
411
704
  return [
@@ -528,29 +821,29 @@ var MahjongPub = class {
528
821
  if (this.ctx.get("cache")) {
529
822
  let res2 = await this.ctx.cache.get("contests", cid);
530
823
  if (res2) return res2;
531
- res2 = new ContestAdmin(this.ctx, cid, pw, this.config);
824
+ res2 = new ContestAdmin(this.ctx, +cid, pw, this.config);
532
825
  await res2.read();
533
826
  await this.ctx.cache.set("contests", cid, res2, 1e3 * 600);
534
827
  return res2;
535
828
  }
536
- const res = new ContestAdmin(this.ctx, cid, pw, this.config);
829
+ const res = new ContestAdmin(this.ctx, +cid, pw, this.config);
537
830
  await res.read();
538
831
  return res;
539
832
  }
540
833
  };
541
- ((MahjongPub2) => {
542
- MahjongPub2.inject = {
834
+ ((MahjongPub3) => {
835
+ MahjongPub3.inject = {
543
836
  required: ["database"],
544
837
  optional: ["assets", "cache", "mahjong", "mahjong.database"]
545
838
  };
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)
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)
551
844
  });
552
- })(MahjongPub || (MahjongPub = {}));
553
- var src_default = MahjongPub;
845
+ })(MahjongPub2 || (MahjongPub2 = {}));
846
+ var index_default = MahjongPub2;
554
847
  // Annotate the CommonJS export names for ESM import in node:
555
848
  0 && (module.exports = {
556
849
  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.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
  }