diff --git a/findmy/errors.py b/findmy/errors.py index b876ebc..2338e89 100644 --- a/findmy/errors.py +++ b/findmy/errors.py @@ -17,6 +17,14 @@ class UnhandledProtocolError(RuntimeError): """ +class EmptyResponseError(RuntimeError): + """ + Raised when Apple servers return an empty response when querying location reports. + + This is a bug on Apple's side. More info: https://github.com/malmeloo/FindMy.py/issues/185 + """ + + class InvalidStateError(RuntimeError): """ Raised when a method is used that is in conflict with the internal account state. diff --git a/findmy/reports/account.py b/findmy/reports/account.py index d9a8a8a..3555975 100644 --- a/findmy/reports/account.py +++ b/findmy/reports/account.py @@ -29,6 +29,7 @@ from findmy import util from findmy.errors import ( + EmptyResponseError, InvalidCredentialsError, InvalidStateError, UnauthorizedError, @@ -669,8 +670,14 @@ async def _do_request() -> util.http.HttpResponse: retry_counter += 1 if retry_counter > 3: - logger.warning("Max retries reached, returning empty response") - return resp + logger.warning( + "Max retries reached, returning empty response. \ + Location reports might be missing!" + ) + msg = "Empty response received from Apple servers. \ + This is most likely a bug on Apple's side. \ + More info: https://github.com/malmeloo/FindMy.py/issues/185" + raise EmptyResponseError(msg) await asyncio.sleep(2) diff --git a/findmy/reports/reports.py b/findmy/reports/reports.py index 7866f62..a616cd3 100644 --- a/findmy/reports/reports.py +++ b/findmy/reports/reports.py @@ -18,6 +18,7 @@ from findmy import util from findmy.accessory import RollingKeyPairSource +from findmy.errors import EmptyResponseError from findmy.keys import HasHashedPublicKey, KeyPair, KeyPairMapping, KeyPairType if TYPE_CHECKING: @@ -417,7 +418,7 @@ async def fetch_location_history( return reports - async def _fetch_accessory_reports( + async def _fetch_accessory_reports( # noqa: C901 self, accessory: RollingKeyPairSource, only_latest: bool = False, @@ -480,7 +481,10 @@ async def _fetch() -> set[LocationReport]: len(cur_keys_primary | new_keys_primary) > 290 or len(cur_keys_secondary | new_keys_secondary) > 290 ): - ret |= await _fetch() + try: + ret |= await _fetch() + except EmptyResponseError: + return [] # if we only want the latest report, we can stop here # since we are iterating backwards in time @@ -498,7 +502,10 @@ async def _fetch() -> set[LocationReport]: if cur_keys_primary or cur_keys_secondary: # fetch remaining keys - ret |= await _fetch() + try: + ret |= await _fetch() + except EmptyResponseError: + return [] return sorted(ret) @@ -510,7 +517,10 @@ async def _fetch_key_reports( # fetch all as primary keys ids = [([key.hashed_adv_key_b64], []) for key in keys] - encrypted_reports: list[LocationReport] = await self._account.fetch_raw_reports(ids) + try: + encrypted_reports: list[LocationReport] = await self._account.fetch_raw_reports(ids) + except EmptyResponseError: + encrypted_reports = [] id_to_key: dict[bytes, HasHashedPublicKey] = {key.hashed_adv_key_bytes: key for key in keys} reports: dict[HasHashedPublicKey, list[LocationReport]] = {key: [] for key in keys}