@prevter/tiktok-scraper
Version:
Library for downloading videos from TikTok (without watermark)
175 lines (174 loc) • 7.13 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fetchVideo = exports.detectVideoId = exports.getVideoId = exports.getFullURL = void 0;
const https_1 = __importDefault(require("https"));
const API_BASE_URL = 'https://api16-normal-v4.tiktokv.com';
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
};
/**
* Get data from a URL
* @param url URL to get data from
* @returns Promise containing the data from the URL
*/
const get = (url) => {
return new Promise((resolve, reject) => {
https_1.default
.get(url, { headers }, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => resolve(data));
})
.on('error', reject);
});
};
/**
* Get a buffer from a URL
* @param url URL to get buffer from
* @returns Promise containing the buffer from the URL
*/
const getBuffer = (url, progress) => {
return new Promise((resolve, reject) => {
https_1.default
.get(url, { headers }, (res) => {
let data = Buffer.from([]);
res.on('data', (chunk) => {
data = Buffer.concat([data, chunk]);
if (!progress)
return;
const downloaded = data.length;
const total = parseInt(res.headers['content-length'] || '0');
progress({
downloaded,
total,
progress: (downloaded / total) * 100,
});
});
res.on('end', () => resolve(data));
})
.on('error', reject);
});
};
/**
* Loads a TikTok video page from short URL to get the full URL
* @param url Short TikTok video URL (e.g. https://vm.tiktok.com/...)
* @returns Promise containing the full TikTok video URL
*/
const getFullURL = (url) => __awaiter(void 0, void 0, void 0, function* () {
var match = url.match(/(vm|vt)\.tiktok\.com\/(.*)/);
if (!match)
match = url.match(/(www|vm|vt)\.tiktok\.com\/t\/(.*)/);
if (!match)
throw new Error(`Unknown TikTok video URL: ${url}`);
// follow the redirect to get the full URL
return new Promise((resolve, reject) => {
https_1.default
.get(url, { headers }, (res) => {
if (res.headers.location) {
resolve(res.headers.location);
}
else {
reject('No redirect found');
}
})
.on('error', reject);
});
});
exports.getFullURL = getFullURL;
/**
* Get the video ID from a TikTok video URL (only full URL)
* @param url TikTok video URL
* @returns Video ID
*/
const getVideoId = (url) => {
const regex = /\/video\/(\d*)/;
const match = url.match(regex);
if (match)
return match[1];
throw new Error(`Invalid TikTok video URL: ${url}`);
};
exports.getVideoId = getVideoId;
/**
* Automatically deduce the video ID from a TikTok video URL
* @param url Any TikTok video URL (full or short)
* @returns Promise containing the video ID
*/
const detectVideoId = (url) => __awaiter(void 0, void 0, void 0, function* () {
if (url.match(/(vm|vt)\.tiktok\.com\/(.*)/) || url.match(/(vm|vt|www)\.tiktok\.com\/t\/(.*)/)) {
url = yield (0, exports.getFullURL)(url);
}
return (0, exports.getVideoId)(url);
});
exports.detectVideoId = detectVideoId;
/**
* Fetches a TikTok video data from a video ID or URL
* @param video TikTok video ID or URL
* @returns Promise containing the TikTok video data (see {@link TikTokVideo} interface for more details)
*/
const fetchVideo = (video) => __awaiter(void 0, void 0, void 0, function* () {
video = video.trim();
const video_id = video.match(/^\d*$/) ? video : yield (0, exports.detectVideoId)(video);
const url = `${API_BASE_URL}/aweme/v1/feed/?aweme_id=${video_id}`;
const data = yield get(url);
const json = JSON.parse(data);
const video_data = json.aweme_list[0];
const videoWatermark = {
uri: video_data.video.download_addr.uri,
url: video_data.video.download_addr.url_list[0],
width: video_data.video.download_addr.width,
height: video_data.video.download_addr.height,
dataSize: video_data.video.download_addr.data_size,
download: (progress) => __awaiter(void 0, void 0, void 0, function* () { return yield getBuffer(videoWatermark.url, progress); }),
};
const videoNoWatermark = {
uri: video_data.video.play_addr.uri,
url: video_data.video.play_addr.url_list[0],
width: video_data.video.play_addr.width,
height: video_data.video.play_addr.height,
dataSize: video_data.video.play_addr.data_size,
download: (progress) => __awaiter(void 0, void 0, void 0, function* () { return yield getBuffer(videoNoWatermark.url, progress); }),
};
const music = {
id: video_data.music.id,
name: video_data.music.title,
author: video_data.music.author,
url: video_data.music.play_url.url_list[0],
download: (progress) => __awaiter(void 0, void 0, void 0, function* () { return yield getBuffer(music.url, progress); }),
};
return {
id: video_data.aweme_id,
url: `https://www.tiktok.com/@${video_data.author.nickname}/video/${video_data.aweme_id}`,
description: video_data.desc,
author: video_data.author.nickname,
videoWatermark,
videoNoWatermark,
width: video_data.video.width,
height: video_data.video.height,
likes: video_data.statistics.digg_count,
shares: video_data.statistics.share_count,
playCount: video_data.statistics.play_count,
comments: video_data.statistics.comment_count,
music,
previewImageUrl: video_data.video.origin_cover.url_list[0],
download: (options) => __awaiter(void 0, void 0, void 0, function* () {
options = options || {};
const video = options.watermark ? videoWatermark : videoNoWatermark;
return yield video.download(options.progress);
}),
};
});
exports.fetchVideo = fetchVideo;