diff --git a/lib/discord.js b/lib/discord.js index c8625fe..740e019 100644 --- a/lib/discord.js +++ b/lib/discord.js @@ -257,10 +257,61 @@ async function sendDiscordMessage(channelId, text, options = {}) { // Convert Slack markdown to Discord markdown let discordText = text.replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g, '[$2]($1)'); + // Convert common Slack emoji codes to Unicode emoji for Discord + // Use a single regex-based replacement for better performance with large messages + const emojiMap = { + ':notes:': '🎵', + ':lock:': '🔒', + ':star:': '⭐', + ':stopwatch:': '⏱️', + ':cricket:': '🦗', + ':musical_note:': '🎵', + ':headphones:': '🎧', + ':speaker:': '🔊', + ':mute:': '🔇', + ':loud_sound:': '🔊', + ':sound:': '🔉', + ':fire:': '🔥', + ':thumbsup:': '👍', + ':thumbsdown:': '👎', + ':clap:': '👏', + ':party_popper:': '🎉', + ':tada:': '🎉', + ':warning:': '⚠️', + ':x:': '❌', + ':white_check_mark:': '✅', + ':checkmark:': '✅', + ':question:': '❓', + ':exclamation:': '❗', + ':sparkles:': '✨' + }; + discordText = discordText.replace(/:[a-z_]+:/g, (match) => emojiMap[match] || match); + // Discord has a 2000 char limit, split into chunks if needed - const maxLength = 1900; // Leave some margin + // Use 1800 as max to have buffer for edge cases with Unicode + const maxLength = 1800; let messages = []; + // Helper function to send a chunk safely (splitting further if needed) + const sendChunkSafe = async (chunk) => { + if (chunk.length <= maxLength) { + const message = await channel.send(chunk); + messages.push(message); + return; + } + // Chunk is still too long, split it + let remaining = chunk; + while (remaining.length > 0) { + const piece = remaining.substring(0, maxLength); + const message = await channel.send(piece); + messages.push(message); + remaining = remaining.substring(maxLength); + if (remaining.length > 0) { + await new Promise(resolve => setTimeout(resolve, 300)); + } + } + }; + if (discordText.length <= maxLength) { // Single message const message = await channel.send(discordText); @@ -274,33 +325,23 @@ async function sendDiscordMessage(channelId, text, options = {}) { for (let i = 0; i < lines.length; i++) { const line = lines[i]; + const potentialLength = currentChunk.length + line.length + 1; // +1 for newline - if ((currentChunk + line + '\n').length > maxLength) { - // Send current chunk + if (potentialLength > maxLength) { + // Send current chunk if it has content if (currentChunk.trim().length > 0) { - const message = await channel.send(currentChunk); - messages.push(message); + await sendChunkSafe(currentChunk); chunkCount++; currentChunk = ''; // Small delay between messages - await new Promise(resolve => setTimeout(resolve, 500)); + await new Promise(resolve => setTimeout(resolve, 300)); } - // Handle oversized single lines by splitting them - if (line.length > maxLength) { - // Split the line into smaller chunks - let remainingLine = line; - while (remainingLine.length > 0) { - const chunk = remainingLine.substring(0, maxLength); - const message = await channel.send(chunk); - messages.push(message); - chunkCount++; - remainingLine = remainingLine.substring(maxLength); - if (remainingLine.length > 0) { - await new Promise(resolve => setTimeout(resolve, 500)); - } - } - // Skip adding to currentChunk since we already sent it + // Handle oversized or exact-length lines (>= to avoid adding newline that exceeds limit) + if (line.length >= maxLength) { + await sendChunkSafe(line); + chunkCount++; + await new Promise(resolve => setTimeout(resolve, 300)); continue; } } @@ -308,10 +349,9 @@ async function sendDiscordMessage(channelId, text, options = {}) { currentChunk += line + '\n'; } - // Send remaining chunk + // Send remaining chunk (with safety check) if (currentChunk.trim().length > 0) { - const message = await channel.send(currentChunk); - messages.push(message); + await sendChunkSafe(currentChunk); chunkCount++; } diff --git a/package-lock.json b/package-lock.json index 29ed8d3..95d47d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -460,9 +460,9 @@ } }, "node_modules/@posthog/core": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.19.0.tgz", - "integrity": "sha512-OMcdu5cJcvkle2hw0rpe+1mTOFRlerTHTtZKZFvB8z0hgzbN1WeaGZfGFY5wOq42LVTSxwdUgK1MYERyzG1Epw==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.22.0.tgz", + "integrity": "sha512-WkmOnq95aAOu6yk6r5LWr5cfXsQdpVbWDCwOxQwxSne8YV6GuZET1ziO5toSQXgrgbdcjrSz2/GopAfiL6iiAA==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6" @@ -622,9 +622,9 @@ } }, "node_modules/@slack/types": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.19.0.tgz", - "integrity": "sha512-7+QZ38HGcNh/b/7MpvPG6jnw7mliV6UmrquJLqgdxkzJgQEYUcEztvFWRU49z0x4vthF0ixL5lTK601AXrS8IA==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.20.0.tgz", + "integrity": "sha512-PVF6P6nxzDMrzPC8fSCsnwaI+kF8YfEpxf3MqXmdyjyWTYsZQURpkK7WWUWvP5QpH55pB7zyYL9Qem/xSgc5VA==", "license": "MIT", "engines": { "node": ">= 12.13.0", @@ -632,16 +632,16 @@ } }, "node_modules/@slack/web-api": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.13.0.tgz", - "integrity": "sha512-ERcExbWrnkDN8ovoWWe6Wgt/usanj1dWUd18dJLpctUI4mlPS0nKt81Joh8VI+OPbNnY1lIilVt9gdMBD9U2ig==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.14.1.tgz", + "integrity": "sha512-RoygyteJeFswxDPJjUMESn9dldWVMD2xUcHHd9DenVavSfVC6FeVnSdDerOO7m8LLvw4Q132nQM4hX8JiF7dng==", "license": "MIT", "dependencies": { "@slack/logger": "^4.0.0", - "@slack/types": "^2.18.0", + "@slack/types": "^2.20.0", "@types/node": ">=18.0.0", "@types/retry": "0.12.0", - "axios": "^1.11.0", + "axios": "^1.13.5", "eventemitter3": "^5.0.1", "form-data": "^4.0.4", "is-electron": "2.2.2", @@ -789,13 +789,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -2239,9 +2239,9 @@ } }, "node_modules/openai": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.16.0.tgz", - "integrity": "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg==", + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.22.0.tgz", + "integrity": "sha512-7Yvy17F33Bi9RutWbsaYt5hJEEJ/krRPOrwan+f9aCPuMat1WVsb2VNSII5W1EksKT6fF69TG/xj4XzodK3JZw==", "license": "Apache-2.0", "bin": { "openai": "bin/cli" @@ -2455,12 +2455,12 @@ } }, "node_modules/posthog-node": { - "version": "5.24.9", - "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.9.tgz", - "integrity": "sha512-afu4kYL+QTEPinnvTF/VimdsGbrpJztqbxIWhQ96C+m24yW/KenEodWH9em989t+MLwGWcnBGhw1vytgeZdySg==", + "version": "5.24.15", + "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-5.24.15.tgz", + "integrity": "sha512-0QnWVOZAPwEAlp+r3r0jIGfk2IaNYM/2YnEJJhBMJZXs4LpHcTu7mX42l+e95o9xX87YpVuZU0kOkmtQUxgnOA==", "license": "MIT", "dependencies": { - "@posthog/core": "1.19.0" + "@posthog/core": "1.22.0" }, "engines": { "node": "^20.20.0 || >=22.22.0"