Skip to content

Commit ed09642

Browse files
authored
TalkAPI: part2 (#111)
Changes proposed in this pull request: * +`reaction` API * +`delete_message` * `send_message` return value was corrected. * +`rename_conversation` * +`get_conversation_by_token` * +`set_conversation_password`, `set_conversation_public`, `set_conversation_notify_lvl`, `set_conversation_readonly`, `set_conversation_fav`, `set_conversation_description` --------- Signed-off-by: Alexander Piskun <[email protected]>
1 parent ad6ba5d commit ed09642

File tree

9 files changed

+510
-232
lines changed

9 files changed

+510
-232
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
## [0.0.43 - 2023-09-0x]
66

7+
### Added
8+
9+
- Basic APIs for Nextcloud Talk(Part 2)
10+
711
### Fixed
812

913
- `makedirs` correctly work with paths started with `/`

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ Python library that provides a robust and well-documented API that allows develo
3131
| User status ||||
3232
| Weather status ||||
3333
| Notifications ||||
34-
| Nextcloud Talk | | | |
34+
| Nextcloud Talk** | | | |
3535
| Talk Bot API* | N/A |||
3636
| Text Processing* | N/A |||
3737
| SpeechToText* | N/A |||
3838

39-
&ast;_available only for NextcloudApp_
39+
&ast;_available only for NextcloudApp_<br>
40+
&ast;&ast; _work is in progress, not all API's is described, yet._
4041

4142
### Differences between the Nextcloud and NextcloudApp classes
4243

docs/reference/Talk.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Talk API
99
:members:
1010
:inherited-members:
1111

12-
.. autoclass:: nc_py_api.talk._TalkAPI
12+
.. autoclass:: nc_py_api._talk_api._TalkAPI
1313
:members:
1414

1515
.. autoclass:: nc_py_api.talk.ConversationType
@@ -53,6 +53,10 @@ Talk API
5353
:members:
5454
:undoc-members:
5555

56+
.. autoclass:: nc_py_api.talk.MessageReactions
57+
:members:
58+
:undoc-members:
59+
5660
.. autoclass:: nc_py_api.talk.BotInfo
5761
:members:
5862
:inherited-members:

nc_py_api/_talk_api.py

Lines changed: 404 additions & 0 deletions
Large diffs are not rendered by default.

nc_py_api/nextcloud.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010
from ._preferences import PreferencesAPI
1111
from ._preferences_ex import AppConfigExAPI, PreferencesExAPI
1212
from ._session import AppConfig, NcSession, NcSessionApp, NcSessionBasic, ServerVersion
13+
from ._talk_api import _TalkAPI
1314
from ._theming import ThemingInfo, get_parsed_theme
1415
from .apps import _AppsAPI
1516
from .ex_app.defs import ApiScope, LogLvl
1617
from .ex_app.ui.ui import UiApi
1718
from .files.files import FilesAPI
1819
from .notifications import _NotificationsAPI
19-
from .talk import _TalkAPI
2020
from .user_status import _UserStatusAPI
2121
from .users import _UsersAPI
2222
from .users_groups import _UsersGroupsAPI

nc_py_api/talk.py

Lines changed: 29 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,9 @@
1-
"""Nextcloud Talk API."""
1+
"""Nextcloud Talk API definitions."""
22

33
import dataclasses
44
import enum
5-
import hashlib
65
import typing
76

8-
from ._misc import (
9-
check_capabilities,
10-
clear_from_params_empty,
11-
random_string,
12-
require_capabilities,
13-
)
14-
from ._session import NcSessionBasic
157
from .user_status import _UserStatus
168

179

@@ -155,6 +147,34 @@ class BreakoutRoomStatus(enum.IntEnum):
155147
"""Breakout rooms lobbies are enabled."""
156148

157149

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+
158178
@dataclasses.dataclass
159179
class TalkMessage:
160180
"""Talk message."""
@@ -605,220 +625,3 @@ def last_error_date(self) -> int:
605625
def last_error_message(self) -> typing.Optional[str]:
606626
"""The last exception message or error response information when trying to reach the bot."""
607627
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}")

nc_py_api/talk_bot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ def delete_reaction(
160160
) -> httpx.Response:
161161
"""Removes reaction from a message.
162162
163-
:param message: Message ID or :py:class:`~nc_py_api.talk_bot.TalkBotMessage` to react to.
163+
:param message: Message ID or :py:class:`~nc_py_api.talk_bot.TalkBotMessage` to remove reaction from.
164164
:param reaction: A single emoji.
165165
:param token: Token of the conversation.
166166
Can be empty if ``message`` is :py:class:`~nc_py_api.talk_bot.TalkBotMessage`.

0 commit comments

Comments
 (0)