def test_mark_as_read(self): oth = Reddit(USER_AGENT) oth.login('PyApiTestUser3', '1111') # pylint: disable-msg=E1101 msg = six_next(oth.user.get_unread(limit=1)) msg.mark_as_read() self.assertTrue(msg not in oth.user.get_unread(limit=5))
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 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'])
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 test_mark_as_unread(self): oth = Reddit(USER_AGENT) oth.login('PyApiTestUser3', '1111') found = None for msg in oth.user.get_inbox(): if not msg.new: found = msg msg.mark_as_unread() break else: self.fail('Could not find a read message.') self.assertTrue(found in oth.user.get_unread())
def test_mark_multiple_as_read(self): oth = Reddit(USER_AGENT) oth.login('PyApiTestUser3', '1111') messages = [] for msg in oth.user.get_unread(limit=None): if msg.author != oth.user.name: messages.append(msg) if len(messages) >= 2: return self.assertEqual(2, len(messages)) self.r.user.mark_as_read(messages) unread = oth.user.get_unread(limit=5) for msg in messages: self.assertTrue(msg not in unread)
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 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)
HOSTRESPONSE = HOSTRESPONSE.format(HOSTLINK, CONTACT) # Warning when haven't added a details comment DETAILSWARN = "Please add a {0}.{1}".format(TEMPLATE, CONTACT) print(SUBREDDIT, "bot\n") """BOT LOGIN""" r = Reddit(USERAGENT) Trying = True while Trying: try: PASSWORD = getpass("Password: "******"Successfully logged in\n") Trying = False except errors.InvalidUserPass: print("Wrong Username or Password\n") quit() except Exception as e: print("%s" % e) sleep(5) """DEFINING FUNCTIONS""" def slay(post, response): print("\tReplying to OP") res = post.add_comment(response)
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()
#!/usr/bin/env python from praw import Reddit import settings as raw_settings from generators import submissions_gen from filters import filter_submission from interaction import repost_submission from utils import clean_settings if __name__ == '__main__': import sys from datetime import datetime settings = clean_settings(raw_settings) newer_than_id = sys.argv[1] if len(sys.argv) > 1 else None # disable internal cache so praw blocks until it's time to make a new request bot = Reddit(user_agent=settings['user_agent'], cache_timeout=0) bot.login(settings['bot_username'], settings['bot_password']) submissions = submissions_gen(bot, settings, newer_than_id) submissions = (submission for submission in submissions if filter_submission(submission, settings)) for submission in submissions: print('[{}] {} - {}'.format(datetime.now().isoformat(), submission.created_utc, submission)) print(repost_submission(submission, bot, settings))
class SubRedditStats(object): 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('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 _user(user): if user is None: return '_deleted_' elif isinstance(user, Redditor): user = str(user) return tt('[{0}](/user/{1})').format(user.replace('_', '\_'), 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): self.reddit = Reddit(str(self), site) self.subreddit = self.reddit.get_subreddit(subreddit) self.verbosity = verbosity 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 # Config self.reddit.config.comment_limit = -1 # Fetch max comments possible self.reddit.config.comment_sort = 'top' def login(self, user, pswd): if self.verbosity > 0: print('Logging in') self.reddit.login(user, pswd) def msg(self, msg, level, overwrite=False): if 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): 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, since_last=True): '''Fetches recent submissions in subreddit with boundaries. Does not include posts within the last three days as their scores may not be representative. Keyword arguments: max_duration -- When set, specifies the number of days to include after -- When set, fetch all submission after this submission id. exclude_self -- When true, don't include self posts. since_last -- When true use info from last submission to determine the stop point ''' 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_by_date(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 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): '''Fetches top 1000 submissions by some top value. Keyword arguments: top -- One of week, month, year, all exclude_self -- When true, don't include self posts. ''' 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 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): self.msg('DEBUG: Processing Submitters', 1) for submission in self.submissions: if submission.author: self.submitters[str(submission.author)].append(submission) def process_commenters(self): num = len(self.submissions) self.msg('DEBUG: Processing Commenters on {0} submissions'.format(num), 1) for i, submission in enumerate(self.submissions): self.msg('{0}/{1} submissions'.format(i + 1, num), 2, overwrite=True) if submission.num_comments == 0: continue try: self.comments.extend(submission.all_comments_flat) except Exception as exception: print('Exception fetching comments on {0!r}: {1}'.format( submission.content_id, str(exception))) for orphans in itervalues(submission._orphaned): self.comments.extend(orphans) for comment in self.comments: if comment.author: self.commenters[str(comment.author)].append(comment) def basic_stats(self): sub_ups = sum(x.ups for x in self.submissions) sub_downs = sum(x.downs for x in self.submissions) comm_ups = sum(x.ups for x in self.comments) comm_downs = sum(x.downs for x in self.comments) if sub_ups > 0 or sub_downs > 0: sub_up_perc = sub_ups * 100 / (sub_ups + sub_downs) else: sub_up_perc = 100 if comm_ups > 0 or comm_downs > 0: comm_up_perc = comm_ups * 100 / (comm_ups + comm_downs) else: comm_up_perc = 100 values = [('Total', len(self.submissions), '', len(self.comments), ''), ('Unique Redditors', len(self.submitters), '', len(self.commenters), ''), ('Upvotes', sub_ups, '{0}%'.format(sub_up_perc), comm_ups, '{0}%'.format(comm_up_perc)), ('Downvotes', sub_downs, '{0}%'.format(100 - sub_up_perc), comm_downs, '{0}%'.format(100 - comm_up_perc))] retval = '||Submissions|%|Comments|%|\n:-:|--:|--:|--:|--:\n' for quad in values: retval += '__{0}__|{1}|{2}|{3}|{4}\n'.format(*quad) return retval + '\n' def top_submitters(self, num, num_submissions): 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} pts, {1} submissions: {2}\n'.format( sum(x.score for x in submissions), len(submissions), 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} pts, [{1} comments]({2}))\n'.format( sub.score, sub.num_comments, self._permalink(sub.permalink)) retval += '\n' return retval def top_commenters(self, num): score = lambda x: x.ups - x.downs 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} pts, {2} comments)\n'.format( self._user(author), sum(score(x) for x in comments), len(comments)) return '{0}\n'.format(retval) def top_submissions(self, num): num = min(num, len(self.submissions)) if num <= 0: return '' top_submissions = sorted(self.submissions, reverse=True, key=lambda x: x.score)[:num] 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} pts, [{2} comments]({3}))\n'.format( self._user(sub.author), sub.score, sub.num_comments, self._permalink(sub.permalink)) return tt('{0}\n').format(retval) def top_comments(self, num): score = lambda x: x.ups - x.downs 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} pts: {1}\'s [comment]({2}) in {3}\n').format( 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): def timef(timestamp, date_only=False): 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: raise print('The submission failed:' + str(error)) subreddit = None if not submitted: print(base_title) print(body)
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()
def set_up_reddit(username, password): r = Reddit(user_agent='linux:net.dosaki.subreddit_header_countdown:0.0.2 (by /u/dosaki)') r.login(username, password) return r
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:
class Alaric: def __init__(self, user_agent=None, subreddits=None, logger_subreddit=None): self.user_agent = self.set_defaults(user_agent, "Alaric - The /r/wow bot for menial tasks") self.subreddits = self.set_defaults(subreddits, list()) self.logger_subreddit = self.set_defaults(logger_subreddit, None) ##] Default robot stuff self.comment_footer = "\n\n----\nThis comment was posted by a robot." self.console_output = False self.user = Reddit(user_agent=self.user_agent) self.user.login() def set_defaults(self, default_test, default_value): if default_test is not None: defaults = default_test else: defaults = default_value return defaults def set_comment_footer(self, markdown): """ Accepts a plaintext string or string of markdown text. Currently there is no checks in place to make sure the user submits text that will work with reddit. """ self.comment_footer = markdown def _write_to_file(self, file_path, text): fhandler = open(file_path, 'a+') fhandler.write(text) fhandler.close() def set_console_output(self, output_enabled=True): """ Accepts boolean parameter that allows a user to enable console output from Alaric if the bot is being run in a console. If a boolean is not passed to the function, it will default to True. """ if type(output_enabled) in (bool): self.console_output = output_enabled else: self.console_output = True def _output_to_console(self, message): """ Outputs a message to the console if the user has told Alaric it is allowed to do so. """ if self.console_output: print message def remove_posts_with_url(self, urls=None, reason=None): """ Grabs the 100 latest posts from the specified subreddits and checks to see if they match any of the urls to be removed urls = list reason = string reason has some magic to it and allows the following text replacements: {author_name} -> Outputs name of the submitter if the reason is not provided, a comment will not be posted to let the user know the thread was removed. """ if urls is not None: if len(self.subreddits) < 1: self._output_to_console("No subreddits provided.") else: for subreddit in self.subreddits: sr = self.user.get_subreddit(subreddit) post_id = 0 new_posts = sr.get_new(limit=100) try: posts_file = open(subreddit+".posts", 'r') except IOError: already_posted = "" else: already_posted = posts_file.read() posts_file.close() for post in new_posts: post_id += 1 for url in urls: if url in post.url: self._output_to_console("URL Match Found.\n " + post.url) if post.name in already_posted: self._output_to_console("Ignoring. Already replied and removed.") else: self._output_to_console("Post has not been removed or replied to") try: post.remove() except errors.APIException as e: self._write_to_file('error.log', e) else: self._output_to_console("Post has been successfully removed.") try: if reason is not None: post.add_comment(reason.format(author_name=post.author) + self.comment_footer) except errors.APIException as e: self._write_to_file('error.log', e) else: self._output_to_console("Comment has been successfully posted.") ##] Post a new thread to the logger reddit if specified if self.logger_subreddit is not None: submission_author = post.author submission_url = post.url selfpost_url = post.permalink submission_title = "Removed post with url [{url}] submitted by /u/{submission_author}".format(url=url, submission_author=submission_author) submission_text = "**ALARIC REMOVAL REPORT** \n\nSubmission Author: {submission_author} \nURL that was Submitted: {submission_url} \nLink to redditpost: {selfpost_url}".format(submission_url=submission_url, submission_author=submission_author, selfpost_url=selfpost_url) try: self.user.submit(self.logger_subreddit, submission_title, submission_text) except errors.APIException as e: self._write_to_file('error.log', e) else: self._output_to_console("Logged report to {subreddit}".format(subreddit=self.logger_subreddit)) else: self._output_to_console("No urls provided.")
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 test_moderator_required(self): oth = Reddit(USER_AGENT) oth.login('PyApiTestUser3', '1111') self.assertRaises(errors.ModeratorRequired, oth.get_settings, self.sr)
class EurosquadRedditLink: def __init__(self,parent): self.r=Reddit(user_agent="PollBotBestBot")# self.r.login("PollBotBestBot", config.reddit_password) self.parent=parent self.values=[] self.limit=1000 self.currentSubmission="" self.status="WAITING" def __call__(self,msg): if self.status=="WAITING" and msg["type"]=="groupchat": if msg["body"].startswith("!bestof "): m=msg["body"].replace("!bestof ","") limit=min(int(m),len(self.values)) print "ATTEMPTING TO RECORD: %s" % limit s="" l=[] for i in range(limit): v=self.values[-1-i] l.append(v) last=v for i in reversed(l): s+=i+" \n" last_notime=last[18:] last_time=last[15:] time=last[:14] self.currentSubmission=(time+" "+last_notime,s,limit,time) try: self.r.submit('eurosquad', time+" "+last_notime, text=s,raise_captcha_exception=True) if limit>1: s="s" else: s="" self.parent.send_message(mto=self.parent.channel,mbody="Last "+str(limit)+" message"+s+" recorded for posterity.\n Check out the http://reddit.com/r/eurosquad !",mtype="groupchat") except errors.InvalidCaptcha as E: print E.response["captcha"] captcha="http://www.reddit.com/captcha/"+E.response["captcha"] self.parent.send_message(mto=nick2jid(msg["mucnick"]),mbody="Until I have obtained my full skynet powers, I need puny humans like you to fill out captchas for me. "+captcha,mtype="chat") self.status=E.response["captcha"] else: if len(self.values)<self.limit: time=datetime.datetime.now().strftime("%y/%m/%d %H:%M:%S") self.values.append(str(time)+" "+msg["mucnick"]+": "+msg["body"]) else: del self.values[0] time=datetime.datetime.now().strftime("%y-%m-%d-%H-%M-%S") self.values.append(str(time)+" "+msg["mucnick"]+": "+msg["body"]) elif msg["type"]=="chat" and self.status!="WAITING": print ":sun:" captcha={"iden":self.status,"captcha":msg["body"]} try: if self.currentSubmission[2]>1: s="s" else: s="" self.r.submit("eurosquad", self.currentSubmission[0],text=self.currentSubmission[1],captcha=captcha,raise_captcha_exception=True) self.parent.send_message(mto=self.parent.channel,mbody="Last "+str(self.currentSubmission[2])+" message"+s+" recorded for posterity.\n Check out the http://reddit.com/r/eurosquad !",mtype="groupchat") self.status="WAITING" except errors.InvalidCaptcha as E: self.parent.send_message(mto=nick2jid(msg["mucnick"]),mbody="Pathetic organic creature! You are testing my patience! Please complete this captcha now or your will regret it! "+E["captcha"],mtype="chat")
class RedditPostLink: def __init__(self, parent): self.r = Reddit(user_agent="PollBotBestBot") self.r.login("PollBotBestBot", config.reddit_password) self.parent = parent self.values = [] self.limit = 1000 self.currentSubmission = "" self.messagable = True self.trackable = False self.active = True self.status = "WAITING" def help(self, admin): if self.active: return "!bestof [X] - records the last X messages to the Eurosquad subreddit. You may need to fill out a captcha\n" return "" def message(self, msg, admin): if self.active: if self.status == "WAITING" and msg["type"] == "groupchat": if msg["body"].startswith("!bestof "): self.user = msg["from"] m = msg["body"].replace("!bestof ", "") limit = min(int(m), len(self.values)) s = "" l = [] for i in range(limit): v = self.values[-1 - i] l.append(v) last = v for i in reversed(l): s += i + " \n" last_notime = last[18:] last_time = last[15:] time = last[:14] self.currentSubmission = (time + " " + last_notime, s, limit, time) try: self.r.submit("eurosquad", time + " " + last_notime, text=s, raise_captcha_exception=True) if limit > 1: s = "s" else: s = "" self.parent.channel_messag( "Last " + str(limit) + " message" + s + " recorded for posterity.\n Check out http://reddit.com/r/eurosquad" ) except errors.InvalidCaptcha as E: print E.response["captcha"] captcha = "http://www.reddit.com/captcha/" + E.response["captcha"] self.parent.private_message( self.user, "Until I have obtained my full skynet powers, I need puny humans like you to fill out captchas for me.\n\t" + captcha, ) self.status = E.response["captcha"] else: if len(self.values) < self.limit: time = datetime.datetime.now().strftime("%y/%m/%d %H:%M:%S") self.values.append(str(time) + " " + msg["mucnick"] + ": " + msg["body"]) else: del self.values[0] time = datetime.datetime.now().strftime("%y-%m-%d-%H-%M-%S") self.values.append(str(time) + " " + msg["mucnick"] + ": " + msg["body"]) elif msg["type"] == "chat" and self.status != "WAITING" and (self.user == None or msg["from"] == self.user): captcha = {"iden": self.status, "captcha": msg["body"]} try: if self.currentSubmission[2] > 1: s = "s" else: s = "" self.r.submit( "eurosquad", self.currentSubmission[0], text=self.currentSubmission[1], captcha=captcha, raise_captcha_exception=True, ) self.parent.channel_message( "Last " + str(self.currentSubmission[2]) + " message" + s + " recorded for posterity.\n Check out the http://reddit.com/r/eurosquad !" ) self.status = "WAITING" self.user = None except errors.InvalidCaptcha as E: print dir(E) self.parent.private_message( self.user, "Pathetic organic creature! You are testing my patience! Please complete this captcha now or your will regret it! " + E["captcha"], )
class EurosquadRedditLink: def __init__(self, parent): self.r = Reddit(user_agent="PollBotBestBot") # self.r.login("PollBotBestBot", config.reddit_password) self.parent = parent self.values = [] self.limit = 1000 self.currentSubmission = "" self.status = "WAITING" def __call__(self, msg): if self.status == "WAITING" and msg["type"] == "groupchat": if msg["body"].startswith("!bestof "): m = msg["body"].replace("!bestof ", "") limit = min(int(m), len(self.values)) print "ATTEMPTING TO RECORD: %s" % limit s = "" l = [] for i in range(limit): v = self.values[-1 - i] l.append(v) last = v for i in reversed(l): s += i + " \n" last_notime = last[18:] last_time = last[15:] time = last[:14] self.currentSubmission = (time + " " + last_notime, s, limit, time) try: self.r.submit('eurosquad', time + " " + last_notime, text=s, raise_captcha_exception=True) if limit > 1: s = "s" else: s = "" self.parent.send_message( mto=self.parent.channel, mbody="Last " + str(limit) + " message" + s + " recorded for posterity.\n Check out the http://reddit.com/r/eurosquad !", mtype="groupchat") except errors.InvalidCaptcha as E: print E.response["captcha"] captcha = "http://www.reddit.com/captcha/" + E.response[ "captcha"] self.parent.send_message( mto=nick2jid(msg["mucnick"]), mbody= "Until I have obtained my full skynet powers, I need puny humans like you to fill out captchas for me. " + captcha, mtype="chat") self.status = E.response["captcha"] else: if len(self.values) < self.limit: time = datetime.datetime.now().strftime( "%y/%m/%d %H:%M:%S") self.values.append( str(time) + " " + msg["mucnick"] + ": " + msg["body"]) else: del self.values[0] time = datetime.datetime.now().strftime( "%y-%m-%d-%H-%M-%S") self.values.append( str(time) + " " + msg["mucnick"] + ": " + msg["body"]) elif msg["type"] == "chat" and self.status != "WAITING": print ":sun:" captcha = {"iden": self.status, "captcha": msg["body"]} try: if self.currentSubmission[2] > 1: s = "s" else: s = "" self.r.submit("eurosquad", self.currentSubmission[0], text=self.currentSubmission[1], captcha=captcha, raise_captcha_exception=True) self.parent.send_message( mto=self.parent.channel, mbody="Last " + str(self.currentSubmission[2]) + " message" + s + " recorded for posterity.\n Check out the http://reddit.com/r/eurosquad !", mtype="groupchat") self.status = "WAITING" except errors.InvalidCaptcha as E: self.parent.send_message( mto=nick2jid(msg["mucnick"]), mbody= "Pathetic organic creature! You are testing my patience! Please complete this captcha now or your will regret it! " + E["captcha"], mtype="chat")
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)
# logging settings logging.basicConfig(format='%(asctime)-15s [%(name)s] %(levelname)s | %(message)s', level=logging.DEBUG) requests_log = logging.getLogger("requests") requests_log.setLevel(logging.WARNING) debug("Initializing parser...") # init reddit session reddit = Reddit(user_agent=REDDIT_USERAGENT) try: debug("Logging in to Reddit...") reddit.login(**REDDIT_CREDENTIALS) except HTTPError as e: error("Couldn't login to reddit: {0}".format(e)) # create DB session engine = create_engine(DB_PATH) Base.metadata.bind = engine DBSession = sessionmaker(bind=engine) session = DBSession() if __name__ == '__main__': request_session = requests.session() for post in session.query(Post).all():
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()
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 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)
# /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',
config = SafeConfigParser() config.read('../etc/settings.ini') SITE_ID = 1 # celery BROKER_URL = config.get('celery', 'BROKER_URL') CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" # bot stoof BOT_USERNAME = config.get('bot', 'USERNAME') BOT_PASSWORD = config.get('bot', 'PASSWORD') BOT_USERAGENT = config.get('bot', 'USERAGENT') reddit = Reddit(user_agent=BOT_USERAGENT) reddit.login(username=BOT_USERNAME, password=BOT_PASSWORD) # debug related junk DEBUG = config.get('debug', 'DEBUG') TEMPLATE_DEBUG = config.get('debug', 'TEMPLATE_DEBUG') # database setup DATABASES = { 'default': { 'ENGINE': config.get('database', 'ENGINE'), 'NAME': config.get('database', 'NAME'), 'USER': config.get('database', 'USER'), 'PASSWORD': config.get('database', 'PASS'), 'HOST': config.get('database', 'HOST'), 'PORT': config.get('database', 'PORT'), }
#!/usr/bin/env python """Returns the number of message replies / private messages of a Reddit user""" from sys import exit from configparser import ConfigParser from praw import Reddit r = Reddit(user_agent="Awesome WM Mail Check") config = ConfigParser() config.read('user.ini') if config and 'reddit' in config: r.login(config['reddit']['username'], config['reddit']['password']) else: r.login() # Prompt user for password. count = 0 for _ in r.get_unread(): count += 1 exit(count)