From b6355176de997f91d76cd9c73b9a2f414d982958 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Sat, 6 Oct 2018 18:40:52 +0200 Subject: [PATCH] add chat.delete(), chat.send_image, msg.filename, msg.filemime and msg.type.is_* --- python/doc/api.rst | 6 ++ python/src/deltachat/chatting.py | 94 +++++++++++++++++++++++++++++++- python/src/deltachat/const.py | 13 ++++- python/tests/conftest.py | 14 +++++ python/tests/test_account.py | 57 +++++++++++++++++++ 5 files changed, 181 insertions(+), 3 deletions(-) diff --git a/python/doc/api.rst b/python/doc/api.rst index 93fcd228..7a6c003e 100644 --- a/python/doc/api.rst +++ b/python/doc/api.rst @@ -11,6 +11,7 @@ high level API reference - :class:`deltachat.chatting.Contact` - :class:`deltachat.chatting.Chat` - :class:`deltachat.chatting.Message` +- :class:`deltachat.chatting.MessageType` - :class:`deltachat.chatting.MessageState` Account @@ -38,6 +39,11 @@ Message .. autoclass:: deltachat.chatting.Message :members: +MessageType +------------ + +.. autoclass:: deltachat.chatting.MessageType + :members: MessageState ------------ diff --git a/python/src/deltachat/chatting.py b/python/src/deltachat/chatting.py index 10c2780e..b6957bc4 100644 --- a/python/src/deltachat/chatting.py +++ b/python/src/deltachat/chatting.py @@ -1,5 +1,6 @@ """ chatting related objects: Contact, Chat, Message. """ +import os from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array from .capi import lib, ffi from .types import property_with_doc @@ -60,6 +61,16 @@ class Chat(object): lib.dc_chat_unref ) + def delete(self): + """Delete this chat and all its messages. + + Note: + + - does not delete messages on server + - the chat or contact is not blocked, new message will arrive + """ + lib.dc_delete_chat(self._dc_context, self.id) + # ------ chat status/metadata API ------------------------------ def is_deaddrop(self): @@ -100,10 +111,28 @@ class Chat(object): """ send a text message and return the resulting Message instance. :param msg: unicode text + :raises: ValueError if message can not be send/chat does not exist. :returns: the resulting :class:`deltachat.chatting.Message` instance """ msg = as_dc_charpointer(msg) msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg) + if msg_id == 0: + raise ValueError("message could not be send, does chat exist?") + return Message(self._dc_context, msg_id) + + def send_image(self, path): + """ send an image message and return the resulting Message instance. + + :param path: path to an image file. + :raises: ValueError if message can not be send/chat does not exist. + :returns: the resulting :class:`deltachat.chatting.Message` instance + """ + if not os.path.exists(path): + raise ValueError("path does not exist: {!r}".format(path)) + path = as_dc_charpointer(path) + msg_id = lib.dc_send_image_msg(self._dc_context, self.id, path, ffi.NULL, 0, 0) + if msg_id == 0: + raise ValueError("chat does not exist") return Message(self._dc_context, msg_id) def get_messages(self): @@ -186,9 +215,27 @@ class Message(object): @property_with_doc def text(self): - """unicode text of this messages. """ + """unicode text of this messages (might be empty if not a text message). """ return from_dc_charpointer(lib.dc_msg_get_text(self._dc_msg)) + @property_with_doc + def filename(self): + """filename if there was an attachment, otherwise empty string. """ + return from_dc_charpointer(lib.dc_msg_get_file(self._dc_msg)) + + @property_with_doc + def filemime(self): + """mime type of the file (if it exists)""" + return from_dc_charpointer(lib.dc_msg_get_filemime(self._dc_msg)) + + @property_with_doc + def type(self): + """the media type of this message. + + :returns: a :class:`deltachat.chatting.MessageType` instance. + """ + return MessageType(lib.dc_msg_get_type(self._dc_msg)) + @property_with_doc def time_sent(self): """time when the message was sent. @@ -210,12 +257,55 @@ class Message(object): def get_sender_contact(self): """return the contact of who wrote the message. - :returns: :class:`deltachat.chatting.Contact`` instance + :returns: :class:`deltachat.chatting.Contact` instance """ contact_id = lib.dc_msg_get_from_id(self._dc_msg) return Contact(self._dc_context, contact_id) +@attr.s +class MessageType(object): + """ DeltaChat message type, with is_* methods. """ + _type = attr.ib(validator=v.instance_of(int)) + _mapping = { + const.DC_MSG_TEXT: 'text', + const.DC_MSG_IMAGE: 'image', + const.DC_MSG_GIF: 'gif', + const.DC_MSG_AUDIO: 'audio', + const.DC_MSG_VIDEO: 'video', + const.DC_MSG_FILE: 'file' + } + + @property_with_doc + def name(self): + """ human readable type name. """ + return self._mapping[self._type] + + def is_text(self): + """ return True if it's a text message. """ + return self._type == const.DC_MSG_TEXT + + def is_image(self): + """ return True if it's an image message. """ + return self._type == const.DC_MSG_IMAGE + + def is_gif(self): + """ return True if it's a gif message. """ + return self._type == const.DC_MSG_GIF + + def is_audio(self): + """ return True if it's an audio message. """ + return self._type == const.DC_MSG_AUDIO + + def is_video(self): + """ return True if it's a video message. """ + return self._type == const.DC_MSG_VIDEO + + def is_file(self): + """ return True if it's a file message. """ + return self._type == const.DC_MSG_FILE + + @attr.s class MessageState(object): """ Current Message In/Out state, updated on each call of is_* methods. diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index 7d91a05a..a66ac847 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -24,6 +24,9 @@ DC_CHAT_TYPE_UNDEFINED = 0 DC_CHAT_TYPE_SINGLE = 100 DC_CHAT_TYPE_GROUP = 120 DC_CHAT_TYPE_VERIFIED_GROUP = 130 +DC_MSG_ID_MARKER1 = 1 +DC_MSG_ID_DAYMARKER = 9 +DC_MSG_ID_LAST_SPECIAL = 9 DC_STATE_UNDEFINED = 0 DC_STATE_IN_FRESH = 10 DC_STATE_IN_NOTICED = 13 @@ -35,6 +38,14 @@ DC_STATE_OUT_MDN_RCVD = 28 DC_CONTACT_ID_SELF = 1 DC_CONTACT_ID_DEVICE = 2 DC_CONTACT_ID_LAST_SPECIAL = 9 +DC_MSG_UNDEFINED = 0 +DC_MSG_TEXT = 10 +DC_MSG_IMAGE = 20 +DC_MSG_GIF = 21 +DC_MSG_AUDIO = 40 +DC_MSG_VOICE = 41 +DC_MSG_VIDEO = 50 +DC_MSG_FILE = 60 DC_EVENT_INFO = 100 DC_EVENT_SMTP_CONNECTED = 101 DC_EVENT_IMAP_CONNECTED = 102 @@ -62,7 +73,7 @@ DC_EVENT_HTTP_GET = 2100 def read_event_defines(f): - rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*') + rex = re.compile(r'#define\s+((?:DC_EVENT_|DC_MSG|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+)\s+([x\d]+).*') for line in f: m = rex.match(line) if m: diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 8cde2d22..fddd0ec9 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -1,4 +1,5 @@ from __future__ import print_function +import os import pytest from deltachat import Account from deltachat.types import cached_property @@ -13,6 +14,19 @@ def pytest_addoption(parser): ) +@pytest.fixture(scope="session") +def data(): + class Data: + def __init__(self): + self.path = os.path.join(os.path.dirname(__file__), "data") + + def get_path(self, bn): + fn = os.path.join(self.path, bn) + assert os.path.exists(fn) + return fn + return Data() + + @pytest.fixture def acfactory(pytestconfig, tmpdir, request): fn = pytestconfig.getoption("--liveconfig") diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 492f09ce..7091fdcf 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1,5 +1,6 @@ from __future__ import print_function import pytest +import os from deltachat import const from datetime import datetime, timedelta from conftest import wait_configuration_progress, wait_successful_IMAP_SMTP_connection @@ -82,12 +83,28 @@ class TestOfflineAccount: chat.set_name("title2") assert chat.get_name() == "title2" + def test_delete_and_send_fails(self, acfactory): + ac1 = acfactory.get_configured_offline_account() + contact1 = ac1.create_contact("some1@hello.com", name="some1") + chat = ac1.create_chat_by_contact(contact1) + chat.delete() + ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") + with pytest.raises(ValueError): + chat.send_text_message("msg1") + def test_message(self, acfactory): ac1 = acfactory.get_configured_offline_account() contact1 = ac1.create_contact("some1@hello.com", name="some1") chat = ac1.create_chat_by_contact(contact1) msg = chat.send_text_message("msg1") assert msg + assert msg.type.is_text() + assert msg.type.name == "text" + assert not msg.type.is_audio() + assert not msg.type.is_video() + assert not msg.type.is_gif() + assert not msg.type.is_file() + assert not msg.type.is_image() msg_state = msg.get_state() assert not msg_state.is_in_fresh() assert not msg_state.is_in_noticed() @@ -97,6 +114,20 @@ class TestOfflineAccount: assert not msg_state.is_out_delivered() assert not msg_state.is_out_mdn_received() + def test_message_image(self, acfactory, data): + ac1 = acfactory.get_configured_offline_account() + contact1 = ac1.create_contact("some1@hello.com", name="some1") + chat = ac1.create_chat_by_contact(contact1) + with pytest.raises(ValueError): + chat.send_image(path="notexists") + fn = data.get_path("d.png") + msg = chat.send_image(fn) + assert msg.type.name == "image" + assert msg + assert msg.id > 0 + assert os.path.exists(msg.filename) + assert msg.filemime == "image/png" + def test_chat_message_distinctions(self, acfactory): ac1 = acfactory.get_configured_offline_account() contact1 = ac1.create_contact("some1@hello.com", name="some1") @@ -199,3 +230,29 @@ class TestOnlineAccount: lp.step("2") ac1._evlogger.get_info_matching("Message marked as seen") assert msg_out.get_state().is_out_mdn_received() + + def test_send_and_receive_image(self, acfactory, lp, data): + lp.sec("starting accounts, waiting for configuration") + ac1 = acfactory.get_online_configuring_account() + ac2 = acfactory.get_online_configuring_account() + c2 = ac1.create_contact(email=ac2.get_config("addr")) + chat = ac1.create_chat_by_contact(c2) + + wait_configuration_progress(ac1, 1000) + wait_configuration_progress(ac2, 1000) + + lp.sec("sending image message from ac1 to ac2") + path = data.get_path("d.png") + msg_out = chat.send_image(path) + ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED") + evt_name, data1, data2 = ev + assert data1 == chat.id + assert data2 == msg_out.id + assert msg_out.get_state().is_out_delivered() + + lp.sec("wait for ac2 to receive message") + ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") + assert ev[2] == msg_out.id + msg_in = ac2.get_message_by_id(msg_out.id) + assert os.path.exists(msg_in.filename) + assert os.stat(msg_in.filename).st_size == os.stat(path).st_size