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

client: new API UploadImage, UploadVoice and deprecate old API

This commit is contained in:
wdvxdr 2022-02-21 23:35:44 +08:00
parent 1e32793eef
commit 6b5e7d35f0
No known key found for this signature in database
GPG Key ID: 703F8C071DE7A1B6
4 changed files with 172 additions and 181 deletions

View File

@ -1,38 +1,21 @@
package client
import (
"bytes"
"encoding/hex"
"image"
"io"
"math/rand"
"strconv"
"github.com/pkg/errors"
"github.com/Mrs4s/MiraiGo/client/internal/highway"
"github.com/Mrs4s/MiraiGo/client/internal/network"
"github.com/Mrs4s/MiraiGo/client/pb/channel"
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/internal/proto"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/utils"
)
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
}
@ -101,7 +84,7 @@ func (s *GuildService) QueryImage(guildId, channelId uint64, hash []byte, size u
if err != nil {
return nil, errors.Wrap(err, "send packet error")
}
body := rsp.(*guildImageUploadResponse)
body := rsp.(*imageUploadResponse)
if body.IsExists {
return &message.GuildImageElement{
FileId: body.FileId,
@ -116,60 +99,18 @@ func (s *GuildService) QueryImage(guildId, channelId uint64, hash []byte, size u
return nil, errors.New("image is not exists")
}
// Deprecated: use QQClient.UploadImage instead
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)))
source := message.Source{
SourceType: message.SourceGuildChannel,
PrimaryID: int64(guildId),
SecondaryID: int64(channelId),
}
image, err := s.c.uploadGroupOrGuildImage(source, img)
if err != nil {
return nil, err
}
body := rsp.(*guildImageUploadResponse)
if body.IsExists {
goto ok
}
if s.c.highwaySession.AddrLength() == 0 {
for i, addr := range body.UploadIp {
s.c.highwaySession.AppendAddr(addr, body.UploadPort[i])
}
}
if _, err = s.c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 83,
Body: img,
Ticket: body.UploadKey,
Ext: proto.DynamicMessage{11: guildId, 12: channelId}.Encode(),
Encrypt: 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
return image.(*message.GuildImageElement), nil
}
func (s *GuildService) PullGuildChannelMessage(guildId, channelId, beginSeq, endSeq uint64) (r []*message.GuildChannelMessage, e error) {
@ -299,18 +240,24 @@ func decodeGuildImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, p
}
rsp := body.TryupImgRsp[0]
if rsp.GetResult() != 0 {
return &guildImageUploadResponse{
return &imageUploadResponse{
ResultCode: int32(rsp.GetResult()),
Message: utils.B2S(rsp.FailMsg),
Message: string(rsp.FailMsg),
}, nil
}
if rsp.GetFileExit() {
if rsp.ImgInfo != nil {
return &guildImageUploadResponse{IsExists: true, FileId: int64(rsp.GetFileid()), DownloadIndex: string(rsp.DownloadIndex), Width: int32(rsp.ImgInfo.GetFileWidth()), Height: int32(rsp.ImgInfo.GetFileHeight())}, nil
resp := &imageUploadResponse{
IsExists: true,
FileId: int64(rsp.GetFileid()),
DownloadIndex: string(rsp.DownloadIndex),
}
return &guildImageUploadResponse{IsExists: true, FileId: int64(rsp.GetFileid()), DownloadIndex: string(rsp.DownloadIndex)}, nil
if rsp.ImgInfo != nil {
resp.Width = int32(rsp.ImgInfo.GetFileWidth())
resp.Height = int32(rsp.ImgInfo.GetFileHeight())
}
return rsp, nil
}
return &guildImageUploadResponse{
return &imageUploadResponse{
FileId: int64(rsp.GetFileid()),
UploadKey: rsp.UpUkey,
UploadIp: rsp.UpIp,

View File

@ -31,19 +31,44 @@ func init() {
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
UploadKey []byte
UploadIp []uint32
UploadPort []uint32
Width int32
Height int32
Message string
DownloadIndex string
ResourceId string
FileId int64
ResultCode int32
IsExists bool
}
func (c *QQClient) UploadImage(target message.Source, img io.ReadSeeker, thread ...int) (message.IMessageElement, error) {
switch target.SourceType {
case message.SourceGroup, message.SourceGuildChannel, message.SourceGuildDirect:
return c.uploadGroupOrGuildImage(target, img, thread...)
case message.SourcePrivate:
return c.uploadPrivateImage(target.PrimaryID, img, 0)
default:
return nil, errors.New("unsupported target type")
}
}
// Deprecated: use UploadImage instead
func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker, thread ...int) (*message.GroupImageElement, error) {
source := message.Source{
SourceType: message.SourceGroup,
PrimaryID: groupCode,
}
x, err := c.UploadImage(source, img, thread...)
if err != nil {
return nil, err
}
return x.(*message.GroupImageElement), nil
}
func (c *QQClient) uploadGroupOrGuildImage(target message.Source, img io.ReadSeeker, thread ...int) (message.IMessageElement, error) {
_, _ = img.Seek(0, io.SeekStart) // safe
fh, length := utils.ComputeMd5AndLength(img)
_, _ = img.Seek(0, io.SeekStart)
@ -56,9 +81,24 @@ func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker, thread .
if len(thread) > 0 {
tc = thread[0]
}
cmd := int32(2)
ext := EmptyBytes
if target.SourceType != message.SourceGroup { // guild
cmd = 83
ext = proto.DynamicMessage{
11: uint64(target.PrimaryID),
12: uint64(target.SecondaryID),
}.Encode()
}
seq, pkt := c.buildGroupImageStorePacket(groupCode, fh, int32(length))
r, err := c.sendAndWait(seq, pkt)
var r interface{}
var err error
switch target.SourceType {
case message.SourceGroup:
r, err = c.sendAndWait(c.buildGroupImageStorePacket(target.PrimaryID, fh, int32(length)))
case message.SourceGuildChannel, message.SourceGuildDirect:
r, err = c.sendAndWait(c.buildGuildImageStorePacket(uint64(target.PrimaryID), uint64(target.SecondaryID), fh, uint64(length)))
}
if err != nil {
return nil, err
}
@ -77,21 +117,19 @@ func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker, thread .
if tc > 1 && length > 3*1024*1024 {
_, err = c.highwaySession.UploadBDHMultiThread(highway.BdhMultiThreadInput{
CommandID: 2,
CommandID: cmd,
Body: utils.ReaderAtFrom2ReadSeeker(img, nil),
Size: length,
Sum: fh,
Ticket: rsp.UploadKey,
Ext: EmptyBytes,
Encrypt: false,
Ext: ext,
}, 4)
} else {
_, err = c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 2,
CommandID: cmd,
Body: img,
Ticket: rsp.UploadKey,
Ext: EmptyBytes,
Encrypt: false,
Ext: ext,
})
}
if err != nil {
@ -104,9 +142,33 @@ ok:
if t == "gif" {
imageType = 2000
}
return message.NewGroupImage(binary.CalculateImageResourceId(fh), fh, rsp.FileId, int32(length), int32(i.Width), int32(i.Height), imageType), nil
width := int32(i.Width)
height := int32(i.Height)
if err != nil && target.SourceType != message.SourceGroup {
c.Warning("waring: decode image error: %v. this image will be displayed by wrong size in pc guild client", err)
width = 200
height = 200
}
if target.SourceType == message.SourceGroup {
return message.NewGroupImage(
binary.CalculateImageResourceId(fh),
fh, rsp.FileId, int32(length),
int32(i.Width), int32(i.Height), imageType,
), nil
}
return &message.GuildImageElement{
FileId: rsp.FileId,
FilePath: hex.EncodeToString(fh) + ".jpg",
Size: int32(length),
DownloadIndex: rsp.DownloadIndex,
Width: width,
Height: height,
ImageType: imageType,
Md5: fh,
}, nil
}
// Deprecated: use UploadImage instead
func (c *QQClient) UploadPrivateImage(target int64, img io.ReadSeeker) (*message.FriendImageElement, error) {
return c.uploadPrivateImage(target, img, 0)
}
@ -126,8 +188,12 @@ func (c *QQClient) uploadPrivateImage(target int64, img io.ReadSeeker, count int
_, _ = img.Seek(0, io.SeekStart)
e, err := c.QueryFriendImage(target, fh, int32(length))
if errors.Is(err, ErrNotExists) {
groupSource := message.Source{
SourceType: message.SourceGroup,
PrimaryID: target,
}
// use group highway upload and query again for image id.
if _, err = c.UploadGroupImage(target, img); err != nil {
if _, err = c.uploadGroupOrGuildImage(groupSource, img); err != nil {
return nil, err
}
if count >= 5 {

View File

@ -1,7 +1,6 @@
package client
import (
"crypto/md5"
"encoding/hex"
"io"
@ -26,24 +25,35 @@ func init() {
var pttWaiter = utils.NewUploadWaiter()
var c2cPttExtraInfo = binary.NewWriterF(func(w *binary.Writer) {
func c2cPttExtraInfo() []byte {
w := binary.SelectWriter()
defer binary.PutWriter(w)
w.WriteByte(2) // tlv count
w.WriteByte(8)
w.WriteUInt16(4)
w.WriteUInt32(1) // codec
w.WriteByte(9)
w.WriteUInt16(4)
w.WriteUInt32(0) // 时长
{
w.WriteByte(8)
w.WriteUInt16(4)
w.WriteUInt32(1) // codec
}
{
w.WriteByte(9)
w.WriteUInt16(4)
w.WriteUInt32(0) // 时长
}
w.WriteByte(10)
reserveInfo := []byte{0x08, 0x00, 0x28, 0x00, 0x38, 0x00} // todo
w.WriteBytesShort(reserveInfo)
})
return append([]byte(nil), w.Bytes()...)
}
// UploadVoice 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送
func (c *QQClient) UploadVoice(target message.Source, voice io.ReadSeeker) (*message.GroupVoiceElement, error) {
switch target.SourceType {
case message.SourceGroup, message.SourcePrivate:
// ok
default:
return nil, errors.New("unsupported source type")
}
// UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送
func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) {
fh, length := utils.ComputeMd5AndLength(voice)
_, _ = voice.Seek(0, io.SeekStart)
@ -51,9 +61,18 @@ func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*messag
pttWaiter.Wait(key)
defer pttWaiter.Done(key)
ext := c.buildGroupPttStoreBDHExt(groupCode, fh, int32(length), 0, int32(length))
var cmd int32
var ext []byte
if target.SourceType == message.SourcePrivate {
cmd = int32(26)
ext = c.buildC2CPttStoreBDHExt(target.PrimaryID, fh, int32(length), int32(length))
} else {
cmd = int32(29)
ext = c.buildGroupPttStoreBDHExt(target.PrimaryID, fh, int32(length), 0, int32(length))
}
// multi-thread upload is no need
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 29,
CommandID: cmd,
Body: voice,
Ticket: c.highwaySession.SigSession,
Ext: ext,
@ -65,71 +84,37 @@ func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*messag
if len(rsp) == 0 {
return nil, errors.New("miss rsp")
}
pkt := cmd0x388.D388RspBody{}
if err = proto.Unmarshal(rsp, &pkt); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
ptt := &msg.Ptt{
FileType: proto.Int32(4),
SrcUin: &c.Uin,
FileMd5: fh,
FileName: proto.String(hex.EncodeToString(fh) + ".amr"),
FileSize: proto.Int32(int32(length)),
BoolValid: proto.Bool(true),
}
if len(pkt.TryupPttRsp) == 0 {
return nil, errors.New("miss try up rsp")
if target.SourceType == message.SourceGroup {
pkt := cmd0x388.D388RspBody{}
if err = proto.Unmarshal(rsp, &pkt); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if len(pkt.TryupPttRsp) == 0 {
return nil, errors.New("miss try up rsp")
}
ptt.PbReserve = []byte{8, 0, 40, 0, 56, 0}
ptt.GroupFileKey = pkt.TryupPttRsp[0].FileKey
return &message.GroupVoiceElement{Ptt: ptt}, nil
} else {
pkt := cmd0x346.C346RspBody{}
if err = proto.Unmarshal(rsp, &pkt); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if pkt.ApplyUploadRsp == nil {
return nil, errors.New("miss apply upload rsp")
}
ptt.FileUuid = pkt.ApplyUploadRsp.Uuid
ptt.Reserve = c2cPttExtraInfo()
return &message.PrivateVoiceElement{Ptt: ptt}, nil
}
return &message.GroupVoiceElement{
Ptt: &msg.Ptt{
FileType: proto.Int32(4),
SrcUin: &c.Uin,
FileMd5: fh,
FileName: proto.String(hex.EncodeToString(fh) + ".amr"),
FileSize: proto.Int32(int32(length)),
GroupFileKey: pkt.TryupPttRsp[0].FileKey,
BoolValid: proto.Bool(true),
PbReserve: []byte{8, 0, 40, 0, 56, 0},
},
}, nil
}
// UploadPrivatePtt 将语音数据使用好友语音通道上传到服务器, 返回 message.PrivateVoiceElement 可直接发送
func (c *QQClient) UploadPrivatePtt(target int64, voice io.ReadSeeker) (*message.PrivateVoiceElement, error) {
h := md5.New()
length, _ := io.Copy(h, voice)
fh := h.Sum(nil)
_, _ = voice.Seek(0, io.SeekStart)
key := hex.EncodeToString(fh)
pttWaiter.Wait(key)
defer pttWaiter.Done(key)
ext := c.buildC2CPttStoreBDHExt(target, fh, int32(length), int32(length))
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
CommandID: 26,
Body: voice,
Ticket: c.highwaySession.SigSession,
Ext: ext,
Encrypt: false,
})
if err != nil {
return nil, err
}
if len(rsp) == 0 {
return nil, errors.New("miss rsp")
}
pkt := cmd0x346.C346RspBody{}
if err = proto.Unmarshal(rsp, &pkt); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
}
if pkt.ApplyUploadRsp == nil {
return nil, errors.New("miss apply upload rsp")
}
return &message.PrivateVoiceElement{
Ptt: &msg.Ptt{
FileType: proto.Int32(4),
SrcUin: &c.Uin,
FileUuid: pkt.ApplyUploadRsp.Uuid,
FileMd5: fh,
FileName: proto.String(hex.EncodeToString(fh) + ".amr"),
FileSize: proto.Int32(int32(length)),
Reserve: c2cPttExtraInfo,
BoolValid: proto.Bool(true),
},
}, nil
}
// UploadShortVideo 将视频和封面上传到服务器, 返回 message.ShortVideoElement 可直接发送

View File

@ -28,10 +28,7 @@ type GroupVoiceElement struct {
Ptt *msg.Ptt
}
type PrivateVoiceElement struct {
Data []byte
Ptt *msg.Ptt
}
type PrivateVoiceElement = GroupVoiceElement
type FaceElement struct {
Index int32
@ -256,10 +253,6 @@ func (e *GroupVoiceElement) Type() ElementType {
return Voice
}
func (e *PrivateVoiceElement) Type() ElementType {
return Voice
}
func (e *VoiceElement) Type() ElementType {
return Voice
}