|
1 | | -"""Nextcloud Talk API.""" |
| 1 | +"""Nextcloud Talk API definitions.""" |
2 | 2 |
|
3 | 3 | import dataclasses |
4 | 4 | import enum |
5 | | -import hashlib |
6 | 5 | import typing |
7 | 6 |
|
8 | | -from ._misc import ( |
9 | | - check_capabilities, |
10 | | - clear_from_params_empty, |
11 | | - random_string, |
12 | | - require_capabilities, |
13 | | -) |
14 | | -from ._session import NcSessionBasic |
15 | 7 | from .user_status import _UserStatus |
16 | 8 |
|
17 | 9 |
|
@@ -155,6 +147,34 @@ class BreakoutRoomStatus(enum.IntEnum): |
155 | 147 | """Breakout rooms lobbies are enabled.""" |
156 | 148 |
|
157 | 149 |
|
| 150 | +@dataclasses.dataclass |
| 151 | +class MessageReactions: |
| 152 | + """One reaction for a message, retrieved with :py:meth:`~nc_py_api._talk_api._TalkAPI.get_message_reactions`.""" |
| 153 | + |
| 154 | + def __init__(self, raw_data: dict): |
| 155 | + self._raw_data = raw_data |
| 156 | + |
| 157 | + @property |
| 158 | + def actor_type(self) -> str: |
| 159 | + """Actor types of the chat message: **users**, **guests**.""" |
| 160 | + return self._raw_data["actorType"] |
| 161 | + |
| 162 | + @property |
| 163 | + def actor_id(self) -> str: |
| 164 | + """Actor id of the message author.""" |
| 165 | + return self._raw_data["actorId"] |
| 166 | + |
| 167 | + @property |
| 168 | + def actor_display_name(self) -> str: |
| 169 | + """A display name of the message author.""" |
| 170 | + return self._raw_data["actorDisplayName"] |
| 171 | + |
| 172 | + @property |
| 173 | + def timestamp(self) -> int: |
| 174 | + """Timestamp in seconds and UTC time zone.""" |
| 175 | + return self._raw_data["timestamp"] |
| 176 | + |
| 177 | + |
158 | 178 | @dataclasses.dataclass |
159 | 179 | class TalkMessage: |
160 | 180 | """Talk message.""" |
@@ -605,220 +625,3 @@ def last_error_date(self) -> int: |
605 | 625 | def last_error_message(self) -> typing.Optional[str]: |
606 | 626 | """The last exception message or error response information when trying to reach the bot.""" |
607 | 627 | return self._raw_data["last_error_message"] |
608 | | - |
609 | | - |
610 | | -class _TalkAPI: |
611 | | - """Class that implements work with Nextcloud Talk.""" |
612 | | - |
613 | | - _ep_base: str = "/ocs/v2.php/apps/spreed" |
614 | | - config_sha: str |
615 | | - """Sha1 value over Talk config. After receiving a different value on subsequent requests, settings got refreshed.""" |
616 | | - modified_since: int |
617 | | - """Used by ``get_user_conversations``, when **modified_since** param is ``True``.""" |
618 | | - |
619 | | - def __init__(self, session: NcSessionBasic): |
620 | | - self._session = session |
621 | | - self.config_sha = "" |
622 | | - self.modified_since = 0 |
623 | | - |
624 | | - @property |
625 | | - def available(self) -> bool: |
626 | | - """Returns True if the Nextcloud instance supports this feature, False otherwise.""" |
627 | | - return not check_capabilities("spreed", self._session.capabilities) |
628 | | - |
629 | | - @property |
630 | | - def bots_available(self) -> bool: |
631 | | - """Returns True if the Nextcloud instance supports this feature, False otherwise.""" |
632 | | - return not check_capabilities("spreed.features.bots-v1", self._session.capabilities) |
633 | | - |
634 | | - def get_user_conversations( |
635 | | - self, no_status_update: bool = True, include_status: bool = False, modified_since: typing.Union[int, bool] = 0 |
636 | | - ) -> list[Conversation]: |
637 | | - """Returns the list of the user's conversations. |
638 | | -
|
639 | | - :param no_status_update: When the user status should not be automatically set to the online. |
640 | | - :param include_status: Whether the user status information of all one-to-one conversations should be loaded. |
641 | | - :param modified_since: When provided only conversations with a newer **lastActivity** |
642 | | - (and one-to-one conversations when includeStatus is provided) are returned. |
643 | | - Can be set to ``True`` to automatically use last ``modified_since`` from previous calls. Default = **0**. |
644 | | -
|
645 | | - .. note:: In rare cases, when a request arrives between seconds, it is possible that return data |
646 | | - will contain part of the conversations from the last call that was not modified( |
647 | | - their `last_activity` will be the same as ``talk.modified_since``). |
648 | | - """ |
649 | | - params: dict = {} |
650 | | - if no_status_update: |
651 | | - params["noStatusUpdate"] = 1 |
652 | | - if include_status: |
653 | | - params["includeStatus"] = True |
654 | | - if modified_since: |
655 | | - params["modifiedSince"] = self.modified_since if modified_since is True else modified_since |
656 | | - |
657 | | - result = self._session.ocs("GET", self._ep_base + "/api/v4/room", params=params) |
658 | | - self.modified_since = int(self._session.response_headers["X-Nextcloud-Talk-Modified-Before"]) |
659 | | - config_sha = self._session.response_headers["X-Nextcloud-Talk-Hash"] |
660 | | - if self.config_sha != config_sha: |
661 | | - self._session.update_server_info() |
662 | | - self.config_sha = config_sha |
663 | | - return [Conversation(i) for i in result] |
664 | | - |
665 | | - def create_conversation( |
666 | | - self, |
667 | | - conversation_type: ConversationType, |
668 | | - invite: str = "", |
669 | | - source: str = "", |
670 | | - room_name: str = "", |
671 | | - object_type: str = "", |
672 | | - object_id: str = "", |
673 | | - ) -> Conversation: |
674 | | - """Creates a new conversation. |
675 | | -
|
676 | | - .. note:: Creating a conversation as a child breakout room will automatically set the lobby when breakout |
677 | | - rooms are not started and will always overwrite the room type with the parent room type. |
678 | | - Also, moderators of the parent conversation will be automatically added as moderators. |
679 | | -
|
680 | | - :param conversation_type: type of the conversation to create. |
681 | | - :param invite: User ID(roomType=ONE_TO_ONE), Group ID(roomType=GROUP - optional), |
682 | | - Circle ID(roomType=GROUP, source='circles', only available with the ``circles-support`` capability). |
683 | | - :param source: The source for the invite, only supported on roomType = GROUP for groups and circles. |
684 | | - :param room_name: Conversation name up to 255 characters(``not available for roomType=ONE_TO_ONE``). |
685 | | - :param object_type: Type of object this room references, currently only allowed |
686 | | - value is **"room"** to indicate the parent of a breakout room. |
687 | | - :param object_id: ID of an object this room references, room token is used for the parent of a breakout room. |
688 | | - """ |
689 | | - params: dict = { |
690 | | - "roomType": int(conversation_type), |
691 | | - "invite": invite, |
692 | | - "source": source, |
693 | | - "roomName": room_name, |
694 | | - "objectType": object_type, |
695 | | - "objectId": object_id, |
696 | | - } |
697 | | - clear_from_params_empty(["invite", "source", "roomName", "objectType", "objectId"], params) |
698 | | - return Conversation(self._session.ocs("POST", self._ep_base + "/api/v4/room", json=params)) |
699 | | - |
700 | | - def delete_conversation(self, conversation: typing.Union[Conversation, str]) -> None: |
701 | | - """Deletes a conversation. |
702 | | -
|
703 | | - .. note:: Deleting a conversation that is the parent of breakout rooms, will also delete them. |
704 | | - ``ONE_TO_ONE`` conversations can not be deleted for them |
705 | | - :py:class:`~nc_py_api.talk._TalkAPI.leave_conversation` should be used. |
706 | | -
|
707 | | - :param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`. |
708 | | - """ |
709 | | - token = conversation.token if isinstance(conversation, Conversation) else conversation |
710 | | - self._session.ocs("DELETE", self._ep_base + f"/api/v4/room/{token}") |
711 | | - |
712 | | - def leave_conversation(self, conversation: typing.Union[Conversation, str]) -> None: |
713 | | - """Removes yourself from the conversation. |
714 | | -
|
715 | | - .. note:: When the participant is a moderator or owner and there are no other moderators or owners left, |
716 | | - participant can not leave conversation. |
717 | | -
|
718 | | - :param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`. |
719 | | - """ |
720 | | - token = conversation.token if isinstance(conversation, Conversation) else conversation |
721 | | - self._session.ocs("DELETE", self._ep_base + f"/api/v4/room/{token}/participants/self") |
722 | | - |
723 | | - def send_message( |
724 | | - self, |
725 | | - message: str, |
726 | | - conversation: typing.Union[Conversation, str] = "", |
727 | | - reply_to_message: typing.Union[int, TalkMessage] = 0, |
728 | | - silent: bool = False, |
729 | | - actor_display_name: str = "", |
730 | | - ) -> str: |
731 | | - """Send a message and returns a "reference string" to identify the message again in a "get messages" request. |
732 | | -
|
733 | | - :param message: The message the user wants to say. |
734 | | - :param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`. |
735 | | - Need only if **reply_to_message** is not :py:class:`~nc_py_api.talk.TalkMessage` |
736 | | - :param reply_to_message: The message ID this message is a reply to. |
737 | | -
|
738 | | - .. note:: Only allowed when the message type is not ``system`` or ``command``. |
739 | | - The message you are replying to should be from the same conversation. |
740 | | - :param silent: Flag controlling if the message should create a chat notifications for the users. |
741 | | - :param actor_display_name: Guest display name (**ignored for the logged-in users**). |
742 | | - :returns: A reference string to be able to identify the message again in a "get messages" request. |
743 | | - :raises ValueError: in case of an invalid usage. |
744 | | - """ |
745 | | - if not conversation and not isinstance(reply_to_message, TalkMessage): |
746 | | - raise ValueError("Either specify 'conversation' or provide 'TalkMessage'.") |
747 | | - |
748 | | - token = ( |
749 | | - reply_to_message.token |
750 | | - if isinstance(reply_to_message, TalkMessage) |
751 | | - else conversation.token if isinstance(conversation, Conversation) else conversation |
752 | | - ) |
753 | | - reference_id = hashlib.sha256(random_string(32).encode("UTF-8")).hexdigest() |
754 | | - params = { |
755 | | - "message": message, |
756 | | - "actorDisplayName": actor_display_name, |
757 | | - "replyTo": reply_to_message.message_id if isinstance(reply_to_message, TalkMessage) else reply_to_message, |
758 | | - "referenceId": reference_id, |
759 | | - "silent": silent, |
760 | | - } |
761 | | - self._session.ocs("POST", self._ep_base + f"/api/v1/chat/{token}", json=params) |
762 | | - return reference_id |
763 | | - |
764 | | - def receive_messages( |
765 | | - self, |
766 | | - conversation: typing.Union[Conversation, str], |
767 | | - look_in_future: bool = False, |
768 | | - limit: int = 100, |
769 | | - timeout: int = 30, |
770 | | - no_status_update: bool = True, |
771 | | - ) -> list[TalkMessage]: |
772 | | - """Receive chat messages of a conversation. |
773 | | -
|
774 | | - :param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`. |
775 | | - :param look_in_future: ``True`` to poll and wait for the new message or ``False`` to get history. |
776 | | - :param limit: Number of chat messages to receive (``100`` by default, ``200`` at most). |
777 | | - :param timeout: ``look_in_future=1`` only: seconds to wait for the new messages (60 secs at most). |
778 | | - :param no_status_update: When the user status should not be automatically set to the online. |
779 | | - """ |
780 | | - token = conversation.token if isinstance(conversation, Conversation) else conversation |
781 | | - params = { |
782 | | - "lookIntoFuture": int(look_in_future), |
783 | | - "limit": limit, |
784 | | - "timeout": timeout, |
785 | | - "noStatusUpdate": int(no_status_update), |
786 | | - } |
787 | | - result = self._session.ocs("GET", self._ep_base + f"/api/v1/chat/{token}", params=params) |
788 | | - return [TalkMessage(i) for i in result] |
789 | | - |
790 | | - def list_bots(self) -> list[BotInfo]: |
791 | | - """Lists the bots that are installed on the server.""" |
792 | | - require_capabilities("spreed.features.bots-v1", self._session.capabilities) |
793 | | - return [BotInfo(i) for i in self._session.ocs("GET", self._ep_base + "/api/v1/bot/admin")] |
794 | | - |
795 | | - def conversation_list_bots(self, conversation: typing.Union[Conversation, str]) -> list[BotInfoBasic]: |
796 | | - """Lists the bots that are enabled and can be enabled for the conversation. |
797 | | -
|
798 | | - :param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`. |
799 | | - """ |
800 | | - require_capabilities("spreed.features.bots-v1", self._session.capabilities) |
801 | | - token = conversation.token if isinstance(conversation, Conversation) else conversation |
802 | | - return [BotInfoBasic(i) for i in self._session.ocs("GET", self._ep_base + f"/api/v1/bot/{token}")] |
803 | | - |
804 | | - def enable_bot(self, conversation: typing.Union[Conversation, str], bot: typing.Union[BotInfoBasic, int]) -> None: |
805 | | - """Enable a bot for a conversation as a moderator. |
806 | | -
|
807 | | - :param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`. |
808 | | - :param bot: bot ID or :py:class:`~nc_py_api.talk.BotInfoBasic`. |
809 | | - """ |
810 | | - require_capabilities("spreed.features.bots-v1", self._session.capabilities) |
811 | | - token = conversation.token if isinstance(conversation, Conversation) else conversation |
812 | | - bot_id = bot.bot_id if isinstance(bot, BotInfoBasic) else bot |
813 | | - self._session.ocs("POST", self._ep_base + f"/api/v1/bot/{token}/{bot_id}") |
814 | | - |
815 | | - def disable_bot(self, conversation: typing.Union[Conversation, str], bot: typing.Union[BotInfoBasic, int]) -> None: |
816 | | - """Disable a bot for a conversation as a moderator. |
817 | | -
|
818 | | - :param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`. |
819 | | - :param bot: bot ID or :py:class:`~nc_py_api.talk.BotInfoBasic`. |
820 | | - """ |
821 | | - require_capabilities("spreed.features.bots-v1", self._session.capabilities) |
822 | | - token = conversation.token if isinstance(conversation, Conversation) else conversation |
823 | | - bot_id = bot.bot_id if isinstance(bot, BotInfoBasic) else bot |
824 | | - self._session.ocs("DELETE", self._ep_base + f"/api/v1/bot/{token}/{bot_id}") |
0 commit comments