diff --git a/docs/run-an-instance.md b/docs/run-an-instance.md index 819e021..01675e2 100644 --- a/docs/run-an-instance.md +++ b/docs/run-an-instance.md @@ -59,9 +59,32 @@ sudo service nscd start | `CORS_URL` | not used | `https://cobalt.tools/` | cross-origin resource sharing url. api will be available only from this url if `CORS_WILDCARD` is set to `0`. | | `COOKIE_PATH` | not used | `/cookies.json` | path for cookie file relative to main folder. | | `PROCESSING_PRIORITY` | not used | `10` | changes `nice` value* for ffmpeg subprocess. available only on unix systems. | +| `TIKTOK_DEVICE_INFO` | ➖ | *see below* | device info (including `iid` and `device_id`) for tiktok functionality. required for tiktok to work. | \* the higher the nice value, the lower the priority. [read more here](https://en.wikipedia.org/wiki/Nice_(Unix)). +#### TIKTOK_DEVICE_INFO +you need to get your own device info for tiktok functionality to work. this can be done by proxying the app through any request-intercepting proxy (such as [mitmproxy](https://mitmproxy.org)). you need to disable ssl pinning to see requests. there will be no assistance provided by cobalt for this. + +example config (replace **ALL** values with ones you got from mitm): +```json +'{ + "iid": "", + "device_id": "", + "channel": "googleplay", + "app_name": "musical_ly", + "version_code": "310503", + "device_platform": "android", + "device_type": "Redmi+7", + "os_version": "13" +}' +``` + +you can compress the json to save space. if you're using a `.env` file then the line would would look like this (***note the quotes***): +``` +TIKTOK_DEVICE_INFO='{"iid":"","device_id":"","channel":"googleplay","app_name":"musical_ly","version_code":"310503","device_platform":"android","device_type":"Redmi+7","os_version":"13"}' +``` + ### variables for web | variable name | default | example | description | |:---------------------|:---------------------|:------------------------|:--------------------------------------------------------------------------------------| diff --git a/package.json b/package.json index feff48f..99f6c74 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "7.12.1", + "version": "7.12.2", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/front/cobalt.css b/src/front/cobalt.css index 90b9718..67516ed 100644 --- a/src/front/cobalt.css +++ b/src/front/cobalt.css @@ -452,6 +452,7 @@ button:active, display: flex; flex-direction: column; gap: 18px; + width: 100%; } .popup.small.visible { transform: translate(-50%, -50%); diff --git a/src/localization/languages/en.json b/src/localization/languages/en.json index 43239ad..8450e11 100644 --- a/src/localization/languages/en.json +++ b/src/localization/languages/en.json @@ -1,7 +1,7 @@ { "name": "english", "substrings": { - "ContactLink": "check the status page or create an issue on github." + "ContactLink": "check the status page or create an issue on github" }, "strings": { "AppTitleCobalt": "cobalt", diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 4169057..6f715ef 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -83,10 +83,8 @@ export default async function(host, patternMatch, url, lang, obj) { id: patternMatch.id }); break; - case "douyin": case "tiktok": r = await tiktok({ - host: host, postId: patternMatch.postId, id: patternMatch.id, fullAudio: obj.isTTFullAudio, diff --git a/src/modules/processing/services/tiktok.js b/src/modules/processing/services/tiktok.js index 1818daf..f362910 100644 --- a/src/modules/processing/services/tiktok.js +++ b/src/modules/processing/services/tiktok.js @@ -1,42 +1,21 @@ import { genericUserAgent } from "../../config.js"; -const userAgent = genericUserAgent.split(' Chrome/1')[0], - config = { - tiktok: { - short: "https://vt.tiktok.com/", - api: "https://api22-normal-c-useast2a.tiktokv.com/aweme/v1/feed/?aweme_id={postId}&version_code=262&app_name=musical_ly&channel=App&device_id=null&os_version=14.4.2&device_platform=iphone&device_type=iPhone9®ion=US&carrier_region=US", - userAgent: "TikTok 26.2.0 rv:262018 (iPhone; iOS 14.4.2; en_US) Cronet" - }, - douyin: { - short: "https://v.douyin.com/", - api: "https://www.iesdouyin.com/aweme/v1/web/aweme/detail/?aweme_id={postId}", - userAgent: "TikTok 26.2.0 rv:262018 (iPhone; iOS 14.4.2; en_US) Cronet" - } -} - -function selector(j, h, id) { - if (!j) return false; - let t; - switch (h) { - case "tiktok": - t = j.aweme_list.filter(v => v.aweme_id === id)[0]; - break; - case "douyin": - t = j.aweme_detail; - break; - } - if (t?.length < 3) return false; - return t; -} +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 default async function(obj) { let postId = obj.postId ? obj.postId : false; + if (!process.env.TIKTOK_DEVICE_INFO) return { error: 'ErrorCouldntFetch' }; + if (!postId) { - let html = await fetch(`${config[obj.host].short}${obj.id}`, { + let html = await fetch(`${shortDomain}${obj.id}`, { redirect: "manual", - headers: { "user-agent": userAgent } - }).then((r) => { return r.text() }).catch(() => { return false }); + headers: { + "user-agent": genericUserAgent.split(' Chrome/1')[0] + } + }).then(r => r.text()).catch(() => {}); if (!html) return { error: 'ErrorCouldntFetch' }; @@ -48,30 +27,35 @@ export default async function(obj) { } if (!postId) return { error: 'ErrorCantGetID' }; - let detail; - detail = await fetch(config[obj.host].api.replace("{postId}", postId), { - headers: { - "user-agent": config[obj.host].userAgent - } - }).then((r) => { return r.json() }).catch(() => { return false }); + let deviceInfo = JSON.parse(process.env.TIKTOK_DEVICE_INFO); + deviceInfo = new URLSearchParams(deviceInfo).toString(); - detail = selector(detail, obj.host, postId); + let apiURL = new URL(apiPath); + apiURL.searchParams.append("aweme_id", postId); + + let detail = await fetch(`${apiURL.href}&${deviceInfo}`, { + headers: { + "user-agent": apiUserAgent + } + }).then(r => r.json()).catch(() => {}); + + detail = detail?.aweme_list?.find(v => v.aweme_id === postId); if (!detail) return { error: 'ErrorCouldntFetch' }; let video, videoFilename, audioFilename, isMp3, audio, images, - filenameBase = `${obj.host}_${detail.author.unique_id}_${postId}`; + filenameBase = `tiktok_${detail.author.unique_id}_${postId}`; - if (obj.host === "tiktok") { - images = detail.image_post_info ? detail.image_post_info.images : false - } else { - images = detail.images ? detail.images : false - } + images = detail.image_post_info?.images; + + let playAddr = detail.video.play_addr_h264; + + if (!playAddr) playAddr = detail.video.play_addr; if (!obj.isAudioOnly && !images) { - video = detail.video.play_addr.url_list[0]; + video = playAddr.url_list[0]; videoFilename = `${filenameBase}.mp4`; } else { - let fallback = detail.video.play_addr.url_list[0]; + let fallback = playAddr.url_list[0]; audio = fallback; audioFilename = `${filenameBase}_audio_fv`; // fv - from video if (obj.fullAudio || fallback.includes("music")) { @@ -94,7 +78,7 @@ export default async function(obj) { if (images) { let imageLinks = []; for (let i in images) { - let sel = obj.host === "tiktok" ? images[i].display_image.url_list : images[i].url_list; + let sel = images[i].display_image.url_list; sel = sel.filter(p => p.includes(".jpeg?")) imageLinks.push({url: sel[0]}) }