Added attachment and mention support in onMessage
Deprecated `message` in `onMessage`
This commit is contained in:
@@ -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::
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
@@ -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={}):
|
||||
"""
|
||||
|
213
fbchat/models.py
213
fbchat/models.py
@@ -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')
|
||||
}
|
||||
|
@@ -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',
|
||||
|
Reference in New Issue
Block a user