Что нового
  • Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

/dev/null Не Нужен: Пишем Бота На Python, Который Будет Присылать Свежие Мемасики

Sascha

Заместитель Администратора
Команда форума
Администратор
Регистрация
9 Май 2015
Сообщения
1,071
Баллы
155
Возраст
52
Сегодня мы создадим бота для Facebook Messenger, который будет присылать нам свежие мемы, мотивационные сообщения и шутки. В этой статье есть большая часть информации, которую нужно знать для создания своего бота.

Вот так будет выглядеть финальная версия нашего приложения:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Технический стек


Нам понадобятся:

Создание приложения Reddit


Мы будем использовать Facebook, Heroku и Reddit. Во-первых, убедитесь, что у вас есть учетная запись в каждом из этих сервисов. Затем

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

приложение Reddit:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Нажмите кнопку «create an app…» и следуйте инструкциям на экране:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Два нижних поля не будут использоваться, поэтому оставьте их пустыми. В поле с описанием приложения лучше написать что-то связанное с проектом. Когда приложение начнёт делать много запросов, представители Reddit могут проверить, для чего оно.

Теперь, когда ваше приложение создано, вам нужно сохранить client_id и client_secret в безопасном месте:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Первая часть нашего проекта выполнена. Теперь нам нужно настроить базу для нашего приложения Heroku.

Создание приложения на Heroku


Перейдите на сайт

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

и создайте новое приложение:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


На следующей странице дайте своему приложению уникальное имя. Нажмите «Heroku CLI» и загрузите последнюю версию интерфейса командной строки Heroku для вашей операционной системы. Следуйте инструкциям на экране и вернитесь, как только интерфейс будет загружен.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Создание базового приложения Python


Приведенный ниже код взят с

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

.

from flask import Flask, request
import json
import requests

app = Flask(__name__)

# This needs to be filled with the Page Access Token that will be provided
# by the Facebook App that will be created.
PAT = ''

@app.route('/', methods=['GET'])
def handle_verification():
print "Handling Verification."
if request.args.get('hub.verify_token', '') == 'my_voice_is_my_password_verify_me':
print "Verification successful!"
return request.args.get('hub.challenge', '')
else:
print "Verification failed!"
return 'Error, wrong validation token'

@app.route('/', methods=['POST'])
def handle_messages():
print "Handling 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 "ok"

def messaging_events(payload):
"""Generate tuples of (sender_id, message_text) from the
provided payload.
"""
data = json.loads(payload)
messaging_events = data["entry"][0]["messaging"]
for event in messaging_events:
if "message" in event and "text" in event["message"]:
yield event["sender"]["id"], event["message"]["text"].encode('unicode_escape')
else:
yield event["sender"]["id"], "I can't echo this"


def send_message(token, recipient, text):
"""Send the message text to recipient with id recipient.
"""

r = requests.post("https://graph.facebook.com/v2.6/me/messages",
params={"access_token": token},
data=json.dumps({
"recipient": {"id": recipient},
"message": {"text": text.decode('unicode_escape')}
}),
headers={'Content-type': 'application/json'})
if r.status_code != requests.codes.ok:
print r.text

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

Мы будем редактировать файл в соответствии с нашими потребностями. Бот Facebook будет работать следующим образом:

  1. Facebook отправляет запрос на наш сервер всякий раз, когда пользователь пишет сообщение на нашей странице Facebook.
  2. Мы отвечаем на запрос Facebook и сохраняем идентификатор пользователя и сообщение, которое было отправлено на нашу страницу.
  3. Мы отвечаем на сообщение пользователя через Graph API, используя сохранённый идентификатор пользователя и идентификатор сообщения.

Подробный разбор приведенного выше кода доступен на

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

Константиноса Цапраилиса. В этом посте я сосредоточусь главным образом на интеграции Reddit и использовании базы данных Postgres на Heroku.

Прежде чем двигаться дальше, разместим вышеприведённый код Python на Heroku. Для этого вам нужно создать локальный репозиторий Git. Выполните следующие шаги:

$ messenger-bot
$ cd messenger-bot
$ touch requirements.txt app.py Procfile

Выполните вышеприведенные команды в терминале и поместите вышеуказанный код Python в файл app.py. Потом поместите в Procfile следующее:

web: gunicorn app:app

Теперь мы должны сообщить Heroku, какие библиотеки Python будет использовать наше приложение. Эти библиотеки должны быть перечислены в файле requirements.txt:

click==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

Выполните следующую команду в терминале. Вы получите такой результат:

$ ls
Procfile app.py requirements.txt

Теперь мы создадим Git-репозиторий, который затем может быть помещен на серверы Heroku. Для его создания мы должны выполнить следующие шаги:

  1. Войти в Heroku.
  2. Создать новый репозиторий.
  3. Сохранить состояние проекта в репозиторий.
  4. Отправить его на Heroku.

Для этого введите следующие команды в терминал:

$ heroku login
$ git init
$ heroku git:remote -a
$ git commit -am "Initial commit"

$ git push heroku master
...
remote: https://.herokuapp.com/ deployed to Heroku
...

$ heroku config:set WEB_CONCURRENCY=3

Сохраните адрес из строки «remote: …» — это адрес вашего Heroku-приложения. Он понадобится нам на следующем этапе.

Создание приложения Facebook


Для создания приложения нам нужна его страница на

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

. Таково требование Facebook — у каждого приложения должна быть страница.

Теперь нам нужно зарегистрировать новое приложение. Перейдите на

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

создания приложения и следуйте инструкциям ниже:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


Затем перейдите в свой файл app.py и замените PAT в строке 9 на Page Access Token, который мы сохранили выше.

Cохраните все и отправьте код в Heroku.

$ git commit -am "Added in the PAT"
$ git push heroku master

Теперь, если вы перейдете на страницу Facebook и отправите сообщение на эту страницу, вы получите своё собственное сообщение в ответ. Это подтвердит, что мы всё сделали верно. Если сообщение не пришло, проверьте логи Heroku, которые дадут вам некоторое представление об ошибке. Вы можете получить доступ к логам следующим образом:

$ heroku logs -t -a


Примечание: бот будет отвечать только на ваши сообщения, потому что он ещё не одобрен Facebook. Однако вы можете добавить нескольких тестировщиков. Для этого следуйте указаниям на странице разработчика.
Получение данных с Reddit


Мы будем использовать данные из следующих источников:


Установим praw — Python-библиотеку сайта Reddit. Это легко сделать, введя следующую команду в терминале:

$ pip install praw

Теперь протестируем некоторые прелести Reddit в оболочке Python. В документации чётко показано, как получить доступ к Reddit и его сообществам. Сейчас самое подходящее время, чтобы использовать client_id и client_secret, которые мы создали в первой части статьи.

$ 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
Type "help", "copyright", "credits" or "license" for more information.
>>> import praw
>>> reddit = praw.Reddit(client_id='**********',
... client_secret='*****************',
... user_agent='my user agent')

>>>
>>> submissions = list(reddit.subreddit("GetMotivated").hot(limit=None))
>>> submissions[-4].title
u'[Video] Hi, Stranger.'

Не забудьте добавить в свой собственный client_id и client_secret вместо ****.

В приложении используется limit = None. Это позволит получать как можно больше сообщений. Изображения мы будем использовать только из GetMotivated и Memes, а текстовые сообщения только из Jokes и ShowerThoughts.

Теперь, когда мы знаем, как получить доступ к Reddit, используя библиотеку Python, мы можем продолжить и интегрировать его в app.py.

Добавим несколько дополнительных библиотек в наш файл requirements.txt, чтобы он выглядел примерно так:

$ cat requirements.txt
click==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

Просто отправить пользователю изображение или текст, взятый из Reddit, было бы нетрудно. В функции send_message мы могли бы сделать что-то вроде этого:

import praw
...

def send_message(token, recipient, text):
"""Send the message text to recipient with id recipient.
"""
if "meme" in text.lower():
subreddit_name = "memes"
elif "shower" in text.lower():
subreddit_name = "Showerthoughts"
elif "joke" in text.lower():
subreddit_name = "Jokes"
else:
subreddit_name = "GetMotivated"
....

if subreddit_name == "Showerthoughts":
for submission in reddit.subreddit(subreddit_name).hot(limit=None):
payload = submission.url
break
...

r = requests.post("https://graph.facebook.com/v2.6/me/messages",
params={"access_token": token},
data=json.dumps({
"recipient": {"id": recipient},
"message": {"attachment": {
"type": "image",
"payload": {
"url": payload
}}
}),
headers={'Content-type': 'application/json'})
...

Но нам нужен идентификатор для каждого изображения или текста, отправляемого пользователю, чтобы не отправлять одну и ту же запись дважды. Чтобы решить эту проблему, мы будем использовать PostgresSQL и идентификаторы постов Reddit (у каждого поста на Reddit есть уникальный id).

Мы будем использовать отношение «многие-ко-многим». Создадим две таблицы:

  • пользователи;
  • посты.

Сначала определим их в нашем коде, а затем уже разберёмся, как это будет работать:

from flask_sqlalchemy import SQLAlchemy

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

...
relationship_table=db.Table('relationship_table
db.Column('user_id', db.Integer,db.ForeignKey('users.id'), nullable=False),
db.Column('post_id',db.Integer,db.ForeignKey('posts.id'),nullable=False db.PrimaryKeyConstraint('user_id', 'post_id') )

class Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255),nullable=False)
posts=db.relationship('Posts', secondary=relationship_table, backref='users' )

def __init__(self, name):
self.name = name

class Posts(db.Model):
id=db.Column(db.Integer, primary_key=True)
name=db.Column(db.String, unique=True, nullable=False)
url=db.Column(db.String, nullable=False)

Таким образом, в таблице будет два поля. Имя будет идентификатором, отправленным с запросом Facebook Messenger Webhook. Посты будут связаны с другой таблицей — «Посты». В таблице сообщений есть имя и URL-адрес. «Имя» будет заполнено идентификатором поста Reddit, а URL — адресом этого поста.

Итак, теперь наш окончательный код будет работать так:

  1. Мы запрашиваем список сообщений из определенного сообщества Reddit:

    reddit.subreddit(subreddit_name).hot(limit=None)
  2. Проверяем, был ли конкретный пост отправлен пользователю ранее.
  3. Если сообщение уже было отправлено, мы будем продолжать запрашивать посты от Reddit, пока не найдем новую запись.
  4. Если сообщение не было отправлено пользователю, мы отправляем сообщение и выходим из цикла.

Итоговый код app.py выглядит так:

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 user agent')

# This needs to be filled with the Page Access Token that will be provided
# by the Facebook App that will 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('/', methods=['GET'])
def handle_verification():
print "Handling Verification."
if request.args.get('hub.verify_token', '') == 'my_voice_is_my_password_verify_me':
print "Verification successful!"
return request.args.get('hub.challenge', '')
else:
print "Verification failed!"
return 'Error, wrong validation token'

@app.route('/', methods=['POST'])
def handle_messages():
print "Handling 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 "ok"

def messaging_events(payload):
"""Generate tuples of (sender_id, message_text) from the
provided payload.
"""
data = json.loads(payload)
messaging_events = data["entry"][0]["messaging"]
for event in messaging_events:
if "message" in event and "text" in event["message"]:
yield event["sender"]["id"], event["message"]["text"].encode('unicode_escape')
else:
yield event["sender"]["id"], "I can't echo this"


def send_message(token, recipient, text):
"""Send the message text to recipient with id recipient.
"""
if "meme" in text.lower():
subreddit_name = "memes"
elif "shower" in text.lower():
subreddit_name = "Showerthoughts"
elif "joke" in text.lower():
subreddit_name = "Jokes"
else:
subreddit_name = "GetMotivated"

myUser = get_or_create(db.session, Users, name=recipient)

if subreddit_name == "Showerthoughts":
for submission in reddit.subreddit(subreddit_name).hot(limit=None):
if (submission.is_self == True):
query_result = Posts.query.filter(Posts.name == 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.users:
myUser.posts.append(query_result)
db.session.commit()
payload = submission.title
break
else:
continue

r = requests.post("https://graph.facebook.com/v2.6/me/messages",
params={"access_token": token},
data=json.dumps({
"recipient": {"id": recipient},
"message": {"text": payload,
"quick_replies":quick_replies_list}
}),
headers={'Content-type': 'application/json'})

elif subreddit_name == "Jokes":
for submission in reddit.subreddit(subreddit_name).hot(limit=None):
if ((submission.is_self == True) and ( submission.link_flair_text is None)):
query_result = Posts.query.filter(Posts.name == 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.users:
myUser.posts.append(query_result)
db.session.commit()
payload = submission.title
payload_text = submission.selftext
break
else:
continue

r = requests.post("https://graph.facebook.com/v2.6/me/messages",
params={"access_token": token},
data=json.dumps({
"recipient": {"id": recipient},
"message": {"text": payload}
}),
headers={'Content-type': 'application/json'})

r = requests.post("https://graph.facebook.com/v2.6/me/messages",
params={"access_token": token},
data=json.dumps({
"recipient": {"id": recipient},
"message": {"text": payload_text,
"quick_replies":quick_replies_list}
}),
headers={'Content-type': 'application/json'})

else:
payload = "http://imgur.com/WeyNGtQ.jpg"
for submission in reddit.subreddit(subreddit_name).hot(limit=None):
if (submission.link_flair_css_class == 'image') or ((submission.is_self != True) and ((".jpg" in submission.url) or (".png" in submission.url))):
query_result = Posts.query.filter(Posts.name == 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.users:
myUser.posts.append(query_result)
db.session.commit()
payload = submission.url
break
else:
continue

r = requests.post("https://graph.facebook.com/v2.6/me/messages",
params={"access_token": token},
data=json.dumps({
"recipient": {"id": recipient},
"message": {"attachment": {
"type": "image",
"payload": {
"url": payload
}},
"quick_replies":quick_replies_list}
}),
headers={'Content-type': 'application/json'})

if r.status_code != requests.codes.ok:
print r.text

def get_or_create(session, model, **kwargs):
instance = session.query(model).filter_by(**kwargs).first()
if instance:
return instance
else:
instance = model(**kwargs)
session.add(instance)
session.commit()
return instance

relationship_table=db.Table('relationship_table',
db.Column('user_id', db.Integer,db.ForeignKey('users.id'), nullable=False),
db.Column('post_id',db.Integer,db.ForeignKey('posts.id'),nullable=False),
db.PrimaryKeyConstraint('user_id', 'post_id') )

class Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255),nullable=False)
posts=db.relationship('Posts', secondary=relationship_table, backref='users' )

def __init__(self, name=None):
self.name = name

class Posts(db.Model):
id=db.Column(db.Integer, primary_key=True)
name=db.Column(db.String, unique=True, nullable=False)
url=db.Column(db.String, nullable=False)

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

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

Отправьте его в Heroku:

$ git commit -am "Updated the code with Reddit feature"
$ git push heroku master

Осталось последнее. Нам нужно сказать Heroku, что мы будем использовать базу данных. Для этого введите в терминале следующую команду:

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

Так мы создадим базу данных, которой достаточно для нашего проекта. Теперь нам нужно инициализировать её правильными таблицами. Для этого мы сначала должны запустить оболочку Python на нашем сервере Heroku:

$ heroku run python

Затем в оболочке Python ввести следующие команды:

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

Наше приложение готово! Поздравляю!

Некоторые интересные особенности кода


Во-первых, в коде используется функция быстрых ответов интерфейса Facebook Messenger Bot API. Это позволяет нам отправлять некоторые предварительно отформатированные данные, которые пользователь может быстро выбрать. Они будут выглядеть примерно так:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.


С каждым запросом на публикацию в API Facebook мы отправляем дополнительные данные:

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",
}]

Еще одна интересная особенность кода заключается в том, как мы определяем, является ли сообщение текстом, изображением или видеозаписью. В сообществе GetMotivated некоторые изображения не имеют расширения «.jpg» или «.png» в их URL, поэтому мы полагаемся на:

submission.link_flair_css_class == 'image'

Вы могли заметить эту строку кода в файле app.py:

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

Она гарантирует, что если новые записи не будут найдены для конкретного пользователя (у каждого сообщества есть максимальное количество «горячих» сообщений), приложению будет, что отправить. В противном случае мы получим ошибку.

Следующая функция проверяет, существует ли пользователь с определенным именем или нет. Если он существует, она выбирает этого пользователя из базы данных и возвращает его. Если он не существует, она создает его, а затем возвращает вновь созданного пользователя:

myUser = get_or_create(db.session, Users, name=recipient)
...

def get_or_create(session, model, **kwargs):
instance = session.query(model).filter_by(**kwargs).first()
if instance:
return instance
else:
instance = model(**kwargs)
session.add(instance)
session.commit()
return instance

Исходный код приложения также доступен

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

.


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

.
 
Вверх