diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f7c2177 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +# To run, just type "make", or + +# docker build -t deletefb . +# docker run -ti --rm \ +# -e DISPLAY=$DISPLAY \ +# -v /tmp/.X11-unix:/tmp/.X11-unix \ +# --cap-add=SYS_ADMIN \ +# --cap-add=NET_ADMIN \ +# --cpuset-cpus 0 \ +# --memory 4GB \ +# -v /tmp/.X11-unix:/tmp/.X11-unix \ +# -e DISPLAY=unix:0 \ +# --device /dev/snd \ +# --device /dev/dri \ +# -v /dev/shm:/dev/shm \ +# deletefb -e mail="your@email.com" -e pass="Y0Ur*P4ss" -e url="http://facebook.com/your-username" deletefb:latest + +FROM debian:stable-slim + + RUN apt-get update && \ + apt-get install -y \ + git \ + python3 \ + python3-pip \ + libcanberra-gtk-module \ + curl \ + sudo \ + vim \ + unzip \ + chromium \ + chromium-driver + +#creating new user + ENV user deletefb + RUN export uid=1000 gid=1000 && \ + mkdir -p /home/${user} && \ + echo "${user}:x:${uid}:${gid}:${user},,,:/home/${user}:/bin/bash" >> /etc/passwd && \ + echo "${user}:x:${uid}:" >> /etc/group && \ + echo "${user} ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/${user} && \ + chmod 0440 /etc/sudoers.d/${user} && \ + chown ${uid}:${gid} -R /home/${user} && \ + usermod -aG sudo ${user} + + +# deletefb install + USER ${user} + WORKDIR /home/${user} + + ARG email + ARG pass + ARG url + #ARG --conversations + + RUN pip3 install --user delete-facebook-posts + RUN pip3 install --user selenium attrs pybloom_live + + ADD run.sh /tmp/run.sh + ENTRYPOINT [ "/tmp/run.sh" ] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..87bf189 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# Makefile + +NAME:= deletefb + +.PHONY: all build run + +all: build run + +build: + @docker build -t $(NAME) . + +run: + @read -p "Enter your Facebook email: " email && read -p "Enter your Facebook password: " password && read -p "Enter your Facebook username: " username && docker run -ti --rm \ + -e DISPLAY=$$DISPLAY \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + --cap-add=SYS_ADMIN \ + --cap-add=NET_ADMIN \ + --cpuset-cpus 0 \ + --device /dev/dri \ + -v /dev/shm:/dev/shm \ + -e EMAIL="$$email" \ + -e PASS="$$password" \ + -e URL="https://facebook.com/$$username" \ + $(NAME):latest \ No newline at end of file diff --git a/README.md b/README.md index 856e1f3..41730e5 100644 --- a/README.md +++ b/README.md @@ -19,69 +19,46 @@ Personally, I did this so I would feel less attached to my Facebook profile ## Installation You have several options to run it. -1) Install from PyPI with `pip3 install --user delete-facebook-posts` -2) Clone this repo and run `pip3 install --user .` or do `pip3 install --user +1) Install from PyPI with `pip install --user delete-facebook-posts` +2) Clone this repo and run `pip install --user .` or do `pip install --user git+https://github.com/weskerfoot/DeleteFB.git` -3) Set up a Python virtualenv, activate it, and run `pip3 install -r requirements.txt`, then you can just run `python -m deletefb.deletefb` in the DeleteFB directory. +3) Set up a Python virtualenv, activate it, and run `pip install -r +requirements.txt`, then you can just run `python deletefb/deletefb.py` as you +would if you had installed it from PyPI. ## How To Use It -* Make sure that you have a recent version of Python 3.x installed (preferably - 3.6 or greater) * Make sure that you have Google Chrome installed and that it is up to date -* Also install the chromedriver for Selenium. See [here](https://sites.google.com/a/chromium.org/chromedriver/home) for an explanation of what the chromedriver does. - * On Linux, it will be called something like `chromium-chromedriver` or just - `chromium`. - * On MacOS, it will be available via brew, with the following commands: - - ``` - brew tap homebrew/cask; - brew cask install chromedriver - ``` - -* Run `deletefb -E 'youremail@example.org' -P 'yourfacebookpassword' -U 'https://www.facebook.com/your.profile.url'` +* Also install the chromedriver for Selenium. See [here](https://sites.google.com/a/chromium.org/chromedriver/downloads). On Arch Linux you can find this in the `chromium` package, and on Ubuntu it is `chromium-chromedriver`. +* Run `deletefb -E "youremail@example.org" -P "yourfacebookpassword" -U "https://www.facebook.com/your.profile.url"` * The script will log into your Facebook account, go to your profile page, and start deleting posts. If it cannot delete something, then it will "hide" it from your timeline instead. * Be patient as it will take a very long time, but it will eventually clear everything. You may safely minimize the chrome window without breaking it. -## Login -* The tool will log in using the credentials passed to it. It will wait until the page `https://www.facebook.com/` is loaded in order to avoid any issues with logging in. If you pass a 2FA token explicitly with the `-F` option, then it will try to enter that for you. If there are any issues, it simply pauses indefinitely to allow the user to resolve the problems, and then continues execution. - ## 2FA -* It is recommended that you disable Two-Factor Authentication temporarily - while you are running the script, in order to get the best experience. - -* If you run into issues with Facebook complaining about your browser, - currently the only workaround is to manually click through them. - -* If you do have 2-Factor Auth configured then the script will pause for 35 +* If you have 2-Factor Auth configured then the script will pause for 20 seconds to allow you to enter your code and log in. -* You may also pass in a code by using the `-F` argument, e.g. `-F 111111`. +## Headless mode +* The tool supports running Chrome in headless mode with the `--headless` + option, which may be preferable if you plan on running it in the background. + +## How To Install Python -## Delete By Year -* The tool supports passing the `--year` flag in order to delete wall posts by - year. E.g. `-Y 2010` would delete posts from the year 2010. It is incompatible with any mode other than `wall`. +### MacOS +See [this link](https://docs.python-guide.org/starting/install3/osx/) for +instructions on installing with Brew. -## Unlike Pages -* You may use `-M unlike_pages` to unlike all of your pages. The names of the - pages will be archived (unless archival is turned off), and this option - conflicts with the year option. This will only unlike your *pages* that you - have liked. It will *not* unlike anything else (like books or movies). +### Linux +Use your native package manager -## Archival -* The tool will archive everything being deleted by default in `.log` files. - Currently they are simply stored as JSON objects for each line in the log. It - will archive the content, and a timestamp if it is available. You may disable - this feature by using `--no-archive`. +### Windows +See [this link](https://www.howtogeek.com/197947/how-to-install-python-on-windows/), but I make no guarantees that Selenium will actually work as I have not tested it. -## Headless mode -* The tool supports running Chrome in headless mode with the `--headless` - option, which may be preferable if you plan on running it in the background. -## Bugs +### Bugs If it stops working or otherwise crashes, delete the latest post manually and start it again after waiting a minute. I make no guarantees that it will work diff --git a/deletefb/__main__.py b/deletefb/__main__.py deleted file mode 100644 index c735177..0000000 --- a/deletefb/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -import deletefb - -if __name__ == "__main__": - deletefb.run_delete() diff --git a/deletefb/deletefb.py b/deletefb/deletefb.py index 6688f4c..01dd660 100755 --- a/deletefb/deletefb.py +++ b/deletefb/deletefb.py @@ -1,30 +1,19 @@ -#!/usr/bin/env python -from .tools.common import logger -from .tools.config import settings -from .tools.likes import unlike_pages -from .tools.login import login -from .tools.wall import delete_posts +#! /usr/bin/env python import argparse +import time import getpass -import sys -LOG = logger("deletefb") +from seleniumrequests import Chrome +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.chrome.options import Options +from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException + +MAX_POSTS = 5000 +SELENIUM_EXCEPTIONS = (NoSuchElementException, StaleElementReferenceException) def run_delete(): parser = argparse.ArgumentParser() - - parser.add_argument( - "-M", - "--mode", - required=False, - default="wall", - dest="mode", - type=str, - choices=["wall", "unlike_pages"], - help="The mode you want to run in. Default is `wall' which deletes wall posts" - ) - parser.add_argument( "-E", "--email", @@ -52,15 +41,6 @@ def run_delete(): help="The link to your Facebook profile, e.g. https://www.facebook.com/your.name" ) - parser.add_argument( - "-F", - "--two-factor", - required=False, - dest="two_factor_token", - type=str, - help="The code generated by your 2FA device for Facebook" - ) - parser.add_argument( "-H", "--headless", @@ -70,51 +50,93 @@ def run_delete(): help="Run browser in headless mode (no gui)" ) - parser.add_argument( - "--no-archive", - action="store_true", - dest="archive_off", - default=False, - help="Turn off archiving (on by default)" - ) - - parser.add_argument( - "-Y", - "--year", - required=False, - dest="year", - type=str, - help="The year(s) you want posts deleted." - ) - args = parser.parse_args() - settings["ARCHIVE"] = not args.archive_off - - if args.year and args.mode != "wall": - parser.error("The --year option is only supported in wall mode") - args_user_password = args.password or getpass.getpass('Enter your password: ') - driver = login( + delete_posts( user_email_address=args.email, user_password=args_user_password, - is_headless=args.is_headless, - two_factor_token=args.two_factor_token + user_profile_url=args.profile_url, + is_headless=args.is_headless ) - if args.mode == "wall": - delete_posts( - driver, - args.profile_url, - year=args.year - ) - - elif args.mode == "unlike_pages": - unlike_pages(driver, args.profile_url) - else: - print("Please enter a valid mode") - sys.exit(1) +def delete_posts(user_email_address, + user_password, + user_profile_url, + is_headless): + """ + user_email_address: str Your Email + user_password: str Your password + user_profile_url: str Your profile URL + """ + # The Chrome driver is required because Gecko was having issues + chrome_options = Options() + prefs = {"profile.default_content_setting_values.notifications": 2, 'disk-cache-size': 4096} + chrome_options.add_experimental_option("prefs", prefs) + chrome_options.add_argument("start-maximized") + + if is_headless: + chrome_options.add_argument('--headless') + chrome_options.add_argument('--disable-gpu') + chrome_options.add_argument('log-level=2') + + driver = Chrome(options=chrome_options) + driver.implicitly_wait(10) + + driver.get("https://facebook.com") + + email = "email" + password = "pass" + login = "loginbutton" + + emailelement = driver.find_element_by_name(email) + passwordelement = driver.find_element_by_name(password) + + emailelement.send_keys(user_email_address) + passwordelement.send_keys(user_password) + + loginelement = driver.find_element_by_id(login) + loginelement.click() + + if "two-factor authentication" in driver.page_source.lower(): + # Allow time to enter 2FA code + print("Pausing to enter 2FA code") + time.sleep(20) + print("Continuing execution") + driver.get(user_profile_url) + + for _ in range(MAX_POSTS): + post_button_sel = "_4xev" + + while True: + try: + timeline_element = driver.find_element_by_class_name(post_button_sel) + actions = ActionChains(driver) + actions.move_to_element(timeline_element).click().perform() + + menu = driver.find_element_by_css_selector("#globalContainer > div.uiContextualLayerPositioner.uiLayer > div") + actions.move_to_element(menu).perform() + + try: + delete_button = menu.find_element_by_xpath("//a[@data-feed-option-name=\"FeedDeleteOption\"]") + except SELENIUM_EXCEPTIONS: + delete_button = menu.find_element_by_xpath("//a[@data-feed-option-name=\"HIDE_FROM_TIMELINE\"]") + + actions.move_to_element(delete_button).click().perform() + confirmation_button = driver.find_element_by_class_name("layerConfirm") + + # Facebook would not let me get focus on this button without some custom JS + driver.execute_script("arguments[0].click();", confirmation_button) + except SELENIUM_EXCEPTIONS: + continue + else: + break + + # Required to sleep the thread for a bit after using JS to click this button + time.sleep(5) + driver.refresh() + if __name__ == "__main__": run_delete() diff --git a/deletefb/logging_conf.json b/deletefb/logging_conf.json deleted file mode 100644 index e8037ec..0000000 --- a/deletefb/logging_conf.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "logging": { - "version": 1, - "disable_existing_loggers": true, - "formatters": { - "brief": { - "class": "logging.Formatter", - "style": "{", - "datefmt": "%H:%M:%S", - "format": "{name:s}-{levelname:s}-{asctime:s}-{message:s}" - }, - "verbose": { - "class": "logging.Formatter", - "style": "{", - "datefmt": "%Y-%m-%dT%H:%M:%S", - "format": "{name:s}:{levelname:s}:L{lineno:d} {asctime:s} {message:s}" - } - }, - "handlers": { - "console": { - "level": "DEBUG", - "class": "logging.StreamHandler", - "formatter": "brief", - "stream": "ext://sys.stdout" - }, - "file_handler": { - "level": "INFO", - "class": "logging.handlers.WatchedFileHandler", - "formatter": "verbose", - "filename": "./deletefb.log", - "mode": "a", - "encoding": "utf-8" - } - }, - "loggers": { - "root": { - "level": "DEBUG", - "handlers": ["console", "file_handler"] - }, - "deletefb": { - "level": "DEBUG", - "handlers": ["console"], - "propagate": false - }, - "login": { - "level": "DEBUG", - "handlers": ["file_handler"], - "propagate": false - }, - "likes": { - "level": "DEBUG", - "handlers": ["file_handler"], - "propagate": false - }, - "wall": { - "level": "DEBUG", - "handlers": ["file_handler"], - "propagate": false - } - } - } -} diff --git a/deletefb/tools/__init__.py b/deletefb/tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/deletefb/tools/archive.py b/deletefb/tools/archive.py deleted file mode 100644 index 1bc7364..0000000 --- a/deletefb/tools/archive.py +++ /dev/null @@ -1,54 +0,0 @@ -from .config import settings -from contextlib import contextmanager -from pathlib import Path - -import attr -import json - -# Used to avoid duplicates in the log -from pybloom_live import BloomFilter - -def make_filter(): - return BloomFilter( - capacity=settings["MAX_POSTS"], - error_rate=0.001 - ) - -@attr.s -class Archive: - archive_type = attr.ib() - - # We give the Archive class a file handle - archive_file = attr.ib() - - _bloom_filter = attr.ib(factory=make_filter) - - def archive(self, content): - """ - Archive an object - """ - print("Archiving {0}".format(content)) - - if content.name not in self._bloom_filter: - self.archive_file.write(json.dumps(attr.asdict(content)) + "\n") - self._bloom_filter.add(content.name) - return - -@contextmanager -def archiver(archive_type): - - archive_file = open( - (Path(".") / Path(archive_type).name).with_suffix(".log"), - mode="ta", - buffering=1 - ) - - archiver_instance = Archive( - archive_type=archive_type, - archive_file=archive_file - ) - - try: - yield archiver_instance - finally: - archive_file.close() diff --git a/deletefb/tools/common.py b/deletefb/tools/common.py deleted file mode 100644 index 7f6364a..0000000 --- a/deletefb/tools/common.py +++ /dev/null @@ -1,51 +0,0 @@ -from os.path import isfile -from selenium.common.exceptions import ( - NoSuchElementException, - StaleElementReferenceException, - TimeoutException -) - -import json -import logging -import logging.config -import os - -SELENIUM_EXCEPTIONS = ( - NoSuchElementException, - StaleElementReferenceException, - TimeoutException -) - -def click_button(driver, el): - """ - Click a button using Javascript - Args: - driver: seleniumrequests.Chrome Driver instance - Returns: - None - """ - driver.execute_script("arguments[0].click();", el) - -def logger(name): - """ - Args: - name (str): Logger name - - Returns: - logging.Logger - """ - - # Make sure the path always points to the correct directory - config_path = os.path.dirname(os.path.realpath(__file__)) + "/../logging_conf.json" - - if not isfile(config_path): # called from file (deletefb.py) - os.chdir("..") - with open(config_path, "r", encoding="utf-8") as config_file: - config = json.load(config_file) - logging.config.dictConfig(config["logging"]) - return logging.getLogger(name) - -NO_CHROME_DRIVER = """ -You need to install the chromedriver for Selenium\n -Please see this link https://github.com/weskerfoot/DeleteFB#how-to-use-it\n -""" diff --git a/deletefb/tools/config.py b/deletefb/tools/config.py deleted file mode 100644 index 078efca..0000000 --- a/deletefb/tools/config.py +++ /dev/null @@ -1,4 +0,0 @@ -settings = { - "ARCHIVE" : True, - "MAX_POSTS" : 5000 -} diff --git a/deletefb/tools/likes.py b/deletefb/tools/likes.py deleted file mode 100644 index 091a144..0000000 --- a/deletefb/tools/likes.py +++ /dev/null @@ -1,114 +0,0 @@ -from .archive import archiver -from ..types import Page -from .common import SELENIUM_EXCEPTIONS, logger, click_button -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.support.ui import WebDriverWait - -LOG = logger(__name__) - -def load_likes(driver, profile_url): - """ - Loads the page that lists all pages you like - - Args: - driver: seleniumrequests.Chrome Driver instance - - Returns: - None - """ - - driver.get("{0}/likes_all".format(profile_url)) - - wait = WebDriverWait(driver, 20) - - try: - wait.until( - EC.presence_of_element_located((By.CSS_SELECTOR, ".PageLikeButton")) - ) - except SELENIUM_EXCEPTIONS: - LOG.exception("Traceback of load_likes") - return - -def get_page_links(driver): - """ - Gets all of the links to the pages you like - - Args: - driver: seleniumrequests.Chrome Driver instance - Returns: - List of URLs to pages - """ - pages = driver.find_elements_by_xpath("//li//div/div/a[contains(@class, 'lfloat')]") - - return [page.get_attribute("href").replace("www", "mobile") for page in pages] - -def unlike_page(driver, url, archive=None): - """ - Unlikes a page given the URL to it - Args: - driver: seleniumrequests.Chrome Driver instance - url: url string pointing to a page - archive: archiver instance - - Returns: - None - - """ - - driver.get(url) - - print("Unliking {0}".format(url)) - - wait = WebDriverWait(driver, 20) - - try: - wait.until( - EC.presence_of_element_located((By.XPATH, "//*[text()='Liked']")) - ) - except SELENIUM_EXCEPTIONS: - # Something went wrong with this page, so skip it - return - - button = driver.find_element_by_xpath("//*[text()='Liked']") - - # Click the "Liked" button to open up "Unlike" - click_button(driver, button) - - wait.until( - EC.presence_of_element_located((By.XPATH, "//*[text()='Unlike']")) - ) - - # There should be an "Unlike" button now, click it - unlike_button = driver.find_element_by_xpath("//*[text()='Unlike']/..") - - click_button(driver, unlike_button) - - if archive: - archive(Page(name=url)) - -def unlike_pages(driver, profile_url): - """ - Unlike all pages - - Args: - driver: seleniumrequests.Chrome Driver instance - - Returns: - None - """ - - with archiver("likes") as archive_likes: - load_likes(driver, profile_url) - - urls = get_page_links(driver) - - while urls: - for url in urls: - unlike_page(driver, url, archive=archive_likes.archive) - try: - load_likes(driver, profile_url) - urls = get_page_links(driver) - except SELENIUM_EXCEPTIONS: - # We're done - break diff --git a/deletefb/tools/login.py b/deletefb/tools/login.py deleted file mode 100644 index 772b53e..0000000 --- a/deletefb/tools/login.py +++ /dev/null @@ -1,106 +0,0 @@ -from .common import NO_CHROME_DRIVER -from selenium.common.exceptions import NoSuchElementException -from selenium.webdriver.chrome.options import Options -from seleniumrequests import Chrome - -import sys -import time - -def login(user_email_address, - user_password, - is_headless, - two_factor_token): - """ - Attempts to log into Facebook - Returns a driver object - - Args: - user_email_address: str Your email - user_password: str Your password - user_profile_url: str Your profile URL - - Returns: - seleniumrequests.Chrome instance - - """ - # The Chrome driver is required because Gecko was having issues - chrome_options = Options() - prefs = {"profile.default_content_setting_values.notifications": 2, 'disk-cache-size': 4096} - chrome_options.add_experimental_option("prefs", prefs) - chrome_options.add_argument("start-maximized") - - if is_headless: - chrome_options.add_argument('--headless') - chrome_options.add_argument('--disable-gpu') - chrome_options.add_argument('log-level=2') - - try: - driver = Chrome(options=chrome_options) - except Exception as e: - # The user does not have chromedriver installed - # Tell them to install it - sys.stderr.write(str(e)) - sys.stderr.write(NO_CHROME_DRIVER) - sys.exit(1) - - 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" - - emailelement = driver.find_element_by_name(email) - passwordelement = driver.find_element_by_name(password) - - emailelement.send_keys(user_email_address) - passwordelement.send_keys(user_password) - - loginelement = driver.find_element_by_id(login_button) - loginelement.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 diff --git a/deletefb/tools/wall.py b/deletefb/tools/wall.py deleted file mode 100644 index 890bae0..0000000 --- a/deletefb/tools/wall.py +++ /dev/null @@ -1,85 +0,0 @@ -from ..types import Post -from .archive import archiver -from .common import SELENIUM_EXCEPTIONS, click_button -from .config import settings -from selenium.webdriver.common.action_chains import ActionChains - -import time - -# Used as a threshold to avoid running forever -MAX_POSTS = settings["MAX_POSTS"] - -def delete_posts(driver, - user_profile_url, - year=None): - """ - Deletes or hides all posts from the wall - - Args: - driver: seleniumrequests.Chrome Driver instance - user_profile_url: str - year: optional int YYYY year - """ - - if year is not None: - user_profile_url = "{0}/timeline?year={1}".format(user_profile_url, year) - - driver.get(user_profile_url) - - for _ in range(MAX_POSTS): - post_button_sel = "_4xev" - - post_content_sel = "userContent" - post_timestamp_sel = "timestampContent" - - button_types = ["FeedDeleteOption", "HIDE_FROM_TIMELINE", "UNTAG"] - - with archiver("wall") as archive_wall_post: - while True: - try: - timeline_element = driver.find_element_by_class_name(post_button_sel) - - post_content_element = driver.find_element_by_class_name(post_content_sel) - post_content_ts = driver.find_element_by_class_name(post_timestamp_sel) - - - # Archive the post - archive_wall_post.archive( - Post( - content=post_content_element.text, - date=post_content_ts.text - ) - ) - - actions = ActionChains(driver) - actions.move_to_element(timeline_element).click().perform() - - menu = driver.find_element_by_css_selector("#globalContainer > div.uiContextualLayerPositioner.uiLayer > div") - actions.move_to_element(menu).perform() - - delete_button = None - - for button_type in button_types: - try: - delete_button = menu.find_element_by_xpath("//a[@data-feed-option-name=\"{0}\"]".format(button_type)) - break - except SELENIUM_EXCEPTIONS: - continue - - if not delete_button: - print("Could not find anything to delete") - break - - actions.move_to_element(delete_button).click().perform() - confirmation_button = driver.find_element_by_class_name("layerConfirm") - - click_button(driver, confirmation_button) - - except SELENIUM_EXCEPTIONS: - continue - else: - break - - # Required to sleep the thread for a bit after using JS to click this button - time.sleep(5) - driver.refresh() diff --git a/deletefb/types.py b/deletefb/types.py deleted file mode 100644 index a771c67..0000000 --- a/deletefb/types.py +++ /dev/null @@ -1,29 +0,0 @@ -import attr -import uuid -import datetime - -def timestamp_now(): - """ - Returns: a timestamp for this instant, in ISO 8601 format - """ - return datetime.datetime.isoformat(datetime.datetime.now()) - -# Data type definitions of posts and comments -@attr.s -class Post: - content = attr.ib() - comments = attr.ib(default=[]) - date = attr.ib(factory=timestamp_now) - name = attr.ib(factory=lambda: uuid.uuid4().hex) - -@attr.s -class Comment: - commenter = attr.ib() - content = attr.ib() - date = attr.ib(factory=timestamp_now) - name = attr.ib(factory=lambda: uuid.uuid4().hex) - -@attr.s -class Page: - name = attr.ib() - date = attr.ib(factory=timestamp_now) diff --git a/requirements.txt b/requirements.txt index 0780145..0511c3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,6 @@ -attrs==19.1.0 -bitarray==0.9.3 certifi==2018.11.29 chardet==3.0.4 idna==2.8 -pybloom-live==3.0.0 requests==2.22.0 requests-file==1.4.3 selenium==3.141.0 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..6a6897b --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +/usr/bin/python3 -m deletefb.deletefb -E $EMAIL -P $PASS -U $URL \ No newline at end of file diff --git a/setup.py b/setup.py index a9b692d..745c8c5 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r") as fh: setuptools.setup( name="delete-facebook-posts", - version="1.1.7", + version="1.0.4", author="Wesley Kerfoot", author_email="wes@wesk.tech", description="A Selenium Script to Delete Facebook Posts", @@ -13,18 +13,10 @@ setuptools.setup( long_description_content_type="text/markdown", url="https://github.com/weskerfoot/DeleteFB", packages=setuptools.find_packages(), - include_package_data=True, - requires_python=">=3.6", - package_data={ - # Include *json files in the package: - '': ['*.json'], - }, install_requires = [ "selenium", "selenium-requests", - "requests", - "pybloom-live", - "attrs" + "requests" ], classifiers= [ "Programming Language :: Python :: 3",