refactor: 脚本格式对齐mediabot(直接Jsoup/ctx.auth/capturedUrl)+PluginContext桩类补充字段
This commit is contained in:
@@ -1,12 +1,7 @@
|
|||||||
// PTTIME 适配脚本
|
// PTTIME 适配脚本
|
||||||
// PTTIME 基于 NexusPHP,HTML 结构差异:
|
|
||||||
// - 搜索页 torrent 表格无 <tbody>
|
|
||||||
// - 用户信息在 #info_block 中
|
|
||||||
// - 做种/下载数用 ⬆⬇ emoji
|
|
||||||
// - 认证字段 ctx.site.auth
|
|
||||||
|
|
||||||
Map parse(String content, String url) {
|
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('/torrents.php')) return parseSearch(doc)
|
||||||
if (url.contains('/details.php')) return parseDetail(doc)
|
if (url.contains('/details.php')) return parseDetail(doc)
|
||||||
@@ -17,9 +12,9 @@ Map parse(String content, String url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Map parseIndex(doc) {
|
private Map parseIndex(doc) {
|
||||||
def el = utils.selectFirst(doc, '#info_block')
|
def infoEl = doc.selectFirst('#info_block')
|
||||||
if (!el) return [pageName: '首页', message: '未找到用户信息']
|
if (!infoEl) return [pageName: '首页', message: '未找到用户信息']
|
||||||
def t = utils.text(el)
|
def t = infoEl.text()
|
||||||
def fields = [:]
|
def fields = [:]
|
||||||
def um = t =~ /上传[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/
|
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 dm = t =~ /下载[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/
|
||||||
@@ -33,55 +28,66 @@ private Map parseIndex(doc) {
|
|||||||
if (bm) fields.bonus = bm[0][1]
|
if (bm) fields.bonus = bm[0][1]
|
||||||
if (sm) fields.seeding = sm[0][1]
|
if (sm) fields.seeding = sm[0][1]
|
||||||
if (lm) fields.leeching = lm[0][1]
|
if (lm) fields.leeching = lm[0][1]
|
||||||
return [pageName: '用户信息', fields: fields]
|
return [pageName: '用户信息', fields: fields, message: fields.isEmpty() ? '未提取' : '解析成功']
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseSearch(doc) {
|
private Map parseSearch(doc) {
|
||||||
def rows = []
|
def rows = []
|
||||||
doc.select('table.torrents tr:has(td.rowfollow):not(:has(.colhead))').each { row ->
|
doc.select('table.torrents tr:has(td.rowfollow):not(:has(.colhead))').each { row ->
|
||||||
def titleEl = utils.selectFirst(row, 'td.embedded a.torrentname_title')
|
def titleEl = row.selectFirst('td.embedded a.torrentname_title')
|
||||||
if (!titleEl) return
|
if (!titleEl) return
|
||||||
def catEl = utils.selectFirst(row, 'td.rowfollow span.category')
|
def catEl = row.selectFirst('td.rowfollow span.category')
|
||||||
def freeEl = utils.selectFirst(row, 'font.promotion')
|
def freeEl = row.selectFirst('font.promotion')
|
||||||
def tds = row.select('td.rowfollow')
|
def tds = row.select('td.rowfollow')
|
||||||
rows << [
|
rows << [
|
||||||
title: utils.text(titleEl),
|
title: titleEl.text(),
|
||||||
detailUrl: utils.attr(titleEl, 'href'),
|
detailUrl: titleEl.attr('href'),
|
||||||
category: utils.attr(catEl, 'title') ?: '',
|
category: catEl?.attr('title') ?: '',
|
||||||
free: utils.text(freeEl),
|
free: freeEl?.text(),
|
||||||
size: utils.text(tds.size() > 5 ? tds.get(5) : null),
|
size: tds.size() > 5 ? tds[5].text() : '',
|
||||||
seeders: utils.text(tds.size() > 6 ? tds.get(6) : null),
|
seeders: tds.size() > 6 ? tds[6].text() : '',
|
||||||
leechers: utils.text(tds.size() > 7 ? tds.get(7) : null),
|
leechers: tds.size() > 7 ? tds[7].text() : '',
|
||||||
completed: utils.text(tds.size() > 8 ? tds.get(8) : null),
|
completed: tds.size() > 8 ? tds[8].text() : '',
|
||||||
publishDate: utils.text(tds.size() > 4 ? tds.get(4) : null),
|
publishDate: tds.size() > 4 ? tds[4].text() : '',
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"]
|
return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseDetail(doc) {
|
private Map parseDetail(doc) {
|
||||||
def fields = [:]
|
def titleEl = doc.selectFirst('a.torrentname_title b, #top h1')
|
||||||
def titleEl = utils.selectFirst(doc, 'a.torrentname_title b, #top h1')
|
def descEl = doc.selectFirst('td.embedded font[title], #kdescr')
|
||||||
fields.title = utils.text(titleEl)
|
return [pageName: '种子详情', fields: [
|
||||||
def descEl = utils.selectFirst(doc, 'td.embedded font[title], #kdescr')
|
title: titleEl?.text(),
|
||||||
if (descEl) fields.description = utils.text(descEl).take(200)
|
description: descEl?.text()?.take(200)
|
||||||
return [pageName: '种子详情', fields: fields]
|
]]
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map parseSign(doc) {
|
private Map parseSign(doc) {
|
||||||
def t = utils.text(doc.body())
|
def t = doc.body().text()
|
||||||
def matched = t ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/
|
def matched = t ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/
|
||||||
return [pageName: '签到', fields: [result: t.take(200)], message: matched ? '成功' : '未知']
|
return [pageName: '签到', fields: [result: t.take(200)], message: matched ? '成功' : '未知']
|
||||||
}
|
}
|
||||||
|
|
||||||
Map sync(PluginContext ctx) {
|
// ==================== mediabot 运行时 ====================
|
||||||
if (!ctx.site?.auth) return [message: "未配置认证", loginRequired: true]
|
|
||||||
|
Map sign(PluginContext ctx) {
|
||||||
|
if (!ctx.auth) return [message: '未配置认证', loginRequired: true]
|
||||||
try {
|
try {
|
||||||
def conn = ctx.httpGet('/index.php')
|
def body = httpGet(ctx, '/attendance.php')
|
||||||
def doc = jsoup.parse(conn.body())
|
def matched = body ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/
|
||||||
def el = utils.selectFirst(doc, '#info_block')
|
return [success: matched, message: matched ? '已签到' : '失败']
|
||||||
if (!el) return [message: "未找到用户信息"]
|
} catch (e) { return [message: "签到异常: ${e.message}"] }
|
||||||
def t = utils.text(el)
|
}
|
||||||
|
|
||||||
|
Map sync(PluginContext ctx) {
|
||||||
|
if (!ctx.auth) return [message: '未配置认证', loginRequired: true]
|
||||||
|
try {
|
||||||
|
def body = httpGet(ctx, '/index.php')
|
||||||
|
def doc = Jsoup.parse(body)
|
||||||
|
def infoEl = doc.selectFirst('#info_block')
|
||||||
|
if (!infoEl) { ctx.incomplete = true; return [message: '未找到用户信息'] }
|
||||||
|
def t = infoEl.text()
|
||||||
def result = [:]
|
def result = [:]
|
||||||
def um = t =~ /上传[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/
|
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 dm = t =~ /下载[::]\s*([0-9.,]+)\s*(TB|GB|MB|KB|B)?/
|
||||||
@@ -90,47 +96,54 @@ Map sync(PluginContext ctx) {
|
|||||||
def sm = t =~ /⬆\s*(\d+)/
|
def sm = t =~ /⬆\s*(\d+)/
|
||||||
if (um) result.upload = parseTraffic(um[0][1].replace(',','') as double, um[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 (dm) result.download = parseTraffic(dm[0][1].replace(',','') as double, dm[0][2])
|
||||||
if (rm) result.shareRate = rm[0][1].replace(',','') as double
|
if (rm) result.shareRatio = rm[0][1].replace(',','') as double
|
||||||
if (bm) result.bonus = bm[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 (sm) result.seeding = sm[0][1] as int
|
||||||
|
if (!result.upload || !result.download) ctx.incomplete = true
|
||||||
return result
|
return result
|
||||||
} catch (e) { return [message: "同步失败: ${e.message}"] }
|
} catch (e) { ctx.incomplete = true; 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}"] }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List search(PluginContext ctx, Map params) {
|
List search(PluginContext ctx, Map params) {
|
||||||
if (!ctx.site?.auth) return []
|
if (!ctx.auth) return []
|
||||||
try {
|
try {
|
||||||
def keyword = params.keyword ?: ''
|
def keyword = params.keyword ?: ''
|
||||||
def page = (params.page ?: 0) as int
|
def page = (params.page ?: 0) as int
|
||||||
def conn = ctx.httpGet("/torrents.php?search=${URLEncoder.encode(keyword,'UTF-8')}&page=${page}")
|
def body = httpGet(ctx, "/torrents.php?search=${URLEncoder.encode(keyword,'UTF-8')}&page=${page}")
|
||||||
def doc = jsoup.parse(conn.body())
|
def doc = Jsoup.parse(body)
|
||||||
def rows = []
|
def rows = []
|
||||||
doc.select('table.torrents tr:has(td.rowfollow):not(:has(.colhead))').each { row ->
|
doc.select('table.torrents tr:has(td.rowfollow):not(:has(.colhead))').each { row ->
|
||||||
def titleEl = utils.selectFirst(row, 'td.embedded a.torrentname_title')
|
def titleEl = row.selectFirst('td.embedded a.torrentname_title')
|
||||||
if (!titleEl) return
|
if (!titleEl) return
|
||||||
def tds = row.select('td.rowfollow')
|
def tds = row.select('td.rowfollow')
|
||||||
rows << [
|
rows << [
|
||||||
title: utils.text(titleEl),
|
title: titleEl.text(),
|
||||||
detailUrl: "${ctx.siteUrl}${utils.attr(titleEl,'href')}",
|
detailUrl: "${ctx.siteUrl}${titleEl.attr('href')}",
|
||||||
size: tds.size() > 5 ? parseHtmlSize(utils.text(tds.get(5))) : 0,
|
size: tds.size() > 5 ? parseHtmlSize(tds[5].text()) : 0,
|
||||||
seeders: tds.size() > 6 ? utils.toInt(utils.text(tds.get(6))) ?: 0 : 0,
|
seeders: tds.size() > 6 ? (tds[6].text() as int ?: 0) : 0,
|
||||||
leechers: tds.size() > 7 ? utils.toInt(utils.text(tds.get(7))) ?: 0 : 0,
|
leechers: tds.size() > 7 ? (tds[7].text() as int ?: 0) : 0,
|
||||||
grabs: tds.size() > 8 ? utils.toInt(utils.text(tds.get(8))) ?: 0 : 0,
|
grabs: tds.size() > 8 ? (tds[8].text() as int ?: 0) : 0,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
return rows
|
return rows
|
||||||
} catch (e) { return [] }
|
} catch (e) { return [] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== HTTP 工具 ====================
|
||||||
|
|
||||||
|
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()
|
||||||
|
return resp.body()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 工具 ====================
|
||||||
|
|
||||||
private long parseTraffic(double val, String unit) {
|
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
|
return (val * (m[unit?.toUpperCase()] ?: 1)) as long
|
||||||
|
|||||||
@@ -2,18 +2,19 @@ package com.mediabot.site;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* PluginContext 桩类(mediabot 命名空间),仅供 PAR Groovy 编译通过。
|
* PluginContext 桩类(mediabot 命名空间),仅供 PAR Groovy 编译通过。
|
||||||
* 实际运行时由 mediabot 注入真实实现。
|
* 字段名和结构参考 mediabo 实际 PluginContext。
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PluginContext {
|
public class PluginContext {
|
||||||
|
public String auth;
|
||||||
|
public String siteUrl;
|
||||||
public Site site;
|
public Site site;
|
||||||
public Object httpClient;
|
public Object httpClient;
|
||||||
|
public boolean incomplete;
|
||||||
public Object httpGet(String path) { return null; }
|
public String capturedUrl;
|
||||||
public Object httpPost(String path, String body) { return null; }
|
public String capturedBody;
|
||||||
|
|
||||||
public static class Site {
|
public static class Site {
|
||||||
public String authorization;
|
|
||||||
public String auth;
|
public String auth;
|
||||||
public String siteUrl;
|
public String siteUrl;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user