fix: applying new types
This commit is contained in:
parent
a19dd9c062
commit
cb64f0585a
@ -1,5 +1,6 @@
|
|||||||
import Axios from "axios"
|
import Axios from "axios"
|
||||||
import { load } from "cheerio"
|
import { load } from "cheerio"
|
||||||
|
type CheerioAPI = ReturnType<typeof load>
|
||||||
import {
|
import {
|
||||||
MusicalDownResponse,
|
MusicalDownResponse,
|
||||||
GetMusicalDownMusic,
|
GetMusicalDownMusic,
|
||||||
@ -12,87 +13,178 @@ import {
|
|||||||
} from "../../constants/api"
|
} from "../../constants/api"
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||||
|
import { ERROR_MESSAGES } from "../../constants"
|
||||||
|
|
||||||
/**
|
/** Constants */
|
||||||
* Using API from Website:
|
const TIKTOK_URL_REGEX =
|
||||||
* BASE URL : https://ssstik.io
|
|
||||||
*/
|
|
||||||
|
|
||||||
const TiktokURLregex =
|
|
||||||
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
|
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
|
||||||
|
const USER_AGENT =
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"
|
||||||
|
|
||||||
const getRequest = (
|
/** Types */
|
||||||
|
interface ProxyConfig {
|
||||||
|
httpsAgent?: HttpsProxyAgent<string> | SocksProxyAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestForm {
|
||||||
|
[key: string]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Helper Functions */
|
||||||
|
const createProxyAgent = (proxy?: string): ProxyConfig => {
|
||||||
|
if (!proxy) return {}
|
||||||
|
|
||||||
|
const isHttpProxy = proxy.startsWith("http") || proxy.startsWith("https")
|
||||||
|
const isSocksProxy = proxy.startsWith("socks")
|
||||||
|
|
||||||
|
if (!isHttpProxy && !isSocksProxy) return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
httpsAgent: isHttpProxy
|
||||||
|
? new HttpsProxyAgent(proxy)
|
||||||
|
: new SocksProxyAgent(proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateTikTokUrl = (url: string): boolean => {
|
||||||
|
return TIKTOK_URL_REGEX.test(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidUrl = (url: string): boolean => {
|
||||||
|
try {
|
||||||
|
new URL(url)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractRequestForm = ($: CheerioAPI): RequestForm => {
|
||||||
|
const input = $("div > input").map((_, el) => $(el))
|
||||||
|
return {
|
||||||
|
[input.get(0).attr("name") || ""]: input.get(0).attr("value") || "",
|
||||||
|
[input.get(1).attr("name") || ""]: input.get(1).attr("value") || "",
|
||||||
|
[input.get(2).attr("name") || ""]: input.get(2).attr("value") || ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseImages = ($: CheerioAPI): string[] => {
|
||||||
|
const images: string[] = []
|
||||||
|
$("div.row > div[class='col s12 m3']").each((_, v) => {
|
||||||
|
const src = $(v).find("img").attr("src")
|
||||||
|
if (src) images.push(src)
|
||||||
|
})
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseVideos = ($: CheerioAPI): Record<string, string> => {
|
||||||
|
const videos: Record<string, string> = {}
|
||||||
|
const videoContainer = $("div.row > div")
|
||||||
|
.map((_, el) => $(el))
|
||||||
|
.get(1)
|
||||||
|
|
||||||
|
if (!videoContainer) return videos
|
||||||
|
|
||||||
|
$(videoContainer)
|
||||||
|
.find("a")
|
||||||
|
.each((_, v) => {
|
||||||
|
const href = $(v).attr("href")
|
||||||
|
if (!href || href === "#modal2") return
|
||||||
|
|
||||||
|
if (!isValidUrl(href)) return
|
||||||
|
|
||||||
|
const dataEvent = $(v).attr("data-event") || ""
|
||||||
|
const onclick = $(v).attr("onclick") || ""
|
||||||
|
const downloadUrl =
|
||||||
|
href !== undefined ? href : /downloadX\('([^']+)'\)/.exec(onclick)?.[1]
|
||||||
|
|
||||||
|
if (!downloadUrl) return
|
||||||
|
|
||||||
|
if (dataEvent.includes("hd")) {
|
||||||
|
videos.videoHD = downloadUrl
|
||||||
|
} else if (dataEvent.includes("mp4")) {
|
||||||
|
videos.videoSD = downloadUrl
|
||||||
|
} else if (dataEvent.includes("watermark")) {
|
||||||
|
videos.videoWatermark = downloadUrl
|
||||||
|
} else if (href.includes("type=mp3")) {
|
||||||
|
videos.music = downloadUrl
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return videos
|
||||||
|
}
|
||||||
|
|
||||||
|
const createImageResponse = (images: string[]): MusicalDownResponse => ({
|
||||||
|
status: "success",
|
||||||
|
result: {
|
||||||
|
type: "image",
|
||||||
|
images
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const createVideoResponse = (
|
||||||
|
$: CheerioAPI,
|
||||||
|
videos: Record<string, string>
|
||||||
|
): MusicalDownResponse => ({
|
||||||
|
status: "success",
|
||||||
|
result: {
|
||||||
|
type: "video",
|
||||||
|
author: {
|
||||||
|
avatar: $("div.img-area > img").attr("src") || "",
|
||||||
|
nickname: $("h2.video-author > b").text()
|
||||||
|
},
|
||||||
|
desc: $("p.video-desc").text(),
|
||||||
|
...videos
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const getRequest = async (
|
||||||
url: string,
|
url: string,
|
||||||
proxy?: string
|
proxy?: string
|
||||||
): Promise<GetMusicalDownReuqest> =>
|
): Promise<GetMusicalDownReuqest> => {
|
||||||
new Promise((resolve) => {
|
try {
|
||||||
if (!TiktokURLregex.test(url)) {
|
if (!validateTikTokUrl(url)) {
|
||||||
return resolve({
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
message: "Invalid Tiktok URL. Make sure your url is correct!"
|
message: ERROR_MESSAGES.INVALID_URL
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
Axios(_musicaldownurl, {
|
|
||||||
|
const { data, headers } = await Axios(_musicaldownurl, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Accept:
|
Accept:
|
||||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||||
|
|
||||||
"Update-Insecure-Requests": "1",
|
"Update-Insecure-Requests": "1",
|
||||||
"User-Agent":
|
"User-Agent": USER_AGENT
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"
|
|
||||||
},
|
},
|
||||||
httpsAgent:
|
...createProxyAgent(proxy)
|
||||||
(proxy &&
|
|
||||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
|
||||||
? new HttpsProxyAgent(proxy)
|
|
||||||
: proxy.startsWith("socks")
|
|
||||||
? new SocksProxyAgent(proxy)
|
|
||||||
: undefined)) ||
|
|
||||||
undefined
|
|
||||||
})
|
})
|
||||||
.then((data) => {
|
|
||||||
const cookie = data.headers["set-cookie"][0].split(";")[0]
|
|
||||||
const $ = load(data.data)
|
|
||||||
const input = $("div > input").map((_, el) => $(el))
|
|
||||||
const request = {
|
|
||||||
[input.get(0).attr("name")]: url,
|
|
||||||
[input.get(1).attr("name")]: input.get(1).attr("value"),
|
|
||||||
[input.get(2).attr("name")]: input.get(2).attr("value")
|
|
||||||
}
|
|
||||||
resolve({ status: "success", request, cookie })
|
|
||||||
})
|
|
||||||
.catch((e) =>
|
|
||||||
resolve({ status: "error", message: "Failed to get the request form!" })
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// const getMusic = (cookie: string, proxy?: string) =>
|
const cookie = headers["set-cookie"]?.[0]?.split(";")[0]
|
||||||
// new Promise<getMusic>((resolve) => {
|
if (!cookie) {
|
||||||
// Axios(_musicaldownmusicapi, {
|
return {
|
||||||
// method: "GET",
|
status: "error",
|
||||||
// headers: {
|
message: ERROR_MESSAGES.NETWORK_ERROR
|
||||||
// cookie: cookie,
|
}
|
||||||
// "Upgrade-Insecure-Requests": "1",
|
}
|
||||||
// "User-Agent":
|
|
||||||
// "Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"
|
const $ = load(data)
|
||||||
// },
|
const request = extractRequestForm($)
|
||||||
// httpsAgent:
|
|
||||||
// (proxy &&
|
return {
|
||||||
// (proxy.startsWith("http") || proxy.startsWith("https")
|
status: "success",
|
||||||
// ? new HttpsProxyAgent(proxy)
|
request,
|
||||||
// : proxy.startsWith("socks")
|
cookie
|
||||||
// ? new SocksProxyAgent(proxy)
|
}
|
||||||
// : undefined)) ||
|
} catch (error) {
|
||||||
// undefined
|
return {
|
||||||
// })
|
status: "error",
|
||||||
// .then(({ data }) => {
|
message:
|
||||||
// const $ = load(data)
|
error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR
|
||||||
// const music = $("audio > source").attr("src")
|
}
|
||||||
// resolve({ status: "success", result: music })
|
}
|
||||||
// })
|
}
|
||||||
// .catch((e) => resolve({ status: "error" }))
|
|
||||||
// })
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tiktok MusicalDown Downloader
|
* Tiktok MusicalDown Downloader
|
||||||
@ -100,16 +192,20 @@ const getRequest = (
|
|||||||
* @param {string} proxy - Proxy
|
* @param {string} proxy - Proxy
|
||||||
* @returns {Promise<MusicalDownResponse>}
|
* @returns {Promise<MusicalDownResponse>}
|
||||||
*/
|
*/
|
||||||
|
export const MusicalDown = async (
|
||||||
export const MusicalDown = (
|
|
||||||
url: string,
|
url: string,
|
||||||
proxy?: string
|
proxy?: string
|
||||||
): Promise<MusicalDownResponse> =>
|
): Promise<MusicalDownResponse> => {
|
||||||
new Promise(async (resolve) => {
|
try {
|
||||||
const request: GetMusicalDownReuqest = await getRequest(url)
|
const request = await getRequest(url, proxy)
|
||||||
if (request.status !== "success")
|
if (request.status !== "success") {
|
||||||
return resolve({ status: "error", message: request.message })
|
return {
|
||||||
Axios(_musicaldownapi, {
|
status: "error",
|
||||||
|
message: request.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await Axios(_musicaldownapi, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
cookie: request.cookie,
|
cookie: request.cookie,
|
||||||
@ -117,98 +213,33 @@ export const MusicalDown = (
|
|||||||
Origin: "https://musicaldown.com",
|
Origin: "https://musicaldown.com",
|
||||||
Referer: "https://musicaldown.com/en",
|
Referer: "https://musicaldown.com/en",
|
||||||
"Upgrade-Insecure-Requests": "1",
|
"Upgrade-Insecure-Requests": "1",
|
||||||
"User-Agent":
|
"User-Agent": USER_AGENT
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:127.0) Gecko/20100101 Firefox/127.0"
|
|
||||||
},
|
},
|
||||||
data: new URLSearchParams(Object.entries(request.request)),
|
data: new URLSearchParams(Object.entries(request.request)),
|
||||||
httpsAgent:
|
...createProxyAgent(proxy)
|
||||||
(proxy &&
|
|
||||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
|
||||||
? new HttpsProxyAgent(proxy)
|
|
||||||
: proxy.startsWith("socks")
|
|
||||||
? new SocksProxyAgent(proxy)
|
|
||||||
: undefined)) ||
|
|
||||||
undefined
|
|
||||||
})
|
})
|
||||||
.then(async ({ data }) => {
|
|
||||||
const $ = load(data)
|
|
||||||
|
|
||||||
// Get Image Video
|
const $ = load(data)
|
||||||
const images = []
|
const images = parseImages($)
|
||||||
$("div.row > div[class='col s12 m3']")
|
|
||||||
.get()
|
|
||||||
.map((v) => {
|
|
||||||
images.push($(v).find("img").attr("src"))
|
|
||||||
})
|
|
||||||
|
|
||||||
// Result
|
if (images.length > 0) {
|
||||||
if (images.length !== 0) {
|
return createImageResponse(images)
|
||||||
// Images or Slide Result
|
}
|
||||||
resolve({
|
|
||||||
status: "success",
|
|
||||||
result: {
|
|
||||||
type: "image",
|
|
||||||
images
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Video Result
|
|
||||||
// Get Result Video
|
|
||||||
let i = 1
|
|
||||||
let videos = {}
|
|
||||||
$("div.row > div")
|
|
||||||
.map((_, el) => $(el))
|
|
||||||
.get(1)
|
|
||||||
.find("a")
|
|
||||||
.get()
|
|
||||||
.map((v: any) => {
|
|
||||||
if ($(v).attr("href") !== "#modal2") {
|
|
||||||
if (!isURL($(v).attr("href"))) return
|
|
||||||
videos[
|
|
||||||
$(v).attr("data-event").includes("hd")
|
|
||||||
? "videoHD"
|
|
||||||
: $(v).attr("data-event").includes("mp4")
|
|
||||||
? "videoSD"
|
|
||||||
: $(v).attr("data-event").includes("watermark")
|
|
||||||
? "videoWatermark"
|
|
||||||
: $(v).attr("href").includes("type=mp3") && "music"
|
|
||||||
] =
|
|
||||||
$(v).attr("href") != undefined
|
|
||||||
? $(v).attr("href")
|
|
||||||
: /downloadX\('([^']+)'\)/.exec($(v).attr("onclick"))[1]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (Object.keys(videos).length === 0)
|
const videos = parseVideos($)
|
||||||
return resolve({
|
if (Object.keys(videos).length === 0) {
|
||||||
status: "success",
|
return {
|
||||||
message: "There is an error. Can't find download link"
|
status: "error",
|
||||||
})
|
message: "There is an error. Can't find download link"
|
||||||
resolve({
|
}
|
||||||
status: "success",
|
}
|
||||||
result: {
|
|
||||||
type: "video",
|
|
||||||
author: {
|
|
||||||
avatar: $("div.img-area > img").attr("src"),
|
|
||||||
nickname: $("h2.video-author > b").text()
|
|
||||||
},
|
|
||||||
desc: $("p.video-desc").text(),
|
|
||||||
...videos
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => resolve({ status: "error", message: e.message }))
|
|
||||||
})
|
|
||||||
|
|
||||||
const isURL = (url: string) => {
|
return createVideoResponse($, videos)
|
||||||
let status = false
|
} catch (error) {
|
||||||
try {
|
return {
|
||||||
new URL(url)
|
status: "error",
|
||||||
status = true
|
message:
|
||||||
} catch {
|
error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR
|
||||||
status = false
|
}
|
||||||
}
|
}
|
||||||
return status
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Axios from "axios"
|
import Axios from "axios"
|
||||||
import asyncRetry from "async-retry"
|
import asyncRetry from "async-retry"
|
||||||
import { load } from "cheerio"
|
import { load } from "cheerio"
|
||||||
|
type CheerioAPI = ReturnType<typeof load>
|
||||||
import {
|
import {
|
||||||
AuthorSSSTik,
|
AuthorSSSTik,
|
||||||
StatisticsSSSTik,
|
StatisticsSSSTik,
|
||||||
@ -10,47 +11,137 @@ import {
|
|||||||
import { _ssstikapi, _ssstikurl } from "../../constants/api"
|
import { _ssstikapi, _ssstikurl } from "../../constants/api"
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||||
|
import { ERROR_MESSAGES } from "../../constants"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using API from Website:
|
* Using API from Website:
|
||||||
* BASE URL : https://ssstik.io
|
* BASE URL : https://ssstik.io
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TiktokURLregex =
|
/** Constants */
|
||||||
|
const TIKTOK_URL_REGEX =
|
||||||
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
|
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
|
||||||
|
const USER_AGENT =
|
||||||
|
"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
|
||||||
|
|
||||||
const fetchTT = (proxy?: string): Promise<SSSTikFetchTT> =>
|
/** Types */
|
||||||
new Promise(async (resolve) => {
|
interface ProxyConfig {
|
||||||
Axios(_ssstikurl, {
|
httpsAgent?: HttpsProxyAgent<string> | SocksProxyAgent
|
||||||
method: "GET",
|
}
|
||||||
headers: {
|
|
||||||
"User-Agent":
|
/** Helper Functions */
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
|
const createProxyAgent = (proxy?: string): ProxyConfig => {
|
||||||
},
|
if (!proxy) return {}
|
||||||
httpsAgent:
|
|
||||||
(proxy &&
|
const isHttpProxy = proxy.startsWith("http") || proxy.startsWith("https")
|
||||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
const isSocksProxy = proxy.startsWith("socks")
|
||||||
? new HttpsProxyAgent(proxy)
|
|
||||||
: proxy.startsWith("socks")
|
if (!isHttpProxy && !isSocksProxy) return {}
|
||||||
? new SocksProxyAgent(proxy)
|
|
||||||
: undefined)) ||
|
return {
|
||||||
undefined
|
httpsAgent: isHttpProxy
|
||||||
})
|
? new HttpsProxyAgent(proxy)
|
||||||
.then(({ data }) => {
|
: new SocksProxyAgent(proxy)
|
||||||
const regex = /s_tt\s*=\s*["']([^"']+)["']/
|
}
|
||||||
const match = data.match(regex)
|
}
|
||||||
if (match) {
|
|
||||||
const value = match[1]
|
const validateTikTokUrl = (url: string): boolean => {
|
||||||
return resolve({ status: "success", result: value })
|
return TIKTOK_URL_REGEX.test(url)
|
||||||
} else {
|
}
|
||||||
return resolve({
|
|
||||||
status: "error",
|
const extractTTValue = (html: string): string | null => {
|
||||||
message: "Failed to get the request form!"
|
const regex = /s_tt\s*=\s*["']([^"']+)["']/
|
||||||
})
|
const match = html.match(regex)
|
||||||
}
|
return match ? match[1] : null
|
||||||
})
|
}
|
||||||
.catch((e) => resolve({ status: "error", message: e.message }))
|
|
||||||
|
const parseAuthor = ($: CheerioAPI): AuthorSSSTik => ({
|
||||||
|
avatar: $("img.result_author").attr("src") || "",
|
||||||
|
nickname: $("h2").text().trim()
|
||||||
|
})
|
||||||
|
|
||||||
|
const parseStatistics = ($: CheerioAPI): StatisticsSSSTik => ({
|
||||||
|
likeCount: $("#trending-actions > .justify-content-start").text().trim(),
|
||||||
|
commentCount: $("#trending-actions > .justify-content-center").text().trim(),
|
||||||
|
shareCount: $("#trending-actions > .justify-content-end").text().trim()
|
||||||
|
})
|
||||||
|
|
||||||
|
const parseImages = ($: CheerioAPI): string[] => {
|
||||||
|
const images: string[] = []
|
||||||
|
$("ul.splide__list > li").each((_, img) => {
|
||||||
|
const href = $(img).find("a").attr("href")
|
||||||
|
if (href) images.push(href)
|
||||||
})
|
})
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
|
||||||
|
const createImageResponse = (
|
||||||
|
$: CheerioAPI,
|
||||||
|
author: AuthorSSSTik,
|
||||||
|
statistics: StatisticsSSSTik,
|
||||||
|
images: string[],
|
||||||
|
music?: string
|
||||||
|
): SSSTikResponse["result"] => ({
|
||||||
|
type: "image",
|
||||||
|
desc: $("p.maintext").text().trim(),
|
||||||
|
author,
|
||||||
|
statistics,
|
||||||
|
images,
|
||||||
|
...(music && { music: { playUrl: [music] } })
|
||||||
|
})
|
||||||
|
|
||||||
|
const createVideoResponse = (
|
||||||
|
$: CheerioAPI,
|
||||||
|
author: AuthorSSSTik,
|
||||||
|
statistics: StatisticsSSSTik,
|
||||||
|
video: string,
|
||||||
|
music?: string
|
||||||
|
): SSSTikResponse["result"] => ({
|
||||||
|
type: "video",
|
||||||
|
desc: $("p.maintext").text().trim(),
|
||||||
|
author,
|
||||||
|
statistics,
|
||||||
|
video: { playAddr: [video] },
|
||||||
|
...(music && { music: { playUrl: [music] } })
|
||||||
|
})
|
||||||
|
|
||||||
|
const createMusicResponse = (
|
||||||
|
music: string,
|
||||||
|
direct?: string
|
||||||
|
): SSSTikResponse["result"] => ({
|
||||||
|
type: "music",
|
||||||
|
music: { playUrl: [music] },
|
||||||
|
direct: direct || ""
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchTT = async (proxy?: string): Promise<SSSTikFetchTT> => {
|
||||||
|
try {
|
||||||
|
const { data } = await Axios(_ssstikurl, {
|
||||||
|
method: "GET",
|
||||||
|
headers: { "User-Agent": USER_AGENT },
|
||||||
|
...createProxyAgent(proxy)
|
||||||
|
})
|
||||||
|
|
||||||
|
const ttValue = extractTTValue(data)
|
||||||
|
if (!ttValue) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: ERROR_MESSAGES.NETWORK_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "success",
|
||||||
|
result: ttValue
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message:
|
||||||
|
error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tiktok SSSTik Downloader
|
* Tiktok SSSTik Downloader
|
||||||
@ -58,128 +149,89 @@ const fetchTT = (proxy?: string): Promise<SSSTikFetchTT> =>
|
|||||||
* @param {string} proxy - Your Proxy (optional)
|
* @param {string} proxy - Your Proxy (optional)
|
||||||
* @returns {Promise<SSSTikResponse>}
|
* @returns {Promise<SSSTikResponse>}
|
||||||
*/
|
*/
|
||||||
|
export const SSSTik = async (
|
||||||
export const SSSTik = (url: string, proxy?: string): Promise<SSSTikResponse> =>
|
url: string,
|
||||||
new Promise(async (resolve) => {
|
proxy?: string
|
||||||
try {
|
): Promise<SSSTikResponse> => {
|
||||||
if (!TiktokURLregex.test(url)) {
|
try {
|
||||||
return resolve({
|
if (!validateTikTokUrl(url)) {
|
||||||
status: "error",
|
return {
|
||||||
message: "Invalid Tiktok URL. Make sure your url is correct!"
|
status: "error",
|
||||||
})
|
message: ERROR_MESSAGES.INVALID_URL
|
||||||
}
|
}
|
||||||
const tt: SSSTikFetchTT = await fetchTT(proxy)
|
|
||||||
if (tt.status !== "success")
|
|
||||||
return resolve({ status: "error", message: tt.message })
|
|
||||||
|
|
||||||
const response = asyncRetry(
|
|
||||||
async () => {
|
|
||||||
const res = await Axios(_ssstikapi, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type":
|
|
||||||
"application/x-www-form-urlencoded; charset=UTF-8",
|
|
||||||
Origin: _ssstikurl,
|
|
||||||
Referer: _ssstikurl + "/en",
|
|
||||||
"User-Agent":
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0"
|
|
||||||
},
|
|
||||||
data: new URLSearchParams(
|
|
||||||
Object.entries({
|
|
||||||
id: url,
|
|
||||||
locale: "en",
|
|
||||||
tt: tt.result
|
|
||||||
})
|
|
||||||
),
|
|
||||||
httpsAgent:
|
|
||||||
(proxy &&
|
|
||||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
|
||||||
? new HttpsProxyAgent(proxy)
|
|
||||||
: proxy.startsWith("socks")
|
|
||||||
? new SocksProxyAgent(proxy)
|
|
||||||
: undefined)) ||
|
|
||||||
undefined
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.status === 200 && res.data !== "") return res.data
|
|
||||||
|
|
||||||
throw new Error("Failed to fetch data from SSSTik!")
|
|
||||||
},
|
|
||||||
{
|
|
||||||
retries: 20,
|
|
||||||
minTimeout: 200,
|
|
||||||
maxTimeout: 1000
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const $ = load(await response)
|
|
||||||
|
|
||||||
// Result
|
|
||||||
const author: AuthorSSSTik = {
|
|
||||||
avatar: $("img.result_author").attr("src"),
|
|
||||||
nickname: $("h2").text().trim()
|
|
||||||
}
|
|
||||||
const statistics: StatisticsSSSTik = {
|
|
||||||
likeCount: $("#trending-actions > .justify-content-start")
|
|
||||||
.text()
|
|
||||||
.trim(),
|
|
||||||
commentCount: $("#trending-actions > .justify-content-center")
|
|
||||||
.text()
|
|
||||||
.trim(),
|
|
||||||
shareCount: $("#trending-actions > .justify-content-end").text().trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video & Music Result
|
|
||||||
const video = $("a.without_watermark").attr("href")
|
|
||||||
const music = $("a.music").attr("href")
|
|
||||||
const direct = $("a.music_direct").attr("href")
|
|
||||||
|
|
||||||
// Images / Slide Result
|
|
||||||
const images: string[] = []
|
|
||||||
$("ul.splide__list > li")
|
|
||||||
.get()
|
|
||||||
.map((img) => {
|
|
||||||
images.push($(img).find("a").attr("href"))
|
|
||||||
})
|
|
||||||
|
|
||||||
let result: SSSTikResponse["result"]
|
|
||||||
if (images.length !== 0) {
|
|
||||||
// Images / Slide Result
|
|
||||||
result = {
|
|
||||||
type: "image",
|
|
||||||
desc: $("p.maintext").text().trim(),
|
|
||||||
author,
|
|
||||||
statistics,
|
|
||||||
images
|
|
||||||
}
|
|
||||||
|
|
||||||
if (music) {
|
|
||||||
result.music = music
|
|
||||||
}
|
|
||||||
} else if (video) {
|
|
||||||
// Video Result
|
|
||||||
result = {
|
|
||||||
type: "video",
|
|
||||||
desc: $("p.maintext").text().trim(),
|
|
||||||
author,
|
|
||||||
statistics,
|
|
||||||
video
|
|
||||||
}
|
|
||||||
|
|
||||||
if (music) {
|
|
||||||
result.music = music
|
|
||||||
}
|
|
||||||
} else if (music) {
|
|
||||||
// Music Result
|
|
||||||
result = {
|
|
||||||
type: "music",
|
|
||||||
music,
|
|
||||||
direct: direct || ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({ status: "success", result })
|
|
||||||
} catch (err) {
|
|
||||||
resolve({ status: "error", message: err.message })
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
const tt = await fetchTT(proxy)
|
||||||
|
if (tt.status !== "success") {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: tt.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await asyncRetry(
|
||||||
|
async () => {
|
||||||
|
const res = await Axios(_ssstikapi, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
|
Origin: _ssstikurl,
|
||||||
|
Referer: `${_ssstikurl}/en`,
|
||||||
|
"User-Agent": USER_AGENT
|
||||||
|
},
|
||||||
|
data: new URLSearchParams({
|
||||||
|
id: url,
|
||||||
|
locale: "en",
|
||||||
|
tt: tt.result
|
||||||
|
}),
|
||||||
|
...createProxyAgent(proxy)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.status === 200 && res.data) {
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(ERROR_MESSAGES.NETWORK_ERROR)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
retries: 20,
|
||||||
|
minTimeout: 200,
|
||||||
|
maxTimeout: 1000
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const $ = load(response)
|
||||||
|
const author = parseAuthor($)
|
||||||
|
const statistics = parseStatistics($)
|
||||||
|
const video = $("a.without_watermark").attr("href")
|
||||||
|
const music = $("a.music").attr("href")
|
||||||
|
const direct = $("a.music_direct").attr("href")
|
||||||
|
const images = parseImages($)
|
||||||
|
|
||||||
|
let result: SSSTikResponse["result"]
|
||||||
|
|
||||||
|
if (images.length > 0) {
|
||||||
|
result = createImageResponse($, author, statistics, images, music)
|
||||||
|
} else if (video) {
|
||||||
|
result = createVideoResponse($, author, statistics, video, music)
|
||||||
|
} else if (music) {
|
||||||
|
result = createMusicResponse(music, direct)
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: ERROR_MESSAGES.NETWORK_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "success",
|
||||||
|
result
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message:
|
||||||
|
error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -12,168 +12,122 @@ import {
|
|||||||
} from "../../types/downloader/tiktokApi"
|
} from "../../types/downloader/tiktokApi"
|
||||||
import { HttpsProxyAgent } from "https-proxy-agent"
|
import { HttpsProxyAgent } from "https-proxy-agent"
|
||||||
import { SocksProxyAgent } from "socks-proxy-agent"
|
import { SocksProxyAgent } from "socks-proxy-agent"
|
||||||
|
import { ERROR_MESSAGES } from "../../constants"
|
||||||
|
|
||||||
const TiktokURLregex =
|
/** Constants */
|
||||||
|
const TIKTOK_URL_REGEX =
|
||||||
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
|
/https:\/\/(?:m|www|vm|vt|lite)?\.?tiktok\.com\/((?:.*\b(?:(?:usr|v|embed|user|video|photo)\/|\?shareId=|\&item_id=)(\d+))|\w+)/
|
||||||
|
const USER_AGENT =
|
||||||
|
"com.zhiliaoapp.musically/300904 (2018111632; U; Android 10; en_US; Pixel 4; Build/QQ3A.200805.001; Cronet/58.0.2991.0)"
|
||||||
|
|
||||||
/**
|
/** Types */
|
||||||
* Tiktok API Downloader
|
interface ProxyConfig {
|
||||||
* @param {string} url - Tiktok URL
|
httpsAgent?: HttpsProxyAgent<string> | SocksProxyAgent
|
||||||
* @param {string} proxy - Your Proxy (optional)
|
}
|
||||||
* @param {boolean} showOriginalResponse - Show Original Response (optional)
|
|
||||||
* @returns {Promise<TiktokAPIResponse>}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const TiktokAPI = (
|
/** Helper Functions */
|
||||||
url: string,
|
const createProxyAgent = (proxy?: string): ProxyConfig => {
|
||||||
proxy?: string,
|
if (!proxy) return {}
|
||||||
showOriginalResponse?: boolean
|
|
||||||
): Promise<TiktokAPIResponse> =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
if (!TiktokURLregex.test(url)) {
|
|
||||||
return resolve({
|
|
||||||
status: "error",
|
|
||||||
message: "Invalid Tiktok URL. Make sure your url is correct!"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
url = url.replace("https://vm", "https://vt")
|
|
||||||
Axios(url, {
|
|
||||||
method: "HEAD",
|
|
||||||
httpsAgent:
|
|
||||||
(proxy &&
|
|
||||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
|
||||||
? new HttpsProxyAgent(proxy)
|
|
||||||
: proxy.startsWith("socks")
|
|
||||||
? new SocksProxyAgent(proxy)
|
|
||||||
: undefined)) ||
|
|
||||||
undefined
|
|
||||||
})
|
|
||||||
.then(async ({ request }) => {
|
|
||||||
const { responseUrl } = request.res
|
|
||||||
let ID = responseUrl.match(/\d{17,21}/g)
|
|
||||||
if (ID === null)
|
|
||||||
return resolve({
|
|
||||||
status: "error",
|
|
||||||
message:
|
|
||||||
"Failed to fetch tiktok url. Make sure your tiktok url is correct!"
|
|
||||||
})
|
|
||||||
ID = ID[0]
|
|
||||||
|
|
||||||
let data2 = await fetchTiktokData(ID, proxy)
|
const isHttpProxy = proxy.startsWith("http") || proxy.startsWith("https")
|
||||||
|
const isSocksProxy = proxy.startsWith("socks")
|
||||||
|
|
||||||
if (!data2?.content) {
|
if (!isHttpProxy && !isSocksProxy) return {}
|
||||||
return resolve({
|
|
||||||
status: "error",
|
|
||||||
message:
|
|
||||||
"Failed to fetch tiktok data. Make sure your tiktok url is correct!"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const { content, author, statistics, music } = data2
|
return {
|
||||||
|
httpsAgent: isHttpProxy
|
||||||
|
? new HttpsProxyAgent(proxy)
|
||||||
|
: new SocksProxyAgent(proxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let response: TiktokAPIResponse
|
const validateTikTokUrl = (url: string): boolean => {
|
||||||
// Download Result
|
return TIKTOK_URL_REGEX.test(url)
|
||||||
if (content.image_post_info) {
|
}
|
||||||
// Images or Slide Result
|
|
||||||
response = {
|
|
||||||
status: "success",
|
|
||||||
result: {
|
|
||||||
type: "image",
|
|
||||||
id: content.aweme_id,
|
|
||||||
createTime: content.create_time,
|
|
||||||
description: content.desc,
|
|
||||||
isTurnOffComment: content.item_comment_settings === 3,
|
|
||||||
hashtag: content.text_extra
|
|
||||||
.filter((x: any) => x.hashtag_name !== undefined)
|
|
||||||
.map((v: any) => v.hashtag_name),
|
|
||||||
isADS: content.is_ads,
|
|
||||||
author,
|
|
||||||
statistics,
|
|
||||||
images:
|
|
||||||
content.image_post_info.images?.map(
|
|
||||||
(v: any) => v?.display_image?.url_list[0]
|
|
||||||
) || [],
|
|
||||||
music
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Video Result
|
|
||||||
const video: VideoTiktokAPI = {
|
|
||||||
ratio: content.video.ratio,
|
|
||||||
duration: content.video.duration,
|
|
||||||
playAddr: content.video?.play_addr?.url_list || [], // No Watermark Video
|
|
||||||
downloadAddr: content.video?.download_addr?.url_list || [], // Watermark Video
|
|
||||||
cover: content.video?.cover?.url_list || [],
|
|
||||||
dynamicCover: content.video?.dynamic_cover?.url_list || [],
|
|
||||||
originCover: content.video?.origin_cover?.url_list || []
|
|
||||||
}
|
|
||||||
|
|
||||||
response = {
|
const extractVideoId = (responseUrl: string): string | null => {
|
||||||
status: "success",
|
const matches = responseUrl.match(/\d{17,21}/g)
|
||||||
result: {
|
return matches ? matches[0] : null
|
||||||
type: "video",
|
}
|
||||||
id: content.aweme_id,
|
|
||||||
createTime: content.create_time,
|
|
||||||
description: content.desc,
|
|
||||||
isTurnOffComment: content.item_comment_settings === 3,
|
|
||||||
hashtag: content.text_extra
|
|
||||||
.filter((x: any) => x.hashtag_name !== undefined)
|
|
||||||
.map((v: any) => v.hashtag_name),
|
|
||||||
isADS: content.is_ads,
|
|
||||||
author,
|
|
||||||
statistics,
|
|
||||||
video,
|
|
||||||
music
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show Original Response
|
const parseStatistics = (content: any): StatisticsTiktokAPI => ({
|
||||||
if (showOriginalResponse) {
|
commentCount: content.statistics.comment_count,
|
||||||
response = {
|
likeCount: content.statistics.digg_count,
|
||||||
status: "success",
|
shareCount: content.statistics.share_count,
|
||||||
resultNotParsed: data2
|
playCount: content.statistics.play_count,
|
||||||
}
|
downloadCount: content.statistics.download_count
|
||||||
}
|
})
|
||||||
resolve(response)
|
|
||||||
})
|
const parseAuthor = (content: any): AuthorTiktokAPI => ({
|
||||||
.catch((e) => resolve({ status: "error", message: e.message }))
|
uid: content.author.uid,
|
||||||
})
|
username: content.author.unique_id,
|
||||||
|
nickname: content.author.nickname,
|
||||||
|
signature: content.author.signature,
|
||||||
|
region: content.author.region,
|
||||||
|
avatarThumb: content.author?.avatar_thumb?.url_list || [],
|
||||||
|
avatarMedium: content.author?.avatar_medium?.url_list || [],
|
||||||
|
url: `${_tiktokurl}/@${content.author.unique_id}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const parseMusic = (content: any): MusicTiktokAPI => ({
|
||||||
|
id: content.music.id,
|
||||||
|
title: content.music.title,
|
||||||
|
author: content.music.author,
|
||||||
|
album: content.music.album,
|
||||||
|
playUrl: content.music?.play_url?.url_list || [],
|
||||||
|
coverLarge: content.music?.cover_large?.url_list || [],
|
||||||
|
coverMedium: content.music?.cover_medium?.url_list || [],
|
||||||
|
coverThumb: content.music?.cover_thumb?.url_list || [],
|
||||||
|
duration: content.music.duration,
|
||||||
|
isCommerceMusic: content.music.is_commerce_music,
|
||||||
|
isOriginalSound: content.music.is_original_sound,
|
||||||
|
isAuthorArtist: content.music.is_author_artist
|
||||||
|
})
|
||||||
|
|
||||||
|
const parseVideo = (content: any): VideoTiktokAPI => ({
|
||||||
|
ratio: content.video.ratio,
|
||||||
|
duration: content.video.duration,
|
||||||
|
playAddr: content.video?.play_addr?.url_list || [],
|
||||||
|
downloadAddr: content.video?.download_addr?.url_list || [],
|
||||||
|
cover: content.video?.cover?.url_list || [],
|
||||||
|
dynamicCover: content.video?.dynamic_cover?.url_list || [],
|
||||||
|
originCover: content.video?.origin_cover?.url_list || []
|
||||||
|
})
|
||||||
|
|
||||||
|
const parseTiktokData = (ID: string, data: any): ResponseParserTiktokAPI => {
|
||||||
|
const content = data?.aweme_list?.find((v: any) => v.aweme_id === ID)
|
||||||
|
|
||||||
|
if (!content) return { content: null }
|
||||||
|
|
||||||
|
return {
|
||||||
|
content,
|
||||||
|
statistics: parseStatistics(content),
|
||||||
|
author: parseAuthor(content),
|
||||||
|
music: parseMusic(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fetchTiktokData = async (
|
const fetchTiktokData = async (
|
||||||
ID: string,
|
ID: string,
|
||||||
proxy?: string
|
proxy?: string
|
||||||
): Promise<ResponseParserTiktokAPI> | null => {
|
): Promise<ResponseParserTiktokAPI | null> => {
|
||||||
try {
|
try {
|
||||||
const response = asyncRetry(
|
const response = await asyncRetry(
|
||||||
async () => {
|
async () => {
|
||||||
const res = await Axios(
|
const res = await Axios(
|
||||||
_tiktokvFeed(
|
_tiktokvFeed(_tiktokApiParams({ aweme_id: ID })),
|
||||||
_tiktokApiParams({
|
|
||||||
aweme_id: ID
|
|
||||||
})
|
|
||||||
),
|
|
||||||
{
|
{
|
||||||
method: "OPTIONS",
|
method: "OPTIONS",
|
||||||
headers: {
|
headers: { "User-Agent": USER_AGENT },
|
||||||
"User-Agent":
|
...createProxyAgent(proxy)
|
||||||
"com.zhiliaoapp.musically/300904 (2018111632; U; Android 10; en_US; Pixel 4; Build/QQ3A.200805.001; Cronet/58.0.2991.0)"
|
|
||||||
},
|
|
||||||
httpsAgent:
|
|
||||||
proxy &&
|
|
||||||
(proxy.startsWith("http") || proxy.startsWith("https")
|
|
||||||
? new HttpsProxyAgent(proxy)
|
|
||||||
: proxy.startsWith("socks")
|
|
||||||
? new SocksProxyAgent(proxy)
|
|
||||||
: undefined)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (res.data !== "" && res.data.status_code === 0) {
|
if (res.data && res.data.status_code === 0) {
|
||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("Failed to fetch tiktok data")
|
throw new Error(ERROR_MESSAGES.NETWORK_ERROR)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
retries: 20,
|
retries: 20,
|
||||||
@ -182,64 +136,131 @@ const fetchTiktokData = async (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const data = await response
|
return parseTiktokData(ID, response)
|
||||||
if (data) {
|
} catch (error) {
|
||||||
return parseTiktokData(ID, data)
|
console.error("Error fetching TikTok data:", error)
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseTiktokData = (ID: string, data: any): ResponseParserTiktokAPI => {
|
const createImageResponse = (
|
||||||
let content = data?.aweme_list
|
content: any,
|
||||||
|
author: AuthorTiktokAPI,
|
||||||
if (!content) return { content: null }
|
statistics: StatisticsTiktokAPI,
|
||||||
|
music: MusicTiktokAPI
|
||||||
content = content.find((v: any) => v.aweme_id === ID)
|
): TiktokAPIResponse => ({
|
||||||
|
status: "success",
|
||||||
// Statistics Result
|
result: {
|
||||||
const statistics: StatisticsTiktokAPI = {
|
type: "image",
|
||||||
commentCount: content.statistics.comment_count,
|
id: content.aweme_id,
|
||||||
diggCount: content.statistics.digg_count,
|
createTime: content.create_time,
|
||||||
downloadCount: content.statistics.download_count,
|
desc: content.desc,
|
||||||
playCount: content.statistics.play_count,
|
isTurnOffComment: content.item_comment_settings === 3,
|
||||||
shareCount: content.statistics.share_count,
|
hashtag: content.text_extra
|
||||||
forwardCount: content.statistics.forward_count,
|
.filter((x: any) => x.hashtag_name !== undefined)
|
||||||
loseCount: content.statistics.lose_count,
|
.map((v: any) => v.hashtag_name),
|
||||||
loseCommentCount: content.statistics.lose_comment_count,
|
isADS: content.is_ads,
|
||||||
whatsappShareCount: content.statistics.whatsapp_share_count,
|
author,
|
||||||
collectCount: content.statistics.collect_count,
|
statistics,
|
||||||
repostCount: content.statistics.repost_count
|
images:
|
||||||
|
content.image_post_info.images?.map(
|
||||||
|
(v: any) => v?.display_image?.url_list[0]
|
||||||
|
) || [],
|
||||||
|
music
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Author Result
|
const createVideoResponse = (
|
||||||
const author: AuthorTiktokAPI = {
|
content: any,
|
||||||
uid: content.author.uid,
|
author: AuthorTiktokAPI,
|
||||||
username: content.author.unique_id,
|
statistics: StatisticsTiktokAPI,
|
||||||
nickname: content.author.nickname,
|
music: MusicTiktokAPI
|
||||||
signature: content.author.signature,
|
): TiktokAPIResponse => ({
|
||||||
region: content.author.region,
|
status: "success",
|
||||||
avatarThumb: content.author?.avatar_thumb?.url_list || [],
|
result: {
|
||||||
avatarMedium: content.author?.avatar_medium?.url_list || [],
|
type: "video",
|
||||||
url: `${_tiktokurl}/@${content.author.unique_id}`
|
id: content.aweme_id,
|
||||||
|
createTime: content.create_time,
|
||||||
|
desc: content.desc,
|
||||||
|
isTurnOffComment: content.item_comment_settings === 3,
|
||||||
|
hashtag: content.text_extra
|
||||||
|
.filter((x: any) => x.hashtag_name !== undefined)
|
||||||
|
.map((v: any) => v.hashtag_name),
|
||||||
|
isADS: content.is_ads,
|
||||||
|
author,
|
||||||
|
statistics,
|
||||||
|
video: parseVideo(content),
|
||||||
|
music
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Music Result
|
/**
|
||||||
const music: MusicTiktokAPI = {
|
* Tiktok API Downloader
|
||||||
id: content.music.id,
|
* @param {string} url - Tiktok URL
|
||||||
title: content.music.title,
|
* @param {string} proxy - Your Proxy (optional)
|
||||||
author: content.music.author,
|
* @param {boolean} showOriginalResponse - Show Original Response (optional)
|
||||||
album: content.music.album,
|
* @returns {Promise<TiktokAPIResponse>}
|
||||||
playUrl: content.music?.play_url?.url_list || [],
|
*/
|
||||||
coverLarge: content.music?.cover_large?.url_list || [],
|
export const TiktokAPI = async (
|
||||||
coverMedium: content.music?.cover_medium?.url_list || [],
|
url: string,
|
||||||
coverThumb: content.music?.cover_thumb?.url_list || [],
|
proxy?: string,
|
||||||
duration: content.music.duration,
|
showOriginalResponse?: boolean
|
||||||
isCommerceMusic: content.music.is_commerce_music,
|
): Promise<TiktokAPIResponse> => {
|
||||||
isOriginalSound: content.music.is_original_sound,
|
try {
|
||||||
isAuthorArtist: content.music.is_author_artist
|
if (!validateTikTokUrl(url)) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: ERROR_MESSAGES.INVALID_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize URL
|
||||||
|
url = url.replace("https://vm", "https://vt")
|
||||||
|
|
||||||
|
// Get video ID
|
||||||
|
const { request } = await Axios(url, {
|
||||||
|
method: "HEAD",
|
||||||
|
...createProxyAgent(proxy)
|
||||||
|
})
|
||||||
|
|
||||||
|
const videoId = extractVideoId(request.res.responseUrl)
|
||||||
|
if (!videoId) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: ERROR_MESSAGES.INVALID_URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch TikTok data
|
||||||
|
const data = await fetchTiktokData(videoId, proxy)
|
||||||
|
if (!data?.content) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message: ERROR_MESSAGES.NETWORK_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { content, author, statistics, music } = data
|
||||||
|
|
||||||
|
// Create response based on content type
|
||||||
|
const response = content.image_post_info
|
||||||
|
? createImageResponse(content, author, statistics, music)
|
||||||
|
: createVideoResponse(content, author, statistics, music)
|
||||||
|
|
||||||
|
// Return original response if requested
|
||||||
|
if (showOriginalResponse) {
|
||||||
|
return {
|
||||||
|
status: "success",
|
||||||
|
resultNotParsed: data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
status: "error",
|
||||||
|
message:
|
||||||
|
error instanceof Error ? error.message : ERROR_MESSAGES.NETWORK_ERROR
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { content, statistics, author, music }
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user