diff --git a/.gitignore b/.gitignore
index fb8905f..177124e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,4 +47,7 @@ venv.bak/
Video
note.txt
list_proxy.txt
-cmd.txt
\ No newline at end of file
+cmd.txt
+bot_config.json
+active_requests.json
+scripts.json
diff --git a/README.md b/README.md
index 2a17245..4d419dc 100644
--- a/README.md
+++ b/README.md
@@ -24,9 +24,6 @@
-
-
-
# 📋 Table of Contents
@@ -39,6 +36,7 @@
- 📝 [Manual Installation](#3-manual-installation)
- 💻 [Win 7](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Installation#win-7)
- 📱 [Termux](https://github.com/Ghost6446/StreamingCommunity_api/wiki/Termux)
+ - 📝 [Telegram Usage](#4-telegram-usage)
- ⚙️ [Configuration](#configuration)
- 🔧 [Default](#default-settings)
- 📩 [Request](#requests-settings)
@@ -258,6 +256,34 @@ python3 update.py
+## 4. Telegram Usage
+
+### Configuration
+
+You need to create an .env file and enter your Telegram token
+
+and user ID to authorize only one user to use it
+
+### .env Example:
+
+```
+TOKEN_TELEGRAM=IlTuo2131TOKEN$12D3Telegram
+AUTHORIZED_USER_ID=12345678
+DEBUG=False
+```
+
+### Install Python Dependencies
+
+```bash
+pip install -r requirements.txt
+```
+
+### On Linux/MacOS:
+
+```bash
+python3 telegram_bot.py
+```
+
# Configuration
You can change some behaviors by tweaking the configuration file.
@@ -471,12 +497,12 @@ The `run-container` command mounts also the `config.json` file, so any change to
| Website | Status |
|:-------------------|:------:|
| [1337xx](https://1337xx.to/) | ✅ |
-| [AltadefinizioneGratis](https://altadefinizionegratis.site/) | ✅ |
+| [AltadefinizioneGratis](https://altadefinizionegratis.info/) | ✅ |
| [AnimeUnity](https://animeunity.so/) | ✅ |
| [Ilcorsaronero](https://ilcorsaronero.link/) | ✅ |
| [CB01New](https://cb01new.video/) | ✅ |
| [DDLStreamItaly](https://ddlstreamitaly.co/) | ✅ |
-| [GuardaSerie](https://guardaserie.meme/) | ✅ |
+| [GuardaSerie](https://guardaserie.academy/) | ✅ |
| [MostraGuarda](https://mostraguarda.stream/) | ✅ |
| [StreamingCommunity](https://streamingcommunity.paris/) | ✅ |
diff --git a/StreamingCommunity/Api/Site/1337xx/__init__.py b/StreamingCommunity/Api/Site/1337xx/__init__.py
index 9a9ee35..4652162 100644
--- a/StreamingCommunity/Api/Site/1337xx/__init__.py
+++ b/StreamingCommunity/Api/Site/1337xx/__init__.py
@@ -11,6 +11,12 @@ from StreamingCommunity.Util.console import console, msg
from .site import title_search, run_get_select_title, media_search_manager
from .title import download_title
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+import sys
+import subprocess
# Variable
indice = 8
@@ -25,10 +31,25 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
"""
Main function of the application for film and series.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
- if string_to_search is None:
+ # Chiedi la scelta all'utente con il bot Telegram
+ string_to_search = bot.ask(
+ "key_search",
+ f"Inserisci la parola da cercare\noppure 🔙 back per tornare alla scelta: ",
+ None
+ )
+
+ if string_to_search == 'back':
+ # Riavvia lo script
+ # Chiude il processo attuale e avvia una nuova istanza dello script
+ subprocess.Popen([sys.executable] + sys.argv)
+ sys.exit()
+ else:
+ if string_to_search is None:
string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
-
+
# Search on database
len_database = title_search(quote_plus(string_to_search))
diff --git a/StreamingCommunity/Api/Site/1337xx/site.py b/StreamingCommunity/Api/Site/1337xx/site.py
index 4ad4590..6f50e23 100644
--- a/StreamingCommunity/Api/Site/1337xx/site.py
+++ b/StreamingCommunity/Api/Site/1337xx/site.py
@@ -17,6 +17,10 @@ from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.Util import search_domain
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
# Variable
from .costant import SITE_NAME, DOMAIN_NOW
@@ -36,6 +40,10 @@ def title_search(word_to_search: str) -> int:
Returns:
- int: The number of titles found.
"""
+
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
media_search_manager.clear()
table_show_manager.clear()
@@ -48,8 +56,8 @@ def title_search(word_to_search: str) -> int:
# Construct the full site URL and load the search page
try:
response = httpx.get(
- url=f"https://{SITE_NAME}.{domain_to_use}/search/{word_to_search}/1/",
- headers={'user-agent': get_headers()},
+ url=f"https://{SITE_NAME}.{domain_to_use}/search/{word_to_search}/1/",
+ headers={'user-agent': get_headers()},
follow_redirects=True,
timeout=max_timeout
)
@@ -61,6 +69,10 @@ def title_search(word_to_search: str) -> int:
# Create soup and find table
soup = BeautifulSoup(response.text, "html.parser")
+ if TELEGRAM_BOT:
+ # Inizializza la lista delle scelte
+ choices = []
+
for tr in soup.find_all('tr'):
try:
@@ -72,12 +84,23 @@ def title_search(word_to_search: str) -> int:
'date': tr.find_all("td")[-3].get_text(strip=True).replace("'", ""),
'size': tr.find_all("td")[-2].get_text(strip=True)
}
-
- media_search_manager.add_media(title_info)
+
+ if TELEGRAM_BOT:
+ # Crea una stringa formattata per ogni scelta con numero
+ choice_text = f"{len(choices)} - {title_info.get('name')} ({title_info.get('type')}) - {title_info.get('date')}"
+ choices.append(choice_text)
+
+ media_search_manager.add_media(title_info)
except Exception as e:
print(f"Error parsing a film entry: {e}")
+ if TELEGRAM_BOT:
+ # Se ci sono scelte, inviale a Telegram
+ if choices:
+ # Invio a telegram la lista
+ bot.send_message(f"Lista dei risultati:", choices)
+
# Return the number of titles found
return media_search_manager.get_length()
diff --git a/StreamingCommunity/Api/Site/1337xx/title.py b/StreamingCommunity/Api/Site/1337xx/title.py
index 06a5551..de8a4c8 100644
--- a/StreamingCommunity/Api/Site/1337xx/title.py
+++ b/StreamingCommunity/Api/Site/1337xx/title.py
@@ -23,6 +23,11 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaItem
# Config
from .costant import DOMAIN_NOW, SITE_NAME, MOVIE_FOLDER
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from session import get_session, updateScriptId, deleteScriptId
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
def download_title(select_title: MediaItem):
"""
@@ -31,6 +36,12 @@ def download_title(select_title: MediaItem):
Parameters:
- select_title (MediaItem): The media item to be downloaded. This should be an instance of the MediaItem class, containing attributes like `name` and `url`.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+ bot.send_message(f"Download in corso:\n{select_title.name}", None)
+ script_id = get_session()
+ if script_id != "unknown":
+ updateScriptId(script_id, select_title.name)
start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n")
@@ -38,7 +49,9 @@ def download_title(select_title: MediaItem):
# Define output path
title_name = os_manager.get_sanitize_file(select_title.name)
- mp4_path = os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ mp4_path = os_manager.get_sanitize_path(
+ os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ )
# Create output folder
os_manager.create_path(mp4_path)
@@ -62,3 +75,9 @@ def download_title(select_title: MediaItem):
manager.add_magnet_link(final_url)
manager.start_download()
manager.move_downloaded_files(mp4_path)
+
+ if TELEGRAM_BOT:
+ # Delete script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ deleteScriptId(script_id)
diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py b/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py
index b5d540b..35123c1 100644
--- a/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py
+++ b/StreamingCommunity/Api/Site/altadefinizionegratis/__init__.py
@@ -11,6 +11,10 @@ from StreamingCommunity.Util.console import console, msg
from .site import title_search, run_get_select_title, media_search_manager
from .film import download_film
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
# Variable
indice = 2
@@ -26,8 +30,25 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
Main function of the application for film and series.
"""
- if string_to_search is None:
- string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
+ if string_to_search is None:
+ # Chiedi la scelta all'utente con il bot Telegram
+ string_to_search = bot.ask(
+ "key_search",
+ f"Inserisci la parola da cercare\noppure 🔙 back per tornare alla scelta: ",
+ None
+ )
+
+ if string_to_search == 'back':
+ # Riavvia lo script
+ # Chiude il processo attuale e avvia una nuova istanza dello script
+ subprocess.Popen([sys.executable] + sys.argv)
+ sys.exit()
+ else:
+ if string_to_search is None:
+ string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
# Search on database
len_database = title_search(quote_plus(string_to_search))
@@ -45,6 +66,10 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
download_film(select_title)
else:
+
+ if TELEGRAM_BOT:
+ bot.send_message(f"Nessun risultato trovato riprova", None)
+
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry
diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/film.py b/StreamingCommunity/Api/Site/altadefinizionegratis/film.py
index ea83bef..8662b6f 100644
--- a/StreamingCommunity/Api/Site/altadefinizionegratis/film.py
+++ b/StreamingCommunity/Api/Site/altadefinizionegratis/film.py
@@ -24,6 +24,12 @@ from StreamingCommunity.Api.Player.supervideo import VideoSource
# Config
from .costant import MOVIE_FOLDER
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from session import get_session, updateScriptId, deleteScriptId
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
def download_film(select_title: MediaItem) -> str:
"""
@@ -37,17 +43,29 @@ def download_film(select_title: MediaItem) -> str:
- str: output path
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
+ # Invio a telegram
+ bot.send_message(f"Download in corso:\n{select_title.name}", None)
+
+ # Get script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ updateScriptId(script_id, select_title.name)
+
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n")
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
-
+
# Set domain and media ID for the video source
video_source = VideoSource(select_title.url)
# Define output path
title_name = os_manager.get_sanitize_file(select_title.name) + ".mp4"
- mp4_path = os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ mp4_path = os_manager.get_sanitize_path(
+ os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ )
# Get m3u8 master playlist
master_playlist = video_source.get_playlist()
@@ -65,7 +83,13 @@ def download_film(select_title: MediaItem) -> str:
if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n":
frames = get_call_stack()
execute_search(frames[-4])"""
-
+
+ if TELEGRAM_BOT:
+ # Delete script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ deleteScriptId(script_id)
+
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/site.py b/StreamingCommunity/Api/Site/altadefinizionegratis/site.py
index 20b89fd..36947a0 100644
--- a/StreamingCommunity/Api/Site/altadefinizionegratis/site.py
+++ b/StreamingCommunity/Api/Site/altadefinizionegratis/site.py
@@ -25,6 +25,11 @@ table_show_manager = TVShowManager()
max_timeout = config_manager.get_int("REQUESTS", "timeout")
disable_searchDomain = config_manager.get_bool("DEFAULT", "disable_searchDomain")
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
def title_search(title_search: str) -> int:
"""
@@ -36,6 +41,9 @@ def title_search(title_search: str) -> int:
Returns:
int: The number of titles found.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
media_search_manager.clear()
table_show_manager.clear()
@@ -63,6 +71,10 @@ def title_search(title_search: str) -> int:
# Create soup and find table
soup = BeautifulSoup(response.text, "html.parser")
+ if TELEGRAM_BOT:
+ # Inizializza la lista delle scelte
+ choices = []
+
for row in soup.find_all('div', class_='col-lg-3 col-md-3 col-xs-4'):
try:
@@ -81,8 +93,20 @@ def title_search(title_search: str) -> int:
media_search_manager.add_media(film_info)
+ if TELEGRAM_BOT:
+ # Crea una stringa formattata per ogni scelta con numero
+ choice_text = f"{len(choices)} - {film_info.get('name')} ({film_info.get('url')}) {film_info.get('score')}"
+ choices.append(choice_text)
+
except AttributeError as e:
print(f"Error parsing a film entry: {e}")
+
+
+ if TELEGRAM_BOT:
+ # Se ci sono scelte, inviale a Telegram
+ if choices:
+ # Invio a telegram la lista
+ bot.send_message(f"Lista dei risultati:", choices)
# Return the number of titles found
return media_search_manager.get_length()
diff --git a/StreamingCommunity/Api/Site/animeunity/__init__.py b/StreamingCommunity/Api/Site/animeunity/__init__.py
index 613c9aa..4107680 100644
--- a/StreamingCommunity/Api/Site/animeunity/__init__.py
+++ b/StreamingCommunity/Api/Site/animeunity/__init__.py
@@ -11,6 +11,13 @@ from StreamingCommunity.Util.console import console, msg
from .site import title_search, run_get_select_title, media_search_manager
from .film_serie import download_film, download_series
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+import sys
+import subprocess
+
# Variable
indice = 1
@@ -23,8 +30,25 @@ from .costant import SITE_NAME
def search(string_to_search: str = None, get_onylDatabase: bool = False):
- if string_to_search is None:
- string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
+ if string_to_search is None:
+ # Chiedi la scelta all'utente con il bot Telegram
+ string_to_search = bot.ask(
+ "key_search",
+ f"Inserisci la parola da cercare\noppure 🔙 back per tornare alla scelta: ",
+ None
+ )
+
+ if string_to_search == 'back':
+ # Riavvia lo script
+ # Chiude il processo attuale e avvia una nuova istanza dello script
+ subprocess.Popen([sys.executable] + sys.argv)
+ sys.exit()
+ else:
+ if string_to_search is None:
+ string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
# Search on database
len_database = title_search(string_to_search)
@@ -43,8 +67,12 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
else:
download_series(select_title)
-
+
else:
+
+ if TELEGRAM_BOT:
+ bot.send_message(f"Nessun risultato trovato riprova", None)
+
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
# Retry
diff --git a/StreamingCommunity/Api/Site/animeunity/film_serie.py b/StreamingCommunity/Api/Site/animeunity/film_serie.py
index 575c9e9..82c7802 100644
--- a/StreamingCommunity/Api/Site/animeunity/film_serie.py
+++ b/StreamingCommunity/Api/Site/animeunity/film_serie.py
@@ -24,10 +24,16 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSourceAnime
# Variable
from .costant import SITE_NAME, ANIME_FOLDER, MOVIE_FOLDER
-KILL_HANDLER = bool(False)
-def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_source: VideoSourceAnime) -> tuple[str,bool]:
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from session import get_session, updateScriptId, deleteScriptId
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
+
+def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_source: VideoSourceAnime, list_episode_select = None) -> str:
"""
Downloads the selected episode.
@@ -36,8 +42,9 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
Return:
- str: output path
- - bool: kill handler status
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
# Get information about the selected episode
obj_episode = scrape_serie.get_info_episode(index_select)
@@ -46,13 +53,22 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
start_message()
console.print(f"[yellow]Download: [red]EP_{obj_episode.number} \n")
- console.print("[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
-
+
+ if TELEGRAM_BOT:
+ # Invio a telegram
+ bot.send_message(f"Download in corso:\nTitolo:{scrape_serie.series_name}\nEpisodio: {obj_episode.number}", None)
+
+ # Get script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ updateScriptId(script_id, f"{scrape_serie.series_name} - E{obj_episode.number}")
+
# Collect mp4 url
video_source.get_embed(obj_episode.id)
# Create output path
- title_name = f"{obj_episode.number}.mp4"
+ #title_name = f"{obj_episode.number}.mp4"
+ title_name = f"{scrape_serie.series_name}_EP_{obj_episode.number}.mp4"
if scrape_serie.is_series:
mp4_path = os_manager.get_sanitize_path(
@@ -64,15 +80,14 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so
)
# Create output folder
- os_manager.create_path(mp4_path)
+ os_manager.create_path(mp4_path)
# Start downloading
-
r_proc = MP4_downloader(
url=str(video_source.src_mp4).strip(),
path=os.path.join(mp4_path, title_name)
)
-
+
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
@@ -91,6 +106,9 @@ def download_series(select_title: MediaItem):
- tv_id (int): The ID of the TV series.
- tv_name (str): The name of the TV series.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
scrape_serie = ScrapeSerieAnime(SITE_NAME)
video_source = VideoSourceAnime(SITE_NAME)
@@ -101,23 +119,40 @@ def download_series(select_title: MediaItem):
episoded_count = scrape_serie.get_count_episodes()
console.print(f"[cyan]Episodes find: [red]{episoded_count}")
- # Prompt user to select an episode index
- 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")
+ if TELEGRAM_BOT:
+ console.print(f"\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")
+
+ # Invio a telegram
+ bot.send_message(f"Episodi trovati: {episoded_count}", None)
+
+ last_command = bot.ask(
+ "select_title",
+ f"Inserisci l'indice del media o (*) per scaricare tutti i media, oppure [1-2] o [3-*] per un intervallo di media.",
+ None
+ )
+ else:
+ # Prompt user to select an episode index
+ 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")
# Manage user selection
list_episode_select = manage_selection(last_command, episoded_count)
# Download selected episodes
if len(list_episode_select) == 1 and last_command != "*":
- download_episode(list_episode_select[0]-1, scrape_serie, video_source)[0]
+ download_episode(list_episode_select[0]-1, scrape_serie, video_source, list_episode_select=list_episode_select)
# Download all other episodes selecter
else:
- kill_handler=bool(False)
for i_episode in list_episode_select:
- if kill_handler:
- break
- kill_handler= download_episode(i_episode-1, scrape_serie, video_source)[1]
+ download_episode(i_episode-1, scrape_serie, video_source, list_episode_select=list_episode_select)
+
+ if TELEGRAM_BOT:
+ bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
+
+ # Get script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ deleteScriptId(script_id)
def download_film(select_title: MediaItem):
diff --git a/StreamingCommunity/Api/Site/animeunity/site.py b/StreamingCommunity/Api/Site/animeunity/site.py
index daaa571..6984626 100644
--- a/StreamingCommunity/Api/Site/animeunity/site.py
+++ b/StreamingCommunity/Api/Site/animeunity/site.py
@@ -19,6 +19,11 @@ from StreamingCommunity.Api.Template import get_select_title
from StreamingCommunity.Api.Template.Util import search_domain
from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
# Variable
from .costant import SITE_NAME, DOMAIN_NOW
@@ -103,6 +108,9 @@ def title_search(title: str) -> int:
Returns:
- int: A number containing the length of media search manager.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
media_search_manager.clear()
table_show_manager.clear()
@@ -146,12 +154,16 @@ def title_search(title: str) -> int:
except Exception as e:
console.print(f"Site: {SITE_NAME}, request search error: {e}")
+ if TELEGRAM_BOT:
+ # Inizializza la lista delle scelte
+ choices = []
+
for dict_title in response.json()['records']:
try:
-
- # Rename keys for consistency
+ # Rinomina le chiavi per consistenza
dict_title['name'] = get_real_title(dict_title)
+ # Aggiungi al media manager
media_search_manager.add_media({
'id': dict_title.get('id'),
'slug': dict_title.get('slug'),
@@ -161,8 +173,18 @@ def title_search(title: str) -> int:
'episodes_count': dict_title.get('episodes_count')
})
+ if TELEGRAM_BOT:
+ # Crea una stringa formattata per ogni scelta con numero
+ choice_text = f"{len(choices)} - {dict_title.get('name')} ({dict_title.get('type')}) - Episodi: {dict_title.get('episodes_count')}"
+ choices.append(choice_text)
+
except Exception as e:
print(f"Error parsing a film entry: {e}")
+ if TELEGRAM_BOT:
+ # Se ci sono scelte, inviale a Telegram
+ if choices:
+ # Invio a telegram la lista
+ bot.send_message(f"Lista dei risultati:", choices)
# Return the length of media search manager
return media_search_manager.get_length()
diff --git a/StreamingCommunity/Api/Site/cb01new/film.py b/StreamingCommunity/Api/Site/cb01new/film.py
index b886cac..11a1304 100644
--- a/StreamingCommunity/Api/Site/cb01new/film.py
+++ b/StreamingCommunity/Api/Site/cb01new/film.py
@@ -38,7 +38,6 @@ def download_film(select_title: MediaItem) -> str:
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n")
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Setup api manger
print(select_title.url)
@@ -46,7 +45,9 @@ def download_film(select_title: MediaItem) -> str:
# Define output path
title_name = os_manager.get_sanitize_file(select_title.name) +".mp4"
- mp4_path = os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ mp4_path = os_manager.get_sanitize_path(
+ os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ )
# Get m3u8 master playlist
master_playlist = video_source.get_playlist()
diff --git a/StreamingCommunity/Api/Site/ddlstreamitaly/series.py b/StreamingCommunity/Api/Site/ddlstreamitaly/series.py
index 743f636..ec18bf1 100644
--- a/StreamingCommunity/Api/Site/ddlstreamitaly/series.py
+++ b/StreamingCommunity/Api/Site/ddlstreamitaly/series.py
@@ -28,7 +28,7 @@ from .costant import SERIES_FOLDER
-def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo, video_source: VideoSource) -> tuple[str,bool]:
+def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo, video_source: VideoSource) -> str:
"""
Download a single episode video.
@@ -38,14 +38,13 @@ def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo,
Return:
- str: output path
- - bool: kill handler status
"""
start_message()
# Get info about episode
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
- console.print(f"[yellow]Download: [red]{obj_episode.get('name')}\n")
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
+ console.print(f"[yellow]Download: [red]{obj_episode.get('name')}")
+ print()
# Define filename and path for the downloaded video
title_name = os_manager.get_sanitize_file(
@@ -71,7 +70,7 @@ def download_video(index_episode_selected: int, scape_info_serie: GetSerieInfo,
path=os.path.join(mp4_path, title_name),
referer=f"{parsed_url.scheme}://{parsed_url.netloc}/",
)
-
+
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
@@ -106,11 +105,8 @@ def download_thread(dict_serie: MediaItem):
return
# Download selected episodes
- kill_handler = bool(False)
for i_episode in list_episode_select:
- if kill_handler:
- break
- kill_handler = download_video(i_episode, scape_info_serie, video_source)[1]
+ download_video(i_episode, scape_info_serie, video_source)
def display_episodes_list(obj_episode_manager) -> str:
diff --git a/StreamingCommunity/Api/Site/guardaserie/series.py b/StreamingCommunity/Api/Site/guardaserie/series.py
index 93a2658..70d8fa0 100644
--- a/StreamingCommunity/Api/Site/guardaserie/series.py
+++ b/StreamingCommunity/Api/Site/guardaserie/series.py
@@ -44,8 +44,9 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
# Get info about episode
obj_episode = scape_info_serie.list_episodes[index_episode_selected - 1]
- console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.get('name')}\n")
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
+ console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.get('name')}")
+ print()
+
# Define filename and path for the downloaded video
mp4_name = f"{map_episode_title(scape_info_serie.tv_name, index_season_selected, index_episode_selected, obj_episode.get('name'))}.mp4"
mp4_path = os.path.join(SERIES_FOLDER, scape_info_serie.tv_name, f"S{index_season_selected}")
@@ -69,7 +70,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scap
if msg.ask("[green]Do you want to continue [white]([red]y[white])[green] or return at home[white]([red]n[white]) ", choices=['y', 'n'], default='y', show_choices=True) == "n":
frames = get_call_stack()
execute_search(frames[-4])"""
-
+
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
@@ -112,10 +113,7 @@ def download_episode(scape_info_serie: GetSerieInfo, index_season_selected: int,
return
# Download selected episodes
- stopped = bool(False)
for i_episode in list_episode_select:
- if stopped:
- break
download_video(index_season_selected, i_episode, scape_info_serie)
diff --git a/StreamingCommunity/Api/Site/ilcorsaronero/title.py b/StreamingCommunity/Api/Site/ilcorsaronero/title.py
index e32af4d..886de89 100644
--- a/StreamingCommunity/Api/Site/ilcorsaronero/title.py
+++ b/StreamingCommunity/Api/Site/ilcorsaronero/title.py
@@ -32,7 +32,9 @@ def download_title(select_title: MediaItem):
# Define output path
title_name = os_manager.get_sanitize_file(select_title.name)
- mp4_path = os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ mp4_path = os_manager.get_sanitize_path(
+ os.path.join(MOVIE_FOLDER, title_name.replace(".mp4", ""))
+ )
# Create output folder
os_manager.create_path(mp4_path)
diff --git a/StreamingCommunity/Api/Site/mostraguarda/film.py b/StreamingCommunity/Api/Site/mostraguarda/film.py
index 2e04228..b7889d9 100644
--- a/StreamingCommunity/Api/Site/mostraguarda/film.py
+++ b/StreamingCommunity/Api/Site/mostraguarda/film.py
@@ -50,7 +50,7 @@ def download_film(movie_details: Json_film) -> str:
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{movie_details.title} \n")
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
+
# Make request to main site
try:
url = f"https://{SITE_NAME}.{DOMAIN_NOW}/set-movie-a/{movie_details.imdb_id}"
diff --git a/StreamingCommunity/Api/Site/streamingcommunity/__init__.py b/StreamingCommunity/Api/Site/streamingcommunity/__init__.py
index 7f93feb..16bc91f 100644
--- a/StreamingCommunity/Api/Site/streamingcommunity/__init__.py
+++ b/StreamingCommunity/Api/Site/streamingcommunity/__init__.py
@@ -1,7 +1,8 @@
# 21.05.24
from urllib.parse import quote_plus
-
+import subprocess
+import sys
# Internal utilities
from StreamingCommunity.Util.console import console, msg
@@ -12,6 +13,10 @@ from .site import get_version_and_domain, title_search, run_get_select_title, me
from .film import download_film
from .series import download_series
+# Telegram bot instance
+from StreamingCommunity.Util._jsonConfig import config_manager
+from telegram_bot import get_bot_instance
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
# Variable
indice = 0
@@ -27,8 +32,25 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
Main function of the application for film and series.
"""
- if string_to_search is None:
- string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
+ if string_to_search is None:
+ # Chiedi la scelta all'utente con il bot Telegram
+ string_to_search = bot.ask(
+ "key_search",
+ f"Inserisci la parola da cercare\noppure 🔙 back per tornare alla scelta: ",
+ None
+ )
+
+ if string_to_search == 'back':
+ # Riavvia lo script
+ # Chiude il processo attuale e avvia una nuova istanza dello script
+ subprocess.Popen([sys.executable] + sys.argv)
+ sys.exit()
+ else:
+ if string_to_search is None:
+ string_to_search = msg.ask(f"\n[purple]Insert word to search in [green]{SITE_NAME}").strip()
# Get site domain and version and get result of the search
site_version, domain = get_version_and_domain()
@@ -37,20 +59,23 @@ def search(string_to_search: str = None, get_onylDatabase: bool = False):
# Return list of elements
if get_onylDatabase:
return media_search_manager
-
+
if len_database > 0:
# Select title from list
select_title = run_get_select_title()
-
+
if select_title.type == 'tv':
download_series(select_title, site_version)
-
+
else:
download_film(select_title)
-
+
else:
console.print(f"\n[red]Nothing matching was found for[white]: [purple]{string_to_search}")
+ if TELEGRAM_BOT:
+ bot.send_message(f"Nessun risultato trovato riprova", None)
+
# Retry
search()
\ No newline at end of file
diff --git a/StreamingCommunity/Api/Site/streamingcommunity/film.py b/StreamingCommunity/Api/Site/streamingcommunity/film.py
index eebfcdd..4ade5d6 100644
--- a/StreamingCommunity/Api/Site/streamingcommunity/film.py
+++ b/StreamingCommunity/Api/Site/streamingcommunity/film.py
@@ -23,7 +23,13 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSource
# Variable
from .costant import SITE_NAME, MOVIE_FOLDER
-
+
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from session import get_session, updateScriptId, deleteScriptId
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
def download_film(select_title: MediaItem) -> str:
"""
@@ -36,11 +42,20 @@ def download_film(select_title: MediaItem) -> str:
Return:
- str: output path
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
+ # Invio a telegram
+ bot.send_message(f"Download in corso:\n{select_title.name}", None)
+
+ # Get script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ updateScriptId(script_id, select_title.name)
# Start message and display film information
start_message()
console.print(f"[yellow]Download: [red]{select_title.name} \n")
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
# Init class
video_source = VideoSource(SITE_NAME, False)
@@ -57,10 +72,16 @@ def download_film(select_title: MediaItem) -> str:
# Download the film using the m3u8 playlist, and output filename
r_proc = HLS_Downloader(
- m3u8_playlist=master_playlist,
+ m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, title_name)
).start()
+ if TELEGRAM_BOT:
+ # Delete script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ deleteScriptId(script_id)
+
"""if r_proc == 404:
time.sleep(2)
diff --git a/StreamingCommunity/Api/Site/streamingcommunity/series.py b/StreamingCommunity/Api/Site/streamingcommunity/series.py
index 16b3d3b..8fe16fc 100644
--- a/StreamingCommunity/Api/Site/streamingcommunity/series.py
+++ b/StreamingCommunity/Api/Site/streamingcommunity/series.py
@@ -26,9 +26,14 @@ from StreamingCommunity.Api.Player.vixcloud import VideoSource
# Variable
from .costant import SITE_NAME, SERIES_FOLDER
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from session import get_session, updateScriptId, deleteScriptId
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
-def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: ScrapeSerie, video_source: VideoSource) -> tuple[str,bool]:
+def download_video(index_season_selected: int, index_episode_selected: int, scrape_serie: ScrapeSerie, video_source: VideoSource) -> str:
"""
Download a single episode video.
@@ -38,16 +43,31 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
Return:
- str: output path
- - bool: kill handler status
"""
+
start_message()
index_season_selected = dynamic_format_number(index_season_selected)
# Get info about episode
obj_episode = scrape_serie.episode_manager.get(index_episode_selected - 1)
- console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.name}\n")
- console.print(f"[cyan]You can safely stop the download with [bold]Ctrl+c[bold] [cyan] \n")
-
+ console.print(f"[yellow]Download: [red]{index_season_selected}:{index_episode_selected} {obj_episode.name}")
+ print()
+
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
+ # Invio a telegram
+ bot.send_message(
+ f"Download in corso\nSerie: {scrape_serie.series_name}\nStagione: {index_season_selected}\nEpisodio: {index_episode_selected}\nTitolo: {obj_episode.name}",
+ None
+ )
+
+ # Get script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ # Update script_id
+ updateScriptId(script_id, f"{scrape_serie.series_name} - S{index_season_selected} - E{index_episode_selected} - {obj_episode.name}")
+
# Define filename and path for the downloaded video
mp4_name = f"{map_episode_title(scrape_serie.series_name, index_season_selected, index_episode_selected, obj_episode.name)}.mp4"
mp4_path = os.path.join(SERIES_FOLDER, scrape_serie.series_name, f"S{index_season_selected}")
@@ -56,13 +76,15 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
video_source.get_iframe(obj_episode.id)
video_source.get_content()
master_playlist = video_source.get_playlist()
-
+
# Download the episode
r_proc = HLS_Downloader(
m3u8_playlist=master_playlist,
output_filename=os.path.join(mp4_path, mp4_name)
).start()
-
+
+ #bot.send_message(f"Serie scaricata tutta", None)
+
"""if r_proc == 404:
time.sleep(2)
@@ -74,6 +96,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra
if r_proc != None:
console.print("[green]Result: ")
console.print(r_proc)
+ #bot.send_message(f"Episodio scaricato", None)
return os.path.join(mp4_path, mp4_name)
@@ -84,8 +107,11 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide
Parameters:
- index_season_selected (int): Index of the selected season.
- download_all (bool): Download all episodes in the season.
+ - list_season_select (list): Lista delle stagioni selezionate per il download.
"""
+ #bot = get_bot_instance()
+
# Clean memory of all episodes and get the number of the season
scrape_serie.episode_manager.clear()
@@ -101,6 +127,8 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide
download_video(index_season_selected, i_episode, scrape_serie, video_source)
console.print(f"\n[red]End downloaded [yellow]season: [red]{index_season_selected}.")
+ #bot.send_message(f"Finito di scaricare la stagione: {index_season_selected}", None)
+
else:
# Display episodes list and manage user selection
@@ -111,14 +139,12 @@ def download_episode(index_season_selected: int, scrape_serie: ScrapeSerie, vide
list_episode_select = validate_episode_selection(list_episode_select, episodes_count)
except ValueError as e:
console.print(f"[red]{str(e)}")
+ #bot.send_message(f"{str(e)}", None)
return
- # Download selected episodes if not stopped
- stopped = bool(False)
+ # Download selected episodes
for i_episode in list_episode_select:
- if stopped:
- break
- stopped=download_video(index_season_selected, i_episode, scrape_serie, video_source)[1]
+ download_video(index_season_selected, i_episode, scrape_serie, video_source)
def download_series(select_season: MediaItem, version: str) -> None:
"""
@@ -129,6 +155,8 @@ def download_series(select_season: MediaItem, version: str) -> None:
- domain (str): Domain from which to download.
- version (str): Version of the site.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
# Start message and set up video source
start_message()
@@ -147,11 +175,24 @@ def download_series(select_season: MediaItem, version: str) -> None:
# Prompt user for season selection and download episodes
console.print(f"\n[green]Seasons found: [red]{seasons_count}")
- index_season_selected = msg.ask(
- "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
- "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
- )
-
+
+ if TELEGRAM_BOT:
+ console.print("\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
+ "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end")
+
+ bot.send_message(f"Stagioni trovate: {seasons_count}", None)
+
+ index_season_selected = bot.ask(
+ "select_title_episode",
+ "Inserisci il numero della stagione (es. 1), * per scaricare tutte le stagioni, (es. 1-2) per un intervallo di stagioni, o (es. 3-*) per scaricare dalla stagione specificata fino alla fine",
+ None
+ )
+ else:
+ index_season_selected = msg.ask(
+ "\n[cyan]Insert season number [yellow](e.g., 1), [red]* [cyan]to download all seasons, "
+ "[yellow](e.g., 1-2) [cyan]for a range of seasons, or [yellow](e.g., 3-*) [cyan]to download from a specific season to the end"
+ )
+
# Manage and validate the selection
list_season_select = manage_selection(index_season_selected, seasons_count)
@@ -172,6 +213,13 @@ def download_series(select_season: MediaItem, version: str) -> None:
# Otherwise, let the user select specific episodes for the single season
download_episode(i_season, scrape_serie, video_source, download_all=False)
+ if TELEGRAM_BOT:
+ bot.send_message(f"Finito di scaricare tutte le serie e episodi", None)
+ # Get script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ deleteScriptId(script_id)
+
def display_episodes_list(scrape_serie) -> str:
"""
@@ -180,6 +228,8 @@ def display_episodes_list(scrape_serie) -> str:
Returns:
last_command (str): Last command entered by the user.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
# Set up table for displaying episodes
table_show_manager = TVShowManager()
@@ -194,6 +244,8 @@ def display_episodes_list(scrape_serie) -> str:
table_show_manager.add_column(column_info)
# Populate the table with episodes information
+ if TELEGRAM_BOT:
+ choices = []
for i, media in enumerate(scrape_serie.episode_manager.episodes):
table_show_manager.add_tv_show({
'Index': str(media.number),
@@ -201,6 +253,22 @@ def display_episodes_list(scrape_serie) -> str:
'Duration': str(media.duration)
})
+ if TELEGRAM_BOT:
+ # Creazione della stringa per il messaggio Telegram
+ choice_text = f"{media.number} - {media.name} ({media.duration} min)"
+ choices.append(choice_text)
+
+ if TELEGRAM_BOT:
+ # creo episoded_count
+ #episoded_count = len(scrape_serie.episode_manager.episodes)
+
+ # Invio a telegram
+ #bot.send_message(f"Episodi trovati: {episoded_count}", None)
+
+ # Invia la lista degli episodi al bot Telegram
+ if choices:
+ bot.send_message(f"Lista episodi:", choices)
+
# Run the table and handle user input
last_command = table_show_manager.run()
diff --git a/StreamingCommunity/Api/Site/streamingcommunity/site.py b/StreamingCommunity/Api/Site/streamingcommunity/site.py
index 19c2acb..a8b21fd 100644
--- a/StreamingCommunity/Api/Site/streamingcommunity/site.py
+++ b/StreamingCommunity/Api/Site/streamingcommunity/site.py
@@ -27,6 +27,9 @@ from StreamingCommunity.Api.Template.Class.SearchType import MediaManager
# Config
from .costant import SITE_NAME, DOMAIN_NOW
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
# Variable
media_search_manager = MediaManager()
@@ -47,8 +50,8 @@ def get_version(domain: str):
"""
try:
response = httpx.get(
- url=f"https://{SITE_NAME}.{domain}/",
- headers={'User-Agent': get_headers()},
+ url=f"https://{SITE_NAME}.{domain}/",
+ headers={'User-Agent': get_headers()},
timeout=max_timeout
)
response.raise_for_status()
@@ -61,7 +64,7 @@ def get_version(domain: str):
#console.print(f"[cyan]Get version [white]=> [red]{version} \n")
return version
-
+
except Exception as e:
logging.error(f"Error extracting version: {e}")
raise
@@ -88,38 +91,37 @@ def get_version_and_domain():
#console.print("[green]Auto generate version ...")
#version = secrets.token_hex(32 // 2)
version = None
-
+
return version, domain_to_use
def title_search(title_search: str, domain: str) -> int:
- """
- Search for titles based on a search query.
-
- Parameters:
- - title_search (str): The title to search for.
- - domain (str): The domain to search on.
-
- Returns:
- int: The number of titles found.
- """
media_search_manager.clear()
table_show_manager.clear()
-
+
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
try:
response = httpx.get(
- url=f"https://{SITE_NAME}.{domain}/api/search?q={title_search.replace(' ', '+')}",
- headers={'user-agent': get_headers()},
+ url=f"https://{SITE_NAME}.{domain}/api/search?q={title_search.replace(' ', '+')}",
+ headers={'user-agent': get_headers()},
timeout=max_timeout
)
response.raise_for_status()
except Exception as e:
+ if TELEGRAM_BOT:
+ bot.send_message(f"Sito: {SITE_NAME}, errore richiesta ricerca", choices)
console.print(f"Site: {SITE_NAME}, request search error: {e}")
+ return 0
- for dict_title in response.json()['data']:
- try:
-
+ try:
+ if TELEGRAM_BOT:
+ # Prepara le scelte per l'utente
+ choices = []
+ for i, dict_title in enumerate(response.json()['data']):
+ # Aggiungi al media manager
media_search_manager.add_media({
'id': dict_title.get('id'),
'slug': dict_title.get('slug'),
@@ -129,10 +131,20 @@ def title_search(title_search: str, domain: str) -> int:
'score': dict_title.get('score')
})
- except Exception as e:
- print(f"Error parsing a film entry: {e}")
+ if TELEGRAM_BOT:
+ # Crea una stringa formattata per ogni scelta con numero
+ choice_text = f"{i} - {dict_title.get('name')} ({dict_title.get('type')}) - {dict_title.get('last_air_date')}"
+ choices.append(choice_text)
+
+ except Exception as e:
+ print(f"Error parsing entries: {e}")
+ return 0
+
+ if TELEGRAM_BOT:
+ if choices:
+ # Invio a telegram la lista
+ bot.send_message(f"Lista dei risultati:", choices)
- # Return the number of titles found
return media_search_manager.get_length()
diff --git a/StreamingCommunity/Api/Template/Util/get_domain.py b/StreamingCommunity/Api/Template/Util/get_domain.py
index 53b444f..0c7fb09 100644
--- a/StreamingCommunity/Api/Template/Util/get_domain.py
+++ b/StreamingCommunity/Api/Template/Util/get_domain.py
@@ -7,7 +7,7 @@ from urllib.parse import urlparse, unquote
# External libraries
import httpx
-from googlesearch import search
+from serpapi import search
# Internal utilities
@@ -15,6 +15,7 @@ from StreamingCommunity.Util.headers import get_headers
from StreamingCommunity.Util.console import console, msg
from StreamingCommunity.Util._jsonConfig import config_manager
+api_key = "ebfaafb043613442e0010e3795c9ead4cab196e5448a6e3728d64edbbccdf731"
base_headers = {
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'accept-language': 'it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7',
@@ -75,7 +76,7 @@ def get_base_url(url_str):
except Exception:
return None
-def validate_url(url, base_url, max_timeout, max_retries=2, sleep=1):
+def validate_url(url, base_url, max_timeout, max_retries=3, sleep=3):
"""Validate if URL is accessible and matches expected base domain."""
console.print(f"\n[cyan]Starting validation for URL[white]: [yellow]{url}")
@@ -163,9 +164,16 @@ def search_domain(site_name: str, base_url: str, get_first: bool = False):
console.print(f"\n[cyan]Searching for alternate domains for[white]: [yellow]{base_domain}")
try:
- search_results = list(search(base_domain, num_results=20, lang="it"))
-
+ params = {
+ "q": base_domain,
+ "hl": "it",
+ "gl": "it",
+ "num": 20,
+ "api_key": api_key
+ }
+ search_results = [element['link'] for element in search(params)['organic_results']]
base_urls = set()
+
for url in search_results:
element_url = get_base_url(url)
if element_url:
diff --git a/StreamingCommunity/Lib/Downloader/HLS/downloader.py b/StreamingCommunity/Lib/Downloader/HLS/downloader.py
index 2f4bf23..fdaab35 100644
--- a/StreamingCommunity/Lib/Downloader/HLS/downloader.py
+++ b/StreamingCommunity/Lib/Downloader/HLS/downloader.py
@@ -1,9 +1,11 @@
# 17.10.24
import os
+import re
import sys
import time
import logging
+import subprocess
# External libraries
@@ -35,11 +37,17 @@ from ...M3U8 import (
)
from .segments import M3U8_Segments
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
# Config
-DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_audio')
+DOWNLOAD_SPECIFIC_AUDIO = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_audio')
DOWNLOAD_SPECIFIC_SUBTITLE = config_manager.get_list('M3U8_DOWNLOAD', 'specific_list_subtitles')
+DOWNLOAD_VIDEO = config_manager.get_bool('M3U8_DOWNLOAD', 'download_video')
+DOWNLOAD_AUDIO = config_manager.get_bool('M3U8_DOWNLOAD', 'download_audio')
MERGE_AUDIO = config_manager.get_bool('M3U8_DOWNLOAD', 'merge_audio')
+DOWNLOAD_SUBTITLE = config_manager.get_bool('M3U8_DOWNLOAD', 'download_sub')
MERGE_SUBTITLE = config_manager.get_bool('M3U8_DOWNLOAD', 'merge_subs')
REMOVE_SEGMENTS_FOLDER = config_manager.get_bool('M3U8_DOWNLOAD', 'cleanup_tmp_folder')
FILTER_CUSTOM_REOLUTION = config_manager.get_int('M3U8_PARSER', 'force_resolution')
@@ -54,29 +62,6 @@ list_MissingTs = []
-class HttpClient:
- def __init__(self, headers: dict = None):
- self.headers = headers or {'User-Agent': get_headers()}
- self.client = httpx.Client(headers=self.headers, timeout=max_timeout, follow_redirects=True)
-
- def _make_request(self, url: str, return_content: bool = False):
- for attempt in range(RETRY_LIMIT):
- try:
- response = self.client.get(url)
- response.raise_for_status()
- return response.content if return_content else response.text
- except Exception as e:
- logging.error(f"Attempt {attempt+1} failed for {url}: {str(e)}")
- time.sleep(1.5 ** attempt)
- return None
-
- def get(self, url: str) -> str:
- return self._make_request(url)
-
- def get_content(self, url: str) -> bytes:
- return self._make_request(url, return_content=True)
-
-
class PathManager:
def __init__(self, output_filename):
"""
@@ -86,11 +71,11 @@ class PathManager:
output_filename (str): The name of the output file (should end with .mp4).
"""
self.output_filename = output_filename
-
+
# Create the base path by removing the '.mp4' extension from the output filename
self.base_path = str(output_filename).replace(".mp4", "")
logging.info(f"class 'PathManager'; set base path: {self.base_path}")
-
+
# Define the path for a temporary directory where segments will be stored
self.base_temp = os.path.join(self.base_path, "tmp")
self.video_segments_path = os.path.join(self.base_temp, "video")
@@ -108,7 +93,66 @@ class PathManager:
os.makedirs(self.subtitle_segments_path, exist_ok=True)
+class HttpClient:
+ def __init__(self, headers: str = None):
+ """
+ Initializes the HttpClient with specified headers.
+ """
+ self.headers = headers
+
+ def get(self, url: str):
+ """
+ Sends a GET request to the specified URL and returns the response as text.
+
+ Returns:
+ str: The response body as text if the request is successful, None otherwise.
+ """
+ logging.info(f"class 'HttpClient'; make request: {url}")
+ try:
+ response = httpx.get(
+ url=url,
+ headers=self.headers,
+ timeout=max_timeout,
+ follow_redirects=True
+ )
+
+ response.raise_for_status()
+ return response.text
+
+ except Exception as e:
+ console.print(f"Request to {url} failed with error: {e}")
+ return 404
+
+ def get_content(self, url):
+ """
+ Sends a GET request to the specified URL and returns the raw response content.
+
+ Returns:
+ bytes: The response content as bytes if the request is successful, None otherwise.
+ """
+ logging.info(f"class 'HttpClient'; make request: {url}")
+ try:
+ response = httpx.get(
+ url=url,
+ headers=self.headers,
+ timeout=max_timeout
+ )
+
+ response.raise_for_status()
+ return response.content # Return the raw response content
+
+ except Exception as e:
+ logging.error(f"Request to {url} failed: {response.status_code} when get content.")
+ return None
+
+
class ContentExtractor:
+ def __init__(self):
+ """
+ This class is responsible for extracting audio, subtitle, and video information from an M3U8 playlist.
+ """
+ pass
+
def start(self, obj_parse: M3U8_Parser):
"""
Starts the extraction process by parsing the M3U8 playlist and collecting audio, subtitle, and video data.
@@ -116,7 +160,10 @@ class ContentExtractor:
Args:
obj_parse (str): The M3U8_Parser obj of the M3U8 playlist.
"""
+
self.obj_parse = obj_parse
+
+ # Collect audio, subtitle, and video information
self._collect_audio()
self._collect_subtitle()
self._collect_video()
@@ -129,13 +176,13 @@ class ContentExtractor:
# Collect available audio tracks and their corresponding URIs and names
self.list_available_audio = self.obj_parse._audio.get_all_uris_and_names()
-
+
# Check if there are any audio tracks available; if not, disable download
if self.list_available_audio is not None:
# Extract available languages from the audio tracks
available_languages = [obj_audio.get('language') for obj_audio in self.list_available_audio]
- set_language = DOWNLOAD_SPECIFIC_AUDIO
+ set_language = DOWNLOAD_SPECIFIC_AUDIO
downloadable_languages = list(set(available_languages) & set(set_language))
# Only show if there is something available
@@ -155,7 +202,7 @@ class ContentExtractor:
# Collect available subtitles and their corresponding URIs and names
self.list_available_subtitles = self.obj_parse._subtitle.get_all_uris_and_names()
-
+
# Check if there are any subtitles available; if not, disable download
if self.list_available_subtitles is not None:
@@ -189,7 +236,7 @@ class ContentExtractor:
# Otherwise, get the best available video quality
self.m3u8_index, video_res = self.obj_parse._video.get_best_uri()
-
+
self.codec: M3U8_Codec = self.obj_parse.codec
# List all available resolutions
@@ -203,7 +250,7 @@ class ContentExtractor:
f"[yellow]Downloadable:[/yellow] [purple]{video_res[0]}x{video_res[1]}[/purple]")
if self.codec is not None:
-
+
# Generate the string for available codec information
available_codec_info = (
f"[green]v[/green]: [yellow]{self.codec.video_codec_name}[/yellow] "
@@ -242,7 +289,7 @@ class ContentExtractor:
else:
logging.error("[download_m3u8] Can't find a valid m3u8 index")
raise ValueError("Invalid m3u8 index URL")
-
+
print("")
@@ -325,9 +372,9 @@ class DownloadTracker:
# Skip this subtitle if its language is not in the specified list
if obj_subtitle.get('language') not in DOWNLOAD_SPECIFIC_SUBTITLE:
continue
-
+
sub_language = obj_subtitle.get('language')
-
+
# Construct the full path for the subtitle file
sub_full_path = os.path.join(self.subtitle_segments_path, sub_language + ".vtt")
@@ -374,7 +421,9 @@ class ContentDownloader:
# Download the video streams and print status
info_dw = video_m3u8.download_streams(f"{Colors.MAGENTA}video", "video")
list_MissingTs.append(info_dw)
- self.stopped=list_MissingTs.pop()
+
+ # Print duration information of the downloaded video
+ #print_duration_table(downloaded_video[0].get('path'))
else:
console.log("[cyan]Video [red]already exists.")
@@ -403,7 +452,9 @@ class ContentDownloader:
# Download the audio segments and print status
info_dw = audio_m3u8.download_streams(f"{Colors.MAGENTA}audio {Colors.RED}{obj_audio.get('language')}", f"audio_{obj_audio.get('language')}")
list_MissingTs.append(info_dw)
- self.stopped=list_MissingTs.pop()
+
+ # Print duration information of the downloaded audio
+ #print_duration_table(obj_audio.get('path'))
else:
console.log(f"[cyan]Audio [white]([green]{obj_audio.get('language')}[white]) [red]already exists.")
@@ -503,7 +554,7 @@ class ContentJoiner:
self.downloaded_audio = downloaded_audio
self.downloaded_subtitle = downloaded_subtitle
self.codec = codec
-
+
# Initialize flags to check if media is available
self.converted_out_path = None
self.there_is_video = len(downloaded_video) > 0
@@ -540,7 +591,7 @@ class ContentJoiner:
# Rename the audio file to the new path
os.rename(path, new_path)
logging.info(f"Audio moved to {new_path}")
-
+
except Exception as e:
logging.error(f"Failed to move audio {path} to {new_path}: {e}")
@@ -573,12 +624,12 @@ class ContentJoiner:
new_path = self.path_manager.output_filename.replace(".mp4", f".{language}.forced.vtt")
else:
new_path = self.path_manager.output_filename.replace(".mp4", f".{language}.vtt")
-
+
try:
# Rename the subtitle file to the new path
os.rename(path, new_path)
logging.info(f"Subtitle moved to {new_path}")
-
+
except Exception as e:
logging.error(f"Failed to move subtitle {path} to {new_path}: {e}")
@@ -664,8 +715,6 @@ class ContentJoiner:
class HLS_Downloader:
- stopped = bool(False)
-
def __init__(self, output_filename: str=None, m3u8_playlist: str=None, m3u8_index: str=None, is_playlist_url: bool=True, is_index_url: bool=True):
"""
Initializes the HLS_Downloader class.
@@ -714,7 +763,7 @@ class HLS_Downloader:
Returns:
str: The generated output filename.
"""
- root_path = config_manager.get('DEFAULT', 'root_path')
+ root_path = config_manager.get('DEFAULT', 'root_path')
new_filename = None
new_folder = os.path.join(root_path, "undefined")
logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); destination folder: {new_folder}")
@@ -729,8 +778,8 @@ class HLS_Downloader:
else:
# Check if output_filename contains a folder path
- folder, base_name = os.path.split(output_filename)
-
+ folder, base_name = os.path.split(output_filename)
+
# If no folder is specified, default to 'undefined'
if not folder:
folder = new_folder
@@ -745,17 +794,22 @@ class HLS_Downloader:
logging.info(f"class 'HLS_Downloader'; call _generate_output_filename(); return path: {new_filename}")
return new_filename
-
+
def start(self):
"""
Initiates the downloading process. Checks if the output file already exists and proceeds with processing the playlist or index.
- """
+ """
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
if os.path.exists(self.output_filename):
console.log("[red]Output file already exists.")
+ if TELEGRAM_BOT:
+ bot.send_message(f"Contenuto già scaricato!", None)
return 400
-
+
self.path_manager.create_directories()
-
+
# Determine whether to process a playlist or index
if self.m3u8_playlist:
if self.m3u8_playlist is not None:
@@ -773,14 +827,14 @@ class HLS_Downloader:
if r_proc == 404:
return 404
else:
- return None
-
+ return None
+
else:
return {
'path': self.output_filename,
- 'url': self.m3u8_playlist,
+ 'url': self.m3u8_playlist
}
-
+
else:
console.log("[red]Error: URL passed to M3U8_Parser is an index playlist; expected a master playlist. Crucimorfo strikes again!")
else:
@@ -805,9 +859,9 @@ class HLS_Downloader:
else:
return {
'path': self.output_filename,
- 'url': self.m3u8_index,
+ 'url': self.m3u8_index
}
-
+
else:
console.log("[red]Error: URL passed to M3U8_Parser is an master playlist; expected a index playlist. Crucimorfo strikes again!")
else:
@@ -825,6 +879,8 @@ class HLS_Downloader:
Args:
out_path (str): The path of the output file to be cleaned up.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
# Check if the final output file exists
logging.info(f"Check if end file converted exists: {out_path}")
@@ -869,6 +925,13 @@ class HLS_Downloader:
border_style="green"
))
+ if TELEGRAM_BOT:
+ message = f"Download completato\nDimensione: {formatted_size}\nDurata: {formatted_duration}\nPercorso: {os.path.abspath(self.output_filename)}"
+ # Rimuovere i tag di colore usando una regex
+ clean_message = re.sub(r'\[[a-zA-Z]+\]', '', message)
+ # Invio a telegram
+ bot.send_message(clean_message, None)
+
# Handle missing segments
if missing_ts:
os.rename(self.output_filename, self.output_filename.replace(".mp4", "_failed.mp4"))
@@ -890,7 +953,7 @@ class HLS_Downloader:
logging.info("class 'HLS_Downloader'; call _valida_playlist()")
# Retrieve the m3u8 playlist content
- if self.is_playlist_url:
+ if self.is_playlist_url:
if self.request_m3u8_playlist != 404:
m3u8_playlist_text = self.request_m3u8_playlist
m3u8_url_fixer.set_playlist(self.m3u8_playlist)
@@ -905,7 +968,7 @@ class HLS_Downloader:
# Check if the m3u8 content is valid
if m3u8_playlist_text is None:
console.log("[red]Playlist m3u8 to download is empty.")
- sys.exit(0)
+ sys.exit(0)
# Save the m3u8 playlist text to a temporary file
open(os.path.join(self.path_manager.base_temp, "playlist.m3u8"), "w+", encoding="utf-8").write(m3u8_playlist_text)
@@ -928,11 +991,11 @@ class HLS_Downloader:
self.download_tracker.add_subtitle(self.content_extractor.list_available_subtitles)
# Download each type of content
- if len(self.download_tracker.downloaded_video) > 0:
+ if DOWNLOAD_VIDEO and len(self.download_tracker.downloaded_video) > 0:
self.content_downloader.download_video(self.download_tracker.downloaded_video)
- if len(self.download_tracker.downloaded_audio) > 0:
+ if DOWNLOAD_AUDIO and len(self.download_tracker.downloaded_audio) > 0:
self.content_downloader.download_audio(self.download_tracker.downloaded_audio)
- if len(self.download_tracker.downloaded_subtitle) > 0:
+ if DOWNLOAD_SUBTITLE and len(self.download_tracker.downloaded_subtitle) > 0:
self.content_downloader.download_subtitle(self.download_tracker.downloaded_subtitle)
# Join downloaded content
@@ -940,7 +1003,7 @@ class HLS_Downloader:
# Clean up temporary files and directories
self._clean(self.content_joiner.converted_out_path)
-
+
def _process_index(self):
"""
Processes the m3u8 index to download only video.
@@ -950,7 +1013,7 @@ class HLS_Downloader:
# Download video
self.download_tracker.add_video(self.m3u8_index)
self.content_downloader.download_video(self.download_tracker.downloaded_video)
-
+
# Join video
self.content_joiner.setup(self.download_tracker.downloaded_video, [], [])
diff --git a/StreamingCommunity/Lib/Downloader/HLS/segments.py b/StreamingCommunity/Lib/Downloader/HLS/segments.py
index 0482ec2..d33d7f1 100644
--- a/StreamingCommunity/Lib/Downloader/HLS/segments.py
+++ b/StreamingCommunity/Lib/Downloader/HLS/segments.py
@@ -8,10 +8,10 @@ import signal
import logging
import binascii
import threading
+
from queue import PriorityQueue
from urllib.parse import urljoin, urlparse
from concurrent.futures import ThreadPoolExecutor, as_completed
-from typing import Dict
# External libraries
@@ -20,11 +20,12 @@ from tqdm import tqdm
# Internal utilities
-from StreamingCommunity.Util.color import Colors
from StreamingCommunity.Util.console import console
from StreamingCommunity.Util.headers import get_headers, random_headers
+from StreamingCommunity.Util.color import Colors
from StreamingCommunity.Util._jsonConfig import config_manager
from StreamingCommunity.Util.os import os_manager
+from StreamingCommunity.Util.call_stack import get_call_stack
# Logic class
@@ -34,19 +35,27 @@ from ...M3U8 import (
M3U8_Parser,
M3U8_UrlFix
)
+from ...FFmpeg.util import print_duration_table, format_duration
from .proxyes import main_test_proxy
# Config
TQDM_DELAY_WORKER = config_manager.get_float('M3U8_DOWNLOAD', 'tqdm_delay')
-TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
+TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
+
REQUEST_MAX_RETRY = config_manager.get_int('REQUESTS', 'max_retry')
REQUEST_VERIFY = False
+
THERE_IS_PROXY_LIST = os_manager.check_file("list_proxy.txt")
PROXY_START_MIN = config_manager.get_float('REQUESTS', 'proxy_start_min')
PROXY_START_MAX = config_manager.get_float('REQUESTS', 'proxy_start_max')
+
DEFAULT_VIDEO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_video_workser')
DEFAULT_AUDIO_WORKERS = config_manager.get_int('M3U8_DOWNLOAD', 'default_audio_workser')
-MAX_TIMEOOUT = config_manager.get_int("REQUESTS", "timeout")
+
+
+
+# Variable
+max_timeout = config_manager.get_int("REQUESTS", "timeout")
@@ -64,6 +73,8 @@ class M3U8_Segments:
self.tmp_folder = tmp_folder
self.is_index_url = is_index_url
self.expected_real_time = None
+ self.max_timeout = max_timeout
+
self.tmp_file_path = os.path.join(self.tmp_folder, "0.ts")
os.makedirs(self.tmp_folder, exist_ok=True)
@@ -76,8 +87,8 @@ class M3U8_Segments:
self.queue = PriorityQueue()
self.stop_event = threading.Event()
self.downloaded_segments = set()
- self.base_timeout = 0.5
- self.current_timeout = 3.0
+ self.base_timeout = 1.0
+ self.current_timeout = 5.0
# Stopping
self.interrupt_flag = threading.Event()
@@ -89,41 +100,86 @@ class M3U8_Segments:
self.info_nFailed = 0
def __get_key__(self, m3u8_parser: M3U8_Parser) -> bytes:
+ """
+ Retrieves the encryption key from the M3U8 playlist.
+
+ Parameters:
+ - m3u8_parser (M3U8_Parser): The parser object containing M3U8 playlist information.
+
+ Returns:
+ bytes: The encryption key in bytes.
+ """
+
+ # Construct the full URL of the key
key_uri = urljoin(self.url, m3u8_parser.keys.get('uri'))
parsed_url = urlparse(key_uri)
self.key_base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/"
-
+ logging.info(f"Uri key: {key_uri}")
+
+ # Make request to get porxy
try:
- client_params = {'headers': {'User-Agent': get_headers()}, 'timeout': MAX_TIMEOOUT}
- response = httpx.get(url=key_uri, **client_params)
+ response = httpx.get(
+ url=key_uri,
+ headers={'User-Agent': get_headers()},
+ timeout=max_timeout
+ )
response.raise_for_status()
- hex_content = binascii.hexlify(response.content).decode('utf-8')
- return bytes.fromhex(hex_content)
-
except Exception as e:
- raise Exception(f"Failed to fetch key: {e}")
+ raise Exception(f"Failed to fetch key from {key_uri}: {e}")
+
+ # Convert the content of the response to hexadecimal and then to bytes
+ hex_content = binascii.hexlify(response.content).decode('utf-8')
+ byte_content = bytes.fromhex(hex_content)
+ logging.info(f"URI: Hex content: {hex_content}, Byte content: {byte_content}")
+
+ #console.print(f"[cyan]Find key: [red]{hex_content}")
+ return byte_content
def parse_data(self, m3u8_content: str) -> None:
+ """
+ Parses the M3U8 content to extract segment information.
+
+ Parameters:
+ - m3u8_content (str): The content of the M3U8 file.
+ """
m3u8_parser = M3U8_Parser()
m3u8_parser.parse_data(uri=self.url, raw_content=m3u8_content)
+ self.expected_real_time = m3u8_parser.get_duration(return_string=False)
self.expected_real_time_s = m3u8_parser.duration
- if m3u8_parser.keys:
- key = self.__get_key__(m3u8_parser)
- self.decryption = M3U8_Decryption(
- key,
- m3u8_parser.keys.get('iv'),
- m3u8_parser.keys.get('method')
- )
+ # Check if there is an encryption key in the playlis
+ if m3u8_parser.keys is not None:
+ try:
- self.segments = [
- self.class_url_fixer.generate_full_url(seg)
- if "http" not in seg else seg
- for seg in m3u8_parser.segments
- ]
+ # Extract byte from the key
+ key = self.__get_key__(m3u8_parser)
+
+ except Exception as e:
+ raise Exception(f"Failed to retrieve encryption key {e}.")
+
+ iv = m3u8_parser.keys.get('iv')
+ method = m3u8_parser.keys.get('method')
+ logging.info(f"M3U8_Decryption - IV: {iv}, method: {method}")
+
+ # Create a decryption object with the key and set the method
+ self.decryption = M3U8_Decryption(key, iv, method)
+
+ # Store the segment information parsed from the playlist
+ self.segments = m3u8_parser.segments
+
+ # Fix URL if it is incomplete (missing 'http')
+ for i in range(len(self.segments)):
+ segment_url = self.segments[i]
+
+ if "http" not in segment_url:
+ self.segments[i] = self.class_url_fixer.generate_full_url(segment_url)
+ logging.info(f"Generated new URL: {self.segments[i]}, from: {segment_url}")
+
+ # Update segments for estimator
self.class_ts_estimator.total_segments = len(self.segments)
+ logging.info(f"Segmnets to download: [{len(self.segments)}]")
# Proxy
if THERE_IS_PROXY_LIST:
@@ -135,18 +191,35 @@ class M3U8_Segments:
sys.exit(0)
def get_info(self) -> None:
+ """
+ Makes a request to the index M3U8 file to get information about segments.
+ """
if self.is_index_url:
+
try:
- client_params = {'headers': {'User-Agent': get_headers()}, 'timeout': MAX_TIMEOOUT}
- response = httpx.get(self.url, **client_params)
+
+ # Send a GET request to retrieve the index M3U8 file
+ response = httpx.get(
+ self.url,
+ headers={'User-Agent': get_headers()},
+ timeout=max_timeout,
+ follow_redirects=True
+ )
response.raise_for_status()
-
- self.parse_data(response.text)
- with open(os.path.join(self.tmp_folder, "playlist.m3u8"), "w") as f:
- f.write(response.text)
-
+
+ # Save the M3U8 file to the temporary folder
+ path_m3u8_file = os.path.join(self.tmp_folder, "playlist.m3u8")
+ open(path_m3u8_file, "w+").write(response.text)
+
+ # Parse the text from the M3U8 index file
+ self.parse_data(response.text)
+
except Exception as e:
- raise RuntimeError(f"M3U8 info retrieval failed: {e}")
+ print(f"Error during M3U8 index request: {e}")
+
+ else:
+ # Parser data of content of index pass in input to class
+ self.parse_data(self.url)
def setup_interrupt_handler(self):
"""
@@ -164,19 +237,7 @@ class M3U8_Segments:
else:
print("Signal handler must be set in the main thread")
- def _get_http_client(self, index: int = None):
- client_params = {
- 'headers': random_headers(self.key_base_url) if hasattr(self, 'key_base_url') else {'User-Agent': get_headers()},
- 'timeout': MAX_TIMEOOUT,
- 'follow_redirects': True
- }
-
- if THERE_IS_PROXY_LIST and index is not None and hasattr(self, 'valid_proxy'):
- client_params['proxies'] = self.valid_proxy[index % len(self.valid_proxy)]
-
- return httpx.Client(**client_params)
-
- def download_segment(self, ts_url: str, index: int, progress_bar: tqdm, backoff_factor: float = 1.3) -> None:
+ def make_requests_stream(self, ts_url: str, index: int, progress_bar: tqdm, backoff_factor: float = 1.5) -> None:
"""
Downloads a TS segment and adds it to the segment queue with retry logic.
@@ -192,36 +253,84 @@ class M3U8_Segments:
return
try:
- with self._get_http_client(index) as client:
- start_time = time.time()
- response = client.get(ts_url)
-
- # Validate response and content
- response.raise_for_status()
- segment_content = response.content
- content_size = len(segment_content)
- duration = time.time() - start_time
+ start_time = time.time()
+
+ # Make request to get content
+ if THERE_IS_PROXY_LIST:
- # Decrypt if needed and verify decrypted content
- if self.decryption is not None:
- try:
- segment_content = self.decryption.decrypt(segment_content)
-
- except Exception as e:
- logging.error(f"Decryption failed for segment {index}: {str(e)}")
- self.interrupt_flag.set() # Interrupt the download process
- self.stop_event.set() # Trigger the stopping event for all threads
- break # Stop the current task immediately
+ # Get proxy from list
+ proxy = self.valid_proxy[index % len(self.valid_proxy)]
+ logging.info(f"Use proxy: {proxy}")
- self.class_ts_estimator.update_progress_bar(content_size, duration, progress_bar)
- self.queue.put((index, segment_content))
- self.downloaded_segments.add(index)
- progress_bar.update(1)
- return
+ with httpx.Client(proxies=proxy, verify=REQUEST_VERIFY) as client:
+ if 'key_base_url' in self.__dict__:
+ response = client.get(
+ url=ts_url,
+ headers=random_headers(self.key_base_url),
+ timeout=max_timeout,
+ follow_redirects=True
+ )
+
+ else:
+ response = client.get(
+ url=ts_url,
+ headers={'User-Agent': get_headers()},
+ timeout=max_timeout,
+ follow_redirects=True
+ )
+
+ else:
+ with httpx.Client(verify=REQUEST_VERIFY) as client_2:
+ if 'key_base_url' in self.__dict__:
+ response = client_2.get(
+ url=ts_url,
+ headers=random_headers(self.key_base_url),
+ timeout=max_timeout,
+ follow_redirects=True
+ )
+
+ else:
+ response = client_2.get(
+ url=ts_url,
+ headers={'User-Agent': get_headers()},
+ timeout=max_timeout,
+ follow_redirects=True
+ )
+
+ # Validate response and content
+ response.raise_for_status()
+ segment_content = response.content
+ content_size = len(segment_content)
+ duration = time.time() - start_time
+
+ # Decrypt if needed and verify decrypted content
+ if self.decryption is not None:
+ try:
+ segment_content = self.decryption.decrypt(segment_content)
+
+ except Exception as e:
+ logging.error(f"Decryption failed for segment {index}: {str(e)}")
+ self.interrupt_flag.set() # Interrupt the download process
+ self.stop_event.set() # Trigger the stopping event for all threads
+ break # Stop the current task immediately
+
+ # Update progress and queue
+ self.class_ts_estimator.update_progress_bar(content_size, duration, progress_bar)
+
+ # Add the segment to the queue
+ self.queue.put((index, segment_content))
+
+ # Track successfully downloaded segments
+ self.downloaded_segments.add(index)
+ progress_bar.update(1)
+
+ # Break out of the loop on success
+ return
except Exception as e:
logging.info(f"Attempt {attempt + 1} failed for segment {index} - '{ts_url}': {e}")
+ # Update stat variable class
if attempt > self.info_maxRetry:
self.info_maxRetry = ( attempt + 1 )
self.info_nRetry += 1
@@ -231,6 +340,8 @@ class M3U8_Segments:
self.queue.put((index, None)) # Marker for failed segment
progress_bar.update(1)
self.info_nFailed += 1
+
+ #break
sleep_time = backoff_factor * (2 ** attempt)
logging.info(f"Retrying segment {index} in {sleep_time} seconds...")
@@ -242,6 +353,7 @@ class M3U8_Segments:
"""
buffer = {}
expected_index = 0
+ segments_written = set()
with open(self.tmp_file_path, 'wb') as f:
while not self.stop_event.is_set() or not self.queue.empty():
@@ -263,6 +375,7 @@ class M3U8_Segments:
# Write segment if it's the next expected one
if index == expected_index:
f.write(segment_content)
+ segments_written.add(index)
f.flush()
expected_index += 1
@@ -272,6 +385,7 @@ class M3U8_Segments:
if next_segment is not None:
f.write(next_segment)
+ segments_written.add(expected_index)
f.flush()
expected_index += 1
@@ -280,7 +394,8 @@ class M3U8_Segments:
buffer[index] = segment_content
except queue.Empty:
- self.current_timeout = min(MAX_TIMEOOUT, self.current_timeout * 1.25)
+ self.current_timeout = min(self.max_timeout, self.current_timeout * 1.25)
+
if self.stop_event.is_set():
break
@@ -297,34 +412,84 @@ class M3U8_Segments:
"""
self.setup_interrupt_handler()
+ # Get config site from prev stack
+ frames = get_call_stack()
+ logging.info(f"Extract info from: {frames}")
+ config_site = str(frames[-4]['folder_base'])
+ logging.info(f"Use frame: {frames[-1]}")
+
+ # Workers to use for downloading
+ TQDM_MAX_WORKER = 0
+
+ # Select audio workers from folder of frames stack prev call.
+ try:
+ VIDEO_WORKERS = int(config_manager.get_dict('SITE', config_site)['video_workers'])
+ except:
+ #VIDEO_WORKERS = os.cpu_count()
+ VIDEO_WORKERS = DEFAULT_VIDEO_WORKERS
+
+ try:
+ AUDIO_WORKERS = int(config_manager.get_dict('SITE', config_site)['audio_workers'])
+ except:
+ #AUDIO_WORKERS = os.cpu_count()
+ AUDIO_WORKERS = DEFAULT_AUDIO_WORKERS
+
+ # Differnt workers for audio and video
+ if "video" in str(type):
+ TQDM_MAX_WORKER = VIDEO_WORKERS
+
+ if "audio" in str(type):
+ TQDM_MAX_WORKER = AUDIO_WORKERS
+
+ #console.print(f"[cyan]Video workers[white]: [green]{VIDEO_WORKERS} [white]| [cyan]Audio workers[white]: [green]{AUDIO_WORKERS}")
+
+ # Custom bar for mobile and pc
+ if TQDM_USE_LARGE_BAR:
+ bar_format = (
+ f"{Colors.YELLOW}[HLS] {Colors.WHITE}({Colors.CYAN}{description}{Colors.WHITE}): "
+ f"{Colors.RED}{{percentage:.2f}}% "
+ f"{Colors.MAGENTA}{{bar}} "
+ f"{Colors.WHITE}[ {Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] "
+ f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
+ )
+ else:
+ bar_format = (
+ f"{Colors.YELLOW}Proc{Colors.WHITE}: "
+ f"{Colors.RED}{{percentage:.2f}}% "
+ f"{Colors.WHITE}| "
+ f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
+ )
+
+ # Create progress bar
progress_bar = tqdm(
total=len(self.segments),
unit='s',
ascii='░▒█',
- bar_format=self._get_bar_format(description),
+ bar_format=bar_format,
mininterval=0.05
)
try:
+
+ # Start writer thread
writer_thread = threading.Thread(target=self.write_segments_to_file)
writer_thread.daemon = True
writer_thread.start()
# Configure workers and delay
- max_workers = self._get_worker_count(type)
+ max_workers = len(self.valid_proxy) if THERE_IS_PROXY_LIST else TQDM_MAX_WORKER
delay = max(PROXY_START_MIN, min(PROXY_START_MAX, 1 / (len(self.valid_proxy) + 1))) if THERE_IS_PROXY_LIST else TQDM_DELAY_WORKER
# Download segments with completion verification
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = []
for index, segment_url in enumerate(self.segments):
-
# Check for interrupt before submitting each task
if self.interrupt_flag.is_set():
break
time.sleep(delay)
- futures.append(executor.submit(self.download_segment, segment_url, index, progress_bar))
+ futures.append(executor.submit(self.make_requests_stream, segment_url, index, progress_bar))
# Wait for futures with interrupt handling
for future in as_completed(futures):
@@ -350,87 +515,59 @@ class M3U8_Segments:
break
try:
- self.download_segment(self.segments[index], index, progress_bar)
+ self.make_requests_stream(self.segments[index], index, progress_bar)
except Exception as e:
logging.error(f"Failed to retry segment {index}: {str(e)}")
+ except Exception as e:
+ logging.error(f"Download failed: {str(e)}")
+ raise
+
finally:
- self._cleanup_resources(writer_thread, progress_bar)
- if not self.interrupt_flag.is_set():
- self._verify_download_completion()
+ # Clean up resources
+ self.stop_event.set()
+ writer_thread.join(timeout=30)
+ progress_bar.close()
- return self._generate_results(type)
-
+ # Check if download was interrupted
+ if self.download_interrupted:
+ console.log("[red] Download was manually stopped.")
- def _get_bar_format(self, description: str) -> str:
- """
- Generate platform-appropriate progress bar format.
- """
- if not TQDM_USE_LARGE_BAR:
- return (
- f"{Colors.YELLOW}Proc{Colors.WHITE}: "
- f"{Colors.RED}{{percentage:.2f}}% "
- f"{Colors.WHITE}| "
- f"{Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
- )
-
- else:
- return (
- f"{Colors.YELLOW}[HLS] {Colors.WHITE}({Colors.CYAN}{description}{Colors.WHITE}): "
- f"{Colors.RED}{{percentage:.2f}}% "
- f"{Colors.MAGENTA}{{bar}} "
- f"{Colors.WHITE}[ {Colors.YELLOW}{{n_fmt}}{Colors.WHITE} / {Colors.RED}{{total_fmt}} {Colors.WHITE}] "
- f"{Colors.YELLOW}{{elapsed}} {Colors.WHITE}< {Colors.CYAN}{{remaining}}{{postfix}} {Colors.WHITE}]"
- )
-
- def _get_worker_count(self, stream_type: str) -> int:
- """
- Calculate optimal parallel workers based on stream type and infrastructure.
- """
- base_workers = {
- 'video': DEFAULT_VIDEO_WORKERS,
- 'audio': DEFAULT_AUDIO_WORKERS
- }.get(stream_type.lower(), 1)
-
- if THERE_IS_PROXY_LIST:
- return min(len(self.valid_proxy), base_workers * 2)
- return base_workers
-
- def _generate_results(self, stream_type: str) -> Dict:
- """Package final download results."""
- return {
- 'type': stream_type,
- 'nFailed': self.info_nFailed,
- 'stopped': self.download_interrupted
- }
-
- def _verify_download_completion(self) -> None:
- """Validate final download integrity."""
- total = len(self.segments)
- if len(self.downloaded_segments) / total < 0.999:
- missing = sorted(set(range(total)) - self.downloaded_segments)
- raise RuntimeError(f"Download incomplete ({len(self.downloaded_segments)/total:.1%}). Missing segments: {missing}")
-
- def _cleanup_resources(self, writer_thread: threading.Thread, progress_bar: tqdm) -> None:
- """Ensure resource cleanup and final reporting."""
+ # Clean up
self.stop_event.set()
writer_thread.join(timeout=30)
progress_bar.close()
-
- if self.download_interrupted:
- console.print("\n[red]Download terminated by user")
-
- if self.info_nFailed > 0:
- self._display_error_summary()
- def _display_error_summary(self) -> None:
- """Generate final error report."""
- console.print(f"\n[cyan]Retry Summary: "
- f"[white]Max retries: [green]{self.info_maxRetry} "
- f"[white]Total retries: [green]{self.info_nRetry} "
- f"[white]Failed segments: [red]{self.info_nFailed}")
+ # Final verification
+ try:
+ final_completion = (len(self.downloaded_segments) / total_segments) * 100
+ if final_completion < 99.9: # Less than 99.9% complete
+ missing = set(range(total_segments)) - self.downloaded_segments
+ raise Exception(f"Download incomplete ({final_completion:.1f}%). Missing segments: {sorted(missing)}")
+
+ except:
+ pass
+
+ # Verify output file
+ if not os.path.exists(self.tmp_file_path):
+ raise Exception("Output file missing")
- if self.info_nRetry > len(self.segments) * 0.3:
- console.print("[yellow]Warning: High retry count detected. Consider reducing worker count in config.")
\ No newline at end of file
+ file_size = os.path.getsize(self.tmp_file_path)
+ if file_size == 0:
+ raise Exception("Output file is empty")
+
+ # Display additional info when there is missing stream file
+ if self.info_nFailed > 0:
+
+ # Get expected time
+ ex_hours, ex_minutes, ex_seconds = format_duration(self.expected_real_time_s)
+ ex_formatted_duration = f"[yellow]{int(ex_hours)}[red]h [yellow]{int(ex_minutes)}[red]m [yellow]{int(ex_seconds)}[red]s"
+ console.print(f"[cyan]Max retry per URL[white]: [green]{self.info_maxRetry}[green] [white]| [cyan]Total retry done[white]: [green]{self.info_nRetry}[green] [white]| [cyan]Missing TS: [red]{self.info_nFailed} [white]| [cyan]Duration: {print_duration_table(self.tmp_file_path, None, True)} [white]| [cyan]Expected duation: {ex_formatted_duration} \n")
+
+ if self.info_nRetry >= len(self.segments) * 0.3:
+ console.print("[yellow]⚠ Warning:[/yellow] Too many retries detected! Consider reducing the number of [cyan]workers[/cyan] in the [magenta]config.json[/magenta] file. This will impact [bold]performance[/bold]. \n")
+
+ # Info to return
+ return {'type': type, 'nFailed': self.info_nFailed}
\ No newline at end of file
diff --git a/StreamingCommunity/Lib/Downloader/MP4/downloader.py b/StreamingCommunity/Lib/Downloader/MP4/downloader.py
index 0b19d76..01d96df 100644
--- a/StreamingCommunity/Lib/Downloader/MP4/downloader.py
+++ b/StreamingCommunity/Lib/Downloader/MP4/downloader.py
@@ -1,12 +1,12 @@
# 09.06.24
import os
-import signal
+import re
import sys
import ssl
import certifi
import logging
-import atexit
+
# External libraries
import httpx
@@ -29,15 +29,17 @@ from ...FFmpeg import print_duration_table
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
# Config
GET_ONLY_LINK = config_manager.get_bool('M3U8_PARSER', 'get_only_link')
-TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
+TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
-#Ending constant
-KILL_HANDLER = bool(False)
-
+
def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = None):
"""
@@ -49,6 +51,14 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
- referer (str, optional): The referer header value.
- headers_ (dict, optional): Custom headers for the request.
"""
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
+ if os.path.exists(path):
+ console.log("[red]Output file already exists.")
+ if TELEGRAM_BOT:
+ bot.send_message(f"Contenuto già scaricato!", None)
+ return 400
# Early return for link-only mode
if GET_ONLY_LINK:
@@ -65,7 +75,7 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
headers = {}
if referer:
headers['Referer'] = referer
-
+
# Use custom headers if provided, otherwise use default user agent
if headers_:
headers.update(headers_)
@@ -83,15 +93,15 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
verify=False, # Disable SSL certificate verification
http2=True # Optional: enable HTTP/2 support
)
-
+
# Download with streaming and progress tracking
with httpx.Client(transport=transport, timeout=httpx.Timeout(60.0)) as client:
with client.stream("GET", url, headers=headers, timeout=REQUEST_TIMEOUT) as response:
response.raise_for_status()
-
+
# Get total file size
total = int(response.headers.get('content-length', 0))
-
+
# Handle empty streams
if total == 0:
console.print("[bold red]No video stream found.[/bold red]")
@@ -115,34 +125,15 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
# Ensure directory exists
os.makedirs(os.path.dirname(path), exist_ok=True)
-
- def signal_handler(*args):
- """
- Signal handler for SIGINT
-
- Parameters:
- - args (tuple): The signal arguments (to prevent errors).
- """
-
- if(downloaded MAX_DOWNLOAD_SIZE:
# break
@@ -152,12 +143,20 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
console.print(Panel(
f"[bold green]Download completed![/bold green]\n"
f"[cyan]File size: [bold red]{internet_manager.format_file_size(os.path.getsize(path))}[/bold red]\n"
- f"[cyan]Duration: [bold]{print_duration_table(path, description=False, return_string=True)}[/bold]",
- title=f"{os.path.basename(path.replace('.mp4', ''))}",
+ f"[cyan]Duration: [bold]{print_duration_table(path, description=False, return_string=True)}[/bold]",
+ title=f"{os.path.basename(path.replace('.mp4', ''))}",
border_style="green"
))
- return path,KILL_HANDLER
-
+
+ if TELEGRAM_BOT:
+ message = f"Download completato\nDimensione: {internet_manager.format_file_size(os.path.getsize(path))}\nDurata: {print_duration_table(path, description=False, return_string=True)}\nTitolo: {os.path.basename(path.replace('.mp4', ''))}"
+ # Rimuovere i tag di colore usando una regex
+ clean_message = re.sub(r'\[[a-zA-Z]+\]', '', message)
+ # Invio a telegram
+ bot.send_message(clean_message, None)
+
+ return path
+
else:
console.print("[bold red]Download failed or file is empty.[/bold red]")
return None
@@ -166,17 +165,13 @@ def MP4_downloader(url: str, path: str, referer: str = None, headers_: dict = No
logging.error(f"HTTP error occurred: {http_err}")
console.print(f"[bold red]HTTP Error: {http_err}[/bold red]")
return None
-
+
except httpx.RequestError as req_err:
logging.error(f"Request error: {req_err}")
console.print(f"[bold red]Request Error: {req_err}[/bold red]")
return None
-
+
except Exception as e:
logging.error(f"Unexpected error during download: {e}")
console.print(f"[bold red]Unexpected Error: {e}[/bold red]")
return None
-
- except KeyboardInterrupt:
- console.print("[bold red]Download stopped by user.[/bold red]")
- return None
\ No newline at end of file
diff --git a/StreamingCommunity/Lib/Downloader/TOR/downloader.py b/StreamingCommunity/Lib/Downloader/TOR/downloader.py
index d5a79c0..08bfd8e 100644
--- a/StreamingCommunity/Lib/Downloader/TOR/downloader.py
+++ b/StreamingCommunity/Lib/Downloader/TOR/downloader.py
@@ -28,7 +28,7 @@ USERNAME = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['user'])
PASSWORD = str(config_manager.get_dict('DEFAULT', 'config_qbit_tor')['pass'])
# Config
-TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
+TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
REQUEST_TIMEOUT = config_manager.get_float('REQUESTS', 'timeout')
diff --git a/StreamingCommunity/Lib/FFmpeg/command.py b/StreamingCommunity/Lib/FFmpeg/command.py
index cc9e8bc..51a8a83 100644
--- a/StreamingCommunity/Lib/FFmpeg/command.py
+++ b/StreamingCommunity/Lib/FFmpeg/command.py
@@ -31,7 +31,7 @@ FFMPEG_DEFAULT_PRESET = config_manager.get("M3U8_CONVERSION", "default_preset")
# Variable
-TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
+TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
FFMPEG_PATH = os_summary.ffmpeg_path
diff --git a/StreamingCommunity/Lib/M3U8/estimator.py b/StreamingCommunity/Lib/M3U8/estimator.py
index 543e39b..76c3530 100644
--- a/StreamingCommunity/Lib/M3U8/estimator.py
+++ b/StreamingCommunity/Lib/M3U8/estimator.py
@@ -1,6 +1,6 @@
# 21.04.25
-import sys
+import os
import time
import logging
import threading
@@ -15,10 +15,11 @@ from tqdm import tqdm
# Internal utilities
from StreamingCommunity.Util.color import Colors
from StreamingCommunity.Util.os import internet_manager
+from StreamingCommunity.Util._jsonConfig import config_manager
# Variable
-TQDM_USE_LARGE_BAR = not ("android" in sys.platform or "ios" in sys.platform)
+TQDM_USE_LARGE_BAR = config_manager.get_int('M3U8_DOWNLOAD', 'tqdm_use_large_bar')
class M3U8_Ts_Estimator:
@@ -34,10 +35,13 @@ class M3U8_Ts_Estimator:
self.total_segments = total_segments
self.lock = threading.Lock()
self.speed = {"upload": "N/A", "download": "N/A"}
+ self.process_pid = os.getpid() # Get current process PID
+ logging.debug(f"Initializing M3U8_Ts_Estimator with PID: {self.process_pid}")
+ # Start the speed capture thread if TQDM_USE_LARGE_BAR is True
if TQDM_USE_LARGE_BAR:
logging.debug("TQDM_USE_LARGE_BAR is True, starting speed capture thread")
- self.speed_thread = threading.Thread(target=self.capture_speed)
+ self.speed_thread = threading.Thread(target=self.capture_speed, args=(1, self.process_pid))
self.speed_thread.daemon = True
self.speed_thread.start()
@@ -46,6 +50,8 @@ class M3U8_Ts_Estimator:
def add_ts_file(self, size: int, size_download: int, duration: float):
"""Add a file size to the list of file sizes."""
+ logging.debug(f"Adding ts file - size: {size}, download size: {size_download}, duration: {duration}")
+
if size <= 0 or size_download <= 0 or duration <= 0:
logging.error(f"Invalid input values: size={size}, size_download={size_download}, duration={duration}")
return
@@ -54,36 +60,95 @@ class M3U8_Ts_Estimator:
self.now_downloaded_size += size_download
logging.debug(f"Current total downloaded size: {self.now_downloaded_size}")
- def capture_speed(self, interval: float = 1):
+ def capture_speed(self, interval: float = 1, pid: int = None):
"""Capture the internet speed periodically."""
- last_upload, last_download = 0, 0
- speed_buffer = deque(maxlen=3)
+ logging.debug(f"Starting speed capture with interval {interval}s for PID: {pid}")
+ def get_network_io(process=None):
+ try:
+ if process:
+
+ # For process-specific monitoring
+ connections = process.connections(kind='inet')
+ if connections:
+ io_counters = process.io_counters()
+ logging.debug(f"Process IO counters: {io_counters}")
+ return io_counters
+
+ else:
+ logging.debug("No active internet connections found for process")
+ return None
+ else:
+
+ # For system-wide monitoring
+ io_counters = psutil.net_io_counters()
+ logging.debug(f"System IO counters: {io_counters}")
+ return io_counters
+
+ except Exception as e:
+ logging.error(f"Error getting network IO: {str(e)}")
+ return None
+
+ try:
+ process = psutil.Process(pid) if pid else None
+ logging.debug(f"Monitoring process: {process}")
+
+ except Exception as e:
+ logging.error(f"Failed to get process with PID {pid}: {str(e)}")
+ process = None
+
+ last_upload = None
+ last_download = None
+ first_run = True
+
+ # Buffer circolare per le ultime N misurazioni
+ speed_buffer_size = 3
+ speed_buffer = deque(maxlen=speed_buffer_size)
+
while True:
try:
- io_counters = psutil.net_io_counters()
- if not io_counters:
- raise ValueError("No IO counters available")
+ io_counters = get_network_io()
- current_upload, current_download = io_counters.bytes_sent, io_counters.bytes_recv
- if last_upload and last_download:
- upload_speed = (current_upload - last_upload) / interval
- download_speed = (current_download - last_download) / interval
- speed_buffer.append(max(0, download_speed))
+ if io_counters:
+ current_upload = io_counters.bytes_sent
+ current_download = io_counters.bytes_recv
- with self.lock:
- self.speed = {
- "upload": internet_manager.format_transfer_speed(max(0, upload_speed)),
- "download": internet_manager.format_transfer_speed(sum(speed_buffer) / len(speed_buffer))
- }
- logging.debug(f"Updated speeds - Upload: {self.speed['upload']}, Download: {self.speed['download']}")
+ if not first_run and last_upload is not None and last_download is not None:
+
+ # Calcola la velocità istantanea
+ upload_speed = max(0, (current_upload - last_upload) / interval)
+ download_speed = max(0, (current_download - last_download) / interval)
+
+ # Aggiungi al buffer
+ speed_buffer.append(download_speed)
+
+ # Calcola la media mobile delle velocità
+ if len(speed_buffer) > 0:
+ avg_download_speed = sum(speed_buffer) / len(speed_buffer)
+
+ if avg_download_speed > 0:
+ with self.lock:
+ self.speed = {
+ "upload": internet_manager.format_transfer_speed(upload_speed),
+ "download": internet_manager.format_transfer_speed(avg_download_speed)
+ }
+ logging.debug(f"Updated speeds - Upload: {self.speed['upload']}, Download: {self.speed['download']}")
+
+ last_upload = current_upload
+ last_download = current_download
+ first_run = False
- last_upload, last_download = current_upload, current_download
+ time.sleep(interval)
except Exception as e:
- logging.error(f"Error in speed capture: {str(e)}")
- self.speed = {"upload": "N/A", "download": "N/A"}
-
- time.sleep(interval)
+ logging.error(f"Error in speed capture loop: {str(e)}")
+ logging.exception("Full traceback:")
+ logging.sleep(interval)
+
+ def get_average_speed(self) -> list:
+ """Calculate the average internet speed."""
+ with self.lock:
+ logging.debug(f"Current speed data: {self.speed}")
+ return self.speed['download'].split(" ")
def calculate_total_size(self) -> str:
"""
@@ -93,20 +158,38 @@ class M3U8_Ts_Estimator:
str: The mean size of the files in a human-readable format.
"""
try:
+ if len(self.ts_file_sizes) == 0:
+ raise ValueError("No file sizes available to calculate total size.")
+
total_size = sum(self.ts_file_sizes)
mean_size = total_size / len(self.ts_file_sizes)
+
+ # Return formatted mean size
return internet_manager.format_file_size(mean_size)
+ except ZeroDivisionError as e:
+ logging.error("Division by zero error occurred: %s", e)
+ return "0B"
+
except Exception as e:
logging.error("An unexpected error occurred: %s", e)
return "Error"
+
+ def get_downloaded_size(self) -> str:
+ """
+ Get the total downloaded size formatted as a human-readable string.
+
+ Returns:
+ str: The total downloaded size as a human-readable string.
+ """
+ return internet_manager.format_file_size(self.now_downloaded_size)
def update_progress_bar(self, total_downloaded: int, duration: float, progress_counter: tqdm) -> None:
"""Updates the progress bar with download information."""
try:
self.add_ts_file(total_downloaded * self.total_segments, total_downloaded, duration)
- downloaded_file_size_str = internet_manager.format_file_size(self.now_downloaded_size)
+ downloaded_file_size_str = self.get_downloaded_size()
file_total_size = self.calculate_total_size()
number_file_downloaded = downloaded_file_size_str.split(' ')[0]
@@ -115,13 +198,15 @@ class M3U8_Ts_Estimator:
units_file_total_size = file_total_size.split(' ')[1]
if TQDM_USE_LARGE_BAR:
- speed_data = self.speed['download'].split(" ")
+ speed_data = self.get_average_speed()
+ #logging.debug(f"Speed data for progress bar: {speed_data}")
if len(speed_data) >= 2:
average_internet_speed = speed_data[0]
average_internet_unit = speed_data[1]
else:
+ logging.warning(f"Invalid speed data format: {speed_data}")
average_internet_speed = "N/A"
average_internet_unit = ""
@@ -138,6 +223,7 @@ class M3U8_Ts_Estimator:
)
progress_counter.set_postfix_str(progress_str)
+ #logging.debug(f"Updated progress bar: {progress_str}")
except Exception as e:
logging.error(f"Error updating progress bar: {str(e)}")
\ No newline at end of file
diff --git a/StreamingCommunity/Upload/version.py b/StreamingCommunity/Upload/version.py
index 6e81304..e8f4c86 100644
--- a/StreamingCommunity/Upload/version.py
+++ b/StreamingCommunity/Upload/version.py
@@ -1,5 +1,5 @@
__title__ = 'StreamingCommunity'
-__version__ = '2.5.0'
+__version__ = '2.4.0'
__author__ = 'Lovi-0'
__description__ = 'A command-line program to download film'
__copyright__ = 'Copyright 2024'
diff --git a/StreamingCommunity/Util/table.py b/StreamingCommunity/Util/table.py
index 65190c2..9845017 100644
--- a/StreamingCommunity/Util/table.py
+++ b/StreamingCommunity/Util/table.py
@@ -18,6 +18,11 @@ from typing import Dict, List, Any
from .message import start_message
from .call_stack import get_call_stack
+# Telegram bot instance
+from telegram_bot import get_bot_instance
+from StreamingCommunity.Util._jsonConfig import config_manager
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+
class TVShowManager:
def __init__(self):
@@ -44,7 +49,7 @@ class TVShowManager:
def add_column(self, column_info: Dict[str, Dict[str, str]]) -> None:
"""
Add column information.
-
+
Parameters:
- column_info (Dict[str, Dict[str, str]]): Dictionary containing column names, their colors, and justification.
"""
@@ -102,34 +107,34 @@ class TVShowManager:
current_path = research_func['folder']
while not os.path.exists(os.path.join(current_path, 'StreamingCommunity')):
current_path = os.path.dirname(current_path)
-
+
# Add project root to Python path
project_root = current_path
#print(f"[DEBUG] Project Root: {project_root}")
-
+
if project_root not in sys.path:
sys.path.insert(0, project_root)
-
+
# Import using full absolute import
module_path = f'StreamingCommunity.Api.Site.{site_name}'
#print(f"[DEBUG] Importing module: {module_path}")
-
+
# Import the module
module = importlib.import_module(module_path)
-
+
# Get the search function
search_func = getattr(module, 'search')
-
+
# Call the search function with the search string
search_func(None)
-
+
except Exception as e:
self.console.print(f"[red]Error during search: {e}")
-
+
# Print detailed traceback
import traceback
traceback.print_exc()
-
+
# Optionally remove the path if you want to clean up
if project_root in sys.path:
sys.path.remove(project_root)
@@ -142,13 +147,16 @@ class TVShowManager:
Parameters:
- force_int_input(bool): If True, only accept integer inputs from 0 to max_int_input
- max_int_input (int): range of row to show
-
+
Returns:
str: Last command executed before breaking out of the loop.
"""
total_items = len(self.tv_shows)
last_command = "" # Variable to store the last command executed
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+
while True:
start_message()
@@ -167,16 +175,33 @@ class TVShowManager:
self.console.print(f"\n[green]Press [red]Enter [green]for next page, [red]'q' [green]to quit, or [red]'back' [green]to search.")
if not force_int_input:
- key = Prompt.ask(
+ if TELEGRAM_BOT:
+ self.console.print(f"\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, "
+ "[yellow](e.g., 1-2) [cyan]for a range of media, or [yellow](e.g., 3-*) [cyan]to download from a specific index to the end")
+ key = bot.ask(
+ "select_title_episode",
+ f"Inserisci l'indice dei media (ad esempio, 1), * per scaricare tutti i media, (ad esempio, 1-2) per un intervallo di media, o (ad esempio, 3-*) per scaricare dal un indice specifico fino alla fine.",
+ None
+ )
+ else:
+ key = Prompt.ask(
"\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, "
"[yellow](e.g., 1-2) [cyan]for a range of media, or [yellow](e.g., 3-*) [cyan]to download from a specific index to the end"
- )
-
+ )
+
else:
choices = [str(i) for i in range(0, max_int_input)]
choices.extend(["q", "quit", "b", "back"])
-
- key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
+ if TELEGRAM_BOT:
+ self.console.print(f"[cyan]Insert media [red]index")
+ key = bot.ask(
+ "select_title",
+ f"Scegli il contenuto da scaricare:\n📺 Serie TV - 🎞️ Film - 🌀 Anime oppure `back` per tornare indietro",
+ None
+ )
+ else:
+ key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
+
last_command = key
if key.lower() == "q" or key.lower() == "quit":
@@ -198,16 +223,34 @@ class TVShowManager:
# Last slice, ensure all remaining items are shown
self.console.print(f"\n [green]You've reached the end. [red]Enter [green]for first page, [red]'q' [green]to quit, or [red]'back' [green]to search.")
if not force_int_input:
- key = Prompt.ask(
- "\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, "
- "[yellow](e.g., 1-2) [cyan]for a range of media, or [yellow](e.g., 3-*) [cyan]to download from a specific index to the end"
- )
+ if TELEGRAM_BOT:
+ self.console.print(f"\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, "
+ "[yellow](e.g., 1-2) [cyan]for a range of media, or [yellow](e.g., 3-*) [cyan]to download from a specific index to the end")
+ key = bot.ask(
+ "select_title_episode",
+ f"Inserisci l'indice dei media (ad esempio, 1), * per scaricare tutti i media, (ad esempio, 1-2) per un intervallo di media, o (ad esempio, 3-*) per scaricare dal un indice specifico fino alla fine.",
+ None
+ )
+ else:
+ key = Prompt.ask(
+ "\n[cyan]Insert media index [yellow](e.g., 1), [red]* [cyan]to download all media, "
+ "[yellow](e.g., 1-2) [cyan]for a range of media, or [yellow](e.g., 3-*) [cyan]to download from a specific index to the end"
+ )
else:
choices = [str(i) for i in range(0, max_int_input)]
choices.extend(["q", "quit", "b", "back"])
- key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
+ if TELEGRAM_BOT:
+ self.console.print(f"[cyan]Insert media [red]index")
+ key = bot.ask(
+ "select_title",
+ f"Scegli il contenuto da scaricare:\n📺 Serie TV - 🎞️ Film - 🌀 Anime oppure `back` per tornare indietro",
+ None
+ )
+ else:
+ key = Prompt.ask("[cyan]Insert media [red]index", choices=choices, show_choices=False)
+
last_command = key
if key.lower() == "q" or key.lower() == "quit":
@@ -222,7 +265,7 @@ class TVShowManager:
else:
break
-
+
return last_command
def clear(self):
diff --git a/StreamingCommunity/run.py b/StreamingCommunity/run.py
index f1a616e..dd949d7 100644
--- a/StreamingCommunity/run.py
+++ b/StreamingCommunity/run.py
@@ -1,5 +1,8 @@
# 10.12.23
+
+import threading
+import asyncio
import os
import sys
import time
@@ -19,9 +22,13 @@ from StreamingCommunity.Upload.update import update as git_update
from StreamingCommunity.Util.os import os_summary
from StreamingCommunity.Util.logger import Logger
+# Telegram bot instance
+from telegram_bot import get_bot_instance
# Config
CLOSE_CONSOLE = config_manager.get_bool('DEFAULT', 'not_close')
+TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
+from session import get_session, deleteScriptId
def run_function(func: Callable[..., None], close_console: bool = False) -> None:
@@ -33,16 +40,18 @@ def run_function(func: Callable[..., None], close_console: bool = False) -> None
close_console (bool, optional): Whether to close the console after running the function once. Defaults to False.
"""
if close_console:
- while 1:
+ while True:
func()
else:
func()
-
def load_search_functions():
modules = []
loaded_functions = {}
+ # Lista dei siti da escludere se TELEGRAM_BOT è attivo
+ excluded_sites = {"cb01new", "ddlstreamitaly", "guardaserie", "ilcorsaronero", "mostraguarda"} if TELEGRAM_BOT else set()
+
# Find api home directory
if getattr(sys, 'frozen', False): # Modalità PyInstaller
base_path = os.path.join(sys._MEIPASS, "StreamingCommunity")
@@ -51,12 +60,17 @@ def load_search_functions():
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:
@@ -118,13 +132,54 @@ def initialize():
sys.exit(0)
# Attempting GitHub update
- try:
+ """ try:
git_update()
except:
- console.log("[red]Error with loading github.")
+ console.log("[red]Error with loading github.") """
+
+def restart_script():
+ """Riavvia lo script con gli stessi argomenti della riga di comando."""
+ print("\n🔄 Riavvio dello script...\n")
+ python = sys.executable
+ os.execv(python, [python] + sys.argv) # Riavvia lo stesso script con gli stessi argomenti
+
+def force_exit():
+ """Forza la chiusura dello script in qualsiasi contesto."""
+
+ print("\n🛑 Chiusura dello script in corso...")
+
+ # 1️⃣ Chiudi tutti i thread tranne il principale
+ for t in threading.enumerate():
+ if t is not threading.main_thread():
+ print(f"🔄 Chiusura thread: {t.name}")
+ t.join(timeout=1)
+
+ # 2️⃣ Ferma asyncio, se attivo
+ try:
+ loop = asyncio.get_event_loop()
+ if loop.is_running():
+ print("⚡ Arresto del loop asyncio...")
+ loop.stop()
+ except RuntimeError:
+ pass # Se non c'è un loop asyncio attivo, ignora l'errore
+
+ # 3️⃣ Esce con sys.exit(), se fallisce usa os._exit()
+ try:
+ print("✅ Uscita con sys.exit(0)")
+ sys.exit(0)
+ except SystemExit:
+ pass # Se viene intercettato da un try/except, ignora
+
+ print("🚨 Uscita forzata con os._exit(0)")
+ os._exit(0) # Se sys.exit() non funziona, esce forzatamente
-def main():
+def main(script_id):
+
+ if TELEGRAM_BOT:
+ bot = get_bot_instance()
+ bot.send_message(f"🏁 Avviato script {script_id}", None)
+
start = time.time()
# Create logger
@@ -140,6 +195,8 @@ def main():
description='Script to download movies and series from the internet. Use these commands to configure the script and control its behavior.'
)
+ parser.add_argument("script_id", nargs="?", default="unknown", help="ID dello script")
+
# Add arguments for the main configuration parameters
parser.add_argument(
'--add_siteName', type=bool, help='Enable or disable adding the site name to the file name (e.g., true/false).'
@@ -173,7 +230,7 @@ def main():
"film_serie": "yellow",
"film": "blue",
"serie": "green",
- "other": "white"
+ "altro": "white"
}
# Add dynamic arguments based on loaded search modules
@@ -234,12 +291,67 @@ def main():
[f"{key}: [{color_map[label[1]]}]{label[0]}[/{color_map[label[1]]}]" for key, label in choice_labels.items()]
) + "[white])"
- # Ask the user for input
- category = msg.ask(prompt_message, choices=list(choice_labels.keys()), default="0", show_choices=False, show_default=False)
+ if TELEGRAM_BOT:
+
+ # Mappa delle emoji per i colori
+ emoji_map = {
+ "yellow": "🟡", # Giallo
+ "red": "🔴", # Rosso
+ "blue": "🔵", # Blu
+ "green": "🟢" # Verde
+ }
+
+ # Display the category legend in a single line
+ category_legend_str = "Categorie: \n" + " | ".join([
+ f"{emoji_map.get(color, '⚪')} {category.capitalize()}"
+ for category, color in color_map.items()
+ ])
+
+ # Costruisci il messaggio con le emoji al posto dei colori
+ prompt_message = "Inserisci il sito:\n" + "\n".join(
+ [f"{key}: {emoji_map[color_map[label[1]]]} {label[0]}" for key, label in choice_labels.items()]
+ )
+
+ console.print(f"\n{prompt_message}")
+
+ # Chiedi la scelta all'utente con il bot Telegram
+ category = bot.ask(
+ "select_provider",
+ f"{category_legend_str}\n\n{prompt_message}",
+ None # Passiamo la lista delle chiavi come scelte
+ )
+
+ else:
+
+ #console.print(f"\n{prompt_message}")
+
+ # Ask the user for input
+ category = msg.ask(prompt_message, choices=list(choice_labels.keys()), default="0", show_choices=False, show_default=False)
+
# Run the corresponding function based on user input
if category in input_to_function:
run_function(input_to_function[category])
else:
+
+ if TELEGRAM_BOT:
+ bot.send_message(f"Categoria non valida", None)
+
console.print("[red]Invalid category.")
- sys.exit(0)
\ No newline at end of file
+
+ if CLOSE_CONSOLE:
+ restart_script() # Riavvia lo script invece di uscire
+ # Riavvia lo script
+ # Chiude il processo attuale e avvia una nuova istanza dello script
+ """ subprocess.Popen([sys.executable] + sys.argv)
+ sys.exit() """
+ else:
+ force_exit() # Usa la funzione per chiudere sempre
+
+ if TELEGRAM_BOT:
+ bot.send_message(f"Chiusura in corso", None)
+ # Delete script_id
+ script_id = get_session()
+ if script_id != "unknown":
+ deleteScriptId(script_id)
+
\ No newline at end of file
diff --git a/app.log b/app.log
new file mode 100644
index 0000000..c9e5a55
--- /dev/null
+++ b/app.log
@@ -0,0 +1,138 @@
+[os.py:424 - get_system_summary() ] 2025-01-30 17:14:34,489 - INFO - Python: 3.13.1 (CPython x86_64) - Linux-6.12.9-200.fc41.x86_64-x86_64-with-glibc2.40 (glibc 2...4.0)
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,079 - INFO - Library: httpx-0.28.1
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,080 - INFO - Library: bs4-0.0.2
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,082 - INFO - Library: rich-13.9.4
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,084 - INFO - Library: tqdm-4.66.6
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,085 - INFO - Library: m3u8-6.0.0
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,086 - INFO - Library: psutil-5.9.8
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,088 - INFO - Library: unidecode-1.3.8
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,088 - INFO - Library: jsbeautifier-1.15.1
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,089 - INFO - Library: pathvalidate-3.2.3
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,090 - INFO - Library: pycryptodomex-3.21.0
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,091 - INFO - Library: fake-useragent-2.0.3
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,092 - INFO - Library: qbittorrent-api-2024.12.71
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,092 - INFO - Library: python-qbittorrent-0.4.3
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,093 - INFO - Library: serpapi-0.1.5
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,094 - INFO - Library: flask-3.1.0
+[os.py:476 - get_system_summary() ] 2025-01-30 17:14:35,094 - INFO - Library: flask-socketio-5.5.1
+[os.py:479 - get_system_summary() ] 2025-01-30 17:14:35,122 - INFO - Libraries: httpx-0.28.1, bs4-0.0.2, rich-13.9.4, tqdm-4.66.6, m3u8-6.0.0, psutil-5.9.8, unidecode-1.3.8, jsbeautifier-1.15.1, pathvalidate-3.2.3, pycryptodomex-3.21.0, fake-useragent-2.0.3, qbittorrent-api-2024.12.71, python-qbittorrent-0.4.3, serpapi-0.1.5, flask-3.1.0, flask-socketio-5.5.1
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:38,841 - INFO - Load module name: 1337xx
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,057 - INFO - Read key: DEFAULT.map_episode_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,058 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,058 - INFO - Read key: SITE.1337xx
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,058 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,058 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,059 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,059 - INFO - Read key: DEFAULT.disable_searchDomain
+[decryptor.py:20 - () ] 2025-01-30 17:14:39,062 - INFO - [cyan]Decrypy use: Cryptodomex
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,203 - INFO - Read key: M3U8_DOWNLOAD.tqdm_use_large_bar
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,211 - INFO - Read key: DEFAULT.debug
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,211 - INFO - Read key: M3U8_CONVERSION.use_codec
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,211 - INFO - Read key: M3U8_CONVERSION.use_vcodec
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,211 - INFO - Read key: M3U8_CONVERSION.use_acodec
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,211 - INFO - Read key: M3U8_CONVERSION.use_bitrate
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,211 - INFO - Read key: M3U8_CONVERSION.use_gpu
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,211 - INFO - Read key: M3U8_CONVERSION.default_preset
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,212 - INFO - Read key: M3U8_DOWNLOAD.tqdm_use_large_bar
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,213 - INFO - Read key: M3U8_DOWNLOAD.tqdm_delay
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,213 - INFO - Read key: M3U8_DOWNLOAD.tqdm_use_large_bar
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,213 - INFO - Read key: REQUESTS.max_retry
+[os.py:229 - check_file() ] 2025-01-30 17:14:39,214 - INFO - Check if file exists: list_proxy.txt
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,214 - INFO - Read key: REQUESTS.proxy_start_min
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,214 - INFO - Read key: REQUESTS.proxy_start_max
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,214 - INFO - Read key: M3U8_DOWNLOAD.default_video_workser
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,214 - INFO - Read key: M3U8_DOWNLOAD.default_audio_workser
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,214 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,215 - INFO - Read key: M3U8_DOWNLOAD.specific_list_audio
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,215 - INFO - Read key: M3U8_DOWNLOAD.specific_list_subtitles
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,215 - INFO - Read key: M3U8_DOWNLOAD.download_video
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,215 - INFO - Read key: M3U8_DOWNLOAD.download_audio
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,215 - INFO - Read key: M3U8_DOWNLOAD.merge_audio
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,215 - INFO - Read key: M3U8_DOWNLOAD.download_sub
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,215 - INFO - Read key: M3U8_DOWNLOAD.merge_subs
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,216 - INFO - Read key: M3U8_DOWNLOAD.cleanup_tmp_folder
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,216 - INFO - Read key: M3U8_PARSER.force_resolution
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,216 - INFO - Read key: M3U8_PARSER.get_only_link
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,216 - INFO - Read key: REQUESTS.max_retry
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,216 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,217 - INFO - Read key: M3U8_PARSER.get_only_link
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,218 - INFO - Read key: M3U8_DOWNLOAD.tqdm_use_large_bar
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,218 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,220 - INFO - Read key: DEFAULT.config_qbit_tor
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,220 - INFO - Read key: DEFAULT.config_qbit_tor
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,220 - INFO - Read key: DEFAULT.config_qbit_tor
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,221 - INFO - Read key: DEFAULT.config_qbit_tor
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,221 - INFO - Read key: M3U8_DOWNLOAD.tqdm_use_large_bar
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,221 - INFO - Read key: REQUESTS.timeout
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,221 - INFO - Load module name: altadefinizionegratis
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,222 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,222 - INFO - Read key: SITE.altadefinizionegratis
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,223 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,223 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,223 - INFO - Read key: DEFAULT.add_siteName
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,223 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,223 - INFO - Read key: DEFAULT.disable_searchDomain
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,237 - INFO - Read key: REQUESTS.timeout
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,238 - INFO - Load module name: animeunity
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,239 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,240 - INFO - Read key: SITE.animeunity
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,240 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,240 - INFO - Read key: DEFAULT.anime_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,240 - INFO - Read key: DEFAULT.add_siteName
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,240 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,240 - INFO - Read key: DEFAULT.disable_searchDomain
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,243 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,244 - INFO - Read key: REQUESTS.timeout
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,244 - INFO - Load module name: cb01new
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,245 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,245 - INFO - Read key: SITE.cb01new
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,246 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,246 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,246 - INFO - Read key: DEFAULT.add_siteName
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,246 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,247 - INFO - Read key: DEFAULT.disable_searchDomain
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,248 - INFO - Read key: REQUESTS.timeout
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,249 - INFO - Load module name: ddlstreamitaly
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,251 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,251 - INFO - Read key: SITE.ddlstreamitaly
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,252 - INFO - Read key: SITE.ddlstreamitaly
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,252 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,252 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,253 - INFO - Read key: DEFAULT.add_siteName
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,253 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,254 - INFO - Read key: DEFAULT.disable_searchDomain
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,255 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,256 - INFO - Read key: REQUESTS.timeout
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,256 - INFO - Load module name: guardaserie
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,258 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,258 - INFO - Read key: SITE.guardaserie
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,258 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,258 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,258 - INFO - Read key: DEFAULT.add_siteName
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,258 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,258 - INFO - Read key: DEFAULT.disable_searchDomain
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,260 - INFO - Load module name: ilcorsaronero
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,261 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,262 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,262 - INFO - Read key: SITE.ilcorsaronero
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,262 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,262 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,262 - INFO - Read key: DEFAULT.add_siteName
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,263 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,263 - INFO - Read key: DEFAULT.disable_searchDomain
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,263 - INFO - Load module name: mostraguarda
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,266 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,267 - INFO - Read key: SITE.mostraguarda
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,267 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,267 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,267 - INFO - Read key: DEFAULT.add_siteName
+[run.py:77 - load_search_functions() ] 2025-01-30 17:14:39,267 - INFO - Load module name: streamingcommunity
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,269 - INFO - Read key: DEFAULT.root_path
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,269 - INFO - Read key: SITE.streamingcommunity
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,269 - INFO - Read key: DEFAULT.serie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,269 - INFO - Read key: DEFAULT.movie_folder_name
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,270 - INFO - Read key: DEFAULT.add_siteName
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,270 - INFO - Read key: REQUESTS.timeout
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,270 - INFO - Read key: DEFAULT.disable_searchDomain
+[_jsonConfig.py:89 - read_key() ] 2025-01-30 17:14:39,271 - INFO - Read key: REQUESTS.timeout
+[run.py:157 - main() ] 2025-01-30 17:14:39,272 - INFO - Load module in: 5.0520179271698 s
diff --git a/config.json b/config.json
index a9858a9..9a7093d 100644
--- a/config.json
+++ b/config.json
@@ -2,23 +2,24 @@
"DEFAULT": {
"debug": false,
"log_file": "app.log",
- "log_to_file": true,
- "show_message": true,
+ "log_to_file": false,
+ "show_message": false,
"clean_console": true,
- "root_path": "Video",
+ "root_path": "/home/giuseppepiccolo/Develop/docker/jellyfin/media/",
"movie_folder_name": "Movie",
- "serie_folder_name": "TV",
+ "serie_folder_name": "Serie",
"anime_folder_name": "Anime",
- "map_episode_name": "E%(episode)_%(episode_name)",
+ "map_episode_name": "%(tv_name)_S%(season)E%(episode)_%(episode_name)",
"config_qbit_tor": {
- "host": "192.168.1.99",
- "port": "7060",
+ "host": "192.168.5.172",
+ "port": "8080",
"user": "admin",
"pass": "adminadmin"
},
"add_siteName": false,
"disable_searchDomain": false,
- "not_close": false
+ "not_close": false,
+ "telegram_bot": true
},
"REQUESTS": {
"timeout": 30,
@@ -28,13 +29,17 @@
},
"M3U8_DOWNLOAD": {
"tqdm_delay": 0.12,
+ "tqdm_use_large_bar": false,
"default_video_workser": 12,
"default_audio_workser": 12,
+ "download_video": true,
+ "download_audio": true,
"merge_audio": true,
"specific_list_audio": [
"ita"
],
- "merge_subs": true,
+ "download_sub": false,
+ "merge_subs": false,
"specific_list_subtitles": [
"eng",
"spa"
@@ -58,10 +63,10 @@
"domain": "paris"
},
"altadefinizionegratis": {
- "domain": "site"
+ "domain": "info"
},
"guardaserie": {
- "domain": "meme"
+ "domain": "academy"
},
"mostraguarda": {
"domain": "stream"
@@ -78,7 +83,7 @@
"domain": "so"
},
"cb01new": {
- "domain": "mobi"
+ "domain": "video"
},
"1337xx": {
"domain": "to"
diff --git a/request_manager.py b/request_manager.py
new file mode 100644
index 0000000..963bbad
--- /dev/null
+++ b/request_manager.py
@@ -0,0 +1,79 @@
+import json
+import time
+from typing import Optional
+
+class RequestManager:
+ _instance = None
+
+ def __new__(cls, *args, **kwargs):
+ if not cls._instance:
+ cls._instance = super().__new__(cls)
+ return cls._instance
+
+ def __init__(self, json_file: str = "active_requests.json"):
+ if not hasattr(self, 'initialized'):
+ self.json_file = json_file
+ self.initialized = True
+ self.on_response_callback = None # Aggiungi un campo per il callback
+
+ def create_request(self, type: str) -> str:
+ request_data = {
+ "type": type,
+ "response": None,
+ "timestamp": time.time()
+ }
+
+ # Aggiungi il tipo al salvataggio della richiesta
+ with open(self.json_file, "w") as f:
+ json.dump(request_data, f)
+
+ return "Ok"
+
+ def save_response(self, message_text: str) -> bool:
+ try:
+ # Carica il file JSON
+ with open(self.json_file, "r") as f:
+ data = json.load(f)
+
+ # Controlla se esiste la chiave 'type' e se la risposta è presente
+ if "type" in data and "response" in data:
+ data["response"] = message_text # Aggiorna la risposta
+
+ # Scrivi il file JSON aggiornato
+ with open(self.json_file, "w") as f:
+ json.dump(data, f, indent=4) # Formatta il file JSON
+
+ return True
+ else:
+ return False
+
+ except (FileNotFoundError, json.JSONDecodeError) as e:
+ print(f"⚠️ save_response - errore: {e}")
+ return False
+
+ def get_response(self) -> Optional[str]:
+ try:
+ with open(self.json_file, "r") as f:
+ data = json.load(f)
+ # Verifica se esiste la chiave "response"
+ if "response" in data:
+ response = data["response"] # Ottieni la risposta direttamente
+ if response is not None and self.on_response_callback:
+ # Se la risposta è disponibile, chiama il callback
+ self.on_response_callback(response)
+ return response
+
+ except (FileNotFoundError, json.JSONDecodeError) as e:
+ print(f"get_response - errore: {e}")
+ return None
+
+ def clear_file(self) -> bool:
+ try:
+ # Svuota il file JSON scrivendo un oggetto vuoto
+ with open(self.json_file, "w") as f:
+ json.dump({}, f)
+ print(f"File {self.json_file} è stato svuotato con successo.")
+ return True
+ except Exception as e:
+ print(f"⚠️ clear_file - errore: {e}")
+ return False
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index 01f23e8..a710d62 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,7 +8,9 @@ unidecode
jsbeautifier
pathvalidate
pycryptodomex
-googlesearch-python
fake-useragent
qbittorrent-api
-python-qbittorrent
\ No newline at end of file
+python-qbittorrent
+serpapi
+pyTelegramBotAPI
+Pillow
\ No newline at end of file
diff --git a/session.py b/session.py
new file mode 100755
index 0000000..b81add8
--- /dev/null
+++ b/session.py
@@ -0,0 +1,62 @@
+import json
+
+session_data = {}
+
+def set_session(value):
+ # salvo script_id in session_data
+ session_data['script_id'] = value
+
+def get_session():
+ # controllo se script_id è presente in session_data
+ return session_data.get('script_id', 'unknown')
+
+def updateScriptId(screen_id, titolo):
+ # definisco il nome del file json
+ json_file = "scripts.json"
+ try:
+ # apro il file json
+ with open(json_file, 'r') as f:
+ scripts_data = json.load(f)
+ except FileNotFoundError:
+ # Se il file non esiste, inizializzo la lista vuota
+ scripts_data = []
+
+ # cerco lo script con lo screen_id
+ for script in scripts_data:
+ if script["screen_id"] == screen_id:
+ # se trovo il match, aggiorno il titolo
+ script["titolo"] = titolo
+ # aggiorno il file json
+ with open(json_file, 'w') as f:
+ json.dump(scripts_data, f, indent=4)
+ #print(f"Titolo aggiornato per screen_id {screen_id}")
+ return
+
+ # se non trovo nessuno script con lo screen_id
+ print(f"Screen_id {screen_id} non trovato.")
+
+# creo la funzione che elimina lo script con lo screen_id specificato
+def deleteScriptId(screen_id):
+ # definisco il nome del file json
+ json_file = "scripts.json"
+ try:
+ # apro il file json
+ with open(json_file, 'r') as f:
+ scripts_data = json.load(f)
+ except FileNotFoundError:
+ # Se il file non esiste, inizializzo la lista vuota
+ scripts_data = []
+
+ # cerco lo script con lo screen_id
+ for script in scripts_data:
+ if script["screen_id"] == screen_id:
+ # se trovo il match, elimino lo script
+ scripts_data.remove(script)
+ # aggiorno il file json
+ with open(json_file, 'w') as f:
+ json.dump(scripts_data, f, indent=4)
+ print(f"Script eliminato per screen_id {screen_id}")
+ return
+
+ # se non trovo nessuno script con lo screen_id
+ print(f"Screen_id {screen_id} non trovato.")
\ No newline at end of file
diff --git a/telegram_bot.py b/telegram_bot.py
new file mode 100644
index 0000000..61f3e45
--- /dev/null
+++ b/telegram_bot.py
@@ -0,0 +1,513 @@
+import telebot
+from telebot import types
+import time
+import uuid
+import subprocess
+import os, re, sys
+import json
+from request_manager import RequestManager
+import threading
+
+# Funzione per caricare variabili da un file .env
+def load_env(file_path=".env"):
+ if os.path.exists(file_path):
+ with open(file_path) as f:
+ for line in f:
+ if line.strip() and not line.startswith("#"):
+ key, value = line.strip().split("=", 1)
+ os.environ[key] = value
+
+# Carica le variabili
+load_env()
+
+class TelegramBot:
+ _instance = None
+ _config_file = "bot_config.json"
+
+ @classmethod
+ def get_instance(cls):
+ if cls._instance is None:
+ # Prova a caricare la configurazione e inizializzare il bot
+ if os.path.exists(cls._config_file):
+ with open(cls._config_file, 'r') as f:
+ config = json.load(f)
+ cls._instance = cls.init_bot(config['token'], config['authorized_user_id'])
+ else:
+ raise Exception("Bot non ancora inizializzato. Chiamare prima init_bot() con token e authorized_user_id")
+ return cls._instance
+
+ @classmethod
+ def init_bot(cls, token, authorized_user_id):
+ if cls._instance is None:
+ cls._instance = cls(token, authorized_user_id)
+ # Salva la configurazione
+ config = {
+ 'token': token,
+ 'authorized_user_id': authorized_user_id
+ }
+ with open(cls._config_file, 'w') as f:
+ json.dump(config, f)
+ return cls._instance
+
+ def __init__(self, token, authorized_user_id):
+
+ def monitor_scripts():
+ while True:
+ try:
+ with open("scripts.json", "r") as f:
+ scripts_data = json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError):
+ scripts_data = []
+
+ current_time = time.time()
+
+ # Crea una nuova lista senza gli script che sono scaduti
+ scripts_data_to_save = []
+
+ for script in scripts_data:
+ if "titolo" not in script and script["status"] == "running" and (current_time - script["start_time"]) > 600:
+ # Prova a terminare la sessione screen
+ try:
+ subprocess.check_output(["screen", "-S", script["screen_id"], "-X", "quit"])
+ print(f"✅ La sessione screen con ID {script['screen_id']} è stata fermata automaticamente.")
+ except subprocess.CalledProcessError:
+ print(f"⚠️ Impossibile fermare la sessione screen con ID {script['screen_id']}.")
+
+ # Aggiungi solo gli script che non sono scaduti
+ print(f"⚠️ Lo script con ID {script['screen_id']} ha superato i 10 minuti e verrà rimosso.")
+ else:
+ scripts_data_to_save.append(script)
+
+ # Salva la lista aggiornata, senza gli script scaduti
+ with open("scripts.json", "w") as f:
+ json.dump(scripts_data_to_save, f, indent=4)
+
+ time.sleep(60) # Controlla ogni minuto
+
+ # Avvia il thread di monitoraggio
+ monitor_thread = threading.Thread(target=monitor_scripts, daemon=True)
+ monitor_thread.start()
+
+
+ if TelegramBot._instance is not None:
+ raise Exception("Questa classe è un singleton! Usa get_instance() per ottenere l'istanza.")
+
+ self.token = token
+ self.authorized_user_id = authorized_user_id
+ self.chat_id = authorized_user_id
+ self.bot = telebot.TeleBot(token)
+ self.request_manager = RequestManager()
+
+ # Registra gli handler
+ self.register_handlers()
+
+ def register_handlers(self):
+
+ """ @self.bot.message_handler(commands=['start'])
+ def start(message):
+ self.handle_start(message) """
+
+ @self.bot.message_handler(commands=['get_id'])
+ def get_id(message):
+ self.handle_get_id(message)
+
+ @self.bot.message_handler(commands=['start'])
+ def start_script(message):
+ self.handle_start_script(message)
+
+ @self.bot.message_handler(commands=['list'])
+ def list_scripts(message):
+ self.handle_list_scripts(message)
+
+ @self.bot.message_handler(commands=['stop'])
+ def stop_script(message):
+ self.handle_stop_script(message)
+
+ @self.bot.message_handler(commands=['screen'])
+ def screen_status(message):
+ self.handle_screen_status(message)
+
+ """ @self.bot.message_handler(commands=['replay'])
+ def send_welcome(message):
+ # Crea una tastiera personalizzata
+ markup = types.ReplyKeyboardMarkup(row_width=2)
+ itembtn1 = types.KeyboardButton('Start')
+ itembtn2 = types.KeyboardButton('Lista')
+ markup.add(itembtn1, itembtn2)
+
+ # Invia un messaggio con la tastiera
+ self.bot.send_message(message.chat.id, "Scegli un'opzione:", reply_markup=markup) """
+
+ """@self.bot.message_handler(commands=['inline'])
+ def send_welcome(message):
+ # Crea una tastiera inline
+ markup = types.InlineKeyboardMarkup()
+ itembtn1 = types.InlineKeyboardButton('Azione 1', callback_data='action1')
+ itembtn2 = types.InlineKeyboardButton('Azione 2', callback_data='action2')
+ itembtn3 = types.InlineKeyboardButton('Azione 3', callback_data='action3')
+ markup.add(itembtn1, itembtn2, itembtn3)
+
+ # Invia un messaggio con la tastiera inline
+ self.bot.send_message(message.chat.id, "Scegli un'opzione:", reply_markup=markup)
+
+ # Gestisce le callback delle tastiere inline
+ @self.bot.callback_query_handler(func=lambda call: True)
+ def handle_callback(call):
+ if call.data == 'action1':
+ self.bot.answer_callback_query(call.id, "Hai scelto Azione 1!")
+ self.bot.send_message(call.message.chat.id, "Hai eseguito Azione 1!")
+ elif call.data == 'action2':
+ self.bot.answer_callback_query(call.id, "Hai scelto Azione 2!")
+ self.bot.send_message(call.message.chat.id, "Hai eseguito Azione 2!")
+ elif call.data == 'action3':
+ self.bot.answer_callback_query(call.id, "Hai scelto Azione 3!")
+ self.bot.send_message(call.message.chat.id, "Hai eseguito Azione 3!")
+ """
+
+ @self.bot.message_handler(func=lambda message: True)
+ def handle_all_messages(message):
+ self.handle_response(message)
+
+ def is_authorized(self, user_id):
+ return user_id == self.authorized_user_id
+
+ def handle_get_id(self, message):
+ if not self.is_authorized(message.from_user.id):
+ self.bot.send_message(message.chat.id, "❌ Non sei autorizzato.")
+ return
+
+ self.bot.send_message(message.chat.id, f"Il tuo ID utente è: `{message.from_user.id}`", parse_mode="Markdown")
+
+ def handle_start_script(self, message):
+ if not self.is_authorized(message.from_user.id):
+ self.bot.send_message(message.chat.id, "❌ Non sei autorizzato.")
+ return
+
+ screen_id = str(uuid.uuid4())[:8]
+ #screen_id = '0000'
+
+ # Impostare a True per avviare il test_run.py in modalità verbose per il debug
+
+ debug_mode = os.getenv("DEBUG")
+ verbose = debug_mode
+
+ if debug_mode == "True":
+ subprocess.Popen(["python3", "test_run.py", screen_id])
+ else:
+ # Verifica se lo screen con il nome esiste già
+ try:
+ subprocess.check_output(["screen", "-list"])
+ existing_screens = subprocess.check_output(["screen", "-list"]).decode('utf-8')
+ if screen_id in existing_screens:
+ self.bot.send_message(message.chat.id, f"⚠️ Lo script con ID {screen_id} è già in esecuzione.")
+ return
+ except subprocess.CalledProcessError:
+ pass # Se il comando fallisce, significa che non ci sono screen attivi.
+
+ # Crea la sessione screen e avvia lo script al suo interno
+ command = ["screen", "-dmS", screen_id, "python3", "test_run.py", screen_id]
+
+ # Avvia il comando tramite subprocess
+ subprocess.Popen(command)
+
+ # Creazione oggetto script info
+ script_info = {
+ "screen_id": screen_id,
+ "start_time": time.time(),
+ "status": "running",
+ "user_id": message.from_user.id
+ }
+
+ # Salvataggio nel file JSON
+ json_file = "scripts.json"
+
+ # Carica i dati esistenti o crea una nuova lista
+ try:
+ with open(json_file, 'r') as f:
+ scripts_data = json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError):
+ scripts_data = []
+
+ # Aggiungi il nuovo script
+ scripts_data.append(script_info)
+
+ # Scrivi il file aggiornato
+ with open(json_file, 'w') as f:
+ json.dump(scripts_data, f, indent=4)
+
+ def handle_list_scripts(self, message):
+ if not self.is_authorized(message.from_user.id):
+ self.bot.send_message(message.chat.id, "❌ Non sei autorizzato.")
+ return
+
+ try:
+ with open("scripts.json", "r") as f:
+ scripts_data = json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError):
+ scripts_data = []
+
+ if not scripts_data:
+ self.bot.send_message(message.chat.id, "⚠️ Nessuno script registrato.")
+ return
+
+ current_time = time.time()
+ msg = ["🖥️ **Script Registrati:**\n"]
+
+ for script in scripts_data:
+ # Calcola la durata
+ duration = current_time - script["start_time"]
+ if "end_time" in script:
+ duration = script["end_time"] - script["start_time"]
+
+ # Formatta la durata
+ hours, rem = divmod(duration, 3600)
+ minutes, seconds = divmod(rem, 60)
+ duration_str = f"{int(hours)}h {int(minutes)}m {int(seconds)}s"
+
+ # Icona stato
+ status_icons = {
+ "running": "🟢",
+ "stopped": "🔴",
+ "completed": "⚪"
+ }
+
+ # Costruisci riga
+ line = (
+ f"• ID: `{script['screen_id']}`\n"
+ f"• Stato: {status_icons.get(script['status'], '⚫')}\n"
+ f"• Stop: `/stop {script['screen_id']}`\n"
+ f"• Screen: `/screen {script['screen_id']}`\n"
+ f"• Durata: {duration_str}\n"
+ f"• Download:\n{script.get('titolo', 'N/A')}\n"
+ )
+ msg.append(line)
+
+ # Formatta la risposta finale
+ final_msg = "\n".join(msg)
+ if len(final_msg) > 4000:
+ final_msg = final_msg[:4000] + "\n[...] (messaggio troncato)"
+
+ self.bot.send_message(message.chat.id, final_msg, parse_mode="Markdown")
+
+ def handle_stop_script(self, message):
+ if not self.is_authorized(message.from_user.id):
+ self.bot.send_message(message.chat.id, "❌ Non sei autorizzato.")
+ return
+
+ parts = message.text.split()
+ if len(parts) < 2:
+ try:
+ with open("scripts.json", "r") as f:
+ scripts_data = json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError):
+ scripts_data = []
+
+ running_scripts = [s for s in scripts_data if s["status"] == "running"]
+
+ if not running_scripts:
+ self.bot.send_message(message.chat.id, "⚠️ Nessuno script attivo da fermare.")
+ return
+
+ msg = "🖥️ **Script Attivi:**\n"
+ for script in running_scripts:
+ msg += f"🔹 `/stop {script['screen_id']}` per fermarlo\n"
+
+ self.bot.send_message(message.chat.id, msg, parse_mode="Markdown")
+
+ elif len(parts) == 2:
+ screen_id = parts[1]
+
+ try:
+ with open("scripts.json", "r") as f:
+ scripts_data = json.load(f)
+ except (FileNotFoundError, json.JSONDecodeError):
+ scripts_data = []
+
+ # Filtra la lista eliminando lo script con l'ID specificato
+ new_scripts_data = [script for script in scripts_data if script["screen_id"] != screen_id]
+
+ if len(new_scripts_data) == len(scripts_data):
+ # Nessun elemento rimosso, quindi ID non trovato
+ self.bot.send_message(message.chat.id, f"⚠️ Nessuno script attivo con ID `{screen_id}`.", parse_mode="Markdown")
+ return
+
+ # Terminare la sessione screen
+ try:
+ subprocess.check_output(["screen", "-S", screen_id, "-X", "quit"])
+ print(f"✅ La sessione screen con ID {screen_id} è stata fermata.")
+ except subprocess.CalledProcessError:
+ self.bot.send_message(message.chat.id, f"⚠️ Impossibile fermare la sessione screen con ID `{screen_id}`.", parse_mode="Markdown")
+ return
+
+ # Salva la lista aggiornata senza lo script eliminato
+ with open("scripts.json", "w") as f:
+ json.dump(new_scripts_data, f, indent=4)
+
+ self.bot.send_message(message.chat.id, f"✅ Script `{screen_id}` terminato con successo!", parse_mode="Markdown")
+
+ def handle_response(self, message):
+ """ if message.text == 'Start':
+ self.handle_start_script(self, message)
+ elif message.text == 'Lista':
+ self.handle_list_scripts(self, message)
+ elif message.text == 'Azione 3':
+ self.bot.send_message(message.chat.id, "Hai scelto Azione 3!")
+ else:
+ self.bot.send_message(message.chat.id, "Comando non riconosciuto.")
+
+ if not self.is_authorized(message.from_user.id):
+ self.bot.reply_to(message, "❌ Non sei autorizzato.")
+ return """
+
+ text = message.text
+ if self.request_manager.save_response(text):
+ print(f"📥 Risposta salvata correttamente per il tipo {text}")
+ else:
+ print("⚠️ Nessuna richiesta attiva.")
+ self.bot.reply_to(message, "❌ Nessuna richiesta attiva.")
+
+ def handle_screen_status(self, message):
+ command_parts = message.text.split()
+ if len(command_parts) < 2:
+ self.bot.send_message(message.chat.id, "⚠️ ID mancante nel comando. Usa: /screen ")
+ return
+
+ screen_id = command_parts[1]
+ temp_file = f"/tmp/screen_output_{screen_id}.txt"
+
+ try:
+ # Cattura l'output della screen
+ subprocess.run(["screen", "-X", "-S", screen_id, "hardcopy", "-h", temp_file], check=True)
+ except subprocess.CalledProcessError as e:
+ self.bot.send_message(message.chat.id, f"❌ Errore durante la cattura dell'output della screen: {e}")
+ return
+
+ if not os.path.exists(temp_file):
+ self.bot.send_message(message.chat.id, f"❌ Impossibile catturare l'output della screen.")
+ return
+
+ try:
+ # Leggi il file con la codifica corretta
+ with open(temp_file, 'r', encoding='latin-1') as file:
+ screen_output = file.read()
+
+ # Pulisci l'output
+ cleaned_output = re.sub(r'[\x00-\x1F\x7F]', '', screen_output) # Rimuovi caratteri di controllo
+ cleaned_output = cleaned_output.replace('\n\n', '\n') # Rimuovi newline multipli
+
+ # Estrarre tutte le parti da "Download:" fino a "Video" o "Subtitle", senza includerli
+ download_matches = re.findall(r"Download: (.*?)(?:Video|Subtitle)", cleaned_output)
+ if download_matches:
+ # Serie TV e Film StreamingCommunity
+
+ proc_matches = re.findall(r"Proc: ([\d\.]+%)", cleaned_output)
+
+ # Creare una stringa unica con tutti i risultati
+ result_string = "\n".join([f"Download: {download_matches[i].strip()}\nDownload al {proc_matches[i]}" for i in range(len(download_matches)) if i < len(proc_matches)])
+
+ if result_string != "":
+ cleaned_output = result_string
+ else:
+ print(f"❌ La parola 'Download:' non è stata trovata nella stringa.")
+ else:
+
+ download_list = []
+
+ # Estrai tutte le righe che iniziano con "Download:" fino al prossimo "Download" o alla fine della riga
+ matches = re.findall(r"Download:\s*(.*?)(?=Download|$)", cleaned_output)
+
+ # Se sono stati trovati download, stampali
+ if matches:
+ for i, match in enumerate(matches, 1):
+ # rimuovo solo la parte "downloader.py:57Result:400" se esiste
+ match = re.sub(r"downloader.py:\d+Result:400", "", match)
+ match = match.strip() # Rimuovo gli spazi bianchi in eccesso
+ if match: # Assicurati che la stringa non sia vuota
+ print(f"Download {i}: {match}")
+
+ # Aggiungi il risultato modificato alla lista
+ download_list.append(f"Download {i}: {match}")
+
+ # Creare una stringa unica con tutti i risultati
+ cleaned_output = "\n".join(download_list)
+ else:
+ print("❌ Nessun download trovato")
+
+ # Invia l'output pulito
+ self._send_long_message(message.chat.id, f"📄 Output della screen {screen_id}:\n{cleaned_output}")
+
+ except Exception as e:
+ self.bot.send_message(message.chat.id, f"❌ Errore durante la lettura o l'invio dell'output della screen: {e}")
+
+ # Cancella il file temporaneo
+ os.remove(temp_file)
+
+ def send_message(self, message, choices):
+ if choices is None:
+ if self.chat_id:
+ self.bot.send_message(self.chat_id, message)
+ else:
+ formatted_choices = "\n".join(choices)
+ message = f"{message}\n\n{formatted_choices}"
+ if self.chat_id:
+ self.bot.send_message(self.chat_id, message)
+
+ def _send_long_message(self, chat_id, text, chunk_size=4096):
+ """Suddivide e invia un messaggio troppo lungo in più parti."""
+ for i in range(0, len(text), chunk_size):
+ self.bot.send_message(chat_id, text[i:i+chunk_size])
+
+ def ask(self, type, prompt_message, choices, timeout=60):
+ self.request_manager.create_request(type)
+
+ if choices is None:
+ self.bot.send_message(
+ self.chat_id,
+ f"{prompt_message}",
+ )
+ else:
+ self.bot.send_message(
+ self.chat_id,
+ f"{prompt_message}\n\nOpzioni: {', '.join(choices)}",
+ )
+
+ start_time = time.time()
+ while time.time() - start_time < timeout:
+ response = self.request_manager.get_response()
+ if response is not None:
+ return response
+ time.sleep(1)
+
+ self.bot.send_message(self.chat_id, "⚠️ Timeout: nessuna risposta ricevuta.")
+ self.request_manager.clear_file()
+ return None
+
+ def run(self):
+ print("🚀 Avvio del bot...")
+ # svuoto il file scripts.json
+ with open("scripts.json", "w") as f:
+ json.dump([], f)
+ self.bot.infinity_polling()
+
+def get_bot_instance():
+ return TelegramBot.get_instance()
+
+# Esempio di utilizzo
+if __name__ == "__main__":
+
+ # Usa le variabili
+ token = os.getenv("TOKEN_TELEGRAM")
+ authorized_user_id = os.getenv("AUTHORIZED_USER_ID")
+
+ TOKEN = token # Inserisci il token del tuo bot Telegram sul file .env
+ AUTHORIZED_USER_ID = int(authorized_user_id) # Inserisci il tuo ID utente Telegram sul file .env
+
+ # Inizializza il bot
+ bot = TelegramBot.init_bot(TOKEN, AUTHORIZED_USER_ID)
+ bot.run()
+
+"""
+start - Avvia lo script
+list - Lista script attivi
+get - Mostra ID utente Telegram
+"""
diff --git a/test_run.py b/test_run.py
index 74806a1..73ea787 100644
--- a/test_run.py
+++ b/test_run.py
@@ -1,5 +1,15 @@
# 26.11.24
+import sys
from StreamingCommunity.run import main
+from request_manager import RequestManager
+from session import set_session
-main()
\ No newline at end of file
+# Svuoto il file
+request_manager = RequestManager()
+request_manager.clear_file()
+script_id = sys.argv[1] if len(sys.argv) > 1 else "unknown"
+
+set_session(script_id)
+
+main(script_id)
\ No newline at end of file