diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index f7760a0f2..5146093ed 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -218,20 +218,36 @@ class Account(object): with that e-mail address, it is unblocked and its display name is updated. - :param addr: email-address (text type) or Contact (from other account). + :param addr: email-address, Account or Contact instance. :param name: display name for this contact (optional) :returns: :class:`deltachat.contact.Contact` instance. """ - if isinstance(addr, Contact): - # might come from another account - name = addr.name - addr = addr.addr + if not isinstance(addr, (Account, Contact, str)): + raise TypeError(str(addr)) + return self.as_contact(addr, name=name) + + def as_contact(self, obj, name=None): + """ Create a contact from an Account, Contact or e-mail address. """ + if isinstance(obj, Account): + if not obj.is_configured(): + raise ValueError("can only add addresses from configured accounts") + addr, displayname = obj.get_config("addr"), obj.get_config("displayname") + elif isinstance(obj, Contact): + if obj.account != self: + raise ValueError("account mismatch {}".format(obj)) + addr, displayname = obj.addr, obj.name + elif isinstance(obj, str): + displayname, addr = parseaddr(obj) else: - parse_name, addr = parseaddr(addr) - if not name and parse_name: - name = parse_name - name = as_dc_charpointer(name) + raise TypeError("don't know how to create chat for %r" % (obj, )) + + if name is None and displayname: + name = displayname + return self._create_contact(addr, name) + + def _create_contact(self, addr, name): addr = as_dc_charpointer(addr) + name = as_dc_charpointer(name) contact_id = lib.dc_create_contact(self._dc_context, name, addr) assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL, contact_id return Contact(self, contact_id) @@ -262,12 +278,6 @@ class Account(object): """ 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. @@ -298,35 +308,28 @@ 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 or e-mail addresse. """ - if isinstance(obj, Account): - if not obj.is_configured(): - raise ValueError("can only add addresses from a configured account") - addr, name = obj.get_config("addr"), obj.get_config("displayname") - contact = self.create_contact(addr, name) - elif isinstance(obj, Contact): - contact = self._port_contact(obj) - elif isinstance(obj, str): - 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.create_chat() + """ Create a 1:1 chat with Account, Contact or e-mail address. """ + return self.as_contact(obj).create_chat() def _create_chat_by_message_id(self, msg_id): return Chat(self, lib.dc_create_chat_by_msg_id(self._dc_context, msg_id)) - def create_group_chat(self, name, verified=False): + def create_group_chat(self, name, contacts=None, verified=False): """ create a new group chat object. Chats are unpromoted until the first message is sent. + :param contacts: list of contacts to add :param verified: if true only verified contacts can be added. :returns: a :class:`deltachat.chat.Chat` object. """ bytes_name = name.encode("utf8") chat_id = lib.dc_create_group_chat(self._dc_context, int(verified), bytes_name) - return Chat(self, chat_id) + chat = Chat(self, chat_id) + if contacts is not None: + for contact in contacts: + chat.add_contact(contact) + return chat def get_chats(self): """ return list of chats. diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index d6f02e2df..e0e696053 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -330,39 +330,34 @@ class Chat(object): # ------ group management API ------------------------------ - def add_contact(self, contact): + def add_contact(self, obj): """ add a contact to this chat. - If the contact is from another account create a new - contact and add it to the group. - - :params: contact object. + :params obj: Contact, Account or e-mail address. :raises ValueError: if contact could not be added :returns: None """ - contact = self.account._port_contact(contact) + contact = self.account.as_contact(obj) 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)) return contact - def remove_contact(self, contact): + def remove_contact(self, obj): """ remove a contact from this chat. - :params: contact object. + :params obj: Contact, Account or e-mail address. :raises ValueError: if contact could not be removed :returns: None """ - contact = self.account._port_contact(contact) + contact = self.account.as_contact(obj) 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)) def get_contacts(self): """ get all contacts for this chat. - :params: contact object. :returns: list of :class:`deltachat.contact.Contact` objects for this chat - """ from .contact import Contact dc_array = ffi.gc( diff --git a/python/src/deltachat/testplugin.py b/python/src/deltachat/testplugin.py index c45d7d969..933b2c874 100644 --- a/python/src/deltachat/testplugin.py +++ b/python/src/deltachat/testplugin.py @@ -396,6 +396,14 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data): ac2.create_chat(ac1) return ac1.create_chat(ac2) + def accept_each_other(self, accounts, sending=False): + for i, acc in enumerate(accounts): + for acc2 in accounts[i + 1:]: + chat = self.get_accepted_chat(acc, acc2) + if sending: + chat.send_text("hi") + acc2._evtracker.wait_next_incoming_message() + am = AccountMaker() request.addfinalizer(am.finalize) yield am diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 86b1c0b7b..94546cf81 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -143,10 +143,8 @@ class TestOfflineContact: ac1 = acfactory.get_configured_offline_account() ac2 = acfactory.get_configured_offline_account() chat1 = ac1.create_chat(ac2) - contact = ac1.create_contact(ac2.get_self_contact()) - chat2 = ac1.create_chat(contact) - chat3 = ac1.create_chat(ac2.get_self_contact().addr) - assert chat1 == chat2 and chat2 == chat3 + chat2 = ac1.create_chat(ac2.get_self_contact().addr) + assert chat1 == chat2 ac3 = acfactory.get_unconfigured_account() with pytest.raises(ValueError): ac1.create_chat(ac3) @@ -192,10 +190,13 @@ class TestOfflineChat: ac1 = acfactory.get_configured_offline_account() ac2 = acfactory.get_configured_offline_account() chat = ac1.create_group_chat(name="title1") - ac2_contact = ac2.get_self_contact() - contact = chat.add_contact(ac2_contact) - assert contact != ac2_contact + with pytest.raises(ValueError): + chat.add_contact(ac2.get_self_contact()) + contact = chat.add_contact(ac2) + assert contact.addr == ac2.get_config("addr") + assert contact.name == ac2.get_config("displayname") assert contact.account == ac1 + chat.remove_contact(ac2) def test_group_chat_creation(self, ac1): contact1 = ac1.create_contact("some1@hello.com", name="some1") @@ -966,7 +967,7 @@ class TestOnlineAccount: lp.sec("create group chat with two members, one of which has no encrypt state") chat = ac1.create_group_chat("encryption test") - chat.add_contact(ac2.get_self_contact()) + chat.add_contact(ac2) chat.add_contact(ac1.create_contact("notexisting@testrun.org")) msg = chat.send_text("test not encrypt") assert not msg.is_encrypted() @@ -1146,6 +1147,7 @@ class TestOnlineAccount: lp.sec("create some chat content") contact1 = ac1.create_contact("some1@hello.com", name="some1") contact1.create_chat().send_text("msg1") + assert len(ac1.get_contacts(query="some1")) == 1 backupdir = tmpdir.mkdir("backup") lp.sec("export all to {}".format(backupdir)) @@ -1596,41 +1598,16 @@ class TestOnlineAccount: class TestGroupStressTests: def test_group_many_members_add_leave_remove(self, acfactory, lp): - lp.sec("creating and configuring five accounts") accounts = acfactory.get_many_online_accounts(5) - ac1 = accounts.pop() - - lp.sec("ac1: setting up contacts with 4 other members") - contacts = [] - for acc, name in zip(accounts, list("äöüsr")): - contact = ac1.create_contact(acc.get_config("addr"), name=name) - contacts.append(contact) - - # make sure we accept the "hi" message - contact.create_chat() - - # make sure the other side accepts our messages - acc.create_chat(ac1).send_text("hi") - - # send a message to get the contact key via autocrypt header - msg = ac1._evtracker.wait_next_incoming_message() - assert msg.text == "hi" - - # Save fifth account for later - ac5 = accounts.pop() - contact5 = contacts.pop() + acfactory.accept_each_other(accounts, sending=True) + ac1, ac5 = accounts.pop(), accounts.pop() lp.sec("ac1: creating group chat with 3 other members") - chat = ac1.create_group_chat("title1") - for contact in contacts: - chat.add_contact(contact) - assert not chat.is_promoted() + chat = ac1.create_group_chat("title1", contacts=accounts) lp.sec("ac1: send message to new group chat") - msg = chat.send_text("hello") - assert chat.is_promoted() - assert msg.is_encrypted() - + msg1 = chat.send_text("hello") + assert msg1.is_encrypted() gossiped_timestamp = chat.get_summary()["gossiped_timestamp"] assert gossiped_timestamp > 0 @@ -1639,24 +1616,23 @@ class TestGroupStressTests: lp.sec("ac2: checking that the chat arrived correctly") ac2 = accounts[0] - msg = ac2._evtracker.wait_next_incoming_message() - assert msg.text == "hello" - print("chat is", msg.chat) - assert len(msg.chat.get_contacts()) == 4 + msg2 = ac2._evtracker.wait_next_incoming_message() + assert msg2.text == "hello" + print("chat is", msg2.chat) + assert len(msg2.chat.get_contacts()) == 4 lp.sec("ac3: checking that 'ac4' is a known contact") ac3 = accounts[1] msg3 = ac3._evtracker.wait_next_incoming_message() assert msg3.text == "hello" ac3_contacts = ac3.get_contacts() - assert len(ac3_contacts) == 3 + assert len(ac3_contacts) == 4 ac4_contacts = ac3.get_contacts(query=accounts[2].get_config("addr")) assert len(ac4_contacts) == 1 lp.sec("ac2: removing one contact") - to_remove = contacts[-1] - - msg.chat.remove_contact(to_remove) + to_remove = ac2.create_contact(accounts[-1]) + msg2.chat.remove_contact(to_remove) lp.sec("ac1: receiving system message about contact removal") sysmsg = ac1._evtracker.wait_next_incoming_message() @@ -1673,13 +1649,13 @@ class TestGroupStressTests: assert chat.get_summary()["gossiped_timestamp"] == gossiped_timestamp lp.sec("ac1: adding fifth member to the chat") - chat.add_contact(contact5) - # Additng contact to chat resets gossiped_timestamp + chat.add_contact(ac5) + # Adding contact to chat resets gossiped_timestamp assert chat.get_summary()["gossiped_timestamp"] >= gossiped_timestamp lp.sec("ac2: receiving system message about contact addition") sysmsg = ac2._evtracker.wait_next_incoming_message() - assert contact5.addr in sysmsg.text + assert ac5.addr in sysmsg.text assert len(sysmsg.chat.get_contacts()) == 4 lp.sec("ac5: waiting for message about addition to the chat") diff --git a/python/tests/test_increation.py b/python/tests/test_increation.py index da61c1b24..d244fbd68 100644 --- a/python/tests/test_increation.py +++ b/python/tests/test_increation.py @@ -74,7 +74,7 @@ class TestOnlineInCreation: lp.sec("forward the message while still in creation") chat2 = ac1.create_group_chat("newgroup") - chat2.add_contact(ac2.get_self_contact()) + chat2.add_contact(ac2) 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