Files
dsh-telegram-channel/lib/format.js
T
rootandShirobana Agent Bot <tg-bridge> da06485d64 fix(ask): prototype 拦截器替代 waterfall hook + TG 原生按钮作答
根因:dsh 0.1.5-rc.1 把 'user-questions/request' waterfall 分发到
per-fiber events 注册表,其他 fiber 的 ctx.on 监听(含本插件)永远收不到
分发(实测 root/global/agent 作用域监听全部落空)。

修复:包装 UserQuestionService.prototype.ask(类跨实例共享),无论哪个
fiber/实例发起的 ask 都能拦截到 agent+questions;TG 答案与原 ask 竞速,
保留 dual-path 语义;stop 时恢复原型,热重载不叠加。

功能(应用户要求):
- 提问前显式发送预告文本消息
- 选项改用 TG 原生 inline keyboard 按钮(单选即点即答,多选带提交键)
- 文本作答保留并与按钮作答合并,逐题累计、齐题自动提交
- 附带修复:Telegram API 调用加 15s 客户端超时(代理挂死会卡死通知队列)

Co-authored-by: Shirobana Agent Bot <tg-bridge>
2026-09-17 11:15:16 +08:00

75 lines
2.3 KiB
JavaScript

export function escapeHtml(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function inlineToHtml(text) {
let html = escapeHtml(text);
html = html.replace(/`([^`\n]+)`/g, '<code>$1</code>');
html = html.replace(/\*\*([^*\n]+)\*\*/g, '<b>$1</b>');
return html;
}
/** Convert `> …` line groups into `<blockquote>` (Telegram renders it with the accent-colored left bar). */
function blocksToHtml(text) {
const lines = text.split('\n');
const out = [];
let quote = [];
const flush = () => {
if (quote.length === 0)
return;
out.push(`<blockquote>${inlineToHtml(quote.join('\n'))}</blockquote>`);
quote = [];
};
for (const line of lines) {
if (line.startsWith('> '))
quote.push(line.slice(2));
else if (line === '>')
quote.push('');
else {
flush();
out.push(inlineToHtml(line));
}
}
flush();
return out.join('\n');
}
export function markdownToHtml(text) {
const parts = text.split(/```[\w-]*/);
if (parts.length % 2 === 0)
return inlineToHtml(text);
let html = '';
for (let i = 0; i < parts.length; i++) {
if (i % 2 === 1) {
const code = parts[i].replace(/^\n/, '').replace(/\n$/, '');
html += `<pre>${escapeHtml(code)}</pre>`;
}
else {
html += blocksToHtml(parts[i]);
}
}
return html;
}
export function splitMessage(text, maxLength) {
if (maxLength < 1)
throw new Error('maxLength must be >= 1');
if (text.length <= maxLength)
return [text];
const chunks = [];
let rest = text;
while (rest.length > maxLength) {
const window = rest.slice(0, maxLength);
const newline = window.lastIndexOf('\n');
const ideographic = window.lastIndexOf('。');
const sentence = window.lastIndexOf('. ');
const breakAt = Math.max(newline, ideographic, sentence);
const cut = breakAt > 0 ? (breakAt === sentence ? breakAt + 2 : breakAt + 1) : maxLength;
chunks.push(rest.slice(0, cut));
rest = rest.slice(cut);
}
chunks.push(rest);
return chunks;
}