Skip to content
This repository has been archived by the owner on Aug 28, 2021. It is now read-only.

Commit

Permalink
Merge pull request #77 from CT1994/echo360-download-url-path-fix
Browse files Browse the repository at this point in the history
add the ability to download direct url if not hls
  • Loading branch information
ericjiang97 authored Jul 6, 2020
2 parents 1615e98 + 03835e8 commit ae5e464
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 24 deletions.
55 changes: 38 additions & 17 deletions app/src/background.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,62 @@
async function getDownloadLink({lessonID, lessonName}, echo360Domain, downloadHD) {
const regex = /(?:\(\")(?:.*)(?:\"\))/;
const classroomAppRegex = new RegExp('(classroomApp)');
const dataRegex = /(?:\(\")(?:.*)(?:\"\))/;
const lessonHTMLPageRequest = new Request(`${echo360Domain}/lesson/${lessonID}/classroom`, { method: 'GET', credentials: 'include' });
const lessonHTMLPageResponse = await fetch(lessonHTMLPageRequest)
const lessonHTMLPageText = await lessonHTMLPageResponse.text();
const dummyEl = document.createElement('html')
dummyEl.innerHTML = lessonHTMLPageText;
const videoDataScript = Array.from(dummyEl.getElementsByTagName('script')).filter((script) => classroomAppRegex.test(script.innerText));

const videoDataString = dummyEl.getElementsByTagName('script')[11].innerText.match(regex)[0]
if (videoDataScript.length === 0)
{
return null;
}

const videoDataString = videoDataScript[0].innerText.match(dataRegex)[0];

const cleanString = videoDataString.substring(1, videoDataString.length - 1);
const videoDataObject = JSON.parse(JSON.parse(cleanString));

let totalSoruces = 0;
const videosData = videoDataObject.video.playableMedias.filter((videoData) => {
const includesAudio = videoData.trackType.includes('Audio')
const includesVideo = videoData.trackType.includes('Video')
const wantedQuality = downloadHD ? videoData.quality.includes(1) : videoData.quality.includes(0)
return includesAudio && includesVideo && wantedQuality;
})

if (!videoDataObject.video) {
if (videosData.length === 0) {
return null;
}

videoDataObject.video.playableMedias.forEach((media) => {
if (media.sourceIndex > totalSoruces) {
totalSoruces = media.sourceIndex;
const downloadArray = videosData.map((videoData) => {
const quality = downloadHD ? `hd` : `sd`;
const videoName = `video_source_${videoData.sourceIndex}_${quality}.mp4`

if (videoData.isHls)
{
// here i am guessing what the url is since hls video are different format
const templateUrl = new URL(videoData.uri);
templateUrl.search = '';
templateUrl.pathname = templateUrl.pathname.replace(/\/[^\/]*$/, `/${quality}${videoData.sourceIndex}.mp4`)

return {
url: templateUrl.href,
lessonName,
videoName,
}
}
})

const downloadArray = [];
for (let i = 1; i <= totalSoruces; i++) {
const quality = downloadHD ? `hd${i}.mp4` : `sd${i}.mp4`;
const videoName = `video_source_${i}_${quality}`
const templateUrl = new URL(videoDataObject.video.playableMedias[0].uri);
const templateUrl = new URL(videoData.uri);
templateUrl.search = '';
templateUrl.pathname = templateUrl.pathname.replace(/\/[^\/]*$/, `/${quality}`)

downloadArray.push({
return {
url: templateUrl.href,
lessonName,
videoName,
});
}
}

});

return downloadArray;
}
Expand Down
29 changes: 22 additions & 7 deletions app/src/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ const echo360BaseURIs = [
* @param {*} param0
* @returns {boolean}
*/
function canDownload({ lesson }) {
function canDownload(lesson) {
if (lesson.medias.length <= 0) {
return false;
}

return lesson.medias.some((media) => media.mediaType === "Video")
}

function getVideoFileName({lesson}) {
const {updatedAt} = lesson.lesson;
function getVideoFileName(lesson) {
const { updatedAt } = lesson.lesson;
return updatedAt.slice(0, updatedAt.indexOf("T"));
}

Expand All @@ -55,7 +55,7 @@ async function getCourseName(courseSectionId) {

const courseMatch = courseDataJson.data[0].userSections.find((userSection) => userSection.sectionId === courseSectionId)

return courseMatch.courseName.replace(/[/\\?%*:|"<>]/g, ' ')
return courseMatch ? courseMatch.courseName.replace(/[/\\?%*:|"<>]/g, ' ') : 'unknown course';
}

/**
Expand All @@ -68,6 +68,20 @@ async function getMediaData({ url }) {
return getMediaLessonsResponse.json();
}

function getLessons(mediaLessonsJson) {
const lessons = [];
mediaLessonsJson.data.forEach((mediaLessonData) => {
if (mediaLessonData.lessons) {
mediaLessonData.lessons.forEach((lesson) => lessons.push(lesson.lesson));
}
else {
lessons.push(mediaLessonData.lesson);
}
})

return lessons;
}

// Job of this function is to listen init mediaLessons once per click.
async function webRequestOnComplete(xhrRequest) {
console.log("Media Lessons obtained!");
Expand All @@ -76,11 +90,12 @@ async function webRequestOnComplete(xhrRequest) {
mediaLessons = xhrRequest;

const getMediaLessonsJson = await getMediaData(mediaLessons);
const courseSectionId = getMediaLessonsJson.data[0].lesson.lesson.sectionId;
const lessons = getLessons(getMediaLessonsJson);
const courseSectionId = lessons[0].lesson.sectionId;
courseName = await getCourseName(courseSectionId);

console.log(getMediaLessonsJson);
downloadables = getMediaLessonsJson.data.filter((dataItem) => {
downloadables = lessons.filter((dataItem) => {
return canDownload(dataItem);
});

Expand Down Expand Up @@ -169,7 +184,7 @@ document.addEventListener('DOMContentLoaded', function () {
options.forEach((option, i) => {
if (option.selected) {
toDownload.push({
lessonID: downloadables[i].lesson.lesson.id,
lessonID: downloadables[i].lesson.id,
lessonName: getVideoFileName(downloadables[i]),
})
}
Expand Down

0 comments on commit ae5e464

Please sign in to comment.