Skip to content

Commit

Permalink
messenger integration
Browse files Browse the repository at this point in the history
  • Loading branch information
jlmadurga committed Apr 22, 2016
1 parent b12fcc3 commit b97eae1
Show file tree
Hide file tree
Showing 37 changed files with 1,599 additions and 77 deletions.
15 changes: 15 additions & 0 deletions microbot/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ def connect_kik_bot_signals():
sender=sender,
dispatch_uid='kik_bot_delete_cache')

def connect_messenger_bot_signals():
from . import signals as handlers
sender = apps.get_model("microbot", "MessengerBot")
signals.pre_save.connect(handlers.set_bot_webhook,
sender=sender,
dispatch_uid='messenger_bot_set_webhook')
signals.post_save.connect(handlers.delete_cache,
sender=sender,
dispatch_uid='messenger_bot_delete_cache')
signals.post_delete.connect(handlers.delete_cache,
sender=sender,
dispatch_uid='messenger_bot_delete_cache')

def connect_telegram_api_signals():
from . import signals as handlers
chat = apps.get_model("microbot", "Chat")
Expand All @@ -73,6 +86,7 @@ def connect_kik_api_signals():
signals.post_delete.connect(handlers.delete_cache,
sender=user,
dispatch_uid='kik_user_delete_cache')


def connect_environment_vars_signals():
from . import signals as handlers
Expand All @@ -92,6 +106,7 @@ def ready(self):
connect_bot_signals()
connect_telegram_bot_signals()
connect_kik_bot_signals()
connect_messenger_bot_signals()
connect_telegram_api_signals()
connect_kik_api_signals()
connect_environment_vars_signals()
94 changes: 94 additions & 0 deletions microbot/migrations/0004_auto_20160422_0654.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.4 on 2016-04-22 11:54
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion
import uuid


class Migration(migrations.Migration):

dependencies = [
('microbot', '0003_auto_20160420_0401'),
]

operations = [
migrations.CreateModel(
name='MessengerBot',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('enabled', models.BooleanField(default=True, help_text='Enable/disable telegram bot', verbose_name='Enable')),
('token', models.CharField(db_index=True, max_length=512, verbose_name='Messenger Token')),
],
options={
'verbose_name': 'Messenger Bot',
'verbose_name_plural': 'Messenger Bots',
},
),
migrations.CreateModel(
name='MessengerChatState',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('context', models.TextField(blank=True, help_text='Context serialized to json when this state was set', null=True, verbose_name='Context')),
('chat', models.CharField(db_index=True, max_length=255, verbose_name='Sender Id')),
('state', models.ForeignKey(help_text='State related to the chat', on_delete=django.db.models.deletion.CASCADE, related_name='messengerchatstate_chat', to='microbot.State', verbose_name='State')),
],
options={
'verbose_name': 'Messenger Chat State',
'verbose_name_plural': 'Messenger Chats States',
},
),
migrations.CreateModel(
name='MessengerMessage',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('sender', models.CharField(max_length=255, verbose_name='Sender Id')),
('recipient', models.CharField(max_length=255, verbose_name='Recipient Id')),
('timestamp', models.DateTimeField(verbose_name='Timestamp')),
('type', models.CharField(choices=[(b'message', 'Message'), (b'postback', 'Postback'), (b'delivery', 'Delivery')], max_length=255)),
('postback', models.CharField(blank=True, max_length=255, null=True, verbose_name='PostBack')),
('text', models.TextField(blank=True, null=True, verbose_name='Text')),
('bot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='microbot.MessengerBot', verbose_name='Messenger Bot')),
],
options={
'ordering': ['-timestamp'],
'verbose_name': 'Messenger Message',
'verbose_name_plural': 'Messenger Messages',
},
),
migrations.CreateModel(
name='MessengerRecipient',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Date created')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Date updated')),
('chat_id', models.CharField(db_index=True, help_text='Chat identifier provided by Messenger API', max_length=150, verbose_name='Chat Id')),
('name', models.CharField(db_index=True, help_text='Name of recipient', max_length=100, verbose_name='Name')),
('hook', models.ForeignKey(help_text='Hook which recipient is attached to', on_delete=django.db.models.deletion.CASCADE, related_name='messenger_recipients', to='microbot.Hook', verbose_name='Hook')),
],
options={
'verbose_name': 'Messenger Recipient',
'verbose_name_plural': 'Messenger Recipients',
},
),
migrations.AlterModelOptions(
name='kikchatstate',
options={'verbose_name': 'Kik Chat State', 'verbose_name_plural': 'Kik Chats States'},
),
migrations.AlterModelOptions(
name='telegramchatstate',
options={'verbose_name': 'Telegram Chat State', 'verbose_name_plural': 'Telegram Chats States'},
),
migrations.AddField(
model_name='bot',
name='messenger_bot',
field=models.OneToOneField(blank=True, help_text='Messenger Bot', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bot', to='microbot.MessengerBot', verbose_name='Messenger Bot'),
),
]
7 changes: 4 additions & 3 deletions microbot/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
Message as TelegramMessage, # NOQA
Update as TelegramUpdate) # NOQA
from microbot.models.kik_api import (KikUser, KikChat, KikMessage) # NOQA
from microbot.models.state import State, TelegramChatState, KikChatState # NOQA
from microbot.models.bot import Bot, TelegramBot, KikBot # NOQA
from microbot.models.messenger_api import MessengerMessage # NOQA
from microbot.models.state import State, TelegramChatState, KikChatState, MessengerChatState # NOQA
from microbot.models.bot import Bot, TelegramBot, KikBot, MessengerBot # NOQA
from microbot.models.response import Response # NOQA
from microbot.models.handler import Handler, Request, UrlParam, HeaderParam # NOQA
from microbot.models.environment_vars import EnvironmentVar # NOQA
from microbot.models.hook import Hook, TelegramRecipient, KikRecipient # NOQA
from microbot.models.hook import Hook, TelegramRecipient, KikRecipient, MessengerRecipient # NOQA
108 changes: 105 additions & 3 deletions microbot/models/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from kik import KikApi
import logging
from microbot.models.base import MicrobotModel
from microbot.models import TelegramUser, TelegramChatState, KikChatState
from microbot.models import TelegramUser, TelegramChatState, KikChatState, MessengerChatState
from django.core.urlresolvers import RegexURLResolver
from django.core.urlresolvers import Resolver404
from telegram import ParseMode, ReplyKeyboardHide, ReplyKeyboardMarkup
Expand All @@ -19,6 +19,7 @@
from kik.messages.text import TextMessage
from kik.messages.keyboards import SuggestedResponseKeyboard
from kik.configuration import Configuration
from messengerbot import MessengerClient, messages, attachments, templates, elements
import sys

logger = logging.getLogger(__name__)
Expand All @@ -34,6 +35,9 @@ class Bot(MicrobotModel):
kik_bot = models.OneToOneField('KikBot', verbose_name=_("Kik Bot"), related_name='bot',
on_delete=models.SET_NULL, blank=True, null=True,
help_text=_("Kik Bot"))
messenger_bot = models.OneToOneField('MessengerBot', verbose_name=_("Messenger Bot"), related_name='bot',
on_delete=models.SET_NULL, blank=True, null=True,
help_text=_("Messenger Bot"))

class Meta:
verbose_name = _('Bot')
Expand Down Expand Up @@ -96,10 +100,14 @@ def handle_hook(self, hook, data):
kik_keyboard = hook.bot.kik_bot.build_keyboard(keyboard)
for recipient in hook.kik_recipients.all():
hook.bot.kik_bot.send_message(recipient.chat_id, text, kik_keyboard, user=recipient.username)
if hook.bot.messenger_bot and hook.bot.messenger_bot.enabled:
messenger_keyboard = hook.bot.messenger_bot.build_keyboard(keyboard)
for recipient in hook.messenger_recipients.all():
hook.bot.messenger_bot.send_message(recipient.chat_id, text, messenger_keyboard)

class IntegrationBot(MicrobotModel):
enabled = models.BooleanField(_('Enable'), default=True, help_text=_("Enable/disable telegram bot"))

class Meta:
verbose_name = _('Integration Bot')
verbose_name_plural = _('Integration Bots')
Expand Down Expand Up @@ -326,4 +334,98 @@ def send_message(self, chat_id, text, keyboard, reply_message=None, user=None):
logger.debug("Message sent OK:(%s)" % msg.to_json())
except:
exctype, value = sys.exc_info()[:2]
logger.error("Error trying to send message:(%s): %s:%s" % (msg.to_json(), exctype, value))
logger.error("Error trying to send message:(%s): %s:%s" % (msg.to_json(), exctype, value))

@python_2_unicode_compatible
class MessengerBot(IntegrationBot):
token = models.CharField(_('Messenger Token'), max_length=512, db_index=True)

class Meta:
verbose_name = _('Messenger Bot')
verbose_name_plural = _('Messenger Bots')

def __init__(self, *args, **kwargs):
super(MessengerBot, self).__init__(*args, **kwargs)
self._bot = None
self.webhook = False
if self.token:
self.init_bot()

def __str__(self):
return "%s" % self.token

def __repr__(self):
return "(%s, %s)" % (self.id, self.token)

def init_bot(self):
self._bot = MessengerClient(self.token)

def set_webhook(self, url):
# Url is set in facebook dashboard. Just subscribe
self._bot.subscribe_app()

@property
def hook_url(self):
return 'microbot:messengerbot'

@property
def hook_id(self):
return str(self.id)

@property
def null_url(self):
# not used
return "https://example.com"

@property
def identity(self):
return 'messenger'

def message_text(self, message):
return message.data

def get_chat_state(self, message):
try:
return MessengerChatState.objects.get(chat=message.sender, state__bot=self.bot)
except MessengerChatState.DoesNotExist:
return None

def build_keyboard(self, keyboard):
def traverse(o, tree_types=(list, tuple)):
if isinstance(o, tree_types):
for value in o:
for subvalue in traverse(value, tree_types):
yield subvalue
else:
yield o

built_keyboard = None
if keyboard:
# same payload as title
buttons = [elements.PostbackButton(element[0:20], element[0:20]) for element in traverse(ast.literal_eval(keyboard))]
button_template = templates.ButtonTemplate(None, buttons)
built_keyboard = attachments.TemplateAttachment(button_template)
return built_keyboard

def create_chat_state(self, message, target_state, context):
MessengerChatState.objects.create(chat=message.sender,
state=target_state,
ctx=context)

def get_chat_id(self, message):
return message.sender

def send_message(self, chat_id, text, keyboard, reply_message=None, user=None):
body = text[0:100].encode('utf-8')
if keyboard:
keyboard.template.text = body
msg = messages.Message(attachment=keyboard)
else:
msg = messages.Message(text=body)
try:
logger.debug("Message to send:(%s)" % msg.to_dict())
self._bot.send(messages.MessageRequest(chat_id, msg))
logger.debug("Message sent OK:(%s)" % msg.to_dict())
except:
exctype, value = sys.exc_info()[:2]
logger.error("Error trying to send message:(%s): %s:%s" % (msg.to_dict(), exctype, value))
16 changes: 15 additions & 1 deletion microbot/models/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,18 @@ class Meta:
verbose_name_plural = _('Kik Recipients')

def __str__(self):
return "(%s, %s, %s)" % (self.name, self.name, self.username)
return "(%s, %s, %s)" % (self.name, self.chat_id, self.username)

@python_2_unicode_compatible
class MessengerRecipient(MicrobotModel):
chat_id = models.CharField(_('Chat Id'), max_length=150, db_index=True, help_text=_("Chat identifier provided by Messenger API"))
name = models.CharField(_('Name'), max_length=100, db_index=True, help_text=_("Name of recipient"))
hook = models.ForeignKey(Hook, verbose_name=_('Hook'), related_name="messenger_recipients",
help_text=_("Hook which recipient is attached to"))

class Meta:
verbose_name = _('Messenger Recipient')
verbose_name_plural = _('Messenger Recipients')

def __str__(self):
return "(%s, %s, %s)" % (self.name, self.chat_id)
64 changes: 64 additions & 0 deletions microbot/models/messenger_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from microbot.models.base import MicrobotModel


@python_2_unicode_compatible
class MessengerMessage(MicrobotModel):
bot = models.ForeignKey('MessengerBot', related_name='messages', verbose_name=_("Messenger Bot"))
sender = models.CharField(_("Sender Id"), max_length=255)
recipient = models.CharField(_("Recipient Id"), max_length=255)
timestamp = models.DateTimeField(_('Timestamp'))
MESSAGE, POSTBACK, DELIVERY = 'message', 'postback', 'delivery'

TYPE_CHOICES = (
(MESSAGE, _('Message')),
(POSTBACK, _('Postback')),
(DELIVERY, _('Delivery')),
)
type = models.CharField(max_length=255, choices=TYPE_CHOICES)
postback = models.CharField(_("PostBack"), null=True, blank=True, max_length=255)
text = models.TextField(null=True, blank=True, verbose_name=_("Text"))

class Meta:
verbose_name = 'Messenger Message'
verbose_name_plural = 'Messenger Messages'
ordering = ['-timestamp', ]

@property
def is_message(self):
return self.type == self.MESSAGE

@property
def is_postback(self):
return self.type == self.POSTBACK

@property
def is_delivery(self):
return self.type == self.DELIVERY

@property
def data(self):
if self.is_message:
return self.text
elif self.is_postback:
return self.postback
else:
return None

def __str__(self):

return "(%s,%s:%s)" % (self.id, self.type, self.data)

def to_dict(self):
message_dict = {'sender': self.sender,
'recipient': self.recipient,
'timestamp': self.timestamp,
}
if self.is_message:
message_dict.update({'text': self.text})
elif self.is_postback:
message_dict.update({'postback': self.postback})
return message_dict

0 comments on commit b97eae1

Please sign in to comment.