Upload folder using huggingface_hub

#1
by imseldrith - opened
.github/ISSUE_TEMPLATE/bug-form.yml ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Bug Report
2
+ description: Please let us know if you're facing any problems, errors, or unexpected behavior.
3
+ title: "[Bug]: "
4
+ labels: ["Type: Bug", "Status: TBC"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thank you for taking the time to fill out this bug report!
10
+
11
+ - type: textarea
12
+ id: what-happened
13
+ attributes:
14
+ label: What happened?
15
+ description: |
16
+ Please describe the issue you encountered and what you expected to happen.
17
+ You can also attach images or log files by highlighting this area and dragging the files in.
18
+ placeholder: Describe your experience.
19
+ validations:
20
+ required: true
21
+
22
+ - type: dropdown
23
+ id: enroller
24
+ attributes:
25
+ label: Enroller
26
+ description: Which Enroller caused this issue?
27
+ options:
28
+ - GUI
29
+ - CLI
30
+ validations:
31
+ required: true
32
+
33
+ - type: dropdown
34
+ id: os
35
+ attributes:
36
+ label: OS
37
+ options:
38
+ - Windows
39
+ - Linux-Distro
40
+ - Mac-OS
41
+ validations:
42
+ required: true
43
+
44
+ - type: textarea
45
+ id: logs
46
+ attributes:
47
+ label: Relevant log output
48
+ description: Please copy and paste any relevant log output. It will be automatically formatted as code.
49
+ render: shell
50
+
51
+ - type: checkboxes
52
+ id: terms
53
+ attributes:
54
+ label: Terms
55
+ description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/techtanic/Discounted-Udemy-Course-Enroller/blob/master/CODE_OF_CONDUCT.md)
56
+ options:
57
+ - label: I am using the latest available version.
58
+ required: true
.github/ISSUE_TEMPLATE/config.yml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: 💬 Discord
4
+ url: https://discord.gg/wFsfhJh4Rh
5
+ about: |
6
+ For any other help or just discussion.
7
+
8
+ - name: Telegram
9
+ url: https://t.me/techtanic
10
+ about: |
11
+ For any other help or just discussion.
.github/ISSUE_TEMPLATE/feature_request.yml ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Feature Request
2
+ description: Suggest an idea for this project
3
+ title: "[Feature]: "
4
+ labels: ["Type: Enhancement"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Thanks for taking the time to suggest a feature for this project!
10
+
11
+ - type: textarea
12
+ id: problem
13
+ attributes:
14
+ label: Is your feature request related to a problem? Please describe.
15
+ description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
16
+ placeholder: Describe the problem.
17
+ validations:
18
+ required: false
19
+
20
+ - type: textarea
21
+ id: solution
22
+ attributes:
23
+ label: Describe the solution you'd like
24
+ description: A clear and concise description of what you want to happen.
25
+ placeholder: Describe the solution.
26
+ validations:
27
+ required: true
28
+
29
+ - type: textarea
30
+ id: alternatives
31
+ attributes:
32
+ label: Describe alternatives you've considered
33
+ description: A clear and concise description of any alternative solutions or features you've considered.
34
+ placeholder: Describe the alternatives.
35
+ validations:
36
+ required: false
37
+
38
+ - type: textarea
39
+ id: additional-context
40
+ attributes:
41
+ label: Additional context
42
+ description: Add any other context or screenshots about the feature request here.
43
+ placeholder: Add any other context.
44
+ validations:
45
+ required: false
.github/workflows/build.yml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - master
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: windows-latest
12
+ strategy:
13
+ matrix:
14
+ include:
15
+ - name: "DUCE-GUI-windows"
16
+ mode: "-w"
17
+ script: "gui"
18
+ - name: "DUCE-CLI-windows"
19
+ mode: "-c"
20
+ script: "cli"
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: "3.11"
28
+ cache: "pip"
29
+
30
+ - name: Install dependencies and PyInstaller
31
+ run: pip install -r requirements.txt pyinstaller -U
32
+
33
+ - name: Build ${{ matrix.name }}
34
+ run: >
35
+ pyinstaller -y -F ${{ matrix.mode }} -i "extra/DUCE-LOGO.ico" --clean --name "${{ matrix.name }}"
36
+ --add-data "base.py;."
37
+ --add-data "colors.py;."
38
+ --add-data "default-duce-${{ matrix.script }}-settings.json;."
39
+ --add-data "README.md;."
40
+ --add-data "LICENSE;."
41
+ "${{ matrix.script }}.py"
42
+
43
+ - name: Upload ${{ matrix.name }}.exe
44
+ uses: actions/upload-artifact@v4
45
+ with:
46
+ name: ${{ matrix.name }}.exe
47
+ path: ./dist/${{ matrix.name }}.exe
.github/workflows/del-old.yml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: 'Delete old releases'
2
+ on:
3
+ workflow_dispatch:
4
+
5
+ jobs:
6
+ stale:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - run: ls
10
+ #- uses: dev-drprasad/[email protected]
11
+ # with:
12
+ # keep_latest: 1
13
+ # delete_tags: true
14
+ # env:
15
+ # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .vscode/
2
+ /test
3
+ /Courses
4
+ /debug
5
+ /build
6
+ /dist
7
+ /output
8
+ *.pyc
9
+ tempCodeRunnerFile.py
10
+ DUCE-GUI-windows.spec
11
+ duce.py
12
+ gui-test.py
13
+ duce-gui-settings.json
14
+ duce-cli-settings.json
CHANGELOG.md ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Changelog
2
+
3
+
4
+ ## v2.3.1
5
+
6
+ - Fixed missing color in print
7
+ - Improve update checker
8
+ - Improved Already enrolled course detection
9
+
10
+
11
+ ## v2.3
12
+
13
+ - Removed getting settings from github is file not found. Default settings will be included in exe.
14
+ - Changed Manual Login API
15
+ - Fixed `TutorialBar`
16
+ - Fixed Error for Courses that are no longer accepting new enrollments
17
+ - Refactored some code
18
+
19
+ ## v2.2
20
+
21
+ - Fixed `CourseVania`
22
+ - Refactored code
23
+ - Added Course last Updated filter
24
+
25
+ ## v2.1
26
+
27
+ - Fixed Scrappers
28
+ - Optimized some code
29
+ - Fixed multiple issues
30
+ - Hopefully all known errors are fixed
31
+ - CLI now supports Browser Cookie Login (Can be changed in settings)
32
+
33
+ ## v2.0
34
+
35
+ - Fix Retrying error
36
+
37
+ ## v1.9
38
+
39
+ - Potential fix for Manual Login
40
+ - Fixed error on encountering free course with coupons
41
+ - Fixed IDownloadCoupons
42
+ - Added support for Urdu and Nepali language
43
+
44
+ ## v1.8
45
+
46
+ - Refactored code
47
+ - Fixed Course not enrolling
48
+ - Fixed real discount
49
+ - Fixed enext
50
+ - Fixed coursevania
51
+ - Fixed Manual Login
52
+ - Fixed scrapers
53
+ - Fixed a lot of things
54
+ - Removed Colab Version because Login not possible
55
+
56
+ ## v1.7
57
+
58
+ - Fixed Auto-Login
59
+
60
+ ## v1.6
61
+
62
+ - Fixed Login issues
63
+ - Fixed `CourseVania`
64
+ - Fixed Enrolling
65
+ - Some minor fixes
66
+
67
+ ## v1.5
68
+
69
+ - Fixed login problem.
70
+ - Fixed my ego.
71
+
72
+ ## v1.4
73
+
74
+ - Added `e-next.in`
75
+ - Added Discounted only filter
76
+ - Hopeful fix for `Amount saved` not showing
77
+ - Hopeful fix for Manual login
78
+ - Fixed not saving courses to file on unexpected exit.
79
+ - Simplified some logic
80
+
81
+ ## v1.3
82
+
83
+ - Added Save to txt file option in CLI and GUI
84
+ - Fixed some logic
85
+
86
+ ## v1.2
87
+
88
+ - Fixed RealDiscount and CourseVania
89
+
90
+ ## v1.1
91
+
92
+ - Fixed RealDiscount and CourseVania
93
+ - Added Russian Language filter
94
+
95
+ ## v1.0
96
+
97
+ - Fresh start
CODE_OF_CONDUCT.md ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the
26
+ overall community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or
31
+ advances of any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email
35
+ address, without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official e-mail address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series
86
+ of actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or
93
+ permanent ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within
113
+ the community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.0, available at
119
+ https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120
+
121
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct
122
+ enforcement ladder](https://github.com/mozilla/diversity).
123
+
124
+ [homepage]: https://www.contributor-covenant.org
125
+
126
+ For answers to common questions about this code of conduct, see the FAQ at
127
+ https://www.contributor-covenant.org/faq. Translations are available at
128
+ https://www.contributor-covenant.org/translations.
CONTRIBUTING.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to the project
2
+
3
+ I am happy to receive issues describing bug reports and feature requests! If your bug report relates to a security vulnerability, please do not file a public issue, and please instead reach out to me. I do not accept (and do not wish to receive) contributions of user-created or third-party code, including patches, pull requests, or code snippets incorporated into submitted issues. Please do not send me any such code!
4
+
5
+ If you have a feature request, please describe the feature you would like to see, and why you think it would be useful. I am always interested in hearing about new ideas for the project.
6
+
7
+ If you have a bug report, please describe the issue you are experiencing, and provide as much detail as possible. If you can provide a minimal example that reproduces the issue, that is even better! I will do my best to address the issue as quickly as possible.
8
+
9
+ Thank you for your interest in contributing to the project! I appreciate your help in making the project better for everyone.
10
+
11
+
12
+
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 TECHTANIC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
base.py ADDED
@@ -0,0 +1,1065 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import re
4
+ import sys
5
+ import threading
6
+ import time
7
+ import traceback
8
+ from datetime import datetime, timezone
9
+ from decimal import Decimal
10
+ from urllib.parse import parse_qs, unquote, urlparse, urlsplit, urlunparse
11
+
12
+ import cloudscraper
13
+ import requests
14
+ import rookiepy
15
+ from bs4 import BeautifulSoup as bs
16
+
17
+ from colors import fb, fc, fg, flb, flg, fm, fr, fy
18
+
19
+ VERSION = "v2.3.1"
20
+
21
+ scraper_dict: dict = {
22
+ "Udemy Freebies": "uf",
23
+ "Tutorial Bar": "tb",
24
+ "Real Discount": "rd",
25
+ "Course Vania": "cv",
26
+ "IDownloadCoupons": "idc",
27
+ "E-next": "en",
28
+ "Discudemy": "du",
29
+ }
30
+
31
+ LINKS = {
32
+ "github": "https://github.com/techtanic/Discounted-Udemy-Course-Enroller",
33
+ "support": "https://techtanic.github.io/duce/support",
34
+ "discord": "https://discord.gg/wFsfhJh4Rh",
35
+ }
36
+
37
+
38
+ class LoginException(Exception):
39
+ """Login Error
40
+
41
+ Args:
42
+ Exception (str): Exception Reason
43
+ """
44
+
45
+ pass
46
+
47
+
48
+ class RaisingThread(threading.Thread):
49
+ def run(self):
50
+ self._exc = None
51
+ try:
52
+ super().run()
53
+ except Exception as e:
54
+ self._exc = e
55
+
56
+ def join(self, timeout=None):
57
+ super().join(timeout=timeout)
58
+ if self._exc:
59
+ raise self._exc
60
+
61
+
62
+ def resource_path(relative_path):
63
+ if hasattr(sys, "_MEIPASS"):
64
+ return os.path.join(sys._MEIPASS, relative_path)
65
+ return os.path.join(os.path.abspath("."), relative_path)
66
+
67
+
68
+ class Scraper:
69
+ """
70
+ Scrapers: RD,TB, CV, IDC, EN, DU, UF
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ site_to_scrape: list = list(scraper_dict.keys()),
76
+ debug: bool = False,
77
+ ):
78
+ self.sites = site_to_scrape
79
+ self.debug = debug
80
+ for site in self.sites:
81
+ code_name = scraper_dict[site]
82
+ setattr(self, f"{code_name}_length", 0)
83
+ setattr(self, f"{code_name}_data", [])
84
+ setattr(self, f"{code_name}_done", False)
85
+ setattr(self, f"{code_name}_progress", 0)
86
+ setattr(self, f"{code_name}_error", "")
87
+
88
+ def get_scraped_courses(self, target: object) -> list:
89
+ threads = []
90
+ scraped_data = {}
91
+ for site in self.sites:
92
+ t = threading.Thread(
93
+ target=target,
94
+ args=(site,),
95
+ daemon=True,
96
+ )
97
+ t.start()
98
+ threads.append(t)
99
+ time.sleep(0.2)
100
+ for t in threads:
101
+ t.join()
102
+ for site in self.sites:
103
+ scraped_data[site] = getattr(self, f"{scraper_dict[site]}_data")
104
+ return scraped_data
105
+
106
+ def append_to_list(self, target: list, title: str, link: str):
107
+ target.append((title, link))
108
+
109
+ def fetch_page_content(self, url: str, headers: dict = None) -> bytes:
110
+ return requests.get(url, headers=headers).content
111
+
112
+ def parse_html(self, content: str):
113
+ return bs(content, "html5lib")
114
+
115
+ def handle_exception(self, site_code: str):
116
+ setattr(self, f"{site_code}_error", traceback.format_exc())
117
+ setattr(self, f"{site_code}_length", -1)
118
+ setattr(self, f"{site_code}_done", True)
119
+ if self.debug:
120
+ print(getattr(self, f"{site_code}_error"))
121
+
122
+ def cleanup_link(self, link: str) -> str:
123
+ parsed_url = urlparse(link)
124
+
125
+ if parsed_url.netloc == "www.udemy.com":
126
+ return link
127
+
128
+ if parsed_url.netloc == "click.linksynergy.com":
129
+ query_params = parse_qs(parsed_url.query)
130
+
131
+ if "RD_PARM1" in query_params:
132
+ return unquote(query_params["RD_PARM1"][0])
133
+ elif "murl" in query_params:
134
+ return unquote(query_params["murl"][0])
135
+ else:
136
+ return ""
137
+ raise ValueError(f"Unknown link format: {link}")
138
+
139
+ def du(self):
140
+ try:
141
+ all_items = []
142
+ head = {
143
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84",
144
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
145
+ }
146
+
147
+ for page in range(1, 4):
148
+ content = self.fetch_page_content(
149
+ f"https://www.discudemy.com/all/{page}", headers=head
150
+ )
151
+ soup = self.parse_html(content)
152
+ page_items = soup.find_all("a", {"class": "card-header"})
153
+ all_items.extend(page_items)
154
+ self.du_length = len(all_items)
155
+ if self.debug:
156
+ print("Length:", self.du_length)
157
+ for index, item in enumerate(all_items):
158
+ self.du_progress = index
159
+ title = item.string
160
+ url = item["href"].split("/")[-1]
161
+ content = self.fetch_page_content(
162
+ f"https://www.discudemy.com/go/{url}", headers=head
163
+ )
164
+ soup = self.parse_html(content)
165
+ link = soup.find("div", {"class": "ui segment"}).a["href"]
166
+ if self.debug:
167
+ print(title, link)
168
+ self.append_to_list(self.du_data, title, link)
169
+
170
+ except:
171
+ self.handle_exception("du")
172
+ self.du_done = True
173
+ if self.debug:
174
+ print("Return Length:", len(self.du_data))
175
+
176
+ def uf(self):
177
+ try:
178
+ all_items = []
179
+ for page in range(1, 4):
180
+ content = self.fetch_page_content(
181
+ f"https://www.udemyfreebies.com/free-udemy-courses/{page}"
182
+ )
183
+ soup = self.parse_html(content)
184
+ page_items = soup.find_all("a", {"class": "theme-img"})
185
+ all_items.extend(page_items)
186
+ self.uf_length = len(all_items)
187
+ if self.debug:
188
+ print("Length:", self.uf_length)
189
+ for index, item in enumerate(all_items):
190
+ title = item.img["alt"]
191
+ link = requests.get(
192
+ f"https://www.udemyfreebies.com/out/{item['href'].split('/')[4]}"
193
+ ).url
194
+ self.append_to_list(self.uf_data, title, link)
195
+ self.uf_progress = index
196
+
197
+ except:
198
+ self.handle_exception("uf")
199
+ self.uf_done = True
200
+ if self.debug:
201
+ print("Return Length:", len(self.uf_data))
202
+
203
+ def tb(self):
204
+ try:
205
+ all_items = []
206
+
207
+ for page in range(1, 5):
208
+ content = self.fetch_page_content(
209
+ f"https://www.tutorialbar.com/all-courses/page/{page}"
210
+ )
211
+ soup = self.parse_html(content)
212
+ page_items = soup.find_all(
213
+ "h2", class_="mb15 mt0 font110 mobfont100 fontnormal lineheight20"
214
+ )
215
+ all_items.extend(page_items)
216
+ self.tb_length = len(all_items)
217
+ if self.debug:
218
+ print("Length:", self.tb_length)
219
+
220
+ for index, item in enumerate(all_items):
221
+ self.tb_progress = index
222
+ title = item.a.string
223
+ url = item.a["href"]
224
+ content = self.fetch_page_content(url)
225
+ soup = self.parse_html(content)
226
+ link = soup.find("a", class_="btn_offer_block re_track_btn")["href"]
227
+ if "www.udemy.com" in link:
228
+ self.append_to_list(self.tb_data, title, link)
229
+
230
+ except:
231
+ self.handle_exception("tb")
232
+ self.tb_done = True
233
+ if self.debug:
234
+ print("Return Length:", len(self.tb_data))
235
+
236
+ def rd(self):
237
+ all_items = []
238
+
239
+ try:
240
+ headers = {
241
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36 Edg/92.0.902.84",
242
+ "Host": "www.real.discount",
243
+ "Connection": "Keep-Alive",
244
+ "dnt": "1",
245
+ }
246
+ try:
247
+ r = requests.get(
248
+ "https://www.real.discount/api-web/all-courses/?store=Udemy&page=1&per_page=500&orderby=date&free=1&editorschoices=0",
249
+ headers=headers,
250
+ timeout=(10, 30),
251
+ ).json()
252
+ except requests.exceptions.Timeout:
253
+ self.rd_error = "Timeout"
254
+ self.rd_length = -1
255
+ self.rd_done = True
256
+ return
257
+ all_items.extend(r["results"])
258
+
259
+ self.rd_length = len(all_items)
260
+ if self.debug:
261
+ print("Length:", self.rd_length)
262
+ for index, item in enumerate(all_items):
263
+ self.rd_progress = index
264
+ title: str = item["name"]
265
+ link: str = item["url"]
266
+ link = self.cleanup_link(link)
267
+ if link:
268
+ self.append_to_list(self.rd_data, title, link)
269
+
270
+ except:
271
+ self.handle_exception("rd")
272
+ if self.debug:
273
+ print("Return Length:", len(self.rd_data))
274
+ self.rd_done = True
275
+
276
+ def cv(self):
277
+ try:
278
+ content = self.fetch_page_content("https://coursevania.com/courses/")
279
+ soup = self.parse_html(content)
280
+ try:
281
+ nonce = json.loads(
282
+ re.search(
283
+ r"var stm_lms_nonces = ({.*?});", soup.text, re.DOTALL
284
+ ).group(1)
285
+ )["load_content"]
286
+ if self.debug:
287
+ print("Nonce:", nonce)
288
+ except IndexError:
289
+ self.cv_error = "Nonce not found"
290
+ self.cv_length = -1
291
+ self.cv_done = True
292
+ return
293
+ r = requests.get(
294
+ "https://coursevania.com/wp-admin/admin-ajax.php?&template=courses/grid&args={%22posts_per_page%22:%2260%22}&action=stm_lms_load_content&nonce="
295
+ + nonce
296
+ + "&sort=date_high"
297
+ ).json()
298
+
299
+ soup = self.parse_html(r["content"])
300
+ page_items = soup.find_all(
301
+ "div", {"class": "stm_lms_courses__single--title"}
302
+ )
303
+ self.cv_length = len(page_items)
304
+ if self.debug:
305
+ print("Small Length:", self.cv_length)
306
+ for index, item in enumerate(page_items):
307
+ self.cv_progress = index
308
+ title = item.h5.string
309
+ content = self.fetch_page_content(item.a["href"])
310
+ soup = self.parse_html(content)
311
+ link = soup.find(
312
+ "a",
313
+ {"class": "masterstudy-button-affiliate__link"},
314
+ )["href"]
315
+ self.append_to_list(self.cv_data, title, link)
316
+
317
+ except:
318
+ self.handle_exception("cv")
319
+ self.cv_done = True
320
+ if self.debug:
321
+ print("Return Length:", len(self.cv_data))
322
+
323
+ def idc(self):
324
+ try:
325
+ all_items = []
326
+ for page in range(1, 5):
327
+ content = self.fetch_page_content(
328
+ f"https://idownloadcoupon.com/product-category/udemy/page/{page}"
329
+ )
330
+ soup = self.parse_html(content)
331
+ page_items = soup.find_all(
332
+ "a",
333
+ attrs={
334
+ "class": "woocommerce-LoopProduct-link woocommerce-loop-product__link"
335
+ },
336
+ )
337
+ all_items.extend(page_items)
338
+ self.idc_length = len(all_items)
339
+ if self.debug:
340
+ print("Length:", self.idc_length)
341
+ for index, item in enumerate(all_items):
342
+ self.idc_progress = index
343
+ title = item.h2.string
344
+ link_num = item["href"].split("/")[4]
345
+ if link_num == "85":
346
+ continue
347
+ link = f"https://idownloadcoupon.com/udemy/{link_num}/"
348
+
349
+ r = requests.get(
350
+ link,
351
+ allow_redirects=False,
352
+ )
353
+ link = unquote(r.headers["Location"])
354
+ link = self.cleanup_link(link)
355
+ self.append_to_list(self.idc_data, title, link)
356
+
357
+ except:
358
+ self.handle_exception("idc")
359
+ self.idc_done = True
360
+ if self.debug:
361
+ print("Return Length:", len(self.idc_data))
362
+
363
+ def en(self):
364
+ try:
365
+ all_items = []
366
+ for page in range(1, 6):
367
+ content = self.fetch_page_content(
368
+ f"https://jobs.e-next.in/course/udemy/{page}"
369
+ )
370
+ soup = self.parse_html(content)
371
+ page_items = soup.find_all(
372
+ "a", {"class": "btn btn-secondary btn-sm btn-block"}
373
+ )
374
+ all_items.extend(page_items)
375
+
376
+ self.en_length = len(all_items)
377
+
378
+ if self.debug:
379
+ print("Length:", self.en_length)
380
+ for index, item in enumerate(all_items):
381
+ self.en_progress = index
382
+ content = self.fetch_page_content(item["href"])
383
+ soup = self.parse_html(content)
384
+ title = soup.find("h3").string.strip()
385
+ link = soup.find("a", {"class": "btn btn-primary"})["href"]
386
+ self.append_to_list(self.en_data, title, link)
387
+
388
+ except:
389
+ self.handle_exception("en")
390
+ self.en_done = True
391
+ if self.debug:
392
+ print("Return Length:", len(self.en_data))
393
+ print(self.en_data)
394
+
395
+
396
+ class Udemy:
397
+ def __init__(self, interface: str, debug: bool = False):
398
+ self.interface = interface
399
+ self.client = cloudscraper.CloudScraper()
400
+ headers = {
401
+ "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
402
+ "Accept": "application/json, text/plain, */*",
403
+ "Accept-Language": "en-GB,en;q=0.5",
404
+ "Referer": "https://www.udemy.com/",
405
+ "X-Requested-With": "XMLHttpRequest",
406
+ "DNT": "1",
407
+ "Connection": "keep-alive",
408
+ "Sec-Fetch-Dest": "empty",
409
+ "Sec-Fetch-Mode": "cors",
410
+ "Sec-Fetch-Site": "same-origin",
411
+ "Pragma": "no-cache",
412
+ "Cache-Control": "no-cache",
413
+ }
414
+
415
+ self.client.headers.update(headers)
416
+ self.debug = debug
417
+
418
+ def print(self, content: str, color: str = "red", **kargs):
419
+ content = str(content)
420
+ colours_dict = {
421
+ "yellow": fy,
422
+ "red": fr,
423
+ "blue": fb,
424
+ "light blue": flb,
425
+ "green": fg,
426
+ "light green": flg,
427
+ "cyan": fc,
428
+ "magenta": fm,
429
+ }
430
+ if self.interface == "gui":
431
+ self.window["out"].print(content, text_color=color, **kargs)
432
+ else:
433
+ print(colours_dict[color] + content, **kargs)
434
+
435
+ def get_date_from_utc(self, d: str):
436
+ utc_dt = datetime.strptime(d, "%Y-%m-%dT%H:%M:%SZ")
437
+ dt = utc_dt.replace(tzinfo=timezone.utc).astimezone(tz=None)
438
+ return dt.strftime("%B %d, %Y")
439
+
440
+ def get_now_to_utc(self):
441
+ return datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
442
+
443
+ def load_settings(self):
444
+ try:
445
+ with open(f"duce-{self.interface}-settings.json") as f:
446
+ self.settings = json.load(f)
447
+ except FileNotFoundError:
448
+ with open(
449
+ resource_path(f"default-duce-{self.interface}-settings.json")
450
+ ) as f:
451
+ self.settings = json.load(f)
452
+ if (
453
+ self.interface == "cli" and "use_browser_cookies" not in self.settings
454
+ ): # v2.1
455
+ self.settings.get("use_browser_cookies", False)
456
+ # v2.2
457
+ if "course_update_threshold_months" not in self.settings:
458
+ self.settings["course_update_threshold_months"] = 24 # 2 years
459
+
460
+ self.settings["languages"] = dict(
461
+ sorted(self.settings["languages"].items(), key=lambda item: item[0])
462
+ )
463
+ self.save_settings()
464
+ self.title_exclude = "\n".join(self.settings["title_exclude"])
465
+ self.instructor_exclude = "\n".join(self.settings["instructor_exclude"])
466
+
467
+ def save_settings(self):
468
+ with open(f"duce-{self.interface}-settings.json", "w") as f:
469
+ json.dump(self.settings, f, indent=4)
470
+
471
+ def make_cookies(self, client_id: str, access_token: str, csrf_token: str):
472
+ self.cookie_dict = dict(
473
+ client_id=client_id,
474
+ access_token=access_token,
475
+ csrf_token=csrf_token,
476
+ )
477
+
478
+ def fetch_cookies(self):
479
+ """Gets cookies from browser
480
+ Sets cookies_dict, cookie_jar
481
+ """
482
+ cookies = rookiepy.to_cookiejar(rookiepy.load(["www.udemy.com"]))
483
+ self.cookie_dict: dict = requests.utils.dict_from_cookiejar(cookies)
484
+ self.cookie_jar = cookies
485
+
486
+ def get_enrolled_courses(self):
487
+ """Get enrolled courses
488
+ Sets enrolled_courses
489
+
490
+ {slug:enrollment_time}
491
+ """
492
+ next_page = "https://www.udemy.com/api-2.0/users/me/subscribed-courses/?ordering=-enroll_time&fields[course]=enrollment_time,url&page_size=100"
493
+ courses = {}
494
+ while next_page:
495
+ r = self.client.get(
496
+ next_page,
497
+ ).json()
498
+ for course in r["results"]:
499
+ slug = course["url"].split("/")[2]
500
+ courses[slug] = course["enrollment_time"]
501
+ next_page = r["next"]
502
+ self.enrolled_courses = courses
503
+
504
+ def compare_versions(self, version1, version2):
505
+ v1_parts = list(map(int, version1.split(".")))
506
+ v2_parts = list(map(int, version2.split(".")))
507
+ max_length = max(len(v1_parts), len(v2_parts))
508
+ v1_parts.extend([0] * (max_length - len(v1_parts)))
509
+ v2_parts.extend([0] * (max_length - len(v2_parts)))
510
+
511
+ for v1, v2 in zip(v1_parts, v2_parts):
512
+ if v1 < v2:
513
+ return -1
514
+ elif v1 > v2:
515
+ return 1
516
+ return 0
517
+
518
+ def check_for_update(self) -> tuple[str, str]:
519
+ r_version = (
520
+ requests.get(
521
+ "https://api.github.com/repos/techtanic/Discounted-Udemy-Course-Enroller/releases/latest"
522
+ )
523
+ .json()["tag_name"]
524
+ .removeprefix("v")
525
+ )
526
+ c_version = VERSION.removeprefix("v")
527
+
528
+ comparison = self.compare_versions(c_version, r_version)
529
+
530
+ if comparison == -1:
531
+ return (
532
+ f"Update {r_version} Available",
533
+ f"Update {r_version} Available",
534
+ )
535
+ elif comparison == 0:
536
+ return (
537
+ f"Login {c_version}",
538
+ f"Discounted-Udemy-Course-Enroller {c_version}",
539
+ )
540
+ else:
541
+ return (
542
+ f"Dev Login {c_version}",
543
+ f"Dev Discounted-Udemy-Course-Enroller {c_version}",
544
+ )
545
+
546
+ def manual_login(self, email: str, password: str):
547
+ """Manual Login to Udemy using email and password and sets cookies
548
+ Args:
549
+ email (str): Email
550
+ password (str): Password
551
+ Raises:
552
+ LoginException: Login Error
553
+ """
554
+ # s = cloudscraper.CloudScraper()
555
+
556
+ s = requests.session()
557
+ r = s.get(
558
+ "https://www.udemy.com/join/signup-popup/?locale=en_US&response_type=html&next=https%3A%2F%2Fwww.udemy.com%2Flogout%2F",
559
+ headers={"User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)"},
560
+ # headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0",
561
+ # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
562
+ # 'Accept-Language': 'en-US,en;q=0.5',
563
+ # #'Accept-Encoding': 'gzip, deflate, br',
564
+ # 'DNT': '1',
565
+ # 'Connection': 'keep-alive',
566
+ # 'Upgrade-Insecure-Requests': '1',
567
+ # 'Sec-Fetch-Dest': 'document',
568
+ # 'Sec-Fetch-Mode': 'navigate',
569
+ # 'Sec-Fetch-Site': 'none',
570
+ # 'Sec-Fetch-User': '?1',
571
+ # 'Pragma': 'no-cache',
572
+ # 'Cache-Control': 'no-cache'},
573
+ )
574
+ try:
575
+ csrf_token = r.cookies["csrftoken"]
576
+ except:
577
+ if self.debug:
578
+ print(r.text)
579
+ data = {
580
+ "csrfmiddlewaretoken": csrf_token,
581
+ "locale": "en_US",
582
+ "email": email,
583
+ "password": password,
584
+ }
585
+
586
+ # ss = requests.session()
587
+ s.cookies.update(r.cookies)
588
+ s.headers.update(
589
+ {
590
+ "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
591
+ "Accept": "application/json, text/plain, */*",
592
+ "Accept-Language": "en-GB,en;q=0.5",
593
+ "Referer": "https://www.udemy.com/join/login-popup/?passwordredirect=True&response_type=json",
594
+ "Origin": "https://www.udemy.com",
595
+ "DNT": "1",
596
+ "Host": "www.udemy.com",
597
+ "Connection": "keep-alive",
598
+ "Sec-Fetch-Dest": "empty",
599
+ "Sec-Fetch-Mode": "cors",
600
+ "Sec-Fetch-Site": "same-origin",
601
+ "Pragma": "no-cache",
602
+ "Cache-Control": "no-cache",
603
+ }
604
+ )
605
+ s = cloudscraper.create_scraper(sess=s)
606
+ r = s.post(
607
+ "https://www.udemy.com/join/login-popup/?passwordredirect=True&response_type=json",
608
+ data=data,
609
+ allow_redirects=False,
610
+ )
611
+ if r.text.__contains__("returnUrl"):
612
+ self.make_cookies(
613
+ r.cookies["client_id"], r.cookies["access_token"], csrf_token
614
+ )
615
+ else:
616
+ login_error = r.json()["error"]["data"]["formErrors"][0]
617
+ if login_error[0] == "Y":
618
+ raise LoginException("Too many logins per hour try later")
619
+ elif login_error[0] == "T":
620
+ raise LoginException("Email or password incorrect")
621
+ else:
622
+ raise LoginException(login_error)
623
+
624
+ def get_session_info(self):
625
+ """Get Session info
626
+ Sets Client Session, currency and name
627
+ """
628
+ s = cloudscraper.CloudScraper()
629
+ # headers = {
630
+ # "authorization": "Bearer " + self.cookie_dict["access_token"],
631
+ # "accept": "application/json, text/plain, */*",
632
+ # "x-requested-with": "XMLHttpRequest",
633
+ # "x-forwarded-for": str(
634
+ # ".".join(map(str, (random.randint(0, 255) for _ in range(4))))
635
+ # ),
636
+ # "x-udemy-authorization": "Bearer " + self.cookie_dict["access_token"],
637
+ # "content-type": "application/json;charset=UTF-8",
638
+ # "origin": "https://www.udemy.com",
639
+ # "referer": "https://www.udemy.com/",
640
+ # "dnt": "1",
641
+ # "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
642
+ # }
643
+
644
+ headers = {
645
+ "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
646
+ "Accept": "application/json, text/plain, */*",
647
+ "Accept-Language": "en-GB,en;q=0.5",
648
+ "Referer": "https://www.udemy.com/",
649
+ "X-Requested-With": "XMLHttpRequest",
650
+ "DNT": "1",
651
+ "Connection": "keep-alive",
652
+ "Sec-Fetch-Dest": "empty",
653
+ "Sec-Fetch-Mode": "cors",
654
+ "Sec-Fetch-Site": "same-origin",
655
+ "Pragma": "no-cache",
656
+ "Cache-Control": "no-cache",
657
+ }
658
+
659
+ r = s.get(
660
+ "https://www.udemy.com/api-2.0/contexts/me/?header=True",
661
+ cookies=self.cookie_dict,
662
+ headers=headers,
663
+ )
664
+ r = r.json()
665
+ if self.debug:
666
+ print(r)
667
+ if not r["header"]["isLoggedIn"]:
668
+ raise LoginException("Login Failed")
669
+
670
+ self.display_name: str = r["header"]["user"]["display_name"]
671
+ r = s.get(
672
+ "https://www.udemy.com/api-2.0/shopping-carts/me/",
673
+ headers=headers,
674
+ cookies=self.cookie_dict,
675
+ )
676
+ r = r.json()
677
+ self.currency: str = r["user"]["credit"]["currency_code"]
678
+
679
+ s = cloudscraper.CloudScraper()
680
+ s.cookies.update(self.cookie_dict)
681
+ s.headers.update(headers)
682
+ s.keep_alive = False
683
+ self.client = s
684
+ self.get_enrolled_courses()
685
+
686
+ def is_keyword_excluded(self, title: str) -> bool:
687
+ title_words = title.casefold().split()
688
+ for word in title_words:
689
+ word = word.casefold()
690
+ if word in self.title_exclude:
691
+ return True
692
+ return False
693
+
694
+ def is_instructor_excluded(self, instructors: list) -> bool:
695
+ for instructor in instructors:
696
+ if instructor in self.settings["instructor_exclude"]:
697
+ return True
698
+ return False
699
+
700
+ def is_course_updated(self, last_update: str | None) -> bool:
701
+ if not last_update:
702
+ return True
703
+ current_date = datetime.now()
704
+ last_update_date = datetime.strptime(last_update, "%Y-%m-%d")
705
+ # Calculate the difference in years and months
706
+ years = current_date.year - last_update_date.year
707
+ months = current_date.month - last_update_date.month
708
+ days = current_date.day - last_update_date.day
709
+
710
+ # Adjust the months and years if necessary
711
+ if days < 0:
712
+ months -= 1
713
+
714
+ if months < 0:
715
+ years -= 1
716
+ months += 12
717
+
718
+ # Calculate the total month difference
719
+ month_diff = years * 12 + months
720
+ return month_diff < self.settings["course_update_threshold_months"]
721
+
722
+ def is_user_dumb(self) -> bool:
723
+ self.sites = [key for key, value in self.settings["sites"].items() if value]
724
+ self.categories = [
725
+ key for key, value in self.settings["categories"].items() if value
726
+ ]
727
+ self.languages = [
728
+ key for key, value in self.settings["languages"].items() if value
729
+ ]
730
+ self.instructor_exclude = self.settings["instructor_exclude"]
731
+ self.title_exclude = self.settings["title_exclude"]
732
+ self.min_rating = self.settings["min_rating"]
733
+ return not all([bool(self.sites), bool(self.categories), bool(self.languages)])
734
+
735
+ def save_course(self):
736
+ if self.settings["save_txt"]:
737
+ self.txt_file.write(f"{self.title} - {self.link}\n")
738
+ self.txt_file.flush()
739
+ os.fsync(self.txt_file.fileno())
740
+
741
+ def remove_duplicate_courses(self):
742
+ existing_links = set()
743
+ new_data = {}
744
+ for key, courses in self.scraped_data.items():
745
+ new_data[key] = []
746
+ for title, link in courses:
747
+ link = self.normalize_link(link)
748
+ if link not in existing_links:
749
+ new_data[key].append((title, link))
750
+ existing_links.add(link)
751
+ self.scraped_data = {k: v for k, v in new_data.items() if v}
752
+
753
+ def normalize_link(self, link):
754
+ parsed_url = urlparse(link)
755
+ path = (
756
+ parsed_url.path if parsed_url.path.endswith("/") else parsed_url.path + "/"
757
+ )
758
+ return urlunparse(
759
+ (
760
+ parsed_url.scheme,
761
+ parsed_url.netloc,
762
+ path,
763
+ parsed_url.params,
764
+ parsed_url.query,
765
+ parsed_url.fragment,
766
+ )
767
+ )
768
+
769
+ def get_course_id(self, url):
770
+ course = {
771
+ "course_id": None,
772
+ "url": url,
773
+ "is_invalid": False,
774
+ "is_free": None,
775
+ "is_excluded": None,
776
+ "retry": None,
777
+ "msg": "Report to developer",
778
+ }
779
+ url = re.sub(r"\W+$", "", unquote(url))
780
+ try:
781
+ r = self.client.get(url)
782
+ except requests.exceptions.ConnectionError:
783
+ if self.debug:
784
+ print(r.text)
785
+ course["retry"] = True
786
+ return course
787
+ course["url"] = r.url
788
+ soup = bs(r.content, "html5lib")
789
+
790
+ course_id = soup.find("body").get("data-clp-course-id", "invalid")
791
+
792
+ if course_id == "invalid":
793
+ course["is_invalid"] = True
794
+ course["msg"] = "Course ID not found: Report to developer"
795
+ return course
796
+ course["course_id"] = course_id
797
+ dma = json.loads(soup.find("body")["data-module-args"])
798
+ if self.debug:
799
+ with open("debug/dma.json", "w") as f:
800
+ json.dump(dma, f, indent=4)
801
+
802
+ if dma.get("view_restriction"):
803
+ course["is_invalid"] = True
804
+ course["msg"] = dma["serverSideProps"]["limitedAccess"]["errorMessage"][
805
+ "title"
806
+ ]
807
+ return course
808
+
809
+ course["is_free"] = not dma["serverSideProps"]["course"].get("isPaid", True)
810
+ if not self.debug and self.is_course_excluded(dma):
811
+ course["is_excluded"] = True
812
+ return course
813
+
814
+ return course
815
+
816
+ def is_course_excluded(self, dma):
817
+ instructors = [
818
+ i["absolute_url"].split("/")[-2]
819
+ for i in dma["serverSideProps"]["course"]["instructors"]["instructors_info"]
820
+ if i["absolute_url"]
821
+ ]
822
+ lang = dma["serverSideProps"]["course"]["localeSimpleEnglishTitle"]
823
+ cat = dma["serverSideProps"]["topicMenu"]["breadcrumbs"][0]["title"]
824
+ rating = dma["serverSideProps"]["course"]["rating"]
825
+ last_update = dma["serverSideProps"]["course"]["lastUpdateDate"]
826
+
827
+ if not self.is_course_updated(last_update):
828
+ self.print(
829
+ f"Course excluded: Last updated {last_update}", color="light blue"
830
+ )
831
+ elif self.is_instructor_excluded(instructors):
832
+ self.print(f"Instructor excluded: {instructors[0]}", color="light blue")
833
+ elif self.is_keyword_excluded(self.title):
834
+ self.print("Keyword Excluded", color="light blue")
835
+ elif cat not in self.categories:
836
+ self.print(f"Category excluded: {cat}", color="light blue")
837
+ elif lang not in self.languages:
838
+ self.print(f"Language excluded: {lang}", color="light blue")
839
+ elif rating < self.min_rating:
840
+ self.print(f"Low rating: {rating}", color="light blue")
841
+ else:
842
+ return False
843
+ return True
844
+
845
+ def extract_course_coupon(self, url):
846
+ params = parse_qs(urlsplit(url).query)
847
+ return params.get("couponCode", [False])[0]
848
+
849
+ def check_course(self, course_id, coupon_code=None):
850
+ url = f"https://www.udemy.com/api-2.0/course-landing-components/{course_id}/me/?components=purchase"
851
+ if coupon_code:
852
+ url += f",redeem_coupon&couponCode={coupon_code}"
853
+
854
+ r = self.client.get(url).json()
855
+ if self.debug:
856
+ with open("test/check_course.json", "w") as f:
857
+ json.dump(r, f, indent=4)
858
+ amount = (
859
+ r.get("purchase", {})
860
+ .get("data", {})
861
+ .get("list_price", {})
862
+ .get("amount", "retry")
863
+ )
864
+ coupon_valid = False
865
+
866
+ if coupon_code and "redeem_coupon" in r:
867
+ discount = r["purchase"]["data"]["pricing_result"]["discount_percent"]
868
+ status = r["redeem_coupon"]["discount_attempts"][0]["status"]
869
+ coupon_valid = discount == 100 and status == "applied"
870
+
871
+ return Decimal(amount), coupon_valid
872
+
873
+ def start_enrolling(self):
874
+ self.remove_duplicate_courses()
875
+ self.initialize_counters()
876
+ self.setup_txt_file()
877
+
878
+ total_courses = sum(len(courses) for courses in self.scraped_data.values())
879
+ previous_courses_count = 0
880
+ for site_index, (site, courses) in enumerate(self.scraped_data.items()):
881
+ self.print(f"\nSite: {site} [{len(courses)}]", color="cyan")
882
+
883
+ for index, (title, link) in enumerate(courses):
884
+ self.title = title
885
+ self.link = link
886
+ self.print_course_info(previous_courses_count + index, total_courses)
887
+ self.handle_course_enrollment()
888
+ previous_courses_count += len(courses)
889
+
890
+ def initialize_counters(self):
891
+ self.successfully_enrolled_c = 0
892
+ self.already_enrolled_c = 0
893
+ self.expired_c = 0
894
+ self.excluded_c = 0
895
+ self.amount_saved_c = 0
896
+
897
+ def setup_txt_file(self):
898
+ if self.settings["save_txt"]:
899
+ os.makedirs("Courses/", exist_ok=True)
900
+ self.txt_file = open(
901
+ f"Courses/{time.strftime('%Y-%m-%d--%H-%M')}.txt", "w", encoding="utf-8"
902
+ )
903
+
904
+ def print_course_info(self, index, total_courses):
905
+ self.print(f"[{index + 1} / {total_courses}] ", color="magenta", end=" ")
906
+ self.print(self.title, color="yellow", end=" ")
907
+ self.print(self.link, color="blue")
908
+
909
+ def handle_course_enrollment(self):
910
+ slug = self.link.split("/")[4]
911
+
912
+ if slug in self.enrolled_courses:
913
+ self.print(
914
+ f"You purchased this course on {self.get_date_from_utc(self.enrolled_courses[slug])}",
915
+ color="light blue",
916
+ )
917
+ self.already_enrolled_c += 1
918
+ return
919
+
920
+ course = self.get_course_id(self.link)
921
+ if course["is_invalid"]:
922
+ self.print(course["msg"], color="red")
923
+ self.excluded_c += 1
924
+ elif course["retry"]:
925
+ self.print("Retrying...", color="red")
926
+ time.sleep(1)
927
+ self.handle_course_enrollment()
928
+ elif course["is_excluded"]:
929
+ self.excluded_c += 1
930
+ elif course["is_free"]:
931
+ self.handle_free_course(course["course_id"])
932
+ elif not course["is_free"]:
933
+ self.handle_discounted_course(course["course_id"])
934
+ else:
935
+ self.print("Unknown Error: Report this link to the developer", color="red")
936
+ self.excluded_c += 1
937
+
938
+ def handle_free_course(self, course_id):
939
+ if self.settings["discounted_only"]:
940
+ self.print("Free course excluded", color="light blue")
941
+ self.excluded_c += 1
942
+ else:
943
+ success = self.free_checkout(course_id)
944
+ if success:
945
+ self.print("Successfully Subscribed", color="green")
946
+ self.successfully_enrolled_c += 1
947
+ self.save_course()
948
+ else:
949
+ self.print(
950
+ "Unknown Error: Report this link to the developer", color="red"
951
+ )
952
+ self.expired_c += 1
953
+
954
+ def discounted_checkout(self, coupon, course_id) -> dict:
955
+ payload = {
956
+ "checkout_environment": "Marketplace",
957
+ "checkout_event": "Submit",
958
+ "payment_info": {
959
+ "method_id": "0",
960
+ "payment_method": "free-method",
961
+ "payment_vendor": "Free",
962
+ },
963
+ "shopping_info": {
964
+ "items": [
965
+ {
966
+ "buyable": {"id": course_id, "type": "course"},
967
+ "discountInfo": {"code": coupon},
968
+ "price": {"amount": 0, "currency": self.currency.upper()},
969
+ }
970
+ ],
971
+ "is_cart": False,
972
+ },
973
+ }
974
+ headers = {
975
+ "User-Agent": "okhttp/4.9.2 UdemyAndroid 8.9.2(499) (phone)",
976
+ "Accept": "application/json, text/plain, */*",
977
+ "Accept-Language": "en-US",
978
+ "Referer": f"https://www.udemy.com/payment/checkout/express/course/{course_id}/?discountCode={coupon}",
979
+ "Content-Type": "application/json",
980
+ "X-Requested-With": "XMLHttpRequest",
981
+ "x-checkout-is-mobile-app": "false",
982
+ "Origin": "https://www.udemy.com",
983
+ "DNT": "1",
984
+ "Sec-GPC": "1",
985
+ "Connection": "keep-alive",
986
+ "Sec-Fetch-Dest": "empty",
987
+ "Sec-Fetch-Mode": "cors",
988
+ "Sec-Fetch-Site": "same-origin",
989
+ "Priority": "u=0",
990
+ }
991
+ csrftoken = None
992
+ for cookie in self.client.cookies:
993
+ if cookie.name == "csrftoken":
994
+ csrftoken = cookie.value
995
+ break
996
+
997
+ if csrftoken:
998
+ headers["X-CSRFToken"] = csrftoken
999
+ else:
1000
+ raise ValueError("CSRF token not found")
1001
+
1002
+ r = self.client.post(
1003
+ "https://www.udemy.com/payment/checkout-submit/",
1004
+ json=payload,
1005
+ headers=headers,
1006
+ )
1007
+ try:
1008
+ r = r.json()
1009
+ except:
1010
+ self.print(r.text, color="red")
1011
+ self.print("Unknown Error: Report this to the developer", color="red")
1012
+ return r
1013
+
1014
+ def free_checkout(self, course_id):
1015
+ self.client.get(f"https://www.udemy.com/course/subscribe/?courseId={course_id}")
1016
+ r = self.client.get(
1017
+ f"https://www.udemy.com/api-2.0/users/me/subscribed-courses/{course_id}/?fields%5Bcourse%5D=%40default%2Cbuyable_object_type%2Cprimary_subcategory%2Cis_private"
1018
+ ).json()
1019
+ return r.get("_class") == "course"
1020
+
1021
+ def handle_discounted_course(self, course_id):
1022
+ coupon_code = self.extract_course_coupon(self.link)
1023
+ amount, coupon_valid = self.check_course(course_id, coupon_code)
1024
+ if amount == "retry":
1025
+ self.print("Retrying...", color="red")
1026
+ time.sleep(1)
1027
+ self.handle_discounted_course(course_id)
1028
+ elif coupon_valid: # elif coupon_code and coupon_valid:
1029
+ self.process_coupon(course_id, coupon_code, amount)
1030
+ else:
1031
+ self.print("Coupon Expired", color="red")
1032
+ self.expired_c += 1
1033
+
1034
+ def process_coupon(self, course_id, coupon_code, amount):
1035
+ checkout_response = self.discounted_checkout(coupon_code, course_id)
1036
+ if msg := checkout_response.get("detail"):
1037
+ self.print(msg, color="red")
1038
+ try:
1039
+ wait_time = int(re.search(r"\d+", checkout_response["detail"]).group(0))
1040
+ except:
1041
+ self.print(
1042
+ "Unknown Error: Report this link to the developer", color="red"
1043
+ )
1044
+ self.print(checkout_response, color="red")
1045
+ wait_time = 60
1046
+ time.sleep(wait_time + 1)
1047
+ self.process_coupon(course_id, coupon_code, amount)
1048
+ elif checkout_response["status"] == "succeeded":
1049
+ self.print("Successfully Enrolled To Course :)", color="green")
1050
+ self.successfully_enrolled_c += 1
1051
+ self.enrolled_courses[course_id] = self.get_now_to_utc()
1052
+ self.amount_saved_c += amount
1053
+ self.save_course()
1054
+ time.sleep(3.7)
1055
+ elif checkout_response["status"] == "failed":
1056
+ message = checkout_response["message"]
1057
+ if "item_already_subscribed" in message:
1058
+ self.print("Already Enrolled", color="light blue")
1059
+ self.already_enrolled_c += 1
1060
+ else:
1061
+ self.print("Unknown Error: Report this to the developer", color="red")
1062
+ self.print(checkout_response, color="red")
1063
+ else:
1064
+ self.print("Unknown Error: Report this to the developer", color="red")
1065
+ self.print(checkout_response, color="red")
cli.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import threading
2
+ import time
3
+ import traceback
4
+
5
+ from tqdm import tqdm
6
+
7
+ from base import VERSION, LoginException, Scraper, Udemy, scraper_dict
8
+ from colors import bw, by, fb, fg, fr
9
+
10
+ # DUCE-CLI
11
+
12
+
13
+ def create_scraping_thread(site: str):
14
+
15
+ code_name = scraper_dict[site]
16
+ try:
17
+ t = threading.Thread(target=getattr(scraper, code_name), daemon=True)
18
+ t.start()
19
+
20
+ while getattr(scraper, f"{code_name}_length") == 0:
21
+ time.sleep(0.1) # Avoid busy waiting
22
+ if getattr(scraper, f"{code_name}_length") == -1:
23
+ raise Exception(f"Error in: {site}")
24
+ progress_bar = tqdm(
25
+ total=getattr(scraper, f"{code_name}_length"), desc=site, leave=False
26
+ )
27
+ prev_progress = -1
28
+
29
+ while not getattr(scraper, f"{code_name}_done"):
30
+ time.sleep(0.1)
31
+ current_progress = getattr(scraper, f"{code_name}_progress")
32
+ progress_bar.update(current_progress - prev_progress)
33
+ prev_progress = current_progress
34
+
35
+ progress_bar.update(getattr(scraper, f"{code_name}_length") - prev_progress)
36
+
37
+ except Exception:
38
+ error = getattr(scraper, f"{code_name}_error", traceback.format_exc())
39
+ print(error)
40
+ print("\nError in: " + site + " " + str(VERSION))
41
+
42
+
43
+ ##########################################
44
+
45
+ udemy = Udemy("cli")
46
+ udemy.load_settings()
47
+ login_title, main_title = udemy.check_for_update()
48
+ if login_title.__contains__("Update"):
49
+ print(by + fr + login_title)
50
+
51
+ ############## MAIN #############
52
+
53
+ login_successful = False
54
+ while not login_successful:
55
+ try:
56
+ if udemy.settings["use_browser_cookies"]:
57
+ udemy.fetch_cookies()
58
+ login_method = "Browser Cookies"
59
+ elif udemy.settings["email"] and udemy.settings["password"]:
60
+ email, password = udemy.settings["email"], udemy.settings["password"]
61
+ login_method = "Saved Email and Password"
62
+ else:
63
+ email = input("Email: ")
64
+ password = input("Password: ")
65
+ login_method = "Email and Password"
66
+ print(fb + f"Trying to login using {login_method}")
67
+ if "Email" in login_method:
68
+ udemy.manual_login(email, password)
69
+ udemy.get_session_info()
70
+ if "Email" in login_method:
71
+ udemy.settings["email"], udemy.settings["password"] = email, password
72
+ login_successful = True
73
+ except LoginException as e:
74
+ print(fr + str(e))
75
+ if "Browser" in login_method:
76
+ print("Cant login using cookies")
77
+ udemy.settings["use_browser_cookies"] = False
78
+ elif "Email" in login_method:
79
+ udemy.settings["email"], udemy.settings["password"] = "", ""
80
+
81
+ udemy.save_settings()
82
+
83
+ print(fg + f"Logged in as {udemy.display_name}")
84
+ user_dumb = udemy.is_user_dumb()
85
+ if user_dumb:
86
+ print(bw + fr + "What do you even expect to happen!")
87
+ exit()
88
+ if not user_dumb:
89
+ scraper = Scraper(udemy.sites)
90
+ try:
91
+ udemy.scraped_data = scraper.get_scraped_courses(create_scraping_thread)
92
+ time.sleep(0.5)
93
+ print("\n")
94
+ udemy.start_enrolling()
95
+
96
+ udemy.print(
97
+ f"\nSuccessfully Enrolled: {udemy.successfully_enrolled_c}", color="green"
98
+ )
99
+ udemy.print(
100
+ f"Amount Saved: {round(udemy.amount_saved_c,2)} {udemy.currency.upper()}",
101
+ color="light green",
102
+ )
103
+ udemy.print(f"Already Enrolled: {udemy.already_enrolled_c}", color="blue")
104
+ udemy.print(f"Excluded Courses: {udemy.excluded_c}", color="yellow")
105
+ udemy.print(f"Expired Courses: {udemy.expired_c}", color="red")
106
+
107
+ except:
108
+ e = traceback.format_exc()
109
+ print(
110
+ (
111
+ "Error",
112
+ e + f"\n\n{udemy.link}\n{udemy.title}" + f"|:|Unknown Error {VERSION}",
113
+ )
114
+ )
115
+ input("Press Enter to exit...")
colors.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from colorama import init, Fore, Back, Style
2
+
3
+ init(autoreset=True)
4
+ # colors foreground text:
5
+ fc = Fore.CYAN
6
+ fg = Fore.GREEN
7
+ fw = Fore.WHITE
8
+ fr = Fore.RED
9
+ fb = Fore.BLUE
10
+ flb = Fore.LIGHTBLUE_EX
11
+ fbl = Fore.BLACK
12
+ fy = Fore.YELLOW
13
+ fm = Fore.MAGENTA
14
+ flg = Fore.LIGHTGREEN_EX
15
+
16
+ # colors background text:
17
+ bc = Back.CYAN
18
+ bg = Back.GREEN
19
+ bw = Back.WHITE
20
+ br = Back.RED
21
+ bb = Back.BLUE
22
+ by = Back.YELLOW
23
+ bm = Back.MAGENTA
24
+
25
+ # colors style text:
26
+ sd = Style.DIM
27
+ sn = Style.NORMAL
28
+ sb = Style.BRIGHT
default-duce-cli-settings.json ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "categories": {
3
+ "Business": true,
4
+ "Design": true,
5
+ "Development": true,
6
+ "Finance & Accounting": true,
7
+ "Health & Fitness": true,
8
+ "IT & Software": true,
9
+ "Lifestyle": true,
10
+ "Marketing": true,
11
+ "Music": true,
12
+ "Office Productivity": true,
13
+ "Personal Development": true,
14
+ "Photography & Video": true,
15
+ "Teaching & Academics": true
16
+ },
17
+ "languages": {
18
+ "Arabic": true,
19
+ "Chinese": true,
20
+ "Dutch": true,
21
+ "English": true,
22
+ "French": true,
23
+ "German": true,
24
+ "Hindi": true,
25
+ "Indonesian": true,
26
+ "Italian": true,
27
+ "Japanese": true,
28
+ "Korean": true,
29
+ "Nepali": true,
30
+ "Polish": true,
31
+ "Portuguese": true,
32
+ "Romanian": true,
33
+ "Russian": true,
34
+ "Spanish": true,
35
+ "Thai": true,
36
+ "Turkish": true,
37
+ "Urdu": true
38
+ },
39
+ "sites": {
40
+ "Real Discount": true,
41
+ "Discudemy": true,
42
+ "IDownloadCoupons": true,
43
+ "Tutorial Bar": true,
44
+ "E-next": true,
45
+ "Course Vania": true,
46
+ "Udemy Freebies": true
47
+ },
48
+ "min_rating": 0.0,
49
+ "instructor_exclude": [
50
+ "instructor-1",
51
+ "instructor-2",
52
+ "more-bad-instructor"
53
+ ],
54
+ "title_exclude": [
55
+ "keyword One",
56
+ "noT_cAse SenSItiVe"
57
+ ],
58
+ "email": "",
59
+ "password": "",
60
+ "save_txt": true,
61
+ "discounted_only": false,
62
+ "use_browser_cookies": false,
63
+ "course_update_threshold_months": 24
64
+ }
default-duce-gui-settings.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "stay_logged_in": {
3
+ "auto": false,
4
+ "manual": false
5
+ },
6
+ "min_rating": 0.0,
7
+ "title_exclude": [],
8
+ "instructor_exclude": [],
9
+ "languages": {
10
+ "Arabic": true,
11
+ "Chinese": true,
12
+ "Dutch": true,
13
+ "English": true,
14
+ "French": true,
15
+ "German": true,
16
+ "Hindi": true,
17
+ "Indonesian": true,
18
+ "Italian": true,
19
+ "Japanese": true,
20
+ "Korean": true,
21
+ "Nepali": true,
22
+ "Polish": true,
23
+ "Portuguese": true,
24
+ "Romanian": true,
25
+ "Russian": true,
26
+ "Spanish": true,
27
+ "Thai": true,
28
+ "Turkish": true,
29
+ "Urdu": true
30
+ },
31
+ "categories": {
32
+ "Business": true,
33
+ "Design": true,
34
+ "Development": true,
35
+ "Finance & Accounting": true,
36
+ "Health & Fitness": true,
37
+ "IT & Software": true,
38
+ "Lifestyle": true,
39
+ "Marketing": true,
40
+ "Music": true,
41
+ "Office Productivity": true,
42
+ "Personal Development": true,
43
+ "Photography & Video": true,
44
+ "Teaching & Academics": true
45
+ },
46
+ "sites": {
47
+ "Real Discount": true,
48
+ "Discudemy": true,
49
+ "IDownloadCoupons": true,
50
+ "Tutorial Bar": true,
51
+ "E-next": true,
52
+ "Course Vania": true,
53
+ "Udemy Freebies": true
54
+ },
55
+ "email": "",
56
+ "password": "",
57
+ "save_txt": false,
58
+ "discounted_only": false,
59
+ "course_update_threshold_months": 24
60
+ }
extra/DUCE-LOGO.ico ADDED
extra/DUCE-LOGO.png ADDED
extra/duce-gui-main.png ADDED
extra/promo.gif ADDED
gui.py ADDED
@@ -0,0 +1,680 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import threading
3
+ import time
4
+ import traceback
5
+ from webbrowser import open as web
6
+
7
+ import FreeSimpleGUI as sg
8
+
9
+ from base import LINKS, VERSION, LoginException, Scraper, Udemy, scraper_dict
10
+ from images import (
11
+ auto_login,
12
+ back,
13
+ check_mark,
14
+ exit_,
15
+ icon,
16
+ login,
17
+ logout,
18
+ manual_login_,
19
+ start,
20
+ )
21
+
22
+ sg.set_global_icon(icon)
23
+
24
+ sg.change_look_and_feel("dark")
25
+ sg.theme_background_color
26
+ sg.set_options(
27
+ button_color=(sg.theme_background_color(), sg.theme_background_color()),
28
+ border_width=0,
29
+ font=10,
30
+ )
31
+
32
+
33
+ def update_enrolled_courses():
34
+ while True:
35
+ new_menu = [
36
+ ["Help", ["Support", "Github", "Discord"]],
37
+ [f"Total Courses: {len(udemy.enrolled_courses)}"],
38
+ ]
39
+ main_window.write_event_value("Update-Menu", new_menu)
40
+ time.sleep(10)
41
+
42
+
43
+ def create_scraping_thread(site: str):
44
+ code_name = scraper_dict[site]
45
+ main_window[f"i{site}"].update(visible=False)
46
+ main_window[f"p{site}"].update(0, visible=True)
47
+
48
+ try:
49
+ threading.Thread(target=getattr(scraper, code_name), daemon=True).start()
50
+ while getattr(scraper, f"{code_name}_length") == 0:
51
+ time.sleep(0.1) # Avoid busy waiting
52
+ if getattr(scraper, f"{code_name}_length") == -1:
53
+ raise Exception(f"Error in: {site}")
54
+
55
+ main_window[f"p{site}"].update(0, max=getattr(scraper, f"{code_name}_length"))
56
+ while not getattr(scraper, f"{code_name}_done") and not getattr(
57
+ scraper, f"{code_name}_error"
58
+ ):
59
+ main_window[f"p{site}"].update(
60
+ getattr(scraper, f"{code_name}_progress") + 1
61
+ )
62
+ time.sleep(0.1) # Update every 0.1 seconds
63
+
64
+ if getattr(scraper, f"{code_name}_error"):
65
+ raise Exception(f"Error in: {site}")
66
+ except Exception:
67
+ error_message = getattr(scraper, f"{code_name}_error", "Unknown Error")
68
+ main_window.write_event_value(
69
+ "Error", f"{error_message}|:|Unknown Error in: {site} {VERSION}"
70
+ )
71
+ finally:
72
+ main_window[f"p{site}"].update(0, visible=False)
73
+ main_window[f"i{site}"].update(visible=True)
74
+
75
+
76
+ ##########################################
77
+
78
+
79
+ def scrape():
80
+ try:
81
+ for site in udemy.sites:
82
+ main_window[f"pcol{site}"].update(visible=True)
83
+ main_window["main_col"].update(visible=False)
84
+ main_window["scrape_col"].update(visible=True)
85
+ udemy.scraped_data = scraper.get_scraped_courses(create_scraping_thread)
86
+ main_window["scrape_col"].update(visible=False)
87
+ main_window["output_col"].update(visible=True)
88
+ # ------------------------------------------
89
+ udemy.start_enrolling()
90
+ main_window["output_col"].Update(visible=False)
91
+
92
+ main_window["done_col"].update(visible=True)
93
+
94
+ main_window["se_c"].update(
95
+ value=f"Successfully Enrolled: {udemy.successfully_enrolled_c}"
96
+ )
97
+ main_window["as_c"].update(
98
+ value=f"Amount Saved: {round(udemy.amount_saved_c,2)} {udemy.currency.upper()}"
99
+ )
100
+ main_window["ae_c"].update(
101
+ value=f"Already Enrolled: {udemy.already_enrolled_c}"
102
+ )
103
+ main_window["e_c"].update(value=f"Expired Courses: {udemy.expired_c}")
104
+ main_window["ex_c"].update(value=f"Excluded Courses: {udemy.excluded_c}")
105
+
106
+ except Exception:
107
+ e = traceback.format_exc()
108
+ main_window.write_event_value(
109
+ "Error",
110
+ f"{e}\n\nVersion:{VERSION}\nLink:{getattr(udemy, 'link', 'None')}\nTitle:{getattr(udemy, 'title','None')}|:|Error g100",
111
+ )
112
+
113
+
114
+ #################################
115
+ udemy = Udemy("gui")
116
+ udemy.load_settings()
117
+ login_title, main_title = udemy.check_for_update()
118
+
119
+
120
+ menu = [["Help", ["Support", "Github", "Discord"]]]
121
+
122
+ login_error = False
123
+
124
+ try:
125
+ if udemy.settings["stay_logged_in"]["auto"]:
126
+ udemy.fetch_cookies()
127
+
128
+ elif udemy.settings["stay_logged_in"]["manual"]:
129
+ udemy.manual_login(udemy.settings["email"], udemy.settings["password"])
130
+ else:
131
+ raise LoginException("No Saved Login Found")
132
+ udemy.get_session_info()
133
+ except LoginException:
134
+ login_error = True
135
+ # if (
136
+ # not udemy.settings["stay_logged_in"]["auto"]
137
+ # and not udemy.settings["stay_logged_in"]["manual"]
138
+ # ) or login_error:
139
+ if login_error:
140
+ c1 = [
141
+ [
142
+ sg.Button(key="a_login", image_data=auto_login),
143
+ sg.T(""),
144
+ sg.B(key="m_login", image_data=manual_login_),
145
+ ],
146
+ [
147
+ sg.Checkbox(
148
+ "Stay logged-in",
149
+ default=udemy.settings["stay_logged_in"]["auto"],
150
+ key="sli_a",
151
+ )
152
+ ],
153
+ ]
154
+ c2 = [
155
+ [
156
+ sg.T("Email"),
157
+ sg.InputText(
158
+ default_text=udemy.settings["email"],
159
+ key="email",
160
+ size=(20, 1),
161
+ pad=(5, 5),
162
+ ),
163
+ ],
164
+ [
165
+ sg.T("Password"),
166
+ sg.InputText(
167
+ default_text=udemy.settings["password"],
168
+ key="password",
169
+ size=(20, 1),
170
+ pad=(5, 5),
171
+ password_char="*",
172
+ ),
173
+ ],
174
+ [
175
+ sg.Checkbox(
176
+ "Stay logged-in",
177
+ default=udemy.settings["stay_logged_in"]["manual"],
178
+ key="sli_m",
179
+ )
180
+ ],
181
+ [
182
+ sg.B(key="Back", image_data=back),
183
+ sg.T(" "),
184
+ sg.B(key="Login", image_data=login),
185
+ ],
186
+ ]
187
+
188
+ login_layout = [
189
+ [sg.Menu(menu)],
190
+ [sg.Column(c1, key="col1"), sg.Column(c2, visible=False, key="col2")],
191
+ ]
192
+
193
+ login_window = sg.Window(login_title, login_layout, finalize=True)
194
+ login_window.bind("a", "a_login")
195
+ login_window.bind("m", "m_login")
196
+ while True:
197
+ event, values = login_window.read()
198
+
199
+ if event in (None,):
200
+ login_window.close()
201
+ sys.exit()
202
+
203
+ elif event == "a_login" and not login_window["a_login"].Disabled:
204
+ login_window["a_login"].update(disabled=True)
205
+ login_window.refresh()
206
+ try:
207
+ udemy.fetch_cookies()
208
+ try:
209
+ udemy.get_session_info()
210
+ udemy.settings["stay_logged_in"]["auto"] = values["sli_a"]
211
+ udemy.save_settings()
212
+ login_window.close()
213
+ break
214
+ except Exception:
215
+ e = traceback.format_exc()
216
+ print(e)
217
+ sg.popup_auto_close(
218
+ "Make sure you are logged in to udemy.com in chrome browser",
219
+ title="Error",
220
+ auto_close_duration=3,
221
+ no_titlebar=True,
222
+ )
223
+
224
+ except Exception:
225
+ e = traceback.format_exc()
226
+ sg.popup_scrolled(e, title=f"Unknown Error {VERSION}")
227
+
228
+ login_window["a_login"].update(disabled=False)
229
+ elif event == "m_login":
230
+ login_window["col1"].update(visible=False)
231
+ login_window["col2"].update(visible=True)
232
+
233
+ login_window["email"].update(value=udemy.settings["email"])
234
+ login_window["password"].update(value=udemy.settings["password"])
235
+
236
+ elif event == "Github":
237
+ web(LINKS["github"])
238
+
239
+ elif event == "Support":
240
+ web(LINKS["support"])
241
+
242
+ elif event == "Discord":
243
+ web(LINKS["discord"])
244
+
245
+ elif event == "Back":
246
+ login_window["col1"].update(visible=True)
247
+ login_window["col2"].update(visible=False)
248
+
249
+ elif event == "Login":
250
+ udemy.settings["email"] = values["email"]
251
+ udemy.settings["password"] = values["password"]
252
+ try:
253
+ try:
254
+ udemy.manual_login(
255
+ udemy.settings["email"], udemy.settings["password"]
256
+ )
257
+ udemy.get_session_info()
258
+ udemy.settings["stay_logged_in"]["manual"] = values["sli_m"]
259
+ udemy.save_settings()
260
+ login_window.close()
261
+ break
262
+ except LoginException as e:
263
+ sg.popup_auto_close(
264
+ e,
265
+ title="Error",
266
+ auto_close_duration=3,
267
+ no_titlebar=True,
268
+ )
269
+ except Exception:
270
+ e = traceback.format_exc()
271
+ sg.popup_scrolled(e, title=f"Unknown Error {VERSION}")
272
+
273
+ checkbox_lo = []
274
+ for key in udemy.settings["sites"]:
275
+ checkbox_lo.append(
276
+ [sg.Checkbox(key, key=key, default=udemy.settings["sites"][key], size=(18, 1))]
277
+ )
278
+
279
+ categories_lo = []
280
+ categories_k = list(udemy.settings["categories"].keys())
281
+ categories_v = list(udemy.settings["categories"].values())
282
+ for index, _ in enumerate(udemy.settings["categories"]):
283
+ if index % 3 == 0:
284
+ try:
285
+ categories_lo.append(
286
+ [
287
+ sg.Checkbox(
288
+ categories_k[index],
289
+ default=categories_v[index],
290
+ key=categories_k[index],
291
+ size=(18, 1),
292
+ ),
293
+ sg.Checkbox(
294
+ categories_k[index + 1],
295
+ default=categories_v[index + 1],
296
+ key=categories_k[index + 1],
297
+ size=(18, 1),
298
+ ),
299
+ sg.Checkbox(
300
+ categories_k[index + 2],
301
+ default=categories_v[index + 2],
302
+ key=categories_k[index + 2],
303
+ size=(18, 1),
304
+ ),
305
+ ]
306
+ )
307
+ except IndexError:
308
+ categories_lo.append(
309
+ [
310
+ sg.Checkbox(
311
+ categories_k[index],
312
+ default=categories_v[index],
313
+ key=categories_k[index],
314
+ size=(18, 1),
315
+ )
316
+ ]
317
+ )
318
+
319
+ languages_lo = []
320
+ languages_k = list(udemy.settings["languages"].keys())
321
+ languages_v = list(udemy.settings["languages"].values())
322
+ for index, _ in enumerate(udemy.settings["languages"]):
323
+ if index % 3 == 0:
324
+ try:
325
+ languages_lo.append(
326
+ [
327
+ sg.Checkbox(
328
+ languages_k[index],
329
+ default=languages_v[index],
330
+ key=languages_k[index],
331
+ size=(10, 1),
332
+ ),
333
+ sg.Checkbox(
334
+ languages_k[index + 1],
335
+ default=languages_v[index + 1],
336
+ key=languages_k[index + 1],
337
+ size=(10, 1),
338
+ ),
339
+ sg.Checkbox(
340
+ languages_k[index + 2],
341
+ default=languages_v[index + 2],
342
+ key=languages_k[index + 2],
343
+ size=(10, 1),
344
+ ),
345
+ ]
346
+ )
347
+
348
+ except IndexError:
349
+ languages_lo.append(
350
+ [
351
+ sg.Checkbox(
352
+ languages_k[index],
353
+ default=languages_v[index],
354
+ key=languages_k[index],
355
+ size=(10, 1),
356
+ ),
357
+ sg.Checkbox(
358
+ languages_k[index + 1],
359
+ default=languages_v[index + 1],
360
+ key=languages_k[index + 1],
361
+ size=(10, 1),
362
+ ),
363
+ ]
364
+ )
365
+
366
+ main_tab = [
367
+ [
368
+ sg.Frame(
369
+ "Websites",
370
+ checkbox_lo,
371
+ "#4deeea",
372
+ border_width=4,
373
+ title_location="n",
374
+ key="fcb",
375
+ ),
376
+ sg.Frame(
377
+ "Language",
378
+ languages_lo,
379
+ "#4deeea",
380
+ border_width=4,
381
+ title_location="n",
382
+ key="fl",
383
+ ),
384
+ ],
385
+ [
386
+ sg.Frame(
387
+ "Category",
388
+ categories_lo,
389
+ "#4deeea",
390
+ border_width=4,
391
+ title_location="n",
392
+ key="fc",
393
+ )
394
+ ],
395
+ ]
396
+
397
+ instructor_ex_lo = [
398
+ [
399
+ sg.Multiline(
400
+ default_text=udemy.instructor_exclude,
401
+ key="instructor_exclude",
402
+ size=(15, 10),
403
+ )
404
+ ],
405
+ [sg.Text("Paste instructor(s)\nusername in new lines")],
406
+ ]
407
+ title_ex_lo = [
408
+ [
409
+ sg.Multiline(
410
+ default_text=udemy.title_exclude, key="title_exclude", size=(20, 10)
411
+ )
412
+ ],
413
+ [sg.Text("Keywords in new lines\nNot cAsE sensitive")],
414
+ ]
415
+
416
+ rating_lo = [
417
+ [
418
+ sg.Spin(
419
+ [i * 0.5 for i in range(11)],
420
+ initial_value=udemy.settings["min_rating"],
421
+ key="min_rating",
422
+ ),
423
+ sg.Text("0.0 <-> 5.0"),
424
+ ]
425
+ ]
426
+
427
+ courses_last_updated_lo = [
428
+ [
429
+ sg.Text("Past"),
430
+ sg.Spin(
431
+ [i for i in range(1, 48)],
432
+ initial_value=udemy.settings["course_update_threshold_months"],
433
+ key="course_update_threshold_months",
434
+ ),
435
+ sg.Text("Month(s)"),
436
+ ]
437
+ ]
438
+
439
+
440
+ advanced_tab = [
441
+ [
442
+ sg.Frame(
443
+ "Exclude Instructor",
444
+ instructor_ex_lo,
445
+ "#4deeea",
446
+ border_width=4,
447
+ title_location="n",
448
+ ),
449
+ sg.Frame(
450
+ "Title Keyword Exclusion",
451
+ title_ex_lo,
452
+ "#4deeea",
453
+ border_width=4,
454
+ title_location="n",
455
+ ),
456
+ ],
457
+ [
458
+ sg.Frame(
459
+ "Minimum Rating",
460
+ rating_lo,
461
+ "#4deeea",
462
+ border_width=4,
463
+ title_location="n",
464
+ key="f_min_rating",
465
+ font=25,
466
+ ),
467
+ sg.Frame(
468
+ "Course Last Updated",
469
+ courses_last_updated_lo,
470
+ "#4deeea",
471
+ border_width=4,
472
+ title_location="n",
473
+ key="f_course_last_updated",
474
+ font=25,
475
+ ),
476
+ ],
477
+ [
478
+ sg.Checkbox(
479
+ "Save enrolled courses in txt",
480
+ key="save_txt",
481
+ default=udemy.settings["save_txt"],
482
+ )
483
+ ],
484
+ [
485
+ sg.Checkbox(
486
+ "Enrol in Discounted courses only",
487
+ key="discounted_only",
488
+ default=udemy.settings["discounted_only"],
489
+ )
490
+ ],
491
+ ]
492
+
493
+
494
+ scrape_col = []
495
+ for key in udemy.settings["sites"]:
496
+ scrape_col.append(
497
+ [
498
+ sg.pin(
499
+ sg.Column(
500
+ [
501
+ [
502
+ sg.Text(key, size=(12, 1)),
503
+ sg.ProgressBar(
504
+ 3,
505
+ orientation="h",
506
+ key=f"p{key}",
507
+ bar_color=("#1c6fba", "#000000"),
508
+ border_width=1,
509
+ size=(20, 20),
510
+ ),
511
+ sg.Image(data=check_mark, visible=False, key=f"i{key}"),
512
+ ]
513
+ ],
514
+ key=f"pcol{key}",
515
+ visible=False,
516
+ )
517
+ )
518
+ ]
519
+ )
520
+
521
+ output_col = [
522
+ [sg.Text("Output")],
523
+ [sg.Multiline(size=(69, 12), key="out", autoscroll=False, disabled=True)],
524
+ # [
525
+ # sg.ProgressBar(
526
+ # 3,
527
+ # orientation="h",
528
+ # key="pout",
529
+ # bar_color=("#1c6fba", "#000000"),
530
+ # border_width=1,
531
+ # size=(46, 20),
532
+ # )
533
+ # ],
534
+ ]
535
+
536
+ done_col = [
537
+ [sg.Text(" Stats", text_color="#FFD700")],
538
+ [
539
+ sg.Text(
540
+ "Successfully Enrolled: ",
541
+ key="se_c",
542
+ text_color="#7CFC00",
543
+ )
544
+ ],
545
+ [
546
+ sg.Text(
547
+ "Amount Saved: $ ",
548
+ key="as_c",
549
+ text_color="#00FA9A",
550
+ )
551
+ ],
552
+ [sg.Text("Already Enrolled: ", key="ae_c", text_color="#00FFFF")],
553
+ [sg.Text("Expired Courses: ", key="e_c", text_color="#FF0000")],
554
+ [sg.Text("Excluded Courses: ", key="ex_c", text_color="#FF4500")],
555
+ ]
556
+
557
+ main_col = [
558
+ [
559
+ sg.TabGroup(
560
+ [[sg.Tab("Main", main_tab), sg.Tab("Advanced", advanced_tab)]],
561
+ border_width=2,
562
+ )
563
+ ],
564
+ [
565
+ sg.Button(
566
+ key="Start",
567
+ tooltip="Once started will not stop until completed",
568
+ image_data=start,
569
+ )
570
+ ],
571
+ ]
572
+
573
+ if (
574
+ udemy.settings["stay_logged_in"]["auto"]
575
+ or udemy.settings["stay_logged_in"]["manual"]
576
+ ):
577
+ logout_btn_lo = sg.Button(key="Logout", image_data=logout)
578
+ else:
579
+ logout_btn_lo = sg.Button(key="Logout", image_data=logout, visible=False)
580
+
581
+ main_lo = [
582
+ [
583
+ sg.Menu(
584
+ menu,
585
+ key="mn",
586
+ )
587
+ ],
588
+ [sg.Text(f"Logged in as: {udemy.display_name}", key="user_t"), logout_btn_lo],
589
+ [
590
+ sg.pin(sg.Column(main_col, key="main_col")),
591
+ sg.pin(sg.Column(output_col, key="output_col", visible=False)),
592
+ sg.pin(sg.Column(scrape_col, key="scrape_col", visible=False)),
593
+ sg.pin(sg.Column(done_col, key="done_col", visible=False)),
594
+ ],
595
+ [sg.Button(key="Exit", image_data=exit_)],
596
+ ]
597
+
598
+ # ,sg.Button(key='Dummy',image_data=back)
599
+
600
+ global main_window
601
+
602
+ # position windows in center
603
+ main_window = sg.Window(
604
+ main_title,
605
+ main_lo,
606
+ finalize=True,
607
+ )
608
+ threading.Thread(target=update_enrolled_courses, daemon=True).start()
609
+ while True:
610
+ event, values = main_window.read()
611
+ if event == "Dummy":
612
+ print(values)
613
+
614
+ if event in (None, "Exit"):
615
+ break
616
+
617
+ elif event == "Logout":
618
+ (
619
+ udemy.settings["stay_logged_in"]["auto"],
620
+ udemy.settings["stay_logged_in"]["manual"],
621
+ ) = (
622
+ False,
623
+ False,
624
+ )
625
+ udemy.save_settings()
626
+ break
627
+
628
+ elif event == "Support":
629
+ web(LINKS["support"])
630
+
631
+ elif event == "Github":
632
+ web(LINKS["github"])
633
+
634
+ elif event == "Discord":
635
+ web(LINKS["discord"])
636
+
637
+ elif event == "Start" and main_window["main_col"].visible:
638
+ # for key in udemy.settings["languages"]:
639
+ # udemy.settings["languages"][key] = values[key]
640
+ # for key in udemy.settings["categories"]:
641
+ # udemy.settings["categories"][key] = values[key]
642
+ # for key in udemy.settings["sites"]:
643
+ # udemy.settings["sites"][key] = values[key]
644
+ for setting in ["languages", "categories", "sites"]:
645
+ for key in udemy.settings[setting]:
646
+ udemy.settings[setting][key] = values[key]
647
+
648
+ udemy.settings["instructor_exclude"] = str(values["instructor_exclude"]).split()
649
+ udemy.settings["title_exclude"] = list(
650
+ filter(None, values["title_exclude"].split("\n"))
651
+ )
652
+ udemy.settings["min_rating"] = float(values["min_rating"])
653
+ udemy.settings["course_update_threshold_months"] = int(
654
+ values["course_update_threshold_months"]
655
+ )
656
+ udemy.settings["save_txt"] = values["save_txt"]
657
+ udemy.settings["discounted_only"] = values["discounted_only"]
658
+ udemy.save_settings()
659
+
660
+ user_dumb = udemy.is_user_dumb()
661
+ if user_dumb:
662
+ sg.popup_auto_close(
663
+ "What do you even expect to happen!",
664
+ auto_close_duration=5,
665
+ no_titlebar=True,
666
+ )
667
+ continue
668
+ scraper = Scraper(udemy.sites)
669
+ udemy.window = main_window
670
+ threading.Thread(target=scrape, daemon=True).start()
671
+
672
+ elif event == "Error":
673
+ msg = values["Error"].split("|:|")
674
+ e = msg[0]
675
+ title = msg[1]
676
+ sg.popup_scrolled(e, title=title)
677
+ elif event == "Update-Menu":
678
+ menu = values["Update-Menu"]
679
+ main_window["mn"].update(menu)
680
+ main_window.close()
images.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ auto_login = b"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"
2
+ manual_login_ = b"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"
3
+ login = b"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"
4
+ back = b"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"
5
+ start = b"iVBORw0KGgoAAAANSUhEUgAAAEUAAAAaCAYAAADhVZELAAAGG0lEQVRYheWZe0xTVxzHv/fBvaVAe9uiwMBSoIA81Ikzc+oysymig/l2E50gk80tMWYz0yUasy2bLuqymGyL2XRCjJJAxEmIqMw4DS6bc2abikB5tShPobctYG8ft/sD6Hi3kA2MfpIm95z7+/2+v/5yzrnn3Eu4XC6MxJ1HDxMLTbq1F8x1ywx2SzjvFDiby8mO6PCYwhCUwFEsH87I6lfIon5cw8UURLFczUj2xHBFqRNMEXubyw4U8FWv/6/ZTh6udVxM/oGQF3epGZlh8M0hRckzVqS/03DpmNXl9J2wFCcJKUl3nVQvT0+TRxX17yf7Nw623Pgow1By6mkoCAB0iw6/tfVFZ4+1387u3+8eKXnGivQMQ8mpSclukiEAsThy1bIlAZpLQG9R6gRTxKzK3LtPywgZDjnJ8n9P3xwf4uPfRALA3uayA09zQQDAJArcodabuwGAuN3dlphUdfK2N45ZykRsVsYjURIIlqBQZzPjDF+Fw2030SXaURyxCskyzagxbnW3YJ7utLstJ1k0JLwNCUmjxd4FTfn3cOLfxT+KkeNeXNaAGDbRCYPdgp87G3Cw9QbqbeZxaQ+GBmmvjn9LQxaadGtHjdTL16Ev4+i0JZjvFwoZxYIlaUyXKLEneB4uRK4BBcKbMEN4QxELCUkDAIJ8/LBMFuHRhyEpaFkOW1UzcCNmIzSMbFzag3FA9Cm16JPpyxbDYk/GCopFtmomAKDc2o4MfQlMooDPQxZiHReL5/1C8KosElkNF+FL9PzB1+RR+DJ0EQBgd+M1FPI6AIDgcg6IvUWZOKCdqUxAsbl22DzyjZXY01QGCUlhx5Q52KqaAY6SIEuZOC7t4Thvrk2la2y81pMhAQIE0TMSrKID9+0WtDut2PngKs70CpZb29Hq6Hb7tDseDbjW281D4iZKVEiSBgEALlv0eCUgHMtlkZhKSwfE6qNTtLvjHG79HVtVMwAAUwbZe6M9EjUCryV5p8B5MuxwWpHbcRcAkCQNQm18Ngo0aVjkH4Zicy0KTTpU23ivhfvI7B0l3aIdOx5cAQDQBImNirhR/RiCwnou1t2+Z+0Ys/ZI1NnMEbS3Z5ltDaWos5mwc8pzCKAYrJBrsUKuxX6bBZsNJSjrejAmcRokNiimAwBKLXpUCUbc6m5BkjQIGcoEfNX2xxCfLFUislQDp1uFtQM5HXfGpD0aFtEmIz2b9eCEC/tbfkN4+XfINlzEFUvPkSGMCUBRxEqE0H5jEk+VR2IKLQUAFJl6zmbnTNUAgHiJCnOlwR5jVAs8XtCdhlm0jUnbEyRDUIIno5QADXLUKchRp0DLcsg1lmNp7Rl82HgVAOBPMUiVR41JOEOR4L4+rl4K26z38UnIAnffFmXCEJ98YyWiy4/jkrkeAKBlOcRLVGPS9cRUWtpCchTrcTFwuFxIV8QhXRGHT4MXIJZVIIiWIoKRu22cLtFr4SBaiqUe9hTruVhICGpAX99Cu6/5urtvf8hCr3W9IZiWNtMxrKKy1dEdNJrhT516FPI6rOaikSKLQMqgvUSTvRNnTTqvhTcp4kETPTN3X9N15Bkr3Pc+mDoH7wY+CxnFYg0Xg1+7Gof433rUinOmaqyQa/GS/zQkB4TjkkXvtf5oRLOKKjIlQFPijfEGfTGyDRdxrfM+eKcVVtGBGoHHtw//xHxdHoxOj7PQTUbv1HC6RPzQcQd6u9n9O9Fv0cwcZgr18XHzLxB7D7Of/YejZaU8upCosRojp1ecqAbGuSV9gmAJylofnx1GRrJc7TouJn+yE3oc2B44+4iK9m0nXC4XDDazemZlbnm36Bjbc/UJIpDybSuPy4zhKAlPAoCakRlOqpenE4D3j5AnCIaghAJN2mqOkvBAv9eRafKoom/CFm972grDEJSQo055c4F/aFlf35AX16WW+uRN+vN5RqegnPAMJ5hAyretQJO2un9BgBE+cTTZO0MOtd7cffThX+85IPpMWJYTBEtQ1u2Bs4/sCpr7Rd+U6c+wRemj0d75TKlFn3zeXJtaI/DaeptZYxZt8hEdHlNkJGPSMLL6aFZRtVIeXbg4QF2qon3bR7L/BxAtaalv+EgeAAAAAElFTkSuQmCC"
6
+ exit_ = b"iVBORw0KGgoAAAANSUhEUgAAAC4AAAAaCAYAAADIUm6MAAAEP0lEQVRYhc2YX0xTVxzHv+f+6W0pHcgfZyiBKgZfZPAAsuALZIvp4jTAqJoQF5NFwahxPvioIfwJEZfgA+NFUf6oi5EYzFRMNMxpRkKmWRUzm3ZEtsCAkEihlN7b23vPHgqFspZ2rFg/yUnO+Z1ffvdzTm7OPbmEUopQ0Pn5ZO/jx1/7XrzYo9jthdTp/DhkYizheYls2jTJbt06zO3adZ8vKeljUlImQ6WS1eLU40kUu7rqvQ8e1ECSEjZcdi14XhSqqxuEqqrvCM97V04FiSsjIwULDQ296sREznuXXANm2zarvrl5D5OcPL0UC4grY2O57jNnfqFzc2lxM1wDkp7+l/7Chc9Zo9EBAAzgfz0Wzp//8UOVBgA6PZ3laWnpporCAYviYmdnozo+nhtftcgoNtun0u3bZwGAKJOT2a4jR0agqmy8xaKBJCVNG27cyOTkp08toaSZjAwYrl0LW0B+/hzqxASEffsAAGJPD6Tr1wEAXFER9I2NAACf3Q73qVP46O5dEK0WPpsN7tOnkdDUBL6wcE1JxeHA/MmTQTE6O5suDw6WM75Xr0r/05JXIHV1gbpcAAChshIkMdHfP3zY/xBKIba3r7d8WBSr9TNOGR3dGSnR++QJxKtXg4OSBOpyQezuhu7ECRC9HpqqKig2G7gdOwAA8sAAlDdvQtb0XLwIjyAAAPiSEuhqa/3xy5chP3vmT5Ll0OIjI/kcnZmJ/EUURdCpqdCLuncPmr17wZpMEMrLoS7mUVGEeOVK2JLU6Vzuz84G9cM9KyD+9u0nHGRZG8lbYzZDYzYHxurcHFwWy+JAhae9HYktLSA6HViTCQAg3bwJ+u5dpNLrw+vVMbGoo7x8CWVsLDCmqgpvf38sSoeFiybJ++gRPG1ty4FV9xtu926wmZmBMWEYCBYLxI6O2FiGILodVxRAFJebJC3P8Tx0x44BAKjLBcXhAABoKirAGI0xF14iOnFBAElLC24Gg3/q4EEwW7YAAKTe3sAuE56H9vjxDZEmqanjHElOnop019aUlUFTVhYUk4eG4Glrg3DgAABAdToh9fUBogjf8DC4vDzwRUXgiovhGxqKqTiTkfEHw27f/tt6C+hqakAWz2Lp1i3/awRA7OwM5GhrawGe/3+mq2AyM+1E6u//xtPaGv7A/QBJqK//kuFLS3+AICzEWyZq9HonV1AwwBCtdkGorGyNt0+06I4ePUsEwcMAgFBdXc9kZf0eb6lIsPn5P/FmcweweBwSnvcm1NWVk82b/4yvWniY7OzX+nPnviKEUGDFOc4ajY7ES5dK2NzcX+OnFxo2L+9nfVPTF8RgmFmK/fv3hKJw3jt3vhV7euogSfr3brkCkpLyt3DoULNm//7vl3Y6MBf2h5DbneSzWsvkwcEKdWoqWx0d3UldrtQNNRUEN2syvWZzcqxcYeFDrrj4PuG4kJfyfwAG+6Z6AHIPmAAAAABJRU5ErkJggg=="
7
+ icon = b"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"
8
+ check_mark = b"iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABhUlEQVQ4T2NkwAEsHCwciluKp8goymoyMjIygZT9////35P7T270VHfnnDx4cj82rYzogiCD+pdO2AszBJeFIMMLowucTxw4cQBZDYqBnXM7l9l7OkTiMgSb+MEdB5eXJ5VFweTgBnbO7Vpm72lPkmEwQw5uP7C8PLkcbCjYQJA3JyybiDVMiHVtQVS+I8j7YAOPPz3xl1CYYTP46LNjDNZSVmApUJhaSlswM5rbmztOXD5pH7EuganrOt3D8OvfbwZHWXu4oSBXMq4+suaKrJKsNikGdp3pZfj19xcDKxMrQ7lpCVzr43uPrjKS6l1chsG8zXji2cn/6K5DDhtkOXyGwZMNuoEgw/Y/PojhHWIMAycbbF5G1lxmWszQfaYPa5ih+wwU04yrj6y+IqskhxEpMENhmtAjAFskgiMFX6Im1psww8HJhlDC7j83iaHQKI9gqoInbJpkPZChVC0cYP6havEFM9TS0dK+b0n/PkKFBVEFLHLI464CHl/vrenNQS+pYXoB5qvqn2cyjBsAAAAASUVORK5CYII="
9
+ logout = b"iVBORw0KGgoAAAANSUhEUgAAAFQAAAAVCAYAAADYb8kIAAAE6UlEQVRYhe2Ya0iUWRjHf/M6tV4qUXGbhIomzVoyzTSVxHSnsHC3oqCLyZIJ2hcRFqwPW21Bl8VbN1PaFjIqtaiItVhro0zFWGtN7YIkmpfQpWg1K9Oaxv3w7Kw644y6LQ1T+4eXl/Oe5znnf/7v8zzncFR9fX2YobfXiWvXvuH27RhqanT09jqbG31iUKt7cXP7A2/v39HpjjNv3i+o1W9NzVSDBDUYFIqLEyko+J7OTs2H5Gt38PB4TErKRubO/XXg535BDQaFQ4d+4urVeFvws1usXJlOfPxmY1P5pyM3N+d/Mf8Fzp9P5cSJXcamCFpSEktxcZLNSNk7zpz5jsrKrwAUenqcOXp0v6052T2OHUvDYFAUysrW0tXlaWagKFBUBKmpNmA3AJ6eMG2a5f6ZMyE7G86ehb17wd19aDvjegY+cXGWx3V1hZMnYenSkfF4/HgWVVVLFCoqVg6/Khti40ZYs8Zyf1ISqNWQmwuzZsGyZdbHq6uDzEx5ysst2716BZcvi/1IeABUVn6tpqlpjnWrIbBgAaxfDxMnQksLHDkiE48dCykpEBQEzn8fXXt6YMMGCAgY2mfKFDh8GI4flyjz94ddu2D/fvDykrlUKomozEwoKRnMxWCAFy+gsVGi8Plz69yfPBk8RlycCLVnD1RXy4/p6ZH26tWg10N4+PA8AKqrdQqdnZ+PSszJk6UMPHsGOTng5ARbt4qYixZBRARs3y7pB5CeLmloyccaSktlQXfvwpYtUFVlbnPxoqR9ZibU1MClS9bHjIjoT/nQUCgshOZmSEyEhARwc5Of+XbAmX0kPAA6Oyeq0es/s87ABP7+4OAAp05JhDk6wqZN4Ovbb6NWw5gxI/OxFlHNzfLu6oIHD8z73d0hNlYiytERHj6UKCoshPp62LbN3Keurl/0+noR6sAByMiA6Gi4cEFsJk0aOQ8jXr+eoLbcawFqCy56PVy5Ajod7N4tNaioCG7dguXLLfsY4eAgb1fXkXOJjgaNBnbuhAkTIDkZwsLAxUWEGQqmKQ8wfryUi9HObwpPz2aFceP+tGqk0YhIOp2kSFUVvHsnkREZCTEx0NEhNUyjgRkzZHc8eFCKvkpl3aejA/r6pE4tXAirVg2e/+VLmD4doqJAqx3cZ4zumBiJ0IYGKUmtrXDnzvDr0Wql1icnQ1OTZFBUFMyfb+5njUe/oC0OO2Jj59Pa+oVZp0oF69aBh4cIGRoqA+XnQ1ubREJkJDx9CllZ8uddXESUkBCpVYsXi09RkWWfN2+kXgUFgZ+fpOOcORLZjx5Jf3CwPPfvi1hGNDTInAEBIkJ3N1y/LmNptXDzpmxaltbT1SWbzezZsgmVl0s7PBwqKmDJEqmb9+5Z52FEcPAlVd+NG2tJTy8YWvJRIjtbdvCsLFlIYqKIahp1HysyMkIVwsLOo9E0/CcDOjnJzu3hIcchHx8p/J8Cpk69i6/vb3LbVFa2mrS00+89aGCgHIC9vKTm1NRAXp4clz5mKIqeffuC0Wqr+6/v8vJ+4Ny5LbZlZqdISPiWFSv2gekFc37+DgoKtgMqG1GzPwwQE0wFBaitjSI7+0fa270/NDe7go/PLeLjN+PnVzLws7mgILf3tbVfUlq6lvZ2bxobA+jufo8T70cAZ+fnuLu3ERLyM4GBl/HzK0GlMhPvLzg09RVrdeiVAAAAAElFTkSuQmCC"
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ FreeSimpleGUI
2
+ rookiepy
3
+ cloudscraper
4
+ bs4
5
+ requests
6
+ html5lib
7
+ pyopenssl
8
+ colorama
9
+ tqdm