Unit3DwebUp 0.0.14__py3-none-any.whl
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.
- config/__init__.py +4 -0
- config/api_data.py +28 -0
- config/constants.py +34 -0
- config/host_data.py +47 -0
- config/itt.py +154 -0
- config/logger.py +37 -0
- config/settings.py +212 -0
- config/sis.py +137 -0
- config/tags.py +395 -0
- config/trackers.py +47 -0
- external/__init__.py +0 -0
- external/async_http_client_service.py +48 -0
- external/websocket.py +41 -0
- models/__init__.py +0 -0
- models/interfaces.py +39 -0
- models/keywords.py +13 -0
- models/media.py +597 -0
- models/media_info.py +142 -0
- models/movie.py +287 -0
- models/tv.py +266 -0
- models/tvdb_search.py +102 -0
- models/videos.py +26 -0
- repositories/__init__.py +0 -0
- repositories/db_online.py +82 -0
- repositories/interfaces.py +59 -0
- repositories/job_repos.py +166 -0
- repositories/media_info_factory.py +28 -0
- services/__init__.py +0 -0
- services/auto_async_service.py +237 -0
- services/create_torrent_service.py +94 -0
- services/interfaces.py +72 -0
- services/itt_tracker_helper.py +463 -0
- services/itt_tracker_service.py +85 -0
- services/lifespan_service.py +58 -0
- services/media_service.py +114 -0
- services/tags_service.py +389 -0
- services/tmdb.py +246 -0
- services/torrent_client_service.py +92 -0
- services/torrent_service.py +107 -0
- services/tvdb.py +65 -0
- services/utility.py +433 -0
- services/video_service.py +356 -0
- unit3dwebup-0.0.14.dist-info/METADATA +191 -0
- unit3dwebup-0.0.14.dist-info/RECORD +53 -0
- unit3dwebup-0.0.14.dist-info/WHEEL +5 -0
- unit3dwebup-0.0.14.dist-info/entry_points.txt +2 -0
- unit3dwebup-0.0.14.dist-info/top_level.txt +6 -0
- use_case/__init__.py +0 -0
- use_case/make_torrent_usecase.py +67 -0
- use_case/process_all_usecase.py +43 -0
- use_case/scan_media_usecase.py +133 -0
- use_case/seed_usecase.py +117 -0
- use_case/upload_usecase.py +77 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Based on the old code 0.8.21
|
|
3
|
+
import io
|
|
4
|
+
import asyncio
|
|
5
|
+
import aiohttp
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from urllib.parse import urljoin
|
|
9
|
+
from config.api_data import trackers_api_data
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Myhttp:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
tracker_name: str,
|
|
16
|
+
pass_key: str = "",
|
|
17
|
+
session: aiohttp.ClientSession | None = None,
|
|
18
|
+
):
|
|
19
|
+
"""
|
|
20
|
+
:param tracker_name: short name of the tracker
|
|
21
|
+
:param pass_key: not used
|
|
22
|
+
:param session: aiohttp.ClientSession | None
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
api_data = trackers_api_data.get(tracker_name.upper())
|
|
26
|
+
if not api_data:
|
|
27
|
+
raise ValueError(f"Tracker '{tracker_name}' not found")
|
|
28
|
+
|
|
29
|
+
self.session = session or aiohttp.ClientSession()
|
|
30
|
+
self.pass_key = pass_key
|
|
31
|
+
|
|
32
|
+
self.base_url = api_data["url"]
|
|
33
|
+
self.api_token = api_data["api_key"]
|
|
34
|
+
|
|
35
|
+
# Endpoints
|
|
36
|
+
self.upload_url = urljoin(str(self.base_url), "api/torrents/upload")
|
|
37
|
+
self.filter_url = urljoin(str(self.base_url), "api/torrents/filter")
|
|
38
|
+
self.fetch_url = urljoin(str(self.base_url), "api/torrents/")
|
|
39
|
+
|
|
40
|
+
self.headers = {
|
|
41
|
+
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
|
42
|
+
"Accept": "application/json",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
self.base_params = {}
|
|
46
|
+
|
|
47
|
+
# default Payload
|
|
48
|
+
self.data = {
|
|
49
|
+
"name": "TEST.torrent",
|
|
50
|
+
"description": "", # mandatory
|
|
51
|
+
"mediainfo": "",
|
|
52
|
+
"bdinfo": "",
|
|
53
|
+
"type_id": 1,
|
|
54
|
+
"resolution_id": 10, # mandatory
|
|
55
|
+
"tmdb": 0, # mandatory
|
|
56
|
+
"imdb": 0,
|
|
57
|
+
"tvdb": 0,
|
|
58
|
+
"mal": 0, # no ancora implementato
|
|
59
|
+
"igdb": 0,
|
|
60
|
+
"anonymous": 0,
|
|
61
|
+
"sd": 0,
|
|
62
|
+
"keywords": "",
|
|
63
|
+
"personal_release": 0,
|
|
64
|
+
"internal": 0,
|
|
65
|
+
"featured": 0,
|
|
66
|
+
"free": 0,
|
|
67
|
+
"doubleup": 0,
|
|
68
|
+
"sticky": 0,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async def close(self):
|
|
72
|
+
await self.session.close()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Tracker(Myhttp):
|
|
76
|
+
"""
|
|
77
|
+
calls "low level"...
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
async def _get(self, params: dict):
|
|
81
|
+
|
|
82
|
+
# Build the params
|
|
83
|
+
final_params = {k: v for k, v in params.items() if v is not None}
|
|
84
|
+
|
|
85
|
+
# Set the _token_
|
|
86
|
+
headers = {
|
|
87
|
+
**self.headers,
|
|
88
|
+
"Authorization": f"Bearer {self.api_token}"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
while True:
|
|
92
|
+
try:
|
|
93
|
+
async with self.session.get(self.filter_url, params=final_params, headers=headers,
|
|
94
|
+
timeout=aiohttp.ClientTimeout(total=15)) as resp:
|
|
95
|
+
if resp.status == 429:
|
|
96
|
+
await asyncio.sleep(60)
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
resp.raise_for_status()
|
|
100
|
+
data = await resp.json()
|
|
101
|
+
return data
|
|
102
|
+
|
|
103
|
+
except aiohttp.ClientResponseError as e:
|
|
104
|
+
raise RuntimeError(f"Tracker HTTP error {e.status}") from e
|
|
105
|
+
|
|
106
|
+
except asyncio.TimeoutError as e:
|
|
107
|
+
raise TimeoutError("Tracker timeout") from e
|
|
108
|
+
|
|
109
|
+
async def _post(self, files: dict, data: dict) -> dict:
|
|
110
|
+
# Prepare the form
|
|
111
|
+
form = aiohttp.FormData()
|
|
112
|
+
|
|
113
|
+
# Fill the form
|
|
114
|
+
for k, v in data.items():
|
|
115
|
+
form.add_field(k, str(v))
|
|
116
|
+
|
|
117
|
+
# Prepare the field for the file
|
|
118
|
+
for k, (name, fp, ctype) in files.items():
|
|
119
|
+
form.add_field(k, fp, filename=name, content_type=ctype)
|
|
120
|
+
|
|
121
|
+
# Set the _token_
|
|
122
|
+
headers = {
|
|
123
|
+
**self.headers,
|
|
124
|
+
"Authorization": f"Bearer {self.api_token}"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Send to the tracker timeout 30
|
|
128
|
+
async with self.session.post(self.upload_url, headers=headers, data=form,
|
|
129
|
+
timeout=aiohttp.ClientTimeout(total=30)) as resp:
|
|
130
|
+
return await resp.json()
|
|
131
|
+
|
|
132
|
+
# Main endpoints fetch all
|
|
133
|
+
async def _fetch_all(self, params: dict):
|
|
134
|
+
final_params = {**self.base_params, **params}
|
|
135
|
+
async with self.session.get(
|
|
136
|
+
self.fetch_url,
|
|
137
|
+
params=final_params,
|
|
138
|
+
headers=self.headers,
|
|
139
|
+
) as resp:
|
|
140
|
+
resp.raise_for_status()
|
|
141
|
+
return await resp.json()
|
|
142
|
+
|
|
143
|
+
# Main endpoints fetch by id
|
|
144
|
+
async def _fetch_id(self, torrent_id: int):
|
|
145
|
+
async with self.session.get(
|
|
146
|
+
f"{self.fetch_url}{torrent_id}",
|
|
147
|
+
params=self.base_params,
|
|
148
|
+
headers=self.headers,
|
|
149
|
+
) as resp:
|
|
150
|
+
resp.raise_for_status()
|
|
151
|
+
return await resp.json()
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def bool_to_str(value: bool) -> str:
|
|
155
|
+
return 'true' if value else 'false'
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class filterAPI(Tracker):
|
|
159
|
+
"""
|
|
160
|
+
Calls "high level"...
|
|
161
|
+
"""
|
|
162
|
+
|
|
163
|
+
async def tmdb(self, tmdb_id: int, perPage: int = None):
|
|
164
|
+
return await self._get({"tmdbId": tmdb_id, "perPage": perPage})
|
|
165
|
+
|
|
166
|
+
async def imdb(self, imdb_id: int, perPage: int = None):
|
|
167
|
+
return await self._get({"imdbId": imdb_id, "perPage": perPage})
|
|
168
|
+
|
|
169
|
+
async def tvdb(self, tvdb_id: int, perPage: int = None):
|
|
170
|
+
return await self._get({"tvdbId": tvdb_id, "perPage": perPage})
|
|
171
|
+
|
|
172
|
+
async def mal(self, mal_id: int, perPage: int = None):
|
|
173
|
+
return await self._get({"malId": mal_id, "perPage": perPage})
|
|
174
|
+
|
|
175
|
+
async def playlist_id(self, playlistId: int, perPage: int = None):
|
|
176
|
+
return await self._get({"playlistId": playlistId, "perPage": perPage})
|
|
177
|
+
|
|
178
|
+
async def collection_id(self, collectionId: int, perPage: int = None):
|
|
179
|
+
return await self._get({"collectionId": collectionId, "perPage": perPage})
|
|
180
|
+
|
|
181
|
+
async def freeleech(self, freeleech: int, perPage: int = None):
|
|
182
|
+
return await self._get({"free": freeleech, "perPage": perPage})
|
|
183
|
+
|
|
184
|
+
async def name(self, name: str, perPage: int = None):
|
|
185
|
+
return await self._get({"name": name, "perPage": perPage})
|
|
186
|
+
|
|
187
|
+
async def description(self, description: str, perPage: int = None):
|
|
188
|
+
return await self._get({"description": description, "perPage": perPage})
|
|
189
|
+
|
|
190
|
+
async def mediainfo(self, mediainfo: str, perPage: int = None):
|
|
191
|
+
return await self._get({"mediainfo": mediainfo, "perPage": perPage})
|
|
192
|
+
|
|
193
|
+
async def bdinfo(self, bdinfo: str, perPage: int = None):
|
|
194
|
+
return await self._get({"bdinfo": bdinfo, "perPage": perPage})
|
|
195
|
+
|
|
196
|
+
async def start_year(self, start_year: str, perPage: int = None):
|
|
197
|
+
return await self._get({"startYear": start_year, "perPage": perPage})
|
|
198
|
+
|
|
199
|
+
async def end_year(self, end_year: str, perPage: int = None):
|
|
200
|
+
return await self._get({"endYear": end_year, "perPage": perPage})
|
|
201
|
+
|
|
202
|
+
async def uploader(self, uploader: str, perPage: int = None):
|
|
203
|
+
return await self._get({"uploader": uploader, "perPage": perPage})
|
|
204
|
+
|
|
205
|
+
async def alive(self, alive: int, perPage: int = None):
|
|
206
|
+
return await self._get({"alive": alive, "perPage": perPage})
|
|
207
|
+
|
|
208
|
+
async def dying(self, dying: int, perPage: int = None):
|
|
209
|
+
return await self._get({"dying": dying, "perPage": perPage})
|
|
210
|
+
|
|
211
|
+
async def dead(self, dead: int, perPage: int = None):
|
|
212
|
+
return await self._get({"dead": dead, "perPage": perPage})
|
|
213
|
+
|
|
214
|
+
async def file_name(self, file_name: str, perPage: int = None):
|
|
215
|
+
return await self._get({"file_name": file_name, "perPage": perPage})
|
|
216
|
+
|
|
217
|
+
async def seasonNumber(self, seasonNumber: int, perPage: int = None):
|
|
218
|
+
return await self._get({"seasonNumber": seasonNumber, "perPage": perPage})
|
|
219
|
+
|
|
220
|
+
async def episodeNumber(self, episodeNumber: int, perPage: int = None):
|
|
221
|
+
return await self._get({"episodeNumber": episodeNumber, "perPage": perPage})
|
|
222
|
+
|
|
223
|
+
async def types(self, type_id: str, perPage: int = None):
|
|
224
|
+
return await self._get({"types[]": type_id, "perPage": perPage})
|
|
225
|
+
|
|
226
|
+
async def resolution(self, res_id: str, perPage: int = None):
|
|
227
|
+
return await self._get({"resolutions[]": res_id, "perPage": perPage})
|
|
228
|
+
|
|
229
|
+
async def doubleup(self, double_up: int, perPage: int = None):
|
|
230
|
+
return await self._get({"doubleup": double_up, "perPage": perPage})
|
|
231
|
+
|
|
232
|
+
async def featured(self, featured: int, perPage: int = None):
|
|
233
|
+
return await self._get({"featured": featured, "perPage": perPage})
|
|
234
|
+
|
|
235
|
+
async def refundable(self, refundable: int, perPage: int = None):
|
|
236
|
+
return await self._get({"refundable": refundable, "perPage": perPage})
|
|
237
|
+
|
|
238
|
+
async def stream(self, stream: int, perPage: int = None):
|
|
239
|
+
return await self._get({"stream": stream, "perPage": perPage})
|
|
240
|
+
|
|
241
|
+
async def sd(self, sd: int, perPage: int = None):
|
|
242
|
+
return await self._get({"sd": sd, "perPage": perPage})
|
|
243
|
+
|
|
244
|
+
async def highspeed(self, high_speed: int, perPage: int = None):
|
|
245
|
+
return await self._get({"highspeed": high_speed, "perPage": perPage})
|
|
246
|
+
|
|
247
|
+
async def internal(self, internal: int, perPage: int = None):
|
|
248
|
+
return await self._get({"internal": internal, "perPage": perPage})
|
|
249
|
+
|
|
250
|
+
async def personal_release(self, personalRelease: int, perPage: int = None):
|
|
251
|
+
return await self._get({"personalRelease": personalRelease, "perPage": perPage})
|
|
252
|
+
|
|
253
|
+
async def tmdb_res(self, tmdb_id: int, res_id: str, perPage: int = None):
|
|
254
|
+
return await self._get({
|
|
255
|
+
"tmdbId": tmdb_id,
|
|
256
|
+
"resolutions[]": res_id,
|
|
257
|
+
"perPage": perPage,
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class Torrents(Tracker):
|
|
262
|
+
"""
|
|
263
|
+
TORRENTS
|
|
264
|
+
High level calls for fetch endpoints
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
async def torrents(self, perPage: int = None):
|
|
268
|
+
return await self._fetch_all({"perPage": perPage})
|
|
269
|
+
|
|
270
|
+
async def torrent(self, torrent_id: int):
|
|
271
|
+
return await self._fetch_id(torrent_id)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class Uploader(Tracker):
|
|
275
|
+
"""
|
|
276
|
+
UPLOADER
|
|
277
|
+
Upload the torrent file *.torrent
|
|
278
|
+
Upload the nfo file
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
async def upload_t(self, data: dict, torrent_path: Path, nfo_path=None) -> dict | None:
|
|
282
|
+
files = {}
|
|
283
|
+
|
|
284
|
+
if Path(torrent_path).exists():
|
|
285
|
+
with open(torrent_path, "rb") as tf:
|
|
286
|
+
files["torrent"] = (
|
|
287
|
+
"upload.torrent",
|
|
288
|
+
tf,
|
|
289
|
+
"application/x-bittorrent",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if nfo_path:
|
|
293
|
+
with open(nfo_path, "rb") as nf:
|
|
294
|
+
files["nfo"] = ("file.nfo", nf, "text/plain")
|
|
295
|
+
return await self._post(files, data)
|
|
296
|
+
|
|
297
|
+
return await self._post(files, data)
|
|
298
|
+
return None
|
|
299
|
+
|
|
300
|
+
@staticmethod
|
|
301
|
+
def encode_utf8(file_path: str) -> bytes | io.BytesIO:
|
|
302
|
+
encodings = ["utf-8", "iso-8859-1", "windows-1252", "latin1"]
|
|
303
|
+
raw = open(file_path, "rb").read()
|
|
304
|
+
|
|
305
|
+
for enc in encodings:
|
|
306
|
+
try:
|
|
307
|
+
return raw.decode(enc).encode("utf-8")
|
|
308
|
+
except UnicodeDecodeError:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
return io.BytesIO(b"Error: Unable to decode NFO file")
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class Unit3D(filterAPI, Torrents, Uploader):
|
|
315
|
+
"""
|
|
316
|
+
UNIT3D - calls
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
async def get_tmdb(self, tmdb_id: int, perPage: int = None):
|
|
320
|
+
return await self.tmdb(tmdb_id=tmdb_id, perPage=perPage)
|
|
321
|
+
|
|
322
|
+
async def get_tvdb(self, tvdb_id: int, perPage: int = None):
|
|
323
|
+
return await self.tvdb(tvdb_id=tvdb_id, perPage=perPage)
|
|
324
|
+
|
|
325
|
+
async def get_imdb(self, imdb_id: int, perPage: int = None):
|
|
326
|
+
return await self.imdb(imdb_id=imdb_id, perPage=perPage)
|
|
327
|
+
|
|
328
|
+
async def get_igdb(self, igdb_id: int, perPage: int = None):
|
|
329
|
+
return await self.igdb(igdb_id=igdb_id, perPage=perPage)
|
|
330
|
+
|
|
331
|
+
async def get_mal(self, mal_id: int, perPage: int = None):
|
|
332
|
+
return await self.mal(mal_id=mal_id, perPage=perPage)
|
|
333
|
+
|
|
334
|
+
async def get_playlist_id(self, playlist_id: int, perPage: int = None):
|
|
335
|
+
return await self.playlist_id(
|
|
336
|
+
playlistId=playlist_id, perPage=perPage
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
async def get_collection_id(self, collection_id: int, perPage: int = None):
|
|
340
|
+
return await self.collection_id(
|
|
341
|
+
collectionId=collection_id, perPage=perPage
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
async def get_freeleech(self, freeleech: int, perPage: int = None):
|
|
345
|
+
return await self.freeleech(
|
|
346
|
+
freeleech=freeleech, perPage=perPage
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
async def get_name(self, name: str, perPage: int = None):
|
|
350
|
+
return await self.name(name=name, perPage=perPage)
|
|
351
|
+
|
|
352
|
+
async def get_description(self, description: str, perPage: int = None):
|
|
353
|
+
return await self.description(
|
|
354
|
+
description=description, perPage=perPage
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
async def get_bdinfo(self, bdinfo: str, perPage: int = None):
|
|
358
|
+
return await self.bdinfo(bdinfo=bdinfo, perPage=perPage)
|
|
359
|
+
|
|
360
|
+
async def get_mediainfo(self, mediainfo: str, perPage: int = None):
|
|
361
|
+
return await self.mediainfo(
|
|
362
|
+
mediainfo=mediainfo, perPage=perPage
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
async def get_uploader(self, uploader: str, perPage: int = None):
|
|
366
|
+
return await self.uploader(
|
|
367
|
+
uploader=uploader, perPage=perPage
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
async def after_start_year(self, start_year: str, perPage: int = None):
|
|
371
|
+
return await self.start_year(
|
|
372
|
+
start_year=start_year, perPage=perPage
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
async def before_end_year(self, end_year: str, perPage: int = None):
|
|
376
|
+
return await self.end_year(
|
|
377
|
+
end_year=end_year, perPage=perPage
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
async def get_alive(self, alive: int, perPage: int = None):
|
|
381
|
+
return await self.alive(alive=alive, perPage=perPage)
|
|
382
|
+
|
|
383
|
+
async def get_dying(self, dying: int, perPage: int = None):
|
|
384
|
+
return await self.dying(dying=dying, perPage=perPage)
|
|
385
|
+
|
|
386
|
+
async def get_dead(self, dead: int, perPage: int = None):
|
|
387
|
+
return await self.dead(dead=dead, perPage=perPage)
|
|
388
|
+
|
|
389
|
+
async def get_filename(self, file_name: str, perPage: int = None):
|
|
390
|
+
return await self.file_name(
|
|
391
|
+
file_name=file_name, perPage=perPage
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
async def get_season_number(self, se_number: int, perPage: int = None):
|
|
395
|
+
return await self.seasonNumber(
|
|
396
|
+
seasonNumber=se_number, perPage=perPage
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
async def get_episode_number(self, ep_number: int, perPage: int = None):
|
|
400
|
+
return await self.episodeNumber(
|
|
401
|
+
episodeNumber=ep_number, perPage=perPage
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
async def get_types(self, type_id: str, perPage: int = None):
|
|
405
|
+
if type_id:
|
|
406
|
+
return await self.types(type_id=type_id, perPage=perPage)
|
|
407
|
+
return None
|
|
408
|
+
|
|
409
|
+
async def get_res(self, res_id: str, perPage: int = None):
|
|
410
|
+
if res_id:
|
|
411
|
+
return await self.resolution(res_id=res_id, perPage=perPage)
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
async def fetch_all(self, perPage: int = None):
|
|
415
|
+
return await self.torrents(perPage=perPage)
|
|
416
|
+
|
|
417
|
+
async def fetch_id(self, torrent_id: int):
|
|
418
|
+
return await self.torrent(torrent_id=torrent_id)
|
|
419
|
+
|
|
420
|
+
async def get_double_up(self, double_up: int, perPage: int = None):
|
|
421
|
+
return await self.doubleup(
|
|
422
|
+
double_up=double_up, perPage=perPage
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
async def get_featured(self, featured: int, perPage: int = None):
|
|
426
|
+
return await self.featured(
|
|
427
|
+
featured=featured, perPage=perPage
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
async def get_refundable(self, refundable: int, perPage: int = None):
|
|
431
|
+
return await self.refundable(
|
|
432
|
+
refundable=refundable, perPage=perPage
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
async def get_stream(self, stream: int, perPage: int = None):
|
|
436
|
+
return await self.stream(
|
|
437
|
+
stream=stream, perPage=perPage
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
async def get_sd(self, sd: int, perPage: int = None):
|
|
441
|
+
return await self.sd(sd=sd, perPage=perPage)
|
|
442
|
+
|
|
443
|
+
async def get_highspeed(self, highspeed: int, perPage: int = None):
|
|
444
|
+
return await self.highspeed(
|
|
445
|
+
high_speed=highspeed, perPage=perPage
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
async def get_internal(self, internal: int, perPage: int = None):
|
|
449
|
+
return await self.internal(
|
|
450
|
+
internal=internal, perPage=perPage
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
async def get_personal_release(self, personalRelease: int, perPage: int = None):
|
|
454
|
+
return await self.personal_release(
|
|
455
|
+
personalRelease=personalRelease, perPage=perPage
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
async def get_tmdb_res(self, tmdb_id: int, res_id: str, perPage: int = None):
|
|
459
|
+
if tmdb_id and res_id:
|
|
460
|
+
return await self.tmdb_res(
|
|
461
|
+
tmdb_id=tmdb_id, res_id=res_id, perPage=perPage
|
|
462
|
+
)
|
|
463
|
+
return None
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from config.constants import MediaStatus
|
|
3
|
+
from config.trackers import TRACKData
|
|
4
|
+
from config.settings import get_settings
|
|
5
|
+
|
|
6
|
+
from services.interfaces import TrackerServiceInterface
|
|
7
|
+
from services.itt_tracker_helper import Unit3D
|
|
8
|
+
from models.media import Media
|
|
9
|
+
|
|
10
|
+
from fastapi import FastAPI
|
|
11
|
+
import aiohttp
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ITTtrackerService(TrackerServiceInterface):
|
|
15
|
+
"""
|
|
16
|
+
a Class to interact with tracker
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session: aiohttp.ClientSession, app: FastAPI):
|
|
20
|
+
"""
|
|
21
|
+
:param session: aiohttp.ClientSession
|
|
22
|
+
:param app: FastAPI
|
|
23
|
+
"""
|
|
24
|
+
self.session = session
|
|
25
|
+
self.app = app
|
|
26
|
+
self.tracker_name = "ITT"
|
|
27
|
+
self.tracker_data = TRACKData.load_from_module(self.tracker_name)
|
|
28
|
+
self.tracker = Unit3D(tracker_name=self.tracker_name, session=session)
|
|
29
|
+
|
|
30
|
+
async def prepare_payload(self, media: Media) -> dict:
|
|
31
|
+
"""
|
|
32
|
+
:param media: The Media object processed
|
|
33
|
+
:return:
|
|
34
|
+
|
|
35
|
+
The Media object processed helps to build the payload for the tracker
|
|
36
|
+
"""
|
|
37
|
+
settings = get_settings()
|
|
38
|
+
return {
|
|
39
|
+
"name": media.display_name,
|
|
40
|
+
"tmdb": media.tmdb_id or 0,
|
|
41
|
+
"imdb": media.imdb_id_from_tvdb or 0,
|
|
42
|
+
"tvdb": media.tvdb_id if media.tvdb_id and media.category == 'series' else None,
|
|
43
|
+
"keywords": media.keyword,
|
|
44
|
+
"category_id": self.tracker_data.category.get(media.category),
|
|
45
|
+
"anonymous": int(settings.prefs.ANON),
|
|
46
|
+
"resolution_id": self.tracker_data.resolution.get(media.resolution),
|
|
47
|
+
"mediainfo": media.media_to_string,
|
|
48
|
+
"description": media.description,
|
|
49
|
+
"sd": media.is_hd,
|
|
50
|
+
"type_id": self.tracker_data.filter_type(media.file_name),
|
|
51
|
+
"season_number": media.guess_season or "",
|
|
52
|
+
"episode_number": media.guess_episode or "",
|
|
53
|
+
"personal_release": int(settings.prefs.PERSONAL_RELEASE)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async def upload(self, media: Media) -> dict:
|
|
57
|
+
"""
|
|
58
|
+
:param media: Media object processed
|
|
59
|
+
:return:
|
|
60
|
+
"""
|
|
61
|
+
# Payload ready
|
|
62
|
+
payload = await self.prepare_payload(media)
|
|
63
|
+
|
|
64
|
+
# Load the file and send to the tracker
|
|
65
|
+
response = await self.tracker.upload_t(data=payload, torrent_path=media.torrent_file_path)
|
|
66
|
+
|
|
67
|
+
# Wait for response
|
|
68
|
+
if not response:
|
|
69
|
+
media.status = MediaStatus.TRACKER_NOT_UPLOADED
|
|
70
|
+
return {'status': '404', 'message': 'Torrent file not found !', 'file': media.torrent_file_path,
|
|
71
|
+
'job_id': media.job_id}
|
|
72
|
+
|
|
73
|
+
if response.get('success', None):
|
|
74
|
+
media.status = MediaStatus.TRACKER_UPLOADED
|
|
75
|
+
await self.app.state.job.update_job(job_id=media.job_id, new_data=media.to_dict())
|
|
76
|
+
return {'status': '200', 'message': 'Torrent uploaded', 'file': media.torrent_file_path,
|
|
77
|
+
'job_id': media.job_id}
|
|
78
|
+
else:
|
|
79
|
+
media.status = MediaStatus.TRACKER_NOT_UPLOADED
|
|
80
|
+
await self.app.state.job.update_job(job_id=media.job_id, new_data=media.to_dict())
|
|
81
|
+
return {'status': '409', 'message': response.get('data', None),
|
|
82
|
+
'file': media.torrent_file_path, 'job_id': media.job_id}
|
|
83
|
+
|
|
84
|
+
async def search(self, query: str) -> dict:
|
|
85
|
+
return await self.tracker.name(query)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from config.settings import Settings
|
|
7
|
+
from config.logger import get_logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def update_mounted_paths(app: FastAPI):
|
|
11
|
+
"""
|
|
12
|
+
Updates to mounted path strings are valid only when the backend is running on the host
|
|
13
|
+
When using docker ,docker must also be restarted
|
|
14
|
+
:param app: the FastAPI app
|
|
15
|
+
:return:
|
|
16
|
+
"""
|
|
17
|
+
settings = Settings()
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
# Switch path between dev and Docker
|
|
21
|
+
app.state.watcher_path = "/home/app/watcher" if os.getenv(
|
|
22
|
+
"DOCKER") == "true" else app.state.settings.prefs.WATCHER_PATH
|
|
23
|
+
app.state.watcher_destination_path = "/home/app/watcher_destination_path" if os.getenv(
|
|
24
|
+
"DOCKER") == "true" else app.state.settings.prefs.WATCHER_DESTINATION_PATH
|
|
25
|
+
|
|
26
|
+
app.state.scan_path = "/home/app/scan" if os.getenv("DOCKER") == "true" else app.state.settings.prefs.SCAN_PATH
|
|
27
|
+
torrent_archive_path = Path("/home/app/torrent_archive") if os.getenv("DOCKER") == "true" else Path(
|
|
28
|
+
settings.prefs.TORRENT_ARCHIVE_PATH)
|
|
29
|
+
app.state.torrent_archive_path = torrent_archive_path.expanduser().resolve()
|
|
30
|
+
|
|
31
|
+
# Env file mount: used to edit Env file from the frontend
|
|
32
|
+
app.state.env_file = "/home/app/config/.env" if os.getenv("DOCKER") == "true" else Path(".env")
|
|
33
|
+
|
|
34
|
+
# Main folder
|
|
35
|
+
logger.info("\nChecking Unit3DwebUp configuration file..\n")
|
|
36
|
+
logger.info(f"Unit3DwebUp : v.{settings.unit3DwebUp.VERSION}")
|
|
37
|
+
logger.info(f"Docker mode : '{os.getenv("DOCKER")}'")
|
|
38
|
+
logger.info(f"Env Path : '{app.state.env_file}'\n")
|
|
39
|
+
|
|
40
|
+
logger.info(f"Scan Path : '{app.state.scan_path}' <-> {app.state.settings.prefs.SCAN_PATH}")
|
|
41
|
+
logger.info(
|
|
42
|
+
f"Torrent Archive Path : '{app.state.torrent_archive_path}' <-> {app.state.settings.prefs.TORRENT_ARCHIVE_PATH}")
|
|
43
|
+
logger.info(f"Watcher Path : '{app.state.watcher_path}' <-> {app.state.settings.prefs.WATCHER_PATH}")
|
|
44
|
+
logger.info(
|
|
45
|
+
f"Watcher Dest. Path : '{app.state.watcher_destination_path}' <-> {app.state.settings.prefs.WATCHER_DESTINATION_PATH}\n")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def checking_env_file(app: FastAPI):
|
|
49
|
+
settings = Settings()
|
|
50
|
+
logger = get_logger(__name__)
|
|
51
|
+
|
|
52
|
+
# Check configuration file (only once because get_settings() is cached)
|
|
53
|
+
for field_name, field in settings.tracker.model_fields.items():
|
|
54
|
+
# optional field (default=None)
|
|
55
|
+
if field.default is None:
|
|
56
|
+
value = getattr(settings.tracker, field_name)
|
|
57
|
+
if value is None:
|
|
58
|
+
logger.warning(f"{field_name} not set in .env – related feature may be disabled")
|