@fudrouter/fsrouter 0.6.91 → 0.6.93

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 (57) hide show
  1. package/dist/open-sse/handlers/chatCore.js +16 -0
  2. package/dist/routes/oauth/[provider]/[action]/route.js +6 -0
  3. package/package.json +10 -2
  4. package/public/.vite/manifest.json +89 -89
  5. package/public/assets/EndpointPageClient.kiCCiZeY.js +6 -0
  6. package/public/assets/Loading.BUhrzmZs.js +1 -0
  7. package/public/assets/NoAuthProxyCard.BI22GO9q.js +89 -0
  8. package/public/assets/index.DZiGd5-F.js +89 -0
  9. package/public/assets/page.1oqivCfq.js +1 -0
  10. package/public/assets/page.B5R9sdV7.js +1 -0
  11. package/public/assets/page.B5x2tCz5.js +1 -0
  12. package/public/assets/page.BFjNdqva.js +1 -0
  13. package/public/assets/page.BGs8R5av.js +1 -0
  14. package/public/assets/page.BLO61tLD.js +2 -0
  15. package/public/assets/page.BLWAx8lB.js +1 -0
  16. package/public/assets/page.BMcCQehz.js +6 -0
  17. package/public/assets/page.BS1sQouT.js +1 -0
  18. package/public/assets/page.BUWLUkXc.js +1 -0
  19. package/public/assets/page.BWaWJ_9t.js +1 -0
  20. package/public/assets/page.BYhmIkhw.js +1 -0
  21. package/public/assets/page.Buhl7l9y.js +1 -0
  22. package/public/assets/page.CBlkUp7t.js +1 -0
  23. package/public/assets/page.CG2arj7j.js +4 -0
  24. package/public/assets/page.CLgmM6Q_.js +5 -0
  25. package/public/assets/page.CLu_guvG.js +1 -0
  26. package/public/assets/page.COCdcdEM.js +1 -0
  27. package/public/assets/page.C_zhg1wq.js +1 -0
  28. package/public/assets/page.CtaqVBbT.js +6 -0
  29. package/public/assets/page.Cx0GMnA6.js +5 -0
  30. package/public/assets/page.D47UJLch.js +2 -0
  31. package/public/assets/page.D9ZFXJ3P.js +1 -0
  32. package/public/assets/page.D9kdvCh4.js +1 -0
  33. package/public/assets/page.DO1WoRkx.js +23 -0
  34. package/public/assets/page.DU201OUE.js +1 -0
  35. package/public/assets/page.DXxD-0Vl.js +31 -0
  36. package/public/assets/page.DaJXSZm4.js +1 -0
  37. package/public/assets/page.Dkeyd3nc.js +1 -0
  38. package/public/assets/page.DwJ35Irb.js +1 -0
  39. package/public/assets/page.DzLEPjRK.js +64 -0
  40. package/public/assets/page.VAojhnMr.js +1 -0
  41. package/public/assets/page.WBkwLufg.js +1 -0
  42. package/public/assets/page.laQQR26l.js +1 -0
  43. package/public/assets/page.nzF601n5.js +1 -0
  44. package/public/assets/page.tzSMuTXr.js +1 -0
  45. package/public/assets/page.xCigoHGB.js +1 -0
  46. package/public/index.html +4 -4
  47. package/scripts/postinstall.cjs +90 -0
  48. package/src/automation/cf_token_via_session.py +0 -158
  49. package/src/automation/cloudflare_signup.py +0 -3689
  50. package/src/automation/codebuddy_signup.py +0 -636
  51. package/src/automation/flashloop_signup.py +0 -338
  52. package/src/automation/kimi_signup.py +0 -401
  53. package/src/automation/leonardo_signup.py +0 -902
  54. package/src/automation/openvecta_signup.py +0 -652
  55. package/src/automation/qoder_signup.py +0 -323
  56. package/src/automation/test_proxy.py +0 -86
  57. package/src/automation/weavy_signup.py +0 -1964
@@ -1,323 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Standalone script to automate Qoder login via Google OAuth and retrieve device token.
3
- Outputs step logs and final results to stdout as JSON lines.
4
- """
5
-
6
- import sys
7
- import json
8
- import argparse
9
- import time
10
- import logging
11
- import re
12
- import uuid
13
- import hashlib
14
- import base64
15
- import urllib.request
16
- import urllib.parse
17
- import os
18
- from pathlib import Path
19
-
20
- # Setup simple stdout logger to not conflict with JSON line prints
21
- logging.basicConfig(level=logging.WARNING)
22
- logger = logging.getLogger("qoder_signup")
23
-
24
- class QoderAutomationError(RuntimeError):
25
- pass
26
-
27
- def log_step(msg, *args):
28
- txt = msg % args if args else msg
29
- sys.stdout.write(json.dumps({"step": txt}, ensure_ascii=False) + "\n")
30
- sys.stdout.flush()
31
-
32
- def safe_email_to_dirname(email: str) -> str:
33
- cleaned = (email or "").strip().lower()
34
- cleaned = cleaned.replace("@", "_at_")
35
- cleaned = re.sub(r"[^a-z0-9._-]+", "_", cleaned)
36
- cleaned = cleaned.strip("._-")
37
- return cleaned or "account"
38
-
39
- def base64Url(buf: bytes) -> str:
40
- return base64.urlsafe_b64encode(buf).decode("utf-8").replace("=", "")
41
-
42
- def poll_device_token(nonce: str, verifier: str) -> dict:
43
- url = f"https://openapi.qoder.sh/api/v1/deviceToken/poll?nonce={urllib.parse.quote(nonce)}&verifier={urllib.parse.quote(verifier)}&challenge_method=S256"
44
- headers = {
45
- "Accept": "application/json",
46
- "User-Agent": "Go-http-client/2.0",
47
- }
48
-
49
- # Poll up to 60 seconds
50
- deadline = time.time() + 60.0
51
- while time.time() < deadline:
52
- req = urllib.request.Request(url, headers=headers, method="GET")
53
- try:
54
- with urllib.request.urlopen(req, timeout=10) as resp:
55
- status = resp.status
56
- if status == 200:
57
- res_data = json.loads(resp.read().decode("utf-8"))
58
- if "token" in res_data:
59
- return res_data
60
- except urllib.error.HTTPError as he:
61
- # 202/404 means authorization pending
62
- if he.code in (202, 404):
63
- pass
64
- else:
65
- log_step(f"Polling HTTP Error: {he.code}")
66
- except Exception as e:
67
- log_step(f"Polling Network Error: {e}")
68
-
69
- time.sleep(2.0)
70
- return {}
71
-
72
- def fetch_user_info(access_token: str) -> dict:
73
- url = "https://openapi.qoder.sh/api/v1/userinfo"
74
- headers = {
75
- "Authorization": f"Bearer {access_token}",
76
- "Accept": "application/json",
77
- "User-Agent": "Go-http-client/2.0",
78
- }
79
- req = urllib.request.Request(url, headers=headers, method="GET")
80
- try:
81
- with urllib.request.urlopen(req, timeout=10) as resp:
82
- if resp.status == 200:
83
- return json.loads(resp.read().decode("utf-8"))
84
- except Exception:
85
- pass
86
- return {}
87
-
88
- def main():
89
- parser = argparse.ArgumentParser(description="Qoder Google auto-login tool")
90
- parser.add_argument("--email", required=True)
91
- parser.add_argument("--password", required=True)
92
- parser.add_argument("--proxy-server")
93
- parser.add_argument("--proxy-user")
94
- parser.add_argument("--proxy-pass")
95
- parser.add_argument("--profiles-dir", required=True)
96
- parser.add_argument("--headless", action="store_true", default=False)
97
- args = parser.parse_args()
98
-
99
- # Step 1: Initiate local device flow PKCE/nonce parameters
100
- verifier = base64Url(os.urandom(32))
101
- challenge = base64Url(hashlib.sha256(verifier.encode("utf-8")).digest())
102
- nonce = str(uuid.uuid4())
103
- machine_id = str(uuid.uuid4())
104
-
105
- client_id = "e883ade2-e6e3-4d6d-adf7-f92ceff5fdcb"
106
- url = f"https://qoder.com/device/selectAccounts?challenge={challenge}&challenge_method=S256&machine_id={machine_id}&nonce={nonce}&client_id={client_id}"
107
- log_step(f"Meluncurkan flow otorisasi Qoder...")
108
-
109
- # Step 2: Set up profile dir
110
- profiles_root = Path(args.profiles_dir)
111
- profiles_root.mkdir(parents=True, exist_ok=True)
112
- profile_dir = profiles_root / safe_email_to_dirname(args.email)
113
- profile_dir.mkdir(parents=True, exist_ok=True)
114
-
115
- proxy_dict = None
116
- if args.proxy_server:
117
- proxy_dict = {"server": args.proxy_server}
118
- if args.proxy_user:
119
- proxy_dict["username"] = args.proxy_user
120
- if args.proxy_pass:
121
- proxy_dict["password"] = args.proxy_pass
122
-
123
- try:
124
- from camoufox.sync_api import Camoufox
125
- except ImportError:
126
- sys.stdout.write(json.dumps({"status": "error", "message": "Camoufox tidak terinstall di environment python."}) + "\n")
127
- sys.exit(1)
128
-
129
- kwargs = dict(
130
- headless=args.headless,
131
- persistent_context=True, no_viewport=True,
132
- user_data_dir=str(profile_dir),
133
- humanize=True,
134
- geoip=True,
135
- locale="en-US",
136
- os=("windows", "macos", "linux"),
137
- window=(1280, 800),
138
- firefox_user_prefs={
139
- "network.trr.mode": 5,
140
- }
141
- )
142
- if proxy_dict:
143
- kwargs["proxy"] = {k: v for k, v in proxy_dict.items() if v}
144
-
145
- log_step("Meluncurkan browser...")
146
- token_data = {}
147
- try:
148
- with Camoufox(**kwargs) as browser:
149
- context = getattr(browser, "context", None) or browser
150
- page = context.new_page()
151
-
152
- log_step("Membuka halaman verifikasi Qoder...")
153
- page.goto(url, wait_until="domcontentloaded", timeout=45000)
154
-
155
- # Wait for either sign-in or authorization page to render
156
- login_needed = False
157
- for _ in range(30):
158
- cur_url = page.url.lower()
159
- if "sign-in" in cur_url or page.locator("a:has-text('Sign in with Google')").count() > 0:
160
- login_needed = True
161
- break
162
- # Authorize / Confirm page or success already loaded
163
- if "success" in cur_url or page.locator("button:has-text('Confirm'), button:has-text('Authorize'), button:has-text('Allow')").count() > 0 or "success" in (page.title() or "").lower():
164
- break
165
- time.sleep(0.5)
166
-
167
- if login_needed:
168
- google_sel = "a:has-text('Sign in with Google'), a[href*='/sso/login/google']"
169
- try:
170
- page.wait_for_selector(google_sel, timeout=10000)
171
- google_link = page.locator(google_sel).first
172
- log_step("Mengklik tombol Google Sign-in...")
173
- google_link.click(force=True)
174
- time.sleep(5.0)
175
- except Exception as e:
176
- log_step(f"Tombol Google Sign-in tidak ditemukan: {e}")
177
-
178
- # Handle Google login pages
179
- google_deadline = time.time() + 60.0
180
- filled_email = False
181
- filled_pass = False
182
- clicked_account = False
183
-
184
- GOOGLE_EMAIL_SELECTORS = ["input[type='email']", "input[name='identifier']", "input#identifierId"]
185
- GOOGLE_PASSWORD_SELECTORS = ["input[type='password'][name='Passwd']", "input[type='password']", "input[name='password']"]
186
-
187
- while time.time() < google_deadline:
188
- cur_url = page.url.lower()
189
- if "google.com" not in cur_url and "googleusercontent.com" not in cur_url:
190
- break
191
-
192
- # Disabled speedbump check
193
- if "speedbump/disabled" in cur_url or "disabled" in cur_url or "disabled" in (page.title() or "").lower():
194
- raise QoderAutomationError(f"Akun Google ({args.email}) dinonaktifkan oleh Google.")
195
-
196
- # 1. Chooser page / Select account
197
- if not clicked_account and ("accountchooser" in cur_url or "Choose an account" in (page.title() or "")):
198
- clicked = page.evaluate(f"""
199
- () => {{
200
- const email = '{args.email}';
201
- const lis = document.querySelectorAll('li[data-identifier], li.JDAKTe');
202
- for (const li of lis) {{
203
- const id = li.getAttribute('data-identifier') || '';
204
- if (id === email || li.textContent.includes(email)) {{
205
- li.click(); return true;
206
- }}
207
- }}
208
- const all = document.querySelectorAll('[data-email], [data-identifier]');
209
- for (const el of all) {{
210
- const v = el.getAttribute('data-email') || el.getAttribute('data-identifier') || '';
211
- if (v === email) {{ el.click(); return true; }}
212
- }}
213
- return false;
214
- }}
215
- """)
216
- if clicked:
217
- log_step("JS klik akun di chooser...")
218
- clicked_account = True
219
- time.sleep(3.0)
220
- continue
221
-
222
- # 2. Email input
223
- if not filled_email:
224
- email_in = page.locator(",".join(GOOGLE_EMAIL_SELECTORS)).first
225
- if email_in.count() > 0 and email_in.is_visible(timeout=500):
226
- log_step("Mengisi email Google...")
227
- email_in.fill(args.email)
228
- page.keyboard.press("Enter")
229
- filled_email = True
230
- time.sleep(3.0)
231
- continue
232
-
233
- # 3. Password input
234
- if filled_email and not filled_pass:
235
- pass_in = page.locator(",".join(GOOGLE_PASSWORD_SELECTORS)).first
236
- if pass_in.count() > 0 and pass_in.is_visible(timeout=500):
237
- log_step("Mengisi password Google...")
238
- pass_in.fill(args.password)
239
- page.keyboard.press("Enter")
240
- filled_pass = True
241
- time.sleep(5.0)
242
- continue
243
-
244
- # 4. Workspace terms agreement
245
- understand_btn = page.locator("button:has-text('I understand'), button:has-text('I Understand')").first
246
- if understand_btn.count() > 0 and understand_btn.is_visible(timeout=500):
247
- log_step("Klik 'I understand' di terms...")
248
- understand_btn.click(timeout=3000)
249
- time.sleep(2.0)
250
- continue
251
-
252
- # 5. Consent Continue/Allow buttons
253
- clicked_btn = False
254
- for btn_text in ["Continue", "Allow", "Next", "Accept", "Confirm"]:
255
- try:
256
- btn = page.locator(f"button:has-text('{btn_text}')").first
257
- if btn.count() > 0 and btn.is_visible(timeout=300):
258
- log_step(f"Klik tombol Google: {btn_text}")
259
- btn.click(timeout=1000, force=True)
260
- clicked_btn = True
261
- time.sleep(3.0)
262
- break
263
- except Exception:
264
- pass
265
- if clicked_btn:
266
- continue
267
-
268
- time.sleep(1.0)
269
-
270
- # Back on Qoder - Wait for authorization to resolve
271
- time.sleep(5.0)
272
-
273
- # Check if there is an authorize button to confirm
274
- confirm_sel = "button:has-text('Confirm'), button:has-text('Authorize'), button:has-text('Allow'), button:has-text('Sign in'), button:has-text('Continue')"
275
- try:
276
- confirm_btn = page.locator(confirm_sel).first
277
- if confirm_btn.count() > 0 and confirm_btn.is_visible(timeout=1000):
278
- log_step("Mengklik tombol konfirmasi/otorisasi...")
279
- confirm_btn.click(force=True)
280
- time.sleep(5.0)
281
- except Exception:
282
- pass
283
-
284
- # Step 3: Poll for the device token
285
- log_step("Menunggu Qoder menerbitkan token...")
286
- token_data = poll_device_token(nonce, verifier)
287
- if not token_data or "token" not in token_data:
288
- # Take screenshot on warning/error
289
- try:
290
- screenshot_path = str(profile_dir / "qoder_not_success.png")
291
- page.screenshot(path=screenshot_path)
292
- log_step(f"Screenshot disimpan ke {screenshot_path}")
293
- html_path = str(profile_dir / "qoder_not_success.html")
294
- with open(html_path, "w", encoding="utf-8") as f:
295
- f.write(page.content())
296
- log_step(f"HTML disimpan ke {html_path}")
297
- except Exception as ex:
298
- log_step(f"Gagal mengambil screenshot/HTML: {ex}")
299
- raise QoderAutomationError("Timeout menunggu device token Qoder.")
300
-
301
- except Exception as e:
302
- sys.stdout.write(json.dumps({"status": "error", "message": f"Browser Error: {e}"}) + "\n")
303
- sys.exit(1)
304
-
305
- # Step 4: Fetch Profile User Info best effort
306
- user_info = fetch_user_info(token_data["token"])
307
-
308
- # Output Success JSON line
309
- sys.stdout.write(json.dumps({
310
- "status": "success",
311
- "api_key": token_data["token"],
312
- "refresh_token": token_data.get("refresh_token") or "",
313
- "expires_in": token_data.get("expires_in") or 2592000,
314
- "user_id": token_data.get("user_id") or user_info.get("user_id") or "",
315
- "machine_id": machine_id,
316
- "name": user_info.get("name") or user_info.get("username") or "",
317
- "email": user_info.get("email") or args.email,
318
- "organization_id": user_info.get("organization_id") or ""
319
- }) + "\n")
320
- sys.stdout.flush()
321
-
322
- if __name__ == "__main__":
323
- main()
@@ -1,86 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Proxy smoke-test using Camoufox — matches leoapi-main approach."""
3
- import sys
4
- import json
5
- import time
6
- import argparse
7
-
8
- def parse_proxy(proxy_str):
9
- """Parse any proxy format into Camoufox proxy dict."""
10
- if not proxy_str:
11
- return None
12
- raw = proxy_str.strip()
13
-
14
- import re
15
-
16
- # Correct URL format: http://user:pass@host:port or socks5://host:port
17
- url_match = re.match(r'^(socks[45]?|https?|http)://(?:([^:@]+):([^@]+)@)?([^:]+):(\d+)$', raw, re.I)
18
- if url_match:
19
- proto, user, password, host, port = url_match.groups()
20
- proxy = {"server": f"{proto}://{host}:{port}"}
21
- if user: proxy["username"] = user
22
- if password: proxy["password"] = password
23
- return proxy
24
-
25
- # Malformed: http://host:port:user:pass (incorrectly stored)
26
- bad_url = re.match(r'^(https?|socks[45]?)://([^:]+):(\d+):([^:]+):(.+)$', raw, re.I)
27
- if bad_url:
28
- proto, host, port, user, password = bad_url.groups()
29
- return {"server": f"http://{host}:{port}", "username": user, "password": password}
30
-
31
- # Plain: host:port:user:pass
32
- parts = raw.split(":")
33
- if len(parts) == 4 and parts[1].isdigit():
34
- host, port, user, password = parts
35
- return {"server": f"http://{host}:{port}", "username": user, "password": password}
36
-
37
- # Plain: host:port
38
- if len(parts) == 2 and parts[1].isdigit():
39
- return {"server": f"http://{parts[0]}:{parts[1]}"}
40
-
41
- return None
42
-
43
- def main():
44
- parser = argparse.ArgumentParser()
45
- parser.add_argument("--proxy", required=True)
46
- parser.add_argument("--headless", action="store_true", default=True)
47
- args = parser.parse_args()
48
-
49
- proxy = parse_proxy(args.proxy)
50
- if not proxy:
51
- print(json.dumps({"ok": False, "error": "Invalid proxy format"}))
52
- sys.exit(1)
53
-
54
- try:
55
- from camoufox.sync_api import Camoufox
56
- except ImportError:
57
- try:
58
- from camoufox import Camoufox
59
- except ImportError:
60
- print(json.dumps({"ok": False, "error": "Camoufox not installed"}))
61
- sys.exit(1)
62
-
63
- result = {"ok": False, "server": proxy.get("server")}
64
- try:
65
- t0 = time.time()
66
- kwargs = dict(
67
- headless=args.headless,
68
- proxy=proxy,
69
- humanize=False,
70
- )
71
- with Camoufox(**kwargs) as browser:
72
- context = getattr(browser, "context", None) or browser
73
- page = context.new_page()
74
- page.goto("https://api.ipify.org?format=json", wait_until="domcontentloaded", timeout=20000)
75
- body = page.locator("body").inner_text(timeout=5000)
76
- info = json.loads(body)
77
- result["ok"] = True
78
- result["ip"] = info.get("ip")
79
- result["latency"] = round((time.time() - t0) * 1000)
80
- except Exception as e:
81
- result["error"] = str(e)[:300]
82
-
83
- print(json.dumps(result))
84
-
85
- if __name__ == "__main__":
86
- main()