fibioslocation 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: fibioslocation
3
+ Version: 1.0.0
4
+ Summary: Fetch iCloud Find My device locations and push to Fibaro HC3
5
+ License-Expression: MIT
6
+ Keywords: fibaro,icloud,location,hc3,home-automation
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Topic :: Home Automation
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pyicloud
13
+ Requires-Dist: rich
14
+ Requires-Dist: requests
15
+
16
+ # fibioslocation
17
+
18
+ Fetch iCloud Find My device locations (your own + family) and push them to a [Fibaro HC3](https://www.fibaro.com/) home automation hub.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install fibioslocation
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ fibioslocation
30
+ fibioslocation --email you@icloud.com
31
+ fibioslocation --interval 60 # poll every 60 s instead of 3 min
32
+ fibioslocation --once # single shot, then exit
33
+ fibioslocation --no-hc3 # display only, don't push to HC3
34
+ fibioslocation --debug # diagnose 2FA options
35
+ ```
36
+
37
+ ## Credentials
38
+
39
+ Create `~/.env` with:
40
+
41
+ ```
42
+ HC3_HOST=192.168.1.10
43
+ HC3_USER=admin
44
+ HC3_PASSWORD=secret
45
+ ```
46
+
47
+ ## Requirements
48
+
49
+ - Python ≥ 3.10
50
+ - `pyicloud`, `rich`, `requests`
@@ -0,0 +1,35 @@
1
+ # fibioslocation
2
+
3
+ Fetch iCloud Find My device locations (your own + family) and push them to a [Fibaro HC3](https://www.fibaro.com/) home automation hub.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install fibioslocation
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ fibioslocation
15
+ fibioslocation --email you@icloud.com
16
+ fibioslocation --interval 60 # poll every 60 s instead of 3 min
17
+ fibioslocation --once # single shot, then exit
18
+ fibioslocation --no-hc3 # display only, don't push to HC3
19
+ fibioslocation --debug # diagnose 2FA options
20
+ ```
21
+
22
+ ## Credentials
23
+
24
+ Create `~/.env` with:
25
+
26
+ ```
27
+ HC3_HOST=192.168.1.10
28
+ HC3_USER=admin
29
+ HC3_PASSWORD=secret
30
+ ```
31
+
32
+ ## Requirements
33
+
34
+ - Python ≥ 3.10
35
+ - `pyicloud`, `rich`, `requests`
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: fibioslocation
3
+ Version: 1.0.0
4
+ Summary: Fetch iCloud Find My device locations and push to Fibaro HC3
5
+ License-Expression: MIT
6
+ Keywords: fibaro,icloud,location,hc3,home-automation
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Topic :: Home Automation
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pyicloud
13
+ Requires-Dist: rich
14
+ Requires-Dist: requests
15
+
16
+ # fibioslocation
17
+
18
+ Fetch iCloud Find My device locations (your own + family) and push them to a [Fibaro HC3](https://www.fibaro.com/) home automation hub.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install fibioslocation
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ fibioslocation
30
+ fibioslocation --email you@icloud.com
31
+ fibioslocation --interval 60 # poll every 60 s instead of 3 min
32
+ fibioslocation --once # single shot, then exit
33
+ fibioslocation --no-hc3 # display only, don't push to HC3
34
+ fibioslocation --debug # diagnose 2FA options
35
+ ```
36
+
37
+ ## Credentials
38
+
39
+ Create `~/.env` with:
40
+
41
+ ```
42
+ HC3_HOST=192.168.1.10
43
+ HC3_USER=admin
44
+ HC3_PASSWORD=secret
45
+ ```
46
+
47
+ ## Requirements
48
+
49
+ - Python ≥ 3.10
50
+ - `pyicloud`, `rich`, `requests`
@@ -0,0 +1,9 @@
1
+ README.md
2
+ fibioslocation.py
3
+ pyproject.toml
4
+ fibioslocation.egg-info/PKG-INFO
5
+ fibioslocation.egg-info/SOURCES.txt
6
+ fibioslocation.egg-info/dependency_links.txt
7
+ fibioslocation.egg-info/entry_points.txt
8
+ fibioslocation.egg-info/requires.txt
9
+ fibioslocation.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fibioslocation = fibioslocation:main
@@ -0,0 +1,3 @@
1
+ pyicloud
2
+ rich
3
+ requests
@@ -0,0 +1 @@
1
+ fibioslocation
@@ -0,0 +1,424 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ fibioslocation.py - Fetch iCloud Find My device locations and push to Fibaro HC3.
4
+
5
+ Every 3 minutes (configurable) it:
6
+ 1. Fetches all device locations from iCloud (own + family)
7
+ 2. Pushes a JSON payload to the HC3 via:
8
+ GET http://<hc3>/api/callAction?deviceID=<id>&name=iosLocation&arg1=<json>
9
+
10
+ Requirements:
11
+ pip install pyicloud rich
12
+
13
+ Usage:
14
+ python fibioslocation.py
15
+ python fibioslocation.py --email you@icloud.com
16
+ python fibioslocation.py --interval 60 # poll every 60s instead of 180s
17
+ python fibioslocation.py --no-hc3 # display only, don't push
18
+ python fibioslocation.py --debug # print raw 2FA options from Apple
19
+
20
+ Credentials are read from ~/.env (HC3_HOST, HC3_USER, HC3_PASSWORD).
21
+ """
22
+
23
+ import argparse
24
+ import getpass
25
+ import json
26
+ import os
27
+ import re
28
+ import sys
29
+ import time
30
+ from datetime import datetime
31
+
32
+ import requests as _requests
33
+
34
+ try:
35
+ from pyicloud import PyiCloudService
36
+ from pyicloud.exceptions import PyiCloudFailedLoginException, PyiCloudAPIResponseException
37
+ except ImportError:
38
+ print("ERROR: pyicloud not installed. Run: pip install pyicloud rich")
39
+ sys.exit(1)
40
+
41
+ try:
42
+ from rich.console import Console
43
+ from rich.table import Table
44
+ from rich import box
45
+ except ImportError:
46
+ print("ERROR: rich not installed. Run: pip install pyicloud rich")
47
+ sys.exit(1)
48
+
49
+ console = Console()
50
+
51
+
52
+ # ── .env loader ──────────────────────────────────────────────────────────────
53
+
54
+ def load_env(path: str = "~/.env") -> dict[str, str]:
55
+ """Parse a simple KEY=value .env file, ignoring comments and blank lines."""
56
+ result: dict[str, str] = {}
57
+ try:
58
+ with open(os.path.expanduser(path)) as f:
59
+ for line in f:
60
+ line = line.strip()
61
+ if not line or line.startswith("#"):
62
+ continue
63
+ m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*"?([^"]*)"?$', line)
64
+ if m:
65
+ result[m.group(1)] = m.group(2)
66
+ except FileNotFoundError:
67
+ pass
68
+ return result
69
+
70
+
71
+ ENV = load_env()
72
+
73
+ # HC3 defaults (overridable via CLI args)
74
+ HC3_HOST = ENV.get("HC3_HOST", "hc3")
75
+ HC3_USER = ENV.get("HC3_USER", "admin")
76
+ HC3_PASS = ENV.get("HC3_PASSWORD", "")
77
+ HC3_DEVICE = 4200
78
+
79
+
80
+ # ── helpers ──────────────────────────────────────────────────────────────────
81
+
82
+ def format_time(timestamp_ms: int | None) -> str:
83
+ """Convert iCloud ms-epoch timestamp to readable local time."""
84
+ if not timestamp_ms:
85
+ return "?"
86
+ dt = datetime.fromtimestamp(timestamp_ms / 1000)
87
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
88
+
89
+
90
+ def battery_bar(level: float | None) -> str:
91
+ """Return a small visual bar for battery level (0.0-1.0 or 0-100)."""
92
+ if level is None:
93
+ return "?"
94
+ pct = int(level * 100) if level <= 1.0 else int(level)
95
+ filled = pct // 10
96
+ bar = "█" * filled + "░" * (10 - filled)
97
+ colour = "green" if pct > 40 else ("yellow" if pct > 15 else "red")
98
+ return f"[{colour}]{bar}[/{colour}] {pct}%"
99
+
100
+
101
+ def get_map_link(lat: float, lon: float) -> str:
102
+ """Return an Apple Maps URL for the given coordinates."""
103
+ return f"https://maps.apple.com/?q={lat},{lon}"
104
+
105
+
106
+ # ── iCloud login ──────────────────────────────────────────────────────────────
107
+
108
+ def _request_sms_code(api: PyiCloudService) -> bool:
109
+ """
110
+ Explicitly ask Apple to send a 2FA code via SMS to the user's trusted phone.
111
+ Uses Apple's internal auth endpoint (reverse-engineered, same as pyicloud uses).
112
+ """
113
+ auth_data = api._auth_data
114
+ pnv = auth_data.get("phoneNumberVerification") or auth_data
115
+ phone = pnv.get("trustedPhoneNumber") or (
116
+ (pnv.get("trustedPhoneNumbers") or [None])[0]
117
+ )
118
+ if not phone:
119
+ return False
120
+ phone_id = phone.get("id")
121
+ non_fteu = phone.get("nonFTEU", False)
122
+ headers = api._get_auth_headers({"Accept": "application/json"})
123
+ try:
124
+ api.session.put(
125
+ f"{api._auth_endpoint}/verify/phone",
126
+ json={"phoneNumber": {"id": phone_id, "nonFTEU": non_fteu}, "mode": "sms"},
127
+ headers=headers,
128
+ )
129
+ # Patch auth_data so validate_2fa_code uses SMS path
130
+ api._auth_data["mode"] = "sms"
131
+ api._auth_data["trustedPhoneNumber"] = phone
132
+ return True
133
+ except Exception as exc:
134
+ console.print(f"[dim]SMS request failed: {exc}[/dim]")
135
+ return False
136
+
137
+
138
+ def login(email: str, password: str, debug: bool = False) -> PyiCloudService:
139
+ console.print(f"\n[bold cyan]Connecting to iCloud as[/bold cyan] [yellow]{email}[/yellow] …")
140
+ try:
141
+ api = PyiCloudService(apple_id=email, password=password, with_family=True)
142
+ except PyiCloudFailedLoginException as exc:
143
+ console.print(f"[bold red]Login failed:[/bold red] {exc}")
144
+ sys.exit(1)
145
+
146
+ if api.requires_2fa:
147
+ console.print("[bold yellow]Two-factor authentication required (HSA2).[/bold yellow]")
148
+
149
+ # _auth_data may be empty if pyicloud used a cached session token and
150
+ # skipped SRP authentication. Fetch fresh auth options from Apple now.
151
+ if not api._auth_data:
152
+ try:
153
+ api._auth_data = api._get_mfa_auth_options()
154
+ except Exception:
155
+ pass
156
+
157
+ auth_data = api._auth_data
158
+
159
+ if debug:
160
+ import json
161
+ console.print("[dim]Raw auth options from Apple:[/dim]")
162
+ console.print(json.dumps(auth_data, indent=2, default=str))
163
+
164
+ # Apple nests the useful fields under "phoneNumberVerification" in newer API responses
165
+ pnv = auth_data.get("phoneNumberVerification") or auth_data
166
+ mode = pnv.get("mode", auth_data.get("mode", "trusteddevice"))
167
+ phones = pnv.get("trustedPhoneNumbers") or (
168
+ [pnv["trustedPhoneNumber"]] if pnv.get("trustedPhoneNumber") else []
169
+ )
170
+
171
+ if debug:
172
+ console.print(f"[dim]mode={mode!r} trusted phones found: {len(phones)}[/dim]\n")
173
+
174
+ if mode == "sms":
175
+ # Apple already decided to send SMS
176
+ phone_display = phones[0].get("numberWithDialCode", "your phone") if phones else "your phone"
177
+ console.print(f"[green]A 6-digit code has been sent via SMS to {phone_display}.[/green]")
178
+ else:
179
+ # Default: trusted device push
180
+ console.print("[dim]Apple should pop up a 6-digit code on your trusted iPhone/Mac.[/dim]")
181
+ if phones:
182
+ phone_display = phones[0].get("numberWithDialCode", f'phone id={phones[0].get("id")}')
183
+ console.print(f"[dim]SMS fallback available: {phone_display}[/dim]\n")
184
+ choice = console.input(
185
+ "[bold]Press [green]Enter[/green] to wait for device push, "
186
+ "or type [yellow]sms[/yellow] to receive an SMS code instead: [/bold]"
187
+ ).strip().lower()
188
+ if choice == "sms":
189
+ console.print("Requesting SMS code …")
190
+ if _request_sms_code(api):
191
+ console.print(f"[green]SMS sent to {phone_display}.[/green]")
192
+ else:
193
+ console.print("[yellow]SMS request failed — waiting for device push code.[/yellow]")
194
+ else:
195
+ console.print("[dim]No trusted phone numbers found — only device push is available.[/dim]")
196
+ console.print("[yellow]If no popup appears, make sure your Apple ID has a trusted phone number registered at appleid.apple.com[/yellow]\n")
197
+
198
+ code = console.input("Enter 6-digit code: ").strip()
199
+ result = api.validate_2fa_code(code)
200
+ if not result:
201
+ console.print("[bold red]Invalid 2FA code.[/bold red]")
202
+ sys.exit(1)
203
+ if not api.is_trusted_session:
204
+ console.print("Trusting this session …")
205
+ api.trust_session()
206
+
207
+ elif api.requires_2sa:
208
+ console.print("[bold yellow]Two-step verification required.[/bold yellow]")
209
+ devices = api.trusted_devices
210
+ for i, device in enumerate(devices):
211
+ name = device.get("deviceName") or f"SMS to {device.get('phoneNumber', '?')}"
212
+ console.print(f" [{i}] {name}")
213
+ idx = int(console.input("Choose device index: "))
214
+ device = devices[idx]
215
+ if not api.send_verification_code(device):
216
+ console.print("[bold red]Failed to send verification code.[/bold red]")
217
+ sys.exit(1)
218
+ code = console.input("Enter the verification code: ").strip()
219
+ if not api.validate_verification_code(device, code):
220
+ console.print("[bold red]Invalid verification code.[/bold red]")
221
+ sys.exit(1)
222
+
223
+ console.print("[bold green]✓ Logged in successfully.[/bold green]\n")
224
+ return api
225
+
226
+
227
+ # ── HC3 push ──────────────────────────────────────────────────────────────────
228
+
229
+ def push_to_hc3(payload: list[dict], host: str, user: str, password: str,
230
+ device_id: int = HC3_DEVICE) -> bool:
231
+ """
232
+ Push device locations to HC3 via:
233
+ POST http://<host>/api/devices/<id>/action/iosLocation
234
+ {"args": [<json data>]}
235
+ Returns True on success.
236
+ """
237
+ url = f"http://{host}/api/devices/{device_id}/action/iosLocation"
238
+ try:
239
+ resp = _requests.post(
240
+ url,
241
+ auth=(user, password),
242
+ json={"args": [payload]},
243
+ timeout=10,
244
+ )
245
+ resp.raise_for_status()
246
+ return True
247
+ except Exception as exc:
248
+ console.print(f"[bold red]HC3 push failed:[/bold red] {exc}")
249
+ return False
250
+
251
+
252
+ # ── fetch + display ───────────────────────────────────────────────────────────
253
+
254
+ def fetch_device_data(api: PyiCloudService) -> list[dict]:
255
+ """Fetch all devices and return a list of location dicts."""
256
+ try:
257
+ devices = api.devices
258
+ devices.refresh(locate=True)
259
+ except Exception as exc:
260
+ console.print(f"[bold red]Error fetching devices:[/bold red] {exc}")
261
+ return []
262
+
263
+ result = []
264
+ for device in devices:
265
+ raw_loc = device.location or {}
266
+ raw_data = device.data
267
+ battery = raw_data.get("batteryLevel") or (
268
+ (raw_data.get("location") or {}).get("batteryLevel")
269
+ )
270
+ lat = raw_loc.get("latitude")
271
+ lon = raw_loc.get("longitude")
272
+ ts = raw_loc.get("timeStamp") or raw_loc.get("timestamp")
273
+ result.append({
274
+ "name": device.name or "Unknown",
275
+ "model": device.model_name or device.model or "?",
276
+ "lat": lat,
277
+ "lon": lon,
278
+ "accuracy": raw_loc.get("horizontalAccuracy"),
279
+ "battery": round(battery, 3) if battery is not None else None,
280
+ "timestamp": ts,
281
+ "map": get_map_link(lat, lon) if lat and lon else None,
282
+ })
283
+ return result
284
+
285
+
286
+ def show_devices(data: list[dict]) -> None:
287
+ """Render a Rich table from a list of device dicts."""
288
+ if not data:
289
+ console.print("[yellow]No devices found.[/yellow]")
290
+ return
291
+
292
+ table = Table(
293
+ title=f"[bold]Find My – All Devices[/bold] [dim]{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}[/dim]",
294
+ box=box.ROUNDED,
295
+ show_header=True,
296
+ header_style="bold magenta",
297
+ )
298
+ table.add_column("#", justify="right", style="dim", no_wrap=True)
299
+ table.add_column("Device", style="bold white", no_wrap=True)
300
+ table.add_column("Model", style="dim", no_wrap=True)
301
+ table.add_column("Latitude", justify="right", style="cyan")
302
+ table.add_column("Longitude", justify="right", style="cyan")
303
+ table.add_column("Accuracy (m)", justify="right")
304
+ table.add_column("Battery", justify="center")
305
+ table.add_column("Last Seen", style="dim")
306
+ table.add_column("Map", style="blue")
307
+
308
+ for idx, d in enumerate(data):
309
+ lat, lon = d["lat"], d["lon"]
310
+ table.add_row(
311
+ str(idx + 1),
312
+ d["name"],
313
+ d["model"],
314
+ f"{lat:.6f}" if lat is not None else "–",
315
+ f"{lon:.6f}" if lon is not None else "–",
316
+ f"{d['accuracy']:.0f}" if d["accuracy"] is not None else "–",
317
+ battery_bar(d["battery"]),
318
+ format_time(d["timestamp"]),
319
+ d["map"] or "–",
320
+ )
321
+
322
+ console.print(table)
323
+ console.print(
324
+ f"[dim]Total: {len(data)} device(s) — own + family members sharing location.[/dim]\n"
325
+ )
326
+
327
+
328
+ # ── CLI ───────────────────────────────────────────────────────────────────────
329
+
330
+ DEFAULT_INTERVAL = 180 # 3 minutes
331
+
332
+
333
+ def main() -> None:
334
+ parser = argparse.ArgumentParser(
335
+ prog="fibioslocation.py",
336
+ description=(
337
+ "Fetch iCloud Find My device locations (your own + family members)\n"
338
+ "and push them to a Fibaro HC3 home controller every N seconds.\n"
339
+ ),
340
+ epilog=(
341
+ "Credentials / defaults are read from ~/.env:\n"
342
+ " HC3_HOST – HC3 hostname or IP (e.g. hc3 or 192.168.1.10)\n"
343
+ " HC3_USER – HC3 login user (default: admin)\n"
344
+ " HC3_PASSWORD – HC3 login password\n"
345
+ "\n"
346
+ "HC3 API call made on each poll:\n"
347
+ " POST http://<hc3-host>/api/devices/<hc3-device>/action/iosLocation\n"
348
+ " Body: {\"args\": [[ {name, model, lat, lon, accuracy, battery,\n"
349
+ " timestamp, map}, ... ]]}\n"
350
+ "\n"
351
+ "Examples:\n"
352
+ " python fibioslocation.py # poll every 3 min\n"
353
+ " python fibioslocation.py --once # single shot\n"
354
+ " python fibioslocation.py --no-hc3 # display only\n"
355
+ " python fibioslocation.py -i 60 # poll every 60s\n"
356
+ " python fibioslocation.py --hc3-host 192.168.1.10 --hc3-device 4200\n"
357
+ " python fibioslocation.py --debug # diagnose 2FA\n"
358
+ ),
359
+ formatter_class=argparse.RawDescriptionHelpFormatter,
360
+ )
361
+
362
+ # iCloud
363
+ icloud = parser.add_argument_group("iCloud options")
364
+ icloud.add_argument("--email", "-e", metavar="EMAIL",
365
+ help="Apple ID e-mail (prompted if omitted)")
366
+ icloud.add_argument("--debug", "-d", action="store_true",
367
+ help="Print raw 2FA payload from Apple (helps diagnose login issues)")
368
+
369
+ # polling
370
+ poll = parser.add_argument_group("polling options")
371
+ poll.add_argument("--interval", "-i", type=int, default=DEFAULT_INTERVAL,
372
+ metavar="SECONDS",
373
+ help=f"Poll interval in seconds (default: {DEFAULT_INTERVAL})")
374
+ poll.add_argument("--once", action="store_true",
375
+ help="Fetch once and exit instead of looping")
376
+
377
+ # HC3
378
+ hc3 = parser.add_argument_group("HC3 options")
379
+ hc3.add_argument("--hc3-host", default=HC3_HOST, metavar="HOST",
380
+ help=f"HC3 hostname or IP (default from ~/.env: {HC3_HOST})")
381
+ hc3.add_argument("--hc3-user", default=HC3_USER, metavar="USER",
382
+ help=f"HC3 username (default from ~/.env: {HC3_USER})")
383
+ hc3.add_argument("--hc3-password", default=HC3_PASS, metavar="PASS",
384
+ help="HC3 password (default from ~/.env)")
385
+ hc3.add_argument("--hc3-device", type=int, default=HC3_DEVICE, metavar="ID",
386
+ help=f"HC3 QuickApp device ID (default: {HC3_DEVICE})")
387
+ hc3.add_argument("--no-hc3", action="store_true",
388
+ help="Display locations in terminal only, do not push to HC3")
389
+
390
+ args = parser.parse_args()
391
+
392
+ email = args.email or console.input("[bold]Apple ID (email):[/bold] ").strip()
393
+ password = getpass.getpass("Password: ")
394
+
395
+ api = login(email, password, debug=args.debug)
396
+
397
+ def cycle() -> None:
398
+ data = fetch_device_data(api)
399
+ show_devices(data)
400
+ if not args.no_hc3:
401
+ ok = push_to_hc3(data, host=args.hc3_host, user=args.hc3_user,
402
+ password=args.hc3_password, device_id=args.hc3_device)
403
+ if ok:
404
+ console.print(
405
+ f"[green]✓ Pushed {len(data)} device(s) to HC3 "
406
+ f"(device {args.hc3_device} @ {args.hc3_host})[/green] "
407
+ f"[dim]{datetime.now().strftime('%H:%M:%S')}[/dim]\n"
408
+ )
409
+
410
+ if args.once:
411
+ cycle()
412
+ else:
413
+ console.print(f"[dim]Polling every {args.interval}s — press Ctrl-C to stop.[/dim]\n")
414
+ try:
415
+ while True:
416
+ console.clear()
417
+ cycle()
418
+ time.sleep(args.interval)
419
+ except KeyboardInterrupt:
420
+ console.print("\n[dim]Stopped.[/dim]")
421
+
422
+
423
+ if __name__ == "__main__":
424
+ main()
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fibioslocation"
7
+ version = "1.0.0"
8
+ description = "Fetch iCloud Find My device locations and push to Fibaro HC3"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ keywords = ["fibaro", "icloud", "location", "hc3", "home-automation"]
13
+ classifiers = [
14
+ "Programming Language :: Python :: 3",
15
+ "Operating System :: OS Independent",
16
+ "Topic :: Home Automation",
17
+ ]
18
+ dependencies = [
19
+ "pyicloud",
20
+ "rich",
21
+ "requests",
22
+ ]
23
+
24
+ [project.scripts]
25
+ fibioslocation = "fibioslocation:main"
26
+
27
+ [tool.setuptools]
28
+ py-modules = ["fibioslocation"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+