commit bc9cf2900493f45c4410837f6775a163d68cbd7a Author: ningmengchongshui <916415899@qq.com> Date: Tue Jun 11 21:03:42 2024 +0800 update: 修改 diff --git a/README.md b/README.md new file mode 100644 index 0000000..16881d1 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# System-Plugin + +Miao-Yunzai V4 插件开发示例 + +## 使用教程 + +- 安装源码 + +```sh +git clone --depth=1 -b dev https://github.com/yoimiya-kokomi/Miao-Yunzai.git +``` + diff --git a/apps.ts b/apps.ts new file mode 100644 index 0000000..c48df00 --- /dev/null +++ b/apps.ts @@ -0,0 +1,18 @@ +/** + * *********** + * 不想开启的功能,自行注释 + * ***** + */ +export * from './apps/add' +export * from './apps/disFriPoke' +export * from './apps/disablePrivate' +export * from './apps/friend' +export * from './apps/invite' +export * from './apps/quit' +export * from './apps/restart' +export * from './apps/sendLog' +export * from './apps/status' +export * from './apps/update' +export * from './apps/example2' +export * from './apps/newcomer' +export * from './apps/outNotice' \ No newline at end of file diff --git a/apps/add.ts b/apps/add.ts new file mode 100644 index 0000000..d62ca33 --- /dev/null +++ b/apps/add.ts @@ -0,0 +1,966 @@ + +import fs from 'node:fs' +import lodash from 'lodash' +import { pipeline } from 'stream' +import { promisify } from 'util' +import fetch from 'node-fetch' +import moment from 'moment' +import { ConfigController as cfg } from '#miao/config' +import { plugin } from '#miao/core' +import * as common from '#miao/core' + +const textArr = {} + +export class add extends plugin { + + /** + * + */ + path = './data/textJson/' + + /** + * + */ + facePath = './data/face/' + + /** + * + */ + isGlobal = false + + /** + * + */ + constructor() { + /** + name: '添加表情', + dsc: '添加表情,文字等', + */ + super({ + event: 'message', + priority: 50000, + }); + + /** + * rule + */ + this.rule = [ + { + reg: '^#(全局)?添加(.*)', + fnc: this.add.name + }, + { + reg: '^#(全局)?删除(.*)', + fnc: this.del.name + }, + { + reg: '(.*)', + fnc: this.getText.name, + log: false + }, + { + reg: '^#+(全局)?(?:查看|查询)(?:表情|词条)(.+)$', + fnc: this.faceDetail.name + }, + { + reg: '#(全局)?(表情|词条)(.*)', + fnc: this.list.name + }, + ] + + } + + /** + * + */ + async init() { + if (!fs.existsSync(this.path)) { + fs.mkdirSync(this.path) + } + if (!fs.existsSync(this.facePath)) { + fs.mkdirSync(this.facePath) + } + } + + /** + * + */ + async accept() { + /** 处理消息 */ + if (this.e.atBot && this.e.msg && this.e?.msg.includes('添加') && !this.e?.msg.includes('#')) { + this.e.msg = '#' + this.e.msg + } + } + + /** + * + */ + get grpKey() { + return `Yz:group_id:${this.e.user_id}` + } + + /** + * + */ + async add() { + this.isGlobal = this.e?.msg.includes("全局"); + await this.getGroupId() + + if (!this.group_id) { + this.e.reply('请先在群内触发表情,确定添加的群') + return + } + + this.initTextArr() + + if (!this.checkAuth()) return + if (!this.checkKeyWord()) return + if (await this.singleAdd()) return + /** 获取关键词 */ + this.getKeyWord() + + if (!this.keyWord) { + this.e.reply('添加错误:没有关键词') + return + } + if (/uid/i.test(this.keyWord)) { + this.e.reply('请勿添加特殊关键词') + return + } + + this.setContext('addContext') + + await this.e.reply('请发送添加内容', false, { at: true }) + } + + /** + * + */ + async getGroupId() { + /** 添加全局表情,存入到机器人qq文件中 */ + if (this.isGlobal) { + this.group_id = this.e.bot.uin; + return this.e.bot.uin; + } + + if (this.e.isGroup) { + this.group_id = this.e.group_id + redis.setEx(this.grpKey, 3600 * 24 * 30, String(this.group_id)) + return this.group_id + } + + // redis获取 + let groupId = await redis.get(this.grpKey) + if (groupId) { + this.group_id = groupId + return this.group_id + } + + return false + } + + /** + * + */ + checkAuth() { + if (this.e.isMaster) return true + + let groupCfg = cfg.getGroup(this.group_id) + if (groupCfg.imgAddLimit == 2) { + this.e.reply('暂无权限,只有主人才能操作') + return false + } + if (groupCfg.imgAddLimit == 1) { + if (!this.e.bot.gml.has(this.group_id)) { + return false + } + if (!this.e.bot.gml.get(this.group_id).get(this.e.user_id)) { + return false + } + if (!this.e.member.is_admin) { + this.e.reply('暂无权限,只有管理员才能操作') + return false + } + } + + if (!this.e.isGroup && groupCfg.addPrivate != 1) { + this.e.reply('禁止私聊添加') + return false + } + + return true + } + + /** + * + */ + checkKeyWord() { + if (this.e.img && this.e.img.length > 1) { + this.e.reply('添加错误:只能发送一个表情当关键词') + return false + } + + if (this.e.at) { + let at = lodash.filter(this.e.message, (o) => { return o.type == 'at' && o.qq != this.e.bot.uin }) + if (at.length > 1) { + this.e.reply('添加错误:只能@一个人当关键词') + return false + } + } + + if (this.e.img && this.e.at) { + this.e.reply('添加错误:没有关键词') + return false + } + + return true + } + + /** + * 单独添加 + * @returns + */ + async singleAdd() { + if (this.e.message.length != 2) return false + let msg = lodash.keyBy(this.e.message, 'type') + if (!this.e.msg || !msg.image) return false + + // #全局添加文字+表情包,无法正确添加到全局路径 + this.e.isGlobal = this.isGlobal; + let keyWord = this.e.msg.replace(/#|#|图片|表情|添加|全局/g, '').trim() + if (!keyWord) return false + + this.keyWord = this.trimAlias(keyWord) + this.e.keyWord = this.keyWord + + if (this.e.msg.includes('添加图片')) { + this.e.addImg = true + } + this.e.message = [msg.image] + await this.addContext() + + return true + } + + /** + * 获取添加关键词 + */ + getKeyWord() { + this.e.isGlobal = this.e.msg.includes("全局"); + + this.keyWord = this.e.toString() + .trim() + /** 过滤#添加 */ + .replace(/#|#|图片|表情|添加|删除|全局/g, '') + /** 过滤@ */ + .replace(new RegExp('{at:' + this.e.bot.uin + '}', 'g'), '') + .trim() + + this.keyWord = this.trimAlias(this.keyWord) + this.e.keyWord = this.keyWord + + if (this.e.msg.includes('添加图片')) { + this.e.addImg = true + } + } + + /** + * 过滤别名 + * @param msg + * @returns + */ + trimAlias(msg) { + let groupCfg = cfg.getGroup(this.group_id) + let alias = groupCfg.botAlias + if (!Array.isArray(alias)) { + alias = [alias] + } + for (let name of alias) { + if (msg.startsWith(name)) { + msg = lodash.trimStart(msg, name).trim() + } + } + + return msg + } + + /** + * 添加内容 + * @returns + */ + async addContext() { + this.isGlobal = this.e.isGlobal || this.getContext()?.addContext?.isGlobal; + await this.getGroupId() + /** 关键词 */ + let keyWord = this.keyWord || this.getContext()?.addContext?.keyWord + let addImg = this.e.addImg || this.getContext()?.addContext?.addImg + + /** 添加内容 */ + let message = this.e.message + + let retMsg = this.getRetMsg() + this.finish('addContext') + + for (let i in message) { + if (message[i].type == "at") { + if (message[i].qq == this.e.bot.uin) { + this.e.reply("添加内容不能@机器人!"); + return; + } + } + if (message[i].type == "file") { + this.e.reply("添加错误:禁止添加文件"); + return; + } + + // 保存用户信息用于追溯添加者 + message[i].from_user = { + card: this.e.sender.card, + nickname: this.e.sender.nickname, + user_id: this.e.sender.user_id, + }; + } + + if (message.length == 1 && message[0].type == 'image') { + let local = await this.saveImg(message[0].url, keyWord) + if (!local) return + message[0].local = local + message[0].asface = true + if (addImg) message[0].asface = false + } + + if (!textArr[this.group_id]) textArr[this.group_id] = new Map() + + /** 支持单个关键词添加多个 */ + let text = textArr[this.group_id].get(keyWord) + if (text) { + text.push(message) + textArr[this.group_id].set(keyWord, text) + } else { + text = [message] + textArr[this.group_id].set(keyWord, text) + } + + if (text.length > 1 && retMsg[0].type != 'image') { + retMsg.push(String(text.length)) + } + + retMsg.unshift('添加成功:') + + this.saveJson() + this.e.reply(retMsg) + } + + /** + * 添加成功回复消息 + * @returns + */ + getRetMsg() { + let retMsg = this.getContext() + let msg = '' + if (retMsg?.addContext?.message) { + msg = retMsg.addContext.message + + for (let i in msg) { + if (msg[i].type == 'text' && msg[i].text.includes('添加')) { + msg[i].text = this.trimAlias(msg[i].text) + msg[i].text = msg[i].text.trim().replace(/#|#|图片|表情|添加|全局/g, '') + if (!msg[i].text) delete msg[i] + continue + } + if (msg[i].type == 'at') { + if (msg[i].qq == this.e.bot.uin) { + delete msg[i] + continue + } else { + msg[i].text = '' + } + } + } + } + if (!msg && this.keyWord) { + msg = [this.keyWord] + } + return lodash.compact(msg) + } + + /** + * + */ + saveJson() { + let obj = {} + for (let [k, v] of textArr[this.group_id]) { + obj[k] = v + } + + fs.writeFileSync(`${this.path}${this.group_id}.json`, JSON.stringify(obj, '', '\t')) + } + + /** + * + */ + saveGlobalJson() { + let obj = {}; + for (let [k, v] of textArr[this.e.bot.uin]) { + obj[k] = v; + } + + fs.writeFileSync( + `${this.path}${this.e.bot.uin}.json`, + JSON.stringify(obj, "", "\t") + ); + } + + /** + * + * @param url + * @param keyWord + * @returns + */ + async saveImg(url, keyWord) { + let groupCfg = cfg.getGroup(this.group_id) + let savePath = `${this.facePath}${this.group_id}/` + + if (!fs.existsSync(savePath)) { + fs.mkdirSync(savePath) + } + + const response = await fetch(url) + + keyWord = keyWord.replace(/\.|\\|\/|:|\*|\?|<|>|\|"/g, '_') + + if (!response.ok) { + this.e.reply('添加图片下载失败。。') + return false + } + + let imgSize = (response.headers.get('size') / 1024 / 1024).toFixed(2) + if (imgSize > 1024 * 1024 * groupCfg.imgMaxSize) { + this.e.reply(`添加失败:表情太大了,${imgSize}m`) + return false + } + + let type = response.headers.get('content-type').split('/')[1] + if (type == 'jpeg') type = 'jpg' + + if (fs.existsSync(`${savePath}${keyWord}.${type}`)) { + keyWord = `${keyWord}_${moment().format('X')}` + } + + savePath = `${savePath}${keyWord}.${type}` + + const streamPipeline = promisify(pipeline) + await streamPipeline(response.body, fs.createWriteStream(savePath)) + + return savePath + } + + /** + * + * @returns + */ + async getText() { + if (!this.e.message) return false + + this.isGlobal = false + + await this.getGroupId() + + if (!this.group_id) return false + + this.initTextArr() + + this.initGlobalTextArr() + + let keyWord = this.e.toString() + .replace(/#|#/g, '') + .replace(`{at:${this.e.bot.uin}}`, '') + .trim() + + keyWord = this.trimAlias(keyWord) + + let num = 0 + if (isNaN(keyWord)) { + num = keyWord.trim().match(/[0-9]+$/)?.[0] + + if (!isNaN(num) && !textArr[this.group_id].has(keyWord) && !textArr[this.e.bot.uin].has(keyWord)) { + keyWord = lodash.trimEnd(keyWord, num).trim() + num-- + } + } + + let msg = textArr[this.group_id].get(keyWord) || [] + let globalMsg = textArr[this.e.bot.uin].get(keyWord) || [] + if (lodash.isEmpty(msg) && lodash.isEmpty(globalMsg)) return false + + msg = [...msg, ...globalMsg] + /** 如果只有一个则不随机 */ + if (num >= 0 && msg.length === 1) { + msg = msg[num] + } else { + /** 随机获取一个 */ + num = lodash.random(0, msg.length - 1) + msg = msg[num] + } + + if (msg[0] && msg[0].local) { + if (fs.existsSync(msg[0].local)) { + let tmp = segment.image(msg[0].local) + tmp.asface = msg[0].asface + msg = tmp + } else { + // this.e.reply(`表情已删除:${keyWord}`) + return + } + } + + if (Array.isArray(msg)) { + msg.forEach(m => { + /** 去除回复@@ */ + if (m?.type == 'at') { delete m.text } + }) + } + + logger.mark(`[发送表情]${this.e.logText} ${keyWord}`) + let ret = await this.e.reply(msg) + if (!ret) { + this.expiredMsg(keyWord, num) + } + + return true + } + + /** + * + * @param keyWord + * @param num + */ + expiredMsg(keyWord, num) { + logger.mark(`[发送表情]${this.e.logText} ${keyWord} 表情已过期失效`) + + let arr = textArr[this.group_id].get(keyWord) + arr.splice(num, 1) + + if (arr.length <= 0) { + textArr[this.group_id].delete(keyWord) + } else { + textArr[this.group_id].set(keyWord, arr) + } + + this.saveJson() + } + + /** + * 初始化已添加内容 + * @returns + */ + initTextArr() { + if (textArr[this.group_id]) return + + textArr[this.group_id] = new Map() + + let path = `${this.path}${this.group_id}.json` + if (!fs.existsSync(path)) { + return + } + + try { + let text = JSON.parse(fs.readFileSync(path, 'utf8')) + for (let i in text) { + if (text[i][0] && !Array.isArray(text[i][0])) { + text[i] = [text[i]] + } + + textArr[this.group_id].set(String(i), text[i]) + } + } catch (error) { + logger.error(`json格式错误:${path}`) + delete textArr[this.group_id] + return false + } + + /** 加载表情 */ + let facePath = `${this.facePath}${this.group_id}` + + if (fs.existsSync(facePath)) { + const files = fs.readdirSync(`${this.facePath}${this.group_id}`).filter(file => /\.(jpeg|jpg|png|gif)$/g.test(file)) + for (let val of files) { + let tmp = val.split('.') + tmp[0] = tmp[0].replace(/_[0-9]{10}$/, '') + if (/at|image/g.test(val)) continue + + if (textArr[this.group_id].has(tmp[0])) continue + + textArr[this.group_id].set(tmp[0], [[{ + local: `${facePath}/${val}`, + asface: true + }]]) + } + + this.saveJson() + } else { + fs.mkdirSync(facePath) + } + } + + /** + * 初始化全局已添加内容 + * @returns + */ + initGlobalTextArr() { + if (textArr[this.e.bot.uin]) return; + + textArr[this.e.bot.uin] = new Map(); + + let globalPath = `${this.path}${this.e.bot.uin}.json`; + if (!fs.existsSync(globalPath)) { + return; + } + + try { + let text = JSON.parse(fs.readFileSync(globalPath, "utf8")); + + for (let i in text) { + if (text[i][0] && !Array.isArray(text[i][0])) { + text[i] = [text[i]]; + } + textArr[this.e.bot.uin].set(String(i), text[i]); + } + } catch (error) { + logger.error(`json格式错误:${globalPath}`); + delete textArr[this.e.bot.uin]; + return false; + } + + /** 加载表情 */ + let globalFacePath = `${this.facePath}${this.e.bot.uin}`; + + if (fs.existsSync(globalFacePath)) { + const files = fs + .readdirSync(`${this.facePath}${this.e.bot.uin}`) + .filter((file) => /\.(jpeg|jpg|png|gif)$/g.test(file)); + + for (let val of files) { + let tmp = val.split("."); + tmp[0] = tmp[0].replace(/_[0-9]{10}$/, ""); + if (/at|image/g.test(val)) continue; + + if (textArr[this.e.bot.uin].has(tmp[0])) continue; + + textArr[this.e.bot.uin].set(tmp[0], [ + [ + { + local: `${globalFacePath}/${val}`, + asface: true, + }, + ], + ]); + } + + this.saveGlobalJson(); + } else { + fs.mkdirSync(globalFacePath); + } + } + + /** + * + * @returns + */ + async del() { + this.isGlobal = this.e?.msg.includes("全局"); + await this.getGroupId() + if (!this.group_id) return false + if (!this.checkAuth()) return + + this.initTextArr() + + let keyWord = this.e.toString().replace(/#|#|图片|表情|删除|全部|全局/g, '') + + keyWord = this.trimAlias(keyWord) + + let num = false + let index = 0 + if (isNaN(keyWord)) { + num = keyWord.charAt(keyWord.length - 1) + + if (!isNaN(num) && !textArr[this.group_id].has(keyWord)) { + keyWord = lodash.trimEnd(keyWord, num).trim() + index = num - 1 + } else { + num = false + } + } + + let arr = textArr[this.group_id].get(keyWord) + if (!arr) { + // await this.e.reply(`暂无此表情:${keyWord}`) + return false + } + + let tmp = [] + if (num) { + if (!arr[index]) { + // await this.e.reply(`暂无此表情:${keyWord}${num}`) + return false + } + + tmp = arr[index] + arr.splice(index, 1) + + if (arr.length <= 0) { + textArr[this.group_id].delete(keyWord) + } else { + textArr[this.group_id].set(keyWord, arr) + } + } else { + if (this.e.msg.includes('删除全部')) { + tmp = arr + arr = [] + } else { + tmp = arr.pop() + } + + if (arr.length <= 0) { + textArr[this.group_id].delete(keyWord) + } else { + textArr[this.group_id].set(keyWord, arr) + } + } + if (!num) num = '' + + let retMsg = [{ type: 'text', text: '删除成功:' }] + for (let msg of this.e.message) { + if (msg.type == 'text') { + msg.text = msg.text.replace(/#|#|图片|表情|删除|全部|全局/g, '') + + if (!msg.text) continue + } + retMsg.push(msg) + } + if (num > 0) { + retMsg.push({ type: 'text', text: num }) + } + + await this.e.reply(retMsg) + + /** 删除图片 */ + tmp.forEach(item => { + let img = item + if (Array.isArray(item)) { + img = item[0] + } + if (img.local) { + fs.unlink(img.local, () => { }) + } + }) + + this.saveJson() + } + + /** + * + */ + async list() { + this.isGlobal = this.e?.msg.includes("全局"); + + let page = 1 + let pageSize = 100 + let type = 'list' + + await this.getGroupId() + if (!this.group_id) return false + + this.initTextArr() + + let search = this.e.msg.replace(/#|#|表情|词条|全局/g, '') + + if (search.includes('列表')) { + page = search.replace(/列表/g, '') || 1 + } else { + type = 'search' + } + + let list = textArr[this.group_id] + + if (lodash.isEmpty(list)) { + await this.e.reply('暂无表情') + return + } + + let arr = [] + for (let [k, v] of textArr[this.group_id]) { + if (type == 'list') { + arr.push({ key: k, val: v, num: arr.length + 1 }) + } else if (k.includes(search)) { + /** 搜索表情 */ + arr.push({ key: k, val: v, num: arr.length + 1 }) + } + } + + let count = arr.length + arr = arr.reverse() + + if (type == 'list') { + arr = this.pagination(page, pageSize, arr) + } + + if (lodash.isEmpty(arr)) { + return + } + + let msg = [], result = [], num = 0 + for (let i in arr) { + if (num >= page * pageSize) break + + let keyWord = await this.keyWordTran(arr[i].key) + if (!keyWord) continue + if (Array.isArray(keyWord)) { + keyWord.unshift(`${num + 1}、`) + // keyWord.push('\n') + keyWord.push(v => msg.push(v)) + } else if (keyWord.type) { + msg.push(`\n${num + 1}、`, keyWord) + } else { + msg.push(`${num + 1}、`, keyWord) + } + num++ + } + /** 数组分段 */ + for (const i in msg) { + result.push([msg[i]]) + } + /** 计算页数 */ + let book = count / pageSize; + if (book % 1 === 0) { + book = result; + } else { + book = Math.floor(book) + 1; + } + if (type == 'list' && msg.length >= pageSize) { + result.push(`更多内容请翻页查看\n如:#表情列表${Number(page) + 1}`) + } + + let title = `表情列表,第${page}页,共${count}条,共${book}页` + if (type == 'search') { + title = `表情${search},${count}条` + } + + let forwardMsg = await common.makeForwardMsg(this.e, [title, ...result], title) + + this.e.reply(forwardMsg) + } + + /** + * + */ + pagination(pageNo, pageSize, array) { + let offset = (pageNo - 1) * pageSize + return offset + pageSize >= array.length ? array.slice(offset, array.length) : array.slice(offset, offset + pageSize) + } + + /** + * 关键词转换成可发送消息 + * @param msg + * @returns + */ + async keyWordTran(msg) { + /** 图片 */ + if (msg.includes('{image')) { + let tmp = msg.split('{image') + if (tmp.length > 2) return false + + let md5 = tmp[1].replace(/}|_|:/g, '') + + msg = segment.image(`http://gchat.qpic.cn/gchatpic_new/0/0-0-${md5}/0`) + msg.asface = true + } else if (msg.includes('{at:')) { + let tmp = msg.match(/{at:(.+?)}/g) + + for (let qq of tmp) { + qq = qq.match(/[1-9][0-9]{4,14}/g)[0] + let member = await await this.e.bot.getGroupMemberInfo(this.group_id, Number(qq)).catch(() => { }) + let name = member?.card ?? member?.nickname + if (!name) continue + msg = msg.replace(`{at:${qq}}`, `@${name}`) + } + } else if (msg.includes('{face')) { + let tmp = msg.match(/{face(:|_)(.+?)}/g) + if (!tmp) return msg + msg = [] + for (let face of tmp) { + let id = face.match(/\d+/g) + msg.push(segment.face(id)) + } + } + + return msg + } + + /** + * + * @returns + */ + async faceDetail() { + if (!this.e.message) return false + this.isGlobal = false + await this.getGroupId() + if (!this.group_id) return false + let faceDetailReg = /^#+(全局)?(?:查看|查询)(?:表情|词条)(.+)$/ + let regGroup = faceDetailReg.exec(this.e.msg) + let keyWord + if (regGroup[1]) { + this.isGlobal = true + } + keyWord = regGroup[2].trim() + + if (keyWord === '') return + + this.initTextArr() + this.initGlobalTextArr() + + let faces = textArr[this.group_id].get(keyWord) || [] + let globalfaces = textArr[this.e.bot.uin].get(keyWord) || [] + faces = [...faces, ...globalfaces] + + if (lodash.isEmpty(faces)) { + await this.e.reply(`表情${keyWord}不存在`) + return + } + + + // process faces into replyArr in type: + let replyArr = [] + for (let i = 0; i < faces.length; i++) { + let face = faces[i] + let faceItem = face[0] + let fromUser = faceItem?.from_user + if (fromUser) { + fromUser = `添加者: ${fromUser.card}(${fromUser.nickname})[${fromUser.user_id}]` + } else { + fromUser = '未知' + } + let faceContent + console.log(faceItem) + if (faceItem.type === 'image') { + // face is an image + let tmp = segment.image(faceItem.local) + tmp.asface = faceItem.asface + faceContent = tmp + replyArr.push(`${i + 1}、${fromUser}`) + replyArr.push(faceContent) + } else { + faceContent = `${faceItem.text}` + replyArr.push(`${i + 1}、${fromUser}: ` + faceContent) + } + } + + if (lodash.isEmpty(replyArr)) { + return + } + + let forwardMsg = await common.makeForwardMsg(this.e, replyArr, `表情${keyWord}详情`) + + this.e.reply(forwardMsg) + } +} diff --git a/apps/disFriPoke.ts b/apps/disFriPoke.ts new file mode 100644 index 0000000..1e3a397 --- /dev/null +++ b/apps/disFriPoke.ts @@ -0,0 +1,25 @@ + +import { ConfigController as cfg } from '#miao/config' +import { plugin } from '#miao/core' +/** + * + */ +export class disFriPoke extends plugin { + constructor() { + super({ + name: '禁止私聊', + dsc: '对私聊禁用做处理当开启私聊禁用时只接收cookie以及抽卡链接', + event: 'notice.friend.poke' + }) + this.priority = 0 + } + + async accept() { + if (!cfg.other?.disablePrivate) return + + if (this.e.isMaster) return + + this.e.reply(cfg.other.disableMsg) + return 'return' + } +} diff --git a/apps/disablePrivate.ts b/apps/disablePrivate.ts new file mode 100644 index 0000000..a226954 --- /dev/null +++ b/apps/disablePrivate.ts @@ -0,0 +1,79 @@ + +import { ConfigController as cfg } from '#miao/config' +import { plugin } from '#miao/core' +/** + * + */ +export class disPri extends plugin { + /** + * + */ + constructor() { + /** + name: '禁止私聊', + dsc: '对私聊禁用做处理当开启私聊禁用时只接收cookie以及抽卡链接', + */ + super({ + event: 'message.private' + }) + /** + * + */ + this.priority = 0 + } + + /** + * + * @returns + */ + async accept() { + if (!cfg.other?.disablePrivate) return + + if (this.e.isMaster) return + + /** 发送日志文件,xlsx,json */ + if (this.e.file) { + if (!/(.*)\.txt|xlsx|json/ig.test(this.e.file?.name)) { + this.sendTips() + return 'return' + } else { + return false + } + } + + /** 绑定ck,抽卡链接 */ + let wordReg = /(.*)(ltoken|_MHYUUID|authkey=)(.*)|导出记录(json)*|(记录|安卓|苹果|ck|cookie|体力)帮助|^帮助$|^#*(删除|我的)ck$|^#(我的)?(uid|UID)[0-9]{0,2}$/g + /** 自定义通行字符 */ + let disableAdopt = cfg.other?.disableAdopt + if (!Array.isArray(disableAdopt)) { + disableAdopt = [] + } + disableAdopt = disableAdopt.filter(str => str != null && str !== ''); + let disableReg = `(.*)(${disableAdopt.join('|')})(.*)` + if (this.e.raw_message) { + if (!new RegExp(wordReg).test(this.e.raw_message) && (disableAdopt.length === 0 || !new RegExp(disableReg).test(this.e.raw_message))) { + this.sendTips() + return 'return' + } + } + } + + /** + * + * @returns + */ + async sendTips() { + /** 冷却cd 10s */ + let cd = 10 + + if (this.e.user_id == cfg.qq) return + + /** cd */ + let key = `Yz:disablePrivate:${this.e.user_id}` + if (await redis.get(key)) return + + this.e.reply(cfg.other.disableMsg) + + redis.setEx(key, cd, '1') + } +} \ No newline at end of file diff --git a/apps/example2.ts b/apps/example2.ts new file mode 100644 index 0000000..f8499f7 --- /dev/null +++ b/apps/example2.ts @@ -0,0 +1,40 @@ +import { plugin } from '#miao/core' +/** + * + */ +export class example2 extends plugin { + constructor () { + /** + name: '复读', + dsc: '复读用户发送的内容,然后撤回', + */ + super({ + event: 'message', + priority: 5000, + rule: [ + { + reg: '^#复读$', + fnc: 'repeat' + } + ] + }) + } + /** + * + */ + async repeat () { + /** 设置上下文,后续接收到内容会执行doRep方法 */ + this.setContext('doRep') + /** 回复 */ + await this.reply('请发送要复读的内容', false, { at: true }) + } + /** + * 接受内容 + */ + doRep () { + /** 复读内容 */ + this.reply(this.e.message, false, { recallMsg: 5 }) + /** 结束上下文 */ + this.finish('doRep') + } +} diff --git a/apps/friend.ts b/apps/friend.ts new file mode 100644 index 0000000..53e33ca --- /dev/null +++ b/apps/friend.ts @@ -0,0 +1,40 @@ + +import { ConfigController as cfg } from '#miao/config' +import { sleep } from '#miao/utils' +import { plugin } from '#miao/core' +/** + * + */ +export class friend extends plugin { + /** + * + */ + constructor() { + /** + * + name: 'autoFriend', + dsc: '自动同意好友', + */ + super({ + event: 'request.friend' + }) + } + /** + * + */ + async accept() { + /** + * + */ + if (this.e.sub_type == 'add' || this.e.sub_type == 'single') { + /** + * + */ + if (cfg.other.autoFriend == 1) { + logger.mark(`[自动同意][添加好友] ${this.e.user_id}`) + await sleep(2000) + this.e.approve(true) + } + } + } +} diff --git a/apps/invite.ts b/apps/invite.ts new file mode 100644 index 0000000..7419d61 --- /dev/null +++ b/apps/invite.ts @@ -0,0 +1,36 @@ + +import { ConfigController as cfg } from '#miao/config' +import { plugin } from '#miao/core' +/** + * + */ +export class invite extends plugin { + /** + * + */ + constructor() { + /** + * + name: 'invite', + dsc: '主人邀请自动进群', + */ + super({ + event: 'request.group.invite' + }) + } + /** + * + * @returns + */ + async accept() { + if (!cfg.masterQQ || !cfg.masterQQ.includes(String(this.e.user_id))) { + logger.mark(`[邀请加群]:${this.e.group_name}:${this.e.group_id}`) + return + } + logger.mark(`[主人邀请加群]:${this.e.group_name}:${this.e.group_id}`) + this.e.approve(true) + this.e.bot.sendPrivateMsg(this.e.user_id, `已同意加群:${this.e.group_name}`).catch((err) => { + logger.error(err) + }) + } +} diff --git a/apps/newcomer.ts b/apps/newcomer.ts new file mode 100644 index 0000000..6ca3a04 --- /dev/null +++ b/apps/newcomer.ts @@ -0,0 +1,42 @@ +import { plugin, segment } from '#miao/core' +/** + * + */ +export class newcomer extends plugin { + /** + * + */ + constructor() { + /** + name: '欢迎新人', + dsc: '新人入群欢迎', + */ + super({ + event: 'notice.group.increase', + priority: 5000 + }) + } + + /** + * 接受到消息都会执行一次 + * @returns + */ + async accept() { + /** 定义入群欢迎内容 */ + let msg = '欢迎新人!' + /** 冷却cd 30s */ + let cd = 30 + if (this.e.user_id == this.e.bot.uin) return + /** cd */ + let key = `Yz:newcomers:${this.e.group_id}` + if (await redis.get(key)) return + redis.set(key, '1', { EX: cd }) + /** 回复 */ + await this.reply([ + segment.at(this.e.user_id), + // segment.image(), + msg + ]) + } +} + diff --git a/apps/outNotice.ts b/apps/outNotice.ts new file mode 100644 index 0000000..f44261a --- /dev/null +++ b/apps/outNotice.ts @@ -0,0 +1,31 @@ +import { plugin } from '#miao/core' +export class outNotice extends plugin { + tips = '退群了' + constructor() { + /** + name: '退群通知', + dsc: 'xx退群了', + */ + super({ + event: 'notice.group.decrease' + }) + } + /** + * + * @returns + */ + async accept() { + if (this.e.user_id == this.e.bot.uin) return + let name = null, msg = null + if (this.e.member) { + name = this.e.member.card || this.e.member.nickname + } + if (name) { + msg = `${name}(${this.e.user_id}) ${this.tips}` + } else { + msg = `${this.e.user_id} ${this.tips}` + } + logger.mark(`[退出通知]${this.e.logText} ${msg}`) + await this.reply(msg) + } +} diff --git a/apps/quit.ts b/apps/quit.ts new file mode 100644 index 0000000..5c15995 --- /dev/null +++ b/apps/quit.ts @@ -0,0 +1,66 @@ +import { ConfigController as cfg } from '#miao/config' +import { plugin } from '#miao/core' +/** + * + */ +export class quit extends plugin { + /** + * + */ + constructor() { + /** + name: 'notice', + dsc: '自动退群', + */ + super({ + event: 'notice.group.increase' + }) + } + /** + * + * @returns + */ + async accept() { + if (this.e.user_id != this.e.bot.uin) return + /** + * + */ + let other = cfg.other + /** + * + */ + if (other.autoQuit <= 0) return + /** + * 判断主人,主人邀请不退群 + */ + let gl = await this.e.group.getMemberMap() + for (let qq of cfg.masterQQ) { + if (gl.has(Number(qq))) { + logger.mark(`[主人拉群] ${this.e.group_id}`) + return + } + } + /** + * 自动退群 + */ + if (Array.from(gl).length <= other.autoQuit && !this.e.group.is_owner) { + /** + * + */ + await this.e.reply('禁止拉群,已自动退出') + /** + * + */ + logger.mark(`[自动退群] ${this.e.group_id}`) + /** + * + */ + setTimeout(() => { + /** + * + */ + this.e.group.quit() + }, 2000) + } + } +} diff --git a/apps/restart.ts b/apps/restart.ts new file mode 100644 index 0000000..aa24a7b --- /dev/null +++ b/apps/restart.ts @@ -0,0 +1,187 @@ +import { plugin } from '#miao/core' +import fetch from 'node-fetch' +import net from 'net' +import fs from 'fs' +import YAML from 'yaml' +import { exec } from 'child_process' + +/** + * + * @param port + * @returns + */ +const isPortTaken = async (port) => { + return new Promise((resolve) => { + const tester = net.createServer() + .once('error', () => resolve(true)) + .once('listening', () => tester.once('close', () => resolve(false)).close()) + .listen(port); + }); +}; + +/** + * + */ +export class Restart extends plugin { + key = 'Yz:restart' + + /** + * + * @param e + */ + constructor() { + /** + name: '重启', + dsc: '#重启', + */ + super({ + event: 'message', + priority: 10, + rule: [{ + reg: '^#重启$', + fnc: 'restart', + permission: 'master' + }, { + reg: '^#(停机|关机)$', + fnc: 'stop', + permission: 'master' + }] + }) + + + } + + async init() { + let restart = await redis.get(this.key) + if (restart) { + restart = JSON.parse(restart) + const uin = restart?.uin || Bot.uin + let time = restart.time || new Date().getTime() + time = (new Date().getTime() - time) / 1000 + + let msg = `重启成功:耗时${time.toFixed(2)}秒` + try { + if (restart.isGroup) { + Bot[uin].pickGroup(restart.id).sendMsg(msg) + } else { + Bot[uin].pickUser(restart.id).sendMsg(msg) + } + } catch (error) { + /** 不发了,发不出去... */ + logger.debug(error) + } + redis.del(this.key) + } + } + + async restart() { + let restart_port + try { + restart_port = YAML.parse(fs.readFileSync(`./config/config/bot.yaml`, `utf-8`)) + restart_port = restart_port.restart_port || 27881 + } catch { } + await this.e.reply('开始执行重启,请稍等...') + logger.mark(`${this.e.logFnc} 开始执行重启,请稍等...`) + + let data = JSON.stringify({ + uin: this.e?.self_id || this.e.bot.uin, + isGroup: !!this.e.isGroup, + id: this.e.isGroup ? this.e.group_id : this.e.user_id, + time: new Date().getTime() + }) + + let npm = await this.checkPnpm() + await redis.set(this.key, data, { EX: 120 }) + if (await isPortTaken(restart_port || 27881)) { + try { + let result = await fetch(`http://localhost:${restart_port || 27881}/restart`) + result = await result.text() + if (result !== `OK`) { + redis.del(this.key) + this.e.reply(`操作失败!`) + logger.error(`重启失败`) + } + } catch (error) { + redis.del(this.key) + this.e.reply(`操作失败!\n${error}`) + } + } else { + try { + let cm = `${npm} start` + if (process.argv[1].includes('pm2')) { + cm = `${npm} run restart` + } + + exec(cm, { windowsHide: true }, (error, stdout, stderr) => { + if (error) { + redis.del(this.key) + this.e.reply(`操作失败!\n${error.stack}`) + logger.error(`重启失败\n${error.stack}`) + } else if (stdout) { + logger.mark('重启成功,运行已由前台转为后台') + logger.mark(`查看日志请用命令:${npm} run log`) + logger.mark(`停止后台运行命令:${npm} stop`) + process.exit() + } + }) + } catch (error) { + redis.del(this.key) + let e = error.stack ?? error + this.e.reply(`操作失败!\n${e}`) + } + } + + return true + } + + async checkPnpm() { + let npm = 'npm' + let ret = await this.execSync('pnpm -v') + if (ret.stdout) npm = 'pnpm' + return npm + } + + async execSync(cmd) { + return new Promise((resolve, reject) => { + exec(cmd, { windowsHide: true }, (error, stdout, stderr) => { + resolve({ error, stdout, stderr }) + }) + }) + } + + async stop() { + let restart_port + try { + restart_port = YAML.parse(fs.readFileSync(`./config/config/bot.yaml`, `utf-8`)) + restart_port = restart_port.restart_port || 27881 + } catch { } + if (await isPortTaken(restart_port || 27881)) { + try { + logger.mark('关机成功,已停止运行') + await this.e.reply(`关机成功,已停止运行`) + await fetch(`http://localhost:${restart_port || 27881}/exit`) + return + } catch (error) { + this.e.reply(`操作失败!\n${error}`) + logger.error(`关机失败\n${error}`) + } + } + + if (!process.argv[1].includes('pm2')) { + logger.mark('关机成功,已停止运行') + await this.e.reply('关机成功,已停止运行') + process.exit() + } + + logger.mark('关机成功,已停止运行') + await this.e.reply('关机成功,已停止运行') + + let npm = await this.checkPnpm() + exec(`${npm} stop`, { windowsHide: true }, (error, stdout, stderr) => { + if (error) { + this.e.reply(`操作失败!\n${error.stack}`) + logger.error(`关机失败\n${error.stack}`) + } + }) + } +} diff --git a/apps/sendLog.ts b/apps/sendLog.ts new file mode 100644 index 0000000..7fd726e --- /dev/null +++ b/apps/sendLog.ts @@ -0,0 +1,91 @@ +import { plugin } from '#miao/core' +import {makeForwardMsg} from '#miao/core' +import fs from "node:fs" +import lodash from "lodash" +import moment from "moment" + +/** + * + */ +export class sendLog extends plugin { + lineNum = 100 + maxNum = 1000 + errFile = "logs/error.log" + logFile = `logs/command.${moment().format("YYYY-MM-DD")}.log` + constructor() { + /** + name: "发送日志", + dsc: "发送最近100条运行日志", + * + */ + super({ + event: "message", + rule: [ + { + reg: "^#(运行|错误)*日志[0-9]*(.*)", + fnc: "sendLog", + permission: "master" + } + ] + }) + } + + /** + * + * @returns + */ + async sendLog() { + let lineNum = this.e.msg.match(/\d+/g) + if (lineNum) { + this.lineNum = lineNum[0] + } else { + this.keyWord = this.e.msg.replace(/#|运行|错误|日志|\d/g, "") + } + + let logFile = this.logFile + let type = "运行" + if (this.e.msg.includes("错误")) { + logFile = this.errFile + type = "错误" + } + + if (this.keyWord) type = this.keyWord + + const log = this.getLog(logFile) + + if (lodash.isEmpty(log)) + return this.reply(`暂无相关日志:${type}`) + + return this.reply(await makeForwardMsg(this.e, [log.join("\n")], `最近${log.length}条${type}日志`)) + } + + /** + * + * @param logFile + * @returns + */ + getLog(logFile) { + let log = fs.readFileSync(logFile, { encoding: "utf-8" }) + log = log.split("\n") + + if (this.keyWord) { + for (const i in log) + if (!log[i].includes(this.keyWord)) + delete log[i] + } else { + log = lodash.slice(log, (Number(this.lineNum) + 1) * -1) + } + log = log.reverse() + + const tmp = [] + for (let i of log) { + if (!i) continue + if (this.keyWord && tmp.length >= this.maxNum) return + /* eslint-disable no-control-regex */ + i = i.replace(/\x1b[[0-9;]*m/g, "") + i = i.replace(/\r|\n/, "") + tmp.push(i) + } + return tmp + } +} diff --git a/apps/status.ts b/apps/status.ts new file mode 100644 index 0000000..78f278c --- /dev/null +++ b/apps/status.ts @@ -0,0 +1,141 @@ + +import { ConfigController as cfg } from '#miao/config' +import moment from 'moment' +import { plugin } from '#miao/core' +/** + * + */ +export class status extends plugin { + /** + name: '其他功能', + dsc: '#状态', + */ + constructor() { + super({ + event: 'message', + rule: [ + { + reg: '^#状态$', + fnc: 'status' + } + ] + }) + } + + /** + * + * @returns + */ + async status() { + if (this.e.isMaster) return this.statusMaster() + + if (!this.e.isGroup) { + this.reply('请群聊查看') + return + } + + return this.statusGroup() + } + + async statusMaster() { + let runTime = moment().diff(moment.unix(this.e.bot.stat.start_time), 'seconds') + let Day = Math.floor(runTime / 3600 / 24) + let Hour = Math.floor((runTime / 3600) % 24) + let Min = Math.floor((runTime / 60) % 60) + if (Day > 0) { + runTime = `${Day}天${Hour}小时${Min}分钟` + } else { + runTime = `${Hour}小时${Min}分钟` + } + + let format = (bytes) => { + return (bytes / 1024 / 1024).toFixed(2) + 'MB' + } + + let msg = '-------状态-------' + msg += `\n运行时间:${runTime}` + msg += `\n内存使用:${format(process.memoryUsage().rss)}` + msg += `\n当前版本:v${cfg.package.version}` + msg += '\n-------累计-------' + msg += await this.getCount() + + await this.reply(msg) + } + + async statusGroup() { + let msg = '-------状态-------' + msg += await this.getCount(this.e.group_id) + + await this.reply(msg) + } + + async getCount(groupId = '') { + this.date = moment().format('MMDD') + this.month = Number(moment().month()) + 1 + + this.key = 'Yz:count:' + + if (groupId) { + this.key += `group:${groupId}:` + } + + this.msgKey = { + day: `${this.key}sendMsg:day:`, + month: `${this.key}sendMsg:month:` + } + + this.screenshotKey = { + day: `${this.key}screenshot:day:`, + month: `${this.key}screenshot:month:` + } + + let week = { + msg: 0, + screenshot: 0 + } + for (let i = 0; i <= 6; i++) { + let date = moment().startOf('week').add(i, 'days').format('MMDD') + + week.msg += Number(await redis.get(`${this.msgKey.day}${date}`)) ?? 0 + week.screenshot += Number(await redis.get(`${this.screenshotKey.day}${date}`)) ?? 0 + } + + let count = { + total: { + msg: await redis.get(`${this.key}sendMsg:total`) || 0, + screenshot: await redis.get(`${this.key}screenshot:total`) || 0 + }, + today: { + msg: await redis.get(`${this.msgKey.day}${this.date}`) || 0, + screenshot: await redis.get(`${this.screenshotKey.day}${this.date}`) || 0 + }, + week, + month: { + msg: await redis.get(`${this.msgKey.month}${this.month}`) || 0, + screenshot: await redis.get(`${this.screenshotKey.month}${this.month}`) || 0 + } + } + + let msg = '' + if (groupId) { + msg = `\n发送消息:${count.today.msg}条` + msg += `\n生成图片:${count.today.screenshot}次` + } else { + msg = `\n发送消息:${count.total.msg}条` + msg += `\n生成图片:${count.total.screenshot}次` + } + + if (count.month.msg > 200) { + msg += '\n-------本周-------' + msg += `\n发送消息:${count.week.msg}条` + msg += `\n生成图片:${count.week.screenshot}次` + } + if (moment().format('D') >= 8 && count.month.msg > 400) { + msg += '\n-------本月-------' + msg += `\n发送消息:${count.month.msg}条` + msg += `\n生成图片:${count.month.screenshot}次` + } + + return msg + } +} diff --git a/apps/update.ts b/apps/update.ts new file mode 100644 index 0000000..11f00f4 --- /dev/null +++ b/apps/update.ts @@ -0,0 +1,266 @@ +import { makeForwardMsg, plugin } from '#miao/core' +import lodash from 'lodash' +import fs from 'node:fs' +import { Restart } from './restart.js' +import {} from '#miao/core' +import { sleep } from '#miao/utils' +import { exec, execSync } from 'child_process' +import { BOT_NAME } from '#miao/config' + +let uping = false + +export class update extends plugin { + typeName = BOT_NAME + messages = [] + + constructor() { + /** + name: '更新', + dsc: '#更新 #强制更新', + */ + super({ + event: 'message', + priority: 4000, + rule: [ + { + reg: '^#更新日志', + fnc: 'updateLog' + }, + { + reg: '^#(强制)?更新', + fnc: 'update' + }, + { + reg: '^#(静默)?全部(强制)?更新$', + fnc: 'updateAll', + permission: 'master' + } + ] + }) + + } + + async update() { + if (!this.e.isMaster) return false + if (uping) return this.reply('已有命令更新中..请勿重复操作') + + if (/详细|详情|面板|面版/.test(this.e.msg)) return false + + /** 获取插件 */ + let plugin = this.getPlugin() + if (plugin === false) return false + + /** 执行更新 */ + if (plugin === '') { + await this.runUpdate('') + await sleep(1000) + plugin = this.getPlugin('miao-plugin') + await this.runUpdate(plugin) + } else { + await this.runUpdate(plugin) + } + + /** 是否需要重启 */ + if (this.isUp) { + // await this.reply('即将执行重启,以应用更新') + setTimeout(() => this.restart(), 2000) + } + } + + getPlugin(plugin = '') { + if (!plugin) { + plugin = this.e.msg.replace(/#(强制)?更新(日志)?/, '') + if (!plugin) return '' + } + + if (!fs.existsSync(`plugins/${plugin}/.git`)) return false + + this.typeName = plugin + return plugin + } + + async execSync(cmd) { + return new Promise((resolve, reject) => { + exec(cmd, { windowsHide: true }, (error, stdout, stderr) => { + resolve({ error, stdout, stderr }) + }) + }) + } + + async runUpdate(plugin = '') { + this.isNowUp = false + + let cm = 'git pull --no-rebase' + + let type = '更新' + if (this.e.msg.includes('强制')) { + type = '强制更新' + cm = `git reset --hard && git pull --rebase --allow-unrelated-histories` + } + if (plugin) cm = `cd "plugins/${plugin}" && ${cm}` + + this.oldCommitId = await this.getcommitId(plugin) + + logger.mark(`${this.e.logFnc} 开始${type}:${this.typeName}`) + + await this.reply(`开始${type} ${this.typeName}`) + uping = true + const ret = await this.execSync(cm) + uping = false + + if (ret.error) { + logger.mark(`${this.e.logFnc} 更新失败:${this.typeName}`) + this.gitErr(ret.error, ret.stdout) + return false + } + + const time = await this.getTime(plugin) + + if (/Already up|已经是最新/g.test(ret.stdout)) { + await this.reply(`${this.typeName} 已是最新\n最后更新时间:${time}`) + } else { + await this.reply(`${this.typeName} 更新成功\n更新时间:${time}`) + this.isUp = true + await this.reply(await this.getLog(plugin)) + } + + logger.mark(`${this.e.logFnc} 最后更新时间:${time}`) + return true + } + + async getcommitId(plugin = '') { + let cm = 'git rev-parse --short HEAD' + if (plugin) cm = `cd "plugins/${plugin}" && ${cm}` + + const commitId = await execSync(cm, { encoding: 'utf-8' }) + return lodash.trim(commitId) + } + + async getTime(plugin = '') { + let cm = 'git log -1 --pretty=%cd --date=format:"%F %T"' + if (plugin) cm = `cd "plugins/${plugin}" && ${cm}` + + let time = '' + try { + time = await execSync(cm, { encoding: 'utf-8' }) + time = lodash.trim(time) + } catch (error) { + logger.error(error.toString()) + time = '获取时间失败' + } + + return time + } + + async gitErr(err, stdout) { + const msg = '更新失败!' + const errMsg = err.toString() + stdout = stdout.toString() + + if (errMsg.includes('Timed out')) { + const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '') + return this.reply(`${msg}\n连接超时:${remote}`) + } + + if (/Failed to connect|unable to access/g.test(errMsg)) { + const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '') + return this.reply(`${msg}\n连接失败:${remote}`) + } + + if (errMsg.includes('be overwritten by merge')) { + return this.reply(`${msg}\n存在冲突:\n${errMsg}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`) + } + + if (stdout.includes('CONFLICT')) { + return this.reply(`${msg}\n存在冲突:\n${errMsg}${stdout}\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改`) + } + + return this.reply([errMsg, stdout]) + } + + async updateAll() { + const dirs = fs.readdirSync('./plugins/') + + const originalReply = this.reply + + const testReg = /^#静默全部(强制)?更新$/.test(this.e.msg) + if (testReg) { + await this.reply(`开始执行静默全部更新,请稍等...`) + this.reply = (message) => { + this.messages.push(message) + } + } + + await this.runUpdate() + + for (let plu of dirs) { + plu = this.getPlugin(plu) + if (plu === false) continue + await sleep(1500) + await this.runUpdate(plu) + } + + if (testReg) { + await this.reply(await makeForwardMsg(this.e, this.messages)) + } + + if (this.isUp) { + // await this.reply('即将执行重启,以应用更新') + setTimeout(() => this.restart(), 2000) + } + + this.reply = originalReply + } + + restart() { + new Restart(this.e).restart() + } + + async getLog(plugin = '') { + let cm = 'git log -100 --pretty="%h||[%cd] %s" --date=format:"%F %T"' + if (plugin) cm = `cd "plugins/${plugin}" && ${cm}` + + let logAll + try { + logAll = await execSync(cm, { encoding: 'utf-8' }) + } catch (error) { + logger.error(error.toString()) + await this.reply(error.toString()) + } + + if (!logAll) return false + + logAll = logAll.trim().split('\n') + + let log = [] + for (let str of logAll) { + str = str.split('||') + if (str[0] == this.oldCommitId) break + if (str[1].includes('Merge branch')) continue + log.push(str[1]) + } + let line = log.length + log = log.join('\n\n') + + if (log.length <= 0) return '' + + let end = '' + try { + cm = 'git config -l' + if (plugin) cm = `cd "plugins/${plugin}" && ${cm}` + end = await execSync(cm, { encoding: 'utf-8' }) + end = end.match(/remote\..*\.url=.+/g).join('\n\n').replace(/remote\..*\.url=/g, '').replace(/\/\/([^@]+)@/, '//') + } catch (error) { + logger.error(error.toString()) + await this.reply(error.toString()) + } + + return makeForwardMsg(this.e, [log, end], `${plugin || 'Miao-Yunzai'} 更新日志,共${line}条`) + } + + async updateLog() { + const plugin = this.getPlugin() + if (plugin === false) return false + return this.reply(await this.getLog(plugin)) + } +} diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..4f11b54 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +# 确保脚本抛出遇到的错误 +set -e + +git init +git add -A +git commit -m 'update: 修改' + +git push -f git@github.com:yoimiya-kokomi/Miao-Yunzai.git master:system \ No newline at end of file diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..4e87a0f --- /dev/null +++ b/index.ts @@ -0,0 +1 @@ +export * as apps from './apps.js' \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..66373eb --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "system-plugin", + "version": "1.0.0-rc.0", + "author": "Yoimiya-Kokomi, Le-niao", + "description": "QQ Group Bot", + "main": "./index.js", + "private":true, + "type": "module", + "scripts": {}, + "dependencies": {}, + "devDependencies": {} +}