Skip to content

Commit 9b1688b

Browse files
rootclaude
andcommitted
Fix rate limiting: use session, modern UA, retry, and bug fixes
- Use requests.Session for connection/cookie reuse across all API calls - Update User-Agent to modern Instagram Android string (was broken "iphone_ua" literal) - Add standard headers (X-IG-App-ID, X-IG-Device-ID, X-IG-Connection-Type) - Add delay between sequential API requests to avoid rate limiting - Pass session ID to advanced_lookup() (was unauthenticated) - Fix searchType default parameter bug ("username" or "id" → "username") - Add retry with exponential backoff on 429 responses - Fix bare except clause, add 401 handling, catch KeyError/TypeError Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d999671 commit 9b1688b

1 file changed

Lines changed: 76 additions & 35 deletions

File tree

toutatis/core.py

Lines changed: 76 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import argparse
2+
import time
3+
import uuid
24
import requests
35
from urllib.parse import quote_plus
46
from json import dumps, decoder
@@ -10,28 +12,63 @@
1012
)
1113
import pycountry
1214

15+
USER_AGENT = "Instagram 317.0.0.34.109 Android (31/12; 420dpi; 1080x2276; samsung; SM-G991B; o1s; exynos2100)"
16+
IG_APP_ID = "936619743392459"
17+
COMMON_HEADERS = {
18+
"User-Agent": USER_AGENT,
19+
"X-IG-App-ID": IG_APP_ID,
20+
"X-IG-Connection-Type": "WiFi",
21+
"Accept-Language": "en-US,en;q=0.9",
22+
"Accept-Encoding": "gzip, deflate",
23+
}
24+
MAX_RETRIES = 3
25+
RETRY_BASE_DELAY = 2
26+
27+
28+
def _create_session(sessionId):
29+
session = requests.Session()
30+
session.headers.update(COMMON_HEADERS)
31+
session.cookies.set("sessionid", sessionId, domain=".instagram.com")
32+
session.headers["X-IG-Device-ID"] = str(uuid.uuid4())
33+
return session
34+
35+
36+
def _request_with_retry(method, session, url, **kwargs):
37+
for attempt in range(MAX_RETRIES):
38+
response = method(url, **kwargs)
39+
if response.status_code == 429:
40+
delay = RETRY_BASE_DELAY ** (attempt + 1)
41+
print(f"Rate limited, retrying in {delay}s... (attempt {attempt + 1}/{MAX_RETRIES})")
42+
time.sleep(delay)
43+
continue
44+
return response
45+
return response
46+
1347

14-
def getUserId(username, sessionsId):
15-
headers = {"User-Agent": "iphone_ua", "x-ig-app-id": "936619743392459"}
16-
api = requests.get(
48+
def getUserId(username, session):
49+
response = _request_with_retry(
50+
session.get, session,
1751
f'https://i.instagram.com/api/v1/users/web_profile_info/?username={username}',
18-
headers=headers,
19-
cookies={'sessionid': sessionsId}
2052
)
21-
try:
22-
if api.status_code == 404:
23-
return {"id": None, "error": "User not found"}
53+
if response.status_code == 404:
54+
return {"id": None, "error": "User not found"}
55+
if response.status_code == 401:
56+
return {"id": None, "error": "Invalid or expired session ID"}
57+
if response.status_code == 429:
58+
return {"id": None, "error": "Rate limit"}
2459

25-
id = api.json()["data"]['user']['id']
26-
return {"id": id, "error": None}
60+
try:
61+
user_id = response.json()["data"]['user']['id']
62+
return {"id": user_id, "error": None}
63+
except (decoder.JSONDecodeError, KeyError, TypeError):
64+
return {"id": None, "error": f"Rate limit (status {response.status_code})"}
2765

28-
except decoder.JSONDecodeError:
29-
return {"id": None, "error": "Rate limit"}
3066

67+
def getInfo(search, sessionId, searchType="username"):
68+
session = _create_session(sessionId)
3169

32-
def getInfo(search, sessionId, searchType="username" or "id"):
3370
if searchType == "username":
34-
data = getUserId(search, sessionId)
71+
data = getUserId(search, session)
3572
if data["error"]:
3673
return data
3774
userId = data["id"]
@@ -41,12 +78,15 @@ def getInfo(search, sessionId, searchType="username" or "id"):
4178
except ValueError:
4279
return {"user": None, "error": "Invalid ID"}
4380

81+
time.sleep(1.5)
82+
4483
try:
45-
response = requests.get(
84+
response = _request_with_retry(
85+
session.get, session,
4686
f'https://i.instagram.com/api/v1/users/{userId}/info/',
47-
headers={'User-Agent': 'Instagram 64.0.0.14.96'},
48-
cookies={'sessionid': sessionId}
4987
)
88+
if response.status_code == 401:
89+
return {"user": None, "error": "Invalid or expired session ID"}
5090
if response.status_code == 429:
5191
return {"user": None, "error": "Rate limit"}
5292

@@ -57,40 +97,40 @@ def getInfo(search, sessionId, searchType="username" or "id"):
5797
return {"user": None, "error": "Not found"}
5898

5999
info_user["userID"] = userId
100+
info_user["_session"] = session
60101
return {"user": info_user, "error": None}
61102

62103
except requests.exceptions.RequestException:
63104
return {"user": None, "error": "Not found"}
64105

65106

66-
def advanced_lookup(username):
107+
def advanced_lookup(username, session):
67108
"""
68109
Post to get obfuscated login infos
69110
"""
70111
data = "signed_body=SIGNATURE." + quote_plus(dumps(
71112
{"q": username, "skip_recovery": "1"},
72113
separators=(",", ":")
73114
))
74-
api = requests.post(
115+
116+
time.sleep(1.5)
117+
118+
response = _request_with_retry(
119+
session.post, session,
75120
'https://i.instagram.com/api/v1/users/lookup/',
76121
headers={
77-
"Accept-Language": "en-US",
78-
"User-Agent": "Instagram 101.0.0.15.120",
79122
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
80-
"X-IG-App-ID": "124024574287414",
81-
"Accept-Encoding": "gzip, deflate",
82123
"Host": "i.instagram.com",
83-
# "X-FB-HTTP-Engine": "Liger",
84124
"Connection": "keep-alive",
85125
"Content-Length": str(len(data))
86126
},
87127
data=data
88128
)
89129

90130
try:
91-
return ({"user": api.json(), "error": None})
131+
return {"user": response.json(), "error": None}
92132
except decoder.JSONDecodeError:
93-
return ({"user": None, "error": "rate limit"})
133+
return {"user": None, "error": "rate limit"}
94134

95135

96136
def main():
@@ -108,6 +148,7 @@ def main():
108148
if not infos.get("user"):
109149
exit(infos["error"])
110150

151+
session = infos["user"].pop("_session", None)
111152
infos = infos["user"]
112153

113154
print("Informations about : " + infos["username"])
@@ -119,14 +160,14 @@ def main():
119160
print(
120161
"Follower : " + str(infos["follower_count"]) + " | Following : " + str(infos["following_count"]))
121162
print("Number of posts : " + str(infos["media_count"]))
122-
# print("Number of tag in posts : "+str(infos["following_tag_count"]))
123163
if infos["external_url"]:
124164
print("External url : " + infos["external_url"])
125-
print("IGTV posts : " + str(infos["total_igtv_videos"]))
126-
print("Biography : " + (f"""\n{" " * 25}""").join(infos["biography"].split("\n")))
127-
print("Linked WhatsApp : " + str(infos["is_whatsapp_linked"]))
128-
print("Memorial Account : " + str(infos["is_memorialized"]))
129-
print("New Instagram user : " + str(infos["is_new_to_instagram"]))
165+
if "total_igtv_videos" in infos:
166+
print("IGTV posts : " + str(infos["total_igtv_videos"]))
167+
print("Biography : " + (f"""\n{" " * 25}""").join(infos.get("biography", "").split("\n")))
168+
print("Linked WhatsApp : " + str(infos.get("is_whatsapp_linked", "N/A")))
169+
print("Memorial Account : " + str(infos.get("is_memorialized", "N/A")))
170+
print("New Instagram user : " + str(infos.get("is_new_to_instagram", "N/A")))
130171

131172
if "public_email" in infos.keys():
132173
if infos["public_email"]:
@@ -140,11 +181,11 @@ def main():
140181
countrycode = region_code_for_country_code(pn.country_code)
141182
country = pycountry.countries.get(alpha_2=countrycode)
142183
phonenr = phonenr + " ({}) ".format(country.name)
143-
except: # except what ??
144-
pass # pass what ??
184+
except (phonenumbers.NumberParseException, AttributeError):
185+
pass
145186
print("Public Phone number : " + phonenr)
146187

147-
other_infos = advanced_lookup(infos["username"])
188+
other_infos = advanced_lookup(infos["username"], session)
148189

149190
if other_infos["error"] == "rate limit":
150191
print("Rate limit please wait a few minutes before you try again")

0 commit comments

Comments
 (0)