1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 11:07:40 +08:00

feat: UploadGuildImage

This commit is contained in:
Mrs4s 2021-11-12 01:50:30 +08:00
parent e3411adb3d
commit b644f5c158
No known key found for this signature in database
GPG Key ID: 3186E98FA19CE3A7
5 changed files with 219 additions and 109 deletions

View File

@ -265,19 +265,6 @@ type (
list []*GroupMemberInfo list []*GroupMemberInfo
} }
imageUploadResponse struct {
UploadKey []byte
UploadIp []uint32
UploadPort []uint32
ResourceId string
Message string
FileId int64
Width int32
Height int32
ResultCode int32
IsExists bool
}
pttUploadResponse struct { pttUploadResponse struct {
ResultCode int32 ResultCode int32
Message string Message string

View File

@ -1,14 +1,13 @@
package client package client
import ( import (
"sync"
"time"
"github.com/Mrs4s/MiraiGo/client/pb/channel" "github.com/Mrs4s/MiraiGo/client/pb/channel"
"github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/internal/packets" "github.com/Mrs4s/MiraiGo/internal/packets"
"github.com/pkg/errors" "github.com/pkg/errors"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"sync"
"time"
) )
func init() { func init() {
@ -19,6 +18,13 @@ var (
updateChanLock sync.Mutex updateChanLock sync.Mutex
) )
type tipsPushInfo struct {
TinyId uint64
TargetMessageSenderUin int64
GuildId uint64
ChannelId uint64
}
func decodeGuildEventFlowPacket(c *QQClient, _ *incomingPacketInfo, payload []byte) (interface{}, error) { func decodeGuildEventFlowPacket(c *QQClient, _ *incomingPacketInfo, payload []byte) (interface{}, error) {
push := new(channel.MsgOnlinePush) push := new(channel.MsgOnlinePush)
if err := proto.Unmarshal(payload, push); err != nil { if err := proto.Unmarshal(payload, push); err != nil {
@ -26,12 +32,6 @@ func decodeGuildEventFlowPacket(c *QQClient, _ *incomingPacketInfo, payload []by
} }
for _, m := range push.Msgs { for _, m := range push.Msgs {
if m.Head.ContentHead.GetType() == 3841 { if m.Head.ContentHead.GetType() == 3841 {
type tipsPushInfo struct {
TinyId uint64
TargetMessageSenderUin int64
GuildId uint64
ChannelId uint64
}
// todo: 回头 event flow 的处理移出去重构下逻辑, 先暂时这样方便改 // todo: 回头 event flow 的处理移出去重构下逻辑, 先暂时这样方便改
var common *msg.CommonElem var common *msg.CommonElem
if m.Body != nil { if m.Body != nil {
@ -64,68 +64,7 @@ func decodeGuildEventFlowPacket(c *QQClient, _ *incomingPacketInfo, payload []by
c.Error("failed to unmarshal guild channel event body: %v", err) c.Error("failed to unmarshal guild channel event body: %v", err)
continue continue
} }
if eventBody.ChangeChanInfo != nil { c.processGuildEventBody(m, eventBody)
updateChanLock.Lock()
defer updateChanLock.Unlock()
guild := c.GuildService.FindGuild(m.Head.RoutingHead.GetGuildId())
oldInfo := guild.FindChannel(eventBody.ChangeChanInfo.GetChanId())
if time.Now().Unix()-oldInfo.fetchTime <= 2 {
continue
}
newInfo, err := c.GuildService.FetchChannelInfo(m.Head.RoutingHead.GetGuildId(), eventBody.ChangeChanInfo.GetChanId())
if err != nil {
c.Error("error to decode channel info updated event: fetch channel info failed: %v", err)
continue
}
for i := range guild.Channels {
if guild.Channels[i].ChannelId == newInfo.ChannelId {
guild.Channels[i] = newInfo
break
}
}
c.dispatchGuildChannelUpdatedEvent(&GuildChannelUpdatedEvent{
OperatorId: m.Head.RoutingHead.GetFromTinyid(),
GuildId: m.Head.RoutingHead.GetGuildId(),
ChannelId: eventBody.ChangeChanInfo.GetChanId(),
OldChannelInfo: oldInfo,
NewChannelInfo: newInfo,
})
continue
}
if eventBody.UpdateMsg != nil {
if eventBody.UpdateMsg.GetEventType() == 1 || eventBody.UpdateMsg.GetEventType() == 2 { // todo: 撤回消息
continue
}
if eventBody.UpdateMsg.GetEventType() == 4 { // 消息贴表情更新 (包含添加或删除)
t, err := c.GuildService.pullRoamMsgByEventFlow(m.Head.RoutingHead.GetGuildId(), m.Head.RoutingHead.GetChannelId(), eventBody.UpdateMsg.GetMsgSeq(), eventBody.UpdateMsg.GetMsgSeq(), eventBody.UpdateMsg.GetEventVersion()-1)
if err != nil || len(t) == 0 {
c.Error("process guild event flow error: pull eventMsg message error: %v", err)
continue
}
// 自己的消息被贴表情会单独推送一个tips, 这里不需要解析
if t[0].Head.RoutingHead.GetFromTinyid() == c.GuildService.TinyId {
continue
}
updatedEvent := &GuildMessageReactionsUpdatedEvent{
GuildId: m.Head.RoutingHead.GetGuildId(),
ChannelId: m.Head.RoutingHead.GetChannelId(),
MessageId: t[0].Head.ContentHead.GetSeq(),
CurrentReactions: decodeGuildMessageEmojiReactions(t[0]),
}
tipsInfo, err := c.waitPacketTimeoutSyncF("MsgPush.PushGroupProMsg", time.Second, func(i interface{}) bool {
if i == nil {
return false
}
_, ok := i.(*tipsPushInfo)
return ok
})
if err == nil {
updatedEvent.OperatorId = tipsInfo.(*tipsPushInfo).TinyId
updatedEvent.MessageSenderUin = tipsInfo.(*tipsPushInfo).TargetMessageSenderUin
}
c.dispatchGuildMessageReactionsUpdatedEvent(updatedEvent)
}
}
continue continue
} }
if cm := c.parseGuildChannelMessage(m); cm != nil { if cm := c.parseGuildChannelMessage(m); cm != nil {
@ -135,6 +74,70 @@ func decodeGuildEventFlowPacket(c *QQClient, _ *incomingPacketInfo, payload []by
return nil, nil return nil, nil
} }
func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody *channel.EventBody) {
switch {
case eventBody.ChangeChanInfo != nil:
updateChanLock.Lock()
defer updateChanLock.Unlock()
guild := c.GuildService.FindGuild(m.Head.RoutingHead.GetGuildId())
oldInfo := guild.FindChannel(eventBody.ChangeChanInfo.GetChanId())
if time.Now().Unix()-oldInfo.fetchTime <= 2 {
return
}
newInfo, err := c.GuildService.FetchChannelInfo(m.Head.RoutingHead.GetGuildId(), eventBody.ChangeChanInfo.GetChanId())
if err != nil {
c.Error("error to decode channel info updated event: fetch channel info failed: %v", err)
return
}
for i := range guild.Channels {
if guild.Channels[i].ChannelId == newInfo.ChannelId {
guild.Channels[i] = newInfo
break
}
}
c.dispatchGuildChannelUpdatedEvent(&GuildChannelUpdatedEvent{
OperatorId: m.Head.RoutingHead.GetFromTinyid(),
GuildId: m.Head.RoutingHead.GetGuildId(),
ChannelId: eventBody.ChangeChanInfo.GetChanId(),
OldChannelInfo: oldInfo,
NewChannelInfo: newInfo,
})
case eventBody.UpdateMsg != nil:
if eventBody.UpdateMsg.GetEventType() == 1 || eventBody.UpdateMsg.GetEventType() == 2 { // todo: 撤回消息
return
}
if eventBody.UpdateMsg.GetEventType() == 4 { // 消息贴表情更新 (包含添加或删除)
t, err := c.GuildService.pullRoamMsgByEventFlow(m.Head.RoutingHead.GetGuildId(), m.Head.RoutingHead.GetChannelId(), eventBody.UpdateMsg.GetMsgSeq(), eventBody.UpdateMsg.GetMsgSeq(), eventBody.UpdateMsg.GetEventVersion()-1)
if err != nil || len(t) == 0 {
c.Error("process guild event flow error: pull eventMsg message error: %v", err)
return
}
// 自己的消息被贴表情会单独推送一个tips, 这里不需要解析
if t[0].Head.RoutingHead.GetFromTinyid() == c.GuildService.TinyId {
return
}
updatedEvent := &GuildMessageReactionsUpdatedEvent{
GuildId: m.Head.RoutingHead.GetGuildId(),
ChannelId: m.Head.RoutingHead.GetChannelId(),
MessageId: t[0].Head.ContentHead.GetSeq(),
CurrentReactions: decodeGuildMessageEmojiReactions(t[0]),
}
tipsInfo, err := c.waitPacketTimeoutSyncF("MsgPush.PushGroupProMsg", time.Second, func(i interface{}) bool {
if i == nil {
return false
}
_, ok := i.(*tipsPushInfo)
return ok
})
if err == nil {
updatedEvent.OperatorId = tipsInfo.(*tipsPushInfo).TinyId
updatedEvent.MessageSenderUin = tipsInfo.(*tipsPushInfo).TargetMessageSenderUin
}
c.dispatchGuildMessageReactionsUpdatedEvent(updatedEvent)
}
}
}
func (s *GuildService) pullRoamMsgByEventFlow(guildId, channelId, beginSeq, endSeq, eventVersion uint64) ([]*channel.ChannelMsgContent, error) { func (s *GuildService) pullRoamMsgByEventFlow(guildId, channelId, beginSeq, endSeq, eventVersion uint64) ([]*channel.ChannelMsgContent, error) {
payload, _ := proto.Marshal(&channel.ChannelMsgReq{ payload, _ := proto.Marshal(&channel.ChannelMsgReq{
ChannelParam: &channel.ChannelParam{ ChannelParam: &channel.ChannelParam{

View File

@ -1,7 +1,13 @@
package client package client
import ( import (
"bytes"
"encoding/hex" "encoding/hex"
"fmt"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/utils"
"image"
"io"
"math/rand" "math/rand"
"strconv" "strconv"
@ -16,6 +22,25 @@ import (
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type (
guildImageUploadResponse struct {
UploadKey []byte
UploadIp []uint32
UploadPort []uint32
Width int32
Height int32
Message string
DownloadIndex string
FileId int64
ResultCode int32
IsExists bool
}
)
func init() {
decoders["ImgStore.QQMeetPicUp"] = decodeGuildImageStoreResponse
}
func (s *GuildService) SendGuildChannelMessage(guildId, channelId uint64, m *message.SendingMessage) error { func (s *GuildService) SendGuildChannelMessage(guildId, channelId uint64, m *message.SendingMessage) error {
mr := rand.Uint32() // 客户端似乎是生成的 u32 虽然类型是u64 mr := rand.Uint32() // 客户端似乎是生成的 u32 虽然类型是u64
req := &channel.DF62ReqBody{Msg: &channel.ChannelMsgContent{ req := &channel.DF62ReqBody{Msg: &channel.ChannelMsgContent{
@ -55,14 +80,84 @@ func (s *GuildService) SendGuildChannelMessage(guildId, channelId uint64, m *mes
} }
func (s *GuildService) QueryImage(guildId, channelId uint64, hash []byte, size uint64) (*message.GuildImageElement, error) { func (s *GuildService) QueryImage(guildId, channelId uint64, hash []byte, size uint64) (*message.GuildImageElement, error) {
seq := s.c.nextSeq() rsp, err := s.c.sendAndWait(s.c.buildGuildImageStorePacket(guildId, channelId, hash, size))
if err != nil {
return nil, errors.Wrap(err, "send packet error")
}
body := rsp.(*guildImageUploadResponse)
if body.IsExists {
return &message.GuildImageElement{
FileId: body.FileId,
FilePath: hex.EncodeToString(hash) + ".jpg",
Size: int32(size),
DownloadIndex: body.DownloadIndex,
Width: body.Width,
Height: body.Height,
Md5: hash,
}, nil
}
return nil, errors.New("image is not exists")
}
func (s *GuildService) UploadGuildImage(guildId, channelId uint64, img io.ReadSeeker) (*message.GuildImageElement, error) {
_, _ = img.Seek(0, io.SeekStart) // safe
fh, length := utils.ComputeMd5AndLength(img)
_, _ = img.Seek(0, io.SeekStart)
rsp, err := s.c.sendAndWait(s.c.buildGuildImageStorePacket(guildId, channelId, fh, uint64(length)))
if err != nil {
return nil, err
}
body := rsp.(*guildImageUploadResponse)
if body.IsExists {
goto ok
}
if len(s.c.srvSsoAddrs) == 0 {
for i, addr := range body.UploadIp {
s.c.srvSsoAddrs = append(s.c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr), body.UploadPort[i]))
}
}
if _, err = s.c.highwayUploadByBDH(img, length, 83, body.UploadKey, fh, binary.DynamicProtoMessage{11: guildId, 12: channelId}.Encode(), false); err == nil {
goto ok
}
return nil, errors.Wrap(err, "highway upload error")
ok:
_, _ = img.Seek(0, io.SeekStart)
i, _, err := image.DecodeConfig(img)
var imageType int32 = 1000
_, _ = img.Seek(0, io.SeekStart)
tmp := make([]byte, 4)
_, _ = img.Read(tmp)
if bytes.Equal(tmp, []byte{0x47, 0x49, 0x46, 0x38}) {
imageType = 2000
}
width := int32(i.Width)
height := int32(i.Height)
if err != nil {
s.c.Warning("waring: decode image error: %v. this image will be displayed by wrong size in pc guild client", err)
width = 200
height = 200
}
return &message.GuildImageElement{
FileId: body.FileId,
FilePath: hex.EncodeToString(fh) + ".jpg",
Size: int32(length),
DownloadIndex: body.DownloadIndex,
Width: width,
Height: height,
ImageType: imageType,
Md5: fh,
}, nil
}
func (c *QQClient) buildGuildImageStorePacket(guildId, channelId uint64, hash []byte, size uint64) (uint16, []byte) {
seq := c.nextSeq()
payload, _ := proto.Marshal(&cmd0x388.D388ReqBody{ payload, _ := proto.Marshal(&cmd0x388.D388ReqBody{
NetType: proto.Uint32(3), NetType: proto.Uint32(3),
Subcmd: proto.Uint32(1), Subcmd: proto.Uint32(1),
TryupImgReq: []*cmd0x388.TryUpImgReq{ TryupImgReq: []*cmd0x388.TryUpImgReq{
{ {
GroupCode: &channelId, GroupCode: &channelId,
SrcUin: proto.Uint64(uint64(s.c.Uin)), SrcUin: proto.Uint64(uint64(c.Uin)),
FileId: proto.Uint64(0), FileId: proto.Uint64(0),
FileMd5: hash, FileMd5: hash,
FileSize: &size, FileSize: &size,
@ -80,28 +175,8 @@ func (s *GuildService) QueryImage(guildId, channelId uint64, hash []byte, size u
}, },
CommandId: proto.Uint32(83), CommandId: proto.Uint32(83),
}) })
packet := packets.BuildUniPacket(s.c.Uin, seq, "ImgStore.QQMeetPicUp", 1, s.c.OutGoingPacketSessionId, []byte{}, s.c.sigInfo.d2Key, payload) packet := packets.BuildUniPacket(c.Uin, seq, "ImgStore.QQMeetPicUp", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, payload)
rsp, err := s.c.sendAndWaitDynamic(seq, packet) return seq, packet
if err != nil {
return nil, errors.Wrap(err, "send packet error")
}
body := new(cmd0x388.D388RspBody)
if err = proto.Unmarshal(rsp, body); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if len(body.TryupImgRsp) == 0 {
return nil, errors.New("response is empty")
}
if body.TryupImgRsp[0].GetFileExit() {
return &message.GuildImageElement{
FileId: body.TryupImgRsp[0].GetFileid(),
FilePath: hex.EncodeToString(hash) + ".jpg",
Size: int32(size),
DownloadIndex: string(body.TryupImgRsp[0].GetDownloadIndex()),
Md5: hash,
}, nil
}
return nil, errors.New("image is not exists")
} }
func decodeGuildMessageEmojiReactions(content *channel.ChannelMsgContent) (r []*message.GuildMessageEmojiReaction) { func decodeGuildMessageEmojiReactions(content *channel.ChannelMsgContent) (r []*message.GuildMessageEmojiReaction) {
@ -140,6 +215,36 @@ func decodeGuildMessageEmojiReactions(content *channel.ChannelMsgContent) (r []*
return return
} }
func decodeGuildImageStoreResponse(_ *QQClient, _ *incomingPacketInfo, payload []byte) (interface{}, error) {
body := new(cmd0x388.D388RspBody)
if err := proto.Unmarshal(payload, body); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if len(body.TryupImgRsp) == 0 {
return nil, errors.New("response is empty")
}
rsp := body.TryupImgRsp[0]
if rsp.GetResult() != 0 {
return &guildImageUploadResponse{
ResultCode: int32(rsp.GetResult()),
Message: utils.B2S(rsp.GetFailMsg()),
}, nil
}
if rsp.GetFileExit() {
if rsp.ImgInfo != nil {
return &guildImageUploadResponse{IsExists: true, FileId: int64(rsp.GetFileid()), DownloadIndex: string(rsp.GetDownloadIndex()), Width: int32(rsp.ImgInfo.GetFileWidth()), Height: int32(rsp.ImgInfo.GetFileHeight())}, nil
}
return &guildImageUploadResponse{IsExists: true, FileId: int64(rsp.GetFileid()), DownloadIndex: string(rsp.GetDownloadIndex())}, nil
}
return &guildImageUploadResponse{
FileId: int64(rsp.GetFileid()),
UploadKey: rsp.UpUkey,
UploadIp: rsp.GetUpIp(),
UploadPort: rsp.GetUpPort(),
DownloadIndex: string(rsp.GetDownloadIndex()),
}, nil
}
func (c *QQClient) parseGuildChannelMessage(msg *channel.ChannelMsgContent) *message.GuildChannelMessage { func (c *QQClient) parseGuildChannelMessage(msg *channel.ChannelMsgContent) *message.GuildChannelMessage {
guild := c.GuildService.FindGuild(msg.Head.RoutingHead.GetGuildId()) guild := c.GuildService.FindGuild(msg.Head.RoutingHead.GetGuildId())
if guild == nil { if guild == nil {

View File

@ -34,6 +34,21 @@ func init() {
var imgWaiter = utils.NewUploadWaiter() var imgWaiter = utils.NewUploadWaiter()
type (
imageUploadResponse struct {
UploadKey []byte
UploadIp []uint32
UploadPort []uint32
ResourceId string
Message string
FileId int64
Width int32
Height int32
ResultCode int32
IsExists bool
}
)
func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker) (*message.GroupImageElement, error) { func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker) (*message.GroupImageElement, error) {
_, _ = img.Seek(0, io.SeekStart) // safe _, _ = img.Seek(0, io.SeekStart) // safe
fh, length := utils.ComputeMd5AndLength(img) fh, length := utils.ComputeMd5AndLength(img)

View File

@ -37,7 +37,7 @@ type FriendImageElement struct {
} }
type GuildImageElement struct { type GuildImageElement struct {
FileId uint64 FileId int64
FilePath string FilePath string
ImageType int32 ImageType int32
Size int32 Size int32