1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 19:17:38 +08:00

feature GetAllowedClients() & OnlineClients.

This commit is contained in:
Mrs4s 2021-01-20 21:12:07 +08:00
parent b007a4d0d6
commit a618d101db
11 changed files with 330 additions and 71 deletions

View File

@ -136,6 +136,65 @@ type (
ClientAutoStatusInterval int64 `jceId:"19"` ClientAutoStatusInterval int64 `jceId:"19"`
} }
SvcReqRegisterNew struct {
IJceStruct
RequestOptional int64 `jceId:"0"`
C2CMsg IJceStruct `jceId:"1"` // SvcReqGetMsgV2
GroupMsg IJceStruct `jceId:"2"` // SvcReqPullGroupMsgSeq
GroupMask byte `jceId:"15"`
EndSeq int64 `jceId:"16"`
}
SvcReqGetMsgV2 struct {
IJceStruct
Uin int64 `jceId:"0"`
DateTime int32 `jceId:"1"`
RecivePic byte `jceId:"4"`
Ability int16 `jceId:"6"`
Channel byte `jceId:"9"`
Inst byte `jceId:"16"`
ChannelEx byte `jceId:"17"`
SyncCookie []byte `jceId:"18"`
SyncFlag int `jceId:"19"`
RambleFlag byte `jceId:"20"`
GeneralAbi int64 `jceId:"26"`
PubAccountCookie []byte `jceId:"27"`
}
SvcReqPullGroupMsgSeq struct {
IJceStruct
GroupInfo []IJceStruct `jceId:"0"` // PullGroupSeqParam
VerifyType byte `jceId:"1"`
Filter int32 `jceId:"2"`
}
PullGroupSeqParam struct {
IJceStruct
GroupCode int64 `jceId:"0"`
LastSeqId int64 `jceId:"1"`
}
SvcRespParam struct {
PCStat int32 `jceId:"0"`
IsSupportC2CRoamMsg int32 `jceId:"1"`
IsSupportDataLine int32 `jceId:"2"`
IsSupportPrintable int32 `jceId:"3"`
IsSupportViewPCFile int32 `jceId:"4"`
PcVersion int32 `jceId:"5"`
RoamFlag int64 `jceId:"6"`
OnlineInfos []OnlineInfo `jceId:"7"`
PCClientType int32 `jceId:"8"`
}
OnlineInfo struct {
InstanceId int32 `jceId:"0"`
ClientType int32 `jceId:"1"`
OnlineStatus int32 `jceId:"2"`
PlatformId int32 `jceId:"3"`
SubPlatform string `jceId:"4"`
UClientType int64 `jceId:"5"`
}
PushMessageInfo struct { PushMessageInfo struct {
FromUin int64 `jceId:"0"` FromUin int64 `jceId:"0"`
MsgTime int64 `jceId:"1"` MsgTime int64 `jceId:"1"`
@ -665,6 +724,28 @@ func (pkt *SvcDevLoginInfo) ReadFrom(r *JceReader) {
pkt.CanBeKicked = r.ReadInt64(10) pkt.CanBeKicked = r.ReadInt64(10)
} }
func (pkt *SvcRespParam) ReadFrom(r *JceReader) {
pkt.OnlineInfos = []OnlineInfo{}
pkt.PCStat = r.ReadInt32(0)
pkt.IsSupportC2CRoamMsg = r.ReadInt32(1)
pkt.IsSupportDataLine = r.ReadInt32(2)
pkt.IsSupportPrintable = r.ReadInt32(3)
pkt.IsSupportViewPCFile = r.ReadInt32(4)
pkt.PcVersion = r.ReadInt32(5)
pkt.RoamFlag = r.ReadInt64(6)
r.ReadSlice(&pkt.OnlineInfos, 7)
pkt.PCClientType = r.ReadInt32(8)
}
func (pkt *OnlineInfo) ReadFrom(r *JceReader) {
pkt.InstanceId = r.ReadInt32(0)
pkt.ClientType = r.ReadInt32(1)
pkt.OnlineStatus = r.ReadInt32(2)
pkt.PlatformId = r.ReadInt32(3)
pkt.SubPlatform = string(r.ReadAny(4).([]byte))
pkt.UClientType = r.ReadInt64(5)
}
func (pkt *SvcRespPushMsg) ToBytes() []byte { func (pkt *SvcRespPushMsg) ToBytes() []byte {
w := NewJceWriter() w := NewJceWriter()
w.WriteJceStructRaw(pkt) w.WriteJceStructRaw(pkt)
@ -682,3 +763,9 @@ func (pkt *SvcReqGetDevLoginInfo) ToBytes() []byte {
w.WriteJceStructRaw(pkt) w.WriteJceStructRaw(pkt)
return w.Bytes() return w.Bytes()
} }
func (pkt *SvcReqRegisterNew) ToBytes() []byte {
w := NewJceWriter()
w.WriteJceStructRaw(pkt)
return w.Bytes()
}

View File

@ -96,11 +96,12 @@ func (w *JceWriter) WriteString(s string, tag int) *JceWriter {
return w return w
} }
func (w *JceWriter) WriteBytes(l []byte, tag int) { func (w *JceWriter) WriteBytes(l []byte, tag int) *JceWriter {
w.writeHead(13, tag) w.writeHead(13, tag)
w.writeHead(0, 0) w.writeHead(0, 0)
w.WriteInt32(int32(len(l)), 0) w.WriteInt32(int32(len(l)), 0)
w.buf.Write(l) w.buf.Write(l)
return w
} }
func (w *JceWriter) WriteInt64Slice(l []int64, tag int) { func (w *JceWriter) WriteInt64Slice(l []int64, tag int) {

View File

@ -1,8 +1,8 @@
package binary package binary
import ( import (
"crypto/rand"
"encoding/binary" "encoding/binary"
"math/rand"
"reflect" "reflect"
"unsafe" "unsafe"
) )

View File

@ -302,29 +302,6 @@ func (c *QQClient) buildConfPushRespPacket(t int32, pktSeq int64, jceBuf []byte)
return seq, packet return seq, packet
} }
// StatSvc.GetDevLoginInfo
func (c *QQClient) buildDeviceListRequestPacket() (uint16, []byte) {
seq := c.nextSeq()
req := &jce.SvcReqGetDevLoginInfo{
Guid: SystemDeviceInfo.Guid,
LoginType: 1,
AppName: "com.tencent.mobileqq",
RequireMax: 20,
GetDevListType: 2,
}
buf := &jce.RequestDataVersion3{Map: map[string][]byte{"SvcReqGetDevLoginInfo": packUniRequestData(req.ToBytes())}}
pkt := &jce.RequestPacket{
IVersion: 3,
SServantName: "StatSvc",
SFuncName: "SvcReqGetDevLoginInfo",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
packet := packets.BuildUniPacket(c.Uin, seq, "StatSvc.GetDevLoginInfo", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, pkt.ToBytes())
return seq, packet
}
// friendlist.getFriendGroupList // friendlist.getFriendGroupList
func (c *QQClient) buildFriendGroupListRequestPacket(friendStartIndex, friendListCount, groupStartIndex, groupListCount int16) (uint16, []byte) { func (c *QQClient) buildFriendGroupListRequestPacket(friendStartIndex, friendListCount, groupStartIndex, groupListCount int16) (uint16, []byte) {
seq := c.nextSeq() seq := c.nextSeq()

View File

@ -41,6 +41,7 @@ type QQClient struct {
Gender uint16 Gender uint16
FriendList []*FriendInfo FriendList []*FriendInfo
GroupList []*GroupInfo GroupList []*GroupInfo
OnlineClients []*OtherClientInfo
Online bool Online bool
NetLooping bool NetLooping bool
@ -121,7 +122,6 @@ var decoders = map[string]func(*QQClient, uint16, []byte) (interface{}, error){
"wtlogin.exchange_emp": decodeExchangeEmpResponse, "wtlogin.exchange_emp": decodeExchangeEmpResponse,
"StatSvc.register": decodeClientRegisterResponse, "StatSvc.register": decodeClientRegisterResponse,
"StatSvc.ReqMSFOffline": decodeMSFOfflinePacket, "StatSvc.ReqMSFOffline": decodeMSFOfflinePacket,
"StatSvc.GetDevLoginInfo": decodeDevListResponse,
"MessageSvc.PushNotify": decodeSvcNotify, "MessageSvc.PushNotify": decodeSvcNotify,
"OnlinePush.ReqPush": decodeOnlinePushReqPacket, "OnlinePush.ReqPush": decodeOnlinePushReqPacket,
"OnlinePush.PbPushTransMsg": decodeOnlinePushTransPacket, "OnlinePush.PbPushTransMsg": decodeOnlinePushTransPacket,
@ -289,29 +289,6 @@ func (c *QQClient) SubmitSMS(code string) (*LoginResponse, error) {
return &l, nil return &l, nil
} }
func (c *QQClient) init() {
c.Online = true
_ = c.registerClient()
c.groupSysMsgCache, _ = c.GetGroupSystemMessages()
if !c.heartbeatEnabled {
go c.doHeartbeat()
}
c.stat.once.Do(func() {
c.OnGroupMessage(func(_ *QQClient, _ *message.GroupMessage) {
c.stat.MessageReceived++
})
c.OnPrivateMessage(func(_ *QQClient, _ *message.PrivateMessage) {
c.stat.MessageReceived++
})
c.OnTempMessage(func(_ *QQClient, _ *message.TempMessage) {
c.stat.MessageReceived++
})
c.onGroupMessageReceipt("internal", func(_ *QQClient, _ *groupMessageReceiptEvent) {
c.stat.MessageSent++
})
})
}
func (c *QQClient) RequestSMS() bool { func (c *QQClient) RequestSMS() bool {
rsp, err := c.sendAndWait(c.buildSMSRequestPacket()) rsp, err := c.sendAndWait(c.buildSMSRequestPacket())
if err != nil { if err != nil {
@ -321,6 +298,33 @@ func (c *QQClient) RequestSMS() bool {
return rsp.(LoginResponse).Error == SMSNeededError return rsp.(LoginResponse).Error == SMSNeededError
} }
func (c *QQClient) init() {
c.Online = true
_ = c.registerClient()
c.groupSysMsgCache, _ = c.GetGroupSystemMessages()
if !c.heartbeatEnabled {
go c.doHeartbeat()
}
_ = c.RefreshStatus()
c.stat.once.Do(func() {
c.OnGroupMessage(func(_ *QQClient, _ *message.GroupMessage) {
c.stat.MessageReceived++
c.stat.LastMessageTime = time.Now().Unix()
})
c.OnPrivateMessage(func(_ *QQClient, _ *message.PrivateMessage) {
c.stat.MessageReceived++
c.stat.LastMessageTime = time.Now().Unix()
})
c.OnTempMessage(func(_ *QQClient, _ *message.TempMessage) {
c.stat.MessageReceived++
c.stat.LastMessageTime = time.Now().Unix()
})
c.onGroupMessageReceipt("internal", func(_ *QQClient, _ *groupMessageReceiptEvent) {
c.stat.MessageSent++
})
})
}
func (c *QQClient) GetVipInfo(target int64) (*VipInfo, error) { func (c *QQClient) GetVipInfo(target int64) (*VipInfo, error) {
b, err := utils.HttpGetBytes(fmt.Sprintf("https://h5.vip.qq.com/p/mc/cardv2/other?platform=1&qq=%d&adtag=geren&aid=mvip.pingtai.mobileqq.androidziliaoka.fromqita", target), c.getCookiesWithDomain("h5.vip.qq.com")) b, err := utils.HttpGetBytes(fmt.Sprintf("https://h5.vip.qq.com/p/mc/cardv2/other?platform=1&qq=%d&adtag=geren&aid=mvip.pingtai.mobileqq.androidziliaoka.fromqita", target), c.getCookiesWithDomain("h5.vip.qq.com"))
if err != nil { if err != nil {

View File

@ -413,19 +413,6 @@ func decodeSvcNotify(c *QQClient, _ uint16, _ []byte) (interface{}, error) {
return nil, err return nil, err
} }
// StatSvc.GetDevLoginInfo
func decodeDevListResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion2{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
rsp := jce.NewJceReader(data.Map["SvcRspGetDevLoginInfo"]["QQService.SvcRspGetDevLoginInfo"][1:])
d := []jce.SvcDevLoginInfo{}
ret := rsp.ReadInt64(3)
rsp.ReadSlice(&d, 5)
return ret, nil
}
// SummaryCard.ReqSummaryCard // SummaryCard.ReqSummaryCard
func decodeSummaryCardResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) { func decodeSummaryCardResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{} request := &jce.RequestPacket{}

View File

@ -58,6 +58,12 @@ type (
Qid string Qid string
} }
OtherClientInfo struct {
AppId int64
DeviceName string
DeviceKind string
}
FriendListResponse struct { FriendListResponse struct {
TotalCount int32 TotalCount int32
List []*FriendInfo List []*FriendInfo

View File

@ -151,7 +151,7 @@ func (fs *GroupFileSystem) GetFilesByFolder(folderId string) ([]*GroupFile, []*G
return files, folders, nil return files, folders, nil
} }
func (fs *GroupFileSystem) UploadFile(p, name, folderId string) error { func (fs *GroupFileSystem) uploadFile(p, name, folderId string) error {
file, err := os.OpenFile(p, os.O_RDONLY, 0666) file, err := os.OpenFile(p, os.O_RDONLY, 0666)
if err != nil { if err != nil {
return err return err

View File

@ -311,7 +311,7 @@ func decodeGroupMessagePacket(c *QQClient, _ uint16, payload []byte) (interface{
return nil, nil return nil, nil
} }
if pkt.Message.Content != nil && pkt.Message.Content.GetPkgNum() > 1 { if pkt.Message.Content != nil && pkt.Message.Content.GetPkgNum() > 1 {
var builder *groupMessageBuilder // TODO: 支持多SEQ var builder *groupMessageBuilder
i, ok := c.groupMsgBuilders.Load(pkt.Message.Content.GetDivSeq()) i, ok := c.groupMsgBuilders.Load(pkt.Message.Content.GetDivSeq())
if !ok { if !ok {
builder = &groupMessageBuilder{} builder = &groupMessageBuilder{}

View File

@ -10,6 +10,7 @@ type Statistics struct {
MessageSent uint64 `json:"message_sent"` MessageSent uint64 `json:"message_sent"`
DisconnectTimes uint32 `json:"disconnect_times"` DisconnectTimes uint32 `json:"disconnect_times"`
LostTimes uint32 `json:"lost_times"` LostTimes uint32 `json:"lost_times"`
LastMessageTime int64 `json:"last_message_time"`
once sync.Once once sync.Once
} }

196
client/sync.go Normal file
View File

@ -0,0 +1,196 @@
package client
import (
"github.com/Mrs4s/MiraiGo/binary/jce"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
"time"
)
func init() {
decoders["StatSvc.GetDevLoginInfo"] = decodeDevListResponse
decoders["RegPrxySvc.getOffMsg"] = decodeOfflineMsgResponse
decoders["RegPrxySvc.PushParam"] = decodePushParamPacket
}
// GetAllowedClients 获取已允许的其他客户端
func (c *QQClient) GetAllowedClients() ([]*OtherClientInfo, error) {
i, err := c.sendAndWait(c.buildDeviceListRequestPacket())
if err != nil {
return nil, err
}
list := i.([]jce.SvcDevLoginInfo)
var ret []*OtherClientInfo
for _, l := range list {
ret = append(ret, &OtherClientInfo{
AppId: l.AppId,
DeviceName: l.DeviceName,
DeviceKind: l.DeviceTypeInfo,
})
}
return ret, nil
}
// RefreshClientStatus 刷新客户端状态
func (c *QQClient) RefreshStatus() error {
_, pkt := c.buildGetOfflineMsgRequest()
c.send(pkt)
return nil
}
// StatSvc.GetDevLoginInfo
func (c *QQClient) buildDeviceListRequestPacket() (uint16, []byte) {
seq := c.nextSeq()
req := &jce.SvcReqGetDevLoginInfo{
Guid: SystemDeviceInfo.Guid,
LoginType: 1,
AppName: "com.tencent.mobileqq",
RequireMax: 20,
GetDevListType: 2,
}
buf := &jce.RequestDataVersion3{Map: map[string][]byte{"SvcReqGetDevLoginInfo": packUniRequestData(req.ToBytes())}}
pkt := &jce.RequestPacket{
IVersion: 3,
SServantName: "StatSvc",
SFuncName: "SvcReqGetDevLoginInfo",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
packet := packets.BuildUniPacket(c.Uin, seq, "StatSvc.GetDevLoginInfo", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, pkt.ToBytes())
return seq, packet
}
// RegPrxySvc.getOffMsg
func (c *QQClient) buildGetOfflineMsgRequest() (uint16, []byte) {
seq := c.nextSeq()
regReq := &jce.SvcReqRegisterNew{
RequestOptional: 0x101C2 | 32,
C2CMsg: &jce.SvcReqGetMsgV2{
Uin: c.Uin,
DateTime: func() int32 {
if c.stat.LastMessageTime == 0 {
return 1
}
return int32(c.stat.LastMessageTime)
}(),
RecivePic: 1,
Ability: 15,
Channel: 4,
Inst: 1,
ChannelEx: 1,
SyncCookie: c.syncCookie,
SyncFlag: 0, // START
RambleFlag: 0,
GeneralAbi: 1,
PubAccountCookie: c.pubAccountCookie,
},
GroupMsg: &jce.SvcReqPullGroupMsgSeq{
VerifyType: 0,
Filter: 1, // LIMIT_10_AND_IN_3_DAYS
},
EndSeq: time.Now().Unix(),
}
flag := msg.SyncFlag_START
msgReq, _ := proto.Marshal(&msg.GetMessageRequest{
SyncFlag: &flag,
SyncCookie: c.syncCookie,
RambleFlag: proto.Int32(0),
ContextFlag: proto.Int32(1),
OnlineSyncFlag: proto.Int32(0),
LatestRambleNumber: proto.Int32(20),
OtherRambleNumber: proto.Int32(3),
})
buf := &jce.RequestDataVersion3{Map: map[string][]byte{
"req_PbOffMsg": jce.NewJceWriter().WriteBytes(append([]byte{0, 0, 0, 0}, msgReq...), 0).Bytes(),
"req_OffMsg": packUniRequestData(regReq.ToBytes()),
}}
pkt := &jce.RequestPacket{
IVersion: 3,
SServantName: "RegPrxySvc",
SBuffer: buf.ToBytes(),
Context: make(map[string]string),
Status: make(map[string]string),
}
packet := packets.BuildUniPacket(c.Uin, seq, "RegPrxySvc.getOffMsg", 1, c.OutGoingPacketSessionId, []byte{}, c.sigInfo.d2Key, pkt.ToBytes())
return seq, packet
}
// StatSvc.GetDevLoginInfo
func decodeDevListResponse(_ *QQClient, _ uint16, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion2{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
rsp := jce.NewJceReader(data.Map["SvcRspGetDevLoginInfo"]["QQService.SvcRspGetDevLoginInfo"][1:])
d := []jce.SvcDevLoginInfo{}
rsp.ReadSlice(&d, 4)
if len(d) > 0 {
return d, nil
}
rsp.ReadSlice(&d, 5)
if len(d) > 0 {
return d, nil
}
rsp.ReadSlice(&d, 6)
if len(d) > 0 {
return d, nil
}
return nil, errors.New("not any device")
}
// RegPrxySvc.getOffMsg
func decodeOfflineMsgResponse(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
/*
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
*/
return nil, nil
}
// RegPrxySvc.PushParam
func decodePushParamPacket(c *QQClient, _ uint16, payload []byte) (interface{}, error) {
request := &jce.RequestPacket{}
request.ReadFrom(jce.NewJceReader(payload))
data := &jce.RequestDataVersion2{}
data.ReadFrom(jce.NewJceReader(request.SBuffer))
reader := jce.NewJceReader(data.Map["SvcRespParam"]["RegisterProxySvcPack.SvcRespParam"][1:])
rsp := &jce.SvcRespParam{}
rsp.ReadFrom(reader)
allowedClients, _ := c.GetAllowedClients()
c.OnlineClients = []*OtherClientInfo{}
for _, i := range rsp.OnlineInfos {
c.OnlineClients = append(c.OnlineClients, &OtherClientInfo{
AppId: int64(i.InstanceId),
DeviceName: func() string {
for _, ac := range allowedClients {
if ac.AppId == int64(i.InstanceId) {
return ac.DeviceName
}
}
return i.SubPlatform
}(),
DeviceKind: func() string {
switch i.UClientType {
case 65793:
return "Windows"
case 65805, 68104:
return "aPad"
case 66818, 66831, 81154:
return "Mac"
case 68361, 72194:
return "iPad"
case 75023, 78082, 78096:
return "Watch"
case 77313:
return "Windows TIM"
default:
return i.SubPlatform
}
}(),
})
}
return nil, nil
}