fix tiktok downloads (#407)

This commit is contained in:
wukko 2024-03-29 06:57:05 +06:00 committed by GitHub
commit 36c697cc5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 57 additions and 51 deletions

View file

@ -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`. | | `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. | | `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. | | `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)). \* 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": "<install_id here>",
"device_id": "<device_id here>",
"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":"<install_id here>","device_id":"<device_id here>","channel":"googleplay","app_name":"musical_ly","version_code":"310503","device_platform":"android","device_type":"Redmi+7","os_version":"13"}'
```
### variables for web ### variables for web
| variable name | default | example | description | | variable name | default | example | description |
|:---------------------|:---------------------|:------------------------|:--------------------------------------------------------------------------------------| |:---------------------|:---------------------|:------------------------|:--------------------------------------------------------------------------------------|

View file

@ -1,7 +1,7 @@
{ {
"name": "cobalt", "name": "cobalt",
"description": "save what you love", "description": "save what you love",
"version": "7.12.1", "version": "7.12.2",
"author": "wukko", "author": "wukko",
"exports": "./src/cobalt.js", "exports": "./src/cobalt.js",
"type": "module", "type": "module",

View file

@ -452,6 +452,7 @@ button:active,
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 18px; gap: 18px;
width: 100%;
} }
.popup.small.visible { .popup.small.visible {
transform: translate(-50%, -50%); transform: translate(-50%, -50%);

View file

@ -1,7 +1,7 @@
{ {
"name": "english", "name": "english",
"substrings": { "substrings": {
"ContactLink": "check the <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">status page</a> or <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>." "ContactLink": "check the <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">status page</a> or <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">create an issue on github</a>"
}, },
"strings": { "strings": {
"AppTitleCobalt": "cobalt", "AppTitleCobalt": "cobalt",

View file

@ -83,10 +83,8 @@ export default async function(host, patternMatch, url, lang, obj) {
id: patternMatch.id id: patternMatch.id
}); });
break; break;
case "douyin":
case "tiktok": case "tiktok":
r = await tiktok({ r = await tiktok({
host: host,
postId: patternMatch.postId, postId: patternMatch.postId,
id: patternMatch.id, id: patternMatch.id,
fullAudio: obj.isTTFullAudio, fullAudio: obj.isTTFullAudio,

View file

@ -1,42 +1,21 @@
import { genericUserAgent } from "../../config.js"; import { genericUserAgent } from "../../config.js";
const userAgent = genericUserAgent.split(' Chrome/1')[0], const shortDomain = "https://vt.tiktok.com/";
config = { const apiPath = "https://api22-normal-c-alisg.tiktokv.com/aweme/v1/feed/?region=US&carrier_region=US";
tiktok: { const apiUserAgent = "TikTok/338014 CFNetwork/1410.1 Darwin/22.6.0";
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&region=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;
}
export default async function(obj) { export default async function(obj) {
let postId = obj.postId ? obj.postId : false; let postId = obj.postId ? obj.postId : false;
if (!process.env.TIKTOK_DEVICE_INFO) return { error: 'ErrorCouldntFetch' };
if (!postId) { if (!postId) {
let html = await fetch(`${config[obj.host].short}${obj.id}`, { let html = await fetch(`${shortDomain}${obj.id}`, {
redirect: "manual", redirect: "manual",
headers: { "user-agent": userAgent } headers: {
}).then((r) => { return r.text() }).catch(() => { return false }); "user-agent": genericUserAgent.split(' Chrome/1')[0]
}
}).then(r => r.text()).catch(() => {});
if (!html) return { error: 'ErrorCouldntFetch' }; if (!html) return { error: 'ErrorCouldntFetch' };
@ -48,30 +27,35 @@ export default async function(obj) {
} }
if (!postId) return { error: 'ErrorCantGetID' }; if (!postId) return { error: 'ErrorCantGetID' };
let detail; let deviceInfo = JSON.parse(process.env.TIKTOK_DEVICE_INFO);
detail = await fetch(config[obj.host].api.replace("{postId}", postId), { deviceInfo = new URLSearchParams(deviceInfo).toString();
headers: {
"user-agent": config[obj.host].userAgent
}
}).then((r) => { return r.json() }).catch(() => { return false });
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' }; if (!detail) return { error: 'ErrorCouldntFetch' };
let video, videoFilename, audioFilename, isMp3, audio, images, 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?.images;
images = detail.image_post_info ? detail.image_post_info.images : false
} else { let playAddr = detail.video.play_addr_h264;
images = detail.images ? detail.images : false
} if (!playAddr) playAddr = detail.video.play_addr;
if (!obj.isAudioOnly && !images) { if (!obj.isAudioOnly && !images) {
video = detail.video.play_addr.url_list[0]; video = playAddr.url_list[0];
videoFilename = `${filenameBase}.mp4`; videoFilename = `${filenameBase}.mp4`;
} else { } else {
let fallback = detail.video.play_addr.url_list[0]; let fallback = playAddr.url_list[0];
audio = fallback; audio = fallback;
audioFilename = `${filenameBase}_audio_fv`; // fv - from video audioFilename = `${filenameBase}_audio_fv`; // fv - from video
if (obj.fullAudio || fallback.includes("music")) { if (obj.fullAudio || fallback.includes("music")) {
@ -94,7 +78,7 @@ export default async function(obj) {
if (images) { if (images) {
let imageLinks = []; let imageLinks = [];
for (let i in images) { 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?")) sel = sel.filter(p => p.includes(".jpeg?"))
imageLinks.push({url: sel[0]}) imageLinks.push({url: sel[0]})
} }