feat: clean cache api

This commit is contained in:
linyuchen 2024-03-09 22:58:21 +08:00
commit 354ee389bc
6 changed files with 567 additions and 274 deletions

@ -1,72 +1,73 @@
import {type BrowserWindow} from 'electron' import {BrowserWindow} from 'electron';
import {getConfigUtil, log, sleep} from '../common/utils' import {getConfigUtil, log, sleep} from "../common/utils";
import {NTQQApi, type NTQQApiClass, sendMessagePool} from './ntcall' import {NTQQApi, NTQQApiClass, sendMessagePool} from "./ntcall";
import {type Group, type RawMessage, type User} from './types' import {Group, RawMessage, User} from "./types";
import {friends, groups, selfInfo, tempGroupCodeMap} from '../common/data' import {friends, groups, selfInfo, tempGroupCodeMap} from "../common/data";
import {OB11GroupDecreaseEvent} from '../onebot11/event/notice/OB11GroupDecreaseEvent' import {OB11GroupDecreaseEvent} from "../onebot11/event/notice/OB11GroupDecreaseEvent";
import {OB11GroupIncreaseEvent} from '../onebot11/event/notice/OB11GroupIncreaseEvent' import {OB11GroupIncreaseEvent} from "../onebot11/event/notice/OB11GroupIncreaseEvent";
import {v4 as uuidv4} from 'uuid' import {v4 as uuidv4} from "uuid"
import {postOB11Event} from '../onebot11/server/postOB11Event' import {postOB11Event} from "../onebot11/server/postOB11Event";
import {HOOK_LOG} from '../common/config' import {HOOK_LOG} from "../common/config";
import fs from 'fs' import fs from "fs";
import {dbUtil} from "../common/db"; import {dbUtil} from "../common/db";
export const hookApiCallbacks: Record<string, (apiReturn: any) => void> = {} export let hookApiCallbacks: Record<string, (apiReturn: any) => void> = {}
export enum ReceiveCmd { export enum ReceiveCmd {
UPDATE_MSG = 'nodeIKernelMsgListener/onMsgInfoListUpdate', UPDATE_MSG = "nodeIKernelMsgListener/onMsgInfoListUpdate",
NEW_MSG = 'nodeIKernelMsgListener/onRecvMsg', NEW_MSG = "nodeIKernelMsgListener/onRecvMsg",
SELF_SEND_MSG = 'nodeIKernelMsgListener/onAddSendMsg', SELF_SEND_MSG = "nodeIKernelMsgListener/onAddSendMsg",
USER_INFO = 'nodeIKernelProfileListener/onProfileSimpleChanged', USER_INFO = "nodeIKernelProfileListener/onProfileSimpleChanged",
USER_DETAIL_INFO = 'nodeIKernelProfileListener/onProfileDetailInfoChanged', USER_DETAIL_INFO = "nodeIKernelProfileListener/onProfileDetailInfoChanged",
GROUPS = 'nodeIKernelGroupListener/onGroupListUpdate', GROUPS = "nodeIKernelGroupListener/onGroupListUpdate",
GROUPS_UNIX = 'onGroupListUpdate', GROUPS_UNIX = "onGroupListUpdate",
FRIENDS = 'onBuddyListChange', FRIENDS = "onBuddyListChange",
MEDIA_DOWNLOAD_COMPLETE = 'nodeIKernelMsgListener/onRichMediaDownloadComplete', MEDIA_DOWNLOAD_COMPLETE = "nodeIKernelMsgListener/onRichMediaDownloadComplete",
UNREAD_GROUP_NOTIFY = 'nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated', UNREAD_GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupNotifiesUnreadCountUpdated",
GROUP_NOTIFY = 'nodeIKernelGroupListener/onGroupSingleScreenNotifies', GROUP_NOTIFY = "nodeIKernelGroupListener/onGroupSingleScreenNotifies",
FRIEND_REQUEST = 'nodeIKernelBuddyListener/onBuddyReqChange', FRIEND_REQUEST = "nodeIKernelBuddyListener/onBuddyReqChange",
SELF_STATUS = 'nodeIKernelProfileListener/onSelfStatusChanged', SELF_STATUS = 'nodeIKernelProfileListener/onSelfStatusChanged',
CACHE_SCAN_FINISH = "nodeIKernelStorageCleanListener/onFinishScan",
} }
interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> { interface NTQQApiReturnData<PayloadType = unknown> extends Array<any> {
0: { 0: {
'type': 'request' "type": "request",
'eventName': NTQQApiClass "eventName": NTQQApiClass,
'callbackId'?: string "callbackId"?: string
} },
1: 1:
Array<{ {
cmdName: ReceiveCmd cmdName: ReceiveCmd,
cmdType: 'event' cmdType: "event",
payload: PayloadType payload: PayloadType
}> }[]
} }
const receiveHooks: Array<{ let receiveHooks: Array<{
method: ReceiveCmd method: ReceiveCmd,
hookFunc: ((payload: any) => void | Promise<void>) hookFunc: ((payload: any) => void | Promise<void>)
id: string id: string
}> = [] }> = []
export function hookNTQQApiReceive(window: BrowserWindow) { export function hookNTQQApiReceive(window: BrowserWindow) {
const originalSend = window.webContents.send const originalSend = window.webContents.send;
const patchSend = (channel: string, ...args: NTQQApiReturnData) => { const patchSend = (channel: string, ...args: NTQQApiReturnData) => {
HOOK_LOG && log(`received ntqq api message: ${channel}`, JSON.stringify(args)) HOOK_LOG && log(`received ntqq api message: ${channel}`, JSON.stringify(args))
if (args?.[1] instanceof Array) { if (args?.[1] instanceof Array) {
for (const receiveData of args?.[1]) { for (let receiveData of args?.[1]) {
const ntQQApiMethodName = receiveData.cmdName const ntQQApiMethodName = receiveData.cmdName;
// log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData)) // log(`received ntqq api message: ${channel} ${ntQQApiMethodName}`, JSON.stringify(receiveData))
for (const hook of receiveHooks) { for (let hook of receiveHooks) {
if (hook.method === ntQQApiMethodName) { if (hook.method === ntQQApiMethodName) {
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
try { try {
const _ = hook.hookFunc(receiveData.payload) let _ = hook.hookFunc(receiveData.payload)
if (hook.hookFunc.constructor.name === 'AsyncFunction') { if (hook.hookFunc.constructor.name === "AsyncFunction") {
(_ as Promise<void>).then() (_ as Promise<void>).then()
} }
} catch (e) { } catch (e) {
log('hook error', e, receiveData.payload) log("hook error", e, receiveData.payload)
} }
}).then() }).then()
} }
@ -75,35 +76,35 @@ export function hookNTQQApiReceive(window: BrowserWindow) {
} }
if (args[0]?.callbackId) { if (args[0]?.callbackId) {
// log("hookApiCallback", hookApiCallbacks, args) // log("hookApiCallback", hookApiCallbacks, args)
const callbackId = args[0].callbackId const callbackId = args[0].callbackId;
if (hookApiCallbacks[callbackId]) { if (hookApiCallbacks[callbackId]) {
// log("callback found") // log("callback found")
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
hookApiCallbacks[callbackId](args[1]) hookApiCallbacks[callbackId](args[1]);
}).then() }).then()
delete hookApiCallbacks[callbackId] delete hookApiCallbacks[callbackId];
} }
} }
return originalSend.call(window.webContents, channel, ...args) return originalSend.call(window.webContents, channel, ...args);
} }
window.webContents.send = patchSend window.webContents.send = patchSend;
} }
export function hookNTQQApiCall(window: BrowserWindow) { export function hookNTQQApiCall(window: BrowserWindow) {
// 监听调用NTQQApi // 监听调用NTQQApi
const webContents = window.webContents as any let webContents = window.webContents as any;
const ipc_message_proxy = webContents._events['-ipc-message']?.[0] || webContents._events['-ipc-message'] const ipc_message_proxy = webContents._events["-ipc-message"]?.[0] || webContents._events["-ipc-message"];
const proxyIpcMsg = new Proxy(ipc_message_proxy, { const proxyIpcMsg = new Proxy(ipc_message_proxy, {
apply(target, thisArg, args) { apply(target, thisArg, args) {
HOOK_LOG && log('call NTQQ api', thisArg, args) HOOK_LOG && log("call NTQQ api", thisArg, args);
return target.apply(thisArg, args) return target.apply(thisArg, args);
} },
}) });
if (webContents._events['-ipc-message']?.[0]) { if (webContents._events["-ipc-message"]?.[0]) {
webContents._events['-ipc-message'][0] = proxyIpcMsg webContents._events["-ipc-message"][0] = proxyIpcMsg;
} else { } else {
webContents._events['-ipc-message'] = proxyIpcMsg webContents._events["-ipc-message"] = proxyIpcMsg;
} }
} }
@ -114,29 +115,29 @@ export function registerReceiveHook<PayloadType>(method: ReceiveCmd, hookFunc: (
hookFunc, hookFunc,
id id
}) })
return id return id;
} }
export function removeReceiveHook(id: string) { export function removeReceiveHook(id: string) {
const index = receiveHooks.findIndex(h => h.id === id) const index = receiveHooks.findIndex(h => h.id === id)
receiveHooks.splice(index, 1) receiveHooks.splice(index, 1);
} }
async function updateGroups(_groups: Group[], needUpdate: boolean = true) { async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
for (const group of _groups) { for (let group of _groups) {
let existGroup = groups.find(g => g.groupCode == group.groupCode) let existGroup = groups.find(g => g.groupCode == group.groupCode);
if (existGroup) { if (existGroup) {
Object.assign(existGroup, group) Object.assign(existGroup, group);
} else { } else {
groups.push(group) groups.push(group);
existGroup = group existGroup = group;
} }
if (needUpdate) { if (needUpdate) {
const members = await NTQQApi.getGroupMembers(group.groupCode) const members = await NTQQApi.getGroupMembers(group.groupCode);
if (members) { if (members) {
existGroup.members = members existGroup.members = members;
} }
} }
} }
@ -144,83 +145,84 @@ async function updateGroups(_groups: Group[], needUpdate: boolean = true) {
async function processGroupEvent(payload) { async function processGroupEvent(payload) {
try { try {
const newGroupList = payload.groupList const newGroupList = payload.groupList;
for (const group of newGroupList) { for (const group of newGroupList) {
const existGroup = groups.find(g => g.groupCode == group.groupCode) let existGroup = groups.find(g => g.groupCode == group.groupCode);
if (existGroup) { if (existGroup) {
if (existGroup.memberCount > group.memberCount) { if (existGroup.memberCount > group.memberCount) {
const oldMembers = existGroup.members const oldMembers = existGroup.members;
await sleep(200) // 如果请求QQ API的速度过快通常无法正确拉取到最新的群信息因此这里人为引入一个延时 await sleep(200); // 如果请求QQ API的速度过快通常无法正确拉取到最新的群信息因此这里人为引入一个延时
const newMembers = await NTQQApi.getGroupMembers(group.groupCode) const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
group.members = newMembers group.members = newMembers;
const newMembersSet = new Set<string>() // 建立索引降低时间复杂度 const newMembersSet = new Set<string>(); // 建立索引降低时间复杂度
for (const member of newMembers) { for (const member of newMembers) {
newMembersSet.add(member.uin) newMembersSet.add(member.uin);
} }
for (const member of oldMembers) { for (const member of oldMembers) {
if (!newMembersSet.has(member.uin)) { if (!newMembersSet.has(member.uin)) {
postOB11Event(new OB11GroupDecreaseEvent(group.groupCode, parseInt(member.uin))) postOB11Event(new OB11GroupDecreaseEvent(group.groupCode, parseInt(member.uin)));
break break;
} }
} }
} else if (existGroup.memberCount < group.memberCount) { } else if (existGroup.memberCount < group.memberCount) {
const oldMembers = existGroup.members const oldMembers = existGroup.members;
const oldMembersSet = new Set<string>() const oldMembersSet = new Set<string>();
for (const member of oldMembers) { for (const member of oldMembers) {
oldMembersSet.add(member.uin) oldMembersSet.add(member.uin);
} }
await sleep(200) await sleep(200);
const newMembers = await NTQQApi.getGroupMembers(group.groupCode) const newMembers = await NTQQApi.getGroupMembers(group.groupCode);
group.members = newMembers group.members = newMembers;
for (const member of newMembers) { for (const member of newMembers) {
if (!oldMembersSet.has(member.uin)) { if (!oldMembersSet.has(member.uin)) {
postOB11Event(new OB11GroupIncreaseEvent(group.groupCode, parseInt(member.uin))) postOB11Event(new OB11GroupIncreaseEvent(group.groupCode, parseInt(member.uin)));
break break;
} }
} }
} }
} }
} }
updateGroups(newGroupList, false).then() updateGroups(newGroupList, false).then();
} catch (e) { } catch (e) {
updateGroups(payload.groupList).then() updateGroups(payload.groupList).then();
console.log(e) console.log(e);
} }
} }
registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => { registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS, (payload) => {
if (payload.updateType != 2) { if (payload.updateType != 2) {
updateGroups(payload.groupList).then() updateGroups(payload.groupList).then();
} else { } else {
if (process.platform == 'win32') { if (process.platform == "win32") {
processGroupEvent(payload).then() processGroupEvent(payload).then();
} }
} }
}) })
registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => { registerReceiveHook<{ groupList: Group[], updateType: number }>(ReceiveCmd.GROUPS_UNIX, (payload) => {
if (payload.updateType != 2) { if (payload.updateType != 2) {
updateGroups(payload.groupList).then() updateGroups(payload.groupList).then();
} else { } else {
if (process.platform != 'win32') { if (process.platform != "win32") {
processGroupEvent(payload).then() processGroupEvent(payload).then();
} }
} }
}) })
registerReceiveHook<{ registerReceiveHook<{
data: Array<{ categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }> data: { categoryId: number, categroyName: string, categroyMbCount: number, buddyList: User[] }[]
}>(ReceiveCmd.FRIENDS, payload => { }>(ReceiveCmd.FRIENDS, payload => {
for (const fData of payload.data) { for (const fData of payload.data) {
const _friends = fData.buddyList const _friends = fData.buddyList;
for (const friend of _friends) { for (let friend of _friends) {
const existFriend = friends.find(f => f.uin == friend.uin) let existFriend = friends.find(f => f.uin == friend.uin)
if (!existFriend) { if (!existFriend) {
friends.push(friend) friends.push(friend)
} else { } else {
@ -230,8 +232,8 @@ registerReceiveHook<{
} }
}) })
registerReceiveHook<{ msgList: RawMessage[] }>(ReceiveCmd.NEW_MSG, (payload) => { registerReceiveHook<{ msgList: Array<RawMessage> }>(ReceiveCmd.NEW_MSG, (payload) => {
const {autoDeleteFile, autoDeleteFileSecond} = getConfigUtil().getConfig() const {autoDeleteFile} = getConfigUtil().getConfig();
for (const message of payload.msgList) { for (const message of payload.msgList) {
// log("收到新消息push到历史记录", message.msgSeq) // log("收到新消息push到历史记录", message.msgSeq)
dbUtil.addMsg(message).then() dbUtil.addMsg(message).then()
@ -255,27 +257,27 @@ registerReceiveHook<{ msgList: RawMessage[] }>(ReceiveCmd.NEW_MSG, (payload) =>
for (const path of pathList) { for (const path of pathList) {
if (path) { if (path) {
fs.unlink(picPath, () => { fs.unlink(picPath, () => {
log('删除文件成功', path) log("删除文件成功", path)
}) });
} }
} }
}, autoDeleteFileSecond * 1000) }, 60 * 1000)
} }
} }
}) })
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, ({msgRecord}) => { registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmd.SELF_SEND_MSG, ({msgRecord}) => {
const message = msgRecord const message = msgRecord;
const peerUid = message.peerUid const peerUid = message.peerUid;
// log("收到自己发送成功的消息", Object.keys(sendMessagePool), message); // log("收到自己发送成功的消息", Object.keys(sendMessagePool), message);
// log("收到自己发送成功的消息", message); // log("收到自己发送成功的消息", message);
dbUtil.addMsg(message).then() dbUtil.addMsg(message).then()
const sendCallback = sendMessagePool[peerUid] const sendCallback = sendMessagePool[peerUid]
if (sendCallback) { if (sendCallback) {
try { try {
sendCallback(message) sendCallback(message);
} catch (e) { } catch (e) {
log('receive self msg error', e.stack) log("receive self msg error", e.stack)
} }
} }
}) })

@ -1,25 +1,29 @@
import {ipcMain} from 'electron' import {ipcMain} from "electron";
import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from './hook' import {hookApiCallbacks, ReceiveCmd, registerReceiveHook, removeReceiveHook} from "./hook";
import {log, sleep} from '../common/utils' import {log, sleep} from "../common/utils";
import { import {
type ChatType, ChatType,
ElementType, ElementType,
type Friend, Friend,
type FriendRequest, FriendRequest,
type Group, GroupMember, Group,
type GroupMemberRole, GroupMember,
type GroupNotifies, GroupMemberRole,
type GroupNotify, GroupNotifies,
type GroupRequestOperateTypes, GroupNotify,
type RawMessage, GroupRequestOperateTypes,
type SelfInfo, RawMessage,
type SendMessageElement, SelfInfo,
type User SendMessageElement,
} from './types' User,
import * as fs from 'node:fs' CacheScanResult,
import {friendRequests, groupNotifies, selfInfo, uidMaps} from '../common/data' ChatCacheList, ChatCacheListItemBasic,
import {v4 as uuidv4} from 'uuid' CacheFileList, CacheFileListItem, CacheFileType,
import path from 'path' } from "./types";
import * as fs from "fs";
import {friendRequests, groupNotifies, selfInfo, uidMaps} from "../common/data";
import {v4 as uuidv4} from "uuid"
import path from "path";
import {dbUtil} from "../common/db"; import {dbUtil} from "../common/db";
interface IPCReceiveEvent { interface IPCReceiveEvent {
@ -35,89 +39,104 @@ export type IPCReceiveDetail = [
] ]
export enum NTQQApiClass { export enum NTQQApiClass {
NT_API = 'ns-ntApi', NT_API = "ns-ntApi",
FS_API = 'ns-FsApi', FS_API = "ns-FsApi",
GLOBAL_DATA = 'ns-GlobalDataApi' OS_API = "ns-OsApi",
HOTUPDATE_API = "ns-HotUpdateApi",
BUSINESS_API = "ns-BusinessApi",
GLOBAL_DATA = "ns-GlobalDataApi"
} }
export enum NTQQApiMethod { export enum NTQQApiMethod {
LIKE_FRIEND = 'nodeIKernelProfileLikeService/setBuddyProfileLike', LIKE_FRIEND = "nodeIKernelProfileLikeService/setBuddyProfileLike",
SELF_INFO = 'fetchAuthData', SELF_INFO = "fetchAuthData",
FRIENDS = 'nodeIKernelBuddyService/getBuddyList', FRIENDS = "nodeIKernelBuddyService/getBuddyList",
GROUPS = 'nodeIKernelGroupService/getGroupList', GROUPS = "nodeIKernelGroupService/getGroupList",
GROUP_MEMBER_SCENE = 'nodeIKernelGroupService/createMemberListScene', GROUP_MEMBER_SCENE = "nodeIKernelGroupService/createMemberListScene",
GROUP_MEMBERS = 'nodeIKernelGroupService/getNextMemberList', GROUP_MEMBERS = "nodeIKernelGroupService/getNextMemberList",
USER_INFO = 'nodeIKernelProfileService/getUserSimpleInfo', USER_INFO = "nodeIKernelProfileService/getUserSimpleInfo",
USER_DETAIL_INFO = 'nodeIKernelProfileService/getUserDetailInfo', USER_DETAIL_INFO = "nodeIKernelProfileService/getUserDetailInfo",
FILE_TYPE = 'getFileType', FILE_TYPE = "getFileType",
FILE_MD5 = 'getFileMd5', FILE_MD5 = "getFileMd5",
FILE_COPY = 'copyFile', FILE_COPY = "copyFile",
IMAGE_SIZE = 'getImageSizeFromPath', IMAGE_SIZE = "getImageSizeFromPath",
FILE_SIZE = 'getFileSize', FILE_SIZE = "getFileSize",
MEDIA_FILE_PATH = 'nodeIKernelMsgService/getRichMediaFilePathForGuild', MEDIA_FILE_PATH = "nodeIKernelMsgService/getRichMediaFilePathForGuild",
RECALL_MSG = 'nodeIKernelMsgService/recallMsg', RECALL_MSG = "nodeIKernelMsgService/recallMsg",
SEND_MSG = 'nodeIKernelMsgService/sendMsg', SEND_MSG = "nodeIKernelMsgService/sendMsg",
DOWNLOAD_MEDIA = 'nodeIKernelMsgService/downloadRichMedia', DOWNLOAD_MEDIA = "nodeIKernelMsgService/downloadRichMedia",
FORWARD_MSG = "nodeIKernelMsgService/forwardMsgWithComment", // 逐条转发 FORWARD_MSG = "nodeIKernelMsgService/forwardMsgWithComment",
MULTI_FORWARD_MSG = 'nodeIKernelMsgService/multiForwardMsgWithComment', // 合并转发 MULTI_FORWARD_MSG = "nodeIKernelMsgService/multiForwardMsgWithComment", // 合并转发
GET_GROUP_NOTICE = 'nodeIKernelGroupService/getSingleScreenNotifies', GET_GROUP_NOTICE = "nodeIKernelGroupService/getSingleScreenNotifies",
HANDLE_GROUP_REQUEST = 'nodeIKernelGroupService/operateSysNotify', HANDLE_GROUP_REQUEST = "nodeIKernelGroupService/operateSysNotify",
QUIT_GROUP = 'nodeIKernelGroupService/quitGroup', QUIT_GROUP = "nodeIKernelGroupService/quitGroup",
// READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange" // READ_FRIEND_REQUEST = "nodeIKernelBuddyListener/onDoubtBuddyReqUnreadNumChange"
HANDLE_FRIEND_REQUEST = 'nodeIKernelBuddyService/approvalFriendRequest', HANDLE_FRIEND_REQUEST = "nodeIKernelBuddyService/approvalFriendRequest",
KICK_MEMBER = 'nodeIKernelGroupService/kickMember', KICK_MEMBER = "nodeIKernelGroupService/kickMember",
MUTE_MEMBER = 'nodeIKernelGroupService/setMemberShutUp', MUTE_MEMBER = "nodeIKernelGroupService/setMemberShutUp",
MUTE_GROUP = 'nodeIKernelGroupService/setGroupShutUp', MUTE_GROUP = "nodeIKernelGroupService/setGroupShutUp",
SET_MEMBER_CARD = 'nodeIKernelGroupService/modifyMemberCardName', SET_MEMBER_CARD = "nodeIKernelGroupService/modifyMemberCardName",
SET_MEMBER_ROLE = 'nodeIKernelGroupService/modifyMemberRole', SET_MEMBER_ROLE = "nodeIKernelGroupService/modifyMemberRole",
PUBLISH_GROUP_BULLETIN = 'nodeIKernelGroupService/publishGroupBulletinBulletin', PUBLISH_GROUP_BULLETIN = "nodeIKernelGroupService/publishGroupBulletinBulletin",
SET_GROUP_NAME = 'nodeIKernelGroupService/modifyGroupName', SET_GROUP_NAME = "nodeIKernelGroupService/modifyGroupName",
CACHE_SET_SILENCE = 'nodeIKernelStorageCleanService/setSilentScan',
CACHE_ADD_SCANNED_PATH = 'nodeIKernelStorageCleanService/addCacheScanedPaths',
CACHE_PATH_HOT_UPDATE = 'getHotUpdateCachePath',
CACHE_PATH_DESKTOP_TEMP = 'getDesktopTmpPath',
CACHE_PATH_SESSION = 'getCleanableAppSessionPathList',
CACHE_SCAN = 'nodeIKernelStorageCleanService/scanCache',
CACHE_CLEAR = 'nodeIKernelStorageCleanService/clearCacheDataByKeys',
CACHE_CHAT_GET = 'nodeIKernelStorageCleanService/getChatCacheInfo',
CACHE_FILE_GET = 'nodeIKernelStorageCleanService/getFileCacheInfo',
CACHE_CHAT_CLEAR = 'nodeIKernelStorageCleanService/clearChatCacheInfo',
} }
enum NTQQApiChannel { enum NTQQApiChannel {
IPC_UP_2 = 'IPC_UP_2', IPC_UP_2 = "IPC_UP_2",
IPC_UP_3 = 'IPC_UP_3', IPC_UP_3 = "IPC_UP_3",
IPC_UP_1 = 'IPC_UP_1', IPC_UP_1 = "IPC_UP_1",
} }
export interface Peer { export interface Peer {
chatType: ChatType chatType: ChatType
peerUid: string // 如果是群聊uid为群号私聊uid就是加密的字符串 peerUid: string // 如果是群聊uid为群号私聊uid就是加密的字符串
guildId?: '' guildId?: ""
} }
interface NTQQApiParams { interface NTQQApiParams {
methodName: NTQQApiMethod | string methodName: NTQQApiMethod | string,
className?: NTQQApiClass className?: NTQQApiClass,
channel?: NTQQApiChannel channel?: NTQQApiChannel,
classNameIsRegister?: boolean classNameIsRegister?: boolean
args?: unknown[] args?: unknown[],
cbCmd?: ReceiveCmd | null cbCmd?: ReceiveCmd | null,
cmdCB?: (payload: any) => boolean cmdCB?: (payload: any) => boolean;
afterFirstCmd?: boolean // 是否在methodName调用完之后再去hook cbCmd afterFirstCmd?: boolean, // 是否在methodName调用完之后再去hook cbCmd
timeoutSecond?: number timeoutSecond?: number,
} }
async function callNTQQApi<ReturnType>(params: NTQQApiParams) { function callNTQQApi<ReturnType>(params: NTQQApiParams) {
let { let {
className, methodName, channel, args, className, methodName, channel, args,
cbCmd, timeoutSecond: timeout, cbCmd, timeoutSecond: timeout,
classNameIsRegister, cmdCB, afterFirstCmd classNameIsRegister, cmdCB, afterFirstCmd
} = params } = params;
className = className ?? NTQQApiClass.NT_API className = className ?? NTQQApiClass.NT_API;
channel = channel ?? NTQQApiChannel.IPC_UP_2 channel = channel ?? NTQQApiChannel.IPC_UP_2;
args = args ?? [] args = args ?? [];
timeout = timeout ?? 5 timeout = timeout ?? 5;
afterFirstCmd = afterFirstCmd ?? true afterFirstCmd = afterFirstCmd ?? true;
const uuid = uuidv4() const uuid = uuidv4();
// log("callNTQQApi", channel, className, methodName, args, uuid) // log("callNTQQApi", channel, className, methodName, args, uuid)
return await new Promise((resolve: (data: ReturnType) => void, reject) => { return new Promise((resolve: (data: ReturnType) => void, reject) => {
// log("callNTQQApiPromise", channel, className, methodName, args, uuid) // log("callNTQQApiPromise", channel, className, methodName, args, uuid)
const _timeout = timeout * 1000 const _timeout = timeout * 1000
let success = false let success = false
let eventName = className + '-' + channel[channel.length - 1] let eventName = className + "-" + channel[channel.length - 1];
if (classNameIsRegister) { if (classNameIsRegister) {
eventName += '-register' eventName += "-register";
} }
const apiArgs = [methodName, ...args] const apiArgs = [methodName, ...args]
if (!cbCmd) { if (!cbCmd) {
@ -125,40 +144,40 @@ async function callNTQQApi<ReturnType>(params: NTQQApiParams) {
hookApiCallbacks[uuid] = (r: ReturnType) => { hookApiCallbacks[uuid] = (r: ReturnType) => {
success = true success = true
resolve(r) resolve(r)
} };
} else { } else {
// 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据 // 这里的callback比较特殊QQ后端先返回是否调用成功再返回一条结果数据
const secondCallback = () => { const secondCallback = () => {
const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => { const hookId = registerReceiveHook<ReturnType>(cbCmd, (payload) => {
// log(methodName, "second callback", cbCmd, payload, cmdCB); // log(methodName, "second callback", cbCmd, payload, cmdCB);
if (cmdCB) { if (!!cmdCB) {
if (cmdCB(payload)) { if (cmdCB(payload)) {
removeReceiveHook(hookId) removeReceiveHook(hookId);
success = true success = true
resolve(payload) resolve(payload);
} }
} else { } else {
removeReceiveHook(hookId) removeReceiveHook(hookId);
success = true success = true
resolve(payload) resolve(payload);
} }
}) })
} }
!afterFirstCmd && secondCallback() !afterFirstCmd && secondCallback();
hookApiCallbacks[uuid] = (result: GeneralCallResult) => { hookApiCallbacks[uuid] = (result: GeneralCallResult) => {
log(`${methodName} callback`, result) log(`${methodName} callback`, result)
if (result?.result == 0 || result === undefined) { if (result?.result == 0 || result === undefined) {
afterFirstCmd && secondCallback() afterFirstCmd && secondCallback();
} else { } else {
success = true success = true
reject(`ntqq api call failed, ${result.errMsg}`) reject(`ntqq api call failed, ${result.errMsg}`);
} }
} }
} }
setTimeout(() => { setTimeout(() => {
// log("ntqq api timeout", success, channel, className, methodName) // log("ntqq api timeout", success, channel, className, methodName)
if (!success) { if (!success) {
log(`ntqq api timeout ${channel}, ${eventName}, ${methodName}`, apiArgs) log(`ntqq api timeout ${channel}, ${eventName}, ${methodName}`, apiArgs);
reject(`ntqq api timeout ${channel}, ${eventName}, ${methodName}, ${apiArgs}`) reject(`ntqq api timeout ${channel}, ${eventName}, ${methodName}, ${apiArgs}`)
} }
}, _timeout) }, _timeout)
@ -172,13 +191,15 @@ async function callNTQQApi<ReturnType>(params: NTQQApiParams) {
}) })
} }
export const sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {}// peerUid: callbackFunnc
export let sendMessagePool: Record<string, ((sendSuccessMsg: RawMessage) => void) | null> = {}// peerUid: callbackFunnc
interface GeneralCallResult { interface GeneralCallResult {
result: number // 0: success result: number, // 0: success
errMsg: string errMsg: string
} }
export class NTQQApi { export class NTQQApi {
// static likeFriend = defineNTQQApi<void>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.LIKE_FRIEND) // static likeFriend = defineNTQQApi<void>(NTQQApiChannel.IPC_UP_2, NTQQApiClass.NT_API, NTQQApiMethod.LIKE_FRIEND)
static async likeFriend(uid: string, count = 1) { static async likeFriend(uid: string, count = 1) {
@ -198,9 +219,7 @@ export class NTQQApi {
static async getSelfInfo() { static async getSelfInfo() {
return await callNTQQApi<SelfInfo>({ return await callNTQQApi<SelfInfo>({
className: NTQQApiClass.GLOBAL_DATA, className: NTQQApiClass.GLOBAL_DATA,
// channel: NTQQApiChannel.IPC_UP_3, methodName: NTQQApiMethod.SELF_INFO, timeoutSecond: 2
methodName: NTQQApiMethod.SELF_INFO,
timeoutSecond: 2
}) })
} }
@ -235,19 +254,19 @@ export class NTQQApi {
static async getFriends(forced = false) { static async getFriends(forced = false) {
const data = await callNTQQApi<{ const data = await callNTQQApi<{
data: Array<{ data: {
categoryId: number categoryId: number,
categroyName: string categroyName: string,
categroyMbCount: number categroyMbCount: number,
buddyList: Friend[] buddyList: Friend[]
}> }[]
}>( }>(
{ {
methodName: NTQQApiMethod.FRIENDS, methodName: NTQQApiMethod.FRIENDS,
args: [{force_update: forced}, undefined], args: [{force_update: forced}, undefined],
cbCmd: ReceiveCmd.FRIENDS cbCmd: ReceiveCmd.FRIENDS
}) })
const _friends: Friend[] = [] let _friends: Friend[] = [];
for (const fData of data.data) { for (const fData of data.data) {
_friends.push(...fData.buddyList) _friends.push(...fData.buddyList)
} }
@ -256,11 +275,11 @@ export class NTQQApi {
static async getGroups(forced = false) { static async getGroups(forced = false) {
let cbCmd = ReceiveCmd.GROUPS let cbCmd = ReceiveCmd.GROUPS
if (process.platform != 'win32') { if (process.platform != "win32") {
cbCmd = ReceiveCmd.GROUPS_UNIX cbCmd = ReceiveCmd.GROUPS_UNIX
} }
const result = await callNTQQApi<{ const result = await callNTQQApi<{
updateType: number updateType: number,
groupList: Group[] groupList: Group[]
}>({methodName: NTQQApiMethod.GROUPS, args: [{force_update: forced}, undefined], cbCmd}) }>({methodName: NTQQApiMethod.GROUPS, args: [{force_update: forced}, undefined], cbCmd})
return result.groupList return result.groupList
@ -271,7 +290,7 @@ export class NTQQApi {
methodName: NTQQApiMethod.GROUP_MEMBER_SCENE, methodName: NTQQApiMethod.GROUP_MEMBER_SCENE,
args: [{ args: [{
groupCode: groupQQ, groupCode: groupQQ,
scene: 'groupMemberList_MainWindow' scene: "groupMemberList_MainWindow"
}] }]
}) })
// log("get group member sceneId", sceneId); // log("get group member sceneId", sceneId);
@ -281,8 +300,8 @@ export class NTQQApi {
}>({ }>({
methodName: NTQQApiMethod.GROUP_MEMBERS, methodName: NTQQApiMethod.GROUP_MEMBERS,
args: [{ args: [{
sceneId, sceneId: sceneId,
num num: num
}, },
null null
] ]
@ -343,35 +362,35 @@ export class NTQQApi {
// 上传文件到QQ的文件夹 // 上传文件到QQ的文件夹
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC) { static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC) {
const md5 = await NTQQApi.getFileMd5(filePath) const md5 = await NTQQApi.getFileMd5(filePath);
let ext = (await NTQQApi.getFileType(filePath))?.ext let ext = (await NTQQApi.getFileType(filePath))?.ext
if (ext) { if (ext) {
ext = '.' + ext ext = "." + ext
} else { } else {
ext = '' ext = ""
} }
let fileName = `${path.basename(filePath)}` let fileName = `${path.basename(filePath)}`;
if (!fileName.includes('.')) { if (fileName.indexOf(".") === -1) {
fileName += ext fileName += ext;
} }
const mediaPath = await callNTQQApi<string>({ const mediaPath = await callNTQQApi<string>({
methodName: NTQQApiMethod.MEDIA_FILE_PATH, methodName: NTQQApiMethod.MEDIA_FILE_PATH,
args: [{ args: [{
path_info: { path_info: {
md5HexStr: md5, md5HexStr: md5,
fileName, fileName: fileName,
elementType, elementType: elementType,
elementSubType: 0, elementSubType: 0,
thumbSize: 0, thumbSize: 0,
needCreate: true, needCreate: true,
downloadType: 1, downloadType: 1,
file_uuid: '' file_uuid: ""
} }
}] }]
}) })
log('media path', mediaPath) log("media path", mediaPath)
await NTQQApi.copyFile(filePath, mediaPath) await NTQQApi.copyFile(filePath, mediaPath);
const fileSize = await NTQQApi.getFileSize(filePath) const fileSize = await NTQQApi.getFileSize(filePath);
return { return {
md5, md5,
fileName, fileName,
@ -388,16 +407,16 @@ export class NTQQApi {
const apiParams = [ const apiParams = [
{ {
getReq: { getReq: {
msgId, msgId: msgId,
chatType, chatType: chatType,
peerUid, peerUid: peerUid,
elementId, elementId: elementId,
thumbSize: 0, thumbSize: 0,
downloadType: 1, downloadType: 1,
filePath: thumbPath filePath: thumbPath,
} },
}, },
undefined undefined,
] ]
// log("需要下载media", sourcePath); // log("需要下载media", sourcePath);
await callNTQQApi({ await callNTQQApi({
@ -406,7 +425,7 @@ export class NTQQApi {
cbCmd: ReceiveCmd.MEDIA_DOWNLOAD_COMPLETE, cbCmd: ReceiveCmd.MEDIA_DOWNLOAD_COMPLETE,
cmdCB: (payload: { notifyInfo: { filePath: string } }) => { cmdCB: (payload: { notifyInfo: { filePath: string } }) => {
// log("media 下载完成判断", payload.notifyInfo.filePath, sourcePath); // log("media 下载完成判断", payload.notifyInfo.filePath, sourcePath);
return payload.notifyInfo.filePath == sourcePath return payload.notifyInfo.filePath == sourcePath;
} }
}) })
return sourcePath return sourcePath
@ -426,30 +445,30 @@ export class NTQQApi {
const peerUid = peer.peerUid const peerUid = peer.peerUid
// 等待上一个相同的peer发送完 // 等待上一个相同的peer发送完
let checkLastSendUsingTime = 0 let checkLastSendUsingTime = 0;
const waitLastSend = async () => { const waitLastSend = async () => {
if (checkLastSendUsingTime > timeout) { if (checkLastSendUsingTime > timeout) {
throw ('发送超时') throw ("发送超时")
} }
const lastSending = sendMessagePool[peer.peerUid] let lastSending = sendMessagePool[peer.peerUid]
if (lastSending) { if (lastSending) {
// log("有正在发送的消息,等待中...") // log("有正在发送的消息,等待中...")
await sleep(500) await sleep(500);
checkLastSendUsingTime += 500 checkLastSendUsingTime += 500;
return await waitLastSend() return await waitLastSend();
} else { } else {
return;
} }
} }
await waitLastSend() await waitLastSend();
let sentMessage: RawMessage = null let sentMessage: RawMessage = null;
sendMessagePool[peerUid] = async (rawMessage: RawMessage) => { sendMessagePool[peerUid] = async (rawMessage: RawMessage) => {
delete sendMessagePool[peerUid] delete sendMessagePool[peerUid];
sentMessage = rawMessage sentMessage = rawMessage;
} }
let checkSendCompleteUsingTime = 0 let checkSendCompleteUsingTime = 0;
const checkSendComplete = async (): Promise<RawMessage> => { const checkSendComplete = async (): Promise<RawMessage> => {
if (sentMessage) { if (sentMessage) {
if (waitComplete) { if (waitComplete) {
@ -469,14 +488,13 @@ export class NTQQApi {
await sleep(500) await sleep(500)
return await checkSendComplete() return await checkSendComplete()
} }
log("开始发送消息", peer, msgElements)
callNTQQApi({ callNTQQApi({
methodName: NTQQApiMethod.SEND_MSG, methodName: NTQQApiMethod.SEND_MSG,
args: [{ args: [{
msgId: '0', msgId: "0",
peer, peer, msgElements,
msgElements, msgAttributeInfos: new Map(),
msgAttributeInfos: new Map()
}, null] }, null]
}).then() }).then()
return await checkSendComplete() return await checkSendComplete()
@ -512,13 +530,13 @@ export class NTQQApi {
commentElements: [], commentElements: [],
msgAttributeInfos: new Map() msgAttributeInfos: new Map()
}, },
null null,
] ]
return await new Promise<RawMessage>((resolve, reject) => { return await new Promise<RawMessage>((resolve, reject) => {
let complete = false let complete = false
setTimeout(() => { setTimeout(() => {
if (!complete) { if (!complete) {
reject('转发消息超时') reject("转发消息超时");
} }
}, 5000) }, 5000)
registerReceiveHook(ReceiveCmd.SELF_SEND_MSG, async (payload: { msgRecord: RawMessage }) => { registerReceiveHook(ReceiveCmd.SELF_SEND_MSG, async (payload: { msgRecord: RawMessage }) => {
@ -544,10 +562,10 @@ export class NTQQApi {
methodName: NTQQApiMethod.MULTI_FORWARD_MSG, methodName: NTQQApiMethod.MULTI_FORWARD_MSG,
args: apiArgs args: apiArgs
}).then(result => { }).then(result => {
log('转发消息结果:', result, apiArgs) log("转发消息结果:", result, apiArgs)
if (result.result !== 0) { if (result.result !== 0) {
complete = true complete = true;
reject('转发消息失败,' + JSON.stringify(result)) reject("转发消息失败," + JSON.stringify(result));
} }
}) })
}) })
@ -558,56 +576,56 @@ export class NTQQApi {
// 加群通知,退出通知,需要管理员权限 // 加群通知,退出通知,需要管理员权限
callNTQQApi<GeneralCallResult>({ callNTQQApi<GeneralCallResult>({
methodName: ReceiveCmd.GROUP_NOTIFY, methodName: ReceiveCmd.GROUP_NOTIFY,
classNameIsRegister: true classNameIsRegister: true,
}).then() }).then()
return await callNTQQApi<GroupNotifies>({ return await callNTQQApi<GroupNotifies>({
methodName: NTQQApiMethod.GET_GROUP_NOTICE, methodName: NTQQApiMethod.GET_GROUP_NOTICE,
cbCmd: ReceiveCmd.GROUP_NOTIFY, cbCmd: ReceiveCmd.GROUP_NOTIFY,
afterFirstCmd: false, afterFirstCmd: false,
args: [ args: [
{doubt: false, startSeq: '', number: 14}, {"doubt": false, "startSeq": "", "number": 14},
null null
] ]
}) });
} }
static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) { static async handleGroupRequest(seq: string, operateType: GroupRequestOperateTypes, reason?: string) {
const notify: GroupNotify = groupNotifies[seq] const notify: GroupNotify = groupNotifies[seq];
if (!notify) { if (!notify) {
throw `${seq}对应的加群通知不存在` throw `${seq}对应的加群通知不存在`
} }
delete groupNotifies[seq] delete groupNotifies[seq];
return await callNTQQApi<GeneralCallResult>({ return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST, methodName: NTQQApiMethod.HANDLE_GROUP_REQUEST,
args: [ args: [
{ {
doubt: false, "doubt": false,
operateMsg: { "operateMsg": {
operateType, // 2 拒绝 "operateType": operateType, // 2 拒绝
targetMsg: { "targetMsg": {
seq, // 通知序列号 "seq": seq, // 通知序列号
type: notify.type, "type": notify.type,
groupCode: notify.group.groupCode, "groupCode": notify.group.groupCode,
postscript: reason "postscript": reason
} }
} }
}, },
null null
] ]
}) });
} }
static async quitGroup(groupQQ: string) { static async quitGroup(groupQQ: string) {
await callNTQQApi<GeneralCallResult>({ await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.QUIT_GROUP, methodName: NTQQApiMethod.QUIT_GROUP,
args: [ args: [
{groupCode: groupQQ}, {"groupCode": groupQQ},
null null
] ]
}) })
} }
static async handleFriendRequest(sourceId: number, accept: boolean) { static async handleFriendRequest(sourceId: number, accept: boolean,) {
const request: FriendRequest = friendRequests[sourceId] const request: FriendRequest = friendRequests[sourceId]
if (!request) { if (!request) {
throw `sourceId ${sourceId}, 对应的好友请求不存在` throw `sourceId ${sourceId}, 对应的好友请求不存在`
@ -616,16 +634,16 @@ export class NTQQApi {
methodName: NTQQApiMethod.HANDLE_FRIEND_REQUEST, methodName: NTQQApiMethod.HANDLE_FRIEND_REQUEST,
args: [ args: [
{ {
approvalInfo: { "approvalInfo": {
friendUid: request.friendUid, "friendUid": request.friendUid,
reqTime: request.reqTime, "reqTime": request.reqTime,
accept accept
} }
} }
] ]
}) })
delete friendRequests[sourceId] delete friendRequests[sourceId];
return result return result;
} }
static async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') { static async kickMember(groupQQ: string, kickUids: string[], refuseForever: boolean = false, kickReason: string = '') {
@ -637,7 +655,7 @@ export class NTQQApi {
groupCode: groupQQ, groupCode: groupQQ,
kickUids, kickUids,
refuseForever, refuseForever,
kickReason kickReason,
} }
] ]
} }
@ -652,7 +670,7 @@ export class NTQQApi {
args: [ args: [
{ {
groupCode: groupQQ, groupCode: groupQQ,
memList memList,
} }
] ]
} }
@ -712,4 +730,107 @@ export class NTQQApi {
static publishGroupBulletin(groupQQ: string, title: string, content: string) { static publishGroupBulletin(groupQQ: string, title: string, content: string) {
} }
}
static async setCacheSilentScan(isSilent: boolean = true) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_SET_SILENCE,
args: [{
isSilent
}, null]
});
}
static addCacheScannedPaths(pathMap: object = {}) {
return callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH,
args: [{
pathMap: {...pathMap},
}, null]
});
}
static scanCache() {
callNTQQApi<GeneralCallResult>({
methodName: ReceiveCmd.CACHE_SCAN_FINISH,
classNameIsRegister: true,
}).then();
return callNTQQApi<CacheScanResult>({
methodName: NTQQApiMethod.CACHE_SCAN,
args: [null, null],
timeoutSecond: 300,
});
}
static getHotUpdateCachePath() {
return callNTQQApi<string>({
className: NTQQApiClass.HOTUPDATE_API,
methodName: NTQQApiMethod.CACHE_PATH_HOT_UPDATE
});
}
static getDesktopTmpPath() {
return callNTQQApi<string>({
className: NTQQApiClass.BUSINESS_API,
methodName: NTQQApiMethod.CACHE_PATH_DESKTOP_TEMP
});
}
static getCacheSessionPathList() {
return callNTQQApi<{
key: string,
value: string
}[]>({
className: NTQQApiClass.OS_API,
methodName: NTQQApiMethod.CACHE_PATH_SESSION,
});
}
static clearCache(cacheKeys: Array<string> = [ 'tmp', 'hotUpdate' ]) {
return callNTQQApi<any>({ // TODO: 目前还不知道真正的返回值是什么
methodName: NTQQApiMethod.CACHE_CLEAR,
args: [{
keys: cacheKeys
}, null]
});
}
static getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
return new Promise<ChatCacheList>((res, rej) => {
callNTQQApi<ChatCacheList>({
methodName: NTQQApiMethod.CACHE_CHAT_GET,
args: [{
chatType: type,
pageSize,
order: 1,
pageIndex
}, null]
}).then(list => res(list))
.catch(e => rej(e));
});
}
static getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
const _lastRecord = lastRecord ? lastRecord : { fileType: fileType };
return callNTQQApi<CacheFileList>({
methodName: NTQQApiMethod.CACHE_FILE_GET,
args: [{
fileType: fileType,
restart: true,
pageSize: pageSize,
order: 1,
lastRecord: _lastRecord,
}, null]
})
}
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR,
args: [{
chats,
fileKeys
}, null]
});
}
}

@ -403,4 +403,68 @@ export interface FriendRequestNotify {
unreadNums: number, unreadNums: number,
buddyReqs: FriendRequest[] 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,
}

@ -0,0 +1,103 @@
import BaseAction from "./BaseAction";
import {ActionName} from "./types";
import {NTQQApi} from "../../ntqqapi/ntcall";
import fs from "fs";
import Path from "path";
import {
ChatType,
ChatCacheListItemBasic,
CacheFileType
} from '../../ntqqapi/types';
export default class CleanCache extends BaseAction<void, void> {
actionName = ActionName.CleanCache
protected _handle(): Promise<void> {
return new Promise<void>(async (res, rej) => {
try {
const cacheFilePaths: string[] = [];
await NTQQApi.setCacheSilentScan(false);
cacheFilePaths.push((await NTQQApi.getHotUpdateCachePath()));
cacheFilePaths.push((await NTQQApi.getDesktopTmpPath()));
(await NTQQApi.getCacheSessionPathList()).forEach(e => cacheFilePaths.push(e.value));
// await NTQQApi.addCacheScannedPaths(); // XXX: 调用就崩溃,原因目前还未知
const cacheScanResult = await NTQQApi.scanCache();
const cacheSize = parseInt(cacheScanResult.size[6]);
if (cacheScanResult.result !== 0) {
throw('Something went wrong while scanning cache. Code: ' + cacheScanResult.result);
}
await NTQQApi.setCacheSilentScan(true);
if (cacheSize > 0 && cacheFilePaths.length > 2) { // 存在缓存文件且大小不为 0 时执行清理动作
// await NTQQApi.clearCache([ 'tmp', 'hotUpdate', ...cacheScanResult ]) // XXX: 也是调用就崩溃,调用 fs 删除得了
deleteCachePath(cacheFilePaths);
}
// 获取聊天记录列表
// NOTE: 以防有人不需要删除聊天记录,暂时先注释掉,日后加个开关
// const privateChatCache = await getCacheList(ChatType.friend); // 私聊消息
// const groupChatCache = await getCacheList(ChatType.group); // 群聊消息
// const chatCacheList = [ ...privateChatCache, ...groupChatCache ];
const chatCacheList: ChatCacheListItemBasic[] = [];
// 获取聊天缓存文件列表
const cacheFileList: string[] = [];
for (const name in CacheFileType) {
if (!isNaN(parseInt(name))) continue;
const fileTypeAny: any = CacheFileType[name];
const fileType: CacheFileType = fileTypeAny;
cacheFileList.push(...(await NTQQApi.getFileCacheInfo(fileType)).infos.map(file => file.fileKey));
}
// 一并清除
await NTQQApi.clearChatCache(chatCacheList, cacheFileList);
res();
} catch(e) {
console.error('清理缓存时发生了错误');
rej(e);
}
});
}
}
function deleteCachePath(pathList: string[]) {
const emptyPath = (path: string) => {
if (!fs.existsSync(path)) return;
const files = fs.readdirSync(path);
files.forEach(file => {
const filePath = Path.resolve(path, file);
const stats = fs.statSync(filePath);
if (stats.isDirectory()) emptyPath(filePath);
else fs.unlinkSync(filePath);
});
fs.rmdirSync(path);
}
for (const path of pathList) {
emptyPath(path);
}
}
function getCacheList(type: ChatType) { // NOTE: 做这个方法主要是因为目前还不支持针对频道消息的清理
return new Promise<Array<ChatCacheListItemBasic>>((res, rej) => {
NTQQApi.getChatCacheList(type, 1000, 0)
.then(data => {
const list = data.infos.filter(e => e.chatType === type && parseInt(e.basicChatCacheInfo.chatSize) > 0);
const result = list.map(e => {
const result = { ...e.basicChatCacheInfo };
result.chatType = type;
result.isChecked = true;
return result;
});
res(result);
})
.catch(e => rej(e));
});
}

@ -31,6 +31,7 @@ import SetGroupCard from "./SetGroupCard";
import GetImage from "./GetImage"; import GetImage from "./GetImage";
import GetRecord from "./GetRecord"; import GetRecord from "./GetRecord";
import GoCQHTTPMarkMsgAsRead from "./MarkMsgAsRead"; import GoCQHTTPMarkMsgAsRead from "./MarkMsgAsRead";
import CleanCache from "./CleanCache";
export const actionHandlers = [ export const actionHandlers = [
new Debug(), new Debug(),
@ -56,6 +57,7 @@ export const actionHandlers = [
new SetGroupCard(), new SetGroupCard(),
new GetImage(), new GetImage(),
new GetRecord(), new GetRecord(),
new CleanCache(),
//以下为go-cqhttp api //以下为go-cqhttp api
new GoCQHTTPSendGroupForwardMsg(), new GoCQHTTPSendGroupForwardMsg(),

@ -42,6 +42,7 @@ export enum ActionName {
SetGroupName = "set_group_name", SetGroupName = "set_group_name",
GetImage = "get_image", GetImage = "get_image",
GetRecord = "get_record", GetRecord = "get_record",
CleanCache = "clean_cache",
// 以下为go-cqhttp api // 以下为go-cqhttp api
GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg", GoCQHTTP_SendGroupForwardMsg = "send_group_forward_msg",
GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg", GoCQHTTP_SendPrivateForwardMsg = "send_private_forward_msg",