diff --git a/PostCleaner.py b/PostCleaner.py index 8698e0f..2e50881 100644 --- a/PostCleaner.py +++ b/PostCleaner.py @@ -1,9 +1,26 @@ import praw +import prawcore import time from datetime import datetime from drive_upload import maybe_upload_logs +_RETRY_WAIT = (5, 15, 45) + + +def _with_retry(fn, label="operation"): + """Call fn(), retrying up to 3 times on rate-limit errors.""" + for attempt, wait in enumerate(_RETRY_WAIT, start=1): + try: + return fn() + except prawcore.exceptions.TooManyRequests as exc: + retry_after = getattr(exc, "retry_after", None) or wait + print(f" Rate limited on {label}. Waiting {retry_after}s (attempt {attempt}/3)…") + time.sleep(retry_after) + except praw.exceptions.APIException: + raise + return fn() + def get_reddit_credentials(credentials_file="Credentials.txt"): """ @@ -68,7 +85,7 @@ def initialize_reddit(client_id, client_secret, username, password): reddit.user.me() print("Authenticated successfully.") return reddit - except praw.exceptions.APIException as e: + except praw.exceptions.APIException: print("Error: Could not authenticate with the provided credentials.") exit() @@ -105,11 +122,11 @@ def delete_old_posts(reddit, username, days_old, posts_deleted): with open("deleted_posts.txt", "a", encoding="utf-8") as f: f.write(f"{submission.title}, {datetime.utcfromtimestamp(submission.created_utc)}, {submission.score}, {submission.subreddit.display_name}\n") try: - submission.edit(".") - submission.delete() + _with_retry(lambda: submission.edit("."), "post edit") + _with_retry(submission.delete, "post delete") posts_deleted += 1 print(f"Deleted post: {submission.title}") - except praw.exceptions.APIException as e: + except (praw.exceptions.APIException, prawcore.exceptions.TooManyRequests) as e: print(f"Error removing post: {e}") print(f"Deleted {posts_deleted} posts.") diff --git a/commentCleaner.py b/commentCleaner.py index 4c4eda7..3215ab5 100644 --- a/commentCleaner.py +++ b/commentCleaner.py @@ -1,9 +1,27 @@ import praw +import prawcore import time from datetime import datetime, timedelta from drive_upload import maybe_upload_logs +_RETRY_WAIT = (5, 15, 45) + + +def _with_retry(fn, label="operation"): + """Call fn(), retrying up to 3 times on rate-limit errors.""" + for attempt, wait in enumerate(_RETRY_WAIT, start=1): + try: + return fn() + except prawcore.exceptions.TooManyRequests as exc: + retry_after = getattr(exc, "retry_after", None) or wait + print(f" Rate limited on {label}. Waiting {retry_after}s (attempt {attempt}/3)…") + time.sleep(retry_after) + except praw.exceptions.APIException: + raise + return fn() + + def get_reddit_credentials(credentials_file="Credentials.txt"): """ Prompt the user to input Reddit client credentials. @@ -83,7 +101,7 @@ def initialize_reddit(client_id, client_secret, username, password): reddit.user.me() print("Authenticated successfully.") return reddit - except praw.exceptions.APIException as e: + except praw.exceptions.APIException: print("Error: Could not authenticate with the provided credentials.") exit() @@ -107,10 +125,10 @@ def delete_old_comments(reddit, username, days_old, comments_deleted): # Write the date, karma score, and comment body to the file f.write(f"{comment_date} | {comment.score} | {comment.body}\n") try: - comment.edit(".") - comment.delete() + _with_retry(lambda: comment.edit("."), "comment edit") + _with_retry(comment.delete, "comment delete") comments_deleted.append(comment) - except praw.exceptions.APIException as e: + except (praw.exceptions.APIException, prawcore.exceptions.TooManyRequests) as e: print(f"Error deleting comment: {e}") @@ -133,13 +151,13 @@ def remove_comments_with_negative_karma(reddit, username, comments_deleted): # Write the date, karma score, and comment body to the file f.write(f"{comment_date} | {comment.score} | {comment.body}\n") try: - comment.edit(".") - comment.delete() + _with_retry(lambda: comment.edit("."), "comment edit") + _with_retry(comment.delete, "comment delete") comments_deleted.append(comment) - except praw.exceptions.APIException as e: + except (praw.exceptions.APIException, prawcore.exceptions.TooManyRequests) as e: print(f"Error removing comment: {e}") - - + + def remove_comments_with_one_karma_and_no_replies(reddit, username, comments_deleted): """ Remove comments with one karma, no replies, and are at least a week old. @@ -168,12 +186,13 @@ def remove_comments_with_one_karma_and_no_replies(reddit, username, comments_del # Write the date, karma score, and comment to the file f.write(f"{comment_date} | {comment.score} | {comment.body}\n") try: - comment.edit(".") - comment.delete() + _with_retry(lambda: comment.edit("."), "comment edit") + _with_retry(comment.delete, "comment delete") comments_deleted.append(comment) - except praw.exceptions.APIException as e: + except (praw.exceptions.APIException, prawcore.exceptions.TooManyRequests) as e: print(f"Error removing comment: {e}") + def main(): client_id, client_secret, username, password = get_reddit_credentials() diff --git a/weekly_cleanup.py b/weekly_cleanup.py index 4696998..732f6e6 100644 --- a/weekly_cleanup.py +++ b/weekly_cleanup.py @@ -17,12 +17,35 @@ """ import os +import time from datetime import datetime, timezone import praw +import prawcore from drive_upload import maybe_upload_logs +_RETRY_WAIT = (5, 15, 45) # seconds between successive retries + + +def _with_retry(fn, label="operation"): + """Call fn(), retrying up to 3 times on rate-limit errors. + + Args: + fn: Zero-argument callable to invoke. + label: Human-readable description for log messages. + """ + for attempt, wait in enumerate(_RETRY_WAIT, start=1): + try: + return fn() + except prawcore.exceptions.TooManyRequests as exc: + retry_after = getattr(exc, "retry_after", None) or wait + print(f" Rate limited on {label}. Waiting {retry_after}s (attempt {attempt}/3)…") + time.sleep(retry_after) + except praw.exceptions.APIException: + raise + return fn() # final attempt — let exceptions propagate + AGE_THRESHOLD_DAYS = 14 @@ -42,7 +65,7 @@ def _load_credentials(): cred_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Credentials.txt") if os.path.exists(cred_path): with open(cred_path, encoding="utf-8") as f: - lines = [l.strip() for l in f.readlines()] + lines = [line.strip() for line in f.readlines()] if len(lines) >= 4: return lines[0], lines[1], lines[2], lines[3] @@ -83,11 +106,11 @@ def main(): with open("deleted_comments.txt", "a", encoding="utf-8") as f: f.write(f"{date_str} | {comment.score} | {comment.body}\n") try: - comment.edit(".") - comment.delete() + _with_retry(lambda: comment.edit("."), "comment edit") + _with_retry(comment.delete, "comment delete") comments_deleted += 1 print(f" Deleted comment (score={comment.score}) in r/{comment.subreddit}") - except praw.exceptions.APIException as e: + except (praw.exceptions.APIException, prawcore.exceptions.TooManyRequests) as e: print(f" Error deleting comment {comment.id}: {e}") # ── Posts ───────────────────────────────────────────────────────────── @@ -103,11 +126,11 @@ def main(): f"{submission.subreddit.display_name}\n" ) try: - submission.edit(".") - submission.delete() + _with_retry(lambda: submission.edit("."), "post edit") + _with_retry(submission.delete, "post delete") posts_deleted += 1 print(f" Deleted post '{submission.title}' (score={submission.score}) in r/{submission.subreddit}") - except praw.exceptions.APIException as e: + except (praw.exceptions.APIException, prawcore.exceptions.TooManyRequests) as e: print(f" Error deleting post {submission.id}: {e}") print(f"\nDone. Deleted {comments_deleted} comment(s) and {posts_deleted} post(s).")