1- import * as cheerio from "cheerio" ;
2-
31import { NormalizedPost , Platform } from "../types" ;
42
53const MATCH_RE = / ^ (?: h t t p s ? : \/ \/ ) ? (?: [ \w - ] + \. ) * t i k t o k \. c o m (?: \/ | $ ) / ;
64
75const FOLLOWUP_RE =
86 / ^ h t t p s : \/ \/ (?: m | w w w | v m ) ? \. ? t i k t o k \. c o m \/ (?< tiktok_user > @ (?: [ \w . - ] + ) ? ) \/ (?< tiktok_type > v i d e o | p h o t o ) \/ (?< 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
1921export 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