Commit 6252b3fe authored by nanahira's avatar nanahira

rearrange modules

parent 3ae9ffa2
Pipeline #43330 passed with stages
in 3 minutes and 14 seconds
......@@ -44,3 +44,4 @@ Dockerfile
/ssl
/ygopro
/windbot
/src/plugins
......@@ -40,3 +40,4 @@ lerna-debug.log*
/ssl
/ygopro
/windbot
/src/plugins
{
"python-envs.pythonProjects": []
}
......@@ -83,6 +83,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
......@@ -573,6 +574,7 @@
"resolved": "https://registry.npmjs.org/@cordisjs/core/-/core-3.18.1.tgz",
"integrity": "sha512-yRuATOamFxeD1ztE2L3o1SaHuT2zw5DTXijQYxt9azVNeILbvtomfGG6sLZegrynJO9XZbNvXbOlQ7LBJDOlAg==",
"license": "MIT",
"peer": true,
"dependencies": {
"cosmokit": "^1.6.2"
}
......@@ -609,6 +611,7 @@
"resolved": "https://registry.npmjs.org/@cordisjs/plugin-http/-/plugin-http-0.6.3.tgz",
"integrity": "sha512-kmw4G1t39ZZzFGpBKUNuTv2aefEUtxa1e4qu4+bTrM5Z8uYHTRzPpyJfkj9zuZwopP/Vz271OUr2UfDN5lrJKQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"cosmokit": "^1.6.3",
"file-type": "^16.5.4",
......@@ -1464,6 +1467,7 @@
"resolved": "https://registry.npmjs.org/@koishijs/core/-/core-4.18.10.tgz",
"integrity": "sha512-P2dc9EqoMasqqdUz3iKyGbcd0krAG7dMT7Ju6CqrzAobj0w7i548cCE89mD3qTzhvdTKzbf4UBYDRg21lXrb4A==",
"license": "MIT",
"peer": true,
"dependencies": {
"@koishijs/i18n-utils": "^1.0.1",
"@koishijs/utils": "^7.2.1",
......@@ -2117,6 +2121,7 @@
"integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.55.0",
"@typescript-eslint/types": "8.55.0",
......@@ -2622,6 +2627,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
......@@ -3017,6 +3023,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
......@@ -3471,6 +3478,7 @@
"resolved": "https://registry.npmjs.org/cordis/-/cordis-3.18.1.tgz",
"integrity": "sha512-9IbthFbFBVJ15WBDNPqHdl59TyEAMNWxF1Dxsdi14625ePQkMX35WvcqgGno0BcXlpXDbUaBZgQ+jfBYT+uuSQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@cordisjs/core": "3.18.1",
"@cordisjs/loader": "^0.13.1",
......@@ -3891,6 +3899,7 @@
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
......@@ -3947,6 +3956,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
......@@ -4940,6 +4950,7 @@
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.2.tgz",
"integrity": "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@ioredis/commands": "1.5.0",
"cluster-key-slot": "^1.1.0",
......@@ -5227,6 +5238,7 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
......@@ -6020,6 +6032,7 @@
"resolved": "https://registry.npmjs.org/koishi/-/koishi-4.18.10.tgz",
"integrity": "sha512-rEbF2NuHDAsBJ0+5B12RsRk7QWM/1sZvMnlaSj2TqUP+558Z1+lVZO9HhsyxrEp+r+bheRNfpJDN8aO369/myg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@koishijs/core": "4.18.10",
"@koishijs/loader": "4.6.10",
......@@ -6689,6 +6702,7 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz",
"integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.11.0",
"pg-pool": "^3.11.0",
......@@ -7009,6 +7023,7 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
......@@ -8252,6 +8267,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -8516,6 +8532,7 @@
"resolved": "https://registry.npmjs.org/typed-struct/-/typed-struct-2.7.1.tgz",
"integrity": "sha512-GluzA9kYlHjATJmzBDA2X9G9237Md5zsJsc8uEkmpvUFeuUvt+e7Sq11/nQnVB2VZIfKNR1CrwTCgpJVz52pAA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
......@@ -8650,6 +8667,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
......
......@@ -16,6 +16,9 @@ import { KoaService } from './services/koa-service';
import { FileResourceService } from './file-resource';
import { LegacyApiAuthService } from './services/legacy-api-auth-service';
import { LegacyApiModule } from './legacy-api/legacy-api-module';
import { TailModule } from './tail-module';
import { PreJoinModule } from './pre-join';
import { PluginLoader } from './plugin-loader';
const core = createAppContext()
.provide(ConfigService, {
......@@ -51,8 +54,11 @@ export type ContextState = AppContextState<Context>;
export const app = core
.use(TransportModule)
.use(RoomModule)
.use(FeatsModule)
.use(LegacyApiModule)
.use(RoomModule)
.use(PreJoinModule)
.use(PluginLoader())
.use(JoinHandlerModule)
.use(TailModule)
.define();
......@@ -20,7 +20,9 @@ import {
import { YGOProCtosDisconnect } from '../utility/ygopro-ctos-disconnect';
export class ClientHandler {
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx
.middleware(YGOProCtosExternalAddress, async (msg, client, next) => {
if (client.ip) {
......
......@@ -8,6 +8,9 @@ export class I18nService extends I18n {
locales: Object.keys(TRANSLATIONS),
defaultLocale: 'en-US',
});
}
async init() {
this.middleware(I18nLookupMiddleware(TRANSLATIONS));
}
}
......@@ -42,7 +42,9 @@ export class IpResolver {
{ count: this.trustedProxies.length },
'Trusted proxies initialized',
);
}
async init() {
this.ctx.middleware(YGOProCtosDisconnect, async (_msg, client, next) => {
await this.releaseClientIp(client);
return next();
......
......@@ -4,7 +4,9 @@ import { Context } from '../app';
export class BlockReplay {
private enabled = this.ctx.config.getBoolean('BLOCK_REPLAY_TO_PLAYER');
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.enabled) {
return;
}
......
......@@ -69,7 +69,9 @@ export class ChallongeService {
private challongeApi?: Challonge;
private challongeApiFingerprint = '';
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.registerLockDeckCheck();
this.registerScoreHooks();
this.registerToObserverGuard();
......@@ -463,10 +465,14 @@ export class ChallongeService {
if (participant0Id == null && participant1Id != null) {
participant0Id =
participant1Id === match.player1_id ? match.player2_id : match.player1_id;
participant1Id === match.player1_id
? match.player2_id
: match.player1_id;
} else if (participant1Id == null && participant0Id != null) {
participant1Id =
participant0Id === match.player1_id ? match.player2_id : match.player1_id;
participant0Id === match.player1_id
? match.player2_id
: match.player1_id;
}
return [participant0Id, participant1Id];
......
......@@ -50,48 +50,50 @@ export class ChatgptService {
private tiktokenUnavailableLogged = false;
private tokenizerByModel = new Map<string, TiktokenEncoder>();
private tiktokenModulePromise?: Promise<any>;
private middlewareRegistered = false;
constructor(private ctx: Context) {
if (!this.enabled) {
return;
}
this.ctx.middleware(YGOProCtosChat, async (msg, client, next) => {
const room = this.resolveChatRoom(client);
if (!room) {
return next();
}
const content = (msg.msg || '').trim();
if (!this.shouldRespond(client, room, content)) {
return next();
}
if (room.isRequestingChatgpt) {
return next();
}
constructor(private ctx: Context) {}
room.isRequestingChatgpt = true;
void this.requestChatgptAndReply(room, client, content)
.catch((error) => {
this.logger.error(
{
roomName: room.name,
clientName: client.name,
error: (error as Error).toString(),
},
'CHATGPT ERROR',
);
})
.finally(() => {
room.isRequestingChatgpt = false;
async init() {
if (!this.middlewareRegistered) {
if (this.enabled) {
this.ctx.middleware(YGOProCtosChat, async (msg, client, next) => {
const room = this.resolveChatRoom(client);
if (!room) {
return next();
}
const content = (msg.msg || '').trim();
if (!this.shouldRespond(client, room, content)) {
return next();
}
if (room.isRequestingChatgpt) {
return next();
}
room.isRequestingChatgpt = true;
void this.requestChatgptAndReply(room, client, content)
.catch((error) => {
this.logger.error(
{
roomName: room.name,
clientName: client.name,
error: (error as Error).toString(),
},
'CHATGPT ERROR',
);
})
.finally(() => {
room.isRequestingChatgpt = false;
});
return next();
});
}
this.middlewareRegistered = true;
}
return next();
});
}
async init() {
if (
!this.enabled ||
this.tiktokenUnavailable ||
......
......@@ -69,7 +69,9 @@ export class CloudReplayService {
private clientKeyProvider = this.ctx.get(() => ClientKeyProvider);
private menuManager = this.ctx.get(() => MenuManager);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(OnRoomWin, async (event, _client, next) => {
await this.saveDuelRecord(event);
return next();
......
import { createAppContext } from 'nfkit';
import { ClientVersionCheck } from './client-version-check';
import { Welcome } from './welcome';
import { PlayerStatusNotify } from './player-status-notify';
import { Reconnect, RefreshFieldService } from './reconnect';
......@@ -20,6 +19,7 @@ import { BlockReplay } from './block-replay';
import { RoomDeathService } from './room-death-service';
import { RoomAutoDeathService } from './room-auto-death-service';
import { ChallongeService } from './challonge-service';
import { TagSurrenderConfirmMiddleware } from './tag-surrender-confirm-middleware';
export const FeatsModule = createAppContext()
.provide(ClientKeyProvider)
......@@ -28,8 +28,7 @@ export const FeatsModule = createAppContext()
.provide(CommandsService) // some chat commands
.provide(Welcome)
.provide(MenuManager)
.provide(ClientVersionCheck)
.provide(PlayerStatusNotify)
.provide(PlayerStatusNotify) // hint meessages when player status changes
.provide(CloudReplayService) // persist duel records
.provide(BlockReplay) // block replay packets for in-room players
.provide(ChatgptService) // AI-room chat replies
......@@ -38,10 +37,11 @@ export const FeatsModule = createAppContext()
.provide(RoomAutoDeathService) // auto trigger death mode after duel start
.provide(ChallongeService) // challonge deck lock + score sync
.provide(LockDeckService) // srvpro-style tournament deck lock check
.provide(RefreshFieldService)
.provide(Reconnect)
.provide(RefreshFieldService) // utility for
.provide(Reconnect) // allow players to reconnect to ongoing duels without leaving the room
.provide(WaitForPlayerProvider) // chat refresh
.provide(SideTimeout)
.provide(SideTimeout) // side timeout in duel
.provide(TagSurrenderConfirmMiddleware) // teammate-confirm surrender in tag duel
.use(ResourceModule) // chat bad words
.use(RandomDuelModule) // chat random duel block
.use(WindbotModule)
......
......@@ -12,7 +12,9 @@ export class HidePlayerNameProvider {
private roomManager = this.ctx.get(() => RoomManager);
private hidePlayerNameMode = this.resolveMode();
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.enabled) {
return;
}
......
export * from './client-version-check';
export * from '../pre-join/client-version-check';
export * from './client-key-provider';
export * from './chatgpt-service';
export * from './block-replay';
......
......@@ -20,7 +20,9 @@ class SrvproDeckBadError extends YGOProLFListError {
}
export class LockDeckService {
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.ctx.config.getBoolean('TOURNAMENT_MODE_CHECK_DECK')) {
return;
}
......
......@@ -10,7 +10,9 @@ import { RoomManager } from '../room';
export class LpLowHintService {
private roomManager = this.ctx.get(() => RoomManager);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProMsgDamage, async (msg, client, next) => {
await this.trySendLowLpHint(msg.player, client, '#{lp_low_opponent}');
return next();
......
......@@ -49,7 +49,9 @@ export class MenuManager {
private chnroute = this.ctx.get(() => Chnroute);
private welcome = this.ctx.get(() => Welcome);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(
YGOProCtosBase,
async (msg, client, next) => {
......
......@@ -4,7 +4,9 @@ import { OnRoomJoinObserver } from '../room/room-event/on-room-join-observer';
import { OnRoomLeave } from '../room/room-event/on-room-leave';
export class PlayerStatusNotify {
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
// 观战者加入
this.ctx.middleware(OnRoomJoinObserver, async (event, client, next) => {
const room = event.room;
......
......@@ -22,7 +22,7 @@ import { fillRandomString } from '../../utility/fill-random-string';
import {
OnClientBadwordViolation,
OnClientWaitTimeout,
} from '../random-duel-events';
} from './random-duel-events';
import { CanReconnectCheck } from '../reconnect';
import { WaitForPlayerProvider } from '../wait-for-player-provider';
import { ClientKeyProvider } from '../client-key-provider';
......@@ -118,22 +118,12 @@ export class RandomDuelProvider {
private blankPassModes = this.resolveBlankPassModes();
private supportedTypes = this.resolveSupportedTypes();
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.enabled) {
return;
}
this.registerRandomRoomModes();
this.waitForPlayerProvider.registerTick({
roomFilter: (room) => !!room.randomType,
raadyTimeoutMs: this.waitForPlayerReadyTimeoutMs,
hangTimeoutMs: this.waitForPlayerHangTimeoutMs,
longAgoBackoffMs: this.waitForPlayerLongAgoBackoffMs,
});
if (this.recordMatchScoresConfigured && !this.ctx.database) {
this.logger.warn(
'RANDOM_DUEL_RECORD_MATCH_SCORES is enabled but database is unavailable',
);
}
this.ctx.middleware(CanReconnectCheck, async (msg, _client, next) => {
if (msg.room.randomType && this.getDisconnectedCount(msg.room) > 1) {
......@@ -206,6 +196,19 @@ export class RandomDuelProvider {
return next();
},
);
this.registerRandomRoomModes();
this.waitForPlayerProvider.registerTick({
roomFilter: (room) => !!room.randomType,
raadyTimeoutMs: this.waitForPlayerReadyTimeoutMs,
hangTimeoutMs: this.waitForPlayerHangTimeoutMs,
longAgoBackoffMs: this.waitForPlayerLongAgoBackoffMs,
});
if (this.recordMatchScoresConfigured && !this.ctx.database) {
this.logger.warn(
'RANDOM_DUEL_RECORD_MATCH_SCORES is enabled but database is unavailable',
);
}
}
get defaultType() {
......
import { Client } from '../client';
import { Room } from '../room';
import { Client } from '../../client';
import { Room } from '../../room';
export type RandomDuelWaitTimeoutType = 'ready' | 'hang';
......
......@@ -59,7 +59,9 @@ export class Reconnect {
private clientKeyProvider = this.ctx.get(() => ClientKeyProvider);
private refreshFieldService = this.ctx.get(() => RefreshFieldService);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
// 检查是否启用断线重连(默认启用)
if (!this.ctx.config.getBoolean('ENABLE_RECONNECT')) {
return;
......
......@@ -4,7 +4,7 @@ import { Client } from '../../client';
import { Room, RoomManager } from '../../room';
import { escapeRegExp } from '../../utility/escape-regexp';
import { ValueContainer } from '../../utility/value-container';
import { OnClientBadwordViolation } from '../random-duel-events';
import { OnClientBadwordViolation } from '../random-duel/random-duel-events';
import { BaseResourceProvider } from './base-resource-provider';
import { isObjectRecord } from './resource-util';
import { BadwordsData, EMPTY_BADWORDS_DATA } from './types';
......@@ -60,46 +60,49 @@ export class BadwordProvider extends BaseResourceProvider<BadwordsData> {
resourceName: 'badwords',
emptyData: EMPTY_BADWORDS_DATA,
});
}
if (!this.enabled) {
return;
}
async init() {
if (this.enabled) {
this.ctx.middleware(YGOProCtosChat, async (msg, client, next) => {
if (client.isInternal) {
return next();
}
const room = client.roomName
? this.roomManager.findByName(client.roomName)
: undefined;
const originalMessage = msg.msg;
const filtered = await this.filterText(originalMessage, room, client);
if (filtered.level >= 0) {
await this.ctx.dispatch(
new OnClientBadwordViolation(
client,
room,
originalMessage,
filtered.level,
filtered.message !== originalMessage
? filtered.message
: undefined,
),
client as any,
);
}
if (filtered.blocked) {
await client.sendChat('#{chat_warn_level2}', ChatColor.RED);
return;
}
if (filtered.message !== msg.msg) {
msg.msg = filtered.message;
await client.sendChat('#{chat_warn_level1}', ChatColor.BABYBLUE);
}
this.ctx.middleware(YGOProCtosChat, async (msg, client, next) => {
if (client.isInternal) {
return next();
}
const room = client.roomName
? this.roomManager.findByName(client.roomName)
: undefined;
const originalMessage = msg.msg;
const filtered = await this.filterText(originalMessage, room, client);
if (filtered.level >= 0) {
await this.ctx.dispatch(
new OnClientBadwordViolation(
client,
room,
originalMessage,
filtered.level,
filtered.message !== originalMessage ? filtered.message : undefined,
),
client as any,
);
}
if (filtered.blocked) {
await client.sendChat('#{chat_warn_level2}', ChatColor.RED);
return;
}
if (filtered.message !== msg.msg) {
msg.msg = filtered.message;
await client.sendChat('#{chat_warn_level1}', ChatColor.BABYBLUE);
}
return next();
});
});
}
await super.init();
}
async refreshResources() {
......
......@@ -38,15 +38,16 @@ export class DialoguesProvider extends BaseResourceProvider<DialoguesData> {
resourceName: 'dialogues',
emptyData: EMPTY_DIALOGUES_DATA,
});
}
if (!this.enabled) {
return;
async init() {
if (this.enabled) {
this.ctx.middleware(YGOProMsgBase, async (msg, client, next) => {
await this.handleDialogueMessage(msg, client);
return next();
});
}
this.ctx.middleware(YGOProMsgBase, async (msg, client, next) => {
await this.handleDialogueMessage(msg, client);
return next();
});
await super.init();
}
async refreshResources() {
......
......@@ -58,14 +58,15 @@ export class TipsProvider extends BaseResourceProvider<TipsData> {
}
await this.sendRandomTip(commandContext.client, commandContext.room);
});
this.ctx.middleware(OnRoomDuelStart, async (event, _client, next) => {
await this.sendRandomTipToRoom(event.room);
return next();
});
}
async init() {
if (this.enabled) {
this.ctx.middleware(OnRoomDuelStart, async (event, _client, next) => {
await this.sendRandomTipToRoom(event.room);
return next();
});
}
await super.init();
this.registerAutoTipTimers();
}
......
......@@ -24,18 +24,19 @@ export class WordsProvider extends BaseResourceProvider<WordsData> {
resourceName: 'words',
emptyData: EMPTY_WORDS_DATA,
});
}
if (!this.enabled) {
return;
async init() {
if (this.enabled) {
this.ctx.middleware(OnRoomJoin, async (event, client, next) => {
const line = await this.getRandomWords(event.room, client);
if (line) {
await event.room.sendChat(line, ChatColor.PINK);
}
return next();
});
}
this.ctx.middleware(OnRoomJoin, async (event, client, next) => {
const line = await this.getRandomWords(event.room, client);
if (line) {
await event.room.sendChat(line, ChatColor.PINK);
}
return next();
});
await super.init();
}
async refreshResources() {
......
......@@ -32,7 +32,9 @@ export class RoomAutoDeathService {
Number.isFinite(deathTime) && deathTime > 0 ? deathTime : 40,
};
});
}
async init() {
this.ctx.middleware(OnRoomGameStart, async (event, _client, next) => {
const scheduled = this.tryScheduleAutoDeath(
event.room.name,
......
......@@ -13,7 +13,9 @@ declare module '../room' {
export class RoomDeathService {
private roomManager = this.ctx.get(() => RoomManager);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(OnRoomDuelStart, async (event, _client, next) => {
const room = event.room;
if (!room.death) {
......
......@@ -6,16 +6,16 @@ import {
} from 'ygopro-msg-encode';
import { Context } from '../app';
import { Client } from '../client';
import { DuelStage } from './duel-stage';
import { Room } from './room';
import { RoomManager } from './room-manager';
import { YGOProCtosDisconnect } from '../utility/ygopro-ctos-disconnect';
import { Room, DuelStage, RoomManager } from '../room';
export class TagSurrenderConfirmMiddleware {
// Track per-room pending teammate-confirm surrender by player position.
private pendingByRoom = new WeakMap<Room, Set<number>>();
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProMsgNewTurn, async (msg, client, next) => {
const room = this.getRoom(client);
if (room?.isTag && (msg.player & 0x2) === 0) {
......
......@@ -20,7 +20,7 @@ import {
Room,
RoomManager,
} from '../room';
import { OnClientWaitTimeout } from './random-duel-events';
import { OnClientWaitTimeout } from './random-duel/random-duel-events';
export interface WaitForPlayerConfig {
roomFilter: (room: Room) => boolean;
......@@ -55,7 +55,9 @@ export class WaitForPlayerProvider {
private tickRuntimes = new Map<number, WaitForPlayerTickRuntime>();
private nextTickId = 1;
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(
YGOProMsgResponseBase,
async (msg, client, next) => {
......
......@@ -18,7 +18,9 @@ declare module '../client' {
}
export class Welcome {
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(OnRoomJoin, async (event, client, next) => {
const room = event.room;
await this.sendConfigWelcome(client);
......
......@@ -15,7 +15,9 @@ export class JoinWindbotAi {
private windbotProvider = this.ctx.get(() => WindBotProvider);
private roomManager = this.ctx.get(() => RoomManager);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.windbotProvider.enabled) {
return;
}
......
......@@ -9,7 +9,9 @@ export class JoinWindbotToken {
private logger = this.ctx.createLogger(this.constructor.name);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.windbotProvider.enabled) {
return;
}
......
......@@ -94,7 +94,12 @@ export class WindBotProvider {
}
await this.requestWindbotJoin(room, botName);
});
}
async init() {
if (!this.enabled) {
return;
}
this.ctx
.middleware(OnRoomFinalize, async (event, _client, next) => {
this.deleteRoomToken(event.room.name);
......@@ -110,12 +115,6 @@ export class WindBotProvider {
},
true,
);
}
async init() {
if (!this.enabled) {
return;
}
await this.loadBotList();
}
......
......@@ -8,7 +8,9 @@ export class ChallongeJoinHandler {
private challongeService = this.ctx.get(() => ChallongeService);
private roomManager = this.ctx.get(() => RoomManager);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!this.challongeService.enabled) {
return next();
......
......@@ -7,7 +7,9 @@ export class CloudReplayJoinHandler {
private enabled =
this.ctx.config.getBoolean('ENABLE_CLOUD_REPLAY') && !!this.ctx.database;
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!this.enabled) {
return next();
......
......@@ -2,7 +2,9 @@ import { YGOProCtosJoinGame } from 'ygopro-msg-encode';
import { Context } from '../app';
export class JoinFallback {
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
return client.die('#{blank_room_name}');
});
......
......@@ -13,7 +13,9 @@ export class JoinBlankPassMenu {
private enabled = this.ctx.config.getBoolean('ENABLE_MENU');
private rootMenu = this.loadRootMenu();
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.enabled) {
return;
}
......
......@@ -5,7 +5,9 @@ import { RandomDuelProvider } from '../feats';
export class JoinBlankPassRandomDuel {
private randomDuelProvider = this.ctx.get(() => RandomDuelProvider);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
msg.pass = (msg.pass || '').trim();
if (msg.pass || !this.randomDuelProvider.enabled) {
......
......@@ -5,7 +5,9 @@ import { JoinWindbotAi } from '../feats/windbot';
export class JoinBlankPassWindbotAi {
private joinWindbotAi = this.ctx.get(() => JoinWindbotAi);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
msg.pass = (msg.pass || '').trim();
if (msg.pass) {
......
......@@ -9,7 +9,9 @@ export class JoinBotlist {
private joinWindbotAi = this.ctx.get(() => JoinWindbotAi);
private windbotProvider = this.ctx.get(() => WindBotProvider);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.windbotProvider.enabled) {
return;
}
......
import { createAppContext } from 'nfkit';
import { ContextState } from '../app';
import { ClientVersionCheck } from '../feats';
import { JoinWindbotAi, JoinWindbotToken } from '../feats/windbot';
import { JoinWindbotAi } from '../feats/windbot';
import { JoinRoom } from './join-room';
import { JoinRoomIp } from './join-room-ip';
import { CloudReplayJoinHandler } from './cloud-replay-join-handler';
import { JoinFallback } from './fallback';
import { JoinPrechecks } from './join-prechecks';
import { RandomDuelJoinHandler } from './random-duel-join-handler';
import { BadwordPlayerInfoChecker } from './badword-player-info-checker';
import { JoinBlankPassRandomDuel } from './join-blank-pass-random-duel';
import { JoinBlankPassWindbotAi } from './join-blank-pass-windbot-ai';
import { JoinBlankPassMenu } from './join-blank-pass-menu';
......@@ -17,10 +13,6 @@ import { JoinBotlist } from './join-botlist';
import { ChallongeJoinHandler } from './challonge-join-handler';
export const JoinHandlerModule = createAppContext<ContextState>()
.provide(ClientVersionCheck)
.provide(JoinPrechecks)
.provide(JoinWindbotToken)
.provide(BadwordPlayerInfoChecker)
.provide(JoinRoomIp) // IP
.provide(CloudReplayJoinHandler) // R, R#, W, W#, YRP#
.provide(JoinRoomlist) // L
......@@ -32,5 +24,4 @@ export const JoinHandlerModule = createAppContext<ContextState>()
.provide(JoinBlankPassMenu) // blank pass below
.provide(JoinBlankPassRandomDuel)
.provide(JoinBlankPassWindbotAi)
.provide(JoinFallback)
.define();
......@@ -2,7 +2,9 @@ import { ChatColor, YGOProCtosJoinGame } from 'ygopro-msg-encode';
import { Context } from '../app';
export class JoinRoomIp {
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
const pass = (msg.pass || '').trim();
if (!pass) {
......
......@@ -4,7 +4,10 @@ import { RoomManager } from '../room/room-manager';
export class JoinRoom {
private logger = this.ctx.createLogger(this.constructor.name);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!msg.pass) {
return next();
......
......@@ -10,7 +10,9 @@ export class JoinRoomlist {
private roomManager = this.ctx.get(() => RoomManager);
private enabled = this.ctx.config.getBoolean('ENABLE_ROOMLIST');
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!this.enabled) {
return next();
......
......@@ -5,7 +5,9 @@ import { RandomDuelProvider } from '../feats';
export class RandomDuelJoinHandler {
private randomDuelProvider = this.ctx.get(() => RandomDuelProvider);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (!this.randomDuelProvider.enabled) {
return;
}
......
......@@ -99,10 +99,10 @@ export class KoishiContextService {
private koishiStarted = false;
private koishiStartPromise?: Promise<void>;
private chatBridgeRegistered = false;
constructor(private ctx: Context) {
this.koishi.plugin(koishiHelp);
this.registerChatBridge();
}
get instance() {
......@@ -124,6 +124,10 @@ export class KoishiContextService {
}
async init() {
if (!this.chatBridgeRegistered) {
this.registerChatBridge();
this.chatBridgeRegistered = true;
}
if (this.attachI18nTasks.length) {
await Promise.all(this.attachI18nTasks);
this.attachI18nTasks = [];
......
......@@ -41,10 +41,13 @@ export class LegacyApiDeckService {
constructor(private ctx: Context) {
this.registerRoutes();
this.registerLockDeckCheck();
void this.ensureBackgroundsFresh();
}
async init() {
this.registerLockDeckCheck();
}
private registerRoutes() {
const router = this.ctx.router;
......
......@@ -8,6 +8,15 @@ export class LegacyBanService {
private logger = this.ctx.createLogger('LegacyBanService');
constructor(private ctx: Context) {
this.ctx
.get(() => LegacyApiService)
.addApiMessageHandler('ban', 'ban_user', async (value) => {
const result = await this.banUser(value);
return [result ? 'ban ok' : 'ban fail', value];
});
}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (client.isLocal || client.isInternal) {
return next();
......@@ -36,13 +45,6 @@ export class LegacyBanService {
}
return next();
});
this.ctx
.get(() => LegacyApiService)
.addApiMessageHandler('ban', 'ban_user', async (value) => {
const result = await this.banUser(value);
return [result ? 'ban ok' : 'ban fail', value];
});
}
async banUser(name: string) {
......
......@@ -10,7 +10,9 @@ export class LegacyRoomIdService {
private roomNameToRoomId = new Map<string, string>();
private roomIdToRoomName = new Map<string, string>();
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(OnRoomCreate, async (event, client, next) => {
this.bindRoom(event.room.name, event.room.identifier);
return next();
......
......@@ -10,13 +10,6 @@ export class LegacyStopService {
private stopText?: string;
constructor(private ctx: Context) {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!this.stopText) {
return next();
}
return client.die(this.stopText, ChatColor.RED);
});
this.ctx
.get(() => LegacyApiService)
.addApiMessageHandler('stop', 'stop', async (value) => {
......@@ -26,6 +19,13 @@ export class LegacyStopService {
}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!this.stopText) {
return next();
}
return client.die(this.stopText, ChatColor.RED);
});
const text = await this.loadStopTextFromDatabase();
this.stopText = text;
if (text) {
......
......@@ -9,14 +9,6 @@ export class LegacyWelcomeService {
private logger = this.ctx.createLogger('LegacyWelcomeService');
constructor(private ctx: Context) {
this.ctx.middleware(WelcomeConfigCheck, async (event, client, next) => {
const dbWelcome = await this.getWelcomeFromDatabase();
if (dbWelcome) {
event.use(dbWelcome);
}
return next();
});
this.ctx
.get(() => LegacyApiService)
.addApiMessageHandler('getwelcome', 'change_settings', async () => {
......@@ -29,6 +21,16 @@ export class LegacyWelcomeService {
});
}
async init() {
this.ctx.middleware(WelcomeConfigCheck, async (event, client, next) => {
const dbWelcome = await this.getWelcomeFromDatabase();
if (dbWelcome) {
event.use(dbWelcome);
}
return next();
});
}
async getWelcomeText() {
const dbWelcome = await this.getWelcomeFromDatabase();
if (dbWelcome) {
......
import { createAppContext } from 'nfkit';
import { ContextState } from './app';
export const PluginLoader = () => {
const ctx = createAppContext<ContextState>();
return ctx;
};
......@@ -6,7 +6,9 @@ export class BadwordPlayerInfoChecker {
private logger = this.ctx.createLogger(this.constructor.name);
private badwordProvider = this.ctx.get(() => BadwordProvider);
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
if (this.ctx.config.getBoolean('TOURNAMENT_MODE')) {
return;
}
......
......@@ -10,7 +10,9 @@ export class ClientVersionCheck {
version = this.ctx.config.getInt('YGOPRO_VERSION');
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(
YGOProCtosJoinGame,
async (msg, client, next) => {
......
export * from './badword-player-info-checker';
export * from './join-prechecks';
export * from './client-version-check';
export * from './pre-join-module';
......@@ -2,7 +2,9 @@ import { ChatColor, YGOProCtosJoinGame } from 'ygopro-msg-encode';
import { Context } from '../app';
export class JoinPrechecks {
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(YGOProCtosJoinGame, async (msg, client, next) => {
if (!client.name || !client.name.length) {
return client.die('#{bad_user_name}', ChatColor.RED);
......
import { createAppContext } from 'nfkit';
import { ContextState } from '../app';
import { ClientVersionCheck } from './client-version-check';
import { JoinPrechecks } from './join-prechecks';
import { JoinWindbotToken } from '../feats/windbot';
import { BadwordPlayerInfoChecker } from './badword-player-info-checker';
export const PreJoinModule = createAppContext<ContextState>()
.provide(ClientVersionCheck)
.provide(JoinPrechecks)
.provide(JoinWindbotToken) // AIJOIN#
.provide(BadwordPlayerInfoChecker)
.define();
......@@ -3,8 +3,10 @@ import { checkDeck } from '../utility/check-deck';
import { RoomCheckDeck } from './room-event/room-check-deck';
export class DefaultDeckChecker {
constructor(private ctx: Context) {
ctx.middleware(RoomCheckDeck, (msg, client, next) => {
constructor(private ctx: Context) {}
async init() {
this.ctx.middleware(RoomCheckDeck, (msg, client, next) => {
const { room, deck, cardReader } = msg;
if (room.hostinfo.no_check_deck) {
return next();
......
......@@ -11,7 +11,9 @@ import { makeArray } from 'nfkit';
export class RoomEventRegister {
private logger = this.ctx.createLogger('RoomEventRegister');
constructor(private ctx: Context) {
constructor(private ctx: Context) {}
async init() {
this.registerRoomEvents();
}
......
......@@ -2,15 +2,12 @@ import { createAppContext } from 'nfkit';
import { ContextState } from '../app';
import { YGOProResourceLoader } from './ygopro-resource-loader';
import { DefaultHostInfoProvider } from './default-hostinfo-provder';
import { RoomEventRegister } from './room-event-register';
import { RoomManager } from './room-manager';
import { TagSurrenderConfirmMiddleware } from './tag-surrender-confirm-middleware';
import { DefaultDeckChecker } from './default-deck-checker';
export const RoomModule = createAppContext<ContextState>()
.provide(DefaultHostInfoProvider)
.provide(YGOProResourceLoader)
.provide(RoomManager)
.provide(TagSurrenderConfirmMiddleware)
.provide(DefaultDeckChecker)
.provide(RoomEventRegister);
.define();
import { createAppContext } from 'nfkit';
import { ContextState } from './app';
import { JoinFallback } from './join-handlers/fallback';
import { RoomEventRegister } from './room/room-event-register';
export const TailModule = createAppContext<ContextState>()
.provide(RoomEventRegister)
.provide(JoinFallback)
.define();
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