Chrunos commited on
Commit
449efc6
·
verified ·
1 Parent(s): 118a06a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +257 -191
app.py CHANGED
@@ -7,7 +7,8 @@ import logging
7
  import random
8
  import json
9
  from datetime import datetime, timedelta
10
- import hashlib
 
11
  from urllib.parse import quote
12
 
13
  # Configure logging
@@ -26,39 +27,15 @@ os.makedirs(CACHE_DIR, exist_ok=True)
26
  # Cache state
27
  STORY_CACHE = {}
28
  CACHE_EXPIRY = {}
29
- LAST_REQUEST = {}
30
 
31
  # User agents
32
  USER_AGENTS = [
33
- "Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1",
34
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
35
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
36
  ]
37
 
38
- # More diverse demo content for better user experience
39
- DEMO_STORIES = {
40
- "type1": [
41
- {"id": "nature1", "type": "image", "url": "https://images.unsplash.com/photo-1682687980961-78fa83781450"},
42
- {"id": "nature2", "type": "image", "url": "https://images.unsplash.com/photo-1709921233257-4d6fad0af5c8"}
43
- ],
44
- "type2": [
45
- {"id": "city1", "type": "image", "url": "https://images.unsplash.com/photo-1710319367362-089a828a423c"},
46
- {"id": "city2", "type": "video", "url": "https://assets.mixkit.co/videos/preview/mixkit-aerial-view-of-city-traffic-at-night-11-large.mp4"}
47
- ],
48
- "type3": [
49
- {"id": "food1", "type": "image", "url": "https://images.unsplash.com/photo-1710170883104-2e9ac8709bb7"},
50
- {"id": "food2", "type": "image", "url": "https://images.unsplash.com/photo-1683009427500-71a13c0dce20"}
51
- ],
52
- "type4": [
53
- {"id": "travel1", "type": "image", "url": "https://images.unsplash.com/photo-1682687982501-1e58ab814714"},
54
- {"id": "travel2", "type": "video", "url": "https://assets.mixkit.co/videos/preview/mixkit-going-down-a-curved-highway-through-a-mountain-range-41576-large.mp4"}
55
- ],
56
- "type5": [
57
- {"id": "people1", "type": "image", "url": "https://images.unsplash.com/photo-1710161380135-9b617884d65f"},
58
- {"id": "people2", "type": "video", "url": "https://assets.mixkit.co/videos/preview/mixkit-man-dancing-under-changing-lights-1240-large.mp4"}
59
- ]
60
- }
61
-
62
  def get_cache_key(username):
63
  """Generate a cache key from username"""
64
  return username.lower()
@@ -76,8 +53,8 @@ def get_cached_stories(username):
76
  CACHE_EXPIRY.pop(key, None)
77
  return None
78
 
79
- def save_to_cache(username, data, minutes=60):
80
- """Save stories to cache with a longer cache time"""
81
  key = get_cache_key(username)
82
  STORY_CACHE[key] = data
83
  CACHE_EXPIRY[key] = datetime.now() + timedelta(minutes=minutes)
@@ -94,137 +71,203 @@ def save_to_cache(username, data, minutes=60):
94
  except Exception as e:
95
  logger.warning(f"Failed to save cache: {str(e)}")
96
 
97
- def should_rate_limit_request(username):
98
- """Basic rate limiting check"""
 
99
  key = username.lower()
100
- now = datetime.now()
101
-
102
- if key in LAST_REQUEST:
103
- seconds_since = (now - LAST_REQUEST[key]).total_seconds()
104
- if seconds_since < 60: # 1 minute between requests
105
- logger.warning(f"Rate limiting {username}: {seconds_since:.1f}s since last request")
106
  return True
107
-
108
- LAST_REQUEST[key] = now
 
109
  return False
110
 
111
- def get_demo_stories(username):
112
- """Return demo stories for a username with deterministic but varied selection"""
113
- logger.info(f"Using demo stories for {username}")
114
-
115
- # Use hash of username to deterministically select a demo type for each username
116
- # This ensures the same username always gets the same demo content
117
- hash_value = int(hashlib.md5(username.lower().encode()).hexdigest(), 16)
118
- demo_type = f"type{(hash_value % 5) + 1}" # Get a number between 1-5
119
-
120
- stories = DEMO_STORIES[demo_type]
121
-
122
- # Add timestamps to make them seem fresh
123
- for story in stories:
124
- # Random time in the last 24 hours
125
- random_hours = random.uniform(0, 23)
126
- story_time = datetime.now() - timedelta(hours=random_hours)
127
- story["timestamp"] = story_time.isoformat()
128
-
129
- result = {
130
- "data": stories,
131
- "count": len(stories),
132
- "username": username,
133
- "demo": True, # Mark as demo content
134
- "note": "Using placeholder content - Instagram API access is currently restricted",
135
- "fetched_at": datetime.now().isoformat()
136
  }
137
-
138
- return result
139
 
140
- def try_get_real_stories(username):
141
- """Attempt to get real stories using multiple methods"""
142
- # Try method 1: Direct API (very likely to fail due to restrictions)
 
 
 
 
 
143
  try:
144
- logger.info(f"Attempting direct API access for {username}")
145
- url = f"https://i.instagram.com/api/v1/feed/user/{username}/story/"
146
- headers = {
147
- 'User-Agent': 'Instagram 219.0.0.12.117 Android',
148
- 'Accept-Language': 'en-US',
149
- }
150
- response = requests.get(url, headers=headers, timeout=5)
151
-
152
- if response.status_code == 200:
153
- data = response.json()
154
- if 'reel' in data and 'items' in data['reel'] and data['reel']['items']:
155
- logger.info(f"Successfully got stories via direct API for {username}")
156
- # Process items and return
157
- stories = []
158
- for item in data['reel']['items']:
159
- story = {
160
- "id": item.get('pk', ''),
161
- "type": "video" if item.get('media_type') == 2 else "image",
162
- "timestamp": datetime.fromtimestamp(item.get('taken_at')).isoformat()
163
- }
164
-
165
- if story["type"] == "video" and 'video_versions' in item and item['video_versions']:
166
- story["url"] = item['video_versions'][0]['url']
167
- elif 'image_versions2' in item and 'candidates' in item['image_versions2'] and item['image_versions2']['candidates']:
168
- story["url"] = item['image_versions2']['candidates'][0]['url']
169
- else:
170
- continue # Skip if no URL
171
-
172
- stories.append(story)
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
174
- if stories:
175
- return {
176
- "data": stories,
177
- "count": len(stories),
178
- "username": username,
179
- "source": "instagram_api",
180
- "fetched_at": datetime.now().isoformat()
181
- }
 
 
 
182
  except Exception as e:
183
- logger.warning(f"Direct API access failed: {str(e)}")
 
 
 
 
 
184
 
185
- # Try method 2: Unofficial service (if available)
186
  try:
187
- logger.info(f"Attempting unofficial service for {username}")
188
- url = f"https://instagram-stories1.p.rapidapi.com/v1/get_stories?username={username}"
189
- headers = {
190
- "X-RapidAPI-Key": os.getenv('RAPIDAPI_KEY', ''), # Set this in your environment if available
191
- "X-RapidAPI-Host": "instagram-stories1.p.rapidapi.com"
192
- }
 
193
 
194
- if headers["X-RapidAPI-Key"]: # Only try if API key is set
195
- response = requests.get(url, headers=headers, timeout=10)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
- if response.status_code == 200:
198
- data = response.json()
199
- if isinstance(data, dict) and 'stories' in data and data['stories']:
200
- logger.info(f"Successfully got stories via unofficial service for {username}")
201
- stories = []
202
-
203
- for item in data['stories']:
204
- story = {
205
- "id": item.get('id', str(hash(item.get('media_url', '')))),
206
- "type": item.get('media_type', 'image'),
207
- "url": item.get('media_url', ''),
208
- "timestamp": item.get('timestamp', datetime.now().isoformat())
209
- }
210
- stories.append(story)
211
-
212
- if stories:
213
- return {
214
- "data": stories,
215
- "count": len(stories),
216
- "username": username,
217
- "source": "unofficial_api",
218
- "fetched_at": datetime.now().isoformat()
219
- }
220
  except Exception as e:
221
- logger.warning(f"Unofficial service failed: {str(e)}")
 
 
 
 
 
222
 
223
- # All methods failed
224
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
  @app.get("/stories/{username}")
227
- async def get_stories(username: str, cached: bool = False, demo: bool = False):
228
  """Get Instagram stories for a user"""
229
  logger.info(f"Request for @{username} stories")
230
 
@@ -234,42 +277,70 @@ async def get_stories(username: str, cached: bool = False, demo: bool = False):
234
  if cached_result:
235
  return {**cached_result, "from_cache": True}
236
 
237
- # If demo parameter is specified, return demo stories
238
- if demo:
239
- demo_stories = get_demo_stories(username)
240
- return demo_stories
241
-
242
  try:
243
- # Check rate limiting
244
- if should_rate_limit_request(username):
245
  cached_result = get_cached_stories(username)
246
  if cached_result:
247
  return {**cached_result, "from_cache": True, "rate_limited": True}
248
  else:
249
- # If no cache but rate limited, use demo content
250
- demo_stories = get_demo_stories(username)
251
- save_to_cache(username, demo_stories, minutes=5) # Short cache time for demo content
252
- return {**demo_stories, "rate_limited": True}
253
 
254
- # Try to get real stories
255
- real_stories = try_get_real_stories(username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
 
257
- if real_stories:
258
  # Cache the successful result
259
- save_to_cache(username, real_stories)
260
- return real_stories
 
261
 
262
- # If no result, check cache
263
  cached_result = get_cached_stories(username)
264
  if cached_result:
265
- logger.info(f"No live stories found, returning cached result")
266
- return {**cached_result, "from_cache": True}
 
 
 
 
 
267
 
268
- # No result and no cache, use demo content
269
- demo_stories = get_demo_stories(username)
270
- save_to_cache(username, demo_stories, minutes=30)
271
- return demo_stories
 
272
 
 
 
 
273
  except Exception as e:
274
  error_message = str(e)
275
  logger.error(f"Error getting stories: {error_message}")
@@ -280,9 +351,22 @@ async def get_stories(username: str, cached: bool = False, demo: bool = False):
280
  logger.info(f"Returning cached result due to error")
281
  return {**cached_result, "from_cache": True, "error_occurred": True}
282
 
283
- # No cache available, use demo content
284
- demo_stories = get_demo_stories(username)
285
- return {**demo_stories, "error_occurred": True}
 
 
 
 
 
 
 
 
 
 
 
 
 
286
 
287
  @app.get("/download/{url:path}")
288
  async def download_media(url: str):
@@ -301,7 +385,7 @@ async def download_media(url: str):
301
  session = requests.Session()
302
  headers = {
303
  "User-Agent": random.choice(USER_AGENTS),
304
- "Referer": "https://www.google.com/",
305
  "Accept": "*/*",
306
  }
307
 
@@ -335,23 +419,6 @@ async def download_media(url: str):
335
  detail="Failed to download media"
336
  )
337
 
338
- # Add an endpoint to explain what's happening
339
- @app.get("/")
340
- async def root():
341
- return {
342
- "message": "Instagram Stories API",
343
- "status": "Running with demo mode",
344
- "note": "Due to Instagram's API restrictions, this service currently provides placeholder content.",
345
- "endpoints": {
346
- "get_stories": "/stories/{username}",
347
- "get_stories_from_cache": "/stories/{username}?cached=true",
348
- "get_demo_stories": "/stories/{username}?demo=true",
349
- "download_media": "/download/{url}",
350
- "health_check": "/health"
351
- },
352
- "timestamp": datetime.now().isoformat()
353
- }
354
-
355
  # Load cache from disk at startup
356
  @app.on_event("startup")
357
  def load_cache_from_disk():
@@ -383,8 +450,7 @@ def load_cache_from_disk():
383
  async def health_check():
384
  return {
385
  "status": "ok",
386
- "mode": "demo",
387
- "note": "Instagram access restricted - using placeholder content",
388
  "timestamp": datetime.now().isoformat(),
389
  "cache_size": len(STORY_CACHE),
 
390
  }
 
7
  import random
8
  import json
9
  from datetime import datetime, timedelta
10
+ import re
11
+ import base64
12
  from urllib.parse import quote
13
 
14
  # Configure logging
 
27
  # Cache state
28
  STORY_CACHE = {}
29
  CACHE_EXPIRY = {}
30
+ RATE_LIMITS = {}
31
 
32
  # User agents
33
  USER_AGENTS = [
34
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
35
+ "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
36
+ "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/116.0.5845.0 Mobile/15E148 Safari/604.1"
37
  ]
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  def get_cache_key(username):
40
  """Generate a cache key from username"""
41
  return username.lower()
 
53
  CACHE_EXPIRY.pop(key, None)
54
  return None
55
 
56
+ def save_to_cache(username, data, minutes=30):
57
+ """Save stories to cache"""
58
  key = get_cache_key(username)
59
  STORY_CACHE[key] = data
60
  CACHE_EXPIRY[key] = datetime.now() + timedelta(minutes=minutes)
 
71
  except Exception as e:
72
  logger.warning(f"Failed to save cache: {str(e)}")
73
 
74
+ def is_rate_limited(username):
75
+ """Check if we should rate limit this request"""
76
+ # Check rate limits
77
  key = username.lower()
78
+ if key in RATE_LIMITS:
79
+ if datetime.now() < RATE_LIMITS[key]["until"]:
80
+ seconds = (RATE_LIMITS[key]["until"] - datetime.now()).total_seconds()
81
+ logger.warning(f"Rate limited {username} for {int(seconds)}s")
 
 
82
  return True
83
+ else:
84
+ # Expired rate limit
85
+ RATE_LIMITS.pop(key)
86
  return False
87
 
88
+ def set_rate_limit(username, minutes=15):
89
+ """Set rate limiting for a username"""
90
+ key = username.lower()
91
+ RATE_LIMITS[key] = {
92
+ "until": datetime.now() + timedelta(minutes=minutes),
93
+ "requests": 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
+ logger.warning(f"Setting rate limit for {username} for {minutes} minutes")
 
96
 
97
+ def get_random_delay():
98
+ """Get a random delay time to mimic human behavior"""
99
+ return random.uniform(1, 3)
100
+
101
+ def fetch_stories_from_web(username):
102
+ """Fetch stories using the public web interface"""
103
+ url = f"https://storiesig.info/api/ig/stories/{username}"
104
+
105
  try:
106
+ session = requests.Session()
107
+ session.headers.update({
108
+ 'User-Agent': random.choice(USER_AGENTS),
109
+ 'Accept': 'application/json, text/plain, */*',
110
+ 'Accept-Language': 'en-US,en;q=0.5',
111
+ 'Referer': f'https://storiesig.info/stories/{username}',
112
+ 'Origin': 'https://storiesig.info',
113
+ 'Sec-Fetch-Dest': 'empty',
114
+ 'Sec-Fetch-Mode': 'cors',
115
+ 'Sec-Fetch-Site': 'same-origin',
116
+ })
117
+
118
+ # Add random delay to mimic human behavior
119
+ time.sleep(get_random_delay())
120
+
121
+ response = session.get(url)
122
+
123
+ if response.status_code == 429:
124
+ logger.warning(f"Rate limited by storiesig.info for {username}")
125
+ set_rate_limit(username, 10)
126
+ raise Exception("Rate limited by service")
127
+
128
+ response.raise_for_status()
129
+ data = response.json()
130
+
131
+ if not data.get('stories'):
132
+ logger.warning(f"No stories found for {username}")
133
+ raise Exception("No stories found")
134
+
135
+ # Process the stories
136
+ stories = []
137
+ for item in data.get('stories', []):
138
+ story = {
139
+ "id": item.get('id', ''),
140
+ "type": item.get('type', 'image'),
141
+ "timestamp": datetime.fromtimestamp(item.get('taken_at_timestamp', 0)).isoformat(),
142
+ "url": item.get('url', '')
143
+ }
144
+
145
+ # Add thumbnail if available
146
+ if item.get('thumbnail_url'):
147
+ story["thumbnail"] = item.get('thumbnail_url')
148
 
149
+ stories.append(story)
150
+
151
+ result = {
152
+ "data": stories,
153
+ "count": len(stories),
154
+ "username": username,
155
+ "fetched_at": datetime.now().isoformat()
156
+ }
157
+
158
+ return result
159
+
160
  except Exception as e:
161
+ logger.error(f"Error fetching stories from web: {str(e)}")
162
+ raise
163
+
164
+ def fetch_stories_from_alternative(username):
165
+ """Try an alternative source for stories"""
166
+ url = f"https://instastories.watch/api/stories/{username}"
167
 
 
168
  try:
169
+ session = requests.Session()
170
+ session.headers.update({
171
+ 'User-Agent': random.choice(USER_AGENTS),
172
+ 'Accept': 'application/json',
173
+ 'Referer': f'https://instastories.watch/stories/{username}/',
174
+ 'Origin': 'https://instastories.watch'
175
+ })
176
 
177
+ # Add random delay
178
+ time.sleep(get_random_delay())
179
+
180
+ response = session.get(url)
181
+ response.raise_for_status()
182
+ data = response.json()
183
+
184
+ if not data or not data.get('stories'):
185
+ logger.warning(f"No stories found for {username} on alternative source")
186
+ raise Exception("No stories found")
187
+
188
+ # Process the stories
189
+ stories = []
190
+ for item in data.get('stories', []):
191
+ story = {
192
+ "id": item.get('id', ''),
193
+ "type": "video" if item.get('is_video', False) else "image",
194
+ "timestamp": datetime.fromtimestamp(item.get('taken_at', 0)).isoformat(),
195
+ "url": item.get('media_url', '')
196
+ }
197
 
198
+ if item.get('thumbnail_url'):
199
+ story["thumbnail"] = item.get('thumbnail_url')
200
+
201
+ stories.append(story)
202
+
203
+ result = {
204
+ "data": stories,
205
+ "count": len(stories),
206
+ "username": username,
207
+ "fetched_at": datetime.now().isoformat()
208
+ }
209
+
210
+ return result
211
+
 
 
 
 
 
 
 
 
 
212
  except Exception as e:
213
+ logger.error(f"Error fetching stories from alternative: {str(e)}")
214
+ raise
215
+
216
+ def fetch_stories_from_third_source(username):
217
+ """Try a third source for stories"""
218
+ url = f"https://instasupersave.com/api/ig/stories/{username}"
219
 
220
+ try:
221
+ session = requests.Session()
222
+ session.headers.update({
223
+ 'User-Agent': random.choice(USER_AGENTS),
224
+ 'Accept': 'application/json',
225
+ 'Referer': f'https://instasupersave.com/instagram-stories/{username}/',
226
+ 'Origin': 'https://instasupersave.com'
227
+ })
228
+
229
+ # Add random delay
230
+ time.sleep(get_random_delay())
231
+
232
+ response = session.get(url)
233
+ response.raise_for_status()
234
+ data = response.json()
235
+
236
+ if not data or not data.get('result') or not data['result'].get('stories'):
237
+ logger.warning(f"No stories found for {username} on third source")
238
+ raise Exception("No stories found")
239
+
240
+ # Process the stories
241
+ stories = []
242
+ for item in data['result'].get('stories', []):
243
+ story = {
244
+ "id": item.get('id', ''),
245
+ "type": "video" if item.get('is_video', False) else "image",
246
+ "timestamp": datetime.fromtimestamp(item.get('taken_at', 0)).isoformat(),
247
+ "url": item.get('source', '')
248
+ }
249
+
250
+ if item.get('thumbnail'):
251
+ story["thumbnail"] = item.get('thumbnail')
252
+
253
+ stories.append(story)
254
+
255
+ result = {
256
+ "data": stories,
257
+ "count": len(stories),
258
+ "username": username,
259
+ "fetched_at": datetime.now().isoformat(),
260
+ "source": "third"
261
+ }
262
+
263
+ return result
264
+
265
+ except Exception as e:
266
+ logger.error(f"Error fetching stories from third source: {str(e)}")
267
+ raise
268
 
269
  @app.get("/stories/{username}")
270
+ async def get_stories(username: str, cached: bool = False):
271
  """Get Instagram stories for a user"""
272
  logger.info(f"Request for @{username} stories")
273
 
 
277
  if cached_result:
278
  return {**cached_result, "from_cache": True}
279
 
 
 
 
 
 
280
  try:
281
+ # Check for rate limiting
282
+ if is_rate_limited(username):
283
  cached_result = get_cached_stories(username)
284
  if cached_result:
285
  return {**cached_result, "from_cache": True, "rate_limited": True}
286
  else:
287
+ raise HTTPException(
288
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
289
+ detail="Rate limit exceeded for this username. Please try again later."
290
+ )
291
 
292
+ # Try multiple sources in sequence
293
+ result = None
294
+ sources_tried = 0
295
+ error_messages = []
296
+
297
+ # Try first source
298
+ try:
299
+ sources_tried += 1
300
+ result = fetch_stories_from_web(username)
301
+ except Exception as e:
302
+ error_messages.append(f"Source 1: {str(e)}")
303
+ # Let's try the second source
304
+ try:
305
+ sources_tried += 1
306
+ time.sleep(get_random_delay()) # Add delay between attempts
307
+ result = fetch_stories_from_alternative(username)
308
+ except Exception as e2:
309
+ error_messages.append(f"Source 2: {str(e2)}")
310
+ # Let's try the third source
311
+ try:
312
+ sources_tried += 1
313
+ time.sleep(get_random_delay())
314
+ result = fetch_stories_from_third_source(username)
315
+ except Exception as e3:
316
+ error_messages.append(f"Source 3: {str(e3)}")
317
 
318
+ if result:
319
  # Cache the successful result
320
+ save_to_cache(username, result)
321
+ result["sources_tried"] = sources_tried
322
+ return result
323
 
324
+ # All sources failed, check cache
325
  cached_result = get_cached_stories(username)
326
  if cached_result:
327
+ logger.info(f"All sources failed, returning cached result")
328
+ return {
329
+ **cached_result,
330
+ "from_cache": True,
331
+ "sources_tried": sources_tried,
332
+ "errors": error_messages
333
+ }
334
 
335
+ # No result from any source and no cache
336
+ raise HTTPException(
337
+ status_code=status.HTTP_404_NOT_FOUND,
338
+ detail=f"Stories not found after trying {sources_tried} sources"
339
+ )
340
 
341
+ except HTTPException:
342
+ # Re-raise HTTP exceptions directly
343
+ raise
344
  except Exception as e:
345
  error_message = str(e)
346
  logger.error(f"Error getting stories: {error_message}")
 
351
  logger.info(f"Returning cached result due to error")
352
  return {**cached_result, "from_cache": True, "error_occurred": True}
353
 
354
+ # Handle specific errors
355
+ if 'rate' in error_message.lower() or 'limit' in error_message.lower():
356
+ raise HTTPException(
357
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
358
+ detail="Rate limit exceeded. Please try again later."
359
+ )
360
+ elif 'no stories' in error_message.lower():
361
+ raise HTTPException(
362
+ status_code=status.HTTP_404_NOT_FOUND,
363
+ detail="No stories found for this user"
364
+ )
365
+ else:
366
+ raise HTTPException(
367
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
368
+ detail="Error fetching stories"
369
+ )
370
 
371
  @app.get("/download/{url:path}")
372
  async def download_media(url: str):
 
385
  session = requests.Session()
386
  headers = {
387
  "User-Agent": random.choice(USER_AGENTS),
388
+ "Referer": "https://www.instagram.com/",
389
  "Accept": "*/*",
390
  }
391
 
 
419
  detail="Failed to download media"
420
  )
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  # Load cache from disk at startup
423
  @app.on_event("startup")
424
  def load_cache_from_disk():
 
450
  async def health_check():
451
  return {
452
  "status": "ok",
 
 
453
  "timestamp": datetime.now().isoformat(),
454
  "cache_size": len(STORY_CACHE),
455
+ "rate_limits": len(RATE_LIMITS)
456
  }