/
Processor.go
1071 lines (964 loc) · 32.7 KB
/
Processor.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 Processor
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"math/big"
"net/http"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/hashicorp/go-multierror"
"github.com/hoshinonyaruko/gensokyo/callapi"
"github.com/hoshinonyaruko/gensokyo/config"
"github.com/hoshinonyaruko/gensokyo/echo"
"github.com/hoshinonyaruko/gensokyo/handlers"
"github.com/hoshinonyaruko/gensokyo/idmap"
"github.com/hoshinonyaruko/gensokyo/images"
"github.com/hoshinonyaruko/gensokyo/mylog"
"github.com/hoshinonyaruko/gensokyo/wsclient"
"github.com/tencent-connect/botgo/dto"
"github.com/tencent-connect/botgo/dto/keyboard"
"github.com/tencent-connect/botgo/openapi"
)
// Processor 结构体用于处理消息
type Processors struct {
Api openapi.OpenAPI // API 类型
Apiv2 openapi.OpenAPI //群的API
Settings *config.Settings // 使用指针
Wsclient []*wsclient.WebSocketClient // 指针的切片
WsServerClients []callapi.WebSocketServerClienter //ws server被连接的客户端
}
type Sender struct {
Nickname string `json:"nickname"`
TinyID string `json:"tiny_id"`
UserID int64 `json:"user_id"`
Role string `json:"role,omitempty"`
Card string `json:"card,omitempty"`
Sex string `json:"sex,omitempty"`
Age int32 `json:"age,omitempty"`
Area string `json:"area,omitempty"`
Level string `json:"level,omitempty"`
Title string `json:"title,omitempty"`
}
// 频道信息事件
type OnebotChannelMessage struct {
ChannelID string `json:"channel_id"`
GuildID string `json:"guild_id"`
Message interface{} `json:"message"`
MessageID string `json:"message_id"`
MessageType string `json:"message_type"`
PostType string `json:"post_type"`
SelfID int64 `json:"self_id"`
SelfTinyID string `json:"self_tiny_id"`
Sender Sender `json:"sender"`
SubType string `json:"sub_type"`
Time int64 `json:"time"`
Avatar string `json:"avatar,omitempty"`
UserID int64 `json:"user_id"`
RawMessage string `json:"raw_message"`
Echo string `json:"echo,omitempty"`
RealMessageType string `json:"real_message_type,omitempty"` //当前信息的真实类型 表情表态
}
// 群信息事件
type OnebotGroupMessage struct {
RawMessage string `json:"raw_message"`
MessageID int `json:"message_id"`
GroupID int64 `json:"group_id"` // Can be either string or int depending on p.Settings.CompleteFields
MessageType string `json:"message_type"`
PostType string `json:"post_type"`
SelfID int64 `json:"self_id"` // Can be either string or int
Sender Sender `json:"sender"`
SubType string `json:"sub_type"`
Time int64 `json:"time"`
Avatar string `json:"avatar,omitempty"`
Echo string `json:"echo,omitempty"`
Message interface{} `json:"message"` // For array format
MessageSeq int `json:"message_seq"`
Font int `json:"font"`
UserID int64 `json:"user_id"`
RealMessageType string `json:"real_message_type,omitempty"` //当前信息的真实类型 group group_private guild guild_private
RealUserID string `json:"real_user_id,omitempty"` //当前真实uid
RealGroupID string `json:"real_group_id,omitempty"` //当前真实gid
IsBindedGroupId bool `json:"is_binded_group_id,omitempty"` //当前群号是否是binded后的
IsBindedUserId bool `json:"is_binded_user_id,omitempty"` //当前用户号号是否是binded后的
}
// 私聊信息事件
type OnebotPrivateMessage struct {
RawMessage string `json:"raw_message"`
MessageID int `json:"message_id"` // Can be either string or int depending on logic
MessageType string `json:"message_type"`
PostType string `json:"post_type"`
SelfID int64 `json:"self_id"` // Can be either string or int depending on logic
Sender PrivateSender `json:"sender"`
SubType string `json:"sub_type"`
Time int64 `json:"time"`
Avatar string `json:"avatar,omitempty"`
Echo string `json:"echo,omitempty"`
Message interface{} `json:"message"` // For array format
MessageSeq int `json:"message_seq"` // Optional field
Font int `json:"font"` // Optional field
UserID int64 `json:"user_id"` // Can be either string or int depending on logic
RealMessageType string `json:"real_message_type,omitempty"` //当前信息的真实类型 group group_private guild guild_private
IsBindedUserId bool `json:"is_binded_user_id,omitempty"` //当前用户号号是否是binded后的
}
// onebotv11标准扩展
type OnebotInteractionNotice struct {
GroupID int64 `json:"group_id,omitempty"`
NoticeType string `json:"notice_type,omitempty"`
PostType string `json:"post_type,omitempty"`
SelfID int64 `json:"self_id,omitempty"`
SubType string `json:"sub_type,omitempty"`
Time int64 `json:"time,omitempty"`
UserID int64 `json:"user_id,omitempty"`
Data *dto.WSInteractionData `json:"data,omitempty"`
}
type PrivateSender struct {
Nickname string `json:"nickname"`
UserID int64 `json:"user_id"` // Can be either string or int depending on logic
}
// 打印结构体的函数
func PrintStructWithFieldNames(v interface{}) {
val := reflect.ValueOf(v)
// 如果是指针,获取其指向的元素
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
// 确保我们传入的是一个结构体
if typ.Kind() != reflect.Struct {
mylog.Println("Input is not a struct")
return
}
// 迭代所有的字段并打印字段名和值
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
value := val.Field(i)
mylog.Printf("%s: %v\n", field.Name, value.Interface())
}
}
// 将结构体转换为 map[string]interface{}
func structToMap(obj interface{}) map[string]interface{} {
out := make(map[string]interface{})
j, _ := json.Marshal(obj)
json.Unmarshal(j, &out)
return out
}
// 修改函数的返回类型为 *Processor
func NewProcessor(api openapi.OpenAPI, apiv2 openapi.OpenAPI, settings *config.Settings, wsclient []*wsclient.WebSocketClient) *Processors {
return &Processors{
Api: api,
Apiv2: apiv2,
Settings: settings,
Wsclient: wsclient,
}
}
// 修改函数的返回类型为 *Processor
func NewProcessorV2(api openapi.OpenAPI, apiv2 openapi.OpenAPI, settings *config.Settings) *Processors {
return &Processors{
Api: api,
Apiv2: apiv2,
Settings: settings,
}
}
// 发信息给所有连接正向ws的客户端
func (p *Processors) SendMessageToAllClients(message map[string]interface{}) error {
var result *multierror.Error
for _, client := range p.WsServerClients {
// 使用接口的方法
err := client.SendMessage(message)
if err != nil {
// Append the error to our result
result = multierror.Append(result, fmt.Errorf("failed to send to client: %w", err))
}
}
// This will return nil if no errors were added
return result.ErrorOrNil()
}
// 方便快捷的发信息函数
func (p *Processors) BroadcastMessageToAll(message map[string]interface{}) error {
var errors []string
// 发送到我们作为客户端的Wsclient
for _, client := range p.Wsclient {
//mylog.Printf("第%v个Wsclient", test)
err := client.SendMessage(message)
if err != nil {
errors = append(errors, fmt.Sprintf("error sending private message via wsclient: %v", err))
}
}
// 发送到我们作为服务器连接到我们的WsServerClients
for _, serverClient := range p.WsServerClients {
err := serverClient.SendMessage(message)
if err != nil {
errors = append(errors, fmt.Sprintf("error sending private message via WsServerClient: %v", err))
}
}
// 在循环结束后处理记录的错误
if len(errors) > 0 {
return fmt.Errorf(strings.Join(errors, "; "))
}
//判断是否填写了反向post地址
if !allEmpty(config.GetPostUrl()) {
PostMessageToUrls(message)
}
return nil
}
// allEmpty checks if all the strings in the slice are empty.
func allEmpty(addresses []string) bool {
for _, addr := range addresses {
if addr != "" {
return false
}
}
return true
}
// 上报信息给反向Http
func PostMessageToUrls(message map[string]interface{}) {
// 获取上报 URL 列表
postUrls := config.GetPostUrl()
// 检查 postUrls 是否为空
if len(postUrls) > 0 {
// 转换 message 为 JSON 字符串
jsonString, err := handlers.ConvertMapToJSONString(message)
if err != nil {
mylog.Printf("Error converting message to JSON: %v", err)
return
}
for _, url := range postUrls {
// 创建请求体
reqBody := bytes.NewBufferString(jsonString)
// 创建 POST 请求
req, err := http.NewRequest("POST", url, reqBody)
if err != nil {
mylog.Printf("Error creating POST request to %s: %v", url, err)
continue
}
// 设置请求头
req.Header.Set("Content-Type", "application/json")
// 设置 X-Self-ID
var selfid string
if config.GetUseUin() {
selfid = config.GetUinStr()
} else {
selfid = config.GetAppIDStr()
}
req.Header.Set("X-Self-ID", selfid)
// 发送请求
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
mylog.Printf("Error sending POST request to %s: %v", url, err)
continue
}
// 处理响应
defer resp.Body.Close()
// 可以添加更多的响应处理逻辑,如检查状态码等
mylog.Printf("Posted to %s successfully", url)
}
}
}
func (p *Processors) HandleFrameworkCommand(messageText string, data interface{}, Type string) error {
// 正则表达式匹配转换后的 CQ 码
cqRegex := regexp.MustCompile(`\[CQ:at,qq=\d+\]`)
// 使用正则表达式替换所有的 CQ 码为 ""
cleanedMessage := cqRegex.ReplaceAllString(messageText, "")
// 去除字符串前后的空格
cleanedMessage = strings.TrimSpace(cleanedMessage)
if cleanedMessage == "t" {
// 生成临时指令
tempCmd := handleNoPermission()
mylog.Printf("临时bind指令: %s 可忽略权限检查1次,或将masterid设置为空数组", tempCmd)
}
var err error
var now, new, newpro1, newpro2 string
var nowgroup, newgroup string
var realid, realid2 string
var guildid, guilduserid string
switch v := data.(type) {
case *dto.WSGroupATMessageData:
realid = v.Author.ID
case *dto.WSATMessageData:
realid = v.Author.ID
guildid = v.GuildID
guilduserid = v.Author.ID
case *dto.WSMessageData:
realid = v.Author.ID
guildid = v.GuildID
guilduserid = v.Author.ID
case *dto.WSDirectMessageData:
realid = v.Author.ID
case *dto.WSC2CMessageData:
realid = v.Author.ID
}
switch v := data.(type) {
case *dto.WSGroupATMessageData:
realid2 = v.GroupID
case *dto.WSATMessageData:
realid2 = v.ChannelID
case *dto.WSMessageData:
realid2 = v.ChannelID
case *dto.WSDirectMessageData:
realid2 = v.ChannelID
case *dto.WSC2CMessageData:
realid2 = "group_private"
}
// 获取MasterID数组
masterIDs := config.GetMasterID()
// idmaps-pro获取群和用户id
if config.GetIdmapPro() {
newpro1, newpro2, err = idmap.RetrieveVirtualValuev2Pro(realid2, realid)
if err != nil {
mylog.Printf("idmaps-pro获取群和用户id 错误:%v", err)
}
} else {
// 根据realid获取new(用户id)
now, new, err = idmap.RetrieveVirtualValuev2(realid)
if err != nil {
mylog.Printf("根据realid获取new(用户id) 错误:%v", err)
}
// 根据realid获取new(群id)
nowgroup, newgroup, err = idmap.RetrieveVirtualValuev2(realid2)
if err != nil {
mylog.Printf("根据realid获取new(群id)错误:%v", err)
}
}
// 检查真实值或虚拟值是否在数组中
var realValueIncluded, virtualValueIncluded bool
// 如果 masterIDs 数组为空,则这两个值恒为 true
if len(masterIDs) == 0 {
realValueIncluded = true
virtualValueIncluded = true
} else {
// 否则,检查真实值或虚拟值是否在数组中
realValueIncluded = contains(masterIDs, realid)
virtualValueIncluded = contains(masterIDs, new)
}
//unlock指令
if Type == "guild" && strings.HasPrefix(cleanedMessage, config.GetUnlockPrefix()) {
dm := &dto.DirectMessageToCreate{
SourceGuildID: guildid,
RecipientID: guilduserid,
}
cdm, err := p.Api.CreateDirectMessage(context.TODO(), dm)
if err != nil {
mylog.Printf("unlock指令创建dm失败:%v", err)
}
msg := &dto.MessageToCreate{
Content: "欢迎使用Gensokyo框架部署QQ机器人",
MsgType: 0,
MsgID: "",
}
_, err = p.Api.PostDirectMessage(context.TODO(), cdm, msg)
if err != nil {
mylog.Printf("unlock指令发送失败:%v", err)
}
}
// me指令处理逻辑
if strings.HasPrefix(cleanedMessage, config.GetMePrefix()) {
if err != nil {
// 发送错误信息
SendMessage(err.Error(), data, Type, p.Api, p.Apiv2)
return err
}
// 发送成功信息
if config.GetIdmapPro() {
// 构造清晰的对应关系信息
userMapping := fmt.Sprintf("当前真实值(用户)/当前虚拟值(用户) = [%s/%s]", realid, newpro2)
groupMapping := fmt.Sprintf("当前真实值(群/频道)/当前虚拟值(群/频道) = [%s/%s]", realid2, newpro1)
// 构造 bind 指令的使用说明
bindInstruction := fmt.Sprintf("bind 指令: %s 当前虚拟值(用户) 目标虚拟值(用户) [当前虚拟值(群/频道) 目标虚拟值(群/频道)]", config.GetBindPrefix())
// 发送整合后的消息
message := fmt.Sprintf("idmaps-pro状态:\n%s\n%s\n%s", userMapping, groupMapping, bindInstruction)
SendMessage(message, data, Type, p.Api, p.Apiv2)
} else {
SendMessage("目前状态:\n当前真实值(用户) "+now+"\n当前虚拟值(用户) "+new+"\n当前真实值(群/频道) "+nowgroup+"\n当前虚拟值(群/频道) "+newgroup+"\nbind指令:"+config.GetBindPrefix()+" 当前虚拟值"+" 目标虚拟值", data, Type, p.Api, p.Apiv2)
}
return nil
}
fields := strings.Fields(cleanedMessage)
// 首先确保消息不是空的,然后检查是否是有效的临时指令
if len(fields) > 0 && isValidTemporaryCommand(fields[0]) {
// 执行 bind 操作
if config.GetIdmapPro() {
err := performBindOperationV2(cleanedMessage, data, Type, p.Api, p.Apiv2, newpro1)
if err != nil {
mylog.Printf("bind遇到错误:%v", err)
}
} else {
err := performBindOperation(cleanedMessage, data, Type, p.Api, p.Apiv2)
if err != nil {
mylog.Printf("bind遇到错误:%v", err)
}
}
return nil
}
// 如果不是临时指令,检查是否具有执行bind操作的权限并且消息以特定前缀开始
if (realValueIncluded || virtualValueIncluded) && strings.HasPrefix(cleanedMessage, config.GetBindPrefix()) {
// 执行 bind 操作
if config.GetIdmapPro() {
err := performBindOperationV2(cleanedMessage, data, Type, p.Api, p.Apiv2, newpro1)
if err != nil {
mylog.Printf("bind遇到错误:%v", err)
}
} else {
err := performBindOperation(cleanedMessage, data, Type, p.Api, p.Apiv2)
if err != nil {
mylog.Printf("bind遇到错误:%v", err)
}
}
return nil
} else if strings.HasPrefix(cleanedMessage, config.GetBindPrefix()) {
// 生成临时指令
tempCmd := handleNoPermission()
mylog.Printf("您没有权限,使用临时指令:%s 忽略权限检查,或将masterid设置为空数组", tempCmd)
SendMessage("您没有权限,请配置config.yml或查看日志,使用临时指令", data, Type, p.Api, p.Apiv2)
}
//link指令
if Type == "group" && strings.HasPrefix(cleanedMessage, config.GetLinkPrefix()) {
md, kb := generateMdByConfig()
SendMessageMd(md, kb, data, Type, p.Api, p.Apiv2)
}
return nil
}
// 生成由两个英文字母构成的唯一临时指令
func generateTemporaryCommand() (string, error) {
bytes := make([]byte, 1) // 生成1字节的随机数,足以表示2个十六进制字符
if _, err := rand.Read(bytes); err != nil {
return "", err // 处理随机数生成错误
}
command := hex.EncodeToString(bytes)[:2] // 将1字节转换为2个十六进制字符
return command, nil
}
// 生成并添加一个新的临时指令
func handleNoPermission() string {
idmap.MutexT.Lock()
defer idmap.MutexT.Unlock()
cmd, _ := generateTemporaryCommand()
idmap.TemporaryCommands = append(idmap.TemporaryCommands, cmd)
return cmd
}
// 检查指令是否是有效的临时指令
func isValidTemporaryCommand(cmd string) bool {
idmap.MutexT.Lock()
defer idmap.MutexT.Unlock()
for i, tempCmd := range idmap.TemporaryCommands {
if tempCmd == cmd {
// 删除已验证的临时指令
idmap.TemporaryCommands = append(idmap.TemporaryCommands[:i], idmap.TemporaryCommands[i+1:]...)
return true
}
}
return false
}
// 执行 bind 操作的逻辑
func performBindOperation(cleanedMessage string, data interface{}, Type string, p openapi.OpenAPI, p2 openapi.OpenAPI) error {
// 分割指令以获取参数
parts := strings.Fields(cleanedMessage)
if len(parts) != 3 {
mylog.Printf("bind指令参数错误\n正确的格式" + config.GetBindPrefix() + " 当前虚拟值 新虚拟值")
return nil
}
// 将字符串转换为 int64
oldRowValue, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
return err
}
newRowValue, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil {
return err
}
// 调用 UpdateVirtualValue
err = idmap.UpdateVirtualValuev2(oldRowValue, newRowValue)
if err != nil {
SendMessage(err.Error(), data, Type, p, p2)
return err
}
now, new, err := idmap.RetrieveRealValuev2(newRowValue)
if err != nil {
SendMessage(err.Error(), data, Type, p, p2)
} else {
SendMessage("绑定成功,目前状态:\n当前真实值 "+new+"\n当前虚拟值 "+now, data, Type, p, p2)
}
return nil
}
func performBindOperationV2(cleanedMessage string, data interface{}, Type string, p openapi.OpenAPI, p2 openapi.OpenAPI, GroupVir string) error {
// 分割指令以获取参数
parts := strings.Fields(cleanedMessage)
// 检查参数数量
if len(parts) < 3 || len(parts) > 5 {
mylog.Printf("bind指令参数错误\n正确的格式: " + config.GetBindPrefix() + " 当前虚拟值(用户) 新虚拟值(用户) [当前虚拟值(群) 新虚拟值(群)]")
return nil
}
// 当前虚拟值 用户
oldVirtualValue1, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil {
return err
}
//新的虚拟值 用户
newVirtualValue1, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil {
return err
}
// 设置默认值
var oldRowValue, newRowValue int64
// 如果提供了第3个和第4个参数,则解析它们
if len(parts) > 3 {
oldRowValue, err = parseOrDefault(parts[3], GroupVir)
if err != nil {
return err
}
newRowValue, err = parseOrDefault(parts[4], GroupVir)
if err != nil {
return err
}
} else {
// 如果没有提供这些参数,则直接使用 GroupVir
oldRowValue, err = strconv.ParseInt(GroupVir, 10, 64)
if err != nil {
return err
}
newRowValue = oldRowValue // 使用相同的值
}
// 调用 UpdateVirtualValue(兼顾老转换)
err = idmap.UpdateVirtualValuev2(oldVirtualValue1, newVirtualValue1)
if err != nil {
SendMessage(err.Error(), data, Type, p, p2)
return err
}
// 调用 UpdateVirtualValuev2Pro
err = idmap.UpdateVirtualValuev2Pro(oldRowValue, newRowValue, oldVirtualValue1, newVirtualValue1)
if err != nil {
SendMessage(err.Error(), data, Type, p, p2)
return err
}
now, new, err := idmap.RetrieveRealValuesv2Pro(newRowValue, newVirtualValue1)
if err != nil {
SendMessage(err.Error(), data, Type, p, p2)
} else {
newVirtualValue1Str := strconv.FormatInt(newRowValue, 10)
newVirtualValue2Str := strconv.FormatInt(newVirtualValue1, 10)
SendMessage("绑定成功,目前状态:\n当前真实值(群)"+now+"\n当前真实值(用户)"+new+"\n当前虚拟值(群)"+newVirtualValue1Str+"当前虚拟值(用户)"+newVirtualValue2Str, data, Type, p, p2)
}
return nil
}
// parseOrDefault 将字符串转换为int64,如果无法转换或为0,则使用默认值
func parseOrDefault(s string, defaultValue string) (int64, error) {
value, err := strconv.ParseInt(s, 10, 64)
if err == nil && value != 0 {
return value, nil
}
return strconv.ParseInt(defaultValue, 10, 64)
}
// contains 检查数组中是否包含指定的字符串
func contains(slice []string, item string) bool {
for _, a := range slice {
if a == item {
return true
}
}
return false
}
// SendMessage 发送消息根据不同的类型
func SendMessage(messageText string, data interface{}, messageType string, api openapi.OpenAPI, apiv2 openapi.OpenAPI) error {
// 强制类型转换,获取Message结构
var msg *dto.Message
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 nil
}
switch messageType {
case "guild":
// 处理公会消息
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
textMsg, _ := handlers.GenerateReplyMessage(msg.ID, nil, messageText, msgseq+1)
if _, err := api.PostMessage(context.TODO(), msg.ChannelID, textMsg); err != nil {
mylog.Printf("发送文本信息失败: %v", err)
return err
}
case "group":
// 处理群组消息
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
textMsg, _ := handlers.GenerateReplyMessage(msg.ID, nil, messageText, msgseq+1)
_, err := apiv2.PostGroupMessage(context.TODO(), msg.GroupID, textMsg)
if err != nil {
mylog.Printf("发送文本群组信息失败: %v", err)
return err
}
case "guild_private":
// 处理私信
timestamp := time.Now().Unix()
timestampStr := fmt.Sprintf("%d", timestamp)
dm := &dto.DirectMessage{
GuildID: msg.GuildID,
ChannelID: msg.ChannelID,
CreateTime: timestampStr,
}
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
textMsg, _ := handlers.GenerateReplyMessage(msg.ID, nil, messageText, msgseq+1)
if _, err := apiv2.PostDirectMessage(context.TODO(), dm, textMsg); err != nil {
mylog.Printf("发送文本信息失败: %v", err)
return err
}
case "group_private":
// 处理群组私聊消息
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
textMsg, _ := handlers.GenerateReplyMessage(msg.ID, nil, messageText, msgseq+1)
_, err := apiv2.PostC2CMessage(context.TODO(), msg.Author.ID, textMsg)
if err != nil {
mylog.Printf("发送文本私聊信息失败: %v", err)
return err
}
default:
return errors.New("未知的消息类型")
}
return nil
}
// SendMessageMd 发送Md消息根据不同的类型
func SendMessageMd(md *dto.Markdown, kb *keyboard.MessageKeyboard, data interface{}, messageType string, api openapi.OpenAPI, apiv2 openapi.OpenAPI) error {
// 强制类型转换,获取Message结构
var msg *dto.Message
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 nil
}
switch messageType {
case "guild":
// 处理公会消息
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
Message := &dto.MessageToCreate{
Content: "markdown",
MsgID: msg.ID,
MsgSeq: msgseq,
Markdown: md,
Keyboard: kb,
MsgType: 2, //md信息
}
Message.Timestamp = time.Now().Unix() // 设置时间戳
if _, err := api.PostMessage(context.TODO(), msg.ChannelID, Message); err != nil {
mylog.Printf("发送文本信息失败: %v", err)
return err
}
case "group":
// 处理群组消息
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
Message := &dto.MessageToCreate{
Content: "markdown",
MsgID: msg.ID,
MsgSeq: msgseq,
Markdown: md,
Keyboard: kb,
MsgType: 2, //md信息
}
Message.Timestamp = time.Now().Unix() // 设置时间戳
_, err := apiv2.PostGroupMessage(context.TODO(), msg.GroupID, Message)
if err != nil {
mylog.Printf("发送文本群组信息失败: %v", err)
return err
}
case "guild_private":
// 处理私信
timestamp := time.Now().Unix()
timestampStr := fmt.Sprintf("%d", timestamp)
dm := &dto.DirectMessage{
GuildID: msg.GuildID,
ChannelID: msg.ChannelID,
CreateTime: timestampStr,
}
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
Message := &dto.MessageToCreate{
Content: "markdown",
MsgID: msg.ID,
MsgSeq: msgseq,
Markdown: md,
Keyboard: kb,
MsgType: 2, //md信息
}
Message.Timestamp = time.Now().Unix() // 设置时间戳
if _, err := apiv2.PostDirectMessage(context.TODO(), dm, Message); err != nil {
mylog.Printf("发送文本信息失败: %v", err)
return err
}
case "group_private":
// 处理群组私聊消息
msgseq := echo.GetMappingSeq(msg.ID)
echo.AddMappingSeq(msg.ID, msgseq+1)
Message := &dto.MessageToCreate{
Content: "markdown",
MsgID: msg.ID,
MsgSeq: msgseq,
Markdown: md,
Keyboard: kb,
MsgType: 2, //md信息
}
Message.Timestamp = time.Now().Unix() // 设置时间戳
_, err := apiv2.PostC2CMessage(context.TODO(), msg.Author.ID, Message)
if err != nil {
mylog.Printf("发送文本私聊信息失败: %v", err)
return err
}
default:
return errors.New("未知的消息类型")
}
return nil
}
// autobind 函数接受 interface{} 类型的数据
// commit by 紫夜 2023-11-19
func (p *Processors) Autobind(data interface{}) error {
var realID string
var groupID string
var attachmentURL string
// 群at
switch v := data.(type) {
case *dto.WSGroupATMessageData:
realID = v.Author.ID
groupID = v.GroupID
attachmentURL = v.Attachments[0].URL
//群私聊
case *dto.WSC2CMessageData:
realID = v.Author.ID
groupID = v.GroupID
attachmentURL = v.Attachments[0].URL
default:
return fmt.Errorf("未知的数据类型")
}
// 从 URL 中提取 newRowValue (vuin)
vuinRegex := regexp.MustCompile(`vuin=(\d+)`)
vuinMatches := vuinRegex.FindStringSubmatch(attachmentURL)
if len(vuinMatches) < 2 {
mylog.Errorf("无法从 URL 中提取 vuin")
return nil
}
vuinstr := vuinMatches[1]
vuinValue, err := strconv.ParseInt(vuinMatches[1], 10, 64)
if err != nil {
return err
}
// 从 URL 中提取第二个 newRowValue (群号)
idRegex := regexp.MustCompile(`gchatpic_new/(\d+)/`)
idMatches := idRegex.FindStringSubmatch(attachmentURL)
if len(idMatches) < 2 {
mylog.Errorf("无法从 URL 中提取 ID")
return nil
}
idValuestr := idMatches[1]
idValue, err := strconv.ParseInt(idMatches[1], 10, 64)
if err != nil {
return err
}
var GroupID64, userid64 int64
//获取虚拟值
// 映射str的GroupID到int
GroupID64, err = idmap.StoreIDv2(groupID)
if err != nil {
mylog.Errorf("failed to convert ChannelID to int: %v", err)
return nil
}
// 映射str的userid到int
userid64, err = idmap.StoreIDv2(realID)
if err != nil {
mylog.Printf("Error storing ID: %v", err)
return nil
}
//覆盖赋值
if config.GetIdmapPro() {
//转换idmap-pro 虚拟值
//将真实id转为int userid64
GroupID64, userid64, err = idmap.StoreIDv2Pro(groupID, realID)
if err != nil {
mylog.Fatalf("Error storing ID689: %v", err)
}
}
// 单独检查vuin和gid的绑定状态
vuinBound := strconv.FormatInt(userid64, 10) == vuinstr
gidBound := strconv.FormatInt(GroupID64, 10) == idValuestr
// 根据不同情况进行处理
if !vuinBound && !gidBound {
// 两者都未绑定,更新两个映射
if err := updateMappings(userid64, vuinValue, GroupID64, idValue); err != nil {
mylog.Printf("Error updateMappings for both: %v", err)
//return err
}
// idmaps pro也更新
err = idmap.UpdateVirtualValuev2Pro(GroupID64, idValue, userid64, vuinValue)
if err != nil {
mylog.Fatalf("Error storing ID703: %v", err)
}
} else if !vuinBound {
// 只有vuin未绑定,更新vuin映射
if err := idmap.UpdateVirtualValuev2(userid64, vuinValue); err != nil {
mylog.Printf("Error UpdateVirtualValuev2 for vuin: %v", err)
//return err
}
// idmaps pro也更新,但只更新vuin
idmap.UpdateVirtualValuev2Pro(GroupID64, idValue, userid64, vuinValue)
} else if !gidBound {
// 只有gid未绑定,更新gid映射
if err := idmap.UpdateVirtualValuev2(GroupID64, idValue); err != nil {
mylog.Printf("Error UpdateVirtualValuev2 for gid: %v", err)
//return err
}
// idmaps pro也更新,但只更新gid
idmap.UpdateVirtualValuev2Pro(GroupID64, idValue, userid64, vuinValue)
} else {
// 两者都已绑定,不执行任何操作
mylog.Errorf("Both vuin and gid are already binded")
}
return nil
}
// 更新映射的辅助函数
func updateMappings(userid64, vuinValue, GroupID64, idValue int64) error {
if err := idmap.UpdateVirtualValuev2(userid64, vuinValue); err != nil {
mylog.Printf("Error UpdateVirtualValuev2 for vuin: %v", err)
return err
}
if err := idmap.UpdateVirtualValuev2(GroupID64, idValue); err != nil {
mylog.Printf("Error UpdateVirtualValuev2 for gid: %v", err)
return err
}
return nil
}
// GenerateAvatarURL 生成根据给定 userID 和随机 q 值组合的 QQ 头像 URL
func GenerateAvatarURL(userID int64) (string, error) {
// 使用 crypto/rand 生成更安全的随机数
n, err := rand.Int(rand.Reader, big.NewInt(5))
if err != nil {
return "", err
}
qNumber := n.Int64() + 1 // 产生 1 到 5 的随机数
// 构建并返回 URL
return fmt.Sprintf("http://q%d.qlogo.cn/g?b=qq&nk=%d&s=640", qNumber, userID), nil
}
// GenerateAvatarURLV2 生成根据32位ID 和 Appid 组合的 新QQ 头像 URL
func GenerateAvatarURLV2(openid string) (string, error) {
appidstr := config.GetAppIDStr()
// 构建并返回 URL
return fmt.Sprintf("https://q.qlogo.cn/qqapp/%s/%s/640", appidstr, openid), nil
}
// 生成link卡片
func generateMdByConfig() (md *dto.Markdown, kb *keyboard.MessageKeyboard) {
//相关配置获取
mdtext := config.GetLinkText()
mdtext = "\r" + mdtext
CustomTemplateID := config.GetCustomTemplateID()
linkBots := config.GetLinkBots()
imgURL := config.GetLinkPic()
//超过16个时候随机显示
if len(linkBots) > 16 {
linkBots = getRandomSelection(linkBots, 16)
}
//组合 mdParams
var mdParams []*dto.MarkdownParams
if imgURL != "" {
height, width, err := images.GetImageDimensions(imgURL)
if err != nil {
mylog.Printf("获取图片宽高出错")
}
imgDesc := fmt.Sprintf("图片 #%dpx #%dpx", width, height)
// 创建 MarkdownParams 的实例
mdParams = []*dto.MarkdownParams{
{Key: "img_dec", Values: []string{imgDesc}},
{Key: "img_url", Values: []string{imgURL}},
{Key: "text_end", Values: []string{mdtext}},
}
} else {
mdParams = []*dto.MarkdownParams{
{Key: "text_end", Values: []string{mdtext}},
}
}
// 组合模板 Markdown