Thursday, March 23, 2023
HomePythonMaking a Reddit + Fb Messenger Bot

Making a Reddit + Fb Messenger Bot


Hello guys! I haven’t been programming lots currently due to exams. Nonetheless, on the previous weekend I managed to come up with my laptop computer and crank out one thing helpful. It was a Fb messenger bot which servers you recent memes, motivational posts, jokes and bathe ideas. It was the primary time I had delved into bot creation. On this submit I’ll train you many of the stuff you’ll want to know in an effort to get your bot off the bottom.

To begin with some screenshots of the ultimate product:

Img 1
Img 2
Img 2
Img 2

Tech Stack

We can be making use of the next:

  • Flask framework for coding up the backend as it’s light-weight and permits us to give attention to the logic as an alternative of the folder construction.
  • Heroku – For internet hosting our code on-line without cost
  • Reddit – As an information supply as a result of it get’s new posts each minute

1. Getting issues prepared

Making a Reddit app

We can be utilizing Fb, Heroku and Reddit. Firstly, just be sure you have an account on all three of those providers. Subsequent you’ll want to create a Reddit software on this hyperlink.

Img

Within the above picture you’ll be able to already see the “motivation” app which I’ve created. Click on on “create one other app” and comply with the on-screen directions.

Img

The about and redirect url won’t be used therefore it’s okay to depart them clean. For manufacturing apps it’s higher to place in one thing associated to your challenge in order that when you begin making a variety of requests and reddit begins to note it they will verify the about web page of you app and act in a extra knowledgeable method.

So now that your app is created you’ll want to save the ‘client_id’ and ‘client_secret’ in a protected place.

Img

One a part of our challenge is finished. Now we have to setup the bottom for our Heroku app.

Creating an App on Heroku

Go to this dashboard url and create a brand new software.

Img

On the following web page give your software a singular title.

Img

From the following web page click on on “Heroku CLI” and obtain the newest Heroku CLI in your working system. Comply with the on-screen set up directions and are available again as soon as it has been put in.

Img

Making a primary Python software

The beneath code is taken from Konstantinos Tsaprailis’s web site.

from flask import Flask, request
import json
import requests

app = Flask(__name__)

# This must be crammed with the Web page Entry Token that can be offered
# by the Fb App that can be created.
PAT = ''

@app.route("https://yasoob.me/", strategies=['GET'])
def handle_verification():
    print "Dealing with Verification."
    if request.args.get('hub.verify_token', '') == 'my_voice_is_my_password_verify_me':
        print "Verification profitable!"
        return request.args.get('hub.problem', '')
    else:
        print "Verification failed!"
        return 'Error, incorrect validation token'

@app.route("https://yasoob.me/", strategies=['POST'])
def handle_messages():
    print "Dealing with Messages"
    payload = request.get_data()
    print payload
    for sender, message in messaging_events(payload):
        print "Incoming from %s: %s" % (sender, message)
        send_message(PAT, sender, message)
    return "okay"

def messaging_events(payload):
    """Generate tuples of (sender_id, message_text) from the
    offered payload.
    """
    information = json.masses(payload)
    messaging_events = information["entry"][0]["messaging"]
    for occasion in messaging_events:
        if "message" in occasion and "textual content" in occasion["message"]:
            yield occasion["sender"]["id"], occasion["message"]["text"].encode('unicode_escape')
        else:
            yield occasion["sender"]["id"], "I am unable to echo this"


def send_message(token, recipient, textual content):
    """Ship the message textual content to recipient with id recipient.
    """

    r = requests.submit("https://graph.fb.com/v2.6/me/messages",
        params={"access_token": token},
        information=json.dumps({
            "recipient": {"id": recipient},
            "message": {"textual content": textual content.decode('unicode_escape')}
        }),
        headers={'Content material-type': 'software/json'})
    if r.status_code != requests.codes.okay:
        print r.textual content

if __name__ == '__main__':
    app.run()

We can be modifying the file based on our wants. So mainly a Fb bot works like this:

  1. Fb sends a request to our server every time a consumer messages our web page on Fb.
  2. We reply to the Fb’s request and retailer the id of the consumer and the message which was despatched to our web page.
  3. We reply to consumer’s message via Graph API utilizing the saved consumer id and message id.

An in depth breakdown of the above code is on the market of this web site. On this submit I’ll primarily be specializing in the Reddit integration and the way to use the Postgres Database on Heroku.

Earlier than shifting additional let’s deploy the above Python code onto Heroku. For that you must create a neighborhood Git repository. Comply with the next steps:

$ mkdir messenger-bot
$ cd messenger-bot
$ contact necessities.txt app.py Procfile

Execute the above instructions in a terminal and put the above Python code into the app.py file. Put the next into Procfile:

net: gunicorn app:app 

Now we have to inform Heroku which Python libraries our app might want to perform correctly. These libraries will have to be listed within the necessities.txt file. I’m going to fast-forward a bit over right here and easily copy the necessities from this submit. Put the next strains into necessities.txt file and you need to be good to go for now.

click on==6.6
Flask==0.11
gunicorn==19.6.0
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
requests==2.10.0
Werkzeug==0.11.10

Run the next command within the terminal and it’s best to get the same output:

$ ls
Procfile      app.py     necessities.txt

Now we’re able to create a Git repository which may then be pushed onto Heroku servers. We are going to perform the next steps now:

  • Login into Heroku
  • Create a brand new git repository
  • commit every part into the brand new repo
  • push the repo onto Heroku

The instructions required to realize this are listed beneath:

$ heroku login
$ git init
$ heroku git:distant -a 
$ git commit -am "Preliminary commit"

$ git push heroku grasp
...
distant: https://.herokuapp.com/ deployed to Heroku
...

$ heroku config:set WEB_CONCURRENCY=3

Save the url which is outputted above after distant . It’s the url of your Heroku app. We are going to  want it within the subsequent step once we create a Fb app.

Making a Fb App

Firstly we’d like a Fb web page. It’s a requirement by Fb to complement each app with a related web page.

Now we have to register a brand new app. Go to this app creation web page and comply with the directions beneath.

Img

Img

Img

Img

Img

Img

Img

Now head over to your app.py file and change the PAT string on line 9 with the Web page Entry Token we saved above.

Commit every part and push the code to Heroku.

$ git commit -am "Added within the PAT"
$ git push heroku grasp

Now when you go to the Fb web page and ship a message onto that web page you’ll get your personal message as a reply from the web page. This reveals that every part we now have finished to this point is working. If one thing doesn’t work verify your Heroku logs which will provide you with some clue about what goes incorrect. Later, a fast Google search will assist you resolve the difficulty. You may entry the logs like this:

$ heroku logs -t -a

Be aware: Solely your msgs can be replied by the Fb web page. If another random consumer messages the web page his messages won’t be replied by the bot as a result of the bot is presently not authorised by Fb. Nonetheless if you wish to allow a few customers to check your app you’ll be able to add them as testers. You are able to do so by going to your Fb app’s developer web page and following the onscreen directions.

Getting information from Reddit

We can be utilizing information from the next subreddits:

To begin with let’s set up Reddit’s Python library “praw”. It will possibly simply be finished by typing the next directions within the terminal:

$ pip set up praw

Now let’s take a look at some Reddit goodness in a Python shell. I adopted the docs which clearly present the way to entry Reddit and the way to entry a subreddit. Now’s one of the best time to seize the “_clientid” and “_clientsecret” which we created within the first a part of this submit.

$ python
Python 2.7.13 (default, Dec 17 2016, 23:03:43) 
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Sort "assist", "copyright", "credit" or "license" for extra info.
>>> import praw
>>> reddit = praw.Reddit(client_id='**********',
... client_secret="*****************",
... user_agent="my consumer agent")

>>> 
>>> submissions = checklist(reddit.subreddit("GetMotivated").scorching(restrict=None))
>>> submissions[-4].title
u'[Video] Hello, Stranger.'

Be aware: Don’t overlook so as to add in your personal client_id and client_secret rather than ****

Let’s focus on the necessary bits right here. I’m utilizing restrict=None as a result of I need to get again as many posts as I can. Initially this appears like an overkill however you’ll shortly see that when a consumer begins utilizing the Fb bot fairly often we’ll run out of recent posts if we restrict ourselves to 10 or 20 posts. A further constraint which we’ll add is that we’ll solely use the picture posts from GetMotivated and Memes and solely textual content posts from Jokes and ShowerThoughts. Attributable to this constraint just one or two posts from high 10 scorching posts could be helpful to us as a result of a variety of video submissions are additionally finished to GetMotivated.

Now that we all know the way to entry Reddit utilizing the Python library we will go forward and combine it into our app.py.

Firstly add some extra libraries into our necessities.txt in order that it appears one thing like this:

$ cat necessities.txt
click on==6.6
Flask==0.11
gunicorn==19.6.0
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
requests==2.10.0
Werkzeug==0.11.10
flask-sqlalchemy
psycopg2
praw

Now if we solely wished to ship the consumer a picture or textual content taken from reddit, it wouldn’t have been very troublesome. Within the “_sendmessage” perform we might have finished one thing like this:

import praw
...

def send_message(token, recipient, textual content):
    """Ship the message textual content to recipient with id recipient.
    """
    if "meme" in textual content.decrease():
        subreddit_name = "memes"
    elif "bathe" in textual content.decrease():
        subreddit_name = "Showerthoughts"
    elif "joke" in textual content.decrease():
        subreddit_name = "Jokes"
    else:
        subreddit_name = "GetMotivated"
    ....

    if subreddit_name == "Showerthoughts":
        for submission in reddit.subreddit(subreddit_name).scorching(restrict=None):
            payload = submission.url
            break
    ...
    
    r = requests.submit("https://graph.fb.com/v2.6/me/messages",
            params={"access_token": token},
            information=json.dumps({
                "recipient": {"id": recipient},
                "message": {"attachment": {
                              "sort": "picture",
                              "payload": {
                                "url": payload
                              }}
            }),
            headers={'Content material-type': 'software/json'})
    ...

However there’s one problem with this method. How will we all know whether or not a consumer has been despatched a selected picture/textual content or not? We’d like some sort of id for every picture/textual content we ship the consumer in order that we don’t ship the identical submit twice. With the intention to resolve this problem we’re going to take some assist of Postgresql and the reddit posts id (Each submit on reddit has a particular id).

We’re going to use a Many-to-Many relation. There can be two tables:

Let’s first outline them in our code after which I’ll clarify the way it will work:

from flask_sqlalchemy import SQLAlchemy

...
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
db = SQLAlchemy(app)

...
relationship_table=db.Desk('relationship_table',                            
    db.Column('user_id', db.Integer,db.ForeignKey('customers.id'), nullable=False),
    db.Column('post_id',db.Integer,db.ForeignKey('posts.id'),nullable=False),
    db.PrimaryKeyConstraint('user_id', 'post_id') )
 
class Customers(db.Mannequin):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255),nullable=False)
    posts=db.relationship('Posts', secondary=relationship_table, backref="customers" )  

    def __init__(self, title):
        self.title = title
 
class Posts(db.Mannequin):
    id=db.Column(db.Integer, primary_key=True)
    title=db.Column(db.String, distinctive=True, nullable=False)
    url=db.Column(db.String, nullable=False)

    def __init__(self, title, url):
        self.title = title
        self.url = url

So the consumer desk has two fields. The title would be the id despatched with the Fb Messenger Webhook request. The posts can be linked to the opposite desk, “Posts”. The Posts desk has title and url area. “title” can be populated by the reddit submission id and the url can be populated by the url of that submit. We don’t really want to have the “url” area. I can be utilizing it for another makes use of sooner or later therefore I included it within the code.

So now the best way our remaining code will work is that this:

  • We request a listing of posts from a selected subreddit. The next code:

    reddit.subreddit(subreddit_name).scorching(restrict=None)
    

    returns a generator so we don’t want to fret about reminiscence

  • We are going to verify whether or not the actual submit has already been despatched to the consumer up to now or not

  • If the submit has been despatched up to now we’ll proceed requesting extra posts from Reddit till we discover a recent submit

  • If the submit has not been despatched to the consumer, we ship the submit and escape of the loop

So the ultimate code of the app.py file is that this:

from flask import Flask, request
import json
import requests
from flask_sqlalchemy import SQLAlchemy
import os
import praw

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
db = SQLAlchemy(app)
reddit = praw.Reddit(client_id='*************',
                     client_secret="****************",
                     user_agent="my consumer agent")

# This must be crammed with the Web page Entry Token that can be offered
# by the Fb App that can be created.
PAT = '*********************************************'

quick_replies_list = [{
    "content_type":"text",
    "title":"Meme",
    "payload":"meme",
},
{
    "content_type":"text",
    "title":"Motivation",
    "payload":"motivation",
},
{
    "content_type":"text",
    "title":"Shower Thought",
    "payload":"Shower_Thought",
},
{
    "content_type":"text",
    "title":"Jokes",
    "payload":"Jokes",
}
]
@app.route("https://yasoob.me/", strategies=['GET'])
def handle_verification():
    print "Dealing with Verification."
    if request.args.get('hub.verify_token', '') == 'my_voice_is_my_password_verify_me':
        print "Verification profitable!"
        return request.args.get('hub.problem', '')
    else:
        print "Verification failed!"
        return 'Error, incorrect validation token'

@app.route("https://yasoob.me/", strategies=['POST'])
def handle_messages():
    print "Dealing with Messages"
    payload = request.get_data()
    print payload
    for sender, message in messaging_events(payload):
        print "Incoming from %s: %s" % (sender, message)
        send_message(PAT, sender, message)
    return "okay"

def messaging_events(payload):
    """Generate tuples of (sender_id, message_text) from the
    offered payload.
    """
    information = json.masses(payload)
    messaging_events = information["entry"][0]["messaging"]
    for occasion in messaging_events:
        if "message" in occasion and "textual content" in occasion["message"]:
            yield occasion["sender"]["id"], occasion["message"]["text"].encode('unicode_escape')
        else:
            yield occasion["sender"]["id"], "I am unable to echo this"


def send_message(token, recipient, textual content):
    """Ship the message textual content to recipient with id recipient.
    """
    if "meme" in textual content.decrease():
        subreddit_name = "memes"
    elif "bathe" in textual content.decrease():
        subreddit_name = "Showerthoughts"
    elif "joke" in textual content.decrease():
        subreddit_name = "Jokes"
    else:
        subreddit_name = "GetMotivated"

    myUser = get_or_create(db.session, Customers, title=recipient)

    if subreddit_name == "Showerthoughts":
        for submission in reddit.subreddit(subreddit_name).scorching(restrict=None):
            if (submission.is_self == True):
                query_result = Posts.question.filter(Posts.title == submission.id).first()
                if query_result is None:
                    myPost = Posts(submission.id, submission.title)
                    myUser.posts.append(myPost)
                    db.session.commit()
                    payload = submission.title
                    break
                elif myUser not in query_result.customers:
                    myUser.posts.append(query_result)
                    db.session.commit()
                    payload = submission.title
                    break
                else:
                    proceed  

        r = requests.submit("https://graph.fb.com/v2.6/me/messages",
            params={"access_token": token},
            information=json.dumps({
                "recipient": {"id": recipient},
                "message": {"textual content": payload,
                            "quick_replies":quick_replies_list}
            }),
            headers={'Content material-type': 'software/json'})
    
    elif subreddit_name == "Jokes":
        for submission in reddit.subreddit(subreddit_name).scorching(restrict=None):
            if ((submission.is_self == True) and ( submission.link_flair_text is None)):
                query_result = Posts.question.filter(Posts.title == submission.id).first()
                if query_result is None:
                    myPost = Posts(submission.id, submission.title)
                    myUser.posts.append(myPost)
                    db.session.commit()
                    payload = submission.title
                    payload_text = submission.selftext
                    break
                elif myUser not in query_result.customers:
                    myUser.posts.append(query_result)
                    db.session.commit()
                    payload = submission.title
                    payload_text = submission.selftext
                    break
                else:
                    proceed  

        r = requests.submit("https://graph.fb.com/v2.6/me/messages",
            params={"access_token": token},
            information=json.dumps({
                "recipient": {"id": recipient},
                "message": {"textual content": payload}
            }),
            headers={'Content material-type': 'software/json'})

        r = requests.submit("https://graph.fb.com/v2.6/me/messages",
            params={"access_token": token},
            information=json.dumps({
                "recipient": {"id": recipient},
                "message": {"textual content": payload_text,
                            "quick_replies":quick_replies_list}
            }),
            headers={'Content material-type': 'software/json'})
        
    else:
        payload = "http://imgur.com/WeyNGtQ.jpg"
        for submission in reddit.subreddit(subreddit_name).scorching(restrict=None):
            if (submission.link_flair_css_class == 'picture') or ((submission.is_self != True) and ((".jpg" in submission.url) or (".png" in submission.url))):
                query_result = Posts.question.filter(Posts.title == submission.id).first()
                if query_result is None:
                    myPost = Posts(submission.id, submission.url)
                    myUser.posts.append(myPost)
                    db.session.commit()
                    payload = submission.url
                    break
                elif myUser not in query_result.customers:
                    myUser.posts.append(query_result)
                    db.session.commit()
                    payload = submission.url
                    break
                else:
                    proceed

        r = requests.submit("https://graph.fb.com/v2.6/me/messages",
            params={"access_token": token},
            information=json.dumps({
                "recipient": {"id": recipient},
                "message": {"attachment": {
                              "sort": "picture",
                              "payload": {
                                "url": payload
                              }},
                              "quick_replies":quick_replies_list}
            }),
            headers={'Content material-type': 'software/json'})

    if r.status_code != requests.codes.okay:
        print r.textual content

def get_or_create(session, mannequin, **kwargs):
    occasion = session.question(mannequin).filter_by(**kwargs).first()
    if occasion:
        return occasion
    else:
        occasion = mannequin(**kwargs)
        session.add(occasion)
        session.commit()
        return occasion

relationship_table=db.Desk('relationship_table',                            
    db.Column('user_id', db.Integer,db.ForeignKey('customers.id'), nullable=False),
    db.Column('post_id',db.Integer,db.ForeignKey('posts.id'),nullable=False),
    db.PrimaryKeyConstraint('user_id', 'post_id') )
 
class Customers(db.Mannequin):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(255),nullable=False)
    posts=db.relationship('Posts', secondary=relationship_table, backref="customers" )  

    def __init__(self, title=None):
        self.title = title
 
class Posts(db.Mannequin):
    id=db.Column(db.Integer, primary_key=True)
    title=db.Column(db.String, distinctive=True, nullable=False)
    url=db.Column(db.String, nullable=False)

    def __init__(self, title=None, url=None):
        self.title = title
        self.url = url

if __name__ == '__main__':
    app.run()

So put this code into app.py file and ship it to Heroku.

$ git commit -am "Up to date the code with Reddit characteristic"
$ git push heroku grasp

One last item remains to be remaining. We have to inform Heroku that we’ll be utilizing the database. It’s easy. Simply problem the next command within the terminal:

$ heroku addons:create heroku-postgresql:hobby-dev --app <app_name>

This can create a free interest database which is sufficient for our challenge. Now we solely have to initialise the database with the proper tables. With the intention to try this we first have to run the Python shell on our Heroku server:

$ heroku run python

Now within the Python shell sort the next instructions:

>>> from app import db
>>> db.create_all()

So now our challenge is full. Congrats!

Let me focus on some attention-grabbing options of the code. Firstly, I’m making use of the “quick-replies” characteristic of Fb Messenger Bot API. This permits us to ship some pre-formatted inputs which the consumer can shortly choose. They may look one thing like this:

Img

It’s simple to show these fast replies to the consumer. With each submit request to the Fb graph API we ship some extra information:

quick_replies_list = [{
 "content_type":"text",
 "title":"Meme",
 "payload":"meme",
},
{
 "content_type":"text",
 "title":"Motivation",
 "payload":"motivation",
},
{
 "content_type":"text",
 "title":"Shower Thought",
 "payload":"Shower_Thought",
},
{
 "content_type":"text",
 "title":"Jokes",
 "payload":"Jokes",
}]

One other attention-grabbing characteristic of the code is how we decide whether or not a submit is a textual content, picture or a video submit. Within the GetMotivated subreddit some photos don’t have a “.jpg” or “.png” of their url so we depend on

submission.link_flair_css_class == 'picture'

This manner we’re in a position to choose even these posts which don’t have a identified picture extension within the url.

You may need seen this little bit of code within the app.py file:

payload = "http://imgur.com/WeyNGtQ.jpg"

It makes certain that if no new posts are discovered for a selected consumer (each subreddit has a most variety of “scorching” posts) we now have not less than one thing to return. In any other case we’ll get a variable undeclared error.

Create if the Consumer doesn’t exist:

The next perform checks whether or not a consumer with the actual title exists or not. If it exists it selects that consumer from the db and returns it. In case it doesn’t exist (consumer), it creates it after which returns that newly created consumer:

myUser = get_or_create(db.session, Customers, title=recipient)
...

def get_or_create(session, mannequin, **kwargs):
    occasion = session.question(mannequin).filter_by(**kwargs).first()
    if occasion:
        return occasion
    else:
        occasion = mannequin(**kwargs)
        session.add(occasion)
        session.commit()
        return occasion

I hope you guys loved the submit. Please remark beneath you probably have any questions. I’m additionally beginning premium promoting on the weblog. This can both be within the type of sponsored posts or weblog sponsorship for a selected time. I’m nonetheless fleshing out the main points. If your organization works with Python and needs to succeed in out to potential clients, please e mail me on yasoob (at) gmail.com.

Supply: You may get the code from GitHub as properly.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments