refactor: 按JSON配置完善脚本,补全 parse/sync/search 全部字段

This commit is contained in:
mediabot-pt
2026-07-01 11:19:59 +08:00
parent 8c8496348d
commit cb1daf90c9

View File

@@ -1,63 +1,69 @@
// M-Team 适配脚本 (mteam) // M-Team 适配脚本 (mteam)
// 站点类型: mtorrent // API 基地址(io 优先,cc 备用)
// API 基地址: https://api.m-team.io (优先) / https://api.m-team.cc (备用)
final String API_BASE = "https://api.m-team.io" final String API_BASE = "https://api.m-team.io"
// ==================== PAR 校验入口 ==================== // ==================== PAR 校验入口 ====================
// content: 访问结果页面内容(JSON 字符串)
// url: 访问结果的 URL
// 返回: [pageName:"xx", fields:[k:v,...]] 或 [pageName:"xx", extracted:[[...]], message:"..."]
Map parse(String content, String url) { Map parse(String content, String url) {
def slurp = new groovy.json.JsonSlurper() def slurp = new groovy.json.JsonSlurper()
def root = slurp.parseText(content) def root = slurp.parseText(content)
// ── 用户信息 ── if (url.contains('/api/member/profile'))
if (url.contains('/api/member/profile')) { return parseProfile(root?.data)
def d = root?.data
if (!d) return [pageName: '用户信息', message: 'data 为空']
return buildProfile(d)
}
// ── 搜索 ── if (url.contains('/api/tracker/myPeerStatistics'))
if (url.contains('/api/torrent/search')) { return parsePeerStats(root?.data)
def items = root?.data?.data ?: []
if (items.isEmpty()) return [pageName: '搜索', message: '无结果'] if (url.contains('/api/torrent/search'))
return buildSearchResults(items) return parseSearch(root?.data?.data ?: [])
}
// ── 未匹配 ──
return [pageName: '未知接口', message: "未配置解析: ${url}"] return [pageName: '未知接口', message: "未配置解析: ${url}"]
} }
private Map buildProfile(d) { private Map parseProfile(d) {
if (!d) return [pageName: '用户信息', message: 'data 为空']
return [ return [
pageName: '用户信息', pageName: '用户信息',
fields: [ fields: [
id: d.id?.toString(), uid: d.id?.toString(),
username: d.username?.toString(), username: d.username?.toString(),
uploaded: utils.formatSize(d.uploaded as long), email: d.email?.toString(),
downloaded: utils.formatSize(d.downloaded as long), upload: utils.formatSize((d.memberCount?.uploaded ?: 0) as long),
bonus: d.bonus?.toString(), download: utils.formatSize((d.memberCount?.downloaded ?: 0) as long),
seeding: d.seeding?.toString(), bonus: d.memberCount?.bonus?.toString(),
seedingSize: utils.formatSize(d.seedingSize as long), shareRate:d.memberCount?.shareRate?.toString(),
invite: d.memberCount?.invite?.toString(), invites: d.invites?.toString(),
] ]
] ]
} }
private Map buildSearchResults(List items) { private Map parsePeerStats(d) {
if (!d) return [pageName: '做种统计', message: 'data 为空']
return [
pageName: '做种统计',
fields: [
seeding: d.seederCount?.toString() ?: '0',
]
]
}
private Map parseSearch(List items) {
if (items.isEmpty()) return [pageName: '搜索', message: '无结果']
def rows = items.collect { it -> def rows = items.collect { it ->
def st = it.status ?: [:] def st = it.status ?: [:]
return [ return [
title: it.name ?: '', id: it.id?.toString(),
size: utils.formatSize((it.size ?: 0) as long), title: it.name ?: '',
seeders: st.seeders ?: 0, subTitle: it.smallDescr ?: '',
leechers: st.leechers ?: 0, size: utils.formatSize((it.size ?: 0) as long),
grabs: st.timesCompleted ?: 0, seeders: (st.seeders ?: 0) as int,
discount: st.discount ?: 'NORMAL', leechers: (st.leechers ?: 0) as int,
category: it.category ?: '', completed: (st.timesCompleted ?: 0) as int,
category: it.category ?: '',
free: st.discount ?: 'NORMAL',
publisher: it.author ?: '',
cnSub: it.hasChineseSubtitle != null ? it.hasChineseSubtitle.toString() : '',
standard: it.standard ?: '',
] ]
} }
return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"] return [pageName: '搜索', extracted: rows, message: "共 ${rows.size()} 条"]
@@ -68,7 +74,8 @@ private Map buildSearchResults(List items) {
Map sync(PluginContext ctx) { Map sync(PluginContext ctx) {
if (!ctx.site.authorization) return [message: "未配置 Token", loginRequired: true] if (!ctx.site.authorization) return [message: "未配置 Token", loginRequired: true]
try { try {
def resp = apiGet(ctx, "/api/member/profile") // 用户信息
def resp = apiPost(ctx, "/api/member/profile")
if (resp.statusCode() == 403 || resp.statusCode() == 401) if (resp.statusCode() == 403 || resp.statusCode() == 401)
return [message: "Token 无效", loginRequired: true] return [message: "Token 无效", loginRequired: true]
if (resp.statusCode() != 200) if (resp.statusCode() != 200)
@@ -77,14 +84,26 @@ Map sync(PluginContext ctx) {
def json = new groovy.json.JsonSlurper().parseText(resp.body()) def json = new groovy.json.JsonSlurper().parseText(resp.body())
if (json.message != "SUCCESS" || !json.data) if (json.message != "SUCCESS" || !json.data)
return [message: "响应格式异常"] return [message: "响应格式异常"]
def d = json.data def d = json.data
return [ def mc = d.memberCount ?: [:]
upload: d.uploaded ? (d.uploaded as long) * 1073741824 : null, def result = [
download: d.downloaded ? (d.downloaded as long) * 1073741824 : null, upload: mc.uploaded ? (mc.uploaded as long) * 1073741824 : null,
bonus: d.bonus ? d.bonus as double : null, download: mc.downloaded ? (mc.downloaded as long) * 1073741824 : null,
seeding: d.seeding ? d.seeding as int : null, bonus: mc.bonus ? mc.bonus as double : null,
seedingSize: d.seedingSize? (d.seedingSize as long) * 1073741824: null, shareRate:mc.shareRate ? mc.shareRate as double : null,
invites: d.invites ? d.invites as int : null,
] ]
// 做种统计
def seedResp = apiPost(ctx, "/api/tracker/myPeerStatistics")
if (seedResp.statusCode() == 200) {
def sj = new groovy.json.JsonSlurper().parseText(seedResp.body())
if (sj?.data?.seederCount != null)
result.seeding = sj.data.seederCount as int
}
return result
} catch (e) { } catch (e) {
return [message: "同步失败: ${e.message}"] return [message: "同步失败: ${e.message}"]
} }
@@ -100,7 +119,7 @@ List search(PluginContext ctx, Map params) {
def keyword = params.keyword ?: "" def keyword = params.keyword ?: ""
def page = (params.page ?: 1) as int def page = (params.page ?: 1) as int
def body = groovy.json.JsonOutput.toJson([ def body = groovy.json.JsonOutput.toJson([
keyword: keyword, pageNumber: page, pageSize: 50, visible: 1 keyword: keyword, pageNumber: page, pageSize: 100, mode: "normal", visible: 1
]) ])
def resp = apiPost(ctx, "/api/torrent/search", body) def resp = apiPost(ctx, "/api/torrent/search", body)
if (resp.statusCode() != 200) return [] if (resp.statusCode() != 200) return []
@@ -110,14 +129,25 @@ List search(PluginContext ctx, Map params) {
return list.collect { item -> return list.collect { item ->
def st = item.status ?: [:] def st = item.status ?: [:]
return [ return [
title: item.name ?: "", id: item.id?.toString(),
detailUrl: "${ctx.siteUrl}detail/${item.id ?: ''}", title: item.name ?: "",
size: (item.size ?: 0) as long, subTitle: item.smallDescr ?: "",
seeders: (st.seeders ?: 0) as int, size: (item.size ?: 0) as long,
leechers: (st.leechers ?: 0) as int, seeders: (st.seeders ?: 0) as int,
grabs: (st.timesCompleted ?: 0) as int, leechers: (st.leechers ?: 0) as int,
category: item.category ?: "", completed: (st.timesCompleted ?: 0) as int,
free: st.discount == "FREE" || st.discount == "_2X_FREE", publishDate: item.createdDate ?: "",
category: item.category ?: "",
free: st.discount == "FREE" || st.discount == "_2X_FREE",
freeEndTime: st.discountEndTime ?: "",
publisher: item.author ?: "",
tags: item.labelsNew?.join(",") ?: "",
doubanUrl: item.douban ?: "",
doubanRating: item.doubanRating?.toString() ?: "",
imdbUrl: item.imdb ?: "",
cnSub: item.hasChineseSubtitle != null ? item.hasChineseSubtitle.toString() : "",
standard: item.standard ?: "",
detailUrl: "${ctx.siteUrl}detail/${item.id ?: ''}",
] ]
} }
} catch (e) { } catch (e) {
@@ -127,28 +157,17 @@ List search(PluginContext ctx, Map params) {
// ==================== 工具方法 ==================== // ==================== 工具方法 ====================
private def apiGet(PluginContext ctx, String path) { private def apiPost(PluginContext ctx, String path, String body = null) {
return ctx.httpClient.send( def builder = java.net.http.HttpRequest.newBuilder()
java.net.http.HttpRequest.newBuilder() .uri(new java.net.URI(API_BASE + path))
.uri(new java.net.URI(API_BASE + path)) .timeout(java.time.Duration.ofSeconds(20))
.timeout(java.time.Duration.ofSeconds(path.contains('profile') ? 10 : 20)) .header("User-Agent", "Mozilla/5.0")
.header("User-Agent", "Mozilla/5.0") .header("x-api-key", ctx.site.authorization)
.header("x-api-key", ctx.site.authorization) .header("Content-Type", "application/json")
.header("Origin", ctx.siteUrl)
.POST(java.net.http.HttpRequest.BodyPublishers.noBody())
.build(),
java.net.http.HttpResponse.BodyHandlers.ofString())
}
private def apiPost(PluginContext ctx, String path, String body) { if (body) builder.POST(java.net.http.HttpRequest.BodyPublishers.ofString(body))
return ctx.httpClient.send( else builder.POST(java.net.http.HttpRequest.BodyPublishers.noBody())
java.net.http.HttpRequest.newBuilder()
.uri(new java.net.URI(API_BASE + path)) return ctx.httpClient.send(builder.build(),
.timeout(java.time.Duration.ofSeconds(20))
.header("User-Agent", "Mozilla/5.0")
.header("x-api-key", ctx.site.authorization)
.header("Content-Type", "application/json")
.POST(java.net.http.HttpRequest.BodyPublishers.ofString(body))
.build(),
java.net.http.HttpResponse.BodyHandlers.ofString()) java.net.http.HttpResponse.BodyHandlers.ofString())
} }