UNPKG

onebots

Version:

OneBots 整合适配器和协议,提供HTTP/WebSocket服务

293 lines 11 kB
/** * OneBots CLI:gateway 网关控制、config、setup、onboard、send */ import { Command } from "commander"; import * as path from "path"; import * as fs from "fs"; import { createOnebots, App } from "./app.js"; import { yaml } from "@onebots/core"; import { getPidPath, readPid, writePid, removePidFile, isProcessRunning, stopProcess, daemonStart, } from "./daemon.js"; import { serviceInstall, serviceUninstall, serviceStatus, } from "./service-manager.js"; const program = new Command(); program .name("onebots") .description("OneBots CLI - 网关控制与配置管理") .version("1.0.1") .option("-c, --config <path>", "配置文件路径", "config.yaml") .option("-r, --register <adapter>", "注册适配器(可多次)", collect, []) .option("-p, --protocol <protocol>", "注册协议(可多次)", collect, []); function collect(val, prev) { return (prev || []).concat([val]); } function getConfigPath(configOption) { return path.resolve(process.cwd(), configOption); } async function loadAdaptersAndProtocols(adapters, protocols) { for (const adapter of adapters) { const ok = await App.loadAdapterFactory(adapter); if (!ok) { console.warn(`[onebots] 加载适配器 ${adapter} 失败,请检查是否已安装`); } } for (const protocol of protocols) { await App.loadProtocolFactory(protocol); } } async function runGatewayStart(configPath, adapters, protocols) { await loadAdaptersAndProtocols(adapters, protocols); createOnebots(configPath).start(); } // ---------- gateway 子命令 ---------- const gateway = program.command("gateway").description("网关服务(start/daemon/stop/service)"); gateway .command("start") .description("前台启动网关") .action(async () => { const opts = program.opts(); const configPath = getConfigPath(opts.config); await runGatewayStart(configPath, opts.register || [], opts.protocol || []); }); gateway .command("daemon") .description("后台启动网关") .action(async () => { const opts = program.opts(); const configPath = getConfigPath(opts.config); await loadAdaptersAndProtocols(opts.register || [], opts.protocol || []); const pid = daemonStart({ configPath, adapters: opts.register || [], protocols: opts.protocol || [], nodePath: process.execPath, binPath: process.argv[1], }); writePid(configPath, pid); console.log(`网关已在后台启动,PID: ${pid}`); console.log(`PID 文件: ${getPidPath(configPath)}`); process.exit(0); }); gateway .command("stop") .description("停止网关") .action(async () => { const opts = program.opts(); const configPath = getConfigPath(opts.config); const pid = readPid(configPath); if (pid === null) { console.error("[onebots] 未找到 PID 文件,网关可能未以后台方式运行"); process.exit(2); } if (!isProcessRunning(pid)) { console.warn("[onebots] 进程已不存在,清理 PID 文件"); removePidFile(configPath); process.exit(0); } stopProcess(pid); removePidFile(configPath); console.log("网关已停止"); process.exit(0); }); const gatewayService = gateway.command("service").description("系统服务(install/uninstall/status)"); gatewayService .command("install") .description("安装网关为系统服务") .action(async () => { const opts = program.opts(); const configPath = getConfigPath(opts.config); await serviceInstall(configPath); }); gatewayService .command("uninstall") .description("卸载网关系统服务") .action(async () => { await serviceUninstall(); }); gatewayService .command("status") .description("查看网关服务状态") .action(async () => { await serviceStatus(); }); // ---------- config 子命令 ---------- const configCmd = program.command("config").description("命令式修改配置(get/set/list)"); configCmd .command("get <key>") .description("获取配置项") .action(async (key) => { const opts = program.opts(); const configPath = getConfigPath(opts.config); if (!fs.existsSync(configPath)) { console.error("[onebots] 配置文件不存在,请先运行 onebots setup"); process.exit(1); } const raw = fs.readFileSync(configPath, "utf8"); const config = yaml.load(raw) || {}; const keys = key.split("."); let cur = config; for (const k of keys) { cur = cur?.[k]; if (cur === undefined) break; } console.log(cur === undefined ? "" : String(cur)); }); configCmd .command("set <key> <value>") .description("设置配置项") .action(async (key, value) => { const opts = program.opts(); const configPath = getConfigPath(opts.config); if (!fs.existsSync(configPath)) { console.error("[onebots] 配置文件不存在,请先运行 onebots setup"); process.exit(1); } const raw = fs.readFileSync(configPath, "utf8"); const config = yaml.load(raw) || {}; const keys = key.split("."); let cur = config; for (let i = 0; i < keys.length - 1; i++) { const k = keys[i]; if (!(cur[k] instanceof Object)) cur[k] = {}; cur = cur[k]; } const last = keys[keys.length - 1]; const num = Number(value); cur[last] = value === "true" ? true : value === "false" ? false : Number.isNaN(num) ? value : num; fs.writeFileSync(configPath, yaml.dump(config), "utf8"); console.log(`已设置 ${key} = ${value}`); }); configCmd .command("list") .description("列出全部配置") .action(async () => { const opts = program.opts(); const configPath = getConfigPath(opts.config); if (!fs.existsSync(configPath)) { console.error("[onebots] 配置文件不存在,请先运行 onebots setup"); process.exit(1); } const raw = fs.readFileSync(configPath, "utf8"); const config = yaml.load(raw) || {}; console.log(yaml.dump(config)); }); // ---------- setup ---------- program .command("setup") .description("初始化 OneBots(创建配置目录与默认配置)") .action(async () => { const opts = program.opts(); const configPath = getConfigPath(opts.config); const configDir = path.dirname(configPath); if (fs.existsSync(configPath)) { console.log("配置文件已存在:", configPath); process.exit(0); } if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }); } const samplePath = path.resolve(import.meta.dirname, "./config.sample.yaml"); fs.copyFileSync(samplePath, configPath); const dataDir = path.join(configDir, "data"); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } console.log("已初始化,配置文件:", configPath); console.log("请修改配置后使用 onebots gateway start 启动"); process.exit(0); }); // ---------- onboard ---------- program .command("onboard") .description("引导式调整配置") .action(async () => { const opts = program.opts(); const configPath = getConfigPath(opts.config); if (!fs.existsSync(configPath)) { console.error("[onebots] 请先运行 onebots setup"); process.exit(1); } const readline = await import("readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const ask = (q) => new Promise((resolve) => rl.question(q, resolve)); let config = yaml.load(fs.readFileSync(configPath, "utf8")) || {}; const port = await ask("HTTP 端口 [6727]: "); if (port) config.port = parseInt(port, 10) || 6727; const logLevel = await ask("日志级别 (trace/debug/info/warn/error) [info]: "); if (logLevel) config.log_level = logLevel || "info"; fs.writeFileSync(configPath, yaml.dump(config), "utf8"); console.log("配置已更新:", configPath); rl.close(); process.exit(0); }); // ---------- send ---------- program .command("send <target_id> <message>") .description("通过已运行的网关发送消息") .requiredOption("--target_type <type>", "private | group | channel") .requiredOption("--channel <channel>", "发信 bot,格式 platform.account_id(如 qq.my_bot)") .option("--url <baseUrl>", "网关 base URL(默认从 -c 读取 port/path 构造)") .action(async (target_id, message, options) => { const opts = program.opts(); let baseUrl = options.url; let auth = {}; if (!baseUrl) { const configPath = getConfigPath(opts.config); if (!fs.existsSync(configPath)) { console.error("[onebots] 配置文件不存在,请指定 -c 或 --url"); process.exit(1); } const config = yaml.load(fs.readFileSync(configPath, "utf8")); const port = config?.port ?? 6727; const pathPrefix = config?.path ?? ""; baseUrl = `http://127.0.0.1:${port}${pathPrefix}`; if (config?.access_token) { auth = { Authorization: `Bearer ${config.access_token}` }; } else if (config?.username && config?.password) { auth = { Authorization: "Basic " + Buffer.from(`${config.username}:${config.password}`).toString("base64"), }; } } const body = { channel: options.channel, target_id, target_type: options.target_type, message: message || "", }; try { const res = await fetch(`${baseUrl.replace(/\/$/, "")}/api/send`, { method: "POST", headers: { "Content-Type": "application/json", ...auth }, body: JSON.stringify(body), }); const text = await res.text(); if (!res.ok) { console.error("[onebots] 发送失败:", res.status, text); process.exit(2); } console.log(text || "发送成功"); } catch (e) { console.error("[onebots] 请求失败,请确认网关已启动(onebots gateway start 或 onebots gateway daemon):", e); process.exit(2); } process.exit(0); }); // 无子命令时默认执行 gateway start(仅当没有未被选项消耗的剩余参数时) program.action(async () => { // 使用 program.args:Commander 解析后只包含未被 -r/-p/-c 等消耗的剩余参数。 // 用 process.argv 会把 -r/-p 的值(如 kook、config.yaml)误判为位置参数导致不启动。 if (program.args.length > 0) return; // 有剩余位置参数则交给 Commander 显示 unknown command const opts = program.opts(); const configPath = getConfigPath(opts.config); await runGatewayStart(configPath, opts.register || [], opts.protocol || []); }); export function runCli() { program.parse(); } //# sourceMappingURL=cli.js.map