Bump v2.9.5

This commit is contained in:
None 2025-03-17 16:15:01 +01:00
parent 3e5b126c07
commit 64e382c32e
22 changed files with 685 additions and 296 deletions

View File

@ -45,6 +45,7 @@
- 📥 [Download](#m3u8_download-settings) - 📥 [Download](#m3u8_download-settings)
- 🔍 [Parser](#m3u8_parser-settings) - 🔍 [Parser](#m3u8_parser-settings)
- 📝 [Command](#command) - 📝 [Command](#command)
- 🔍 [Global search](#global-search)
- 💻 [Examples of terminal](#examples-of-terminal-usage) - 💻 [Examples of terminal](#examples-of-terminal-usage)
- 🔧 [Manual domain configuration](#update-domains) - 🔧 [Manual domain configuration](#update-domains)
- 🐳 [Docker](#docker) - 🐳 [Docker](#docker)
@ -637,19 +638,41 @@ Note: If `use_api` is set to `false` and no `domains.json` file is found, the sc
#### 💡 Adding a New Site to the Legacy API #### 💡 Adding a New Site to the Legacy API
If you want to add a new site to the legacy API, just message me on the Discord server, and I'll add it! If you want to add a new site to the legacy API, just message me on the Discord server, and I'll add it!
# COMMAND # Global Search
- Download a specific season by entering its number. You can now search across multiple streaming sites at once using the Global Search feature. This allows you to find content more efficiently without having to search each site individually.
* **Example:** `1` will download *Season 1* only.
- Use the wildcard `*` to download every available season. ## Using Global Search
* **Example:** `*` will download all seasons in the series.
- Specify a range of seasons using a hyphen `-`. The Global Search feature provides a unified interface to search across all supported sites:
* **Example:** `1-2` will download *Seasons 1 and 2*.
- Enter a season number followed by `-*` to download from that season to the end. ## Search Options
* **Example:** `3-*` will download from *Season 3* to the final season.
When using Global Search, you have three ways to select which sites to search:
1. **Search all sites** - Searches across all available streaming sites
2. **Search by category** - Group sites by their categories (movies, series, anime, etc.)
3. **Select specific sites** - Choose individual sites to include in your search
## Navigation and Selection
After performing a search:
1. Results are displayed in a consolidated table showing:
- Title
- Media type (movie, TV series, etc.)
- Source site
2. Select an item by number to view details or download
3. The system will automatically use the appropriate site's API to handle the download
## Command Line Arguments
The Global Search can be configured from the command line:
- `--global` - Perform a global search across multiple sites.
- `-s`, `--search` - Specify the search terms.
# Examples of terminal usage # Examples of terminal usage
@ -662,6 +685,9 @@ python test_run.py --specific_list_audio ita,eng --specific_list_subtitles eng,s
# Keep console open after download # Keep console open after download
python test_run.py --not_close true python test_run.py --not_close true
# Use global search
python test_run.py --global -s "cars"
``` ```
# Docker # Docker

View File

@ -10,10 +10,11 @@ from rich.prompt import Prompt
# Internal utilities # Internal utilities
from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Logic class # Logic class
from StreamingCommunity.Api.Template.config_loader import site_constant
from .site import title_search, media_search_manager, table_show_manager from .site import title_search, media_search_manager, table_show_manager
from .title import download_title from .title import download_title
@ -29,30 +30,43 @@ console = Console()
msg = Prompt() msg = Prompt()
def search(string_to_search: str = None, get_onylDatabase: bool = False):
def process_search_result(select_title):
""" """
Main function of the application for film and series. Handles the search result and initiates the download for either a film or series.
""" """
download_title(select_title)
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
if string_to_search is None: if string_to_search is None:
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip() string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
# Search on database # Perform the database search
len_database = title_search(quote_plus(string_to_search)) len_database = title_search(quote_plus(string_to_search))
# Return list of elements # If only the database is needed, return the manager
if get_onylDatabase: if get_onlyDatabase:
return media_search_manager return media_search_manager
if len_database > 0: if len_database > 0:
# Select title from list
select_title = get_select_title(table_show_manager, media_search_manager) select_title = get_select_title(table_show_manager, media_search_manager)
# Download title
download_title(select_title) download_title(select_title)
else: else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry # If no results are found, ask again
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
search() search()

View File

@ -62,7 +62,8 @@ def title_search(word_to_search: str) -> int:
'seader': tr.find_all("td")[-5].get_text(strip=True), 'seader': tr.find_all("td")[-5].get_text(strip=True),
'leacher': tr.find_all("td")[-4].get_text(strip=True), 'leacher': tr.find_all("td")[-4].get_text(strip=True),
'date': tr.find_all("td")[-3].get_text(strip=True).replace("'", ""), 'date': tr.find_all("td")[-3].get_text(strip=True).replace("'", ""),
'size': tr.find_all("td")[-2].get_text(strip=True) 'size': tr.find_all("td")[-2].get_text(strip=True),
'type': 'torrent'
} }
media_search_manager.add_media(title_info) media_search_manager.add_media(title_info)

View File

@ -2,19 +2,22 @@
import sys import sys
import subprocess import subprocess
from urllib.parse import quote_plus from urllib.parse import quote_plus
# External library # External library
from rich.console import Console from rich.console import Console
from rich.prompt import Prompt from rich.prompt import Prompt
# Internal utilities # Internal utilities
from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
# Logic class # Logic class
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
from .site import title_search, table_show_manager, media_search_manager from .site import title_search, table_show_manager, media_search_manager
from .film import download_film from .film import download_film
from .series import download_series from .series import download_series
@ -30,51 +33,76 @@ msg = Prompt()
console = Console() console = Console()
def search(string_to_search: str = None, get_onylDatabase: bool = False): def get_user_input(string_to_search: str = None):
""" """
Main function of the application for film and series. Asks the user to input a search term.
Handles both Telegram bot input and direct input.
""" """
if string_to_search is None:
if site_constant.TELEGRAM_BOT: if site_constant.TELEGRAM_BOT:
bot = get_bot_instance() bot = get_bot_instance()
if string_to_search is None:
# Chiedi la scelta all'utente con il bot Telegram
string_to_search = bot.ask( string_to_search = bot.ask(
"key_search", "key_search",
f"Inserisci la parola da cercare\noppure back per tornare alla scelta: ", f"Enter the search term\nor type 'back' to return to the menu: ",
None None
) )
if string_to_search == 'back': if string_to_search == 'back':
# Riavvia lo script
# Chiude il processo attuale e avvia una nuova istanza dello script # Restart the script
subprocess.Popen([sys.executable] + sys.argv) subprocess.Popen([sys.executable] + sys.argv)
sys.exit() sys.exit()
else: else:
if string_to_search is None: string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
len_database = title_search(quote_plus(string_to_search)) return string_to_search
# Return list of elements
if get_onylDatabase:
return media_search_manager
if len_database > 0:
# Select title from list
select_title = get_select_title(table_show_manager, media_search_manager)
def process_search_result(select_title):
"""
Handles the search result and initiates the download for either a film or series.
"""
if select_title.type == 'tv': if select_title.type == 'tv':
download_series(select_title) download_series(select_title)
else: else:
download_film(select_title) download_film(select_title)
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
# Get the user input for the search term
string_to_search = get_user_input(string_to_search)
# Perform the database search
len_database = title_search(quote_plus(string_to_search))
# If only the database is needed, return the manager
if get_onlyDatabase:
return media_search_manager
if site_constant.TELEGRAM_BOT:
bot = get_bot_instance()
if len_database > 0:
select_title = get_select_title(table_show_manager, media_search_manager)
process_search_result(select_title)
else: else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry if site_constant.TELEGRAM_BOT:
bot.send_message(f"No results found, please try again", None)
# If no results are found, ask again
string_to_search = get_user_input()
search() search()

View File

@ -1,4 +1,4 @@
# 3.12.23 # 16.03.25
import os import os
@ -8,13 +8,14 @@ import httpx
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from rich.console import Console from rich.console import Console
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession
# Internal utilities # Internal utilities
from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.os import os_manager
from StreamingCommunity.Util.message import start_message from StreamingCommunity.Util.message import start_message
from StreamingCommunity.Util.headers import get_headers from StreamingCommunity.Util.headers import get_headers
from StreamingCommunity.Util.config_json import config_manager from StreamingCommunity.Util.config_json import config_manager
from StreamingCommunity.Lib.Downloader import HLS_Downloader from StreamingCommunity.Lib.Downloader import HLS_Downloader
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession
# Logic class # Logic class

View File

@ -1,4 +1,4 @@
# 3.12.23 # 16.03.25
import os import os
from typing import Tuple from typing import Tuple

View File

@ -1,4 +1,4 @@
# 10.12.23 # 16.03.25
# External libraries # External libraries
@ -6,11 +6,12 @@ import httpx
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from rich.console import Console from rich.console import Console
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
# Internal utilities # Internal utilities
from StreamingCommunity.Util.config_json import config_manager from StreamingCommunity.Util.config_json import config_manager
from StreamingCommunity.Util.headers import get_userAgent from StreamingCommunity.Util.headers import get_userAgent
from StreamingCommunity.Util.table import TVShowManager from StreamingCommunity.Util.table import TVShowManager
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
# Logic class # Logic class
@ -61,10 +62,8 @@ def title_search(title_search: str) -> int:
# Create soup istance # Create soup istance
soup = BeautifulSoup(response.text, "html.parser") soup = BeautifulSoup(response.text, "html.parser")
i = 0
# Collect data from soup # Collect data from soup
for movie_div in soup.find_all("div", class_="movie"): for i, movie_div in enumerate(soup.find_all("div", class_="movie")):
title_tag = movie_div.find("h2", class_="movie-title") title_tag = movie_div.find("h2", class_="movie-title")
title = title_tag.find("a").get_text(strip=True) title = title_tag.find("a").get_text(strip=True)
@ -86,8 +85,6 @@ def title_search(title_search: str) -> int:
choice_text = f"{i} - {title} ({tipo})" choice_text = f"{i} - {title} ({tipo})"
choices.append(choice_text) choices.append(choice_text)
i += 1
if site_constant.TELEGRAM_BOT: if site_constant.TELEGRAM_BOT:
if choices: if choices:
bot.send_message(f"Lista dei risultati:", choices) bot.send_message(f"Lista dei risultati:", choices)

View File

@ -1,4 +1,4 @@
# 01.03.24 # 16.03.25
# External libraries # External libraries
import httpx import httpx

View File

@ -11,11 +11,12 @@ from rich.prompt import Prompt
# Internal utilities # Internal utilities
from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
# Logic class # Logic class
from StreamingCommunity.Api.Template.config_loader import site_constant
from .site import title_search, media_search_manager, table_show_manager from .site import title_search, media_search_manager, table_show_manager
from .film_serie import download_film, download_series from .film_serie import download_film, download_series
@ -31,53 +32,73 @@ msg = Prompt()
console = Console() console = Console()
def search(string_to_search: str = None, get_onylDatabase: bool = False): def get_user_input(string_to_search: str = None):
"""
Asks the user to input a search term.
Handles both Telegram bot input and direct input.
"""
if string_to_search is None:
if site_constant.TELEGRAM_BOT: if site_constant.TELEGRAM_BOT:
bot = get_bot_instance() bot = get_bot_instance()
if string_to_search is None:
# Chiedi la scelta all'utente con il bot Telegram
string_to_search = bot.ask( string_to_search = bot.ask(
"key_search", "key_search",
f"Inserisci la parola da cercare\noppure back per tornare alla scelta: ", f"Enter the search term\nor type 'back' to return to the menu: ",
None None
) )
if string_to_search == 'back': if string_to_search == 'back':
# Riavvia lo script
# Chiude il processo attuale e avvia una nuova istanza dello script # Restart the script
subprocess.Popen([sys.executable] + sys.argv) subprocess.Popen([sys.executable] + sys.argv)
sys.exit() sys.exit()
else: else:
if string_to_search is None: string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
# Search on database return string_to_search
len_database = title_search(string_to_search)
# Return list of elements def process_search_result(select_title):
if get_onylDatabase: """
return media_search_manager Handles the search result and initiates the download for either a film or series.
"""
if len_database > 0:
# Select title from list (type: TV \ Movie \ OVA)
select_title = get_select_title(table_show_manager, media_search_manager)
if select_title.type == 'Movie' or select_title.type == 'OVA':
download_film(select_title)
else:
download_series(select_title) download_series(select_title)
else: def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
if site_constant.TELEGRAM_BOT: """
bot.send_message(f"Nessun risultato trovato riprova", None) Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onlyDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
# Get the user input for the search term
string_to_search = get_user_input(string_to_search)
# Perform the database search
len_database = title_search(string_to_search)
##If only the database is needed, return the manager
if get_onlyDatabase:
return media_search_manager
if site_constant.TELEGRAM_BOT:
bot = get_bot_instance()
if len_database > 0:
select_title = get_select_title(table_show_manager, media_search_manager)
process_search_result(select_title)
else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry if site_constant.TELEGRAM_BOT:
bot.send_message(f"No results found, please try again", None)
# If no results are found, ask again
string_to_search = get_user_input()
search() search()

View File

@ -77,10 +77,8 @@ def get_real_title(record):
""" """
if record['title_eng'] is not None: if record['title_eng'] is not None:
return record['title_eng'] return record['title_eng']
elif record['title'] is not None: elif record['title'] is not None:
return record['title'] return record['title']
else: else:
return record['title_it'] return record['title_it']

View File

@ -10,10 +10,11 @@ from rich.prompt import Prompt
# Internal utilities # Internal utilities
from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Logic class # Logic class
from StreamingCommunity.Api.Template.config_loader import site_constant
from .site import title_search, media_search_manager, table_show_manager from .site import title_search, media_search_manager, table_show_manager
from .film import download_film from .film import download_film
@ -29,10 +30,26 @@ msg = Prompt()
console = Console() console = Console()
def search(string_to_search: str = None, get_onylDatabase: bool = False): def process_search_result(select_title):
""" """
Main function of the application for film and series. Handles the search result and initiates the download for either a film or series.
""" """
# !!! ADD TYPE DONT WORK FOR SERIE
download_film(select_title)
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
if string_to_search is None: if string_to_search is None:
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip() 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):
# Search on database # Search on database
len_database = title_search(quote_plus(string_to_search)) len_database = title_search(quote_plus(string_to_search))
# Return list of elements ## If only the database is needed, return the manager
if get_onylDatabase: if get_onlyDatabase:
return media_search_manager return media_search_manager
if len_database > 0: if len_database > 0:
# Select title from list
select_title = get_select_title(table_show_manager, media_search_manager) select_title = get_select_title(table_show_manager, media_search_manager)
process_search_result(select_title)
# !!! ADD TYPE DONT WORK FOR SERIE
download_film(select_title)
else: else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry # If no results are found, ask again
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
search() search()

View File

@ -62,7 +62,8 @@ def title_search(word_to_search: str) -> int:
title_info = { title_info = {
'name': title, 'name': title,
'url': url 'url': url,
'type': 'film'
} }
media_search_manager.add_media(title_info) media_search_manager.add_media(title_info)

View File

@ -12,6 +12,7 @@ from rich.prompt import Prompt
# Internal utilities # Internal utilities
from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Logic class # Logic class
@ -30,10 +31,28 @@ msg = Prompt()
console = Console() console = Console()
def search(string_to_search: str = None, get_onylDatabase: bool = False): def process_search_result(select_title):
""" """
Main function of the application for film and series. Handles the search result and initiates the download for either a film or series.
""" """
if "Serie TV" in str(select_title.type):
download_thread(select_title)
else:
logging.error(f"Not supported: {select_title.type}")
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
if string_to_search is None: if string_to_search is None:
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip() string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
@ -41,24 +60,16 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
# Search on database # Search on database
len_database = title_search(quote_plus(string_to_search)) len_database = title_search(quote_plus(string_to_search))
# Return list of elements # If only the database is needed, return the manager
if get_onylDatabase: if get_onlyDatabase:
return media_search_manager return media_search_manager
if len_database > 0: if len_database > 0:
# Select title from list
select_title = get_select_title(table_show_manager, media_search_manager) select_title = get_select_title(table_show_manager, media_search_manager)
process_search_result(select_title)
# Download only film
if "Serie TV" in str(select_title.type):
download_thread(select_title)
else: else:
logging.error(f"Not supported: {select_title.type}")
else: # If no results are found, ask again
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry
search() search()

View File

@ -11,6 +11,7 @@ from rich.prompt import Prompt
# Internal utilities # Internal utilities
from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Logic class # Logic class
@ -29,10 +30,25 @@ msg = Prompt()
console = Console() console = Console()
def search(string_to_search: str = None, get_onylDatabase: bool = False): def process_search_result(select_title):
""" """
Main function of the application for film and series. Handles the search result and initiates the download for either a film or series.
""" """
download_series(select_title)
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
if string_to_search is None: if string_to_search is None:
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip() string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
@ -40,20 +56,16 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
# Search on database # Search on database
len_database = title_search(quote_plus(string_to_search)) len_database = title_search(quote_plus(string_to_search))
# Return list of elements # If only the database is needed, return the manager
if get_onylDatabase: if get_onlyDatabase:
return media_search_manager return media_search_manager
if len_database > 0: if len_database > 0:
# Select title from list
select_title = get_select_title(table_show_manager, media_search_manager) select_title = get_select_title(table_show_manager, media_search_manager)
process_search_result(select_title)
# Download only film
download_series(select_title)
else: else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry # If no results are found, ask again
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
search() search()

View File

@ -63,7 +63,8 @@ def title_search(word_to_search: str) -> int:
serie_info = { serie_info = {
'name': title, 'name': title,
'url': link 'url': link,
'type': 'tv'
} }
media_search_manager.add_media(serie_info) media_search_manager.add_media(serie_info)

View File

@ -5,12 +5,16 @@ from urllib.parse import quote_plus
# External library # External library
from rich.console import Console from rich.console import Console
from rich.prompt import Prompt, Confirm from rich.prompt import Prompt
# Internal utilities
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
from StreamingCommunity.Lib.TMBD import tmdb, Json_film
# Logic class # Logic class
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Lib.TMBD import tmdb, Json_film
from .film import download_film from .film import download_film
@ -25,16 +29,32 @@ msg = Prompt()
console = Console() console = Console()
def search(string_to_search: str = None, get_onylDatabase: bool = False): def process_search_result(select_title):
""" """
Main function of the application for film and series. Handles the search result and initiates the download for either a film or series.
""" """
download_film(select_title)
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
if string_to_search is None: if string_to_search is None:
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip() string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
# Not available for the moment # Not available for the moment
if get_onylDatabase: if get_onlyDatabase:
return 0 return 0
# Search on database # Search on database
@ -47,7 +67,7 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
download_film(movie_details) download_film(movie_details)
else: else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry # If no results are found, ask again
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
search() search()

View File

@ -12,11 +12,12 @@ from rich.prompt import Prompt
# Internal utilities # Internal utilities
from StreamingCommunity.Api.Template import get_select_title from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.config_loader import site_constant
from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance
# Logic class # Logic class
from StreamingCommunity.Api.Template.config_loader import site_constant
from .site import title_search, table_show_manager, media_search_manager from .site import title_search, table_show_manager, media_search_manager
from .film import download_film from .film import download_film
from .series import download_series from .series import download_series
@ -33,54 +34,76 @@ msg = Prompt()
console = Console() console = Console()
def search(string_to_search: str = None, get_onylDatabase: bool = False): def get_user_input(string_to_search: str = None):
""" """
Main function of the application for film and series. Asks the user to input a search term.
Handles both Telegram bot input and direct input.
""" """
if string_to_search is None:
if site_constant.TELEGRAM_BOT: if site_constant.TELEGRAM_BOT:
bot = get_bot_instance() bot = get_bot_instance()
if string_to_search is None:
# Chiedi la scelta all'utente con il bot Telegram
string_to_search = bot.ask( string_to_search = bot.ask(
"key_search", "key_search",
f"Inserisci la parola da cercare\noppure back per tornare alla scelta: ", f"Enter the search term\nor type 'back' to return to the menu: ",
None None
) )
if string_to_search == 'back': if string_to_search == 'back':
# Riavvia lo script
# Chiude il processo attuale e avvia una nuova istanza dello script # Restart the script
subprocess.Popen([sys.executable] + sys.argv) subprocess.Popen([sys.executable] + sys.argv)
sys.exit() sys.exit()
else: else:
if string_to_search is None: string_to_search = msg.ask(f"\n[purple]Insert a word to search in [green]{site_constant.SITE_NAME}").strip()
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{site_constant.SITE_NAME}").strip()
len_database = title_search(quote_plus(string_to_search)) return string_to_search
# Return list of elements
if get_onylDatabase:
return media_search_manager
if len_database > 0:
# Select title from list
select_title = get_select_title(table_show_manager, media_search_manager)
def process_search_result(select_title):
"""
Handles the search result and initiates the download for either a film or series.
"""
if select_title.type == 'tv': if select_title.type == 'tv':
download_series(select_title) download_series(select_title)
else: else:
download_film(select_title) download_film(select_title)
def search(string_to_search: str = None, get_onlyDatabase: bool = False, direct_item: dict = None):
"""
Main function of the application for search film, series and anime.
Parameters:
string_to_search (str, optional): String to search for
get_onylDatabase (bool, optional): If True, return only the database object
direct_item (dict, optional): Direct item to process (bypass search)
"""
if direct_item:
select_title = MediaItem(**direct_item)
process_search_result(select_title)
return
# Get the user input for the search term
string_to_search = get_user_input(string_to_search)
# Perform the database search
len_database = title_search(quote_plus(string_to_search))
# If only the database is needed, return the manager
if get_onlyDatabase:
return media_search_manager
if site_constant.TELEGRAM_BOT:
bot = get_bot_instance()
if len_database > 0:
select_title = get_select_title(table_show_manager, media_search_manager)
process_search_result(select_title)
else: else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}") console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
if site_constant.TELEGRAM_BOT: if site_constant.TELEGRAM_BOT:
bot.send_message(f"Nessun risultato trovato riprova", None) bot.send_message(f"No results found, please try again", None)
# Retry # If no results are found, ask again
string_to_search = get_user_input()
search() search()

View File

@ -1,5 +1,5 @@
__title__ = 'StreamingCommunity' __title__ = 'StreamingCommunity'
__version__ = '2.9.4' __version__ = '2.9.5'
__author__ = 'Arrowar' __author__ = 'Arrowar'
__description__ = 'A command-line program to download film' __description__ = 'A command-line program to download film'
__copyright__ = 'Copyright 2024' __copyright__ = 'Copyright 2024'

View File

@ -0,0 +1,315 @@
# 17.03.25
import os
import sys
import time
import glob
import logging
import importlib
# External library
from rich.console import Console
from rich.prompt import Prompt
from rich.table import Table
from rich.progress import Progress
# Internal utilities
from StreamingCommunity.Util.message import start_message
# Variable
console = Console()
msg = Prompt()
# !!! DA METTERE IN COMUNE CON QUELLA DI RUN
def load_search_functions():
modules = []
loaded_functions = {}
excluded_sites = set()
# Find api home directory
if getattr(sys, 'frozen', False): # Modalità PyInstaller
base_path = os.path.join(sys._MEIPASS, "StreamingCommunity")
else:
base_path = os.path.dirname(__file__)
api_dir = os.path.join(base_path, 'Api', 'Site')
init_files = glob.glob(os.path.join(api_dir, '*', '__init__.py'))
# Retrieve modules and their indices
for init_file in init_files:
# Get folder name as module name
module_name = os.path.basename(os.path.dirname(init_file))
# Se il modulo è nella lista da escludere, saltalo
if module_name in excluded_sites:
continue
logging.info(f"Load module name: {module_name}")
try:
# Dynamically import the module
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
# Get 'indice' from the module
indice = getattr(mod, 'indice', 0)
is_deprecate = bool(getattr(mod, '_deprecate', True))
use_for = getattr(mod, '_useFor', 'other')
if not is_deprecate:
modules.append((module_name, indice, use_for))
except Exception as e:
console.print(f"[red]Failed to import module {module_name}: {str(e)}")
# Sort modules by 'indice'
modules.sort(key=lambda x: x[1])
# Load search functions in the sorted order
for module_name, _, use_for in modules:
# Construct a unique alias for the module
module_alias = f'{module_name}_search'
try:
# Dynamically import the module
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
# Get the search function from the module (assuming the function is named 'search' and defined in __init__.py)
search_function = getattr(mod, 'search')
# Add the function to the loaded functions dictionary
loaded_functions[module_alias] = (search_function, use_for)
except Exception as e:
console.print(f"[red]Failed to load search function from module {module_name}: {str(e)}")
return loaded_functions
def global_search(search_terms: str = None, selected_sites: list = None):
"""
Perform a search across multiple sites based on selection.
Parameters:
search_terms (str, optional): The terms to search for. If None, will prompt the user.
selected_sites (list, optional): List of site aliases to search. If None, will search all sites.
Returns:
dict: Consolidated search results from all searched sites.
"""
search_functions = load_search_functions()
all_results = {}
if search_terms is None:
search_terms = msg.ask("\n[purple]Enter search terms for global search: ").strip()
# Organize sites by category for better display
sites_by_category = {}
for alias, (func, category) in search_functions.items():
if category not in sites_by_category:
sites_by_category[category] = []
sites_by_category[category].append((alias, func))
# If no sites are specifically selected, prompt the user
if selected_sites is None:
console.print("\n[bold green]Select sites to search:[/bold green]")
console.print("[bold cyan]1.[/bold cyan] Search all sites")
console.print("[bold cyan]2.[/bold cyan] Search by category")
console.print("[bold cyan]3.[/bold cyan] Select specific sites")
choice = msg.ask("[green]Enter your choice (1-3)", choices=["1", "2", "3"], default="1")
if choice == "1":
# Search all sites
selected_sites = list(search_functions.keys())
elif choice == "2":
# Search by category
console.print("\n[bold green]Select categories to search:[/bold green]")
for i, category in enumerate(sites_by_category.keys(), 1):
console.print(f"[bold cyan]{i}.[/bold cyan] {category.capitalize()}")
category_choices = msg.ask("[green]Enter category numbers separated by commas", default="1")
selected_categories = [list(sites_by_category.keys())[int(c.strip())-1] for c in category_choices.split(",")]
selected_sites = []
for category in selected_categories:
for alias, _ in sites_by_category.get(category, []):
selected_sites.append(alias)
else:
# Select specific sites
console.print("\n[bold green]Select specific sites to search:[/bold green]")
for i, (alias, _) in enumerate(search_functions.items(), 1):
site_name = alias.split("_")[0].capitalize()
console.print(f"[bold cyan]{i}.[/bold cyan] {site_name}")
site_choices = msg.ask("[green]Enter site numbers separated by commas", default="1")
selected_indices = [int(c.strip())-1 for c in site_choices.split(",")]
selected_sites = [list(search_functions.keys())[i] for i in selected_indices if i < len(search_functions)]
# Display progress information
console.print(f"\n[bold green]Searching for:[/bold green] [yellow]{search_terms}[/yellow]")
console.print(f"[bold green]Searching across:[/bold green] {len(selected_sites)} sites")
with Progress() as progress:
search_task = progress.add_task("[cyan]Searching...", total=len(selected_sites))
# Search each selected site
for alias in selected_sites:
site_name = alias.split("_")[0].capitalize()
progress.update(search_task, description=f"[cyan]Searching {site_name}...")
func, _ = search_functions[alias]
try:
# Call the search function with get_onlyDatabase=True to get database object
database = func(search_terms, get_onlyDatabase=True)
# Check if database has media_list attribute and it's not empty
if database and hasattr(database, 'media_list') and len(database.media_list) > 0:
# Store media_list items with additional source information
all_results[alias] = []
for element in database.media_list:
# Convert element to dictionary if it's an object
if hasattr(element, '__dict__'):
item_dict = element.__dict__.copy()
else:
item_dict = {} # Fallback for non-object items
# Add source information
item_dict['source'] = site_name
item_dict['source_alias'] = alias
all_results[alias].append(item_dict)
console.print(f"[green]Found {len(database.media_list)} results from {site_name}")
except Exception as e:
console.print(f"[bold red]Error searching {site_name}:[/bold red] {str(e)}")
progress.update(search_task, advance=1)
# Display the consolidated results
if all_results:
all_media_items = []
for alias, results in all_results.items():
for item in results:
all_media_items.append(item)
# Display consolidated results
display_consolidated_results(all_media_items, search_terms)
# Allow user to select an item
selected_item = select_from_consolidated_results(all_media_items)
if selected_item:
# Process the selected item - download or further actions
process_selected_item(selected_item, search_functions)
else:
console.print(f"\n[bold red]No results found for:[/bold red] [yellow]{search_terms}[/yellow]")
# Optionally offer to search again or return to main menu
if msg.ask("[green]Search again? (y/n)", choices=["y", "n"], default="y") == "y":
global_search()
return all_results
def display_consolidated_results(all_media_items, search_terms):
"""
Display consolidated search results from multiple sites.
Parameters:
all_media_items (list): List of media items from all searched sites.
search_terms (str): The search terms used.
"""
time.sleep(1)
start_message()
console.print(f"\n[bold green]Search results for:[/bold green] [yellow]{search_terms}[/yellow] \n")
table = Table(show_header=True, header_style="bold cyan")
table.add_column("#", style="dim", width=4)
table.add_column("Title", min_width=20)
table.add_column("Type", width=15)
table.add_column("Source", width=25)
for i, item in enumerate(all_media_items, 1):
# Extract values from item dict, with fallbacks if keys don't exist
title = item.get('title', item.get('name', 'Unknown'))
media_type = item.get('type', item.get('media_type', 'Unknown'))
source = item.get('source', 'Unknown')
table.add_row(
str(i),
str(title),
str(media_type),
str(source),
)
console.print(table)
def select_from_consolidated_results(all_media_items):
"""
Allow user to select an item from consolidated results.
Parameters:
all_media_items (list): List of media items from all searched sites.
Returns:
dict: The selected media item or None if no selection was made.
"""
if not all_media_items:
return None
max_index = len(all_media_items)
choice = msg.ask(
f"[green]Select item # (1-{max_index}) or 0 to cancel",
choices=[str(i) for i in range(max_index + 1)],
default="1",
show_choices=False
)
if choice == "0":
return None
return all_media_items[int(choice) - 1]
def process_selected_item(selected_item, search_functions):
"""
Process the selected item - download the media using the appropriate site API.
Parameters:
selected_item (dict): The selected media item.
search_functions (dict): Dictionary of search functions by alias.
"""
source_alias = selected_item.get('source_alias')
if not source_alias or source_alias not in search_functions:
console.print("[bold red]Error: Cannot process this item - source information missing.[/bold red]")
return
# Get the appropriate search function for this source
func, _ = search_functions[source_alias]
console.print(f"\n[bold green]Processing selection from:[/bold green] {selected_item.get('source')}")
# Extract necessary information to pass to the site's search function
item_id = selected_item.get('id', selected_item.get('media_id'))
item_type = selected_item.get('type', selected_item.get('media_type', 'unknown'))
item_title = selected_item.get('title', selected_item.get('name', 'Unknown'))
if item_id:
console.print(f"[bold green]Selected item:[/bold green] {item_title} (ID: {item_id}, Type: {item_type})")
# Call the site's search function with direct_item parameter to process download
try:
func(direct_item=selected_item)
except Exception as e:
console.print(f"[bold red]Error processing download:[/bold red] {str(e)}")
else:
console.print("[bold red]Error: Item ID not found.[/bold red]")

View File

@ -18,6 +18,7 @@ from rich.prompt import Prompt
# Internal utilities # Internal utilities
from .global_search import global_search
from StreamingCommunity.Util.message import start_message from StreamingCommunity.Util.message import start_message
from StreamingCommunity.Util.config_json import config_manager from StreamingCommunity.Util.config_json import config_manager
from StreamingCommunity.Util.os import os_summary from StreamingCommunity.Util.os import os_summary
@ -54,6 +55,7 @@ def run_function(func: Callable[..., None], close_console: bool = False, search_
func(search_terms) func(search_terms)
# !!! DA METTERE IN COMUNE CON QUELLA DI GLOBAL
def load_search_functions(): def load_search_functions():
modules = [] modules = []
loaded_functions = {} loaded_functions = {}
@ -237,6 +239,11 @@ def main(script_id = 0):
'--specific_list_subtitles', type=str, help='Comma-separated list of specific subtitle languages to download (e.g., eng,spa).' '--specific_list_subtitles', type=str, help='Comma-separated list of specific subtitle languages to download (e.g., eng,spa).'
) )
# Add global search option
parser.add_argument(
'--global', action='store_true', help='Perform a global search across multiple sites.'
)
# Add arguments for search functions # Add arguments for search functions
color_map = { color_map = {
"anime": "red", "anime": "red",
@ -253,6 +260,7 @@ def main(script_id = 0):
parser.add_argument(f'-{short_option}', f'--{long_option}', action='store_true', help=f'Search for {alias.split("_")[0]} on streaming platforms.') parser.add_argument(f'-{short_option}', f'--{long_option}', action='store_true', help=f'Search for {alias.split("_")[0]} on streaming platforms.')
parser.add_argument('-s', '--search', default=None, help='Search terms') parser.add_argument('-s', '--search', default=None, help='Search terms')
# Parse command-line arguments # Parse command-line arguments
args = parser.parse_args() args = parser.parse_args()
@ -280,6 +288,11 @@ def main(script_id = 0):
config_manager.write_config() config_manager.write_config()
# Check if global search is requested
if getattr(args, 'global'):
global_search(search_terms)
return
# Map command-line arguments to functions # Map command-line arguments to functions
arg_to_function = {alias: func for alias, (func, _) in search_functions.items()} arg_to_function = {alias: func for alias, (func, _) in search_functions.items()}
@ -295,13 +308,18 @@ def main(script_id = 0):
# Create dynamic prompt message and choices # Create dynamic prompt message and choices
choice_labels = {str(i): (alias.split("_")[0].capitalize(), use_for) for i, (alias, (_, use_for)) in enumerate(search_functions.items())} choice_labels = {str(i): (alias.split("_")[0].capitalize(), use_for) for i, (alias, (_, use_for)) in enumerate(search_functions.items())}
# Add global search option to the menu
#global_search_key = str(len(choice_labels))
#choice_labels[global_search_key] = ("Global Search", "all")
#input_to_function[global_search_key] = global_search
# Display the category legend in a single line # Display the category legend in a single line
legend_text = " | ".join([f"[{color}]{category.capitalize()}[/{color}]" for category, color in color_map.items()]) legend_text = " | ".join([f"[{color}]{category.capitalize()}[/{color}]" for category, color in color_map.items()])
console.print(f"\n[bold green]Category Legend:[/bold green] {legend_text}") console.print(f"\n[bold green]Category Legend:[/bold green] {legend_text}")
# Construct the prompt message with color-coded site names # Construct the prompt message with color-coded site names
prompt_message = "[green]Insert category [white](" + ", ".join( prompt_message = "[green]Insert category [white](" + ", ".join(
[f"{key}: [{color_map[label[1]]}]{label[0]}[/{color_map[label[1]]}]" for key, label in choice_labels.items()] [f"{key}: [{color_map.get(label[1], 'white')}]{label[0]}[/{color_map.get(label[1], 'white')}]" for key, label in choice_labels.items()]
) + "[white])" ) + "[white])"
if TELEGRAM_BOT: if TELEGRAM_BOT:
@ -330,10 +348,16 @@ def main(script_id = 0):
# Run the corresponding function based on user input # Run the corresponding function based on user input
if category in input_to_function: if category in input_to_function:
run_function(input_to_function[category], search_terms = args.search) """if category == global_search_key:
# Run global search
run_function(input_to_function[category], search_terms=search_terms)
else:"""
# Run normal site-specific search
run_function(input_to_function[category], search_terms=search_terms)
else: else:
if TELEGRAM_BOT: if TELEGRAM_BOT:
bot.send_message(f"Categoria non valida", None) bot.send_message(f"Categoria non valida", None)

View File

@ -1,116 +0,0 @@
# 12.11.24
# Fix import
import os
import sys
src_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.append(src_path)
# Other
import glob
import logging
import importlib
from rich.console import Console
# Other import
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Variable
console = Console()
def load_search_functions():
modules = []
loaded_functions = {}
# Traverse the Api directory
api_dir = os.path.join(os.path.dirname(__file__), '..', 'StreamingCommunity', 'Api', 'Site')
init_files = glob.glob(os.path.join(api_dir, '*', '__init__.py'))
logging.info(f"Base folder path: {api_dir}")
logging.info(f"Api module path: {init_files}")
# Retrieve modules and their indices
for init_file in init_files:
# Get folder name as module name
module_name = os.path.basename(os.path.dirname(init_file))
logging.info(f"Load module name: {module_name}")
try:
# Dynamically import the module
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
# Get 'indice' from the module
indice = getattr(mod, 'indice', 0)
is_deprecate = bool(getattr(mod, '_deprecate', True))
use_for = getattr(mod, '_useFor', 'other')
if not is_deprecate:
modules.append((module_name, indice, use_for))
except Exception as e:
console.print(f"[red]Failed to import module {module_name}: {str(e)}")
# Sort modules by 'indice'
modules.sort(key=lambda x: x[1])
# Load search functions in the sorted order
for module_name, _, use_for in modules:
# Construct a unique alias for the module
module_alias = f'{module_name}_search'
logging.info(f"Module alias: {module_alias}")
try:
# Dynamically import the module
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
# Get the search function from the module (assuming the function is named 'search' and defined in __init__.py)
search_function = getattr(mod, 'search')
# Add the function to the loaded functions dictionary
loaded_functions[module_alias] = (search_function, use_for)
except Exception as e:
console.print(f"[red]Failed to load search function from module {module_name}: {str(e)}")
return loaded_functions
def search_all_sites(loaded_functions, search_string, max_sites=10):
total_len_database = 0
site_count = 0
for module_alias, (search_function, use_for) in loaded_functions.items():
if max_sites is not None and site_count >= max_sites:
break
console.print(f"\n[blue]Searching in module: {module_alias} [white](Use for: {use_for})")
try:
database: MediaManager = search_function(search_string, get_onylDatabase=True)
len_database = len(database.media_list)
for element in database.media_list:
print(element.__dict__)
console.print(f"[green]Database length for {module_alias}: {len_database}")
total_len_database += len_database
site_count += 1
except Exception as e:
console.print(f"[red]Error while executing search function for {module_alias}: {str(e)}")
return total_len_database
# Main
search_string = "cars"
loaded_functions = load_search_functions()
total_len = search_all_sites(loaded_functions, search_string)
console.print(f"\n[cyan]Total number of results from all sites: {total_len}")

View File

@ -10,7 +10,7 @@ with open(os.path.join(os.path.dirname(__file__), "requirements.txt"), "r", enco
setup( setup(
name="StreamingCommunity", name="StreamingCommunity",
version="2.9.4", version="2.9.5",
long_description=read_readme(), long_description=read_readme(),
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
author="Lovi-0", author="Lovi-0",