Added attachment and mention support in onMessage

Deprecated `message` in `onMessage`
This commit is contained in:
Mads Marquart
2017-09-21 22:32:11 +02:00
parent c51a332560
commit a58791048a
6 changed files with 301 additions and 55 deletions

View File

@@ -175,8 +175,8 @@ meaning it will simply print information to the console when an event happens
The event actions can be changed by subclassing the :class:`Client`, and then overwriting the event methods::
class CustomClient(Client):
def onMessage(self, mid, author_id, message, thread_id, thread_type, ts, metadata, msg, **kwargs):
# Do something with the message here
def onMessage(self, mid, author_id, message_object, thread_id, thread_type, ts, metadata, msg, **kwargs):
# Do something with the message_object here
pass
client = CustomClient('<email>', '<password>')
@@ -184,13 +184,13 @@ The event actions can be changed by subclassing the :class:`Client`, and then ov
**Notice:** The following snippet is as equally valid as the previous one::
class CustomClient(Client):
def onMessage(self, message, author_id, thread_id, thread_type, **kwargs):
def onMessage(self, message_object, author_id, thread_id, thread_type, **kwargs):
# Do something with the message here
pass
client = CustomClient('<email>', '<password>')
The change was in the parameters that our `onMessage` method took: ``message`` and ``author_id`` got swapped,
The change was in the parameters that our `onMessage` method took: ``message_object`` and ``author_id`` got swapped,
and ``mid``, ``ts``, ``metadata`` and ``msg`` got removed, but the function still works, since we included ``**kwargs``
.. note::

View File

@@ -4,15 +4,15 @@ from fbchat import log, Client
# Subclass fbchat.Client and override required methods
class EchoBot(Client):
def onMessage(self, author_id, message, thread_id, thread_type, **kwargs):
def onMessage(self, author_id, message_object, thread_id, thread_type, **kwargs):
self.markAsDelivered(author_id, thread_id)
self.markAsRead(author_id)
log.info("Message from {} in {} ({}): {}".format(author_id, thread_id, thread_type.name, message))
log.info("{} from {} in {}".format(message_object, thread_id, thread_type.name))
# If you're not the author, echo
if author_id != self.uid:
self.sendMessage(message, thread_id=thread_id, thread_type=thread_type)
# If you're not the author, and the message was a message containing text, echo
if author_id != self.uid and message_object.text is not None:
self.sendMessage(message_object.text, thread_id=thread_id, thread_type=thread_type)
client = EchoBot("<email>", "<password>")
client.listen()

View File

@@ -4,14 +4,14 @@ from fbchat import log, Client
from fbchat.models import *
class RemoveBot(Client):
def onMessage(self, author_id, message, thread_id, thread_type, **kwargs):
def onMessage(self, author_id, message_object, thread_id, thread_type, **kwargs):
# We can only kick people from group chats, so no need to try if it's a user chat
if message == 'Remove me!' and thread_type == ThreadType.GROUP:
if message_object.text == 'Remove me!' and thread_type == ThreadType.GROUP:
log.info('{} will be removed from {}'.format(author_id, thread_id))
self.removeUserFromGroup(author_id, thread_id=thread_id)
else:
# Sends the data to the inherited onMessage, so that we can still see when a message is recieved
super(type(self), self).onMessage(author_id=author_id, message=message, thread_id=thread_id, thread_type=thread_type, **kwargs)
super(RemoveBot, self).onMessage(author_id=author_id, message_object=message_object, thread_id=thread_id, thread_type=thread_type, **kwargs)
client = RemoveBot("<email>", "<password>")
client.listen()

View File

@@ -12,6 +12,7 @@ from .utils import *
from .models import *
from .graphql import *
import time
import json
@@ -638,7 +639,7 @@ class Client(object):
}))
j = self.graphql_requests(*queries)
for i, entry in enumerate(j):
if entry.get('message_thread') is None:
# If you don't have an existing thread with this person, attempt to retrieve user data anyways
@@ -1279,7 +1280,7 @@ class Client(object):
delta_type = delta.get("type")
metadata = delta.get("messageMetadata")
if metadata is not None:
if metadata:
mid = metadata["messageId"]
author_id = str(metadata['actorFbId'])
ts = int(metadata.get("timestamp"))
@@ -1360,9 +1361,91 @@ class Client(object):
# New message
elif delta.get("class") == "NewMessage":
message = delta.get('body', '')
mentions = []
if delta.get('data') and delta['data'].get('prng'):
try:
mentions = [Mention(str(mention.get('i')), offset=mention.get('o'), length=mention.get('l')) for mention in json.loads(delta['data']['prng'])]
except Exception:
log.exception('An exception occured while reading attachments')
attachments = []
if delta.get('attachments'):
try:
for a in delta['attachments']:
mercury = a['mercury']
blob = mercury.get('blob_attachment', {})
image_metadata = a.get('imageMetadata', {})
attach_type = mercury['attach_type']
if attach_type in ['photo', 'animated_image']:
attachments.append(ImageAttachment(
original_extension=blob.get('original_extension') or (blob['filename'].split('-')[0] if blob.get('filename') else None),
width=int(image_metadata['width']),
height=int(image_metadata['height']),
is_animated=attach_type=='animated_image',
thumbnail_url=mercury.get('thumbnail_url'),
preview=blob.get('preview') or blob.get('preview_image'),
large_preview=blob.get('large_preview'),
animated_preview=blob.get('animated_image'),
uid=a['id']
))
elif attach_type == 'file':
# Add more data here for audio files
attachments.append(FileAttachment(
url=mercury.get('url'),
size=int(a['fileSize']),
name=mercury.get('name'),
is_malicious=blob.get('is_malicious'),
uid=a['id']
))
elif attach_type == 'video':
attachments.append(VideoAttachment(
size=int(a['fileSize']),
width=int(image_metadata['width']),
height=int(image_metadata['height']),
duration=blob.get('playable_duration_in_ms'),
preview_url=blob.get('playable_url'),
small_image=blob.get('chat_image'),
medium_image=blob.get('inbox_image'),
large_image=blob.get('large_image'),
uid=a['id']
))
elif attach_type == 'sticker':
# Add more data here for stickers
attachments.append(StickerAttachment(
uid=mercury.get('metadata', {}).get('stickerID')
))
elif attach_type == 'share':
# Add more data here for shared stuff (URLs, events and so on)
attachments.append(ShareAttachment(
uid=a.get('id')
))
else:
attachments.append(Attachment(
uid=a.get('id')
))
except Exception:
log.exception('An exception occured while reading attachments: {}'.format(delta['attachments']))
emoji_size = None
if metadata and metadata.get('tags'):
for tag in metadata['tags']:
if tag.startswith('hot_emoji_size:'):
emoji_size = LIKES[tag.split(':')[1]]
break
message = Message(
text=delta.get('body'),
mentions=mentions,
emoji_size=emoji_size
)
message.uid = mid
message.author = author_id
message.timestamp = ts
message.attachments = attachments
#message.is_read = None
#message.reactions = []
thread_id, thread_type = getThreadIdAndThreadType(metadata)
self.onMessage(mid=mid, author_id=author_id, message=message,
self.onMessage(mid=mid, author_id=author_id, message=delta.get('body', ''), message_object=message,
thread_id=thread_id, thread_type=thread_type, ts=ts, metadata=metadata, msg=m)
# Unknown message type
@@ -1509,21 +1592,23 @@ class Client(object):
log.exception('Got exception while listening')
def onMessage(self, mid=None, author_id=None, message=None, thread_id=None, thread_type=ThreadType.USER, ts=None, metadata=None, msg={}):
def onMessage(self, mid=None, author_id=None, message=None, message_object=None, thread_id=None, thread_type=ThreadType.USER, ts=None, metadata=None, msg={}):
"""
Called when the client is listening, and somebody sends a message
:param mid: The message ID
:param author_id: The ID of the author
:param message: The message
:param message: (deprecated. Use `message_object.text` instead)
:param message_object: The message (As a `Message` object)
:param thread_id: Thread ID that the message was sent to. See :ref:`intro_threads`
:param thread_type: Type of thread that the message was sent to. See :ref:`intro_threads`
:param ts: The timestamp of the message
:param metadata: Extra metadata about the message
:param msg: A full set of the data recieved
:type message_object: models.Message
:type thread_type: models.ThreadType
"""
log.info("Message from {} in {} ({}): {}".format(author_id, thread_id, thread_type.name, message))
log.info("{} from {} in {}".format(message_object, thread_id, thread_type.name))
def onColorChange(self, mid=None, author_id=None, new_color=None, thread_id=None, thread_type=ThreadType.USER, ts=None, metadata=None, msg={}):
"""

View File

@@ -112,6 +112,10 @@ class Page(Thread):
class Message(object):
#: The actual message
text = str
#: A list of :class:`Mention` objects
mentions = list
#: The message ID
uid = str
#: ID of the sender
@@ -122,29 +126,185 @@ class Message(object):
is_read = bool
#: A list of message reactions
reactions = list
#: The actual message
text = str
#: A list of :class:`Mention` objects
mentions = list
#: An ID of a sent sticker
sticker = str
#: A :class:`EmojiSize`. Size of a sent emoji
emoji_size = None
#: A list of attachments
attachments = list
#: An extensible attachment, e.g. share object
extensible_attachment = dict
def __init__(self, uid, author=None, timestamp=None, is_read=None, reactions=[], text=None, mentions=[], sticker=None, attachments=[], extensible_attachment={}):
def __init__(self, text=None, mentions=[], emoji_size=None):
"""Represents a Facebook message"""
self.uid = uid
self.author = author
self.timestamp = timestamp
self.is_read = is_read
self.reactions = reactions
self.text = text
self.mentions = mentions
self.sticker = sticker
self.attachments = attachments
self.extensible_attachment = extensible_attachment
self.emoji_size = emoji_size
def __repr__(self):
return self.__unicode__()
def __unicode__(self):
return '<Message ({}): {}, mentions={} emoji_size={} attachments={}>'.format(self.uid, repr(self.text), self.mentions, self.emoji_size, self.attachments)
class Attachment(object):
#: The attachment ID
uid = str
def __init__(self, uid=None, mime_type=None):
"""Represents a Facebook attachment"""
self.uid = uid
class StickerAttachment(Attachment):
def __init__(self, **kwargs):
"""Represents a sticker that has been sent as a Facebook attachment - *Currently Incomplete!*"""
super(StickerAttachment, self).__init__(**kwargs)
class ShareAttachment(Attachment):
def __init__(self, **kwargs):
"""Represents a shared item (eg. URL) that has been sent as a Facebook attachment - *Currently Incomplete!*"""
super(ShareAttachment, self).__init__(**kwargs)
class FileAttachment(Attachment):
#: Url where you can download the file
url = str
#: Size of the file in bytes
size = int
#: Name of the file
name = str
#: Whether Facebook determines that this file may be harmful
is_malicious = bool
def __init__(self, url=None, size=None, name=None, is_malicious=None, **kwargs):
"""Represents a file that has been sent as a Facebook attachment"""
super(FileAttachment, self).__init__(**kwargs)
self.url = url
self.size = size
self.name = name
self.is_malicious = is_malicious
class AudioAttachment(FileAttachment):
def __init__(self, **kwargs):
"""Represents an audio file that has been sent as a Facebook attachment - *Currently Incomplete!*"""
super(StickerAttachment, self).__init__(**kwargs)
class ImageAttachment(Attachment):
#: The extension of the original image (eg. 'png')
original_extension = str
#: Width of original image
width = int
#: Height of original image
height = int
#: Whether the image is animated
is_animated = bool
#: Url to a thumbnail of the image
thumbnail_url = str
#: URL to a medium preview of the image
preview_url = str
#: Width of the medium preview image
preview_width = int
#: Height of the medium preview image
preview_height = int
#: URL to a large preview of the image
large_preview_url = str
#: Width of the large preview image
large_preview_width = int
#: Height of the large preview image
large_preview_height = int
#: URL to an animated preview of the image (eg. for gifs)
animated_preview_url = str
#: Width of the animated preview image
animated_preview_width = int
#: Height of the animated preview image
animated_preview_height = int
def __init__(self, original_extension=None, width=None, height=None, is_animated=None, thumbnail_url=None, preview=None, large_preview=None, animated_preview=None, **kwargs):
"""Represents an image that has been sent as a Facebook attachment"""
super(ImageAttachment, self).__init__(**kwargs)
self.original_extension = original_extension
self.width = width
self.height = height
self.is_animated = is_animated
self.thumbnail_url = thumbnail_url
if preview is None:
preview = {}
self.preview_url = preview.get('uri')
self.preview_width = preview.get('width')
self.preview_height = preview.get('height')
if large_preview is None:
large_preview = {}
self.large_preview_url = large_preview.get('uri')
self.large_preview_width = large_preview.get('width')
self.large_preview_height = large_preview.get('height')
if animated_preview is None:
animated_preview = {}
self.animated_preview_url = animated_preview.get('uri')
self.animated_preview_width = animated_preview.get('width')
self.animated_preview_height = animated_preview.get('height')
class VideoAttachment(Attachment):
#: Size of the original video in bytes
size = int
#: Width of original video
width = int
#: Height of original video
height = int
#: Length of video in milliseconds
duration = int
#: URL to very compressed preview video
preview_url = str
#: URL to a small preview image of the video
small_image_url = str
#: Width of the small preview image
small_image_width = int
#: Height of the small preview image
small_image_height = int
#: URL to a medium preview image of the video
medium_image_url = str
#: Width of the medium preview image
medium_image_width = int
#: Height of the medium preview image
medium_image_height = int
#: URL to a large preview image of the video
large_image_url = str
#: Width of the large preview image
large_image_width = int
#: Height of the large preview image
large_image_height = int
def __init__(self, size=None, width=None, height=None, duration=None, preview_url=None, small_image=None, medium_image=None, large_image=None, **kwargs):
"""Represents a video that has been sent as a Facebook attachment"""
super(VideoAttachment, self).__init__(**kwargs)
self.size = size
self.width = width
self.height = height
self.duration = duration
self.preview_url = preview_url
if small_image is None:
small_image = {}
self.small_image_url = small_image.get('uri')
self.small_image_width = small_image.get('width')
self.small_image_height = small_image.get('height')
if medium_image is None:
medium_image = {}
self.medium_image_url = medium_image.get('uri')
self.medium_image_width = medium_image.get('width')
self.medium_image_height = medium_image.get('height')
if large_image is None:
large_image = {}
self.large_image_url = large_image.get('uri')
self.large_image_width = large_image.get('width')
self.large_image_height = large_image.get('height')
class Mention(object):
@@ -211,22 +371,3 @@ class MessageReaction(Enum):
ANGRY = '😠'
YES = '👍'
NO = '👎'
LIKES = {
'large': EmojiSize.LARGE,
'medium': EmojiSize.MEDIUM,
'small': EmojiSize.SMALL,
'l': EmojiSize.LARGE,
'm': EmojiSize.MEDIUM,
's': EmojiSize.SMALL
}
MessageReactionFix = {
'😍': ('0001f60d', '%F0%9F%98%8D'),
'😆': ('0001f606', '%F0%9F%98%86'),
'😮': ('0001f62e', '%F0%9F%98%AE'),
'😢': ('0001f622', '%F0%9F%98%A2'),
'😠': ('0001f620', '%F0%9F%98%A0'),
'👍': ('0001f44d', '%F0%9F%91%8D'),
'👎': ('0001f44e', '%F0%9F%91%8E')
}

View File

@@ -32,6 +32,26 @@ USER_AGENTS = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6"
]
LIKES = {
'large': EmojiSize.LARGE,
'medium': EmojiSize.MEDIUM,
'small': EmojiSize.SMALL,
'l': EmojiSize.LARGE,
'm': EmojiSize.MEDIUM,
's': EmojiSize.SMALL
}
MessageReactionFix = {
'😍': ('0001f60d', '%F0%9F%98%8D'),
'😆': ('0001f606', '%F0%9F%98%86'),
'😮': ('0001f62e', '%F0%9F%98%AE'),
'😢': ('0001f622', '%F0%9F%98%A2'),
'😠': ('0001f620', '%F0%9F%98%A0'),
'👍': ('0001f44d', '%F0%9F%91%8D'),
'👎': ('0001f44e', '%F0%9F%91%8E')
}
GENDERS = {
# For standard requests
0: 'unknown',