Browse Source

Merge pull request #31 from weskerfoot/various-improvements

Various improvements
pull/44/head
Wesley Kerfoot 5 years ago
committed by GitHub
parent
commit
c437fa41f8
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      README.md
  2. 49
      deletefb/deletefb.py
  3. 51
      deletefb/tools/common.py
  4. 65
      deletefb/tools/likes.py
  5. 68
      deletefb/tools/login.py
  6. 26
      deletefb/tools/wall.py

13
README.md

@ -49,11 +49,24 @@ requirements.txt`, then you can just run `python -m deletefb.deletefb.py` in the
* It is recommended that you disable Two-Factor Authentication tempoprarily
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 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`.
## Delete by year
* The tool supports passing the `--year` flag in order to delete wall posts by
year. It is incompatible with any mode other than `wall`.
## 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`.
## 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.

49
deletefb/deletefb.py

@ -3,11 +3,26 @@
import argparse
import getpass
from sys import exit
from os import environ
from .tools.login import login
from .tools.wall import delete_posts
from .tools.likes import unlike_pages
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",
@ -53,19 +68,49 @@ def run_delete():
help="Run browser in headless mode (no gui)"
)
parser.add_argument(
"--no-archive",
action="store_true",
dest="archive_off",
default=True,
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()
if args.archive_off:
environ["DELETEFB_ARCHIVE"] = "false" if args.archive_off else "true"
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(
user_email_address=args.email,
user_password=args_user_password,
user_profile_url=args.profile_url,
is_headless=args.is_headless,
two_factor_token=args.two_factor_token
)
delete_posts(driver)
if args.mode == "wall":
delete_posts(driver,
args.profile_url,
year=args.year)
elif args.mode == "unlike_pages":
unlike_pages(driver)
else:
print("Please enter a valid mode")
exit(1)
if __name__ == "__main__":
run_delete()

51
deletefb/tools/common.py

@ -1,3 +1,50 @@
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException
from selenium.common.exceptions import (NoSuchElementException,
StaleElementReferenceException,
TimeoutException)
from time import sleep
from json import dumps
from os.path import abspath, relpath, split
from os import environ
SELENIUM_EXCEPTIONS = (NoSuchElementException, StaleElementReferenceException)
SELENIUM_EXCEPTIONS = (NoSuchElementException,
StaleElementReferenceException,
TimeoutException)
def try_move(actions, el):
for _ in range(10):
try:
actions.move_to_element(el).perform()
except StaleElementReferenceException:
sleep(5)
continue
def archiver(category):
"""
category: the category of logs you want to log
return values: (log_file_handle, archiver)
call archiver like archive("some content")
"""
log_path = "{0}.log".format(abspath(relpath(split(category)[-1], ".")))
log_file = open(log_path, mode="wt", buffering=1)
def log(content, timestamp=False):
if environ.get("DELETEFB_ARCHIVE", "true") == "false":
return
structured_content = {
"category" : category,
"content" : content,
"timestamp" : timestamp
}
log_file.write("{0}\n".format(dumps(structured_content)))
return (log_file, log)
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
"""

65
deletefb/tools/likes.py

@ -1,8 +1,69 @@
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from .common import SELENIUM_EXCEPTIONS
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from .common import SELENIUM_EXCEPTIONS, archiver
def load_likes(driver):
"""
Loads the page that lists all pages you like
"""
driver.get("https://www.facebook.com/pages/?category=liked")
wait = WebDriverWait(driver, 20)
try:
wait.until(
EC.presence_of_element_located((By.XPATH, "//button/div/div[text()='Liked']"))
)
wait.until(
EC.presence_of_element_located((By.XPATH, "//button/div/i[@aria-hidden=\"true\"]"))
)
except SELENIUM_EXCEPTIONS:
return
def unlike_pages(driver):
"""
Unlike all pages
"""
return
like_log, archive_likes = archiver("likes")
actions = ActionChains(driver)
load_likes(driver)
pages_list = driver.find_element_by_css_selector("#all_liked_pages")
actions.move_to_element(pages_list).perform()
unlike_buttons = pages_list.find_elements_by_xpath("//button/div/div[text()='Liked']/../..")
while unlike_buttons:
for button in unlike_buttons:
try:
if "Liked" in button.text:
page_name = button.find_element_by_xpath("./../..").text.split("\n")[0]
driver.execute_script("arguments[0].click();", button)
archive_likes(page_name)
print("{0} was unliked".format(page_name))
except SELENIUM_EXCEPTIONS as e:
continue
load_likes(driver)
try:
pages_list = driver.find_element_by_css_selector("#all_liked_pages")
actions.move_to_element(pages_list).perform()
unlike_buttons = pages_list.find_elements_by_xpath("//button")
if not unlike_buttons:
break
except SELENIUM_EXCEPTIONS:
break
# Explicitly close the log file when we're done with it
like_log.close()

68
deletefb/tools/login.py

@ -1,11 +1,13 @@
import time
from sys import stderr, exit
from selenium.webdriver.chrome.options import Options
from seleniumrequests import Chrome
from selenium.common.exceptions import NoSuchElementException
from .common import no_chrome_driver
def login(user_email_address,
user_password,
user_profile_url,
is_headless,
two_factor_token):
"""
@ -28,7 +30,15 @@ def login(user_email_address,
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('log-level=2')
driver = Chrome(options=chrome_options)
try:
driver = Chrome(options=chrome_options)
except Exception as e:
# The user does not have chromedriver installed
# Tell them to install it
stderr.write(str(e))
stderr.write(no_chrome_driver)
exit(1)
driver.implicitly_wait(10)
driver.get("https://facebook.com")
@ -47,24 +57,40 @@ def login(user_email_address,
loginelement = driver.find_element_by_id(login)
loginelement.click()
if "two-factor authentication" in driver.page_source.lower():
if two_factor_token:
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()
else:
# Allow time to enter 2FA code
print("Pausing to enter 2FA code")
time.sleep(20)
print("Continuing execution")
# 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
driver.get(user_profile_url)
return driver

26
deletefb/tools/wall.py

@ -1,20 +1,40 @@
import time
from selenium.webdriver.common.action_chains import ActionChains
from .common import SELENIUM_EXCEPTIONS
from .common import SELENIUM_EXCEPTIONS, archiver
MAX_POSTS = 5000
# Used as a threshold to avoid running forever
MAX_POSTS = 15000
def delete_posts(driver):
def delete_posts(driver,
user_profile_url,
year=None):
"""
Deletes or hides all posts from the wall
"""
if not year is None:
user_profile_url = "{0}/timeline?year={1}".format(user_profile_url, year)
driver.get(user_profile_url)
wall_log, archive_wall_post = archiver("wall")
for _ in range(MAX_POSTS):
post_button_sel = "_4xev"
post_content_sel = "_5_jv"
post_timestamp_sel = "timestamp"
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_wall_post(post_content_element.text, timestamp=post_content_ts.text)
actions = ActionChains(driver)
actions.move_to_element(timeline_element).click().perform()

Loading…
Cancel
Save