From b472ecfc5b3e7731c4e880c14b10a92dd187a670 Mon Sep 17 00:00:00 2001 From: qq27800734 <27800734@qq.com> Date: Sun, 20 Sep 2026 16:37:05 +0000 Subject: [PATCH] Publish nexusphp v1.0.2 --- adapters/nexusphp/adapter.js | 136 ++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 2 deletions(-) diff --git a/adapters/nexusphp/adapter.js b/adapters/nexusphp/adapter.js index 91d37ed..c080a74 100644 --- a/adapters/nexusphp/adapter.js +++ b/adapters/nexusphp/adapter.js @@ -9,7 +9,7 @@ // download(ctx) —— 下载 .torrent 种子文件字节(能力: download) // sync(ctx) —— 站点信息同步(mediabot2 Host 调用:首页统计 + 账号资料) // parse(content, url) —— 解析用户统计页(校验沙箱 /validate 使用,http 走 mock) -// rss(ctx) —— RSS/Atom 刷流(供 RssDiscoveryJob,非标准能力) +// rss(ctx) —— RSS 刷流(供 RssDiscoveryJob,非标准能力) // ==================== 运行环境兼容层 ==================== // mediabot2 宿主注入: http / html / utils / json; @@ -555,6 +555,21 @@ function search(input) { var title = titleEl.text(); if (!title) continue; + // torrentnameHtml: 只定位、不解析. 把标题 所在 td 的 innerHTML 片段原样传给 C#, + // 由 TorrentNameHtmlParser 用 AngleSharp 精拆 subtitle + tags. + var torrentnameHtml = null; + var titleTd = titleEl.closest ? titleEl.closest('td') : null; + if (!titleTd) { + var p = titleEl.parentElement; + while (p && p.tagName && p.tagName.toLowerCase() !== 'td') p = p.parentElement; + titleTd = p; + } + if (titleTd && typeof titleTd.html === 'function') { + torrentnameHtml = titleTd.html(); + } else if (titleTd && typeof titleTd.innerHTML !== 'undefined') { + torrentnameHtml = titleTd.innerHTML; + } + var href = titleEl.attr('href'); var detailUrl = null; if (href) { @@ -624,7 +639,7 @@ function search(input) { var encoding = parseMediaTag(title, /\b(x264|x265|H\.?265|H\.?264|AVC|HEVC|VC-?1|XviD|AV1)\b/i); results.push({ - title: title, + torrentnameHtml: torrentnameHtml, detailUrl: detailUrl, downloadUrl: downloadUrl, size: size, @@ -659,6 +674,38 @@ function search(input) { return results; } +// 从详情页 DOM 中提取外部 ID (doubanId / imdbId / tmdbId / tvdbId). +// NexusPHP 详情页的外部链接通常放在 #kdescr 描述块、#info_block 标签区或页面底部, +// 通过遍历所有 的 href 正则命中即可覆盖多数站点布局。 +function extractExternalIds(doc) { + var ids = {}; + try { + var links = doc.queryList('a'); + for (var i = 0; i < links.length; i++) { + var href = links[i].attr('href') || ''; + if (!ids.doubanId) { + var dbm = href.match(/douban\.com\/subject\/(\d+)/i); + if (dbm) ids.doubanId = dbm[1]; + } + if (!ids.imdbId) { + var imm = href.match(/imdb\.com\/title\/(tt\d+)/i); + if (imm) ids.imdbId = imm[1]; + } + if (!ids.tmdbId) { + var tmm = href.match(/themoviedb\.org\/(?:movie|tv)\/(\d+)/i); + if (tmm) ids.tmdbId = parseInt(tmm[1], 10); + } + if (!ids.tvdbId) { + var tvm = href.match(/thetvdb\.com\/series\/(\d+)/i) || href.match(/tvdbid=(\d+)/i); + if (tvm) ids.tvdbId = parseInt(tvm[1], 10); + } + // 四个全齐就提前退出 + if (ids.doubanId && ids.imdbId && ids.tmdbId && ids.tvdbId) break; + } + } catch (e) { /* 任何异常都静默降级, 返回部分结果 */ } + return ids; +} + // ================= 能力: detail(种子详情)================= // ctx: { baseUrl, id (或 detailId), detailUrl?(可选, 直接指定详情页地址) } // 输出字段与能力模板 detail 一致(size/seeders 等保留页面原始文本)。 @@ -722,9 +769,94 @@ function detail(input) { if (mhref.indexOf('magnet:') === 0) { out.magnet = mhref; break; } } + // 提取外部 ID (doubanId / imdbId / tmdbId / tvdbId) + var extIds = extractExternalIds(doc); + for (var k in extIds) if (extIds[k] !== undefined) out[k] = extIds[k]; + return out; } +// fetchDetail: C# DetailVerifyJob 调用入口, 别名到 detail 函数. +// C# 端 Invoke("fetchDetail") 是外部约定, 这里保持兼容. +function fetchDetail(input) { return detail(input); } + +// ================= 能力: hr(H&R 列表)================= +// 访问 myhr.php, 解析当前账号 H&R 考察中的种子列表. +// C# SiteHrSyncJob 约定入口名: "hr". +// ctx: { baseUrl, hrPath?(可选, 默认 /myhr.php) } +// 返回 { items: [...] } — 见 SiteScriptTorrentFetcher.ParseHrEntries 契约. +// +// #hr-table 表格列: +// H&R ID | 种子名称 | 上传量 | 下载量 | 分享率 | 还需做种时间 | 下载完成时间 | 剩余考察时间 | 备注 | 操作 +// +// hrState 值: reviewing (考察中, 默认列表) | satisfied (已达标) | failed (未达标) | pardoned (已免罪) +// myhr.php 默认页 = status=1 考察中; 其他状态通过 ?status=2/3/4 切换. +function parseMyhr(htmlStr) { + var doc = html.parse(htmlStr); + var items = []; + + var table = doc.querySelector('#hr-table'); + if (!table) return { items: items }; + + var rows = table.queryList('tr'); + for (var i = 0; i < rows.length; i++) { + var tds = rows[i].queryList('td'); + if (tds.length < 5) continue; + if ((tds[0].attr('class') || '').indexOf('colhead') !== -1) continue; + + var hrId = (tds[0].text() || '').trim(); + if (!/^\d+$/.test(hrId)) continue; + + // 种子详情链接 → detailId + var nameTd = tds[1]; + var detailId = ''; + if (nameTd) { + var a = nameTd.querySelector('a[href*="details.php"]'); + if (a) { + var idM = (a.attr('href') || '').match(/details\.php\?.*?id=(\d+)/i); + if (idM) detailId = idM[1]; + } + } + if (!detailId) continue; // C# ParseHrEntries 跳过 detailId 为空的条目 + + // 剩余考察时间 → remainingSeconds + // 格式: "9天09:10:50" 或 "10:19:39" (不足一天时可能只有 HH:mm:ss) + var remain = tds[7] ? (tds[7].text() || '').trim() : ''; + var remainingSeconds = 0; + var daysMatch = remain.match(/(\d+)\s*天/); + var days = daysMatch ? parseInt(daysMatch[1], 10) : 0; + var hms = remain.match(/(\d{1,2}):(\d{2}):(\d{2})/); + if (hms) { + remainingSeconds = days * 86400 + + parseInt(hms[1], 10) * 3600 + + parseInt(hms[2], 10) * 60 + + parseInt(hms[3], 10); + } else if (days > 0) { + remainingSeconds = days * 86400; + } + + items.push({ + detailId: detailId, + remainingSeconds: remainingSeconds, + hrState: remainingSeconds > 0 ? 'reviewing' : 'satisfied', + hrId: hrId + }); + } + + return { items: items }; +} + +function hr(input) { + var ctx = __asCtx(input); + var base = __resolveBase(ctx); + if (!base || base === 'http://mock.local') return { items: [] }; + var path = ctx && ctx.hrPath ? ctx.hrPath : '/myhr.php'; + var url = base + path; + var htmlStr = http.get(url); + if (!htmlStr) return { items: [] }; + return parseMyhr(htmlStr); +} + // ================= 能力: download(下载种子)================= // 优先直接使用 ctx.downloadUrl / ctx.detailUrl (搜索解析时抓取的行内下载链接, 已含 passkey/uid 等完整参数, 如 pttime); // 无行内链接时由 ctx.id (或 detailId) 构造, 可带 ctx.passkey (仅需 Cookie 的站点如 carpt 不传即可)。