""" Example showing how to fetch locations of an AirTag, or any other FindMy accessory. """ from __future__ import annotations import json import logging import sys from pathlib import Path from _login import get_account_sync from findmy import FindMyAccessory, keys from findmy.reports import RemoteAnisetteProvider from datetime import datetime, timedelta # URL to (public or local) anisette server ANISETTE_SERVER = "http://localhost:6969" LASTINDEX_DB = 'lastindex.json' logging.basicConfig(level=logging.INFO) def main(plist_path: str) -> int: # Step 0: create an accessory key generator with Path(plist_path).open("rb") as f: airtag = FindMyAccessory.from_plist(f) # Step 1: log into an Apple account print("Logging into account") anisette = RemoteAnisetteProvider(ANISETTE_SERVER) acc = get_account_sync(anisette) kdict = {} # Scan the last 200 days worth of indexes. May need to adjust this until reports are found days = 200 firstindex = 1 lastindex = int(days * 24 * 60 / 15) # if we have a lastindex from a previous run, use that instead try: with open(LASTINDEX_DB, 'r') as f: data = json.load(f) if airtag.identifier in data: lastindex = int(data[airtag.identifier]['lastindex']) print(f"Last index for identifier {airtag.identifier} is {lastindex}") # We know the last successful index from previous runs, # so start from here. firstindex = lastindex-1 # We know the timestamp of last successful report, so we can # get reports for the index between now and then. Assuming # separated tags, one extra index per day last_timestamp = datetime.fromisoformat(str(data[airtag.identifier]['timestamp'])) current_timestamp = datetime.now(last_timestamp.tzinfo) days_diff = (current_timestamp - last_timestamp).days # Adjust lastindex based on the number of days since the last report # and add one extra to compensate for clock drift. lastindex += days_diff + 1 logging.info(f"Last discovered report was {last_timestamp}, {days_diff} days ago.") logging.info(f"Scanning {firstindex} to {lastindex}") except FileNotFoundError: logging.info(f"No previous lastindex found. Doing full range index scan {firstindex} to {lastindex}.") with open(LASTINDEX_DB, 'w') as f: json.dump({}, f) # initialize file except Exception as e: logging.error(f"An error occurred: {e}") logging.info(f"Scanning {firstindex} to {lastindex}") #for k in airtag.keys_between(firstindex, lastindex): for i in range (firstindex, lastindex): for k in airtag.keys_at(i): # Only use secondary keys that rotate daily to reduce lookups # Note if tag is near owner device, primary key is used so # secondary key may not be discovered. if k.key_type == keys.KeyType.SECONDARY: kdict[k] = i # step 2: fetch reports! print("Fetching reports") reports = acc.fetch_last_reports(list(kdict.keys()), hours=7 * 24) # step 3: print 'em lastreportts = None lastkey = None lastindex = None print() print("Location reports:") for k, r in reports.items(): print(f"Index {kdict[k]} Key {k}") for report in sorted(r): print(f" - {report}") if lastreportts is None or report.timestamp > lastreportts: lastreportts = report.timestamp lastkey = k lastindex = kdict[k] print(f"Last report was {lastreportts} with key {lastkey} at index {lastindex}") # Update json database with last index and timestamp if lastindex is not None: try: with open(LASTINDEX_DB, 'r') as f: data = json.load(f) data[airtag.identifier] = { 'lastindex': lastindex, 'timestamp': lastreportts.isoformat() if lastreportts else None } print(f"Updating json database with last index and timestamp for identifier {airtag.identifier}") with open(LASTINDEX_DB, 'w') as f: json.dump(data, f) except FileNotFoundError: data = {} return 0 if __name__ == "__main__": if len(sys.argv) < 2: print(f"Usage: {sys.argv[0]} ", file=sys.stderr) print(file=sys.stderr) print("The plist file should be dumped from MacOS's FindMy app.", file=sys.stderr) sys.exit(1) sys.exit(main(sys.argv[1]))