feat: add the view graph on tiktok
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
const TIKTOK_AUTH_URL = "https://www.tiktok.com/v2/auth/authorize";
|
||||
const TIKTOK_TOKEN_URL = "https://open.tiktokapis.com/v2/oauth/token/";
|
||||
const TIKTOK_USER_INFO_URL = "https://open.tiktokapis.com/v2/user/info/";
|
||||
const TIKTOK_VIDEO_LIST_URL = "https://open.tiktokapis.com/v2/video/list/";
|
||||
|
||||
const CLIENT_KEY = process.env.TIKTOK_CLIENT_KEY!;
|
||||
const CLIENT_SECRET = process.env.TIKTOK_CLIENT_SECRET!;
|
||||
@@ -38,7 +39,8 @@ export function getTikTokAuthUrl(state: string, codeChallenge: string): string {
|
||||
const params = new URLSearchParams({
|
||||
client_key: CLIENT_KEY,
|
||||
response_type: "code",
|
||||
scope: "user.info.basic,user.info.stats",
|
||||
// video.list is used as a fallback to compute total views from all videos.
|
||||
scope: "user.info.basic,user.info.stats,video.list",
|
||||
redirect_uri: getRedirectUri(),
|
||||
state,
|
||||
code_challenge: codeChallenge,
|
||||
@@ -115,13 +117,57 @@ export interface TikTokUserStats {
|
||||
followers: number;
|
||||
likes: number;
|
||||
videoCount: number;
|
||||
views: number;
|
||||
profileViews: number;
|
||||
username: string;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
}
|
||||
|
||||
async function fetchTotalVideoViews(accessToken: string): Promise<number> {
|
||||
const fields = "id,view_count";
|
||||
let cursor = 0;
|
||||
let totalViews = 0;
|
||||
|
||||
for (let page = 0; page < 50; page++) {
|
||||
const res = await fetch(`${TIKTOK_VIDEO_LIST_URL}?fields=${fields}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ max_count: 20, cursor }),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok || data.error?.code !== "ok") {
|
||||
throw new Error(data.error?.message || "TikTok video list fetch failed");
|
||||
}
|
||||
|
||||
const videos = Array.isArray(data.data?.videos) ? data.data.videos : [];
|
||||
for (const video of videos) {
|
||||
totalViews += Number(video?.view_count ?? 0);
|
||||
}
|
||||
|
||||
const hasMore = Boolean(data.data?.has_more);
|
||||
if (!hasMore) {
|
||||
break;
|
||||
}
|
||||
|
||||
const nextCursor = Number(data.data?.cursor ?? cursor + videos.length);
|
||||
if (!Number.isFinite(nextCursor) || nextCursor <= cursor) {
|
||||
break;
|
||||
}
|
||||
|
||||
cursor = nextCursor;
|
||||
}
|
||||
|
||||
return totalViews;
|
||||
}
|
||||
|
||||
export async function fetchUserStats(accessToken: string, openId: string): Promise<TikTokUserStats> {
|
||||
const fields = "follower_count,following_count,likes_count,video_count,display_name,avatar_url";
|
||||
const fields = "follower_count,following_count,likes_count,video_count,video_view_count,profile_view_count,display_name,avatar_url";
|
||||
const url = `${TIKTOK_USER_INFO_URL}?fields=${fields}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
@@ -137,11 +183,27 @@ export async function fetchUserStats(accessToken: string, openId: string): Promi
|
||||
}
|
||||
|
||||
const user = data.data?.user ?? {};
|
||||
let views = user.video_view_count ?? user.profile_view_count ?? 0;
|
||||
|
||||
// Some TikTok apps do not receive video_view_count in user.info.stats.
|
||||
// In that case we compute total views from the account video list.
|
||||
if (user.video_view_count == null) {
|
||||
try {
|
||||
const computedViews = await fetchTotalVideoViews(accessToken);
|
||||
if (computedViews > 0) {
|
||||
views = computedViews;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[TikTok views fallback error]", err);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
followers: user.follower_count ?? 0,
|
||||
likes: user.likes_count ?? 0,
|
||||
videoCount: user.video_count ?? 0,
|
||||
views,
|
||||
profileViews: user.profile_view_count ?? 0,
|
||||
username: openId,
|
||||
displayName: user.display_name ?? "",
|
||||
avatarUrl: user.avatar_url ?? "",
|
||||
|
||||
Reference in New Issue
Block a user