From 4f0a85ca0971725bda67cb7ea317b66fd37dd8d3 Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 6 Dec 2016 02:57:47 -0600 Subject: [PATCH] Updates core functionality to PRAW v4 --- shreddit/app.py | 11 +---- shreddit/shredder.py | 96 ++++++++++++++++---------------------------- shreddit/util.py | 8 ++++ 3 files changed, 45 insertions(+), 70 deletions(-) diff --git a/shreddit/app.py b/shreddit/app.py index b25c2d5..d16b0e8 100644 --- a/shreddit/app.py +++ b/shreddit/app.py @@ -6,21 +6,15 @@ import logging import os import pkg_resources from shreddit import default_config -from shreddit.oauth import oauth_test from shreddit.shredder import Shredder def main(): parser = argparse.ArgumentParser(description="Command-line frontend to the shreddit library.") parser.add_argument("-c", "--config", help="Config file to use instead of the default shreddit.yml") - parser.add_argument("-p", "--praw", help="PRAW config (if not ./praw.ini)") - parser.add_argument("-t", "--test-oauth", help="Perform OAuth test and exit", action="store_true") + parser.add_argument("-u", "--user", help="User section from praw.ini if not default", default="default") args = parser.parse_args() - if args.test_oauth: - oauth_test(args.praw) - return - config_filename = args.config or "shreddit.yml" if not os.path.isfile(config_filename): print("No configuration file could be found. Paste the following into a file called \"shreddit.yml\" and " \ @@ -36,8 +30,7 @@ def main(): if option in user_config: default_config[option] = user_config[option] - # TODO: Validate config options - shredder = Shredder(default_config, args.praw) + shredder = Shredder(default_config, args.user) shredder.shred() diff --git a/shreddit/shredder.py b/shreddit/shredder.py index 80a9d23..8a9f208 100644 --- a/shreddit/shredder.py +++ b/shreddit/shredder.py @@ -1,32 +1,30 @@ -import os -import sys -import logging +import arrow import argparse import json -import arrow -import yaml +import logging +import os import praw +import sys import time -from re import sub +import yaml from datetime import datetime, timedelta -from praw.errors import (InvalidUser, InvalidUserPass, RateLimitExceeded, - HTTPException, OAuthAppRequired) -from praw.objects import Comment, Submission -from shreddit.util import get_sentence +from praw.models import Comment, Submission +from prawcore.exceptions import ResponseException, OAuthException +from re import sub +from shreddit.util import get_sentence, ShredditError class Shredder(object): """This class stores state for configuration, API objects, logging, etc. It exposes a shred() method that application code can call to start it. """ - def __init__(self, config, praw_ini=None): + def __init__(self, config, user): logging.basicConfig() self._logger = logging.getLogger("shreddit") self._logger.setLevel(level=logging.DEBUG if config.get("verbose", True) else logging.INFO) self.__dict__.update({"_{}".format(k): config[k] for k in config}) - self._praw_ini = praw_ini - self._connect(praw_ini, self._username, self._password) + self._connect(user) if self._save_directory: self._r.config.store_json_result = True @@ -36,8 +34,6 @@ class Shredder(object): if self._save_directory: if not os.path.exists(self._save_directory): os.makedirs(self._save_directory) - self._limit = None - self._api_calls = [] # Add any multireddit subreddits to the whitelist self._whitelist = set([s.lower() for s in self._whitelist]) @@ -68,7 +64,7 @@ class Shredder(object): self._logger.info("Trial run - no deletion will be performed") def shred(self): - deleted = self._remove_things(self._get_things()) + deleted = self._remove_things(self._build_iterator()) self._logger.info("Finished deleting {} items. ".format(deleted)) if deleted >= 1000: # This user has more than 1000 items to handle, which angers the gods of the Reddit API. So chill for a @@ -78,33 +74,14 @@ class Shredder(object): self._connect(None, self._username, self._password) self.shred() - def _connect(self, praw_ini, username, password): - self._r = praw.Reddit(user_agent="shreddit/5.0") - if praw_ini: - # PRAW won't panic if the file is invalid, so check first - if not os.path.exists(praw_ini): - print("PRAW configuration file \"{}\" not found.".format(praw_ini)) - return - praw.settings.CONFIG.read(praw_ini) + def _connect(self, user): try: - # Try to login with OAuth2 - self._r.refresh_access_information() - self._logger.debug("Logged in with OAuth.") - except (HTTPException, OAuthAppRequired) as e: - self._logger.warning("You should migrate to OAuth2 using get_secret.py before Reddit disables this login " - "method.") - try: - try: - self._r.login(username, password) - except InvalidUserPass: - self._r.login() # Supply details on the command line - except InvalidUser as e: - raise InvalidUser("User does not exist.", e) - except InvalidUserPass as e: - raise InvalidUserPass("Specified an incorrect password.", e) - except RateLimitExceeded as e: - raise RateLimitExceeded("You're doing that too much.", e) - self._logger.info("Logged in as {user}.".format(user=self._r.user)) + self._r = praw.Reddit(user, user_agent="python:shreddit:v6.0.0") + self._logger.info("Logged in as {user}.".format(user=self._r.user.me())) + except ResponseException: + raise ShredditError("Bad OAuth credentials") + except OAuthException: + raise ShredditError("Bad username or password") def _check_whitelist(self, item): """Returns True if the item is whitelisted, False otherwise. @@ -137,13 +114,9 @@ class Shredder(object): short_text = sub(b"\n\r\t", " ", comment.body[:35].encode("utf-8")) msg = "/r/{}/ #{} ({}) with: {}".format(comment.subreddit, comment.id, short_text, replacement_text) - if self._edit_only: - self._logger.debug("Editing (not removing) {msg}".format(msg=msg)) - else: - self._logger.debug("Editing and deleting {msg}".format(msg=msg)) + self._logger.debug("Editing and deleting {msg}".format(msg=msg)) if not self._trial_run: comment.edit(replacement_text) - self._api_calls.append(int(time.time())) def _remove(self, item): if self._keep_a_copy and self._save_directory: @@ -151,27 +124,20 @@ class Shredder(object): if self._clear_vote: try: item.clear_vote() - self._api_calls.append(int(time.time())) except HTTPException: self._logger.debug("Couldn't clear vote on {item}".format(item=item)) if isinstance(item, Submission): self._remove_submission(item) elif isinstance(item, Comment): self._remove_comment(item) - if not self._edit_only and not self._trial_run: + if not self._trial_run: item.delete() - self._api_calls.append(int(time.time())) def _remove_things(self, items): self._logger.info("Loading items to delete...") to_delete = [item for item in items] self._logger.info("Done. Starting on batch of {} items...".format(len(to_delete))) for idx, item in enumerate(to_delete): - minute_ago = arrow.now().replace(minutes=-1).timestamp - self._api_calls = [api_call for api_call in self._api_calls if api_call >= minute_ago] - if len(self._api_calls) >= 60: - self._logger.info("Sleeping 10 seconds to wait out API cooldown...") - time.sleep(10) self._logger.debug("Examining item {}: {}".format(idx + 1, item)) created = arrow.get(item.created_utc) if str(item.subreddit).lower() in self._blacklist: @@ -190,12 +156,20 @@ class Shredder(object): self._remove(item) return idx + 1 - def _get_things(self): + def _build_iterator(self): + item = self._r.user.me() if self._item == "comments": - return self._r.user.get_comments(limit=self._limit, sort=self._sort) + item = item.comments elif self._item == "submitted": - return self._r.user.get_submitted(limit=self._limit, sort=self._sort) - elif self._item == "overview": - return self._r.user.get_overview(limit=self._limit, sort=self._sort) + item = item.submissions + + if self._sort == "new": + return item.new(limit=None) + elif self._sort == "top": + return item.top(limit=None) + elif self._sort == "hot": + return item.hot(limit=None) + elif self._sort == "controversial": + return item.controversial(limit=None) else: - raise Exception("Your deletion section is wrong") + raise ShredditError("Sorting \"{}\" not recognized.".format(self._sort)) diff --git a/shreddit/util.py b/shreddit/util.py index 9d8931f..dea428a 100644 --- a/shreddit/util.py +++ b/shreddit/util.py @@ -21,3 +21,11 @@ except ImportError: words = LOREM.split() random.shuffle(words) return " ".join(words) + + +class ShredditError(Exception): + def __init__(self, value=None): + self.value = value if value else "No information provided" + + def __str__(self): + return repr(self.value)