mirror of
https://github.com/malmeloo/FindMy.py.git
synced 2026-04-17 23:53:57 +02:00
Add option to align FindMyAccessories key generation
Sometimes the key generation diverges for example if the accessory has no power. FindMy solves this be re-aligning the key generation if a btle connection is established. FindMy.app stores there alignment records in the `KeyAlignmentRecord` directory. This PR extends the FindMyAccessory class to read those records during `from_plist`-generation and re-sync the key generation by this
This commit is contained in:
@@ -4,6 +4,7 @@ Example showing how to fetch locations of an AirTag, or any other FindMy accesso
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -19,10 +20,15 @@ ANISETTE_SERVER = "http://localhost:6969"
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
def main(plist_path: str) -> int:
|
def main(plist_path: Path, alignment_plist_path: Path | None) -> int:
|
||||||
# Step 0: create an accessory key generator
|
# Step 0: create an accessory key generator
|
||||||
with Path(plist_path).open("rb") as f:
|
with plist_path.open("rb") as f:
|
||||||
airtag = FindMyAccessory.from_plist(f)
|
f2 = alignment_plist_path.open("rb") if alignment_plist_path else None
|
||||||
|
|
||||||
|
airtag = FindMyAccessory.from_plist(f, f2)
|
||||||
|
|
||||||
|
if f2:
|
||||||
|
f2.close()
|
||||||
|
|
||||||
# Step 1: log into an Apple account
|
# Step 1: log into an Apple account
|
||||||
print("Logging into account")
|
print("Logging into account")
|
||||||
@@ -43,10 +49,9 @@ def main(plist_path: str) -> int:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if len(sys.argv) < 2:
|
parser = argparse.ArgumentParser()
|
||||||
print(f"Usage: {sys.argv[0]} <path to accessory plist>", file=sys.stderr)
|
parser.add_argument("plist_path", type=Path)
|
||||||
print(file=sys.stderr)
|
parser.add_argument("--alignment_plist_path", default=None, type=Path)
|
||||||
print("The plist file should be dumped from MacOS's FindMy app.", file=sys.stderr)
|
args = parser.parse_args()
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
sys.exit(main(sys.argv[1]))
|
sys.exit(main(args.plist_path, args.alignment_plist_path))
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ class FindMyAccessory(RollingKeyPairSource):
|
|||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
model: str | None = None,
|
model: str | None = None,
|
||||||
identifier: str | None = None,
|
identifier: str | None = None,
|
||||||
|
alignment_date: datetime | None = None,
|
||||||
|
alignment_index: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize a FindMyAccessory. These values are usually obtained during pairing.
|
Initialize a FindMyAccessory. These values are usually obtained during pairing.
|
||||||
@@ -98,6 +100,16 @@ class FindMyAccessory(RollingKeyPairSource):
|
|||||||
self._name = name
|
self._name = name
|
||||||
self._model = model
|
self._model = model
|
||||||
self._identifier = identifier
|
self._identifier = identifier
|
||||||
|
self._alignment_date = (
|
||||||
|
alignment_date if alignment_date is not None else paired_at
|
||||||
|
)
|
||||||
|
self._alignment_index = alignment_index if alignment_index is not None else 0
|
||||||
|
if self._alignment_date.tzinfo is None:
|
||||||
|
self._alignment_date = self._alignment_date.astimezone()
|
||||||
|
logging.warning(
|
||||||
|
"Alignment datetime is timezone-naive. Assuming system tz: %s.",
|
||||||
|
self._alignment_date.tzname(),
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def paired_at(self) -> datetime:
|
def paired_at(self) -> datetime:
|
||||||
@@ -140,25 +152,29 @@ class FindMyAccessory(RollingKeyPairSource):
|
|||||||
secondary_offset = 0
|
secondary_offset = 0
|
||||||
|
|
||||||
if isinstance(ind, datetime):
|
if isinstance(ind, datetime):
|
||||||
# number of 15-minute slots since pairing time
|
# number of 15-minute slots since alignment
|
||||||
ind = (
|
slots_since_alignment = (
|
||||||
int(
|
int(
|
||||||
(ind - self._paired_at).total_seconds() / (15 * 60),
|
(ind - self._alignment_date).total_seconds() / (15 * 60),
|
||||||
)
|
)
|
||||||
+ 1
|
+ 1
|
||||||
)
|
)
|
||||||
|
ind = self._alignment_index + slots_since_alignment
|
||||||
|
|
||||||
# number of slots until first 4 am
|
# number of slots until first 4 am
|
||||||
first_rollover = self._paired_at.astimezone().replace(
|
first_rollover = self._alignment_date.astimezone().replace(
|
||||||
hour=4,
|
hour=4,
|
||||||
minute=0,
|
minute=0,
|
||||||
second=0,
|
second=0,
|
||||||
microsecond=0,
|
microsecond=0,
|
||||||
)
|
)
|
||||||
if first_rollover < self._paired_at: # we rolled backwards, so increment the day
|
if (
|
||||||
|
first_rollover < self._alignment_date
|
||||||
|
): # we rolled backwards, so increment the day
|
||||||
first_rollover += timedelta(days=1)
|
first_rollover += timedelta(days=1)
|
||||||
secondary_offset = (
|
secondary_offset = (
|
||||||
int(
|
int(
|
||||||
(first_rollover - self._paired_at).total_seconds() / (15 * 60),
|
(first_rollover - self._alignment_date).total_seconds() / (15 * 60),
|
||||||
)
|
)
|
||||||
+ 1
|
+ 1
|
||||||
)
|
)
|
||||||
@@ -177,7 +193,9 @@ class FindMyAccessory(RollingKeyPairSource):
|
|||||||
return possible_keys
|
return possible_keys
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_plist(cls, plist: IO[bytes]) -> FindMyAccessory:
|
def from_plist(
|
||||||
|
cls, plist: IO[bytes], key_alignment_plist: IO[bytes] | None = None
|
||||||
|
) -> FindMyAccessory:
|
||||||
"""Create a FindMyAccessory from a .plist file dumped from the FindMy app."""
|
"""Create a FindMyAccessory from a .plist file dumped from the FindMy app."""
|
||||||
device_data = plistlib.load(plist)
|
device_data = plistlib.load(plist)
|
||||||
|
|
||||||
@@ -201,7 +219,27 @@ class FindMyAccessory(RollingKeyPairSource):
|
|||||||
model = device_data["model"]
|
model = device_data["model"]
|
||||||
identifier = device_data["identifier"]
|
identifier = device_data["identifier"]
|
||||||
|
|
||||||
return cls(master_key, skn, sks, paired_at, None, model, identifier)
|
alignment_date = None
|
||||||
|
index = None
|
||||||
|
if key_alignment_plist:
|
||||||
|
alignment_data = plistlib.load(key_alignment_plist)
|
||||||
|
|
||||||
|
alignment_date = alignment_data["lastIndexObservationDate"].replace(
|
||||||
|
tzinfo=timezone.utc,
|
||||||
|
)
|
||||||
|
index = alignment_data["lastIndexObserved"]
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
master_key,
|
||||||
|
skn,
|
||||||
|
sks,
|
||||||
|
paired_at,
|
||||||
|
None,
|
||||||
|
model,
|
||||||
|
identifier,
|
||||||
|
alignment_date,
|
||||||
|
index,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AccessoryKeyGenerator(KeyGenerator[KeyPair]):
|
class AccessoryKeyGenerator(KeyGenerator[KeyPair]):
|
||||||
|
|||||||
Reference in New Issue
Block a user