qq-official-bot
Version:
366 lines (365 loc) • 10.9 kB
JavaScript
"use strict";
/**
* 消息构建器 - 专门负责构建消息内容
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MessageBuilder = void 0;
const utils_1 = require("../utils");
const crypto_1 = require("crypto");
/**
* 消息构建器
* 专门负责将Sendable类型的消息转换为API所需的格式
*/
class MessageBuilder {
constructor(appid, baseUrl, source) {
this.appid = appid;
this.baseUrl = baseUrl;
this.source = source;
this.buttons = [];
this.isFile = false;
this.contentType = 'application/json';
this.brief = '';
this.messagePayload = {
msg_seq: (0, crypto_1.randomInt)(1, 1000000),
content: ''
};
this.filePayload = {
srv_send_msg: true
};
if (source?.id) {
this.messagePayload.msg_id = source.id;
}
}
/**
* 构建消息
*/
async build(message) {
await this.processMessage(message);
await this.processButtons();
return {
messagePayload: this.messagePayload,
filePayload: this.filePayload,
isFile: this.isFile,
contentType: this.contentType,
brief: this.brief
};
}
/**
* 处理消息内容
*/
async processMessage(message) {
if (!Array.isArray(message)) {
message = [message];
}
const messageQueue = [...message];
while (messageQueue.length) {
const elem = messageQueue.shift();
if (typeof elem === 'string') {
const parsedElems = this.parseFromTemplate(elem);
messageQueue.unshift(...parsedElems);
continue;
}
await this.processElement(elem);
}
}
/**
* 处理单个消息元素
*/
async processElement(elem) {
const { type, ...data } = elem;
switch (type) {
case 'reply':
this.handleReply(elem);
break;
case 'at':
this.handleAt(elem);
break;
case 'link':
this.handleLink(elem);
break;
case 'text':
this.handleText(elem);
break;
case 'face':
this.handleFace(elem);
break;
case 'image':
case 'audio':
case 'video':
await this.handleMedia(elem);
break;
case 'markdown':
this.handleMarkdown(elem);
break;
case 'keyboard':
this.handleKeyboard(elem);
break;
case 'button':
this.handleButton(elem);
break;
case 'embed':
this.handleEmbed(elem);
break;
case 'ark':
this.handleArk(elem);
break;
default:
console.warn(`未知的消息元素类型: ${type}`);
}
}
/**
* 处理回复元素
*/
handleReply(elem) {
if (elem.event_id) {
this.messagePayload.event_id = elem.event_id;
this.brief += `<reply,event_id=${elem.event_id}>`;
}
else if (elem.id) {
this.messagePayload.msg_id = elem.id;
this.messagePayload.message_reference = {
message_id: elem.id
};
this.brief += `<reply,msg_id=${elem.id}>`;
}
}
/**
* 处理@元素
*/
handleAt(elem) {
const userId = elem.user_id === 'all' ? 'everyone' : elem.user_id;
this.messagePayload.content += `<@${userId}>`;
this.brief += `<at,user=${userId}>`;
}
/**
* 处理链接元素
*/
handleLink(elem) {
this.messagePayload.content += `<#${elem.channel_id}>`;
this.brief += `<link,channel=${elem.channel_id}>`;
}
/**
* 处理文本元素
*/
handleText(elem) {
this.messagePayload.content += elem.text;
this.brief += elem.text;
}
/**
* 处理表情元素
*/
handleFace(elem) {
this.messagePayload.content += `<emoji:${elem.id}>`;
this.brief += `<face,id=${elem.id}>`;
}
/**
* 处理媒体元素(图片、视频、音频)
*/
async handleMedia(elem) {
const mediaType = this.getMediaType(elem.type);
// 如果是回复消息,需要特殊处理
if (this.messagePayload.msg_id || this.messagePayload.event_id) {
if (!this.baseUrl.startsWith('/v2')) {
// 频道消息的文件处理
const fileData = await this.prepareGuildMediaData(elem);
if (typeof fileData !== 'string') {
this.messagePayload.file_image = fileData;
}
else {
this.messagePayload.image = elem.file;
}
}
else {
// 群聊/私聊消息的文件处理
this.messagePayload.msg_type = 7;
// 这里需要调用上传接口,暂时标记为需要文件上传
this.isFile = true;
this.filePayload.file_type = mediaType;
}
}
else {
// 非回复消息的处理
if (!this.baseUrl.startsWith('/v2')) {
const fileData = await this.prepareGuildMediaData(elem);
if (typeof fileData !== 'string') {
this.messagePayload.file_image = fileData;
}
else {
this.messagePayload.image = elem.file;
}
}
else {
this.isFile = true;
this.filePayload.file_type = mediaType;
}
}
this.brief += `<${elem.type}:${this.getFileHash(elem.file)}>`;
}
/**
* 处理Markdown元素
*/
handleMarkdown(elem) {
this.messagePayload.markdown = elem;
this.messagePayload.msg_type = 2;
const content = elem.content ? `content=${elem.content}` : `template_id=${elem.custom_template_id}`;
this.brief += `<markdown,${content}>`;
}
/**
* 处理键盘元素
*/
handleKeyboard(elem) {
this.messagePayload.msg_type = 2;
this.messagePayload.keyboard = elem;
this.messagePayload.bot_appid = this.appid;
this.brief += `<keyboard>`;
}
/**
* 处理按钮元素
*/
handleButton(elem) {
this.buttons.push(elem);
this.brief += `<button,data=${JSON.stringify(elem)}>`;
}
/**
* 处理嵌入元素
*/
handleEmbed(elem) {
if (this.baseUrl.startsWith('/v2'))
return;
this.messagePayload.msg_type = 4;
this.messagePayload.embed = elem;
this.brief += `<embed>`;
}
/**
* 处理ARK元素
*/
handleArk(elem) {
this.messagePayload.msg_type = 3;
this.messagePayload.ark = elem;
this.brief += `<ark>`;
}
/**
* 处理按钮组
*/
async processButtons() {
if (this.buttons.length === 0)
return;
const rows = [];
let row = [];
for (let i = 0; i < this.buttons.length; i++) {
if (row.length >= 5) {
rows.push(row);
row = [];
}
if (Array.isArray(this.buttons[i].buttons)) {
rows.push(this.buttons[i].buttons);
continue;
}
row.push(this.buttons[i]);
}
if (row.length > 0) {
rows.push(row);
}
this.messagePayload.keyboard = {
content: {
rows: rows.map(row => ({
buttons: row
}))
},
bot_appid: this.appid
};
}
/**
* 从模板字符串解析消息元素
*/
parseFromTemplate(template) {
const result = [];
const reg = /(<[^>]+>)/;
while (template.length) {
const [match] = template.match(reg) || [];
if (!match) {
if (template) {
result.push({
type: 'text',
text: template
});
}
break;
}
const index = template.indexOf(match);
const prevText = template.slice(0, index);
if (prevText) {
result.push({
type: 'text',
text: prevText
});
}
template = template.slice(index + match.length);
const [type, ...attrArr] = match.slice(1, -1).split(',');
const attrs = Object.fromEntries(attrArr.map((attr) => {
const [key, value] = attr.split('=');
try {
return [key, JSON.parse(value)];
}
catch {
return [key, value];
}
}));
result.push({
type,
...attrs
});
}
return result;
}
/**
* 准备频道媒体数据
*/
async prepareGuildMediaData(elem) {
if ('url' in elem && elem.url)
return elem.url;
if (typeof elem.file === "string" && elem.file.startsWith('http')) {
return elem.file;
}
this.contentType = 'multipart/form-data';
if (Buffer.isBuffer(elem.file)) {
return new Blob([elem.file]);
}
else if (typeof elem.file !== "string") {
throw new Error("无效的文件参数: " + elem.file);
}
else if (elem.file.startsWith("base64://")) {
return new Blob([Buffer.from(elem.file.slice(9), 'base64')]);
}
else if (/^data:[^/]+\/[^;]+;base64,/.test(elem.file)) {
return new Blob([Buffer.from(elem.file.replace(/^data:[^/]+\/[^;]+;base64,/, ''), 'base64')]);
}
else {
try {
const fs = require('node:fs/promises');
return new Blob([await fs.readFile(elem.file.replace("file://", ""))]);
}
catch {
throw new Error("无效的文件路径: " + elem.file);
}
}
}
/**
* 获取媒体类型编号
*/
getMediaType(type) {
return (['image', 'video', 'audio'].indexOf(type) + 1);
}
/**
* 获取文件哈希值
*/
getFileHash(file) {
try {
return (0, utils_1.md5)(file);
}
catch {
return 'unknown';
}
}
}
exports.MessageBuilder = MessageBuilder;