Compare commits

...

21 Commits

Author SHA1 Message Date
dependabot[bot] ed96dd7e6a
Bump urllib3 from 1.25.2 to 1.25.8 (#162) 3 years ago
Sean Leavey b93f528f99
Ensure protocol is provided in mobile URLs (fixes #160) (#161) 3 years ago
dependabot[bot] ddf358bed2
Bump pygments from 2.4.2 to 2.7.4 (#158) 3 years ago
dependabot[bot] 262c7f84c6
Bump lxml from 4.6.2 to 4.6.3 (#159) 3 years ago
Ian Hunter a92fd3eb35
Update Required Python version (#157) 3 years ago
Wesley Kerfoot d64ed5a3db
Update README.md 3 years ago
Wesley Kerfoot 39b2437f1d
Update README.md 3 years ago
dependabot[bot] 72eb1bdcde
Bump bleach from 3.1.4 to 3.3.0 (#155) 3 years ago
Wesley Kerfoot 86403534b9
bump versions of cattrs and attrs (#151) 3 years ago
dependabot[bot] 11c21f6d2c
Bump lxml from 4.4.0 to 4.6.2 (#150) 3 years ago
tklam a5ed694a46
Add "Hide from profile" (#145) 3 years ago
Wesley Kerfoot ede08a42a9 bump version 4 years ago
Thomas c1af411453
WebDriverException not defined (#139) 4 years ago
Wesley Kerfoot 7d24450b0f
add FUNDING.yml (#137) 4 years ago
Wesley Kerfoot 1a846f1dac bump version 4 years ago
Wesley Kerfoot a182eaaaa3
fixes https://github.com/weskerfoot/DeleteFB/issues/135 (#136) 4 years ago
Wesley Kerfoot 50162ba996
fix unliking (#134) 4 years ago
Wesley Kerfoot 9747f0a00d bump version 4 years ago
Wesley Kerfoot 5830cfeecd
force url to mobile site for deleting wall posts (#130) 4 years ago
Wesley Kerfoot 1875b754ab
Fixes for new facebook site (#129) 4 years ago
dependabot[bot] af949c2d24
Bump bleach from 3.1.2 to 3.1.4 (#124) 4 years ago
  1. 2
      FUNDING.yml
  2. 8
      README.md
  3. 1
      deletefb/tools/chrome_driver.py
  4. 31
      deletefb/tools/common.py
  5. 19
      deletefb/tools/conversations.py
  6. 42
      deletefb/tools/likes.py
  7. 55
      deletefb/tools/wall.py
  8. 12
      requirements.txt
  9. 6
      setup.py

2
FUNDING.yml

@ -0,0 +1,2 @@
github: [weskerfoot]
custom: "39qHYvjVcMCNFr3RPAVetci9mKjzYGTQPz"

8
README.md

@ -1,3 +1,7 @@
WARNING:
This currently only works for English language Facebook accounts, due to the lack of a usable API.
Also, year by year deletion is currently broken. Feel free to fork or make pull requests.
## Why?
I needed a simple and reliable way to delete Facebook posts. There are
@ -18,7 +22,7 @@ Personally, I did this so I would feel less attached to my Facebook profile
(and hence feel the need to use it less).
## Dependencies
- This tool requires at least Python 3.6 in order to run.
- This tool requires at least Python 3.7 in order to run.
- A recent copy of Chrome or Chromium installed and available in your `$PATH`
## Installation
@ -57,7 +61,7 @@ optional arguments:
```
* Make sure that you have a recent version of Python 3.x installed (preferably
3.6 or greater)
3.7 or greater)
* Make sure that you have Google Chrome installed and that it is up to date
* The tool will attempt to automatically install chromedriver for Selenium. See [here](https://sites.google.com/a/chromium.org/chromedriver/home) for an explanation of what the chromedriver does. You may have to manually install it if auto-install fails.
* On Linux, it will be called something like `chromium-chromedriver` or just

1
deletefb/tools/chrome_driver.py

@ -2,6 +2,7 @@ from ..exceptions import UnknownOSException, ChromeError
from .common import NO_CHROME_DRIVER
from clint.textui import puts, colored
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
from shutil import which
from subprocess import check_output
from urllib.request import urlretrieve

31
deletefb/tools/common.py

@ -12,6 +12,7 @@ import json
import logging
import logging.config
import os
import urllib.parse as urlparse
SELENIUM_EXCEPTIONS = (
NoSuchElementException,
@ -19,13 +20,20 @@ SELENIUM_EXCEPTIONS = (
TimeoutException
)
def click_button(driver, el):
"""
Click a button using Javascript
"""
driver.execute_script("arguments[0].click();", el)
def scroll_until_element_exists(driver, xpath_expr):
while True:
driver.execute_script("window.scrollTo(0, document.body.scrollHeight)")
try:
element = driver.find_element_by_xpath(xpath_expr)
except SELENIUM_EXCEPTIONS:
continue
break
def scroll_to(driver, el):
"""
@ -36,7 +44,6 @@ def scroll_to(driver, el):
except SELENIUM_EXCEPTIONS:
return
def logger(name):
"""
Args:
@ -56,17 +63,29 @@ def logger(name):
logging.config.dictConfig(config["logging"])
return logging.getLogger(name)
def wait_xpath(driver, expr):
def wait_xpath(driver, expr, timeout=20):
"""
Takes an XPath expression, and waits at most 20 seconds until it exists
Takes an XPath expression, and waits at most `timeout` seconds until it exists
"""
wait = WebDriverWait(driver, 20)
wait = WebDriverWait(driver, timeout)
try:
wait.until(EC.presence_of_element_located((By.XPATH, expr)))
except SELENIUM_EXCEPTIONS:
return
def force_mobile(url):
"""
Force a url to use the mobile site.
"""
parsed = urlparse.urlparse(url)
# Ensure a protocol is given (needed by selenium).
scheme = parsed.scheme or "https"
return urlparse.urlunparse((scheme,
"mobile.facebook.com",
parsed.path,
parsed.params,
parsed.query,
parsed.fragment))
NO_CHROME_DRIVER = """
You need to manually install the chromedriver for Selenium\n

19
deletefb/tools/conversations.py

@ -6,6 +6,7 @@ from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import Select
from pendulum import now
from json import loads
from time import sleep
import lxml.html as lxh
@ -29,12 +30,13 @@ def get_conversations(driver):
date = None
if url and "messages/read" in url:
date = convo.find_element_by_xpath("../../..//abbr").text
conversation_name = convo.find_element_by_xpath("../../../div/div/header/h3").text.strip()
assert(conversation_name)
assert(url)
try:
date = convo.find_element_by_xpath("../../..//abbr").text
conversation_name = convo.find_element_by_xpath("../../../div/div/header/h3").text.strip()
assert(conversation_name)
assert(url)
except (SELENIUM_EXCEPTIONS + (AssertionError,)):
continue
conversations.append(
Conversation(
@ -49,7 +51,10 @@ def get_conversations(driver):
find_element_by_xpath("a").
get_attribute("href"))
except SELENIUM_EXCEPTIONS:
print("next_url", next_url)
except SELENIUM_EXCEPTIONS as e:
print(e)
break
if not next_url:
break

42
deletefb/tools/likes.py

@ -1,6 +1,6 @@
from .archive import archiver
from ..types import Page
from .common import SELENIUM_EXCEPTIONS, logger, click_button
from .common import SELENIUM_EXCEPTIONS, logger, click_button, wait_xpath, force_mobile, scroll_to, scroll_until_element_exists
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
@ -18,17 +18,25 @@ def load_likes(driver, profile_url):
None
"""
driver.get("{0}/likes_all".format(profile_url))
likes_link_xpath = "//div[normalize-space(text())='Likes']/../..//a[contains(@href, 'app_section')]"
wait = WebDriverWait(driver, 20)
all_likes_link_xpath = "//div[normalize-space(text())='All Likes']/../../../..//a[contains(@href, 'app_collection')]"
try:
wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".PageLikeButton"))
)
except SELENIUM_EXCEPTIONS:
LOG.exception("Traceback of load_likes")
return
driver.get(force_mobile("{0}/about".format(profile_url)))
scroll_until_element_exists(driver, "//div[text()='Likes']")
likes_link_el = driver.find_element_by_xpath(likes_link_xpath)
driver.get(likes_link_el.get_attribute("href"))
wait_xpath(driver, all_likes_link_xpath)
all_likes_link_el = driver.find_element_by_xpath(all_likes_link_xpath)
all_likes_link = all_likes_link_el.get_attribute("href")
driver.get(all_likes_link)
def get_page_links(driver):
"""
@ -39,8 +47,7 @@ def get_page_links(driver):
Returns:
List of URLs to pages
"""
pages = driver.find_elements_by_xpath("//li//div/div/a[contains(@class, 'lfloat')]")
pages = driver.find_elements_by_xpath("//header/..//a")
return [page.get_attribute("href").replace("www", "mobile") for page in pages]
def unlike_page(driver, url, archive=None):
@ -60,7 +67,7 @@ def unlike_page(driver, url, archive=None):
print("Unliking {0}".format(url))
wait = WebDriverWait(driver, 20)
wait = WebDriverWait(driver, 5)
try:
wait.until(
@ -70,19 +77,14 @@ def unlike_page(driver, url, archive=None):
# 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)
driver.find_element_by_xpath("//*[text()='Liked']/../../../..").click()
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)
driver.find_element_by_xpath("//*[text()='Unlike']/..").click()
if archive:
archive(Page(name=url))

55
deletefb/tools/wall.py

@ -1,6 +1,6 @@
from ..types import Post
from .archive import archiver
from .common import SELENIUM_EXCEPTIONS, click_button, wait_xpath
from .common import SELENIUM_EXCEPTIONS, click_button, wait_xpath, force_mobile
from .config import settings
from selenium.webdriver.common.action_chains import ActionChains
@ -24,23 +24,43 @@ def delete_posts(driver,
if year is not None:
user_profile_url = "{0}/timeline?year={1}".format(user_profile_url, year)
user_profile_url = force_mobile(user_profile_url)
driver.get(user_profile_url)
for _ in range(MAX_POSTS):
post_button_sel = "_4xev"
finished = False
with archiver("wall") as archive_wall_post:
for _ in range(MAX_POSTS):
if finished:
break
post_button_sel = "_4s19"
post_content_sel = "userContent"
post_timestamp_sel = "timestampContent"
confirmation_button_exp = "//div[contains(@data-sigil, 'undo-content')]//*/a[contains(@href, 'direct_action_execute')]"
post_content_sel = "userContent"
post_timestamp_sel = "timestampContent"
# Cannot return a text node, so it returns the parent.
# Tries to be pretty resilient against DOM re-organizations
timestamp_exp = "//article//*/header//*/div/a[contains(@href, 'story_fbid')]//text()/.."
button_types = ["FeedDeleteOption", "HIDE_FROM_TIMELINE", "UNTAG"]
button_types = ["Delete post", "Remove tag", "Hide from timeline", "Hide from profile"]
with archiver("wall") as archive_wall_post:
while True:
try:
timeline_element = driver.find_element_by_class_name(post_button_sel)
try:
timeline_element = driver.find_element_by_xpath("//div[@data-sigil='story-popup-causal-init']/a")
except SELENIUM_EXCEPTIONS:
print("Could not find any posts")
finished = True
break
post_content_element = driver.find_element_by_xpath("//article/div[@class='story_body_container']/div")
post_content_ts = driver.find_element_by_xpath(timestamp_exp)
post_content_element = driver.find_element_by_class_name(post_content_sel)
post_content_ts = driver.find_element_by_class_name(post_timestamp_sel)
if not (post_content_element or post_content_ts):
break
# Archive the post
archive_wall_post.archive(
@ -53,15 +73,18 @@ def delete_posts(driver,
actions = ActionChains(driver)
actions.move_to_element(timeline_element).click().perform()
wait_xpath(driver, "//*[@id='feed_post_menu']/..")
menu = driver.find_element_by_xpath("//*[@id='feed_post_menu']/..")
# Wait until the buttons show up
wait_xpath(driver, "//*[contains(@data-sigil, 'removeStoryButton')]")
delete_button = None
# Search for visible buttons in priority order
# Delete -> Untag -> Hide
for button_type in button_types:
try:
delete_button = menu.find_element_by_xpath("//a[@data-feed-option-name=\"{0}\"]".format(button_type))
delete_button = driver.find_element_by_xpath("//*[text()='{0}']".format(button_type))
if not delete_button.is_displayed():
continue
break
except SELENIUM_EXCEPTIONS as e:
print(e)
@ -72,8 +95,10 @@ def delete_posts(driver,
break
click_button(driver, delete_button)
confirmation_button = driver.find_element_by_class_name("layerConfirm")
wait_xpath(driver, confirmation_button_exp)
confirmation_button = driver.find_element_by_xpath(confirmation_button_exp)
print(confirmation_button)
click_button(driver, confirmation_button)
except SELENIUM_EXCEPTIONS as e:

12
requirements.txt

@ -1,20 +1,20 @@
appdirs==1.4.3
args==0.1.0
attrs==19.1.0
attrs==20.3.0
bitarray==0.9.3
bleach==3.1.2
cattrs-3.8==0.9.1
bleach==3.3.0
cattrs==1.1.2
certifi==2018.11.29
chardet==3.0.4
clint==0.5.1
docutils==0.14
idna==2.8
lxml==4.4.0
lxml==4.6.3
pendulum==2.0.5
pkginfo==1.5.0.1
progressbar==2.5
pybloom-live==3.0.0
Pygments==2.4.2
Pygments==2.7.4
python-dateutil==2.8.0
pytzdata==2019.2
readme-renderer==24.0
@ -28,5 +28,5 @@ tldextract==2.2.0
tqdm==4.32.2
twine==1.13.0
typing==3.7.4
urllib3==1.25.2
urllib3==1.25.8
webencodings==0.5.1

6
setup.py

@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
setuptools.setup(
name="delete-facebook-posts",
version="1.1.13",
version="1.1.17",
author="Wesley Kerfoot",
author_email="wes@wesk.tech",
description="A Selenium Script to Delete Facebook Posts",
@ -24,8 +24,8 @@ setuptools.setup(
"selenium-requests",
"requests",
"pybloom-live",
"attrs",
"cattrs-3.8",
"attrs>=20.3.0",
"cattrs>=1.1.2",
"lxml",
"pendulum",
"clint",

Loading…
Cancel
Save