From e569d87c01115e7ccc3f31c49448f44ddf7826cc Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Thu, 2 Jul 2026 00:06:06 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=BB=98=E8=AE=A4nexusphp=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E5=AF=B9=E9=BD=90PTTIME=E9=A3=8E=E6=A0=BC(http.get+ut?= =?UTF-8?q?ils+=E9=82=80=E8=AF=B7+=E7=BA=A2=E8=89=B2HR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/scripts/default-nexusphp.groovy | 174 +++++++++--------- 1 file changed, 90 insertions(+), 84 deletions(-) diff --git a/par-core/src/main/resources/scripts/default-nexusphp.groovy b/par-core/src/main/resources/scripts/default-nexusphp.groovy index 4d5f870..d8ee356 100644 --- a/par-core/src/main/resources/scripts/default-nexusphp.groovy +++ b/par-core/src/main/resources/scripts/default-nexusphp.groovy @@ -1,9 +1,8 @@ // NexusPHP 站点默认脚本 -// 适用: PThome / HDSky / OurBits / HDFans / 聆音 等 -// 覆盖: 搜索 / 详情 / 用户信息 / 签到 +// 适用: PThome / HDSky / HDFans / OurBits 等 Map parse(String content, String url) { - def doc = jsoup.parse(content) + def doc = Jsoup.parse(content) if (url.contains('/torrents.php')) return parseSearch(doc) if (url.contains('/details.php')) return parseDetail(doc) @@ -19,56 +18,58 @@ private Map parseUserInfo(doc) { def el = utils.selectFirst(doc, '#info_block, td.bottom.clearfix, .userbar') if (el) { def t = utils.text(el) - // 通用正则提取 def um = t =~ /上传[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/ def dm = t =~ /下载[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/ def rm = t =~ /分享率[::]\s*([\d.,]+)/ def bm = t =~ /魔力[值::]*[^0-9]*([\d.,]+)/ - if (um) fields.upload = um[0][1] + (um[0][2] ?: '') - if (dm) fields.download = dm[0][1] + (dm[0][2] ?: '') - if (rm) fields.ratio = rm[0][1] - if (bm) fields.bonus = bm[0][1] - // PTTIME 特殊:做种/下载计数 def sm = t =~ /⬆\s*(\d+)/ def lm = t =~ /⬇\s*(\d+)/ - if (sm) fields.seeding = sm[0][1] + def im = t =~ /邀请[::]\s*(\d+)/ + if (um) fields.upload = um[0][1] + (um[0][2] ?: '') + if (dm) fields.download = dm[0][1] + (dm[0][2] ?: '') + if (rm) fields.ratio = rm[0][1] + if (bm) fields.bonus = bm[0][1] + if (sm) fields.seeding = sm[0][1] if (lm) fields.leeching = lm[0][1] + if (im) fields.invites = im[0][1] + // H&R 红色数字(未达标) + def hrEl = doc.selectFirst('#info_block font[class=fcr], #info_block font[color=red], #info_block .red') + if (hrEl) { + def hrm = hrEl.text() =~ /(\d+)/ + if (hrm) fields.hnr = hrm[0][1] + } } - return [pageName: '用户信息', fields: fields] + return [pageName: '用户信息', fields: fields, message: fields.isEmpty() ? '未提取' : '解析成功'] } private Map parseSearch(doc) { def rows = [] - // 兼容有/无 tbody 的 NexusPHP 变体 doc.select('table.torrents tr:has(td.rowfollow):not(:has(.colhead))').each { row -> - def titleEl = utils.selectFirst(row, 'td.embedded a[href*=details]') + def titleEl = row.selectFirst('td.embedded a[href*=details]') if (!titleEl) return - def catEl = utils.selectFirst(row, 'td.rowfollow a[href*=cat] img, td.rowfollow a[href*=torrents] img, span.category') - def freeEl = utils.selectFirst(row, 'font.promotion') + def catEl = row.selectFirst('td.rowfollow img, td.rowfollow span.category') + def freeEl = row.selectFirst('font.promotion') def tds = row.select('td.rowfollow') - // 按列索引查找:size(含br)、seeders、leechers、completed、time - def size = '', seeders = '', leechers = '', completed = '', time = '', publisher = '' + def size = '', seeders = '', leechers = '', completed = '', time = '' for (int i = 0; i < tds.size(); i++) { - def t = utils.text(tds.get(i)) - if (!t) continue - if (t.contains('GB') || t.contains('MB') || t.contains('TB') || t.contains('KB')) size = t - else if (t ==~ /^\d+$/ && !seeders) { seeders = t } - else if (t ==~ /^\d+$/ && seeders) { leechers = t } - else if (t ==~ /^\d+$/ && seeders && leechers) { completed = t } - else if (t ==~ /^\d+年|^\d+月|^\d+天|^\d+时/) time = t - else if (seeders && !leechers) publisher = t + def cell = tds[i].text() + if (!cell) continue + if (cell.contains('GB')||cell.contains('MB')||cell.contains('TB')||cell.contains('KB')) size = cell + else if (cell ==~ /^\d+$/ && !seeders) seeders = cell + else if (cell ==~ /^\d+$/ && seeders && !leechers) leechers = cell + else if (cell ==~ /^\d+$/ && leechers) completed = cell + else if (cell ==~ /^\d+年|^\d+月|^\d+天|^\d+时/) time = cell } rows << [ - title: utils.text(titleEl), - detailUrl: utils.attr(titleEl, 'href'), - category: utils.attr(catEl, 'alt') ?: utils.attr(catEl, 'title') ?: '', - free: utils.text(freeEl), + title: titleEl.text(), + detailUrl: titleEl.attr('href'), + category: catEl?.attr('alt') ?: catEl?.attr('title') ?: '', + free: freeEl?.text(), size: size, seeders: seeders, leechers: leechers, completed: completed, publishDate: time, - publisher: publisher, ] } return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"] @@ -76,16 +77,16 @@ private Map parseSearch(doc) { private Map parseDetail(doc) { return [pageName: '种子详情', fields: [ - title: utils.text(utils.selectFirst(doc, '#top h1, h1')), - subtitle: utils.text(utils.selectFirst(doc, 'td.embedded b')), - size: utils.text(utils.selectFirst(doc, 'td:has(b:contains(大小)) + td, td.rowhead:contains(大小) + td')), - seeders: utils.text(utils.selectFirst(doc, 'td:contains(做种者) + td')), - leechers: utils.text(utils.selectFirst(doc, 'td:contains(下载者) + td')), + title: doc.selectFirst('#top h1, h1')?.text(), + subtitle: doc.selectFirst('td.embedded b')?.text(), + size: doc.selectFirst('td:has(b:contains(大小)) + td, td.rowhead:contains(大小) + td')?.text(), + seeders: doc.selectFirst('td:contains(做种者) + td')?.text(), + leechers: doc.selectFirst('td:contains(下载者) + td')?.text(), ]] } private Map parseSign(doc) { - def t = utils.text(doc.body()) + def t = doc.body().text() def matched = t ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/ return [pageName: '签到', fields: [result: t.take(200)], message: matched ? '成功' : '未知'] } @@ -93,86 +94,91 @@ private Map parseSign(doc) { private Map parseUserPanel(doc) { def fields = [:] doc.select('td.text').each { td -> - def t = utils.text(td) + def t = td.text() if (t.contains('分享率')) fields.shareRate = t.replaceAll(/[^0-9.]/, '') if (t.contains('魔力')) fields.bonus = t.replaceAll(/[^0-9.,]/, '') } - return [pageName: '控制面板', fields: fields] + return [pageName: '控制面板', fields: fields, message: fields.isEmpty() ? '未提取' : '解析成功'] +} + +// ==================== mediabot 运行时 ==================== + +Map sign(PluginContext ctx) { + if (!ctx.auth) return [message: '未配置认证', loginRequired: true] + try { + def url = ctx.siteUrl.replaceAll('/+\$', '') + '/attendance.php' + def body = http.get(url, ctx.auth) + def matched = body ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/ + return [success: matched, message: matched ? '已签到' : '失败'] + } catch (e) { return [message: "签到异常: ${e.message}"] } } Map sync(PluginContext ctx) { - if (!ctx.site.auth) return [message: "未配置认证", loginRequired: true] + if (!ctx.auth) return [message: '未配置认证', loginRequired: true] try { - def conn = ctx.httpGet('/index.php') - def doc = jsoup.parse(conn.body()) + def url = ctx.siteUrl.replaceAll('/+\$', '') + '/index.php' + def body = http.get(url, ctx.auth) + def doc = Jsoup.parse(body) def el = utils.selectFirst(doc, '#info_block, td.bottom.clearfix, .userbar') - if (!el) return [message: "未找到用户信息"] + if (!el) { ctx.incomplete = true; return [message: '未找到用户信息'] } def t = utils.text(el) def result = [:] def um = t =~ /上传[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/ def dm = t =~ /下载[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/ def rm = t =~ /分享率[::]\s*([\d.,]+)/ def bm = t =~ /魔力[值::]*[^0-9]*([\d.,]+)/ - if (um) result.upload = parseTraffic(um[0][1].replace(',','') as double, um[0][2]) - if (dm) result.download = parseTraffic(dm[0][1].replace(',','') as double, dm[0][2]) - if (rm) result.shareRate = rm[0][1].replace(',','') as double - if (bm) result.bonus = bm[0][1].replace(',','') as double - // 做种数 def sm = t =~ /⬆\s*(\d+)/ - if (sm) result.seeding = sm[0][1] as int + def im = t =~ /邀请[::]\s*(\d+)/ + if (um) result.upload = utils.parseTraffic(um[0][1] + (um[0][2] ?: '')) + if (dm) result.download = utils.parseTraffic(dm[0][1] + (dm[0][2] ?: '')) + if (rm) result.shareRatio = rm[0][1].replace(',','') as double + if (bm) result.bonus = bm[0][1].replace(',','') as double + if (sm) result.seeding = sm[0][1] as int + if (im) result.invites = im[0][1] as int + def hrEl = doc.selectFirst('#info_block font[class=fcr], #info_block font[color=red], #info_block .red') + if (hrEl) { + def hrm = hrEl.text() =~ /(\d+)/ + if (hrm) result.hnr = hrm[0][1] as int + } + if (!result.upload || !result.download) ctx.incomplete = true return result - } catch (e) { return [message: "同步失败: ${e.message}"] } -} - -Map sign(PluginContext ctx) { - if (!ctx.site.auth) return [message: "未配置认证", loginRequired: true] - try { - def conn = ctx.httpGet('/attendance.php') - def matched = conn.body() ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/ - return [success: matched, message: matched ? '已签到' : '失败'] - } catch (e) { return [message: "签到异常: ${e.message}"] } + } catch (e) { ctx.incomplete = true; return [message: "同步失败: ${e.message}"] } } List search(PluginContext ctx, Map params) { - if (!ctx.site.auth) return [] + if (!ctx.auth) return [] try { def keyword = params.keyword ?: '' def page = (params.page ?: 0) as int - def conn = ctx.httpGet("/torrents.php?search=${URLEncoder.encode(keyword,'UTF-8')}&page=${page}") - def doc = jsoup.parse(conn.body()) + def url = "${ctx.siteUrl.replaceAll('/+\$', '')}/torrents.php?search=${URLEncoder.encode(keyword,'UTF-8')}&page=${page}" + def body = http.get(url, ctx.auth) + def doc = Jsoup.parse(body) def rows = [] doc.select('table.torrents tr:has(td.rowfollow):not(:has(.colhead))').each { row -> - def titleEl = utils.selectFirst(row, 'td.embedded a[href*=details]') + def titleEl = row.selectFirst('td.embedded a[href*=details]') if (!titleEl) return def tds = row.select('td.rowfollow') - def size = 0, seeders = 0, leechers = 0, grabs = 0 + long size = 0; int seeders = 0, leechers = 0, grabs = 0 boolean hasSize = false, hasSeed = false, hasLeech = false for (int i = 0; i < tds.size(); i++) { - def t = utils.text(tds.get(i)) - if (!t) continue - if (!hasSize && (t.contains('GB')||t.contains('MB')||t.contains('TB')||t.contains('KB'))) {size=parseHtmlSize(t);hasSize=true} - else if (hasSize && !hasSeed && t ==~ /^\d+$/) {seeders=t as int;hasSeed=true} - else if (hasSeed && !hasLeech && t ==~ /^\d+$/) {leechers=t as int;hasLeech=true} - else if (hasSeed && hasLeech && t ==~ /^\d+$/) {grabs=t as int} + def cell = tds[i].text() + if (!cell) continue + if (!hasSize && (cell.contains('GB')||cell.contains('MB')||cell.contains('TB')||cell.contains('KB'))) { + size = utils.parseHtmlSize(cell); hasSize = true + } else if (hasSize && !hasSeed && cell ==~ /^\d+$/) { + seeders = utils.toInt(cell) ?: 0; hasSeed = true + } else if (hasSeed && !hasLeech && cell ==~ /^\d+$/) { + leechers = utils.toInt(cell) ?: 0; hasLeech = true + } else if (hasLeech && cell ==~ /^\d+$/) { + grabs = utils.toInt(cell) ?: 0 + } } rows << [ - title: utils.text(titleEl), - detailUrl: "${ctx.siteUrl}${utils.attr(titleEl,'href')}", - size: size, - seeders: seeders, - leechers: leechers, - grabs: grabs, + title: titleEl.text(), + detailUrl: "${ctx.siteUrl}${titleEl.attr('href')}", + size: size, seeders: seeders, leechers: leechers, grabs: grabs, ] } return rows } catch (e) { return [] } } - -private long parseTraffic(double val, String unit) { - def m = [B:1,KB:1024,MB:1048576,GB:1073741824,TB:1099511627776] - return (val * (m[unit?.toUpperCase()] ?: 1)) as long -} -private long parseHtmlSize(String s) { - def m = s =~ /([\d.]+)\s*(TB|GB|MB|KB|B)?/ - return m ? parseTraffic(m[0][1] as double, m[0][2]) : 0 -}