refactor: separate implementation and handler

This commit is contained in:
Wesley F. Young 2024-10-05 20:09:30 +08:00
parent 5e06a491e3
commit e7cb70a5d8
2 changed files with 224 additions and 171 deletions

View File

@ -2,9 +2,12 @@ import { NapCatLaanaAdapter } from '..';
import { NapCatCore } from '@/core'; import { NapCatCore } from '@/core';
import { LaanaActionHandler } from '../action'; import { LaanaActionHandler } from '../action';
import fs from 'fs'; import fs from 'fs';
import { ForwardMessagePing_Operation } from '@laana-proto/def'; import {
ForwardMessagePing_Operation,
LaanaPeer,
OutgoingMessage,
} from '@laana-proto/def';
// TODO: separate implementation and handler
export class LaanaMessageActionHandler { export class LaanaMessageActionHandler {
constructor( constructor(
public core: NapCatCore, public core: NapCatCore,
@ -13,7 +16,77 @@ export class LaanaMessageActionHandler {
impl: LaanaActionHandler = { impl: LaanaActionHandler = {
sendMessage: async (params) => { sendMessage: async (params) => {
const { elements, fileCacheRecords } = await this.laana.utils.msg.laanaMessageToRaw(params.message!, params); return { msgId: await this.sendMessage(params.message!, params.targetPeer!) };
},
sendPackedMessages: async (params) => {
// first send every single message to self, then forward them to target peer
const sendMsgIds: string[] = [];
for (const message of params.messages) {
sendMsgIds.push(await this.sendMessage(message, params.targetPeer!));
}
const packedMsgId = await this.forwardMessageAsPacked(sendMsgIds, params.targetPeer!);
return {
packedMsgId,
msgIds: sendMsgIds,
};
},
getMessage: async (params) => {
return { message: await this.getMessage(params.msgId) };
},
getMessages: async (params) => {
if (params.msgIds.length === 0) {
throw new Error('消息 ID 列表不能为空');
}
return { messages: await this.getMessages(params.msgIds) };
},
getForwardedMessages: async (params) => {
return {
forwardMessage: {
refId: params.refId,
messages: await this.getForwardedMessages(params.refId)
}
};
},
getHistoryMessages: async (params) => {
return { messages: await this.getHistoryMessages(params.targetPeer!, params.lastMsgId, params.count) };
},
withdrawMessage: async (params) => {
await this.withdrawMessage(params.msgId);
return { success: true };
},
markPeerMessageAsRead: async (params) => {
await this.markPeerMessageAsRead(params.peer!);
return { success: true };
},
forwardMessage: async (params) => {
if (params.msgIds.length === 0) {
throw new Error('消息 ID 列表不能为空');
}
if (params.operation === ForwardMessagePing_Operation.AS_SINGLETONS) {
await this.forwardMessageAsSingletons(params.msgIds, params.targetPeer!);
} else {
await this.forwardMessageAsPacked(params.msgIds, params.targetPeer!);
}
return { success: true };
},
};
/**
* Send a message to a peer.
* @param msg The message to send.
* @param targetPeer The peer to send the message to.
* @returns The Laana-styled msgId of the message sent.
*/
async sendMessage(msg: OutgoingMessage, targetPeer: LaanaPeer) {
const { elements, fileCacheRecords } = await this.laana.utils.msg.laanaMessageToRaw(msg, targetPeer);
let cacheSize = 0; let cacheSize = 0;
try { try {
@ -29,7 +102,7 @@ export class LaanaMessageActionHandler {
10000; // fallback timeout 10000; // fallback timeout
const sentMsgOrEmpty = await this.core.apis.MsgApi.sendMsg( const sentMsgOrEmpty = await this.core.apis.MsgApi.sendMsg(
await this.laana.utils.msg.laanaPeerToRaw(params.targetPeer!), await this.laana.utils.msg.laanaPeerToRaw(targetPeer),
elements, elements,
true, // TODO: add 'wait complete' (bool) field true, // TODO: add 'wait complete' (bool) field
estimatedSendMsgTimeout, estimatedSendMsgTimeout,
@ -44,38 +117,19 @@ export class LaanaMessageActionHandler {
if (!sentMsgOrEmpty) { if (!sentMsgOrEmpty) {
throw Error('消息发送失败'); throw Error('消息发送失败');
} }
return { return this.laana.utils.msg.encodeMsgToLaanaMsgId(
msgId: this.laana.utils.msg.encodeMsgToLaanaMsgId(
sentMsgOrEmpty.msgId, sentMsgOrEmpty.msgId,
sentMsgOrEmpty.chatType, sentMsgOrEmpty.chatType,
sentMsgOrEmpty.peerUid, sentMsgOrEmpty.peerUid,
), );
};
},
sendPackedMessages: async (params) => {
// first send every single message to self, then forward them to target peer
// send message one by one
const sendMsgIds: string[] = [];
for (const message of params.messages) {
sendMsgIds.push((await this.impl.sendMessage!({ targetPeer: params.targetPeer, message })).msgId);
} }
await this.impl.forwardMessage!({ /**
msgIds: sendMsgIds, * Get a message by its Laana-styled msgId.
targetPeer: params.targetPeer, * @param laanaMsgId The Laana-styled msgId of the message.
operation: ForwardMessagePing_Operation.AS_PACKED, */
}); async getMessage(laanaMsgId: string) {
const { msgId, chatType, peerUid } = this.laana.utils.msg.decodeLaanaMsgId(laanaMsgId);
return {
packedMsgId: '', // unimplemented
msgIds: sendMsgIds,
};
},
getMessage: async (params) => {
const { msgId, chatType, peerUid } = this.laana.utils.msg.decodeLaanaMsgId(params.msgId);
const msgListWrapper = await this.core.apis.MsgApi.getMsgsByMsgId( const msgListWrapper = await this.core.apis.MsgApi.getMsgsByMsgId(
{ chatType, peerUid, guildId: '' }, { chatType, peerUid, guildId: '' },
[msgId], [msgId],
@ -84,26 +138,21 @@ export class LaanaMessageActionHandler {
throw new Error('消息不存在'); throw new Error('消息不存在');
} }
const msg = msgListWrapper.msgList[0]; const msg = msgListWrapper.msgList[0];
return { return await this.laana.utils.msg.rawMessageToLaana(msg);
message: await this.laana.utils.msg.rawMessageToLaana(msg),
};
},
getMessages: async (params) => {
if (params.msgIds.length === 0) {
throw new Error('消息 ID 列表不能为空');
} }
const msgIdWrappers = params.msgIds.map(msgId => this.laana.utils.msg.decodeLaanaMsgId(msgId)); /**
* Get multiple messages by their Laana-styled msgIds.
* This method is optimized for fetching multiple messages at once.
* @param laanaMsgIds The Laana-styled msgIds of the messages.
*/
async getMessages(laanaMsgIds: string[]) {
const msgIdWrappers = laanaMsgIds.map(msgId => this.laana.utils.msg.decodeLaanaMsgId(msgId));
// check whether chatType and peerUid for each message are the same // check whether chatType and peerUid for each message are the same
const firstMsg = msgIdWrappers[0]; const firstMsg = msgIdWrappers[0];
if (msgIdWrappers.some(msg => msg.chatType !== firstMsg.chatType || msg.peerUid !== firstMsg.peerUid)) { if (msgIdWrappers.some(msg => msg.chatType !== firstMsg.chatType || msg.peerUid !== firstMsg.peerUid)) {
return { // one request for each message
// one request per message return await Promise.all(msgIdWrappers.map(async ({ msgId, chatType, peerUid }) => {
messages: await Promise.all(
params.msgIds.map(msgId => this.laana.utils.msg.decodeLaanaMsgId(msgId))
.map(async ({ msgId, chatType, peerUid }) => {
const msgListWrapper = await this.core.apis.MsgApi.getMsgsByMsgId( const msgListWrapper = await this.core.apis.MsgApi.getMsgsByMsgId(
{ chatType, peerUid, guildId: '' }, { chatType, peerUid, guildId: '' },
[msgId], [msgId],
@ -112,25 +161,23 @@ export class LaanaMessageActionHandler {
throw new Error('消息不存在'); throw new Error('消息不存在');
} }
return await this.laana.utils.msg.rawMessageToLaana(msgListWrapper.msgList[0]); return await this.laana.utils.msg.rawMessageToLaana(msgListWrapper.msgList[0]);
}) }));
)
};
} else { } else {
// a single request for all messages // a single request for all messages
const msgListWrapper = await this.core.apis.MsgApi.getMsgsByMsgId( const msgList = (await this.core.apis.MsgApi.getMsgsByMsgId(
{ chatType: firstMsg.chatType, peerUid: firstMsg.peerUid, guildId: '' }, { chatType: firstMsg.chatType, peerUid: firstMsg.peerUid, guildId: '' },
msgIdWrappers.map(msg => msg.msgId), msgIdWrappers.map(msg => msg.msgId),
); )).msgList;
return { return await Promise.all(msgList.map(msg => this.laana.utils.msg.rawMessageToLaana(msg)));
messages: await Promise.all( }
msgListWrapper.msgList.map(msg => this.laana.utils.msg.rawMessageToLaana(msg)),
),
};
} }
},
getForwardedMessages: async (params) => { /**
const { rootMsgLaanaId, currentMsgId } = this.laana.utils.msg.decodeLaanaForwardMsgRefId(params.refId); * Get forwarded messages by a Laana-styled refId.
* @param refId The Laana-styled refId of the message.
*/
async getForwardedMessages(refId: string) {
const { rootMsgLaanaId, currentMsgId } = this.laana.utils.msg.decodeLaanaForwardMsgRefId(refId);
const decodedRootMsgId = this.laana.utils.msg.decodeLaanaMsgId(rootMsgLaanaId); const decodedRootMsgId = this.laana.utils.msg.decodeLaanaMsgId(rootMsgLaanaId);
const rawForwardedMessages = await this.core.apis.MsgApi.getMultiMsg( const rawForwardedMessages = await this.core.apis.MsgApi.getMultiMsg(
{ {
@ -144,70 +191,66 @@ export class LaanaMessageActionHandler {
if (!rawForwardedMessages || rawForwardedMessages.result !== 0 || rawForwardedMessages.msgList.length === 0) { if (!rawForwardedMessages || rawForwardedMessages.result !== 0 || rawForwardedMessages.msgList.length === 0) {
throw new Error('获取转发消息失败'); throw new Error('获取转发消息失败');
} }
return { return await Promise.all(
forwardMessage: {
refId: params.refId,
messages: await Promise.all(
rawForwardedMessages.msgList.map(async msg => { rawForwardedMessages.msgList.map(async msg => {
return await this.laana.utils.msg.rawMessageToLaana(msg, rootMsgLaanaId); return await this.laana.utils.msg.rawMessageToLaana(msg, rootMsgLaanaId);
}), }),
), );
} }
};
},
getHistoryMessages: async (params) => { // TODO: add 'reverseOrder' field /**
const { msgId } = this.laana.utils.msg.decodeLaanaMsgId(params.lastMsgId); * Get history messages of a peer.
* @param peer The peer to get history messages from.
* @param lastMsgId The Laana-styled msgId of the last message.
* @param count The number of messages to get.
*/
async getHistoryMessages(peer: LaanaPeer, lastMsgId: string, count: number) {
const { msgId } = this.laana.utils.msg.decodeLaanaMsgId(lastMsgId);
const msgListWrapper = await this.core.apis.MsgApi.getMsgHistory( const msgListWrapper = await this.core.apis.MsgApi.getMsgHistory(
await this.laana.utils.msg.laanaPeerToRaw(params.targetPeer!), await this.laana.utils.msg.laanaPeerToRaw(peer),
msgId, msgId,
params.count, count,
); );
if (msgListWrapper.msgList.length === 0) { if (msgListWrapper.msgList.length === 0) {
this.core.context.logger.logWarn('获取历史消息失败', params.targetPeer!.uin); this.core.context.logger.logWarn('获取历史消息失败', peer.uin);
} }
return { // TODO: check order return await Promise.all(
messages: await Promise.all(
msgListWrapper.msgList.map(async msg => { msgListWrapper.msgList.map(async msg => {
return await this.laana.utils.msg.rawMessageToLaana(msg); return await this.laana.utils.msg.rawMessageToLaana(msg);
}), }),
), );
}; }
},
withdrawMessage: async (params) => { /**
const { msgId, chatType, peerUid } = this.laana.utils.msg.decodeLaanaMsgId(params.msgId); * Withdraw a message by its Laana-styled msgId.
try { * @param laanaMsgId The Laana-styled msgId of the message.
*/
async withdrawMessage(laanaMsgId: string) {
const { msgId, chatType, peerUid } = this.laana.utils.msg.decodeLaanaMsgId(laanaMsgId);
await this.core.apis.MsgApi.recallMsg( await this.core.apis.MsgApi.recallMsg(
{ chatType, peerUid, guildId: '' }, { chatType, peerUid, guildId: '' },
msgId, msgId,
); );
} catch (e) {
throw new Error(`消息撤回失败: ${e}`);
} }
return { success: true };
},
markPeerMessageAsRead: async (params) => { /**
const { chatType, peerUid } = await this.laana.utils.msg.laanaPeerToRaw(params.peer!); * Mark a peer's messages as read.
try { * @param peer The peer to mark messages as read.
*/
async markPeerMessageAsRead(peer: LaanaPeer) {
const { chatType, peerUid } = await this.laana.utils.msg.laanaPeerToRaw(peer);
await this.core.apis.MsgApi.setMsgRead({ chatType, peerUid }); await this.core.apis.MsgApi.setMsgRead({ chatType, peerUid });
} catch (e) {
throw new Error(`标记消息已读失败: ${e}`);
} }
return { success: true };
},
forwardMessage: async (params) => { /**
if (params.msgIds.length === 0) { * Forward messages as singletons to a target peer.
throw new Error('消息 ID 列表不能为空'); * @param laanaMsgIds The Laana-styled msgIds of the messages to forward.
} * @param targetPeer The peer to forward the messages to.
const { chatType, peerUid } = this.laana.utils.msg.decodeLaanaMsgId(params.msgIds[0]); */
const msgIdList = params.msgIds async forwardMessageAsSingletons(laanaMsgIds: string[], targetPeer: LaanaPeer) {
.map(msgId => this.laana.utils.msg.decodeLaanaMsgId(msgId).msgId); const { chatType, peerUid } = this.laana.utils.msg.decodeLaanaMsgId(laanaMsgIds[0]);
const destPeer = await this.laana.utils.msg.laanaPeerToRaw(params.targetPeer!); const msgIdList = laanaMsgIds.map(msgId => this.laana.utils.msg.decodeLaanaMsgId(msgId).msgId);
const destPeer = await this.laana.utils.msg.laanaPeerToRaw(targetPeer);
if (params.operation === ForwardMessagePing_Operation.AS_SINGLETONS) {
const ret = await this.core.apis.MsgApi.forwardMsg( const ret = await this.core.apis.MsgApi.forwardMsg(
{ chatType, peerUid, guildId: '' }, { chatType, peerUid, guildId: '' },
destPeer, destPeer,
@ -216,12 +259,22 @@ export class LaanaMessageActionHandler {
if (ret.result !== 0) { if (ret.result !== 0) {
throw new Error(`转发消息失败 ${ret.errMsg}`); throw new Error(`转发消息失败 ${ret.errMsg}`);
} }
} else {
throw new Error('unimplemented');
// TODO: refactor NTQQMsgApi.multiForwardMsg
} }
return { success: true }; /**
}, * Forward messages as packed to a target peer.
}; * @param laanaMsgIds The Laana-styled msgIds of the messages to forward.
* @param targetPeer The peer to forward the messages to.
*/
async forwardMessageAsPacked(laanaMsgIds: string[], targetPeer: LaanaPeer) {
const { chatType, peerUid } = this.laana.utils.msg.decodeLaanaMsgId(laanaMsgIds[0]);
const msgIdList = laanaMsgIds.map(msgId => this.laana.utils.msg.decodeLaanaMsgId(msgId).msgId);
const destPeer = await this.laana.utils.msg.laanaPeerToRaw(targetPeer);
const retMsg = await this.core.apis.MsgApi.multiForwardMsg(
{ chatType, peerUid, guildId: '' },
destPeer,
msgIdList,
);
return this.laana.utils.msg.encodeMsgToLaanaMsgId(retMsg.msgId, retMsg.chatType, retMsg.peerUid);
}
} }

View File

@ -33,7 +33,7 @@ type Laana2RawConverters = {
// eslint-disable-next-line // eslint-disable-next-line
// @ts-ignore // @ts-ignore
msgContent: Extract<OutgoingMessage['content'], { oneofKind: key; }>[key], msgContent: Extract<OutgoingMessage['content'], { oneofKind: key; }>[key],
params: SendMessagePing, targetPeer: LaanaPeer,
) => PromiseLike<{ ) => PromiseLike<{
elements: SendMessageElement[], elements: SendMessageElement[],
fileCacheRecords: SentMessageFileCacheRecord[], fileCacheRecords: SentMessageFileCacheRecord[],
@ -56,7 +56,7 @@ export class LaanaMessageUtils {
} }
l2r: Laana2RawConverters = { l2r: Laana2RawConverters = {
bubble: async (msgContent, params) => { bubble: async (msgContent, targetPeer) => {
function at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement { function at(atUid: string, atNtUid: string, atType: AtType, atName: string): SendTextElement {
return { return {
elementType: ElementType.TEXT, elementType: ElementType.TEXT,
@ -77,7 +77,7 @@ export class LaanaMessageUtils {
if (msgContent.repliedMsgId) { if (msgContent.repliedMsgId) {
const replyMsg = ( const replyMsg = (
await this.core.apis.MsgApi.getMsgsByMsgId( await this.core.apis.MsgApi.getMsgsByMsgId(
await this.laanaPeerToRaw(params.targetPeer!), await this.laanaPeerToRaw(targetPeer),
[msgContent.repliedMsgId] [msgContent.repliedMsgId]
) )
).msgList[0]; ).msgList[0];
@ -112,7 +112,7 @@ export class LaanaMessageUtils {
}, },
}); });
} else if (content.oneofKind === 'at') { } else if (content.oneofKind === 'at') {
if (params.targetPeer?.type !== LaanaPeer_Type.GROUP) { if (targetPeer.type !== LaanaPeer_Type.GROUP) {
throw Error('试图在私聊会话中使用 At'); throw Error('试图在私聊会话中使用 At');
} }
@ -125,7 +125,7 @@ export class LaanaMessageUtils {
} }
const atMember = await this.core.apis.GroupApi const atMember = await this.core.apis.GroupApi
.getGroupMember(params.targetPeer.uin, content.at.uin); .getGroupMember(targetPeer.uin, content.at.uin);
if (atMember) { if (atMember) {
elements.push(at( elements.push(at(
content.at.uin, content.at.uin,
@ -298,7 +298,7 @@ export class LaanaMessageUtils {
}; };
} }
async laanaMessageToRaw(msg: OutgoingMessage, params: SendMessagePing) { async laanaMessageToRaw(msg: OutgoingMessage, targetPeer: LaanaPeer) {
if (!msg.content.oneofKind) { if (!msg.content.oneofKind) {
throw Error('消息内容类型未知'); throw Error('消息内容类型未知');
} }
@ -306,7 +306,7 @@ export class LaanaMessageUtils {
// eslint-disable-next-line // eslint-disable-next-line
// @ts-ignore // @ts-ignore
msg.content[msg.content.oneofKind], msg.content[msg.content.oneofKind],
params targetPeer
); );
} }