UNPKG

koishi

Version:

A QQ bot framework based on CQHTTP

561 lines (556 loc) 17.9 kB
#!/usr/bin/env node var __create = Object.create; var __defProp = Object.defineProperty; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __markAsModule = (target) => __defProp(target, "__esModule", {value: true}); var __commonJS = (cb, mod) => () => (mod || cb((mod = {exports: {}}).exports, mod), mod.exports); var __reExport = (target, module2, desc) => { if (module2 && typeof module2 === "object" || typeof module2 === "function") { for (let key of __getOwnPropNames(module2)) if (!__hasOwnProp.call(target, key) && key !== "default") __defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable}); } return target; }; var __toModule = (module2) => { return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2); }; // packages/koishi/ecosystem.json var require_ecosystem = __commonJS((exports2, module2) => { module2.exports = { "koishi-adapter-discord": { version: "1.0.9", description: "Discord adapter for Koishi" }, "koishi-adapter-kaiheila": { version: "1.0.4", description: "Kaiheila adapter for Koishi" }, "koishi-adapter-onebot": { version: "3.0.8", description: "CQHTTP adapter for Koishi" }, "koishi-adapter-telegram": { version: "1.0.3", description: "Telegram adapter for Koishi" }, "koishi-adapter-tomon": { version: "1.0.0-beta.11", description: "Tomon adapter for Koishi" }, "koishi-plugin-adventure": { version: "0.2.0", description: "Adventure Game for Koishi" }, "koishi-plugin-assets": { version: "1.1.0", description: "Asset provider plugin for Koishi" }, "koishi-plugin-chat": { version: "0.3.1", description: "Chat plugin for Koishi" }, "koishi-plugin-chess": { version: "3.0.1", description: "Chess Plugin for Koishi" }, "koishi-plugin-common": { version: "4.2.3", description: "Common plugins for Koishi" }, "koishi-plugin-dice": { version: "0.1.10", description: "COC / DND Tools for Koishi" }, "koishi-plugin-eval": { version: "3.1.0", description: "Execute JavaScript in Koishi" }, "koishi-plugin-github": { version: "3.0.3", description: "GitHub webhook plugin for Koishi" }, "koishi-plugin-image-search": { version: "3.0.3", description: "Image searching plugin for Koishi" }, "koishi-plugin-mongo": { version: "2.2.2", description: "MongoDB support for Koishi" }, "koishi-plugin-monitor": { version: "1.0.0-beta.29" }, "koishi-plugin-mysql": { version: "3.3.1", description: "MySQL support for Koishi" }, "koishi-plugin-puppeteer": { version: "2.0.2", description: "Take Screenshots in Koishi" }, "koishi-plugin-rss": { version: "2.0.0", description: "Subscribe RSS Url for Koishi" }, "koishi-plugin-schedule": { version: "3.0.4", description: "Schedule plugin for Koishi" }, "koishi-plugin-teach": { version: "2.1.4", description: "Teach plugin for Koishi" }, "koishi-plugin-tools": { version: "2.1.1", description: "Some simple tools for Koishi" }, "koishi-plugin-webui": { version: "4.3.0", description: "Show Web UI of Koishi" } }; }); // packages/koishi/src/init.ts var import_fs = __toModule(require("fs")); var import_kleur = __toModule(require("kleur")); var import_path = __toModule(require("path")); var import_child_process = __toModule(require("child_process")); var import_prompts = __toModule(require("prompts")); var serverQuestions = [{ name: "type", type: "select", message: "Adapter Type", choices: [ {title: "OneBot - HTTP", value: "onebot:http"}, {title: "OneBot - WebSocket", value: "onebot:ws"}, {title: "OneBot - WebSocket Reverse", value: "onebot:ws-reverse"}, {title: "Discord - WebSocket", value: "discord"}, {title: "Telegram - HTTP", value: "telegram"}, {title: "Kaiheila - HTTP", value: "kaiheila:http"}, {title: "Kaiheila - WebSocket", value: "kaiheila:ws"} ] }, { name: "port", type: () => !bots.length ? "number" : null, message: "Koishi Port", initial: 8080 }]; var botMap = { onebot: [{ name: "server", type: () => config.type === "onebot:http" ? "text" : null, message: "HTTP Server", initial: "http://localhost:5700" }, { name: "server", type: () => config.type === "onebot:ws" ? "text" : null, message: "WebSocket Server", initial: "ws://localhost:6700" }, { name: "selfId", type: "number", message: "Your Bot's QQ Number" }, { name: "token", type: "text", message: "Token for CQHTTP Server" }], discord: [{ name: "token", type: "text", message: "Token for Discord" }], telegram: [{ name: "token", type: "text", message: "Token for Telegram" }], kaiheila: [{ name: "token", type: () => config.type === "kaiheila:ws" ? "text" : null, message: "Token for Kaiheila" }, { name: "verifyToken", type: () => config.type === "kaiheila:http" ? "text" : null, message: "Verify Token for Kaiheila" }], tomon: [{ name: "token", type: "text", message: "Token for Tomon" }] }; var adapterMap = { onebot: [{ name: "path", type: () => !config["onebot"] && config.type !== "onebot:ws" ? "text" : null, message: "Server Path" }, { name: "secret", type: () => !config["onebot"] ? "text" : null, message: "Secret for Koishi Server" }], telegram: [{ name: "selfUrl", type: "text", message: "Your Public URL" }, { name: "path", type: () => !config["telegram"] ? "text" : null, message: "Telegram Path" }], kaiheila: [{ name: "path", type: () => !config["kaiheila"] && config.type !== "kaiheila:ws" ? "text" : null, message: "Kaiheila Path" }] }; var databaseQuestions = [{ name: "database", type: "select", message: "Database Type", choices: [ {title: "None", value: null}, {title: "MySQL", value: "mysql"}, {title: "MongoDB", value: "mongo"} ] }]; var databaseMap = { mysql: [{ name: "host", type: "text", message: "MySQL / Host", initial: "127.0.0.1" }, { name: "port", type: "number", message: "MySQL / Port", initial: "3306" }, { name: "user", type: "text", message: "MySQL / Username", initial: "root" }, { name: "password", type: "text", message: "MySQL / Password" }, { name: "database", type: "text", message: "MySQL / Database", initial: "koishi" }], mongo: [{ name: "host", type: "text", message: "MongoDB / Host", initial: "127.0.0.1" }, { name: "port", type: "number", message: "MongoDB / Port", initial: "27017" }, { name: "username", type: "text", message: "MongoDB / Username", initial: "root" }, { name: "password", type: "text", message: "MongoDB / Password" }, { name: "name", type: "text", message: "MongoDB / Database", initial: "koishi" }] }; async function question(questions) { let succeed = true; const data = await (0, import_prompts.default)(questions, { onCancel: () => succeed = false }); if (!succeed) throw new Error("interrupted"); return data; } async function confirm(message, initial) { const {confirmed} = await question([{ name: "confirmed", type: "confirm", initial, message }]); return confirmed; } var cwd = process.cwd(); var metaPath = (0, import_path.resolve)(cwd, "package.json"); var ecosystem = require_ecosystem(); var builtinPackages = ["koishi-plugin-common"]; var config = {}; var bots = []; async function createConfig() { let bot; do { Object.assign(config, await question(serverQuestions)); const [platform] = config.type.split(":", 1); bots.push(bot = {type: config.type}); Object.assign(bot, await question(botMap[platform])); if (adapterMap[platform]) { config[platform] = await question(adapterMap[platform]); } } while (await confirm("configurate another bot?", false)); delete config.type; config.bots = bots; config.plugins = {}; const {database} = await question(databaseQuestions); if (database) { config.plugins[database] = await question(databaseMap[database]); } const choices = Object.entries(ecosystem).map(([title, meta]) => { if (!title.startsWith("koishi-plugin-")) return; const value = title.slice(14); if (value in databaseMap) return; const {description} = meta; const selected = builtinPackages.includes(title); return {title, value, description, selected}; }).filter(Boolean); const {plugins} = await (0, import_prompts.default)({ type: "multiselect", name: "plugins", message: "Choose Official Plugins", choices }); for (const name of plugins) { config.plugins[name] = {}; } } var sourceTypes = ["js", "ts", "json", "yaml", "yml"]; var error = (0, import_kleur.red)("error"); var success = (0, import_kleur.green)("success"); var info = (0, import_kleur.magenta)("info"); function joinLines(lines, type, indent) { if (!lines.length) return ""; let separator = "\n " + indent; if (type !== "yaml") separator = "," + separator; return ` ${indent}${lines.join(separator)}${type === "json" || type === "yaml" ? "" : ","} ${indent}`; } function comment(data, prop) { if (prop === "port") return "Koishi 服务器监听的端口"; if (prop === "server" && data.type === "onebot:http") { return "对应 cqhttp 配置项 http_config.port"; } if (prop === "server" && data.type === "onebot:ws") { return "对应 cqhttp 配置项 ws_config.port"; } if (prop === "path" && data === config["onebot"]) { return "对应 cqhttp 配置项 http_config.post_urls, ws_reverse_servers.reverse_url"; } } function codegen(data, type, indent = "") { if (data === null) return "null"; switch (typeof data) { case "number": case "boolean": return "" + data; case "string": return type === "yaml" ? data : type === "json" || data.includes("'") && !data.includes('"') ? `"${data.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : `'${data.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`; case "undefined": return void 0; } if (Array.isArray(data)) { return type === "yaml" ? joinLines(data.map((value) => "- " + codegen(value, type, " " + indent).trimStart()), type, indent) : `[${data.map((value) => codegen(value, type, indent)).join(", ")}]`; } const prefix = type === "yaml" ? "# " : "// "; const output = joinLines(Object.entries(data).filter(([, value]) => value !== void 0).map(([key, value]) => { let output2 = type !== "json" && comment(data, key) || ""; if (output2) output2 = output2.split("\n").map((line) => prefix + line + "\n " + indent).join(""); output2 += type === "json" ? `"${key}"` : key; output2 += ": " + codegen(value, type, " " + indent); return output2; }), type, indent); return type === "yaml" ? output : `{${output}}`; } var rootComment = "配置项文档:https://koishi.js.org/api/app.html"; async function writeConfig(config2, path, type) { if (type === "yml") type = "yaml"; let output = codegen(config2, type) + "\n"; if (type === "js") { output = "// " + rootComment + "\nmodule.exports = " + output; } else if (type === "ts") { output = "import { AppConfig } from 'koishi'\n\n// " + rootComment + "\nexport default " + output.replace(/\n$/, " as AppConfig\n"); } else if (type === "yaml") { output = "# " + rootComment + "\n" + output.replace(/^ {2}/mg, ""); } const folder = (0, import_path.dirname)(path); await import_fs.promises.mkdir(folder, {recursive: true}); await import_fs.promises.writeFile(path, output.replace(/^ +$/mg, "")); console.log(`${success} created config file: ${path}`); } async function loadMeta() { return JSON.parse(await import_fs.promises.readFile(metaPath, "utf8")); } function execute(bin, args = [], stdio = "inherit") { const child2 = (0, import_child_process.spawn)(bin + (process.platform === "win32" ? ".cmd" : ""), args, {stdio}); return new Promise((resolve3) => { child2.on("close", resolve3); }); } async function getManager() { if ((0, import_fs.existsSync)((0, import_path.resolve)(cwd, "yarn.lock"))) return "yarn"; if ((0, import_fs.existsSync)((0, import_path.resolve)(cwd, "package-lock.json"))) return "npm"; if (!await execute("yarn", ["--version"], "ignore")) return "yarn"; return "npm"; } var _meta = loadMeta(); var _kind = getManager(); var installArgs = { yarn: [], npm: ["install", "--loglevel", "error"] }; async function updateMeta() { const meta = await _meta; const kind = await _kind; let modified = false; if (!meta.dependencies) meta.dependencies = {}; function ensureDependency(name) { if (meta.dependencies[name]) return; modified = true; meta.dependencies[name] = "^" + ecosystem[name].version; } for (const bot of config.bots) { const [name] = bot.type.split(":", 1); ensureDependency("koishi-adapter-" + name); } for (const name of Object.keys(config.plugins)) { ensureDependency("koishi-plugin-" + name); } if (!modified) return; await import_fs.promises.writeFile(metaPath, JSON.stringify(meta, null, 2)); console.log(`${success} package.json was updated`); const args = installArgs[kind]; if (!await confirm("package.json was updated. install new dependencies now?", true).catch(() => false)) { console.log(`${info} type "${[kind, ...args].join(" ")}" to install new dependencies before using koishi`); return; } process.exit(await execute(kind, args)); } function init_default(cli2) { cli2.command("init [file]", "initialize a koishi configuration file").option("-f, --forced", "overwrite config file if it exists").action(async (file = "koishi.config.js", options) => { const path = (0, import_path.resolve)(cwd, file); if (!options.forced && (0, import_fs.existsSync)(path)) { console.warn(`${error} configuration file already exists. If you want to overwrite the current file, use ${(0, import_kleur.yellow)("koishi init -f")}`); process.exit(1); } const extension = (0, import_path.extname)(path).slice(1); if (!extension) { console.warn(`${error} configuration file should have an extension, received "${file}"`); process.exit(1); } else if (!sourceTypes.includes(extension)) { console.warn(`${error} configuration file type "${extension}" is currently not supported`); process.exit(1); } await createConfig().catch(() => { console.warn(`${error} initialization was canceled`); process.exit(0); }); await writeConfig(config, path, extension); await updateMeta(); }); } // packages/koishi/src/run.ts var import_perf_hooks = __toModule(require("perf_hooks")); var import_koishi_utils = __toModule(require("koishi-utils")); var import_child_process2 = __toModule(require("child_process")); var import_path2 = __toModule(require("path")); var import_kleur2 = __toModule(require("kleur")); process.env.KOISHI_START_TIME = "" + import_perf_hooks.performance.now(); var codes = [ 134, 114 ]; var child; process.on("SIGINT", () => { if (child) { child.emit("SIGINT"); } else { process.exit(); } }); var payload; function createWorker(options) { child = (0, import_child_process2.fork)((0, import_path2.resolve)(__dirname, "worker"), [], { execArgv: options["--"] }); let started = false; child.on("message", (message) => { if (message.type === "start") { started = true; if (payload) { child.send({type: "send", payload}); } } else if (message.type === "exit") { payload = message.payload; } }); child.on("exit", (code) => { if (!started || !codes.includes(code)) { process.exit(code); } createWorker(options); }); } function setEnvArg(name, value) { if (value === true) { process.env[name] = ""; } else if (value) { process.env[name] = value; } } function run_default(cli2) { cli2.command("start [file]", "start a koishi bot").alias("run").option("--debug [namespace]", "specify debug namespace").option("--log-level [level]", "specify log level (default: 2)").option("--log-time [format]", "show timestamp in logs").option("--watch [path]", "watch and reload at change").action((file, options) => { const {logLevel} = options; if (logLevel !== void 0 && (!(0, import_koishi_utils.isInteger)(logLevel) || logLevel < 0)) { console.warn(`${import_kleur2.default.red("error")} log level should be a positive integer.`); process.exit(1); } setEnvArg("KOISHI_WATCH_ROOT", options.watch); setEnvArg("KOISHI_LOG_TIME", options.logTime); process.env.KOISHI_LOG_LEVEL = logLevel || ""; process.env.KOISHI_DEBUG = options.debug || ""; process.env.KOISHI_CONFIG_FILE = file || ""; createWorker(options); }); } // packages/koishi/src/index.ts var import_cac = __toModule(require("cac")); var cli = (0, import_cac.default)("koishi").help().version("3.9.1"); init_default(cli); run_default(cli); cli.parse(); if (!cli.matchedCommand) { cli.outputHelp(); } //# sourceMappingURL=index.js.map