Merge pull request #46 from TobyG74/pr-45

Pr 45
This commit is contained in:
Tobi Saputra 2025-06-03 17:26:16 +07:00 committed by GitHub
commit 88bff6b727
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 1186 additions and 321 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ pnpm-lock.yaml
package-lock.json package-lock.json
yarn.lock yarn.lock
lib lib
!src/lib
test.js test.js
bun.lockb bun.lockb
tsconfig.tsbuildinfo tsconfig.tsbuildinfo

View File

@ -371,3 +371,27 @@
### Changed ### Changed
- Update Documentation [4260ea6](https://github.com/TobyG74/tiktok-api-dl/commit/4260ea653ee50569f898cc0653cb35e4557992a9) - 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)

249
README.md
View File

@ -46,6 +46,8 @@
- [Tiktok Video User Comments](#tiktok-video-comments) - [Tiktok Video User Comments](#tiktok-video-comments)
- [Tiktok Get User Posts](#tiktok-get-user-posts) - [Tiktok Get User Posts](#tiktok-get-user-posts)
- [Tiktok Get User Favorite Videos](#tiktok-get-user-favorite-videos) - [Tiktok Get User Favorite Videos](#tiktok-get-user-favorite-videos)
- [Tiktok Collection](#tiktok-collection)
- [Tiktok Playlist](#tiktok-playlist)
- [API Response Types](#api-response-types) - [API Response Types](#api-response-types)
- [Tiktok Downloader](#tiktok-downloader-1) - [Tiktok Downloader](#tiktok-downloader-1)
- [Version 1 Response](#version-1-response) - [Version 1 Response](#version-1-response)
@ -182,6 +184,22 @@ Tiktok.Downloader(url, {
}).then((result) => console.log(result)) }).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 1 Response](#version-1-response)
- [Version 2 Response](#version-2-response) - [Version 2 Response](#version-2-response)
- [Version 3 Response](#version-3-response) - [Version 3 Response](#version-3-response)
@ -192,15 +210,47 @@ Tiktok.Downloader(url, {
const Tiktok = require("@tobyg74/tiktok-api-dl") const Tiktok = require("@tobyg74/tiktok-api-dl")
Tiktok.Search("username", { Tiktok.Search("username", {
type: "user", // "user" | "live" type: "user", // "user" | "live" | "video"
page: 1, page: 1,
cookie: "YOUR_COOKIE", // needed cookie: "YOUR_COOKIE", // needed
proxy: "YOUR_PROXY" // optional proxy: "YOUR_PROXY" // optional
}).then((result) => console.log(result)) }).then((result) => console.log(result))
``` ```
### CLI Usage
```bash
# Search Tiktok Users
tiktokdl search user <username>
# Search Tiktok Users with pagination
tiktokdl search user <username> -p 1
# Search Tiktok Users with proxy
tiktokdl search user <username> -p 1 -proxy "http://your-proxy-url"
# Search Tiktok Live Streams
tiktokdl search live <username>
# Search Tiktok Live Streams with pagination
tiktokdl search live <username> -p 1
# Search Tiktok Live Streams with proxy
tiktokdl search live <username> -p 1 -proxy "http://your-proxy-url"
# Search Tiktok Videos
tiktokdl search video <query>
# Search Tiktok Videos with pagination
tiktokdl search video <query> -p 1
# Search Tiktok Videos with proxy
tiktokdl search video <query> -p 1 -proxy "http://your-proxy-url"
```
- [User Search Response](#user-search-response) - [User Search Response](#user-search-response)
- [Live Search Response](live-search-response) - [Live Search Response](live-search-response)
- [Video Search Response](#video-search-response)
## Tiktok Stalk User Profile ## Tiktok Stalk User Profile
@ -208,12 +258,21 @@ Tiktok.Search("username", {
const Tiktok = require("@tobyg74/tiktok-api-dl") const Tiktok = require("@tobyg74/tiktok-api-dl")
const username = "Tobz2k19" const username = "Tobz2k19"
Tiktok.Stalker(username, { Tiktok.StalkUser(username, {
cookie: "YOUR_COOKIE", // optional, if response null
proxy: "YOUR_PROXY" // optional proxy: "YOUR_PROXY" // optional
}).then((result) => console.log(result)) }).then((result) => console.log(result))
``` ```
### CLI Usage
```bash
# Stalk User Profile
tiktokdl stalk <username>
# Stalk User Profile with proxy
tiktokdl stalk <username> -proxy "http://your-proxy-url"
```
- [Tiktok Stalk User Response](#tiktok-stalk-user-profile-1) - [Tiktok Stalk User Response](#tiktok-stalk-user-profile-1)
## Tiktok Video Comments ## Tiktok Video Comments
@ -228,6 +287,19 @@ Tiktok.GetVideoComments(url, {
}).then((result) => console.log(result)) }).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 Video Comments Response](#tiktok-video-comments-1)
## Tiktok Get User Posts ## Tiktok Get User Posts
@ -242,6 +314,19 @@ Tiktok.GetUserPosts(username, {
}).then((result) => console.log(result)) }).then((result) => console.log(result))
``` ```
### CLI Usage
```bash
# Get User Posts
tiktokdl getuserposts <username>
# Get User Posts with limit of posts
tiktokdl getuserposts <username> -l 10
# Get User Posts with proxy
tiktokdl getuserposts <username> -l 10 -proxy "http://your-proxy-url"
```
- [Tiktok User Posts Response](#tiktok-user-posts) - [Tiktok User Posts Response](#tiktok-user-posts)
## Tiktok Get User Liked Videos ## Tiktok Get User Liked Videos
@ -259,6 +344,19 @@ Tiktok.GetUserLiked(username, {
}) })
``` ```
### CLI Usage
```bash
# Get User Liked Videos
tiktokdl getuserliked <username>
# Get User Liked Videos with limit of posts
tiktokdl getuserliked <username> -l 10
# Get User Liked Videos with proxy
tiktokdl getuserliked <username> -l 10 -proxy "http://your-proxy-url"
```
- [Tiktok User Liked Videos Response](#tiktok-user-liked-videos) - [Tiktok User Liked Videos Response](#tiktok-user-liked-videos)
## Tiktok Collection ## Tiktok Collection
@ -301,59 +399,41 @@ tiktokdl collection 7507916135931218695 -p 1 -n 5
tiktokdl collection 7507916135931218695 -n 5 -proxy "http://your-proxy-url" tiktokdl collection 7507916135931218695 -n 5 -proxy "http://your-proxy-url"
``` ```
### Response Type - [Tiktok Collection Response](#tiktok-collection-1)
```typescript ## Tiktok Playlist
interface TiktokCollectionResponse {
status: "success" | "error" Get videos from a TikTok playlist (supports playlist ID or URL)
message?: string
result?: { ```javascript
itemList: Array<{ const Tiktok = require("@tobyg74/tiktok-api-dl")
id: string
desc: string const playlistIdOrUrl = "https://www.tiktok.com/@username/playlist/name-id"
createTime: number Tiktok.Playlist(playlistIdOrUrl, {
author?: { page: 1,
id: string count: 5,
uniqueId: string proxy: "YOUR_PROXY"
nickname: string }).then((result) => console.log(result))
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
}
}
``` ```
### 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 # API Response Types
## Tiktok Downloader ## Tiktok Downloader
@ -446,8 +526,12 @@ interface SSSTikResponse {
shareCount: string shareCount: string
} }
images?: string[] images?: string[]
video?: string video?: {
music?: string playAddr: string
}
music?: {
playUrl: string
}
direct?: 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 # Changelog
- All changes will be documented in the [CHANGELOG.md](https://github.com/TobyG74/tiktok-api-dl/blob/master/CHANGELOG.md) file. - All changes will be documented in the [CHANGELOG.md](https://github.com/TobyG74/tiktok-api-dl/blob/master/CHANGELOG.md) file.

View File

@ -1,6 +1,6 @@
{ {
"name": "@tobyg74/tiktok-api-dl", "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", "description": "Scraper for downloading media in the form of videos, images and audio from Tiktok. Also for stalking Tiktok Users",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -27,10 +27,6 @@
"tiktok-stalk" "tiktok-stalk"
], ],
"author": "Tobz", "author": "Tobz",
"contributors": [
"aqulzz",
"nugraizy"
],
"license": "ISC", "license": "ISC",
"bugs": { "bugs": {
"url": "https://github.com/TobyG74/tiktok-api-dl/issues" "url": "https://github.com/TobyG74/tiktok-api-dl/issues"

View File

@ -10,9 +10,6 @@ import {
handleMediaDownload handleMediaDownload
} from "../services/downloadManager" } from "../services/downloadManager"
import { _tiktokurl } from "../constants/api" import { _tiktokurl } from "../constants/api"
import path from "path"
import * as fs from "fs"
import axios from "axios"
const cookieManager = new CookieManager() const cookieManager = new CookieManager()
@ -86,7 +83,7 @@ cookieCommand
const searchCommand = program const searchCommand = program
.command("search") .command("search")
.description("Search TikTok users or live streams") .description("Search TikTok users or live streams or videos")
searchCommand searchCommand
.command("user") .command("user")
@ -503,7 +500,7 @@ program
.command("playlist") .command("playlist")
.description("Get videos from a TikTok playlist") .description("Get videos from a TikTok playlist")
.argument( .argument(
"<url>", "<PlaylistIdOrUrl>",
"Collection URL (e.g. https://www.tiktok.com/@username/playlist/name-id)" "Collection URL (e.g. https://www.tiktok.com/@username/playlist/name-id)"
) )
.option("-p, --page <number>", "Page number", "1") .option("-p, --page <number>", "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(
"<PlaylistIdOrUrl>",
"Playlist URL (e.g. https://www.tiktok.com/@username/playlist/name-id)"
)
.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 (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(
"<collectionIdOrUrl>",
"Collection ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/collection/name-id)"
)
.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(
"-n, --count <number>",
"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() program.parse()

View File

@ -1,7 +1,7 @@
/** Types */ /** Types */
import { TiktokAPIResponse } from "./types/downloader/tiktokApi" import { TiktokAPIResponse } from "./types/downloader/tiktokApiDownloader"
import { SSSTikResponse } from "./types/downloader/ssstik" import { SSSTikResponse } from "./types/downloader/ssstikDownloader"
import { MusicalDownResponse } from "./types/downloader/musicaldown" import { MusicalDownResponse } from "./types/downloader/musicaldownDownloader"
import { UserSearchResult } from "./types/search/userSearch" import { UserSearchResult } from "./types/search/userSearch"
import { LiveSearchResult } from "./types/search/liveSearch" import { LiveSearchResult } from "./types/search/liveSearch"
import { VideoSearchResult } from "./types/search/videoSearch" import { VideoSearchResult } from "./types/search/videoSearch"
@ -12,9 +12,9 @@ import { TiktokUserFavoriteVideosResponse } from "./types/get/getUserLiked"
import { TiktokCollectionResponse } from "./types/get/getCollection" import { TiktokCollectionResponse } from "./types/get/getCollection"
/** Services */ /** Services */
import { extractPlaylistId, TiktokAPI } from "./utils/downloader/tiktokApi" import { TiktokAPI } from "./utils/downloader/tiktokAPIDownloader"
import { SSSTik } from "./utils/downloader/ssstik" import { SSSTik } from "./utils/downloader/ssstikDownloader"
import { MusicalDown } from "./utils/downloader/musicalDown" import { MusicalDown } from "./utils/downloader/musicaldownDownloader"
import { StalkUser } from "./utils/get/getProfile" import { StalkUser } from "./utils/get/getProfile"
import { SearchUser } from "./utils/search/userSearch" import { SearchUser } from "./utils/search/userSearch"
import { SearchLive } from "./utils/search/liveSearch" import { SearchLive } from "./utils/search/liveSearch"
@ -23,7 +23,6 @@ import { getUserPosts } from "./utils/get/getUserPosts"
import { getUserLiked } from "./utils/get/getUserLiked" import { getUserLiked } from "./utils/get/getUserLiked"
import { SearchVideo } from "./utils/search/videoSearch" import { SearchVideo } from "./utils/search/videoSearch"
import { getCollection } from "./utils/get/getCollection" import { getCollection } from "./utils/get/getCollection"
import { extractCollectionId } from "./utils/downloader/tiktokApi"
/** Constants */ /** Constants */
import { DOWNLOADER_VERSIONS, SEARCH_TYPES } from "./constants" import { DOWNLOADER_VERSIONS, SEARCH_TYPES } from "./constants"
@ -31,6 +30,8 @@ import { ERROR_MESSAGES } from "./constants"
import { validateCookie } from "./utils/validator" import { validateCookie } from "./utils/validator"
import { TiktokPlaylistResponse } from "./types/get/getPlaylist" import { TiktokPlaylistResponse } from "./types/get/getPlaylist"
import { getPlaylist } from "./utils/get/getPlaylist" import { getPlaylist } from "./utils/get/getPlaylist"
import { extractPlaylistId } from "./utils/get/getPlaylist"
import { extractCollectionId } from "./utils/get/getCollection"
/** Types */ /** Types */
type DownloaderVersion = "v1" | "v2" | "v3" type DownloaderVersion = "v1" | "v2" | "v3"
@ -82,7 +83,7 @@ export = {
Downloader: async <T extends DownloaderVersion>( Downloader: async <T extends DownloaderVersion>(
url: string, url: string,
options?: { options?: {
version: DownloaderVersion version: T
proxy?: string proxy?: string
showOriginalResponse?: boolean 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<TiktokCollectionResponse>}
*/
Collection: async (
collectionIdOrUrl: string,
options?: {
proxy?: string
page?: number
count?: number
}
): Promise<TiktokCollectionResponse> => {
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 * Tiktok Search
* @param {string} keyword - The query you want to search * @param {string} keyword - The query you want to search
@ -164,7 +133,7 @@ export = {
keyword: string, keyword: string,
options?: { options?: {
type?: T type?: T
cookie?: string cookie: string | any[]
page?: number page?: number
proxy?: string proxy?: string
} }
@ -240,11 +209,10 @@ export = {
StalkUser: async ( StalkUser: async (
username: string, username: string,
options?: { options?: {
cookie?: string | any[]
proxy?: string proxy?: string
} }
): Promise<TiktokStalkUserResponse> => { ): Promise<TiktokStalkUserResponse> => {
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<TiktokCollectionResponse>}
*/
Collection: async (
collectionIdOrUrl: string,
options?: {
proxy?: string
page?: number
count?: number
}
): Promise<TiktokCollectionResponse> => {
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 * 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 {Object} options - The options for playlist
* @param {string} [options.proxy] - Optional proxy URL * @param {string} [options.proxy] - Optional proxy URL
* @param {string} [options.page] - Optional page for pagination * @param {string} [options.page] - Optional page for pagination
@ -324,14 +324,14 @@ export = {
* @returns {Promise<TiktokPlaylistResponse>} * @returns {Promise<TiktokPlaylistResponse>}
*/ */
Playlist: async ( Playlist: async (
url: string, playlistIdOrUrl: string,
options?: { options?: {
proxy?: string proxy?: string
page?: number page?: number
count?: number count?: number
} }
): Promise<TiktokPlaylistResponse> => { ): Promise<TiktokPlaylistResponse> => {
const playlistId = extractPlaylistId(url) const playlistId = extractPlaylistId(playlistIdOrUrl)
if (!playlistId) { if (!playlistId) {
return { return {
status: "error", status: "error",

View File

@ -2,19 +2,19 @@ import chalk from "chalk"
export class Logger { export class Logger {
static success(message: string): void { static success(message: string): void {
console.log(chalk.green("✓ " + message)) console.log(chalk.green("✓ " + message))
} }
static error(message: string): void { static error(message: string): void {
console.error(chalk.red("✗ " + message)) console.error(chalk.red("✗ " + message))
} }
static info(message: string): void { static info(message: string): void {
console.log(chalk.blue(" " + message)) console.log(chalk.blue(" " + message))
} }
static warning(message: string): void { static warning(message: string): void {
console.log(chalk.yellow("⚠ " + message)) console.log(chalk.yellow("⚠ " + message))
} }
static result(message: string, color = chalk.cyan): void { static result(message: string, color = chalk.cyan): void {

View File

@ -61,7 +61,7 @@ async function handleMediaDownload(
case "video": { case "video": {
const videoUrl = const videoUrl =
version === "v1" version === "v1"
? result.video.downloadAddr[0] ? result.video.playAddr[0]
: version === "v2" : version === "v2"
? result.video.playAddr[0] ? result.video.playAddr[0]
: result.videoHD : result.videoHD

View File

@ -5,6 +5,7 @@ import { userAgent, webUserAgent } from "../constants/headers"
import qs from "qs" import qs from "qs"
import fs from "fs" import fs from "fs"
import { createCipheriv } from "crypto" import { createCipheriv } from "crypto"
import path from "path"
export class TiktokService { export class TiktokService {
/** /**
@ -82,6 +83,7 @@ export class TiktokService {
const baseUrl = `${TiktokService.BASE_URL}api/search/user/full/?` const baseUrl = `${TiktokService.BASE_URL}api/search/user/full/?`
const queryParams = _userSearchParams(username, page) const queryParams = _userSearchParams(username, page)
const xbogusParams = xbogus(`${baseUrl}${queryParams}`, userAgent) const xbogusParams = xbogus(`${baseUrl}${queryParams}`, userAgent)
console.log(`${baseUrl}${_userSearchParams(username, page, xbogusParams)}`)
return `${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 BASE_URL = "https://www.tiktok.com/"
private static readonly AES_KEY = "webapp1.0+202106" private static readonly AES_KEY = "webapp1.0+202106"
private static readonly AES_IV = "webapp1.0+202106" private static readonly AES_IV = "webapp1.0+202106"
private signaturejs = fs.readFileSync("./helper/signature.js", "utf-8") private signaturejs = fs.readFileSync(
private webmssdk = fs.readFileSync("./helper/webmssdk.js", "utf-8") 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({ private resourceLoader = new ResourceLoader({
userAgent: 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" "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"

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -2,7 +2,7 @@ import {
StatisticsTiktokAPI, StatisticsTiktokAPI,
MusicTiktokAPI, MusicTiktokAPI,
VideoTiktokAPI VideoTiktokAPI
} from "../downloader/tiktokApi" } from "../downloader/tiktokApiDownloader"
import { PlaylistAuthor } from "./getPlaylist" import { PlaylistAuthor } from "./getPlaylist"
export interface CollectionItem { export interface CollectionItem {

View File

@ -2,7 +2,7 @@ import {
AuthorTiktokAPI, AuthorTiktokAPI,
MusicTiktokAPI, MusicTiktokAPI,
VideoTiktokAPI VideoTiktokAPI
} from "../downloader/tiktokApi" } from "../downloader/tiktokApiDownloader"
export interface PlaylistAuthor export interface PlaylistAuthor
extends Omit<AuthorTiktokAPI, "username" | "uid"> { extends Omit<AuthorTiktokAPI, "username" | "uid"> {

View File

@ -3,7 +3,7 @@ import { load } from "cheerio"
import { import {
MusicalDownResponse, MusicalDownResponse,
GetMusicalDownReuqest GetMusicalDownReuqest
} from "../../types/downloader/musicaldown" } from "../../types/downloader/musicaldownDownloader"
import { _musicaldownapi, _musicaldownurl } from "../../constants/api" import { _musicaldownapi, _musicaldownurl } from "../../constants/api"
import { HttpsProxyAgent } from "https-proxy-agent" import { HttpsProxyAgent } from "https-proxy-agent"
import { SocksProxyAgent } from "socks-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent"

View File

@ -7,7 +7,7 @@ import {
StatisticsSSSTik, StatisticsSSSTik,
SSSTikFetchTT, SSSTikFetchTT,
SSSTikResponse SSSTikResponse
} from "../../types/downloader/ssstik" } from "../../types/downloader/ssstikDownloader"
import { _ssstikapi, _ssstikurl } from "../../constants/api" import { _ssstikapi, _ssstikurl } from "../../constants/api"
import { HttpsProxyAgent } from "https-proxy-agent" import { HttpsProxyAgent } from "https-proxy-agent"
import { SocksProxyAgent } from "socks-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent"

View File

@ -17,21 +17,17 @@ import {
StatisticsTiktokAPI, StatisticsTiktokAPI,
MusicTiktokAPI, MusicTiktokAPI,
ResponseParserTiktokAPI, ResponseParserTiktokAPI,
VideoTiktokAPI, VideoTiktokAPI
TiktokCollectionResponse } from "../../types/downloader/tiktokApiDownloader"
} from "../../types/downloader/tiktokApi"
import { HttpsProxyAgent } from "https-proxy-agent" import { HttpsProxyAgent } from "https-proxy-agent"
import { SocksProxyAgent } from "socks-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent"
import { ERROR_MESSAGES } from "../../constants" import { ERROR_MESSAGES } from "../../constants"
import { TiktokPlaylistResponse } from "../../types/get/getPlaylist"
/** Constants */ /** Constants */
const TIKTOK_URL_REGEX = const TIKTOK_URL_REGEX =
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
const USER_AGENT = const USER_AGENT =
"com.zhiliaoapp.musically/300904 (2018111632; U; Android 10; en_US; Pixel 4; Build/QQ3A.200805.001; Cronet/58.0.2991.0)" "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 */ /** Types */
interface ProxyConfig { interface ProxyConfig {
@ -208,7 +204,10 @@ const createVideoResponse = (
} }
}) })
const handleRedirect = async (url: string, proxy?: string): Promise<string> => { export const handleRedirect = async (
url: string,
proxy?: string
): Promise<string> => {
try { try {
const response = await Axios(url, { const response = await Axios(url, {
method: "HEAD", method: "HEAD",
@ -228,22 +227,6 @@ const handleRedirect = async (url: string, proxy?: string): Promise<string> => {
} }
} }
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 * Tiktok API Downloader
* @param {string} url - Tiktok URL * @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<TiktokCollectionResponse> => {
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<TiktokPlaylistResponse> => {
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
}
}
}

View File

@ -6,6 +6,10 @@ import { SocksProxyAgent } from "socks-proxy-agent"
import { TiktokCollectionResponse } from "../../types/get/getCollection" import { TiktokCollectionResponse } from "../../types/get/getCollection"
import { ERROR_MESSAGES } from "../../constants" import { ERROR_MESSAGES } from "../../constants"
import retry from "async-retry" import retry from "async-retry"
import { handleRedirect } from "../downloader/tiktokAPIDownloader"
/** Constants */
const COLLECTION_URL_REGEX = /collection\/[^/]+-(\d+)/
/** Types */ /** Types */
interface ProxyConfig { interface ProxyConfig {
@ -44,9 +48,7 @@ export const getCollection = async (
const response = await retry( const response = await retry(
async () => { async () => {
const res = await Axios( const res = await Axios(
_tiktokGetCollection( _tiktokGetCollection(_getCollectionParams(collectionId, page, count)),
_getCollectionParams(collectionId, page, count)
),
{ {
method: "GET", method: "GET",
headers: { headers: {
@ -90,3 +92,79 @@ export const getCollection = async (
} }
} }
} }
export const Collection = async (
collectionIdOrUrl: string,
options?: {
page?: number
proxy?: string
count?: number
}
): Promise<TiktokCollectionResponse> => {
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
}

View File

@ -6,6 +6,10 @@ import { SocksProxyAgent } from "socks-proxy-agent"
import { ERROR_MESSAGES } from "../../constants" import { ERROR_MESSAGES } from "../../constants"
import retry from "async-retry" import retry from "async-retry"
import { TiktokPlaylistResponse } from "../../types/get/getPlaylist" import { TiktokPlaylistResponse } from "../../types/get/getPlaylist"
import { handleRedirect } from "../downloader/tiktokAPIDownloader"
/** Constants */
const PLAYLIST_URL_REGEX = /playlist\/[^/]+-(\d+)/
/** Types */ /** Types */
interface ProxyConfig { interface ProxyConfig {
@ -54,7 +58,7 @@ export const getPlaylist = async (
"Accept-Language": "en-US,en;q=0.7", "Accept-Language": "en-US,en;q=0.7",
Referer: "https://www.tiktok.com/", Referer: "https://www.tiktok.com/",
Origin: "https://www.tiktok.com", Origin: "https://www.tiktok.com",
"Content-Type": "application/json", "Content-Type": "application/json"
}, },
...createProxyAgent(proxy) ...createProxyAgent(proxy)
} }
@ -78,7 +82,7 @@ export const getPlaylist = async (
result: { result: {
hasMore: response.hasMore, hasMore: response.hasMore,
itemList: response.itemList || [], itemList: response.itemList || [],
extra: response.extra, extra: response.extra
} }
} }
} catch (error) { } catch (error) {
@ -89,3 +93,79 @@ export const getPlaylist = async (
} }
} }
} }
export const Playlist = async (
url: string,
options?: {
page?: number
proxy?: string
count?: number
}
): Promise<TiktokPlaylistResponse> => {
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
}

View File

@ -23,7 +23,6 @@ import { SocksProxyAgent } from "socks-proxy-agent"
export const StalkUser = ( export const StalkUser = (
username: string, username: string,
cookie?: string | any[],
proxy?: string proxy?: string
): Promise<TiktokStalkUserResponse> => ): Promise<TiktokStalkUserResponse> =>
new Promise(async (resolve) => { new Promise(async (resolve) => {
@ -31,10 +30,6 @@ export const StalkUser = (
Axios(`${_tiktokurl}/@${username}`, { Axios(`${_tiktokurl}/@${username}`, {
method: "GET", method: "GET",
headers: { headers: {
cookie:
typeof cookie === "object"
? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ")
: cookie,
"User-Agent": "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" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
}, },

View File

@ -3,7 +3,6 @@ import { _tiktokSearchVideoFull } from "../../constants/api"
import { _liveSearchParams, _videoSearchParams } from "../../constants/params" import { _liveSearchParams, _videoSearchParams } from "../../constants/params"
import { SocksProxyAgent } from "socks-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent"
import { HttpsProxyAgent } from "https-proxy-agent" import { HttpsProxyAgent } from "https-proxy-agent"
import { TiktokService } from "../../services/tiktokService"
import retry from "async-retry" import retry from "async-retry"
import { import {
TiktokVideoSearchResponse, TiktokVideoSearchResponse,

View File

@ -1,14 +1,15 @@
import { Collection } from "../src/utils/downloader/tiktokApi" import Tiktok from "../src/index"
async function testCollection() { async function testCollection() {
try { try {
// You can use either a collection ID or URL // You can use either a collection ID or URL
const collectionId = "7507916135931218695" 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/" const collectionShareableLink = "https://vt.tiktok.com/ZShvmqNjQ/"
console.log("Testing Collection method...") console.log("Testing Collection method...")
const result = await Collection(collectionId, { const result = await Tiktok.Collection(collectionId, {
page: 1, page: 1,
count: 5, // Optional: Number of items to fetch count: 5, // Optional: Number of items to fetch
proxy: undefined // Optional: Add your proxy if needed proxy: undefined // Optional: Add your proxy if needed
@ -29,7 +30,9 @@ async function testCollection() {
console.log(`ID: ${item.id}`) console.log(`ID: ${item.id}`)
console.log(`Description: ${item.desc}`) console.log(`Description: ${item.desc}`)
console.log(`Author: ${item.author.nickname}`) 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 // Log video URL
if (item.video?.playAddr?.[0]) { if (item.video?.playAddr?.[0]) {
@ -50,7 +53,7 @@ async function testCollection() {
// Log hashtags if available // Log hashtags if available
if (item.textExtra?.length > 0) { if (item.textExtra?.length > 0) {
console.log("\nHashtags:") console.log("\nHashtags:")
item.textExtra.forEach(tag => { item.textExtra.forEach((tag) => {
if (tag.hashtagName) { if (tag.hashtagName) {
console.log(`- #${tag.hashtagName}`) console.log(`- #${tag.hashtagName}`)
} }
@ -67,4 +70,4 @@ async function testCollection() {
} }
// Run the test // Run the test
testCollection() testCollection()

54
test/comments-test.ts Normal file
View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

48
test/playlist-test.ts Normal file
View File

@ -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()

41
test/profile-test.ts Normal file
View File

@ -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()

42
test/search-live-test.ts Normal file
View File

@ -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()

46
test/search-user-test.ts Normal file
View File

@ -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()

53
test/search-video-test.ts Normal file
View File

@ -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()

65
test/userliked-test.ts Normal file
View File

@ -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()

55
test/userposts-test.ts Normal file
View File

@ -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()