Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save cuylerstuwe/eea76f5f6c9b1311c9776180780d506e to your computer and use it in GitHub Desktop.

Select an option

Save cuylerstuwe/eea76f5f6c9b1311c9776180780d506e to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Udemy "List All Courses" And Course Planning Checkmarks
// @namespace salembeats
// @version 1.31
// @description .
// @author Cuyler Stuwe (salembeats)
// @grant GM_setValue
// @grant GM_getValue
// @require file:///C:\private-keys-for-public-tampermonkey-scripts\udemy-list-all-courses.js
// @include https://www.udemy.com/home/my-courses/learning/*
// ==/UserScript==
const globals = {
url: `https://www.udemy.com/api-2.0/users/me/subscribed-courses?fields%5Bcourse%5D=@min,visible_instructors,image_240x135,image_480x270,favorite_time,archive_time,completion_ratio,last_accessed_time,enrollment_time,is_practice_test_course,features,num_collections,published_title&fields%5Buser%5D=@min,job_title&ordering=-access_time,-enrolled&page=1&page_size=100`,
bearerToken: udemyPrivate.bearerToken,
courseStatuses: JSON.parse(GM_getValue("courseStatuses") || "{}")
};
function storeCourseStatuses() {
GM_setValue("courseStatuses", JSON.stringify(globals.courseStatuses));
}
function upsertCourseRecord(course) {
const existingCourseRecord = globals.courseStatuses[course.title] || {};
globals.courseStatuses[course.title] = {
...existingCourseRecord,
title: course.title,
author: course.visible_instructors[0].display_name,
completion: course.completion_ratio
};
}
function upsertCourses(courses) {
for(const course of courses) {
upsertCourseRecord(course);
}
}
function isScheduled(course) {
return !!globals.courseStatuses[course.title].isScheduled;
}
function isFinished(course) {
return !!globals.courseStatuses[course.title].isFinished || course.completion_ratio === 100;
}
function isPlanned(course) {
return !!globals.courseStatuses[course.title].isPlanned;
}
function courseTable(courses) {
return `<table>
<tr class='tampermonkey-course-table-header'>
<td>Title</td>
<td>Author</td>
<td>% Complete</td>
<td>Finished?</td>
<td>Scheduled?</td>
<td>Planned?</td>
</tr>
${
courses.map(course => {
const courseHTML = `
<tr>
<td>${course.title}</td>
<td>${course.visible_instructors[0].display_name}</td>
<td>${course.completion_ratio}</td>
<td><input type="checkbox" class="is-finished" ${isFinished(course) ? "checked" : ""}></td>
<td><input type="checkbox" class="is-scheduled" ${isScheduled(course) ? "checked" : ""}></td>
<td><input type="checkbox" class="is-planned" ${isPlanned(course) ? "checked" : ""}></td>
</tr>
`;
return courseHTML;
}).join("")
}
</table>
<style>
.tampermonkey-course-table-header {
font-weight: 700;
}
tr:nth-child(even) {
background: #DDDDDD;
}
</style>`;
}
function scrapeCourseTitleFromParentRow(checkbox) {
const checkboxParentRow = checkbox.parentElement.parentElement;
return checkboxParentRow.querySelector("td").innerText.trim();
}
function markCourseByCheckbox(checkbox) {
const courseTitle = scrapeCourseTitleFromParentRow(checkbox);
const classToPropertyMap = {
"is-finished": "isFinished",
"is-scheduled": "isScheduled",
"is-planned": "isPlanned"
};
const propertyToUpdate = classToPropertyMap[checkbox.className];
globals.courseStatuses[courseTitle][propertyToUpdate] = checkbox.checked;
}
async function main() {
const fetchResults = await fetch(globals.url, {
method: "GET",
headers: {
"accept": "application/json",
"authorization": `Bearer ${globals.bearerToken}`
}
});
const apiCourseResults = (await fetchResults.json()).results;
const sortedApiCourseResults = apiCourseResults.sort((a, b) => {
return (
({
"true": 1,
"false": -1
})[(a.completion_ratio > b.completion_ratio).toString()]
);
});
upsertCourses(sortedApiCourseResults);
storeCourseStatuses();
document.querySelector(".next.btn.btn-default").insertAdjacentHTML("afterend", "<div><button id='createCoursesPopup' class='btn btn-default'><span class='udi'>List All Courses</span></button></div>");
const createCoursesPopup = document.getElementById('createCoursesPopup');
createCoursesPopup.addEventListener("click", e => {
const newWindow = unsafeWindow.open(`about:blank`, null, "left=0, top=0, width=800, height=500, scrollbars=1");
const sortedTable = courseTable(sortedApiCourseResults);
newWindow.document.write(sortedTable);
newWindow.document.body.addEventListener("click", e => {
if(e.target.tagName === "INPUT" &&
e.target.getAttribute("type") === "checkbox") {
markCourseByCheckbox(e.target);
storeCourseStatuses();
}
});
});
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment