143 lines
4.2 KiB
TypeScript
143 lines
4.2 KiB
TypeScript
import { prisma } from "@/lib/db";
|
|
import { getActiveAccountId } from "@/lib/account-context";
|
|
import {
|
|
getMediaInsightsViaThreadsApi,
|
|
getMediaRepliesViaThreadsApi,
|
|
getOwnPostsViaThreadsApi,
|
|
getProfileInsightsViaThreadsApi,
|
|
type ThreadsInsights,
|
|
} from "@/lib/threads-api";
|
|
import { getActiveThreadsCredentials } from "@/lib/services/threads-credentials";
|
|
|
|
function normalizeDate(value?: string | null): Date | undefined {
|
|
if (!value) return undefined;
|
|
const date = new Date(value);
|
|
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
}
|
|
|
|
export async function syncThreadsOwnPostsAndInsights(options?: {
|
|
postsLimit?: number;
|
|
repliesLimit?: number;
|
|
}) {
|
|
const accountId = await getActiveAccountId();
|
|
const credentials = await getActiveThreadsCredentials();
|
|
if (!credentials) throw new Error("Threads API 尚未連線");
|
|
|
|
const emptyInsights: ThreadsInsights = {};
|
|
const [ownPosts, profileInsights] = await Promise.all([
|
|
getOwnPostsViaThreadsApi(credentials, options?.postsLimit ?? 25),
|
|
getProfileInsightsViaThreadsApi(credentials).catch(() => emptyInsights),
|
|
]);
|
|
|
|
const synced = [];
|
|
|
|
for (const post of ownPosts) {
|
|
const insights = await getMediaInsightsViaThreadsApi(credentials, post.id).catch(
|
|
() => emptyInsights
|
|
);
|
|
const existing = await prisma.published.findFirst({
|
|
where: { externalId: post.id, ...(accountId ? { accountId } : {}) },
|
|
});
|
|
|
|
const published = existing
|
|
? await prisma.published.update({
|
|
where: { id: existing.id },
|
|
data: {
|
|
accountId,
|
|
text: post.text ?? undefined,
|
|
permalink: post.permalink,
|
|
views: insights.views,
|
|
likes: insights.likes,
|
|
replies: insights.replies,
|
|
reposts: insights.reposts,
|
|
quotes: insights.quotes,
|
|
},
|
|
})
|
|
: await prisma.published.create({
|
|
data: {
|
|
accountId,
|
|
externalId: post.id,
|
|
text: post.text ?? "",
|
|
permalink: post.permalink,
|
|
publishedAt: normalizeDate(post.timestamp) ?? new Date(),
|
|
views: insights.views,
|
|
likes: insights.likes,
|
|
replies: insights.replies,
|
|
reposts: insights.reposts,
|
|
quotes: insights.quotes,
|
|
},
|
|
});
|
|
|
|
await prisma.performanceSnapshot.create({
|
|
data: {
|
|
publishedId: published.id,
|
|
profileId: credentials.userId,
|
|
views: insights.views,
|
|
likes: insights.likes,
|
|
replies: insights.replies,
|
|
reposts: insights.reposts,
|
|
quotes: insights.quotes,
|
|
followers: profileInsights.followers_count,
|
|
},
|
|
});
|
|
|
|
const replies = await getMediaRepliesViaThreadsApi(
|
|
credentials,
|
|
post.id,
|
|
options?.repliesLimit ?? 25
|
|
).catch(() => []);
|
|
|
|
for (const reply of replies) {
|
|
if (!reply.id || !reply.text?.trim()) continue;
|
|
await prisma.inboundReply.upsert({
|
|
where: { externalId: reply.id },
|
|
create: {
|
|
publishedId: published.id,
|
|
externalId: reply.id,
|
|
parentId: reply.parent_id,
|
|
text: reply.text.trim(),
|
|
authorName: reply.username,
|
|
permalink: reply.permalink,
|
|
postedAt: normalizeDate(reply.timestamp),
|
|
likeCount: reply.like_count,
|
|
},
|
|
update: {
|
|
publishedId: published.id,
|
|
parentId: reply.parent_id,
|
|
text: reply.text.trim(),
|
|
authorName: reply.username,
|
|
permalink: reply.permalink,
|
|
postedAt: normalizeDate(reply.timestamp),
|
|
likeCount: reply.like_count,
|
|
},
|
|
});
|
|
}
|
|
|
|
synced.push({
|
|
id: published.id,
|
|
externalId: post.id,
|
|
replies: replies.length,
|
|
insights,
|
|
});
|
|
}
|
|
|
|
if (Object.keys(profileInsights).length > 0) {
|
|
await prisma.performanceSnapshot.create({
|
|
data: {
|
|
profileId: credentials.userId,
|
|
views: profileInsights.views,
|
|
likes: profileInsights.likes,
|
|
replies: profileInsights.replies,
|
|
reposts: profileInsights.reposts,
|
|
quotes: profileInsights.quotes,
|
|
followers: profileInsights.followers_count,
|
|
},
|
|
});
|
|
}
|
|
|
|
return {
|
|
profileInsights,
|
|
synced,
|
|
};
|
|
}
|