// M-Team 适配脚本 (mteam) // API 基地址(io 优先,cc 备用) final String API_BASE = "https://api.m-team.io" // ==================== PAR 校验入口 ==================== Map parse(String content, String url) { Map root try { root = json.readValue(content, Map) } catch (e) { def msg = e.message instanceof String ? e.message : e.toString() return [pageName: '解析失败', message: 'JSON 解析异常: ' + msg] } if (url.contains('/api/member/profile')) return parseProfile(root?.data) if (url.contains('/api/tracker/myPeerStatistics')) return parsePeerStats(root?.data) if (url.contains('/api/torrent/search')) return parseSearch(root?.data?.data ?: []) return [pageName: '未知接口', message: "未配置解析: ${url}"] } private Map parseProfile(d) { if (!d) return [pageName: '用户信息', message: 'data 为空'] return [ pageName: '用户信息', fields: [ uid: d.id?.toString(), username: d.username?.toString(), email: d.email?.toString(), upload: utils.formatSize((d.memberCount?.uploaded ?: 0) as long), download: utils.formatSize((d.memberCount?.downloaded ?: 0) as long), bonus: d.memberCount?.bonus?.toString(), shareRate:d.memberCount?.shareRate?.toString(), invites: d.invites?.toString(), ] ] } 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 st = it.status ?: [:] return [ id: it.id?.toString(), title: it.name ?: '', subTitle: it.smallDescr ?: '', size: utils.formatSize((it.size ?: 0) as long), seeders: (st.seeders ?: 0) as int, leechers: (st.leechers ?: 0) as int, 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()} 条"] } // ==================== mediabot 运行时入口 ==================== Map sync(PluginContext ctx) { if (!ctx.site.authorization) return [message: "未配置 Token", loginRequired: true] try { // 用户信息 def resp = apiPost(ctx, "/api/member/profile") if (resp.statusCode() == 403 || resp.statusCode() == 401) return [message: "Token 无效", loginRequired: true] if (resp.statusCode() != 200) return [message: "HTTP ${resp.statusCode()}", siteUnavailable: true] def json = new groovy.json.JsonSlurper().parseText(resp.body()) if (json.message != "SUCCESS" || !json.data) return [message: "响应格式异常"] def d = json.data def mc = d.memberCount ?: [:] def result = [ upload: mc.uploaded ? (mc.uploaded as long) * 1073741824 : null, download: mc.downloaded ? (mc.downloaded as long) * 1073741824 : null, bonus: mc.bonus ? mc.bonus as double : 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) { return [message: "同步失败: ${e.message}"] } } Map sign(PluginContext ctx) { return [message: "M-Team 不支持签到"] } List search(PluginContext ctx, Map params) { if (!ctx.site.authorization) return [] try { def keyword = params.keyword ?: "" def page = (params.page ?: 1) as int def body = groovy.json.JsonOutput.toJson([ keyword: keyword, pageNumber: page, pageSize: 100, mode: "normal", visible: 1 ]) def resp = apiPost(ctx, "/api/torrent/search", body) if (resp.statusCode() != 200) return [] def json = new groovy.json.JsonSlurper().parseText(resp.body()) def list = json?.data?.data ?: [] return list.collect { item -> def st = item.status ?: [:] return [ id: item.id?.toString(), title: item.name ?: "", subTitle: item.smallDescr ?: "", size: (item.size ?: 0) as long, seeders: (st.seeders ?: 0) as int, leechers: (st.leechers ?: 0) as int, completed: (st.timesCompleted ?: 0) as int, 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) { return [] } } // ==================== 工具方法 ==================== private def apiPost(PluginContext ctx, String path, String body = null) { def builder = java.net.http.HttpRequest.newBuilder() .uri(new java.net.URI(API_BASE + path)) .timeout(java.time.Duration.ofSeconds(20)) .header("User-Agent", "Mozilla/5.0") .header("x-api-key", ctx.site.authorization) .header("Content-Type", "application/json") if (body) builder.POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)) else builder.POST(java.net.http.HttpRequest.BodyPublishers.noBody()) return ctx.httpClient.send(builder.build(), java.net.http.HttpResponse.BodyHandlers.ofString()) }