mirror of
https://github.com/malmeloo/FindMy.py.git
synced 2026-04-17 21:53:57 +02:00
106 lines
3.5 KiB
Python
106 lines
3.5 KiB
Python
"""usage: python -m findmy""" # noqa: D400, D415
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
from importlib.metadata import version
|
|
from pathlib import Path
|
|
|
|
from .plist import list_accessories
|
|
|
|
|
|
def main() -> None: # noqa: D103
|
|
parser = argparse.ArgumentParser(prog="findmy", description="FindMy.py CLI tool")
|
|
parser.add_argument(
|
|
"-v",
|
|
"--version",
|
|
action="version",
|
|
version=version("FindMy"),
|
|
)
|
|
parser.add_argument(
|
|
"--log-level",
|
|
type=str,
|
|
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
default="INFO",
|
|
help="Set the logging level (default: INFO)",
|
|
)
|
|
subparsers = parser.add_subparsers(dest="command", title="commands")
|
|
subparsers.required = True
|
|
|
|
decrypt_parser = subparsers.add_parser(
|
|
"decrypt",
|
|
help="""
|
|
Decrypt and print (in json) all the local FindMy accessories.
|
|
|
|
This looks through the local FindMy accessory plist files,
|
|
decrypts them using the system keychain, and prints the
|
|
decrypted JSON representation of each accessory.
|
|
|
|
eg
|
|
```
|
|
[
|
|
{
|
|
"master_key": "e01ae426431867e92d512ae1cb6c9e5bbc20a2b7d1c677d7",
|
|
"skn": "e01ae426431867e92d512ae1cb6c9e5bbc20a2b7d1c677d7",
|
|
"sks": "e01ae426431867e92d512ae1cb6c9e5bbc20a2b7d1c677d7",
|
|
"paired_at": "2020-01-08T21:26:36.177409+00:00",
|
|
"name": "Nick's MacBook Pro",
|
|
"model": "MacBookPro11,5",
|
|
"identifier": "03FF9E28-2508-425B-BD57-D738F2D2F6C0"
|
|
},
|
|
{
|
|
"master_key": "e01ae426431867e92d512ae1cb6c9e5bbc20a2b7d1c677d7",
|
|
"skn": "e01ae426431867e92d512ae1cb6c9e5bbc20a2b7d1c677d7",
|
|
"sks": "e01ae426431867e92d512ae1cb6c9e5bbc20a2b7d1c677d7",
|
|
"paired_at": "2023-10-22T20:40:39.285225+00:00",
|
|
"name": "ncmbp",
|
|
"model": "MacBookPro18,2",
|
|
"identifier": "71D276DF-A8FA-47C8-A93C-9B3B714BDFEC"
|
|
}
|
|
]
|
|
```
|
|
|
|
You can chain the output with jq or similar tools.
|
|
eg `python -m findmy decrypt | jq '.[] | select(.name == "my airtag")' > my_airtag.json`
|
|
""",
|
|
)
|
|
decrypt_parser.add_argument(
|
|
"--out-dir",
|
|
type=Path,
|
|
default=None,
|
|
help="Output directory for decrypted files. If not specified, files will not be saved to disk.", # noqa: E501
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
logging.basicConfig(level=args.log_level.upper())
|
|
if args.command == "decrypt":
|
|
decrypt_all(args.out_dir)
|
|
else:
|
|
# This else block should ideally not be reached if subparsers.required is True
|
|
# and a default command isn't set, or if a command is always given.
|
|
# However, it's good practice for unexpected cases or if the logic changes.
|
|
parser.print_help()
|
|
parser.exit(1)
|
|
|
|
|
|
def decrypt_all(out_dir: str | Path | None = None) -> None:
|
|
"""Decrypt all accessories and save them to the specified directory as JSON files."""
|
|
|
|
def get_path(d, acc) -> Path | None: # noqa: ANN001
|
|
if out_dir is None:
|
|
return None
|
|
d = Path(d)
|
|
d = d.resolve().absolute()
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
return d / f"{acc.identifier}.json"
|
|
|
|
accs = list_accessories()
|
|
jsons = [acc.to_json(get_path(out_dir, acc)) for acc in accs]
|
|
print(json.dumps(jsons, indent=4, ensure_ascii=False)) # noqa: T201
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|