Skip to content

Commit 76b0bdf

Browse files
committed
fix get info error when torrent is a hybird torrent
1 parent 37c5e05 commit 76b0bdf

4 files changed

Lines changed: 119 additions & 31 deletions

File tree

backend/src/module/downloader/client/qbittorrent/qbittorrent.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ async def torrent_info(self, hash: str) -> TorrentDownloadInfo | None:
149149
url=QB_API_URL["properties"],
150150
params=data,
151151
)
152+
print(reps.json())
152153
reps.raise_for_status()
153154
logger.debug(f"[qbittorrent] Torrent info: {hash}")
154155
reps = reps.json()
@@ -228,8 +229,11 @@ async def add(self, torrent_url, save_path, category) -> str | None:
228229
logger.debug(
229230
f"[QbDownloader] Got torrent content, getting hash for {torrent_url}"
230231
)
231-
torrent_link = await req.get_torrent_hash(torrent_url)
232-
logger.debug(f"[QbDownloader] Got torrent hash: {torrent_link}")
232+
torrent_hashes = await req.get_torrent_hash(torrent_url)
233+
# 优先使用v2 hash,如果没有则使用v1 hash
234+
torrent_link = torrent_hashes.get("v2", torrent_hashes.get("v1", ""))
235+
logger.debug(f"[QbDownloader] Got torrent hashes: {torrent_hashes}")
236+
logger.debug(f"[QbDownloader] Using hash: {torrent_link}")
233237
file = {"torrents": torrent_file}
234238
else:
235239
logger.warning(
@@ -250,7 +254,8 @@ async def add(self, torrent_url, save_path, category) -> str | None:
250254
f"[QbDownloader] A BAD TORRENT{save_path} , send torrent to download fail.{resp.text.lower()}"
251255
)
252256
if resp.status_code == 200:
253-
return torrent_link
257+
# 只取前40个字符作为hash
258+
return torrent_link[:40]
254259
except Exception as e:
255260
self.handle_exception(e, "add")
256261

backend/src/module/network/request_contents.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from httpx import Response
77

88
from module.models import Torrent
9-
from module.utils import get_hash, torrent_to_link
9+
from module.utils import get_torrent_hashes
1010

1111
from .request_url import RequestURL
1212
from .site import rss_parser
@@ -166,10 +166,9 @@ async def get_rss_title(self, _url: str) -> str | None:
166166
if title is not None:
167167
return title.text
168168

169-
async def get_torrent_hash(self, _url: str) -> str:
169+
async def get_torrent_hash(self, _url: str) -> dict[str, str]:
170170
# 下载种子文件,处理 hash 与 url 不一致的情况
171171
if torrent_file := await self.get_content(_url):
172-
torrent_url = await torrent_to_link(torrent_file)
173-
torrent_hash = get_hash(torrent_url)
172+
torrent_hash = await get_torrent_hashes(torrent_file)
174173
return torrent_hash
175-
return ""
174+
return {"v1": "", "v2": ""}

backend/src/module/utils/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
from .cache_image import gen_poster_path, str_to_url, url_to_str
33
from .events import Event, EventBus, EventType, event_bus
44
from .path_parser import check_file, gen_save_path, get_path_basename, path_to_bangumi
5-
from .torrent import torrent_to_link
5+
from .torrent import torrent_to_link, get_torrent_hashes
66

77
__all__ = [
88
"check_file",
99
"gen_poster_path",
1010
"get_path_basename",
1111
"url_to_str",
1212
"torrent_to_link",
13+
"get_torrent_hashes",
1314
"str_to_url",
1415
"path_to_bangumi",
1516
"get_hash",
Lines changed: 105 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,121 @@
11
import base64
22
import hashlib
33
from urllib.parse import quote
4+
import logging
45

56
import bencodepy
67

8+
logger = logging.getLogger(__name__)
9+
10+
11+
def calculate_v2_hash(info_dict):
12+
"""计算BitTorrent v2 hash"""
13+
# 检查是否为v2种子或混合种子
14+
if b"meta version" in info_dict and info_dict[b"meta version"] == 2:
15+
# 对于v2种子,使用SHA256
16+
encoded_info = bencodepy.encode(info_dict)
17+
return hashlib.sha256(encoded_info).digest()
18+
19+
# 检查是否存在v2特有的字段
20+
if b"file tree" in info_dict:
21+
# 这是一个混合种子,计算v2 hash
22+
encoded_info = bencodepy.encode(info_dict)
23+
return hashlib.sha256(encoded_info).digest()
24+
25+
return None
26+
727

828
async def torrent_to_link(torrent_file):
9-
torrent_info = bencodepy.decode(torrent_file)
29+
try:
30+
logger.debug(
31+
f"torrent_to_link: Processing torrent file, type: {type(torrent_file)}, size: {len(torrent_file) if hasattr(torrent_file, '__len__') else 'unknown'}"
32+
)
1033

11-
# 获取 info 字段并进行 bencode 编码
12-
info = torrent_info[b"info"]
13-
encoded_info = bencodepy.encode(info)
34+
torrent_info = bencodepy.decode(torrent_file)
35+
logger.debug(
36+
f"torrent_to_link: Successfully decoded torrent, keys: {list(torrent_info.keys())}"
37+
)
1438

15-
# 计算 info_hash (SHA1 hash of the encoded info dictionary)
16-
info_hash = hashlib.sha1(encoded_info).digest()
39+
# 获取 info 字段并进行 bencode 编码
40+
info = torrent_info[b"info"]
41+
encoded_info = bencodepy.encode(info)
1742

18-
# 将 hash 转换为磁力链接格式
19-
info_hash_hex = base64.b16encode(info_hash).decode("utf-8").lower()
43+
# 计算 info_hash (SHA1 hash of the encoded info dictionary)
44+
info_hash_v1 = hashlib.sha1(encoded_info).digest()
45+
info_hash_hex_v1 = base64.b16encode(info_hash_v1).decode("utf-8").lower()
46+
logger.debug(f"torrent_to_link: V1 hash calculated: {info_hash_hex_v1}")
2047

21-
# # 获取文件名
22-
# name = torrent_info.get(b"info", {}).get(b"name", b"").decode("utf-8")
48+
# 尝试计算v2 hash
49+
info_hash_v2 = calculate_v2_hash(info)
2350

24-
# 构建磁力链接
25-
magnet_link = f"magnet:?xt=urn:btih:{info_hash_hex}"
26-
# if name:
27-
# magnet_link += f"&dn={quote(name)}"
51+
# 如果是混合种子或v2种子,优先使用v2 hash
52+
if info_hash_v2:
53+
info_hash_hex_v2 = base64.b16encode(info_hash_v2).decode("utf-8").lower()
54+
# V2 hash 使用 btmh (BitTorrent Meta Hash) 格式,1220表示SHA256
55+
magnet_link = f"magnet:?xt=urn:btmh:1220{info_hash_hex_v2}"
56+
logger.debug(f"torrent_to_link: Using V2 hash: {info_hash_hex_v2}")
57+
else:
58+
# 使用v1 hash
59+
magnet_link = f"magnet:?xt=urn:btih:{info_hash_hex_v1}"
60+
logger.debug(f"torrent_to_link: Using V1 hash: {info_hash_hex_v1}")
2861

29-
# 添加 trackers (可选)
30-
if b"announce" in torrent_info:
31-
tracker = torrent_info[b"announce"].decode("utf-8")
32-
magnet_link += f"&tr={quote(tracker)}"
62+
# # 获取文件名
63+
# name = torrent_info.get(b"info", {}).get(b"name", b"").decode("utf-8")
64+
# if name:
65+
# magnet_link += f"&dn={quote(name)}"
3366

34-
if b"announce-list" in torrent_info:
35-
for tracker_list in torrent_info[b"announce-list"]:
36-
tracker = tracker_list[0].decode("utf-8")
67+
# 添加 trackers (可选)
68+
if b"announce" in torrent_info:
69+
tracker = torrent_info[b"announce"].decode("utf-8")
3770
magnet_link += f"&tr={quote(tracker)}"
38-
return magnet_link
71+
72+
if b"announce-list" in torrent_info:
73+
for tracker_list in torrent_info[b"announce-list"]:
74+
tracker = tracker_list[0].decode("utf-8")
75+
magnet_link += f"&tr={quote(tracker)}"
76+
77+
logger.debug(f"torrent_to_link: Generated magnet link: {magnet_link}")
78+
return magnet_link
79+
80+
except Exception as e:
81+
logger.error(f"torrent_to_link: Error processing torrent file: {e}")
82+
logger.error(f"torrent_to_link: Torrent file type: {type(torrent_file)}")
83+
if hasattr(torrent_file, "__len__"):
84+
logger.error(f"torrent_to_link: Torrent file size: {len(torrent_file)}")
85+
if isinstance(torrent_file, bytes) and len(torrent_file) > 0:
86+
logger.error(f"torrent_to_link: First 50 bytes: {torrent_file[:50]}")
87+
return ""
88+
89+
90+
async def get_torrent_hashes(torrent_file):
91+
"""获取种子的所有可能hash (v1和v2)"""
92+
try:
93+
logger.debug(f"get_torrent_hashes: Processing torrent file")
94+
95+
torrent_info = bencodepy.decode(torrent_file)
96+
info = torrent_info[b"info"]
97+
encoded_info = bencodepy.encode(info)
98+
99+
# V1 hash (SHA1)
100+
v1_hash = hashlib.sha1(encoded_info).digest()
101+
v1_hash_hex = base64.b16encode(v1_hash).decode("utf-8").lower()
102+
logger.debug(f"get_torrent_hashes: V1 hash: {v1_hash_hex}")
103+
104+
hashes = {"v1": v1_hash_hex}
105+
106+
# V2 hash (SHA256)
107+
v2_hash = calculate_v2_hash(info)
108+
if v2_hash:
109+
v2_hash_hex = base64.b16encode(v2_hash).decode("utf-8").lower()
110+
hashes["v2"] = v2_hash_hex
111+
logger.debug(f"get_torrent_hashes: V2 hash: {v2_hash_hex}")
112+
else:
113+
logger.debug(
114+
"get_torrent_hashes: No V2 hash found, this is a V1-only torrent"
115+
)
116+
117+
return hashes
118+
119+
except Exception as e:
120+
logger.error(f"get_torrent_hashes: Error processing torrent file: {e}")
121+
return {"v1": ""}

0 commit comments

Comments
 (0)