initialize.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. import argparse
  2. import configparser
  3. import logging
  4. import os.path
  5. import subprocess
  6. import sys
  7. import time
  8. import shutil
  9. import json
  10. from .auth import login
  11. from .downloader import start_single, start_multiple
  12. from .logger import log_seperator, supports_color, log_info_blue, log_info_green, log_warn, log_error, log_whiteline, log_plain
  13. from .settings import settings
  14. script_version = "2.5.9"
  15. python_version = sys.version.split(' ')[0]
  16. bool_values = {'True', 'False'}
  17. def check_ffmpeg():
  18. try:
  19. FNULL = open(os.devnull, 'w')
  20. if settings.ffmpeg_path == None:
  21. subprocess.call(["ffmpeg"], stdout=FNULL, stderr=subprocess.STDOUT)
  22. else:
  23. subprocess.call([settings.ffmpeg_path], stdout=FNULL, stderr=subprocess.STDOUT)
  24. return True
  25. except OSError as e:
  26. return False
  27. def check_pyinstalive():
  28. try:
  29. FNULL = open(os.devnull, 'w')
  30. subprocess.call(["pyinstalive"], stdout=FNULL, stderr=subprocess.STDOUT)
  31. return True
  32. except OSError as e:
  33. return False
  34. def check_config_validity(config, args=None):
  35. try:
  36. has_thrown_errors = False
  37. settings.username = config.get('pyinstalive', 'username')
  38. settings.password = config.get('pyinstalive', 'password')
  39. try:
  40. settings.use_locks = config.get('pyinstalive', 'use_locks').title()
  41. if not settings.use_locks in bool_values:
  42. log_warn("Invalid or missing setting detected for 'use_locks', using default value (True)")
  43. settings.use_locks = 'true'
  44. has_thrown_errors = True
  45. except:
  46. log_warn("Invalid or missing setting detected for 'use_locks', using default value (True)")
  47. settings.use_locks = 'true'
  48. has_thrown_errors = True
  49. try:
  50. settings.show_cookie_expiry = config.get('pyinstalive', 'show_cookie_expiry').title()
  51. if not settings.show_cookie_expiry in bool_values:
  52. log_warn("Invalid or missing setting detected for 'show_cookie_expiry', using default value (True)")
  53. settings.show_cookie_expiry = 'true'
  54. has_thrown_errors = True
  55. except:
  56. log_warn("Invalid or missing setting detected for 'show_cookie_expiry', using default value (True)")
  57. settings.show_cookie_expiry = 'true'
  58. has_thrown_errors = True
  59. try:
  60. settings.clear_temp_files = config.get('pyinstalive', 'clear_temp_files').title()
  61. if not settings.clear_temp_files in bool_values:
  62. log_warn("Invalid or missing setting detected for 'clear_temp_files', using default value (True)")
  63. settings.clear_temp_files = 'true'
  64. has_thrown_errors = True
  65. except:
  66. log_warn("Invalid or missing setting detected for 'clear_temp_files', using default value (True)")
  67. settings.clear_temp_files = 'true'
  68. has_thrown_errors = True
  69. try:
  70. settings.save_replays = config.get('pyinstalive', 'save_replays').title()
  71. if not settings.save_replays in bool_values:
  72. log_warn("Invalid or missing setting detected for 'save_replays', using default value (True)")
  73. settings.save_replays = 'true'
  74. has_thrown_errors = True
  75. except:
  76. log_warn("Invalid or missing setting detected for 'save_replays', using default value (True)")
  77. settings.save_replays = 'true'
  78. has_thrown_errors = True
  79. try:
  80. settings.save_lives = config.get('pyinstalive', 'save_lives').title()
  81. if not settings.save_lives in bool_values:
  82. log_warn("Invalid or missing setting detected for 'save_lives', using default value (True)")
  83. settings.save_lives = 'true'
  84. has_thrown_errors = True
  85. except:
  86. log_warn("Invalid or missing setting detected for 'save_lives', using default value (True)")
  87. settings.save_lives = 'true'
  88. has_thrown_errors = True
  89. try:
  90. settings.log_to_file = config.get('pyinstalive', 'log_to_file').title()
  91. if not settings.log_to_file in bool_values:
  92. log_warn("Invalid or missing setting detected for 'log_to_file', using default value (False)")
  93. settings.log_to_file = 'False'
  94. has_thrown_errors = True
  95. except:
  96. log_warn("Invalid or missing setting detected for 'log_to_file', using default value (False)")
  97. settings.log_to_file = 'False'
  98. has_thrown_errors = True
  99. try:
  100. settings.run_at_start = config.get('pyinstalive', 'run_at_start').replace("\\", "\\\\")
  101. if not settings.run_at_start:
  102. settings.run_at_start = "None"
  103. except:
  104. log_warn("Invalid or missing settings detected for 'run_at_start', using default value (empty)")
  105. settings.run_at_start = "None"
  106. has_thrown_errors = True
  107. try:
  108. settings.run_at_finish = config.get('pyinstalive', 'run_at_finish').replace("\\", "\\\\")
  109. if not settings.run_at_finish:
  110. settings.run_at_finish = "None"
  111. except:
  112. log_warn("Invalid or missing settings detected for 'run_at_finish', using default value (empty)")
  113. settings.run_at_finish = "None"
  114. has_thrown_errors = True
  115. try:
  116. settings.save_comments = config.get('pyinstalive', 'save_comments').title()
  117. wide_build = sys.maxunicode > 65536
  118. if sys.version.split(' ')[0].startswith('2') and settings.save_comments == "True" and not wide_build:
  119. log_warn("Your Python 2 build does not support wide unicode characters.")
  120. log_warn("This means characters such as emojis will not be saved.")
  121. has_thrown_errors = True
  122. else:
  123. if not settings.show_cookie_expiry in bool_values:
  124. log_warn("Invalid or missing setting detected for 'save_comments', using default value (False)")
  125. settings.save_comments = 'false'
  126. has_thrown_errors = True
  127. except:
  128. log_warn("Invalid or missing setting detected for 'save_comments', using default value (False)")
  129. settings.save_comments = 'false'
  130. has_thrown_errors = True
  131. try:
  132. if args and args.savepath:
  133. args.savepath = args.savepath.replace("\"", "").replace("'", "")
  134. if (os.path.exists(args.savepath)) and (args.savepath != config.get('pyinstalive', 'save_path')):
  135. settings.save_path = args.savepath
  136. log_info_blue("Overriding save path: {:s}".format(args.savepath))
  137. log_seperator()
  138. elif (args.savepath != config.get('pyinstalive', 'save_path')):
  139. log_warn("Custom save path does not exist, falling back to path specified in config.")
  140. settings.save_path = config.get('pyinstalive', 'save_path')
  141. log_seperator()
  142. else:
  143. settings.save_path = config.get('pyinstalive', 'save_path')
  144. else:
  145. settings.save_path = config.get('pyinstalive', 'save_path')
  146. if not settings.save_path.endswith('/'):
  147. settings.save_path = settings.save_path + '/'
  148. if (os.path.exists(settings.save_path)):
  149. pass
  150. else:
  151. log_warn("Invalid or missing setting detected for 'save_path', falling back to path: {:s}".format(os.getcwd()))
  152. settings.save_path = os.getcwd()
  153. has_thrown_errors = True
  154. if not settings.save_path.endswith('/'):
  155. settings.save_path = settings.save_path + '/'
  156. except Exception as e:
  157. log_warn("Invalid or missing setting detected for 'save_path', falling back to path: {:s}".format(os.getcwd()))
  158. settings.save_path = os.getcwd()
  159. has_thrown_errors = True
  160. if not settings.save_path.endswith('/'):
  161. settings.save_path = settings.save_path + '/'
  162. try:
  163. settings.ffmpeg_path = config.get('pyinstalive', 'ffmpeg_path')
  164. if (len(settings.ffmpeg_path) > 1) and (os.path.exists(settings.ffmpeg_path)):
  165. log_info_blue("Overriding FFmpeg path: {:s}".format(settings.ffmpeg_path))
  166. log_seperator()
  167. else:
  168. if (len(settings.ffmpeg_path) > 1):
  169. log_warn("Invalid setting detected for 'ffmpeg_path', falling back to environment variable.")
  170. has_thrown_errors = True
  171. settings.ffmpeg_path = None
  172. except:
  173. log_warn("Invalid or missing setting detected for 'ffmpeg_path', falling back to environment variable.")
  174. settings.ffmpeg_path = None
  175. has_thrown_errors = True
  176. if has_thrown_errors:
  177. log_seperator()
  178. return True
  179. except Exception as e:
  180. print(str(e))
  181. return False
  182. def show_info(config):
  183. if os.path.exists(settings.custom_config_path):
  184. try:
  185. config.read(settings.custom_config_path)
  186. except Exception as e:
  187. log_error("Could not read configuration file: {:s}".format(str(e)))
  188. log_seperator()
  189. else:
  190. new_config()
  191. sys.exit(1)
  192. if not check_config_validity(config):
  193. log_warn("Config file is not valid, some information may be inaccurate.")
  194. log_whiteline()
  195. cookie_files = []
  196. cookie_from_config = ''
  197. try:
  198. for file in os.listdir(os.getcwd()):
  199. if file.endswith(".json"):
  200. with open(file) as data_file:
  201. try:
  202. json_data = json.load(data_file)
  203. if (json_data.get('created_ts')):
  204. cookie_files.append(file)
  205. except Exception as e:
  206. pass
  207. if settings.username == file.replace(".json", ''):
  208. cookie_from_config = file
  209. except Exception as e:
  210. log_warn("Could not check for cookie files: {:s}".format(str(e)))
  211. log_whiteline()
  212. log_info_green("To see all the available arguments, use the -h argument.")
  213. log_whiteline()
  214. log_info_green("PyInstaLive version: {:s}".format(script_version))
  215. log_info_green("Python version: {:s}".format(python_version))
  216. if not check_ffmpeg():
  217. log_error("FFmpeg framework: Not found")
  218. else:
  219. log_info_green("FFmpeg framework: Available")
  220. if (len(cookie_from_config) > 0):
  221. log_info_green("Cookie files: {:s} ({:s} matches config user)".format(str(len(cookie_files)), cookie_from_config))
  222. elif len(cookie_files) > 0:
  223. log_info_green("Cookie files: {:s}".format(str(len(cookie_files))))
  224. else:
  225. log_warn("Cookie files: None found")
  226. log_info_green("CLI supports color: {:s}".format(str(supports_color()[0])))
  227. log_info_green("File to run at start: {:s}".format(settings.run_at_start))
  228. log_info_green("File to run at finish: {:s}".format(settings.run_at_finish))
  229. log_whiteline()
  230. if os.path.exists(settings.custom_config_path):
  231. log_info_green("Config file:")
  232. log_whiteline()
  233. with open(settings.custom_config_path) as f:
  234. for line in f:
  235. log_plain(" {:s}".format(line.rstrip()))
  236. else:
  237. log_error("Config file: Not found")
  238. log_whiteline()
  239. log_info_green("End of PyInstaLive information screen.")
  240. log_seperator()
  241. def new_config():
  242. try:
  243. if os.path.exists(settings.custom_config_path):
  244. log_info_green("A configuration file is already present:")
  245. log_whiteline()
  246. with open(settings.custom_config_path) as f:
  247. for line in f:
  248. log_plain(" {:s}".format(line.rstrip()))
  249. log_whiteline()
  250. log_info_green("To create a default config file, delete 'pyinstalive.ini' and run this script again.")
  251. log_seperator()
  252. else:
  253. try:
  254. log_warn("Could not find configuration file, creating a default one...")
  255. config_template = """
  256. [pyinstalive]
  257. username = johndoe
  258. password = grapefruits
  259. save_path = {:s}
  260. ffmpeg_path =
  261. show_cookie_expiry = true
  262. clear_temp_files = false
  263. save_lives = true
  264. save_replays = true
  265. run_at_start =
  266. run_at_finish =
  267. save_comments = false
  268. log_to_file = false
  269. """.format(os.getcwd())
  270. config_file = open(settings.custom_config_path, "w")
  271. config_file.write(config_template.strip())
  272. config_file.close()
  273. log_warn("Edit the created 'pyinstalive.ini' file and run this script again.")
  274. log_seperator()
  275. sys.exit(0)
  276. except Exception as e:
  277. log_error("Could not create default config file: {:s}".format(str(e)))
  278. log_warn("You must manually create and edit it with the following template: ")
  279. log_whiteline()
  280. for line in config_template.strip().splitlines():
  281. log_plain(" {:s}".format(line.rstrip()))
  282. log_whiteline()
  283. log_warn("Save it as 'pyinstalive.ini' and run this script again.")
  284. log_seperator()
  285. except Exception as e:
  286. log_error("An error occurred: {:s}".format(str(e)))
  287. log_warn("If you don't have a configuration file, manually create and edit one with the following template:")
  288. log_whiteline()
  289. log_plain(config_template)
  290. log_whiteline()
  291. log_warn("Save it as 'pyinstalive.ini' and run this script again.")
  292. log_seperator()
  293. def clean_download_dir():
  294. dir_delcount = 0
  295. error_count = 0
  296. lock_count = 0
  297. log_info_green('Cleaning up temporary files and folders...')
  298. try:
  299. if sys.version.split(' ')[0].startswith('2'):
  300. directories = (os.walk(settings.save_path).next()[1])
  301. files = (os.walk(settings.save_path).next()[2])
  302. else:
  303. directories = (os.walk(settings.save_path).__next__()[1])
  304. files = (os.walk(settings.save_path).__next__()[2])
  305. for directory in directories:
  306. if directory.endswith('_downloads'):
  307. if not any(filename.endswith('.lock') for filename in os.listdir(settings.save_path + directory)):
  308. try:
  309. shutil.rmtree(settings.save_path + directory)
  310. dir_delcount += 1
  311. except Exception as e:
  312. log_error("Could not remove temp folder: {:s}".format(str(e)))
  313. error_count += 1
  314. else:
  315. lock_count += 1
  316. log_seperator()
  317. log_info_green('The cleanup has finished.')
  318. if dir_delcount == 0 and error_count == 0 and lock_count == 0:
  319. log_info_green('No folders were removed.')
  320. log_seperator()
  321. return
  322. log_info_green('Folders removed: {:d}'.format(dir_delcount))
  323. log_info_green('Locked folders: {:d}'.format(lock_count))
  324. log_info_green('Errors: {:d}'.format(error_count))
  325. log_seperator()
  326. except KeyboardInterrupt as e:
  327. log_seperator()
  328. log_warn("The cleanup has been aborted.")
  329. if dir_delcount == 0 and error_count == 0 and lock_count == 0:
  330. log_info_green('No folders were removed.')
  331. log_seperator()
  332. return
  333. log_info_green('Folders removed: {:d}'.format(dir_delcount))
  334. log_info_green('Locked folders: {:d}'.format(lock_count))
  335. log_info_green('Errors: {:d}'.format(error_count))
  336. log_seperator()
  337. def run():
  338. logging.disable(logging.CRITICAL)
  339. config = configparser.ConfigParser()
  340. parser = argparse.ArgumentParser(description="You are running PyInstaLive {:s} using Python {:s}".format(script_version, python_version))
  341. parser.add_argument('-u', '--username', dest='username', type=str, required=False, help="Instagram username to login with.")
  342. parser.add_argument('-p', '--password', dest='password', type=str, required=False, help="Instagram password to login with.")
  343. parser.add_argument('-r', '--record', dest='download', type=str, required=False, help="The username of the user whose livestream or replay you want to save.")
  344. parser.add_argument('-d', '--download', dest='download', type=str, required=False, help="The username of the user whose livestream or replay you want to save.")
  345. parser.add_argument('-i', '--info', dest='info', action='store_true', help="View information about PyInstaLive.")
  346. parser.add_argument('-c', '--config', dest='config', action='store_true', help="Create a default configuration file if it doesn't exist.")
  347. parser.add_argument('-nr', '--noreplays', dest='noreplays', action='store_true', help="When used, do not check for any available replays.")
  348. parser.add_argument('-nl', '--nolives', dest='nolives', action='store_true', help="When used, do not check for any available livestreams.")
  349. parser.add_argument('-cl', '--clean', dest='clean', action='store_true', help="PyInstaLive will clean the current download folder of all leftover files.")
  350. parser.add_argument('-df', '--downloadfollowing', dest='downloadfollowing', action='store_true', help="PyInstaLive will check for available livestreams and replays from users the account used to login follows.")
  351. parser.add_argument('-cp', '--configpath', dest='configpath', type=str, required=False, help="Path to a PyInstaLive configuration file.")
  352. parser.add_argument('-sp', '--savepath', dest='savepath', type=str, required=False, help="Path to folder where PyInstaLive should save livestreams and replays.")
  353. # Workaround to 'disable' argument abbreviations
  354. parser.add_argument('--usernamx', help=argparse.SUPPRESS, metavar='IGNORE')
  355. parser.add_argument('--passworx', help=argparse.SUPPRESS, metavar='IGNORE')
  356. parser.add_argument('--recorx', help=argparse.SUPPRESS, metavar='IGNORE')
  357. parser.add_argument('--infx', help=argparse.SUPPRESS, metavar='IGNORE')
  358. parser.add_argument('--confix', help=argparse.SUPPRESS, metavar='IGNORE')
  359. parser.add_argument('--noreplayx', help=argparse.SUPPRESS, metavar='IGNORE')
  360. parser.add_argument('--cleax', help=argparse.SUPPRESS, metavar='IGNORE')
  361. parser.add_argument('--downloadfollowinx', help=argparse.SUPPRESS, metavar='IGNORE')
  362. parser.add_argument('--configpatx', help=argparse.SUPPRESS, metavar='IGNORE')
  363. parser.add_argument('-cx', help=argparse.SUPPRESS, metavar='IGNORE')
  364. parser.add_argument('-nx', help=argparse.SUPPRESS, metavar='IGNORE')
  365. parser.add_argument('-dx', help=argparse.SUPPRESS, metavar='IGNORE')
  366. args, unknown_args = parser.parse_known_args()
  367. if args.configpath:
  368. args.configpath = args.configpath.replace("\"", "").replace("'", "")
  369. if os.path.exists(args.configpath):
  370. settings.custom_config_path = args.configpath
  371. try:
  372. config.read(settings.custom_config_path)
  373. settings.log_to_file = config.get('pyinstalive', 'log_to_file').title()
  374. if not settings.log_to_file in bool_values:
  375. settings.log_to_file = 'False'
  376. elif settings.log_to_file == "True":
  377. if args.download:
  378. settings.user_to_download = args.download
  379. try:
  380. with open("pyinstalive{:s}.log".format("_" + settings.user_to_download if len(settings.user_to_download) > 0 else ".default"),"a+") as f:
  381. f.write("\n")
  382. f.close()
  383. except:
  384. pass
  385. except Exception as e:
  386. settings.log_to_file = 'False'
  387. pass # Pretend nothing happened
  388. log_seperator()
  389. log_info_blue('PYINSTALIVE (SCRIPT V{:s} - PYTHON V{:s}) - {:s}'.format(script_version, python_version, time.strftime('%I:%M:%S %p')))
  390. log_seperator()
  391. if args.configpath and settings.custom_config_path != 'pyinstalive.ini':
  392. log_info_blue("Overriding config path: {:s}".format(args.configpath))
  393. log_seperator()
  394. elif args.configpath and settings.custom_config_path == 'pyinstalive.ini':
  395. log_warn("Custom config path does not exist, falling back to path: {:s}".format(os.getcwd()))
  396. log_seperator()
  397. if unknown_args:
  398. log_error("The following invalid argument(s) were provided: ")
  399. log_whiteline()
  400. log_info_blue(' ' + ' '.join(unknown_args))
  401. log_whiteline()
  402. if (supports_color()[1] == True):
  403. log_info_green("'pyinstalive -h' can be used to display command help.")
  404. else:
  405. log_plain("pyinstalive -h can be used to display command help.")
  406. log_seperator()
  407. exit(1)
  408. if (args.info) or (not
  409. args.username and not
  410. args.password and not
  411. args.download and not
  412. args.downloadfollowing and not
  413. args.info and not
  414. args.config and not
  415. args.noreplays and not
  416. args.nolives and not
  417. args.clean and not
  418. args.configpath and not
  419. args.savepath):
  420. show_info(config)
  421. sys.exit(0)
  422. if (args.config):
  423. new_config()
  424. sys.exit(0)
  425. if os.path.exists(settings.custom_config_path):
  426. try:
  427. config.read(settings.custom_config_path)
  428. except Exception:
  429. log_error("Could not read configuration file.")
  430. log_seperator()
  431. else:
  432. new_config()
  433. sys.exit(1)
  434. if check_config_validity(config, args):
  435. try:
  436. if (args.clean):
  437. clean_download_dir()
  438. sys.exit(0)
  439. if not check_ffmpeg():
  440. log_error("Could not find ffmpeg, the script will now exit. ")
  441. log_seperator()
  442. sys.exit(1)
  443. if (args.noreplays):
  444. settings.save_replays = "False"
  445. if (args.nolives):
  446. settings.save_lives = "False"
  447. if settings.save_lives == "False" and settings.save_replays == "False":
  448. log_warn("Script will not run because both live and replay saving is disabled.")
  449. log_seperator()
  450. sys.exit(1)
  451. if not args.download and not args.downloadfollowing:
  452. log_warn("Neither argument -d or -df was passed. Please use one of the two and try again.")
  453. log_seperator()
  454. sys.exit(1)
  455. if args.download and args.downloadfollowing:
  456. log_warn("You can't pass both the -d and -df arguments. Please use one of the two and try again.")
  457. log_seperator()
  458. sys.exit(1)
  459. if (args.username is not None) and (args.password is not None):
  460. api = login(args.username, args.password, settings.show_cookie_expiry, True)
  461. elif (args.username is not None) or (args.password is not None):
  462. log_warn("Missing --username or --password argument, falling back to configuration file...")
  463. if (not len(settings.username) > 0) or (not len(settings.password) > 0):
  464. log_error("Username or password are missing. Please check your configuration file and try again.")
  465. log_seperator()
  466. sys.exit(1)
  467. else:
  468. api = login(settings.username, settings.password, settings.show_cookie_expiry, False)
  469. else:
  470. if (not len(settings.username) > 0) or (not len(settings.password) > 0):
  471. log_error("Username or password are missing. Please check your configuration file and try again.")
  472. log_seperator()
  473. sys.exit(1)
  474. else:
  475. api = login(settings.username, settings.password, settings.show_cookie_expiry, False)
  476. if args.download and not args.downloadfollowing:
  477. start_single(api, args.download, settings)
  478. if not args.download and args.downloadfollowing:
  479. if settings.use_locks.title() == "False":
  480. log_warn("The use of lock files is disabled, this might cause trouble!")
  481. log_seperator()
  482. if check_pyinstalive():
  483. start_multiple(api, settings, "pyinstalive", args)
  484. else:
  485. log_warn("You probably ran PyInstaLive as a script module with the -m argument.")
  486. log_warn("PyInstaLive should be properly installed when using the -df argument.")
  487. log_seperator()
  488. if python_version[0] == "3":
  489. start_multiple(api, settings, "python3 -m pyinstalive", args)
  490. else:
  491. start_multiple(api, settings, "python -m pyinstalive", args)
  492. except KeyboardInterrupt as ee:
  493. log_warn("Pre-download checks have been aborted, exiting...")
  494. log_seperator()
  495. else:
  496. log_error("The configuration file is not valid. Please double-check and try again.")
  497. log_seperator()
  498. sys.exit(1)