export function decodeHtmlEntities(text: string) { if (!text) return text; return text .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, "'") .replace(/&/g, '&'); } export function normalizeInlineCodeFences(text: string) { if (!text || typeof text !== 'string') return text; try { return text.replace(/```\s*([^\n\r]+?)\s*```/g, '`$1`'); } catch { return text; } } export function unescapeWithMathProtection(text: string) { if (!text || typeof text !== 'string') return text; const mathBlocks: string[] = []; const placeholderPrefix = '__MATH_BLOCK_'; const placeholderSuffix = '__'; let processedText = text.replace(/\$\$([\s\S]*?)\$\$|\$([^\$\n]+?)\$/g, (match) => { const index = mathBlocks.length; mathBlocks.push(match); return `${placeholderPrefix}${index}${placeholderSuffix}`; }); processedText = processedText.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r'); processedText = processedText.replace( new RegExp(`${placeholderPrefix}(\\d+)${placeholderSuffix}`, 'g'), (match, index) => { return mathBlocks[parseInt(index, 10)]; }, ); return processedText; } export function escapeRegExp(value: string) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } export function formatUsageLimitText(text: string) { try { if (typeof text !== 'string') return text; return text.replace(/Claude AI usage limit reached\|(\d{10,13})/g, (match, ts) => { let timestampMs = parseInt(ts, 10); if (!Number.isFinite(timestampMs)) return match; if (timestampMs < 1e12) timestampMs *= 1000; const reset = new Date(timestampMs); const timeStr = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit', hour12: false, }).format(reset); const offsetMinutesLocal = -reset.getTimezoneOffset(); const sign = offsetMinutesLocal >= 0 ? '+' : '-'; const abs = Math.abs(offsetMinutesLocal); const offH = Math.floor(abs / 60); const offM = abs % 60; const gmt = `GMT${sign}${offH}${offM ? ':' + String(offM).padStart(2, '0') : ''}`; const tzId = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; const cityRaw = tzId.split('/').pop() || ''; const city = cityRaw .replace(/_/g, ' ') .toLowerCase() .replace(/\b\w/g, (char) => char.toUpperCase()); const tzHuman = city ? `${gmt} (${city})` : gmt; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const dateReadable = `${reset.getDate()} ${months[reset.getMonth()]} ${reset.getFullYear()}`; return `Claude usage limit reached. Your limit will reset at **${timeStr} ${tzHuman}** - ${dateReadable}`; }); } catch { return text; } }