Browse Source

Merge pull request #51 from weskerfoot/handle-grid-layout

[WIP] Handle grid layout and fix unliking pages
pull/57/head
Wesley Kerfoot 6 years ago
committed by GitHub
parent
commit
82f4d13a1d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      README.md
  2. 19
      deletefb/deletefb.py
  3. 38
      deletefb/tools/common.py
  4. 4
      deletefb/tools/config.py
  5. 104
      deletefb/tools/likes.py
  6. 11
      deletefb/tools/wall.py
  7. 5
      setup.py

5
README.md

@ -37,7 +37,7 @@ git+https://github.com/weskerfoot/DeleteFB.git`
brew cask install chromedriver brew cask install chromedriver
``` ```
* Run `deletefb -E "youremail@example.org" -P "yourfacebookpassword" -U "https://www.facebook.com/your.profile.url"` * 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 * 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 start deleting posts. If it cannot delete something, then it will "hide" it
from your timeline instead. from your timeline instead.
@ -63,7 +63,8 @@ git+https://github.com/weskerfoot/DeleteFB.git`
## Unlike Pages ## Unlike Pages
* You may use `-M unlike_pages` to unlike all of your pages. The names of the * 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 pages will be archived (unless archival is turned off), and this option
conflicts with the year 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).
## Archival ## Archival
* The tool will archive everything being deleted by default in `.log` files. * The tool will archive everything being deleted by default in `.log` files.

19
deletefb/deletefb.py

@ -6,6 +6,7 @@ import json
import os import os
import sys import sys
from .tools.config import settings
from .tools.common import logger from .tools.common import logger
from .tools.login import login from .tools.login import login
from .tools.wall import delete_posts from .tools.wall import delete_posts
@ -13,7 +14,6 @@ from .tools.likes import unlike_pages
LOG = logger("deletefb") LOG = logger("deletefb")
def run_delete(): def run_delete():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -92,11 +92,7 @@ def run_delete():
args = parser.parse_args() args = parser.parse_args()
if args.archive_off: settings["ARCHIVE"] = not args.archive_off
os.environ["DELETEFB_ARCHIVE"] = "false"
else:
os.environ["DELETEFB_ARCHIVE"] = "true"
if args.year and args.mode != "wall": if args.year and args.mode != "wall":
parser.error("The --year option is only supported in wall mode") parser.error("The --year option is only supported in wall mode")
@ -111,11 +107,14 @@ def run_delete():
) )
if args.mode == "wall": if args.mode == "wall":
delete_posts(driver, delete_posts(
args.profile_url, driver,
year=args.year) args.profile_url,
year=args.year
)
elif args.mode == "unlike_pages": elif args.mode == "unlike_pages":
unlike_pages(driver) unlike_pages(driver, args.profile_url)
else: else:
print("Please enter a valid mode") print("Please enter a valid mode")
sys.exit(1) sys.exit(1)

38
deletefb/tools/common.py

@ -2,29 +2,35 @@ import json
import logging import logging
import logging.config import logging.config
import os import os
from os.path import abspath, relpath, split, isfile
import time import time
from .config import settings
# Used to avoid duplicates in the log
from pybloom_live import BloomFilter
from os.path import abspath, relpath, split, isfile
from selenium.common.exceptions import ( from selenium.common.exceptions import (
NoSuchElementException, NoSuchElementException,
StaleElementReferenceException, StaleElementReferenceException,
TimeoutException TimeoutException
) )
SELENIUM_EXCEPTIONS = ( SELENIUM_EXCEPTIONS = (
NoSuchElementException, NoSuchElementException,
StaleElementReferenceException, StaleElementReferenceException,
TimeoutException TimeoutException
) )
def try_move(actions, el): def click_button(driver, el):
for _ in range(10): """
try: Click a button using Javascript
actions.move_to_element(el).perform() Args:
except StaleElementReferenceException: driver: seleniumrequests.Chrome Driver instance
time.sleep(5) Returns:
continue None
"""
driver.execute_script("arguments[0].click();", el)
def logger(name): def logger(name):
""" """
@ -56,9 +62,19 @@ def archiver(category):
log_file = open(log_path, mode="ta", buffering=1) log_file = open(log_path, mode="ta", buffering=1)
bfilter = BloomFilter(
capacity=settings["MAX_POSTS"],
error_rate=0.001
)
def log(content, timestamp=False): def log(content, timestamp=False):
if os.environ.get("DELETEFB_ARCHIVE", "true") == "false": if not settings["ARCHIVE"]:
return return
if content in bfilter:
# This was already archived
return
structured_content = { structured_content = {
"category" : category, "category" : category,
"content" : content, "content" : content,
@ -67,6 +83,8 @@ def archiver(category):
log_file.write("{0}\n".format(json.dumps(structured_content))) log_file.write("{0}\n".format(json.dumps(structured_content)))
bfilter.add(content)
return (log_file, log) return (log_file, log)

4
deletefb/tools/config.py

@ -0,0 +1,4 @@
settings = {
"ARCHIVE" : True,
"MAX_POSTS" : 5000
}

104
deletefb/tools/likes.py

@ -3,12 +3,11 @@ from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support import expected_conditions as EC
from .common import SELENIUM_EXCEPTIONS, archiver, logger from .common import SELENIUM_EXCEPTIONS, archiver, logger, click_button
LOG = logger(__name__) LOG = logger(__name__)
def load_likes(driver, profile_url):
def load_likes(driver):
""" """
Loads the page that lists all pages you like Loads the page that lists all pages you like
@ -18,68 +17,107 @@ def load_likes(driver):
Returns: Returns:
None None
""" """
driver.get("https://www.facebook.com/pages/?category=liked")
wait = WebDriverWait(driver, 20) driver.get("{0}/likes_all".format(profile_url))
try: wait = WebDriverWait(driver, 30)
wait.until(
EC.presence_of_element_located((By.XPATH, "//button/div/div[text()='Liked']"))
)
try:
wait.until( wait.until(
EC.presence_of_element_located((By.XPATH, "//button/div/i[@aria-hidden=\"true\"]")) EC.presence_of_element_located((By.CSS_SELECTOR, ".PageLikeButton"))
) )
except SELENIUM_EXCEPTIONS: except SELENIUM_EXCEPTIONS:
LOG.exception("Traceback of load_likes") LOG.exception("Traceback of load_likes")
return return
def unlike_pages(driver):
def get_page_links(driver):
""" """
Unlike all pages Gets all of the links to the pages you like
Args: Args:
driver: seleniumrequests.Chrome Driver instance driver: seleniumrequests.Chrome Driver instance
Returns:
List of URLs to pages
"""
pages = driver.find_elements_by_xpath("//li//div/div/a[contains(@class, 'lfloat')]")
actions = ActionChains(driver)
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: Returns:
None None
""" """
like_log, archive_likes = archiver("likes") driver.get(url)
print("Unliking {0}".format(url))
wait = WebDriverWait(driver, 60)
actions = ActionChains(driver) actions = ActionChains(driver)
load_likes(driver) 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']")
pages_list = driver.find_element_by_css_selector("#all_liked_pages") # Click the "Liked" button to open up "Unlike"
click_button(driver, button)
actions.move_to_element(pages_list).perform() wait.until(
EC.presence_of_element_located((By.XPATH, "//*[text()='Unlike']"))
)
unlike_buttons = pages_list.find_elements_by_xpath("//button/div/div[text()='Liked']/../..") # There should be an "Unlike" button now, click it
unlike_button = driver.find_element_by_xpath("//*[text()='Unlike']/..")
while unlike_buttons: click_button(driver, unlike_button)
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) if archive:
archive(url)
archive_likes(page_name) def unlike_pages(driver, profile_url):
"""
Unlike all pages
Args:
driver: seleniumrequests.Chrome Driver instance
Returns:
None
"""
like_log, archive_likes = archiver("likes")
print("{0} was unliked".format(page_name)) load_likes(driver, profile_url)
except SELENIUM_EXCEPTIONS: urls = get_page_links(driver)
continue
load_likes(driver) while urls:
for url in urls:
unlike_page(driver, url, archive=archive_likes)
try: try:
pages_list = driver.find_element_by_css_selector("#all_liked_pages") load_likes(driver, profile_url)
actions.move_to_element(pages_list).perform() urls = get_page_links(driver)
unlike_buttons = pages_list.find_elements_by_xpath("//button")
if not unlike_buttons:
break
except SELENIUM_EXCEPTIONS: except SELENIUM_EXCEPTIONS:
# We're done
break break
# Explicitly close the log file when we're done with it # Explicitly close the log file when we're done with it

11
deletefb/tools/wall.py

@ -1,12 +1,11 @@
import time import time
from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.action_chains import ActionChains
from .common import SELENIUM_EXCEPTIONS, archiver from .config import settings
from .common import SELENIUM_EXCEPTIONS, archiver, click_button
# Used as a threshold to avoid running forever # Used as a threshold to avoid running forever
MAX_POSTS = 15000 MAX_POSTS = settings["MAX_POSTS"]
def delete_posts(driver, def delete_posts(driver,
user_profile_url, user_profile_url,
@ -59,8 +58,8 @@ def delete_posts(driver,
actions.move_to_element(delete_button).click().perform() actions.move_to_element(delete_button).click().perform()
confirmation_button = driver.find_element_by_class_name("layerConfirm") confirmation_button = driver.find_element_by_class_name("layerConfirm")
# Facebook would not let me get focus on this button without some custom JS click_button(driver, confirmation_button)
driver.execute_script("arguments[0].click();", confirmation_button)
except SELENIUM_EXCEPTIONS: except SELENIUM_EXCEPTIONS:
continue continue
else: else:

5
setup.py

@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
setuptools.setup( setuptools.setup(
name="delete-facebook-posts", name="delete-facebook-posts",
version="1.1.1", version="1.1.2",
author="Wesley Kerfoot", author="Wesley Kerfoot",
author_email="wes@wesk.tech", author_email="wes@wesk.tech",
description="A Selenium Script to Delete Facebook Posts", description="A Selenium Script to Delete Facebook Posts",
@ -16,7 +16,8 @@ setuptools.setup(
install_requires = [ install_requires = [
"selenium", "selenium",
"selenium-requests", "selenium-requests",
"requests" "requests",
"pybloom-live"
], ],
classifiers= [ classifiers= [
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",

Loading…
Cancel
Save