Skip to content

Commit 99c1f76

Browse files
authored
Nextcloud Talk: Conversations Avatar API (#133)
Relatively small and simple API. Signed-off-by: Alexander Piskun <[email protected]>
1 parent 7b0c0a4 commit 99c1f76

File tree

5 files changed

+85
-2
lines changed

5 files changed

+85
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [0.2.2 - 2023-09-2x]
5+
## [0.2.2 - 2023-09-26]
66

77
### Added
88

99
- FilesAPI: [Chunked v2 upload](https://docs.nextcloud.com/server/latest/developer_manual/client_apis/WebDAV/chunking.html#chunked-upload-v2) support, enabled by default.
1010
- New option to disable `chunked v2 upload` if there is a need for that: `CHUNKED_UPLOAD_V2`
1111
- TalkAPI: Poll API support(create_poll, get_poll, vote_poll, close_poll).
12+
- TalkAPI: Conversation avatar API(get_conversation_avatar, set_conversation_avatar, delete_conversation_avatar)
1213

1314
### Changed
1415

nc_py_api/_session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ def _ocs(self, method: str, path_params: str, headers: dict, data: Optional[byte
190190
url_params = f"{self.cfg.endpoint}{path_params}"
191191
info = f"request: method={method}, url={url_params}"
192192
nested_req = kwargs.pop("nested_req", False)
193+
not_parse = kwargs.pop("not_parse", False)
193194
try:
194195
timeout = kwargs.pop("timeout", self.cfg.options.timeout)
195196
if method == "GET":
@@ -203,6 +204,8 @@ def _ocs(self, method: str, path_params: str, headers: dict, data: Optional[byte
203204

204205
self.response_headers = response.headers
205206
check_error(response.status_code, info)
207+
if not_parse:
208+
return response
206209
response_data = loads(response.text)
207210
ocs_meta = response_data["ocs"]["meta"]
208211
if ocs_meta["status"] != "ok":

nc_py_api/_talk_api.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,55 @@ def close_poll(self, poll: typing.Union[Poll, int], conversation: typing.Union[C
463463
token = conversation.token if isinstance(conversation, Conversation) else conversation
464464
return Poll(self._session.ocs("DELETE", self._ep_base + f"/api/v1/poll/{token}/{poll_id}"), token)
465465

466+
def set_conversation_avatar(
467+
self,
468+
conversation: typing.Union[Conversation, str],
469+
avatar: typing.Union[bytes, tuple[str, typing.Union[str, None]]],
470+
) -> Conversation:
471+
"""Set image or emoji as avatar for the conversation.
472+
473+
:param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`.
474+
:param avatar: Squared image with mimetype equal to PNG or JPEG or a tuple with emoji and optional
475+
HEX color code(6 times ``0-9A-F``) without the leading ``#`` character.
476+
477+
.. note:: Color omit to fallback to the default bright/dark mode icon background color.
478+
"""
479+
require_capabilities("spreed.features.avatar", self._session.capabilities)
480+
token = conversation.token if isinstance(conversation, Conversation) else conversation
481+
if isinstance(avatar, bytes):
482+
r = self._session.ocs("POST", self._ep_base + f"/api/v1/room/{token}/avatar", files={"file": avatar})
483+
else:
484+
r = self._session.ocs(
485+
"POST",
486+
self._ep_base + f"/api/v1/room/{token}/avatar/emoji",
487+
json={
488+
"emoji": avatar[0],
489+
"color": avatar[1],
490+
},
491+
)
492+
return Conversation(r)
493+
494+
def delete_conversation_avatar(self, conversation: typing.Union[Conversation, str]) -> Conversation:
495+
"""Delete conversation avatar.
496+
497+
:param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`.
498+
"""
499+
require_capabilities("spreed.features.avatar", self._session.capabilities)
500+
token = conversation.token if isinstance(conversation, Conversation) else conversation
501+
return Conversation(self._session.ocs("DELETE", self._ep_base + f"/api/v1/room/{token}/avatar"))
502+
503+
def get_conversation_avatar(self, conversation: typing.Union[Conversation, str], dark=False) -> bytes:
504+
"""Get conversation avatar (binary).
505+
506+
:param conversation: conversation token or :py:class:`~nc_py_api.talk.Conversation`.
507+
:param dark: boolean indicating should be or not avatar fetched for dark theme.
508+
"""
509+
require_capabilities("spreed.features.avatar", self._session.capabilities)
510+
token = conversation.token if isinstance(conversation, Conversation) else conversation
511+
ep_suffix = "/dark" if dark else ""
512+
response = self._session.ocs("GET", self._ep_base + f"/api/v1/room/{token}/avatar" + ep_suffix, not_parse=True)
513+
return response.content
514+
466515
@staticmethod
467516
def _get_token(message: typing.Union[TalkMessage, str], conversation: typing.Union[Conversation, str]) -> str:
468517
if not conversation and not isinstance(message, TalkMessage):

nc_py_api/options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
elif str_val.lower() not in ("true", "1"):
4040
NPA_NC_CERT = str_val
4141

42-
CHUNKED_UPLOAD_V2 = True
42+
CHUNKED_UPLOAD_V2 = environ.get("CHUNKED_UPLOAD_V2", True)
4343
"""Option to enable/disable **version 2** chunked upload(better Object Storages support).
4444
4545
Additional information can be found in Nextcloud documentation:

tests/actual_tests/talk_test.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from io import BytesIO
12
from os import environ
23

34
import pytest
5+
from PIL import Image
46

57
from nc_py_api import Nextcloud, talk, talk_bot
68

@@ -322,3 +324,31 @@ def test_vote_poll(nc_any):
322324
assert isinstance(poll.details[0].actor_display_name, str)
323325
finally:
324326
nc_any.talk.delete_conversation(conversation)
327+
328+
329+
@pytest.mark.require_nc(major=27)
330+
def test_conversation_avatar(nc_any):
331+
if nc_any.talk.available is False:
332+
pytest.skip("Nextcloud Talk is not installed")
333+
334+
conversation = nc_any.talk.create_conversation(talk.ConversationType.GROUP, "admin")
335+
try:
336+
assert conversation.is_custom_avatar is False
337+
r = nc_any.talk.get_conversation_avatar(conversation)
338+
assert isinstance(r, bytes)
339+
im = Image.effect_mandelbrot((512, 512), (-3, -2.5, 2, 2.5), 100)
340+
buffer = BytesIO()
341+
im.save(buffer, format="PNG")
342+
buffer.seek(0)
343+
r = nc_any.talk.set_conversation_avatar(conversation, buffer.read())
344+
assert r.is_custom_avatar is True
345+
r = nc_any.talk.get_conversation_avatar(conversation)
346+
assert isinstance(r, bytes)
347+
r = nc_any.talk.delete_conversation_avatar(conversation)
348+
assert r.is_custom_avatar is False
349+
r = nc_any.talk.set_conversation_avatar(conversation, ("🫡", None))
350+
assert r.is_custom_avatar is True
351+
r = nc_any.talk.get_conversation_avatar(conversation, dark=True)
352+
assert isinstance(r, bytes)
353+
finally:
354+
nc_any.talk.delete_conversation(conversation)

0 commit comments

Comments
 (0)