diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f6b1d23 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +* text eol=lf + +*.png -text +*.jpg -text +*.ico -text +*.gif -text +*.webp -text diff --git a/electron.vite.config.ts b/electron.vite.config.ts index a16bf15..e6b320a 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -31,7 +31,6 @@ const config: ElectronViteConfig = { resolve: { alias: { '@': path.resolve(__dirname, './src'), - './lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg', }, }, plugins: [ diff --git a/manifest.json b/manifest.json index 529f1e3..b08932a 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "LLOneBot", "slug": "LLOneBot", "description": "实现 OneBot 11 协议,用于 QQ 机器人开发", - "version": "3.32.8", + "version": "3.33.0", "icon": "./icon.webp", "authors": [ { diff --git a/package.json b/package.json index e0fef99..6795284 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,16 @@ "author": "", "license": "MIT", "dependencies": { - "@minatojs/driver-sqlite": "^4.5.0", + "@minatojs/driver-sqlite": "^4.6.0", "compressing": "^1.10.1", - "cordis": "^3.18.0", + "cordis": "^3.18.1", "cors": "^2.8.5", "cosmokit": "^1.6.2", "express": "^5.0.0", "fast-xml-parser": "^4.5.0", "file-type": "^19.5.0", "fluent-ffmpeg": "^2.1.3", - "minato": "^3.5.1", + "minato": "^3.6.0", "protobufjs": "^7.4.0", "silk-wasm": "^3.6.1", "ws": "^8.18.0" @@ -41,7 +41,7 @@ "electron-vite": "^2.3.0", "protobufjs-cli": "^1.1.3", "typescript": "^5.6.2", - "vite": "^5.4.5", + "vite": "^5.4.6", "vite-plugin-cp": "^4.0.8" }, "packageManager": "yarn@4.4.1" diff --git a/src/ntqqapi/api/file.ts b/src/ntqqapi/api/file.ts index 895d81e..22eae3f 100644 --- a/src/ntqqapi/api/file.ts +++ b/src/ntqqapi/api/file.ts @@ -13,15 +13,14 @@ import { PicElement, } from '../types' import path from 'node:path' -import fs from 'node:fs' +import { existsSync } from 'node:fs' import { ReceiveCmdS } from '../hook' import { RkeyManager } from '@/ntqqapi/helper/rkey' import { getSession } from '@/ntqqapi/wrapper' -import { Peer } from '@/ntqqapi/types/msg' +import { OnRichMediaDownloadCompleteParams, Peer } from '@/ntqqapi/types/msg' import { calculateFileMD5 } from '@/common/utils/file' import { fileTypeFromFile } from 'file-type' -import fsPromise from 'node:fs/promises' -import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners' +import { copyFile, stat, unlink } from 'node:fs/promises' import { Time } from 'cosmokit' import { Service, Context } from 'cordis' import { TEMP_DIR } from '@/common/globalVars' @@ -111,8 +110,8 @@ export class NTQQFileApi extends Service { }, }]) } - await fsPromise.copyFile(filePath, mediaPath) - const fileSize = (await fsPromise.stat(filePath)).size + await copyFile(filePath, mediaPath) + const fileSize = (await stat(filePath)).size return { md5: fileMd5, fileName, @@ -133,10 +132,10 @@ export class NTQQFileApi extends Service { force = false ) { // 用于下载收到的消息中的图片等 - if (sourcePath && fs.existsSync(sourcePath)) { + if (sourcePath && existsSync(sourcePath)) { if (force) { try { - await fsPromise.unlink(sourcePath) + await unlink(sourcePath) } catch { } } else { return sourcePath diff --git a/src/ntqqapi/api/friend.ts b/src/ntqqapi/api/friend.ts index 27595f8..b05edce 100644 --- a/src/ntqqapi/api/friend.ts +++ b/src/ntqqapi/api/friend.ts @@ -42,13 +42,7 @@ export class NTQQFriendApi extends Service { return _friends } - async handleFriendRequest(flag: string, accept: boolean) { - const data = flag.split('|') - if (data.length < 2) { - return - } - const friendUid = data[0] - const reqTime = data[1] + async handleFriendRequest(friendUid: string, reqTime: string, accept: boolean) { const session = getSession() if (session) { return session.getBuddyService().approvalFriendRequest({ @@ -194,4 +188,10 @@ export class NTQQFriendApi extends Service { const ret = await invoke('nodeIKernelBuddyService/getBuddyRecommendContactArkJson', [{ uin }, null]) return ret.arkMsg } + + async setBuddyRemark(uid: string, remark: string) { + return await invoke('nodeIKernelBuddyService/setBuddyRemark', [{ + remarkParams: { uid, remark } + }, null]) + } } diff --git a/src/ntqqapi/api/group.ts b/src/ntqqapi/api/group.ts index 1158fe8..0458169 100644 --- a/src/ntqqapi/api/group.ts +++ b/src/ntqqapi/api/group.ts @@ -1,10 +1,18 @@ import { ReceiveCmdS } from '../hook' -import { Group, GroupMember, GroupMemberRole, GroupNotifies, GroupRequestOperateTypes, GetFileListParam, PublishGroupBulletinReq } from '../types' +import { + Group, + GroupMember, + GroupMemberRole, + GroupNotifies, + GroupRequestOperateTypes, + GetFileListParam, + OnGroupFileInfoUpdateParams, + PublishGroupBulletinReq +} from '../types' import { invoke, NTClass, NTMethod } from '../ntcall' import { GeneralCallResult } from '../services' import { NTQQWindows } from './window' import { getSession } from '../wrapper' -import { OnGroupFileInfoUpdateParams } from '../listeners' import { NodeIKernelGroupService } from '../services' import { Service, Context } from 'cordis' import { isNumeric } from '@/common/utils/misc' diff --git a/src/ntqqapi/api/msg.ts b/src/ntqqapi/api/msg.ts index c478826..3ca5551 100644 --- a/src/ntqqapi/api/msg.ts +++ b/src/ntqqapi/api/msg.ts @@ -256,4 +256,23 @@ export class NTQQMsgApi extends Service { async setMsgRead(peer: Peer) { return await invoke('nodeIKernelMsgService/setMsgRead', [{ peer }, null]) } + + async getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, count: number) { + return await invoke('nodeIKernelMsgService/getMsgEmojiLikesList', [{ + peer, + msgSeq, + emojiId, + emojiType, + cnt: count + }, null]) + } + + async fetchFavEmojiList(count: number) { + return await invoke('nodeIKernelMsgService/fetchFavEmojiList', [{ + resId: '', + count, + backwardFetch: true, + forceRefresh: true + }, null]) + } } diff --git a/src/ntqqapi/listeners/NodeIKernelGroupListener.ts b/src/ntqqapi/listeners/NodeIKernelGroupListener.ts deleted file mode 100644 index 0eb222a..0000000 --- a/src/ntqqapi/listeners/NodeIKernelGroupListener.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Group, GroupListUpdateType, GroupMember, GroupNotify } from '@/ntqqapi/types' - -export interface IGroupListener { - onGroupListUpdate(updateType: GroupListUpdateType, groupList: Group[]): void - - onGroupExtListUpdate(...args: unknown[]): void - - onGroupSingleScreenNotifies(doubt: boolean, seq: string, notifies: GroupNotify[]): void - - onGroupNotifiesUpdated(dboubt: boolean, notifies: GroupNotify[]): void - - onGroupNotifiesUnreadCountUpdated(...args: unknown[]): void - - onGroupDetailInfoChange(...args: unknown[]): void - - onGroupAllInfoChange(...args: unknown[]): void - - onGroupsMsgMaskResult(...args: unknown[]): void - - onGroupConfMemberChange(...args: unknown[]): void - - onGroupBulletinChange(...args: unknown[]): void - - onGetGroupBulletinListResult(...args: unknown[]): void - - onMemberListChange(arg: { - sceneId: string, - ids: string[], - infos: Map, - finish: boolean, - hasRobot: boolean - }): void - - onMemberInfoChange(groupCode: string, changeType: number, members: Map): void - - onSearchMemberChange(...args: unknown[]): void - - onGroupBulletinRichMediaDownloadComplete(...args: unknown[]): void - - onGroupBulletinRichMediaProgressUpdate(...args: unknown[]): void - - onGroupStatisticInfoChange(...args: unknown[]): void - - onJoinGroupNotify(...args: unknown[]): void - - onShutUpMemberListChanged(...args: unknown[]): void - - onGroupBulletinRemindNotify(...args: unknown[]): void - - onGroupFirstBulletinNotify(...args: unknown[]): void - - onJoinGroupNoVerifyFlag(...args: unknown[]): void - - onGroupArkInviteStateResult(...args: unknown[]): void - - // 发现于Win 9.9.9 23159 - onGroupMemberLevelInfoChange(...args: unknown[]): void -} \ No newline at end of file diff --git a/src/ntqqapi/listeners/NodeIKernelMsgListener.ts b/src/ntqqapi/listeners/NodeIKernelMsgListener.ts deleted file mode 100644 index fd0c8de..0000000 --- a/src/ntqqapi/listeners/NodeIKernelMsgListener.ts +++ /dev/null @@ -1,268 +0,0 @@ -import { ChatType, RawMessage } from '@/ntqqapi/types' - -export interface OnRichMediaDownloadCompleteParams { - fileModelId: string, - msgElementId: string, - msgId: string, - fileId: string, - fileProgress: string, // '0' - fileSpeed: string, // '0' - fileErrCode: string, // '0' - fileErrMsg: string, - fileDownType: number, // 暂时未知 - thumbSize: number, - filePath: string, - totalSize: string, - trasferStatus: number, - step: number, - commonFileInfo: unknown | null, - fileSrvErrCode: string, - clientMsg: string, - businessId: number, - userTotalSpacePerDay: unknown | null, - userUsedSpacePerDay: unknown | null -} - -export interface OnGroupFileInfoUpdateParams { - retCode: number - retMsg: string - clientWording: string - isEnd: boolean - item: { - peerId: string - type: number - folderInfo?: { - folderId: string - parentFolderId: string - folderName: string - createTime: number - modifyTime: number - createUin: string - creatorName: string - totalFileCount: number - modifyUin: string - modifyName: string - usedSpace: string - } - fileInfo?: { - fileModelId: string - fileId: string - fileName: string - fileSize: string - busId: number - uploadedSize: string - uploadTime: number - deadTime: number - modifyTime: number - downloadTimes: number - sha: string - sha3: string - md5: string - uploaderLocalPath: string - uploaderName: string - uploaderUin: string - parentFolderId: string - localPath: string - transStatus: number - transType: number - elementId: string - isFolder: boolean - } - }[] - allFileCount: number - nextIndex: number - reqId: number -} - -// { -// sessionType: 1, -// chatType: 100, -// peerUid: 'u_PVQ3tl6K78xxxx', -// groupCode: '809079648', -// fromNick: '拾xxxx, -// sig: '0x' -// } -export interface TempOnRecvParams { - sessionType: number,//1 - chatType: ChatType,//100 - peerUid: string,//uid - groupCode: string,//gc - fromNick: string,//gc name - sig: string, -} - -export interface IKernelMsgListener { - onAddSendMsg(msgRecord: RawMessage): void - - onBroadcastHelperDownloadComplete(broadcastHelperTransNotifyInfo: unknown): void - - onBroadcastHelperProgressUpdate(broadcastHelperTransNotifyInfo: unknown): void - - onChannelFreqLimitInfoUpdate(contact: unknown, z: unknown, freqLimitInfo: unknown): void - - onContactUnreadCntUpdate(hashMap: unknown): void - - onCustomWithdrawConfigUpdate(customWithdrawConfig: unknown): void - - onDraftUpdate(contact: unknown, arrayList: unknown, j2: unknown): void - - onEmojiDownloadComplete(emojiNotifyInfo: unknown): void - - onEmojiResourceUpdate(emojiResourceInfo: unknown): void - - onFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void - - onFileMsgCome(arrayList: unknown): void - - onFirstViewDirectMsgUpdate(firstViewDirectMsgNotifyInfo: unknown): void - - onFirstViewGroupGuildMapping(arrayList: unknown): void - - onGrabPasswordRedBag(i2: unknown, str: unknown, i3: unknown, recvdOrder: unknown, msgRecord: unknown): void - - onGroupFileInfoAdd(groupItem: unknown): void - - onGroupFileInfoUpdate(groupFileListResult: OnGroupFileInfoUpdateParams): void - - onGroupGuildUpdate(groupGuildNotifyInfo: unknown): void - - onGroupTransferInfoAdd(groupItem: unknown): void - - onGroupTransferInfoUpdate(groupFileListResult: unknown): void - - onGuildInteractiveUpdate(guildInteractiveNotificationItem: unknown): void - - onGuildMsgAbFlagChanged(guildMsgAbFlag: unknown): void - - onGuildNotificationAbstractUpdate(guildNotificationAbstractInfo: unknown): void - - onHitCsRelatedEmojiResult(downloadRelateEmojiResultInfo: unknown): void - - onHitEmojiKeywordResult(hitRelatedEmojiWordsResult: unknown): void - - onHitRelatedEmojiResult(relatedWordEmojiInfo: unknown): void - - onImportOldDbProgressUpdate(importOldDbMsgNotifyInfo: unknown): void - - onInputStatusPush(inputStatusInfo: unknown): void - - onKickedOffLine(kickedInfo: unknown): void - - onLineDev(arrayList: unknown): void - - onLogLevelChanged(j2: unknown): void - - onMsgAbstractUpdate(arrayList: unknown): void - - onMsgBoxChanged(arrayList: unknown): void - - onMsgDelete(contact: unknown, arrayList: unknown): void - - onMsgEventListUpdate(hashMap: unknown): void - - onMsgInfoListAdd(arrayList: unknown): void - - onMsgInfoListUpdate(msgList: RawMessage[]): void - - onMsgQRCodeStatusChanged(i2: unknown): void - - onMsgRecall(i2: unknown, str: unknown, j2: unknown): void - - onMsgSecurityNotify(msgRecord: unknown): void - - onMsgSettingUpdate(msgSetting: unknown): void - - onNtFirstViewMsgSyncEnd(): void - - onNtMsgSyncEnd(): void - - onNtMsgSyncStart(): void - - onReadFeedEventUpdate(firstViewDirectMsgNotifyInfo: unknown): void - - onRecvGroupGuildFlag(i2: unknown): void - - onRecvMsg(...arrayList: unknown[]): void - - onRecvMsgSvrRspTransInfo(j2: unknown, contact: unknown, i2: unknown, i3: unknown, str: unknown, bArr: unknown): void - - onRecvOnlineFileMsg(arrayList: unknown): void - - onRecvS2CMsg(arrayList: unknown): void - - onRecvSysMsg(arrayList: unknown): void - - onRecvUDCFlag(i2: unknown): void - - onRichMediaDownloadComplete(fileTransNotifyInfo: OnRichMediaDownloadCompleteParams): void - - onRichMediaProgerssUpdate(fileTransNotifyInfo: unknown): void - - onRichMediaUploadComplete(fileTransNotifyInfo: unknown): void - - onSearchGroupFileInfoUpdate(searchGroupFileResult: - { - result: { - retCode: number, - retMsg: string, - clientWording: string - }, - syncCookie: string, - totalMatchCount: number, - ownerMatchCount: number, - isEnd: boolean, - reqId: number, - item: Array<{ - groupCode: string, - groupName: string, - uploaderUin: string, - uploaderName: string, - matchUin: string, - matchWords: Array, - fileNameHits: Array<{ - start: number, - end: number - }>, - fileModelId: string, - fileId: string, - fileName: string, - fileSize: string, - busId: number, - uploadTime: number, - modifyTime: number, - deadTime: number, - downloadTimes: number, - localPath: string - }> - }): void - - onSendMsgError(j2: unknown, contact: unknown, i2: unknown, str: unknown): void - - onSysMsgNotification(i2: unknown, j2: unknown, j3: unknown, arrayList: unknown): void - - onTempChatInfoUpdate(tempChatInfo: TempOnRecvParams): void - - onUnreadCntAfterFirstView(hashMap: unknown): void - - onUnreadCntUpdate(hashMap: unknown): void - - onUserChannelTabStatusChanged(z: unknown): void - - onUserOnlineStatusChanged(z: unknown): void - - onUserTabStatusChanged(arrayList: unknown): void - - onlineStatusBigIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void - - onlineStatusSmallIconDownloadPush(i2: unknown, j2: unknown, str: unknown): void - - // 第一次发现于Linux - onUserSecQualityChanged(...args: unknown[]): void - - onMsgWithRichLinkInfoUpdate(...args: unknown[]): void - - onRedTouchChanged(...args: unknown[]): void - - // 第一次发现于Win 9.9.9 23159 - onBroadcastHelperProgerssUpdate(...args: unknown[]): void -} \ No newline at end of file diff --git a/src/ntqqapi/listeners/NodeIKernelProfileListener.ts b/src/ntqqapi/listeners/NodeIKernelProfileListener.ts deleted file mode 100644 index 0690815..0000000 --- a/src/ntqqapi/listeners/NodeIKernelProfileListener.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { User, UserDetailInfoListenerArg } from '@/ntqqapi/types' - -export interface IProfileListener { - onProfileSimpleChanged(...args: unknown[]): void - - onUserDetailInfoChanged(arg: UserDetailInfoListenerArg): void - - onProfileDetailInfoChanged(profile: User): void - - onStatusUpdate(...args: unknown[]): void - - onSelfStatusChanged(...args: unknown[]): void - - onStrangerRemarkChanged(...args: unknown[]): void -} \ No newline at end of file diff --git a/src/ntqqapi/listeners/index.ts b/src/ntqqapi/listeners/index.ts deleted file mode 100644 index e0589aa..0000000 --- a/src/ntqqapi/listeners/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './NodeIKernelProfileListener' -export * from './NodeIKernelGroupListener' -export * from './NodeIKernelMsgListener' \ No newline at end of file diff --git a/src/ntqqapi/ntcall.ts b/src/ntqqapi/ntcall.ts index a0f606d..687f208 100644 --- a/src/ntqqapi/ntcall.ts +++ b/src/ntqqapi/ntcall.ts @@ -123,11 +123,15 @@ export function invoke< return new Promise((resolve, reject) => { const apiArgs = [method, ...args] const callbackId = randomUUID() - let success = false + const timeoutId = setTimeout(() => { + log(`ntqq api timeout ${channel}, ${eventName}, ${method}`, apiArgs) + reject(`ntqq api timeout ${channel}, ${eventName}, ${method}, ${apiArgs}`) + }, timeout) + if (!options.cbCmd) { // QQ后端会返回结果,并且可以根据uuid识别 hookApiCallbacks[callbackId] = res => { - success = true + clearTimeout(timeoutId) resolve(res) } } @@ -139,13 +143,13 @@ export function invoke< if (options.cmdCB) { if (options.cmdCB(payload, result)) { removeReceiveHook(hookId) - success = true + clearTimeout(timeoutId) resolve(payload) } } else { removeReceiveHook(hookId) - success = true + clearTimeout(timeoutId) resolve(payload) } }) @@ -158,16 +162,11 @@ export function invoke< } else { log('ntqq api call failed,', method, res) + clearTimeout(timeoutId) reject(`ntqq api call failed, ${method}, ${res.errMsg}`) } } } - setTimeout(() => { - if (!success) { - log(`ntqq api timeout ${channel}, ${eventName}, ${method}`, apiArgs) - reject(`ntqq api timeout ${channel}, ${eventName}, ${method}, ${apiArgs}`) - } - }, timeout) ipcMain.emit( channel, diff --git a/src/ntqqapi/services/NodeIKernelGroupService.ts b/src/ntqqapi/services/NodeIKernelGroupService.ts index c3ec6a2..1bbeb91 100644 --- a/src/ntqqapi/services/NodeIKernelGroupService.ts +++ b/src/ntqqapi/services/NodeIKernelGroupService.ts @@ -104,11 +104,20 @@ export interface NodeIKernelGroupService { createMemberListScene(groupCode: string, scene: string): string - destroyMemberListScene(SceneId: string): void - //About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string} + destroyMemberListScene(sceneId: string): void + getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ - errCode: number, errMsg: string, - result: { ids: string[], infos: Map, finish: boolean, hasRobot: boolean } + errCode: number + errMsg: string + result: { + ids: { + uid: string + index: number + }[] + infos: Map + finish: boolean + hasRobot: boolean + } }> getPrevMemberList(): unknown diff --git a/src/ntqqapi/services/NodeIKernelMsgService.ts b/src/ntqqapi/services/NodeIKernelMsgService.ts index b2011dd..52c92e8 100644 --- a/src/ntqqapi/services/NodeIKernelMsgService.ts +++ b/src/ntqqapi/services/NodeIKernelMsgService.ts @@ -214,7 +214,7 @@ export interface NodeIKernelMsgService { getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown getSourceOfReplyMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown - //cnt clientSeq?并不是吧 + getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array }): unknown getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array }>): unknown @@ -223,31 +223,8 @@ export interface NodeIKernelMsgService { queryMsgsWithFilter(...args: unknown[]): unknown - /** - * @deprecated 该函数已被标记为废弃,请使用新的替代方法。 - * 使用过滤条件查询消息列表的版本2接口。 - * - * 该函数通过一系列过滤条件来查询特定聊天中的消息列表。这些条件包括消息类型、发送者、时间范围等。 - * 函数返回一个Promise,解析为查询结果的未知类型对象。 - * - * @param MsgId 消息ID,用于特定消息的查询。 - * @param MsgTime 消息时间,用于指定消息的时间范围。 - * @param param 查询参数对象,包含详细的过滤条件和分页信息。 - * @param param.chatInfo 聊天信息,包括聊天类型和对方用户ID。 - * @param param.filterMsgType 需要过滤的消息类型数组,留空表示不过滤。 - * @param param.filterSendersUid 需要过滤的发送者用户ID数组。 - * @param param.filterMsgFromTime 查询消息的起始时间。 - * @param param.filterMsgToTime 查询消息的结束时间。 - * @param param.pageLimit 每页的消息数量限制。 - * @param param.isReverseOrder 是否按时间顺序倒序返回消息。 - * @param param.isIncludeCurrent 是否包含当前页码。 - * @returns 返回一个Promise,解析为查询结果的未知类型对象。 - */ - queryMsgsWithFilterVer2(MsgId: string, MsgTime: string, param: QueryMsgsParams): Promise - // this.chatType = i2 // this.peerUid = str - // this.chatInfo = new ChatInfo() // this.filterMsgType = new ArrayList<>() // this.filterSendersUid = new ArrayList<>() @@ -495,16 +472,15 @@ export interface NodeIKernelMsgService { setMsgEmojiLikes(...args: unknown[]): unknown getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{ - result: number, - errMsg: string, - emojiLikesList: - Array<{ - tinyId: string, - nickName: string, + result: number + errMsg: string + emojiLikesList: { + tinyId: string + nickName: string headUrl: string - }>, - cookie: string, - isLastPage: boolean, + }[] + cookie: string + isLastPage: boolean isFirstPage: boolean }> @@ -686,9 +662,8 @@ export interface NodeIKernelMsgService { dataMigrationStopOperation(...args: unknown[]): unknown - //新的希望 dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{ - extensionData: string//"Hex" + extensionData: string //"Hex" extraData: string //"" chatType: number chatUin: string @@ -740,4 +715,4 @@ export interface NodeIKernelMsgService { getGroupMsgStorageTime(): unknown//这是嘛啊 -} \ No newline at end of file +} diff --git a/src/ntqqapi/types/group.ts b/src/ntqqapi/types/group.ts index 9f93304..876636b 100644 --- a/src/ntqqapi/types/group.ts +++ b/src/ntqqapi/types/group.ts @@ -62,8 +62,9 @@ export interface GroupMember { sex?: Sex qqLevel?: QQLevel isChangeRole: boolean - joinTime: string - lastSpeakTime: string + joinTime: number + lastSpeakTime: number + memberLevel: number } export interface PublishGroupBulletinReq { @@ -76,4 +77,4 @@ export interface PublishGroupBulletinReq { oldFeedsId: '' pinned: number confirmRequired: number -} \ No newline at end of file +} diff --git a/src/ntqqapi/types/msg.ts b/src/ntqqapi/types/msg.ts index 666a755..9812fe5 100644 --- a/src/ntqqapi/types/msg.ts +++ b/src/ntqqapi/types/msg.ts @@ -6,6 +6,7 @@ export interface GetFileListParam { startIndex: number sortOrder: number showOnlinedocFolder: number + folderId?: string } export enum ElementType { @@ -533,3 +534,77 @@ export interface MessageElement { recommendedMsgElement?: unknown actionBarElement?: unknown } + +export interface OnRichMediaDownloadCompleteParams { + fileModelId: string + msgElementId: string + msgId: string + fileId: string + fileProgress: string // '0' + fileSpeed: string // '0' + fileErrCode: string // '0' + fileErrMsg: string + fileDownType: number // 暂时未知 + thumbSize: number + filePath: string + totalSize: string + trasferStatus: number + step: number + commonFileInfo: unknown + fileSrvErrCode: string + clientMsg: string + businessId: number + userTotalSpacePerDay: unknown + userUsedSpacePerDay: unknown +} + +export interface OnGroupFileInfoUpdateParams { + retCode: number + retMsg: string + clientWording: string + isEnd: boolean + item: { + peerId: string + type: number + folderInfo?: { + folderId: string + parentFolderId: string + folderName: string + createTime: number + modifyTime: number + createUin: string + creatorName: string + totalFileCount: number + modifyUin: string + modifyName: string + usedSpace: string + } + fileInfo?: { + fileModelId: string + fileId: string + fileName: string + fileSize: string + busId: number + uploadedSize: string + uploadTime: number + deadTime: number + modifyTime: number + downloadTimes: number + sha: string + sha3: string + md5: string + uploaderLocalPath: string + uploaderName: string + uploaderUin: string + parentFolderId: string + localPath: string + transStatus: number + transType: number + elementId: string + isFolder: boolean + } + }[] + allFileCount: number + nextIndex: number + reqId: number +} diff --git a/src/ntqqapi/wrapper.ts b/src/ntqqapi/wrapper.ts index fc00750..80fc730 100644 --- a/src/ntqqapi/wrapper.ts +++ b/src/ntqqapi/wrapper.ts @@ -33,30 +33,8 @@ export interface WrapperApi { NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession } -export interface WrapperConstructor { - [key: string]: unknown -} - const wrapperApi: WrapperApi = {} -export const wrapperConstructor: WrapperConstructor = {} - -const constructor = [ - 'NodeIKernelBuddyListener', - 'NodeIKernelGroupListener', - 'NodeQQNTWrapperUtil', - 'NodeIKernelMsgListener', - 'NodeIQQNTWrapperEngine', - 'NodeIGlobalAdapter', - 'NodeIDependsAdapter', - 'NodeIDispatcherAdapter', - 'NodeIKernelSessionListener', - 'NodeIKernelLoginService', - 'NodeIKernelLoginListener', - 'NodeIKernelProfileService', - 'NodeIKernelProfileListener', -] - Process.dlopenOrig = Process.dlopen Process.dlopen = function (module: Dict, filename: string, flags = constants.dlopen.RTLD_LAZY) { @@ -69,9 +47,6 @@ Process.dlopen = function (module: Dict, filename: string, flags = constants.dlo return ret } }) - if (constructor.includes(export_name)) { - wrapperConstructor[export_name] = module.exports[export_name] - } } return dlopenRet } diff --git a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts index 4e16cff..53f3c47 100644 --- a/src/onebot11/action/go-cqhttp/GetForwardMsg.ts +++ b/src/onebot11/action/go-cqhttp/GetForwardMsg.ts @@ -1,4 +1,4 @@ -import { BaseAction } from '../BaseAction' +import { BaseAction, Schema } from '../BaseAction' import { OB11ForwardMessage } from '../../types' import { OB11Entities } from '../../entities' import { ActionName } from '../types' @@ -16,6 +16,11 @@ interface Response { export class GetForwardMsg extends BaseAction { actionName = ActionName.GoCQHTTP_GetForwardMsg + payloadSchema = Schema.object({ + message_id: String, + id: String + }) + protected async _handle(payload: Payload) { const msgId = payload.id || payload.message_id if (!msgId) { diff --git a/src/onebot11/action/go-cqhttp/GetGroupAtAllRemain.ts b/src/onebot11/action/go-cqhttp/GetGroupAtAllRemain.ts index d39032a..31bc17b 100644 --- a/src/onebot11/action/go-cqhttp/GetGroupAtAllRemain.ts +++ b/src/onebot11/action/go-cqhttp/GetGroupAtAllRemain.ts @@ -1,4 +1,4 @@ -import { BaseAction } from '../BaseAction' +import { BaseAction, Schema } from '../BaseAction' import { ActionName } from '../types' interface Payload { @@ -13,6 +13,9 @@ interface Response { export class GetGroupAtAllRemain extends BaseAction { actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain + payloadSchema = Schema.object({ + group_id: Schema.union([Number, String]).required() + }) async _handle(payload: Payload) { const data = await this.ctx.ntGroupApi.getGroupRemainAtTimes(payload.group_id.toString()) diff --git a/src/onebot11/action/go-cqhttp/GetGroupFilesByFolder.ts b/src/onebot11/action/go-cqhttp/GetGroupFilesByFolder.ts new file mode 100644 index 0000000..c3ee992 --- /dev/null +++ b/src/onebot11/action/go-cqhttp/GetGroupFilesByFolder.ts @@ -0,0 +1,54 @@ +import { BaseAction, Schema } from '../BaseAction' +import { ActionName } from '../types' +import { OB11GroupFile, OB11GroupFileFolder } from '@/onebot11/types' + +interface Payload { + group_id: string | number + folder_id: string + file_count: string | number +} + +interface Response { + files: OB11GroupFile[] + folders: OB11GroupFileFolder[] +} + +export class GetGroupFilesByFolder extends BaseAction { + actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder + payloadSchema = Schema.object({ + group_id: Schema.union([Number, String]).required(), + folder_id: Schema.string().required(), + file_count: Schema.union([Number, String]).default(50) + }) + + async _handle(payload: Payload) { + const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), { + sortType: 1, + fileCount: +payload.file_count, + startIndex: 0, + sortOrder: 2, + showOnlinedocFolder: 0, + folderId: payload.folder_id + }) + return { + files: data.filter(item => item.fileInfo) + .map(item => { + const file = item.fileInfo! + return { + group_id: +item.peerId, + file_id: file.fileId, + file_name: file.fileName, + busid: file.busId, + file_size: +file.fileSize, + upload_time: file.uploadTime, + dead_time: file.deadTime, + modify_time: file.modifyTime, + download_times: file.downloadTimes, + uploader: +file.uploaderUin, + uploader_name: file.uploaderName + } + }), + folders: [] + } + } +} diff --git a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts index 5227976..1dd0b0c 100644 --- a/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts +++ b/src/onebot11/action/go-cqhttp/GetGroupMsgHistory.ts @@ -1,4 +1,4 @@ -import { BaseAction } from '../BaseAction' +import { BaseAction, Schema } from '../BaseAction' import { OB11Message } from '../../types' import { ActionName } from '../types' import { ChatType } from '@/ntqqapi/types' @@ -10,8 +10,8 @@ import { filterNullable } from '@/common/utils/misc' interface Payload { group_id: number | string message_seq?: number | string - count?: number | string - reverseOrder?: boolean + count: number | string + reverseOrder: boolean } interface Response { @@ -20,10 +20,15 @@ interface Response { export class GetGroupMsgHistory extends BaseAction { actionName = ActionName.GoCQHTTP_GetGroupMsgHistory + payloadSchema = Schema.object({ + group_id: Schema.union([Number, String]).required(), + message_seq: Schema.union([Number, String]), + count: Schema.union([Number, String]).default(20), + reverseOrder: Schema.boolean().default(false), + }) protected async _handle(payload: Payload): Promise { - const count = payload.count || 20 - const isReverseOrder = payload.reverseOrder || true + const { count, reverseOrder } = payload const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() } let msgList: RawMessage[] | undefined // 包含 message_seq 0 @@ -35,7 +40,7 @@ export class GetGroupMsgHistory extends BaseAction { msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, +count)).msgList } if (!msgList?.length) throw new Error('未找到消息') - if (isReverseOrder) msgList.reverse() + if (reverseOrder) msgList.reverse() await Promise.all( msgList.map(async msg => { msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId) diff --git a/src/onebot11/action/go-cqhttp/GetGroupRootFiles.ts b/src/onebot11/action/go-cqhttp/GetGroupRootFiles.ts index 65952f9..ab3e5bf 100644 --- a/src/onebot11/action/go-cqhttp/GetGroupRootFiles.ts +++ b/src/onebot11/action/go-cqhttp/GetGroupRootFiles.ts @@ -1,4 +1,4 @@ -import { BaseAction } from '../BaseAction' +import { BaseAction, Schema } from '../BaseAction' import { ActionName } from '../types' import { OB11GroupFile, OB11GroupFileFolder } from '../../types' @@ -14,18 +14,19 @@ interface Response { export class GetGroupRootFiles extends BaseAction { actionName = ActionName.GoCQHTTP_GetGroupRootFiles + payloadSchema = Schema.object({ + group_id: Schema.union([Number, String]).required(), + file_count: Schema.union([Number, String]).default(50), + }) async _handle(payload: Payload) { const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), { sortType: 1, - fileCount: +(payload.file_count ?? 50), + fileCount: +payload.file_count, startIndex: 0, sortOrder: 2, showOnlinedocFolder: 0, }) - - this.ctx.logger.info(data) - return { files: data.filter(item => item.fileInfo) .map(item => { diff --git a/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts b/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts index 4980bb7..d485b8c 100644 --- a/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts +++ b/src/onebot11/action/go-cqhttp/GetStrangerInfo.ts @@ -1,4 +1,4 @@ -import { BaseAction } from '../BaseAction' +import { BaseAction, Schema } from '../BaseAction' import { OB11User } from '../../types' import { OB11Entities } from '../../entities' import { ActionName } from '../types' @@ -12,6 +12,9 @@ interface Payload { export class GetStrangerInfo extends BaseAction { actionName = ActionName.GoCQHTTP_GetStrangerInfo + payloadSchema = Schema.object({ + user_id: Schema.union([Number, String]).required() + }) protected async _handle(payload: Payload): Promise { if (!(getBuildVersion() >= 26702)) { diff --git a/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts b/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts index faaeb64..8c73cae 100644 --- a/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts +++ b/src/onebot11/action/go-cqhttp/MarkMsgAsRead.ts @@ -1,4 +1,4 @@ -import { BaseAction } from '../BaseAction' +import { BaseAction, Schema } from '../BaseAction' import { ActionName } from '../types' import { MessageUnique } from '@/common/utils/messageUnique' @@ -8,11 +8,11 @@ interface Payload { export class MarkMsgAsRead extends BaseAction { actionName = ActionName.GoCQHTTP_MarkMsgAsRead + payloadSchema = Schema.object({ + message_id: Schema.union([Number, String]).required() + }) protected async _handle(payload: Payload) { - if (!payload.message_id) { - throw new Error('参数 message_id 不能为空') - } const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) if (!msg) { throw new Error('msg not found') diff --git a/src/onebot11/action/go-cqhttp/QuickOperation.ts b/src/onebot11/action/go-cqhttp/QuickOperation.ts index fc2c080..00ffb49 100644 --- a/src/onebot11/action/go-cqhttp/QuickOperation.ts +++ b/src/onebot11/action/go-cqhttp/QuickOperation.ts @@ -9,6 +9,7 @@ interface Payload { export class HandleQuickOperation extends BaseAction { actionName = ActionName.GoCQHTTP_HandleQuickOperation + protected async _handle(payload: Payload): Promise { handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e)) return null diff --git a/src/onebot11/action/go-cqhttp/SendForwardMsg.ts b/src/onebot11/action/go-cqhttp/SendForwardMsg.ts index d9476f4..c76cd63 100644 --- a/src/onebot11/action/go-cqhttp/SendForwardMsg.ts +++ b/src/onebot11/action/go-cqhttp/SendForwardMsg.ts @@ -1,20 +1,180 @@ -import SendMsg from '../msg/SendMsg' -import { OB11PostSendMsg } from '../../types' +import { unlink } from 'node:fs/promises' +import { OB11MessageNode } from '../../types' import { ActionName } from '../types' +import { BaseAction, Schema } from '../BaseAction' +import { Peer } from '@/ntqqapi/types/msg' +import { ChatType, ElementType, RawMessage, SendMessageElement } from '@/ntqqapi/types' +import { MessageUnique } from '@/common/utils/messageUnique' +import { selfInfo } from '@/common/globalVars' +import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage' -export class SendForwardMsg extends SendMsg { +interface Payload { + user_id?: string | number + group_id?: string | number + messages: OB11MessageNode[] + message_type?: 'group' | 'private' +} + +interface Response { + message_id: number + forward_id?: string +} + +export class SendForwardMsg extends BaseAction { actionName = ActionName.GoCQHTTP_SendForwardMsg + payloadSchema = Schema.object({ + user_id: Schema.union([Number, String]), + group_id: Schema.union([Number, String]), + messages: Schema.array(Schema.any()).required(), + message_type: Schema.union(['group', 'private']) + }) - protected async _handle(payload: OB11PostSendMsg) { - if (payload.messages) payload.message = payload.messages - return super._handle(payload) + protected async _handle(payload: Payload) { + let contextMode = CreatePeerMode.Normal + if (payload.message_type === 'group') { + contextMode = CreatePeerMode.Group + } else if (payload.message_type === 'private') { + contextMode = CreatePeerMode.Private + } + const peer = await createPeer(this.ctx, payload, contextMode) + const returnMsg = await this.handleForwardNode(peer, payload.messages) + return { message_id: returnMsg.msgShortId! } + } + + private async cloneMsg(msg: RawMessage): Promise { + this.ctx.logger.info('克隆的目标消息', msg) + const sendElements: SendMessageElement[] = [] + for (const ele of msg.elements) { + sendElements.push(ele as SendMessageElement) + } + if (sendElements.length === 0) { + this.ctx.logger.warn('需要clone的消息无法解析,将会忽略掉', msg) + } + this.ctx.logger.info('克隆消息', sendElements) + try { + const peer = { + chatType: ChatType.friend, + peerUid: selfInfo.uid + } + const nodeMsg = await this.ctx.ntMsgApi.sendMsg(peer, sendElements) + await this.ctx.sleep(400) + return nodeMsg + } catch (e) { + this.ctx.logger.warn(e, '克隆转发消息失败,将忽略本条消息', msg) + } + } + + // 返回一个合并转发的消息id + private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) { + const selfPeer = { + chatType: ChatType.friend, + peerUid: selfInfo.uid, + } + let nodeMsgIds: string[] = [] + // 先判断一遍是不是id和自定义混用 + for (const messageNode of messageNodes) { + // 一个node表示一个人的消息 + const nodeId = messageNode.data.id + // 有nodeId表示一个子转发消息卡片 + if (nodeId) { + const nodeMsg = await MessageUnique.getMsgIdAndPeerByShortId(+nodeId) || await MessageUnique.getPeerByMsgId(nodeId) + if (!nodeMsg) { + this.ctx.logger.warn('转发消息失败,未找到消息', nodeId) + continue + } + nodeMsgIds.push(nodeMsg.MsgId) + } + else { + // 自定义的消息 + // 提取消息段,发给自己生成消息id + try { + const { sendElements, deleteAfterSentFiles } = await createSendElements( + this.ctx, + convertMessage2List(messageNode.data.content), + destPeer + ) + this.ctx.logger.info('开始生成转发节点', sendElements) + const sendElementsSplit: SendMessageElement[][] = [] + let splitIndex = 0 + for (const ele of sendElements) { + if (!sendElementsSplit[splitIndex]) { + sendElementsSplit[splitIndex] = [] + } + + if (ele.elementType === ElementType.FILE || ele.elementType === ElementType.VIDEO) { + if (sendElementsSplit[splitIndex].length > 0) { + splitIndex++ + } + sendElementsSplit[splitIndex] = [ele] + splitIndex++ + } + else { + sendElementsSplit[splitIndex].push(ele) + } + this.ctx.logger.info(sendElementsSplit) + } + // log("分割后的转发节点", sendElementsSplit) + for (const eles of sendElementsSplit) { + const nodeMsg = await sendMsg(this.ctx, selfPeer, eles, []) + if (!nodeMsg) { + this.ctx.logger.warn('转发节点生成失败', eles) + continue + } + nodeMsgIds.push(nodeMsg.msgId) + await this.ctx.sleep(400) + } + deleteAfterSentFiles.map(path => unlink(path)) + } catch (e) { + this.ctx.logger.error('生成转发消息节点失败', e) + } + } + } + + // 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发 + const nodeMsgArray: RawMessage[] = [] + let srcPeer: Peer | null = null + let needSendSelf = false + for (const msgId of nodeMsgIds) { + const nodeMsgPeer = await MessageUnique.getPeerByMsgId(msgId) + if (nodeMsgPeer) { + const nodeMsg = (await this.ctx.ntMsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0] + srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid } + if (srcPeer.peerUid !== nodeMsg.peerUid) { + needSendSelf = true + } + nodeMsgArray.push(nodeMsg) + } + } + nodeMsgIds = nodeMsgArray.map((msg) => msg.msgId) + if (needSendSelf) { + for (const msg of nodeMsgArray) { + if (msg.peerUid === selfPeer.peerUid) continue + await this.cloneMsg(msg) + } + } + if (nodeMsgIds.length === 0) { + throw Error('转发消息失败,节点为空') + } + const returnMsg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds) + returnMsg.msgShortId = MessageUnique.createMsg(destPeer, returnMsg.msgId) + return returnMsg } } export class SendPrivateForwardMsg extends SendForwardMsg { actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg + + protected _handle(payload: Payload) { + payload.message_type = 'private' + return super._handle(payload) + } } export class SendGroupForwardMsg extends SendForwardMsg { actionName = ActionName.GoCQHTTP_SendGroupForwardMsg + + protected _handle(payload: Payload) { + payload.message_type = 'group' + return super._handle(payload) + } } diff --git a/src/onebot11/action/go-cqhttp/SendGroupNotice.ts b/src/onebot11/action/go-cqhttp/SendGroupNotice.ts index 9bac373..c1575f0 100644 --- a/src/onebot11/action/go-cqhttp/SendGroupNotice.ts +++ b/src/onebot11/action/go-cqhttp/SendGroupNotice.ts @@ -1,4 +1,4 @@ -import { BaseAction } from '../BaseAction' +import { BaseAction, Schema } from '../BaseAction' import { ActionName } from '../types' import { unlink } from 'fs/promises' import { checkFileReceived, uri2local } from '@/common/utils/file' @@ -7,20 +7,24 @@ interface Payload { group_id: number | string content: string image?: string - pinned?: number | string //扩展 - confirm_required?: number | string //扩展 + pinned: number | string //扩展 + confirm_required: number | string //扩展 } export class SendGroupNotice extends BaseAction { actionName = ActionName.GoCQHTTP_SendGroupNotice + payloadSchema = Schema.object({ + group_id: Schema.union([Number, String]).required(), + content: Schema.string().required(), + image: Schema.string(), + pinned: Schema.union([Number, String]).default(0), + confirm_required: Schema.union([Number, String]).default(1) + }) async _handle(payload: Payload) { - if (!payload.content) { - throw new Error('参数 content 不能为空') - } const groupCode = payload.group_id.toString() - const pinned = Number(payload.pinned ?? 0) - const confirmRequired = Number(payload.confirm_required ?? 1) + const pinned = +payload.pinned + const confirmRequired = +payload.confirm_required let picInfo: { id: string, width: number, height: number } | undefined if (payload.image) { diff --git a/src/onebot11/action/group/GetGroupMemberList.ts b/src/onebot11/action/group/GetGroupMemberList.ts index 3bf2127..a6d5efc 100644 --- a/src/onebot11/action/group/GetGroupMemberList.ts +++ b/src/onebot11/action/group/GetGroupMemberList.ts @@ -13,11 +13,16 @@ class GetGroupMemberList extends BaseAction { actionName = ActionName.GetGroupMemberList protected async _handle(payload: Payload) { - const groupMembers = await this.ctx.ntGroupApi.getGroupMembers(payload.group_id.toString()) + const groupCode = payload.group_id.toString() + let groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode) + if (groupMembers.size === 0) { + await this.ctx.sleep(100) + groupMembers = await this.ctx.ntGroupApi.getGroupMembers(groupCode) + } const groupMembersArr = Array.from(groupMembers.values()) - let _groupMembers = groupMembersArr.map(item => { - return OB11Entities.groupMember(payload.group_id.toString(), item) + const _groupMembers = groupMembersArr.map(item => { + return OB11Entities.groupMember(groupCode, item) }) const MemberMap: Map = new Map() @@ -25,8 +30,8 @@ class GetGroupMemberList extends BaseAction { for (let i = 0, len = _groupMembers.length; i < len; i++) { // 保证基础数据有这个 同时避免群管插件过于依赖这个杀了 - _groupMembers[i].join_time = date - _groupMembers[i].last_sent_time = date + _groupMembers[i].join_time ||= date + _groupMembers[i].last_sent_time ||= date MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]) } @@ -34,24 +39,24 @@ class GetGroupMemberList extends BaseAction { const isPrivilege = selfRole === 3 || selfRole === 4 if (isPrivilege) { - const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(payload.group_id.toString()) + const webGroupMembers = await this.ctx.ntWebApi.getGroupMembers(groupCode) for (let i = 0, len = webGroupMembers.length; i < len; i++) { if (!webGroupMembers[i]?.uin) { continue } const MemberData = MemberMap.get(webGroupMembers[i]?.uin) if (MemberData) { - MemberData.join_time = webGroupMembers[i]?.join_time - MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time + if (MemberData.join_time === date) { + MemberData.join_time = webGroupMembers[i]?.join_time + MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time + } MemberData.qage = webGroupMembers[i]?.qage - MemberData.level = webGroupMembers[i]?.lv.level.toString() MemberMap.set(webGroupMembers[i]?.uin, MemberData) } } } - _groupMembers = Array.from(MemberMap.values()) - return _groupMembers + return Array.from(MemberMap.values()) } } diff --git a/src/onebot11/action/group/SendGroupMsg.ts b/src/onebot11/action/group/SendGroupMsg.ts index bd363bf..0004003 100644 --- a/src/onebot11/action/group/SendGroupMsg.ts +++ b/src/onebot11/action/group/SendGroupMsg.ts @@ -6,7 +6,6 @@ class SendGroupMsg extends SendMsg { actionName = ActionName.SendGroupMsg protected _handle(payload: OB11PostSendMsg) { - delete (payload as Partial).user_id payload.message_type = 'group' return super._handle(payload) } diff --git a/src/onebot11/action/index.ts b/src/onebot11/action/index.ts index c5ddccf..0c85a33 100644 --- a/src/onebot11/action/index.ts +++ b/src/onebot11/action/index.ts @@ -53,7 +53,7 @@ import { GetGroupHonorInfo } from './group/GetGroupHonorInfo' import { HandleQuickOperation } from './go-cqhttp/QuickOperation' import { SetEssenceMsg } from './go-cqhttp/SetEssenceMsg' import { DelEssenceMsg } from './go-cqhttp/DelEssenceMsg' -import GetEvent from './llonebot/GetEvent' +import { GetEvent } from './llonebot/GetEvent' import { DelGroupFile } from './go-cqhttp/DelGroupFile' import { GetGroupSystemMsg } from './go-cqhttp/GetGroupSystemMsg' import { CreateGroupFileFolder } from './go-cqhttp/CreateGroupFileFolder' @@ -63,6 +63,10 @@ import { GetGroupRootFiles } from './go-cqhttp/GetGroupRootFiles' import { SetOnlineStatus } from './llonebot/SetOnlineStatus' import { SendGroupNotice } from './go-cqhttp/SendGroupNotice' import { GetProfileLike } from './llonebot/GetProfileLike' +import { FetchEmojiLike } from './llonebot/FetchEmojiLike' +import { FetchCustomFace } from './llonebot/FetchCustomFace' +import { GetFriendMsgHistory } from './llonebot/GetFriendMsgHistory' +import { GetGroupFilesByFolder } from './go-cqhttp/GetGroupFilesByFolder' export function initActionMap(adapter: Adapter) { const actionHandlers = [ @@ -76,6 +80,9 @@ export function initActionMap(adapter: Adapter) { new GetEvent(adapter), new SetOnlineStatus(adapter), new GetProfileLike(adapter), + new GetFriendMsgHistory(adapter), + new FetchEmojiLike(adapter), + new FetchCustomFace(adapter), // onebot11 new SendLike(adapter), new GetMsg(adapter), @@ -109,7 +116,7 @@ export function initActionMap(adapter: Adapter) { new SetMsgEmojiLike(adapter), new ForwardFriendSingleMsg(adapter), new ForwardGroupSingleMsg(adapter), - //以下为go-cqhttp api + // go-cqhttp new GetGroupEssence(adapter), new GetGroupHonorInfo(adapter), new SendForwardMsg(adapter), @@ -132,7 +139,8 @@ export function initActionMap(adapter: Adapter) { new DelGroupFolder(adapter), new GetGroupAtAllRemain(adapter), new GetGroupRootFiles(adapter), - new SendGroupNotice(adapter) + new SendGroupNotice(adapter), + new GetGroupFilesByFolder(adapter), ] const actionMap = new Map>() for (const action of actionHandlers) { diff --git a/src/onebot11/action/llonebot/FetchCustomFace.ts b/src/onebot11/action/llonebot/FetchCustomFace.ts new file mode 100644 index 0000000..b28cb2f --- /dev/null +++ b/src/onebot11/action/llonebot/FetchCustomFace.ts @@ -0,0 +1,18 @@ +import { BaseAction, Schema } from '../BaseAction' +import { ActionName } from '../types' + +interface Payload { + count: number | string +} + +export class FetchCustomFace extends BaseAction { + actionName = ActionName.FetchCustomFace + payloadSchema = Schema.object({ + count: Schema.union([Number, String]).default(48) + }) + + async _handle(payload: Payload) { + const ret = await this.ctx.ntMsgApi.fetchFavEmojiList(+payload.count) + return ret.emojiInfoList.map(e => e.url) + } +} diff --git a/src/onebot11/action/llonebot/FetchEmojiLike.ts b/src/onebot11/action/llonebot/FetchEmojiLike.ts new file mode 100644 index 0000000..85fdd77 --- /dev/null +++ b/src/onebot11/action/llonebot/FetchEmojiLike.ts @@ -0,0 +1,28 @@ +import { BaseAction, Schema } from '../BaseAction' +import { ActionName } from '../types' +import { MessageUnique } from '@/common/utils/messageUnique' +import { Dict } from 'cosmokit' + +interface Payload { + emojiId: string + emojiType: string + message_id: string | number + count: string | number +} + +export class FetchEmojiLike extends BaseAction { + actionName = ActionName.FetchEmojiLike + payloadSchema = Schema.object({ + emojiId: Schema.string().required(), + emojiType: Schema.string().required(), + message_id: Schema.union([Number, String]).required(), + count: Schema.union([Number, String]).default(20) + }) + + async _handle(payload: Payload) { + const msgInfo = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) + if (!msgInfo) throw new Error('消息不存在') + const { msgSeq } = (await this.ctx.ntMsgApi.getMsgsByMsgId(msgInfo.Peer, [msgInfo.MsgId])).msgList[0] + return await this.ctx.ntMsgApi.getMsgEmojiLikesList(msgInfo.Peer, msgSeq, payload.emojiId, payload.emojiType, +payload.count) + } +} diff --git a/src/onebot11/action/llonebot/GetEvent.ts b/src/onebot11/action/llonebot/GetEvent.ts index 8cb3aed..effb7fe 100644 --- a/src/onebot11/action/llonebot/GetEvent.ts +++ b/src/onebot11/action/llonebot/GetEvent.ts @@ -11,7 +11,7 @@ interface Payload { timeout: number } -export default class GetEvent extends BaseAction { +export class GetEvent extends BaseAction { actionName = ActionName.GetEvent protected async _handle(payload: Payload): Promise { diff --git a/src/onebot11/action/llonebot/GetFriendMsgHistory.ts b/src/onebot11/action/llonebot/GetFriendMsgHistory.ts new file mode 100644 index 0000000..9beee54 --- /dev/null +++ b/src/onebot11/action/llonebot/GetFriendMsgHistory.ts @@ -0,0 +1,53 @@ +import { BaseAction, Schema } from '../BaseAction' +import { OB11Message } from '@/onebot11/types' +import { ActionName } from '../types' +import { ChatType, RawMessage } from '@/ntqqapi/types' +import { MessageUnique } from '@/common/utils/messageUnique' +import { OB11Entities } from '@/onebot11/entities' +import { filterNullable } from '@/common/utils/misc' + +interface Payload { + user_id: number | string + message_seq?: number | string + message_id?: number | string + count: number | string + reverseOrder: boolean +} + +interface Response { + messages: OB11Message[] +} + +export class GetFriendMsgHistory extends BaseAction { + actionName = ActionName.GetFriendMsgHistory + payloadSchema = Schema.object({ + user_id: Schema.union([Number, String]).required(), + message_seq: Schema.union([Number, String]), + message_id: Schema.union([Number, String]), + count: Schema.union([Number, String]).default(20), + reverseOrder: Schema.boolean().default(false) + }) + + async _handle(payload: Payload): Promise { + const startMsgId = payload.message_seq ?? payload.message_id + let msgList: RawMessage[] + if (startMsgId) { + const msgInfo = await MessageUnique.getMsgIdAndPeerByShortId(+startMsgId) + if (!msgInfo) throw new Error(`消息${startMsgId}不存在`) + msgList = (await this.ctx.ntMsgApi.getMsgHistory(msgInfo.Peer, msgInfo.MsgId, +payload.count)).msgList + } else { + const uid = await this.ctx.ntUserApi.getUidByUin(payload.user_id.toString()) + if (!uid) throw new Error(`记录${payload.user_id}不存在`) + const isBuddy = await this.ctx.ntFriendApi.isBuddy(uid) + const peer = { chatType: isBuddy ? ChatType.friend : ChatType.temp, peerUid: uid } + msgList = (await this.ctx.ntMsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList + } + if (msgList.length === 0) throw new Error('未找到消息') + if (payload.reverseOrder) msgList.reverse() + msgList.map(msg => { + msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId) + }) + const ob11MsgList = await Promise.all(msgList.map(msg => OB11Entities.message(this.ctx, msg))) + return { messages: filterNullable(ob11MsgList) } + } +} diff --git a/src/onebot11/action/msg/SendMsg.ts b/src/onebot11/action/msg/SendMsg.ts index e44e5ae..06ea824 100644 --- a/src/onebot11/action/msg/SendMsg.ts +++ b/src/onebot11/action/msg/SendMsg.ts @@ -1,25 +1,14 @@ -import { - ChatType, - ElementType, - RawMessage, - SendMessageElement, -} from '@/ntqqapi/types' import { OB11MessageCustomMusic, OB11MessageData, OB11MessageDataType, OB11MessageJson, OB11MessageMusic, - OB11MessageNode, OB11PostSendMsg, } from '../../types' -import fs from 'node:fs' import { BaseAction } from '../BaseAction' import { ActionName } from '../types' import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '@/common/utils/sign' -import { Peer } from '@/ntqqapi/types/msg' -import { MessageUnique } from '@/common/utils/messageUnique' -import { selfInfo } from '@/common/globalVars' import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage' interface ReturnData { @@ -42,12 +31,7 @@ export class SendMsg extends BaseAction { payload.auto_escape === true || payload.auto_escape === 'true', ) if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) { - try { - const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[]) - return { message_id: returnMsg.msgShortId! } - } catch (e) { - throw '发送转发消息失败 ' + e - } + throw new Error('请使用 /send_group_forward_msg 或 /send_private_forward_msg 进行合并转发') } else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) { const music = messages[0] as OB11MessageMusic @@ -114,140 +98,10 @@ export class SendMsg extends BaseAction { private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number { if (Array.isArray(message)) { - return message.filter((msg) => msg.type == msgType).length + return message.filter((msg) => msg.type === msgType).length } return 0 } - - private async cloneMsg(msg: RawMessage): Promise { - this.ctx.logger.info('克隆的目标消息', msg) - const sendElements: SendMessageElement[] = [] - for (const ele of msg.elements) { - sendElements.push(ele as SendMessageElement) - } - if (sendElements.length === 0) { - this.ctx.logger.warn('需要clone的消息无法解析,将会忽略掉', msg) - } - this.ctx.logger.info('克隆消息', sendElements) - try { - const peer = { - chatType: ChatType.friend, - peerUid: selfInfo.uid - } - const nodeMsg = await this.ctx.ntMsgApi.sendMsg(peer, sendElements) - await this.ctx.sleep(400) - return nodeMsg - } catch (e) { - this.ctx.logger.warn(e, '克隆转发消息失败,将忽略本条消息', msg) - } - } - - // 返回一个合并转发的消息id - private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]) { - const selfPeer = { - chatType: ChatType.friend, - peerUid: selfInfo.uid, - } - let nodeMsgIds: string[] = [] - // 先判断一遍是不是id和自定义混用 - for (const messageNode of messageNodes) { - // 一个node表示一个人的消息 - const nodeId = messageNode.data.id - // 有nodeId表示一个子转发消息卡片 - if (nodeId) { - const nodeMsg = await MessageUnique.getMsgIdAndPeerByShortId(+nodeId) || await MessageUnique.getPeerByMsgId(nodeId) - if (!nodeMsg) { - this.ctx.logger.warn('转发消息失败,未找到消息', nodeId) - continue - } - nodeMsgIds.push(nodeMsg.MsgId) - } - else { - // 自定义的消息 - // 提取消息段,发给自己生成消息id - try { - const { sendElements, deleteAfterSentFiles } = await createSendElements( - this.ctx, - convertMessage2List(messageNode.data.content), - destPeer - ) - this.ctx.logger.info('开始生成转发节点', sendElements) - const sendElementsSplit: SendMessageElement[][] = [] - let splitIndex = 0 - for (const ele of sendElements) { - if (!sendElementsSplit[splitIndex]) { - sendElementsSplit[splitIndex] = [] - } - - if (ele.elementType === ElementType.FILE || ele.elementType === ElementType.VIDEO) { - if (sendElementsSplit[splitIndex].length > 0) { - splitIndex++ - } - sendElementsSplit[splitIndex] = [ele] - splitIndex++ - } - else { - sendElementsSplit[splitIndex].push(ele) - } - this.ctx.logger.info(sendElementsSplit) - } - // log("分割后的转发节点", sendElementsSplit) - for (const eles of sendElementsSplit) { - const nodeMsg = await sendMsg(this.ctx, selfPeer, eles, []) - if (!nodeMsg) { - this.ctx.logger.warn('转发节点生成失败', eles) - continue - } - nodeMsgIds.push(nodeMsg.msgId) - await this.ctx.sleep(400) - } - deleteAfterSentFiles.map((f) => fs.unlink(f, () => { - })) - } catch (e) { - this.ctx.logger.error('生成转发消息节点失败', e) - } - } - } - - // 检查srcPeer是否一致,不一致则需要克隆成自己的消息, 让所有srcPeer都变成自己的,使其保持一致才能够转发 - const nodeMsgArray: RawMessage[] = [] - let srcPeer: Peer | null = null - let needSendSelf = false - for (const msgId of nodeMsgIds) { - const nodeMsgPeer = await MessageUnique.getPeerByMsgId(msgId) - if (nodeMsgPeer) { - const nodeMsg = (await this.ctx.ntMsgApi.getMsgsByMsgId(nodeMsgPeer.Peer, [msgId])).msgList[0] - srcPeer = srcPeer ?? { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid } - if (srcPeer.peerUid !== nodeMsg.peerUid) { - needSendSelf = true - } - nodeMsgArray.push(nodeMsg) - } - } - nodeMsgIds = nodeMsgArray.map((msg) => msg.msgId) - if (needSendSelf) { - for (const msg of nodeMsgArray) { - if (msg.peerUid === selfPeer.peerUid) continue - await this.cloneMsg(msg) - } - } - // elements之间用换行符分隔 - // let _sendForwardElements: SendMessageElement[] = [] - // for(let i = 0; i < sendForwardElements.length; i++){ - // _sendForwardElements.push(sendForwardElements[i]) - // _sendForwardElements.push(SendMsgElementConstructor.text("\n\n")) - // } - // const nodeMsg = await NTQQApi.sendMsg(selfPeer, _sendForwardElements, true); - // nodeIds.push(nodeMsg.msgId) - // await sleep(500); - // 开发转发 - if (nodeMsgIds.length === 0) { - throw Error('转发消息失败,节点为空') - } - const returnMsg = await this.ctx.ntMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds) - returnMsg.msgShortId = MessageUnique.createMsg(destPeer, returnMsg.msgId) - return returnMsg - } } export default SendMsg diff --git a/src/onebot11/action/types.ts b/src/onebot11/action/types.ts index f128bf8..967ce67 100644 --- a/src/onebot11/action/types.ts +++ b/src/onebot11/action/types.ts @@ -21,6 +21,9 @@ export enum ActionName { GetEvent = 'get_event', SetOnlineStatus = 'set_online_status', GetProfileLike = 'get_profile_like', + FetchEmojiLike = 'fetch_emoji_like', + FetchCustomFace = 'fetch_custom_face', + GetFriendMsgHistory = 'get_friend_msg_history', // onebot 11 SendLike = 'send_like', GetLoginInfo = 'get_login_info', @@ -78,4 +81,5 @@ export enum ActionName { GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain', GoCQHTTP_GetGroupRootFiles = 'get_group_root_files', GoCQHTTP_SendGroupNotice = '_send_group_notice', + GoCQHTTP_GetGroupFilesByFolder = 'get_group_files_by_folder' } diff --git a/src/onebot11/action/user/SetFriendAddRequest.ts b/src/onebot11/action/user/SetFriendAddRequest.ts index 65330a5..c87ee76 100644 --- a/src/onebot11/action/user/SetFriendAddRequest.ts +++ b/src/onebot11/action/user/SetFriendAddRequest.ts @@ -12,7 +12,16 @@ export default class SetFriendAddRequest extends BaseAction { protected async _handle(payload: Payload): Promise { const approve = payload.approve?.toString() !== 'false' - await this.ctx.ntFriendApi.handleFriendRequest(payload.flag, approve) + const data = payload.flag.split('|') + if (data.length < 2) { + throw new Error('无效的flag') + } + const uid = data[0] + const reqTime = data[1] + await this.ctx.ntFriendApi.handleFriendRequest(uid, reqTime, approve) + if (payload.remark) { + await this.ctx.ntFriendApi.setBuddyRemark(uid, payload.remark) + } return null } } diff --git a/src/onebot11/entities.ts b/src/onebot11/entities.ts index ad513b3..fb341ae 100644 --- a/src/onebot11/entities.ts +++ b/src/onebot11/entities.ts @@ -155,13 +155,21 @@ export namespace OB11Entities { guildId: '' } try { - const { replayMsgSeq, replyMsgTime, senderUidStr } = replyElement + const { replayMsgSeq, replyMsgTime } = replyElement const records = msg.records.find(msgRecord => msgRecord.msgId === replyElement.sourceMsgIdInRecords) - if (!records || !replyMsgTime || !senderUidStr) { + const senderUid = replyElement.senderUidStr || records?.senderUid + if (!records || !replyMsgTime || !senderUid) { throw new Error('找不到回复消息') } - const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, replayMsgSeq, replyMsgTime, [senderUidStr]) - const replyMsg = msgList.find(msg => msg.msgRandom === records.msgRandom) + const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, replayMsgSeq, replyMsgTime, [senderUid]) + + let replyMsg: RawMessage | undefined + if (records.msgRandom !== '0') { + replyMsg = msgList.find(msg => msg.msgRandom === records.msgRandom) + } else { + ctx.logger.info('msgRandom is missing', replyElement, records) + replyMsg = msgList[0] + } // 284840486: 合并消息内侧 消息具体定位不到 if (!replyMsg && msg.peerUin !== '284840486') { @@ -702,10 +710,10 @@ export namespace OB11Entities { sex: sex(member.sex!), age: 0, area: '', - level: '0', + level: String(member.memberLevel ?? 0), qq_level: (member.qqLevel && calcQQLevel(member.qqLevel)) || 0, - join_time: 0, // 暂时没法获取 - last_sent_time: 0, // 暂时没法获取 + join_time: member.joinTime, + last_sent_time: member.lastSpeakTime, title_expire_time: 0, unfriendly: false, card_changeable: true, diff --git a/src/onebot11/helper/quickOperation.ts b/src/onebot11/helper/quickOperation.ts index cf0214e..1f0973f 100644 --- a/src/onebot11/helper/quickOperation.ts +++ b/src/onebot11/helper/quickOperation.ts @@ -112,8 +112,16 @@ async function handleMsg(ctx: Context, msg: OB11Message, quickAction: QuickOpera async function handleFriendRequest(ctx: Context, request: OB11FriendRequestEvent, quickAction: QuickOperationFriendRequest) { if (!isNullable(quickAction.approve)) { - // todo: set remark - ctx.ntFriendApi.handleFriendRequest(request.flag, quickAction.approve).catch(e => ctx.logger.error(e)) + const data = request.flag.split('|') + if (data.length < 2) { + return + } + const uid = data[0] + const reqTime = data[1] + await ctx.ntFriendApi.handleFriendRequest(uid, reqTime, quickAction.approve).catch(e => ctx.logger.error(e)) + if (!isNullable(quickAction.remark)) { + ctx.ntFriendApi.setBuddyRemark(uid, quickAction.remark).catch(e => ctx.logger.error(e)) + } } } diff --git a/src/onebot11/types.ts b/src/onebot11/types.ts index 91eb137..2e19965 100644 --- a/src/onebot11/types.ts +++ b/src/onebot11/types.ts @@ -308,8 +308,8 @@ export type OB11MessageData = export interface OB11PostSendMsg { message_type?: 'private' | 'group' - user_id: string - group_id?: string + user_id?: string | number + group_id?: string | number message: OB11MessageMixType messages?: OB11MessageMixType // 兼容 go-cqhttp auto_escape?: boolean | string diff --git a/src/version.ts b/src/version.ts index 849cf54..ff2c349 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const version = '3.32.8' +export const version = '3.33.0' diff --git a/tsconfig.json b/tsconfig.json index a383f6f..21e5d46 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,7 +15,7 @@ "./src/common/*" ], "@/onebot11/*": [ - "./src/onebot11" + "./src/onebot11/*" ], "@/ntqqapi/*": [ "./src/ntqqapi/*" @@ -27,4 +27,4 @@ "src", "scripts" ] -} \ No newline at end of file +}