UNPKG

kitten-cloud-function

Version:

用于编程猫源码云功能(云变量、云列表等)的客户端工具

845 lines (844 loc) 32.3 kB
#!/usr/bin/env node import fs from "fs"; import path from "path"; import { Command } from "commander"; import axios from "axios"; import chalk from "chalk"; import { diffArrays } from "diff"; import { CodemaoUser, KittenCloudFunction, KittenCloudVariable, KittenCloudList, CodemaoWork, CodemaoWorkEditor, KittenCloudPrivateVariable, KittenCloudPublicVariable } from "./kitten-cloud-function-package"; import { None } from "../utils/other"; import { dirs } from "../utils/app-dirs"; import * as stringify from "@slightning/anything-to-string"; const { project } = require("../../project"); const program = new Command(); const language = { outputVersionNumber: "输出版本号", displayCommandsHelp: "显示命令帮助", setAuthorization: "设置身份", pleaseTypeInAuthorization: "请输入身份:", showUserInfo: "显示用户信息", IDOfUserToShow: "要显示用户的 ID,默认显示当前登录的用户", userIDMustBeInteger: "用户 ID 必须是整数", infoNameOfUserToShow: "要显示用户的信息名称,默认显示一堆用户信息", setWorkToUse: "设置要使用的作品", IDOfWorkToUse: "要使用的作品的 ID", workIDMustBeInteger: "作品 ID 必须是整数", useWork: "使用作品", showUsingWork: "显示正在使用的作品", usingWorkIs: "正在使用的作品为", noUsingWorkNow: "当前没有使用作品", exit: "退出", pleaseSetWorkToUseFirst: "请先设置要使用的作品,使用 kcf use <work-id> 命令", onlineUserNumber: "在线用户数", listAllCloudDataNameAndValue: "列出所有云数据的名称和值", cloudVariableName: "云变量名", getCloudVariableValue: "获取云变量的值", setCloudVariableValue: "设置云变量的值", getPrivateCloudVariableRankingList: "获取私有云变量排名列表", privateCloudVariableName: "私有云变量名", rankingListLimit: "限制数量,列表的长度不超过限制数量", rankingListOrder: "排名列表顺序,positive 表示顺序,reverse 表示逆序,默认为逆序", cloudListName: "云列表名", zeroBasedIndex: "从 0 开始的索引", getCloudListAllItems: "获取云列表所有项", getCloudListItem: "获取云列表的项", cloudListLength: "云列表的长度", cloudListIndexOf: "指定项在云列表中第一次出现的位置,如果不存在则输出 `-1`", cloudListIncludes: "判断指定项是否在云列表中", cloudListPush: "添加新的项到云列表尾部", cloudListUnshift: "添加新的项到云列表头部", cloudListAdd: "添加新的项到云列表指定位置", cloudListPop: "移除云列表最后一项", cloudListRemove: "移除云列表指项", cloudListEmpty: "清空云列表", cloudListReplaceLast: "替换云列表最后一项", cloudListReplace: "替换云列表指定项", watchCloudDataAndOnlineUserNumberChange: "监视云数据和在线用户数变化", noWatchOnlineUserNumberChange: "监视在线用户数变化", testDataByRegExpMatchName: "通过正则表达式匹配数据名称筛选数据,默认监视所有数据", showOriginalValue: "显示原来的值", showDiff: "显示差异", watchStarted: "监视已启动" }; program .name("Kitten-Cloud-Function") .alias("kcf") .description(project.description) .version(project.version, "-v, --version", language.outputVersionNumber) .helpOption("-h, --help", language.displayCommandsHelp) .helpCommand("help [command]", language.displayCommandsHelp); let errorExit = false; let opened = false; let connection = None; const workInfoFilePath = path.resolve(dirs.config, "work.json"); function simplifyErrorMessage(data) { var _a, _b, _c, _d; if (data == null || typeof data == "string" || typeof data == "number" || typeof data == "boolean" || typeof data == "bigint" || typeof data == "symbol") { return data; } else if (axios.isAxiosError(data)) { const { config, request, code, response } = data; let requestDescription = null; if ((config === null || config === void 0 ? void 0 : config.method) != null && config.url != null) { requestDescription = `HTTP 请求 ${config === null || config === void 0 ? void 0 : config.method} ${config === null || config === void 0 ? void 0 : config.url}`; } else { requestDescription = "未知 HTTP 请求"; } if (request == None) { data.message = `${requestDescription} 失败:请求发送失败`; } else if (response == None) { if (code != null) { data.message = `${requestDescription} 失败:请求已发出,但未收到响应,状态码为:${code}`; } else { data.message = `${requestDescription} 失败:请求已发出,但未收到响应,无状态码`; } } else { const { config, status, data: responseData } = response; if (config.method != null && config.url != null) { requestDescription = `HTTP 请求 ${config === null || config === void 0 ? void 0 : config.method} ${config === null || config === void 0 ? void 0 : config.url}`; } let responseMessage = null; if (responseData != null && typeof responseData == "object") { responseMessage = (_d = (_c = (_b = (_a = responseData.error_message) !== null && _a !== void 0 ? _a : responseData.error_name) !== null && _b !== void 0 ? _b : responseData.error) !== null && _c !== void 0 ? _c : responseData.msg) !== null && _d !== void 0 ? _d : null; } if (responseMessage == null) { responseMessage = JSON.stringify(data); } data.message = `${requestDescription} 失败:${status}${responseMessage}`; } return data; } else if (Array.isArray(data)) { const result = []; for (const item of data) { result.push(simplifyErrorMessage(item)); } return result; } else if (data != null && typeof data == "object" && Object.prototype.toString.call(data) == "[object Object]") { let result = {}; for (const [key, value] of Object.entries(data)) { result[key] = simplifyErrorMessage(value); } return result; } else { return data; } } function anythingToString(data) { return stringify.stringify(simplifyErrorMessage(data), { rules: stringify.Rules.LESSER, depth: 0 }); } async function tryRun(func) { try { await func(); } catch (error) { console.error(chalk.red.bold(anythingToString(error))); if (opened && connection != None) { errorExit = true; connection.close(); } else { process.exit(1); } } } async function connect() { let workInfo; try { workInfo = JSON.parse(String(await fs.promises.readFile(workInfoFilePath))); } catch (error) { if (error instanceof Error && "code" in error && error.code == "ENOENT") { console.log(chalk.red.bold(language.pleaseSetWorkToUseFirst)); process.exit(1); } else { throw error; } } connection = new KittenCloudFunction(new CodemaoWork({ id: workInfo.id, editor: CodemaoWorkEditor.parse(workInfo.editor) })); connection.opened.connect(() => { opened = true; }); connection.errored.connect((error) => { if (!errorExit && opened) { console.error(chalk.red.bold(anythingToString(error))); } }); connection.closed.connect(() => { process.exit(errorExit ? 1 : 0); }); return connection; } function processInputValue(value) { const numberValue = Number(value); if (Number.isNaN(numberValue)) { return value; } else { return numberValue; } } async function getCloudVariable(connection, variableName) { const data = await connection.get(variableName); if (!(data instanceof KittenCloudVariable)) { throw new Error(`${variableName} 不是云变量`); } return data; } program .command("set-authorization") .description(language.setAuthorization) .helpOption("-h, --help", language.displayCommandsHelp) .action(() => { tryRun(async () => { // @ts-ignore const readline = (await import("@johnls/readline-password")).default.createInstance(process.stdin, process.stdout); const authorization = await readline.passwordAsync(language.pleaseTypeInAuthorization); process.stdin.unref(); CodemaoUser.setAuthorization(authorization); }); }); const userCommand = program .command("user") .description(language.showUserInfo) .helpOption("-h, --help", language.displayCommandsHelp) .option("-i, --user-id [user-id]", language.IDOfUserToShow, parseInt) .option("-n, --info-name [info-name]", language.infoNameOfUserToShow) .action(() => { tryRun(async () => { const { userId: userID, infoName } = userCommand.opts(); if (userID != None && (Number.isNaN(userID) || !Number.isInteger(userID))) { throw new Error(language.userIDMustBeInteger); } let user; if (userID == None) { user = new CodemaoUser(); } else { user = new CodemaoUser({ id: userID }); } if (infoName == None) { const badge = await user.info.badge; let badgeChalk = chalk; if (badge.color != None) { badgeChalk = badgeChalk.hex("#FFFFFF"); badgeChalk = badgeChalk.bgHex(badge.color); } console.log(`${await user.info.nickname} ${badgeChalk(badge.name)}`); console.log(await user.info.description); console.log(`训练师编号: ${await user.info.id}`); console.log(`被浏览:${await user.info.viewTimes} 次`); console.log(`被点赞:${await user.info.praiseTimes} 次`); console.log(`被收藏:${await user.info.collectTimes} 次`); console.log(`被再创作:${await user.info.forkTimes} 次`); } else { console.log(await user.info[infoName]); } }); }); program .command("use") .description(language.setWorkToUse) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<work-id>", language.IDOfWorkToUse, parseInt) .action((workID) => { tryRun(async () => { if (Number.isNaN(workID) || !Number.isInteger(workID)) { throw new Error(language.workIDMustBeInteger); } const work = new CodemaoWork({ id: workID }); tryRun(async () => { await fs.promises.mkdir(path.dirname(workInfoFilePath), { recursive: true }); await fs.promises.writeFile(workInfoFilePath, JSON.stringify({ id: workID, editor: (await work.info.editor).code })); }); let workName = None; try { workName = await work.info.name; } catch (__ignore) { } if (workName == None) { console.log(`${language.useWork} ${workID}`); } else { console.log(`${language.useWork} ${workName}(${workID})`); } }); }); program .command("work") .description(language.showUsingWork) .helpOption("-h, --help", language.displayCommandsHelp) .action(() => { tryRun(async () => { try { let workInfo; workInfo = JSON.parse(String(await fs.promises.readFile(workInfoFilePath))); const work = new CodemaoWork({ id: workInfo.id, editor: CodemaoWorkEditor.parse(workInfo.editor) }); let workName = None; try { workName = await work.info.name; } catch (__ignore) { } if (workName == None) { console.log(`${language.usingWorkIs} ${workInfo.id}`); } else { console.log(`${language.usingWorkIs} ${workName}(${workInfo.id})`); } } catch (error) { if (error instanceof Error && "code" in error && error.code == "ENOENT") { console.log(chalk.green.bold(language.noUsingWorkNow)); } else { throw error; } } }); }); program .command("exit") .description(language.exit) .helpOption("-h, --help", language.displayCommandsHelp) .action(() => { tryRun(async () => { await fs.promises.unlink(workInfoFilePath); }); }); program .command("online-user-number") .description(language.onlineUserNumber) .description(language.listAllCloudDataNameAndValue) .helpOption("-h, --help", language.displayCommandsHelp) .action(() => { tryRun(async () => { const connection = await connect(); console.log((await connection.onlineUserNumber).value); connection.close(); }); }); program .command("list") .description(language.listAllCloudDataNameAndValue) .helpOption("-h, --help", language.displayCommandsHelp) .action(() => { tryRun(async () => { const connection = await connect(); for (const cloudData of await connection.getAll()) { let label, value; if (cloudData instanceof KittenCloudVariable) { if (cloudData instanceof KittenCloudPrivateVariable) { label = `[私有]${cloudData.name}`; } else if (cloudData instanceof KittenCloudPublicVariable) { label = cloudData.name; } value = cloudData.get(); if (typeof value == "string" && value.length > 64) { value = `${value.substring(0, 64)}……`; } } else if (cloudData instanceof KittenCloudList) { label = cloudData.name; let array = cloudData.copy(); if (array.length > 4) { array = [...array.slice(0, 4), None]; } value = stringify.default(array, { rules: [new (class { test(data) { return data == None; } toString() { return "……"; } })(), ...stringify.Rules.LESSER] }); } console.log(`${label}${value}`); } connection.close(); }); }); program .command("get") .description(language.getCloudVariableValue) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<variable-name>", language.cloudVariableName) .action((variableName) => { tryRun(async () => { const connection = await connect(); const variable = await getCloudVariable(connection, variableName); console.log(variable.get()); connection.close(); }); }); program .command("set") .description(language.setCloudVariableValue) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<variable-name>", language.cloudVariableName) .argument("<value>") .action((variableName, value) => { tryRun(async () => { const connection = await connect(); const variable = await getCloudVariable(connection, variableName); await variable.set(processInputValue(value)); connection.close(); }); }); const rankCommand = program .command("rank") .description(language.getPrivateCloudVariableRankingList) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<variable-name>", language.privateCloudVariableName) .option("-l, --limit [limit]", language.rankingListLimit, (value) => { return parseInt(value); }, 31) .option("-o, --order [order]", language.rankingListOrder, (value) => { switch (value.toLowerCase()) { case "positive": return 1; case "reverse": return -1; default: throw new Error(`顺序必须为 positive 或 reverse,得到 ${value}`); } }, -1) .action((variableName) => { tryRun(async () => { const { limit, order } = rankCommand.opts(); const connection = await connect(); const variable = await connection.privateVariable.get(variableName); let count = 0; for (const item of await variable.getRankingList(limit, order)) { console.log(`${++count} ${item.value} ${await item.user.info.nickname}(${await item.user.info.id})`); } connection.close(); }); }); program .command("all") .description(language.getCloudListAllItems) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .action((listName) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); console.log(JSON.stringify(list.copy(), undefined, 4)); connection.close(); }); }); program .command("item") .description(language.getCloudListItem) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<index>", language.zeroBasedIndex) .action((listName, index) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); console.log(list.get(index)); connection.close(); }); }); program .command("length") .description(language.cloudListLength) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .action((listName) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); console.log(list.length); connection.close(); }); }); program .command("index-of") .description(language.cloudListIndexOf) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<value>") .action((listName, value) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); console.log(list.indexOf(processInputValue(value))); connection.close(); }); }); program .command("includes") .description(language.cloudListIncludes) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<value>") .action((listName, value) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); console.log(list.includes(processInputValue(value))); connection.close(); }); }); program .command("push") .description(language.cloudListPush) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<value>") .action((listName, value) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.push(processInputValue(value)); connection.close(); }); }); program .command("unshift") .description(language.cloudListUnshift) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<value>") .action((listName, value) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.unshift(processInputValue(value)); connection.close(); }); }); program .command("add") .description(language.cloudListAdd) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<index>", language.zeroBasedIndex, parseInt) .argument("<value>") .action((listName, index, value) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.add(index, processInputValue(value)); connection.close(); }); }); program .command("pop") .description(language.cloudListPush) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .action((listName) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.pop(); connection.close(); }); }); program .command("remove") .description(language.cloudListRemove) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<index>", language.zeroBasedIndex, parseInt) .action((listName, index) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.remove(index); connection.close(); }); }); program .command("empty") .description(language.cloudListEmpty) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .action((listName) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.empty(); connection.close(); }); }); program .command("replaceLast") .description(language.cloudListEmpty) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<value>") .action((listName, value) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.replaceLast(processInputValue(value)); connection.close(); }); }); program .command("replace") .description(language.cloudListReplace) .helpOption("-h, --help", language.displayCommandsHelp) .argument("<list-name>", language.cloudListName) .argument("<index>", language.zeroBasedIndex, parseInt) .argument("<value>") .action((listName, index, value) => { tryRun(async () => { const connection = await connect(); const list = await connection.list.get(listName); await list.replace(index, processInputValue(value)); connection.close(); }); }); function separateNumber(str) { const result = []; let lastIsNumber = None; let last = ""; for (const char of str) { const isNumber = "0123456789.-".includes(char); if (lastIsNumber != None && (!isNumber || !lastIsNumber)) { result.push(last); last = ""; } last += char; lastIsNumber = isNumber; } result.push(last); return result; } function getDiff(oldStr, newStr) { return diffArrays(separateNumber(oldStr), separateNumber(newStr)) .map((change) => { var _a, _b; return ({ count: change.count, value: change.value.join(""), added: (_a = change.added) !== null && _a !== void 0 ? _a : false, removed: (_b = change.removed) !== null && _b !== void 0 ? _b : false }); }); } function diffLog(originalValue, newValue, showDiff, showOriginalValue) { if (showDiff) { const diff = getDiff(String(originalValue), String(newValue)); if (showOriginalValue) { console.log(" - " + diff.map((value) => { if (value.added) { return ""; } else if (value.removed) { return chalk.bgRed(value.value); } else { return value.value; } }).join("")); console.log(" + " + diff.map((value) => { if (value.added) { return chalk.bgGreen(value.value); } else if (value.removed) { return ""; } else { return value.value; } }).join("")); } else { console.log(" " + diff.map((value) => { if (value.added) { return chalk.bgGreen(value.value); } else if (value.removed) { return chalk.bgRed(value.value); } else { return value.value; } }).join("")); } } else { if (showOriginalValue) { console.log(` ${originalValue} => ${newValue}`); } else { console.log(` ${newValue}`); } } } const watchCommand = program .command("watch") .description(language.watchCloudDataAndOnlineUserNumberChange) .helpOption("-h, --help", language.displayCommandsHelp) .option("-u, --no-watch-online-user-number", language.noWatchOnlineUserNumberChange) .option("-t, --test <reg-exp>", language.testDataByRegExpMatchName) .option("-o, --show-original-value", language.showOriginalValue) .option("-d, --show-diff", language.showDiff) .action(() => { tryRun(async () => { const connection = await connect(); const { watchOnlineUserNumber, test, showOriginalValue, showDiff } = watchCommand.opts(); if (watchOnlineUserNumber) { (await connection.onlineUserNumber).changed.connect(({ originalNumber, newNumber }) => { if (showOriginalValue) { if (showDiff) { if (newNumber >= originalNumber) { console.log(`在线用户数改变:${originalNumber} => ${newNumber} ${chalk.bgGreen(`(+${newNumber - originalNumber})`)}`); } else { console.log(`在线用户数改变:${originalNumber} => ${newNumber} ${chalk.bgRed(`(${newNumber - originalNumber})`)}`); } } else { console.log(`在线用户数改变:${originalNumber} => ${newNumber}`); } } else { console.log(`在线用户数改变:${originalNumber}`); } }); } let dataArray = await connection.getAll(); dataArray = dataArray.filter((data) => !(data instanceof KittenCloudPrivateVariable)); if (test != None) { const testRegExp = new RegExp(test); dataArray = dataArray.filter((data) => testRegExp.test(data.name)); } for (const data of dataArray) { if (data instanceof KittenCloudPublicVariable) { data.changed.connect(({ originalValue, newValue }) => { console.log(`云变量 ${chalk.green(data.name)} 改变:`); diffLog(String(originalValue), String(newValue), showDiff, showOriginalValue); }); } else if (data instanceof KittenCloudList) { data.pushed.connect(({ item }) => { console.log(`云列表 ${chalk.green(data.name)} 添加尾项:`); if (showDiff) { console.log(" + " + chalk.bgGreen(item)); } else { console.log(" " + item); } }); data.unshifted.connect(({ item }) => { console.log(`云列表 ${chalk.green(data.name)} 添加首项:`); if (showDiff) { console.log(" + " + chalk.bgGreen(item)); } else { console.log(" " + item); } }); data.added.connect(({ index, item }) => { console.log(`云列表 ${chalk.green(data.name)} 添加到第 ${index + 1} 项:`); if (showDiff) { console.log(" + " + chalk.bgGreen(item)); } else { console.log(" " + item); } }); data.popped.connect(({ item }) => { if (showOriginalValue || showDiff) { console.log(`云列表 ${chalk.green(data.name)} 删除最后一项:`); if (showDiff) { console.log(" - " + chalk.bgRed(item)); } else { console.log(" " + item); } } else { console.log(`云列表 ${chalk.green(data.name)} 删除最后一项`); } }); data.removed.connect(({ index, item }) => { if (showOriginalValue || showDiff) { console.log(`云列表 ${chalk.green(data.name)} 删除第 ${index + 1} 项:`); if (showDiff) { console.log(" - " + chalk.bgRed(item)); } else { console.log(" " + item); } } else { console.log(`云列表 ${chalk.green(data.name)} 删除第 ${index + 1} 项`); } }); data.emptied.connect(({ list }) => { if (showOriginalValue || showDiff) { console.log(`云列表 ${chalk.green(data.name)} 清空:`); if (showDiff) { console.log(list.map((item) => (" - " + chalk.bgRed(item)))); } else { console.log(JSON.stringify(list, undefined, 2) .split("\n") .map((line) => (" " + line))); } } else { console.log(`云列表 ${chalk.green(data.name)} 清空`); } }); data.replacedLast.connect(({ originalItem, newItem }) => { console.log(`云列表 ${chalk.green(data.name)} 替换最后一项`); diffLog(String(originalItem), String(newItem), showDiff, showOriginalValue); }); data.replaced.connect(({ index, originalItem, newItem }) => { console.log(`云列表 ${chalk.green(data.name)} 替换第 ${index + 1} 项`); diffLog(String(originalItem), String(newItem), showDiff, showOriginalValue); }); } else { throw new Error("未知的云数据类型"); } } console.log(language.watchStarted); if (watchOnlineUserNumber) { console.log("监视在线用户数变化"); } console.log("监视云数据:" + dataArray.map((data) => chalk.green(data.name)).join(",")); process.on("SIGINT", () => { connection.close(); }); }); }); program.parse(process.argv);