import "server-only"; import { getTiktokApiConfig } from "@/lib/config/server"; let cachedAccessToken: string | null = null; let tokenExpiresAt = 0; async function getAccessToken(): Promise { // Return cached token if still valid (with 5 min buffer) if (cachedAccessToken && Date.now() < tokenExpiresAt - 300_000) { return cachedAccessToken; } try { const { accessToken, clientKey, clientSecret, refreshToken } = getTiktokApiConfig(); if (accessToken) return accessToken; if (!clientKey || !clientSecret || !refreshToken) return null; const res = await fetch("https://open.tiktokapis.com/v2/oauth/token/", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_key: clientKey, client_secret: clientSecret, grant_type: "refresh_token", refresh_token: refreshToken, }), signal: AbortSignal.timeout(5000), }); if (!res.ok) return null; const data = await res.json(); if (data.access_token) { cachedAccessToken = data.access_token; tokenExpiresAt = Date.now() + (data.expires_in ?? 7200) * 1000; return cachedAccessToken; } return null; } catch { return null; } } export interface TiktokProfile { followerCount: number | null; displayName: string | null; username: string | null; avatarUrl: string | null; } export async function getTiktokProfile(): Promise { const empty: TiktokProfile = { followerCount: null, displayName: null, username: null, avatarUrl: null, }; const accessToken = await getAccessToken(); if (!accessToken) return empty; try { const res = await fetch( "https://open.tiktokapis.com/v2/user/info/?fields=follower_count,display_name,username,avatar_url", { headers: { Authorization: `Bearer ${accessToken}` }, cache: "no-store", signal: AbortSignal.timeout(5000), } ); if (!res.ok) return empty; const data = await res.json(); const user = data.data?.user; if (!user) return empty; return { followerCount: user.follower_count != null ? Number(user.follower_count) : null, displayName: user.display_name ?? null, username: user.username ?? null, avatarUrl: user.avatar_url ?? null, }; } catch { return empty; } } /** Convenience wrapper that just returns the follower count */ export async function getTiktokFollowerCount(): Promise { const profile = await getTiktokProfile(); return profile.followerCount; } export interface TiktokVideo { id: string; title: string; coverImageUrl: string; shareUrl: string; createTime: number; } export async function getLatestTiktokVideo(): Promise { const profile = await getTiktokProfile(); if (!profile.username) { console.error("[TikTok API] Could not get username from profile to fetch TikWM."); return null; } try { const res = await fetch(`https://www.tikwm.com/api/user/posts?unique_id=@${profile.username}&count=1`, { cache: "no-store", signal: AbortSignal.timeout(5000), }); if (!res.ok) return null; const data = await res.json(); const videos = data.data?.videos; if (!videos || videos.length === 0) return null; const v = videos[0]; const shareUrl = `https://www.tiktok.com/@${profile.username}/video/${v.video_id}`; return { id: v.video_id, title: v.title ?? "", coverImageUrl: v.cover ?? "", shareUrl: shareUrl, createTime: v.create_time ?? 0, }; } catch (e) { console.error("[TikTok API] Error fetching latest video via TikWM:", e); return null; } }