torrt 1.2.0__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {torrt-1.2.0 → torrt-1.2.1}/.gitignore +1 -0
- {torrt-1.2.0 → torrt-1.2.1}/PKG-INFO +2 -3
- {torrt-1.2.0 → torrt-1.2.1}/pyproject.toml +4 -1
- torrt-1.2.1/src/torrt/__init__.py +3 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/base_rpc.py +28 -1
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/base_tracker.py +84 -9
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/bots/telegram_bot.py +68 -13
- torrt-1.2.1/src/torrt/incomplete.py +324 -0
- torrt-1.2.1/src/torrt/magnet.py +68 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/main.py +14 -5
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/rpc/deluge.py +32 -10
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/rpc/qbittorrent.py +46 -15
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/rpc/transmission.py +46 -8
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/rpc/utorrent.py +44 -26
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/toolbox.py +138 -67
- torrt-1.2.1/src/torrt/trackers/anilibria.py +217 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/eniahd.py +4 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/nnmclub.py +4 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/rutor.py +4 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/rutracker.py +4 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/utils.py +111 -23
- torrt-1.2.0/src/torrt/__init__.py +0 -3
- torrt-1.2.0/src/torrt/trackers/anilibria.py +0 -178
- {torrt-1.2.0 → torrt-1.2.1}/LICENSE +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/README.md +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/base_bot.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/base_notifier.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/bots/__init__.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/exceptions.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/notifiers/__init__.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/notifiers/mail.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/notifiers/telegram.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/rpc/__init__.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/__init__.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/anidub.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/casstudio.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/kinozal.py +0 -0
- {torrt-1.2.0 → torrt-1.2.1}/src/torrt/trackers/ytsmx.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: torrt
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Automates torrent updates for you.
|
|
5
5
|
Project-URL: Homepage, https://github.com/idlesign/torrt
|
|
6
6
|
Project-URL: Documentation, https://torrt.readthedocs.io/
|
|
@@ -13,7 +13,6 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
13
13
|
Classifier: Operating System :: OS Independent
|
|
14
14
|
Classifier: Programming Language :: Python
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -15,7 +15,6 @@ classifiers = [
|
|
|
15
15
|
"Operating System :: OS Independent",
|
|
16
16
|
"Programming Language :: Python",
|
|
17
17
|
"Programming Language :: Python :: 3",
|
|
18
|
-
"Programming Language :: Python :: 3.10",
|
|
19
18
|
"Programming Language :: Python :: 3.11",
|
|
20
19
|
"Programming Language :: Python :: 3.12",
|
|
21
20
|
"Programming Language :: Python :: 3.13",
|
|
@@ -75,6 +74,10 @@ packages = ["src/torrt"]
|
|
|
75
74
|
packages = ["src/"]
|
|
76
75
|
|
|
77
76
|
[tool.pytest.ini_options]
|
|
77
|
+
addopts = "-m 'not live_trackers'"
|
|
78
|
+
markers = [
|
|
79
|
+
"live_trackers: explicit checks against real torrent tracker websites",
|
|
80
|
+
]
|
|
78
81
|
testpaths = [
|
|
79
82
|
"tests",
|
|
80
83
|
]
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
from typing import Any, ClassVar
|
|
1
|
+
from typing import Any, ClassVar, NamedTuple
|
|
2
2
|
|
|
3
|
+
from .exceptions import TorrtRPCException
|
|
3
4
|
from .utils import HttpClient, RPCClassesRegistry, RPCObjectsRegistry, TorrentData, WithSettings
|
|
4
5
|
|
|
5
6
|
|
|
7
|
+
class TorrentFile(NamedTuple):
|
|
8
|
+
"""Identify a file in the client's current torrent metadata."""
|
|
9
|
+
|
|
10
|
+
index: int
|
|
11
|
+
name: str
|
|
12
|
+
|
|
13
|
+
class TorrentStatus(NamedTuple):
|
|
14
|
+
"""Describe a client's torrent without requiring a local torrent file."""
|
|
15
|
+
|
|
16
|
+
hash: str
|
|
17
|
+
metadata_ready: bool
|
|
18
|
+
files: tuple[TorrentFile, ...] = ()
|
|
19
|
+
|
|
6
20
|
class BaseRPC(WithSettings):
|
|
7
21
|
"""Base RPC class. All RPC classes should inherit from this."""
|
|
8
22
|
|
|
@@ -65,6 +79,19 @@ class BaseRPC(WithSettings):
|
|
|
65
79
|
"""
|
|
66
80
|
raise NotImplementedError
|
|
67
81
|
|
|
82
|
+
def method_get_torrent_status(self, hash_str: str) -> TorrentStatus | None:
|
|
83
|
+
"""Return metadata status, or None only when absence is confirmed.
|
|
84
|
+
|
|
85
|
+
Failed status queries must raise TorrtRPCException rather than report absence.
|
|
86
|
+
"""
|
|
87
|
+
raise TorrtRPCException("Magnet readiness checks are not supported by this RPC")
|
|
88
|
+
|
|
89
|
+
def method_apply_file_exclusions(self, status: TorrentStatus, excluded_names: list[str]) -> None:
|
|
90
|
+
"""Apply saved file exclusions using the supplied torrent metadata."""
|
|
91
|
+
|
|
92
|
+
if excluded_names:
|
|
93
|
+
raise TorrtRPCException("File exclusions are not supported by this RPC")
|
|
94
|
+
|
|
68
95
|
def method_remove_torrent(self, hash_str: str, *, with_data: bool = False) -> Any: # pragma: nocover
|
|
69
96
|
"""Removes torrent from torrent client using RPC.
|
|
70
97
|
|
|
@@ -7,6 +7,7 @@ from typing import ClassVar
|
|
|
7
7
|
from urllib.parse import parse_qs, urljoin, urlparse
|
|
8
8
|
|
|
9
9
|
from .exceptions import TorrtTrackerException
|
|
10
|
+
from .magnet import parse_magnet
|
|
10
11
|
from .utils import (
|
|
11
12
|
BeautifulSoup,
|
|
12
13
|
HttpClient,
|
|
@@ -110,7 +111,7 @@ class BaseTracker(WithSettings):
|
|
|
110
111
|
silence_exceptions=True,
|
|
111
112
|
)
|
|
112
113
|
|
|
113
|
-
if response and response.url.startswith(mirror_url):
|
|
114
|
+
if response and response.ok and response.url.startswith(mirror_url):
|
|
114
115
|
mirror_picked = mirror_domain
|
|
115
116
|
break
|
|
116
117
|
|
|
@@ -141,9 +142,31 @@ class BaseTracker(WithSettings):
|
|
|
141
142
|
:param string: String, describing torrent. E.g. URL from torrent comment.
|
|
142
143
|
|
|
143
144
|
"""
|
|
145
|
+
|
|
146
|
+
if not isinstance(string, str) or not string:
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
parsed_url = urlparse(string)
|
|
151
|
+
hostname = parsed_url.hostname
|
|
152
|
+
|
|
153
|
+
if (
|
|
154
|
+
parsed_url.scheme not in ("http", "https")
|
|
155
|
+
or not hostname
|
|
156
|
+
or parsed_url.username is not None
|
|
157
|
+
or parsed_url.port == 0
|
|
158
|
+
):
|
|
159
|
+
# maybe forgery
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
except ValueError:
|
|
163
|
+
return False
|
|
164
|
+
|
|
144
165
|
for domain in chain([cls.alias], cls.mirrors):
|
|
145
|
-
|
|
166
|
+
|
|
167
|
+
if domain and hostname in (domain.lower(), f"www.{domain.lower()}"):
|
|
146
168
|
return True
|
|
169
|
+
|
|
147
170
|
return False
|
|
148
171
|
|
|
149
172
|
@classmethod
|
|
@@ -280,16 +303,31 @@ class BaseTracker(WithSettings):
|
|
|
280
303
|
"""This should implement a configuration test, e.g. make test login and report success."""
|
|
281
304
|
return True
|
|
282
305
|
|
|
283
|
-
def get_torrent(
|
|
284
|
-
|
|
285
|
-
|
|
306
|
+
def get_torrent(
|
|
307
|
+
self, url: str, *, last_updated: datetime | None = None, prefer_magnet: bool = False,
|
|
308
|
+
) -> TorrentData | None:
|
|
309
|
+
"""Return torrent file contents or a preferred magnet extracted by this tracker.
|
|
286
310
|
|
|
287
311
|
:param url: URL to download torrent file from
|
|
288
312
|
:param last_updated: torrent last updated datetime
|
|
313
|
+
:param prefer_magnet: use a valid magnet instead of downloading a torrent file
|
|
289
314
|
|
|
290
315
|
"""
|
|
291
316
|
raise NotImplementedError # pragma: nocover
|
|
292
317
|
|
|
318
|
+
def get_magnet_link(self, url: str) -> str:
|
|
319
|
+
"""Return a magnet only when the tracker explicitly implements extraction."""
|
|
320
|
+
return ""
|
|
321
|
+
|
|
322
|
+
def _find_magnet_link(self, url: str) -> str:
|
|
323
|
+
"""Find a magnet href on a supported tracker page using the page cache."""
|
|
324
|
+
|
|
325
|
+
if not self.can_handle(url):
|
|
326
|
+
return ""
|
|
327
|
+
|
|
328
|
+
page = self.get_torrent_page(url)
|
|
329
|
+
return self.find_links(url, page, definite=r"(?i)^magnet:\?") or ""
|
|
330
|
+
|
|
293
331
|
def extract_page_data(self) -> PageData:
|
|
294
332
|
data = PageData(
|
|
295
333
|
title=self.extract_page_title(),
|
|
@@ -364,14 +402,32 @@ class GenericTracker(BaseTracker):
|
|
|
364
402
|
"""
|
|
365
403
|
return url.split('=')[1]
|
|
366
404
|
|
|
367
|
-
def get_torrent(
|
|
368
|
-
|
|
369
|
-
|
|
405
|
+
def get_torrent(
|
|
406
|
+
self, url: str, *, last_updated: datetime | None = None, prefer_magnet: bool = False,
|
|
407
|
+
) -> TorrentData | None:
|
|
408
|
+
"""Get torrent data from a tracker page, preferring a magnet when requested.
|
|
370
409
|
|
|
371
410
|
:param url: URL to find and get torrent from
|
|
372
411
|
:param last_updated: torrent last updated datetime
|
|
412
|
+
:param prefer_magnet: use a valid magnet instead of downloading a torrent file
|
|
373
413
|
|
|
374
414
|
"""
|
|
415
|
+
|
|
416
|
+
if not self.can_handle(url):
|
|
417
|
+
self.log_warning("Unsupported tracker page URL")
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
if prefer_magnet and (magnet := self.get_magnet_link(url)):
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
parse_magnet(magnet)
|
|
424
|
+
|
|
425
|
+
except ValueError:
|
|
426
|
+
self.log_warning("Invalid magnet link on tracker page; using torrent file instead")
|
|
427
|
+
|
|
428
|
+
else:
|
|
429
|
+
return TorrentData(url=url, magnet=magnet, page=self.extract_page_data())
|
|
430
|
+
|
|
375
431
|
download_link = self.get_download_link(url)
|
|
376
432
|
|
|
377
433
|
if not download_link:
|
|
@@ -382,7 +438,9 @@ class GenericTracker(BaseTracker):
|
|
|
382
438
|
|
|
383
439
|
self.log_debug(f'Torrent download link found: {download_link}')
|
|
384
440
|
|
|
385
|
-
|
|
441
|
+
date_updated = page_data.date_updated
|
|
442
|
+
|
|
443
|
+
if last_updated and date_updated and last_updated >= date_updated:
|
|
386
444
|
self.log_debug('Skipped as up to date')
|
|
387
445
|
return None
|
|
388
446
|
else:
|
|
@@ -501,9 +559,24 @@ class GenericPrivateTracker(GenericPublicTracker):
|
|
|
501
559
|
def test_configuration(self) -> bool:
|
|
502
560
|
return self.login(self.alias)
|
|
503
561
|
|
|
562
|
+
def _find_magnet_link(self, url: str) -> str:
|
|
563
|
+
"""Find a visible magnet, signing in once if the page requires a session."""
|
|
564
|
+
magnet = super()._find_magnet_link(url)
|
|
565
|
+
|
|
566
|
+
if not magnet and not self.logged_in and self.login(self.extract_domain(url)):
|
|
567
|
+
magnet = super()._find_magnet_link(url)
|
|
568
|
+
|
|
569
|
+
return magnet
|
|
570
|
+
|
|
504
571
|
def login(self, domain: str) -> bool:
|
|
505
572
|
"""Implements tracker login procedure. Returns success bool."""
|
|
506
573
|
|
|
574
|
+
domain_url = f"https://{domain}/"
|
|
575
|
+
|
|
576
|
+
if not self.can_handle(domain_url) or urlparse(domain_url).netloc != domain:
|
|
577
|
+
self.log_warning("Unsupported tracker login domain")
|
|
578
|
+
return False
|
|
579
|
+
|
|
507
580
|
login_url = self.login_url % {'domain': domain}
|
|
508
581
|
|
|
509
582
|
self.log_debug(f'Trying to login at {login_url} ...')
|
|
@@ -549,6 +622,8 @@ class GenericPrivateTracker(GenericPublicTracker):
|
|
|
549
622
|
self.query_string = parsed_qs[self.auth_qs_param_name][0]
|
|
550
623
|
|
|
551
624
|
self.cookies = response.cookies
|
|
625
|
+
self._torrent_page = None
|
|
626
|
+
self._torrent_page_url = ""
|
|
552
627
|
|
|
553
628
|
# Save auth info to config.
|
|
554
629
|
self.save_settings()
|
|
@@ -27,14 +27,18 @@ class TelegramBot(BaseBot):
|
|
|
27
27
|
alias: str = 'telegram'
|
|
28
28
|
url: str = 'https://api.telegram.org/bot'
|
|
29
29
|
|
|
30
|
-
def __init__(self, token: str, *, allowed_users: str =
|
|
30
|
+
def __init__(self, token: str, *, allowed_users: str = "") -> None:
|
|
31
31
|
"""
|
|
32
32
|
:param token: str Telegram's bot token
|
|
33
|
-
:param allowed_users: comma-joined list of users allowed to
|
|
33
|
+
:param allowed_users: comma-joined list of users allowed to manage torrents.
|
|
34
34
|
|
|
35
35
|
"""
|
|
36
36
|
self.token = token
|
|
37
37
|
self.allowed_users = allowed_users or ''
|
|
38
|
+
self._allowed_usernames = frozenset(
|
|
39
|
+
name.strip().removeprefix("@").lower()
|
|
40
|
+
for name in self.allowed_users.split(",") if name.strip()
|
|
41
|
+
)
|
|
38
42
|
|
|
39
43
|
if not telegram:
|
|
40
44
|
raise BotRegistrationFailed('You have not installed python-telegram-bot library.')
|
|
@@ -44,10 +48,19 @@ class TelegramBot(BaseBot):
|
|
|
44
48
|
self.dispatcher = self.updater.dispatcher
|
|
45
49
|
|
|
46
50
|
if self.allowed_users:
|
|
47
|
-
self.handler_kwargs = {
|
|
51
|
+
self.handler_kwargs = {"filters": Filters.user(username=list(self._allowed_usernames))}
|
|
48
52
|
|
|
49
53
|
super().__init__()
|
|
50
54
|
|
|
55
|
+
def is_allowed_user(self, update: "Update") -> bool:
|
|
56
|
+
"""Check the actual sender, including users clicking shared group buttons."""
|
|
57
|
+
|
|
58
|
+
if not self.allowed_users:
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
user = update.effective_user
|
|
62
|
+
return bool(user and user.username and user.username.lower() in self._allowed_usernames)
|
|
63
|
+
|
|
51
64
|
def test_configuration(self) -> bool:
|
|
52
65
|
return telegram and bool(self.updater)
|
|
53
66
|
|
|
@@ -91,9 +104,21 @@ class TelegramBot(BaseBot):
|
|
|
91
104
|
add_handler(CommandHandler('remove', self.command_remove_torrents, **kwargs))
|
|
92
105
|
add_handler(CommandHandler('help', self.command_help, **kwargs))
|
|
93
106
|
|
|
94
|
-
def handle_callbacks(self, update:
|
|
107
|
+
def handle_callbacks(self, update: "Update", context: "CallbackContext") -> None:
|
|
95
108
|
"""Handler to process all callbacks from buttons"""
|
|
96
109
|
|
|
110
|
+
query = update.callback_query
|
|
111
|
+
|
|
112
|
+
if not self.is_allowed_user(update):
|
|
113
|
+
|
|
114
|
+
if query:
|
|
115
|
+
query.answer("Access denied.", show_alert=True)
|
|
116
|
+
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
if not query or not isinstance(query.data, str):
|
|
120
|
+
return
|
|
121
|
+
|
|
97
122
|
handlers = {
|
|
98
123
|
'add_torrent': self.handle_ask_url,
|
|
99
124
|
'list_torrents': self.command_list_torrents,
|
|
@@ -107,7 +132,10 @@ class TelegramBot(BaseBot):
|
|
|
107
132
|
elif update.callback_query.data.startswith('hash:'):
|
|
108
133
|
self.handle_remove_torrents(update, context)
|
|
109
134
|
|
|
110
|
-
def handle_ask_url(self, update:
|
|
135
|
+
def handle_ask_url(self, update: "Update", context: "CallbackContext") -> int:
|
|
136
|
+
|
|
137
|
+
if not self.is_allowed_user(update):
|
|
138
|
+
return ConversationHandler.END
|
|
111
139
|
|
|
112
140
|
message = update.callback_query.message
|
|
113
141
|
message.reply_text(
|
|
@@ -116,7 +144,10 @@ class TelegramBot(BaseBot):
|
|
|
116
144
|
)
|
|
117
145
|
return self.URL
|
|
118
146
|
|
|
119
|
-
def handle_process_url(self, update:
|
|
147
|
+
def handle_process_url(self, update: "Update", context: "CallbackContext") -> int:
|
|
148
|
+
|
|
149
|
+
if not self.is_allowed_user(update):
|
|
150
|
+
return ConversationHandler.END
|
|
120
151
|
|
|
121
152
|
torrent_url = update.message.text
|
|
122
153
|
torrent_data = get_torrent_from_url(torrent_url)
|
|
@@ -158,7 +189,10 @@ class TelegramBot(BaseBot):
|
|
|
158
189
|
|
|
159
190
|
return self.PATH
|
|
160
191
|
|
|
161
|
-
def handle_ask_download_path(self, update:
|
|
192
|
+
def handle_ask_download_path(self, update: "Update", context: "CallbackContext") -> int:
|
|
193
|
+
|
|
194
|
+
if not self.is_allowed_user(update):
|
|
195
|
+
return ConversationHandler.END
|
|
162
196
|
|
|
163
197
|
torrent_url = context.user_data.get('url')
|
|
164
198
|
|
|
@@ -204,7 +238,7 @@ class TelegramBot(BaseBot):
|
|
|
204
238
|
|
|
205
239
|
return ConversationHandler.END
|
|
206
240
|
|
|
207
|
-
def handle_remove_torrents(self, update:
|
|
241
|
+
def handle_remove_torrents(self, update: "Update", context: "CallbackContext") -> None:
|
|
208
242
|
"""
|
|
209
243
|
Handler for torrent remove action.
|
|
210
244
|
data is colon-joined string which is contains:
|
|
@@ -213,6 +247,10 @@ class TelegramBot(BaseBot):
|
|
|
213
247
|
3. 0|1 - optional attribute (with_data) responsible for data removal from RPC
|
|
214
248
|
For example 'hash:1234567890' or 'hash:1234567890:0'
|
|
215
249
|
"""
|
|
250
|
+
|
|
251
|
+
if not self.is_allowed_user(update):
|
|
252
|
+
return
|
|
253
|
+
|
|
216
254
|
message = update.callback_query.message
|
|
217
255
|
data = update.callback_query.data
|
|
218
256
|
split_data = data.split(':')[1:]
|
|
@@ -256,9 +294,12 @@ class TelegramBot(BaseBot):
|
|
|
256
294
|
|
|
257
295
|
return self.URL
|
|
258
296
|
|
|
259
|
-
def command_add_torrent(self, update:
|
|
297
|
+
def command_add_torrent(self, update: "Update", context: "CallbackContext") -> None:
|
|
260
298
|
"""Stand-alone handler to add torrent"""
|
|
261
299
|
|
|
300
|
+
if not self.is_allowed_user(update):
|
|
301
|
+
return
|
|
302
|
+
|
|
262
303
|
torrent_url = update.message.text.removeprefix('/add ')
|
|
263
304
|
|
|
264
305
|
if not torrent_url:
|
|
@@ -286,9 +327,17 @@ class TelegramBot(BaseBot):
|
|
|
286
327
|
else:
|
|
287
328
|
context.bot.send_message(chat_id, text='Unable to register the torrent.')
|
|
288
329
|
|
|
289
|
-
def command_list_torrents(self, update:
|
|
330
|
+
def command_list_torrents(self, update: "Update", context: "CallbackContext") -> None:
|
|
290
331
|
"""Command to list all monitored torrents"""
|
|
291
332
|
|
|
333
|
+
if not self.is_allowed_user(update):
|
|
334
|
+
return
|
|
335
|
+
|
|
336
|
+
message = update.effective_message
|
|
337
|
+
|
|
338
|
+
if message is None:
|
|
339
|
+
return
|
|
340
|
+
|
|
292
341
|
torrents = []
|
|
293
342
|
|
|
294
343
|
for idx, torrent in enumerate(get_registered_torrents().values(), 1):
|
|
@@ -298,16 +347,23 @@ class TelegramBot(BaseBot):
|
|
|
298
347
|
else:
|
|
299
348
|
torrents.append(f"{idx}. {torrent['name']}")
|
|
300
349
|
|
|
301
|
-
message = update.callback_query.message
|
|
302
350
|
if torrents:
|
|
303
351
|
message.reply_text('\n\n'.join(torrents))
|
|
304
352
|
|
|
305
353
|
else:
|
|
306
354
|
message.reply_text('No torrents yet.')
|
|
307
355
|
|
|
308
|
-
def command_remove_torrents(self, update:
|
|
356
|
+
def command_remove_torrents(self, update: "Update", context: "CallbackContext") -> None:
|
|
309
357
|
"""Command to remove torrent"""
|
|
310
358
|
|
|
359
|
+
if not self.is_allowed_user(update):
|
|
360
|
+
return
|
|
361
|
+
|
|
362
|
+
message = update.effective_message
|
|
363
|
+
|
|
364
|
+
if message is None:
|
|
365
|
+
return
|
|
366
|
+
|
|
311
367
|
buttons = [
|
|
312
368
|
[
|
|
313
369
|
InlineKeyboardButton(
|
|
@@ -318,7 +374,6 @@ class TelegramBot(BaseBot):
|
|
|
318
374
|
for torrent in get_registered_torrents().values()
|
|
319
375
|
]
|
|
320
376
|
|
|
321
|
-
message = update.callback_query.message
|
|
322
377
|
if not buttons:
|
|
323
378
|
message.reply_text('No torrents yet.')
|
|
324
379
|
return
|