tiktok-downloader
Version:
A JavaScript package to download videos from TikTok.
200 lines (181 loc) • 8.21 kB
text/typescript
const bypassCorsHeaders: string = "https://cors-tiktok.herokuapp.com/?u="; //Bypass CORS (Github : https://github.com/abdelyouni/cors-tiktok)
const linkPattern: string = '<link data-react-helmet="true" rel="canonical" href="';
const greaterSymbol: string = '>';
const closingTag: string = '/>';
const invertedTag: string = '</';
const idPattern: string = 'id="__NEXT_DATA__"';
const downloadParameter: string = '&d=1';
const tiktokRegex: RegExp = /https?:\/\/www\.tiktok\.com\/@.+\/video\/\d*/gm;
const tiktokMobileRegex: RegExp = /https?:\/\/m\.tiktok\.com\/v\/\d*/gm;
const tiktokMobileAltRegex: RegExp = /https?:\/\/vm\.tiktok\.com\/\d*/gm;
export namespace JSTikTok {
export interface DownloadResponse {
blob: Blob;
fileName: string;
type: string;
}
export interface VideoInfo {
id: any,
height: any,
duration: any,
sizeFormat: any,
cover: any,
animatedCover: any,
likes: any,
shares: any,
comments: any,
views: any,
title: any,
keywords: any,
description: any,
original_url: any,
download_url: any
}
export interface MusicInfo {
id: any,
title: any,
cover_large: any,
cover_medium: any,
cover_small: any,
artist: any,
album: any,
duration: any,
url: any
}
export interface AuthorInfo {
id: any,
uniqueId: any,
username: any,
avatar_large: any,
avatar_medium: any,
avatar_small: any,
signature: any,
createDate: any,
isVerified: any,
followers: any,
followings: any,
hearts: any,
totalVideos: any,
diggCount: any,
}
export interface TikTokInfo {
video: VideoInfo,
music: MusicInfo,
author: AuthorInfo
}
export function validateUrl(url: string): boolean {
return url && (tiktokRegex.test(url) || tiktokMobileRegex.test(url) || tiktokMobileAltRegex.test(url));
}
export function urlEncode(str: string): string {
if (str) {
str = encodeURIComponent(str)
.replace('!', '%21')
.replace('\'', '%27')
.replace('(', '%28')
.replace(')', '%29')
.replace('*', '%2A')
.replace('%20', '+');
}
return str;
}
export function getBlob(url: string): Promise<Blob> {
return new Promise<Blob>((response, reject) => {
const xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.responseType = 'blob';
xhr.onload = () => response(xhr.response);
xhr.onerror = (e) => reject(e);
xhr.send();
});
}
export async function getInfo(url: string): Promise<TikTokInfo> {
let tiktokUrl: string = bypassCorsHeaders + urlEncode(url);
let resp: string = await fetch(tiktokUrl).then(response => response.text()).then((data) => { return data; });
if (resp.includes(linkPattern)) {
tiktokUrl = urlEncode(resp.split(linkPattern)[1].split(greaterSymbol)[0]);
resp = await fetch(bypassCorsHeaders + tiktokUrl).then(response => response.text()).then((data) => { return data; });
}
const json: any = JSON.parse(resp.split(idPattern)[1].split(invertedTag)[0].split(closingTag)[1]);
const video : VideoInfo = {
id: json.props.pageProps.itemInfo.itemStruct.video.id,
height: json.props.pageProps.itemInfo.itemStruct.video.height,
duration: json.props.pageProps.itemInfo.itemStruct.video.duration,
sizeFormat: json.props.pageProps.itemInfo.itemStruct.video.ratio,
cover: json.props.pageProps.itemInfo.itemStruct.video.cover,
animatedCover: json.props.pageProps.itemInfo.itemStruct.video.dynamicCover,
likes: json.props.pageProps.itemInfo.itemStruct.stats.diggCount,
shares: json.props.pageProps.itemInfo.itemStruct.stats.shareCount,
comments: json.props.pageProps.itemInfo.itemStruct.stats.commentCount,
views: json.props.pageProps.itemInfo.itemStruct.stats.playCount,
title: json.props.pageProps.metaParams.title,
keywords: json.props.pageProps.metaParams.keywords,
description: json.props.pageProps.metaParams.description,
original_url: json.props.pageProps.metaParams.canonicalHref,
download_url: json.props.pageProps.itemInfo.itemStruct.video.downloadAddr,
};
const music: MusicInfo = {
id:json.props.pageProps.itemInfo.itemStruct.music.id,
title:json.props.pageProps.itemInfo.itemStruct.music.title,
cover_large:json.props.pageProps.itemInfo.itemStruct.music.coverLarge,
cover_medium:json.props.pageProps.itemInfo.itemStruct.music.coverMedium,
cover_small:json.props.pageProps.itemInfo.itemStruct.music.coverThumb,
artist:json.props.pageProps.itemInfo.itemStruct.music.authorName,
album:json.props.pageProps.itemInfo.itemStruct.music.album,
duration:json.props.pageProps.itemInfo.itemStruct.music.duration,
url:json.props.pageProps.itemInfo.itemStruct.music.playUrl,
};
const author: AuthorInfo = {
id:json.props.pageProps.itemInfo.itemStruct.author.id,
uniqueId:json.props.pageProps.itemInfo.itemStruct.author.uniqueId,
username:json.props.pageProps.itemInfo.itemStruct.author.nickname,
avatar_large:json.props.pageProps.itemInfo.itemStruct.author.avatarLarger,
avatar_medium:json.props.pageProps.itemInfo.itemStruct.author.avatarMedium,
avatar_small:json.props.pageProps.itemInfo.itemStruct.author.avatarThumb,
signature:json.props.pageProps.itemInfo.itemStruct.author.signature,
createDate:json.props.pageProps.itemInfo.itemStruct.author.createTime,
isVerified:json.props.pageProps.itemInfo.itemStruct.author.verified,
followers:json.props.pageProps.itemInfo.itemStruct.authorStats.followerCount,
followings:json.props.pageProps.itemInfo.itemStruct.authorStats.followingCount,
hearts:json.props.pageProps.itemInfo.itemStruct.authorStats.heart,
totalVideos:json.props.pageProps.itemInfo.itemStruct.authorStats.videoCount,
diggCount:json.props.pageProps.itemInfo.itemStruct.authorStats.diggCount,
};
return {
video: video,
music: music,
author: author
};
}
export async function downloadMusic(url: string): Promise<DownloadResponse> {
if(url == null) {
throw 'No URL was provided.';
}
if (!validateUrl(url)) {
throw 'The URL provided is not valid.'
}
const info: TikTokInfo = await getInfo(url);
const downloadUrl: string = bypassCorsHeaders + urlEncode(info.music.url) + downloadParameter;
const blob: Blob = await getBlob(downloadUrl);
return {
blob: blob,
fileName: info.music.id,
type: 'mp3'
};
}
export async function downloadVideo(url: string): Promise<DownloadResponse> {
if(url == null) {
throw 'No URL was provided.';
}
if (!validateUrl(url)) {
throw 'The URL provided is not valid.'
}
const info: TikTokInfo = await getInfo(url);
const downloadUrl: string = bypassCorsHeaders + urlEncode(info.video.download_url) + downloadParameter;
const blob: Blob = await getBlob(downloadUrl);
return {
blob: blob,
fileName: info.video.id,
type: 'mp4'
};
}
}