From 8ce4ade974c06d2d17a7deb83afeb1a62118f45c Mon Sep 17 00:00:00 2001 From: CakmLexi Date: Mon, 1 Jul 2024 07:19:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20input=20=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8=20=E5=AE=8C=E5=96=84=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E3=80=81=E8=AF=B7=E6=B1=82=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/defSet/config.yaml | 11 ++ package.json | 1 + src/adapter/input/index.ts | 206 +++++++++++++++++++++++++++++++++ src/adapter/onebot/onebot11.ts | 133 ++++++++++++--------- src/core/index.ts | 1 + src/core/listener.ts | 8 +- src/core/server.ts | 27 +++++ src/event/event.handler.ts | 2 +- src/event/event.ts | 2 +- src/types/adapter.ts | 2 +- src/types/config.ts | 21 ++++ src/utils/common.ts | 11 +- src/utils/config.ts | 41 ++++--- src/utils/yamlEditor.ts | 2 +- 14 files changed, 386 insertions(+), 82 deletions(-) create mode 100644 src/adapter/input/index.ts diff --git a/config/defSet/config.yaml b/config/defSet/config.yaml index 49fb52f..9a6fc89 100644 --- a/config/defSet/config.yaml +++ b/config/defSet/config.yaml @@ -17,6 +17,17 @@ multi_progress: false # 控制台触发插件日志颜色 十六进制 默认#FFFF00 不支持热更新 log_color: "#E1D919" +# input适配器配置 以下所有配置均不支持热更新 +AdapterInput: + # 是否启用 + enable: true + # 是否将语音、图片、视频消息转为文件 转为文件后可通过url访问 + msgToFile: true + # url访问token 如果为 AdapterInput 每次启动后会重新生成 + token: "AdapterInput" + # 访问ip + ip: 127.0.0.1 + # ffmpeg配置 用于音视频处理 ffmpeg_path: ffprobe_path: diff --git a/package.json b/package.json index a0bdaba..6b78f0e 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "dependencies": { "@grpc/grpc-js": "1.10.10", "@grpc/proto-loader": "0.7.13", + "@inquirer/prompts": "^5.0.7", "art-template": "4.13.2", "axios": "1.7.2", "chalk": "5.3.0", diff --git a/src/adapter/input/index.ts b/src/adapter/input/index.ts new file mode 100644 index 0000000..d7da584 --- /dev/null +++ b/src/adapter/input/index.ts @@ -0,0 +1,206 @@ +import fs from 'fs' +import { randomUUID } from 'crypto' +import { listener } from 'karin/core' +import { KarinMessage } from 'karin/event' +import { KarinAdapter } from 'karin/types/adapter' +import { contact, KarinElement } from 'karin/types' +import { config, common, YamlEditor } from 'karin/utils' + +const { enable, msgToFile, token: oldToken, ip } = config.Config.AdapterInput + +let token = oldToken + +if (oldToken === 'AdapterInput') { + try { + token = randomUUID() + const yaml = new YamlEditor('./config/config/config.yaml') + const data = yaml.get('AdapterInput') + if (!data) { + const yaml1 = new YamlEditor('./config/defSet/config.yaml') + const data1 = yaml1.get('AdapterInput') + data1.token = token + yaml.set('AdapterInput', data1) + } else { + data.token = token + yaml.set('AdapterInput', data) + } + + yaml.save() + } catch (e) { + logger.error('AdapterInput token更换失败,请手动更换token') + } +} + +// 清空文件夹 +fs.readdirSync('./temp/input').forEach((file) => { + fs.unlinkSync(`./temp/input/${file}`) +}) + +/** + * - 标准输入输出适配器 + */ +export class AdapterInput implements KarinAdapter { + #stdin: boolean + socket!: WebSocket + account: KarinAdapter['account'] + adapter: KarinAdapter['adapter'] + version: KarinAdapter['version'] + constructor () { + this.#stdin = false + this.account = { uid: 'input', uin: 'input', name: 'input' } + this.adapter = { id: 'shell', name: 'input', type: 'internal', sub_type: 'internal', start_time: Date.now(), connect: '' } + this.version = { name: 'input', app_name: 'input', version: '1.0.0' } + } + + get self_id () { + return this.account.uid + } + + stdin () { + if (this.#stdin) return + this.#stdin = true + process.stdin.on('data', data => this.#input(data.toString())) + process.once('stdin.close', () => process.stdin.removeAllListeners('data')) + } + + logger (level: 'info' | 'error' | 'trace' | 'debug' | 'mark' | 'warn' | 'fatal', ...args: any[]) { + logger.bot(level, this.account.uid || this.account.uin, ...args) + } + + async #input (elements: string) { + const message = { + event: 'message' as 'message' | 'message_sent', + self_id: 'input', + user_id: 'input', + time: Date.now(), + message_id: `input.${Date.now()}`, + message_seq: '', + sender: { + uid: 'input', + uin: 'input', + nick: 'input', + role: 'member' as 'member', + }, + elements: [{ type: 'text', text: elements }] as KarinElement[], + contact: { + scene: 'private' as 'private' | 'group', + peer: 'input', + sub_peer: '', + }, + group_id: '', + raw_message: elements, + } + + const e = new KarinMessage(message) + e.bot = this + /** + * 快速回复 开发者不应该使用这个方法,应该使用由karin封装过后的reply方法 + */ + e.replyCallback = async elements => { + this.SendMessage(e.contact, elements) + return { message_id: e.message_id } + } + + listener.emit('message', e) + } + + async #MsgToFile (type: 'image' | 'voice', file: Uint8Array | string): Promise { + if (!msgToFile) return '' + + // 判断是否为string 如果是则继续判断是否为url、path + if (typeof file === 'string') { + if (file.startsWith('http')) return file + if (common.exists(file)) return file + } + + const buffer = await common.buffer(file) as Uint8Array + // 生成文件名 根据type生成不同的文件后缀 + const name = `${Date.now()}.${type === 'image' ? 'jpg' : type === 'voice' ? 'mp3' : 'file'}` + // 写入文件 + fs.writeFileSync(`./temp/input/${name}`, buffer) + return `[${type === 'image' ? '图片' : '语音'}: http://${ip}:${config.Server.http.port}/api/input?name=${name}&token=${token} ]` + } + + async GetVersion () { + const data = this.version + delete (data as { name?: string }).name + return data + } + + async SendMessage (_contact: contact, elements: Array) { + const text = [] + for (const v of elements) { + switch (v.type) { + case 'at': + text.push(`@${v.uid}`) + break + case 'face': + text.push(`[表情:${v.id}]`) + break + case 'text': + text.push(v.text) + break + case 'image': + case 'voice': + text.push(await this.#MsgToFile(v.type, v.file)) + break + default: + text.push(`[未知消息类型:${JSON.stringify(v)}]`) + } + } + this.logger('info', text.join('')) + return { message_id: 'input' } + } + + getAvatarUrl () { + return 'https://p.qlogo.cn/gh/967068507/967068507/0' + } + + getGroupAvatar () { + return 'https://p.qlogo.cn/gh/967068507/967068507/0' + } + + async GetCurrentAccount () { + return { account_uid: 'input', account_uin: 'input', account_name: 'input' } + } + + async GetEssenceMessageList (): Promise { throw new Error('Method not implemented.') } + async DownloadForwardMessage (): Promise { throw new Error('Method not implemented.') } + async SetEssenceMessage (): Promise { throw new Error('Method not implemented.') } + async DeleteEssenceMessage (): Promise { throw new Error('Method not implemented.') } + async SetFriendApplyResult (): Promise { throw new Error('Method not implemented.') } + async SetGroupApplyResultRequest (): Promise { throw new Error('Method not implemented.') } + async SetInvitedJoinGroupResult (): Promise { throw new Error('Method not implemented.') } + async ReactMessageWithEmojiRequest (): Promise { throw new Error('Method not implemented.') } + async UploadPrivateFile (): Promise { throw new Error('Method not implemented.') } + async UploadGroupFile (): Promise { throw new Error('Method not implemented.') } + async UploadForwardMessage (): Promise { throw new Error('Method not implemented.') } + async sendForwardMessage (): Promise { throw new Error('Method not implemented.') } + async SendMessageByResId (): Promise { throw new Error('Method not implemented.') } + async RecallMessage (): Promise { throw new Error('Method not implemented.') } + async GetMessage (): Promise { throw new Error('Method not implemented.') } + async GetHistoryMessage (): Promise { throw new Error('Method not implemented.') } + async VoteUser (): Promise { throw new Error('Method not implemented.') } + async KickMember (): Promise { throw new Error('Method not implemented.') } + async BanMember (): Promise { throw new Error('Method not implemented.') } + async SetGroupWholeBan (): Promise { throw new Error('Method not implemented.') } + async SetGroupAdmin (): Promise { throw new Error('Method not implemented.') } + async ModifyMemberCard (): Promise { throw new Error('Method not implemented.') } + async ModifyGroupName (): Promise { throw new Error('Method not implemented.') } + async LeaveGroup (): Promise { throw new Error('Method not implemented.') } + async SetGroupUniqueTitle (): Promise { throw new Error('Method not implemented.') } + async GetStrangerProfileCard (): Promise { throw new Error('Method not implemented.') } + async GetFriendList (): Promise { throw new Error('Method not implemented.') } + async GetGroupInfo (): Promise { throw new Error('Method not implemented.') } + async GetGroupList (): Promise { throw new Error('Method not implemented.') } + async GetGroupMemberInfo (): Promise { throw new Error('Method not implemented.') } + async GetGroupMemberList (): Promise { throw new Error('Method not implemented.') } + async GetGroupHonor (): Promise { throw new Error('Method not implemented.') } +} + +if (enable) { + const bot = new AdapterInput() + bot.stdin() + /** 注册bot */ + listener.emit('bot', { type: 'internal', bot }) +} diff --git a/src/adapter/onebot/onebot11.ts b/src/adapter/onebot/onebot11.ts index dc70872..cc3f374 100644 --- a/src/adapter/onebot/onebot11.ts +++ b/src/adapter/onebot/onebot11.ts @@ -17,6 +17,9 @@ import { OneBot11Segment, CustomNodeSegment, OneBot11ApiParamsType, + GroupInfo, + KarinNodeElement, + CustomMusicElemen, } from 'karin/types' /** @@ -28,10 +31,6 @@ export class OneBot11 implements KarinAdapter { * 是否初始化 */ #init = false - /** - * 机器人QQ号 - */ - self_id: string /** * - 重连次数 仅正向ws使用 */ @@ -42,7 +41,6 @@ export class OneBot11 implements KarinAdapter { version: KarinAdapter['version'] constructor () { - this.self_id = '' this.index = 0 this.account = { uid: '', uin: '', name: '' } this.adapter = { id: 'QQ', name: 'OneBot11', type: 'ws', sub_type: 'internal', start_time: Date.now(), connect: '' } @@ -136,6 +134,10 @@ export class OneBot11 implements KarinAdapter { this.#init = true } + get self_id () { + return this.account.uid || this.account.uin + } + /** * 获取当前登录号信息 */ @@ -584,10 +586,9 @@ export class OneBot11 implements KarinAdapter { /** * onebot11转karin - * @param {Array<{type: string, data: any}>} data onebot11格式消息 * @return karin格式消息 * */ - AdapterConvertKarin (data: Array) { + AdapterConvertKarin (data: Array): Array { const elements = [] for (const i of data) { switch (i.type) { @@ -641,12 +642,9 @@ export class OneBot11 implements KarinAdapter { /** * karin转onebot11 * @param data karin格式消息 - * @return {Array<{type: string, data: any}>} onebot11格式消息 * */ - KarinConvertAdapter (data: Array) { + KarinConvertAdapter (data: Array): Array { const elements = [] - // const selfUin = this.account.uin - // const selfNick = this.account.name for (const i of data) { switch (i.type) { @@ -668,37 +666,34 @@ export class OneBot11 implements KarinAdapter { elements.push({ type: i.type, data: { file: i.file } }) break } - case 'xml': + case 'xml': { + elements.push({ type: 'xml', data: { data: i.data } }) + break + } case 'json': { - elements.push({ type: i.type, data: { data: i.data } }) + elements.push({ type: 'json' as 'json', data: { data: i.data } }) break } - // case 'node': { - // let { type, user_id = selfUin, nickname = selfNick, content } = i - // content = this.KarinConvertAdapter(content) - // elements.push({ type, data: { uin: user_id, name: nickname, content } }) - // break - // } case 'forward': { elements.push({ type: 'forward', data: { id: i.res_id } }) break } + case 'record': case 'voice': { elements.push({ type: 'record', data: { file: i.file, magic: i.magic || false } }) break } case 'music': { - // if (i.platform) { - // elements.push({ type: 'music', data: { type: i.platform, id: i.id } }) - // } else { - // const { url, audio, title, content, image } = i - // elements.push({ type: 'music', data: { type: 'custom', url, audio, title, content, image } }) - // } + if (i.id) { + elements.push({ type: 'music', data: { type: i.platform, id: i.id } }) + } else { + const { url, audio, title, author, pic } = i as unknown as CustomMusicElemen + elements.push({ type: 'music', data: { type: 'custom', url, audio, title, content: author, image: pic } }) + } break } case 'button': { - // todo - // elements.push({ type: 'button', data: { buttons: i.buttons } }) + elements.push({ type: 'button', data: i.data }) break } case 'markdown': { @@ -706,23 +701,55 @@ export class OneBot11 implements KarinAdapter { elements.push({ type, data: { ...data } }) break } - // case 'rows': { - // for (const val of i.rows) { - // elements.push({ type: 'button', data: { buttons: val.buttons } }) - // } - // break - // } + case 'rows': { + for (const val of i.rows) { + elements.push({ type: 'button', data: val.data }) + } + break + } case 'poke': { elements.push({ type: 'poke', data: { type: i.poke_type, id: i.id } }) break } + case 'bubble_face': { + elements.push({ type: 'bubble_face', data: { id: i.id, count: i.count } }) + break + } + case 'contact': { + elements.push({ type: 'contact', data: { type: i.scene, id: i.peer } }) + break + } + case 'location': { + elements.push({ type: 'location', data: { lat: i.lat, lon: i.lon, title: i.title, content: i.address } }) + break + } + case 'long_msg': + case 'basketball': + case 'dice': + case 'market_face': + case 'rps': { + elements.push({ type: i.type, data: { id: i.id } }) + break + } + case 'gift': { + elements.push({ type: 'gift', data: { qq: i.qq, id: i.id } }) + break + } + case 'share': { + elements.push({ type: 'share', data: { url: i.url, title: i.title, content: i.content, image: i.image } }) + break + } + case 'weather': { + elements.push({ type: 'weather', data: { city: i.city, type: i.type } }) + break + } default: { elements.push(i) - logger.info(i) + break } } } - return elements + return elements as Array } /** @@ -798,14 +825,22 @@ export class OneBot11 implements KarinAdapter { * @param elements - nodes * @returns - 资源id * */ - async UploadForwardMessage (contact: { scene: Scene; peer: string }, elements: any[]) { + async UploadForwardMessage (contact: contact, elements: KarinNodeElement[]) { if (!Array.isArray(elements)) elements = [elements] if (elements.some((element: { type: string }) => element.type !== 'node')) { throw new Error('elements should be all node type') } const { scene, peer } = contact const message_type = scene === 'group' ? 'group_id' : 'user_id' - const messages = this.KarinConvertAdapter(elements) + const messages = [] + const selfUin = this.account.uin + const selfNick = this.account.name + + for (const i of elements) { + const { type, user_id, nickname, content: contents } = i + const content = this.KarinConvertAdapter(contents as KarinElement[]) + messages.push({ type, data: { uin: user_id || selfUin, name: nickname || selfNick, content } }) + } const params = { [message_type]: String(peer), messages } return await this.SendApi('send_forward_msg', params) @@ -816,7 +851,7 @@ export class OneBot11 implements KarinAdapter { * @param contact - 联系人信息 * @param id - 资源id * */ - async SendMessageByResId (contact: { scene: Scene; peer: string }, id: any) { + async SendMessageByResId (contact: contact, id: string) { const { scene, peer } = contact const message_type = scene === 'group' ? 'group' : 'private' const key = scene === 'group' ? 'group_id' : 'user_id' @@ -828,7 +863,7 @@ export class OneBot11 implements KarinAdapter { /** * 撤回消息 - * @param {null} [_contact] - ob11无需提供contact参数 + * @param _contact - ob11无需提供contact参数 * @param message_id - 消息ID * @returns {Promise} */ @@ -839,12 +874,12 @@ export class OneBot11 implements KarinAdapter { /** * 获取消息 - * @param {null} [_contact] - ob11无需提供contact参数 + * @param _contact - ob11无需提供contact参数 * @param message_id - 消息ID * @returns {Promise} - 消息内容 */ - async GetMessage (_contact: any, message_id: any) { + async GetMessage (_contact: contact, message_id: string) { let res = await this.SendApi('get_msg', { message_id }) res = { time: res.time, @@ -1074,22 +1109,8 @@ export class OneBot11 implements KarinAdapter { * 获取群信息 * @param group_id - 群号 * @param no_cache - 是否不使用缓存 - * @returns {Promise} - 群信息 */ - async GetGroupInfo (group_id: string, no_cache = false) { - /** - * @type {{ - * group_id: number, - * group_name: string, - * group_memo: string, - * group_remark: string, - * group_create_time: number, - * group_level: number, - * member_count: number, - * max_member_count: number, - * admins: number[] - * }} - */ + async GetGroupInfo (group_id: string, no_cache = false): Promise { const groupInfo = await this.SendApi('get_group_info', { group_id, no_cache }) return { group_id: groupInfo.group_id, diff --git a/src/core/index.ts b/src/core/index.ts index 745106c..059dfa1 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -7,3 +7,4 @@ export * from './plugin.app' export * from './plugin.loader' export * from './process' export * from './server' +import '../adapter/input/index' diff --git a/src/core/listener.ts b/src/core/listener.ts index 6edbdef..2db3b09 100644 --- a/src/core/listener.ts +++ b/src/core/listener.ts @@ -3,6 +3,8 @@ import { pluginLoader } from './plugin.loader' import { common, logger, config } from 'karin/utils' import { MessageHandler } from 'karin/event/message.handler' import { KarinAdapter, contact, KarinElement } from 'karin/types' +import NoticeHandler from 'karin/event/notice.handler' +import RequestHandler from 'karin/event/request.handler' /** * 监听器管理 @@ -36,11 +38,13 @@ class Listeners extends EventEmitter { this.addAdapter(data) }) this.on('bot', data => { - this.addBot(data) + if (!this.addBot(data)) return logger.info(`[机器人][注册][${data.type}] ` + logger.green(`[account:${data.bot.account.uid || data.bot.account.uin}(${data.bot.account.name})]`)) this.emit('karin:online', data.bot.account.uid || data.bot.account.uin) }) this.on('message', data => new MessageHandler(data)) + this.on('notice', data => new NoticeHandler(data)) + this.on('request', data => new RequestHandler(data)) } /** @@ -50,7 +54,7 @@ class Listeners extends EventEmitter { this.index++ const index = this.index if (!data.bot) { - logger.error('[Bot管理][注册] 注册失败: Bot实例不能为空') + logger.error('[Bot管理][注册] 注册失败: Bot实例不能为空', JSON.stringify(data)) return false } diff --git a/src/core/server.ts b/src/core/server.ts index 06b4d69..d3702c7 100644 --- a/src/core/server.ts +++ b/src/core/server.ts @@ -82,6 +82,33 @@ export const server = new (class Server { } }) + /** 控制台适配器 */ + this.app.get('/api/input', (req, res) => { + const name = req.query.name as string + const token = req.query.token as string + if (!name || !token) { + logger.error('[HTTP][input] 缺少参数', req.query) + return res.status(403).json({ error: '禁止访问', message: '缺少参数' }) + } + // 禁止键入向上级目录 + if (name.includes('/')) { + logger.error('[HTTP][input] 无效的文件名', name) + return res.status(403).json({ error: '禁止访问', message: '无效的文件名' }) + } + const CfgToken = config.Config.AdapterInput.token + if (CfgToken === 'AdapterInput' || CfgToken !== token) { + logger.error('[HTTP][input] 无效的令牌', token) + return res.status(403).json({ error: '禁止访问', message: '无效的令牌' }) + } + const file = process.cwd() + `/temp/input/${name}` + if (!fs.existsSync(file)) { + logger.error('[HTTP][input] 文件不存在', file) + return res.status(404).json({ error: '文件不存在', message: '找不到指定文件' }) + } + logger.info(`${logger.yellow('[HTTP][input]')} ${logger.green(token)} file:${file}`) + res.sendFile(file) + }) + /** 监听端口 */ const { host, port } = config.Server.http this.server.listen(port, host, () => { diff --git a/src/event/event.handler.ts b/src/event/event.handler.ts index 45d7926..a5cb5d9 100644 --- a/src/event/event.handler.ts +++ b/src/event/event.handler.ts @@ -135,7 +135,7 @@ export default class EventHandler { if (this.e.isGroup) { review.GroupMsgPrint(this.e) && logger.bot('info', this.e.self_id, `${logger.green(`Send Group ${this.e.group_id}: `)}${ReplyLog}`) } else { - logger.bot('info', this.e.self_id, `${logger.green(`Send private ${this.e.user_id}: `)}${ReplyLog}`) + this.e.self_id === 'input' && logger.bot('info', this.e.self_id, `${logger.green(`Send private ${this.e.user_id}: `)}${ReplyLog}`) } let message_id = '' diff --git a/src/event/event.ts b/src/event/event.ts index 45b9ec0..d205397 100644 --- a/src/event/event.ts +++ b/src/event/event.ts @@ -151,7 +151,7 @@ export class KarinEvent { }) { this.self_id = self_id this.user_id = user_id - this.group_id = group_id + this.group_id = contact.scene === 'group' ? (contact.peer || group_id) : group_id this.time = time this.event = event this.event_id = event_id diff --git a/src/types/adapter.ts b/src/types/adapter.ts index e2eb50e..de9536d 100644 --- a/src/types/adapter.ts +++ b/src/types/adapter.ts @@ -53,7 +53,7 @@ export interface KarinAdapter { /** * - 适配器名称 */ - name: 'ICQQ' | 'OneBot11' | 'OntBot12' | 'Kritor' | string + name: 'ICQQ' | 'OneBot11' | 'OntBot12' | 'Kritor' | 'input' | string /** * - 适配器类型 */ diff --git a/src/types/config.ts b/src/types/config.ts index 397d2ee..ffdae01 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -132,6 +132,27 @@ export interface Config { * 管理员列表 */ admin: string[] + /** + * + */ + AdapterInput: { + /** + * - 是否开启input适配器 + */ + enable: boolean + /** + * - 是否将语音、图片、视频消息转为文件 转为文件后可通过url访问 + */ + msgToFile: boolean + /** + * - url访问token 如果为 AdapterInput 每次启动后会重新生成 + */ + token: string + /** + * - ip地址 + */ + ip: string + } /** * 黑名单相关 */ diff --git a/src/utils/common.ts b/src/utils/common.ts index ca00e53..d28ed24 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -186,7 +186,7 @@ export const common = new (class Common { * @param - 为true时,http地址会直接返回,否则会下载文件并转换为base64字符串 * @returns 返回base64字符串 */ - async base64 (file: string | Buffer | Readable, options = { http: false }): Promise { + async base64 (file: any, options = { http: false }): Promise { /** 先判断是否非字符串情况 */ if (typeof file !== 'string') { /** buffer */ @@ -249,12 +249,11 @@ export const common = new (class Common { /** * 将文件转换为Buffer对象 - * @param {string|Buffer|http|stream.Readable} file - 文件路径或Buffer对象、可读流对象、http地址、base64://字符串 - * @param {object} options - 附加数据 - * @param {boolean} options.http - 为true时,http地址会直接返回,否则会下载文件并转换为Buffer对象 - * @returns {Promise} - 返回Buffer对象 + * @param file - 文件路径或Buffer对象、可读流对象、http地址、base64://字符串 + * @param options - 选项 + * @returns - 返回Buffer对象 */ - async buffer (file: string | Buffer | Readable, options = { http: false }): Promise { + async buffer (file: any, options = { http: false }): Promise { if (typeof file !== 'string') { if (Buffer.isBuffer(file)) return file if (file instanceof Readable) return this.stream(file) diff --git a/src/utils/config.ts b/src/utils/config.ts index fdae95d..8fbe219 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -9,7 +9,7 @@ import { Redis, App, Config, Server, Package, GroupCfg } from 'karin/types' export const config = new (class Cfg { dir: string _path: string - _pathDef: string + npmCfgDir: string change: Map watcher: Map review: boolean @@ -17,7 +17,7 @@ export const config = new (class Cfg { constructor () { this.dir = karinDir this._path = process.cwd() + '/config' - this._pathDef = this.dir + '/config/defSet' + this.npmCfgDir = this.dir + '/config/defSet' /** 缓存 */ this.change = new Map() @@ -30,19 +30,25 @@ export const config = new (class Cfg { /** 初始化配置 */ async initCfg () { - if (!fs.existsSync(this._path)) fs.mkdirSync(this._path) - this._path = process.cwd() + '/config/config' - if (!fs.existsSync(this._path)) fs.mkdirSync(this._path) - const files = fs.readdirSync(this._pathDef).filter(file => file.endsWith('.yaml')) - for (const file of files) { - const path = `${this._path}/${file}` - const pathDef = `${this._pathDef}/${file}` - if (!fs.existsSync(path)) fs.copyFileSync(pathDef, path) + const list = [ + this._path, + this._path + '/config', + process.cwd() + '/temp/input', + './plugins', + './plugins/karin-plugin-example', + ] + + list.forEach(path => this.checkPath(path)) + if (this.npmCfgDir !== (this._path + '/defSet').replace(/\\/g, '/')) { + const files = fs.readdirSync(this.npmCfgDir).filter(file => file.endsWith('.yaml')) + files.forEach(file => { + const path = `${this._path}/config/${file}` + const pathDef = `${this.npmCfgDir}/${file}` + if (!fs.existsSync(path)) fs.copyFileSync(pathDef, path) + }) } // 创建插件文件夹文件夹 - if (!fs.existsSync('./plugins')) fs.mkdirSync('./plugins') - if (!fs.existsSync('./plugins/karin-plugin-example')) fs.mkdirSync('./plugins/karin-plugin-example') const plugins = this.getPlugins() this.dirPath('data', plugins) this.dirPath('temp', plugins) @@ -57,15 +63,22 @@ export const config = new (class Cfg { return files.filter(file => file.isDirectory() && (file.name.startsWith('karin-plugin-'))).map(dir => dir.name) } + /** + * 检查路径是否存在 不存在则创建 + */ + checkPath (path: string) { + if (!fs.existsSync(path)) fs.mkdirSync(path) + } + /** * 为每一个插件建立对应的文件夹 */ async dirPath (name: string, plugins: string[]) { name = `./${name}` - if (!fs.existsSync(name)) fs.mkdirSync(name) + this.checkPath(name) for (const plugin of plugins) { const path = `${name}/${plugin}` - if (!fs.existsSync(path)) fs.mkdirSync(path) + this.checkPath(path) } } diff --git a/src/utils/yamlEditor.ts b/src/utils/yamlEditor.ts index 94f62bd..556936d 100644 --- a/src/utils/yamlEditor.ts +++ b/src/utils/yamlEditor.ts @@ -45,7 +45,7 @@ export class YamlEditor { * @param path - 路径,用点号分隔,例如:'a.b.c' * @param value - 要设置的值 */ - set (path: string | string[], value: string) { + set (path: string | string[], value: string | boolean) { try { path = typeof path === 'string' ? path.split('.') : path this.document.setIn(path, value)