diff --git a/PostCleaner.py b/PostCleaner.py index 4f0375e..0697fc7 100644 --- a/PostCleaner.py +++ b/PostCleaner.py @@ -1,10 +1,27 @@ import json 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"): """ @@ -119,11 +136,11 @@ def delete_old_posts(reddit, username, days_old): "source": "cli", }) + "\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 42dcee0..5ee6494 100644 --- a/commentCleaner.py +++ b/commentCleaner.py @@ -1,10 +1,27 @@ import json 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. @@ -115,10 +132,10 @@ def delete_old_comments(reddit, username, days_old, comments_deleted): "source": "cli-mode-1", }) + "\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}") @@ -148,10 +165,10 @@ def remove_comments_with_negative_karma(reddit, username, comments_deleted): "source": "cli-mode-2", }) + "\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}") @@ -188,10 +205,10 @@ def remove_comments_with_one_karma_and_no_replies(reddit, username, comments_del "source": "cli-mode-3", }) + "\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(): diff --git a/web/app.py b/web/app.py index 7acf718..b447505 100644 --- a/web/app.py +++ b/web/app.py @@ -1,9 +1,11 @@ import json import os import sys +import time from datetime import datetime import praw +import prawcore from flask import Flask, jsonify, redirect, render_template, request, session, url_for app = Flask(__name__) @@ -19,6 +21,22 @@ DELETED_COMMENTS_FILE = os.path.join(BASE_DIR, "deleted_comments.txt") DELETED_POSTS_FILE = os.path.join(BASE_DIR, "deleted_posts.txt") +_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 make_reddit(): return praw.Reddit( @@ -138,8 +156,8 @@ def api_delete(): "body": comment.body, "source": "web", }) + "\n") - comment.edit(".") - comment.delete() + _with_retry(lambda: comment.edit("."), "comment edit") + _with_retry(comment.delete, "comment delete") deleted_comments += 1 except Exception as e: errors.append(f"Comment {cid}: {e}") @@ -159,8 +177,8 @@ def api_delete(): "num_comments": submission.num_comments, "source": "web", }) + "\n") - submission.edit(".") - submission.delete() + _with_retry(lambda: submission.edit("."), "post edit") + _with_retry(submission.delete, "post delete") deleted_posts += 1 except Exception as e: errors.append(f"Post {pid}: {e}") diff --git a/weekly_cleanup.py b/weekly_cleanup.py index 8e64ca6..e1c6538 100644 --- a/weekly_cleanup.py +++ b/weekly_cleanup.py @@ -24,12 +24,31 @@ import argparse import json 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) + + +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() + + AGE_THRESHOLD_DAYS = 14 @@ -105,11 +124,11 @@ def main(dry_run: bool = False): "source": "ci", }) + "\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 ───────────────────────────────────────────────────────────── @@ -133,11 +152,11 @@ def main(dry_run: bool = False): "source": "ci", }) + "\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}") if dry_run: