update: 修改
This commit is contained in:
commit
bc9cf29004
|
@ -0,0 +1,12 @@
|
|||
# System-Plugin
|
||||
|
||||
Miao-Yunzai V4 插件开发示例
|
||||
|
||||
## 使用教程
|
||||
|
||||
- 安装源码
|
||||
|
||||
```sh
|
||||
git clone --depth=1 -b dev https://github.com/yoimiya-kokomi/Miao-Yunzai.git
|
||||
```
|
||||
|
|
@ -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'
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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": {}
|
||||
}
|
Loading…
Reference in New Issue