UNPKG

@tobyg74/tiktok-api-dl

Version:

Scraper for downloading media in the form of videos, images and audio from Tiktok. Also for stalking Tiktok Users

547 lines (546 loc) 27.4 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const __1 = __importDefault(require("..")); const cookieManager_1 = require("../services/cookieManager"); const logger_1 = require("../lib/logger"); const chalk_1 = __importDefault(require("chalk")); const downloadManager_1 = require("../services/downloadManager"); const api_1 = require("../constants/api"); const TIKTOK_URL_REGEX = { playlist: /https:\/\/(?:www|m)\.tiktok\.com\/@[\w.-]+\/playlist\/[\w-]+-(\d+)/, collection: /https:\/\/(?:www|m)\.tiktok\.com\/@[\w.-]+\/collection\/[\w-]+-(\d+)/, video: /https:\/\/(?:www|m)\.tiktok\.com\/@[\w.-]+\/video\/(\d+)/, photo: /https:\/\/(?:www|m)\.tiktok\.com\/@[\w.-]+\/photo\/(\d+)/, shortLink: /https:\/\/(vm|vt|lite)\.tiktok\.com\/[\w\d]+\/?/, music: /https:\/\/(?:www|m)\.tiktok\.com\/music\/[\w%-]+-(\d+)/ }; const cookieManager = new cookieManager_1.CookieManager(); commander_1.program .name("tiktokdl") .description("TikTok downloader and search CLI tool") .version("1.0.0"); commander_1.program .command("download") .description("Download TikTok Video / Slide / Music / Playlist / Collection") .argument("<urls...>", "TikTok URLs (Video / Slide / Music / Playlist / Collection)") .option("-o, --output <path>", "Output directory path") .option("-v, --version <version>", "Downloader version (v1/v2/v3)", "v1") .option("-p, --proxy <proxy>", "Proxy URL (http/https/socks)") .option("-c, --count <number>", "Number of items to fetch for playlist/collection (default: 20)", (val) => parseInt(val), 20) .action(async (urls, options) => { const outputPath = options.output || (0, downloadManager_1.getDefaultDownloadPath)(); const version = options.version.toLowerCase(); const count = options.count || 20; if (!Array.isArray(urls)) urls = [urls]; for (const url of urls) { try { if (!["v1", "v2", "v3"].includes(version)) { throw new Error("Invalid version. Use v1, v2 or v3"); } if (TIKTOK_URL_REGEX.playlist.test(url)) { logger_1.Logger.info(`Fetching playlist items from: ${url}`); const match = url.match(TIKTOK_URL_REGEX.playlist); const results = await __1.default.Playlist(match[1], { page: 1, proxy: options.proxy, count: count }); if (results.status === "success" && results.result) { const { itemList } = results.result; logger_1.Logger.info(`Found ${itemList.length} items in playlist. Starting download...`); for (const [index, item] of itemList.entries()) { logger_1.Logger.info(`Downloading [${index + 1}/${itemList.length}]: ${item.id}`); const videoUrl = `https://www.tiktok.com/@${item.author?.uniqueId || "unknown"}/video/${item.id}`; try { const data = await __1.default.Downloader(videoUrl, { version: version, proxy: options.proxy }); await (0, downloadManager_1.handleMediaDownload)(data, outputPath, version); logger_1.Logger.success(`Downloaded: ${videoUrl}`); } catch (err) { logger_1.Logger.error(`Failed to download ${videoUrl}: ${err.message}`); } } logger_1.Logger.info("All downloads finished."); } else { logger_1.Logger.error(`Error: ${results.message}`); } } else if (TIKTOK_URL_REGEX.collection.test(url)) { logger_1.Logger.info(`Fetching collection items from: ${url}`); const match = url.match(TIKTOK_URL_REGEX.collection); const results = await __1.default.Collection(match[1], { page: 1, proxy: options.proxy, count: count }); if (results.status === "success" && results.result) { const { itemList } = results.result; logger_1.Logger.info(`Found ${itemList.length} items in collection. Starting download...`); for (const [index, item] of itemList.entries()) { logger_1.Logger.info(`Downloading [${index + 1}/${itemList.length}]: ${item.id}`); const videoUrl = `https://www.tiktok.com/@${item.author?.uniqueId || "unknown"}/video/${item.id}`; try { const data = await __1.default.Downloader(videoUrl, { version: version, proxy: options.proxy }); await (0, downloadManager_1.handleMediaDownload)(data, outputPath, version); logger_1.Logger.success(`Downloaded: ${videoUrl}`); } catch (err) { logger_1.Logger.error(`Failed to download ${videoUrl}: ${err.message}`); } } logger_1.Logger.info("All downloads finished."); } else { logger_1.Logger.error(`Error: ${results.message}`); } } else if (TIKTOK_URL_REGEX.video.test(url) || TIKTOK_URL_REGEX.shortLink.test(url) || TIKTOK_URL_REGEX.photo.test(url)) { logger_1.Logger.info("Fetching media information..."); const data = await __1.default.Downloader(url, { version: version, proxy: options.proxy }); await (0, downloadManager_1.handleMediaDownload)(data, outputPath, version); } else { logger_1.Logger.error("URL tidak valid atau tidak dikenali: " + url); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } } }); const cookieCommand = commander_1.program.command("cookie").description("Cookie Manager"); cookieCommand .command("set <value>") .description("Set a cookie") .action((value) => { cookieManager.setCookie(value); logger_1.Logger.success("Cookie set successfully."); }); cookieCommand .command("get") .description("Get cookie value") .action(() => { const cookie = cookieManager.getCookie(); if (cookie) { logger_1.Logger.info(`Cookie: ${cookie}`); } else { logger_1.Logger.warning("No cookie found."); } }); cookieCommand .command("delete") .description("Delete cookie") .action(() => { cookieManager.deleteCookie(); logger_1.Logger.success("Cookie deleted successfully."); }); const searchCommand = commander_1.program .command("search") .description("Search TikTok users or live streams or videos"); searchCommand .command("user") .description("Search TikTok users") .argument("<keyword>", "Search keyword") .option("-p, --page <number>", "Page number", "1") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .action(async (keyword, options) => { try { const page = parseInt(options.page); const results = await __1.default.Search(keyword, { type: "user", cookie: cookieManager.getCookie(), page: page, proxy: options.proxy }); if (results.status === "success") { const data = results.result; for (const [index, item] of data.entries()) { if (item.type === "user") { logger_1.Logger.info(`---- USER ${index + 1} ----`); logger_1.Logger.result(`Username: ${item.username}`, chalk_1.default.green); logger_1.Logger.result(`Nickname: ${item.nickname}`, chalk_1.default.green); logger_1.Logger.result(`Bio: ${item.signature}`, chalk_1.default.green); logger_1.Logger.result(`Followers: ${item.followerCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Verified: ${item.isVerified ? "Yes" : "No"}`, chalk_1.default.yellow); logger_1.Logger.result(`Profile URL: ${item.url}`, chalk_1.default.yellow); } } logger_1.Logger.info(`Total users: ${data.length}`); } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); searchCommand .command("live") .description("Search TikTok live streams") .argument("<keyword>", "Search keyword") .option("-p, --page <number>", "Page number", "1") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .action(async (keyword, options) => { try { const page = parseInt(options.page); const results = await __1.default.Search(keyword, { type: "live", cookie: cookieManager.getCookie(), page: page, proxy: options.proxy }); if (results.status === "success") { const data = results.result; for (const [index, item] of data.entries()) { if (item.type === "live") { logger_1.Logger.info(`---- LIVE ${index + 1} ----`); logger_1.Logger.result(`Title: ${item.liveInfo.title}`, chalk_1.default.green); logger_1.Logger.result(`Nickname: ${item.liveInfo.owner.nickname}`, chalk_1.default.green); logger_1.Logger.result(`Username: ${item.liveInfo.owner.username}`, chalk_1.default.green); logger_1.Logger.result(`Verified: ${item.liveInfo.owner.isVerified ? "Yes" : "No"}`, chalk_1.default.green); logger_1.Logger.result(`Type Third Party: ${item.liveInfo.liveTypeThirdParty ? "Yes" : "No"}`, chalk_1.default.green); logger_1.Logger.result(`Hashtag: ${item.liveInfo.hashtag}`, chalk_1.default.green); logger_1.Logger.info(`---- STATISTICS ----`); logger_1.Logger.result(`Likes: ${item.liveInfo.stats.likeCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Views: ${item.liveInfo.stats.viewerCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Users: ${item.liveInfo.stats.totalUser}`, chalk_1.default.yellow); } } logger_1.Logger.info(`Total live streams: ${data.length}`); } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); searchCommand .command("video") .description("Search TikTok videos") .argument("<keyword>", "Search keyword") .option("-p, --page <number>", "Page number", "1") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .action(async (keyword, options) => { try { const page = parseInt(options.page); const results = await __1.default.Search(keyword, { type: "video", cookie: cookieManager.getCookie(), page: page, proxy: options.proxy }); if (results.status === "success") { const data = results.result; for (const [index, item] of data.entries()) { if (item.type === "video") { logger_1.Logger.info(`---- VIDEO ${index + 1} ----`); logger_1.Logger.result(`Video ID: ${item.id}`, chalk_1.default.green); logger_1.Logger.result(`Description: ${item.desc}`, chalk_1.default.yellow); logger_1.Logger.result(`Author: ${item.author.nickname}`, chalk_1.default.yellow); logger_1.Logger.result(`Video URL: ${api_1._tiktokurl}/@${item.author.uniqueId}/video/${item.id}`, chalk_1.default.yellow); logger_1.Logger.info(`---- STATISTICS ----`); logger_1.Logger.result(`Likes: ${item.stats.likeCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Favorites: ${item.stats.collectCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Views: ${item.stats.playCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Shares: ${item.stats.shareCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Comments: ${item.stats.commentCount}`, chalk_1.default.yellow); } } logger_1.Logger.info(`Total videos: ${data.length}`); } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); commander_1.program .command("getvideocomments") .description("Get comments from a TikTok video") .argument("<url>", "TikTok video URL") .option("-l, --limit <number>", "Limit of comments", "10") .option("-p, --proxy <proxy>", "Proxy URL (http/https/socks)") .action(async (url, options) => { try { const limit = parseInt(options.limit); const comments = await __1.default.GetVideoComments(url, { commentLimit: limit, proxy: options.proxy }); if (comments.status === "success") { const data = comments.result; for (const [index, comment] of data.entries()) { logger_1.Logger.info(`---- COMMENT ${index + 1} ----`); logger_1.Logger.result(`Username: ${comment.user.username}`, chalk_1.default.green); logger_1.Logger.result(`Text: ${comment.text}`, chalk_1.default.green); logger_1.Logger.result(`Likes: ${comment.likeCount}`, chalk_1.default.yellow); } logger_1.Logger.info(`Total comments: ${data.length}`); } else { logger_1.Logger.error(`Error: ${comments.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); commander_1.program .command("getuserposts") .description("Get posts from a TikTok user") .argument("<username>", "TikTok username") .option("-l, --limit <number>", "Limit of posts", "5") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .action(async (username, options) => { try { const postLimit = parseInt(options.limit); const results = await __1.default.GetUserPosts(username, { postLimit: postLimit, proxy: options.proxy }); if (results.status === "success") { const data = results.result; for (const [index, post] of data.entries()) { logger_1.Logger.info(`---- POST ${index + 1} ----`); logger_1.Logger.result(`Video ID: ${post.id}`, chalk_1.default.green); logger_1.Logger.result(`Description: ${post.desc}`, chalk_1.default.yellow); logger_1.Logger.info(`---- STATISTICS ----`); logger_1.Logger.result(`Likes: ${post.stats.likeCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Favorites: ${post.stats.collectCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Views: ${post.stats.playCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Shares: ${post.stats.shareCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Comments: ${post.stats.commentCount}`, chalk_1.default.yellow); } logger_1.Logger.info(`Total posts: ${data.length}`); } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); commander_1.program .command("getuserliked") .description("Get user liked videos from a TikTok user") .argument("<username>", "TikTok username") .option("-l, --limit <number>", "Limit of posts", "5") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .action(async (username, options) => { try { const postLimit = parseInt(options.limit); const results = await __1.default.GetUserLiked(username, { cookie: cookieManager.getCookie(), postLimit: postLimit, proxy: options.proxy }); if (results.status === "success") { const data = results.result; for (const [index, liked] of data.entries()) { logger_1.Logger.info(`---- FAVORITE ${index + 1} ----`); logger_1.Logger.result(`Video ID: ${liked.id}`, chalk_1.default.green); logger_1.Logger.result(`Description: ${liked.desc}`, chalk_1.default.yellow); logger_1.Logger.result(`Author: ${liked.author.nickname}`, chalk_1.default.yellow); logger_1.Logger.result(`Video URL: ${api_1._tiktokurl}/@${liked.author.username}/video/${liked.video.id}`, chalk_1.default.yellow); logger_1.Logger.info(`---- STATISTICS ----`); logger_1.Logger.result(`Likes: ${liked.stats.diggCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Favorites: ${liked.stats.collectCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Views: ${liked.stats.playCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Shares: ${liked.stats.shareCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Comments: ${liked.stats.commentCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Reposts: ${liked.stats.repostCount}`, chalk_1.default.yellow); } logger_1.Logger.info(`Total Liked Videos: ${data.length}`); } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); commander_1.program .command("stalk") .description("Stalk a TikTok user") .argument("<username>", "TikTok username") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .action(async (username, options) => { try { const results = await __1.default.StalkUser(username, { proxy: options.proxy }); if (results.status === "success") { const data = results.result; logger_1.Logger.info("---- TIKTOK STALKER ----"); logger_1.Logger.result(`Username:${data.user.username}`, chalk_1.default.green); logger_1.Logger.result(`Nickname:${data.user.nickname}`, chalk_1.default.green); logger_1.Logger.result(`Bio:${data.user.signature}`, chalk_1.default.green); logger_1.Logger.result(`Verified:${data.user.verified ? "Yes" : "No"}`, chalk_1.default.green); logger_1.Logger.result(`Commerce User:${data.user.commerceUser ? "Yes" : "No"}`, chalk_1.default.green); logger_1.Logger.result(`Private Account:${data.user.privateAccount ? "Yes" : "No"}`, chalk_1.default.green); logger_1.Logger.result(`Region:${data.user.region}`, chalk_1.default.green); logger_1.Logger.info("---- STATISTICS ----"); logger_1.Logger.result(`Followers:${data.stats.followerCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Following:${data.stats.followingCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Hearts:${data.stats.heartCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Videos:${data.stats.videoCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Likes:${data.stats.likeCount}`, chalk_1.default.yellow); logger_1.Logger.result(`Friends:${data.stats.friendCount}`, chalk_1.default.yellow); } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); commander_1.program .command("collection") .description("Get videos from a TikTok collection (supports collection ID or URL)") .argument("<collectionIdOrUrl>", "Collection ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/collection/name-id)") .option("-p, --page <number>", "Page number", "1") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .option("-n, --count <number>", "Number of items to fetch", (val) => parseInt(val), 5) .action(async (collectionIdOrUrl, options) => { try { logger_1.Logger.info(`Fetching page ${options.page} with ${options.count} items per page from collection...`); const results = await __1.default.Collection(collectionIdOrUrl, { page: options.page, proxy: options.proxy, count: options.count }); if (results.status === "success" && results.result) { const { itemList, hasMore } = results.result; logger_1.Logger.info(`Found ${itemList.length} videos in collection`); logger_1.Logger.info(`Has more videos: ${hasMore}`); for (const [index, video] of itemList.entries()) { logger_1.Logger.info(`---- VIDEO ${index + 1} ----`); logger_1.Logger.result(`Video ID: ${video.id}`, chalk_1.default.green); logger_1.Logger.result(`Description: ${video.desc}`, chalk_1.default.yellow); logger_1.Logger.result(`Author: ${video.author?.nickname || "Unknown"}`, chalk_1.default.yellow); logger_1.Logger.result(`Created: ${new Date(video.createTime * 1000).toLocaleString()}`, chalk_1.default.yellow); if (video.statistics) { logger_1.Logger.info(`---- STATISTICS ----`); logger_1.Logger.result(`Likes: ${video.statistics.likeCount || 0}`, chalk_1.default.yellow); logger_1.Logger.result(`Comments: ${video.statistics.commentCount || 0}`, chalk_1.default.yellow); logger_1.Logger.result(`Shares: ${video.statistics.shareCount || 0}`, chalk_1.default.yellow); logger_1.Logger.result(`Plays: ${video.statistics.playCount || 0}`, chalk_1.default.yellow); } if (video.video) { logger_1.Logger.info(`---- VIDEO URLs ----`); const videoUrl = `${api_1._tiktokurl}/@${video.author?.uniqueId || "unknown"}/video/${video.id}`; logger_1.Logger.result(`Video URL: ${videoUrl}`, chalk_1.default.blue); } if (video.textExtra?.length > 0) { logger_1.Logger.info(`---- HASHTAGS ----`); video.textExtra.forEach((tag) => { if (tag.hashtagName) { logger_1.Logger.result(`#${tag.hashtagName}`, chalk_1.default.cyan); } }); } } if (hasMore) { logger_1.Logger.info("\nTo fetch more videos, use:"); logger_1.Logger.info(`tiktokdl collection ${collectionIdOrUrl} -p ${parseInt(options.page) + 1}`); } } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); commander_1.program .command("playlist") .description("Get videos from a TikTok playlist") .argument("<PlaylistIdOrUrl>", "Collection URL (e.g. https://www.tiktok.com/@username/playlist/name-id)") .option("-p, --page <number>", "Page number", "1") .option("--proxy <proxy>", "Proxy URL (http/https/socks)") .option("-c, --count <number>", "Number of items to fetch (max: 20)", (val) => parseInt(val), 5) .option("-r, --raw", "Show raw response", false) .action(async (url, options) => { try { logger_1.Logger.info(`Fetching page ${options.page} with ${options.count} items per page from playlist...`); const results = await __1.default.Playlist(url, { page: options.page, proxy: options.proxy, count: options.count }); const contentType = (content) => { if (content?.imagePost) { return "photo"; } else { return "video"; } }; if (results.status === "success" && results.result) { if (options.raw) { console.log(JSON.stringify(results.result, null, 2)); return; } const { itemList, hasMore } = results.result; logger_1.Logger.info(`Found ${itemList.length} items in playlist`); logger_1.Logger.info(`Has more items: ${hasMore}`); for (const [index, item] of itemList.entries()) { logger_1.Logger.info(`---- ITEM ${index + 1} ----`); logger_1.Logger.result(`Item ID: ${item.id}`, chalk_1.default.green); logger_1.Logger.result(`Description: ${item.desc}`, chalk_1.default.yellow); logger_1.Logger.result(`Author: ${item.author?.nickname || "Unknown"}`, chalk_1.default.yellow); logger_1.Logger.result(`Created: ${new Date(item.createTime * 1000).toLocaleString()}`, chalk_1.default.yellow); if (item.stats) { logger_1.Logger.info(`---- STATISTICS ----`); logger_1.Logger.result(`Comments: ${item.stats.commentCount || 0}`, chalk_1.default.yellow); logger_1.Logger.result(`Shares: ${item.stats.shareCount || 0}`, chalk_1.default.yellow); logger_1.Logger.result(`Plays: ${item.stats.playCount || 0}`, chalk_1.default.yellow); } if (item.video) { logger_1.Logger.info(`---- VIDEO URLs ----`); const videoUrl = `${api_1._tiktokurl}/@${item.author?.uniqueId || "unknown"}/${contentType(item)}/${item.id}`; logger_1.Logger.result(`Video URL: ${videoUrl}`, chalk_1.default.blue); } } if (hasMore) { logger_1.Logger.info("\nTo fetch more videos, use:"); logger_1.Logger.info(`tiktokdl playlist ${url} -p ${parseInt(options.page) + 1}`); } } else { logger_1.Logger.error(`Error: ${results.message}`); } } catch (error) { logger_1.Logger.error(`Error: ${error.message}`); } }); commander_1.program.parse();