github.com/Mrs4s/MiraiGo@v0.0.0-20240226124653-54bdd873e3fe/message/message.go (about) 1 package message 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "reflect" 7 "strconv" 8 "strings" 9 10 "github.com/Mrs4s/MiraiGo/binary" 11 "github.com/Mrs4s/MiraiGo/client/pb/msg" 12 "github.com/Mrs4s/MiraiGo/internal/proto" 13 "github.com/Mrs4s/MiraiGo/utils" 14 ) 15 16 type IMessage interface { 17 GetElements() []IMessageElement 18 Chat() int64 19 ToString() string 20 Texts() []string 21 } 22 23 type ( 24 PrivateMessage struct { 25 Id int32 26 InternalId int32 27 Self int64 28 Target int64 29 Time int32 30 Sender *Sender 31 Elements []IMessageElement 32 } 33 34 TempMessage struct { 35 Id int32 36 GroupCode int64 37 GroupName string 38 Self int64 39 Sender *Sender 40 Elements []IMessageElement 41 } 42 43 GroupMessage struct { 44 Id int32 45 InternalId int32 46 GroupCode int64 47 GroupName string 48 Sender *Sender 49 Time int32 50 Elements []IMessageElement 51 OriginalObject *msg.Message 52 // OriginalElements []*msg.Elem 53 } 54 55 SendingMessage struct { 56 Elements []IMessageElement 57 } 58 59 Sender struct { 60 Uin int64 61 Nickname string 62 CardName string 63 AnonymousInfo *AnonymousInfo 64 IsFriend bool 65 } 66 67 AnonymousInfo struct { 68 AnonymousId string 69 AnonymousNick string 70 } 71 72 IMessageElement interface { 73 Type() ElementType 74 } 75 76 IRichMessageElement interface { 77 Pack() []*msg.Elem 78 } 79 80 ElementType int 81 ) 82 83 // MusicType values. 84 const ( 85 QQMusic = iota // QQ音乐 86 CloudMusic // 网易云音乐 87 MiguMusic // 咪咕音乐 88 KugouMusic // 酷狗音乐 89 KuwoMusic // 酷我音乐 90 ) 91 92 //go:generate stringer -type ElementType -linecomment 93 const ( 94 Text ElementType = iota // 文本 95 Image // 图片 96 Face // 表情 97 At // 艾特 98 Reply // 回复 99 Service // 服务 100 Forward // 转发 101 File // 文件 102 Voice // 语音 103 Video // 视频 104 LightApp // 轻应用 105 RedBag // 红包 106 ) 107 108 func (s *Sender) IsAnonymous() bool { 109 return s.Uin == 80000000 110 } 111 112 func NewSendingMessage() *SendingMessage { 113 return &SendingMessage{} 114 } 115 116 func (msg *PrivateMessage) ToString() (res string) { 117 for _, elem := range msg.GetElements() { 118 switch e := elem.(type) { 119 case *TextElement: 120 res += e.Content 121 case *FaceElement: 122 res += "[" + e.Name + "]" 123 case *AtElement: 124 res += e.Display 125 } 126 } 127 return 128 } 129 130 func (msg *PrivateMessage) Chat() int64 { 131 return msg.Sender.Uin 132 } 133 134 func (msg *PrivateMessage) GetElements() []IMessageElement { 135 return msg.Elements 136 } 137 138 func (msg *PrivateMessage) Texts() []string { 139 return parseTexts(msg.GetElements()) 140 } 141 142 func (msg *GroupMessage) Chat() int64 { 143 return msg.GroupCode 144 } 145 146 func (msg *GroupMessage) GetElements() []IMessageElement { 147 return msg.Elements 148 } 149 150 func (msg *TempMessage) Chat() int64 { 151 return msg.GroupCode 152 } 153 154 func (msg *TempMessage) GetElements() []IMessageElement { 155 return msg.Elements 156 } 157 158 func (msg *TempMessage) Texts() []string { 159 return parseTexts(msg.GetElements()) 160 } 161 162 func (msg *TempMessage) ToString() (res string) { 163 var strBuilder strings.Builder 164 for _, elem := range msg.Elements { 165 switch e := elem.(type) { 166 case *TextElement: 167 strBuilder.WriteString(e.Content) 168 case *FaceElement: 169 strBuilder.WriteString("[") 170 strBuilder.WriteString(e.Name) 171 strBuilder.WriteString("]") 172 case *AtElement: 173 strBuilder.WriteString(e.Display) 174 } 175 } 176 res = strBuilder.String() 177 return 178 } 179 180 func (msg *GroupMessage) ToString() (res string) { 181 var strBuilder strings.Builder 182 for _, elem := range msg.GetElements() { 183 switch e := elem.(type) { 184 case *TextElement: 185 strBuilder.WriteString(e.Content) 186 case *FaceElement: 187 strBuilder.WriteString("[") 188 strBuilder.WriteString(e.Name) 189 strBuilder.WriteString("]") 190 case *MarketFaceElement: 191 strBuilder.WriteString("[") 192 strBuilder.WriteString(e.Name) 193 strBuilder.WriteString("]") 194 case *GroupImageElement: 195 strBuilder.WriteString("Image: ") 196 strBuilder.WriteString(e.ImageId) 197 strBuilder.WriteString("]") 198 case *AtElement: 199 strBuilder.WriteString(e.Display) 200 case *RedBagElement: 201 strBuilder.WriteString("[RedBag: ") 202 strBuilder.WriteString(e.Title) 203 strBuilder.WriteString("]") 204 case *ReplyElement: 205 strBuilder.WriteString("[Reply: ") 206 strBuilder.WriteString(strconv.FormatInt(int64(e.ReplySeq), 10)) 207 strBuilder.WriteString("]") 208 } 209 } 210 res = strBuilder.String() 211 return 212 } 213 214 func (msg *GroupMessage) Texts() []string { 215 return parseTexts(msg.GetElements()) 216 } 217 218 func parseTexts(elements []IMessageElement) []string { 219 texts := make([]string, 0, 4) 220 for _, elem := range elements { 221 if elem.Type() == Text { 222 texts = append(texts, elem.(*TextElement).Content) 223 } 224 } 225 return texts 226 } 227 228 func (msg *SendingMessage) Append(e IMessageElement) *SendingMessage { 229 v := reflect.ValueOf(e) 230 if v.Kind() == reflect.Ptr && !v.IsNil() { 231 msg.Elements = append(msg.Elements, e) 232 } 233 return msg 234 } 235 236 func (msg *SendingMessage) Any(filter func(e IMessageElement) bool) bool { 237 for _, e := range msg.Elements { 238 if filter(e) { 239 return true 240 } 241 } 242 return false 243 } 244 245 func (msg *SendingMessage) FirstOrNil(filter func(e IMessageElement) bool) IMessageElement { 246 for _, e := range msg.Elements { 247 if filter(e) { 248 return e 249 } 250 } 251 return nil 252 } 253 254 func (msg *SendingMessage) Count(filter func(e IMessageElement) bool) (c int) { 255 for _, e := range msg.Elements { 256 if filter(e) { 257 c++ 258 } 259 } 260 return 261 } 262 263 func (msg *SendingMessage) ToFragmented() [][]IMessageElement { 264 var fragmented [][]IMessageElement 265 for _, elem := range msg.Elements { 266 switch o := elem.(type) { 267 case *TextElement: 268 for _, text := range utils.ChunkString(o.Content, 80) { 269 fragmented = append(fragmented, []IMessageElement{NewText(text)}) 270 } 271 default: 272 fragmented = append(fragmented, []IMessageElement{o}) 273 } 274 } 275 return fragmented 276 } 277 278 // 单条消息发送的大小限制(预估) 279 const MaxMessageSize = 5000 280 281 func EstimateLength(elems []IMessageElement) int { 282 sum := 0 283 for _, elem := range elems { 284 switch e := elem.(type) { 285 case *TextElement: 286 sum += len(e.Content) 287 case *AtElement: 288 sum += len(e.Display) 289 case *ReplyElement: 290 sum += 444 + EstimateLength(e.Elements) 291 case *GroupImageElement, *FriendImageElement: 292 sum += 100 293 default: 294 sum += len(ToReadableString([]IMessageElement{elem})) 295 } 296 } 297 return sum 298 } 299 300 func (s *Sender) DisplayName() string { 301 if s.CardName == "" { 302 return s.Nickname 303 } 304 return s.CardName 305 } 306 307 func ToProtoElems(elems []IMessageElement, generalFlags bool) (r []*msg.Elem) { 308 if len(elems) == 0 { 309 return nil 310 } 311 for _, elem := range elems { 312 if reply, ok := elem.(*ReplyElement); ok { 313 r = append(r, &msg.Elem{ 314 SrcMsg: &msg.SourceMsg{ 315 OrigSeqs: []int32{reply.ReplySeq}, 316 SenderUin: proto.Some(reply.Sender), 317 Time: proto.Some(reply.Time), 318 Flag: proto.Int32(1), 319 Elems: ToSrcProtoElems(reply.Elements), 320 RichMsg: []byte{}, 321 PbReserve: []byte{}, 322 SrcMsg: []byte{}, 323 TroopName: []byte{}, 324 }, 325 }) 326 if len(elems) > 1 { 327 if elems[0].Type() == Image || elems[1].Type() == Image { 328 r = append(r, &msg.Elem{Text: &msg.Text{Str: proto.String(" ")}}) 329 } 330 } 331 } 332 } 333 for _, elem := range elems { 334 if e, ok := elem.(*ShortVideoElement); ok { 335 r = e.Pack() 336 break 337 } 338 if e, ok := elem.(IRichMessageElement); ok { 339 r = append(r, e.Pack()...) 340 } 341 } 342 if generalFlags { 343 L: 344 for _, elem := range elems { 345 switch e := elem.(type) { 346 case *ServiceElement: 347 if e.SubType == "Long" { 348 r = append(r, &msg.Elem{ 349 GeneralFlags: &msg.GeneralFlags{ 350 LongTextFlag: proto.Int32(1), 351 LongTextResid: proto.Some(e.ResId), 352 PbReserve: []byte{0x78, 0x00, 0xF8, 0x01, 0x00, 0xC8, 0x02, 0x00}, 353 }, 354 }) 355 break L 356 } 357 // d, _ := hex.DecodeString("08097800C80100F00100F80100900200C80200980300A00320B00300C00300D00300E803008A04020803900480808010B80400C00400") 358 r = append(r, &msg.Elem{ 359 GeneralFlags: &msg.GeneralFlags{ 360 PbReserve: []byte{ 361 0x08, 0x09, 0x78, 0x00, 0xC8, 0x01, 0x00, 0xF0, 0x01, 0x00, 0xF8, 0x01, 0x00, 0x90, 0x02, 0x00, 362 0xC8, 0x02, 0x00, 0x98, 0x03, 0x00, 0xA0, 0x03, 0x20, 0xB0, 0x03, 0x00, 0xC0, 0x03, 0x00, 0xD0, 363 0x03, 0x00, 0xE8, 0x03, 0x00, 0x8A, 0x04, 0x02, 0x08, 0x03, 0x90, 0x04, 0x80, 0x80, 0x80, 0x10, 364 0xB8, 0x04, 0x00, 0xC0, 0x04, 0x00, 365 }, 366 }, 367 }) 368 break L 369 } 370 } 371 } 372 return 373 } 374 375 var photoTextElem IMessageElement = NewText("[图片]") 376 377 func ToSrcProtoElems(elems []IMessageElement) []*msg.Elem { 378 elems2 := make([]IMessageElement, len(elems)) 379 copy(elems2, elems) 380 for i, elem := range elems2 { 381 if elem.Type() == Image { 382 elems2[i] = photoTextElem 383 } 384 } 385 return ToProtoElems(elems2, false) 386 } 387 388 func ParseMessageElems(elems []*msg.Elem) []IMessageElement { 389 var res []IMessageElement 390 var newImg = false 391 for _, elem := range elems { 392 if elem.SrcMsg != nil && len(elem.SrcMsg.OrigSeqs) != 0 { 393 r := &ReplyElement{ 394 ReplySeq: elem.SrcMsg.OrigSeqs[0], 395 Time: elem.SrcMsg.Time.Unwrap(), 396 Sender: elem.SrcMsg.SenderUin.Unwrap(), 397 GroupID: elem.SrcMsg.ToUin.Unwrap(), 398 Elements: ParseMessageElems(elem.SrcMsg.Elems), 399 } 400 res = append(res, r) 401 } 402 if elem.TransElemInfo != nil { 403 if elem.TransElemInfo.ElemType.Unwrap() == 24 { // QFile 404 i3 := len(elem.TransElemInfo.ElemValue) 405 r := binary.NewReader(elem.TransElemInfo.ElemValue) 406 if i3 > 3 { 407 if r.ReadByte() == 1 { 408 pb := r.ReadBytes(int(r.ReadUInt16())) 409 objMsg := msg.ObjMsg{} 410 if err := proto.Unmarshal(pb, &objMsg); err == nil && len(objMsg.MsgContentInfo) > 0 { 411 info := objMsg.MsgContentInfo[0] 412 res = append(res, &GroupFileElement{ 413 Name: info.MsgFile.FileName, 414 Size: info.MsgFile.FileSize, 415 Path: string(info.MsgFile.FilePath), 416 Busid: info.MsgFile.BusId, 417 }) 418 } 419 } 420 } 421 } 422 } 423 if elem.LightApp != nil && len(elem.LightApp.Data) > 1 { 424 var content []byte 425 if elem.LightApp.Data[0] == 0 { 426 content = elem.LightApp.Data[1:] 427 } 428 if elem.LightApp.Data[0] == 1 { 429 content = binary.ZlibUncompress(elem.LightApp.Data[1:]) 430 } 431 if len(content) > 0 && len(content) < 1024*1024*1024 { // 解析出错 or 非法内容 432 // TODO: 解析具体的APP 433 return append(res, &LightAppElement{Content: string(content)}) 434 } 435 } 436 if elem.VideoFile != nil { 437 return []IMessageElement{ 438 &ShortVideoElement{ 439 Name: string(elem.VideoFile.FileName), 440 Uuid: elem.VideoFile.FileUuid, 441 Size: elem.VideoFile.FileSize.Unwrap(), 442 ThumbSize: elem.VideoFile.ThumbFileSize.Unwrap(), 443 Md5: elem.VideoFile.FileMd5, 444 ThumbMd5: elem.VideoFile.ThumbFileMd5, 445 }, 446 } 447 } 448 if elem.Text != nil { 449 switch { 450 case len(elem.Text.Attr6Buf) > 0: 451 att6 := binary.NewReader(elem.Text.Attr6Buf) 452 att6.ReadBytes(7) 453 target := int64(uint32(att6.ReadInt32())) 454 at := NewAt(target, elem.Text.Str.Unwrap()) 455 at.SubType = AtTypeGroupMember 456 res = append(res, at) 457 case len(elem.Text.PbReserve) > 0: 458 resv := new(msg.TextResvAttr) 459 _ = proto.Unmarshal(elem.Text.PbReserve, resv) 460 if resv.AtType.Unwrap() == 2 { 461 at := NewAt(int64(resv.AtMemberTinyid.Unwrap()), elem.Text.Str.Unwrap()) 462 at.SubType = AtTypeGuildMember 463 res = append(res, at) 464 break 465 } 466 if resv.AtType.Unwrap() == 4 { 467 at := NewAt(int64(resv.AtChannelInfo.ChannelId.Unwrap()), elem.Text.Str.Unwrap()) 468 at.SubType = AtTypeGuildChannel 469 res = append(res, at) 470 break 471 } 472 fallthrough 473 default: 474 res = append(res, NewText(func() string { 475 // 这么处理应该没问题 476 if strings.Contains(elem.Text.Str.Unwrap(), "\r") && !strings.Contains(elem.Text.Str.Unwrap(), "\r\n") { 477 return strings.ReplaceAll(elem.Text.Str.Unwrap(), "\r", "\r\n") 478 } 479 return elem.Text.Str.Unwrap() 480 }())) 481 } 482 } 483 if elem.RichMsg != nil { 484 var content string 485 if elem.RichMsg.Template1[0] == 0 { 486 content = string(elem.RichMsg.Template1[1:]) 487 } 488 if elem.RichMsg.Template1[0] == 1 { 489 content = string(binary.ZlibUncompress(elem.RichMsg.Template1[1:])) 490 } 491 if content != "" { 492 if elem.RichMsg.ServiceId.Unwrap() == 35 { 493 elem := forwardMsgFromXML(content) 494 if elem != nil { 495 res = append(res, elem) 496 continue 497 } 498 } 499 if elem.RichMsg.ServiceId.Unwrap() == 33 { 500 continue // 前面一个 elem 已经解析到链接 501 } 502 if isOk := strings.Contains(content, "<?xml"); isOk { 503 res = append(res, NewRichXml(content, int64(elem.RichMsg.ServiceId.Unwrap()))) 504 continue 505 } else if json.Valid(utils.S2B(content)) { 506 res = append(res, NewRichJson(content)) 507 continue 508 } 509 res = append(res, NewText(content)) 510 } 511 } 512 if elem.CustomFace != nil { 513 if len(elem.CustomFace.Md5) == 0 { 514 continue 515 } 516 var url string 517 if elem.CustomFace.OrigUrl.Unwrap() == "" { 518 url = fmt.Sprintf("https://gchat.qpic.cn/gchatpic_new/0/0-0-%X/0?term=2", elem.CustomFace.Md5) 519 } else { 520 url = "https://gchat.qpic.cn" + elem.CustomFace.OrigUrl.Unwrap() 521 } 522 if strings.Contains(elem.CustomFace.OrigUrl.Unwrap(), "qmeet") { 523 res = append(res, &GuildImageElement{ 524 FileId: int64(elem.CustomFace.FileId.Unwrap()), 525 FilePath: elem.CustomFace.FilePath.Unwrap(), 526 Size: elem.CustomFace.Size.Unwrap(), 527 Width: elem.CustomFace.Width.Unwrap(), 528 Height: elem.CustomFace.Height.Unwrap(), 529 Url: url, 530 Md5: elem.CustomFace.Md5, 531 }) 532 continue 533 } 534 bizType := UnknownBizType 535 if len(elem.CustomFace.PbReserve) != 0 { 536 attr := new(msg.ResvAttr) 537 if proto.Unmarshal(elem.CustomFace.PbReserve, attr) == nil { 538 bizType = ImageBizType(attr.ImageBizType.Unwrap()) 539 } 540 } 541 if !newImg { 542 res = append(res, &GroupImageElement{ 543 FileId: int64(elem.CustomFace.FileId.Unwrap()), 544 ImageId: elem.CustomFace.FilePath.Unwrap(), 545 Size: elem.CustomFace.Size.Unwrap(), 546 Width: elem.CustomFace.Width.Unwrap(), 547 Height: elem.CustomFace.Height.Unwrap(), 548 Url: url, 549 ImageBizType: bizType, 550 Md5: elem.CustomFace.Md5, 551 }) 552 } 553 } 554 if elem.MarketFace != nil { 555 face := &MarketFaceElement{ 556 Name: utils.B2S(elem.MarketFace.FaceName), 557 FaceId: elem.MarketFace.FaceId, 558 TabId: int32(elem.MarketFace.TabId.Unwrap()), 559 ItemType: int32(elem.MarketFace.ItemType.Unwrap()), 560 SubType: int32(elem.MarketFace.SubType.Unwrap()), 561 MediaType: int32(elem.MarketFace.MediaType.Unwrap()), 562 EncryptKey: elem.MarketFace.Key, 563 MagicValue: utils.B2S(elem.MarketFace.Mobileparam), 564 } 565 if face.Name == "[骰子]" || face.Name == "[随机骰子]" { 566 _, v, _ := strings.Cut(face.MagicValue, "=") 567 t, _ := strconv.ParseInt(v, 10, 32) 568 return []IMessageElement{ 569 &DiceElement{ 570 MarketFaceElement: face, 571 Value: int32(t) + 1, 572 }, 573 } 574 } 575 if face.Name == "[猜拳]" { 576 _, v, _ := strings.Cut(face.MagicValue, "=") 577 t, _ := strconv.ParseInt(v, 10, 32) 578 return []IMessageElement{ 579 &FingerGuessingElement{ 580 MarketFaceElement: face, 581 Value: int32(t), 582 Name: fingerGuessingName[int32(t)], 583 }, 584 } 585 } 586 return []IMessageElement{face} 587 } 588 589 if elem.NotOnlineImage != nil { 590 img := elem.NotOnlineImage 591 592 var url string 593 switch { 594 case img.PbReserve != nil && img.PbReserve.Url.Unwrap() != "": 595 url = fmt.Sprintf("https://c2cpicdw.qpic.cn%s&spec=0&rf=naio", img.PbReserve.Url.Unwrap()) 596 case img.OrigUrl.Unwrap() != "": 597 url = "https://c2cpicdw.qpic.cn" + img.OrigUrl.Unwrap() 598 default: 599 url = "https://c2cpicdw.qpic.cn/offpic_new/0" 600 downloadPath := img.ResId.Unwrap() 601 if img.DownloadPath.Unwrap() != "" { 602 downloadPath = img.DownloadPath.Unwrap() 603 } 604 if !strings.HasPrefix(downloadPath, "/") { 605 url += "/" 606 } 607 url += downloadPath + "/0?term=3" 608 } 609 610 if !newImg { 611 res = append(res, &FriendImageElement{ 612 ImageId: img.FilePath.Unwrap(), 613 Size: img.FileLen.Unwrap(), 614 Url: url, 615 Md5: img.PicMd5, 616 }) 617 } 618 619 } 620 621 if elem.QQWalletMsg != nil && elem.QQWalletMsg.AioBody != nil { 622 // /com/tencent/mobileqq/data/MessageForQQWalletMsg.java#L366 623 msgType := elem.QQWalletMsg.AioBody.MsgType.Unwrap() 624 if msgType <= 1000 && elem.QQWalletMsg.AioBody.RedType.IsSome() { 625 return []IMessageElement{ 626 &RedBagElement{ 627 MsgType: RedBagMessageType(msgType), 628 Title: elem.QQWalletMsg.AioBody.Receiver.Title.Unwrap(), 629 }, 630 } 631 } 632 } 633 if elem.Face != nil { 634 res = append(res, NewFace(elem.Face.Index.Unwrap())) 635 } 636 if elem.CommonElem != nil { 637 switch elem.CommonElem.ServiceType.Unwrap() { 638 case 3: 639 flash := &msg.MsgElemInfoServtype3{} 640 _ = proto.Unmarshal(elem.CommonElem.PbElem, flash) 641 if flash.FlashTroopPic != nil { 642 res = append(res, &GroupImageElement{ 643 FileId: int64(flash.FlashTroopPic.FileId.Unwrap()), 644 ImageId: flash.FlashTroopPic.FilePath.Unwrap(), 645 Size: flash.FlashTroopPic.Size.Unwrap(), 646 Width: flash.FlashTroopPic.Width.Unwrap(), 647 Height: flash.FlashTroopPic.Height.Unwrap(), 648 Md5: flash.FlashTroopPic.Md5, 649 Flash: true, 650 }) 651 return res 652 } 653 if flash.FlashC2CPic != nil { 654 res = append(res, &FriendImageElement{ 655 ImageId: flash.FlashC2CPic.FilePath.Unwrap(), 656 Size: flash.FlashC2CPic.FileLen.Unwrap(), 657 Md5: flash.FlashC2CPic.PicMd5, 658 Flash: true, 659 }) 660 return res 661 } 662 case 33: 663 newSysFaceMsg := &msg.MsgElemInfoServtype33{} 664 _ = proto.Unmarshal(elem.CommonElem.PbElem, newSysFaceMsg) 665 res = append(res, NewFace(int32(newSysFaceMsg.Index.Unwrap()))) 666 case 37: 667 animatedStickerMsg := &msg.MsgElemInfoServtype37{} 668 _ = proto.Unmarshal(elem.CommonElem.PbElem, animatedStickerMsg) 669 sticker := &AnimatedSticker{ 670 ID: int32(animatedStickerMsg.Qsid.Unwrap()), 671 Name: strings.TrimPrefix(string(animatedStickerMsg.Text), "/"), 672 } 673 return []IMessageElement{sticker} // sticker 永远为单独消息 674 case 48: 675 img := &msg.PbMultiMediaElement{} 676 _ = proto.Unmarshal(elem.CommonElem.PbElem, img) 677 domain := img.Elem1.Data.Domain.Unwrap() 678 imgURL := img.Elem1.Data.ImgURL.Unwrap() 679 680 if img.Elem2.Data.Friend != nil { 681 rKey := img.Elem2.Data.Friend.RKey.Unwrap() 682 url := fmt.Sprintf("https://%s%s%s&spec=0&rf=naio", domain, imgURL, rKey) 683 res = append(res, &FriendImageElement{ 684 ImageId: img.Elem1.Meta.FilePath.Unwrap(), 685 Size: img.Elem1.Meta.Data.FileLen.Unwrap(), 686 Url: url, 687 Md5: img.Elem1.Meta.Data.PicMd5, 688 }) 689 newImg = true 690 } 691 if img.Elem2.Data.Group != nil { 692 rKey := img.Elem2.Data.Group.RKey.Unwrap() 693 url := fmt.Sprintf("https://%s%s%s&spec=0&rf=naio", domain, imgURL, rKey) 694 res = append(res, &GroupImageElement{ 695 ImageId: img.Elem1.Meta.FilePath.Unwrap(), 696 Size: img.Elem1.Meta.Data.FileLen.Unwrap(), 697 Url: url, 698 Md5: img.Elem1.Meta.Data.PicMd5, 699 }) 700 newImg = true 701 } 702 } 703 704 } 705 } 706 return res 707 } 708 709 func ToReadableString(m []IMessageElement) string { 710 sb := new(strings.Builder) 711 for _, elem := range m { 712 switch e := elem.(type) { 713 case *TextElement: 714 sb.WriteString(e.Content) 715 case *GroupImageElement, *FriendImageElement: 716 sb.WriteString("[图片]") 717 case *FaceElement: 718 sb.WriteByte('/') 719 sb.WriteString(e.Name) 720 case *ForwardElement: 721 sb.WriteString("[聊天记录]") 722 // NOTE: flash pic is singular 723 // To be clarified 724 // case *GroupFlashImgElement: 725 // return "[闪照]" 726 // case *FriendFlashImgElement: 727 // return "[闪照]" 728 case *AtElement: 729 sb.WriteString(e.Display) 730 } 731 } 732 return sb.String() 733 } 734 735 func FaceNameById(id int) string { 736 if name, ok := faceMap[id]; ok { 737 return name 738 } 739 return "未知表情" 740 } 741 742 // SplitLongMessage 将过长的消息分割为若干个适合发送的消息 743 func SplitLongMessage(sendingMessage *SendingMessage) []*SendingMessage { 744 // 合并连续文本消息 745 sendingMessage = mergeContinuousTextMessages(sendingMessage) 746 747 // 分割过长元素 748 sendingMessage = splitElements(sendingMessage) 749 750 // 将元素分为多组,确保各组不超过单条消息的上限 751 splitMessages := splitMessages(sendingMessage) 752 753 return splitMessages 754 } 755 756 // mergeContinuousTextMessages 预先将所有连续的文本消息合并为到一起,方便后续统一切割 757 func mergeContinuousTextMessages(sendingMessage *SendingMessage) *SendingMessage { 758 // 检查下是否有连续的文本消息,若没有,则可以直接返回 759 lastIsText := false 760 hasContinuousText := false 761 for _, message := range sendingMessage.Elements { 762 if message.Type() == Text { 763 if lastIsText { 764 // 有连续的文本消息,需要进行处理 765 hasContinuousText = true 766 break 767 } 768 769 // 遇到文本元素先存放起来,方便将连续的文本元素合并 770 lastIsText = true 771 continue 772 } else { 773 lastIsText = false 774 } 775 } 776 if !hasContinuousText { 777 return sendingMessage 778 } 779 780 // 存在连续的文本消息,需要进行合并处理 781 textBuffer := strings.Builder{} 782 lastIsText = false 783 totalMessageCount := 0 784 for _, message := range sendingMessage.Elements { 785 if msgVal, ok := message.(*TextElement); ok { 786 // 遇到文本元素先存放起来,方便将连续的文本元素合并 787 textBuffer.WriteString(msgVal.Content) 788 lastIsText = true 789 continue 790 } 791 792 // 如果之前的是文本元素(可能是多个合并起来的),则在这里将其实际放入消息中 793 if lastIsText { 794 sendingMessage.Elements[totalMessageCount] = NewText(textBuffer.String()) 795 totalMessageCount += 1 796 textBuffer.Reset() 797 } 798 lastIsText = false 799 800 // 非文本元素则直接处理 801 sendingMessage.Elements[totalMessageCount] = message 802 totalMessageCount += 1 803 } 804 // 处理最后几个元素是文本的情况 805 if textBuffer.Len() != 0 { 806 sendingMessage.Elements[totalMessageCount] = NewText(textBuffer.String()) 807 totalMessageCount += 1 808 textBuffer.Reset() 809 } 810 sendingMessage.Elements = sendingMessage.Elements[:totalMessageCount] 811 812 return sendingMessage 813 } 814 815 // splitElements 将原有消息的各个元素先尝试处理,如过长的文本消息按需分割为多个元素 816 func splitElements(sendingMessage *SendingMessage) *SendingMessage { 817 // 检查下是否存在需要文本消息,若不存在,则直接返回 818 needSplit := false 819 for _, message := range sendingMessage.Elements { 820 if msgVal, ok := message.(*TextElement); ok { 821 if textNeedSplit(msgVal.Content) { 822 needSplit = true 823 break 824 } 825 } 826 } 827 if !needSplit { 828 return sendingMessage 829 } 830 831 // 开始尝试切割 832 messageParts := NewSendingMessage() 833 834 for _, message := range sendingMessage.Elements { 835 switch msgVal := message.(type) { 836 case *TextElement: 837 messageParts.Elements = append(messageParts.Elements, splitPlainMessage(msgVal.Content)...) 838 default: 839 messageParts.Append(message) 840 } 841 } 842 843 return messageParts 844 } 845 846 // splitMessages 根据大小分为多个消息进行发送 847 func splitMessages(sendingMessage *SendingMessage) []*SendingMessage { 848 var splitMessages []*SendingMessage 849 850 messagePart := NewSendingMessage() 851 msgSize := 0 852 for _, part := range sendingMessage.Elements { 853 estimateSize := EstimateLength([]IMessageElement{part}) 854 // 若当前分消息加上新的元素后大小会超限,且已经有元素(确保不会无限循环),则开始切分为新的一个元素 855 if msgSize+estimateSize > MaxMessageSize && len(messagePart.Elements) > 0 { 856 splitMessages = append(splitMessages, messagePart) 857 858 messagePart = NewSendingMessage() 859 msgSize = 0 860 } 861 862 // 加上新的元素 863 messagePart.Append(part) 864 msgSize += estimateSize 865 } 866 // 将最后一个分片加上 867 if len(messagePart.Elements) != 0 { 868 splitMessages = append(splitMessages, messagePart) 869 } 870 871 return splitMessages 872 } 873 874 func splitPlainMessage(content string) []IMessageElement { 875 if !textNeedSplit(content) { 876 return []IMessageElement{NewText(content)} 877 } 878 879 splittedMessage := make([]IMessageElement, 0, (len(content)+MaxMessageSize-1)/MaxMessageSize) 880 881 last := 0 882 for runeIndex, runeValue := range content { 883 // 如果加上新的这个字符后,会超出大小,则从这个字符前分一次片 884 if runeIndex+len(string(runeValue))-last > MaxMessageSize { 885 splittedMessage = append(splittedMessage, NewText(content[last:runeIndex])) 886 last = runeIndex 887 } 888 } 889 if last != len(content) { 890 splittedMessage = append(splittedMessage, NewText(content[last:])) 891 } 892 893 return splittedMessage 894 } 895 896 func textNeedSplit(content string) bool { 897 return len(content) > MaxMessageSize 898 }