UNPKG

koishi-plugin-adapter-iirose

Version:
419 lines (371 loc) 11.4 kB
import { Context, MessageEncoder, h } from 'koishi'; import { } from '@koishijs/assets'; import { cacheSentMessage, ensureNewlineBefore, rgbaToHex, transformUrl, Unknown_User_Name } from '../utils/utils'; 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('private:')) { const result = PrivateMessage(this.channelId.split(':')[1], this.outDataOringin, rgbaToHex(this.bot.config.color)); this.currentMessageId = result.messageId; this.outDataOringinObj = result.data; } else { const result = PublicMessage(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 协议的资源 const transformedUrl = await transformUrl(this.bot, h.video(url).toString()); if (transformedUrl) { url = transformedUrl; } else { this.outDataOringin = ensureNewlineBefore(this.outDataOringin); this.outDataOringin += '[视频转存失败]'; this.outDataOringin += '\n'; break; } } // 直接发送视频链接 this.outDataOringin = ensureNewlineBefore(this.outDataOringin); this.outDataOringin += `[${url}]`; this.outDataOringin += '\n'; break; } case 'audio': { let url = attrs.link || attrs.url || attrs.src; // 如果是 https 协议,直接使用 if (url.startsWith('http')) { // 直接使用 } else { // 使用 assets 服务转存非 https 协议的资源 const transformedUrl = await transformUrl(this.bot, h.audio(url).toString()); if (transformedUrl) { url = transformedUrl; } else { this.outDataOringin += '[音频转存失败]'; 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('private:')) { const result = PrivateMessage(this.channelId.split(':')[1], url, rgbaToHex(this.bot.config.color)); audioMessage = result.data; audioMessageId = result.messageId; } else { const result = PublicMessage(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 协议的资源 const transformedUrl = await transformUrl(this.bot, h.image(url).toString()); if (transformedUrl) { this.outDataOringin = ensureNewlineBefore(this.outDataOringin); this.outDataOringin += `[${transformedUrl}#e]`; this.outDataOringin += '\n'; } else { this.outDataOringin = ensureNewlineBefore(this.outDataOringin); this.outDataOringin += '[图片转存失败]'; this.outDataOringin += '\n'; } break; } case 'quote': { let quoteId = attrs.id; if (!quoteId) { const messageKeys = this.bot.getMessageKeys(); quoteId = messageKeys[messageKeys.length - 1]; } const messData = await this.bot.getMessage('', quoteId); 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: ${quoteId}`); } 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('name')) { this.outDataOringin += ` [*${attrs.name}*] `; } else if (attrs.hasOwnProperty('roomId') || attrs.hasOwnProperty('roomid')) { this.outDataOringin += ` [_${attrs.roomId}_] `; } else if (attrs.hasOwnProperty('id')) { const userId = `${attrs.id}`.toLowerCase(); const user = await this.bot.getUser(userId); const name = user?.name; if (!name || name == Unknown_User_Name) { this.outDataOringin += ` [@${userId}@] `; } else { this.outDataOringin += ` [*${name}*] `; } } break; } case 'sharp': { // 提及频道 let channelId = `${attrs.id}`.toLowerCase(); // 如果 id 不在 attrs 中,尝试从子元素获取 if (!channelId && children.length > 0) { channelId = children.map(c => c.attrs.content).join(''); } if (channelId) { this.outDataOringin += ` [_${channelId.replace(/^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); } } }