-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathindex.js
1931 lines (1733 loc) · 73.9 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'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}秒`