1
0
mirror of https://github.com/snobu/destreamer.git synced 2026-02-01 04:42:22 +00:00

Still a way long to go

This commit is contained in:
snobu
2020-03-31 12:41:21 +03:00
parent 7c0a2b53ce
commit b20bbe0b5e

View File

@@ -86,33 +86,18 @@ function sanityChecks() {
} }
} }
async function rentVideoForLater(videoUrls: string[], outputDirectory: string, username?: string) {
let accessToken = null;
try { async function DoInteractiveLogin(username?: string) {
accessToken = tokenCache.Read();
console.log(`Read returned: ${accessToken}`);
process.exit(200);
}
catch (e)
{
console.log("cache is empty or expired");
console.log(accessToken);
process.exit(404);
}
console.log('Launching headless Chrome to perform the OpenID Connect dance...'); console.log('Launching headless Chrome to perform the OpenID Connect dance...');
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
// Switch to false if you need to login interactively
headless: false, headless: false,
args: ['--disable-dev-shm-usage'] args: ['--disable-dev-shm-usage']
}); });
const page = (await browser.pages())[0]; const page = (await browser.pages())[0];
console.log('Navigating to STS login page...'); console.log('Navigating to microsoftonline.com login page...');
// This breaks on slow connections, needs more reliable logic // This breaks on slow connections, needs more reliable logic
await page.goto(videoUrls[0], { waitUntil: "networkidle2" }); await page.goto('https://web.microsoftstream.com', { waitUntil: "networkidle2" });
await page.waitForSelector('input[type="email"]'); await page.waitForSelector('input[type="email"]');
if (username) { if (username) {
@@ -126,75 +111,102 @@ async function rentVideoForLater(videoUrls: string[], outputDirectory: string, u
// Who am i to deny a perfectly good nap? // Who am i to deny a perfectly good nap?
await sleep(1500); await sleep(1500);
for (let videoUrl of videoUrls) { console.log('Got cookie. Consuming cookie...');
let videoID = videoUrl.split('/').pop() ??
(console.error("Couldn't split the videoID, wrong url"), process.exit(25)); await sleep(4000);
console.log("Calling Microsoft Stream API...");
let cookie = await exfiltrateCookie(page);
// changed waitUntil value to load (page completly loaded) let sessionInfo: any;
await page.goto(videoUrl, { waitUntil: 'load' }); let session = await page.evaluate(
() => {
await sleep(2000); return {
// try this instead of hardcoding sleep AccessToken: sessionInfo.AccessToken,
// https://github.com/GoogleChrome/puppeteer/issues/3649 ApiGatewayUri: sessionInfo.ApiGatewayUri,
ApiGatewayVersion: sessionInfo.ApiGatewayVersion,
const cookie = await exfiltrateCookie(page); Cookie: cookie
console.log('Got cookie. Consuming cookie...'); };
await sleep(4000);
console.log("Calling Microsoft Stream API...");
let sessionInfo: any;
let session = await page.evaluate(
() => {
return {
AccessToken: sessionInfo.AccessToken,
ApiGatewayUri: sessionInfo.ApiGatewayUri,
ApiGatewayVersion: sessionInfo.ApiGatewayVersion
};
}
);
tokenCache.Write(session.AccessToken);
console.log(`ApiGatewayUri: ${session.ApiGatewayUri}`);
console.log(`ApiGatewayVersion: ${session.ApiGatewayVersion}`);
console.log("Fetching title and HLS URL...");
var [title, hlsUrl] = await getVideoInfo(videoID, session);
title = (sanitize(title) == "") ?
`Video${videoUrls.indexOf(videoUrl)}` :
sanitize(title);
term.blue("Video title is: ");
console.log(`${title} \n`);
console.log('Spawning youtube-dl with cookie and HLS URL...');
const format = argv.format ? `-f "${argv.format}"` : "";
var youtubedlCmd = 'youtube-dl --no-call-home --no-warnings ' + format +
` --output "${outputDirectory}/${title}.mp4" --add-header ` +
`Cookie:"${cookie}" "${hlsUrl}"`;
if (argv.simulate) {
youtubedlCmd = youtubedlCmd + " -s";
} }
);
tokenCache.Write(session.AccessToken);
console.log("Wrote access token to token cache.");
if (argv.verbose) { console.log(`ApiGatewayUri: ${session.ApiGatewayUri}`);
console.log(`\n\n[VERBOSE] Invoking youtube-dl:\n${youtubedlCmd}\n\n`); console.log(`ApiGatewayVersion: ${session.ApiGatewayVersion}`);
console.log("At this point Chromium's job is done, shutting it down...");
await browser.close();
return session;
}
function extractVideoGuid(videoUrls: string[]): string[] {
let videoGuids: string[] = [];
let guid: string = "";
for (let url of videoUrls) {
try {
let guid = url.split('/').pop();
} }
execSync(youtubedlCmd, { stdio: 'inherit' }); catch (e)
{
console.error(`Could not split the video GUID from URL: ${e.message}`);
process.exit(25);
}
videoGuids.push(guid);
}
return videoGuids;
}
async function rentVideoForLater(videoUrls: string[], outputDirectory: string, session: object) {
const videoGuids = extractVideoGuid(videoUrls);
let accessToken = null;
try {
accessToken = tokenCache.Read();
}
catch (e)
{
console.log("Cache is empty or expired, performing interactive log on...");
} }
console.log("At this point Chrome's job is done, shutting it down..."); console.log("Fetching title and HLS URL...");
await browser.close(); var [title, hlsUrl] = await getVideoMetadata(videoGuids, session);
title = (sanitize(title) == "") ?
`Video${videoUrls.indexOf(videoUrl)}` :
sanitize(title);
term.blue("Video title is: ");
console.log(`${title} \n`);
console.log('Spawning youtube-dl with cookie and HLS URL...');
const format = argv.format ? `-f "${argv.format}"` : "";
var youtubedlCmd = 'youtube-dl --no-call-home --no-warnings ' + format +
` --output "${outputDirectory}/${title}.mp4" --add-header ` +
`Cookie:"${session.AccessToken}" "${hlsUrl}"`;
if (argv.simulate) {
youtubedlCmd = youtubedlCmd + " -s";
}
if (argv.verbose) {
console.log(`\n\n[VERBOSE] Invoking youtube-dl:\n${youtubedlCmd}\n\n`);
}
execSync(youtubedlCmd, { stdio: 'inherit' });
} }
function sleep(ms: number) { function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
async function exfiltrateCookie(page: puppeteer.Page) { async function exfiltrateCookie(page: puppeteer.Page) {
var jar = await page.cookies("https://.api.microsoftstream.com"); var jar = await page.cookies("https://.api.microsoftstream.com");
var authzCookie = jar.filter(c => c.name === 'Authorization_Api')[0]; var authzCookie = jar.filter(c => c.name === 'Authorization_Api')[0];
@@ -215,60 +227,66 @@ async function exfiltrateCookie(page: puppeteer.Page) {
return `Authorization=${authzCookie.value}; Signature=${sigCookie.value}`; return `Authorization=${authzCookie.value}; Signature=${sigCookie.value}`;
} }
class Metadata {
title!: string;
hlsUrl!: string;
playbackUrl!: string;
}
async function getVideoInfo(videoID: string, session: any) { async function getVideoMetadata(videoGuids: string[], session: any) {
let title: string; let title: string;
let hlsUrl: string; let hlsUrl: string;
let metadata = new Metadata();
let content = axios.get( videoGuids.forEach(guid => {
`${session.ApiGatewayUri}videos/${videoID}` + let content = axios.get(
`?$expand=creator,tokens,status,liveEvent,extensions&api-version=${session.ApiGatewayVersion}`, `${session.ApiGatewayUri}videos/${guid}?api-version=${session.ApiGatewayVersion}`,
{ {
headers: { headers: {
Authorization: `Bearer ${session.AccessToken}` Authorization: `Bearer ${session.AccessToken}`
} }
}) })
.then(function (response) { .then(function (response) {
return response.data; return response.data;
}) })
.catch(function (error) { .catch(function (error) {
term.red('Error when calling Microsoft Stream API: ' + term.red('Error when calling Microsoft Stream API: ' +
`${error.response.status} ${error.response.reason}`); `${error.response.status} ${error.response.reason}`);
console.error(error.response.status); console.error(error.response.status);
console.error(error.response.data); console.error(error.response.data);
console.error("Exiting..."); console.error("Exiting...");
if (argv.verbose) { if (argv.verbose) {
console.error(`[VERBOSE] ${error}`); console.error(`[VERBOSE] ${error}`);
} }
process.exit(29); process.exit(29);
}); });
title = await content.then(data => { title = await content.then(data => {
return data["name"]; return data["name"];
}); });
hlsUrl = await content.then(data => { hlsUrl = await content.then(data => {
if (argv.verbose) { if (argv.verbose) {
console.log(JSON.stringify(data, undefined, 2)); console.log(JSON.stringify(data, undefined, 2));
} }
let playbackUrl = null; let playbackUrl = null;
try { try {
playbackUrl = data["playbackUrls"] playbackUrl = data["playbackUrls"]
.filter((item: { [x: string]: string; }) => .filter((item: { [x: string]: string; }) =>
item["mimeType"] == "application/vnd.apple.mpegurl") item["mimeType"] == "application/vnd.apple.mpegurl")
.map((item: { [x: string]: string }) => .map((item: { [x: string]: string }) =>
{ return item["playbackUrl"]; })[0]; { return item["playbackUrl"]; })[0];
} }
catch (e) { catch (e) {
console.error(`Error fetching HLS URL: ${e}.\n playbackUrl is ${playbackUrl}`); console.error(`Error fetching HLS URL: ${e}.\n playbackUrl is ${playbackUrl}`);
process.exit(27); process.exit(27);
} }
return playbackUrl; return playbackUrl;
}); });
return [title, hlsUrl]; return [title, hlsUrl];
});
} }
// We should probably use Mocha or something // We should probably use Mocha or something