Gets time remaining for any modules in a Udemy course
// ==UserScript==
// @name Show Udemy Time Remaining
// @namespace Namespace (usually a URL)
// @version 1.0.0
// @description Gets time remaining for any modules in a Udemy course
// @author lundeen-bryan
// @match https://www.udemy.com/*
// @grant GM_registerMenuCommand
// @icon URL of an icon for the script
// @license License information (e.g., MIT, GPL)
// ==/UserScript==
/**
*
* Name..............: Show Udemy Time Remaining
* Description.......: This script enhances Udemy course pages by calculating and displaying the total remaining time for uncompleted modules.
* It automatically identifies unwatched course content and sums up the durations, providing a quick overview of the time
* required to complete the course. Additionally, it adds convenient buttons to the course interface for expanding all
* sections, collapsing all sections, and showing the total remaining time.
* Syntax............: The script operates automatically on Udemy course overview pages that contain '#overview' in the URL. It also provides
* buttons for manual control. (Currently needs user to click the button * in the Udemy menu)
* Parameters........: None.
* Return data type..: Void. The script directly modifies the webpage content without returning data.
* Links.............: n/a
* Author............: lundeen-bryan
* Related...........: This script is specific to Udemy courses and is designed to work with the current structure of Udemy's course content
* pages as of the script's last update.
* Example...........: To see the script in action, navigate to a Udemy course overview page (the URL should contain '#overview') and observe
* the added "Remaining Time" display and control buttons. Manual triggers are available through the Tampermonkey extension
* menu under "Activate Udemy Time Tracker."
*
*/
(function () {
"use strict";
function initializeScript() {
"use strict";
// Check if the URL fragment includes '#overview'
if (!location.hash.includes("overview")) {
console.log("Not on the overview page, script will not run.");
return; // Exit the script if not on the overview page
}
// Call main function
addScriptButton();
fetchAndDisplayCourseData();
}
function getMetaContentByName(name) {
const element = document.querySelector(`meta[name="${name}"]`);
return element && element.getAttribute("content");
}
function storeCourseMetadata(data) {
localStorage.setItem("courseMetadata", JSON.stringify(data));
}
function expandUnopenedSections() {
const unopenedSections = [];
document
.querySelectorAll(".section--section--yXfqc > span")
.forEach((section) => {
const isExpanded = section.getAttribute("data-checked") === "checked";
if (!isExpanded) {
const accordionTitle = section.parentNode.querySelector(
".ud-accordion-panel-heading"
);
if (accordionTitle) {
accordionTitle.click();
unopenedSections.push(section);
} else {
console.warn(
"Could not find the accordion panel heading for one of the sections. The website structure might have changed."
);
}
}
});
return unopenedSections;
}
function calculateTotalMinutes() {
let totalMinutes = 0;
document
.querySelectorAll(".item-link.ud-custom-focus-visible")
.forEach((item) => {
const isChecked = item.querySelector(".ud-real-toggle-input").checked;
if (!isChecked) {
const timer = item.querySelector(".ud-text-xs span");
if (timer) {
let time = parseInt(timer.innerText.replace("min", "").trim(), 10);
totalMinutes += isNaN(time) ? 0 : time;
}
}
});
return totalMinutes;
}
function convertDuration(totalMinutes) {
const hours = String(Math.trunc(totalMinutes / 60)).padStart(2, "0");
const minutes = String(totalMinutes % 60).padStart(2, "0");
return { hours, minutes };
}
function collapseSections(sections) {
sections.forEach((section) => {
const accordionTitle = section.parentNode.querySelector(
".ud-accordion-panel-heading"
);
if (accordionTitle) {
accordionTitle.click();
} else {
console.warn(
"Could not find the accordion panel heading for one of the sections while trying to collapse. The website structure might have changed."
);
}
});
}
function insertRemainingDuration(totalMinutes) {
const { hours, minutes } = convertDuration(totalMinutes);
const displayArea = document.querySelector(
'dd[data-purpose="course-additional-stats"]'
);
if (displayArea) {
const existingVideoDuration = displayArea.querySelector(
"div:nth-child(2)"
);
const existingRemainingTimeElement = displayArea.querySelector(
".remaining-time"
);
if (existingRemainingTimeElement) {
existingRemainingTimeElement.textContent = `Remaining Time: ${hours}:${minutes}`;
} else {
const remainingTimeElement = document.createElement("div");
remainingTimeElement.textContent = `Remaining Time: ${hours}:${minutes}`;
remainingTimeElement.className = "remaining-time";
remainingTimeElement.style.backgroundColor = "yellow"; // Set the background color to yellow
existingVideoDuration.insertAdjacentElement(
"afterend",
remainingTimeElement
);
}
}
}
function addScriptButton() {
const statsSection = document.querySelector(
'dd[data-purpose="course-additional-stats"]'
);
if (!statsSection) return;
const buttonContainer = document.createElement("div");
buttonContainer.style.marginTop = "10px";
// Button for Remaining Time
const remainingTimeButton = document.createElement("button");
remainingTimeButton.id = "courseMetadataButton";
remainingTimeButton.textContent = "Remaining Time";
remainingTimeButton.style.backgroundColor = "#007bff";
remainingTimeButton.style.color = "#fff";
remainingTimeButton.style.border = "none";
remainingTimeButton.style.padding = "10px";
remainingTimeButton.style.cursor = "pointer";
remainingTimeButton.style.display = "block"; // ensures each button takes up the full width of the container
remainingTimeButton.style.marginBottom = "5px"; // adds a little space between buttons
remainingTimeButton.addEventListener("click", () => {
fetchAndDisplayCourseData();
});
buttonContainer.appendChild(remainingTimeButton);
// Button for Expand All
const expandAllButton = document.createElement("button");
expandAllButton.textContent = "Expand All";
expandAllButton.style.backgroundColor = "#008000";
expandAllButton.style.color = "#fff";
expandAllButton.style.border = "none";
expandAllButton.style.padding = "10px";
expandAllButton.style.cursor = "pointer";
expandAllButton.style.display = "block"; // ensures each button takes up the full width of the container
expandAllButton.style.marginBottom = "5px"; // adds a little space between buttons
expandAllButton.addEventListener("click", () => {
expandAllSections();
});
buttonContainer.appendChild(expandAllButton);
// Button for Collapse All
const collapseAllButton = document.createElement("button");
collapseAllButton.textContent = "Collapse All";
collapseAllButton.style.backgroundColor = "#ff0000";
collapseAllButton.style.color = "#fff";
collapseAllButton.style.border = "none";
collapseAllButton.style.padding = "10px";
collapseAllButton.style.cursor = "pointer";
collapseAllButton.style.display = "block"; // ensures each button takes up the full width of the container
collapseAllButton.addEventListener("click", () => {
collapseAllSections();
});
buttonContainer.appendChild(collapseAllButton);
statsSection.parentNode.insertBefore(
buttonContainer,
statsSection.nextSibling
);
}
function expandAllSections() {
document
.querySelectorAll(".section--section--yXfqc > span")
.forEach((section) => {
const isExpanded = section.getAttribute("data-checked") === "checked";
if (!isExpanded) {
const accordionTitle = section.parentNode.querySelector(
".ud-accordion-panel-heading"
);
if (accordionTitle) {
accordionTitle.click();
} else {
console.warn(
"Could not find the accordion panel heading for one of the sections. The website structure might have changed."
);
}
}
});
}
function collapseAllSections() {
document
.querySelectorAll(".section--section--yXfqc > span")
.forEach((section) => {
const isExpanded = section.getAttribute("data-checked") === "checked";
if (isExpanded) {
const accordionTitle = section.parentNode.querySelector(
".ud-accordion-panel-heading"
);
if (accordionTitle) {
accordionTitle.click();
} else {
console.warn(
"Could not find the accordion panel heading for one of the sections while trying to collapse. The website structure might have changed."
);
}
}
});
}
function fetchAndDisplayCourseData() {
const title = getMetaContentByName("twitter:title");
const url = getMetaContentByName("twitter:url");
const description = getMetaContentByName("twitter:description");
const totalMinutes = calculateTotalMinutes();
const courseMetadata = {
courseTitle: title,
courseURL: url,
courseDescription: description,
remainingDuration: totalMinutes,
};
storeCourseMetadata(courseMetadata);
insertRemainingDuration(totalMinutes);
const sectionsToCollapse = expandUnopenedSections();
collapseSections(sectionsToCollapse);
}
// Automatically attempt to run the script on page load
initializeScript();
// Register a Tampermonkey menu command to manually trigger the script
GM_registerMenuCommand("Activate Udemy Time Tracker", initializeScript, "t");
})();