Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub (#1)
Browse files- Upload folder using huggingface_hub (1dc2ed635218271810de58cdcff8d71e71a032e5)
- .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"iVBORw0KGgoAAAANSUhEUgAAAEMAAAAeCAYAAAB32qNaAAAF6klEQVRYheWZfUwTZxzHv71e3yhHSwsVkHcILogT5zJfh0FMJ9PBQoxjLJuJiTp1Y9M/pllmlhmnWzQmOGOWZQnLppsDEZElbjIhbHOiDsWoYEAoIFCgFK6Fvlzbu9sfQKGTKm0QnHySJs9zv9/vnu997+6553oCnufhjQamP6Xc3LLhkqU9s8M5FGPiGKWD5yReC54yxAKCURASOkpEtWZSseeyqPjiOLGi2Vu+YCIz2hzmuAOGa4fKBpvfeKJqpx8+m0oo+lSz5KNIEdX+3+BDZpSYmvJ2dVd/a+dZ2bRJnGYCBKTl64iMvLVU7Pnx24nxnQLjzb3b9ZWnnmUjAMDKu+SbOn8r/YFu2DJ+u/vKKDE15W3XV56aEXUzhADgTke9mpkuj7oIjJjR5jDHvawruvusXxETEUSI6cvxG5PnkHI9AQAHDNcOzUYjAMDMOZTHjHV7AEBQbzemrNIV355pUTMJCcJ5IyEvlig3t2yYaTEzjQucqMrSoSX/sHasmUxBnCgIVxPeBACcou9hV3e119wwMgAfqBdBGxgDjTAABtaKi0PtKDDehN5leSj3Q/UL0AZGI1QYgF7Witv2PnxlrEOtvdedp0vaDDkhQq2tB5lt5zz0tDnMWKkrAsOzAIBcRRKOhacDAHZ0VeKMuemxx1dhaVtP6hzmxMmYMVmSxEqURmchlBybgiIJCpuD5yOLikdOeznuOQYADBv8S8zrHrlRBIUoEYVXAmPwVsevqLQ8eOyYMeIg7FQtxFHjDb916xymRMLEMUq/9zABx8PTEUrKwPIc9vfWQNt6Fvt7a8DyHEJIGU5EZLhzj4atchtxuO8fLG/5GXkPLqCftUMoIPD5nOWTHjdfnYoIUu637jbnYBw5le8aKRI1UmUaAECpuRnH+28BAOrsBiRJgpGrmIcUqRqLpRr0slaskEcAAP60dOJwXy0A4L6Dxr6ev5EujwIPHnJCBAvnfOzYAYQIn2mWYUvX735pH+KcQaRflV5Ikard7au2bo/YFaseuYp5AIDnpSFodw66YzU2vUdusbkJxZO4z0e5ZTdggSQE2UEJKKTv+iMdADClZigJqbs9wNo9YsZxfZVQChPncPf7XcOxfFUqPtEs8ahLaylyzzHeuGM34pbdgHeUyTioWYFvBvxbKRCPT5k8Jo5xt4OFUo+YSjh2N/azdgyNu/SVI7FBzgG90+IRmywHDddBswySpWpsUib7XA9MsRm37X3u9rKAcI/YUtlY/w5jRCMzdrZfCggDABTS9VjYfBI/0vd8HrufteNLw3UAwKKRectX/DJDRpAIJ+UeP5mAxB3GiDrb8Nogm4rH+6pUpEjU2Ba8ABsVSQCABqYf1209aHWacc06PK+ky6OwN+RFPCcORopEjfkStdexH0UhXY+7dqNftQBAigUE4+sTJScoETlBnsuTfH0VTpsa8Z6+CmXRWVCTMuzTLME+jM0BNMtgZ1elu7+ruxrlMdlQCaXYHbIYu0MWe+yzz2VDP8tgsnDg8XHPZZTFZPlyOACAUKGsh1AQEtrnykfQ6KCxurUE3w3cRYdzEAzHoss5hJN0A9a0luAOM3bmmhw0VuvO4Hu6Hp3OIbh4DmaWwU1bL4701SJNV4Re1urT+FdsepSa7/usW0PKugWvtZZV19j0aT5XP2NkUfHFREZg1IWZFvI0sI6KO0tkUwlFALz/RT4LkAiE9lXyyAoiVhzUMmLIrGVr8IIClVBqFPA8jw7nYPTKlqJ6K+/y/03nf4paKDXUxOcmKYQSmgCASBHV/nVERp4A4GZa3HQiFhBM4VxtjkI4/ER1L7rWUrHnj4SlvTtbDBELCOZE+Oq3lwaE/zW67aGPSFWWB9ptnZd+ojlGNe0Kpwm1UGoonKvNGW8E4OXzYo/LEn7MWLencKB+hwucaNpUPmEkAqF9a/CCgnx16hejt8Z4JjRjlG6nJaLK0qGtsLSt1zlMie3OodhBzqF4ooqnEIoQm6JFga0JYmXjOiru7Cp5ZIVKKPX68vIv62JU0iIiscgAAAAASUVORK5CYII="
|
| 4 |
+
back = b"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"
|
| 5 |
+
start = b"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"
|
| 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
|