From 3341f4a45c4b9d771b95d9ad54949324d048b565 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 9 Jan 2020 19:51:06 +0100 Subject: [PATCH 1/4] Remove MessageReaction --- docs/api.rst | 2 -- docs/intro.rst | 6 ++---- examples/interract.py | 2 +- fbchat/__init__.py | 2 +- fbchat/_client.py | 11 ++++------ fbchat/_message.py | 35 +++++++++++++++----------------- tests/test_message_management.py | 7 +------ 7 files changed, 25 insertions(+), 40 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index f1d8b35..942371d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -28,8 +28,6 @@ Messages .. autoclass:: Mention .. autoclass:: EmojiSize(Enum) :undoc-members: -.. autoclass:: MessageReaction(Enum) - :undoc-members: Exceptions ---------- diff --git a/docs/intro.rst b/docs/intro.rst index 6f8f849..5dc9f90 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -82,12 +82,10 @@ Message IDs Every message you send on Facebook has a unique ID, and every action you do in a thread, like changing a nickname or adding a person, has a unique ID too. -Some of ``fbchat``'s functions require these ID's, like `Client.react_to_message`, -and some of then provide this ID, like `Client.send`. This snippet shows how to send a message, and then use the returned ID to react to that message with a 😍 emoji:: - message_id = thread.send(Message(text='message')) - client.react_to_message(message_id, MessageReaction.LOVE) + message = thread.send_text("A message!") + message.react("😍") .. _intro_interacting: diff --git a/examples/interract.py b/examples/interract.py index 16c6f7a..279c0cb 100644 --- a/examples/interract.py +++ b/examples/interract.py @@ -63,4 +63,4 @@ thread.set_emoji("👍") message = fbchat.Message(session=session, id="") # Will react to a message with a 😍 emoji -message.react(fbchat.MessageReaction.LOVE) +message.react("😍") diff --git a/fbchat/__init__.py b/fbchat/__init__.py index ef39f5c..afbc45d 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -18,7 +18,7 @@ from ._thread import ThreadLocation, ThreadColor, ThreadABC, Thread from ._user import TypingStatus, User, UserData, ActiveStatus from ._group import Group, GroupData from ._page import Page, PageData -from ._message import EmojiSize, MessageReaction, Mention, Message +from ._message import EmojiSize, Mention, Message from ._attachment import Attachment, UnsentMessage, ShareAttachment from ._sticker import Sticker from ._location import LocationAttachment, LiveLocationAttachment diff --git a/fbchat/_client.py b/fbchat/_client.py index 9568ba5..0240638 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -10,7 +10,7 @@ from ._thread import ThreadLocation, ThreadColor from ._user import TypingStatus, User, UserData, ActiveStatus from ._group import Group, GroupData from ._page import Page, PageData -from ._message import EmojiSize, MessageReaction, Mention, Message +from ._message import EmojiSize, Mention, Message from ._attachment import Attachment from ._sticker import Sticker from ._location import LocationAttachment, LiveLocationAttachment @@ -1259,14 +1259,11 @@ class Client: i = d["deltaMessageReaction"] mid = i["messageId"] author_id = str(i["userId"]) - reaction = ( - MessageReaction(i["reaction"]) if i.get("reaction") else None - ) add_reaction = not bool(i["action"]) if add_reaction: self.on_reaction_added( mid=mid, - reaction=reaction, + reaction=i.get("reaction"), author_id=author_id, thread=get_thread(metadata), at=at, @@ -1855,7 +1852,7 @@ class Client: Args: mid: Message ID, that user reacted to - reaction (MessageReaction): Reaction + reaction: The added reaction. Not limited to the ones in `Message.react` add_reaction: Whether user added or removed reaction author_id: The ID of the person who reacted to the message thread: Thread that the action was sent to. See :ref:`intro_threads` @@ -1863,7 +1860,7 @@ class Client: """ log.info( "{} reacted to message {} with {} in {}".format( - author_id, mid, reaction.name, thread + author_id, mid, reaction, thread ) ) diff --git a/fbchat/_message.py b/fbchat/_message.py index f7e273d..f9807c7 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -29,19 +29,6 @@ class EmojiSize(Enum): return None -class MessageReaction(Enum): - """Used to specify a message reaction.""" - - HEART = "❤" - LOVE = "😍" - SMILE = "😆" - WOW = "😮" - SAD = "😢" - ANGRY = "😠" - YES = "👍" - NO = "👎" - - @attrs_default class Mention: """Represents a ``@mention``.""" @@ -74,6 +61,9 @@ class Mention: } +SENDABLE_REACTIONS = ("❤", "😍", "😆", "😮", "😢", "😠", "👍", "👎") + + @attrs_default class Message: """Represents a Facebook message.""" @@ -93,18 +83,26 @@ class Message: data = {"message_id": self.id} j = self.session._payload_post("/messaging/unsend_message/?dpr=1", data) - def react(self, reaction: Optional[MessageReaction]): + def react(self, reaction: Optional[str]): """React to the message, or removes reaction. + Currently, you can use "❤", "😍", "😆", "😮", "😢", "😠", "👍" or "👎". It + should be possible to add support for more, but we haven't figured that out yet. + Args: - reaction: Reaction emoji to use, if ``None`` removes reaction + reaction: Reaction emoji to use, or if ``None``, removes reaction. """ + if reaction and reaction not in SENDABLE_REACTIONS: + raise ValueError( + "Invalid reaction! Please use one of: {}".format(SENDABLE_REACTIONS) + ) + data = { "action": "ADD_REACTION" if reaction else "REMOVE_REACTION", "client_mutation_id": "1", "actor_id": self.session.user_id, "message_id": self.id, - "reaction": reaction.value if reaction else None, + "reaction": reaction, } data = { "doc_id": 1491398900900362, @@ -190,7 +188,7 @@ class MessageData(Message): is_read = attr.ib(None) #: A list of people IDs who read the message, works only with `Client.fetch_thread_messages` read_by = attr.ib(factory=list) - #: A dictionary with user's IDs as keys, and their `MessageReaction` as values + #: A dictionary with user's IDs as keys, and their reaction as values reactions = attr.ib(factory=dict) #: A `Sticker` sticker = attr.ib(None) @@ -266,8 +264,7 @@ class MessageData(Message): if _util.millis_to_datetime(int(receipt["watermark"])) >= created_at ], reactions={ - str(r["user"]["id"]): MessageReaction._extend_if_invalid(r["reaction"]) - for r in data["message_reactions"] + str(r["user"]["id"]): r["reaction"] for r in data["message_reactions"] }, sticker=_sticker.Sticker._from_graphql(data.get("sticker")), attachments=attachments, diff --git a/tests/test_message_management.py b/tests/test_message_management.py index 9db09a4..66440b4 100644 --- a/tests/test_message_management.py +++ b/tests/test_message_management.py @@ -1,16 +1,11 @@ import pytest -from fbchat import Message, MessageReaction +from fbchat import Message from utils import subset pytestmark = pytest.mark.online -def test_set_reaction(client): - mid = client.send(Message(text="This message will be reacted to")) - client.react_to_message(mid, MessageReaction.LOVE) - - def test_delete_messages(client): text1 = "This message will stay" text2 = "This message will be removed" From 8b6d9b16c6028b16dd3afd8bcaa8c2a6183444a9 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 9 Jan 2020 21:00:34 +0100 Subject: [PATCH 2/4] Remove ThreadColor Replaced with raw color values. In the future, we should probably investigate using "themes" --- docs/api.rst | 2 - docs/intro.rst | 4 +- examples/interract.py | 4 +- examples/keepbot.py | 2 +- fbchat/__init__.py | 2 +- fbchat/_client.py | 7 +- fbchat/_group.py | 6 +- fbchat/_thread.py | 111 ++++++++++++++++++------------ fbchat/_user.py | 10 +-- tests/test_group.py | 2 +- tests/test_thread.py | 23 +++---- tests/test_thread_interraction.py | 12 ++-- tests/test_user.py | 3 +- 13 files changed, 103 insertions(+), 85 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 942371d..61fc4f1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -54,8 +54,6 @@ Miscellaneous .. autoclass:: ThreadLocation(Enum) :undoc-members: -.. autoclass:: ThreadColor(Enum) - :undoc-members: .. autoclass:: ActiveStatus() .. autoclass:: TypingStatus(Enum) :undoc-members: diff --git a/docs/intro.rst b/docs/intro.rst index 5dc9f90..46fa972 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -70,8 +70,8 @@ corresponds to the ID of a single user, and the ID of a group respectively:: Some functions don't require a thread type, so in these cases you just provide the thread ID:: thread = fbchat.Thread(session=session, id="") - thread.set_color(ThreadColor.BILOBA_FLOWER) - thread.set_color(ThreadColor.MESSENGER_BLUE) + thread.set_color("#a695c7") + thread.set_color("#67b868") .. _intro_message_ids: diff --git a/examples/interract.py b/examples/interract.py index 279c0cb..ec2471f 100644 --- a/examples/interract.py +++ b/examples/interract.py @@ -54,8 +54,8 @@ thread.set_nickname(fbchat.User(session=session, id=""), " str: + if not inp: + return DEFAULT_COLOR + # Strip the alpha value, and lower the string + return "#{}".format(inp[2:].lower()) + @staticmethod def _parse_customization_info(data: Any) -> MutableMapping[str, Any]: - if data is None or data.get("customization_info") is None: - return {} + if not data or not data.get("customization_info"): + return {"emoji": None, "color": DEFAULT_COLOR} info = data["customization_info"] rtn = { "emoji": info.get("emoji"), - "color": ThreadColor._from_graphql(info.get("outgoing_bubble_color")), + "color": ThreadABC._parse_color(info.get("outgoing_bubble_color")), } if ( data.get("thread_type") == "GROUP" diff --git a/fbchat/_user.py b/fbchat/_user.py index ef26967..0e51f7c 100644 --- a/fbchat/_user.py +++ b/fbchat/_user.py @@ -110,7 +110,7 @@ class UserData(User): nickname = attr.ib(None) #: The clients nickname, as seen by the user own_nickname = attr.ib(None) - #: A `ThreadColor`. The message color + #: The message color color = attr.ib(None) #: The default emoji emoji = attr.ib(None) @@ -136,8 +136,8 @@ class UserData(User): gender=GENDERS.get(data["gender"]), affinity=data.get("viewer_affinity"), nickname=c_info.get("nickname"), - color=c_info.get("color"), - emoji=c_info.get("emoji"), + color=c_info["color"], + emoji=c_info["emoji"], own_nickname=c_info.get("own_nickname"), photo=Image._from_uri(data["profile_picture"]), name=data["name"], @@ -186,8 +186,8 @@ class UserData(User): is_friend=user["is_viewer_friend"], gender=GENDERS.get(user["gender"]), nickname=c_info.get("nickname"), - color=c_info.get("color"), - emoji=c_info.get("emoji"), + color=c_info["color"], + emoji=c_info["emoji"], own_nickname=c_info.get("own_nickname"), photo=Image._from_uri(user["big_image_src"]), message_count=data["messages_count"], diff --git a/tests/test_group.py b/tests/test_group.py index 5fc7135..ebdfed9 100644 --- a/tests/test_group.py +++ b/tests/test_group.py @@ -35,7 +35,7 @@ def test_group_from_graphql(session): plan=None, participants={"1234", "2345", "3456"}, nicknames={}, - color=None, + color="#0084ff", emoji="😀", admins={"1234"}, approval_mode=False, diff --git a/tests/test_thread.py b/tests/test_thread.py index 6fc597c..344b42e 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -1,20 +1,19 @@ import pytest import fbchat -from fbchat import ThreadColor, ThreadABC, Thread +from fbchat import ThreadABC, Thread -def test_thread_color_from_graphql(): - assert None is ThreadColor._from_graphql(None) - assert ThreadColor.MESSENGER_BLUE is ThreadColor._from_graphql("") - assert ThreadColor.VIKING is ThreadColor._from_graphql("FF44BEC7") - assert ThreadColor._from_graphql("DEADBEEF") is getattr( - ThreadColor, "UNKNOWN_#ADBEEF" - ) +def test_parse_color(): + assert "#0084ff" == ThreadABC._parse_color(None) + assert "#0084ff" == ThreadABC._parse_color("") + assert "#44bec7" == ThreadABC._parse_color("FF44BEC7") + assert "#adbeef" == ThreadABC._parse_color("DEADBEEF") def test_thread_parse_customization_info_empty(): - assert {} == ThreadABC._parse_customization_info(None) - assert {} == ThreadABC._parse_customization_info({"customization_info": None}) + default = {"color": "#0084ff", "emoji": None} + assert default == ThreadABC._parse_customization_info(None) + assert default == ThreadABC._parse_customization_info({"customization_info": None}) def test_thread_parse_customization_info_group(): @@ -34,7 +33,7 @@ def test_thread_parse_customization_info_group(): } expected = { "emoji": "🎉", - "color": ThreadColor.BRILLIANT_ROSE, + "color": "#ff5ca1", "nicknames": {"123456789": "A", "987654321": "B"}, } assert expected == ThreadABC._parse_customization_info(data) @@ -55,7 +54,7 @@ def test_thread_parse_customization_info_user(): "thread_type": "ONE_TO_ONE", # ... Other irrelevant fields } - expected = {"emoji": None, "color": None, "own_nickname": "A", "nickname": "B"} + expected = {"emoji": None, "color": "#0084ff", "own_nickname": "A", "nickname": "B"} assert expected == ThreadABC._parse_customization_info(data) diff --git a/tests/test_thread_interraction.py b/tests/test_thread_interraction.py index df05fdb..5b05366 100644 --- a/tests/test_thread_interraction.py +++ b/tests/test_thread_interraction.py @@ -1,6 +1,6 @@ import pytest -from fbchat import Message, FBchatFacebookError, TypingStatus, ThreadColor +from fbchat import Message, FBchatFacebookError, TypingStatus from utils import random_hex, subset from os import path @@ -91,14 +91,10 @@ def test_change_image_remote(client1, group, catch_event): ) -@pytest.mark.parametrize( - "color", - [x for x in ThreadColor if x in [ThreadColor.MESSENGER_BLUE, ThreadColor.PUMPKIN]], -) -def test_change_color(client, catch_event, compare, color): +def test_change_color(client, catch_event, compare): with catch_event("on_color_change") as x: - client.change_thread_color(color) - assert compare(x, new_color=color) + client.change_thread_color("#44bec7") + assert compare(x, new_color="#44bec7") @pytest.mark.xfail(raises=FBchatFacebookError, reason="Should fail, but doesn't") diff --git a/tests/test_user.py b/tests/test_user.py index 3685c24..a26eedd 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -27,6 +27,7 @@ def test_user_from_graphql(session): is_friend=True, gender="female_singular", affinity=0.4560002, + color="#0084ff", ) == UserData._from_graphql(session, data) @@ -152,7 +153,7 @@ def test_user_from_thread_fetch(session): gender="female_singular", nickname="A", own_nickname="B", - color=None, + color="#0084ff", emoji=None, ) == UserData._from_thread_fetch(session, data) From c81e509eb08220777e2482b4d5cf81ef4d32ec37 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 9 Jan 2020 20:56:10 +0100 Subject: [PATCH 3/4] Remove TypingStatus --- docs/api.rst | 2 -- fbchat/__init__.py | 2 +- fbchat/_client.py | 7 +++---- fbchat/_user.py | 9 +-------- tests/test_thread_interraction.py | 4 ++-- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 61fc4f1..274bf24 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -55,8 +55,6 @@ Miscellaneous .. autoclass:: ThreadLocation(Enum) :undoc-members: .. autoclass:: ActiveStatus() -.. autoclass:: TypingStatus(Enum) - :undoc-members: .. autoclass:: QuickReply .. autoclass:: QuickReplyText diff --git a/fbchat/__init__.py b/fbchat/__init__.py index 85a57cf..d4a1355 100644 --- a/fbchat/__init__.py +++ b/fbchat/__init__.py @@ -15,7 +15,7 @@ from ._core import Image from ._exception import FBchatException, FBchatFacebookError from ._session import Session from ._thread import ThreadLocation, ThreadABC, Thread -from ._user import TypingStatus, User, UserData, ActiveStatus +from ._user import User, UserData, ActiveStatus from ._group import Group, GroupData from ._page import Page, PageData from ._message import EmojiSize, Mention, Message diff --git a/fbchat/_client.py b/fbchat/_client.py index dc1540e..a15fd5f 100644 --- a/fbchat/_client.py +++ b/fbchat/_client.py @@ -7,7 +7,7 @@ from . import _util, _graphql, _session from ._exception import FBchatException, FBchatFacebookError from ._thread import ThreadLocation -from ._user import TypingStatus, User, UserData, ActiveStatus +from ._user import User, UserData, ActiveStatus from ._group import Group, GroupData from ._page import Page, PageData from ._message import EmojiSize, Mention, Message @@ -1401,9 +1401,8 @@ class Client: else: thread_id = author_id thread = User(session=self.session, id=thread_id) - typing_status = TypingStatus(m.get("st")) self.on_typing( - author_id=author_id, status=typing_status, thread=thread, + author_id=author_id, status=m["st"] == 1, thread=thread ) # Delivered @@ -1812,7 +1811,7 @@ class Client: Args: author_id: The ID of the person who sent the action - status (TypingStatus): The typing status + is_typing: ``True`` if the user started typing, ``False`` if they stopped. thread: Thread that the action was sent to. See :ref:`intro_threads` """ pass diff --git a/fbchat/_user.py b/fbchat/_user.py index 0e51f7c..081cead 100644 --- a/fbchat/_user.py +++ b/fbchat/_user.py @@ -1,5 +1,5 @@ import attr -from ._core import log, attrs_default, Enum, Image +from ._core import log, attrs_default, Image from . import _util, _session, _plan, _thread @@ -33,13 +33,6 @@ GENDERS = { } -class TypingStatus(Enum): - """Used to specify whether the user is typing or has stopped typing.""" - - STOPPED = 0 - TYPING = 1 - - @attrs_default class User(_thread.ThreadABC): """Represents a Facebook user. Implements `ThreadABC`.""" diff --git a/tests/test_thread_interraction.py b/tests/test_thread_interraction.py index 5b05366..babd376 100644 --- a/tests/test_thread_interraction.py +++ b/tests/test_thread_interraction.py @@ -1,6 +1,6 @@ import pytest -from fbchat import Message, FBchatFacebookError, TypingStatus +from fbchat import Message, FBchatFacebookError from utils import random_hex, subset from os import path @@ -105,7 +105,7 @@ def test_change_color_invalid(client): client.change_thread_color(InvalidColor()) -@pytest.mark.parametrize("status", TypingStatus) +@pytest.mark.parametrize("status", [True, False]) def test_typing_status(client, catch_event, compare, status): with catch_event("on_typing") as x: client.set_typing_status(status) From 9f1c9c9697f6cdea3eb6210f4d3fc4751d9fba14 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Thu, 9 Jan 2020 20:58:27 +0100 Subject: [PATCH 4/4] Remove _core.Enum and aenum dependency --- fbchat/_core.py | 20 -------------------- fbchat/_message.py | 5 +++-- fbchat/_plan.py | 5 +++-- fbchat/_thread.py | 5 +++-- pyproject.toml | 1 - tests/test_core.py | 14 -------------- 6 files changed, 9 insertions(+), 41 deletions(-) delete mode 100644 tests/test_core.py diff --git a/fbchat/_core.py b/fbchat/_core.py index 3f03a4b..fed25af 100644 --- a/fbchat/_core.py +++ b/fbchat/_core.py @@ -1,7 +1,6 @@ import sys import attr import logging -import aenum log = logging.getLogger("fbchat") @@ -12,25 +11,6 @@ kw_only = sys.version_info[:2] > (3, 5) attrs_default = attr.s(slots=True, kw_only=kw_only) -class Enum(aenum.Enum): - """Used internally to support enumerations""" - - def __repr__(self): - # For documentation: - return "{}.{}".format(type(self).__name__, self.name) - - @classmethod - def _extend_if_invalid(cls, value): - try: - return cls(value) - except ValueError: - log.warning( - "Failed parsing {.__name__}({!r}). Extending enum.".format(cls, value) - ) - aenum.extend_enum(cls, "UNKNOWN_{}".format(value).upper(), value) - return cls(value) - - # Frozen, so that it can be used in sets @attr.s(frozen=True, slots=True, kw_only=kw_only) class Image: diff --git a/fbchat/_message.py b/fbchat/_message.py index f9807c7..6cefdc8 100644 --- a/fbchat/_message.py +++ b/fbchat/_message.py @@ -1,11 +1,12 @@ import attr +import enum from string import Formatter -from ._core import log, attrs_default, Enum +from ._core import log, attrs_default from . import _util, _session, _attachment, _location, _file, _quick_reply, _sticker from typing import Optional -class EmojiSize(Enum): +class EmojiSize(enum.Enum): """Used to specify the size of a sent emoji.""" LARGE = "369239383222810" diff --git a/fbchat/_plan.py b/fbchat/_plan.py index 9eff6ac..3cfbf89 100644 --- a/fbchat/_plan.py +++ b/fbchat/_plan.py @@ -1,10 +1,11 @@ import attr import datetime -from ._core import attrs_default, Enum +import enum +from ._core import attrs_default from . import _exception, _util, _session -class GuestStatus(Enum): +class GuestStatus(enum.Enum): INVITED = 1 GOING = 2 DECLINED = 3 diff --git a/fbchat/_thread.py b/fbchat/_thread.py index 990568c..628cb3e 100644 --- a/fbchat/_thread.py +++ b/fbchat/_thread.py @@ -2,12 +2,13 @@ import abc import attr import collections import datetime -from ._core import attrs_default, Enum, Image +import enum +from ._core import attrs_default, Image from . import _util, _exception, _session, _graphql, _attachment, _file, _plan from typing import MutableMapping, Any, Iterable, Tuple, Optional -class ThreadLocation(Enum): +class ThreadLocation(enum.Enum): """Used to specify where a thread is located (inbox, pending, archived, other).""" INBOX = "INBOX" diff --git a/pyproject.toml b/pyproject.toml index f586ea2..50c5ea0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ maintainer = "Mads Marquart" maintainer-email = "madsmtm@gmail.com" home-page = "https://github.com/carpedm20/fbchat/" requires = [ - "aenum~=2.0", "attrs>=19.1", "requests~=2.19", "beautifulsoup4~=4.0", diff --git a/tests/test_core.py b/tests/test_core.py deleted file mode 100644 index a35404c..0000000 --- a/tests/test_core.py +++ /dev/null @@ -1,14 +0,0 @@ -import pytest -from fbchat._core import Enum - - -@pytest.mark.filterwarnings("ignore::DeprecationWarning") -def test_enum_extend_if_invalid(): - class TestEnum(Enum): - A = 1 - B = 2 - - assert TestEnum._extend_if_invalid(1) == TestEnum.A - assert TestEnum._extend_if_invalid(3) == TestEnum.UNKNOWN_3 - assert TestEnum._extend_if_invalid(3) == TestEnum.UNKNOWN_3 - assert TestEnum(3) == TestEnum.UNKNOWN_3