diff --git a/.gitignore b/.gitignore index 65872b4..89e8678 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ pnpm-lock.yaml package-lock.json yarn.lock lib +!src/lib test.js bun.lockb tsconfig.tsbuildinfo diff --git a/CHANGELOG.md b/CHANGELOG.md index ffd8e2c..7e9b466 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -371,3 +371,27 @@ ### Changed - Update Documentation [4260ea6](https://github.com/TobyG74/tiktok-api-dl/commit/4260ea653ee50569f898cc0653cb35e4557992a9) + +### Version 1.3.2 - 03-06-2025 + +### Added + +- Add Tiktok Collection [5bd743a](https://github.com/TobyG74/tiktok-api-dl/pull/42/commits/5bd743a888cfafe932f083a2f887dbdd98e99d0c) +- Add Tiktok Collection Documentation [a37640e](https://github.com/TobyG74/tiktok-api-dl/pull/42/commits/a37640e332a43827bca8599881c931097d07256e) +- Add Tiktok Collection Types [baa8fa2](https://github.com/TobyG74/tiktok-api-dl/pull/42/commits/baa8fa2cc8d1bcc7aabc7fdef5a93677fed10be5) +- Add Tiktok Playlist [2fa9e6f](https://github.com/TobyG74/tiktok-api-dl/pull/45/commits/2fa9e6fef414c2825cf3072655998e1322210062) +- Add Tiktok Playlist Documentation [d0b1f2c](https://github.com/TobyG74/tiktok-api-dl/commit/db1686fc9b0480402db7bd6bd0c41f899ff56668) +- Add a New CLI Downloader for Tiktok Collection & Playlist [ae53775](https://github.com/TobyG74/tiktok-api-dl/commit/ae537757d8fa274e52060f445d4822c617f9ac93) +- Add Some Test Files [4f7cd9f](https://github.com/TobyG74/tiktok-api-dl/commit/4f7cd9f083d1798c18a0cad823b2e9b224893f14) + +### Fixed + +- Repair Musicaldown Broken Logics [732c0d4](https://github.com/TobyG74/tiktok-api-dl/pull/45/commits/732c0d4c146d7ed743b5902fbd392717e2df5692) +- Fix Downloader Types [1ad6d8b](https://github.com/TobyG74/tiktok-api-dl/pull/45/commits/1ad6d8baea7c2ac45ffc6cccd089ce193d17f491) +- Fix Handle Downloader Function [a56bc8c](https://github.com/TobyG74/tiktok-api-dl/pull/45/commits/a56bc8c9d9bedd42305c92a7bb15edb3ecad390a) +- Fix Duplicate Types [9d53637](https://github.com/TobyG74/tiktok-api-dl/pull/45/commits/9d53637ac267568fc0f2d3c4556dd2367b4cbf19) + +### Changed + +- Moving the Function to the Function Owner's File [c83e329](https://github.com/TobyG74/tiktok-api-dl/pull/45/commits/c83e329f27bd744dd2274604e15e10ec2264e083) +- Removing Cookie Options on Tiktok Stalk User [c7af8d5](https://github.com/TobyG74/tiktok-api-dl/pull/45/commits/c7af8d53a99fbbf1bda453a9ee1293bfb7ac6cf4) diff --git a/README.md b/README.md index 98b98e1..047cded 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,8 @@ - [Tiktok Video User Comments](#tiktok-video-comments) - [Tiktok Get User Posts](#tiktok-get-user-posts) - [Tiktok Get User Favorite Videos](#tiktok-get-user-favorite-videos) + - [Tiktok Collection](#tiktok-collection) + - [Tiktok Playlist](#tiktok-playlist) - [API Response Types](#api-response-types) - [Tiktok Downloader](#tiktok-downloader-1) - [Version 1 Response](#version-1-response) @@ -182,6 +184,22 @@ Tiktok.Downloader(url, { }).then((result) => console.log(result)) ``` +### CLI Usage + +```bash +# Download Tiktok Video +tiktokdl download "https://vt.tiktok.com/xxxxxxxx" + +# Download Tiktok Video with version +tiktokdl download "https://vt.tiktok.com/xxxxxxxx" -v v1 + +# Download Tiktok Video with Custom Output Directory Path +tiktokdl download "https://vt.tiktok.com/xxxxxxxx" -v v1 -o "/path/to/save/video.mp4" + +# Download Tiktok Video with Proxy +tiktokdl download "https://vt.tiktok.com/xxxxxxxx" -v v1 -proxy "http://your-proxy-url" +``` + - [Version 1 Response](#version-1-response) - [Version 2 Response](#version-2-response) - [Version 3 Response](#version-3-response) @@ -192,15 +210,47 @@ Tiktok.Downloader(url, { const Tiktok = require("@tobyg74/tiktok-api-dl") Tiktok.Search("username", { - type: "user", // "user" | "live" + type: "user", // "user" | "live" | "video" page: 1, cookie: "YOUR_COOKIE", // needed proxy: "YOUR_PROXY" // optional }).then((result) => console.log(result)) ``` +### CLI Usage + +```bash +# Search Tiktok Users +tiktokdl search user + +# Search Tiktok Users with pagination +tiktokdl search user -p 1 + +# Search Tiktok Users with proxy +tiktokdl search user -p 1 -proxy "http://your-proxy-url" + +# Search Tiktok Live Streams +tiktokdl search live + +# Search Tiktok Live Streams with pagination +tiktokdl search live -p 1 + +# Search Tiktok Live Streams with proxy +tiktokdl search live -p 1 -proxy "http://your-proxy-url" + +# Search Tiktok Videos +tiktokdl search video + +# Search Tiktok Videos with pagination +tiktokdl search video -p 1 + +# Search Tiktok Videos with proxy +tiktokdl search video -p 1 -proxy "http://your-proxy-url" +``` + - [User Search Response](#user-search-response) - [Live Search Response](live-search-response) +- [Video Search Response](#video-search-response) ## Tiktok Stalk User Profile @@ -208,12 +258,21 @@ Tiktok.Search("username", { const Tiktok = require("@tobyg74/tiktok-api-dl") const username = "Tobz2k19" -Tiktok.Stalker(username, { - cookie: "YOUR_COOKIE", // optional, if response null +Tiktok.StalkUser(username, { proxy: "YOUR_PROXY" // optional }).then((result) => console.log(result)) ``` +### CLI Usage + +```bash +# Stalk User Profile +tiktokdl stalk + +# Stalk User Profile with proxy +tiktokdl stalk -proxy "http://your-proxy-url" +``` + - [Tiktok Stalk User Response](#tiktok-stalk-user-profile-1) ## Tiktok Video Comments @@ -228,6 +287,19 @@ Tiktok.GetVideoComments(url, { }).then((result) => console.log(result)) ``` +### CLI Usage + +```bash +# Get Video Comments +tiktokdl getvideocomments "https://vt.tiktok.com/xxxxxxxx" + +# Get Video Comments with limit of comments +tiktokdl getvideocomments "https://vt.tiktok.com/xxxxxxxx" -l 10 + +# Get Video Comments with proxy +tiktokdl getvideocomments "https://vt.tiktok.com/xxxxxxxx" -l 10 -proxy "http://your-proxy-url" +``` + - [Tiktok Video Comments Response](#tiktok-video-comments-1) ## Tiktok Get User Posts @@ -242,6 +314,19 @@ Tiktok.GetUserPosts(username, { }).then((result) => console.log(result)) ``` +### CLI Usage + +```bash +# Get User Posts +tiktokdl getuserposts + +# Get User Posts with limit of posts +tiktokdl getuserposts -l 10 + +# Get User Posts with proxy +tiktokdl getuserposts -l 10 -proxy "http://your-proxy-url" +``` + - [Tiktok User Posts Response](#tiktok-user-posts) ## Tiktok Get User Liked Videos @@ -259,6 +344,19 @@ Tiktok.GetUserLiked(username, { }) ``` +### CLI Usage + +```bash +# Get User Liked Videos +tiktokdl getuserliked + +# Get User Liked Videos with limit of posts +tiktokdl getuserliked -l 10 + +# Get User Liked Videos with proxy +tiktokdl getuserliked -l 10 -proxy "http://your-proxy-url" +``` + - [Tiktok User Liked Videos Response](#tiktok-user-liked-videos) ## Tiktok Collection @@ -301,59 +399,41 @@ tiktokdl collection 7507916135931218695 -p 1 -n 5 tiktokdl collection 7507916135931218695 -n 5 -proxy "http://your-proxy-url" ``` -### Response Type +- [Tiktok Collection Response](#tiktok-collection-1) -```typescript -interface TiktokCollectionResponse { - status: "success" | "error" - message?: string - result?: { - itemList: Array<{ - id: string - desc: string - createTime: number - author?: { - id: string - uniqueId: string - nickname: string - avatarThumb: string - avatarMedium: string - avatarLarger: string - signature: string - verified: boolean - } - statistics?: { - playCount: number - diggCount: number - shareCount: number - commentCount: number - collectCount: number - } - video?: { - id: string - height: number - width: number - duration: number - ratio: string - cover: string - originCover: string - dynamicCover: string - playAddr: string - downloadAddr: string - format: string - bitrate: number - } - textExtra?: Array<{ - hashtagName: string - hashtagId: string - type: number - }> - }> - hasMore: boolean - } -} +## Tiktok Playlist + +Get videos from a TikTok playlist (supports playlist ID or URL) + +```javascript +const Tiktok = require("@tobyg74/tiktok-api-dl") + +const playlistIdOrUrl = "https://www.tiktok.com/@username/playlist/name-id" +Tiktok.Playlist(playlistIdOrUrl, { + page: 1, + count: 5, + proxy: "YOUR_PROXY" +}).then((result) => console.log(result)) ``` +### CLI Usage + +```bash +# Using playlist ID +tiktokdl playlist 7507916135931218695 -n 5 + +# Using playlist URL +tiktokdl playlist "https://www.tiktok.com/@username/playlist/name-id" -n 5 + +# With page for pagination +tiktokdl playlist 7507916135931218695 -p 1 -n 5 + +# With proxy +tiktokdl playlist 7507916135931218695 -n 5 -proxy "http://your-proxy-url" +``` + +- [Tiktok Playlist Response](#tiktok-playlist-1) + # API Response Types ## Tiktok Downloader @@ -446,8 +526,12 @@ interface SSSTikResponse { shareCount: string } images?: string[] - video?: string - music?: string + video?: { + playAddr: string + } + music?: { + playUrl: string + } direct?: string } } @@ -862,6 +946,61 @@ interface TiktokCollectionResponse { } ``` +## Tiktok Playlist + +### Playlist Response + +```typescript +status: "success" | "error" +message?: string +result?: { + hasMore: boolean + itemList: Array<{ + id: string + desc: string + createTime: number + author: PlaylistAuthor + stats: Statistics + video: VideoTiktokAPI + music: MusicTiktokAPI + challenges: Array<{ + id: string + title: string + desc: string + coverLarger: string + coverMedium: string + coverThumb: string + profileLarger: string + profileMedium: string + profileThumb: string + }> + collected: boolean + digged: boolean + duetDisplay: number + forFriend: boolean + officalItem: boolean + originalItem: boolean + privateItem: boolean + shareEnabled: boolean + stitchDisplay: number + textExtra: Array<{ + awemeId: string + end: number + hashtagName: string + isCommerce: boolean + start: number + subType: number + type: number + }> + }> + extra?: { + fatal_item_ids: string[] + logid: string + now: number + } +} +``` + # Changelog - All changes will be documented in the [CHANGELOG.md](https://github.com/TobyG74/tiktok-api-dl/blob/master/CHANGELOG.md) file. diff --git a/package.json b/package.json index 85fb7b7..27ada11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tobyg74/tiktok-api-dl", - "version": "1.3.1-fix", + "version": "1.3.2", "description": "Scraper for downloading media in the form of videos, images and audio from Tiktok. Also for stalking Tiktok Users", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -27,10 +27,6 @@ "tiktok-stalk" ], "author": "Tobz", - "contributors": [ - "aqulzz", - "nugraizy" - ], "license": "ISC", "bugs": { "url": "https://github.com/TobyG74/tiktok-api-dl/issues" diff --git a/src/cli/index.ts b/src/cli/index.ts index 7528155..84bfbee 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -10,9 +10,6 @@ import { handleMediaDownload } from "../services/downloadManager" import { _tiktokurl } from "../constants/api" -import path from "path" -import * as fs from "fs" -import axios from "axios" const cookieManager = new CookieManager() @@ -86,7 +83,7 @@ cookieCommand const searchCommand = program .command("search") - .description("Search TikTok users or live streams") + .description("Search TikTok users or live streams or videos") searchCommand .command("user") @@ -503,7 +500,7 @@ program .command("playlist") .description("Get videos from a TikTok playlist") .argument( - "", + "", "Collection URL (e.g. https://www.tiktok.com/@username/playlist/name-id)" ) .option("-p, --page ", "Page number", "1") @@ -590,4 +587,122 @@ program } }) +// Download all items in a TikTok playlist +program + .command("download-playlist") + .description("Download all videos from a TikTok playlist") + .argument( + "", + "Playlist URL (e.g. https://www.tiktok.com/@username/playlist/name-id)" + ) + .option("-o, --output ", "Output directory path") + .option("-v, --version ", "Downloader version (v1/v2/v3)", "v1") + .option("-p, --proxy ", "Proxy URL (http/https/socks)") + .option( + "-c, --count ", + "Number of items to fetch (max: 20)", + (val) => parseInt(val), + 20 + ) + .action(async (url, options) => { + try { + const outputPath = options.output || getDefaultDownloadPath() + const version = options.version.toLowerCase() + Logger.info(`Fetching playlist items...`) + const results = await Tiktok.Playlist(url, { + page: 1, + proxy: options.proxy, + count: options.count + }) + if (results.status === "success" && results.result) { + const { itemList } = results.result + Logger.info( + `Found ${itemList.length} items in playlist. Starting download...` + ) + for (const [index, item] of itemList.entries()) { + 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 Tiktok.Downloader(videoUrl, { + version: version, + proxy: options.proxy + }) + await handleMediaDownload(data, outputPath, version) + Logger.success(`Downloaded: ${videoUrl}`) + } catch (err) { + Logger.error(`Failed to download ${videoUrl}: ${err.message}`) + } + } + Logger.info("All downloads finished.") + } else { + Logger.error(`Error: ${results.message}`) + } + } catch (error) { + Logger.error(`Error: ${error.message}`) + } + }) + +// Download all items in a TikTok collection +program + .command("download-collection") + .description("Download all videos from a TikTok collection") + .argument( + "", + "Collection ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/collection/name-id)" + ) + .option("-o, --output ", "Output directory path") + .option("-v, --version ", "Downloader version (v1/v2/v3)", "v1") + .option("-p, --proxy ", "Proxy URL (http/https/socks)") + .option( + "-n, --count ", + "Number of items to fetch", + (val) => parseInt(val), + 20 + ) + .action(async (collectionIdOrUrl, options) => { + try { + const outputPath = options.output || getDefaultDownloadPath() + const version = options.version.toLowerCase() + Logger.info(`Fetching collection items...`) + const results = await Tiktok.Collection(collectionIdOrUrl, { + page: 1, + proxy: options.proxy, + count: options.count + }) + if (results.status === "success" && results.result) { + const { itemList } = results.result + Logger.info( + `Found ${itemList.length} items in collection. Starting download...` + ) + for (const [index, item] of itemList.entries()) { + 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 Tiktok.Downloader(videoUrl, { + version: version, + proxy: options.proxy + }) + await handleMediaDownload(data, outputPath, version) + Logger.success(`Downloaded: ${videoUrl}`) + } catch (err) { + Logger.error(`Failed to download ${videoUrl}: ${err.message}`) + } + } + Logger.info("All downloads finished.") + } else { + Logger.error(`Error: ${results.message}`) + } + } catch (error) { + Logger.error(`Error: ${error.message}`) + } + }) + program.parse() diff --git a/src/index.ts b/src/index.ts index aa8c1c1..a28250c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ /** Types */ -import { TiktokAPIResponse } from "./types/downloader/tiktokApi" -import { SSSTikResponse } from "./types/downloader/ssstik" -import { MusicalDownResponse } from "./types/downloader/musicaldown" +import { TiktokAPIResponse } from "./types/downloader/tiktokApiDownloader" +import { SSSTikResponse } from "./types/downloader/ssstikDownloader" +import { MusicalDownResponse } from "./types/downloader/musicaldownDownloader" import { UserSearchResult } from "./types/search/userSearch" import { LiveSearchResult } from "./types/search/liveSearch" import { VideoSearchResult } from "./types/search/videoSearch" @@ -12,9 +12,9 @@ import { TiktokUserFavoriteVideosResponse } from "./types/get/getUserLiked" import { TiktokCollectionResponse } from "./types/get/getCollection" /** Services */ -import { extractPlaylistId, TiktokAPI } from "./utils/downloader/tiktokApi" -import { SSSTik } from "./utils/downloader/ssstik" -import { MusicalDown } from "./utils/downloader/musicalDown" +import { TiktokAPI } from "./utils/downloader/tiktokAPIDownloader" +import { SSSTik } from "./utils/downloader/ssstikDownloader" +import { MusicalDown } from "./utils/downloader/musicaldownDownloader" import { StalkUser } from "./utils/get/getProfile" import { SearchUser } from "./utils/search/userSearch" import { SearchLive } from "./utils/search/liveSearch" @@ -23,7 +23,6 @@ import { getUserPosts } from "./utils/get/getUserPosts" import { getUserLiked } from "./utils/get/getUserLiked" import { SearchVideo } from "./utils/search/videoSearch" import { getCollection } from "./utils/get/getCollection" -import { extractCollectionId } from "./utils/downloader/tiktokApi" /** Constants */ import { DOWNLOADER_VERSIONS, SEARCH_TYPES } from "./constants" @@ -31,6 +30,8 @@ import { ERROR_MESSAGES } from "./constants" import { validateCookie } from "./utils/validator" import { TiktokPlaylistResponse } from "./types/get/getPlaylist" import { getPlaylist } from "./utils/get/getPlaylist" +import { extractPlaylistId } from "./utils/get/getPlaylist" +import { extractCollectionId } from "./utils/get/getCollection" /** Types */ type DownloaderVersion = "v1" | "v2" | "v3" @@ -82,7 +83,7 @@ export = { Downloader: async ( url: string, options?: { - version: DownloaderVersion + version: T proxy?: string showOriginalResponse?: boolean } @@ -118,38 +119,6 @@ export = { } }, - /** - * Get TikTok Collection - * @param {string} collectionIdOrUrl - Collection ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/collection/name-id) - * @param {Object} options - The options for collection - * @param {string} [options.proxy] - Optional proxy URL - * @param {string} [options.page] - Optional page for pagination - * @param {number} [options.count] - Optional number of items to fetch - * @returns {Promise} - */ - Collection: async ( - collectionIdOrUrl: string, - options?: { - proxy?: string - page?: number - count?: number - } - ): Promise => { - const collectionId = extractCollectionId(collectionIdOrUrl) - if (!collectionId) { - return { - status: "error", - message: "Invalid collection ID or URL format" - } - } - return await getCollection( - collectionId, - options?.proxy, - options?.page, - options?.count - ) - }, - /** * Tiktok Search * @param {string} keyword - The query you want to search @@ -164,7 +133,7 @@ export = { keyword: string, options?: { type?: T - cookie?: string + cookie: string | any[] page?: number proxy?: string } @@ -240,11 +209,10 @@ export = { StalkUser: async ( username: string, options?: { - cookie?: string | any[] proxy?: string } ): Promise => { - return await StalkUser(username, options?.cookie, options?.proxy) + return await StalkUser(username, options?.proxy) }, /** @@ -314,9 +282,41 @@ export = { ) }, + /** + * Get TikTok Collection + * @param {string} collectionIdOrUrl - Collection ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/collection/name-id) + * @param {Object} options - The options for collection + * @param {string} [options.proxy] - Optional proxy URL + * @param {string} [options.page] - Optional page for pagination + * @param {number} [options.count] - Optional number of items to fetch + * @returns {Promise} + */ + Collection: async ( + collectionIdOrUrl: string, + options?: { + proxy?: string + page?: number + count?: number + } + ): Promise => { + const collectionId = extractCollectionId(collectionIdOrUrl) + if (!collectionId) { + return { + status: "error", + message: "Invalid collection ID or URL format" + } + } + return await getCollection( + collectionId, + options?.proxy, + options?.page, + options?.count + ) + }, + /** * Get TikTok Playlist - * @param {string} url - URL (e.g. https://www.tiktok.com/@username/playlist/name-id) + * @param {string} playlistIdOrUrl - Playlist ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/playlist/name-id) * @param {Object} options - The options for playlist * @param {string} [options.proxy] - Optional proxy URL * @param {string} [options.page] - Optional page for pagination @@ -324,14 +324,14 @@ export = { * @returns {Promise} */ Playlist: async ( - url: string, + playlistIdOrUrl: string, options?: { proxy?: string page?: number count?: number } ): Promise => { - const playlistId = extractPlaylistId(url) + const playlistId = extractPlaylistId(playlistIdOrUrl) if (!playlistId) { return { status: "error", diff --git a/src/lib/logger.ts b/src/lib/logger.ts index eeb392c..cf720be 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -2,19 +2,19 @@ import chalk from "chalk" export class Logger { static success(message: string): void { - console.log(chalk.green("✓ " + message)) + console.log(chalk.green("✓ " + message)) } static error(message: string): void { - console.error(chalk.red("✗ " + message)) + console.error(chalk.red("✗ " + message)) } static info(message: string): void { - console.log(chalk.blue("ℹ " + message)) + console.log(chalk.blue("ℹ " + message)) } static warning(message: string): void { - console.log(chalk.yellow("⚠ " + message)) + console.log(chalk.yellow("⚠ " + message)) } static result(message: string, color = chalk.cyan): void { diff --git a/src/services/downloadManager.ts b/src/services/downloadManager.ts index 6393c7a..eef4bfd 100644 --- a/src/services/downloadManager.ts +++ b/src/services/downloadManager.ts @@ -61,7 +61,7 @@ async function handleMediaDownload( case "video": { const videoUrl = version === "v1" - ? result.video.downloadAddr[0] + ? result.video.playAddr[0] : version === "v2" ? result.video.playAddr[0] : result.videoHD diff --git a/src/services/tiktokService.ts b/src/services/tiktokService.ts index 8943a16..cd50a4b 100644 --- a/src/services/tiktokService.ts +++ b/src/services/tiktokService.ts @@ -5,6 +5,7 @@ import { userAgent, webUserAgent } from "../constants/headers" import qs from "qs" import fs from "fs" import { createCipheriv } from "crypto" +import path from "path" export class TiktokService { /** @@ -82,6 +83,7 @@ export class TiktokService { const baseUrl = `${TiktokService.BASE_URL}api/search/user/full/?` const queryParams = _userSearchParams(username, page) const xbogusParams = xbogus(`${baseUrl}${queryParams}`, userAgent) + console.log(`${baseUrl}${_userSearchParams(username, page, xbogusParams)}`) return `${baseUrl}${_userSearchParams(username, page, xbogusParams)}` } @@ -102,11 +104,18 @@ export class TiktokService { } } + private static readonly FILE_PATH = path.join(__dirname, "../../helper") private static readonly BASE_URL = "https://www.tiktok.com/" private static readonly AES_KEY = "webapp1.0+202106" private static readonly AES_IV = "webapp1.0+202106" - private signaturejs = fs.readFileSync("./helper/signature.js", "utf-8") - private webmssdk = fs.readFileSync("./helper/webmssdk.js", "utf-8") + private signaturejs = fs.readFileSync( + path.join(TiktokService.FILE_PATH, "signature.js"), + "utf-8" + ) + private webmssdk = fs.readFileSync( + path.join(TiktokService.FILE_PATH, "webmssdk.js"), + "utf-8" + ) private resourceLoader = new ResourceLoader({ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35" diff --git a/src/types/downloader/musicaldown.ts b/src/types/downloader/musicaldown.ts deleted file mode 100644 index a7ab306..0000000 --- a/src/types/downloader/musicaldown.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BaseResponse, Content } from "../common" - -export type GetMusicalDownReuqest = BaseResponse & { - request?: { - [key: string]: string - } - cookie?: string -} - -export type MusicalDownResponse = BaseResponse & { - result?: Content -} - -export type GetMusicalDownMusic = BaseResponse & { - result?: string -} diff --git a/src/types/downloader/musicaldownDownloader.ts b/src/types/downloader/musicaldownDownloader.ts new file mode 100644 index 0000000..32b0e56 --- /dev/null +++ b/src/types/downloader/musicaldownDownloader.ts @@ -0,0 +1,32 @@ +import { BaseResponse } from "../common" + +export type GetMusicalDownReuqest = BaseResponse & { + request?: { + [key: string]: string + } + cookie?: string +} + +export type MusicalDownResponse = BaseResponse & { + result?: ContentMusicalDown +} + +export type ContentMusicalDown = { + type: "video" | "image" + author?: AuthorMusicalDown + desc?: string + images?: string[] + videoHD?: string + videoSD?: string + videoWatermark?: string + music?: string +} + +export type AuthorMusicalDown = { + avatar: string + nickname: string +} + +export type GetMusicalDownMusic = BaseResponse & { + result?: string +} diff --git a/src/types/downloader/ssstik.ts b/src/types/downloader/ssstik.ts deleted file mode 100644 index 7ed4413..0000000 --- a/src/types/downloader/ssstik.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { BaseResponse, Content, Author, Statistics } from "../common" - -export type SSSTikFetchTT = BaseResponse & { - result?: string -} - -export type SSSTikResponse = BaseResponse & { - result?: Content -} - -export type AuthorSSSTik = Author - -export type StatisticsSSSTik = Statistics diff --git a/src/types/downloader/ssstikDownloader.ts b/src/types/downloader/ssstikDownloader.ts new file mode 100644 index 0000000..9b6015a --- /dev/null +++ b/src/types/downloader/ssstikDownloader.ts @@ -0,0 +1,35 @@ +import { BaseResponse } from "../common" + +export type SSSTikFetchTT = BaseResponse & { + result?: string +} + +export type SSSTikResponse = BaseResponse & { + result?: ResultContentSSSTik +} + +export type ResultContentSSSTik = { + type: "video" | "music" | "image" + desc?: string + author?: AuthorSSSTik + statistics?: StatisticsSSSTik + video?: { + playAddr: string[] + } + music?: { + playUrl: string[] + } + images?: string[] + direct?: string +} + +export type AuthorSSSTik = { + avatar: string + nickname: string +} + +export type StatisticsSSSTik = { + likeCount: string + commentCount: string + shareCount: string +} diff --git a/src/types/downloader/tiktokApi.ts b/src/types/downloader/tiktokApiDownloader.ts similarity index 100% rename from src/types/downloader/tiktokApi.ts rename to src/types/downloader/tiktokApiDownloader.ts diff --git a/src/types/get/getCollection.ts b/src/types/get/getCollection.ts index 56fa278..602abe0 100644 --- a/src/types/get/getCollection.ts +++ b/src/types/get/getCollection.ts @@ -2,7 +2,7 @@ import { StatisticsTiktokAPI, MusicTiktokAPI, VideoTiktokAPI -} from "../downloader/tiktokApi" +} from "../downloader/tiktokApiDownloader" import { PlaylistAuthor } from "./getPlaylist" export interface CollectionItem { diff --git a/src/types/get/getPlaylist.ts b/src/types/get/getPlaylist.ts index dae94ab..352e462 100644 --- a/src/types/get/getPlaylist.ts +++ b/src/types/get/getPlaylist.ts @@ -2,7 +2,7 @@ import { AuthorTiktokAPI, MusicTiktokAPI, VideoTiktokAPI -} from "../downloader/tiktokApi" +} from "../downloader/tiktokApiDownloader" export interface PlaylistAuthor extends Omit { diff --git a/src/utils/downloader/musicalDown.ts b/src/utils/downloader/musicaldownDownloader.ts similarity index 99% rename from src/utils/downloader/musicalDown.ts rename to src/utils/downloader/musicaldownDownloader.ts index 6f6fb42..3a1fb69 100644 --- a/src/utils/downloader/musicalDown.ts +++ b/src/utils/downloader/musicaldownDownloader.ts @@ -3,7 +3,7 @@ import { load } from "cheerio" import { MusicalDownResponse, GetMusicalDownReuqest -} from "../../types/downloader/musicaldown" +} from "../../types/downloader/musicaldownDownloader" import { _musicaldownapi, _musicaldownurl } from "../../constants/api" import { HttpsProxyAgent } from "https-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent" diff --git a/src/utils/downloader/ssstik.ts b/src/utils/downloader/ssstikDownloader.ts similarity index 99% rename from src/utils/downloader/ssstik.ts rename to src/utils/downloader/ssstikDownloader.ts index a765405..09f399b 100644 --- a/src/utils/downloader/ssstik.ts +++ b/src/utils/downloader/ssstikDownloader.ts @@ -7,7 +7,7 @@ import { StatisticsSSSTik, SSSTikFetchTT, SSSTikResponse -} from "../../types/downloader/ssstik" +} from "../../types/downloader/ssstikDownloader" import { _ssstikapi, _ssstikurl } from "../../constants/api" import { HttpsProxyAgent } from "https-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent" diff --git a/src/utils/downloader/tiktokApi.ts b/src/utils/downloader/tiktokAPIDownloader.ts similarity index 66% rename from src/utils/downloader/tiktokApi.ts rename to src/utils/downloader/tiktokAPIDownloader.ts index 4a9f90b..0102b43 100644 --- a/src/utils/downloader/tiktokApi.ts +++ b/src/utils/downloader/tiktokAPIDownloader.ts @@ -17,21 +17,17 @@ import { StatisticsTiktokAPI, MusicTiktokAPI, ResponseParserTiktokAPI, - VideoTiktokAPI, - TiktokCollectionResponse -} from "../../types/downloader/tiktokApi" + VideoTiktokAPI +} from "../../types/downloader/tiktokApiDownloader" import { HttpsProxyAgent } from "https-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent" import { ERROR_MESSAGES } from "../../constants" -import { TiktokPlaylistResponse } from "../../types/get/getPlaylist" /** Constants */ const TIKTOK_URL_REGEX = /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ const USER_AGENT = "com.zhiliaoapp.musically/300904 (2018111632; U; Android 10; en_US; Pixel 4; Build/QQ3A.200805.001; Cronet/58.0.2991.0)" -const COLLECTION_URL_REGEX = /collection\/[^/]+-(\d+)/ -const PLAYLIST_URL_REGEX = /playlist\/[^/]+-(\d+)/ /** Types */ interface ProxyConfig { @@ -208,7 +204,10 @@ const createVideoResponse = ( } }) -const handleRedirect = async (url: string, proxy?: string): Promise => { +export const handleRedirect = async ( + url: string, + proxy?: string +): Promise => { try { const response = await Axios(url, { method: "HEAD", @@ -228,22 +227,6 @@ const handleRedirect = async (url: string, proxy?: string): Promise => { } } -export const extractCollectionId = (input: string): string | null => { - // If it's already just a number, return it - if (/^\d+$/.test(input)) { - return input - } - - // Try to extract from URL - const match = input.match(COLLECTION_URL_REGEX) - return match ? match[1] : null -} - -export const extractPlaylistId = (url: string): string | null => { - const match = url.match(PLAYLIST_URL_REGEX) - return match ? match[1] : null -} - /** * Tiktok API Downloader * @param {string} url - Tiktok URL @@ -314,133 +297,3 @@ export const TiktokAPI = async ( } } } - -export const Collection = async ( - collectionIdOrUrl: string, - options?: { - page?: number - proxy?: string - count?: number - } -): Promise => { - try { - // Only handle redirects if the input is a URL - const processedUrl = collectionIdOrUrl.startsWith("http") - ? await handleRedirect(collectionIdOrUrl, options?.proxy) - : collectionIdOrUrl - - const collectionId = extractCollectionId(processedUrl) - if (!collectionId) { - return { - status: "error", - message: "Invalid collection ID or URL format" - } - } - - const response = await Axios( - _tiktokGetCollection( - _getCollectionParams(collectionId, options.page, options.count) - ), - { - method: "GET", - headers: { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", - Accept: "*/*", - "Accept-Language": "en-US,en;q=0.7", - Referer: "https://www.tiktok.com/", - Origin: "https://www.tiktok.com" - }, - ...createProxyAgent(options?.proxy) - } - ) - - if (response.data && response.data.status_code === 0) { - const data = response.data - - return { - status: "success", - result: { - itemList: data.itemList || [], - hasMore: data.hasMore - } - } - } - - return { - status: "error", - message: ERROR_MESSAGES.NETWORK_ERROR - } - } catch (error) { - return { - status: "error", - message: - error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR - } - } -} - -export const Playlist = async ( - url: string, - options?: { - page?: number - proxy?: string - count?: number - } -): Promise => { - try { - const processedUrl = url.startsWith("http") - ? await handleRedirect(url, options?.proxy) - : url - - const playlistId = extractPlaylistId(processedUrl) - if (!playlistId) { - return { - status: "error", - message: "Invalid playlist ID or URL format" - } - } - - const response = await Axios( - _tiktokGetPlaylist( - _getPlaylistParams(playlistId, options.page, options.count) - ), - { - method: "GET", - headers: { - "User-Agent": - "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0", - Accept: "*/*", - "Accept-Language": "en-US,en;q=0.7", - Referer: "https://www.tiktok.com/", - Origin: "https://www.tiktok.com" - }, - ...createProxyAgent(options?.proxy) - } - ) - - if (response.data && response.data.status_code === 0) { - const data = response.data - - return { - status: "success", - result: { - itemList: data.itemList || [], - hasMore: data.hasMore, - extra: data.extra - } - } - } - - return { - status: "error", - message: ERROR_MESSAGES.NETWORK_ERROR - } - } catch (error) { - return { - status: "error", - message: - error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR - } - } -} diff --git a/src/utils/get/getCollection.ts b/src/utils/get/getCollection.ts index 6fe7858..8479372 100644 --- a/src/utils/get/getCollection.ts +++ b/src/utils/get/getCollection.ts @@ -6,6 +6,10 @@ import { SocksProxyAgent } from "socks-proxy-agent" import { TiktokCollectionResponse } from "../../types/get/getCollection" import { ERROR_MESSAGES } from "../../constants" import retry from "async-retry" +import { handleRedirect } from "../downloader/tiktokAPIDownloader" + +/** Constants */ +const COLLECTION_URL_REGEX = /collection\/[^/]+-(\d+)/ /** Types */ interface ProxyConfig { @@ -44,9 +48,7 @@ export const getCollection = async ( const response = await retry( async () => { const res = await Axios( - _tiktokGetCollection( - _getCollectionParams(collectionId, page, count) - ), + _tiktokGetCollection(_getCollectionParams(collectionId, page, count)), { method: "GET", headers: { @@ -90,3 +92,79 @@ export const getCollection = async ( } } } + +export const Collection = async ( + collectionIdOrUrl: string, + options?: { + page?: number + proxy?: string + count?: number + } +): Promise => { + try { + // Only handle redirects if the input is a URL + const processedUrl = collectionIdOrUrl.startsWith("http") + ? await handleRedirect(collectionIdOrUrl, options?.proxy) + : collectionIdOrUrl + + const collectionId = extractCollectionId(processedUrl) + if (!collectionId) { + return { + status: "error", + message: "Invalid collection ID or URL format" + } + } + + const response = await Axios( + _tiktokGetCollection( + _getCollectionParams(collectionId, options.page, options.count) + ), + { + method: "GET", + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36", + Accept: "*/*", + "Accept-Language": "en-US,en;q=0.7", + Referer: "https://www.tiktok.com/", + Origin: "https://www.tiktok.com" + }, + ...createProxyAgent(options?.proxy) + } + ) + + if (response.data && response.data.status_code === 0) { + const data = response.data + + return { + status: "success", + result: { + itemList: data.itemList || [], + hasMore: data.hasMore + } + } + } + + return { + status: "error", + message: ERROR_MESSAGES.NETWORK_ERROR + } + } catch (error) { + return { + status: "error", + message: + error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR + } + } +} + +export const extractCollectionId = (input: string): string | null => { + // If it's already just a number, return it + if (/^\d+$/.test(input)) { + return input + } + + // Try to extract from URL + const match = input.match(COLLECTION_URL_REGEX) + return match ? match[1] : null +} diff --git a/src/utils/get/getPlaylist.ts b/src/utils/get/getPlaylist.ts index ee0354e..c91853a 100644 --- a/src/utils/get/getPlaylist.ts +++ b/src/utils/get/getPlaylist.ts @@ -6,6 +6,10 @@ import { SocksProxyAgent } from "socks-proxy-agent" import { ERROR_MESSAGES } from "../../constants" import retry from "async-retry" import { TiktokPlaylistResponse } from "../../types/get/getPlaylist" +import { handleRedirect } from "../downloader/tiktokAPIDownloader" + +/** Constants */ +const PLAYLIST_URL_REGEX = /playlist\/[^/]+-(\d+)/ /** Types */ interface ProxyConfig { @@ -54,7 +58,7 @@ export const getPlaylist = async ( "Accept-Language": "en-US,en;q=0.7", Referer: "https://www.tiktok.com/", Origin: "https://www.tiktok.com", - "Content-Type": "application/json", + "Content-Type": "application/json" }, ...createProxyAgent(proxy) } @@ -78,7 +82,7 @@ export const getPlaylist = async ( result: { hasMore: response.hasMore, itemList: response.itemList || [], - extra: response.extra, + extra: response.extra } } } catch (error) { @@ -89,3 +93,79 @@ export const getPlaylist = async ( } } } + +export const Playlist = async ( + url: string, + options?: { + page?: number + proxy?: string + count?: number + } +): Promise => { + try { + const processedUrl = url.startsWith("http") + ? await handleRedirect(url, options?.proxy) + : url + + const playlistId = extractPlaylistId(processedUrl) + if (!playlistId) { + return { + status: "error", + message: "Invalid playlist ID or URL format" + } + } + + const response = await Axios( + _tiktokGetPlaylist( + _getPlaylistParams(playlistId, options.page, options.count) + ), + { + method: "GET", + headers: { + "User-Agent": + "Mozilla/5.0 (X11; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0", + Accept: "*/*", + "Accept-Language": "en-US,en;q=0.7", + Referer: "https://www.tiktok.com/", + Origin: "https://www.tiktok.com" + }, + ...createProxyAgent(options?.proxy) + } + ) + + if (response.data && response.data.status_code === 0) { + const data = response.data + + return { + status: "success", + result: { + itemList: data.itemList || [], + hasMore: data.hasMore, + extra: data.extra + } + } + } + + return { + status: "error", + message: ERROR_MESSAGES.NETWORK_ERROR + } + } catch (error) { + return { + status: "error", + message: + error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR + } + } +} + +export const extractPlaylistId = (input: string): string | null => { + // If it's already just a number, return it + if (/^\d+$/.test(input)) { + return input + } + + // Try to extract from URL + const match = input.match(PLAYLIST_URL_REGEX) + return match ? match[1] : null +} diff --git a/src/utils/get/getProfile.ts b/src/utils/get/getProfile.ts index b8f04e9..b5e58da 100644 --- a/src/utils/get/getProfile.ts +++ b/src/utils/get/getProfile.ts @@ -23,7 +23,6 @@ import { SocksProxyAgent } from "socks-proxy-agent" export const StalkUser = ( username: string, - cookie?: string | any[], proxy?: string ): Promise => new Promise(async (resolve) => { @@ -31,10 +30,6 @@ export const StalkUser = ( Axios(`${_tiktokurl}/@${username}`, { method: "GET", headers: { - cookie: - typeof cookie === "object" - ? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ") - : cookie, "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" }, diff --git a/src/utils/search/videoSearch.ts b/src/utils/search/videoSearch.ts index 14e6211..06c31b3 100644 --- a/src/utils/search/videoSearch.ts +++ b/src/utils/search/videoSearch.ts @@ -3,7 +3,6 @@ import { _tiktokSearchVideoFull } from "../../constants/api" import { _liveSearchParams, _videoSearchParams } from "../../constants/params" import { SocksProxyAgent } from "socks-proxy-agent" import { HttpsProxyAgent } from "https-proxy-agent" -import { TiktokService } from "../../services/tiktokService" import retry from "async-retry" import { TiktokVideoSearchResponse, diff --git a/test/collection-test.ts b/test/collection-test.ts index bc8f832..2c5238a 100644 --- a/test/collection-test.ts +++ b/test/collection-test.ts @@ -1,14 +1,15 @@ -import { Collection } from "../src/utils/downloader/tiktokApi" +import Tiktok from "../src/index" async function testCollection() { try { // You can use either a collection ID or URL const collectionId = "7507916135931218695" - const collectionUrl = "https://www.tiktok.com/@getrex.co.nz/collection/big%20back-7507916135931218695" + const collectionUrl = + "https://www.tiktok.com/@getrex.co.nz/collection/big%20back-7507916135931218695" const collectionShareableLink = "https://vt.tiktok.com/ZShvmqNjQ/" console.log("Testing Collection method...") - const result = await Collection(collectionId, { + const result = await Tiktok.Collection(collectionId, { page: 1, count: 5, // Optional: Number of items to fetch proxy: undefined // Optional: Add your proxy if needed @@ -29,7 +30,9 @@ async function testCollection() { console.log(`ID: ${item.id}`) console.log(`Description: ${item.desc}`) console.log(`Author: ${item.author.nickname}`) - console.log(`Created: ${new Date(item.createTime * 1000).toLocaleString()}`) + console.log( + `Created: ${new Date(item.createTime * 1000).toLocaleString()}` + ) // Log video URL if (item.video?.playAddr?.[0]) { @@ -50,7 +53,7 @@ async function testCollection() { // Log hashtags if available if (item.textExtra?.length > 0) { console.log("\nHashtags:") - item.textExtra.forEach(tag => { + item.textExtra.forEach((tag) => { if (tag.hashtagName) { console.log(`- #${tag.hashtagName}`) } @@ -67,4 +70,4 @@ async function testCollection() { } // Run the test -testCollection() \ No newline at end of file +testCollection() diff --git a/test/comments-test.ts b/test/comments-test.ts new file mode 100644 index 0000000..63c242d --- /dev/null +++ b/test/comments-test.ts @@ -0,0 +1,54 @@ +// Test for Tiktok Video Comments +import Tiktok from "../src/index" + +async function testComments() { + try { + const url = "https://www.tiktok.com/@tobz2k19/video/7451777267107187986" // Change to a valid TikTok video URL + const result = await Tiktok.GetVideoComments(url, { + commentLimit: 10, + proxy: undefined + }) + if (result.status === "success" && result.result) { + console.log("\nComments fetched successfully!") + console.log("========================") + console.log("Comments Overview:") + console.log("========================") + console.log(`Total comments fetched: ${result.result.length}`) + // Log all comments + result.result.forEach((comment, index) => { + console.log(`\nComment ${index + 1}:`) + console.log("-------------------") + console.log(`ID: ${comment.cid}`) + if (comment.user) { + console.log( + `Author: ${comment.user.nickname} (@${comment.user.username})` + ) + console.log(`Verified: ${comment.user.isVerified ? "Yes" : "No"}`) + } + console.log(`Text: ${comment.text}`) + if (comment.createTime) { + console.log( + `Created: ${new Date(comment.createTime * 1000).toLocaleString()}` + ) + } + // Log comment statistics + if (typeof comment.likeCount !== "undefined") { + console.log("\nStatistics:") + console.log(`- Likes: ${comment.likeCount}`) + } + if (typeof comment.replyCommentTotal !== "undefined") { + console.log(`- Replies: ${comment.replyCommentTotal}`) + } + if (comment.isAuthorLiked) console.log("👍 Liked by author") + if (comment.isCommentTranslatable) console.log("🌐 Translatable") + console.log("========================") + }) + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testComments() diff --git a/test/downloader-v1-test.ts b/test/downloader-v1-test.ts new file mode 100644 index 0000000..54e2feb --- /dev/null +++ b/test/downloader-v1-test.ts @@ -0,0 +1,49 @@ +// Test for Tiktok Downloader v1 +import Tiktok from "../src/index" + +async function testDownloaderV1() { + try { + const url = "https://www.tiktok.com/@tobz2k19/video/7451777267107187986" // Change to a valid TikTok video URL + console.log(`\nTesting Downloader version: v1`) + const result = await Tiktok.Downloader(url, { + version: "v1", + proxy: undefined + }) + if (result.status === "success" && result.result) { + const r = result.result + console.log(`Type: ${r.type}`) + console.log(`ID: ${r.id}`) + console.log(`Description: ${r.desc}`) + if (r.author) { + console.log(`Author: ${r.author.nickname}`) + } + if (r.statistics) { + console.log("Statistics:") + console.log(`- Likes: ${r.statistics.likeCount}`) + console.log(`- Comments: ${r.statistics.commentCount}`) + console.log(`- Shares: ${r.statistics.shareCount}`) + console.log(`- Plays: ${r.statistics.playCount}`) + } + if (r.video?.playAddr?.length) { + console.log(`Video URL: ${r.video.playAddr[0]}`) + } + if (r.images?.length) { + console.log(`Images: ${r.images.join(", ")}`) + } + if (r.music) { + console.log(`Music:`) + console.log(`- Title: ${r.music.title}`) + if (r.music.playUrl?.length) { + console.log(`- Music URL: ${r.music.playUrl[0]}`) + } + } + console.log("========================") + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testDownloaderV1() diff --git a/test/downloader-v2-test.ts b/test/downloader-v2-test.ts new file mode 100644 index 0000000..2250103 --- /dev/null +++ b/test/downloader-v2-test.ts @@ -0,0 +1,47 @@ +// Test for Tiktok Downloader v2 +import Tiktok from "../src/index" + +async function testDownloaderV2() { + try { + const url = "https://www.tiktok.com/@tobz2k19/video/7451777267107187986" // Change to a valid TikTok video URL + console.log(`\nTesting Downloader version: v2`) + const result = await Tiktok.Downloader(url, { + version: "v2", + proxy: undefined + }) + if (result.status === "success" && result.result) { + const r = result.result + console.log(`Type: ${r.type}`) + if (r.desc) console.log(`Description: ${r.desc}`) + if (r.author && r.author.nickname) { + console.log(`Author: ${r.author.nickname}`) + } else if (r.author && r.author.avatar) { + // fallback for v2 author structure + console.log(`Author Avatar: ${r.author.avatar}`) + } + if (r.statistics) { + console.log("Statistics:") + if (r.statistics.likeCount !== undefined) + console.log(`- Likes: ${r.statistics.likeCount}`) + if (r.statistics.commentCount !== undefined) + console.log(`- Comments: ${r.statistics.commentCount}`) + if (r.statistics.shareCount !== undefined) + console.log(`- Shares: ${r.statistics.shareCount}`) + } + if (r.video?.playAddr?.length) { + console.log(`Video URL: ${r.video.playAddr[0]}`) + } + if (r.music?.playUrl?.length) { + console.log(`Music URL: ${r.music.playUrl[0]}`) + } + if (r.images?.length) console.log(`Images: ${r.images.join(", ")}`) + console.log("========================") + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testDownloaderV2() diff --git a/test/downloader-v3-test.ts b/test/downloader-v3-test.ts new file mode 100644 index 0000000..0ced1f5 --- /dev/null +++ b/test/downloader-v3-test.ts @@ -0,0 +1,35 @@ +// Test for Tiktok Downloader v3 +import Tiktok from "../src/index" + +async function testDownloaderV3() { + try { + const url = "https://www.tiktok.com/@tobz2k19/video/7451777267107187986" // Change to a valid TikTok video URL + console.log(`\nTesting Downloader version: v3`) + const result = await Tiktok.Downloader(url, { + version: "v3", + proxy: undefined + }) + if (result.status === "success" && result.result) { + const r = result.result + console.log(`Type: ${r.type}`) + if (r.desc) console.log(`Description: ${r.desc}`) + if (r.author && r.author.nickname) { + console.log(`Author: ${r.author.nickname}`) + } else if (r.author && r.author.avatar) { + // fallback for v3 author structure + console.log(`Author Avatar: ${r.author.avatar}`) + } + if (r.videoHD) console.log(`Video HD: ${r.videoHD}`) + if (r.videoWatermark) console.log(`Video Watermark: ${r.videoWatermark}`) + if (r.images?.length) console.log(`Images: ${r.images.join(", ")}`) + if (r.music) console.log(`Music: ${r.music}`) + console.log("========================") + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testDownloaderV3() diff --git a/test/playlist-test.ts b/test/playlist-test.ts new file mode 100644 index 0000000..e4c108b --- /dev/null +++ b/test/playlist-test.ts @@ -0,0 +1,48 @@ +// Test for Tiktok Playlist +import Tiktok from "../src/index" + +async function testPlaylist() { + try { + const playlistUrl = + "https://www.tiktok.com/@tobz2k19/playlist/tset-7511644672511626004" // Ganti dengan URL playlist yang valid jika perlu + console.log(`\nTesting Playlist: ${playlistUrl}`) + const result = await Tiktok.Playlist(playlistUrl, { + proxy: undefined, + page: 1, + count: 5 + }) + if (result.status === "success" && result.result) { + const { itemList, hasMore, extra } = result.result + console.log(`Total Videos: ${itemList.length}`) + itemList.forEach((item, idx) => { + console.log(`\n[${idx + 1}] ID: ${item.id}`) + console.log(`Description: ${item.desc}`) + if (item.author) { + console.log(`Author: ${item.author.nickname}`) + } + if (item.stats) { + console.log("Statistics:") + console.log(`- Likes: ${item.stats.diggCount}`) + console.log(`- Comments: ${item.stats.commentCount}`) + console.log(`- Shares: ${item.stats.shareCount}`) + console.log(`- Plays: ${item.stats.playCount}`) + } + if (item.video?.playAddr?.length) { + console.log(`Video URL: ${item.video.playAddr}`) + } + }) + console.log("========================") + if (hasMore) { + console.log( + "There are more videos. Use the 'page' option to fetch next page." + ) + } + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testPlaylist() diff --git a/test/profile-test.ts b/test/profile-test.ts new file mode 100644 index 0000000..08d24cf --- /dev/null +++ b/test/profile-test.ts @@ -0,0 +1,41 @@ +// Test for Tiktok Stalk User Profile +import Tiktok from "../src/index" + +async function testProfile() { + try { + const username = "tobz2k19" // Change to a valid TikTok username + const result = await Tiktok.StalkUser(username, { + proxy: undefined + }) + if (result.status === "success" && result.result) { + const user = result.result.user + const stats = result.result.stats + console.log("\nProfile fetched successfully!") + console.log("========================") + console.log("User Profile:") + console.log("========================") + console.log(`Username: @${user.username}`) + console.log(`Nickname: ${user.nickname}`) + console.log(`Signature: ${user.signature}`) + console.log(`Verified: ${user.verified ? "Yes" : "No"}`) + console.log(`Region: ${user.region}`) + console.log(`Private Account: ${user.privateAccount ? "Yes" : "No"}`) + console.log(`Commerce User: ${user.commerceUser ? "Yes" : "No"}`) + console.log(`Avatar: ${user.avatarLarger}`) + console.log("\nStats:") + console.log(`- Followers: ${stats.followerCount}`) + console.log(`- Following: ${stats.followingCount}`) + console.log(`- Hearts: ${stats.heartCount}`) + console.log(`- Videos: ${stats.videoCount}`) + console.log(`- Likes: ${stats.likeCount}`) + console.log(`- Friends: ${stats.friendCount}`) + console.log("========================") + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testProfile() diff --git a/test/search-live-test.ts b/test/search-live-test.ts new file mode 100644 index 0000000..18fcfe2 --- /dev/null +++ b/test/search-live-test.ts @@ -0,0 +1,42 @@ +// Test for Tiktok Search Live +import Tiktok from "../src/index" + +async function testSearchLive() { + try { + const keyword = "call of duty" // Change to a valid search keyword + const cookie = "" // Optional: provide a valid TikTok cookie if needed + console.log(`\nTesting Search type: live`) + const result = await Tiktok.Search(keyword, { + type: "live", + cookie, + page: 1, + proxy: undefined + }) + if (result.status === "success" && result.result) { + console.log("Success! Parsed Result:") + result.result.forEach((item, index) => { + if (item.type === "live") { + const live = item as typeof item & { liveInfo: any } + if (live.liveInfo) { + console.log(`\nResult ${index + 1}:`) + console.log("-------------------") + console.log(`ID: ${live.liveInfo.id}`) + console.log(`Title: ${live.liveInfo.title}`) + console.log(`Hashtag: ${live.liveInfo.hashtag}`) + if (live.liveInfo.owner) + console.log(`Owner: ${live.liveInfo.owner.nickname}`) + if (live.liveInfo.stats) + console.log(`Viewers: ${live.liveInfo.stats.viewerCount}`) + console.log("========================") + } + } + }) + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testSearchLive() diff --git a/test/search-user-test.ts b/test/search-user-test.ts new file mode 100644 index 0000000..27e184a --- /dev/null +++ b/test/search-user-test.ts @@ -0,0 +1,46 @@ +// Test for Tiktok Search User +import Tiktok from "../src/index" + +async function testSearchUser() { + try { + const keyword = "call of duty" // Change to a valid search keyword + const cookie = "" // Optional: provide a valid TikTok cookie if needed + console.log(`\nTesting Search type: user`) + const result = await Tiktok.Search(keyword, { + type: "user", + cookie, + page: 1, + proxy: undefined + }) + if (result.status === "success" && result.result) { + console.log("Success! Parsed Result:") + result.result.forEach((item, index) => { + if (item.type === "user") { + const user = item as typeof item & { + uid: string + username: string + nickname: string + followerCount: number + isVerified: boolean + url: string + } + console.log(`\nResult ${index + 1}:`) + console.log("-------------------") + console.log(`UID: ${user.uid}`) + console.log(`Username: ${user.username}`) + console.log(`Nickname: ${user.nickname}`) + console.log(`Followers: ${user.followerCount}`) + console.log(`Verified: ${user.isVerified ? "Yes" : "No"}`) + console.log(`Profile URL: ${user.url}`) + console.log("========================") + } + }) + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testSearchUser() diff --git a/test/search-video-test.ts b/test/search-video-test.ts new file mode 100644 index 0000000..1940e17 --- /dev/null +++ b/test/search-video-test.ts @@ -0,0 +1,53 @@ +// Test for Tiktok Search Video +import Tiktok from "../src/index" + +async function testSearchVideo() { + try { + const keyword = "call of duty" // Change to a valid search keyword + const cookie = "" // Optional: provide a valid TikTok cookie if needed + console.log(`\nTesting Search type: video`) + const result = await Tiktok.Search(keyword, { + type: "video", + cookie, + page: 1, + proxy: undefined + }) + if (result.status === "success" && result.result) { + console.log("Success! Parsed Result:") + result.result.forEach((item, index) => { + if (item.type === "video") { + const video = item as typeof item & { + id: string + desc: string + author: any + createTime: number + stats: any + } + console.log(`\nResult ${index + 1}:`) + console.log("-------------------") + console.log(`ID: ${video.id}`) + console.log(`Description: ${video.desc}`) + if (video.author) console.log(`Author: ${video.author.nickname}`) + if (video.createTime) + console.log( + `Created: ${new Date(video.createTime * 1000).toLocaleString()}` + ) + if (video.stats) { + console.log("Statistics:") + console.log(`- Likes: ${video.stats.likeCount}`) + console.log(`- Comments: ${video.stats.commentCount}`) + console.log(`- Shares: ${video.stats.shareCount}`) + console.log(`- Plays: ${video.stats.playCount}`) + } + console.log("========================") + } + }) + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testSearchVideo() diff --git a/test/userliked-test.ts b/test/userliked-test.ts new file mode 100644 index 0000000..c027f4c --- /dev/null +++ b/test/userliked-test.ts @@ -0,0 +1,65 @@ +// Test for Tiktok Get User Liked Videos +import Tiktok from "../src/index" + +async function testUserLiked() { + try { + const username = "Tobz2k19" // Change to a valid TikTok username + const cookie = "" // Optional: provide a valid TikTok cookie if needed + const result = await Tiktok.GetUserLiked(username, { + cookie, + postLimit: 5, + proxy: undefined + }) + if (result.status === "success" && result.result) { + console.log("\nUser Liked Videos fetched successfully!") + console.log("========================") + console.log("Liked Videos Overview:") + console.log("========================") + console.log(`Total liked videos fetched: ${result.result.length}`) + result.result.forEach((liked, index) => { + console.log(`\nLiked Video ${index + 1}:`) + console.log("-------------------") + console.log(`ID: ${liked.id}`) + console.log(`Description: ${liked.desc}`) + if (liked.author) { + console.log( + `Author: ${liked.author.nickname} (@${liked.author.username})` + ) + } + if (liked.createTime) { + console.log( + `Created: ${new Date( + Number(liked.createTime) * 1000 + ).toLocaleString()}` + ) + } + if (liked.stats) { + console.log("Statistics:") + console.log(`- Likes: ${liked.stats.diggCount}`) + console.log(`- Favorites: ${liked.stats.collectCount}`) + console.log(`- Comments: ${liked.stats.commentCount}`) + console.log(`- Shares: ${liked.stats.shareCount}`) + console.log(`- Plays: ${liked.stats.playCount}`) + console.log(`- Reposts: ${liked.stats.repostCount}`) + } + if (liked.video?.playAddr) { + console.log(`Video URL: ${liked.video.playAddr}`) + } + if (liked.imagePost?.length) { + console.log( + `Images: \n${liked.imagePost + .map((img) => img.images) + .join("\n - ")}` + ) + } + console.log("========================") + }) + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testUserLiked() diff --git a/test/userposts-test.ts b/test/userposts-test.ts new file mode 100644 index 0000000..0083f2b --- /dev/null +++ b/test/userposts-test.ts @@ -0,0 +1,55 @@ +// Test for Tiktok Get User Posts +import Tiktok from "../src/index" + +async function testUserPosts() { + try { + const username = "Tobz2k19" // Change to a valid TikTok username + const result = await Tiktok.GetUserPosts(username, { + postLimit: 5, + proxy: undefined + }) + if (result.status === "success" && result.result) { + console.log("\nUser Posts fetched successfully!") + console.log("========================") + console.log("Posts Overview:") + console.log("========================") + console.log(`Total posts fetched: ${result.result.length}`) + result.result.forEach((post, index) => { + console.log(`\nPost ${index + 1}:`) + console.log("-------------------") + console.log(`ID: ${post.id}`) + console.log(`Description: ${post.desc}`) + if (post.author) { + console.log( + `Author: ${post.author.nickname} (@${post.author.username})` + ) + } + if (post.createTime) { + console.log( + `Created: ${new Date(post.createTime * 1000).toLocaleString()}` + ) + } + if (post.stats) { + console.log("Statistics:") + console.log(`- Likes: ${post.stats.likeCount}`) + console.log(`- Comments: ${post.stats.commentCount}`) + console.log(`- Shares: ${post.stats.shareCount}`) + console.log(`- Plays: ${post.stats.playCount}`) + } + if (post.video?.playAddr) { + console.log(`Video URL: ${post.video.playAddr}`) + } + if (post.imagePost?.length) { + console.log(`Images: ${post.imagePost.join(", ")}`) + } + console.log("========================") + }) + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +testUserPosts()