diff --git a/docs/pttime.groovy b/docs/pttime.groovy index 731d46e..b1f7c0f 100644 --- a/docs/pttime.groovy +++ b/docs/pttime.groovy @@ -1,4 +1,8 @@ // PTTIME 适配脚本 +// 内置变量: content, url, jsoup(Jsoup), json(Jackson), utils, http(httpHelper) +// 导入: groovy.json.*, java.net.http.*, com.par.core.adapter.*, com.mediabot.site.* + +// ==================== PAR 校验入口 ==================== Map parse(String content, String url) { def doc = Jsoup.parse(content) @@ -8,9 +12,11 @@ Map parse(String content, String url) { if (url.contains('/attendance.php')) return parseSign(doc) if (url.contains('/index.php') && !url.contains('?')) return parseIndex(doc) - return [pageName: '未知页面', message: "未配置: ${url}"] + return [pageName: '未知页面', message: "未配置解析: ${url}"] } +// ── 首页 / 用户信息 ── + private Map parseIndex(doc) { def infoEl = doc.selectFirst('#info_block') if (!infoEl) return [pageName: '首页', message: '未找到用户信息'] @@ -22,23 +28,25 @@ private Map parseIndex(doc) { def bm = t =~ /魔力值\([^)]*\)[^0-9]*([\d.,]+)/ def sm = t =~ /⬆\s*(\d+)/ def lm = t =~ /⬇\s*(\d+)/ - if (um) fields.upload = um[0][1] + (um[0][2] ?: '') + 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 (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] - return [pageName: '用户信息', fields: fields, message: fields.isEmpty() ? '未提取' : '解析成功'] + return [pageName: '用户信息', fields: fields, message: fields.isEmpty() ? '未提取到数据' : '解析成功'] } +// ── 搜索(无 tbody, 列索引方式)── + private Map parseSearch(doc) { def rows = [] doc.select('table.torrents tr:has(td.rowfollow):not(:has(.colhead))').each { row -> def titleEl = row.selectFirst('td.embedded a.torrentname_title') if (!titleEl) return - def catEl = row.selectFirst('td.rowfollow span.category') + def catEl = row.selectFirst('td.rowfollow span.category') def freeEl = row.selectFirst('font.promotion') - def tds = row.select('td.rowfollow') + def tds = row.select('td.rowfollow') rows << [ title: titleEl.text(), detailUrl: titleEl.attr('href'), @@ -54,15 +62,19 @@ private Map parseSearch(doc) { return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"] } +// ── 种子详情 ── + private Map parseDetail(doc) { def titleEl = doc.selectFirst('a.torrentname_title b, #top h1') - def descEl = doc.selectFirst('td.embedded font[title], #kdescr') + def descEl = doc.selectFirst('td.embedded font[title], #kdescr') return [pageName: '种子详情', fields: [ - title: titleEl?.text(), + title: titleEl?.text(), description: descEl?.text()?.take(200) ]] } +// ── 签到 ── + private Map parseSign(doc) { def t = doc.body().text() def matched = t ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/ @@ -86,7 +98,7 @@ Map sync(PluginContext ctx) { def body = httpGet(ctx, '/index.php') def doc = Jsoup.parse(body) def infoEl = doc.selectFirst('#info_block') - if (!infoEl) { ctx.incomplete = true; return [message: '未找到用户信息'] } + if (!infoEl) { ctx.incomplete = true; return [message: '未找到用户信息区域'] } def t = infoEl.text() def result = [:] def um = t =~ /上传[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/ @@ -94,11 +106,11 @@ Map sync(PluginContext ctx) { def rm = t =~ /分享率[::]\s*([\d.,]+)/ def bm = t =~ /魔力值\([^)]*\)[^0-9]*([\d.,]+)/ def sm = t =~ /⬆\s*(\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 (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.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 (bm) result.bonus = bm[0][1].replace(',','') as double + if (sm) result.seeding = sm[0][1] as int if (!result.upload || !result.download) ctx.incomplete = true return result } catch (e) { ctx.incomplete = true; return [message: "同步失败: ${e.message}"] } @@ -133,21 +145,24 @@ List search(PluginContext ctx, Map params) { private String httpGet(PluginContext ctx, String path) { def url = ctx.siteUrl.replaceAll('/+\$', '') + '/' + path.replaceAll('^/+', '') - def req = HttpRequest.newBuilder().uri(java.net.URI.create(url)) - .timeout(Duration.ofSeconds(15)).header('User-Agent', 'MediaBot/1.0') - if (ctx.auth) req.header('Cookie', ctx.auth) - req.GET() - def resp = ctx.httpClient.send(req.build(), HttpResponse.BodyHandlers.ofString()) - ctx.capturedUrl = url; ctx.capturedBody = resp.body() + def builder = HttpRequest.newBuilder().uri(java.net.URI.create(url)) + .timeout(Duration.ofSeconds(15)) + .header('User-Agent', 'MediaBot/1.0') + .GET() + if (ctx.auth) builder.header('Cookie', ctx.auth) + def resp = ctx.httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString()) + ctx.capturedUrl = url + ctx.capturedBody = resp.body() return resp.body() } -// ==================== 工具 ==================== +// ==================== 数字工具 ==================== private long parseTraffic(double val, String unit) { - def m = [B:1,KB:1024,MB:1048576,GB:1073741824,TB:1099511627776] + 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)?/ if (!m) return 0