@hieuzest/koishi-plugin-mahjongpub 0.2.12 → 0.2.14

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,6 +1,7 @@
1
1
  import { Context, Dict, Schema, Service } from 'koishi';
2
2
  import { ContestAdmin, TeamAdmin } from './api';
3
3
  import { ContestManager } from './manager';
4
+ import { MahjongPub as Contest } from './mahjongpub';
4
5
  declare module 'koishi' {
5
6
  interface Context {
6
7
  mahjongpub: MahjongPub;
@@ -26,6 +27,7 @@ export declare class MahjongPub extends Service {
26
27
  config: MahjongPub.Config;
27
28
  teams: Dict<TeamAdmin>;
28
29
  contests: Dict<ContestAdmin>;
30
+ contest: Contest;
29
31
  constructor(ctx: Context, config: MahjongPub.Config);
30
32
  getTeam(pw: string): Promise<TeamAdmin>;
31
33
  getContest(cid: string, pw: string): Promise<ContestAdmin>;
package/lib/index.js CHANGED
@@ -336,6 +336,105 @@ var MahjongPub = class _MahjongPub {
336
336
  }
337
337
  };
338
338
 
339
+ // src/lark.tsx
340
+ var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
341
+ var LarkExtension = class {
342
+ constructor(ctx, manager) {
343
+ this.ctx = ctx;
344
+ this.manager = manager;
345
+ ctx.command("mahjongpub.manager.manage").channelFields(["mahjongpub/bind-contest"]).action(async ({ session }) => {
346
+ const cid = +(session.channel["mahjongpub/bind-contest"] || 0);
347
+ if (!cid) return "Unauthorized.";
348
+ const cextra = this.manager.extra[cid] ??= new ContestExtra(ctx);
349
+ const contest = await this.manager.mahjongpub.getContest(cid);
350
+ const rounds = await this.manager.mahjongpub.getRounds(cid);
351
+ const classes = [];
352
+ for (const r of Object.values(rounds)) {
353
+ if (r.round !== (cextra.round ?? contest.c_round)) continue;
354
+ classes.push([r.t_class, await this.manager.getClsStatus(cid, r.t_class)]);
355
+ }
356
+ this.ctx.logger.info("Contest management statuses:", classes);
357
+ const stringifyStatus = /* @__PURE__ */ __name((status) => {
358
+ switch (status.status) {
359
+ case "pending":
360
+ return `<text_tag color='gray'>等待中</text_tag>`;
361
+ case "running":
362
+ return `<text_tag color='green'>进行中</text_tag>`;
363
+ case "finished":
364
+ return `<text_tag color='blue'>已结束</text_tag>`;
365
+ case "suspended":
366
+ return `<text_tag color='orange'>已停止</text_tag>`;
367
+ case "error":
368
+ return `<text_tag color='red'>错误</text_tag>`;
369
+ }
370
+ }, "stringifyStatus");
371
+ const renderCls = /* @__PURE__ */ __name((cls, status) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("lark:column-set", { backgroundStyle: "blue-50", children: [
372
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("lark:column", { weight: 3, width: "weighted", children: [
373
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
374
+ " 组",
375
+ cls + 1,
376
+ "-",
377
+ status.rowi,
378
+ " ",
379
+ stringifyStatus(status)
380
+ ] }),
381
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("lark:column-set", { children: Array(4).fill(0).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("lark:column", { weight: 1, width: "weighted", backgroundStyle: "bg-white", children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { align: "center", children: [
383
+ " ",
384
+ status.next?.[i]?.player ?? status.last?.[i]?.player,
385
+ " "
386
+ ] }),
387
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { align: "center", children: [
388
+ " ",
389
+ status.next?.[i]?.point ?? status.last?.[i]?.point,
390
+ " "
391
+ ] })
392
+ ] })) })
393
+ ] }),
394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("lark:column", { weight: 1, width: "weighted", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("lark:column-set", { children: [
395
+ status.status === "pending" || status.status === "suspended" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("lark:column", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", text: `mahjongpub.manager.callback -a 1 -c ${cls}`, children: "开赛" }) }) : null,
396
+ status.status === "pending" || status.status === "running" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("lark:column", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", text: `mahjongpub.manager.callback -a 2 -c ${cls}`, children: "停止" }) }) : null
397
+ ] }) })
398
+ ] }), "renderCls");
399
+ const msg = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("lark:card", { width: "fill", title: "比赛管理", color: "blue", children: [
400
+ classes.map(([i, status]) => renderCls(i, status)),
401
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("lark:column-set", { children: [
402
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("lark:column", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", text: `mahjongpub.manager.callback -a 1 -A`, children: "全部开赛" }) }),
403
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("lark:column", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", text: `mahjongpub.manager.callback -a 2 -A`, children: "全部停止" }) }),
404
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("lark:column", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", text: `mahjongpub.manager.manage`, children: "刷新" }) })
405
+ ] })
406
+ ] }) });
407
+ if (session.type === "interaction/command") {
408
+ await session.bot.editMessage(session.channelId, session.referrer.event.context.open_message_id, msg);
409
+ } else {
410
+ await session.send(msg);
411
+ }
412
+ });
413
+ ctx.command("mahjongpub.manager.callback").option("action", "-a <action:number>").option("cls", "-c <cls:number>").option("all", "-A").action(async ({ session, options }) => {
414
+ this.ctx.logger.info("Contest management callback:", session.userId, options);
415
+ switch (options.action) {
416
+ case 1:
417
+ await session.execute({
418
+ name: `mahjongpub.manager.start`,
419
+ args: options.all ? [] : [options.cls]
420
+ });
421
+ return `Callback executed: start class ${options.all ? "all" : options.cls}.`;
422
+ case 2:
423
+ await session.execute({
424
+ name: `mahjongpub.manager.stop`,
425
+ args: options.all ? [] : [options.cls]
426
+ });
427
+ return `Callback executed: stop class ${options.all ? "all" : options.cls}.`;
428
+ default:
429
+ return "Unknown action.";
430
+ }
431
+ });
432
+ }
433
+ static {
434
+ __name(this, "LarkExtension");
435
+ }
436
+ };
437
+
339
438
  // src/manager.ts
340
439
  var ContestExtra = class {
341
440
  constructor(ctx) {
@@ -351,25 +450,29 @@ var ContestExtra = class {
351
450
  type;
352
451
  round;
353
452
  lobby;
453
+ playerIndex;
354
454
  ver = 0;
455
+ maxRow;
355
456
  async broadcast(msg) {
356
457
  this.subscribers.forEach((channel) => this.ctx.sendMessage(channel, msg).catch((e) => this.ctx.logger.debug(e)));
357
458
  }
358
459
  };
359
- function getRowPlayer(contest, team, rowi) {
360
- const roleList = contest.t_type.split(/\s+/);
361
- const roleSet = [...new Set(roleList).values()];
362
- const index = roleSet.indexOf(roleList[rowi]);
460
+ function getRowPlayer(contest, team, rowi, index) {
461
+ if ((0, import_koishi2.isNullable)(index)) {
462
+ const roleList = contest.t_type.split(/\s+/);
463
+ const roleSet = [...new Set(roleList).values()];
464
+ index = roleSet.indexOf(roleList[rowi]);
465
+ }
363
466
  return team.players[index]?.split("##", 1)[0];
364
467
  }
365
468
  __name(getRowPlayer, "getRowPlayer");
366
- var ContestManager = class {
469
+ var ContestManager2 = class {
367
470
  constructor(ctx, config) {
368
471
  this.ctx = ctx;
369
472
  this.config = config;
370
473
  this.mahjongpub = MahjongPub.new(ctx);
371
474
  ctx.command("mahjongpub.manager", { authority: 3 }).action(import_koishi2.noop);
372
- ctx.command("mahjongpub.manager.init").channelFields(["mahjongpub/bind-contest"]).option("clear", "-c").option("type", "-t [type:string]").option("ver", "-v [ver:number]").option("round", "-r [round:number]").option("lobby", "-l [lobby:string]").action(async ({ session, options }) => {
475
+ ctx.command("mahjongpub.manager.init").channelFields(["mahjongpub/bind-contest"]).option("clear", "-c").option("type", "-t [type:string]").option("ver", "-v [ver:number]").option("round", "-r [round:number]").option("lobby", "-l [lobby:string]").option("index", "-i [index:number]").option("maxRow", "-m [maxRow:number]").action(async ({ session, options }) => {
373
476
  const cid = +(session.channel["mahjongpub/bind-contest"] || 0);
374
477
  if (!cid) return "Unauthorized.";
375
478
  this.extra[cid] ??= new ContestExtra(ctx);
@@ -379,6 +482,8 @@ var ContestManager = class {
379
482
  if (!(0, import_koishi2.isNullable)(options.ver)) this.extra[cid].ver = options.ver;
380
483
  if (!(0, import_koishi2.isNullable)(options.round)) this.extra[cid].round = options.round;
381
484
  if (!(0, import_koishi2.isNullable)(options.lobby)) this.extra[cid].lobby = options.lobby;
485
+ if (!(0, import_koishi2.isNullable)(options.index)) this.extra[cid].playerIndex = options.index;
486
+ if (!(0, import_koishi2.isNullable)(options.maxRow)) this.extra[cid].maxRow = options.maxRow;
382
487
  return "Finished.";
383
488
  });
384
489
  ctx.command("mahjongpub.manager.deinit").channelFields(["mahjongpub/bind-contest"]).option("clear", "-c").action(async ({ session, options }) => {
@@ -436,6 +541,7 @@ var ContestManager = class {
436
541
  kCtx.status = 200;
437
542
  return true;
438
543
  });
544
+ ctx.plugin(LarkExtension, this);
439
545
  }
440
546
  static {
441
547
  __name(this, "ContestManager");
@@ -443,6 +549,101 @@ var ContestManager = class {
443
549
  static inject = ["server", "mahjong", "mahjong.database", "mahjongpub", "sendMessage", "zx-dhs", "majsoul-dhs"];
444
550
  mahjongpub;
445
551
  extra = /* @__PURE__ */ Object.create(null);
552
+ async getClsStatus(cid, cls) {
553
+ const cextra = this.extra[cid];
554
+ if (!cextra) {
555
+ return {
556
+ status: "error",
557
+ rowi: -1,
558
+ msg: "比赛未初始化"
559
+ };
560
+ }
561
+ const contest = await this.mahjongpub.getContest(cid);
562
+ const rounds = await this.mahjongpub.getRounds(cid);
563
+ const teams = await this.mahjongpub.getTeams(cid);
564
+ const [lastRecord, secondRecord] = await this.ctx.mahjong.database.db("scoreboard").collection("matches").find({
565
+ cid: `${cid}`,
566
+ round: cextra.round ?? +contest.c_round,
567
+ cls,
568
+ ver: cextra.ver
569
+ }).sort({ rowi: -1 }).limit(2).toArray();
570
+ if (lastRecord && !lastRecord._finished) {
571
+ return {
572
+ status: "running",
573
+ rowi: lastRecord.rowi,
574
+ last: secondRecord?.results.map((x) => ({
575
+ player: x.name,
576
+ point: x.num
577
+ })) || lastRecord.players.map((x) => ({
578
+ player: x,
579
+ point: 0
580
+ }))
581
+ };
582
+ }
583
+ if (cextra.type === "ti" && lastRecord && lastRecord.results?.some((x) => x.num < 0) && !this.config.allowNegativeScore) {
584
+ return {
585
+ status: "finished",
586
+ rowi: lastRecord.rowi,
587
+ last: lastRecord.results.map((x) => ({
588
+ player: x.name,
589
+ point: x.num
590
+ }))
591
+ };
592
+ }
593
+ const rowi = lastRecord ? lastRecord.rowi + 1 : 0;
594
+ if (rowi >= (cextra.maxRow ?? contest.t_type.split(/\s+/).filter((x) => x).length)) {
595
+ return {
596
+ status: "finished",
597
+ rowi: lastRecord.rowi,
598
+ last: lastRecord.results.map((x) => ({
599
+ player: x.name,
600
+ point: x.num
601
+ }))
602
+ };
603
+ }
604
+ const round = Object.values(rounds).find((x) => x.round === (cextra.round ?? contest.c_round) && x.t_class === cls);
605
+ const players = round.tids.map((tid) => getRowPlayer(contest, teams[tid], rowi, cextra.playerIndex));
606
+ if (!players.every((x) => x)) {
607
+ return {
608
+ status: "error",
609
+ rowi,
610
+ msg: "名单未填写"
611
+ };
612
+ }
613
+ try {
614
+ if (cextra.type === "ti") {
615
+ return {
616
+ status: cextra.stopCls.has(cls) ? "suspended" : "pending",
617
+ rowi,
618
+ next: players.map((x, i) => ({
619
+ player: x,
620
+ point: lastRecord ? lastRecord.results?.[i].num : 1e5
621
+ }))
622
+ };
623
+ } else if (cextra.type === "ssb") {
624
+ return {
625
+ status: cextra.stopCls.has(cls) ? "suspended" : "pending",
626
+ rowi,
627
+ next: players.map((x) => ({
628
+ player: x,
629
+ point: 25e3
630
+ }))
631
+ };
632
+ } else {
633
+ return {
634
+ status: "error",
635
+ rowi,
636
+ msg: `未知的比赛类型:${cextra.type}`
637
+ };
638
+ }
639
+ } catch (e) {
640
+ return {
641
+ status: "error",
642
+ rowi,
643
+ msg: `未知错误:${e}`
644
+ };
645
+ }
646
+ }
446
647
  async _startMatch(cid, cls, timeout = 0, force = false) {
447
648
  const cextra = this.extra[cid];
448
649
  const tag = `${cls}组`;
@@ -473,12 +674,12 @@ var ContestManager = class {
473
674
  return;
474
675
  }
475
676
  const rowi = lastRecord ? lastRecord.rowi + 1 : 0;
476
- if (rowi >= contest.t_type.split(/\s+/).filter((x) => x).length) {
677
+ if (rowi >= (cextra.maxRow ?? contest.t_type.split(/\s+/).filter((x) => x).length)) {
477
678
  cextra.broadcast(`[${tag}] 已经全部完成`);
478
679
  return;
479
680
  }
480
681
  const round = Object.values(rounds).find((x) => x.round === (cextra.round ?? contest.c_round) && x.t_class === cls);
481
- const players = round.tids.map((tid) => getRowPlayer(contest, teams[tid], rowi));
682
+ const players = round.tids.map((tid) => getRowPlayer(contest, teams[tid], rowi, cextra.playerIndex));
482
683
  if (!players.every((x) => x)) {
483
684
  cextra.broadcast(`[${tag}] 失败: 名单未填写`);
484
685
  return;
@@ -537,8 +738,8 @@ var ContestManager = class {
537
738
  return this.startMatch(+record.cid, record.cls);
538
739
  }
539
740
  };
540
- ((ContestManager2) => {
541
- ContestManager2.Config = import_koishi2.Schema.object({
741
+ ((ContestManager3) => {
742
+ ContestManager3.Config = import_koishi2.Schema.object({
542
743
  token: import_koishi2.Schema.string(),
543
744
  startInterval: import_koishi2.Schema.number().default(import_koishi2.Time.second).description("比赛开始间隔"),
544
745
  startTimeoutInterval: import_koishi2.Schema.number().default(import_koishi2.Time.second * 30).description("比赛开始重试间隔"),
@@ -546,7 +747,7 @@ var ContestManager = class {
546
747
  finishInterval: import_koishi2.Schema.number().default(import_koishi2.Time.second * 10).description("比赛结束间隔"),
547
748
  allowNegativeScore: import_koishi2.Schema.boolean().default(false).description("允许负分")
548
749
  });
549
- })(ContestManager || (ContestManager = {}));
750
+ })(ContestManager2 || (ContestManager2 = {}));
550
751
 
551
752
  // src/index.ts
552
753
  function parsePlatform(target) {
@@ -561,8 +762,9 @@ var MahjongPub2 = class extends import_koishi3.Service {
561
762
  super(ctx, "mahjongpub", true);
562
763
  this.ctx = ctx;
563
764
  this.config = config;
564
- ctx.plugin(ContestManager, config.manager);
765
+ ctx.plugin(ContestManager2, config.manager);
565
766
  ctx.i18n.define("zh-CN", require_zh_CN());
767
+ this.contest = MahjongPub.new(ctx);
566
768
  ctx.model.extend("user", {
567
769
  "mahjongpub/bind-team": "string",
568
770
  "mahjongpub/bind-teams": "json"
@@ -826,6 +1028,7 @@ var MahjongPub2 = class extends import_koishi3.Service {
826
1028
  ctx.command("mahjongpub.database.record.last <round:natural>", { authority: 3 }).option("rowi", "-i <rowi:integer>", { fallback: -1 }).option("ver", "-v <ver:integer>", { fallback: 0 }).channelFields(["mahjongpub/bind-contest"]).action(async ({ session, options }, round, cls) => {
827
1029
  const [cid] = [session.channel["mahjongpub/bind-contest"]];
828
1030
  try {
1031
+ const cteams = await this.contest.getTeams(+cid);
829
1032
  const record = await ctx.mahjong.database.db("scoreboard").collection("matches").find({
830
1033
  cid,
831
1034
  round,
@@ -841,7 +1044,7 @@ var MahjongPub2 = class extends import_koishi3.Service {
841
1044
  seenCls.add(r.cls);
842
1045
  }
843
1046
  });
844
- return filtered.map((r) => `[${r.cls}] ` + r.results.map((x) => `${x.name} ${x.num}`).join(" / ")).join("\n");
1047
+ return filtered.map((r) => `[${r.cls}-${r.rowi + 1}] ` + r.results.map((x, i) => `${cteams[r.tids[i]].t_name} ${x.num}`).join(" / ")).join("\n");
845
1048
  } catch (e) {
846
1049
  ctx.logger.warn(e);
847
1050
  return session.text(".failed");
@@ -931,6 +1134,7 @@ var MahjongPub2 = class extends import_koishi3.Service {
931
1134
  }
932
1135
  teams = {};
933
1136
  contests = {};
1137
+ contest;
934
1138
  async getTeam(pw) {
935
1139
  return new TeamAdmin(this.ctx, pw, this.config);
936
1140
  }
@@ -958,7 +1162,7 @@ var MahjongPub2 = class extends import_koishi3.Service {
958
1162
  endpoint: import_koishi3.Schema.string().default("https://cdn.r-mj.com/"),
959
1163
  batchInterval: import_koishi3.Schema.natural().default(300),
960
1164
  batchCount: import_koishi3.Schema.natural().default(10),
961
- manager: ContestManager.Config
1165
+ manager: ContestManager2.Config
962
1166
  });
963
1167
  })(MahjongPub2 || (MahjongPub2 = {}));
964
1168
  var index_default = MahjongPub2;
package/lib/lark.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { Context } from 'koishi';
2
+ import { ContestManager } from './manager';
3
+ export declare class LarkExtension {
4
+ protected ctx: Context;
5
+ protected manager: ContestManager;
6
+ constructor(ctx: Context, manager: ContestManager);
7
+ }
package/lib/manager.d.ts CHANGED
@@ -21,14 +21,29 @@ interface MatchRecord {
21
21
  _finished?: boolean;
22
22
  results?: PlayerResult[];
23
23
  }
24
- declare class ContestExtra {
24
+ export interface ClsStatus {
25
+ status: 'pending' | 'running' | 'finished' | 'suspended' | 'error';
26
+ rowi: number;
27
+ msg?: string;
28
+ last?: {
29
+ player: string;
30
+ point: number;
31
+ }[];
32
+ next?: {
33
+ player: string;
34
+ point: number;
35
+ }[];
36
+ }
37
+ export declare class ContestExtra {
25
38
  ctx: Context;
26
39
  subscribers: Set<string>;
27
40
  stopCls: Set<number>;
28
41
  type: 'ti' | 'ssb';
29
42
  round?: number;
30
43
  lobby?: string;
44
+ playerIndex?: number;
31
45
  ver: number;
46
+ maxRow?: number;
32
47
  constructor(ctx: Context);
33
48
  broadcast(msg: string): Promise<void>;
34
49
  }
@@ -39,6 +54,7 @@ export declare class ContestManager {
39
54
  mahjongpub: MahjongPub;
40
55
  extra: Dict<ContestExtra>;
41
56
  constructor(ctx: Context, config: ContestManager.Config);
57
+ getClsStatus(cid: number, cls: number): Promise<ClsStatus>;
42
58
  _startMatch(cid: number, cls: number, timeout?: number, force?: boolean): Promise<void>;
43
59
  startMatch(cid: number, cls: number, timeout?: number, force?: boolean): Promise<void>;
44
60
  onMatchFinished(record: MatchRecord): Promise<void>;
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.12",
4
+ "version": "0.2.14",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -20,6 +20,18 @@
20
20
  "koishi": "^4.17.1"
21
21
  },
22
22
  "dependencies": {
23
- "@hieuzest/cachetools": "^0.1.5"
23
+ "@hieuzest/cachetools": "^0.1.5",
24
+ "@satorijs/element": "^3.1.8"
25
+ },
26
+ "devDependencies": {
27
+ "@hieuzest/koishi-buildtools": "^1.0.2",
28
+ "@hieuzest/koishi-plugin-mahjong": "^3.0.0",
29
+ "@hieuzest/koishi-plugin-majsoul-dhs": "^2.5.0",
30
+ "@hieuzest/koishi-plugin-riichi-city": "^0.5.2",
31
+ "@hieuzest/koishi-plugin-send": "^1.3.7",
32
+ "@koishijs/assets": "^1.1.2",
33
+ "@koishijs/cache": "^2.1.0",
34
+ "@koishijs/plugin-server": "^3.2.7",
35
+ "koishi": "^4.18.9"
24
36
  }
25
37
  }