Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Quick replies #342

Merged
merged 15 commits into from
Jan 31, 2019
28 changes: 28 additions & 0 deletions fbchat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,20 @@ def _getSendData(self, message=None, thread_id=None, thread_type=ThreadType.USER
if message.sticker:
data['sticker_id'] = message.sticker.uid

if message.quick_replies:
xmd = {"quick_replies": []}
for quick_reply in message.quick_replies:
q = dict()
q["content_type"] = quick_reply.type.value.lower()
q["payload"] = quick_reply.payload
q["data"] = quick_reply.data
if quick_reply.type == QuickReplyType.TEXT: q["title"] = quick_reply.title
if quick_reply.type is not QuickReplyType.LOCATION: q["image_url"] = quick_reply.image_url
xmd["quick_replies"].append(q)
if len(message.quick_replies) == 1 and message.quick_replies[0].is_response:
xmd["quick_replies"] = xmd["quick_replies"][0]
data['platform_xmd'] = json.dumps(xmd)

return data

def _doSendRequest(self, data, get_thread_id=False):
Expand Down Expand Up @@ -1116,6 +1130,20 @@ def wave(self, wave_first=True, thread_id=None, thread_type=None):
data['specific_to_list[0]'] = "fbid:{}".format(thread_id)
return self._doSendRequest(data)

def quickReply(self, quick_reply, thread_id=None, thread_type=None):
"""
Replies to a chosen quick reply

:param thread_id: User/Group ID to send to. See :ref:`intro_threads`
:param thread_type: See :ref:`intro_threads`
:type thread_type: models.ThreadType
:return: :ref:`Message ID <intro_message_ids>` of the sent message
:raises: FBchatException if request failed
"""
quick_reply.is_response = True
if quick_reply.type == QuickReplyType.TEXT:
return self.send(Message(text=quick_reply.title, quick_replies=[quick_reply]))

def _upload(self, files):
"""
Uploads files to Facebook
Expand Down
24 changes: 24 additions & 0 deletions fbchat/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,24 @@ def graphql_to_plan(a):
rtn.invited = [m.get('node').get('id') for m in guests if m.get('guest_list_state') == "INVITED"]
return rtn

def graphql_to_quick_reply(q, is_response=False):
data = dict()
_type = QuickReplyType(q.get('content_type').upper())
if q.get('payload'): data["payload"] = q["payload"]
if q.get('data'): data["data"] = q["data"]
if q.get('image_url') and _type is not QuickReplyType.LOCATION: data["image_url"] = q["image_url"]
data["is_response"] = is_response
if _type == QuickReplyType.TEXT:
if q.get('title') is not None: data["title"] = q["title"]
rtn = QuickReplyText(**data)
elif _type == QuickReplyType.LOCATION:
rtn = QuickReplyLocation(**data)
elif _type == QuickReplyType.PHONE_NUMBER:
rtn = QuickReplyPhoneNumber(**data)
elif _type == QuickReplyType.EMAIL:
rtn = QuickReplyEmail(**data)
return rtn

def graphql_to_message(message):
if message.get('message_sender') is None:
message['message_sender'] = {}
Expand All @@ -214,6 +232,12 @@ def graphql_to_message(message):
rtn.attachments = [graphql_to_attachment(attachment) for attachment in message['blob_attachments']]
# TODO: This is still missing parsing:
# message.get('extensible_attachment')
if message.get('platform_xmd_encoded'):
quick_replies = json.loads(message['platform_xmd_encoded']).get('quick_replies')
if isinstance(quick_replies, list):
rtn.quick_replies = [graphql_to_quick_reply(q) for q in quick_replies]
elif isinstance(quick_replies, dict):
rtn.quick_replies = [graphql_to_quick_reply(quick_replies, is_response=True)]
return rtn

def graphql_to_user(user):
Expand Down
73 changes: 72 additions & 1 deletion fbchat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,10 @@ class Message(object):
sticker = None
#: A list of attachments
attachments = None
#: A list of :class:`QuickReply`
quick_replies = None

def __init__(self, text=None, mentions=None, emoji_size=None, sticker=None, attachments=None):
def __init__(self, text=None, mentions=None, emoji_size=None, sticker=None, attachments=None, quick_replies=None):
"""Represents a Facebook message"""
self.text = text
if mentions is None:
Expand All @@ -200,6 +202,9 @@ def __init__(self, text=None, mentions=None, emoji_size=None, sticker=None, atta
if attachments is None:
attachments = []
self.attachments = attachments
if quick_replies is None:
quick_replies = []
self.quick_replies = quick_replies
self.reactions = {}

def __repr__(self):
Expand Down Expand Up @@ -439,6 +444,66 @@ def __repr__(self):
def __unicode__(self):
return '<Mention {}: offset={} length={}>'.format(self.thread_id, self.offset, self.length)

class QuickReply(object):
#: Type of the quick reply
type = None
#: Payload of the quick reply
payload = None
#: Additional data
data = None
#: Whether it's a response for a quick reply
is_response = None

def __init__(self, _type=None, payload=None, data=None, is_response=False):
"""Represents a quick reply"""
self.type = _type
self.payload = payload
self.data = data
self.is_response = is_response

def __repr__(self):
return self.__unicode__()

def __unicode__(self):
return '<{}: payload={!r}>'.format(self.__class__.__name__, self.payload)

class QuickReplyText(QuickReply):
#: Title of the quick reply
title = None
#: URL of the quick reply image (optional)
image_url = None

def __init__(self, title=None, image_url=None, **kwargs):
"""Represents a text quick reply"""
super(QuickReplyText, self).__init__(_type=QuickReplyType.TEXT, **kwargs)
self.title = title
self.image_url = image_url

class QuickReplyLocation(QuickReply):

def __init__(self, **kwargs):
"""Represents a location quick reply (Doesn't work on mobile)"""
super(QuickReplyLocation, self).__init__(_type=QuickReplyType.LOCATION, **kwargs)
self.is_response = False

class QuickReplyPhoneNumber(QuickReply):
#: URL of the quick reply image (optional)
image_url = None

def __init__(self, image_url=None, **kwargs):
"""Represents a phone number quick reply (Doesn't work on mobile)"""
super(QuickReplyPhoneNumber, self).__init__(_type=QuickReplyType.PHONE_NUMBER, **kwargs)
self.image_url = image_url

class QuickReplyEmail(QuickReply):
#: URL of the quick reply image (optional)
image_url = None

def __init__(self, image_url=None, **kwargs):
"""Represents an email quick reply (Doesn't work on mobile)"""
super(QuickReplyEmail, self).__init__(_type=QuickReplyType.EMAIL, **kwargs)
self.image_url = image_url

class Poll(object):
#: ID of the poll
uid = None
Expand Down Expand Up @@ -526,6 +591,12 @@ def __repr__(self):
# For documentation:
return '{}.{}'.format(type(self).__name__, self.name)

class QuickReplyType(Enum):
TEXT = 'TEXT'
LOCATION = 'LOCATION'
PHONE_NUMBER = 'USER_PHONE_NUMBER'
EMAIL = 'USER_EMAIL'

class ThreadType(Enum):
"""Used to specify what type of Facebook thread is being used. See :ref:`intro_threads` for more info"""
USER = 1
Expand Down