From 949f8a712f7fba3f6f86052794f2b93078b6abf7 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Thu, 7 May 2026 15:49:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=9F=E4=BA=A7=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E4=BD=BF=E7=94=A8=E7=BD=91=E7=9B=98=E6=9C=89=E6=95=88?= =?UTF-8?q?=E6=80=A7=E6=A3=80=E6=B5=8B=E6=8A=A5=E9=94=99=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=89=BE=E5=88=B0=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/pancheck/index.ts | 60 +++++-------- .../vendor/checkers/{aliyun.js => aliyun.ts} | 25 +++--- .../vendor/checkers/{baidu.js => baidu.ts} | 42 +++------ .../vendor/checkers/{cmcc.js => cmcc.ts} | 54 +++--------- .../vendor/checkers/{http.js => http.ts} | 21 +++-- .../vendor/checkers/{pan115.js => pan115.ts} | 24 ++--- .../vendor/checkers/{pan123.js => pan123.ts} | 34 ++------ .../vendor/checkers/{quark.js => quark.ts} | 29 +++---- .../vendor/checkers/{tianyi.js => tianyi.ts} | 31 ++----- src/lib/pancheck/vendor/checkers/uc.js | 58 ------------- src/lib/pancheck/vendor/checkers/uc.ts | 44 ++++++++++ .../vendor/checkers/{xunlei.js => xunlei.ts} | 87 ++++++------------- 12 files changed, 178 insertions(+), 331 deletions(-) rename src/lib/pancheck/vendor/checkers/{aliyun.js => aliyun.ts} (75%) rename src/lib/pancheck/vendor/checkers/{baidu.js => baidu.ts} (79%) rename src/lib/pancheck/vendor/checkers/{cmcc.js => cmcc.ts} (65%) rename src/lib/pancheck/vendor/checkers/{http.js => http.ts} (74%) rename src/lib/pancheck/vendor/checkers/{pan115.js => pan115.ts} (80%) rename src/lib/pancheck/vendor/checkers/{pan123.js => pan123.ts} (62%) rename src/lib/pancheck/vendor/checkers/{quark.js => quark.ts} (80%) rename src/lib/pancheck/vendor/checkers/{tianyi.js => tianyi.ts} (74%) delete mode 100644 src/lib/pancheck/vendor/checkers/uc.js create mode 100644 src/lib/pancheck/vendor/checkers/uc.ts rename src/lib/pancheck/vendor/checkers/{xunlei.js => xunlei.ts} (71%) diff --git a/src/lib/pancheck/index.ts b/src/lib/pancheck/index.ts index 6df98cf..7a69d59 100644 --- a/src/lib/pancheck/index.ts +++ b/src/lib/pancheck/index.ts @@ -9,9 +9,15 @@ import { setNetdiskCheckInflight, } from './cache'; import type { NetdiskCheckPlatform, NetdiskCheckResult } from './types'; - -// eslint-disable-next-line no-eval -const nodeRequire = eval('require') as NodeRequire; +import { check115 } from './vendor/checkers/pan115'; +import { checkAliyun } from './vendor/checkers/aliyun'; +import { checkBaidu } from './vendor/checkers/baidu'; +import { checkCMCC } from './vendor/checkers/cmcc'; +import { check123 } from './vendor/checkers/pan123'; +import { checkQuark } from './vendor/checkers/quark'; +import { checkTianyi } from './vendor/checkers/tianyi'; +import { checkUC } from './vendor/checkers/uc'; +import { checkXunlei } from './vendor/checkers/xunlei'; const RATE_LIMIT_REASON_PATTERNS = [/频率限制/i, /请求过快/i, /rate.?limit/i, /too many/i, /风控/i]; @@ -21,10 +27,6 @@ type RawCheckerResult = { isRateLimited?: boolean; }; -type CheckerModule = { - [key: string]: (url: string) => Promise; -}; - const PLATFORM_PATTERNS: Record = { '115': [/115(?:cdn)?\.com\/s\//i, /anxia\.com\/s\//i], quark: [/pan\.quark\.cn\/s\//i, /pan\.qoark\.cn\/s\//i], @@ -37,37 +39,18 @@ const PLATFORM_PATTERNS: Record = { cmcc: [/yun\.139\.com\/shareweb/i, /caiyun\.139\.com\/m\/i/i], }; -const CHECKER_EXPORTS: Record = { - '115': { modulePath: '@/lib/pancheck/vendor/checkers/pan115.js', exportName: 'check115' }, - aliyun: { modulePath: '@/lib/pancheck/vendor/checkers/aliyun.js', exportName: 'checkAliyun' }, - baidu: { modulePath: '@/lib/pancheck/vendor/checkers/baidu.js', exportName: 'checkBaidu' }, - cmcc: { modulePath: '@/lib/pancheck/vendor/checkers/cmcc.js', exportName: 'checkCMCC' }, - pan123: { modulePath: '@/lib/pancheck/vendor/checkers/pan123.js', exportName: 'check123' }, - quark: { modulePath: '@/lib/pancheck/vendor/checkers/quark.js', exportName: 'checkQuark' }, - tianyi: { modulePath: '@/lib/pancheck/vendor/checkers/tianyi.js', exportName: 'checkTianyi' }, - uc: { modulePath: '@/lib/pancheck/vendor/checkers/uc.js', exportName: 'checkUC' }, - xunlei: { modulePath: '@/lib/pancheck/vendor/checkers/xunlei.js', exportName: 'checkXunlei' }, +const CHECKERS: Record Promise> = { + '115': check115, + aliyun: checkAliyun, + baidu: checkBaidu, + cmcc: checkCMCC, + pan123: check123, + quark: checkQuark, + tianyi: checkTianyi, + uc: checkUC, + xunlei: checkXunlei, }; -const checkerFnCache = new Map Promise>(); - -function resolveModulePath(modulePath: string) { - return modulePath.replace(/^@\//, `${process.cwd()}/src/`); -} - -function getChecker(platform: NetdiskCheckPlatform) { - const cached = checkerFnCache.get(platform); - if (cached) return cached; - const config = CHECKER_EXPORTS[platform]; - const mod = nodeRequire(resolveModulePath(config.modulePath)) as CheckerModule; - const fn = mod[config.exportName]; - if (typeof fn !== 'function') { - throw new Error(`未找到 ${platform} 检测器`); - } - checkerFnCache.set(platform, fn); - return fn; -} - export function normalizeNetdiskCheckUrl(url: string) { return url.trim().replace(/\s+/g, '').replace(/\/+$/, ''); } @@ -156,7 +139,10 @@ export async function checkNetdiskLink(platform: NetdiskCheckPlatform, url: stri const runner = (async () => { const startedAt = Date.now(); try { - const checker = getChecker(platform); + const checker = CHECKERS[platform]; + if (typeof checker !== 'function') { + throw new Error(`未找到 ${platform} 检测器`); + } const raw = await checker(normalizedUrl); const finalResult = toFinalResult(platform, url, normalizedUrl, raw, Date.now() - startedAt); setCachedNetdiskCheckResult(cacheKey, finalResult); diff --git a/src/lib/pancheck/vendor/checkers/aliyun.js b/src/lib/pancheck/vendor/checkers/aliyun.ts similarity index 75% rename from src/lib/pancheck/vendor/checkers/aliyun.js rename to src/lib/pancheck/vendor/checkers/aliyun.ts index c504a32..ab696d0 100644 --- a/src/lib/pancheck/vendor/checkers/aliyun.js +++ b/src/lib/pancheck/vendor/checkers/aliyun.ts @@ -1,11 +1,8 @@ -const { request } = require('./http'); +// @ts-nocheck -/** - * 阿里云盘链接检测 - * URL格式: https://www.alipan.com/s/{share_id} 或 https://www.aliyundrive.com/s/{share_id} - * API: POST https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous - */ -async function checkAliyun(link) { +import { request } from './http'; + +export async function checkAliyun(link) { const { shareId, error: parseError } = extractParamsAliPan(link); if (parseError) { return { valid: false, reason: '链接格式无效: ' + parseError }; @@ -17,11 +14,11 @@ async function checkAliyun(link) { method: 'POST', body: { share_id: shareId }, headers: { - 'authorization': '', + authorization: '', 'Content-Type': 'application/json', - 'Origin': 'https://www.alipan.com', - 'Referer': 'https://www.alipan.com/', - 'Priority': 'u=1, i', + Origin: 'https://www.alipan.com', + Referer: 'https://www.alipan.com/', + Priority: 'u=1, i', 'Sec-Ch-Ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"Windows"', @@ -39,7 +36,7 @@ async function checkAliyun(link) { return { valid: false, reason: `API返回错误状态码: ${statusCode}` }; } - JSON.parse(body); // 验证可解析即可 + JSON.parse(body); return { valid: true, reason: '' }; } catch (err) { if (err.message === '请求超时') return { valid: false, reason: '请求超时' }; @@ -47,7 +44,7 @@ async function checkAliyun(link) { } } -function extractParamsAliPan(urlStr) { +export function extractParamsAliPan(urlStr) { try { const u = new URL(urlStr); const pathParts = u.pathname.replace(/\/+$/, '').split('/').filter(Boolean); @@ -63,5 +60,3 @@ function extractParamsAliPan(urlStr) { return { shareId: '', error: e.message }; } } - -module.exports = { checkAliyun, extractParamsAliPan }; diff --git a/src/lib/pancheck/vendor/checkers/baidu.js b/src/lib/pancheck/vendor/checkers/baidu.ts similarity index 79% rename from src/lib/pancheck/vendor/checkers/baidu.js rename to src/lib/pancheck/vendor/checkers/baidu.ts index 3b98af1..c8ac02b 100644 --- a/src/lib/pancheck/vendor/checkers/baidu.js +++ b/src/lib/pancheck/vendor/checkers/baidu.ts @@ -1,11 +1,8 @@ -const { request } = require('./http'); +// @ts-nocheck -/** - * 百度网盘链接检测 - * URL格式: https://pan.baidu.com/s/{surl}?pwd={password} - * 两步检测: 有密码时先验证密码获取randsk,再调用share/list - */ -async function checkBaidu(link) { +import { request } from './http'; + +export async function checkBaidu(link) { const normalizedLink = normalizeBaiduURL(link); if (!normalizedLink) { return { valid: false, reason: '未找到有效的百度网盘URL' }; @@ -26,8 +23,6 @@ async function checkBaidu(link) { try { let bdclnd = ''; - - // 如果有提取码,先验证 if (password) { const verifyURL = `https://pan.baidu.com/share/verify?surl=${encodeURIComponent(shorturl)}&pwd=${encodeURIComponent(password)}`; const formBody = `pwd=${encodeURIComponent(password)}&vcode=&vcode_str=`; @@ -36,7 +31,7 @@ async function checkBaidu(link) { body: formBody, headers: { 'Content-Type': 'application/x-www-form-urlencoded', - 'Referer': normalizedLink, + Referer: normalizedLink, }, }); @@ -52,20 +47,16 @@ async function checkBaidu(link) { bdclnd = vData.randsk || ''; } - // 调用 share/list API const apiURL = `https://pan.baidu.com/share/list?web=5&app_id=250528&desc=1&showempty=0&page=1&num=20&order=time&shorturl=${encodeURIComponent(shorturl)}&root=1&view_mode=1&channel=chunlei&web=1&clienttype=0`; const reqHeaders = { - 'Accept': 'application/json, text/plain, */*', + Accept: 'application/json, text/plain, */*', 'Accept-Language': 'zh,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6', }; if (bdclnd) { - reqHeaders['Cookie'] = `BDCLND=${bdclnd}`; + reqHeaders.Cookie = `BDCLND=${bdclnd}`; } - const { statusCode, body } = await request(apiURL, { - headers: reqHeaders, - }); - + const { statusCode, body } = await request(apiURL, { headers: reqHeaders }); if (statusCode !== 200) { return { valid: false, reason: `API返回错误状态码: ${statusCode}` }; } @@ -73,7 +64,6 @@ async function checkBaidu(link) { const result = JSON.parse(body); const errno = result.errno; const errMsg = result.errmsg || result.err_msg || ''; - if (errno === 0) { return { valid: true, reason: '' }; } @@ -87,12 +77,10 @@ async function checkBaidu(link) { } } -function normalizeBaiduURL(link) { +export function normalizeBaiduURL(link) { const cleaned = link.trim(); const startIdx = cleaned.indexOf('https://pan.baidu.com/s/'); - if (startIdx === -1) { - return null; - } + if (startIdx === -1) return null; let endIdx = startIdx; while (endIdx < cleaned.length) { const char = cleaned[endIdx]; @@ -103,7 +91,7 @@ function normalizeBaiduURL(link) { return cleaned.substring(startIdx, endIdx).trim(); } -function extractBaiduShareID(shareURL) { +export function extractBaiduShareID(shareURL) { try { const u = new URL(shareURL); if (u.pathname.startsWith('/s/')) { @@ -123,11 +111,9 @@ function getFailureReason(errno, errMsg) { if (errMsg) return `分享链接无效 (errno: ${errno}, err_msg: ${errMsg})`; switch (errno) { case -12: return '缺少提取码 (errno: -12)'; - case -9: return '提取码错误 (errno: -9)'; + case -9: return '提取码错误 (errno: -9)'; case -62: return '请求接口受限 (errno: -62)'; - case -8: return '分享文件已过期 (errno: -8)'; - default: return `分享链接无效 (errno: ${errno})`; + case -8: return '分享文件已过期 (errno: -8)'; + default: return `分享链接无效 (errno: ${errno})`; } } - -module.exports = { checkBaidu, normalizeBaiduURL, extractBaiduShareID }; \ No newline at end of file diff --git a/src/lib/pancheck/vendor/checkers/cmcc.js b/src/lib/pancheck/vendor/checkers/cmcc.ts similarity index 65% rename from src/lib/pancheck/vendor/checkers/cmcc.js rename to src/lib/pancheck/vendor/checkers/cmcc.ts index 986e24a..26bde84 100644 --- a/src/lib/pancheck/vendor/checkers/cmcc.js +++ b/src/lib/pancheck/vendor/checkers/cmcc.ts @@ -1,44 +1,28 @@ -const crypto = require('crypto'); -const { request } = require('./http'); +// @ts-nocheck -/** - * 中国移动云盘链接检测 - * URL格式: - * https://yun.139.com/shareweb/#/w/i/{shareID} - * https://caiyun.139.com/m/i?{shareID} - * API: POST https://share-kd-njs.yun.139.com/yun-share/richlifeApp/devapp/IOutLink/getOutLinkInfoV6 - * 请求和响应均通过AES-CBC加密(固定密钥) - */ +import crypto from 'crypto'; +import { request } from './http'; const CMCC_AES_KEY = 'PVGDwmcvfs1uV3d1'; -function aesCBCEncrypt(plaintext, key) { +export function aesCBCEncrypt(plaintext, key) { const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv('aes-128-cbc', Buffer.from(key, 'utf-8'), iv); - - // PKCS7 padding const blockSize = 16; const padLen = blockSize - (Buffer.byteLength(plaintext, 'utf-8') % blockSize); - const padded = Buffer.concat([ - Buffer.from(plaintext, 'utf-8'), - Buffer.alloc(padLen, padLen), - ]); - + const padded = Buffer.concat([Buffer.from(plaintext, 'utf-8'), Buffer.alloc(padLen, padLen)]); const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]); return Buffer.concat([iv, encrypted]).toString('base64'); } -function aesCBCDecrypt(encryptedBase64, key) { +export function aesCBCDecrypt(encryptedBase64, key) { const rawData = Buffer.from(encryptedBase64, 'base64'); if (rawData.length < 16) throw new Error('加密数据长度不足'); const iv = rawData.subarray(0, 16); const ciphertext = rawData.subarray(16); - const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(key, 'utf-8'), iv); const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]); - - // 去除PKCS7填充 const padLen = decrypted[decrypted.length - 1]; if (padLen > 0 && padLen <= 16) { return decrypted.subarray(0, decrypted.length - padLen).toString('utf-8'); @@ -46,7 +30,7 @@ function aesCBCDecrypt(encryptedBase64, key) { return decrypted.toString('utf-8'); } -async function checkCMCC(link) { +export async function checkCMCC(link) { const shareID = extractShareID(link); if (!shareID) { return { valid: false, reason: '链接格式无效:无法提取分享ID' }; @@ -71,10 +55,7 @@ async function checkCMCC(link) { }, }; - const jsonStr = JSON.stringify(requestData); - const encryptedData = aesCBCEncrypt(jsonStr, CMCC_AES_KEY); - const encryptedJSON = JSON.stringify(encryptedData); - + const encryptedJSON = JSON.stringify(aesCBCEncrypt(JSON.stringify(requestData), CMCC_AES_KEY)); const { statusCode, body } = await request( 'https://share-kd-njs.yun.139.com/yun-share/richlifeApp/devapp/IOutLink/getOutLinkInfoV6', { @@ -82,7 +63,7 @@ async function checkCMCC(link) { body: encryptedJSON, headers: { 'Content-Type': 'application/json', - 'Accept': 'application/json, text/plain, */*', + Accept: 'application/json, text/plain, */*', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'hcy-cool-flag': '1', 'x-deviceinfo': '||3|12.27.0|chrome|131.0.0.0|5c7c68368f048245e1ce47f1c0f8f2d0||windows 10|1536X695|zh-CN|||', @@ -94,19 +75,12 @@ async function checkCMCC(link) { return { valid: false, reason: `API返回错误状态码: ${statusCode}` }; } - // 解密响应 - const decryptedData = aesCBCDecrypt(body.trim(), CMCC_AES_KEY); - const response = JSON.parse(decryptedData); - - const resultCode = response.resultCode; - const desc = response.desc; - const data = response.data; - - if (resultCode === '0' && data != null) { + const response = JSON.parse(aesCBCDecrypt(body.trim(), CMCC_AES_KEY)); + if (response.resultCode === '0' && response.data != null) { return { valid: true, reason: '' }; } - const failReason = desc || (resultCode ? `错误码: ${resultCode}` : '获取分享信息失败'); + const failReason = response.desc || (response.resultCode ? `错误码: ${response.resultCode}` : '获取分享信息失败'); return { valid: false, reason: failReason }; } catch (err) { if (err.message === '请求超时') return { valid: false, reason: '请求超时' }; @@ -114,9 +88,7 @@ async function checkCMCC(link) { } } -function extractShareID(shareURL) { +export function extractShareID(shareURL) { const match = shareURL.match(/https:\/\/(?:yun\.139\.com\/shareweb\/#\/w\/i\/|caiyun\.139\.com\/m\/i\?)([^&]+)/); return match ? match[1] : ''; } - -module.exports = { checkCMCC, extractShareID, aesCBCEncrypt, aesCBCDecrypt }; \ No newline at end of file diff --git a/src/lib/pancheck/vendor/checkers/http.js b/src/lib/pancheck/vendor/checkers/http.ts similarity index 74% rename from src/lib/pancheck/vendor/checkers/http.js rename to src/lib/pancheck/vendor/checkers/http.ts index 7d8c756..24cce26 100644 --- a/src/lib/pancheck/vendor/checkers/http.js +++ b/src/lib/pancheck/vendor/checkers/http.ts @@ -1,24 +1,25 @@ -const https = require('https'); -const http = require('http'); -const { URL } = require('url'); +// @ts-nocheck -const DEFAULT_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'; -const DEFAULT_HEADERS = { - 'accept': 'application/json;charset=UTF-8', +import http from 'http'; +import https from 'https'; + +export const DEFAULT_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'; +export const DEFAULT_HEADERS = { + accept: 'application/json;charset=UTF-8', 'accept-language': 'en,zh-CN;q=0.9,zh;q=0.8', 'user-agent': DEFAULT_UA, 'cache-control': 'no-cache', - 'pragma': 'no-cache', + pragma: 'no-cache', }; -function request(url, options = {}) { +export function request(url, options = {}) { return new Promise((resolve, reject) => { const timeout = options.timeout || 15000; const parsedUrl = new URL(url); const transport = parsedUrl.protocol === 'https:' ? https : http; const headers = { ...DEFAULT_HEADERS, ...(options.headers || {}) }; - delete headers['Content-Type']; // handled below + delete headers['Content-Type']; const reqOptions = { hostname: parsedUrl.hostname, @@ -56,5 +57,3 @@ function request(url, options = {}) { req.end(); }); } - -module.exports = { request, DEFAULT_UA, DEFAULT_HEADERS }; diff --git a/src/lib/pancheck/vendor/checkers/pan115.js b/src/lib/pancheck/vendor/checkers/pan115.ts similarity index 80% rename from src/lib/pancheck/vendor/checkers/pan115.js rename to src/lib/pancheck/vendor/checkers/pan115.ts index 7aa905a..67d8cdf 100644 --- a/src/lib/pancheck/vendor/checkers/pan115.js +++ b/src/lib/pancheck/vendor/checkers/pan115.ts @@ -1,11 +1,8 @@ -const { request } = require('./http'); +// @ts-nocheck -/** - * 115网盘链接检测 - * URL格式: https://115cdn.com/s/{share_code}?password={receive_code} - * API: GET https://115cdn.com/webapi/share/snap - */ -async function check115(link) { +import { request } from './http'; + +export async function check115(link) { const { shareCode, receiveCode, error: parseError } = extractParams115(link); if (parseError || !shareCode || !receiveCode) { return { valid: false, reason: parseError || (!shareCode ? '缺少分享码' : '缺少提取码') }; @@ -15,7 +12,7 @@ async function check115(link) { const apiURL = `https://115cdn.com/webapi/share/snap?share_code=${encodeURIComponent(shareCode)}&offset=0&limit=20&receive_code=${encodeURIComponent(receiveCode)}&cid=`; const { statusCode, body } = await request(apiURL, { headers: { - 'Referer': `https://115cdn.com/s/${shareCode}?password=${receiveCode}&`, + Referer: `https://115cdn.com/s/${shareCode}?password=${receiveCode}&`, 'Sec-Ch-Ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"Windows"', @@ -23,7 +20,7 @@ async function check115(link) { 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin', 'X-Requested-With': 'XMLHttpRequest', - 'Priority': 'u=1, i', + Priority: 'u=1, i', }, }); @@ -32,10 +29,8 @@ async function check115(link) { } const data = JSON.parse(body); - if (data.state === true && data.errno === 0) { let shareState = data.data?.share_state || 0; - // 兼容部分响应只在 shareinfo 中返回 share_state if (shareState === 0 && data.data?.shareinfo?.share_state) { shareState = data.data.shareinfo.share_state; } @@ -44,8 +39,7 @@ async function check115(link) { return { valid: true, reason: '' }; } - const failReason = (data.data?.shareinfo?.forbid_reason || '').trim() - || `链接状态异常(share_state=${shareState})`; + const failReason = (data.data?.shareinfo?.forbid_reason || '').trim() || `链接状态异常(share_state=${shareState})`; return { valid: false, reason: failReason }; } @@ -56,7 +50,7 @@ async function check115(link) { } } -function extractParams115(urlStr) { +export function extractParams115(urlStr) { try { const u = new URL(urlStr); const pathParts = u.pathname.replace(/\/+$/, '').split('/'); @@ -73,5 +67,3 @@ function extractParams115(urlStr) { return { shareCode: '', receiveCode: '', error: e.message }; } } - -module.exports = { check115, extractParams115 }; diff --git a/src/lib/pancheck/vendor/checkers/pan123.js b/src/lib/pancheck/vendor/checkers/pan123.ts similarity index 62% rename from src/lib/pancheck/vendor/checkers/pan123.js rename to src/lib/pancheck/vendor/checkers/pan123.ts index 4fe6899..31cf4af 100644 --- a/src/lib/pancheck/vendor/checkers/pan123.js +++ b/src/lib/pancheck/vendor/checkers/pan123.ts @@ -1,13 +1,8 @@ -const { request } = require('./http'); +// @ts-nocheck -/** - * 123网盘链接检测 - * URL格式: https://www.123pan.com/s/{shareKey} - * API: GET https://www.123pan.com/api/share/info?shareKey={shareKey} - * - * 注意: 此检测器采用保守策略,超时/403/请求错误均视为有效,避免误判 - */ -async function check123(link) { +import { request } from './http'; + +export async function check123(link) { const { shareKey, error: parseError } = extractShareKey123(link); if (parseError) { return { valid: false, reason: '链接格式无效: ' + parseError }; @@ -21,35 +16,27 @@ async function check123(link) { }, }); - // 403视为有效(访问限制,不是链接失效) - if (statusCode === 403) { - return { valid: true, reason: '' }; - } - - if (statusCode !== 200) { - return { valid: true, reason: '' }; // 非预期状态码也视为有效,避免误判 - } + if (statusCode === 403) return { valid: true, reason: '' }; + if (statusCode !== 200) return { valid: true, reason: '' }; let data; try { data = JSON.parse(body); } catch (_) { - return { valid: true, reason: '' }; // JSON解析错误视为有效 + return { valid: true, reason: '' }; } - // code==0 或 HasPwd==true 均视为有效 if (data.code === 0 || data.data?.HasPwd === true) { return { valid: true, reason: '' }; } return { valid: false, reason: '链接已失效' }; - } catch (err) { - // 超时和请求错误均视为有效,避免误判 + } catch (_) { return { valid: true, reason: '' }; } } -function extractShareKey123(urlStr) { +export function extractShareKey123(urlStr) { const patterns = [ /https?:\/\/(?:www\.)?(?:123684|123685|123912|123pan|123592|123865)\.com\/s\/([a-zA-Z0-9-]+)/, /https?:\/\/(?:www\.)?123pan\.cn\/s\/([a-zA-Z0-9-]+)/, @@ -62,7 +49,6 @@ function extractShareKey123(urlStr) { } } - // Fallback: 从URL路径中提取 try { const u = new URL(urlStr); const pathParts = u.pathname.replace(/\/+$/, '').split('/').filter(Boolean); @@ -73,5 +59,3 @@ function extractShareKey123(urlStr) { return { shareKey: '', error: '无法从URL中提取shareKey' }; } - -module.exports = { check123, extractShareKey123 }; diff --git a/src/lib/pancheck/vendor/checkers/quark.js b/src/lib/pancheck/vendor/checkers/quark.ts similarity index 80% rename from src/lib/pancheck/vendor/checkers/quark.js rename to src/lib/pancheck/vendor/checkers/quark.ts index 1b25792..d0f0113 100644 --- a/src/lib/pancheck/vendor/checkers/quark.js +++ b/src/lib/pancheck/vendor/checkers/quark.ts @@ -1,18 +1,14 @@ -const { request } = require('./http'); +// @ts-nocheck -/** - * 夸克网盘链接检测 - * URL格式: https://pan.quark.cn/s/{pwd_id}?pwd={passcode} - * 两步检测: 先获取stoken,再验证文件列表 - */ -async function checkQuark(link) { +import { request } from './http'; + +export async function checkQuark(link) { const { resId, pwd, error: parseError } = extractParamsQuark(link); if (parseError) { return { valid: false, reason: '链接格式无效: ' + parseError }; } try { - // Step 1: 获取 stoken const tokenURL = 'https://drive-h.quark.cn/1/clouddrive/share/sharepage/token'; const { statusCode: status1, body: body1 } = await request(tokenURL, { method: 'POST', @@ -23,8 +19,8 @@ async function checkQuark(link) { }, headers: { 'Content-Type': 'application/json', - 'Origin': 'https://pan.quark.cn', - 'Referer': 'https://pan.quark.cn/', + Origin: 'https://pan.quark.cn', + Referer: 'https://pan.quark.cn/', }, }); @@ -40,16 +36,15 @@ async function checkQuark(link) { return { valid: false, reason: '分享链接无效:未获取到访问令牌' }; } - // Step 2: 获取文件列表 const detailURL = `https://drive-pc.quark.cn/1/clouddrive/share/sharepage/detail?pwd_id=${encodeURIComponent(resId)}&stoken=${encodeURIComponent(tokenResp.data.stoken)}&ver=2&pr=ucpro`; const { statusCode: status2, body: body2 } = await request(detailURL, { headers: { - 'Accept': 'application/json, text/plain, */*', + Accept: 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Cache-Control': 'no-cache', - 'Origin': 'https://pan.quark.cn', - 'Referer': 'https://pan.quark.cn/', - 'Pragma': 'no-cache', + Origin: 'https://pan.quark.cn', + Referer: 'https://pan.quark.cn/', + Pragma: 'no-cache', }, }); @@ -69,7 +64,7 @@ async function checkQuark(link) { } } -function extractParamsQuark(rawURL) { +export function extractParamsQuark(rawURL) { const urlRegex = /^https:\/\/(?:pan\.quark\.cn|pan\.qoark\.cn)\/s\/[a-zA-Z0-9]+(?:\?[^#]*)?(?:#.*)?$/; if (!urlRegex.test(rawURL)) { return { resId: '', pwd: '', error: '无效的URL格式' }; @@ -93,5 +88,3 @@ function extractParamsQuark(rawURL) { return { resId: '', pwd: '', error: e.message }; } } - -module.exports = { checkQuark, extractParamsQuark }; diff --git a/src/lib/pancheck/vendor/checkers/tianyi.js b/src/lib/pancheck/vendor/checkers/tianyi.ts similarity index 74% rename from src/lib/pancheck/vendor/checkers/tianyi.js rename to src/lib/pancheck/vendor/checkers/tianyi.ts index 5ae2955..4a194f6 100644 --- a/src/lib/pancheck/vendor/checkers/tianyi.js +++ b/src/lib/pancheck/vendor/checkers/tianyi.ts @@ -1,14 +1,8 @@ -const { request } = require('./http'); +// @ts-nocheck -/** - * 天翼云盘链接检测 - * URL格式: - * https://cloud.189.cn/web/share?code=xxx - * https://cloud.189.cn/t/xxx - * https://h5.cloud.189.cn/share.html#/t/xxx - * API: GET https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action - */ -async function checkTianyi(link) { +import { request } from './http'; + +export async function checkTianyi(link) { const { codeValue, accessCode, refererValue, error: parseError } = extractCodeFromURL(link); if (parseError) { return { valid: false, reason: '链接格式无效: ' + parseError }; @@ -16,7 +10,6 @@ async function checkTianyi(link) { try { const noCache = Math.random(); - // 如果有访问码,需要将访问码包含在shareCode参数中 let shareCodeParam = codeValue; if (accessCode) { shareCodeParam = `${codeValue}(访问码:${accessCode})`; @@ -25,8 +18,8 @@ async function checkTianyi(link) { const apiURL = `https://cloud.189.cn/api/open/share/getShareInfoByCodeV2.action?noCache=${noCache}&shareCode=${encodeURIComponent(shareCodeParam)}`; const { statusCode, body } = await request(apiURL, { headers: { - 'Priority': 'u=1, i', - 'Referer': refererValue, + Priority: 'u=1, i', + Referer: refererValue, 'Sec-Ch-Ua': '"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"Windows"', @@ -54,21 +47,17 @@ async function checkTianyi(link) { } } -function extractCodeFromURL(urlStr) { +export function extractCodeFromURL(urlStr) { try { const u = new URL(urlStr); let codeValue = ''; let accessCode = ''; - - // 1. 从查询参数获取code codeValue = u.searchParams.get('code') || ''; - // 2. 从路径获取 /t/xxx if (!codeValue && u.pathname.startsWith('/t/')) { codeValue = u.pathname.replace('/t/', '').split('/')[0]; } - // 3. 从hash获取 #/t/xxx if (!codeValue && u.hash) { const fragment = u.hash.replace(/^#/, ''); if (fragment.startsWith('/t/')) { @@ -82,9 +71,7 @@ function extractCodeFromURL(urlStr) { return { codeValue: '', accessCode: '', refererValue: '', error: '输入URL中未找到code参数' }; } - // 提取访问码(访问码:xxx) - const accessCodePattern = /[((]访问码[::]\s*([a-zA-Z0-9]+)[))]/; - const match = urlStr.match(accessCodePattern); + const match = urlStr.match(/[((]访问码[::]\s*([a-zA-Z0-9]+)[))]/); if (match && match[1]) { accessCode = match[1]; } @@ -94,5 +81,3 @@ function extractCodeFromURL(urlStr) { return { codeValue: '', accessCode: '', refererValue: '', error: e.message }; } } - -module.exports = { checkTianyi, extractCodeFromURL }; \ No newline at end of file diff --git a/src/lib/pancheck/vendor/checkers/uc.js b/src/lib/pancheck/vendor/checkers/uc.js deleted file mode 100644 index 0a96106..0000000 --- a/src/lib/pancheck/vendor/checkers/uc.js +++ /dev/null @@ -1,58 +0,0 @@ -const { request, DEFAULT_UA } = require('./http'); - -/** - * UC网盘链接检测 - * URL格式: https://drive.uc.cn/s/{shareID} - * 检测方法: 页面爬取,通过关键词判断有效性 - */ -async function checkUC(link) { - const { shareID, error: parseError } = extractShareIDFromURL(link); - if (parseError) { - return { valid: false, reason: '链接格式无效: ' + parseError }; - } - - try { - const url = `https://drive.uc.cn/s/${shareID}`; - const { statusCode, body } = await request(url, { - headers: { - 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36', - }, - }); - - if (statusCode !== 200) { - return { valid: false, reason: `HTTP状态码: ${statusCode}` }; - } - - const pageText = body.toLowerCase(); - const errorKeywords = ['失效', '不存在', '违规', '删除', '已过期', '被取消']; - - for (const keyword of errorKeywords) { - if (pageText.includes(keyword)) { - return { valid: false, reason: '链接已失效' }; - } - } - - const validKeywords = ['文件', '分享']; - for (const keyword of validKeywords) { - if (pageText.includes(keyword)) { - return { valid: true, reason: '' }; - } - } - - return { valid: false, reason: '无法判断链接有效性' }; - } catch (err) { - if (err.message === '请求超时') return { valid: true, reason: '' }; // 超时视为有效,避免误判 - return { valid: true, reason: '' }; // 连接错误也视为有效,避免误判 - } -} - -function extractShareIDFromURL(urlStr) { - const pattern = /https?:\/\/drive\.uc\.cn\/s\/([a-zA-Z0-9]+)/; - const match = urlStr.match(pattern); - if (match && match[1]) { - return { shareID: match[1], error: null }; - } - return { shareID: '', error: '无法从URL中提取share_id' }; -} - -module.exports = { checkUC, extractShareIDFromURL }; \ No newline at end of file diff --git a/src/lib/pancheck/vendor/checkers/uc.ts b/src/lib/pancheck/vendor/checkers/uc.ts new file mode 100644 index 0000000..ffc4315 --- /dev/null +++ b/src/lib/pancheck/vendor/checkers/uc.ts @@ -0,0 +1,44 @@ +// @ts-nocheck + +import { request } from './http'; + +export async function checkUC(link) { + const { shareID, error: parseError } = extractShareIDFromURL(link); + if (parseError) { + return { valid: false, reason: '链接格式无效: ' + parseError }; + } + + try { + const url = `https://drive.uc.cn/s/${shareID}`; + const { statusCode, body } = await request(url, { + headers: { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 10; SM-G975F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.101 Mobile Safari/537.36', + }, + }); + + if (statusCode !== 200) { + return { valid: false, reason: `HTTP状态码: ${statusCode}` }; + } + + const pageText = body.toLowerCase(); + for (const keyword of ['失效', '不存在', '违规', '删除', '已过期', '被取消']) { + if (pageText.includes(keyword)) return { valid: false, reason: '链接已失效' }; + } + for (const keyword of ['文件', '分享']) { + if (pageText.includes(keyword)) return { valid: true, reason: '' }; + } + + return { valid: false, reason: '无法判断链接有效性' }; + } catch (err) { + if (err.message === '请求超时') return { valid: true, reason: '' }; + return { valid: true, reason: '' }; + } +} + +export function extractShareIDFromURL(urlStr) { + const match = urlStr.match(/https?:\/\/drive\.uc\.cn\/s\/([a-zA-Z0-9]+)/); + if (match && match[1]) { + return { shareID: match[1], error: null }; + } + return { shareID: '', error: '无法从URL中提取share_id' }; +} diff --git a/src/lib/pancheck/vendor/checkers/xunlei.js b/src/lib/pancheck/vendor/checkers/xunlei.ts similarity index 71% rename from src/lib/pancheck/vendor/checkers/xunlei.js rename to src/lib/pancheck/vendor/checkers/xunlei.ts index dadffe5..e855994 100644 --- a/src/lib/pancheck/vendor/checkers/xunlei.js +++ b/src/lib/pancheck/vendor/checkers/xunlei.ts @@ -1,12 +1,8 @@ -const crypto = require('crypto'); -const zlib = require('zlib'); -const { request } = require('./http'); +// @ts-nocheck -/** - * 迅雷云盘链接检测 - * URL格式: https://pan.xunlei.com/s/{share_id}?pwd={pass_code} - * 两步检测: 先获取captcha token,再调用share API - */ +import crypto from 'crypto'; +import zlib from 'zlib'; +import { request } from './http'; const XUNLEI_DEVICE_ID = '5505bd0cab8c9469b98e5891d9fb3e0d'; const XUNLEI_CLIENT_ID = 'ZUBzD9J_XPXfn7f7'; @@ -26,18 +22,16 @@ const CAPTCHA_ALGORITHMS = [ 'ThTWPG5eC0UBqlbQ+04nZAptqGCdpv9o55A', ]; -function getCaptchaSign(clientID, clientVersion, packageName, deviceID) { +export function getCaptchaSign(clientID, clientVersion, packageName, deviceID) { const timestamp = Date.now().toString(); let str = `${clientID}${clientVersion}${packageName}${deviceID}${timestamp}`; - for (const algorithm of CAPTCHA_ALGORITHMS) { str = crypto.createHash('md5').update(str + algorithm).digest('hex'); } - return { timestamp, sign: `1.${str}` }; } -async function getCaptchaToken(action, metas = {}) { +export async function getCaptchaToken(action, metas = {}) { const { timestamp, sign: captchaSign } = getCaptchaSign( XUNLEI_CLIENT_ID, XUNLEI_CLIENT_VERSION, XUNLEI_PACKAGE_NAME, XUNLEI_DEVICE_ID ); @@ -47,22 +41,20 @@ async function getCaptchaToken(action, metas = {}) { metas.client_version = XUNLEI_CLIENT_VERSION; metas.package_name = XUNLEI_PACKAGE_NAME; - const requestBody = { - action, - captcha_token: '', - client_id: XUNLEI_CLIENT_ID, - device_id: XUNLEI_DEVICE_ID, - meta: metas, - redirect_uri: 'xlaccsdk01://xunlei.com/callback?state=harbor', - }; - const { statusCode, body, headers } = await request( 'https://xluser-ssl.xunlei.com/v1/shield/captcha/init', { method: 'POST', - body: requestBody, + body: { + action, + captcha_token: '', + client_id: XUNLEI_CLIENT_ID, + device_id: XUNLEI_DEVICE_ID, + meta: metas, + redirect_uri: 'xlaccsdk01://xunlei.com/callback?state=harbor', + }, headers: { - 'Accept': 'application/json;charset=UTF-8', + Accept: 'application/json;charset=UTF-8', 'Content-Type': 'application/json', 'User-Agent': XUNLEI_UA, 'X-Device-Id': XUNLEI_DEVICE_ID, @@ -77,7 +69,6 @@ async function getCaptchaToken(action, metas = {}) { } let respBody = body; - // 解压 gzip/deflate const encoding = (headers['content-encoding'] || '').toLowerCase(); if (encoding === 'gzip') { respBody = zlib.gunzipSync(Buffer.from(body, 'binary')).toString('utf-8'); @@ -86,16 +77,12 @@ async function getCaptchaToken(action, metas = {}) { } const data = JSON.parse(respBody); - if (data.url) { - throw new Error(`需要验证: ${data.url}`); - } - if (!data.captcha_token) { - throw new Error('未获取到验证码token'); - } + if (data.url) throw new Error(`需要验证: ${data.url}`); + if (!data.captcha_token) throw new Error('未获取到验证码token'); return data.captcha_token; } -async function checkXunlei(link) { +export async function checkXunlei(link) { const shareID = extractShareID(link); if (!shareID) { return { valid: false, reason: '链接格式无效:无法提取share_id' }; @@ -108,7 +95,6 @@ async function checkXunlei(link) { } catch (_) {} try { - // Step 1: 获取 captcha token let captchaToken = ''; try { captchaToken = await getCaptchaToken('get:/drive/v1/share', { @@ -119,17 +105,14 @@ async function checkXunlei(link) { client_version: '1.92.10', user_id: '0', }); - } catch (_) { - // token获取失败时继续,不带token请求 - } + } catch (_) {} - // Step 2: 调用share API const apiURL = `https://api-pan.xunlei.com/drive/v1/share?share_id=${encodeURIComponent(shareID)}&pass_code=${encodeURIComponent(passCode)}&limit=100&pass_code_token=&page_token=&thumbnail_size=SIZE_SMALL`; const reqHeaders = { - 'Accept': '*/*', + Accept: '*/*', 'Content-Type': 'application/json', - 'Origin': 'https://pan.xunlei.com', - 'Referer': 'https://pan.xunlei.com/', + Origin: 'https://pan.xunlei.com', + Referer: 'https://pan.xunlei.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36', 'Accept-Encoding': 'gzip, deflate', 'X-Client-Id': XUNLEI_CLIENT_ID, @@ -140,9 +123,7 @@ async function checkXunlei(link) { } const { statusCode, body, headers } = await request(apiURL, { headers: reqHeaders }); - let respBody = body; - // 解压 const encoding = (headers['content-encoding'] || '').toLowerCase(); if (encoding === 'gzip') { respBody = zlib.gunzipSync(Buffer.from(body, 'binary')).toString('utf-8'); @@ -151,14 +132,12 @@ async function checkXunlei(link) { } if (statusCode !== 200) { - let isRateLimited = false; try { const errData = JSON.parse(respBody); - if (errData.error_code === 9) isRateLimited = true; return { valid: false, reason: `HTTP状态码: ${statusCode}, 响应: ${respBody}`, - isRateLimited, + isRateLimited: errData.error_code === 9, }; } catch (_) { return { valid: false, reason: `HTTP状态码: ${statusCode}` }; @@ -166,26 +145,16 @@ async function checkXunlei(link) { } const apiResp = JSON.parse(respBody); - - if (apiResp.share_status === 'OK') { - return { valid: true, reason: '' }; - } - - if (apiResp.error) { - return { valid: false, reason: apiResp.error }; - } - - const statusText = apiResp.share_status_text || `分享状态: ${apiResp.share_status}`; - return { valid: false, reason: statusText }; + if (apiResp.share_status === 'OK') return { valid: true, reason: '' }; + if (apiResp.error) return { valid: false, reason: apiResp.error }; + return { valid: false, reason: apiResp.share_status_text || `分享状态: ${apiResp.share_status}` }; } catch (err) { - if (err.message === '请求超时') return { valid: true, reason: '' }; // 超时视为有效,避免误判 + if (err.message === '请求超时') return { valid: true, reason: '' }; return { valid: false, reason: `检测失败: ${err.message}` }; } } -function extractShareID(shareURL) { +export function extractShareID(shareURL) { const match = shareURL.match(/pan\.xunlei\.com\/s\/([^?/#]+)/); return match ? match[1] : ''; } - -module.exports = { checkXunlei, extractShareID, getCaptchaSign, getCaptchaToken }; \ No newline at end of file