Commit a470da9c authored by nanahira's avatar nanahira

finish

parent 3cc4eb3f
...@@ -17,17 +17,20 @@ ...@@ -17,17 +17,20 @@
"@aws-sdk/util-format-url": "^3.38.0", "@aws-sdk/util-format-url": "^3.38.0",
"class-transformer": "^0.4.0", "class-transformer": "^0.4.0",
"koishi-utils-schemagen": "^1.1.9", "koishi-utils-schemagen": "^1.1.9",
"moment": "^2.29.1",
"source-map-support": "^0.5.20" "source-map-support": "^0.5.20"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0", "@typescript-eslint/parser": "^4.33.0",
"axios": "^0.24.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.1", "eslint-plugin-prettier": "^3.4.1",
"koishi": "^4.0.0-alpha.12", "koishi": "^4.0.0-alpha.12",
"prettier": "^2.4.1", "prettier": "^2.4.1",
"proxy-agent": "^5.0.0",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"ts-loader": "^9.2.6", "ts-loader": "^9.2.6",
"typescript": "^4.4.4", "typescript": "^4.4.4",
...@@ -2140,6 +2143,15 @@ ...@@ -2140,6 +2143,15 @@
"websocket-stream": "^5.5.2" "websocket-stream": "^5.5.2"
} }
}, },
"node_modules/aws-crt/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"peer": true,
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/aws-sign2": { "node_modules/aws-sign2": {
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
...@@ -2156,11 +2168,12 @@ ...@@ -2156,11 +2168,12 @@
"peer": true "peer": true
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "0.21.4", "version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"dev": true,
"dependencies": { "dependencies": {
"follow-redirects": "^1.14.0" "follow-redirects": "^1.14.4"
} }
}, },
"node_modules/balanced-match": { "node_modules/balanced-match": {
...@@ -4771,6 +4784,14 @@ ...@@ -4771,6 +4784,14 @@
"reflect-metadata": "^0.1.13" "reflect-metadata": "^0.1.13"
} }
}, },
"node_modules/koishi/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/lcid": { "node_modules/lcid": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
...@@ -5044,6 +5065,14 @@ ...@@ -5044,6 +5065,14 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
"engines": {
"node": "*"
}
},
"node_modules/mqtt": { "node_modules/mqtt": {
"version": "4.2.8", "version": "4.2.8",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.2.8.tgz", "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.2.8.tgz",
...@@ -8892,6 +8921,17 @@ ...@@ -8892,6 +8921,17 @@
"mqtt": "^4.2.8", "mqtt": "^4.2.8",
"tar": "^6.1.11", "tar": "^6.1.11",
"websocket-stream": "^5.5.2" "websocket-stream": "^5.5.2"
},
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"peer": true,
"requires": {
"follow-redirects": "^1.14.0"
}
}
} }
}, },
"aws-sign2": { "aws-sign2": {
...@@ -8907,11 +8947,12 @@ ...@@ -8907,11 +8947,12 @@
"peer": true "peer": true
}, },
"axios": { "axios": {
"version": "0.21.4", "version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"dev": true,
"requires": { "requires": {
"follow-redirects": "^1.14.0" "follow-redirects": "^1.14.4"
} }
}, },
"balanced-match": { "balanced-match": {
...@@ -10870,6 +10911,16 @@ ...@@ -10870,6 +10911,16 @@
"parseurl": "^1.3.3", "parseurl": "^1.3.3",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"proxy-agent": "^5.0.0" "proxy-agent": "^5.0.0"
},
"dependencies": {
"axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"requires": {
"follow-redirects": "^1.14.0"
}
}
} }
}, },
"koishi-utils-schemagen": { "koishi-utils-schemagen": {
...@@ -11099,6 +11150,11 @@ ...@@ -11099,6 +11150,11 @@
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"peer": true "peer": true
}, },
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"mqtt": { "mqtt": {
"version": "4.2.8", "version": "4.2.8",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.2.8.tgz", "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.2.8.tgz",
......
...@@ -11,13 +11,11 @@ import { Context, Schema } from 'koishi'; ...@@ -11,13 +11,11 @@ import { Context, Schema } from 'koishi';
import { import {
GetObjectCommand, GetObjectCommand,
ListObjectsCommand, ListObjectsCommand,
S3,
S3Client, S3Client,
S3ClientConfig, S3ClientConfig,
} from '@aws-sdk/client-s3'; } from '@aws-sdk/client-s3';
import { Tournament } from './def/challonge';
import { Type } from 'class-transformer';
import { SRVProRoomInfo } from './def/srvpro'; import { SRVProRoomInfo } from './def/srvpro';
import { Selection } from './def/selection';
const credentialsSchema: Schema<Credentials> = Schema.object( const credentialsSchema: Schema<Credentials> = Schema.object(
{ {
...@@ -106,6 +104,16 @@ export class TournamentConfig { ...@@ -106,6 +104,16 @@ export class TournamentConfig {
}) })
challongeCacheTTL: number; challongeCacheTTL: number;
@DefineSchema({
desc: 'Challonge URL 前缀',
default: 'https://api.challonge.com/v1/tournaments/',
})
challongeUrlPrefix: string;
getChallongeUrl() {
return `${this.challongeUrlPrefix}${this.challongeTournamentId}`;
}
async fetchRooms(ctx: Context) { async fetchRooms(ctx: Context) {
return ctx.http.get<Partial<SRVProRoomInfo>>( return ctx.http.get<Partial<SRVProRoomInfo>>(
`${this.endpoint}/api/getrooms`, `${this.endpoint}/api/getrooms`,
...@@ -115,9 +123,25 @@ export class TournamentConfig { ...@@ -115,9 +123,25 @@ export class TournamentConfig {
}, },
); );
} }
async kickRoom(ctx: Context, search: string) {
return ctx.http.get(`${this.endpoint}/api/message`, {
username: this.username,
pass: this.password,
kick: search,
});
}
} }
export class YGOTournamentPluginConfig { export class YGOTournamentPluginConfig {
@DefineSchema({
type: 'object',
allowUnknown: true,
desc: '裁判接口作用域',
required: true,
})
judgeSelection: Selection;
@DefineSchema() @DefineSchema()
tournament: TournamentConfig; tournament: TournamentConfig;
...@@ -134,6 +158,7 @@ export class YGOTournamentPluginConfig { ...@@ -134,6 +158,7 @@ export class YGOTournamentPluginConfig {
} }
export interface YGOTournamentPluginConfigLike { export interface YGOTournamentPluginConfigLike {
judgeSelection: Selection;
tournament?: Partial<TournamentConfig>; tournament?: Partial<TournamentConfig>;
deckFetch?: Partial<DeckFetchConfig>; deckFetch?: Partial<DeckFetchConfig>;
} }
...@@ -35,7 +35,7 @@ export class MatchWrapper { ...@@ -35,7 +35,7 @@ export class MatchWrapper {
isClean() { isClean() {
return ( return (
this.match.state !== 'complete' && this.match.state !== 'complete' &&
(this.match.scores_csv?.length && 0) < 3 (this.match.scores_csv?.length || 0) < 3
); );
} }
getPlayerIds() { getPlayerIds() {
...@@ -91,6 +91,10 @@ export class ParticipantWrapper { ...@@ -91,6 +91,10 @@ export class ParticipantWrapper {
} }
return { userId: matching[1], userName: matching[2] }; return { userId: matching[1], userName: matching[2] };
} }
getDisplayName() {
return `${this.participant.name}(${this.participant.id})`;
}
} }
export class Tournament { export class Tournament {
......
import { MaybeArray } from 'koishi';
const selectors = [
'user',
'guild',
'channel',
'self',
'private',
'platform',
] as const;
type SelectorType = typeof selectors[number];
type SelectorValue = boolean | MaybeArray<string | number>;
type BaseSelection = { [K in SelectorType as `$${K}`]?: SelectorValue };
export interface Selection extends BaseSelection {
$and?: Selection[];
$or?: Selection[];
$not?: Selection;
}
import 'source-map-support/register'; import 'source-map-support/register';
import { Context, Schema } from 'koishi'; import { Context, Schema, Quester } from 'koishi';
import { import {
YGOTournamentPluginConfig, YGOTournamentPluginConfig,
YGOTournamentPluginConfigLike, YGOTournamentPluginConfigLike,
...@@ -10,12 +10,21 @@ import { Tournament, TournamentWrapper } from './def/challonge'; ...@@ -10,12 +10,21 @@ import { Tournament, TournamentWrapper } from './def/challonge';
import { S3Client } from '@aws-sdk/client-s3'; import { S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from './presign'; import { getSignedUrl } from './presign';
import { Room, SRVProRoomInfo } from './def/srvpro'; import { Room, SRVProRoomInfo } from './def/srvpro';
import moment from 'moment';
import axios, { AxiosRequestConfig } from 'axios';
import ProxyAgent from 'proxy-agent';
export interface FromAndToTimeUnix {
fromTime: number;
toTime: number;
}
declare module 'koishi' { declare module 'koishi' {
// eslint-disable-next-line @typescript-eslint/no-namespace // eslint-disable-next-line @typescript-eslint/no-namespace
namespace Cache { namespace Cache {
interface Tables { interface Tables {
challongeData: any; challongeData: any;
lateDeclarationTime: FromAndToTimeUnix;
} }
} }
} }
...@@ -55,7 +64,7 @@ export class YGOTournamentPlugin { ...@@ -55,7 +64,7 @@ export class YGOTournamentPlugin {
return cached; return cached;
} }
const plain = await this.ctx.http.get( const plain = await this.ctx.http.get(
`https://api.challonge.com/v1/tournaments/${this.config.tournament.challongeTournamentId}.json`, `${this.config.tournament.getChallongeUrl()}.json`,
{ {
api_key: this.config.tournament.challongeKey, api_key: this.config.tournament.challongeKey,
include_participants: 1, include_participants: 1,
...@@ -69,7 +78,6 @@ export class YGOTournamentPlugin { ...@@ -69,7 +78,6 @@ export class YGOTournamentPlugin {
private async onUserQuit(userId: string) { private async onUserQuit(userId: string) {
const tournament = await this.fetchChallongeData(); const tournament = await this.fetchChallongeData();
const { matches } = tournament.tournament;
const participant = tournament.getParticipantFromId(userId); const participant = tournament.getParticipantFromId(userId);
if (!participant) { if (!participant) {
return '未找到您的参赛信息。'; return '未找到您的参赛信息。';
...@@ -77,7 +85,7 @@ export class YGOTournamentPlugin { ...@@ -77,7 +85,7 @@ export class YGOTournamentPlugin {
// const participantName = participant.getNameMatching().userName; // const participantName = participant.getNameMatching().userName;
this.ctx this.ctx
.logger('challonge') .logger('challonge')
.info(`Player ${participant.participant.name} requested for quit.`); .info(`Player ${participant.getDisplayName()} requested for quit.`);
const currentMatch = tournament.getCurrentMatchFromId( const currentMatch = tournament.getCurrentMatchFromId(
participant.participant.id, participant.participant.id,
); );
...@@ -86,19 +94,21 @@ export class YGOTournamentPlugin { ...@@ -86,19 +94,21 @@ export class YGOTournamentPlugin {
participant.participant.id === currentMatch.match.player2_id participant.participant.id === currentMatch.match.player2_id
? '2--1' ? '2--1'
: '-1-2'; : '-1-2';
const winner_id = currentMatch
.getPlayerIds()
.find((id) => id !== participant.participant.id);
this.ctx this.ctx
.logger('challonge') .logger('challonge')
.info( .info(
`Cleaning remaining match for player ${participant.participant.name}.`, `Cleaning remaining match for player ${participant.getDisplayName()}.`,
); );
await this.ctx.http.put( await this.ctx.http.put(
`https://api.challonge.com/v1/tournaments/${this.config.tournament.challongeTournamentId}/matches/${currentMatch.match.id}.json`, `${this.config.tournament.getChallongeUrl()}/matches/${
currentMatch.match.id
}.json`,
{ {
api_key: this.config.tournament.challongeKey, api_key: this.config.tournament.challongeKey,
scores_csv, match: { scores_csv, winner_id },
winner_id: currentMatch
.getPlayerIds()
.find((id) => id !== participant.participant.id),
}, },
); );
this.ctx this.ctx
...@@ -107,23 +117,33 @@ export class YGOTournamentPlugin { ...@@ -107,23 +117,33 @@ export class YGOTournamentPlugin {
`Cleaned remaining match for player ${participant.participant.name}.`, `Cleaned remaining match for player ${participant.participant.name}.`,
); );
await this.ctx.http.delete( await this.ctx.http.delete(
`https://api.challonge.com/v1/tournaments/${ `${this.config.tournament.getChallongeUrl()}/participants/${
this.config.tournament.challongeTournamentId
}/participants/${
participant.participant.id participant.participant.id
}.json?api_key=${encodeURIComponent( }.json`,
this.config.tournament.challongeKey, {
)}`, api_key: this.config.tournament.challongeKey,
},
); );
this.ctx this.ctx
.logger('challonge') .logger('challonge')
.info(`Player ${participant.participant.name} quitted.`); .info(`Player ${participant.getDisplayName()} quited.`);
await this.deleteChallongeCache(); await this.deleteChallongeCache();
return '退赛成功。'; return '退赛成功。';
} }
} }
private async onUserDeclareLate(userId: string) { private async onUserDeclareLate(userId: string) {
const timeUnix = await this.ctx.cache.get('lateDeclarationTime', 'current');
this.ctx.logger('test').warn(JSON.stringify(timeUnix));
if (
!timeUnix ||
!moment().isBetween(
moment.unix(timeUnix.fromTime),
moment.unix(timeUnix.toTime),
)
) {
return '现在不是允许迟到杀的时间。';
}
const tournament = await this.fetchChallongeData(); const tournament = await this.fetchChallongeData();
const participant = tournament.getParticipantFromId(userId); const participant = tournament.getParticipantFromId(userId);
if (!participant) { if (!participant) {
...@@ -157,27 +177,39 @@ export class YGOTournamentPlugin { ...@@ -157,27 +177,39 @@ export class YGOTournamentPlugin {
participant.participant.id === currentMatch.match.player1_id participant.participant.id === currentMatch.match.player1_id
? '2--1' ? '2--1'
: '-1-2'; : '-1-2';
const rivalId = currentMatch
.getPlayerIds()
.find((id) => id !== participant.participant.id);
const rival = tournament.tournament.participants.find(
(p) => p.participant.id === rivalId,
);
this.ctx this.ctx
.logger('challonge') .logger('challonge')
.info( .info(
`Player ${participant.participant.name} requested for a late declaration.`, `Player ${participant.getDisplayName()} requested for a late declaration against ${rival?.getDisplayName()}.`,
); );
const result = await this.ctx.http.put( const result = await this.ctx.http.put(
`https://api.challonge.com/v1/tournaments/${this.config.tournament.challongeTournamentId}/matches/${currentMatch.match.id}.json`, `${this.config.tournament.getChallongeUrl()}/matches/${
currentMatch.match.id
}.json`,
{ {
api_key: this.config.tournament.challongeKey, api_key: this.config.tournament.challongeKey,
scores_csv, match: { scores_csv, winner_id: participant.participant.id },
winner_id: participant.participant.id,
}, },
); );
this.ctx this.ctx
.logger('challonge') .logger('challonge')
.info( .info(
`Result of late declaration for player ${ `Player ${participant.getDisplayName()} declared late against ${rival?.getDisplayName()}.`,
participant.participant.name
}: ${JSON.stringify(result)}`,
); );
await this.deleteChallongeCache();
await Promise.all([
this.deleteChallongeCache(),
...roomsWithPlayer.map((r) =>
this.config.tournament.kickRoom(this.ctx, r.roomid),
),
]);
return '迟到杀成功。'; return '迟到杀成功。';
} }
...@@ -220,13 +252,117 @@ export class YGOTournamentPlugin { ...@@ -220,13 +252,117 @@ export class YGOTournamentPlugin {
}); });
} }
// WTF fix for missing ctx.http.delete
private workaroundQuester(quester: Quester) {
const config = quester.config;
const options: AxiosRequestConfig = {
timeout: config.timeout,
headers: config.headers,
};
if (config.proxyAgent) {
const agent = new ProxyAgent(config.proxyAgent);
options.httpAgent = agent;
options.httpsAgent = agent;
}
quester.delete = async (url, params, headers) => {
const { data } = await axios.delete(url, {
...options,
...config,
params,
headers: {
...options.headers,
...headers,
},
});
return data;
};
}
initializeTournament() { initializeTournament() {
if (!this.config.isTournamentEnabled()) { if (!this.config.isTournamentEnabled()) {
return; return;
} }
this.ctx.cache.table('challongeData', { this.workaroundQuester(this.ctx.http);
maxAge: this.config.tournament.challongeCacheTTL, this.ctx.cache.table('lateDeclarationTime', { maxAge: 3600 * 1000 });
}); this.ctx
.command('tournament/late', '迟到杀')
.shortcut('迟到杀')
.usage('迟到杀需要选手在房间内,且只有1人才可以迟到杀。')
.action(async ({ session }) => {
try {
return await this.onUserDeclareLate(session.userId);
} catch (e) {
this.ctx
.logger('challonge')
.error(`Failed to quit user ${session.userId}: ${e.toString()}`);
return '迟到杀出现了一些问题,请与技术人员联系。';
}
});
this.ctx
.command('tournament/quit', '退赛')
.shortcut('退赛')
.usage('退赛需要慎重,会取消后续的比赛资格。')
.action(async ({ session }) => {
await session.send('确定要退赛吗?输入 yes 以确认。');
const reply = await session.prompt();
if (reply !== 'yes') {
return '退赛被取消,接下来要继续努力哦。';
}
try {
return await this.onUserQuit(session.userId);
} catch (e) {
this.ctx
.logger('challonge')
.error(`Failed to quit user ${session.userId}: ${e.toString()}`);
return '退赛出现了一些问题,请与技术人员联系。';
}
});
const judgeCommand = this.ctx
.select(this.config.judgeSelection)
.command('tournament/judge', '裁判操作')
.usage('需要有裁判权限才能执行这些操作。');
judgeCommand
.subcommand('.enablelate', '开启迟到杀')
.usage('允许选手使用指令来进行迟到杀。')
.option('delay', '-d <time:posint> 在指定分钟后开启迟到杀。', {
fallback: 0,
})
.option('duration', '-t <time:posint> 迟到杀允许的分钟数。', {
fallback: 30,
})
.action(async ({ session, options }) => {
const fromTime = moment().add(options.delay, 'minutes');
const toTime = fromTime.clone().add(options.duration, 'minutes');
await this.ctx.cache.set(
'lateDeclarationTime',
'current',
{
fromTime: fromTime.unix(),
toTime: toTime.unix(),
},
(options.delay + options.duration) * 60000,
);
return `设置成功。将允许在 ${fromTime.format(
'HH:mm:ss',
)}${toTime.format('HH:mm:ss')} 期间允许迟到杀。`;
});
judgeCommand
.subcommand('.disablelate', '关闭迟到杀')
.usage('不再允许选手进行迟到杀操作。')
.action(async () => {
await this.ctx.cache.del('lateDeclarationTime', 'current');
return '设置成功。选手不再允许进行迟到杀操作。';
});
judgeCommand
.subcommand('.refresh', '清理 Challonge 缓存')
.action(async () => {
await this.ctx.cache.del(
'challongeData',
this.config.tournament.challongeTournamentId,
);
return '清理缓存成功。';
});
} }
name = 'ygotournament-main'; name = 'ygotournament-main';
......
...@@ -29,6 +29,8 @@ module.exports = { ...@@ -29,6 +29,8 @@ module.exports = {
}, },
externals: { externals: {
koishi: 'koishi', koishi: 'koishi',
'proxy-agent': 'proxy-agent',
axios: 'axios',
...(packAll ...(packAll
? {} ? {}
: { : {
...@@ -37,8 +39,10 @@ module.exports = { ...@@ -37,8 +39,10 @@ module.exports = {
'@aws-sdk/protocol-http': '@aws-sdk/protocol-http', '@aws-sdk/protocol-http': '@aws-sdk/protocol-http',
'@aws-sdk/smithy-client': '@aws-sdk/smithy-client', '@aws-sdk/smithy-client': '@aws-sdk/smithy-client',
'@aws-sdk/util-format-url': '@aws-sdk/util-format-url', '@aws-sdk/util-format-url': '@aws-sdk/util-format-url',
'@aws-sdk/types': '@aws-sdk/types',
'koishi-utils-schemagen': 'koishi-utils-schemagen', 'koishi-utils-schemagen': 'koishi-utils-schemagen',
'class-transformer': 'class-transformer', 'class-transformer': 'class-transformer',
moment: 'moment',
}), }),
}, },
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment