Skip to content

Commit

Permalink
Merge pull request #342 from kapi2289/quick_replies
Browse files Browse the repository at this point in the history
[Feature] Quick replies
  • Loading branch information
madsmtm authored Jan 31, 2019
2 parents fb7bf43 + 27ffba3 commit b1a02ad
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 1 deletion.
73 changes: 73 additions & 0 deletions fbchat/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -984,6 +984,30 @@ def fetchPlanInfo(self, plan_id):
plan = graphql_to_plan(j["payload"])
return plan

def _getPrivateData(self):
j = self.graphql_request(GraphQL(doc_id='1868889766468115'))
return j['viewer']

def getPhoneNumbers(self):
"""
Fetches a list of user phone numbers.
:return: List of phone numbers
:rtype: list
"""
data = self._getPrivateData()
return [j['phone_number']['universal_number'] for j in data['user']['all_phones']]

def getEmails(self):
"""
Fetches a list of user emails.
:return: List of emails
:rtype: list
"""
data = self._getPrivateData()
return [j['display_email'] for j in data['all_emails']]

"""
END FETCH METHODS
"""
Expand Down Expand Up @@ -1040,6 +1064,25 @@ 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
q["payload"] = quick_reply.payload
q["external_payload"] = quick_reply.external_payload
q["data"] = quick_reply.data
if quick_reply.is_response:
q["ignore_for_webhook"] = False
if isinstance(quick_reply, QuickReplyText):
q["title"] = quick_reply.title
if not isinstance(quick_reply, QuickReplyLocation):
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 @@ -1111,6 +1154,36 @@ 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, payload=None, thread_id=None, thread_type=None):
"""
Replies to a chosen quick reply
:param quick_reply: Quick reply to reply to
:param payload: Optional answer to the quick reply
:param thread_id: User/Group ID to send to. See :ref:`intro_threads`
:param thread_type: See :ref:`intro_threads`
:type quick_reply: models.QuickReply
: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 isinstance(quick_reply, QuickReplyText):
return self.send(Message(text=quick_reply.title, quick_replies=[quick_reply]))
elif isinstance(quick_reply, QuickReplyLocation):
if not isinstance(payload, LocationAttachment): raise ValueError("Payload must be an instance of `fbchat.models.LocationAttachment`")
return self.sendLocation(payload, thread_id=thread_id, thread_type=thread_type)
elif isinstance(quick_reply, QuickReplyEmail):
if not payload: payload = self.getEmails()[0]
quick_reply.external_payload = quick_reply.payload
quick_reply.payload = payload
return self.send(Message(text=payload, quick_replies=[quick_reply]))
elif isinstance(quick_reply, QuickReplyPhoneNumber):
if not payload: payload = self.getPhoneNumbers()[0]
quick_reply.external_payload = quick_reply.payload
quick_reply.payload = payload
return self.send(Message(text=payload, quick_replies=[quick_reply]))

def unsend(self, mid):
"""
Unsends a message (removes for everyone)
Expand Down
24 changes: 24 additions & 0 deletions fbchat/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,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 = q.get('content_type').lower()
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 QuickReplyLocation._type: data["image_url"] = q["image_url"]
data["is_response"] = is_response
if _type == QuickReplyText._type:
if q.get('title') is not None: data["title"] = q["title"]
rtn = QuickReplyText(**data)
elif _type == QuickReplyLocation._type:
rtn = QuickReplyLocation(**data)
elif _type == QuickReplyPhoneNumber._type:
rtn = QuickReplyPhoneNumber(**data)
elif _type == QuickReplyEmail._type:
rtn = QuickReplyEmail(**data)
return rtn

def graphql_to_message(message):
if message.get('message_sender') is None:
message['message_sender'] = {}
Expand All @@ -290,6 +308,12 @@ def graphql_to_message(message):
}
if message.get('blob_attachments') is not None:
rtn.attachments = [graphql_to_attachment(attachment) for attachment in message['blob_attachments']]
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)]
if message.get('extensible_attachment') is not None:
attachment = graphql_to_extensible_attachment(message['extensible_attachment'])
if isinstance(attachment, UnsentMessage):
Expand Down
74 changes: 73 additions & 1 deletion fbchat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,12 @@ class Message(object):
sticker = None
#: A list of attachments
attachments = None
#: A list of :class:`QuickReply`
quick_replies = None
#: Whether the message is unsent (deleted for everyone)
unsent = 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 @@ -204,6 +206,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 = {}
self.read_by = []
self.deleted = False
Expand Down Expand Up @@ -521,6 +526,73 @@ def __repr__(self):
def __unicode__(self):
return '<Mention {}: offset={} length={}>'.format(self.thread_id, self.offset, self.length)

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

def __init__(self, payload=None, data=None, is_response=False):
"""Represents a quick reply"""
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
#: Type of the quick reply
_type = "text"

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

class QuickReplyLocation(QuickReply):
#: Type of the quick reply
_type = "location"

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

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

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

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

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

class Poll(object):
#: ID of the poll
uid = None
Expand Down

0 comments on commit b1a02ad

Please sign in to comment.