|
|
|
|
|
|
|
|
|
|
|
import { serve } from "https://deno.land/std/http/server.ts"; |
|
|
|
|
|
const { createHash } = await import('node:crypto'); |
|
|
|
|
|
|
|
|
function md5Hash(message: string): string { |
|
|
return createHash('md5').update(message).digest('hex'); |
|
|
} |
|
|
|
|
|
|
|
|
function sha1Hash(message: string): string { |
|
|
return createHash('sha1').update(message).digest('hex'); |
|
|
} |
|
|
|
|
|
|
|
|
function testHash() { |
|
|
const testStr = "9527lampa_device_17455140938907j4r3gwsxud1745539125729"; |
|
|
console.log(`测试字符串: ${testStr}`); |
|
|
console.log(`MD5 哈希: ${md5Hash(testStr)}`); |
|
|
console.log(`SHA1 哈希: ${sha1Hash(testStr)}`); |
|
|
} |
|
|
|
|
|
|
|
|
testHash(); |
|
|
|
|
|
const generateRandomString = (length: number = 12): string => { |
|
|
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; |
|
|
let result = ''; |
|
|
const randomValues = new Uint8Array(length); |
|
|
crypto.getRandomValues(randomValues); |
|
|
for (let i = 0; i < length; i++) { |
|
|
result += chars[randomValues[i] % chars.length]; |
|
|
} |
|
|
return result; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function getUaKey(deviceId: string): Promise<string> { |
|
|
const rank1 = sha1Hash(`${deviceId}com.thunder.downloader1appkey`); |
|
|
const rank2 = md5Hash(rank1); |
|
|
return `${deviceId}${rank2}`; |
|
|
} |
|
|
|
|
|
|
|
|
function getUserAgent( |
|
|
clientId: string, |
|
|
deviceId: string, |
|
|
uaKey: string, |
|
|
timestamp: number, |
|
|
phoneModel: string, |
|
|
phoneBuilder: string, |
|
|
version: string |
|
|
): string { |
|
|
return `ANDROID-com.thunder.downloader/${version} protocolversion/200 accesstype/ clientid/${clientId} clientversion/${version} action_type/ networktype/WIFI sessionid/ deviceid/${deviceId} providername/NONE devicesign/div101.${uaKey} refresh_token/ sdkversion/2.0.3.203100 datetime/${timestamp} usrno/ appname/android-com.thunder.downloader session_origin/ grant_type/ appid/ clientip/ devicename/${phoneBuilder}_${phoneModel} osversion/13 platformversion/10 accessmode/ devicemodel/${phoneModel}`; |
|
|
} |
|
|
|
|
|
|
|
|
function checkPassword(password: string): boolean { |
|
|
return password.length >= 8 && |
|
|
/[0-9]/.test(password) && |
|
|
/[A-Z]/.test(password) && |
|
|
/[a-z]/.test(password); |
|
|
} |
|
|
|
|
|
|
|
|
async function apiRequest( |
|
|
method: string, |
|
|
url: string, |
|
|
data?: any, |
|
|
headers?: Record<string, string> |
|
|
): Promise<any> { |
|
|
const options: RequestInit = { |
|
|
method, |
|
|
headers: { |
|
|
'Content-Type': 'application/json; charset=utf-8', |
|
|
...headers, |
|
|
}, |
|
|
}; |
|
|
|
|
|
if (data) { |
|
|
options.body = JSON.stringify(data); |
|
|
} |
|
|
|
|
|
try { |
|
|
const response = await fetch(url, options); |
|
|
const responseData = await response.json(); |
|
|
|
|
|
if (!response.ok) { |
|
|
throw new Error(`请求失败 (HTTP ${response.status}): ${JSON.stringify(responseData)}`); |
|
|
} |
|
|
|
|
|
if (responseData.error) { |
|
|
throw new Error(`API错误: ${responseData.error.message || JSON.stringify(responseData.error)}`); |
|
|
} |
|
|
|
|
|
return responseData; |
|
|
} catch (error) { |
|
|
console.error(`请求错误: ${error.message}`); |
|
|
throw error; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
async function handleRequest(request: Request): Promise<Response> { |
|
|
const url = new URL(request.url); |
|
|
|
|
|
|
|
|
if (request.method === "OPTIONS") { |
|
|
return new Response(null, { |
|
|
status: 204, |
|
|
headers: { |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
"Access-Control-Allow-Methods": "POST, GET, OPTIONS", |
|
|
"Access-Control-Allow-Headers": "Content-Type", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
if (url.pathname === "/" || url.pathname === "") { |
|
|
const html = ` |
|
|
<!DOCTYPE html> |
|
|
<html> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>迅雷X 注册服务</title> |
|
|
<style> |
|
|
body { |
|
|
font-family: Arial, sans-serif; |
|
|
max-width: 600px; |
|
|
margin: 0 auto; |
|
|
padding: 20px; |
|
|
} |
|
|
h1 { |
|
|
color: #333; |
|
|
} |
|
|
.form-group { |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
label { |
|
|
display: block; |
|
|
margin-bottom: 5px; |
|
|
} |
|
|
input[type="email"], input[type="text"], input[type="password"] { |
|
|
width: 100%; |
|
|
padding: 8px; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 4px; |
|
|
} |
|
|
button { |
|
|
background-color: #4CAF50; |
|
|
color: white; |
|
|
padding: 10px 15px; |
|
|
border: none; |
|
|
border-radius: 4px; |
|
|
cursor: pointer; |
|
|
} |
|
|
button:hover { |
|
|
background-color: #45a049; |
|
|
} |
|
|
#result, #verification-section { |
|
|
margin-top: 20px; |
|
|
padding: 10px; |
|
|
border: 1px solid #ddd; |
|
|
border-radius: 4px; |
|
|
display: none; |
|
|
} |
|
|
.steps { |
|
|
margin-top: 20px; |
|
|
} |
|
|
.step { |
|
|
display: none; |
|
|
} |
|
|
.step.active { |
|
|
display: block; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<h1>迅雷X 注册服务</h1> |
|
|
|
|
|
<div class="steps"> |
|
|
<!-- 步骤1: 输入邮箱和密码 --> |
|
|
<div id="step1" class="step active"> |
|
|
<h2>步骤1: 输入邮箱和密码</h2> |
|
|
<div class="form-group"> |
|
|
<label for="email">邮箱地址:</label> |
|
|
<input type="email" id="email" required> |
|
|
</div> |
|
|
<div class="form-group"> |
|
|
<label for="password">密码 (至少8位,包含数字、大小写字母):</label> |
|
|
<input type="password" id="password" placeholder="留空则自动生成强密码"> |
|
|
</div> |
|
|
<button id="request-code">请求验证码</button> |
|
|
</div> |
|
|
|
|
|
<!-- 步骤2: 输入验证码 --> |
|
|
<div id="step2" class="step"> |
|
|
<h2>步骤2: 输入验证码</h2> |
|
|
<p>验证码已发送到您的邮箱,请查收并输入:</p> |
|
|
<div class="form-group"> |
|
|
<label for="code">验证码:</label> |
|
|
<input type="text" id="code" required> |
|
|
</div> |
|
|
<button id="verify-code">验证并注册</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="result"></div> |
|
|
|
|
|
<script> |
|
|
// 全局变量存储注册过程中的数据 |
|
|
let registrationData = { |
|
|
email: '', |
|
|
password: '', |
|
|
verificationId: '', |
|
|
captchaToken: '', |
|
|
deviceId: '' |
|
|
}; |
|
|
|
|
|
// 步骤1: 请求验证码 |
|
|
document.getElementById('request-code').addEventListener('click', async () => { |
|
|
const email = document.getElementById('email').value; |
|
|
const password = document.getElementById('password').value; |
|
|
|
|
|
if (!email) { |
|
|
alert('请填写邮箱'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
const resultDiv = document.getElementById('result'); |
|
|
resultDiv.style.display = 'block'; |
|
|
resultDiv.innerHTML = '<p>正在请求验证码,请稍候...</p>'; |
|
|
|
|
|
const response = await fetch('/api/request-code', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify({ email, password }) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (data.success) { |
|
|
// 保存数据 |
|
|
registrationData.email = email; |
|
|
registrationData.password = password; |
|
|
registrationData.verificationId = data.verification_id; |
|
|
registrationData.captchaToken = data.captcha_token; |
|
|
registrationData.deviceId = data.device_id; |
|
|
|
|
|
// 显示步骤2 |
|
|
document.getElementById('step1').classList.remove('active'); |
|
|
document.getElementById('step2').classList.add('active'); |
|
|
|
|
|
resultDiv.innerHTML = '<p>验证码已发送到您的邮箱,请查收</p>'; |
|
|
} else { |
|
|
resultDiv.innerHTML = \`<p>请求验证码失败: \${data.error}</p>\`; |
|
|
} |
|
|
} catch (error) { |
|
|
document.getElementById('result').innerHTML = \`<p>请求验证码失败: \${error.message}</p>\`; |
|
|
} |
|
|
}); |
|
|
|
|
|
// 步骤2: 验证验证码并完成注册 |
|
|
document.getElementById('verify-code').addEventListener('click', async () => { |
|
|
const code = document.getElementById('code').value; |
|
|
|
|
|
if (!code) { |
|
|
alert('请填写验证码'); |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
const resultDiv = document.getElementById('result'); |
|
|
resultDiv.innerHTML = '<p>正在验证并注册,请稍候...</p>'; |
|
|
|
|
|
const response = await fetch('/api/verify-and-register', { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json' |
|
|
}, |
|
|
body: JSON.stringify({ |
|
|
email: registrationData.email, |
|
|
password: registrationData.password, |
|
|
verification_id: registrationData.verificationId, |
|
|
verification_code: code, |
|
|
captcha_token: registrationData.captchaToken, |
|
|
device_id: registrationData.deviceId |
|
|
}) |
|
|
}); |
|
|
|
|
|
const data = await response.json(); |
|
|
|
|
|
if (data.success) { |
|
|
resultDiv.innerHTML = \` |
|
|
<h3>注册成功!</h3> |
|
|
<p><strong>邮箱:</strong> \${data.email}</p> |
|
|
<p><strong>密码:</strong> \${data.password}</p> |
|
|
<p><strong>用户ID:</strong> \${data.user_id}</p> |
|
|
\`; |
|
|
} else { |
|
|
resultDiv.innerHTML = \` |
|
|
<h3>注册失败</h3> |
|
|
<p>\${data.error}</p> |
|
|
\`; |
|
|
} |
|
|
} catch (error) { |
|
|
document.getElementById('result').innerHTML = \`<p>注册失败: \${error.message}</p>\`; |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
`; |
|
|
|
|
|
return new Response(html, { |
|
|
headers: { |
|
|
"Content-Type": "text/html; charset=utf-8", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
if (url.pathname === "/api/request-code") { |
|
|
if (request.method !== "POST") { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "只支持 POST 请求" |
|
|
}), { |
|
|
status: 405, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
try { |
|
|
const body = await request.json(); |
|
|
const { email, password } = body; |
|
|
|
|
|
if (!email) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "邮箱不能为空" |
|
|
}), { |
|
|
status: 400, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const version = "1.06.0.2132"; |
|
|
const clientId = "ZQL_zwA4qhHcoe_2"; |
|
|
const deviceId = generateRandomString(32); |
|
|
|
|
|
const timestamp = Date.now(); |
|
|
|
|
|
|
|
|
let orgStr = `${clientId}${version}com.thunder.downloader${deviceId}${timestamp}`; |
|
|
let captchaSign = orgStr; |
|
|
|
|
|
|
|
|
const salts = [ |
|
|
"kVy0WbPhiE4v6oxXZ88DvoA3Q", |
|
|
"lON/AUoZKj8/nBtcE85mVbkOaVdVa", |
|
|
"rLGffQrfBKH0BgwQ33yZofvO3Or", |
|
|
"FO6HWqw", |
|
|
"GbgvyA2", |
|
|
"L1NU9QvIQIH7DTRt", |
|
|
"y7llk4Y8WfYflt6", |
|
|
"iuDp1WPbV3HRZudZtoXChxH4HNVBX5ZALe", |
|
|
"8C28RTXmVcco0", |
|
|
"X5Xh", |
|
|
"7xe25YUgfGgD0xW3ezFS", |
|
|
"", |
|
|
"CKCR", |
|
|
"8EmDjBo6h3eLaK7U6vU2Qys0NsMx", |
|
|
"t2TeZBXKqbdP09Arh9C3" |
|
|
]; |
|
|
|
|
|
|
|
|
for (const salt of salts) { |
|
|
captchaSign = md5Hash(`${captchaSign}${salt}`); |
|
|
|
|
|
console.log(`Salt: ${salt}, Sign: ${captchaSign}`); |
|
|
} |
|
|
|
|
|
|
|
|
const phoneModel = "MI-ONE"; |
|
|
const phoneBuilder = "XIAOMI"; |
|
|
const uaKey = await getUaKey(deviceId); |
|
|
const userAgent = getUserAgent(clientId, deviceId, uaKey, timestamp, phoneModel, phoneBuilder, version); |
|
|
|
|
|
|
|
|
const commonHeaders = { |
|
|
'X-Device-Id': deviceId, |
|
|
'User-Agent': userAgent, |
|
|
'Accept-Language': 'zh', |
|
|
'Content-Type': 'application/json; charset=utf-8', |
|
|
'Connection': 'Keep-Alive', |
|
|
'Accept-Encoding': 'gzip' |
|
|
}; |
|
|
|
|
|
|
|
|
const initUrl = "https://xluser-ssl.xunleix.com/v1/shield/captcha/init"; |
|
|
const initPayload = { |
|
|
action: "POST:/v1/auth/verification", |
|
|
captcha_token: "", |
|
|
client_id: clientId, |
|
|
device_id: deviceId, |
|
|
meta: { email }, |
|
|
redirect_uri: "xlaccsdk01://xbase.cloud/callback?state=harbor" |
|
|
}; |
|
|
|
|
|
const initResponse = await apiRequest("POST", initUrl, initPayload, commonHeaders); |
|
|
const captchaToken = initResponse.captcha_token; |
|
|
|
|
|
if (!captchaToken) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "无法获取captcha_token" |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const verificationUrl = "https://xluser-ssl.xunleix.com/v1/auth/verification"; |
|
|
const verificationPayload = { |
|
|
captcha_token: captchaToken, |
|
|
email: email, |
|
|
locale: "zh-CN", |
|
|
target: "ANY", |
|
|
client_id: clientId |
|
|
}; |
|
|
|
|
|
const verificationResponse = await apiRequest("POST", verificationUrl, verificationPayload, commonHeaders); |
|
|
const verificationId = verificationResponse.verification_id; |
|
|
|
|
|
if (!verificationId) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "无法获取验证ID" |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
return new Response(JSON.stringify({ |
|
|
success: true, |
|
|
message: "验证码已发送到邮箱,请查收", |
|
|
verification_id: verificationId, |
|
|
captcha_token: captchaToken, |
|
|
device_id: deviceId |
|
|
}), { |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} catch (error) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: error.message |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (url.pathname === "/api/verify-and-register") { |
|
|
if (request.method !== "POST") { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "只支持 POST 请求" |
|
|
}), { |
|
|
status: 405, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
try { |
|
|
const body = await request.json(); |
|
|
const { |
|
|
email, |
|
|
password, |
|
|
verification_id, |
|
|
verification_code, |
|
|
captcha_token, |
|
|
device_id |
|
|
} = body; |
|
|
|
|
|
if (!email || !verification_id || !verification_code || !captcha_token || !device_id) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "缺少必要参数" |
|
|
}), { |
|
|
status: 400, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const version = "1.06.0.2132"; |
|
|
const clientId = "ZQL_zwA4qhHcoe_2"; |
|
|
const clientSecret = "Og9Vr1L8Ee6bh0olFxFDRg"; |
|
|
let timestamp = Date.now(); |
|
|
|
|
|
|
|
|
const phoneModel = "MI-ONE"; |
|
|
const phoneBuilder = "XIAOMI"; |
|
|
const uaKey = await getUaKey(device_id); |
|
|
const userAgent = getUserAgent(clientId, device_id, uaKey, timestamp, phoneModel, phoneBuilder, version); |
|
|
|
|
|
|
|
|
const commonHeaders = { |
|
|
'X-Device-Id': device_id, |
|
|
'User-Agent': userAgent, |
|
|
'Accept-Language': 'zh', |
|
|
'Content-Type': 'application/json; charset=utf-8', |
|
|
'Connection': 'Keep-Alive', |
|
|
'Accept-Encoding': 'gzip' |
|
|
}; |
|
|
|
|
|
|
|
|
const verifyUrl = "https://xluser-ssl.xunleix.com/v1/auth/verification/verify"; |
|
|
const verifyPayload = { |
|
|
client_id: clientId, |
|
|
verification_id: verification_id, |
|
|
verification_code: verification_code |
|
|
}; |
|
|
|
|
|
const verifyResponse = await apiRequest("POST", verifyUrl, verifyPayload, commonHeaders); |
|
|
const verificationToken = verifyResponse.verification_token; |
|
|
|
|
|
if (!verificationToken) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "验证码验证失败" |
|
|
}), { |
|
|
status: 400, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
timestamp = Date.now(); |
|
|
let orgStr = `${clientId}${version}com.thunder.downloader${device_id}${timestamp}`; |
|
|
let captchaSign = orgStr; |
|
|
|
|
|
|
|
|
const salts = [ |
|
|
"kVy0WbPhiE4v6oxXZ88DvoA3Q", |
|
|
"lON/AUoZKj8/nBtcE85mVbkOaVdVa", |
|
|
"rLGffQrfBKH0BgwQ33yZofvO3Or", |
|
|
"FO6HWqw", |
|
|
"GbgvyA2", |
|
|
"L1NU9QvIQIH7DTRt", |
|
|
"y7llk4Y8WfYflt6", |
|
|
"iuDp1WPbV3HRZudZtoXChxH4HNVBX5ZALe", |
|
|
"8C28RTXmVcco0", |
|
|
"X5Xh", |
|
|
"7xe25YUgfGgD0xW3ezFS", |
|
|
"", |
|
|
"CKCR", |
|
|
"8EmDjBo6h3eLaK7U6vU2Qys0NsMx", |
|
|
"t2TeZBXKqbdP09Arh9C3" |
|
|
]; |
|
|
|
|
|
|
|
|
for (const salt of salts) { |
|
|
captchaSign = md5Hash(`${captchaSign}${salt}`); |
|
|
} |
|
|
|
|
|
|
|
|
const updatedUaKey = await getUaKey(device_id); |
|
|
const updatedUserAgent = getUserAgent(clientId, device_id, updatedUaKey, timestamp, phoneModel, phoneBuilder, version); |
|
|
|
|
|
const updatedHeaders = { |
|
|
'X-Device-Id': device_id, |
|
|
'User-Agent': updatedUserAgent, |
|
|
'Accept-Language': 'zh', |
|
|
'Content-Type': 'application/json; charset=utf-8', |
|
|
'Connection': 'Keep-Alive', |
|
|
'Accept-Encoding': 'gzip' |
|
|
}; |
|
|
|
|
|
const meta1 = { |
|
|
captcha_sign: `1.${captchaSign}`, |
|
|
user_id: "", |
|
|
package_name: "com.thunder.downloader", |
|
|
client_version: version, |
|
|
timestamp: `${timestamp}` |
|
|
}; |
|
|
|
|
|
const initUrl = "https://xluser-ssl.xunleix.com/v1/shield/captcha/init"; |
|
|
const initPayload2 = { |
|
|
action: "POST:/v1/auth/signup", |
|
|
captcha_token: captcha_token, |
|
|
client_id: clientId, |
|
|
device_id: device_id, |
|
|
meta: meta1, |
|
|
redirect_uri: "xlaccsdk01://xbase.cloud/callback?state=harbor" |
|
|
}; |
|
|
|
|
|
const initResponse2 = await apiRequest("POST", initUrl, initPayload2, updatedHeaders); |
|
|
const newCaptchaToken = initResponse2.captcha_token; |
|
|
|
|
|
if (!newCaptchaToken) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "无法获取二次验证token" |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const name = email.split('@')[0]; |
|
|
|
|
|
|
|
|
let finalPassword: string; |
|
|
|
|
|
if (password && checkPassword(password)) { |
|
|
|
|
|
finalPassword = password; |
|
|
} else { |
|
|
|
|
|
finalPassword = generateRandomString(12) + |
|
|
generateRandomString(1).toUpperCase() + |
|
|
generateRandomString(1).toLowerCase() + |
|
|
Math.floor(Math.random() * 10); |
|
|
} |
|
|
|
|
|
const signupUrl = "https://xluser-ssl.xunleix.com/v1/auth/signup"; |
|
|
const signupPayload = { |
|
|
captcha_token: newCaptchaToken, |
|
|
client_id: clientId, |
|
|
client_secret: clientSecret, |
|
|
email: email, |
|
|
name: name, |
|
|
password: finalPassword, |
|
|
verification_token: verificationToken |
|
|
}; |
|
|
|
|
|
const signupResponse = await apiRequest("POST", signupUrl, signupPayload, updatedHeaders); |
|
|
const userId = signupResponse.sub; |
|
|
|
|
|
if (!userId) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "注册失败 - 响应中没有用户ID" |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
return new Response(JSON.stringify({ |
|
|
success: true, |
|
|
email: email, |
|
|
password: finalPassword, |
|
|
user_id: userId |
|
|
}), { |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} catch (error) { |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: error.message |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
function getFileNameFromUrl(url: string): string { |
|
|
try { |
|
|
|
|
|
if (url.startsWith('magnet:')) { |
|
|
const dnMatch = url.match(/&dn=([^&]+)/); |
|
|
if (dnMatch && dnMatch[1]) { |
|
|
return decodeURIComponent(dnMatch[1].replace(/\+/g, ' ')); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const urlObj = new URL(url); |
|
|
const pathname = urlObj.pathname; |
|
|
const filename = pathname.split('/').pop() || ''; |
|
|
|
|
|
|
|
|
if (!filename || filename.indexOf('.') === -1) { |
|
|
return '未命名文件_' + Date.now(); |
|
|
} |
|
|
|
|
|
|
|
|
return decodeURIComponent(filename); |
|
|
} catch (e) { |
|
|
console.error("从URL提取文件名失败", e); |
|
|
return '未命名文件_' + Date.now(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (url.pathname === "/api/offline-download") { |
|
|
console.log("收到离线下载请求"); |
|
|
if (request.method !== "POST") { |
|
|
console.log(`请求方法错误: ${request.method}`); |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "只支持 POST 请求" |
|
|
}), { |
|
|
status: 405, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
try { |
|
|
console.log("开始解析请求体"); |
|
|
const body = await request.json(); |
|
|
console.log("请求体内容:", JSON.stringify(body)); |
|
|
|
|
|
const { file_url, parent_id, name, access_token } = body; |
|
|
|
|
|
const device_id = generateRandomString(32); |
|
|
console.log(`设备ID: ${device_id}, 是否生成新ID: ${!body.device_id}`); |
|
|
|
|
|
if (!file_url || !access_token) { |
|
|
console.log("缺少必要参数:", { |
|
|
has_file_url: !!file_url, |
|
|
has_access_token: !!access_token |
|
|
}); |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "缺少必要参数" |
|
|
}), { |
|
|
status: 400, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const version = "1.06.0.2132"; |
|
|
const clientId = "ZQL_zwA4qhHcoe_2"; |
|
|
const timestamp = Date.now(); |
|
|
console.log(`版本信息: ${version}, 客户端ID: ${clientId}, 时间戳: ${timestamp}`); |
|
|
|
|
|
|
|
|
const phoneModel = "MI-ONE"; |
|
|
const phoneBuilder = "XIAOMI"; |
|
|
console.log("开始获取uaKey"); |
|
|
const uaKey = await getUaKey(device_id); |
|
|
console.log(`获取到uaKey: ${uaKey.substring(0, 10)}...`); |
|
|
|
|
|
|
|
|
const userAgent = getUserAgent(clientId, device_id, uaKey, timestamp, phoneModel, phoneBuilder, version); |
|
|
console.log(`生成的UserAgent: ${userAgent}`); |
|
|
|
|
|
|
|
|
const commonHeaders = { |
|
|
'Authorization': `Bearer ${access_token}`, |
|
|
'X-Device-Id': device_id, |
|
|
'User-Agent': userAgent, |
|
|
'Accept-Language': 'zh', |
|
|
'Content-Type': 'application/json; charset=utf-8', |
|
|
'Connection': 'Keep-Alive', |
|
|
'Accept-Encoding': 'gzip' |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
let orgStr = `${clientId}${version}com.thunder.downloader${device_id}${timestamp}`; |
|
|
let captchaSign = orgStr; |
|
|
console.log(`开始生成captcha签名, 原始字符串: ${orgStr.substring(0, 20)}...`); |
|
|
|
|
|
|
|
|
const salts = [ |
|
|
"kVy0WbPhiE4v6oxXZ88DvoA3Q", |
|
|
"lON/AUoZKj8/nBtcE85mVbkOaVdVa", |
|
|
"rLGffQrfBKH0BgwQ33yZofvO3Or", |
|
|
"FO6HWqw", |
|
|
"GbgvyA2", |
|
|
"L1NU9QvIQIH7DTRt", |
|
|
"y7llk4Y8WfYflt6", |
|
|
"iuDp1WPbV3HRZudZtoXChxH4HNVBX5ZALe", |
|
|
"8C28RTXmVcco0", |
|
|
"X5Xh", |
|
|
"7xe25YUgfGgD0xW3ezFS", |
|
|
"", |
|
|
"CKCR", |
|
|
"8EmDjBo6h3eLaK7U6vU2Qys0NsMx", |
|
|
"t2TeZBXKqbdP09Arh9C3" |
|
|
]; |
|
|
|
|
|
|
|
|
for (const salt of salts) { |
|
|
captchaSign = md5Hash(`${captchaSign}${salt}`); |
|
|
|
|
|
console.log(`Salt: ${salt}, Sign: ${captchaSign}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const meta = { |
|
|
captcha_sign: `1.${captchaSign}`, |
|
|
user_id: "", |
|
|
package_name: "com.thunder.downloader", |
|
|
client_version: version, |
|
|
timestamp: `${timestamp}` |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const initPayload = { |
|
|
"action": "GET:/drive/v1/files", |
|
|
"captcha_token": "", |
|
|
"client_id": clientId, |
|
|
"device_id": device_id, |
|
|
"meta": meta, |
|
|
"redirect_uri": "xlaccsdk01://xbase.cloud/callback?state=harbor" |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
console.log("开始请求验证码token"); |
|
|
let captchaResponse; |
|
|
try { |
|
|
captchaResponse = await apiRequest( |
|
|
"POST", |
|
|
"https://xluser-ssl.xunleix.com/v1/shield/captcha/init", |
|
|
initPayload, |
|
|
commonHeaders |
|
|
); |
|
|
console.log("获取到验证码token响应:", JSON.stringify(captchaResponse)); |
|
|
} catch (err) { |
|
|
console.error("获取验证码token失败:", err); |
|
|
throw new Error(`获取验证码token失败: ${err.message}`); |
|
|
} |
|
|
|
|
|
if (!captchaResponse.captcha_token) { |
|
|
console.error("验证码token不存在:", JSON.stringify(captchaResponse)); |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "获取验证码token失败" |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
const params = { |
|
|
"kind": "drive#file", |
|
|
"name": name || getFileNameFromUrl(file_url) || "未命名文件", |
|
|
"upload_type": "UPLOAD_TYPE_URL", |
|
|
"url": { |
|
|
"url": file_url, |
|
|
"parent_id": parent_id || "root" |
|
|
}, |
|
|
"parent_id": parent_id || "root" |
|
|
}; |
|
|
console.log("准备的离线下载请求参数:", JSON.stringify(params)); |
|
|
|
|
|
|
|
|
const downloadHeaders = { |
|
|
...commonHeaders, |
|
|
'X-Captcha-Token': captchaResponse.captcha_token |
|
|
}; |
|
|
console.log("离线下载请求头:", JSON.stringify({ |
|
|
'X-Captcha-Token': captchaResponse.captcha_token, |
|
|
'Authorization': `Bearer ${access_token.substring(0, 10)}...`, |
|
|
})); |
|
|
|
|
|
|
|
|
console.log("开始发送离线下载请求"); |
|
|
let downloadResponse; |
|
|
try { |
|
|
downloadResponse = await apiRequest( |
|
|
"POST", |
|
|
"https://api-pan.xunleix.com/drive/v1/files", |
|
|
params, |
|
|
downloadHeaders |
|
|
); |
|
|
console.log("离线下载请求成功:", JSON.stringify(downloadResponse)); |
|
|
} catch (err) { |
|
|
console.error("离线下载请求失败:", err); |
|
|
throw new Error(`离线下载请求失败: ${err.message}`); |
|
|
} |
|
|
|
|
|
return new Response(JSON.stringify({ |
|
|
success: true, |
|
|
message: "离线下载任务已创建", |
|
|
data: downloadResponse |
|
|
}), { |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} catch (error) { |
|
|
console.error("离线下载错误:", error); |
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: `离线下载失败: ${error.message}` |
|
|
}), { |
|
|
status: 500, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return new Response(JSON.stringify({ |
|
|
success: false, |
|
|
error: "Not Found" |
|
|
}), { |
|
|
status: 404, |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Access-Control-Allow-Origin": "*", |
|
|
}, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
console.log("迅雷X 注册服务已启动,监听端口 8000..."); |
|
|
await serve(handleRequest, { port: 8000 }); |