Extract and show total course lectures and durations.
// ==UserScript==
// @name Programming Advices Enhancer
// @namespace http://tampermonkey.net/
// @version 2025-01-08
// @description Extract and show total course lectures and durations.
// @author Muhannad Elbolaky
// @license MIT
// @match https://programmingadvices.com/p/*
// @match https://programmingadvices.com/courses/enrolled/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=programmingadvices.com
// @grant none
// ==/UserScript==
(function () {
'use strict';
/**
* Wait for specific elements to load in the DOM and invoke a callback when they are found.
* @param {string} selector - CSS selector for the target elements.
* @param {Function} callback - Function to execute once the elements are loaded.
*/
function waitForElements(selector, callback) {
const observer = new MutationObserver((mutations, obs) => {
const elements = document.querySelectorAll(selector);
if (elements.length > 0) {
obs.disconnect(); // Stop observing once the elements are found.
callback(elements);
}
});
observer.observe(document, {
childList: true,
subtree: true,
});
}
/**
* Calculate the total duration from a list of elements containing duration text.
* @param {NodeList} elements - A NodeList of elements containing the duration text in (HH:MM:SS) or (MM:SS) format.
* @returns {string} - Formatted total duration (e.g., "2.5 ساعة" or "150 دقيقة").
*/
function calculateTotalDuration(elements) {
let totalSeconds = 0;
elements.forEach((element) => {
const durationText = element.textContent.trim();
const match = durationText.match(/\((\d{1,2}):(\d{2})(?::(\d{2}))?\)/); // Match durations like (MM:SS) or (HH:MM:SS)
if (match) {
let hours = 0;
let minutes = parseInt(match[1], 10); // Default to MM
let seconds = parseInt(match[2], 10); // Always parse SS
if (match[3]) {
hours = minutes; // When there are three parts, MM becomes HH.
minutes = parseInt(match[2], 10);
seconds = parseInt(match[3], 10);
}
totalSeconds += hours * 3600 + minutes * 60 + seconds;
}
});
const totalMinutes = Math.floor(totalSeconds / 60);
const totalHours = totalMinutes / 60;
return totalHours >= 1
? `${totalHours.toFixed(1)} ساعة`
: `${totalMinutes} دقيقة`;
}
/**
* Count the number of lectures from the provided elements.
* @param {NodeList} elements - A NodeList of elements representing lectures.
* @returns {number} - The total number of lectures.
*/
function countLectures(elements) {
return elements.length;
}
// /p/*
function handlePLecturePage() {
waitForElements(".block__curriculum__section__list__item__lecture-duration", (durationElements) => {
const formattedDuration = calculateTotalDuration(durationElements);
const lectureCount = countLectures(durationElements);
const titleElement = document.querySelector(".block__curriculum__title");
if (titleElement) {
const infoElement = document.createElement("div");
infoElement.innerHTML = `مدة الكورس: ${formattedDuration} <br> عدد المحاضرات: ${lectureCount}`;
infoElement.style.marginTop = "0.6rem";
titleElement.prepend(infoElement);
}
});
}
// /courses/enrolled/*
function handleEnrolledCoursesPage() {
waitForElements("p.duration, h1.heading", () => {
const durationElements = document.querySelectorAll("p.duration");
const headingElement = document.querySelector("h1.heading");
if (durationElements.length > 0 && headingElement) {
const formattedDuration = calculateTotalDuration(durationElements);
const lectureCount = countLectures(durationElements);
const newHeadingElement = document.createElement("h3");
newHeadingElement.innerHTML = `مدة الكورس: ${formattedDuration} <br> عدد المحاضرات: ${lectureCount}`;
newHeadingElement.style.textAlign = "center";
newHeadingElement.style.fontSize = "1.75rem";
newHeadingElement.style.color = "#333";
newHeadingElement.style.border = "2px solid";
newHeadingElement.style.padding = "15px";
newHeadingElement.style.margin = "20px auto";
newHeadingElement.style.borderRadius = "10px";
newHeadingElement.style.width = "80%";
newHeadingElement.style.boxShadow = "0 4px 6px rgba(0, 0, 0, 0.1)";
headingElement.insertAdjacentElement('afterend', newHeadingElement);
}
});
}
// Check the current page URL and apply the appropriate enhancements.
if (window.location.pathname.startsWith("/p/")) {
handlePLecturePage();
} else if (window.location.pathname.startsWith("/courses/enrolled/")) {
handleEnrolledCoursesPage();
}
})();