def update_reddit(self): #Initialize PRAW and login r = Reddit(user_agent='/r/reddevils bot by /u/Hubwub') r.login(self.username,self.password) #Grab the current settings settings = r.get_subreddit(self.subreddit).get_settings() #Update the sidebar #print sidebar settings['description'] = sidebar settings = r.get_subreddit(self.subreddit).update_settings(description=settings['description'])
def reset_image_pool(self, num_pictures): """ Retrieves the links to all the images that we want. """ reddit = Reddit(user_agent=PROGRAM_NAME) images_downloaded = 0 sr_dict = { sr_name : reddit.get_subreddit(sr_name).get_top() for sr_name in self.subreddit_pool } # randomly pick a number of pictures from the subreddits. # skip the ones that aren't actually pictures. while images_downloaded < num_pictures: subreddit = random.choice([key for key in sr_dict.keys()]) try: submission = next(sr_dict[subreddit]) except StopIteration: # remove subreddits which run out of content del sr_dict[subreddit] # quit if we've run out of subreddits if len(sr_dict.keys()) > 0: continue; else: break; link = submission.url if utils.is_image_link(link): print('({num_pics} of {total_pics}) Image found at {url}. Downloading...'.format(num_pics=images_downloaded+1, total_pics=num_pictures, url=link)) self.download_image(link, self.get_file_name(link)) images_downloaded += 1
class GenericRedditLink: def __init__(self,parent): self.r=Reddit(user_agent="PollBotBestBot") self.parent=parent def __call__(self,msg): if msg["body"].lower().startswith("!r/"): m=msg["body"].lstrip("!r/") spli=m.split(":") subreddit=spli[0] print subreddit if subreddit in config.banned_subreddits: self.parent.scheduler.add("punishment:"+msg["mucnick"],0.1,self.parent.punishment,args=(msg["mucnick"],),repeat=True) self.parent.send_message(mto=self.parent.channel,mbody="Nope, not touching that.",mtype="groupchat") return body=self.get_hot(subreddit,msg) if body!=None: self.parent.send_message(mto=self.parent.channel,mbody=body,mtype="groupchat") if msg["body"].lower().startswith("!block ") and msg["mucnick"] in config.admins: m=m.spli(" ") subreddit=spli[1] config.banned_subreddits.append(subreddit) def get_hot(self,subreddit,msg): if msg["type"]=="groupchat": subreddit=self.r.get_subreddit(subreddit) try: if subreddit.over18: pass except (HTTPError, errors.InvalidSubreddit) as E: self.parent.send_message(mto=self.parent.channel,mbody="Learn to Reddit please, "+msg["mucnick"],mtype="groupchat") return None if subreddit.over18: #self.parent.send_message(mto=self.parent.channel,mbody="NSFW content is currently blocked. Direct complaints to mods and admins.",mtype="groupchat") extra=" :nws: " else: extra="" submissions=subreddit.get_hot(limit=10) a=None a1=None limit=random.randint(0,9) while limit>0: a1=a print a1 try: a=next(submissions) except StopIteration: a=a1 break print a limit-=1 try: return "\n"+extra+str(a.title)+extra+"\n"+extra+str(a.url)+extra except AttributeError: return "Reddit API is currently not accepting connections. Please wait ~30 seconds before retrying."
def reddit_titles(sub): from praw import Reddit r = Reddit(user_agent="my_useragent") news = r.get_subreddit(sub).get_hot(limit=10) news = list(news) #num = 3 for i in news: print(i.title)
def reddit_titles(sub): from praw import Reddit r = Reddit(user_agent="my_useragent") news = r.get_subreddit(sub).get_hot(limit=10) news = list(news) #num = 3 for i in news: print(i.title)
def reddit_parser(): args = build_reddit_parser().parse_args() db_connection = create_connection(args.db_host, args.db_password) reddit = Reddit(user_agent=args.user_agent) subreddit = reddit.get_subreddit(args.subreddit) if args.username: password = getpass("Enter reddit password: ") reddit.login(args.username, password) single_threaded_impl(subreddit, db_connection, args.sub_limit, args.method)
def ban_users(reddit: praw.Reddit, users): """ Ban the users on reddit. """ print("Updating DB objects...") # First pass, update database objects. for user, subs in users.items(): if user in config.EXEMPT_USERS: # Ignore these guys. They're cool/bots. continue if not db.session.query(exists().where(db.BannedUser.username == user)).scalar(): # Update. u = db.BannedUser(username=user, reason="AutoCensor automatic ban - Subreddits: {}".format(",").join(subs)) db.session.add(u) # Commit session. db.session.commit() print("Building many-to-many entries...") # Second pass, check for users and subreddits, and update many-to-many entires. for user in users: if user in config.EXEMPT_USERS: # Ignore these guys. They're cool/bots. continue try: u_object = db.session.query(db.BannedUser).filter(db.BannedUser.username == user).one() except sqlalchemy.orm.exc.NoResultFound: continue for managing_sub in db.session.query(db.ManagingSubreddit).all(): if u_object not in managing_sub.users: managing_sub.users.append(u_object) db.session.add(managing_sub) db.session.commit() print("Banning users...") banned = 0 # Last pass, ban them on our subreddits. for subobject in db.session.query(db.ManagingSubreddit).all(): # Get the subreddit. sub = reddit.get_subreddit(subobject.name) # Get the users. for user in subobject.users: # Begin the bans! try: sub.add_ban( user.username, ban_reason="Automatically banned for posting in these subreddits: {}".format(user.reason), note="Automatically banned for posting in these subreddits: {}".format(user.reason), ) banned += 1 except: # whatever continue return banned
class Snitch: def __init__(self, username, passwd, url, subreddit): self.rh = Reddit('Release the Snitch v 0.1 by Kolpa') self.rh.login(username, passwd) self.css = self.__load_css() self.desc = self.__load_desc() self.url = url self.subreddit = subreddit def __load_css(self): with open('raw.css', 'r') as f: return f.read() def __load_desc(self): with open('descripiton.txt', 'r') as f: return f.read() def __get_last_id(self): with open('current', 'r') as f: return f.read() def __set_new_id(self): id = randrange(0, 999999) with open('current', 'w') as f: f.write(str(id)) return id def _get_random_pos(self): return randrange(0, 100, 10), randrange(0, 100, 10) def __update_desc(self, desc): self.rh.update_settings(self.rh.get_subreddit(self.subreddit), description=desc) def can_move(self, id): if not os.path.exists('current'): return True return id == self.__get_last_id() def move(self, id): try: if self.can_move(id): new_id = self.__set_new_id() desc = self.desc.format(new_id) x, y = self._get_random_pos() css = self.css.format(x, y, self.url) self.rh.set_stylesheet(self.subreddit, css) self.__update_desc(desc) return True except Exception: return False
def main(): parser = argparse.ArgumentParser( prog='modbot.py', description="Subreddit automoderating script") parser.add_argument('subreddit') parser.add_argument('-r', '--rulesdir', default='./rules') parser.add_argument('-u', '--user') parser.add_argument('-p', '--password') args = parser.parse_args() rh = RuleHandler(args.rulesdir, '*.rule') logging.config.fileConfig('logging.conf') reddit = Reddit('%s/%s' % (NAME, VERSION)) try: reddit.login(args.user, args.password) except praw.errors.InvalidUserPass: logging.critical("Login failure") sys.exit(1) sub = reddit.get_subreddit(args.subreddit) read_thinglists() comments_ph = None submissions_ph = None while True: logging.debug("Loop start") loopstart = time.time() try: modqueue_items = sub.get_mod_queue(limit=100) except Exception, e: modqueue_items = [] num = 0 for modqueue_item in modqueue_items: num += 1 matchrules(modqueue_item, rh.rules, is_modqueue=True) logging.info("Checked %d modqueue items" % num) try: if comments_ph == None: comments = sub.get_comments(limit=100) else: comments = sub.get_comments(place_holder=comments_ph, limit=500) except Exception, e: comments = []
def main(): parser = argparse.ArgumentParser( prog='modbot.py', description="Subreddit automoderating script") parser.add_argument('subreddit') parser.add_argument('-r', '--rulesdir', default='./rules') parser.add_argument('-u', '--user') parser.add_argument('-p', '--password') args = parser.parse_args() rh = RuleHandler(args.rulesdir, '*.rule') logging.config.fileConfig('logging.conf') reddit = Reddit('%s/%s' % (NAME, VERSION)) try: reddit.login(args.user, args.password) except praw.errors.InvalidUserPass: logging.critical("Login failure") sys.exit(1) sub = reddit.get_subreddit(args.subreddit) read_thinglists() comments_ph = None submissions_ph = None while True: logging.debug("Loop start") loopstart = time.time() try: modqueue_items = sub.get_mod_queue(limit=100) except Exception, e: modqueue_items = [] num = 0 for modqueue_item in modqueue_items: num += 1 matchrules(modqueue_item, rh.rules, is_modqueue=True) logging.info("Checked %d modqueue items" % num) try: if comments_ph == None: comments = sub.get_comments(limit=100) else: comments = sub.get_comments(place_holder=comments_ph, limit=500) except Exception, e: comments = []
def update_countdown(username, password, subreddit_name, target): user_agent = '/r/{0} countdown'.format(subreddit_name) reddit = Reddit(user_agent) reddit.login(username, password, disable_warning=True) # reddit access subreddit = reddit.get_subreddit(subreddit_name) settings = subreddit.get_settings() description = HTMLParser().unescape(settings['description']) # time matches countdown_delta = target - datetime.now() days_left = countdown_delta.days hours_left = countdown_delta.seconds // 3600 # regex and strings # countdownSearch = re.compile("(\[\d?\d?\]\([#][a-z]*\)\s[a-z]*[!]?\s?)+", re.I) # old regex countdownSearch = re.compile("((\s([a-z]{4})*)*\s?\[\d?\d?\]\([#][a-z]*\)\s[a-z]*[!]?\s?)+", re.I) origStr = " less than [](#days) days [](#hours) hours\n" noDaysStr = " less than [](#hours) hours\n" noHoursStr = " less than [](#minutes) minutes\n" raceLiveStr = " [](#days) Racing [](#hours) is [](#minutes) live\n" updatemeStr = " [](#days) Current [](#hours) event [](#minutes) ended\n" # make sure our event hasn't happened yet if target > datetime.now(): if days_left > 0: description = re.sub(countdownSearch, origStr, description) elif hours_left > 0 and days_left == 0: description = re.sub(countdownSearch, noDaysStr, description) else: description = re.sub(countdownSearch, noHoursStr, description) for key, value in compute_time_ago_params(target).iteritems(): pattern = "\\[[^\\]]*\\]\\(#{0}\\)".format(key) # replace [<anything>](#<key>) repl = "[{0}](#{1})".format(value, key) # with [<value>](#<key>) description = re.sub(pattern, repl, description) # if race is happening, show raceLiveStr, else race is over, show updatemeStr else: countdownSearch.search(description) if target.hour > datetime.now().hour - 3: description = re.sub(countdownSearch, raceLiveStr, description) else: description = re.sub(countdownSearch, updatemeStr, description) subreddit.update_settings(description=description)
def update_countdown(username, password, subreddit_name, target): user_agent = '/r/{0} countdown bot'.format(subreddit_name) reddit = Reddit(user_agent) reddit.login(username, password) subreddit = reddit.get_subreddit(subreddit_name) settings = subreddit.get_settings() description = HTMLParser().unescape(settings['description']) for key, value in compute_time_ago_params(target).iteritems(): pattern = "\\[[^\\]]*\\]\\(#{0}\\)".format(key) # replace [<anything>](#<key>) repl = "[{0}](#{1})".format(value, key) # with [<value>](#<key>) description = re.sub(pattern, repl, description) print(description) subreddit.update_settings(description=description)
def create_sidebar(self): h = HTMLParser.HTMLParser() #Initialize PRAW and login r = Reddit(user_agent='/r/reddevils bot by /u/Hubwub') r.login(self.username,self.password) #Grab the sidebar template from the wiki sidebar = r.get_subreddit(self.subreddit).get_wiki_page('edit_sidebar').content_md #Create list from sidebar by splitting at #### sidebar_list = sidebar.split('####') #Sidebar #print sidebar_list #sidebar = (sidebar_list[0]+league+sidebar_list[1]) sidebar = (sidebar_list[0]+league+rfixtures+goals+sidebar_list[1]) #Fix characters in sidebar sidebar = h.unescape(sidebar) return sidebar
def update_countdown(username, password, subreddit_name, target): user_agent = '/r/{0} countdown bot'.format(subreddit_name) reddit = Reddit(user_agent) reddit.login(username, password) subreddit = reddit.get_subreddit(subreddit_name) settings = subreddit.get_settings() description = HTMLParser().unescape(settings['description']) for key, value in compute_time_ago_params(target).iteritems(): pattern = "\\[[^\\]]*\\]\\(#{0}\\)".format( key) # replace [<anything>](#<key>) repl = "[{0}](#{1})".format(value, key) # with [<value>](#<key>) description = re.sub(pattern, repl, description) print(description) subreddit.update_settings(description=description)
def test_report(self): # login as new user to report submission oth = Reddit(USER_AGENT) oth.login('PyApiTestUser3', '1111') subreddit = oth.get_subreddit(self.sr) submission = None for submission in subreddit.get_new_by_date(): if not submission.hidden: break else: self.fail('Could not find a non-reported submission.') submission.report() # check if submission was reported for report in self.r.get_subreddit(self.sr).get_reports(): if report.id == submission.id: break else: self.fail('Could not find reported submission.')
def scrape(subreddit_name, backfill_to=None): """ Scrape a subreddit. :type subreddit_name: str :type backfill_to: datetime.datetime """ subreddit_name = subreddit_name.lower() imgur_client = ImgurClient( settings.IMGUR_CLIENT_ID, settings.IMGUR_CLIENT_SECRET) reddit = Reddit(user_agent=settings.REDDIT_USER_AGENT) subreddit = reddit.get_subreddit(subreddit_name) with session_manager() as session: if backfill_to is not None: _backfill( session, subreddit, subreddit_name, imgur_client, backfill_to) else: _scrape(session, subreddit, subreddit_name, imgur_client)
def scrape(reddit: praw.Reddit) -> dict: """ Begin a scraping pass. Uses the subreddits defined in config.EDGELORD_SUBREDDITS. :param reddit: The reddit object to use. :return: A dict containing users and a list of subreddits. """ banning = {} for sub in config.EDGELORD_SUBREDDITS: # Get sub. print("Scraping subreddit {}".format(sub)) try: subreddit = reddit.get_subreddit(sub) hot_posts = subreddit.get_hot(limit=30) for i, post in enumerate(hot_posts): print("{} - [{}/30] - Scraping comments of".format(sub, i + 1), post) # Get submitter name. assert isinstance(post, praw.objects.Submission) # TODO: Figure out way to get submitters name. # Otherwise, loop over comments, plucking the submitter off. for comment in praw.helpers.flatten_tree(post.comments): if isinstance(comment, praw.objects.MoreComments): # TODO: Add MoreComments parsing. continue if should_ban(comment): if comment.author is not None: print("{} - [{}/30] - Adding to ban list: {}".format(sub, i + 1, comment.author)) if not comment.author.name in banning: banning[comment.author.name] = [] if sub in banning[comment.author.name]: continue else: banning[comment.author.name].append(sub) except Exception as e: print("Error happened - {}".format(e)) print("Skipping to next sub.") return banning
def random_reddit_image(self, caption=None, bot=None, update=None): """ Find and send a random image from a random subreddit containing images. """ if not (bot and update): return bot.send_chat_action(update.message.chat.id) # choose a random subreddit to pull image from subreddit = choice(self.subreddits) # grab submissions from the subreddit reddit = Reddit(user_agent=self.reddit_user_agent) submissions = reddit.get_subreddit(subreddit).get_hot(limit=50) # skip non-imgur links, animated images, and choose a random submission submission = choice([sub.url for sub in submissions if 'imgur.com' in sub.url]) # find all the image links in the submission, and choose a random one try: image_url = choice(get_image_links_from_imgur(submission)) except (IndexError, ValueError): # no image found, so try again return self.random_reddit_image(caption=caption, bot=bot, update=update) # get the image content logger.info('"/{command}" from {user}: posting image at {url}'.format( command=' '.join([update.command] + update.command_args), user=update.message.user.username, url=image_url, )) response = requests.get(image_url) if response.status_code != 200: bot.send_message(update.message.chat.id, self.error_message) return # image_content = make_thumbnail(response.content) image_content = response.content bot.send_photo(update.message.chat.id, image_content, reply_to_message_id=update.message.id, caption=image_url)
def verify(moderator_username, moderator_password, username, subreddit, flair_text=None, flair_css=None): api = Reddit(user_agent=settings.USER_AGENT) try: api.login(moderator_username, moderator_password) subreddit = api.get_subreddit(subreddit) subreddit.add_contributor(username) api.set_flair(subreddit, username, flair_text, flair_css) except praw.errors.RateLimitExceeded: raise RateLimitExceededException except praw.errors.ModeratorRequired: raise ModeratorRequiredException except praw.errors.InvalidUserPass: raise InvalidLoginException except praw.errors.BadCSS: raise InvalidCSSException except praw.errors.InvalidUser: raise InvalidUserException except praw.errors.APIException as e: raise RedditAPIException(e) except praw.errors.ClientException as e: raise RedditAPIException(e)
class RedditBot(_RedditBotBase): """ Basic extendable Reddit bot. Provides means to loop over a list of whitelisted subreddits. """ VERSION = (0, 0, 0) # override this USER_AGENT = '{name} v{version} (by /u/{admin})' # if loop() returns this the bot will refresh its settings BOT_SHOULD_REFRESH = 'BOT_SHOULD_REFRESH' def __init__(self, config): """ Initialize the bot with a dict of configuration values. """ self._setup(config) self._login(config) self.subreddits = self._get_subreddits() self.blocked_users = self._get_blocked_users() def _setup(self, config): try: admins = config['admins'] if isinstance(admins, list): self.admins = admins else: self.admins = list(map(str.strip, admins.split(','))) self.settings = DEFAULT_SETTINGS.copy() self.settings.update(config.get('settings', {})) except KeyError as e: import sys sys.stderr.write('error: missing {} in configuration'.format(e)) sys.exit(2) def _login(self, config): logger.info('Attempting to login using OAuth2') for attr in ['client_id', 'client_secret', 'redirect_uri']: assert attr in config[ 'oauth_info'], 'Missing `{}` in oauth_info'.format(attr) self.r = Reddit('OAuth Login v1.0') self.r.set_oauth_app_info(**config['oauth_info']) for attr in ['access_token', 'refresh_token']: assert attr in config[ 'access_info'], 'Missing `{}` in access_info'.format(attr) access_info = config['access_info'] access_info['scope'] = self.__class__.get_scope() self.r.set_access_credentials(**access_info) self.bot_name = self.r.user.name self.admins.append(self.bot_name) user_agent = self.USER_AGENT.format(name=self.bot_name, admin=self.admins[0], version='.'.join( map(str, self.VERSION))) logger.debug('User-Agent: {!r}'.format(user_agent)) self.r.http.headers['User-Agent'] = user_agent logger.info('Logged in as {}'.format(self.bot_name)) @classmethod def get_scope(cls): """Basic permission scope for RedditReplyBot operations.""" return super(RedditBot, cls).get_scope() | { 'identity', 'subscribe', 'mysubreddits', } def run_forever(self): self.bot_start() try: while True: self.do_loop() self.refresh() except Exception as e: self.bot_error(e) raise finally: self.bot_stop() def refresh(self): logger.info('Refreshing settings') self.subreddits = self._get_subreddits() self.blocked_users = self._get_blocked_users() def do_loop(self): for subreddit in cycle(self.subreddits): try: if self.loop(subreddit) == self.BOT_SHOULD_REFRESH: break except Forbidden as e: logger.error( 'Forbidden in {}! Removing from whitelist.'.format( subreddit)) self.remove_subreddits(subreddit) break except RateLimitExceeded as e: logger.warning( 'RateLimitExceeded! Sleeping {} seconds.'.format( e.sleep_time)) time.sleep(e.sleep_time) except (ConnectionError, HTTPException) as e: logger.warning( 'Error: Reddit down or no connection? {!r}'.format(e)) time.sleep(self.settings['loop_sleep'] * 10) else: time.sleep(self.settings['loop_sleep']) else: logger.error( "No subreddits in file. Will read file again in 5 seconds.") time.sleep(5) def _get_subreddits(self): subreddits = list( map(lambda s: s.display_name, self.r.get_my_subreddits())) logger.info('Subreddits: {} entries'.format(len(subreddits))) logger.debug('List: {!r}'.format(subreddits)) return subreddits def _get_blocked_users(self, filename=None): """Friends are blocked users, because Reddit only allows blocking users by private messages.""" blocked_users = list(map(lambda u: u.name, self.r.get_friends())) logger.info('Blocked users: {} entries'.format(len(blocked_users))) logger.debug('List: {!r}'.format(blocked_users)) return blocked_users def is_user_blocked(self, user_name): if user_name == self.bot_name: return True return user_name in self.blocked_users def is_subreddit_whitelisted(self, subreddit): return subreddit in self.subreddits def remove_subreddits(self, *subreddits): for sub_name in subreddits: if sub_name in self.subreddits: self.subreddits.remove(sub_name) sub = self.r.get_subreddit(sub_name) sub.unsubscribe() logger.info('Unsubscribed from /r/{}'.format(sub_name)) def add_subreddits(self, *subreddits): for sub_name in subreddits: if sub_name not in self.subreddits: self.subreddits.add(sub_name) sub = self.r.get_subreddit(sub_name) sub.subscribe() logger.info('Subscribed to /r/{}'.format(sub_name)) def block_users(self, *users): for user_name in users: if user_name not in self.blocked_users: self.blocked_users.add(user_name) user = self.r.get_redditor(user_name) user.friend() logger.info('Blocked /u/{}'.format(user_name)) def unblock_users(self, *users): for user_name in users: if user_name in self.blocked_users: self.blocked_users.remove(user_name) user = self.r.get_redditor(user_name) user.unfriend() logger.info('Unblocked /u/{}'.format(user_name))
class Reddit_Cleverbot: def __init__(self, username, password, subreddit='all', useragent=USERAGENT): self.username = username self.password = password self.useragent = useragent self.subreddit = subreddit self.reddit = Reddit(useragent) self.reddit.login(username, password) self.stopped = True self.thread = None self.done = set() self.conversations = dict() def random_hot_comment(self): sub = self.reddit.get_subreddit(self.subreddit) hot = [post for post in sub.get_hot(limit=25)] post = random.choice(hot) comments = praw.helpers.flatten_tree(post.comments) # filter the comments to remove already-replied ones comments = [comment for comment in comments if comment not in self.done and isinstance(comment, praw.objects.Comment)] return random.choice(comments[0:100]) def random_comment(self): comments = self.reddit.get_comments(self.subreddit) # filter the comments to remove already-replied ones comments = [comment for comment in comments if comment not in self.done] return random.choice(comments) def get_summoned_comments(self): comments = self.reddit.get_comments(self.subreddit) children = [comment for comment in comments if comment not in self.done and SUMMON in comment.body] # print "--> " + str(len(children)) + " summons found!" return [self.reddit.get_info(thing_id=comment.parent_id) for comment in children] def reply(self, comment): if self.reddit.get_info(thing_id=comment.parent_id).author.name == self.username: # TODO: handle a threaded conversation over restarts. will need a DB. ugh pass if comment.parent_id in self.conversations: cleverbot = self.conversations[comment.parent_id] else: cleverbot = Cleverbot() response = cleverbot.ask(comment.body) post = comment.reply(response) self.done.add(comment.id) self.conversations[post.id] = copy(cleverbot) def reply_unread(self, interval): for item in self.reddit.get_unread(): if item.parent_id not in self.conversations: print "Could not find conversation! Ignoring for now." pass self.reply(item) item.mark_as_read() time.sleep(interval) def reply_to_summons(self): summons = self.get_summoned_comments() for comment in summons: self.reply(comment) def _run_random(self, interval): while not self.stopped: self.reply_unread(interval) self.reply(self.random_hot_comment()) time.sleep(interval) def run_random(self, interval): self.stopped = False self.thread = Thread(target=self._run_random, args=(interval,)) self.thread.start() def stop(self): self.stopped = True #self.thread.join()
# /r/AFL Subreddit Flair Statistics #!/usr/bin/env python import sys from datetime import datetime from praw import Reddit user = '******' password = '******' srname = 'AFL' edit_reason = 'Updated by /u/rAFLgamethread bot' r = Reddit('AFL-flairstats/0.1') r.login(user, password) sr = r.get_subreddit(srname) flair_templates = { 'adelaide': 'Adelaide', 'adelaide2': 'Adelaide 2', 'adelaide3': 'Adelaide 3', 'adelaide5': 'Adelaide 5', 'brisbane': 'Brisbane', 'brisbane2': 'Brisbane 2', 'brisbane3': 'Brisbane 3', 'brisbane5': 'Brisbane 5', 'carlton': 'Carlton', 'carlton2': 'Carlton 2', 'carlton3': 'Carlton 3', 'carlton4': 'Carlton 4', 'collingwood': 'Collingwood', 'collingwood2': 'Collingwood 2',
def get_reddit_submissions(subreddit='earthporn'): reddit = Reddit(user_agent='earthporn_wallpapers_downloader') submissions = reddit.get_subreddit(subreddit).get_top_from_week() log.info('Fetching submissions from "{0}"'.format(subreddit)) return submissions
class GenericRedditLink: def __init__(self, parent): self.r = Reddit(user_agent="PollBotBestBot") self.parent = parent def __call__(self, msg): if msg["body"].lower().startswith("!r/"): m = msg["body"].lstrip("!r/") spli = m.split(":") subreddit = spli[0] print subreddit if subreddit in config.banned_subreddits: self.parent.scheduler.add("punishment:" + msg["mucnick"], 0.1, self.parent.punishment, args=(msg["mucnick"], ), repeat=True) self.parent.send_message(mto=self.parent.channel, mbody="Nope, not touching that.", mtype="groupchat") return body = self.get_hot(subreddit, msg) if body != None: self.parent.send_message(mto=self.parent.channel, mbody=body, mtype="groupchat") if msg["body"].lower().startswith( "!block ") and msg["mucnick"] in config.admins: m = m.spli(" ") subreddit = spli[1] config.banned_subreddits.append(subreddit) def get_hot(self, subreddit, msg): if msg["type"] == "groupchat": subreddit = self.r.get_subreddit(subreddit) try: if subreddit.over18: pass except (HTTPError, errors.InvalidSubreddit) as E: self.parent.send_message(mto=self.parent.channel, mbody="Learn to Reddit please, " + msg["mucnick"], mtype="groupchat") return None if subreddit.over18: #self.parent.send_message(mto=self.parent.channel,mbody="NSFW content is currently blocked. Direct complaints to mods and admins.",mtype="groupchat") extra = " :nws: " else: extra = "" submissions = subreddit.get_hot(limit=10) a = None a1 = None limit = random.randint(0, 9) while limit > 0: a1 = a print a1 try: a = next(submissions) except StopIteration: a = a1 break print a limit -= 1 try: return "\n" + extra + str( a.title) + extra + "\n" + extra + str(a.url) + extra except AttributeError: return "Reddit API is currently not accepting connections. Please wait ~30 seconds before retrying."
class OAuth2RedditTest(PRAWTest): def setUp(self): self.configure() self.r = Reddit(USER_AGENT, site_name='reddit_oauth_test', disable_update_check=True) def test_authorize_url(self): self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') self.r.set_oauth_app_info(self.r.config.client_id, self.r.config.client_secret, self.r.config.redirect_uri) url, params = self.r.get_authorize_url('...').split('?', 1) self.assertTrue('api/v1/authorize/' in url) params = dict(x.split('=', 1) for x in params.split('&')) expected = { 'client_id': self.r.config.client_id, 'duration': 'temporary', 'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F' 'authorize_callback'), 'response_type': 'code', 'scope': 'identity', 'state': '...' } self.assertEqual(expected, params) @betamax() @mock_sys_stream("stdin") def test_empty_captcha_file(self): # Use the alternate account because it has low karma, # so we can test the captcha. self.r.refresh_access_information(self.other_refresh_token['submit']) self.assertRaises(errors.InvalidCaptcha, self.r.submit, self.sr, 'captcha test will fail', 'body') @betamax() def test_get_access_information(self): # If this test fails, the following URL will need to be visted in order # to obtain a new code to pass to `get_access_information`: # self.r.get_authorize_url('...') token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ') expected = { 'access_token': self.r.access_token, 'refresh_token': None, 'scope': set(('identity', )) } self.assertEqual(expected, token) self.assertEqual('PyAPITestUser2', text_type(self.r.user)) @betamax() def test_get_access_information_with_invalid_code(self): self.assertRaises(errors.OAuthInvalidGrant, self.r.get_access_information, 'invalid_code') @betamax() @mock_sys_stream("stdin") def test_inject_captcha_into_kwargs_and_raise(self): # Use the alternate account because it has low karma, # so we can test the captcha. self.r.refresh_access_information(self.other_refresh_token['submit']) # praw doesn't currently add the captcha into kwargs so lets # write a function in which it would and alias it to Reddit.submit @decorators.restrict_access(scope='submit') @decorators.require_captcha def submit_alias(r, sr, title, text, **kw): return self.r.submit.__wrapped__.__wrapped__( r, sr, title, text, captcha=kw.get('captcha')) self.assertRaises(errors.InvalidCaptcha, submit_alias, self.r, self.sr, 'captcha test will fail', 'body') def test_invalid_app_access_token(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_access_information, 'dummy_code') def test_invalid_app_authorize_url(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') @betamax() def test_invalid_set_access_credentials(self): self.assertRaises(errors.OAuthInvalidToken, self.r.set_access_credentials, set( ('identity', )), 'dummy_access_token') def test_oauth_scope_required(self): self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url') self.r.set_access_credentials(set('dummy_scope', ), 'dummy_token') self.assertRaises(errors.OAuthScopeRequired, self.r.get_me) def test_raise_client_exception(self): def raise_client_exception(*args): raise errors.ClientException(*args) self.assertRaises(errors.ClientException, raise_client_exception) self.assertRaises(errors.ClientException, raise_client_exception, 'test') ce_message = errors.ClientException('Test') ce_no_message = errors.ClientException() self.assertEqual(ce_message.message, str(ce_message)) self.assertEqual(ce_no_message.message, str(ce_no_message)) def test_raise_http_exception(self): def raise_http_exception(): raise errors.HTTPException('fakeraw') self.assertRaises(errors.HTTPException, raise_http_exception) http_exception = errors.HTTPException('fakeraw') self.assertEqual(http_exception.message, str(http_exception)) def test_raise_oauth_exception(self): oerrormessage = "fakemessage" oerrorurl = "http://oauth.reddit.com/" def raise_oauth_exception(): raise errors.OAuthException(oerrormessage, oerrorurl) self.assertRaises(errors.OAuthException, raise_oauth_exception) oauth_exception = errors.OAuthException(oerrormessage, oerrorurl) self.assertEqual( oauth_exception.message + " on url {0}".format(oauth_exception.url), str(oauth_exception)) def test_raise_redirect_exception(self): apiurl = "http://api.reddit.com/" oauthurl = "http://oauth.reddit.com/" def raise_redirect_exception(): raise errors.RedirectException(apiurl, oauthurl) self.assertRaises(errors.RedirectException, raise_redirect_exception) redirect_exception = errors.RedirectException(apiurl, oauthurl) self.assertEqual(redirect_exception.message, str(redirect_exception)) @betamax() def test_scope_history(self): self.r.refresh_access_information(self.refresh_token['history']) self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted())) @betamax() def test_scope_identity(self): self.r.refresh_access_information(self.refresh_token['identity']) self.assertEqual(self.un, self.r.get_me().name) @betamax() def test_scope_mysubreddits(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) self.assertTrue(list(self.r.get_my_moderation())) @betamax() def test_scope_creddits(self): # Assume there are insufficient creddits. self.r.refresh_access_information(self.refresh_token['creddits']) redditor = self.r.get_redditor('bboe') sub = self.r.get_submission(url=self.comment_url) # Test error conditions self.assertRaises(TypeError, sub.gild, months=1) for value in (False, 0, -1, '0', '-1', 37, '37'): self.assertRaises(TypeError, redditor.gild, value) # Test object gilding self.assertRaises(errors.InsufficientCreddits, redditor.gild) self.assertRaises(errors.InsufficientCreddits, sub.gild) self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild) @betamax() def test_scope_privatemessages(self): self.r.refresh_access_information( self.refresh_token['privatemessages']) self.assertTrue(list(self.r.get_inbox())) @betamax() def test_scope_read(self): self.r.refresh_access_information(self.refresh_token['read']) self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0) fullname = '{0}_{1}'.format(self.r.config.by_object[Submission], self.priv_submission_id) method1 = self.r.get_info(thing_id=fullname) method2 = self.r.get_submission(submission_id=self.priv_submission_id) self.assertEqual(method1, method2) @betamax() def test_scope_read_get_front_page(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) subscribed = list(self.r.get_my_subreddits(limit=None)) self.r.refresh_access_information(self.refresh_token['read']) for post in self.r.get_front_page(): self.assertTrue(post.subreddit in subscribed) @betamax() def test_set_access_credentials(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information( self.refresh_token['identity'], update_session=False) self.assertTrue(self.r.user is None) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax() def test_set_access_credentials_with_list(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information( self.refresh_token['identity'], update_session=False) self.assertTrue(self.r.user is None) result['scope'] = list(result['scope']) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax() def test_set_access_credentials_with_string(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information( self.refresh_token['identity'], update_session=False) self.assertTrue(self.r.user is None) result['scope'] = ' '.join(result['scope']) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax() @mock_sys_stream("stdin", "ljgtoo") def test_solve_captcha(self): # Use the alternate account because it has low karma, # so we can test the captcha. self.r.refresh_access_information(self.other_refresh_token['submit']) self.r.submit(self.sr, 'captcha test', 'body') @betamax() @mock_sys_stream("stdin", "DFIRSW") def test_solve_captcha_on_bound_subreddit(self): # Use the alternate account because it has low karma, # so we can test the captcha. self.r.refresh_access_information(self.other_refresh_token['submit']) subreddit = self.r.get_subreddit(self.sr) # praw doesn't currently have a function in which require_captcha # gets a reddit instance from a subreddit and uses it, so lets # write a function in which it would and alias it to Reddit.submit @decorators.restrict_access(scope='submit') @decorators.require_captcha def submit_alias(sr, title, text, **kw): return self.r.submit.__wrapped__.__wrapped__( self.r, sr, title, text, captcha=kw.get('captcha')) submit_alias(subreddit, 'captcha test on bound subreddit', 'body') @betamax() def test_oauth_without_identy_doesnt_set_user(self): self.assertTrue(self.r.user is None) self.r.refresh_access_information(self.refresh_token['edit']) self.assertTrue(self.r.user is None)
class SubRedditStats(object): """Contain all the functionality of the subreddit_stats command.""" post_prefix = tt('Subreddit Stats:') post_header = tt('---\n###{0}\n') post_footer = tt('>Generated with [BBoe](/u/bboe)\'s [Subreddit Stats]' '(https://github.com/praw-dev/prawtools) \n{0}' 'SRS Marker: {1}') re_marker = re.compile(r'SRS Marker: (\d+)') @staticmethod def _previous_max(submission): try: val = SubRedditStats.re_marker.findall(submission.selftext)[-1] return float(val) except (IndexError, TypeError): print('End marker not found in previous submission. Aborting') sys.exit(1) @staticmethod def _permalink(permalink): tokens = permalink.split('/') if tokens[8] == '': # submission return tt('/comments/{0}/_/').format(tokens[6]) else: # comment return tt('/comments/{0}/_/{1}?context=1').format( tokens[6], tokens[8]) @staticmethod def _pts(points): return '1 pt' if points == 1 else '{0} pts'.format(points) @staticmethod def _user(user): if user is None: return '_deleted_' elif isinstance(user, Redditor): user = str(user) return tt('[{0}](/user/{1})').format(user.replace('_', r'\_'), user) @staticmethod def _submit(func, *args, **kwargs): def sleep(sleep_time): print('\tSleeping for {0} seconds'.format(sleep_time)) time.sleep(sleep_time) while True: try: return func(*args, **kwargs) except RateLimitExceeded as error: sleep(error.sleep_time) except ExceptionList as exception_list: for error in exception_list.errors: if isinstance(error, RateLimitExceeded): sleep(error.sleep_time) break else: raise def __init__(self, subreddit, site, verbosity, distinguished): """Initialize the SubRedditStats instance with config options.""" self.reddit = Reddit(str(self), site, disable_update_check=True) self.subreddit = self.reddit.get_subreddit(subreddit) self.verbosity = verbosity self.distinguished = distinguished self.submissions = [] self.comments = [] self.submitters = defaultdict(list) self.commenters = defaultdict(list) self.min_date = 0 self.max_date = time.time() - DAYS_IN_SECONDS * 3 self.prev_srs = None def login(self, user, pswd): """Login and provide debugging output if so wanted.""" if self.verbosity > 0: print('Logging in') self.reddit.login(user, pswd) def msg(self, msg, level, overwrite=False): """Output a messaage to the screen if the verbosity is sufficient.""" if self.verbosity and self.verbosity >= level: sys.stdout.write(msg) if overwrite: sys.stdout.write('\r') sys.stdout.flush() else: sys.stdout.write('\n') def prev_stat(self, prev_url): """Load the previous subreddit stats page.""" submission = self.reddit.get_submission(prev_url) self.min_date = self._previous_max(submission) self.prev_srs = prev_url def fetch_recent_submissions(self, max_duration, after, exclude_self, exclude_link, since_last=True): """Fetch recent submissions in subreddit with boundaries. Does not include posts within the last three days as their scores may not be representative. :param max_duration: When set, specifies the number of days to include :param after: When set, fetch all submission after this submission id. :param exclude_self: When true, don't include self posts. :param exclude_link: When true, don't include links. :param since_last: When true use info from last submission to determine the stop point :returns: True if any submissions were found. """ if exclude_self and exclude_link: raise TypeError('Cannot set both exclude_self and exclude_link.') if max_duration: self.min_date = self.max_date - DAYS_IN_SECONDS * max_duration params = {'after': after} if after else None self.msg('DEBUG: Fetching submissions', 1) for submission in self.subreddit.get_new(limit=None, params=params): if submission.created_utc > self.max_date: continue if submission.created_utc <= self.min_date: break if since_last and str(submission.author) == str(self.reddit.user) \ and submission.title.startswith(self.post_prefix): # Use info in this post to update the min_date # And don't include this post self.msg( tt('Found previous: {0}').format(safe_title(submission)), 2) if self.prev_srs is None: # Only use the most recent self.min_date = max(self.min_date, self._previous_max(submission)) self.prev_srs = submission.permalink continue if exclude_self and submission.is_self: continue if exclude_link and not submission.is_self: continue self.submissions.append(submission) num_submissions = len(self.submissions) self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1) if num_submissions == 0: return False # Update real min and max dates self.submissions.sort(key=lambda x: x.created_utc) self.min_date = self.submissions[0].created_utc self.max_date = self.submissions[-1].created_utc return True def fetch_top_submissions(self, top, exclude_self, exclude_link): """Fetch top 1000 submissions by some top value. :param top: One of week, month, year, all :param exclude_self: When true, don't include self posts. :param exclude_link: When true, include only self posts :returns: True if any submissions were found. """ if exclude_self and exclude_link: raise TypeError('Cannot set both exclude_self and exclude_link.') if top not in ('day', 'week', 'month', 'year', 'all'): raise TypeError('{0!r} is not a valid top value'.format(top)) self.msg('DEBUG: Fetching submissions', 1) params = {'t': top} for submission in self.subreddit.get_top(limit=None, params=params): if exclude_self and submission.is_self: continue if exclude_link and not submission.is_self: continue self.submissions.append(submission) num_submissions = len(self.submissions) self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1) if num_submissions == 0: return False # Update real min and max dates self.submissions.sort(key=lambda x: x.created_utc) self.min_date = self.submissions[0].created_utc self.max_date = self.submissions[-1].created_utc return True def process_submitters(self): """Group submissions by author.""" self.msg('DEBUG: Processing Submitters', 1) for submission in self.submissions: if submission.author and (self.distinguished or submission.distinguished is None): self.submitters[str(submission.author)].append(submission) def process_commenters(self): """Group comments by author.""" num = len(self.submissions) self.msg('DEBUG: Processing Commenters on {0} submissions'.format(num), 1) for i, submission in enumerate(self.submissions): # Explicitly fetch as many comments as possible by top sort # Note that this is the first time the complete submission object # is obtained. Only a partial object was returned when getting the # subreddit listings. try: submission = self.reddit.get_submission(submission.permalink, comment_limit=None, comment_sort='top') except HTTPError as exc: print('Ignoring comments on {0} due to HTTP status {1}'.format( submission.url, exc.response.status_code)) continue self.msg('{0}/{1} submissions'.format(i + 1, num), 2, overwrite=True) if submission.num_comments == 0: continue skipped = submission.replace_more_comments() if skipped: skip_num = sum(x.count for x in skipped) print('Ignored {0} comments ({1} MoreComment objects)'.format( skip_num, len(skipped))) comments = [ x for x in flatten_tree(submission.comments) if self.distinguished or x.distinguished is None ] self.comments.extend(comments) # pylint: disable=W0212 for orphans in itervalues(submission._orphaned): self.comments.extend(orphans) # pylint: enable=W0212 for comment in self.comments: if comment.author: self.commenters[str(comment.author)].append(comment) def basic_stats(self): """Return a markdown representation of simple statistics.""" sub_score = sum(x.score for x in self.submissions) comm_score = sum(x.score for x in self.comments) sub_duration = self.max_date - self.min_date sub_rate = (86400. * len(self.submissions) / sub_duration if sub_duration else len(self.submissions)) # Compute comment rate if self.comments: self.comments.sort(key=lambda x: x.created_utc) duration = (self.comments[-1].created_utc - self.comments[0].created_utc) comm_rate = (86400. * len(self.comments) / duration if duration else len(self.comments)) else: comm_rate = 0 values = [('Total', len(self.submissions), len(self.comments)), ('Rate (per day)', '{0:.2f}'.format(sub_rate), '{0:.2f}'.format(comm_rate)), ('Unique Redditors', len(self.submitters), len(self.commenters)), ('Combined Score', sub_score, comm_score)] retval = 'Period: {0:.2f} days\n\n'.format(sub_duration / 86400.) retval += '||Submissions|Comments|\n:-:|--:|--:\n' for quad in values: # pylint: disable=W0142 retval += '__{0}__|{1}|{2}\n'.format(*quad) # pylint: enable=W0142 return retval + '\n' def top_submitters(self, num, num_submissions): """Return a markdown representation of the top submitters.""" num = min(num, len(self.submitters)) if num <= 0: return '' top_submitters = sorted(iteritems(self.submitters), reverse=True, key=lambda x: (sum(y.score for y in x[1]), len(x[1])))[:num] retval = self.post_header.format('Top Submitters\' Top Submissions') for (author, submissions) in top_submitters: retval += '0. {0}, {1} submission{2}: {3}\n'.format( self._pts(sum(x.score for x in submissions)), len(submissions), 's' if len(submissions) > 1 else '', self._user(author)) for sub in sorted(submissions, reverse=True, key=lambda x: x.score)[:num_submissions]: title = safe_title(sub) if sub.permalink != sub.url: retval += tt(' 0. [{0}]({1})').format(title, sub.url) else: retval += tt(' 0. {0}').format(title) retval += ' ({0}, [{1} comment{2}]({3}))\n'.format( self._pts(sub.score), sub.num_comments, 's' if sub.num_comments > 1 else '', self._permalink(sub.permalink)) retval += '\n' return retval def top_commenters(self, num): """Return a markdown representation of the top commenters.""" score = lambda x: x.score num = min(num, len(self.commenters)) if num <= 0: return '' top_commenters = sorted(iteritems(self.commenters), reverse=True, key=lambda x: (sum(score(y) for y in x[1]), len(x[1])))[:num] retval = self.post_header.format('Top Commenters') for author, comments in top_commenters: retval += '0. {0} ({1}, {2} comment{3})\n'.format( self._user(author), self._pts(sum(score(x) for x in comments)), len(comments), 's' if len(comments) > 1 else '') return '{0}\n'.format(retval) def top_submissions(self, num): """Return a markdown representation of the top submissions.""" num = min(num, len(self.submissions)) if num <= 0: return '' top_submissions = sorted([ x for x in self.submissions if self.distinguished or x.distinguished is None ], reverse=True, key=lambda x: x.score)[:num] if not top_submissions: return '' retval = self.post_header.format('Top Submissions') for sub in top_submissions: title = safe_title(sub) if sub.permalink != sub.url: retval += tt('0. [{0}]({1})').format(title, sub.url) else: retval += tt('0. {0}').format(title) retval += ' by {0} ({1}, [{2} comment{3}]({4}))\n'.format( self._user(sub.author), self._pts(sub.score), sub.num_comments, 's' if sub.num_comments > 1 else '', self._permalink(sub.permalink)) return tt('{0}\n').format(retval) def top_comments(self, num): """Return a markdown representation of the top comments.""" score = lambda x: x.score num = min(num, len(self.comments)) if num <= 0: return '' top_comments = sorted(self.comments, reverse=True, key=score)[:num] retval = self.post_header.format('Top Comments') for comment in top_comments: title = safe_title(comment.submission) retval += tt('0. {0}: {1}\'s [comment]({2}) in {3}\n').format( self._pts(score(comment)), self._user(comment.author), self._permalink(comment.permalink), title) return tt('{0}\n').format(retval) def publish_results(self, subreddit, submitters, commenters, submissions, comments, top, debug=False): """Submit the results to the subreddit. Has no return value (None).""" def timef(timestamp, date_only=False): """Return a suitable string representaation of the timestamp.""" dtime = datetime.fromtimestamp(timestamp) if date_only: retval = dtime.strftime('%Y-%m-%d') else: retval = dtime.strftime('%Y-%m-%d %H:%M PDT') return retval if self.prev_srs: prev = '[Prev SRS]({0}) \n'.format(self._permalink(self.prev_srs)) else: prev = '' basic = self.basic_stats() t_commenters = self.top_commenters(commenters) t_submissions = self.top_submissions(submissions) t_comments = self.top_comments(comments) footer = self.post_footer.format(prev, self.max_date) body = '' num_submissions = 10 while body == '' or len(body) > MAX_BODY_SIZE and num_submissions > 2: t_submitters = self.top_submitters(submitters, num_submissions) body = (basic + t_submitters + t_commenters + t_submissions + t_comments + footer) num_submissions -= 1 if len(body) > MAX_BODY_SIZE: print('The resulting message is too big. Not submitting.') debug = True # Set the initial title base_title = '{0} {1} {2}posts from {3} to {4}'.format( self.post_prefix, str(self.subreddit), 'top ' if top else '', timef(self.min_date, True), timef(self.max_date)) submitted = False while not debug and not submitted: if subreddit: # Verify the user wants to submit to the subreddit msg = ('You are about to submit to subreddit {0!r} as {1!r}.\n' 'Are you sure? yes/[no]: '.format( subreddit, str(self.reddit.user))) sys.stdout.write(msg) sys.stdout.flush() if sys.stdin.readline().strip().lower() not in ['y', 'yes']: subreddit = None elif not subreddit: # Prompt for the subreddit to submit to msg = ('Please enter a subreddit to submit to (press return to' ' abort): ') sys.stdout.write(msg) sys.stdout.flush() subreddit = sys.stdin.readline().strip() if not subreddit: print('Submission aborted\n') debug = True # Vary the title depending on where posting if str(self.subreddit) == subreddit: title = '{0} {1}posts from {2} to {3}'.format( self.post_prefix, 'top ' if top else '', timef(self.min_date, True), timef(self.max_date)) else: title = base_title if subreddit: # Attempt to make the submission try: res = self._submit(self.reddit.submit, subreddit, title, text=body) print(res.permalink) submitted = True except Exception as error: # pylint: disable=W0703 print('The submission failed:' + str(error)) subreddit = None if not submitted: print(base_title) print(body) def save_csv(self, filename): """Create csv file containing comments and submissions by author.""" redditors = set(self.submitters.keys()).union(self.commenters.keys()) mapping = dict((x.lower(), x) for x in redditors) with codecs.open(filename, 'w', encoding='utf-8') as outfile: outfile.write('username, type, permalink, score\n') for _, redditor in sorted(mapping.items()): for submission in self.submitters.get(redditor, []): outfile.write(u'{0}, submission, {1}, {2}\n'.format( redditor, submission.permalink, submission.score)) for comment in self.commenters.get(redditor, []): outfile.write(u'{0}, comment, {1}, {2}\n'.format( redditor, comment.permalink, comment.score))
def get_reddit_submissions(subreddit='earthporn'): reddit = Reddit(user_agent='earthporn_wallpapers_downloader') submissions = reddit.get_subreddit(subreddit).get_top_from_week() log.info('Fetching submissions from "{0}"'.format(subreddit)) return submissions
class SubRedditStats(object): """Contain all the functionality of the subreddit_stats command.""" post_prefix = tt('Subreddit Stats:') post_header = tt('---\n###{0}\n') post_footer = tt('>Generated with [BBoe](/u/bboe)\'s [Subreddit Stats]' '(https://github.com/praw-dev/prawtools) \n{0}' 'SRS Marker: {1}') re_marker = re.compile(r'SRS Marker: (\d+)') @staticmethod def _previous_max(submission): try: val = SubRedditStats.re_marker.findall(submission.selftext)[-1] return float(val) except (IndexError, TypeError): print('End marker not found in previous submission. Aborting') sys.exit(1) @staticmethod def _permalink(permalink): tokens = permalink.split('/') if tokens[8] == '': # submission return tt('/comments/{0}/_/').format(tokens[6]) else: # comment return tt('/comments/{0}/_/{1}?context=1').format(tokens[6], tokens[8]) @staticmethod def _pts(points): return '1 pt' if points == 1 else '{0} pts'.format(points) @staticmethod def _user(user): if user is None: return '_deleted_' elif isinstance(user, Redditor): user = str(user) return tt('[{0}](/user/{1})').format(user.replace('_', r'\_'), user) @staticmethod def _submit(func, *args, **kwargs): def sleep(sleep_time): print('\tSleeping for {0} seconds'.format(sleep_time)) time.sleep(sleep_time) while True: try: return func(*args, **kwargs) except RateLimitExceeded as error: sleep(error.sleep_time) except ExceptionList as exception_list: for error in exception_list.errors: if isinstance(error, RateLimitExceeded): sleep(error.sleep_time) break else: raise def __init__(self, subreddit, site, verbosity, distinguished): """Initialize the SubRedditStats instance with config options.""" self.reddit = Reddit(str(self), site, disable_update_check=True) self.subreddit = self.reddit.get_subreddit(subreddit) self.verbosity = verbosity self.distinguished = distinguished self.submissions = [] self.comments = [] self.submitters = defaultdict(list) self.commenters = defaultdict(list) self.min_date = 0 self.max_date = time.time() - DAYS_IN_SECONDS * 3 self.prev_srs = None def login(self, user, pswd): """Login and provide debugging output if so wanted.""" if self.verbosity > 0: print('Logging in') self.reddit.login(user, pswd) def msg(self, msg, level, overwrite=False): """Output a messaage to the screen if the verbosity is sufficient.""" if self.verbosity and self.verbosity >= level: sys.stdout.write(msg) if overwrite: sys.stdout.write('\r') sys.stdout.flush() else: sys.stdout.write('\n') def prev_stat(self, prev_url): """Load the previous subreddit stats page.""" submission = self.reddit.get_submission(prev_url) self.min_date = self._previous_max(submission) self.prev_srs = prev_url def fetch_recent_submissions(self, max_duration, after, exclude_self, exclude_link, since_last=True): """Fetch recent submissions in subreddit with boundaries. Does not include posts within the last three days as their scores may not be representative. :param max_duration: When set, specifies the number of days to include :param after: When set, fetch all submission after this submission id. :param exclude_self: When true, don't include self posts. :param exclude_link: When true, don't include links. :param since_last: When true use info from last submission to determine the stop point :returns: True if any submissions were found. """ if exclude_self and exclude_link: raise TypeError('Cannot set both exclude_self and exclude_link.') if max_duration: self.min_date = self.max_date - DAYS_IN_SECONDS * max_duration params = {'after': after} if after else None self.msg('DEBUG: Fetching submissions', 1) for submission in self.subreddit.get_new(limit=None, params=params): if submission.created_utc > self.max_date: continue if submission.created_utc <= self.min_date: break if since_last and str(submission.author) == str(self.reddit.user) \ and submission.title.startswith(self.post_prefix): # Use info in this post to update the min_date # And don't include this post self.msg(tt('Found previous: {0}') .format(safe_title(submission)), 2) if self.prev_srs is None: # Only use the most recent self.min_date = max(self.min_date, self._previous_max(submission)) self.prev_srs = submission.permalink continue if exclude_self and submission.is_self: continue if exclude_link and not submission.is_self: continue self.submissions.append(submission) num_submissions = len(self.submissions) self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1) if num_submissions == 0: return False # Update real min and max dates self.submissions.sort(key=lambda x: x.created_utc) self.min_date = self.submissions[0].created_utc self.max_date = self.submissions[-1].created_utc return True def fetch_top_submissions(self, top, exclude_self, exclude_link): """Fetch top 1000 submissions by some top value. :param top: One of week, month, year, all :param exclude_self: When true, don't include self posts. :param exclude_link: When true, include only self posts :returns: True if any submissions were found. """ if exclude_self and exclude_link: raise TypeError('Cannot set both exclude_self and exclude_link.') if top not in ('day', 'week', 'month', 'year', 'all'): raise TypeError('{0!r} is not a valid top value'.format(top)) self.msg('DEBUG: Fetching submissions', 1) params = {'t': top} for submission in self.subreddit.get_top(limit=None, params=params): if exclude_self and submission.is_self: continue if exclude_link and not submission.is_self: continue self.submissions.append(submission) num_submissions = len(self.submissions) self.msg('DEBUG: Found {0} submissions'.format(num_submissions), 1) if num_submissions == 0: return False # Update real min and max dates self.submissions.sort(key=lambda x: x.created_utc) self.min_date = self.submissions[0].created_utc self.max_date = self.submissions[-1].created_utc return True def process_submitters(self): """Group submissions by author.""" self.msg('DEBUG: Processing Submitters', 1) for submission in self.submissions: if submission.author and (self.distinguished or submission.distinguished is None): self.submitters[str(submission.author)].append(submission) def process_commenters(self): """Group comments by author.""" num = len(self.submissions) self.msg('DEBUG: Processing Commenters on {0} submissions'.format(num), 1) for i, submission in enumerate(self.submissions): # Explicitly fetch as many comments as possible by top sort # Note that this is the first time the complete submission object # is obtained. Only a partial object was returned when getting the # subreddit listings. try: submission = self.reddit.get_submission(submission.permalink, comment_limit=None, comment_sort='top') except HTTPError as exc: print('Ignoring comments on {0} due to HTTP status {1}' .format(submission.url, exc.response.status_code)) continue self.msg('{0}/{1} submissions'.format(i + 1, num), 2, overwrite=True) if submission.num_comments == 0: continue skipped = submission.replace_more_comments() if skipped: skip_num = sum(x.count for x in skipped) print('Ignored {0} comments ({1} MoreComment objects)' .format(skip_num, len(skipped))) comments = [x for x in flatten_tree(submission.comments) if self.distinguished or x.distinguished is None] self.comments.extend(comments) # pylint: disable=W0212 for orphans in itervalues(submission._orphaned): self.comments.extend(orphans) # pylint: enable=W0212 for comment in self.comments: if comment.author: self.commenters[str(comment.author)].append(comment) def basic_stats(self): """Return a markdown representation of simple statistics.""" sub_score = sum(x.score for x in self.submissions) comm_score = sum(x.score for x in self.comments) sub_duration = self.max_date - self.min_date sub_rate = (86400. * len(self.submissions) / sub_duration if sub_duration else len(self.submissions)) # Compute comment rate if self.comments: self.comments.sort(key=lambda x: x.created_utc) duration = (self.comments[-1].created_utc - self.comments[0].created_utc) comm_rate = (86400. * len(self.comments) / duration if duration else len(self.comments)) else: comm_rate = 0 values = [('Total', len(self.submissions), len(self.comments)), ('Rate (per day)', '{0:.2f}'.format(sub_rate), '{0:.2f}'.format(comm_rate)), ('Unique Redditors', len(self.submitters), len(self.commenters)), ('Combined Score', sub_score, comm_score)] retval = 'Period: {0:.2f} days\n\n'.format(sub_duration / 86400.) retval += '||Submissions|Comments|\n:-:|--:|--:\n' for quad in values: # pylint: disable=W0142 retval += '__{0}__|{1}|{2}\n'.format(*quad) # pylint: enable=W0142 return retval + '\n' def top_submitters(self, num, num_submissions): """Return a markdown representation of the top submitters.""" num = min(num, len(self.submitters)) if num <= 0: return '' top_submitters = sorted(iteritems(self.submitters), reverse=True, key=lambda x: (sum(y.score for y in x[1]), len(x[1])))[:num] retval = self.post_header.format('Top Submitters\' Top Submissions') for (author, submissions) in top_submitters: retval += '0. {0}, {1} submission{2}: {3}\n'.format( self._pts(sum(x.score for x in submissions)), len(submissions), 's' if len(submissions) > 1 else '', self._user(author)) for sub in sorted(submissions, reverse=True, key=lambda x: x.score)[:num_submissions]: title = safe_title(sub) if sub.permalink != sub.url: retval += tt(' 0. [{0}]({1})').format(title, sub.url) else: retval += tt(' 0. {0}').format(title) retval += ' ({0}, [{1} comment{2}]({3}))\n'.format( self._pts(sub.score), sub.num_comments, 's' if sub.num_comments > 1 else '', self._permalink(sub.permalink)) retval += '\n' return retval def top_commenters(self, num): """Return a markdown representation of the top commenters.""" score = lambda x: x.score num = min(num, len(self.commenters)) if num <= 0: return '' top_commenters = sorted(iteritems(self.commenters), reverse=True, key=lambda x: (sum(score(y) for y in x[1]), len(x[1])))[:num] retval = self.post_header.format('Top Commenters') for author, comments in top_commenters: retval += '0. {0} ({1}, {2} comment{3})\n'.format( self._user(author), self._pts(sum(score(x) for x in comments)), len(comments), 's' if len(comments) > 1 else '') return '{0}\n'.format(retval) def top_submissions(self, num): """Return a markdown representation of the top submissions.""" num = min(num, len(self.submissions)) if num <= 0: return '' top_submissions = sorted( [x for x in self.submissions if self.distinguished or x.distinguished is None], reverse=True, key=lambda x: x.score)[:num] if not top_submissions: return '' retval = self.post_header.format('Top Submissions') for sub in top_submissions: title = safe_title(sub) if sub.permalink != sub.url: retval += tt('0. [{0}]({1})').format(title, sub.url) else: retval += tt('0. {0}').format(title) retval += ' by {0} ({1}, [{2} comment{3}]({4}))\n'.format( self._user(sub.author), self._pts(sub.score), sub.num_comments, 's' if sub.num_comments > 1 else '', self._permalink(sub.permalink)) return tt('{0}\n').format(retval) def top_comments(self, num): """Return a markdown representation of the top comments.""" score = lambda x: x.score num = min(num, len(self.comments)) if num <= 0: return '' top_comments = sorted(self.comments, reverse=True, key=score)[:num] retval = self.post_header.format('Top Comments') for comment in top_comments: title = safe_title(comment.submission) retval += tt('0. {0}: {1}\'s [comment]({2}) in {3}\n').format( self._pts(score(comment)), self._user(comment.author), self._permalink(comment.permalink), title) return tt('{0}\n').format(retval) def publish_results(self, subreddit, submitters, commenters, submissions, comments, top, debug=False): """Submit the results to the subreddit. Has no return value (None).""" def timef(timestamp, date_only=False): """Return a suitable string representaation of the timestamp.""" dtime = datetime.fromtimestamp(timestamp) if date_only: retval = dtime.strftime('%Y-%m-%d') else: retval = dtime.strftime('%Y-%m-%d %H:%M PDT') return retval if self.prev_srs: prev = '[Prev SRS]({0}) \n'.format(self._permalink(self.prev_srs)) else: prev = '' basic = self.basic_stats() t_commenters = self.top_commenters(commenters) t_submissions = self.top_submissions(submissions) t_comments = self.top_comments(comments) footer = self.post_footer.format(prev, self.max_date) body = '' num_submissions = 10 while body == '' or len(body) > MAX_BODY_SIZE and num_submissions > 2: t_submitters = self.top_submitters(submitters, num_submissions) body = (basic + t_submitters + t_commenters + t_submissions + t_comments + footer) num_submissions -= 1 if len(body) > MAX_BODY_SIZE: print('The resulting message is too big. Not submitting.') debug = True # Set the initial title base_title = '{0} {1} {2}posts from {3} to {4}'.format( self.post_prefix, str(self.subreddit), 'top ' if top else '', timef(self.min_date, True), timef(self.max_date)) submitted = False while not debug and not submitted: if subreddit: # Verify the user wants to submit to the subreddit msg = ('You are about to submit to subreddit {0!r} as {1!r}.\n' 'Are you sure? yes/[no]: ' .format(subreddit, str(self.reddit.user))) sys.stdout.write(msg) sys.stdout.flush() if sys.stdin.readline().strip().lower() not in ['y', 'yes']: subreddit = None elif not subreddit: # Prompt for the subreddit to submit to msg = ('Please enter a subreddit to submit to (press return to' ' abort): ') sys.stdout.write(msg) sys.stdout.flush() subreddit = sys.stdin.readline().strip() if not subreddit: print('Submission aborted\n') debug = True # Vary the title depending on where posting if str(self.subreddit) == subreddit: title = '{0} {1}posts from {2} to {3}'.format( self.post_prefix, 'top ' if top else '', timef(self.min_date, True), timef(self.max_date)) else: title = base_title if subreddit: # Attempt to make the submission try: res = self._submit(self.reddit.submit, subreddit, title, text=body) print(res.permalink) submitted = True except Exception as error: # pylint: disable=W0703 print('The submission failed:' + str(error)) subreddit = None if not submitted: print(base_title) print(body) def save_csv(self, filename): """Create csv file containing comments and submissions by author.""" redditors = set(self.submitters.keys()).union(self.commenters.keys()) mapping = dict((x.lower(), x) for x in redditors) with codecs.open(filename, 'w', encoding='utf-8') as outfile: outfile.write('username, type, permalink, score\n') for _, redditor in sorted(mapping.items()): for submission in self.submitters.get(redditor, []): outfile.write(u'{0}, submission, {1}, {2}\n' .format(redditor, submission.permalink, submission.score)) for comment in self.commenters.get(redditor, []): outfile.write(u'{0}, comment, {1}, {2}\n' .format(redditor, comment.permalink, comment.score))
class OAuth2RedditTest(PRAWTest): def setUp(self): self.configure() self.r = Reddit(USER_AGENT, site_name="reddit_oauth_test", disable_update_check=True) def test_authorize_url(self): self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, "dummy_state") self.r.set_oauth_app_info(self.r.config.client_id, self.r.config.client_secret, self.r.config.redirect_uri) url, params = self.r.get_authorize_url("...").split("?", 1) self.assertTrue("api/v1/authorize/" in url) params = dict(x.split("=", 1) for x in params.split("&")) expected = { "client_id": self.r.config.client_id, "duration": "temporary", "redirect_uri": ("https%3A%2F%2F127.0.0.1%3A65010%2F" "authorize_callback"), "response_type": "code", "scope": "identity", "state": "...", } self.assertEqual(expected, params) # @betamax() is currently broken for this test def test_auto_refresh_token(self): self.r.refresh_access_information(self.refresh_token["identity"]) old_token = self.r.access_token self.r.access_token += "x" # break the token self.r.user.refresh() current_token = self.r.access_token self.assertNotEqual(old_token, current_token) self.r.user.refresh() self.assertEqual(current_token, self.r.access_token) @betamax() def test_get_access_information(self): # If this test fails, the following URL will need to be visted in order # to obtain a new code to pass to `get_access_information`: # self.r.get_authorize_url('...') token = self.r.get_access_information("MQALrr1di8GzcnT8szbTWhLcBUQ") expected = {"access_token": self.r.access_token, "refresh_token": None, "scope": set(("identity",))} self.assertEqual(expected, token) self.assertEqual("PyAPITestUser2", text_type(self.r.user)) @betamax() def test_get_access_information_with_invalid_code(self): self.assertRaises(errors.OAuthInvalidGrant, self.r.get_access_information, "invalid_code") def test_invalid_app_access_token(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_access_information, "dummy_code") def test_invalid_app_authorize_url(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, "dummy_state") @betamax() def test_invalid_set_access_credentials(self): self.assertRaises( errors.OAuthInvalidToken, self.r.set_access_credentials, set(("identity",)), "dummy_access_token" ) def test_oauth_scope_required(self): self.r.set_oauth_app_info("dummy_client", "dummy_secret", "dummy_url") self.r.set_access_credentials(set("dummy_scope"), "dummy_token") self.assertRaises(errors.OAuthScopeRequired, self.r.get_me) @betamax() def test_scope_edit(self): self.r.refresh_access_information(self.refresh_token["edit"]) submission = Submission.from_id(self.r, self.submission_edit_id) self.assertEqual(submission, submission.edit("Edited text")) @betamax() def test_scope_history(self): self.r.refresh_access_information(self.refresh_token["history"]) self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted())) @betamax() def test_scope_identity(self): self.r.refresh_access_information(self.refresh_token["identity"]) self.assertEqual(self.un, self.r.get_me().name) @betamax() def test_scope_modconfig(self): self.r.refresh_access_information(self.refresh_token["modconfig"]) self.r.get_subreddit(self.sr).set_settings("foobar") retval = self.r.get_subreddit(self.sr).get_stylesheet() self.assertTrue("images" in retval) @betamax() def test_scope_modflair(self): self.r.refresh_access_information(self.refresh_token["modflair"]) self.r.get_subreddit(self.sr).set_flair(self.un, "foobar") @betamax() def test_scope_modlog(self): num = 50 self.r.refresh_access_information(self.refresh_token["modlog"]) result = self.r.get_subreddit(self.sr).get_mod_log(limit=num) self.assertEqual(num, len(list(result))) @betamax() def test_scope_modothers_modself(self): subreddit = self.r.get_subreddit(self.sr) self.r.refresh_access_information(self.refresh_token["modothers"]) subreddit.add_moderator(self.other_user_name) # log in as other user self.r.refresh_access_information(self.other_refresh_token["modself"]) self.r.accept_moderator_invite(self.sr) # now return to original user. self.r.refresh_access_information(self.refresh_token["modothers"]) subreddit.remove_moderator(self.other_user_name) @betamax() def test_scope_modposts(self): self.r.refresh_access_information(self.refresh_token["modposts"]) Submission.from_id(self.r, self.submission_edit_id).remove() @betamax() def test_scope_modself(self): subreddit = self.r.get_subreddit(self.sr) self.r.refresh_access_information(self.refresh_token["modothers"]) subreddit.add_moderator(self.other_user_name) self.r.refresh_access_information(self.refresh_token["modcontributors"]) subreddit.add_contributor(self.other_user_name) # log in as other user self.r.refresh_access_information(self.other_refresh_token["modself"]) self.r.accept_moderator_invite(self.sr) self.r.leave_moderator(subreddit) subreddit.leave_contributor() subreddit.refresh() self.assertFalse(subreddit.user_is_moderator) self.assertFalse(subreddit.user_is_contributor) @betamax() def test_scope_mysubreddits(self): self.r.refresh_access_information(self.refresh_token["mysubreddits"]) self.assertTrue(list(self.r.get_my_moderation())) @betamax() def test_scope_modwiki(self): self.r.refresh_access_information(self.refresh_token["modwiki"]) subreddit = self.r.get_subreddit(self.sr) page = subreddit.get_wiki_page("index") page.add_editor(self.other_user_name) page.remove_editor(self.other_user_name) @betamax() def test_scope_modwiki_modcontributors(self): self.r.refresh_access_information(self.refresh_token["modwiki+contr"]) subreddit = self.r.get_subreddit(self.sr) subreddit.add_wiki_ban(self.other_user_name) subreddit.remove_wiki_ban(self.other_user_name) subreddit.add_wiki_contributor(self.other_user_name) subreddit.remove_wiki_contributor(self.other_user_name) @betamax() def test_scope_creddits(self): # Assume there are insufficient creddits. self.r.refresh_access_information(self.refresh_token["creddits"]) redditor = self.r.get_redditor("bboe") sub = self.r.get_submission(url=self.comment_url) # Test error conditions self.assertRaises(TypeError, sub.gild, months=1) for value in (False, 0, -1, "0", "-1"): self.assertRaises(TypeError, redditor.gild, value) # Test object gilding self.assertRaises(errors.InsufficientCreddits, redditor.gild) self.assertRaises(errors.InsufficientCreddits, sub.gild) self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild) @betamax() def test_scope_privatemessages(self): self.r.refresh_access_information(self.refresh_token["privatemessages"]) self.assertTrue(list(self.r.get_inbox())) @betamax() def test_scope_read(self): self.r.refresh_access_information(self.refresh_token["read"]) self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0) fullname = "{0}_{1}".format(self.r.config.by_object[Submission], self.priv_submission_id) method1 = self.r.get_info(thing_id=fullname) method2 = self.r.get_submission(submission_id=self.priv_submission_id) self.assertEqual(method1, method2) @betamax() def test_scope_read_get_front_page(self): self.r.refresh_access_information(self.refresh_token["mysubreddits"]) subscribed = list(self.r.get_my_subreddits(limit=None)) self.r.refresh_access_information(self.refresh_token["read"]) for post in self.r.get_front_page(): self.assertTrue(post.subreddit in subscribed) @betamax() def test_scope_read_get_sub_listingr(self): self.r.refresh_access_information(self.refresh_token["read"]) subreddit = self.r.get_subreddit(self.priv_sr) self.assertTrue(list(subreddit.get_top())) @betamax() def test_scope_read_get_submission_by_url(self): url = "https://www.reddit.com/r/reddit_api_test_priv/comments/16kbb7/" "google/" self.r.refresh_access_information(self.refresh_token["read"]) submission = Submission.from_url(self.r, url) self.assertTrue(submission.num_comments != 0) @betamax() def test_scope_read_priv_sr_comments(self): self.r.refresh_access_information(self.refresh_token["read"]) self.assertTrue(list(self.r.get_comments(self.priv_sr))) @betamax() def test_scope_wikiread_wiki_page(self): self.r.refresh_access_information(self.refresh_token["wikiread"]) self.assertTrue(self.r.get_wiki_page(self.sr, "index")) @betamax() def test_scope_read_priv_sub_comments(self): self.r.refresh_access_information(self.refresh_token["read"]) submission = Submission.from_id(self.r, self.priv_submission_id) self.assertTrue(submission.comments) @betamax() def test_scope_submit(self): self.r.refresh_access_information(self.refresh_token["submit"]) result = self.r.submit(self.sr, "OAuth Submit", text="Foo") self.assertTrue(isinstance(result, Submission)) @betamax() def test_scope_subscribe(self): self.r.refresh_access_information(self.refresh_token["subscribe"]) self.r.get_subreddit(self.sr).subscribe() @betamax() def test_scope_vote(self): self.r.refresh_access_information(self.refresh_token["vote"]) submission = Submission.from_id(self.r, self.submission_edit_id) submission.clear_vote() @betamax() def test_set_access_credentials(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information(self.refresh_token["identity"], update_session=False) self.assertTrue(self.r.user is None) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax() def test_oauth_without_identy_doesnt_set_user(self): self.assertTrue(self.r.user is None) self.r.refresh_access_information(self.refresh_token["edit"]) self.assertTrue(self.r.user is None)
def subreddit(self, value): reddit = Reddit('gtmanfred/1.0', cache_timeout=self.interval) self['subreddit'] = reddit.get_subreddit(value)
def get_post(self): """ Get a post """ r = Reddit(self.args.name) r.login(self.args.username, self.args.password) return r.get_subreddit(self.args.subreddit).get_new(limit=1).next()
class OAuth2RedditTest(PRAWTest): def setUp(self): self.configure() self.r = Reddit(USER_AGENT, site_name='reddit_oauth_test', disable_update_check=True) def test_authorize_url(self): self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') self.r.set_oauth_app_info(self.r.config.client_id, self.r.config.client_secret, self.r.config.redirect_uri) url, params = self.r.get_authorize_url('...').split('?', 1) self.assertTrue('api/v1/authorize/' in url) params = dict(x.split('=', 1) for x in params.split('&')) expected = {'client_id': self.r.config.client_id, 'duration': 'temporary', 'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F' 'authorize_callback'), 'response_type': 'code', 'scope': 'identity', 'state': '...'} self.assertEqual(expected, params) # @betamax() is currently broken for this test because the cassettes # are caching too aggressively and not performing a token refresh. def test_auto_refresh_token(self): self.r.refresh_access_information(self.refresh_token['identity']) old_token = self.r.access_token self.r.access_token += 'x' # break the token self.r.user.refresh() current_token = self.r.access_token self.assertNotEqual(old_token, current_token) self.r.user.refresh() self.assertEqual(current_token, self.r.access_token) @betamax() def test_get_access_information(self): # If this test fails, the following URL will need to be visted in order # to obtain a new code to pass to `get_access_information`: # self.r.get_authorize_url('...') token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ') expected = {'access_token': self.r.access_token, 'refresh_token': None, 'scope': set(('identity',))} self.assertEqual(expected, token) self.assertEqual('PyAPITestUser2', text_type(self.r.user)) @betamax() def test_get_access_information_with_invalid_code(self): self.assertRaises(errors.OAuthInvalidGrant, self.r.get_access_information, 'invalid_code') def test_invalid_app_access_token(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_access_information, 'dummy_code') def test_invalid_app_authorize_url(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') @betamax() def test_invalid_set_access_credentials(self): self.assertRaises(errors.OAuthInvalidToken, self.r.set_access_credentials, set(('identity',)), 'dummy_access_token') def test_oauth_scope_required(self): self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url') self.r.set_access_credentials(set('dummy_scope',), 'dummy_token') self.assertRaises(errors.OAuthScopeRequired, self.r.get_me) def test_raise_client_exception(self): def raise_client_exception(*args): raise errors.ClientException(*args) self.assertRaises(errors.ClientException, raise_client_exception) self.assertRaises(errors.ClientException, raise_client_exception, 'test') ce_message = errors.ClientException('Test') ce_no_message = errors.ClientException() self.assertEqual(ce_message.message, str(ce_message)) self.assertEqual(ce_no_message.message, str(ce_no_message)) def test_raise_http_exception(self): def raise_http_exception(): raise errors.HTTPException('fakeraw') self.assertRaises(errors.HTTPException, raise_http_exception) http_exception = errors.HTTPException('fakeraw') self.assertEqual(http_exception.message, str(http_exception)) def test_raise_oauth_exception(self): oerrormessage = "fakemessage" oerrorurl = "http://oauth.reddit.com/" def raise_oauth_exception(): raise errors.OAuthException(oerrormessage, oerrorurl) self.assertRaises(errors.OAuthException, raise_oauth_exception) oauth_exception = errors.OAuthException(oerrormessage, oerrorurl) self.assertEqual(oauth_exception.message + " on url {0}".format(oauth_exception.url), str(oauth_exception)) def test_raise_redirect_exception(self): apiurl = "http://api.reddit.com/" oauthurl = "http://oauth.reddit.com/" def raise_redirect_exception(): raise errors.RedirectException(apiurl, oauthurl) self.assertRaises(errors.RedirectException, raise_redirect_exception) redirect_exception = errors.RedirectException(apiurl, oauthurl) self.assertEqual(redirect_exception.message, str(redirect_exception)) @betamax() def test_scope_history(self): self.r.refresh_access_information(self.refresh_token['history']) self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted())) @betamax() def test_scope_identity(self): self.r.refresh_access_information(self.refresh_token['identity']) self.assertEqual(self.un, self.r.get_me().name) @betamax() def test_scope_mysubreddits(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) self.assertTrue(list(self.r.get_my_moderation())) @betamax() def test_scope_creddits(self): # Assume there are insufficient creddits. self.r.refresh_access_information( self.refresh_token['creddits']) redditor = self.r.get_redditor('bboe') sub = self.r.get_submission(url=self.comment_url) # Test error conditions self.assertRaises(TypeError, sub.gild, months=1) for value in (False, 0, -1, '0', '-1'): self.assertRaises(TypeError, redditor.gild, value) # Test object gilding self.assertRaises(errors.InsufficientCreddits, redditor.gild) self.assertRaises(errors.InsufficientCreddits, sub.gild) self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild) @betamax() def test_scope_privatemessages(self): self.r.refresh_access_information( self.refresh_token['privatemessages']) self.assertTrue(list(self.r.get_inbox())) @betamax() def test_scope_read(self): self.r.refresh_access_information(self.refresh_token['read']) self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0) fullname = '{0}_{1}'.format(self.r.config.by_object[Submission], self.priv_submission_id) method1 = self.r.get_info(thing_id=fullname) method2 = self.r.get_submission(submission_id=self.priv_submission_id) self.assertEqual(method1, method2) @betamax() def test_scope_read_get_front_page(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) subscribed = list(self.r.get_my_subreddits(limit=None)) self.r.refresh_access_information(self.refresh_token['read']) for post in self.r.get_front_page(): self.assertTrue(post.subreddit in subscribed) @betamax() def test_set_access_credentials(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information( self.refresh_token['identity'], update_session=False) self.assertTrue(self.r.user is None) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax() def test_solve_captcha(self): # Use the alternate account because it has low karma, # so we can test the captcha. self.r.refresh_access_information(self.other_refresh_token['submit']) original_stdin = sys.stdin sys.stdin = FakeStdin('ljgtoo') # Comment this line when rebuilding self.r.submit(self.sr, 'captcha test', 'body') sys.stdin = original_stdin @betamax() def test_oauth_without_identy_doesnt_set_user(self): self.assertTrue(self.r.user is None) self.r.refresh_access_information(self.refresh_token['edit']) self.assertTrue(self.r.user is None)
class ModUtils(object): """Class that provides all the modutils functionality.""" def __init__(self, subreddit, site=None, user=None, pswd=None, verbose=None): """Initialize the ModUtils class by passing in config options.""" self.reddit = Reddit(str(self), site, disable_update_check=True) self.reddit.config.decode_html_entities = True self._logged_in = False self._user = user self._pswd = pswd self.sub = self.reddit.get_subreddit(subreddit) self.verbose = verbose self._current_flair = None def add_users(self, category): """Add users to 'banned', 'contributors', or 'moderators'.""" mapping = { 'banned': 'ban', 'contributors': 'make_contributor', 'moderators': 'make_moderator' } if category not in mapping: print('%r is not a valid option for --add' % category) return self.login() func = getattr(self.sub, mapping[category]) print('Enter user names (any separation should suffice):') data = sys.stdin.read().strip() for name in re.split('[^A-Za-z0-9_]+', data): func(name) print('Added %r to %s' % (name, category)) def clear_empty(self): """Remove flair that is not visible or has been set to empty.""" for flair in self.current_flair(): if not flair['flair_text'] and not flair['flair_css_class']: print(self.reddit.delete_flair(self.sub, flair['user'])) print('Removed flair for {0}'.format(flair['user'])) def current_flair(self): """Generate the flair, by user, for the subreddit.""" if self._current_flair is None: self._current_flair = [] self.login() if self.verbose: print('Fetching flair list for %s' % self.sub) for flair in self.sub.get_flair_list(limit=None): self._current_flair.append(flair) yield flair else: for item in self._current_flair: yield item def flair_template_sync( self, editable, limit, # pylint: disable=R0912 static, sort, use_css, use_text): """Synchronize templates with flair that already exists on the site. :param editable: Indicates that all the options should be editable. :param limit: The minimum number of users that must share the flair before it is added as a template. :param static: A list of flair templates that will always be added. :param sort: The order to sort the flair templates. :param use_css: Include css in the templates. :param use_text: Include text in the templates. """ # Parameter verification if not use_text and not use_css: raise Exception('At least one of use_text or use_css must be True') sorts = ('alpha', 'size') if sort not in sorts: raise Exception('Sort must be one of: %s' % ', '.join(sorts)) # Build current flair list along with static values counter = {} if static: for key in static: if use_css and use_text: parts = tuple(x.strip() for x in key.split(',')) if len(parts) != 2: raise Exception('--static argument %r must have two ' 'parts (comma separated) when using ' 'both text and css.' % parts) key = parts counter[key] = limit self.login() if self.verbose: sys.stdout.write('Retrieving current flair\n') sys.stdout.flush() for flair in self.current_flair(): if self.verbose: sys.stdout.write('.') sys.stdout.flush() if use_text and use_css: key = (flair['flair_text'], flair['flair_css_class']) elif use_text: key = flair['flair_text'] else: key = flair['flair_css_class'] if key in counter: counter[key] += 1 else: counter[key] = 1 if self.verbose: print() # Sort flair list items according to the specified sort if sort == 'alpha': items = sorted(counter.items()) else: items = sorted(counter.items(), key=lambda x: x[1], reverse=True) # Clear current templates and store flair according to the sort if self.verbose: print('Clearing current flair templates') self.sub.clear_flair_templates() for key, count in items: if not key or count < limit: continue if use_text and use_css: text, css = key elif use_text: text, css = key, '' else: text, css = '', key if self.verbose: print('Adding template: text: "%s" css: "%s"' % (text, css)) self.sub.add_flair_template(text, css, editable) def login(self): """Login and provide debugging output if so wanted.""" if not self._logged_in: if self.verbose: print('Logging in') self.reddit.login(self._user, self._pswd) self._logged_in = True def message(self, category, subject, msg_file): """Send message to all users in `category`.""" self.login() users = getattr(self.sub, 'get_%s' % category)() if not users: print('There are no %s on %s.' % (category, str(self.sub))) return if msg_file: try: msg = open(msg_file).read() except IOError as error: print(str(error)) return else: print('Enter message:') msg = sys.stdin.read() print('You are about to send the following ' 'message to the users %s:' % ', '.join([str(x) for x in users])) print('---BEGIN MESSAGE---\n%s\n---END MESSAGE---' % msg) if raw_input('Are you sure? yes/[no]: ').lower() not in ['y', 'yes']: print('Message sending aborted.') return for user in users: user.send_message(subject, msg) print('Sent to: %s' % str(user)) def output_current_flair(self, as_json=False): """Display the current flair for all users in the subreddit.""" flair_list = sorted(self.current_flair(), key=lambda x: x['user']) if as_json: print(json.dumps(flair_list, sort_keys=True, indent=4)) return for flair in flair_list: print(flair['user']) print(' Text: %s\n CSS: %s' % (flair['flair_text'], flair['flair_css_class'])) def output_flair_stats(self): """Display statistics (number of users) for each unique flair item.""" css_counter = Counter() text_counter = Counter() for flair in self.current_flair(): if flair['flair_css_class']: css_counter[flair['flair_css_class']] += 1 if flair['flair_text']: text_counter[flair['flair_text']] += 1 print('Flair CSS Statistics') for flair, count in sorted(css_counter.items(), key=lambda x: (x[1], x[0])): print('{0:3} {1}'.format(count, flair)) print('Flair Text Statistics') for flair, count in sorted(text_counter.items(), key=lambda x: (x[1], x[0]), reverse=True): print('{0:3} {1}'.format(count, flair)) def output_list(self, category): """Display the list of users in `category`.""" self.login() print('%s users:' % category) for user in getattr(self.sub, 'get_%s' % category)(): print(' %s' % user)
tag_check(post, ptitle) flair_assign(post, purl, ptitle, flair) approve_host(post, purl, ptitle) if details_scan(post, pauthor, ptime) is False: pass else: with open("oldposts", "a+") as file: file.write(pid + "\n") """RUNNING BOT""" print("Running on /r/" + SUBREDDIT) while True: print("\nRunning at " + str(datetime.now(timezone.utc))) subreddit = r.get_subreddit(SUBREDDIT) posts = subreddit.get_new(limit=MAXPOSTS) try: with open("oldposts", "r") as file: oldposts = [line.strip() for line in file] except: oldposts = [] try: for post in posts: if post.id in oldposts: pass else: actions(post) except Exception as e: print("An error has occurred\n", e) for var in range(WAIT, 0, -1):
tag_check(post, ptitle) flair_assign(post, purl, ptitle, flair) approve_host(post, purl, ptitle) if details_scan(post, pauthor, ptime) is False: pass else: with open("oldposts", "a+") as file: file.write(pid + "\n") """RUNNING BOT""" print("Running on /r/" + SUBREDDIT) while True: print("\nRunning at " + str(datetime.now(timezone.utc))) subreddit = r.get_subreddit(SUBREDDIT) posts = subreddit.get_new(limit=MAXPOSTS) try: with open("oldposts", "r") as file: oldposts = [line.strip() for line in file] except: oldposts = [] try: for post in posts: if post.id in oldposts: pass else: actions(post) except Exception as e: print("An error has occurred\n", e) for var in range(WAIT, 0, -1):
class RedditBot(_RedditBotBase): """ Basic extendable Reddit bot. Provides means to loop over a list of whitelisted subreddits. """ VERSION = (0, 0, 0) # override this USER_AGENT = '{name} v{version} (by /u/{admin})' # if loop() returns this the bot will refresh its settings BOT_SHOULD_REFRESH = 'BOT_SHOULD_REFRESH' def __init__(self, config): """ Initialize the bot with a dict of configuration values. """ self._setup(config) self._login(config) self.subreddits = self._get_subreddits() self.blocked_users = self._get_blocked_users() def _setup(self, config): try: admins = config['admins'] if isinstance(admins, list): self.admins = admins else: self.admins = list(map(str.strip, admins.split(','))) self.settings = DEFAULT_SETTINGS.copy() self.settings.update(config.get('settings', {})) except KeyError as e: import sys sys.stderr.write('error: missing {} in configuration'.format(e)) sys.exit(2) def _login(self, config): logger.info('Attempting to login using OAuth2') for attr in ['client_id', 'client_secret', 'redirect_uri']: assert attr in config['oauth_info'], 'Missing `{}` in oauth_info'.format(attr) self.r = Reddit('OAuth Login v1.0') self.r.set_oauth_app_info(**config['oauth_info']) for attr in ['access_token', 'refresh_token']: assert attr in config['access_info'], 'Missing `{}` in access_info'.format(attr) access_info = config['access_info'] access_info['scope'] = self.__class__.get_scope() self.r.set_access_credentials(**access_info) self.bot_name = self.r.user.name self.admins.append(self.bot_name) user_agent = self.USER_AGENT.format( name=self.bot_name, admin=self.admins[0], version='.'.join(map(str, self.VERSION)) ) logger.debug('User-Agent: {!r}'.format(user_agent)) self.r.http.headers['User-Agent'] = user_agent logger.info('Logged in as {}'.format(self.bot_name)) @classmethod def get_scope(cls): """Basic permission scope for RedditReplyBot operations.""" return super(RedditBot, cls).get_scope() | { 'identity', 'subscribe', 'mysubreddits', } def run_forever(self): self.bot_start() try: while True: self.do_loop() self.refresh() except Exception as e: self.bot_error(e) raise finally: self.bot_stop() def refresh(self): logger.info('Refreshing settings') self.subreddits = self._get_subreddits() self.blocked_users = self._get_blocked_users() def do_loop(self): for subreddit in cycle(self.subreddits): try: if self.loop(subreddit) == self.BOT_SHOULD_REFRESH: break except Forbidden as e: logger.error('Forbidden in {}! Removing from whitelist.'.format(subreddit)) self.remove_subreddits(subreddit) break except RateLimitExceeded as e: logger.warning('RateLimitExceeded! Sleeping {} seconds.'.format(e.sleep_time)) time.sleep(e.sleep_time) except (ConnectionError, HTTPException) as e: logger.warning('Error: Reddit down or no connection? {!r}'.format(e)) time.sleep(self.settings['loop_sleep'] * 10) else: time.sleep(self.settings['loop_sleep']) else: logger.error("No subreddits in file. Will read file again in 5 seconds.") time.sleep(5) def _get_subreddits(self): subreddits = list(map(lambda s: s.display_name, self.r.get_my_subreddits())) logger.info('Subreddits: {} entries'.format(len(subreddits))) logger.debug('List: {!r}'.format(subreddits)) return subreddits def _get_blocked_users(self, filename=None): """Friends are blocked users, because Reddit only allows blocking users by private messages.""" blocked_users = list(map(lambda u: u.name, self.r.get_friends())) logger.info('Blocked users: {} entries'.format(len(blocked_users))) logger.debug('List: {!r}'.format(blocked_users)) return blocked_users def is_user_blocked(self, user_name): if user_name == self.bot_name: return True return user_name in self.blocked_users def is_subreddit_whitelisted(self, subreddit): return subreddit in self.subreddits def remove_subreddits(self, *subreddits): for sub_name in subreddits: if sub_name in self.subreddits: self.subreddits.remove(sub_name) sub = self.r.get_subreddit(sub_name) sub.unsubscribe() logger.info('Unsubscribed from /r/{}'.format(sub_name)) def add_subreddits(self, *subreddits): for sub_name in subreddits: if sub_name not in self.subreddits: self.subreddits.add(sub_name) sub = self.r.get_subreddit(sub_name) sub.subscribe() logger.info('Subscribed to /r/{}'.format(sub_name)) def block_users(self, *users): for user_name in users: if user_name not in self.blocked_users: self.blocked_users.add(user_name) user = self.r.get_redditor(user_name) user.friend() logger.info('Blocked /u/{}'.format(user_name)) def unblock_users(self, *users): for user_name in users: if user_name in self.blocked_users: self.blocked_users.remove(user_name) user = self.r.get_redditor(user_name) user.unfriend() logger.info('Unblocked /u/{}'.format(user_name))
class OAuth2RedditTest(PRAWTest): def setUp(self): self.configure() self.r = Reddit(USER_AGENT, site_name='reddit_oauth_test', disable_update_check=True) def test_authorize_url(self): self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') self.r.set_oauth_app_info(self.r.config.client_id, self.r.config.client_secret, self.r.config.redirect_uri) url, params = self.r.get_authorize_url('...').split('?', 1) self.assertTrue('api/v1/authorize/' in url) params = dict(x.split('=', 1) for x in params.split('&')) expected = { 'client_id': self.r.config.client_id, 'duration': 'temporary', 'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F' 'authorize_callback'), 'response_type': 'code', 'scope': 'identity', 'state': '...' } self.assertEqual(expected, params) # @betamax() is currently broken for this test because the cassettes # are caching too aggressively and not performing a token refresh. def test_auto_refresh_token(self): self.r.refresh_access_information(self.refresh_token['identity']) old_token = self.r.access_token self.r.access_token += 'x' # break the token self.r.user.refresh() current_token = self.r.access_token self.assertNotEqual(old_token, current_token) self.r.user.refresh() self.assertEqual(current_token, self.r.access_token) @betamax() def test_get_access_information(self): # If this test fails, the following URL will need to be visted in order # to obtain a new code to pass to `get_access_information`: # self.r.get_authorize_url('...') token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ') expected = { 'access_token': self.r.access_token, 'refresh_token': None, 'scope': set(('identity', )) } self.assertEqual(expected, token) self.assertEqual('PyAPITestUser2', text_type(self.r.user)) @betamax() def test_get_access_information_with_invalid_code(self): self.assertRaises(errors.OAuthInvalidGrant, self.r.get_access_information, 'invalid_code') def test_invalid_app_access_token(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_access_information, 'dummy_code') def test_invalid_app_authorize_url(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') @betamax() def test_invalid_set_access_credentials(self): self.assertRaises(errors.OAuthInvalidToken, self.r.set_access_credentials, set( ('identity', )), 'dummy_access_token') def test_oauth_scope_required(self): self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url') self.r.set_access_credentials(set('dummy_scope', ), 'dummy_token') self.assertRaises(errors.OAuthScopeRequired, self.r.get_me) def test_raise_client_exception(self): def raise_client_exception(*args): raise errors.ClientException(*args) self.assertRaises(errors.ClientException, raise_client_exception) self.assertRaises(errors.ClientException, raise_client_exception, 'test') ce_message = errors.ClientException('Test') ce_no_message = errors.ClientException() self.assertEqual(ce_message.message, str(ce_message)) self.assertEqual(ce_no_message.message, str(ce_no_message)) def test_raise_http_exception(self): def raise_http_exception(): raise errors.HTTPException('fakeraw') self.assertRaises(errors.HTTPException, raise_http_exception) http_exception = errors.HTTPException('fakeraw') self.assertEqual(http_exception.message, str(http_exception)) def test_raise_oauth_exception(self): oerrormessage = "fakemessage" oerrorurl = "http://oauth.reddit.com/" def raise_oauth_exception(): raise errors.OAuthException(oerrormessage, oerrorurl) self.assertRaises(errors.OAuthException, raise_oauth_exception) oauth_exception = errors.OAuthException(oerrormessage, oerrorurl) self.assertEqual( oauth_exception.message + " on url {0}".format(oauth_exception.url), str(oauth_exception)) def test_raise_redirect_exception(self): apiurl = "http://api.reddit.com/" oauthurl = "http://oauth.reddit.com/" def raise_redirect_exception(): raise errors.RedirectException(apiurl, oauthurl) self.assertRaises(errors.RedirectException, raise_redirect_exception) redirect_exception = errors.RedirectException(apiurl, oauthurl) self.assertEqual(redirect_exception.message, str(redirect_exception)) @betamax() def test_scope_history(self): self.r.refresh_access_information(self.refresh_token['history']) self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted())) @betamax() def test_scope_identity(self): self.r.refresh_access_information(self.refresh_token['identity']) self.assertEqual(self.un, self.r.get_me().name) @betamax() def test_scope_mysubreddits(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) self.assertTrue(list(self.r.get_my_moderation())) @betamax() def test_scope_creddits(self): # Assume there are insufficient creddits. self.r.refresh_access_information(self.refresh_token['creddits']) redditor = self.r.get_redditor('bboe') sub = self.r.get_submission(url=self.comment_url) # Test error conditions self.assertRaises(TypeError, sub.gild, months=1) for value in (False, 0, -1, '0', '-1'): self.assertRaises(TypeError, redditor.gild, value) # Test object gilding self.assertRaises(errors.InsufficientCreddits, redditor.gild) self.assertRaises(errors.InsufficientCreddits, sub.gild) self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild) @betamax() def test_scope_privatemessages(self): self.r.refresh_access_information( self.refresh_token['privatemessages']) self.assertTrue(list(self.r.get_inbox())) @betamax() def test_scope_read(self): self.r.refresh_access_information(self.refresh_token['read']) self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0) fullname = '{0}_{1}'.format(self.r.config.by_object[Submission], self.priv_submission_id) method1 = self.r.get_info(thing_id=fullname) method2 = self.r.get_submission(submission_id=self.priv_submission_id) self.assertEqual(method1, method2) @betamax() def test_scope_read_get_front_page(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) subscribed = list(self.r.get_my_subreddits(limit=None)) self.r.refresh_access_information(self.refresh_token['read']) for post in self.r.get_front_page(): self.assertTrue(post.subreddit in subscribed) @betamax() def test_set_access_credentials(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information( self.refresh_token['identity'], update_session=False) self.assertTrue(self.r.user is None) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax() def test_solve_captcha(self): # Use the alternate account because it has low karma, # so we can test the captcha. self.r.refresh_access_information(self.other_refresh_token['submit']) original_stdin = sys.stdin sys.stdin = FakeStdin('ljgtoo') # Comment this line when rebuilding self.r.submit(self.sr, 'captcha test', 'body') sys.stdin = original_stdin @betamax() def test_oauth_without_identy_doesnt_set_user(self): self.assertTrue(self.r.user is None) self.r.refresh_access_information(self.refresh_token['edit']) self.assertTrue(self.r.user is None)
class ModUtils(object): @staticmethod def remove_entities(item): if not item: return item return item.replace("&", "&").replace("<", "<").replace(">", ">") def __init__(self, subreddit, site=None, user=None, pswd=None, verbose=None): self.reddit = Reddit(str(self), site) self._logged_in = False self._user = user self._pswd = pswd self.sub = self.reddit.get_subreddit(subreddit) self.verbose = verbose self._current_flair = None def add_users(self, category): mapping = {"banned": "ban", "contributors": "make_contributor", "moderators": "make_moderator"} if category not in mapping: print "%r is not a valid option for --add" % category return self.login() func = getattr(self.sub, mapping[category]) print "Enter user names (any separation should suffice):" data = sys.stdin.read().strip() for name in re.split("[^A-Za-z0-9_]+", data): func(name) print "Added %r to %s" % (name, category) def current_flair(self): if self._current_flair is None: self._current_flair = [] self.login() if self.verbose: print "Fetching flair list for %s" % self.sub for flair in self.sub.flair_list(): for item in ("flair_text", "flair_css_class"): flair[item] = self.remove_entities(flair[item]) self._current_flair.append(flair) yield flair else: for item in self._current_flair: yield item def flair_template_sync(self, editable, limit, static, sort, use_css, use_text): # pylint: disable-msg=R0912 # Parameter verification if not use_text and not use_css: raise Exception("At least one of use_text or use_css must be True") sorts = ("alpha", "size") if sort not in sorts: raise Exception("Sort must be one of: %s" % ", ".join(sorts)) # Build current flair list along with static values counter = {} if static: for key in static: if use_css and use_text: parts = tuple(x.strip() for x in key.split(",")) if len(parts) != 2: raise Exception( "--static argument %r must have two " "parts (comma separated) when using " "both text and css." % parts ) key = parts counter[key] = limit self.login() if self.verbose: sys.stdout.write("Retrieving current flair\n") sys.stdout.flush() for flair in self.current_flair(): if self.verbose: sys.stdout.write(".") sys.stdout.flush() if use_text and use_css: key = (flair["flair_text"], flair["flair_css_class"]) elif use_text: key = flair["flair_text"] else: key = flair["flair_css_class"] if key in counter: counter[key] += 1 else: counter[key] = 1 if self.verbose: print # Sort flair list items according to the specified sort if sort == "alpha": items = sorted(counter.items()) else: items = sorted(counter.items(), key=lambda x: x[1], reverse=True) # Clear current templates and store flair according to the sort if self.verbose: print "Clearing current flair templates" self.sub.clear_flair_templates() for key, count in items: if not key or count < limit: continue if use_text and use_css: text, css = key elif use_text: text, css = key, "" else: text, css = "", key if self.verbose: print 'Adding template: text: "%s" css: "%s"' % (text, css) self.sub.add_flair_template(text, css, editable) def login(self): if not self._logged_in: if self.verbose: print "Logging in" self.reddit.login(self._user, self._pswd) self.logged_in = True def message(self, category, subject, msg_file): self.login() users = getattr(self.sub, "get_%s" % category)() if not users: print "There are no %s on %s." % (category, str(self.sub)) return if msg_file: try: msg = open(msg_file).read() except IOError, error: print str(error) return else:
def add_callback(): tmp = Reddit(USER_AGENT, disable_update_check=True) tmp.login(self.other_user_name, self.other_user_pswd, disable_warning=True) tmp.get_subreddit(self.sr).accept_moderator_invite()
class ModUtils(object): """Class that provides all the modutils functionality.""" def __init__(self, subreddit, site=None, user=None, pswd=None, verbose=None): self.reddit = Reddit(str(self), site, disable_update_check=True) self.reddit.config.decode_html_entities = True self._logged_in = False self._user = user self._pswd = pswd self.sub = self.reddit.get_subreddit(subreddit) self.verbose = verbose self._current_flair = None def add_users(self, category): """Add users to 'banned', 'contributors', or 'moderators'.""" mapping = {"banned": "ban", "contributors": "make_contributor", "moderators": "make_moderator"} if category not in mapping: print("%r is not a valid option for --add" % category) return self.login() func = getattr(self.sub, mapping[category]) print("Enter user names (any separation should suffice):") data = sys.stdin.read().strip() for name in re.split("[^A-Za-z0-9_]+", data): func(name) print("Added %r to %s" % (name, category)) def clear_empty(self): """Remove flair that is not visible or has been set to empty.""" for flair in self.current_flair(): if not flair["flair_text"] and not flair["flair_css_class"]: print(self.reddit.delete_flair(self.sub, flair["user"])) print("Removed flair for {0}".format(flair["user"])) def current_flair(self): """Generate the flair, by user, for the subreddit.""" if self._current_flair is None: self._current_flair = [] self.login() if self.verbose: print("Fetching flair list for %s" % self.sub) for flair in self.sub.get_flair_list(limit=None): self._current_flair.append(flair) yield flair else: for item in self._current_flair: yield item def flair_template_sync(self, editable, limit, static, sort, use_css, use_text): # pylint: disable-msg=R0912 """Synchronize templates with flair that already exists on the site. :param editable: Indicates that all the options should be editable. :param limit: The minimum number of users that must share the flair before it is added as a template. :param static: A list of flair templates that will always be added. :param sort: The order to sort the flair templates. :param use_css: Include css in the templates. :param use_text: Include text in the templates. """ # Parameter verification if not use_text and not use_css: raise Exception("At least one of use_text or use_css must be True") sorts = ("alpha", "size") if sort not in sorts: raise Exception("Sort must be one of: %s" % ", ".join(sorts)) # Build current flair list along with static values counter = {} if static: for key in static: if use_css and use_text: parts = tuple(x.strip() for x in key.split(",")) if len(parts) != 2: raise Exception( "--static argument %r must have two " "parts (comma separated) when using " "both text and css." % parts ) key = parts counter[key] = limit self.login() if self.verbose: sys.stdout.write("Retrieving current flair\n") sys.stdout.flush() for flair in self.current_flair(): if self.verbose: sys.stdout.write(".") sys.stdout.flush() if use_text and use_css: key = (flair["flair_text"], flair["flair_css_class"]) elif use_text: key = flair["flair_text"] else: key = flair["flair_css_class"] if key in counter: counter[key] += 1 else: counter[key] = 1 if self.verbose: print() # Sort flair list items according to the specified sort if sort == "alpha": items = sorted(counter.items()) else: items = sorted(counter.items(), key=lambda x: x[1], reverse=True) # Clear current templates and store flair according to the sort if self.verbose: print("Clearing current flair templates") self.sub.clear_flair_templates() for key, count in items: if not key or count < limit: continue if use_text and use_css: text, css = key elif use_text: text, css = key, "" else: text, css = "", key if self.verbose: print('Adding template: text: "%s" css: "%s"' % (text, css)) self.sub.add_flair_template(text, css, editable) def login(self): """Login and provide debugging output if so wanted.""" if not self._logged_in: if self.verbose: print("Logging in") self.reddit.login(self._user, self._pswd) self._logged_in = True def message(self, category, subject, msg_file): """Send message to all users in `category`.""" self.login() users = getattr(self.sub, "get_%s" % category)() if not users: print("There are no %s on %s." % (category, str(self.sub))) return if msg_file: try: msg = open(msg_file).read() except IOError as error: print(str(error)) return else: print("Enter message:") msg = sys.stdin.read() print("You are about to send the following " "message to the users %s:" % ", ".join([str(x) for x in users])) print("---BEGIN MESSAGE---\n%s\n---END MESSAGE---" % msg) if raw_input("Are you sure? yes/[no]: ").lower() not in ["y", "yes"]: print("Message sending aborted.") return for user in users: user.send_message(subject, msg) print("Sent to: %s" % str(user)) def output_current_flair(self, as_json=False): """Display the current flair for all users in the subreddit.""" flair_list = sorted(self.current_flair(), key=lambda x: x["user"]) if as_json: print(json.dumps(flair_list, sort_keys=True, indent=4)) return for flair in flair_list: print(flair["user"]) print(" Text: %s\n CSS: %s" % (flair["flair_text"], flair["flair_css_class"])) def output_flair_stats(self): """Display statistics (number of users) for each unique flair item.""" css_counter = Counter() text_counter = Counter() for flair in self.current_flair(): if flair["flair_css_class"]: css_counter[flair["flair_css_class"]] += 1 if flair["flair_text"]: text_counter[flair["flair_text"]] += 1 print("Flair CSS Statistics") for flair, count in sorted(css_counter.items(), key=lambda x: (x[1], x[0])): print("{0:3} {1}".format(count, flair)) print("Flair Text Statistics") for flair, count in sorted(text_counter.items(), key=lambda x: (x[1], x[0]), reverse=True): print("{0:3} {1}".format(count, flair)) def output_list(self, category): """Display the list of users in `category`.""" self.login() print("%s users:" % category) for user in getattr(self.sub, "get_%s" % category)(): print(" %s" % user)
class OAuth2RedditTest(PRAWTest): def setUp(self): self.configure() site_name = (os.getenv('REDDIT_SITE') or 'reddit') + '_oauth_test' self.r = Reddit(USER_AGENT, site_name=site_name, disable_update_check=True) def test_authorize_url(self): self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') self.r.set_oauth_app_info(self.r.config.client_id, self.r.config.client_secret, self.r.config.redirect_uri) url, params = self.r.get_authorize_url('...').split('?', 1) self.assertTrue('api/v1/authorize/' in url) params = dict(x.split('=', 1) for x in params.split('&')) expected = { 'client_id': self.r.config.client_id, 'duration': 'temporary', 'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F' 'authorize_callback'), 'response_type': 'code', 'scope': 'identity', 'state': '...' } self.assertEqual(expected, params) @betamax def test_get_access_information(self): # If this test fails, the following URL will need to be visted in order # to obtain a new code to pass to `get_access_information`: # self.r.get_authorize_url('...') token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ') expected = { 'access_token': self.r.access_token, 'refresh_token': None, 'scope': set(('identity', )) } self.assertEqual(expected, token) self.assertEqual('PyAPITestUser2', text_type(self.r.user)) @betamax def test_get_access_information_with_invalid_code(self): self.assertRaises(errors.OAuthInvalidGrant, self.r.get_access_information, 'invalid_code') def test_invalid_app_access_token(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_access_information, 'dummy_code') def test_invalid_app_authorize_url(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') @betamax def test_invalid_set_access_credentials(self): self.assertRaises(errors.OAuthInvalidToken, self.r.set_access_credentials, set( ('identity', )), 'dummy_access_token') def test_oauth_scope_required(self): self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url') self.r.set_access_credentials(set('dummy_scope', ), 'dummy_token') self.assertRaises(errors.OAuthScopeRequired, self.r.get_me) @betamax def test_scope_edit(self): self.r.refresh_access_information(self.refresh_token['edit']) submission = Submission.from_id(self.r, self.submission_edit_id) self.assertEqual(submission, submission.edit('Edited text')) @betamax def test_scope_history(self): self.r.refresh_access_information(self.refresh_token['history']) self.assertTrue(list(self.r.get_redditor(self.un).get_liked())) @betamax def test_scope_identity(self): self.r.refresh_access_information(self.refresh_token['identity']) self.assertEqual(self.un, self.r.get_me().name) @betamax def test_scope_modconfig(self): self.r.refresh_access_information(self.refresh_token['modconfig']) self.r.get_subreddit(self.sr).set_settings('foobar') retval = self.r.get_subreddit(self.sr).get_stylesheet() self.assertTrue('images' in retval) @betamax def test_scope_modflair(self): self.r.refresh_access_information(self.refresh_token['modflair']) self.r.get_subreddit(self.sr).set_flair(self.un, 'foobar') @betamax def test_scope_modlog(self): num = 50 self.r.refresh_access_information(self.refresh_token['modlog']) result = self.r.get_subreddit(self.sr).get_mod_log(limit=num) self.assertEqual(num, len(list(result))) @betamax def test_scope_modposts(self): self.r.refresh_access_information(self.refresh_token['modposts']) Submission.from_id(self.r, self.submission_edit_id).remove() @betamax def test_scope_mysubreddits(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) self.assertTrue(list(self.r.get_my_moderation())) @betamax def test_scope_creddits(self): # Assume there are insufficient creddits. self.r.refresh_access_information(self.refresh_token['creddits']) redditor = self.r.get_redditor('bboe') sub = self.r.get_submission(url=self.comment_url) # Test error conditions self.assertRaises(TypeError, sub.gild, months=1) for value in (False, 0, -1, '0', '-1'): self.assertRaises(TypeError, redditor.gild, value) # Test object gilding self.assertRaises(errors.InsufficientCreddits, redditor.gild) self.assertRaises(errors.InsufficientCreddits, sub.gild) self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild) @betamax def test_scope_privatemessages(self): self.r.refresh_access_information( self.refresh_token['privatemessages']) self.assertTrue(list(self.r.get_inbox())) @betamax def test_scope_read(self): self.r.refresh_access_information(self.refresh_token['read']) self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0) fullname = '{0}_{1}'.format(self.r.config.by_object[Submission], self.priv_submission_id) method1 = self.r.get_info(thing_id=fullname) method2 = self.r.get_submission(submission_id=self.priv_submission_id) self.assertEqual(method1, method2) @betamax def test_scope_read_get_front_page(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) subscribed = list(self.r.get_my_subreddits(limit=None)) self.r.refresh_access_information(self.refresh_token['read']) for post in self.r.get_front_page(): self.assertTrue(post.subreddit in subscribed) @betamax def test_scope_read_get_sub_listingr(self): self.r.refresh_access_information(self.refresh_token['read']) subreddit = self.r.get_subreddit(self.priv_sr) self.assertTrue(list(subreddit.get_top())) @betamax def test_scope_read_get_submission_by_url(self): url = ("https://www.reddit.com/r/reddit_api_test_priv/comments/16kbb7/" "google/") self.r.refresh_access_information(self.refresh_token['read']) submission = Submission.from_url(self.r, url) self.assertTrue(submission.num_comments != 0) @betamax def test_scope_read_priv_sr_comments(self): self.r.refresh_access_information(self.refresh_token['read']) self.assertTrue(list(self.r.get_comments(self.priv_sr))) @betamax def test_scope_read_priv_sub_comments(self): self.r.refresh_access_information(self.refresh_token['read']) submission = Submission.from_id(self.r, self.priv_submission_id) self.assertTrue(submission.comments) @betamax def test_scope_submit(self): self.r.refresh_access_information(self.refresh_token['submit']) result = self.r.submit(self.sr, 'OAuth Submit', text='Foo') self.assertTrue(isinstance(result, Submission)) @betamax def test_scope_subscribe(self): self.r.refresh_access_information(self.refresh_token['subscribe']) self.r.get_subreddit(self.sr).subscribe() @betamax def test_scope_vote(self): self.r.refresh_access_information(self.refresh_token['vote']) submission = Submission.from_id(self.r, self.submission_edit_id) submission.clear_vote() @betamax def test_set_access_credentials(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information( self.refresh_token['identity'], update_session=False) self.assertTrue(self.r.user is None) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax def test_oauth_without_identy_doesnt_set_user(self): self.assertTrue(self.r.user is None) self.r.refresh_access_information(self.refresh_token['edit']) self.assertTrue(self.r.user is None)
class OAuth2RedditTest(PRAWTest): def setUp(self): self.configure() self.r = Reddit(USER_AGENT, site_name='reddit_oauth_test', disable_update_check=True) def test_authorize_url(self): self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') self.r.set_oauth_app_info(self.r.config.client_id, self.r.config.client_secret, self.r.config.redirect_uri) url, params = self.r.get_authorize_url('...').split('?', 1) self.assertTrue('api/v1/authorize/' in url) params = dict(x.split('=', 1) for x in params.split('&')) expected = {'client_id': self.r.config.client_id, 'duration': 'temporary', 'redirect_uri': ('https%3A%2F%2F127.0.0.1%3A65010%2F' 'authorize_callback'), 'response_type': 'code', 'scope': 'identity', 'state': '...'} self.assertEqual(expected, params) @betamax() def test_get_access_information(self): # If this test fails, the following URL will need to be visted in order # to obtain a new code to pass to `get_access_information`: # self.r.get_authorize_url('...') token = self.r.get_access_information('MQALrr1di8GzcnT8szbTWhLcBUQ') expected = {'access_token': self.r.access_token, 'refresh_token': None, 'scope': set(('identity',))} self.assertEqual(expected, token) self.assertEqual('PyAPITestUser2', text_type(self.r.user)) @betamax() def test_get_access_information_with_invalid_code(self): self.assertRaises(errors.OAuthInvalidGrant, self.r.get_access_information, 'invalid_code') def test_invalid_app_access_token(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_access_information, 'dummy_code') def test_invalid_app_authorize_url(self): self.r.clear_authentication() self.r.set_oauth_app_info(None, None, None) self.assertRaises(errors.OAuthAppRequired, self.r.get_authorize_url, 'dummy_state') @betamax() def test_invalid_set_access_credentials(self): self.assertRaises(errors.OAuthInvalidToken, self.r.set_access_credentials, set(('identity',)), 'dummy_access_token') def test_oauth_scope_required(self): self.r.set_oauth_app_info('dummy_client', 'dummy_secret', 'dummy_url') self.r.set_access_credentials(set('dummy_scope',), 'dummy_token') self.assertRaises(errors.OAuthScopeRequired, self.r.get_me) @betamax() def test_scope_edit(self): self.r.refresh_access_information(self.refresh_token['edit']) submission = Submission.from_id(self.r, self.submission_edit_id) self.assertEqual(submission, submission.edit('Edited text')) @betamax() def test_scope_history(self): self.r.refresh_access_information(self.refresh_token['history']) self.assertTrue(list(self.r.get_redditor(self.un).get_upvoted())) @betamax() def test_scope_identity(self): self.r.refresh_access_information(self.refresh_token['identity']) self.assertEqual(self.un, self.r.get_me().name) @betamax() def test_scope_modconfig(self): self.r.refresh_access_information(self.refresh_token['modconfig']) self.r.get_subreddit(self.sr).set_settings('foobar') retval = self.r.get_subreddit(self.sr).get_stylesheet() self.assertTrue('images' in retval) @betamax() def test_scope_modflair(self): self.r.refresh_access_information(self.refresh_token['modflair']) self.r.get_subreddit(self.sr).set_flair(self.un, 'foobar') @betamax() def test_scope_modlog(self): num = 50 self.r.refresh_access_information(self.refresh_token['modlog']) result = self.r.get_subreddit(self.sr).get_mod_log(limit=num) self.assertEqual(num, len(list(result))) @betamax() def test_scope_modothers_modself(self): subreddit = self.r.get_subreddit(self.sr) self.r.refresh_access_information(self.refresh_token['modothers']) subreddit.add_moderator(self.other_user_name) # log in as other user self.r.refresh_access_information(self.other_refresh_token['modself']) self.r.accept_moderator_invite(self.sr) # now return to original user. self.r.refresh_access_information(self.refresh_token['modothers']) subreddit.remove_moderator(self.other_user_name) @betamax() def test_scope_modposts(self): self.r.refresh_access_information(self.refresh_token['modposts']) Submission.from_id(self.r, self.submission_edit_id).remove() @betamax() def test_scope_mysubreddits(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) self.assertTrue(list(self.r.get_my_moderation())) @betamax() def test_scope_creddits(self): # Assume there are insufficient creddits. self.r.refresh_access_information( self.refresh_token['creddits']) redditor = self.r.get_redditor('bboe') sub = self.r.get_submission(url=self.comment_url) # Test error conditions self.assertRaises(TypeError, sub.gild, months=1) for value in (False, 0, -1, '0', '-1'): self.assertRaises(TypeError, redditor.gild, value) # Test object gilding self.assertRaises(errors.InsufficientCreddits, redditor.gild) self.assertRaises(errors.InsufficientCreddits, sub.gild) self.assertRaises(errors.InsufficientCreddits, sub.comments[0].gild) @betamax() def test_scope_privatemessages(self): self.r.refresh_access_information( self.refresh_token['privatemessages']) self.assertTrue(list(self.r.get_inbox())) @betamax() def test_scope_read(self): self.r.refresh_access_information(self.refresh_token['read']) self.assertTrue(self.r.get_subreddit(self.priv_sr).subscribers > 0) fullname = '{0}_{1}'.format(self.r.config.by_object[Submission], self.priv_submission_id) method1 = self.r.get_info(thing_id=fullname) method2 = self.r.get_submission(submission_id=self.priv_submission_id) self.assertEqual(method1, method2) @betamax() def test_scope_read_get_front_page(self): self.r.refresh_access_information(self.refresh_token['mysubreddits']) subscribed = list(self.r.get_my_subreddits(limit=None)) self.r.refresh_access_information(self.refresh_token['read']) for post in self.r.get_front_page(): self.assertTrue(post.subreddit in subscribed) @betamax() def test_scope_read_get_sub_listingr(self): self.r.refresh_access_information(self.refresh_token['read']) subreddit = self.r.get_subreddit(self.priv_sr) self.assertTrue(list(subreddit.get_top())) @betamax() def test_scope_read_get_submission_by_url(self): url = ("https://www.reddit.com/r/reddit_api_test_priv/comments/16kbb7/" "google/") self.r.refresh_access_information(self.refresh_token['read']) submission = Submission.from_url(self.r, url) self.assertTrue(submission.num_comments != 0) @betamax() def test_scope_read_priv_sr_comments(self): self.r.refresh_access_information(self.refresh_token['read']) self.assertTrue(list(self.r.get_comments(self.priv_sr))) @betamax() def test_scope_wikiread_wiki_page(self): self.r.refresh_access_information(self.refresh_token['wikiread']) self.assertTrue(self.r.get_wiki_page(self.sr, 'index')) @betamax() def test_scope_read_priv_sub_comments(self): self.r.refresh_access_information(self.refresh_token['read']) submission = Submission.from_id(self.r, self.priv_submission_id) self.assertTrue(submission.comments) @betamax() def test_scope_submit(self): self.r.refresh_access_information(self.refresh_token['submit']) result = self.r.submit(self.sr, 'OAuth Submit', text='Foo') self.assertTrue(isinstance(result, Submission)) @betamax() def test_scope_subscribe(self): self.r.refresh_access_information(self.refresh_token['subscribe']) self.r.get_subreddit(self.sr).subscribe() @betamax() def test_scope_vote(self): self.r.refresh_access_information(self.refresh_token['vote']) submission = Submission.from_id(self.r, self.submission_edit_id) submission.clear_vote() @betamax() def test_set_access_credentials(self): self.assertTrue(self.r.user is None) result = self.r.refresh_access_information( self.refresh_token['identity'], update_session=False) self.assertTrue(self.r.user is None) self.r.set_access_credentials(**result) self.assertFalse(self.r.user is None) @betamax() def test_oauth_without_identy_doesnt_set_user(self): self.assertTrue(self.r.user is None) self.r.refresh_access_information(self.refresh_token['edit']) self.assertTrue(self.r.user is None)
def add_callback(): tmp = Reddit(USER_AGENT, disable_update_check=True) tmp.login(self.other_user_name, self.other_user_pswd, disable_warning=True) tmp.get_subreddit(self.sr).accept_moderator_invite()