koishi-plugin-adapter-iirose
Version:
[IIROSE-蔷薇花园](https://iirose.com/)适配器
473 lines (419 loc) • 13.5 kB
text/typescript
import { Context, MessageEncoder, h } from 'koishi';
import { } from '@koishijs/assets';
import { cacheSentMessage, ensureNewlineBefore, getMediaMetadata, rgbaToHex } from '../utils/utils';
import { decode as unescapeHtml } from '../utils/entities';
import PrivateMessage from '../encoder/messages/PrivateMessage';
import PublicMessage from '../encoder/messages/PublicMessage';
import { IIROSE_WSsend } from '../utils/ws';
import { IIROSE_Bot } from './bot';
export class IIROSE_BotMessageEncoder extends MessageEncoder<Context, IIROSE_Bot>
{
private outDataOringin: string = '';
private outDataOringinObj: string = '';
private currentMessageId: string = '';
private audioSent: boolean = false;
private isMarkdown: boolean = false;
private resetState(): void
{
this.outDataOringin = '';
this.outDataOringinObj = '';
this.currentMessageId = '';
this.audioSent = false;
this.isMarkdown = false;
}
async flush(): Promise<void>
{
if (this.bot.config.onlyHangUpMode) { return; }
// 如果已经发送了音频消息且没有其他内容,则不发送额外消息
if (this.audioSent && this.outDataOringin.length <= 0)
{
this.resetState();
return;
}
if (this.outDataOringin.length <= 0)
{
this.resetState();
return;
}
// 清理消息末尾多余的换行符
this.outDataOringin = this.outDataOringin.replace(/\n+$/, '');
if (this.outDataOringin.length <= 0)
{
this.resetState();
return;
}
// 如果是 Markdown 消息,添加前缀
if (this.isMarkdown)
{
this.outDataOringin = `\\\\\\*\n${this.outDataOringin}`;
}
// 在实际发送消息时生成消息ID和消息对象
if (this.channelId.startsWith('public:'))
{
const result = PublicMessage(this.outDataOringin, rgbaToHex(this.bot.config.color));
this.currentMessageId = result.messageId;
this.outDataOringinObj = result.data;
} else if (this.channelId.startsWith('private:'))
{
const result = PrivateMessage(this.channelId.split(':')[1], this.outDataOringin, rgbaToHex(this.bot.config.color));
this.currentMessageId = result.messageId;
this.outDataOringinObj = result.data;
}
await IIROSE_WSsend(this.bot, this.outDataOringinObj);
if (this.currentMessageId)
{
this.results.push({ id: this.currentMessageId });
await cacheSentMessage(this.bot, this.channelId, this.currentMessageId, this.outDataOringin);
}
this.resetState();
}
// 获取消息ID
getMessageId(): string
{
return this.currentMessageId;
}
async visit(element: h): Promise<void>
{
const { type, attrs, children } = element;
switch (type)
{
case 'video': {
let url = attrs.link || attrs.url || attrs.src;
// 如果是 https 协议,直接使用
if (url.startsWith('http'))
{
// 直接使用
} else
{
// 使用 assets 服务转存非 https 协议的资源
try
{
const videoElement = `${h.video(url)}`;
const transformedContent = await this.bot.ctx.assets.transform(videoElement);
// 从转存后的内容中提取 URL
const urlMatch = transformedContent.match(/src="([^"]+)"/);
if (urlMatch && urlMatch[1])
{
url = unescapeHtml(urlMatch[1]);
} else
{
throw new Error('无法从转存结果中提取视频 URL');
}
} catch (error)
{
this.outDataOringin = ensureNewlineBefore(this.outDataOringin);
this.outDataOringin += '[视频转存异常]';
this.outDataOringin += '\n';
this.bot.loggerError(error);
break;
}
}
// 直接发送视频链接
this.outDataOringin = ensureNewlineBefore(this.outDataOringin);
this.outDataOringin += `[${url}]`;
this.outDataOringin += '\n';
// 音乐卡片不好用,不用了,直接发视频即可。
// const metadata = await getMediaMetadata(url, this.bot.ctx);
// const obj: musicOrigin = {
// type: "video",
// name: attrs.name || metadata.title,
// signer: attrs.author || metadata.artist,
// cover: attrs.cover || metadata.picture,
// link: url,
// url: url,
// duration: attrs.duration || metadata.duration,
// bitRate: attrs.bitRate || metadata.bitrate,
// color: attrs.color,
// lyrics: (attrs.lyrics) ? attrs.lyrics : (Math.random() > 0.9) ? '' : '俺不中嘞,插件没给俺歌词啊喵',
// origin: (attrs.origin) ? attrs.origin : null
// };
// this.bot.internal.makeMusic(obj);
// // ctx.emit('iirose/makeMusic', obj);
break;
}
case 'audio': {
let url = attrs.link || attrs.url || attrs.src;
// 如果是 https 协议,直接使用
if (url.startsWith('http'))
{
// 直接使用
} else
{
// 使用 assets 服务转存非 https 协议的资源
try
{
const audioElement = `${h.audio(url)}`;
const transformedContent = await this.bot.ctx.assets.transform(audioElement);
// 从转存后的内容中提取 URL
const urlMatch = transformedContent.match(/src="([^"]+)"/);
if (urlMatch && urlMatch[1])
{
url = unescapeHtml(urlMatch[1]);
} else
{
throw new Error('无法从转存结果中提取音频 URL');
}
} catch (error)
{
this.outDataOringin += '[音频转存异常]';
this.bot.loggerError(error);
break;
}
}
// 确保URL以.weba结尾,IIROSE平台需要此后缀才能正确识别为语音消息
if (!url.endsWith('.weba'))
{
if (url.includes('?'))
{
url += `&iiroseaudio=.weba`;
} else
{
url += `?iiroseaudio=.weba`;
}
}
let audioMessage: string;
let audioMessageId: string;
if (this.channelId.startsWith('public:'))
{
const result = PublicMessage(url, rgbaToHex(this.bot.config.color));
audioMessage = result.data;
audioMessageId = result.messageId;
} else if (this.channelId.startsWith('private:'))
{
const result = PrivateMessage(this.channelId.split(':')[1], url, rgbaToHex(this.bot.config.color));
audioMessage = result.data;
audioMessageId = result.messageId;
}
if (audioMessage)
{
await IIROSE_WSsend(this.bot, audioMessage);
// 标记已发送音频消息,防止flush时发送空格消息
this.audioSent = true;
if (audioMessageId)
{
this.results.push({ id: audioMessageId });
await cacheSentMessage(this.bot, this.channelId, audioMessageId, url);
}
}
break;
}
case 'image':
case 'img': {
let url = attrs.src;
// 如果是 https 协议,直接使用
if (url.startsWith('http'))
{
this.outDataOringin = ensureNewlineBefore(this.outDataOringin);
this.outDataOringin += `[${url}#e]`;
this.outDataOringin += '\n';
break;
}
// 使用 assets 服务转存非 https 协议的资源
try
{
const imgElement = `${h.image(url)}`;
const transformedContent = await this.bot.ctx.assets.transform(imgElement);
// 从转存后的内容中提取 URL
const urlMatch = transformedContent.match(/src="([^"]+)"/);
if (urlMatch && urlMatch[1])
{
const transformedUrl = unescapeHtml(urlMatch[1]);
this.outDataOringin = ensureNewlineBefore(this.outDataOringin);
this.outDataOringin += `[${transformedUrl}#e]`;
this.outDataOringin += '\n';
} else
{
throw new Error('无法从转存结果中提取图片 URL');
}
} catch (error)
{
this.outDataOringin = ensureNewlineBefore(this.outDataOringin);
this.outDataOringin += '[图片转存异常]';
this.outDataOringin += '\n';
this.bot.loggerError(error);
}
break;
}
case 'quote': {
let id = attrs.id;
if (!id)
{
const messageKeys = this.bot.getMessageKeys();
id = messageKeys[messageKeys.length - 1];
}
const messData = await this.bot.getMessage('', id);
if (messData)
{
this.outDataOringin = `${messData.content} (_hr) ${messData.user.name}_${Math.round(new Date().getTime() / 1e3)} (hr_) ` + this.outDataOringin;
} else
{
this.bot.loggerWarn(`[Quote处理] 未找到消息ID: ${id}`);
}
break;
}
case 'text': {
if (this.outDataOringin.length > 0)
{
this.outDataOringin += `${attrs.content}`;
} else
{
this.outDataOringin += `${attrs.content}`;
}
break;
}
case 'i18n': {
try
{
const path = attrs?.path;
if (path && this.bot.ctx.i18n)
{
const locales = this.bot.ctx.i18n.fallback([]);
try
{
const text = this.bot.ctx.i18n.render(locales, [path], attrs || {});
if (text && typeof text === 'string')
{
this.outDataOringin += text;
break;
}
} catch (e)
{
// i18n解析失败,使用fallback
}
}
this.outDataOringin += `[${path || 'i18n'}]`;
} catch (error)
{
this.outDataOringin += `[${attrs?.path || 'i18n'}]`;
}
break;
}
case 'at': {
if (attrs.hasOwnProperty('id'))
{
const user = await this.bot.getUser(attrs.id);
const name = user?.name;
if (name && name !== ("用户数据库初始化ing"))
{
this.outDataOringin += ` [*${name}*] `;
} else
{
this.outDataOringin += ` [@${attrs.id}@] `;
}
} else if (attrs.hasOwnProperty('name'))
{
this.outDataOringin += ` [*${attrs.name}*] `;
} else if (attrs.hasOwnProperty('roomId'))
{
this.outDataOringin += ` [_${attrs.roomId}_] `;
}
break;
}
case 'sharp': {
// 提及频道
let channelId = attrs.id;
// 如果 id 不在 attrs 中,尝试从子元素获取
if (!channelId && children.length > 0)
{
channelId = children.map(c => c.attrs.content).join('');
}
if (channelId)
{
this.outDataOringin += ` [_${channelId.replace(/^public:|private:/, '')}_] `;
}
return; // 阻止后续对子节点的重复渲染
}
case 'a': {
// 链接
let url = attrs.href;
if (!url && children.length > 0)
{
url = children.map(c => c.attrs.content).join('');
}
if (url)
{
this.outDataOringin += `\\${url}`;
}
return; // 阻止后续对子节点的重复渲染
}
case 'b':
case 'strong': {
this.isMarkdown = true;
this.outDataOringin += '**';
await this.render(children);
this.outDataOringin += '**';
return;
}
case 'i':
case 'em': {
this.isMarkdown = true;
this.outDataOringin += '*';
await this.render(children);
this.outDataOringin += '*';
return;
}
case 'u':
case 'ins': {
this.isMarkdown = true;
this.outDataOringin += '__';
await this.render(children);
this.outDataOringin += '__';
return;
}
case 's':
case 'del': {
this.isMarkdown = true;
this.outDataOringin += '~~';
await this.render(children);
this.outDataOringin += '~~';
return;
}
case 'spl': {
this.isMarkdown = true;
this.outDataOringin += '||';
await this.render(children);
this.outDataOringin += '||';
return;
}
case 'code': {
this.isMarkdown = true;
this.outDataOringin += '`';
await this.render(children);
this.outDataOringin += '`';
return;
}
case 'iirose:markdown':
case 'markdown': {
this.isMarkdown = true;
break;
}
case 'br': {
this.outDataOringin += '\n';
break;
}
case 'message': {
await this.flush();
await this.render(children);
return;
}
case 'p':
case '':
default: {
break;
}
}
await this.render(children);
// p元素 处理完子元素后需要添加换行符
if (type === 'p' && this.outDataOringin.length > 0)
{
this.outDataOringin += '\n';
}
}
async render(elements: h[]): Promise<void>
{
for (const element of elements)
{
await this.visit(element);
}
}
}