From 2534931b60516ded00704d2ae768d945205da542 Mon Sep 17 00:00:00 2001 From: Damir Modyarov Date: Tue, 21 May 2024 23:41:43 +0300 Subject: [PATCH] tiktok: use webapp-based downloading method (#503) Signed-off-by: Damir Modyarov Co-authored-by: wukko --- src/modules/processing/matchActionDecider.js | 30 ++++--- src/modules/processing/services/tiktok.js | 82 ++++++++++---------- src/modules/stream/shared.js | 8 +- 3 files changed, 68 insertions(+), 52 deletions(-) diff --git a/src/modules/processing/matchActionDecider.js b/src/modules/processing/matchActionDecider.js index f54bb3f5..6b44f8ab 100644 --- a/src/modules/processing/matchActionDecider.js +++ b/src/modules/processing/matchActionDecider.js @@ -2,6 +2,7 @@ import { audioIgnore, services, supportedAudio } from "../config.js"; import { createResponse } from "../processing/request.js"; import loc from "../../localization/manager.js"; import createFilename from "./createFilename.js"; +import { createStream } from "../stream/manage.js"; export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, disableMetadata, filenamePattern, toGif, requestIP) { let action, @@ -41,7 +42,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di case "photo": responseType = "redirect"; break; - + case "gif": params = { type: "gif" } break; @@ -68,16 +69,22 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di params = { picker: r.picker }; break; case "tiktok": - let pickerType = "render"; - if (audioFormat === "mp3" || audioFormat === "best") { + let audioStreamType = "render"; + if (r.bestAudio === "mp3" && (audioFormat === "mp3" || audioFormat === "best")) { audioFormat = "mp3"; - pickerType = "bridge" + audioStreamType = "bridge" } params = { - type: pickerType, picker: r.picker, - u: Array.isArray(r.urls) ? r.urls[1] : r.urls, - copy: audioFormat === "best" ? true : false + u: createStream({ + service: "tiktok", + type: audioStreamType, + u: r.urls, + filename: r.audioFilename, + isAudioOnly: true, + audioFormat, + }), + copy: audioFormat === "best" } } break; @@ -101,7 +108,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di responseType = "redirect"; } break; - + case "twitter": if (r.type === "remux") { params = { type: r.type }; @@ -125,7 +132,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di } break; - case "audio": + case "audio": if (audioIgnore.includes(host) || (host === "reddit" && r.typeId === "redirect")) { return createResponse("error", { t: loc(lang, 'ErrorEmptyDownload') }) @@ -133,7 +140,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di let processType = "render", copy = false; - + if (!supportedAudio.includes(audioFormat)) { audioFormat = "best" } @@ -146,11 +153,12 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di const isTumblrAudio = host === "tumblr" && !r.filename; const isSoundCloud = host === "soundcloud"; + const isTiktok = host === "tiktok"; if (isBestAudioDefined || isBestHostAudio) { audioFormat = serviceBestAudio; processType = "bridge"; - if (isSoundCloud) { + if (isSoundCloud || (isTiktok && audioFormat === "m4a")) { processType = "render" copy = true } diff --git a/src/modules/processing/services/tiktok.js b/src/modules/processing/services/tiktok.js index b81d57d9..6d3cf314 100644 --- a/src/modules/processing/services/tiktok.js +++ b/src/modules/processing/services/tiktok.js @@ -1,13 +1,13 @@ -import { genericUserAgent, env } from "../../config.js"; +import { genericUserAgent } from "../../config.js"; +import { updateCookie } from "../cookie/manager.js"; +import { extract } from "../url.js"; +import Cookie from "../cookie/cookie.js"; const shortDomain = "https://vt.tiktok.com/"; -const apiPath = "https://api22-normal-c-alisg.tiktokv.com/aweme/v1/feed/?region=US&carrier_region=US"; -const apiUserAgent = "TikTok/338014 CFNetwork/1410.1 Darwin/22.6.0"; +export const cookie = new Cookie({}); export default async function(obj) { - let postId = obj.postId ? obj.postId : false; - - if (!env.tiktokDeviceInfo) return { error: 'ErrorCouldntFetch' }; + let postId = obj.postId; if (!postId) { let html = await fetch(`${shortDomain}${obj.id}`, { @@ -19,54 +19,59 @@ export default async function(obj) { if (!html) return { error: 'ErrorCouldntFetch' }; - if (html.slice(0, 17) === ' r.json()).catch(() => {}); + }) + updateCookie(cookie, res.headers); - detail = detail?.aweme_list?.find(v => v.aweme_id === postId); - if (!detail) return { error: 'ErrorCouldntFetch' }; + const html = await res.text(); + + let detail; + try { + const json = html + .split('')[0] + const data = JSON.parse(json) + detail = data["__DEFAULT_SCOPE__"]["webapp.video-detail"]["itemInfo"]["itemStruct"] + } catch { + return { error: 'ErrorCouldntFetch' }; + } let video, videoFilename, audioFilename, audio, images, - filenameBase = `tiktok_${detail.author.unique_id}_${postId}`, + filenameBase = `tiktok_${detail.author.uniqueId}_${postId}`, bestAudio = 'm4a'; - images = detail.image_post_info?.images; + images = detail.imagePost?.images; - let playAddr = detail.video.play_addr_h264; + let playAddr = detail.video.playAddr; if (obj.h265) { - playAddr = detail.video.bit_rate[0].play_addr - } - if (!playAddr && detail.video.play_addr) { - playAddr = detail.video.play_addr + const h265PlayAddr = detail.video.bitrateInfo.find(b => b.CodecType.includes("h265"))?.PlayAddr.UrlList[0] + playAddr = h265PlayAddr || playAddr } if (!obj.isAudioOnly && !images) { - video = playAddr.url_list[0]; + video = playAddr; videoFilename = `${filenameBase}.mp4`; } else { - let fallback = playAddr.url_list[0]; - audio = fallback; + audio = playAddr; audioFilename = `${filenameBase}_audio`; - if (obj.fullAudio || fallback.includes("music")) { - audio = detail.music.play_url.url_list[0] - audioFilename = `${filenameBase}_audio_original` + + if (obj.fullAudio || !audio) { + audio = detail.music.playUrl; + audioFilename += `_original` } - if (audio.slice(-4) === ".mp3") bestAudio = 'mp3'; + if (audio.includes("mime_type=audio_mpeg")) bestAudio = 'mp3'; } if (video) return { @@ -80,12 +85,9 @@ export default async function(obj) { bestAudio } if (images) { - let imageLinks = []; - for (let i in images) { - let sel = images[i].display_image.url_list; - sel = sel.filter(p => p.includes(".jpeg?")) - imageLinks.push({url: sel[0]}) - } + let imageLinks = images + .map(i => i.imageURL.urlList.find(p => p.includes(".jpeg?"))) + .map(url => ({ url })) return { picker: imageLinks, urls: audio, diff --git a/src/modules/stream/shared.js b/src/modules/stream/shared.js index 8555f8ba..42df2758 100644 --- a/src/modules/stream/shared.js +++ b/src/modules/stream/shared.js @@ -1,4 +1,5 @@ import { genericUserAgent } from "../config.js"; +import { cookie as tiktokCookie } from "../processing/services/tiktok.js"; const defaultHeaders = { 'user-agent': genericUserAgent @@ -13,6 +14,9 @@ const serviceHeaders = { origin: 'https://www.youtube.com', referer: 'https://www.youtube.com', DNT: '?1' + }, + tiktok: { + cookie: tiktokCookie } } @@ -22,5 +26,7 @@ export function closeResponse(res) { } export function getHeaders(service) { - return { ...defaultHeaders, ...serviceHeaders[service] } + // Converting all header values to strings + return Object.entries({ ...defaultHeaders, ...serviceHeaders[service] }) + .reduce((p, [key, val]) => ({ ...p, [key]: String(val) }), {}) } \ No newline at end of file