Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

pluralsight.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import os
  2. import random
  3. import re
  4. import secrets
  5. import string
  6. import time
  7. from sys import platform
  8. import requests
  9. import youtube_dl
  10. from bs4 import BeautifulSoup
  11. from selenium import webdriver
  12. from selenium.webdriver.firefox.options import Options
  13. # region Global Constant(s) and Readonly Variable(s)
  14. # True/False to determine whether selenium instances will be visible or not (headless)
  15. HIDE_SELENIUM_INSTANCES = False
  16. # The maximum number of courses to download from a single account
  17. MAX_COURSE_DOWNLOAD_COUNT = 5
  18. # Denotes Time.Sleep() duration in seconds
  19. SLEEP_DURATION = 5
  20. # Master Directory Path
  21. MASTER_DIRECTORY = os.path.join(os.path.expanduser("~/Desktop"), "Pluralsight")
  22. # Path of the text file where pluralsight account details will be stored
  23. ACCOUNT_FILE_PATH = os.path.join(MASTER_DIRECTORY, "ps.txt")
  24. # Path of the text file where pluralsight courses to be downloaded will be stored
  25. COURSE_LINKS_FILE_PATH = os.path.join(MASTER_DIRECTORY, "c.txt")
  26. # Path of the directory where downloaded courses will be saved
  27. SAVE_DIRECTORY_PATH = os.path.join(MASTER_DIRECTORY, "Courses")
  28. # Path of the archive text file used by Youtube-dl to keep track of downloaded videos
  29. ARCHIVE_FILE_PATH = os.path.join(MASTER_DIRECTORY, "archive.txt")
  30. # 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
  31. ydl_options = {
  32. 'writesubtitles': True,
  33. 'nooverwrites': True,
  34. 'download_archive': ARCHIVE_FILE_PATH,
  35. 'sleep_interval': 20,
  36. 'max_sleep_interval': 40,
  37. # 'outtmpl': f"{SAVE_DIRECTORY_PATH}/%(playlist)s/%(chapter_number)s - %(chapter)s/%(playlist_index)s - %(title)s.%(ext)s"
  38. # Windows Users should comment out the previous outtmpl and uncomment the following
  39. # 'outtmpl': f"{save_directory_path}\\%(playlist)s\\%(chapter_number)s - %(chapter)s\\%(playlist_index)s - %(title)s.%(ext)s"
  40. }
  41. if platform.startswith("win"):
  42. ydl_options[
  43. 'outtmpl'] = f"{SAVE_DIRECTORY_PATH}\\%(playlist)s\\%(chapter_number)s - %(chapter)s\\%(playlist_index)s - %(title)s.%(ext)s"
  44. else:
  45. ydl_options[
  46. 'outtmpl'] = f"{SAVE_DIRECTORY_PATH}/%(playlist)s/%(chapter_number)s - %(chapter)s/%(playlist_index)s - %(title)s.%(ext)s"
  47. # endregion
  48. class TempGmail:
  49. """
  50. This class is used to generate random disposable gmails from https://freetempemails.com
  51. and use them for registration purpose
  52. """
  53. def __init__(self, email_address: str):
  54. self.email_address = email_address
  55. def get_email_id(self) -> object:
  56. post_url = "https://gmailnator.com/mailbox/mailboxquery"
  57. post_data = {
  58. 'action': 'LoadMailList',
  59. 'Email_address': self.email_address
  60. }
  61. while True:
  62. try:
  63. time.sleep(1)
  64. response_text = requests.post(post_url, post_data).json()[0]['content']
  65. result = re.findall('#(.*)\\">', response_text)
  66. mail_id = result[0]
  67. return mail_id
  68. except Exception as e:
  69. pass
  70. def get_verification_link(self) -> str:
  71. post_url = "https://gmailnator.com/mailbox/get_single_message/"
  72. post_data = {
  73. 'action': 'LoadMailList',
  74. 'message_id': self.get_email_id(),
  75. 'email': self.email_address.split("+")[0]
  76. }
  77. response_data = requests.post(post_url, post_data).text
  78. soup = BeautifulSoup(response_data)
  79. for link in soup.findAll('a', href=True):
  80. if "https://app.pluralsight.com/id/forgotpassword/reset?token" in link['href']:
  81. return link['href']
  82. class Pluralsight:
  83. """
  84. This class handles the registration, verification and bootstrapping of new Pluralsight accounts
  85. """
  86. def __init__(self, email: str, password: str, is_headless: bool = True):
  87. if is_headless:
  88. options = Options()
  89. options.add_argument("--headless")
  90. self.driver = webdriver.Firefox(options=options)
  91. else:
  92. self.driver = webdriver.Firefox()
  93. self.email = email
  94. self.password = password
  95. def __enter__(self):
  96. return self
  97. def __exit__(self, exc_type, exc_val, exc_tb):
  98. self.driver.quit()
  99. @staticmethod
  100. def get_name() -> str:
  101. """
  102. Generate a random string to be used as first or last name
  103. Returns:
  104. str: Generated string
  105. """
  106. letters = string.ascii_lowercase
  107. return ''.join(random.choice(letters) for _ in range(random.randint(5, 15)))
  108. def register(self) -> None:
  109. """
  110. Registers new Pluralsight account
  111. """
  112. self.driver.get("https://www.pluralsight.com/offer/2020/free-april-month")
  113. time.sleep(SLEEP_DURATION)
  114. accept_cookie_button_element = self.driver.find_element_by_class_name("cookie_notification--opt_in")
  115. accept_cookie_button_element.click()
  116. time.sleep(1)
  117. sign_up_now_button_element = self.driver.find_element_by_xpath('//a[@data-aa-title="Free-April-Start-Now"]')
  118. sign_up_now_button_element.click()
  119. time.sleep(1)
  120. email_input_element = self.driver.find_element_by_name("email")
  121. firstname_input_element = self.driver.find_element_by_name("firstname")
  122. lastname_input_element = self.driver.find_element_by_name("lastname")
  123. tos_checkbox_element = self.driver.find_element_by_name("optInBox")
  124. email_input_element.send_keys(self.email)
  125. firstname_input_element.send_keys(self.get_name())
  126. lastname_input_element.send_keys(self.get_name())
  127. tos_checkbox_element.click()
  128. time.sleep(SLEEP_DURATION)
  129. create_account_button_element = self.driver.find_element_by_xpath(
  130. "//*[contains(text(), 'I agree, activate benefit')]")
  131. create_account_button_element.click()
  132. time.sleep(30)
  133. cancel_button_element = self.driver.find_element_by_class_name("cancelButton---CKAut")
  134. cancel_button_element.click()
  135. def set_password(self, verification_link: str) -> None:
  136. """
  137. Sets password in the given verification link
  138. Args:
  139. verification_link: The verification link (as string) to set up password
  140. """
  141. self.driver.get(verification_link)
  142. time.sleep(SLEEP_DURATION)
  143. password_input_element = self.driver.find_element_by_id("Password")
  144. password_confirm_input_element = self.driver.find_element_by_id("PasswordConfirmation")
  145. save_button_element = self.driver.find_element_by_class_name("psds-button--appearance-primary")
  146. password_input_element.send_keys(self.password)
  147. password_confirm_input_element.send_keys(self.password)
  148. time.sleep(1)
  149. save_button_element.click()
  150. time.sleep(SLEEP_DURATION)
  151. def bootstrap(self):
  152. """
  153. Bootstraps newly registered accounts to prevent 403 errors in youtube-dl
  154. """
  155. username_input_element = self.driver.find_element_by_id("Username")
  156. password_input_element = self.driver.find_element_by_id("Password")
  157. login_button_element = self.driver.find_element_by_id("login")
  158. username_input_element.send_keys(self.email)
  159. password_input_element.send_keys(self.password)
  160. time.sleep(1)
  161. login_button_element.click()
  162. time.sleep(SLEEP_DURATION)
  163. cancel_button_element = self.driver.find_element_by_class_name("cancelButton---CKAut")
  164. cancel_button_element.click()
  165. def get_password(length: int = 30) -> str:
  166. """
  167. Generates a random password using ascii letters and numerical digits
  168. Args:
  169. length: Length of the password, default is 30
  170. Returns: Generated password as string
  171. """
  172. alphabet = string.ascii_letters + string.digits
  173. password = ''.join(secrets.choice(alphabet) for _ in range(length))
  174. return password
  175. def generate_email(is_gmail: bool = True) -> str:
  176. """
  177. Generates a new email
  178. Returns: Generated email string
  179. """
  180. gmailnator_gen_url = "https://gmailnator.com/index/indexquery"
  181. post_data = {
  182. 'action': 'GenerateEmail'
  183. }
  184. email = requests.post(gmailnator_gen_url, post_data).text
  185. return email
  186. def create_pluralsight_account() -> None:
  187. """
  188. Creates new Pluralsight account using Pluralsight and TempMail
  189. """
  190. try:
  191. email = generate_email()
  192. password = get_password()
  193. with Pluralsight(email=email, password=password, is_headless=HIDE_SELENIUM_INSTANCES) as ps:
  194. ps.register()
  195. verification_link = TempGmail(email_address=email).get_verification_link()
  196. ps.set_password(verification_link=verification_link)
  197. time.sleep(SLEEP_DURATION)
  198. with open(ACCOUNT_FILE_PATH, 'w+') as account_file:
  199. account_file.write(f"{email}\n")
  200. account_file.write(f"{password}\n")
  201. except Exception as e:
  202. print(f"ERROR OCCURRED!!\n\nDETAILS: {e.__str__()} | {e.__context__}")
  203. def download_course(course_link: str, username: str, password: str) -> bool:
  204. """
  205. Download the given course using the provided credential
  206. Args:
  207. course_link: The link of the course to download
  208. username: Username (Email) of the Pluralsight account to be used for download
  209. password: Password of the Pluralsight account to be used for download
  210. Returns: True/False bool value denoting the success status of the download
  211. """
  212. try:
  213. ydl_options['username'] = username
  214. ydl_options['password'] = password
  215. with youtube_dl.YoutubeDL(ydl_options) as ydl:
  216. ydl.download([course_link])
  217. return True
  218. except Exception as exception:
  219. return False
  220. def main():
  221. if not os.path.exists(COURSE_LINKS_FILE_PATH):
  222. print(f"{COURSE_LINKS_FILE_PATH} NOT FOUND!")
  223. return
  224. with open(COURSE_LINKS_FILE_PATH, 'r') as course_file:
  225. course_list = [course.rstrip() for course in course_file.readlines()]
  226. download_count = 0
  227. for course in course_list:
  228. try:
  229. while True:
  230. if not os.path.exists(ACCOUNT_FILE_PATH) or download_count > 5:
  231. print("CREATING NEW PLURALSIGHT ACCOUNT")
  232. create_pluralsight_account()
  233. download_count = 0
  234. print("SUCCESS! NEW PLURALSIGHT ACCOUNT CREATED.")
  235. with open(ACCOUNT_FILE_PATH, 'r') as account_file:
  236. lines = account_file.readlines()
  237. email = lines[0].rstrip()
  238. password = lines[1].rstrip()
  239. print(f"[{email}] DOWNLOADING COURSE: {course}")
  240. is_download_success = download_course(course, username=email, password=password)
  241. if not is_download_success:
  242. os.remove(ACCOUNT_FILE_PATH)
  243. continue
  244. print(f"[{email}] SUCCESSFULLY DOWNLOADED COURSE: {course}")
  245. break
  246. except Exception as e:
  247. print(e)
  248. finally:
  249. download_count += 1
  250. if __name__ == '__main__':
  251. main()