diff --git a/docs/pttime.groovy b/docs/pttime.groovy index b1f7c0f..7b884a3 100644 --- a/docs/pttime.groovy +++ b/docs/pttime.groovy @@ -1,8 +1,7 @@ // PTTIME 适配脚本 -// 内置变量: content, url, jsoup(Jsoup), json(Jackson), utils, http(httpHelper) -// 导入: groovy.json.*, java.net.http.*, com.par.core.adapter.*, com.mediabot.site.* +// 内置: content, url, jsoup, json, utils, http -// ==================== PAR 校验入口 ==================== +// ==================== PAR 校验 ==================== Map parse(String content, String url) { def doc = Jsoup.parse(content) @@ -12,11 +11,9 @@ 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: '未找到用户信息'] @@ -34,11 +31,9 @@ private Map parseIndex(doc) { 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 -> @@ -62,26 +57,21 @@ 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') return [pageName: '种子详情', fields: [ - title: titleEl?.text(), - description: descEl?.text()?.take(200) + title: titleEl?.text(), description: descEl?.text()?.take(200) ]] } -// ── 签到 ── - private Map parseSign(doc) { def t = doc.body().text() def matched = t ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/ return [pageName: '签到', fields: [result: t.take(200)], message: matched ? '成功' : '未知'] } -// ==================== mediabot 运行时 ==================== +// ==================== mediabot 运行时(统一用 http.get/post + utils) ==================== Map sign(PluginContext ctx) { if (!ctx.auth) return [message: '未配置认证', loginRequired: true] @@ -98,7 +88,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)?/ @@ -106,8 +96,8 @@ 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 = 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 @@ -131,40 +121,21 @@ List search(PluginContext ctx, Map params) { rows << [ title: titleEl.text(), detailUrl: "${ctx.siteUrl}${titleEl.attr('href')}", - size: tds.size() > 5 ? parseHtmlSize(tds[5].text()) : 0, - seeders: tds.size() > 6 ? (tds[6].text() as int ?: 0) : 0, - leechers: tds.size() > 7 ? (tds[7].text() as int ?: 0) : 0, - grabs: tds.size() > 8 ? (tds[8].text() as int ?: 0) : 0, + size: tds.size() > 5 ? utils.parseHtmlSize(tds[5].text()) : 0, + seeders: tds.size() > 6 ? utils.toInt(tds[6].text()) ?: 0 : 0, + leechers: tds.size() > 7 ? utils.toInt(tds[7].text()) ?: 0 : 0, + grabs: tds.size() > 8 ? utils.toInt(tds[8].text()) ?: 0 : 0, ] } return rows } catch (e) { return [] } } -// ==================== HTTP 工具 ==================== +// ── HTTP 封装(mediabot用,设置 capturedUrl/Body 以便自动提交到 PAR)── private String httpGet(PluginContext ctx, String path) { - def url = ctx.siteUrl.replaceAll('/+\$', '') + '/' + path.replaceAll('^/+', '') - 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()) + def url = path.startsWith('http') ? path : ctx.siteUrl.replaceAll('/+\$', '') + '/' + path.replaceAll('^/+', '') 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] - 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 - return parseTraffic(m[0][1].replace(',','') as double, m[0][2]) + ctx.capturedBody = http.get(url, ctx.auth) + return ctx.capturedBody } diff --git a/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java b/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java index b64f7a1..cce8e61 100644 --- a/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java +++ b/par-core/src/main/java/com/par/core/adapter/GroovyScriptEngine.java @@ -193,7 +193,6 @@ public class GroovyScriptEngine { return null; } catch (Exception e) { return null; } } - /** 字节数 → 可读大小 */ public String formatSize(long bytes) { if (bytes == 0) return "0"; String[] units = {"B","KB","MB","GB","TB"}; @@ -202,12 +201,33 @@ public class GroovyScriptEngine { while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; } return String.format("%.2f %s", v, units[i]); } - /** 字符串转数字 */ public Long toLong(String s) { try { return Long.parseLong(s.replaceAll("[^\\d-]", "")); } catch (NumberFormatException e) { return null; } } public Integer toInt(String s) { try { return Integer.parseInt(s.replaceAll("[^\\d-]", "")); } catch (NumberFormatException e) { return null; } } + /** 解析流量字符串:600.44GB → 字节数 */ + public long parseTraffic(String s) { + if (s == null) return 0; + double val = 0; String unit = null; + var m = java.util.regex.Pattern.compile("([\\d.,]+)\\s*(TB|GB|MB|KB|B)?", java.util.regex.Pattern.CASE_INSENSITIVE).matcher(s); + if (m.find()) { + val = Double.parseDouble(m.group(1).replace(",", "")); + unit = m.group(2); + } + if (unit == null) return (long) val; + return (long) (val * switch (unit.toUpperCase()) { + case "TB" -> 1099511627776L; + case "GB" -> 1073741824L; + case "MB" -> 1048576L; + case "KB" -> 1024L; + default -> 1L; + }); + } + /** 解析 HTML 大小字符串:20.21GB → 字节数 */ + public long parseHtmlSize(String s) { + return parseTraffic(s); + } } }