Files
erika/lib/followers/tiktok.ts
T

88 lines
2.4 KiB
TypeScript

import "server-only";
import { getTiktokApiConfig } from "@/lib/config/server";
let cachedAccessToken: string | null = null;
let tokenExpiresAt = 0;
async function getAccessToken(): Promise<string | null> {
// 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<TiktokProfile> {
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;
}
}