koishi
Version:
A QQ bot framework based on CQHTTP
561 lines (556 loc) • 17.9 kB
JavaScript
#!/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