fix(route/tiktok): fix route (#20648)

* fix(route/tiktok): fix route

* fix(route/tiktok): clarify comment on officalItem property
This commit is contained in:
Tony 2025-12-08 01:00:47 +08:00 committed by GitHub
parent fd98be1027
commit 24612ee4d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 299 additions and 14 deletions

273
lib/routes/tiktok/types.ts Normal file
View File

@ -0,0 +1,273 @@
export type Item = {
AIGCDescription: string;
CategoryType: number;
author: {
UserStoryStatus: number;
avatarLarger: string;
avatarMedium: string;
avatarThumb: string;
commentSetting: number;
downloadSetting: number;
duetSetting: number;
ftc: boolean;
id: string;
isADVirtual: boolean;
isEmbedBanned: boolean;
nickname: string;
openFavorite: boolean;
privateAccount: boolean;
relation: number;
secUid: string;
secret: boolean;
signature: string;
stitchSetting: number;
uniqueId: string;
verified: boolean;
};
authorStats: {
diggCount: number;
followerCount: number;
followingCount: number;
friendCount: number;
heart: number;
heartCount: number;
videoCount: number;
};
authorStatsV2: {
diggCount: string;
followerCount: string;
followingCount: string;
friendCount: string;
heart: string;
heartCount: string;
videoCount: string;
};
backendSourceEventTracking: string;
collected: boolean;
createTime: number;
creatorAIComment: {
eligibleVideo: boolean;
hasAITopic: boolean;
notEligibleReason: number;
};
desc: string;
digged: boolean;
duetDisplay: number;
duetEnabled: boolean;
forFriend: boolean;
id: string;
isAd: boolean;
isReviewing: boolean;
itemCommentStatus: number;
item_control: {
can_repost: boolean;
can_comment?: boolean;
can_creator_redirect?: boolean;
can_music_redirect?: boolean;
can_share?: boolean;
};
music: {
authorName: string;
coverLarge: string;
coverMedium: string;
coverThumb: string;
duration: number;
id: string;
isCopyrighted: boolean;
is_commerce_music: boolean;
is_unlimited_music: boolean;
original: boolean;
playUrl: string;
private: boolean;
shoot_duration: number;
title: string;
tt2dsp: {
tt_to_dsp_song_infos: {
meta_song_id: string;
platform: number;
song_id: string;
token: {
apple_music_token: {
developer_token: string;
};
};
}[];
};
};
officalItem: boolean; // Not a typo for "officialItem"
originalItem: boolean;
privateItem: boolean;
secret: boolean;
shareEnabled: boolean;
stats: {
collectCount: number;
commentCount: number;
diggCount: number;
playCount: number;
shareCount: number;
};
statsV2: {
collectCount: string;
commentCount: string;
diggCount: string;
playCount: string;
repostCount: string;
shareCount: string;
};
stitchDisplay: number;
stitchEnabled: boolean;
textLanguage: string;
textTranslatable: boolean;
video: {
PlayAddrStruct: {
DataSize: number;
FileCs: string;
FileHash: string;
Height: number;
Uri: string;
UrlKey: string;
UrlList: string[];
Width: number;
};
VQScore: string;
bitrate: number;
bitrateInfo: {
Bitrate: number;
BitrateFPS: number;
CodecType: string;
Format: string;
GearName: string;
MVMAF: string;
PlayAddr: {
DataSize: number;
FileCs: string;
FileHash: string;
Height: number;
Uri: string;
UrlKey: string;
UrlList: string[];
Width: number;
};
QualityType: number;
VideoExtra: string;
}[];
claInfo: {
enableAutoCaption: boolean;
hasOriginalAudio: boolean;
noCaptionReason?: number;
captionInfos?: {
captionFormat: string;
claSubtitleID: string;
expire: string;
isAutoGen: boolean;
isOriginalCaption: boolean;
language: string;
languageCode: string;
languageID: string;
subID: string;
subtitleType: string;
translationType: string;
url: string;
urlList: string[];
variant: string;
}[];
captionsType?: number;
originalLanguageInfo?: {
canTranslateRealTimeNoCheck: boolean;
language: string;
languageCode: string;
languageID: string;
};
};
codecType: string;
cover: string;
definition: string;
downloadAddr: string;
duration: number;
dynamicCover: string;
encodeUserTag: string;
encodedType: string;
format: string;
height: number;
id: string;
originCover: string;
playAddr: string;
ratio: string;
size: number;
videoID: string;
videoQuality: string;
volumeInfo: {
Loudness: number;
Peak: number;
};
width: number;
zoomCover: {
240: string;
480: string;
720: string;
960: string;
};
subtitleInfos?: {
Format: string;
LanguageCodeName: string;
LanguageID: string;
Size: number;
Source: string;
Url: string;
UrlExpire: number;
Version: string;
}[];
};
challenges?: {
coverLarger: string;
coverMedium: string;
coverThumb: string;
desc: string;
id: string;
profileLarger: string;
profileMedium: string;
profileThumb: string;
title: string;
}[];
textExtra?: Array<{
awemeId: string;
end: number;
hashtagName: string;
isCommerce: boolean;
start: number;
subType: number;
type: number;
secUid?: string;
userId?: string;
userUniqueId?: string;
}>;
videoSuggestWordsList?: {
video_suggest_words_struct: {
hint_text: string;
scene: string;
words: Array<{
word: string;
word_id: string;
}>;
}[];
};
diversificationId?: number;
contents?: {
desc: string;
textExtra?: {
awemeId: string;
end: number;
hashtagName: string;
isCommerce: boolean;
start: number;
subType: number;
type: number;
secUid?: string;
userId?: string;
userUniqueId?: string;
}[];
}[];
BAInfo?: string;
adAuthorization?: boolean;
adLabelVersion?: number;
};

View File

@ -1,5 +1,7 @@
import path from 'node:path';
import { load } from 'cheerio';
import { config } from '@/config';
import type { Route } from '@/types';
import cache from '@/utils/cache';
@ -8,6 +10,8 @@ import puppeteer from '@/utils/puppeteer';
import { queryToBoolean } from '@/utils/readable-social';
import { art } from '@/utils/render';
import type { Item } from './types';
const baseUrl = 'https://www.tiktok.com';
export const route: Route = {
@ -44,26 +48,36 @@ async function handler(ctx) {
const browser = await puppeteer();
const page = await browser.newPage();
await page.setRequestInterception(true);
let itemList = { itemList: [] };
page.on('request', (request) => {
request.resourceType() === 'document' || request.resourceType() === 'script' ? request.continue() : request.abort();
['document', 'script', 'xhr', 'fetch'].includes(request.resourceType()) ? request.continue() : request.abort();
});
page.on('response', async (response) => {
const request = response.request();
if (request.url().startsWith('https://www.tiktok.com/api/post/item_list/')) {
itemList = await response.json();
}
});
await page.goto(`${baseUrl}/${user}`, {
waitUntil: 'networkidle0',
});
const SIGI_STATE = await page.evaluate(() => window.SIGI_STATE);
const pageHtml = await page.content();
await browser.close();
const lang = SIGI_STATE.AppContext.lang;
const SharingMetaState = SIGI_STATE.SharingMetaState;
const ItemModule = SIGI_STATE.ItemModule;
const $ = load(pageHtml);
const rehydrationData = JSON.parse($('script#__UNIVERSAL_DATA_FOR_REHYDRATION__').text());
const userDetail = rehydrationData.__DEFAULT_SCOPE__['webapp.user-detail'];
return { lang, SharingMetaState, ItemModule };
return { itemList, userDetail };
},
config.cache.routeExpire,
false
);
const items = Object.values(data.ItemModule).map((item) => ({
const { itemList, userDetail } = data;
const items = itemList.itemList.map((item: Item) => ({
title: item.desc,
description: art(path.join(__dirname, 'templates/user.art'), {
poster: item.video.cover,
@ -71,18 +85,16 @@ async function handler(ctx) {
useIframe,
id: item.id,
}),
author: item.nickname,
author: item.author.nickname,
pubDate: parseDate(item.createTime, 'X'),
link: `${baseUrl}/@${item.author}/video/${item.id}`,
category: item.textExtra.map((t) => `#${t.hashtagName}`),
link: `${baseUrl}/@${item.author.uniqueId}/video/${item.id}`,
}));
return {
title: data.SharingMetaState.value['og:title'],
description: data.SharingMetaState.value['og:description'],
image: data.SharingMetaState.value['og:image'],
title: userDetail.shareMeta.title,
description: userDetail.shareMeta.desc,
image: userDetail.userInfo.user.avatarLarger,
link: `${baseUrl}/${user}`,
item: items,
language: data.lang,
};
}