1
0
mirror of https://github.com/Mrs4s/MiraiGo.git synced 2025-05-04 11:07:40 +08:00
MiraiGo/client/client.go
2020-07-24 22:41:26 +08:00

688 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package client
import (
"crypto/md5"
"errors"
"fmt"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
"github.com/Mrs4s/MiraiGo/client/pb/msg"
"github.com/Mrs4s/MiraiGo/client/pb/multimsg"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/golang/protobuf/proto"
"io"
"log"
"math"
"math/rand"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
)
type QQClient struct {
Uin int64
PasswordMd5 [16]byte
Nickname string
Age uint16
Gender uint16
FriendList []*FriendInfo
GroupList []*GroupInfo
Online bool
SequenceId uint16
OutGoingPacketSessionId []byte
RandomKey []byte
Conn net.Conn
decoders map[string]func(*QQClient, uint16, []byte) (interface{}, error)
handlers map[uint16]func(interface{}, error)
syncCookie []byte
pubAccountCookie []byte
msgCtrlBuf []byte
ksid []byte
t104 []byte
t150 []byte
t149 []byte
t528 []byte
t530 []byte
rollbackSig []byte
timeDiff int64
sigInfo *loginSigInfo
pwdFlag bool
lastMessageSeq int32
lastMessageSeqTmp sync.Map
groupMsgBuilders sync.Map
onlinePushCache []int16 // reset on reconnect
requestPacketRequestId int32
groupSeq int32
friendSeq int32
groupDataTransSeq int32
highwayApplyUpSeq int32
eventHandlers *eventHandlers
groupListLock *sync.Mutex
}
type loginSigInfo struct {
loginBitmap uint64
tgt []byte
tgtKey []byte
userStKey []byte
userStWebSig []byte
sKey []byte
d2 []byte
d2Key []byte
wtSessionTicketKey []byte
deviceToken []byte
}
func init() {
rand.Seed(time.Now().UTC().UnixNano())
}
// NewClient create new qq client
func NewClient(uin int64, password string) *QQClient {
return NewClientMd5(uin, md5.Sum([]byte(password)))
}
func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
cli := &QQClient{
Uin: uin,
PasswordMd5: passwordMd5,
SequenceId: 0x3635,
RandomKey: make([]byte, 16),
OutGoingPacketSessionId: []byte{0x02, 0xB0, 0x5B, 0x8B},
decoders: map[string]func(*QQClient, uint16, []byte) (interface{}, error){
"wtlogin.login": decodeLoginResponse,
"StatSvc.register": decodeClientRegisterResponse,
"MessageSvc.PushNotify": decodeSvcNotify,
"OnlinePush.PbPushGroupMsg": decodeGroupMessagePacket,
"OnlinePush.ReqPush": decodeOnlinePushReqPacket,
"OnlinePush.PbPushTransMsg": decodeOnlinePushTransPacket,
"ConfigPushSvc.PushReq": decodePushReqPacket,
"MessageSvc.PbGetMsg": decodeMessageSvcPacket,
"MessageSvc.PushForceOffline": decodeForceOfflinePacket,
"friendlist.getFriendGroupList": decodeFriendGroupListResponse,
"friendlist.GetTroopListReqV2": decodeGroupListResponse,
"friendlist.GetTroopMemberListReq": decodeGroupMemberListResponse,
"ImgStore.GroupPicUp": decodeGroupImageStoreResponse,
"LongConn.OffPicUp": decodeOffPicUpResponse,
"ProfileService.Pb.ReqSystemMsgNew.Group": decodeSystemMsgGroupPacket,
"ProfileService.Pb.ReqSystemMsgNew.Friend": decodeSystemMsgFriendPacket,
"MultiMsg.ApplyUp": decodeMultiApplyUpResponse,
},
handlers: map[uint16]func(interface{}, error){},
sigInfo: &loginSigInfo{},
requestPacketRequestId: 1921334513,
groupSeq: 22911,
friendSeq: 22911,
highwayApplyUpSeq: 77918,
ksid: []byte("|454001228437590|A8.2.7.27f6ea96"),
eventHandlers: &eventHandlers{},
groupListLock: new(sync.Mutex),
}
rand.Read(cli.RandomKey)
return cli
}
// Login send login request
func (c *QQClient) Login() (*LoginResponse, error) {
if c.Online {
return nil, ErrAlreadyOnline
}
err := c.connect()
if err != nil {
return nil, err
}
c.Online = true
go c.loop()
seq, packet := c.buildLoginPacket()
rsp, err := c.sendAndWait(seq, packet)
if err != nil {
return nil, err
}
l := rsp.(LoginResponse)
if l.Success {
c.registerClient()
go c.heartbeat()
_, _ = c.sendAndWait(c.buildGetMessageRequestPacket(msg.SyncFlag_START, time.Now().Unix()))
}
return &l, nil
}
// SubmitCaptcha send captcha to server
func (c *QQClient) SubmitCaptcha(result string, sign []byte) (*LoginResponse, error) {
seq, packet := c.buildCaptchaPacket(result, sign)
rsp, err := c.sendAndWait(seq, packet)
if err != nil {
return nil, err
}
l := rsp.(LoginResponse)
if l.Success {
c.registerClient()
go c.heartbeat()
}
return &l, nil
}
// ReloadFriendList refresh QQClient.FriendList field via GetFriendList()
func (c *QQClient) ReloadFriendList() error {
rsp, err := c.GetFriendList()
if err != nil {
return err
}
c.FriendList = rsp.List
return nil
}
// GetFriendList request friend list
func (c *QQClient) GetFriendList() (*FriendListResponse, error) {
var curFriendCount = 0
r := &FriendListResponse{}
for {
rsp, err := c.sendAndWait(c.buildFriendGroupListRequestPacket(int16(curFriendCount), 150, 0, 0))
if err != nil {
return nil, err
}
list := rsp.(FriendListResponse)
r.TotalCount = list.TotalCount
r.List = append(r.List, list.List...)
curFriendCount += len(list.List)
if int32(curFriendCount) >= r.TotalCount {
break
}
}
return r, nil
}
func (c *QQClient) SendGroupMessage(groupCode int64, m *message.SendingMessage) *message.GroupMessage {
eid := utils.RandomString(6)
mr := int32(rand.Uint32())
ch := make(chan int32)
c.onGroupMessageReceipt(eid, func(c *QQClient, e *groupMessageReceiptEvent) {
if e.Rand == mr {
ch <- e.Seq
}
})
defer c.onGroupMessageReceipt(eid)
_, pkt := c.buildGroupSendingPacket(groupCode, mr, m)
_ = c.send(pkt)
var mid int32
ret := &message.GroupMessage{
Id: -1,
InternalId: mr,
GroupCode: groupCode,
Sender: &message.Sender{
Uin: c.Uin,
Nickname: c.Nickname,
IsFriend: true,
},
Time: int32(time.Now().Unix()),
Elements: m.Elements,
}
select {
case mid = <-ch:
case <-time.After(time.Second * 5):
return ret
}
ret.Id = mid
return ret
}
func (c *QQClient) SendPrivateMessage(target int64, m *message.SendingMessage) *message.PrivateMessage {
mr := int32(rand.Uint32())
seq := c.nextFriendSeq()
t := time.Now().Unix()
_, pkt := c.buildFriendSendingPacket(target, seq, mr, t, m)
_ = c.send(pkt)
return &message.PrivateMessage{
Id: seq,
InternalId: mr,
Target: target,
Time: int32(t),
Sender: &message.Sender{
Uin: c.Uin,
Nickname: c.Nickname,
IsFriend: true,
},
Elements: m.Elements,
}
}
// 目前似乎iOS端无法正常打开转发消息经测试数据上传正常应该是解析问题。iOS目前没有越狱设备抓不到日志有空再测试。
func (c *QQClient) SendForwardMessage(groupCode int64, m *message.ForwardMessage) *message.GroupMessage {
if len(m.Nodes) >= 200 {
return nil
}
ts := time.Now().Unix()
seq := c.nextGroupSeq()
data, hash := m.CalculateValidationData(seq, rand.Int31(), groupCode)
i, err := c.sendAndWait(c.buildMultiApplyUpPacket(data, hash, utils.ToGroupUin(groupCode)))
if err != nil {
return nil
}
rsp := i.(*multimsg.MultiMsgApplyUpRsp)
body, _ := proto.Marshal(&longmsg.LongReqBody{
Subcmd: 1,
TermType: 5,
PlatformType: 9,
MsgUpReq: []*longmsg.LongMsgUpReq{
{
MsgType: 3,
DstUin: utils.ToGroupUin(groupCode),
MsgContent: data,
StoreType: 2,
MsgUkey: rsp.MsgUkey,
},
},
})
for i, ip := range rsp.Uint32UpIp {
updServer := binary.UInt32ToIPV4Address(uint32(ip))
err := c.highwayUploadImage(updServer+":"+strconv.FormatInt(int64(rsp.Uint32UpPort[i]), 10), rsp.MsgSig, body, 27)
if err == nil {
var pv string
for i := 0; i < int(math.Min(4, float64(len(m.Nodes)))); i++ {
pv += fmt.Sprintf(`<title size="26" color="#777777">%s: %s</title>`, m.Nodes[i].SenderName, message.ToReadableString(m.Nodes[i].Message))
}
return c.SendGroupMessage(groupCode, genForwardMessage(rsp.MsgResid, pv, "群聊的聊天记录", "[聊天记录]", "聊天记录", fmt.Sprintf("查看 %d 条转发消息", len(m.Nodes)), ts))
}
}
return nil
}
func (c *QQClient) RecallGroupMessage(groupCode int64, msgId, msgInternalId int32) {
_, pkt := c.buildGroupRecallPacket(groupCode, msgId, msgInternalId)
_ = c.send(pkt)
}
func (c *QQClient) UploadGroupImage(groupCode int64, img []byte) (*message.GroupImageElement, error) {
h := md5.Sum(img)
seq, pkt := c.buildGroupImageStorePacket(groupCode, h[:], int32(len(img)))
r, err := c.sendAndWait(seq, pkt)
if err != nil {
return nil, err
}
rsp := r.(imageUploadResponse)
if rsp.ResultCode != 0 {
return nil, errors.New(rsp.Message)
}
if rsp.IsExists {
return message.NewGroupImage(binary.CalculateImageResourceId(h[:]), h[:]), nil
}
for i, ip := range rsp.UploadIp {
updServer := binary.UInt32ToIPV4Address(uint32(ip))
err := c.highwayUploadImage(updServer+":"+strconv.FormatInt(int64(rsp.UploadPort[i]), 10), rsp.UploadKey, img, 2)
if err != nil {
continue
}
return message.NewGroupImage(binary.CalculateImageResourceId(h[:]), h[:]), nil
}
return nil, errors.New("upload failed")
}
func (c *QQClient) UploadPrivateImage(target int64, img []byte) (*message.FriendImageElement, error) {
return c.uploadPrivateImage(target, img, 0)
}
func (c *QQClient) uploadPrivateImage(target int64, img []byte, count int) (*message.FriendImageElement, error) {
count++
h := md5.Sum(img)
e, err := c.QueryFriendImage(target, h[:], int32(len(img)))
if err != nil {
// use group highway upload and query again for image id.
if _, err = c.UploadGroupImage(target, img); err != nil {
return nil, err
}
// safe
if count >= 5 {
return nil, errors.New("upload failed")
}
return c.uploadPrivateImage(target, img, count)
}
return e, nil
}
func (c *QQClient) QueryGroupImage(groupCode int64, hash []byte, size int32) (*message.GroupImageElement, error) {
r, err := c.sendAndWait(c.buildGroupImageStorePacket(groupCode, hash, size))
if err != nil {
return nil, err
}
rsp := r.(imageUploadResponse)
if rsp.ResultCode != 0 {
return nil, errors.New(rsp.Message)
}
if rsp.IsExists {
return message.NewGroupImage(binary.CalculateImageResourceId(hash), hash), nil
}
return nil, errors.New("image not exists")
}
func (c *QQClient) QueryFriendImage(target int64, hash []byte, size int32) (*message.FriendImageElement, error) {
i, err := c.sendAndWait(c.buildOffPicUpPacket(target, hash, size))
if err != nil {
return nil, err
}
rsp := i.(imageUploadResponse)
if rsp.ResultCode != 0 {
return nil, errors.New(rsp.Message)
}
if !rsp.IsExists {
return nil, errors.New("image not exists")
}
return &message.FriendImageElement{
ImageId: rsp.ResourceId,
Md5: hash,
}, nil
}
func (c *QQClient) ReloadGroupList() error {
c.groupListLock.Lock()
defer c.groupListLock.Unlock()
list, err := c.GetGroupList()
if err != nil {
return err
}
c.GroupList = list
return nil
}
func (c *QQClient) GetGroupList() ([]*GroupInfo, error) {
rsp, err := c.sendAndWait(c.buildGroupListRequestPacket())
if err != nil {
return nil, err
}
r := rsp.([]*GroupInfo)
for _, group := range r {
m, err := c.GetGroupMembers(group)
if err != nil {
continue
}
group.Members = m
}
return r, nil
}
func (c *QQClient) GetGroupMembers(group *GroupInfo) ([]*GroupMemberInfo, error) {
var nextUin int64
var list []*GroupMemberInfo
for {
data, err := c.sendAndWait(c.buildGroupMemberListRequestPacket(group.Uin, group.Code, nextUin))
if err != nil {
return nil, err
}
rsp := data.(groupMemberListResponse)
nextUin = rsp.NextUin
for _, m := range rsp.list {
m.Group = group
if m.Uin == group.OwnerUin {
m.Permission = Owner
}
}
list = append(list, rsp.list...)
if nextUin == 0 {
return list, nil
}
}
}
func (c *QQClient) FindFriend(uin int64) *FriendInfo {
for _, t := range c.FriendList {
f := t
if f.Uin == uin {
return f
}
}
return nil
}
func (c *QQClient) FindGroupByUin(uin int64) *GroupInfo {
for _, g := range c.GroupList {
f := g
if f.Uin == uin {
return f
}
}
return nil
}
func (c *QQClient) FindGroup(code int64) *GroupInfo {
for _, g := range c.GroupList {
f := g
if f.Code == code {
return f
}
}
return nil
}
func (c *QQClient) SolveGroupJoinRequest(i interface{}, accept bool) {
switch req := i.(type) {
case *UserJoinGroupRequest:
_, pkt := c.buildSystemMsgGroupActionPacket(req.RequestId, req.RequesterUin, req.GroupCode, false, accept, false)
_ = c.send(pkt)
case *GroupInvitedRequest:
_, pkt := c.buildSystemMsgGroupActionPacket(req.RequestId, req.InvitorUin, req.GroupCode, true, accept, false)
_ = c.send(pkt)
}
}
func (c *QQClient) SolveFriendRequest(req *NewFriendRequest, accept bool) {
_, pkt := c.buildSystemMsgFriendActionPacket(req.RequestId, req.RequesterUin, accept)
_ = c.send(pkt)
}
func (g *GroupInfo) SelfPermission() MemberPermission {
return g.FindMember(g.client.Uin).Permission
}
func (g *GroupInfo) AdministratorOrOwner() bool {
return g.SelfPermission() == Administrator || g.SelfPermission() == Owner
}
func (g *GroupInfo) FindMember(uin int64) *GroupMemberInfo {
for _, m := range g.Members {
f := m
if f.Uin == uin {
return f
}
}
return nil
}
func (c *QQClient) editMemberCard(groupCode, memberUin int64, card string) {
_, _ = c.sendAndWait(c.buildEditGroupTagPacket(groupCode, memberUin, card))
}
func (c *QQClient) editMemberSpecialTitle(groupCode, memberUin int64, title string) {
_, _ = c.sendAndWait(c.buildEditSpecialTitlePacket(groupCode, memberUin, title))
}
func (c *QQClient) updateGroupName(groupCode int64, newName string) {
_, _ = c.sendAndWait(c.buildGroupNameUpdatePacket(groupCode, newName))
}
func (c *QQClient) groupMuteAll(groupCode int64, mute bool) {
_, _ = c.sendAndWait(c.buildGroupMuteAllPacket(groupCode, mute))
}
func (c *QQClient) groupMute(groupCode, memberUin int64, time uint32) {
_, _ = c.sendAndWait(c.buildGroupMutePacket(groupCode, memberUin, time))
}
func (c *QQClient) kickGroupMember(groupCode, memberUin int64, msg string) {
_, _ = c.sendAndWait(c.buildGroupKickPacket(groupCode, memberUin, msg))
}
func (g *GroupInfo) removeMember(uin int64) {
if g.memLock == nil {
g.memLock = new(sync.Mutex)
}
g.memLock.Lock()
defer g.memLock.Unlock()
for i, m := range g.Members {
if m.Uin == uin {
g.Members = append(g.Members[:i], g.Members[i+1:]...)
break
}
}
}
func (c *QQClient) connect() error {
conn, err := net.Dial("tcp", "125.94.60.146:80") //TODO: more servers
if err != nil {
return err
}
c.Conn = conn
c.onlinePushCache = []int16{}
return nil
}
func (c *QQClient) registerClient() {
_, packet := c.buildClientRegisterPacket()
_ = c.send(packet)
}
func (c *QQClient) nextSeq() uint16 {
c.SequenceId++
c.SequenceId &= 0x7FFF
if c.SequenceId == 0 {
c.SequenceId++
}
return c.SequenceId
}
func (c *QQClient) nextPacketSeq() int32 {
s := atomic.LoadInt32(&c.requestPacketRequestId)
atomic.AddInt32(&c.requestPacketRequestId, 2)
return s
}
func (c *QQClient) nextGroupSeq() int32 {
s := atomic.LoadInt32(&c.groupSeq)
atomic.AddInt32(&c.groupSeq, 2)
return s
}
func (c *QQClient) nextFriendSeq() int32 {
s := atomic.LoadInt32(&c.friendSeq)
atomic.AddInt32(&c.friendSeq, 1)
return s
}
func (c *QQClient) nextGroupDataTransSeq() int32 {
s := atomic.LoadInt32(&c.groupDataTransSeq)
atomic.AddInt32(&c.groupDataTransSeq, 2)
return s
}
func (c *QQClient) nextHighwayApplySeq() int32 {
s := atomic.LoadInt32(&c.highwayApplyUpSeq)
atomic.AddInt32(&c.highwayApplyUpSeq, 2)
return s
}
func (c *QQClient) send(pkt []byte) error {
_, err := c.Conn.Write(pkt)
return err
}
func (c *QQClient) sendAndWait(seq uint16, pkt []byte) (interface{}, error) {
type T struct {
Response interface{}
Error error
}
_, err := c.Conn.Write(pkt)
if err != nil {
return nil, err
}
ch := make(chan T)
defer close(ch)
c.handlers[seq] = func(i interface{}, err error) {
ch <- T{
Response: i,
Error: err,
}
}
rsp := <-ch
return rsp.Response, rsp.Error
}
func (c *QQClient) loop() {
reader := binary.NewNetworkReader(c.Conn)
retry := 0
for c.Online {
l, err := reader.ReadInt32()
if err == io.EOF || err == io.ErrClosedPipe {
err = c.connect()
if err != nil {
c.Online = false
c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "Connection lost."})
break
}
reader = binary.NewNetworkReader(c.Conn)
c.registerClient()
}
if l <= 0 {
retry++
time.Sleep(time.Second * 3)
if retry > 10 {
c.Online = false
c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "Connection lost."})
}
continue
}
data, err := reader.ReadBytes(int(l) - 4)
pkt, err := packets.ParseIncomingPacket(data, c.sigInfo.d2Key)
if err != nil {
log.Println("parse incoming packet error: " + err.Error())
continue
}
payload := pkt.Payload
if pkt.Flag2 == 2 {
payload, err = pkt.DecryptPayload(c.RandomKey)
if err != nil {
continue
}
}
retry = 0
//fmt.Println(pkt.CommandName)
go func() {
decoder, ok := c.decoders[pkt.CommandName]
if !ok {
if f, ok := c.handlers[pkt.SequenceId]; ok {
delete(c.handlers, pkt.SequenceId)
f(nil, nil)
}
return
}
rsp, err := decoder(c, pkt.SequenceId, payload)
if err != nil {
log.Println("decode", pkt.CommandName, "error:", err)
}
if f, ok := c.handlers[pkt.SequenceId]; ok {
delete(c.handlers, pkt.SequenceId)
f(rsp, err)
}
}()
}
_ = c.Conn.Close()
}
func (c *QQClient) heartbeat() {
for c.Online {
time.Sleep(time.Second * 30)
seq := c.nextSeq()
sso := packets.BuildSsoPacket(seq, "Heartbeat.Alive", SystemDeviceInfo.IMEI, []byte{}, c.OutGoingPacketSessionId, []byte{}, c.ksid)
packet := packets.BuildLoginPacket(c.Uin, 0, []byte{}, sso, []byte{})
_, _ = c.sendAndWait(seq, packet)
}
}