Skip to content

Commit

Permalink
v1.14.0
Browse files Browse the repository at this point in the history
  • Loading branch information
heliomarpm committed Dec 22, 2024
1 parent 063bc3d commit ed4af8d
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 14 deletions.
8 changes: 8 additions & 0 deletions CHANGE_LOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change Log

## Version [1.14.0](https://github.com/heliomarpm/udemy-downloader-gui/compare/v1.13.4...v1.14.0)
##### Dec, 21 2024
![](https://img.shields.io/github/downloads/heliomarpm/udemy-downloader-gui/v1.14.0/total)

### features
- Single search for courses purchased individually or subscribed to via subscription
- Download list m3u file

## Version [1.13.4](https://github.com/heliomarpm/udemy-downloader-gui/compare/v1.13.3...v1.13.4)
##### Dec, 7 2024
![](https://img.shields.io/github/downloads/heliomarpm/udemy-downloader-gui/v1.13.4/total)
Expand Down
94 changes: 85 additions & 9 deletions app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ $(".ui.dashboard .content").on("click", ".download-success, .course-encrypted",
$(this).parents(".course").find(".download-status").show();
});

$(".ui.dashboard .content").on("click", ".save_m3u.button", function (e) {
e.stopImmediatePropagation();
saveM3u($(this).parents(".course"));
});
$(".ui.dashboard .content").on("click", ".download.button, .download-error", function (e) {
e.stopImmediatePropagation();
prepareDownloading($(this).parents(".course"));
Expand Down Expand Up @@ -301,7 +305,7 @@ async function checkLogin(alertExpired = true) {
ui.busyLogin(false);
ui.showDashboard();

Settings.subscriber = utils.toBoolean(userContext.header.user.enableLabsInPersonalPlan);
Settings.subscriber = utils.toBoolean(userContext.header.user.enableLabsInPersonalPlan) || utils.toBoolean(userContext.header.user.consumer_subscription_active);
fetchCourses(Settings.subscriber).then(() => {
console.log("fetchCourses done");
});
Expand Down Expand Up @@ -553,9 +557,10 @@ function renderCourses(response, isResearch = false) {
$coursesItems.append(courseElements);

if (response.next) {
const dataUrl = Array.isArray(response.next) ? response.next : [response.next];
// added loadMore Button
$coursesSection.append(
`<button class="ui basic blue fluid load-more button disposable" data-url=${response.next}>
`<button class="ui basic blue fluid load-more button disposable" data-url=${JSON.stringify(dataUrl)}>
${translate("Load More")}
</button>`
);
Expand Down Expand Up @@ -707,6 +712,7 @@ async function fetchCourseContent(courseId, courseName, courseUrl) {
} else {

switch ( (lecture.quality || "").toLowerCase()) {
case "":
case "auto":
case "highest":
lecture.quality = streams.maxQuality;
Expand All @@ -715,10 +721,10 @@ async function fetchCourseContent(courseId, courseName, courseUrl) {
lecture.quality = streams.minQuality;
break;
default:
lecture.quality = utils.isNumber(lecture.quality) ? lecture.quality : lecture.quality.slice(0, -1);
lecture.quality = utils.isNumber(lecture.quality) ? lecture.quality : lecture.quality.slice(0, -1);
}

if (!streams.sources[lecture.quality]) {
if (lecture.quality && !streams.sources[lecture.quality]) {
if (utils.isNumber(lecture.quality) && streams.maxQuality != "auto") {
const source = utils.getClosestValue(streams.sources, lecture.quality);
lecture.quality = source?.key || streams.maxQuality;
Expand Down Expand Up @@ -801,18 +807,26 @@ async function fetchCourses(isSubscriber) {
function loadMore(loadMoreButton) {
const $button = $(loadMoreButton);
const $courses = $button.prev(".courses.items");
const url = $button.data("url");
const url = [...$button.data("url")];

ui.busyLoadCourses(true);
udemyService
.fetchLoadMore(url)
.fetchLoadMore(url[0])
.then((resp) => {
$courses.append(...resp.results.map((course) => createCourseElement(course, false)));
if (!resp.next) {
$button.remove();
if (url.length > 1) {
$button.data("url", [url[1]]);
} else {
$button.remove();
}
} else {
$button.data("url", resp.next);
}
if (url.length > 1) {
$button.data("url", [resp.next, url[1]]);
}else {
$button.data("url", [resp.next]);
}
}
})
.catch((e) => {
const statusCode = (e.response?.status || 0).toString() + (e.code ? ` :${e.code}` : "");
Expand Down Expand Up @@ -966,6 +980,68 @@ function removeCurseDownloads(courseId) {
});
}

async function saveM3u($course) {
ui.prepareDownloading($course);

const courseId = $course.attr("course-id");
const courseName = $course.find(".coursename").text();
const courseUrl = `https://${Settings.subDomain}.udemy.com${$course.attr("course-url")}`;

console.clear();

let courseData = null;
try {
courseData = await fetchCourseContent(courseId, courseName, courseUrl);
if (!courseData) {
// ui.showProgress($course, false);
return;
}

console.log(courseData);
dialog
.showSaveDialog({
title: "Save M3U",
defaultPath: `${courseName}.m3u`,
filters: [{ name: "M3U File (*.m3u)", fileExtension: ["m3u"] }],
})
.then((result) => {
if (!result.canceled) {
let filePath = result.filePath;
if (!filePath.endsWith(".m3u")) filePath += ".m3u";

let content = "#EXTM3U";
let index = 0;
courseData.chapters.forEach((chapter) => {
chapter.lectures.forEach((lecture, lec_index) => {
index++;
content += `\n#EXTINF:-1,${lec_index+1}. ${lecture.name}\n${lecture.src}`;

if (lecture.attachments && lecture.attachments.length > 0)
lecture.attachments.forEach((attachment, attach_index) => {
content += `\n#EXTINF:-1,${lec_index+1}.${attach_index+1} ${attachment.name}\n${attachment.src}`;
})
})
});

fs.writeFile(filePath, content, (error) => {
if (error) {
appendLog("saveM3u_Error", error);
return;
}
console.log("File successfully create!");
});
}
});

} catch (error) {
handleApiError(error, "ESAVE_M3U", null, false);
ui.busyOff();
$course.find(".prepare-downloading").hide();
} finally {
ui.showProgress($course, false);
}
}

async function prepareDownloading($course, subtitle) {
ui.prepareDownloading($course);
// ui.showProgress($course, true);
Expand Down
42 changes: 40 additions & 2 deletions app/core/services/udemy.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,26 @@ class UdemyService {
pageSize = Math.max(pageSize, 10);

const param = `page=1&ordering=title&fields[user]=job_title&page_size=${pageSize}&search=${keyword}`;
const url = !isSubscriber ? `${this.#URL_COURSES}?${param}` : `${this.#URL_COURSES_ENROLL}?${param}`;
// const url = !isSubscriber ? `${this.#URL_COURSES}?${param}` : `${this.#URL_COURSES_ENROLL}?${param}`;
const url = `${this.#URL_COURSES}?${param}`;
const urlEnroll = `${this.#URL_COURSES_ENROLL}?${param}`;

if (isSubscriber) {
const [courses, enrolledCourses] = await Promise.all([
this.#fetchEndpoint(url, "GET", httpTimeout),
this.#fetchEndpoint(urlEnroll, "GET", httpTimeout)
]);

const next = [courses.next, enrolledCourses.next].filter((n) => n !== null);
const previous = [courses.previous, enrolledCourses.previous].filter((p) => p !== null);

return {
count: courses.count + enrolledCourses.count,
next: next.length > 0 ? next : null,
previous: previous.length > 0 ? previous : null,
results: [...courses.results, ...enrolledCourses.results]
}
}

return await this.#fetchEndpoint(url, "GET", httpTimeout);
}
Expand All @@ -256,7 +275,26 @@ class UdemyService {
pageSize = Math.max(pageSize, 10);

const param = `page_size=${pageSize}&ordering=-last_accessed`;
const url = !isSubscriber ? `${this.#URL_COURSES}?${param}` : `${this.#URL_COURSES_ENROLL}?${param}`;
// const url = !isSubscriber ? `${this.#URL_COURSES}?${param}` : `${this.#URL_COURSES_ENROLL}?${param}`;
const url = `${this.#URL_COURSES}?${param}`;
const urlEnroll = `${this.#URL_COURSES_ENROLL}?${param}`;

if (isSubscriber) {
const [courses, enrolledCourses] = await Promise.all([
this.#fetchEndpoint(url, "GET", httpTimeout),
this.#fetchEndpoint(urlEnroll, "GET", httpTimeout)
]);

const next = [courses.next, enrolledCourses.next].filter((n) => n !== null);
const previous = [courses.previous, enrolledCourses.previous].filter((p) => p !== null);

return {
count: courses.count + enrolledCourses.count,
next: next.length > 0 ? next : null,
previous: previous.length > 0 ? previous : null,
results: [...courses.results, ...enrolledCourses.results]
}
}

return await this.#fetchEndpoint(url, "GET", httpTimeout);
}
Expand Down
2 changes: 2 additions & 0 deletions app/helpers/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ const ui = {
get actionCardTemplate() {
return `
<div class="ui tiny icon action buttons">
<button class="ui basic blue save_m3u button"><i class="save outline icon"></i></button>
<div style="height: 1px; width: 5px;"></div>
<button class="ui basic blue download button"><i class="download icon"></i></button>
<button class="ui basic red disabled pause button"><i class="pause icon"></i></button>
<button class="ui basic green disabled resume button"><i class="play icon"></i></button>
Expand Down
2 changes: 2 additions & 0 deletions app/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ const utils = {
// human-readable page numbers usually start with 1, so we reduce 1 in the first argument
return array.slice((page_number - 1) * page_size, page_number * page_size);
},

sleep: (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
},

newError(name, message = "") {
const error = new Error();
error.name = name;
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "udeler",
"productName": "Udeler",
"version": "1.13.4",
"version": "1.14.0",
"description": "A cross platform (Windows, Mac, Linux) desktop application for downloading Udemy Courses.",
"main": "main.js",
"type": "commonjs",
Expand Down

0 comments on commit ed4af8d

Please sign in to comment.