Compare commits

...

13 Commits

Author SHA1 Message Date
Mads Marquart
0b0d6179a2 Version up, thanks to @sdnian
`fetchThreadMessages` and `listen` can now parse AudioAttachments
2018-01-30 17:20:47 +01:00
Mads Marquart
e8806d4ef8 Merge pull request #254 from sdnian/bransh1
modify AudioAttachment function
2018-01-30 17:15:55 +01:00
Steve Nian
c96e5f174c update 2018-01-30 20:22:18 +08:00
Steve Nian
315242e069 update 2018-01-30 20:17:09 +08:00
Steve Nian
a94fa5fbe3 AudioAttachment 2018-01-30 17:33:29 +08:00
Mads Marquart
90203afdd0 Fixes documentation error 2018-01-23 20:20:13 +01:00
Mads Marquart
2c0d098852 Fixes #240, small backwards-compatablitity issue when sending images 2018-01-08 21:55:11 +01:00
Mads Marquart
e4290cd465 Version up, thanks to @lobstr 2018-01-02 13:40:50 +01:00
Mads Marquart
46b85dec5c Merge remote-tracking branch 'lobstr/master' 2018-01-02 13:40:25 +01:00
Mads Marquart
bbc34bd009 Added onTyping method 2018-01-02 13:33:13 +01:00
cirrux
c495317e65 Fix setTypingStatus to send correctly 2018-01-01 23:11:35 -05:00
cirrux
a946050228 Re-enable typing notification 2017-12-31 12:27:55 -05:00
cirrux
83789dcefa Fix attachment parsing for newer structure 2017-12-26 19:12:10 -05:00
4 changed files with 129 additions and 91 deletions

View File

@@ -17,7 +17,7 @@ from .client import *
__copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year) __copyright__ = 'Copyright 2015 - {} by Taehoon Kim'.format(datetime.now().year)
__version__ = '1.1.3' __version__ = '1.3.0'
__license__ = 'BSD' __license__ = 'BSD'
__author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart' __author__ = 'Taehoon Kim; Moreels Pieter-Jan; Mads Marquart'
__email__ = 'carpedm20@gmail.com' __email__ = 'carpedm20@gmail.com'

View File

@@ -987,7 +987,7 @@ class Client(object):
Deprecated. Use :func:`fbchat.Client.send` instead Deprecated. Use :func:`fbchat.Client.send` instead
""" """
thread_id, thread_type = self._getThread(thread_id, thread_type) thread_id, thread_type = self._getThread(thread_id, thread_type)
data = self._getSendData(message=message, thread_id=thread_id, thread_type=thread_type) data = self._getSendData(message=self._oldMessage(message), thread_id=thread_id, thread_type=thread_type)
data['action_type'] = 'ma-type:user-generated-message' data['action_type'] = 'ma-type:user-generated-message'
data['has_attachment'] = True data['has_attachment'] = True
@@ -1248,7 +1248,7 @@ class Client(object):
:type thread_type: models.ThreadType :type thread_type: models.ThreadType
:raises: FBchatException if request failed :raises: FBchatException if request failed
""" """
thread_id, thread_type = self._getThread(thread_id, None) thread_id, thread_type = self._getThread(thread_id, thread_type)
data = { data = {
"typ": status.value, "typ": status.value,
@@ -1482,21 +1482,20 @@ class Client(object):
try: try:
for a in delta['attachments']: for a in delta['attachments']:
mercury = a['mercury'] mercury = a['mercury']
if mercury.get('attach_type'): if mercury.get('blob_attachment'):
image_metadata = a.get('imageMetadata', {}) image_metadata = a.get('imageMetadata', {})
attach_type = mercury['attach_type'] attach_type = mercury['blob_attachment']['__typename']
if attach_type != 'share':
attachment = graphql_to_attachment(mercury.get('blob_attachment', {})) attachment = graphql_to_attachment(mercury.get('blob_attachment', {}))
else:
# TODO: Add more data here for shared stuff (URLs, events and so on)
pass
if attach_type == ['file', 'video']: if attach_type == ['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)
if a['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'):
# TODO: Add more data here for shared stuff (URLs, events and so on)
pass
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']))
@@ -1527,10 +1526,15 @@ class Client(object):
self.onInbox(unseen=m["unseen"], unread=m["unread"], recent_unread=m["recent_unread"], msg=m) self.onInbox(unseen=m["unseen"], unread=m["unread"], recent_unread=m["recent_unread"], msg=m)
# Typing # Typing
# elif mtype == "typ": elif mtype == "typ":
# author_id = str(m.get("from")) author_id = str(m.get("from"))
# typing_status = TypingStatus(m.get("st")) thread_id = str(m.get("to"))
# self.onTyping(author_id=author_id, typing_status=typing_status) if thread_id == self.uid:
thread_type = ThreadType.USER
else:
thread_type = ThreadType.GROUP
typing_status = TypingStatus(m.get("st"))
self.onTyping(author_id=author_id, status=typing_status, thread_id=thread_id, thread_type=thread_type, msg=m)
# Delivered # Delivered
@@ -1850,6 +1854,20 @@ class Client(object):
""" """
log.info('Inbox event: {}, {}, {}'.format(unseen, unread, recent_unread)) log.info('Inbox event: {}, {}, {}'.format(unseen, unread, recent_unread))
def onTyping(self, author_id=None, status=None, thread_id=None, thread_type=None, msg=None):
"""
Called when the client is listening, and somebody starts or stops typing into a chat
:param author_id: The ID of the person who sent the action
:param status: The typing status
: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 msg: A full set of the data recieved
:type typing_status: models.TypingStatus
:type thread_type: models.ThreadType
"""
pass
def onQprimer(self, ts=None, msg=None): def onQprimer(self, ts=None, msg=None):
""" """
Called when the client just started listening Called when the client just started listening

View File

@@ -109,6 +109,13 @@ def graphql_to_attachment(a):
large_image=a.get('large_image'), large_image=a.get('large_image'),
uid=a.get('legacy_attachment_id') uid=a.get('legacy_attachment_id')
) )
elif _type == 'MessageAudio':
return AudioAttachment(
filename=a.get('filename'),
url=a.get('playable_url'),
duration=a.get('playable_duration_in_ms'),
audio_type=a.get('audio_type')
)
elif _type == 'MessageFile': elif _type == 'MessageFile':
return FileAttachment( return FileAttachment(
url=a.get('url'), url=a.get('url'),

View File

@@ -9,11 +9,11 @@ class FBchatException(Exception):
class FBchatFacebookError(FBchatException): class FBchatFacebookError(FBchatException):
#: The error code that Facebook returned #: The error code that Facebook returned
fb_error_code = str fb_error_code = None
#: The error message that Facebook returned (In the user's own language) #: The error message that Facebook returned (In the user's own language)
fb_error_message = str fb_error_message = None
#: The status code that was sent in the http response (eg. 404) (Usually only set if not successful, aka. not 200) #: The status code that was sent in the http response (eg. 404) (Usually only set if not successful, aka. not 200)
request_status_code = int request_status_code = None
def __init__(self, message, fb_error_code=None, fb_error_message=None, request_status_code=None): def __init__(self, message, fb_error_code=None, fb_error_message=None, request_status_code=None):
super(FBchatFacebookError, self).__init__(message) super(FBchatFacebookError, self).__init__(message)
"""Thrown by fbchat when Facebook returns an error""" """Thrown by fbchat when Facebook returns an error"""
@@ -26,17 +26,17 @@ class FBchatUserError(FBchatException):
class Thread(object): class Thread(object):
#: The unique identifier of the thread. Can be used a `thread_id`. See :ref:`intro_threads` for more info #: The unique identifier of the thread. Can be used a `thread_id`. See :ref:`intro_threads` for more info
uid = str uid = None
#: Specifies the type of thread. Can be used a `thread_type`. See :ref:`intro_threads` for more info #: Specifies the type of thread. Can be used a `thread_type`. See :ref:`intro_threads` for more info
type = None type = None
#: The thread's picture #: A url to the thread's picture
photo = str photo = None
#: The name of the thread #: The name of the thread
name = str name = None
#: Timestamp of last message #: Timestamp of last message
last_message_timestamp = str last_message_timestamp = None
#: Number of messages in the thread #: Number of messages in the thread
message_count = int message_count = None
def __init__(self, _type, uid, photo=None, name=None, last_message_timestamp=None, message_count=None): def __init__(self, _type, uid, photo=None, name=None, last_message_timestamp=None, message_count=None):
"""Represents a Facebook thread""" """Represents a Facebook thread"""
self.uid = str(uid) self.uid = str(uid)
@@ -55,25 +55,25 @@ class Thread(object):
class User(Thread): class User(Thread):
#: The profile url #: The profile url
url = str url = None
#: The users first name #: The users first name
first_name = str first_name = None
#: The users last name #: The users last name
last_name = str last_name = None
#: Whether the user and the client are friends #: Whether the user and the client are friends
is_friend = bool is_friend = None
#: The user's gender #: The user's gender
gender = str gender = None
#: From 0 to 1. How close the client is to the user #: From 0 to 1. How close the client is to the user
affinity = float affinity = None
#: The user's nickname #: The user's nickname
nickname = str nickname = None
#: The clients nickname, as seen by the user #: The clients nickname, as seen by the user
own_nickname = str own_nickname = None
#: A :class:`ThreadColor`. The message color #: A :class:`ThreadColor`. The message color
color = None color = None
#: The default emoji #: The default emoji
emoji = str emoji = None
def __init__(self, uid, url=None, first_name=None, last_name=None, is_friend=None, gender=None, affinity=None, nickname=None, own_nickname=None, color=None, emoji=None, **kwargs): def __init__(self, uid, url=None, first_name=None, last_name=None, is_friend=None, gender=None, affinity=None, nickname=None, own_nickname=None, color=None, emoji=None, **kwargs):
"""Represents a Facebook user. Inherits `Thread`""" """Represents a Facebook user. Inherits `Thread`"""
@@ -92,13 +92,13 @@ class User(Thread):
class Group(Thread): class Group(Thread):
#: Unique list (set) of the group thread's participant user IDs #: Unique list (set) of the group thread's participant user IDs
participants = set participants = None
#: Dict, containing user nicknames mapped to their IDs #: A dict, containing user nicknames mapped to their IDs
nicknames = dict nicknames = None
#: A :class:`ThreadColor`. The groups's message color #: A :class:`ThreadColor`. The groups's message color
color = None color = None
#: The groups's default emoji #: The groups's default emoji
emoji = str emoji = None
def __init__(self, uid, participants=None, nicknames=None, color=None, emoji=None, **kwargs): def __init__(self, uid, participants=None, nicknames=None, color=None, emoji=None, **kwargs):
"""Represents a Facebook group. Inherits `Thread`""" """Represents a Facebook group. Inherits `Thread`"""
@@ -115,15 +115,15 @@ class Group(Thread):
class Room(Group): class Room(Group):
# Set containing user IDs of thread admins # Set containing user IDs of thread admins
admins = set admins = None
# True if users need approval to join # True if users need approval to join
approval_mode = bool approval_mode = None
# Set containing user IDs requesting to join # Set containing user IDs requesting to join
approval_requests = set approval_requests = None
# Link for joining room # Link for joining room
join_link = str join_link = None
# True is room is not discoverable # True is room is not discoverable
privacy_mode = bool privacy_mode = None
def __init__(self, uid, admins=None, approval_mode=None, approval_requests=None, join_link=None, privacy_mode=None, **kwargs): def __init__(self, uid, admins=None, approval_mode=None, approval_requests=None, join_link=None, privacy_mode=None, **kwargs):
"""Represents a Facebook room. Inherits `Group`""" """Represents a Facebook room. Inherits `Group`"""
@@ -142,15 +142,15 @@ class Room(Group):
class Page(Thread): class Page(Thread):
#: The page's custom url #: The page's custom url
url = str url = None
#: The name of the page's location city #: The name of the page's location city
city = str city = None
#: Amount of likes the page has #: Amount of likes the page has
likes = int likes = None
#: Some extra information about the page #: Some extra information about the page
sub_title = str sub_title = None
#: The page's category #: The page's category
category = str category = None
def __init__(self, uid, url=None, city=None, likes=None, sub_title=None, category=None, **kwargs): def __init__(self, uid, url=None, city=None, likes=None, sub_title=None, category=None, **kwargs):
"""Represents a Facebook page. Inherits `Thread`""" """Represents a Facebook page. Inherits `Thread`"""
@@ -166,7 +166,7 @@ class Message(object):
#: The actual message #: The actual message
text = None text = None
#: A list of :class:`Mention` objects #: A list of :class:`Mention` objects
mentions = [] mentions = None
#: A :class:`EmojiSize`. Size of a sent emoji #: A :class:`EmojiSize`. Size of a sent emoji
emoji_size = None emoji_size = None
#: The message ID #: The message ID
@@ -178,13 +178,13 @@ class Message(object):
#: Whether the message is read #: Whether the message is read
is_read = None is_read = None
#: A dict with user's IDs as keys, and their :class:`MessageReaction` as values #: A dict with user's IDs as keys, and their :class:`MessageReaction` as values
reactions = {} reactions = None
#: The actual message #: The actual message
text = None text = None
#: A :class:`Sticker` #: A :class:`Sticker`
sticker = None sticker = None
#: A list of attachments #: A list of attachments
attachments = [] attachments = 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):
"""Represents a Facebook message""" """Represents a Facebook message"""
@@ -207,7 +207,7 @@ class Message(object):
class Attachment(object): class Attachment(object):
#: The attachment ID #: The attachment ID
uid = str uid = None
def __init__(self, uid=None): def __init__(self, uid=None):
"""Represents a Facebook attachment""" """Represents a Facebook attachment"""
@@ -251,13 +251,13 @@ class ShareAttachment(Attachment):
class FileAttachment(Attachment): class FileAttachment(Attachment):
#: Url where you can download the file #: Url where you can download the file
url = str url = None
#: Size of the file in bytes #: Size of the file in bytes
size = int size = None
#: Name of the file #: Name of the file
name = str name = None
#: Whether Facebook determines that this file may be harmful #: Whether Facebook determines that this file may be harmful
is_malicious = bool is_malicious = None
def __init__(self, url=None, size=None, name=None, is_malicious=None, **kwargs): def __init__(self, url=None, size=None, name=None, is_malicious=None, **kwargs):
"""Represents a file that has been sent as a Facebook attachment""" """Represents a file that has been sent as a Facebook attachment"""
@@ -267,45 +267,58 @@ class FileAttachment(Attachment):
self.name = name self.name = name
self.is_malicious = is_malicious self.is_malicious = is_malicious
class AudioAttachment(FileAttachment): class AudioAttachment(Attachment):
def __init__(self, **kwargs): #: Name of the file
"""Represents an audio file that has been sent as a Facebook attachment - *Currently Incomplete!*""" filename = None
super(StickerAttachment, self).__init__(**kwargs) #: Url of the audio file
url = None
#: Duration of the audioclip in milliseconds
duration = None
#: Audio type
audio_type = None
def __init__(self, filename=None, url=None, duration=None, audio_type=None, **kwargs):
"""Represents an audio file that has been sent as a Facebook attachment"""
super(AudioAttachment, self).__init__(**kwargs)
self.filename = filename
self.url = url
self.duration = duration
self.audio_type = audio_type
class ImageAttachment(Attachment): class ImageAttachment(Attachment):
#: The extension of the original image (eg. 'png') #: The extension of the original image (eg. 'png')
original_extension = str original_extension = None
#: Width of original image #: Width of original image
width = int width = None
#: Height of original image #: Height of original image
height = int height = None
#: Whether the image is animated #: Whether the image is animated
is_animated = bool is_animated = None
#: URL to a thumbnail of the image #: URL to a thumbnail of the image
thumbnail_url = str thumbnail_url = None
#: URL to a medium preview of the image #: URL to a medium preview of the image
preview_url = str preview_url = None
#: Width of the medium preview image #: Width of the medium preview image
preview_width = int preview_width = None
#: Height of the medium preview image #: Height of the medium preview image
preview_height = int preview_height = None
#: URL to a large preview of the image #: URL to a large preview of the image
large_preview_url = str large_preview_url = None
#: Width of the large preview image #: Width of the large preview image
large_preview_width = int large_preview_width = None
#: Height of the large preview image #: Height of the large preview image
large_preview_height = int large_preview_height = None
#: URL to an animated preview of the image (eg. for gifs) #: URL to an animated preview of the image (eg. for gifs)
animated_preview_url = str animated_preview_url = None
#: Width of the animated preview image #: Width of the animated preview image
animated_preview_width = int animated_preview_width = None
#: Height of the animated preview image #: Height of the animated preview image
animated_preview_height = int animated_preview_height = None
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): 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):
""" """
@@ -344,36 +357,36 @@ class ImageAttachment(Attachment):
class VideoAttachment(Attachment): class VideoAttachment(Attachment):
#: Size of the original video in bytes #: Size of the original video in bytes
size = int size = None
#: Width of original video #: Width of original video
width = int width = None
#: Height of original video #: Height of original video
height = int height = None
#: Length of video in milliseconds #: Length of video in milliseconds
duration = int duration = None
#: URL to very compressed preview video #: URL to very compressed preview video
preview_url = str preview_url = None
#: URL to a small preview image of the video #: URL to a small preview image of the video
small_image_url = str small_image_url = None
#: Width of the small preview image #: Width of the small preview image
small_image_width = int small_image_width = None
#: Height of the small preview image #: Height of the small preview image
small_image_height = int small_image_height = None
#: URL to a medium preview image of the video #: URL to a medium preview image of the video
medium_image_url = str medium_image_url = None
#: Width of the medium preview image #: Width of the medium preview image
medium_image_width = int medium_image_width = None
#: Height of the medium preview image #: Height of the medium preview image
medium_image_height = int medium_image_height = None
#: URL to a large preview image of the video #: URL to a large preview image of the video
large_image_url = str large_image_url = None
#: Width of the large preview image #: Width of the large preview image
large_image_width = int large_image_width = None
#: Height of the large preview image #: Height of the large preview image
large_image_height = int large_image_height = None
def __init__(self, size=None, width=None, height=None, duration=None, preview_url=None, small_image=None, medium_image=None, large_image=None, **kwargs): 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""" """Represents a video that has been sent as a Facebook attachment"""
@@ -405,11 +418,11 @@ class VideoAttachment(Attachment):
class Mention(object): class Mention(object):
#: The thread ID the mention is pointing at #: The thread ID the mention is pointing at
thread_id = str thread_id = None
#: The character where the mention starts #: The character where the mention starts
offset = int offset = None
#: The length of the mention #: The length of the mention
length = int length = None
def __init__(self, thread_id, offset=0, length=10): def __init__(self, thread_id, offset=0, length=10):
"""Represents a @mention""" """Represents a @mention"""