superbrain-server 1.0.6 → 1.0.10

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.
Files changed (36) hide show
  1. package/package.json +2 -2
  2. package/payload/analyzers/__pycache__/__init__.cpython-311.pyc +0 -0
  3. package/payload/analyzers/__pycache__/audio_transcribe.cpython-311.pyc +0 -0
  4. package/payload/analyzers/__pycache__/caption.cpython-311.pyc +0 -0
  5. package/payload/analyzers/__pycache__/music_identifier.cpython-311.pyc +0 -0
  6. package/payload/analyzers/__pycache__/text_analyzer.cpython-311.pyc +0 -0
  7. package/payload/analyzers/__pycache__/visual_analyze.cpython-311.pyc +0 -0
  8. package/payload/analyzers/__pycache__/webpage_analyzer.cpython-311.pyc +0 -0
  9. package/payload/analyzers/__pycache__/youtube_analyzer.cpython-311.pyc +0 -0
  10. package/payload/api.py +46 -4
  11. package/payload/config/backend_id.txt +1 -0
  12. package/payload/config/localtunnel.log +86 -0
  13. package/payload/core/__pycache__/__init__.cpython-311.pyc +0 -0
  14. package/payload/core/__pycache__/category_manager.cpython-311.pyc +0 -0
  15. package/payload/core/__pycache__/database.cpython-311.pyc +0 -0
  16. package/payload/core/__pycache__/link_checker.cpython-311.pyc +0 -0
  17. package/payload/core/__pycache__/model_router.cpython-311.pyc +0 -0
  18. package/payload/core/model_router.py +12 -0
  19. package/payload/instagram/__pycache__/__init__.cpython-311.pyc +0 -0
  20. package/payload/instagram/__pycache__/instagram_downloader.cpython-311.pyc +0 -0
  21. package/payload/instagram/__pycache__/instagram_login.cpython-311.pyc +0 -0
  22. package/payload/start.py +5 -5
  23. package/payload/test_backend.py +241 -0
  24. package/payload/tests/__init__.py +0 -0
  25. package/payload/tests/__pycache__/__init__.cpython-311.pyc +0 -0
  26. package/payload/tests/__pycache__/test_api.cpython-311.pyc +0 -0
  27. package/payload/tests/__pycache__/test_db.cpython-311.pyc +0 -0
  28. package/payload/tests/__pycache__/test_sync_code.cpython-311.pyc +0 -0
  29. package/payload/tests/test_api.py +17 -0
  30. package/payload/tests/test_db.py +22 -0
  31. package/payload/tests/test_sync_code.py +65 -0
  32. package/payload/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  33. package/payload/utils/__pycache__/db_stats.cpython-311.pyc +0 -0
  34. package/payload/utils/__pycache__/manage_token.cpython-311.pyc +0 -0
  35. package/payload/fix.py +0 -66
  36. package/payload/fix2.py +0 -63
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superbrain-server",
3
- "version": "1.0.6",
3
+ "version": "1.0.10",
4
4
  "description": "1-Line Auto-Installer and Server Execution wrapper for SuperBrain",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -22,4 +22,4 @@
22
22
  "bin/",
23
23
  "payload/"
24
24
  ]
25
- }
25
+ }
package/payload/api.py CHANGED
@@ -289,7 +289,15 @@ class AnalysisResponse(BaseModel):
289
289
  @app.get("/")
290
290
  async def root():
291
291
  """API information and health check (no authentication required)"""
292
+
293
+ backend_id = "unknown"
294
+ backend_id_path = get_config_path("backend_id.txt")
295
+ if backend_id_path.exists():
296
+ backend_id = backend_id_path.read_text().strip()
297
+
292
298
  return {
299
+ "backendId": backend_id,
300
+
293
301
  "name": "SuperBrain Instagram Analyzer API",
294
302
  "version": "1.02",
295
303
  "status": "operational",
@@ -561,12 +569,46 @@ async def analyze_instagram(request: AnalyzeRequest, token: str = Depends(verify
561
569
  logger.warning(f"⚠️ [{shortcode}] main.py stderr:\n{stderr[:1000]}")
562
570
 
563
571
  if returncode == 2:
564
- retry_lines = [l.strip() for l in stdout.splitlines() if l.strip().startswith('?')]
565
- retry_msg = retry_lines[-1].replace('?', '').strip() if retry_lines else "API quota exhausted or rate limited. Queued for automatic retry in 24 hours."
566
- logger.info(f"? [{shortcode}] {retry_msg}")
572
+ # main.py detected quota exhaustion and queued item for retry.
573
+ # NOTE: Do NOT remove from queue here main.py already called
574
+ # queue_for_retry() which set status='retry'. Removing would lose it.
575
+ logger.info(f"⏰ [{shortcode}] Quota exhausted — queued for automatic retry")
567
576
  raise HTTPException(
568
577
  status_code=202,
569
- detail=retry_msg
578
+ detail="API quota exhausted. Your request has been queued for automatic retry in 24 hours."
579
+ )
580
+
581
+ if returncode != 0:
582
+ # Extract last meaningful error line from stdout for the error message
583
+ error_lines = [l.strip() for l in stdout.splitlines() if l.strip() and ('❌' in l or 'Error' in l or 'failed' in l.lower())]
584
+ error_detail = error_lines[-1] if error_lines else (stderr.strip()[:200] or "Analysis failed")
585
+ logger.error(f"❌ [{shortcode}] Analysis failed: {error_detail}")
586
+ logger.debug(f"[{shortcode}] stdout tail:\n{stdout[-800:]}")
587
+ raise HTTPException(
588
+ status_code=400,
589
+ detail=error_detail
590
+ )
591
+
592
+ logger.info(f"✅ [{shortcode}] Analysis complete! Fetching from database...")
593
+
594
+ # Get result from database — retry up to 4 times in case the SQLite write
595
+ # hasn't flushed yet (race condition between subprocess write and our read).
596
+ analysis = None
597
+ for _attempt in range(4):
598
+ analysis = db.check_cache(shortcode)
599
+ if analysis:
600
+ if _attempt > 0:
601
+ logger.info(f"🔄 [{shortcode}] Found in database on retry {_attempt}")
602
+ break
603
+ if _attempt < 3:
604
+ logger.warning(f"⏳ [{shortcode}] Not in DB yet (attempt {_attempt+1}/4), retrying in 1s…")
605
+ await asyncio.sleep(1)
606
+
607
+ if not analysis:
608
+ logger.error(f"❌ [{shortcode}] Not found in database after 4 attempts!")
609
+ raise HTTPException(
610
+ status_code=500,
611
+ detail="Analysis completed but result not found in database"
570
612
  )
571
613
 
572
614
  # Filter response
@@ -0,0 +1 @@
1
+ 3f491f84-184a-40ba-9660-b882b881c4d5
@@ -0,0 +1,86 @@
1
+
2
+ ===============================================================================
3
+ Welcome to localhost.run!
4
+
5
+ Follow your favourite reverse tunnel at [https://twitter.com/localhost_run].
6
+
7
+ To set up and manage custom domains go to https://admin.localhost.run/
8
+
9
+ More details on custom domains (and how to enable subdomains of your custom
10
+ domain) at https://localhost.run/docs/custom-domains
11
+
12
+ If you get a permission denied error check the faq for how to connect with a key or
13
+ create a free tunnel without a key at [http://localhost:3000/docs/faq#generating-an-ssh-key].
14
+
15
+ To explore using localhost.run visit the documentation site:
16
+ https://localhost.run/docs/
17
+
18
+ ===============================================================================
19
+
20
+ ** your connection id is 78ac0b8b-a8f4-4185-9da5-a446d15e032f, please mention it if you send me a message about an issue. **
21
+
22
+ authenticated as anonymous user
23
+ 14ebb2ac2ccab3.lhr.life tunneled with tls termination, https://14ebb2ac2ccab3.lhr.life
24
+ create an account and add your key for a longer lasting domain name. see https://localhost.run/docs/forever-free/ for more information.
25
+ Open your tunnel address on your mobile with this QR:
26
+
27
+                            
28
+                            
29
+                            
30
+                            
31
+                            
32
+                            
33
+                            
34
+                            
35
+                            
36
+                            
37
+                            
38
+                            
39
+                            
40
+                            
41
+                            
42
+                            
43
+                            
44
+                            
45
+                            
46
+                            
47
+                            
48
+                            
49
+                            
50
+                            
51
+                            
52
+                            
53
+                            
54
+
55
+ c3f3097e2d129a.lhr.life tunneled with tls termination, https://c3f3097e2d129a.lhr.life
56
+ create an account and add your key for a longer lasting domain name. see https://localhost.run/docs/forever-free/ for more information.
57
+ Open your tunnel address on your mobile with this QR:
58
+
59
+                            
60
+                            
61
+                            
62
+                            
63
+                            
64
+                            
65
+                            
66
+                            
67
+                            
68
+                            
69
+                            
70
+                            
71
+                            
72
+                            
73
+                            
74
+                            
75
+                            
76
+                            
77
+                            
78
+                            
79
+                            
80
+                            
81
+                            
82
+                            
83
+                            
84
+                            
85
+                            
86
+
@@ -663,6 +663,9 @@ class ModelRouter:
663
663
  try:
664
664
  self._refresh_openrouter_models()
665
665
  except Exception as e:
666
+ if "429" in str(e) or "quota" in str(e).lower():
667
+ raise RateLimitError("Quota limit hit")
668
+ raise e
666
669
  print(f"⚠️ OpenRouter auto-refresh error: {e}")
667
670
  time.sleep(OPENROUTER_FREE_CACHE_HOURS * 3600)
668
671
 
@@ -707,6 +710,9 @@ class ModelRouter:
707
710
  resp.raise_for_status()
708
711
  all_models = resp.json().get("data", [])
709
712
  except Exception as e:
713
+ if "429" in str(e) or "quota" in str(e).lower():
714
+ raise RateLimitError("Quota limit hit")
715
+ raise e
710
716
  print(f"⚠️ OpenRouter model discovery failed: {e}")
711
717
  return
712
718
 
@@ -1100,6 +1106,9 @@ class ModelRouter:
1100
1106
  return result
1101
1107
 
1102
1108
  except Exception as e:
1109
+ if "429" in str(e) or "quota" in str(e).lower():
1110
+ raise RateLimitError("Quota limit hit")
1111
+ raise e
1103
1112
  status = 429 if "429" in str(e) else 0
1104
1113
  self._record_failure(key, str(e), status_code=status)
1105
1114
  print(f" ✗ Failed ({type(e).__name__}), trying next …", flush=True)
@@ -1144,6 +1153,9 @@ class ModelRouter:
1144
1153
  return result
1145
1154
 
1146
1155
  except Exception as e:
1156
+ if "429" in str(e) or "quota" in str(e).lower():
1157
+ raise RateLimitError("Quota limit hit")
1158
+ raise e
1147
1159
  status = 429 if "429" in str(e) else 0
1148
1160
  self._record_failure(key, str(e), status_code=status)
1149
1161
  print(f" ✗ Failed ({type(e).__name__}), trying next …", flush=True)
package/payload/start.py CHANGED
@@ -1294,11 +1294,11 @@ def launch_backend_status():
1294
1294
  network_url = f"http://{local_ip}:5000"
1295
1295
 
1296
1296
  nl()
1297
- print(f" Local URL ? {CYAN}{local_url}{RESET}")
1298
- print(f" Network URL ? {CYAN}{network_url}{RESET}")
1299
- print(f" Public URL ? {CYAN}{url}{RESET} (localtunnel)")
1300
- print(f" API docs ? {CYAN}{local_url}/docs{RESET}")
1301
- print(f" Access Token ? {BOLD}{MAGENTA}{token}{RESET}")
1297
+ print(f" Local URL \u2192 {CYAN}{local_url}{RESET}")
1298
+ print(f" Network URL \u2192 {CYAN}{network_url}{RESET}")
1299
+ print(f" Public URL \u2192 {CYAN}{url}{RESET} (localtunnel)")
1300
+ print(f" API docs \u2192 {CYAN}{local_url}/docs{RESET}")
1301
+ print(f" Access Token \u2192 {BOLD}{MAGENTA}{token}{RESET}")
1302
1302
  nl()
1303
1303
 
1304
1304
  def main():
@@ -0,0 +1,241 @@
1
+ """
2
+ SuperBrain - Comprehensive Backend Test Suite
3
+ Tests all API endpoints against the running backend.
4
+ """
5
+ import requests
6
+ import sys
7
+ import json
8
+ import time
9
+
10
+ # Config
11
+ BASE_URL = "http://localhost:5000"
12
+ TOKEN = "4XVTLWWV"
13
+ HEADERS = {"X-API-Key": TOKEN, "Content-Type": "application/json"}
14
+
15
+ TEST_YT_URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
16
+
17
+ passed = 0
18
+ failed = 0
19
+ skipped = 0
20
+
21
+ def test(name, condition, detail=""):
22
+ global passed, failed
23
+ if condition:
24
+ passed += 1
25
+ print(f" [PASS] {name}")
26
+ else:
27
+ failed += 1
28
+ print(f" [FAIL] {name} -- {detail}")
29
+
30
+ def skip(name, reason):
31
+ global skipped
32
+ skipped += 1
33
+ print(f" [SKIP] {name} -- {reason}")
34
+
35
+ # Phase 1: Health & Auth
36
+ print("\n--- Phase 1: Health & Auth ---")
37
+
38
+ try:
39
+ r = requests.get(f"{BASE_URL}/status", timeout=5)
40
+ test("1.1 GET /status", r.status_code == 200 and r.json().get("status") == "online", f"status={r.status_code}")
41
+ except Exception as e:
42
+ test("1.1 GET /status", False, str(e))
43
+
44
+ try:
45
+ r = requests.get(f"{BASE_URL}/health", headers=HEADERS, timeout=5)
46
+ data = r.json()
47
+ test("1.2 GET /health (auth)", r.status_code == 200 and "database" in data, f"status={r.status_code}")
48
+ except Exception as e:
49
+ test("1.2 GET /health", False, str(e))
50
+
51
+ try:
52
+ r = requests.get(f"{BASE_URL}/health", headers={"X-API-Key": "WRONGTKN"}, timeout=5)
53
+ test("1.3 Auth rejection (wrong)", r.status_code == 401, f"status={r.status_code}")
54
+ except Exception as e:
55
+ test("1.3 Auth rejection", False, str(e))
56
+
57
+ try:
58
+ r = requests.get(f"{BASE_URL}/health", timeout=5)
59
+ test("1.4 Auth rejection (none)", r.status_code in [401, 403, 422], f"status={r.status_code}")
60
+ except Exception as e:
61
+ test("1.4 Auth rejection (none)", False, str(e))
62
+
63
+ # Phase 2: Read Endpoints
64
+ print("\n--- Phase 2: Read Endpoints ---")
65
+
66
+ try:
67
+ r = requests.get(f"{BASE_URL}/recent?limit=10", headers=HEADERS, timeout=10)
68
+ data = r.json()
69
+ test("2.1 GET /recent", r.status_code == 200 and "data" in data, f"status={r.status_code}")
70
+ print(f" found {len(data.get('data', []))} posts")
71
+ except Exception as e:
72
+ test("2.1 GET /recent", False, str(e))
73
+
74
+ try:
75
+ r = requests.get(f"{BASE_URL}/categories", headers=HEADERS, timeout=10)
76
+ data = r.json()
77
+ test("2.2 GET /categories", r.status_code == 200 and "categories" in data, f"status={r.status_code}")
78
+ print(f" found {len(data.get('categories', []))} categories")
79
+ except Exception as e:
80
+ test("2.2 GET /categories", False, str(e))
81
+
82
+ try:
83
+ r = requests.get(f"{BASE_URL}/queue-status", headers=HEADERS, timeout=10)
84
+ data = r.json()
85
+ test("2.3 GET /queue-status", r.status_code == 200 and "processing_count" in data, f"keys={list(data.keys())}")
86
+ print(f" processing={data.get('processing_count',0)}, queue={data.get('queue_count',0)}, retry={data.get('retry_count',0)}")
87
+ except Exception as e:
88
+ test("2.3 GET /queue-status", False, str(e))
89
+
90
+ try:
91
+ r = requests.get(f"{BASE_URL}/stats", headers=HEADERS, timeout=10)
92
+ test("2.4 GET /stats", r.status_code == 200, f"status={r.status_code}")
93
+ except Exception as e:
94
+ test("2.4 GET /stats", False, str(e))
95
+
96
+ try:
97
+ r = requests.get(f"{BASE_URL}/search?tags=travel&limit=5", headers=HEADERS, timeout=10)
98
+ test("2.5 GET /search?tags=travel", r.status_code == 200, f"status={r.status_code}")
99
+ except Exception as e:
100
+ test("2.5 GET /search", False, str(e))
101
+
102
+ try:
103
+ r = requests.get(f"{BASE_URL}/queue/retry", headers=HEADERS, timeout=10)
104
+ test("2.6 GET /queue/retry", r.status_code == 200, f"status={r.status_code}")
105
+ except Exception as e:
106
+ test("2.6 GET /queue/retry", False, str(e))
107
+
108
+ # Phase 3: Collections CRUD
109
+ print("\n--- Phase 3: Collections CRUD ---")
110
+
111
+ test_collection_id = None
112
+
113
+ try:
114
+ r = requests.get(f"{BASE_URL}/collections", headers=HEADERS, timeout=10)
115
+ data = r.json()
116
+ test("3.1 GET /collections", r.status_code == 200, f"status={r.status_code}")
117
+ print(f" found {len(data)} collections")
118
+ except Exception as e:
119
+ test("3.1 GET /collections", False, str(e))
120
+
121
+ try:
122
+ r = requests.post(f"{BASE_URL}/collections", headers=HEADERS, json={"id": "test-id", "name": "Test Collection", "icon": "test"}, timeout=10)
123
+ data = r.json()
124
+ test("3.2 POST /collections (create)", r.status_code == 200 and data.get("id"), f"status={r.status_code}")
125
+ test_collection_id = data.get("id")
126
+ print(f" created ID: {test_collection_id}")
127
+ except Exception as e:
128
+ test("3.2 POST /collections", False, str(e))
129
+
130
+ if test_collection_id:
131
+ try:
132
+ r = requests.post(f"{BASE_URL}/collections", headers=HEADERS, json={"id": test_collection_id, "name": "Updated Test", "icon": "ok"}, timeout=10)
133
+ test("3.3 POST /collections (update)", r.status_code == 200, f"status={r.status_code}")
134
+ except Exception as e:
135
+ test("3.3 POST /collections (update)", False, str(e))
136
+
137
+ try:
138
+ r = requests.delete(f"{BASE_URL}/collections/{test_collection_id}", headers=HEADERS, timeout=10)
139
+ test("3.4 DELETE /collections", r.status_code == 200, f"status={r.status_code}")
140
+ except Exception as e:
141
+ test("3.4 DELETE /collections", False, str(e))
142
+ else:
143
+ skip("3.3 PUT /collections", "no collection created")
144
+ skip("3.4 DELETE /collections", "no collection created")
145
+
146
+ try:
147
+ r = requests.get(f"{BASE_URL}/collections", headers=HEADERS, timeout=10)
148
+ data = r.json()
149
+ collections = data.get("data", [])
150
+ wl = next((c for c in collections if c.get("name") == "Watch Later"), None)
151
+ if wl:
152
+ r = requests.delete(f"{BASE_URL}/collections/{wl['id']}", headers=HEADERS, timeout=10)
153
+ test("3.5 Watch Later protection", r.status_code in [400, 403], f"status={r.status_code} (should be blocked)")
154
+ else:
155
+ skip("3.5 Watch Later protection", "no Watch Later found")
156
+ except Exception as e:
157
+ test("3.5 Watch Later protection", False, str(e))
158
+
159
+ # Phase 4: Settings
160
+ print("\n--- Phase 4: Settings ---")
161
+
162
+ try:
163
+ r = requests.get(f"{BASE_URL}/settings/ai-providers", headers=HEADERS, timeout=10)
164
+ test("4.1 GET /settings/ai-providers", r.status_code == 200, f"status={r.status_code}")
165
+ except Exception as e:
166
+ test("4.1 GET /settings/ai-providers", False, str(e))
167
+
168
+ try:
169
+ r = requests.post(f"{BASE_URL}/settings/ai-providers", headers=HEADERS,
170
+ json={"provider": "groq", "api_key": "gsk_test"}, timeout=10)
171
+ test("4.2 POST ai-providers (Groq)", r.status_code == 200, f"status={r.status_code}")
172
+ except Exception as e:
173
+ test("4.2 POST ai-providers", False, str(e))
174
+
175
+ try:
176
+ r = requests.get(f"{BASE_URL}/settings/instagram", headers=HEADERS, timeout=10)
177
+ test("4.3 GET /settings/instagram", r.status_code == 200, f"status={r.status_code}")
178
+ except Exception as e:
179
+ test("4.3 GET /settings/instagram", False, str(e))
180
+
181
+ try:
182
+ r = requests.post(f"{BASE_URL}/settings/instagram", headers=HEADERS,
183
+ json={"username": "testuser", "password": "testpass"}, timeout=10)
184
+ test("4.4 POST /settings/instagram", r.status_code == 200, f"status={r.status_code}")
185
+ except Exception as e:
186
+ test("4.4 POST /settings/instagram", False, str(e))
187
+
188
+ # Phase 5: Export/Import
189
+ print("\n--- Phase 5: Export/Import ---")
190
+
191
+ try:
192
+ r = requests.get(f"{BASE_URL}/export", headers=HEADERS, timeout=15)
193
+ test("5.1 GET /export", r.status_code == 200, f"status={r.status_code}")
194
+ ed = r.json()
195
+ print(f" exported {len(ed.get('posts', []))} posts, {len(ed.get('collections', []))} collections")
196
+ except Exception as e:
197
+ test("5.1 GET /export", False, str(e))
198
+
199
+ try:
200
+ r = requests.post(f"{BASE_URL}/import", headers=HEADERS,
201
+ json={"posts": [], "collections": [], "mode": "merge"}, timeout=15)
202
+ test("5.2 POST /import (merge, empty)", r.status_code == 200, f"status={r.status_code}")
203
+ except Exception as e:
204
+ test("5.2 POST /import", False, str(e))
205
+
206
+ # Phase 6: Analysis
207
+ print("\n--- Phase 6: Analysis (YouTube) ---")
208
+
209
+ try:
210
+ print(" submitting YouTube URL... (timeout 90s)")
211
+ r = requests.post(f"{BASE_URL}/analyze", headers=HEADERS,
212
+ json={"url": TEST_YT_URL}, timeout=90)
213
+ data = r.json()
214
+ if r.status_code == 200:
215
+ test("6.1 POST /analyze (YouTube)", data.get("success") == True, f"success={data.get('success')}")
216
+ if data.get("data"):
217
+ d = data["data"]
218
+ print(f" title: {d.get('title', 'N/A')[:60]}")
219
+ print(f" category: {d.get('category', 'N/A')}")
220
+ print(f" tags: {str(d.get('tags', [])[:5])}")
221
+ print(f" cached: {data.get('cached', False)}")
222
+ elif r.status_code == 202:
223
+ skip("6.1 POST /analyze (YouTube)", "queued for retry (quota)")
224
+ elif r.status_code == 503:
225
+ skip("6.1 POST /analyze (YouTube)", "503 server busy")
226
+ else:
227
+ test("6.1 POST /analyze (YouTube)", False, f"status={r.status_code}")
228
+ except requests.exceptions.Timeout:
229
+ skip("6.1 POST /analyze (YouTube)", "timed out (90s)")
230
+ except Exception as e:
231
+ test("6.1 POST /analyze (YouTube)", False, str(e))
232
+
233
+ # Summary
234
+ print("\n" + "=" * 50)
235
+ print(f" PASSED: {passed}")
236
+ print(f" FAILED: {failed}")
237
+ print(f" SKIPPED: {skipped}")
238
+ print(f" TOTAL: {passed + failed + skipped}")
239
+ print("=" * 50)
240
+
241
+ sys.exit(1 if failed > 0 else 0)
File without changes
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env python3
2
+ import requests
3
+ import json
4
+
5
+ # Read token
6
+ with open('token.txt', 'r') as f:
7
+ token = f.read().strip()
8
+
9
+ # Test the /recent endpoint
10
+ response = requests.get(
11
+ 'http://localhost:5000/recent?limit=10',
12
+ headers={'X-API-Key': token}
13
+ )
14
+
15
+ print(f"Status Code: {response.status_code}")
16
+ print(f"\nResponse:")
17
+ print(json.dumps(response.json(), indent=2))
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python3
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ # Ensure backend root is in sys.path
6
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
7
+
8
+ from core.database import get_db
9
+
10
+ db = get_db()
11
+ posts = db.get_recent(5)
12
+ print(f'Found {len(posts)} posts in database')
13
+ print()
14
+
15
+ for p in posts:
16
+ print(f"Shortcode: {p.get('shortcode', 'N/A')}")
17
+ print(f"Title: {p.get('title', 'N/A')}")
18
+ print(f"Username: {p.get('username', 'N/A')}")
19
+ print(f"URL: {p.get('url', 'N/A')}")
20
+ print(f"Category: {p.get('category', 'N/A')}")
21
+ print(f"Analyzed: {p.get('analyzed_at', 'N/A')}")
22
+ print("-" * 60)
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Legacy filename retained for compatibility.
4
+ This script now validates API key authentication behavior.
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from fastapi.testclient import TestClient
11
+
12
+ # Ensure backend root is in sys.path
13
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
14
+
15
+ from api import app, API_TOKEN, generate_api_token # noqa: E402
16
+
17
+
18
+ client = TestClient(app)
19
+
20
+
21
+ def test_generate_api_token_shape():
22
+ token = generate_api_token()
23
+ assert isinstance(token, str)
24
+ assert len(token) == 8
25
+ assert token.isalnum()
26
+
27
+
28
+ def test_ping_works_without_auth():
29
+ response = client.get('/ping')
30
+ assert response.status_code == 200
31
+
32
+
33
+ def test_queue_status_rejects_invalid_api_key():
34
+ response = client.get('/queue-status', headers={'X-API-Key': 'INVALID_TOKEN'})
35
+ assert response.status_code == 401
36
+
37
+
38
+ def test_queue_status_accepts_valid_api_key():
39
+ response = client.get('/queue-status', headers={'X-API-Key': API_TOKEN})
40
+ assert response.status_code == 200
41
+
42
+
43
+ def test_connect_endpoint_is_deprecated():
44
+ response = client.post('/connect', json={'api_key': API_TOKEN})
45
+ assert response.status_code == 410
46
+
47
+
48
+ if __name__ == '__main__':
49
+ print('Running API key auth tests...')
50
+ test_generate_api_token_shape()
51
+ print('PASS: API token generation shape')
52
+
53
+ test_ping_works_without_auth()
54
+ print('PASS: /ping unauthenticated access')
55
+
56
+ test_queue_status_rejects_invalid_api_key()
57
+ print('PASS: /queue-status rejects invalid API key')
58
+
59
+ test_queue_status_accepts_valid_api_key()
60
+ print('PASS: /queue-status accepts valid API key')
61
+
62
+ test_connect_endpoint_is_deprecated()
63
+ print('PASS: /connect deprecated behavior')
64
+
65
+ print('\nAll API key auth tests passed!')
package/payload/fix.py DELETED
@@ -1,66 +0,0 @@
1
- import sys, shutil
2
- path1 = 'D:/superbrain/backend/start.py'
3
- path2 = 'D:/superbrain/superbrain-cli/payload/start.py'
4
-
5
- status_code = '''
6
- def launch_backend_status():
7
- h1("SuperBrain Server Status")
8
- PORT = 5000
9
- token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "�"
10
- local_ip = _detect_local_ip()
11
-
12
- localtunnel_enabled = bool(shutil.which("npx") or shutil.which("npx.cmd"))
13
- localtunnel_url: str | None = None
14
- if localtunnel_enabled:
15
- localtunnel_url = _find_localtunnel_url_from_log()
16
-
17
- if localtunnel_url:
18
- tunnel_line = f" Public URL ? {GREEN}{BOLD}{localtunnel_url}{RESET} {DIM}(localtunnel){RESET}"
19
- tunnel_hint = f" � public ? {GREEN}{localtunnel_url}{RESET}"
20
- elif localtunnel_enabled:
21
- tunnel_line = f" Public URL ? {YELLOW}(running � URL in localtunnel.log){RESET}"
22
- tunnel_hint = f" � public ? run: {DIM}npx localtunnel --port {PORT}{RESET}"
23
- else:
24
- tunnel_line = ""
25
- tunnel_hint = f" � public ? install Node.js first, then run: {DIM}npx localtunnel --port {PORT}{RESET}"
26
-
27
- qr_url = localtunnel_url if localtunnel_url else f"http://{local_ip}:{PORT}"
28
- _display_connect_qr(qr_url, token)
29
-
30
- print(f\"\"\"
31
- {GREEN}{BOLD}Server Status{RESET}
32
-
33
- Local URL ? {CYAN}http://127.0.0.1:{PORT}{RESET}
34
- Network URL ? {CYAN}http://{local_ip}:{PORT}{RESET}
35
- {(tunnel_line + chr(10)) if tunnel_line else ''} API docs ? {CYAN}http://127.0.0.1:{PORT}/docs{RESET}
36
- Access Token ? {BOLD}{MAGENTA}{token}{RESET}
37
-
38
- {YELLOW}Mobile app setup:{RESET}
39
- {BOLD}Option A � Scan QR code:{RESET}
40
- 1. Open the app ? Settings ? tap the {BOLD}QR icon{RESET} ??
41
- 2. Scan the QR code shown above
42
-
43
- {BOLD}Option B � Manual setup:{RESET}
44
- 1. Go to app ? ? settings
45
- 2. Set {BOLD}Server URL{RESET} to:
46
- {tunnel_hint}
47
- � Same WiFi ? http://{local_ip}:{PORT}
48
- 3. Set {BOLD}Access Token{RESET} to: {BOLD}{MAGENTA}{token}{RESET}
49
- \"\"\")
50
- sys.exit(0)
51
-
52
- '''
53
-
54
- for path in [path1, path2]:
55
- with open(path, 'r', encoding='utf-8') as f:
56
- content = f.read()
57
-
58
- if 'def launch_backend_status()' not in content:
59
- content = content.replace('def main():', status_code + 'def main():')
60
-
61
- if 'status_mode = "--status" in sys.argv' not in content:
62
- content = content.replace(' reset_mode = "--reset" in sys.argv', ' status_mode = "--status" in sys.argv\\n if status_mode:\\n launch_backend_status()\\n return\\n\\n reset_mode = "--reset" in sys.argv')
63
-
64
- with open(path, 'w', encoding='utf-8') as f:
65
- f.write(content)
66
- print("Done fixing start.py!")
package/payload/fix2.py DELETED
@@ -1,63 +0,0 @@
1
- import sys, shutil
2
- path1 = 'D:/superbrain/backend/start.py'
3
- path2 = 'D:/superbrain/superbrain-cli/payload/start.py'
4
-
5
- status_code = '''
6
- def launch_backend_status():
7
- h1("SuperBrain Server Status")
8
- PORT = 5000
9
- token = TOKEN_FILE.read_text().strip() if TOKEN_FILE.exists() else "�"
10
- local_ip = _detect_local_ip()
11
-
12
- localtunnel_enabled = bool(shutil.which("npx") or shutil.which("npx.cmd"))
13
- localtunnel_url: str | None = None
14
- if localtunnel_enabled:
15
- localtunnel_url = _find_localtunnel_url_from_log()
16
-
17
- if localtunnel_url:
18
- tunnel_line = f" Public URL ? {GREEN}{BOLD}{localtunnel_url}{RESET} {DIM}(localtunnel){RESET}"
19
- tunnel_hint = f" � public ? {GREEN}{localtunnel_url}{RESET}"
20
- elif localtunnel_enabled:
21
- tunnel_line = f" Public URL ? {YELLOW}(running � URL in localtunnel.log){RESET}"
22
- tunnel_hint = f" � public ? run: {DIM}npx localtunnel --port {PORT}{RESET}"
23
- else:
24
- tunnel_line = ""
25
- tunnel_hint = f" � public ? install Node.js first, then run: {DIM}npx localtunnel --port {PORT}{RESET}"
26
-
27
- qr_url = localtunnel_url if localtunnel_url else f"http://{local_ip}:{PORT}"
28
- _display_connect_qr(qr_url, token)
29
-
30
- print(f\"\"\"
31
- {GREEN}{BOLD}Server Status{RESET}
32
-
33
- Local URL ? {CYAN}http://127.0.0.1:{PORT}{RESET}
34
- Network URL ? {CYAN}http://{local_ip}:{PORT}{RESET}
35
- {(tunnel_line + chr(10)) if tunnel_line else ''} API docs ? {CYAN}http://127.0.0.1:{PORT}/docs{RESET}
36
- Access Token ? {BOLD}{MAGENTA}{token}{RESET}
37
-
38
- {YELLOW}Mobile app setup:{RESET}
39
- {BOLD}Option A � Scan QR code:{RESET}
40
- 1. Open the app ? Settings ? tap the {BOLD}QR icon{RESET} ??
41
- 2. Scan the QR code shown above
42
-
43
- {BOLD}Option B � Manual setup:{RESET}
44
- 1. Go to app ? ? settings
45
- 2. Set {BOLD}Server URL{RESET} to:
46
- {tunnel_hint}
47
- � Same WiFi ? http://{local_ip}:{PORT}
48
- 3. Set {BOLD}Access Token{RESET} to: {BOLD}{MAGENTA}{token}{RESET}
49
- \"\"\")
50
- sys.exit(0)
51
-
52
- '''
53
-
54
- for path in [path1, path2]:
55
- with open(path, 'r', encoding='utf-8') as f:
56
- content = f.read()
57
-
58
- if 'def launch_backend_status()' not in content:
59
- content = content.replace('def main():', status_code + 'def main():')
60
-
61
- with open(path, 'w', encoding='utf-8') as f:
62
- f.write(content)
63
- print("Done fixing start.py again!")