feat: add new feature (tiktok get user liked videos & user posts) & fix some types
This commit is contained in:
parent
afc5e83cb7
commit
973a94e517
@ -10,6 +10,8 @@ export const _tiktokGetPosts = (params: any): string =>
|
||||
`${_tiktokurl}/api/post/item_list/?${params}`
|
||||
export const _tiktokGetComments = (params: any): string =>
|
||||
`${_tiktokurl}/api/comment/list/?${params}`
|
||||
export const _tiktokGetUserLiked = (params: any): string =>
|
||||
`${_tiktokurl}/api/favorite/item_list/?${params}`
|
||||
|
||||
/** Tiktokv */
|
||||
export const _tiktokvApi: string = `https://api16-normal-useast5.tiktokv.us`
|
||||
|
||||
5
src/constants/headers.ts
Normal file
5
src/constants/headers.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const userAgent =
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
|
||||
|
||||
export const webUserAgent =
|
||||
"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"
|
||||
@ -38,6 +38,42 @@ export const _getUserPostsParams = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export const _getUserLikedParams = (
|
||||
id: string,
|
||||
secUid: string,
|
||||
count: number
|
||||
) => {
|
||||
let cursor = 0
|
||||
if (count > 50) {
|
||||
for (let i = 1; i < count; i++) {
|
||||
cursor += 50
|
||||
}
|
||||
}
|
||||
|
||||
return qs.stringify({
|
||||
aid: "1988",
|
||||
cookie_enabled: true,
|
||||
screen_width: 0,
|
||||
screen_height: 0,
|
||||
browser_language: "",
|
||||
browser_platform: "",
|
||||
browser_name: "",
|
||||
browser_version:
|
||||
"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Edg/135.0.0.0",
|
||||
browser_online: "",
|
||||
timezone_name: "Europe/London",
|
||||
is_page_visible: true,
|
||||
id,
|
||||
secUid,
|
||||
count,
|
||||
cursor,
|
||||
needPinnedItemIds: true,
|
||||
odinId: "7002566096994190854",
|
||||
history_len: 3,
|
||||
user_is_login: true
|
||||
})
|
||||
}
|
||||
|
||||
export const _xttParams = (secUid: string, cursor: number, count: number) => {
|
||||
return qs.stringify({
|
||||
aid: "1988",
|
||||
@ -59,8 +95,6 @@ export const _xttParams = (secUid: string, cursor: number, count: number) => {
|
||||
|
||||
export const _getCommentsParams = (id: string, count: number) => {
|
||||
let cursor = 0
|
||||
|
||||
// 50 comments per page
|
||||
if (count > 50) {
|
||||
for (let i = 1; i < count; i++) {
|
||||
cursor += 50
|
||||
@ -89,15 +123,13 @@ export const _getCommentsParams = (id: string, count: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
/** Search */
|
||||
/** Search Params */
|
||||
export const _userSearchParams = (
|
||||
keyword: string,
|
||||
page: number,
|
||||
xbogus?: any
|
||||
) => {
|
||||
let cursor = 0
|
||||
|
||||
// 10 users per page
|
||||
if (page > 1) {
|
||||
for (let i = 1; i < page; i++) {
|
||||
cursor += 10
|
||||
@ -157,8 +189,6 @@ export const _userSearchParams = (
|
||||
|
||||
export const _liveSearchParams = (keyword: string, page: number) => {
|
||||
let cursor = 0
|
||||
|
||||
// 12 cursor for 20 lives per page
|
||||
if (page > 1) {
|
||||
for (let i = 1; i < page; i++) {
|
||||
cursor += 12
|
||||
@ -203,6 +233,53 @@ export const _liveSearchParams = (keyword: string, page: number) => {
|
||||
})
|
||||
}
|
||||
|
||||
export const _videoSearchParams = (keyword: string, page: number) => {
|
||||
let cursor = 0
|
||||
if (page > 1) {
|
||||
for (let i = 1; i < page; i++) {
|
||||
cursor += 12
|
||||
}
|
||||
}
|
||||
|
||||
let offset = `${cursor}`
|
||||
|
||||
return qs.stringify({
|
||||
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",
|
||||
is_user_login: "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"
|
||||
})
|
||||
}
|
||||
|
||||
/** Downloader Params */
|
||||
export const _tiktokApiParams = (args: any) => {
|
||||
return new URLSearchParams({
|
||||
@ -240,12 +317,11 @@ export const _tiktokApiParams = (args: any) => {
|
||||
}).toString()
|
||||
}
|
||||
|
||||
/** Helper Functions */
|
||||
const randomChar = (char: string, range: number) => {
|
||||
let chars = ""
|
||||
|
||||
for (let i = 0; i < range; i++) {
|
||||
chars += char[Math.floor(Math.random() * char.length)]
|
||||
}
|
||||
|
||||
return chars
|
||||
}
|
||||
|
||||
105
src/index.ts
105
src/index.ts
@ -15,10 +15,16 @@ import { MusicalDownResponse } from "./types/downloader/musicaldown"
|
||||
import { SSSTikResponse } from "./types/downloader/ssstik"
|
||||
import { TiktokAPIResponse } from "./types/downloader/tiktokApi"
|
||||
import { TiktokUserSearchResponse } from "./types/search/userSearch"
|
||||
import { StalkResult } from "./types/get/getProfile"
|
||||
import { TiktokStalkUserResponse } from "./types/get/getProfile"
|
||||
import { TiktokLiveSearchResponse } from "./types/search/liveSearch"
|
||||
import { CommentsResult } from "./types/get/getComments"
|
||||
import { TiktokVideoCommentsResponse } from "./types/get/getComments"
|
||||
import { getComments } from "./utils/get/getComments"
|
||||
import { TiktokUserPostsResponse } from "./types/get/getUserPosts"
|
||||
import { getUserPosts } from "./utils/get/getUserPosts"
|
||||
import { getUserLiked } from "./utils/get/getUserLiked"
|
||||
import { TiktokUserFavoriteVideosResponse } from "./types/get/getUserLiked"
|
||||
import { TiktokVideoSearchResponse } from "./types/search/videoSearch"
|
||||
import { SearchVideo } from "./utils/search/videoSearch"
|
||||
|
||||
type TiktokDownloaderResponse<T extends "v1" | "v2" | "v3"> = T extends "v1"
|
||||
? TiktokAPIResponse
|
||||
@ -27,11 +33,12 @@ type TiktokDownloaderResponse<T extends "v1" | "v2" | "v3"> = T extends "v1"
|
||||
: T extends "v3"
|
||||
? MusicalDownResponse
|
||||
: TiktokAPIResponse
|
||||
type TiktokSearchResponse<T extends "user" | "live"> = T extends "user"
|
||||
? TiktokUserSearchResponse
|
||||
: T extends "live"
|
||||
? TiktokLiveSearchResponse
|
||||
: any
|
||||
type TiktokSearchResponse<T extends "user" | "live" | "video"> =
|
||||
T extends "user"
|
||||
? TiktokUserSearchResponse
|
||||
: T extends "live"
|
||||
? TiktokLiveSearchResponse
|
||||
: TiktokVideoSearchResponse
|
||||
|
||||
export = {
|
||||
/**
|
||||
@ -85,15 +92,21 @@ export = {
|
||||
* @param {string} options.proxy - Your Proxy (optional)
|
||||
* @returns {Promise<TiktokSearchResponse>}
|
||||
*/
|
||||
Search: async <T extends "user" | "live">(
|
||||
Search: async <T extends "user" | "live" | "video">(
|
||||
query: string,
|
||||
options: {
|
||||
type: T
|
||||
cookie?: string | any[]
|
||||
cookie: string | any[]
|
||||
page?: number
|
||||
proxy?: string
|
||||
}
|
||||
): Promise<TiktokSearchResponse<T>> => {
|
||||
if (!options?.cookie) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "Cookie is required!"
|
||||
} as TiktokSearchResponse<T>
|
||||
}
|
||||
switch (options?.type.toLowerCase()) {
|
||||
case "user": {
|
||||
const response = await SearchUser(
|
||||
@ -113,6 +126,15 @@ export = {
|
||||
)
|
||||
return response as TiktokSearchResponse<T>
|
||||
}
|
||||
case "video": {
|
||||
const response = await SearchVideo(
|
||||
query,
|
||||
options?.cookie,
|
||||
options?.page,
|
||||
options?.proxy
|
||||
)
|
||||
return response as TiktokSearchResponse<T>
|
||||
}
|
||||
default: {
|
||||
const response = await SearchUser(
|
||||
query,
|
||||
@ -129,9 +151,8 @@ export = {
|
||||
* @param {string} username - The username you want to stalk
|
||||
* @param {object} options - The options for stalk
|
||||
* @param {string | any[]} options.cookie - Your Tiktok Cookie (optional)
|
||||
* @param {number} options.postLimit - The limit of post you want to get (optional)
|
||||
* @param {string} options.proxy - Your Proxy (optional)
|
||||
* @returns {Promise<StalkResult>}
|
||||
* @returns {Promise<TiktokStalkUserResponse>}
|
||||
*/
|
||||
StalkUser: async (
|
||||
username: string,
|
||||
@ -140,12 +161,8 @@ export = {
|
||||
postLimit?: number
|
||||
proxy?: string
|
||||
}
|
||||
): Promise<StalkResult> => {
|
||||
const response = await StalkUser(
|
||||
username,
|
||||
options?.postLimit,
|
||||
options?.proxy
|
||||
)
|
||||
): Promise<TiktokStalkUserResponse> => {
|
||||
const response = await StalkUser(username, options?.cookie, options?.proxy)
|
||||
return response
|
||||
},
|
||||
|
||||
@ -155,17 +172,65 @@ export = {
|
||||
* @param {object} options - The options for get comments
|
||||
* @param {string} options.proxy - Your Proxy (optional)
|
||||
* @param {number} options.page - The page you want to get (optional)
|
||||
* @returns {Promise<CommentsResult>}
|
||||
* @returns {Promise<TiktokVideoCommentsResponse>}
|
||||
*/
|
||||
GetComments: async (
|
||||
GetVideoComments: async (
|
||||
url: string,
|
||||
options?: { commentLimit?: number; proxy?: string }
|
||||
): Promise<CommentsResult> => {
|
||||
): Promise<TiktokVideoCommentsResponse> => {
|
||||
const response = await getComments(
|
||||
url,
|
||||
options?.proxy,
|
||||
options?.commentLimit
|
||||
)
|
||||
return response
|
||||
},
|
||||
|
||||
/**
|
||||
* Tiktok Get User Posts
|
||||
* @param {string} username - The username you want to get posts from
|
||||
* @param {object} options - The options for getting posts
|
||||
* @param {number} options.postLimit - Limit number of posts to return (optional)
|
||||
* @param {string} options.proxy - Your Proxy (optional)
|
||||
* @returns {Promise<TiktokUserPostsResponse>}
|
||||
*/
|
||||
GetUserPosts: async (
|
||||
username: string,
|
||||
options?: { postLimit?: number; proxy?: string }
|
||||
): Promise<TiktokUserPostsResponse> => {
|
||||
const response = await getUserPosts(
|
||||
username,
|
||||
options?.proxy,
|
||||
options?.postLimit
|
||||
)
|
||||
return response
|
||||
},
|
||||
|
||||
/**
|
||||
* Tiktok Get User Liked Videos
|
||||
* @param {string} username - The username you want to get liked videos from
|
||||
* @param {object} options - The options for getting liked videos
|
||||
* @param {string | any[]} options.cookie - Your Tiktok Cookie (optional)
|
||||
* @param {number} options.postLimit - Limit number of posts to return (optional)
|
||||
* @param {string} options.proxy - Your Proxy (optional)
|
||||
* @returns {Promise<TiktokUserFavoriteVideosResponse>}
|
||||
*/
|
||||
GetUserLiked: async (
|
||||
username: string,
|
||||
options: { cookie: string | any[]; postLimit?: number; proxy?: string }
|
||||
): Promise<TiktokUserFavoriteVideosResponse> => {
|
||||
if (!options?.cookie) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "Cookie is required!"
|
||||
} as TiktokUserFavoriteVideosResponse
|
||||
}
|
||||
const response = await getUserLiked(
|
||||
username,
|
||||
options?.cookie,
|
||||
options?.proxy,
|
||||
options?.postLimit
|
||||
)
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ export class CookieManager {
|
||||
private cookieFile: string
|
||||
private cookieData: { [key: string]: string }
|
||||
|
||||
constructor(name: string) {
|
||||
constructor() {
|
||||
// Create cookies directory in user's home directory
|
||||
const homeDir = process.env.HOME || process.env.USERPROFILE
|
||||
const cookieDir = path.join(homeDir!, ".tiktok-api")
|
||||
@ -2,7 +2,7 @@ import * as path from "path"
|
||||
import * as os from "os"
|
||||
import axios from "axios"
|
||||
import * as fs from "fs"
|
||||
import { Logger } from "./logger"
|
||||
import { Logger } from "../lib/logger"
|
||||
|
||||
function getDefaultDownloadPath(): string {
|
||||
const homeDir = os.homedir()
|
||||
114
src/services/tiktokService.ts
Normal file
114
src/services/tiktokService.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { JSDOM, ResourceLoader } from "jsdom"
|
||||
import { _getUserLikedParams, _userSearchParams } from "../constants/params"
|
||||
import xbogus from "../../helper/xbogus"
|
||||
import { userAgent, webUserAgent } from "../constants/headers"
|
||||
import qs from "qs"
|
||||
import fs from "fs"
|
||||
import { createCipheriv } from "crypto"
|
||||
|
||||
export class TiktokService {
|
||||
/**
|
||||
* Generate Signature parameter for TikTok API requests
|
||||
* @param {string} id - User ID to generate X-Bogus for
|
||||
* @param {string} secUid - User's secure ID
|
||||
* @param {number} count - Number of items to request
|
||||
* @returns {string} URL with X-Bogus parameter appended
|
||||
*/
|
||||
public generateSignature(url: URL): string {
|
||||
const stringUrl = url.toString()
|
||||
const jsdomOptions = this.getJsdomOptions()
|
||||
|
||||
const { window } = new JSDOM(``, jsdomOptions)
|
||||
let _window = window
|
||||
_window.eval(this.signaturejs.toString())
|
||||
_window.byted_acrawler.init({
|
||||
aid: 24,
|
||||
dfp: true
|
||||
})
|
||||
_window.eval(this.webmssdk)
|
||||
const signature = _window.byted_acrawler.sign({ url: stringUrl })
|
||||
return signature
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate X-Bogus parameter for TikTok API requests
|
||||
* @param {string} id - User ID to generate X-Bogus for
|
||||
* @param {string} secUid - User's secure ID
|
||||
* @param {number} count - Number of items to request
|
||||
* @returns {string} URL with X-Bogus parameter appended
|
||||
*/
|
||||
public generateXBogus(url: URL, signature?: string): string {
|
||||
const jsdomOptions = this.getJsdomOptions()
|
||||
|
||||
const { window } = new JSDOM(``, jsdomOptions)
|
||||
let _window = window
|
||||
_window.eval(this.signaturejs.toString())
|
||||
_window.byted_acrawler.init({
|
||||
aid: 24,
|
||||
dfp: true
|
||||
})
|
||||
_window.eval(this.webmssdk)
|
||||
if (signature) {
|
||||
url.searchParams.append("_signature", signature)
|
||||
}
|
||||
const xbogus = _window._0x32d649(url.searchParams.toString())
|
||||
return xbogus
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate XTTPParams
|
||||
* @param {any} params - The params you want to encrypt
|
||||
* @returns {string}
|
||||
*/
|
||||
public generateXTTParams(params: any): string {
|
||||
const cipher = createCipheriv(
|
||||
"aes-128-cbc",
|
||||
TiktokService.AES_KEY,
|
||||
TiktokService.AES_IV
|
||||
)
|
||||
return Buffer.concat([cipher.update(params), cipher.final()]).toString(
|
||||
"base64"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate URL with X-Bogus
|
||||
* Special thanks to https://github.com/iamatef/xbogus
|
||||
* @param {string} username - The username you want to search
|
||||
* @param {number} page - The page you want to search
|
||||
* @returns {string}
|
||||
*/
|
||||
public generateURLXbogus(username: string, page: number): string {
|
||||
const baseUrl = `${TiktokService.BASE_URL}api/search/user/full/?`
|
||||
const queryParams = _userSearchParams(username, page)
|
||||
const xbogusParams = xbogus(`${baseUrl}${queryParams}`, userAgent)
|
||||
|
||||
return `${baseUrl}${_userSearchParams(username, page, xbogusParams)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSDOM Options
|
||||
* @returns {object}
|
||||
*/
|
||||
private getJsdomOptions() {
|
||||
return {
|
||||
url: TiktokService.BASE_URL,
|
||||
referrer: TiktokService.BASE_URL,
|
||||
contentType: "text/html",
|
||||
includeNodeLocations: false,
|
||||
runScripts: "outside-only",
|
||||
pretendToBeVisual: true,
|
||||
resources: new ResourceLoader({ userAgent: webUserAgent })
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly BASE_URL = "https://www.tiktok.com/"
|
||||
private static readonly AES_KEY = "webapp1.0+202106"
|
||||
private static readonly AES_IV = "webapp1.0+202106"
|
||||
private signaturejs = fs.readFileSync("./helper/signature.js", "utf-8")
|
||||
private webmssdk = fs.readFileSync("./helper/webmssdk.js", "utf-8")
|
||||
private resourceLoader = new ResourceLoader({
|
||||
userAgent:
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.35"
|
||||
})
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
export type getRequest = {
|
||||
export type GetMusicalDownReuqest = {
|
||||
status: "success" | "error"
|
||||
request?: {
|
||||
[key: string]: string
|
||||
@ -24,7 +24,7 @@ export type MusicalDownResponse = {
|
||||
}
|
||||
}
|
||||
|
||||
export type getMusic = {
|
||||
export type GetMusicalDownMusic = {
|
||||
status: "success" | "error"
|
||||
result?: string
|
||||
}
|
||||
|
||||
@ -10,8 +10,8 @@ export type SSSTikResponse = {
|
||||
result?: {
|
||||
type: "image" | "video" | "music"
|
||||
desc?: string
|
||||
author?: Author
|
||||
statistics?: Statistics
|
||||
author?: AuthorSSSTik
|
||||
statistics?: StatisticsSSSTik
|
||||
images?: string[]
|
||||
video?: string
|
||||
music?: string
|
||||
@ -19,12 +19,12 @@ export type SSSTikResponse = {
|
||||
}
|
||||
}
|
||||
|
||||
export type Author = {
|
||||
export type AuthorSSSTik = {
|
||||
avatar: string
|
||||
nickname: string
|
||||
}
|
||||
|
||||
export type Statistics = {
|
||||
export type StatisticsSSSTik = {
|
||||
likeCount: string
|
||||
commentCount: string
|
||||
shareCount: string
|
||||
|
||||
@ -6,22 +6,22 @@ export type TiktokAPIResponse = {
|
||||
id: string
|
||||
createTime: number
|
||||
description: string
|
||||
author: Author
|
||||
statistics: Statistics
|
||||
author: AuthorTiktokAPI
|
||||
statistics: StatisticsTiktokAPI
|
||||
hashtag: string[]
|
||||
isTurnOffComment: boolean
|
||||
isADS: boolean
|
||||
cover?: string[]
|
||||
dynamicCover?: string[]
|
||||
originCover?: string[]
|
||||
video?: Video
|
||||
video?: VideoTiktokAPI
|
||||
images?: string[]
|
||||
music: Music
|
||||
music: MusicTiktokAPI
|
||||
}
|
||||
resultNotParsed?: any
|
||||
}
|
||||
|
||||
export type Author = {
|
||||
export type AuthorTiktokAPI = {
|
||||
uid: number
|
||||
username: string
|
||||
nickname: string
|
||||
@ -32,7 +32,7 @@ export type Author = {
|
||||
url: string
|
||||
}
|
||||
|
||||
export type Statistics = {
|
||||
export type StatisticsTiktokAPI = {
|
||||
playCount: number
|
||||
downloadCount: number
|
||||
shareCount: number
|
||||
@ -46,7 +46,7 @@ export type Statistics = {
|
||||
repostCount: number
|
||||
}
|
||||
|
||||
export type Video = {
|
||||
export type VideoTiktokAPI = {
|
||||
ratio: string
|
||||
duration: number
|
||||
playAddr: string[]
|
||||
@ -56,7 +56,7 @@ export type Video = {
|
||||
originCover: string[]
|
||||
}
|
||||
|
||||
export type Music = {
|
||||
export type MusicTiktokAPI = {
|
||||
id: number
|
||||
title: string
|
||||
author: string
|
||||
@ -71,9 +71,9 @@ export type Music = {
|
||||
isAuthorArtist: boolean
|
||||
}
|
||||
|
||||
export type responseParser = {
|
||||
export type ResponseParserTiktokAPI = {
|
||||
content?: any
|
||||
statistics?: Statistics
|
||||
author?: Author
|
||||
music?: Music
|
||||
statistics?: StatisticsTiktokAPI
|
||||
author?: AuthorTiktokAPI
|
||||
music?: MusicTiktokAPI
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export type CommentsResult = {
|
||||
export type TiktokVideoCommentsResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: Comments[]
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
export type StalkResult = {
|
||||
export type TiktokStalkUserResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: {
|
||||
users: Users
|
||||
stats: Stats
|
||||
posts: Posts[]
|
||||
user: UserProfile
|
||||
stats: StatsUserProfile
|
||||
}
|
||||
totalPosts?: number
|
||||
}
|
||||
|
||||
export type Users = {
|
||||
export type UserProfile = {
|
||||
uid: string
|
||||
username: string
|
||||
nickname: string
|
||||
@ -23,19 +21,19 @@ export type Users = {
|
||||
commerceUser: boolean
|
||||
usernameModifyTime: number
|
||||
nicknameModifyTime: number
|
||||
secUid: string
|
||||
}
|
||||
|
||||
export type Stats = {
|
||||
export type StatsUserProfile = {
|
||||
followerCount: number
|
||||
followingCount: number
|
||||
heartCount: number
|
||||
videoCount: number
|
||||
likeCount: number
|
||||
friendCount: number
|
||||
postCount: number
|
||||
}
|
||||
|
||||
export type Statistics = {
|
||||
export type StatisticsUserProfile = {
|
||||
likeCount: number
|
||||
shareCount: number
|
||||
commentCount: number
|
||||
@ -67,70 +65,3 @@ export type Music = {
|
||||
authorName: string
|
||||
duration: string
|
||||
}
|
||||
|
||||
export type Posts = {
|
||||
id: string
|
||||
desc: string
|
||||
createTime: number
|
||||
digged: number
|
||||
duetEnabled: number
|
||||
forFriend: number
|
||||
officalItem: number
|
||||
originalItem: number
|
||||
privateItem: number
|
||||
shareEnabled: number
|
||||
stitchEnabled: number
|
||||
stats: StatsPost
|
||||
author: AuthorPost
|
||||
video?: VideoPost
|
||||
music: MusicPost
|
||||
images?: string[]
|
||||
}
|
||||
|
||||
export type StatsPost = {
|
||||
collectCount: number
|
||||
commentCount: number
|
||||
diggCount: number
|
||||
playCount: number
|
||||
shareCount: number
|
||||
}
|
||||
|
||||
export type AuthorPost = {
|
||||
id: string
|
||||
username: string
|
||||
nickname: string
|
||||
avatarLarger: string
|
||||
avatarThumb: string
|
||||
avatarMedium: string
|
||||
signature: string
|
||||
verified: boolean
|
||||
openFavorite: boolean
|
||||
privateAccount: boolean
|
||||
isADVirtual: boolean
|
||||
isEmbedBanned: boolean
|
||||
}
|
||||
|
||||
export type VideoPost = {
|
||||
id: string
|
||||
duration: number
|
||||
ratio: string
|
||||
cover: string
|
||||
originCover: string
|
||||
dynamicCover: string
|
||||
playAddr: string
|
||||
downloadAddr: string
|
||||
format: string
|
||||
bitrate: number
|
||||
}
|
||||
|
||||
export type MusicPost = {
|
||||
authorName: string
|
||||
coverLarge: string
|
||||
coverMedium: string
|
||||
coverThumb: string
|
||||
duration: number
|
||||
id: string
|
||||
title: string
|
||||
playUrl: string
|
||||
original: boolean
|
||||
}
|
||||
|
||||
97
src/types/get/getUserLiked.ts
Normal file
97
src/types/get/getUserLiked.ts
Normal file
@ -0,0 +1,97 @@
|
||||
export type TiktokUserFavoriteVideosResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: LikedResponse[]
|
||||
totalPosts?: number
|
||||
}
|
||||
|
||||
export type LikedResponse = {
|
||||
id: string
|
||||
desc: string
|
||||
createTime: string
|
||||
duetEnabled: boolean
|
||||
digged: boolean
|
||||
forFriend: boolean
|
||||
isAd: boolean
|
||||
originalItem: boolean
|
||||
privateItem: boolean
|
||||
officialItem: boolean
|
||||
secret: boolean
|
||||
shareEnabled: boolean
|
||||
stitchEanbled: boolean
|
||||
textTranslatable: boolean
|
||||
author: AuthorLiked
|
||||
stats: StatisticsLiked
|
||||
video?: VideoLiked
|
||||
imagePost?: ImagesLiked[]
|
||||
music: MusicLiked
|
||||
}
|
||||
|
||||
export type AuthorLiked = {
|
||||
id: string
|
||||
username: string
|
||||
nickname: string
|
||||
avatarLarger: string
|
||||
avatarThumb: string
|
||||
avatarMedium: string
|
||||
signature: string
|
||||
verified: string
|
||||
openFavorite: string
|
||||
privateAccount: string
|
||||
isADVirtual: string
|
||||
isEmbedBanned: string
|
||||
stats: StatisticsAuthorLiked
|
||||
}
|
||||
|
||||
export type StatisticsAuthorLiked = {
|
||||
likeCount: string
|
||||
followerCount: string
|
||||
followingCount: string
|
||||
friendCount: string
|
||||
heartCount: string
|
||||
postsCount: string
|
||||
}
|
||||
|
||||
export type StatisticsLiked = {
|
||||
collectCount: string
|
||||
commentCount: string
|
||||
diggCount: string
|
||||
playCount: string
|
||||
repostCount: string
|
||||
shareCount: string
|
||||
}
|
||||
|
||||
export type ImagesLiked = {
|
||||
title: string
|
||||
images: string[]
|
||||
}
|
||||
|
||||
export type VideoLiked = {
|
||||
id: string
|
||||
videoID: string
|
||||
duration: number
|
||||
ratio: string
|
||||
cover: string
|
||||
originCover: string
|
||||
dynamicCover: string
|
||||
playAddr: string
|
||||
downloadAddr: string
|
||||
format: string
|
||||
bitrate: number
|
||||
bitrateInfo: any[]
|
||||
}
|
||||
|
||||
export type MusicLiked = {
|
||||
id: string
|
||||
title: string
|
||||
playUrl: string
|
||||
coverThumb: string
|
||||
coverMedium: string
|
||||
coverLarge: string
|
||||
authorName: string
|
||||
original: boolean
|
||||
album: string
|
||||
duration: number
|
||||
isCopyrighted: boolean
|
||||
private: boolean
|
||||
}
|
||||
73
src/types/get/getUserPosts.ts
Normal file
73
src/types/get/getUserPosts.ts
Normal file
@ -0,0 +1,73 @@
|
||||
export type TiktokUserPostsResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: Posts[]
|
||||
totalPosts?: number
|
||||
}
|
||||
|
||||
export type Posts = {
|
||||
id: string
|
||||
desc: string
|
||||
createTime: number
|
||||
digged: number
|
||||
duetEnabled: number
|
||||
forFriend: number
|
||||
officalItem: number
|
||||
originalItem: number
|
||||
privateItem: number
|
||||
shareEnabled: number
|
||||
stitchEnabled: number
|
||||
stats: StatsPost
|
||||
author: AuthorPost
|
||||
video?: VideoPost
|
||||
music: MusicPost
|
||||
imagePost?: string[]
|
||||
}
|
||||
|
||||
export type StatsPost = {
|
||||
collectCount: number
|
||||
commentCount: number
|
||||
diggCount: number
|
||||
playCount: number
|
||||
shareCount: number
|
||||
}
|
||||
|
||||
export type AuthorPost = {
|
||||
id: string
|
||||
username: string
|
||||
nickname: string
|
||||
avatarLarger: string
|
||||
avatarThumb: string
|
||||
avatarMedium: string
|
||||
signature: string
|
||||
verified: boolean
|
||||
openFavorite: boolean
|
||||
privateAccount: boolean
|
||||
isADVirtual: boolean
|
||||
isEmbedBanned: boolean
|
||||
}
|
||||
|
||||
export type VideoPost = {
|
||||
id: string
|
||||
duration: number
|
||||
ratio: string
|
||||
cover: string
|
||||
originCover: string
|
||||
dynamicCover: string
|
||||
playAddr: string
|
||||
downloadAddr: string
|
||||
format: string
|
||||
bitrate: number
|
||||
}
|
||||
|
||||
export type MusicPost = {
|
||||
authorName: string
|
||||
coverLarge: string
|
||||
coverMedium: string
|
||||
coverThumb: string
|
||||
duration: number
|
||||
id: string
|
||||
title: string
|
||||
playUrl: string
|
||||
original: boolean
|
||||
}
|
||||
@ -1,12 +1,12 @@
|
||||
export type TiktokLiveSearchResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: Result[]
|
||||
result?: LiveSearchResult[]
|
||||
page?: number
|
||||
totalResults?: number
|
||||
}
|
||||
|
||||
export type Result = {
|
||||
export type LiveSearchResult = {
|
||||
roomInfo: RoomInfo
|
||||
liveInfo: LiveInfo
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export type TiktokUserSearchResponse = {
|
||||
totalResults?: number
|
||||
}
|
||||
|
||||
export type Result = {
|
||||
export type UserSearchResult = {
|
||||
uid: string
|
||||
username: string
|
||||
nickname: string
|
||||
|
||||
67
src/types/search/videoSearch.ts
Normal file
67
src/types/search/videoSearch.ts
Normal file
@ -0,0 +1,67 @@
|
||||
export type TiktokVideoSearchResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: VideoSearchResult[]
|
||||
page?: number
|
||||
totalResults?: number
|
||||
}
|
||||
|
||||
export type VideoSearchResult = {
|
||||
id: string
|
||||
desc: string
|
||||
createTime: number
|
||||
author: AuthorVideoSearch
|
||||
stats: StatisticsVideoSearch
|
||||
video: VideoSearch
|
||||
music: MusicVideoSearch
|
||||
}
|
||||
|
||||
export type VideoSearch = {
|
||||
id: string
|
||||
ratio: string
|
||||
cover: string
|
||||
originCover: string
|
||||
dynamicCover: string
|
||||
playAddr: string
|
||||
downloadAddr: string
|
||||
format: string
|
||||
}
|
||||
|
||||
export type StatisticsVideoSearch = {
|
||||
collectCount: number
|
||||
commentCount: number
|
||||
diggCount: number
|
||||
playCount: number
|
||||
shareCount: number
|
||||
}
|
||||
|
||||
export type AuthorVideoSearch = {
|
||||
id: string
|
||||
uniqueId: string
|
||||
nickname: string
|
||||
avatarThumb: string
|
||||
avatarMedium: string
|
||||
avatarLarger: string
|
||||
signature: string
|
||||
verified: boolean
|
||||
secUid: string
|
||||
openFavorite: boolean
|
||||
privateAccount: boolean
|
||||
isADVirtual: boolean
|
||||
tiktokSeller: boolean
|
||||
isEmbedBanned: boolean
|
||||
}
|
||||
|
||||
export type MusicVideoSearch = {
|
||||
id: string
|
||||
title: string
|
||||
playUrl: string
|
||||
coverThumb: string
|
||||
coverMedium: string
|
||||
coverLarge: string
|
||||
authorName: string
|
||||
original: boolean
|
||||
album: string
|
||||
duration: number
|
||||
isCopyrighted: boolean
|
||||
}
|
||||
@ -2,8 +2,8 @@ import Axios from "axios"
|
||||
import { load } from "cheerio"
|
||||
import {
|
||||
MusicalDownResponse,
|
||||
getMusic,
|
||||
getRequest
|
||||
GetMusicalDownMusic,
|
||||
GetMusicalDownReuqest
|
||||
} from "../../types/downloader/musicaldown"
|
||||
import {
|
||||
_musicaldownapi,
|
||||
@ -21,7 +21,10 @@ import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
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, proxy?: string): Promise<getRequest> =>
|
||||
const getRequest = (
|
||||
url: string,
|
||||
proxy?: string
|
||||
): Promise<GetMusicalDownReuqest> =>
|
||||
new Promise((resolve) => {
|
||||
if (!TiktokURLregex.test(url)) {
|
||||
return resolve({
|
||||
@ -103,7 +106,7 @@ export const MusicalDown = (
|
||||
proxy?: string
|
||||
): Promise<MusicalDownResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
const request: getRequest = await getRequest(url)
|
||||
const request: GetMusicalDownReuqest = await getRequest(url)
|
||||
if (request.status !== "success")
|
||||
return resolve({ status: "error", message: request.message })
|
||||
Axios(_musicaldownapi, {
|
||||
|
||||
@ -2,10 +2,10 @@ import Axios from "axios"
|
||||
import asyncRetry from "async-retry"
|
||||
import { load } from "cheerio"
|
||||
import {
|
||||
Author,
|
||||
Statistics,
|
||||
AuthorSSSTik,
|
||||
StatisticsSSSTik,
|
||||
SSSTikFetchTT,
|
||||
SSSTikResponse,
|
||||
SSSTikResponse
|
||||
} from "../../types/downloader/ssstik"
|
||||
import { _ssstikapi, _ssstikurl } from "../../constants/api"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
@ -115,11 +115,11 @@ export const SSSTik = (url: string, proxy?: string): Promise<SSSTikResponse> =>
|
||||
const $ = load(await response)
|
||||
|
||||
// Result
|
||||
const author: Author = {
|
||||
const author: AuthorSSSTik = {
|
||||
avatar: $("img.result_author").attr("src"),
|
||||
nickname: $("h2").text().trim()
|
||||
}
|
||||
const statistics: Statistics = {
|
||||
const statistics: StatisticsSSSTik = {
|
||||
likeCount: $("#trending-actions > .justify-content-start")
|
||||
.text()
|
||||
.trim(),
|
||||
@ -174,7 +174,7 @@ export const SSSTik = (url: string, proxy?: string): Promise<SSSTikResponse> =>
|
||||
result = {
|
||||
type: "music",
|
||||
music,
|
||||
direct: direct || "",
|
||||
direct: direct || ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,12 +3,12 @@ import asyncRetry from "async-retry"
|
||||
import { _tiktokvFeed, _tiktokurl } from "../../constants/api"
|
||||
import { _tiktokApiParams } from "../../constants/params"
|
||||
import {
|
||||
Author,
|
||||
AuthorTiktokAPI,
|
||||
TiktokAPIResponse,
|
||||
Statistics,
|
||||
Music,
|
||||
responseParser,
|
||||
Video
|
||||
StatisticsTiktokAPI,
|
||||
MusicTiktokAPI,
|
||||
ResponseParserTiktokAPI,
|
||||
VideoTiktokAPI
|
||||
} from "../../types/downloader/tiktokApi"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
@ -98,7 +98,7 @@ export const TiktokAPI = (
|
||||
}
|
||||
} else {
|
||||
// Video Result
|
||||
const video: Video = {
|
||||
const video: VideoTiktokAPI = {
|
||||
ratio: content.video.ratio,
|
||||
duration: content.video.duration,
|
||||
playAddr: content.video?.play_addr?.url_list || [], // No Watermark Video
|
||||
@ -143,7 +143,7 @@ export const TiktokAPI = (
|
||||
const fetchTiktokData = async (
|
||||
ID: string,
|
||||
proxy?: string
|
||||
): Promise<responseParser> | null => {
|
||||
): Promise<ResponseParserTiktokAPI> | null => {
|
||||
try {
|
||||
const response = asyncRetry(
|
||||
async () => {
|
||||
@ -191,7 +191,7 @@ const fetchTiktokData = async (
|
||||
}
|
||||
}
|
||||
|
||||
const parseTiktokData = (ID: string, data: any): responseParser => {
|
||||
const parseTiktokData = (ID: string, data: any): ResponseParserTiktokAPI => {
|
||||
let content = data?.aweme_list
|
||||
|
||||
if (!content) return { content: null }
|
||||
@ -199,7 +199,7 @@ const parseTiktokData = (ID: string, data: any): responseParser => {
|
||||
content = content.find((v: any) => v.aweme_id === ID)
|
||||
|
||||
// Statistics Result
|
||||
const statistics: Statistics = {
|
||||
const statistics: StatisticsTiktokAPI = {
|
||||
commentCount: content.statistics.comment_count,
|
||||
diggCount: content.statistics.digg_count,
|
||||
downloadCount: content.statistics.download_count,
|
||||
@ -214,7 +214,7 @@ const parseTiktokData = (ID: string, data: any): responseParser => {
|
||||
}
|
||||
|
||||
// Author Result
|
||||
const author: Author = {
|
||||
const author: AuthorTiktokAPI = {
|
||||
uid: content.author.uid,
|
||||
username: content.author.unique_id,
|
||||
nickname: content.author.nickname,
|
||||
@ -226,7 +226,7 @@ const parseTiktokData = (ID: string, data: any): responseParser => {
|
||||
}
|
||||
|
||||
// Music Result
|
||||
const music: Music = {
|
||||
const music: MusicTiktokAPI = {
|
||||
id: content.music.id,
|
||||
title: content.music.title,
|
||||
author: content.music.author,
|
||||
|
||||
@ -3,7 +3,11 @@ import { _tiktokGetComments } from "../../constants/api"
|
||||
import { _getCommentsParams } from "../../constants/params"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
import { Comments, CommentsResult, User } from "../../types/get/getComments"
|
||||
import {
|
||||
Comments,
|
||||
TiktokVideoCommentsResponse,
|
||||
User
|
||||
} from "../../types/get/getComments"
|
||||
|
||||
const TiktokURLregex =
|
||||
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
|
||||
@ -13,14 +17,14 @@ const TiktokURLregex =
|
||||
* @param {string} url - Tiktok URL
|
||||
* @param {string} proxy - Your Proxy (optional)
|
||||
* @param {number} commentLimit - Comment Limit (optional)
|
||||
* @returns {Promise<CommentsResult>}
|
||||
* @returns {Promise<TiktokVideoCommentsResponse>}
|
||||
*/
|
||||
|
||||
export const getComments = async (
|
||||
url: string,
|
||||
proxy?: string,
|
||||
commentLimit?: number
|
||||
): Promise<CommentsResult> =>
|
||||
): Promise<TiktokVideoCommentsResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
if (!TiktokURLregex.test(url)) {
|
||||
return resolve({
|
||||
@ -99,7 +103,7 @@ const parseComments = async (
|
||||
let hasMore: boolean = true
|
||||
|
||||
while (hasMore) {
|
||||
const result = await requestComments(id, cursor, proxy)
|
||||
const result = await requestComments(id, commentLimit, proxy)
|
||||
|
||||
// Check if the result has more comments
|
||||
hasMore = result.has_more === 1
|
||||
|
||||
@ -1,36 +1,40 @@
|
||||
import Axios from "axios"
|
||||
import { load } from "cheerio"
|
||||
import { _tiktokGetPosts, _tiktokurl } from "../../constants/api"
|
||||
import {
|
||||
AuthorPost,
|
||||
Posts,
|
||||
StalkResult,
|
||||
Stats,
|
||||
Users
|
||||
_tiktokGetUserLiked,
|
||||
_tiktokGetPosts,
|
||||
_tiktokurl
|
||||
} from "../../constants/api"
|
||||
import {
|
||||
TiktokStalkUserResponse,
|
||||
StatsUserProfile,
|
||||
UserProfile
|
||||
} from "../../types/get/getProfile"
|
||||
import { _getUserPostsParams, _xttParams } from "../../constants/params"
|
||||
import { createCipheriv } from "crypto"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
|
||||
/**
|
||||
* Tiktok Stalk User
|
||||
* @param {string} username - The username you want to stalk
|
||||
* @param {number} postLimit - The limit of post you want to get (optional)
|
||||
* @param {string} proxy - Your Proxy (optional)
|
||||
* @returns {Promise<StalkResult>}
|
||||
* @returns {Promise<TiktokStalkUserResponse>}
|
||||
*/
|
||||
|
||||
export const StalkUser = (
|
||||
username: string,
|
||||
postLimit?: number,
|
||||
cookie?: string | any[],
|
||||
proxy?: string
|
||||
): Promise<StalkResult> =>
|
||||
): Promise<TiktokStalkUserResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
username = username.replace("@", "")
|
||||
Axios(`${_tiktokurl}/@${username}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
cookie:
|
||||
typeof cookie === "object"
|
||||
? cookie.map((v: any) => `${v.name}=${v.value}`).join("; ")
|
||||
: cookie,
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"
|
||||
},
|
||||
@ -60,17 +64,21 @@ export const StalkUser = (
|
||||
const dataUser =
|
||||
result["__DEFAULT_SCOPE__"]["webapp.user-detail"]["userInfo"]
|
||||
|
||||
const posts: Posts[] = await parsePosts(dataUser, postLimit, proxy)
|
||||
const { users, stats } = parseDataUser(dataUser, posts)
|
||||
if (!dataUser) {
|
||||
return resolve({
|
||||
status: "error",
|
||||
message: "User not found!"
|
||||
})
|
||||
}
|
||||
|
||||
let response: StalkResult = {
|
||||
const { user, stats } = parseDataUser(dataUser)
|
||||
|
||||
let response: TiktokStalkUserResponse = {
|
||||
status: "success",
|
||||
result: {
|
||||
users,
|
||||
stats,
|
||||
posts
|
||||
},
|
||||
totalPosts: posts.length
|
||||
user,
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
resolve(response)
|
||||
@ -78,42 +86,9 @@ export const StalkUser = (
|
||||
.catch((e) => resolve({ status: "error", message: e.message }))
|
||||
})
|
||||
|
||||
/**
|
||||
* Thanks to:
|
||||
* https://github.com/atharahmed/tiktok-private-api/blob/020ede2eaa6021bcd363282d8cef1aacaff2f88c/src/repositories/user.repository.ts#L148
|
||||
*/
|
||||
|
||||
const getUserPosts = async (
|
||||
secUid: string,
|
||||
cursor = 0,
|
||||
count = 30,
|
||||
proxy?: string
|
||||
) => {
|
||||
const { data } = await Axios.get(
|
||||
`${_tiktokGetPosts(_getUserPostsParams())}`,
|
||||
{
|
||||
headers: {
|
||||
"user-agent":
|
||||
"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",
|
||||
"X-tt-params": xttparams(_xttParams(secUid, cursor, count))
|
||||
},
|
||||
httpsAgent:
|
||||
(proxy &&
|
||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
||||
? new HttpsProxyAgent(proxy)
|
||||
: proxy.startsWith("socks")
|
||||
? new SocksProxyAgent(proxy)
|
||||
: undefined)) ||
|
||||
undefined
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
const parseDataUser = (dataUser: any, posts: Posts[]) => {
|
||||
const parseDataUser = (dataUser: any) => {
|
||||
// User Info Result
|
||||
const users: Users = {
|
||||
const user: UserProfile = {
|
||||
uid: dataUser.user.id,
|
||||
username: dataUser.user.uniqueId,
|
||||
nickname: dataUser.user.nickname,
|
||||
@ -126,142 +101,19 @@ const parseDataUser = (dataUser: any, posts: Posts[]) => {
|
||||
region: dataUser.user.region,
|
||||
commerceUser: dataUser.user.commerceUserInfo.commerceUser,
|
||||
usernameModifyTime: dataUser.user.uniqueIdModifyTime,
|
||||
nicknameModifyTime: dataUser.user.nickNameModifyTime
|
||||
nicknameModifyTime: dataUser.user.nickNameModifyTime,
|
||||
secUid: dataUser.user.secUid
|
||||
}
|
||||
|
||||
// Statistics Result
|
||||
const stats: Stats = {
|
||||
const stats: StatsUserProfile = {
|
||||
followerCount: dataUser.stats.followerCount,
|
||||
followingCount: dataUser.stats.followingCount,
|
||||
heartCount: dataUser.stats.heartCount,
|
||||
videoCount: dataUser.stats.videoCount,
|
||||
likeCount: dataUser.stats.diggCount,
|
||||
friendCount: dataUser.stats.friendCount,
|
||||
postCount: posts.length
|
||||
friendCount: dataUser.stats.friendCount
|
||||
}
|
||||
|
||||
return { users, stats }
|
||||
}
|
||||
|
||||
const parsePosts = async (
|
||||
dataUser: any,
|
||||
postLimit?: number,
|
||||
proxy?: string
|
||||
): Promise<Posts[]> => {
|
||||
// Posts Result
|
||||
let hasMore = true
|
||||
let cursor = 0
|
||||
const posts: Posts[] = []
|
||||
let counter = 0
|
||||
while (hasMore) {
|
||||
let result: any | null = null
|
||||
|
||||
// Prevent missing response posts
|
||||
for (let i = 0; i < 30; i++) {
|
||||
result = await getUserPosts(dataUser.user.secUid, cursor, 30, proxy)
|
||||
if (result !== "") break
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (result === "") {
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
|
||||
result?.itemList?.forEach((v: any) => {
|
||||
const author: AuthorPost = {
|
||||
id: v.author.id,
|
||||
username: v.author.uniqueId,
|
||||
nickname: v.author.nickname,
|
||||
avatarLarger: v.author.avatarLarger,
|
||||
avatarThumb: v.author.avatarThumb,
|
||||
avatarMedium: v.author.avatarMedium,
|
||||
signature: v.author.signature,
|
||||
verified: v.author.verified,
|
||||
openFavorite: v.author.openFavorite,
|
||||
privateAccount: v.author.privateAccount,
|
||||
isADVirtual: v.author.isADVirtual,
|
||||
isEmbedBanned: v.author.isEmbedBanned
|
||||
}
|
||||
|
||||
if (v.imagePost) {
|
||||
const images: string[] = v.imagePost.images.map(
|
||||
(img: any) => img.imageURL.urlList[0]
|
||||
)
|
||||
|
||||
posts.push({
|
||||
id: v.id,
|
||||
desc: v.desc,
|
||||
createTime: v.createTime,
|
||||
digged: v.digged,
|
||||
duetEnabled: v.duetEnabled,
|
||||
forFriend: v.forFriend,
|
||||
officalItem: v.officalItem,
|
||||
originalItem: v.originalItem,
|
||||
privateItem: v.privateItem,
|
||||
shareEnabled: v.shareEnabled,
|
||||
stitchEnabled: v.stitchEnabled,
|
||||
stats: v.stats,
|
||||
music: v.music,
|
||||
author,
|
||||
images
|
||||
})
|
||||
} else {
|
||||
const video = {
|
||||
id: v.video.id,
|
||||
duration: v.video.duration,
|
||||
format: v.video.format,
|
||||
bitrate: v.video.bitrate,
|
||||
ratio: v.video.ratio,
|
||||
playAddr: v.video.playAddr,
|
||||
cover: v.video.cover,
|
||||
originCover: v.video.originCover,
|
||||
dynamicCover: v.video.dynamicCover,
|
||||
downloadAddr: v.video.downloadAddr
|
||||
}
|
||||
|
||||
posts.push({
|
||||
id: v.id,
|
||||
desc: v.desc,
|
||||
createTime: v.createTime,
|
||||
digged: v.digged,
|
||||
duetEnabled: v.duetEnabled,
|
||||
forFriend: v.forFriend,
|
||||
officalItem: v.officalItem,
|
||||
originalItem: v.originalItem,
|
||||
privateItem: v.privateItem,
|
||||
shareEnabled: v.shareEnabled,
|
||||
stitchEnabled: v.stitchEnabled,
|
||||
stats: v.stats,
|
||||
music: v.music,
|
||||
author,
|
||||
video
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Update hasMore and cursor for next iteration
|
||||
hasMore = result.hasMore
|
||||
cursor = hasMore ? result.cursor : null
|
||||
counter++
|
||||
|
||||
// Check post limit if specified
|
||||
if (postLimit && posts.length >= postLimit) {
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return postLimit ? posts.slice(0, postLimit) : posts
|
||||
}
|
||||
|
||||
const xttparams = (params: any) => {
|
||||
const cipher = createCipheriv(
|
||||
"aes-128-cbc",
|
||||
"webapp1.0+202106",
|
||||
"webapp1.0+202106"
|
||||
)
|
||||
return Buffer.concat([cipher.update(params), cipher.final()]).toString(
|
||||
"base64"
|
||||
)
|
||||
return { user, stats }
|
||||
}
|
||||
|
||||
236
src/utils/get/getUserLiked.ts
Normal file
236
src/utils/get/getUserLiked.ts
Normal file
@ -0,0 +1,236 @@
|
||||
import Axios from "axios"
|
||||
import { _tiktokGetUserLiked, _tiktokurl } from "../../constants/api"
|
||||
import { StalkUser } from "./getProfile"
|
||||
import { _getUserLikedParams, _xttParams } from "../../constants/params"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
import { TiktokService } from "../../services/tiktokService"
|
||||
import {
|
||||
AuthorLiked,
|
||||
LikedResponse,
|
||||
TiktokUserFavoriteVideosResponse,
|
||||
StatisticsLiked,
|
||||
MusicLiked,
|
||||
VideoLiked,
|
||||
StatisticsAuthorLiked,
|
||||
ImagesLiked
|
||||
} from "../../types/get/getUserLiked"
|
||||
|
||||
export const getUserLiked = (
|
||||
username: string,
|
||||
cookie: string | any[],
|
||||
proxy?: string,
|
||||
postLimit?: number
|
||||
): Promise<TiktokUserFavoriteVideosResponse> =>
|
||||
new Promise((resolve) => {
|
||||
if (!cookie) {
|
||||
return {
|
||||
status: "error",
|
||||
message: "Cookie is required!"
|
||||
} as TiktokUserFavoriteVideosResponse
|
||||
}
|
||||
|
||||
StalkUser(username).then(async (res) => {
|
||||
if (res.status === "error") {
|
||||
return resolve({
|
||||
status: "error",
|
||||
message: res.message
|
||||
})
|
||||
}
|
||||
|
||||
const id = res.result.user.uid
|
||||
const secUid = res.result.user.secUid
|
||||
const data = await parseUserLiked(id, secUid, cookie, postLimit, proxy)
|
||||
|
||||
resolve({
|
||||
status: "success",
|
||||
result: data,
|
||||
totalPosts: data.length
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const parseUserLiked = async (
|
||||
id: string,
|
||||
secUid: string,
|
||||
cookie: string | any[],
|
||||
postLimit?: number,
|
||||
proxy?: string
|
||||
): Promise<LikedResponse[]> => {
|
||||
// Liked Result
|
||||
let hasMore = true
|
||||
let cursor = 0
|
||||
const favorites: LikedResponse[] = []
|
||||
let counter = 0
|
||||
while (hasMore) {
|
||||
let result: any | null = null
|
||||
|
||||
// Prevent missing response favorites
|
||||
result = await requestUserLiked(id, secUid, cookie, postLimit, proxy)
|
||||
|
||||
// Validate
|
||||
if (result === "") {
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
|
||||
result?.itemList?.forEach((v: any) => {
|
||||
const statsAuthor: StatisticsAuthorLiked = {
|
||||
likeCount: v.authorStats.diggCount,
|
||||
followerCount: v.authorStats.followerCount,
|
||||
followingCount: v.authorStats.followingCount,
|
||||
friendCount: v.authorStats.friendCount,
|
||||
heartCount: v.authorStats.heartCount,
|
||||
postsCount: v.authorStats.videoCount
|
||||
}
|
||||
|
||||
const author: AuthorLiked = {
|
||||
id: v.author.id,
|
||||
username: v.author.uniqueId,
|
||||
nickname: v.author.nickname,
|
||||
avatarLarger: v.author.avatarLarger,
|
||||
avatarThumb: v.author.avatarThumb,
|
||||
avatarMedium: v.author.avatarMedium,
|
||||
signature: v.author.signature,
|
||||
verified: v.author.verified,
|
||||
openFavorite: v.author.openFavorite,
|
||||
privateAccount: v.author.privateAccount,
|
||||
isADVirtual: v.author.isADVirtual,
|
||||
isEmbedBanned: v.author.isEmbedBanned,
|
||||
stats: statsAuthor
|
||||
}
|
||||
|
||||
const stats: StatisticsLiked = {
|
||||
collectCount: v.statsV2.collectCount,
|
||||
commentCount: v.statsV2.commentCount,
|
||||
diggCount: v.statsV2.diggCount,
|
||||
playCount: v.statsV2.playCount,
|
||||
repostCount: v.statsV2.repostCount,
|
||||
shareCount: v.statsV2.shareCount
|
||||
}
|
||||
|
||||
const music: MusicLiked = {
|
||||
id: v.music.id,
|
||||
title: v.music.title,
|
||||
playUrl: v.music.playUrl,
|
||||
coverThumb: v.music.coverThumb,
|
||||
coverMedium: v.music.coverMedium,
|
||||
coverLarge: v.music.coverLarge,
|
||||
authorName: v.music.authorName,
|
||||
original: v.music.original,
|
||||
album: v.music.album,
|
||||
duration: v.music.duration,
|
||||
isCopyrighted: v.music.isCopyrighted,
|
||||
private: v.music.private
|
||||
}
|
||||
|
||||
const response = {
|
||||
id: v.id,
|
||||
desc: v.desc,
|
||||
createTime: v.createTime,
|
||||
duetEnabled: v.duetEnabled || false,
|
||||
digged: v.digged || false,
|
||||
forFriend: v.forFriend || false,
|
||||
isAd: v.isAd || false,
|
||||
originalItem: v.originalItem || false,
|
||||
privateItem: v.privateItem || false,
|
||||
officialItem: v.officialItem || false,
|
||||
secret: v.secret || false,
|
||||
shareEnabled: v.shareEnabled || false,
|
||||
stitchEanbled: v.stitchEanbled || false,
|
||||
textTranslatable: v.textTranslatable || false
|
||||
}
|
||||
|
||||
if (v.imagePost) {
|
||||
const imagePost: ImagesLiked[] = []
|
||||
v.imagePost.images.forEach((image: any) => {
|
||||
imagePost.push({
|
||||
title: image.title,
|
||||
images: image.imageURL.urlList[0]
|
||||
})
|
||||
})
|
||||
|
||||
favorites.push({
|
||||
...response,
|
||||
author,
|
||||
stats,
|
||||
imagePost,
|
||||
music
|
||||
})
|
||||
} else {
|
||||
const video: VideoLiked = {
|
||||
id: v.video.id,
|
||||
videoID: v.video.id,
|
||||
duration: v.video.duration,
|
||||
ratio: v.video.ratio,
|
||||
cover: v.video.cover,
|
||||
originCover: v.video.originCover,
|
||||
dynamicCover: v.video.dynamicCover,
|
||||
playAddr: v.video.playAddr,
|
||||
downloadAddr: v.video.downloadAddr,
|
||||
format: v.video.format,
|
||||
bitrate: v.video.bitrate,
|
||||
bitrateInfo: v.video.bitrateInfo
|
||||
}
|
||||
|
||||
favorites.push({
|
||||
...response,
|
||||
author,
|
||||
stats,
|
||||
video,
|
||||
music
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Update hasMore and cursor for next iteration
|
||||
hasMore = result.hasMore
|
||||
cursor = hasMore ? result.cursor : null
|
||||
counter++
|
||||
|
||||
// Check post limit if specified
|
||||
if (postLimit && favorites.length >= postLimit) {
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return postLimit ? favorites.slice(0, postLimit) : favorites
|
||||
}
|
||||
|
||||
const requestUserLiked = async (
|
||||
id: string,
|
||||
secUid: string,
|
||||
cookie: string | any[],
|
||||
postLimit: number,
|
||||
proxy?: string
|
||||
): Promise<any> => {
|
||||
const Tiktok = new TiktokService()
|
||||
|
||||
const url = new URL(
|
||||
_tiktokGetUserLiked(_getUserLikedParams(id, secUid, postLimit))
|
||||
)
|
||||
const signature = Tiktok.generateSignature(url)
|
||||
url.searchParams.append("_signature", signature)
|
||||
const xbogus = Tiktok.generateXBogus(url, signature)
|
||||
url.searchParams.append("X-Bogus", xbogus)
|
||||
const xttparams = Tiktok.generateXTTParams(url.searchParams.toString())
|
||||
|
||||
const { data } = await Axios.get(`${url.toString()}`, {
|
||||
headers: {
|
||||
"user-agent":
|
||||
"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",
|
||||
cookie,
|
||||
"x-tt-params": xttparams
|
||||
},
|
||||
httpsAgent:
|
||||
(proxy &&
|
||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
||||
? new HttpsProxyAgent(proxy)
|
||||
: proxy.startsWith("socks")
|
||||
? new SocksProxyAgent(proxy)
|
||||
: undefined)) ||
|
||||
undefined
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
181
src/utils/get/getUserPosts.ts
Normal file
181
src/utils/get/getUserPosts.ts
Normal file
@ -0,0 +1,181 @@
|
||||
import Axios from "axios"
|
||||
import { _tiktokGetPosts, _tiktokurl } from "../../constants/api"
|
||||
import {
|
||||
AuthorPost,
|
||||
Posts,
|
||||
TiktokUserPostsResponse
|
||||
} from "../../types/get/getUserPosts"
|
||||
import { _getUserPostsParams, _xttParams } from "../../constants/params"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
import { TiktokService } from "../../services/tiktokService"
|
||||
import { StalkUser } from "../get/getProfile"
|
||||
|
||||
export const getUserPosts = (
|
||||
username: string,
|
||||
proxy?: string,
|
||||
postLimit?: number
|
||||
): Promise<TiktokUserPostsResponse> =>
|
||||
new Promise((resolve) => {
|
||||
StalkUser(username).then(async (res) => {
|
||||
if (res.status === "error") {
|
||||
return resolve({
|
||||
status: "error",
|
||||
message: res.message
|
||||
})
|
||||
}
|
||||
|
||||
const secUid = res.result.user.secUid
|
||||
const data = await parseUserPosts(secUid, postLimit, proxy)
|
||||
|
||||
resolve({
|
||||
status: "success",
|
||||
result: data,
|
||||
totalPosts: data.length
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const parseUserPosts = async (
|
||||
secUid: string,
|
||||
postLimit?: number,
|
||||
proxy?: string
|
||||
): Promise<Posts[]> => {
|
||||
// Posts Result
|
||||
let hasMore = true
|
||||
let cursor = 0
|
||||
const posts: Posts[] = []
|
||||
let counter = 0
|
||||
while (hasMore) {
|
||||
let result: any | null = null
|
||||
|
||||
// Prevent missing response posts
|
||||
for (let i = 0; i < 30; i++) {
|
||||
result = await requestUserPosts(secUid, cursor, postLimit, proxy)
|
||||
if (result !== "") break
|
||||
}
|
||||
|
||||
// Validate
|
||||
if (result === "") {
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
|
||||
result?.itemList?.forEach((v: any) => {
|
||||
const author: AuthorPost = {
|
||||
id: v.author.id,
|
||||
username: v.author.uniqueId,
|
||||
nickname: v.author.nickname,
|
||||
avatarLarger: v.author.avatarLarger,
|
||||
avatarThumb: v.author.avatarThumb,
|
||||
avatarMedium: v.author.avatarMedium,
|
||||
signature: v.author.signature,
|
||||
verified: v.author.verified,
|
||||
openFavorite: v.author.openFavorite,
|
||||
privateAccount: v.author.privateAccount,
|
||||
isADVirtual: v.author.isADVirtual,
|
||||
isEmbedBanned: v.author.isEmbedBanned
|
||||
}
|
||||
|
||||
if (v.imagePost) {
|
||||
const imagePost: string[] = v.imagePost.images.map(
|
||||
(img: any) => img.imageURL.urlList[0]
|
||||
)
|
||||
|
||||
posts.push({
|
||||
id: v.id,
|
||||
desc: v.desc,
|
||||
createTime: v.createTime,
|
||||
digged: v.digged,
|
||||
duetEnabled: v.duetEnabled,
|
||||
forFriend: v.forFriend,
|
||||
officalItem: v.officalItem,
|
||||
originalItem: v.originalItem,
|
||||
privateItem: v.privateItem,
|
||||
shareEnabled: v.shareEnabled,
|
||||
stitchEnabled: v.stitchEnabled,
|
||||
stats: v.stats,
|
||||
music: v.music,
|
||||
author,
|
||||
imagePost
|
||||
})
|
||||
} else {
|
||||
const video = {
|
||||
id: v.video.id,
|
||||
duration: v.video.duration,
|
||||
format: v.video.format,
|
||||
bitrate: v.video.bitrate,
|
||||
ratio: v.video.ratio,
|
||||
playAddr: v.video.playAddr,
|
||||
cover: v.video.cover,
|
||||
originCover: v.video.originCover,
|
||||
dynamicCover: v.video.dynamicCover,
|
||||
downloadAddr: v.video.downloadAddr
|
||||
}
|
||||
|
||||
posts.push({
|
||||
id: v.id,
|
||||
desc: v.desc,
|
||||
createTime: v.createTime,
|
||||
digged: v.digged,
|
||||
duetEnabled: v.duetEnabled,
|
||||
forFriend: v.forFriend,
|
||||
officalItem: v.officalItem,
|
||||
originalItem: v.originalItem,
|
||||
privateItem: v.privateItem,
|
||||
shareEnabled: v.shareEnabled,
|
||||
stitchEnabled: v.stitchEnabled,
|
||||
stats: v.stats,
|
||||
music: v.music,
|
||||
author,
|
||||
video
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Update hasMore and cursor for next iteration
|
||||
hasMore = result.hasMore
|
||||
cursor = hasMore ? result.cursor : null
|
||||
counter++
|
||||
|
||||
// Check post limit if specified
|
||||
if (postLimit && posts.length >= postLimit) {
|
||||
hasMore = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return postLimit ? posts.slice(0, postLimit) : posts
|
||||
}
|
||||
|
||||
const requestUserPosts = async (
|
||||
secUid: string,
|
||||
cursor: number = 0,
|
||||
count: number = 30,
|
||||
proxy?: string
|
||||
): Promise<any> => {
|
||||
const Tiktok = new TiktokService()
|
||||
|
||||
const { data } = await Axios.get(
|
||||
`${_tiktokGetPosts(_getUserPostsParams())}`,
|
||||
{
|
||||
headers: {
|
||||
"user-agent":
|
||||
"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",
|
||||
"X-tt-params": Tiktok.generateXTTParams(
|
||||
_xttParams(secUid, cursor, count)
|
||||
)
|
||||
},
|
||||
httpsAgent:
|
||||
(proxy &&
|
||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
||||
? new HttpsProxyAgent(proxy)
|
||||
: proxy.startsWith("socks")
|
||||
? new SocksProxyAgent(proxy)
|
||||
: undefined)) ||
|
||||
undefined
|
||||
}
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import chalk from "chalk"
|
||||
|
||||
export class Logger {
|
||||
static success(message: string): void {
|
||||
console.log(chalk.green("✓ " + message))
|
||||
}
|
||||
|
||||
static error(message: string): void {
|
||||
console.error(chalk.red("✗ " + message))
|
||||
}
|
||||
|
||||
static info(message: string): void {
|
||||
console.log(chalk.blue("ℹ " + message))
|
||||
}
|
||||
|
||||
static warning(message: string): void {
|
||||
console.log(chalk.yellow("⚠ " + message))
|
||||
}
|
||||
|
||||
static result(message: string, color = chalk.cyan): void {
|
||||
console.log(color(message))
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import {
|
||||
LiveInfo,
|
||||
Owner,
|
||||
OwnerStats,
|
||||
LiveSearchResult,
|
||||
TiktokLiveSearchResponse
|
||||
} from "../../types/search/liveSearch"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
@ -66,7 +67,7 @@ export const SearchLive = async (
|
||||
if (!data.data)
|
||||
return resolve({ status: "error", message: "Live not found!" })
|
||||
|
||||
const result = []
|
||||
const result: LiveSearchResult[] = []
|
||||
data.data.forEach((v: any) => {
|
||||
const content = JSON.parse(v.live_info.raw_data)
|
||||
|
||||
|
||||
@ -1,32 +1,14 @@
|
||||
import Axios from "axios"
|
||||
import { _tiktokSearchUserFull, _tiktokurl } from "../../constants/api"
|
||||
import { TiktokUserSearchResponse } from "../../types/search/userSearch"
|
||||
import {
|
||||
UserSearchResult,
|
||||
TiktokUserSearchResponse
|
||||
} from "../../types/search/userSearch"
|
||||
import { _userSearchParams } from "../../constants/params"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
import xbogus from "../../../helper/xbogus"
|
||||
|
||||
const userAgent =
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
|
||||
|
||||
/**
|
||||
* Generate URL with X-Bogus
|
||||
* Special thanks to https://github.com/iamatef/xbogus
|
||||
* @param {string} username - The username you want to search
|
||||
* @param {number} page - The page you want to search
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
export const generateURLXbogus = (username: string, page: number) => {
|
||||
const url =
|
||||
"https://www.tiktok.com/api/search/user/full/?" +
|
||||
_userSearchParams(username, page)
|
||||
const xbogusParams = xbogus(url, userAgent)
|
||||
const urlXbogus =
|
||||
"https://www.tiktok.com/api/search/user/full/?" +
|
||||
_userSearchParams(username, page, xbogusParams)
|
||||
return urlXbogus
|
||||
}
|
||||
import { TiktokService } from "../../services/tiktokService"
|
||||
import { userAgent } from "../../constants/headers"
|
||||
|
||||
/**
|
||||
* Tiktok Search User
|
||||
@ -50,7 +32,10 @@ export const SearchUser = (
|
||||
message: "Cookie is required!"
|
||||
})
|
||||
}
|
||||
Axios(generateURLXbogus(username, page), {
|
||||
|
||||
const Tiktok = new TiktokService()
|
||||
|
||||
Axios(Tiktok.generateURLXbogus(username, page), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"User-Agent": userAgent,
|
||||
@ -83,7 +68,7 @@ export const SearchUser = (
|
||||
if (!data.user_list)
|
||||
return resolve({ status: "error", message: "User not found!" })
|
||||
|
||||
const result = []
|
||||
const result: UserSearchResult[] = []
|
||||
for (let i = 0; i < data.user_list.length; i++) {
|
||||
const user = data.user_list[i]
|
||||
result.push({
|
||||
|
||||
154
src/utils/search/videoSearch.ts
Normal file
154
src/utils/search/videoSearch.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import Axios from "axios"
|
||||
import { _tiktokSearchVideoFull } from "../../constants/api"
|
||||
import { _liveSearchParams, _videoSearchParams } from "../../constants/params"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
import { TiktokService } from "../../services/tiktokService"
|
||||
import {
|
||||
TiktokVideoSearchResponse,
|
||||
AuthorVideoSearch,
|
||||
MusicVideoSearch,
|
||||
VideoSearch,
|
||||
VideoSearchResult,
|
||||
StatisticsVideoSearch
|
||||
} from "../../types/search/videoSearch"
|
||||
|
||||
/**
|
||||
* Tiktok Search Live
|
||||
* @param {string} keyword - The keyword you want to search
|
||||
* @param {string | any[]} cookie - Your Tiktok cookie (optional)
|
||||
* @param {number} page - The page you want to search (optional)
|
||||
* @param {string} proxy - Your Proxy (optional)
|
||||
* @returns {Promise<TiktokVideoSearchResponse>}
|
||||
*/
|
||||
|
||||
export const SearchVideo = async (
|
||||
keyword: string,
|
||||
cookie: string | any[],
|
||||
page: number = 1,
|
||||
proxy?: string
|
||||
): Promise<TiktokVideoSearchResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
if (!cookie) {
|
||||
return resolve({
|
||||
status: "error",
|
||||
message: "Cookie is required!"
|
||||
})
|
||||
}
|
||||
|
||||
const Tiktok = new TiktokService()
|
||||
const url = new URL(
|
||||
_tiktokSearchVideoFull(_videoSearchParams(keyword, page))
|
||||
)
|
||||
const signature = Tiktok.generateSignature(url)
|
||||
url.searchParams.append("_signature", signature)
|
||||
const xbogus = Tiktok.generateXBogus(url, signature)
|
||||
url.searchParams.append("X-Bogus", xbogus)
|
||||
|
||||
Axios(_tiktokSearchVideoFull(_videoSearchParams(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
|
||||
},
|
||||
httpsAgent:
|
||||
(proxy &&
|
||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
||||
? new HttpsProxyAgent(proxy)
|
||||
: proxy.startsWith("socks")
|
||||
? new SocksProxyAgent(proxy)
|
||||
: undefined)) ||
|
||||
undefined
|
||||
})
|
||||
.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.item_list)
|
||||
return resolve({ status: "error", message: "Video not found!" })
|
||||
|
||||
const result: VideoSearchResult[] = []
|
||||
data.item_list.forEach((v: any) => {
|
||||
const video: VideoSearch = {
|
||||
id: v.video.id,
|
||||
ratio: v.video.ratio,
|
||||
cover: v.video.cover,
|
||||
originCover: v.video.originCover,
|
||||
dynamicCover: v.video.dynamicCover,
|
||||
playAddr: v.video.playAddr,
|
||||
downloadAddr: v.video.downloadAddr,
|
||||
format: v.video.format
|
||||
}
|
||||
|
||||
const stats: StatisticsVideoSearch = {
|
||||
diggCount: v.stats.diggCount,
|
||||
shareCount: v.stats.shareCount,
|
||||
commentCount: v.stats.commentCount,
|
||||
playCount: v.stats.playCount,
|
||||
collectCount: v.stats.collectCount
|
||||
}
|
||||
|
||||
const author: AuthorVideoSearch = {
|
||||
id: v.author.id,
|
||||
uniqueId: v.author.uniqueId,
|
||||
nickname: v.author.nickname,
|
||||
avatarThumb: v.author.avatarThumb,
|
||||
avatarMedium: v.author.avatarMedium,
|
||||
avatarLarger: v.author.avatarLarger,
|
||||
signature: v.author.signature,
|
||||
verified: v.author.verified,
|
||||
secUid: v.author.secUid,
|
||||
openFavorite: v.author.openFavorite,
|
||||
privateAccount: v.author.privateAccount,
|
||||
isADVirtual: v.author.isADVirtual,
|
||||
tiktokSeller: v.author.ttSeller,
|
||||
isEmbedBanned: v.author.isEmbedBanned
|
||||
}
|
||||
|
||||
const music: MusicVideoSearch = {
|
||||
id: v.music.id,
|
||||
title: v.music.title,
|
||||
playUrl: v.music.playUrl,
|
||||
coverThumb: v.music.coverThumb,
|
||||
coverMedium: v.music.coverMedium,
|
||||
coverLarge: v.music.coverLarge,
|
||||
authorName: v.music.authorName,
|
||||
original: v.music.original,
|
||||
album: v.music.album,
|
||||
duration: v.music.duration,
|
||||
isCopyrighted: v.music.isCopyrighted
|
||||
}
|
||||
result.push({
|
||||
id: v.id,
|
||||
desc: v.desc,
|
||||
createTime: v.createTime,
|
||||
author,
|
||||
stats,
|
||||
video,
|
||||
music
|
||||
})
|
||||
})
|
||||
|
||||
resolve({
|
||||
status: "success",
|
||||
result,
|
||||
page,
|
||||
totalResults: result.length
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
resolve({ status: "error", message: e.message })
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user