diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 9af639891..4d8ba0b83 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -492,6 +492,19 @@ class Account(object): def on_dc_event_imex_file_written(self, data1, data2): self._imex_events.put(data1) + def set_location(self, latitude=0.0, longitude=0.0, accuracy=0.0): + """set location for this chat. + + :param latitude: float (use 0.0 if not known) + :param longitude: float (use 0.0 if not known) + :param accuracy: float (use 0.0 if not known) + :raises: ValueError if no chat is currently streaming locations + :returns: None + """ + dc_res = lib.dc_set_location(self._dc_context, latitude, longitude, accuracy) + if dc_res == 0: + raise ValueError("no chat is streaming locations") + class IOThreads: def __init__(self, dc_context, log_event=lambda *args: None): diff --git a/python/src/deltachat/chatting.py b/python/src/deltachat/chatting.py index 2a5de9b12..d4dd763ac 100644 --- a/python/src/deltachat/chatting.py +++ b/python/src/deltachat/chatting.py @@ -1,6 +1,8 @@ """ chatting related objects: Contact, Chat, Message. """ import mimetypes +import calendar +from datetime import datetime import os from . import props from .cutil import as_dc_charpointer, from_dc_charpointer, iter_array @@ -365,3 +367,58 @@ class Chat(object): if dc_res == ffi.NULL: return None return from_dc_charpointer(dc_res) + + def is_sending_locations(self): + """return True if this chat has location-sending enabled currently. + :returns: True if location sending is enabled. + """ + return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id) + + def enable_sending_locations(self, seconds): + """enable sending locations for this chat. + + all subsequent messages will carry a location with them. + """ + lib.dc_send_locations_to_chat(self._dc_context, self.id, seconds) + + def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None): + """return list of locations for the given contact in the given timespan. + + :param contact: the contact for which locations shall be returned. + :param timespan_from: a datetime object or None (indicating "since beginning") + :param timespan_to: a datetime object or None (indicating up till now) + :returns: list of :class:`deltachat.chatting.Location` objects. + """ + if timestamp_from is None: + time_from = 0 + else: + time_from = calendar.timegm(timestamp_from.utctimetuple()) + if timestamp_to is None: + time_to = 0 + else: + time_to = calendar.timegm(timestamp_to.utctimetuple()) + + if contact is None: + contact_id = 0 + else: + contact_id = contact.id + + dc_array = lib.dc_get_locations(self._dc_context, self.id, contact_id, time_from, time_to) + locations = [] + for i in range(0, lib.dc_array_get_cnt(dc_array)): + locations.append(Location( + latitude=lib.dc_array_get_latitude(dc_array, i), + longitude=lib.dc_array_get_longitude(dc_array, i), + accuracy=lib.dc_array_get_accuracy(dc_array, i), + timestamp=datetime.utcfromtimestamp(lib.dc_array_get_accuracy(dc_array, i)), + )) + return locations + + +class Location: + def __init__(self, latitude, longitude, accuracy, timestamp): + assert isinstance(timestamp, datetime.datetime) + self.latitude = latitude + self.longitude = longitude + self.accuracy = accuracy + self.timestamp = timestamp diff --git a/python/tests/test_account.py b/python/tests/test_account.py index d4f146a4e..e134ab25f 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -810,6 +810,35 @@ class TestOnlineAccount: assert chat1b.get_profile_image() is None assert chat.get_profile_image() is None + def test_send_receive_locations(self, acfactory, lp): + ac1, ac2 = acfactory.get_two_online_accounts() + + lp.sec("ac1: create chat with ac2") + chat1 = self.get_chat(ac1, ac2) + chat2 = self.get_chat(ac2, ac1) + + assert not chat1.is_sending_locations() + with pytest.raises(ValueError): + ac1.set_location(latitude=0.0, longitude=10.0) + + ac1._evlogger.consume_events() + ac2._evlogger.consume_events() + lp.sec("ac1: enable location sending in chat") + chat1.enable_sending_locations(seconds=100) + assert chat1.is_sending_locations() + ac1.set_location(latitude=1.0, longitude=2.0) + ac1._evlogger.get_matching("DC_EVENT_LOCATION_CHANGED") + chat1.send_text("hello") + ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT") + ac1._evlogger.get_matching("DC_EVENT_SMTP_MESSAGE_SENT") + + lp.sec("ac2: wait for incoming location message") + ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG") + ac2._evlogger.get_matching("DC_EVENT_INCOMING_MSG") + # contact = ac2.create_contact(ac1.get_config("addr")) + locations = chat2.get_locations() # XXX per contact + assert len(locations) >= 1 + class TestOnlineConfigureFails: def test_invalid_password(self, acfactory):