A Simple “Call and Response” Bot

A common use case for the PRAW library is writing bots that monitor the /all/comments feed for comments meeting particular criteria, triggering an automated response of some kind. To build a bot of this kind, we’ll need three things:

  1. An effective way to monitor new comments.
  2. A way to evaluate if a comment merits a response.
  3. A way to trigger the desired action.

We’ll need to provide the latter two parts, but PRAW contains an excellent convenience function for the first part: praw.helpers.comment_stream, which yields new comments in the order they are authored while handling rate limiting for us. We can constrain our attention to comments generated by a single subreddit, or monitor all public comments on reddit by passing in the subreddit name “all”.

Using this helper function effectively reduces most bots to something like the following:

import praw

UA = 'my user-agent string. Contact me at /u/MyUsername or my@email.com'
r = praw.Reddit(UA)
r.login()

for c in praw.helpers.comment_stream(r, 'all'):
    if check_condition(c):
        bot_action(c)

Now all we need to do to transform this into a working bot is to provide a check_condition function and a bot_action function.

Working demo: ListFixerBot

Let’s say we wanted a bot to correct bad list formatting with lazily-generated but hopefully improved reddit markdown (snudown). Reddit markdown recognizes lists when a numbered item is numbered like ‘1.’, ‘2.’ etc. Let’s try to fix lists where the comment author replaced dots with parens. Our goal is to find comments containing lists that look like:

  1. Item one
  2. Item two
  3. Item three

or even

  1. Item one 2) Item two 3) Item three

and modify them so they will appear as:

  1. Item one
  2. Item two
  3. Item three

First, let’s write a function to determine if a comment matches what we’re looking for. Following the template presented earlier, let’s just call this function check_condition so we can drop it directly into our existing code.

def check_condition(c):
    text = c.body
    tokens = text.split()
    if "1)" in tokens and "2)" in tokens:
        return True

We can use regular expressions to replace these parens with dots, and even add preceding line breaks.

fixed = re.sub(r'([0-9]+)(\))', r'\n\n\1.', c.body)

This expression add two newline characters before each list item to break up list items that aren’t separated by newlines (which they need to be). Reddit markdown will treat this as a single line between items. Also, markdown doesn’t care what number we use when enumerating a list, so we can just use “1.” for each list item and let the markdown interpreter figure out what the correct number should be.

We’ll wrap this regular expression with our bot_action function to have the bot perform this operation only on the comments that passed the check_condition filter. If we include a “verbose” parameter, we can easily choose whether or not the function will output relevant text to the console by default. Keep in mind: reddit supports unicode text in comments.

def bot_action(c, verbose=True, respond=False):
    fixed = re.sub(r'(\n?)([0-9]+)(\))', r'\n\n\2.', c.body)

    if verbose:
        print c.body.encode("UTF-8")
        print "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"
        print fixed.encode("UTF-8")

We can similarly define an optional input parameter to choose whether or not the bot will actually respond to people with the corrected markdown. NB: If we want our bot to respond, it will need to be logged in. You can create a new account specific to the bot (recommended) or just use an existing login if you have one.

def bot_action(c, verbose=True, respond=False):
    fixed = re.sub(r'(\n?)([0-9]+)(\))', r'\n\n\2.', c.body)

    if verbose:
        print c.body.encode("UTF-8")
        print "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"
        print fixed.encode("UTF-8")

    if respond:
        head = "Hi! Let me try to beautify the list in your comment:\n\n"
        c.reply(head + fixed)

It’s generally considered good practice to create a subreddit whose name matches the name of your bot to centralize discussion, questions, and feature requests relating to your bot. Referencing this subreddit in your bot’s comments is a good way to inform people how best to provide feedback.

Our final bot_action function looks like this:

def bot_action(c, verbose=True, respond=False):
    fixed = re.sub(r'(\n?)([0-9]+)(\))', r'\n\n\2.', c.body)

    if verbose:
        print c.body.encode("UTF-8")
        print "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"
        print fixed.encode("UTF-8")

    if respond:
        head = "Hi! Let me try to beautify the list in  your comment:\n\n"
        tail = "\n\nI am a bot. You can provide feedback in my subreddit: /r/ListFormatFixer"
        c.reply(head + fixed + tail)

Here’s our completed bot!

import re

def check_condition(c):
    text = c.body
    tokens = text.split()
    if "1)" in tokens and "2)" in tokens:
        return True

def bot_action(c, verbose=True, respond=False):
    fixed = re.sub(r'(\n?)([0-9]+)(\))', r'\n\n\2.', c.body)

    if verbose:
        print c.body.encode("UTF-8")
        print "\n\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"
        print fixed.encode("UTF-8")

    if respond:
        head = "Hi! Let me try to beautify the list in  your comment:\n\n"
        tail = "\n\nI am a bot. You can provide feedback in my subreddit: /r/ListFormatFixer"
        c.reply(head + fixed + tail)

if __name__ is '__main__':
    import praw
    r = praw.Reddit(UA)

    # Provide a descriptive user-agent string. Explain what your bot does, reference
    # yourself as the author, and offer some preferred contact method. A reddit
    # username is sufficient, but nothing wrong with adding an email in here.
    UA = 'ListFormatFixer praw demo, Created by /u/shaggorama'

    # If you want the bot to be able to respond to people, you will need to login.
    # It is strongly recommended you login with oAuth
    # http://praw.readthedocs.io/en/stable/pages/oauth.html

    # NB: This login method is being deprecated soon
    r.login()

    for c in praw.helpers.comment_stream(r, 'all'):
        if check_condition(c):
            # set 'respond=True' to activate bot responses. Must be logged in.
            bot_action(c, respond=False)

Keep in mind: bots of this kind are often perceived as annoying and quickly get banned from many subreddits. If/when your bot gets banned, don’t take it personally.