Turn twist-dl into a nodejs script and make it fully work
This commit is contained in:
parent
90e6fbdca1
commit
a12ae9248a
192
twist-dl.node
Executable file
192
twist-dl.node
Executable file
@ -0,0 +1,192 @@
|
||||
#!/bin/node
|
||||
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const zlib = require('zlib');
|
||||
const crypto = require('crypto');
|
||||
const CryptoJS = require('crypto-js');
|
||||
|
||||
const url = process.argv[2];
|
||||
const episodes = process.argv.slice(3);
|
||||
|
||||
const animeMatches = url.match(/\/a\/([^/]+)(\/([0-9]+))?/i);
|
||||
const anime = animeMatches[1];
|
||||
const episode = Number(animeMatches[3]);
|
||||
if (episodes.length === 0) episodes.push(episode);
|
||||
|
||||
console.log('Anime:', anime, 'episodes:', episodes);
|
||||
|
||||
function handleResponse(response, callback) {
|
||||
const enc = response.headers['content-encoding'];
|
||||
let d = '';
|
||||
response.on('data', data => d += enc === 'gzip' ? zlib.unzipSync(data) : data);
|
||||
response.on('end', () => {
|
||||
callback(d);
|
||||
});
|
||||
}
|
||||
|
||||
function getCookie(then) {
|
||||
https.request(url, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache"
|
||||
}
|
||||
}, response => {
|
||||
handleResponse(response, data => {
|
||||
const script = data.match(/<script>(.+?)e\(r\);<\/script>/);
|
||||
if (!script) {
|
||||
console.error(`Cannot find cookie script in`, data);
|
||||
return;
|
||||
}
|
||||
|
||||
eval(script[1]);
|
||||
r = r.replace('document.cookie=', 'cookie=')
|
||||
.replace('location.reload();', '');
|
||||
eval(r);
|
||||
console.log('Found cookie:', cookie);
|
||||
then(cookie);
|
||||
});
|
||||
}).end();
|
||||
}
|
||||
|
||||
function getAccessToken(cookie, then) {
|
||||
https.request(url, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache",
|
||||
"Cookie": cookie
|
||||
}
|
||||
}, response => {
|
||||
handleResponse(response, data => {
|
||||
let reqMatches = data.match(/(<link href="([^"]+?)\.js" rel="preload" as="script">)/g);
|
||||
if (!reqMatches) {
|
||||
console.error(`Couldn't find script.`, data);
|
||||
console.log(cookie);
|
||||
console.log(response.headers);
|
||||
return;
|
||||
}
|
||||
reqMatches = reqMatches[reqMatches.length - 1].match(/href="(.+?)"/);
|
||||
const scriptUrl = "https://twist.moe" + reqMatches[1];
|
||||
|
||||
console.log("Obtaining access token from " + scriptUrl);
|
||||
https.request(scriptUrl, {
|
||||
}, response => {
|
||||
handleResponse(response, data => {
|
||||
const accessToken = data.match(/"token=([^"]+?)"/)[1];
|
||||
const password = data.match(/name: ?".+",.+?k: ?"([^"]+?)",/)[1];
|
||||
console.log('Access token:', accessToken);
|
||||
//k: "LXgIVP&PorO68Rq7dTx8N^lP!Fa5sGJ^*XK",
|
||||
console.log('password:', password);
|
||||
then(accessToken, password);
|
||||
});
|
||||
}).end();
|
||||
});
|
||||
}).end();
|
||||
}
|
||||
|
||||
function getPlaylist(accessToken, then) {
|
||||
https.request('https://twist.moe/api/anime/' + anime + '/sources', {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0",
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache",
|
||||
"Referer": url,
|
||||
"x-access-token": accessToken,
|
||||
}
|
||||
}, response => {
|
||||
handleResponse(response, data => {
|
||||
const playlist = JSON.parse(data);
|
||||
console.log('Playlist found with', playlist.length, 'items.');
|
||||
then(playlist);
|
||||
});
|
||||
}).end();
|
||||
}
|
||||
|
||||
function download(sourceUrl, file) {
|
||||
return new Promise(resolve => {
|
||||
https.request(sourceUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0",
|
||||
"Accept": "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
|
||||
"Accept-Language": "en-US,en;q=0.5",
|
||||
"Range": "bytes=0-",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "https://twist.moe",
|
||||
"Pragma": "no-cache",
|
||||
"Cache-Control": "no-cache",
|
||||
}
|
||||
}, response => {
|
||||
if (response.headers.location) {
|
||||
console.log('Redirected to', response.headers.location);
|
||||
return resolve(download(response.headers.location, file));
|
||||
}
|
||||
|
||||
console.log('Downloading', sourceUrl, 'to', file);
|
||||
|
||||
const t = Number(response.headers["content-length"]);
|
||||
let l = 0;
|
||||
response.on('data', data => {
|
||||
fs.appendFileSync(file, data);
|
||||
l += data.length;
|
||||
process.stdout.clearLine();
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(((l / t) * 100).toFixed(2) + '%');
|
||||
});
|
||||
|
||||
response.on('end', () => {
|
||||
console.log();
|
||||
console.log('Done');
|
||||
resolve();
|
||||
});
|
||||
}).end();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
getCookie(cookie => {
|
||||
getAccessToken(cookie, (accessToken, password) => {
|
||||
getPlaylist(accessToken, async playlist => {
|
||||
try {
|
||||
for (const episode of episodes) {
|
||||
const episodeData = playlist[episode - 1];
|
||||
if (!episodeData) {
|
||||
console.error('Episode', episode, 'not found.');
|
||||
continue;
|
||||
}
|
||||
|
||||
const url = 'https://twistcdn.bunny.sh' + CryptoJS.AES.decrypt(episodeData.source, password).toString(CryptoJS.enc.Utf8);
|
||||
|
||||
// Get automatic filename
|
||||
const parts = url.split('/');
|
||||
const file = parts[parts.length - 1];
|
||||
if (fs.existsSync(file)) {
|
||||
console.error(file + ' already exists.');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Download
|
||||
await download(url, file);
|
||||
}
|
||||
} catch (err) { console.error(err); }
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
});
|
13
twist-dl.sh
13
twist-dl.sh
@ -1,13 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
url=$1
|
||||
|
||||
curl -g -H "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:79.0) Gecko/20100101 Firefox/79.0" \
|
||||
-H "Accept: video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5" \
|
||||
-H "Accept-Language: en-US,en;q=0.5" \
|
||||
-H "Range: bytes=0-" \
|
||||
-H "Connection: keep-alive" \
|
||||
-H "Referer: https://twist.moe" \
|
||||
-H "Pragma: no-cache" \
|
||||
-H "Cache-Control: no-cache" \
|
||||
"$url"
|
Loading…
Reference in New Issue
Block a user