Skip to content

Commit fc3de5a

Browse files
committed
fix(platforms): use tiktok player api
1 parent 44e0164 commit fc3de5a

1 file changed

Lines changed: 38 additions & 51 deletions

File tree

packages/platforms/src/platforms/tiktok.ts

Lines changed: 38 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
1-
import * as cheerio from "cheerio";
2-
31
import { NormalizedPost, Platform } from "../types";
42

53
const MATCH_RE = /^(?:https?:\/\/)?(?:[\w-]+\.)*tiktok\.com(?:\/|$)/;
64

75
const FOLLOWUP_RE =
86
/^https:\/\/(?:m|www|vm)?\.?tiktok\.com\/(?<tiktok_user>@(?:[\w.-]+)?)\/(?<tiktok_type>video|photo)\/(?<tiktok_id>\d+)/;
97

10-
async function parseMedia(raw: Record<string, any>): Promise<NormalizedPost["media"]> {
11-
if (raw.video) {
12-
const urls = raw.video.PlayAddrStruct?.UrlList ?? [];
13-
const videoURL = urls.find((url: string) => url.includes("/aweme/v1/play/")) ?? urls[0];
14-
if (videoURL) return [{ url: videoURL, type: "video" }];
8+
function parseMedia(raw: Record<string, any>): NormalizedPost["media"] {
9+
if (raw.image_post_info) {
10+
return raw.image_post_info.images
11+
.map((image: any) => image.display_image?.url_list?.[0])
12+
.filter((url: unknown): url is string => typeof url === "string")
13+
.map((url: string) => ({ url, type: "image" }));
1514
}
16-
return [];
15+
16+
const urls = raw.video_info?.url_list ?? [];
17+
const videoURL = urls.find((url: string) => url.includes("/aweme/v1/play/")) ?? urls[0];
18+
return videoURL ? [{ url: videoURL, type: "video" }] : [];
1719
}
1820

1921
export const TikTok: Platform<"TikTok", Record<string, any>, {}> = {
@@ -35,69 +37,54 @@ export const TikTok: Platform<"TikTok", Record<string, any>, {}> = {
3537
return `${tiktok_user}/${tiktok_type}/${tiktok_id}`;
3638
},
3739
async fetch(id, env) {
38-
const [tiktok_user, tiktok_type, tiktok_id] = id.split("/");
39-
const resp = await fetch(`https://www.tiktok.com/${tiktok_user}/${tiktok_type}/${tiktok_id}`, {
40+
const tiktok_id = id.split("/")[2];
41+
const url = new URL("https://www.tiktok.com/player/api/v1/items");
42+
url.searchParams.set("item_ids", tiktok_id);
43+
url.searchParams.set("language", "en-US");
44+
url.searchParams.set("aid", "1459");
45+
url.searchParams.set("data_source", "pack");
46+
47+
const resp = await fetch(url, {
4048
method: "GET",
4149
headers: {
42-
"User-Agent": env?.EMBED_USER_AGENT ?? "",
50+
"User-Agent": env?.EMBED_USER_AGENT ?? "curl/8.7.1",
4351
},
4452
});
4553
if (!resp.ok) {
4654
throw { code: resp.status, message: resp.statusText };
4755
}
48-
const html = await resp.text();
49-
const $ = cheerio.load(html);
50-
let script: string;
51-
try {
52-
script = $("script#__UNIVERSAL_DATA_FOR_REHYDRATION__").text();
53-
} catch {
54-
throw {
55-
code: 500,
56-
message: "TikTok page structure changed: missing data script",
57-
};
58-
}
59-
60-
let data: any;
61-
try {
62-
data = JSON.parse(script);
63-
} catch {
64-
throw { code: 500, message: "Failed to parse TikTok data" };
65-
}
66-
67-
if (data?.__DEFAULT_SCOPE__?.["webapp.browserRedirect-context"]) {
68-
return this.fetch(
69-
data?.__DEFAULT_SCOPE__?.["webapp.browserRedirect-context"].browserRedirectUrl,
70-
env,
71-
);
72-
}
7356

74-
let itemStruct = data?.__DEFAULT_SCOPE__?.["webapp.video-detail"]?.itemInfo?.itemStruct;
75-
if (!itemStruct) {
57+
const data: any = await resp.json();
58+
const item = data?.items?.find((item: any) => item.id_str === tiktok_id);
59+
if (!item) {
60+
const result = data?.results?.find((result: any) => result.id_str === tiktok_id);
7661
throw {
7762
code: 500,
78-
message: "TikTok page structure changed: missing video data",
63+
message: result?.code ?? "TikTok player API returned no item data",
7964
};
8065
}
81-
return itemStruct;
66+
return item;
8267
},
8368
async transform(raw) {
69+
const id = raw.id_str;
70+
const handle = raw.author_info.unique_id;
71+
const type = raw.image_post_info ? "photo" : "video";
8472
return {
8573
platform: this.type,
8674
author: {
87-
name: raw.author.nickname,
88-
avatar: raw.author.avatarMedium,
89-
handle: raw.author.uniqueId,
90-
url: `https://tiktok.com/@${raw.author.uniqueId}`,
75+
name: raw.author_info.nickname,
76+
avatar: raw.author_info.avatar_url_list[0],
77+
handle,
78+
url: `https://tiktok.com/@${handle}`,
9179
},
92-
timestamp: +raw.createTime,
93-
url: `https://tiktok.com/@${raw.author.uniqueId}/video/${raw.video.id}`,
80+
timestamp: raw.create_time ?? Number(BigInt(id) >> 32n),
81+
url: `https://tiktok.com/@${handle}/${type}/${id}`,
9482
text: raw.desc,
95-
media: await parseMedia(raw),
83+
media: parseMedia(raw),
9684
stats: {
97-
comments: raw.statsV2.commentCount,
98-
reposts: raw.statsV2.shareCount,
99-
likes: raw.statsV2.diggCount,
100-
views: raw.statsV2.playCount,
85+
comments: raw.statistics_info.comment_count,
86+
reposts: raw.statistics_info.share_count,
87+
likes: raw.statistics_info.digg_count,
10188
},
10289
};
10390
},

0 commit comments

Comments
 (0)