Merge pull request #337 from LLOneBot/dev

3.29.1
This commit is contained in:
idranme 2024-08-14 19:00:42 +08:00 committed by GitHub
commit 26fc0c68b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 117 additions and 83 deletions

View File

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

View File

@ -41,11 +41,10 @@ export interface LLOneBotError {
export interface FileCache { export interface FileCache {
fileName: string fileName: string
filePath: string
fileSize: string fileSize: string
fileUuid?: string msgId: string
url?: string peerUid: string
msgId?: string chatType: number
elementId: string elementId: string
downloadFunc?: () => Promise<void> elementType: number
} }

View File

@ -34,7 +34,7 @@ export class NTEventWrapper {
if (typeof target[prop] === 'undefined') { if (typeof target[prop] === 'undefined') {
// 如果方法不存在返回一个函数这个函数调用existentMethod // 如果方法不存在返回一个函数这个函数调用existentMethod
return (...args: any[]) => { return (...args: any[]) => {
current.DispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then() current.dispatcherListener.apply(current, [ListenerMainName, prop, ...args]).then()
} }
} }
// 如果方法存在,正常返回 // 如果方法存在,正常返回
@ -48,7 +48,7 @@ export class NTEventWrapper {
this.WrapperSession = WrapperSession this.WrapperSession = WrapperSession
} }
CreatEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined { createEventFunction<T extends (...args: any) => any>(eventName: string): T | undefined {
const eventNameArr = eventName.split('/') const eventNameArr = eventName.split('/')
type eventType = { type eventType = {
[key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> } [key: string]: () => { [key: string]: (...params: Parameters<T>) => Promise<ReturnType<T>> }
@ -69,16 +69,14 @@ export class NTEventWrapper {
} }
} }
createEventFunction = this.CreatEventFunction createListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
CreatListenerFunction<T>(listenerMainName: string, uniqueCode: string = ''): T {
const ListenerType = this.ListenerMap![listenerMainName] const ListenerType = this.ListenerMap![listenerMainName]
let Listener = this.ListenerManger.get(listenerMainName + uniqueCode) let Listener = this.ListenerManger.get(listenerMainName + uniqueCode)
if (!Listener && ListenerType) { if (!Listener && ListenerType) {
Listener = new ListenerType(this.createProxyDispatch(listenerMainName)) Listener = new ListenerType(this.createProxyDispatch(listenerMainName))
const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1] const ServiceSubName = listenerMainName.match(/^NodeIKernel(.*?)Listener$/)![1]
const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener' const Service = 'NodeIKernel' + ServiceSubName + 'Service/addKernel' + ServiceSubName + 'Listener'
const addfunc = this.CreatEventFunction<(listener: T) => number>(Service) const addfunc = this.createEventFunction<(listener: T) => number>(Service)
addfunc!(Listener as T) addfunc!(Listener as T)
//console.log(addfunc!(Listener as T)) //console.log(addfunc!(Listener as T))
this.ListenerManger.set(listenerMainName + uniqueCode, Listener) this.ListenerManger.set(listenerMainName + uniqueCode, Listener)
@ -87,7 +85,7 @@ export class NTEventWrapper {
} }
//统一回调清理事件 //统一回调清理事件
async DispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) { async dispatcherListener(ListenerMainName: string, ListenerSubName: string, ...args: any[]) {
//console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args) //console.log("[EventDispatcher]",ListenerMainName, ListenerSubName, ...args)
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => { this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.forEach((task, uuid) => {
//console.log(task.func, uuid, task.createtime, task.timeout) //console.log(task.func, uuid, task.createtime, task.timeout)
@ -103,7 +101,7 @@ export class NTEventWrapper {
async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) { async CallNoListenerEvent<EventType extends (...args: any[]) => Promise<any> | any>(EventName = '', timeout: number = 3000, ...args: Parameters<EventType>) {
return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => { return new Promise<Awaited<ReturnType<EventType>>>(async (resolve, reject) => {
const EventFunc = this.CreatEventFunction<EventType>(EventName) const EventFunc = this.createEventFunction<EventType>(EventName)
let complete = false let complete = false
const Timeouter = setTimeout(() => { const Timeouter = setTimeout(() => {
if (!complete) { if (!complete) {
@ -152,7 +150,7 @@ export class NTEventWrapper {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()) this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
} }
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak) this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
this.CreatListenerFunction(ListenerMainName) this.createListenerFunction(ListenerMainName)
}) })
} }
@ -198,8 +196,8 @@ export class NTEventWrapper {
this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map()) this.EventTask.get(ListenerMainName)?.set(ListenerSubName, new Map())
} }
this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak) this.EventTask.get(ListenerMainName)?.get(ListenerSubName)?.set(id, eventCallbak)
this.CreatListenerFunction(ListenerMainName) this.createListenerFunction(ListenerMainName)
const EventFunc = this.CreatEventFunction<EventType>(EventName) const EventFunc = this.createEventFunction<EventType>(EventName)
retEvent = await EventFunc!(...(args as any[])) retEvent = await EventFunc!(...(args as any[]))
}) })
} }

View File

@ -7,6 +7,7 @@ import SQLite from '@minatojs/driver-sqlite'
import fsPromise from 'node:fs/promises' import fsPromise from 'node:fs/promises'
import fs from 'node:fs' import fs from 'node:fs'
import path from 'node:path' import path from 'node:path'
import { FileCache } from '../types'
interface SQLiteTables extends Tables { interface SQLiteTables extends Tables {
message: { message: {
@ -15,6 +16,7 @@ interface SQLiteTables extends Tables {
chatType: number chatType: number
peerUid: string peerUid: string
} }
file: FileCache
} }
interface MsgIdAndPeerByShortId { interface MsgIdAndPeerByShortId {
@ -50,6 +52,17 @@ class MessageUniqueWrapper {
}, { }, {
primary: 'shortId' primary: 'shortId'
}) })
database.extend('file', {
fileName: 'string',
fileSize: 'string',
msgId: 'string(24)',
peerUid: 'string(24)',
chatType: 'unsigned',
elementId: 'string(24)',
elementType: 'unsigned',
}, {
primary: 'fileName'
})
this.db = database this.db = database
} }
@ -128,6 +141,14 @@ class MessageUniqueWrapper {
this.msgIdMap.resize(maxSize) this.msgIdMap.resize(maxSize)
this.msgDataMap.resize(maxSize) this.msgDataMap.resize(maxSize)
} }
addFileCache(data: FileCache) {
return this.db?.upsert('file', [data], 'fileName')
}
getFileCache(fileName: string) {
return this.db?.get('file', { fileName })
}
} }
export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper() export const MessageUnique: MessageUniqueWrapper = new MessageUniqueWrapper()

View File

@ -179,7 +179,6 @@ export class NTQQFileApi {
const url: string = element.originImageUrl! // 没有域名 const url: string = element.originImageUrl! // 没有域名
const md5HexStr = element.md5HexStr const md5HexStr = element.md5HexStr
const fileMd5 = element.md5HexStr const fileMd5 = element.md5HexStr
const fileUuid = element.fileUuid
if (url) { if (url) {
const UrlParse = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接 const UrlParse = new URL(IMAGE_HTTP_HOST + url) //临时解析拼接

View File

@ -3,9 +3,9 @@ import fsPromise from 'node:fs/promises'
import { getConfigUtil } from '@/common/config' import { getConfigUtil } from '@/common/config'
import { NTQQFileApi, NTQQGroupApi, NTQQUserApi, NTQQFriendApi, NTQQMsgApi } from '@/ntqqapi/api' import { NTQQFileApi, NTQQGroupApi, NTQQUserApi, NTQQFriendApi, NTQQMsgApi } from '@/ntqqapi/api'
import { ActionName } from '../types' import { ActionName } from '../types'
import { RawMessage } from '@/ntqqapi/types'
import { UUIDConverter } from '@/common/utils/helper' import { UUIDConverter } from '@/common/utils/helper'
import { Peer, ChatType, ElementType } from '@/ntqqapi/types' import { Peer, ChatType, ElementType } from '@/ntqqapi/types'
import { MessageUnique } from '@/common/utils/MessageUnique'
export interface GetFilePayload { export interface GetFilePayload {
file: string // 文件名或者fileUuid file: string // 文件名或者fileUuid
@ -51,10 +51,10 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
throw new Error('chattype not support') throw new Error('chattype not support')
} }
const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId]) const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [msgId])
if (msgList.msgList.length == 0) { if (msgList.msgList.length === 0) {
throw new Error('msg not found') throw new Error('msg not found')
} }
const msg = msgList.msgList[0]; const msg = msgList.msgList[0]
const findEle = msg.elements.find(e => e.elementType == ElementType.VIDEO || e.elementType == ElementType.FILE || e.elementType == ElementType.PTT) const findEle = msg.elements.find(e => e.elementType == ElementType.VIDEO || e.elementType == ElementType.FILE || e.elementType == ElementType.PTT)
if (!findEle) { if (!findEle) {
throw new Error('element not found') throw new Error('element not found')
@ -68,7 +68,7 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
file_size: fileSize, file_size: fileSize,
file_name: fileName, file_name: fileName,
} }
if (enableLocalFile2Url) { if (enableLocalFile2Url && downloadPath) {
try { try {
res.base64 = await fsPromise.readFile(downloadPath, 'base64') res.base64 = await fsPromise.readFile(downloadPath, 'base64')
} catch (e) { } catch (e) {
@ -82,33 +82,42 @@ export abstract class GetFileBase extends BaseAction<GetFilePayload, GetFileResp
} }
const NTSearchNameResult = (await NTQQFileApi.searchfile([payload.file])).resultItems const fileCache = await MessageUnique.getFileCache(String(payload.file))
if (NTSearchNameResult.length !== 0) { if (fileCache?.length) {
const MsgId = NTSearchNameResult[0].msgId const downloadPath = await NTQQFileApi.downloadMedia(
let peer: Peer | undefined = undefined fileCache[0].msgId,
if (NTSearchNameResult[0].chatType == ChatType.group) { fileCache[0].chatType,
peer = { chatType: ChatType.group, peerUid: NTSearchNameResult[0].groupChatInfo[0].groupCode } fileCache[0].peerUid,
} fileCache[0].elementId,
if (!peer) { '',
throw new Error('chattype not support') ''
} )
const msgList: RawMessage[] = (await NTQQMsgApi.getMsgsByMsgId(peer, [MsgId]))?.msgList
if (!msgList || msgList.length == 0) {
throw new Error('msg not found')
}
const msg = msgList[0]
const file = msg.elements.filter(e => e.elementType == NTSearchNameResult[0].elemType)
if (file.length == 0) {
throw new Error('file not found')
}
const downloadPath = await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid, file[0].elementId, '', '')
const res: GetFileResponse = { const res: GetFileResponse = {
file: downloadPath, file: downloadPath,
url: downloadPath, url: downloadPath,
file_size: NTSearchNameResult[0].fileSize.toString(), file_size: fileCache[0].fileSize,
file_name: NTSearchNameResult[0].fileName, file_name: fileCache[0].fileName,
} }
if (enableLocalFile2Url) { const peer: Peer = {
chatType: fileCache[0].chatType,
peerUid: fileCache[0].peerUid,
guildId: ''
}
if (fileCache[0].elementType === ElementType.PIC) {
const msgList = await NTQQMsgApi.getMsgsByMsgId(peer, [fileCache[0].msgId])
if (msgList.msgList.length === 0) {
throw new Error('msg not found')
}
const msg = msgList.msgList[0]
const findEle = msg.elements.find(e => e.elementId === fileCache[0].elementId)
if (!findEle) {
throw new Error('element not found')
}
res.url = await NTQQFileApi.getImageUrl(findEle.picElement)
} else if (fileCache[0].elementType === ElementType.VIDEO) {
res.url = await NTQQFileApi.getVideoUrl(peer, fileCache[0].msgId, fileCache[0].elementId)
}
if (enableLocalFile2Url && downloadPath && res.file === res.url) {
try { try {
res.base64 = await fsPromise.readFile(downloadPath, 'base64') res.base64 = await fsPromise.readFile(downloadPath, 'base64')
} catch (e) { } catch (e) {

View File

@ -3,4 +3,11 @@ import { ActionName } from '../types'
export default class GetImage extends GetFileBase { export default class GetImage extends GetFileBase {
actionName = ActionName.GetImage actionName = ActionName.GetImage
protected async _handle(payload: { file: string }) {
if (!payload.file) {
throw new Error('参数 file 不能为空')
}
return super._handle(payload)
}
} }

View File

@ -184,21 +184,30 @@ export class OB11Constructor {
} }
else if (element.picElement) { else if (element.picElement) {
message_data['type'] = OB11MessageDataType.image message_data['type'] = OB11MessageDataType.image
let fileName = element.picElement.fileName const { picElement } = element
const isGif = element.picElement.picType === PicType.gif /*let fileName = picElement.fileName
const isGif = picElement.picType === PicType.gif
if (isGif && !fileName.endsWith('.gif')) { if (isGif && !fileName.endsWith('.gif')) {
fileName += '.gif' fileName += '.gif'
} }*/
message_data['data']['file'] = fileName message_data['data']['file'] = picElement.fileName
message_data['data']['subType'] = element.picElement.picSubType message_data['data']['subType'] = picElement.picSubType
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId) message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId)
message_data['data']['url'] = await NTQQFileApi.getImageUrl(element.picElement) message_data['data']['url'] = await NTQQFileApi.getImageUrl(picElement)
message_data['data']['file_size'] = element.picElement.fileSize message_data['data']['file_size'] = picElement.fileSize
MessageUnique.addFileCache({
peerUid: msg.peerUid,
msgId: msg.msgId,
chatType: msg.chatType,
elementId: element.elementId,
elementType: element.elementType,
fileName: picElement.fileName,
fileSize: String(picElement.fileSize || '0'),
})
} }
else if (element.videoElement || element.fileElement) { else if (element.videoElement || element.fileElement) {
const videoOrFileElement = element.videoElement || element.fileElement const videoOrFileElement = element.videoElement || element.fileElement
const ob11MessageDataType = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file message_data['type'] = element.videoElement ? OB11MessageDataType.video : OB11MessageDataType.file
message_data['type'] = ob11MessageDataType
message_data['data']['file'] = videoOrFileElement.fileName message_data['data']['file'] = videoOrFileElement.fileName
message_data['data']['path'] = videoOrFileElement.filePath message_data['data']['path'] = videoOrFileElement.filePath
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId) message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId)
@ -210,40 +219,32 @@ export class OB11Constructor {
}, msg.msgId, element.elementId, }, msg.msgId, element.elementId,
) )
} }
NTQQFileApi.addFileCache( MessageUnique.addFileCache({
{
peerUid: msg.peerUid, peerUid: msg.peerUid,
msgId: msg.msgId,
chatType: msg.chatType, chatType: msg.chatType,
guildId: '', elementId: element.elementId,
}, elementType: element.elementType,
msg.msgId, fileName: videoOrFileElement.fileName,
msg.msgSeq, fileSize: String(videoOrFileElement.fileSize || '0')
msg.senderUid, })
element.elementId,
element.elementType.toString(),
videoOrFileElement.fileSize || '0',
videoOrFileElement.fileName,
)
} }
else if (element.pttElement) { else if (element.pttElement) {
message_data['type'] = OB11MessageDataType.voice message_data['type'] = OB11MessageDataType.voice
message_data['data']['file'] = element.pttElement.fileName const { pttElement } = element
message_data['data']['path'] = element.pttElement.filePath message_data['data']['file'] = pttElement.fileName
message_data['data']['path'] = pttElement.filePath
message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId) message_data['data']['file_id'] = UUIDConverter.encode(msg.peerUin, msg.msgId)
message_data['data']['file_size'] = element.pttElement.fileSize message_data['data']['file_size'] = pttElement.fileSize
NTQQFileApi.addFileCache({ MessageUnique.addFileCache({
peerUid: msg.peerUid, peerUid: msg.peerUid,
msgId: msg.msgId,
chatType: msg.chatType, chatType: msg.chatType,
guildId: '', elementId: element.elementId,
}, elementType: element.elementType,
msg.msgId, fileName: pttElement.fileName,
msg.msgSeq, fileSize: String(pttElement.fileSize || '0')
msg.senderUid, })
element.elementId,
element.elementType.toString(),
element.pttElement.fileSize || '0',
element.pttElement.fileUuid || '',
)
} }
else if (element.arkElement) { else if (element.arkElement) {
message_data['type'] = OB11MessageDataType.json message_data['type'] = OB11MessageDataType.json

View File

@ -1 +1 @@
export const version = '3.29.0' export const version = '3.29.1'