Unit3Dup 0.11.8__tar.gz → 0.11.12__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 (109) hide show
  1. {unit3dup-0.11.8 → unit3dup-0.11.12}/PKG-INFO +2 -2
  2. {unit3dup-0.11.8 → unit3dup-0.11.12}/Unit3Dup.egg-info/PKG-INFO +2 -2
  3. {unit3dup-0.11.8 → unit3dup-0.11.12}/Unit3Dup.egg-info/requires.txt +1 -1
  4. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/command.py +4 -2
  5. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/settings.py +7 -3
  6. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/tags.py +26 -18
  7. unit3dup-0.11.12/common/torrent_clients.py +281 -0
  8. unit3dup-0.11.12/common/trackers/ban_list.py +5 -0
  9. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/signs_list.py +24 -8
  10. unit3dup-0.11.12/common/trackers/tags_list.py +116 -0
  11. {unit3dup-0.11.8 → unit3dup-0.11.12}/pyproject.toml +1 -1
  12. {unit3dup-0.11.8 → unit3dup-0.11.12}/requirements.txt +1 -1
  13. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/__main__.py +1 -4
  14. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/common.py +17 -23
  15. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/upload.py +2 -1
  16. unit3dup-0.11.8/common/torrent_clients.py +0 -275
  17. unit3dup-0.11.8/common/trackers/ban_list.py +0 -6
  18. unit3dup-0.11.8/common/trackers/tags_list.py +0 -115
  19. {unit3dup-0.11.8 → unit3dup-0.11.12}/LICENSE +0 -0
  20. {unit3dup-0.11.8 → unit3dup-0.11.12}/README.rst +0 -0
  21. {unit3dup-0.11.8 → unit3dup-0.11.12}/Unit3Dup.egg-info/SOURCES.txt +0 -0
  22. {unit3dup-0.11.8 → unit3dup-0.11.12}/Unit3Dup.egg-info/dependency_links.txt +0 -0
  23. {unit3dup-0.11.8 → unit3dup-0.11.12}/Unit3Dup.egg-info/entry_points.txt +0 -0
  24. {unit3dup-0.11.8 → unit3dup-0.11.12}/Unit3Dup.egg-info/top_level.txt +0 -0
  25. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/__init__.py +0 -0
  26. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/bdinfo_string.py +0 -0
  27. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/bittorrent.py +0 -0
  28. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/constants.py +0 -0
  29. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/database.py +0 -0
  30. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/__init__.py +0 -0
  31. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/__init__.py +0 -0
  32. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/client.py +0 -0
  33. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/core/__init__.py +0 -0
  34. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/core/ftpx_service.py +0 -0
  35. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/core/ftpx_session.py +0 -0
  36. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/core/menu.py +0 -0
  37. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/core/models/__init__.py +0 -0
  38. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/ftpx/core/models/list.py +0 -0
  39. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/__init__.py +0 -0
  40. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/client.py +0 -0
  41. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/core/__init__.py +0 -0
  42. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/core/api.py +0 -0
  43. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/core/models/__init__.py +0 -0
  44. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/core/models/search.py +0 -0
  45. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/core/platformid.py +0 -0
  46. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/igdb/core/tags.py +0 -0
  47. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/imageHost.py +0 -0
  48. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/mediaresult.py +0 -0
  49. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/sessions/__init__.py +0 -0
  50. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/sessions/agents.py +0 -0
  51. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/sessions/exceptions.py +0 -0
  52. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/sessions/session.py +0 -0
  53. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/__init__.py +0 -0
  54. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/__init__.py +0 -0
  55. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/api.py +0 -0
  56. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/keywords.py +0 -0
  57. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/__init__.py +0 -0
  58. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/movie/__init__.py +0 -0
  59. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/movie/alternative_titles.py +0 -0
  60. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/movie/details.py +0 -0
  61. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/movie/movie.py +0 -0
  62. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/movie/nowplaying.py +0 -0
  63. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/movie/release_info.py +0 -0
  64. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/tvshow/__init__.py +0 -0
  65. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/tvshow/alternative.py +0 -0
  66. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/tvshow/details.py +0 -0
  67. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/tvshow/on_the_air.py +0 -0
  68. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/tvshow/translations.py +0 -0
  69. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/models/tvshow/tvshow.py +0 -0
  70. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/theMovieDB/core/videos.py +0 -0
  71. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/trailers/__init__.py +0 -0
  72. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/trailers/api.py +0 -0
  73. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/trailers/response.py +0 -0
  74. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/external_services/tvdb.py +0 -0
  75. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/extractor.py +0 -0
  76. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/frames.py +0 -0
  77. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/mediainfo.py +0 -0
  78. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/mediainfo_string.py +0 -0
  79. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/title.py +0 -0
  80. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/__init__.py +0 -0
  81. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/ast.py +0 -0
  82. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/data.py +0 -0
  83. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/itt.py +0 -0
  84. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/ptt.py +0 -0
  85. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/sis.py +0 -0
  86. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/trackers/trackers.py +0 -0
  87. {unit3dup-0.11.8 → unit3dup-0.11.12}/common/utility.py +0 -0
  88. {unit3dup-0.11.8 → unit3dup-0.11.12}/setup.cfg +0 -0
  89. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/__init__.py +0 -0
  90. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/automode.py +0 -0
  91. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/bot.py +0 -0
  92. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/duplicate.py +0 -0
  93. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/exceptions.py +0 -0
  94. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media.py +0 -0
  95. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/ContentManager.py +0 -0
  96. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/DocuManager.py +0 -0
  97. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/GameManager.py +0 -0
  98. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/MediaInfoManager.py +0 -0
  99. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/SeedManager.py +0 -0
  100. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/TorrentManager.py +0 -0
  101. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/VideoManager.py +0 -0
  102. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/media_manager/__init__.py +0 -0
  103. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/pvtDocu.py +0 -0
  104. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/pvtTorrent.py +0 -0
  105. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/pvtTracker.py +0 -0
  106. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/pvtVideo.py +0 -0
  107. {unit3dup-0.11.8 → unit3dup-0.11.12}/unit3dup/torrent.py +0 -0
  108. {unit3dup-0.11.8 → unit3dup-0.11.12}/view/__init__.py +0 -0
  109. {unit3dup-0.11.8 → unit3dup-0.11.12}/view/custom_console.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Unit3Dup
3
- Version: 0.11.8
3
+ Version: 0.11.12
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.8
3
+ Version: 0.11.12
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,8 @@ 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
+
91
93
 
92
94
  # /////////////////////////
93
95
  # Filter Options
@@ -123,7 +125,7 @@ class CommandLine:
123
125
  special_group.add_argument("-str", "--stream", action="store_true", help="Stream")
124
126
  special_group.add_argument("-sd", "--standard", action="store_true", help="SD")
125
127
  special_group.add_argument("-hs", "--highspeed", action="store_true", help="Highspeed")
126
- special_group.add_argument("-int", "--internal", action="store_true", help="Internal")
128
+ special_group.add_argument("-inter", "--intern_r", action="store_true", help="Internal Release")
127
129
  special_group.add_argument("-pr", "--prelease", action="store_true", help="Personal")
128
130
 
129
131
  # /////////////////////////
@@ -159,4 +161,4 @@ class CommandLine:
159
161
  if self.args.force.lower() not in valid_categories:
160
162
  self.args.force = None
161
163
  print("Invalid -force category")
162
- exit()
164
+ 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.8"
27
+ version = "0.11.12"
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,
@@ -16,6 +16,7 @@ hdr_map = {
16
16
  "HDR10+": "HDR10+",
17
17
  "HDR10": "HDR10",
18
18
  "BLU-RAY / HDR10": "HDR10",
19
+ "BLU-RAY / HDR10+": "HDR10+",
19
20
  "HDR10 / HDR10": "HDR10",
20
21
  "HDR10 / HDR10 / HDR10": "HDR10",
21
22
  "HDR10 / HDR10+": "HDR10+",
@@ -82,7 +83,7 @@ class SearchTags(object):
82
83
  self.title = title
83
84
  self.year = year
84
85
  self.tags_dict = {}
85
- self.tags_position = tags_position
86
+
86
87
  self.TAG_TYPES: dict = tags_list
87
88
  self.SIGNS_LIST: dict = sign_list
88
89
  self.BAN_LIST: dict = ban_list
@@ -116,6 +117,11 @@ class SearchTags(object):
116
117
  tag_esc = re.escape(tag)
117
118
  return tag_esc
118
119
 
120
+ @staticmethod
121
+ def normalize_remux(tag: str) -> str:
122
+ tag_esc = re.escape(tag)
123
+ return tag_esc
124
+
119
125
  @staticmethod
120
126
  def normalize_video_encoder(tag: str) -> str:
121
127
  tag_esc = re.escape(tag)
@@ -139,7 +145,7 @@ class SearchTags(object):
139
145
  patterns = []
140
146
 
141
147
  # Remove banned items from categories
142
- self.tags_position = [x for x in self.tags_position if x not in self.BAN_LIST]
148
+ self.tags_position = [x.lower() for x in self.tags_position if x not in self.BAN_LIST]
143
149
 
144
150
  # loop sorted TAG_TYPES dictionary
145
151
  for i, (tag, category) in enumerate(
@@ -149,6 +155,8 @@ class SearchTags(object):
149
155
  norm = self.normalize_version_tag(tag)
150
156
  elif category == "platform":
151
157
  norm = self.normalize_platform_tag(tag)
158
+ elif category == "remux":
159
+ norm = self.normalize_remux(tag)
152
160
  elif category == "source":
153
161
  norm = self.normalize_sources(tag)
154
162
  elif category == "video_encoder":
@@ -180,7 +188,14 @@ class SearchTags(object):
180
188
  # /// Read from mediainfo
181
189
  updated_category = {}
182
190
  for category in self.tags_position:
183
- if category == "acodec":
191
+ if category == "remux":
192
+ remux_tags = self.tags_dict.get('remux', None)
193
+ if remux_tags:
194
+ if not any('remux' in tag.lower() for tag in remux_tags):
195
+ remux_tags.append('REMUX')
196
+ self.tags_dict.update({'remux': remux_tags})
197
+
198
+ elif category == "acodec":
184
199
  updated_category = self.mediainfo_audio(category=category)
185
200
 
186
201
  elif category == "vcodec":
@@ -259,7 +274,6 @@ class SearchTags(object):
259
274
  ch = {2: "2.0", 6: "5.1", 8: "7.1"}.get(channel_s, "")
260
275
  if f"{codec_translated} {ch} {atmos}".strip() not in audio_codecs:
261
276
  audio_codecs.append(f"{codec_translated} {ch} {atmos}".strip())
262
- # print(f"Mediainfo {other_format} -> {codec_translated} {ch} {atmos}")
263
277
 
264
278
  # Add flags
265
279
  for l in audio.get('other_language', []):
@@ -301,12 +315,8 @@ class SearchTags(object):
301
315
 
302
316
  # Check hdr
303
317
  if hdr_format_commercial:
304
- # print(f"hdr_format_commercial: {hdr_format_commercial}")
305
- # print(f"hdr_format: {hdr_format}")
306
318
  hdr = ''
307
319
  if hdr_format_commercial.upper() in hdr_map:
308
- # print(
309
- # f"hdr_format_commercial: {hdr_format_commercial} -> Tag: {hdr_map[hdr_format_commercial]}")
310
320
  hdr = hdr_map[hdr_format_commercial.upper()]
311
321
  # Check dolby vision
312
322
  if hdr not in hdr_map:
@@ -315,16 +325,14 @@ class SearchTags(object):
315
325
  if 'DOLBY VISION' in hdr_format_commercial.upper() or 'DOLBY VISION' in hdr_format.upper():
316
326
  # Search for fake remux
317
327
  if any("dvhe.08" in s or "Profile 8" in s for s in other_hdr_format):
318
- remux = self.tags_dict.get('remux', '')
319
- if remux:
320
- remux = remux[0]
321
- if 'remux' in remux.lower():
322
- del self.tags_dict[remux.lower()]
323
- self.tags_dict.update({'source': 'ENCODE'})
324
- custom_console.bot_warning_log(
325
- f"<> Warning: Detected REMUX with {other_hdr_format}")
326
- hdr = f"DOLBY VISION {hdr}"
327
- return {category: f"{hdr_map.get(hdr, '*HDR')}"}
328
+ if self.tags_dict.get('remux', ''):
329
+ remux = self.tags_dict.get('remux', '')
330
+ remux.append('HYBRID')
331
+ self.tags_dict.update({'remux': remux})
332
+ custom_console.bot_warning_log(
333
+ f"<> Warning: HYBRID REMUX with {other_hdr_format}")
334
+ hdr = f"DOLBY VISION {hdr}"
335
+ return {category: f"{hdr_map.get(hdr, '*HDR')}"}
328
336
 
329
337
  hdr = f"DOLBY VISION {hdr}"
330
338
  return {category: hdr_map.get(hdr, '*HDR')}
@@ -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])
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ BAN_LIST = {
4
+ "dummy" : "banned"
5
+ }
@@ -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",