superbrain-server 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/api.py +69 -16
- package/payload/instagram/instagram_login.py +23 -1
- package/payload/start.py +3 -3
package/package.json
CHANGED
package/payload/api.py
CHANGED
|
@@ -570,13 +570,13 @@ async def analyze_instagram(request: AnalyzeRequest, token: str = Depends(verify
|
|
|
570
570
|
logger.warning(f"⚠️ [{shortcode}] main.py stderr:\n{stderr[:1000]}")
|
|
571
571
|
|
|
572
572
|
if returncode == 2:
|
|
573
|
-
# main.py detected quota exhaustion and queued item for retry.
|
|
573
|
+
# main.py detected quota exhaustion (Instagram block or AI) and queued item for retry.
|
|
574
574
|
# NOTE: Do NOT remove from queue here — main.py already called
|
|
575
575
|
# queue_for_retry() which set status='retry'. Removing would lose it.
|
|
576
|
-
logger.info(f"⏰ [{shortcode}]
|
|
576
|
+
logger.info(f"⏰ [{shortcode}] Rate limit or quota exhausted (often Instagram blocking the download). Your request has been queued for automatic retry in 24 hours.")
|
|
577
577
|
raise HTTPException(
|
|
578
578
|
status_code=202,
|
|
579
|
-
detail="
|
|
579
|
+
detail="Rate limit or quota exhausted (often Instagram blocking the download). Your request has been queued for automatic retry in 24 hours."
|
|
580
580
|
)
|
|
581
581
|
|
|
582
582
|
if returncode != 0:
|
|
@@ -1494,8 +1494,9 @@ class ProviderKeyUpdate(BaseModel):
|
|
|
1494
1494
|
api_key: str
|
|
1495
1495
|
|
|
1496
1496
|
class InstagramCredentials(BaseModel):
|
|
1497
|
-
username: str
|
|
1498
|
-
password: str
|
|
1497
|
+
username: Optional[str] = None
|
|
1498
|
+
password: Optional[str] = None
|
|
1499
|
+
sessionid: Optional[str] = None
|
|
1499
1500
|
|
|
1500
1501
|
def get_config_path(filename: str) -> Path:
|
|
1501
1502
|
"""Get path to config directory files."""
|
|
@@ -1548,16 +1549,36 @@ async def set_ai_provider_key(
|
|
|
1548
1549
|
- provider: groq, gemini, or openrouter
|
|
1549
1550
|
"""
|
|
1550
1551
|
from core.model_router import get_router
|
|
1552
|
+
import httpx
|
|
1551
1553
|
|
|
1552
1554
|
valid_providers = ["groq", "gemini", "openrouter"]
|
|
1553
|
-
|
|
1555
|
+
provider_slug = data.provider.lower()
|
|
1556
|
+
if provider_slug not in valid_providers:
|
|
1554
1557
|
raise HTTPException(
|
|
1555
1558
|
status_code=400,
|
|
1556
1559
|
detail=f"Invalid provider. Must be one of: {', '.join(valid_providers)}"
|
|
1557
1560
|
)
|
|
1558
1561
|
|
|
1559
1562
|
if not data.api_key or len(data.api_key.strip()) < 5:
|
|
1560
|
-
raise HTTPException(status_code=400, detail="Invalid API key")
|
|
1563
|
+
raise HTTPException(status_code=400, detail="Invalid API key format")
|
|
1564
|
+
|
|
1565
|
+
# Validate API key explicitly
|
|
1566
|
+
try:
|
|
1567
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
1568
|
+
if provider_slug == "groq":
|
|
1569
|
+
resp = await client.get("https://api.groq.com/openai/v1/models", headers={"Authorization": f"Bearer {data.api_key.strip()}"})
|
|
1570
|
+
if resp.status_code != 200:
|
|
1571
|
+
raise HTTPException(status_code=401, detail="Invalid Groq API Key")
|
|
1572
|
+
elif provider_slug == "gemini":
|
|
1573
|
+
resp = await client.get(f"https://generativelanguage.googleapis.com/v1beta/models?key={data.api_key.strip()}")
|
|
1574
|
+
if resp.status_code != 200:
|
|
1575
|
+
raise HTTPException(status_code=401, detail="Invalid Gemini API Key")
|
|
1576
|
+
elif provider_slug == "openrouter":
|
|
1577
|
+
resp = await client.get("https://openrouter.ai/api/v1/auth/key", headers={"Authorization": f"Bearer {data.api_key.strip()}"})
|
|
1578
|
+
if resp.status_code != 200:
|
|
1579
|
+
raise HTTPException(status_code=401, detail="Invalid OpenRouter API Key")
|
|
1580
|
+
except httpx.RequestError as e:
|
|
1581
|
+
raise HTTPException(status_code=502, detail=f"Network error validating API key: {e}")
|
|
1561
1582
|
|
|
1562
1583
|
router = get_router()
|
|
1563
1584
|
success = router.set_api_key(data.provider.lower(), data.api_key.strip())
|
|
@@ -1630,11 +1651,43 @@ async def set_instagram_credentials(
|
|
|
1630
1651
|
Set Instagram credentials.
|
|
1631
1652
|
- Requires API authentication
|
|
1632
1653
|
"""
|
|
1633
|
-
|
|
1634
|
-
|
|
1654
|
+
sessionid = getattr(data, "sessionid", None)
|
|
1655
|
+
|
|
1656
|
+
if not sessionid and (not data.username or not data.password):
|
|
1657
|
+
raise HTTPException(status_code=400, detail="Either login (username + password) OR Session ID is required")
|
|
1658
|
+
|
|
1659
|
+
username_to_use = data.username or "session_user"
|
|
1635
1660
|
|
|
1636
1661
|
api_keys_file = get_config_path(".api_keys")
|
|
1637
1662
|
|
|
1663
|
+
# Authenticate with Instagram first
|
|
1664
|
+
import instaloader
|
|
1665
|
+
L = instaloader.Instaloader()
|
|
1666
|
+
try:
|
|
1667
|
+
session_file = Path(__file__).parent / ".instaloader_session"
|
|
1668
|
+
if sessionid:
|
|
1669
|
+
L.context._session.cookies.set("sessionid", sessionid, domain=".instagram.com")
|
|
1670
|
+
L.context.username = username_to_use
|
|
1671
|
+
# To verify sessionid, attempt to get a profile or just save it directly.
|
|
1672
|
+
L.get_profile("instagram")
|
|
1673
|
+
else:
|
|
1674
|
+
L.login(data.username, data.password)
|
|
1675
|
+
username_to_use = data.username
|
|
1676
|
+
|
|
1677
|
+
L.save_session_to_file(str(session_file))
|
|
1678
|
+
logger.info(f"🍪 Instagram session successfully verified and saved for {username_to_use}")
|
|
1679
|
+
except instaloader.exceptions.BadCredentialsException:
|
|
1680
|
+
raise HTTPException(status_code=401, detail="Invalid username or password")
|
|
1681
|
+
except instaloader.exceptions.TwoFactorAuthRequiredException:
|
|
1682
|
+
raise HTTPException(status_code=401, detail="Two-factor auth blocked login. Please use Session ID cookie instead.")
|
|
1683
|
+
except instaloader.exceptions.ConnectionException as e:
|
|
1684
|
+
error_msg = str(e).lower()
|
|
1685
|
+
if "checkpoint" in error_msg or "challenge" in error_msg:
|
|
1686
|
+
raise HTTPException(status_code=401, detail="Instagram Checkpoint block. Please use Session ID cookie instead.")
|
|
1687
|
+
raise HTTPException(status_code=400, detail=f"Instagram connection error: {e}")
|
|
1688
|
+
except Exception as e:
|
|
1689
|
+
raise HTTPException(status_code=500, detail=f"Failed to authenticate with Instagram: {e}")
|
|
1690
|
+
|
|
1638
1691
|
# Read existing content
|
|
1639
1692
|
lines = []
|
|
1640
1693
|
username_found = False
|
|
@@ -1644,27 +1697,27 @@ async def set_instagram_credentials(
|
|
|
1644
1697
|
with open(api_keys_file, "r") as f:
|
|
1645
1698
|
for line in f:
|
|
1646
1699
|
if line.startswith("INSTAGRAM_USERNAME="):
|
|
1647
|
-
lines.append(f"INSTAGRAM_USERNAME={
|
|
1700
|
+
lines.append(f"INSTAGRAM_USERNAME={username_to_use}\n")
|
|
1648
1701
|
username_found = True
|
|
1649
1702
|
elif line.startswith("INSTAGRAM_PASSWORD="):
|
|
1650
|
-
lines.append(f"INSTAGRAM_PASSWORD={data.password}\n")
|
|
1703
|
+
lines.append(f"INSTAGRAM_PASSWORD={data.password}\n" if data.password else "INSTAGRAM_PASSWORD=\n")
|
|
1651
1704
|
password_found = True
|
|
1652
1705
|
else:
|
|
1653
1706
|
lines.append(line)
|
|
1654
1707
|
|
|
1655
1708
|
if not username_found:
|
|
1656
|
-
lines.append(f"INSTAGRAM_USERNAME={
|
|
1709
|
+
lines.append(f"INSTAGRAM_USERNAME={username_to_use}\n")
|
|
1657
1710
|
if not password_found:
|
|
1658
|
-
lines.append(f"INSTAGRAM_PASSWORD={data.password}\n")
|
|
1711
|
+
lines.append(f"INSTAGRAM_PASSWORD={data.password}\n" if data.password else "INSTAGRAM_PASSWORD=\n")
|
|
1659
1712
|
|
|
1660
1713
|
with open(api_keys_file, "w") as f:
|
|
1661
1714
|
f.writelines(lines)
|
|
1662
|
-
|
|
1663
|
-
logger.info(f"📸 Instagram credentials updated for {
|
|
1715
|
+
|
|
1716
|
+
logger.info(f"📸 Instagram credentials updated for {username_to_use}")
|
|
1664
1717
|
|
|
1665
1718
|
return {
|
|
1666
1719
|
"success": True,
|
|
1667
|
-
"username":
|
|
1720
|
+
"username": username_to_use,
|
|
1668
1721
|
"message": "Instagram credentials updated"
|
|
1669
1722
|
}
|
|
1670
1723
|
|
|
@@ -119,7 +119,29 @@ def setup_instaloader_session(username: str, password: str) -> bool:
|
|
|
119
119
|
return False
|
|
120
120
|
except Exception as e:
|
|
121
121
|
print(f" ✗ Login error: {e}")
|
|
122
|
-
|
|
122
|
+
|
|
123
|
+
# ── FALLBACK: Manual Session Cookie Import ──
|
|
124
|
+
print("\n ⚠️ Instagram has blocked this direct login attempt from this machine.")
|
|
125
|
+
print(" This happens often on cloud servers or new IPs (Checkpoint Required).")
|
|
126
|
+
print("\n 👉 To instantly bypass this, you can paste an active 'sessionid' cookie")
|
|
127
|
+
print(" from an Instagram account that is already logged in on your web browser.")
|
|
128
|
+
print("\n How to get it:")
|
|
129
|
+
print(" 1. Log into instagram.com on your computer's web browser.")
|
|
130
|
+
print(" 2. Open Developer Tools (F12) → Application/Storage → Cookies → instagram.com")
|
|
131
|
+
print(" 3. Copy the value of the 'sessionid' row.")
|
|
132
|
+
|
|
133
|
+
fallback = input("\n Do you want to manually enter a sessionid now? [y/N]: ").strip().lower()
|
|
134
|
+
if fallback == 'y':
|
|
135
|
+
sessionid = input(" Enter 'sessionid' cookie value: ").strip()
|
|
136
|
+
if sessionid:
|
|
137
|
+
# Inject the session into instaloader
|
|
138
|
+
L.context._session.cookies.set("sessionid", sessionid, domain=".instagram.com")
|
|
139
|
+
L.context.username = username
|
|
140
|
+
print(" ✓ Session cookie accepted and injected.")
|
|
141
|
+
else:
|
|
142
|
+
return False
|
|
143
|
+
else:
|
|
144
|
+
return False
|
|
123
145
|
|
|
124
146
|
if not L.context.is_logged_in:
|
|
125
147
|
print(" ✗ Login did not succeed.")
|
package/payload/start.py
CHANGED
|
@@ -1287,13 +1287,13 @@ def launch_backend():
|
|
|
1287
1287
|
tunnel_hint = ""
|
|
1288
1288
|
|
|
1289
1289
|
if public_url:
|
|
1290
|
-
tunnel_line = f" Public URL
|
|
1290
|
+
tunnel_line = f" Public URL → {GREEN}{BOLD}{public_url}{RESET} {DIM}({tunnel_type}){RESET}"
|
|
1291
1291
|
tunnel_hint = f" · public → {GREEN}{public_url}{RESET}"
|
|
1292
1292
|
elif NGROK_ENABLED.exists():
|
|
1293
|
-
tunnel_line = f" Public URL
|
|
1293
|
+
tunnel_line = f" Public URL → {YELLOW}(failed to start ngrok and localtunnel){RESET}"
|
|
1294
1294
|
tunnel_hint = f" · public → run manually: {DIM}ngrok http {PORT}{RESET}"
|
|
1295
1295
|
else:
|
|
1296
|
-
tunnel_line = f" Public URL
|
|
1296
|
+
tunnel_line = f" Public URL → {YELLOW}(failed to start localtunnel){RESET}"
|
|
1297
1297
|
tunnel_hint = f" · public → configure ngrok via {DIM}python start.py --reset{RESET} or ensure node/npx is installed"
|
|
1298
1298
|
|
|
1299
1299
|
# ── Generate and display QR code ──────────────────────────────────────────
|