icloud
This commit is contained in:
Binary file not shown.
31
icloud.py
31
icloud.py
@@ -1,13 +1,35 @@
|
||||
import asyncio
|
||||
import json
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
|
||||
from pyicloud import PyiCloudService
|
||||
from pyicloud.exceptions import PyiCloud2FARequiredException
|
||||
class JsonFormatter(logging.Formatter):
|
||||
def format(self, record: logging.LogRecord) -> str:
|
||||
payload = {
|
||||
"ts": self.formatTime(record, "%Y-%m-%dT%H:%M:%S%z"),
|
||||
"level": record.levelname,
|
||||
"logger": record.name,
|
||||
"message": record.getMessage(),
|
||||
}
|
||||
if record.exc_info:
|
||||
payload["exc_info"] = self.formatException(record.exc_info)
|
||||
return json.dumps(payload, ensure_ascii=True)
|
||||
|
||||
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(JsonFormatter())
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.handlers = [handler]
|
||||
root_logger.setLevel(logging.INFO)
|
||||
logger = logging.getLogger("ios-api")
|
||||
|
||||
class FindMyMonitor:
|
||||
def __init__(self, username, password, queue: asyncio.Queue, token_file="icloud_token.txt"):
|
||||
self.username = username
|
||||
self.password = password
|
||||
def __init__(self, queue: asyncio.Queue, token_file="icloud_token.txt"):
|
||||
self.username = os.getenv("APPLE_ID")
|
||||
self.password = os.getenv("APPLE_PW")
|
||||
self.token_file = token_file
|
||||
self.queue = queue
|
||||
self.api = None
|
||||
@@ -72,6 +94,7 @@ class FindMyMonitor:
|
||||
self.start()
|
||||
|
||||
while self.running:
|
||||
logger.info("Starting iCloud FMF loop")
|
||||
try:
|
||||
lat, lon, ts = await self.get_location()
|
||||
print(f"[{ts}] Location: {lat}, {lon}")
|
||||
|
||||
@@ -5,7 +5,9 @@ requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"fastapi==0.135.1",
|
||||
"pydantic==2.12.5",
|
||||
"pyicloud>=2.4.1",
|
||||
"pymobiledevice3==9.0.0",
|
||||
"python-dotenv>=1.2.2",
|
||||
"python-socketio==5.16.1",
|
||||
"typing==3.10.0.0",
|
||||
"uvicorn==0.41.0",
|
||||
|
||||
53
server.py
53
server.py
@@ -157,12 +157,14 @@ class LocationSimulationState:
|
||||
self.queue_data: Dict = {}
|
||||
self.queue_status: Optional[asyncio.Event] = asyncio.Event()
|
||||
self.queue_state: str = "STOPPED"
|
||||
self.test_mode: bool = False
|
||||
self.test_mode: bool = True
|
||||
self.simulation_task: Optional[asyncio.Task] = None
|
||||
self.sio: socketio.AsyncServer = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
|
||||
self.tunnel: Optional[RemoteServiceDiscoveryService] = None
|
||||
self.fmf_queue: asyncio.Queue = asyncio.Queue
|
||||
self.fmf_location: Optional[iCloudLocationData] = None
|
||||
self.icloud_monitor = FindMyMonitor(self.fmf_queue)
|
||||
self.icloud_monitor_task = None
|
||||
|
||||
|
||||
class TunneldRunnerSio:
|
||||
@@ -205,6 +207,7 @@ class TunneldRunnerSio:
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
self._tunneld_core.start()
|
||||
await start_icloud_monitor()
|
||||
yield
|
||||
logger.info("Closing tunneld tasks...")
|
||||
await empty_simulation_queue()
|
||||
@@ -384,16 +387,17 @@ class TunneldRunnerSio:
|
||||
|
||||
|
||||
|
||||
async def start_icloud_monitor()
|
||||
async def start_icloud_monitor():
|
||||
"""Start Apple iCloud Find My Monitor to retreive actual reported device location"""
|
||||
monitor = FindMyMonitor(apple_id, apple_pw, self.context.fmf_queue)
|
||||
monitor_task = asyncio.create_task(monitor.run_monitor(interval=30))
|
||||
self.context.icloud_monitor_task = asyncio.create_task(self.context.icloud_monitor.run_monitor(interval=30))
|
||||
while True:
|
||||
updated_location = await self.context.fmf_queue.get()
|
||||
if self.context.fmf_location !== updated_location:
|
||||
if self.context.fmf_location != updated_location:
|
||||
self.context.fmf_location = update_location
|
||||
self.context.sio.emit("fmf_update", updated_location, namespace="/",)
|
||||
|
||||
async def end_icloud_monitor():
|
||||
self.context.icloud_monitor.end()
|
||||
|
||||
async def pause_simulation_queue():
|
||||
"""Pauses asyncio.Queue playback"""
|
||||
@@ -444,7 +448,7 @@ class TunneldRunnerSio:
|
||||
|
||||
async def end_simulation_queue() -> bool:
|
||||
"""Ends asyncio.Queue playback and closes tunnel"""
|
||||
logger.info("End location simulation request from %s", sid)
|
||||
logger.info("End location simulation request")
|
||||
try:
|
||||
if self.context.test_mode:
|
||||
q = self.context.queue
|
||||
@@ -503,6 +507,7 @@ class TunneldRunnerSio:
|
||||
"test_mode": self.context.test_mode,
|
||||
"simulation_task": self.context.simulation_task.get_name() if self.context.simulation_task else None,
|
||||
"tunnel": self.context.tunnel.service.address[0] if self.context.tunnel else None,
|
||||
"fmf_location": self.context.fmf_location,
|
||||
}
|
||||
return data
|
||||
|
||||
@@ -636,6 +641,7 @@ class TunneldRunnerSio:
|
||||
async def app_add_location(data: SimulationRequestData) -> fastapi.Response:
|
||||
""" Add a location to the simulation queue"""
|
||||
logger.info("Request to add new location to queue")
|
||||
|
||||
loc_id = str(uuid.uuid4())
|
||||
latitude = data.get("latitude") if isinstance(data, dict) else getattr(data, "latitude", None)
|
||||
longitude = data.get("longitude") if isinstance(data, dict) else getattr(data, "longitude", None)
|
||||
@@ -644,28 +650,27 @@ class TunneldRunnerSio:
|
||||
if latitude is not None and longitude is not None:
|
||||
logger.info("Adding location %s (%s, %s) with %s delay to the queue", loc_id, latitude, longitude,
|
||||
delay)
|
||||
await self.context.queue.put((loc_id, latitude, longitude, delay))
|
||||
if delay == 0:
|
||||
start_time = datetime.now(timezone.utc).isoformat()
|
||||
else:
|
||||
now_time = datetime.now(timezone.utc)
|
||||
new_time = now_time + timedelta(seconds=delay)
|
||||
start_time = new_time.isoformat()
|
||||
accrued_delay = 0
|
||||
if self.context.queue_data:
|
||||
accrued_delay = sum(item.get('delay', 0) for item in self.context.queue_data.values())
|
||||
now_time = datetime.now(timezone.utc)
|
||||
new_time = now_time + timedelta(seconds=accrued_delay) + timedelta(seconds=delay)
|
||||
start_time = new_time.isoformat()
|
||||
location_item = {
|
||||
loc_id: {
|
||||
"loc_id": loc_id,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"delay": delay,
|
||||
"start": start_time
|
||||
}
|
||||
"loc_id": loc_id,
|
||||
"latitude": latitude,
|
||||
"longitude": longitude,
|
||||
"delay": delay,
|
||||
"start": start_time
|
||||
}
|
||||
self.context.queue_list.append(location_item)
|
||||
resp = {
|
||||
"status": "added",
|
||||
"message": f"Location {loc_id} added to the queue",
|
||||
"item": location_item
|
||||
}
|
||||
await self.context.queue.put(loc_id)
|
||||
add_item(loc_id, location_item)
|
||||
logger.info("Location %s added to the queue", loc_id)
|
||||
else:
|
||||
resp = {"status": "error", "message": "Invalid location data"}
|
||||
return generate_http_response(resp)
|
||||
@@ -1040,8 +1045,9 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
|
||||
async def play_queue(self, disable_sleep: bool = False, timing_randomness_range: int = 0) -> None:
|
||||
while True:
|
||||
while self.context.queue_state == "PAUSED":
|
||||
if self.context.queue_state == "PAUSED":
|
||||
await asyncio.sleep(0.1)
|
||||
continue
|
||||
if self.context.queue_state == "SHUTDOWN":
|
||||
break
|
||||
loc_id = await self.context.queue.get()
|
||||
@@ -1051,6 +1057,7 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
latitude = location_item.get("latitude")
|
||||
longitude = location_item.get("longitude")
|
||||
delay = location_item.get("delay")
|
||||
delay = 0 if delay is None else delay
|
||||
start_time = location_item.get("start_time")
|
||||
if self.context.set_location_enabled:
|
||||
if delay > 0 and not disable_sleep:
|
||||
@@ -1075,9 +1082,7 @@ class LocationSimulationTestQueue(LocationSimulationBase):
|
||||
await self.set(latitude, longitude)
|
||||
self.context.latitude = latitude
|
||||
self.context.longitude = longitude
|
||||
self.context.loc_id = loc_id
|
||||
await self.context.sio.emit(
|
||||
|
||||
"simulation_status",
|
||||
{
|
||||
"status": self.context.simulation_active,
|
||||
|
||||
171
uv.lock
generated
171
uv.lock
generated
@@ -95,7 +95,9 @@ source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "fastapi" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pyicloud" },
|
||||
{ name = "pymobiledevice3" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-socketio" },
|
||||
{ name = "typing" },
|
||||
{ name = "uvicorn" },
|
||||
@@ -105,7 +107,9 @@ dependencies = [
|
||||
requires-dist = [
|
||||
{ name = "fastapi", specifier = "==0.135.1" },
|
||||
{ name = "pydantic", specifier = "==2.12.5" },
|
||||
{ name = "pyicloud", specifier = ">=2.4.1" },
|
||||
{ name = "pymobiledevice3", specifier = "==9.0.0" },
|
||||
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
||||
{ name = "python-socketio", specifier = "==5.16.1" },
|
||||
{ name = "typing", specifier = "==3.10.0.0" },
|
||||
{ name = "uvicorn", specifier = "==0.41.0" },
|
||||
@@ -412,6 +416,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fido2"
|
||||
version = "2.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/3c/c65377e48c144afca6b02c69f10c0fe936db556096a4e2c9798e2aa72db6/fido2-2.1.1.tar.gz", hash = "sha256:f1379f845870cc7fc64c7f07323c3ce41e8c96c37054e79e0acd5630b3fec5ac", size = 4455940, upload-time = "2026-01-19T11:08:34.683Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/ab/d0fa89cc4b982800dd88daa799612f11642bf9393851715d9eaeba3cfcac/fido2-2.1.1-py3-none-any.whl", hash = "sha256:f85c16c8084abf6530b6c6ec3a0cf8575943321842e06916686943a8b784182c", size = 226945, upload-time = "2026-01-19T11:08:29.675Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpxpy"
|
||||
version = "1.6.2"
|
||||
@@ -541,6 +557,39 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jaraco-classes"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "more-itertools" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jaraco-context"
|
||||
version = "6.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jaraco-functools"
|
||||
version = "4.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "more-itertools" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0f/27/056e0638a86749374d6f57d0b0db39f29509cce9313cf91bdc0ac4d91084/jaraco_functools-4.4.0.tar.gz", hash = "sha256:da21933b0417b89515562656547a77b4931f98176eb173644c0d35032a33d6bb", size = 19943, upload-time = "2025-12-21T09:29:43.6Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl", hash = "sha256:9eec1e36f45c818d9bf307c8948eb03b2b56cd44087b3cdc989abca1f20b9176", size = 10481, upload-time = "2025-12-21T09:29:42.27Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.2"
|
||||
@@ -553,6 +602,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jeepney"
|
||||
version = "0.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinxed"
|
||||
version = "1.3.0"
|
||||
@@ -565,6 +623,36 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/27/e3/0e0014d6ab159d48189e92044ace13b1e1fe9aa3024ba9f4e8cf172aa7c2/jinxed-1.3.0-py2.py3-none-any.whl", hash = "sha256:b993189f39dc2d7504d802152671535b06d380b26d78070559551cbf92df4fc5", size = 33085, upload-time = "2024-07-31T22:39:17.426Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "25.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jaraco-classes" },
|
||||
{ name = "jaraco-context" },
|
||||
{ name = "jaraco-functools" },
|
||||
{ name = "jeepney", marker = "sys_platform == 'linux'" },
|
||||
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
||||
{ name = "secretstorage", marker = "sys_platform == 'linux'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyrings-alt"
|
||||
version = "5.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jaraco-classes" },
|
||||
{ name = "jaraco-context" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5c/7b/e3bf53326e0753bee11813337b1391179582ba5c6851b13e0d9502d15a50/keyrings_alt-5.0.2.tar.gz", hash = "sha256:8f097ebe9dc8b185106502b8cdb066c926d2180e13b4689fd4771a3eab7d69fb", size = 29229, upload-time = "2024-08-14T01:09:28.12Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/0d/9c59313ab43d0858a9a665e80763bd830dc78d5f379afc3815e123c486c2/keyrings.alt-5.0.2-py3-none-any.whl", hash = "sha256:6be74693192f3f37bbb752bfac9b86e6177076b17d2ac12a390f1d6abff8ac7c", size = 17930, upload-time = "2024-08-14T01:09:26.785Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "la-panic"
|
||||
version = "0.5.0"
|
||||
@@ -631,6 +719,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "more-itertools"
|
||||
version = "10.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opack2"
|
||||
version = "0.0.1"
|
||||
@@ -897,6 +994,25 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/96/17/58ee1f079114ea360601ccd331fd73032701abfe9278495e3dec0c35ef3f/pygnuutils-0.1.1-py3-none-any.whl", hash = "sha256:3b690540cc13f2c763250ee5cc647e9c81055d1002b1bcf7ac07ea6d259a21c5", size = 46351, upload-time = "2023-05-12T12:56:58.211Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyicloud"
|
||||
version = "2.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "certifi" },
|
||||
{ name = "click" },
|
||||
{ name = "fido2" },
|
||||
{ name = "keyring" },
|
||||
{ name = "keyrings-alt" },
|
||||
{ name = "requests" },
|
||||
{ name = "srp" },
|
||||
{ name = "tzlocal" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0c/f0/48aeab9d92690b6a7cf31200159c369441cdae1ac9d93d6bf69c096bf001/pyicloud-2.4.1.tar.gz", hash = "sha256:9c13bc46e08cabd87c4d4418133b5a303f30c3ef0478b6d1b13aa74c2e5334f6", size = 141404, upload-time = "2026-02-21T17:08:18.109Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/d2/875b873d54d3bc7dad37b50fd255b357f8adaa105af5bb2b02443cafc783/pyicloud-2.4.1-py3-none-any.whl", hash = "sha256:a767ada7cc2961428f8c2d0ce327102ae7666e3835610945409247fcf9d85e68", size = 66974, upload-time = "2026-02-21T17:08:16.196Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyimg4"
|
||||
version = "0.8.8"
|
||||
@@ -1009,6 +1125,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-engineio"
|
||||
version = "4.13.1"
|
||||
@@ -1071,6 +1196,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pywin32-ctypes"
|
||||
version = "0.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qh3"
|
||||
version = "1.6.0"
|
||||
@@ -1168,6 +1302,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/b6/049c75d399ccf6e25abea0652b85bf7e7e101e0300aa9c1d284ad7061c0b/runs-1.3.0-py3-none-any.whl", hash = "sha256:e71a551cfa8da9ef882cac1d5a108bda78c9edee5b8d87e37c1003da5b6a7bed", size = 6406, upload-time = "2026-02-03T15:59:59.96Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secretstorage"
|
||||
version = "3.5.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cryptography" },
|
||||
{ name = "jeepney" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "82.0.0"
|
||||
@@ -1207,6 +1354,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srp"
|
||||
version = "1.0.22"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/fb/9210875dd162d3977580407b1c5ce6e779e770b8197a0de76819144a9755/srp-1.0.22.tar.gz", hash = "sha256:f330d0ec7387e2ac8577487b164963155d4a031bca6e2024f1b0930eb92baa5d", size = 22472, upload-time = "2024-11-01T21:52:54.006Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/89/75/5352c3ebd26e7d119042ae8de07354435a19c77fa2b44058fa97a1416783/srp-1.0.22-py3-none-any.whl", hash = "sha256:35aa8af053285a35683eb37182dcb2e46dbd85c7075d28e139f200d6bf16ea43", size = 25347, upload-time = "2024-11-01T21:52:53.021Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "srptools"
|
||||
version = "1.0.1"
|
||||
@@ -1353,6 +1512,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzlocal"
|
||||
version = "5.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.6.3"
|
||||
|
||||
Reference in New Issue
Block a user