UNPKG

tiktok-downloader

Version:

A JavaScript package to download videos from TikTok.

200 lines (181 loc) 8.21 kB
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' }; } }