diff --git a/.gitignore b/.gitignore index c45468f..49018cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .envrc __pycache__ venv +.mypy_cache diff --git a/deletefb/deletefb.py b/deletefb/deletefb.py index 4d8c5a9..68a1127 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 .tools.env_variables import EnvParser import argparse import getpass @@ -13,8 +14,18 @@ import sys LOG = logger("deletefb") + def run_delete(): parser = argparse.ArgumentParser() + env_parser = EnvParser() + + parser.add_argument( + "--help-env", + action="store_true", + dest="help_env", + default=False, + help="Print help about environment variables" + ) parser.add_argument( "-M", @@ -30,7 +41,7 @@ def run_delete(): parser.add_argument( "-E", "--email", - required=True, + required='--help-env' not in sys.argv, dest="email", type=str, help="Your email address associated with the account" @@ -48,7 +59,7 @@ def run_delete(): parser.add_argument( "-U", "--profile-url", - required=True, + required='--help-env' not in sys.argv, dest="profile_url", type=str, help="The link to your Facebook profile, e.g. https://www.facebook.com/your.name" @@ -99,9 +110,23 @@ def run_delete(): help="Optional path to the Google Chrome (or Chromium) binary" ) + env_parser.add_argument( + "DELETEFB_RETRY_THRESHOLD", + default=5, + dest="retry_threshold", + type=int, + help="Number of refreshes to ensure your wall is empty before quitting" + ) + args = parser.parse_args() + env = env_parser.parse_args() + + if args.help_env: + env_parser.print_help() + sys.exit(0) settings["ARCHIVE"] = not args.archive_off + settings["RETRY_THRESHOLD"] = env.retry_threshold if args.year and args.mode not in ("wall", "conversations"): parser.error("The --year option is not supported in this mode") @@ -133,5 +158,6 @@ def run_delete(): print("Please enter a valid mode") sys.exit(1) + if __name__ == "__main__": run_delete() diff --git a/deletefb/tools/config.py b/deletefb/tools/config.py index 078efca..16fd87a 100644 --- a/deletefb/tools/config.py +++ b/deletefb/tools/config.py @@ -1,4 +1,5 @@ settings = { "ARCHIVE" : True, - "MAX_POSTS" : 5000 + "MAX_POSTS" : 5000, + "RETRY_THRESHOLD": 5 } diff --git a/deletefb/tools/env_variables.py b/deletefb/tools/env_variables.py new file mode 100644 index 0000000..d02ac3e --- /dev/null +++ b/deletefb/tools/env_variables.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import sys +import os +import re +import typing as ty + +from types import SimpleNamespace + + +class EnvVar: + def __init__(self, + env_key: str, + dest: ty.Optional[str] = None, + type: ty.Type = str, + default: ty.Optional[str] = None, + help: ty.Optional[str] = None): + + assert re.match('^[a-zA-Z_]+[a-zA-Z0-9_]*$', env_key), "Bad env key {0}".format(env_key) + self.key = env_key + self.dest = dest or env_key.lower() + self.type = type + self.help = help + self.default = default + + +class EnvParser: + def __init__(self): + self.env_vars = set() + + def add_argument(self, *args, **kwargs): + self.env_vars.add(EnvVar(*args, **kwargs)) + + def print_help(self): + print("environment variables:") + for v in self.env_vars: + print(" {0}=<{1}>\t{2}".format(v.key, v.type.__name__, v.help or '')) + + def parse_args(self): + args = {} + for v in self.env_vars: + try: + args[v.dest] = v.type(os.environ.get(v.key, v.default)) + except ValueError: + print("{0}: error: environment variable {1} should be of type {2}".format( + sys.argv[0].split('/')[-1], + v.key, + v.type.__name__)) + sys.exit(1) + return SimpleNamespace(**args) diff --git a/deletefb/tools/wall.py b/deletefb/tools/wall.py index fb8ff69..3ed93d4 100644 --- a/deletefb/tools/wall.py +++ b/deletefb/tools/wall.py @@ -26,7 +26,11 @@ def delete_posts(driver, driver.get(user_profile_url) + retry_threshold = 0 for _ in range(MAX_POSTS): + if retry_threshold >= settings["RETRY_THRESHOLD"]: + break + post_button_sel = "_4xev" post_content_sel = "userContent" @@ -36,6 +40,9 @@ def delete_posts(driver, with archiver("wall") as archive_wall_post: while True: + if retry_threshold >= settings["RETRY_THRESHOLD"]: + break + try: timeline_element = driver.find_element_by_class_name(post_button_sel) @@ -67,15 +74,18 @@ def delete_posts(driver, continue if not delete_button: + retry_threshold += 1 print("Could not find anything to delete") break + else: + retry_threshold = 0 click_button(driver, delete_button) confirmation_button = driver.find_element_by_class_name("layerConfirm") click_button(driver, confirmation_button) - except SELENIUM_EXCEPTIONS as e: + retry_threshold += 1 print(e) continue else: