diff --git a/python/examples/test_examples.py b/python/examples/test_examples.py index c9c38c26d..914db39e7 100644 --- a/python/examples/test_examples.py +++ b/python/examples/test_examples.py @@ -26,7 +26,7 @@ def test_echo_quit_plugin(acfactory, lp): lp.sec("sending a message to the bot") bot_contact = ac1.create_contact(botproc.addr) - ch1 = ac1.create_chat_by_contact(bot_contact) + ch1 = bot_contact.create_chat() ch1.send_text("hello") lp.sec("waiting for the bot-reply to arrive") diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 0dfdb7f9d..96cbce0ec 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -214,27 +214,27 @@ class Account(object): """ return Contact(self, const.DC_CONTACT_ID_SELF) - def create_contact(self, email, name=None): + def create_contact(self, addr, name=None): """ create a (new) Contact. If there already is a Contact with that e-mail address, it is unblocked and its display name is updated. - :param email: email-address (text type) or Contact (from other account). + :param addr: email-address (text type) or Contact (from other account). :param name: display name for this contact (optional) :returns: :class:`deltachat.contact.Contact` instance. """ - if isinstance(email, Contact): + if isinstance(addr, Contact): # might come from another account - name = email.name - addr = email.addr + name = addr.name + addr = addr.addr else: - realname, addr = parseaddr(email) - if not name and realname: - name = realname + parse_name, addr = parseaddr(addr) + if not name and parse_name: + name = parse_name name = as_dc_charpointer(name) addr = as_dc_charpointer(addr) contact_id = lib.dc_create_contact(self._dc_context, name, addr) - assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL + assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL, contact_id return Contact(self, contact_id) def delete_contact(self, contact): @@ -256,6 +256,19 @@ class Account(object): if contact_id: return self.get_contact_by_id(contact_id) + def get_contact_by_id(self, contact_id): + """ return Contact instance or None. + :param contact_id: integer id of this contact. + :returns: None or :class:`deltachat.contact.Contact` instance. + """ + return Contact(self, contact_id) + + def _port_contact(self, contact): + assert isinstance(contact, Contact) + if self != contact.account: + return self.create_contact(addr=contact.addr, name=contact.name) + return contact + def get_contacts(self, query=None, with_self=False, only_verified=False): """ get a (filtered) list of contacts. @@ -286,36 +299,19 @@ class Account(object): yield from iter_array(dc_array, lambda x: Message.from_db(self, x)) def create_chat(self, obj): - """ Create a 1:1 chat with Account/Contact/e-mail addresses. """ + """ Create a 1:1 chat with Account or e-mail addresse. """ if isinstance(obj, Account): if not obj.is_configured(): raise ValueError("can only add addresses from a configured account") - other = obj.get_self_contact() - contact = self.create_contact(other.addr, other.name) + contact = self.create_contact(obj.get_self_contact().addr) elif isinstance(obj, Contact): - contact = obj + contact = self._port_contact(obj) elif isinstance(obj, str): - realname, addr = parseaddr(obj) - contact = self.create_contact(addr, realname) + name, addr = parseaddr(obj) + contact = self.create_contact(addr, name) else: raise TypeError("don't know how to create chat for %r" % (obj, )) - return contact.get_chat() - - def create_chat_by_contact(self, contact): - """ create or get an existing 1:1 chat object for the specified contact or contact id. - - :param contact: chat_id (int) or contact object. - :returns: a :class:`deltachat.chat.Chat` object. - """ - if hasattr(contact, "id"): - if contact.account != self: - raise ValueError("Contact belongs to a different Account") - contact_id = contact.id - else: - assert isinstance(contact, int) - contact_id = contact - chat_id = lib.dc_create_chat_by_contact_id(self._dc_context, contact_id) - return Chat(self, chat_id) + return contact.create_chat() def create_chat_by_message(self, message): """ create or get an existing chat object for the @@ -376,13 +372,6 @@ class Account(object): """ return Message.from_db(self, msg_id) - def get_contact_by_id(self, contact_id): - """ return Contact instance or None. - :param contact_id: integer id of this contact. - :returns: None or :class:`deltachat.contact.Contact` instance. - """ - return Contact(self, contact_id) - def get_chat_by_id(self, chat_id): """ return Chat instance. :param chat_id: integer id of this chat. diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 32b50a1a0..d6f02e2df 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -18,6 +18,8 @@ class Chat(object): """ def __init__(self, account, id): + from .account import Account + assert isinstance(account, Account), repr(account) self.account = account self.id = id @@ -328,11 +330,6 @@ class Chat(object): # ------ group management API ------------------------------ - def _port_contact(self, contact): - if contact.account != self.account: - contact = self.account.create_contact(contact.addr, contact.display_name) - return contact - def add_contact(self, contact): """ add a contact to this chat. @@ -343,7 +340,7 @@ class Chat(object): :raises ValueError: if contact could not be added :returns: None """ - contact = self._port_contact(contact) + contact = self.account._port_contact(contact) ret = lib.dc_add_contact_to_chat(self.account._dc_context, self.id, contact.id) if ret != 1: raise ValueError("could not add contact {!r} to chat".format(contact)) @@ -356,7 +353,7 @@ class Chat(object): :raises ValueError: if contact could not be removed :returns: None """ - contact = self._port_contact(contact) + contact = self.account._port_contact(contact) ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id) if ret != 1: raise ValueError("could not remove contact {!r} from chat".format(contact)) diff --git a/python/src/deltachat/contact.py b/python/src/deltachat/contact.py index 4eb5efc68..7b7a59f6c 100644 --- a/python/src/deltachat/contact.py +++ b/python/src/deltachat/contact.py @@ -3,6 +3,8 @@ from . import props from .cutil import from_dc_charpointer from .capi import lib, ffi +from .chat import Chat +from . import const class Contact(object): @@ -11,6 +13,8 @@ class Contact(object): You obtain instances of it through :class:`deltachat.account.Account`. """ def __init__(self, account, id): + from .account import Account + assert isinstance(account, Account), repr(account) self.account = account self.id = id @@ -61,6 +65,16 @@ class Contact(object): return None return from_dc_charpointer(dc_res) - def get_chat(self): - """return 1:1 chat for this contact. """ - return self.account.create_chat_by_contact(self) + def create_chat(self): + """ create or get an existing 1:1 chat object for the specified contact or contact id. + + :param contact: chat_id (int) or contact object. + :returns: a :class:`deltachat.chat.Chat` object. + """ + dc_context = self.account._dc_context + chat_id = lib.dc_create_chat_by_contact_id(dc_context, self.id) + assert chat_id > const.DC_CHAT_ID_LAST_SPECIAL, chat_id + return Chat(self.account, chat_id) + + # deprecated name + get_chat = create_chat diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index 411e4a6af..933496835 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -387,14 +387,9 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): imap.dump_account_info(logfile=logfile) imap.dump_imap_structures(tmpdir, logfile=logfile) - def get_chats(self, ac1, ac2, both=True): - chat12 = ac1.create_chat_by_contact( - ac1.create_contact(email=ac2.get_config("addr"))) - chat21 = None - if both: - chat21 = ac2.create_chat_by_contact( - ac2.create_contact(email=ac1.get_config("addr"))) - return chat12, chat21 + def get_accepted_chat(self, ac1, ac2): + ac2.create_chat(ac1) + return ac1.create_chat(ac2) am = AccountMaker() request.addfinalizer(am.finalize) diff --git a/python/tests/test_account.py b/python/tests/test_account.py index c6aad913e..676ee88fd 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -105,8 +105,8 @@ class TestOfflineAccountBasic: class TestOfflineContact: def test_contact_attr(self, acfactory): ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact(email="some1@hello.com", name="some1") - contact2 = ac1.create_contact(email="some1@hello.com", name="some1") + contact1 = ac1.create_contact(addr="some1@hello.com", name="some1") + contact2 = ac1.create_contact(addr="some1@hello.com", name="some1") str(contact1) repr(contact1) assert contact1 == contact2 @@ -118,7 +118,7 @@ class TestOfflineContact: def test_get_contacts_and_delete(self, acfactory): ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact(email="some1@hello.com", name="some1") + contact1 = ac1.create_contact(addr="some1@hello.com", name="some1") contacts = ac1.get_contacts() assert len(contacts) == 1 assert contact1 in contacts @@ -134,9 +134,8 @@ class TestOfflineContact: def test_get_contacts_and_delete_fails(self, acfactory): ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact(email="some1@example.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) - msg = chat.send_text("one message") + contact1 = ac1.create_contact(addr="some1@example.com", name="some1") + msg = contact1.create_chat().send_text("one message") assert not ac1.delete_contact(contact1) assert not msg.filemime @@ -160,10 +159,7 @@ class TestOfflineChat: @pytest.fixture def chat1(self, ac1): - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) - assert chat.id > const.DC_CHAT_ID_LAST_SPECIAL, chat.id - return chat + return ac1.create_contact("some1@hello.com", name="some1").create_chat() def test_display(self, chat1): str(chat1) @@ -180,7 +176,7 @@ class TestOfflineChat: def test_chat_idempotent(self, chat1, ac1): contact1 = chat1.get_contacts()[0] - chat2 = ac1.create_chat_by_contact(contact1.id) + chat2 = contact1.create_chat() assert chat2.id == chat1.id assert chat2.get_name() == chat1.get_name() assert chat1 == chat2 @@ -379,13 +375,11 @@ class TestOfflineChat: contact2 = ac1.create_contact("display1 ", "real") assert contact2.display_name == "real" - def test_create_chat_mismatch(self, acfactory): + def test_create_chat_and_mismatch(self, acfactory): ac1 = acfactory.get_configured_offline_account() ac2 = acfactory.get_configured_offline_account() contact1 = ac1.create_contact("some1@hello.com", name="some1") - with pytest.raises(ValueError): - ac2.create_chat_by_contact(contact1) - chat1 = ac1.create_chat_by_contact(contact1) + chat1 = contact1.create_chat() msg = chat1.send_text("hello") with pytest.raises(ValueError): ac2.create_chat_by_message(msg) @@ -409,8 +403,7 @@ class TestOfflineChat: def test_import_export_one_contact(self, acfactory, tmpdir): backupdir = tmpdir.mkdir("backup") ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) + chat = ac1.create_contact("some1 ").create_chat() # send a text message msg = chat.send_text("msg1") # send a binary file @@ -429,7 +422,7 @@ class TestOfflineChat: assert len(contacts) == 1 contact2 = contacts[0] assert contact2.addr == "some1@hello.com" - chat2 = ac2.create_chat_by_contact(contact2) + chat2 = contact2.create_chat() messages = chat2.get_messages() assert len(messages) == 2 assert messages[0].text == "msg1" @@ -534,7 +527,7 @@ class TestOfflineChat: def test_basic_imap_api(acfactory, tmpdir): ac1, ac2 = acfactory.get_two_online_accounts() - chat12, _ = acfactory.get_chats(ac1, ac2) + chat12 = acfactory.get_accepted_chat(ac1, ac2) imap2 = ac2.direct_imap @@ -569,7 +562,7 @@ class TestOnlineAccount: ac1.start_io() ac2.wait_configure_finish() ac2.start_io() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) lp.sec("ac1: send unencrypted message to ac2") chat.send_text("message1") @@ -630,7 +623,7 @@ class TestOnlineAccount: ac1_clone.wait_configure_finish() ac1_clone.start_io() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) self_addr = ac1.get_config("addr") other_addr = ac2.get_config("addr") @@ -672,7 +665,7 @@ class TestOnlineAccount: def test_send_file_twice_unicode_filename_mangling(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) basename = "somedäüta.html.zip" p = os.path.join(tmpdir.strpath, basename) @@ -704,7 +697,7 @@ class TestOnlineAccount: def test_send_file_html_attachment(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) basename = "test.html" content = "textdata" @@ -742,7 +735,7 @@ class TestOnlineAccount: ac1.start_io() lp.sec("ac1: send message and wait for ac2 to receive it") - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) chat.send_text("message1") ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG") assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL @@ -755,7 +748,7 @@ class TestOnlineAccount: ac2.start_io() ac1.wait_configure_finish() ac1.start_io() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) chat.send_text("message1") ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG") assert ev.data2 > const.DC_CHAT_ID_LAST_SPECIAL @@ -770,7 +763,7 @@ class TestOnlineAccount: ac1.wait_configure_finish() ac1.start_io() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) chat.send_text("message1") chat.send_text("message2") chat.send_text("message3") @@ -813,7 +806,7 @@ class TestOnlineAccount: def test_forward_own_message(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) lp.sec("sending message") msg_out = chat.send_text("message2") @@ -838,7 +831,7 @@ class TestOnlineAccount: def test_send_self_message_and_empty_folder(self, acfactory, lp): ac1 = acfactory.get_one_online_account(mvbox=True, move=True) lp.sec("ac1: create self chat") - chat = ac1.create_chat_by_contact(ac1.get_self_contact()) + chat = ac1.get_self_contact().create_chat() chat.send_text("hello") ac1._evtracker.get_matching("DC_EVENT_SMTP_MESSAGE_SENT") ac1.empty_server_folders(inbox=True, mvbox=True) @@ -1002,7 +995,7 @@ class TestOnlineAccount: ac2.set_config("save_mime_headers", "1") lp.sec("ac1: create chat with ac2") - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) lp.sec("sending multi-line non-unicode message from ac1 to ac2") text1 = "hello\nworld" @@ -1021,7 +1014,7 @@ class TestOnlineAccount: lp.sec("wait for ac2 to receive multi-line unicode message") msg_in = ac2._evtracker.wait_next_incoming_message() assert msg_in.text == text2 - assert ac1.get_config("addr") in msg_in.chat.get_name() + assert ac1.get_config("addr") in [x.addr for x in msg_in.chat.get_contacts()] def test_reply_encrypted(self, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() @@ -1097,7 +1090,7 @@ class TestOnlineAccount: def test_send_mark_seen_clean_incoming_events(self, acfactory, lp, data): ac1, ac2 = acfactory.get_two_online_accounts() - chat, _ = acfactory.get_chats(ac1, ac2) + chat = acfactory.get_accepted_chat(ac1, ac2) message_queue = queue.Queue() @@ -1174,8 +1167,7 @@ class TestOnlineAccount: lp.sec("create some chat content") contact1 = ac1.create_contact("some1@hello.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) - chat.send_text("msg1") + contact1.create_chat().send_text("msg1") backupdir = tmpdir.mkdir("backup") lp.sec("export all to {}".format(backupdir)) @@ -1196,7 +1188,7 @@ class TestOnlineAccount: assert len(contacts) == 1 contact2 = contacts[0] assert contact2.addr == "some1@hello.com" - chat2 = ac2.create_chat_by_contact(contact2) + chat2 = contact2.create_chat() messages = chat2.get_messages() assert len(messages) == 1 assert messages[0].text == "msg1" @@ -1331,9 +1323,7 @@ class TestOnlineAccount: ac2.set_avatar(p) lp.sec("ac1: send message to ac2") - chat = ac1.create_chat(ac2) - - msg1 = chat.send_text("hi -- do you see my brand new avatar?") + msg1 = ac1.create_chat(ac2).send_text("with avatar!") assert not msg1.is_encrypted() lp.sec("ac2: wait for receiving message and avatar from ac1") @@ -1357,7 +1347,7 @@ class TestOnlineAccount: lp.sec("ac1: delete profile image from chat, and send message to ac2") ac1.set_avatar(None) - msg5 = chat.send_text("i don't like my avatar anymore and removed it") + msg5 = ac1.create_chat(ac2).send_text("removing my avatar") assert msg5.is_encrypted() lp.sec("ac2: wait for message along with avatar deletion of ac1") @@ -1398,7 +1388,7 @@ class TestOnlineAccount: lp.sec("ac1: create group chat with ac2") chat = ac1.create_group_chat("hello") - contact = ac1.create_contact(email=ac2_addr) + contact = ac1.create_contact(ac2_addr) chat.add_contact(contact) lp.sec("ac1: send a message to group chat to promote the group") @@ -1412,7 +1402,7 @@ class TestOnlineAccount: lp.sec("ac1: add address2") # note that if the above create_chat() would not # happen we would not receive a proper member_added event - contact2 = ac1.create_contact(email="notexistingaccountihope@testrun.org") + contact2 = ac1.create_contact(addr="notexistingaccountihope@testrun.org") chat.add_contact(contact2) ev = in_list.get(timeout=10) assert ev.action == "chat-modified" @@ -1459,12 +1449,12 @@ class TestOnlineAccount: assert chat.get_profile_image() lp.sec("ac2: check that initial message arrived") - c1 = ac2.create_contact(email=ac1.get_config("addr")) - ac2.create_chat_by_contact(c1) + c1 = ac2.create_contact(addr=ac1.get_config("addr")) + c1.create_chat() ac2._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") lp.sec("ac1: add ac2 to promoted group chat") - c2 = ac1.create_contact(email=ac2.get_config("addr")) + c2 = ac1.create_contact(addr=ac2.get_config("addr")) chat.add_contact(c2) # sends one message lp.sec("ac1: send a first message to ac2") @@ -1496,7 +1486,8 @@ class TestOnlineAccount: ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create chat with ac2") - chat1, chat2 = acfactory.get_chats(ac1, ac2) + chat1 = ac1.create_chat(ac2) + chat2 = ac2.create_chat(ac1) assert not chat1.is_sending_locations() with pytest.raises(ValueError): @@ -1642,14 +1633,12 @@ class TestGroupStressTests: contacts.append(contact) # make sure we accept the "hi" message - ac1.create_chat_by_contact(contact) + contact.create_chat() # make sure the other side accepts our messages - c1 = acc.create_contact(ac1.get_config("addr"), "ä member") - chat1 = acc.create_chat_by_contact(c1) + acc.create_chat(ac1).send_text("hi") # send a message to get the contact key via autocrypt header - chat1.send_text("hi") msg = ac1._evtracker.wait_next_incoming_message() assert msg.text == "hi" @@ -1746,11 +1735,10 @@ class TestGroupStressTests: contacts.append(contact) # make sure we accept the "hi" message - ac1.create_chat_by_contact(contact) + contact.create_chat() # make sure the other side accepts our messages - c1 = acc.create_contact(ac1.get_config("addr"), "a member") - chat1 = acc.create_chat_by_contact(c1) + chat1 = acc.create_chat(ac1) # send a message to get the contact key via autocrypt header chat1.send_text("hi") diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index 99d49771d..da61c1b24 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -36,9 +36,7 @@ def wait_msgs_changed(account, msgs_list): class TestOnlineInCreation: def test_increation_not_blobdir(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() - - c2 = ac1.create_contact(email=ac2.get_config("addr")) - chat = ac1.create_chat_by_contact(c2) + chat = ac1.create_chat(ac2) lp.sec("Creating in-creation file outside of blobdir") assert tmpdir.strpath != ac1.get_blobdir() @@ -48,9 +46,7 @@ class TestOnlineInCreation: def test_no_increation_copies_to_blobdir(self, tmpdir, acfactory, lp): ac1, ac2 = acfactory.get_two_online_accounts() - - c2 = ac1.create_contact(email=ac2.get_config("addr")) - chat = ac1.create_chat_by_contact(c2) + chat = ac1.create_chat(ac2) lp.sec("Creating file outside of blobdir") assert tmpdir.strpath != ac1.get_blobdir() @@ -64,9 +60,7 @@ class TestOnlineInCreation: def test_forward_increation(self, acfactory, data, lp): ac1, ac2 = acfactory.get_two_online_accounts() - c2 = ac1.create_contact(email=ac2.get_config("addr")) - chat = ac1.create_chat_by_contact(c2) - assert chat.id >= const.DC_CHAT_ID_LAST_SPECIAL + chat = ac1.create_chat(ac2) wait_msgs_changed(ac1, [(0, 0)]) # why no chat id? lp.sec("create a message with a file in creation") @@ -80,7 +74,7 @@ class TestOnlineInCreation: lp.sec("forward the message while still in creation") chat2 = ac1.create_group_chat("newgroup") - chat2.add_contact(c2) + chat2.add_contact(ac2.get_self_contact()) wait_msgs_changed(ac1, [(0, 0)]) # why not chat id? ac1.forward_messages([prepared_original], chat2) # XXX there might be two EVENT_MSGS_CHANGED and only one of them diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index 2e7640e36..62fce7275 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -69,8 +69,8 @@ def test_sig(): def test_markseen_invalid_message_ids(acfactory): ac1 = acfactory.get_configured_offline_account() - contact1 = ac1.create_contact(email="some1@example.com", name="some1") - chat = ac1.create_chat_by_contact(contact1) + contact1 = ac1.create_contact(addr="some1@example.com", name="some1") + chat = contact1.create_chat() chat.send_text("one messae") ac1._evtracker.get_matching("DC_EVENT_MSGS_CHANGED") msg_ids = [9]