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-14 02:40:22 +08:00

471 lines
11 KiB
Go

package client
import (
"crypto/md5"
"errors"
"github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client/pb"
"github.com/Mrs4s/MiraiGo/message"
"github.com/Mrs4s/MiraiGo/protocol/packets"
"github.com/Mrs4s/MiraiGo/utils"
"github.com/golang/protobuf/proto"
"io"
"log"
"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
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
running bool
lastMessageSeq int32
onlinePushCache []int16 // reset on reconnect
requestPacketRequestId int32
messageSeq int32
groupDataTransSeq 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,
"friendlist.getFriendGroupList": decodeFriendGroupListResponse,
"friendlist.GetTroopListReqV2": decodeGroupListResponse,
"friendlist.GetTroopMemberListReq": decodeGroupMemberListResponse,
"ImgStore.GroupPicUp": decodeGroupImageStoreResponse,
},
handlers: map[uint16]func(interface{}, error){},
sigInfo: &loginSigInfo{},
requestPacketRequestId: 1921334513,
messageSeq: 22911,
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.running {
return nil, ErrAlreadyRunning
}
err := c.connect()
if err != nil {
return nil, err
}
c.running = 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()
}
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(groupUin int64, m *message.SendingMessage) int32 {
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
c.onGroupMessageReceipt(eid)
}
})
_, pkt := c.buildGroupSendingPacket(utils.ToGroupCode(groupUin), mr, m)
_ = c.send(pkt)
var mid int32
select {
case mid = <-ch:
case <-time.After(time.Second * 5):
c.onGroupMessageReceipt(eid)
return -1
}
return mid
}
func (c *QQClient) UploadGroupImage(groupUin int64, img []byte) (*message.GroupImageElement, error) {
h := md5.Sum(img)
seq, pkt := c.buildGroupImageStorePacket(utils.ToGroupCode(groupUin), h, int32(len(img)))
r, err := c.sendAndWait(seq, pkt)
if err != nil {
return nil, err
}
rsp := r.(groupImageUploadResponse)
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))
conn, err := net.DialTimeout("tcp", updServer+":"+strconv.FormatInt(int64(rsp.UploadPort[i]), 10), time.Second*5)
if err != nil {
continue
}
if conn.SetDeadline(time.Now().Add(time.Second*10)) != nil {
_ = conn.Close()
continue
}
pkt := c.buildImageUploadPacket(img, rsp.UploadKey, 2, h)
for _, p := range pkt {
_, err = conn.Write(p)
}
if err != nil {
continue
}
r := binary.NewNetworkReader(conn)
_, err = r.ReadByte()
if err != nil {
continue
}
hl, _ := r.ReadInt32()
_, _ = r.ReadBytes(4)
payload, _ := r.ReadBytes(int(hl))
_ = conn.Close()
rsp := pb.RspDataHighwayHead{}
if proto.Unmarshal(payload, &rsp) != nil {
continue
}
if rsp.ErrorCode != 0 {
return nil, errors.New("upload failed")
}
return message.NewGroupImage(binary.CalculateImageResourceId(h[:]), h[:]), nil
}
return nil, errors.New("upload failed")
}
func (c *QQClient) ReloadGroupList() error {
list, err := c.GetGroupList()
if err != nil {
return err
}
c.GroupList = list
return nil
}
func (c *QQClient) GetGroupList() ([]*GroupInfo, error) {
c.groupListLock.Lock()
defer c.groupListLock.Unlock()
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 {
if m.Uin == group.OwnerUin {
m.Permission = Owner
break
}
}
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) FindGroup(uin int64) *GroupInfo {
for _, g := range c.GroupList {
f := g
if f.Uin == uin {
return f
}
}
return nil
}
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) 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) nextMessageSeq() int32 {
s := atomic.LoadInt32(&c.messageSeq)
atomic.AddInt32(&c.messageSeq, 2)
return s
}
func (c *QQClient) nextGroupDataTransSeq() int32 {
s := atomic.LoadInt32(&c.groupDataTransSeq)
atomic.AddInt32(&c.groupDataTransSeq, 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)
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)
for c.running {
l, err := reader.ReadInt32()
if err == io.EOF || err == io.ErrClosedPipe {
err = c.connect()
if err != nil {
c.running = false
return
}
reader = binary.NewNetworkReader(c.Conn)
c.registerClient()
}
if l <= 0 {
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
}
}
//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)
}
}()
}
}
func (c *QQClient) heartbeat() {
for c.running {
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)
}
}