Chrunos commited on
Commit
a2c8032
·
verified ·
1 Parent(s): 74106ba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +119 -36
app.py CHANGED
@@ -1,66 +1,149 @@
1
- import logging
2
- from dotenv import load_dotenv
3
- import os
4
- from fastapi import FastAPI, HTTPException
5
  from fastapi.responses import StreamingResponse
6
  import instaloader
7
  import requests
 
 
 
8
 
9
- # Load environment variables
10
- load_dotenv()
11
 
12
- # Get credentials from environment variables
13
  INSTAGRAM_USERNAME = os.getenv('INSTAGRAM_USERNAME')
14
  INSTAGRAM_PASSWORD = os.getenv('INSTAGRAM_PASSWORD')
15
 
16
- # Configure logging
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
- app = FastAPI(title="Instagram Stories API", docs_url=None, redoc_url=None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- @app.get("/stories/{username}", summary="Get available stories")
 
24
  async def get_stories(username: str):
25
- """Retrieve metadata for public Instagram stories"""
26
- L = instaloader.Instaloader()
 
27
  try:
28
  profile = instaloader.Profile.from_username(L.context, username)
29
  except instaloader.exceptions.ProfileNotExistsException:
30
- raise HTTPException(404, "Profile not found")
31
-
32
- stories = [{
33
- "id": str(item.mediaid),
34
- "url": item.url,
35
- "type": "video" if item.is_video else "image",
36
- "timestamp": item.date_utc.isoformat(),
37
- "views": item.view_count
38
- } for story in profile.get_stories() for item in story.get_items()]
39
 
40
- return {"data": stories} if stories else HTTPException(404, "No stories found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
- @app.get("/download/{media_id}", summary="Download story content")
 
 
 
43
  async def download_story(media_id: str, username: str):
44
  """Download specific story media by ID"""
45
- L = instaloader.Instaloader()
 
46
  try:
47
  profile = instaloader.Profile.from_username(L.context, username)
48
  except instaloader.exceptions.ProfileNotExistsException:
49
- raise HTTPException(404, "Profile not found")
50
-
51
- # Find the story item
52
- story_item = next((item for story in profile.get_stories()
53
- for item in story.get_items()
54
- if str(item.mediaid) == media_id), None)
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  if not story_item:
57
- raise HTTPException(404, "Story not found")
58
 
59
  try:
60
- response = requests.get(story_item.url, stream=True)
 
61
  response.raise_for_status()
62
  except requests.exceptions.RequestException:
63
- raise HTTPException(500, "Failed to fetch media")
 
 
 
64
 
65
  return StreamingResponse(
66
  response.iter_content(chunk_size=8192),
 
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
+ from functools import wraps
8
 
9
+ app = FastAPI(title="Instagram Stories API", docs_url=None, redoc_url=None)
 
10
 
11
+ # Load Instagram credentials from environment variables
12
  INSTAGRAM_USERNAME = os.getenv('INSTAGRAM_USERNAME')
13
  INSTAGRAM_PASSWORD = os.getenv('INSTAGRAM_PASSWORD')
14
 
15
+ # Configure Instaloader with session persistence
16
+ def get_instaloader():
17
+ L = instaloader.Instaloader()
18
+
19
+ # Try to load existing session
20
+ try:
21
+ L.load_session_from_file(INSTAGRAM_USERNAME)
22
+ except FileNotFoundError:
23
+ # If session doesn't exist, perform login
24
+ try:
25
+ L.login(INSTAGRAM_USERNAME, INSTAGRAM_PASSWORD)
26
+ L.save_session_to_file()
27
+ except instaloader.exceptions.BadCredentialsException:
28
+ raise HTTPException(
29
+ status_code=status.HTTP_401_UNAUTHORIZED,
30
+ detail="Invalid Instagram credentials"
31
+ )
32
+ except instaloader.exceptions.TwoFactorAuthRequiredException:
33
+ raise HTTPException(
34
+ status_code=status.HTTP_401_UNAUTHORIZED,
35
+ detail="Two-factor authentication required"
36
+ )
37
+
38
+ # Set request delay to avoid rate limits
39
+ L.request_timeout = 300
40
+ L.sleep = True
41
+ L.delay_seconds = 5 # Minimum 5 seconds between requests
42
+
43
+ return L
44
 
45
+ def handle_instagram_errors(func):
46
+ @wraps(func)
47
+ async def wrapper(*args, **kwargs):
48
+ try:
49
+ return await func(*args, **kwargs)
50
+ except instaloader.exceptions.QueryReturnedBadRequestException:
51
+ raise HTTPException(
52
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
53
+ detail="Instagram rate limit exceeded - try again later"
54
+ )
55
+ except instaloader.exceptions.ConnectionException:
56
+ raise HTTPException(
57
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
58
+ detail="Instagram connection failed - try again later"
59
+ )
60
+ except instaloader.exceptions.PrivateProfileNotFollowedException:
61
+ raise HTTPException(
62
+ status_code=status.HTTP_403_FORBIDDEN,
63
+ detail="Cannot access private profile"
64
+ )
65
+ return wrapper
66
 
67
+ @app.get("/stories/{username}")
68
+ @handle_instagram_errors
69
  async def get_stories(username: str):
70
+ """Retrieve metadata for Instagram stories (public or private if followed)"""
71
+ L = get_instaloader()
72
+
73
  try:
74
  profile = instaloader.Profile.from_username(L.context, username)
75
  except instaloader.exceptions.ProfileNotExistsException:
76
+ raise HTTPException(status.HTTP_404_NOT_FOUND, "Profile not found")
 
 
 
 
 
 
 
 
77
 
78
+ # Check if we can access stories
79
+ if not profile.has_public_story and not profile.is_followed_by_viewer:
80
+ raise HTTPException(
81
+ status.HTTP_403_FORBIDDEN,
82
+ "No accessible stories for this profile"
83
+ )
84
+
85
+ stories = []
86
+ try:
87
+ for story in L.get_stories(userids=[profile.userid]):
88
+ for item in story.get_items():
89
+ stories.append({
90
+ "id": str(item.mediaid),
91
+ "url": item.url,
92
+ "type": "video" if item.is_video else "image",
93
+ "timestamp": item.date_utc.isoformat(),
94
+ "views": item.view_count if item.is_video else None,
95
+ })
96
+ except instaloader.exceptions.QueryReturnedNotFoundException:
97
+ raise HTTPException(
98
+ status.HTTP_404_NOT_FOUND,
99
+ "Stories not found or expired"
100
+ )
101
+
102
+ if not stories:
103
+ raise HTTPException(
104
+ status.HTTP_404_NOT_FOUND,
105
+ "No active stories available"
106
+ )
107
 
108
+ return {"data": stories}
109
+
110
+ @app.get("/download/{media_id}")
111
+ @handle_instagram_errors
112
  async def download_story(media_id: str, username: str):
113
  """Download specific story media by ID"""
114
+ L = get_instaloader()
115
+
116
  try:
117
  profile = instaloader.Profile.from_username(L.context, username)
118
  except instaloader.exceptions.ProfileNotExistsException:
119
+ raise HTTPException(status.HTTP_404_NOT_FOUND, "Profile not found")
 
 
 
 
 
120
 
121
+ # Find the story item with retry logic
122
+ story_item = None
123
+ for _ in range(3): # Retry up to 3 times
124
+ try:
125
+ story_item = next(
126
+ (item for story in L.get_stories(userids=[profile.userid])
127
+ for item in story.get_items()
128
+ if str(item.mediaid) == media_id
129
+ ), None)
130
+ if story_item:
131
+ break
132
+ except instaloader.exceptions.QueryReturnedBadRequestException:
133
+ time.sleep(10) # Wait 10 seconds before retrying
134
+
135
  if not story_item:
136
+ raise HTTPException(status.HTTP_404_NOT_FOUND, "Story not found")
137
 
138
  try:
139
+ # Use Instaloader's context to maintain session cookies
140
+ response = L.context.get(story_item.url, stream=True)
141
  response.raise_for_status()
142
  except requests.exceptions.RequestException:
143
+ raise HTTPException(
144
+ status.HTTP_500_INTERNAL_SERVER_ERROR,
145
+ "Failed to fetch media content"
146
+ )
147
 
148
  return StreamingResponse(
149
  response.iter_content(chunk_size=8192),