import {
Emoticon,
GroupMessageSender,
Image,
Mention,
Message,
MessageContent,
MessageContentType,
MessageHeader,
MessageId,
MessageSender,
MessageType,
PlainText,
Quote,
Voice,
} from './Message';
import {OutboundMessageChain} from './OutboundMessageChain';
import {escapeMirai} from '../utils/TextUtils';
import {shouldNotBeMultipart} from '../utils/InspectUtils';
import {MiraiService} from '../services/MiraiService';
/**
* 入站消息
*/
export class InboundMessage<T extends Message> {
/**
* @constructor
* @hideconstructor
* @param {Message} message 消息
* @param {MiraiService} srvc 服务接口
*/
constructor(public readonly message: T, private readonly srvc: MiraiService) {
}
/**
* 获取消息头
*
* @returns {MessageHeader}
* @since 0.0.1
*/
public get header(): MessageHeader {
return this.message.messageChain[0] as MessageHeader;
}
/**
* 获取发送方 ID
*
* @since 0.0.1
*/
public get id(): number {
return this.header.id;
}
/**
* 获取发送方
*
* @returns {PrivateMessageSender | GroupMessageSender}
* @since 0.0.1
*/
public get sender(): MessageSender {
return this.message.sender;
}
/**
* 撤销这条消息。
*
* 当该消息为好友消息或临时消息时,尝试撤销会导致 Promise 被立刻拒绝。
*
* 如果机器人没有权限撤销成员消息,那么返回的 {BasicResponse} 的 `code` 为非 0 值,具体见 {@link StatusCode}
*
* @since 0.0.1
*/
public revoke(): Promise<void> {
if (this.message.type !== MessageType.GROUP_MESSAGE) {
return Promise.reject('Cannot revoke a message of others');
} else {
return this.srvc.revokeMessage(this.message.sender.id, this.id);
}
}
/**
* 回复这条消息。
* <br>
* 该函数会自动添加引用回复(自从 0.1.1 版本)。
* <br>
* 对于特殊类型的消息(如 XML, JSON, 语音消息等)将不会生成引用回复部分或 at 部分(自从 0.1.8 版本)。
*
* @param {OutboundMessageChain} chain 消息链
* @param {boolean} useAt 回复时提到该消息的发送人。对于非群消息则该参数被忽略。默认为否。(自从 0.1.4 版本添加)
* @see OutboundMessageChain
* @since 0.0.1
*/
public reply(chain: OutboundMessageChain, useAt = false): Promise<MessageId> {
const header = this.header;
const sender = this.message.sender;
const quoteBlock: Quote = {
type: MessageContentType.QUOTE,
id: header.id,
groupId: this.message.type === MessageType.GROUP_MESSAGE ? ((sender) as GroupMessageSender).group.id : 0,
senderId: sender.id,
targetId: sender.id,
origin: this.message.messageChain,
};
if (!shouldNotBeMultipart(chain.chain)) {
chain.prepend(quoteBlock); // FIXME: it doesn't quite work as intended.
}
if (useAt && this.message.type === MessageType.GROUP_MESSAGE && !shouldNotBeMultipart(chain.chain)) {
const mentionBlock: Mention = {
type: MessageContentType.MENTION,
target: sender.id,
display: '',
};
chain.append(mentionBlock);
}
switch (this.message.type) {
case MessageType.GROUP_MESSAGE:
return this.srvc.sendToGroup((sender as GroupMessageSender).group.id, chain);
case MessageType.FRIEND_MESSAGE:
return this.srvc.sendToFriend(sender.id, chain);
case MessageType.TEMP_MESSAGE:
return this.srvc.sendToTemp((sender as GroupMessageSender).group.id, this.message.sender.id, chain);
default:
return Promise.reject('Cannot reply to an event!');
}
}
/**
* 这条消息是否为纯文字消息。
*
* @remarks 如果消息包含表情,则也会被判定为非纯文字消息。
* @since 0.0.1
*/
public isPlainTextMessage(): boolean {
const firstPiece = this.getFirstPieceOfMessageContent();
return firstPiece != null && firstPiece.type === MessageContentType.TEXT;
}
/**
* 这条消息是否为图片消息。
*
* @since 0.0.1
*/
public isImageMessage(): boolean {
const firstPiece = this.getFirstPieceOfMessageContent();
return firstPiece != null && firstPiece.type === MessageContentType.IMAGE;
}
/**
* 这条消息是否提到了机器人。
*
* @since 0.1.3
*/
public isMentioned(): boolean {
return this.message.messageChain
.filter(it => it.type === MessageContentType.MENTION && (it as Mention).target === this.srvc.account)
.length > 0;
}
/**
* 获取消息中第一个由发送者产生的内容。这会跳过消息头和引用部分(如果有)。
*
* 如果发送者没有生产任何内容,则返回 undefined.
*
* @returns {MessageContent} 消息中第一个由发送者产生的内容
* @see MessageContent
* @since 0.0.1
*/
public getFirstPieceOfMessageContent(): MessageContent | undefined {
for (const msg of this.message.messageChain) {
if (msg.type === MessageContentType.MESSAGE_HEADER || msg.type === MessageContentType.QUOTE) {
continue;
}
return msg;
}
return undefined;
}
/**
* 提取消息中的文字。
* <br>
* 当消息中包含多个内容时,文字将会被滤出并用连接符连接。
*
* @param {string} joiner 连接符,默认为换行
* @since 0.1.3
*/
public extractText(joiner = '\n'): string {
return this.message.messageChain
.filter(it => it.type === MessageContentType.TEXT)
.map(it => it as PlainText)
.map(it => it.text)
.join(joiner);
}
/**
* 获取消息中的文字。
*
* @deprecated 使用 {@link InboundMessage#extractText} 代替。
* @since 0.0.1
*/
public getPlainText(): string {
return this.message.messageChain
.filter(it => it.type === MessageContentType.TEXT)
.map(it => it as PlainText)
.map(it => it.text)
.join('\n');
}
/**
* 将消息转为 Mirai 码。不可被转换的部分(未在规范中指明的部分)会被替换为 `[mirai:not-specified:${type}]`.
* 特殊的,回复引用部分将被丢弃。
* <br>
* 由于上游文档的不一致性,部分在文档中的内容也会被替换为 `[mirai:not-specified:...]`.
* 这些内容包括 `PokeMessage` ({@link InteractMessage}) 和 `VipFace` (未在 `mirai-api-http` 中实现).
* <br>
* 对于可以推测的内容,将转换为可能的形式,如 {@link Voice} 会被转换为 `[mirai:voice:${voiceId}]` 的形式。
* 消息头则按照 `mirai-console` 内的实现转换为 `[mirai:source:${id}]`. 如果不希望保留消息头,则传入 `true`.
*
* @param {boolean} discardHeader 是否去除消息头,默认为否
* @since 0.1.4
* @see {@link https://github.com/mamoe/mirai/blob/dev/docs/mirai-code-specification.md 规范文档}
*/
public toMiraiCode(discardHeader = false): string {
return this.message.messageChain
.map(it => {
switch (it.type) {
case MessageContentType.TEXT:
return escapeMirai((it as PlainText).text);
case MessageContentType.MENTION_ALL:
return '[mirai:atall]';
case MessageContentType.MENTION:
return `[mirai:at:${(it as Mention).target},${escapeMirai((it as Mention).display)}]`;
case MessageContentType.EMOTICON:
return `[mirai:face:${(it as Emoticon).faceId}]`;
case MessageContentType.TRANSIENT_IMAGE:
return `[mirai:flash:${(it as Image).imageId}]`;
case MessageContentType.IMAGE:
return `[mirai:image:${(it as Image).imageId}]`;
case MessageContentType.VOICE:
return `[mirai:voice:${(it as Voice).voiceId}]`;
case MessageContentType.SOURCE:
return discardHeader ? '' : `[mirai:source:${(it as MessageHeader).id}]`;
case MessageContentType.QUOTE:
return '';
default:
return `[mirai:not-specified:${it.type}]`;
}
})
.join('');
}
}
Source