From 1a6739ffab7763d689d21eae4096db0edb77c571 Mon Sep 17 00:00:00 2001
From: linyuchen <lin.yu.chen@foxmail.com>
Date: Sun, 17 Mar 2024 11:35:38 +0800
Subject: [PATCH] refactor: ntqqapi types

---
 src/common/utils/qqlevel.ts               |   7 +
 src/main/main.ts                          |   2 +-
 src/ntqqapi/api/msg.ts                    |  26 ++-
 src/ntqqapi/hook.ts                       |   6 +-
 src/ntqqapi/ntcall.ts                     |   1 +
 src/ntqqapi/types/cache.ts                |  65 +++++++
 src/ntqqapi/types/group.ts                |  55 ++++++
 src/ntqqapi/types/index.ts                |   7 +
 src/ntqqapi/{types.ts => types/msg.ts}    | 198 +---------------------
 src/ntqqapi/types/notify.ts               |  64 +++++++
 src/ntqqapi/types/user.ts                 |  28 +++
 src/onebot11/action/GetGroupMemberInfo.ts |   6 +
 src/onebot11/constructor.ts               |  16 +-
 13 files changed, 277 insertions(+), 204 deletions(-)
 create mode 100644 src/common/utils/qqlevel.ts
 create mode 100644 src/ntqqapi/types/cache.ts
 create mode 100644 src/ntqqapi/types/group.ts
 create mode 100644 src/ntqqapi/types/index.ts
 rename src/ntqqapi/{types.ts => types/msg.ts} (62%)
 create mode 100644 src/ntqqapi/types/notify.ts
 create mode 100644 src/ntqqapi/types/user.ts

diff --git a/src/common/utils/qqlevel.ts b/src/common/utils/qqlevel.ts
new file mode 100644
index 0000000..49cc13a
--- /dev/null
+++ b/src/common/utils/qqlevel.ts
@@ -0,0 +1,7 @@
+// QQ等级换算
+import {QQLevel} from "../../ntqqapi/types";
+
+export function calcQQLevel(level: QQLevel) {
+    const {crownNum, sunNum, moonNum, starNum} = level
+    return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum
+}
\ No newline at end of file
diff --git a/src/main/main.ts b/src/main/main.ts
index 06b4091..194c7df 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -131,7 +131,7 @@ function onLoad() {
                 log("report message error: ", e.stack.toString());
             }
         })
-        registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG, ReceiveCmdS.UPDATE_ACTIVE_MSG], async (payload) => {
+        registerReceiveHook<{ msgList: Array<RawMessage> }>([ReceiveCmdS.UPDATE_MSG], async (payload) => {
             for (const message of payload.msgList) {
                 // log("message update", message.sendStatus, message.msgId, message.msgSeq)
                 if (message.recallTime != "0") { //todo: 这个判断方法不太好,应该使用灰色消息元素来判断
diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts
index 9aa5a44..5721d53 100644
--- a/src/ntqqapi/api/msg.ts
+++ b/src/ntqqapi/api/msg.ts
@@ -4,6 +4,7 @@ import {log, sleep} from "../../common/utils";
 import {dbUtil} from "../../common/db";
 import {selfInfo} from "../../common/data";
 import {ReceiveCmdS, registerReceiveHook} from "../hook";
+
 export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {}// peerUid: callbackFunnc
 
 export interface Peer {
@@ -13,12 +14,33 @@ export interface Peer {
 }
 
 export class NTQQMsgApi {
-    static async activateChat(peer: Peer) {
+    static async activateGroupChat(groupCode: string) {
         return await callNTQQApi({
             methodName: NTQQApiMethod.ADD_ACTIVE_CHAT,
-            args: [{peer, cnt: 20}]
+            args: [{peer:{peerUid: groupCode, chatType: ChatType.group}, cnt: 20}]
         })
     }
+    static async fetchRecentContact(){
+        await callNTQQApi({
+            methodName: NTQQApiMethod.RECENT_CONTACT,
+            args: [
+                {
+                    fetchParam: {
+                        anchorPointContact: {
+                            contactId: '',
+                            sortField: '',
+                            pos: 0,
+                        },
+                        relativeMoveCount: 0,
+                        listType: 2,  // 1普通消息,2群助手内的消息
+                        count: 200,
+                        fetchOld: true,
+                    },
+                }
+            ]
+        })
+    }
+
     static async recallMsg(peer: Peer, msgIds: string[]) {
         return await callNTQQApi({
             methodName: NTQQApiMethod.RECALL_MSG,
diff --git a/src/ntqqapi/hook.ts b/src/ntqqapi/hook.ts
index 0e4099e..18ba1d0 100644
--- a/src/ntqqapi/hook.ts
+++ b/src/ntqqapi/hook.ts
@@ -1,8 +1,8 @@
 import {BrowserWindow} from 'electron';
 import {getConfigUtil, log, sleep} from "../common/utils";
 import {NTQQApiClass} from "./ntcall";
-import {sendMessagePool} from "./api/msg"
-import {Group, RawMessage, User} from "./types";
+import {NTQQMsgApi, sendMessagePool} from "./api/msg"
+import {ChatType, Group, RawMessage, User} from "./types";
 import {friends, groups, selfInfo, tempGroupCodeMap} from "../common/data";
 import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
 import {v4 as uuidv4} from "uuid"
@@ -146,6 +146,8 @@ export function removeReceiveHook(id: string) {
 
 async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
     for (let group of _groups) {
+        log("update group", group)
+        NTQQMsgApi.activateGroupChat(group.groupCode).then()
         let existGroup = groups.find(g => g.groupCode == group.groupCode);
         if (existGroup) {
             Object.assign(existGroup, group);
diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts
index 9ffd3f7..b886459 100644
--- a/src/ntqqapi/ntcall.ts
+++ b/src/ntqqapi/ntcall.ts
@@ -15,6 +15,7 @@ export enum NTQQApiClass {
 }
 
 export enum NTQQApiMethod {
+    RECENT_CONTACT = "nodeIKernelRecentContactService/fetchAndSubscribeABatchOfRecentContact",
     ADD_ACTIVE_CHAT = "nodeIKernelMsgService/getAioFirstViewLatestMsgsAndAddActiveChat",  // 激活群助手内的聊天窗口,这样才能收到消息
     LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike",
     SELF_INFO = "fetchAuthData",
diff --git a/src/ntqqapi/types/cache.ts b/src/ntqqapi/types/cache.ts
new file mode 100644
index 0000000..e4407d7
--- /dev/null
+++ b/src/ntqqapi/types/cache.ts
@@ -0,0 +1,65 @@
+import {ChatType} from "./msg";
+
+export interface CacheScanResult {
+    result: number,
+    size: [ // 单位为字节
+        string, // 系统总存储空间
+        string, // 系统可用存储空间
+        string, // 系统已用存储空间
+        string, // QQ总大小
+        string, // 「聊天与文件」大小
+        string, // 未知
+        string, // 「缓存数据」大小
+        string, // 「其他数据」大小
+        string, // 未知
+    ]
+}
+
+export interface ChatCacheList {
+    pageCount: number,
+    infos: ChatCacheListItem[]
+}
+
+export interface ChatCacheListItem {
+    chatType: ChatType,
+    basicChatCacheInfo: ChatCacheListItemBasic,
+    guildChatCacheInfo: unknown[] // TODO: 没用过频道所以不知道这里边的详细内容
+}
+
+export interface ChatCacheListItemBasic {
+    chatSize: string,
+    chatTime: string,
+    uid: string,
+    uin: string,
+    remarkName: string,
+    nickName: string,
+    chatType?: ChatType,
+    isChecked?: boolean
+}
+
+export enum CacheFileType {
+    IMAGE = 0,
+    VIDEO = 1,
+    AUDIO = 2,
+    DOCUMENT = 3,
+    OTHER = 4,
+}
+
+export interface CacheFileList {
+    infos: CacheFileListItem[],
+}
+
+export interface CacheFileListItem {
+    fileSize: string,
+    fileTime: string,
+    fileKey: string,
+    elementId: string,
+    elementIdStr: string,
+    fileType: CacheFileType,
+    path: string,
+    fileName: string,
+    senderId: string,
+    previewPath: string,
+    senderName: string,
+    isChecked?: boolean,
+}
diff --git a/src/ntqqapi/types/group.ts b/src/ntqqapi/types/group.ts
new file mode 100644
index 0000000..5e4b690
--- /dev/null
+++ b/src/ntqqapi/types/group.ts
@@ -0,0 +1,55 @@
+import {QQLevel, Sex} from "./user";
+
+export interface Group {
+    groupCode: string,
+    maxMember: number,
+    memberCount: number,
+    groupName: string,
+    groupStatus: 0,
+    memberRole: 2,
+    isTop: boolean,
+    toppedTimestamp: "0",
+    privilegeFlag: number, //65760
+    isConf: boolean,
+    hasModifyConfGroupFace: boolean,
+    hasModifyConfGroupName: boolean,
+    remarkName: string,
+    hasMemo: boolean,
+    groupShutupExpireTime: string, //"0",
+    personShutupExpireTime: string, //"0",
+    discussToGroupUin: string, //"0",
+    discussToGroupMaxMsgSeq: number,
+    discussToGroupTime: number,
+    groupFlagExt: number, //1073938496,
+    authGroupType: number, //0,
+    groupCreditLevel: number, //0,
+    groupFlagExt3: number, //0,
+    groupOwnerId: {
+        "memberUin": string, //"0",
+        "memberUid": string, //"u_fbf8N7aeuZEnUiJAbQ9R8Q"
+    },
+    members: GroupMember[]  // 原始数据是没有这个的,为了方便自己加了这个字段
+}
+
+export enum GroupMemberRole {
+    normal = 2,
+    admin = 3,
+    owner = 4
+}
+
+export interface GroupMember {
+    avatarPath: string;
+    cardName: string;
+    cardType: number;
+    isDelete: boolean;
+    nick: string;
+    qid: string;
+    remark: string;
+    role: GroupMemberRole; // 群主:4, 管理员:3,群员:2
+    shutUpTime: number; // 禁言时间,单位是什么暂时不清楚
+    uid: string; // 加密的字符串
+    uin: string; // QQ号
+    isRobot: boolean;
+    sex?: Sex
+    qqLevel?: QQLevel
+}
\ No newline at end of file
diff --git a/src/ntqqapi/types/index.ts b/src/ntqqapi/types/index.ts
new file mode 100644
index 0000000..dc69d2a
--- /dev/null
+++ b/src/ntqqapi/types/index.ts
@@ -0,0 +1,7 @@
+
+export * from './user';
+export * from './group';
+export * from './msg';
+export * from './notify';
+export * from './cache';
+
diff --git a/src/ntqqapi/types.ts b/src/ntqqapi/types/msg.ts
similarity index 62%
rename from src/ntqqapi/types.ts
rename to src/ntqqapi/types/msg.ts
index 6618982..53e056d 100644
--- a/src/ntqqapi/types.ts
+++ b/src/ntqqapi/types/msg.ts
@@ -1,70 +1,4 @@
-export interface User {
-    uid: string; // 加密的字符串
-    uin: string; // QQ号
-    nick: string;
-    avatarUrl?: string;
-    longNick?: string; // 签名
-    remark?: string
-}
-
-export interface SelfInfo extends User {
-    online?: boolean;
-}
-
-export interface Friend extends User {
-}
-
-export interface Group {
-    groupCode: string,
-    maxMember: number,
-    memberCount: number,
-    groupName: string,
-    groupStatus: 0,
-    memberRole: 2,
-    isTop: boolean,
-    toppedTimestamp: "0",
-    privilegeFlag: number, //65760
-    isConf: boolean,
-    hasModifyConfGroupFace: boolean,
-    hasModifyConfGroupName: boolean,
-    remarkName: string,
-    hasMemo: boolean,
-    groupShutupExpireTime: string, //"0",
-    personShutupExpireTime: string, //"0",
-    discussToGroupUin: string, //"0",
-    discussToGroupMaxMsgSeq: number,
-    discussToGroupTime: number,
-    groupFlagExt: number, //1073938496,
-    authGroupType: number, //0,
-    groupCreditLevel: number, //0,
-    groupFlagExt3: number, //0,
-    groupOwnerId: {
-        "memberUin": string, //"0",
-        "memberUid": string, //"u_fbf8N7aeuZEnUiJAbQ9R8Q"
-    },
-    members: GroupMember[]  // 原始数据是没有这个的,为了方便自己加了这个字段
-}
-
-export enum GroupMemberRole {
-    normal = 2,
-    admin = 3,
-    owner = 4
-}
-
-export interface GroupMember {
-    avatarPath: string;
-    cardName: string;
-    cardType: number;
-    isDelete: boolean;
-    nick: string;
-    qid: string;
-    remark: string;
-    role: GroupMemberRole; // 群主:4, 管理员:3,群员:2
-    shutUpTime: number; // 禁言时间,单位是什么暂时不清楚
-    uid: string; // 加密的字符串
-    uin: string; // QQ号
-    isRobot: boolean;
-}
+import {GroupMemberRole} from "./group";
 
 export enum ElementType {
     TEXT = 1,
@@ -383,132 +317,4 @@ export interface RawMessage {
         videoElement: VideoElement;
         fileElement: FileElement;
     }[];
-}
-
-export enum GroupNotifyTypes {
-    INVITE_ME = 1,
-    INVITED_JOIN = 4,  // 有人接受了邀请入群
-    JOIN_REQUEST = 7,
-    ADMIN_SET = 8,
-    ADMIN_UNSET = 12,
-    MEMBER_EXIT = 11, // 主动退出?
-
-}
-
-export interface GroupNotifies {
-    doubt: boolean,
-    nextStartSeq: string,
-    notifies: GroupNotify[],
-}
-
-export enum GroupNotifyStatus {
-    IGNORE = 0,
-    WAIT_HANDLE = 1,
-    APPROVE = 2,
-    REJECT = 3
-}
-
-export interface GroupNotify {
-    time: number;  // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify
-    seq: string, // 唯一标识符,转成数字再除以1000应该就是时间戳?
-    type: GroupNotifyTypes,
-    status: GroupNotifyStatus,  // 0是已忽略?,1是未处理,2是已同意
-    group: { groupCode: string, groupName: string },
-    user1: { uid: string, nickName: string }, // 被设置管理员的人
-    user2: { uid: string, nickName: string },  // 操作者
-    actionUser: { uid: string, nickName: string }, //未知
-    actionTime: string,
-    invitationExt: {
-        srcType: number,  // 0?未知
-        groupCode: string, waitStatus: number
-    },
-    postscript: string,  // 加群用户填写的验证信息
-    repeatSeqs: [],
-    warningTips: string
-}
-
-export enum GroupRequestOperateTypes {
-    approve = 1,
-    reject = 2
-}
-
-export interface FriendRequest {
-    friendUid: string,
-    reqTime: string,  // 时间戳,秒
-    extWords: string,  // 申请人填写的验证消息
-    isUnread: boolean,
-    friendNick: string,
-    sourceId: number,
-    groupCode: string
-}
-
-export interface FriendRequestNotify {
-    data: {
-        unreadNums: number,
-        buddyReqs: FriendRequest[]
-    }
-}
-
-export interface CacheScanResult {
-    result: number,
-    size: [ // 单位为字节
-        string, // 系统总存储空间
-        string, // 系统可用存储空间
-        string, // 系统已用存储空间
-        string, // QQ总大小
-        string, // 「聊天与文件」大小
-        string, // 未知
-        string, // 「缓存数据」大小
-        string, // 「其他数据」大小
-        string, // 未知
-    ]
-}
-
-export interface ChatCacheList {
-    pageCount: number,
-    infos: ChatCacheListItem[]
-}
-
-export interface ChatCacheListItem {
-    chatType: ChatType,
-    basicChatCacheInfo: ChatCacheListItemBasic,
-    guildChatCacheInfo: unknown[] // TODO: 没用过频道所以不知道这里边的详细内容
-}
-
-export interface ChatCacheListItemBasic {
-    chatSize: string,
-    chatTime: string,
-    uid: string,
-    uin: string,
-    remarkName: string,
-    nickName: string,
-    chatType?: ChatType,
-    isChecked?: boolean
-}
-
-export enum CacheFileType {
-    IMAGE = 0,
-    VIDEO = 1,
-    AUDIO = 2,
-    DOCUMENT = 3,
-    OTHER = 4,
-}
-
-export interface CacheFileList {
-    infos: CacheFileListItem[],
-}
-
-export interface CacheFileListItem {
-    fileSize: string,
-    fileTime: string,
-    fileKey: string,
-    elementId: string,
-    elementIdStr: string,
-    fileType: CacheFileType,
-    path: string,
-    fileName: string,
-    senderId: string,
-    previewPath: string,
-    senderName: string,
-    isChecked?: boolean,
-}
+}
\ No newline at end of file
diff --git a/src/ntqqapi/types/notify.ts b/src/ntqqapi/types/notify.ts
new file mode 100644
index 0000000..29874fe
--- /dev/null
+++ b/src/ntqqapi/types/notify.ts
@@ -0,0 +1,64 @@
+
+export enum GroupNotifyTypes {
+    INVITE_ME = 1,
+    INVITED_JOIN = 4,  // 有人接受了邀请入群
+    JOIN_REQUEST = 7,
+    ADMIN_SET = 8,
+    ADMIN_UNSET = 12,
+    MEMBER_EXIT = 11, // 主动退出?
+
+}
+
+export interface GroupNotifies {
+    doubt: boolean,
+    nextStartSeq: string,
+    notifies: GroupNotify[],
+}
+
+export enum GroupNotifyStatus {
+    IGNORE = 0,
+    WAIT_HANDLE = 1,
+    APPROVE = 2,
+    REJECT = 3
+}
+
+export interface GroupNotify {
+    time: number;  // 自己添加的字段,时间戳,毫秒, 用于判断收到短时间内收到重复的notify
+    seq: string, // 唯一标识符,转成数字再除以1000应该就是时间戳?
+    type: GroupNotifyTypes,
+    status: GroupNotifyStatus,  // 0是已忽略?,1是未处理,2是已同意
+    group: { groupCode: string, groupName: string },
+    user1: { uid: string, nickName: string }, // 被设置管理员的人
+    user2: { uid: string, nickName: string },  // 操作者
+    actionUser: { uid: string, nickName: string }, //未知
+    actionTime: string,
+    invitationExt: {
+        srcType: number,  // 0?未知
+        groupCode: string, waitStatus: number
+    },
+    postscript: string,  // 加群用户填写的验证信息
+    repeatSeqs: [],
+    warningTips: string
+}
+
+export enum GroupRequestOperateTypes {
+    approve = 1,
+    reject = 2
+}
+
+export interface FriendRequest {
+    friendUid: string,
+    reqTime: string,  // 时间戳,秒
+    extWords: string,  // 申请人填写的验证消息
+    isUnread: boolean,
+    friendNick: string,
+    sourceId: number,
+    groupCode: string
+}
+
+export interface FriendRequestNotify {
+    data: {
+        unreadNums: number,
+        buddyReqs: FriendRequest[]
+    }
+}
diff --git a/src/ntqqapi/types/user.ts b/src/ntqqapi/types/user.ts
new file mode 100644
index 0000000..891b8a4
--- /dev/null
+++ b/src/ntqqapi/types/user.ts
@@ -0,0 +1,28 @@
+export enum Sex {
+    male = 0,
+    female = 2,
+    unknown = 255,
+}
+
+export interface QQLevel {
+    "crownNum": number,
+    "sunNum": number,
+    "moonNum": number,
+    "starNum": number
+}
+export interface User {
+    uid: string; // 加密的字符串
+    uin: string; // QQ号
+    nick: string;
+    avatarUrl?: string;
+    longNick?: string; // 签名
+    remark?: string;
+    sex?: Sex;
+    "qqLevel"?: QQLevel
+}
+
+export interface SelfInfo extends User {
+    online?: boolean;
+}
+
+export interface Friend extends User {}
\ No newline at end of file
diff --git a/src/onebot11/action/GetGroupMemberInfo.ts b/src/onebot11/action/GetGroupMemberInfo.ts
index eb541c5..9e43e4c 100644
--- a/src/onebot11/action/GetGroupMemberInfo.ts
+++ b/src/onebot11/action/GetGroupMemberInfo.ts
@@ -3,6 +3,8 @@ import {getGroupMember} from "../../common/data";
 import {OB11Constructor} from "../constructor";
 import BaseAction from "./BaseAction";
 import {ActionName} from "./types";
+import {NTQQUserApi} from "../../ntqqapi/api/user";
+import {isNull, log} from "../../common/utils";
 
 
 export interface PayloadType {
@@ -16,6 +18,10 @@ class GetGroupMemberInfo extends BaseAction<PayloadType, OB11GroupMember> {
     protected async _handle(payload: PayloadType) {
         const member = await getGroupMember(payload.group_id.toString(), payload.user_id.toString())
         if (member) {
+            if (isNull(member.sex)){
+                let info = (await NTQQUserApi.getUserDetailInfo(member.uid))
+                Object.assign(member, info);
+            }
             return OB11Constructor.groupMember(payload.group_id.toString(), member)
         } else {
             throw (`群成员${payload.user_id}不存在`)
diff --git a/src/onebot11/constructor.ts b/src/onebot11/constructor.ts
index 57af42e..8aff3b0 100644
--- a/src/onebot11/constructor.ts
+++ b/src/onebot11/constructor.ts
@@ -16,7 +16,7 @@ import {
     GroupMember,
     IMAGE_HTTP_HOST,
     RawMessage,
-    SelfInfo,
+    SelfInfo, Sex,
     TipGroupElementType,
     User
 } from '../ntqqapi/types';
@@ -31,6 +31,7 @@ import {OB11GroupUploadNoticeEvent} from "./event/notice/OB11GroupUploadNoticeEv
 import {OB11GroupNoticeEvent} from "./event/notice/OB11GroupNoticeEvent";
 import {NTQQUserApi} from "../ntqqapi/api/user";
 import {NTQQFileApi} from "../ntqqapi/api/file";
+import {calcQQLevel} from "../common/utils/qqlevel";
 
 
 export class OB11Constructor {
@@ -225,6 +226,7 @@ export class OB11Constructor {
         if (msg.chatType !== ChatType.group) {
             return;
         }
+        // log("group msg", msg);
         for (let element of msg.elements) {
             const grayTipElement = element.grayTipElement
             const groupElement = grayTipElement?.groupElement
@@ -325,16 +327,24 @@ export class OB11Constructor {
         }[role]
     }
 
+    static sex(sex: Sex): OB11UserSex{
+        const sexMap = {
+            [Sex.male]: OB11UserSex.male,
+            [Sex.female]: OB11UserSex.female,
+            [Sex.unknown]: OB11UserSex.unknown
+        }
+        return sexMap[sex] || OB11UserSex.unknown
+    }
     static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
         return {
             group_id: parseInt(group_id),
             user_id: parseInt(member.uin),
             nickname: member.nick,
             card: member.cardName,
-            sex: OB11UserSex.unknown,
+            sex: OB11Constructor.sex(member.sex),
             age: 0,
             area: "",
-            level: 0,
+            level: member.qqLevel && calcQQLevel(member.qqLevel) || 0,
             join_time: 0,  // 暂时没法获取
             last_sent_time: 0,  // 暂时没法获取
             title_expire_time: 0,