1 mainītis faili ar 353 papildinājumiem un 0 dzēšanām
@ -0,0 +1,353 @@
@@ -0,0 +1,353 @@
|
||||
import os |
||||
import random |
||||
import re |
||||
import secrets |
||||
import string |
||||
import time |
||||
from sys import platform |
||||
|
||||
import requests |
||||
import youtube_dl |
||||
from bs4 import BeautifulSoup |
||||
from selenium import webdriver |
||||
from selenium.webdriver.firefox.options import Options |
||||
|
||||
# region Global Constant(s) and Readonly Variable(s) |
||||
|
||||
# True/False to determine whether selenium instances will be visible or not (headless) |
||||
HIDE_SELENIUM_INSTANCES = False |
||||
|
||||
# The maximum number of courses to download from a single account |
||||
MAX_COURSE_DOWNLOAD_COUNT = 5 |
||||
|
||||
# Denotes Time.Sleep() duration in seconds |
||||
SLEEP_DURATION = 5 |
||||
|
||||
# Master Directory Path |
||||
MASTER_DIRECTORY = os.path.join(os.path.expanduser("~/Desktop"), "Pluralsight") |
||||
|
||||
# Path of the text file where pluralsight account details will be stored |
||||
ACCOUNT_FILE_PATH = os.path.join(MASTER_DIRECTORY, "ps.txt") |
||||
|
||||
# Path of the text file where pluralsight courses to be downloaded will be stored |
||||
COURSE_LINKS_FILE_PATH = os.path.join(MASTER_DIRECTORY, "c.txt") |
||||
|
||||
# Path of the directory where downloaded courses will be saved |
||||
SAVE_DIRECTORY_PATH = os.path.join(MASTER_DIRECTORY, "Courses") |
||||
|
||||
# Path of the archive text file used by Youtube-dl to keep track of downloaded videos |
||||
ARCHIVE_FILE_PATH = os.path.join(MASTER_DIRECTORY, "archive.txt") |
||||
|
||||
# Options for youtube-dl. For a complete list of options, check https://github.com/ytdl-org/youtube-dl/blob/3e4cedf9e8cd3157df2457df7274d0c842421945/youtube_dl/YoutubeDL.py#L137-L312 |
||||
ydl_options = { |
||||
'writesubtitles': True, |
||||
'nooverwrites': True, |
||||
'download_archive': ARCHIVE_FILE_PATH, |
||||
'sleep_interval': 20, |
||||
'max_sleep_interval': 40, |
||||
# 'outtmpl': f"{SAVE_DIRECTORY_PATH}/%(playlist)s/%(chapter_number)s - %(chapter)s/%(playlist_index)s - %(title)s.%(ext)s" |
||||
# Windows Users should comment out the previous outtmpl and uncomment the following |
||||
# 'outtmpl': f"{save_directory_path}\\%(playlist)s\\%(chapter_number)s - %(chapter)s\\%(playlist_index)s - %(title)s.%(ext)s" |
||||
} |
||||
|
||||
if platform.startswith("win"): |
||||
ydl_options[ |
||||
'outtmpl'] = f"{SAVE_DIRECTORY_PATH}\\%(playlist)s\\%(chapter_number)s - %(chapter)s\\%(playlist_index)s - %(title)s.%(ext)s" |
||||
else: |
||||
ydl_options[ |
||||
'outtmpl'] = f"{SAVE_DIRECTORY_PATH}/%(playlist)s/%(chapter_number)s - %(chapter)s/%(playlist_index)s - %(title)s.%(ext)s" |
||||
|
||||
|
||||
# endregion |
||||
|
||||
class TempGmail: |
||||
""" |
||||
This class is used to generate random disposable gmails from https://freetempemails.com |
||||
and use them for registration purpose |
||||
""" |
||||
|
||||
def __init__(self, email_address: str): |
||||
self.email_address = email_address |
||||
|
||||
def get_email_id(self) -> object: |
||||
post_url = "https://gmailnator.com/mailbox/mailboxquery" |
||||
post_data = { |
||||
'action': 'LoadMailList', |
||||
'Email_address': self.email_address |
||||
} |
||||
|
||||
while True: |
||||
try: |
||||
time.sleep(1) |
||||
|
||||
response_text = requests.post(post_url, post_data).json()[0]['content'] |
||||
|
||||
result = re.findall('#(.*)\\">', response_text) |
||||
mail_id = result[0] |
||||
|
||||
return mail_id |
||||
|
||||
|
||||
except Exception as e: |
||||
pass |
||||
|
||||
def get_verification_link(self) -> str: |
||||
post_url = "https://gmailnator.com/mailbox/get_single_message/" |
||||
post_data = { |
||||
'action': 'LoadMailList', |
||||
'message_id': self.get_email_id(), |
||||
'email': self.email_address.split("+")[0] |
||||
} |
||||
|
||||
response_data = requests.post(post_url, post_data).text |
||||
|
||||
soup = BeautifulSoup(response_data) |
||||
for link in soup.findAll('a', href=True): |
||||
if "https://app.pluralsight.com/id/forgotpassword/reset?token" in link['href']: |
||||
return link['href'] |
||||
|
||||
|
||||
class Pluralsight: |
||||
""" |
||||
This class handles the registration, verification and bootstrapping of new Pluralsight accounts |
||||
""" |
||||
|
||||
def __init__(self, email: str, password: str, is_headless: bool = True): |
||||
if is_headless: |
||||
options = Options() |
||||
options.add_argument("--headless") |
||||
self.driver = webdriver.Firefox(options=options) |
||||
else: |
||||
self.driver = webdriver.Firefox() |
||||
|
||||
self.email = email |
||||
self.password = password |
||||
|
||||
def __enter__(self): |
||||
return self |
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb): |
||||
self.driver.quit() |
||||
|
||||
@staticmethod |
||||
def get_name() -> str: |
||||
""" |
||||
Generate a random string to be used as first or last name |
||||
|
||||
Returns: |
||||
str: Generated string |
||||
""" |
||||
|
||||
letters = string.ascii_lowercase |
||||
|
||||
return ''.join(random.choice(letters) for _ in range(random.randint(5, 15))) |
||||
|
||||
def register(self) -> None: |
||||
""" |
||||
Registers new Pluralsight account |
||||
""" |
||||
|
||||
self.driver.get("https://www.pluralsight.com/offer/2020/free-april-month") |
||||
time.sleep(SLEEP_DURATION) |
||||
|
||||
accept_cookie_button_element = self.driver.find_element_by_class_name("cookie_notification--opt_in") |
||||
accept_cookie_button_element.click() |
||||
|
||||
time.sleep(1) |
||||
|
||||
sign_up_now_button_element = self.driver.find_element_by_xpath('//a[@data-aa-title="Free-April-Start-Now"]') |
||||
sign_up_now_button_element.click() |
||||
|
||||
time.sleep(1) |
||||
|
||||
email_input_element = self.driver.find_element_by_name("email") |
||||
firstname_input_element = self.driver.find_element_by_name("firstname") |
||||
lastname_input_element = self.driver.find_element_by_name("lastname") |
||||
tos_checkbox_element = self.driver.find_element_by_name("optInBox") |
||||
|
||||
email_input_element.send_keys(self.email) |
||||
firstname_input_element.send_keys(self.get_name()) |
||||
lastname_input_element.send_keys(self.get_name()) |
||||
tos_checkbox_element.click() |
||||
|
||||
time.sleep(SLEEP_DURATION) |
||||
|
||||
create_account_button_element = self.driver.find_element_by_xpath( |
||||
"//*[contains(text(), 'I agree, activate benefit')]") |
||||
create_account_button_element.click() |
||||
|
||||
time.sleep(30) |
||||
|
||||
cancel_button_element = self.driver.find_element_by_class_name("cancelButton---CKAut") |
||||
cancel_button_element.click() |
||||
|
||||
def set_password(self, verification_link: str) -> None: |
||||
""" |
||||
Sets password in the given verification link |
||||
Args: |
||||
verification_link: The verification link (as string) to set up password |
||||
""" |
||||
|
||||
self.driver.get(verification_link) |
||||
time.sleep(SLEEP_DURATION) |
||||
|
||||
password_input_element = self.driver.find_element_by_id("Password") |
||||
password_confirm_input_element = self.driver.find_element_by_id("PasswordConfirmation") |
||||
save_button_element = self.driver.find_element_by_class_name("psds-button--appearance-primary") |
||||
|
||||
password_input_element.send_keys(self.password) |
||||
password_confirm_input_element.send_keys(self.password) |
||||
|
||||
time.sleep(1) |
||||
|
||||
save_button_element.click() |
||||
|
||||
time.sleep(SLEEP_DURATION) |
||||
|
||||
def bootstrap(self): |
||||
""" |
||||
Bootstraps newly registered accounts to prevent 403 errors in youtube-dl |
||||
""" |
||||
|
||||
username_input_element = self.driver.find_element_by_id("Username") |
||||
password_input_element = self.driver.find_element_by_id("Password") |
||||
login_button_element = self.driver.find_element_by_id("login") |
||||
|
||||
username_input_element.send_keys(self.email) |
||||
password_input_element.send_keys(self.password) |
||||
|
||||
time.sleep(1) |
||||
|
||||
login_button_element.click() |
||||
|
||||
time.sleep(SLEEP_DURATION) |
||||
|
||||
cancel_button_element = self.driver.find_element_by_class_name("cancelButton---CKAut") |
||||
cancel_button_element.click() |
||||
|
||||
|
||||
def get_password(length: int = 30) -> str: |
||||
""" |
||||
Generates a random password using ascii letters and numerical digits |
||||
Args: |
||||
length: Length of the password, default is 30 |
||||
|
||||
Returns: Generated password as string |
||||
""" |
||||
|
||||
alphabet = string.ascii_letters + string.digits |
||||
password = ''.join(secrets.choice(alphabet) for _ in range(length)) |
||||
|
||||
return password |
||||
|
||||
|
||||
def generate_email(is_gmail: bool = True) -> str: |
||||
""" |
||||
Generates a new email |
||||
|
||||
Returns: Generated email string |
||||
""" |
||||
|
||||
gmailnator_gen_url = "https://gmailnator.com/index/indexquery" |
||||
post_data = { |
||||
'action': 'GenerateEmail' |
||||
} |
||||
email = requests.post(gmailnator_gen_url, post_data).text |
||||
|
||||
return email |
||||
|
||||
|
||||
def create_pluralsight_account() -> None: |
||||
""" |
||||
Creates new Pluralsight account using Pluralsight and TempMail |
||||
""" |
||||
|
||||
try: |
||||
email = generate_email() |
||||
password = get_password() |
||||
|
||||
with Pluralsight(email=email, password=password, is_headless=HIDE_SELENIUM_INSTANCES) as ps: |
||||
ps.register() |
||||
|
||||
verification_link = TempGmail(email_address=email).get_verification_link() |
||||
|
||||
ps.set_password(verification_link=verification_link) |
||||
|
||||
time.sleep(SLEEP_DURATION) |
||||
|
||||
with open(ACCOUNT_FILE_PATH, 'w+') as account_file: |
||||
account_file.write(f"{email}\n") |
||||
account_file.write(f"{password}\n") |
||||
|
||||
except Exception as e: |
||||
print(f"ERROR OCCURRED!!\n\nDETAILS: {e.__str__()} | {e.__context__}") |
||||
|
||||
|
||||
def download_course(course_link: str, username: str, password: str) -> bool: |
||||
""" |
||||
Download the given course using the provided credential |
||||
|
||||
Args: |
||||
course_link: The link of the course to download |
||||
username: Username (Email) of the Pluralsight account to be used for download |
||||
password: Password of the Pluralsight account to be used for download |
||||
|
||||
Returns: True/False bool value denoting the success status of the download |
||||
""" |
||||
|
||||
try: |
||||
ydl_options['username'] = username |
||||
ydl_options['password'] = password |
||||
|
||||
with youtube_dl.YoutubeDL(ydl_options) as ydl: |
||||
ydl.download([course_link]) |
||||
|
||||
return True |
||||
except Exception as exception: |
||||
return False |
||||
|
||||
|
||||
def main(): |
||||
if not os.path.exists(COURSE_LINKS_FILE_PATH): |
||||
print(f"{COURSE_LINKS_FILE_PATH} NOT FOUND!") |
||||
return |
||||
|
||||
with open(COURSE_LINKS_FILE_PATH, 'r') as course_file: |
||||
course_list = [course.rstrip() for course in course_file.readlines()] |
||||
|
||||
download_count = 0 |
||||
for course in course_list: |
||||
try: |
||||
while True: |
||||
if not os.path.exists(ACCOUNT_FILE_PATH) or download_count > 5: |
||||
print("CREATING NEW PLURALSIGHT ACCOUNT") |
||||
|
||||
create_pluralsight_account() |
||||
download_count = 0 |
||||
|
||||
print("SUCCESS! NEW PLURALSIGHT ACCOUNT CREATED.") |
||||
|
||||
with open(ACCOUNT_FILE_PATH, 'r') as account_file: |
||||
lines = account_file.readlines() |
||||
email = lines[0].rstrip() |
||||
password = lines[1].rstrip() |
||||
|
||||
print(f"[{email}] DOWNLOADING COURSE: {course}") |
||||
|
||||
is_download_success = download_course(course, username=email, password=password) |
||||
|
||||
if not is_download_success: |
||||
os.remove(ACCOUNT_FILE_PATH) |
||||
continue |
||||
|
||||
print(f"[{email}] SUCCESSFULLY DOWNLOADED COURSE: {course}") |
||||
|
||||
break |
||||
except Exception as e: |
||||
print(e) |
||||
finally: |
||||
download_count += 1 |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
main() |
Notiek ielāde…
Atsaukties uz šo jaunā problēmā