@kotori-bot/loader 1.4.2 → 1.5.0-beta.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.
@@ -1,9 +1,9 @@
1
- import { Container, Symbols } from '@kotori-bot/core';
1
+ import { Container, Symbols, formatFactory } from '@kotori-bot/core';
2
2
  import Logger from '@kotori-bot/logger';
3
3
  import Runner from './runner';
4
- import Server from './service/server';
5
- import type Database from './service/database';
6
- import File from './service/file';
4
+ import Server from '../service/server';
5
+ import type Database from '../service/database';
6
+ import File from '../service/file';
7
7
  declare module '@kotori-bot/core' {
8
8
  interface Context {
9
9
  readonly baseDir: Runner['baseDir'];
@@ -15,6 +15,8 @@ declare module '@kotori-bot/core' {
15
15
  server: Server;
16
16
  db: Database;
17
17
  file: File;
18
+ format: ReturnType<typeof formatFactory>;
19
+ locale: Context['i18n']['locale'];
18
20
  }
19
21
  interface GlobalConfig {
20
22
  dirs: string[];
@@ -22,11 +24,12 @@ declare module '@kotori-bot/core' {
22
24
  }
23
25
  }
24
26
  export declare class Loader extends Container {
25
- private ctx;
27
+ private readonly ctx;
26
28
  private loadCount;
27
29
  constructor(options?: {
28
30
  dir?: string;
29
31
  mode?: string;
32
+ level?: number;
30
33
  });
31
34
  run(): void;
32
35
  private handleError;
@@ -32,47 +32,44 @@ exports.Loader = void 0;
32
32
  * @Blog: https://hotaru.icu
33
33
  * @Date: 2023-06-24 15:12:55
34
34
  * @LastEditors: Hotaru biyuehuya@gmail.com
35
- * @LastEditTime: 2024-02-24 19:11:35
35
+ * @LastEditTime: 2024-05-26 16:45:40
36
36
  */
37
37
  const core_1 = require("@kotori-bot/core");
38
- const path_1 = __importDefault(require("path"));
39
- const fs_1 = __importDefault(require("fs"));
40
- const logger_1 = __importDefault(require("@kotori-bot/logger"));
38
+ const node_path_1 = __importDefault(require("node:path"));
39
+ const node_fs_1 = __importDefault(require("node:fs"));
40
+ const logger_1 = __importStar(require("@kotori-bot/logger"));
41
41
  const runner_1 = __importStar(require("./runner"));
42
- const log_1 = __importDefault(require("./log"));
43
- const consts_1 = require("./consts");
44
- const server_1 = __importDefault(require("./service/server"));
45
- const file_1 = __importDefault(require("./service/file"));
46
- function getRunnerConfig(file, dir) {
42
+ const log_1 = __importDefault(require("../utils/log"));
43
+ const constants_1 = require("../constants");
44
+ const server_1 = __importDefault(require("../service/server"));
45
+ const file_1 = __importDefault(require("../service/file"));
46
+ function getBaseDir(file, dir) {
47
47
  const handle = (root) => {
48
48
  const baseDir = {
49
49
  root,
50
- modules: path_1.default.join(root, 'modules'),
51
- data: path_1.default.join(root, 'data'),
52
- logs: path_1.default.join(root, 'logs')
50
+ modules: node_path_1.default.join(root, 'modules'),
51
+ data: node_path_1.default.join(root, 'data'),
52
+ logs: node_path_1.default.join(root, 'logs')
53
53
  };
54
54
  Object.values(baseDir).forEach((val) => {
55
- if (!fs_1.default.existsSync(val))
56
- fs_1.default.mkdirSync(val);
55
+ if (!node_fs_1.default.existsSync(val))
56
+ node_fs_1.default.mkdirSync(val);
57
57
  });
58
58
  return baseDir;
59
59
  };
60
- const options = {
61
- mode: file === consts_1.DEV_CONFIG_NAME ? 'dev' : 'build'
62
- };
63
60
  if (dir)
64
- return { baseDir: handle(path_1.default.resolve(dir)), options };
65
- let root = path_1.default.resolve(__dirname, '..').replace('loader', 'kotori');
61
+ return handle(node_path_1.default.resolve(dir));
62
+ let root = node_path_1.default.resolve(__dirname, '..').replace('loader', 'kotori');
66
63
  let count = 0;
67
- while (!fs_1.default.existsSync(path_1.default.join(root, file))) {
64
+ while (!node_fs_1.default.existsSync(node_path_1.default.join(root, file))) {
68
65
  if (count > 5) {
69
66
  logger_1.default.fatal(`cannot find file ${file} `);
70
67
  process.exit();
71
68
  }
72
- root = path_1.default.join(root, '..');
69
+ root = node_path_1.default.join(root, '..');
73
70
  count += 1;
74
71
  }
75
- return { baseDir: handle(root), options };
72
+ return handle(root);
76
73
  }
77
74
  /* eslint consistent-return: 0 */
78
75
  function getCoreConfig(file, baseDir) {
@@ -91,7 +88,7 @@ function getCoreConfig(file, baseDir) {
91
88
  .default(core_1.DEFAULT_CORE_CONFIG.plugin)
92
89
  })
93
90
  .default({ global: Object.assign(core_1.DEFAULT_CORE_CONFIG.global), plugin: core_1.DEFAULT_CORE_CONFIG.plugin })
94
- .parse((0, core_1.loadConfig)(path_1.default.join(baseDir.root, file), 'yaml'));
91
+ .parse((0, core_1.loadConfig)(node_path_1.default.join(baseDir.root, file), 'yaml'));
95
92
  return core_1.Tsu.Object({
96
93
  adapter: core_1.Tsu.Object({})
97
94
  .index(core_1.Tsu.Object({
@@ -115,13 +112,27 @@ class Loader extends core_1.Container {
115
112
  loadCount = 0;
116
113
  constructor(options) {
117
114
  super();
118
- const file = options && options.mode === 'dev' ? consts_1.DEV_CONFIG_NAME : consts_1.BUILD_CONFIG_NAME;
119
- const runnerConfig = getRunnerConfig(file, options?.dir);
115
+ const file = options && options.mode?.startsWith(constants_1.DEV_MODE) ? constants_1.DEV_CONFIG_NAME : constants_1.BUILD_CONFIG_NAME;
116
+ const runnerConfig = {
117
+ baseDir: getBaseDir(file, options?.dir),
118
+ options: { mode: (options?.mode || constants_1.BUILD_MODE) },
119
+ level: options?.level || options?.mode?.startsWith(constants_1.DEV_MODE) ? logger_1.LoggerLevel.DEBUG : logger_1.LoggerLevel.INFO
120
+ };
120
121
  const ctx = new core_1.Core(getCoreConfig(file, runnerConfig.baseDir));
121
122
  ctx.provide('runner', new runner_1.default(ctx, runnerConfig));
122
123
  ctx.mixin('runner', ['baseDir', 'options']);
123
124
  core_1.Container.setInstance(ctx);
125
+ ctx.provide('loader-tools', { format: (0, core_1.formatFactory)(ctx.i18n), locale: ctx.i18n.locale.bind(ctx.i18n) });
126
+ ctx.mixin('loader-tools', ['locale', 'format']);
127
+ ctx.i18n.use(node_path_1.default.resolve(__dirname, '../../locales'));
124
128
  this.ctx = core_1.Container.getInstance();
129
+ this.ctx.logger.trace(`options:`, options);
130
+ this.ctx.logger.trace(`runnerConfig:`, runnerConfig);
131
+ this.ctx.logger.trace(`baseDir:`, this.ctx.baseDir);
132
+ this.ctx.logger.trace(`options:`, this.ctx.options);
133
+ this.ctx.logger.trace(`config:`, this.ctx.config);
134
+ this.ctx.logger.trace(`where:`, __dirname, __filename);
135
+ this.ctx.logger.trace(`running:`, process.cwd());
125
136
  }
126
137
  run() {
127
138
  (0, log_1.default)(this.ctx.pkg, this.ctx);
@@ -153,42 +164,39 @@ class Loader extends core_1.Container {
153
164
  process.on('uncaughtExceptionMonitor', (err) => this.handleError(err, 'sync'));
154
165
  process.on('unhandledRejection', (err) => this.handleError(err, 'async'));
155
166
  process.on('SIGINT', () => process.exit());
156
- this.ctx.logger.debug('run info: develop with debuing...');
167
+ this.ctx.logger.debug(this.ctx.locale('loader.debug.info'));
157
168
  }
158
169
  listenMessage() {
159
170
  this.ctx.on('connect', (data) => {
160
- const { type, mode, normal, address, adapter } = data;
171
+ const { type, mode, normal, address: addr, adapter } = data;
161
172
  let msg;
162
173
  if (type === 'connect') {
163
174
  switch (mode) {
164
175
  case 'ws':
165
- msg = `${normal ? 'Connect' : 'Reconnect'} server to ${address}`;
176
+ msg = this.ctx.format(`loader.bots.${normal ? 'connect' : 'reconnect'}`, [addr]);
166
177
  break;
167
178
  case 'ws-reverse':
168
- msg = `server ${normal ? 'start' : 'restart'} at ${address}`;
179
+ msg = this.ctx.format(`loader.bots.${normal ? 'start' : 'restart'}`, [addr]);
169
180
  break;
170
181
  default:
171
- msg = `ready completed about ${address}`;
182
+ msg = this.ctx.format('loader.bots.ready', [addr]);
172
183
  }
173
184
  }
174
185
  else {
175
186
  switch (mode) {
176
187
  case 'ws':
177
- msg = `disconnect server from ${address}${normal ? '' : ' unexpectedly'}`;
188
+ msg = this.ctx.format(`loader.bots.disconnect${normal ? '' : '.error'}`, [addr]);
178
189
  break;
179
190
  case 'ws-reverse':
180
- msg = `server stop at ${address}${normal ? '' : ' unexpectedly'}`;
191
+ msg = this.ctx.format(`loader.bots.stop${normal ? '' : '.error'}`, [addr]);
181
192
  break;
182
193
  default:
183
- msg = `dispose completed about ${address}`;
194
+ msg = this.ctx.format('loader.bots.dispose', [addr]);
184
195
  }
185
196
  }
186
197
  adapter.ctx.logger[normal ? 'info' : 'warn'](msg);
187
198
  });
188
- this.ctx.on('status', (data) => {
189
- const { status, adapter } = data;
190
- adapter.ctx.logger.info(status);
191
- });
199
+ this.ctx.on('status', ({ status, adapter }) => adapter.ctx.logger.info(status));
192
200
  this.ctx.on('ready_module', (data) => {
193
201
  if (typeof data.instance !== 'object')
194
202
  return;
@@ -199,16 +207,17 @@ class Loader extends core_1.Container {
199
207
  return;
200
208
  this.loadCount += 1;
201
209
  const { name, version, author, peerDependencies } = pkg[0].pkg;
202
- this.ctx.logger.info(`loaded module ${name} version: ${version} ${Array.isArray(author) ? `authors: ${author.join(',')}` : `author: ${author}`}`);
210
+ this.ctx.logger.info(this.ctx.format('loader.modules.load', [name, version, Array.isArray(author) ? author.join(',') : author]));
203
211
  const requiredVersion = peerDependencies['kotori-bot'];
204
- if (!requiredVersion.includes('workspace') &&
205
- (!consts_1.SUPPORTS_VERSION.exec(requiredVersion) || requiredVersion !== this.ctx.pkg.version)) {
206
- if (consts_1.SUPPORTS_HALF_VERSION.exec(requiredVersion)) {
207
- this.ctx.logger.warn(`incomplete supported module version: ${requiredVersion}`);
208
- }
209
- else {
210
- this.ctx.logger.error(`unsupported module version: ${requiredVersion}`);
211
- }
212
+ if (requiredVersion.includes('workspace') ||
213
+ constants_1.SUPPORTS_VERSION.exec(requiredVersion) ||
214
+ requiredVersion.includes(this.ctx.pkg.version))
215
+ return;
216
+ if (constants_1.SUPPORTS_HALF_VERSION.exec(requiredVersion)) {
217
+ this.ctx.logger.warn(this.ctx.format('loader.modules.incomplete', [requiredVersion]));
218
+ }
219
+ else {
220
+ this.ctx.logger.error(this.ctx.format('loader.modules.unsupported', [requiredVersion]));
212
221
  }
213
222
  });
214
223
  }
@@ -219,7 +228,7 @@ class Loader extends core_1.Container {
219
228
  loadAllModules() {
220
229
  this.ctx.get('runner').loadAll();
221
230
  const failLoadCount = this.ctx.get('runner')[core_1.Symbols.modules].size - this.loadCount;
222
- this.ctx.logger.info(`loaded ${this.loadCount} modules successfully${failLoadCount > 0 ? `, failed to load ${failLoadCount} modules` : ''} `);
231
+ this.ctx.logger.info(this.ctx.format(`loader.modules.all${failLoadCount > 0 ? '.fail' : ''}`, [this.loadCount, failLoadCount]));
223
232
  this.loadAllAdapter();
224
233
  this.ctx.emit('ready');
225
234
  }
@@ -228,13 +237,11 @@ class Loader extends core_1.Container {
228
237
  Object.keys(this.ctx.config.adapter).forEach((botName) => {
229
238
  const botConfig = this.ctx.config.adapter[botName];
230
239
  const array = adapters.get(botConfig.extends);
231
- if (!array) {
232
- this.ctx.logger.warn(`cannot find adapter '${botConfig.extends}' for ${botName}`);
233
- return;
234
- }
240
+ if (!array)
241
+ return this.ctx.logger.warn(this.ctx.format('loader.adapters.notfound', [botConfig.extends, botName]));
235
242
  const result = array[1]?.parseSafe(botConfig);
236
243
  if (result && !result.value)
237
- throw new core_1.ModuleError(`Config format of adapter ${botName} is error: ${result.error.message}`);
244
+ throw new core_1.ModuleError(this.ctx.format('error.module.config_bot', [botName, result.error.message]));
238
245
  const bot = new array[0](this.ctx.extends({}, `${botConfig.extends}/${botName}`), result ? result.data : botConfig, botName);
239
246
  this.ctx.on('ready', () => bot.start());
240
247
  this.ctx.on('dispose', () => bot.stop());
@@ -243,16 +250,16 @@ class Loader extends core_1.Container {
243
250
  async checkUpdate() {
244
251
  const { version } = this.ctx.pkg;
245
252
  const res = await this.ctx.http
246
- .get("https://hotaru.icu/api/agent/?url=https://raw.githubusercontent.com/kotorijs/kotori/master/packages/kotori/package.json" /* GLOBAL.UPDATE */)
247
- .catch(() => this.ctx.logger.error('get update failed, please check your network'));
253
+ .get("https://hotaru.icu/api/agent/?url=https://raw.githubusercontent.com/kotorijs/kotori/master/packages/core/package.json" /* GLOBAL.UPDATE */)
254
+ .catch(() => this.ctx.logger.error(this.ctx.locale('loader.tips.update.failed')));
248
255
  if (!res || !core_1.Tsu.Object({ version: core_1.Tsu.String() }).check(res)) {
249
- this.ctx.logger.warn(`detection update failed`);
256
+ this.ctx.logger.warn(this.ctx.locale('loader.tips.update.failed'));
250
257
  }
251
258
  else if (version === res.version) {
252
- this.ctx.logger.info('kotori is currently the latest version');
259
+ this.ctx.logger.info(this.ctx.locale('loader.tips.update.latest'));
253
260
  }
254
261
  else {
255
- this.ctx.logger.warn(`the current version of Kotori is ${version}, and the latest version is ${res.version}. please go to ${"https://github.com/kotorijs/kotori" /* GLOBAL.REPO */} to update`);
262
+ this.ctx.logger.warn(this.ctx.format('loader.tips.update.available', [version, res.version, "https://github.com/kotorijs/kotori" /* GLOBAL.REPO */]));
256
263
  }
257
264
  }
258
265
  }
@@ -1,4 +1,6 @@
1
1
  import { Context, LocaleType, ModuleConfig, Symbols } from '@kotori-bot/core';
2
+ import { BUILD_MODE, DEV_MODE, DEV_SOURCE_MODE } from '../constants';
3
+ import './loader';
2
4
  interface BaseDir {
3
5
  root: string;
4
6
  modules: string;
@@ -6,11 +8,12 @@ interface BaseDir {
6
8
  logs: string;
7
9
  }
8
10
  interface Options {
9
- mode: 'dev' | 'build';
11
+ mode: typeof BUILD_MODE | typeof DEV_MODE | typeof DEV_SOURCE_MODE;
10
12
  }
11
13
  interface RunnerConfig {
12
14
  baseDir: BaseDir;
13
15
  options: Options;
16
+ level: number;
14
17
  }
15
18
  interface ModulePackage {
16
19
  name: string;
@@ -40,13 +43,16 @@ export declare const localeTypeSchema: import("@kotori-bot/core").UnionParser<[i
40
43
  export declare class Runner {
41
44
  readonly baseDir: BaseDir;
42
45
  readonly options: Options;
43
- private ctx;
44
- private isDev;
46
+ private readonly ctx;
47
+ private readonly isDev;
48
+ private readonly isSourceDev;
45
49
  readonly [Symbols.modules]: Map<string, [ModuleMeta, ModuleConfig]>;
46
50
  constructor(ctx: Context, config: RunnerConfig);
47
51
  private getDirFiles;
48
52
  private getModuleRootDir;
53
+ private checkModuleFiles;
49
54
  private getModuleList;
55
+ private loadLang;
50
56
  private loadEx;
51
57
  private unloadEx;
52
58
  loadAll(): void;
@@ -27,12 +27,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.Runner = exports.localeTypeSchema = void 0;
30
- const fs_1 = __importStar(require("fs"));
31
- const path_1 = __importDefault(require("path"));
30
+ const node_fs_1 = __importStar(require("node:fs"));
31
+ const node_path_1 = __importStar(require("node:path"));
32
32
  const core_1 = require("@kotori-bot/core");
33
33
  const logger_1 = require("@kotori-bot/logger");
34
- const consts_1 = require("./consts");
35
- const logger_2 = __importDefault(require("./utils/logger"));
34
+ const constants_1 = require("../constants");
35
+ const logger_2 = __importDefault(require("../utils/logger"));
36
+ require("./loader");
36
37
  exports.localeTypeSchema = core_1.Tsu.Union([
37
38
  core_1.Tsu.Union([core_1.Tsu.Literal('en_US'), core_1.Tsu.Literal('ja_JP')]),
38
39
  core_1.Tsu.Union([core_1.Tsu.Literal('zh_TW'), core_1.Tsu.Any()])
@@ -75,18 +76,37 @@ class Runner {
75
76
  options;
76
77
  ctx;
77
78
  isDev;
79
+ isSourceDev;
78
80
  [core_1.Symbols.modules] = new Map();
79
81
  constructor(ctx, config) {
80
82
  this.ctx = ctx;
81
83
  /* handle config */
82
84
  this.baseDir = config.baseDir;
83
85
  this.options = config.options;
84
- this.isDev = this.options.mode === 'dev';
86
+ this.isDev = this.options.mode.startsWith(constants_1.DEV_MODE);
87
+ this.isSourceDev = this.options.mode === constants_1.DEV_SOURCE_MODE;
85
88
  const loggerOptions = {
86
- level: this.isDev ? logger_1.LoggerLevel.TRACE : logger_1.LoggerLevel.INFO,
89
+ level: this.ctx.config.global.level ?? config.level,
87
90
  label: [],
88
91
  transports: [
89
- new logger_1.ConsoleTransport(),
92
+ new logger_1.ConsoleTransport({
93
+ template: '%time% %type% %label%%msg%',
94
+ label: '[%name%] ',
95
+ labelColor: 'cyan',
96
+ time: 'M/D H:m:s',
97
+ timeColor: 'blue',
98
+ pidColor: 'bold',
99
+ useColor: true,
100
+ detail: {
101
+ FATAL: ['FATAL', 'redBright', 'redBright'],
102
+ ERROR: ['ERROR', 'red', 'red'],
103
+ WARN: ['WARN', 'yellowBright', 'yellowBright'],
104
+ INFO: ['INFO', 'green'],
105
+ DEBUG: ['DEBUG', 'magenta', 'magentaBright'],
106
+ TRACE: ['TRACE', 'gray', 'gray']
107
+ },
108
+ indent: 2
109
+ }),
90
110
  new logger_1.FileTransport({ dir: this.baseDir.logs, filter: (data) => data.level >= logger_1.LoggerLevel.WARN })
91
111
  ]
92
112
  };
@@ -94,90 +114,116 @@ class Runner {
94
114
  ctx.inject('logger');
95
115
  }
96
116
  getDirFiles(rootDir) {
97
- const files = fs_1.default.readdirSync(rootDir);
117
+ const files = node_fs_1.default.readdirSync(rootDir);
98
118
  const list = [];
99
119
  files.forEach((fileName) => {
100
- const file = path_1.default.join(rootDir, fileName);
101
- if (fs_1.default.statSync(file).isDirectory()) {
120
+ const file = node_path_1.default.join(rootDir, fileName);
121
+ if (node_fs_1.default.statSync(file).isDirectory()) {
102
122
  list.push(...this.getDirFiles(file));
103
123
  }
104
- if (path_1.default.parse(file).ext !== (this.isDev ? consts_1.DEV_FILE : consts_1.BUILD_FILE))
124
+ if (node_path_1.default.parse(file).ext !== (this.isSourceDev ? constants_1.DEV_FILE : constants_1.BUILD_FILE))
105
125
  return;
106
- list.push(path_1.default.resolve(file));
126
+ list.push(node_path_1.default.resolve(file));
107
127
  });
108
128
  return list;
109
129
  }
110
130
  getModuleRootDir() {
111
131
  const moduleRootDir = [];
112
132
  [
113
- ...this.ctx.config.global.dirs.map((dir) => path_1.default.resolve(this.ctx.baseDir.root, dir)),
133
+ ...this.ctx.config.global.dirs.map((dir) => node_path_1.default.resolve(this.ctx.baseDir.root, dir)),
114
134
  this.ctx.baseDir.modules
115
135
  ].forEach((dir) => {
116
- if (fs_1.default.existsSync(dir) && fs_1.default.statSync(dir).isDirectory())
136
+ if (node_fs_1.default.existsSync(dir) && node_fs_1.default.statSync(dir).isDirectory())
117
137
  moduleRootDir.push(dir);
118
138
  });
119
139
  return moduleRootDir;
120
140
  }
121
- getModuleList(rootDir) {
122
- fs_1.default.readdirSync(rootDir).forEach(async (fileName) => {
123
- const dir = path_1.default.join(rootDir, fileName);
124
- if (!fs_1.default.statSync(dir).isDirectory())
125
- return;
126
- if (rootDir !== this.ctx.baseDir.modules && !fileName.startsWith(core_1.PLUGIN_PREFIX))
127
- return;
128
- const packagePath = path_1.default.join(dir, 'package.json');
129
- let pkg;
130
- if (!fs_1.default.existsSync(packagePath))
141
+ async checkModuleFiles(rootDir, filename) {
142
+ const dir = node_path_1.default.join(rootDir, filename);
143
+ if (!node_fs_1.default.statSync(dir).isDirectory())
144
+ return;
145
+ if (rootDir !== this.ctx.baseDir.modules && !filename.startsWith(core_1.PLUGIN_PREFIX))
146
+ return;
147
+ const packagePath = node_path_1.default.join(dir, 'package.json');
148
+ let pkg;
149
+ if (!node_fs_1.default.existsSync(packagePath))
150
+ return;
151
+ try {
152
+ pkg = JSON.parse(node_fs_1.default.readFileSync(packagePath).toString());
153
+ }
154
+ catch {
155
+ throw new core_1.DevError(this.ctx.format('error.dev.package.illegal', [packagePath]));
156
+ }
157
+ const result = modulePackageSchema.parseSafe(pkg);
158
+ if (!result.value) {
159
+ if (rootDir !== this.ctx.baseDir.modules)
131
160
  return;
132
- try {
133
- pkg = JSON.parse(fs_1.default.readFileSync(packagePath).toString());
134
- }
135
- catch {
136
- throw new core_1.DevError(`illegal package.json ${packagePath}`);
137
- }
138
- const result = modulePackageSchema.parseSafe(pkg);
139
- if (!result.value) {
140
- if (rootDir !== this.ctx.baseDir.modules)
141
- return;
142
- throw new core_1.DevError(`package.json format error ${packagePath}: ${result.error.message}`);
143
- }
144
- pkg = result.data;
145
- const devMode = this.isDev && (0, fs_1.existsSync)(path_1.default.resolve(dir, consts_1.DEV_IMPORT));
146
- const main = path_1.default.resolve(dir, devMode ? consts_1.DEV_IMPORT : pkg.main);
147
- if (!fs_1.default.existsSync(main))
148
- throw new core_1.DevError(`cannot find ${main}`);
149
- const dirs = path_1.default.join(dir, devMode ? consts_1.DEV_CODE_DIRS : path_1.default.dirname(pkg.main));
150
- const files = fs_1.default.statSync(dirs).isDirectory() ? this.getDirFiles(dirs) : [];
151
- this[core_1.Symbols.modules].set(pkg.name, [
152
- { pkg, files, main },
153
- this.ctx.config.plugin[(0, core_1.stringRightSplit)(pkg.name, core_1.PLUGIN_PREFIX)] || {}
154
- ]);
161
+ throw new core_1.DevError(this.ctx.format('error.dev.package.missing', [packagePath, result.error.message]));
162
+ }
163
+ pkg = result.data;
164
+ const devMode = this.isSourceDev && (0, node_fs_1.existsSync)(node_path_1.default.resolve(dir, constants_1.DEV_IMPORT));
165
+ const main = node_path_1.default.resolve(dir, devMode ? constants_1.DEV_IMPORT : pkg.main);
166
+ if (!node_fs_1.default.existsSync(main))
167
+ throw new core_1.DevError(this.ctx.format('error.dev.main_file', [main]));
168
+ const dirs = node_path_1.default.join(dir, devMode ? constants_1.DEV_CODE_DIRS : node_path_1.default.dirname(pkg.main));
169
+ const files = node_fs_1.default.statSync(dirs).isDirectory() ? this.getDirFiles(dirs) : [];
170
+ this[core_1.Symbols.modules].set(pkg.name, [
171
+ { pkg, files, main },
172
+ this.ctx.config.plugin[(0, core_1.stringRightSplit)(pkg.name, core_1.PLUGIN_PREFIX)] || {}
173
+ ]);
174
+ }
175
+ getModuleList(rootDir) {
176
+ this.ctx.logger.trace('load dirs:', rootDir);
177
+ node_fs_1.default.readdirSync(rootDir).forEach(async (filename) => {
178
+ await this.checkModuleFiles(rootDir, filename);
155
179
  });
156
180
  }
157
- loadEx(instance, config) {
181
+ loadLang(lang) {
182
+ if (lang)
183
+ this.ctx.i18n.use((0, node_path_1.resolve)(...(Array.isArray(lang) ? lang : [lang])));
184
+ }
185
+ loadEx(instance, origin) {
186
+ this.ctx.logger.trace('module:', instance, origin);
187
+ const parsed = (schema) => {
188
+ const result = schema.parseSafe(config);
189
+ if (!result.value)
190
+ throw new core_1.ModuleError(this.ctx.format('error.module.config', [pkg.name, result.error.message]));
191
+ return result.data;
192
+ };
158
193
  const { main, pkg } = instance;
159
194
  /* eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires */
160
195
  let obj = require(main);
161
- let handle = config;
196
+ let config = origin;
162
197
  const adapterName = pkg.name.split(core_1.ADAPTER_PREFIX)[1];
163
198
  if (core_1.Adapter.isPrototypeOf.call(core_1.Adapter, obj.default) &&
164
199
  adapterName &&
165
200
  (!obj.config || obj.config instanceof core_1.Parser)) {
201
+ /* Adapter Class */
166
202
  this.ctx[core_1.Symbols.adapter].set(adapterName, [obj.default, obj.config]);
167
203
  obj = {};
168
204
  }
169
205
  else if (core_1.Service.isPrototypeOf.call(core_1.Service, obj.default)) {
206
+ /* Service Class */
170
207
  obj = {};
171
208
  }
172
209
  else if (obj.config instanceof core_1.Parser) {
173
- const result = obj.config.parseSafe(handle);
174
- if (!result.value)
175
- throw new core_1.ModuleError(`Config format of module ${pkg.name} is error: ${result.error.message}`);
176
- handle = result.data;
210
+ config = parsed(obj.config);
177
211
  }
178
212
  if (obj.lang)
179
- this.ctx.i18n.use(Array.isArray(obj.lang) ? path_1.default.resolve(...obj.lang) : path_1.default.resolve(obj.lang));
180
- this.ctx.load({ name: pkg.name, ...obj, config: handle });
213
+ this.loadLang(obj.lang);
214
+ if (obj.default) {
215
+ if (obj.default.lang)
216
+ this.loadLang(obj.default.lang);
217
+ if (obj.default.config instanceof core_1.Parser)
218
+ config = parsed(obj.default.config);
219
+ }
220
+ else if (obj.Main) {
221
+ if (obj.Main.lang)
222
+ this.loadLang(obj.Main.lang);
223
+ if (obj.Main.config instanceof core_1.Parser)
224
+ config = parsed(obj.Main.config);
225
+ }
226
+ this.ctx.load({ name: pkg.name, ...obj, config });
181
227
  }
182
228
  unloadEx(instance) {
183
229
  instance.files.forEach((file) => delete require.cache[require.resolve(file)]);
@@ -194,8 +240,8 @@ class Runner {
194
240
  this.watcher();
195
241
  }
196
242
  watcher() {
197
- this[core_1.Symbols.modules].forEach((data) => data[0].files.forEach((file) => fs_1.default.watchFile(file, async () => {
198
- this.ctx.logger.debug(`file happen changed, module ${data[0].pkg.name} is reloading...`);
243
+ this[core_1.Symbols.modules].forEach((data) => data[0].files.forEach((file) => node_fs_1.default.watchFile(file, async () => {
244
+ this.ctx.logger.debug(this.ctx.format('loader.debug.reload', [data[0].pkg.name]));
199
245
  this.unloadEx(data[0]);
200
246
  this.loadEx(...data);
201
247
  })));
@@ -6,3 +6,6 @@ export declare const BUILD_CONFIG_NAME = "kotori.yml";
6
6
  export declare const DEV_CONFIG_NAME = "kotori.dev.yml";
7
7
  export declare const SUPPORTS_VERSION: RegExp;
8
8
  export declare const SUPPORTS_HALF_VERSION: RegExp;
9
+ export declare const BUILD_MODE: "build";
10
+ export declare const DEV_MODE: "dev";
11
+ export declare const DEV_SOURCE_MODE: "dev-source";
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEV_SOURCE_MODE = exports.DEV_MODE = exports.BUILD_MODE = exports.SUPPORTS_HALF_VERSION = exports.SUPPORTS_VERSION = exports.DEV_CONFIG_NAME = exports.BUILD_CONFIG_NAME = exports.DEV_IMPORT = exports.DEV_CODE_DIRS = exports.BUILD_FILE = exports.DEV_FILE = void 0;
4
+ exports.DEV_FILE = '.ts';
5
+ exports.BUILD_FILE = '.js';
6
+ exports.DEV_CODE_DIRS = './src/';
7
+ exports.DEV_IMPORT = `${exports.DEV_CODE_DIRS}index.ts`;
8
+ exports.BUILD_CONFIG_NAME = 'kotori.yml';
9
+ exports.DEV_CONFIG_NAME = 'kotori.dev.yml';
10
+ exports.SUPPORTS_VERSION = /(1\.1\.0)|(1\.2\.0)|(1\.3\.(.*))|(1\.4\.(.*))/;
11
+ exports.SUPPORTS_HALF_VERSION = /(x\.x\.(.*?))/;
12
+ exports.BUILD_MODE = 'build';
13
+ exports.DEV_MODE = 'dev';
14
+ exports.DEV_SOURCE_MODE = 'dev-source';
@@ -0,0 +1,5 @@
1
+ import Decorators from './utils';
2
+ export declare function plugins(plugin: string | string[] | {
3
+ name: string;
4
+ }): Decorators;
5
+ export default plugins;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.plugins = void 0;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const core_1 = require("@kotori-bot/core");
10
+ const utils_1 = __importDefault(require("./utils"));
11
+ function plugins(plugin) {
12
+ let pkgName;
13
+ if (!Array.isArray(plugin) && typeof plugin === 'object') {
14
+ pkgName = plugin.name;
15
+ }
16
+ else {
17
+ pkgName = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.resolve)(...(Array.isArray(plugin) ? plugin : [plugin]), 'package.json')).toString()).name;
18
+ }
19
+ const pluginName = pkgName.split(core_1.PLUGIN_PREFIX)[1] ?? pkgName;
20
+ const ctx = core_1.Container.getInstance().extends(undefined, pluginName);
21
+ return new utils_1.default(ctx);
22
+ }
23
+ exports.plugins = plugins;
24
+ exports.default = plugins;
@@ -0,0 +1,35 @@
1
+ import { Context, EventsList, CommandAccess, MessageScope } from '@kotori-bot/core';
2
+ export declare class Decorators {
3
+ private readonly ctx;
4
+ private isCreated;
5
+ private pkgName;
6
+ private object?;
7
+ private register;
8
+ constructor(ctx: Context);
9
+ readonly import: (Target: object) => void;
10
+ readonly lang: <T extends object>(target: T, property: keyof T) => void;
11
+ readonly inject: <T extends object>(target: T, property: keyof T) => void;
12
+ readonly schema: <T extends object>(Target: T, property: keyof T) => void;
13
+ on<T extends keyof EventsList>(meta: {
14
+ type: T;
15
+ }): (...args: any[]) => void;
16
+ once<T extends keyof EventsList>(meta: {
17
+ type: T;
18
+ }): (...args: any[]) => void;
19
+ midware(meta?: {
20
+ priority: number;
21
+ }): (...args: any[]) => void;
22
+ command(meta: {
23
+ template: string;
24
+ alias?: string[];
25
+ description?: string;
26
+ help?: string;
27
+ scope?: MessageScope | 'all';
28
+ access?: CommandAccess;
29
+ options?: [string, string][];
30
+ }): (...args: any[]) => void;
31
+ regexp(meta: {
32
+ match: RegExp;
33
+ }): (...args: any[]) => void;
34
+ }
35
+ export default Decorators;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Decorators = void 0;
4
+ const node_path_1 = require("node:path");
5
+ const core_1 = require("@kotori-bot/core");
6
+ class Decorators {
7
+ ctx;
8
+ isCreated = false;
9
+ pkgName;
10
+ object;
11
+ /* eslint-disable @typescript-eslint/no-explicit-any */
12
+ register(callback) {
13
+ return (...args) => this.ctx.parent.once('ready_module', (data) => {
14
+ if (data.instance.name === this.pkgName) {
15
+ callback(...args);
16
+ return;
17
+ }
18
+ this.register(callback);
19
+ });
20
+ }
21
+ /* eslint-enable @typescript-eslint/no-explicit-any */
22
+ constructor(ctx) {
23
+ this.ctx = ctx;
24
+ this.pkgName = `${core_1.PLUGIN_PREFIX}${ctx.identity}`;
25
+ }
26
+ import = (Target) => {
27
+ this.register(() => {
28
+ if (!this.isCreated)
29
+ this.schema(Target, undefined);
30
+ })();
31
+ };
32
+ lang = (target, property) => {
33
+ this.register(() => {
34
+ const lang = target[property];
35
+ this.ctx.parent.i18n.use((0, node_path_1.resolve)(...(Array.isArray(lang) ? lang : [lang])));
36
+ })();
37
+ };
38
+ inject = (target, property) => {
39
+ this.register(() => {
40
+ const inject = target[property];
41
+ inject.forEach((identity) => this.ctx[core_1.Tokens.container].forEach((service, name) => {
42
+ if (!(service instanceof core_1.Service) || service.identity !== identity)
43
+ return;
44
+ this.ctx.inject(name);
45
+ }));
46
+ })();
47
+ };
48
+ schema = (Target, property) => {
49
+ this.register(() => {
50
+ let config = (this.ctx[core_1.Symbols.modules].get(this.pkgName) ?? [])[1];
51
+ const result = Target[property].parseSafe(config);
52
+ if (!result.value)
53
+ throw new core_1.ModuleError(`Config format of module ${this.pkgName} is error: ${result.error.message}`);
54
+ config = result.data;
55
+ if (this.isCreated)
56
+ return;
57
+ this.isCreated = true;
58
+ this.object = new Target(this.ctx, config);
59
+ })();
60
+ };
61
+ on(meta) {
62
+ return this.register((target, property) => this.ctx.on(meta.type, (...args) => target[property].bind(this.object)(...args)));
63
+ }
64
+ once(meta) {
65
+ return this.register((target, property) => this.ctx.once(meta.type, (...args) => target[property].bind(this.object)(...args)));
66
+ }
67
+ midware(meta) {
68
+ return this.register((target, property) => this.ctx.midware((next, session) => target[property].bind(this.object)(next, session), meta?.priority));
69
+ }
70
+ command(meta) {
71
+ return this.register((target, property) => {
72
+ const command = this.ctx
73
+ .command(meta.template, meta)
74
+ .action((data, session) => target[property].bind(this.object)(data, session));
75
+ meta.options?.forEach(([name, template]) => command.option(name, template));
76
+ });
77
+ }
78
+ regexp(meta) {
79
+ return this.register((target, property) => this.ctx.regexp(meta.match, (match, session) => target[property].bind(this.object)(match, session)));
80
+ }
81
+ }
82
+ exports.Decorators = Decorators;
83
+ exports.default = Decorators;
package/lib/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export * from './loader';
2
- export * from './consts';
1
+ export * from './class/loader';
2
+ export * from './constants';
3
+ export * from './decorators';
4
+ export * from './types';
3
5
  export * from '@kotori-bot/logger';
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * @Blog: https://hotaru.icu
5
5
  * @Date: 2023-10-29 16:20:51
6
6
  * @LastEditors: Hotaru biyuehuya@gmail.com
7
- * @LastEditTime: 2024-02-20 20:20:54
7
+ * @LastEditTime: 2024-05-26 15:36:01
8
8
  */
9
9
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
10
  if (k2 === undefined) k2 = k;
@@ -21,6 +21,8 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
21
21
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
- __exportStar(require("./loader"), exports);
25
- __exportStar(require("./consts"), exports);
24
+ __exportStar(require("./class/loader"), exports);
25
+ __exportStar(require("./constants"), exports);
26
+ __exportStar(require("./decorators"), exports);
27
+ __exportStar(require("./types"), exports);
26
28
  __exportStar(require("@kotori-bot/logger"), exports);
@@ -0,0 +1,15 @@
1
+ import { Api, Adapter as OriginAdapter } from '@kotori-bot/core';
2
+ import { WsRouteHandler } from '../types/server';
3
+ export declare namespace Adapter {
4
+ abstract class WebSocket<T extends Api = Api> extends OriginAdapter<T> {
5
+ private isSetup;
6
+ private destroyFn?;
7
+ protected destroy(): void;
8
+ protected setup(): void;
9
+ abstract handle<T extends object>(data: T): void;
10
+ connection?: (ws: Parameters<WsRouteHandler>[0], req: Parameters<WsRouteHandler>[1]) => void;
11
+ start(): void;
12
+ stop(): void;
13
+ }
14
+ }
15
+ export default Adapter;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Adapter = void 0;
4
+ const core_1 = require("@kotori-bot/core");
5
+ /* eslint-disable-next-line @typescript-eslint/no-namespace */
6
+ var Adapter;
7
+ (function (Adapter) {
8
+ class WebSocket extends core_1.Adapter {
9
+ isSetup = false;
10
+ destroyFn;
11
+ destroy() {
12
+ if (!this.destroyFn)
13
+ return;
14
+ this.destroyFn();
15
+ this.isSetup = false;
16
+ }
17
+ setup() {
18
+ if (this.isSetup)
19
+ return;
20
+ this.ctx.inject('server');
21
+ this.destroyFn = this.ctx.server.wss(`/adapter/${this.identity}`, (ws, req) => {
22
+ if (this.connection)
23
+ this.connection(ws, req);
24
+ ws.on('message', (raw) => {
25
+ let data;
26
+ try {
27
+ data = JSON.parse(raw.toString());
28
+ }
29
+ catch (e) {
30
+ this.ctx.logger.error(`Data parse error: ${e instanceof Error ? e.message : e}`);
31
+ }
32
+ if (data)
33
+ this.handle(data);
34
+ });
35
+ });
36
+ this.isSetup = true;
37
+ }
38
+ connection;
39
+ start() {
40
+ this.setup();
41
+ }
42
+ stop() {
43
+ this.destroy();
44
+ }
45
+ }
46
+ Adapter.WebSocket = WebSocket;
47
+ })(Adapter || (exports.Adapter = Adapter = {}));
48
+ exports.default = Adapter;
@@ -3,7 +3,7 @@ export declare class File extends Service {
3
3
  constructor(ctx: Context);
4
4
  getDir(): string;
5
5
  getFile(filename: string): string;
6
- load(filename: string, type?: Parameters<typeof loadConfig>[1], init?: Parameters<typeof loadConfig>[2]): string | object | unknown[] | null;
6
+ load<T = Parameters<typeof saveConfig>[1]>(filename: string, type?: Parameters<typeof loadConfig>[1], init?: Parameters<typeof loadConfig>[2]): T;
7
7
  save(filename: string, data: Parameters<typeof saveConfig>[1], type?: Parameters<typeof saveConfig>[2]): void;
8
8
  create(filename: string, data?: Parameters<typeof createConfig>[1], type?: Parameters<typeof createConfig>[2]): void;
9
9
  }
@@ -2,17 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.File = void 0;
4
4
  const core_1 = require("@kotori-bot/core");
5
- const path_1 = require("path");
5
+ const node_path_1 = require("node:path");
6
6
  class File extends core_1.Service {
7
7
  constructor(ctx) {
8
8
  super(ctx, {}, 'file');
9
9
  }
10
10
  getDir() {
11
- return (0, path_1.join)(this.ctx.baseDir.data, ...(this.ctx.identity ? this.ctx.identity.split('/') : []));
11
+ return (0, node_path_1.join)(this.ctx.baseDir.data, ...(this.ctx.identity ? this.ctx.identity.split('/') : []));
12
12
  }
13
13
  getFile(filename) {
14
- return (0, path_1.join)(this.getDir(), filename);
14
+ return (0, node_path_1.join)(this.getDir(), filename);
15
15
  }
16
+ /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
16
17
  load(filename, type, init) {
17
18
  return (0, core_1.loadConfig)(this.getFile(filename), type, init);
18
19
  }
@@ -1,26 +1,63 @@
1
- /// <reference types="body-parser" />
2
- /// <reference types="connect" />
3
- /// <reference types="serve-static" />
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
4
3
  import { Context, Service } from '@kotori-bot/core';
5
- import express from 'express';
4
+ import { IncomingMessage, ServerResponse } from 'node:http';
5
+ import { HttpRouteHandler, HttpRoutes, WsRouteHandler } from '../types/server';
6
6
  interface ServerConfig {
7
7
  port: number;
8
8
  }
9
- export declare class Server extends Service<ServerConfig> {
9
+ interface BodyParserOptions {
10
+ inflate?: boolean | undefined;
11
+ limit?: number | string | undefined;
12
+ type?: string | string[] | ((req: IncomingMessage) => any) | undefined;
13
+ verify?(req: IncomingMessage, res: ServerResponse, buf: Buffer, encoding: string): void;
14
+ }
15
+ interface UrlencodedOptions extends BodyParserOptions {
16
+ extended?: boolean | undefined;
17
+ parameterLimit?: number | undefined;
18
+ }
19
+ interface ServeStaticOptions<R extends ServerResponse = ServerResponse> {
20
+ acceptRanges?: boolean | undefined;
21
+ cacheControl?: boolean | undefined;
22
+ dotfiles?: string | undefined;
23
+ etag?: boolean | undefined;
24
+ extensions?: string[] | false | undefined;
25
+ fallthrough?: boolean | undefined;
26
+ immutable?: boolean | undefined;
27
+ index?: boolean | string | string[] | undefined;
28
+ lastModified?: boolean | undefined;
29
+ maxAge?: number | string | undefined;
30
+ redirect?: boolean | undefined;
31
+ setHeaders?: ((res: R, path: string, stat: any) => any) | undefined;
32
+ }
33
+ interface RouterOptions {
34
+ caseSensitive?: boolean | undefined;
35
+ /**
36
+ * @default false
37
+ * @since 4.5.0
38
+ */
39
+ mergeParams?: boolean | undefined;
40
+ strict?: boolean | undefined;
41
+ }
42
+ export declare class Server extends Service<ServerConfig> implements HttpRoutes {
10
43
  private app;
11
- private server?;
44
+ private server;
45
+ private wsServer;
46
+ private wsRoutes;
12
47
  constructor(ctx: Context, config: ServerConfig);
13
48
  start(): void;
14
49
  stop(): void;
15
- get: Server['app']['get'];
16
- post: Server['app']['post'];
17
- patch: Server['app']['patch'];
18
- put: Server['app']['put'];
19
- delete: Server['app']['delete'];
20
- all: Server['app']['all'];
21
- use: Server['app']['use'];
22
- router: typeof express.Router;
23
- json: (options?: import("body-parser").OptionsJson | undefined) => import("connect").NextHandleFunction;
24
- static: import("serve-static").RequestHandlerConstructor<express.Response<any, Record<string, any>>>;
50
+ get<P extends string>(path: P, ...callback: HttpRouteHandler<P>[]): void;
51
+ post<P extends string>(path: P, ...callback: HttpRouteHandler<P>[]): void;
52
+ patch<P extends string>(path: P, ...callback: HttpRouteHandler<P>[]): void;
53
+ put<P extends string>(path: P, ...callback: HttpRouteHandler<P>[]): void;
54
+ delete<P extends string>(path: P, ...callback: HttpRouteHandler<P>[]): void;
55
+ all<P extends string>(path: P, ...callback: HttpRouteHandler<P>[]): void;
56
+ use<P extends string>(path: P | HttpRouteHandler | HttpRoutes, ...callback: (HttpRouteHandler<P> | HttpRoutes)[]): void;
57
+ router: (options?: RouterOptions) => HttpRoutes;
58
+ json: (options?: BodyParserOptions) => HttpRouteHandler;
59
+ static: (root: string, options?: ServeStaticOptions) => HttpRouteHandler;
60
+ urlencoded: (options?: UrlencodedOptions) => HttpRouteHandler;
61
+ wss<P extends string>(path: P, callback: WsRouteHandler<P>): () => boolean;
25
62
  }
26
63
  export default Server;
@@ -5,14 +5,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Server = void 0;
7
7
  const core_1 = require("@kotori-bot/core");
8
+ const node_http_1 = require("node:http");
9
+ const path_to_regexp_1 = require("path-to-regexp");
8
10
  const express_1 = __importDefault(require("express"));
11
+ const ws_1 = __importDefault(require("ws"));
9
12
  class Server extends core_1.Service {
10
13
  app;
11
14
  server;
15
+ wsServer;
16
+ wsRoutes = new Map();
12
17
  constructor(ctx, config) {
13
18
  super(ctx, config, 'server');
14
19
  this.app = (0, express_1.default)();
15
- this.app.use('/', (_, res, next) => {
20
+ this.app.use(express_1.default.json());
21
+ this.app.use('/', (req, res, next) => {
16
22
  let isWebui = false;
17
23
  ctx[core_1.Symbols.modules].forEach((module) => {
18
24
  if (isWebui)
@@ -20,41 +26,84 @@ class Server extends core_1.Service {
20
26
  if (module[0].pkg.name === '@kotori-bot/kotori-plugin-webui')
21
27
  isWebui = true;
22
28
  });
23
- if (!isWebui) {
24
- res.setHeader('Content-type', 'text/html');
25
- res.send(/* html */ `<h1>Welcome to kotori!</h1>`);
29
+ if (isWebui || req.url !== '/') {
30
+ next();
31
+ return;
26
32
  }
27
- next();
33
+ res.setHeader('Content-type', 'text/html');
34
+ res.send(/* html */ `<h1>Welcome to kotori!</h1>`);
35
+ });
36
+ this.server = (0, node_http_1.createServer)(this.app);
37
+ this.wsServer = new ws_1.default.Server({ noServer: true });
38
+ this.server.on('upgrade', (req, socket, head) => {
39
+ this.wsServer.handleUpgrade(req, socket, head, (ws) => {
40
+ this.wsServer.emit('connection', ws, req);
41
+ });
42
+ });
43
+ this.wsServer.on('connection', (ws, req) => {
44
+ let triggered = false;
45
+ /* eslint-disable no-restricted-syntax,no-continue */
46
+ for (const [template, list] of this.wsRoutes.entries()) {
47
+ if (!req.url)
48
+ continue;
49
+ const result = (0, path_to_regexp_1.match)(template, { decode: decodeURIComponent })(req.url);
50
+ if (!result)
51
+ continue;
52
+ if (!triggered)
53
+ triggered = true;
54
+ list.forEach((callback) => {
55
+ callback(ws, Object.assign(req, { params: result.params }));
56
+ });
57
+ }
58
+ /* eslint-enable no-restricted-syntax,no-continue */
59
+ if (!triggered)
60
+ ws.close(1002);
28
61
  });
29
- this.get = this.app.get.bind(this.app);
30
- this.post = this.app.post.bind(this.app);
31
- this.patch = this.app.patch.bind(this.app);
32
- this.put = this.app.put.bind(this.app);
33
- this.delete = this.app.delete.bind(this.app);
34
- this.use = this.app.use.bind(this.app);
35
- this.all = this.app.all.bind(this.app);
36
62
  }
37
63
  start() {
38
- if (this.server)
39
- return;
40
- this.server = this.app.listen(this.config.port);
41
- this.ctx.logger.label('server').info(`server start at http://127.0.0.1:${this.config.port}`);
64
+ this.server.listen(this.config.port, () => {
65
+ this.ctx.logger.label('server').info(`http server start at http://127.0.0.1:${this.config.port}`);
66
+ this.ctx.logger.label('server').info(`websocket server start at ws://127.0.0.1:${this.config.port}`);
67
+ });
42
68
  }
43
69
  stop() {
44
- if (!this.server)
45
- return;
70
+ this.wsServer.close();
46
71
  this.server.close();
47
72
  }
48
- get;
49
- post;
50
- patch;
51
- put;
52
- delete;
53
- all;
54
- use;
73
+ get(path, ...callback) {
74
+ this.app.get(path, ...callback);
75
+ }
76
+ post(path, ...callback) {
77
+ this.app.post(path, ...callback);
78
+ }
79
+ patch(path, ...callback) {
80
+ this.app.patch(path, ...callback);
81
+ }
82
+ put(path, ...callback) {
83
+ this.app.put(path, ...callback);
84
+ }
85
+ delete(path, ...callback) {
86
+ this.app.delete(path, ...callback);
87
+ }
88
+ all(path, ...callback) {
89
+ this.app.all(path, ...callback);
90
+ }
91
+ use(path, ...callback) {
92
+ if (typeof path === 'string')
93
+ this.app.use(path, ...callback);
94
+ else
95
+ this.app.use('/', path, ...callback);
96
+ }
55
97
  router = express_1.default.Router;
56
98
  json = express_1.default.json;
57
99
  static = express_1.default.static;
100
+ urlencoded = express_1.default.urlencoded;
101
+ wss(path, callback) {
102
+ const list = this.wsRoutes.get(path) || new Set();
103
+ list.add(callback);
104
+ this.wsRoutes.set(path, list);
105
+ return () => list.delete(callback);
106
+ }
58
107
  }
59
108
  exports.Server = Server;
60
109
  exports.default = Server;
@@ -0,0 +1 @@
1
+ export * from './server';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./server"), exports);
@@ -0,0 +1,27 @@
1
+ /// <reference types="node" />
2
+ import { NextFunction, Request, Response } from 'express';
3
+ import { IncomingMessage } from 'node:http';
4
+ import Ws from 'ws';
5
+ type RemoveTail<S extends string, Tail extends string> = S extends `${infer P}${Tail}` ? P : S;
6
+ type GetRouteParameter<S extends string> = RemoveTail<RemoveTail<RemoveTail<S, `/${string}`>, `-${string}`>, `.${string}`>;
7
+ interface ParamsDictionary {
8
+ [key: string]: string;
9
+ }
10
+ type RouteParameters<Route extends string> = string extends Route ? ParamsDictionary : Route extends `${string}(${string}` ? ParamsDictionary : Route extends `${string}:${infer Rest}` ? (GetRouteParameter<Rest> extends never ? ParamsDictionary : GetRouteParameter<Rest> extends `${infer ParamName}?` ? {
11
+ [P in ParamName]?: string;
12
+ } : {
13
+ [P in GetRouteParameter<Rest>]: string;
14
+ }) & (Rest extends `${GetRouteParameter<Rest>}${infer Next}` ? RouteParameters<Next> : unknown) : object;
15
+ export type HttpRouteHandler<P extends string = string> = (req: Request<RouteParameters<P>>, res: Response, next: NextFunction) => unknown;
16
+ type KeyList = 'get' | 'post' | 'patch' | 'put' | 'delete' | 'all';
17
+ export type WsRouteHandler<P extends string = ''> = (ws: Ws, req: IncomingMessage & {
18
+ params: RouteParameters<P>;
19
+ }) => void;
20
+ type HttpRoutesReflect = {
21
+ [K in KeyList]: <P extends string>(path: P, ...callback: HttpRouteHandler<P>[]) => unknown;
22
+ };
23
+ export interface HttpRoutes extends HttpRoutesReflect {
24
+ use<P extends string>(path: P, ...callback: (HttpRouteHandler<P> | HttpRoutes)[]): unknown;
25
+ use(...callback: (HttpRouteHandler | HttpRoutes)[]): unknown;
26
+ }
27
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kotori-bot/loader",
3
- "version": "1.4.2",
3
+ "version": "1.5.0-beta.1",
4
4
  "description": "Loader For KotoriBot",
5
5
  "license": "GPL-3.0",
6
6
  "main": "lib/index.js",
@@ -24,9 +24,11 @@
24
24
  },
25
25
  "homepage": "https://kotori.js.org",
26
26
  "dependencies": {
27
+ "@types/ws": "^8.5.8",
27
28
  "express": "^4.18.2",
28
29
  "knex": "^3.1.0",
29
- "@kotori-bot/core": "^1.3.0",
30
+ "ws": "^8.14.2",
31
+ "@kotori-bot/core": "^1.4.0",
30
32
  "@kotori-bot/logger": "^1.2.0"
31
33
  },
32
34
  "devDependencies": {
package/lib/consts.js DELETED
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SUPPORTS_HALF_VERSION = exports.SUPPORTS_VERSION = exports.DEV_CONFIG_NAME = exports.BUILD_CONFIG_NAME = exports.DEV_IMPORT = exports.DEV_CODE_DIRS = exports.BUILD_FILE = exports.DEV_FILE = void 0;
4
- exports.DEV_FILE = '.ts';
5
- exports.BUILD_FILE = '.js';
6
- exports.DEV_CODE_DIRS = './src/';
7
- exports.DEV_IMPORT = `${exports.DEV_CODE_DIRS}index.ts`;
8
- exports.BUILD_CONFIG_NAME = 'kotori.yml';
9
- exports.DEV_CONFIG_NAME = 'kotori.dev.yml';
10
- exports.SUPPORTS_VERSION = /(1\.1\.0)|(1\.2\.0)|(1\.3\.(.*?))/;
11
- exports.SUPPORTS_HALF_VERSION = /(x\.x\.(.*?))/;
File without changes
File without changes