From 994d662945fad47ecc48d0ee14310a65e3af219a Mon Sep 17 00:00:00 2001 From: pk5ls20 Date: Sun, 22 Sep 2024 13:24:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=8B=89=E5=8F=96?= =?UTF-8?q?=E5=92=8C=E7=BC=93=E5=AD=98rkey=20(#92)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/cache.go | 30 +++++++++++++++++++ client/entity/rkey.go | 18 +++++++++++ client/internal/cache/base.go | 41 +++++++++++++++++++------ client/internal/cache/cache.go | 25 ++++++++++++++++ client/internal/cache/operation.go | 14 ++++++++- client/operation.go | 15 +++++++++- client/packets/oidb/fetch_rkey.go | 48 ++++++++++++++++++++++++++++++ 7 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 client/entity/rkey.go create mode 100644 client/packets/oidb/fetch_rkey.go diff --git a/client/cache.go b/client/cache.go index b6638e42..424fd813 100644 --- a/client/cache.go +++ b/client/cache.go @@ -93,6 +93,26 @@ func (c *QQClient) GetCachedMembersInfo(groupUin uint32) map[uint32]*entity.Grou return c.cache.GetGroupMembers(groupUin) } +// GetCachedRkeyInfo 获取指定类型的RKey信息(缓存) +func (c *QQClient) GetCachedRkeyInfo(rkeyType entity.RKeyType) *entity.RKeyInfo { + if c.cache.RkeyInfoCacheIsEmpty() || c.cache.RkeyInfoCacheIsExpired() { + if err := c.RefreshAllRkeyInfoCache(); err != nil { + return nil + } + } + return c.cache.GetRKeyInfo(rkeyType) +} + +// GetCachedRkeyInfos 获取所有RKey信息(缓存) +func (c *QQClient) GetCachedRkeyInfos() map[entity.RKeyType]*entity.RKeyInfo { + if c.cache.RkeyInfoCacheIsEmpty() || c.cache.RkeyInfoCacheIsExpired() { + if err := c.RefreshAllRkeyInfoCache(); err != nil { + return nil + } + } + return c.cache.GetAllRkeyInfo() +} + // RefreshFriendCache 刷新好友缓存 func (c *QQClient) RefreshFriendCache() error { friendsData, err := c.GetFriendsData() @@ -143,6 +163,16 @@ func (c *QQClient) RefreshAllGroupsInfo() error { return nil } +// RefreshAllRkeyInfoCache 刷新RKey缓存 +func (c *QQClient) RefreshAllRkeyInfoCache() error { + rkeyInfo, err := c.FetchRkey() + if err != nil { + return err + } + c.cache.RefreshAllRKeyInfo(rkeyInfo) + return nil +} + // GetFriendsData 获取好友列表数据 func (c *QQClient) GetFriendsData() (map[uint32]*entity.Friend, error) { friendsData := make(map[uint32]*entity.Friend) diff --git a/client/entity/rkey.go b/client/entity/rkey.go new file mode 100644 index 00000000..233c324f --- /dev/null +++ b/client/entity/rkey.go @@ -0,0 +1,18 @@ +package entity + +type ( + RKeyType uint32 + RKeyMap map[RKeyType]*RKeyInfo +) + +const ( + FriendRKey RKeyType = 10 + GroupRKey RKeyType = 20 +) + +type RKeyInfo struct { + RKeyType RKeyType + RKey string + CreateTime uint64 + ExpireTime uint64 +} diff --git a/client/internal/cache/base.go b/client/internal/cache/base.go index e189528f..8147c93a 100644 --- a/client/internal/cache/base.go +++ b/client/internal/cache/base.go @@ -2,19 +2,26 @@ package cache import ( "reflect" + "time" "unsafe" "github.com/LagrangeDev/LagrangeGo/client/entity" "github.com/RomiChan/syncx" ) -type cacheType uint32 +type ( + cacheType uint32 + KeyType interface { + ~uint32 + } +) const ( cacheTypeCache cacheType = 1 << iota cacheTypeFriend cacheTypeGroupInfo cacheTypeGroupMember + cacheTypeRKey ) func typenameof[T any]() string { @@ -26,11 +33,13 @@ var cacheTypesMap = map[string]cacheType{ typenameof[entity.Friend](): cacheTypeFriend, typenameof[entity.Group](): cacheTypeGroupInfo, typenameof[entity.GroupMember](): cacheTypeGroupMember, + typenameof[entity.RKeyInfo](): cacheTypeRKey, } type Cache struct { - m syncx.Map[uint64, unsafe.Pointer] - refreshed syncx.Map[cacheType, struct{}] + m syncx.Map[uint64, unsafe.Pointer] + refreshed syncx.Map[cacheType, struct{}] + expiredTime syncx.Map[cacheType, int64] } func hasRefreshed[T any](c *Cache) bool { @@ -42,7 +51,16 @@ func hasRefreshed[T any](c *Cache) bool { return ok } -func refreshAllCacheOf[T any](c *Cache, newcache map[uint32]*T) { +func hasExpired[T any](c *Cache) bool { + typ := cacheTypesMap[reflect.ValueOf((*T)(nil)).Type().String()] + if typ == 0 { + return false + } + v, ok := c.expiredTime.Load(typ) + return ok && v <= time.Now().Unix() +} + +func refreshAllCacheOf[T any, K KeyType](c *Cache, newcache map[K]*T) { typstr := reflect.ValueOf((*T)(nil)).Type().String() typ := cacheTypesMap[typstr] if typ == 0 { @@ -53,7 +71,7 @@ func refreshAllCacheOf[T any](c *Cache, newcache map[uint32]*T) { dellst := make([]uint64, 0, 64) c.m.Range(func(k uint64, v unsafe.Pointer) bool { if k&key != 0 { - if _, ok := newcache[uint32(k)]; !ok { + if _, ok := newcache[K(uint32(k))]; !ok { dellst = append(dellst, k) } } @@ -67,7 +85,12 @@ func refreshAllCacheOf[T any](c *Cache, newcache map[uint32]*T) { } } -func setCacheOf[T any](c *Cache, k uint32, v *T) { +func refreshAllExpiredCacheOf[T any, K KeyType](c *Cache, newcache map[K]*T, newextime int64) { + refreshAllCacheOf(c, newcache) + c.expiredTime.Store(cacheTypesMap[reflect.ValueOf((*T)(nil)).Type().String()], newextime) +} + +func setCacheOf[T any, K KeyType](c *Cache, k K, v *T) { typstr := reflect.ValueOf(v).Type().String() typ := cacheTypesMap[typstr] if typ == 0 { @@ -77,7 +100,7 @@ func setCacheOf[T any](c *Cache, k uint32, v *T) { c.m.Store(key, unsafe.Pointer(v)) } -func getCacheOf[T any](c *Cache, k uint32) (v *T, ok bool) { +func getCacheOf[T any, K KeyType](c *Cache, k K) (v *T, ok bool) { typstr := reflect.ValueOf(v).Type().String() typ := cacheTypesMap[typstr] if typ == 0 { @@ -91,7 +114,7 @@ func getCacheOf[T any](c *Cache, k uint32) (v *T, ok bool) { return } -func rangeCacheOf[T any](c *Cache, iter func(k uint32, v *T) bool) { +func rangeCacheOf[T any, K KeyType](c *Cache, iter func(k K, v *T) bool) { typ := cacheTypesMap[reflect.ValueOf((*T)(nil)).Type().String()] if typ == 0 { return @@ -99,7 +122,7 @@ func rangeCacheOf[T any](c *Cache, iter func(k uint32, v *T) bool) { key := uint64(typ) << 32 c.m.Range(func(k uint64, v unsafe.Pointer) bool { if k&key != 0 { - return iter(uint32(k), (*T)(v)) + return iter(K(uint32(k)), (*T)(v)) } return true }) diff --git a/client/internal/cache/cache.go b/client/internal/cache/cache.go index c046c873..c2e9ebfe 100644 --- a/client/internal/cache/cache.go +++ b/client/internal/cache/cache.go @@ -96,6 +96,22 @@ func (c *Cache) GetGroupMembers(groupUin uint32) map[uint32]*entity.GroupMember return members } +// GetRKeyInfo 获取指定类型的RKey信息 +func (c *Cache) GetRKeyInfo(rkeyType entity.RKeyType) *entity.RKeyInfo { + v, _ := getCacheOf[entity.RKeyInfo](c, rkeyType) + return v +} + +// GetAllRkeyInfo 获取所有RKey信息 +func (c *Cache) GetAllRkeyInfo() entity.RKeyMap { + infos := make(map[entity.RKeyType]*entity.RKeyInfo, 2) + rangeCacheOf[entity.RKeyInfo](c, func(k entity.RKeyType, v *entity.RKeyInfo) bool { + infos[k] = v + return true + }) + return infos +} + // FriendCacheIsEmpty 好友信息缓存是否为空 func (c *Cache) FriendCacheIsEmpty() bool { return !hasRefreshed[entity.Friend](c) @@ -118,3 +134,12 @@ func (c *Cache) GroupMemberCacheIsEmpty(groupUin uint32) bool { func (c *Cache) GroupInfoCacheIsEmpty() bool { return !hasRefreshed[entity.Group](c) } + +// RkeyInfoCacheIsEmpty RKey缓存是否为空 +func (c *Cache) RkeyInfoCacheIsEmpty() bool { + return !hasRefreshed[entity.RKeyInfo](c) +} + +func (c *Cache) RkeyInfoCacheIsExpired() bool { + return hasExpired[entity.RKeyInfo](c) +} diff --git a/client/internal/cache/operation.go b/client/internal/cache/operation.go index b4453d4d..378355ea 100644 --- a/client/internal/cache/operation.go +++ b/client/internal/cache/operation.go @@ -2,12 +2,14 @@ package cache import ( "github.com/LagrangeDev/LagrangeGo/client/entity" + "math" ) -func (c *Cache) RefreshAll(friendCache map[uint32]*entity.Friend, groupCache map[uint32]*entity.Group, groupMemberCache map[uint32]map[uint32]*entity.GroupMember) { +func (c *Cache) RefreshAll(friendCache map[uint32]*entity.Friend, groupCache map[uint32]*entity.Group, groupMemberCache map[uint32]map[uint32]*entity.GroupMember, rkeyCache entity.RKeyMap) { c.RefreshAllFriend(friendCache) c.RefreshAllGroup(groupCache) c.RefreshAllGroupMembers(groupMemberCache) + c.RefreshAllRKeyInfo(rkeyCache) } // RefreshFriend 刷新一个好友的缓存 @@ -59,3 +61,13 @@ func (c *Cache) RefreshGroup(group *entity.Group) { func (c *Cache) RefreshAllGroup(groupCache map[uint32]*entity.Group) { refreshAllCacheOf(c, groupCache) } + +// RefreshAllRKeyInfo 刷新所有RKey缓存 +func (c *Cache) RefreshAllRKeyInfo(rkeyCache entity.RKeyMap) { + var expTime int64 = math.MaxInt64 + for _, ext := range rkeyCache { + expTime = int64(ext.ExpireTime) + break + } + refreshAllExpiredCacheOf(c, rkeyCache, expTime) +} diff --git a/client/operation.go b/client/operation.go index 4cecbf24..f302e6b4 100644 --- a/client/operation.go +++ b/client/operation.go @@ -470,6 +470,19 @@ func (c *QQClient) SetFriendRequest(accept bool, targetUid string) error { return oidb2.ParseSetFriendRequestResp(resp) } +// FetchRkey 获取Rkey +func (c *QQClient) FetchRkey() (entity.RKeyMap, error) { + pkt, err := oidb2.BuildFetchRKeyReq() + if err != nil { + return nil, err + } + resp, err := c.sendOidbPacketAndWait(pkt) + if err != nil { + return nil, err + } + return oidb2.ParseFetchRKeyResp(resp) +} + // FetchClientKey 获取ClientKey func (c *QQClient) FetchClientKey() (string, error) { pkt, err := oidb2.BuildFetchClientKeyReq() @@ -483,7 +496,7 @@ func (c *QQClient) FetchClientKey() (string, error) { return oidb2.ParseFetchClientKeyResp(resp) } -// FetchCookies 获取cooikes +// FetchCookies 获取cookies func (c *QQClient) FetchCookies(domains []string) ([]string, error) { pkt, err := oidb2.BuildFetchCookieReq(domains) if err != nil { diff --git a/client/packets/oidb/fetch_rkey.go b/client/packets/oidb/fetch_rkey.go new file mode 100644 index 00000000..cfe63589 --- /dev/null +++ b/client/packets/oidb/fetch_rkey.go @@ -0,0 +1,48 @@ +package oidb + +import ( + "github.com/LagrangeDev/LagrangeGo/client/entity" + "github.com/LagrangeDev/LagrangeGo/client/packets/pb/service/oidb" +) + +func BuildFetchRKeyReq() (*OidbPacket, error) { + body := &oidb.NTV2RichMediaReq{ + ReqHead: &oidb.MultiMediaReqHead{ + Common: &oidb.CommonHead{ + RequestId: 1, + Command: 202, + }, + Scene: &oidb.SceneInfo{ + RequestType: 2, + BusinessType: 1, + SceneType: 0, + }, + Client: &oidb.ClientMeta{ + AgentType: 2, + }, + }, + DownloadRKey: &oidb.DownloadRKeyReq{ + Types: []int32{10, 20, 2}, + }, + } + return BuildOidbPacket(0x9067, 202, body, false, false) +} + +func ParseFetchRKeyResp(data []byte) (entity.RKeyMap, error) { + resp := &oidb.NTV2RichMediaResp{} + _, err := ParseOidbPacket(data, resp) + if err != nil { + return nil, err + } + var rKeyInfo = entity.RKeyMap{} + for _, rkey := range resp.DownloadRKey.RKeys { + typ := entity.RKeyType(rkey.Type.Unwrap()) + rKeyInfo[typ] = &entity.RKeyInfo{ + RKey: rkey.Rkey, + RKeyType: typ, + CreateTime: uint64(rkey.RkeyCreateTime.Unwrap()), + ExpireTime: uint64(rkey.RkeyCreateTime.Unwrap()) + rkey.RkeyTtlSec, + } + } + return rKeyInfo, nil +}