import { THREADS_MAX_CHARS } from "@/lib/utils"; import { threadsGraphGet, threadsGraphPost, ThreadsApiError } from "./client"; import type { ThreadsApiCredentials, ThreadsPublishInput, ThreadsPublishResult } from "./types"; const POLL_INTERVAL_MS = 3000; const POLL_MAX_ATTEMPTS = 20; export async function publishViaThreadsApi( credentials: ThreadsApiCredentials, input: ThreadsPublishInput ): Promise { if (input.text.length > THREADS_MAX_CHARS) { return { success: false, error: `貼文超過 ${THREADS_MAX_CHARS} 字上限`, method: "api" }; } try { const containerParams: Record = { access_token: credentials.accessToken, text: input.text, }; if (input.imageUrl) { containerParams.media_type = "IMAGE"; containerParams.image_url = input.imageUrl; } else { containerParams.media_type = "TEXT"; } if (input.replyToId) { containerParams.reply_to_id = input.replyToId; } const container = await threadsGraphPost<{ id: string }>( `/${credentials.userId}/threads`, containerParams ); if (!container.id) { return { success: false, error: "無法建立發布容器", method: "api" }; } await waitForContainerReady(container.id, credentials.accessToken); const published = await threadsGraphPost<{ id: string }>( `/${credentials.userId}/threads_publish`, { access_token: credentials.accessToken, creation_id: container.id, } ); if (!published.id) { return { success: false, error: "發布請求未回傳貼文 ID", method: "api" }; } const permalink = await fetchPermalink(published.id, credentials.accessToken); return { success: true, mediaId: published.id, permalink: permalink ?? undefined, method: "api", }; } catch (error) { const message = error instanceof ThreadsApiError ? error.message : error instanceof Error ? error.message : "Threads API 發布失敗"; return { success: false, error: message, method: "api" }; } } async function waitForContainerReady(containerId: string, accessToken: string) { for (let attempt = 0; attempt < POLL_MAX_ATTEMPTS; attempt++) { if (attempt > 0) { await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); } const status = await threadsGraphGet<{ status?: string; error_message?: string }>( `/${containerId}`, { access_token: accessToken, fields: "status,error_message", } ); if (status.status === "FINISHED") return; if (status.status === "ERROR") { throw new ThreadsApiError(status.error_message ?? "媒體處理失敗"); } } // Meta 建議至少等 30 秒,若狀態查不到仍嘗試發布 } async function fetchPermalink(mediaId: string, accessToken: string): Promise { try { const media = await threadsGraphGet<{ permalink?: string }>(`/${mediaId}`, { access_token: accessToken, fields: "permalink", }); return media.permalink ?? null; } catch { return null; } }