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 }>([ReceiveCmdS.UPDATE_MSG, ReceiveCmdS.UPDATE_ACTIVE_MSG], async (payload) => { + registerReceiveHook<{ msgList: Array }>([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 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 { 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,