diff --git a/destreamer.ts b/destreamer.ts index e9cadd0..7b84b8f 100644 --- a/destreamer.ts +++ b/destreamer.ts @@ -5,10 +5,16 @@ import fs from 'fs'; import path from 'path'; import { BrowserTests } from './BrowserTests'; import yargs = require('yargs'); -import sanitize = require('sanitize-filename') +import sanitize = require('sanitize-filename'); +import axios from 'axios'; + + +/** + * exitCode 25 = cannot split videoID from videUrl + * exitCode 27 = no hlsUrl in the api + * exitCode 88 = error extracting cookies + */ -// Type in your username here (the one you use to -// login to Microsoft Stream). const args: string[] = process.argv.slice(2); // TODO: Remove this const argv = yargs.options({ @@ -69,7 +75,7 @@ function sanityChecks() { } /* Removed check on the first argoumenti not being null or - longer than 10 since we have yargs now */ + longer than 10 since we use yargs now */ } async function rentVideoForLater(videoUrls: string[], username: string, outputDirectory: string) { @@ -84,7 +90,7 @@ async function rentVideoForLater(videoUrls: string[], username: string, outputDi // This breaks on slow connections, needs more reliable logic //const oidcUrl = "https://login.microsoftonline.com/common/oauth2/authorize?client_id=cf53fce8-def6-4aeb-8d30-b158e7b1cf83&response_mode=form_post&response_type=code+id_token&scope=openid+profile&state=OpenIdConnect.AuthenticationProperties%3d1VtrsKV5QUHtzn8cDWL4wJmacu-VHH_DfpPxMQBhnfbar-_e8X016GGJDPfqfvcyUK3F3vBoiFwUpahR2ANfrzHE469vcw7Mk86wcAqBGXCvAUmv59MDU_OZFHpSL360oVRBo84GfVXAKYdhCjhPtelRHLHEM_ADiARXeMdVTAO3SaTiVQMhw3c9vLWuXqrKKevpI7E5esCQy5V_dhr2Q7kKrlW3gHX0232b8UWAnSDpc-94&nonce=636832485747560726.NzMyOWIyYWQtM2I3NC00MmIyLTg1NTMtODBkNDIwZTI1YjAxNDJiN2JkNDMtMmU5Ni00OTc3LWFkYTQtNTNlNmUwZmM1NTVl&nonceKey=OpenIdConnect.nonce.F1tPks6em0M%2fWMwvatuGWfFM9Gj83LwRKLvbx9rYs5M%3d&site_id=500453&redirect_uri=https%3a%2f%2fmsit.microsoftstream.com%2f&post_logout_redirect_uri=https%3a%2f%2fproducts.office.com%2fmicrosoft-stream&msafed=0"; - await page.goto(videoUrls[0], { waitUntil: 'networkidle2' }); + await page.goto(videoUrls[0], { waitUntil: "networkidle2" }); await page.waitForSelector('input[type="email"]'); await page.keyboard.type(username); await page.click('input[type="submit"]'); @@ -95,7 +101,11 @@ async function rentVideoForLater(videoUrls: string[], username: string, outputDi console.log('Sorry, i mean "you".'); for (let videoUrl of videoUrls) { - await page.goto(videoUrl, { waitUntil: 'networkidle2' }); + let videoID = videoUrl.split('/').pop() ?? (console.error("Couldn't split the videoID, wrong url"), process.exit(25)) + + // changed waitUntil value to load (page completly loaded) + await page.goto(videoUrl, { waitUntil: 'load' }); + await sleep(2000); // try this instead of hardcoding sleep // https://github.com/GoogleChrome/puppeteer/issues/3649 @@ -104,39 +114,20 @@ async function rentVideoForLater(videoUrls: string[], username: string, outputDi console.log('Got cookie. Consuming cookie...'); await sleep(4000); - console.log('Looking up AMS stream locator...'); + console.log("Accessing API..."); - let document: any; - const amsUrl = await page.evaluate( - // maybe there should be some check in case the url fetch fails + let sessionInfo: any; + var accesToken = await page.evaluate( () => { - return document?.querySelector(".azuremediaplayer")?.player?.cache_?.src; + return sessionInfo.AccessToken; } ); - // console.log(`Video url is: ${amsUrl}`); - console.log('Fetching title...'); + console.log("Fetching title and HLS URL...") + var [title, hlsUrl] = await getVideoInfo(videoID, accesToken) - let title = await page.evaluate( - // Using optional chaining to return handle null case, generating default name - () => { - return document?.querySelector(".title")?.textContent?.trim() ?? - `Video${videoUrls.indexOf(videoUrl)}`; - } - ); + title = (sanitize(title) == "") ? `Video${videoUrls.indexOf(videoUrl)}` : sanitize(title) - // Implemented sanitize-filename as suggested in issue #11 - title = sanitize(title) - - if (title == "") - title = `Video${videoUrls.indexOf(videoUrl)}` - - //console.log(`Video title is: ${title}`); - - console.log('Constructing HLS URL...'); - const hlsUrl = amsUrl.substring(0, amsUrl.lastIndexOf('/')) + '/manifest(format=m3u8-aapl)'; - - // If the simulate flag is true skip the download if (!argv.simulate) { console.log('Spawning youtube-dl with cookie and HLS URL...'); let format = '' @@ -186,6 +177,42 @@ async function exfiltrateCookie(page: puppeteer.Page) { return `Authorization=${authzCookie.value}; Signature=${sigCookie.value}`; } + +async function getVideoInfo(videoID: string, accesToken: string) { + let title: string; + let hlsUrl: string; + + let content = axios.get( + `https://euwe-1.api.microsoftstream.com/api/videos/${videoID}?$expand=creator,tokens,status,liveEvent,extensions&api-version=1.3-private`, + { + headers: { + Authorization: "Bearer " + accesToken + } + }) + .then(function (response) { + return response.data + }) + .catch(function (error) { + console.error(error) + }) + + + title = await content.then(data => { + return data["name"]; + }) + + hlsUrl = await content.then(data => { + for (const item of data["playbackUrls"]) { + if (item["mimeType"] == "application/vnd.apple.mpegurl") + return item["playbackUrl"] + } + console.error("Error fetching hlsUrl") + process.exit(27) + }) + + return [title, hlsUrl]; +} + // We should probably use Mocha or something if (args[0] === 'test') {