-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathU2TrackerUpdater.py
376 lines (300 loc) · 14.4 KB
/
U2TrackerUpdater.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# -*- coding: utf-8 -*
# U2TrackerUpdater-1.6
# 批量更新任务Tracker地址中的秘钥
# 依赖:Python3.8(requests,qbittorrent,python-qbittorrent,transmissionrpc,deluge-client)
# 原作者:
# qB-U2@杯杯杯杯具(https://gist.github.com/tongyifan/83220b417cffdd23528860ee0c518d15)
# Tr-U2@ITGR(https://gist.github.com/inertia42/f6120d118e47925095dbceb5e8e27272)
# De-U2@種崎敦美(https://github.com/XSky123/dmhy_change_securekey_deluge)
# 整合优化:U2@Loid(https://github.com/LoidVC/U2TrackerUpdater)
# 感谢帮助:U2@vincent-163(PR#1),U2@x琳x(PR#3),U2@Noira(论坛#139477),U2@Rhilip(PR#4),U2@soleil(PR#5),Github@ThunderMikey(PR#6)
# 原作者备注
# 0. 免责声明:程序仅在本地客户端qBittorrent v4.2.5/Transmission v2.94/Deluge v1.3.15上测试通过,运行结果与作者无关
# 1. 已知bug:从第 48 个请求开始会连续失败 10 次,在管理组修复之前请手动重复执行至所有种子更新完毕,直到显示找到0个未被更新的种子为止(已修复)
# 优化备注
# 1.添加交互逻辑
# 2.整合多客户端
# 3.本工具仅限用于于个人更新Tracker中的秘钥,禁止利用其进行带宽和运算资源占用、数据挖掘、规律遍历、商业使用或类似的活动。违规操作造成的警告、禁用相关账户,封锁IP、中止或终止API等后果自负责任。
# 4.执行脚本的主机和运行客户端的主机在同一局域网即可,客户端地址写运行客户端的主机的地址
import json
import os
import time
import requests
import transmission_rpc
from deluge_client import DelugeRPCClient
from qbittorrent import Client as QbittorrentClient
# 声明
notes = """
# U2TrackerUpdater-1.6
# 批量更新任务Tracker地址中的秘钥
# 依赖:Python3.8(requests,qbittorrent,python-qbittorrent,transmissionrpc,deluge-client)
# 原作者:U2@杯杯杯杯具(https://gist.github.com/tongyifan/83220b417cffdd23528860ee0c518d15)
# Tr-U2@ITGR(https://gist.github.com/inertia42/f6120d118e47925095dbceb5e8e27272)
# De-U2@種崎敦美(https://github.com/XSky123/dmhy_change_securekey_deluge)
# 整合优化:U2@Loid(https://github.com/LoidVC/U2TrackerUpdater)
# 感谢帮助:U2@vincent-163(PR#1),U2@x琳x(PR#3),U2@Noira(论坛#139477),U2@Rhilip(PR#4),U2@soleil(PR#5),Github@ThunderMikey(PR#6)
# 原作者备注
# 0. 免责声明:程序仅在本地客户端qBittorrent v4.2.5/Transmission v2.94/Deluge v1.3.15上测试通过,运行结果与作者无关
# 1. 已知bug:从第 48 个请求开始会连续失败 10 次,在管理组修复之前请手动重复执行至所有种子更新完毕,直到显示找到0个未被更新的种子为止
# 优化备注
# 1.添加交互逻辑
# 2.整合多客户端
# 3.本工具仅限用于于个人更新Tracker中的秘钥,禁止利用其进行带宽和运算资源占用、数据挖掘、规律遍历、商业使用或类似的活动。违规操作造成的警告、禁用相关账户,封锁IP、中止或终止API等后果自负责任。
# 4.执行脚本的主机和运行客户端的主机在同一局域网即可,客户端地址写运行客户端的主机的地址
"""
# 和U2的jsonrpc交互部分
endpoint = "https://u2.dmhy.org/jsonrpc_torrentkey.php"
u2_tracker = ["daydream.dmhy.best", "tracker.dmhy.org"]
to_tracker = "https://daydream.dmhy.best/announce?secure={}"
class BtClient(object):
batch_size = 25 # 防止单次提交的info_hashes过多的情况,把他chunk成25个作为一组提交
cache_file = "updated_torrents.json"
apikey = None
cached_hashes = []
def __init__(self):
self.apikey = input("输入APIKEY(站内查看API地址中的apikey=后面的部分):")
# 加载缓存
if os.path.exists(self.cache_file):
with open(self.cache_file, "r") as fp:
try:
self.cached_hashes = json.load(fp)
except Exception:
pass
def saveCachedTorrentHashes(self):
with open(self.cache_file, "w") as fp:
json.dump(self.cached_hashes, fp)
def getAllTorrentHashes(self) -> list:
"""
返回所有U2的种子信息,每个列表项都是个字典,必定包含 hash项,即 {hash: string,[key: string]: any}[]
:return:
"""
raise NotImplementedError()
def getUnCachedTorrentHashes(self) -> list:
return [
t for t in self.getAllTorrentHashes() if t["hash"] not in self.cached_hashes
]
def changeTorrentTracker(self, info, item):
"""
:param info: getAllTorrentHashes()中返回的列表项
:param item: u2对应info_hash的API返回值
:return:
"""
raise NotImplementedError()
def deleteTorrent(self, info_hash):
pass
def runJob(self):
u2_torrent_info_hash = self.getUnCachedTorrentHashes()
print("找到了{}个未被更新的种子~".format(len(u2_torrent_info_hash)))
# 防止单次提交的info_hashes过多的情况,把他chunk成{batch_size}个作为一组提交
for i in range(0, len(u2_torrent_info_hash), self.batch_size):
deal_torrents = u2_torrent_info_hash[i:i + self.batch_size]
print("正在获取第{}到第{}个种子的新secret key".format(i, i + len(deal_torrents)))
request_data = []
_index = {}
for index, torrent in enumerate(deal_torrents):
request_data.append(
{
"jsonrpc": "2.0",
"method": "query",
"params": [torrent["hash"]],
"id": index,
}
)
_index[index] = torrent
resp = requests.post(endpoint, params={"apikey": self.apikey}, json=request_data)
if resp.status_code == 503:
while resp.status_code == 503:
wait_second = int(resp.headers["Retry-After"]) + 5
print("速度过快,将在{}秒后重试".format(wait_second))
time.sleep(wait_second)
resp = requests.post(
endpoint, params={"apikey": self.apikey}, json=request_data
)
if resp.status_code == 403:
print("APIKEY无效,请检查(注意APIKEY不是passkey)")
exit()
if resp.status_code != 200:
print("意外的错误:{}".format(resp.status_code))
response_data = resp.json()
for item in response_data:
if item.get("error"):
if item["error"]["code"] == -10003:
self.deleteTorrent(_index[item["id"]]["hash"])
print(item.get("error"))
continue
print("正在编辑种子 {} 的Tracker信息".format(_index[item["id"]]["hash"]))
self.changeTorrentTracker(_index[item["id"]], item)
self.cached_hashes.append(_index[item["id"]]["hash"])
# 每个轮次结束后就更新缓存,不用等到所有结束后再缓存
self.saveCachedTorrentHashes()
class QBittorrent(BtClient):
def __init__(self):
super().__init__()
host = input("输入客户端地址(http://IP:端口):")
user = input("输入客户端用户名:")
password = input("输入客户端密码:")
qb = QbittorrentClient(host)
print("开始连接 Qbittorrent 客户端...")
qb.login(user, password)
self.qb = qb
def getAllTorrentHashes(self) -> list:
torrents = self.qb.torrents()
u2_torrent_info_hash = []
for torrent in torrents:
hashes = torrent["hash"]
trackers = self.qb.get_torrent_trackers(hashes)
for tker in trackers:
if any([tracker in tker["url"] for tracker in u2_tracker]):
u2_torrent_info_hash.append(
{"hash": hashes, "tracker": tker["url"]}
)
continue
return u2_torrent_info_hash
def changeTorrentTracker(self, info, item):
return self.qb._post(
"torrents/editTracker",
data={
"hash": info["hash"],
"origUrl": info["tracker"],
"newUrl": to_tracker.format(item["result"]),
},
)
def deleteTorrent(self, info_hash):
return self.qb.delete([info_hash])
class Transmission(BtClient):
def __init__(self):
super().__init__()
host = input("输入客户端IP:")
port = input("输入客户端端口(通常为9091):")
user = input("输入客户端用户名:")
password = input("输入客户端密码:")
print("开始连接 Transmission 客户端...")
self.tc = transmission_rpc.Client(host=host, port=int(port), username=user, password=password)
def getAllTorrentHashes(self) -> list:
torrents = list(filter(lambda x: x.trackers and 'dmhy' in x.trackers[0]['announce'], self.tc.get_torrents()))
u2_torrent_info_hash = []
for torrent in torrents:
u2_torrent_info_hash.append(
{"hash": torrent.hashString, "tracker": torrent.trackers[0]['announce'], "id": torrent.id}
)
return u2_torrent_info_hash
def changeTorrentTracker(self, info, item):
return self.tc.change_torrent(info["id"], trackerReplace=(0, to_tracker.format(item["result"])))
class DelugeRPC(BtClient):
def __init__(self):
super().__init__()
__DE_URL__ = input("输入客户端IP:")
__DE_PORT__ = int(input("输入客户端后端端口(非WebUI端口):"))
__DE_USER__ = input("输入客户端用户名:")
__DE_PW__ = input("输入客户端密码:")
self.client = DelugeRPCClient(__DE_URL__, __DE_PORT__, __DE_USER__, __DE_PW__)
print("开始连接 Deluge 客户端...")
self.client.connect()
def getAllTorrentHashes(self) -> list:
print("Fetching DMHY torrents from Deluge...")
torrent_list = self.client.core.get_torrents_status({}, ['trackers'])
return [
{"hash": str(hash_)[2:-1], "raw_hash": hash_}
for hash_ in torrent_list
if any([tracker in str(torrent_list[hash_][b'trackers'][0][b'url']) for tracker in u2_tracker])
]
def changeTorrentTracker(self, info, item):
self.client.core.set_torrent_trackers(info["raw_hash"], [
{'url': to_tracker.format(item["result"]), 'tier': 0}
])
class DelugeWebApi(BtClient):
id_ = 0
sess = None
def __init__(self):
print("本方法是以WEB API的形式连接Deluge客户端,如果你选择想以RPC API的形式连接。请退出重新选择 3: Deluge")
super().__init__()
self.host = input("输入客户端地址(http://IP:端口):")
password = input("输入客户端密码:")
print("开始连接 Deluge 客户端...")
login_json = self.webApiRequest('auth.login', [password])
try:
assert login_json['result'] is True
except Exception:
print('登录错误,请检查客户端地址和密码是否正确')
exit()
def webApiRequest(self, method, params):
if not self.sess:
self.sess = requests.Session()
self.id_ += 1
try:
req = self.sess.post("{}/json".format(self.host), json={
'method': method,
'params': params,
'id': self.id_
})
return req.json()
except Exception:
return {}
def getAllTorrentHashes(self) -> list:
res = self.webApiRequest('core.get_torrents_status', [{}, [
"trackers"
]])
torrent_list = res.get('result', [])
return [{"hash": hash_} for (hash_, details) in torrent_list.items() if
any([tracker in torrent_list[hash_]['trackers'][0]['url'] for tracker in u2_tracker])]
def changeTorrentTracker(self, info, item):
self.webApiRequest('core.set_torrent_trackers', [info['hash'], [{
'tier': 0,
'url': to_tracker.format(item["result"])
}]])
class RuTorrent(BtClient):
sess = None
def __init__(self):
super().__init__()
self.host = input("输入客户端地址(http://IP:端口):")
self.user = input("输入客户端用户名:")
self.password = input("输入客户端密码:")
try:
ping = self.webRequest('php/getsettings.php')
ping.json()
except Exception:
print('检查ruTorrent连接性失败,请确认客户端地址、用户名、密码是否正确')
exit()
def webRequest(self, path, data=None):
return requests.post('{}/{}'.format(self.host, path),
data=data,
auth=(self.user, self.password))
def getAllTorrentHashes(self) -> list:
res = self.webRequest('/plugins/httprpc/action.php', {'mode': 'trkall'})
torrent_list = res.json()
return [{"hash": hash_} for (hash_, details) in torrent_list.items() if
any([tracker in torrent_list[hash_][0][0] for tracker in u2_tracker])]
def changeTorrentTracker(self, info, item):
self.webRequest('/plugins/edit/action.php', {
'comment': '',
'set_comment': 1,
'set_trackers': 1,
'set_private': 0,
'private': 1,
'tracker': to_tracker.format(item["result"]),
'hash': info['hash']
})
if __name__ == '__main__':
# 声明
print(notes)
termDes = input('# 我已阅读并同意上述条款(Y/N):')
if termDes.lower() != 'y':
exit()
# 客户端类型
client = None
clientType = input('请输入客户端类型:\n1:qBittorrent, 2:Transmission, 3:Deluge (RPC API), 4:Deluge (Web API), 5:ruTorrent :')
if clientType == '1':
client = QBittorrent()
elif clientType == '2':
client = Transmission()
elif clientType == '3':
client = DelugeRPC()
elif clientType == '4':
client = DelugeWebApi()
elif clientType == '5':
client = RuTorrent()
else:
exit()
if client:
client.runJob()
input('感谢使用,祝您生活愉快。如果对您有帮助,欢迎star支持。(按任意键退出)')