Skip to content

Commit 91d23e5

Browse files
author
Simple-Tracker
committed
Improve & Add BitComet WebUI API
1 parent 1d8e9eb commit 91d23e5

6 files changed

Lines changed: 162 additions & 10 deletions

File tree

client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func DetectClient() bool {
4343
// 先尝试 qBittorrent.
4444
qb := &QBClient{}
4545
if config.ClientType == "" || config.ClientType == qb.GetClientType() {
46-
if qB_GetAPIVersion() {
46+
if qb.Detect() {
4747
currentClient = qb
4848
currentClientType = qb.GetClientType()
4949
Log("DetectClient", GetLangText("Success-DetectClient"), true, currentClientType)
@@ -54,7 +54,7 @@ func DetectClient() bool {
5454
// 再尝试 Transmission.
5555
tr := &TRClient{}
5656
if config.ClientType == "" || config.ClientType == tr.GetClientType() {
57-
if Tr_DetectVersion() {
57+
if tr.Detect() {
5858
currentClient = tr
5959
currentClientType = tr.GetClientType()
6060
Log("DetectClient", GetLangText("Success-DetectClient"), true, currentClientType)
@@ -65,7 +65,7 @@ func DetectClient() bool {
6565
// 最后尝试 BitComet.
6666
bc := &BCClient{}
6767
if config.ClientType == "" || config.ClientType == bc.GetClientType() {
68-
if BC_DetectClient() {
68+
if bc.Detect() {
6969
currentClient = bc
7070
currentClientType = bc.GetClientType()
7171
Log("DetectClient", GetLangText("Success-DetectClient"), true, currentClientType)

client_BitComet.go

Lines changed: 156 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package main
22

33
import (
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

1316
func (c *BCClient) GetClientType() string {
1417
return "BitComet"
@@ -28,6 +31,10 @@ func (c *BCClient) Login() bool {
2831

2932
// FetchTorrents 获取所有活动的种子列表.
3033
func (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 列表.
4855
func (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

7081
func (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

74179
func (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+
94231
func 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
}
207359
func BC_Login() bool {
208360
apiResponseStatusCode, _, _ := Fetch(config.ClientURL+"/panel/", false, true, false, nil)

client_Transmission.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func Tr_ProcessHTTP(w http.ResponseWriter, r *http.Request) bool {
147147
func Tr_SetURL() bool {
148148
return false
149149
}
150-
func Tr_DetectVersion() bool {
150+
func (c *TRClient) Detect() bool {
151151
detectJSON, err := json.Marshal(Tr_RequestStruct{Method: "session-get", Args: Tr_ArgsStruct{Field: []string{"version"}}})
152152
if err != nil {
153153
Log("DetectVersion", GetLangText("Error-GenJSON"), true, err.Error())

client_qBittorrent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ func qB_SetURL() bool {
207207
Log("SetURL", GetLangText("Success-SetURL"), true, qBWebUIEnabled, config.ClientURL, config.ClientUsername)
208208
return true
209209
}
210-
func qB_GetAPIVersion() bool {
210+
func (c *QBClient) Detect() bool {
211211
if !strings.HasSuffix(config.ClientURL, "/api") {
212212
apiResponseStatusCodeWithSuffix, _, _ := Fetch(config.ClientURL+"/api/v2/app/webapiVersion", false, false, false, nil)
213213
if apiResponseStatusCodeWithSuffix == 200 || apiResponseStatusCodeWithSuffix == 403 {

client_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ type Client interface {
1010
FetchTorrentPeers(torrent *Torrent) ([]*Peer, error)
1111
SubmitBlockPeer(blockPeerMap map[string]BlockPeerInfoStruct) bool
1212
SubmitShadowBanPeer(blockPeerMap map[string]BlockPeerInfoStruct) bool
13+
Detect() bool
1314
}
1415

1516
// Torrent 统一表示不同客户端返回的种子信息.

util.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"net"
55
"os"
66
"os/exec"
7-
"strconv"
87
"strings"
98
"sync"
109
"time"

0 commit comments

Comments
 (0)