diff --git a/src/ntqqapi/constructor.ts b/src/ntqqapi/constructor.ts index 0be0f05..afe40cf 100644 --- a/src/ntqqapi/constructor.ts +++ b/src/ntqqapi/constructor.ts @@ -1,16 +1,16 @@ import { - AtType, - ElementType, - FaceType, - PicType, - SendArkElement, - SendFaceElement, - SendFileElement, - SendPicElement, - SendPttElement, - SendReplyElement, - SendTextElement, - SendVideoElement + AtType, + ElementType, + FaceType, + PicType, + SendArkElement, + SendFaceElement, + SendFileElement, + SendPicElement, + SendPttElement, + SendReplyElement, + SendTextElement, + SendVideoElement } from "./types"; import {promises as fs} from "node:fs"; import ffmpeg from "fluent-ffmpeg" @@ -24,280 +24,295 @@ import {isNull} from "../common/utils"; export class SendMsgElementConstructor { - static poke(groupCode: string, uin: string){ - return null + static poke(groupCode: string, uin: string) { + return null + } + + static text(content: string): SendTextElement { + return { + elementType: ElementType.TEXT, + elementId: "", + textElement: { + content, + atType: AtType.notAt, + atUid: "", + atTinyId: "", + atNtUid: "", + }, + }; + } + + static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement { + return { + elementType: ElementType.TEXT, + elementId: "", + textElement: { + content: `@${atName}`, + atType, + atUid, + atTinyId: "", + atNtUid, + }, + }; + } + + static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { + return { + elementType: ElementType.REPLY, + elementId: "", + replyElement: { + replayMsgSeq: msgSeq, // raw.msgSeq + replayMsgId: msgId, // raw.msgId + senderUin: senderUin, + senderUinStr: senderUinStr, + } } - static text(content: string): SendTextElement { - return { - elementType: ElementType.TEXT, - elementId: "", - textElement: { - content, - atType: AtType.notAt, - atUid: "", - atTinyId: "", - atNtUid: "", - }, - }; + } + + static async pic(picPath: string, summary: string = "", subType: 0 | 1 = 0): Promise { + const {md5, fileName, path, fileSize} = await NTQQFileApi.uploadFile(picPath, ElementType.PIC, subType); + if (fileSize === 0) { + throw "文件异常,大小为0"; + } + const imageSize = await NTQQFileApi.getImageSize(picPath); + const picElement = { + 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 + }; + log("图片信息", picElement) + return { + elementType: ElementType.PIC, + elementId: "", + picElement, + }; + } + + static async file(filePath: string, fileName: string = ""): Promise { + const {md5, fileName: _fileName, path, fileSize} = await NTQQFileApi.uploadFile(filePath, ElementType.FILE); + if (fileSize === 0) { + throw "文件异常,大小为0"; + } + let element: SendFileElement = { + elementType: ElementType.FILE, + elementId: "", + fileElement: { + fileName: fileName || _fileName, + "filePath": path, + "fileSize": (fileSize).toString(), + } } - static at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement { - return { - elementType: ElementType.TEXT, - elementId: "", - textElement: { - content: `@${atName}`, - atType, - atUid, - atTinyId: "", - atNtUid, - }, - }; + return element; + } + + static async video(filePath: string, fileName: string = "", diyThumbPath: string = ""): Promise { + let {fileName: _fileName, path, fileSize, md5} = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO); + if (fileSize === 0) { + throw "文件异常,大小为0"; } - - static reply(msgSeq: string, msgId: string, senderUin: string, senderUinStr: string): SendReplyElement { - return { - elementType: ElementType.REPLY, - elementId: "", - replyElement: { - replayMsgSeq: msgSeq, // raw.msgSeq - replayMsgId: msgId, // raw.msgId - senderUin: senderUin, - senderUinStr: senderUinStr, - } - } + const pathLib = require("path"); + let thumbDir = path.replace(`${pathLib.sep}Ori${pathLib.sep}`, `${pathLib.sep}Thumb${pathLib.sep}`) + thumbDir = pathLib.dirname(thumbDir) + // log("thumb 目录", thumb) + let videoInfo = { + width: 1920, height: 1080, + time: 15, + format: "mp4", + size: fileSize, + filePath + }; + try { + videoInfo = await getVideoInfo(path); + log("视频信息", videoInfo) + } catch (e) { + log("获取视频信息失败", e) } + const createThumb = new Promise((resolve, reject) => { + const thumbFileName = `${md5}_0.png` + const thumbPath = pathLib.join(thumbDir, thumbFileName) + log("开始生成视频缩略图", filePath); + let completed = false; - static async pic(picPath: string, summary: string = "", subType: 0|1=0): Promise { - const {md5, fileName, path, fileSize} = await NTQQFileApi.uploadFile(picPath, ElementType.PIC, subType); - if (fileSize === 0) { - throw "文件异常,大小为0"; - } - const imageSize = await NTQQFileApi.getImageSize(picPath); - const picElement = { - 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 - }; - log("图片信息", picElement) - return { - elementType: ElementType.PIC, - elementId: "", - picElement, - }; - } + function useDefaultThumb() { + if (completed) return; + log("获取视频封面失败,使用默认封面"); + fs.writeFile(thumbPath, defaultVideoThumb).then(() => { + resolve(thumbPath); + }).catch(reject) + } - static async file(filePath: string, fileName: string = ""): Promise { - const {md5, fileName: _fileName, path, fileSize} = await NTQQFileApi.uploadFile(filePath, ElementType.FILE); - if (fileSize === 0) { - throw "文件异常,大小为0"; - } - let element: SendFileElement = { - elementType: ElementType.FILE, - elementId: "", - fileElement: { - fileName: fileName || _fileName, - "filePath": path, - "fileSize": (fileSize).toString(), - } - } - - return element; - } - - static async video(filePath: string, fileName: string = "", diyThumbPath: string = ""): Promise { - let {fileName: _fileName, path, fileSize, md5} = await NTQQFileApi.uploadFile(filePath, ElementType.VIDEO); - if (fileSize === 0) { - throw "文件异常,大小为0"; - } - const pathLib = require("path"); - 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); - log("视频信息", videoInfo) - } catch (e) { - log("获取视频信息失败", e) - } - const createThumb = new Promise((resolve, reject) => { - const thumbFileName = `${md5}_0.png` - const thumbPath = pathLib.join(thumb, thumbFileName) - ffmpeg(filePath) - .on("end", () => { - }) - .on("error", (err) => { - log("获取视频封面失败,使用默认封面", err) - if (diyThumbPath) { - fs.copyFile(diyThumbPath, thumbPath).then(() => { - resolve(thumbPath); - }).catch(reject) - } else { - fs.writeFile(thumbPath, defaultVideoThumb).then(() => { - resolve(thumbPath); - }).catch(reject) - } - }) - .screenshots({ - timestamps: [0], - filename: thumbFileName, - folder: thumb, - size: videoInfo.width + "x" + videoInfo.height - }).on("end", () => { - resolve(thumbPath); - }); + setTimeout(useDefaultThumb, 5000); + ffmpeg(filePath) + .on("end", () => { }) - let thumbPath = new Map() - const _thumbPath = await createThumb; - const thumbSize = (await fs.stat(_thumbPath)).size; - // log("生成缩略图", _thumbPath) - thumbPath.set(0, _thumbPath) - const thumbMd5 = await calculateFileMD5(_thumbPath); - let element: SendVideoElement = { - 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, - // fileUuid: "", - // transferStatus: 0, - // progress: 0, - // invalidState: 0, - // fileSubId: "", - // fileBizId: null, - // originVideoMd5: "", - // fileFormat: 2, - // import_rich_media_context: null, - // sourceVideoCodecFormat: 2 - } - } - return element; + .on("error", (err) => { + if (diyThumbPath) { + fs.copyFile(diyThumbPath, thumbPath).then(() => { + completed = true; + resolve(thumbPath); + }).catch(reject) + } else { + useDefaultThumb() + } + }) + .screenshots({ + timestamps: [0], + filename: thumbFileName, + folder: thumbDir, + size: videoInfo.width + "x" + videoInfo.height + }).on("end", () => { + log("生成视频缩略图", thumbPath) + completed = true; + resolve(thumbPath); + }) + }) + let thumbPath = new Map() + const _thumbPath = await createThumb; + log("生成缩略图", _thumbPath) + const thumbSize = (await fs.stat(_thumbPath)).size; + // log("生成缩略图", _thumbPath) + thumbPath.set(0, _thumbPath) + const thumbMd5 = await calculateFileMD5(_thumbPath); + let element: SendVideoElement = { + 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, + // fileUuid: "", + // transferStatus: 0, + // progress: 0, + // invalidState: 0, + // fileSubId: "", + // fileBizId: null, + // originVideoMd5: "", + // fileFormat: 2, + // import_rich_media_context: null, + // sourceVideoCodecFormat: 2 + } } + log("videoElement", element) + return element; + } - static async ptt(pttPath: string): Promise { - const {converted, path: silkPath, duration} = await encodeSilk(pttPath); - // log("生成语音", silkPath, duration); - const {md5, fileName, path, fileSize} = await NTQQFileApi.uploadFile(silkPath, ElementType.PTT); - if (fileSize === 0) { - throw "文件异常,大小为0"; - } - if (converted) { - fs.unlink(silkPath).then(); - } - 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, - 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, - } - }; + static async ptt(pttPath: string): Promise { + const {converted, path: silkPath, duration} = await encodeSilk(pttPath); + // log("生成语音", silkPath, duration); + const {md5, fileName, path, fileSize} = await NTQQFileApi.uploadFile(silkPath, ElementType.PTT); + if (fileSize === 0) { + throw "文件异常,大小为0"; } - - static face(faceId: number): SendFaceElement { - return { - elementType: ElementType.FACE, - elementId: "", - faceElement: { - faceIndex: faceId, - faceType: FaceType.normal - } - } + if (converted) { + fs.unlink(silkPath).then(); } + 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, + 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, + } + }; + } - static dice(resultId: number|null): SendFaceElement{ - // 实际测试并不能控制结果 - - // 随机1到6 - if (isNull(resultId)) resultId = Math.floor(Math.random() * 6) + 1; - return { - elementType: ElementType.FACE, - elementId: "", - faceElement: { - faceIndex: 358, - faceType: FaceType.dice, - "faceText": "[骰子]", - "packId": "1", - "stickerId": "33", - "sourceType": 1, - "stickerType": 2, - resultId: resultId.toString(), - "surpriseId": "", - // "randomType": 1, - } - } + static face(faceId: number): SendFaceElement { + return { + elementType: ElementType.FACE, + elementId: "", + faceElement: { + faceIndex: faceId, + faceType: FaceType.normal + } } + } - // 猜拳(石头剪刀布)表情 - static rps(resultId: number | null): SendFaceElement{ - // 实际测试并不能控制结果 - if (isNull(resultId)) resultId = Math.floor(Math.random() * 3) + 1; - return { - elementType: ElementType.FACE, - elementId: "", - faceElement: { - "faceIndex": 359, - "faceText": "[包剪锤]", - "faceType": 3, - "packId": "1", - "stickerId": "34", - "sourceType": 1, - "stickerType": 2, - "resultId": resultId.toString(), - "surpriseId": "", - // "randomType": 1, - } - } - } + static dice(resultId: number | null): SendFaceElement { + // 实际测试并不能控制结果 - static ark(data: any): SendArkElement { - return { - elementType: ElementType.ARK, - elementId: "", - arkElement: { - bytesData: data, - linkInfo: null, - subElementType: null - } - } + // 随机1到6 + if (isNull(resultId)) resultId = Math.floor(Math.random() * 6) + 1; + return { + elementType: ElementType.FACE, + elementId: "", + faceElement: { + faceIndex: 358, + faceType: FaceType.dice, + "faceText": "[骰子]", + "packId": "1", + "stickerId": "33", + "sourceType": 1, + "stickerType": 2, + resultId: resultId.toString(), + "surpriseId": "", + // "randomType": 1, + } } + } + + // 猜拳(石头剪刀布)表情 + static rps(resultId: number | null): SendFaceElement { + // 实际测试并不能控制结果 + if (isNull(resultId)) resultId = Math.floor(Math.random() * 3) + 1; + return { + elementType: ElementType.FACE, + elementId: "", + faceElement: { + "faceIndex": 359, + "faceText": "[包剪锤]", + "faceType": 3, + "packId": "1", + "stickerId": "34", + "sourceType": 1, + "stickerType": 2, + "resultId": resultId.toString(), + "surpriseId": "", + // "randomType": 1, + } + } + } + + static ark(data: any): SendArkElement { + return { + elementType: ElementType.ARK, + elementId: "", + arkElement: { + bytesData: data, + linkInfo: null, + subElementType: null + } + } + } } \ No newline at end of file diff --git a/src/onebot11/server/postOB11Event.ts b/src/onebot11/server/postOB11Event.ts index c7fa65b..876ea1d 100644 --- a/src/onebot11/server/postOB11Event.ts +++ b/src/onebot11/server/postOB11Event.ts @@ -1,5 +1,5 @@ import {OB11Message, OB11MessageAt, OB11MessageData} from "../types"; -import {getGroup, selfInfo} from "../../common/data"; +import {getFriend, getGroup, getUidByUin, selfInfo} from "../../common/data"; import {OB11BaseMetaEvent} from "../event/meta/OB11BaseMetaEvent"; import {OB11BaseNoticeEvent} from "../event/notice/OB11BaseNoticeEvent"; import {WebSocket as WebSocketClass} from "ws"; @@ -115,6 +115,7 @@ export function postOB11Event(msg: PostEventType, reportSelf = false) { peerUid: msg.user_id.toString() } if (msg.message_type == "private") { + peer.peerUid = getUidByUin(msg.user_id.toString()) if (msg.sub_type === "group") { peer.chatType = ChatType.temp } @@ -139,6 +140,7 @@ export function postOB11Event(msg: PostEventType, reportSelf = false) { } replyMessage = replyMessage.concat(convertMessage2List(reply, resJson.auto_escape)) const {sendElements, deleteAfterSentFiles} = await createSendElements(replyMessage, group) + log(`发送消息给`, peer, sendElements) sendMsg(peer, sendElements, deleteAfterSentFiles, false).then() } else if (resJson.delete) { NTQQMsgApi.recallMsg(peer, [rawMessage.msgId]).then()