Chrunos commited on
Commit
a1b5d59
·
verified ·
1 Parent(s): 5810c83

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +191 -200
app.py CHANGED
@@ -1,210 +1,201 @@
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
8
- import random
9
- from functools import wraps
10
-
11
- # Configure logging
12
- logging.basicConfig(
13
- level=logging.INFO,
14
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
15
- )
16
- logger = logging.getLogger(__name__)
17
-
18
- app = FastAPI(title="Instagram Stories API", docs_url=None, redoc_url=None)
19
-
20
- # Environment variables
21
- INSTAGRAM_USERNAME = os.getenv('INSTAGRAM_USERNAME')
22
- INSTAGRAM_PASSWORD = os.getenv('INSTAGRAM_PASSWORD')
23
-
24
- # Session configuration
25
- SESSION_DIR = "/tmp/instagram_sessions"
26
- SESSION_FILE = os.path.join(SESSION_DIR, f"session-{INSTAGRAM_USERNAME}") if INSTAGRAM_USERNAME else None
27
-
28
- # Create session directory if not exists
29
- os.makedirs(SESSION_DIR, exist_ok=True)
30
-
31
- USER_AGENTS = [
32
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
33
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15",
34
- "Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"
35
- ]
36
-
37
- def get_instaloader() -> instaloader.Instaloader:
38
- """Create and configure Instaloader instance with proper parameters"""
39
- L = instaloader.Instaloader(
40
- sleep=True,
41
- request_timeout=300,
42
- max_connection_attempts=2,
43
- user_agent=random.choice(USER_AGENTS),
44
- # CORRECTED PARAMETER NAME:
45
- delay_seconds=15 # Changed from sleep_seconds to delay_seconds
46
- )
47
 
48
- if not INSTAGRAM_USERNAME or not INSTAGRAM_PASSWORD:
49
- logger.error("Instagram credentials not configured")
50
- raise HTTPException(
51
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
52
- detail="Server configuration error"
53
- )
54
-
55
- try:
56
- if SESSION_FILE and os.path.exists(SESSION_FILE):
57
- logger.info(f"Attempting to load session from {SESSION_FILE}")
58
- try:
59
- L.load_session_from_file(INSTAGRAM_USERNAME, SESSION_FILE)
60
- logger.info("Session loaded successfully")
61
- # Verify session is still valid
62
- test_profile = instaloader.Profile.from_username(L.context, INSTAGRAM_USERNAME)
63
- if not test_profile.is_verified:
64
- raise Exception("Session validation failed")
65
- return L
66
- except Exception as e:
67
- logger.warning(f"Session load failed: {str(e)}, performing fresh login")
68
-
69
- logger.info("Starting fresh login process")
70
- L.login(INSTAGRAM_USERNAME, INSTAGRAM_PASSWORD)
71
 
72
- L.context._session.headers.update({
73
- 'Accept-Language': 'en-US,en;q=0.9',
74
- 'Accept-Encoding': 'gzip, deflate, br',
75
- 'Referer': 'https://www.instagram.com/',
76
- 'DNT': '1',
77
- })
78
 
79
- if SESSION_FILE:
80
- L.save_session_to_file(SESSION_FILE)
81
- logger.info(f"Saved new session to {SESSION_FILE}")
82
 
83
- return L
84
-
85
- except instaloader.exceptions.BadCredentialsException as e:
86
- logger.error("Invalid credentials: %s", str(e))
87
- raise HTTPException(
88
- status_code=status.HTTP_401_UNAUTHORIZED,
89
- detail="Invalid Instagram credentials"
90
- )
91
- except instaloader.exceptions.TwoFactorAuthRequiredException as e:
92
- logger.error("2FA required: %s", str(e))
93
- raise HTTPException(
94
- status_code=status.HTTP_401_UNAUTHORIZED,
95
- detail="Two-factor authentication required"
96
- )
97
- except Exception as e:
98
- logger.error("Login failed: %s", str(e))
99
- raise HTTPException(
100
- status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
101
- detail="Instagram login service unavailable"
102
- )
103
-
104
- # Rest of the code remains the same as previous version...
105
-
106
-
107
- def handle_instagram_errors(func):
108
- @wraps(func)
109
- async def wrapper(*args, **kwargs):
110
- try:
111
- return await func(*args, **kwargs)
112
- except instaloader.exceptions.QueryReturnedBadRequestException as e:
113
- logger.error("API error 400: %s", str(e))
114
- raise HTTPException(
115
- status_code=status.HTTP_429_TOO_MANY_REQUESTS,
116
- detail="Instagram rate limit exceeded"
117
- )
118
- except instaloader.exceptions.ConnectionException as e:
119
- logger.error("Connection error: %s", str(e))
120
- raise HTTPException(
121
- status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
122
- detail="Instagram service unavailable"
123
- )
124
- except Exception as e:
125
- logger.error("Unexpected error: %s", str(e))
126
- raise HTTPException(
127
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
128
- detail="Internal server error"
129
- )
130
- return wrapper
131
-
132
- @app.get("/stories/{username}")
133
- @handle_instagram_errors
134
- async def get_stories(username: str):
135
- """Retrieve stories with enhanced anti-detection measures"""
136
- logger.info(f"Processing request for @{username}")
137
 
138
- try:
139
- L = get_instaloader()
140
- logger.info("Instaloader instance configured")
141
 
142
- # Randomized delay before profile request
143
- delay = random.uniform(2.5, 5.5)
144
- logger.debug(f"Applying initial delay of {delay:.2f}s")
145
- time.sleep(delay)
146
 
147
- # Profile lookup with retry
148
- for attempt in range(3):
149
- try:
150
- logger.info(f"Fetching profile (attempt {attempt+1}/3)")
151
- profile = instaloader.Profile.from_username(L.context, username)
152
- break
153
- except instaloader.exceptions.QueryReturnedBadRequestException:
154
- wait_time = (attempt + 1) * 10
155
- logger.warning(f"Rate limited, waiting {wait_time}s")
156
- time.sleep(wait_time)
157
- else:
158
- raise HTTPException(
159
- status.HTTP_429_TOO_MANY_REQUESTS,
160
- "Too many attempts to access Instagram"
161
- )
162
-
163
- logger.info(f"Access check for @{username}")
164
- if not profile.has_public_story and not profile.is_followed_by_viewer:
165
- logger.warning("Private profile access denied")
166
- raise HTTPException(
167
- status.HTTP_403_FORBIDDEN,
168
- "No accessible stories for this profile"
169
- )
170
-
171
- # Additional delay before story fetch
172
- time.sleep(random.uniform(1.5, 3.5))
173
 
174
- logger.info("Fetching stories")
175
- stories = []
176
- try:
177
- for story in L.get_stories(userids=[profile.userid]):
178
- for item in story.get_items():
179
- stories.append({
180
- "id": str(item.mediaid),
181
- "url": item.url,
182
- "type": "video" if item.is_video else "image",
183
- "timestamp": item.date_utc.isoformat(),
184
- "views": item.view_count if item.is_video else None,
185
- })
186
- except instaloader.exceptions.QueryReturnedNotFoundException:
187
- logger.error("Stories not found")
188
- raise HTTPException(
189
- status.HTTP_404_NOT_FOUND,
190
- "Stories not available"
191
- )
192
-
193
- if not stories:
194
- logger.info("No active stories found")
195
- raise HTTPException(
196
- status.HTTP_404_NOT_FOUND,
197
- "No active stories available"
198
- )
199
-
200
- # Final randomized delay
201
- time.sleep(random.uniform(0.5, 1.5))
202
 
203
- logger.info(f"Returning {len(stories)} stories")
204
- return {"data": stories}
205
 
206
- except Exception as e:
207
- logger.error(f"Critical failure: {str(e)}")
208
- raise
209
-
210
- # Keep the download endpoint from previous version
 
1
+ from fastapi import FastAPI, HTTPException, status
2
+ import instaloader
3
+ import os
4
+ import time
5
+ import logging
6
+ import random
7
+ from functools import wraps
8
+
9
+ # Configure logging
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
13
+ )
14
+ logger = logging.getLogger(__name__)
15
+
16
+ app = FastAPI(title="Instagram Stories API", docs_url=None, redoc_url=None)
17
+
18
+ # Environment variables
19
+ INSTAGRAM_USERNAME = os.getenv('INSTAGRAM_USERNAME')
20
+ INSTAGRAM_PASSWORD = os.getenv('INSTAGRAM_PASSWORD')
21
+
22
+ # Session configuration
23
+ SESSION_DIR = "/tmp/instagram_sessions"
24
+ SESSION_FILE = os.path.join(SESSION_DIR, f"session-{INSTAGRAM_USERNAME}") if INSTAGRAM_USERNAME else None
25
+
26
+ # Create session directory if not exists
27
+ os.makedirs(SESSION_DIR, exist_ok=True)
28
+
29
+ USER_AGENTS = [
30
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
31
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15",
32
+ "Mozilla/5.0 (Linux; Android 10; SM-G981B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"
33
+ ]
34
+
35
+ def get_instaloader() -> instaloader.Instaloader:
36
+ """Create and configure Instaloader instance with proper parameters"""
37
+ L = instaloader.Instaloader(
38
+ sleep=True,
39
+ request_timeout=300,
40
+ max_connection_attempts=2,
41
+ user_agent=random.choice(USER_AGENTS),
42
+ )
 
 
 
 
43
 
44
+ if not INSTAGRAM_USERNAME or not INSTAGRAM_PASSWORD:
45
+ logger.error("Instagram credentials not configured")
46
+ raise HTTPException(
47
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
48
+ detail="Server configuration error"
49
+ )
50
+
51
+ try:
52
+ if SESSION_FILE and os.path.exists(SESSION_FILE):
53
+ logger.info(f"Attempting to load session from {SESSION_FILE}")
54
+ try:
55
+ L.load_session_from_file(INSTAGRAM_USERNAME, SESSION_FILE)
56
+ logger.info("Session loaded successfully")
57
+ # Verify session is still valid
58
+ test_profile = instaloader.Profile.from_username(L.context, INSTAGRAM_USERNAME)
59
+ if not test_profile.is_verified:
60
+ raise Exception("Session validation failed")
61
+ return L
62
+ except Exception as e:
63
+ logger.warning(f"Session load failed: {str(e)}, performing fresh login")
64
+
65
+ logger.info("Starting fresh login process")
66
+ L.login(INSTAGRAM_USERNAME, INSTAGRAM_PASSWORD)
67
 
68
+ L.context._session.headers.update({
69
+ 'Accept-Language': 'en-US,en;q=0.9',
70
+ 'Accept-Encoding': 'gzip, deflate, br',
71
+ 'Referer': 'https://www.instagram.com/',
72
+ 'DNT': '1',
73
+ })
74
 
75
+ if SESSION_FILE:
76
+ L.save_session_to_file(SESSION_FILE)
77
+ logger.info(f"Saved new session to {SESSION_FILE}")
78
 
79
+ return L
80
+
81
+ except instaloader.exceptions.BadCredentialsException as e:
82
+ logger.error("Invalid credentials: %s", str(e))
83
+ raise HTTPException(
84
+ status_code=status.HTTP_401_UNAUTHORIZED,
85
+ detail="Invalid Instagram credentials"
86
+ )
87
+ except instaloader.exceptions.TwoFactorAuthRequiredException as e:
88
+ logger.error("2FA required: %s", str(e))
89
+ raise HTTPException(
90
+ status_code=status.HTTP_401_UNAUTHORIZED,
91
+ detail="Two-factor authentication required"
92
+ )
93
+ except Exception as e:
94
+ logger.error("Login failed: %s", str(e))
95
+ raise HTTPException(
96
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
97
+ detail="Instagram login service unavailable"
98
+ )
99
+
100
+ def handle_instagram_errors(func):
101
+ @wraps(func)
102
+ async def wrapper(*args, **kwargs):
103
+ try:
104
+ return await func(*args, **kwargs)
105
+ except instaloader.exceptions.QueryReturnedBadRequestException as e:
106
+ logger.error("API error 400: %s", str(e))
107
+ raise HTTPException(
108
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
109
+ detail="Instagram rate limit exceeded"
110
+ )
111
+ except instaloader.exceptions.ConnectionException as e:
112
+ logger.error("Connection error: %s", str(e))
113
+ raise HTTPException(
114
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
115
+ detail="Instagram service unavailable"
116
+ )
117
+ except Exception as e:
118
+ logger.error("Unexpected error: %s", str(e))
119
+ raise HTTPException(
120
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
121
+ detail="Internal server error"
122
+ )
123
+ return wrapper
124
+
125
+ @app.get("/stories/{username}")
126
+ @handle_instagram_errors
127
+ async def get_stories(username: str):
128
+ """Retrieve stories with enhanced anti-detection measures"""
129
+ logger.info(f"Processing request for @{username}")
 
 
 
130
 
131
+ try:
132
+ L = get_instaloader()
133
+ logger.info("Instaloader instance configured")
134
 
135
+ # Randomized delay before profile request
136
+ delay = random.uniform(2.5, 5.5)
137
+ logger.debug(f"Applying initial delay of {delay:.2f}s")
138
+ time.sleep(delay)
139
 
140
+ # Profile lookup with retry
141
+ for attempt in range(3):
142
+ try:
143
+ logger.info(f"Fetching profile (attempt {attempt+1}/3)")
144
+ profile = instaloader.Profile.from_username(L.context, username)
145
+ break
146
+ except instaloader.exceptions.QueryReturnedBadRequestException:
147
+ wait_time = (attempt + 1) * 10
148
+ logger.warning(f"Rate limited, waiting {wait_time}s")
149
+ time.sleep(wait_time)
150
+ else:
151
+ raise HTTPException(
152
+ status.HTTP_429_TOO_MANY_REQUESTS,
153
+ "Too many attempts to access Instagram"
154
+ )
155
+
156
+ logger.info(f"Access check for @{username}")
157
+ if not profile.has_public_story and not profile.is_followed_by_viewer:
158
+ logger.warning("Private profile access denied")
159
+ raise HTTPException(
160
+ status.HTTP_403_FORBIDDEN,
161
+ "No accessible stories for this profile"
162
+ )
163
+
164
+ # Additional delay before story fetch
165
+ time.sleep(random.uniform(1.5, 3.5))
166
 
167
+ logger.info("Fetching stories")
168
+ stories = []
169
+ try:
170
+ for story in L.get_stories(userids=[profile.userid]):
171
+ for item in story.get_items():
172
+ stories.append({
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
+ "views": item.view_count if item.is_video else None,
178
+ })
179
+ except instaloader.exceptions.QueryReturnedNotFoundException:
180
+ logger.error("Stories not found")
181
+ raise HTTPException(
182
+ status.HTTP_404_NOT_FOUND,
183
+ "Stories not available"
184
+ )
185
+
186
+ if not stories:
187
+ logger.info("No active stories found")
188
+ raise HTTPException(
189
+ status.HTTP_404_NOT_FOUND,
190
+ "No active stories available"
191
+ )
192
+
193
+ # Final randomized delay
194
+ time.sleep(random.uniform(0.5, 1.5))
195
 
196
+ logger.info(f"Returning {len(stories)} stories")
197
+ return {"data": stories}
198
 
199
+ except Exception as e:
200
+ logger.error(f"Critical failure: {str(e)}")
201
+ raise