// ==UserScript==
// @name Udemy - show section time
// @namespace http://tampermonkey.net/
// @version 1.3
// @description For Udemy, displays the time a section has ( remaining time / total time).
// @copyright 2017, Pedro Mass (https://github.com/pedro-mass)
// @author pedro-mass
// @match *.udemy.com/*
// @grant none
// @require http://code.jquery.com/jquery-3.2.0.min.js
// @require https://greasyfork.org/scripts/5392-waitforkeyelements/code/WaitForKeyElements.js?version=115012
// @run-at document-idle
// @downloadURL https://update.greasyfork.icu/scripts/28295/Udemy%20-%20show%20section%20time.user.js
// @updateURL https://update.greasyfork.icu/scripts/28295/Udemy%20-%20show%20section%20time.meta.js
// ==/UserScript==
(function () {
var $ = window.$;
var classes = {
sectionTime: "section-time",
};
var selectors = {
sectionCard: "[class^=section--section--] > .panel-body",
sectionHeader: "[class^=section--section-heading--]",
sectionTitle: "[class^=section--section-heading--] > h3",
sectionProgress: "[class^=section--section-heading--] > .text-secondary",
// class needed by this user script
sectionTime: "." + classes.sectionTime,
lectureItem: "[class^=curriculum-item--curriculum-item--]",
lectureTime: "[class^=curriculum-item--duration--]",
// lectureStatus: '.cur-status',
lectureStatus: "[class^=curriculum-item--progress]",
lectureProgress: "#top-detail > div.detail__progress > div > div.fx",
lectureCompleted: "[class^=curriculum-item--is-completed]",
};
// run();
// waits for the cards to be loaded
waitForKeyElements(selectors.sectionCard, run, true);
/**
Gets total and remaining time for each section.
Displays these per section.
Display the total of all sections
*/
function run() {
var sections = $(selectors.sectionCard);
var totalLectureTime = 0;
var remainingLectureTime = 0;
$.each(sections, function (index, section) {
// remove previous time display
$(section).find(selectors.sectionTime).remove();
// get the section title
var title = $(section).find(selectors.sectionTitle).text();
// get the total times
var totalTimeTexts = getTimeTexts(section, false);
var totalTimeSeconds = textTimesToSeconds(totalTimeTexts);
var totalTime = secondsToTextTime(totalTimeSeconds);
// update the lecture time
totalLectureTime += totalTimeSeconds;
// initialize the text to display with the total time
var textToDisplay = totalTime;
// check if we need to display partial time
if (checkPartialTime(section)) {
// sum the partial times
var partialTimeTexts = getTimeTexts(section, true);
var partialTimeSeconds = textTimesToSeconds(partialTimeTexts);
var partialTime = secondsToTextTime(partialTimeSeconds);
// update the lecture time
remainingLectureTime += partialTimeSeconds;
// prepend partial time
textToDisplay = partialTime + " / " + textToDisplay;
}
// check if we need to add up the total time to remaining time
if (getRemainingParts(section) === 0) {
remainingLectureTime += totalTimeSeconds;
}
// display the section text
displaySectionTime(section, textToDisplay);
});
// display lecture totals
displayLectureTimeProgress(totalLectureTime, remainingLectureTime);
// stops the script from running continuously
return false;
}
function displayLectureTimeProgress(totalLectureTime, remainingLectureTime) {
// start with total
var displayText = secondsToTextTime(totalLectureTime);
// conditional add remaining
if (remainingLectureTime) {
displayText =
secondsToTextTime(remainingLectureTime) + " / " + displayText;
}
// surround in parens
displayText = "(" + displayText + ")";
// add to DOM
var lectureProgressClass = "lecture-progress-time";
var lectureProgressSpans = $(selectors.lectureProgress).find(
"." + lectureProgressClass
);
if (lectureProgressSpans.length > 0) {
$(lectureProgressSpans[0]).text(displayText);
} else {
$(selectors.lectureProgress + " > div").before(
"' +
displayText +
""
);
}
return displayText;
}
/*
Gets the section parts and checks if it's not 0 or the total parts
*/
function checkPartialTime(section) {
// get the section parts
var sectionParts = getSectionParts(section);
// determine what times to get
var totalSections = sectionParts[1];
var sectionsToGo = sectionParts[0];
return sectionsToGo !== 0 && sectionsToGo != totalSections;
}
function getRemainingParts(section) {
// get the section parts
var sectionParts = getSectionParts(section);
// determine what times to get
return sectionParts[0];
}
/**
Get the section's time. Whether partial or total is determined by the passed in
parameter.
*/
function getTimeTexts(section, isPartialTime) {
// get the times
var $lectures = $(section).find(selectors.lectureItem);
// Check for partial time
if (isPartialTime) {
// filter down to just the non-completed ones
$lectures = $lectures.filter((index, element) => {
return $(element).has(selectors.lectureCompleted).length === 0;
});
}
// get the time spans
var timeSpans = $lectures.find(selectors.lectureTime);
// convert to timeTexts and return
return convertTimeSpansToTexts(timeSpans);
}
function displaySectionTime(section, displayText) {
var sectionTimeClass = classes.sectionTime;
// prepend to lecture status
var location = $(section).find(selectors.sectionHeader);
var totalTimeSpan = location.find(sectionTimeClass);
// check to see if we've already added the time to the DOM
if (totalTimeSpan.length > 0) {
$(totalTimeSpan[0]).text(displayText);
} else {
// we haven't, so create the element and add it
location.prepend(
'' +
displayText +
""
);
}
}
function getSectionParts(section) {
return (
$(section)
.find(selectors.sectionProgress)
.text()
// split up the parts by "/"
.split("/")
// trim up the space
.map(function (text) {
return text.trim();
})
);
}
function convertTimeSpansToTexts(timeSpans) {
var timeTexts = [];
for (var i = 0; i < timeSpans.length; i++) {
timeTexts.push($(timeSpans[i]).text());
}
return timeTexts;
}
function convertTextToSeconds(textTime) {
if (!textTime || textTime.trim().length === 0) return 0;
var timeParts = textTime.split(":");
var seconds = parseInt(timeParts[1]);
seconds += parseInt(timeParts[0]) * 60;
return seconds;
}
// get the summation of the times
function textTimesToSeconds(textTimes) {
var totalSeconds = 0;
// get total minutes
$.each(textTimes, function (index, textTime) {
totalSeconds += convertTextToSeconds(textTime);
});
return totalSeconds;
}
function secondsToTextTime(totalSeconds) {
// convert back to hh:mm
var hours = Math.floor(totalSeconds / 60 / 60);
var remainingTime = totalSeconds - hours * 60 * 60;
var minutes = Math.floor(remainingTime / 60);
remainingTime = remainingTime - minutes * 60;
var seconds = remainingTime;
return getTime(hours, minutes, seconds);
}
// get the time in the following format -> hh:mm:ss
function getTime(hours, minutes, seconds) {
var result = "";
// get the hours part
if (hours > 0) {
result += hours + ":";
result += timePad(minutes);
} else {
// we didn't have any hours
result += minutes;
}
// check for minutes
if (minutes > 0) {
result += ":" + timePad(seconds);
} else {
// we didn't have any minutes, but we should say 0
result += "0:" + timePad(seconds);
}
return result;
}
// time should be a length of 2, so prepend with 0
function timePad(timeSegment) {
var result = timeSegment + "";
while (result.length < 2) {
result = "0" + result;
}
return result;
}
})();