Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
#1
by
imseldrith
- opened
- .github/ISSUE_TEMPLATE/bug-form.yml +58 -0
- .github/ISSUE_TEMPLATE/config.yml +11 -0
- .github/ISSUE_TEMPLATE/feature_request.yml +45 -0
- .github/workflows/build.yml +47 -0
- .github/workflows/del-old.yml +15 -0
- .gitignore +14 -0
- CHANGELOG.md +97 -0
- CODE_OF_CONDUCT.md +128 -0
- CONTRIBUTING.md +12 -0
- LICENSE +21 -0
- base.py +1065 -0
- cli.py +115 -0
- colors.py +28 -0
- default-duce-cli-settings.json +64 -0
- default-duce-gui-settings.json +60 -0
- extra/DUCE-LOGO.ico +0 -0
- extra/DUCE-LOGO.png +0 -0
- extra/duce-gui-main.png +0 -0
- extra/promo.gif +0 -0
- gui.py +680 -0
- images.py +9 -0
- requirements.txt +9 -0
.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"iVBORw0KGgoAAAANSUhEUgAAAH0AAAAZCAYAAAAc5SFpAAAIC0lEQVRoge2ae2xT1x3HP/f6HceJyTshhjw7mrKWDWjoizBAlAQ6oEDbISH2xygUFaZW24r26FqJjW7dxuAP1nWVqFRV0EChJBTUAkmHuo0C471QkuG84yTGIbbjxO+7P+I4zsPBeY0N+SNd6Z5zvvbvd36/e8495+gKkiQRjhuujpnlNuOa046G4iZP13Sr36V3S35V2B9EuScoBdEVL6o6DQpdXbEu65Pv6nIOZivjb4XTC8Mlvd5ty95hPrfzqP3W85PqbZTJQlqhyy39ZUrhTzIVuobBjUOS/rG1Zt0rrX99zyn5NP81F6NMCjGC3PFOxqJ1S3VZZaH1Ymhht+XS9pdMFR9GE35/0C15tRuaPzvyQeeNjaH1wZH+sbVm3Uumig/viXdRJhUB/AcMJcXf0Ro+h0DS69227KdqS/8VHeH3L3GisvNvOc8VpMq1JhFgh/nczmjC729sfrd+j+XyawBCldMys6j24LXhhDNViVRkrwmWNzaf4qi9fycwW53CiaxVwfKGps840VUXLC/SGthvKAHgLfN5/mC5OOx/H7b9m80tp4Ntf89+njyVnoquRl5oOk6hJo3y6SsA2NJSwSFbTa89fQFvpz0FwHxjKV+774za9jZTJQes1SMGLFL9Q6oEXk2czWMx6ahFOTddHbzbcY0j9oG7pwJVAj9Kms1jmnRUopwGt41Dthr+cuc6LsmHDAHTjBcB2Gu5whvms6OKVzjkiJ6LueuyxHKbcU1Y1SCKdVkDyksHlQezOi4/eL8m5H4wq3S5PKJOjtSNiIjU9kQxT5POp9NX8kxcDklyDbGigtmaVP48dTE/TZob1M1Rp/Lp9JUs1+WQGNAVqBN5PWUef8pYGJGtscbLi19R6WhaIp7pbloc6Y8Waw3IQxb8JbFZYbUaQT7gIclT6cM6KggCb6bMi9SNuzIa2xOBAOxJX0CMqKDObWNh7SFm1nzAUVvvCP9h4rd4WJ0EwO/T56MVFbR7u3m67jCGm+/xW/MFAJbrcpirSb27vXHE66SjfrlY67blRSJu83YTJ1PxREw6ADmKePJVU2jzdg+rL9ZloRUVdPncXHWaAVg7woh7PCaDp2Onj7YPE2J7vMzTpJOljANgl+Ui110W2n3dbG/7Eo/kQxAEvhf/DQpUCTyoSgDg3Y5rXHKacUk+9nRcotRazSFrDTpRGZHNscar1m3NE61+lz4ScUVXIwDFumwASgIjqdLROKy+b0r9oruJ4/Y6AFbG5SIiDNH2Jeb15EJkw7SPltHYnggKAokEuBLoC4DF56TR0wXAQ6pE8pVThtW5JT8vmyrZYqqgIkw8QxlPvOo99mwx0rP0SkcjkiSxNPB0LQ1M7X0PQygJMjULtJkAnOpq4POuegBS5DEUaacO0b9/p4pat5V81RTW6x8cVSfGa3si0Mv6Q2j3uwe0dfpcQb/iZP2juDOgO5O9lvYZm4LXz5Mfvau98cSry++JE5WC6IpEbPO7ueBsI0MRyyKtgTmaVJo9XVSHrJj7WKnLRS70vvtPOxq57rJg8jiA4RdVXvz8ynwOgB8nzSFGlI+qI+OxPRFYQxKtF9UD2voeiA6fE4ffE6yPD0zjV523OdfdOip744lXskzTJsaLqs5IxFpRTpnNCMDO1CcRBYFyu5EYQTFEuzq+f5lwLW897TM2ka7QAr2vB40w1Mkyu5HzPa0kyzVkKGIHtPVI3uB9aAdD77sCAR2L7fFS5bIE7x8JLNgApogqDIG+VLk6qHb1D5C+hd3LpkpWNx4btc2R4jUSKXJNq5ir1N+MRKwR5JTbjUiSFFy0lNuNqEXZAN00hY65mjQA/JKET/IHL4BYUTFk69fHG+1nh62vcXXSHUjqc3EPkChTkyzTsEqXC4DZ20OL1zEu2+PhH90m6tw2AF5J+jYzVYkkytT8OvUJFIIMSZIotVZz3WWhJpD4zQkPU6hJQ4HIrDHuLMLFayRylfpq+aJYw4mzPab5dxOLgkCL18E/ne3M0aRi8jg439PG/JiB78nQ/fHCukNUuToAUAoiX+d/n1hRwdq4/OD0FMr5njbKbUaeicsZUN8jedl1+yI/Synk0Zg0buRvGND+9u0L+JHGZPuPaQvYlVYULNe6bTxe+1HYOITTbzN9wUeGEgwK3YADLYC9HVe56GwH4NXWMxw0LCNFHhM8cArlltsa1vZgwsVrJJbpsg+LK3S5pUD4LykG0TfFH7Mbh21fHdc7vTa47cGgQ+8KtTKw6CvSZqIPszXZYf4Kj+QbUr+74zJbWyq54jTj9Htx+D1c6GnjB80neb+zasy2RUFAJogh18ir4XD6sz0mSuqPcMxuxOLtodvv4XJPO1tbKnnT3D8iv+pppaT+E47ba+n0ufBIPkweB2W2W6yoL2O/NaKJ967xGg6VIHMWaTNPCpIksbH51IHoBxP3P1sTZv3mFymF2wVJkmjy2Kc9aSyt6pa82nvtWJTJIVGmNp/NeeGBeJmqUwTIVOga3slYtE4A/712LsrEoxRE176pS56Nl/Xu1IIH6Ut1WWW/S5u/OZr4+wulILr2pi9cPy8m/cu+uiHfyFU6Gpdsaj69v9PvShjyD1H+r0iUqc37pi55NjThEOZr2DavI32P5fJr++5UbfHiH3r6EuV/GpUgc7445Zu7tyXOeqtvSg9l2KT30epxZFQ6mpacdNQvr3Vb8xo8XVl2vzt+Uj2OMmp0otI6TRFbl6vUVy/TZR8u0maeTJCpLeH0/wGCIIyl39uhaAAAAABJRU5ErkJggg=="
|
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
|