Compare commits

..

20 Commits

Author SHA1 Message Date
idranme
6c66dab3dc Merge pull request #433 from LLOneBot/dev
release: 3.33.1
2024-09-19 18:31:01 +08:00
idranme
0f7939fe5e chore: v3.33.1 2024-09-19 18:29:16 +08:00
idranme
73a2b4e35f fix 2024-09-19 18:29:12 +08:00
idranme
936b1d911c Merge pull request #428 from LLOneBot/dev
release: 3.33.0
2024-09-18 20:59:57 +08:00
idranme
58817d1c02 chore: v3.33.0 2024-09-18 20:53:28 +08:00
idranme
2c24422478 feat: support setting remark when agreeing to a friend request 2024-09-18 20:47:45 +08:00
idranme
c2a723380a fix: get_group_member_list API 2024-09-18 19:35:58 +08:00
idranme
156bbaea33 feat: get_group_files_by_folder API 2024-09-18 17:22:09 +08:00
idranme
6c485634e1 feat: get_friend_msg_history API 2024-09-18 16:56:15 +08:00
idranme
f39a9aeafb feat: fetch_custom_face API 2024-09-18 16:11:08 +08:00
idranme
1160cd4b26 feat: fetch_emoji_like API 2024-09-18 15:49:37 +08:00
idranme
9a7ff523dd optimize 2024-09-18 14:07:42 +08:00
idranme
f49995ea97 refactor 2024-09-17 21:04:36 +08:00
idranme
1876dd29ac Merge pull request #423 from LLOneBot/dev
release: 3.32.8
2024-09-17 11:59:57 +08:00
idranme
9944b53266 chore: v3.32.8 2024-09-17 11:55:50 +08:00
idranme
9a791e3a21 fix 2024-09-17 02:17:16 +08:00
idranme
64c5eb6c04 Merge pull request #422 from LLOneBot/dev
release: 3.32.7
2024-09-16 20:48:15 +08:00
idranme
e5750786cb chore: v3.32.7 2024-09-16 20:46:14 +08:00
idranme
18cb46ade5 fix 2024-09-16 20:43:18 +08:00
idranme
e39c89a441 fix 2024-09-16 19:01:59 +08:00
51 changed files with 735 additions and 776 deletions

7
.gitattributes vendored Normal file
View File

@@ -0,0 +1,7 @@
* text eol=lf
*.png -text
*.jpg -text
*.ico -text
*.gif -text
*.webp -text

View File

@@ -31,7 +31,6 @@ const config: ElectronViteConfig = {
resolve: { resolve: {
alias: { alias: {
'@': path.resolve(__dirname, './src'), '@': path.resolve(__dirname, './src'),
'./lib-cov/fluent-ffmpeg': './lib/fluent-ffmpeg',
}, },
}, },
plugins: [ plugins: [

View File

@@ -4,7 +4,7 @@
"name": "LLOneBot", "name": "LLOneBot",
"slug": "LLOneBot", "slug": "LLOneBot",
"description": "实现 OneBot 11 协议,用于 QQ 机器人开发", "description": "实现 OneBot 11 协议,用于 QQ 机器人开发",
"version": "3.32.6", "version": "3.33.1",
"icon": "./icon.webp", "icon": "./icon.webp",
"authors": [ "authors": [
{ {

View File

@@ -17,16 +17,16 @@
"author": "", "author": "",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@minatojs/driver-sqlite": "^4.5.0", "@minatojs/driver-sqlite": "4.5.0",
"compressing": "^1.10.1", "compressing": "^1.10.1",
"cordis": "^3.18.0", "cordis": "^3.18.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"cosmokit": "^1.6.2", "cosmokit": "^1.6.2",
"express": "^5.0.0", "express": "^5.0.0",
"fast-xml-parser": "^4.5.0", "fast-xml-parser": "^4.5.0",
"file-type": "^19.5.0", "file-type": "^19.5.0",
"fluent-ffmpeg": "^2.1.3", "fluent-ffmpeg": "^2.1.3",
"minato": "^3.5.1", "minato": "3.5.1",
"protobufjs": "^7.4.0", "protobufjs": "^7.4.0",
"silk-wasm": "^3.6.1", "silk-wasm": "^3.6.1",
"ws": "^8.18.0" "ws": "^8.18.0"
@@ -41,7 +41,7 @@
"electron-vite": "^2.3.0", "electron-vite": "^2.3.0",
"protobufjs-cli": "^1.1.3", "protobufjs-cli": "^1.1.3",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"vite": "^5.4.5", "vite": "^5.4.6",
"vite-plugin-cp": "^4.0.8" "vite-plugin-cp": "^4.0.8"
}, },
"packageManager": "yarn@4.4.1" "packageManager": "yarn@4.4.1"

View File

@@ -91,17 +91,26 @@ interface FetchFileRes {
} }
export async function fetchFile(url: string, headersInit?: Record<string, string>): Promise<FetchFileRes> { export async function fetchFile(url: string, headersInit?: Record<string, string>): Promise<FetchFileRes> {
const headers: Record<string, string> = { const headers = new Headers({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36',
'Host': new URL(url).hostname, 'Host': new URL(url).hostname,
...headersInit ...headersInit
} })
const raw = await fetch(url, { headers }).catch((err) => { let raw = await fetch(url, { headers }).catch((err) => {
if (err.cause) { if (err.cause) {
throw err.cause throw err.cause
} }
throw err throw err
}) })
if (raw.status === 403 && !headers.has('Referer')) {
headers.set('Referer', url)
raw = await fetch(url, { headers }).catch((err) => {
if (err.cause) {
throw err.cause
}
throw err
})
}
if (!raw.ok) throw new Error(`statusText: ${raw.statusText}`) if (!raw.ok) throw new Error(`statusText: ${raw.statusText}`)
return { return {
data: Buffer.from(await raw.arrayBuffer()), data: Buffer.from(await raw.arrayBuffer()),
@@ -133,7 +142,7 @@ export async function uri2local(uri: string, filename?: string, needExt?: boolea
if (type === FileUriType.RemoteURL) { if (type === FileUriType.RemoteURL) {
try { try {
const res = await fetchFile(uri, { 'Referer': uri }) const res = await fetchFile(uri)
const match = res.url.match(/.+\/([^/?]*)(?=\?)?/) const match = res.url.match(/.+\/([^/?]*)(?=\?)?/)
if (match?.[1]) { if (match?.[1]) {
filename ??= match[1].replace(/[/\\:*?"<>|]/g, '_') filename ??= match[1].replace(/[/\\:*?"<>|]/g, '_')

View File

@@ -127,7 +127,6 @@ class MessageUniqueWrapper {
} }
} }
} }
return undefined
} }
getShortIdByMsgId(msgId: string): number | undefined { getShortIdByMsgId(msgId: string): number | undefined {
@@ -160,4 +159,4 @@ class MessageUniqueWrapper {
} }
} }
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper() export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper()

View File

@@ -23,8 +23,7 @@ export default class Log {
return return
} }
const dateTime = new Date(record.timestamp).toLocaleString() const dateTime = new Date(record.timestamp).toLocaleString()
const userInfo = selfInfo.uin ? `${selfInfo.nick}(${selfInfo.uin})` : '' const content = `${dateTime} [${record.type}] ${selfInfo.nick}(${selfInfo.uin}) | ${record.name} ${record.content}\n\n`
const content = `${dateTime} [${record.type}] ${userInfo} | ${record.name} ${record.content}\n\n`
appendFile(file, content, noop) appendFile(file, content, noop)
}, },
} }

View File

@@ -13,15 +13,14 @@ import {
PicElement, PicElement,
} from '../types' } from '../types'
import path from 'node:path' import path from 'node:path'
import fs from 'node:fs' import { existsSync } from 'node:fs'
import { ReceiveCmdS } from '../hook' import { ReceiveCmdS } from '../hook'
import { RkeyManager } from '@/ntqqapi/helper/rkey' import { RkeyManager } from '@/ntqqapi/helper/rkey'
import { getSession } from '@/ntqqapi/wrapper' 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 { calculateFileMD5 } from '@/common/utils/file'
import { fileTypeFromFile } from 'file-type' import { fileTypeFromFile } from 'file-type'
import fsPromise from 'node:fs/promises' import { copyFile, stat, unlink } from 'node:fs/promises'
import { OnRichMediaDownloadCompleteParams } from '@/ntqqapi/listeners'
import { Time } from 'cosmokit' import { Time } from 'cosmokit'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { TEMP_DIR } from '@/common/globalVars' import { TEMP_DIR } from '@/common/globalVars'
@@ -111,8 +110,8 @@ export class NTQQFileApi extends Service {
}, },
}]) }])
} }
await fsPromise.copyFile(filePath, mediaPath) await copyFile(filePath, mediaPath)
const fileSize = (await fsPromise.stat(filePath)).size const fileSize = (await stat(filePath)).size
return { return {
md5: fileMd5, md5: fileMd5,
fileName, fileName,
@@ -133,10 +132,10 @@ export class NTQQFileApi extends Service {
force = false force = false
) { ) {
// 用于下载收到的消息中的图片等 // 用于下载收到的消息中的图片等
if (sourcePath && fs.existsSync(sourcePath)) { if (sourcePath && existsSync(sourcePath)) {
if (force) { if (force) {
try { try {
await fsPromise.unlink(sourcePath) await unlink(sourcePath)
} catch { } } catch { }
} else { } else {
return sourcePath return sourcePath

View File

@@ -42,13 +42,7 @@ export class NTQQFriendApi extends Service {
return _friends return _friends
} }
async handleFriendRequest(flag: string, accept: boolean) { async handleFriendRequest(friendUid: string, reqTime: string, accept: boolean) {
const data = flag.split('|')
if (data.length < 2) {
return
}
const friendUid = data[0]
const reqTime = data[1]
const session = getSession() const session = getSession()
if (session) { if (session) {
return session.getBuddyService().approvalFriendRequest({ return session.getBuddyService().approvalFriendRequest({
@@ -194,4 +188,10 @@ export class NTQQFriendApi extends Service {
const ret = await invoke('nodeIKernelBuddyService/getBuddyRecommendContactArkJson', [{ uin }, null]) const ret = await invoke('nodeIKernelBuddyService/getBuddyRecommendContactArkJson', [{ uin }, null])
return ret.arkMsg return ret.arkMsg
} }
async setBuddyRemark(uid: string, remark: string) {
return await invoke('nodeIKernelBuddyService/setBuddyRemark', [{
remarkParams: { uid, remark }
}, null])
}
} }

View File

@@ -1,10 +1,18 @@
import { ReceiveCmdS } from '../hook' 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 { invoke, NTClass, NTMethod } from '../ntcall'
import { GeneralCallResult } from '../services' import { GeneralCallResult } from '../services'
import { NTQQWindows } from './window' import { NTQQWindows } from './window'
import { getSession } from '../wrapper' import { getSession } from '../wrapper'
import { OnGroupFileInfoUpdateParams } from '../listeners'
import { NodeIKernelGroupService } from '../services' import { NodeIKernelGroupService } from '../services'
import { Service, Context } from 'cordis' import { Service, Context } from 'cordis'
import { isNumeric } from '@/common/utils/misc' import { isNumeric } from '@/common/utils/misc'

View File

@@ -183,7 +183,7 @@ export class NTQQMsgApi extends Service {
if (!arkElement) { if (!arkElement) {
continue continue
} }
const forwardData = JSON.parse(arkElement.arkElement.bytesData) const forwardData = JSON.parse(arkElement.arkElement!.bytesData)
if (forwardData.app != 'com.tencent.multimsg') { if (forwardData.app != 'com.tencent.multimsg') {
continue continue
} }
@@ -235,7 +235,7 @@ export class NTQQMsgApi extends Service {
}, null]) }, null])
} }
async queryMsgsWithFilterExBySeq(peer: Peer, msgSeq: string, filterMsgTime: string, filterSendersUid: string[]) { async queryMsgsWithFilterExBySeq(peer: Peer, msgSeq: string, filterMsgTime: string, filterSendersUid: string[] = []) {
return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{ return await invoke('nodeIKernelMsgService/queryMsgsWithFilterEx', [{
msgId: '0', msgId: '0',
msgTime: '0', msgTime: '0',
@@ -256,4 +256,23 @@ export class NTQQMsgApi extends Service {
async setMsgRead(peer: Peer) { async setMsgRead(peer: Peer) {
return await invoke('nodeIKernelMsgService/setMsgRead', [{ peer }, null]) 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])
}
} }

View File

@@ -305,4 +305,34 @@ export class NTQQWebApi extends Service {
private cookieToString(cookieObject: Dict) { private cookieToString(cookieObject: Dict) {
return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ') return Object.entries(cookieObject).map(([key, value]) => `${key}=${value}`).join('; ')
} }
async findGroupEssenceMsg(groupCode: string, msgSeq: number) {
for (let i = 0; i < 20; i++) {
const res = await this.getGroupEssenceMsgList(groupCode, i, 50)
if (!res) break
const msg = res.data.msg_list.find(e => e.msg_seq === msgSeq)
if (msg) return msg
if (res.data.is_end) break
}
}
async getGroupEssenceMsgList(groupCode: string, pageStart: number, pageLimit: number) {
const cookieObject = await this.ctx.ntUserApi.getCookies('qun.qq.com')
try {
const ret = await RequestUtil.HttpGetJson<GroupEssenceMsgRet>(
`https://qun.qq.com/cgi-bin/group_digest/digest_list?${new URLSearchParams({
bkn: this.genBkn(cookieObject.skey),
page_start: pageStart.toString(),
page_limit: pageLimit.toString(),
group_code: groupCode,
})}`,
'GET',
'',
{ 'Cookie': this.cookieToString(cookieObject) }
)
return ret.retcode === 0 ? ret : undefined
} catch {
return undefined
}
}
} }

View File

@@ -27,8 +27,8 @@ declare module 'cordis' {
} }
interface Events { interface Events {
'nt/message-created': (input: RawMessage[]) => void 'nt/message-created': (input: RawMessage[]) => void
'nt/message-deleted': (input: RawMessage[]) => void 'nt/message-deleted': (input: RawMessage) => void
'nt/message-sent': (input: RawMessage[]) => void 'nt/message-sent': (input: RawMessage) => void
'nt/group-notify': (input: GroupNotify[]) => void 'nt/group-notify': (input: GroupNotify[]) => void
'nt/friend-request': (input: FriendRequest[]) => void 'nt/friend-request': (input: FriendRequest[]) => void
'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void 'nt/group-member-info-updated': (input: { groupCode: string, members: GroupMember[] }) => void
@@ -96,7 +96,7 @@ class Core extends Service {
} }
for (const path of pathList) { for (const path of pathList) {
if (path) { if (path) {
fs.unlink(picPath, () => { fs.unlink(path, () => {
this.ctx.logger.info('删除文件成功', path) this.ctx.logger.info('删除文件成功', path)
}) })
} }
@@ -174,23 +174,26 @@ class Core extends Service {
this.ctx.parallel('nt/message-created', payload.msgList) this.ctx.parallel('nt/message-created', payload.msgList)
}) })
const sentMsgIds = new Map<string, boolean>()
const recallMsgIds: string[] = [] // 避免重复上报 const recallMsgIds: string[] = [] // 避免重复上报
registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => { registerReceiveHook<{ msgList: RawMessage[] }>([ReceiveCmdS.UPDATE_MSG], payload => {
const list = payload.msgList.filter(v => { for (const msg of payload.msgList) {
if (recallMsgIds.includes(v.msgId)) { if (msg.recallTime !== '0' && !recallMsgIds.includes(msg.msgId)) {
return false recallMsgIds.push(msg.msgId)
this.ctx.parallel('nt/message-deleted', msg)
} else if (sentMsgIds.get(msg.msgId)) {
sentMsgIds.delete(msg.msgId)
this.ctx.parallel('nt/message-sent', msg)
} }
recallMsgIds.push(v.msgId) }
return true
})
this.ctx.parallel('nt/message-deleted', list)
}) })
registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => { registerReceiveHook<{ msgRecord: RawMessage }>(ReceiveCmdS.SELF_SEND_MSG, payload => {
if (!this.config.reportSelfMessage) { if (!this.config.reportSelfMessage) {
return return
} }
this.ctx.parallel('nt/message-sent', [payload.msgRecord]) sentMsgIds.set(payload.msgRecord.msgId, true)
}) })
const groupNotifyFlags: string[] = [] const groupNotifyFlags: string[] = []

View File

@@ -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<string, GroupMember>,
finish: boolean,
hasRobot: boolean
}): void
onMemberInfoChange(groupCode: string, changeType: number, members: Map<string, GroupMember>): 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
}

View File

@@ -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<unknown>,
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
}

View File

@@ -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
}

View File

@@ -1,3 +0,0 @@
export * from './NodeIKernelProfileListener'
export * from './NodeIKernelGroupListener'
export * from './NodeIKernelMsgListener'

View File

@@ -123,11 +123,15 @@ export function invoke<
return new Promise<R>((resolve, reject) => { return new Promise<R>((resolve, reject) => {
const apiArgs = [method, ...args] const apiArgs = [method, ...args]
const callbackId = randomUUID() 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) { if (!options.cbCmd) {
// QQ后端会返回结果并且可以根据uuid识别 // QQ后端会返回结果并且可以根据uuid识别
hookApiCallbacks[callbackId] = res => { hookApiCallbacks[callbackId] = res => {
success = true clearTimeout(timeoutId)
resolve(res) resolve(res)
} }
} }
@@ -139,13 +143,13 @@ export function invoke<
if (options.cmdCB) { if (options.cmdCB) {
if (options.cmdCB(payload, result)) { if (options.cmdCB(payload, result)) {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true clearTimeout(timeoutId)
resolve(payload) resolve(payload)
} }
} }
else { else {
removeReceiveHook(hookId) removeReceiveHook(hookId)
success = true clearTimeout(timeoutId)
resolve(payload) resolve(payload)
} }
}) })
@@ -158,16 +162,11 @@ export function invoke<
} }
else { else {
log('ntqq api call failed,', method, res) log('ntqq api call failed,', method, res)
clearTimeout(timeoutId)
reject(`ntqq api call failed, ${method}, ${res.errMsg}`) 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( ipcMain.emit(
channel, channel,

View File

@@ -104,11 +104,20 @@ export interface NodeIKernelGroupService {
createMemberListScene(groupCode: string, scene: string): string createMemberListScene(groupCode: string, scene: string): string
destroyMemberListScene(SceneId: string): void destroyMemberListScene(sceneId: string): void
//About Arg (a) name: lastId 根据手Q来看为object {index:?(number),uid:string}
getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{ getNextMemberList(sceneId: string, a: undefined, num: number): Promise<{
errCode: number, errMsg: string, errCode: number
result: { ids: string[], infos: Map<string, GroupMember>, finish: boolean, hasRobot: boolean } errMsg: string
result: {
ids: {
uid: string
index: number
}[]
infos: Map<string, GroupMember>
finish: boolean
hasRobot: boolean
}
}> }>
getPrevMemberList(): unknown getPrevMemberList(): unknown

View File

@@ -214,7 +214,7 @@ export interface NodeIKernelMsgService {
getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown getMsgByClientSeqAndTime(peer: Peer, clientSeq: string, time: string): unknown
getSourceOfReplyMsgByClientSeqAndTime(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<number> }): unknown getMsgsByTypeFilter(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilter: { type: number, subtype: Array<number> }): unknown
getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown getMsgsByTypeFilters(peer: Peer, msgId: string, cnt: unknown, queryOrder: boolean, typeFilters: Array<{ type: number, subtype: Array<number> }>): unknown
@@ -223,31 +223,8 @@ export interface NodeIKernelMsgService {
queryMsgsWithFilter(...args: unknown[]): unknown 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<unknown>
// this.chatType = i2 // this.chatType = i2
// this.peerUid = str // this.peerUid = str
// this.chatInfo = new ChatInfo() // this.chatInfo = new ChatInfo()
// this.filterMsgType = new ArrayList<>() // this.filterMsgType = new ArrayList<>()
// this.filterSendersUid = new ArrayList<>() // this.filterSendersUid = new ArrayList<>()
@@ -495,16 +472,15 @@ export interface NodeIKernelMsgService {
setMsgEmojiLikes(...args: unknown[]): unknown setMsgEmojiLikes(...args: unknown[]): unknown
getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{ getMsgEmojiLikesList(peer: Peer, msgSeq: string, emojiId: string, emojiType: string, cookie: string, bForward: boolean, number: number): Promise<{
result: number, result: number
errMsg: string, errMsg: string
emojiLikesList: emojiLikesList: {
Array<{ tinyId: string
tinyId: string, nickName: string
nickName: string,
headUrl: string headUrl: string
}>, }[]
cookie: string, cookie: string
isLastPage: boolean, isLastPage: boolean
isFirstPage: boolean isFirstPage: boolean
}> }>
@@ -686,9 +662,8 @@ export interface NodeIKernelMsgService {
dataMigrationStopOperation(...args: unknown[]): unknown dataMigrationStopOperation(...args: unknown[]): unknown
//新的希望
dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{ dataMigrationImportMsgPbRecord(DataMigrationMsgInfo: Array<{
extensionData: string//"Hex" extensionData: string //"Hex"
extraData: string //"" extraData: string //""
chatType: number chatType: number
chatUin: string chatUin: string
@@ -740,4 +715,4 @@ export interface NodeIKernelMsgService {
getGroupMsgStorageTime(): unknown//这是嘛啊 getGroupMsgStorageTime(): unknown//这是嘛啊
} }

View File

@@ -62,8 +62,9 @@ export interface GroupMember {
sex?: Sex sex?: Sex
qqLevel?: QQLevel qqLevel?: QQLevel
isChangeRole: boolean isChangeRole: boolean
joinTime: string joinTime: number
lastSpeakTime: string lastSpeakTime: number
memberLevel: number
} }
export interface PublishGroupBulletinReq { export interface PublishGroupBulletinReq {
@@ -76,4 +77,4 @@ export interface PublishGroupBulletinReq {
oldFeedsId: '' oldFeedsId: ''
pinned: number pinned: number
confirmRequired: number confirmRequired: number
} }

View File

@@ -6,6 +6,7 @@ export interface GetFileListParam {
startIndex: number startIndex: number
sortOrder: number sortOrder: number
showOnlinedocFolder: number showOnlinedocFolder: number
folderId?: string
} }
export enum ElementType { export enum ElementType {
@@ -102,7 +103,7 @@ export interface SendPicElement {
export interface SendReplyElement { export interface SendReplyElement {
elementType: ElementType.REPLY elementType: ElementType.REPLY
elementId: '' elementId: ''
replyElement: ReplyElement replyElement: Partial<ReplyElement>
} }
export interface SendFaceElement { export interface SendFaceElement {
@@ -129,6 +130,12 @@ export interface ReplyElement {
replayMsgId: string replayMsgId: string
senderUin: string senderUin: string
senderUinStr: string senderUinStr: string
sourceMsgIdInRecords: string
senderUid: string
senderUidStr: string
sourceMsgIsIncPic: boolean // 原消息是否有图片
sourceMsgText: string
replyMsgTime: string
} }
export interface FileElement { export interface FileElement {
@@ -303,7 +310,7 @@ export enum GrayTipElementSubType {
export interface GrayTipElement { export interface GrayTipElement {
subElementType: GrayTipElementSubType subElementType: GrayTipElementSubType
revokeElement: { revokeElement?: {
operatorRole: string operatorRole: string
operatorUid: string operatorUid: string
operatorNick: string operatorNick: string
@@ -313,14 +320,14 @@ export interface GrayTipElement {
isSelfOperate?: boolean isSelfOperate?: boolean
wording: string // 自定义的撤回提示语 wording: string // 自定义的撤回提示语
} }
aioOpGrayTipElement: TipAioOpGrayTipElement aioOpGrayTipElement?: TipAioOpGrayTipElement
groupElement: TipGroupElement groupElement?: TipGroupElement
xmlElement: { xmlElement?: {
templId: string templId: string
content: string content: string
} }
jsonGrayTipElement: { jsonGrayTipElement?: {
busiId: number busiId: string
jsonStr: string jsonStr: string
} }
} }
@@ -475,7 +482,7 @@ export interface RawMessage {
msgSeq: string msgSeq: string
msgRandom: string msgRandom: string
senderUid: string senderUid: string
senderUin?: string // 发送者QQ号 senderUin: string // 发送者QQ号
peerUid: string // 群号 或者 QQ uid peerUid: string // 群号 或者 QQ uid
peerUin: string // 群号 或者 发送者QQ号 peerUin: string // 群号 或者 发送者QQ号
guildId: string guildId: string
@@ -485,36 +492,7 @@ export interface RawMessage {
sendStatus?: number // 消息状态别人发的2是已撤回自己发的2是已发送 sendStatus?: number // 消息状态别人发的2是已撤回自己发的2是已发送
recallTime: string // 撤回时间, "0"是没有撤回 recallTime: string // 撤回时间, "0"是没有撤回
records: RawMessage[] records: RawMessage[]
elements: { elements: MessageElement[]
elementId: string
elementType: ElementType
replyElement: {
sourceMsgIdInRecords: string
senderUid: string // 原消息发送者QQ号
sourceMsgIsIncPic: boolean // 原消息是否有图片
sourceMsgText: string
replayMsgSeq: string // 源消息的msgSeq可以通过这个找到源消息的msgId
senderUidStr: string
replyMsgTime: string
}
textElement: {
atType: AtType
atUid: string // QQ号
content: string
atNtUid: string // uid号
}
picElement: PicElement
pttElement: PttElement
arkElement: ArkElement
grayTipElement: GrayTipElement
faceElement: FaceElement
videoElement: VideoElement
fileElement: FileElement
marketFaceElement: MarketFaceElement
inlineKeyboardElement: InlineKeyboardElement
markdownElement: MarkdownElement
multiForwardMsgElement: MultiForwardMsgElement
}[]
} }
export interface Peer { export interface Peer {
@@ -529,7 +507,7 @@ export interface MessageElement {
extBufForUI: string //"0x" extBufForUI: string //"0x"
textElement?: TextElement textElement?: TextElement
faceElement?: FaceElement faceElement?: FaceElement
marketFaceElement?: MarkdownElement marketFaceElement?: MarketFaceElement
replyElement?: ReplyElement replyElement?: ReplyElement
picElement?: PicElement picElement?: PicElement
pttElement?: PttElement pttElement?: PttElement
@@ -556,3 +534,77 @@ export interface MessageElement {
recommendedMsgElement?: unknown recommendedMsgElement?: unknown
actionBarElement?: 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
}

View File

@@ -33,30 +33,8 @@ export interface WrapperApi {
NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession NodeIQQNTWrapperSession?: NodeIQQNTWrapperSession
} }
export interface WrapperConstructor {
[key: string]: unknown
}
const wrapperApi: WrapperApi = {} 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.dlopenOrig = Process.dlopen
Process.dlopen = function (module: Dict, filename: string, flags = constants.dlopen.RTLD_LAZY) { 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 return ret
} }
}) })
if (constructor.includes(export_name)) {
wrapperConstructor[export_name] = module.exports[export_name]
}
} }
return dlopenRet return dlopenRet
} }

View File

@@ -59,7 +59,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
if (!findEle) { if (!findEle) {
throw new Error('element not found') throw new Error('element not found')
} }
res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement) res.url = await this.ctx.ntFileApi.getImageUrl(findEle.picElement!)
} else if (fileCache[0].elementType === ElementType.VIDEO) { } else if (fileCache[0].elementType === ElementType.VIDEO) {
res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId) res.url = await this.ctx.ntFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId)
} }

View File

@@ -1,4 +1,4 @@
import { BaseAction } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11ForwardMessage } from '../../types' import { OB11ForwardMessage } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -16,6 +16,11 @@ interface Response {
export class GetForwardMsg extends BaseAction<Payload, Response> { export class GetForwardMsg extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetForwardMsg actionName = ActionName.GoCQHTTP_GetForwardMsg
payloadSchema = Schema.object({
message_id: String,
id: String
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
const msgId = payload.id || payload.message_id const msgId = payload.id || payload.message_id
if (!msgId) { if (!msgId) {

View File

@@ -1,4 +1,4 @@
import { BaseAction } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
interface Payload { interface Payload {
@@ -13,6 +13,9 @@ interface Response {
export class GetGroupAtAllRemain extends BaseAction<Payload, Response> { export class GetGroupAtAllRemain extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain
payloadSchema = Schema.object({
group_id: Schema.union([Number, String]).required()
})
async _handle(payload: Payload) { async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupRemainAtTimes(payload.group_id.toString()) const data = await this.ctx.ntGroupApi.getGroupRemainAtTimes(payload.group_id.toString())

View File

@@ -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<Payload, Response> {
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: []
}
}
}

View File

@@ -1,4 +1,4 @@
import { BaseAction } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11Message } from '../../types' import { OB11Message } from '../../types'
import { ActionName } from '../types' import { ActionName } from '../types'
import { ChatType } from '@/ntqqapi/types' import { ChatType } from '@/ntqqapi/types'
@@ -10,8 +10,8 @@ import { filterNullable } from '@/common/utils/misc'
interface Payload { interface Payload {
group_id: number | string group_id: number | string
message_seq?: number | string message_seq?: number | string
count?: number | string count: number | string
reverseOrder?: boolean reverseOrder: boolean
} }
interface Response { interface Response {
@@ -20,10 +20,15 @@ interface Response {
export class GetGroupMsgHistory extends BaseAction<Payload, Response> { export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupMsgHistory 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<Response> { protected async _handle(payload: Payload): Promise<Response> {
const count = payload.count || 20 const { count, reverseOrder } = payload
const isReverseOrder = payload.reverseOrder || true
const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() } const peer = { chatType: ChatType.group, peerUid: payload.group_id.toString() }
let msgList: RawMessage[] | undefined let msgList: RawMessage[] | undefined
// 包含 message_seq 0 // 包含 message_seq 0
@@ -35,7 +40,7 @@ export class GetGroupMsgHistory extends BaseAction<Payload, Response> {
msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, +count)).msgList msgList = (await this.ctx.ntMsgApi.getMsgHistory(peer, startMsgId, +count)).msgList
} }
if (!msgList?.length) throw new Error('未找到消息') if (!msgList?.length) throw new Error('未找到消息')
if (isReverseOrder) msgList.reverse() if (reverseOrder) msgList.reverse()
await Promise.all( await Promise.all(
msgList.map(async msg => { msgList.map(async msg => {
msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId) msg.msgShortId = MessageUnique.createMsg({ chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId)

View File

@@ -1,4 +1,4 @@
import { BaseAction } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { OB11GroupFile, OB11GroupFileFolder } from '../../types' import { OB11GroupFile, OB11GroupFileFolder } from '../../types'
@@ -14,18 +14,19 @@ interface Response {
export class GetGroupRootFiles extends BaseAction<Payload, Response> { export class GetGroupRootFiles extends BaseAction<Payload, Response> {
actionName = ActionName.GoCQHTTP_GetGroupRootFiles 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) { async _handle(payload: Payload) {
const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), { const data = await this.ctx.ntGroupApi.getGroupFileList(payload.group_id.toString(), {
sortType: 1, sortType: 1,
fileCount: +(payload.file_count ?? 50), fileCount: +payload.file_count,
startIndex: 0, startIndex: 0,
sortOrder: 2, sortOrder: 2,
showOnlinedocFolder: 0, showOnlinedocFolder: 0,
}) })
this.ctx.logger.info(data)
return { return {
files: data.filter(item => item.fileInfo) files: data.filter(item => item.fileInfo)
.map(item => { .map(item => {

View File

@@ -1,4 +1,4 @@
import { BaseAction } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { OB11User } from '../../types' import { OB11User } from '../../types'
import { OB11Entities } from '../../entities' import { OB11Entities } from '../../entities'
import { ActionName } from '../types' import { ActionName } from '../types'
@@ -12,6 +12,9 @@ interface Payload {
export class GetStrangerInfo extends BaseAction<Payload, OB11User> { export class GetStrangerInfo extends BaseAction<Payload, OB11User> {
actionName = ActionName.GoCQHTTP_GetStrangerInfo actionName = ActionName.GoCQHTTP_GetStrangerInfo
payloadSchema = Schema.object({
user_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload): Promise<OB11User> { protected async _handle(payload: Payload): Promise<OB11User> {
if (!(getBuildVersion() >= 26702)) { if (!(getBuildVersion() >= 26702)) {

View File

@@ -1,4 +1,4 @@
import { BaseAction } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { MessageUnique } from '@/common/utils/messageUnique' import { MessageUnique } from '@/common/utils/messageUnique'
@@ -8,11 +8,11 @@ interface Payload {
export class MarkMsgAsRead extends BaseAction<Payload, null> { export class MarkMsgAsRead extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_MarkMsgAsRead actionName = ActionName.GoCQHTTP_MarkMsgAsRead
payloadSchema = Schema.object({
message_id: Schema.union([Number, String]).required()
})
protected async _handle(payload: Payload) { protected async _handle(payload: Payload) {
if (!payload.message_id) {
throw new Error('参数 message_id 不能为空')
}
const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id) const msg = await MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id)
if (!msg) { if (!msg) {
throw new Error('msg not found') throw new Error('msg not found')

View File

@@ -9,6 +9,7 @@ interface Payload {
export class HandleQuickOperation extends BaseAction<Payload, null> { export class HandleQuickOperation extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_HandleQuickOperation actionName = ActionName.GoCQHTTP_HandleQuickOperation
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e)) handleQuickOperation(this.ctx, payload.context, payload.operation).catch(e => this.ctx.logger.error(e))
return null return null

View File

@@ -1,20 +1,180 @@
import SendMsg from '../msg/SendMsg' import { unlink } from 'node:fs/promises'
import { OB11PostSendMsg } from '../../types' import { OB11MessageNode } from '../../types'
import { ActionName } 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<Payload, Response> {
actionName = ActionName.GoCQHTTP_SendForwardMsg 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) { protected async _handle(payload: Payload) {
if (payload.messages) payload.message = payload.messages let contextMode = CreatePeerMode.Normal
return super._handle(payload) 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<RawMessage | undefined> {
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 { export class SendPrivateForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg
protected _handle(payload: Payload) {
payload.message_type = 'private'
return super._handle(payload)
}
} }
export class SendGroupForwardMsg extends SendForwardMsg { export class SendGroupForwardMsg extends SendForwardMsg {
actionName = ActionName.GoCQHTTP_SendGroupForwardMsg actionName = ActionName.GoCQHTTP_SendGroupForwardMsg
protected _handle(payload: Payload) {
payload.message_type = 'group'
return super._handle(payload)
}
} }

View File

@@ -1,4 +1,4 @@
import { BaseAction } from '../BaseAction' import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { unlink } from 'fs/promises' import { unlink } from 'fs/promises'
import { checkFileReceived, uri2local } from '@/common/utils/file' import { checkFileReceived, uri2local } from '@/common/utils/file'
@@ -7,20 +7,24 @@ interface Payload {
group_id: number | string group_id: number | string
content: string content: string
image?: string image?: string
pinned?: number | string //扩展 pinned: number | string //扩展
confirm_required?: number | string //扩展 confirm_required: number | string //扩展
} }
export class SendGroupNotice extends BaseAction<Payload, null> { export class SendGroupNotice extends BaseAction<Payload, null> {
actionName = ActionName.GoCQHTTP_SendGroupNotice 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) { async _handle(payload: Payload) {
if (!payload.content) {
throw new Error('参数 content 不能为空')
}
const groupCode = payload.group_id.toString() const groupCode = payload.group_id.toString()
const pinned = Number(payload.pinned ?? 0) const pinned = +payload.pinned
const confirmRequired = Number(payload.confirm_required ?? 1) const confirmRequired = +payload.confirm_required
let picInfo: { id: string, width: number, height: number } | undefined let picInfo: { id: string, width: number, height: number } | undefined
if (payload.image) { if (payload.image) {

View File

@@ -13,11 +13,16 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
actionName = ActionName.GetGroupMemberList actionName = ActionName.GetGroupMemberList
protected async _handle(payload: Payload) { 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()) const groupMembersArr = Array.from(groupMembers.values())
let _groupMembers = groupMembersArr.map(item => { const _groupMembers = groupMembersArr.map(item => {
return OB11Entities.groupMember(payload.group_id.toString(), item) return OB11Entities.groupMember(groupCode, item)
}) })
const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>() const MemberMap: Map<number, OB11GroupMember> = new Map<number, OB11GroupMember>()
@@ -25,8 +30,8 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
for (let i = 0, len = _groupMembers.length; i < len; i++) { for (let i = 0, len = _groupMembers.length; i < len; i++) {
// 保证基础数据有这个 同时避免群管插件过于依赖这个杀了 // 保证基础数据有这个 同时避免群管插件过于依赖这个杀了
_groupMembers[i].join_time = date _groupMembers[i].join_time ||= date
_groupMembers[i].last_sent_time = date _groupMembers[i].last_sent_time ||= date
MemberMap.set(_groupMembers[i].user_id, _groupMembers[i]) MemberMap.set(_groupMembers[i].user_id, _groupMembers[i])
} }
@@ -34,24 +39,24 @@ class GetGroupMemberList extends BaseAction<Payload, OB11GroupMember[]> {
const isPrivilege = selfRole === 3 || selfRole === 4 const isPrivilege = selfRole === 3 || selfRole === 4
if (isPrivilege) { 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++) { for (let i = 0, len = webGroupMembers.length; i < len; i++) {
if (!webGroupMembers[i]?.uin) { if (!webGroupMembers[i]?.uin) {
continue continue
} }
const MemberData = MemberMap.get(webGroupMembers[i]?.uin) const MemberData = MemberMap.get(webGroupMembers[i]?.uin)
if (MemberData) { if (MemberData) {
MemberData.join_time = webGroupMembers[i]?.join_time if (MemberData.join_time === date) {
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time MemberData.join_time = webGroupMembers[i]?.join_time
MemberData.last_sent_time = webGroupMembers[i]?.last_speak_time
}
MemberData.qage = webGroupMembers[i]?.qage MemberData.qage = webGroupMembers[i]?.qage
MemberData.level = webGroupMembers[i]?.lv.level.toString()
MemberMap.set(webGroupMembers[i]?.uin, MemberData) MemberMap.set(webGroupMembers[i]?.uin, MemberData)
} }
} }
} }
_groupMembers = Array.from(MemberMap.values()) return Array.from(MemberMap.values())
return _groupMembers
} }
} }

View File

@@ -6,7 +6,6 @@ class SendGroupMsg extends SendMsg {
actionName = ActionName.SendGroupMsg actionName = ActionName.SendGroupMsg
protected _handle(payload: OB11PostSendMsg) { protected _handle(payload: OB11PostSendMsg) {
delete (payload as Partial<OB11PostSendMsg>).user_id
payload.message_type = 'group' payload.message_type = 'group'
return super._handle(payload) return super._handle(payload)
} }

View File

@@ -53,7 +53,7 @@ import { GetGroupHonorInfo } from './group/GetGroupHonorInfo'
import { HandleQuickOperation } from './go-cqhttp/QuickOperation' import { HandleQuickOperation } from './go-cqhttp/QuickOperation'
import { SetEssenceMsg } from './go-cqhttp/SetEssenceMsg' import { SetEssenceMsg } from './go-cqhttp/SetEssenceMsg'
import { DelEssenceMsg } from './go-cqhttp/DelEssenceMsg' import { DelEssenceMsg } from './go-cqhttp/DelEssenceMsg'
import GetEvent from './llonebot/GetEvent' import { GetEvent } from './llonebot/GetEvent'
import { DelGroupFile } from './go-cqhttp/DelGroupFile' import { DelGroupFile } from './go-cqhttp/DelGroupFile'
import { GetGroupSystemMsg } from './go-cqhttp/GetGroupSystemMsg' import { GetGroupSystemMsg } from './go-cqhttp/GetGroupSystemMsg'
import { CreateGroupFileFolder } from './go-cqhttp/CreateGroupFileFolder' import { CreateGroupFileFolder } from './go-cqhttp/CreateGroupFileFolder'
@@ -63,6 +63,10 @@ import { GetGroupRootFiles } from './go-cqhttp/GetGroupRootFiles'
import { SetOnlineStatus } from './llonebot/SetOnlineStatus' import { SetOnlineStatus } from './llonebot/SetOnlineStatus'
import { SendGroupNotice } from './go-cqhttp/SendGroupNotice' import { SendGroupNotice } from './go-cqhttp/SendGroupNotice'
import { GetProfileLike } from './llonebot/GetProfileLike' 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) { export function initActionMap(adapter: Adapter) {
const actionHandlers = [ const actionHandlers = [
@@ -76,6 +80,9 @@ export function initActionMap(adapter: Adapter) {
new GetEvent(adapter), new GetEvent(adapter),
new SetOnlineStatus(adapter), new SetOnlineStatus(adapter),
new GetProfileLike(adapter), new GetProfileLike(adapter),
new GetFriendMsgHistory(adapter),
new FetchEmojiLike(adapter),
new FetchCustomFace(adapter),
// onebot11 // onebot11
new SendLike(adapter), new SendLike(adapter),
new GetMsg(adapter), new GetMsg(adapter),
@@ -109,7 +116,7 @@ export function initActionMap(adapter: Adapter) {
new SetMsgEmojiLike(adapter), new SetMsgEmojiLike(adapter),
new ForwardFriendSingleMsg(adapter), new ForwardFriendSingleMsg(adapter),
new ForwardGroupSingleMsg(adapter), new ForwardGroupSingleMsg(adapter),
//以下为go-cqhttp api // go-cqhttp
new GetGroupEssence(adapter), new GetGroupEssence(adapter),
new GetGroupHonorInfo(adapter), new GetGroupHonorInfo(adapter),
new SendForwardMsg(adapter), new SendForwardMsg(adapter),
@@ -132,7 +139,8 @@ export function initActionMap(adapter: Adapter) {
new DelGroupFolder(adapter), new DelGroupFolder(adapter),
new GetGroupAtAllRemain(adapter), new GetGroupAtAllRemain(adapter),
new GetGroupRootFiles(adapter), new GetGroupRootFiles(adapter),
new SendGroupNotice(adapter) new SendGroupNotice(adapter),
new GetGroupFilesByFolder(adapter),
] ]
const actionMap = new Map<string, BaseAction<any, unknown>>() const actionMap = new Map<string, BaseAction<any, unknown>>()
for (const action of actionHandlers) { for (const action of actionHandlers) {

View File

@@ -0,0 +1,18 @@
import { BaseAction, Schema } from '../BaseAction'
import { ActionName } from '../types'
interface Payload {
count: number | string
}
export class FetchCustomFace extends BaseAction<Payload, string[]> {
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)
}
}

View File

@@ -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<Payload, Dict> {
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)
}
}

View File

@@ -11,7 +11,7 @@ interface Payload {
timeout: number timeout: number
} }
export default class GetEvent extends BaseAction<Payload, PostEventType[]> { export class GetEvent extends BaseAction<Payload, PostEventType[]> {
actionName = ActionName.GetEvent actionName = ActionName.GetEvent
protected async _handle(payload: Payload): Promise<PostEventType[]> { protected async _handle(payload: Payload): Promise<PostEventType[]> {

View File

@@ -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<Payload, Response> {
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<Response> {
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) }
}
}

View File

@@ -1,25 +1,14 @@
import {
ChatType,
ElementType,
RawMessage,
SendMessageElement,
} from '@/ntqqapi/types'
import { import {
OB11MessageCustomMusic, OB11MessageCustomMusic,
OB11MessageData, OB11MessageData,
OB11MessageDataType, OB11MessageDataType,
OB11MessageJson, OB11MessageJson,
OB11MessageMusic, OB11MessageMusic,
OB11MessageNode,
OB11PostSendMsg, OB11PostSendMsg,
} from '../../types' } from '../../types'
import fs from 'node:fs'
import { BaseAction } from '../BaseAction' import { BaseAction } from '../BaseAction'
import { ActionName } from '../types' import { ActionName } from '../types'
import { CustomMusicSignPostData, IdMusicSignPostData, MusicSign, MusicSignPostData } from '@/common/utils/sign' 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' import { convertMessage2List, createSendElements, sendMsg, createPeer, CreatePeerMode } from '../../helper/createMessage'
interface ReturnData { interface ReturnData {
@@ -42,12 +31,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnData> {
payload.auto_escape === true || payload.auto_escape === 'true', payload.auto_escape === true || payload.auto_escape === 'true',
) )
if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) { if (this.getSpecialMsgNum(messages, OB11MessageDataType.node)) {
try { throw new Error('请使用 /send_group_forward_msg 或 /send_private_forward_msg 进行合并转发')
const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[])
return { message_id: returnMsg.msgShortId! }
} catch (e) {
throw '发送转发消息失败 ' + e
}
} }
else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) { else if (this.getSpecialMsgNum(messages, OB11MessageDataType.music)) {
const music = messages[0] as OB11MessageMusic const music = messages[0] as OB11MessageMusic
@@ -114,140 +98,10 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnData> {
private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number { private getSpecialMsgNum(message: OB11MessageData[], msgType: OB11MessageDataType): number {
if (Array.isArray(message)) { if (Array.isArray(message)) {
return message.filter((msg) => msg.type == msgType).length return message.filter((msg) => msg.type === msgType).length
} }
return 0 return 0
} }
private async cloneMsg(msg: RawMessage): Promise<RawMessage | undefined> {
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 export default SendMsg

View File

@@ -21,6 +21,9 @@ export enum ActionName {
GetEvent = 'get_event', GetEvent = 'get_event',
SetOnlineStatus = 'set_online_status', SetOnlineStatus = 'set_online_status',
GetProfileLike = 'get_profile_like', GetProfileLike = 'get_profile_like',
FetchEmojiLike = 'fetch_emoji_like',
FetchCustomFace = 'fetch_custom_face',
GetFriendMsgHistory = 'get_friend_msg_history',
// onebot 11 // onebot 11
SendLike = 'send_like', SendLike = 'send_like',
GetLoginInfo = 'get_login_info', GetLoginInfo = 'get_login_info',
@@ -78,4 +81,5 @@ export enum ActionName {
GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain', GoCQHTTP_GetGroupAtAllRemain = 'get_group_at_all_remain',
GoCQHTTP_GetGroupRootFiles = 'get_group_root_files', GoCQHTTP_GetGroupRootFiles = 'get_group_root_files',
GoCQHTTP_SendGroupNotice = '_send_group_notice', GoCQHTTP_SendGroupNotice = '_send_group_notice',
GoCQHTTP_GetGroupFilesByFolder = 'get_group_files_by_folder'
} }

View File

@@ -12,7 +12,16 @@ export default class SetFriendAddRequest extends BaseAction<Payload, null> {
protected async _handle(payload: Payload): Promise<null> { protected async _handle(payload: Payload): Promise<null> {
const approve = payload.approve?.toString() !== 'false' 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 return null
} }
} }

View File

@@ -226,20 +226,16 @@ class OneBot11Adapter extends Service {
} }
} }
private handleRecallMsg(msgList: RawMessage[]) { private handleRecallMsg(message: RawMessage) {
for (const message of msgList) { const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId)
if (message.recallTime != '0') { if (!oriMessageId) {
const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId) return
if (!oriMessageId) {
continue
}
OB11Entities.recallEvent(this.ctx, message, oriMessageId).then((recallEvent) => {
if (recallEvent) {
this.dispatch(recallEvent)
}
})
}
} }
OB11Entities.recallEvent(this.ctx, message, oriMessageId).then((recallEvent) => {
if (recallEvent) {
this.dispatch(recallEvent)
}
})
} }
private async handleFriendRequest(buddyReqs: FriendRequest[]) { private async handleFriendRequest(buddyReqs: FriendRequest[]) {
@@ -404,7 +400,7 @@ class OneBot11Adapter extends Service {
this.handleRecallMsg(input) this.handleRecallMsg(input)
}) })
this.ctx.on('nt/message-sent', input => { this.ctx.on('nt/message-sent', input => {
this.handleMsg(input) this.handleMsg([input])
}) })
this.ctx.on('nt/group-notify', input => { this.ctx.on('nt/group-notify', input => {
this.handleGroupNotify(input) this.handleGroupNotify(input)

View File

@@ -15,7 +15,6 @@ import {
FaceIndex, FaceIndex,
GrayTipElementSubType, GrayTipElementSubType,
Group, Group,
Peer,
GroupMember, GroupMember,
RawMessage, RawMessage,
Sex, Sex,
@@ -155,13 +154,21 @@ export namespace OB11Entities {
guildId: '' guildId: ''
} }
try { try {
const { replayMsgSeq, replyMsgTime, senderUidStr } = replyElement const { replayMsgSeq, replyMsgTime } = replyElement
const records = msg.records.find(msgRecord => msgRecord.msgId === replyElement.sourceMsgIdInRecords) 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('找不到回复消息') throw new Error('找不到回复消息')
} }
const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, replayMsgSeq, replyMsgTime, [senderUidStr]) const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, replayMsgSeq, replyMsgTime, [senderUid])
const replyMsg = msgList.find(msg => msg.msgRandom === records.msgRandom)
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: 合并消息内侧 消息具体定位不到 // 284840486: 合并消息内侧 消息具体定位不到
if (!replyMsg && msg.peerUin !== '284840486') { if (!replyMsg && msg.peerUin !== '284840486') {
@@ -375,29 +382,25 @@ export namespace OB11Entities {
for (const element of msg.elements) { for (const element of msg.elements) {
if (element.grayTipElement) { if (element.grayTipElement) {
const { grayTipElement } = element const { grayTipElement } = element
if (grayTipElement.subElementType === GrayTipElementSubType.JSON) { if (grayTipElement.jsonGrayTipElement?.busiId === '1061') {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr) const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr)
if (grayTipElement.jsonGrayTipElement.busiId === 1061) { const pokedetail: Dict[] = json.items
const pokedetail: Dict[] = json.items //筛选item带有uid的元素
//筛选item带有uid的元素 const poke_uid = pokedetail.filter(item => item.uid)
const poke_uid = pokedetail.filter(item => item.uid) if (poke_uid.length === 2) {
if (poke_uid.length == 2) { return new OB11FriendPokeEvent(
return new OB11FriendPokeEvent( Number(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid)),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[0].uid)), Number(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid)),
parseInt(await ctx.ntUserApi.getUinByUid(poke_uid[1].uid)), pokedetail
pokedetail )
)
}
} }
} }
if (grayTipElement.xmlElement?.templId === '10229') {
const uin = +msg.peerUin || +(await ctx.ntUserApi.getUinByUid(msg.peerUid))
return new OB11FriendAddNoticeEvent(uin)
}
} }
} }
// 好友增加事件
if (msg.msgType === 5 && msg.subMsgType === 12) {
const uin = +msg.peerUin || +(await ctx.ntUserApi.getUinByUid(msg.peerUid))
const event = new OB11FriendAddNoticeEvent(uin)
return event
}
} }
export async function groupEvent(ctx: Context, msg: RawMessage): Promise<OB11GroupNoticeEvent | void> { export async function groupEvent(ctx: Context, msg: RawMessage): Promise<OB11GroupNoticeEvent | void> {
@@ -564,8 +567,8 @@ export namespace OB11Entities {
} }
} }
else if (grayTipElement.subElementType == GrayTipElementSubType.JSON) { else if (grayTipElement.subElementType == GrayTipElementSubType.JSON) {
const json = JSON.parse(grayTipElement.jsonGrayTipElement.jsonStr) const json = JSON.parse(grayTipElement.jsonGrayTipElement!.jsonStr)
if (grayTipElement.jsonGrayTipElement.busiId == 1061) { if (grayTipElement.jsonGrayTipElement?.busiId === '1061') {
const pokedetail: Dict[] = json.items const pokedetail: Dict[] = json.items
//筛选item带有uid的元素 //筛选item带有uid的元素
const poke_uid = pokedetail.filter(item => item.uid) const poke_uid = pokedetail.filter(item => item.uid)
@@ -578,34 +581,29 @@ export namespace OB11Entities {
) )
} }
} }
if (grayTipElement.jsonGrayTipElement.busiId == 2401) { if (grayTipElement.jsonGrayTipElement?.busiId === '2401') {
ctx.logger.info('收到群精华消息', json) ctx.logger.info('收到群精华消息', json)
const searchParams = new URL(json.items[0].jp).searchParams const searchParams = new URL(json.items[0].jp).searchParams
const msgSeq = searchParams.get('msgSeq')! const msgSeq = searchParams.get('msgSeq')
const Group = searchParams.get('groupCode') const groupCode = searchParams.get('groupCode')
const Peer: Peer = { if (!groupCode || !msgSeq) return
const peer = {
guildId: '', guildId: '',
chatType: ChatType.group, chatType: ChatType.group,
peerUid: Group! peerUid: groupCode
} }
const msgList = (await ctx.ntMsgApi.getMsgsBySeqAndCount(Peer, msgSeq.toString(), 1, true, true))?.msgList const essence = await ctx.ntWebApi.findGroupEssenceMsg(groupCode, +msgSeq)
if (!msgList?.length) { if (!essence) return
return const { msgList } = await ctx.ntMsgApi.queryMsgsWithFilterExBySeq(peer, msgSeq, String(essence.sender_time))
}
//const origMsg = await dbUtil.getMsgByLongId(msgList[0].msgId)
//const postMsg = await dbUtil.getMsgBySeqId(origMsg?.msgSeq!) ?? origMsg
// 如果 senderUin 为 0可能是 历史消息 或 自身消息
//if (msgList[0].senderUin === '0') {
//msgList[0].senderUin = postMsg?.senderUin ?? getSelfUin()
//}
return new OB11GroupEssenceEvent( return new OB11GroupEssenceEvent(
parseInt(msg.peerUid), parseInt(msg.peerUid),
MessageUnique.getShortIdByMsgId(msgList[0].msgId)!, MessageUnique.getShortIdByMsgId(msgList[0].msgId)!,
parseInt(msgList[0].senderUin!) parseInt(msgList[0].senderUin),
parseInt(essence.add_digest_uin),
) )
// 获取MsgSeq+Peer可获取具体消息 // 获取MsgSeq+Peer可获取具体消息
} }
if (grayTipElement.jsonGrayTipElement.busiId == 2407) { if (grayTipElement.jsonGrayTipElement?.busiId === '2407') {
const memberUin = json.items[1].param[0] const memberUin = json.items[1].param[0]
const title = json.items[3].txt const title = json.items[3].txt
ctx.logger.info('收到群成员新头衔消息', json) ctx.logger.info('收到群成员新头衔消息', json)
@@ -632,9 +630,9 @@ export namespace OB11Entities {
if (!msgElement) { if (!msgElement) {
return return
} }
const revokeElement = msgElement.grayTipElement.revokeElement const revokeElement = msgElement.grayTipElement!.revokeElement
if (msg.chatType === ChatType.group) { if (msg.chatType === ChatType.group) {
const operator = await ctx.ntGroupApi.getGroupMember(msg.peerUid, revokeElement.operatorUid) const operator = await ctx.ntGroupApi.getGroupMember(msg.peerUid, revokeElement!.operatorUid)
return new OB11GroupRecallNoticeEvent( return new OB11GroupRecallNoticeEvent(
parseInt(msg.peerUid), parseInt(msg.peerUid),
parseInt(msg.senderUin!), parseInt(msg.senderUin!),
@@ -706,10 +704,10 @@ export namespace OB11Entities {
sex: sex(member.sex!), sex: sex(member.sex!),
age: 0, age: 0,
area: '', area: '',
level: '0', level: String(member.memberLevel ?? 0),
qq_level: (member.qqLevel && calcQQLevel(member.qqLevel)) || 0, qq_level: (member.qqLevel && calcQQLevel(member.qqLevel)) || 0,
join_time: 0, // 暂时没法获取 join_time: member.joinTime,
last_sent_time: 0, // 暂时没法获取 last_sent_time: member.lastSpeakTime,
title_expire_time: 0, title_expire_time: 0,
unfriendly: false, unfriendly: false,
card_changeable: true, card_changeable: true,

View File

@@ -1,16 +1,20 @@
import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'; import { OB11GroupNoticeEvent } from './OB11GroupNoticeEvent'
export class OB11GroupEssenceEvent extends OB11GroupNoticeEvent { export class OB11GroupEssenceEvent extends OB11GroupNoticeEvent {
notice_type = 'essence' notice_type = 'essence'
message_id: number message_id: number
sender_id: number sender_id: number
sub_type: 'add' | 'delete' = 'add' sub_type: 'add' | 'delete' = 'add'
group_id: number group_id: number
user_id: number = 0 user_id: number
operator_id: number
constructor(groupId: number, message_id: number, sender_id: number) { constructor(groupId: number, messageId: number, senderId: number, operatorId: number) {
super() super()
this.group_id = groupId this.group_id = groupId
this.message_id = message_id this.user_id = senderId
this.sender_id = sender_id this.message_id = messageId
this.sender_id = senderId
this.operator_id = operatorId
} }
} }

View File

@@ -112,8 +112,16 @@ async function handleMsg(ctx: Context, msg: OB11Message, quickAction: QuickOpera
async function handleFriendRequest(ctx: Context, request: OB11FriendRequestEvent, quickAction: QuickOperationFriendRequest) { async function handleFriendRequest(ctx: Context, request: OB11FriendRequestEvent, quickAction: QuickOperationFriendRequest) {
if (!isNullable(quickAction.approve)) { if (!isNullable(quickAction.approve)) {
// todo: set remark const data = request.flag.split('|')
ctx.ntFriendApi.handleFriendRequest(request.flag, quickAction.approve).catch(e => ctx.logger.error(e)) 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))
}
} }
} }

View File

@@ -308,8 +308,8 @@ export type OB11MessageData =
export interface OB11PostSendMsg { export interface OB11PostSendMsg {
message_type?: 'private' | 'group' message_type?: 'private' | 'group'
user_id: string user_id?: string | number
group_id?: string group_id?: string | number
message: OB11MessageMixType message: OB11MessageMixType
messages?: OB11MessageMixType // 兼容 go-cqhttp messages?: OB11MessageMixType // 兼容 go-cqhttp
auto_escape?: boolean | string auto_escape?: boolean | string

View File

@@ -1 +1 @@
export const version = '3.32.6' export const version = '3.33.1'

View File

@@ -15,7 +15,7 @@
"./src/common/*" "./src/common/*"
], ],
"@/onebot11/*": [ "@/onebot11/*": [
"./src/onebot11" "./src/onebot11/*"
], ],
"@/ntqqapi/*": [ "@/ntqqapi/*": [
"./src/ntqqapi/*" "./src/ntqqapi/*"
@@ -27,4 +27,4 @@
"src", "src",
"scripts" "scripts"
] ]
} }