diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 5dfd2765..3278c18f 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -111,6 +111,7 @@ export default async function(host, patternMatch, lang, obj) { r = await tiktok({ postId: patternMatch.postId, id: patternMatch.id, + user: patternMatch.user, fullAudio: obj.isTTFullAudio, isAudioOnly: isAudioOnly, h265: obj.tiktokH265 diff --git a/src/modules/processing/matchActionDecider.js b/src/modules/processing/matchActionDecider.js index f54bb3f5..764a1d3b 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; @@ -76,8 +77,13 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di 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: pickerType, + u: r.urls, + filename: r.audioFilename, + }), + copy: audioFormat === "best" } } break; @@ -101,7 +107,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 +131,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 +139,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di let processType = "render", copy = false; - + if (!supportedAudio.includes(audioFormat)) { audioFormat = "best" } diff --git a/src/modules/processing/services/tiktok.js b/src/modules/processing/services/tiktok.js index b81d57d9..d6b2079f 100644 --- a/src/modules/processing/services/tiktok.js +++ b/src/modules/processing/services/tiktok.js @@ -1,15 +1,16 @@ -import { genericUserAgent, env } from "../../config.js"; +import { genericUserAgent } from "../../config.js"; +import { updateCookie } from "../cookie/manager.js"; +import Cookie from "../cookie/cookie.js"; +const fullDomain = "https://tiktok.com/"; 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; + let postId = obj.postId || false + let username = obj.user || false - if (!env.tiktokDeviceInfo) return { error: 'ErrorCouldntFetch' }; - - if (!postId) { + if (!username || !postId) { let html = await fetch(`${shortDomain}${obj.id}`, { redirect: "manual", headers: { @@ -20,52 +21,47 @@ 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() + const json = html + .split('')[0] + const data = JSON.parse(json) + const detail = data["__DEFAULT_SCOPE__"]["webapp.video-detail"]["itemInfo"]["itemStruct"] let video, videoFilename, audioFilename, audio, images, filenameBase = `tiktok_${detail.author.unique_id}_${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.contains("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 = detail.music.playUrl || playAddr; audioFilename = `${filenameBase}_audio`; - if (obj.fullAudio || fallback.includes("music")) { - audio = detail.music.play_url.url_list[0] - audioFilename = `${filenameBase}_audio_original` - } if (audio.slice(-4) === ".mp3") bestAudio = 'mp3'; } @@ -80,12 +76,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 2f898c52..5a2fe538 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,9 +14,14 @@ const serviceHeaders = { origin: 'https://www.youtube.com', referer: 'https://www.youtube.com', DNT: '?1' + }, + tiktok: { + cookie: tiktokCookie } } export function getHeaders(service) { - return { ...defaultHeaders, ...serviceHeaders[service] } + // Converting all header values to strings + return Object.entries({ ...defaultHeaders, ...serviceHeaders[service] }) + .reduce((p, c) => ({ ...p, [c[0]]: String(c[1]) }), {}) } \ No newline at end of file