// Adapted from claudegram's message-sender.ts — send with retry, chunking, fallback import { logger } from '../utils/logger.js'; const MAX_MSG_LENGTH = 4000; export function splitMessage(text) { if (text.length <= MAX_MSG_LENGTH) return [text]; const chunks = []; let remaining = text; while (remaining.length > 0) { chunks.push(remaining.slice(0, MAX_MSG_LENGTH)); remaining = remaining.slice(MAX_MSG_LENGTH); } return chunks; } export function escapeMarkdown(text) { if (!text) return ''; return text .replace(/_/g, '\\_') .replace(/\*/g, '\\*') .replace(/\[/g, '\\[') .replace(/`/g, '\\`'); } export async function sendFormatted(ctx, text) { if (!text) return; try { // Try Markdown first const chunks = splitMessage(text); for (const chunk of chunks) { await ctx.reply(chunk, { parse_mode: 'Markdown' }); } } catch { // Fallback to plain text logger.warn('Markdown send failed, falling back to plain text'); const chunks = splitMessage(text); for (const chunk of chunks) { await ctx.reply(chunk, { parse_mode: undefined }); } } } export async function sendStreamingMessage(ctx, text, options = {}) { if (!text) return; const { delay = 100, minDelay = 50, maxDelay = 250, charMode = false // Set to true for character-by-character, false for word-by-word } = options; try { // Send initial "typing..." indicator const sendTyping = () => ctx.api.sendChatAction(ctx.chat.id, 'typing').catch(() => {}); if (charMode) { // Character-by-character streaming let sentText = ''; const chars = text.split(''); for (const char of chars) { sentText += char; // Send message with the accumulated text await ctx.reply(sentText, { parse_mode: 'Markdown' }); // Random delay between min and max const delayMs = minDelay + Math.random() * (maxDelay - minDelay); await new Promise(resolve => setTimeout(resolve, delayMs)); } } else { // Word-by-word streaming (default) const words = text.split(' '); let sentText = ''; for (let i = 0; i < words.length; i++) { sentText += (i > 0 ? ' ' : '') + words[i]; // Send accumulated text await ctx.reply(sentText, { parse_mode: 'Markdown' }); // Variable delay based on word length const wordDelay = Math.max(minDelay, Math.min(maxDelay, delay + words[i].length * 10)); await new Promise(resolve => setTimeout(resolve, wordDelay)); } } } catch (error) { logger.error('Streaming send failed:', error); // Fallback to non-streaming await sendFormatted(ctx, text); } }