feat: added playlist parsing support
This commit is contained in:
parent
5a4143e283
commit
2fa9e6fef4
111
src/cli/index.ts
111
src/cli/index.ts
@ -10,6 +10,9 @@ 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()
|
||||
|
||||
@ -412,7 +415,9 @@ program
|
||||
)
|
||||
.action(async (collectionIdOrUrl, options) => {
|
||||
try {
|
||||
Logger.info(`Fetching page ${options.page} with ${options.count} items per page from collection...`)
|
||||
Logger.info(
|
||||
`Fetching page ${options.page} with ${options.count} items per page from collection...`
|
||||
)
|
||||
const results = await Tiktok.Collection(collectionIdOrUrl, {
|
||||
page: options.page,
|
||||
proxy: options.proxy,
|
||||
@ -421,7 +426,6 @@ program
|
||||
|
||||
if (results.status === "success" && results.result) {
|
||||
const { itemList, hasMore } = results.result
|
||||
|
||||
Logger.info(`Found ${itemList.length} videos in collection`)
|
||||
Logger.info(`Has more videos: ${hasMore}`)
|
||||
|
||||
@ -460,7 +464,8 @@ program
|
||||
|
||||
if (video.video) {
|
||||
Logger.info(`---- VIDEO URLs ----`)
|
||||
const videoUrl = `${_tiktokurl}/@${video.author?.uniqueId || "unknown"
|
||||
const videoUrl = `${_tiktokurl}/@${
|
||||
video.author?.uniqueId || "unknown"
|
||||
}/video/${video.id}`
|
||||
Logger.result(`Video URL: ${videoUrl}`, chalk.blue)
|
||||
}
|
||||
@ -477,7 +482,105 @@ program
|
||||
|
||||
if (hasMore) {
|
||||
Logger.info("\nTo fetch more videos, use:")
|
||||
Logger.info(`tiktokdl collection ${collectionIdOrUrl} -p ${parseInt(options.page) + 1}`)
|
||||
Logger.info(
|
||||
`tiktokdl collection ${collectionIdOrUrl} -p ${
|
||||
parseInt(options.page) + 1
|
||||
}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Logger.error(`Error: ${results.message}`)
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(`Error: ${error.message}`)
|
||||
}
|
||||
})
|
||||
|
||||
// =============================================
|
||||
// Playlist parser
|
||||
// =============================================
|
||||
program
|
||||
.command("playlist")
|
||||
.description("Get videos from a TikTok playlist")
|
||||
.argument(
|
||||
"<url>",
|
||||
"Collection URL (e.g. https://www.tiktok.com/@username/playlist/name-id)"
|
||||
)
|
||||
.option("-p, --page <number>", "Page number", "1")
|
||||
.option("--proxy <proxy>", "Proxy URL (http/https/socks)")
|
||||
.option(
|
||||
"-c, --count <number>",
|
||||
"Number of items to fetch (max: 20)",
|
||||
(val) => parseInt(val),
|
||||
5
|
||||
)
|
||||
.option("-r, --raw", "Show raw response", false)
|
||||
.action(async (url, options) => {
|
||||
try {
|
||||
Logger.info(
|
||||
`Fetching page ${options.page} with ${options.count} items per page from playlist...`
|
||||
)
|
||||
const results = await Tiktok.Playlist(url, {
|
||||
page: options.page,
|
||||
proxy: options.proxy,
|
||||
count: options.count
|
||||
})
|
||||
|
||||
const contentType = (content: any): string => {
|
||||
if (content?.imagePost) {
|
||||
return "photo"
|
||||
} else {
|
||||
return "video"
|
||||
}
|
||||
}
|
||||
|
||||
if (results.status === "success" && results.result) {
|
||||
if (options.raw) {
|
||||
console.log(JSON.stringify(results.result, null, 2))
|
||||
return
|
||||
}
|
||||
const { itemList, hasMore } = results.result
|
||||
|
||||
Logger.info(`Found ${itemList.length} items in playlist`)
|
||||
Logger.info(`Has more items: ${hasMore}`)
|
||||
|
||||
for (const [index, item] of itemList.entries()) {
|
||||
Logger.info(`---- ITEM ${index + 1} ----`)
|
||||
Logger.result(`Item ID: ${item.id}`, chalk.green)
|
||||
Logger.result(`Description: ${item.desc}`, chalk.yellow)
|
||||
Logger.result(
|
||||
`Author: ${item.author?.nickname || "Unknown"}`,
|
||||
chalk.yellow
|
||||
)
|
||||
Logger.result(
|
||||
`Created: ${new Date(item.createTime * 1000).toLocaleString()}`,
|
||||
chalk.yellow
|
||||
)
|
||||
|
||||
if (item.stats) {
|
||||
Logger.info(`---- STATISTICS ----`)
|
||||
Logger.result(
|
||||
`Comments: ${item.stats.commentCount || 0}`,
|
||||
chalk.yellow
|
||||
)
|
||||
Logger.result(`Shares: ${item.stats.shareCount || 0}`, chalk.yellow)
|
||||
Logger.result(`Plays: ${item.stats.playCount || 0}`, chalk.yellow)
|
||||
}
|
||||
|
||||
if (item.video) {
|
||||
Logger.info(`---- VIDEO URLs ----`)
|
||||
const videoUrl = `${_tiktokurl}/@${
|
||||
item.author?.uniqueId || "unknown"
|
||||
}/${contentType(item)}/${item.id}`
|
||||
Logger.result(`Video URL: ${videoUrl}`, chalk.blue)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasMore) {
|
||||
Logger.info("\nTo fetch more videos, use:")
|
||||
Logger.info(
|
||||
`tiktokdl playlist ${url} -p ${parseInt(options.page) + 1}`
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Logger.error(`Error: ${results.message}`)
|
||||
|
||||
@ -14,6 +14,8 @@ export const _tiktokGetUserLiked = (params: any): string =>
|
||||
`${_tiktokurl}/api/favorite/item_list/?${params}`
|
||||
export const _tiktokGetCollection = (params: any): string =>
|
||||
`${_tiktokurl}/api/collection/item_list/?${params}`
|
||||
export const _tiktokGetPlaylist = (params: any): string =>
|
||||
`${_tiktokurl}/api/mix/item_list/?${params}`
|
||||
|
||||
/** Tiktokv */
|
||||
export const _tiktokvApi: string = `https://api16-normal-useast5.tiktokv.us`
|
||||
|
||||
@ -353,7 +353,11 @@ const generateOdinId = () => {
|
||||
return `${prefix}${random}`
|
||||
}
|
||||
|
||||
export const _getCollectionParams = (collectionId: string, page: number = 1, count: number = 5) => {
|
||||
export const _getCollectionParams = (
|
||||
collectionId: string,
|
||||
page: number = 1,
|
||||
count: number = 5
|
||||
) => {
|
||||
let cursor = 0
|
||||
if (page > 0) {
|
||||
cursor = (page - 1) * count
|
||||
@ -368,7 +372,8 @@ export const _getCollectionParams = (collectionId: string, page: number = 1, cou
|
||||
browser_name: "Mozilla",
|
||||
browser_online: true,
|
||||
browser_platform: "Win32",
|
||||
browser_version: "5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
||||
browser_version:
|
||||
"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36",
|
||||
channel: "tiktok_web",
|
||||
collectionId,
|
||||
cookie_enabled: true,
|
||||
@ -398,4 +403,58 @@ export const _getCollectionParams = (collectionId: string, page: number = 1, cou
|
||||
})
|
||||
}
|
||||
|
||||
export const _getPlaylistParams = (
|
||||
playlistId: string,
|
||||
page: number = 1,
|
||||
/**
|
||||
* @max 20
|
||||
* @default 5
|
||||
*/
|
||||
count: number = 5
|
||||
) => {
|
||||
count = Math.min(Math.max(1, count), 20)
|
||||
|
||||
let cursor = 0
|
||||
if (page > 0) {
|
||||
cursor = (page - 1) * count
|
||||
}
|
||||
|
||||
return qs.stringify({
|
||||
WebIdLastTime: Date.now(),
|
||||
aid: 1988,
|
||||
app_language: "en",
|
||||
app_name: "tiktok_web",
|
||||
browser_language: "en-US",
|
||||
browser_name: "Mozilla",
|
||||
browser_online: true,
|
||||
browser_platform: "Linux x86_64",
|
||||
browser_version: "5.0 (X11)",
|
||||
channel: "tiktok_web",
|
||||
cookie_enabled: true,
|
||||
count,
|
||||
cursor: cursor.toString(),
|
||||
data_collection_enabled: true,
|
||||
device_id: generateDeviceId(),
|
||||
device_platform: "web_pc",
|
||||
focus_state: true,
|
||||
from_page: "user",
|
||||
history_len: 1,
|
||||
is_fullscreen: false,
|
||||
is_page_visible: true,
|
||||
language: "en",
|
||||
mixId: playlistId,
|
||||
odinId: generateOdinId(),
|
||||
os: "linux",
|
||||
priority_region: "NZ",
|
||||
referer: "",
|
||||
region: "NZ",
|
||||
screen_height: 1440,
|
||||
screen_width: 2560,
|
||||
tz_name: "Pacific/Auckland",
|
||||
user_is_login: true,
|
||||
verifyFp: "verify_lacphy8d_z2ux9idt_xdmu_4gKb_9nng_NNTTTvsFS8ao",
|
||||
webcast_language: "en"
|
||||
})
|
||||
}
|
||||
|
||||
export { randomChar, generateSearchId, generateDeviceId, generateOdinId }
|
||||
|
||||
58
src/index.ts
58
src/index.ts
@ -2,18 +2,9 @@
|
||||
import { TiktokAPIResponse } from "./types/downloader/tiktokApi"
|
||||
import { SSSTikResponse } from "./types/downloader/ssstik"
|
||||
import { MusicalDownResponse } from "./types/downloader/musicaldown"
|
||||
import {
|
||||
TiktokUserSearchResponse,
|
||||
UserSearchResult
|
||||
} from "./types/search/userSearch"
|
||||
import {
|
||||
TiktokLiveSearchResponse,
|
||||
LiveSearchResult
|
||||
} from "./types/search/liveSearch"
|
||||
import {
|
||||
TiktokVideoSearchResponse,
|
||||
VideoSearchResult
|
||||
} from "./types/search/videoSearch"
|
||||
import { UserSearchResult } from "./types/search/userSearch"
|
||||
import { LiveSearchResult } from "./types/search/liveSearch"
|
||||
import { VideoSearchResult } from "./types/search/videoSearch"
|
||||
import { TiktokStalkUserResponse } from "./types/get/getProfile"
|
||||
import { TiktokVideoCommentsResponse } from "./types/get/getComments"
|
||||
import { TiktokUserPostsResponse } from "./types/get/getUserPosts"
|
||||
@ -21,7 +12,7 @@ import { TiktokUserFavoriteVideosResponse } from "./types/get/getUserLiked"
|
||||
import { TiktokCollectionResponse } from "./types/get/getCollection"
|
||||
|
||||
/** Services */
|
||||
import { TiktokAPI } from "./utils/downloader/tiktokApi"
|
||||
import { extractPlaylistId, TiktokAPI } from "./utils/downloader/tiktokApi"
|
||||
import { SSSTik } from "./utils/downloader/ssstik"
|
||||
import { MusicalDown } from "./utils/downloader/musicalDown"
|
||||
import { StalkUser } from "./utils/get/getProfile"
|
||||
@ -38,6 +29,8 @@ import { extractCollectionId } from "./utils/downloader/tiktokApi"
|
||||
import { DOWNLOADER_VERSIONS, SEARCH_TYPES } from "./constants"
|
||||
import { ERROR_MESSAGES } from "./constants"
|
||||
import { validateCookie } from "./utils/validator"
|
||||
import { TiktokPlaylistResponse } from "./types/get/getPlaylist"
|
||||
import { getPlaylist } from "./utils/get/getPlaylist"
|
||||
|
||||
/** Types */
|
||||
type DownloaderVersion = "v1" | "v2" | "v3"
|
||||
@ -149,7 +142,12 @@ export = {
|
||||
message: "Invalid collection ID or URL format"
|
||||
}
|
||||
}
|
||||
return await getCollection(collectionId, options?.proxy, options?.page, options?.count)
|
||||
return await getCollection(
|
||||
collectionId,
|
||||
options?.proxy,
|
||||
options?.page,
|
||||
options?.count
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
@ -314,5 +312,37 @@ export = {
|
||||
options?.proxy,
|
||||
options?.postLimit
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get TikTok Playlist
|
||||
* @param {string} url - URL (e.g. 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
|
||||
* @param {number} [options.count] - Optional number of items to fetch(max: 20)
|
||||
* @returns {Promise<TiktokPlaylistResponse>}
|
||||
*/
|
||||
Playlist: async (
|
||||
url: string,
|
||||
options?: {
|
||||
proxy?: string
|
||||
page?: number
|
||||
count?: number
|
||||
}
|
||||
): Promise<TiktokPlaylistResponse> => {
|
||||
const playlistId = extractPlaylistId(url)
|
||||
if (!playlistId) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "Invalid playlist ID or URL format"
|
||||
}
|
||||
}
|
||||
return await getPlaylist(
|
||||
playlistId,
|
||||
options?.proxy,
|
||||
options?.page,
|
||||
options?.count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
import { AuthorTiktokAPI, StatisticsTiktokAPI, MusicTiktokAPI, VideoTiktokAPI } from "../downloader/tiktokApi"
|
||||
import {
|
||||
StatisticsTiktokAPI,
|
||||
MusicTiktokAPI,
|
||||
VideoTiktokAPI
|
||||
} from "../downloader/tiktokApi"
|
||||
import { PlaylistAuthor } from "./getPlaylist"
|
||||
|
||||
export interface CollectionItem {
|
||||
id: string
|
||||
desc: string
|
||||
createTime: number
|
||||
author: AuthorTiktokAPI
|
||||
author: PlaylistAuthor
|
||||
statistics: StatisticsTiktokAPI
|
||||
video: VideoTiktokAPI
|
||||
music: MusicTiktokAPI
|
||||
|
||||
73
src/types/get/getPlaylist.ts
Normal file
73
src/types/get/getPlaylist.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {
|
||||
AuthorTiktokAPI,
|
||||
MusicTiktokAPI,
|
||||
VideoTiktokAPI
|
||||
} from "../downloader/tiktokApi"
|
||||
|
||||
export interface PlaylistAuthor
|
||||
extends Omit<AuthorTiktokAPI, "username" | "uid"> {
|
||||
avatarLarger: string
|
||||
nickname: string
|
||||
id: string
|
||||
}
|
||||
|
||||
interface Statistics {
|
||||
collectCount: number
|
||||
commentCount: number
|
||||
diggCount: number
|
||||
playCount: number
|
||||
shareCount: number
|
||||
}
|
||||
|
||||
export interface PlaylistItem {
|
||||
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
|
||||
}>
|
||||
}
|
||||
|
||||
export interface TiktokPlaylistResponse {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: {
|
||||
hasMore: boolean
|
||||
itemList: PlaylistItem[]
|
||||
extra?: {
|
||||
fatal_item_ids: string[]
|
||||
logid: string
|
||||
now: number
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,16 @@
|
||||
import Axios from "axios"
|
||||
import asyncRetry from "async-retry"
|
||||
import { _tiktokvFeed, _tiktokurl, _tiktokGetCollection } from "../../constants/api"
|
||||
import { _tiktokApiParams, _getCollectionParams } from "../../constants/params"
|
||||
import {
|
||||
_tiktokvFeed,
|
||||
_tiktokurl,
|
||||
_tiktokGetCollection,
|
||||
_tiktokGetPlaylist
|
||||
} from "../../constants/api"
|
||||
import {
|
||||
_tiktokApiParams,
|
||||
_getCollectionParams,
|
||||
_getPlaylistParams
|
||||
} from "../../constants/params"
|
||||
import {
|
||||
AuthorTiktokAPI,
|
||||
TiktokAPIResponse,
|
||||
@ -14,6 +23,7 @@ import {
|
||||
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 =
|
||||
@ -21,6 +31,7 @@ const TIKTOK_URL_REGEX =
|
||||
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 {
|
||||
@ -200,7 +211,7 @@ const createVideoResponse = (
|
||||
const handleRedirect = async (url: string, proxy?: string): Promise<string> => {
|
||||
try {
|
||||
const response = await Axios(url, {
|
||||
method: 'HEAD',
|
||||
method: "HEAD",
|
||||
maxRedirects: 5,
|
||||
validateStatus: (status) => status >= 200 && status < 400,
|
||||
...createProxyAgent(proxy)
|
||||
@ -210,9 +221,9 @@ const handleRedirect = async (url: string, proxy?: string): Promise<string> => {
|
||||
const finalUrl = response.request.res.responseUrl
|
||||
|
||||
// Remove query parameters
|
||||
return finalUrl.split('?')[0]
|
||||
return finalUrl.split("?")[0]
|
||||
} catch (error) {
|
||||
console.error('Error handling redirect:', error)
|
||||
console.error("Error handling redirect:", error)
|
||||
return url
|
||||
}
|
||||
}
|
||||
@ -228,6 +239,11 @@ export const extractCollectionId = (input: string): string | null => {
|
||||
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
|
||||
@ -302,14 +318,14 @@ export const TiktokAPI = async (
|
||||
export const Collection = async (
|
||||
collectionIdOrUrl: string,
|
||||
options?: {
|
||||
page?: number,
|
||||
proxy?: string,
|
||||
page?: number
|
||||
proxy?: string
|
||||
count?: number
|
||||
}
|
||||
): Promise<TiktokCollectionResponse> => {
|
||||
try {
|
||||
// Only handle redirects if the input is a URL
|
||||
const processedUrl = collectionIdOrUrl.startsWith('http')
|
||||
const processedUrl = collectionIdOrUrl.startsWith("http")
|
||||
? await handleRedirect(collectionIdOrUrl, options?.proxy)
|
||||
: collectionIdOrUrl
|
||||
|
||||
@ -358,7 +374,73 @@ export const Collection = async (
|
||||
} catch (error) {
|
||||
return {
|
||||
status: "error",
|
||||
message: error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
91
src/utils/get/getPlaylist.ts
Normal file
91
src/utils/get/getPlaylist.ts
Normal file
@ -0,0 +1,91 @@
|
||||
import Axios from "axios"
|
||||
import { _tiktokGetPlaylist, _tiktokurl } from "../../constants/api"
|
||||
import { _getPlaylistParams } from "../../constants/params"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
import { ERROR_MESSAGES } from "../../constants"
|
||||
import retry from "async-retry"
|
||||
import { TiktokPlaylistResponse } from "../../types/get/getPlaylist"
|
||||
|
||||
/** Types */
|
||||
interface ProxyConfig {
|
||||
httpsAgent?: HttpsProxyAgent<string> | SocksProxyAgent
|
||||
}
|
||||
|
||||
const createProxyAgent = (proxy?: string): ProxyConfig => {
|
||||
if (!proxy) return {}
|
||||
|
||||
if (proxy.startsWith("socks")) {
|
||||
return {
|
||||
httpsAgent: new SocksProxyAgent(proxy)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
httpsAgent: new HttpsProxyAgent(proxy)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TikTok Collection
|
||||
* @param {string} collectionId - Collection ID
|
||||
* @param {string} proxy - Your Proxy (optional)
|
||||
* @param {string} page - Page for pagination (optional)
|
||||
* @param {number} count - Number of items to fetch (optional)
|
||||
* @returns {Promise<TiktokPlaylistResponse>}
|
||||
*/
|
||||
export const getPlaylist = async (
|
||||
playlistId: string,
|
||||
proxy?: string,
|
||||
page: number = 1,
|
||||
count: number = 5
|
||||
): Promise<TiktokPlaylistResponse> => {
|
||||
try {
|
||||
const response = await retry(
|
||||
async () => {
|
||||
const res = await Axios(
|
||||
_tiktokGetPlaylist(_getPlaylistParams(playlistId, page, 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",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
...createProxyAgent(proxy)
|
||||
}
|
||||
)
|
||||
|
||||
if (res.data && res.data.statusCode === 0) {
|
||||
return res.data
|
||||
}
|
||||
|
||||
throw new Error(ERROR_MESSAGES.NETWORK_ERROR)
|
||||
},
|
||||
{
|
||||
retries: 20,
|
||||
minTimeout: 200,
|
||||
maxTimeout: 1000
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
result: {
|
||||
hasMore: response.hasMore,
|
||||
itemList: response.itemList || [],
|
||||
extra: response.extra,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
status: "error",
|
||||
message:
|
||||
error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user