(client: any, messages: any[])
| 1113 | } finally { |
| 1114 | try { if (fs.existsSync(input)) fs.unlinkSync(input); } catch (err) { |
| 1115 | console.debug("[quote] waitForStableFile loop error:", err?.message || err); |
| 1116 | } |
| 1117 | } |
| 1118 | } |
| 1119 | |
| 1120 | function looksLikeAnimatedEmoji(buffer: Buffer | undefined): boolean { |
| 1121 | if (!buffer || buffer.length < 16) return false; |
| 1122 | const head = buffer.subarray(0, 64).toString("utf8"); |
| 1123 | if (isAnimatedRasterBuffer(buffer)) return true; |
| 1124 | // WebM (EBML header + "webm" string): 0x1A 0x45 0xDF 0xA3 + "webm" in first 64 bytes |
| 1125 | if (buffer[0] === 0x1a && buffer[1] === 0x45 && buffer[2] === 0xdf && buffer[3] === 0xa3 && head.includes("webm")) return true; |
| 1126 | // Lottie JSON: starts with {"v" or has "layers" |
| 1127 | if (head.trimStart().startsWith("{\"v\"") || head.includes("\"layers\"")) return true; |
| 1128 | // .tgs gzip/lottie |
| 1129 | if (buffer[0] === 0x1f && buffer[1] === 0x8b) return true; |
| 1130 | return false; |
| 1131 | } |
| 1132 | |
| 1133 | async function convertAnimatedEmojiToPng(buffer: Buffer): Promise<Buffer | undefined> { |
| 1134 | const tmpBase = path.join(os.tmpdir(), `telebox_quote_emoji_${Date.now()}_${Math.random().toString(16).slice(2)}`); |
| 1135 | const input = `${tmpBase}.bin`; |
| 1136 | const output = `${tmpBase}.png`; |
| 1137 | try { |
| 1138 | fs.writeFileSync(input, buffer); |
| 1139 | // ffmpeg handles webm/video stickers. It may not handle tgs/lottie; those will fall back below. |
| 1140 | await execFileAsync("ffmpeg", [ |
| 1141 | "-hide_banner", "-loglevel", "error", "-y", |
| 1142 | "-c:v", "libvpx-vp9", |
| 1143 | "-i", input, |
| 1144 | "-frames:v", "1", |
| 1145 | "-vf", "scale=128:128:force_original_aspect_ratio=decrease:flags=lanczos,format=rgba", |
| 1146 | "-f", "image2", output |
| 1147 | ], 12000); |
| 1148 | if (fs.existsSync(output)) { |
| 1149 | const png = fs.readFileSync(output); |
| 1150 | if (png.length > 0) { |
| 1151 | return await (await getSharp())(png, { animated: false }).ensureAlpha().png({ force: true }).toBuffer(); |
| 1152 | } |
| 1153 | } |
| 1154 | } catch (_) { |
| 1155 | // keep fallback quiet; normal static buffers and unsupported tgs land here |
| 1156 | } finally { |
| 1157 | try { if (fs.existsSync(input)) fs.unlinkSync(input); } catch (err) { |
| 1158 | console.debug("[quote] waitForStableFile loop error:", err?.message || err); |
| 1159 | } |
| 1160 | try { if (fs.existsSync(output)) fs.unlinkSync(output); } catch (err) { |
| 1161 | console.debug("[quote] waitForStableFile loop error:", err?.message || err); |
| 1162 | } |
| 1163 | } |
| 1164 | |
| 1165 | try { |
| 1166 | // Some animated emoji downloads are tgs/lottie. Sharp cannot render lottie, |
| 1167 | // but if Telegram provided a raster thumbnail this path is not used. |
| 1168 | return await (await getSharp())(buffer, { animated: false }).resize(128, 128, { fit: "inside" }).png({ force: true }).toBuffer(); |
| 1169 | } catch (_) { |
| 1170 | return undefined; |
| 1171 | } |
| 1172 | } |
no test coverage detected