@@ -2,13 +2,16 @@ package main
22
33import (
44 "bytes"
5+ "encoding/json"
56 "github.com/PuerkitoBio/goquery"
67 "strconv"
78 "strings"
89)
910
1011// BCClient 实现了 BitComet 的客户端接口.
11- type BCClient struct {}
12+ type BCClient struct {
13+ Version int // 1: HTML, 2: JSON (v2.09+)
14+ }
1215
1316func (c * BCClient ) GetClientType () string {
1417 return "BitComet"
@@ -28,6 +31,10 @@ func (c *BCClient) Login() bool {
2831
2932// FetchTorrents 获取所有活动的种子列表.
3033func (c * BCClient ) FetchTorrents () ([]* Torrent , error ) {
34+ if c .Version == 2 {
35+ return c .FetchTorrents_v2 ()
36+ }
37+
3138 torrents := BC_FetchTorrents ()
3239 if torrents == nil {
3340 return nil , nil
@@ -46,6 +53,10 @@ func (c *BCClient) FetchTorrents() ([]*Torrent, error) {
4653
4754// FetchTorrentPeers 获取特定种子的 Peer 列表.
4855func (c * BCClient ) FetchTorrentPeers (torrent * Torrent ) ([]* Peer , error ) {
56+ if c .Version == 2 {
57+ return c .FetchTorrentPeers_v2 (torrent )
58+ }
59+
4960 peers := BC_FetchTorrentPeers (torrent .Hash )
5061 if peers == nil {
5162 return nil , nil
@@ -68,7 +79,101 @@ func (c *BCClient) FetchTorrentPeers(torrent *Torrent) ([]*Peer, error) {
6879}
6980
7081func (c * BCClient ) SubmitBlockPeer (blockPeerMap map [string ]BlockPeerInfoStruct ) bool {
71- return false // BitComet 暂未通过 WebUI 实现封禁.
82+ if c .Version == 2 {
83+ return c .BC_SubmitBlockPeer_v2 (blockPeerMap )
84+ }
85+ return false // BitComet 1.x 暂未通过 WebUI 实现封禁.
86+ }
87+
88+ // Version 2 实现逻辑.
89+ func (c * BCClient ) FetchTorrents_v2 () ([]* Torrent , error ) {
90+ _ , _ , responseBody := Fetch (config .ClientURL + "/api_v2/task_list/get?state_group=ACTIVE" , true , true , false , nil )
91+ if responseBody == nil {
92+ return nil , nil
93+ }
94+
95+ var resp BC_v2_TaskListResponse
96+ if err := json .Unmarshal (responseBody , & resp ); err != nil {
97+ Log ("FetchTorrents_v2" , GetLangText ("Error-Parse" ), true , err .Error ())
98+ return nil , err
99+ }
100+
101+ var result []* Torrent
102+ for _ , t := range resp .TaskList {
103+ if strings .ToUpper (t .Type ) != "BT" {
104+ continue
105+ }
106+ result = append (result , & Torrent {
107+ Hash : t .TaskID ,
108+ TotalSize : t .Size ,
109+ Tracker : "BitComet-v2" ,
110+ LeechCount : int64 (t .LeechCount ),
111+ })
112+ }
113+ return result , nil
114+ }
115+
116+ func (c * BCClient ) FetchTorrentPeers_v2 (torrent * Torrent ) ([]* Peer , error ) {
117+ _ , _ , responseBody := Fetch (config .ClientURL + "/api/task/peers/get?task_id=" + torrent .Hash + "&groups=peers_connected" , true , true , false , nil )
118+ if responseBody == nil {
119+ return nil , nil
120+ }
121+
122+ var resp BC_v2_PeerListResponse
123+ if err := json .Unmarshal (responseBody , & resp ); err != nil {
124+ Log ("FetchTorrentPeers_v2" , GetLangText ("Error-Parse" ), true , err .Error ())
125+ return nil , err
126+ }
127+
128+ var result []* Peer
129+ for _ , p := range resp .PeerList {
130+ result = append (result , & Peer {
131+ IP : p .IP ,
132+ Port : p .Port ,
133+ Client : p .Client ,
134+ DlSpeed : p .DlSpeed ,
135+ UpSpeed : p .UpSpeed ,
136+ Progress : p .Progress / 100.0 , // API 返回通常是 0-100.
137+ Downloaded : p .Downloaded ,
138+ Uploaded : p .Uploaded ,
139+ })
140+ }
141+ return result , nil
142+ }
143+
144+ func (c * BCClient ) BC_SubmitBlockPeer_v2 (blockPeerMap map [string ]BlockPeerInfoStruct ) bool {
145+ // 按 Torrent 分组 IP 以匹配 ban_ip 接口要求.
146+ taskIPs := make (map [string ][]string )
147+ for peerIP , peerInfo := range blockPeerMap {
148+ if peerInfo .InfoHash != "" {
149+ taskIPs [peerInfo .InfoHash ] = append (taskIPs [peerInfo .InfoHash ], peerIP )
150+ }
151+ }
152+
153+ if len (taskIPs ) == 0 {
154+ return true
155+ }
156+
157+ allSuccess := true
158+ for taskID , ips := range taskIPs {
159+ params := BC_v2_BanParams {
160+ TaskID : taskID ,
161+ BanTime : "ban_ip_forever" ,
162+ IPList : ips ,
163+ }
164+ postData , _ := json .Marshal (params )
165+ code , _ , _ := Submit (config .ClientURL + "/api/task/peers/ban_ip" , string (postData ), true , true , & Tr_jsonHeader )
166+ if code != 200 {
167+ allSuccess = false
168+ }
169+ }
170+ return allSuccess
171+ }
172+
173+ type BC_v2_BanParams struct {
174+ TaskID string `json:"task_id"`
175+ BanTime string `json:"ban_time"`
176+ IPList []string `json:"ip_list"`
72177}
73178
74179func (c * BCClient ) SubmitShadowBanPeer (blockPeerMap map [string ]BlockPeerInfoStruct ) bool {
@@ -91,6 +196,38 @@ type BC_PeerStruct struct {
91196 UpSpeed int64
92197}
93198
199+ // BitComet v2 JSON API 结构体.
200+ type BC_v2_CommonResponse struct {
201+ Result string `json:"result"`
202+ }
203+ type BC_v2_TaskListResponse struct {
204+ TaskList []BC_v2_Task `json:"movie_list"` // 该 API 实际返回的是 movie_list.
205+ }
206+ type BC_v2_Task struct {
207+ TaskID string `json:"task_id"`
208+ Type string `json:"type"`
209+ Size int64 `json:"total_size"`
210+ UpSpeed int64 `json:"upload_speed"`
211+ Status string `json:"state"`
212+ InfoHash string `json:"info_hash"`
213+ Name string `json:"name"`
214+ Progress int `json:"progress"`
215+ LeechCount int `json:"leechers_count"`
216+ }
217+ type BC_v2_PeerListResponse struct {
218+ PeerList []BC_v2_Peer `json:"peers_connected"`
219+ }
220+ type BC_v2_Peer struct {
221+ IP string `json:"address"`
222+ Port int `json:"remoteport"`
223+ Client string `json:"clienttype"`
224+ Progress float64 `json:"progress"`
225+ DlSpeed int64 `json:"downrate"`
226+ UpSpeed int64 `json:"uprate"`
227+ Downloaded int64 `json:"downsize"`
228+ Uploaded int64 `json:"upsize"`
229+ }
230+
94231func BC_ParseTorrentLink (torrentLinkStr string ) int {
95232 torrentIDSplit1 := strings .SplitN (StrTrim (torrentLinkStr ), "?id=" , 2 )
96233 if len (torrentIDSplit1 ) < 2 {
@@ -200,9 +337,24 @@ func BC_ParseIP(ipStr string) (string, int) {
200337
201338 return ipWithoutPortStr , port
202339}
203- func BC_DetectClient () bool {
340+ func (c * BCClient ) Detect () bool {
341+ // 优先探测 Version 2 (JSON API).
342+ apiResponseStatusCode , _ , _ := Fetch (config .ClientURL + "/api_v2/task_list/get" , false , false , false , nil )
343+ if apiResponseStatusCode == 200 || apiResponseStatusCode == 401 {
344+ c .Version = 2
345+ Log ("DetectClient" , "BitComet (Version 2 - JSON API) Detected" , true )
346+ return true
347+ }
348+
349+ // 回落探测 Version 1 (HTML).
204350 apiResponseStatusCode , apiResponseHeaders , _ := Fetch (config .ClientURL + "/panel/" , false , false , false , nil )
205- return (apiResponseStatusCode == 401 && strings .Contains (apiResponseHeaders .Get ("WWW-Authenticate" ), "BitComet" ))
351+ if apiResponseStatusCode == 401 && strings .Contains (apiResponseHeaders .Get ("WWW-Authenticate" ), "BitComet" ) {
352+ c .Version = 1
353+ Log ("DetectClient" , "BitComet (Version 1 - HTML) Detected" , true )
354+ return true
355+ }
356+
357+ return false
206358}
207359func BC_Login () bool {
208360 apiResponseStatusCode , _ , _ := Fetch (config .ClientURL + "/panel/" , false , true , false , nil )
0 commit comments