tiktok.mjs
javascript•Created 26 Des 2025, 10:05•290 views
Tiktok video and image slide downloader with complete metadata.
#downloader#tools#utility
javascript
0 lines
/***
@ Base: https://www.tiktok.com/
@ Author: Shannz
@ Note: Tiktok video and image slide downloader with complete metadata.
***/
import axios from 'axios';
import * as cheerio from 'cheerio';
import { CookieJar } from 'tough-cookie';
import { wrapper } from 'axios-cookiejar-support';
import { uploadFile } from 'cloudsky-storage';
async function getBuffer(url, apiInstance) {
try {
const response = await apiInstance.get(url, {
responseType: 'arraybuffer',
headers: {
'Referer': 'https://www.tiktok.com/',
'Range': 'bytes=0-'
}
});
return Buffer.from(response.data);
} catch (e) {
console.error(`[Download Error]:`, e.message);
return null;
}
}
export const tiktok = {
video: async (url) => {
const jar = new CookieJar();
const api = axios.create({
jar: jar,
withCredentials: true,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
}
});
wrapper(api);
try {
console.log(`Downloading Video...`);
const htmlResponse = await api.get(url);
const $ = cheerio.load(htmlResponse.data);
let scriptContent = $('#__UNIVERSAL_DATA_FOR_REHYDRATION__').html() || $('#SIGI_STATE').html();
if (!scriptContent) throw new Error('Script tag data tidak ditemukan (Captcha/IP Blocked).');
const jsonData = JSON.parse(scriptContent);
const defaultScope = jsonData?.__DEFAULT_SCOPE__;
const itemStruct = defaultScope?.["webapp.video-detail"]?.itemInfo?.itemStruct
|| Object.values(jsonData.ItemModule || {})[0];
if (!itemStruct) throw new Error('Struct video tidak ditemukan dalam JSON.');
const videoId = itemStruct.id;
const videoData = itemStruct.video;
const watermarkUrl = videoData.downloadAddr || videoData.playAddr;
let hdNoWatermarkUrl = null;
let bitrateLabel = 0;
let qualityLabel = 'Original';
if (videoData.bitrateInfo && Array.isArray(videoData.bitrateInfo)) {
const bestQuality = videoData.bitrateInfo.sort((a, b) => b.Bitrate - a.Bitrate)[0];
if (bestQuality) {
bitrateLabel = bestQuality.Bitrate;
qualityLabel = bestQuality.QualityType;
const urlList = bestQuality.PlayAddr?.UrlList || [];
hdNoWatermarkUrl = urlList.find(u => u.includes('aweme/v1/play')) || urlList[urlList.length - 1];
}
}
if (!hdNoWatermarkUrl) hdNoWatermarkUrl = videoData.playAddr;
const finalResult = {
metadata: {
id: videoId,
description: itemStruct.desc,
createTime: new Date(itemStruct.createTime * 1000).toLocaleString(),
region: itemStruct.locationCreated || 'N/A',
hashtags: itemStruct.challenges?.map(tag => ({
id: tag.id,
name: tag.title
})) || []
},
originalUrl: {
watermark: watermarkUrl,
hd_nonwatermark: hdNoWatermarkUrl
},
cloudUrl: {
watermark: null,
hd_nonwatermark: null
},
videoInfo: {
duration: videoData?.duration,
resolution: `${videoData?.width}x${videoData?.height}`,
format: videoData?.format,
codec: videoData?.codecType,
bitrate: bitrateLabel,
quality: qualityLabel,
cover: {
static: videoData?.cover,
dynamic: videoData?.dynamicCover,
origin: videoData?.originCover
}
},
author: {
id: itemStruct.author?.id,
uniqueId: itemStruct.author?.uniqueId,
nickname: itemStruct.author?.nickname,
signature: itemStruct.author?.signature,
avatar: itemStruct.author?.avatarLarger || itemStruct.author?.avatarThumb,
verified: itemStruct.author?.verified
},
music: {
id: itemStruct.music?.id,
title: itemStruct.music?.title,
author: itemStruct.music?.authorName,
cover: itemStruct.music?.coverLarge,
playUrl: itemStruct.music?.playUrl,
isOriginal: itemStruct.music?.original
},
stats: {
views: itemStruct.statsV2?.playCount || itemStruct.stats?.playCount,
likes: itemStruct.statsV2?.diggCount || itemStruct.stats?.diggCount,
comments: itemStruct.statsV2?.commentCount || itemStruct.stats?.commentCount,
shares: itemStruct.statsV2?.shareCount || itemStruct.stats?.shareCount,
saves: itemStruct.statsV2?.collectCount || itemStruct.stats?.collectCount
}
};
if (watermarkUrl) {
const wmBuffer = await getBuffer(watermarkUrl, api);
if (wmBuffer) {
try {
const upload = await uploadFile(wmBuffer, `tiktok_wm_${videoId}.mp4`);
if (upload.success) {
finalResult.cloudUrl.watermark = upload.data.url;
}
} catch (e) {
console.error(`[Upload WM Error]:`, e.message);
}
}
}
if (hdNoWatermarkUrl) {
const hdBuffer = await getBuffer(hdNoWatermarkUrl, api);
if (hdBuffer) {
try {
const upload = await uploadFile(hdBuffer, `tiktok_hd_${videoId}.mp4`);
if (upload.success) {
finalResult.cloudUrl.hd_nonwatermark = upload.data.url;
}
} catch (e) {
console.error(`[Upload HD Error]:`, e.message);
}
}
}
return {
status: true,
result: finalResult
};
} catch (error) {
console.error('Error Main Process:', error.message);
return { status: false, error: error.message };
}
},
slide: async (url) => {
const jar = new CookieJar();
const api = axios.create({
jar: jar,
withCredentials: true,
headers: {
'User-Agent': 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
}
});
wrapper(api);
try {
console.log(`Downloading Image Slide...`);
const htmlResponse = await api.get(url);
const $ = cheerio.load(htmlResponse.data);
let scriptContent = $('#__UNIVERSAL_DATA_FOR_REHYDRATION__').html() || $('#SIGI_STATE').html();
if (!scriptContent) throw new Error('Script tag data tidak ditemukan (Captcha/IP Blocked).');
const jsonData = JSON.parse(scriptContent);
const defaultScope = jsonData?.__DEFAULT_SCOPE__;
const itemStruct = defaultScope?.["webapp.reflow.video.detail"]?.itemInfo?.itemStruct
|| defaultScope?.["webapp.video-detail"]?.itemInfo?.itemStruct;
if (!itemStruct) throw new Error('Struct video tidak ditemukan dalam JSON.');
if (!itemStruct.imagePost) throw new Error('Konten ini bukan berupa image slide.');
const videoId = itemStruct.id;
const imagesList = itemStruct.imagePost.images.map(img => img.imageURL.urlList[0]);
const audioUrl = itemStruct.music?.playUrl;
const finalResult = {
metadata: {
id: videoId,
type: 'image_slide',
description: itemStruct.desc,
createTime: new Date(itemStruct.createTime * 1000).toLocaleString(),
region: itemStruct.locationCreated || 'N/A',
hashtags: itemStruct.challenges?.map(tag => ({
id: tag.id,
name: tag.title
})) || []
},
originalUrl: {
images: imagesList,
audio: audioUrl
},
author: {
id: itemStruct.author?.id,
uniqueId: itemStruct.author?.uniqueId,
nickname: itemStruct.author?.nickname,
signature: itemStruct.author?.signature,
avatar: itemStruct.author?.avatarLarger || itemStruct.author?.avatarThumb,
verified: itemStruct.author?.verified
},
music: {
id: itemStruct.music?.id,
title: itemStruct.music?.title,
author: itemStruct.music?.authorName,
cover: itemStruct.music?.coverLarge,
playUrl: audioUrl,
isOriginal: itemStruct.music?.original
},
stats: {
views: itemStruct.statsV2?.playCount || itemStruct.stats?.playCount,
likes: itemStruct.statsV2?.diggCount || itemStruct.stats?.diggCount,
comments: itemStruct.statsV2?.commentCount || itemStruct.stats?.commentCount,
shares: itemStruct.statsV2?.shareCount || itemStruct.stats?.shareCount,
saves: itemStruct.statsV2?.collectCount || itemStruct.stats?.collectCount
}
};
return {
status: true,
result: finalResult
};
} catch (error) {
console.error('Error Slide Process:', error.message);
return { status: false, error: error.message };
}
}
}
//tiktok.slide('https://vm.tiktok.com/ZP8bbmHn6/').then(a => console.log(JSON.stringify(a, null, 2)))
//tiktok.video('https://vm.tiktok.com/ZP8bbXdyf/').then(a => console.log(JSON.stringify(a, null, 2)))