diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..1aa2988 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +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}` diff --git a/src/types/downloader.ts b/src/types/downloader.ts new file mode 100644 index 0000000..4cd1dcb --- /dev/null +++ b/src/types/downloader.ts @@ -0,0 +1,57 @@ +export interface DLResult { + status: "success" | "error" + message?: string + result?: { + type: "video" | "image" + id: string + createTime: number + description: string + duration?: string + author: Author + statistics: Statistics + hashtag: string[] + cover?: string[] + dynamicCover?: string[] + originCover?: string[] + video?: string[] + images?: string[] + music: Music + } +} + +export interface Author { + uid: number + username: string + nickname: string + signature: string + region: string + avatarLarger: string[] + avatarThumb: string[] + avatarMedium: string[] + url: string +} + +export interface Statistics { + playCount: number + downloadCount: number + shareCount: number + commentCount: number + likeCount: number + favoriteCount: number + forwardCount: number + whatsappShareCount: number + loseCount: number + loseCommentCount: number +} + +export interface Music { + id: number + title: string + author: string + album: string + playUrl: string[] + coverLarge: string[] + coverMedium: string[] + coverThumb: string[] + duration: number +} diff --git a/src/types/index.ts b/src/types/index.ts index 6ad1e13..9097ddc 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,61 +1,2 @@ -export interface DLResult { - status: "success" | "error" - message?: string - result?: { - type: "video" | "image" - id: string - createTime: number - description: string - author: Author - statistics: Statistics - video?: string[] - cover?: string[] - dynamic_cover?: string[] - images?: string[] - music: string[] - } -} - -export interface Author { - uid: number - username: string - nickname: string - signature: string - birthday: string - region: string -} - -export interface Statistics { - playCount: number - downloadCount: number - shareCount: number - commentCount: number - likeCount: number - favoriteCount: number -} - -export interface StalkResult { - status: "success" | "error" - message?: string - result?: { - users: Users - stats: Stats - } -} - -export interface Users { - username: string - nickname: string - avatar: string - signature: string - verified: boolean - region: string -} - -export interface Stats { - followerCount: number - followingCount: number - heartCount: number - videoCount: number - likeCount: number -} +export * from "./downloader" +export * from "./stalker" diff --git a/src/types/stalker.ts b/src/types/stalker.ts new file mode 100644 index 0000000..6c27bcf --- /dev/null +++ b/src/types/stalker.ts @@ -0,0 +1,31 @@ +export interface StalkResult { + status: "success" | "error" + message?: string + result?: { + users: Users + stats: Stats + } +} + +export interface Users { + username: string + nickname: string + avatarLarger: string + avatarThumb: string + avatarMedium: string + signature: string + verified: boolean + region: string + commerceUser: boolean + usernameModifyTime: number + nicknameModifyTime: number +} + +export interface Stats { + followerCount: number + followingCount: number + heartCount: number + videoCount: number + likeCount: number + friendCount: number +} diff --git a/src/utils/downloader.ts b/src/utils/downloader.ts new file mode 100644 index 0000000..2f0361f --- /dev/null +++ b/src/utils/downloader.ts @@ -0,0 +1,123 @@ +import axios from "axios" +import { _tiktokapi, _tiktokurl } from "../api" +import { Author, DLResult, Statistics, Music } from "../types" + +const toMinute = (duration) => { + const mins = ~~((duration % 3600) / 60) + const secs = ~~duration % 60 + + let ret = "" + + ret += "" + mins + ":" + (secs < 10 ? "0" : "") + ret += "" + secs + + return ret +} + +export const TiktokDL = (url: string): Promise => + new Promise((resolve, reject) => { + url = url.replace("https://vm", "https://vt") + axios + .head(url) + .then(({ 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] + axios + .get(_tiktokapi(ID)) + .then(({ data }) => { + const content = data.aweme_list.filter((v) => v.aweme_id === ID)[0] + if (!content) + return resolve({ + status: "error", + message: "Failed to find tiktok data. Make sure your tiktok url is correct!" + }) + + // Statictics Result + const statistics: Statistics = { + playCount: content.statistics.play_count, + downloadCount: content.statistics.download_count, + shareCount: content.statistics.share_count, + commentCount: content.statistics.comment_count, + likeCount: content.statistics.digg_count, + favoriteCount: content.statistics.collect_count, + forwardCount: content.statistics.forward_count, + whatsappShareCount: content.statistics.whatsapp_share_count, + loseCount: content.statistics.lose_count, + loseCommentCount: content.statistics.lose_comment_count + } + + // Author Result + const author: Author = { + uid: content.author.uid, + username: content.author.unique_id, + nickname: content.author.nickname, + signature: content.author.signature, + region: content.author.region, + avatarLarger: content.author.avatar_larger.url_list, + avatarThumb: content.author.avatar_thumb.url_list, + avatarMedium: content.author.avatar_medium.url_list, + url: `${_tiktokurl}/@${content.username}` + } + + // Music Result + const music: Music = { + 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 + } + + // Download Result + if (content.image_post_info) { + // Images or Slide Result + resolve({ + status: "success", + result: { + type: "image", + id: content.aweme_id, + createTime: content.create_time, + description: content.desc, + hashtag: content.text_extra.filter((x) => x.hashtag_name !== undefined).map((v) => v.hashtag_name), + author, + statistics, + images: content.image_post_info.images.map((v) => v.display_image.url_list[0]), + music + } + }) + } else { + // Video Result + resolve({ + status: "success", + result: { + type: "video", + id: content.aweme_id, + createTime: content.create_time, + description: content.desc, + hashtag: content.text_extra.filter((x) => x.hashtag_name !== undefined).map((v) => v.hashtag_name), + duration: toMinute(content.duration), + author, + statistics, + video: content.video.play_addr.url_list, + cover: content.video.cover.url_list, + dynamicCover: content.video.dynamic_cover.url_list, + originCover: content.video.origin_cover.url_list, + music + } + }) + } + }) + .catch((e) => resolve({ status: "error", message: e.message })) + }) + .catch((e) => resolve({ status: "error", message: e.message })) + }) diff --git a/src/utils/index.ts b/src/utils/index.ts index 1604d8a..9097ddc 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,133 +1,2 @@ -import axios from "axios" -import { load } from "cheerio" -import { Author, DLResult, StalkResult, Statistics, Stats, Users } from "../types" - -const _tiktokurl: string = "https://www.tiktok.com" -const _tiktokapi = (id: string): string => `https://api16-core.tiktokv.com/aweme/v1/feed/?aweme_id=${id}&openudid=vi8vz5c5aec5wllw&uuid=7661132520610792&_rticket=1694319262046&ts=1694319262&device_platform=android&channel=googleplay&ac=wifi` - -export const TiktokDL = (url: string): Promise => - new Promise((resolve, reject) => { - url = url.replace("https://vm", "https://vt") - axios - .head(url) - .then(({ 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] - axios - .get(_tiktokapi(ID)) - .then(({ data }) => { - const content = data.aweme_list.filter((v) => v.aweme_id === ID)[0] - if (!content) - return resolve({ - status: "error", - message: "Failed to find tiktok data. Make sure your tiktok url is correct!" - }) - const statistics: Statistics = { - playCount: content.statistics.play_count, - downloadCount: content.statistics.download_count, - shareCount: content.statistics.share_count, - commentCount: content.statistics.comment_count, - likeCount: content.statistics.digg_count, - favoriteCount: content.statistics.collect_count - } - const author: Author = { - uid: content.author.uid, - username: content.author.unique_id, - nickname: content.author.nickname, - signature: content.author.signature, - birthday: content.author.birthday, - region: content.author.region - } - if (content.image_post_info) { - resolve({ - status: "success", - result: { - type: "image", - id: content.aweme_id, - createTime: content.create_time, - description: content.desc, - author, - statistics, - images: content.image_post_info.images.map((v) => v.display_image.url_list[0]), - music: content.music.play_url.url_list - } - }) - } else { - resolve({ - status: "success", - result: { - type: "video", - id: content.aweme_id, - createTime: content.create_time, - description: content.desc, - author, - statistics, - video: content.video.play_addr.url_list, - cover: content.video.cover.url_list, - dynamic_cover: content.video.dynamic_cover.url_list, - music: content.music.play_url.url_list - } - }) - } - }) - .catch((e) => resolve({ status: "error", message: e.message })) - }) - .catch((e) => reject(e)) - }) - -export const TiktokStalk = (username: string): Promise => - new Promise((resolve, reject) => { - axios - .get("https://pastebin.com/raw/ELJjcbZT") - .then(({ data: cookie }) => { - username = username.replace("@", "") - axios - .get(`${_tiktokurl}/@${username}`, { - 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: cookie - } - }) - .then(({ data }) => { - const $ = load(data) - const result = JSON.parse($("script#SIGI_STATE").text()) - if (!result.UserModule) { - return resolve({ - status: "error", - message: "User not found!" - }) - } - const user = result.UserModule - const users: Users = { - username: user.users[username].uniqueId, - nickname: user.users[username].nickname, - avatar: user.users[username].avatarLarger, - signature: user.users[username].signature, - verified: user.users[username].verified, - region: user.users[username].region - } - 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 - } - resolve({ - status: "success", - result: { - users, - stats - } - }) - }) - .catch((e) => resolve({ status: "error", message: e.message })) - }) - .catch((e) => reject(e)) - }) +export * from "./downloader" +export * from "./stalker" diff --git a/src/utils/stalker.ts b/src/utils/stalker.ts new file mode 100644 index 0000000..eaee011 --- /dev/null +++ b/src/utils/stalker.ts @@ -0,0 +1,66 @@ +import axios from "axios" +import { load } from "cheerio" +import { _tiktokurl } from "../api" +import { StalkResult, Stats, Users } from "../types" + +const getCookie = () => + new Promise((resolve, reject) => { + axios + .get("https://pastebin.com/raw/ELJjcbZT") + .then(({ data: cookie }) => { + resolve(cookie) + }) + .catch((e) => resolve({ status: "error", message: "Failed to fetch cookie." })) + }) + +export const TiktokStalk = (username: string, options: { cookie: string }): Promise => + new Promise(async (resolve, reject) => { + username = username.replace("@", "") + axios + .get(`${_tiktokurl}/@${username}`, { + 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: (options?.cookie ? options.cookie : await getCookie()) as string + } + }) + .then(({ data }) => { + const $ = load(data) + const result = JSON.parse($("script#SIGI_STATE").text()) + if (!result.UserModule) { + return resolve({ + status: "error", + message: "User not found!" + }) + } + const user = result.UserModule + const users: Users = { + username: user.users[username].uniqueId, + nickname: user.users[username].nickname, + avatarLarger: user.users[username].avatarLarger, + avatarThumb: user.users[username].avatarThumb, + avatarMedium: user.users[username].avatarMedium, + signature: user.users[username].signature, + verified: user.users[username].verified, + region: user.users[username].region, + commerceUser: user.users[username].commerceUserInfo.commerceUser, + usernameModifyTime: user.users[username].uniqueIdModifyTime, + nicknameModifyTime: user.users[username].nickNameModifyTime + } + 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 + } + resolve({ + status: "success", + result: { + users, + stats + } + }) + }) + .catch((e) => resolve({ status: "error", message: e.message })) + })