13 Star 65 Fork 13

Giftina / ChatDACS

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
index.js 73.92 KB
一键复制 编辑 原始数据 按行查看 历史
Giftina 提交于 2024-03-18 17:19 . fix: qq内置回复说不出话
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931
'use strict'
/**
* Author: Giftina: https://github.com/Giftia/
* 沙雕Ai聊天系统 ChatDACS (Chatbot : shaDiao Ai Chat System),一个简单的机器人框架,支持接入哔哩哔哩直播,具备完全功能的web网页控制台。
*/
/**
* 启动时中文路径检查
*/
const {exec} = require('child_process')
const _cn_reg = new RegExp('[\u4e00-\u9fa5]')
if (_cn_reg.test(process.cwd())) {
const warnMessage = `因为Unicode字符的兼容性问题,本程序所在路径不能存在非ASCII字符。如有疑问,请加QQ群 157311946 咨询。当前路径含有非ASCII字符: ${process.cwd()}`
console.log(warnMessage)
exec(`msg %username% ${warnMessage}`)
}
/**
* 声明依赖与配置
*/
const versionNumber = `v${require('./package.json').version}` // 版本号
const version = `ChatDACS ${versionNumber}` // 系统版本,会显示在web端标题栏
const utils = require('./plugins/system/utils.js') // 载入系统通用模块
const Constants = require('./config/constants.js') // 系统常量
const compression = require('compression') // 用于gzip压缩
const express = require('express') // 轻巧的express框架
const app = require('express')()
app.use(compression()) // 对express所有路由启用gzip
app.use(express.static('static')) // 静态文件引入
app.use(express.json()) // 解析post
app.use(express.urlencoded({extended: false})) // 解析post
const multer = require('multer') // 用于文件上传
const upload = multer({dest: './static/uploads/'}) // 用户上传目录
const cookie = require('cookie')
const http = require('http').Server(app)
const io = require('socket.io')(http)
const request = require('request')
const axios = require('axios').default
const https = require('https')
const colors = require('colors') // Console日志染色颜色配置
colors.setTheme({
alert: 'inverse',
on: 'brightMagenta',
off: 'gray',
warn: 'brightYellow',
error: 'brightRed',
log: 'brightBlue',
})
const fs = require('fs')
const path = require('path')
require.all = require('require.all') // 插件加载器
const {KeepLiveTCP} = require('bilibili-live-ws')
const yaml = require('yaml') // 使用yaml解析配置文件
const voicePlayer = require('play-sound')({
player: path.join(process.cwd(), 'plugins', 'mpg123', 'mpg123.exe'),
}) // mp3静默播放工具,用于直播时播放语音
const ipTranslator = require('lib-qqwry')(true) // lib-qqwry是一个高效纯真IP库(qqwry.dat)引擎,传参 true 是将IP库文件读入内存中以提升效率
const {createOpenAPI, createWebsocket} = require('qq-guild-bot') // QQ频道SDK
const semverDiff = require('semver-diff') // 版本比较
const TelegramBot = require('node-telegram-bot-api') // Telegram机器人SDK
/**
* 中文分词器
*/
const jieba = require('nodejs-jieba')
jieba.load({
dict: path.join(process.cwd(), 'config', 'jieba.dict.utf8'),
hmmDict: path.join(process.cwd(), 'config', 'hmm_model.utf8'),
userDict: path.join(process.cwd(), 'config', 'userDict.txt'), // 加载自定义分词库
idfDict: path.join(process.cwd(), 'config', 'idf.utf8'),
stopWordDict: path.join(process.cwd(), 'config', 'stopWordDict.txt'), // 加载分词库黑名单
})
/**
* 本地日志配置
*/
const winston = require('winston')
const {format, transports} = require('winston')
const {printf} = format
const myFormat = printf(({level, message, timestamp}) => {
return `[${level}] [${timestamp}]: ${message}`
})
winston.addColors(Constants.LOG_LEVELS.colors)
const logger = winston.createLogger({
levels: Constants.LOG_LEVELS.levels,
format: winston.format.combine(
format.timestamp({format: 'YYYY-MM-DD HH:mm:ss.SSS'}),
format.errors({stack: true}),
format.json(),
),
transports: [
new transports.Console({
format: winston.format.combine(winston.format.colorize(), myFormat),
}),
new transports.Http({
level: 'warn',
}),
new winston.transports.File({
filename: 'error.log',
level: 'error',
}),
new winston.transports.File({
filename: 'combined.log',
}),
],
})
logger.info('world.execute(me);'.alert)
/**
* 错误捕获
*/
process.on('uncaughtException', (err) => {
io.emit('system', `@未捕获的异常: ${err}`)
logger.error(err)
})
process.on('unhandledRejection', (err) => {
io.emit('system', `@未捕获的promise异常: ${err}`)
logger.error(err)
})
/**
* 系统配置和开关,以及固定变量
*/
var boomTimer // 60s计时器
var onlineUsers = 0, // 预定义
QQBOT_ADMIN_LIST,
QQ_GROUP_WELCOME_MESSAGE,
QQ_GROUP_POKE_REPLY_MESSAGE,
QQ_GROUP_POKE_BOOM_REPLY_MESSAGE,
BILIBILI_LIVE_ROOM_ID,
CHAT_SWITCH,
CONNECT_GO_CQHTTP_SWITCH,
CONNECT_BILIBILI_LIVE_SWITCH,
WEB_PORT,
GO_CQHTTP_SERVICE_ANTI_POST_API,
GO_CQHTTP_SERVICE_API_URL,
CHAT_JIEBA_LIMIT,
QQBOT_REPLY_PROBABILITY,
QQBOT_FUDU_PROBABILITY,
QQBOT_SAVE_ALL_IMAGE_TO_LOCAL_SWITCH,
QQBOT_MAX_MINE_AT_MOST,
QQBOT_PRIVATE_CHAT_SWITCH,
AUTO_APPROVE_QQ_FRIEND_REQUEST_SWITCH,
c1c_count = 0,
CONNECT_QQ_GUILD_SWITCH,
QQ_GUILD_APP_ID,
QQ_GUILD_TOKEN,
CONNECT_TELEGRAM_SWITCH,
TELEGRAM_BOT_TOKEN
/**
* 声明结束,开始初始化
*/
console.log('_______________________________________\n'.rainbow)
console.log(`\n| ${version} |`.alert)
console.log(' Giftina: https://github.com/Giftia/ \n'.alert)
console.log('_______________________________________\n'.rainbow)
logger.info('开始加载插件……'.log)
const plugins = require.all({
dir: path.join(process.cwd(), 'plugins'),
match: /\.js$/,
require: /\.js$/,
recursive: false,
encoding: 'utf-8',
resolve: function (plugins) {
plugins.all.load()
},
})
let pluginsMap = ['当前安装的插件列表:']
for (const i in plugins) {
pluginsMap.push(plugins[i].插件名)
}
console.log(pluginsMap)
logger.info('插件加载完毕√'.log)
InitConfig()
/**
* 下面是各种功能实现
*/
/**
* web端消息处理,前端使用layim框架
*/
io.on('connection', async (socket) => {
socket.emit('getCookie')
const CID = cookie.parse(socket.request.headers.cookie || '').ChatdacsID
if (CID == undefined) {
socket.emit('getCookie')
return 0
}
// 获取 ip 与 地理位置
const ip = socket.handshake.headers['x-forwarded-for']
? socket.handshake.headers['x-forwarded-for']?.split('::ffff:')[1]
: socket.handshake.address.split('::ffff:')[1] ?? socket.handshake.address
let location = '未知归属地'
try {
location = ipTranslator.searchIP(ip).Country
} catch (error) {
logger.error(`获取地理位置失败: ${error}`)
}
socket.emit('version', version)
io.emit('onlineUsers', ++onlineUsers)
// 开始获取用户信息并处理
const {nickname, loginTimes, updatedAt} = await utils.GetUserData(CID)
if (updatedAt) {
socket.username = `${nickname}[来自${location}]`
logger.info(`web端用户 ${nickname}(${CID}) 已经连接,登录次数 ${loginTimes + 1},上次登录时间 ${updatedAt}`.log)
// 更新登录次数
utils.UpdateLoginTimes(CID)
io.emit(
'system',
`@欢迎回来,${socket.username}(${CID}) 。这是你第${loginTimes + 1}次访问。上次访问时间: ${updatedAt}`,
)
} else {
// 若无法获取该用户信息,则应该是其第一次访问,接下来是新增用户操作:
const CID = cookie.parse(socket.request.headers.cookie || '').ChatdacsID
const randomNickname = await utils.RandomNickname()
socket.username = `${randomNickname}[来自${location}]`
logger.info(`web端用户 ${socket.username}(${CID}) 第一次访问,新增该用户`.log)
// 新增用户
utils.AddUser(CID, randomNickname)
io.emit(
'system',
`@新用户 ${socket.username}(${CID}) 已连接。小夜帮你取了一个随机昵称: ${socket.username},请前往 更多-设置 来更改昵称`,
)
socket.emit('message', {
CID: '0',
msg: Constants.HELP_CONTENT,
})
}
socket.on('disconnect', () => {
onlineUsers--
io.emit('onlineUsers', onlineUsers)
logger.info(`web端用户 ${socket.username}(${CID}) 已经断开连接`.log)
io.emit('system', '@用户 ' + socket.username + ' 已断开连接')
})
socket.on('typing', () => {
io.emit('typing', `${socket.username} 正在输入...`)
})
socket.on('typingOver', () => {
io.emit('typing', '')
})
// 用户设置
socket.on('getSettings', () => {
const CID = cookie.parse(socket.request.headers.cookie || '').ChatdacsID
socket.emit('settings', {CID: CID, name: socket.username})
})
// web端最核心代码,聊天处理
socket.on('message', async (msgIn) => {
const CID = cookie.parse(socket.request.headers.cookie || '').ChatdacsID ?? 0
const msg = msgIn.msg.replace(/['<>]/g, '') // 防爆
logger.info(`web端用户 ${socket.username}(${CID}) 发送了消息: ${msg}`.warn)
// 新消息写入数据库
utils.AddMessage(CID, msg)
io.emit('message', {CID: CID, name: socket.username, msg: msg}) // 用户广播
// web端插件应答器
const pluginsReply =
(await ProcessExecute(msg, CID, socket.username, '1145141919810', '', {
type: 'web',
})) ?? ''
if (pluginsReply) {
const replyToWeb = utils.PluginAnswerToWebStyle(pluginsReply)
const answerMessage = {
CID: '0',
msg: replyToWeb,
}
io.emit('message', answerMessage)
}
if (CHAT_SWITCH) {
// 交给聊天函数处理
const chatReply = await ChatProcess(msg)
if (chatReply) {
io.emit('message', {CID: '0', msg: chatReply})
}
}
})
})
/**
* qq端消息处理,对接go-cqhttp
*/
async function StartQQBot() {
/**
* go-cqhttp 启动后加载当前所有群,写入数据库进行群服务初始化
*/
logger.info('正在进行群服务初始化……'.log)
await utils.InitGroupList()
app.post(GO_CQHTTP_SERVICE_ANTI_POST_API, async (req, res) => {
const event = req.body
// 处理频道消息
if (event.message_type == 'guild') {
logger.info(
`小夜收到频道 ${event.channel_id}${event.user_id} (${event.sender.nickname}) 发来的消息: ${event.message}`,
)
await ProcessGuildMessage(event)
return 0
}
// 被禁言1小时以上自动退群
if (event.sub_type == 'ban' && event.user_id == event.self_id) {
if (event.duration >= 3599) {
await axios.get(`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_leave?group_id=${event.group_id}`)
logger.info(`小夜在群 ${event.group_id} 被禁言超过1小时,自动退群`.error)
io.emit('system', `@小夜在群 ${event.group_id} 被禁言超过1小时,自动退群`)
} else {
// 被禁言改名
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_card?group_id=${event.group_id}&user_id=${
event.self_id
}&card=${encodeURI('你妈的,为什么 禁言我')}`,
)
logger.info(`小夜在群 ${event.group_id} 被禁言,自动改名为 你妈的,为什么 禁言我`.log)
}
return 0
}
// 添加好友请求
if (event.request_type == 'friend') {
logger.info(`小夜收到好友请求,请求人:${event.user_id},请求内容:${event.comment},按配置自动处理`.log)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_friend_add_request?flag=${event.flag}&approve=${AUTO_APPROVE_QQ_FRIEND_REQUEST_SWITCH}}`,
)
return 0
}
// 加群请求发送给管理员
if (event.request_type == 'group' && event.sub_type == 'invite') {
const msg = `用户 ${event.user_id} 邀请小夜加入群 ${event.group_id},批准请发送
批准 ${event.flag}`
logger.info(`小夜收到加群请求,请求人:${event.user_id},请求内容:${event.comment},发送小夜管理员审核`.log)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_private_msg?user_id=${QQBOT_ADMIN_LIST[0]}&message=${encodeURI(msg)}`,
)
// 发送给邀请者批准提醒
const inviteReplyContent = `你好呀,谢谢你邀请小夜,请联系这只小夜的主人 ${QQBOT_ADMIN_LIST[0]} 来批准入群邀请噢。小夜开源于 https://github.com/Giftia/ChatDACS ,开发组欢迎你的加入!`
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_private_msg?user_id=${event.user_id}&message=${encodeURI(
inviteReplyContent,
)}`,
)
return 0
}
// 管理员批准群邀请
if (
event.message_type == 'private' &&
event.user_id == QQBOT_ADMIN_LIST[0] &&
Constants.approve_group_invite_reg.test(event.message)
) {
const flag = event.message.match(Constants.approve_group_invite_reg)[1]
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_add_request?flag=${encodeURI(flag)}&type=invite&approve=1`,
)
logger.info(`管理员批准了群邀请请求 ${flag}`.log)
await sendMessageToQQ('已批准', event)
return 0
}
// ————————————————————下面是功能————————————————————
let notify = ''
switch (event.sub_type) {
case 'friend':
case 'group':
notify = `小夜收到好友 ${event.user_id} (${event.sender.nickname}) 发来的消息: ${event.message}`
break
case 'normal':
notify = `小夜收到群 ${event.group_id}${event.user_id} (${event.sender.nickname}) 发来的消息: ${event.message}`
break
case 'approve':
notify = `${event.user_id} 加入了群 ${event.group_id}`.log
break
case 'ban':
notify = `${event.user_id} 在群 ${event.group_id} 被禁言 ${event.duration} 秒`.error
break
case 'poke':
notify = `${event.user_id} 戳了一下 ${event.target_id}`.log
break
default:
return 0
}
logger.info(notify)
io.emit('system', `@${notify}`)
// 转发图片到web端
if (QQBOT_SAVE_ALL_IMAGE_TO_LOCAL_SWITCH) {
if (Constants.isImage_reg.test(event.message)) {
const url = Constants.img_url_reg.exec(event.message)
utils
.SaveQQimg(url)
.then((resolve) => {
io.emit('qqImage', resolve)
})
.catch((reject) => {
logger.error(`转发图片失败:${reject}`.error)
})
return 0
}
}
// 转发视频到web端
if (Constants.isVideo_reg.test(event.message)) {
const url = Constants.video_url_reg.exec(event.message)[0]
io.emit('qqVideo', {file: url, filename: 'qq视频'})
return 0
}
// 群服务开关判断
const subTypeCondition = ['ban', 'poke', 'friend_add']
if (
event.message_type == 'group' ||
event.notice_type == 'group_increase' ||
subTypeCondition.includes(event.sub_type)
) {
// 服务启用开关
// 指定小夜的话
if (Constants.open_ju_reg.test(event.message) && Constants.has_qq_reg.test(event.message)) {
const who = Constants.has_qq_reg.exec(event.message)[1]
if (Constants.is_qq_reg.test(who)) {
// 如果是自己要被张菊,那么张菊
if (event.self_id == who) {
axios
.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/get_group_member_info?group_id=${event.group_id}&user_id=${event.user_id}`,
)
.then(async (response) => {
if (response.data.data.role === 'owner' || response.data.data.role === 'admin') {
logger.info(`群 ${event.group_id} 启用了小夜服务`.log)
await utils.EnableGroupService(event.group_id)
await sendMessageToQQ(
'小夜的菊花被管理员张开了,这只小夜在本群的所有服务已经启用,要停用请发 闭菊',
event,
)
return 0
}
// 申请人不是管理,再看看是不是qqBot管理员
else {
if (QQBOT_ADMIN_LIST.includes(event.user_id)) {
logger.info(`群 ${event.group_id} 启用了小夜服务`.log)
await utils.EnableGroupService(event.group_id)
await sendMessageToQQ(
'小夜的菊花被主人张开了,这只小夜在本群的所有服务已经启用,要停用请发 闭菊',
event,
)
return 0
}
// 看来真不是管理员呢
await sendMessageToQQ('你不是群管理呢,小夜不张,张菊需要让管理员来帮忙张噢', event)
return 0
}
})
return 0
}
// 不是这只小夜被张菊的话,嘲讽那只小夜
else {
await sendMessageToQQ(`[CQ:at,qq=${who}] 说你呢,快张菊!`, event)
return 0
}
}
}
// 在收到群消息的时候判断群服务开关
else {
const groupServiceSwitch = await utils.GetGroupServiceSwitch(event.group_id)
// 闭嘴了就无视掉所有消息
if (!groupServiceSwitch) {
logger.info(`群 ${event.group_id} 服务已停用,无视群所有消息`.error)
return 0
} else {
// 服务启用了,允许进入后续的指令系统
// 群欢迎
if (event.notice_type === 'group_increase') {
console.log(`${event.user_id} 加入了群 ${event.group_id},小夜欢迎了ta`.log)
const welcomeMessage = QQ_GROUP_WELCOME_MESSAGE.replace(/@新人/g, `[CQ:at,qq=${event.user_id}]`)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
welcomeMessage,
)}`,
)
// 在小夜加入新群后,将新群写入群服务表
await utils.AddNewGroup(event.group_id)
return 0
}
// 地雷爆炸判断,先判断这条消息是否引爆,再从数据库取来群地雷数组,引爆后删除地雷,原先的地雷是用随机数生成被炸前最大回复作为引信,现在换一种思路,用更简单的随机数引爆
const boom = Math.floor(Math.random() * 100) < 10 // 踩中地雷的概率为10%
// 如果判定踩中,检查该群是否有雷
if (boom) {
const mine = await utils.GetGroupMine(event.group_id)
if (mine) {
// 先把地雷排掉
await utils.DeleteGroupMine(mine.id)
// 判断是否哑雷
const isDumb = Math.floor(Math.random() * 100) < 30 // 哑雷的概率为30%
if (isDumb) {
console.log(`${mine.owner} 在群 ${mine.groupId} 埋的地雷被踩中,但这是一颗哑雷`.log)
await sendMessageToQQ(
`[CQ:at,qq=${event.user_id}]恭喜你躲过一劫,[CQ:at,qq=${mine.owner}]埋的地雷掺了沙子,是哑雷,炸了,但没有完全炸`,
event,
)
}
// 判断是否神圣地雷
else {
const isHollyMine = Math.floor(Math.random() * 100) < 1 // 神圣地雷的概率为1%
if (isHollyMine) {
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_whole_ban?group_id=${event.group_id}&enable=1`,
)
console.log(`${mine.owner} 在群 ${mine.groupId} 触发了神圣地雷`.error)
await sendMessageToQQ(
'噢,该死,我的上帝啊,真是不敢相信,瞧瞧我发现了什么,我发誓我没有看错,这竟然是一颗出现率为千分之一的神圣地雷!我是说,这是一颗毁天灭地的神圣地雷啊!哈利路亚!麻烦管理员解除一下',
event,
)
}
// 是常规地雷
else {
const boomTime = Math.floor(Math.random() * 60 * 2) // 造成伤害时间,2分钟内
console.log(`${mine.owner} 在群 ${mine.groupId} 埋的地雷被引爆,伤害时间${boomTime}秒`.log)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${mine.groupId}&user_id=${event.user_id}&duration=${boomTime}`,
)
await sendMessageToQQ(
`[CQ:at,qq=${event.user_id}]恭喜你,被[CQ:at,qq=${mine.owner}]所埋地雷炸伤,休养生息${boomTime}秒!`,
event,
)
}
}
}
return 0 // 踩中地雷后不再处理消息
}
// 服务停用开关
// 指定小夜的话
if (Constants.close_ju_reg.test(event.message) && Constants.has_qq_reg.test(event.message)) {
const who = Constants.has_qq_reg.exec(event.message)[1]
if (Constants.is_qq_reg.test(who)) {
// 如果是自己要被闭菊,那么闭菊
if (event.self_id == who) {
console.log(`群 ${event.group_id} 停止了小夜服务`.error)
await utils.DisableGroupService(event.group_id)
await sendMessageToQQ(
`小夜的菊花闭上了,这只小夜在本群的所有服务已经停用,取消请发 张菊[CQ:at,qq=${event.self_id}]`,
event,
)
// 不是这只小夜被闭菊的话,嘲讽那只小夜(或人
} else {
await sendMessageToQQ(`[CQ:at,qq=${who}] 说你呢,快闭菊!`, event)
}
return 0
}
// 没指定小夜
} else if (event.message === '闭菊') {
console.log(`群 ${event.group_id} 停止了小夜服务`.error)
await utils.DisableGroupService(event.group_id)
await sendMessageToQQ(
`小夜的菊花闭上了,小夜在本群的所有服务已经停用,取消请发 张菊[CQ:at,qq=${event.self_id}]`,
event,
)
return 0
}
// qq端插件应答器
const pluginsReply = await ProcessExecute(
event.message,
event.user_id,
event?.sender?.nickname,
event.group_id,
(
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/get_group_info?group_id=${event.group_id}&no_cache=1`,
)
).data.data.group_name,
{
selfId: event.self_id,
targetId: event.sub_type == 'poke' ? event.target_id : null,
type: 'qq',
},
)
if (pluginsReply != '') {
await sendPluginsReplyToQQ(pluginsReply, event)
}
// 戳一戳
if (event.sub_type === 'poke' && event.target_id == event.self_id) {
poked(event)
return 0
}
// 嘴臭,小夜的回复转化为语音
if (Constants.come_yap_reg.test(event.message)) {
const message = event.message.match(Constants.come_yap_reg)[1]
console.log(`有人对线说 ${message},小夜要嘴臭了`.log)
io.emit('system message', `@有人对线说 ${message},小夜要嘴臭了`)
ChatProcess(message).then((reply) => {
plugins.tts
.execute(`吠 ${reply}`)
.then(async (resolve) => {
const tts_file = `[CQ:record,file=http://127.0.0.1:${WEB_PORT}${resolve.file},url=http://127.0.0.1:${WEB_PORT}${resolve.file}]`
await sendMessageToQQ(tts_file, event)
})
.catch((reject) => {
console.log(`TTS错误: ${reject}`.error)
})
})
return 0
}
// 伪造转发
if (Constants.fake_forward_reg.test(event.message)) {
let who,
name = event.sender.nickname,
text,
xiaoye_say,
requestData
if (event.message == '强制迫害') {
who = event.sender.user_id // 如果没有要求迫害谁,那就是迫害自己
} else {
let msg = event.message + ' ' // 结尾加一个空格防爆
// for (let i in msg.substr(i).split(" ")) {
// console.log(msg[i]);
// }
msg = msg.substr(4).split(' ')
who = msg[1].trim() // 谁
text = msg[2].trim() // 说啥
xiaoye_say = msg[3].trim() // 小夜说啥
who = event.message.match(Constants.fake_forward_reg)[1]
who = who.replace('[CQ:at,qq=', '').replace(']', '').trim()
if (Constants.is_qq_reg.test(who)) {
console.log(`群 ${event.group_id} 的 群员 ${event.user_id} 强制迫害 ${who}`.log)
} else {
// 目标不是qq号
who = event.sender.user_id // 如果没有要求迫害谁,那就是迫害自己
}
}
if (!name) {
name = event.sender.nickname
}
if (!text) {
text = '我是群友专用RBQ'
}
if (!xiaoye_say) {
xiaoye_say =
'[CQ:image,file=1ea870ec3656585d4a81e13648d66db5.image,url=https://gchat.qpic.cn/gchatpic_new/1277161008/2063243247-2238741340-1EA870EC3656585D4A81E13648D66DB5/0?term=3]'
}
// 发送
// 先获取昵称
request(
`http://${GO_CQHTTP_SERVICE_API_URL}/get_group_member_info?group_id=${event.group_id}&user_id=${who}&no_cache=0`,
function (error, _response, body) {
if (!error) {
body = JSON.parse(body)
name = body.data.nickname
requestData = {
group_id: event.group_id,
messages: [
{
type: 'node',
data: {name: name, uin: who, content: text},
},
{
type: 'node',
data: {
name: '星野夜蝶Official',
uin: '1648468212',
content: xiaoye_say,
},
},
],
}
request(
{
url: `http://${GO_CQHTTP_SERVICE_API_URL}/send_group_forward_msg`,
method: 'POST',
json: true,
headers: {
'content-type': 'application/json',
},
body: requestData,
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body)
}
},
)
} else {
requestData = {
group_id: event.group_id,
messages: [
{
type: 'node',
data: {name: name, uin: who, content: text},
},
{
type: 'node',
data: {
name: '星野夜蝶Official',
uin: '1648468212',
content: xiaoye_say,
},
},
],
}
request(
{
url: `http://${GO_CQHTTP_SERVICE_API_URL}/send_group_forward_msg`,
method: 'POST',
json: true,
headers: {
'content-type': 'application/json',
},
body: requestData,
},
function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body)
}
},
)
}
},
)
return 0
}
// 埋地雷
if (Constants.mine_reg.test(event.message)) {
// 搜索地雷库中现有地雷
const mines = await utils.GetGroupAllMines(event.group_id)
// 该群是否已经达到最大共存地雷数
if (mines.length < QQBOT_MAX_MINE_AT_MOST) {
// 地雷还没满,增加群地雷
await utils.AddOneGroupMine(event.group_id, event.user_id)
console.log(`${event.user_id} 在群 ${event.group_id} 埋了一颗地雷`.log)
await sendMessageToQQ(`大伙注意啦![CQ:at,qq=${event.user_id}]埋雷干坏事啦!`, event)
}
// 雷满了,不能埋了
else {
console.log(`群 ${event.group_id} 的地雷满了`.log)
await sendMessageToQQ(
`[CQ:at,qq=${event.user_id}] 这个群的地雷已经塞满啦,等有幸运群友踩中地雷之后再来埋吧`,
event,
)
}
return 0
}
// 踩地雷
if (Constants.fuck_mine_reg.test(event.message)) {
// 搜索地雷库中现有地雷
const mine = await utils.GetGroupMine(event.group_id)
if (mine) {
// 先把地雷排掉
await utils.DeleteGroupMine(mine.id)
// 有雷,直接炸
const boomTime = Math.floor(Math.random() * 60 * 3) + 60 // 造成伤害时间,随机在60-180秒内
console.log(`${mine.owner} 在群 ${mine.groupId} 埋的地雷被排爆`.log)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${mine.groupId}&user_id=${event.user_id}&duration=${boomTime}`,
)
await sendMessageToQQ(
`[CQ:at,qq=${event.user_id}] 踩了一脚地雷,为什么要想不开呢,被[CQ:at,qq=${mine.owner}]所埋地雷炸成重伤,休养生息${boomTime}秒!`,
event,
)
} else {
// 没有雷
await sendMessageToQQ(
`[CQ:at,qq=${event.user_id}] 这个雷区里的雷似乎已经被勇士们排干净了,不如趁现在埋一个吧!`,
event,
)
}
return 0
}
// 希望的花
if (Constants.hope_flower_reg.test(event.message)) {
let who
let boomTime = Math.floor(Math.random() * 30) // 造成0-30伤害时间
if (event.message === '希望的花') {
console.log(`群 ${event.group_id} 的群员 ${event.user_id} 朝自己丢出一朵希望的花`.log)
await sendMessageToQQ('团长,你在做什么啊!团长!希望的花,不要乱丢啊啊啊啊', event)
return 0
} else {
who = Constants.has_qq_reg.exec(event.message)[1]
if (Constants.is_qq_reg.test(who)) {
console.log(`群 ${event.group_id} 的 群员 ${event.user_id}${who} 丢出一朵希望的花`.log)
} else {
// 目标不是qq号
await sendMessageToQQ(`团长,你在做什么啊!团长!希望的花目标不可以是${who},不要乱丢啊啊啊啊`, event)
return 0
}
}
// 先救活目标
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${event.group_id}&user_id=${who}&duration=0`,
)
console.log(`群 ${event.group_id} 的 群员 ${event.user_id} 救活了 ${who}`.log)
await sendMessageToQQ(
`团长,团长你在做什么啊团长,团长!为什么要救他啊,哼,呃,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!!!团长救下了[CQ:at,qq=${who}],但自己被炸飞了,休养生息${boomTime}秒!不要停下来啊!`,
event,
)
// 再禁言团长
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${event.group_id}&user_id=${event.user_id}&duration=${boomTime}`,
)
console.log(`${event.user_id} 团长自己被炸伤${boomTime}秒`.log)
return 0
}
// 击鼓传雷
if (Constants.loop_bomb_reg.test(event.message)) {
// 先检查群有没有开始游戏
const loopBombGame = await utils.GetGroupLoopBombGameStatus(event.group_id)
// 判断游戏开关,没有开始的话就开始游戏,如果游戏已经超时结束了的话就重新开始
if (!loopBombGame.loopBombEnabled || 60 - process.hrtime([loopBombGame.loopBombStartTime, 0])[0] < 0) {
// 游戏开始
const text = '击鼓传雷游戏开始啦,这是一个只有死亡才能结束的游戏,做好准备了吗'
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
text,
)}`,
)
console.log(`群 ${event.group_id} 开始了击鼓传雷`.log)
// 给发起人出题,等待ta回答
const wenDa = await ECYWenDa()
const question = `那么[CQ:at,qq=${event.user_id}]请听题:${wenDa.question} 请按如下格式告诉小夜:击鼓传雷 你的答案,时间剩余59秒`
// 把答案、持有人、开始时间存入数据库
await utils.StartGroupLoopBombGame(event.group_id, wenDa.answer, event.user_id, process.hrtime()[0])
// 金手指
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_card?group_id=${event.group_id}&user_id=${
event.user_id
}&card=${encodeURI(wenDa.answer)}`,
)
// 丢出问题
setTimeout(async () => {
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
question,
)}`,
)
}, 1000)
// 开始倒计时,倒计时结束宣布游戏结束
boomTimer = setTimeout(async () => {
console.log(`群 ${event.group_id} 的击鼓传雷到达时间,炸了`.log)
const boomTime = Math.floor(Math.random() * 60 * 3) + 60 // 造成伤害时间,随机在60-180秒内
// 获取这个雷现在是谁手上,炸ta
const {bombHolder, bombAnswer} = await utils.GetGroupLoopBomb(event.group_id)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${event.group_id}&user_id=${bombHolder}&duration=${boomTime}`,
)
console.log(`${bombHolder} 在群 ${event.group_id} 回答超时,被炸伤${boomTime}秒`.log)
// 金手指关闭
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_card?group_id=${event.group_id}&user_id=${bombHolder}&card=`,
)
const gameOverContent = `时间到了,pia,雷在[CQ:at,qq=${bombHolder}]手上炸了,你被炸成重伤了,休养生息${boomTime}秒!游戏结束!下次加油噢,那么答案公布:${bombAnswer}`
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
gameOverContent,
)}`,
)
// 游戏结束,清空数据
await utils.EndGroupLoopBombGame(event.group_id)
return 0
}, 1000 * 60)
}
// 已经开始游戏了,判断答案对不对
else {
let playerAnswer = event.message
playerAnswer = playerAnswer.replace('击鼓传雷 ', '')
playerAnswer = playerAnswer.replace('击鼓传雷', '')
playerAnswer = playerAnswer.trim()
// 从数据库里取答案判断
const {bombHolder, bombAnswer} = await utils.GetGroupLoopBomb(event.group_id)
// 判断答案 loop_bomb_answer
if (bombAnswer == playerAnswer) {
let reply = ''
// 答对了
if (bombHolder != event.user_id) {
// 不是本人回答,是来抢答的,无论对错都惩罚
console.log(`抢答了,${event.user_id} 被禁言`.log)
reply = `[CQ:at,qq=${event.user_id}] 抢答正确!答案确实是 ${bombAnswer} !但因为抢答了别人的题目所以被惩罚了!`
// 金手指关闭
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_card?group_id=${event.group_id}&user_id=${bombHolder}&card=`,
)
// 禁言这个游戏周期
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${event.group_id}&user_id=${event.user_id}&duration=60`,
)
}
// 回答正确
else {
console.log(`${bombHolder} 回答正确`.log)
reply = `[CQ:at,qq=${event.user_id}] 回答正确!答案确实是 ${bombAnswer} !`
// 金手指关闭
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_card?group_id=${event.group_id}&user_id=${bombHolder}&card=`,
)
}
// 答题成功,返回消息
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
reply,
)}`,
)
// 把雷传给随机幸运群友,进入下一题
setTimeout(async () => {
// 随机选一位幸运群友
const randomMember = await axios
.get(`http://${GO_CQHTTP_SERVICE_API_URL}/get_group_member_list?group_id=${event.group_id}`)
.then(async (response) => {
const members = response.data.data
const randomMember = members[Math.floor(Math.random() * members.length)].user_id
console.log(`随机选取一个群友 ${randomMember} 给他下一题`.log)
return randomMember
})
// 开始下一轮游戏,,给幸运群友出题,等待ta回答
console.log(`群 ${event.group_id} 开始了下一轮击鼓传雷`.log)
//获取剩余时间
const {bombStartTime} = await utils.GetGroupLoopBomb(event.group_id)
const diff = 60 - process.hrtime([bombStartTime, 0])[0] // 剩余时间
const wenDa = await ECYWenDa()
const question = `抽到了幸运群友[CQ:at,qq=${randomMember}]!请听题:${wenDa.question} 请按如下格式告诉小夜:击鼓传雷 你的答案,时间还剩余${diff}秒`
// 把答案、持有人存入数据库
await utils.UpdateGroupLoopBombGame(event.group_id, wenDa.answer, randomMember)
// 金手指
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_card?group_id=${event.group_id}&user_id=${
event.user_id
}&card=${encodeURI(wenDa.answer)}`,
)
// 丢出问题
setTimeout(async () => {
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${
event.group_id
}&message=${encodeURI(question)}`,
)
}, 1000)
return 0
}, 500)
}
// 答错了,游戏结束
else {
const boomTime = Math.floor(Math.random() * 60 * 3) + 60 // 造成伤害时间
const endGameContent = `[CQ:at,qq=${event.user_id}] 回答错误,好可惜,你被炸成重伤了,休养生息${boomTime}秒!游戏结束!下次加油噢,那么答案公布:${bombAnswer}`
console.log(`${event.user_id} 回答错误,被炸伤${boomTime}秒`.log)
// 禁言
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${event.group_id}&user_id=${event.user_id}&duration=${boomTime}`,
)
clearTimeout(boomTimer)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
endGameContent,
)}`,
)
// 游戏结束,删掉游戏记录
await utils.EndGroupLoopBombGame(event.group_id)
// 金手指关闭
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_card?group_id=${event.group_id}&user_id=${bombHolder}&card=`,
)
return 0
}
}
}
// 孤寡
if (Constants.gu_gua_reg.test(event.message)) {
if (event.message == '孤寡') {
await sendMessageToQQ('小夜收到了你的孤寡订单,现在就开始孤寡你了噢孤寡~', event)
utils.GuGua(event.user_id)
return 0
}
const who = Constants.has_qq_reg.exec(event.message)[1]
console.log(`孤寡对象:${who}`.log)
if (Constants.is_qq_reg.test(who)) {
await axios.get(`http://${GO_CQHTTP_SERVICE_API_URL}/get_friend_list`).then(async (response) => {
if (response.length != 0) {
// 判断 who 是否在 response.data.data 数组里
const userExist = response.data.data.some((item) => {
return item.user_id == who
})
if (userExist) {
console.log(`群 ${event.group_id} 的 群员 ${event.user_id} 孤寡了 ${who}`.log)
await sendMessageToQQ(`小夜收到了你的孤寡订单,现在就开始孤寡[CQ:at,qq=${who}]了噢孤寡~`, event)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_private_msg?user_id=${who}&message=${encodeURI(
`您好,我是孤寡小夜,您的好友 ${event.user_id} 给您点了一份孤寡套餐,请查收`,
)}`,
)
utils.GuGua(who)
return 0
}
// 没有加好友,不能私聊孤寡
else {
await sendMessageToQQ(
`小夜没有加[CQ:at,qq=${who}]为好友,没有办法孤寡ta呢,请先让ta加小夜为好友吧,为了补偿,小夜就在群里孤寡大家吧`,
event,
)
utils.QunGuGua(event.group_id)
return 0
}
}
})
} else {
// 目标不是qq号
console.log('孤寡对象目标不是qq号')
await sendMessageToQQ(`你想孤寡谁啊,目标不可以是${who},不要乱孤寡,小心孤寡你一辈子啊`, event)
}
return 0
}
// 手动复读,复读回复中指定的消息
if (Constants.reply_reg.test(event.message)) {
// 从 [CQ:reply,id=-1982767585][CQ:at,qq=1005056803] 复读 消息里获取id
const msgID = event.message.split('id=')[1].split(']')[0].trim()
logger.info(`收到手动复读指令,消息id: ${msgID}`.log)
const historyMessage = (await axios.get(`http://${GO_CQHTTP_SERVICE_API_URL}/get_msg?message_id=${msgID}`))
.data.data.message
logger.info(`复读历史消息: ${historyMessage}`.log)
await sendMessageToQQ(historyMessage, event)
return 0
}
// 管理员功能: 修改聊天回复率
if (Constants.change_reply_probability_reg.test(event.message)) {
if (QQBOT_ADMIN_LIST.includes(event.user_id)) {
const replyPercentage = event.message.match(Constants.change_reply_probability_reg)[1]
QQBOT_REPLY_PROBABILITY = replyPercentage
await sendMessageToQQ(`小夜回复率已修改为${replyPercentage}%`, event)
return 0
}
await sendMessageToQQ('你不是狗管理噢,不能让小夜这样那样的', event)
return 0
}
// 管理员功能: 修改聊天随机复读率
if (Constants.change_fudu_probability_reg.test(event.message)) {
if (QQBOT_ADMIN_LIST.includes(event.user_id)) {
const fuduPercentage = event.message.match(Constants.change_fudu_probability_reg)[1]
QQBOT_FUDU_PROBABILITY = fuduPercentage
await sendMessageToQQ(`小夜复读率已修改为${fuduPercentage}%`, event)
return 0
}
await sendMessageToQQ('你不是狗管理噢,不能让小夜这样那样的', event)
return 0
}
// 是否触发复读
const couldRepeat = Math.floor(Math.random() * 100) < QQBOT_FUDU_PROBABILITY
if (couldRepeat) {
console.log(`小夜复读 ${event.message}`.log)
await sendMessageToQQ(event.message, event)
return 0
}
// 是否触发回复
let replyFlag = Math.floor(Math.random() * 100)
// 如果被@了,那么回复几率上升80%
let atReplacedMsg = event.message // 要把[CQ:at,qq=${event.self_id}] 去除掉,否则聊天核心会乱成一锅粥
if (new RegExp(`\\[CQ:at,qq=${event.self_id}\\]`).test(event.message)) {
replyFlag -= 80
atReplacedMsg = event.message.replace(`[CQ:at,qq=${event.self_id}]`, '').trim() // 去除@小夜
}
// 根据权重回复
if (replyFlag < QQBOT_REPLY_PROBABILITY) {
let replyMsg = await ChatProcess(atReplacedMsg)
if (replyMsg.indexOf('[name]') || replyMsg.indexOf('&#91;name&#93;')) {
replyMsg = replyMsg.toString().replace('[name]', `[CQ:at,qq=${event.user_id}]`) // 替换[name]为正确的@
replyMsg = replyMsg.toString().replace('&#91;name&#93;', `[CQ:at,qq=${event.user_id}]`) // 替换[name]为正确的@
}
console.log(`对于QQ聊天 ${atReplacedMsg} ,小夜回复 ${replyMsg}`.log)
await sendMessageToQQ(replyMsg, event)
return 0
}
}
}
} else if (event.message_type == 'private' && QQBOT_PRIVATE_CHAT_SWITCH == true) {
// 私聊回复
ChatProcess(event.message).then(async (resolve) => {
logger.info(`小夜回复 ${resolve}`.log)
io.emit('system', `@小夜回复: ${resolve}`)
await sendMessageToQQ(resolve, event)
})
return 0
} else {
return 0
}
})
// 每隔24小时搜索qqGroup表,随机延时提醒停用服务的群启用服务
setInterval(async () => await utils.DelayAlert(), 1000 * 60 * 60 * 24)
async function poked(event) {
logger.info('小夜被戳了'.log)
c1c_count++
if (c1c_count > 2) {
logger.info(`小夜被戳坏了,${event.user_id} 被禁言10s`.error)
c1c_count = 0
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
QQ_GROUP_POKE_BOOM_REPLY_MESSAGE,
)}`,
)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/set_group_ban?group_id=${event.group_id}&user_id=${event.user_id}&duration=10`,
)
} else {
// 被戳的回复
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(
QQ_GROUP_POKE_REPLY_MESSAGE,
)}`,
)
}
}
async function sendMessageToQQ(message, event) {
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(message)}`,
)
}
async function sendPluginsReplyToQQ(pluginsReply, event) {
const replyToQQ = utils.PluginAnswerToGoCqhttpStyle(pluginsReply)
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_group_msg?group_id=${event.group_id}&message=${encodeURI(replyToQQ)}`,
)
}
}
/**
* qq内嵌频道的消息处理,并非独立的qq频道
*/
async function ProcessGuildMessage(event) {
const content = event.message
// qq内嵌频道插件应答器
const pluginsReply = await ProcessExecute(content, event.user_id, event?.sender?.nickname, event.channel_id, '', {
type: 'qqInsideGuild',
})
let replyToGuild = ''
if (pluginsReply) {
replyToGuild = utils.PluginAnswerToGoCqhttpStyle(pluginsReply)
} else {
// 交给聊天函数处理
const replyFlag = Math.floor(Math.random() * 100)
if (replyFlag < QQBOT_REPLY_PROBABILITY) {
const chatReply = await ChatProcess(content)
if (chatReply) {
console.log(`对于QQ频道聊天 ${content} ,小夜回复 ${chatReply}`.log)
replyToGuild = chatReply
}
}
}
if (replyToGuild) {
await axios.get(
`http://${GO_CQHTTP_SERVICE_API_URL}/send_guild_channel_msg?guild_id=${event.guild_id}&channel_id=${
event.channel_id
}&message=${encodeURI(replyToGuild)}`,
)
}
}
/**
* b站直播端消息处理,虚拟主播星野夜蝶上线!
*/
async function StartLive() {
const live = new KeepLiveTCP(BILIBILI_LIVE_ROOM_ID)
live.on('open', () => logger.info(`哔哩哔哩直播间 ${BILIBILI_LIVE_ROOM_ID} 连接成功`.log))
live.on('live', () => {
live.on('heartbeat', (online) => logger.info(`直播间在线人数: ${online}`.log))
live.on('DANMU_MSG', async (data) => {
const danmu = {
content: data.info[1],
userId: data.info[2][0],
userName: data.info[2][1],
}
console.log(`${danmu.userName} 说: ${danmu.content}`.log)
// 哔哩哔哩端插件应答器
const pluginsReply =
(await ProcessExecute(danmu.content, danmu.userId, danmu.userName, '', '', {
type: 'bilibili',
})) ?? ''
let replyToBiliBili = ''
if (pluginsReply) {
// 插件响应弹幕
replyToBiliBili = pluginsReply
} else {
// 交给聊天函数处理
const chatReply = await ChatProcess(danmu.content)
if (chatReply) {
replyToBiliBili = chatReply
}
}
fs.writeFileSync(Constants.TTS_FILE_RECV_PATH, `@${danmu.userName} ${replyToBiliBili}`)
const chatReplyToTTS = await plugins.tts.execute(`吠 ${replyToBiliBili}`)
// 如果语音合成成功的话,直接播放
if (chatReplyToTTS.content.file) {
const ttsFile = `${process.cwd()}/static${chatReplyToTTS.content.file}`
voicePlayer.play(ttsFile, function (err) {
if (err) {
console.log('播放失败:', err)
}
})
}
})
live.on('SEND_GIFT', (data) => {
const gift = data.data
console.log(`${gift.uname}送了 ${gift.num}${gift.giftName}`.log)
})
live.on('WELCOME', (data) => {
const welcome = data.data
console.log(`${welcome.uname} 进入直播间`.log)
})
live.on('WELCOME_GUARD', (data) => {
const welcome = data.data
console.log(`${welcome.uname} 进入直播间`.log)
})
})
}
/**
* qq频道消息处理,需要注册独立的qq频道bot号
*/
async function StartQQGuild() {
const testConfig = {
appID: QQ_GUILD_APP_ID,
token: QQ_GUILD_TOKEN,
intents: ['GUILD_MESSAGES'], // 事件订阅,用于开启可接收的消息类型
sandbox: true, // 沙箱频道
}
const qqGuildClient = createOpenAPI(testConfig)
const qqGuildWS = createWebsocket(testConfig)
// 消息监听
qqGuildWS.on('READY', (data) => {
console.log('[READY] 事件接收 :', data)
})
qqGuildWS.on('ERROR', (data) => {
console.log('[ERROR] 事件接收 :', data)
})
qqGuildWS.on('GUILDS', (data) => {
console.log('[GUILDS] 事件接收 :', data)
})
qqGuildWS.on('GUILD_MEMBERS', (data) => {
console.log('[GUILD_MEMBERS] 事件接收 :', data)
})
qqGuildWS.on('GUILD_MESSAGE_REACTIONS', (data) => {
console.log('[GUILD_MESSAGE_REACTIONS] 事件接收 :', data)
})
qqGuildWS.on('DIRECT_MESSAGE', (data) => {
console.log('[DIRECT_MESSAGE] 事件接收 :', data)
})
qqGuildWS.on('INTERACTION', (data) => {
console.log('[INTERACTION] 事件接收 :', data)
})
qqGuildWS.on('MESSAGE_AUDIT', (data) => {
console.log('[MESSAGE_AUDIT] 事件接收 :', data)
})
qqGuildWS.on('FORUMS_EVENT', (data) => {
console.log('[FORUMS_EVENT] 事件接收 :', data)
})
qqGuildWS.on('AUDIO_ACTION', (data) => {
console.log('[AUDIO_ACTION] 事件接收 :', data)
})
qqGuildWS.on('GUILD_MESSAGES', async (data) => {
console.log('[GUILD_MESSAGES] 事件接收 :', data)
// 需要把指令前 <@!1234567890 > 和 [sandbox] 移除
const content = data.msg.content?.replace(/<@!\d+> /g, '').replace(/\[sandbox\]/g, '')
// QQ频道端插件应答器
const pluginsReply = await ProcessExecute(
content,
data.msg.author.id,
data.msg.author.username,
data.msg.channel_id,
'',
{
type: 'qqGuild',
},
)
const channelID = data.msg.channel_id
const replyMsgID = data.msg.id
if (pluginsReply) {
const replyToQQGuild = utils.PluginAnswerToQQGuildStyle(pluginsReply)
if (replyToQQGuild?.audio) {
const message = {
audio_url: replyToQQGuild.audio,
msg_id: replyMsgID,
text: replyToQQGuild.text,
state: Constants.AUDIO_START,
}
qqGuildClient.audioApi
.postAudio(channelID, message)
.then((res) => {
console.log('[GUILD_MESSAGES] 语音应答成功 :', res)
})
.catch((err) => {
console.log('[GUILD_MESSAGES] 语音应答失败 :', err)
})
} else {
const message = {
content: replyToQQGuild?.text ?? '',
msg_id: replyMsgID,
image: replyToQQGuild?.image ?? '',
}
qqGuildClient.messageApi
.postMessage(channelID, message)
.then((res) => {
console.log('[GUILD_MESSAGES] 插件应答成功 :', res.data)
})
.catch((err) => {
console.log('[GUILD_MESSAGES] 插件应答失败 :', err)
})
}
} else {
// 交给聊天函数处理
const replyFlag = Math.floor(Math.random() * 100)
if (replyFlag < QQBOT_REPLY_PROBABILITY) {
const chatReply = await ChatProcess(content)
if (chatReply) {
console.log(`对于QQ频道Bot端聊天 ${content} ,小夜回复 ${chatReply}`.log)
const message = {
content: chatReply,
msg_id: replyMsgID,
}
qqGuildClient.messageApi
.postMessage(channelID, message)
.then((res) => {
console.log('[GUILD_MESSAGES] 聊天应答成功 :', res.data)
})
.catch((err) => {
console.log('[GUILD_MESSAGES] 聊天应答失败 :', err)
})
}
}
}
})
}
/**
* Telegram端消息处理
*/
async function StartTelegram() {
const telegramClient = new TelegramBot(TELEGRAM_BOT_TOKEN, {polling: true})
telegramClient.on('message', async (data) => {
const chatId = data.chat.id
const content = data.text
const userName = data.from.username
logger.info(`[Telegram] 收到用户 ${userName} 的消息: ${content}`)
// Telegram插件应答器
const pluginsReply = await ProcessExecute(content, data.from.id, userName, chatId, '', {
type: 'telegram',
})
if (pluginsReply) {
const replyToTelegram = utils.PluginAnswerToTelegramStyle(pluginsReply)
if (pluginsReply.type == 'text') {
telegramClient.sendMessage(chatId, replyToTelegram.text)
} else if (pluginsReply.type == 'picture' || pluginsReply.type == 'directPicture') {
telegramClient.sendPhoto(
chatId,
replyToTelegram.image,
{},
{
contentType: 'image/jpeg',
},
)
} else if (pluginsReply.type == 'audio') {
telegramClient.sendAudio(
chatId,
replyToTelegram.audio,
{
title: replyToTelegram.text,
duration: replyToTelegram.duration,
},
{
contentType: 'audio/mpeg',
},
)
}
} else {
// 交给聊天函数处理
const replyFlag = Math.floor(Math.random() * 100)
if (replyFlag < QQBOT_REPLY_PROBABILITY) {
const chatReply = await ChatProcess(content)
if (chatReply) {
console.log(`对于Telegram端聊天 ${content} ,小夜回复 ${chatReply}`.log)
telegramClient.sendMessage(chatId, chatReply)
}
}
}
})
}
/**
* 更改web端个人资料接口
*/
app.get('/profile', async (req, res) => {
await utils.UpdateNickname(req.query.CID, req.query.name)
res.sendFile(process.cwd() + Constants.HTML_PATH)
})
/**
* web端图片上传接口
*/
app.post('/upload/image', upload.single('file'), function (req) {
logger.info('用户上传图片'.log)
logger.info(req.file)
const oldname = req.file.path
const newname = req.file.path + path.parse(req.file.originalname).ext
fs.renameSync(oldname, newname)
io.emit('picture', {
type: 'picture',
content: `/uploads/${req.file.filename}${path.parse(req.file.originalname).ext}`,
})
})
/**
* web端文件/视频上传接口
*/
app.post('/upload/file', upload.single('file'), function (req) {
logger.info('用户上传文件'.log)
logger.info(req.file)
const oldname = req.file.path
const newname = req.file.path + path.parse(req.file.originalname).ext
fs.renameSync(oldname, newname)
const isVideo = new RegExp('^video*')
const isAudio = new RegExp('^audio*')
const file = {
file: `/uploads/${req.file.filename}${path.parse(req.file.originalname).ext}`,
filename: req.file.originalname,
}
if (isVideo.test(req.file.mimetype)) {
io.emit('video', {type: 'video', content: file})
} else if (isAudio.test(req.file.mimetype)) {
io.emit('audio', {type: 'audio', content: file})
} else {
io.emit('file', {type: 'file', content: file})
}
})
/**
* 读取配置文件 config.yml
*/
function ReadConfig() {
return new Promise((resolve, reject) => {
logger.info('开始加载配置……'.log)
fs.readFile(path.join(process.cwd(), 'config', 'config.yml'), 'utf-8', function (err, data) {
if (!err) {
logger.info('配置加载完毕√'.log)
resolve(yaml.parse(data))
} else {
reject('读取配置文件错误,尝试以默认配置启动。错误原因: ' + err)
}
})
})
}
/**
* 初始化配置
*/
async function InitConfig() {
const config = await ReadConfig()
CHAT_SWITCH = config.System.CHAT_SWITCH ?? true
CONNECT_GO_CQHTTP_SWITCH = config.System.CONNECT_GO_CQHTTP_SWITCH ?? false
CONNECT_BILIBILI_LIVE_SWITCH = config.System.CONNECT_BILIBILI_LIVE_SWITCH ?? false
CONNECT_QQ_GUILD_SWITCH = config.System.CONNECT_QQ_GUILD_SWITCH ?? false
CONNECT_TELEGRAM_SWITCH = config.System.CONNECT_TELEGRAM_SWITCH ?? false
WEB_PORT = config.System.WEB_PORT ?? 80
GO_CQHTTP_SERVICE_ANTI_POST_API = config.System.GO_CQHTTP_SERVICE_ANTI_POST_API ?? '/bot'
GO_CQHTTP_SERVICE_API_URL = config.System.GO_CQHTTP_SERVICE_API_URL ?? '127.0.0.1:5700'
QQ_GUILD_APP_ID = config.ApiKey.QQ_GUILD_APP_ID ?? ''
QQ_GUILD_TOKEN = config.ApiKey.QQ_GUILD_TOKEN ?? ''
TELEGRAM_BOT_TOKEN = config.ApiKey.TELEGRAM_BOT_TOKEN ?? ''
QQBOT_ADMIN_LIST = config.qqBot.QQBOT_ADMIN_LIST // 小夜的管理员列表
QQ_GROUP_WELCOME_MESSAGE = config.qqBot.QQ_GROUP_WELCOME_MESSAGE // qq入群欢迎语
QQ_GROUP_POKE_REPLY_MESSAGE = config.qqBot.QQ_GROUP_POKE_REPLY_MESSAGE // 戳一戳的文案
QQ_GROUP_POKE_BOOM_REPLY_MESSAGE = config.qqBot.QQ_GROUP_POKE_BOOM_REPLY_MESSAGE // 戳坏了的文案
AUTO_APPROVE_QQ_FRIEND_REQUEST_SWITCH = config.qqBot.AUTO_APPROVE_QQ_FRIEND_REQUEST_SWITCH // 自动批准好友请求开关
QQBOT_PRIVATE_CHAT_SWITCH = config.qqBot.QQBOT_PRIVATE_CHAT_SWITCH // 私聊开关
CHAT_JIEBA_LIMIT = config.qqBot.CHAT_JIEBA_LIMIT // qqBot限制分词数量
QQBOT_REPLY_PROBABILITY = config.qqBot.QQBOT_REPLY_PROBABILITY // 回复几率
QQBOT_FUDU_PROBABILITY = config.qqBot.QQBOT_FUDU_PROBABILITY // 复读几率
QQBOT_SAVE_ALL_IMAGE_TO_LOCAL_SWITCH = config.qqBot.QQBOT_SAVE_ALL_IMAGE_TO_LOCAL_SWITCH // 保存接收图片开关
QQBOT_MAX_MINE_AT_MOST = config.qqBot.QQBOT_MAX_MINE_AT_MOST // 最大共存地雷数
BILIBILI_LIVE_ROOM_ID = config.Others.BILIBILI_LIVE_ROOM_ID ?? 49148 // 哔哩哔哩直播间id
if (CHAT_SWITCH) {
logger.info('小夜web端自动聊天开启'.on)
} else {
logger.info('小夜web端自动聊天关闭'.off)
}
if (CONNECT_GO_CQHTTP_SWITCH) {
/**
* 在 Windows、Linux 系统下自动启动go-cqhttp
*/
const autoStartGoCqhttpSystemCondition = ['win32', 'linux']
const goCqhttpFile = {win32: 'go-cqhttp.bat', linux: './go-cqhttp -faststart'}
if (autoStartGoCqhttpSystemCondition.includes(process.platform)) {
const goCqhttp = exec(
goCqhttpFile[process.platform],
{
cwd: path.join(process.cwd(), 'plugins', 'go-cqhttp'),
windowsHide: true,
},
(error) => {
if (error) {
logger.error(`go-cqhttp启动失败,错误原因: ${error}`.error)
return
}
logger.error(
'go-cqhttp窗口意外退出,qq小夜将无法正常使用,请在右下角托盘区右键小夜头像,选择 重启go-cqhttp'.error,
)
return
},
)
/**
* 在 Linux 系统下直接输出 go-cqhttp 的打印信息
*/
if (process.platform === 'linux') {
goCqhttp.stdout.on('data', function (data) {
console.log(data.toString())
})
}
}
logger.info(
`小夜QQ接入开启,配置: \n ·对接go-cqhttp接口 ${GO_CQHTTP_SERVICE_API_URL}\n ·监听反向post于 127.0.0.1:${WEB_PORT}${GO_CQHTTP_SERVICE_ANTI_POST_API}\n ·私聊服务${
QQBOT_PRIVATE_CHAT_SWITCH ? '开启' : '关闭'
}`.on,
)
await StartQQBot()
} else {
logger.info('小夜QQ接入关闭'.off)
}
if (CONNECT_BILIBILI_LIVE_SWITCH) {
logger.info(`小夜哔哩哔哩直播接入开启,直播间id为 ${BILIBILI_LIVE_ROOM_ID}`.on)
StartLive()
} else {
logger.info('小夜哔哩哔哩直播接入关闭'.off)
}
if (CONNECT_QQ_GUILD_SWITCH) {
logger.info('小夜QQ频道接入开启'.on)
StartQQGuild()
} else {
logger.info('小夜QQ频道接入关闭'.off)
}
if (CONNECT_TELEGRAM_SWITCH) {
logger.info('小夜Telegram接入开启'.on)
StartTelegram()
} else {
logger.info('小夜Telegram接入关闭'.off)
}
StartHttpServer()
CheckUpdate()
RunMigration()
}
/**
* HTTP服务器启动
*/
function StartHttpServer() {
http.listen(WEB_PORT, () => {
logger.info(`HTTP服务启动完毕,访问 127.0.0.1:${WEB_PORT} 即可进入本地web端√`.log)
})
}
http.on('error', (err) => {
http.close()
logger.error(
`本机${WEB_PORT}端口被其他应用程序占用,请尝试关闭占用${WEB_PORT}端口的其他程序 或 修改配置文件的 WEB_PORT 配置项。错误代码:${err.code}`
.error,
)
setTimeout(() => StartHttpServer(), 10000)
})
const UnauthorizedHttpsAgent = new https.Agent({rejectUnauthorized: false}) // #303,Watt Toolkit(Steam++)的自签证书问题
/**
* 检查本体更新
*/
function CheckUpdate() {
axios
.get('https://api.github.com/repos/Giftia/ChatDACS/releases/latest', {UnauthorizedHttpsAgent})
.then((res) => {
if (semverDiff(versionNumber, res.data.tag_name) !== undefined) {
logger.info(
`当前小夜版本 ${versionNumber},检测到小夜最新发行版本是 ${res.data.tag_name},请前往 https://github.com/Giftia/ChatDACS/releases 更新小夜吧
${res.data.tag_name}更新日志:
${res.data.body}`.alert,
)
} else {
logger.info(`当前小夜已经是最新发行版本 ${versionNumber}`.log)
}
})
.catch((err) => {
logger.error(`检查小夜更新失败,错误原因: ${err},可能是网络原因`.error)
})
}
/**
* 数据库migration
*/
function RunMigration() {
const migration = exec('npm run migrate')
logger.info('正在检查数据库迁移'.log)
let migrationLog = ''
migration.stdout.on('data', (data) => {
migrationLog += data
})
migration.on('close', (code) => {
if (code === 0) {
if (migrationLog.includes('database schema was already up to date')) {
logger.info('数据库迁移检查完毕,无需迁移√'.log)
} else {
logger.info('数据库迁移检查完毕,迁移完毕√'.log)
}
} else {
logger.error(`数据库迁移失败,错误原因: ${migrationLog}`.error)
}
})
}
/**
* 异步结巴 thanks@ssp97
* @param {Promise<string>} text
*/
async function ChatJiebaFuzzy(msg) {
msg = msg.replace('/', '')
msg = jieba.extract(msg, CHAT_JIEBA_LIMIT) // 按权重分词
if (msg.length === 0) {
return []
}
let candidate = []
let candidateNextList = []
let candidateNextGrand = 0
// 收集数据开始
for (const key in msg) {
if (Object.hasOwnProperty.call(msg, key)) {
const element = msg[key]
const rows = await utils.FuzzyContentSearchAnswer(element.word)
for (const k in rows) {
if (Object.hasOwnProperty.call(rows, k)) {
const answer = rows[k].answer
if (candidate[answer] == undefined) {
candidate[answer] = 1
} else {
candidate[answer] = candidate[answer] + 1
}
}
}
}
}
// 筛选次数最多
for (const key in candidate) {
if (Object.hasOwnProperty.call(candidate, key)) {
const element = candidate[key]
if (element > candidateNextGrand) {
candidateNextList = []
candidateNextGrand = element
candidateNextList.push(key)
} else if (element == candidateNextGrand) {
candidateNextList.push(key)
}
}
}
return candidateNextList
}
/**
* 响应聊天回复,超智能(障)的聊天算法: 全匹配搜索 => 模糊搜索 => 分词模糊搜索 => 敷衍
* @param {string} ask 关键词
* @returns {Promise<string>} 小夜回复
*/
async function ChatProcess(ask) {
//如果ask异常,可能是非聊天事件触发了响应聊天回复,直接敷衍回复
if (!ask) {
console.log('ask异常,直接敷衍回复'.log)
const randomBalaBala = await utils.PerfunctoryAnswer()
console.log(`返回随机敷衍:${randomBalaBala}`.alert)
return randomBalaBala
}
console.log('开始全匹配搜索'.log)
const fullContentSearchAnswer = await utils.FullContentSearchAnswer(ask)
// 优先回复全匹配匹配
if (fullContentSearchAnswer) {
console.log(`返回全匹配匹配:${fullContentSearchAnswer}`.alert)
return fullContentSearchAnswer
}
console.log('没有匹配到全匹配回复,开始模糊搜索'.log)
const fuzzyContentSearchAnswer = await utils.FuzzyContentSearchAnswer(ask)
// 其次是模糊匹配
if (fuzzyContentSearchAnswer) {
console.log(`返回模糊匹配:${fuzzyContentSearchAnswer}`.alert)
return fuzzyContentSearchAnswer
}
// 最后是分词模糊搜索
console.log('没有匹配到模糊回复,开始分词模糊搜索'.log)
const jiebaCandidateList = await ChatJiebaFuzzy(ask)
if (jiebaCandidateList.length > 0 && !jiebaCandidateList[0]) {
const candidateListAnswer = jiebaCandidateList[Math.floor(Math.random() * jiebaCandidateList.length)]
console.log(`返回分词模糊匹配:${candidateListAnswer}`.alert)
return candidateListAnswer
}
// 如果什么回复都没有匹配到,那么随机敷衍
console.log('没有匹配到分词模糊搜索,敷衍一下吧'.log)
const randomBalaBala = await utils.PerfunctoryAnswer()
console.log(`返回随机敷衍:${randomBalaBala}`.alert)
return randomBalaBala
}
/**
* 浓度极高的ACGN圈台词问答题库
* @returns {Promise<object>} { question, answer }
*/
async function ECYWenDa() {
const data = (await axios.get('https://api.oddfar.com/yl/q.php?c=2001&encode=json')).data
const keyWord = jieba.extract(data.text, CHAT_JIEBA_LIMIT) // 分词出关键词
if (keyWord.length == 0) {
// 如果分词不了,那就直接夜爹牛逼
return {
question: '啊噢,出不出题了,你直接回答 夜爹牛逼 吧',
answer: '夜爹牛逼',
}
}
const randomAnswer = keyWord[Math.floor(Math.random() * keyWord.length)].word
console.log(`原句为: ${data.text},随机切去关键词 ${randomAnswer} 作为答案`.log)
// 将答案切除作为问题
const question = data.text.replace(randomAnswer, '______')
return {question: question, answer: randomAnswer}
}
/**
* 插件系统核心
* @param {string} msg 传入消息
* @param {string} userId 消息发送者id
* @param {string} userName 消息发送者昵称
* @param {string} groupId 消息所属群id
* @param {string} groupName 消息所属群昵称
* @param {string} options 其他参数
* @returns {Promise<string>} 插件回复
*/
async function ProcessExecute(msg, userId, userName, groupId, groupName, options) {
if (!msg || !userId || !userName || !groupId || !groupName) {
throw new Error('调用插件时缺少入参')
}
let pluginReturn = ''
// 插件开关
try {
if (Constants.plugins_switch_reg.test(msg)) {
const pluginName = msg.match(Constants.plugins_switch_reg)[1]
if (!pluginName) return '插件名获取有误'
for (const i in plugins) {
if (plugins[i].插件名 == pluginName) {
const pluginStatus = await utils.ToggleGroupPlugin(groupId, pluginName)
console.log(`群${groupId} 的插件 ${pluginName} 状态切换为 ${pluginStatus}`.log)
return {type: 'text', content: `${pluginName}${pluginStatus ? '开启' : '关闭'}`}
}
}
} else {
for (const i in plugins) {
const reg = new RegExp(plugins[i].指令)
if (reg.test(msg)) {
const pluginStatus = await utils.GetGroupPluginStatus(groupId, plugins[i].插件名)
if (!pluginStatus) {
console.log(`群${groupId} 的插件 ${plugins[i].插件名} 已关闭,不响应`.log)
return {type: 'text', content: `群内的 ${plugins[i].插件名} 已关闭,不响应`}
}
try {
pluginReturn = await plugins[i].execute(msg, userId, userName, groupId, groupName, options)
} catch (e) {
logger.error(`插件 ${plugins[i].插件名} ${plugins[i].版本} 爆炸啦: ${e.stack}`.error)
return `插件 ${plugins[i].插件名} ${plugins[i].版本} 爆炸啦: ${e.stack}`
}
if (pluginReturn) {
logger.info(`插件 ${plugins[i].插件名} ${plugins[i].版本} 响应了消息:`.log)
logger.info(JSON.stringify(pluginReturn).log)
return pluginReturn
}
}
}
}
} catch (e) {
logger.error(`Error in ProcessExecute: ${e.stack}`.error)
return `插件爆炸啦:${e.stack}`
}
return pluginReturn
}
/**
* 我正在听:🎧 ALIVE - ClariS
*/
JavaScript
1
https://gitee.com/Giftina/ChatDACS.git
git@gitee.com:Giftina/ChatDACS.git
Giftina
ChatDACS
ChatDACS
master

搜索帮助