@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.
- package/dist/open-sse/handlers/chatCore.js +16 -0
- package/dist/routes/oauth/[provider]/[action]/route.js +6 -0
- package/package.json +10 -2
- package/public/.vite/manifest.json +89 -89
- package/public/assets/EndpointPageClient.kiCCiZeY.js +6 -0
- package/public/assets/Loading.BUhrzmZs.js +1 -0
- package/public/assets/NoAuthProxyCard.BI22GO9q.js +89 -0
- package/public/assets/index.DZiGd5-F.js +89 -0
- package/public/assets/page.1oqivCfq.js +1 -0
- package/public/assets/page.B5R9sdV7.js +1 -0
- package/public/assets/page.B5x2tCz5.js +1 -0
- package/public/assets/page.BFjNdqva.js +1 -0
- package/public/assets/page.BGs8R5av.js +1 -0
- package/public/assets/page.BLO61tLD.js +2 -0
- package/public/assets/page.BLWAx8lB.js +1 -0
- package/public/assets/page.BMcCQehz.js +6 -0
- package/public/assets/page.BS1sQouT.js +1 -0
- package/public/assets/page.BUWLUkXc.js +1 -0
- package/public/assets/page.BWaWJ_9t.js +1 -0
- package/public/assets/page.BYhmIkhw.js +1 -0
- package/public/assets/page.Buhl7l9y.js +1 -0
- package/public/assets/page.CBlkUp7t.js +1 -0
- package/public/assets/page.CG2arj7j.js +4 -0
- package/public/assets/page.CLgmM6Q_.js +5 -0
- package/public/assets/page.CLu_guvG.js +1 -0
- package/public/assets/page.COCdcdEM.js +1 -0
- package/public/assets/page.C_zhg1wq.js +1 -0
- package/public/assets/page.CtaqVBbT.js +6 -0
- package/public/assets/page.Cx0GMnA6.js +5 -0
- package/public/assets/page.D47UJLch.js +2 -0
- package/public/assets/page.D9ZFXJ3P.js +1 -0
- package/public/assets/page.D9kdvCh4.js +1 -0
- package/public/assets/page.DO1WoRkx.js +23 -0
- package/public/assets/page.DU201OUE.js +1 -0
- package/public/assets/page.DXxD-0Vl.js +31 -0
- package/public/assets/page.DaJXSZm4.js +1 -0
- package/public/assets/page.Dkeyd3nc.js +1 -0
- package/public/assets/page.DwJ35Irb.js +1 -0
- package/public/assets/page.DzLEPjRK.js +64 -0
- package/public/assets/page.VAojhnMr.js +1 -0
- package/public/assets/page.WBkwLufg.js +1 -0
- package/public/assets/page.laQQR26l.js +1 -0
- package/public/assets/page.nzF601n5.js +1 -0
- package/public/assets/page.tzSMuTXr.js +1 -0
- package/public/assets/page.xCigoHGB.js +1 -0
- package/public/index.html +4 -4
- package/scripts/postinstall.cjs +90 -0
- package/src/automation/cf_token_via_session.py +0 -158
- package/src/automation/cloudflare_signup.py +0 -3689
- package/src/automation/codebuddy_signup.py +0 -636
- package/src/automation/flashloop_signup.py +0 -338
- package/src/automation/kimi_signup.py +0 -401
- package/src/automation/leonardo_signup.py +0 -902
- package/src/automation/openvecta_signup.py +0 -652
- package/src/automation/qoder_signup.py +0 -323
- package/src/automation/test_proxy.py +0 -86
- package/src/automation/weavy_signup.py +0 -1964
|
@@ -1,636 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Standalone script to automate CodeBuddy signup & API key generation using Playwright/Camoufox.
|
|
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
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
# Patch Playwright's Locator.is_visible to support the timeout argument
|
|
15
|
-
try:
|
|
16
|
-
from playwright.sync_api import Locator
|
|
17
|
-
_orig_is_visible = Locator.is_visible
|
|
18
|
-
def _patched_is_visible(self, *args, **kwargs):
|
|
19
|
-
timeout = kwargs.pop("timeout", None)
|
|
20
|
-
if timeout is None and len(args) > 0:
|
|
21
|
-
timeout = args[0]
|
|
22
|
-
args = args[1:]
|
|
23
|
-
if timeout is not None:
|
|
24
|
-
try:
|
|
25
|
-
self.wait_for(state="visible", timeout=float(timeout))
|
|
26
|
-
return True
|
|
27
|
-
except Exception:
|
|
28
|
-
return False
|
|
29
|
-
return _orig_is_visible(self, *args, **kwargs)
|
|
30
|
-
Locator.is_visible = _patched_is_visible
|
|
31
|
-
except ImportError:
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
# Setup simple stdout logger to not conflict with JSON line prints
|
|
35
|
-
logging.basicConfig(level=logging.WARNING)
|
|
36
|
-
logger = logging.getLogger("codebuddy_signup")
|
|
37
|
-
|
|
38
|
-
CODEBUDDY_LOGIN_URL = "https://www.codebuddy.ai/login?redirect_uri=https://www.codebuddy.ai/home"
|
|
39
|
-
CODEBUDDY_API_KEYS_ENDPOINT = "https://www.codebuddy.ai/console/api/client/v1/api-keys"
|
|
40
|
-
|
|
41
|
-
GOOGLE_EMAIL_SELECTORS = [
|
|
42
|
-
"input[type='email']",
|
|
43
|
-
"input[name='identifier']",
|
|
44
|
-
"input#identifierId",
|
|
45
|
-
]
|
|
46
|
-
GOOGLE_PASSWORD_SELECTORS = [
|
|
47
|
-
"input[type='password'][name='Passwd']",
|
|
48
|
-
"input[type='password']",
|
|
49
|
-
"input[name='password']",
|
|
50
|
-
]
|
|
51
|
-
GOOGLE_NEXT_SELECTORS = [
|
|
52
|
-
"#identifierNext button",
|
|
53
|
-
"#passwordNext button",
|
|
54
|
-
"button[jsname='LgbsSe']",
|
|
55
|
-
"div[role='button']:has-text('Next')",
|
|
56
|
-
"button:has-text('Next')",
|
|
57
|
-
"button:has-text('Berikutnya')",
|
|
58
|
-
]
|
|
59
|
-
|
|
60
|
-
class CodeBuddyAutomationError(RuntimeError):
|
|
61
|
-
pass
|
|
62
|
-
|
|
63
|
-
def log_step(msg, *args):
|
|
64
|
-
txt = msg % args if args else msg
|
|
65
|
-
sys.stdout.write(json.dumps({"step": txt}, ensure_ascii=False) + "\n")
|
|
66
|
-
sys.stdout.flush()
|
|
67
|
-
|
|
68
|
-
def safe_email_to_dirname(email: str) -> str:
|
|
69
|
-
cleaned = (email or "").strip().lower()
|
|
70
|
-
cleaned = cleaned.replace("@", "_at_")
|
|
71
|
-
cleaned = re.sub(r"[^a-z0-9._-]+", "_", cleaned)
|
|
72
|
-
cleaned = cleaned.strip("._-")
|
|
73
|
-
return cleaned or "account"
|
|
74
|
-
|
|
75
|
-
def get_proxy_country(proxy: dict = None) -> str:
|
|
76
|
-
import requests
|
|
77
|
-
url = "http://ip-api.com/json/"
|
|
78
|
-
proxies = None
|
|
79
|
-
if proxy and proxy.get("server"):
|
|
80
|
-
server = proxy["server"]
|
|
81
|
-
username = proxy.get("username")
|
|
82
|
-
password = proxy.get("password")
|
|
83
|
-
if username and password:
|
|
84
|
-
if "://" in server:
|
|
85
|
-
proto, host = server.split("://", 1)
|
|
86
|
-
proxy_url = f"{proto}://{username}:{password}@{host}"
|
|
87
|
-
else:
|
|
88
|
-
proxy_url = f"http://{username}:{password}@{server}"
|
|
89
|
-
else:
|
|
90
|
-
proxy_url = server
|
|
91
|
-
proxies = {
|
|
92
|
-
"http": proxy_url,
|
|
93
|
-
"https": proxy_url
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
for attempt in range(2):
|
|
97
|
-
try:
|
|
98
|
-
response = requests.get(url, proxies=proxies, timeout=10)
|
|
99
|
-
if response.status_code == 200:
|
|
100
|
-
data = response.json()
|
|
101
|
-
if data.get("status") == "success" and data.get("country"):
|
|
102
|
-
return data["country"]
|
|
103
|
-
except Exception:
|
|
104
|
-
pass
|
|
105
|
-
|
|
106
|
-
try:
|
|
107
|
-
fallback_url = "https://ipapi.co/json/"
|
|
108
|
-
response = requests.get(fallback_url, proxies=proxies, timeout=10)
|
|
109
|
-
if response.status_code == 200:
|
|
110
|
-
data = response.json()
|
|
111
|
-
if data.get("country_name"):
|
|
112
|
-
return data["country_name"]
|
|
113
|
-
except Exception:
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
return "United States"
|
|
117
|
-
|
|
118
|
-
def test_api_key(api_key: str, proxy: dict = None) -> bool:
|
|
119
|
-
url = "https://www.codebuddy.ai/v2/chat/completions"
|
|
120
|
-
headers = {
|
|
121
|
-
"Content-Type": "application/json",
|
|
122
|
-
"Authorization": f"Bearer {api_key}",
|
|
123
|
-
}
|
|
124
|
-
payload = {
|
|
125
|
-
"model": "default-model",
|
|
126
|
-
"messages": [
|
|
127
|
-
{"role": "system", "content": "You are a helpful assistant."},
|
|
128
|
-
{"role": "user", "content": "ping"}
|
|
129
|
-
],
|
|
130
|
-
"stream": True
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
proxies = None
|
|
134
|
-
if proxy and proxy.get("server"):
|
|
135
|
-
server = proxy["server"]
|
|
136
|
-
username = proxy.get("username")
|
|
137
|
-
password = proxy.get("password")
|
|
138
|
-
if username and password:
|
|
139
|
-
if "://" in server:
|
|
140
|
-
proto, host = server.split("://", 1)
|
|
141
|
-
proxy_url = f"{proto}://{username}:{password}@{host}"
|
|
142
|
-
else:
|
|
143
|
-
proxy_url = f"http://{username}:{password}@{server}"
|
|
144
|
-
else:
|
|
145
|
-
proxy_url = server
|
|
146
|
-
proxies = {"http": proxy_url, "https": proxy_url}
|
|
147
|
-
|
|
148
|
-
import requests
|
|
149
|
-
try:
|
|
150
|
-
r = requests.post(url, headers=headers, json=payload, proxies=proxies, timeout=10, stream=True)
|
|
151
|
-
if r.status_code == 200:
|
|
152
|
-
try:
|
|
153
|
-
r.close()
|
|
154
|
-
except Exception:
|
|
155
|
-
pass
|
|
156
|
-
return True
|
|
157
|
-
except Exception:
|
|
158
|
-
pass
|
|
159
|
-
return False
|
|
160
|
-
|
|
161
|
-
def click_first(page, selectors, timeout_ms: int = 5000) -> bool:
|
|
162
|
-
deadline = time.time() + (timeout_ms / 1000.0)
|
|
163
|
-
while time.time() < deadline:
|
|
164
|
-
for sel in selectors:
|
|
165
|
-
try:
|
|
166
|
-
loc = page.locator(sel).first
|
|
167
|
-
if loc.count() > 0 and loc.is_visible(timeout=400):
|
|
168
|
-
try:
|
|
169
|
-
loc.click(timeout=1500)
|
|
170
|
-
return True
|
|
171
|
-
except Exception:
|
|
172
|
-
pass
|
|
173
|
-
try:
|
|
174
|
-
handle = loc.element_handle(timeout=500)
|
|
175
|
-
if handle:
|
|
176
|
-
page.evaluate("(el) => el.click()", handle)
|
|
177
|
-
return True
|
|
178
|
-
except Exception:
|
|
179
|
-
pass
|
|
180
|
-
except Exception:
|
|
181
|
-
continue
|
|
182
|
-
time.sleep(0.3)
|
|
183
|
-
return False
|
|
184
|
-
|
|
185
|
-
def fill_first(page, selectors, value: str, timeout_ms: int = 5000) -> bool:
|
|
186
|
-
deadline = time.time() + (timeout_ms / 1000.0)
|
|
187
|
-
while time.time() < deadline:
|
|
188
|
-
for sel in selectors:
|
|
189
|
-
try:
|
|
190
|
-
loc = page.locator(sel).first
|
|
191
|
-
if loc.count() > 0 and loc.is_visible(timeout=400):
|
|
192
|
-
loc.fill(value, timeout=1500)
|
|
193
|
-
return True
|
|
194
|
-
except Exception:
|
|
195
|
-
continue
|
|
196
|
-
time.sleep(0.3)
|
|
197
|
-
return False
|
|
198
|
-
|
|
199
|
-
def do_google_login(page, email: str, password: str) -> bool:
|
|
200
|
-
log_step("Menunggu form email Google...")
|
|
201
|
-
if not fill_first(page, GOOGLE_EMAIL_SELECTORS, email, timeout_ms=10000):
|
|
202
|
-
log_step("Form email tidak ditemukan, cek apakah diminta pilih akun.")
|
|
203
|
-
try:
|
|
204
|
-
account_sel = f"div[data-identifier='{email.lower()}']"
|
|
205
|
-
if page.locator(account_sel).count() > 0:
|
|
206
|
-
page.locator(account_sel).click()
|
|
207
|
-
log_step(f"Memilih akun {email}")
|
|
208
|
-
except Exception:
|
|
209
|
-
pass
|
|
210
|
-
else:
|
|
211
|
-
click_first(page, GOOGLE_NEXT_SELECTORS, timeout_ms=3000)
|
|
212
|
-
|
|
213
|
-
log_step("Menunggu form password Google...")
|
|
214
|
-
if fill_first(page, GOOGLE_PASSWORD_SELECTORS, password, timeout_ms=10000):
|
|
215
|
-
click_first(page, GOOGLE_NEXT_SELECTORS, timeout_ms=3000)
|
|
216
|
-
log_step("Password diisi dan disubmit.")
|
|
217
|
-
return True
|
|
218
|
-
|
|
219
|
-
time.sleep(2)
|
|
220
|
-
if "codebuddy.ai" in page.url.lower():
|
|
221
|
-
log_step("Berhasil auth, redirect ke CodeBuddy.")
|
|
222
|
-
return True
|
|
223
|
-
|
|
224
|
-
return False
|
|
225
|
-
|
|
226
|
-
def perform_login_page_actions(page, email: str) -> None:
|
|
227
|
-
iframe_selector = "iframe[title='login-iframe']"
|
|
228
|
-
try:
|
|
229
|
-
page.wait_for_selector(iframe_selector, timeout=15000)
|
|
230
|
-
for _ in range(30):
|
|
231
|
-
exists = page.evaluate(f"""() => {{
|
|
232
|
-
const iframe = document.querySelector("{iframe_selector}");
|
|
233
|
-
if (iframe && iframe.contentDocument) {{
|
|
234
|
-
const doc = iframe.contentDocument;
|
|
235
|
-
return !!(doc.querySelector("#agree-policy-account") || doc.querySelector("input[type='checkbox']"));
|
|
236
|
-
}}
|
|
237
|
-
return false;
|
|
238
|
-
}}""")
|
|
239
|
-
if exists:
|
|
240
|
-
break
|
|
241
|
-
time.sleep(0.5)
|
|
242
|
-
except Exception:
|
|
243
|
-
pass
|
|
244
|
-
|
|
245
|
-
iframe = page.frame_locator(iframe_selector)
|
|
246
|
-
|
|
247
|
-
log_step("Centang checkbox persetujuan...")
|
|
248
|
-
try:
|
|
249
|
-
page.evaluate(f"""() => {{
|
|
250
|
-
const iframe = document.querySelector("{iframe_selector}");
|
|
251
|
-
if (iframe && iframe.contentDocument) {{
|
|
252
|
-
const doc = iframe.contentDocument;
|
|
253
|
-
const checkbox = doc.querySelector("#agree-policy-account") || doc.querySelector("input[type='checkbox']");
|
|
254
|
-
if (checkbox) {{
|
|
255
|
-
checkbox.checked = true;
|
|
256
|
-
checkbox.dispatchEvent(new Event('change', {{ bubbles: true }}));
|
|
257
|
-
checkbox.dispatchEvent(new Event('click', {{ bubbles: true }}));
|
|
258
|
-
return true;
|
|
259
|
-
}}
|
|
260
|
-
}}
|
|
261
|
-
return false;
|
|
262
|
-
}}""")
|
|
263
|
-
except Exception:
|
|
264
|
-
pass
|
|
265
|
-
|
|
266
|
-
time.sleep(2)
|
|
267
|
-
|
|
268
|
-
log_step("Klik Sign up with Google...")
|
|
269
|
-
google_selector = "#social-google, a[href*='google/login'], a:has-text('Google')"
|
|
270
|
-
clicked_google = False
|
|
271
|
-
try:
|
|
272
|
-
loc = iframe.locator(google_selector).first
|
|
273
|
-
if loc.count() > 0:
|
|
274
|
-
loc.click(timeout=10000, force=True)
|
|
275
|
-
clicked_google = True
|
|
276
|
-
except Exception:
|
|
277
|
-
pass
|
|
278
|
-
|
|
279
|
-
if not clicked_google:
|
|
280
|
-
try:
|
|
281
|
-
page.evaluate(f"""() => {{
|
|
282
|
-
const iframe = document.querySelector("{iframe_selector}");
|
|
283
|
-
if (iframe && iframe.contentDocument) {{
|
|
284
|
-
const doc = iframe.contentDocument;
|
|
285
|
-
const el = doc.querySelector("#social-google") ||
|
|
286
|
-
doc.querySelector("a[href*='google/login']") ||
|
|
287
|
-
Array.from(doc.querySelectorAll("button, a, div[role='button']"))
|
|
288
|
-
.find(e => (e.innerText || '').includes("Google") || (e.textContent || '').includes("Google"));
|
|
289
|
-
if (el) {{
|
|
290
|
-
el.click();
|
|
291
|
-
return true;
|
|
292
|
-
}}
|
|
293
|
-
}}
|
|
294
|
-
return false;
|
|
295
|
-
}}""")
|
|
296
|
-
except Exception:
|
|
297
|
-
pass
|
|
298
|
-
|
|
299
|
-
time.sleep(2)
|
|
300
|
-
|
|
301
|
-
try:
|
|
302
|
-
confirm_btn = page.locator("button:has-text('Confirm')").first
|
|
303
|
-
if confirm_btn.is_visible(timeout=3000):
|
|
304
|
-
log_step("Klik Confirm pada popup Service Agreement...")
|
|
305
|
-
confirm_btn.click(timeout=3000, force=True)
|
|
306
|
-
time.sleep(2)
|
|
307
|
-
else:
|
|
308
|
-
confirm_btn2 = iframe.locator("button:has-text('Confirm')").first
|
|
309
|
-
if confirm_btn2.is_visible(timeout=2000):
|
|
310
|
-
confirm_btn2.click(timeout=3000, force=True)
|
|
311
|
-
time.sleep(2)
|
|
312
|
-
except Exception:
|
|
313
|
-
pass
|
|
314
|
-
|
|
315
|
-
def safe_evaluate(page, js_code: str, arg=None, max_retries: int = 3):
|
|
316
|
-
for attempt in range(max_retries):
|
|
317
|
-
try:
|
|
318
|
-
if arg is not None:
|
|
319
|
-
return page.evaluate(js_code, arg)
|
|
320
|
-
else:
|
|
321
|
-
return page.evaluate(js_code)
|
|
322
|
-
except Exception as e:
|
|
323
|
-
err_msg = str(e).lower()
|
|
324
|
-
if "execution context was destroyed" in err_msg or "context or browser has been closed" in err_msg:
|
|
325
|
-
if attempt < max_retries - 1:
|
|
326
|
-
time.sleep(3)
|
|
327
|
-
try:
|
|
328
|
-
page.wait_for_load_state("domcontentloaded", timeout=5000)
|
|
329
|
-
except Exception:
|
|
330
|
-
pass
|
|
331
|
-
continue
|
|
332
|
-
raise
|
|
333
|
-
raise CodeBuddyAutomationError("Failed to evaluate JS after context destroyed retries")
|
|
334
|
-
|
|
335
|
-
def main():
|
|
336
|
-
parser = argparse.ArgumentParser(description="CodeBuddy auto-signup tool")
|
|
337
|
-
parser.add_argument("--email", required=True)
|
|
338
|
-
parser.add_argument("--password", required=True)
|
|
339
|
-
parser.add_argument("--proxy-server")
|
|
340
|
-
parser.add_argument("--proxy-user")
|
|
341
|
-
parser.add_argument("--proxy-pass")
|
|
342
|
-
parser.add_argument("--profiles-dir", required=True)
|
|
343
|
-
parser.add_argument("--headless", action="store_true", default=False)
|
|
344
|
-
args = parser.parse_args()
|
|
345
|
-
|
|
346
|
-
profiles_root = Path(args.profiles_dir)
|
|
347
|
-
profiles_root.mkdir(parents=True, exist_ok=True)
|
|
348
|
-
profile_dir = profiles_root / safe_email_to_dirname(args.email)
|
|
349
|
-
profile_dir.mkdir(parents=True, exist_ok=True)
|
|
350
|
-
|
|
351
|
-
proxy_dict = None
|
|
352
|
-
if args.proxy_server:
|
|
353
|
-
proxy_dict = {"server": args.proxy_server}
|
|
354
|
-
if args.proxy_user:
|
|
355
|
-
proxy_dict["username"] = args.proxy_user
|
|
356
|
-
if args.proxy_pass:
|
|
357
|
-
proxy_dict["password"] = args.proxy_pass
|
|
358
|
-
|
|
359
|
-
try:
|
|
360
|
-
from camoufox.sync_api import Camoufox
|
|
361
|
-
except ImportError:
|
|
362
|
-
sys.stdout.write(json.dumps({"status": "error", "message": "Camoufox package not installed in python environment."}) + "\n")
|
|
363
|
-
sys.exit(1)
|
|
364
|
-
|
|
365
|
-
kwargs = dict(
|
|
366
|
-
headless=args.headless,
|
|
367
|
-
persistent_context=True, no_viewport=True,
|
|
368
|
-
user_data_dir=str(profile_dir),
|
|
369
|
-
humanize=True,
|
|
370
|
-
geoip=True,
|
|
371
|
-
locale="en-US",
|
|
372
|
-
os=("windows", "macos", "linux"),
|
|
373
|
-
window=(1280, 800),
|
|
374
|
-
firefox_user_prefs={
|
|
375
|
-
"network.trr.mode": 5,
|
|
376
|
-
}
|
|
377
|
-
)
|
|
378
|
-
if proxy_dict:
|
|
379
|
-
kwargs["proxy"] = {k: v for k, v in proxy_dict.items() if v}
|
|
380
|
-
|
|
381
|
-
log_step("Meluncurkan browser...")
|
|
382
|
-
ctx_manager = None
|
|
383
|
-
try:
|
|
384
|
-
try:
|
|
385
|
-
ctx_manager = Camoufox(**kwargs)
|
|
386
|
-
except TypeError:
|
|
387
|
-
for drop in ("window", "os", "geoip", "humanize", "locale"):
|
|
388
|
-
kwargs.pop(drop, None)
|
|
389
|
-
try:
|
|
390
|
-
ctx_manager = Camoufox(**kwargs)
|
|
391
|
-
break
|
|
392
|
-
except TypeError:
|
|
393
|
-
continue
|
|
394
|
-
if not ctx_manager:
|
|
395
|
-
ctx_manager = Camoufox(**kwargs)
|
|
396
|
-
|
|
397
|
-
with ctx_manager as browser:
|
|
398
|
-
page = browser.new_page()
|
|
399
|
-
|
|
400
|
-
log_step("Membuka halaman login CodeBuddy...")
|
|
401
|
-
page.goto(CODEBUDDY_LOGIN_URL, wait_until="domcontentloaded", timeout=45000)
|
|
402
|
-
time.sleep(3)
|
|
403
|
-
|
|
404
|
-
if "codebuddy.ai" in page.url.lower() and "/login" in page.url.lower():
|
|
405
|
-
perform_login_page_actions(page, args.email)
|
|
406
|
-
try:
|
|
407
|
-
page.wait_for_url(lambda url: "accounts.google.com" in url or ("codebuddy.ai" in url and "/login" not in url), timeout=20000)
|
|
408
|
-
except Exception:
|
|
409
|
-
pass
|
|
410
|
-
|
|
411
|
-
if "accounts.google.com" in page.url.lower():
|
|
412
|
-
do_google_login(page, args.email, args.password)
|
|
413
|
-
try:
|
|
414
|
-
page.wait_for_url("**/codebuddy.ai/**", timeout=30000)
|
|
415
|
-
except Exception:
|
|
416
|
-
pass
|
|
417
|
-
|
|
418
|
-
max_google_loops = 10
|
|
419
|
-
for loop_i in range(max_google_loops):
|
|
420
|
-
cur_url = page.url.lower()
|
|
421
|
-
if "accounts.google.com" not in cur_url and "myaccount.google.com" not in cur_url:
|
|
422
|
-
break
|
|
423
|
-
|
|
424
|
-
log_step(f"Halaman interstitial Google: {page.url[:80]}...")
|
|
425
|
-
try:
|
|
426
|
-
page.wait_for_load_state("networkidle", timeout=5000)
|
|
427
|
-
except Exception:
|
|
428
|
-
pass
|
|
429
|
-
|
|
430
|
-
clicked = click_first(page, [
|
|
431
|
-
"button:has-text('Saya mengerti')",
|
|
432
|
-
"a:has-text('Saya mengerti')",
|
|
433
|
-
"button:has-text('I understand')",
|
|
434
|
-
"button:has-text('I Understand')",
|
|
435
|
-
"button:has-text('Continue')",
|
|
436
|
-
"button:has-text('Allow')",
|
|
437
|
-
"button:has-text('Accept')",
|
|
438
|
-
"button:has-text('Agree')",
|
|
439
|
-
"button:has-text('I agree')",
|
|
440
|
-
"button:has-text('Next')",
|
|
441
|
-
".VfPpkd-LgbsSe[data-mdc-ripple-is-unbounded]",
|
|
442
|
-
"button[jsname='LgbsSe']",
|
|
443
|
-
"input[type='submit']",
|
|
444
|
-
"button[type='submit']",
|
|
445
|
-
], timeout_ms=4000)
|
|
446
|
-
|
|
447
|
-
if clicked:
|
|
448
|
-
log_step("Mengklik persetujuan Google, menunggu redirect...")
|
|
449
|
-
time.sleep(3)
|
|
450
|
-
else:
|
|
451
|
-
try:
|
|
452
|
-
continue_url = page.evaluate("""() => {
|
|
453
|
-
const url = new URL(window.location.href);
|
|
454
|
-
return url.searchParams.get('continue') || '';
|
|
455
|
-
}""")
|
|
456
|
-
if continue_url and "google.com" in continue_url:
|
|
457
|
-
page.goto(continue_url, wait_until="domcontentloaded", timeout=15000)
|
|
458
|
-
time.sleep(3)
|
|
459
|
-
else:
|
|
460
|
-
try:
|
|
461
|
-
js_clicked = page.evaluate("""() => {
|
|
462
|
-
const btns = Array.from(document.querySelectorAll('button,input[type=submit]'));
|
|
463
|
-
const target = btns.find(b => {
|
|
464
|
-
const t = (b.innerText || b.value || '').trim();
|
|
465
|
-
return t === 'Continue' || t === 'Allow' || t === 'Saya mengerti' || t === 'I understand' || t === 'Accept';
|
|
466
|
-
});
|
|
467
|
-
if (target) { target.click(); return true; }
|
|
468
|
-
return false;
|
|
469
|
-
}""")
|
|
470
|
-
if js_clicked:
|
|
471
|
-
time.sleep(3)
|
|
472
|
-
continue
|
|
473
|
-
except Exception:
|
|
474
|
-
pass
|
|
475
|
-
time.sleep(3)
|
|
476
|
-
except Exception:
|
|
477
|
-
time.sleep(3)
|
|
478
|
-
|
|
479
|
-
if "no-permission" in page.url.lower() or "login" in page.url.lower():
|
|
480
|
-
log_step("Navigasi ke /register/user/complete...")
|
|
481
|
-
page.goto("https://www.codebuddy.ai/register/user/complete", wait_until="domcontentloaded", timeout=20000)
|
|
482
|
-
time.sleep(3)
|
|
483
|
-
|
|
484
|
-
if "register" in page.url.lower():
|
|
485
|
-
country = get_proxy_country(proxy_dict)
|
|
486
|
-
log_step(f"Mengisi lokasi registrasi ({country})...")
|
|
487
|
-
try:
|
|
488
|
-
page.evaluate(f"""() => {{
|
|
489
|
-
let inputs = Array.from(document.querySelectorAll('input'));
|
|
490
|
-
for (let input of inputs) {{
|
|
491
|
-
if (input.placeholder && input.placeholder.toLowerCase().includes('location')) {{
|
|
492
|
-
input.value = '{country}';
|
|
493
|
-
input.dispatchEvent(new Event('input', {{ bubbles: true }}));
|
|
494
|
-
input.dispatchEvent(new Event('change', {{ bubbles: true }}));
|
|
495
|
-
}}
|
|
496
|
-
}}
|
|
497
|
-
}}""")
|
|
498
|
-
time.sleep(1)
|
|
499
|
-
click_first(page, [
|
|
500
|
-
"button:has-text('Submit')",
|
|
501
|
-
"button:has-text('Complete')",
|
|
502
|
-
"button:has-text('Save')",
|
|
503
|
-
"button:has-text('Continue')"
|
|
504
|
-
])
|
|
505
|
-
time.sleep(5)
|
|
506
|
-
except Exception:
|
|
507
|
-
pass
|
|
508
|
-
|
|
509
|
-
log_step("Menunggu aktivasi trial otomatis...")
|
|
510
|
-
try:
|
|
511
|
-
with page.expect_response(lambda r: "billing/ide/trial" in r.url, timeout=15000):
|
|
512
|
-
pass
|
|
513
|
-
time.sleep(2)
|
|
514
|
-
except Exception:
|
|
515
|
-
pass
|
|
516
|
-
|
|
517
|
-
if "home" not in page.url.lower() and "ide" not in page.url.lower() and "profile" not in page.url.lower():
|
|
518
|
-
page.goto("https://www.codebuddy.ai/home", wait_until="domcontentloaded", timeout=15000)
|
|
519
|
-
time.sleep(4)
|
|
520
|
-
|
|
521
|
-
log_step("Mengaktifkan trial IDE secara manual...")
|
|
522
|
-
try:
|
|
523
|
-
trial_res = safe_evaluate(page, """async () => {
|
|
524
|
-
try {
|
|
525
|
-
const res = await fetch("https://www.codebuddy.ai/billing/ide/trial", {
|
|
526
|
-
method: "POST",
|
|
527
|
-
headers: {
|
|
528
|
-
"Accept": "application/json, text/plain, */*",
|
|
529
|
-
"x-requested-with": "XMLHttpRequest"
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
const text = await res.text();
|
|
533
|
-
try { return JSON.parse(text); } catch(e) { return {raw: text, status: res.status}; }
|
|
534
|
-
} catch(e) {
|
|
535
|
-
return {error: e.toString()};
|
|
536
|
-
}
|
|
537
|
-
}""")
|
|
538
|
-
time.sleep(2)
|
|
539
|
-
except Exception:
|
|
540
|
-
pass
|
|
541
|
-
|
|
542
|
-
log_step("Memanggil API internal generate API Key...")
|
|
543
|
-
unique_key_name = f"AutoKey_{int(time.time())}"
|
|
544
|
-
api_payload = {
|
|
545
|
-
"name": unique_key_name,
|
|
546
|
-
"expire_in_days": 365,
|
|
547
|
-
"user_enterprise_id": "personal-edition-user-id"
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
result = safe_evaluate(page, f"""async (payload) => {{
|
|
551
|
-
try {{
|
|
552
|
-
const res = await fetch("{CODEBUDDY_API_KEYS_ENDPOINT}", {{
|
|
553
|
-
method: "POST",
|
|
554
|
-
headers: {{
|
|
555
|
-
"Content-Type": "application/json",
|
|
556
|
-
"Accept": "application/json"
|
|
557
|
-
}},
|
|
558
|
-
body: JSON.stringify(payload)
|
|
559
|
-
}});
|
|
560
|
-
const text = await res.text();
|
|
561
|
-
let data = null;
|
|
562
|
-
try {{ data = JSON.parse(text); }} catch(e) {{}}
|
|
563
|
-
return {{ok: res.ok, status: res.status, data: data}};
|
|
564
|
-
}} catch(e) {{
|
|
565
|
-
return {{ok: false, error: e.toString()}};
|
|
566
|
-
}}
|
|
567
|
-
}}""", api_payload)
|
|
568
|
-
|
|
569
|
-
api_key = ""
|
|
570
|
-
if result and result.get("ok") and result.get("data"):
|
|
571
|
-
data = result["data"]
|
|
572
|
-
if isinstance(data, dict):
|
|
573
|
-
inner = data.get("data") or {}
|
|
574
|
-
api_key = inner.get("key") or inner.get("api_key") or data.get("key") or data.get("api_key") or ""
|
|
575
|
-
|
|
576
|
-
if not api_key and result and result.get("data") and isinstance(result["data"], dict):
|
|
577
|
-
err_code = result["data"].get("code")
|
|
578
|
-
if err_code == 12502:
|
|
579
|
-
log_step("Nama key konflik, mencoba generate dengan nama lain...")
|
|
580
|
-
retry_name = f"AutoKey_{int(time.time())}_r"
|
|
581
|
-
retry_payload = dict(api_payload, name=retry_name)
|
|
582
|
-
try:
|
|
583
|
-
retry_result = safe_evaluate(page, f"""async (payload) => {{
|
|
584
|
-
const res = await fetch("{CODEBUDDY_API_KEYS_ENDPOINT}", {{
|
|
585
|
-
method: "POST",
|
|
586
|
-
headers: {{"Content-Type": "application/json", "Accept": "application/json"}},
|
|
587
|
-
body: JSON.stringify(payload)
|
|
588
|
-
}});
|
|
589
|
-
const text = await res.text();
|
|
590
|
-
let data = null;
|
|
591
|
-
try {{ data = JSON.parse(text); }} catch(e) {{}}
|
|
592
|
-
return {{ok: res.ok, status: res.status, data: data}};
|
|
593
|
-
}}""", retry_payload)
|
|
594
|
-
if retry_result and retry_result.get("ok") and retry_result.get("data"):
|
|
595
|
-
inner2 = (retry_result["data"].get("data") or {})
|
|
596
|
-
api_key = inner2.get("key") or inner2.get("api_key") or ""
|
|
597
|
-
except Exception:
|
|
598
|
-
pass
|
|
599
|
-
|
|
600
|
-
if not api_key:
|
|
601
|
-
log_step("Mencari API Key yang sudah ada di list...")
|
|
602
|
-
list_result = safe_evaluate(page, f"""async () => {{
|
|
603
|
-
const res = await fetch("{CODEBUDDY_API_KEYS_ENDPOINT}?page=1&page_size=10&user_enterprise_id=personal-edition-user-id");
|
|
604
|
-
const text = await res.text();
|
|
605
|
-
let data = null;
|
|
606
|
-
try {{ data = JSON.parse(text); }} catch(e) {{}}
|
|
607
|
-
return {{ok: res.ok, status: res.status, data: data}};
|
|
608
|
-
}}""")
|
|
609
|
-
|
|
610
|
-
if list_result and list_result.get("data") and isinstance(list_result["data"], dict):
|
|
611
|
-
inner = list_result["data"].get("data") or {}
|
|
612
|
-
items = inner.get("items") or inner.get("list") or []
|
|
613
|
-
if items:
|
|
614
|
-
masked = items[0].get("masked_key", "")
|
|
615
|
-
sys.stdout.write(json.dumps({"status": "error", "message": f"Akun sudah memiliki API key ({masked}), tetapi hanya masked key yang tersedia."}) + "\n")
|
|
616
|
-
sys.exit(1)
|
|
617
|
-
|
|
618
|
-
if api_key:
|
|
619
|
-
log_step("Menguji validitas API Key...")
|
|
620
|
-
if test_api_key(api_key, proxy_dict):
|
|
621
|
-
log_step("API Key valid dan siap digunakan!")
|
|
622
|
-
sys.stdout.write(json.dumps({"status": "success", "api_key": api_key}) + "\n")
|
|
623
|
-
sys.exit(0)
|
|
624
|
-
else:
|
|
625
|
-
sys.stdout.write(json.dumps({"status": "error", "message": "API Key berhasil digenerate tetapi gagal tes fungsionalitas chat."}) + "\n")
|
|
626
|
-
sys.exit(1)
|
|
627
|
-
else:
|
|
628
|
-
sys.stdout.write(json.dumps({"status": "error", "message": "Gagal generate API Key dari CodeBuddy."}) + "\n")
|
|
629
|
-
sys.exit(1)
|
|
630
|
-
|
|
631
|
-
except Exception as e:
|
|
632
|
-
sys.stdout.write(json.dumps({"status": "error", "message": str(e)}) + "\n")
|
|
633
|
-
sys.exit(1)
|
|
634
|
-
|
|
635
|
-
if __name__ == "__main__":
|
|
636
|
-
main()
|