From 929c2137bf6cb0b66db2cebf0b48cc27931eb67d Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 24 Feb 2019 04:48:29 +0100 Subject: [PATCH 1/4] Move model docstrings into the class level, out of init --- docs/conf.py | 2 +- fbchat/_attachment.py | 9 ++++++--- fbchat/_file.py | 20 ++++++++++++-------- fbchat/_group.py | 6 ++++-- fbchat/_location.py | 9 ++++++--- fbchat/_message.py | 6 ++++-- fbchat/_page.py | 3 ++- fbchat/_plan.py | 3 ++- fbchat/_poll.py | 6 ++++-- fbchat/_quick_reply.py | 15 ++++++++++----- fbchat/_sticker.py | 3 ++- fbchat/_thread.py | 3 ++- fbchat/_user.py | 3 ++- 13 files changed, 57 insertions(+), 31 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9a50500..f2763f1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -174,5 +174,5 @@ html_sidebars = {"**": ["sidebar.html", "searchbox.html"]} html_show_sphinx = False html_show_sourcelink = False -autoclass_content = "init" +autoclass_content = "both" html_short_title = description diff --git a/fbchat/_attachment.py b/fbchat/_attachment.py index 20cd50e..e7fe37e 100644 --- a/fbchat/_attachment.py +++ b/fbchat/_attachment.py @@ -3,21 +3,25 @@ from __future__ import unicode_literals class Attachment(object): + """Represents a Facebook attachment""" + #: The attachment ID uid = None def __init__(self, uid=None): - """Represents a Facebook attachment""" self.uid = uid class UnsentMessage(Attachment): + """Represents an unsent message attachment""" + def __init__(self, *args, **kwargs): - """Represents an unsent message attachment""" super(UnsentMessage, self).__init__(*args, **kwargs) class ShareAttachment(Attachment): + """Represents a shared item (eg. URL) that has been sent as a Facebook attachment""" + #: ID of the author of the shared post author = None #: Target URL @@ -56,7 +60,6 @@ class ShareAttachment(Attachment): attachments=None, **kwargs ): - """Represents a shared item (eg. URL) that has been sent as a Facebook attachment""" super(ShareAttachment, self).__init__(**kwargs) self.author = author self.url = url diff --git a/fbchat/_file.py b/fbchat/_file.py index 9acfd11..c73ae8d 100644 --- a/fbchat/_file.py +++ b/fbchat/_file.py @@ -5,6 +5,8 @@ from ._attachment import Attachment class FileAttachment(Attachment): + """Represents a file that has been sent as a Facebook attachment""" + #: Url where you can download the file url = None #: Size of the file in bytes @@ -15,7 +17,6 @@ class FileAttachment(Attachment): is_malicious = None 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 @@ -24,6 +25,8 @@ class FileAttachment(Attachment): class AudioAttachment(Attachment): + """Represents an audio file that has been sent as a Facebook attachment""" + #: Name of the file filename = None #: Url of the audio file @@ -36,7 +39,6 @@ class AudioAttachment(Attachment): 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 @@ -45,6 +47,12 @@ class AudioAttachment(Attachment): class ImageAttachment(Attachment): + """Represents an image that has been sent as a Facebook attachment + + To retrieve the full image url, use: :func:`fbchat.Client.fetchImageUrl`, and pass + it the uid of the image attachment + """ + #: The extension of the original image (eg. 'png') original_extension = None #: Width of original image @@ -91,11 +99,6 @@ class ImageAttachment(Attachment): animated_preview=None, **kwargs ): - """ - Represents an image that has been sent as a Facebook attachment - To retrieve the full image url, use: :func:`fbchat.Client.fetchImageUrl`, - and pass it the uid of the image attachment - """ super(ImageAttachment, self).__init__(**kwargs) self.original_extension = original_extension if width is not None: @@ -127,6 +130,8 @@ class ImageAttachment(Attachment): class VideoAttachment(Attachment): + """Represents a video that has been sent as a Facebook attachment""" + #: Size of the original video in bytes size = None #: Width of original video @@ -171,7 +176,6 @@ class VideoAttachment(Attachment): 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 diff --git a/fbchat/_group.py b/fbchat/_group.py index 1a21bf3..727504c 100644 --- a/fbchat/_group.py +++ b/fbchat/_group.py @@ -5,6 +5,8 @@ from ._thread import ThreadType, Thread class Group(Thread): + """Represents a Facebook group. Inherits `Thread`""" + #: Unique list (set) of the group thread's participant user IDs participants = None #: A dict, containing user nicknames mapped to their IDs @@ -36,7 +38,6 @@ class Group(Thread): privacy_mode=None, **kwargs ): - """Represents a Facebook group. Inherits `Thread`""" super(Group, self).__init__(ThreadType.GROUP, uid, **kwargs) if participants is None: participants = set() @@ -57,11 +58,12 @@ class Group(Thread): class Room(Group): + """Deprecated. Use :class:`Group` instead""" + # True is room is not discoverable privacy_mode = None def __init__(self, uid, privacy_mode=None, **kwargs): - """Deprecated. Use :class:`Group` instead""" super(Room, self).__init__(uid, **kwargs) self.type = ThreadType.ROOM self.privacy_mode = privacy_mode diff --git a/fbchat/_location.py b/fbchat/_location.py index b45a942..c31de64 100644 --- a/fbchat/_location.py +++ b/fbchat/_location.py @@ -5,7 +5,10 @@ from ._attachment import Attachment class LocationAttachment(Attachment): - """Latitude and longitude OR address is provided by Facebook""" + """Represents a user location + + Latitude and longitude OR address is provided by Facebook + """ #: Latitude of the location latitude = None @@ -23,7 +26,6 @@ class LocationAttachment(Attachment): address = None def __init__(self, latitude=None, longitude=None, address=None, **kwargs): - """Represents a user location""" super(LocationAttachment, self).__init__(**kwargs) self.latitude = latitude self.longitude = longitude @@ -31,6 +33,8 @@ class LocationAttachment(Attachment): class LiveLocationAttachment(LocationAttachment): + """Represents a live user location""" + #: Name of the location name = None #: Timestamp when live location expires @@ -39,7 +43,6 @@ class LiveLocationAttachment(LocationAttachment): is_expired = None def __init__(self, name=None, expiration_time=None, is_expired=None, **kwargs): - """Represents a live user location""" super(LiveLocationAttachment, self).__init__(**kwargs) self.expiration_time = expiration_time self.is_expired = is_expired diff --git a/fbchat/_message.py b/fbchat/_message.py index c26b772..ec199fd 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -26,6 +26,8 @@ class MessageReaction(Enum): class Mention(object): + """Represents a @mention""" + #: The thread ID the mention is pointing at thread_id = None #: The character where the mention starts @@ -34,7 +36,6 @@ class Mention(object): length = None def __init__(self, thread_id, offset=0, length=10): - """Represents a @mention""" self.thread_id = thread_id self.offset = offset self.length = length @@ -49,6 +50,8 @@ class Mention(object): class Message(object): + """Represents a Facebook message""" + #: The actual message text = None #: A list of :class:`Mention` objects @@ -87,7 +90,6 @@ class Message(object): attachments=None, quick_replies=None, ): - """Represents a Facebook message""" self.text = text if mentions is None: mentions = [] diff --git a/fbchat/_page.py b/fbchat/_page.py index 53a2bf1..3942559 100644 --- a/fbchat/_page.py +++ b/fbchat/_page.py @@ -5,6 +5,8 @@ from ._thread import ThreadType, Thread class Page(Thread): + """Represents a Facebook page. Inherits `Thread`""" + #: The page's custom url url = None #: The name of the page's location city @@ -26,7 +28,6 @@ class Page(Thread): category=None, **kwargs ): - """Represents a Facebook page. Inherits `Thread`""" super(Page, self).__init__(ThreadType.PAGE, uid, **kwargs) self.url = url self.city = city diff --git a/fbchat/_plan.py b/fbchat/_plan.py index d9524dc..21425be 100644 --- a/fbchat/_plan.py +++ b/fbchat/_plan.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals class Plan(object): + """Represents a plan""" + #: ID of the plan uid = None #: Plan time (unix time stamp), only precise down to the minute @@ -23,7 +25,6 @@ class Plan(object): invited = None def __init__(self, time, title, location=None, location_id=None): - """Represents a plan""" self.time = int(time) self.title = title self.location = location or "" diff --git a/fbchat/_poll.py b/fbchat/_poll.py index 1276e1a..3d1bf05 100644 --- a/fbchat/_poll.py +++ b/fbchat/_poll.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals class Poll(object): + """Represents a poll""" + #: ID of the poll uid = None #: Title of the poll @@ -13,7 +15,6 @@ class Poll(object): options_count = None def __init__(self, title, options): - """Represents a poll""" self.title = title self.options = options @@ -27,6 +28,8 @@ class Poll(object): class PollOption(object): + """Represents a poll option""" + #: ID of the poll option uid = None #: Text of the poll option @@ -39,7 +42,6 @@ class PollOption(object): votes_count = None def __init__(self, text, vote=False): - """Represents a poll option""" self.text = text self.vote = vote diff --git a/fbchat/_quick_reply.py b/fbchat/_quick_reply.py index 3c3cbf1..11ab094 100644 --- a/fbchat/_quick_reply.py +++ b/fbchat/_quick_reply.py @@ -5,6 +5,8 @@ from ._attachment import Attachment class QuickReply(object): + """Represents a quick reply""" + #: Payload of the quick reply payload = None #: External payload for responses @@ -15,7 +17,6 @@ class QuickReply(object): 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 @@ -28,6 +29,8 @@ class QuickReply(object): class QuickReplyText(QuickReply): + """Represents a text quick reply""" + #: Title of the quick reply title = None #: URL of the quick reply image (optional) @@ -36,41 +39,43 @@ class QuickReplyText(QuickReply): _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): + """Represents a location quick reply (Doesn't work on mobile)""" + #: 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): + """Represents a phone number quick reply (Doesn't work on mobile)""" + #: 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): + """Represents an email quick reply (Doesn't work on mobile)""" + #: 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 diff --git a/fbchat/_sticker.py b/fbchat/_sticker.py index e575681..fadab3d 100644 --- a/fbchat/_sticker.py +++ b/fbchat/_sticker.py @@ -5,6 +5,8 @@ from ._attachment import Attachment class Sticker(Attachment): + """Represents a Facebook sticker that has been sent to a thread as an attachment""" + #: The sticker-pack's ID pack = None #: Whether the sticker is animated @@ -32,5 +34,4 @@ class Sticker(Attachment): label = None def __init__(self, *args, **kwargs): - """Represents a Facebook sticker that has been sent to a Facebook thread as an attachment""" super(Sticker, self).__init__(*args, **kwargs) diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 37efd6d..040493e 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -43,6 +43,8 @@ class ThreadColor(Enum): class Thread(object): + """Represents a Facebook thread""" + #: The unique identifier of the thread. Can be used a `thread_id`. See :ref:`intro_threads` for more info uid = None #: Specifies the type of thread. Can be used a `thread_type`. See :ref:`intro_threads` for more info @@ -68,7 +70,6 @@ class Thread(object): message_count=None, plan=None, ): - """Represents a Facebook thread""" self.uid = str(uid) self.type = _type self.photo = photo diff --git a/fbchat/_user.py b/fbchat/_user.py index 75a95c6..0da1f3f 100644 --- a/fbchat/_user.py +++ b/fbchat/_user.py @@ -13,6 +13,8 @@ class TypingStatus(Enum): class User(Thread): + """Represents a Facebook user. Inherits `Thread`""" + #: The profile url url = None #: The users first name @@ -49,7 +51,6 @@ class User(Thread): emoji=None, **kwargs ): - """Represents a Facebook user. Inherits `Thread`""" super(User, self).__init__(ThreadType.USER, uid, **kwargs) self.url = url self.first_name = first_name From f916cb3b53e7f228281aa805cc55d56b07695dc9 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 24 Feb 2019 04:50:17 +0100 Subject: [PATCH 2/4] Add `attrs` as dependency --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index d1cc257..6b2913d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ maintainer-email = "madsmtm@gmail.com" home-page = "https://github.com/carpedm20/fbchat/" requires = [ "aenum", + "attrs", "requests", "beautifulsoup4", ] From 8ae84359401f60cc74dc40f1fd959b702e02021a Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 24 Feb 2019 05:17:16 +0100 Subject: [PATCH 3/4] Use attrs, to remove verbose __init__ and __repr__ methods Backwards compatibility is strictly preserved in `__init__`, including parameter names, defaults and position. Whenever that's difficult using `attrs`, the custom `__init__` is kept instead (for the time being). `__repr__` methods have changed to the format `attrs` use, but people don't rely on this for anything other than debug output, so it shouldn't be a problem. --- fbchat/_attachment.py | 66 +++++++++-------------------- fbchat/_file.py | 95 ++++++++++++++++++++---------------------- fbchat/_group.py | 23 ++++++---- fbchat/_location.py | 30 ++++++------- fbchat/_message.py | 83 +++++++++--------------------------- fbchat/_page.py | 12 +++--- fbchat/_plan.py | 43 ++++++------------- fbchat/_poll.py | 46 ++++++-------------- fbchat/_quick_reply.py | 33 +++++++-------- fbchat/_sticker.py | 28 +++++++------ fbchat/_thread.py | 22 ++++------ fbchat/_user.py | 42 +++++++------------ 12 files changed, 198 insertions(+), 325 deletions(-) diff --git a/fbchat/_attachment.py b/fbchat/_attachment.py index e7fe37e..4ee40b2 100644 --- a/fbchat/_attachment.py +++ b/fbchat/_attachment.py @@ -1,76 +1,48 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr + +@attr.s(cmp=False) class Attachment(object): """Represents a Facebook attachment""" #: The attachment ID - uid = None - - def __init__(self, uid=None): - self.uid = uid + uid = attr.ib(None) +@attr.s(cmp=False) class UnsentMessage(Attachment): """Represents an unsent message attachment""" - def __init__(self, *args, **kwargs): - super(UnsentMessage, self).__init__(*args, **kwargs) - +@attr.s(cmp=False) class ShareAttachment(Attachment): """Represents a shared item (eg. URL) that has been sent as a Facebook attachment""" #: ID of the author of the shared post - author = None + author = attr.ib(None) #: Target URL - url = None + url = attr.ib(None) #: Original URL if Facebook redirects the URL - original_url = None + original_url = attr.ib(None) #: Title of the attachment - title = None + title = attr.ib(None) #: Description of the attachment - description = None + description = attr.ib(None) #: Name of the source - source = None + source = attr.ib(None) #: URL of the attachment image - image_url = None + image_url = attr.ib(None) #: URL of the original image if Facebook uses `safe_image` - original_image_url = None + original_image_url = attr.ib(None) #: Width of the image - image_width = None + image_width = attr.ib(None) #: Height of the image - image_height = None + image_height = attr.ib(None) #: List of additional attachments - attachments = None + attachments = attr.ib(factory=list, converter=lambda x: [] if x is None else x) - def __init__( - self, - author=None, - url=None, - original_url=None, - title=None, - description=None, - source=None, - image_url=None, - original_image_url=None, - image_width=None, - image_height=None, - attachments=None, - **kwargs - ): - super(ShareAttachment, self).__init__(**kwargs) - self.author = author - self.url = url - self.original_url = original_url - self.title = title - self.description = description - self.source = source - self.image_url = image_url - self.original_image_url = original_image_url - self.image_width = image_width - self.image_height = image_height - if attachments is None: - attachments = [] - self.attachments = attachments + # Put here for backwards compatibility, so that the init argument order is preserved + uid = attr.ib(None) diff --git a/fbchat/_file.py b/fbchat/_file.py index c73ae8d..85b2055 100644 --- a/fbchat/_file.py +++ b/fbchat/_file.py @@ -1,51 +1,45 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._attachment import Attachment +@attr.s(cmp=False) class FileAttachment(Attachment): """Represents a file that has been sent as a Facebook attachment""" #: Url where you can download the file - url = None + url = attr.ib(None) #: Size of the file in bytes - size = None + size = attr.ib(None) #: Name of the file - name = None + name = attr.ib(None) #: Whether Facebook determines that this file may be harmful - is_malicious = None + is_malicious = attr.ib(None) - def __init__(self, url=None, size=None, name=None, is_malicious=None, **kwargs): - super(FileAttachment, self).__init__(**kwargs) - self.url = url - self.size = size - self.name = name - self.is_malicious = is_malicious + # Put here for backwards compatibility, so that the init argument order is preserved + uid = attr.ib(None) +@attr.s(cmp=False) class AudioAttachment(Attachment): """Represents an audio file that has been sent as a Facebook attachment""" #: Name of the file - filename = None + filename = attr.ib(None) #: Url of the audio file - url = None + url = attr.ib(None) #: Duration of the audioclip in milliseconds - duration = None + duration = attr.ib(None) #: Audio type - audio_type = None + audio_type = attr.ib(None) - def __init__( - self, filename=None, url=None, duration=None, audio_type=None, **kwargs - ): - super(AudioAttachment, self).__init__(**kwargs) - self.filename = filename - self.url = url - self.duration = duration - self.audio_type = audio_type + # Put here for backwards compatibility, so that the init argument order is preserved + uid = attr.ib(None) +@attr.s(cmp=False, init=False) class ImageAttachment(Attachment): """Represents an image that has been sent as a Facebook attachment @@ -54,38 +48,38 @@ class ImageAttachment(Attachment): """ #: The extension of the original image (eg. 'png') - original_extension = None + original_extension = attr.ib(None) #: Width of original image - width = None + width = attr.ib(None, converter=lambda x: None if x is None else int(x)) #: Height of original image - height = None + height = attr.ib(None, converter=lambda x: None if x is None else int(x)) #: Whether the image is animated - is_animated = None + is_animated = attr.ib(None) #: URL to a thumbnail of the image - thumbnail_url = None + thumbnail_url = attr.ib(None) #: URL to a medium preview of the image - preview_url = None + preview_url = attr.ib(None) #: Width of the medium preview image - preview_width = None + preview_width = attr.ib(None) #: Height of the medium preview image - preview_height = None + preview_height = attr.ib(None) #: URL to a large preview of the image - large_preview_url = None + large_preview_url = attr.ib(None) #: Width of the large preview image - large_preview_width = None + large_preview_width = attr.ib(None) #: Height of the large preview image - large_preview_height = None + large_preview_height = attr.ib(None) #: URL to an animated preview of the image (eg. for gifs) - animated_preview_url = None + animated_preview_url = attr.ib(None) #: Width of the animated preview image - animated_preview_width = None + animated_preview_width = attr.ib(None) #: Height of the animated preview image - animated_preview_height = None + animated_preview_height = attr.ib(None) def __init__( self, @@ -129,40 +123,41 @@ class ImageAttachment(Attachment): self.animated_preview_height = animated_preview.get("height") +@attr.s(cmp=False, init=False) class VideoAttachment(Attachment): """Represents a video that has been sent as a Facebook attachment""" #: Size of the original video in bytes - size = None + size = attr.ib(None) #: Width of original video - width = None + width = attr.ib(None) #: Height of original video - height = None + height = attr.ib(None) #: Length of video in milliseconds - duration = None + duration = attr.ib(None) #: URL to very compressed preview video - preview_url = None + preview_url = attr.ib(None) #: URL to a small preview image of the video - small_image_url = None + small_image_url = attr.ib(None) #: Width of the small preview image - small_image_width = None + small_image_width = attr.ib(None) #: Height of the small preview image - small_image_height = None + small_image_height = attr.ib(None) #: URL to a medium preview image of the video - medium_image_url = None + medium_image_url = attr.ib(None) #: Width of the medium preview image - medium_image_width = None + medium_image_width = attr.ib(None) #: Height of the medium preview image - medium_image_height = None + medium_image_height = attr.ib(None) #: URL to a large preview image of the video - large_image_url = None + large_image_url = attr.ib(None) #: Width of the large preview image - large_image_width = None + large_image_width = attr.ib(None) #: Height of the large preview image - large_image_height = None + large_image_height = attr.ib(None) def __init__( self, diff --git a/fbchat/_group.py b/fbchat/_group.py index 727504c..62cf020 100644 --- a/fbchat/_group.py +++ b/fbchat/_group.py @@ -1,28 +1,32 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._thread import ThreadType, Thread +@attr.s(cmp=False, init=False) class Group(Thread): """Represents a Facebook group. Inherits `Thread`""" #: Unique list (set) of the group thread's participant user IDs - participants = None + participants = attr.ib(factory=set, converter=lambda x: set() if x is None else x) #: A dict, containing user nicknames mapped to their IDs - nicknames = None + nicknames = attr.ib(factory=dict, converter=lambda x: {} if x is None else x) #: A :class:`ThreadColor`. The groups's message color - color = None + color = attr.ib(None) #: The groups's default emoji - emoji = None + emoji = attr.ib(None) # Set containing user IDs of thread admins - admins = None + admins = attr.ib(factory=set, converter=lambda x: set() if x is None else x) # True if users need approval to join - approval_mode = None + approval_mode = attr.ib(None) # Set containing user IDs requesting to join - approval_requests = None + approval_requests = attr.ib( + factory=set, converter=lambda x: set() if x is None else x + ) # Link for joining group - join_link = None + join_link = attr.ib(None) def __init__( self, @@ -57,11 +61,12 @@ class Group(Thread): self.join_link = join_link +@attr.s(cmp=False, init=False) class Room(Group): """Deprecated. Use :class:`Group` instead""" # True is room is not discoverable - privacy_mode = None + privacy_mode = attr.ib(None) def __init__(self, uid, privacy_mode=None, **kwargs): super(Room, self).__init__(uid, **kwargs) diff --git a/fbchat/_location.py b/fbchat/_location.py index c31de64..5361bb5 100644 --- a/fbchat/_location.py +++ b/fbchat/_location.py @@ -1,9 +1,11 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._attachment import Attachment +@attr.s(cmp=False) class LocationAttachment(Attachment): """Represents a user location @@ -11,36 +13,34 @@ class LocationAttachment(Attachment): """ #: Latitude of the location - latitude = None + latitude = attr.ib(None) #: Longitude of the location - longitude = None + longitude = attr.ib(None) #: URL of image showing the map of the location - image_url = None + image_url = attr.ib(None, init=False) #: Width of the image - image_width = None + image_width = attr.ib(None, init=False) #: Height of the image - image_height = None + image_height = attr.ib(None, init=False) #: URL to Bing maps with the location - url = None + url = attr.ib(None, init=False) # Address of the location - address = None + address = attr.ib(None) - def __init__(self, latitude=None, longitude=None, address=None, **kwargs): - super(LocationAttachment, self).__init__(**kwargs) - self.latitude = latitude - self.longitude = longitude - self.address = address + # Put here for backwards compatibility, so that the init argument order is preserved + uid = attr.ib(None) +@attr.s(cmp=False, init=False) class LiveLocationAttachment(LocationAttachment): """Represents a live user location""" #: Name of the location - name = None + name = attr.ib(None) #: Timestamp when live location expires - expiration_time = None + expiration_time = attr.ib(None) #: True if live location is expired - is_expired = None + is_expired = attr.ib(None) def __init__(self, name=None, expiration_time=None, is_expired=None, **kwargs): super(LiveLocationAttachment, self).__init__(**kwargs) diff --git a/fbchat/_message.py b/fbchat/_message.py index ec199fd..3f6f03d 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -1,6 +1,7 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from string import Formatter from ._core import Enum @@ -25,94 +26,48 @@ class MessageReaction(Enum): NO = "👎" +@attr.s(cmp=False) class Mention(object): """Represents a @mention""" #: The thread ID the mention is pointing at - thread_id = None + thread_id = attr.ib() #: The character where the mention starts - offset = None + offset = attr.ib(0) #: The length of the mention - length = None - - def __init__(self, thread_id, offset=0, length=10): - self.thread_id = thread_id - self.offset = offset - self.length = length - - def __repr__(self): - return self.__unicode__() - - def __unicode__(self): - return "".format( - self.thread_id, self.offset, self.length - ) + length = attr.ib(10) +@attr.s(cmp=False) class Message(object): """Represents a Facebook message""" #: The actual message - text = None + text = attr.ib(None) #: A list of :class:`Mention` objects - mentions = None + mentions = attr.ib(factory=list, converter=lambda x: [] if x is None else x) #: A :class:`EmojiSize`. Size of a sent emoji - emoji_size = None + emoji_size = attr.ib(None) #: The message ID - uid = None + uid = attr.ib(None, init=False) #: ID of the sender - author = None + author = attr.ib(None, init=False) #: Timestamp of when the message was sent - timestamp = None + timestamp = attr.ib(None, init=False) #: Whether the message is read - is_read = None + is_read = attr.ib(None, init=False) #: A list of pepole IDs who read the message, works only with :func:`fbchat.Client.fetchThreadMessages` - read_by = None + read_by = attr.ib(factory=list, init=False) #: A dict with user's IDs as keys, and their :class:`MessageReaction` as values - reactions = None - #: The actual message - text = None + reactions = attr.ib(factory=dict, init=False) #: A :class:`Sticker` - sticker = None + sticker = attr.ib(None) #: A list of attachments - attachments = None + attachments = attr.ib(factory=list, converter=lambda x: [] if x is None else x) #: A list of :class:`QuickReply` - quick_replies = None + quick_replies = attr.ib(factory=list, converter=lambda x: [] if x is None else x) #: Whether the message is unsent (deleted for everyone) - unsent = None - - def __init__( - self, - text=None, - mentions=None, - emoji_size=None, - sticker=None, - attachments=None, - quick_replies=None, - ): - self.text = text - if mentions is None: - mentions = [] - self.mentions = mentions - self.emoji_size = emoji_size - self.sticker = sticker - 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 - - def __repr__(self): - return self.__unicode__() - - def __unicode__(self): - return "".format( - self.uid, repr(self.text), self.mentions, self.emoji_size, self.attachments - ) + unsent = attr.ib(False, init=False) @classmethod def formatMentions(cls, text, *args, **kwargs): diff --git a/fbchat/_page.py b/fbchat/_page.py index 3942559..9c696e0 100644 --- a/fbchat/_page.py +++ b/fbchat/_page.py @@ -1,22 +1,24 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._thread import ThreadType, Thread +@attr.s(cmp=False, init=False) class Page(Thread): """Represents a Facebook page. Inherits `Thread`""" #: The page's custom url - url = None + url = attr.ib(None) #: The name of the page's location city - city = None + city = attr.ib(None) #: Amount of likes the page has - likes = None + likes = attr.ib(None) #: Some extra information about the page - sub_title = None + sub_title = attr.ib(None) #: The page's category - category = None + category = attr.ib(None) def __init__( self, diff --git a/fbchat/_plan.py b/fbchat/_plan.py index 21425be..b6f7826 100644 --- a/fbchat/_plan.py +++ b/fbchat/_plan.py @@ -1,47 +1,28 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr + +@attr.s(cmp=False) class Plan(object): """Represents a plan""" #: ID of the plan - uid = None + uid = attr.ib(None, init=False) #: Plan time (unix time stamp), only precise down to the minute - time = None + time = attr.ib(converter=int) #: Plan title - title = None + title = attr.ib() #: Plan location name - location = None + location = attr.ib(None, converter=lambda x: x or "") #: Plan location ID - location_id = None + location_id = attr.ib(None, converter=lambda x: x or "") #: ID of the plan creator - author_id = None + author_id = attr.ib(None, init=False) #: List of the people IDs who will take part in the plan - going = None + going = attr.ib(factory=list, init=False) #: List of the people IDs who won't take part in the plan - declined = None + declined = attr.ib(factory=list, init=False) #: List of the people IDs who are invited to the plan - invited = None - - def __init__(self, time, title, location=None, location_id=None): - self.time = int(time) - self.title = title - self.location = location or "" - self.location_id = location_id or "" - self.author_id = None - self.going = [] - self.declined = [] - self.invited = [] - - def __repr__(self): - return self.__unicode__() - - def __unicode__(self): - return "".format( - self.uid, - repr(self.title), - self.time, - repr(self.location), - repr(self.location_id), - ) + invited = attr.ib(factory=list, init=False) diff --git a/fbchat/_poll.py b/fbchat/_poll.py index 3d1bf05..c2c02c0 100644 --- a/fbchat/_poll.py +++ b/fbchat/_poll.py @@ -1,54 +1,34 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr + +@attr.s(cmp=False) class Poll(object): """Represents a poll""" #: ID of the poll - uid = None + uid = attr.ib(None, init=False) #: Title of the poll - title = None + title = attr.ib() #: List of :class:`PollOption`, can be fetched with :func:`fbchat.Client.fetchPollOptions` - options = None + options = attr.ib() #: Options count - options_count = None - - def __init__(self, title, options): - self.title = title - self.options = options - - def __repr__(self): - return self.__unicode__() - - def __unicode__(self): - return "".format( - self.uid, repr(self.title), self.options - ) + options_count = attr.ib(None, init=False) +@attr.s(cmp=False) class PollOption(object): """Represents a poll option""" #: ID of the poll option - uid = None + uid = attr.ib(None, init=False) #: Text of the poll option - text = None + text = attr.ib() #: Whether vote when creating or client voted - vote = None + vote = attr.ib(False) #: ID of the users who voted for this poll option - voters = None + voters = attr.ib(None, init=False) #: Votes count - votes_count = None - - def __init__(self, text, vote=False): - self.text = text - self.vote = vote - - def __repr__(self): - return self.__unicode__() - - def __unicode__(self): - return "".format( - self.uid, repr(self.text), self.voters - ) + votes_count = attr.ib(None, init=False) diff --git a/fbchat/_quick_reply.py b/fbchat/_quick_reply.py index 11ab094..60323bf 100644 --- a/fbchat/_quick_reply.py +++ b/fbchat/_quick_reply.py @@ -1,40 +1,32 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._attachment import Attachment +@attr.s(cmp=False) class QuickReply(object): """Represents a quick reply""" #: Payload of the quick reply - payload = None + payload = attr.ib(None) #: External payload for responses - external_payload = None + external_payload = attr.ib(None, init=False) #: Additional data - data = None + data = attr.ib(None) #: Whether it's a response for a quick reply - is_response = None - - def __init__(self, payload=None, data=None, is_response=False): - 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) + is_response = attr.ib(False) +@attr.s(cmp=False, init=False) class QuickReplyText(QuickReply): """Represents a text quick reply""" #: Title of the quick reply - title = None + title = attr.ib(None) #: URL of the quick reply image (optional) - image_url = None + image_url = attr.ib(None) #: Type of the quick reply _type = "text" @@ -44,6 +36,7 @@ class QuickReplyText(QuickReply): self.image_url = image_url +@attr.s(cmp=False, init=False) class QuickReplyLocation(QuickReply): """Represents a location quick reply (Doesn't work on mobile)""" @@ -55,11 +48,12 @@ class QuickReplyLocation(QuickReply): self.is_response = False +@attr.s(cmp=False, init=False) class QuickReplyPhoneNumber(QuickReply): """Represents a phone number quick reply (Doesn't work on mobile)""" #: URL of the quick reply image (optional) - image_url = None + image_url = attr.ib(None) #: Type of the quick reply _type = "user_phone_number" @@ -68,11 +62,12 @@ class QuickReplyPhoneNumber(QuickReply): self.image_url = image_url +@attr.s(cmp=False, init=False) class QuickReplyEmail(QuickReply): """Represents an email quick reply (Doesn't work on mobile)""" #: URL of the quick reply image (optional) - image_url = None + image_url = attr.ib(None) #: Type of the quick reply _type = "user_email" diff --git a/fbchat/_sticker.py b/fbchat/_sticker.py index fadab3d..e90655d 100644 --- a/fbchat/_sticker.py +++ b/fbchat/_sticker.py @@ -1,37 +1,39 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._attachment import Attachment +@attr.s(cmp=False, init=False) class Sticker(Attachment): """Represents a Facebook sticker that has been sent to a thread as an attachment""" #: The sticker-pack's ID - pack = None + pack = attr.ib(None) #: Whether the sticker is animated - is_animated = False + is_animated = attr.ib(False) # If the sticker is animated, the following should be present #: URL to a medium spritemap - medium_sprite_image = None + medium_sprite_image = attr.ib(None) #: URL to a large spritemap - large_sprite_image = None + large_sprite_image = attr.ib(None) #: The amount of frames present in the spritemap pr. row - frames_per_row = None + frames_per_row = attr.ib(None) #: The amount of frames present in the spritemap pr. coloumn - frames_per_col = None + frames_per_col = attr.ib(None) #: The frame rate the spritemap is intended to be played in - frame_rate = None + frame_rate = attr.ib(None) #: URL to the sticker's image - url = None + url = attr.ib(None) #: Width of the sticker - width = None + width = attr.ib(None) #: Height of the sticker - height = None + height = attr.ib(None) #: The sticker's label/name - label = None + label = attr.ib(None) - def __init__(self, *args, **kwargs): - super(Sticker, self).__init__(*args, **kwargs) + def __init__(self, uid=None): + super(Sticker, self).__init__(uid=uid) diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 040493e..6b4641e 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -1,6 +1,7 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._core import Enum @@ -42,23 +43,24 @@ class ThreadColor(Enum): BILOBA_FLOWER = "#a695c7" +@attr.s(cmp=False, init=False) class Thread(object): """Represents a Facebook thread""" #: The unique identifier of the thread. Can be used a `thread_id`. See :ref:`intro_threads` for more info - uid = None + uid = attr.ib(converter=str) #: Specifies the type of thread. Can be used a `thread_type`. See :ref:`intro_threads` for more info - type = None + type = attr.ib() #: A url to the thread's picture - photo = None + photo = attr.ib(None) #: The name of the thread - name = None + name = attr.ib(None) #: Timestamp of last message - last_message_timestamp = None + last_message_timestamp = attr.ib(None) #: Number of messages in the thread - message_count = None + message_count = attr.ib(None) #: Set :class:`Plan` - plan = None + plan = attr.ib(None) def __init__( self, @@ -77,9 +79,3 @@ class Thread(object): self.last_message_timestamp = last_message_timestamp self.message_count = message_count self.plan = plan - - def __repr__(self): - return self.__unicode__() - - def __unicode__(self): - return "<{} {} ({})>".format(self.type.name, self.name, self.uid) diff --git a/fbchat/_user.py b/fbchat/_user.py index 0da1f3f..26e8f4d 100644 --- a/fbchat/_user.py +++ b/fbchat/_user.py @@ -1,6 +1,7 @@ # -*- coding: UTF-8 -*- from __future__ import unicode_literals +import attr from ._core import Enum from ._thread import ThreadType, Thread @@ -12,29 +13,30 @@ class TypingStatus(Enum): TYPING = 1 +@attr.s(cmp=False, init=False) class User(Thread): """Represents a Facebook user. Inherits `Thread`""" #: The profile url - url = None + url = attr.ib(None) #: The users first name - first_name = None + first_name = attr.ib(None) #: The users last name - last_name = None + last_name = attr.ib(None) #: Whether the user and the client are friends - is_friend = None + is_friend = attr.ib(None) #: The user's gender - gender = None + gender = attr.ib(None) #: From 0 to 1. How close the client is to the user - affinity = None + affinity = attr.ib(None) #: The user's nickname - nickname = None + nickname = attr.ib(None) #: The clients nickname, as seen by the user - own_nickname = None + own_nickname = attr.ib(None) #: A :class:`ThreadColor`. The message color - color = None + color = attr.ib(None) #: The default emoji - emoji = None + emoji = attr.ib(None) def __init__( self, @@ -64,23 +66,11 @@ class User(Thread): self.emoji = emoji +@attr.s(cmp=False) class ActiveStatus(object): #: Whether the user is active now - active = None + active = attr.ib(None) #: Timestamp when the user was last active - last_active = None + last_active = attr.ib(None) #: Whether the user is playing Messenger game now - in_game = None - - def __init__(self, active=None, last_active=None, in_game=None): - self.active = active - self.last_active = last_active - self.in_game = in_game - - def __repr__(self): - return self.__unicode__() - - def __unicode__(self): - return "".format( - self.active, self.last_active, self.in_game - ) + in_game = attr.ib(None) From c1800a174fd9dd1b60e637fb97738bff56b4d871 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Sun, 24 Feb 2019 19:05:10 +0100 Subject: [PATCH 4/4] Update minimum attrs version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6b2913d..0e32539 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ maintainer-email = "madsmtm@gmail.com" home-page = "https://github.com/carpedm20/fbchat/" requires = [ "aenum", - "attrs", + "attrs~=18.2.0", "requests", "beautifulsoup4", ]