Browse Source

Add 'pluralsight.py'

Rape me senpai - Pluralsight probably
master
parent
commit
9a98977f66
1 changed files with 353 additions and 0 deletions
  1. 353
    0
      pluralsight.py

+ 353
- 0
pluralsight.py View File

@@ -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()

Loading…
Cancel
Save