// NexusPHP 站点默认脚本 // 适用: PThome / HDSky / HDFans / OurBits 等 Map parse(String content, String url) { def doc = Jsoup.parse(content) 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('/index.php') && !url.contains('?')) return parseUserInfo(doc) if (url.contains('/usercp.php')) return parseUserPanel(doc) if (url.contains('/torrentrss.php') || url.contains('/rss')) return parseRss(doc) return [pageName: '未知页面', message: "未配置: ${url}"] } private Map parseRss(doc) { return [pageName: 'RSS', message: 'RSS 内容由 Java 层解析'] } private Map parseUserInfo(doc) { def fields = [:] def el = doc.selectFirst('#info_block, td.bottom.clearfix, .userbar') if (el) { def t = el.text() 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.,]+)/ def sm = t =~ /⬆\s*(\d+)/ def lm = t =~ /⬇\s*(\d+)/ 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, message: fields.isEmpty() ? '未提取' : '解析成功'] } 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[href*=details]') if (!titleEl) return def catEl = row.selectFirst('td.rowfollow img, td.rowfollow span.category') def freeEl = row.selectFirst('font.promotion') def tds = row.select('td.rowfollow') def size = '', seeders = '', leechers = '', completed = '', time = '' for (int i = 0; i < tds.size(); i++) { 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: 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, ] } return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"] } private Map parseDetail(doc) { return [pageName: '种子详情', fields: [ 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 = doc.body().text() def matched = t ==~ /.*(签到成功|已签到|重复签到|获得.*魔力值).*/ return [pageName: '签到', fields: [result: t.take(200)], message: matched ? '成功' : '未知'] } private Map parseUserPanel(doc) { def fields = [:] doc.select('td.text').each { 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, 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.auth) return [message: '未配置认证', loginRequired: true] try { def url = ctx.siteUrl.replaceAll('/+\$', '') + '/index.php' def body = http.get(url, ctx.auth) def doc = Jsoup.parse(body) def el = doc.selectFirst('#info_block, td.bottom.clearfix, .userbar') if (!el) { ctx.incomplete = true; return [message: '未找到用户信息'] } def t = el.text() 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.,]+)/ def sm = t =~ /⬆\s*(\d+)/ 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) { ctx.incomplete = true; return [message: "同步失败: ${e.message}"] } } List search(PluginContext ctx, Map params) { if (!ctx.auth) return [] try { def keyword = params.keyword ?: '' def page = (params.page ?: 0) as int 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 = row.selectFirst('td.embedded a[href*=details]') if (!titleEl) return def tds = row.select('td.rowfollow') 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 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: titleEl.text(), detailUrl: "${ctx.siteUrl}${titleEl.attr('href')}", size: size, seeders: seeders, leechers: leechers, grabs: grabs, ] } return rows } catch (e) { return [] } }