Unit3Dup 0.11.10__tar.gz → 0.12.0__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.
Files changed (107) hide show
  1. {unit3dup-0.11.10 → unit3dup-0.12.0}/PKG-INFO +2 -2
  2. {unit3dup-0.11.10 → unit3dup-0.12.0}/Unit3Dup.egg-info/PKG-INFO +2 -2
  3. {unit3dup-0.11.10 → unit3dup-0.12.0}/Unit3Dup.egg-info/requires.txt +1 -1
  4. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/command.py +5 -2
  5. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/settings.py +7 -3
  6. unit3dup-0.12.0/common/torrent_clients.py +281 -0
  7. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/signs_list.py +24 -8
  8. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/tags_list.py +1 -0
  9. {unit3dup-0.11.10 → unit3dup-0.12.0}/pyproject.toml +2 -2
  10. {unit3dup-0.11.10 → unit3dup-0.12.0}/requirements.txt +1 -1
  11. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/__main__.py +1 -4
  12. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/TorrentManager.py +0 -3
  13. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/common.py +17 -23
  14. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/pvtTracker.py +1 -0
  15. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/upload.py +4 -1
  16. unit3dup-0.11.10/common/torrent_clients.py +0 -275
  17. {unit3dup-0.11.10 → unit3dup-0.12.0}/LICENSE +0 -0
  18. {unit3dup-0.11.10 → unit3dup-0.12.0}/README.rst +0 -0
  19. {unit3dup-0.11.10 → unit3dup-0.12.0}/Unit3Dup.egg-info/SOURCES.txt +0 -0
  20. {unit3dup-0.11.10 → unit3dup-0.12.0}/Unit3Dup.egg-info/dependency_links.txt +0 -0
  21. {unit3dup-0.11.10 → unit3dup-0.12.0}/Unit3Dup.egg-info/entry_points.txt +0 -0
  22. {unit3dup-0.11.10 → unit3dup-0.12.0}/Unit3Dup.egg-info/top_level.txt +0 -0
  23. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/__init__.py +0 -0
  24. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/bdinfo_string.py +0 -0
  25. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/bittorrent.py +0 -0
  26. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/constants.py +0 -0
  27. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/database.py +0 -0
  28. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/__init__.py +0 -0
  29. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/__init__.py +0 -0
  30. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/client.py +0 -0
  31. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/core/__init__.py +0 -0
  32. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/core/ftpx_service.py +0 -0
  33. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/core/ftpx_session.py +0 -0
  34. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/core/menu.py +0 -0
  35. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/core/models/__init__.py +0 -0
  36. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/ftpx/core/models/list.py +0 -0
  37. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/__init__.py +0 -0
  38. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/client.py +0 -0
  39. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/core/__init__.py +0 -0
  40. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/core/api.py +0 -0
  41. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/core/models/__init__.py +0 -0
  42. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/core/models/search.py +0 -0
  43. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/core/platformid.py +0 -0
  44. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/igdb/core/tags.py +0 -0
  45. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/imageHost.py +0 -0
  46. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/mediaresult.py +0 -0
  47. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/sessions/__init__.py +0 -0
  48. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/sessions/agents.py +0 -0
  49. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/sessions/exceptions.py +0 -0
  50. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/sessions/session.py +0 -0
  51. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/__init__.py +0 -0
  52. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/__init__.py +0 -0
  53. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/api.py +0 -0
  54. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/keywords.py +0 -0
  55. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/__init__.py +0 -0
  56. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/movie/__init__.py +0 -0
  57. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/movie/alternative_titles.py +0 -0
  58. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/movie/details.py +0 -0
  59. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/movie/movie.py +0 -0
  60. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/movie/nowplaying.py +0 -0
  61. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/movie/release_info.py +0 -0
  62. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/tvshow/__init__.py +0 -0
  63. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/tvshow/alternative.py +0 -0
  64. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/tvshow/details.py +0 -0
  65. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/tvshow/on_the_air.py +0 -0
  66. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/tvshow/translations.py +0 -0
  67. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/models/tvshow/tvshow.py +0 -0
  68. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/theMovieDB/core/videos.py +0 -0
  69. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/trailers/__init__.py +0 -0
  70. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/trailers/api.py +0 -0
  71. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/trailers/response.py +0 -0
  72. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/external_services/tvdb.py +0 -0
  73. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/extractor.py +0 -0
  74. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/frames.py +0 -0
  75. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/mediainfo.py +0 -0
  76. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/mediainfo_string.py +0 -0
  77. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/tags.py +0 -0
  78. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/title.py +0 -0
  79. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/__init__.py +0 -0
  80. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/ast.py +0 -0
  81. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/ban_list.py +0 -0
  82. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/data.py +0 -0
  83. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/itt.py +0 -0
  84. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/ptt.py +0 -0
  85. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/sis.py +0 -0
  86. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/trackers/trackers.py +0 -0
  87. {unit3dup-0.11.10 → unit3dup-0.12.0}/common/utility.py +0 -0
  88. {unit3dup-0.11.10 → unit3dup-0.12.0}/setup.cfg +0 -0
  89. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/__init__.py +0 -0
  90. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/automode.py +0 -0
  91. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/bot.py +0 -0
  92. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/duplicate.py +0 -0
  93. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/exceptions.py +0 -0
  94. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media.py +0 -0
  95. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/ContentManager.py +0 -0
  96. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/DocuManager.py +0 -0
  97. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/GameManager.py +0 -0
  98. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/MediaInfoManager.py +0 -0
  99. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/SeedManager.py +0 -0
  100. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/VideoManager.py +0 -0
  101. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/media_manager/__init__.py +0 -0
  102. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/pvtDocu.py +0 -0
  103. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/pvtTorrent.py +0 -0
  104. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/pvtVideo.py +0 -0
  105. {unit3dup-0.11.10 → unit3dup-0.12.0}/unit3dup/torrent.py +0 -0
  106. {unit3dup-0.11.10 → unit3dup-0.12.0}/view/__init__.py +0 -0
  107. {unit3dup-0.11.10 → unit3dup-0.12.0}/view/custom_console.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Unit3Dup
3
- Version: 0.11.10
3
+ Version: 0.12.0
4
4
  Summary: An uploader for the Unit3D torrent tracker
5
5
  Author: Parzival
6
6
  License-Expression: MIT
@@ -10,7 +10,7 @@ Description-Content-Type: text/x-rst
10
10
  License-File: LICENSE
11
11
  Requires-Dist: guessit==3.8.0
12
12
  Requires-Dist: pymediainfo==6.1.0
13
- Requires-Dist: python-qbittorrent==0.4.3
13
+ Requires-Dist: qbittorrent-api==2026.6.0
14
14
  Requires-Dist: pydantic==2.10.6
15
15
  Requires-Dist: requests==2.32.3
16
16
  Requires-Dist: rich==13.7.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Unit3Dup
3
- Version: 0.11.10
3
+ Version: 0.12.0
4
4
  Summary: An uploader for the Unit3D torrent tracker
5
5
  Author: Parzival
6
6
  License-Expression: MIT
@@ -10,7 +10,7 @@ Description-Content-Type: text/x-rst
10
10
  License-File: LICENSE
11
11
  Requires-Dist: guessit==3.8.0
12
12
  Requires-Dist: pymediainfo==6.1.0
13
- Requires-Dist: python-qbittorrent==0.4.3
13
+ Requires-Dist: qbittorrent-api==2026.6.0
14
14
  Requires-Dist: pydantic==2.10.6
15
15
  Requires-Dist: requests==2.32.3
16
16
  Requires-Dist: rich==13.7.1
@@ -1,6 +1,6 @@
1
1
  guessit==3.8.0
2
2
  pymediainfo==6.1.0
3
- python-qbittorrent==0.4.3
3
+ qbittorrent-api==2026.6.0
4
4
  pydantic==2.10.6
5
5
  requests==2.32.3
6
6
  rich==13.7.1
@@ -88,6 +88,9 @@ class CommandLine:
88
88
  search_group.add_argument("-d", "--description", type=str, help="By description")
89
89
  search_group.add_argument("-bd", "--bdinfo", type=str, help="Show BDInfo")
90
90
  search_group.add_argument("-m", "--mediainfo", type=str, help="Show MediaInfo")
91
+ search_group.add_argument("-int", "--internal", action="store_true", help="Internal Release")
92
+ search_group.add_argument("-mod", "--moderation", action="store_true", help="In moderation")
93
+
91
94
 
92
95
  # /////////////////////////
93
96
  # Filter Options
@@ -123,7 +126,7 @@ class CommandLine:
123
126
  special_group.add_argument("-str", "--stream", action="store_true", help="Stream")
124
127
  special_group.add_argument("-sd", "--standard", action="store_true", help="SD")
125
128
  special_group.add_argument("-hs", "--highspeed", action="store_true", help="Highspeed")
126
- special_group.add_argument("-int", "--internal", action="store_true", help="Internal")
129
+ special_group.add_argument("-inter", "--intern_r", action="store_true", help="Internal Release")
127
130
  special_group.add_argument("-pr", "--prelease", action="store_true", help="Personal")
128
131
 
129
132
  # /////////////////////////
@@ -159,4 +162,4 @@ class CommandLine:
159
162
  if self.args.force.lower() not in valid_categories:
160
163
  self.args.force = None
161
164
  print("Invalid -force category")
162
- exit()
165
+ exit()
@@ -24,7 +24,7 @@ user_tags_file = "tags_list.json"
24
24
  user_sign_file = "sign_list.json"
25
25
  bane_file = "ban_list.json"
26
26
 
27
- version = "0.11.10"
27
+ version = "0.12.0"
28
28
 
29
29
  if os.name == "nt":
30
30
  WATCHER_DESTINATION_PATH: Path = Path(
@@ -112,6 +112,8 @@ class TorrentClientConfig(BaseModel):
112
112
  SHARED_RTORR_PATH: str | None = None
113
113
  TORRENT_CLIENT: str | None = None
114
114
  TAG: str | None = None
115
+ CATEGORY_MOVIE: str | None = None
116
+ CATEGORY_TV: str | None = None
115
117
 
116
118
 
117
119
  class UserPreferences(BaseModel):
@@ -452,7 +454,7 @@ class Config(BaseModel):
452
454
  section[field] = Validate.integer(value=section[field], field_name=field)
453
455
 
454
456
  if field in ['QBIT_PASS', 'TRASM_PASS', 'RTORR_PASS', 'QBIT_USER', 'TRASM_USER', 'RTORR_USER',
455
- 'TORRENT_CLIENT', 'TAG', 'RTORR_HOST']:
457
+ 'TORRENT_CLIENT', 'TAG', 'CATEGORY_MOVIE','CATEGORY_TV', 'RTORR_HOST']:
456
458
  section[field] = Validate.string(value=section[field], field_name=field)
457
459
 
458
460
  if field in ['SHARED_TRASM_PATH', 'SHARED_QBIT_PATH', 'SHARED_RTORR_PATH']:
@@ -622,7 +624,9 @@ class Load:
622
624
  "RTORR_PORT": "9091",
623
625
  "SHARED_RTORR_PATH": "no_path",
624
626
  "TORRENT_CLIENT": "qbittorrent",
625
- "TAG": "ADDED TORRENTS"
627
+ "TAG": "ADDED TORRENTS",
628
+ "CATEGORY_MOVIE": "movie",
629
+ "CATEGORY_TV": "tv"
626
630
  },
627
631
  "user_preferences": {
628
632
  "PTSCREENS_PRIORITY": 0,
@@ -0,0 +1,281 @@
1
+ # -*- coding: utf-8 -*-
2
+ import hashlib
3
+ import os
4
+ import stat
5
+ import time
6
+ import bencode2
7
+ import requests
8
+ from requests import Response
9
+ from rtorrent_rpc import RTorrent
10
+
11
+ from abc import ABC, abstractmethod
12
+
13
+ import qbittorrentapi
14
+ import transmission_rpc
15
+
16
+ from unit3dup.pvtTorrent import Mytorrent
17
+ from unit3dup import config_settings
18
+ from unit3dup.media import Media
19
+
20
+ from view import custom_console
21
+
22
+
23
+ class TorrClient(ABC):
24
+
25
+ def __init__(self):
26
+ self.client = None
27
+
28
+ @abstractmethod
29
+ def connect(self):
30
+ pass
31
+
32
+ @abstractmethod
33
+ def send_to_client(self, tracker_data_response: str, torrent: Mytorrent, content: Media, archive_path: str):
34
+ pass
35
+
36
+ @staticmethod
37
+ def download(tracker_torrent_url: Response, full_path_archive: str):
38
+ # File archived
39
+ with open(full_path_archive, "wb") as file:
40
+ file.write(tracker_torrent_url.content)
41
+
42
+ # Ready for seeding
43
+ return open(full_path_archive, "rb")
44
+
45
+
46
+ class TransmissionClient(TorrClient):
47
+ def __init__(self) -> None:
48
+ super().__init__()
49
+
50
+ def connect(self) -> transmission_rpc.Client | None:
51
+ try:
52
+ self.client = transmission_rpc.Client(host=config_settings.torrent_client_config.TRASM_HOST,
53
+ port=config_settings.torrent_client_config.TRASM_PORT,
54
+ username=config_settings.torrent_client_config.TRASM_USER,
55
+ password=config_settings.torrent_client_config.TRASM_PASS,
56
+ timeout=10)
57
+ return self.client
58
+ except requests.exceptions.HTTPError:
59
+ custom_console.bot_error_log(
60
+ f"{self.__class__.__name__} HTTP Error. Check IP/port or run Transmission"
61
+ )
62
+ except requests.exceptions.ConnectionError:
63
+ custom_console.bot_error_log(
64
+ f"{self.__class__.__name__} Connection Error. Check IP/port or run Transmission"
65
+ )
66
+ except transmission_rpc.TransmissionError:
67
+ custom_console.bot_error_log(
68
+ f"{self.__class__.__name__} Login required. Check your username and password"
69
+ )
70
+ except Exception as e:
71
+ custom_console.bot_error_log(f"{self.__class__.__name__} Unexpected error: {e}")
72
+ custom_console.bot_error_log(f"{self.__class__.__name__} Please verify your configuration")
73
+
74
+ def send_to_client(self, tracker_data_response: str, torrent: Mytorrent, content: Media, archive_path: str):
75
+ # "Translate" files location to shared_path if necessary
76
+ if config_settings.torrent_client_config.SHARED_QBIT_PATH:
77
+ torr_location = config_settings.torrent_client_config.SHARED_QBIT_PATH
78
+ else:
79
+ # If no shared_path is specified set it to the path specified in the CLI commands (path)
80
+ torr_location = os.path.dirname(content.torrent_path)
81
+
82
+ # Send to the client
83
+ with open(archive_path, "rb") as file_buffer:
84
+ self.client.add_torrent(torrent=file_buffer, download_dir=str(torr_location))
85
+
86
+ def send_file_to_client(self, torrent_path: str):
87
+ self.client.add_torrent(torrent=open(torrent_path, "rb"), download_dir=str(os.path.dirname(torrent_path)))
88
+
89
+
90
+ class QbittorrentClient(TorrClient):
91
+
92
+ def __init__(self):
93
+ super().__init__()
94
+
95
+ def connect(self) -> qbittorrentapi.Client | None:
96
+ try:
97
+ self.client = qbittorrentapi.Client(
98
+ host=config_settings.torrent_client_config.QBIT_HOST,
99
+ port=config_settings.torrent_client_config.QBIT_PORT,
100
+ username=config_settings.torrent_client_config.QBIT_USER,
101
+ password=config_settings.torrent_client_config.QBIT_PASS,
102
+ REQUESTS_ARGS={"timeout": 10},
103
+ )
104
+
105
+ login_count = 0
106
+
107
+ while True:
108
+ try:
109
+ self.client.auth_log_in()
110
+ break
111
+
112
+ except qbittorrentapi.LoginFailed:
113
+
114
+ if login_count > 5:
115
+ custom_console.bot_error_log("Failed to login.")
116
+ exit(1)
117
+
118
+ custom_console.bot_warning_log(
119
+ "Qbittorrent failed to login. Retry...Please wait"
120
+ )
121
+
122
+ time.sleep(2)
123
+ login_count += 1
124
+
125
+ return self.client
126
+
127
+ except requests.exceptions.HTTPError:
128
+ custom_console.bot_error_log(
129
+ f"{self.__class__.__name__} HTTP Error. "
130
+ f"Check IP/port or run qBittorrent"
131
+ )
132
+
133
+ except requests.exceptions.ConnectionError:
134
+ custom_console.bot_error_log(
135
+ f"{self.__class__.__name__} Connection Error. "
136
+ f"Check IP/port or run qBittorrent"
137
+ )
138
+
139
+ except qbittorrentapi.LoginFailed:
140
+ custom_console.bot_error_log(
141
+ f"{self.__class__.__name__} Login required. "
142
+ f"Check your username and password"
143
+ )
144
+
145
+ except Exception as e:
146
+ custom_console.bot_error_log(
147
+ f"{self.__class__.__name__} Unexpected error: {e}"
148
+ )
149
+ custom_console.bot_error_log(
150
+ f"{self.__class__.__name__} Please verify your configuration"
151
+ )
152
+
153
+ def _add_torrent_and_tag(self, torrent_bytes: bytes, save_path: str, info_hash: str,category: str) -> None:
154
+
155
+ self.client.torrents_add(torrent_files=torrent_bytes,save_path=save_path)
156
+
157
+ # wait for torrent to be registered
158
+ for _ in range(20):
159
+ try:
160
+ torrents = self.client.torrents_info(torrent_hashes=info_hash)
161
+ if torrents:
162
+ break
163
+ except Exception:
164
+ pass
165
+ time.sleep(0.25)
166
+
167
+ if category=='movie':
168
+ category = config_settings.torrent_client_config.CATEGORY_MOVIE
169
+ if category=='tv':
170
+ category = config_settings.torrent_client_config.CATEGORY_MOVIE
171
+ self.client.torrents_add_tags(tags=config_settings.torrent_client_config.TAG, torrent_hashes=info_hash)
172
+ self.client.torrents_set_category(category=category, torrent_hashes=info_hash)
173
+
174
+
175
+ def send_to_client(self, tracker_data_response: str, torrent: Mytorrent, content: Media,archive_path: str):
176
+
177
+ if config_settings.torrent_client_config.SHARED_QBIT_PATH:
178
+ torr_location = (
179
+ config_settings.torrent_client_config.SHARED_QBIT_PATH
180
+ )
181
+ else:
182
+ torr_location = os.path.dirname(content.torrent_path)
183
+
184
+ if not torrent:
185
+ with open(archive_path, "rb") as file_buffer:
186
+ torrent_data = file_buffer.read()
187
+
188
+ info = bencode2.bdecode(torrent_data)[b"info"]
189
+ info_hash = hashlib.sha1(bencode2.bencode(info)).hexdigest()
190
+ file_buffer.seek(0)
191
+ self._add_torrent_and_tag(torrent_bytes=file_buffer.read(),save_path=str(torr_location),
192
+ info_hash=info_hash, category = content.category)
193
+ else:
194
+ info = torrent.mytorr.metainfo["info"]
195
+ info_hash = hashlib.sha1(
196
+ bencode2.bencode(info)
197
+ ).hexdigest()
198
+ with open(archive_path, "rb") as file_buffer:
199
+ self._add_torrent_and_tag(torrent_bytes=file_buffer.read(),save_path=str(torr_location),
200
+ info_hash=info_hash, category = content.category)
201
+
202
+
203
+ class RTorrentClient(TorrClient):
204
+ def __init__(self):
205
+ super().__init__()
206
+
207
+ def connect(self) -> RTorrent | None:
208
+
209
+ # Build the socket string for rTorrent
210
+ # Tcp or File
211
+ if os.path.exists(config_settings.torrent_client_config.RTORR_HOST):
212
+ socket_type = os.stat(config_settings.torrent_client_config.RTORR_HOST).st_mode
213
+ if stat.S_ISSOCK(socket_type):
214
+ socket = f"scgi:///{config_settings.torrent_client_config.RTORR_HOST}"
215
+ else:
216
+ custom_console.bot_error_log("Invalid RTorrent host")
217
+ exit(1)
218
+ else:
219
+ socket = (f"scgi://{config_settings.torrent_client_config.RTORR_HOST}:"
220
+ f"{config_settings.torrent_client_config.RTORR_PORT}")
221
+
222
+ login_count = 0
223
+ while True:
224
+ try:
225
+ # open
226
+ self.client = RTorrent(address=socket, timeout=10)
227
+ # Test
228
+ self.client.system_list_methods()
229
+ return self.client
230
+ except requests.exceptions.HTTPError:
231
+ custom_console.bot_warning_log("Rtorrent failed to login. Retry...Please wait")
232
+ time.sleep(2)
233
+ login_count += 1
234
+ if login_count > 5:
235
+ custom_console.bot_error_log("Rtorrent failed to login.")
236
+ exit()
237
+ except requests.exceptions.ConnectionError:
238
+ custom_console.bot_error_log(
239
+ f"{self.__class__.__name__} Connection Error. Check IP/port or run rTorrent"
240
+ )
241
+ exit()
242
+ except TimeoutError:
243
+ custom_console.bot_error_log(
244
+ f"{self.__class__.__name__} Connection Error. Check IP/port or run rTorrent"
245
+ )
246
+ exit()
247
+ except AttributeError:
248
+ custom_console.bot_error_log(
249
+ f"{self.__class__.__name__} Socket connection error or wrong OS platform"
250
+ )
251
+ exit()
252
+ except ConnectionRefusedError:
253
+ custom_console.bot_error_log(
254
+ f"{self.__class__.__name__} Connection refused"
255
+ )
256
+ exit()
257
+
258
+ def send_to_client(self, tracker_data_response: str, torrent: Mytorrent, content: Media, archive_path: str):
259
+ # "Translate" files location to shared_path if necessary
260
+ if config_settings.torrent_client_config.SHARED_RTORR_PATH:
261
+ torr_location = config_settings.torrent_client_config.SHARED_RTORR_PATH
262
+ else:
263
+ # If no shared_path is specified set it to the path specified in the CLI commands (path)
264
+ torr_location = os.path.dirname(content.torrent_path)
265
+
266
+ # Add the torrent folder needed for rTorrent
267
+ if os.path.isdir(content.subfolder):
268
+ torr_location = os.path.join(torr_location, content.torrent_name)
269
+ # Save path for Windows or Linux.The root directory (/mnt or c:\) is the responsibility of the user
270
+ # in shared_folder
271
+ torr_location = torr_location.replace('\\', '/')
272
+
273
+ # Read and send
274
+ with open(archive_path, "rb") as file:
275
+ self.client.add_torrent_by_file(content=file.read(), directory_base=str(torr_location),
276
+ tags=[config_settings.torrent_client_config.TAG])
277
+
278
+ def send_file_to_client(self, torrent_path: str, media_location: str):
279
+ with open(torrent_path, "rb") as file:
280
+ self.client.add_torrent_by_file(content=file.read(), directory_base=str(media_location),
281
+ tags=[config_settings.torrent_client_config.TAG])
@@ -21,6 +21,7 @@ SIGNS_LIST = {
21
21
  "BITSHIFT": "releaser",
22
22
  "BLACKBIT": "releaser",
23
23
  "BL4CK-B4RT-MIRCREW": "releaser",
24
+ "BLOOM": "releaser",
24
25
  "BLUWORLD": "releaser",
25
26
  "BOBDOBBS": "releaser",
26
27
  "BONE": "releaser",
@@ -34,6 +35,9 @@ SIGNS_LIST = {
34
35
  "CHD": "releaser",
35
36
  "CHDWEB": "releaser",
36
37
  "CIAME": "releaser",
38
+ "CINEPHILES": "releaser",
39
+ "CINEPTH": "releaser",
40
+ "CINEWEB": "releaser",
37
41
  "CLOCLONE": "releaser",
38
42
  "CODEX": "releaser",
39
43
  "COMIX21": "releaser",
@@ -57,6 +61,7 @@ SIGNS_LIST = {
57
61
  "DOCH74": "releaser",
58
62
  "DR4GON": "releaser",
59
63
  "DRIIP": "releaser",
64
+ "DRPLEB": "releaser",
60
65
  "DST": "releaser",
61
66
  "DYNUX": "releaser",
62
67
  "EA": "releaser",
@@ -89,6 +94,7 @@ SIGNS_LIST = {
89
94
  "GFM": "releaser",
90
95
  "GIUSEPPETNT": "releaser",
91
96
  "GOLDENTORRENT": "releaser",
97
+ "GRAPPA": "releaser",
92
98
  "GRYM": "releaser",
93
99
  "HAPPITEAM-YNK": "releaser",
94
100
  "HASHDEV": "releaser",
@@ -107,6 +113,7 @@ SIGNS_LIST = {
107
113
  "IDN-CREW": "releaser",
108
114
  "IGS": "releaser",
109
115
  "ILGANZO": "releaser",
116
+ "ILENIA": "releaser",
110
117
  "ILPADRINO": "releaser",
111
118
  "ILSAGGIO": "releaser",
112
119
  "ILSOMMO": "releaser",
@@ -129,6 +136,7 @@ SIGNS_LIST = {
129
136
  "KUCHU": "releaser",
130
137
  "LAZY-SUBS": "releaser",
131
138
  "LATENEVER": "releaser",
139
+ "LAXII": "releaser",
132
140
  "LDI": "releaser",
133
141
  "LEAGUENF": "releaser",
134
142
  "LELE753": "releaser",
@@ -138,20 +146,20 @@ SIGNS_LIST = {
138
146
  "LOZIO-MIRCREW": "releaser",
139
147
  "LULLOZZO": "releaser",
140
148
  "LZ": "releaser",
141
- "LAXII": "releaser",
142
149
  "LZ59": "releaser",
143
150
  "MADHEX": "releaser",
144
151
  "MADSKY": "releaser",
145
152
  "MADTIA": "releaser",
146
153
  "MAX": "releaser",
147
- "MEM.GP" : "releaser",
148
- "MEMGP": "releaser",
149
- "MEM": "releaser",
150
154
  "MAX2014": "releaser",
151
155
  "ME7ALH": "releaser",
156
+ "MEM": "releaser",
157
+ "MEM.GP": "releaser",
158
+ "MEMGP": "releaser",
152
159
  "MIK0YAN": "releaser",
153
160
  "MIRCREW": "releaser",
154
161
  "MISTERNED": "releaser",
162
+ "MOORKAT": "releaser",
155
163
  "MORPHEUS": "releaser",
156
164
  "MRBLONDE": "releaser",
157
165
  "MRROBOT": "releaser",
@@ -166,9 +174,11 @@ SIGNS_LIST = {
166
174
  "NOMADS": "releaser",
167
175
  "NOVARIP": "releaser",
168
176
  "NTB": "releaser",
177
+ "NTG": "releaser",
169
178
  "NTROPIC": "releaser",
170
179
  "ODS": "releaser",
171
180
  "ODINO": "releaser",
181
+ "OFT": "releaser",
172
182
  "ONGOIA": "releaser",
173
183
  "PASO77": "releaser",
174
184
  "PEPPE": "releaser",
@@ -178,6 +188,7 @@ SIGNS_LIST = {
178
188
  "PING": "releaser",
179
189
  "PINKER": "releaser",
180
190
  "PIR8": "releaser",
191
+ "PLAYTV": "releaser",
181
192
  "PLAYWEB": "releaser",
182
193
  "PLUSAM": "releaser",
183
194
  "POKE": "releaser",
@@ -187,10 +198,12 @@ SIGNS_LIST = {
187
198
  "RADESE": "releaser",
188
199
  "RAPTA": "releaser",
189
200
  "RARBG": "releaser",
201
+ "RAMSEIS": "releaser",
190
202
  "RAWR": "releaser",
191
203
  "REALDMDJ": "releaser",
192
204
  "RIDDICKK": "releaser",
193
205
  "RMTEAM": "releaser",
206
+ "R00T": "releaser",
194
207
  "ROMEO": "releaser",
195
208
  "RUNE": "releaser",
196
209
  "RYUKXX": "releaser",
@@ -207,6 +220,7 @@ SIGNS_LIST = {
207
220
  "SK4TTO": "releaser",
208
221
  "SMURF": "releaser",
209
222
  "SMURFADMIN": "releaser",
223
+ "SM737": "releaser",
210
224
  "SP33DY94-MIRCREW": "releaser",
211
225
  "SPWEB": "releaser",
212
226
  "SPYRO": "releaser",
@@ -215,7 +229,12 @@ SIGNS_LIST = {
215
229
  "STEVEJOHNNEE": "releaser",
216
230
  "STINGUAJT": "releaser",
217
231
  "TAHTAKALECI": "releaser",
232
+ "TAREK": "releaser",
218
233
  "TASKO": "releaser",
234
+ "TBR": "releaser",
235
+ "TBZ": "releaser",
236
+ "T4H": "releaser",
237
+ "T4P3": "releaser",
219
238
  "TELESTO": "releaser",
220
239
  "TEPES": "releaser",
221
240
  "TERMINAL": "releaser",
@@ -224,11 +243,8 @@ SIGNS_LIST = {
224
243
  "THUDARA": "releaser",
225
244
  "TIGER": "releaser",
226
245
  "TIGRE67": "releaser",
227
- "TBR": "releaser",
228
- "TBZ": "releaser",
229
- "T4P3": "releaser",
230
- "TORRENTER10": "releaser",
231
246
  "TOTAL_SHARE88": "releaser",
247
+ "TORRENTER10": "releaser",
232
248
  "TREE7458": "releaser",
233
249
  "TRITON": "releaser",
234
250
  "TRL": "releaser",
@@ -29,6 +29,7 @@ TAGS_LIST = {
29
29
  "MD": "source",
30
30
  "UHDRIP": "source",
31
31
  "BLURAY": "source",
32
+ "BLU-RAY": "source",
32
33
  "BRRIP": "source",
33
34
  "BDRIP": "source",
34
35
  "FHDRIP": "source",
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  dynamic = ["dependencies"]
7
7
  name = "Unit3Dup"
8
- version = "0.11.10"
8
+ version = "0.12.0"
9
9
  description = "An uploader for the Unit3D torrent tracker"
10
10
  readme = "README.rst"
11
11
  requires-python = ">=3.10"
@@ -13,8 +13,8 @@ license = "MIT"
13
13
 
14
14
  authors = [
15
15
  { name = "Parzival" }
16
+
16
17
  ]
17
-
18
18
  [project.urls]
19
19
  Homepage = "https://github.com/31December99/Unit3Dup"
20
20
 
@@ -1,6 +1,6 @@
1
1
  guessit==3.8.0
2
2
  pymediainfo==6.1.0
3
- python-qbittorrent==0.4.3
3
+ qbittorrent-api==2026.6.0
4
4
  pydantic==2.10.6
5
5
  requests==2.32.3
6
6
  rich==13.7.1
@@ -1,8 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  import json
3
3
 
4
- import requests
5
-
6
4
  from common.torrent_clients import TransmissionClient, QbittorrentClient, RTorrentClient
7
5
  from common.command import CommandLine
8
6
  from common.settings import Load, DEFAULT_JSON_PATH, USER_TAGS_PATH, USER_SIGN_PATH, BAN_TAGS_PATH, version
@@ -58,7 +56,6 @@ def main():
58
56
  custom_console.bot_log(f"Tracker -> '{cli.args.tracker.upper()}' Online")
59
57
  tracker_name_list = [cli.args.tracker.upper()]
60
58
 
61
-
62
59
  # Send content to the multi_tracker list
63
60
  if cli.args.mt:
64
61
  tracker_name_list = config.tracker_config.MULTI_TRACKER
@@ -294,7 +291,7 @@ def main():
294
291
  torrent_info.view_highspeed()
295
292
  return
296
293
 
297
- if cli.args.internal:
294
+ if cli.args.intern_r:
298
295
  torrent_info.view_internal()
299
296
  return
300
297
 
@@ -124,9 +124,6 @@ class TorrentManager:
124
124
  custom_console.bot_log(f"Tracker '{selected_tracker}' Done.")
125
125
  custom_console.rule()
126
126
 
127
- custom_console.bot_log(f"Done.")
128
- custom_console.rule()
129
-
130
127
  def reseed(self, trackers_name_list: list) -> None:
131
128
  """
132
129