diff --git a/app/src/background.js b/app/src/background.js index 3dbe7ff..bdc579f 100644 --- a/app/src/background.js +++ b/app/src/background.js @@ -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; } diff --git a/app/src/popup.js b/app/src/popup.js index fcb568f..1160b45 100644 --- a/app/src/popup.js +++ b/app/src/popup.js @@ -30,7 +30,7 @@ const echo360BaseURIs = [ * @param {*} param0 * @returns {boolean} */ -function canDownload({ lesson }) { +function canDownload(lesson) { if (lesson.medias.length <= 0) { return false; } @@ -38,8 +38,8 @@ function canDownload({ lesson }) { 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")); } @@ -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'; } /** @@ -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!"); @@ -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); }); @@ -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]), }) }