feat: add page & totalResults object
This commit is contained in:
parent
687a3daf1a
commit
1bae63d2e8
@ -2,6 +2,8 @@ export type TiktokLiveSearchResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: Result[]
|
||||
page?: number
|
||||
totalResults?: number
|
||||
}
|
||||
|
||||
export type Result = {
|
||||
@ -34,7 +36,7 @@ export type Stats = {
|
||||
}
|
||||
|
||||
export type Owner = {
|
||||
id: string
|
||||
uid: string
|
||||
nickname: string
|
||||
username: string
|
||||
signature: string
|
||||
|
||||
@ -1,135 +0,0 @@
|
||||
export type StalkResult = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: {
|
||||
users: Users
|
||||
stats: Stats
|
||||
posts: Posts[]
|
||||
}
|
||||
}
|
||||
|
||||
export type Users = {
|
||||
id: string
|
||||
username: string
|
||||
nickname: string
|
||||
avatarLarger: string
|
||||
avatarThumb: string
|
||||
avatarMedium: string
|
||||
signature: string
|
||||
verified: boolean
|
||||
privateAccount: boolean
|
||||
region: string
|
||||
commerceUser: boolean
|
||||
usernameModifyTime: number
|
||||
nicknameModifyTime: number
|
||||
}
|
||||
|
||||
export type Stats = {
|
||||
followerCount: number
|
||||
followingCount: number
|
||||
heartCount: number
|
||||
videoCount: number
|
||||
likeCount: number
|
||||
friendCount: number
|
||||
postCount: number
|
||||
}
|
||||
|
||||
export type Statistics = {
|
||||
likeCount: number
|
||||
shareCount: number
|
||||
commentCount: number
|
||||
playCount: number
|
||||
favoriteCount: number
|
||||
}
|
||||
|
||||
export type Video = {
|
||||
id: string
|
||||
duration: string
|
||||
ratio: string
|
||||
cover: string
|
||||
originCover: string
|
||||
dynamicCover: string
|
||||
playAddr: string
|
||||
downloadAddr: string
|
||||
format: string
|
||||
bitrate: number
|
||||
}
|
||||
|
||||
export type Music = {
|
||||
id: string
|
||||
title: string
|
||||
album: string
|
||||
playUrl: string
|
||||
coverLarge: string
|
||||
coverMedium: string
|
||||
coverThumb: string
|
||||
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
|
||||
}
|
||||
@ -2,6 +2,8 @@ export type TiktokUserSearchResponse = {
|
||||
status: "success" | "error"
|
||||
message?: string
|
||||
result?: Result[]
|
||||
page?: number
|
||||
totalResults?: number
|
||||
}
|
||||
|
||||
export type Result = {
|
||||
|
||||
@ -21,8 +21,8 @@ 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) =>
|
||||
new Promise<getRequest>((resolve) => {
|
||||
const getRequest = (url: string, proxy?: string): Promise<getRequest> =>
|
||||
new Promise((resolve) => {
|
||||
if (!TiktokURLregex.test(url)) {
|
||||
return resolve({
|
||||
status: "error",
|
||||
@ -98,8 +98,11 @@ const getRequest = (url: string, proxy?: string) =>
|
||||
* @returns {Promise<MusicalDownResponse>}
|
||||
*/
|
||||
|
||||
export const MusicalDown = (url: string, proxy?: string) =>
|
||||
new Promise<MusicalDownResponse>(async (resolve) => {
|
||||
export const MusicalDown = (
|
||||
url: string,
|
||||
proxy?: string
|
||||
): Promise<MusicalDownResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
const request: getRequest = await getRequest(url)
|
||||
if (request.status !== "success")
|
||||
return resolve({ status: "error", message: request.message })
|
||||
|
||||
@ -19,8 +19,8 @@ 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 fetchTT = (proxy?: string) =>
|
||||
new Promise<SSSTikFetchTT>(async (resolve) => {
|
||||
const fetchTT = (proxy?: string): Promise<SSSTikFetchTT> =>
|
||||
new Promise(async (resolve) => {
|
||||
Axios(_ssstikurl, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -59,8 +59,8 @@ const fetchTT = (proxy?: string) =>
|
||||
* @returns {Promise<SSSTikResponse>}
|
||||
*/
|
||||
|
||||
export const SSSTik = (url: string, proxy?: string) =>
|
||||
new Promise<SSSTikResponse>(async (resolve) => {
|
||||
export const SSSTik = (url: string, proxy?: string): Promise<SSSTikResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
try {
|
||||
if (!TiktokURLregex.test(url)) {
|
||||
return resolve({
|
||||
|
||||
269
src/utils/get/getProfile.ts
Normal file
269
src/utils/get/getProfile.ts
Normal file
@ -0,0 +1,269 @@
|
||||
import Axios from "axios"
|
||||
import { load } from "cheerio"
|
||||
import { _tiktokGetPosts, _tiktokurl } from "../../constants/api"
|
||||
import {
|
||||
AuthorPost,
|
||||
Posts,
|
||||
StalkResult,
|
||||
Stats,
|
||||
Users
|
||||
} 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 {object|string} cookie - Your Tiktok Cookie (optional)
|
||||
* @param {number} postLimit - The limit of post you want to get (optional)
|
||||
* @param {string} proxy - Your Proxy (optional)
|
||||
* @returns {Promise<StalkResult>}
|
||||
*/
|
||||
|
||||
export const StalkUser = (
|
||||
username: string,
|
||||
cookie?: any,
|
||||
postLimit?: number,
|
||||
proxy?: string
|
||||
): Promise<StalkResult> =>
|
||||
new Promise(async (resolve) => {
|
||||
username = username.replace("@", "")
|
||||
Axios(`${_tiktokurl}/@${username}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"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",
|
||||
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(async ({ data }) => {
|
||||
const $ = load(data)
|
||||
const result = JSON.parse(
|
||||
$("script#__UNIVERSAL_DATA_FOR_REHYDRATION__").text()
|
||||
)
|
||||
if (
|
||||
!result["__DEFAULT_SCOPE__"] &&
|
||||
!result["__DEFAULT_SCOPE__"]["webapp.user-detail"]
|
||||
) {
|
||||
return resolve({
|
||||
status: "error",
|
||||
message: "User not found!"
|
||||
})
|
||||
}
|
||||
const dataUser =
|
||||
result["__DEFAULT_SCOPE__"]["webapp.user-detail"]["userInfo"]
|
||||
|
||||
const posts: Posts[] = await parsePosts(dataUser, postLimit, proxy)
|
||||
const { users, stats } = parseDataUser(dataUser, posts)
|
||||
|
||||
let response: StalkResult = {
|
||||
status: "success",
|
||||
result: {
|
||||
users,
|
||||
stats,
|
||||
posts
|
||||
},
|
||||
totalPosts: posts.length
|
||||
}
|
||||
|
||||
resolve(response)
|
||||
})
|
||||
.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[]) => {
|
||||
// User Info Result
|
||||
const users: Users = {
|
||||
uid: dataUser.user.id,
|
||||
username: dataUser.user.uniqueId,
|
||||
nickname: dataUser.user.nickname,
|
||||
avatarLarger: dataUser.user.avatarLarger,
|
||||
avatarThumb: dataUser.user.avatarThumb,
|
||||
avatarMedium: dataUser.user.avatarMedium,
|
||||
signature: dataUser.user.signature,
|
||||
verified: dataUser.user.verified,
|
||||
privateAccount: dataUser.user.privateAccount,
|
||||
region: dataUser.user.region,
|
||||
commerceUser: dataUser.user.commerceUserInfo.commerceUser,
|
||||
usernameModifyTime: dataUser.user.uniqueIdModifyTime,
|
||||
nicknameModifyTime: dataUser.user.nickNameModifyTime
|
||||
}
|
||||
|
||||
// Statistics Result
|
||||
const stats: Stats = {
|
||||
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
|
||||
}
|
||||
|
||||
return { users, stats }
|
||||
}
|
||||
|
||||
const parsePosts = async (
|
||||
dataUser: any,
|
||||
postLimit?: number,
|
||||
proxy?: string
|
||||
): Promise<Posts[]> => {
|
||||
// Posts Result
|
||||
let hasMore = true
|
||||
let cursor: number | null = null
|
||||
const posts: Posts[] = []
|
||||
while (hasMore) {
|
||||
let result: any | null = null
|
||||
let counter = 0
|
||||
|
||||
// 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 // No More Post
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Restrict too many data requests
|
||||
if (postLimit !== 0) {
|
||||
let loopCount = Math.floor(postLimit / 30)
|
||||
if (counter >= loopCount) hasMore = false
|
||||
}
|
||||
|
||||
hasMore = result.hasMore
|
||||
cursor = hasMore ? result.cursor : null
|
||||
counter++
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
}
|
||||
@ -1,14 +1,19 @@
|
||||
import Axios from "axios"
|
||||
import { _tiktokSearchLiveFull } from "../../constants/api"
|
||||
import { _liveSearchParams } from "../../constants/params"
|
||||
import { LiveInfo, Owner, OwnerStats } from "../../types/search/liveSearch"
|
||||
import {
|
||||
LiveInfo,
|
||||
Owner,
|
||||
OwnerStats,
|
||||
TiktokLiveSearchResponse
|
||||
} from "../../types/search/liveSearch"
|
||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
|
||||
/**
|
||||
* Tiktok Search Live
|
||||
* @param {string} keyword - The keyword you want to search
|
||||
* @param {object|string} cookie - Your Tiktok cookie (optional)
|
||||
* @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<TiktokLiveSearchResponse>}
|
||||
@ -16,11 +21,17 @@ import { HttpsProxyAgent } from "https-proxy-agent"
|
||||
|
||||
export const SearchLive = async (
|
||||
keyword: string,
|
||||
cookie?: any,
|
||||
cookie: string | any[],
|
||||
page: number = 1,
|
||||
proxy?: string
|
||||
) =>
|
||||
): Promise<TiktokLiveSearchResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
if (!cookie) {
|
||||
return resolve({
|
||||
status: "error",
|
||||
message: "Cookie is required!"
|
||||
})
|
||||
}
|
||||
Axios(_tiktokSearchLiveFull(_liveSearchParams(keyword, page)), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -75,7 +86,7 @@ export const SearchLive = async (
|
||||
likeCount: content.like_count
|
||||
},
|
||||
owner: {
|
||||
id: content.owner.id,
|
||||
uid: content.owner.id,
|
||||
nickname: content.owner.nickname,
|
||||
username: content.owner.display_id,
|
||||
signature: content.owner.bio_description,
|
||||
@ -101,7 +112,12 @@ export const SearchLive = async (
|
||||
result.push({ roomInfo, liveInfo })
|
||||
})
|
||||
|
||||
resolve({ status: "success", result })
|
||||
resolve({
|
||||
status: "success",
|
||||
result,
|
||||
page,
|
||||
totalResults: data.result.length
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
resolve({ status: "error", message: e.message })
|
||||
|
||||
@ -31,7 +31,7 @@ export const generateURLXbogus = (username: string, page: number) => {
|
||||
/**
|
||||
* Tiktok Search User
|
||||
* @param {string} username - The username you want to search
|
||||
* @param {object|string} cookie - Your Tiktok cookie (optional)
|
||||
* @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<TiktokUserSearchResponse>}
|
||||
@ -39,11 +39,17 @@ export const generateURLXbogus = (username: string, page: number) => {
|
||||
|
||||
export const SearchUser = (
|
||||
username: string,
|
||||
cookie?: any,
|
||||
cookie: string | any[],
|
||||
page: number = 1,
|
||||
proxy?: string
|
||||
): Promise<TiktokUserSearchResponse> =>
|
||||
new Promise(async (resolve) => {
|
||||
if (!cookie) {
|
||||
return resolve({
|
||||
status: "error",
|
||||
message: "Cookie is required!"
|
||||
})
|
||||
}
|
||||
Axios(generateURLXbogus(username, page), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
@ -93,7 +99,12 @@ export const SearchUser = (
|
||||
})
|
||||
}
|
||||
|
||||
resolve({ status: "success", result })
|
||||
resolve({
|
||||
status: "success",
|
||||
result,
|
||||
page,
|
||||
totalResults: data.result.length
|
||||
})
|
||||
})
|
||||
.catch((e) => {
|
||||
resolve({ status: "error", message: e.message })
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user