| from fastapi import FastAPI |
| from fastapi.middleware.cors import CORSMiddleware |
| import httpx |
| import json |
| import logging |
|
|
| app = FastAPI() |
| logging.basicConfig(level=logging.INFO) |
|
|
| |
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
| BASE_62_MAP = {c: i for i, c in enumerate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")} |
|
|
| async def get_client() -> httpx.AsyncClient: |
| if not hasattr(app.state, "client"): |
| app.state.client = httpx.AsyncClient(timeout=15.0) |
| return app.state.client |
|
|
| def base62_to_int(token: str) -> int: |
| result = 0 |
| for ch in token: |
| result = result * 62 + BASE_62_MAP[ch] |
| return result |
|
|
| async def get_base_url(token: str) -> str: |
| first = token[0] |
| if first == "A": |
| n = base62_to_int(token[1]) |
| else: |
| n = base62_to_int(token[1:3]) |
| return f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/" |
|
|
| ICLOUD_HEADERS = { |
| "Origin": "https://www.icloud.com", |
| "Content-Type": "text/plain" |
| } |
| ICLOUD_PAYLOAD = '{"streamCtag":null}' |
|
|
| async def get_redirected_base_url(base_url: str, token: str) -> str: |
| client = await get_client() |
| resp = await client.post( |
| f"{base_url}webstream", headers=ICLOUD_HEADERS, data=ICLOUD_PAYLOAD, follow_redirects=False |
| ) |
| if resp.status_code == 330: |
| try: |
| body = resp.json() |
| host = body.get("X-Apple-MMe-Host") |
| if not host: |
| raise ValueError("Missing X-Apple-MMe-Host in 330 response") |
| logging.info(f"Redirected to {host}") |
| return f"https://{host}/{token}/sharedstreams/" |
| except Exception as e: |
| logging.error(f"Redirect parsing failed: {e}") |
| raise |
| elif resp.status_code == 200: |
| return base_url |
| else: |
| resp.raise_for_status() |
|
|
| async def post_json(path: str, base_url: str, payload: str) -> dict: |
| client = await get_client() |
| resp = await client.post(f"{base_url}{path}", headers=ICLOUD_HEADERS, data=payload) |
| resp.raise_for_status() |
| return resp.json() |
|
|
| async def get_metadata(base_url: str) -> list: |
| data = await post_json("webstream", base_url, ICLOUD_PAYLOAD) |
| return data.get("photos", []) |
|
|
| async def get_asset_urls(base_url: str, guids: list) -> dict: |
| payload = json.dumps({"photoGuids": guids}) |
| data = await post_json("webasseturls", base_url, payload) |
| return data.get("items", {}) |
|
|
| @app.get("/album/{token}") |
| async def get_album(token: str): |
| try: |
| base_url = await get_base_url(token) |
| base_url = await get_redirected_base_url(base_url, token) |
|
|
| metadata = await get_metadata(base_url) |
| guids = [photo["photoGuid"] for photo in metadata] |
| asset_map = await get_asset_urls(base_url, guids) |
|
|
| videos = [] |
| for photo in metadata: |
| if photo.get("mediaAssetType", "").lower() != "video": |
| continue |
|
|
| derivatives = photo.get("derivatives", {}) |
| best = max( |
| (d for k, d in derivatives.items() if k.lower() != "posterframe"), |
| key=lambda d: int(d.get("fileSize") or 0), |
| default=None |
| ) |
| if not best: |
| continue |
|
|
| checksum = best.get("checksum") |
| info = asset_map.get(checksum) |
| if not info: |
| continue |
| video_url = f"https://{info['url_location']}{info['url_path']}" |
|
|
| poster = None |
| pf = derivatives.get("PosterFrame") |
| if pf: |
| pf_info = asset_map.get(pf.get("checksum")) |
| if pf_info: |
| poster = f"https://{pf_info['url_location']}{pf_info['url_path']}" |
|
|
| videos.append({ |
| "caption": photo.get("caption", ""), |
| "url": video_url, |
| "poster": poster or "" |
| }) |
|
|
| return {"videos": videos} |
|
|
| except Exception as e: |
| logging.exception("Error in get_album") |
| return {"error": str(e)} |
|
|
| @app.get("/album/{token}/raw") |
| async def get_album_raw(token: str): |
| try: |
| base_url = await get_base_url(token) |
| base_url = await get_redirected_base_url(base_url, token) |
| metadata = await get_metadata(base_url) |
| guids = [photo["photoGuid"] for photo in metadata] |
| asset_map = await get_asset_urls(base_url, guids) |
| return {"metadata": metadata, "asset_urls": asset_map} |
| except Exception as e: |
| logging.exception("Error in get_album_raw") |
| return {"error": str(e)} |
|
|