1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 11:07:40 +08:00
MiraiGo/client/group_msg.go
2021-08-16 20:45:47 +08:00

629 lines
20 KiB
Go

package client
import (
"bytes"
"encoding/base64"
"fmt"
"math"
"math/rand"
"strconv"
"strings"
"time"
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/pkg/errors"
"google.golang.org/protobuf/proto"
)
func init() {
decoders["OnlinePush.PbPushGroupMsg"] = decodeGroupMessagePacket
decoders["MessageSvc.PbSendMsg"] = decodeMsgSendResponse
decoders["MessageSvc.PbGetGroupMsg"] = decodeGetGroupMsgResponse
decoders["OidbSvc.0x8a7_0"] = decodeAtAllRemainResponse
decoders["OidbSvc.0xeac_1"] = decodeEssenceMsgResponse
decoders["OidbSvc.0xeac_2"] = decodeEssenceMsgResponse
}
// SendGroupMessage 发送群消息
func (c *QQClient) SendGroupMessage(groupCode int64, m *message.SendingMessage, f ...bool) *message.GroupMessage {
useFram := false
if len(f) > 0 {
useFram = f[0]
}
imgCount := 0
for _, e := range m.Elements {
switch e.Type() {
case message.Image:
imgCount++
case message.Reply:
useFram = false
}
}
msgLen := message.EstimateLength(m.Elements)
if msgLen > 5000 || imgCount > 50 {
return nil
}
if !useFram && (msgLen > 100 || imgCount > 2) {
ret := c.sendGroupMessage(groupCode, false,
&message.SendingMessage{Elements: []message.IMessageElement{
c.uploadGroupLongMessage(groupCode,
message.NewForwardMessage().AddNode(&message.ForwardNode{
SenderId: c.Uin,
SenderName: c.Nickname,
Time: int32(time.Now().Unix()),
Message: m.Elements,
}),
),
}},
)
ret.Elements = m.Elements
return ret
}
return c.sendGroupMessage(groupCode, false, m)
}
// SendGroupForwardMessage 发送群合并转发消息
func (c *QQClient) SendGroupForwardMessage(groupCode int64, m *message.ForwardElement) *message.GroupMessage {
return c.sendGroupMessage(groupCode, true,
&message.SendingMessage{Elements: []message.IMessageElement{m}},
)
}
// GetGroupMessages 从服务器获取历史信息
func (c *QQClient) GetGroupMessages(groupCode, beginSeq, endSeq int64) ([]*message.GroupMessage, error) {
seq, pkt := c.buildGetGroupMsgRequest(groupCode, beginSeq, endSeq)
i, err := c.sendAndWait(seq, pkt, requestParams{"raw": false})
if err != nil {
return nil, err
}
return i.([]*message.GroupMessage), nil
}
func (c *QQClient) GetAtAllRemain(groupCode int64) (*AtAllRemainInfo, error) {
i, err := c.sendAndWait(c.buildAtAllRemainRequestPacket(groupCode))
if err != nil {
return nil, err
}
return i.(*AtAllRemainInfo), nil
}
func (c *QQClient) sendGroupMessage(groupCode int64, forward bool, m *message.SendingMessage) *message.GroupMessage {
eid := utils.RandomString(6)
mr := int32(rand.Uint32())
ch := make(chan int32, 1)
c.onGroupMessageReceipt(eid, func(c *QQClient, e *groupMessageReceiptEvent) {
if e.Rand == mr {
ch <- e.Seq
}
})
defer c.onGroupMessageReceipt(eid)
imgCount := 0
serviceFlag := true
for _, e := range m.Elements {
switch e.Type() {
case message.Image:
imgCount++
case message.Forward:
forward = true
fallthrough
case message.Reply, message.Voice, message.Service:
serviceFlag = false
}
}
if !forward && serviceFlag && (imgCount > 1 || message.EstimateLength(m.Elements) > 100) {
div := int32(rand.Uint32())
fragmented := m.ToFragmented()
for i, elems := range fragmented {
_, pkt := c.buildGroupSendingPacket(groupCode, mr, int32(len(fragmented)), int32(i), div, forward, elems)
_ = c.sendPacket(pkt)
}
} else {
_, pkt := c.buildGroupSendingPacket(groupCode, mr, 1, 0, 0, forward, m.Elements)
_ = c.sendPacket(pkt)
}
var mid int32
ret := &message.GroupMessage{
Id: -1,
InternalId: mr,
GroupCode: groupCode,
Sender: &message.Sender{
Uin: c.Uin,
Nickname: c.Nickname,
IsFriend: true,
},
Time: int32(time.Now().Unix()),
Elements: m.Elements,
}
select {
case mid = <-ch:
ret.Id = mid
return ret
case <-time.After(time.Second * 5):
if g, err := c.GetGroupInfo(groupCode); err == nil {
if history, err := c.GetGroupMessages(groupCode, g.LastMsgSeq-10, g.LastMsgSeq+1); err == nil {
for _, m := range history {
if m.InternalId == mr {
return m
}
}
}
}
return ret
}
}
func (c *QQClient) uploadGroupLongMessage(groupCode int64, m *message.ForwardMessage) *message.ServiceElement {
if m.Length() >= 200 {
return nil
}
ts := time.Now().UnixNano()
seq := c.nextGroupSeq()
data, hash := m.CalculateValidationData(seq, rand.Int31(), groupCode)
rsp, body, err := c.multiMsgApplyUp(groupCode, data, hash, 1)
if err != nil {
c.Error("upload long message error: %v", err)
return nil
}
for i, ip := range rsp.Uint32UpIp {
err := c.highwayUpload(uint32(ip), int(rsp.Uint32UpPort[i]), rsp.MsgSig, body, 27)
if err != nil {
continue
}
return genLongTemplate(rsp.MsgResid, m.Brief(), ts)
}
c.Error("upload long message error: highway server list is empty")
return nil
}
func (c *QQClient) UploadGroupForwardMessage(groupCode int64, m *message.ForwardMessage) *message.ForwardElement {
if m.Length() > 200 {
return nil
}
ts := time.Now().UnixNano()
seq := c.nextGroupSeq()
data, hash, items := m.CalculateValidationDataForward(seq, rand.Int31(), groupCode)
rsp, body, err := c.multiMsgApplyUp(groupCode, data, hash, 2)
if err != nil {
return nil
}
for i, ip := range rsp.Uint32UpIp {
err := c.highwayUpload(uint32(ip), int(rsp.Uint32UpPort[i]), rsp.MsgSig, body, 27)
if err != nil {
continue
}
return genForwardTemplate(rsp.MsgResid, m.Preview(), "群聊的聊天记录", "[聊天记录]", "聊天记录", fmt.Sprintf("查看 %d 条转发消息", m.Length()), ts, items)
}
return nil
}
func (c *QQClient) multiMsgApplyUp(groupCode int64, data []byte, hash []byte, buType int32) (*multimsg.MultiMsgApplyUpRsp, []byte, error) {
i, err := c.sendAndWait(c.buildMultiApplyUpPacket(data, hash, buType, utils.ToGroupUin(groupCode)))
if err != nil {
return nil, nil, err
}
rsp := i.(*multimsg.MultiMsgApplyUpRsp)
body, _ := proto.Marshal(&longmsg.LongReqBody{
Subcmd: 1,
TermType: 5,
PlatformType: 9,
MsgUpReq: []*longmsg.LongMsgUpReq{
{
MsgType: 3,
DstUin: utils.ToGroupUin(groupCode),
MsgContent: data,
StoreType: 2,
MsgUkey: rsp.MsgUkey,
},
},
})
return rsp, body, nil
}
// MessageSvc.PbSendMsg
func (c *QQClient) buildGroupSendingPacket(groupCode int64, r, pkgNum, pkgIndex, pkgDiv int32, forward bool, m []message.IMessageElement) (uint16, []byte) {
seq := c.nextSeq()
var ptt *message.GroupVoiceElement
if len(m) > 0 {
if p, ok := m[0].(*message.GroupVoiceElement); ok {
ptt = p
m = []message.IMessageElement{}
}
}
req := &msg.SendMessageRequest{
RoutingHead: &msg.RoutingHead{Grp: &msg.Grp{GroupCode: &groupCode}},
ContentHead: &msg.ContentHead{PkgNum: &pkgNum, PkgIndex: &pkgIndex, DivSeq: &pkgDiv},
MsgBody: &msg.MessageBody{
RichText: &msg.RichText{
Elems: message.ToProtoElems(m, true),
Ptt: func() *msg.Ptt {
if ptt != nil {
return ptt.Ptt
}
return nil
}(),
},
},
MsgSeq: proto.Int32(c.nextGroupSeq()),
MsgRand: &r,
SyncCookie: EmptyBytes,
MsgVia: proto.Int32(1),
MsgCtrl: func() *msg.MsgCtrl {
if forward {
return &msg.MsgCtrl{MsgFlag: proto.Int32(4)}
}
return nil
}(),
}
payload, _ := proto.Marshal(req)
packet := packets.BuildUniPacket(c.Uin, seq, "MessageSvc.PbSendMsg", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildGetGroupMsgRequest(groupCode, beginSeq, endSeq int64) (uint16, []byte) {
seq := c.nextSeq()
req := &msg.GetGroupMsgReq{
GroupCode: proto.Uint64(uint64(groupCode)),
BeginSeq: proto.Uint64(uint64(beginSeq)),
EndSeq: proto.Uint64(uint64(endSeq)),
PublicGroup: proto.Bool(false),
}
payload, _ := proto.Marshal(req)
packet := packets.BuildUniPacket(c.Uin, seq, "MessageSvc.PbGetGroupMsg", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
func (c *QQClient) buildAtAllRemainRequestPacket(groupCode int64) (uint16, []byte) {
seq := c.nextSeq()
payload := c.packOIDBPackageProto(2215, 0, &oidb.D8A7ReqBody{
SubCmd: proto.Uint32(1),
LimitIntervalTypeForUin: proto.Uint32(2),
LimitIntervalTypeForGroup: proto.Uint32(1),
Uin: proto.Uint64(uint64(c.Uin)),
GroupCode: proto.Uint64(uint64(groupCode)),
})
packet := packets.BuildUniPacket(c.Uin, seq, "OidbSvc.0x8a7_0", 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
// OnlinePush.PbPushGroupMsg
func decodeGroupMessagePacket(c *QQClient, _ *incomingPacketInfo, payload []byte) (interface{}, error) {
pkt := msg.PushMessagePacket{}
err := proto.Unmarshal(payload, &pkt)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if pkt.Message.Head.GetFromUin() == c.Uin {
c.dispatchGroupMessageReceiptEvent(&groupMessageReceiptEvent{
Rand: pkt.Message.Body.RichText.Attr.GetRandom(),
Seq: pkt.Message.Head.GetMsgSeq(),
Msg: c.parseGroupMessage(pkt.Message),
})
}
if pkt.Message.Content != nil && pkt.Message.Content.GetPkgNum() > 1 {
var builder *groupMessageBuilder
i, ok := c.groupMsgBuilders.Load(pkt.Message.Content.GetDivSeq())
if !ok {
builder = &groupMessageBuilder{}
c.groupMsgBuilders.Store(pkt.Message.Content.GetDivSeq(), builder)
} else {
builder = i.(*groupMessageBuilder)
}
builder.MessageSlices = append(builder.MessageSlices, pkt.Message)
if int32(len(builder.MessageSlices)) >= pkt.Message.Content.GetPkgNum() {
c.groupMsgBuilders.Delete(pkt.Message.Content.GetDivSeq())
if pkt.Message.Head.GetFromUin() == c.Uin {
c.dispatchGroupMessageSelf(c.parseGroupMessage(builder.build()))
} else {
c.dispatchGroupMessage(c.parseGroupMessage(builder.build()))
}
}
return nil, nil
}
if pkt.Message.Head.GetFromUin() == c.Uin {
c.dispatchGroupMessageSelf(c.parseGroupMessage(pkt.Message))
} else {
c.dispatchGroupMessage(c.parseGroupMessage(pkt.Message))
}
return nil, nil
}
func decodeMsgSendResponse(c *QQClient, _ *incomingPacketInfo, payload []byte) (interface{}, error) {
rsp := msg.SendMessageResponse{}
if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
switch rsp.GetResult() {
case 0: // OK.
case 55:
c.Error("sendPacket msg error: %v Bot has blocked target's content", rsp.GetResult())
default:
c.Error("sendPacket msg error: %v %v", rsp.GetResult(), rsp.GetErrMsg())
}
return nil, nil
}
func decodeGetGroupMsgResponse(c *QQClient, info *incomingPacketInfo, payload []byte) (interface{}, error) {
rsp := msg.GetGroupMsgResp{}
if err := proto.Unmarshal(payload, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if rsp.GetResult() != 0 {
c.Error("get msg error: %v %v", rsp.GetResult(), rsp.GetErrmsg())
return nil, errors.Errorf("get msg error: %v msg: %v", rsp.GetResult(), rsp.GetErrmsg())
}
var ret []*message.GroupMessage
for _, m := range rsp.Msg {
if m.Head.FromUin == nil {
continue
}
if m.Content != nil && m.Content.GetPkgNum() > 1 && !info.Params.bool("raw") {
if m.Content.GetPkgIndex() == 0 {
c.Debug("build fragmented message from history")
i := m.Head.GetMsgSeq() - m.Content.GetPkgNum()
builder := &groupMessageBuilder{}
for {
end := int32(math.Min(float64(i+19), float64(m.Head.GetMsgSeq()+m.Content.GetPkgNum())))
seq, pkt := c.buildGetGroupMsgRequest(m.Head.GroupInfo.GetGroupCode(), int64(i), int64(end))
data, err := c.sendAndWait(seq, pkt, requestParams{"raw": true})
if err != nil {
return nil, errors.Wrap(err, "build fragmented message error")
}
for _, fm := range data.([]*message.GroupMessage) {
if fm.OriginalObject.Content != nil && fm.OriginalObject.Content.GetDivSeq() == m.Content.GetDivSeq() {
builder.MessageSlices = append(builder.MessageSlices, fm.OriginalObject)
}
}
if end >= m.Head.GetMsgSeq()+m.Content.GetPkgNum() {
break
}
i = end
}
if elem := c.parseGroupMessage(builder.build()); elem != nil {
ret = append(ret, elem)
}
}
continue
}
if elem := c.parseGroupMessage(m); elem != nil {
ret = append(ret, elem)
}
}
return ret, nil
}
func decodeAtAllRemainResponse(_ *QQClient, _ *incomingPacketInfo, payload []byte) (interface{}, error) {
pkg := oidb.OIDBSSOPkg{}
rsp := oidb.D8A7RspBody{}
if err := proto.Unmarshal(payload, &pkg); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
return &AtAllRemainInfo{
CanAtAll: rsp.GetCanAtAll(),
RemainAtAllCountForGroup: rsp.GetRemainAtAllCountForGroup(),
RemainAtAllCountForUin: rsp.GetRemainAtAllCountForUin(),
}, nil
}
func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage {
group := c.FindGroup(m.Head.GroupInfo.GetGroupCode())
if group == nil {
c.Debug("sync group %v.", m.Head.GroupInfo.GetGroupCode())
info, err := c.GetGroupInfo(m.Head.GroupInfo.GetGroupCode())
if err != nil {
c.Error("error to sync group %v : %+v", m.Head.GroupInfo.GetGroupCode(), err)
return nil
}
group = info
c.GroupList = append(c.GroupList, info)
}
if len(group.Members) == 0 {
mem, err := c.GetGroupMembers(group)
if err != nil {
c.Error("error to sync group %v member : %+v", m.Head.GroupInfo.GroupCode, err)
return nil
}
group.Members = mem
}
var anonInfo *msg.AnonymousGroupMessage
for _, e := range m.Body.RichText.Elems {
if e.AnonGroupMsg != nil {
anonInfo = e.AnonGroupMsg
}
}
var sender *message.Sender
if anonInfo != nil {
sender = &message.Sender{
Uin: 80000000,
Nickname: string(anonInfo.AnonNick),
AnonymousInfo: &message.AnonymousInfo{
AnonymousId: base64.StdEncoding.EncodeToString(anonInfo.AnonId),
AnonymousNick: string(anonInfo.AnonNick),
},
IsFriend: false,
}
} else {
mem := group.FindMember(m.Head.GetFromUin())
if mem == nil {
group.Update(func(_ *GroupInfo) {
if mem = group.FindMemberWithoutLock(m.Head.GetFromUin()); mem != nil {
return
}
info, _ := c.GetMemberInfo(group.Code, m.Head.GetFromUin())
if info == nil {
return
}
mem = info
group.Members = append(group.Members, mem)
group.sort()
go c.dispatchNewMemberEvent(&MemberJoinGroupEvent{
Group: group,
Member: info,
})
})
if mem == nil {
return nil
}
}
sender = &message.Sender{
Uin: mem.Uin,
Nickname: mem.Nickname,
CardName: mem.CardName,
IsFriend: c.FindFriend(mem.Uin) != nil,
}
}
var g *message.GroupMessage
g = &message.GroupMessage{
Id: m.Head.GetMsgSeq(),
GroupCode: group.Code,
GroupName: string(m.Head.GroupInfo.GroupName),
Sender: sender,
Time: m.Head.GetMsgTime(),
Elements: message.ParseMessageElems(m.Body.RichText.Elems),
OriginalObject: m,
}
var extInfo *msg.ExtraInfo
// pre parse
for _, elem := range m.Body.RichText.Elems {
// is rich long msg
if elem.GeneralFlags != nil && elem.GeneralFlags.GetLongTextResid() != "" && len(g.Elements) == 1 {
if f := c.GetForwardMessage(elem.GeneralFlags.GetLongTextResid()); f != nil && len(f.Nodes) == 1 {
g = &message.GroupMessage{
Id: m.Head.GetMsgSeq(),
GroupCode: group.Code,
GroupName: string(m.Head.GroupInfo.GroupName),
Sender: sender,
Time: m.Head.GetMsgTime(),
Elements: f.Nodes[0].Message,
OriginalObject: m,
}
}
}
if elem.ExtraInfo != nil {
extInfo = elem.ExtraInfo
}
}
if !sender.IsAnonymous() {
mem := group.FindMember(m.Head.GetFromUin())
groupCard := m.Head.GroupInfo.GetGroupCard()
if extInfo != nil && len(extInfo.GroupCard) > 0 && extInfo.GroupCard[0] == 0x0A {
buf := oidb.D8FCCommCardNameBuf{}
if err := proto.Unmarshal(extInfo.GroupCard, &buf); err == nil && len(buf.RichCardName) > 0 {
var gcard strings.Builder
for _, e := range buf.RichCardName {
gcard.Write(e.Text)
}
groupCard = gcard.String()
}
}
if m.Head.GroupInfo != nil && groupCard != "" && mem.CardName != groupCard {
old := mem.CardName
if mem.Nickname == groupCard {
mem.CardName = ""
} else {
mem.CardName = groupCard
}
if old != mem.CardName {
go c.dispatchMemberCardUpdatedEvent(&MemberCardUpdatedEvent{
Group: group,
OldCard: old,
Member: mem,
})
}
}
}
if m.Body.RichText.Ptt != nil {
g.Elements = []message.IMessageElement{
&message.VoiceElement{
Name: m.Body.RichText.Ptt.GetFileName(),
Md5: m.Body.RichText.Ptt.FileMd5,
Size: m.Body.RichText.Ptt.GetFileSize(),
Url: "http://grouptalk.c2c.qq.com" + string(m.Body.RichText.Ptt.DownPara),
},
}
}
if m.Body.RichText.Attr != nil {
g.InternalId = m.Body.RichText.Attr.GetRandom()
}
return g
}
// SetEssenceMessage 设为群精华消息
func (c *QQClient) SetEssenceMessage(groupCode int64, msgID, msgInternalId int32) error {
r, err := c.sendAndWait(c.buildEssenceMsgOperatePacket(groupCode, uint32(msgID), uint32(msgInternalId), 1))
if err != nil {
return errors.Wrap(err, "set essence msg network")
}
rsp := r.(*oidb.EACRspBody)
if rsp.GetErrorCode() != 0 {
return errors.New(rsp.GetWording())
}
return nil
}
// DeleteEssenceMessage 移出群精华消息
func (c *QQClient) DeleteEssenceMessage(groupCode int64, msgID, msgInternalId int32) error {
r, err := c.sendAndWait(c.buildEssenceMsgOperatePacket(groupCode, uint32(msgID), uint32(msgInternalId), 2))
if err != nil {
return errors.Wrap(err, "set essence msg networ")
}
rsp := r.(*oidb.EACRspBody)
if rsp.GetErrorCode() != 0 {
return errors.New(rsp.GetWording())
}
return nil
}
func (c *QQClient) buildEssenceMsgOperatePacket(groupCode int64, msgSeq, msgRand, opType uint32) (uint16, []byte) {
seq := c.nextSeq()
commandName := "OidbSvc.0xeac_" + strconv.FormatInt(int64(opType), 10)
payload := c.packOIDBPackageProto(3756, int32(opType), &oidb.EACReqBody{ // serviceType 2 取消
GroupCode: proto.Uint64(uint64(groupCode)),
Seq: proto.Uint32(msgSeq),
Random: proto.Uint32(msgRand),
})
packet := packets.BuildUniPacket(c.Uin, seq, commandName, 1, c.OutGoingPacketSessionId, EmptyBytes, c.sigInfo.d2Key, payload)
return seq, packet
}
// OidbSvc.0xeac_1/2
func decodeEssenceMsgResponse(_ *QQClient, _ *incomingPacketInfo, payload []byte) (interface{}, error) {
pkg := oidb.OIDBSSOPkg{}
rsp := &oidb.EACRspBody{}
if err := proto.Unmarshal(payload, &pkg); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if err := proto.Unmarshal(pkg.Bodybuffer, rsp); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
return rsp, nil
}
// GetGroupEssenceMsgList 获取群精华消息列表
func (c *QQClient) GetGroupEssenceMsgList(groupCode int64) ([]GroupDigest, error) {
essenceURL := "https://qun.qq.com/essence/index?gc=" + strconv.FormatInt(groupCode, 10) + "&_wv=3&_wwv=128&_wvx=2&_wvxBclr=f5f6fa"
rsp, err := utils.HttpGetBytes(essenceURL, c.getCookiesWithDomain("qun.qq.com"))
if err != nil {
return nil, errors.Wrap(err, "get essence msg network error")
}
rsp = rsp[bytes.Index(rsp, []byte("window.__INITIAL_STATE__={"))+25:]
rsp = rsp[:bytes.Index(rsp, []byte("</script>"))]
data := &struct {
List []GroupDigest `json:"msgList"`
}{}
err = json.Unmarshal(rsp, data)
if err != nil {
return nil, errors.Wrap(err, "failed to unmarshal json")
}
return data.List, nil
}