Skip to content

Commit

Permalink
Merge pull request #146 from aapatre/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
fakeid30 authored Dec 9, 2020
2 parents 997ebd4 + a613365 commit cad5c26
Show file tree
Hide file tree
Showing 32 changed files with 2,669 additions and 261 deletions.
6 changes: 6 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[report]
show_missing = True
skip_covered = True
omit =
tests/*
requirements.py
2 changes: 2 additions & 0 deletions .deepsource.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
version = 1

test_patterns = ["*/tests/**"]

[[analyzers]]
name = "python"
enabled = true
Expand Down
30 changes: 23 additions & 7 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ name: CI Build

on:
workflow_dispatch:
schedule:
# Runs at 12am IST
- cron: '30 18 * * *'

jobs:
build:

runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest]
python-version: [3.8]

steps:
Expand All @@ -20,24 +24,36 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
wget -qO- https://deb.opera.com/archive.key | sudo apt-key add -
sudo add-apt-repository "deb [arch=i386,amd64] https://deb.opera.com/opera-stable/ stable non-free"
sudo apt-get update
sudo apt-get -y --no-install-recommends install opera-stable chromium-browser
opera --version
- name: Install Macos dependencies
if: startsWith(runner.os, 'macOS')
run: |
brew cask install chromium opera
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install poetry flake8
poetry install
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Install Chrome and Firefox
- name: Run unittests
run: |
sudo apt install google-chrome-stable
sudo apt-get install firefox
poetry run pytest
- name: Make sure chrome crawler is working (Attempt to subscribe to 1 course)
env:
UDEMY_EMAIL: ${{ secrets.UDEMY_EMAIL }}
UDEMY_PASSWORD: ${{ secrets.UDEMY_PASSWORD }}
CI_TEST: "True"
run: |
python udemy_enroller_chrome.py
poetry run python udemy_enroller_chrome.py
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,12 @@ poetry.lock

# Cache files
.course_cache

# Ignore temporary test folder
test_tmp/

.idea/poetry.xml

#ignore deepsource.toml

.deepsource.toml
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.0] - 2020-12-09

### Added

- Fix for state selection for India
- Added deprecation warning to individual browser endpoint to move to uniform endpoint in preparation for a release in PyPi
- Moved from print to logging for better debug
- Added arguments to script runtime to select max pages and max retry before timeout (default 12)
- Fixed Firefox webdriver autoinstall
- Stop trying to enroll in courses that has any price tag attached to them
- Added unittests
- General performance improvement

## [0.3] - 2020-11-26

Expand Down
57 changes: 33 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@

Do you want to LEARN NEW STUFF for FREE? Don't worry, with the power of
web-scraping and automation, this script will find the necessary Udemy Coupons
& enroll you for PAID UDEMY COURSES, ABSOLUTELY FREE!
& enroll you to PAID UDEMY COURSES, ABSOLUTELY FREE!

The code scrapes course links and coupons from
[tutorialbar.com](https://tutorialbar.com)

In case of any bugs or issues, **feel free to ping me on
[LinkedIn](https://www.linkedin.com/in/aapatre/) or
[Twitter](https://twitter.com/Antariksh_Patre)**
In case of any bugs or issues, please open an issue in github.

Also, don't forget to **Fork & Star the repository if you like it!**

Expand All @@ -25,13 +23,11 @@ Also, don't forget to **Fork & Star the repository if you like it!**

## **_Disclaimer & WARNINGS:_**

1. **IMPORTANT:** Make sure you **clear all saved Debit/Credit Card or any other
saved payment info from your Browser & your Udemy account** before using the
script!
2. **Use** this ONLY for **Educational Purposes!** By using this code you agree

1. **Use** this ONLY for **Educational Purposes!** By using this code you agree
that **I'm not responsible for any kind of trouble** caused by the code.
3. **Make sure web-scraping is legal in your region.**
4. This is **NOT a hacking script**, i.e., it can't enroll you for a specific
2. **Make sure web-scraping is legal in your region.**
3. This is **NOT a hacking script**, i.e., it can't enroll you for a specific
course! Instead it finds courses that provide coupon links to make the
transaction free and then LEGALLY enroll you to the course!

Expand All @@ -43,11 +39,11 @@ Also, don't forget to **Fork & Star the repository if you like it!**

**Required Python version:** [Python 3.8+](https://www.python.org/downloads/)

**You must have pip installed. Please look up how to install pip in your OS.**
**You must have pip or poetry installed. Please look up how to install them in your OS.**

Download a release of this project or clone the repository then navigate to the
folder where you placed the files on. Type `pip install -r requirements.txt` to
get all the requirements installed in one go.
get all the requirements installed in one go. Similar instructions applies for poetry.

- **Webdrivers are now automatically installed! But here are some links in case
you are using the vanilla script or the Safari Browser:**
Expand All @@ -71,7 +67,7 @@ get all the requirements installed in one go.

- Run the script and the cli will guide you through the settings required
- Otherwise you can rename the following file
[sample_settings.yaml](sample_settings.yaml) to **settings.py** and edit it
[sample_settings.yaml](sample_settings.yaml) to **settings.yaml** and edit it
using a text editor and insert your **Udemy registered email in the email
section**, your **Udemy password in the password section**, and the **ZIP Code
in the zipcode section (if you reside in the United States or any other region
Expand All @@ -92,7 +88,7 @@ get all the requirements installed in one go.
- **Has issues when run on custom kernel but works fine on vanilla OS:**

- Firefox:
[udemy_enroller_firefox.py(requires manual driver installation)](https://github.com/aapatre/Automatic-Udemy-Course-Enroller-GET-PAID-UDEMY-COURSES-for-FREE/blob/master/udemy_enroller_firefox.py)
[udemy_enroller_firefox.py(might require manual driver installation)](https://github.com/aapatre/Automatic-Udemy-Course-Enroller-GET-PAID-UDEMY-COURSES-for-FREE/blob/master/udemy_enroller_firefox.py)

- **Untested:**

Expand All @@ -108,10 +104,19 @@ get all the requirements installed in one go.
- Internet Explorer:
[udemy_enroller_internet_explorer.py](https://github.com/aapatre/Automatic-Udemy-Course-Enroller-GET-PAID-UDEMY-COURSES-for-FREE/blob/master/udemy_enroller_internet_explorer.py)

3 . Run the chosen script in terminal like so:
`python udemy_enroller_firefox.py`
3 . The script can be passed arguments:
- `--help`: View full list of arguments available
- `--max-pages=<NUMBER>`: Max number of pages to scrape from tutorialbar.com before exiting the script
- `--browser=<BROWSER_NAME>`: Run with a specific browser
- `--cache-hits=<NUMBER>`: If we hit the cache this number of times in a row we will exit the script

4 . Run the chosen script in terminal like so:
- `python udemy_enroller_firefox.py`

4 . The bot starts scraping the course links from the first **All Courses** page
Or by using the generic script:
- `python udemy_enroller.py --browser=firefox`

5 . The bot starts scraping the course links from the first **All Courses** page
on [Tutorial Bar](https://www.tutorialbar.com/all-courses/page/1) and starts
enrolling you to Udemy courses. After it has enrolled you to courses from the
first page, it then moves to the next Tutorial Bar page and the cycle continues.
Expand Down Expand Up @@ -142,9 +147,7 @@ to make those courses free.

### 3. How frequently should you run the script?

Daily, at least once! If you are using it for the first time, I recommend that
you allow it to scrape through all pages on Tutorial Bar (might take a few hours
since there are >500 pages on the site). I've painstakingly amassed over 4000
Daily, at least once! I've painstakingly amassed over 4000
courses in the last four years! And out of those 4000, I've only paid for 4 of
these courses.

Expand All @@ -164,7 +167,7 @@ it will save your precious time too! :)
![](https://i.imgur.com/pwseilE.jpg) Relax! This happens when you run the script
several times in a short interval of time. Solve the captcha, hit enter in the terminal window you are running
the script from and allow the script to continue as normal.
Easy peasy lemon squeezy! 🍋🙃 <br /><br />
Easy peasy lemon squeezy! 🍋🙃

### 6. The code compiles successfully but it's taking too long to work! IS there any way to fix that?

Expand All @@ -175,10 +178,8 @@ retrieved in the Python console/shell, which may take a while.

### 7. Which is the best way to run the script?

It is recommended to run the script using Python's IDLE IDE.
It is recommended to run the script using your terminal and system python.

**Pro-tip:** Create a batch file, to launch the script instantly, using these
instructions: https://datatofish.com/batch-python-script/

### 8. Which branch to commit against?

Expand All @@ -194,6 +195,14 @@ and help us on what you want or talk to us about your proposed changes.

## Supporter

### Jetbrains

[![JetBrains](https://i.imgur.com/h2R018M.jpg)](https://jetbrains.com/?from=udemy-free-course-enroller)

Thanks to [JetBrains](https://jetbrains.com/?from=udemy-free-course-enroller) for supporting us. They are the maker of world class IDE and developer tooling. If you think their product might help you, please support them.

### GitBook

[![Gitbook](https://i.imgur.com/OkuB14I.jpg)](https://gitbook.com)

Thanks to [Gitbook](https://gitbook.com) for supporting us. Gitbook is the best place to track personal notes and ideas for teams. If you think their product might help you, please support them.
6 changes: 6 additions & 0 deletions core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import logging.config

from .cache import CourseCache
from .driver_manager import ALL_VALID_BROWSER_STRINGS, DriverManager
from .settings import Settings
from .tutorialbar import TutorialBarScraper
from .udemy import UdemyActions

logging.config.fileConfig("logconfig.ini", disable_existing_loggers=False)
6 changes: 3 additions & 3 deletions core/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import os


class CourseCache(object):
class CourseCache:
"""
Basic cache to keep details on courses already scraped
"""

def __init__(self):
self._file_name = ".course_cache"
def __init__(self, file_name=".course_cache"):
self._file_name = file_name
self._cache = []
self._load_cache()

Expand Down
92 changes: 92 additions & 0 deletions core/driver_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import logging

from selenium import webdriver
from selenium.webdriver.chrome.options import Options as ChromeOptions
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.firefox import GeckoDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager, IEDriverManager
from webdriver_manager.opera import OperaDriverManager
from webdriver_manager.utils import ChromeType

VALID_FIREFOX_STRINGS = {"ff", "firefox"}
VALID_CHROME_STRINGS = {"chrome", "google-chrome"}
VALID_CHROMIUM_STRINGS = {"chromium"}
VALID_INTERNET_EXPLORER_STRINGS = {"internet_explorer", "ie"}
VALID_OPERA_STRINGS = {"opera"}
VALID_EDGE_STRINGS = {"edge"}

ALL_VALID_BROWSER_STRINGS = (
VALID_FIREFOX_STRINGS.union(VALID_CHROME_STRINGS)
.union(VALID_CHROMIUM_STRINGS)
.union(VALID_CHROMIUM_STRINGS)
.union(VALID_INTERNET_EXPLORER_STRINGS)
.union(VALID_OPERA_STRINGS)
.union(VALID_EDGE_STRINGS)
)


logger = logging.getLogger("udemy_enroller")


class DriverManager:
def __init__(self, browser: str, is_ci_build: bool = False):
self.driver = None
self.options = None
self.browser = browser
self.is_ci_build = is_ci_build
self._init_driver()

def _init_driver(self):
"""
Initialize the correct web driver based on the users requested browser
:return: None
"""

if self.browser.lower() in VALID_CHROME_STRINGS:
if self.is_ci_build:
self.options = self._build_ci_options_chrome()
self.driver = webdriver.Chrome(
ChromeDriverManager().install(), options=self.options
)
elif self.browser.lower() in VALID_CHROMIUM_STRINGS:
self.driver = webdriver.Chrome(
ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install()
)
elif self.browser.lower() in VALID_EDGE_STRINGS:
self.driver = webdriver.Edge(EdgeChromiumDriverManager().install())
elif self.browser.lower() in VALID_FIREFOX_STRINGS:
self.driver = webdriver.Firefox(
executable_path=GeckoDriverManager().install()
)
elif self.browser.lower() in VALID_OPERA_STRINGS:
self.driver = webdriver.Opera(
executable_path=OperaDriverManager().install()
)
elif self.browser.lower() in VALID_INTERNET_EXPLORER_STRINGS:
self.driver = webdriver.Ie(IEDriverManager().install())
else:
raise ValueError("No matching browser found")

# Maximize the browser
self.driver.maximize_window()

@staticmethod
def _build_ci_options_chrome():
"""
Build chrome options required to run in CI
:return:
"""
# Having the user-agent with Headless param was always leading to robot check
user_agent = (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 "
"Safari/537.36"
)
options = ChromeOptions()
# We need to run headless when using github CI
options.add_argument("--headless")
options.add_argument("user-agent={0}".format(user_agent))
options.add_argument("--window-size=1325x744")
logger.info("This is a CI run")
return options
Loading

0 comments on commit cad5c26

Please sign in to comment.