Unit3Dup 0.8.14__py3-none-any.whl → 0.8.16__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.
common/command.py CHANGED
@@ -43,11 +43,10 @@ class CommandLine:
43
43
  parser.add_argument("-duplicate", "--duplicate", action="store_true", help="Find duplicates")
44
44
  parser.add_argument("-personal", "--personal", action="store_true", help="Set to personal release")
45
45
 
46
- parser.add_argument("-pw", "--pw", type=str, help="")
47
46
  parser.add_argument("-ftp", "--ftp", action="store_true", help="Connect to FTP")
48
47
 
49
48
  # optional
50
- parser.add_argument("-dump", "--dump", type=str, help="Download all torrent files")
49
+ parser.add_argument("-dump", "--dump", action="store_true", help="Download all torrent titles")
51
50
  parser.add_argument("-s", "--search", type=str, help="Search for torrent")
52
51
  parser.add_argument("-i", "--info", type=str, help="Get info on torrent")
53
52
  parser.add_argument("-up", "--uploader", type=str, help="Search by uploader")
common/database.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import json
2
+ import os
2
3
  import sqlite3
4
+ from unit3dup import config_settings
3
5
 
4
6
  # Torrent attributes
5
7
  create_table_sql = ('\n'
@@ -53,14 +55,17 @@ class Database:
53
55
 
54
56
  def __init__(self, db_file):
55
57
  self.filename = db_file
56
- self.database = sqlite3.connect(f"{db_file}.db")
58
+ self.CACHE_PATH = config_settings.user_preferences.CACHE_PATH
59
+ self.database = sqlite3.connect(os.path.join(self.CACHE_PATH,f"{db_file}.db"))
57
60
  self.cursor = self.database.cursor()
58
61
  self.build()
59
62
 
60
63
  def build(self):
64
+ self.cursor.execute("DROP TABLE IF EXISTS torrents")
61
65
  self.cursor.execute(create_table_sql)
62
66
  self.database.commit()
63
67
 
68
+
64
69
  def write(self, data: dict):
65
70
  for key, value in data.items():
66
71
  if isinstance(value, (dict,list)):
@@ -69,26 +74,6 @@ class Database:
69
74
  keys = ', '.join(data.keys())
70
75
  placeholders = ', '.join(['?'] * len(data))
71
76
  values = tuple(data.values())
72
-
73
77
  sql = f'''INSERT INTO torrents ({keys}) VALUES ({placeholders})'''
74
78
  self.cursor.execute(sql, values)
75
- self.database.commit()
76
-
77
- def search(self, query: str):
78
- # Search a substring in 'name'
79
- self.cursor.execute("SELECT name FROM torrents WHERE name LIKE ?", ('%' + query + '%',))
80
- results = self.cursor.fetchall()
81
- # print the results
82
- for r in results:
83
- print(f"[database]{r}")
84
- input("[DATABASE] Press Enter to continue...")
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
79
+ self.database.commit()
common/settings.py CHANGED
@@ -13,7 +13,7 @@ from common.utility import ManageTitles
13
13
  from common import trackers
14
14
 
15
15
  config_file = "Unit3Dbot.json"
16
- version = "0.8.14"
16
+ version = "0.8.16"
17
17
 
18
18
  if os.name == "nt":
19
19
  PW_TORRENT_ARCHIVE_PATH: Path = Path(os.getenv("LOCALAPPDATA", ".")) / "Unit3Dup_config" / "pw_torrent_archive"
unit3dup/__main__.py CHANGED
@@ -125,14 +125,6 @@ def main():
125
125
  bot.watcher(duration=config.user_preferences.WATCHER_INTERVAL, watcher_path=config.user_preferences.WATCHER_PATH,
126
126
  destination_path = config.user_preferences.WATCHER_DESTINATION_PATH)
127
127
 
128
- # Pw
129
- if cli.args.pw:
130
- print("Not yet implemented")
131
- # bot = Bot(path=cli.args.pw,cli=cli.args, trackers_name_list=tracker_name_list)
132
- # bot.pw()
133
- return
134
-
135
-
136
128
  # ftp and upload
137
129
  if cli.args.ftp:
138
130
  bot = Bot(path='', cli=cli.args, mode="folder", trackers_name_list=tracker_name_list)
@@ -153,13 +145,14 @@ def main():
153
145
 
154
146
  # Search by different criteria
155
147
  if cli.args.search:
156
- torrent_info.view_search(cli.args.search)
148
+ print("NOT YET IMPLEMENTED")
149
+ # torrent_info.view_search(cli.args.search)
157
150
  return
158
151
 
159
152
  # Dump
160
153
  if cli.args.dump:
161
- print("Not yet implemented")
162
- # torrent_info.view_search(cli.args.dump,inkey=False)
154
+ print("NOT YET IMPLEMENTED")
155
+ # torrent_info.view_search(" ", inkey=False)
163
156
  return
164
157
 
165
158
  if cli.args.info:
unit3dup/bot.py CHANGED
@@ -11,7 +11,6 @@ from unit3dup.media_manager.ContentManager import ContentManager
11
11
  from unit3dup.media_manager.TorrentManager import TorrentManager
12
12
 
13
13
  from common.external_services.ftpx.core.models.list import FTPDirectory
14
- from common.external_services.Pw.pw_manager import PwManager
15
14
  from common.external_services.ftpx.core.menu import Menu
16
15
  from common.external_services.ftpx.client import Client
17
16
  from common.extractor import Extractor
@@ -55,6 +54,10 @@ class Bot:
55
54
  """
56
55
  custom_console.panel_message("Analyzing your media files... Please wait")
57
56
 
57
+ if not os.path.exists(self.path):
58
+ custom_console.bot_error_log("Path doesn't exist")
59
+ return False
60
+
58
61
  # Get a Files list with basic attributes and create a content object for each
59
62
  self.content_manager: ContentManager = ContentManager(path=self.path, mode=self.mode, cli=self.cli)
60
63
  contents = self.content_manager.process()
@@ -64,10 +67,6 @@ class Bot:
64
67
  custom_console.bot_error_log("There are no Media to process")
65
68
  return False
66
69
 
67
- if not os.path.exists(self.path):
68
- custom_console.bot_error_log("Path doesn't exist")
69
- return False
70
-
71
70
  # we got a handled exception
72
71
  if contents is None:
73
72
  exit(1)
@@ -179,19 +178,6 @@ class Bot:
179
178
  custom_console.bot_log("Exiting...")
180
179
  return True
181
180
 
182
- def pw(self)-> bool:
183
- """
184
- Interacts with the PW service to search for torrent files
185
-
186
- This method performs a search query and logs the results for torrents with
187
- a certain number of seeders
188
- """
189
- # PW service
190
- pw_manager = PwManager(cli=self.cli)
191
- pw_manager.process()
192
- custom_console.panel_message("Searching... Please wait")
193
- return True
194
-
195
181
 
196
182
  def ftp(self)-> None:
197
183
  """
unit3dup/torrent.py CHANGED
@@ -147,6 +147,7 @@ class View(Torrent):
147
147
 
148
148
  # Load the constant tracker
149
149
  self.tracker_data = TRACKData.load_from_module(tracker_name=tracker_name)
150
+ self.tracker_name = tracker_name
150
151
  print()
151
152
 
152
153
  def get_unique_id(self, media_info: str) -> str:
@@ -182,12 +183,12 @@ class View(Torrent):
182
183
  else:
183
184
  release_year = item['attributes']['release_year']
184
185
 
185
- media = f"[TRACKER] TMDB: {item['attributes']['tmdb_id']} - {release_year}"
186
+ media = f"{self.tracker_name} - TMDB: {item['attributes']['tmdb_id']} - {release_year}"
186
187
 
187
188
  elif item['attributes']['igdb_id'] !=0:
188
- media = f"[TRACKER] IGDB: {item['attributes']['igdb_id']}"
189
+ media = f"{self.tracker_name} IGDB: {item['attributes']['igdb_id']}"
189
190
  else:
190
- media = f"[TRACKER] DOC:"
191
+ media = f"{self.tracker_name} DOC:"
191
192
 
192
193
  # Print a data to the console
193
194
  custom_console.bot_log(f"\n {media} - {item['attributes']['name']}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Unit3Dup
3
- Version: 0.8.14
3
+ Version: 0.8.16
4
4
  Summary: An uploader for the Unit3D torrent tracker
5
5
  Author: Parzival
6
6
  License-Expression: MIT
@@ -1,14 +1,14 @@
1
1
  common/__init__.py,sha256=M82CDZm5KKWf8V3a10FKCy1wtFrJd1TMEhBib1Xtk1g,76
2
2
  common/bdinfo_string.py,sha256=dfrU5T90fvkO_fklvVNWSoCgd5qvrVvqEZ_HKEsHAU8,3457
3
3
  common/bittorrent.py,sha256=MPgMykWjmhGox7jf1MQn53Ew4NxpjzEc4FCF6oA2oYs,315
4
- common/command.py,sha256=7QmtTFdawq2S70p9JP2dr2bGbAUdiTVnGFiNrBcz3R4,6029
4
+ common/command.py,sha256=BJNOdnfS5FQpZdCib-q0Tq9S9IoPtHlOEF9kxDQCzls,5978
5
5
  common/constants.py,sha256=tdflS7ZJwAcI1ZYUhBBb60QybVT7TmVS-EYxS4bNFp0,352
6
- common/database.py,sha256=Lp6i3Wu5vza87atWD2NGQRCchVLBcY-k8E0AAuAQxls,3232
6
+ common/database.py,sha256=XOjh-X3XZBHjln9scRgzArpTlfgfR7P3ZlwahPbeUSU,3056
7
7
  common/extractor.py,sha256=WKZwt2kQfKO2VJ1rtwE_j6Zl84zICnowZq_Ql16wmRc,4564
8
8
  common/frames.py,sha256=p_jsaXII5tZTVt5ymu-w1hk2c_UMeOn3PZeuVR-wXWY,7973
9
9
  common/mediainfo.py,sha256=U2r1jJejBsV8GP3iPk4O8_NkHO5RQ9Kkh2bKwVNUBmg,6229
10
10
  common/mediainfo_string.py,sha256=8vuWlF2bqWRKpDbn81bV2fPA7hbl7RwOnxN2i4E3zNE,3958
11
- common/settings.py,sha256=9UCaHT1ZC0c3U0vwOxxpNNkNpyMo93nXmopXdEkn3hg,32782
11
+ common/settings.py,sha256=Lpr3MRybihcsdh27bEnSg1VxCIXRrpKZMn39mtY1Gw8,32782
12
12
  common/title.py,sha256=nFainfUBTYW9ML4Y-CB9ZFD_Es-OZXcAMPUo6D09u3k,3793
13
13
  common/torrent_clients.py,sha256=NOIpYtLG_bA19HwcH9ahGFmGNtRkoMO6nAjma-JzDfs,12040
14
14
  common/utility.py,sha256=r5MuFrafAzSJbe7DLx8uMKSG1qQi8k6oA43Y_qJhH7A,8647
@@ -16,15 +16,6 @@ common/external_services/__init__.py,sha256=rU7HPEcZ7WQFD8eqDzuye2xHPBjxXPwPqpt7
16
16
  common/external_services/imageHost.py,sha256=BpdtGZUtZHl4QJwFKyJs_BI6z4gHI4wu_Beu-fnfkDw,9588
17
17
  common/external_services/imdb.py,sha256=AIo8CO-SfP_uNocDeNY08hpvCAnuotoI7hYUytDiMQA,579
18
18
  common/external_services/mediaresult.py,sha256=Yz-h7QROkfNHwaSSXZ1JRDSmG2YIZkmtQO5vmpkYWZ4,677
19
- common/external_services/Pw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- common/external_services/Pw/pw_manager.py,sha256=a8s3YgzHf0iw45Fh5BeMRSX5aGlatBJbOYamSrWFj2U,2225
21
- common/external_services/Pw/pw_service.py,sha256=_-AQgffqiuoS0xcHc7sEgQBuXidU9RdJZyZnRa-ATZc,1151
22
- common/external_services/Pw/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
- common/external_services/Pw/core/pw_api.py,sha256=C1XzN-hJwJvzjZwiNTL39mu4gJGiEKKZYjt9JAStIHk,3234
24
- common/external_services/Pw/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- common/external_services/Pw/core/models/indexers.py,sha256=7nVqZd-_nbwL40HLGEFauI8zNeC-w66py2tK7h9rGV4,1947
26
- common/external_services/Pw/core/models/search.py,sha256=fmoEccxgRDG26nmEcpJR0e5twBLTOEzDzaeQFG2TAc4,969
27
- common/external_services/Pw/core/models/torrent_client_config.py,sha256=zmPqcYoRSIeXoxLvK0Q5wAj_gkSd1y1u1oEj5g6BvGM,1025
28
19
  common/external_services/ftpx/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
20
  common/external_services/ftpx/client.py,sha256=D_23Cw9zeQQIiVhUIUE13ENXNVyP4-MMx_Rgqg67U7Y,10713
30
21
  common/external_services/ftpx/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -72,9 +63,9 @@ common/trackers/itt.py,sha256=cB0D5ZpMbGgEOAKloET9dS_fAFclhzwPPwRFt-LStX0,4019
72
63
  common/trackers/sis.py,sha256=AahQH-FxAqS19vgEFylOJzeY0_SHr27_Tibs7lraSdI,3480
73
64
  common/trackers/trackers.py,sha256=sts5l_x27V2Op1Y6Au5FoQujspSkWMPWyEYMfcmVYLA,1467
74
65
  unit3dup/__init__.py,sha256=seXz3lHgdrUBiOnhC6Je47npS66UZ0c62VFuoH3z5Mk,78
75
- unit3dup/__main__.py,sha256=UIQ0s9deamfOSS7-O65ojeuN9Cx6zpPGD5H1HXlfrMM,9256
66
+ unit3dup/__main__.py,sha256=DBhzZdUi19N1-WTBQJbps9wQKNcNhwreaXwXQtFORHs,9088
76
67
  unit3dup/automode.py,sha256=HIJrE8qEHp7DZQbppNtB_8jHY3Q94d6frp7BXnZuEac,4387
77
- unit3dup/bot.py,sha256=TM8Htnq-udJDnN-7TwaPP6CiwWX0wb_Nud9p8ej8GRo,9250
68
+ unit3dup/bot.py,sha256=2P24z_8UM58binKqrM0eIXxPTiWEduyK2bdfVkmxPEQ,8759
78
69
  unit3dup/duplicate.py,sha256=Ji1Y9vVLmhFQ5rgkUU-s7uXl-jC8akAa8OiQOfjFa9g,10428
79
70
  unit3dup/exceptions.py,sha256=DhlcbbZEuvEYk5VSZTnle8oRg5I8Pq6Y6fxXTdOOOfo,4078
80
71
  unit3dup/media.py,sha256=5suH2XeekB80zNoIAM8PERZBO746JDlpcIvHz-4jY9E,13888
@@ -82,7 +73,7 @@ unit3dup/pvtDocu.py,sha256=ZLMaal2fn6ZcFPLtET0LTmYEPjhJmLitEjkICHrm9CQ,4594
82
73
  unit3dup/pvtTorrent.py,sha256=cItlsCpcUJL23iXQHy0YzrrvV3JSl54UlBgm8_UROAs,2559
83
74
  unit3dup/pvtTracker.py,sha256=U4xMmhiqMNZaIOgEi3pnP-3H75e7Gagndh8GDFEP7Cs,18647
84
75
  unit3dup/pvtVideo.py,sha256=w_T6wEeGrsHkuR3KObJc4mNpJgg9hhHh_9LoxFlaqjM,3900
85
- unit3dup/torrent.py,sha256=JEcrVG8dlYASBvL51GldU1ouKlfieKccKX2KZ_4UKBY,19883
76
+ unit3dup/torrent.py,sha256=tWnAxZjPKGr_ktqfVmimnSgdnWU0efNbB-FIsLUMmY8,19957
86
77
  unit3dup/upload.py,sha256=gjB8u2oP2FPXZ8t9SMIzkUEr52hKRy_zSR8OWJlW2xc,6075
87
78
  unit3dup/media_manager/ContentManager.py,sha256=68k8sqJgL1a2ir_rpQhkB1ntePp01IpblJYcQCdev2Y,7358
88
79
  unit3dup/media_manager/DocuManager.py,sha256=oFt7jlxj-gIUty9PADBQV5a24bsv3yhjKhwI6niOhf4,3116
@@ -95,11 +86,13 @@ unit3dup/media_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
95
86
  unit3dup/media_manager/common.py,sha256=hG2zOw7ocQfZyI1dhusbehxswpIrZK7T2aAbCNwULqA,10138
96
87
  unit3dup/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
97
88
  unit3dup/web/main.py,sha256=BzjKDgAjKZMnoQwx7nDDbs_64kCrFO1VYpbHmsGiFVc,1267
98
- unit3dup-0.8.14.dist-info/licenses/LICENSE,sha256=GNAZDLhU0xz8QPbIyHAOYlVnQYDvKWk2N9fZJMhqaG8,1090
89
+ unit3dup-0.8.16.dist-info/licenses/LICENSE,sha256=GNAZDLhU0xz8QPbIyHAOYlVnQYDvKWk2N9fZJMhqaG8,1090
99
90
  view/__init__.py,sha256=XIzb6rl58HmYPnksD73cYMFF88dn6FHa3u7bOHFbChk,81
100
91
  view/custom_console.py,sha256=OITmkEoQH9N_uE5ElPaSdc8XvaLzE9wcwTbOHtcMvrI,5629
101
- unit3dup-0.8.14.dist-info/METADATA,sha256=Zqj0fjP9zuTWL1BDAJy6dxRciIYke0m3GP7z2lcArRg,4864
102
- unit3dup-0.8.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
103
- unit3dup-0.8.14.dist-info/entry_points.txt,sha256=fxXSyI6-r6jy9_v-C5ZHm03q1aC3tE9EvCQZxC1NQnI,52
104
- unit3dup-0.8.14.dist-info/top_level.txt,sha256=19NVMnQNkJxBUKebRNaYCRs56A5CO4U1L67GMQCPiLU,21
105
- unit3dup-0.8.14.dist-info/RECORD,,
92
+ view/custom_console2.py,sha256=ipNoZWz1YO5iOp-vIH3PrWoBzpiAxdW6gdsijKSEg7c,6137
93
+ view/settings.py,sha256=dSabnCfUGqPS8w1a2B_O_GF_eT9GsxBDRp7Yzf6RDF8,1289
94
+ unit3dup-0.8.16.dist-info/METADATA,sha256=6b9LonXp-zhs5cbxezMrA9RErDDuB4wvoDLsjAsNjRM,4864
95
+ unit3dup-0.8.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
96
+ unit3dup-0.8.16.dist-info/entry_points.txt,sha256=fxXSyI6-r6jy9_v-C5ZHm03q1aC3tE9EvCQZxC1NQnI,52
97
+ unit3dup-0.8.16.dist-info/top_level.txt,sha256=19NVMnQNkJxBUKebRNaYCRs56A5CO4U1L67GMQCPiLU,21
98
+ unit3dup-0.8.16.dist-info/RECORD,,
@@ -0,0 +1,139 @@
1
+ from nicegui import ui
2
+ from typing import List, Callable
3
+ from common import config_settings
4
+ from datetime import datetime
5
+
6
+
7
+ class NiceGUIConsole:
8
+ def __init__(self):
9
+ self.init_footer_log()
10
+ self.log_area = ui.column().style('max-height: 400px; overflow-y: auto; border: 1px solid #ccc; padding: 10px')
11
+ self.counter_label = ui.label()
12
+
13
+ def init_footer_log(self):
14
+ with ui.element('div').style('''
15
+ position: fixed;
16
+ bottom: 0;
17
+ left: 0;
18
+ width: 100%;
19
+ background-color: #2e3b4e;
20
+ text-align: center;
21
+ padding: 4px 8px; /* meno padding verticale */
22
+ font-size: 14px;
23
+ line-height: 18px;
24
+ z-index: 1000;
25
+ ''') as footer:
26
+ self.footer_label = ui.label("Ready.").style("color: white; margin: 0")
27
+
28
+ def log_message(self, message: str, color: str = "#ffffff"):
29
+ timestamp = datetime.now().strftime("%H:%M:%S")
30
+ """
31
+ with self.log_area:
32
+ ui.label(f"[{timestamp}] {message}").style(f'color: {color}; white-space: pre-wrap')
33
+ """
34
+ self.footer_label.text = f"${timestamp}$ {message}"
35
+ self.footer_label.style(f"color: {color}")
36
+
37
+
38
+ def panel_message(self, message: str, color: str = "#ccc"):
39
+ with ui.card().style(f'background-color: {color}; color: white'):
40
+ ui.label(message).style("text-align: center")
41
+
42
+ def bot_log(self, message: str):
43
+ self._add_log(message, config_settings.console_options.NORMAL_COLOR)
44
+
45
+ def bot_error_log(self, message: str):
46
+ self._add_log(f'❌ {message}', config_settings.console_options.ERROR_COLOR)
47
+
48
+ def bot_warning_log(self, message: str):
49
+ self._add_log(f'⚠️ {message}', config_settings.console_options.QUESTION_MESSAGE_COLOR)
50
+
51
+ def bot_input_log(self, message: str):
52
+ self._add_log(message, config_settings.console_options.NORMAL_COLOR)
53
+
54
+ def bot_question_log(self, message: str):
55
+ self._add_log(message, config_settings.console_options.QUESTION_MESSAGE_COLOR)
56
+
57
+ def bot_counter_log(self, message: str):
58
+ self.counter_label.set_text(message)
59
+ self.counter_label.style(f'color: {config_settings.console_options.QUESTION_MESSAGE_COLOR}')
60
+
61
+ def bot_process_table_log(self, content: List):
62
+ ui.label("Here is your files list" if content else "There are no files here")
63
+ ui.table(columns=[
64
+ {'name': 'pack', 'label': 'Torrent Pack', 'field': 'pack'},
65
+ {'name': 'category', 'label': 'Media', 'field': 'category'},
66
+ {'name': 'path', 'label': 'Path', 'field': 'path'},
67
+ ], rows=[
68
+ {
69
+ 'pack': 'Yes' if item.torrent_pack else 'No',
70
+ 'category': item.category,
71
+ 'path': item.torrent_path,
72
+ } for item in content
73
+ ])
74
+
75
+ def bot_process_table_pw(self, content: List):
76
+ ui.label("Here is your files list" if content else "There are no files here")
77
+ ui.table(columns=[
78
+ {'name': 'category', 'label': 'Category', 'field': 'category'},
79
+ {'name': 'indexer', 'label': 'Indexer', 'field': 'indexer'},
80
+ {'name': 'title', 'label': 'Title', 'field': 'title'},
81
+ {'name': 'size', 'label': 'Size', 'field': 'size'},
82
+ {'name': 'seeders', 'label': 'Seeders', 'field': 'seeders'},
83
+ ], rows=[
84
+ {
85
+ 'category': item.categories[0]['name'],
86
+ 'indexer': item.indexer,
87
+ 'title': item.title,
88
+ 'size': str(item.size),
89
+ 'seeders': str(item.seeders),
90
+ } for item in content
91
+ ])
92
+
93
+ def bot_tmdb_table_log(self, result, title: str, media_info_language: List[str]):
94
+ media_info_audio_languages = ", ".join(media_info_language).upper()
95
+ self.panel_message(f"Results for {title.upper()}")
96
+
97
+ ui.table(columns=[
98
+ {'name': 'tmdb_id', 'label': 'TMDB ID', 'field': 'tmdb_id'},
99
+ {'name': 'language', 'label': 'LANGUAGE', 'field': 'language'},
100
+ {'name': 'poster', 'label': 'TMDB POSTER', 'field': 'poster'},
101
+ {'name': 'backdrop', 'label': 'TMDB BACKDROP', 'field': 'backdrop'},
102
+ ], rows=[{
103
+ 'tmdb_id': str(result.video_id),
104
+ 'language': media_info_audio_languages,
105
+ 'poster': result.poster_path,
106
+ 'backdrop': result.backdrop_path,
107
+ }])
108
+
109
+ def wait_for_user_confirmation(self, message: str, on_confirm: Callable):
110
+ self.bot_error_log(message)
111
+ with ui.row():
112
+ ui.button("OK", on_click=on_confirm)
113
+ ui.button("Cancel",
114
+ on_click=lambda: self.bot_error_log("Operation cancelled. Please update your config file."))
115
+
116
+ def user_input(self, message: str, on_submit: Callable[[int], None]):
117
+ with ui.row():
118
+ input_field = ui.input(label=message, placeholder="Enter TMDB ID").props("type=number")
119
+ ui.button("Submit", on_click=lambda: self._validate_and_submit(input_field.value, on_submit))
120
+
121
+ def user_input_str(self, message: str, on_submit: Callable[[str], None]):
122
+ with ui.row():
123
+ input_field = ui.input(label=message, placeholder="Enter text")
124
+ ui.button("Submit", on_click=lambda: on_submit(input_field.value or "0"))
125
+
126
+ def _add_log(self, message: str, color: str = "#fff"):
127
+ timestamp = datetime.now().strftime("%H:%M:%S")
128
+ with self.log_area:
129
+ ui.label(f"[{timestamp}] {message}").style(f'color: {color}; white-space: pre-wrap')
130
+
131
+ def _validate_and_submit(self, value: str, on_submit: Callable[[int], None]):
132
+ try:
133
+ if value.isdigit():
134
+ user_id = int(value)
135
+ on_submit(user_id if user_id < 9999999 else 0)
136
+ else:
137
+ self.bot_error_log("Please enter a valid number.")
138
+ except Exception as e:
139
+ self.bot_error_log(f"Error: {str(e)}")
view/settings.py ADDED
@@ -0,0 +1,31 @@
1
+ from nicegui import ui
2
+
3
+ class SettingsPanel:
4
+ def __init__(self):
5
+ with ui.column().classes('items-center q-pa-xl'):
6
+
7
+ # Header
8
+ ui.label("Settings").style('font-size: 32px; font-weight: bold; color: #2e3b4e')
9
+
10
+ # Settings card
11
+ with ui.card().style('max-width: 400px; width: 100%; padding: 20px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1)'):
12
+
13
+ self._setting_group("Account Settings", [
14
+ ("Enable notifications", True),
15
+ ("Allow email updates", False),
16
+ ])
17
+
18
+ ui.separator().style("margin: 20px 0")
19
+
20
+ self._setting_group("Privacy", [
21
+ ("Location tracking", False),
22
+ ("Data sharing", True),
23
+ ])
24
+
25
+ def _setting_group(self, title: str, settings: list[tuple[str, bool]]):
26
+ ui.label(title).style('font-size: 18px; font-weight: 500; margin-bottom: 10px; color: #333')
27
+
28
+ for label_text, default_value in settings:
29
+ with ui.row().style('justify-content: space-between; align-items: center; margin-bottom: 12px'):
30
+ ui.label(label_text).style("font-size: 14px; color: #555")
31
+ ui.switch(value=default_value)
File without changes
File without changes
File without changes
@@ -1,80 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from dataclasses import dataclass, field
4
-
5
-
6
- @dataclass
7
- class SubCategory:
8
- id: int = 0
9
- name: str = ''
10
- subCategories: list[any] = field(default_factory=list)
11
-
12
-
13
- @dataclass
14
- class Category:
15
- id: int = 0
16
- name: str = ''
17
- subCategories: list[SubCategory] = field(default_factory=list)
18
-
19
-
20
- @dataclass
21
- class Capability:
22
- bookSearchParams: list[str] = field(default_factory=list)
23
- categories: list[Category] = field(default_factory=list)
24
-
25
-
26
- @dataclass
27
- class FieldOption:
28
- hint: str = ''
29
- name: str = ''
30
- order: int = 0
31
- value: int = 0
32
-
33
-
34
- @dataclass
35
- class Field:
36
- advanced: bool = False
37
- hidden: str = ''
38
- isFloat: bool = False
39
- name: str = ''
40
- order: int = 0
41
- privacy: str = ''
42
- type: str = ''
43
- value: str = ''
44
- helpText: str = ''
45
- label: str = ''
46
- selectOptionsProviderAction: str = ''
47
- selectOptions: list[FieldOption] = field(default_factory=list)
48
- unit: str = ''
49
-
50
-
51
- @dataclass
52
- class Indexer:
53
- added: str = ''
54
- appProfileId: int = 0
55
- capabilities: Capability = field(default_factory=Capability)
56
- configContract: str = ''
57
- definitionName: str = ''
58
- description: str = ''
59
- downloadClientId: int = 0
60
- enable: bool = True
61
- encoding: str = ''
62
- fields: list[Field] = field(default_factory=list)
63
- id: int = 0
64
- implementation: str = ''
65
- implementationName: str = ''
66
- indexerUrls: list[str] = field(default_factory=list)
67
- infoLink: str = ''
68
- language: str = ''
69
- legacyUrls: list[str] = field(default_factory=list)
70
- name: str = ''
71
- priority: int = 0
72
- privacy: str = ''
73
- protocol: str = ''
74
- redirect: bool = False
75
- sortName: str = ''
76
- supportsPagination: bool = False
77
- supportsRedirect: bool = False
78
- supportsRss: bool = False
79
- supportsSearch: bool = False
80
- tags: list[int] = field(default_factory=list)
@@ -1,39 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from dataclasses import dataclass, field
4
-
5
-
6
- @dataclass
7
- class Search:
8
- """
9
- Get a result from the endpoint /search?query
10
-
11
- """
12
-
13
- age: int = 0
14
- ageHours: float = 0.0
15
- ageMinutes: float = 0.0
16
- categories: list[dict[str, any]] = field(default_factory=list)
17
- downloadUrl: None | str = None
18
- fileName: None | str = None
19
- guid: None | str = None
20
- imdbId: int = 0
21
- indexer: None | str = None
22
- indexerFlags: list[str] = field(default_factory=list)
23
- indexerId: int = 0
24
- infoUrl: None | str = None
25
- leechers: int = 0
26
- protocol: str = "torrent"
27
- publishDate: None | str = None
28
- seeders: int = 0
29
- size: int = 0
30
- sortTitle: None | str = None
31
- title: None | str = None
32
- tmdbId: int = 0
33
- tvMazeId: int = 0
34
- tvdbId: int = 0
35
- files: int | None = None
36
- grabs: int | None = None
37
- posterUrl: str | None = None
38
- infoHash: str | None = None
39
-
@@ -1,42 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from dataclasses import dataclass, field
4
-
5
-
6
- @dataclass
7
- class SelectOption:
8
- hint: str
9
- name: str
10
- order: int
11
- value: int
12
-
13
-
14
- @dataclass
15
- class Field:
16
- advanced: bool = False
17
- isFloat: bool = False
18
- label: str = ""
19
- name: str = ""
20
- order: int = 0
21
- privacy: str = "normal"
22
- type: str = "textbox"
23
- value: str | int | bool | None = None
24
- helpText: str | None = None
25
- selectOptions: list[SelectOption] | None = None
26
-
27
-
28
- @dataclass
29
- class TorrentClientConfig:
30
- categories: list[str] = field(default_factory=list)
31
- configContract: str | None = None
32
- enable: bool = True
33
- fields: list[Field] = field(default_factory=list)
34
- id: int = 0
35
- implementation: str | None = None
36
- implementationName: str | None = None
37
- infoLink: str | None = None
38
- name: str | None = None
39
- priority: int = 0
40
- protocol: str | None = None
41
- supportsCategories: bool = False
42
- tags: list[str] = field(default_factory=list)
@@ -1,96 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- import os.path
3
-
4
- import httpx
5
-
6
- from common.external_services.Pw.core.models.torrent_client_config import (
7
- TorrentClientConfig,
8
- )
9
- from common.external_services.Pw.core.models.indexers import Indexer
10
- from common.external_services.Pw.core.models.search import Search
11
- from common.external_services.sessions.session import MyHttp
12
- from common.external_services.sessions.agents import Agent
13
- from common import config_settings
14
-
15
- from view import custom_console
16
-
17
- class PwAPI(MyHttp):
18
-
19
- def __init__(self):
20
- """
21
- Initialize the PwApi instance
22
- """
23
- headers = Agent.headers()
24
- headers.update(
25
- {"X-Api-Key": config_settings.options.PW_API_KEY, "Content-Type": "application/json"}
26
- )
27
-
28
- super().__init__(headers)
29
- self.http_client = self.session
30
- self.base_url = config_settings.options.PW_URL
31
- self.api_key = config_settings.options.PW_API_KEY
32
- self.dataclass = {f"{self.base_url}/indexer": Indexer}
33
-
34
- if not config_settings.options.PW_URL:
35
- custom_console.bot_question_log("No PW_URL provided\n")
36
- exit(1)
37
-
38
- if not config_settings.options.PW_API_KEY:
39
- custom_console.bot_question_log("No PW_API_KEY provided\n")
40
- exit(1)
41
-
42
- def get_indexers(self) -> list[type[[Indexer]]]:
43
- """Get all indexers."""
44
-
45
- response = self.get_url(url=f"{self.base_url}/indexer", params={})
46
-
47
- if response.status_code == 200:
48
- indexers_list = response.json()
49
- return [Indexer(**indexer) for indexer in indexers_list]
50
- else:
51
- return [Indexer]
52
-
53
- def get_torrent_url(self, url: str, filename: str)-> httpx.Response :
54
- return self.get_url(url=url)
55
-
56
- def search(self, query: str) -> list[Search] | None:
57
- """Get search queue."""
58
-
59
- params = {"query": query}
60
- url = f"{self.base_url}/search?"
61
-
62
- response = self.get_url(url=url, params=params)
63
- if response:
64
- if response.status_code == 200:
65
- results_list = response.json()
66
- return [Search(**result) for result in results_list]
67
- else:
68
- return []
69
- return None
70
-
71
- def get_torrent_client_ids(self) -> list["TorrentClientConfig"]:
72
- """Get a list of torrent client configurations"""
73
-
74
- url = f"{self.base_url}/downloadclient"
75
- response = self.get_url(url=url, params={})
76
-
77
- if response.status_code == 200:
78
- configurations_list = response.json()
79
- return [
80
- TorrentClientConfig(**client_config)
81
- for client_config in configurations_list
82
- ]
83
- else:
84
- return []
85
-
86
- def send_torrent_to_client(self, payload):
87
- """send torrent to client"""
88
-
89
- url = f"{self.base_url}/downloadclient/1"
90
- response = self.get_url(url=url, body=payload, get_method=False)
91
-
92
- # TODO: Test again - get_url() updated 21/09/2024
93
- if response.status_code == 202 or response.status_code == 200:
94
- result = response.json()
95
- else:
96
- return []
@@ -1,63 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- import argparse
4
- import os
5
-
6
- from common.external_services.Pw.pw_service import PwService
7
- from common.utility import ManageTitles
8
- from common.database import Database
9
- from common import config_settings
10
- from unit3dup.media import Media
11
-
12
-
13
- from qbittorrent import Client
14
- from view import custom_console
15
-
16
- class PwManager:
17
-
18
- def __init__(self,cli: argparse.Namespace):
19
- # Keyword
20
- self.search = cli.pw
21
-
22
- # filename for the new download
23
- self.filename = ManageTitles.normalize_filename(self.search)
24
-
25
- # Select the tracker database
26
- self.database = Database(db_file=cli.tracker)
27
-
28
-
29
- def process(self):
30
-
31
- # a new qbittorrent instance
32
- qb = Client(f"http://{config_settings.torrent_client_config.QBIT_HOST}:{config_settings.torrent_client_config.QBIT_PORT}/")
33
- # a new pw instance
34
- pw_service = PwService()
35
- # Query the indexers
36
- search = pw_service.search(query=self.search)
37
-
38
- content = []
39
- if search:
40
- for index, s in enumerate(search):
41
- if s.seeders > 0:
42
- category = s.categories[0]['name']
43
- if category in ['Movies','TV','TV/HD']:
44
- content.append(s)
45
- custom_console.bot_process_table_pw(content=content)
46
-
47
-
48
- for c in content:
49
- test = Media(folder=f"c:\\test\\{c.fileName}", subfolder=f"c:\\test\\{c.fileName}")
50
- print(f"[Prowlarr] {c.fileName}")
51
- self.database.search(test.guess_title)
52
-
53
-
54
- """"
55
- qb.login(username=config_settings.torrent_client_config.QBIT_USER,
56
- password=config_settings.torrent_client_config.QBIT_PASS)
57
-
58
- for torrent in content:
59
- filename = str(os.path.join(config_settings.options.PW_TORRENT_ARCHIVE_PATH,torrent.fileName))
60
- print(filename)
61
- magnet = pw_service.get_torrent_from_pw(torrent_url=torrent.downloadUrl,download_filename=filename)
62
- qb.download_from_link(magnet, savepath=config_settings.options.PW_DOWNLOAD_PATH)
63
- """
@@ -1,34 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- from common.external_services.Pw.core.pw_api import PwAPI
4
- from common.external_services.Pw.core.models.indexers import Indexer
5
- from common.external_services.Pw.core.models.search import Search
6
- from common.external_services.Pw.core.models.torrent_client_config import TorrentClientConfig
7
-
8
-
9
- class PwService:
10
-
11
- def __init__(self):
12
- self.pw_api = PwAPI()
13
-
14
- def get_indexers(self) -> [Indexer]:
15
- return self.pw_api.get_indexers()
16
-
17
- def search(self, query: str) -> list[Search]:
18
- return self.pw_api.search(query=query)
19
-
20
- def get_torrent_client_ids(self) -> list[TorrentClientConfig]:
21
- return self.pw_api.get_torrent_client_ids()
22
-
23
- def send_torrent_to_client(self, payload):
24
- return self.pw_api.send_torrent_to_client(payload)
25
-
26
- def get_torrent_from_pw(self, torrent_url: str, download_filename: str)-> str | None:
27
- response = self.pw_api.get_torrent_url(url=torrent_url, filename=download_filename)
28
- # Redirect (PW)
29
- if response.status_code == 301:
30
- return response.headers.get('Location')
31
- return None
32
-
33
-
34
-