github.com/LagrangeDev/LagrangeGo@v0.0.0-20240512064304-ad4a85e10cb4/client/base.go (about) 1 package client 2 3 // 部分借鉴 https://github.com/Mrs4s/MiraiGo/blob/master/client/client.go 4 5 import ( 6 "errors" 7 "net/netip" 8 "sync" 9 "sync/atomic" 10 "time" 11 12 "github.com/RomiChan/syncx" 13 14 "github.com/LagrangeDev/LagrangeGo/client/auth" 15 "github.com/LagrangeDev/LagrangeGo/client/event" 16 "github.com/LagrangeDev/LagrangeGo/client/internal/cache" 17 "github.com/LagrangeDev/LagrangeGo/client/internal/highway" 18 "github.com/LagrangeDev/LagrangeGo/client/internal/network" 19 "github.com/LagrangeDev/LagrangeGo/client/internal/oicq" 20 "github.com/LagrangeDev/LagrangeGo/client/packets/oidb" 21 "github.com/LagrangeDev/LagrangeGo/client/packets/wtlogin" 22 "github.com/LagrangeDev/LagrangeGo/client/sign" 23 "github.com/LagrangeDev/LagrangeGo/message" 24 ) 25 26 // NewClient 创建一个新的 QQ Client 27 func NewClient(uin uint32, signUrl string, appInfo *auth.AppInfo) *QQClient { 28 client := &QQClient{ 29 Uin: uin, 30 oicq: oicq.NewCodec(int64(uin)), 31 highwaySession: highway.Session{ 32 AppID: uint32(appInfo.AppID), 33 SubAppID: uint32(appInfo.SubAppID), 34 }, 35 alive: true, 36 } 37 client.signProvider = sign.NewProviderURL(signUrl, func(msg string) { 38 client.debugln(msg) 39 }) 40 client.transport.Version = appInfo 41 client.transport.Sig.D2Key = make([]byte, 0, 16) 42 client.highwaySession.Uin = &client.transport.Sig.Uin 43 client.Online.Store(false) 44 client.TCP.PlannedDisconnect(client.plannedDisconnect) 45 client.TCP.UnexpectedDisconnect(client.unexpectedDisconnect) 46 return client 47 } 48 49 type QQClient struct { 50 Uin uint32 51 signProvider sign.Provider 52 53 stat Statistics 54 once sync.Once 55 56 Online atomic.Bool 57 58 t106 []byte 59 t16a []byte 60 61 TCP network.TCPClient // todo: combine other protocol state into one struct 62 ConnectTime time.Time 63 transport network.Transport 64 oicq *oicq.Codec 65 logger Logger 66 highwaySession highway.Session 67 68 // internal state 69 handlers syncx.Map[uint32, *handlerInfo] 70 waiters syncx.Map[string, func(any, error)] 71 initServerOnce sync.Once 72 servers []netip.AddrPort 73 currServerIndex int 74 retryTimes int 75 alive bool 76 77 cache cache.Cache 78 79 // event handles 80 GroupMessageEvent EventHandle[*message.GroupMessage] 81 PrivateMessageEvent EventHandle[*message.PrivateMessage] 82 TempMessageEvent EventHandle[*message.TempMessage] 83 84 GroupInvitedEvent EventHandle[*event.GroupInvite] // 邀请入群 85 GroupMemberJoinRequestEvent EventHandle[*event.GroupMemberJoinRequest] // 加群申请 86 GroupMemberJoinEvent EventHandle[*event.GroupMemberIncrease] // 成员入群 87 GroupMemberLeaveEvent EventHandle[*event.GroupMemberDecrease] // 成员退群 88 GroupMuteEvent EventHandle[*event.GroupMute] 89 GroupRecallEvent EventHandle[*event.GroupRecall] 90 FriendRequestEvent EventHandle[*event.FriendRequest] // 好友申请 91 FriendRecallEvent EventHandle[*event.FriendRecall] 92 RenameEvent EventHandle[*event.Rename] 93 94 // client event handles 95 eventHandlers eventHandlers 96 DisconnectedEvent EventHandle[*ClientDisconnectedEvent] 97 } 98 99 func (c *QQClient) version() *auth.AppInfo { 100 return c.transport.Version 101 } 102 103 func (c *QQClient) Device() *auth.DeviceInfo { 104 return c.transport.Device 105 } 106 107 func (c *QQClient) UseDevice(d *auth.DeviceInfo) { 108 c.transport.Device = d 109 } 110 111 func (c *QQClient) UseSig(s auth.SigInfo) { 112 c.transport.Sig = s 113 } 114 115 func (c *QQClient) Sig() *auth.SigInfo { 116 return &c.transport.Sig 117 } 118 119 func (c *QQClient) Release() { 120 if c.Online.Load() { 121 c.Disconnect() 122 } 123 c.alive = false 124 } 125 126 func (c *QQClient) NickName() string { 127 return c.transport.Sig.Nickname 128 } 129 130 func (c *QQClient) sendOidbPacketAndWait(pkt *oidb.OidbPacket) ([]byte, error) { 131 return c.sendUniPacketAndWait(pkt.Cmd, pkt.Data) 132 } 133 134 func (c *QQClient) sendUniPacketAndWait(cmd string, buf []byte) ([]byte, error) { 135 seq, packet := c.uniPacket(cmd, buf) 136 pkt, err := c.sendAndWait(seq, packet) 137 if err != nil { 138 return nil, err 139 } 140 rsp, ok := pkt.([]byte) 141 if !ok { 142 return nil, errors.New("cannot parse response to bytes") 143 } 144 return rsp, nil 145 } 146 147 func (c *QQClient) doHeartbeat() { 148 for c.Online.Load() { 149 time.Sleep(270 * time.Second) 150 if !c.Online.Load() { 151 continue 152 } 153 startTime := time.Now().UnixMilli() 154 _, err := c.sendUniPacketAndWait( 155 "trpc.qq_new_tech.status_svc.StatusService.SsoHeartBeat", 156 wtlogin.BuildSSOHeartbeatRequest()) 157 if errors.Is(err, network.ErrConnectionClosed) { 158 continue 159 } 160 if err != nil { 161 c.error("heartbeat err %s", err) 162 } else { 163 c.debug("heartbeat %dms to server", time.Now().UnixMilli()-startTime) 164 //TODO: times 165 } 166 } 167 c.debugln("heartbeat task stoped") 168 } 169 170 // setOnline 设置qq已经上线 171 func (c *QQClient) setOnline() { 172 c.Online.Store(true) 173 } 174 175 func (c *QQClient) getAndIncreaseSequence() uint32 { 176 return atomic.AddUint32(&c.transport.Sig.Sequence, 1) % 0x8000 177 } 178 179 func (c *QQClient) getSequence() uint32 { 180 return atomic.LoadUint32(&c.transport.Sig.Sequence) % 0x8000 181 }