.. _call_and_response_bot:

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:

.. code-block:: python

    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.

.. code-block:: python

    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.

.. code-block:: python

    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.

.. code-block:: python

    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.

.. code-block:: python

    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:

.. code-block:: python

    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!

.. code-block:: python

    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.
