def update_from_wiki(subreddit, requester): """Updates conditions from the subreddit's wiki.""" global r username = cfg_file.get('reddit', 'username') try: page = subreddit.get_wiki_page(cfg_file.get('reddit', 'wiki_page_name')) except Exception: send_error_message(requester, subreddit.display_name, 'The wiki page could not be accessed. Please ensure the page ' 'http://www.reddit.com/r/{0}/wiki/{1} exists and that {2} ' 'has the "wiki" mod permission to be able to access it.' .format(subreddit.display_name, cfg_file.get('reddit', 'wiki_page_name'), username)) return html_parser = HTMLParser.HTMLParser() page_content = html_parser.unescape(page.content_md) # check that all the conditions are valid yaml condition_defs = yaml.safe_load_all(page_content) condition_num = 1 try: for cond_def in condition_defs: condition_num += 1 except Exception as e: indented = '' for line in str(e).split('\n'): indented += ' {0}\n'.format(line) send_error_message(requester, subreddit.display_name, 'Error when reading conditions from wiki - ' 'Syntax invalid in section #{0}:\n\n{1}' .format(condition_num, indented)) return # reload and actually process the conditions condition_defs = yaml.safe_load_all(page_content) condition_num = 1 kept_sections = [] for cond_def in condition_defs: # ignore any non-dict sections (can be used as comments, etc.) if not isinstance(cond_def, dict): continue # lowercase all keys cond_def = {k.lower(): v for k, v in cond_def.iteritems()} try: check_condition_valid(cond_def) except ValueError as e: send_error_message(requester, subreddit.display_name, 'Invalid condition in section #{0} - {1}' .format(condition_num, e)) return # create a condition for final checks condition = Condition(cond_def) # test to make sure that the final regex(es) are valid for pattern in condition.match_patterns.values(): try: re.compile(pattern) except Exception as e: send_error_message(requester, subreddit.display_name, 'Generated an invalid regex from section #{0} - {1}' .format(condition_num, e)) return condition_num += 1 kept_sections.append(cond_def) # Update the subreddit, or add it if necessary try: db_subreddit = (session.query(Subreddit) .filter(Subreddit.name == subreddit.display_name.lower()) .one()) except NoResultFound: db_subreddit = Subreddit() db_subreddit.name = subreddit.display_name.lower() db_subreddit.last_submission = datetime.utcnow() - timedelta(days=1) db_subreddit.last_spam = datetime.utcnow() - timedelta(days=1) db_subreddit.last_comment = datetime.utcnow() - timedelta(days=1) session.add(db_subreddit) db_subreddit.conditions_yaml = page_content session.commit() r.send_message(requester, '{0} conditions updated'.format(username), "{0}'s conditions were successfully updated for /r/{1}" .format(username, subreddit.display_name))
def update_from_wiki(subreddit, requester): """Updates conditions from the subreddit's wiki.""" global r username = cfg_file.get('reddit', 'username') try: page = subreddit.get_wiki_page(cfg_file.get('reddit', 'wiki_page_name')) except Exception: send_error_message(requester, subreddit.display_name, 'The wiki page could not be accessed. Please ensure the page ' 'http://www.reddit.com/r/{0}/wiki/{1} exists and that {2} ' 'has the "wiki" mod permission to be able to access it.' .format(subreddit.display_name, cfg_file.get('reddit', 'wiki_page_name'), username)) return False html_parser = HTMLParser.HTMLParser() page_content = html_parser.unescape(page.content_md) # check that all the conditions are valid yaml condition_defs = yaml.safe_load_all(page_content) condition_num = 1 try: for cond_def in condition_defs: condition_num += 1 except Exception as e: indented = '' for line in str(e).split('\n'): indented += ' {0}\n'.format(line) send_error_message(requester, subreddit.display_name, 'Error when reading conditions from wiki - ' 'Syntax invalid in section #{0}:\n\n{1}' .format(condition_num, indented)) return False # reload and actually process the conditions condition_defs = yaml.safe_load_all(page_content) condition_num = 1 kept_sections = [] for cond_def in condition_defs: # ignore any non-dict sections (can be used as comments, etc.) if not isinstance(cond_def, dict): continue cond_def = lowercase_keys_recursively(cond_def) try: check_condition_valid(cond_def) except ValueError as e: send_error_message(requester, subreddit.display_name, 'Invalid condition in section #{0} - {1}' .format(condition_num, e)) return False # create a condition for final checks condition = Condition(cond_def) # test to make sure that the final regex(es) are valid for pattern in condition.match_patterns.values(): try: re.compile(pattern) except Exception as e: send_error_message(requester, subreddit.display_name, 'Generated an invalid regex from section #{0} - {1}' .format(condition_num, e)) return False condition_num += 1 kept_sections.append(cond_def) # Update the subreddit, or add it if necessary try: db_subreddit = (session.query(Subreddit) .filter(Subreddit.name == subreddit.display_name.lower()) .one()) except NoResultFound: db_subreddit = Subreddit() db_subreddit.name = subreddit.display_name.lower() db_subreddit.last_submission = datetime.utcnow() - timedelta(days=1) db_subreddit.last_spam = datetime.utcnow() - timedelta(days=1) db_subreddit.last_comment = datetime.utcnow() - timedelta(days=1) session.add(db_subreddit) db_subreddit.conditions_yaml = page_content session.commit() r.send_message(requester, '{0} conditions updated'.format(username), "{0}'s conditions were successfully updated for /r/{1}" .format(username, subreddit.display_name)) return True
def main(): global r prawlogger = logging.getLogger('prawcore') prawlogger.setLevel(logging.WARN) while True: # Login retry loop try: logger.info('Logging in as {0}' .format(cfg_file.get('reddit', 'username'))) r = praw.Reddit(client_id = cfg_file.get('reddit', 'client_id'), client_secret = cfg_file.get('reddit', 'client_secret'), user_agent = cfg_file.get('reddit', 'user_agent'), username = cfg_file.get('reddit', 'username'), password = cfg_file.get('reddit', 'password')) # break except Exception as e: logger.error('ERROR: {0}'.format(e)) logger.debug(traceback.format_exc()) else: sr_dict = get_moderated_subreddits() # load conditions from wiki rule_dict = load_all_rules(sr_dict) pprint.pprint(rule_dict) break while True: # main execution loop sleep_after = True reload_mod_subs = False try: # First, process command messages for message in unread_messages(): try: command = message.body.strip().lower() sr_name = clean_sr_name(message.subject).lower() subreddit = r.subreddit(sr_name) # TODO: validate user is moderator if message.author not in subreddit.moderator(): message.reply('Error: You do not moderate /r/{0}'.format(subreddit.display_name)) continue # OK, validated if command == 'register': # do we know this sub? if sr_name in sr_dict.keys(): message.reply("I already moderate /r/{}.\n\n".format(sr_name)) continue # otherwise... try to accept mod invite try: subreddit.mod.accept_invite() except: # should be APIException(error_type='NO_INVITE_FOUND') message.reply("You must invite me to moderate /r/{} first." .format(sr_name)) raise else: # get sub from db if previously registered: db_subreddit = None try: db_subreddit = (session.query(Subreddit) .filter(Subreddit.name == sr_name) .one()) except NoResultFound: # add to DB db_subreddit = Subreddit() db_subreddit.name = subreddit.display_name.lower() db_subreddit.last_submission = datetime.utcnow() - timedelta(days=1) db_subreddit.last_spam = datetime.utcnow() - timedelta(days=1) db_subreddit.last_comment = datetime.utcnow() - timedelta(days=1) db_subreddit.conditions_yaml = '' session.add(db_subreddit) finally: # now that it definitely exists: set enabled # (should we clear old rules from the db?) db_subreddit.enabled = True session.commit() message.reply("I have joined /r/{}".format(db_subreddit.name)) elif command in ['update', 'status', 'enable', 'disable', 'leave']: # these require the same database query db_subreddit = None try: db_subreddit = (session.query(Subreddit) .filter(Subreddit.name == sr_name) .one()) except NoResultFound: message.reply("Subreddit /r/{} is not registered with me." .format(sr_name)) else: # only proceed if we get a database hit. if command == 'update': # refresh configuration for a subreddit # todo: cache duplicate requests from multiple mods reload_mod_subs = True update_from_wiki(db_subreddit, message) elif command == 'status': pass elif command == 'enable': db_subreddit.enabled = True reload_mod_subs = True elif command == 'disable': db_subreddit.enabled = False reload_mod_subs = True elif command == 'leave': # leave moderator of subreddit if db_subreddit.enabled: message.reply("Please disable me on this subreddit first.") else: # TODO not implemented yet reload_mod_subs = True raise NotImplementedError # the following commands should respond with the enabled status if command in ['status', 'enable', 'disable']: message.reply("Subreddit /r/{} is currently {}abled." .format(db_subreddit.name, 'en' if db_subreddit.enabled else 'dis')) finally: session.commit() elif command == 'help': # should this just provide a link, or real command explanations? raise NotImplementedError else: # invalid command message.reply("Invalid command.") except NotImplementedError: message.reply("Error: that feature is not yet implemented.") except KeyboardInterrupt: raise except Exception as e: logger.error('ERROR: {0}'.format(e)) logger.debug(traceback.format_exc()) message.reply("# ERROR:\n\n{}".format(indent_lines(str(e)))) finally: message.mark_read() # changed mod subs if reload_mod_subs: sr_dict = get_moderated_subreddits() rule_dict = load_all_rules(sr_dict) # Then process queues: submission, comment, spam, report, comment reply, username mention # TODO: queue for edited items... # Queue priority, in increasing specificity: # - reports: multi/about/reports?only=(links|comments) # - comment # - submission # - any # - spam: multi/about/spam?only=(links|comments) # - comment # - submission # - any # - edited: multi/about/edited?only=(links|comments) # - comment # - submission # - any # - reply: inbox # - mention: inbox # - submission: multi/new # - comment: multi/comments multi_mod_queues = ['reports', 'spam', 'edited'] # r.subreddit().mod.<q> multi_queues = ['new', 'comments'] # r.subreddit().<q> user_queues = ['comment_replies', 'submission_replies', 'mentions'] # r.user.inbox.<q> # proof of concept for sr_name, subreddit in sr_dict.items(): logger.debug("Checking items in /r/{}".format(sr_name)) sr = r.subreddit(sr_name) # mod-only level queues for item in sr.mod.spam(): for rule in rule_dict[sr_name]: rule.process(item) for item in sr.mod.reports(): for rule in rule_dict[sr_name]: rule.process(item) for item in sr.mod.edited(): for rule in rule_dict[sr_name]: rule.process(item) # sub-level queues for item in sr.mod.new(): for rule in rule_dict[sr_name]: rule.process(item) for item in sr.mod.comments(): for rule in rule_dict[sr_name]: rule.process(item) # user queues - not implemented # for queue in queue_funcs: # subreddits = [s for s in sr_dict # if s in cond_dict and len(cond_dict[s][queue]) > 0] # if len(subreddits) == 0: # continue # multireddits = build_multireddit_groups(subreddits) # # fetch and process the items for each multireddit # for multi in multireddits: # if queue == 'report': # limit = cfg_file.get('reddit', 'report_backlog_limit_hours') # stop_time = datetime.utcnow() - timedelta(hours=int(limit)) # else: # stop_time = max(getattr(sr, 'last_'+queue) # for sr in sr_dict.values() # if sr.name in multi) # queue_subreddit = r.get_subreddit('+'.join(multi)) # if queue_subreddit: # queue_func = getattr(queue_subreddit, queue_funcs[queue]) # items = queue_func(limit=None) # check_items(queue, items, stop_time, sr_dict, cond_dict) except KeyboardInterrupt: raise except Exception as e: logger.error('ERROR: {0}'.format(e)) logger.debug(traceback.format_exc()) session.rollback() finally: if sleep_after: logger.info('Sleeping for 10 seconds') sleep(10) logger.info('Sleep ended, resuming') logging.info("Looping")