Read commit description
Added: - Detecting extensible attachments - Fetching live user location - New methods for message reacting - New `on` methods: `onReactionAdded`, `onReactionRemoved`, `onBlock`, `onUnblock`, `onLiveLocation` - Fixed `size` of attachments
This commit is contained in:
committed by
GitHub
parent
9b4e753a79
commit
940a65954c
185
fbchat/client.py
185
fbchat/client.py
@@ -1485,7 +1485,30 @@ class Client(object):
|
|||||||
|
|
||||||
j = self._post(self.req_url.THREAD_EMOJI, data, fix_request=True, as_json=True)
|
j = self._post(self.req_url.THREAD_EMOJI, data, fix_request=True, as_json=True)
|
||||||
|
|
||||||
|
def _react(self, message_id, reaction=None, add_reaction=True):
|
||||||
|
data = {
|
||||||
|
"doc_id": 1491398900900362,
|
||||||
|
"variables": json.dumps({
|
||||||
|
"data": {
|
||||||
|
"action": "ADD_REACTION" if add_reaction else "REMOVE_REACTION",
|
||||||
|
"client_mutation_id": "1",
|
||||||
|
"actor_id": self.uid,
|
||||||
|
"message_id": str(message_id),
|
||||||
|
"reaction": reaction.value if add_reaction else None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
r = self._post(self.req_url.MESSAGE_REACTION, data)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
def reactToMessage(self, message_id, reaction):
|
def reactToMessage(self, message_id, reaction):
|
||||||
|
"""
|
||||||
|
Deprecated. Use :func:`fbchat.Client.addReaction` instead
|
||||||
|
"""
|
||||||
|
self.addReaction(message_id=message_id, reaction=reaction)
|
||||||
|
|
||||||
|
def addReaction(self, message_id, reaction):
|
||||||
"""
|
"""
|
||||||
Reacts to a message
|
Reacts to a message
|
||||||
|
|
||||||
@@ -1494,20 +1517,16 @@ class Client(object):
|
|||||||
:type reaction: models.MessageReaction
|
:type reaction: models.MessageReaction
|
||||||
:raises: FBchatException if request failed
|
:raises: FBchatException if request failed
|
||||||
"""
|
"""
|
||||||
full_data = {
|
self._react(message_id=message_id, reaction=reaction, add_reaction=True)
|
||||||
"doc_id": 1491398900900362,
|
|
||||||
"variables": json.dumps({
|
|
||||||
"data": {
|
|
||||||
"action": "ADD_REACTION",
|
|
||||||
"client_mutation_id": "1",
|
|
||||||
"actor_id": self.uid,
|
|
||||||
"message_id": str(message_id),
|
|
||||||
"reaction": reaction.value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
j = self._post(self.req_url.MESSAGE_REACTION, full_data, fix_request=True, as_json=True)
|
def removeReaction(self, message_id):
|
||||||
|
"""
|
||||||
|
Removes reaction from a message
|
||||||
|
|
||||||
|
:param message_id: :ref:`Message ID <intro_message_ids>` to remove reaction from
|
||||||
|
:raises: FBchatException if request failed
|
||||||
|
"""
|
||||||
|
self._react(message_id=message_id, add_reaction=False)
|
||||||
|
|
||||||
def createPlan(self, plan, thread_id=None):
|
def createPlan(self, plan, thread_id=None):
|
||||||
"""
|
"""
|
||||||
@@ -2032,6 +2051,7 @@ class Client(object):
|
|||||||
|
|
||||||
delta = m["delta"]
|
delta = m["delta"]
|
||||||
delta_type = delta.get("type")
|
delta_type = delta.get("type")
|
||||||
|
delta_class = delta.get("class")
|
||||||
metadata = delta.get("messageMetadata")
|
metadata = delta.get("messageMetadata")
|
||||||
|
|
||||||
if metadata:
|
if metadata:
|
||||||
@@ -2068,14 +2088,14 @@ class Client(object):
|
|||||||
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
|
|
||||||
# Thread title change
|
# Thread title change
|
||||||
elif delta.get("class") == "ThreadName":
|
elif delta_class == "ThreadName":
|
||||||
new_title = delta["name"]
|
new_title = delta["name"]
|
||||||
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
thread_id, thread_type = getThreadIdAndThreadType(metadata)
|
||||||
self.onTitleChange(mid=mid, author_id=author_id, new_title=new_title, thread_id=thread_id,
|
self.onTitleChange(mid=mid, author_id=author_id, new_title=new_title, thread_id=thread_id,
|
||||||
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
|
|
||||||
# Forced fetch
|
# Forced fetch
|
||||||
elif delta.get("class") == "ForcedFetch":
|
elif delta_class == "ForcedFetch":
|
||||||
mid = delta.get("messageId")
|
mid = delta.get("messageId")
|
||||||
if mid is None:
|
if mid is None:
|
||||||
self.onUnknownMesssageType(msg=m)
|
self.onUnknownMesssageType(msg=m)
|
||||||
@@ -2121,7 +2141,7 @@ class Client(object):
|
|||||||
thread_id=thread_id, thread_type=thread_type, ts=ts, msg=m)
|
thread_id=thread_id, thread_type=thread_type, ts=ts, msg=m)
|
||||||
|
|
||||||
# Message delivered
|
# Message delivered
|
||||||
elif delta.get("class") == "DeliveryReceipt":
|
elif delta_class == "DeliveryReceipt":
|
||||||
message_ids = delta["messageIds"]
|
message_ids = delta["messageIds"]
|
||||||
delivered_for = str(delta.get("actorFbId") or delta["threadKey"]["otherUserFbId"])
|
delivered_for = str(delta.get("actorFbId") or delta["threadKey"]["otherUserFbId"])
|
||||||
ts = int(delta["deliveredWatermarkTimestampMs"])
|
ts = int(delta["deliveredWatermarkTimestampMs"])
|
||||||
@@ -2131,7 +2151,7 @@ class Client(object):
|
|||||||
metadata=metadata, msg=m)
|
metadata=metadata, msg=m)
|
||||||
|
|
||||||
# Message seen
|
# Message seen
|
||||||
elif delta.get("class") == "ReadReceipt":
|
elif delta_class == "ReadReceipt":
|
||||||
seen_by = str(delta.get("actorFbId") or delta["threadKey"]["otherUserFbId"])
|
seen_by = str(delta.get("actorFbId") or delta["threadKey"]["otherUserFbId"])
|
||||||
seen_ts = int(delta["actionTimestampMs"])
|
seen_ts = int(delta["actionTimestampMs"])
|
||||||
delivered_ts = int(delta["watermarkTimestampMs"])
|
delivered_ts = int(delta["watermarkTimestampMs"])
|
||||||
@@ -2140,7 +2160,7 @@ class Client(object):
|
|||||||
seen_ts=seen_ts, ts=delivered_ts, metadata=metadata, msg=m)
|
seen_ts=seen_ts, ts=delivered_ts, metadata=metadata, msg=m)
|
||||||
|
|
||||||
# Messages marked as seen
|
# Messages marked as seen
|
||||||
elif delta.get("class") == "MarkRead":
|
elif delta_class == "MarkRead":
|
||||||
seen_ts = int(delta.get("actionTimestampMs") or delta.get("actionTimestamp"))
|
seen_ts = int(delta.get("actionTimestampMs") or delta.get("actionTimestamp"))
|
||||||
delivered_ts = int(delta.get("watermarkTimestampMs") or delta.get("watermarkTimestamp"))
|
delivered_ts = int(delta.get("watermarkTimestampMs") or delta.get("watermarkTimestamp"))
|
||||||
|
|
||||||
@@ -2240,6 +2260,51 @@ class Client(object):
|
|||||||
self.onPlanParticipation(mid=mid, plan=plan, take_part=take_part, author_id=author_id,
|
self.onPlanParticipation(mid=mid, plan=plan, take_part=take_part, author_id=author_id,
|
||||||
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
|
||||||
|
|
||||||
|
# Client payload (that weird numbers)
|
||||||
|
elif delta_class == "ClientPayload":
|
||||||
|
payload = json.loads("".join(chr(z) for z in delta['payload']))
|
||||||
|
ts = m.get("ofd_ts")
|
||||||
|
for d in payload.get('deltas', []):
|
||||||
|
|
||||||
|
# Message reaction
|
||||||
|
if d.get('deltaMessageReaction'):
|
||||||
|
i = d['deltaMessageReaction']
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(i)
|
||||||
|
mid = i["messageId"]
|
||||||
|
author_id = str(i["userId"])
|
||||||
|
reaction = MessageReaction(i["reaction"]) if i.get("reaction") else None
|
||||||
|
add_reaction = not bool(i["action"])
|
||||||
|
if add_reaction:
|
||||||
|
self.onReactionAdded(mid=mid, reaction=reaction, author_id=author_id,
|
||||||
|
thread_id=thread_id, thread_type=thread_type, ts=ts, msg=m)
|
||||||
|
else:
|
||||||
|
self.onReactionRemoved(mid=mid, author_id=author_id, thread_id=thread_id,
|
||||||
|
thread_type=thread_type, ts=ts, msg=m)
|
||||||
|
|
||||||
|
# Viewer status change
|
||||||
|
elif d.get('deltaChangeViewerStatus'):
|
||||||
|
i = d['deltaChangeViewerStatus']
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(i)
|
||||||
|
author_id = str(i["actorFbid"])
|
||||||
|
reason = i["reason"]
|
||||||
|
can_reply = i["canViewerReply"]
|
||||||
|
if reason == 2:
|
||||||
|
if can_reply:
|
||||||
|
self.onUnblock(author_id=author_id, thread_id=thread_id, thread_type=thread_type, ts=ts, msg=m)
|
||||||
|
else:
|
||||||
|
self.onBlock(author_id=author_id, thread_id=thread_id, thread_type=thread_type, ts=ts, msg=m)
|
||||||
|
|
||||||
|
# Live location info
|
||||||
|
elif d.get('liveLocationData'):
|
||||||
|
i = d['liveLocationData']
|
||||||
|
thread_id, thread_type = getThreadIdAndThreadType(i)
|
||||||
|
for l in i['messageLiveLocations']:
|
||||||
|
mid = l["messageId"]
|
||||||
|
author_id = l["senderId"]
|
||||||
|
location = graphql_to_live_location(l)
|
||||||
|
self.onLiveLocation(mid=mid, location=location, author_id=author_id, thread_id=thread_id,
|
||||||
|
thread_type=thread_type, ts=ts, msg=m)
|
||||||
|
|
||||||
# New message
|
# New message
|
||||||
elif delta.get("class") == "NewMessage":
|
elif delta.get("class") == "NewMessage":
|
||||||
mentions = []
|
mentions = []
|
||||||
@@ -2260,15 +2325,19 @@ class Client(object):
|
|||||||
attach_type = mercury['blob_attachment']['__typename']
|
attach_type = mercury['blob_attachment']['__typename']
|
||||||
attachment = graphql_to_attachment(mercury.get('blob_attachment', {}))
|
attachment = graphql_to_attachment(mercury.get('blob_attachment', {}))
|
||||||
|
|
||||||
if attach_type == ['MessageFile', 'MessageVideo', 'MessageAudio']:
|
if attach_type in ['MessageFile', 'MessageVideo', 'MessageAudio']:
|
||||||
# TODO: Add more data here for audio files
|
# TODO: Add more data here for audio files
|
||||||
attachment.size = int(a['fileSize'])
|
attachment.size = int(a['fileSize'])
|
||||||
attachments.append(attachment)
|
attachments.append(attachment)
|
||||||
|
|
||||||
elif mercury.get('sticker_attachment'):
|
elif mercury.get('sticker_attachment'):
|
||||||
sticker = graphql_to_sticker(a['mercury']['sticker_attachment'])
|
sticker = graphql_to_sticker(a['mercury']['sticker_attachment'])
|
||||||
|
|
||||||
elif mercury.get('extensible_attachment'):
|
elif mercury.get('extensible_attachment'):
|
||||||
# TODO: Add more data here for shared stuff (URLs, events and so on)
|
attachment = graphql_to_extensible_attachment(mercury.get('extensible_attachment', {}))
|
||||||
pass
|
if attachment:
|
||||||
|
attachments.append(attachment)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception('An exception occured while reading attachments: {}'.format(delta['attachments']))
|
log.exception('An exception occured while reading attachments: {}'.format(delta['attachments']))
|
||||||
|
|
||||||
@@ -2928,6 +2997,7 @@ class Client(object):
|
|||||||
:param metadata: Extra metadata about the action
|
:param metadata: Extra metadata about the action
|
||||||
:param msg: A full set of the data recieved
|
:param msg: A full set of the data recieved
|
||||||
:type plan: models.Plan
|
:type plan: models.Plan
|
||||||
|
:type take_part: bool
|
||||||
:type thread_type: models.ThreadType
|
:type thread_type: models.ThreadType
|
||||||
"""
|
"""
|
||||||
if take_part:
|
if take_part:
|
||||||
@@ -2935,6 +3005,79 @@ class Client(object):
|
|||||||
else:
|
else:
|
||||||
log.info("{} won't take part in {} in {} ({})".format(author_id, plan, thread_id, thread_type.name))
|
log.info("{} won't take part in {} in {} ({})".format(author_id, plan, thread_id, thread_type.name))
|
||||||
|
|
||||||
|
def onReactionAdded(self, mid=None, reaction=None, add_reaction=None, author_id=None, thread_id=None, thread_type=None, ts=None, msg=None):
|
||||||
|
"""
|
||||||
|
Called when the client is listening, and somebody reacts to a message
|
||||||
|
|
||||||
|
:param mid: Message ID, that user reacted to
|
||||||
|
:param reaction: Reaction
|
||||||
|
:param add_reaction: Whether user added or removed reaction
|
||||||
|
:param author_id: The ID of the person who reacted to the message
|
||||||
|
:param thread_id: Thread ID that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param thread_type: Type of thread that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param ts: A timestamp of the action
|
||||||
|
:param msg: A full set of the data recieved
|
||||||
|
:type reaction: models.MessageReaction
|
||||||
|
:type thread_type: models.ThreadType
|
||||||
|
"""
|
||||||
|
log.info("{} reacted to message {} with {} in {} ({})".format(author_id, mid, reaction.name, thread_id, thread_type.name))
|
||||||
|
|
||||||
|
def onReactionRemoved(self, mid=None, author_id=None, thread_id=None, thread_type=None, ts=None, msg=None):
|
||||||
|
"""
|
||||||
|
Called when the client is listening, and somebody removes reaction from a message
|
||||||
|
|
||||||
|
:param mid: Message ID, that user reacted to
|
||||||
|
:param author_id: The ID of the person who removed reaction
|
||||||
|
:param thread_id: Thread ID that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param thread_type: Type of thread that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param ts: A timestamp of the action
|
||||||
|
:param msg: A full set of the data recieved
|
||||||
|
:type thread_type: models.ThreadType
|
||||||
|
"""
|
||||||
|
log.info("{} removed reaction from {} message in {} ({})".format(author_id, mid, thread_id, thread_type))
|
||||||
|
|
||||||
|
def onBlock(self, author_id=None, thread_id=None, thread_type=None, ts=None, msg=None):
|
||||||
|
"""
|
||||||
|
Called when the client is listening, and somebody blocks client
|
||||||
|
|
||||||
|
:param author_id: The ID of the person who blocked
|
||||||
|
:param thread_id: Thread ID that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param thread_type: Type of thread that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param ts: A timestamp of the action
|
||||||
|
:param msg: A full set of the data recieved
|
||||||
|
:type thread_type: models.ThreadType
|
||||||
|
"""
|
||||||
|
log.info("{} blocked {} ({}) thread".format(author_id, thread_id, thread_type.name))
|
||||||
|
|
||||||
|
def onUnblock(self, author_id=None, thread_id=None, thread_type=None, ts=None, msg=None):
|
||||||
|
"""
|
||||||
|
Called when the client is listening, and somebody blocks client
|
||||||
|
|
||||||
|
:param author_id: The ID of the person who unblocked
|
||||||
|
:param thread_id: Thread ID that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param thread_type: Type of thread that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param ts: A timestamp of the action
|
||||||
|
:param msg: A full set of the data recieved
|
||||||
|
:type thread_type: models.ThreadType
|
||||||
|
"""
|
||||||
|
log.info("{} unblocked {} ({}) thread".format(author_id, thread_id, thread_type.name))
|
||||||
|
|
||||||
|
def onLiveLocation(self, mid=None, location=None, author_id=None, thread_id=None, thread_type=None, ts=None, msg=None):
|
||||||
|
"""
|
||||||
|
Called when the client is listening and somebody sends live location info
|
||||||
|
|
||||||
|
:param mid: The action ID
|
||||||
|
:param location: Sent location info
|
||||||
|
:param author_id: The ID of the person who sent location info
|
||||||
|
:param thread_id: Thread ID that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param thread_type: Type of thread that the action was sent to. See :ref:`intro_threads`
|
||||||
|
:param ts: A timestamp of the action
|
||||||
|
:param msg: A full set of the data recieved
|
||||||
|
:type location: models.LiveLocationAttachment
|
||||||
|
:type thread_type: models.ThreadType
|
||||||
|
"""
|
||||||
|
log.info("{} sent live location info in {} ({}) with latitude {} and longitude {}".format(author_id, thread_id, thread_type, location.latitude, location.longitude))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
END EVENTS
|
END EVENTS
|
||||||
"""
|
"""
|
||||||
|
Reference in New Issue
Block a user