mirror of
https://github.com/Mrs4s/MiraiGo.git
synced 2025-05-04 19:17:38 +08:00
refactor: move highway.go to client/internal/highway
This commit is contained in:
parent
ed979508cf
commit
b398cec6a5
@ -63,7 +63,8 @@ var req = RequestDataVersion2{
|
|||||||
"5": {
|
"5": {
|
||||||
"123": []byte(`123`),
|
"123": []byte(`123`),
|
||||||
},
|
},
|
||||||
}}
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func TestRequestDataVersion2_ReadFrom(t *testing.T) {
|
func TestRequestDataVersion2_ReadFrom(t *testing.T) {
|
||||||
// todo(wdv): fuzz test
|
// todo(wdv): fuzz test
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
var globalBytes []byte
|
var globalBytes []byte
|
||||||
|
|
||||||
func BenchmarkJceWriter_WriteMap(b *testing.B) {
|
func BenchmarkJceWriter_WriteMap(b *testing.B) {
|
||||||
var x = globalBytes
|
x := globalBytes
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
w := NewJceWriter()
|
w := NewJceWriter()
|
||||||
w.writeMapStrMapStrBytes(req.Map, 0)
|
w.writeMapStrMapStrBytes(req.Map, 0)
|
||||||
@ -39,7 +39,7 @@ var reqPacket1 = &RequestPacket{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkJceWriter_WriteJceStructRaw(b *testing.B) {
|
func BenchmarkJceWriter_WriteJceStructRaw(b *testing.B) {
|
||||||
var x = globalBytes
|
x := globalBytes
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = reqPacket1.ToBytes()
|
_ = reqPacket1.ToBytes()
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
"github.com/Mrs4s/MiraiGo/binary/jce"
|
"github.com/Mrs4s/MiraiGo/binary/jce"
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/crypto"
|
"github.com/Mrs4s/MiraiGo/internal/crypto"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/packets"
|
"github.com/Mrs4s/MiraiGo/internal/packets"
|
||||||
@ -84,13 +85,12 @@ type QQClient struct {
|
|||||||
// session info
|
// session info
|
||||||
qwebSeq int64
|
qwebSeq int64
|
||||||
sigInfo *loginSigInfo
|
sigInfo *loginSigInfo
|
||||||
bigDataSession *bigDataSessionInfo
|
highwaySession *highway.Session
|
||||||
dpwd []byte
|
dpwd []byte
|
||||||
timeDiff int64
|
timeDiff int64
|
||||||
pwdFlag bool
|
pwdFlag bool
|
||||||
|
|
||||||
// address
|
// address
|
||||||
srvSsoAddrs []string
|
|
||||||
otherSrvAddrs []string
|
otherSrvAddrs []string
|
||||||
fileStorageInfo *jce.FileStoragePushFSSvcList
|
fileStorageInfo *jce.FileStoragePushFSSvcList
|
||||||
|
|
||||||
@ -270,6 +270,7 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
|
|||||||
|
|
||||||
func (c *QQClient) UseDevice(info *DeviceInfo) {
|
func (c *QQClient) UseDevice(info *DeviceInfo) {
|
||||||
c.version = genVersionInfo(info.Protocol)
|
c.version = genVersionInfo(info.Protocol)
|
||||||
|
c.highwaySession = highway.NewSession(int32(c.version.AppId), c.Uin)
|
||||||
c.ksid = []byte(fmt.Sprintf("|%s|A8.2.7.27f6ea96", info.IMEI))
|
c.ksid = []byte(fmt.Sprintf("|%s|A8.2.7.27f6ea96", info.IMEI))
|
||||||
c.deviceInfo = info
|
c.deviceInfo = info
|
||||||
}
|
}
|
||||||
|
@ -346,14 +346,12 @@ func decodePushReqPacket(c *QQClient, _ *incomingPacketInfo, payload []byte) (in
|
|||||||
c.fileStorageInfo = list
|
c.fileStorageInfo = list
|
||||||
rsp := cmd0x6ff.C501RspBody{}
|
rsp := cmd0x6ff.C501RspBody{}
|
||||||
if err := proto.Unmarshal(list.BigDataChannel.PbBuf, &rsp); err == nil && rsp.RspBody != nil {
|
if err := proto.Unmarshal(list.BigDataChannel.PbBuf, &rsp); err == nil && rsp.RspBody != nil {
|
||||||
c.bigDataSession = &bigDataSessionInfo{
|
c.highwaySession.SigSession = rsp.RspBody.SigSession
|
||||||
SigSession: rsp.RspBody.SigSession,
|
c.highwaySession.SessionKey = rsp.RspBody.SessionKey
|
||||||
SessionKey: rsp.RspBody.SessionKey,
|
|
||||||
}
|
|
||||||
for _, srv := range rsp.RspBody.Addrs {
|
for _, srv := range rsp.RspBody.Addrs {
|
||||||
if srv.GetServiceType() == 10 {
|
if srv.GetServiceType() == 10 {
|
||||||
for _, addr := range srv.Addrs {
|
for _, addr := range srv.Addrs {
|
||||||
c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr.GetIp()), addr.GetPort()))
|
c.highwaySession.AppendAddr(addr.GetIp(), addr.GetPort())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if srv.GetServiceType() == 21 {
|
if srv.GetServiceType() == 21 {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/exciting"
|
"github.com/Mrs4s/MiraiGo/client/pb/exciting"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/packets"
|
"github.com/Mrs4s/MiraiGo/internal/packets"
|
||||||
@ -225,11 +226,18 @@ func (fs *GroupFileSystem) UploadFile(p, name, folderId string) error {
|
|||||||
},
|
},
|
||||||
Unknown3: proto.Int32(0),
|
Unknown3: proto.Int32(0),
|
||||||
})
|
})
|
||||||
if _, err = fs.client.excitingUploadStream(file, 71, fs.client.bigDataSession.SigSession, ext); err != nil {
|
client := fs.client
|
||||||
|
input := highway.ExcitingInput{
|
||||||
|
CommandID: 71,
|
||||||
|
Body: file,
|
||||||
|
Ticket: fs.client.highwaySession.SigSession,
|
||||||
|
Ext: ext,
|
||||||
|
}
|
||||||
|
if _, err = fs.client.highwaySession.UploadExciting(input); err != nil {
|
||||||
return errors.Wrap(err, "upload failed")
|
return errors.Wrap(err, "upload failed")
|
||||||
}
|
}
|
||||||
_, pkt := fs.client.buildGroupFileFeedsRequest(fs.GroupCode, rsp.GetFileId(), rsp.GetBusId(), rand.Int31())
|
_, pkt := client.buildGroupFileFeedsRequest(fs.GroupCode, rsp.GetFileId(), rsp.GetBusId(), rand.Int31())
|
||||||
return fs.client.sendPacket(pkt)
|
return client.sendPacket(pkt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *GroupFileSystem) GetDownloadUrl(file *GroupFile) string {
|
func (fs *GroupFileSystem) GetDownloadUrl(file *GroupFile) string {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -260,6 +262,20 @@ func decodeGroupInfoResponse(c *QQClient, _ *incomingPacketInfo, payload []byte)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *QQClient) uploadGroupHeadPortrait(groupCode int64, img []byte) error {
|
||||||
|
url := fmt.Sprintf("http://htdata3.qq.com/cgi-bin/httpconn?htcmd=0x6ff0072&ver=5520&ukey=%v&range=0&uin=%v&seq=23&groupuin=%v&filetype=3&imagetype=5&userdata=0&subcmd=1&subver=101&clip=0_0_0_0&filesize=%v",
|
||||||
|
c.getSKey(), c.Uin, groupCode, len(img))
|
||||||
|
req, _ := http.NewRequest("POST", url, bytes.NewReader(img))
|
||||||
|
req.Header["User-Agent"] = []string{"Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H)"}
|
||||||
|
req.Header["Content-Type"] = []string{"multipart/form-data;boundary=****"}
|
||||||
|
rsp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to upload group head portrait")
|
||||||
|
}
|
||||||
|
rsp.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GroupInfo) UpdateName(newName string) {
|
func (g *GroupInfo) UpdateName(newName string) {
|
||||||
if g.AdministratorOrOwner() && newName != "" && strings.Count(newName, "") <= 20 {
|
if g.AdministratorOrOwner() && newName != "" && strings.Count(newName, "") <= 20 {
|
||||||
g.client.updateGroupName(g.Code, newName)
|
g.client.updateGroupName(g.Code, newName)
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
|
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
|
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
|
||||||
@ -169,7 +170,13 @@ func (c *QQClient) uploadGroupLongMessage(groupCode int64, m *message.ForwardMes
|
|||||||
return nil, errors.Errorf("upload long message error: %v", err)
|
return nil, errors.Errorf("upload long message error: %v", err)
|
||||||
}
|
}
|
||||||
for i, ip := range rsp.Uint32UpIp {
|
for i, ip := range rsp.Uint32UpIp {
|
||||||
err := c.highwayUpload(uint32(ip), int(rsp.Uint32UpPort[i]), rsp.MsgSig, body, 27)
|
addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])}
|
||||||
|
input := highway.Input{
|
||||||
|
CommandID: 27,
|
||||||
|
Key: rsp.MsgSig,
|
||||||
|
Body: bytes.NewReader(body),
|
||||||
|
}
|
||||||
|
err := c.highwaySession.Upload(addr, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error("highway upload long message error: %v", err)
|
c.Error("highway upload long message error: %v", err)
|
||||||
continue
|
continue
|
||||||
@ -191,7 +198,13 @@ func (c *QQClient) UploadGroupForwardMessage(groupCode int64, m *message.Forward
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for i, ip := range rsp.Uint32UpIp {
|
for i, ip := range rsp.Uint32UpIp {
|
||||||
err := c.highwayUpload(uint32(ip), int(rsp.Uint32UpPort[i]), rsp.MsgSig, body, 27)
|
addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])}
|
||||||
|
input := highway.Input{
|
||||||
|
CommandID: 27,
|
||||||
|
Key: rsp.MsgSig,
|
||||||
|
Body: bytes.NewReader(body),
|
||||||
|
}
|
||||||
|
err := c.highwaySession.Upload(addr, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -793,7 +793,6 @@ func decodeGuildPushFirstView(c *QQClient, _ *incomingPacketInfo, payload []byte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(firstViewMsg.ChannelMsgs) > 0 { // sync msg
|
if len(firstViewMsg.ChannelMsgs) > 0 { // sync msg
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,7 @@ func init() {
|
|||||||
decoders["MsgPush.PushGroupProMsg"] = decodeGuildEventFlowPacket
|
decoders["MsgPush.PushGroupProMsg"] = decodeGuildEventFlowPacket
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var updateChanLock sync.Mutex
|
||||||
updateChanLock sync.Mutex
|
|
||||||
)
|
|
||||||
|
|
||||||
type tipsPushInfo struct {
|
type tipsPushInfo struct {
|
||||||
TinyId uint64
|
TinyId uint64
|
||||||
@ -63,7 +61,6 @@ func decodeGuildEventFlowPacket(c *QQClient, _ *incomingPacketInfo, payload []by
|
|||||||
}
|
}
|
||||||
if m.Head.ContentHead.GetSubType() == 2 { // todo: tips?
|
if m.Head.ContentHead.GetSubType() == 2 { // todo: tips?
|
||||||
if common == nil { // empty tips
|
if common == nil { // empty tips
|
||||||
|
|
||||||
}
|
}
|
||||||
tipsInfo := &tipsPushInfo{
|
tipsInfo := &tipsPushInfo{
|
||||||
TinyId: m.Head.RoutingHead.GetFromTinyid(),
|
TinyId: m.Head.RoutingHead.GetFromTinyid(),
|
||||||
|
@ -2,9 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"image"
|
"image"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
@ -13,6 +11,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/channel"
|
"github.com/Mrs4s/MiraiGo/client/pb/channel"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
|
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||||
@ -130,12 +129,18 @@ func (s *GuildService) UploadGuildImage(guildId, channelId uint64, img io.ReadSe
|
|||||||
if body.IsExists {
|
if body.IsExists {
|
||||||
goto ok
|
goto ok
|
||||||
}
|
}
|
||||||
if len(s.c.srvSsoAddrs) == 0 {
|
if s.c.highwaySession.AddrLength() == 0 {
|
||||||
for i, addr := range body.UploadIp {
|
for i, addr := range body.UploadIp {
|
||||||
s.c.srvSsoAddrs = append(s.c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr), body.UploadPort[i]))
|
s.c.highwaySession.AppendAddr(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 {
|
if _, err = s.c.highwaySession.UploadBDH(highway.BdhInput{
|
||||||
|
CommandID: 83,
|
||||||
|
Body: img,
|
||||||
|
Ticket: body.UploadKey,
|
||||||
|
Ext: binary.DynamicProtoMessage{11: guildId, 12: channelId}.Encode(),
|
||||||
|
Encrypt: false,
|
||||||
|
}); err == nil {
|
||||||
goto ok
|
goto ok
|
||||||
}
|
}
|
||||||
return nil, errors.Wrap(err, "highway upload error")
|
return nil, errors.Wrap(err, "highway upload error")
|
||||||
@ -383,14 +388,15 @@ func (c *QQClient) UploadGuildShortVideo(guildId, channelId uint64, video, thumb
|
|||||||
req.BusinessType = 4601
|
req.BusinessType = 4601
|
||||||
req.ToUin = int64(channelId)
|
req.ToUin = int64(channelId)
|
||||||
ext, _ := proto.Marshal(req)
|
ext, _ := proto.Marshal(req)
|
||||||
var hwRsp []byte
|
|
||||||
multi := utils.MultiReadSeeker(thumb, video)
|
multi := utils.MultiReadSeeker(thumb, video)
|
||||||
h := md5.New()
|
|
||||||
length, _ := io.Copy(h, multi)
|
|
||||||
fh := h.Sum(nil)
|
|
||||||
_, _ = multi.Seek(0, io.SeekStart)
|
|
||||||
|
|
||||||
hwRsp, err = c.highwayUploadByBDH(multi, length, 89, c.bigDataSession.SigSession, fh, ext, true)
|
hwRsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
|
||||||
|
CommandID: 89,
|
||||||
|
Body: multi,
|
||||||
|
Ticket: c.highwaySession.SigSession,
|
||||||
|
Ext: ext,
|
||||||
|
Encrypt: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "upload video file error")
|
return nil, errors.Wrap(err, "upload video file error")
|
||||||
}
|
}
|
||||||
|
@ -1,499 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
binary2 "encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb"
|
|
||||||
"github.com/Mrs4s/MiraiGo/internal/proto"
|
|
||||||
"github.com/Mrs4s/MiraiGo/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *QQClient) highwayUpload(ip uint32, port int, updKey, data []byte, cmdID int32) error {
|
|
||||||
return c.highwayUploadStream(ip, port, updKey, bytes.NewReader(data), cmdID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *QQClient) highwayUploadStream(ip uint32, port int, updKey []byte, stream io.ReadSeeker, cmdId int32) error {
|
|
||||||
addr := net.TCPAddr{
|
|
||||||
IP: make([]byte, 4),
|
|
||||||
Port: port,
|
|
||||||
}
|
|
||||||
binary2.LittleEndian.PutUint32(addr.IP, ip)
|
|
||||||
h := md5.New()
|
|
||||||
length, _ := io.Copy(h, stream)
|
|
||||||
fh := h.Sum(nil)
|
|
||||||
const chunkSize = 8192 * 8
|
|
||||||
_, _ = stream.Seek(0, io.SeekStart)
|
|
||||||
conn, err := net.DialTCP("tcp", nil, &addr)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "connect error")
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
offset := 0
|
|
||||||
reader := binary.NewNetworkReader(conn)
|
|
||||||
buf := binary.Get256KBytes()
|
|
||||||
chunk := *buf
|
|
||||||
defer binary.Put256KBytes(buf)
|
|
||||||
|
|
||||||
w := binary.SelectWriter()
|
|
||||||
defer binary.PutWriter(w)
|
|
||||||
for {
|
|
||||||
chunk = chunk[:chunkSize]
|
|
||||||
rl, err := io.ReadFull(stream, chunk)
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
|
||||||
chunk = chunk[:rl]
|
|
||||||
}
|
|
||||||
ch := md5.Sum(chunk)
|
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
|
||||||
MsgBasehead: &pb.DataHighwayHead{
|
|
||||||
Version: 1,
|
|
||||||
Uin: strconv.FormatInt(c.Uin, 10),
|
|
||||||
Command: "PicUp.DataUp",
|
|
||||||
Seq: c.nextGroupDataTransSeq(),
|
|
||||||
Appid: int32(c.version.AppId),
|
|
||||||
Dataflag: 4096,
|
|
||||||
CommandId: cmdId,
|
|
||||||
LocaleId: 2052,
|
|
||||||
},
|
|
||||||
MsgSeghead: &pb.SegHead{
|
|
||||||
Filesize: length,
|
|
||||||
Dataoffset: int64(offset),
|
|
||||||
Datalength: int32(rl),
|
|
||||||
Serviceticket: updKey,
|
|
||||||
Md5: ch[:],
|
|
||||||
FileMd5: fh,
|
|
||||||
},
|
|
||||||
ReqExtendinfo: EmptyBytes,
|
|
||||||
})
|
|
||||||
offset += rl
|
|
||||||
w.Reset()
|
|
||||||
w.WriteByte(40)
|
|
||||||
w.WriteUInt32(uint32(len(head)))
|
|
||||||
w.WriteUInt32(uint32(len(chunk)))
|
|
||||||
w.Write(head)
|
|
||||||
w.Write(chunk)
|
|
||||||
w.WriteByte(41)
|
|
||||||
_, err = conn.Write(w.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "write conn error")
|
|
||||||
}
|
|
||||||
rspHead, _, err := highwayReadResponse(reader)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "highway upload error")
|
|
||||||
}
|
|
||||||
if rspHead.ErrorCode != 0 {
|
|
||||||
return errors.New("upload failed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *QQClient) highwayUploadByBDH(stream io.Reader, length int64, cmdId int32, ticket, sum, ext []byte, encrypt bool) ([]byte, error) {
|
|
||||||
if len(c.srvSsoAddrs) == 0 {
|
|
||||||
return nil, errors.New("srv addrs not found. maybe miss some packet?")
|
|
||||||
}
|
|
||||||
if encrypt {
|
|
||||||
if c.bigDataSession == nil || len(c.bigDataSession.SessionKey) == 0 {
|
|
||||||
return nil, errors.New("session key not found. maybe miss some packet?")
|
|
||||||
}
|
|
||||||
ext = binary.NewTeaCipher(c.bigDataSession.SessionKey).Encrypt(ext)
|
|
||||||
}
|
|
||||||
const chunkSize = 256 * 1024
|
|
||||||
conn, err := net.DialTimeout("tcp", c.srvSsoAddrs[0], time.Second*20)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "connect error")
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
offset := 0
|
|
||||||
reader := binary.NewNetworkReader(conn)
|
|
||||||
if err = c.highwaySendHeartbreak(conn); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "echo error")
|
|
||||||
}
|
|
||||||
if _, _, err = highwayReadResponse(reader); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "echo error")
|
|
||||||
}
|
|
||||||
var rspExt []byte
|
|
||||||
buf := binary.Get256KBytes()
|
|
||||||
chunk := *buf
|
|
||||||
defer binary.Put256KBytes(buf)
|
|
||||||
|
|
||||||
w := binary.SelectWriter()
|
|
||||||
defer binary.PutWriter(w)
|
|
||||||
for {
|
|
||||||
chunk = chunk[:chunkSize]
|
|
||||||
rl, err := io.ReadFull(stream, chunk)
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
|
||||||
chunk = chunk[:rl]
|
|
||||||
}
|
|
||||||
ch := md5.Sum(chunk)
|
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
|
||||||
MsgBasehead: &pb.DataHighwayHead{
|
|
||||||
Version: 1,
|
|
||||||
Uin: strconv.FormatInt(c.Uin, 10),
|
|
||||||
Command: "PicUp.DataUp",
|
|
||||||
Seq: c.nextGroupDataTransSeq(),
|
|
||||||
Appid: int32(c.version.AppId),
|
|
||||||
Dataflag: 4096,
|
|
||||||
CommandId: cmdId,
|
|
||||||
LocaleId: 2052,
|
|
||||||
},
|
|
||||||
MsgSeghead: &pb.SegHead{
|
|
||||||
Filesize: length,
|
|
||||||
Dataoffset: int64(offset),
|
|
||||||
Datalength: int32(rl),
|
|
||||||
Serviceticket: ticket,
|
|
||||||
Md5: ch[:],
|
|
||||||
FileMd5: sum,
|
|
||||||
},
|
|
||||||
ReqExtendinfo: ext,
|
|
||||||
})
|
|
||||||
offset += rl
|
|
||||||
w.Reset()
|
|
||||||
w.WriteByte(40)
|
|
||||||
w.WriteUInt32(uint32(len(head)))
|
|
||||||
w.WriteUInt32(uint32(len(chunk)))
|
|
||||||
w.Write(head)
|
|
||||||
w.Write(chunk)
|
|
||||||
w.WriteByte(41)
|
|
||||||
_, err = conn.Write(w.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "write conn error")
|
|
||||||
}
|
|
||||||
rspHead, _, err := highwayReadResponse(reader)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "highway upload error")
|
|
||||||
}
|
|
||||||
if rspHead.ErrorCode != 0 {
|
|
||||||
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
|
|
||||||
}
|
|
||||||
if rspHead.RspExtendinfo != nil {
|
|
||||||
rspExt = rspHead.RspExtendinfo
|
|
||||||
}
|
|
||||||
if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil {
|
|
||||||
ticket = rspHead.MsgSeghead.Serviceticket
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rspExt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *QQClient) highwayUploadFileMultiThreadingByBDH(path string, cmdId int32, threadCount int, ticket, ext []byte, encrypt bool) ([]byte, error) {
|
|
||||||
if len(c.srvSsoAddrs) == 0 {
|
|
||||||
return nil, errors.New("srv addrs not found. maybe miss some packet?")
|
|
||||||
}
|
|
||||||
if encrypt {
|
|
||||||
if c.bigDataSession == nil || len(c.bigDataSession.SessionKey) == 0 {
|
|
||||||
return nil, errors.New("session key not found. maybe miss some packet?")
|
|
||||||
}
|
|
||||||
ext = binary.NewTeaCipher(c.bigDataSession.SessionKey).Encrypt(ext)
|
|
||||||
}
|
|
||||||
stat, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "get stat error")
|
|
||||||
}
|
|
||||||
file, err := os.OpenFile(path, os.O_RDONLY, 0o666)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "open file error")
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
h := md5.New()
|
|
||||||
length, _ := io.Copy(h, file)
|
|
||||||
fh := h.Sum(nil)
|
|
||||||
_, _ = file.Seek(0, io.SeekStart)
|
|
||||||
if stat.Size() < 1024*1024*3 || threadCount < 2 {
|
|
||||||
return c.highwayUploadByBDH(file, length, cmdId, ticket, fh, ext, false)
|
|
||||||
}
|
|
||||||
type BlockMetaData struct {
|
|
||||||
Id int
|
|
||||||
BeginOffset int64
|
|
||||||
EndOffset int64
|
|
||||||
}
|
|
||||||
const blockSize int64 = 1024 * 512
|
|
||||||
var (
|
|
||||||
blocks []*BlockMetaData
|
|
||||||
rspExt []byte
|
|
||||||
BlockId = ^uint32(0) // -1
|
|
||||||
uploadedCount uint32
|
|
||||||
cond = sync.NewCond(&sync.Mutex{})
|
|
||||||
)
|
|
||||||
// Init Blocks
|
|
||||||
{
|
|
||||||
var temp int64 = 0
|
|
||||||
for temp+blockSize < stat.Size() {
|
|
||||||
blocks = append(blocks, &BlockMetaData{
|
|
||||||
Id: len(blocks),
|
|
||||||
BeginOffset: temp,
|
|
||||||
EndOffset: temp + blockSize,
|
|
||||||
})
|
|
||||||
temp += blockSize
|
|
||||||
}
|
|
||||||
blocks = append(blocks, &BlockMetaData{
|
|
||||||
Id: len(blocks),
|
|
||||||
BeginOffset: temp,
|
|
||||||
EndOffset: stat.Size(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
doUpload := func() error {
|
|
||||||
defer cond.Signal()
|
|
||||||
|
|
||||||
conn, err := net.DialTimeout("tcp", c.srvSsoAddrs[0], time.Second*20)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "connect error")
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
chunk, _ := os.OpenFile(path, os.O_RDONLY, 0o666)
|
|
||||||
defer chunk.Close()
|
|
||||||
reader := binary.NewNetworkReader(conn)
|
|
||||||
if err = c.highwaySendHeartbreak(conn); err != nil {
|
|
||||||
return errors.Wrap(err, "echo error")
|
|
||||||
}
|
|
||||||
if _, _, err = highwayReadResponse(reader); err != nil {
|
|
||||||
return errors.Wrap(err, "echo error")
|
|
||||||
}
|
|
||||||
buffer := make([]byte, blockSize)
|
|
||||||
w := binary.SelectWriter()
|
|
||||||
w.Reset()
|
|
||||||
w.Grow(600 * 1024) // 复用,600k 不要放回池中
|
|
||||||
for {
|
|
||||||
nextId := atomic.AddUint32(&BlockId, 1)
|
|
||||||
if nextId >= uint32(len(blocks)) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
block := blocks[nextId]
|
|
||||||
if block.Id == len(blocks)-1 {
|
|
||||||
cond.L.Lock()
|
|
||||||
for atomic.LoadUint32(&uploadedCount) != uint32(len(blocks))-1 {
|
|
||||||
cond.Wait()
|
|
||||||
}
|
|
||||||
cond.L.Unlock()
|
|
||||||
}
|
|
||||||
buffer = buffer[:blockSize]
|
|
||||||
_, _ = chunk.Seek(block.BeginOffset, io.SeekStart)
|
|
||||||
ri, err := io.ReadFull(chunk, buffer)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err == io.ErrUnexpectedEOF {
|
|
||||||
buffer = buffer[:ri]
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ch := md5.Sum(buffer)
|
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
|
||||||
MsgBasehead: &pb.DataHighwayHead{
|
|
||||||
Version: 1,
|
|
||||||
Uin: strconv.FormatInt(c.Uin, 10),
|
|
||||||
Command: "PicUp.DataUp",
|
|
||||||
Seq: c.nextGroupDataTransSeq(),
|
|
||||||
Appid: int32(c.version.AppId),
|
|
||||||
Dataflag: 4096,
|
|
||||||
CommandId: cmdId,
|
|
||||||
LocaleId: 2052,
|
|
||||||
},
|
|
||||||
MsgSeghead: &pb.SegHead{
|
|
||||||
Filesize: stat.Size(),
|
|
||||||
Dataoffset: block.BeginOffset,
|
|
||||||
Datalength: int32(ri),
|
|
||||||
Serviceticket: ticket,
|
|
||||||
Md5: ch[:],
|
|
||||||
FileMd5: fh,
|
|
||||||
},
|
|
||||||
ReqExtendinfo: ext,
|
|
||||||
})
|
|
||||||
w.Reset()
|
|
||||||
w.WriteByte(40)
|
|
||||||
w.WriteUInt32(uint32(len(head)))
|
|
||||||
w.WriteUInt32(uint32(len(buffer)))
|
|
||||||
w.Write(head)
|
|
||||||
w.Write(buffer)
|
|
||||||
w.WriteByte(41)
|
|
||||||
_, err = conn.Write(w.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "write conn error")
|
|
||||||
}
|
|
||||||
rspHead, _, err := highwayReadResponse(reader)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "highway upload error")
|
|
||||||
}
|
|
||||||
if rspHead.ErrorCode != 0 {
|
|
||||||
return errors.Errorf("upload failed: %d", rspHead.ErrorCode)
|
|
||||||
}
|
|
||||||
if rspHead.RspExtendinfo != nil {
|
|
||||||
rspExt = rspHead.RspExtendinfo
|
|
||||||
}
|
|
||||||
atomic.AddUint32(&uploadedCount, 1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
group := errgroup.Group{}
|
|
||||||
for i := 0; i < threadCount; i++ {
|
|
||||||
group.Go(doUpload)
|
|
||||||
}
|
|
||||||
err = group.Wait()
|
|
||||||
return rspExt, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *QQClient) highwaySendHeartbreak(conn net.Conn) error {
|
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
|
||||||
MsgBasehead: &pb.DataHighwayHead{
|
|
||||||
Version: 1,
|
|
||||||
Uin: strconv.FormatInt(c.Uin, 10),
|
|
||||||
Command: "PicUp.Echo",
|
|
||||||
Seq: c.nextGroupDataTransSeq(),
|
|
||||||
Appid: int32(c.version.AppId),
|
|
||||||
Dataflag: 4096,
|
|
||||||
CommandId: 0,
|
|
||||||
LocaleId: 2052,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
w := binary.SelectWriter()
|
|
||||||
w.WriteByte(40)
|
|
||||||
w.WriteUInt32(uint32(len(head)))
|
|
||||||
w.WriteUInt32(0)
|
|
||||||
w.Write(head)
|
|
||||||
w.WriteByte(41)
|
|
||||||
_, err := conn.Write(w.Bytes())
|
|
||||||
binary.PutWriter(w)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func highwayReadResponse(r *binary.NetworkReader) (*pb.RspDataHighwayHead, []byte, error) {
|
|
||||||
_, err := r.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "failed to read byte")
|
|
||||||
}
|
|
||||||
hl, _ := r.ReadInt32()
|
|
||||||
a2, _ := r.ReadInt32()
|
|
||||||
head, _ := r.ReadBytes(int(hl))
|
|
||||||
payload, _ := r.ReadBytes(int(a2))
|
|
||||||
_, _ = r.ReadByte()
|
|
||||||
rsp := new(pb.RspDataHighwayHead)
|
|
||||||
if err = proto.Unmarshal(head, rsp); err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
|
||||||
}
|
|
||||||
return rsp, payload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *QQClient) excitingUploadStream(stream io.ReadSeeker, cmdId int32, ticket, ext []byte) ([]byte, error) {
|
|
||||||
fileMd5, fileLength := utils.ComputeMd5AndLength(stream)
|
|
||||||
_, _ = stream.Seek(0, io.SeekStart)
|
|
||||||
url := fmt.Sprintf("http://%v/cgi-bin/httpconn?htcmd=0x6FF0087&uin=%v", c.srvSsoAddrs[0], c.Uin)
|
|
||||||
var (
|
|
||||||
rspExt []byte
|
|
||||||
offset int64 = 0
|
|
||||||
chunkSize = 524288
|
|
||||||
)
|
|
||||||
chunk := make([]byte, chunkSize)
|
|
||||||
w := binary.SelectWriter()
|
|
||||||
w.Reset()
|
|
||||||
w.Grow(600 * 1024) // 复用,600k 不要放回池中
|
|
||||||
for {
|
|
||||||
chunk = chunk[:chunkSize]
|
|
||||||
rl, err := io.ReadFull(stream, chunk)
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err == io.ErrUnexpectedEOF {
|
|
||||||
chunk = chunk[:rl]
|
|
||||||
}
|
|
||||||
ch := md5.Sum(chunk)
|
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
|
||||||
MsgBasehead: &pb.DataHighwayHead{
|
|
||||||
Version: 1,
|
|
||||||
Uin: strconv.FormatInt(c.Uin, 10),
|
|
||||||
Command: "PicUp.DataUp",
|
|
||||||
Seq: c.nextGroupDataTransSeq(),
|
|
||||||
Appid: int32(c.version.AppId),
|
|
||||||
Dataflag: 0,
|
|
||||||
CommandId: cmdId,
|
|
||||||
LocaleId: 0,
|
|
||||||
},
|
|
||||||
MsgSeghead: &pb.SegHead{
|
|
||||||
Filesize: fileLength,
|
|
||||||
Dataoffset: offset,
|
|
||||||
Datalength: int32(rl),
|
|
||||||
Serviceticket: ticket,
|
|
||||||
Md5: ch[:],
|
|
||||||
FileMd5: fileMd5,
|
|
||||||
},
|
|
||||||
ReqExtendinfo: ext,
|
|
||||||
})
|
|
||||||
offset += int64(rl)
|
|
||||||
w.Reset()
|
|
||||||
w.WriteByte(40)
|
|
||||||
w.WriteUInt32(uint32(len(head)))
|
|
||||||
w.WriteUInt32(uint32(len(chunk)))
|
|
||||||
w.Write(head)
|
|
||||||
w.Write(chunk)
|
|
||||||
w.WriteByte(41)
|
|
||||||
req, _ := http.NewRequest("POST", url, bytes.NewReader(w.Bytes()))
|
|
||||||
req.Header.Set("Accept", "*/*")
|
|
||||||
req.Header.Set("Connection", "Keep-Alive")
|
|
||||||
req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
|
|
||||||
req.Header.Set("Pragma", "no-cache")
|
|
||||||
rsp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "request error")
|
|
||||||
}
|
|
||||||
body, _ := io.ReadAll(rsp.Body)
|
|
||||||
_ = rsp.Body.Close()
|
|
||||||
r := binary.NewReader(body)
|
|
||||||
r.ReadByte()
|
|
||||||
hl := r.ReadInt32()
|
|
||||||
a2 := r.ReadInt32()
|
|
||||||
h := r.ReadBytes(int(hl))
|
|
||||||
r.ReadBytes(int(a2))
|
|
||||||
rspHead := new(pb.RspDataHighwayHead)
|
|
||||||
if err = proto.Unmarshal(h, rspHead); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
|
||||||
}
|
|
||||||
if rspHead.ErrorCode != 0 {
|
|
||||||
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
|
|
||||||
}
|
|
||||||
if rspHead.RspExtendinfo != nil {
|
|
||||||
rspExt = rspHead.RspExtendinfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rspExt, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *QQClient) uploadGroupHeadPortrait(groupCode int64, img []byte) error {
|
|
||||||
url := fmt.Sprintf(
|
|
||||||
"http://htdata3.qq.com/cgi-bin/httpconn?htcmd=0x6ff0072&ver=5520&ukey=%v&range=0&uin=%v&seq=23&groupuin=%v&filetype=3&imagetype=5&userdata=0&subcmd=1&subver=101&clip=0_0_0_0&filesize=%v",
|
|
||||||
c.getSKey(),
|
|
||||||
c.Uin,
|
|
||||||
groupCode,
|
|
||||||
len(img),
|
|
||||||
)
|
|
||||||
req, _ := http.NewRequest("POST", url, bytes.NewReader(img))
|
|
||||||
req.Header["User-Agent"] = []string{"Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H)"}
|
|
||||||
req.Header["Content-Type"] = []string{"multipart/form-data;boundary=****"}
|
|
||||||
rsp, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to upload group head portrait")
|
|
||||||
}
|
|
||||||
rsp.Body.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
"image"
|
"image"
|
||||||
_ "image/gif"
|
_ "image/gif"
|
||||||
"io"
|
"io"
|
||||||
@ -15,8 +14,9 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
|
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/highway"
|
highway2 "github.com/Mrs4s/MiraiGo/client/pb/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/packets"
|
"github.com/Mrs4s/MiraiGo/internal/packets"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/proto"
|
"github.com/Mrs4s/MiraiGo/internal/proto"
|
||||||
@ -68,12 +68,18 @@ func (c *QQClient) UploadGroupImage(groupCode int64, img io.ReadSeeker) (*messag
|
|||||||
if rsp.IsExists {
|
if rsp.IsExists {
|
||||||
goto ok
|
goto ok
|
||||||
}
|
}
|
||||||
if len(c.srvSsoAddrs) == 0 {
|
if c.highwaySession.AddrLength() == 0 {
|
||||||
for i, addr := range rsp.UploadIp {
|
for i, addr := range rsp.UploadIp {
|
||||||
c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr), rsp.UploadPort[i]))
|
c.highwaySession.AppendAddr(addr, rsp.UploadPort[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err = c.highwayUploadByBDH(img, length, 2, rsp.UploadKey, fh, EmptyBytes, false); err == nil {
|
if _, err = c.highwaySession.UploadBDH(highway.BdhInput{
|
||||||
|
CommandID: 2,
|
||||||
|
Body: img,
|
||||||
|
Ticket: rsp.UploadKey,
|
||||||
|
Ext: EmptyBytes,
|
||||||
|
Encrypt: false,
|
||||||
|
}); err == nil {
|
||||||
goto ok
|
goto ok
|
||||||
}
|
}
|
||||||
return nil, errors.Wrap(err, "upload failed")
|
return nil, errors.Wrap(err, "upload failed")
|
||||||
@ -114,12 +120,19 @@ func (c *QQClient) UploadGroupImageByFile(groupCode int64, path string) (*messag
|
|||||||
if rsp.IsExists {
|
if rsp.IsExists {
|
||||||
goto ok
|
goto ok
|
||||||
}
|
}
|
||||||
if len(c.srvSsoAddrs) == 0 {
|
if c.highwaySession.AddrLength() == 0 {
|
||||||
for i, addr := range rsp.UploadIp {
|
for i, addr := range rsp.UploadIp {
|
||||||
c.srvSsoAddrs = append(c.srvSsoAddrs, fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(addr), rsp.UploadPort[i]))
|
c.highwaySession.AppendAddr(addr, rsp.UploadPort[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err = c.highwayUploadFileMultiThreadingByBDH(path, 2, 1, rsp.UploadKey, EmptyBytes, false); err == nil {
|
|
||||||
|
if _, err = c.highwaySession.UploadBDHMultiThread(highway.BdhInput{
|
||||||
|
CommandID: 2,
|
||||||
|
File: path,
|
||||||
|
Ticket: rsp.UploadKey,
|
||||||
|
Ext: EmptyBytes,
|
||||||
|
Encrypt: false,
|
||||||
|
}, 1); err == nil {
|
||||||
goto ok
|
goto ok
|
||||||
}
|
}
|
||||||
return nil, errors.Wrap(err, "upload failed")
|
return nil, errors.Wrap(err, "upload failed")
|
||||||
@ -176,7 +189,7 @@ func (c *QQClient) ImageOcr(img interface{}) (*OcrResponse, error) {
|
|||||||
case *message.GroupImageElement:
|
case *message.GroupImageElement:
|
||||||
url = e.Url
|
url = e.Url
|
||||||
if b, err := utils.HTTPGetReadCloser(e.Url, ""); err == nil {
|
if b, err := utils.HTTPGetReadCloser(e.Url, ""); err == nil {
|
||||||
if url, err = c.uploadOcrImage(b, int64(e.Size), e.Md5); err != nil {
|
if url, err = c.uploadOcrImage(b); err != nil {
|
||||||
url = e.Url
|
url = e.Url
|
||||||
}
|
}
|
||||||
_ = b.Close()
|
_ = b.Close()
|
||||||
@ -285,18 +298,26 @@ func (c *QQClient) buildGroupImageDownloadPacket(fileId, groupCode int64, fileMd
|
|||||||
return seq, packet
|
return seq, packet
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *QQClient) uploadOcrImage(img io.Reader, length int64, sum []byte) (string, error) {
|
func (c *QQClient) uploadOcrImage(img io.Reader) (string, error) {
|
||||||
r := make([]byte, 16)
|
r := make([]byte, 16)
|
||||||
rand.Read(r)
|
rand.Read(r)
|
||||||
ext, _ := proto.Marshal(&highway.CommFileExtReq{
|
ext, _ := proto.Marshal(&highway2.CommFileExtReq{
|
||||||
ActionType: proto.Uint32(0),
|
ActionType: proto.Uint32(0),
|
||||||
Uuid: binary.GenUUID(r),
|
Uuid: binary.GenUUID(r),
|
||||||
})
|
})
|
||||||
rsp, err := c.highwayUploadByBDH(img, length, 76, c.bigDataSession.SigSession, sum, ext, false)
|
|
||||||
|
buf, _ := io.ReadAll(img)
|
||||||
|
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
|
||||||
|
CommandID: 76,
|
||||||
|
Body: bytes.NewReader(buf),
|
||||||
|
Ticket: c.highwaySession.SigSession,
|
||||||
|
Ext: ext,
|
||||||
|
Encrypt: false,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "upload ocr image error")
|
return "", errors.Wrap(err, "upload ocr image error")
|
||||||
}
|
}
|
||||||
rspExt := highway.CommFileExtRsp{}
|
rspExt := highway2.CommFileExtRsp{}
|
||||||
if err = proto.Unmarshal(rsp, &rspExt); err != nil {
|
if err = proto.Unmarshal(rsp, &rspExt); err != nil {
|
||||||
return "", errors.Wrap(err, "error unmarshal highway resp")
|
return "", errors.Wrap(err, "error unmarshal highway resp")
|
||||||
}
|
}
|
||||||
|
31
client/internal/highway/addr.go
Normal file
31
client/internal/highway/addr.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package highway
|
||||||
|
|
||||||
|
import (
|
||||||
|
binary2 "encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Addr struct {
|
||||||
|
IP uint32
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Addr) asTcpAddr() *net.TCPAddr {
|
||||||
|
addr := &net.TCPAddr{
|
||||||
|
IP: make([]byte, 4),
|
||||||
|
Port: a.Port,
|
||||||
|
}
|
||||||
|
binary2.LittleEndian.PutUint32(addr.IP, a.IP)
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Addr) AsNetIP() net.IP {
|
||||||
|
return net.IPv4(byte(a.IP>>24), byte(a.IP>>16), byte(a.IP>>8), byte(a.IP))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Addr) String() string {
|
||||||
|
return fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(a.IP), a.Port)
|
||||||
|
}
|
275
client/internal/highway/bdh.go
Normal file
275
client/internal/highway/bdh.go
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
package highway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/pb"
|
||||||
|
"github.com/Mrs4s/MiraiGo/internal/proto"
|
||||||
|
"github.com/Mrs4s/MiraiGo/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BdhInput struct {
|
||||||
|
CommandID int32
|
||||||
|
File string // upload multi-thread required
|
||||||
|
Body io.ReadSeeker
|
||||||
|
Ticket []byte
|
||||||
|
Ext []byte
|
||||||
|
Encrypt bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bdh *BdhInput) encrypt(key []byte) error {
|
||||||
|
if bdh.Encrypt {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return errors.New("session key not found. maybe miss some packet?")
|
||||||
|
}
|
||||||
|
bdh.Ext = binary.NewTeaCipher(key).Encrypt(bdh.Ext)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) UploadBDH(input BdhInput) ([]byte, error) {
|
||||||
|
if len(s.SsoAddr) == 0 {
|
||||||
|
return nil, errors.New("srv addrs not found. maybe miss some packet?")
|
||||||
|
}
|
||||||
|
addr := s.SsoAddr[0].String()
|
||||||
|
|
||||||
|
sum, length := utils.ComputeMd5AndLength(input.Body)
|
||||||
|
_, _ = input.Body.Seek(0, io.SeekStart)
|
||||||
|
if err := input.encrypt(s.SessionKey); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "encrypt error")
|
||||||
|
}
|
||||||
|
conn, err := net.DialTimeout("tcp", addr, time.Second*20)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "connect error")
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
offset := 0
|
||||||
|
reader := binary.NewNetworkReader(conn)
|
||||||
|
if err = s.sendEcho(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rspExt []byte
|
||||||
|
const chunkSize = 256 * 1024
|
||||||
|
chunk := make([]byte, chunkSize)
|
||||||
|
w := binary.SelectWriter()
|
||||||
|
defer binary.PutWriter(w)
|
||||||
|
for {
|
||||||
|
chunk = chunk[:chunkSize]
|
||||||
|
rl, err := io.ReadFull(input.Body, chunk)
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
chunk = chunk[:rl]
|
||||||
|
}
|
||||||
|
ch := md5.Sum(chunk)
|
||||||
|
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
||||||
|
MsgBasehead: &pb.DataHighwayHead{
|
||||||
|
Version: 1,
|
||||||
|
Uin: s.uin,
|
||||||
|
Command: "PicUp.DataUp",
|
||||||
|
Seq: s.nextSeq(),
|
||||||
|
Appid: s.appID,
|
||||||
|
Dataflag: 4096,
|
||||||
|
CommandId: input.CommandID,
|
||||||
|
LocaleId: 2052,
|
||||||
|
},
|
||||||
|
MsgSeghead: &pb.SegHead{
|
||||||
|
Filesize: length,
|
||||||
|
Dataoffset: int64(offset),
|
||||||
|
Datalength: int32(rl),
|
||||||
|
Serviceticket: input.Ticket,
|
||||||
|
Md5: ch[:],
|
||||||
|
FileMd5: sum,
|
||||||
|
},
|
||||||
|
ReqExtendinfo: input.Ext,
|
||||||
|
})
|
||||||
|
offset += rl
|
||||||
|
w.Reset()
|
||||||
|
writeHeadBody(w, head, chunk)
|
||||||
|
_, err = conn.Write(w.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "write conn error")
|
||||||
|
}
|
||||||
|
rspHead, _, err := readResponse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "highway upload error")
|
||||||
|
}
|
||||||
|
if rspHead.ErrorCode != 0 {
|
||||||
|
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
|
||||||
|
}
|
||||||
|
if rspHead.RspExtendinfo != nil {
|
||||||
|
rspExt = rspHead.RspExtendinfo
|
||||||
|
}
|
||||||
|
if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil {
|
||||||
|
input.Ticket = rspHead.MsgSeghead.Serviceticket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rspExt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) UploadBDHMultiThread(input BdhInput, threadCount int) ([]byte, error) {
|
||||||
|
if len(s.SsoAddr) == 0 {
|
||||||
|
return nil, errors.New("srv addrs not found. maybe miss some packet?")
|
||||||
|
}
|
||||||
|
addr := s.SsoAddr[0].String()
|
||||||
|
|
||||||
|
stat, err := os.Stat(input.File)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "get stat error")
|
||||||
|
}
|
||||||
|
file, err := os.OpenFile(input.File, os.O_RDONLY, 0o666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "open file error")
|
||||||
|
}
|
||||||
|
sum, length := utils.ComputeMd5AndLength(file)
|
||||||
|
_, _ = file.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
|
if err := input.encrypt(s.SessionKey); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "encrypt error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// for small file and small thread count,
|
||||||
|
// use UploadBDH instead of UploadBDHMultiThread
|
||||||
|
if length < 1024*1024*3 || threadCount < 2 {
|
||||||
|
input.Body = file
|
||||||
|
return s.UploadBDH(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockMetaData struct {
|
||||||
|
Id int
|
||||||
|
BeginOffset int64
|
||||||
|
EndOffset int64
|
||||||
|
}
|
||||||
|
const blockSize int64 = 1024 * 512
|
||||||
|
var (
|
||||||
|
blocks []*BlockMetaData
|
||||||
|
rspExt []byte
|
||||||
|
BlockId = ^uint32(0) // -1
|
||||||
|
uploadedCount uint32
|
||||||
|
cond = sync.NewCond(&sync.Mutex{})
|
||||||
|
)
|
||||||
|
// Init Blocks
|
||||||
|
{
|
||||||
|
var temp int64 = 0
|
||||||
|
for temp+blockSize < stat.Size() {
|
||||||
|
blocks = append(blocks, &BlockMetaData{
|
||||||
|
Id: len(blocks),
|
||||||
|
BeginOffset: temp,
|
||||||
|
EndOffset: temp + blockSize,
|
||||||
|
})
|
||||||
|
temp += blockSize
|
||||||
|
}
|
||||||
|
blocks = append(blocks, &BlockMetaData{
|
||||||
|
Id: len(blocks),
|
||||||
|
BeginOffset: temp,
|
||||||
|
EndOffset: stat.Size(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
doUpload := func() error {
|
||||||
|
// send signal complete uploading
|
||||||
|
defer cond.Signal()
|
||||||
|
|
||||||
|
conn, err := net.DialTimeout("tcp", addr, time.Second*20)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "connect error")
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
chunk, _ := os.OpenFile(input.File, os.O_RDONLY, 0o666)
|
||||||
|
defer chunk.Close()
|
||||||
|
reader := binary.NewNetworkReader(conn)
|
||||||
|
if err = s.sendEcho(conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer := make([]byte, blockSize)
|
||||||
|
w := binary.SelectWriter()
|
||||||
|
w.Reset()
|
||||||
|
w.Grow(600 * 1024) // 复用,600k 不要放回池中
|
||||||
|
for {
|
||||||
|
nextId := atomic.AddUint32(&BlockId, 1)
|
||||||
|
if nextId >= uint32(len(blocks)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
block := blocks[nextId]
|
||||||
|
if block.Id == len(blocks)-1 {
|
||||||
|
cond.L.Lock()
|
||||||
|
for atomic.LoadUint32(&uploadedCount) != uint32(len(blocks))-1 {
|
||||||
|
cond.Wait()
|
||||||
|
}
|
||||||
|
cond.L.Unlock()
|
||||||
|
}
|
||||||
|
buffer = buffer[:blockSize]
|
||||||
|
_, _ = chunk.Seek(block.BeginOffset, io.SeekStart)
|
||||||
|
ri, err := io.ReadFull(chunk, buffer)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
buffer = buffer[:ri]
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch := md5.Sum(buffer)
|
||||||
|
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
||||||
|
MsgBasehead: &pb.DataHighwayHead{
|
||||||
|
Version: 1,
|
||||||
|
Uin: s.uin,
|
||||||
|
Command: "PicUp.DataUp",
|
||||||
|
Seq: s.nextSeq(),
|
||||||
|
Appid: s.appID,
|
||||||
|
Dataflag: 4096,
|
||||||
|
CommandId: input.CommandID,
|
||||||
|
LocaleId: 2052,
|
||||||
|
},
|
||||||
|
MsgSeghead: &pb.SegHead{
|
||||||
|
Filesize: stat.Size(),
|
||||||
|
Dataoffset: block.BeginOffset,
|
||||||
|
Datalength: int32(ri),
|
||||||
|
Serviceticket: input.Ticket,
|
||||||
|
Md5: ch[:],
|
||||||
|
FileMd5: sum,
|
||||||
|
},
|
||||||
|
ReqExtendinfo: input.Ext,
|
||||||
|
})
|
||||||
|
w.Reset()
|
||||||
|
writeHeadBody(w, head, buffer)
|
||||||
|
_, err = conn.Write(w.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "write conn error")
|
||||||
|
}
|
||||||
|
rspHead, _, err := readResponse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "highway upload error")
|
||||||
|
}
|
||||||
|
if rspHead.ErrorCode != 0 {
|
||||||
|
return errors.Errorf("upload failed: %d", rspHead.ErrorCode)
|
||||||
|
}
|
||||||
|
if rspHead.RspExtendinfo != nil {
|
||||||
|
rspExt = rspHead.RspExtendinfo
|
||||||
|
}
|
||||||
|
atomic.AddUint32(&uploadedCount, 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
group := errgroup.Group{}
|
||||||
|
for i := 0; i < threadCount; i++ {
|
||||||
|
group.Go(doUpload)
|
||||||
|
}
|
||||||
|
err = group.Wait()
|
||||||
|
return rspExt, err
|
||||||
|
}
|
265
client/internal/highway/highway.go
Normal file
265
client/internal/highway/highway.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package highway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/pb"
|
||||||
|
"github.com/Mrs4s/MiraiGo/internal/proto"
|
||||||
|
"github.com/Mrs4s/MiraiGo/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
SigSession []byte
|
||||||
|
SessionKey []byte
|
||||||
|
SsoAddr []Addr
|
||||||
|
|
||||||
|
seq int32
|
||||||
|
appID int32
|
||||||
|
uin string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSession(appID int32, uin int64) *Session {
|
||||||
|
return &Session{
|
||||||
|
appID: appID,
|
||||||
|
uin: strconv.FormatInt(uin, 10),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) AddrLength() int {
|
||||||
|
return len(s.SsoAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) AppendAddr(ip, port uint32) {
|
||||||
|
addr := Addr{
|
||||||
|
IP: ip,
|
||||||
|
Port: int(port),
|
||||||
|
}
|
||||||
|
s.SsoAddr = append(s.SsoAddr, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Input struct {
|
||||||
|
CommandID int32
|
||||||
|
Key []byte
|
||||||
|
Body io.ReadSeeker
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) Upload(addr Addr, input Input) error {
|
||||||
|
fh, length := utils.ComputeMd5AndLength(input.Body)
|
||||||
|
_, _ = input.Body.Seek(0, io.SeekStart)
|
||||||
|
conn, err := net.DialTCP("tcp", nil, addr.asTcpAddr())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "connect error")
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
const chunkSize = 8192 * 8
|
||||||
|
chunk := make([]byte, chunkSize)
|
||||||
|
offset := 0
|
||||||
|
reader := binary.NewNetworkReader(conn)
|
||||||
|
w := binary.SelectWriter()
|
||||||
|
defer binary.PutWriter(w)
|
||||||
|
for {
|
||||||
|
chunk = chunk[:chunkSize]
|
||||||
|
rl, err := io.ReadFull(input.Body, chunk)
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
chunk = chunk[:rl]
|
||||||
|
}
|
||||||
|
ch := md5.Sum(chunk)
|
||||||
|
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
||||||
|
MsgBasehead: &pb.DataHighwayHead{
|
||||||
|
Version: 1,
|
||||||
|
Uin: s.uin,
|
||||||
|
Command: "PicUp.DataUp",
|
||||||
|
Seq: s.nextSeq(),
|
||||||
|
Appid: s.appID,
|
||||||
|
Dataflag: 4096,
|
||||||
|
CommandId: input.CommandID,
|
||||||
|
LocaleId: 2052,
|
||||||
|
},
|
||||||
|
MsgSeghead: &pb.SegHead{
|
||||||
|
Filesize: length,
|
||||||
|
Dataoffset: int64(offset),
|
||||||
|
Datalength: int32(rl),
|
||||||
|
Serviceticket: input.Key,
|
||||||
|
Md5: ch[:],
|
||||||
|
FileMd5: fh,
|
||||||
|
},
|
||||||
|
ReqExtendinfo: []byte{},
|
||||||
|
})
|
||||||
|
offset += rl
|
||||||
|
w.Reset()
|
||||||
|
writeHeadBody(w, head, chunk)
|
||||||
|
_, err = conn.Write(w.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "write conn error")
|
||||||
|
}
|
||||||
|
rspHead, _, err := readResponse(reader)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "highway upload error")
|
||||||
|
}
|
||||||
|
if rspHead.ErrorCode != 0 {
|
||||||
|
return errors.New("upload failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExcitingInput struct {
|
||||||
|
CommandID int32
|
||||||
|
Body io.ReadSeeker
|
||||||
|
Ticket []byte
|
||||||
|
Ext []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) UploadExciting(input ExcitingInput) ([]byte, error) {
|
||||||
|
fileMd5, fileLength := utils.ComputeMd5AndLength(input.Body)
|
||||||
|
_, _ = input.Body.Seek(0, io.SeekStart)
|
||||||
|
addr := s.SsoAddr[0]
|
||||||
|
url := fmt.Sprintf("http://%v/cgi-bin/httpconn?htcmd=0x6FF0087&uin=%v", addr, s.uin)
|
||||||
|
var (
|
||||||
|
rspExt []byte
|
||||||
|
offset int64 = 0
|
||||||
|
chunkSize = 524288
|
||||||
|
)
|
||||||
|
chunk := make([]byte, chunkSize)
|
||||||
|
w := binary.SelectWriter()
|
||||||
|
w.Reset()
|
||||||
|
w.Grow(600 * 1024) // 复用,600k 不要放回池中
|
||||||
|
for {
|
||||||
|
chunk = chunk[:chunkSize]
|
||||||
|
rl, err := io.ReadFull(input.Body, chunk)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
chunk = chunk[:rl]
|
||||||
|
}
|
||||||
|
ch := md5.Sum(chunk)
|
||||||
|
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
||||||
|
MsgBasehead: &pb.DataHighwayHead{
|
||||||
|
Version: 1,
|
||||||
|
Uin: s.uin,
|
||||||
|
Command: "PicUp.DataUp",
|
||||||
|
Seq: s.nextSeq(),
|
||||||
|
Appid: s.appID,
|
||||||
|
Dataflag: 0,
|
||||||
|
CommandId: input.CommandID,
|
||||||
|
LocaleId: 0,
|
||||||
|
},
|
||||||
|
MsgSeghead: &pb.SegHead{
|
||||||
|
Filesize: fileLength,
|
||||||
|
Dataoffset: offset,
|
||||||
|
Datalength: int32(rl),
|
||||||
|
Serviceticket: input.Ticket,
|
||||||
|
Md5: ch[:],
|
||||||
|
FileMd5: fileMd5,
|
||||||
|
},
|
||||||
|
ReqExtendinfo: input.Ext,
|
||||||
|
})
|
||||||
|
offset += int64(rl)
|
||||||
|
w.Reset()
|
||||||
|
writeHeadBody(w, head, chunk)
|
||||||
|
req, _ := http.NewRequest("POST", url, bytes.NewReader(w.Bytes()))
|
||||||
|
req.Header.Set("Accept", "*/*")
|
||||||
|
req.Header.Set("Connection", "Keep-Alive")
|
||||||
|
req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
|
||||||
|
req.Header.Set("Pragma", "no-cache")
|
||||||
|
rsp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "request error")
|
||||||
|
}
|
||||||
|
body, _ := io.ReadAll(rsp.Body)
|
||||||
|
_ = rsp.Body.Close()
|
||||||
|
r := binary.NewReader(body)
|
||||||
|
r.ReadByte()
|
||||||
|
hl := r.ReadInt32()
|
||||||
|
a2 := r.ReadInt32()
|
||||||
|
h := r.ReadBytes(int(hl))
|
||||||
|
r.ReadBytes(int(a2))
|
||||||
|
rspHead := new(pb.RspDataHighwayHead)
|
||||||
|
if err = proto.Unmarshal(h, rspHead); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
|
}
|
||||||
|
if rspHead.ErrorCode != 0 {
|
||||||
|
return nil, errors.Errorf("upload failed: %d", rspHead.ErrorCode)
|
||||||
|
}
|
||||||
|
if rspHead.RspExtendinfo != nil {
|
||||||
|
rspExt = rspHead.RspExtendinfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rspExt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) nextSeq() int32 {
|
||||||
|
return atomic.AddInt32(&s.seq, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) sendHeartbreak(conn net.Conn) error {
|
||||||
|
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
||||||
|
MsgBasehead: &pb.DataHighwayHead{
|
||||||
|
Version: 1,
|
||||||
|
Uin: s.uin,
|
||||||
|
Command: "PicUp.Echo",
|
||||||
|
Seq: s.nextSeq(),
|
||||||
|
Appid: s.appID,
|
||||||
|
Dataflag: 4096,
|
||||||
|
CommandId: 0,
|
||||||
|
LocaleId: 2052,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
w := binary.SelectWriter()
|
||||||
|
writeHeadBody(w, head, nil)
|
||||||
|
_, err := conn.Write(w.Bytes())
|
||||||
|
binary.PutWriter(w)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Session) sendEcho(conn net.Conn) error {
|
||||||
|
err := s.sendHeartbreak(conn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "echo error")
|
||||||
|
}
|
||||||
|
if _, _, err = readResponse(binary.NewNetworkReader(conn)); err != nil {
|
||||||
|
return errors.Wrap(err, "echo error")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeHeadBody(w *binary.Writer, head []byte, body []byte) {
|
||||||
|
w.WriteByte(40)
|
||||||
|
w.WriteUInt32(uint32(len(head)))
|
||||||
|
w.WriteUInt32(uint32(len(body)))
|
||||||
|
w.Write(head)
|
||||||
|
w.Write(body)
|
||||||
|
w.WriteByte(41)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readResponse(r *binary.NetworkReader) (*pb.RspDataHighwayHead, []byte, error) {
|
||||||
|
_, err := r.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "failed to read byte")
|
||||||
|
}
|
||||||
|
hl, _ := r.ReadInt32()
|
||||||
|
a2, _ := r.ReadInt32()
|
||||||
|
head, _ := r.ReadBytes(int(hl))
|
||||||
|
payload, _ := r.ReadBytes(int(a2))
|
||||||
|
_, _ = r.ReadByte()
|
||||||
|
rsp := new(pb.RspDataHighwayHead)
|
||||||
|
if err = proto.Unmarshal(head, rsp); err != nil {
|
||||||
|
return nil, nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
|
}
|
||||||
|
return rsp, payload, nil
|
||||||
|
}
|
@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@ -40,7 +39,8 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo {
|
|||||||
r := &ConnectionQualityInfo{}
|
r := &ConnectionQualityInfo{}
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
go func(w *sync.WaitGroup) {
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if r.ChatServerLatency, err = qualityTest(c.servers[c.currServerIndex].String()); err != nil {
|
if r.ChatServerLatency, err = qualityTest(c.servers[c.currServerIndex].String()); err != nil {
|
||||||
@ -57,24 +57,22 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo {
|
|||||||
c.Error("resolve long message server error: %v", err)
|
c.Error("resolve long message server error: %v", err)
|
||||||
r.LongMessageServerLatency = 9999
|
r.LongMessageServerLatency = 9999
|
||||||
}
|
}
|
||||||
if len(c.srvSsoAddrs) > 0 {
|
if c.highwaySession.AddrLength() > 0 {
|
||||||
if r.SrvServerLatency, err = qualityTest(c.srvSsoAddrs[0]); err != nil {
|
if r.SrvServerLatency, err = qualityTest(c.highwaySession.SsoAddr[0].String()); err != nil {
|
||||||
c.Error("test srv server latency error: %v", err)
|
c.Error("test srv server latency error: %v", err)
|
||||||
r.SrvServerLatency = 9999
|
r.SrvServerLatency = 9999
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
w.Done()
|
go func() {
|
||||||
}(&wg)
|
defer wg.Done()
|
||||||
go func(w *sync.WaitGroup) {
|
|
||||||
res := utils.RunICMPPingLoop(&net.IPAddr{IP: c.servers[c.currServerIndex].IP}, 10)
|
res := utils.RunICMPPingLoop(&net.IPAddr{IP: c.servers[c.currServerIndex].IP}, 10)
|
||||||
r.ChatServerPacketLoss = res.PacketsLoss
|
r.ChatServerPacketLoss = res.PacketsLoss
|
||||||
if len(c.srvSsoAddrs) > 0 {
|
if c.highwaySession.AddrLength() > 0 {
|
||||||
res = utils.RunICMPPingLoop(&net.IPAddr{IP: net.ParseIP(strings.Split(c.srvSsoAddrs[0], ":")[0])}, 10)
|
res = utils.RunICMPPingLoop(&net.IPAddr{IP: c.highwaySession.SsoAddr[0].AsNetIP()}, 10)
|
||||||
r.SrvServerPacketLoss = res.PacketsLoss
|
r.SrvServerPacketLoss = res.PacketsLoss
|
||||||
}
|
}
|
||||||
w.Done()
|
}()
|
||||||
}(&wg)
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
if _, err := utils.HttpGetBytes("https://ssl.htdata.qq.com", ""); err == nil {
|
if _, err := utils.HttpGetBytes("https://ssl.htdata.qq.com", ""); err == nil {
|
||||||
r.LongMessageServerResponseLatency = time.Now().Sub(start).Milliseconds()
|
r.LongMessageServerResponseLatency = time.Now().Sub(start).Milliseconds()
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x346"
|
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x346"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
|
"github.com/Mrs4s/MiraiGo/client/pb/cmd0x388"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||||
@ -28,17 +29,21 @@ var pttWaiter = utils.NewUploadWaiter()
|
|||||||
|
|
||||||
// UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送
|
// UploadGroupPtt 将语音数据使用群语音通道上传到服务器, 返回 message.GroupVoiceElement 可直接发送
|
||||||
func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) {
|
func (c *QQClient) UploadGroupPtt(groupCode int64, voice io.ReadSeeker) (*message.GroupVoiceElement, error) {
|
||||||
h := md5.New()
|
fh, length := utils.ComputeMd5AndLength(voice)
|
||||||
length, _ := io.Copy(h, voice)
|
|
||||||
fh := h.Sum(nil)
|
|
||||||
_, _ = voice.Seek(0, io.SeekStart)
|
_, _ = voice.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
key := hex.EncodeToString(fh)
|
key := string(fh)
|
||||||
pttWaiter.Wait(key)
|
pttWaiter.Wait(key)
|
||||||
defer pttWaiter.Done(key)
|
defer pttWaiter.Done(key)
|
||||||
|
|
||||||
ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length))
|
ext := c.buildGroupPttStoreBDHExt(groupCode, fh[:], int32(length), 0, int32(length))
|
||||||
rsp, err := c.highwayUploadByBDH(voice, length, 29, c.bigDataSession.SigSession, fh, ext, false)
|
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
|
||||||
|
CommandID: 29,
|
||||||
|
Body: voice,
|
||||||
|
Ticket: c.highwaySession.SigSession,
|
||||||
|
Ext: ext,
|
||||||
|
Encrypt: false,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -78,7 +83,13 @@ func (c *QQClient) UploadPrivatePtt(target int64, voice io.ReadSeeker) (*message
|
|||||||
defer pttWaiter.Done(key)
|
defer pttWaiter.Done(key)
|
||||||
|
|
||||||
ext := c.buildC2CPttStoreBDHExt(target, fh[:], int32(length), int32(length))
|
ext := c.buildC2CPttStoreBDHExt(target, fh[:], int32(length), int32(length))
|
||||||
rsp, err := c.highwayUploadByBDH(voice, length, 26, c.bigDataSession.SigSession, fh, ext, false)
|
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
|
||||||
|
CommandID: 26,
|
||||||
|
Body: voice,
|
||||||
|
Ticket: c.highwaySession.SigSession,
|
||||||
|
Ext: ext,
|
||||||
|
Encrypt: false,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -135,12 +146,17 @@ func (c *QQClient) UploadGroupShortVideo(groupCode int64, video, thumb io.ReadSe
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
ext, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, groupCode, videoLen, thumbLen, 1).PttShortVideoUploadReq)
|
ext, _ := proto.Marshal(c.buildPttGroupShortVideoProto(videoHash, thumbHash, groupCode, videoLen, thumbLen, 1).PttShortVideoUploadReq)
|
||||||
|
|
||||||
var hwRsp []byte
|
var hwRsp []byte
|
||||||
multi := utils.MultiReadSeeker(thumb, video)
|
multi := utils.MultiReadSeeker(thumb, video)
|
||||||
h := md5.New()
|
input := highway.BdhInput{
|
||||||
length, _ := io.Copy(h, multi)
|
CommandID: 25,
|
||||||
fh := h.Sum(nil)
|
File: cache,
|
||||||
_, _ = multi.Seek(0, io.SeekStart)
|
Body: multi,
|
||||||
|
Ticket: c.highwaySession.SigSession,
|
||||||
|
Ext: ext,
|
||||||
|
Encrypt: true,
|
||||||
|
}
|
||||||
if cache != "" {
|
if cache != "" {
|
||||||
var file *os.File
|
var file *os.File
|
||||||
file, err = os.OpenFile(cache, os.O_WRONLY|os.O_CREATE, 0o666)
|
file, err = os.OpenFile(cache, os.O_WRONLY|os.O_CREATE, 0o666)
|
||||||
@ -149,14 +165,14 @@ func (c *QQClient) UploadGroupShortVideo(groupCode int64, video, thumb io.ReadSe
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err != nil || cp() != nil {
|
if err != nil || cp() != nil {
|
||||||
hwRsp, err = c.highwayUploadByBDH(multi, length, 25, c.bigDataSession.SigSession, fh, ext, true)
|
hwRsp, err = c.highwaySession.UploadBDH(input)
|
||||||
} else {
|
} else {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
hwRsp, err = c.highwayUploadFileMultiThreadingByBDH(cache, 25, 8, c.bigDataSession.SigSession, ext, true)
|
hwRsp, err = c.highwaySession.UploadBDHMultiThread(input, 8)
|
||||||
_ = os.Remove(cache)
|
_ = os.Remove(cache)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
hwRsp, err = c.highwayUploadByBDH(multi, length, 25, c.bigDataSession.SigSession, fh, ext, true)
|
hwRsp, err = c.highwaySession.UploadBDH(input)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "upload video file error")
|
return nil, errors.Wrap(err, "upload video file error")
|
||||||
|
@ -80,7 +80,7 @@ func main() {
|
|||||||
fmt.Printf("%s\n", buf.Bytes())
|
fmt.Printf("%s\n", buf.Bytes())
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
_ = os.WriteFile(*output, formated, 0644)
|
_ = os.WriteFile(*output, formated, 0o644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g Generator) Generate(w io.Writer) {
|
func (g Generator) Generate(w io.Writer) {
|
||||||
|
@ -58,9 +58,7 @@ type (
|
|||||||
content map[string]interface{}
|
content map[string]interface{}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var globalBlockId int64 = 0
|
||||||
globalBlockId int64 = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
func genBlockId() string {
|
func genBlockId() string {
|
||||||
id := atomic.AddInt64(&globalBlockId, 1)
|
id := atomic.AddInt64(&globalBlockId, 1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user