StreamingCommunity 2.9.4__py3-none-any.whl → 2.9.6__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 (48) hide show
  1. StreamingCommunity/Api/Player/sweetpixel.py +49 -0
  2. StreamingCommunity/Api/Site/1337xx/__init__.py +26 -12
  3. StreamingCommunity/Api/Site/1337xx/site.py +5 -4
  4. StreamingCommunity/Api/Site/1337xx/title.py +4 -6
  5. StreamingCommunity/Api/Site/altadefinizione/__init__.py +64 -17
  6. StreamingCommunity/Api/Site/altadefinizione/film.py +32 -2
  7. StreamingCommunity/Api/Site/altadefinizione/series.py +54 -10
  8. StreamingCommunity/Api/Site/altadefinizione/site.py +25 -7
  9. StreamingCommunity/Api/Site/altadefinizione/util/ScrapeSerie.py +2 -2
  10. StreamingCommunity/Api/Site/animeunity/__init__.py +53 -32
  11. StreamingCommunity/Api/Site/animeunity/film_serie.py +8 -5
  12. StreamingCommunity/Api/Site/animeunity/site.py +4 -6
  13. StreamingCommunity/Api/Site/animeworld/__init__.py +71 -0
  14. StreamingCommunity/Api/Site/animeworld/serie.py +107 -0
  15. StreamingCommunity/Api/Site/animeworld/site.py +111 -0
  16. StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +79 -0
  17. StreamingCommunity/Api/Site/cb01new/__init__.py +26 -14
  18. StreamingCommunity/Api/Site/cb01new/film.py +1 -1
  19. StreamingCommunity/Api/Site/cb01new/site.py +9 -7
  20. StreamingCommunity/Api/Site/ddlstreamitaly/__init__.py +26 -15
  21. StreamingCommunity/Api/Site/ddlstreamitaly/series.py +2 -2
  22. StreamingCommunity/Api/Site/ddlstreamitaly/site.py +3 -3
  23. StreamingCommunity/Api/Site/guardaserie/__init__.py +23 -11
  24. StreamingCommunity/Api/Site/guardaserie/series.py +1 -1
  25. StreamingCommunity/Api/Site/guardaserie/site.py +5 -4
  26. StreamingCommunity/Api/Site/mostraguarda/__init__.py +27 -7
  27. StreamingCommunity/Api/Site/mostraguarda/film.py +1 -1
  28. StreamingCommunity/Api/Site/streamingcommunity/__init__.py +50 -27
  29. StreamingCommunity/Api/Site/streamingcommunity/film.py +1 -1
  30. StreamingCommunity/Api/Site/streamingcommunity/series.py +6 -3
  31. StreamingCommunity/Api/Site/streamingcommunity/site.py +7 -3
  32. StreamingCommunity/Lib/Downloader/HLS/segments.py +2 -4
  33. StreamingCommunity/Lib/Downloader/MP4/downloader.py +7 -6
  34. StreamingCommunity/Lib/Downloader/TOR/downloader.py +397 -227
  35. StreamingCommunity/Lib/FFmpeg/util.py +12 -0
  36. StreamingCommunity/Lib/M3U8/estimator.py +5 -8
  37. StreamingCommunity/Upload/version.py +1 -1
  38. StreamingCommunity/Util/config_json.py +2 -8
  39. StreamingCommunity/Util/table.py +12 -2
  40. StreamingCommunity/global_search.py +315 -0
  41. StreamingCommunity/run.py +39 -5
  42. {streamingcommunity-2.9.4.dist-info → streamingcommunity-2.9.6.dist-info}/METADATA +42 -15
  43. streamingcommunity-2.9.6.dist-info/RECORD +85 -0
  44. {streamingcommunity-2.9.4.dist-info → streamingcommunity-2.9.6.dist-info}/WHEEL +1 -1
  45. streamingcommunity-2.9.4.dist-info/RECORD +0 -79
  46. {streamingcommunity-2.9.4.dist-info → streamingcommunity-2.9.6.dist-info}/entry_points.txt +0 -0
  47. {streamingcommunity-2.9.4.dist-info → streamingcommunity-2.9.6.dist-info/licenses}/LICENSE +0 -0
  48. {streamingcommunity-2.9.4.dist-info → streamingcommunity-2.9.6.dist-info}/top_level.txt +0 -0
@@ -11,11 +11,12 @@ from rich.prompt import Prompt
11
11
 
12
12
  # Internal utilities
13
13
  from StreamingCommunity.Api.Template import get_select_title
14
+ from StreamingCommunity.Api.Template.config_loader import site_constant
15
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
14
16
  from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
15
17
 
16
18
 
17
19
  # Logic class
18
- from StreamingCommunity.Api.Template.config_loader import site_constant
19
20
  from .site import title_search, media_search_manager, table_show_manager
20
21
  from .film_serie import download_film, download_series
21
22
 
@@ -31,53 +32,73 @@ msg = Prompt()
31
32
  console = Console()
32
33
 
33
34
 
34
- def search(string_to_search: str = None, get_onylDatabase: bool = False):
35
-
36
- if site_constant.TELEGRAM_BOT:
37
- bot = get_bot_instance()
38
-
39
- if string_to_search is None:
40
-
41
- # Chiedi la scelta all'utente con il bot Telegram
35
+ def get_user_input(string_to_search: str = None):
36
+ """
37
+ Asks the user to input a search term.
38
+ Handles both Telegram bot input and direct input.
39
+ """
40
+ if string_to_search is None:
41
+ if site_constant.TELEGRAM_BOT:
42
+ bot = get_bot_instance()
42
43
  string_to_search = bot.ask(
43
44
  "key_search",
44
- f"Inserisci la parola da cercare\noppure back per tornare alla scelta: ",
45
+ f"Enter the search term\nor type 'back' to return to the menu: ",
45
46
  None
46
47
  )
47
48
 
48
49
  if string_to_search == 'back':
49
- # Riavvia lo script
50
- # Chiude il processo attuale e avvia una nuova istanza dello script
50
+
51
+ # Restart the script
51
52
  subprocess.Popen([sys.executable] + sys.argv)
52
53
  sys.exit()
53
-
54
- else:
55
- if string_to_search is None:
56
- string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
57
-
58
- # Search on database
54
+ else:
55
+ string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
56
+
57
+ return string_to_search
58
+
59
+ def process_search_result(select_title):
60
+ """
61
+ Handles the search result and initiates the download for either a film or series.
62
+ """
63
+ download_series(select_title)
64
+
65
+ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
66
+ """
67
+ Main function of the application for search film, series and anime.
68
+
69
+ Parameters:
70
+ string_to_search (str, optional): String to search for
71
+ get_onlyDatabase (bool, optional): If True, return only the database object
72
+ direct_item (dict, optional): Direct item to process (bypass search)
73
+ """
74
+ if direct_item:
75
+ select_title = MediaItem(**direct_item)
76
+ process_search_result(select_title)
77
+ return
78
+
79
+ # Get the user input for the search term
80
+ string_to_search = get_user_input(string_to_search)
81
+
82
+ # Perform the database search
59
83
  len_database = title_search(string_to_search)
60
84
 
61
- # Return list of elements
62
- if get_onylDatabase:
85
+ ##If only the database is needed, return the manager
86
+ if get_onlyDatabase:
63
87
  return media_search_manager
88
+
89
+ if site_constant.TELEGRAM_BOT:
90
+ bot = get_bot_instance()
64
91
 
65
92
  if len_database > 0:
66
-
67
- # Select title from list (type: TV \ Movie \ OVA)
68
93
  select_title = get_select_title(table_show_manager, media_search_manager)
94
+ process_search_result(select_title)
69
95
 
70
- if select_title.type == 'Movie' or select_title.type == 'OVA':
71
- download_film(select_title)
72
-
73
- else:
74
- download_series(select_title)
75
-
76
96
  else:
77
- if site_constant.TELEGRAM_BOT:
78
- bot.send_message(f"Nessun risultato trovato riprova", None)
79
-
80
97
  console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
81
98
 
82
- # Retry
99
+ if site_constant.TELEGRAM_BOT:
100
+ bot.send_message(f"No results found, please try again", None)
101
+
102
+ # If no results are found, ask again
103
+ string_to_search = get_user_input()
83
104
  search()
@@ -55,8 +55,7 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
55
55
  if obj_episode is not None:
56
56
 
57
57
  start_message()
58
- console.print(f"[yellow]Download: [red]EP_{obj_episode.number} \n")
59
- console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
58
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]E{obj_episode.number}[/cyan]) \n")
60
59
 
61
60
  if site_constant.TELEGRAM_BOT:
62
61
  bot.send_message(f"Download in corso:\nTitolo:{scrape_serie.series_name}\nEpisodio: {obj_episode.number}", None)
@@ -70,7 +69,7 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
70
69
  video_source.get_embed(obj_episode.id)
71
70
 
72
71
  # Create output path
73
- title_name = f"{scrape_serie.series_name}_EP_{dynamic_format_number(str(obj_episode.number))}.mp4"
72
+ mp4_name = f"{scrape_serie.series_name}_EP_{dynamic_format_number(str(obj_episode.number))}.mp4"
74
73
 
75
74
  if scrape_serie.is_series:
76
75
  mp4_path = os_manager.get_sanitize_path(os.path.join(site_constant.ANIME_FOLDER, scrape_serie.series_name))
@@ -84,7 +83,7 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
84
83
  # Start downloading
85
84
  path, kill_handler = MP4_downloader(
86
85
  url=str(video_source.src_mp4).strip(),
87
- path=os.path.join(mp4_path, title_name)
86
+ path=os.path.join(mp4_path, mp4_name)
88
87
  )
89
88
 
90
89
  return path, kill_handler
@@ -123,7 +122,11 @@ def download_series(select_title: MediaItem):
123
122
 
124
123
  last_command = bot.ask(
125
124
  "select_title",
126
- f"Inserisci l'indice del media o * per scaricare tutti i media, oppure 1-2 o 3-* per un intervallo di media.",
125
+ "Menu di selezione degli episodi: \n\n"
126
+ "- Inserisci il numero dell'episodio (ad esempio, 1)\n"
127
+ "- Inserisci * per scaricare tutti gli episodi\n"
128
+ "- Inserisci un intervallo di episodi (ad esempio, 1-2) per scaricare da un episodio all'altro\n"
129
+ "- Inserisci (ad esempio, 3-*) per scaricare dall'episodio specificato fino alla fine della serie",
127
130
  None
128
131
  )
129
132
 
@@ -77,20 +77,18 @@ def get_real_title(record):
77
77
  """
78
78
  if record['title_eng'] is not None:
79
79
  return record['title_eng']
80
-
81
80
  elif record['title'] is not None:
82
81
  return record['title']
83
-
84
82
  else:
85
83
  return record['title_it']
86
84
 
87
85
 
88
- def title_search(title: str) -> int:
86
+ def title_search(query: str) -> int:
89
87
  """
90
- Function to perform an anime search using a provided title.
88
+ Function to perform an anime search using a provided query.
91
89
 
92
90
  Parameters:
93
- - title_search (str): The title to search for.
91
+ - query (str): The query to search for.
94
92
 
95
93
  Returns:
96
94
  - int: A number containing the length of media search manager.
@@ -108,7 +106,7 @@ def title_search(title: str) -> int:
108
106
  'user-agent': get_userAgent(),
109
107
  'x-csrf-token': data.get('csrf_token')
110
108
  }
111
- json_data = {'title': title}
109
+ json_data = {'title': query}
112
110
 
113
111
  # Send a POST request to the API endpoint for live search
114
112
  try:
@@ -0,0 +1,71 @@
1
+ # 21.03.25
2
+
3
+ # External library
4
+ from rich.console import Console
5
+ from rich.prompt import Prompt
6
+
7
+
8
+ # Internal utilities
9
+ from StreamingCommunity.Api.Template import get_select_title
10
+ from StreamingCommunity.Api.Template.config_loader import site_constant
11
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
12
+
13
+
14
+ # Logic class
15
+ from .site import title_search, media_search_manager, table_show_manager
16
+ from .serie import download_series
17
+
18
+
19
+ # Variable
20
+ indice = 8
21
+ _useFor = "anime"
22
+ _deprecate = False
23
+ _priority = 2
24
+ _engineDownload = "mp4"
25
+
26
+ msg = Prompt()
27
+ console = Console()
28
+
29
+
30
+
31
+ def process_search_result(select_title):
32
+ """
33
+ Handles the search result and initiates the download for either a film or series.
34
+ """
35
+ if select_title.type == "TV":
36
+ download_series(select_title)
37
+
38
+ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
39
+ """
40
+ Main function of the application for search film, series and anime.
41
+
42
+ Parameters:
43
+ string_to_search (str, optional): String to search for
44
+ get_onlyDatabase (bool, optional): If True, return only the database object
45
+ direct_item (dict, optional): Direct item to process (bypass search)
46
+ """
47
+ if direct_item:
48
+ select_title = MediaItem(**direct_item)
49
+ process_search_result(select_title)
50
+ return
51
+
52
+ # Get the user input for the search term
53
+ string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
54
+
55
+ # Perform the database search
56
+ len_database = title_search(string_to_search)
57
+
58
+ ##If only the database is needed, return the manager
59
+ if get_onlyDatabase:
60
+ return media_search_manager
61
+
62
+ if len_database > 0:
63
+ select_title = get_select_title(table_show_manager, media_search_manager)
64
+ process_search_result(select_title)
65
+
66
+ else:
67
+ console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
68
+
69
+ # If no results are found, ask again
70
+ string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
71
+ search()
@@ -0,0 +1,107 @@
1
+ # 11.03.24
2
+
3
+ import os
4
+ import logging
5
+ from typing import Tuple
6
+
7
+
8
+ # External library
9
+ from rich.console import Console
10
+ from rich.prompt import Prompt
11
+
12
+
13
+ # Internal utilities
14
+ from StreamingCommunity.Util.os import os_manager
15
+ from StreamingCommunity.Util.message import start_message
16
+ from StreamingCommunity.Lib.Downloader import MP4_downloader
17
+
18
+
19
+ # Logic class
20
+ from .util.ScrapeSerie import ScrapSerie
21
+ from StreamingCommunity.Api.Template.config_loader import site_constant
22
+ from StreamingCommunity.Api.Template.Util import manage_selection, dynamic_format_number, map_episode_title
23
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
24
+
25
+
26
+ # Player
27
+ from StreamingCommunity.Api.Player.sweetpixel import AnimeWorldPlayer
28
+
29
+
30
+ # Variable
31
+ console = Console()
32
+ msg = Prompt()
33
+ KILL_HANDLER = bool(False)
34
+
35
+
36
+
37
+ def download_episode(index_select: int, scrape_serie: ScrapSerie, episodes) -> Tuple[str,bool]:
38
+ """
39
+ Downloads the selected episode.
40
+
41
+ Parameters:
42
+ - index_select (int): Index of the episode to download.
43
+
44
+ Return:
45
+ - str: output path
46
+ - bool: kill handler status
47
+ """
48
+ start_message()
49
+
50
+ # Get information about the selected episode
51
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] ([cyan]E{index_select+1}[/cyan]) \n")
52
+
53
+ # Define filename and path for the downloaded video
54
+ mp4_name = f"{scrape_serie.get_name()}_EP_{dynamic_format_number(str(index_select+1))}.mp4"
55
+ mp4_path = os.path.join(site_constant.ANIME_FOLDER, scrape_serie.get_name())
56
+
57
+ # Create output folder
58
+ os_manager.create_path(mp4_path)
59
+
60
+ # Collect mp4 link
61
+ video_source = AnimeWorldPlayer(site_constant.FULL_URL, episodes[index_select], scrape_serie.session_id, scrape_serie.csrf_token)
62
+ mp4_link = video_source.get_download_link()
63
+
64
+ # Start downloading
65
+ path, kill_handler = MP4_downloader(
66
+ url=str(mp4_link).strip(),
67
+ path=os.path.join(mp4_path, mp4_name)
68
+ )
69
+
70
+ return path, kill_handler
71
+
72
+
73
+ def download_series(select_title: MediaItem):
74
+ """
75
+ Function to download episodes of a TV series.
76
+
77
+ Parameters:
78
+ - tv_id (int): The ID of the TV series.
79
+ - tv_name (str): The name of the TV series.
80
+ """
81
+ start_message()
82
+
83
+ scrape_serie = ScrapSerie(select_title.url, site_constant.FULL_URL)
84
+
85
+ # Get the count of episodes for the TV series
86
+ episodes = scrape_serie.get_episodes()
87
+ episoded_count = len(episodes)
88
+ console.print(f"[cyan]Episodes find: [red]{episoded_count}")
89
+
90
+ # Prompt user to select an episode index
91
+ last_command = msg.ask("\n[cyan]Insert media [red]index [yellow]or [red]* [cyan]to download all media [yellow]or [red]1-2 [cyan]or [red]3-* [cyan]for a range of media")
92
+
93
+ # Manage user selection
94
+ list_episode_select = manage_selection(last_command, episoded_count)
95
+
96
+ # Download selected episodes
97
+ if len(list_episode_select) == 1 and last_command != "*":
98
+ path, _ = download_episode(list_episode_select[0]-1, scrape_serie, episodes)
99
+ return path
100
+
101
+ # Download all other episodes selecter
102
+ else:
103
+ kill_handler = False
104
+ for i_episode in list_episode_select:
105
+ if kill_handler:
106
+ break
107
+ _, kill_handler = download_episode(i_episode-1, scrape_serie, episodes)
@@ -0,0 +1,111 @@
1
+ # 21.03.25
2
+
3
+ import logging
4
+
5
+ # External libraries
6
+ import httpx
7
+ from bs4 import BeautifulSoup
8
+ from rich.console import Console
9
+
10
+
11
+ # Internal utilities
12
+ from StreamingCommunity.Util.config_json import config_manager
13
+ from StreamingCommunity.Util.headers import get_userAgent, get_headers
14
+ from StreamingCommunity.Util.table import TVShowManager
15
+
16
+
17
+ # Logic class
18
+ from StreamingCommunity.Api.Template.config_loader import site_constant
19
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
20
+
21
+
22
+ # Variable
23
+ console = Console()
24
+ media_search_manager = MediaManager()
25
+ table_show_manager = TVShowManager()
26
+ max_timeout = config_manager.get_int("REQUESTS", "timeout")
27
+
28
+
29
+ def get_session_and_csrf() -> dict:
30
+ """
31
+ Get the session ID and CSRF token from the website's cookies and HTML meta data.
32
+ """
33
+ # Send an initial GET request to the website
34
+ response = httpx.get(site_constant.FULL_URL, headers=get_headers())
35
+
36
+ # Extract the sessionId from the cookies
37
+ session_id = response.cookies.get('sessionId')
38
+ logging.info(f"Session ID: {session_id}")
39
+
40
+ # Use BeautifulSoup to parse the HTML and extract the CSRF-Token
41
+ soup = BeautifulSoup(response.text, 'html.parser')
42
+
43
+ # Try to find the CSRF token in a meta tag or hidden input
44
+ csrf_token = None
45
+ meta_tag = soup.find('meta', {'name': 'csrf-token'})
46
+ if meta_tag:
47
+ csrf_token = meta_tag.get('content')
48
+
49
+ # If it's not in the meta tag, check for hidden input fields
50
+ if not csrf_token:
51
+ input_tag = soup.find('input', {'name': '_csrf'})
52
+ if input_tag:
53
+ csrf_token = input_tag.get('value')
54
+
55
+ logging.info(f"CSRF Token: {csrf_token}")
56
+ return session_id, csrf_token
57
+
58
+ def title_search(query: str) -> int:
59
+ """
60
+ Function to perform an anime search using a provided title.
61
+
62
+ Parameters:
63
+ - query (str): The query to search for.
64
+
65
+ Returns:
66
+ - int: A number containing the length of media search manager.
67
+ """
68
+ search_url = f"{site_constant.FULL_URL}/search?keyword={query}"
69
+ console.print(f"[cyan]Search url: [yellow]{search_url}")
70
+
71
+ # Make the GET request
72
+ try:
73
+ response = httpx.get(search_url, headers={'User-Agent': get_userAgent()})
74
+
75
+ except Exception as e:
76
+ console.print(f"Site: {site_constant.SITE_NAME}, request search error: {e}")
77
+ return 0
78
+
79
+ # Create soup istance
80
+ soup = BeautifulSoup(response.text, 'html.parser')
81
+
82
+ # Collect data from soup
83
+ for element in soup.find_all('a', class_='poster'):
84
+ try:
85
+ title = element.find('img').get('alt')
86
+ url = f"{site_constant.FULL_URL}{element.get('href')}"
87
+ status_div = element.find('div', class_='status')
88
+ is_dubbed = False
89
+ anime_type = 'TV'
90
+
91
+ if status_div:
92
+ if status_div.find('div', class_='dub'):
93
+ is_dubbed = True
94
+
95
+ if status_div.find('div', class_='movie'):
96
+ anime_type = 'Movie'
97
+ elif status_div.find('div', class_='ona'):
98
+ anime_type = 'ONA'
99
+
100
+ media_search_manager.add_media({
101
+ 'name': title,
102
+ 'type': anime_type,
103
+ 'DUB': is_dubbed,
104
+ 'url': url
105
+ })
106
+
107
+ except Exception as e:
108
+ print(f"Error parsing a film entry: {e}")
109
+
110
+ # Return the length of media search manager
111
+ return media_search_manager.get_length()
@@ -0,0 +1,79 @@
1
+ # 21.03.25
2
+
3
+
4
+ # External libraries
5
+ import httpx
6
+ from bs4 import BeautifulSoup
7
+
8
+
9
+ # Internal utilities
10
+ from StreamingCommunity.Util.headers import get_userAgent
11
+ from StreamingCommunity.Util.config_json import config_manager
12
+ from StreamingCommunity.Util.os import os_manager
13
+
14
+
15
+ # Player
16
+ from ..site import get_session_and_csrf
17
+ from StreamingCommunity.Api.Player.sweetpixel import AnimeWorldPlayer
18
+
19
+
20
+ # Variable
21
+ max_timeout = config_manager.get_int("REQUESTS", "timeout")
22
+
23
+
24
+ class ScrapSerie:
25
+ def __init__(self, url, full_url):
26
+ """Initialize the ScrapSerie object with the provided URL and setup the HTTP client."""
27
+ self.url = url
28
+ self.link = httpx.URL(url).path
29
+ self.session_id, self.csrf_token = get_session_and_csrf()
30
+ self.client = httpx.Client(
31
+ cookies={"sessionId": self.session_id},
32
+ headers={"User-Agent": get_userAgent(), "csrf-token": self.csrf_token},
33
+ base_url=full_url
34
+ )
35
+
36
+ try:
37
+ self.response = self.client.get(self.link, timeout=max_timeout, follow_redirects=True)
38
+ self.response.raise_for_status()
39
+
40
+ except:
41
+ raise Exception(f"Failed to retrieve anime page.")
42
+
43
+
44
+ def get_name(self):
45
+ """Extract and return the name of the anime series."""
46
+ soup = BeautifulSoup(self.response.content, "html.parser")
47
+ return os_manager.get_sanitize_file(soup.find("h1", {"id": "anime-title"}).get_text(strip=True))
48
+
49
+ def get_episodes(self, nums=None):
50
+ """Fetch and return the list of episodes, optionally filtering by specific episode numbers."""
51
+ soup = BeautifulSoup(self.response.content, "html.parser")
52
+
53
+ raw_eps = {}
54
+ for data in soup.select('li.episode > a'):
55
+ epNum = data.get('data-episode-num')
56
+ epID = data.get('data-episode-id')
57
+
58
+ if nums and epNum not in nums:
59
+ continue
60
+
61
+ if epID not in raw_eps:
62
+ raw_eps[epID] = {
63
+ 'number': epNum,
64
+ 'link': f"/api/download/{epID}"
65
+ }
66
+
67
+ episodes = [episode_data for episode_data in raw_eps.values()]
68
+ return episodes
69
+
70
+ def get_episode(self, index):
71
+ """Fetch a specific episode based on the index, and return an AnimeWorldPlayer instance."""
72
+ episodes = self.get_episodes()
73
+
74
+ if 0 <= index < len(episodes):
75
+ episode_data = episodes[index]
76
+ return AnimeWorldPlayer(episode_data, self.session_id, self.csrf_token)
77
+
78
+ else:
79
+ raise IndexError("Episode index out of range")
@@ -10,10 +10,11 @@ from rich.prompt import Prompt
10
10
 
11
11
  # Internal utilities
12
12
  from StreamingCommunity.Api.Template import get_select_title
13
+ from StreamingCommunity.Api.Template.config_loader import site_constant
14
+ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
13
15
 
14
16
 
15
17
  # Logic class
16
- from StreamingCommunity.Api.Template.config_loader import site_constant
17
18
  from .site import title_search, media_search_manager, table_show_manager
18
19
  from .film import download_film
19
20
 
@@ -29,10 +30,26 @@ msg = Prompt()
29
30
  console = Console()
30
31
 
31
32
 
32
- def search(string_to_search: str = None, get_onylDatabase: bool = False):
33
+ def process_search_result(select_title):
34
+ """
35
+ Handles the search result and initiates the download for either a film or series.
36
+ """
37
+ # !!! ADD TYPE DONT WORK FOR SERIE
38
+ download_film(select_title)
39
+
40
+ def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
33
41
  """
34
- Main function of the application for film and series.
42
+ Main function of the application for search film, series and anime.
43
+
44
+ Parameters:
45
+ string_to_search (str, optional): String to search for
46
+ get_onylDatabase (bool, optional): If True, return only the database object
47
+ direct_item (dict, optional): Direct item to process (bypass search)
35
48
  """
49
+ if direct_item:
50
+ select_title = MediaItem(**direct_item)
51
+ process_search_result(select_title)
52
+ return
36
53
 
37
54
  if string_to_search is None:
38
55
  string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
@@ -40,21 +57,16 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
40
57
  # Search on database
41
58
  len_database = title_search(quote_plus(string_to_search))
42
59
 
43
- # Return list of elements
44
- if get_onylDatabase:
60
+ ## If only the database is needed, return the manager
61
+ if get_onlyDatabase:
45
62
  return media_search_manager
46
63
 
47
64
  if len_database > 0:
48
-
49
- # Select title from list
50
65
  select_title = get_select_title(table_show_manager, media_search_manager)
51
-
52
- # !!! ADD TYPE DONT WORK FOR SERIE
53
- download_film(select_title)
54
-
55
-
66
+ process_search_result(select_title)
67
+
56
68
  else:
57
- console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
58
69
 
59
- # Retry
70
+ # If no results are found, ask again
71
+ console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
60
72
  search()
@@ -37,7 +37,7 @@ def download_film(select_title: MediaItem) -> str:
37
37
  - str: output path
38
38
  """
39
39
  start_message()
40
- console.print(f"[yellow]Download: [red]{select_title.name} \n")
40
+ console.print(f"[bold yellow]Download:[/bold yellow] [red]{site_constant.SITE_NAME}[/red] → [cyan]{select_title.name}[/cyan] \n")
41
41
 
42
42
  # Setup api manger
43
43
  video_source = VideoSource(select_title.url)