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  }