diff --git a/CHANGELOG.md b/CHANGELOG.md index 2834152bb..cfeb46710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ - set a default error if NDN does not provide an error - python: avoid exceptions when messages/contacts/chats are compared with `None` +### API-Changes +- python: added `Message.get_status_updates()` #3416 +- python: added `Message.send_status_update()` #3416 +- python: added `Message.is_webxdc()` #3416 +- python: added `Message.is_videochat_invitation()` #3416 +- python: added support for "videochat" and "webxdc" view types to `Message.new_empty()` #3416 + ## 1.86.0 ### API-Changes diff --git a/python/src/deltachat/message.py b/python/src/deltachat/message.py index ae77f5ffc..a7a9d9b58 100644 --- a/python/src/deltachat/message.py +++ b/python/src/deltachat/message.py @@ -1,9 +1,10 @@ """ The Message object. """ +import json import os import re from datetime import datetime, timezone -from typing import Optional +from typing import Optional, Union from . import const, props from .capi import ffi, lib @@ -54,8 +55,8 @@ class Message(object): def new_empty(cls, account, view_type): """create a non-persistent message. - :param: view_type is the message type code or one of the strings: - "text", "audio", "video", "file", "sticker" + :param view_type: the message type code or one of the strings: + "text", "audio", "video", "file", "sticker", "videochat", "webxdc" """ if isinstance(view_type, int): view_type_code = view_type @@ -130,6 +131,36 @@ class Message(object): """mime type of the file (if it exists)""" return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg)) + def get_status_updates(self, serial: int = 0) -> list: + """Get the status updates of this webxdc message. + + The status updates may be sent by yourself or by other members. + If this message doesn't have a webxdc instance, an empty list is returned. + + :param serial: The last known serial. Pass 0 if there are no known serials to receive all updates. + """ + return json.loads( + from_dc_charpointer(lib.dc_get_webxdc_status_updates(self.account._dc_context, self.id, serial)) + ) + + def send_status_update(self, json_data: Union[str, dict], description: str) -> bool: + """Send an status update for the webxdc instance of this message. + + If the webxdc instance is a draft, the update is not sent immediately. + Instead, the updates are collected and sent out in a batch when the instance is actually sent. + + :param json_data: program-readable data, the actual payload. + :param description: The user-visible description of JSON data + :returns: True on success, False otherwise + """ + if isinstance(json_data, dict): + json_data = json.dumps(json_data, default=str) + return bool( + lib.dc_send_webxdc_status_update( + self.account._dc_context, self.id, as_dc_charpointer(json_data), as_dc_charpointer(description) + ) + ) + def is_system_message(self): """return True if this message is a system/info message.""" return bool(lib.dc_msg_is_info(self._dc_msg)) @@ -402,6 +433,14 @@ class Message(object): """return True if it's a video message.""" return self._view_type == const.DC_MSG_VIDEO + def is_videochat_invitation(self): + """return True if it's a videochat invitation message.""" + return self._view_type == const.DC_MSG_VIDEOCHAT_INVITATION + + def is_webxdc(self): + """return True if it's a Webxdc message.""" + return self._view_type == const.DC_MSG_WEBXDC + def is_file(self): """return True if it's a file message.""" return self._view_type == const.DC_MSG_FILE @@ -421,6 +460,8 @@ _view_type_mapping = { "video": const.DC_MSG_VIDEO, "file": const.DC_MSG_FILE, "sticker": const.DC_MSG_STICKER, + "videochat": const.DC_MSG_VIDEOCHAT_INVITATION, + "webxdc": const.DC_MSG_WEBXDC, } diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index 0dc92188a..07e78ea8c 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -241,6 +241,7 @@ def data(request): os.path.normpath(x) for x in [ os.path.join(os.path.dirname(request.fspath.strpath), "data"), + os.path.join(os.path.dirname(request.fspath.strpath), "..", "..", "test-data"), os.path.join(os.path.dirname(__file__), "..", "..", "..", "test-data"), ] ] diff --git a/python/tests/test_1_online.py b/python/tests/test_1_online.py index d5524cf68..d9f1ac7fc 100644 --- a/python/tests/test_1_online.py +++ b/python/tests/test_1_online.py @@ -238,6 +238,70 @@ def test_html_message(acfactory, lp): assert html_text in msg2.html +def test_videochat_invitation_message(acfactory, lp): + ac1, ac2 = acfactory.get_online_accounts(2) + chat = acfactory.get_accepted_chat(ac1, ac2) + text = "You are invited to a video chat, click https://meet.jit.si/WxEGad0gGzX to join." + + lp.sec("ac1: prepare and send text message to ac2") + msg1 = chat.send_text("message0") + assert not msg1.is_videochat_invitation() + + lp.sec("wait for ac2 to receive message") + msg2 = ac2._evtracker.wait_next_incoming_message() + assert msg2.text == "message0" + assert not msg2.is_videochat_invitation() + + lp.sec("ac1: prepare and send videochat invitation to ac2") + msg1 = Message.new_empty(ac1, "videochat") + msg1.set_text(text) + msg1 = chat.send_msg(msg1) + assert msg1.is_videochat_invitation() + + lp.sec("wait for ac2 to receive message") + msg2 = ac2._evtracker.wait_next_incoming_message() + assert msg2.text == text + assert msg2.is_videochat_invitation() + + +def test_webxdc_message(acfactory, data, lp): + ac1, ac2 = acfactory.get_online_accounts(2) + chat = acfactory.get_accepted_chat(ac1, ac2) + + lp.sec("ac1: prepare and send text message to ac2") + msg1 = chat.send_text("message0") + assert not msg1.is_webxdc() + assert not msg1.send_status_update({"payload": "not an webxdc"}, "invalid") + assert not msg1.get_status_updates() + + lp.sec("wait for ac2 to receive message") + msg2 = ac2._evtracker.wait_next_incoming_message() + assert msg2.text == "message0" + assert not msg2.is_webxdc() + assert not msg1.get_status_updates() + + lp.sec("ac1: prepare and send webxdc instance to ac2") + msg1 = Message.new_empty(ac1, "webxdc") + msg1.set_text("message1") + msg1.set_file(data.get_path("webxdc/minimal.xdc")) + msg1 = chat.send_msg(msg1) + assert msg1.is_webxdc() + assert msg1.filename + + assert msg1.send_status_update({"payload": "test1"}, "some test data") + assert msg1.send_status_update({"payload": "test2"}, "more test data") + assert len(msg1.get_status_updates()) == 2 + update1 = msg1.get_status_updates()[0] + assert update1["payload"] == "test1" + assert len(msg1.get_status_updates(update1["serial"])) == 1 + + lp.sec("wait for ac2 to receive message") + msg2 = ac2._evtracker.wait_next_incoming_message() + assert msg2.text == "message1" + assert msg2.is_webxdc() + assert msg2.filename + + def test_mvbox_sentbox_threads(acfactory, lp): lp.sec("ac1: start with mvbox thread") ac1 = acfactory.new_online_configuring_account(mvbox_move=True, sentbox_watch=True)