diff --git a/lib/fetcher.js b/lib/fetcher.js index 3846ab3..33b7a0b 100644 --- a/lib/fetcher.js +++ b/lib/fetcher.js @@ -3,11 +3,9 @@ import * as cheerio from 'cheerio'; import { getCache, setCache, getConfig } from './cache.js'; import chalk from 'chalk'; -// ─── 通用请求函数 ──────────────────────────────────────── +// ─── 通用请求函数(JAVBUS / JavLibrary 使用)──────────── function fetchHtml(url, cookie = '') { - const cookieHeader = cookie - ? `-H "Cookie: ${cookie}"` - : ''; + const cookieHeader = cookie ? `-H "Cookie: ${cookie}"` : ''; const result = execSync( `curl -sL "${url}" \ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" \ @@ -26,7 +24,6 @@ function searchJavbus(id) { const html = fetchHtml(url); const $ = cheerio.load(html); - // 判断是否找到(404页面包含特定文字) if ($('title').text().includes('404') || !$('.container .row').length) { return null; } @@ -48,12 +45,9 @@ function searchJavbus(id) { score: '', }; - // 遍历详情字段 $('.info p').each((_, el) => { const text = $(el).text().trim(); - const labelEl = $(el).find('span.header'); - const label = labelEl.text().trim(); - + const label = $(el).find('span.header').text().trim(); if (/發行日期|发行日期/.test(label)) { result.releaseDate = text.replace(label, '').trim(); } else if (/長度|长度/.test(label)) { @@ -69,13 +63,11 @@ function searchJavbus(id) { } }); - // 类别 $('span.genre a').each((_, el) => { const tag = $(el).text().trim(); if (tag) result.tags.push(tag); }); - // 女优 $('.star-name a').each((_, el) => { const name = $(el).text().trim(); if (name) result.actresses.push(name); @@ -90,15 +82,11 @@ function searchJavbus(id) { // ─── 数据源二:JavLibrary ──────────────────────────────── function searchJavlibrary(id) { try { - // 中文版搜索页 const searchUrl = `https://www.javlibrary.com/cn/vl_searchbyid.php?keyword=${encodeURIComponent(id)}`; const searchHtml = fetchHtml(searchUrl); const $ = cheerio.load(searchHtml); - // 如果直接跳转到详情页(只有一个结果) let detailHtml = searchHtml; - - // 如果是搜索结果列表,找第一个匹配的 const firstResult = $('.videos .video a').first(); if (firstResult.length) { const detailPath = firstResult.attr('href'); @@ -106,8 +94,6 @@ function searchJavlibrary(id) { } const $d = cheerio.load(detailHtml); - - // 确认找到了正确页面 if (!$d('#video_id .text').length) return null; const foundId = $d('#video_id .text').text().trim().toUpperCase(); @@ -130,13 +116,11 @@ function searchJavlibrary(id) { score: $d('#video_review .score').text().trim(), }; - // 类别 $d('#video_genres .genre a').each((_, el) => { const tag = $d(el).text().trim(); if (tag) result.tags.push(tag); }); - // 演员 $d('#video_cast .cast .star a').each((_, el) => { const name = $d(el).text().trim(); if (name) result.actresses.push(name); @@ -148,59 +132,31 @@ function searchJavlibrary(id) { } } -// ─── 数据源三:JAVDB(需要Cookie) ────────────────────── +// ─── 数据源三:JAVDB(需要Cookie,仅Linux/Mac)────────── async function searchJavdb(id) { try { const config = getConfig(); if (!config.session) return null; + // Windows上Cloudflare封锁了非OpenSSL的TLS请求,跳过 + if (process.platform === 'win32') return null; + const session = decodeURIComponent(config.session); const cookie = `locale=zh; over18=1; _jdb_session=${session}`; - const isWindows = process.platform === 'win32'; - // Linux 用 curl(OpenSSL TLS,Cloudflare 接受) - // Windows 用 Node.js https(绕过 Schannel 问题) - async function fetchJavdbHtml(url) { - if (!isWindows) { - // Linux/Mac:用 curl - return execSync( - `curl -sL "${url}" \ - -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" \ - -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \ - -H "Accept-Language: zh-CN,zh;q=0.9" \ - -H "Cookie: ${cookie}"`, - { timeout: 15000, maxBuffer: 1024 * 1024 * 10 } - ).toString(); - } else { - // Windows:用 Node.js https - const { default: https } = await import('https'); - const { default: http } = await import('http'); - return new Promise((resolve, reject) => { - const client = url.startsWith('https') ? https : http; - const req = client.get(url, { - headers: { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'Accept-Language': 'zh-CN,zh;q=0.9', - 'Cookie': cookie, - }, - }, (res) => { - if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { - resolve(fetchJavdbHtml(res.headers.location)); - return; - } - const chunks = []; - res.on('data', chunk => chunks.push(chunk)); - res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8'))); - }); - req.on('error', reject); - req.setTimeout(15000, () => { req.destroy(); reject(new Error('timeout')); }); - }); - } + function fetchJavdbHtml(url) { + return execSync( + `curl -sL "${url}" \ + -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" \ + -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \ + -H "Accept-Language: zh-CN,zh;q=0.9" \ + -H "Cookie: ${cookie}"`, + { timeout: 15000, maxBuffer: 1024 * 1024 * 10 } + ).toString(); } const searchUrl = `https://javdb.com/search?q=${encodeURIComponent(id)}&f=all`; - const searchHtml = await fetchJavdbHtml(searchUrl); + const searchHtml = fetchJavdbHtml(searchUrl); const $ = cheerio.load(searchHtml); let detailPath = null; @@ -221,13 +177,13 @@ async function searchJavdb(id) { let detailHtml = ''; for (let i = 0; i < 3; i++) { await new Promise(resolve => setTimeout(resolve, 2000)); - detailHtml = await fetchJavdbHtml(`https://javdb.com${detailPath}`); + detailHtml = fetchJavdbHtml(`https://javdb.com${detailPath}`); if (detailHtml.length > 1000) break; } if (detailHtml.length < 1000) return null; return parseJavdb(detailHtml, id); - } catch (e) { + } catch { return null; } } @@ -292,48 +248,39 @@ function parseJavdb(html, queryId) { // ─── 主入口:依次尝试三个数据源 ───────────────────────── export async function search(id) { - // 用户输入连字符 031926-100,搜索时转成下划线 031926_100 + // FC2 格式识别:031926-100 → 031926_100(JAVDB 存储格式) let searchId = id; if (/^\d{5,6}-\d+$/.test(id)) { - searchId = id.replace('-', '_'); - console.log(chalk.gray(` 识别为FC2番号: ${id}`)); + searchId = id.replace(/-/g, '_'); // 全局替换,更安全 + console.log(chalk.gray(` 识别为FC2格式的车牌号: ${id}`)); } const cached = getCache(id); if (cached) return cached; - // 最多自动重试 3 次,每次间隔 1 秒 const MAX_RETRY = 3; for (let attempt = 1; attempt <= MAX_RETRY; attempt++) { let result = null; - // 第一优先:JAVBUS result = searchJavbus(searchId); - if (result) { - setCache(id, result); - return result; - } + if (result) { setCache(id, result); return result; } - // 第二优先:JavLibrary result = searchJavlibrary(searchId); - if (result) { - setCache(id, result); - return result; - } + if (result) { setCache(id, result); return result; } - // 第三优先:JAVDB result = await searchJavdb(searchId); - if (result) { - setCache(id, result); - return result; - } + if (result) { setCache(id, result); return result; } - // 本次没找到,如果还有重试机会就等待 if (attempt < MAX_RETRY) { - await new Promise(resolve => setTimeout(resolve, 2000)); // 改成 2 秒 + await new Promise(resolve => setTimeout(resolve, 2000)); } } + // Windows 用户提示 + if (process.platform === 'win32') { + process.stderr.write(chalk.gray(' 温馨提示: 此车牌号需要JAVDB数据源,Windows暂不支持~\n')); + } + return null; } \ No newline at end of file diff --git a/package.json b/package.json index d15d774..d674e08 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "javinfo", - "version": "1.1.11", + "version": "1.1.12", "description": "AV番号命令行查询工具", "main": "./index.js", "type": "module",