diff --git a/write-server/next-env.d.ts b/write-server/next-env.d.ts
index 9edff1c7..c4b7818f 100644
--- a/write-server/next-env.d.ts
+++ b/write-server/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/write-server/src/lib/bot/handlers.ts b/write-server/src/lib/bot/handlers.ts
index efd1d555..c81cd949 100644
--- a/write-server/src/lib/bot/handlers.ts
+++ b/write-server/src/lib/bot/handlers.ts
@@ -201,9 +201,7 @@ handlers.help = async (chatId) => {
`├─ 🎙️ 语音消息:自动转文字 + 生成语音播放器`,
`├─ 🎵 MP3 文件:交互式问答生成音乐卡片`,
`├─ 📷 普通图片:直接插入文章`,
- `├─ 📷 HEIC 图片(iOS 实况照片):`,
- `│ └─ 动态效果无法自动提取`,
- `│ └─ 需先转为 GIF/视频再发送`,
+ `├─ 📷 Live Photo(iOS HEIC):自动转换为视频 + 生成 livephoto 播放器`,
`├─ 🎬 视频/GIF:直接插入文章`,
`└─ 📄 文档:保存到文章目录`,
].join("\n"));
diff --git a/write-server/src/lib/bot/poll.ts b/write-server/src/lib/bot/poll.ts
index e5e421b0..f19063bf 100644
--- a/write-server/src/lib/bot/poll.ts
+++ b/write-server/src/lib/bot/poll.ts
@@ -502,13 +502,14 @@ async function handleAudioEnhanced(chatId: number, fileId: string, fileName?: st
}
// ============================================================
-// 新增:处理图片(增强版:支持封面图上传 + 实况照片引导)
+// 新增:处理图片(增强版:支持封面图上传 + Live Photo 自动转换)
// ============================================================
async function handlePhotoEnhanced(chatId: number, photos: { file_id: string }[], caption?: string) {
try {
const fs = await import("fs");
const path = await import("path");
+ const { execSync } = await import("child_process");
const { getSession } = await import("./sessions");
const s = getSession(chatId);
@@ -536,27 +537,64 @@ async function handlePhotoEnhanced(chatId: number, photos: { file_id: string }[]
if (!fs.existsSync(articleDir)) fs.mkdirSync(articleDir, { recursive: true });
const ext = filePath.split(".").pop() || "jpg";
- const filename = Date.now() + "-" + largestPhoto.file_id.slice(0, 8) + "." + ext;
- fs.writeFileSync(path.join(articleDir, filename), Buffer.from(imgBuffer));
+ const baseFilename = Date.now() + "-" + largestPhoto.file_id.slice(0, 8);
+ const filename = baseFilename + "." + ext;
+ const fullPath = path.join(articleDir, filename);
+ fs.writeFileSync(fullPath, Buffer.from(imgBuffer));
- const imgCaption = caption || "image";
- appendContent(chatId, `\n\n`);
-
- // 只对 HEIC 文件(iOS 实况照片)显示提示
+ // HEIC 文件(iOS Live Photo)自动转换
const isHEIC = ext.toLowerCase() === "heic" || ext.toLowerCase() === "heif";
if (isHEIC) {
- await sendMessage(chatId, `📷 图片已保存:${filename}
+ await sendMessage(chatId, "🎞️ 检测到 Live Photo,正在转换为视频...");
+
+ try {
+ // 转换 HEIC 为 MP4 视频
+ const videoFilename = baseFilename + ".mp4";
+ const videoPath = path.join(articleDir, videoFilename);
+
+ // 使用 ffmpeg 转换 HEIC 为 MP4(保持原有帧率和质量)
+ execSync(`ffmpeg -y -i "${fullPath}" -c:v libx264 -crf 23 -preset medium -pix_fmt yuv420p "${videoPath}"`, { timeout: 60000 });
+
+ // 提取第一帧作为封面图
+ const coverFilename = baseFilename + "-cover.jpg";
+ const coverPath = path.join(articleDir, coverFilename);
+ execSync(`ffmpeg -y -i "${videoPath}" -vframes 1 -q:v 2 "${coverPath}"`, { timeout: 30000 });
+
+ // 删除原始 HEIC 文件
+ fs.unlinkSync(fullPath);
+
+ // 生成 livephoto 短代码
+ const livePhotoShortcode = `\n\n{{< livephoto src="${videoFilename}" cover="${coverFilename}" >}}\n\n`;
+ appendContent(chatId, livePhotoShortcode);
+
+ await sendMessage(chatId, `✅ Live Photo 转换完成!
-💡 检测到 iOS 实况照片(HEIC 格式)
+📹 视频:${videoFilename}
+🖼️ 封面:${coverFilename}
-动态效果无法自动提取。如需保留动态效果,请:
+已插入 livephoto 短代码,支持点击播放动态效果。`, { parse_mode: "HTML" });
+
+ return;
+ } catch (convertError) {
+ console.error("[bot:heic-convert]", convertError instanceof Error ? convertError.message : String(convertError));
+ // 转换失败,回退到普通图片处理
+ appendContent(chatId, `\n\n`);
+ await sendMessage(chatId, `📷 图片已保存:${filename}
+
+⚠️ Live Photo 转换失败
+
+动态效果无法提取,已作为普通图片插入。如需动态效果,请:
1. 在 iPhone 上使用「快捷指令」将实况照片转为 GIF
-2. 或使用第三方 App 导出为视频后重新发送
-
-继续发送其他内容...`, { parse_mode: "HTML" });
- } else {
- await sendMessage(chatId, `📷 图片已保存:${filename}`);
+2. 或使用第三方 App 导出为视频后重新发送`, { parse_mode: "HTML" });
+ return;
+ }
}
+
+ // 普通图片处理
+ const imgCaption = caption || "image";
+ appendContent(chatId, `\n\n`);
+ await sendMessage(chatId, `📷 图片已保存:${filename}`);
+
} catch (e) {
console.error("[bot:photo]", e instanceof Error ? e.message : String(e));
await sendMessage(chatId, "❌ 图片保存失败");