UNPKG

onebots

Version:

基于icqq的多例oneBot实现

418 lines (417 loc) 16.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.App = void 0; exports.createOnebots = createOnebots; exports.defineConfig = defineConfig; const koa_1 = __importDefault(require("koa")); const os = __importStar(require("os")); require("reflect-metadata"); const fs = __importStar(require("fs")); const fs_1 = require("fs"); const log4js_1 = require("log4js"); const http_1 = require("http"); const koa_static_1 = __importDefault(require("koa-static")); const js_yaml_1 = __importDefault(require("js-yaml")); const koa_bodyparser_1 = __importDefault(require("koa-bodyparser")); const koa_basic_auth_1 = __importDefault(require("koa-basic-auth")); const utils_1 = require("../utils"); const router_1 = require("./router"); const V11_1 = require("../service/V11"); const V12_1 = require("../service/V12"); const path = __importStar(require("path")); const process_1 = __importDefault(require("process")); class App extends koa_1.default { static get configPath() { return path.join(App.configDir, "config.yaml"); } static get dataDir() { return path.join(App.configDir, "data"); } static get logFile() { return path.join(App.configDir, "onebots.log"); } get info() { const pkg = require(path.resolve(__dirname, "../../package.json")); const free_memory = os.freemem(); const total_memory = os.totalmem(); return { system_platform: process_1.default.platform, system_arch: process_1.default.arch, system_cpus: os.cpus(), system_version: os.version(), system_uptime: os.uptime() * 1000, username: os.userInfo().username, total_memory, free_memory, process_id: process_1.default.pid, process_parent_id: process_1.default.ppid, process_cwd: process_1.default.cwd(), process_use_memory: process_1.default.memoryUsage.rss(), node_version: process_1.default.version, sdk_version: pkg.version, uptime: process_1.default.uptime() * 1000, }; } constructor(config = {}) { super(config); this.isStarted = false; this.adapters = new Map(); this.config = (0, utils_1.deepMerge)((0, utils_1.deepClone)(App.defaultConfig), config); this.init(); } init() { this.logger = (0, log4js_1.getLogger)("[OneBots]"); this.logger.level = this.config.log_level; this.httpServer = (0, http_1.createServer)(this.callback()); this.router = new router_1.Router(this.httpServer, { prefix: this.config.path }); this.use((0, koa_bodyparser_1.default)()) .use(this.router.routes()) .use(this.router.allowedMethods()) .use(async (ctx, next) => { const adapter = ctx.path?.slice(1)?.split("/")[0]; if (this.adapters.has(adapter)) return next(); return (0, koa_basic_auth_1.default)({ name: this.config.username, pass: this.config.password, })(ctx, next); }) .use((0, koa_static_1.default)(path.resolve(__dirname, "../../dist"))); this.ws = this.router.ws("/"); this.createOneBots(); } getLogger(patform) { const logger = (0, log4js_1.getLogger)(`[OneBots:${patform}]`); logger.level = this.config.log_level; return logger; } getConfigMaps() { const result = []; for (const [key, value] of Object.entries(this.config)) { const [adapter, ...uinArr] = key.split("."); const uin = uinArr.join("."); if (!uin) continue; result.push([adapter, uin, value]); } return result; } createOneBots() { for (const [platform, uin, config] of this.getConfigMaps()) { this.createOneBot(platform, uin, config); } } addAccount(platform, uin, config) { this.config[`${platform}.${uin}`] = config; const oneBot = this.createOneBot(platform, uin, config); oneBot.start(); (0, fs_1.writeFileSync)(App.configPath, js_yaml_1.default.dump((0, utils_1.deepClone)(this.config))); } updateAccount(platform, uin, config) { const adapter = this.findOrCreateAdapter(platform); if (!adapter) return; const oneBot = adapter.oneBots.get(uin); if (!oneBot) return this.addAccount(platform, uin, config); const newConfig = (0, utils_1.deepMerge)(this.config[uin], config); this.removeAccount(platform, uin); this.addAccount(platform, uin, newConfig); } removeAccount(platform, uin, force) { const adapter = this.findOrCreateAdapter(platform); if (!adapter) return; const oneBot = adapter.oneBots.get(uin); if (!oneBot) return this.logger.warn(`未找到账号${uin}`); oneBot.stop(force); delete this.config[`${platform}.${uin}`]; adapter.oneBots.delete(uin); (0, fs_1.writeFileSync)(App.configPath, js_yaml_1.default.dump(this.config)); } createOneBot(platform, uin, config) { const adapter = this.findOrCreateAdapter(platform, config); if (!adapter) return; return adapter.createOneBot(uin, config.protocol || {}, config.versions || []); } get oneBots() { return [...this.adapters.values()] .map(adapter => { return [...adapter.oneBots.values()]; }) .flat(); } findOrCreateAdapter(platform, config) { if (this.adapters.has(platform)) return this.adapters.get(platform); const AdapterClass = App.ADAPTERS.get(platform); if (!AdapterClass) return this.logger.warn(`未安装适配器(${platform})`); const adapter = new AdapterClass(this, config); this.adapters.set(platform, adapter); return adapter; } async start() { this.httpServer.listen(this.config.port); const fileListener = e => { if (e === "change") this.ws.clients.forEach(async (client) => { client.send(JSON.stringify({ event: "system.log", data: await (0, utils_1.readLine)(1, App.logFile), })); }); }; fs.watch(App.logFile, fileListener); this.once("close", () => { fs.unwatchFile(App.logFile, fileListener); }); process_1.default.once("disconnect", () => { fs.unwatchFile(App.logFile, fileListener); }); this.ws.on("connection", async (client) => { client.send(JSON.stringify({ event: "system.sync", data: { config: fs.readFileSync(App.configPath, "utf8"), adapters: [...this.adapters.values()].map(adapter => { return adapter.info; }), app: this.info, logs: fs.existsSync(App.logFile) ? await (0, utils_1.readLine)(100, App.logFile) : "", }, })); client.on("message", async (raw) => { let payload = {}; try { payload = JSON.parse(raw.toString()); } catch { return; } switch (payload.action) { case "system.input": // 将流的模式切换到“流动模式” process_1.default.stdin.resume(); // 使用以下函数来模拟输入数据 function simulateInput(data) { process_1.default.nextTick(() => { process_1.default.stdin.emit("data", data); }); } simulateInput(Buffer.from(payload.data + "\n", "utf8")); // 模拟结束 process_1.default.nextTick(() => { process_1.default.stdin.emit("end"); }); return true; case "system.saveConfig": return fs.writeFileSync(App.configPath, payload.data, "utf8"); case "system.reload": const config = js_yaml_1.default.load(fs.readFileSync(App.configPath, "utf8")); return this.reload(config); case "bot.start": { const { platform, uin } = JSON.parse(payload.data); await this.adapters.get(platform)?.setOnline(uin); return client.send(JSON.stringify({ event: "bot.change", data: this.adapters.get(platform).getOneBot(uin).info, })); } case "bot.stop": { const { platform, uin } = JSON.parse(payload.data); await this.adapters.get(platform)?.setOffline(uin); return client.send(JSON.stringify({ event: "bot.change", data: this.adapters.get(platform).getOneBot(uin).info, })); } } }); }); this.router.get("/list", ctx => { ctx.body = this.oneBots.map(bot => { return bot.info; }); }); this.router.post("/add", (ctx, next) => { const { uin, ...config } = (ctx.request.body || {}); try { this.addAccount(config.platform, uin, config); ctx.body = `添加成功`; } catch (e) { ctx.body = e.message; } }); this.router.post("/edit", (ctx, next) => { const { uin, ...config } = (ctx.request.body || {}); try { this.updateAccount(config.platform, uin, config); ctx.body = `修改成功`; } catch (e) { ctx.body = e.message; } }); this.router.get("/remove", (ctx, next) => { const { uin, platform, force } = ctx.request.query; try { this.removeAccount(String(platform), String(uin), Boolean(force)); ctx.body = `移除成功`; } catch (e) { console.log(e); ctx.body = e.message; } }); process_1.default.on("uncaughtException", e => { console.error("uncaughtException", e); }); process_1.default.on("unhandledRejection", e => { console.error("unhandledRejection", e); }); this.logger.mark(`server listen at http://0.0.0.0:${this.config.port}/${this.config.path ? this.config.path : ""}`); for (const [_, adapter] of this.adapters) { await adapter.start(); } this.isStarted = true; } async reload(config) { await this.stop(); this.config = (0, utils_1.deepMerge)((0, utils_1.deepClone)(App.defaultConfig), config); this.createOneBots(); await this.start(); } async stop() { for (const [_, adapter] of this.adapters) { await adapter.stop(); } this.adapters.clear(); // this.ws.close() this.httpServer.close(); this.emit("close"); this.isStarted = false; } } exports.App = App; App.configDir = path.join(os.homedir(), ".onebots"); function createOnebots(config = "config.yaml", cp = null) { const isStartWithConfigFile = typeof config === "string"; if (isStartWithConfigFile) { config = path.resolve(process_1.default.cwd(), config); App.configDir = path.dirname(config); } if (!(0, fs_1.existsSync)(App.configDir)) (0, fs_1.mkdirSync)(App.configDir); if (!(0, fs_1.existsSync)(App.configPath) && isStartWithConfigFile) { (0, fs_1.copyFileSync)(path.resolve(__dirname, "../config.sample.yaml"), App.configPath); console.log("未找到对应配置文件,已自动生成默认配置文件,请修改配置文件后重新启动"); console.log(`配置文件在: ${App.configPath}`); process_1.default.exit(); } if (!isStartWithConfigFile) { (0, fs_1.writeFileSync)(App.configPath, js_yaml_1.default.dump(config)); console.log(`已自动保存配置到:${App.configPath}`); } if (!(0, fs_1.existsSync)(App.dataDir)) { (0, fs_1.mkdirSync)(App.dataDir); console.log("已为你创建数据存储目录", App.dataDir); } config = js_yaml_1.default.load((0, fs_1.readFileSync)(App.configPath, "utf8")); (0, log4js_1.configure)({ appenders: { out: { type: "stdout", layout: { type: "colored" }, }, files: { type: "file", maxLogSize: 1024 * 1024 * 50, filename: path.join(process_1.default.cwd(), "onebots.log"), }, }, categories: { default: { appenders: ["out", "files"], level: config.log_level || "info", }, }, disableClustering: true, }); if (cp) process_1.default.on("disconnect", () => cp.kill()); return new App(config); } function defineConfig(config) { return config; } (function (App) { App.ADAPTERS = new Map(); App.defaultConfig = { port: 6727, username: "admin", password: "123456", timeout: 30, general: { V11: V11_1.V11.defaultConfig, V12: V12_1.V12.defaultConfig, }, log_level: "info", }; function registerAdapter(platform, adapter) { if (!adapter) adapter = App.loadAdapter(platform); if (App.ADAPTERS.has(platform)) { console.warn(`已存在对应的适配器:${platform}`); return this; } App.ADAPTERS.set(platform, adapter); } App.registerAdapter = registerAdapter; function loadAdapter(platform) { const maybeNames = [ path.join(__dirname, "../adapters", platform), // 内置的 `@onebots/adapter-${platform}`, // 我写的 `onebots-adapter-${platform}`, // 别人按照规范写的 platform, // 别人写的 ]; let adapter; try { adapter = require(path.join(__dirname, "../adapters", platform))?.default; } catch (e) { console.error(`loadAdapter(${platform}) failed:${e.message}`); } return adapter; } App.loadAdapter = loadAdapter; })(App || (exports.App = App = {}));