def edit_comment(posted: praw.models.Comment) -> None: """edits comment to reflect all users pinged""" body: str = "\n\n".join([f"Pinged members of {group} group.", "---", self._footer([("Subscribe to this group", f"Add yourself to group {group}", "addtogroup", f"{group}"), ("Unsubscribe from this group", f"Unsubscribe from group {group}", "unsubscribe", f"{group}"), ("Unsubscribe from all groups", f"Unsubscribe from all groups", "unsubscribe", "")])]) posted.edit(body)
def make_reply(c: praw.models.Comment) -> None: """ Post a reply to the comment c with BOT_OUTPUT """ # Post a reply if possible try: print("Posting Reply...") c.reply(BOT_OUTPUT) print("Reply Made") # If reddit returns an error except Exception as e: print("Reddit returned an error, no reply was made.")
def handle_mention(self, mention: praw.models.Comment) -> None: """handles parsing and reply""" self.logger.debug("Found potential mention") split: List[str] = mention.body.lower().split() for trigger, ideology in self.ideologies.items(): if trigger in split: self.logger.debug("ideology request found in %s", str(mention)) try: mention.reply(ideology.generate()) return except: self.logger.error("Reply failed to comment: %s", str(mention))
def getaliases(self, comment: praw.models.Comment, subcommands: list, config: dict): if self._botdb.ignore_user(comment.author.name): log.info("Ignoring comment by {}".format(comment.author.name)) return aliases = self._botdb.aliases() response = 'Current aliases:\n\n' for name, alias in sorted(aliases, key=lambda g: g[1]): response += ' * {} = {}\n'.format(alias, name) log.info('Responding to getalaises request with {} aliases'.format( len(aliases))) comment.reply(response)
def removalRequest(self, comment: praw.models.Comment, subcommands: list, config: dict): # if self._botdb.ignore_user(comment.author.name): # log.info("Ignoring comment by {}".format(comment.author.name)) # return # for now removals are limited to admins if not self._botdb.is_admin(comment.author.name): log.info('got remove command from non admin {}, ignoring.'.format( comment.author.name)) return if comment.is_root: log.error( 'removal requested on top-level comment {}, ignoring'.format( comment.id)) return botmessage: praw.models.Comment = comment.parent() try: # delete the post botmessage.delete() # attempt to unmark the parent as read if not botmessage.is_root: self._botdb.remove_comment(botmessage.parent) except boardgamegeek.exceptions.BoardGameGeekError as e: log.error('Error deleting comment {} by {}'.format( original.id, original.author.name)) return
def _process_comment(self, comment: praw.models.Comment): if comment.is_root or \ comment.author.name.lower().endswith("_bot") or \ comment.parent().author.name != config.username: return for reg in self.regexes: matches = reg.search(comment.body.lower()) if not matches: continue cmd = matches.group() attrname = cmd.split(" ")[0][1:] if not hasattr(self, attrname): continue logging.info("%s: %s" % (comment.author.name, cmd)) try: sess = self.Session() getattr(self, attrname)(sess, comment, *matches.groups()) except Exception as e: logging.error(e) sess.rollback() else: sess.commit() finally: sess.close()
def write_comment(self, response: Response, comment: praw.models.Comment) -> None: """log comment and writes to file""" with open('replied_comments.txt', 'a') as file: file.write(str(comment)) file.write(' ') if not random.randint(0, 10): self.logger.debug("Comment lucky, posting") try: comment.reply(str(response)) except praw.exceptions.APIException: self.logger.exception() else: self.logger.debug("Comment posted") else: self.logger.debug("Comment unlucky, not posted")
def getInfo(self, comment: praw.models.Comment, subcommands: list, config: dict, replyTo=None): '''Reply to comment with game information. If replyTo is given reply to original else reply to given comment.''' if self._botdb.ignore_user(comment.author.name): log.info("Ignoring comment by {}".format(comment.author.name)) return mode = None if len(subcommands) > 0 and subcommands[0].lower( ) in self.DISPLAY_MODES: mode = subcommands[0].lower() else: mode = self.DEFAULT_DISPLAY_MODE columns = subcommands[1:] if mode == 'tabular' else None sort = self.DEFAULT_SORT if self.NO_SORT_KEYWORD in subcommands: sort = None else: for sort_type in self.SORT_FUNCTIONS.keys(): if sort_type in subcommands: sort = sort_type break footer = '\n' + config['footer'] if 'footer' in config else '' bolded = self._getBoldedEntries(comment) response = None if bolded: response = self._getInfoResponseBody(comment, bolded, mode, columns, sort) if response: if replyTo: replyTo.reply(response + footer) else: comment.reply(response + footer) log.info('Replied to info request for comment {}'.format( comment.id)) else: log.warn('Did not find anything to reply to in comment {}'.format( comment.id))
def alias(self, comment: praw.models.Comment, subcommands: list, config: dict): '''add an alias to the database.''' if not self._botdb.is_admin(comment.author.name): log.info('got alias command from non admin {}, ignoring.'.format( comment.author.name)) return response = 'executing alias command.\n\n' # TODO: use bold fn for match in re.findall('\*\*([^\*]+)\*\*=\*\*([^\*]+)\*\*', comment.body): mess = 'Adding alias to database: "{}" = "{}"'.format( match[0], match[1]) log.info(mess) response += mess + '\n\n' self._botdb.add_alias(match[0], match[1]) comment.reply(response)
def form_comment(imgur: ImgurClient, comment: praw.models.Comment, formulae: List[str], contexts: List[str], hypercontexts: List[str]) -> None: """Makes and posts an appropriate reply to a comment :param imgur: The client to use for uploading :type imgur: ImgurClient :param comment: The comment to process and respond to :type comment: praw.models.Comment :param formulae: A list of formulae to render :type formulae: List[str] :param contexts: A list of contexts to use :type contexts: List[str] :param hypercontexts: A list of contexts to use for the hyperlinks :type hypercontexts: List[str] """ reply = "" try: for index, formula in enumerate(formulae): image_generation(formula).save("test.png") url = get_imgur(imgur, "test.png") try: if len(contexts[index]) == 0: raise ValueError("Context is too short") reply += f"{contexts[index]}\n\n" except Exception: pass try: if len(hypercontexts[index]) == 0: raise ValueError("Hypercontext is too short") reply += f"[{hypercontexts[index]}]({url})\n\n" except Exception: reply += f"[Click here for LaTeX render]({url})\n\n" reply += "^(This bot was made by) [u/Oryv](https://www.reddit.com/u/Oryv) ^(for /r/HomeworkHelp)" print("Replied to comment") comment.reply(reply) comment.mark_read() # This covers improper LaTeX syntax except RuntimeError as e: print(f"Something went wrong: {e}") comment.mark_read() # This covers being banned from a subreddit except requests.exceptions.HTTPError as e: print(f"Something went wrong: {e}") comment.mark_read() except Exception as e: print(f"Something went wrong: {e}")
def default_mention_reply(message: praw.models.Comment, choices: List[str]) -> dict: """Post a random selection of choices as a reply to a message. Args: message (Comment): The message to respond to. choices (List[str]): The reply body options to choose from. Returns: dict: The posted comment metadata as a datastore-able dict. """ body = f"{random.choice(choices)}" comment = message.reply(body) return _build_obj(comment)
def handleNewComment(comment: praw.models.Comment): discord_codes = extractURLS(comment, discord_invite_pattern) print(discord_codes) anyIllegal = False for code in discord_codes: data = getInviteData(code) print(data) features = data["guild"]["features"] if "DISCOVERABLE" not in features \ and "PARTNERED" not in features \ and "VERIFIED" not in features: anyIllegal = True break if anyIllegal: logging.info("Reporting " + comment.id) comment.report("Self promotion; not verified/partnered/discoverable (auto-detected /u/mlapibot)") try: url = "https://www.reddit.com/comments/{0}/comment/{1}/".format(comment.submission.id, comment.id) e = webHook.getEmbed("Reported Comment", comment.body, url, comment.author.name) webHook._sendWebhook(e) except: pass
def expandURLs(self, comment: praw.models.Comment, subcommands: list, config: dict): if self._botdb.ignore_user(comment.author.name): log.info("Ignoring comment by {}".format(comment.author.name)) return replyTo = None mode = None if len(subcommands) > 0 and subcommands[0].lower( ) in self.DISPLAY_MODES: mode = subcommands[0].lower() else: mode = self.DEFAULT_DISPLAY_MODE footer = '\n' + config['footer'] if 'footer' in config else '' body = comment.body urls = [ ('#' + id) for id in re.findall('boardgamegeek.com/(?:boardgame|thing)/(\d+)', body, flags=re.UNICODE) ] response = self._getInfoResponseBody(comment, urls, mode) log.error('footer {} ({})'.format(footer, type(footer))) if response: if replyTo: replyTo.reply(response + footer) else: comment.reply(response + footer) log.info('Replied to info request for comment {}'.format( comment.id)) else: log.warn('Did not find anything to reply to in comment {}'.format( comment.id))
def check_comment_depth(comment: praw.models.Comment, max_depth=3) -> bool: """ Check if comment is in a allowed depth range :param comment: :class:`praw.models.Comment` to count the depth of :param max_depth: Maximum allowed depth :return: True if comment is in depth range between 0 and max_depth """ count = 0 while not comment.is_root: count += 1 if count > max_depth: return False comment = comment.parent() return True
def remediate_comment( self, comment: praw.models.Comment, to_thread: praw.models.Submission, ): """Reply to a comment and direct them to the new thread Args: comment (praw.models.Comment): Comment to redirect to_thread (praw.models.Submission): Thread to redirect them to """ msg = ( f"Hi u/{comment.author}, I created a " f"[new Entering & Transitioning thread]({to_thread.permalink}). " "Since you haven't received any replies yet, " "please feel free to resubmit your comment in the new thread.") reply = comment.reply(msg) reply.mod.distinguish(how="yes")
def getParentInfo(self, comment: praw.models.Comment, subcommands: list, config: dict): '''Allows others to call the bot to getInfo for parent posts.''' if self._botdb.ignore_user(comment.author.name): log.info("Ignoring comment by {}".format(comment.author.name)) return log.debug('Got getParentInfo comment in id {}'.format(comment.id)) if comment.is_root: # error here - this comment should be in response to a u/r2d8 comment. log.info('Got a repair comment as root, ignoring.') return parent = comment.parent() self.getInfo(parent, subcommands=subcommands, config=config, replyTo=comment)
def xyzzy(self, comment: praw.models.Comment, subcommands: list, config: dict): comment.reply('Nothing happens.')
def repairComment(self, comment: praw.models.Comment, subcommands: list, config: dict): '''Look for maps from missed game names to actual game names. If found repair orginal comment.''' if self._botdb.ignore_user(comment.author.name): log.info("Ignoring comment by {}".format(comment.author.name)) return # # The repair is done by replacing the new games names with the old (wrong) # games names in the original /u/r2d8 response, then recreating the entire # post by regenerating it with the new (fixed) bolded game names. The just replacing # the orginal response with the new one. # log.debug('Got repair response, id {}'.format(comment.id)) if comment.is_root: # error here - this comment should be in response to a u/r2d8 comment. log.info('Got a repair comment as root, ignoring.') return parent = comment.parent() if parent.author.name != self._botname: log.info( 'Parent of repair comment is not authored by the bot, ignoring.' ) return # Look for patterns of **something**=**somethingelse**. This line creates a dict # of something: somethingelse for each one pattern found. repairs = { match[0]: match[1] for match in re.findall('\*\*([^\*]+)\*\*=\*\*([^\*]+)\*\*', comment.body) } pbody = parent.body for wrongName, repairedName in repairs.items(): # check to see if it's actually a game. log.info('Repairing {} --> {}'.format(wrongName, repairedName)) alias = self._botdb.get_name_from_alias(repairedName) tmp_name = alias if alias else repairedName tmp_game = self._bggQueryGame( tmp_name) # with caching it's ok to check twice if tmp_game: # In the parent body we want to replace [NAME](http://... with **NAME**(http:// pbody = pbody.replace('[' + wrongName + ']', '**' + tmp_name + '**') else: log.info( '{} seems to not be a game name according to BGG, ignoring.' .format(tmp_name)) # Now re-bold the not found strings so they are re-searched or re-added to the not found list. for nf in re.findall( '\[([\w|\s]+)]\(http://boardgamegeek.com/geeksearch.php', pbody): pbody += ' **{}**'.format(nf) # now re-insert the original command to retain the mode. grandparent = parent.parent() modes = list() if not grandparent: log.error('Cannot find original GP post. Assuming normal mode.') else: modes = re.findall('[getparent|get]info\s(\w+)', grandparent.body) targetmode = modes[0] if modes else self.DEFAULT_DISPLAY_MODE parent = parent.edit(pbody) bolded = self._getBoldedEntries(comment) new_reply = self._getInfoResponseBody(parent, bolded, targetmode) # should check for Editiable class somehow here. GTL log.debug('Replacing bot comment {} with: {}'.format( parent.id, new_reply)) parent.edit(new_reply)
def edit_comment(posted: praw.models.Comment) -> None: """edits comment to reflect all users pinged""" body: str = "\n\n".join([f"Pinged members of {group} group.", "---", self._footer([("Request to be added to this group", "addtogroup", group)])]) posted.edit(body)