StreamingCommunity 3.3.0__py3-none-any.whl → 3.3.2__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.

Potentially problematic release.


This version of StreamingCommunity might be problematic. Click here for more details.

Files changed (40) hide show
  1. StreamingCommunity/Api/Site/altadefinizione/__init__.py +37 -17
  2. StreamingCommunity/Api/Site/animeunity/__init__.py +36 -16
  3. StreamingCommunity/Api/Site/animeworld/__init__.py +50 -6
  4. StreamingCommunity/Api/Site/crunchyroll/__init__.py +42 -16
  5. StreamingCommunity/Api/Site/crunchyroll/site.py +1 -1
  6. StreamingCommunity/Api/Site/guardaserie/__init__.py +50 -6
  7. StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +43 -5
  8. StreamingCommunity/Api/Site/mediasetinfinity/film.py +1 -1
  9. StreamingCommunity/Api/Site/mediasetinfinity/site.py +6 -3
  10. StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +6 -7
  11. StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +189 -0
  12. StreamingCommunity/Api/Site/raiplay/__init__.py +45 -14
  13. StreamingCommunity/Api/Site/raiplay/series.py +9 -5
  14. StreamingCommunity/Api/Site/raiplay/site.py +6 -4
  15. StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +6 -2
  16. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +7 -2
  17. StreamingCommunity/Api/Site/streamingcommunity/site.py +0 -3
  18. StreamingCommunity/Api/Site/streamingwatch/__init__.py +44 -14
  19. StreamingCommunity/Api/Site/streamingwatch/site.py +0 -3
  20. StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +1 -18
  21. StreamingCommunity/Lib/Downloader/DASH/downloader.py +88 -52
  22. StreamingCommunity/Lib/Downloader/HLS/downloader.py +38 -14
  23. StreamingCommunity/Lib/Downloader/HLS/segments.py +1 -1
  24. StreamingCommunity/Lib/FFmpeg/command.py +66 -7
  25. StreamingCommunity/Lib/FFmpeg/util.py +16 -13
  26. StreamingCommunity/Lib/M3U8/decryptor.py +0 -14
  27. StreamingCommunity/Lib/TMBD/tmdb.py +0 -12
  28. StreamingCommunity/Upload/version.py +1 -1
  29. StreamingCommunity/Util/{bento4_installer.py → installer/bento4_install.py} +15 -33
  30. StreamingCommunity/Util/installer/binary_paths.py +83 -0
  31. StreamingCommunity/Util/{ffmpeg_installer.py → installer/ffmpeg_install.py} +11 -54
  32. StreamingCommunity/Util/logger.py +3 -8
  33. StreamingCommunity/Util/os.py +67 -68
  34. StreamingCommunity/run.py +1 -1
  35. {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/METADATA +313 -498
  36. {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/RECORD +40 -39
  37. {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/WHEEL +0 -0
  38. {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/entry_points.txt +0 -0
  39. {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/licenses/LICENSE +0 -0
  40. {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,5 @@
1
1
  # 16.03.25
2
2
 
3
-
4
3
  import logging
5
4
  from urllib.parse import urlparse
6
5
 
@@ -16,10 +15,6 @@ from StreamingCommunity.Util.config_json import config_manager
16
15
  from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
17
16
 
18
17
 
19
- # Logic class
20
- from .get_license import get_bearer_token
21
-
22
-
23
18
  # Variable
24
19
  max_timeout = config_manager.get_int("REQUESTS", "timeout")
25
20
 
@@ -47,7 +42,10 @@ class GetSerieInfo:
47
42
  return self.serie_id
48
43
 
49
44
  def _get_public_id(self):
50
- """Ottiene il public ID tramite l'API watchlist"""
45
+ self.public_id = "PR1GhC"
46
+ return self.public_id
47
+
48
+ """
51
49
  bearer_token = get_bearer_token()
52
50
  headers = {
53
51
  'authorization': f'Bearer {bearer_token}',
@@ -73,6 +71,7 @@ class GetSerieInfo:
73
71
  else:
74
72
  logging.error(f"Failed to get public ID: {response.status_code}")
75
73
  return None
74
+ """
76
75
 
77
76
  def _get_series_data(self):
78
77
  """Ottiene i dati della serie tramite l'API"""
@@ -290,4 +289,4 @@ class GetSerieInfo:
290
289
  logging.error(f"Episode index {episode_index} is out of range for season {season_number}")
291
290
  return None
292
291
 
293
- return episodes[episode_index]
292
+ return episodes[episode_index]
@@ -1,11 +1,20 @@
1
1
  # 16.03.25
2
2
 
3
+ import json
4
+ import time
3
5
  from urllib.parse import urlencode
4
6
  import xml.etree.ElementTree as ET
5
7
 
6
8
 
7
9
  # External library
8
10
  import httpx
11
+ from rich.console import Console
12
+ try:
13
+ from seleniumbase import Driver
14
+ SELENIUMBASE_AVAILABLE = True
15
+ except ImportError:
16
+ SELENIUMBASE_AVAILABLE = False
17
+ Driver = None
9
18
 
10
19
 
11
20
  # Internal utilities
@@ -14,7 +23,153 @@ from StreamingCommunity.Util.headers import get_headers, get_userAgent
14
23
 
15
24
 
16
25
  # Variable
26
+ console = Console()
17
27
  MAX_TIMEOUT = config_manager.get_int("REQUESTS", "timeout")
28
+ beToken = None
29
+ network_data = []
30
+
31
+
32
+ def save_network_data(data):
33
+ """Save network data and check for beToken"""
34
+ global network_data
35
+
36
+ # Filter only for login API responses
37
+ if data.get('method') == 'Network.responseReceived':
38
+ params = data.get('params', {})
39
+ response = params.get('response', {})
40
+ url = response.get('url', '')
41
+
42
+ if "persona/login/v2.0" in url and params.get('type') != 'Preflight':
43
+ network_data.append(data)
44
+ console.print(f"[green]Found login API request to: {url}")
45
+
46
+
47
+ def generate_betoken(username: str, password: str, sleep_action: float = 1.0) -> str:
48
+ """Generate beToken using browser automation"""
49
+
50
+ if not SELENIUMBASE_AVAILABLE:
51
+ console.print("[red]Error: seleniumbase is not installed. Cannot perform browser login.")
52
+ console.print("[yellow]Install seleniumbase with: pip install seleniumbase")
53
+ return None
54
+
55
+ if not Driver:
56
+ console.print("[red]Error: seleniumbase Driver is not available.")
57
+ return None
58
+
59
+ driver = Driver(uc=True, uc_cdp_events=True, incognito=True, headless=True)
60
+
61
+ try:
62
+ console.print("[cyan]Launching browser...")
63
+ be_token_holder = {"token": None}
64
+ global network_data
65
+ network_data = [] # Reset network data
66
+
67
+ # Load home page
68
+ console.print("[cyan]Navigating to Mediaset Play...")
69
+ driver.uc_open_with_reconnect("https://www.mediasetplay.mediaset.it", sleep_action)
70
+
71
+ # Add CDP listeners after opening the page
72
+ driver.add_cdp_listener(
73
+ "Network.responseReceived",
74
+ lambda data: save_network_data(data)
75
+ )
76
+ driver.sleep(sleep_action)
77
+
78
+ # Accept privacy policy if present
79
+ try:
80
+ driver.click("#rti-privacy-accept-btn-screen1-id", timeout=3)
81
+ except Exception:
82
+ pass
83
+
84
+ # Click Login using the specific div structure
85
+ console.print("[cyan]Clicking login button...")
86
+ try:
87
+ driver.click('div.dwv_v span:contains("Login")', timeout=5)
88
+ except Exception as e:
89
+ print(f"Error clicking login: {e}")
90
+ try:
91
+ driver.click('div.dwv_v', timeout=3)
92
+ except Exception:
93
+ pass
94
+
95
+ driver.sleep(sleep_action)
96
+
97
+ # Click "Accedi con email e password" using the specific input
98
+ console.print("[cyan]Clicking email/password login...")
99
+ try:
100
+ driver.click('input.gigya-input-submit[value="Accedi con email e password"]', timeout=5)
101
+ except Exception as e:
102
+ print(f"Error clicking email login: {e}")
103
+ return None
104
+
105
+ driver.sleep(sleep_action)
106
+
107
+ # Fill login credentials using specific IDs
108
+ console.print("[cyan]Filling login credentials...")
109
+ try:
110
+ email_input = 'input[name="username"].gigya-input-text'
111
+ driver.wait_for_element(email_input, timeout=5)
112
+ driver.type(email_input, username)
113
+
114
+ password_input = 'input[name="password"].gigya-input-password'
115
+ driver.wait_for_element(password_input, timeout=5)
116
+ driver.type(password_input, password)
117
+
118
+ except Exception as e:
119
+ print(f"Error filling credentials: {e}")
120
+ return None
121
+
122
+ driver.sleep(sleep_action)
123
+
124
+ # Click Continue/Procedi using the submit button
125
+ console.print("[cyan]Clicking continue button...")
126
+ try:
127
+ driver.click('input.gigya-input-submit[type="submit"][value="Continua"]', timeout=5)
128
+ except Exception as e:
129
+ print(f"Error clicking continue: {e}")
130
+ return None
131
+
132
+ # Wait for login response and parse network data
133
+ console.print("[cyan]Waiting for login response...")
134
+ for attempt in range(30):
135
+ driver.sleep(0.3)
136
+
137
+ # Check network data for beToken - skip preflight requests
138
+ for data in network_data:
139
+ if data.get('method') == 'Network.responseReceived':
140
+ params = data.get('params', {})
141
+ response = params.get('response', {})
142
+ url = response.get('url', '')
143
+ request_type = params.get('type', '')
144
+
145
+ if "persona/login/v2.0" in url and request_type != 'Preflight':
146
+ request_id = params.get('requestId')
147
+
148
+ if request_id:
149
+ try:
150
+ response_body = driver.execute_cdp_cmd(
151
+ 'Network.getResponseBody',
152
+ {'requestId': request_id}
153
+ )
154
+ body = response_body.get('body', '')
155
+
156
+ if body:
157
+ response_data = json.loads(body)
158
+ be_token = response_data.get("response", {}).get("beToken")
159
+
160
+ if be_token:
161
+ be_token_holder["token"] = be_token
162
+ console.print("[green]Login successful! BeToken found!")
163
+ return be_token
164
+
165
+ except Exception:
166
+ continue
167
+
168
+ console.print(f"[yellow]Login completed. Total network events captured: {len(network_data)}")
169
+ return be_token_holder["token"]
170
+
171
+ finally:
172
+ driver.quit()
18
173
 
19
174
 
20
175
  def get_bearer_token():
@@ -24,8 +179,37 @@ def get_bearer_token():
24
179
  Returns:
25
180
  str: The bearer token string.
26
181
  """
182
+ global beToken
183
+
184
+ # Read beToken from config if already present
185
+ beToken = config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
186
+ if beToken is not None and len(beToken) != 0:
187
+ return beToken
188
+
189
+ username = config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("username", "")
190
+ password = config_manager.get_dict("SITE_LOGIN", "mediasetinfinity").get("password", "")
191
+
192
+ if username and password:
193
+ if not SELENIUMBASE_AVAILABLE:
194
+ console.print("[yellow]Warning: seleniumbase not available. Cannot perform automatic login.")
195
+ console.print("[yellow]Please manually obtain beToken and set it in config.")
196
+ return config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
197
+
198
+ beToken = generate_betoken(username, password)
199
+
200
+ if beToken is not None:
201
+
202
+ # Save current beToken
203
+ current_value = config_manager.get("SITE_LOGIN", "mediasetinfinity", dict)
204
+ current_value["beToken"] = beToken
205
+ config_manager.set_key("SITE_LOGIN", "mediasetinfinity", current_value)
206
+ config_manager.save_config()
207
+
208
+ return beToken
209
+
27
210
  return config_manager.get_dict("SITE_LOGIN", "mediasetinfinity")["beToken"]
28
211
 
212
+
29
213
  def get_playback_url(BEARER_TOKEN, CONTENT_ID):
30
214
  """
31
215
  Gets the playback URL for the specified content.
@@ -70,6 +254,7 @@ def get_playback_url(BEARER_TOKEN, CONTENT_ID):
70
254
  except Exception as e:
71
255
  raise RuntimeError(f"Failed to get playback URL: {e}")
72
256
 
257
+
73
258
  def parse_tracking_data(tracking_value):
74
259
  """
75
260
  Parses the trackingData string into a dictionary.
@@ -82,6 +267,7 @@ def parse_tracking_data(tracking_value):
82
267
  """
83
268
  return dict(item.split('=', 1) for item in tracking_value.split('|') if '=' in item)
84
269
 
270
+
85
271
  def parse_smil_for_tracking_and_video(smil_xml):
86
272
  """
87
273
  Extracts all video_src and trackingData pairs from the SMIL.
@@ -122,6 +308,7 @@ def parse_smil_for_tracking_and_video(smil_xml):
122
308
 
123
309
  return results
124
310
 
311
+
125
312
  def get_tracking_info(BEARER_TOKEN, PLAYBACK_JSON):
126
313
  """
127
314
  Retrieves tracking information from the playback JSON.
@@ -158,12 +345,14 @@ def get_tracking_info(BEARER_TOKEN, PLAYBACK_JSON):
158
345
  response.raise_for_status()
159
346
 
160
347
  smil_xml = response.text
348
+ time.sleep(0.2)
161
349
  results = parse_smil_for_tracking_and_video(smil_xml)
162
350
  return results
163
351
 
164
352
  except Exception:
165
353
  return None
166
354
 
355
+
167
356
  def generate_license_url(BEARER_TOKEN, tracking_info):
168
357
  """
169
358
  Generates the URL to obtain the Widevine license.
@@ -1,5 +1,8 @@
1
1
  # 21.05.24
2
2
 
3
+ import sys
4
+ import subprocess
5
+
3
6
 
4
7
  # External library
5
8
  from rich.console import Console
@@ -33,8 +36,40 @@ console = Console()
33
36
  def get_user_input(string_to_search: str = None):
34
37
  """
35
38
  Asks the user to input a search term.
39
+ Handles both Telegram bot input and direct input.
40
+ If string_to_search is provided, it's returned directly (after stripping).
36
41
  """
37
- return msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
42
+ if string_to_search is not None:
43
+ return string_to_search.strip()
44
+
45
+ if site_constant.TELEGRAM_BOT:
46
+ bot = get_bot_instance()
47
+ user_response = bot.ask(
48
+ "key_search", # Request type
49
+ "Enter the search term\nor type 'back' to return to the menu: ",
50
+ None
51
+ )
52
+
53
+ if user_response is None:
54
+ bot.send_message("Timeout: No search term entered.", None)
55
+ return None
56
+
57
+ if user_response.lower() == 'back':
58
+ bot.send_message("Returning to the main menu...", None)
59
+
60
+ try:
61
+ # Restart the script
62
+ subprocess.Popen([sys.executable] + sys.argv)
63
+ sys.exit()
64
+
65
+ except Exception as e:
66
+ bot.send_message(f"Error during restart attempt: {e}", None)
67
+ return None # Return None if restart fails
68
+
69
+ return user_response.strip()
70
+
71
+ else:
72
+ return msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
38
73
 
39
74
  def process_search_result(select_title, selections=None):
40
75
  """
@@ -44,6 +79,9 @@ def process_search_result(select_title, selections=None):
44
79
  select_title (MediaItem): The selected media item
45
80
  selections (dict, optional): Dictionary containing selection inputs that bypass manual input
46
81
  {'season': season_selection, 'episode': episode_selection}
82
+
83
+ Returns:
84
+ bool: True if processing was successful, False otherwise
47
85
  """
48
86
  if not select_title:
49
87
  if site_constant.TELEGRAM_BOT:
@@ -51,7 +89,7 @@ def process_search_result(select_title, selections=None):
51
89
  bot.send_message("No title selected or selection cancelled.", None)
52
90
  else:
53
91
  console.print("[yellow]No title selected or selection cancelled.")
54
- return
92
+ return False
55
93
 
56
94
  if select_title.type == 'tv':
57
95
  season_selection = None
@@ -62,24 +100,16 @@ def process_search_result(select_title, selections=None):
62
100
  episode_selection = selections.get('episode')
63
101
 
64
102
  download_series(select_title, season_selection, episode_selection)
103
+ return True
65
104
 
66
105
  else:
67
106
  download_film(select_title)
107
+ return True
68
108
 
69
109
  def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
70
110
  """
71
111
  Main function of the application for search.
72
112
 
73
- Parameters:
74
- string_to_search (str, optional): String to search for
75
- get_onlyDatabase (bool, optional): If True, return only the database object
76
- direct_item (dict, optional): Direct item to process (bypass search)
77
- selections (dict, optional): Dictionary containing selection inputs that bypass manual input
78
- {'season': season_selection, 'episode': episode_selection}
79
- """
80
- """
81
- Main function of the application for search.
82
-
83
113
  Parameters:
84
114
  string_to_search (str, optional): String to search for
85
115
  get_onlyDatabase (bool, optional): If True, return only the database object
@@ -94,7 +124,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
94
124
  if direct_item:
95
125
  select_title = MediaItem(**direct_item)
96
126
  process_search_result(select_title, selections)
97
- return
127
+ return True
98
128
 
99
129
  # Get the user input for the search term
100
130
  actual_search_query = get_user_input(string_to_search)
@@ -116,6 +146,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
116
146
  if len_database > 0:
117
147
  select_title = get_select_title(table_show_manager, media_search_manager, len_database)
118
148
  process_search_result(select_title, selections)
149
+ return True
119
150
 
120
151
  else:
121
152
  if bot:
@@ -125,4 +156,4 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
125
156
 
126
157
  # Do not call search() recursively here to avoid infinite loops on no results.
127
158
  # The flow should return to the caller (e.g., main menu in run.py).
128
- return
159
+ return
@@ -11,7 +11,7 @@ from rich.prompt import Prompt
11
11
 
12
12
 
13
13
  # Internal utilities
14
- from StreamingCommunity.Util.headers import get_headers
14
+ from StreamingCommunity.Util.headers import get_headers, get_userAgent
15
15
  from StreamingCommunity.Util.os import get_wvd_path
16
16
  from StreamingCommunity.Util.message import start_message
17
17
 
@@ -92,17 +92,21 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
92
92
  console.print(f"[bold red] CDM file not found or invalid path: {cdm_device_path}[/bold red]")
93
93
  sys.exit(0)
94
94
 
95
- license_url = generate_license_url(obj_episode.mpd_id)
95
+ full_license_url = generate_license_url(obj_episode.mpd_id)
96
+ license_headers = {
97
+ 'nv-authorizations': full_license_url.split("?")[1].split("=")[1],
98
+ 'user-agent': get_userAgent(),
99
+ }
96
100
 
97
101
  dash_process = DASH_Downloader(
98
102
  cdm_device=cdm_device_path,
99
- license_url=license_url,
103
+ license_url=full_license_url.split("?")[0],
100
104
  mpd_url=master_playlist,
101
105
  output_path=os.path.join(mp4_path, mp4_name),
102
106
  )
103
107
  dash_process.parse_manifest(custom_headers=get_headers())
104
108
 
105
- if dash_process.download_and_decrypt():
109
+ if dash_process.download_and_decrypt(custom_headers=license_headers):
106
110
  dash_process.finalize_output()
107
111
 
108
112
  # Get final output path and status
@@ -202,4 +206,4 @@ def download_series(select_season: MediaItem, season_selection: str = None, epis
202
206
  if len(list_season_select) > 1 or index_season_selected == "*":
203
207
  download_episode(season_number, scrape_serie, download_all=True)
204
208
  else:
205
- download_episode(season_number, scrape_serie, download_all=False, episode_selection=episode_selection)
209
+ download_episode(season_number, scrape_serie, download_all=False, episode_selection=episode_selection)
@@ -42,7 +42,7 @@ def determine_media_type(item):
42
42
 
43
43
  scraper = GetSerieInfo(program_name)
44
44
  scraper.collect_info_title()
45
- return "tv" if scraper.getNumberSeason() > 0 else "film"
45
+ return scraper.prog_tipology, scraper.prog_description, scraper.prog_year
46
46
 
47
47
  except Exception as e:
48
48
  console.print(f"[red]Error determining media type: {e}[/red]")
@@ -91,18 +91,20 @@ def title_search(query: str) -> int:
91
91
  return 0
92
92
 
93
93
  # Limit to only 15 results for performance
94
- data = response.json().get('agg').get('titoli').get('cards')
95
- data = data[:15] if len(data) > 15 else data
94
+ data = response.json().get('agg').get('titoli').get('cards')[:15]
96
95
 
97
96
  # Process each item and add to media manager
98
97
  for item in data:
98
+ media_type, prog_description, prog_year = determine_media_type(item)
99
99
  media_search_manager.add_media({
100
100
  'id': item.get('id', ''),
101
101
  'name': item.get('titolo', ''),
102
- 'type': determine_media_type(item),
102
+ 'type': media_type,
103
103
  'path_id': item.get('path_id', ''),
104
104
  'url': f"https://www.raiplay.it{item.get('url', '')}",
105
105
  'image': f"https://www.raiplay.it{item.get('immagine', '')}",
106
+ 'desc': prog_description,
107
+ 'year': prog_year
106
108
  })
107
109
 
108
110
  return media_search_manager.get_length()
@@ -23,6 +23,9 @@ class GetSerieInfo:
23
23
  self.base_url = "https://www.raiplay.it"
24
24
  self.program_name = program_name
25
25
  self.series_name = program_name
26
+ self.prog_tipology = None
27
+ self.prog_description = None
28
+ self.prog_year = None
26
29
  self.seasons_manager = SeasonManager()
27
30
 
28
31
  def collect_info_title(self) -> None:
@@ -38,6 +41,9 @@ class GetSerieInfo:
38
41
 
39
42
  response.raise_for_status()
40
43
  json_data = response.json()
44
+ self.prog_tipology = "tv" if "tv" in json_data.get('track_info').get('typology') else "film"
45
+ self.prog_description = json_data.get('program_info', '').get('vanity', '')
46
+ self.prog_year = json_data.get('program_info', '').get('year', '')
41
47
 
42
48
  # Look for seasons in the 'blocks' property
43
49
  for block in json_data.get('blocks', []):
@@ -59,8 +65,6 @@ class GetSerieInfo:
59
65
  for season_set in block.get('sets', []):
60
66
  self._add_season(season_set, block.get('id'))
61
67
 
62
- except httpx.HTTPError as e:
63
- logging.error(f"Error collecting series info: {e}")
64
68
  except Exception as e:
65
69
  logging.error(f"Unexpected error collecting series info: {e}")
66
70
 
@@ -78,6 +78,8 @@ def process_search_result(select_title, selections=None):
78
78
  select_title (MediaItem): The selected media item. Can be None if selection fails.
79
79
  selections (dict, optional): Dictionary containing selection inputs that bypass manual input
80
80
  e.g., {'season': season_selection, 'episode': episode_selection}
81
+ Returns:
82
+ bool: True if processing was successful, False otherwise
81
83
  """
82
84
  if not select_title:
83
85
  if site_constant.TELEGRAM_BOT:
@@ -85,7 +87,7 @@ def process_search_result(select_title, selections=None):
85
87
  bot.send_message("No title selected or selection cancelled.", None)
86
88
  else:
87
89
  console.print("[yellow]No title selected or selection cancelled.")
88
- return
90
+ return False
89
91
 
90
92
  if select_title.type == 'tv':
91
93
  season_selection = None
@@ -96,9 +98,11 @@ def process_search_result(select_title, selections=None):
96
98
  episode_selection = selections.get('episode')
97
99
 
98
100
  download_series(select_title, season_selection, episode_selection)
101
+ return True
99
102
 
100
103
  else:
101
104
  download_film(select_title)
105
+ return True
102
106
 
103
107
  def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
104
108
  """
@@ -119,7 +123,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
119
123
  if direct_item:
120
124
  select_title_obj = MediaItem(**direct_item)
121
125
  process_search_result(select_title_obj, selections)
122
- return
126
+ return True
123
127
 
124
128
  actual_search_query = get_user_input(string_to_search)
125
129
 
@@ -140,6 +144,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
140
144
  if len_database > 0:
141
145
  select_title = get_select_title(table_show_manager, media_search_manager, len_database)
142
146
  process_search_result(select_title, selections)
147
+ return True
143
148
 
144
149
  else:
145
150
  if bot:
@@ -59,9 +59,6 @@ def title_search(query: str) -> int:
59
59
  version = json.loads(soup.find('div', {'id': "app"}).get("data-page"))['version']
60
60
 
61
61
  except Exception as e:
62
- if "WinError" in str(e) or "Errno" in str(e):
63
- console.print("\n[bold yellow]Please make sure you have enabled and configured a valid proxy.[/bold yellow]")
64
-
65
62
  console.print(f"[red]Site: {site_constant.SITE_NAME} version, request error: {e}")
66
63
  return 0
67
64
 
@@ -1,5 +1,8 @@
1
1
  # 29.04.25
2
2
 
3
+ import sys
4
+ import subprocess
5
+
3
6
  # External library
4
7
  from rich.console import Console
5
8
  from rich.prompt import Prompt
@@ -33,9 +36,39 @@ def get_user_input(string_to_search: str = None):
33
36
  """
34
37
  Asks the user to input a search term.
35
38
  Handles both Telegram bot input and direct input.
39
+ If string_to_search is provided, it's returned directly (after stripping).
36
40
  """
37
- string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
38
- return string_to_search
41
+ if string_to_search is not None:
42
+ return string_to_search.strip()
43
+
44
+ if site_constant.TELEGRAM_BOT:
45
+ bot = get_bot_instance()
46
+ user_response = bot.ask(
47
+ "key_search", # Request type
48
+ "Enter the search term\nor type 'back' to return to the menu: ",
49
+ None
50
+ )
51
+
52
+ if user_response is None:
53
+ bot.send_message("Timeout: No search term entered.", None)
54
+ return None
55
+
56
+ if user_response.lower() == 'back':
57
+ bot.send_message("Returning to the main menu...", None)
58
+
59
+ try:
60
+ # Restart the script
61
+ subprocess.Popen([sys.executable] + sys.argv)
62
+ sys.exit()
63
+
64
+ except Exception as e:
65
+ bot.send_message(f"Error during restart attempt: {e}", None)
66
+ return None # Return None if restart fails
67
+
68
+ return user_response.strip()
69
+
70
+ else:
71
+ return msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
39
72
 
40
73
  def process_search_result(select_title, selections=None):
41
74
  """
@@ -45,6 +78,9 @@ def process_search_result(select_title, selections=None):
45
78
  select_title (MediaItem): The selected media item
46
79
  selections (dict, optional): Dictionary containing selection inputs that bypass manual input
47
80
  {'season': season_selection, 'episode': episode_selection}
81
+
82
+ Returns:
83
+ bool: True if processing was successful, False otherwise
48
84
  """
49
85
  if not select_title:
50
86
  if site_constant.TELEGRAM_BOT:
@@ -52,7 +88,8 @@ def process_search_result(select_title, selections=None):
52
88
  bot.send_message("No title selected or selection cancelled.", None)
53
89
  else:
54
90
  console.print("[yellow]No title selected or selection cancelled.")
55
- return
91
+ return False
92
+
56
93
  if select_title.type == 'tv':
57
94
  season_selection = None
58
95
  episode_selection = None
@@ -62,24 +99,16 @@ def process_search_result(select_title, selections=None):
62
99
  episode_selection = selections.get('episode')
63
100
 
64
101
  download_series(select_title, season_selection, episode_selection)
102
+ return True
65
103
 
66
104
  else:
67
105
  download_film(select_title)
106
+ return True
68
107
 
69
108
  def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None, selections: dict = None):
70
109
  """
71
110
  Main function of the application for search.
72
111
 
73
- Parameters:
74
- string_to_search (str, optional): String to search for
75
- get_onlyDatabase (bool, optional): If True, return only the database object
76
- direct_item (dict, optional): Direct item to process (bypass search)
77
- selections (dict, optional): Dictionary containing selection inputs that bypass manual input
78
- {'season': season_selection, 'episode': episode_selection}
79
- """
80
- """
81
- Main function of the application for search.
82
-
83
112
  Parameters:
84
113
  string_to_search (str, optional): String to search for
85
114
  get_onlyDatabase (bool, optional): If True, return only the database object
@@ -94,7 +123,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
94
123
  if direct_item:
95
124
  select_title = MediaItem(**direct_item)
96
125
  process_search_result(select_title, selections)
97
- return
126
+ return True
98
127
 
99
128
  # Get the user input for the search term
100
129
  actual_search_query = get_user_input(string_to_search)
@@ -116,6 +145,7 @@ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_
116
145
  if len_database > 0:
117
146
  select_title = get_select_title(table_show_manager, media_search_manager, len_database)
118
147
  process_search_result(select_title, selections)
148
+ return True
119
149
 
120
150
  else:
121
151
  if bot: