22
33[ ![ Tests] ( https://github.com/devinslick/fmd_api/actions/workflows/test.yml/badge.svg?branch=main )] ( https://github.com/devinslick/fmd_api/actions/workflows/test.yml )
44[ ![ codecov] ( https://codecov.io/gh/devinslick/fmd_api/branch/main/graph/badge.svg?token=8WA2TKXIOW )] ( https://codecov.io/gh/devinslick/fmd_api )
5+ [ ![ PyPI - Downloads] ( https://img.shields.io/pypi/dm/fmd-api )] ( https://pypi.org/project/fmd-api/ )
56
67Modern, async Python client for the open‑source FMD (Find My Device) server. It handles authentication, key management, encrypted data decryption, location/picture retrieval, and common device commands with safe, validated helpers.
78
@@ -12,11 +13,7 @@ Modern, async Python client for the open‑source FMD (Find My Device) server. I
1213 ``` bash
1314 pip install fmd_api
1415 ```
15- - Pre‑release (Test PyPI):
16- ``` bash
17- pip install --pre --index-url https://test.pypi.org/simple/ \
18- --extra-index-url https://pypi.org/simple/ fmd_api
19- ```
16+ <!-- Pre-release via TestPyPI removed. Use stable releases from PyPI or GitHub Releases for pre-release artifacts -->
2017
2118## Quickstart
2219
@@ -32,7 +29,9 @@ async def main():
3229
3330 # Fetch most recent locations and decrypt the latest
3431 blobs = await client.get_locations(num_to_get = 1 )
35- loc = json.loads(client.decrypt_data_blob(blobs[0 ]))
32+ # decrypt_data_blob() returns raw bytes — decode then parse JSON for clarity
33+ decrypted = client.decrypt_data_blob(blobs[0 ])
34+ loc = json.loads(decrypted.decode(" utf-8" ))
3635 print (loc[" lat" ], loc[" lon" ], loc.get(" accuracy" ))
3736
3837 # Take a picture (validated helper)
@@ -102,7 +101,7 @@ Tips:
102101## What’s in the box
103102
104103- ` FmdClient ` (primary API)
105- - Auth and key retrieval (salt → Argon2id → access token → private key decrypt )
104+ - Auth and key retrieval (salt → Argon2id → access token → private key retrieval and decryption )
106105 - Decrypt blobs (RSA‑OAEP wrapped AES‑GCM)
107106 - Fetch data: ` get_locations ` , ` get_pictures `
108107 - Export: ` export_data_zip(out_path) ` — client-side packaging of all locations/pictures into ZIP (mimics web UI, no server endpoint)
@@ -121,6 +120,13 @@ Tips:
121120 - ` await device.refresh() ` → hydrate cached state
122121 - ` await device.get_location() ` → parsed last location
123122 - ` await device.get_picture_blobs(n) ` + ` await device.decode_picture(blob) `
123+ - ` await device.get_picture_metadata(n) ` -> returns only metadata dicts (if the server exposes them)
124+
125+ IMPORTANT (breaking change in v2.0.5): legacy compatibility wrappers were removed.
126+ The following legacy methods were removed from the ` Device ` API: ` fetch_pictures ` ,
127+ ` get_pictures ` , ` download_photo ` , ` get_picture ` , ` take_front_photo ` , and ` take_rear_photo ` .
128+ Update your code to use ` get_picture_blobs() ` , ` decode_picture() ` , ` take_front_picture() `
129+ and ` take_rear_picture() ` instead.
124130 - Commands: ` await device.play_sound() ` , ` await device.take_front_picture() ` ,
125131 ` await device.take_rear_picture() ` , ` await device.lock(message=None) ` ,
126132 ` await device.wipe(pin="YourSecurePIN", confirm=True) `
@@ -143,6 +149,35 @@ async def main():
143149asyncio.run(main())
144150```
145151
152+ ### Example: Inspect pictures metadata (when available)
153+
154+ Use ` get_picture_blobs() ` to fetch the raw server responses (strings or dicts). If you want a
155+ strongly-typed list of picture metadata objects (where the server provides metadata as JSON
156+ objects), use ` get_picture_metadata() ` , which filters for dict entries and returns only those.
157+
158+ ``` python
159+ from fmd_api import FmdClient, Device
160+
161+ async def inspect_metadata ():
162+ client = await FmdClient.create(" https://fmd.example.com" , " alice" , " secret" )
163+ device = Device(client, " alice" )
164+
165+ # Raw values may be strings (base64 blobs) or dicts (metadata). Keep raw when you need
166+ # to decode or handle both forms yourself.
167+ raw = await device.get_picture_blobs(10 )
168+
169+ # If you want only metadata entries returned by the server, use get_picture_metadata().
170+ # This returns a list of dict-like metadata objects (e.g. id/date/filename) and filters
171+ # out any raw string blobs.
172+ metadata = await device.get_picture_metadata(10 )
173+ for m in metadata:
174+ print (m.get(" id" ), m.get(" date" ))
175+
176+ await client.close()
177+
178+ asyncio.run(inspect_metadata())
179+ ```
180+
146181## Testing
147182
148183### Functional tests
0 commit comments