def do_subreddits(mod_subreddit, sr_dict, start_utc): """Checks conditions and performs actions for subreddits in sr_dict""" # check reports items = mod_subreddit.get_reports(limit=1000) stop_time = datetime.utcnow() - REPORT_BACKLOG_LIMIT check_items('report', items, sr_dict, stop_time) # check spam items = mod_subreddit.get_modqueue(limit=1000) stop_time = (session.query(func.max(Subreddit.last_spam)) .filter(Subreddit.enabled == True).one()[0]) check_items('spam', items, sr_dict, stop_time) # check new submissions items = mod_subreddit.get_new_by_date(limit=1000) stop_time = (session.query(func.max(Subreddit.last_submission)) .filter(Subreddit.enabled == True).one()[0]) check_items('submission', items, sr_dict, stop_time) # check new comments # comment_multi = '+'.join([s.name for s in subreddits comment_multi = '+'.join([s.name for s in sr_dict.itervalues() if not s.reported_comments_only]) if comment_multi: comment_multi_sr = r.get_subreddit(comment_multi) items = comment_multi_sr.get_comments(limit=1000) stop_time = (session.query(func.max(Subreddit.last_comment)) .filter(Subreddit.enabled == True).one()[0]) check_items('comment', items, sr_dict, stop_time) # respond to modmail try: respond_to_modmail(r.user.get_modmail(), start_utc) except Exception as e: logging.error(' ERROR: %s', e)
def respond_to_modmail(modmail, start_time): """Responds to modmail if any submitters sent one before approval.""" cache = list() # respond to any modmail sent in the last 5 mins time_window = timedelta(minutes=5) approvals = session.query(ActionLog).filter( and_(ActionLog.action == 'approve', ActionLog.action_time >= start_time - time_window) ).all() for item in approvals: found = None done = False for i in cache: if datetime.utcfromtimestamp(i.created_utc) < item.created_utc: done = True break if (i.dest.lower() == '#'+item.subreddit.name.lower() and i.author.name == item.user and not i.replies): found = i break if not found and not done: for i in modmail: cache.append(i) if datetime.utcfromtimestamp(i.created_utc) < item.created_utc: break if (i.dest.lower() == '#'+item.subreddit.name.lower() and i.author.name == item.user and not i.replies): found = i break if found: found.reply('Your submission has been approved automatically by '+ cfg_file.get('reddit', 'username')+'. For future submissions ' 'please wait at least 5 minutes before messaging the mods, ' 'this post would have been approved automatically even ' 'without you sending this message.')
def main(): logging.config.fileConfig(path_to_cfg) start_utc = datetime.utcnow() start_time = time() global r try: r = reddit.Reddit(user_agent=cfg_file.get('reddit', 'user_agent')) logging.info('Logging in as %s', cfg_file.get('reddit', 'username')) r.login(cfg_file.get('reddit', 'username'), cfg_file.get('reddit', 'password')) except Exception as e: logging.error(' ERROR: %s', e) mod_subreddit = r.get_subreddit('mod') # # Do actions on individual subreddits # logging.info('CHECKING SUBREDDITS') # get subreddit list subreddits = session.query(Subreddit).filter(Subreddit.enabled == True).all() sr_dict = dict() for subreddit in subreddits: sr_dict[subreddit.name.lower()] = subreddit # do actions on subreddits do_subreddits(mod_subreddit, sr_dict, start_utc) # # Do actions on networks # logging.info('CHECKING NETWORKS') mods_checked = 0 # get network list networks = session.query(Network).filter(Network.enabled == True).all() # do actions on each network for network in networks: # get subreddits in network network_subs = session.query(Subreddit).filter(Subreddit.network == network.id, Subreddit.enabled == True).all() network_sr_dict = dict() for subreddit in network_subs: network_sr_dict[subreddit.name.lower()] = subreddit # do subreddit actions on subreddits do_subreddits(mod_subreddit, network_sr_dict, start_utc) # check network mods logging.info('Checking network moderators') for subreddit in network_sr_dict.itervalues(): # only check subs in networks if subreddit.network: mods_checked += check_network_moderators(network, network_sr_dict) logging.info(' Checked %s networks, added %s moderators', len(networks), mods_checked) logging.info('Completed full run in %s', elapsed_since(start_time))
def check_condition(item, condition): """Checks an item against a single condition (and sub-conditions). Returns True if it matches, or False if not """ start_time = time() if condition.attribute == 'user': if item.author: test_string = item.author.name elif (condition.attribute == 'body' and isinstance(item, reddit.objects.Submission)): test_string = item.selftext elif condition.attribute.startswith('media_'): if item.media: try: if condition.attribute == 'media_user': test_string = item.media['oembed']['author_name'] elif condition.attribute == 'media_title': test_string = item.media['oembed']['description'] elif condition.attribute == 'media_description': test_string = item.media['oembed']['description'] except KeyError: test_string = '' else: test_string = '' elif condition.attribute == 'meme_name': test_string = get_meme_name(item) else: test_string = getattr(item, condition.attribute) if not test_string: test_string = '' if condition.inverse: logging.debug(' Check #%s: "%s" NOT match ^%s$', condition.id, test_string.encode('ascii', 'ignore'), condition.value.encode('ascii', 'ignore').lower()) else: logging.debug(' Check #%s: "%s" match ^%s$', condition.id, test_string.encode('ascii', 'ignore'), condition.value.encode('ascii', 'ignore').lower()) if re.search('^'+condition.value+'$', test_string.lower(), re.DOTALL|re.UNICODE|re.IGNORECASE): satisfied = True else: satisfied = False # flip the result it's an inverse condition if condition.inverse: satisfied = not satisfied # check number of reports if necessary if satisfied and condition.num_reports is not None: if condition.auto_reapproving != False: # get number of reports already cleared try: entry = (session.query(AutoReapproval).filter( AutoReapproval.permalink == get_permalink(item)) .one()) previous_reports = entry.total_reports except NoResultFound: previous_reports = 0 total_reports = item.num_reports + previous_reports else: total_reports = item.num_reports satisfied = (total_reports >= condition.num_reports) elif satisfied and condition.num_reports is None: satisfied = (item.num_reports == 0) # check user conditions if necessary if satisfied: satisfied = check_user_conditions(item, condition) logging.debug(' User condition result = %s', satisfied) # make sure all sub-conditions are satisfied as well if satisfied: if condition.additional_conditions: logging.debug(' Checking sub-conditions:') for sub_condition in condition.additional_conditions: match = check_condition(item, sub_condition) if not match: satisfied = False break if condition.additional_conditions: logging.debug(' Sub-condition result = %s', satisfied) logging.debug(' Result = %s in %s', satisfied, elapsed_since(start_time)) return satisfied
def perform_action(subreddit, item, condition): """Performs the action for the condition(s). Also delivers the comment (if set) and creates an ActionLog entry. """ global r disclaimer = ('\n\n*I am a bot, and this action was performed ' 'automatically. Please [contact the moderators of this ' 'subreddit](http://www.reddit.com/message/compose?' 'to=%23'+item.subreddit.display_name+') if you have any ' 'questions or concerns.*') # build the comment if multiple conditions were matched if isinstance(condition, list): if any([c.comment for c in condition]): if condition[0].action == 'alert': verb = 'alerted' else: verb = condition[0].action+'d' comment = ('This has been '+verb+' for the following reasons:\n\n') for c in condition: if c.comment: comment += '* '+c.comment+'\n' post_comment(item, comment) # bit of a hack and only logs and uses attributes from first # condition matched, should find a better method condition = condition[0] else: comment = condition.comment # abort if it's an alert and we've already alerted on this item if condition.action == 'alert': try: session.query(ActionLog).filter( and_(ActionLog.permalink == get_permalink(item), ActionLog.action == 'alert')).one() return except NoResultFound: pass # perform the action if condition.action == 'remove': item.remove(condition.spam) elif condition.action == 'approve': item.approve() elif condition.action == 'set_flair': item.set_flair(condition.set_flair_text, condition.set_flair_class) # deliver the comment if set if comment: if condition.comment_method == 'comment': post_comment(item, comment+disclaimer) elif condition.comment_method == 'modmail': r.compose_message('#'+subreddit.name, 'AutoModerator condition matched', get_permalink(item)+'\n\n'+comment) elif condition.comment_method == 'message': r.compose_message(item.author.name, 'AutoModerator condition matched', get_permalink(item)+'\n\n'+comment+disclaimer) # log the action taken action_log = ActionLog() action_log.subreddit_id = subreddit.id action_log.user = item.author.name action_log.permalink = get_permalink(item) action_log.created_utc = datetime.utcfromtimestamp(item.created_utc) action_log.action_time = datetime.utcnow() action_log.action = condition.action action_log.matched_condition = condition.id if isinstance(item, reddit.objects.Submission): action_log.title = item.title action_log.url = item.url action_log.domain = item.domain logging.info(' /r/%s: %s submission "%s"', subreddit.name, condition.action, item.title.encode('ascii', 'ignore')) elif isinstance(item, reddit.objects.Comment): logging.info(' /r/%s: %s comment by user %s', subreddit.name, condition.action, item.author.name) session.add(action_log) session.commit()
def check_items(name, items, sr_dict, stop_time): """Checks the items generator for any matching conditions.""" item_count = 0 skip_count = 0 skip_subs = set() start_time = time() seen_subs = set() logging.info('Checking new %ss', name) try: for item in items: # skip any items in /new that have been approved if name == 'submission' and item.approved_by: continue item_time = datetime.utcfromtimestamp(item.created_utc) if item_time <= stop_time: break try: subreddit = sr_dict[item.subreddit.display_name.lower()] except KeyError: skip_count += 1 skip_subs.add(item.subreddit.display_name.lower()) continue conditions = (subreddit.conditions .filter(Condition.parent_id == None) .all()) conditions = filter_conditions(name, conditions) item_count += 1 if subreddit.name not in seen_subs: setattr(subreddit, 'last_'+name, item_time) seen_subs.add(subreddit.name) # check removal conditions, stop checking if any matched if check_conditions(subreddit, item, [c for c in conditions if c.action == 'remove']): continue # check set_flair conditions check_conditions(subreddit, item, [c for c in conditions if c.action == 'set_flair']) # check approval conditions check_conditions(subreddit, item, [c for c in conditions if c.action == 'approve']) # check alert conditions check_conditions(subreddit, item, [c for c in conditions if c.action == 'alert']) # if doing reports, check auto-reapproval if enabled if (name == 'report' and subreddit.auto_reapprove and item.approved_by is not None): try: # see if this item has already been auto-reapproved entry = (session.query(AutoReapproval).filter( AutoReapproval.permalink == get_permalink(item)) .one()) in_db = True except NoResultFound: entry = AutoReapproval() entry.subreddit_id = subreddit.id entry.permalink = get_permalink(item) entry.original_approver = item.approved_by.name entry.total_reports = 0 entry.first_approval_time = datetime.utcnow() in_db = False if (in_db or item.approved_by.name != cfg_file.get('reddit', 'username')): item.approve() entry.total_reports += item.num_reports entry.last_approval_time = datetime.utcnow() session.add(entry) session.commit() logging.info(' Re-approved %s', entry.permalink) session.commit() except Exception as e: logging.error(' ERROR: %s', e) session.rollback() logging.info(' Checked %s items, skipped %s items in %s (skips: %s)', item_count, skip_count, elapsed_since(start_time), ', '.join(skip_subs))