Update app.py
Browse files
app.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1 |
from fastapi import FastAPI, HTTPException, status
|
|
|
2 |
import instaloader
|
|
|
3 |
import os
|
4 |
import time
|
5 |
import logging
|
@@ -169,13 +171,26 @@ async def get_stories(username: str):
|
|
169 |
try:
|
170 |
for story in L.get_stories(userids=[profile.userid]):
|
171 |
for item in story.get_items():
|
172 |
-
|
|
|
173 |
"id": str(item.mediaid),
|
174 |
"url": item.url,
|
175 |
"type": "video" if item.is_video else "image",
|
176 |
"timestamp": item.date_utc.isoformat(),
|
177 |
-
|
178 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
179 |
except instaloader.exceptions.QueryReturnedNotFoundException:
|
180 |
logger.error("Stories not found")
|
181 |
raise HTTPException(
|
@@ -198,4 +213,67 @@ async def get_stories(username: str):
|
|
198 |
|
199 |
except Exception as e:
|
200 |
logger.error(f"Critical failure: {str(e)}")
|
201 |
-
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from fastapi import FastAPI, HTTPException, status
|
2 |
+
from fastapi.responses import StreamingResponse
|
3 |
import instaloader
|
4 |
+
import requests
|
5 |
import os
|
6 |
import time
|
7 |
import logging
|
|
|
171 |
try:
|
172 |
for story in L.get_stories(userids=[profile.userid]):
|
173 |
for item in story.get_items():
|
174 |
+
# Create a story dict with safe attribute access
|
175 |
+
story_data = {
|
176 |
"id": str(item.mediaid),
|
177 |
"url": item.url,
|
178 |
"type": "video" if item.is_video else "image",
|
179 |
"timestamp": item.date_utc.isoformat(),
|
180 |
+
}
|
181 |
+
|
182 |
+
# Only try to add view_count if it's a video and the attribute exists
|
183 |
+
if item.is_video:
|
184 |
+
try:
|
185 |
+
# Safely check if view_count attribute exists
|
186 |
+
if hasattr(item, 'view_count'):
|
187 |
+
story_data["views"] = item.view_count
|
188 |
+
except AttributeError:
|
189 |
+
# Skip adding views if the attribute doesn't exist
|
190 |
+
pass
|
191 |
+
|
192 |
+
stories.append(story_data)
|
193 |
+
|
194 |
except instaloader.exceptions.QueryReturnedNotFoundException:
|
195 |
logger.error("Stories not found")
|
196 |
raise HTTPException(
|
|
|
213 |
|
214 |
except Exception as e:
|
215 |
logger.error(f"Critical failure: {str(e)}")
|
216 |
+
raise
|
217 |
+
|
218 |
+
@app.get("/download/{url:path}")
|
219 |
+
@handle_instagram_errors
|
220 |
+
async def download_media(url: str):
|
221 |
+
"""Download and proxy media content"""
|
222 |
+
logger.info(f"Download request for: {url}")
|
223 |
+
|
224 |
+
try:
|
225 |
+
# Basic validation to prevent arbitrary URL access
|
226 |
+
if not url.startswith(("https://instagram", "https://scontent")):
|
227 |
+
raise HTTPException(
|
228 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
229 |
+
detail="Invalid URL format"
|
230 |
+
)
|
231 |
+
|
232 |
+
# Random delay to avoid detection patterns
|
233 |
+
time.sleep(random.uniform(1.0, 3.0))
|
234 |
+
|
235 |
+
# Configure headers to mimic a browser
|
236 |
+
headers = {
|
237 |
+
"User-Agent": random.choice(USER_AGENTS),
|
238 |
+
"Accept": "*/*",
|
239 |
+
"Accept-Language": "en-US,en;q=0.9",
|
240 |
+
"Referer": "https://www.instagram.com/",
|
241 |
+
"Sec-Fetch-Dest": "empty",
|
242 |
+
"Sec-Fetch-Mode": "cors",
|
243 |
+
"Sec-Fetch-Site": "cross-site",
|
244 |
+
}
|
245 |
+
|
246 |
+
# Request the media with a session
|
247 |
+
response = requests.get(url, headers=headers, stream=True, timeout=30)
|
248 |
+
response.raise_for_status()
|
249 |
+
|
250 |
+
# Determine content type from response or URL
|
251 |
+
if "Content-Type" in response.headers:
|
252 |
+
content_type = response.headers["Content-Type"]
|
253 |
+
elif url.endswith((".jpg", ".jpeg")):
|
254 |
+
content_type = "image/jpeg"
|
255 |
+
elif url.endswith(".mp4"):
|
256 |
+
content_type = "video/mp4"
|
257 |
+
else:
|
258 |
+
content_type = "application/octet-stream"
|
259 |
+
|
260 |
+
logger.info(f"Media downloaded successfully: {content_type}")
|
261 |
+
|
262 |
+
# Stream the response back
|
263 |
+
return StreamingResponse(
|
264 |
+
response.iter_content(chunk_size=8192),
|
265 |
+
media_type=content_type,
|
266 |
+
headers={
|
267 |
+
"Content-Disposition": f"attachment; filename={url.split('/')[-1]}",
|
268 |
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
269 |
+
"Pragma": "no-cache",
|
270 |
+
"Expires": "0",
|
271 |
+
}
|
272 |
+
)
|
273 |
+
|
274 |
+
except requests.exceptions.RequestException as e:
|
275 |
+
logger.error(f"Download failed: {str(e)}")
|
276 |
+
raise HTTPException(
|
277 |
+
status_code=status.HTTP_502_BAD_GATEWAY,
|
278 |
+
detail="Failed to download media"
|
279 |
+
)
|