diff --git a/deletefb/tools/activity.py b/deletefb/tools/activity.py index 4eebcc3..37cbaaa 100644 --- a/deletefb/tools/activity.py +++ b/deletefb/tools/activity.py @@ -3,12 +3,49 @@ from .archive import archiver from .common import SELENIUM_EXCEPTIONS, click_button, wait_xpath, force_mobile from .config import settings from selenium.webdriver.common.action_chains import ActionChains +from calendar import month_name + +months = [m for m in month_name if m] import time # Used as a threshold to avoid running forever MAX_POSTS = settings["MAX_POSTS"] +def get_load_more(driver): + """ + Click the "Load more from X" button repeatedly + """ + + button_expr = f"//div[contains(text(), 'Load more from')]" + + print("Trying to load more") + + while True: + try: + wait_xpath(driver, button_expr) + driver.find_element_by_xpath(button_expr).click() + except SELENIUM_EXCEPTIONS: + break + +def get_timeslices(driver): + """ + Get a list of the time slices Facebook is going to let us click. + """ + + slice_expr = "//header" + + wait_xpath(driver, slice_expr) + for ts in driver.find_elements_by_xpath(slice_expr): + if any(w in months for w in ts.text.strip().split()): + yield ts + try: + int(ts.text.strip()) + if len(ts.text.strip()) == 4: # it's a year + yield ts + except ValueError: + continue + def delete_activity(driver, year=None): """ @@ -21,87 +58,16 @@ def delete_activity(driver, driver.get("https://m.facebook.com/allactivity/?category_key=statuscluster") + #print(get_load_more(driver)) + + actions = ActionChains(driver) + + for ts in get_timeslices(driver): + # Need to figure out how to ignore the ones with nothing in them + print(ts.text) + actions.move_to_element(ts) + get_load_more(driver) + time.sleep(1000) - 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')]" - - # 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 = ["Delete post", "Remove tag", "Hide from timeline"] - - while True: - try: - 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) - - if not (post_content_element or post_content_ts): - break - - # 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() - - # 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 = 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) - continue - - if not delete_button: - print("Could not find anything to delete") - break - - click_button(driver, delete_button) - 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: - print(e) - continue - else: - break - - # Required to sleep the thread for a bit after using JS to click this button - time.sleep(5) - driver.refresh() + #with archiver("activity") as archive_wall_post: