x / web.ts
stnh70's picture
Update web.ts
b8640a7 verified
// 迅雷X 注册服务 - Deno 实现
// 使用方法: deno run --allow-net pikpak_register.ts
import { serve } from "https://deno.land/std/http/server.ts";
// 导入 Node.js 的 crypto 模块
const { createHash } = await import('node:crypto');
// 正确实现 MD5 哈希函数
function md5Hash(message: string): string {
return createHash('md5').update(message).digest('hex');
}
// SHA1 哈希函数
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)}`); // 应该输出: 098f6bcd4621d373cade4e832627b4f6
console.log(`SHA1 哈希: ${sha1Hash(testStr)}`); // 应该输出: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
}
// 执行测试
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;
};
// // MD5 哈希 - 使用 TextEncoder 和 Uint8Array 手动实现
// async function md5Hash(message: string): Promise<string> {
// // 使用 Deno 内置的 crypto 模块
// const encoder = new TextEncoder();
// const data = encoder.encode(message);
// // 使用 SubtleCrypto 的 digest 方法计算 MD5
// // 注意:由于 WebCrypto API 不直接支持 MD5,我们使用一个替代方法
// const hashBuffer = await crypto.subtle.digest("SHA-256", data);
// // 转换为十六进制字符串
// return Array.from(new Uint8Array(hashBuffer))
// .map(b => b.toString(16).padStart(2, '0'))
// .join('');
// }
// // SHA1 哈希
// async function sha1Hash(message: string): Promise<string> {
// const encoder = new TextEncoder();
// const data = encoder.encode(message);
// const hashBuffer = await crypto.subtle.digest('SHA-1', data);
// return Array.from(new Uint8Array(hashBuffer))
// .map(b => b.toString(16).padStart(2, '0'))
// .join('');
// }
// 获取 UA key
async function getUaKey(deviceId: string): Promise<string> {
const rank1 = sha1Hash(`${deviceId}com.thunder.downloader1appkey`);
const rank2 = md5Hash(rank1);
return `${deviceId}${rank2}`;
}
// 获取 User Agent
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);
}
// API 请求函数
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);
// 处理 CORS
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": "*",
},
});
}
// 处理请求验证码 API
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);
// console.log(deviceId)
const timestamp = Date.now();
// 生成captcha签名
let orgStr = `${clientId}${version}com.thunder.downloader${deviceId}${timestamp}`;
let captchaSign = orgStr;
// 应用所有salt进行MD5哈希 - 与bash脚本保持一致
const salts = [
"kVy0WbPhiE4v6oxXZ88DvoA3Q",
"lON/AUoZKj8/nBtcE85mVbkOaVdVa",
"rLGffQrfBKH0BgwQ33yZofvO3Or",
"FO6HWqw",
"GbgvyA2",
"L1NU9QvIQIH7DTRt",
"y7llk4Y8WfYflt6",
"iuDp1WPbV3HRZudZtoXChxH4HNVBX5ZALe",
"8C28RTXmVcco0",
"X5Xh",
"7xe25YUgfGgD0xW3ezFS",
"",
"CKCR",
"8EmDjBo6h3eLaK7U6vU2Qys0NsMx",
"t2TeZBXKqbdP09Arh9C3"
];
// 按顺序应用所有salt
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'
};
// 1. 初始验证
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": "*",
},
});
}
// 2. 请求验证码
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": "*",
},
});
}
}
// 处理验证码验证和注册 API
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'
};
// 4. 验证验证码
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": "*",
},
});
}
// 5. 二次安全验证 - 关键修复点
timestamp = Date.now(); // 更新时间戳
let orgStr = `${clientId}${version}com.thunder.downloader${device_id}${timestamp}`;
let captchaSign = orgStr;
// 应用所有salt进行MD5哈希 - 与bash脚本保持一致
const salts = [
"kVy0WbPhiE4v6oxXZ88DvoA3Q",
"lON/AUoZKj8/nBtcE85mVbkOaVdVa",
"rLGffQrfBKH0BgwQ33yZofvO3Or",
"FO6HWqw",
"GbgvyA2",
"L1NU9QvIQIH7DTRt",
"y7llk4Y8WfYflt6",
"iuDp1WPbV3HRZudZtoXChxH4HNVBX5ZALe",
"8C28RTXmVcco0",
"X5Xh",
"7xe25YUgfGgD0xW3ezFS",
"",
"CKCR",
"8EmDjBo6h3eLaK7U6vU2Qys0NsMx",
"t2TeZBXKqbdP09Arh9C3"
];
// 按顺序应用所有salt
for (const salt of salts) {
captchaSign = md5Hash(`${captchaSign}${salt}`);
}
// 更新 User-Agent 和请求头,确保时间戳一致
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": "*",
},
});
}
// 6. 注册账号
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": "*",
},
});
}
}
// 从URL中提取文件名的辅助函数
function getFileNameFromUrl(url: string): string {
try {
// 尝试从磁力链接的dn参数中提取文件名
if (url.startsWith('magnet:')) {
const dnMatch = url.match(/&dn=([^&]+)/);
if (dnMatch && dnMatch[1]) {
return decodeURIComponent(dnMatch[1].replace(/\+/g, ' '));
}
}
// 如果不是磁力链接或没有dn参数,尝试从URL路径中提取
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const filename = pathname.split('/').pop() || '';
// 如果文件名为空或者没有扩展名,使用默认名称
if (!filename || filename.indexOf('.') === -1) {
return '未命名文件_' + Date.now();
}
// 解码URL编码的文件名
return decodeURIComponent(filename);
} catch (e) {
console.error("从URL提取文件名失败", e);
return '未命名文件_' + Date.now();
}
}
// ... existing code ...
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;
// 优先使用客户端提供的 device_id,如果没有则生成一个新的
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)}...`);
// 更新getUserAgent函数调用,确保与Python版本一致
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', // 修改为与Python代码一致
'Content-Type': 'application/json; charset=utf-8',
'Connection': 'Keep-Alive',
'Accept-Encoding': 'gzip'
};
// 生成captcha签名
let orgStr = `${clientId}${version}com.thunder.downloader${device_id}${timestamp}`;
let captchaSign = orgStr;
console.log(`开始生成captcha签名, 原始字符串: ${orgStr.substring(0, 20)}...`);
// 应用所有salt进行MD5哈希
const salts = [
"kVy0WbPhiE4v6oxXZ88DvoA3Q",
"lON/AUoZKj8/nBtcE85mVbkOaVdVa",
"rLGffQrfBKH0BgwQ33yZofvO3Or",
"FO6HWqw",
"GbgvyA2",
"L1NU9QvIQIH7DTRt",
"y7llk4Y8WfYflt6",
"iuDp1WPbV3HRZudZtoXChxH4HNVBX5ZALe",
"8C28RTXmVcco0",
"X5Xh",
"7xe25YUgfGgD0xW3ezFS",
"",
"CKCR",
"8EmDjBo6h3eLaK7U6vU2Qys0NsMx",
"t2TeZBXKqbdP09Arh9C3"
];
// 按顺序应用所有salt
for (const salt of salts) {
captchaSign = md5Hash(`${captchaSign}${salt}`);
// 可以添加调试输出
console.log(`Salt: ${salt}, Sign: ${captchaSign}`);
}
// 构建meta数据
// const meta = {
// captcha_sign: `1.${captchaSign}`,
// "package_name": "com.thunder.downloader",
// "client_version": version,
// "timestamp": timestamp.toString()
// };
const meta = {
captcha_sign: `1.${captchaSign}`,
user_id: "",
package_name: "com.thunder.downloader",
client_version: version,
timestamp: `${timestamp}`
};
// console.log("构建的meta数据:", JSON.stringify(meta));
// 构建初始化请求 - 使用GET:/drive/v1/files,与Python代码保持一致
const initPayload = {
"action": "GET:/drive/v1/files", // 修改为与Python代码一致的action
"captcha_token": "",
"client_id": clientId,
"device_id": device_id,
"meta": meta,
"redirect_uri": "xlaccsdk01://xbase.cloud/callback?state=harbor"
};
// console.log("构建的初始化请求payload:", JSON.stringify(initPayload));
// 获取验证码token
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也在url对象中
},
"parent_id": parent_id || "root"
};
console.log("准备的离线下载请求参数:", JSON.stringify(params));
// 添加验证码token到请求头
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": "*",
},
});
}
}
// 404 处理
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 });