Created
December 24, 2025 11:45
-
-
Save adammyhre/e121ec8d8399eab0c62dcb05b941e53a to your computer and use it in GitHub Desktop.
Udemy Progress Tooltip 2026 (Chrome Extension)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (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(); | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "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