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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superbrain-server",
3
- "version": "1.0.23",
3
+ "version": "1.0.25",
4
4
  "description": "1-Line Auto-Installer and Server Execution wrapper for SuperBrain",
5
5
  "main": "index.js",
6
6
  "bin": {
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}] Quota exhausted queued for automatic retry")
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="API quota exhausted. Your request has been queued for automatic retry in 24 hours."
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
- if data.provider.lower() not in valid_providers:
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
- if not data.username or not data.password:
1634
- raise HTTPException(status_code=400, detail="Username and password required")
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={data.username}\n")
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={data.username}\n")
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 {data.username}")
1715
+
1716
+ logger.info(f"📸 Instagram credentials updated for {username_to_use}")
1664
1717
 
1665
1718
  return {
1666
1719
  "success": True,
1667
- "username": data.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
- return False
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 → {GREEN}{BOLD}{public_url}{RESET} {DIM}({tunnel_type}){RESET}"
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 → {YELLOW}(failed to start ngrok and localtunnel){RESET}"
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 → {YELLOW}(failed to start localtunnel){RESET}"
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 ──────────────────────────────────────────