@fudrouter/fsrouter 0.6.92 → 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/package.json +10 -2
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fudrouter/fsrouter",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.93",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "9Router v2 — Express Backend (API, SSE, DB, Auth, MITM)",
|
|
@@ -9,8 +9,16 @@
|
|
|
9
9
|
"fsrouter": "bin/cli.cjs"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"start": "node dist/server.js"
|
|
12
|
+
"start": "node dist/server.js",
|
|
13
|
+
"postinstall": "node scripts/postinstall.cjs"
|
|
13
14
|
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"public",
|
|
18
|
+
"open-sse",
|
|
19
|
+
"bin",
|
|
20
|
+
"scripts"
|
|
21
|
+
],
|
|
14
22
|
"dependencies": {
|
|
15
23
|
"bcryptjs": "^3.0.3",
|
|
16
24
|
"better-sqlite3": "^12.11.1",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Post-install: wire automation symlinks so pm2 (cwd=/root) can find the
|
|
4
|
+
* Python venv, source tree and browser profiles used by the signup scripts.
|
|
5
|
+
*
|
|
6
|
+
* pm2 runs the server with cwd=/root, and the automation routes resolve paths
|
|
7
|
+
* relative to process.cwd() (e.g. .venv/bin/python, src/automation/*.py,
|
|
8
|
+
* profiles/<provider>). On this VPS the real files live under /root/AMRouter,
|
|
9
|
+
* so we symlink /root/{.venv,src,profiles} -> /root/AMRouter/{...}.
|
|
10
|
+
*
|
|
11
|
+
* Idempotent and non-fatal: never throws (npm install must succeed even if
|
|
12
|
+
* the symlinks can't be created).
|
|
13
|
+
*/
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
|
|
17
|
+
function log(msg) {
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.log(`[fsrouter:postinstall] ${msg}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function symlinkIfMissing(linkPath, targetPath) {
|
|
23
|
+
try {
|
|
24
|
+
if (!fs.existsSync(targetPath)) {
|
|
25
|
+
// target missing — only create the symlink if the link itself is absent
|
|
26
|
+
if (fs.existsSync(linkPath) || isSymlink(linkPath)) {
|
|
27
|
+
// leave existing link alone; just warn if it points nowhere
|
|
28
|
+
if (!fs.existsSync(linkPath)) fs.unlinkSync(linkPath);
|
|
29
|
+
}
|
|
30
|
+
log(`skip ${linkPath} (target ${targetPath} not found)`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (isSymlink(linkPath)) {
|
|
34
|
+
const cur = fs.readlinkSync(linkPath);
|
|
35
|
+
if (cur === targetPath) {
|
|
36
|
+
log(`ok ${linkPath} -> ${targetPath}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
fs.unlinkSync(linkPath);
|
|
40
|
+
} else if (fs.existsSync(linkPath)) {
|
|
41
|
+
log(`skip ${linkPath} (exists and is not a symlink)`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
fs.symlinkSync(targetPath, linkPath, "dir");
|
|
45
|
+
log(`linked ${linkPath} -> ${targetPath}`);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
log(`warn: could not link ${linkPath} -> ${targetPath}: ${e.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isSymlink(p) {
|
|
52
|
+
try {
|
|
53
|
+
return fs.lstatSync(p).isSymbolicLink();
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function main() {
|
|
60
|
+
// Prefer the canonical AMRouter checkout; fall back to the installed package.
|
|
61
|
+
const candidates = [
|
|
62
|
+
"/root/AMRouter",
|
|
63
|
+
path.resolve(__dirname, ".."), // package root when installed
|
|
64
|
+
];
|
|
65
|
+
let base = null;
|
|
66
|
+
for (const c of candidates) {
|
|
67
|
+
if (fs.existsSync(c)) {
|
|
68
|
+
base = c;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!base) {
|
|
73
|
+
log("no AMRouter base found; skipping symlinks");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
log(`base=${base}`);
|
|
77
|
+
|
|
78
|
+
// Virtualenv (venv python)
|
|
79
|
+
symlinkIfMissing("/root/.venv", path.join(base, ".venv"));
|
|
80
|
+
// Source tree (src/automation/*.py)
|
|
81
|
+
symlinkIfMissing("/root/src", path.join(base, "backend", "src"));
|
|
82
|
+
// Browser profiles used by automation signup scripts
|
|
83
|
+
symlinkIfMissing("/root/profiles", path.join(base, "backend", "profiles"));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
main();
|
|
88
|
+
} catch (e) {
|
|
89
|
+
log(`unexpected error: ${e.message}`);
|
|
90
|
+
}
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
cf_token_via_session.py
|
|
4
|
-
Login ke akun Cloudflare yang ada, buat Workers AI token via session cookie API.
|
|
5
|
-
Usage:
|
|
6
|
-
python cf_token_via_session.py --email X --password Y --account-id Z
|
|
7
|
-
"""
|
|
8
|
-
import argparse, json, time, sys
|
|
9
|
-
import requests
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
|
|
12
|
-
def log(m): print(json.dumps({"step": m}), flush=True)
|
|
13
|
-
def die(m): print(json.dumps({"status": "error", "error": m}), flush=True); sys.exit(1)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def create_token_with_cookies(cookies: dict, account_id: str) -> str | None:
|
|
17
|
-
"""POST /api/v4/user/tokens using browser session cookies."""
|
|
18
|
-
base = "https://dash.cloudflare.com"
|
|
19
|
-
sess = requests.Session()
|
|
20
|
-
sess.cookies.update(cookies)
|
|
21
|
-
sess.headers.update({
|
|
22
|
-
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120",
|
|
23
|
-
"Referer": f"{base}/profile/api-tokens/create",
|
|
24
|
-
"Origin": base,
|
|
25
|
-
"Accept": "application/json",
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
# A1: Get permission groups to find Workers AI ID
|
|
29
|
-
log("Fetching permission groups...")
|
|
30
|
-
r = sess.get(f"{base}/api/v4/user/tokens/permission_groups", timeout=15)
|
|
31
|
-
log(f"Permission groups status: {r.status_code}")
|
|
32
|
-
if r.status_code != 200:
|
|
33
|
-
log(f"Permission groups response: {r.text[:300]}")
|
|
34
|
-
return None
|
|
35
|
-
|
|
36
|
-
pg_data = r.json()
|
|
37
|
-
log(f"Total groups: {len(pg_data.get('result', []))}")
|
|
38
|
-
|
|
39
|
-
# Find Workers AI groups
|
|
40
|
-
workers_ai_ids = []
|
|
41
|
-
for pg in pg_data.get('result', []):
|
|
42
|
-
name = pg.get('name', '')
|
|
43
|
-
if 'Workers AI' in name:
|
|
44
|
-
log(f"Found: {name} -> {pg['id']}")
|
|
45
|
-
workers_ai_ids.append({'id': pg['id'], 'name': name})
|
|
46
|
-
|
|
47
|
-
if not workers_ai_ids:
|
|
48
|
-
log("No Workers AI permission groups found")
|
|
49
|
-
log(f"All groups: {[p['name'] for p in pg_data.get('result', [])][:20]}")
|
|
50
|
-
return None
|
|
51
|
-
|
|
52
|
-
# Prefer Workers AI:Read for safety, otherwise take first
|
|
53
|
-
target_id = workers_ai_ids[0]['id']
|
|
54
|
-
for pg in workers_ai_ids:
|
|
55
|
-
if 'Read' in pg['name']:
|
|
56
|
-
target_id = pg['id']
|
|
57
|
-
break
|
|
58
|
-
|
|
59
|
-
log(f"Using permission group ID: {target_id}")
|
|
60
|
-
|
|
61
|
-
# A2: Create token
|
|
62
|
-
payload = {
|
|
63
|
-
"name": "9router-workers-ai",
|
|
64
|
-
"policies": [{
|
|
65
|
-
"effect": "allow",
|
|
66
|
-
"resources": {f"com.cloudflare.api.account.{account_id}": "*"},
|
|
67
|
-
"permission_groups": [{"id": target_id}]
|
|
68
|
-
}]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
log(f"Creating token with payload: {json.dumps(payload)}")
|
|
72
|
-
r2 = sess.post(f"{base}/api/v4/user/tokens", json=payload, timeout=15)
|
|
73
|
-
log(f"Create token status: {r2.status_code}")
|
|
74
|
-
resp2 = r2.json()
|
|
75
|
-
log(f"Response: {json.dumps(resp2)[:500]}")
|
|
76
|
-
|
|
77
|
-
if resp2.get('success'):
|
|
78
|
-
return resp2['result'].get('value', '')
|
|
79
|
-
return None
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def main():
|
|
83
|
-
parser = argparse.ArgumentParser()
|
|
84
|
-
parser.add_argument("--email", required=True)
|
|
85
|
-
parser.add_argument("--password", required=True)
|
|
86
|
-
parser.add_argument("--account-id", required=True, dest="account_id")
|
|
87
|
-
parser.add_argument("--headless", action="store_true")
|
|
88
|
-
args = parser.parse_args()
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
from camoufox.sync_api import Camoufox
|
|
92
|
-
except ImportError:
|
|
93
|
-
die("Camoufox tidak terinstall")
|
|
94
|
-
|
|
95
|
-
log(f"Login ke CF sebagai {args.email}...")
|
|
96
|
-
with Camoufox(headless=args.headless, os="windows") as browser:
|
|
97
|
-
page = browser.new_page()
|
|
98
|
-
|
|
99
|
-
# Navigate to login
|
|
100
|
-
page.goto("https://dash.cloudflare.com/login", wait_until="domcontentloaded", timeout=30000)
|
|
101
|
-
time.sleep(3)
|
|
102
|
-
|
|
103
|
-
# Fill login form
|
|
104
|
-
try:
|
|
105
|
-
email_inp = page.locator("input[name='email'], input[type='email']").first
|
|
106
|
-
if email_inp.is_visible(timeout=5000):
|
|
107
|
-
email_inp.fill(args.email)
|
|
108
|
-
time.sleep(0.3)
|
|
109
|
-
|
|
110
|
-
pass_inp = page.locator("input[type='password']").first
|
|
111
|
-
if pass_inp.is_visible(timeout=3000):
|
|
112
|
-
pass_inp.fill(args.password)
|
|
113
|
-
time.sleep(0.3)
|
|
114
|
-
|
|
115
|
-
for sel in ["button[type='submit']", "button:has-text('Log in')", "button:has-text('Sign in')"]:
|
|
116
|
-
try:
|
|
117
|
-
btn = page.locator(sel).first
|
|
118
|
-
if btn.is_visible(timeout=2000):
|
|
119
|
-
btn.click()
|
|
120
|
-
break
|
|
121
|
-
except Exception:
|
|
122
|
-
continue
|
|
123
|
-
|
|
124
|
-
log("Login submitted, waiting...")
|
|
125
|
-
time.sleep(6)
|
|
126
|
-
except Exception as e:
|
|
127
|
-
log(f"Login form error: {e}")
|
|
128
|
-
|
|
129
|
-
log(f"Current URL: {page.url}")
|
|
130
|
-
page.screenshot(path="/tmp/cf_login_state.png")
|
|
131
|
-
|
|
132
|
-
# Check if logged in
|
|
133
|
-
if "dash.cloudflare.com" not in page.url:
|
|
134
|
-
die(f"Login failed, URL: {page.url}")
|
|
135
|
-
|
|
136
|
-
# Extract cookies
|
|
137
|
-
cookies_list = page.context.cookies()
|
|
138
|
-
cookies = {c['name']: c['value'] for c in cookies_list}
|
|
139
|
-
log(f"Got {len(cookies)} cookies: {list(cookies.keys())[:10]}")
|
|
140
|
-
|
|
141
|
-
# Create token via API
|
|
142
|
-
token = create_token_with_cookies(cookies, args.account_id)
|
|
143
|
-
|
|
144
|
-
if token:
|
|
145
|
-
log(f"TOKEN BERHASIL: {token[:12]}...")
|
|
146
|
-
print(json.dumps({
|
|
147
|
-
"status": "success",
|
|
148
|
-
"workers_ai_token": token,
|
|
149
|
-
"account_id": args.account_id,
|
|
150
|
-
"email": args.email
|
|
151
|
-
}), flush=True)
|
|
152
|
-
else:
|
|
153
|
-
page.screenshot(path="/tmp/cf_session_debug.png")
|
|
154
|
-
die("Token creation via session failed")
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if __name__ == "__main__":
|
|
158
|
-
main()
|