Chrunos commited on
Commit
7fc3e5e
·
verified ·
1 Parent(s): 9f55880

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +49 -117
app.py CHANGED
@@ -1,13 +1,9 @@
1
  import logging
 
2
  from fastapi import FastAPI, HTTPException
3
- from pydantic import BaseModel
4
  import instaloader
5
- import os
6
- from dotenv import load_dotenv
7
- from typing import Optional, List
8
- import time
9
- import random
10
- from fastapi.middleware.cors import CORSMiddleware
11
 
12
  # Load environment variables
13
  load_dotenv()
@@ -20,120 +16,56 @@ INSTAGRAM_PASSWORD = os.getenv('INSTAGRAM_PASSWORD')
20
  logging.basicConfig(level=logging.INFO)
21
  logger = logging.getLogger(__name__)
22
 
23
- # Check if credentials are available
24
- if not INSTAGRAM_USERNAME or not INSTAGRAM_PASSWORD:
25
- logger.error("Instagram credentials are not set in the environment variables.")
26
- raise RuntimeError("Instagram credentials missing.")
27
-
28
- app = FastAPI(title="Instagram Post Downloader")
29
-
30
- # Add CORS middleware
31
- app.add_middleware(
32
- CORSMiddleware,
33
- allow_origins=["*"],
34
- allow_credentials=True,
35
- allow_methods=["*"],
36
- allow_headers=["*"],
37
- )
38
 
39
- class PostRequest(BaseModel):
40
- url: str
41
 
42
- class DownloadResponse(BaseModel):
43
- download_urls: List[str]
44
-
45
- def extract_shortcode(url: str) -> str:
46
- """Extract Instagram post shortcode from URL"""
47
- url = url.strip()
48
- if 'instagram.com' not in url:
49
- raise ValueError("Invalid Instagram URL")
50
- parts = url.split('/')
51
- for i, part in enumerate(parts):
52
- if part in ['p', 'reel', 'tv'] and (i + 1) < len(parts):
53
- shortcode = parts[i + 1].split('?')[0]
54
- return shortcode
55
- if 'instagram.com/p/' in url:
56
- return url.split('/p/')[1].split('/')[0].split('?')[0]
57
- raise ValueError("Invalid Instagram URL")
58
-
59
-
60
- @app.post("/dl", response_model=DownloadResponse)
61
- async def download_post(request: PostRequest):
62
- """Download Instagram post and return media URL"""
63
  try:
64
- shortcode = extract_shortcode(request.url)
65
- logger.info(f"Extracted shortcode: {shortcode}")
66
- except ValueError as e:
67
- logger.error(f"Invalid URL: {request.url}")
68
- raise HTTPException(status_code=400, detail=str(e))
69
-
 
 
 
 
 
 
 
 
 
 
 
70
  L = instaloader.Instaloader()
71
- session_file = f"{INSTAGRAM_USERNAME}.session"
72
-
73
- if os.path.exists(session_file):
74
- try:
75
- L.load_session_from_file(INSTAGRAM_USERNAME, session_file)
76
- logger.info("Loaded session from file.")
77
- except FileNotFoundError:
78
- pass
79
- else:
80
- try:
81
- # Login to Instagram
82
- L.login(INSTAGRAM_USERNAME, INSTAGRAM_PASSWORD)
83
- logger.info("Successfully logged in to Instagram")
84
- L.save_session_to_file(session_file)
85
- logger.info("Saved session to file.")
86
- except instaloader.exceptions.InstaloaderException as e:
87
- logger.error(f"Login error: {e}")
88
- raise HTTPException(status_code=500, detail="Error logging in to Instagram")
89
-
90
- # Configure retry parameters
91
- max_retries = 3
92
- retry_delay = random.uniform(1, 3)
93
-
94
- for attempt in range(max_retries):
95
- try:
96
- # Fetch post by shortcode
97
- post = instaloader.Post.from_shortcode(L.context, shortcode)
98
- logger.info(f"Fetched post: {post}")
99
- break
100
- except instaloader.exceptions.ConnectionException as e:
101
- logger.error(f"Connection error (attempt {attempt + 1}): {e}")
102
- if attempt == max_retries - 1:
103
- raise HTTPException(status_code=500, detail="Failed to connect to Instagram after multiple attempts")
104
- time.sleep(retry_delay)
105
- retry_delay *= 2 # Exponential backoff
106
- except instaloader.exceptions.InvalidArgumentException:
107
- logger.error(f"Post not found for shortcode: {shortcode}")
108
- raise HTTPException(status_code=404, detail="Post not found")
109
- except instaloader.exceptions.InstaloaderException as e:
110
- logger.error(f"Instaloader error: {e}")
111
- raise HTTPException(status_code=500, detail="Error fetching post")
112
- except Exception as e:
113
- logger.error(f"Unexpected error: {e}")
114
- raise HTTPException(status_code=500, detail="Internal server error")
115
-
116
- download_urls = []
117
- if post.typename == 'GraphSidecar':
118
- # Handle carousel posts
119
- for node in post.get_sidecar_nodes():
120
- if node.is_video:
121
- download_urls.append(node.video_url)
122
- else:
123
- download_urls.append(node.display_url)
124
- else:
125
- if post.is_video:
126
- download_urls.append(post.video_url)
127
- else:
128
- download_urls.append(post.url)
129
-
130
- if not download_urls:
131
- logger.error("No media found in post")
132
- raise HTTPException(status_code=404, detail="No media found in post")
133
 
134
- return {"download_urls": download_urls}
 
 
 
135
 
 
 
136
 
137
- @app.get("/api/health")
138
- async def health_check():
139
- return {"status": "healthy"}
 
 
 
 
 
 
 
 
 
 
 
 
1
  import logging
2
+ from dotenv import load_dotenv
3
  from fastapi import FastAPI, HTTPException
4
+ from fastapi.responses import StreamingResponse
5
  import instaloader
6
+ import requests
 
 
 
 
 
7
 
8
  # Load environment variables
9
  load_dotenv()
 
16
  logging.basicConfig(level=logging.INFO)
17
  logger = logging.getLogger(__name__)
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ app = FastAPI(title="Instagram Stories API", docs_url=None, redoc_url=None)
 
21
 
22
+ @app.get("/stories/{username}", summary="Get available stories")
23
+ async def get_stories(username: str):
24
+ """Retrieve metadata for public Instagram stories"""
25
+ L = instaloader.Instaloader()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  try:
27
+ profile = instaloader.Profile.from_username(L.context, username)
28
+ except instaloader.exceptions.ProfileNotExistsException:
29
+ raise HTTPException(404, "Profile not found")
30
+
31
+ stories = [{
32
+ "id": str(item.mediaid),
33
+ "url": item.url,
34
+ "type": "video" if item.is_video else "image",
35
+ "timestamp": item.date_utc.isoformat(),
36
+ "views": item.view_count
37
+ } for story in profile.get_stories() for item in story.get_items()]
38
+
39
+ return {"data": stories} if stories else HTTPException(404, "No stories found")
40
+
41
+ @app.get("/download/{media_id}", summary="Download story content")
42
+ async def download_story(media_id: str, username: str):
43
+ """Download specific story media by ID"""
44
  L = instaloader.Instaloader()
45
+ try:
46
+ profile = instaloader.Profile.from_username(L.context, username)
47
+ except instaloader.exceptions.ProfileNotExistsException:
48
+ raise HTTPException(404, "Profile not found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ # Find the story item
51
+ story_item = next((item for story in profile.get_stories()
52
+ for item in story.get_items()
53
+ if str(item.mediaid) == media_id), None)
54
 
55
+ if not story_item:
56
+ raise HTTPException(404, "Story not found")
57
 
58
+ try:
59
+ response = requests.get(story_item.url, stream=True)
60
+ response.raise_for_status()
61
+ except requests.exceptions.RequestException:
62
+ raise HTTPException(500, "Failed to fetch media")
63
+
64
+ return StreamingResponse(
65
+ response.iter_content(chunk_size=8192),
66
+ media_type="video/mp4" if story_item.is_video else "image/jpeg",
67
+ headers={
68
+ "Content-Disposition": f"attachment; filename={username}_{media_id}"
69
+ f".{'mp4' if story_item.is_video else 'jpg'}"
70
+ }
71
+ )