优化图文页

This commit is contained in:
2026-04-28 17:11:42 +08:00
parent 674c202264
commit 040273586b
69 changed files with 3939 additions and 2302 deletions
+399 -331
View File
@@ -1,359 +1,427 @@
import type { BrowserContext } from 'playwright';
import { prisma } from '@/lib/prisma';
import { uploadAvatarFromUrl, uploadImageFromUrl } from './uploader';
import { firstUrl } from './utils';
import type { BrowserContext } from "playwright";
import { prisma } from "@/lib/prisma";
import { uploadAvatarFromUrl, uploadImageFromUrl } from "./uploader";
import { firstUrl } from "./utils";
export async function saveToDB(
context: BrowserContext,
detailResp: DouyinVideoDetailResponse,
commentResp: DouyinCommentResponse,
videoUrl?: string,
width?: number,
height?: number,
coverUrl?: string,
fps?: number
context: BrowserContext,
detailResp: DouyinVideoDetailResponse,
commentResp: DouyinCommentResponse,
videoUrl?: string,
width?: number,
height?: number,
coverUrl?: string,
fps?: number,
) {
if (!detailResp?.aweme_detail) throw new Error('视频详情为空');
const d = detailResp.aweme_detail;
if (!detailResp?.aweme_detail) throw new Error("视频详情为空");
const d = detailResp.aweme_detail;
// 1) Upsert Author
const authorAvatarSrc = firstUrl(d.author.avatar_thumb?.url_list);
const authorAvatarUploaded = await uploadAvatarFromUrl(context, authorAvatarSrc, `authors/${d.author.sec_uid}`);
const author = await prisma.author.upsert({
where: { sec_uid: d.author.sec_uid },
create: {
sec_uid: d.author.sec_uid,
uid: d.author.uid,
nickname: d.author.nickname,
signature: d.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt(d.author.follower_count || 0),
total_favorited: BigInt(d.author.total_favorited || 0),
unique_id: d.author.unique_id ?? null,
short_id: d.author.short_id ?? null,
},
update: {
uid: d.author.uid,
nickname: d.author.nickname,
signature: d.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt(d.author.follower_count || 0),
total_favorited: BigInt(d.author.total_favorited || 0),
unique_id: d.author.unique_id ?? null,
short_id: d.author.short_id ?? null,
// 1) Upsert Author
const authorAvatarSrc = firstUrl(d.author.avatar_thumb?.url_list);
const authorAvatarUploaded = await uploadAvatarFromUrl(
context,
authorAvatarSrc,
`authors/${d.author.sec_uid}`,
);
const author = await prisma.author.upsert({
where: { sec_uid: d.author.sec_uid },
create: {
sec_uid: d.author.sec_uid,
uid: d.author.uid,
nickname: d.author.nickname,
signature: d.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt(d.author.follower_count || 0),
total_favorited: BigInt(d.author.total_favorited || 0),
unique_id: d.author.unique_id ?? null,
short_id: d.author.short_id ?? null,
},
update: {
uid: d.author.uid,
nickname: d.author.nickname,
signature: d.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt(d.author.follower_count || 0),
total_favorited: BigInt(d.author.total_favorited || 0),
unique_id: d.author.unique_id ?? null,
short_id: d.author.short_id ?? null,
},
});
// 2) Upsert Video
const video = await prisma.video.upsert({
where: { aweme_id: d.aweme_id },
create: {
aweme_id: d.aweme_id,
desc: d.desc,
preview_title: d.preview_title ?? null,
duration_ms: d.duration,
created_at: new Date((d.create_time || 0) * 1000),
share_url: d.share_url,
digg_count: BigInt(d.statistics?.digg_count || 0),
comment_count: BigInt(d.statistics?.comment_count || 0),
share_count: BigInt(d.statistics?.share_count || 0),
collect_count: BigInt(d.statistics?.collect_count || 0),
authorId: author.sec_uid,
tags: d.tags?.map((t) => t.tag_name) ?? [],
video_url: videoUrl ?? "",
width: width ?? null,
height: height ?? null,
cover_url: coverUrl ?? null,
fps: fps ?? null,
raw_json: detailResp as any, // 保存完整接口 JSON
},
update: {
desc: d.desc,
preview_title: d.preview_title ?? null,
duration_ms: d.duration,
created_at: new Date((d.create_time || 0) * 1000),
share_url: d.share_url,
digg_count: BigInt(d.statistics?.digg_count || 0),
comment_count: BigInt(d.statistics?.comment_count || 0),
share_count: BigInt(d.statistics?.share_count || 0),
collect_count: BigInt(d.statistics?.collect_count || 0),
authorId: author.sec_uid,
...(videoUrl ? { video_url: videoUrl } : {}),
...(width ? { width } : {}),
...(height ? { height } : {}),
...(coverUrl ? { cover_url: coverUrl } : {}),
...(fps ? { fps } : {}),
raw_json: detailResp as any, // 更新完整接口 JSON
},
});
// 3) Upsert Comments + CommentUser
const comments = commentResp?.comments ?? [];
for (const c of comments) {
const origAvatar: string | null =
firstUrl(c.user?.avatar_thumb?.url_list) ?? null;
const nameHint = `comment-users/${(c.user?.nickname || "unknown").replace(/\s+/g, "_")}-${c.cid}`;
const uploadedAvatar = await uploadAvatarFromUrl(
context,
origAvatar ?? undefined,
nameHint,
);
const finalAvatar = uploadedAvatar ?? origAvatar; // string | null
const finalAvatarKey = finalAvatar ?? "";
const cu = await prisma.commentUser.upsert({
where: {
nickname_avatar_url: {
nickname: c.user?.nickname || "未知用户",
avatar_url: finalAvatarKey,
},
},
create: {
nickname: c.user?.nickname || "未知用户",
avatar_url: finalAvatar ?? null,
},
update: {
avatar_url: finalAvatar ?? null,
},
});
// 2) Upsert Video
const video = await prisma.video.upsert({
where: { aweme_id: d.aweme_id },
create: {
aweme_id: d.aweme_id,
desc: d.desc,
preview_title: d.preview_title ?? null,
duration_ms: d.duration,
created_at: new Date((d.create_time || 0) * 1000),
share_url: d.share_url,
digg_count: BigInt(d.statistics?.digg_count || 0),
comment_count: BigInt(d.statistics?.comment_count || 0),
share_count: BigInt(d.statistics?.share_count || 0),
collect_count: BigInt(d.statistics?.collect_count || 0),
authorId: author.sec_uid,
tags: (d.tags?.map(t => t.tag_name) ?? []),
video_url: videoUrl ?? '',
width: width ?? null,
height: height ?? null,
cover_url: coverUrl ?? null,
fps: fps ?? null,
raw_json: detailResp as any, // 保存完整接口 JSON
},
update: {
desc: d.desc,
preview_title: d.preview_title ?? null,
duration_ms: d.duration,
created_at: new Date((d.create_time || 0) * 1000),
share_url: d.share_url,
digg_count: BigInt(d.statistics?.digg_count || 0),
comment_count: BigInt(d.statistics?.comment_count || 0),
share_count: BigInt(d.statistics?.share_count || 0),
collect_count: BigInt(d.statistics?.collect_count || 0),
authorId: author.sec_uid,
...(videoUrl ? { video_url: videoUrl } : {}),
...(width ? { width } : {}),
...(height ? { height } : {}),
...(coverUrl ? { cover_url: coverUrl } : {}),
...(fps ? { fps } : {}),
raw_json: detailResp as any, // 更新完整接口 JSON
},
const savedComment = await prisma.comment.upsert({
where: { cid: c.cid },
create: {
cid: c.cid,
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
videoId: video.aweme_id,
userId: cu.id,
},
update: {
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
videoId: video.aweme_id,
userId: cu.id,
},
});
// 3) Upsert Comments + CommentUser
const comments = commentResp?.comments ?? [];
for (const c of comments) {
const origAvatar: string | null = firstUrl(c.user?.avatar_thumb?.url_list) ?? null;
const nameHint = `comment-users/${(c.user?.nickname || 'unknown').replace(/\s+/g, '_')}-${c.cid}`;
const uploadedAvatar = await uploadAvatarFromUrl(context, origAvatar ?? undefined, nameHint);
const finalAvatar = uploadedAvatar ?? origAvatar; // string | null
const finalAvatarKey = finalAvatar ?? '';
const cu = await prisma.commentUser.upsert({
where: {
nickname_avatar_url: {
nickname: c.user?.nickname || '未知用户',
avatar_url: finalAvatarKey,
},
},
create: {
nickname: c.user?.nickname || '未知用户',
avatar_url: finalAvatar ?? null,
},
update: {
avatar_url: finalAvatar ?? null,
},
// 处理评论贴纸/配图上传与入库
try {
const sources: {
url?: string | null;
width?: number;
height?: number;
}[] = [];
// 贴纸(当作第一张)
const stickerUrl = firstUrl(c.sticker?.animate_url?.url_list);
if (stickerUrl) {
sources.push({
url: stickerUrl,
width: c.sticker?.animate_url?.width,
height: c.sticker?.animate_url?.height,
});
}
// 配图列表
const imgs = c.image_list || [];
for (const it of imgs) {
const u = firstUrl(it?.origin_url?.url_list);
if (u)
sources.push({
url: u,
width: it?.origin_url?.width as any,
height: it?.origin_url?.height as any,
});
}
const savedComment = await prisma.comment.upsert({
where: { cid: c.cid },
create: {
cid: c.cid,
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
videoId: video.aweme_id,
userId: cu.id,
},
update: {
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
videoId: video.aweme_id,
userId: cu.id,
},
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
const uploaded = await uploadImageFromUrl(
context,
s.url ?? undefined,
`comments/${c.cid}/${i}`,
);
if (!uploaded) continue;
await prisma.commentImage.upsert({
where: { commentId_order: { commentId: savedComment.cid, order: i } },
create: {
commentId: savedComment.cid,
order: i,
url: uploaded,
width: typeof s.width === "number" ? s.width : null,
height: typeof s.height === "number" ? s.height : null,
},
update: {
url: uploaded,
width: typeof s.width === "number" ? s.width : null,
height: typeof s.height === "number" ? s.height : null,
},
});
// 处理评论贴纸/配图上传与入库
try {
const sources: { url?: string | null; width?: number; height?: number }[] = [];
// 贴纸(当作第一张)
const stickerUrl = firstUrl(c.sticker?.animate_url?.url_list);
if (stickerUrl) {
sources.push({ url: stickerUrl, width: c.sticker?.animate_url?.width, height: c.sticker?.animate_url?.height });
}
// 配图列表
const imgs = c.image_list || [];
for (const it of imgs) {
const u = firstUrl(it?.origin_url?.url_list);
if (u) sources.push({ url: u, width: it?.origin_url?.width as any, height: it?.origin_url?.height as any });
}
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
const uploaded = await uploadImageFromUrl(
context,
s.url ?? undefined,
`comments/${c.cid}/${i}`,
);
if (!uploaded) continue;
await prisma.commentImage.upsert({
where: { commentId_order: { commentId: savedComment.cid, order: i } },
create: {
commentId: savedComment.cid,
order: i,
url: uploaded,
width: typeof s.width === 'number' ? s.width : null,
height: typeof s.height === 'number' ? s.height : null,
},
update: {
url: uploaded,
width: typeof s.width === 'number' ? s.width : null,
height: typeof s.height === 'number' ? s.height : null,
},
});
}
} catch (e) {
console.warn('[comment-images] 保存失败:', (e as Error)?.message || e);
}
}
} catch (e) {
console.warn("[comment-images] 保存失败:", (e as Error)?.message || e);
}
}
return { aweme_id: video.aweme_id, author_sec_uid: author.sec_uid, comment_count: comments.length };
return {
aweme_id: video.aweme_id,
author_sec_uid: author.sec_uid,
comment_count: comments.length,
};
}
export async function saveImagePostToDB(
context: BrowserContext,
aweme: DouyinImageAweme,
commentResp: DouyinCommentResponse,
uploads: { images: { url: string; width?: number; height?: number, video?: string }[]; musicUrl?: string },
rawJson?: any
context: BrowserContext,
aweme: DouyinImageAweme,
commentResp: DouyinCommentResponse,
uploads: {
images: {
url: string;
width?: number;
height?: number;
video?: string;
duration?: number;
}[];
musicUrl?: string;
},
rawJson?: any,
) {
if (!aweme?.author?.sec_uid) throw new Error('作者 sec_uid 缺失');
if (!aweme?.author?.sec_uid) throw new Error("作者 sec_uid 缺失");
// Upsert Author(与视频一致)
const authorAvatarSrc = firstUrl(aweme.author.avatar_thumb?.url_list);
const authorAvatarUploaded = await uploadAvatarFromUrl(context, authorAvatarSrc, `authors/${aweme.author.sec_uid}`);
const author = await prisma.author.upsert({
where: { sec_uid: aweme.author.sec_uid },
create: {
sec_uid: aweme.author.sec_uid,
uid: aweme.author.uid,
nickname: aweme.author.nickname,
signature: aweme.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt((aweme.author as any).follower_count || 0),
total_favorited: BigInt((aweme.author as any).total_favorited || 0),
unique_id: (aweme.author as any).unique_id ?? null,
short_id: (aweme.author as any).short_id ?? null,
},
update: {
uid: aweme.author.uid,
nickname: aweme.author.nickname,
signature: aweme.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt((aweme.author as any).follower_count || 0),
total_favorited: BigInt((aweme.author as any).total_favorited || 0),
unique_id: (aweme.author as any).unique_id ?? null,
short_id: (aweme.author as any).short_id ?? null,
// Upsert Author(与视频一致)
const authorAvatarSrc = firstUrl(aweme.author.avatar_thumb?.url_list);
const authorAvatarUploaded = await uploadAvatarFromUrl(
context,
authorAvatarSrc,
`authors/${aweme.author.sec_uid}`,
);
const author = await prisma.author.upsert({
where: { sec_uid: aweme.author.sec_uid },
create: {
sec_uid: aweme.author.sec_uid,
uid: aweme.author.uid,
nickname: aweme.author.nickname,
signature: aweme.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt((aweme.author as any).follower_count || 0),
total_favorited: BigInt((aweme.author as any).total_favorited || 0),
unique_id: (aweme.author as any).unique_id ?? null,
short_id: (aweme.author as any).short_id ?? null,
},
update: {
uid: aweme.author.uid,
nickname: aweme.author.nickname,
signature: aweme.author.signature ?? null,
avatar_url: authorAvatarUploaded ?? null,
follower_count: BigInt((aweme.author as any).follower_count || 0),
total_favorited: BigInt((aweme.author as any).total_favorited || 0),
unique_id: (aweme.author as any).unique_id ?? null,
short_id: (aweme.author as any).short_id ?? null,
},
});
// Upsert ImagePost
const imagePost = await prisma.imagePost.upsert({
where: { aweme_id: aweme.aweme_id },
create: {
aweme_id: aweme.aweme_id,
desc: aweme.desc,
created_at: new Date((aweme.create_time || 0) * 1000),
share_url: aweme.share_url || "",
digg_count: BigInt(aweme.statistics?.digg_count || 0),
comment_count: BigInt(aweme.statistics?.comment_count || 0),
share_count: BigInt(aweme.statistics?.share_count || 0),
collect_count: BigInt(aweme.statistics?.collect_count || 0),
authorId: author.sec_uid,
tags: aweme.video_tag?.map((t) => t.tag_name) ?? [],
music_url: uploads.musicUrl ?? null,
raw_json: rawJson ?? null, // 保存完整接口 JSON
},
update: {
desc: aweme.desc,
created_at: new Date((aweme.create_time || 0) * 1000),
share_url: aweme.share_url,
digg_count: BigInt(aweme.statistics?.digg_count || 0),
comment_count: BigInt(aweme.statistics?.comment_count || 0),
share_count: BigInt(aweme.statistics?.share_count || 0),
collect_count: BigInt(aweme.statistics?.collect_count || 0),
authorId: author.sec_uid,
tags: aweme.video_tag?.map((t) => t.tag_name) ?? [],
music_url: uploads.musicUrl ?? undefined,
raw_json: rawJson ?? undefined, // 更新完整接口 JSON
},
});
// Upsert ImageFiles(按顺序)
for (let i = 0; i < uploads.images.length; i++) {
const { url, width, height, video, duration } = uploads.images[i];
const durationMs =
typeof duration === "number" && Number.isFinite(duration)
? Math.max(1, Math.round(duration))
: null;
await prisma.imageFile.upsert({
where: { postId_order: { postId: imagePost.aweme_id, order: i } },
create: {
postId: imagePost.aweme_id,
order: i,
url,
width: typeof width === "number" ? width : null,
height: typeof height === "number" ? height : null,
animated: video || null,
duration: durationMs,
},
update: {
url,
width: typeof width === "number" ? width : null,
height: typeof height === "number" ? height : null,
animated: video || null,
duration: durationMs,
},
});
}
// 评论入库:关联到 ImagePost
const comments = commentResp?.comments ?? [];
for (const c of comments) {
const origAvatar: string | null =
firstUrl(c.user?.avatar_thumb?.url_list) ?? null;
const nameHint = `comment-users/${(c.user?.nickname || "unknown").replace(/\s+/g, "_")}-${c.cid}`;
const uploadedAvatar = await uploadAvatarFromUrl(
context,
origAvatar ?? undefined,
nameHint,
);
const finalAvatar = uploadedAvatar ?? origAvatar; // string | null
const finalAvatarKey = finalAvatar ?? "";
const cu = await prisma.commentUser.upsert({
where: {
nickname_avatar_url: {
nickname: c.user?.nickname || "未知用户",
avatar_url: finalAvatarKey,
},
},
create: {
nickname: c.user?.nickname || "未知用户",
avatar_url: finalAvatar ?? null,
},
update: {
avatar_url: finalAvatar ?? null,
},
});
// Upsert ImagePost
const imagePost = await prisma.imagePost.upsert({
where: { aweme_id: aweme.aweme_id },
create: {
aweme_id: aweme.aweme_id,
desc: aweme.desc,
created_at: new Date((aweme.create_time || 0) * 1000),
share_url: aweme.share_url || '',
digg_count: BigInt(aweme.statistics?.digg_count || 0),
comment_count: BigInt(aweme.statistics?.comment_count || 0),
share_count: BigInt(aweme.statistics?.share_count || 0),
collect_count: BigInt(aweme.statistics?.collect_count || 0),
authorId: author.sec_uid,
tags: (aweme.video_tag?.map(t => t.tag_name) ?? []),
music_url: uploads.musicUrl ?? null,
raw_json: rawJson ?? null, // 保存完整接口 JSON
},
update: {
desc: aweme.desc,
created_at: new Date((aweme.create_time || 0) * 1000),
share_url: aweme.share_url,
digg_count: BigInt(aweme.statistics?.digg_count || 0),
comment_count: BigInt(aweme.statistics?.comment_count || 0),
share_count: BigInt(aweme.statistics?.share_count || 0),
collect_count: BigInt(aweme.statistics?.collect_count || 0),
authorId: author.sec_uid,
tags: (aweme.video_tag?.map(t => t.tag_name) ?? []),
music_url: uploads.musicUrl ?? undefined,
raw_json: rawJson ?? undefined, // 更新完整接口 JSON
},
const savedComment = await prisma.comment.upsert({
where: { cid: c.cid },
create: {
cid: c.cid,
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
imagePostId: imagePost.aweme_id,
userId: cu.id,
},
update: {
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
imagePostId: imagePost.aweme_id,
userId: cu.id,
},
});
// Upsert ImageFiles(按顺序)
for (let i = 0; i < uploads.images.length; i++) {
const { url, width, height, video } = uploads.images[i];
await prisma.imageFile.upsert({
where: { postId_order: { postId: imagePost.aweme_id, order: i } },
create: {
postId: imagePost.aweme_id,
order: i,
url,
width: typeof width === 'number' ? width : null,
height: typeof height === 'number' ? height : null,
animated: video || null,
},
update: {
url,
width: typeof width === 'number' ? width : null,
height: typeof height === 'number' ? height : null,
animated: video || null,
},
// 处理评论贴纸/配图上传与入库
try {
const sources: {
url?: string | null;
width?: number;
height?: number;
}[] = [];
// 贴纸(当作第一张)
const stickerUrl = firstUrl(c.sticker?.animate_url?.url_list);
if (stickerUrl) {
sources.push({
url: stickerUrl,
width: c.sticker?.animate_url?.width,
height: c.sticker?.animate_url?.height,
});
}
// 配图列表
const imgs = c.image_list || [];
for (const it of imgs) {
const u = firstUrl(it?.origin_url?.url_list);
if (u)
sources.push({
url: u,
width: it?.origin_url?.width as any,
height: it?.origin_url?.height as any,
});
}
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
const uploaded = await uploadImageFromUrl(
context,
s.url ?? undefined,
`comments/${c.cid}/${i}`,
);
if (!uploaded) continue;
await prisma.commentImage.upsert({
where: { commentId_order: { commentId: savedComment.cid, order: i } },
create: {
commentId: savedComment.cid,
order: i,
url: uploaded,
width: typeof s.width === "number" ? s.width : null,
height: typeof s.height === "number" ? s.height : null,
},
update: {
url: uploaded,
width: typeof s.width === "number" ? s.width : null,
height: typeof s.height === "number" ? s.height : null,
},
});
}
} catch (e) {
console.warn("[comment-images] 保存失败:", (e as Error)?.message || e);
}
}
// 评论入库:关联到 ImagePost
const comments = commentResp?.comments ?? [];
for (const c of comments) {
const origAvatar: string | null = firstUrl(c.user?.avatar_thumb?.url_list) ?? null;
const nameHint = `comment-users/${(c.user?.nickname || 'unknown').replace(/\s+/g, '_')}-${c.cid}`;
const uploadedAvatar = await uploadAvatarFromUrl(context, origAvatar ?? undefined, nameHint);
const finalAvatar = uploadedAvatar ?? origAvatar; // string | null
const finalAvatarKey = finalAvatar ?? '';
const cu = await prisma.commentUser.upsert({
where: {
nickname_avatar_url: {
nickname: c.user?.nickname || '未知用户',
avatar_url: finalAvatarKey,
},
},
create: {
nickname: c.user?.nickname || '未知用户',
avatar_url: finalAvatar ?? null,
},
update: {
avatar_url: finalAvatar ?? null,
},
});
const savedComment = await prisma.comment.upsert({
where: { cid: c.cid },
create: {
cid: c.cid,
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
imagePostId: imagePost.aweme_id,
userId: cu.id,
},
update: {
text: c.text,
digg_count: BigInt(c.digg_count || 0),
created_at: new Date((c.create_time || 0) * 1000),
imagePostId: imagePost.aweme_id,
userId: cu.id,
},
});
// 处理评论贴纸/配图上传与入库
try {
const sources: { url?: string | null; width?: number; height?: number }[] = [];
// 贴纸(当作第一张)
const stickerUrl = firstUrl(c.sticker?.animate_url?.url_list);
if (stickerUrl) {
sources.push({ url: stickerUrl, width: c.sticker?.animate_url?.width, height: c.sticker?.animate_url?.height });
}
// 配图列表
const imgs = c.image_list || [];
for (const it of imgs) {
const u = firstUrl(it?.origin_url?.url_list);
if (u) sources.push({ url: u, width: it?.origin_url?.width as any, height: it?.origin_url?.height as any });
}
for (let i = 0; i < sources.length; i++) {
const s = sources[i];
const uploaded = await uploadImageFromUrl(
context,
s.url ?? undefined,
`comments/${c.cid}/${i}`,
);
if (!uploaded) continue;
await prisma.commentImage.upsert({
where: { commentId_order: { commentId: savedComment.cid, order: i } },
create: {
commentId: savedComment.cid,
order: i,
url: uploaded,
width: typeof s.width === 'number' ? s.width : null,
height: typeof s.height === 'number' ? s.height : null,
},
update: {
url: uploaded,
width: typeof s.width === 'number' ? s.width : null,
height: typeof s.height === 'number' ? s.height : null,
},
});
}
} catch (e) {
console.warn('[comment-images] 保存失败:', (e as Error)?.message || e);
}
}
return { aweme_id: imagePost.aweme_id, author_sec_uid: author.sec_uid, image_count: uploads.images.length, comment_count: comments.length };
return {
aweme_id: imagePost.aweme_id,
author_sec_uid: author.sec_uid,
image_count: uploads.images.length,
comment_count: comments.length,
};
}