From 5bd743a888cfafe932f083a2f887dbdd98e99d0c Mon Sep 17 00:00:00 2001 From: Tino Laomahei Date: Sat, 24 May 2025 21:50:12 +1200 Subject: [PATCH 1/6] get collection --- .gitignore | 3 +- src/cli/index.ts | 90 +++++++++++++++++++++++++++ src/constants/api.ts | 2 + src/constants/params.ts | 41 ++++++++++++ src/index.ts | 28 +++++++++ src/types/downloader/tiktokApi.ts | 22 +++++++ src/types/get/getCollection.ts | 55 ++++++++++++++++ src/utils/downloader/tiktokApi.ts | 100 +++++++++++++++++++++++++++++- src/utils/get/getCollection.ts | 84 +++++++++++++++++++++++++ test/collection.ts | 85 +++++++++++++++++++++++++ 10 files changed, 507 insertions(+), 3 deletions(-) create mode 100644 src/types/get/getCollection.ts create mode 100644 src/utils/get/getCollection.ts create mode 100644 test/collection.ts diff --git a/.gitignore b/.gitignore index 7abc476..65872b4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ lib test.js bun.lockb tsconfig.tsbuildinfo -cookies.json \ No newline at end of file +cookies.json +dist \ No newline at end of file diff --git a/src/cli/index.ts b/src/cli/index.ts index e4a2858..580d333 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -392,4 +392,94 @@ program } }) +// Collection Command +program + .command("collection") + .description( + "Get videos from a TikTok collection (supports collection ID or URL)" + ) + .argument( + "", + "Collection ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/collection/name-id)" + ) + .option("-c, --cursor ", "Cursor for pagination", "0") + .option("-p, --proxy ", "Proxy URL (http/https/socks)") + .action(async (collectionIdOrUrl, options) => { + try { + Logger.info(`Fetching collection...`) + const results = await Tiktok.Collection(collectionIdOrUrl, { + cursor: options.cursor, + proxy: options.proxy + }) + + if (results.status === "success" && results.result) { + const { itemList, hasMore, cursor } = results.result + + Logger.info(`Found ${itemList.length} videos in collection`) + Logger.info(`Has more videos: ${hasMore}`) + Logger.info(`Next cursor: ${cursor}\n`) + + for (const [index, video] of itemList.entries()) { + Logger.info(`---- VIDEO ${index + 1} ----`) + Logger.result(`Video ID: ${video.id}`, chalk.green) + Logger.result(`Description: ${video.desc}`, chalk.yellow) + Logger.result( + `Author: ${video.author?.nickname || "Unknown"}`, + chalk.yellow + ) + Logger.result( + `Created: ${new Date(video.createTime * 1000).toLocaleString()}`, + chalk.yellow + ) + + if (video.statistics) { + Logger.info(`---- STATISTICS ----`) + Logger.result( + `Likes: ${video.statistics.likeCount || 0}`, + chalk.yellow + ) + Logger.result( + `Comments: ${video.statistics.commentCount || 0}`, + chalk.yellow + ) + Logger.result( + `Shares: ${video.statistics.shareCount || 0}`, + chalk.yellow + ) + Logger.result( + `Plays: ${video.statistics.playCount || 0}`, + chalk.yellow + ) + } + + if (video.video) { + Logger.info(`---- VIDEO URLs ----`) + const videoUrl = `${_tiktokurl}/@${ + video.author?.uniqueId || "unknown" + }/video/${video.id}` + Logger.result(`Video URL: ${videoUrl}`, chalk.blue) + } + + if (video.textExtra?.length > 0) { + Logger.info(`---- HASHTAGS ----`) + video.textExtra.forEach((tag) => { + if (tag.hashtagName) { + Logger.result(`#${tag.hashtagName}`, chalk.cyan) + } + }) + } + } + + if (hasMore) { + Logger.info("\nTo fetch more videos, use:") + Logger.info(`tiktokdl collection ${collectionIdOrUrl} -c ${cursor}`) + } + } else { + Logger.error(`Error: ${results.message}`) + } + } catch (error) { + Logger.error(`Error: ${error.message}`) + } + }) + program.parse() diff --git a/src/constants/api.ts b/src/constants/api.ts index 81bdde4..379e1f4 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -12,6 +12,8 @@ export const _tiktokGetComments = (params: any): string => `${_tiktokurl}/api/comment/list/?${params}` export const _tiktokGetUserLiked = (params: any): string => `${_tiktokurl}/api/favorite/item_list/?${params}` +export const _tiktokGetCollection = (params: any): string => + `${_tiktokurl}/api/collection/item_list/?${params}` /** Tiktokv */ export const _tiktokvApi: string = `https://api16-normal-useast5.tiktokv.us` diff --git a/src/constants/params.ts b/src/constants/params.ts index af21d8b..9fff962 100644 --- a/src/constants/params.ts +++ b/src/constants/params.ts @@ -353,4 +353,45 @@ const generateOdinId = () => { return `${prefix}${random}` } +export const _getCollectionParams = (collectionId: string, cursor: string = "0") => { + return qs.stringify({ + WebIdLastTime: 1741246176, + aid: 1988, + app_language: "en", + app_name: "tiktok_web", + browser_language: "en-US", + 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", + channel: "tiktok_web", + clientABVersions: "70508271,72437276,73356773,73406215,73464037,73558921,73561312,73563784,73607175,73713381,73720541,73737112,73759867,73768252,73787023,73810364,73810951,73811265,73812969,73815488,73815490,73817289,73821742,73849114,73855857,73858886,73858985,73867894,73880997,73902810,70138197,70156809,70405643,71057832,71200802,71381811,71516509,71803300,71962127,72360691,72408100,72854054,72892778,73004916,73171280,73208420,73574728,73628214", + collectionId, + cookie_enabled: true, + count: 30, + cursor, + data_collection_enabled: true, + device_id: "7478595310673266194", + device_platform: "web_pc", + focus_state: true, + from_page: "user", + history_len: 3, + is_fullscreen: false, + is_page_visible: true, + language: "en", + odinId: "7458943931621032978", + os: "windows", + priority_region: "NZ", + referer: "", + region: "NZ", + screen_height: 1440, + screen_width: 2560, + sourceType: 113, + tz_name: "Pacific/Auckland", + user_is_login: true, + verifyFp: "verify_mb1zbd2f_sMPZ5W5a_A3yc_4dmk_8NT3_kp4HJQOdrhp5", + webcast_language: "en" + }) +} + export { randomChar, generateSearchId, generateDeviceId, generateOdinId } diff --git a/src/index.ts b/src/index.ts index 24becf0..5f574f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,6 +18,7 @@ import { TiktokStalkUserResponse } from "./types/get/getProfile" import { TiktokVideoCommentsResponse } from "./types/get/getComments" import { TiktokUserPostsResponse } from "./types/get/getUserPosts" import { TiktokUserFavoriteVideosResponse } from "./types/get/getUserLiked" +import { TiktokCollectionResponse } from "./types/get/getCollection" /** Services */ import { TiktokAPI } from "./utils/downloader/tiktokApi" @@ -30,6 +31,8 @@ import { getComments } from "./utils/get/getComments" 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" @@ -122,6 +125,31 @@ 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.cursor] - Optional cursor for pagination + * @returns {Promise} + */ + Collection: async ( + collectionIdOrUrl: string, + options?: { + proxy?: string + cursor?: string + } + ): Promise => { + const collectionId = extractCollectionId(collectionIdOrUrl) + if (!collectionId) { + return { + status: "error", + message: "Invalid collection ID or URL format" + } + } + return await getCollection(collectionId, options?.proxy, options?.cursor) + }, + /** * Tiktok Search * @param {string} keyword - The query you want to search diff --git a/src/types/downloader/tiktokApi.ts b/src/types/downloader/tiktokApi.ts index c3d6ff9..f096f04 100644 --- a/src/types/downloader/tiktokApi.ts +++ b/src/types/downloader/tiktokApi.ts @@ -12,8 +12,10 @@ export type TiktokAPIResponse = BaseContentResponse export type AuthorTiktokAPI = Author & { uid: string username: string + uniqueId: string avatarThumb: string avatarMedium: string + url: string } export type StatisticsTiktokAPI = Statistics @@ -30,3 +32,23 @@ export type ResponseParserTiktokAPI = { author?: AuthorTiktokAPI music?: MusicTiktokAPI } + +export type TiktokCollectionResponse = { + status: "success" | "error" + message?: string + result?: { + itemList: Array<{ + id: string + desc: string + createTime: number + author: AuthorTiktokAPI + statistics: StatisticsTiktokAPI + video: VideoTiktokAPI + textExtra: Array<{ + hashtagName?: string + }> + }> + hasMore: boolean + cursor: string + } +} diff --git a/src/types/get/getCollection.ts b/src/types/get/getCollection.ts new file mode 100644 index 0000000..65ad7d6 --- /dev/null +++ b/src/types/get/getCollection.ts @@ -0,0 +1,55 @@ +import { AuthorTiktokAPI, StatisticsTiktokAPI, MusicTiktokAPI, VideoTiktokAPI } from "../downloader/tiktokApi" + +export interface CollectionItem { + id: string + desc: string + createTime: number + author: AuthorTiktokAPI + statistics: StatisticsTiktokAPI + 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 TiktokCollectionResponse { + status: "success" | "error" + message?: string + result?: { + cursor: string + hasMore: boolean + itemList: CollectionItem[] + extra?: { + fatal_item_ids: string[] + logid: string + now: number + } + } +} \ No newline at end of file diff --git a/src/utils/downloader/tiktokApi.ts b/src/utils/downloader/tiktokApi.ts index c4d6c1e..01d8cf2 100644 --- a/src/utils/downloader/tiktokApi.ts +++ b/src/utils/downloader/tiktokApi.ts @@ -1,14 +1,15 @@ import Axios from "axios" import asyncRetry from "async-retry" import { _tiktokvFeed, _tiktokurl } from "../../constants/api" -import { _tiktokApiParams } from "../../constants/params" +import { _tiktokApiParams, _getCollectionParams } from "../../constants/params" import { AuthorTiktokAPI, TiktokAPIResponse, StatisticsTiktokAPI, MusicTiktokAPI, ResponseParserTiktokAPI, - VideoTiktokAPI + VideoTiktokAPI, + TiktokCollectionResponse } from "../../types/downloader/tiktokApi" import { HttpsProxyAgent } from "https-proxy-agent" import { SocksProxyAgent } from "socks-proxy-agent" @@ -19,6 +20,7 @@ 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+)/ /** Types */ interface ProxyConfig { @@ -61,6 +63,7 @@ const parseStatistics = (content: any): StatisticsTiktokAPI => ({ const parseAuthor = (content: any): AuthorTiktokAPI => ({ uid: content.author.uid, username: content.author.unique_id, + uniqueId: content.author.unique_id, nickname: content.author.nickname, signature: content.author.signature, region: content.author.region, @@ -194,6 +197,17 @@ const createVideoResponse = ( } }) +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 +} + /** * Tiktok API Downloader * @param {string} url - Tiktok URL @@ -264,3 +278,85 @@ export const TiktokAPI = async ( } } } + +export const Collection = async ( + collectionIdOrUrl: string, + options?: { + cursor?: string + proxy?: string + } +): Promise => { + try { + const collectionId = extractCollectionId(collectionIdOrUrl) + if (!collectionId) { + return { + status: "error", + message: "Invalid collection ID or URL format" + } + } + + const response = await Axios( + _tiktokvFeed(_getCollectionParams(collectionId, options?.cursor)), + { + method: "OPTIONS", + headers: { "User-Agent": USER_AGENT }, + ...createProxyAgent(options?.proxy) + } + ) + + if (response.data && response.data.status_code === 0) { + const data = response.data + const itemList = data.aweme_list.map((item: any) => ({ + id: item.aweme_id, + desc: item.desc, + createTime: item.create_time, + author: { + uid: item.author.uid, + username: item.author.unique_id, + uniqueId: item.author.unique_id, + nickname: item.author.nickname, + signature: item.author.signature, + region: item.author.region, + avatarThumb: item.author?.avatar_thumb?.url_list || [], + avatarMedium: item.author?.avatar_medium?.url_list || [], + url: `${_tiktokurl}/@${item.author.unique_id}` + }, + statistics: { + likeCount: item.statistics.digg_count, + commentCount: item.statistics.comment_count, + shareCount: item.statistics.share_count, + playCount: item.statistics.play_count + }, + video: { + ratio: item.video.ratio, + duration: item.video.duration, + playAddr: item.video?.play_addr?.url_list || [], + downloadAddr: item.video?.download_addr?.url_list || [], + cover: item.video?.cover?.url_list || [], + dynamicCover: item.video?.dynamic_cover?.url_list || [], + originCover: item.video?.origin_cover?.url_list || [] + }, + textExtra: item.text_extra || [] + })) + + return { + status: "success", + result: { + itemList, + hasMore: data.has_more, + cursor: data.cursor + } + } + } + + 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 new file mode 100644 index 0000000..32cfbc9 --- /dev/null +++ b/src/utils/get/getCollection.ts @@ -0,0 +1,84 @@ +import Axios from "axios" +import { _tiktokGetCollection } from "../../constants/api" +import { _getCollectionParams } from "../../constants/params" +import { HttpsProxyAgent } from "https-proxy-agent" +import { SocksProxyAgent } from "socks-proxy-agent" +import { TiktokCollectionResponse } from "../../types/get/getCollection" +import { ERROR_MESSAGES } from "../../constants" +import retry from "async-retry" + +/** Types */ +interface ProxyConfig { + httpsAgent?: HttpsProxyAgent | 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} cursor - Cursor for pagination (optional) + * @returns {Promise} + */ +export const getCollection = async ( + collectionId: string, + proxy?: string, + cursor: string = "0" +): Promise => { + try { + const response = await retry( + async () => { + const res = await Axios(_tiktokGetCollection(_getCollectionParams(collectionId, cursor)), { + 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(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: { + cursor: response.cursor, + hasMore: response.hasMore, + itemList: response.itemList, + extra: response.extra + } + } + } catch (error) { + return { + status: "error", + message: error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR + } + } +} \ No newline at end of file diff --git a/test/collection.ts b/test/collection.ts new file mode 100644 index 0000000..63b7e14 --- /dev/null +++ b/test/collection.ts @@ -0,0 +1,85 @@ +import Tiktok from "../src" + +async function testCollection() { + try { + // Test collection ID from your example + const collectionId = "7507916135931218695" + + console.log("Fetching collection...") + const collection = await Tiktok.Collection(collectionId, { + cursor: "0" // Optional: For pagination + }) + + console.log(collection) + + if (collection.status === "success" && collection.result) { + const { itemList, hasMore, cursor } = collection.result + + console.log(`\nFound ${itemList.length} videos in collection`) + console.log(`Has more videos: ${hasMore}`) + console.log(`Next cursor: ${cursor}\n`) + + // Print details of first video + if (itemList.length > 0) { + const firstVideo = itemList[0] + console.log("First video details:") + console.log("-------------------") + console.log(`Description: ${firstVideo.desc}`) + console.log(`Author: ${firstVideo.author?.nickname || 'Unknown'}`) + console.log( + `Created: ${new Date(firstVideo.createTime * 1000).toLocaleString()}` + ) + + // Print statistics if available + if (firstVideo.statistics) { + console.log("\nStatistics:") + console.log(`- Likes: ${firstVideo.statistics.likeCount || 0}`) + console.log(`- Comments: ${firstVideo.statistics.commentCount || 0}`) + console.log(`- Shares: ${firstVideo.statistics.shareCount || 0}`) + console.log(`- Plays: ${firstVideo.statistics.playCount || 0}`) + } + + // Print video URLs if available + if (firstVideo.video) { + console.log("\nVideo URLs:") + if (firstVideo.video.playAddr?.[0]) { + console.log(`- Play URL: ${firstVideo.video.playAddr[0]}`) + } + if (firstVideo.video.downloadAddr?.[0]) { + console.log(`- Download URL: ${firstVideo.video.downloadAddr[0]}`) + } + } + + // Print hashtags if available + if (firstVideo.textExtra?.length > 0) { + console.log("\nHashtags:") + firstVideo.textExtra.forEach((tag) => { + if (tag.hashtagName) { + console.log(`- #${tag.hashtagName}`) + } + }) + } + } + + // If there are more videos, you can fetch the next page + if (hasMore) { + console.log("\nFetching next page...") + const nextPage = await Tiktok.Collection(collectionId, { + proxy: "http://your-proxy-url", // Optional: Add your proxy if needed + cursor: cursor + }) + + if (nextPage.status === "success" && nextPage.result) { + console.log(`Found ${nextPage.result.itemList.length} more videos`) + } + } + } else { + console.error("Error:", collection.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +// Run the test +testCollection() From a37640e332a43827bca8599881c931097d07256e Mon Sep 17 00:00:00 2001 From: Tino Laomahei Date: Sat, 24 May 2025 22:06:08 +1200 Subject: [PATCH 2/6] add readme + support count --- README.md | 94 ++++++++++++++++++++++++++++++++++ src/cli/index.ts | 11 +++- src/constants/params.ts | 11 ++-- src/index.ts | 4 +- src/utils/get/getCollection.ts | 37 ++++++++----- 5 files changed, 134 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d017ec2..5fe7e4f 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,100 @@ Tiktok.GetUserLiked(username, { - [Tiktok User Liked Videos Response](#tiktok-user-liked-videos) +## Tiktok Collection + +Get videos from a TikTok collection (supports collection ID or URL) + +```javascript +const Tiktok = require("@tobyg74/tiktok-api-dl") + +// Using collection ID +const collectionId = "7507916135931218695" +Tiktok.Collection(collectionId, { + cursor: "0", // optional, default is "0" + count: 5, // optional, default is 5 + proxy: "YOUR_PROXY" // optional +}).then((result) => console.log(result)) + +// Using collection URL +const collectionUrl = "https://www.tiktok.com/@username/collection/name-id" +Tiktok.Collection(collectionUrl, { + cursor: "0", + count: 5, + proxy: "YOUR_PROXY" +}).then((result) => console.log(result)) +``` + +### CLI Usage + +```bash +# Using collection ID +tiktokdl collection 7507916135931218695 -n 5 + +# Using collection URL +tiktokdl collection "https://www.tiktok.com/@username/collection/name-id" -n 5 + +# With cursor for pagination +tiktokdl collection 7507916135931218695 -c 5 -n 5 + +# With proxy +tiktokdl collection 7507916135931218695 -n 5 -p "http://your-proxy-url" +``` + +### Response Type + +```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 + cursor: string + } +} +``` + # API Response Types ## Tiktok Downloader diff --git a/src/cli/index.ts b/src/cli/index.ts index 580d333..c4e88ea 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -404,12 +404,19 @@ program ) .option("-c, --cursor ", "Cursor for pagination", "0") .option("-p, --proxy ", "Proxy URL (http/https/socks)") + .option( + "-n, --count ", + "Number of items to fetch", + (val) => parseInt(val), + 5 + ) .action(async (collectionIdOrUrl, options) => { try { - Logger.info(`Fetching collection...`) + Logger.info(`Fetching collection... (count: ${options.count})`) const results = await Tiktok.Collection(collectionIdOrUrl, { cursor: options.cursor, - proxy: options.proxy + proxy: options.proxy, + count: options.count }) if (results.status === "success" && results.result) { diff --git a/src/constants/params.ts b/src/constants/params.ts index 9fff962..53b622b 100644 --- a/src/constants/params.ts +++ b/src/constants/params.ts @@ -353,9 +353,9 @@ const generateOdinId = () => { return `${prefix}${random}` } -export const _getCollectionParams = (collectionId: string, cursor: string = "0") => { +export const _getCollectionParams = (collectionId: string, cursor: string = "0", count: number = 5) => { return qs.stringify({ - WebIdLastTime: 1741246176, + WebIdLastTime: Date.now(), aid: 1988, app_language: "en", app_name: "tiktok_web", @@ -365,13 +365,12 @@ export const _getCollectionParams = (collectionId: string, cursor: string = "0") 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", channel: "tiktok_web", - clientABVersions: "70508271,72437276,73356773,73406215,73464037,73558921,73561312,73563784,73607175,73713381,73720541,73737112,73759867,73768252,73787023,73810364,73810951,73811265,73812969,73815488,73815490,73817289,73821742,73849114,73855857,73858886,73858985,73867894,73880997,73902810,70138197,70156809,70405643,71057832,71200802,71381811,71516509,71803300,71962127,72360691,72408100,72854054,72892778,73004916,73171280,73208420,73574728,73628214", collectionId, cookie_enabled: true, - count: 30, + count, cursor, data_collection_enabled: true, - device_id: "7478595310673266194", + device_id: "7002566096994190854", device_platform: "web_pc", focus_state: true, from_page: "user", @@ -389,7 +388,7 @@ export const _getCollectionParams = (collectionId: string, cursor: string = "0") sourceType: 113, tz_name: "Pacific/Auckland", user_is_login: true, - verifyFp: "verify_mb1zbd2f_sMPZ5W5a_A3yc_4dmk_8NT3_kp4HJQOdrhp5", + verifyFp: "verify_lacphy8d_z2ux9idt_xdmu_4gKb_9nng_NNTTTvsFS8ao", webcast_language: "en" }) } diff --git a/src/index.ts b/src/index.ts index 5f574f2..052acb4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -131,6 +131,7 @@ export = { * @param {Object} options - The options for collection * @param {string} [options.proxy] - Optional proxy URL * @param {string} [options.cursor] - Optional cursor for pagination + * @param {number} [options.count] - Optional number of items to fetch * @returns {Promise} */ Collection: async ( @@ -138,6 +139,7 @@ export = { options?: { proxy?: string cursor?: string + count?: number } ): Promise => { const collectionId = extractCollectionId(collectionIdOrUrl) @@ -147,7 +149,7 @@ export = { message: "Invalid collection ID or URL format" } } - return await getCollection(collectionId, options?.proxy, options?.cursor) + return await getCollection(collectionId, options?.proxy, options?.cursor, options?.count) }, /** diff --git a/src/utils/get/getCollection.ts b/src/utils/get/getCollection.ts index 32cfbc9..d9df375 100644 --- a/src/utils/get/getCollection.ts +++ b/src/utils/get/getCollection.ts @@ -31,27 +31,35 @@ const createProxyAgent = (proxy?: string): ProxyConfig => { * @param {string} collectionId - Collection ID * @param {string} proxy - Your Proxy (optional) * @param {string} cursor - Cursor for pagination (optional) + * @param {number} count - Number of items to fetch (optional) * @returns {Promise} */ export const getCollection = async ( collectionId: string, proxy?: string, - cursor: string = "0" + cursor: string = "0", + count: number = 5 ): Promise => { try { const response = await retry( async () => { - const res = await Axios(_tiktokGetCollection(_getCollectionParams(collectionId, cursor)), { - 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(proxy) - }) + const res = await Axios( + _tiktokGetCollection( + _getCollectionParams(collectionId, cursor, 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(proxy) + } + ) if (res.data && res.data.statusCode === 0) { return res.data @@ -78,7 +86,8 @@ export const getCollection = 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 } } -} \ No newline at end of file +} From baa8fa2cc8d1bcc7aabc7fdef5a93677fed10be5 Mon Sep 17 00:00:00 2001 From: Tino Laomahei Date: Sat, 24 May 2025 22:08:44 +1200 Subject: [PATCH 3/6] add api response type --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/README.md b/README.md index 5fe7e4f..87af2f3 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ - [Tiktok Video Comments](#tiktok-video-comments-1) - [Tiktok User Posts](#tiktok-user-posts) - [Tiktok User Liked Videos](#tiktok-user-liked-videos) + - [Tiktok Collection](#tiktok-collection) - [Contributing](#contributing) - [License](#license) @@ -806,6 +807,62 @@ interface TiktokUserFavoriteVideosResponse { } ``` +## Tiktok Collection + +### Collection Response + +```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 + cursor: string + } +} +``` + # Changelog - All changes will be documented in the [CHANGELOG.md](https://github.com/TobyG74/tiktok-api-dl/blob/master/CHANGELOG.md) file. From 004e74d3388364f7038decec7c21b6586474c028 Mon Sep 17 00:00:00 2001 From: Tino Laomahei Date: Sun, 25 May 2025 15:35:42 +1200 Subject: [PATCH 4/6] use page instead of cursor --- src/cli/index.ts | 18 +++---- src/constants/params.ts | 9 +++- src/index.ts | 6 +-- src/types/downloader/tiktokApi.ts | 1 - src/types/get/getCollection.ts | 1 - src/utils/downloader/tiktokApi.ts | 59 +++++++-------------- src/utils/get/getCollection.ts | 9 ++-- test/collection-test.ts | 69 +++++++++++++++++++++++++ test/collection.ts | 85 ------------------------------- 9 files changed, 109 insertions(+), 148 deletions(-) create mode 100644 test/collection-test.ts delete mode 100644 test/collection.ts diff --git a/src/cli/index.ts b/src/cli/index.ts index c4e88ea..4da69ef 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -402,8 +402,8 @@ program "", "Collection ID or URL (e.g. 7507916135931218695 or https://www.tiktok.com/@username/collection/name-id)" ) - .option("-c, --cursor ", "Cursor for pagination", "0") - .option("-p, --proxy ", "Proxy URL (http/https/socks)") + .option("-p, --page ", "Page number", "1") + .option("--proxy ", "Proxy URL (http/https/socks)") .option( "-n, --count ", "Number of items to fetch", @@ -412,19 +412,18 @@ program ) .action(async (collectionIdOrUrl, options) => { try { - Logger.info(`Fetching collection... (count: ${options.count})`) + Logger.info(`Fetching page ${options.page} with ${options.count} items per page from collection...`) const results = await Tiktok.Collection(collectionIdOrUrl, { - cursor: options.cursor, + page: options.page, proxy: options.proxy, count: options.count }) if (results.status === "success" && results.result) { - const { itemList, hasMore, cursor } = results.result + const { itemList, hasMore } = results.result Logger.info(`Found ${itemList.length} videos in collection`) Logger.info(`Has more videos: ${hasMore}`) - Logger.info(`Next cursor: ${cursor}\n`) for (const [index, video] of itemList.entries()) { Logger.info(`---- VIDEO ${index + 1} ----`) @@ -461,9 +460,8 @@ program if (video.video) { Logger.info(`---- VIDEO URLs ----`) - const videoUrl = `${_tiktokurl}/@${ - video.author?.uniqueId || "unknown" - }/video/${video.id}` + const videoUrl = `${_tiktokurl}/@${video.author?.uniqueId || "unknown" + }/video/${video.id}` Logger.result(`Video URL: ${videoUrl}`, chalk.blue) } @@ -479,7 +477,7 @@ program if (hasMore) { Logger.info("\nTo fetch more videos, use:") - Logger.info(`tiktokdl collection ${collectionIdOrUrl} -c ${cursor}`) + Logger.info(`tiktokdl collection ${collectionIdOrUrl} -p ${parseInt(options.page) + 1}`) } } else { Logger.error(`Error: ${results.message}`) diff --git a/src/constants/params.ts b/src/constants/params.ts index 53b622b..8203b0d 100644 --- a/src/constants/params.ts +++ b/src/constants/params.ts @@ -353,7 +353,12 @@ const generateOdinId = () => { return `${prefix}${random}` } -export const _getCollectionParams = (collectionId: string, cursor: string = "0", count: number = 5) => { +export const _getCollectionParams = (collectionId: string, page: number = 1, count: number = 5) => { + let cursor = 0 + if (page > 0) { + cursor = (page - 1) * count + } + return qs.stringify({ WebIdLastTime: Date.now(), aid: 1988, @@ -368,7 +373,7 @@ export const _getCollectionParams = (collectionId: string, cursor: string = "0", collectionId, cookie_enabled: true, count, - cursor, + cursor: cursor.toString(), data_collection_enabled: true, device_id: "7002566096994190854", device_platform: "web_pc", diff --git a/src/index.ts b/src/index.ts index 052acb4..8fe6d28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -130,7 +130,7 @@ export = { * @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.cursor] - Optional cursor for pagination + * @param {string} [options.page] - Optional page for pagination * @param {number} [options.count] - Optional number of items to fetch * @returns {Promise} */ @@ -138,7 +138,7 @@ export = { collectionIdOrUrl: string, options?: { proxy?: string - cursor?: string + page?: number count?: number } ): Promise => { @@ -149,7 +149,7 @@ export = { message: "Invalid collection ID or URL format" } } - return await getCollection(collectionId, options?.proxy, options?.cursor, options?.count) + return await getCollection(collectionId, options?.proxy, options?.page, options?.count) }, /** diff --git a/src/types/downloader/tiktokApi.ts b/src/types/downloader/tiktokApi.ts index f096f04..cb56fec 100644 --- a/src/types/downloader/tiktokApi.ts +++ b/src/types/downloader/tiktokApi.ts @@ -49,6 +49,5 @@ export type TiktokCollectionResponse = { }> }> hasMore: boolean - cursor: string } } diff --git a/src/types/get/getCollection.ts b/src/types/get/getCollection.ts index 65ad7d6..fe944b5 100644 --- a/src/types/get/getCollection.ts +++ b/src/types/get/getCollection.ts @@ -43,7 +43,6 @@ export interface TiktokCollectionResponse { status: "success" | "error" message?: string result?: { - cursor: string hasMore: boolean itemList: CollectionItem[] extra?: { diff --git a/src/utils/downloader/tiktokApi.ts b/src/utils/downloader/tiktokApi.ts index 01d8cf2..53507df 100644 --- a/src/utils/downloader/tiktokApi.ts +++ b/src/utils/downloader/tiktokApi.ts @@ -1,6 +1,6 @@ import Axios from "axios" import asyncRetry from "async-retry" -import { _tiktokvFeed, _tiktokurl } from "../../constants/api" +import { _tiktokvFeed, _tiktokurl, _tiktokGetCollection } from "../../constants/api" import { _tiktokApiParams, _getCollectionParams } from "../../constants/params" import { AuthorTiktokAPI, @@ -282,8 +282,9 @@ export const TiktokAPI = async ( export const Collection = async ( collectionIdOrUrl: string, options?: { - cursor?: string - proxy?: string + page?: number, + proxy?: string, + count?: number } ): Promise => { try { @@ -296,55 +297,31 @@ export const Collection = async ( } const response = await Axios( - _tiktokvFeed(_getCollectionParams(collectionId, options?.cursor)), + _tiktokGetCollection( + _getCollectionParams(collectionId, options.page, options.count) + ), { - method: "OPTIONS", - headers: { "User-Agent": USER_AGENT }, + 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 - const itemList = data.aweme_list.map((item: any) => ({ - id: item.aweme_id, - desc: item.desc, - createTime: item.create_time, - author: { - uid: item.author.uid, - username: item.author.unique_id, - uniqueId: item.author.unique_id, - nickname: item.author.nickname, - signature: item.author.signature, - region: item.author.region, - avatarThumb: item.author?.avatar_thumb?.url_list || [], - avatarMedium: item.author?.avatar_medium?.url_list || [], - url: `${_tiktokurl}/@${item.author.unique_id}` - }, - statistics: { - likeCount: item.statistics.digg_count, - commentCount: item.statistics.comment_count, - shareCount: item.statistics.share_count, - playCount: item.statistics.play_count - }, - video: { - ratio: item.video.ratio, - duration: item.video.duration, - playAddr: item.video?.play_addr?.url_list || [], - downloadAddr: item.video?.download_addr?.url_list || [], - cover: item.video?.cover?.url_list || [], - dynamicCover: item.video?.dynamic_cover?.url_list || [], - originCover: item.video?.origin_cover?.url_list || [] - }, - textExtra: item.text_extra || [] - })) return { status: "success", result: { - itemList, - hasMore: data.has_more, - cursor: data.cursor + itemList: data.itemList || [], + hasMore: data.hasMore } } } diff --git a/src/utils/get/getCollection.ts b/src/utils/get/getCollection.ts index d9df375..6fe7858 100644 --- a/src/utils/get/getCollection.ts +++ b/src/utils/get/getCollection.ts @@ -30,14 +30,14 @@ const createProxyAgent = (proxy?: string): ProxyConfig => { * Get TikTok Collection * @param {string} collectionId - Collection ID * @param {string} proxy - Your Proxy (optional) - * @param {string} cursor - Cursor for pagination (optional) + * @param {string} page - Page for pagination (optional) * @param {number} count - Number of items to fetch (optional) * @returns {Promise} */ export const getCollection = async ( collectionId: string, proxy?: string, - cursor: string = "0", + page: number = 1, count: number = 5 ): Promise => { try { @@ -45,7 +45,7 @@ export const getCollection = async ( async () => { const res = await Axios( _tiktokGetCollection( - _getCollectionParams(collectionId, cursor, count) + _getCollectionParams(collectionId, page, count) ), { method: "GET", @@ -77,9 +77,8 @@ export const getCollection = async ( return { status: "success", result: { - cursor: response.cursor, hasMore: response.hasMore, - itemList: response.itemList, + itemList: response.itemList || [], extra: response.extra } } diff --git a/test/collection-test.ts b/test/collection-test.ts new file mode 100644 index 0000000..056efc1 --- /dev/null +++ b/test/collection-test.ts @@ -0,0 +1,69 @@ +import { Collection } from "../src/utils/downloader/tiktokApi" + +async function testCollection() { + try { + // You can use either a collection ID or URL + const collectionIdOrUrl = "https://www.tiktok.com/@getrex.co.nz/collection/big%20back-7507916135931218695" + + console.log("Testing Collection method...") + const result = await Collection(collectionIdOrUrl, { + page: 2, + count: 5, // Optional: Number of items to fetch + proxy: undefined // Optional: Add your proxy if needed + }) + + if (result.status === "success" && result.result) { + console.log("\nCollection fetched successfully!") + console.log("========================") + console.log("Collection Overview:") + console.log("========================") + console.log(`Collection ID: ${collectionIdOrUrl}`) + console.log(`Total items fetched: ${result.result.itemList.length}`) + console.log(`Has more items: ${result.result.hasMore}`) + + // Log all items + result.result.itemList.forEach((item, index) => { + console.log(`\nItem ${index + 1}:`) + console.log("-------------------") + 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()}`) + + // Log video URL + if (item.video?.playAddr?.[0]) { + console.log(`Video URL: ${item.video.playAddr[0]}`) + } else { + console.log("No video URL available") + } + + // Log item statistics + if (item.statistics) { + console.log("\nStatistics:") + console.log(`- Likes: ${item.statistics.likeCount || 0}`) + console.log(`- Comments: ${item.statistics.commentCount || 0}`) + console.log(`- Shares: ${item.statistics.shareCount || 0}`) + console.log(`- Plays: ${item.statistics.playCount || 0}`) + } + + // Log hashtags if available + if (item.textExtra?.length > 0) { + console.log("\nHashtags:") + item.textExtra.forEach(tag => { + if (tag.hashtagName) { + console.log(`- #${tag.hashtagName}`) + } + }) + } + console.log("========================") + }) + } else { + console.error("Error:", result.message) + } + } catch (error) { + console.error("Test failed:", error) + } +} + +// Run the test +testCollection() \ No newline at end of file diff --git a/test/collection.ts b/test/collection.ts deleted file mode 100644 index 63b7e14..0000000 --- a/test/collection.ts +++ /dev/null @@ -1,85 +0,0 @@ -import Tiktok from "../src" - -async function testCollection() { - try { - // Test collection ID from your example - const collectionId = "7507916135931218695" - - console.log("Fetching collection...") - const collection = await Tiktok.Collection(collectionId, { - cursor: "0" // Optional: For pagination - }) - - console.log(collection) - - if (collection.status === "success" && collection.result) { - const { itemList, hasMore, cursor } = collection.result - - console.log(`\nFound ${itemList.length} videos in collection`) - console.log(`Has more videos: ${hasMore}`) - console.log(`Next cursor: ${cursor}\n`) - - // Print details of first video - if (itemList.length > 0) { - const firstVideo = itemList[0] - console.log("First video details:") - console.log("-------------------") - console.log(`Description: ${firstVideo.desc}`) - console.log(`Author: ${firstVideo.author?.nickname || 'Unknown'}`) - console.log( - `Created: ${new Date(firstVideo.createTime * 1000).toLocaleString()}` - ) - - // Print statistics if available - if (firstVideo.statistics) { - console.log("\nStatistics:") - console.log(`- Likes: ${firstVideo.statistics.likeCount || 0}`) - console.log(`- Comments: ${firstVideo.statistics.commentCount || 0}`) - console.log(`- Shares: ${firstVideo.statistics.shareCount || 0}`) - console.log(`- Plays: ${firstVideo.statistics.playCount || 0}`) - } - - // Print video URLs if available - if (firstVideo.video) { - console.log("\nVideo URLs:") - if (firstVideo.video.playAddr?.[0]) { - console.log(`- Play URL: ${firstVideo.video.playAddr[0]}`) - } - if (firstVideo.video.downloadAddr?.[0]) { - console.log(`- Download URL: ${firstVideo.video.downloadAddr[0]}`) - } - } - - // Print hashtags if available - if (firstVideo.textExtra?.length > 0) { - console.log("\nHashtags:") - firstVideo.textExtra.forEach((tag) => { - if (tag.hashtagName) { - console.log(`- #${tag.hashtagName}`) - } - }) - } - } - - // If there are more videos, you can fetch the next page - if (hasMore) { - console.log("\nFetching next page...") - const nextPage = await Tiktok.Collection(collectionId, { - proxy: "http://your-proxy-url", // Optional: Add your proxy if needed - cursor: cursor - }) - - if (nextPage.status === "success" && nextPage.result) { - console.log(`Found ${nextPage.result.itemList.length} more videos`) - } - } - } else { - console.error("Error:", collection.message) - } - } catch (error) { - console.error("Test failed:", error) - } -} - -// Run the test -testCollection() From a3f1adfc88eba6e18c8e654d706c4617e19b1049 Mon Sep 17 00:00:00 2001 From: Tino Laomahei Date: Sun, 25 May 2025 15:40:01 +1200 Subject: [PATCH 5/6] update docs --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 87af2f3..98b98e1 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,7 @@ const Tiktok = require("@tobyg74/tiktok-api-dl") // Using collection ID const collectionId = "7507916135931218695" Tiktok.Collection(collectionId, { - cursor: "0", // optional, default is "0" + page: 1, // optional, default is 1 count: 5, // optional, default is 5 proxy: "YOUR_PROXY" // optional }).then((result) => console.log(result)) @@ -279,7 +279,7 @@ Tiktok.Collection(collectionId, { // Using collection URL const collectionUrl = "https://www.tiktok.com/@username/collection/name-id" Tiktok.Collection(collectionUrl, { - cursor: "0", + page: 1, count: 5, proxy: "YOUR_PROXY" }).then((result) => console.log(result)) @@ -294,11 +294,11 @@ tiktokdl collection 7507916135931218695 -n 5 # Using collection URL tiktokdl collection "https://www.tiktok.com/@username/collection/name-id" -n 5 -# With cursor for pagination -tiktokdl collection 7507916135931218695 -c 5 -n 5 +# With page for pagination +tiktokdl collection 7507916135931218695 -p 1 -n 5 # With proxy -tiktokdl collection 7507916135931218695 -n 5 -p "http://your-proxy-url" +tiktokdl collection 7507916135931218695 -n 5 -proxy "http://your-proxy-url" ``` ### Response Type @@ -350,7 +350,6 @@ interface TiktokCollectionResponse { }> }> hasMore: boolean - cursor: string } } ``` From a39167b8ab52a39013d14b43e43157454bc50717 Mon Sep 17 00:00:00 2001 From: Tino Laomahei Date: Sun, 25 May 2025 15:56:24 +1200 Subject: [PATCH 6/6] handle shareable links --- src/utils/downloader/tiktokApi.ts | 27 ++++++++++++++++++++++++++- test/collection-test.ts | 11 ++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/utils/downloader/tiktokApi.ts b/src/utils/downloader/tiktokApi.ts index 53507df..5bbea07 100644 --- a/src/utils/downloader/tiktokApi.ts +++ b/src/utils/downloader/tiktokApi.ts @@ -197,6 +197,26 @@ const createVideoResponse = ( } }) +const handleRedirect = async (url: string, proxy?: string): Promise => { + try { + const response = await Axios(url, { + method: 'HEAD', + maxRedirects: 5, + validateStatus: (status) => status >= 200 && status < 400, + ...createProxyAgent(proxy) + }) + + // Get the final URL after all redirects + const finalUrl = response.request.res.responseUrl + + // Remove query parameters + return finalUrl.split('?')[0] + } catch (error) { + console.error('Error handling redirect:', error) + return url + } +} + export const extractCollectionId = (input: string): string | null => { // If it's already just a number, return it if (/^\d+$/.test(input)) { @@ -288,7 +308,12 @@ export const Collection = async ( } ): Promise => { try { - const collectionId = extractCollectionId(collectionIdOrUrl) + // 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", diff --git a/test/collection-test.ts b/test/collection-test.ts index 056efc1..bc8f832 100644 --- a/test/collection-test.ts +++ b/test/collection-test.ts @@ -3,11 +3,13 @@ import { Collection } from "../src/utils/downloader/tiktokApi" async function testCollection() { try { // You can use either a collection ID or URL - const collectionIdOrUrl = "https://www.tiktok.com/@getrex.co.nz/collection/big%20back-7507916135931218695" + const collectionId = "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(collectionIdOrUrl, { - page: 2, + const result = await Collection(collectionId, { + page: 1, count: 5, // Optional: Number of items to fetch proxy: undefined // Optional: Add your proxy if needed }) @@ -17,7 +19,6 @@ async function testCollection() { console.log("========================") console.log("Collection Overview:") console.log("========================") - console.log(`Collection ID: ${collectionIdOrUrl}`) console.log(`Total items fetched: ${result.result.itemList.length}`) console.log(`Has more items: ${result.result.hasMore}`) @@ -29,7 +30,7 @@ async function testCollection() { console.log(`Description: ${item.desc}`) console.log(`Author: ${item.author.nickname}`) console.log(`Created: ${new Date(item.createTime * 1000).toLocaleString()}`) - + // Log video URL if (item.video?.playAddr?.[0]) { console.log(`Video URL: ${item.video.playAddr[0]}`)