refactor: better type inferring; move createSendElement into another file

This commit is contained in:
Wesley F. Young 2024-05-15 14:53:58 +08:00
parent 652bfb93cc
commit 4fcf3aa2bd
3 changed files with 286 additions and 300 deletions

View File

@ -0,0 +1,243 @@
import { OB11MessageData, OB11MessageDataType, OB11MessageFileBase } from '@/onebot11/types';
import {
AtType,
CustomMusicSignPostData,
Group,
IdMusicSignPostData,
NTQQFileApi,
SendMessageElement,
SendMsgElementConstructor
} from '@/core';
import { getGroupMember } from '@/core/data';
import { dbUtil } from '@/core/utils/db';
import { logDebug, logError } from '@/common/utils/log';
import { uri2local } from '@/common/utils/file';
import { ob11Config } from '@/onebot11/config';
import { RequestUtil } from '@/common/utils/request';
import fs from 'node:fs';
export type MessageContext = {
group?: Group,
deleteAfterSentFiles: string[],
}
async function handleOb11FileLikeMessage(
{ data: { file, name: payloadFileName } }: OB11MessageFileBase,
{ deleteAfterSentFiles }: MessageContext
) {
let uri = file;
const cache = await dbUtil.getFileCacheByName(file);
if (cache) {
if (fs.existsSync(cache.path)) {
uri = 'file://' + cache.path;
} else if (cache.url) {
uri = cache.url;
} else {
const fileMsg = await dbUtil.getMsgByLongId(cache.msgId);
if (fileMsg) {
cache.path = await NTQQFileApi.downloadMedia(
fileMsg.msgId, fileMsg.chatType, fileMsg.peerUid,
cache.elementId, '', ''
);
uri = 'file://' + cache.path;
dbUtil.updateFileCache(cache);
}
}
logDebug('找到文件缓存', uri);
}
const { path, isLocal, fileName, errMsg } = (await uri2local(uri));
if (errMsg) {
logError('文件下载失败', errMsg);
throw Error('文件下载失败' + errMsg);
}
if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path);
}
return { path, fileName: payloadFileName || fileName };
}
const _handlers: {
[Key in OB11MessageDataType]: (
sendMsg: Extract<OB11MessageData, { type: Key }>,
// This picks the correct message type out
// How great the type system of TypeScript is!
context: MessageContext
) => SendMessageElement | undefined | Promise<SendMessageElement | undefined>
} = {
[OB11MessageDataType.text]: ({ data: { text } }) => SendMsgElementConstructor.text(text),
[OB11MessageDataType.at]: async ({ data: { qq: atQQ } }, context) => {
if (!context.group) return undefined;
if (atQQ === 'all') return SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员');
// then the qq is a group member
const atMember = await getGroupMember(context.group.groupCode, atQQ);
return atMember ?
SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick) :
undefined;
},
[OB11MessageDataType.reply]: async ({ data: { id } }) => {
const replyMsg = await dbUtil.getMsgByShortId(parseInt(id));
return replyMsg ?
SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!) :
undefined;
},
[OB11MessageDataType.face]: ({ data: { id } }) => SendMsgElementConstructor.face(parseInt(id)),
[OB11MessageDataType.mface]: ({
data: {
emoji_package_id,
emoji_id,
key,
summary
}
}) => SendMsgElementConstructor.mface(emoji_package_id, emoji_id, key, summary),
// File service
[OB11MessageDataType.image]: async (sendMsg, context) =>
SendMsgElementConstructor.pic(
(await handleOb11FileLikeMessage(sendMsg, context)).path,
sendMsg.data.summary || '',
sendMsg.data.subType || 0
), // currently not supported
[OB11MessageDataType.file]: async (sendMsg, context) => {
const { path, fileName } = await handleOb11FileLikeMessage(sendMsg, context);
logDebug('发送文件', path, fileName);
return SendMsgElementConstructor.file(path, fileName);
},
[OB11MessageDataType.video]: async (sendMsg, context) => {
const { path, fileName } = await handleOb11FileLikeMessage(sendMsg, context);
logDebug('发送视频', path, fileName);
let thumb = sendMsg.data.thumb;
if (thumb) {
const uri2LocalRes = await uri2local(thumb);
if (uri2LocalRes.success) thumb = uri2LocalRes.path;
}
return SendMsgElementConstructor.video(path, fileName, thumb);
},
[OB11MessageDataType.voice]: async (sendMsg, context) =>
SendMsgElementConstructor.ptt((await handleOb11FileLikeMessage(sendMsg, context)).path),
[OB11MessageDataType.json]: ({ data: { data } }) => SendMsgElementConstructor.ark(data),
[OB11MessageDataType.dice]: ({ data: { result } }) => SendMsgElementConstructor.dice(result),
[OB11MessageDataType.RPS]: ({ data: { result } }) => SendMsgElementConstructor.rps(result),
[OB11MessageDataType.markdown]: ({ data: { content } }) => SendMsgElementConstructor.markdown(content),
[OB11MessageDataType.music]: async ({ data }) => {
// 保留, 直到...找到更好的解决方案
if (data.type === 'custom') {
if (!data.url) {
logError('自定义音卡缺少参数url');
return undefined;
}
if (!data.audio) {
logError('自定义音卡缺少参数audio');
return undefined;
}
if (!data.title) {
logError('自定义音卡缺少参数title');
return undefined;
}
} else {
if (!['qq', '163'].includes(data.type)) {
logError('音乐卡片type错误, 只支持qq、163、custom当前type:', data.type);
return undefined;
}
if (!data.id) {
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;
}
const signUrl = ob11Config.musicSignUrl;
if (!signUrl) {
throw Error('音乐消息签名地址未配置');
}
try {
const musicJson = await RequestUtil.HttpGetJson<any>(signUrl, 'POST', postData);
return SendMsgElementConstructor.ark(musicJson);
} catch (e) {
logError('生成音乐消息失败', e);
}
},
[OB11MessageDataType.node]: () => undefined,
[OB11MessageDataType.forward]: () => undefined,
[OB11MessageDataType.xml]: () => undefined,
[OB11MessageDataType.poke]: () => undefined,
};
const handlers = <{
[Key in OB11MessageDataType]: (
sendMsg: OB11MessageData,
context: MessageContext
) => SendMessageElement | undefined | Promise<SendMessageElement | undefined>
}>_handlers;
export default async function createSendElements(
messageData: OB11MessageData[],
group?: Group,
ignoreTypes: OB11MessageDataType[] = []
) {
const sendElements: SendMessageElement[] = [];
const deleteAfterSentFiles: string[] = [];
for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) {
continue;
}
const callResult = await handlers[sendMsg.type](
sendMsg,
{ group, deleteAfterSentFiles }
);
if (callResult) sendElements.push(callResult);
}
return { sendElements, deleteAfterSentFiles };
}
export async function createSendElementsParallel(
messageData: OB11MessageData[],
group?: Group,
ignoreTypes: OB11MessageDataType[] = []
) {
const deleteAfterSentFiles: string[] = [];
const sendElements = <SendMessageElement[]>(
await Promise.all(
messageData.map(async sendMsg => ignoreTypes.includes(sendMsg.type) ?
undefined :
handlers[sendMsg.type](sendMsg, { group, deleteAfterSentFiles }))
).then(
results => results.filter(
element => element !== undefined
)
)
);
return { sendElements, deleteAfterSentFiles };
}

View File

@ -1,37 +1,35 @@
import {
AtType,
ChatType,
ElementType,
Group, PicSubType,
RawMessage,
SendArkElement,
SendMessageElement,
Peer
} from '@/core/entities';
import BaseAction from '@/onebot11/action/BaseAction';
import {
OB11MessageCustomMusic,
OB11MessageData,
OB11MessageDataType, OB11MessageIdMusic,
OB11MessageDataType,
OB11MessageMixType,
OB11MessageNode,
OB11PostSendMsg
} from '../../types';
import { SendMsgElementConstructor } from '@/core/entities/constructor';
import BaseAction from '../BaseAction';
import { ActionName, BaseCheckResult } from '../types';
import * as fs from 'node:fs';
import { decodeCQCode } from '../../cqcode';
} from '@/onebot11/types';
import { ActionName, BaseCheckResult } from '@/onebot11/action/types';
import { getFriend, getGroup, getUidByUin, selfInfo } from '@/core/data';
import { dbUtil } from '@/core/utils/db';
import { log, logDebug, logError } from '@/common/utils/log';
import {
ChatType,
CustomMusicSignPostData,
ElementType,
Group,
IdMusicSignPostData,
NTQQMsgApi,
Peer,
RawMessage,
SendArkElement,
SendMessageElement,
SendMsgElementConstructor
} from '@/core';
import fs from 'node:fs';
import { logDebug, logError } from '@/common/utils/log';
import { sleep } from '@/common/utils/helper';
import { uri2local } from '@/common/utils/file';
import { getFriend, getGroup, getGroupMember, getUidByUin, selfInfo } from '@/core/data';
import { NTQQMsgApi } from '@/core/apis';
import { NTQQFileApi } from '@/core/apis';
import { ob11Config } from '@/onebot11/config';
import { CustomMusicSignPostData, IdMusicSignPostData } from '@/core/apis/sign';
import { RequestUtil } from '@/common/utils/request';
import { decodeCQCode } from '@/onebot11/cqcode';
import createSendElements from './create-send-elements';
const ALLOW_SEND_TEMP_MSG = false;
@ -47,29 +45,23 @@ function checkSendMessage(sendMsgList: OB11MessageData[]) {
const data = msg['data'];
if (type === 'text' && !data['text']) {
return 400;
}
else if (['image', 'voice', 'record'].includes(type)) {
} else if (['image', 'voice', 'record'].includes(type)) {
if (!data['file']) {
return 400;
}
else {
} else {
if (checkUri(data['file'])) {
return 200;
}
else {
} else {
return 400;
}
}
}
else if (type === 'at' && !data['qq']) {
} else if (type === 'at' && !data['qq']) {
return 400;
} else if (type === 'reply' && !data['id']) {
return 400;
}
else if (type === 'reply' && !data['id']) {
return 400;
}
}
else {
} else {
return 400;
}
}
@ -89,254 +81,15 @@ export function convertMessage2List(message: OB11MessageMixType, autoEscape = fa
text: message
}
}];
}
else {
} else {
message = decodeCQCode(message.toString());
}
}
else if (!Array.isArray(message)) {
} else if (!Array.isArray(message)) {
message = [message];
}
return message;
}
async function genMusicElement(postData: IdMusicSignPostData | CustomMusicSignPostData): Promise<SendArkElement | undefined> {
// const musicJson = {
// app: 'com.tencent.structmsg',
// config: {
// ctime: 1709689928,
// forward: 1,
// token: '5c1e4905f926dd3a64a4bd3841460351',
// type: 'normal'
// },
// extra: { app_type: 1, appid: 100497308, uin: selfInfo.uin },
// meta: {
// news: {
// action: '',
// android_pkg_name: '',
// app_type: 1,
// appid: 100497308,
// ctime: 1709689928,
// desc: content || title,
// jumpUrl: url,
// musicUrl: audio,
// preview: image,
// source_icon: 'https://p.qpic.cn/qqconnect/0/app_100497308_1626060999/100?max-age=2592000&t=0',
// source_url: '',
// tag: 'QQ音乐',
// title: title,
// uin: selfInfo.uin,
// }
// },
// prompt: content || title,
// ver: '0.0.0.1',
// view: 'news'
// };
const signUrl = ob11Config.musicSignUrl;
if (!signUrl) {
throw Error('音乐消息签名地址未配置');
}
try {
//const musicJson = await new MusicSign(signUrl).sign(postData);
// 待测试
const musicJson = await RequestUtil.HttpGetJson<any>(signUrl, 'POST', postData);
return SendMsgElementConstructor.ark(musicJson);
} catch (e) {
logError('生成音乐消息失败', e);
}
}
export async function createSendElements(messageData: OB11MessageData[], group: Group | undefined, ignoreTypes: OB11MessageDataType[] = []) {
const sendElements: SendMessageElement[] = [];
const deleteAfterSentFiles: string[] = [];
for (const sendMsg of messageData) {
if (ignoreTypes.includes(sendMsg.type)) {
continue;
}
switch (sendMsg.type) {
case OB11MessageDataType.text: {
const text = sendMsg.data?.text;
if (text) {
sendElements.push(SendMsgElementConstructor.text(sendMsg.data!.text));
}
}
break;
case OB11MessageDataType.at: {
if (!group) {
continue;
}
let atQQ = sendMsg.data?.qq;
if (atQQ) {
atQQ = atQQ.toString();
if (atQQ === 'all') {
sendElements.push(SendMsgElementConstructor.at(atQQ, atQQ, AtType.atAll, '全体成员'));
}
else {
// const atMember = group?.members.find(m => m.uin == atQQ)
const atMember = await getGroupMember(group?.groupCode, atQQ);
if (atMember) {
sendElements.push(SendMsgElementConstructor.at(atQQ, atMember.uid, AtType.atUser, atMember.cardName || atMember.nick));
}
}
}
}
break;
case OB11MessageDataType.reply: {
const replyMsgId = sendMsg.data.id;
if (replyMsgId) {
const replyMsg = await dbUtil.getMsgByShortId(parseInt(replyMsgId));
if (replyMsg) {
sendElements.push(SendMsgElementConstructor.reply(replyMsg.msgSeq, replyMsg.msgId, replyMsg.senderUin!, replyMsg.senderUin!));
}
}
}
break;
case OB11MessageDataType.face: {
const faceId = sendMsg.data?.id;
if (faceId) {
sendElements.push(SendMsgElementConstructor.face(parseInt(faceId)));
}
}
break;
case OB11MessageDataType.mface: {
sendElements.push(
SendMsgElementConstructor.mface(sendMsg.data.emoji_package_id, sendMsg.data.emoji_id, sendMsg.data.key, sendMsg.data.summary),
);
}
break;
case OB11MessageDataType.image:
case OB11MessageDataType.file:
case OB11MessageDataType.video:
case OB11MessageDataType.voice: {
let file = sendMsg.data?.file;
const payloadFileName = sendMsg.data?.name;
if (file) {
const cache = await dbUtil.getFileCacheByName(file);
if (cache) {
if (fs.existsSync(cache.path)) {
file = 'file://' + cache.path;
}
else if (cache.url) {
file = cache.url;
}
else {
const fileMsg = await dbUtil.getMsgByLongId(cache.msgId);
if (fileMsg) {
const downloadPath = await NTQQFileApi.downloadMedia(fileMsg.msgId, fileMsg.chatType, fileMsg.peerUid,
cache.elementId, '', '');
cache.path = downloadPath!;
dbUtil.updateFileCache(cache).then();
file = 'file://' + cache.path;
}
// await sleep(1000);
// log('download result', downloadPath);
// log('下载完成后的msg', msg);
}
logDebug('找到文件缓存', file);
}
const { path, isLocal, fileName, errMsg } = (await uri2local(file));
if (errMsg) {
logError('文件下载失败', errMsg);
throw Error('文件下载失败' + errMsg);
// throw (errMsg);
// continue
}
if (path) {
if (!isLocal) { // 只删除http和base64转过来的文件
deleteAfterSentFiles.push(path);
}
if (sendMsg.type === OB11MessageDataType.file) {
logDebug('发送文件', path, payloadFileName || fileName);
sendElements.push(await SendMsgElementConstructor.file(path, payloadFileName || fileName));
}
else if (sendMsg.type === OB11MessageDataType.video) {
logDebug('发送视频', path, payloadFileName || fileName);
let thumb = sendMsg.data?.thumb;
if (thumb) {
const uri2LocalRes = await uri2local(thumb);
if (uri2LocalRes.success) {
thumb = uri2LocalRes.path;
}
}
sendElements.push(await SendMsgElementConstructor.video(path, payloadFileName || fileName, thumb));
}
else if (sendMsg.type === OB11MessageDataType.voice) {
sendElements.push(await SendMsgElementConstructor.ptt(path));
}
else if (sendMsg.type === OB11MessageDataType.image) {
sendElements.push(await SendMsgElementConstructor.pic(path, sendMsg.data.summary || '', <PicSubType>parseInt(sendMsg.data?.subType?.toString() || '0')));
}
}
}
}
break;
case OB11MessageDataType.json: {
sendElements.push(SendMsgElementConstructor.ark(sendMsg.data.data));
}
break;
case OB11MessageDataType.dice: {
const resultId = sendMsg.data?.result;
sendElements.push(SendMsgElementConstructor.dice(resultId));
}
break;
case OB11MessageDataType.RPS: {
const resultId = sendMsg.data?.result;
sendElements.push(SendMsgElementConstructor.rps(resultId));
}
break;
case OB11MessageDataType.markdown: {
const content = sendMsg.data?.content;
sendElements.push(SendMsgElementConstructor.markdown(content));
}
break;
case OB11MessageDataType.music: {
const musicData = sendMsg.data;
if (musicData.type === 'custom') {
if (!musicData.url) {
logError('自定义音卡缺少参数url');
break;
}
if (!musicData.audio) {
logError('自定义音卡缺少参数audio');
break;
}
if (!musicData.title) {
logError('自定义音卡缺少参数title');
break;
}
}
else {
if (!['qq', '163'].includes(musicData.type)) {
logError('音乐卡片type错误, 只支持qq、163、custom当前type:', musicData.type);
break;
}
if (!musicData.id) {
logError('音乐卡片缺少参数id');
break;
}
}
const postData = { ...sendMsg.data } as IdMusicSignPostData | CustomMusicSignPostData;
if (sendMsg.data.type === 'custom' && sendMsg.data.content) {
(postData as CustomMusicSignPostData).singer = sendMsg.data.content;
delete (postData as OB11MessageCustomMusic['data']).content;
}
const musicMsgElement = await genMusicElement(postData);
logDebug('生成音乐消息', musicMsgElement);
if (musicMsgElement) {
sendElements.push(musicMsgElement);
}
}
}
}
return {
sendElements,
deleteAfterSentFiles
};
}
export async function sendMsg(peer: Peer, sendElements: SendMessageElement[], deleteAfterSentFiles: string[], waitComplete = true) {
if (!sendElements.length) {
throw ('消息体无法解析, 请检查是否发送了不支持的消息类型');
@ -417,8 +170,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (friend) {
// peer.name = friend.nickName
peer.peerUid = friend.uid;
}
else {
} else {
peer.chatType = ChatType.temp;
const tempUserUid = getUidByUin(payload.user_id.toString());
if (!tempUserUid) {
@ -432,14 +184,11 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (payload?.group_id && payload.message_type === 'group') {
await genGroupPeer();
}
else if (payload?.user_id) {
} else if (payload?.user_id) {
await genFriendPeer();
}
else if (payload.group_id) {
} else if (payload.group_id) {
await genGroupPeer();
}
else {
} else {
throw ('发送消息参数错误, 请指定group_id或user_id');
}
const messages = convertMessage2List(payload.message, payload.auto_escape === true || payload.auto_escape === 'true');
@ -449,15 +198,13 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
if (returnMsg) {
const msgShortId = await dbUtil.addMsg(returnMsg!, false);
return { message_id: msgShortId };
}
else {
} else {
throw Error('发送转发消息失败');
}
} catch (e: any) {
throw Error('发送转发消息失败 ' + e.toString());
}
}
else {
} else {
if (this.getSpecialMsgNum(payload, OB11MessageDataType.music)) {
const music: OB11MessageCustomMusic = messages[0] as OB11MessageCustomMusic;
// if (music) {
@ -528,8 +275,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
const nodeMsg = await dbUtil.getMsgByShortId(parseInt(nodeId));
if (!needClone) {
nodeMsgIds.push(nodeMsg!.msgId);
}
else {
} else {
if (nodeMsg!.peerUid !== selfInfo.uid) {
const cloneMsg = await this.cloneMsg(nodeMsg!);
if (cloneMsg) {
@ -537,8 +283,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
}
}
}
else {
} else {
// 自定义的消息
// 提取消息段发给自己生成消息id
try {
@ -560,8 +305,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
}
sendElementsSplit[splitIndex] = [ele];
splitIndex++;
}
else {
} else {
sendElementsSplit[splitIndex].push(ele);
}
logDebug(sendElementsSplit);
@ -597,8 +341,7 @@ export class SendMsg extends BaseAction<OB11PostSendMsg, ReturnDataType> {
nodeMsgArray.push(nodeMsg);
if (!srcPeer) {
srcPeer = { chatType: nodeMsg.chatType, peerUid: nodeMsg.peerUid };
}
else if (srcPeer.peerUid !== nodeMsg.peerUid) {
} else if (srcPeer.peerUid !== nodeMsg.peerUid) {
needSendSelf = true;
srcPeer = selfPeer;
}

View File

@ -143,7 +143,7 @@ export interface OB11MessageText {
}
}
interface OB11MessageFileBase {
export interface OB11MessageFileBase {
data: {
thumb?: string;
name?: string;
@ -176,7 +176,7 @@ export interface OB11MessageVideo extends OB11MessageFileBase {
export interface OB11MessageAt {
type: OB11MessageDataType.at
data: {
qq: string | 'all'
qq: `${number}` | 'all'
}
}