From cc5a5b8b32ea2a328b26d5a675466b969b5e7a97 Mon Sep 17 00:00:00 2001 From: Wesley Kerfoot <378351+weskerfoot@users.noreply.github.com> Date: Sat, 7 Mar 2020 11:14:50 -0500 Subject: [PATCH] Fix chromedriver (#120) * add appdirs for managing user data * use cache_dir for downloading chromedriver * cleanly shut down selenium when an exception happens --- deletefb/deletefb.py | 37 ++++++----- deletefb/quit_driver.py | 10 +++ deletefb/tools/chrome_driver.py | 29 +++++--- deletefb/tools/login.py | 114 +++++++++++++++++--------------- deletefb/version.py | 6 ++ requirements.txt | 2 + setup.py | 3 +- 7 files changed, 120 insertions(+), 81 deletions(-) create mode 100644 deletefb/quit_driver.py create mode 100644 deletefb/version.py diff --git a/deletefb/deletefb.py b/deletefb/deletefb.py index 4d8c5a9..efe9320 100755 --- a/deletefb/deletefb.py +++ b/deletefb/deletefb.py @@ -6,6 +6,7 @@ from .tools.login import login from .tools.wall import delete_posts from .tools.conversations import traverse_conversations from .tools.comments import delete_comments +from .quit_driver import quit_driver_and_reap_children import argparse import getpass @@ -116,22 +117,26 @@ def run_delete(): chrome_binary_path=args.chromebin ) - if args.mode == "wall": - delete_posts( - driver, - args.profile_url, - year=args.year - ) - - elif args.mode == "unlike_pages": - unlike_pages(driver, args.profile_url) - - elif args.mode == "conversations": - traverse_conversations(driver, year=args.year) - - else: - print("Please enter a valid mode") - sys.exit(1) + try: + if args.mode == "wall": + delete_posts( + driver, + args.profile_url, + year=args.year + ) + + elif args.mode == "unlike_pages": + unlike_pages(driver, args.profile_url) + + elif args.mode == "conversations": + traverse_conversations(driver, year=args.year) + + else: + print("Please enter a valid mode") + sys.exit(1) + except: + if driver: + quit_driver_and_reap_children(driver) if __name__ == "__main__": run_delete() diff --git a/deletefb/quit_driver.py b/deletefb/quit_driver.py new file mode 100644 index 0000000..18e6539 --- /dev/null +++ b/deletefb/quit_driver.py @@ -0,0 +1,10 @@ +import os + +def quit_driver_and_reap_children(driver): + driver.quit() + try: + pid = True + while pid: + pid = os.waitpid(-1, os.WNOHANG) + except ChildProcessError: + pass diff --git a/deletefb/tools/chrome_driver.py b/deletefb/tools/chrome_driver.py index 1f6081d..d19f0f6 100644 --- a/deletefb/tools/chrome_driver.py +++ b/deletefb/tools/chrome_driver.py @@ -5,12 +5,22 @@ from selenium import webdriver from shutil import which from subprocess import check_output from urllib.request import urlretrieve +from appdirs import AppDirs +from ..version import version import os, sys, stat, platform import progressbar import re import zipfile import requests +import pathlib + +cache_dir = AppDirs("DeleteFB", version=version).user_cache_dir + +try: + pathlib.Path(cache_dir).mkdir(parents=True, exist_ok=True) +except FileExistsError: + pass def extract_zip(filename): """ @@ -25,10 +35,10 @@ def extract_zip(filename): sys.exit(1) # Save the name of the new file - new_file_name = _file.namelist()[0] + new_file_name = f"{cache_dir}/{_file.namelist()[0]}" # Extract the file and make it executable - _file.extractall() + _file.extractall(path=cache_dir) driver_stat = os.stat(new_file_name) os.chmod(new_file_name, driver_stat.st_mode | stat.S_IEXEC) @@ -55,7 +65,6 @@ def get_chrome_version(chrome_binary_path=None): driver_locations = [which(loc) for loc in ["google-chrome", "google-chrome-stable", "chromium", "chrome.exe"]] for location in driver_locations: - print(location) if location: return parse_version(check_output([location, "--version"]).strip()) return None @@ -94,17 +103,19 @@ def get_webdriver(chrome_binary_path): Ensure a webdriver is available If Not, Download it. """ - cwd = os.listdir(os.getcwd()) + webdriver_regex = re.compile('chromedriver') - web_driver = list(filter(webdriver_regex.match, cwd)) + + web_driver = list(filter(webdriver_regex.match, cache_dir)) + if web_driver: # check if a extracted copy already exists - if not os.path.isfile('chromedriver'): + if not os.path.isfile(f"{cache_dir}/chromedriver"): # Extract file extract_zip(web_driver[0]) - return "{0}/chromedriver".format(os.getcwd()) + return "{0}/chromedriver".format(cache_dir) else: # Download it according to the current machine @@ -133,13 +144,13 @@ def get_webdriver(chrome_binary_path): pbar.finish() puts(colored.yellow("Downloading Chrome Webdriver")) - file_name = chrome_webdriver.split('/')[-1] + file_name = f"{cache_dir}/{chrome_webdriver.split('/')[-1]}" response = urlretrieve(chrome_webdriver, file_name, show_progress) if int(response[1].get("Content-Length")) == total_size: puts(colored.green("Completed downloading the Chrome Driver.")) - return "{0}/{1}".format(os.getcwd(), extract_zip(file_name)) + return extract_zip(file_name) else: puts(colored.red("An error Occurred While trying to download the driver.")) diff --git a/deletefb/tools/login.py b/deletefb/tools/login.py index 17866cf..65e44f4 100644 --- a/deletefb/tools/login.py +++ b/deletefb/tools/login.py @@ -1,6 +1,7 @@ from .chrome_driver import get_webdriver, setup_selenium from selenium.common.exceptions import NoSuchElementException from selenium.webdriver.chrome.options import Options +from ..quit_driver import quit_driver_and_reap_children import time @@ -42,59 +43,62 @@ def login(user_email_address, driver_path = get_webdriver(chrome_binary_path) driver = setup_selenium(driver_path, chrome_options) - driver.implicitly_wait(10) - - driver.get("https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110") - - email = "email" - password = "pass" - login_button = "loginbutton" - approvals_code = "approvals_code" - - driver.find_element_by_name(email).send_keys(user_email_address) - driver.find_element_by_name(password).send_keys(user_password) - driver.find_element_by_id(login_button).click() - - # Defaults to no 2fa - has_2fa = False - try: - # If this element exists, we've reached a 2FA page - driver.find_element_by_xpath("//form[@class=\"checkpoint\"]") - driver.find_element_by_xpath("//input[@name=\"approvals_code\"]") - has_2fa = True - except NoSuchElementException: - has_2fa = "two-factor authentication" in driver.page_source.lower() or has_2fa - - if has_2fa: - print(""" - Two-Factor Auth is enabled. - Please file an issue at https://github.com/weskerfoot/DeleteFB/issues if you run into any problems - """) - - if two_factor_token and has_2fa: - twofactorelement = driver.find_element_by_name(approvals_code) - twofactorelement.send_keys(two_factor_token) - - # Submits after the code is passed into the form, does not validate 2FA code. - contelement = driver.find_element_by_id("checkpointSubmitButton") - contelement.click() - - # Defaults to saving this new browser, this occurs on each new automated login. - save_browser = driver.find_element_by_id("checkpointSubmitButton") - save_browser.click() - elif has_2fa: - # Allow time to enter 2FA code - print("Pausing to enter 2FA code") - time.sleep(35) - print("Continuing execution") - else: - pass - - # block until we have reached the main page - # print a message warning the user - while driver.current_url != "https://www.facebook.com/": - print("Execution blocked: Please navigate to https://www.facebook.com to continue") - time.sleep(5) - - return driver + driver.implicitly_wait(10) + + driver.get("https://www.facebook.com/login/device-based/regular/login/?login_attempt=1&lwv=110") + + email = "email" + password = "pass" + login_button = "loginbutton" + approvals_code = "approvals_code" + + driver.find_element_by_name(email).send_keys(user_email_address) + driver.find_element_by_name(password).send_keys(user_password) + driver.find_element_by_id(login_button).click() + + # Defaults to no 2fa + has_2fa = False + + try: + # If this element exists, we've reached a 2FA page + driver.find_element_by_xpath("//form[@class=\"checkpoint\"]") + driver.find_element_by_xpath("//input[@name=\"approvals_code\"]") + has_2fa = True + except NoSuchElementException: + has_2fa = "two-factor authentication" in driver.page_source.lower() or has_2fa + + if has_2fa: + print(""" + Two-Factor Auth is enabled. + Please file an issue at https://github.com/weskerfoot/DeleteFB/issues if you run into any problems + """) + + if two_factor_token and has_2fa: + twofactorelement = driver.find_element_by_name(approvals_code) + twofactorelement.send_keys(two_factor_token) + + # Submits after the code is passed into the form, does not validate 2FA code. + contelement = driver.find_element_by_id("checkpointSubmitButton") + contelement.click() + + # Defaults to saving this new browser, this occurs on each new automated login. + save_browser = driver.find_element_by_id("checkpointSubmitButton") + save_browser.click() + elif has_2fa: + # Allow time to enter 2FA code + print("Pausing to enter 2FA code") + time.sleep(35) + print("Continuing execution") + else: + pass + + # block until we have reached the main page + # print a message warning the user + while driver.current_url != "https://www.facebook.com/": + print("Execution blocked: Please navigate to https://www.facebook.com to continue") + time.sleep(5) + + return driver + except: + quit_driver_and_reap_children(driver) diff --git a/deletefb/version.py b/deletefb/version.py new file mode 100644 index 0000000..edbe5b0 --- /dev/null +++ b/deletefb/version.py @@ -0,0 +1,6 @@ +import pkg_resources # part of setuptools + +try: + version = pkg_resources.require("delete-facebook-posts")[0].version +except pkg_resources.DistributionNotFound: + version = "source" diff --git a/requirements.txt b/requirements.txt index 6a0398b..4e6b621 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ +appdirs==1.4.3 +args==0.1.0 attrs==19.1.0 bitarray==0.9.3 bleach==3.1.1 diff --git a/setup.py b/setup.py index 816eafb..a0492ed 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,8 @@ setuptools.setup( "lxml", "pendulum", "clint", - "progressbar" + "progressbar", + "appdirs" ], classifiers= [ "Programming Language :: Python :: 3",