mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2024-11-21 09:36:35 +00:00
chore: gocq接口完成
This commit is contained in:
parent
b016268fdb
commit
a1a378d6f5
88
src/common/utils/audio.ts
Normal file
88
src/common/utils/audio.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import fs from 'fs';
|
||||
import { encode, getDuration, getWavFileInfo, isWav, isSilk } from 'silk-wasm';
|
||||
import fsPromise from 'fs/promises';
|
||||
import path from 'node:path';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { LogWrapper } from './log';
|
||||
export async function encodeSilk(filePath: string, TEMP_DIR: string, logger: LogWrapper) {
|
||||
async function guessDuration(pttPath: string) {
|
||||
const pttFileInfo = await fsPromise.stat(pttPath);
|
||||
let duration = pttFileInfo.size / 1024 / 3; // 3kb/s
|
||||
duration = Math.floor(duration);
|
||||
duration = Math.max(1, duration);
|
||||
logger.log('通过文件大小估算语音的时长:', duration);
|
||||
return duration;
|
||||
}
|
||||
try {
|
||||
const file = await fsPromise.readFile(filePath);
|
||||
const pttPath = path.join(TEMP_DIR, randomUUID());
|
||||
if (!isSilk(file)) {
|
||||
logger.log(`语音文件${filePath}需要转换成silk`);
|
||||
const _isWav = isWav(file);
|
||||
const pcmPath = pttPath + '.pcm';
|
||||
let sampleRate = 0;
|
||||
const convert = () => {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
// todo: 通过配置文件获取ffmpeg路径
|
||||
const ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
const cp = spawn(ffmpegPath, ['-y', '-i', filePath, '-ar', '24000', '-ac', '1', '-f', 's16le', pcmPath]);
|
||||
cp.on('error', err => {
|
||||
logger.log('FFmpeg处理转换出错: ', err.message);
|
||||
return reject(err);
|
||||
});
|
||||
cp.on('exit', (code, signal) => {
|
||||
const EXIT_CODES = [0, 255];
|
||||
if (code == null || EXIT_CODES.includes(code)) {
|
||||
sampleRate = 24000;
|
||||
const data = fs.readFileSync(pcmPath);
|
||||
fs.unlink(pcmPath, (err) => {
|
||||
});
|
||||
return resolve(data);
|
||||
}
|
||||
logger.log(`FFmpeg exit: code=${code ?? 'unknown'} sig=${signal ?? 'unknown'}`);
|
||||
reject(Error('FFmpeg处理转换失败'));
|
||||
});
|
||||
});
|
||||
};
|
||||
let input: Buffer;
|
||||
if (!_isWav) {
|
||||
input = await convert();
|
||||
} else {
|
||||
input = file;
|
||||
const allowSampleRate = [8000, 12000, 16000, 24000, 32000, 44100, 48000];
|
||||
const { fmt } = getWavFileInfo(input);
|
||||
// log(`wav文件信息`, fmt)
|
||||
if (!allowSampleRate.includes(fmt.sampleRate)) {
|
||||
input = await convert();
|
||||
}
|
||||
}
|
||||
const silk = await encode(input, sampleRate);
|
||||
fs.writeFileSync(pttPath, silk.data);
|
||||
logger.log(`语音文件${filePath}转换成功!`, pttPath, '时长:', silk.duration);
|
||||
return {
|
||||
converted: true,
|
||||
path: pttPath,
|
||||
duration: silk.duration / 1000
|
||||
};
|
||||
} else {
|
||||
const silk = file;
|
||||
let duration = 0;
|
||||
try {
|
||||
duration = getDuration(silk) / 1000;
|
||||
} catch (e: any) {
|
||||
logger.log('获取语音文件时长失败, 使用文件大小推测时长', filePath, e.stack);
|
||||
duration = await guessDuration(filePath);
|
||||
}
|
||||
|
||||
return {
|
||||
converted: false,
|
||||
path: filePath,
|
||||
duration,
|
||||
};
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.logError('convert silk failed', error.stack);
|
||||
return {};
|
||||
}
|
||||
}
|
62
src/common/utils/video.ts
Normal file
62
src/common/utils/video.ts
Normal file
File diff suppressed because one or more lines are too long
@ -32,7 +32,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
|
||||
protected async _handle(payload: Payload): Promise<FileResponse> {
|
||||
const isRandomName = !payload.name;
|
||||
const name = payload.name || randomUUID();
|
||||
const filePath = joinPath(getTempDir(), name);
|
||||
const filePath = joinPath(this.CoreContext.NapCatTempPath, name);
|
||||
|
||||
if (payload.base64) {
|
||||
fs.writeFileSync(filePath, payload.base64, 'base64');
|
||||
@ -48,7 +48,7 @@ export default class GoCQHTTPDownloadFile extends BaseAction<Payload, FileRespon
|
||||
if (isRandomName) {
|
||||
// 默认实现要名称未填写时文件名为文件 md5
|
||||
const md5 = await calculateFileMD5(filePath);
|
||||
const newPath = joinPath(getTempDir(), md5);
|
||||
const newPath = joinPath(this.CoreContext.NapCatTempPath, md5);
|
||||
fs.renameSync(filePath, newPath);
|
||||
return { file: newPath };
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { OB11ForwardMessage, OB11Message, OB11MessageData } from '../../types';
|
||||
import { NTQQMsgApi } from '@/core/apis';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import { ActionName, BaseCheckResult } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
@ -41,7 +41,7 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
const msgList = data.msgList;
|
||||
const messages = await Promise.all(msgList.map(async msg => {
|
||||
const resMsg = await OB11Constructor.message(msg);
|
||||
resMsg.message_id = await MessageUnique.createMsg({ guildId:'',chatType:msg.chatType,peerUid:msg.peerUid },msg.msgId)!;
|
||||
resMsg.message_id = MessageUnique.createMsg({ guildId:'',chatType:msg.chatType,peerUid:msg.peerUid },msg.msgId)!;
|
||||
return resMsg;
|
||||
}));
|
||||
messages.map(msg => {
|
||||
|
@ -3,7 +3,7 @@ import { OB11Message, OB11User } from '../../types';
|
||||
import { ActionName } from '../types';
|
||||
import { ChatType, RawMessage } from '@/core/entities';
|
||||
import { NTQQMsgApi } from '@/core/apis/msg';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
|
||||
|
@ -7,7 +7,7 @@ const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
group_id: { type: [ 'number' , 'string' ] },
|
||||
type: { enum: [WebHonorType.ALL, WebHonorType.EMOTION, WebHonorType.LEGEND, WebHonorType.PERFROMER, WebHonorType.STORONGE_NEWBI, WebHonorType.TALKACTIVE] }
|
||||
type: { enum: [WebHonorType.ALL, WebHonorType.EMOTION, WebHonorType.LEGEND, WebHonorType.PERFORMER, WebHonorType.STRONG_NEWBIE, WebHonorType.TALKATIVE] }
|
||||
},
|
||||
required: ['group_id']
|
||||
} as const satisfies JSONSchema;
|
||||
|
@ -1,9 +1,8 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { OB11Message, OB11User } from '../../types';
|
||||
import { OB11Message } from '../../types';
|
||||
import { ActionName } from '../types';
|
||||
import { ChatType, Peer, RawMessage } from '@/core/entities';
|
||||
import { NTQQMsgApi } from '@/core/apis/msg';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { MessageUnique } from '@/common/utils/MessageUnique';
|
||||
interface Response {
|
||||
@ -41,7 +40,7 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
|
||||
if (!startMsgId) throw `消息${payload.message_seq}不存在`;
|
||||
msgList = (await NTQQMsgApi.getMsgHistory(peer, startMsgId, MsgCount)).msgList;
|
||||
}
|
||||
if(isReverseOrder) msgList.reverse();
|
||||
if (isReverseOrder) msgList.reverse();
|
||||
await Promise.all(msgList.map(async msg => {
|
||||
msg.id = MessageUnique.createMsg({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
||||
}));
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { DeviceList } from '@/onebot11/main';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { JSONSchema } from 'json-schema-to-ts';
|
||||
import { NTQQSystemApi } from '@/core';
|
||||
import { sleep } from '@/common/utils/helper';
|
||||
|
||||
const SchemaData = {
|
||||
@ -16,8 +14,11 @@ export class GetOnlineClient extends BaseAction<void, Array<any>> {
|
||||
actionName = ActionName.GetOnlineClient;
|
||||
|
||||
protected async _handle(payload: void) {
|
||||
//注册监听
|
||||
const NTQQSystemApi = this.CoreContext.getApiContext().SystemApi;
|
||||
NTQQSystemApi.getOnlineDev();
|
||||
await sleep(500);
|
||||
return DeviceList;
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,7 @@
|
||||
import BaseAction from '../BaseAction';
|
||||
import { OB11User, OB11UserSex } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQUserApi } from '@/core/apis/user';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { calcQQLevel } from '@/common/utils/qqlevel';
|
||||
const SchemaData = {
|
||||
@ -20,24 +19,24 @@ export default class GoCQHTTPGetStrangerInfo extends BaseAction<Payload, OB11Use
|
||||
|
||||
protected async _handle(payload: Payload): Promise<OB11User> {
|
||||
const NTQQUserApi = this.CoreContext.getApiContext().UserApi;
|
||||
const user_id = payload.user_id.toString();
|
||||
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id);
|
||||
const uid = (await NTQQUserApi.getUidByUin(user_id))!;
|
||||
if (!uid || uid.indexOf('*') != -1) {
|
||||
const ret = {
|
||||
...extendData,
|
||||
user_id: parseInt(extendData.info.uin) || 0,
|
||||
nickname: extendData.info.nick,
|
||||
sex: OB11UserSex.unknown,
|
||||
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
|
||||
qid: extendData.info.qid,
|
||||
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
|
||||
login_days: 0,
|
||||
uid: ''
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
|
||||
return OB11Constructor.stranger(data);
|
||||
const user_id = payload.user_id.toString();
|
||||
const extendData = await NTQQUserApi.getUserDetailInfoByUin(user_id);
|
||||
const uid = (await NTQQUserApi.getUidByUin(user_id))!;
|
||||
if (!uid || uid.indexOf('*') != -1) {
|
||||
const ret = {
|
||||
...extendData,
|
||||
user_id: parseInt(extendData.info.uin) || 0,
|
||||
nickname: extendData.info.nick,
|
||||
sex: OB11UserSex.unknown,
|
||||
age: (extendData.info.birthday_year == 0) ? 0 : new Date().getFullYear() - extendData.info.birthday_year,
|
||||
qid: extendData.info.qid,
|
||||
level: extendData.info.qqLevel && calcQQLevel(extendData.info.qqLevel) || 0,
|
||||
login_days: 0,
|
||||
uid: ''
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
const data = { ...extendData, ...(await NTQQUserApi.getUserDetailInfo(uid)) };
|
||||
return OB11Constructor.stranger(data);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ interface Payload{
|
||||
export class GoCQHTTPHandleQuickAction extends BaseAction<Payload, null>{
|
||||
actionName = ActionName.GoCQHTTP_HandleQuickAction;
|
||||
protected async _handle(payload: Payload): Promise<null> {
|
||||
handleQuickOperation(payload.context, payload.operation).then().catch(log);
|
||||
handleQuickOperation(payload.context, payload.operation,this.CoreContext).then().catch(this.CoreContext.context.logger.logError);
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { checkFileReceived, uri2local } from '@/common/utils/file';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQGroupApi, WebApi } from '@/core/apis';
|
||||
import { unlink } from 'node:fs';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
const SchemaData = {
|
||||
@ -25,7 +24,7 @@ export class SendGroupNotice extends BaseAction<Payload, null> {
|
||||
let UploadImage: { id: string, width: number, height: number } | undefined = undefined;
|
||||
if (payload.image) {
|
||||
//公告图逻辑
|
||||
const { errMsg, path, isLocal, success } = (await uri2local(payload.image));
|
||||
const { errMsg, path, isLocal, success } = (await uri2local(this.CoreContext.NapCatTempPath,payload.image));
|
||||
if (!success) {
|
||||
throw `群公告${payload.image}设置失败,image字段可能格式不正确`;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import fs from 'fs';
|
||||
import { sendMsg } from '@/onebot/action/msg/SendMsg';
|
||||
import { uri2local } from '@/common/utils/file';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { SendMsgElementConstructor } from '@/onebot/helper/msg';
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -27,12 +28,12 @@ export default class GoCQHTTPUploadGroupFile extends BaseAction<Payload, null> {
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`;
|
||||
}
|
||||
const downloadResult = await uri2local(file);
|
||||
const downloadResult = await uri2local(this.CoreContext.NapCatTempPath, file);
|
||||
if (!downloadResult.success) {
|
||||
throw new Error(downloadResult.errMsg);
|
||||
}
|
||||
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name, payload.folder_id);
|
||||
await sendMsg({ chatType: ChatType.group, peerUid: group.groupCode }, [sendFileEle], [], true);
|
||||
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(this.CoreContext, downloadResult.path, payload.name, payload.folder_id);
|
||||
await sendMsg({ chatType: ChatType.group, peerUid: payload.group_id.toString() }, [sendFileEle], [], true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ import BaseAction from '../BaseAction';;
|
||||
import { ActionName } from '../types';
|
||||
import { ChatType, Peer, SendFileElement } from '@/core/entities';
|
||||
import fs from 'fs';
|
||||
import { SendMsg, sendMsg } from '@/onebot/action/msg/SendMsg';
|
||||
import { sendMsg } from '@/onebot/action/msg/SendMsg';
|
||||
import { uri2local } from '@/common/utils/file';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { NTQQFriendApi, NTQQUserApi } from '@/core';
|
||||
import { SendMsgElementConstructor } from '@/onebot/helper/msg';
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@ -22,6 +22,8 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
|
||||
actionName = ActionName.GOCQHTTP_UploadPrivateFile;
|
||||
PayloadSchema = SchemaData;
|
||||
async getPeer(payload: Payload): Promise<Peer> {
|
||||
const NTQQUserApi = this.CoreContext.getApiContext().UserApi;
|
||||
const NTQQFriendApi = this.CoreContext.getApiContext().FriendApi;
|
||||
if (payload.user_id) {
|
||||
const peerUid = await NTQQUserApi.getUidByUin(payload.user_id.toString());
|
||||
if (!peerUid) {
|
||||
@ -38,11 +40,11 @@ export default class GoCQHTTPUploadPrivateFile extends BaseAction<Payload, null>
|
||||
if (fs.existsSync(file)) {
|
||||
file = `file://${file}`;
|
||||
}
|
||||
const downloadResult = await uri2local(file);
|
||||
const downloadResult = await uri2local(this.CoreContext.NapCatTempPath, file);
|
||||
if (!downloadResult.success) {
|
||||
throw new Error(downloadResult.errMsg);
|
||||
}
|
||||
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(downloadResult.path, payload.name);
|
||||
const sendFileEle: SendFileElement = await SendMsgElementConstructor.file(this.CoreContext, downloadResult.path, payload.name);
|
||||
await sendMsg(peer, [sendFileEle], [], true);
|
||||
return null;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OB11Group } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OB11Group } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { Group } from '@/core/entities';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OB11GroupMember } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OB11GroupMember } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { NTQQUserApi } from '@/core/apis/user';
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
import { OB11GroupMember } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OB11Message } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
|
@ -1,6 +1,6 @@
|
||||
|
||||
import { OB11User } from '../../types';
|
||||
import { OB11Constructor } from '../../helper/constructor';
|
||||
import { OB11Constructor } from '../../helper/data';
|
||||
import BaseAction from '../BaseAction';
|
||||
import { ActionName } from '../types';
|
||||
|
||||
|
377
src/onebot/helper/msg.ts
Normal file
377
src/onebot/helper/msg.ts
Normal file
@ -0,0 +1,377 @@
|
||||
import {
|
||||
AtType,
|
||||
ElementType, FaceIndex, FaceType, NapCatCore, PicElement,
|
||||
PicType,
|
||||
SendArkElement,
|
||||
SendFaceElement,
|
||||
SendFileElement, SendMarkdownElement, SendMarketFaceElement,
|
||||
SendPicElement,
|
||||
SendPttElement,
|
||||
SendReplyElement,
|
||||
sendShareLocationElement,
|
||||
SendTextElement,
|
||||
SendVideoElement,
|
||||
viedo_type
|
||||
} from '@/core';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import ffmpeg from 'fluent-ffmpeg';
|
||||
import { NTQQFileApi } from '@/core/apis/file';
|
||||
import { calculateFileMD5, isGIF } from '@/common/utils/file';
|
||||
import { defaultVideoThumb, getVideoInfo } from '@/common/utils/video';
|
||||
import { encodeSilk } from '@/common/utils/audio';
|
||||
import { isNull } from '@/common/utils/helper';
|
||||
import faceConfig from '@/core/external/face_config.json';
|
||||
import * as pathLib from 'node:path';
|
||||
export class SendMsgElementConstructor {
|
||||
static location(CoreContext: NapCatCore): sendShareLocationElement {
|
||||
return {
|
||||
elementType: ElementType.SHARELOCATION,
|
||||
elementId: '',
|
||||
shareLocationElement: {
|
||||
text: "测试",
|
||||
ext: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
static text(CoreContext: NapCatCore, content: string): SendTextElement {
|
||||
return {
|
||||
elementType: ElementType.TEXT,
|
||||
elementId: '',
|
||||
textElement: {
|
||||
content,
|
||||
atType: AtType.notAt,
|
||||
atUid: '',
|
||||
atTinyId: '',
|
||||
atNtUid: '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static at(CoreContext: NapCatCore, atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement {
|
||||
return {
|
||||
elementType: ElementType.TEXT,
|
||||
elementId: '',
|
||||
textElement: {
|
||||
content: `@${atName}`,
|
||||
atType,
|
||||
atUid,
|
||||
atTinyId: '',
|
||||
atNtUid,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static reply(CoreContext: NapCatCore, 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 async pic(CoreContext: NapCatCore, 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: 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
|
||||
};
|
||||
//logDebug('图片信息', picElement);
|
||||
return {
|
||||
elementType: ElementType.PIC,
|
||||
elementId: '',
|
||||
picElement,
|
||||
};
|
||||
}
|
||||
|
||||
static async file(CoreContext: NapCatCore, filePath: string, fileName: string = '', folderId: string = ''): Promise<SendFileElement> {
|
||||
const { md5, fileName: _fileName, path, fileSize } = await NTQQFileApi.uploadFile(filePath, ElementType.FILE);
|
||||
if (fileSize === 0) {
|
||||
throw '文件异常,大小为0';
|
||||
}
|
||||
const element: SendFileElement = {
|
||||
elementType: ElementType.FILE,
|
||||
elementId: '',
|
||||
fileElement: {
|
||||
fileName: fileName || _fileName,
|
||||
folderId: folderId,
|
||||
'filePath': path!,
|
||||
'fileSize': (fileSize).toString(),
|
||||
}
|
||||
};
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
static async video(CoreContext: NapCatCore, filePath: string, fileName: string = '', diyThumbPath: string = '', videotype: viedo_type = viedo_type.VIDEO_FORMAT_MP4): Promise<SendVideoElement> {
|
||||
const { fileName: _fileName, path, fileSize, md5 } = await NTQQFileApi.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);
|
||||
//logDebug('视频信息', videoInfo);
|
||||
} catch (e) {
|
||||
logError('获取视频信息失败', 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) => {
|
||||
logDebug('获取视频封面失败,使用默认封面', 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);
|
||||
});
|
||||
});
|
||||
const 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);
|
||||
const 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,
|
||||
//fileFormat: videotype
|
||||
// fileUuid: "",
|
||||
// transferStatus: 0,
|
||||
// progress: 0,
|
||||
// invalidState: 0,
|
||||
// fileSubId: "",
|
||||
// fileBizId: null,
|
||||
// originVideoMd5: "",
|
||||
// fileFormat: 2,
|
||||
// import_rich_media_context: null,
|
||||
// sourceVideoCodecFormat: 2
|
||||
}
|
||||
};
|
||||
return element;
|
||||
}
|
||||
|
||||
static async ptt(CoreContext: NapCatCore, pttPath: string): Promise<SendPttElement> {
|
||||
const { converted, path: silkPath, duration } = await encodeSilk(pttPath);
|
||||
// log("生成语音", silkPath, duration);
|
||||
if (!silkPath) {
|
||||
throw '语音转换失败, 请检查语音文件是否正常';
|
||||
}
|
||||
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 || 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,
|
||||
}
|
||||
};
|
||||
}
|
||||
// NodeIQQNTWrapperSession sendMsg [
|
||||
// "0",
|
||||
// {
|
||||
// "peerUid": "u_e_RIxgTs2NaJ68h0PwOPSg",
|
||||
// "chatType": 1,
|
||||
// "guildId": ""
|
||||
// },
|
||||
// [
|
||||
// {
|
||||
// "elementId": "0",
|
||||
// "elementType": 6,
|
||||
// "faceElement": {
|
||||
// "faceIndex": 0,
|
||||
// "faceType": 5,
|
||||
// "msgType": 0,
|
||||
// "pokeType": 1,
|
||||
// "pokeStrength": 0
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
// {}
|
||||
// ]
|
||||
static face(CoreContext: NapCatCore, faceId: number): SendFaceElement {
|
||||
// 从face_config.json中获取表情名称
|
||||
const sysFaces = faceConfig.sysface;
|
||||
const emojiFaces = faceConfig.emoji;
|
||||
const face: any = sysFaces.find((face) => face.QSid === faceId.toString());
|
||||
faceId = parseInt(faceId.toString());
|
||||
// let faceType = parseInt(faceId.toString().substring(0, 1));
|
||||
let faceType = 1;
|
||||
if (faceId >= 222) {
|
||||
faceType = 2;
|
||||
}
|
||||
if (face.AniStickerType) {
|
||||
faceType = 3;
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.FACE,
|
||||
elementId: '',
|
||||
faceElement: {
|
||||
faceIndex: faceId,
|
||||
faceType,
|
||||
faceText: face.QDes,
|
||||
stickerId: face.AniStickerId,
|
||||
stickerType: face.AniStickerType,
|
||||
packId: face.AniStickerPackId,
|
||||
sourceType: 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static mface(CoreContext: NapCatCore, emojiPackageId: number, emojiId: string, key: string, faceName: string): SendMarketFaceElement {
|
||||
return {
|
||||
elementType: ElementType.MFACE,
|
||||
marketFaceElement: {
|
||||
emojiPackageId,
|
||||
emojiId,
|
||||
key,
|
||||
faceName: faceName || '[商城表情]',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static dice(CoreContext: NapCatCore, resultId: number | null): SendFaceElement {
|
||||
// 实际测试并不能控制结果
|
||||
|
||||
// 随机1到6
|
||||
// if (isNull(resultId)) resultId = Math.floor(Math.random() * 6) + 1;
|
||||
return {
|
||||
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,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 猜拳(石头剪刀布)表情
|
||||
static rps(CoreContext: NapCatCore, resultId: number | null): SendFaceElement {
|
||||
// 实际测试并不能控制结果
|
||||
// if (isNull(resultId)) resultId = Math.floor(Math.random() * 3) + 1;
|
||||
return {
|
||||
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,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static ark(CoreContext: NapCatCore, data: any): SendArkElement {
|
||||
if (typeof data !== 'string') {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
return {
|
||||
elementType: ElementType.ARK,
|
||||
elementId: '',
|
||||
arkElement: {
|
||||
bytesData: data,
|
||||
linkInfo: null,
|
||||
subElementType: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static markdown(CoreContext: NapCatCore, content: string): SendMarkdownElement {
|
||||
return {
|
||||
elementType: ElementType.MARKDOWN,
|
||||
elementId: '',
|
||||
markdownElement: {
|
||||
content
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user