Skip to content

Instantly share code, notes, and snippets.

@adammyhre
Created December 24, 2025 11:45
Show Gist options
  • Select an option

  • Save adammyhre/e121ec8d8399eab0c62dcb05b941e53a to your computer and use it in GitHub Desktop.

Select an option

Save adammyhre/e121ec8d8399eab0c62dcb05b941e53a to your computer and use it in GitHub Desktop.
Udemy Progress Tooltip 2026 (Chrome Extension)
(async function () {
// ================= utilities =================
function secondsToHMS(seconds) {
seconds = Math.floor(seconds);
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${h}h ${m}m ${s}s`;
}
async function fetchJSON(url) {
const res = await fetch(url, { credentials: "include" });
if (!res.ok) return null;
return res.json();
}
function getCourseId() {
const el = document.querySelector('[data-module-id="course-taking"]');
if (!el) return null;
try {
return JSON.parse(el.dataset.moduleArgs).courseId;
} catch {
return null;
}
}
function getLectureIdFromUrl() {
const m = location.pathname.match(/\/learn\/lecture\/(\d+)/);
return m ? m[1] : null;
}
// ================= guard =================
const courseId = getCourseId();
if (!courseId) return;
// ================= tooltip =================
const tooltip = document.createElement("div");
tooltip.style.position = "fixed";
tooltip.style.zIndex = "999999";
tooltip.style.background = "#1c1d1f";
tooltip.style.color = "#ffffff";
tooltip.style.padding = "10px 12px";
tooltip.style.borderRadius = "6px";
tooltip.style.fontSize = "12px";
tooltip.style.lineHeight = "1.4";
tooltip.style.boxShadow = "0 6px 16px rgba(0,0,0,0.35)";
tooltip.style.display = "none";
tooltip.style.pointerEvents = "none";
tooltip.style.whiteSpace = "nowrap";
document.body.appendChild(tooltip);
// ================= stats refresh =================
let refreshInProgress = false;
async function refreshStats() {
if (refreshInProgress) return;
refreshInProgress = true;
try {
const [courseMeta, curriculum, progress] = await Promise.all([
fetchJSON(
`https://www.udemy.com/api-2.0/courses/${courseId}/?fields[course]=content_length_video`
),
fetchJSON(
`https://www.udemy.com/api-2.0/courses/${courseId}/subscriber-curriculum-items/?` +
`curriculum_types=lecture&fields[lecture]=id,asset&` +
`fields[asset]=asset_type,time_estimation&page_size=1000`
),
fetchJSON(
`https://www.udemy.com/api-2.0/users/me/subscribed-courses/${courseId}/progress/?` +
`fields[course]=completed_lecture_ids`
)
]);
if (!courseMeta || !curriculum || !progress) return;
const lectureDurations = new Map();
for (const item of curriculum.results) {
if (
item._class === "lecture" &&
item.asset?.asset_type === "Video" &&
typeof item.asset.time_estimation === "number"
) {
lectureDurations.set(item.id, item.asset.time_estimation);
}
}
const completedIds = new Set(progress.completed_lecture_ids || []);
let completedSeconds = 0;
for (const [id, sec] of lectureDurations) {
if (completedIds.has(id)) completedSeconds += sec;
}
const totalSeconds =
courseMeta.content_length_video ??
[...lectureDurations.values()].reduce((a, b) => a + b, 0);
const remainingSeconds = totalSeconds - completedSeconds;
const actualProgress =
Math.floor((completedSeconds / totalSeconds) * 100);
const udemyProgress =
Math.floor((completedIds.size / lectureDurations.size) * 100);
const secondsPerPercent = totalSeconds / 100;
const secondsPerPercent135x = secondsPerPercent / 1.35;
const remainingSeconds135x = remainingSeconds / 1.35;
const sessionsNeeded =
Math.ceil(remainingSeconds135x / (30 * 60));
tooltip.innerHTML = `
<div>Progress (actual): ${actualProgress}%</div>
<div>Progress (Udemy): ${udemyProgress}%</div>
<div>Completed: ${secondsToHMS(completedSeconds)}</div>
<div>Total: ${secondsToHMS(totalSeconds)}</div>
<div>Remaining: ${secondsToHMS(remainingSeconds)}</div>
<div>1% ≈ ${secondsToHMS(secondsPerPercent)}</div>
<hr style="border:0;border-top:1px solid #3e4143;margin:6px 0;">
<div>1% @ 1.35× ≈ ${secondsToHMS(secondsPerPercent135x)}</div>
<div>Sessions left (30 min @ 1.35×): ${sessionsNeeded}</div>
`;
} finally {
refreshInProgress = false;
}
}
// ================= hover =================
document.addEventListener("mouseover", e => {
const btn = e.target.closest?.('button[id^="popper-trigger--"]');
if (!btn) return;
const r = btn.getBoundingClientRect();
tooltip.style.left = `${r.left}px`;
tooltip.style.top = `${r.bottom + 6}px`;
tooltip.style.display = "block";
});
document.addEventListener("mouseout", e => {
if (e.target.closest?.('button[id^="popper-trigger--"]')) {
tooltip.style.display = "none";
}
});
// ================= URL polling =================
let lastLectureId = getLectureIdFromUrl();
setInterval(() => {
const currentLectureId = getLectureIdFromUrl();
if (!currentLectureId) return;
if (currentLectureId !== lastLectureId) {
lastLectureId = currentLectureId;
setTimeout(refreshStats, 1500);
}
}, 750);
// ================= initial load =================
refreshStats();
})();
{
"manifest_version": 3,
"name": "Udemy Progress Tooltip",
"version": "1.0",
"content_scripts": [
{
"matches": ["https://*.udemy.com/*"],
"js": ["content.js"],
"run_at": "document_idle"
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment