SalexAI commited on
Commit
47ed581
·
verified ·
1 Parent(s): 9e819cc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -31
app.py CHANGED
@@ -7,28 +7,21 @@ import logging
7
  app = FastAPI()
8
  logging.basicConfig(level=logging.INFO)
9
 
10
- # Allow all CORS so browser apps can call this API
11
- def configure_cors(app: FastAPI):
12
- app.add_middleware(
13
- CORSMiddleware,
14
- allow_origins=["*"],
15
- allow_methods=["*"],
16
- allow_headers=["*"],
17
- )
18
-
19
- configure_cors(app)
20
 
21
- # Precompute Base62 index mapping for O(1) lookup
22
- BASE_62_MAP = {c: i for i, c in enumerate(
23
- "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
24
- )}
25
 
26
  async def get_client() -> httpx.AsyncClient:
27
  if not hasattr(app.state, "client"):
28
- app.state.client = httpx.AsyncClient(timeout=10.0)
29
  return app.state.client
30
 
31
- # Convert base62 string to integer
32
  def base62_to_int(token: str) -> int:
33
  result = 0
34
  for ch in token:
@@ -43,8 +36,10 @@ async def get_base_url(token: str) -> str:
43
  n = base62_to_int(token[1:3])
44
  return f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
45
 
46
- # Common headers and payload for iCloud requests
47
- ICLOUD_HEADERS = {"Origin": "https://www.icloud.com", "Content-Type": "text/plain"}
 
 
48
  ICLOUD_PAYLOAD = '{"streamCtag":null}'
49
 
50
  async def get_redirected_base_url(base_url: str, token: str) -> str:
@@ -53,16 +48,24 @@ async def get_redirected_base_url(base_url: str, token: str) -> str:
53
  f"{base_url}webstream", headers=ICLOUD_HEADERS, data=ICLOUD_PAYLOAD, follow_redirects=False
54
  )
55
  if resp.status_code == 330:
56
- body = resp.json()
57
- host = body.get("X-Apple-MMe-Host")
58
- return f"https://{host}/{token}/sharedstreams/"
59
- return base_url
 
 
 
 
 
 
 
 
 
 
60
 
61
  async def post_json(path: str, base_url: str, payload: str) -> dict:
62
  client = await get_client()
63
- resp = await client.post(
64
- f"{base_url}{path}", headers=ICLOUD_HEADERS, data=payload
65
- )
66
  resp.raise_for_status()
67
  return resp.json()
68
 
@@ -88,7 +91,6 @@ async def get_album(token: str):
88
  videos = []
89
  for photo in metadata:
90
  if photo.get("mediaAssetType", "").lower() != "video":
91
- logging.info(f"Photo {photo.get('photoGuid')} is not a video.")
92
  continue
93
 
94
  derivatives = photo.get("derivatives", {})
@@ -98,13 +100,11 @@ async def get_album(token: str):
98
  default=None
99
  )
100
  if not best:
101
- logging.info(f"No video derivative for photo {photo.get('photoGuid')}")
102
  continue
103
 
104
  checksum = best.get("checksum")
105
  info = asset_map.get(checksum)
106
  if not info:
107
- logging.info(f"Missing asset for checksum {checksum}")
108
  continue
109
  video_url = f"https://{info['url_location']}{info['url_path']}"
110
 
@@ -118,12 +118,13 @@ async def get_album(token: str):
118
  videos.append({
119
  "caption": photo.get("caption", ""),
120
  "url": video_url,
121
- "poster": poster
122
  })
123
 
124
  return {"videos": videos}
 
125
  except Exception as e:
126
- logging.error(f"Error in get_album: {e}")
127
  return {"error": str(e)}
128
 
129
  @app.get("/album/{token}/raw")
@@ -136,5 +137,5 @@ async def get_album_raw(token: str):
136
  asset_map = await get_asset_urls(base_url, guids)
137
  return {"metadata": metadata, "asset_urls": asset_map}
138
  except Exception as e:
139
- logging.error(f"Error in get_album_raw: {e}")
140
  return {"error": str(e)}
 
7
  app = FastAPI()
8
  logging.basicConfig(level=logging.INFO)
9
 
10
+ # Enable CORS for all origins
11
+ app.add_middleware(
12
+ CORSMiddleware,
13
+ allow_origins=["*"],
14
+ allow_methods=["*"],
15
+ allow_headers=["*"],
16
+ )
 
 
 
17
 
18
+ BASE_62_MAP = {c: i for i, c in enumerate("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")}
 
 
 
19
 
20
  async def get_client() -> httpx.AsyncClient:
21
  if not hasattr(app.state, "client"):
22
+ app.state.client = httpx.AsyncClient(timeout=15.0)
23
  return app.state.client
24
 
 
25
  def base62_to_int(token: str) -> int:
26
  result = 0
27
  for ch in token:
 
36
  n = base62_to_int(token[1:3])
37
  return f"https://p{n:02d}-sharedstreams.icloud.com/{token}/sharedstreams/"
38
 
39
+ ICLOUD_HEADERS = {
40
+ "Origin": "https://www.icloud.com",
41
+ "Content-Type": "text/plain"
42
+ }
43
  ICLOUD_PAYLOAD = '{"streamCtag":null}'
44
 
45
  async def get_redirected_base_url(base_url: str, token: str) -> str:
 
48
  f"{base_url}webstream", headers=ICLOUD_HEADERS, data=ICLOUD_PAYLOAD, follow_redirects=False
49
  )
50
  if resp.status_code == 330:
51
+ try:
52
+ body = resp.json()
53
+ host = body.get("X-Apple-MMe-Host")
54
+ if not host:
55
+ raise ValueError("Missing X-Apple-MMe-Host in 330 response")
56
+ logging.info(f"Redirected to {host}")
57
+ return f"https://{host}/{token}/sharedstreams/"
58
+ except Exception as e:
59
+ logging.error(f"Redirect parsing failed: {e}")
60
+ raise
61
+ elif resp.status_code == 200:
62
+ return base_url
63
+ else:
64
+ resp.raise_for_status()
65
 
66
  async def post_json(path: str, base_url: str, payload: str) -> dict:
67
  client = await get_client()
68
+ resp = await client.post(f"{base_url}{path}", headers=ICLOUD_HEADERS, data=payload)
 
 
69
  resp.raise_for_status()
70
  return resp.json()
71
 
 
91
  videos = []
92
  for photo in metadata:
93
  if photo.get("mediaAssetType", "").lower() != "video":
 
94
  continue
95
 
96
  derivatives = photo.get("derivatives", {})
 
100
  default=None
101
  )
102
  if not best:
 
103
  continue
104
 
105
  checksum = best.get("checksum")
106
  info = asset_map.get(checksum)
107
  if not info:
 
108
  continue
109
  video_url = f"https://{info['url_location']}{info['url_path']}"
110
 
 
118
  videos.append({
119
  "caption": photo.get("caption", ""),
120
  "url": video_url,
121
+ "poster": poster or ""
122
  })
123
 
124
  return {"videos": videos}
125
+
126
  except Exception as e:
127
+ logging.exception("Error in get_album")
128
  return {"error": str(e)}
129
 
130
  @app.get("/album/{token}/raw")
 
137
  asset_map = await get_asset_urls(base_url, guids)
138
  return {"metadata": metadata, "asset_urls": asset_map}
139
  except Exception as e:
140
+ logging.exception("Error in get_album_raw")
141
  return {"error": str(e)}