fix: quick reply friend msg

This commit is contained in:
linyuchen 2024-04-11 18:17:02 +08:00
parent 51602b987e
commit b5e578733f
2 changed files with 289 additions and 272 deletions

View File

@ -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<SendPicElement> {
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<SendFileElement> {
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<SendVideoElement> {
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<string>((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<SendPicElement> {
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<SendFileElement> {
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<SendVideoElement> {
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<string>((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<SendPttElement> {
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<SendPttElement> {
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
}
}
}
}

View File

@ -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()