diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts index e327522e..b9f52d1b 100644 --- a/src/core/apis/file.ts +++ b/src/core/apis/file.ts @@ -8,6 +8,11 @@ import { IMAGE_HTTP_HOST_NT, Peer, PicElement, + PicType, + SendFileElement, + SendPicElement, + SendPttElement, + SendVideoElement, } from '@/core/entities'; import path from 'path'; import fs from 'fs'; @@ -18,7 +23,12 @@ import imageSize from 'image-size'; import { ISizeCalculationResult } from 'image-size/dist/types/interface'; import { NodeIKernelSearchService } from '../services/NodeIKernelSearchService'; import { RkeyManager } from '../helper/rkey'; -import { calculateFileMD5 } from '@/common/utils/file'; +import { calculateFileMD5, isGIF } from '@/common/utils/file'; +import pathLib from 'node:path'; +import { defaultVideoThumbB64, getVideoInfo } from '@/common/utils/video'; +import ffmpeg from 'fluent-ffmpeg'; +import fsnormal from 'node:fs'; +import { encodeSilk } from '@/common/utils/audio'; export class NTQQFileApi { @@ -84,6 +94,200 @@ export class NTQQFileApi { }; } + async createValidSendFileElement( + filePath: string, + fileName: string = '', + folderId: string = '' + ): Promise { + const { fileName: _fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.FILE); + if (fileSize === 0) { + throw '文件异常,大小为0'; + } + return { + elementType: ElementType.FILE, + elementId: '', + fileElement: { + fileName: fileName || _fileName, + folderId: folderId, + 'filePath': path!, + 'fileSize': (fileSize).toString(), + }, + }; + } + + async createValidSendPicElement( + picPath: string, + summary: string = '', + subType: 0 | 1 = 0 + ): Promise { + const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(picPath, ElementType.PIC, subType); + if (fileSize === 0) { + throw '文件异常,大小为0'; + } + const imageSize = await this.core.apis.FileApi.getImageSize(picPath); + const picElement: any = { + md5HexStr: md5, + fileSize: fileSize.toString(), + picWidth: imageSize?.width, + picHeight: imageSize?.height, + fileName: fileName, + sourcePath: path, + original: true, + picType: isGIF(picPath) ? PicType.gif : PicType.jpg, + picSubType: subType, + fileUuid: '', + fileSubId: '', + thumbFileSize: 0, + summary, + }; + return { + elementType: ElementType.PIC, + elementId: '', + picElement, + }; + } + + async createValidSendVideoElement( + filePath: string, + fileName: string = '', + diyThumbPath: string = '', + ): Promise { + const logger = this.core.context.logger; + const { fileName: _fileName, path, fileSize, md5 } = await this.core.apis.FileApi.uploadFile(filePath, ElementType.VIDEO); + if (fileSize === 0) { + throw '文件异常,大小为0'; + } + let thumb = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`); + thumb = pathLib.dirname(thumb); + // log("thumb 目录", thumb) + let videoInfo = { + width: 1920, height: 1080, + time: 15, + format: 'mp4', + size: fileSize, + filePath, + }; + try { + videoInfo = await getVideoInfo(path, logger); + //logDebug('视频信息', videoInfo); + } catch (e) { + logger.logError('获取视频信息失败', e); + } + const createThumb = new Promise((resolve, reject) => { + const thumbFileName = `${md5}_0.png`; + const thumbPath = pathLib.join(thumb, thumbFileName); + ffmpeg(filePath) + .on('error', (err) => { + logger.logDebug('获取视频封面失败,使用默认封面', err); + if (diyThumbPath) { + fsPromises.copyFile(diyThumbPath, thumbPath).then(() => { + resolve(thumbPath); + }).catch(reject); + } else { + fsnormal.writeFileSync(thumbPath, Buffer.from(defaultVideoThumbB64, 'base64')); + resolve(thumbPath); + } + }) + .screenshots({ + timestamps: [0], + filename: thumbFileName, + folder: thumb, + size: videoInfo.width + 'x' + videoInfo.height, + }).on('end', () => { + resolve(thumbPath); + }); + }); + const thumbPath = new Map(); + const _thumbPath = await createThumb; + const thumbSize = _thumbPath ? (await fsPromises.stat(_thumbPath)).size : 0; + // log("生成缩略图", _thumbPath) + thumbPath.set(0, _thumbPath); + const thumbMd5 = _thumbPath ? await calculateFileMD5(_thumbPath) : ""; + // "fileElement": { + // "fileMd5": "", + // "fileName": "1.mp4", + // "filePath": "C:\\Users\\nanae\\OneDrive\\Desktop\\1.mp4", + // "fileSize": "1847007", + // "picHeight": 1280, + // "picWidth": 720, + // "picThumbPath": {}, + // "file10MMd5": "", + // "fileSha": "", + // "fileSha3": "", + // "fileUuid": "", + // "fileSubId": "", + // "thumbFileSize": 750 + // } + return { + elementType: ElementType.VIDEO, + elementId: '', + videoElement: { + fileName: fileName || _fileName, + filePath: path, + videoMd5: md5, + thumbMd5, + fileTime: videoInfo.time, + thumbPath: thumbPath, + thumbSize, + thumbWidth: videoInfo.width, + thumbHeight: videoInfo.height, + fileSize: '' + fileSize, + // fileFormat: videotype + // fileUuid: "", + // transferStatus: 0, + // progress: 0, + // invalidState: 0, + // fileSubId: "", + // fileBizId: null, + // originVideoMd5: "", + // fileFormat: 2, + // import_rich_media_context: null, + // sourceVideoCodecFormat: 2 + }, + }; + } + + async createValidSendPttElement(pttPath: string): Promise { + const { + converted, + path: silkPath, + duration, + } = await encodeSilk(pttPath, this.core.NapCatTempPath, this.core.context.logger); + // log("生成语音", silkPath, duration); + if (!silkPath) { + throw '语音转换失败, 请检查语音文件是否正常'; + } + const { md5, fileName, path, fileSize } = await this.core.apis.FileApi.uploadFile(silkPath!, ElementType.PTT); + if (fileSize === 0) { + throw '文件异常,大小为0'; + } + if (converted) { + fsPromises.unlink(silkPath); + } + return { + elementType: ElementType.PTT, + elementId: '', + pttElement: { + fileName: fileName, + filePath: path, + md5HexStr: md5, + fileSize: fileSize, + // duration: Math.max(1, Math.round(fileSize / 1024 / 3)), // 一秒钟大概是3kb大小, 小于1秒的按1秒算 + duration: duration || 1, + formatType: 1, + voiceType: 1, + voiceChangeType: 0, + canConvert2Text: true, + waveAmplitudes: [ + 0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17, + ], + fileSubId: '', + playState: 1, + autoConvertText: 0, + }, + }; + } + async downloadMediaByUuid() { //napCatCore.session.getRichMediaService().downloadFileForFileUuid(); } diff --git a/src/onebot/action/msg/SendMsg/index.ts b/src/onebot/action/msg/SendMsg/index.ts index f0e5a662..4ed5ad49 100644 --- a/src/onebot/action/msg/SendMsg/index.ts +++ b/src/onebot/action/msg/SendMsg/index.ts @@ -9,11 +9,9 @@ import { ActionName, BaseCheckResult } from '@/onebot/action/types'; import fs from 'node:fs'; import fsPromise from 'node:fs/promises'; import { decodeCQCode } from '@/onebot/helper/cqcode'; -import createSendElements from './create-send-elements'; import { MessageUnique } from '@/common/utils/MessageUnique'; -import { ChatType, ElementType, NapCatCore, Peer, SendMessageElement } from '@/core'; +import { ChatType, ElementType, NapCatCore, Peer, RawMessage, SendMessageElement } from '@/core'; import BaseAction from '../../BaseAction'; -import { handleForwardNode } from './handle-forward-node'; export interface ReturnDataType { message_id: number; @@ -34,8 +32,6 @@ export function normalize(message: OB11MessageMixType, autoEscape = false): OB11 ) : Array.isArray(message) ? message : [message]; } -export { createSendElements }; - export async function sendMsg(coreContext: NapCatCore, peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete = true) { const NTQQMsgApi = coreContext.apis.MsgApi; const logger = coreContext.context.logger; @@ -144,9 +140,6 @@ export class SendMsg extends BaseAction { contextMode = ContextMode.Normal; protected async check(payload: OB11PostSendMsg): Promise { - const NTQQGroupApi = this.CoreContext.apis.GroupApi; - const NTQQFriendApi = this.CoreContext.apis.FriendApi; - const NTQQUserApi = this.CoreContext.apis.UserApi; const messages = normalize(payload.message); const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node); if (nodeElementLength > 0 && nodeElementLength != messages.length) { @@ -156,9 +149,9 @@ export class SendMsg extends BaseAction { }; } if (payload.user_id && payload.message_type !== 'group') { - const uid = await NTQQUserApi.getUidByUinV2(payload.user_id.toString()); - const isBuddy = await NTQQFriendApi.isBuddy(uid!); - //if (!isBuddy) { } + // const uid = await this.CoreContext.apis.UserApi.getUidByUinV2(payload.user_id.toString()); + // const isBuddy = await NTQQFriendApi.isBuddy(uid!); + // if (!isBuddy) { } } return { valid: true }; } @@ -174,7 +167,7 @@ export class SendMsg extends BaseAction { ); if (getSpecialMsgNum(payload, OB11MessageDataType.node)) { - const returnMsg = await handleForwardNode(this.CoreContext, this.OneBotContext, peer, messages as OB11MessageNode[]); + const returnMsg = await this.handleForwardNode(peer, messages as OB11MessageNode[]); if (returnMsg) { const msgShortId = MessageUnique.createMsg({ guildId: '', @@ -194,11 +187,134 @@ export class SendMsg extends BaseAction { } // log("send msg:", peer, sendElements) - const { sendElements, deleteAfterSentFiles } = await createSendElements(this.CoreContext, this.OneBotContext, messages, peer); - //console.log(peer, JSON.stringify(sendElements,null,2)); + const { sendElements, deleteAfterSentFiles } = await this.OneBotContext.apiContext.MsgApi + .createSendElements(messages, peer); const returnMsg = await sendMsg(this.CoreContext, peer, sendElements, deleteAfterSentFiles); return { message_id: returnMsg!.id! }; } + + private async handleForwardNode(destPeer: Peer, messageNodes: OB11MessageNode[]): Promise { + const NTQQMsgApi = this.CoreContext.apis.MsgApi; + const selfPeer = { + chatType: ChatType.KCHATTYPEC2C, + peerUid: this.CoreContext.selfInfo.uid, + }; + let nodeMsgIds: string[] = []; + const logger = this.CoreContext.context.logger; + for (const messageNode of messageNodes) { + const nodeId = messageNode.data.id; + if (nodeId) { + //对Mgsid和OB11ID混用情况兜底 + const nodeMsg = MessageUnique.getMsgIdAndPeerByShortId(parseInt(nodeId)) || MessageUnique.getPeerByMsgId(nodeId); + if (!nodeMsg) { + logger.logError('转发消息失败,未找到消息', nodeId); + continue; + } + nodeMsgIds.push(nodeMsg.MsgId); + } else { + // 自定义的消息 + try { + const OB11Data = normalize(messageNode.data.content); + //筛选node消息 + const isNodeMsg = OB11Data.filter(e => e.type === OB11MessageDataType.node).length;//找到子转发消息 + if (isNodeMsg !== 0) { + if (isNodeMsg !== OB11Data.length) { + logger.logError('子消息中包含非node消息 跳过不合法部分'); + continue; + } + const nodeMsg = await this.handleForwardNode(selfPeer, OB11Data.filter(e => e.type === OB11MessageDataType.node)); + if (nodeMsg) { + nodeMsgIds.push(nodeMsg.msgId); + MessageUnique.createMsg(selfPeer, nodeMsg.msgId); + } + //完成子卡片生成跳过后续 + continue; + } + const { sendElements } = await this.OneBotContext.apiContext.MsgApi + .createSendElements(OB11Data, destPeer); + //拆分消息 + const MixElement = sendElements.filter(element => element.elementType !== ElementType.FILE && element.elementType !== ElementType.VIDEO); + const SingleElement = sendElements.filter(element => element.elementType === ElementType.FILE || element.elementType === ElementType.VIDEO).map(e => [e]); + const AllElement: SendMessageElement[][] = [MixElement, ...SingleElement].filter(e => e !== undefined && e.length !== 0); + const MsgNodeList: Promise[] = []; + for (const sendElementsSplitElement of AllElement) { + MsgNodeList.push(sendMsg(this.CoreContext, selfPeer, sendElementsSplitElement, [], true).catch(_ => new Promise((resolve) => { + resolve(undefined); + }))); + } + (await Promise.allSettled(MsgNodeList)).map((result) => { + if (result.status === 'fulfilled' && result.value) { + nodeMsgIds.push(result.value.msgId); + MessageUnique.createMsg(selfPeer, result.value.msgId); + } + }); + } catch (e) { + logger.logDebug('生成转发消息节点失败', e); + } + } + } + const nodeMsgArray: Array = []; + let srcPeer: Peer | undefined = undefined; + let needSendSelf = false; + //检测是否处于同一个Peer 不在同一个peer则全部消息由自身发送 + for (const msgId of nodeMsgIds) { + const nodeMsgPeer = MessageUnique.getPeerByMsgId(msgId); + if (!nodeMsgPeer) { + logger.logError('转发消息失败,未找到消息', msgId); + continue; + } + const nodeMsg = (await NTQQMsgApi.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); + let retMsgIds: string[] = []; + if (needSendSelf) { + for (const msg of nodeMsgArray) { + if (msg.peerUid === this.CoreContext.selfInfo.uid) continue; + const ClonedMsg = await this.cloneMsg(msg); + if (ClonedMsg) retMsgIds.push(ClonedMsg.msgId); + } + } else { + retMsgIds = nodeMsgIds; + } + if (nodeMsgIds.length === 0) throw Error('转发消息失败,生成节点为空'); + try { + logger.logDebug('开发转发', srcPeer, destPeer, nodeMsgIds); + return await NTQQMsgApi.multiForwardMsg(srcPeer!, destPeer, nodeMsgIds); + } catch (e) { + logger.logError('forward failed', e); + return null; + } + } + + async cloneMsg(msg: RawMessage): Promise { + const selfPeer = { + chatType: ChatType.KCHATTYPEC2C, + peerUid: this.CoreContext.selfInfo.uid, + }; + const logger = this.CoreContext.context.logger; + const NTQQMsgApi = this.CoreContext.apis.MsgApi; + //logDebug('克隆的目标消息', msg); + + const sendElements: SendMessageElement[] = []; + + for (const element of msg.elements) { + sendElements.push(element as SendMessageElement); + } + + if (sendElements.length === 0) { + logger.logDebug('需要clone的消息无法解析,将会忽略掉', msg); + } + try { + return await NTQQMsgApi.sendMsg(selfPeer, sendElements, true); + } catch (e) { + logger.logError(e, '克隆转发消息失败,将忽略本条消息', msg); + } + } } export default SendMsg; diff --git a/src/onebot/api/msg.ts b/src/onebot/api/msg.ts index 2e760dee..47def878 100644 --- a/src/onebot/api/msg.ts +++ b/src/onebot/api/msg.ts @@ -1,10 +1,32 @@ import { UUIDConverter } from '@/common/utils/helper'; import { MessageUnique } from '@/common/utils/MessageUnique'; -import { AtType, ChatType, FaceIndex, MessageElement, NapCatCore, RawMessage } from '@/core'; -import { NapCatOneBot11Adapter, OB11Message, OB11MessageData, OB11MessageDataType } from '@/onebot'; -import { OB11Constructor } from '../helper'; +import { + AtType, + ChatType, + CustomMusicSignPostData, + ElementType, + FaceIndex, + FaceType, + IdMusicSignPostData, + MessageElement, + NapCatCore, + Peer, + RawMessage, + SendMessageElement, +} from '@/core'; +import faceConfig from '@/core/external/face_config.json'; +import { + NapCatOneBot11Adapter, + OB11Message, + OB11MessageData, + OB11MessageDataType, + OB11MessageFileBase, +} from '@/onebot'; +import { OB11Constructor, SendMsgElementConstructor } from '../helper'; import { EventType } from '@/onebot/event/OB11BaseEvent'; import { encodeCQCode } from '@/onebot/helper/cqcode'; +import { uri2local } from '@/common/utils/file'; +import { RequestUtil } from '@/common/utils/request'; type RawToOb11Converters = { [Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: ( @@ -14,6 +36,18 @@ type RawToOb11Converters = { ) => PromiseLike } +type Ob11ToRawConverters = { + [Key in OB11MessageDataType]: ( + sendMsg: Extract, + context: MessageContext, + ) => Promise +} + +export type MessageContext = { + deleteAfterSentFiles: string[], + peer: Peer +} + function keyCanBeParsed(key: string, parser: RawToOb11Converters): key is keyof RawToOb11Converters { return key in parser; } @@ -354,6 +388,249 @@ export class OneBotMsgApi { } }; + ob11ToRawConverters: Ob11ToRawConverters = { + [OB11MessageDataType.text]: async ({ data: { text } }) => ({ + elementType: ElementType.TEXT, + elementId: '', + textElement: { + content: text, + atType: AtType.notAt, + atUid: '', + atTinyId: '', + atNtUid: '', + }, + }), + + [OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => { + if (!context.peer || context.peer.chatType == ChatType.KCHATTYPEC2C) return undefined; + if (atQQ === 'all') return SendMsgElementConstructor.at(this.coreContext, atQQ, atQQ, AtType.atAll, '全体成员'); + const NTQQGroupApi = this.coreContext.apis.GroupApi; + const NTQQUserApi = this.coreContext.apis.UserApi; + const atMember = await NTQQGroupApi.getGroupMember(context.peer.peerUid, atQQ); + if (atMember) { + return SendMsgElementConstructor.at(this.coreContext, atQQ, atMember.uid, AtType.atUser, atMember.nick || atMember.cardName); + } + const uid = await NTQQUserApi.getUidByUinV2(`${atQQ}`); + if (!uid) throw new Error('Get Uid Error'); + const info = await NTQQUserApi.getUserDetailInfo(uid); + return SendMsgElementConstructor.at(this.coreContext, atQQ, uid, AtType.atUser, info.nick || ''); + }, + + [OB11MessageDataType.reply]: async ({ data: { id } }) => { + const replyMsgM = MessageUnique.getMsgIdAndPeerByShortId(parseInt(id)); + if (!replyMsgM) { + this.coreContext.context.logger.logWarn('回复消息不存在', id); + return undefined; + } + const NTQQMsgApi = this.coreContext.apis.MsgApi; + const replyMsg = (await NTQQMsgApi.getMsgsByMsgId( + replyMsgM.Peer, [replyMsgM.MsgId!])).msgList[0]; + return replyMsg ? + SendMsgElementConstructor.reply(this.coreContext, replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!) : + undefined; + }, + + [OB11MessageDataType.face]: async ({ data: { id } }) => { + let parsedFaceId = parseInt(id); + // 从face_config.json中获取表情名称 + const sysFaces = faceConfig.sysface; + // const emojiFaces = faceConfig.emoji; + const face: any = sysFaces.find((systemFace) => systemFace.QSid === parsedFaceId.toString()); + parsedFaceId = parseInt(parsedFaceId.toString()); + // let faceType = parseInt(parsedFaceId.toString().substring(0, 1)); + let faceType = 1; + if (parsedFaceId >= 222) { + faceType = 2; + } + if (face.AniStickerType) { + faceType = 3; + } + return { + elementType: ElementType.FACE, + elementId: '', + faceElement: { + faceIndex: parsedFaceId, + faceType, + faceText: face.QDes, + stickerId: face.AniStickerId, + stickerType: face.AniStickerType, + packId: face.AniStickerPackId, + sourceType: 1, + }, + }; + }, + + [OB11MessageDataType.mface]: async ({ + data: { + emoji_package_id, emoji_id, key, summary, + }, + }) => ({ + elementType: ElementType.MFACE, + marketFaceElement: { + emojiPackageId: emoji_package_id, + emojiId: emoji_id, + key, + faceName: summary || '[商城表情]', + }, + }), + + // File service + [OB11MessageDataType.image]: async (sendMsg, context) => { + const sendPicElement = await this.coreContext.apis.FileApi.createValidSendPicElement( + (await this.handleOb11FileLikeMessage(sendMsg, context)).path, + sendMsg.data.summary, + sendMsg.data.sub_type, + ); + context.deleteAfterSentFiles.push(sendPicElement.picElement.sourcePath); + return sendPicElement; + }, + + [OB11MessageDataType.file]: async (sendMsg, context) => { + const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context); + //logDebug('发送文件', path, fileName); + // context.deleteAfterSentFiles.push(fileName || FileEle.fileElement.filePath); + return await this.coreContext.apis.FileApi.createValidSendFileElement(path, fileName); + }, + + [OB11MessageDataType.video]: async (sendMsg, context) => { + const { path, fileName } = await this.handleOb11FileLikeMessage(sendMsg, context); + + let thumb = sendMsg.data.thumb; + if (thumb) { + const uri2LocalRes = await uri2local(this.coreContext.NapCatTempPath, thumb); + if (uri2LocalRes.success) thumb = uri2LocalRes.path; + } + const videoEle = await this.coreContext.apis.FileApi.createValidSendVideoElement(path, fileName, thumb); + + context.deleteAfterSentFiles.push(videoEle.videoElement.filePath); + return videoEle; + }, + + [OB11MessageDataType.voice]: async (sendMsg, context) => + this.coreContext.apis.FileApi.createValidSendPttElement( + (await this.handleOb11FileLikeMessage(sendMsg, context)).path), + + [OB11MessageDataType.json]: async ({ data: { data } }) => ({ + elementType: ElementType.ARK, + elementId: '', + arkElement: { + bytesData: typeof data === 'string' ? data : JSON.stringify(data), + linkInfo: null, + subElementType: null, + }, + }), + + [OB11MessageDataType.dice]: async () => ({ + elementType: ElementType.FACE, + elementId: '', + faceElement: { + faceIndex: FaceIndex.dice, + faceType: FaceType.dice, + 'faceText': '[骰子]', + 'packId': '1', + 'stickerId': '33', + 'sourceType': 1, + 'stickerType': 2, + // resultId: resultId.toString(), + 'surpriseId': '', + // "randomType": 1, + }, + }), + + [OB11MessageDataType.RPS]: async () => ({ + elementType: ElementType.FACE, + elementId: '', + faceElement: { + 'faceIndex': FaceIndex.RPS, + 'faceText': '[包剪锤]', + 'faceType': 3, + 'packId': '1', + 'stickerId': '34', + 'sourceType': 1, + 'stickerType': 2, + // 'resultId': resultId.toString(), + 'surpriseId': '', + // "randomType": 1, + }, + }), + + // Need signing + [OB11MessageDataType.markdown]: async ({ data: { content } }) => ({ + elementType: ElementType.MARKDOWN, + elementId: '', + markdownElement: { content }, + }), + + [OB11MessageDataType.music]: async ({ data }, context) => { + // 保留, 直到...找到更好的解决方案 + if (data.type === 'custom') { + if (!data.url) { + this.coreContext.context.logger.logError('自定义音卡缺少参数url'); + return undefined; + } + if (!data.audio) { + this.coreContext.context.logger.logError('自定义音卡缺少参数audio'); + return undefined; + } + if (!data.title) { + this.coreContext.context.logger.logError('自定义音卡缺少参数title'); + return undefined; + } + } else { + if (!['qq', '163'].includes(data.type)) { + this.coreContext.context.logger.logError('音乐卡片type错误, 只支持qq、163、custom,当前type:', data.type); + return undefined; + } + if (!data.id) { + this.coreContext.context.logger.logError('音乐卡片缺少参数id'); + return undefined; + } + } + + let postData: IdMusicSignPostData | CustomMusicSignPostData; + if (data.type === 'custom' && data.content) { + const { content, ...others } = data; + postData = { singer: content, ...others }; + } else { + postData = data; + } + // Mlikiowa V2.2.7 Refactor Todo + const signUrl = this.obContext.configLoader.configData.musicSignUrl; + if (!signUrl) { + if (data.type === 'qq') { + //const musicJson = (await SignMusicWrapper(data.id.toString())).data.arkResult.slice(0, -1); + //return SendMsgElementConstructor.ark(musicJson); + } + throw Error('音乐消息签名地址未配置'); + } + try { + const musicJson = await RequestUtil.HttpGetJson(signUrl, 'POST', postData); + return this.ob11ToRawConverters.json(musicJson, context); + } catch (e) { + this.coreContext.context.logger.logError('生成音乐消息失败', e); + } + }, + + [OB11MessageDataType.node]: async () => undefined, + + [OB11MessageDataType.forward]: async () => undefined, + + [OB11MessageDataType.xml]: async () => undefined, + + [OB11MessageDataType.poke]: async () => undefined, + + [OB11MessageDataType.Location]: async () => ({ + elementType: ElementType.SHARELOCATION, + elementId: '', + shareLocationElement: { + text: '测试', + ext: '', + }, + }), + + [OB11MessageDataType.miniapp]: async () => undefined, + }; + constructor(obContext: NapCatOneBot11Adapter, coreContext: NapCatCore) { this.obContext = obContext; this.coreContext = coreContext; @@ -439,4 +716,59 @@ export class OneBotMsgApi { } return resMsg; } + + async createSendElements( + messageData: OB11MessageData[], + peer: Peer, + ignoreTypes: OB11MessageDataType[] = [], + ) { + const deleteAfterSentFiles: string[] = []; + const callResultList: Array> = []; + for (const sendMsg of messageData) { + if (ignoreTypes.includes(sendMsg.type)) { + continue; + } + const callResult = this.ob11ToRawConverters[sendMsg.type]( + // eslint-disable-next-line + // @ts-ignore + sendMsg, + { peer, deleteAfterSentFiles }, + )?.catch(undefined); + callResultList.push(callResult); + } + const ret = await Promise.all(callResultList); + const sendElements: SendMessageElement[] = ret.filter(ele => !!ele); + return { sendElements, deleteAfterSentFiles }; + } + + private async handleOb11FileLikeMessage( + { data: inputdata }: OB11MessageFileBase, + { deleteAfterSentFiles }: MessageContext, + ) { + const isBlankUrl = !inputdata.url || inputdata.url === ''; + const isBlankFile = !inputdata.file || inputdata.file === ''; + if (isBlankUrl && isBlankFile) { + this.coreContext.context.logger.logError('文件消息缺少参数', inputdata); + throw Error('文件消息缺少参数'); + } + const fileOrUrl = (isBlankUrl ? inputdata.file : inputdata.url) || ""; + const { + path, + isLocal, + fileName, + errMsg, + success, + } = (await uri2local(this.coreContext.NapCatTempPath, fileOrUrl)); + + if (!success) { + this.coreContext.context.logger.logError('文件下载失败', errMsg); + throw Error('文件下载失败' + errMsg); + } + + if (!isLocal) { // 只删除http和base64转过来的文件 + deleteAfterSentFiles.push(path); + } + + return { path, fileName: inputdata.name || fileName }; + } }