docs: NexusPHP站点通用Groovy脚本,HTML/CSS选择器方式
This commit is contained in:
191
docs/nexusphp.groovy
Normal file
191
docs/nexusphp.groovy
Normal file
@@ -0,0 +1,191 @@
|
||||
// NexusPHP 站点通用脚本
|
||||
// 示例站点: PThome / HDSky / OurBits 等
|
||||
|
||||
// ==================== PAR 校验入口 ====================
|
||||
|
||||
Map parse(String content, String url) {
|
||||
def doc = jsoup.parse(content)
|
||||
|
||||
// ── 首页 / 用户信息 ──
|
||||
if (url.contains('/index.php') && !url.contains('?')) {
|
||||
return parseUserInfo(doc)
|
||||
}
|
||||
|
||||
// ── 搜索页 ──
|
||||
if (url.contains('/torrents.php')) {
|
||||
return parseSearch(doc)
|
||||
}
|
||||
|
||||
// ── 种子详情 ──
|
||||
if (url.contains('/details.php')) {
|
||||
return parseDetail(doc)
|
||||
}
|
||||
|
||||
// ── 签到 ──
|
||||
if (url.contains('/attendance.php')) {
|
||||
return parseSign(doc)
|
||||
}
|
||||
|
||||
// ── 控制面板 ──
|
||||
if (url.contains('/usercp.php')) {
|
||||
return parseUserPanel(doc)
|
||||
}
|
||||
|
||||
return [pageName: '未知页面', message: "未配置解析: ${url}"]
|
||||
}
|
||||
|
||||
private Map parseUserInfo(doc) {
|
||||
def fields = [:]
|
||||
def infoEl = utils.selectFirst(doc, '#info_block')
|
||||
if (infoEl) {
|
||||
def text = utils.text(infoEl)
|
||||
// 提取关键信息
|
||||
def uploadM = text =~ /上传[::]\s*([0-9.]+)\s*(TB|GB|MB|KB|B)?/
|
||||
def downloadM = text =~ /下载[::]\s*([0-9.]+)\s*(TB|GB|MB|KB|B)?/
|
||||
def ratioM = text =~ /分享率[::]\s*([\d.]+)/
|
||||
def bonusM = text =~ /魔力[值::]*\s*([\d,]+)/
|
||||
if (uploadM) fields.upload = uploadM[0][1] + (uploadM[0][2] ?: '')
|
||||
if (downloadM) fields.download = downloadM[0][1] + (downloadM[0][2] ?: '')
|
||||
if (ratioM) fields.ratio = ratioM[0][1]
|
||||
if (bonusM) fields.bonus = bonusM[0][1]
|
||||
}
|
||||
return [pageName: '用户信息', fields: fields, message: fields.isEmpty() ? '未提取到数据' : '解析成功']
|
||||
}
|
||||
|
||||
private Map parseSearch(doc) {
|
||||
def rows = []
|
||||
doc.select('table.torrents > tbody > tr:has(td.rowfollow):not(:has(th)):not(:has(.colhead))').each { row ->
|
||||
def titleEl = utils.selectFirst(row, 'td.embedded a[href*=details]')
|
||||
def catEl = utils.selectFirst(row, 'td.rowfollow a[href*=cat] img, td.rowfollow a[href*=torrents] img')
|
||||
def freeEl = utils.selectFirst(row, 'td.rowfollow font.free')
|
||||
def tds = row.select('td.rowfollow')
|
||||
|
||||
rows << [
|
||||
title: utils.text(titleEl),
|
||||
detailUrl: utils.attr(titleEl, 'href'),
|
||||
category: utils.attr(catEl, 'alt'),
|
||||
free: utils.text(freeEl),
|
||||
size: tds.size() > 4 ? utils.text(tds.get(4)) : '',
|
||||
seeders: tds.size() > 5 ? utils.text(tds.get(5)) : '',
|
||||
leechers: tds.size() > 6 ? utils.text(tds.get(6)) : '',
|
||||
completed: tds.size() > 7 ? utils.text(tds.get(7)) : '',
|
||||
comments: tds.size() > 8 ? utils.text(tds.get(8)) : '',
|
||||
publishDate: tds.size() > 3 ? utils.text(tds.get(3)) : '',
|
||||
torrentUrl: utils.attr(utils.selectFirst(row, 'td.rowfollow a[href*=download]'), 'href'),
|
||||
]
|
||||
}
|
||||
return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"]
|
||||
}
|
||||
|
||||
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, #subtitle')),
|
||||
size: utils.text(utils.selectFirst(doc, 'td.rowhead:contains(大小) + td, b:contains(大小) + span')),
|
||||
seeders: utils.text(utils.selectFirst(doc, 'td:contains(做种者) + td')),
|
||||
leechers: utils.text(utils.selectFirst(doc, 'td:contains(下载者) + td')),
|
||||
publishDate:utils.text(utils.selectFirst(doc, 'td.rowhead:contains(日期) + td')),
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
private Map parseSign(doc) {
|
||||
def text = utils.text(doc.body())
|
||||
def matched = text ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/
|
||||
return [
|
||||
pageName: '签到',
|
||||
fields: [result: text.length() > 200 ? text.substring(0, 200) + '...' : text],
|
||||
message: matched ? '签到成功' : '签到状态未知'
|
||||
]
|
||||
}
|
||||
|
||||
private Map parseUserPanel(doc) {
|
||||
def fields = [:]
|
||||
doc.select('td.text').each { td ->
|
||||
def t = utils.text(td)
|
||||
if (t.contains('分享率')) fields.shareRate = t.replaceAll(/[^0-9.]/, '')
|
||||
if (t.contains('魔力')) fields.bonus = t.replaceAll(/[^0-9.,]/, '')
|
||||
}
|
||||
return [pageName: '控制面板', fields: fields, message: fields.isEmpty() ? '未提取' : '解析成功']
|
||||
}
|
||||
|
||||
// ==================== mediabot 运行时入口 ====================
|
||||
|
||||
Map sign(PluginContext ctx) {
|
||||
if (!ctx.site.auth) return [message: "未配置认证", loginRequired: true]
|
||||
try {
|
||||
def conn = ctx.httpGet('/attendance.php')
|
||||
def body = conn.body()
|
||||
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]
|
||||
try {
|
||||
def conn = ctx.httpGet('/index.php')
|
||||
def doc = jsoup.parse(conn.body())
|
||||
def infoEl = utils.selectFirst(doc, '#info_block')
|
||||
if (!infoEl) return [message: "未找到用户信息区域"]
|
||||
def text = utils.text(infoEl)
|
||||
def result = [:]
|
||||
def uploadM = text =~ /上传[::]\s*([0-9.]+)\s*(TB|GB|MB|KB|B)?/
|
||||
def downloadM = text =~ /下载[::]\s*([0-9.]+)\s*(TB|GB|MB|KB|B)?/
|
||||
def ratioM = text =~ /分享率[::]\s*([\d.]+)/
|
||||
def bonusM = text =~ /魔力[值::]*\s*([\d,]+)/
|
||||
if (uploadM) result.upload = parseTraffic(uploadM[0][1] as double, uploadM[0][2])
|
||||
if (downloadM) result.download = parseTraffic(downloadM[0][1] as double, downloadM[0][2])
|
||||
if (ratioM) result.shareRate = ratioM[0][1] as double
|
||||
if (bonusM) result.bonus = bonusM[0][1].replace(',','') as double
|
||||
return result
|
||||
} catch (e) {
|
||||
return [message: "同步失败: ${e.message}"]
|
||||
}
|
||||
}
|
||||
|
||||
List search(PluginContext ctx, Map params) {
|
||||
if (!ctx.site.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 rows = []
|
||||
doc.select('table.torrents > tbody > tr:has(td.rowfollow):not(:has(th)):not(:has(.colhead))').each { row ->
|
||||
def titleEl = utils.selectFirst(row, 'td.embedded a[href*=details]')
|
||||
def catEl = utils.selectFirst(row, 'td.rowfollow a[href*=cat] img, td.rowfollow a[href*=torrents] img')
|
||||
def tds = row.select('td.rowfollow')
|
||||
rows << [
|
||||
title: utils.text(titleEl),
|
||||
detailUrl: "${ctx.siteUrl}${utils.attr(titleEl,'href')}",
|
||||
category: utils.attr(catEl, 'alt'),
|
||||
size: tds.size() > 4 ? parseHtmlSize(utils.text(tds.get(4))) : 0,
|
||||
seeders: tds.size() > 5 ? utils.toInt(utils.text(tds.get(5))) ?: 0 : 0,
|
||||
leechers: tds.size() > 6 ? utils.toInt(utils.text(tds.get(6))) ?: 0 : 0,
|
||||
grabs: tds.size() > 7 ? utils.toInt(utils.text(tds.get(7))) ?: 0 : 0,
|
||||
publishDate: tds.size() > 3 ? utils.text(tds.get(3)) : '',
|
||||
]
|
||||
}
|
||||
return rows
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 工具方法 ====================
|
||||
|
||||
private long parseTraffic(double val, String unit) {
|
||||
if (!unit) return val as long
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user