diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css
index 333c1889..0a9097c2 100644
--- a/themes/Ying/assets/css/main.css
+++ b/themes/Ying/assets/css/main.css
@@ -5842,6 +5842,17 @@ a.link-card:hover .link-card-arrow {
color: #9ca3af;
}
+/* GIF 模式:自动播放、循环、无控制栏 */
+.video-player.gif-mode {
+ cursor: default;
+}
+
+.video-player.gif-mode video {
+ width: 100%;
+ max-height: 500px;
+ object-fit: contain;
+}
+
[data-theme="dark"] .video-player {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
diff --git a/themes/Ying/layouts/shortcodes/video.html b/themes/Ying/layouts/shortcodes/video.html
index 56bd7e90..e1b17b57 100644
--- a/themes/Ying/layouts/shortcodes/video.html
+++ b/themes/Ying/layouts/shortcodes/video.html
@@ -38,21 +38,28 @@
{{ $src := .Get "src" }}
{{ $poster := .Get "poster" }}
{{ $caption := .Get "caption" }}
+ {{ $loop := .Get "loop" | default "false" }}
+ {{ $muted := .Get "muted" | default "false" }}
+ {{ $isGifMode := or (eq $loop "true") (eq $muted "true") }}
{{ $videoSrc := $src }}
{{ if not (hasPrefix $src "http") }}
{{ $videoSrc = printf "/%s/%s" .Page.Slug $src }}
{{ end }}
-
+
+ {{ if not $isGifMode }}
@@ -73,6 +80,7 @@
+ {{ end }}
{{ with $caption }}
{{ . }}
diff --git a/write-server/src/lib/bot/poll.ts b/write-server/src/lib/bot/poll.ts
index a246fe56..540c979c 100644
--- a/write-server/src/lib/bot/poll.ts
+++ b/write-server/src/lib/bot/poll.ts
@@ -100,9 +100,10 @@ export async function startPolling(): Promise {
// 处理 GIF / 文档 / 视频
if ((msg.animation || msg.document || msg.video) && hasSession(chatId)) {
+ const fileType = msg.animation ? "animation" : msg.video ? "video" : "document";
const file = msg.animation || msg.document || msg.video;
if (file) {
- await handleDocument(chatId, file.file_id, file.file_name, msg.caption);
+ await handleDocument(chatId, file.file_id, file.file_name, msg.caption, fileType);
}
continue;
}
@@ -228,7 +229,7 @@ async function handlePhoto(chatId: number, photos: { file_id: string }[], captio
}
}
-async function handleDocument(chatId: number, fileId: string, fileName?: string, caption?: string) {
+async function handleDocument(chatId: number, fileId: string, fileName?: string, caption?: string, fileType?: string) {
try {
const fs = await import("fs");
const path = await import("path");
@@ -257,14 +258,21 @@ async function handleDocument(chatId: number, fileId: string, fileName?: string,
const filename = Date.now() + "-" + fileId.slice(0, 8) + "." + ext;
fs.writeFileSync(path.join(articleDir, filename), Buffer.from(fileBuffer));
- // GIF 用图片语法,视频用 shortcode,其他用链接
+ // 根据 Telegram 消息类型判断,而不是猜文件名
const extLower = ext.toLowerCase();
const isImage = ["gif", "webp", "png", "jpg", "jpeg"].includes(extLower);
const isVideo = ["mp4", "webm", "mov", "avi", "mkv", "flv"].includes(extLower);
const fileCaption = caption || fileName || "file";
+
if (isImage) {
appendContent(chatId, `\n\n`);
- } else if (isVideo) {
+ } else if (fileType === "animation") {
+ // Telegram GIF 动画 → 自动播放、循环、静音
+ appendContent(chatId, `\n{{< video src="${filename}" loop="true" muted="true" >}}\n`);
+ await sendMessage(chatId, `🎞️ GIF 已保存:${filename}`);
+ return;
+ } else if (fileType === "video" || isVideo) {
+ // 普通视频 → 带控制栏
appendContent(chatId, `\n{{< video src="${filename}" caption="${fileCaption}" >}}\n`);
await sendMessage(chatId, `🎬 视频已保存:${filename}`);
return;