Udemy Course Progress JS Bookmarklet

The Udemy video course UI doesn’t give you a lot of information about your progress through a course. I find it helpful to be able to see progress and the amount of time remaining in a course, so I made this JavaScript bookmarklet to display some figures about progress through a Udemy course.

You need to be on the Udemy video player page when you trigger the bookmarklet. It will display progress stats in a browser alert.

javascript:(async () => {
const panelButtonsToClose = [];
document.querySelectorAll("button[class*='accordion-panel-module--panel-toggler'][aria-expanded='false']").forEach(button => {
  button.click();
  panelButtonsToClose.push(button);
});

let playbackRate = 1.0;
const playBackspan = document.querySelector("span[class^='ud-focus-visible-target playback-rate--trigger-text']");
if (playBackspan && playBackspan.innerText) {
  playbackRate = parseFloat(playBackspan.innerText.replace(/[^0-9\.]/g, ""));
} 

let completedMinutes = 0;
let uncompleteMinutes = 0;
document.querySelectorAll("li[class^='curriculum-item-link--curriculum-item']").forEach(item => {
  const itemTitleMatch = item.innerText.match(/\n[0-9]+\. .+\n/);
  if (itemTitleMatch) {
    console.log(itemTitleMatch[0].trim());
  }
  
  let itemMinutes = 0;
  const itemMinutesMatch = item.innerText.match(/([0-9]{1,2})min/);
  if (itemMinutesMatch && itemMinutesMatch.length >= 2) {
    itemMinutes = Number(itemMinutesMatch[1]);
  }

  let itemHours = 0;
  const itemHoursMatch = item.innerText.match(/([0-9]{1})hr/);
  if (itemHoursMatch && itemHoursMatch.length >= 2) {
    itemHours = Number(itemHoursMatch[1]);
  }

  const itemTotalMinutes = (itemHours * 60) + itemMinutes;

  if (item.innerText.includes("Lecture completed")) {
    completedMinutes += itemTotalMinutes;
  } else {
    uncompleteMinutes += itemTotalMinutes;
  }
});

const minutesToHoursAndMinutes = minutes => {
  minutes = Math.ceil(minutes);
  const wholeHours = Math.floor(minutes / 60);
  const remainderMinutes = minutes % 60;
  if (wholeHours < 1) {
    return `${remainderMinutes} min`;
  }
  if (remainderMinutes === 0) {
    return `${wholeHours} hr`;
  }
  return `${wholeHours} hr ${remainderMinutes} min`;
};

const courseTotalMinutes = completedMinutes + uncompleteMinutes;

const onePercentCourseTime = minutesToHoursAndMinutes(courseTotalMinutes*0.01);

const completion = Math.round((completedMinutes/(courseTotalMinutes))*100);
const summary = `
${minutesToHoursAndMinutes(courseTotalMinutes)} course total.\n

${completion}% complete.\n

${minutesToHoursAndMinutes(completedMinutes)} completed.\n
${minutesToHoursAndMinutes(uncompleteMinutes)} to go.\n

${playbackRate} playback rate.\n

1% = ${minutesToHoursAndMinutes(courseTotalMinutes*0.01)} course time, ${minutesToHoursAndMinutes((courseTotalMinutes*0.01)/playbackRate)} real time.\n

${[7,5,4,3,2].map(days => `With ${days} days, need ${minutesToHoursAndMinutes((uncompleteMinutes/playbackRate)/days)} real time / day.`).join("\n")}
`;
console.log(summary);
panelButtonsToClose.forEach(button => button.click());
await window.navigator.clipboard.writeText(summary);
window.alert(summary);
})();

Tech mentioned