StreamingCommunity 3.3.7__py3-none-any.whl → 3.3.9__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 (30) hide show
  1. StreamingCommunity/Api/Player/supervideo.py +1 -1
  2. StreamingCommunity/Api/Site/animeunity/serie.py +1 -1
  3. StreamingCommunity/Api/Site/animeworld/serie.py +1 -1
  4. StreamingCommunity/Api/Site/crunchyroll/film.py +13 -3
  5. StreamingCommunity/Api/Site/crunchyroll/series.py +6 -6
  6. StreamingCommunity/Api/Site/crunchyroll/site.py +13 -8
  7. StreamingCommunity/Api/Site/crunchyroll/util/ScrapeSerie.py +16 -41
  8. StreamingCommunity/Api/Site/crunchyroll/util/get_license.py +107 -101
  9. StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +1 -1
  10. StreamingCommunity/Api/Site/raiplay/series.py +1 -10
  11. StreamingCommunity/Api/Site/raiplay/site.py +5 -13
  12. StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +12 -12
  13. StreamingCommunity/Api/Template/loader.py +13 -4
  14. StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +8 -3
  15. StreamingCommunity/Lib/Downloader/DASH/decrypt.py +1 -0
  16. StreamingCommunity/Lib/Downloader/DASH/downloader.py +9 -2
  17. StreamingCommunity/Lib/Downloader/DASH/parser.py +456 -98
  18. StreamingCommunity/Lib/Downloader/DASH/segments.py +109 -64
  19. StreamingCommunity/Lib/Downloader/HLS/segments.py +261 -355
  20. StreamingCommunity/Lib/Downloader/MP4/downloader.py +1 -1
  21. StreamingCommunity/Lib/FFmpeg/command.py +3 -3
  22. StreamingCommunity/Lib/M3U8/estimator.py +0 -1
  23. StreamingCommunity/Upload/version.py +1 -1
  24. StreamingCommunity/Util/config_json.py +0 -3
  25. {streamingcommunity-3.3.7.dist-info → streamingcommunity-3.3.9.dist-info}/METADATA +1 -3
  26. {streamingcommunity-3.3.7.dist-info → streamingcommunity-3.3.9.dist-info}/RECORD +30 -30
  27. {streamingcommunity-3.3.7.dist-info → streamingcommunity-3.3.9.dist-info}/WHEEL +0 -0
  28. {streamingcommunity-3.3.7.dist-info → streamingcommunity-3.3.9.dist-info}/entry_points.txt +0 -0
  29. {streamingcommunity-3.3.7.dist-info → streamingcommunity-3.3.9.dist-info}/licenses/LICENSE +0 -0
  30. {streamingcommunity-3.3.7.dist-info → streamingcommunity-3.3.9.dist-info}/top_level.txt +0 -0
@@ -42,7 +42,7 @@ class VideoSource:
42
42
  - str: The response content if successful, None otherwise.
43
43
  """
44
44
  try:
45
- response = requests.get(url, headers=self.headers, timeout=MAX_TIMEOUT, impersonate="chrome110", verify=REQUEST_VERIFY)
45
+ response = requests.get(url, headers=self.headers, timeout=MAX_TIMEOUT, impersonate="chrome136", verify=REQUEST_VERIFY)
46
46
  if response.status_code >= 400:
47
47
  logging.error(f"Request failed with status code: {response.status_code}")
48
48
  return None
@@ -104,7 +104,7 @@ def download_series(select_title: MediaItem, season_selection: str = None, episo
104
104
 
105
105
  # Get episode information
106
106
  episoded_count = scrape_serie.get_count_episodes()
107
- console.print(f"[green]Episodes count:[/green] [red]{episoded_count}[/red]")
107
+ console.print(f"\n[green]Episodes count:[/green] [red]{episoded_count}[/red]")
108
108
 
109
109
  # Telegram bot integration
110
110
  if episode_selection is None:
@@ -84,7 +84,7 @@ def download_series(select_title: MediaItem, episode_selection: str = None):
84
84
  episodes = scrape_serie.get_episodes()
85
85
 
86
86
  # Get episode count
87
- console.print(f"[green]Episodes found:[/green] [red]{len(episodes)}[/red]")
87
+ console.print(f"\n[green]Episodes count:[/green] [red]{len(episodes)}[/red]")
88
88
 
89
89
  # Display episodes list and get user selection
90
90
  if episode_selection is None:
@@ -21,7 +21,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
21
21
 
22
22
  # Player
23
23
  from StreamingCommunity import DASH_Downloader
24
- from .util.get_license import get_playback_session, get_auth_token, generate_device_id
24
+ from .util.get_license import get_playback_session, CrunchyrollClient
25
25
 
26
26
 
27
27
  # Variable
@@ -42,14 +42,19 @@ def download_film(select_title: MediaItem) -> str:
42
42
  start_message()
43
43
  console.print(f"\n[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
44
44
 
45
+ # Initialize Crunchyroll client
46
+ client = CrunchyrollClient()
47
+ if not client.start():
48
+ console.print("[bold red]Failed to authenticate with Crunchyroll.[/bold red]")
49
+ return None, True
50
+
45
51
  # Define filename and path for the downloaded video
46
52
  mp4_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
47
53
  mp4_path = os.path.join(site_constant.MOVIE_FOLDER, mp4_name.replace(".mp4", ""))
48
54
 
49
55
  # Generate mpd and license URLs
50
56
  url_id = select_title.get('url').split('/')[-1]
51
- device_id = generate_device_id()
52
- mpd_url, mpd_headers, mpd_list_sub = get_playback_session(get_auth_token(device_id), device_id, url_id)
57
+ mpd_url, mpd_headers, mpd_list_sub = get_playback_session(client, url_id)
53
58
  parsed_url = urlparse(mpd_url)
54
59
  query_params = parse_qs(parsed_url.query)
55
60
 
@@ -82,4 +87,9 @@ def download_film(select_title: MediaItem) -> str:
82
87
  except Exception:
83
88
  pass
84
89
 
90
+ # Delete stream after download
91
+ token = query_params['playbackGuid'][0]
92
+ if token:
93
+ client.delete_active_stream(url_id, token)
94
+
85
95
  return status['path'], status['stopped']
@@ -16,7 +16,7 @@ from StreamingCommunity.Util.os import os_manager, get_wvd_path
16
16
 
17
17
 
18
18
  # Logic class
19
- from .util.ScrapeSerie import GetSerieInfo, delete_stream_episode
19
+ from .util.ScrapeSerie import GetSerieInfo
20
20
  from StreamingCommunity.Api.Template.Util import (
21
21
  manage_selection,
22
22
  map_episode_title,
@@ -30,7 +30,7 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
30
30
 
31
31
  # Player
32
32
  from StreamingCommunity import DASH_Downloader
33
- from .util.get_license import get_playback_session, get_auth_token, generate_device_id
33
+ from .util.get_license import get_playback_session
34
34
 
35
35
 
36
36
  # Variable
@@ -52,6 +52,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
52
52
  - bool: Whether download was stopped
53
53
  """
54
54
  start_message()
55
+ client = scrape_serie.client
55
56
 
56
57
  # Get episode information
57
58
  obj_episode = scrape_serie.selectEpisode(index_season_selected, index_episode_selected-1)
@@ -64,10 +65,8 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
64
65
 
65
66
  # Generate mpd and license URLs
66
67
  url_id = obj_episode.get('url').split('/')[-1]
67
- device_id = generate_device_id()
68
- token_mpd = get_auth_token(device_id)
69
68
 
70
- mpd_url, mpd_headers, mpd_list_sub = get_playback_session(token_mpd, device_id, url_id)
69
+ mpd_url, mpd_headers, mpd_list_sub = get_playback_session(client, url_id)
71
70
  parsed_url = urlparse(mpd_url)
72
71
  query_params = parse_qs(parsed_url.query)
73
72
 
@@ -101,7 +100,8 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
101
100
  pass
102
101
 
103
102
  # Delete episode stream
104
- delete_stream_episode(url_id, query_params['playbackGuid'][0], mpd_headers)
103
+ token = query_params['playbackGuid'][0]
104
+ client.delete_active_stream(url_id, token)
105
105
 
106
106
  return status['path'], status['stopped']
107
107
 
@@ -10,14 +10,13 @@ from rich.console import Console
10
10
 
11
11
  # Internal utilities
12
12
  from StreamingCommunity.Util.config_json import config_manager
13
- from StreamingCommunity.Util.headers import get_headers
14
13
  from StreamingCommunity.Util.table import TVShowManager
15
14
 
16
15
 
17
16
  # Logic class
18
17
  from StreamingCommunity.Api.Template.config_loader import site_constant
19
18
  from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
20
- from .util.get_license import get_auth_token, generate_device_id
19
+ from .util.get_license import CrunchyrollClient
21
20
 
22
21
 
23
22
  # Variable
@@ -40,9 +39,16 @@ def title_search(query: str) -> int:
40
39
  media_search_manager.clear()
41
40
  table_show_manager.clear()
42
41
 
43
- # Check if x_cr_tab_id or etp_rt is present
44
- if config_manager.get_dict("SITE_LOGIN", "crunchyroll")['x_cr_tab_id'] is None or config_manager.get_dict("SITE_LOGIN", "crunchyroll")['x_cr_tab_id'] == "" or config_manager.get_dict("SITE_LOGIN", "crunchyroll")['etp_rt'] is None or config_manager.get_dict("SITE_LOGIN", "crunchyroll")['etp_rt'] == "":
45
- console.print("[bold red] x_cr_tab_id or etp_rt is missing or empty.[/bold red]")
42
+ # Check if device_id or etp_rt is present
43
+ config = config_manager.get_dict("SITE_LOGIN", "crunchyroll")
44
+ if not config.get('device_id') or not config.get('etp_rt'):
45
+ console.print("[bold red] device_id or etp_rt is missing or empty in config.json.[/bold red]")
46
+ sys.exit(0)
47
+
48
+ # Initialize Crunchyroll client
49
+ client = CrunchyrollClient()
50
+ if not client.start():
51
+ console.print("[bold red] Failed to authenticate with Crunchyroll.[/bold red]")
46
52
  sys.exit(0)
47
53
 
48
54
  # Build new Crunchyroll API search URL
@@ -57,8 +63,7 @@ def title_search(query: str) -> int:
57
63
  "locale": "it-IT"
58
64
  }
59
65
 
60
- headers = get_headers()
61
- headers['authorization'] = f"Bearer {get_auth_token(generate_device_id()).access_token}"
66
+ headers = client._get_headers()
62
67
 
63
68
  console.print(f"[cyan]Search url: [yellow]{api_url}")
64
69
 
@@ -68,7 +73,7 @@ def title_search(query: str) -> int:
68
73
  params=params,
69
74
  headers=headers,
70
75
  timeout=max_timeout,
71
- impersonate="chrome110"
76
+ impersonate="chrome136"
72
77
  )
73
78
  response.raise_for_status()
74
79
 
@@ -8,10 +8,9 @@ from curl_cffi import requests
8
8
 
9
9
 
10
10
  # Internal utilities
11
- from StreamingCommunity.Util.headers import get_headers
12
11
  from StreamingCommunity.Util.config_json import config_manager
13
12
  from StreamingCommunity.Api.Player.Helper.Vixcloud.util import SeasonManager
14
- from .get_license import get_auth_token, generate_device_id
13
+ from .get_license import CrunchyrollClient
15
14
 
16
15
 
17
16
  # Variable
@@ -27,7 +26,7 @@ def get_series_seasons(series_id, headers, params):
27
26
  url,
28
27
  params=params,
29
28
  headers=headers,
30
- impersonate="chrome110"
29
+ impersonate="chrome136"
31
30
  )
32
31
  return response
33
32
 
@@ -41,30 +40,10 @@ def get_season_episodes(season_id, headers, params):
41
40
  url,
42
41
  params=params,
43
42
  headers=headers,
44
- impersonate="chrome110"
43
+ impersonate="chrome136"
45
44
  )
46
45
  return response
47
46
 
48
- def delete_stream_episode(episode_id, stream_id, headers):
49
- """
50
- Deletes a specific stream episode by episode ID and stream ID.
51
- """
52
- url = f'https://www.crunchyroll.com/playback/v1/token/{episode_id}/{stream_id}'
53
- headers = get_headers()
54
-
55
- response = requests.delete(
56
- url,
57
- headers=headers,
58
- impersonate="chrome110"
59
- )
60
-
61
- if response.status_code == 204:
62
- return True
63
-
64
- else:
65
- logging.error(f"Failed to delete stream episode: {response.status_code} - {response.text}")
66
- return False
67
-
68
47
 
69
48
  class GetSerieInfo:
70
49
  def __init__(self, series_id):
@@ -76,8 +55,13 @@ class GetSerieInfo:
76
55
  """
77
56
  self.series_id = series_id
78
57
  self.seasons_manager = SeasonManager()
79
- self.headers = get_headers()
80
- self.headers['authorization'] = f"Bearer {get_auth_token(generate_device_id()).access_token}"
58
+
59
+ # Initialize Crunchyroll client
60
+ self.client = CrunchyrollClient()
61
+ if not self.client.start():
62
+ raise Exception("Failed to authenticate with Crunchyroll")
63
+
64
+ self.headers = self.client._get_headers()
81
65
  self.params = {
82
66
  'force_locale': '',
83
67
  'preferred_audio_language': 'it-IT',
@@ -104,13 +88,10 @@ class GetSerieInfo:
104
88
  if seasons:
105
89
  self.series_name = seasons[0].get("series_title") or seasons[0].get("title")
106
90
 
107
- for season in seasons:
108
- season_num = season.get("season_number", 0)
109
- season_name = season.get("title", f"Season {season_num}")
110
-
91
+ for i, season in enumerate(seasons, start=1):
111
92
  self.seasons_manager.add_season({
112
- 'number': season_num,
113
- 'name': season_name,
93
+ 'number': i,
94
+ 'name': season.get("title", f"Season {i}"),
114
95
  'id': season.get('id')
115
96
  })
116
97
 
@@ -119,15 +100,10 @@ class GetSerieInfo:
119
100
  Fetch and cache episodes for a specific season number.
120
101
  """
121
102
  season = self.seasons_manager.get_season_by_number(season_number)
103
+ ep_response = get_season_episodes(season.id, self.headers, self.params)
122
104
 
123
- if not season or getattr(season, 'id', None) is None:
124
- logging.error(f"Season {season_number} not found or missing id.")
125
- return []
126
-
127
- season_id = season.id
128
- ep_response = get_season_episodes(season_id, self.headers, self.params)
129
105
  if ep_response.status_code != 200:
130
- logging.error(f"Failed to fetch episodes for season {season_id}")
106
+ logging.error(f"Failed to fetch episodes for season {season.id}")
131
107
  return []
132
108
 
133
109
  ep_data = ep_response.json()
@@ -165,7 +141,7 @@ class GetSerieInfo:
165
141
  url,
166
142
  params=params,
167
143
  headers=headers,
168
- impersonate="chrome110"
144
+ impersonate="chrome136"
169
145
  )
170
146
 
171
147
  if response.status_code != 200:
@@ -186,7 +162,6 @@ class GetSerieInfo:
186
162
  if locale and guid:
187
163
  audio_locales.append(locale)
188
164
  urls_by_locale[locale] = f"https://www.crunchyroll.com/it/watch/{guid}"
189
- #print(f"Locale: {locale}, URL: {urls_by_locale[locale]}")
190
165
 
191
166
  return audio_locales, urls_by_locale
192
167
 
@@ -1,12 +1,7 @@
1
1
  # 28.07.25
2
2
 
3
- import uuid
4
- from dataclasses import dataclass, field
5
- from typing import Optional, Dict, Any
6
-
7
-
8
- # External library
9
- from curl_cffi.requests import Session
3
+ from typing import Tuple, List, Dict
4
+ from curl_cffi import requests
10
5
 
11
6
 
12
7
  # Internal utilities
@@ -15,119 +10,130 @@ from StreamingCommunity.Util.headers import get_userAgent
15
10
 
16
11
 
17
12
  # Variable
18
- device_id = None
19
- auth_basic = 'bm9haWhkZXZtXzZpeWcwYThsMHE6'
20
- etp_rt = config_manager.get_dict("SITE_LOGIN", "crunchyroll")['etp_rt']
21
- x_cr_tab_id = config_manager.get_dict("SITE_LOGIN", "crunchyroll")['x_cr_tab_id']
22
-
23
-
24
- @dataclass
25
- class Token:
26
- access_token: Optional[str] = None
27
- refresh_token: Optional[str] = None
28
- expires_in: Optional[int] = None
29
- token_type: Optional[str] = None
30
- scope: Optional[str] = None
31
- country: Optional[str] = None
32
- account_id: Optional[str] = None
33
- profile_id: Optional[str] = None
34
- extra: Dict[str, Any] = field(default_factory=dict)
35
-
13
+ PUBLIC_TOKEN = "bm9haWhkZXZtXzZpeWcwYThsMHE6"
36
14
 
37
15
 
38
- def generate_device_id():
39
- global device_id
40
-
41
- if device_id is not None:
42
- return device_id
43
-
44
- device_id = str(uuid.uuid4())
45
- return device_id
46
-
16
+ class CrunchyrollClient:
17
+ def __init__(self) -> None:
18
+ config = config_manager.get_dict("SITE_LOGIN", "crunchyroll")
19
+ self.device_id = config.get('device_id')
20
+ self.etp_rt = config.get('etp_rt')
21
+ self.locale = "it-IT"
22
+
23
+ self.access_token = None
24
+ self.refresh_token = None
25
+ self.account_id = None
47
26
 
48
- def get_auth_token(device_id):
49
- with Session(impersonate="chrome110") as session:
50
- cookies = {
51
- 'etp_rt': etp_rt,
27
+ def _get_headers(self) -> Dict:
28
+ headers = {
29
+ 'user-agent': get_userAgent(),
52
30
  }
53
- response = session.post(
31
+ if self.access_token:
32
+ headers['authorization'] = f'Bearer {self.access_token}'
33
+ return headers
34
+
35
+ def _get_cookies(self) -> Dict:
36
+ cookies = {'device_id': self.device_id}
37
+ if self.etp_rt:
38
+ cookies['etp_rt'] = self.etp_rt
39
+ return cookies
40
+
41
+ def start(self) -> bool:
42
+ """Autorizza il client"""
43
+ headers = self._get_headers()
44
+ headers['authorization'] = f'Basic {PUBLIC_TOKEN}'
45
+ headers['content-type'] = 'application/x-www-form-urlencoded'
46
+
47
+ response = requests.post(
54
48
  'https://www.crunchyroll.com/auth/v1/token',
55
- headers={
56
- 'authorization': f'Basic {auth_basic}',
57
- 'user-agent': get_userAgent(),
58
- },
49
+ cookies=self._get_cookies(),
50
+ headers=headers,
59
51
  data={
60
- 'device_id': device_id,
52
+ 'device_id': self.device_id,
61
53
  'device_type': 'Chrome on Windows',
62
54
  'grant_type': 'etp_rt_cookie',
63
55
  },
64
- cookies=cookies
56
+ impersonate="chrome136"
65
57
  )
58
+
66
59
  if response.status_code == 400:
67
60
  print("Error 400: Please enter a correct 'etp_rt' value in config.json. You can find the value in the request headers.")
68
-
69
- # Get the JSON response
70
- data = response.json()
71
- known = {
72
- 'access_token', 'refresh_token', 'expires_in', 'token_type', 'scope',
73
- 'country', 'account_id', 'profile_id'
74
- }
75
- extra = {k: v for k, v in data.items() if k not in known}
76
- return Token(
77
- access_token=data.get('access_token'),
78
- refresh_token=data.get('refresh_token'),
79
- expires_in=data.get('expires_in'),
80
- token_type=data.get('token_type'),
81
- scope=data.get('scope'),
82
- country=data.get('country'),
83
- account_id=data.get('account_id'),
84
- profile_id=data.get('profile_id'),
85
- extra=extra
86
- )
87
-
88
-
89
- def get_playback_session(token: Token, device_id: str, url_id: str):
90
- """
91
- Crea una sessione per ottenere i dati di playback e sottotitoli da Crunchyroll.
92
- """
93
- cookies = {
94
- 'device_id': device_id,
95
- 'etp_rt': etp_rt
96
- }
97
- headers = {
98
- 'authorization': f'Bearer {token.access_token}',
99
- 'user-agent': get_userAgent(),
100
- 'x-cr-tab-id': x_cr_tab_id
101
- }
102
-
103
- with Session(impersonate="chrome110") as session:
104
- response = session.get(
105
- f'https://www.crunchyroll.com/playback/v3/{url_id}/web/chrome/play',
106
- cookies=cookies,
107
- headers=headers
61
+ return False
62
+
63
+ result = response.json()
64
+ self.access_token = result.get('access_token')
65
+ self.refresh_token = result.get('refresh_token')
66
+ self.account_id = result.get('account_id')
67
+
68
+ return True
69
+
70
+ def get_streams(self, media_id: str) -> Dict:
71
+ """Ottieni gli stream disponibili"""
72
+ response = requests.get(
73
+ f'https://www.crunchyroll.com/playback/v3/{media_id}/web/chrome/play',
74
+ cookies=self._get_cookies(),
75
+ headers=self._get_headers(),
76
+ params={'locale': self.locale},
77
+ impersonate="chrome136"
108
78
  )
109
-
110
- if (response.status_code == 403):
79
+
80
+ if response.status_code == 403:
111
81
  raise Exception("Playback is Rejected: The current subscription does not have access to this content")
112
82
 
113
- if (response.status_code == 420):
83
+ if response.status_code == 420:
114
84
  raise Exception("TOO_MANY_ACTIVE_STREAMS. Wait a few minutes and try again.")
115
-
85
+
116
86
  response.raise_for_status()
117
-
118
- # Get the JSON response
87
+
119
88
  data = response.json()
120
89
 
121
90
  if data.get('error') == 'Playback is Rejected':
122
91
  raise Exception("Playback is Rejected: Premium required")
123
92
 
124
- url = data.get('url')
125
-
126
- subtitles = []
127
- if 'subtitles' in data:
128
- subtitles = [
129
- {'language': lang, 'url': info['url'], 'format': info.get('format')}
130
- for lang, info in data['subtitles'].items()
131
- ]
132
-
133
- return url, headers, subtitles
93
+ return data
94
+
95
+ def delete_active_stream(self, media_id: str, token: str) -> bool:
96
+ """Elimina uno stream attivo"""
97
+ response = requests.delete(
98
+ f'https://www.crunchyroll.com/playback/v1/token/{media_id}/{token}',
99
+ cookies=self._get_cookies(),
100
+ headers=self._get_headers(),
101
+ impersonate="chrome136"
102
+ )
103
+ response.raise_for_status()
104
+ return response.status_code in [200, 204]
105
+
106
+
107
+ def get_playback_session(client: CrunchyrollClient, url_id: str) -> Tuple[str, Dict, List[Dict]]:
108
+ """
109
+ Return the playback session details including the MPD URL, headers, and subtitles.
110
+
111
+ Parameters:
112
+ - client: Instance of CrunchyrollClient
113
+ - url_id: ID of the media to fetch
114
+ """
115
+ data = client.get_streams(url_id)
116
+ url = data.get('url')
117
+
118
+ # Collect subtitles if available
119
+ subtitles = []
120
+ if 'subtitles' in data:
121
+ collected = []
122
+ for lang, info in data['subtitles'].items():
123
+ sub_url = info.get('url')
124
+
125
+ if not sub_url:
126
+ continue
127
+
128
+ collected.append({
129
+ 'language': lang,
130
+ 'url': sub_url,
131
+ 'format': info.get('format')
132
+ })
133
+
134
+ if collected:
135
+ subtitles = collected
136
+
137
+ # Return the MPD URL, headers, and subtitles
138
+ headers = client._get_headers()
139
+ return url, headers, subtitles
@@ -79,7 +79,7 @@ class MediasetAPI:
79
79
  html = self.fetch_html()
80
80
  scripts = self.find_relevant_script(html)[0:1]
81
81
  pairs = self.extract_pairs_from_scripts(scripts)
82
- return next((h for h, k in pairs.items() if k == "$2a"), None)
82
+ return list(pairs.keys())[-5]
83
83
 
84
84
  def generate_request_headers(self):
85
85
  return {
@@ -166,16 +166,7 @@ def download_series(select_season: MediaItem, season_selection: str = None, epis
166
166
  - episode_selection (str, optional): Pre-defined episode selection that bypasses manual input
167
167
  """
168
168
  start_message()
169
-
170
- # Extract program name from path_id
171
- program_name = None
172
- if select_season.path_id:
173
- parts = select_season.path_id.strip('/').split('/')
174
- if len(parts) >= 2:
175
- program_name = parts[-1].split('.')[0]
176
-
177
- # Init scraper
178
- scrape_serie = GetSerieInfo(program_name)
169
+ scrape_serie = GetSerieInfo(select_season.path_id)
179
170
 
180
171
  # Get seasons info
181
172
  scrape_serie.collect_info_title()
@@ -30,24 +30,13 @@ def determine_media_type(item):
30
30
  using GetSerieInfo.
31
31
  """
32
32
  try:
33
- # Extract program name from path_id
34
- program_name = None
35
- if item.get('path_id'):
36
- parts = item['path_id'].strip('/').split('/')
37
- if len(parts) >= 2:
38
- program_name = parts[-1].split('.')[0]
39
-
40
- if not program_name:
41
- return "film"
42
-
43
- # Dio stranamente guarda che giro bisogna fare per avere il tipo di media.
44
- scraper = GetSerieInfo(program_name)
33
+ scraper = GetSerieInfo(item.get('path_id'))
45
34
  scraper.collect_info_title()
46
35
  return scraper.prog_tipology, scraper.prog_description, scraper.prog_year
47
36
 
48
37
  except Exception as e:
49
38
  console.print(f"[red]Error determining media type: {e}[/red]")
50
- return "film"
39
+ return None, None, None
51
40
 
52
41
 
53
42
  def title_search(query: str) -> int:
@@ -97,6 +86,9 @@ def title_search(query: str) -> int:
97
86
  # Process each item and add to media manager
98
87
  for item in data:
99
88
  media_type, prog_description, prog_year = determine_media_type(item)
89
+ if media_type is None:
90
+ continue
91
+
100
92
  media_search_manager.add_media({
101
93
  'id': item.get('id', ''),
102
94
  'name': item.get('titolo', ''),
@@ -18,12 +18,12 @@ max_timeout = config_manager.get_int("REQUESTS", "timeout")
18
18
 
19
19
 
20
20
  class GetSerieInfo:
21
- def __init__(self, program_name: str):
21
+ def __init__(self, path_id: str):
22
22
  """Initialize the GetSerieInfo class."""
23
23
  self.base_url = "https://www.raiplay.it"
24
- self.program_name = program_name
25
- self.series_name = program_name
26
- self.prog_tipology = None
24
+ self.path_id = path_id
25
+ self.series_name = None
26
+ self.prog_tipology = "film"
27
27
  self.prog_description = None
28
28
  self.prog_year = None
29
29
  self.seasons_manager = SeasonManager()
@@ -31,24 +31,21 @@ class GetSerieInfo:
31
31
  def collect_info_title(self) -> None:
32
32
  """Get series info including seasons."""
33
33
  try:
34
- program_url = f"{self.base_url}/programmi/{self.program_name}.json"
34
+ program_url = f"{self.base_url}/{self.path_id}"
35
35
  response = httpx.get(url=program_url, headers=get_headers(), timeout=max_timeout)
36
36
 
37
37
  # If 404, content is not yet available
38
38
  if response.status_code == 404:
39
- logging.info(f"Content not yet available: {self.program_name}")
39
+ logging.info(f"Content not yet available: {program_url}")
40
40
  return
41
41
 
42
42
  response.raise_for_status()
43
43
  json_data = response.json()
44
44
 
45
- # Dio santissimo ma chi ha fatto le cose cosi di merda.
46
- type_check_1 = "tv" if json_data.get('program_info', {}).get('layout', 'single') == 'multi' else "film"
47
- #type_check_2 = "tv" if "tv" in json_data.get('track_info', {}).get('typology', '') else "film"
48
-
49
- self.prog_tipology = type_check_1
45
+ # Get basic program info
50
46
  self.prog_description = json_data.get('program_info', '').get('vanity', '')
51
47
  self.prog_year = json_data.get('program_info', '').get('year', '')
48
+ self.series_name = json_data.get('program_info', '').get('title', '')
52
49
 
53
50
  # Look for seasons in the 'blocks' property
54
51
  for block in json_data.get('blocks', []):
@@ -60,11 +57,14 @@ class GetSerieInfo:
60
57
 
61
58
  # Extract seasons from sets array
62
59
  for season_set in block.get('sets', []):
60
+ self.prog_tipology = "tv"
61
+
63
62
  if 'stagione' in season_set.get('name', '').lower():
64
63
  self._add_season(season_set, block.get('id'))
65
64
 
66
65
  elif 'stagione' in block.get('name', '').lower():
67
66
  self.publishing_block_id = block.get('id')
67
+ self.prog_tipology = "tv"
68
68
 
69
69
  # Extract season directly from block's sets
70
70
  for season_set in block.get('sets', []):
@@ -88,7 +88,7 @@ class GetSerieInfo:
88
88
  season = self.seasons_manager.get_season_by_number(number_season)
89
89
 
90
90
  # Se stai leggendo questo codice spieami perche hai fatto cosi.
91
- url = f"{self.base_url}/programmi/{self.program_name}/{self.publishing_block_id}/{season.id}/episodes.json"
91
+ url = f"{self.base_url}/{self.path_id.replace(".json", "")}/{self.publishing_block_id}/{season.id}/episodes.json"
92
92
  response = httpx.get(url=url, headers=get_headers(), timeout=max_timeout)
93
93
  response.raise_for_status()
94
94