-
Notifications
You must be signed in to change notification settings - Fork 42
/
message_parser.go
1560 lines (1432 loc) · 51.3 KB
/
message_parser.go
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
package handlers
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
"time"
"github.com/hoshinonyaruko/gensokyo/acnode"
"github.com/hoshinonyaruko/gensokyo/botstats"
"github.com/hoshinonyaruko/gensokyo/callapi"
"github.com/hoshinonyaruko/gensokyo/config"
"github.com/hoshinonyaruko/gensokyo/echo"
"github.com/hoshinonyaruko/gensokyo/idmap"
"github.com/hoshinonyaruko/gensokyo/images"
"github.com/hoshinonyaruko/gensokyo/mylog"
"github.com/hoshinonyaruko/gensokyo/structs"
"github.com/hoshinonyaruko/gensokyo/url"
"github.com/skip2/go-qrcode"
"github.com/tencent-connect/botgo/dto"
"github.com/tencent-connect/botgo/dto/keyboard"
"github.com/tencent-connect/botgo/openapi"
"github.com/tidwall/gjson"
"mvdan.cc/xurls" //xurls是一个从文本提取url的库 适用于多种场景
)
var BotID string
var AppID string
// 定义响应结构体
type ServerResponse struct {
Data struct {
MessageID int `json:"message_id"`
} `json:"data"`
Message string `json:"message"`
GroupID int64 `json:"group_id,omitempty"`
UserID int64 `json:"user_id,omitempty"`
ChannelID int64 `json:"channel_id,omitempty"`
GuildID string `json:"guild_id,omitempty"`
RetCode int `json:"retcode"`
Status string `json:"status"`
Echo interface{} `json:"echo"`
}
// 定义了一个符合 Client 接口的 HttpAPIClient 结构体
type HttpAPIClient struct {
// 可添加所需字段
}
// 实现 Client 接口的 SendMessage 方法
// 假client中不执行任何操作,只是返回 nil 来符合接口要求
func (c *HttpAPIClient) SendMessage(message map[string]interface{}) error {
// 不实际发送消息
// log.Printf("SendMessage called with: %v", message)
// 返回nil占位符
return nil
}
// 发送成功回执 todo 返回可互转的messageid 实现群撤回api
func SendResponse(client callapi.Client, err error, message *callapi.ActionMessage, resp *dto.GroupMessageResponse, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (string, error) {
var messageID64 int64
var mapErr error
// 转换群号
var errr error
var GroupID64 int64
if groupID, ok := message.Params.GroupID.(string); ok && groupID != "" {
if config.GetIdmapPro() {
//将真实id转为int userid64
GroupID64, _, errr = idmap.StoreIDv2Pro(message.Params.GroupID.(string), message.Params.UserID.(string))
if errr != nil {
mylog.Fatalf("Error storing ID: %v", err)
}
} else {
// 映射str的GroupID到int
GroupID64, errr = idmap.StoreIDv2(message.Params.GroupID.(string))
if errr != nil {
mylog.Errorf("failed to convert GroupID64 to int: %v", err)
}
}
}
var channelID64 int64
if channelID, ok := message.Params.ChannelID.(string); ok && channelID != "" {
if config.GetIdmapPro() {
//将真实id转为int userid64
channelID64, _, errr = idmap.StoreIDv2Pro(message.Params.ChannelID.(string), message.Params.UserID.(string))
if errr != nil {
mylog.Fatalf("Error storing ID: %v", err)
}
} else {
// 映射str的GroupID到int
channelID64, errr = idmap.StoreIDv2(message.Params.ChannelID.(string))
if errr != nil {
mylog.Errorf("failed to convert GroupID64 to int: %v", err)
}
}
}
var guildID64 int64
if guildID, ok := message.Params.GuildID.(string); ok && guildID != "" {
if config.GetIdmapPro() {
//将真实id转为int userid64
guildID64, _, errr = idmap.StoreIDv2Pro(message.Params.GuildID.(string), message.Params.UserID.(string))
if errr != nil {
mylog.Fatalf("Error storing ID: %v", err)
}
} else {
// 映射str的GroupID到int
guildID64, errr = idmap.StoreIDv2(message.Params.GuildID.(string))
if errr != nil {
mylog.Errorf("failed to convert GroupID64 to int: %v", err)
}
}
}
var userID64 int64
if userID, ok := message.Params.UserID.(string); ok && userID != "" {
if config.GetIdmapPro() {
//将真实id转为int userid64
userID64, _, errr = idmap.StoreIDv2Pro("group_private", message.Params.UserID.(string))
if errr != nil {
mylog.Fatalf("Error storing ID: %v", err)
}
} else {
// 映射str的GroupID到int
userID64, errr = idmap.StoreIDv2(message.Params.UserID.(string))
if errr != nil {
mylog.Errorf("failed to convert GroupID64 to int: %v", err)
}
}
}
// 设置响应值
response := ServerResponse{}
if resp != nil {
messageID64, mapErr = idmap.StoreIDv2(resp.Message.ID)
if mapErr != nil {
mylog.Printf("Error storing ID: %v", mapErr)
return "", nil
}
response.Data.MessageID = int(messageID64)
// 发送成功 增加今日发信息数
botstats.RecordMessageSent()
// 是否自动撤回
if echoStr, ok := message.Echo.(string); ok {
msg_on_touch := echo.GetMsgIDv3(config.GetAppIDStr(), echoStr)
// 检查是否需要自动撤回
autoWithdrawPrefixes := config.GetAutoWithdraw()
if len(autoWithdrawPrefixes) > 0 {
for _, prefix := range autoWithdrawPrefixes {
if strings.HasPrefix(msg_on_touch, prefix) {
go func() {
delay := config.GetAutoWithdrawTime() // 获取延迟时间
time.Sleep(time.Duration(delay) * time.Second)
// 构建参数
var params callapi.ParamsContent
if groupID, ok := message.Params.GroupID.(string); ok && groupID != "" {
params.GroupID = strconv.FormatInt(GroupID64, 10)
} else if channelID, ok := message.Params.ChannelID.(string); ok && channelID != "" {
params.ChannelID = strconv.FormatInt(channelID64, 10)
} else if guildID, ok := message.Params.GuildID.(string); ok && guildID != "" {
params.GuildID = strconv.FormatInt(guildID64, 10)
} else if userID, ok := message.Params.UserID.(string); ok && userID != "" {
params.UserID = strconv.FormatInt(userID64, 10)
} else {
return // 如果没有有效的参数,则退出
}
params.MessageID = strconv.FormatInt(messageID64, 10)
// 创建撤回消息的请求
deleteMessage := callapi.ActionMessage{
Action: "delete_msg",
Params: params,
}
// 调用删除消息函数
client := &HttpAPIClient{}
_, err := DeleteMsg(client, api, apiv2, deleteMessage)
if err != nil {
mylog.Printf("Error DeleteMsg: %v", err)
}
}()
break
}
}
}
}
} else {
// Default ID handling
response.Data.MessageID = 123
}
//mylog.Printf("convert GroupID64 to int: %v", GroupID64) 测试
// TODO: 改为动态参数 不是固定GroupID 但应用端不支持.会报错.暂时统一从group取id,自己判断类型发撤回请求.
response.GroupID = GroupID64
response.Echo = message.Echo
if err != nil {
response.Message = err.Error() // 可选:在响应中添加错误消息
//response.RetCode = -1 // 可以是任何非零值,表示出错
//response.Status = "failed"
response.RetCode = 0 //官方api审核异步的 审核中默认返回失败,但其实信息发送成功了
response.Status = "ok"
} else {
response.Message = ""
response.RetCode = 0
response.Status = "ok"
}
// 转化为map并发送
outputMap := structToMap(response)
// 将map转换为JSON字符串
jsonResponse, jsonErr := json.Marshal(outputMap)
if jsonErr != nil {
log.Printf("Error marshaling response to JSON: %v", jsonErr)
return "", jsonErr
}
//发送给ws 客户端
sendErr := client.SendMessage(outputMap)
if sendErr != nil {
mylog.Printf("Error sending message via client: %v", sendErr)
return "", sendErr
}
mylog.Printf("发送成功回执: %+v", string(jsonResponse))
return string(jsonResponse), nil
}
// 发送成功回执 todo 返回可互转的messageid 实现频道撤回api
func SendGuildResponse(client callapi.Client, err error, message *callapi.ActionMessage, resp *dto.Message) (string, error) {
var messageID64 int64
var mapErr error
// 设置响应值
response := ServerResponse{}
if resp != nil {
messageID64, mapErr = idmap.StoreIDv2(resp.ID)
if mapErr != nil {
mylog.Printf("Error storing ID: %v", mapErr)
return "", nil
}
response.Data.MessageID = int(messageID64)
// 发送成功 增加今日发信息数
botstats.RecordMessageSent()
} else {
// Default ID handling
response.Data.MessageID = 123
}
//转换成int
ChannelID64, errr := idmap.StoreIDv2(message.Params.ChannelID.(string))
if errr != nil {
mylog.Printf("Error storing ID: %v", err)
return "", nil
}
response.ChannelID = ChannelID64
response.Echo = message.Echo
if err != nil {
response.Message = err.Error() // 可选:在响应中添加错误消息
//response.RetCode = -1 // 可以是任何非零值,表示出错
//response.Status = "failed"
response.RetCode = 0 //官方api审核异步的 审核中默认返回失败,但其实信息发送成功了
response.Status = "ok"
} else {
response.Message = ""
response.RetCode = 0
response.Status = "ok"
}
// 转化为map并发送
outputMap := structToMap(response)
// 将map转换为JSON字符串
jsonResponse, jsonErr := json.Marshal(outputMap)
if jsonErr != nil {
log.Printf("Error marshaling response to JSON: %v", jsonErr)
return "", jsonErr
}
//发送给ws 客户端
sendErr := client.SendMessage(outputMap)
if sendErr != nil {
mylog.Printf("Error sending message via client: %v", sendErr)
return "", sendErr
}
mylog.Printf("发送成功回执: %+v", string(jsonResponse))
return string(jsonResponse), nil
}
// 发送成功回执 todo 返回可互转的messageid 实现C2C撤回api
func SendC2CResponse(client callapi.Client, err error, message *callapi.ActionMessage, resp *dto.C2CMessageResponse) (string, error) {
var messageID64 int64
var mapErr error
// 设置响应值
response := ServerResponse{}
if resp != nil {
messageID64, mapErr = idmap.StoreIDv2(resp.Message.ID)
if mapErr != nil {
mylog.Printf("Error storing ID: %v", mapErr)
return "", nil
}
response.Data.MessageID = int(messageID64)
// 发送成功 增加今日发信息数
botstats.RecordMessageSent()
} else {
// Default ID handling
response.Data.MessageID = 123
}
//将真实id转为int userid64
userid64, errr := idmap.StoreIDv2(message.Params.UserID.(string))
if errr != nil {
mylog.Fatalf("Error storing ID: %v", err)
}
response.UserID = userid64
response.Echo = message.Echo
if err != nil {
response.Message = err.Error() // 可选:在响应中添加错误消息
//response.RetCode = -1 // 可以是任何非零值,表示出错
//response.Status = "failed"
response.RetCode = 0 //官方api审核异步的 审核中默认返回失败,但其实信息发送成功了
response.Status = "ok"
} else {
response.Message = ""
response.RetCode = 0
response.Status = "ok"
}
// 转化为map并发送
outputMap := structToMap(response)
// 将map转换为JSON字符串
jsonResponse, jsonErr := json.Marshal(outputMap)
if jsonErr != nil {
log.Printf("Error marshaling response to JSON: %v", jsonErr)
return "", jsonErr
}
//发送给ws 客户端
sendErr := client.SendMessage(outputMap)
if sendErr != nil {
mylog.Printf("Error sending message via client: %v", sendErr)
return "", sendErr
}
mylog.Printf("发送成功回执: %+v", string(jsonResponse))
return string(jsonResponse), nil
}
// 会返回guildid的频道私信专用SendGuildPrivateResponse
func SendGuildPrivateResponse(client callapi.Client, err error, message *callapi.ActionMessage, resp *dto.Message, guildID string) (string, error) {
var messageID64 int64
var mapErr error
// 设置响应值
response := ServerResponse{}
if resp != nil {
messageID64, mapErr = idmap.StoreIDv2(resp.ID)
if mapErr != nil {
mylog.Printf("Error storing ID: %v", mapErr)
return "", nil
}
response.Data.MessageID = int(messageID64)
} else {
// Default ID handling
response.Data.MessageID = 123
}
response.Echo = message.Echo
response.GuildID = guildID
if err != nil {
response.Message = err.Error() // 可选:在响应中添加错误消息
//response.RetCode = -1 // 可以是任何非零值,表示出错
//response.Status = "failed"
response.RetCode = 0 //官方api审核异步的 审核中默认返回失败,但其实信息发送成功了
response.Status = "ok"
} else {
response.Message = ""
response.RetCode = 0
response.Status = "ok"
}
// 转化为map并发送
outputMap := structToMap(response)
// 将map转换为JSON字符串
jsonResponse, jsonErr := json.Marshal(outputMap)
if jsonErr != nil {
log.Printf("Error marshaling response to JSON: %v", jsonErr)
return "", jsonErr
}
//发送给ws 客户端
sendErr := client.SendMessage(outputMap)
if sendErr != nil {
mylog.Printf("Error sending message via client: %v", sendErr)
return "", sendErr
}
mylog.Printf("发送成功回执: %+v", string(jsonResponse))
return string(jsonResponse), nil
}
// 信息处理函数
func parseMessageContent(paramsMessage callapi.ParamsContent, message callapi.ActionMessage, client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI) (string, map[string][]string) {
messageText := ""
switch message := paramsMessage.Message.(type) {
case string:
mylog.Printf("params.message is a string\n")
messageText = message
// 直接应用替换规则
if config.GetEnableChangeWord() {
messageText = acnode.CheckWordOUT(messageText)
}
case []interface{}:
//多个映射组成的切片
mylog.Printf("params.message is a slice (segment_type_koishi)\n")
for _, segment := range message {
segmentMap, ok := segment.(map[string]interface{})
if !ok {
continue
}
segmentType, ok := segmentMap["type"].(string)
if !ok {
continue
}
segmentContent := ""
switch segmentType {
case "text":
segmentContent, _ = segmentMap["data"].(map[string]interface{})["text"].(string)
// 应用替换规则
if config.GetEnableChangeWord() {
segmentContent = acnode.CheckWordOUT(segmentContent)
}
case "image":
fileContent, _ := segmentMap["data"].(map[string]interface{})["file"].(string)
segmentContent = "[CQ:image,file=" + fileContent + "]"
case "voice":
fileContent, _ := segmentMap["data"].(map[string]interface{})["file"].(string)
segmentContent = "[CQ:record,file=" + fileContent + "]"
case "record":
fileContent, _ := segmentMap["data"].(map[string]interface{})["file"].(string)
segmentContent = "[CQ:record,file=" + fileContent + "]"
case "at":
qqNumber, _ := segmentMap["data"].(map[string]interface{})["qq"].(string)
segmentContent = "[CQ:at,qq=" + qqNumber + "]"
case "markdown":
mdContent, ok := segmentMap["data"].(map[string]interface{})["data"]
if ok {
if mdContentMap, isMap := mdContent.(map[string]interface{}); isMap {
// mdContent是map[string]interface{},按map处理
mdContentBytes, err := json.Marshal(mdContentMap)
if err != nil {
mylog.Printf("Error marshaling mdContentMap to JSON:%v", err)
}
encoded := base64.StdEncoding.EncodeToString(mdContentBytes)
segmentContent = "[CQ:markdown,data=" + encoded + "]"
} else if mdContentStr, isString := mdContent.(string); isString {
// mdContent是string
if strings.HasPrefix(mdContentStr, "base64://") {
// 如果以base64://开头,直接使用
segmentContent = "[CQ:markdown,data=" + mdContentStr + "]"
} else {
// 处理实体化后的JSON文本
mdContentStr = strings.ReplaceAll(mdContentStr, "&", "&")
mdContentStr = strings.ReplaceAll(mdContentStr, "[", "[")
mdContentStr = strings.ReplaceAll(mdContentStr, "]", "]")
mdContentStr = strings.ReplaceAll(mdContentStr, ",", ",")
// 将处理过的字符串视为JSON对象,进行序列化和编码
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(mdContentStr), &jsonMap); err != nil {
mylog.Printf("Error unmarshaling string to JSON:%v", err)
}
mdContentBytes, err := json.Marshal(jsonMap)
if err != nil {
mylog.Printf("Error marshaling jsonMap to JSON:%v", err)
}
encoded := base64.StdEncoding.EncodeToString(mdContentBytes)
segmentContent = "[CQ:markdown,data=" + encoded + "]"
}
}
} else {
mylog.Printf("Error marshaling markdown segment to interface,contain type but data is nil.")
}
}
messageText += segmentContent
}
case map[string]interface{}:
//单个映射
mylog.Printf("params.message is a map (segment_type_trss)\n")
messageType, _ := message["type"].(string)
switch messageType {
case "text":
messageText, _ = message["data"].(map[string]interface{})["text"].(string)
// 应用替换规则
if config.GetEnableChangeWord() {
messageText = acnode.CheckWordOUT(messageText)
}
case "image":
fileContent, _ := message["data"].(map[string]interface{})["file"].(string)
messageText = "[CQ:image,file=" + fileContent + "]"
case "voice":
fileContent, _ := message["data"].(map[string]interface{})["file"].(string)
messageText = "[CQ:record,file=" + fileContent + "]"
case "record":
fileContent, _ := message["data"].(map[string]interface{})["file"].(string)
messageText = "[CQ:record,file=" + fileContent + "]"
case "at":
qqNumber, _ := message["data"].(map[string]interface{})["qq"].(string)
messageText = "[CQ:at,qq=" + qqNumber + "]"
case "markdown":
mdContent, ok := message["data"].(map[string]interface{})["data"]
if ok {
if mdContentMap, isMap := mdContent.(map[string]interface{}); isMap {
// mdContent是map[string]interface{},按map处理
mdContentBytes, err := json.Marshal(mdContentMap)
if err != nil {
mylog.Printf("Error marshaling mdContentMap to JSON:%v", err)
}
encoded := base64.StdEncoding.EncodeToString(mdContentBytes)
messageText = "[CQ:markdown,data=" + encoded + "]"
} else if mdContentStr, isString := mdContent.(string); isString {
// mdContent是string
if strings.HasPrefix(mdContentStr, "base64://") {
// 如果以base64://开头,直接使用
messageText = "[CQ:markdown,data=" + mdContentStr + "]"
} else {
// 处理实体化后的JSON文本
mdContentStr = strings.ReplaceAll(mdContentStr, "&", "&")
mdContentStr = strings.ReplaceAll(mdContentStr, "[", "[")
mdContentStr = strings.ReplaceAll(mdContentStr, "]", "]")
mdContentStr = strings.ReplaceAll(mdContentStr, ",", ",")
// 将处理过的字符串视为JSON对象,进行序列化和编码
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(mdContentStr), &jsonMap); err != nil {
mylog.Printf("Error unmarshaling string to JSON:%v", err)
}
mdContentBytes, err := json.Marshal(jsonMap)
if err != nil {
mylog.Printf("Error marshaling jsonMap to JSON:%v", err)
}
encoded := base64.StdEncoding.EncodeToString(mdContentBytes)
messageText = "[CQ:markdown,data=" + encoded + "]"
}
}
} else {
mylog.Printf("Error marshaling markdown segment to interface,contain type but data is nil.")
}
}
default:
mylog.Println("Unsupported message format: params.message field is not a string, map or slice")
}
//处理at
messageText = transformMessageTextAt(messageText)
//mylog.Printf(messageText)
// 正则表达式部分
var localImagePattern *regexp.Regexp
var localRecordPattern *regexp.Regexp
if runtime.GOOS == "windows" {
localImagePattern = regexp.MustCompile(`\[CQ:image,file=file:///([^\]]+?)\]`)
} else {
localImagePattern = regexp.MustCompile(`\[CQ:image,file=file://([^\]]+?)\]`)
}
if runtime.GOOS == "windows" {
localRecordPattern = regexp.MustCompile(`\[CQ:record,file=file:///([^\]]+?)\]`)
} else {
localRecordPattern = regexp.MustCompile(`\[CQ:record,file=file://([^\]]+?)\]`)
}
httpUrlImagePattern := regexp.MustCompile(`\[CQ:image,file=http://(.+?)\]`)
httpsUrlImagePattern := regexp.MustCompile(`\[CQ:image,file=https://(.+?)\]`)
base64ImagePattern := regexp.MustCompile(`\[CQ:image,file=base64://(.+?)\]`)
base64RecordPattern := regexp.MustCompile(`\[CQ:record,file=base64://(.+?)\]`)
httpUrlRecordPattern := regexp.MustCompile(`\[CQ:record,file=http://(.+?)\]`)
httpsUrlRecordPattern := regexp.MustCompile(`\[CQ:record,file=https://(.+?)\]`)
httpUrlVideoPattern := regexp.MustCompile(`\[CQ:video,file=http://(.+?)\]`)
httpsUrlVideoPattern := regexp.MustCompile(`\[CQ:video,file=https://(.+?)\]`)
mdPattern := regexp.MustCompile(`\[CQ:markdown,data=base64://(.+?)\]`)
qqMusicPattern := regexp.MustCompile(`\[CQ:music,type=qq,id=(\d+)\]`)
patterns := []struct {
key string
pattern *regexp.Regexp
}{
{"local_image", localImagePattern},
{"url_image", httpUrlImagePattern},
{"url_images", httpsUrlImagePattern},
{"base64_image", base64ImagePattern},
{"base64_record", base64RecordPattern},
{"local_record", localRecordPattern},
{"url_record", httpUrlRecordPattern},
{"url_records", httpsUrlRecordPattern},
{"markdown", mdPattern},
{"qqmusic", qqMusicPattern},
{"url_video", httpUrlVideoPattern},
{"url_videos", httpsUrlVideoPattern},
}
foundItems := make(map[string][]string)
for _, pattern := range patterns {
matches := pattern.pattern.FindAllStringSubmatch(messageText, -1)
for _, match := range matches {
if len(match) > 1 {
foundItems[pattern.key] = append(foundItems[pattern.key], match[1])
}
}
// 移动替换操作到这里,确保所有匹配都被处理后再进行替换
messageText = pattern.pattern.ReplaceAllString(messageText, "")
}
//最后再处理Url
messageText = transformMessageTextUrl(messageText, message, client, api, apiv2)
// for key, items := range foundItems {
// fmt.Printf("Key: %s, Items: %v\n", key, items)
// }
return messageText, foundItems
}
func isIPAddress(address string) bool {
return net.ParseIP(address) != nil
}
// at处理
func transformMessageTextAt(messageText string) string {
// 首先,将AppID替换为BotID
messageText = strings.ReplaceAll(messageText, AppID, BotID)
// 去除所有[CQ:reply,id=数字] todo 更好的处理办法
replyRE := regexp.MustCompile(`\[CQ:reply,id=\d+\]`)
messageText = replyRE.ReplaceAllString(messageText, "")
// 使用正则表达式来查找所有[CQ:at,qq=数字]的模式
re := regexp.MustCompile(`\[CQ:at,qq=(\d+)\]`)
messageText = re.ReplaceAllStringFunc(messageText, func(m string) string {
submatches := re.FindStringSubmatch(m)
if len(submatches) > 1 {
realUserID, err := idmap.RetrieveRowByIDv2(submatches[1])
if err != nil {
// 如果出错,也替换成相应的格式,但使用原始QQ号
mylog.Printf("Error retrieving user ID: %v", err)
return "<@!" + submatches[1] + ">"
}
// 在这里检查 GetRemoveBotAtGroup 和 realUserID 的长度
if config.GetRemoveBotAtGroup() && len(realUserID) == 32 {
return ""
}
return "<@!" + realUserID + ">"
}
return m
})
return messageText
}
// 链接处理
func transformMessageTextUrl(messageText string, message callapi.ActionMessage, client callapi.Client, api openapi.OpenAPI, apiv2 openapi.OpenAPI) string {
// 是否处理url
if config.GetTransferUrl() {
// 判断服务器地址是否是IP地址
serverAddress := config.GetServer_dir()
isIP := isIPAddress(serverAddress)
VisualIP := config.GetVisibleIP()
// 使用xurls来查找和替换所有的URL
messageText = xurls.Relaxed.ReplaceAllStringFunc(messageText, func(originalURL string) string {
// 当服务器地址是IP地址且GetVisibleIP为false时,替换URL为空
if isIP && !VisualIP {
return ""
}
// 如果启用了URL到QR码的转换
if config.GetUrlToQrimage() {
// 将URL转换为QR码的字节形式
qrCodeGenerator, _ := qrcode.New(originalURL, qrcode.High)
qrCodeGenerator.DisableBorder = true
qrSize := config.GetQrSize()
pngBytes, _ := qrCodeGenerator.PNG(qrSize)
//pngBytes 二维码图片的字节数据
base64Image := base64.StdEncoding.EncodeToString(pngBytes)
picmsg := processActionMessageWithBase64PicReplace(base64Image, message)
ret := callapi.CallAPIFromDict(client, api, apiv2, picmsg)
mylog.Printf("发送url转图片结果:%v", ret)
// 从文本中去除原始URL
return "" // 返回空字符串以去除URL
}
// 根据配置处理URL
if config.GetLotusValue() {
// 连接到另一个gensokyo
mylog.Printf("转换url:%v", originalURL)
shortURL := url.GenerateShortURL(originalURL)
return shortURL
} else {
// 自己是主节点
shortURL := url.GenerateShortURL(originalURL)
// 使用getBaseURL函数来获取baseUrl并与shortURL组合
return url.GetBaseURL() + "/url/" + shortURL
}
})
}
return messageText
}
// processActionMessageWithBase64PicReplace 将原有的callapi.ActionMessage内容替换为一个base64图片
func processActionMessageWithBase64PicReplace(base64Image string, message callapi.ActionMessage) callapi.ActionMessage {
newMessage := createCQImageMessage(base64Image)
message.Params.Message = newMessage
return message
}
// createCQImageMessage 从 base64 编码的图片创建 CQ 码格式的消息
func createCQImageMessage(base64Image string) string {
return "[CQ:image,file=base64://" + base64Image + "]"
}
// 处理at和其他定形文到onebotv11格式(cq码)
func RevertTransformedText(data interface{}, msgtype string, api openapi.OpenAPI, apiv2 openapi.OpenAPI, vgid int64, vuid int64, whitenable bool) string {
var msg *dto.Message
var menumsg bool
var messageText string
switch v := data.(type) {
case *dto.WSGroupATMessageData:
msg = (*dto.Message)(v)
case *dto.WSATMessageData:
msg = (*dto.Message)(v)
case *dto.WSMessageData:
msg = (*dto.Message)(v)
case *dto.WSDirectMessageData:
msg = (*dto.Message)(v)
case *dto.WSC2CMessageData:
msg = (*dto.Message)(v)
default:
return ""
}
menumsg = false
//单独一个空格的信息的空格用户并不希望去掉
if msg.Content == " " {
menumsg = true
messageText = " "
}
if !menumsg {
//处理前 先去前后空
messageText = strings.TrimSpace(msg.Content)
}
var originmessageText = messageText
//mylog.Printf("1[%v]", messageText)
// 将messageText里的BotID替换成AppID
messageText = strings.ReplaceAll(messageText, BotID, AppID)
// 使用正则表达式来查找所有<@!数字>的模式
re := regexp.MustCompile(`<@!(\d+)>`)
// 使用正则表达式来替换找到的模式为[CQ:at,qq=用户ID]
messageText = re.ReplaceAllStringFunc(messageText, func(m string) string {
submatches := re.FindStringSubmatch(m)
if len(submatches) > 1 {
userID := submatches[1]
// 检查是否是 BotID,如果是则直接返回,不进行映射,或根据用户需求移除
if userID == AppID {
if config.GetRemoveAt() {
return ""
} else {
return "[CQ:at,qq=" + AppID + "]"
}
}
// 不是 BotID,进行正常映射
userID64, err := idmap.StoreIDv2(userID)
if err != nil {
//如果储存失败(数据库损坏)返回原始值
mylog.Printf("Error storing ID: %v", err)
return "[CQ:at,qq=" + userID + "]"
}
// 类型转换
userIDStr := strconv.FormatInt(userID64, 10)
// 经过转换的cq码
return "[CQ:at,qq=" + userIDStr + "]"
}
return m
})
//结构 <@!>空格/内容
//如果移除了前部at,信息就会以空格开头,因为只移去了最前面的at,但at后紧跟随一个空格
if config.GetRemoveAt() {
if !menumsg {
//再次去前后空
messageText = strings.TrimSpace(messageText)
}
}
//mylog.Printf("2[%v]", messageText)
// 检查是否需要移除前缀
if config.GetRemovePrefixValue() {
// 移除消息内容中第一次出现的 "/"
if idx := strings.Index(messageText, "/"); idx != -1 {
messageText = messageText[:idx] + messageText[idx+1:]
}
}
// 检查是否启用白名单模式
if config.GetWhitePrefixMode() && whitenable {
// 获取白名单反转标志
whiteBypassRevers := config.GetWhiteBypassRevers()
// 获取白名单例外群数组(现在返回 int64 数组)
whiteBypass := config.GetWhiteBypass()
bypass := false
// 根据 whiteBypassRevers 的值来改变逻辑
if whiteBypassRevers {
// 如果反转白名单效果,只有在白名单数组中的 vgid 或 vuid 才生效
bypass = true // 默认设置为 true,意味着需要白名单检查
for _, id := range whiteBypass {
if id == vgid || id == vuid {
bypass = false // 如果在白名单数组中找到了 vgid 或 vuid,设置为 false
break
}
}
} else {
// 常规逻辑:检查 vgid 是否在白名单例外数组中
for _, id := range whiteBypass {
if id == vgid || id == vuid {
bypass = true
break
}
}
}
// 如果vgid不在白名单例外数组中,则应用白名单过滤
if !bypass {
// 获取白名单数组
whitePrefixes := config.GetWhitePrefixs()
// 加锁以安全地读取 TemporaryCommands
idmap.MutexT.Lock()
temporaryCommands := make([]string, len(idmap.TemporaryCommands))
copy(temporaryCommands, idmap.TemporaryCommands)
idmap.MutexT.Unlock()
// 合并白名单和临时指令
allPrefixes := append(whitePrefixes, temporaryCommands...)
// 默认设置为不匹配
matched := false
// 遍历白名单数组,检查是否有匹配项
for _, prefix := range allPrefixes {
if strings.HasPrefix(messageText, prefix) {
// 找到匹配项,保留 messageText 并跳出循环
matched = true
break
}
}
// 如果没有匹配项,则将 messageText 置为兜底回复 兜底回复可空
if !matched {
messageText = ""
SendMessage(config.GetNoWhiteResponse(), data, msgtype, api, apiv2)
}
}
}
//mylog.Printf("3[%v]", messageText)
//检查是否启用黑名单模式
if config.GetBlackPrefixMode() {
// 获取黑名单数组
blackPrefixes := config.GetBlackPrefixs()
// 遍历黑名单数组,检查是否有匹配项
for _, prefix := range blackPrefixes {
if strings.HasPrefix(messageText, prefix) {
// 找到匹配项,将 messageText 置为空并停止处理
messageText = ""
break
}
}
}
// 移除以 GetVisualkPrefixs 数组开头的文本
visualkPrefixs := config.GetVisualkPrefixs()
var matchedPrefix *structs.VisualPrefixConfig
var isSpecialType bool // 用于标记是否为特殊类型
var originalPrefix string // 存储原始前缀
// 处理特殊类型前缀
specialPrefixes := make(map[int]string)
for i, vp := range visualkPrefixs {
if strings.HasPrefix(vp.Prefix, "*") {
specialPrefixes[i] = vp.Prefix // 保存原始前缀
visualkPrefixs[i].Prefix = strings.TrimPrefix(vp.Prefix, "*") // 移除 '*'
}
}
//从当前信息去掉虚拟前缀(因为是虚拟的),不会实际发给应用端
for i, vp := range visualkPrefixs {
if strings.HasPrefix(messageText, vp.Prefix) {
if _, ok := specialPrefixes[i]; ok {
isSpecialType = true
originalPrefix = specialPrefixes[i] // 恢复原始前缀
}
// 检查 messageText 的长度是否大于 prefix 的长度 忽略长度为0的初始 vp.Prefix
if len(vp.Prefix) > 0 && len(messageText) > len(vp.Prefix) {
// 移除找到的前缀 且messageText不为空
if messageText != "" {
messageText = strings.TrimPrefix(messageText, vp.Prefix)
messageText = strings.TrimSpace(messageText)
matchedPrefix = &vp
}
break // 只移除第一个匹配的前缀
}
}
}
// 已经完成了移除前缀等操作,进行aliases替换
aliases := config.GetAlias()
messageText = processMessageText(messageText, aliases)
//mylog.Printf("4[%v]", messageText)
// 检查是否启用二级白名单模式
if config.GetVwhitePrefixMode() && matchedPrefix != nil {
// 获取白名单反转标志
whiteBypassRevers := config.GetWhiteBypassRevers()
// 获取白名单例外群数组(现在返回 int64 数组)
whiteBypass := config.GetWhiteBypass()
bypass := false
// 根据 whiteBypassRevers 的值来改变逻辑
if whiteBypassRevers {
// 如果反转白名单效果,只有在白名单数组中的 vgid 或 vuid 才生效
bypass = true // 默认设置为 true,意味着需要白名单检查
for _, id := range whiteBypass {
if id == vgid || id == vuid {
bypass = false // 如果在白名单数组中找到了 vgid 或 vuid,设置为 false
break
}
}
} else {
// 常规逻辑:检查 vgid 是否在白名单例外数组中
for _, id := range whiteBypass {
if id == vgid || id == vuid {
bypass = true
break
}
}
}
// 如果vgid不在白名单例外数组中,则应用白名单过滤
if !bypass {
allPrefixes := matchedPrefix.WhiteList
idmap.MutexT.Lock()
temporaryCommands := make([]string, len(idmap.TemporaryCommands))
copy(temporaryCommands, idmap.TemporaryCommands)
idmap.MutexT.Unlock()
// 合并虚拟前缀的白名单和临时指令
allPrefixes = append(allPrefixes, temporaryCommands...)
matched := false
// 检查allPrefixes中的每个prefix是否都以*开头
allStarPrefixed := true
for _, prefix := range allPrefixes {
if !strings.HasPrefix(prefix, "*") {
allStarPrefixed = false
break
}
}
//如果二级指令白名单全部是*(忽略自身,那么不判断二级白名单是否匹配)
if allStarPrefixed {
if len(messageText) == len(matchedPrefix.Prefix) {
matched = true
} else {
matched = false
// 调用 GetVisualPrefixsBypass 获取前缀数组
visualPrefixes := config.GetVisualPrefixsBypass()
// 判断 messageText 是否以数组中的任一前缀开头
for _, prefix := range visualPrefixes {
if strings.HasPrefix(originmessageText, prefix) {
matched = true
break
}
}
}
} else {
// 遍历白名单数组,检查是否有匹配项
for _, prefix := range allPrefixes {
trimmedPrefix := prefix
if strings.HasPrefix(prefix, "*") {
// 如果前缀以 * 开头,则移除 *
trimmedPrefix = strings.TrimPrefix(prefix, "*")
} else if strings.HasPrefix(prefix, "&") {
// 如果前缀以 & 开头,则移除 & 并从 trimmedPrefix 前端去除 matchedPrefix.Prefix
trimmedPrefix = strings.TrimPrefix(prefix, "&")
trimmedPrefix = strings.TrimPrefix(trimmedPrefix, matchedPrefix.Prefix)