mirror of
https://github.com/hansputera/tiktok-dl.git
synced 2026-04-05 19:51:57 +02:00
feat(lib): add searchPreview method and transformer
This commit is contained in:
@@ -8,22 +8,23 @@ const SearchType = ['trend', 'videos'];
|
||||
export default async (req: VercelRequest, res: VercelResponse) => {
|
||||
try {
|
||||
ow(req.query, ow.object.exactShape({
|
||||
q: ow.string.minLength(5).maxLength(50),
|
||||
t: ow.optional.string.validate((v) => ({
|
||||
q: ow.string.minLength(3).maxLength(50),
|
||||
t: ow.string.validate((v) => ({
|
||||
validator: typeof v === 'string' && SearchType.includes(v.toLowerCase()),
|
||||
message: 'Expected \'t\' is \'trend\' or \'videos\''
|
||||
}))
|
||||
}));
|
||||
|
||||
const t = req.query.t ? '' : req.query.t?.toLowerCase();
|
||||
if (!t?.length || SearchType.indexOf(t) < 0) return res.status(400).json({ 'error': 'Invalid t' });
|
||||
|
||||
switch(t) {
|
||||
switch(req.query.t) {
|
||||
case SearchType[0]:
|
||||
const result = await tiktok.searchPreview(req.query.q);
|
||||
return res.json({ error: null, ...result });
|
||||
const preview = await tiktok.searchPreview(req.query.q);
|
||||
return res.json({ error: null, ...preview });
|
||||
case SearchType[1]:
|
||||
const full = await tiktok.searchFull(req.query.q);
|
||||
return res.json({ error: null, ...full });
|
||||
|
||||
default:
|
||||
return res.json({ error: 'Invalid t' });
|
||||
return res.json({ error: 'Invalid \'t\'' });
|
||||
}
|
||||
} catch (e) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { fetch } from '.';
|
||||
import type { SearchPreviewTypeResult } from '../types';
|
||||
import { fetch, TFetch } from '.';
|
||||
|
||||
import { ItemEnums, SearchFullResult, SearchPreviewTypeResult } from '../types';
|
||||
import { tiktokBase } from './config';
|
||||
import { Transformer } from './transformer';
|
||||
|
||||
class TikTok {
|
||||
async searchPreview(query: string) {
|
||||
@@ -16,6 +18,31 @@ class TikTok {
|
||||
}
|
||||
}
|
||||
|
||||
async searchFull(query: string) {
|
||||
const response = await TFetch('./api/search/general/full/' + this.buildParam(query));
|
||||
const d = JSON.parse(response.body) as SearchFullResult;
|
||||
|
||||
const userIndex = d.data.findIndex(x => x.user_list);
|
||||
if (userIndex >= 0) {
|
||||
d.data[userIndex].user_list?.forEach(uList => {
|
||||
(d.data as unknown[]).push({
|
||||
type: ItemEnums.User,
|
||||
...Transformer.transformUser(uList),
|
||||
});
|
||||
});
|
||||
|
||||
delete d.data[userIndex]; // remove user list item
|
||||
}
|
||||
|
||||
return {
|
||||
'q': encodeURIComponent(query),
|
||||
'data': d.data.map(x => {
|
||||
if (x.item) return { ...Transformer.transformVideo(x.item), type: ItemEnums.Video };
|
||||
else return x;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private buildParam(q: string, region = 'ID'): string {
|
||||
return `?aid=${Math.floor(Math.random() * 5000)}&app_language=en&app_name=tiktok_web&browser_language=en-US&browser_name=Mozilla&browser_online=true&browser_platform=Linux x86_64&browser_version=5.0 (X11)&channel=tiktok_web&cookie_enabled=true&device_id=7015806844518483457&device_platform=web_pc&focus_state=true&from_page=search&history_len=5&is_fullscreen=false&is_page_visible=true&keyword=${encodeURIComponent(q)}&os=linux&priority_region=&referer=®ion=${region.toUpperCase()}&screen_height=768&screen_width=1364&tz_name=Asia/Jakarta`;
|
||||
}
|
||||
|
||||
89
lib/transformer.ts
Normal file
89
lib/transformer.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { UserResult, VideoItemResult } from '../types';
|
||||
|
||||
export class Transformer {
|
||||
static transformUser(u: UserResult) {
|
||||
return {
|
||||
'id': u.user_info.uid,
|
||||
'username': u.user_info.unique_id,
|
||||
'avatar': {
|
||||
'variants': u.user_info.avatar_thumb.url_list,
|
||||
'id': u.user_info.avatar_thumb.uri,
|
||||
'properties': {
|
||||
'width': u.user_info.avatar_thumb.width,
|
||||
'height': u.user_info.avatar_thumb.height
|
||||
}
|
||||
},
|
||||
'bio': u.user_info.signature,
|
||||
'nick': u.user_info.nickname,
|
||||
'verified': Boolean(u.user_info.custom_verify),
|
||||
'followers': u.user_info.follower_count,
|
||||
'custom_verify': u.user_info.custom_verify
|
||||
};
|
||||
};
|
||||
|
||||
static transformAuthor(a: VideoItemResult['author']) {
|
||||
return {
|
||||
'id': a.id,
|
||||
'username': a.uniqueId,
|
||||
'isPrivate': a.privateAccount,
|
||||
'nick': a.nickname,
|
||||
'avatar': {
|
||||
'thumbnail': a.avatarThumb,
|
||||
'medium': a.avatarMedium,
|
||||
'large': a.avatarLarge,
|
||||
},
|
||||
'bio': a.signature,
|
||||
'verified': a.verified
|
||||
}
|
||||
};
|
||||
|
||||
static transformVideo(v: VideoItemResult) {
|
||||
return {
|
||||
'id': v.id,
|
||||
'createdAt': {
|
||||
'iso': new Date(v.createTime),
|
||||
'date': v.createTime
|
||||
},
|
||||
'description': v.desc,
|
||||
'properties': {
|
||||
'video': {
|
||||
'streamUrl': v.video.playAddr,
|
||||
'downloadUrl': v.video.downloadAddr,
|
||||
'quality': v.video.videoQuality,
|
||||
'duration': v.video.duration,
|
||||
'cover': {
|
||||
'photo': v.video.originCover,
|
||||
'animated': v.video.dynamicCover,
|
||||
'share': v.video.shareCover.filter(s => s.length),
|
||||
'reflow': v.video.reflowCover,
|
||||
},
|
||||
'bitrate': v.video.bitrate,
|
||||
'format': v.video.format,
|
||||
'height': v.video.height,
|
||||
'width': v.video.width,
|
||||
},
|
||||
'audio': {
|
||||
'id': v.music.id,
|
||||
'title': v.music.title,
|
||||
'duration': v.music.duration,
|
||||
'sourceUrl': v.music.playUrl,
|
||||
'cover': {
|
||||
'thumbnail': v.music.coverThumb,
|
||||
'medium': v.music.coverMedium,
|
||||
'large': v.music.coverLarge,
|
||||
},
|
||||
'author': v.music.authorName,
|
||||
'isOriginal': v.music.original,
|
||||
'album': v.music.album.length ? v.music.album : '-',
|
||||
},
|
||||
'author': {
|
||||
...this.transformAuthor(v.author),
|
||||
'stats': v.authorStats,
|
||||
},
|
||||
'stats': v.stats,
|
||||
'stickers': v.stickersOnItem,
|
||||
'extra': v.textExtra,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
4
types/enums.ts
Normal file
4
types/enums.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export enum ItemEnums {
|
||||
User = 1,
|
||||
Video = 2
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './search';
|
||||
export * from './search';
|
||||
export * from './enums';
|
||||
|
||||
130
types/search.ts
130
types/search.ts
@@ -29,4 +29,132 @@ export interface SearchPreviewTypeResult {
|
||||
sug_list: SearchPreviewSug[];
|
||||
// other record ignored because i think that isn't important
|
||||
}
|
||||
/** END SEARCH PREVIEW */
|
||||
/** END SEARCH PREVIEW */
|
||||
|
||||
/** SEARCH FULL */
|
||||
export interface UserResult {
|
||||
user_info: {
|
||||
uid: string;
|
||||
nickname: string;
|
||||
signature: string;
|
||||
avatar_thumb: {
|
||||
uri: string;
|
||||
url_list: string[];
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
follow_status: number;
|
||||
follower_count: number;
|
||||
custom_verify?: string;
|
||||
unique_id?: string;
|
||||
room_id: number;
|
||||
enterprise_verify_reason?: string;
|
||||
cover_url?: string;
|
||||
sec_uid: string;
|
||||
}
|
||||
}
|
||||
|
||||
interface Video {
|
||||
id: string;
|
||||
height: number;
|
||||
width: number;
|
||||
duration: number;
|
||||
ratio: string;
|
||||
cover: string;
|
||||
originCover: string;
|
||||
dynamicCover: string;
|
||||
playAddr: string;
|
||||
downloadAddr: string;
|
||||
shareCover: string[];
|
||||
reflowCover: string;
|
||||
bitrate: number;
|
||||
encodedType: string;
|
||||
format: string;
|
||||
videoQuality: string;
|
||||
encodeUserTag: string;
|
||||
};
|
||||
|
||||
interface Author {
|
||||
id: string;
|
||||
uniqueId: string;
|
||||
nickname: string;
|
||||
avatarThumb: string;
|
||||
avatarMedium: string;
|
||||
avatarLarge: string;
|
||||
signature: string;
|
||||
verified: boolean;
|
||||
secUid: string;
|
||||
secret: boolean;
|
||||
ftc: boolean;
|
||||
relation: number;
|
||||
openFavorite: boolean;
|
||||
commentSetting: number;
|
||||
duetSetting: number;
|
||||
stitchSetting: number;
|
||||
privateAccount: boolean;
|
||||
};
|
||||
|
||||
interface Music {
|
||||
id: string;
|
||||
title: string;
|
||||
playUrl: string;
|
||||
coverThumb: string;
|
||||
coverMedium: string;
|
||||
coverLarge: string;
|
||||
authorName: string;
|
||||
original: boolean;
|
||||
duration: number;
|
||||
album: string;
|
||||
};
|
||||
|
||||
interface Stats {
|
||||
diggCount: number;
|
||||
shareCount: number;
|
||||
commentCount: number;
|
||||
playCount: number;
|
||||
};
|
||||
|
||||
interface TextExtra {
|
||||
awemeid: string;
|
||||
start: number;
|
||||
end: number;
|
||||
hashtagName: string;
|
||||
hastagId: string;
|
||||
type: number;
|
||||
userId: string;
|
||||
isCommerce: boolean;
|
||||
userUniqueId: string;
|
||||
secUid: string;
|
||||
};
|
||||
|
||||
interface StickerItem {
|
||||
stickerType: number;
|
||||
stickerText: string[];
|
||||
};
|
||||
|
||||
export interface VideoItemResult {
|
||||
id: string;
|
||||
desc: string;
|
||||
createTime: number;
|
||||
video: Video;
|
||||
author: Author;
|
||||
music: Music;
|
||||
stats: Stats;
|
||||
authorStats: Omit<Stats, 'playCount' | 'commentCount' | 'shareCount'> & {
|
||||
followingCount: number;
|
||||
followerCount: number;
|
||||
heartCount: number;
|
||||
videoCount: number;
|
||||
};
|
||||
textExtra: TextExtra[];
|
||||
stickersOnItem: StickerItem[];
|
||||
}
|
||||
|
||||
export interface SearchFullResult {
|
||||
data: {
|
||||
type: number;
|
||||
user_list?: UserResult[];
|
||||
item?: VideoItemResult;
|
||||
}[];
|
||||
}
|
||||
/** END SEARCH FULL */
|
||||
Reference in New Issue
Block a user