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.
- StreamingCommunity/Api/Site/altadefinizione/__init__.py +37 -17
- StreamingCommunity/Api/Site/animeunity/__init__.py +36 -16
- StreamingCommunity/Api/Site/animeworld/__init__.py +50 -6
- StreamingCommunity/Api/Site/crunchyroll/__init__.py +42 -16
- StreamingCommunity/Api/Site/crunchyroll/site.py +1 -1
- StreamingCommunity/Api/Site/guardaserie/__init__.py +50 -6
- StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +43 -5
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +1 -1
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +6 -3
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +6 -7
- StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +189 -0
- StreamingCommunity/Api/Site/raiplay/__init__.py +45 -14
- StreamingCommunity/Api/Site/raiplay/series.py +9 -5
- StreamingCommunity/Api/Site/raiplay/site.py +6 -4
- StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +6 -2
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +7 -2
- StreamingCommunity/Api/Site/streamingcommunity/site.py +0 -3
- StreamingCommunity/Api/Site/streamingwatch/__init__.py +44 -14
- StreamingCommunity/Api/Site/streamingwatch/site.py +0 -3
- StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +1 -18
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +88 -52
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +38 -14
- StreamingCommunity/Lib/Downloader/HLS/segments.py +1 -1
- StreamingCommunity/Lib/FFmpeg/command.py +66 -7
- StreamingCommunity/Lib/FFmpeg/util.py +16 -13
- StreamingCommunity/Lib/M3U8/decryptor.py +0 -14
- StreamingCommunity/Lib/TMBD/tmdb.py +0 -12
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/{bento4_installer.py → installer/bento4_install.py} +15 -33
- StreamingCommunity/Util/installer/binary_paths.py +83 -0
- StreamingCommunity/Util/{ffmpeg_installer.py → installer/ffmpeg_install.py} +11 -54
- StreamingCommunity/Util/logger.py +3 -8
- StreamingCommunity/Util/os.py +67 -68
- StreamingCommunity/run.py +1 -1
- {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/METADATA +313 -498
- {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/RECORD +40 -39
- {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.3.0.dist-info → streamingcommunity-3.3.2.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
|
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':
|
|
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
|
|
38
|
-
|
|
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:
|