diff --git a/src/constants/api.ts b/src/constants/api.ts index c24ade0..bba1350 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -2,7 +2,8 @@ export const _tiktokurl: string = "https://www.tiktok.com" export const _tiktokSearchUserFull = (params: any): string => `${_tiktokurl}/api/search/user/full/?${params}` export const _tiktokSearchVideoFull = (params: any): string => `${_tiktokurl}/api/search/item/full/?${params}` -export const _tiktokGetPosts = (params: any) => `${_tiktokurl}/api/post/item_list/?${params}` +export const _tiktokSearchLiveFull = (params: any): string => `${_tiktokurl}/api/search/live/full/?${params}` +export const _tiktokGetPosts = (params: any): string => `${_tiktokurl}/api/post/item_list/?${params}` /** Tiktokv */ export const _tiktokvApi: string = `https://api.tiktokv.com` diff --git a/src/constants/params.ts b/src/constants/params.ts index 876e89e..0075331 100644 --- a/src/constants/params.ts +++ b/src/constants/params.ts @@ -35,7 +35,7 @@ export const _userPostsParams = () => { ) } -export const _userSearchParams = (keyword: any, page: number = 1) => { +export const _userSearchParams = (keyword: string, page: number = 1) => { let cursor = 0 for (let i = 1; i < page; i++) { cursor += 10 @@ -75,8 +75,51 @@ export const _userSearchParams = (keyword: any, page: number = 1) => { }) } +export const _liveSearchParams = (keyword: string, page: number = 1) => { + let cursor = 0 + for (let i = 1; i < page; i++) { + cursor += 12 + } + + let offset = `${cursor}` + + return new URLSearchParams({ + WebIdLastTime: "1720342268", + 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: "20", + device_id: "7388813454814086664", + device_platform: "web_pc", + device_type: "web_h264", + focus_state: "true", + from_page: "search", + history_len: "10", + is_fullscreen: "false", + is_page_visible: "true", + keyword, + offset, + os: "linux", + priority_region: "", + referer: "", + region: "ID", + screen_height: "768", + screen_width: "1366", + tz_name: "Asia/Jakarta", + web_search_code: "{ tiktok: { client_params_x: { search_engine: { ies_mt_user_live_video_card_use_libra: 1, mt_search_general_user_live_card: 1 } }, search_server: {} } }", + webcast_language: "en" + }) +} + export const _tiktokApiParams = (args: any) => { - return { + return new URLSearchParams({ ...args, version_name: "1.1.9", version_code: "2018111632", @@ -108,7 +151,7 @@ export const _tiktokApiParams = (args: any) => { ssmix: "a", as: "a1qwert123", cp: "cbfhckdckkde1" - } + }).toString() } export const _xttParams = (secUid: string, cursor: number, count: number) => { diff --git a/src/index.ts b/src/index.ts index 7957655..3257517 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,9 +13,11 @@ import { SSSTikResponse } from "./types/downloader/ssstik" import { TiktokAPIResponse } from "./types/downloader/tiktokApi" import { TiktokUserSearchResponse } from "./types/search/userSearch" import { StalkResult } from "./types/search/stalker" +import { SearchLive } from "./utils/search/liveSearch" +import { TiktokLiveSearchResponse } from "./types/search/liveSearch" type TiktokDownloaderResponse = T extends "v1" ? TiktokAPIResponse : T extends "v2" ? SSSTikResponse : T extends "v3" ? MusicalDownResponse : TiktokAPIResponse -type TiktokSearchResponse = T extends "user" ? TiktokUserSearchResponse : T extends "video" ? any : TiktokUserSearchResponse +type TiktokSearchResponse = T extends "user" ? TiktokUserSearchResponse : T extends "live" ? any : TiktokLiveSearchResponse export = { /** @@ -55,16 +57,16 @@ export = { * @param {number} options.page - The page of search (optional) * @returns {Promise} */ - Search: async (query: string, options: { type: T; cookie?: string; page?: number }): Promise> => { + Search: async (query: string, options: { type: T; cookie?: string; page?: number }): Promise> => { switch (options?.type) { case "user": { const response = await SearchUser(query, options?.cookie, options?.page) return response as TiktokSearchResponse } - // case "video": { - // const response = await SearchVideo(query) - // return response as TiktokSearchResponse - // } + case "live": { + const response = await SearchLive(query, options?.cookie, options?.page) + return response as TiktokSearchResponse + } default: { const response = await SearchUser(query, options?.cookie, options?.page) return response as TiktokSearchResponse diff --git a/src/types/search/liveSearch.ts b/src/types/search/liveSearch.ts new file mode 100644 index 0000000..0101ba1 --- /dev/null +++ b/src/types/search/liveSearch.ts @@ -0,0 +1,52 @@ +export type TiktokLiveSearchResponse = { + status: "success" | "error" + message?: string + result?: Result[] +} + +export type Result = { + roomInfo: RoomInfo + liveInfo: LiveInfo +} + +export type RoomInfo = { + hasCommerceGoods: boolean + isBattle: boolean +} + +export type LiveInfo = { + id: string + title: string + cover: string[] + squareCover: string[] + rectangleCover: string[] + liveTypeThirdParty: boolean + hashtag: string + startTime: number + stats: Stats + owner: Owner +} + +export type Stats = { + totalUser: number + viewerCount: number + likeCount: number +} + +export type Owner = { + id: string + nickname: string + username: string + signature: string + avatarThumb: string[] + avatarMedium: string[] + avatarLarge: string[] + modifyTime: number + stats: OwnerStats + isVerified: boolean +} + +export type OwnerStats = { + followingCount: number + followerCount: number +} diff --git a/src/utils/downloader/musicalDown.ts b/src/utils/downloader/musicalDown.ts index f5df3fb..1a55a75 100644 --- a/src/utils/downloader/musicalDown.ts +++ b/src/utils/downloader/musicalDown.ts @@ -11,7 +11,7 @@ import { _musicaldownapi, _musicaldownmusicapi, _musicaldownurl } from "../../co const TiktokURLregex = /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ const getRequest = (url: string) => - new Promise((resolve, reject) => { + new Promise((resolve) => { if (!TiktokURLregex.test(url)) { return resolve({ status: "error", @@ -38,7 +38,7 @@ const getRequest = (url: string) => }) const getMusic = (cookie: string) => - new Promise((resolve, reject) => { + new Promise((resolve) => { Axios.get(_musicaldownmusicapi, { headers: { cookie: cookie, @@ -61,7 +61,7 @@ const getMusic = (cookie: string) => */ export const MusicalDown = (url: string) => - new Promise(async (resolve, reject) => { + new Promise(async (resolve) => { const request: getRequest = await getRequest(url) if (request.status !== "success") return resolve({ status: "error", message: request.message }) Axios(_musicaldownapi, { diff --git a/src/utils/downloader/ssstik.ts b/src/utils/downloader/ssstik.ts index c07edad..bc1904f 100644 --- a/src/utils/downloader/ssstik.ts +++ b/src/utils/downloader/ssstik.ts @@ -12,7 +12,7 @@ import { _ssstikapi, _ssstikurl } from "../../constants/api" const TiktokURLregex = /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/ const fetchTT = () => - new Promise(async (resolve, reject) => { + new Promise(async (resolve) => { Axios.get(_ssstikurl, { headers: { "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0" diff --git a/src/utils/downloader/tiktokApi.ts b/src/utils/downloader/tiktokApi.ts index 3ee4b9c..30ce13b 100644 --- a/src/utils/downloader/tiktokApi.ts +++ b/src/utils/downloader/tiktokApi.ts @@ -13,7 +13,7 @@ const TiktokURLregex = /https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b( */ export const TiktokAPI = (url: string) => - new Promise((resolve, reject) => { + new Promise((resolve) => { if (!TiktokURLregex.test(url)) { return resolve({ status: "error", @@ -99,11 +99,9 @@ const fetchTiktokData = async (ID: string): Promise | null => { async () => { const res = await fetch( _tiktokvFeed( - new URLSearchParams( - _tiktokApiParams({ - aweme_id: ID - }) - ).toString() + _tiktokApiParams({ + aweme_id: ID + }) ), { method: "OPTIONS", diff --git a/src/utils/search/liveSearch.ts b/src/utils/search/liveSearch.ts new file mode 100644 index 0000000..72327cc --- /dev/null +++ b/src/utils/search/liveSearch.ts @@ -0,0 +1,71 @@ +import Axios from "axios" +import { _tiktokSearchLiveFull } from "../../constants/api" +import { _liveSearchParams } from "../../constants/params" +import { LiveInfo, Owner, OwnerStats } from "../../types/search/liveSearch" + +export const SearchLive = async (keyword: string, cookie?: any, page: number = 1) => + new Promise(async (resolve) => { + Axios(_tiktokSearchLiveFull(_liveSearchParams(keyword, page)), { + method: "GET", + headers: { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0", + cookie: typeof cookie === "object" ? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ") : cookie + } + }) + .then(({ data }) => { + // Cookie Invalid + if (data.status_code === 2483) return resolve({ status: "error", message: "Invalid cookie!" }) + // Another Error + if (data.status_code !== 0) return resolve({ status: "error", message: data.status_msg || "An error occurred! Please report this issue to the developer." }) + if (!data.data) return resolve({ status: "error", message: "Live not found!" }) + + const result = [] + data.data.forEach((v: any) => { + const content = JSON.parse(v.live_info.raw_data) + + // Live Info + const liveInfo: LiveInfo = { + id: content.id, + title: content.title, + cover: content.cover?.url_list || [], + squareCover: content.square_cover_img?.url_list || [], + rectangleCover: content.rectangle_cover_img?.url_list || [], + liveTypeThirdParty: content.live_type_third_party, + hashtag: content.hashtag?.title || "", + startTime: content.start_time, + stats: { + totalUser: content.stats.total_user, + viewerCount: content.user_count, + likeCount: content.like_count + }, + owner: { + id: content.owner.id, + nickname: content.owner.nickname, + username: content.owner.display_id, + signature: content.owner.bio_description, + avatarThumb: content.owner.avatar_thumb?.url_list || [], + avatarMedium: content.owner.avatar_medium?.url_list || [], + avatarLarge: content.owner.avatar_large?.url_list || [], + modifyTime: content.owner.modify_time, + stats: { + followingCount: content.owner.follow_info.following_count, + followerCount: content.owner.follow_info.follower_count + } as OwnerStats, + isVerified: content.owner?.authentication_info?.custom_verify === "verified account" || false + } as Owner + } + + // Room Info + const roomInfo = { + hasCommerceGoods: v.live_info.room_info.has_commerce_goods, + isBattle: v.live_info.room_info.is_battle + } + result.push({ roomInfo, liveInfo }) + }) + + resolve({ status: "success", result }) + }) + .catch((e) => { + resolve({ status: "error", message: e.message }) + }) + }) diff --git a/src/utils/search/stalker.ts b/src/utils/search/stalker.ts index 7971623..aaebfc1 100644 --- a/src/utils/search/stalker.ts +++ b/src/utils/search/stalker.ts @@ -14,7 +14,7 @@ import { createCipheriv } from "crypto" */ export const StalkUser = (username: string, cookie?: any, postLimit?: number): Promise => - new Promise(async (resolve, reject) => { + new Promise(async (resolve) => { username = username.replace("@", "") Axios.get(`${_tiktokurl}/@${username}`, { headers: { diff --git a/src/utils/search/userSearch.ts b/src/utils/search/userSearch.ts index 526e518..f24dcd3 100644 --- a/src/utils/search/userSearch.ts +++ b/src/utils/search/userSearch.ts @@ -12,7 +12,7 @@ import { _userSearchParams } from "../../constants/params" */ export const SearchUser = (username: string, cookie?: any, page?: number): Promise => - new Promise(async (resolve, reject) => { + new Promise(async (resolve) => { Axios(_tiktokSearchUserFull(_userSearchParams(username, page)), { method: "GET", headers: { @@ -21,9 +21,13 @@ export const SearchUser = (username: string, cookie?: any, page?: number): Promi } }) .then(({ data }) => { - if (data.status_code !== 0) return resolve({ status: "error", message: "Failed to find user. Make sure the keywords you are looking for are correct..." }) - const result = [] + // Cookie Invalid + if (data.status_code === 2483) return resolve({ status: "error", message: "Invalid cookie!" }) + // Another Error + if (data.status_code !== 0) return resolve({ status: "error", message: data.status_msg || "An error occurred! Please report this issue to the developer." }) + if (!data.user_list) return resolve({ status: "error", message: "User not found!" }) + const result = [] for (let i = 0; i < data.user_list.length; i++) { const user = data.user_list[i] result.push({