feat: added tiktok downloader v2 & response posts tiktok stalk

This commit is contained in:
TobyG74 2023-10-08 17:02:23 +07:00
parent 246c6bdb30
commit f7f6d3776a
10 changed files with 264 additions and 9 deletions

View File

@ -1,2 +1,2 @@
export const _tiktokurl: string = "https://www.tiktok.com"
export const _tiktokapi = (id: string): string => `https://api16-core.tiktokv.com/aweme/v1/feed/?aweme_id=${id}`
export const _tiktokapi = (id: string): string => `https://api.tiktokv.com/aweme/v1/feed/?aweme_id=${id}`

View File

@ -1,2 +0,0 @@
export * from "./downloader"
export * from "./stalker"

View File

@ -4,6 +4,7 @@ export interface StalkResult {
result?: {
users: Users
stats: Stats
posts: Posts[]
}
}
@ -15,6 +16,7 @@ export interface Users {
avatarMedium: string
signature: string
verified: boolean
privateAccount: boolean
region: string
commerceUser: boolean
usernameModifyTime: number
@ -28,4 +30,50 @@ export interface Stats {
videoCount: number
likeCount: number
friendCount: number
postCount: number
}
export interface Posts {
id: string
desc: string
createTime: number
author: string
locationCreated: string
hashtags: string[]
statistics: Statistics
video: Video
music: Music
}
export interface Statistics {
likeCount: number
shareCount: number
commentCount: number
playCount: number
favoriteCount: number
}
export interface Video {
id: string
duration: string
ratio: string
cover: string
originCover: string
dynamicCover: string
playAddr: string
downloadAddr: string
format: string
bitrate: number
}
export interface Music {
id: string
title: string
album: string
playUrl: string
coverLarge: string
coverMedium: string
coverThumb: string
authorName: string
duration: string
}

View File

@ -0,0 +1,30 @@
export interface TiktokFetchTT {
status: "success" | "error"
message?: string
result?: string
}
export interface TiktokDownload {
status: "success" | "error"
message?: string
result?: {
type: "image" | "video"
desc: string
author: Author
statistics: Statistics
images?: string[]
video?: string
music: string
}
}
export interface Author {
avatar: string
nickname: string
}
export interface Statistics {
likeCount: string
commentCount: string
shareCount: string
}

View File

@ -1,2 +1,2 @@
export * from "./downloader"
export * from "./switch"
export * from "./stalker"

View File

@ -1,7 +1,7 @@
import axios from "axios"
import { load } from "cheerio"
import { _tiktokurl } from "../api"
import { StalkResult, Stats, Users } from "../types"
import { Music, Posts, StalkResult, Statistics, Stats, Users, Video } from "../types/stalker"
const getCookie = () =>
new Promise((resolve, reject) => {
@ -33,6 +33,9 @@ export const TiktokStalk = (username: string, options: { cookie: string }): Prom
})
}
const user = result.UserModule
const itemKeys = Object.keys(result.ItemModule)
// User Info Result
const users: Users = {
username: user.users[username].uniqueId,
nickname: user.users[username].nickname,
@ -41,24 +44,92 @@ export const TiktokStalk = (username: string, options: { cookie: string }): Prom
avatarMedium: user.users[username].avatarMedium,
signature: user.users[username].signature,
verified: user.users[username].verified,
privateAccount: user.users[username].privateAccount,
region: user.users[username].region,
commerceUser: user.users[username].commerceUserInfo.commerceUser,
usernameModifyTime: user.users[username].uniqueIdModifyTime,
nicknameModifyTime: user.users[username].nickNameModifyTime
}
// Statistics Result
const stats: Stats = {
followerCount: user.stats[username].followerCount,
followingCount: user.stats[username].followingCount,
heartCount: user.stats[username].heartCount,
videoCount: user.stats[username].videoCount,
likeCount: user.stats[username].diggCount,
friendCount: user.stats[username].friendCount
friendCount: user.stats[username].friendCount,
postCount: itemKeys.length
}
// Posts Result
const posts: Posts[] = []
itemKeys.forEach((key) => {
const post = result.ItemModule[key]
let media
if (post.imagePost) {
// Images or Slide Posts Result
media = {
images: post.imagePost.images.map((v: any) => v.imageURL.urlList[0])
}
} else {
// Video Posts Result
media = {
video: {
id: post.video.id,
duration: post.video.duration,
ratio: post.video.ratio,
cover: post.video.cover,
originCover: post.video.originCover,
dynamicCover: post.video.dynamicCover,
playAddr: post.video.playAddr,
downloadAddr: post.video.downloadAddr,
format: post.video.format,
bitrate: post.video.bitrate
} as Video
}
}
// Music Posts Result
const music: Music = {
id: post.music.id,
title: post.music.title,
authorName: post.music.authorName,
album: post.music.album,
coverLarge: post.music.coverLarge,
coverMedium: post.music.coverMedium,
coverThumb: post.music.coverThumb,
playUrl: post.music.playUrl,
duration: post.music.duration
}
// Statistics Posts Result
const statistics: Statistics = {
likeCount: post.stats.diggCount,
shareCount: post.stats.shareCount,
commentCount: post.stats.commentCount,
playCount: post.stats.playCount,
favoriteCount: post.stats.collectCount
}
posts.push({
id: post.id,
desc: post.desc,
createTime: post.createTime,
author: post.author,
locationCreated: post.locationCreated,
hashtags: post.challenges.map((v: any) => v.title),
statistics,
music,
...media
})
})
resolve({
status: "success",
result: {
users,
stats
stats,
posts
}
})
})

17
src/utils/switch.ts Normal file
View File

@ -0,0 +1,17 @@
import { TiktokDownload } from "./tiktokdownload"
import { TiktokAPI } from "./tiktokapi"
export const TiktokDL = (url: string, options: { version: "v1" | "v2" }) =>
new Promise(async (resolve, reject) => {
switch (options.version) {
case "v1": {
await TiktokAPI(url).then(resolve).catch(reject)
}
case "v2": {
await TiktokDownload(url).then(resolve).catch(reject)
}
default: {
await TiktokAPI(url).then(resolve).catch(reject)
}
}
})

View File

@ -1,6 +1,6 @@
import axios from "axios"
import { _tiktokapi, _tiktokurl } from "../api"
import { Author, DLResult, Statistics, Music } from "../types"
import { Author, DLResult, Statistics, Music } from "../types/tiktokapi"
const toMinute = (duration) => {
const mins = ~~((duration % 3600) / 60)
@ -14,7 +14,7 @@ const toMinute = (duration) => {
return ret
}
export const TiktokDL = (url: string): Promise<DLResult> =>
export const TiktokAPI = (url: string): Promise<DLResult> =>
new Promise((resolve, reject) => {
url = url.replace("https://vm", "https://vt")
axios

View File

@ -0,0 +1,91 @@
import Axios from "axios"
import { load } from "cheerio"
import { Author, Statistics, TiktokFetchTT } from "../types/tiktokdownload"
const fetchTT = (): Promise<TiktokFetchTT> =>
new Promise(async (resolve, reject) => {
Axios.get("https://tiktokdownload.online/", {
headers: {
"user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
}
})
.then(({ data }) => {
const regex = /form\.setAttribute\("include-vals",\s*"([^"]+)"\)/
const match = data.match(regex)
if (match) {
const includeValsValue = match[1]
resolve({ status: "success", result: includeValsValue })
} else {
resolve({ status: "error", message: "Not found" })
}
})
.catch((e) => resolve({ status: "error", message: e.message }))
})
export const TiktokDownload = (url: string) =>
new Promise(async (resolve, reject) => {
const tt: TiktokFetchTT = await fetchTT()
if (tt.status !== "success") return resolve(tt)
Axios("https://tiktokdownload.online/abc?url=dl", {
method: "POST",
data: new URLSearchParams(
Object.entries({
id: url,
locale: "en",
tt: tt.result
})
)
})
.then(({ data }) => {
const $ = load(data)
// Result
const desc = $("p.maintext").text().trim()
const author: Author = {
avatar: $("img.result_author").attr("src"),
nickname: $("h2").text().trim()
}
const statistics: Statistics = {
likeCount: $("#trending-actions > .justify-content-start").text().trim(),
commentCount: $("#trending-actions > .justify-content-center").text().trim(),
shareCount: $("#trending-actions > .justify-content-end").text().trim()
}
// Images / Slide Result
const images: string[] = []
$("ul.splide__list > li")
.get()
.map((img) => {
images.push($(img).find("img").attr("src"))
})
if (images.length !== 0) {
// Images / Slide Result
resolve({
status: "success",
result: {
type: "image",
desc,
author,
statistics,
images,
music: $("a.music").attr("href")
}
})
} else {
// Video Result
resolve({
status: "success",
result: {
type: "video",
desc,
author,
statistics,
video: $("a.without_watermark").attr("href"),
music: $("a.music").attr("href")
}
})
}
})
.catch(console.log)
})