// 迅雷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 { // // 使用 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 { // 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 { 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 ): Promise { 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 { 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 = ` 迅雷X 注册服务

迅雷X 注册服务

步骤1: 输入邮箱和密码

步骤2: 输入验证码

验证码已发送到您的邮箱,请查收并输入:

`; 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 });