diff --git a/README.md b/README.md index f584f07..867bb5f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ - 💬 [Support](#support) - 🤝 [Contribute](#contributing) - ⚠️ [Disclaimer](#disclaimer) -- ⚡ [Contributors](#contributors) +- ⚡ [Contributors](#contributors) # Installation @@ -177,7 +177,7 @@ For detailed Windows PATH instructions, see the [Windows PATH guide](https://www ```bash # For bash (edit ~/.bash_profile) export PATH="/your/custom/path:$PATH" - + # For zsh (edit ~/.zshrc) export PATH="/your/custom/path:$PATH" ``` @@ -185,7 +185,7 @@ For detailed Windows PATH instructions, see the [Windows PATH guide](https://www ```bash # For bash source ~/.bash_profile - + # For zsh source ~/.zshrc ``` @@ -196,7 +196,7 @@ For detailed Windows PATH instructions, see the [Windows PATH guide](https://www ```bash # For bash (edit ~/.bashrc) export PATH="/your/custom/path:$PATH" - + # For zsh (edit ~/.zshrc) export PATH="/your/custom/path:$PATH" ``` @@ -279,7 +279,7 @@ The configuration file is divided into several main sections: ``` - `root_path`: Directory where all videos will be saved - + ### Path examples: * Windows: `C:\\MyLibrary\\Folder` or `\\\\MyServer\\MyLibrary` (if you want to use a network folder) * Linux/MacOS: `Desktop/MyLibrary/Folder` @@ -305,7 +305,7 @@ The configuration file is divided into several main sections: `

` * Can be changed from terminal with `--map_episode_name`

- + - `add_siteName`: If set to true, appends the site_name to the root path before the movie and serie folders. * Can be changed from terminal with `--add_siteName true/false`

@@ -501,15 +501,31 @@ The `run-container` command mounts also the `config.json` file, so any change to ## Configuration -You need to create an .env file and enter your Telegram token +The bot was created to replace terminal commands and allow interaction via Telegram. Each download runs within a screen session, enabling multiple downloads to run simultaneously. -and user ID to authorize only one user to use it +To run the bot in the background, simply start it inside a screen session and then press Ctrl + A, followed by D, to detach from the session without stopping the bot. + +Command Functions: + +🔹 /start – Starts a new search for a download. This command performs the same operations as manually running the script in the terminal with test_run.py. + +🔹 /list – Displays the status of active downloads, with options to: + +Stop an incorrect download using /stop . + +View the real-time output of a download using /screen . + +⚠ Warning: If a download is interrupted, incomplete files may remain in the folder specified in config.json. These files must be deleted manually to avoid storage or management issues. + +🛠 Configuration: Currently, the bot's settings are stored in the config.json file, which is located in the same directory as the telegram_bot.py script. ## .env Example: +You need to create an .env file and enter your Telegram token and user ID to authorize only one user to use it + ``` TOKEN_TELEGRAM=IlTuo2131TOKEN$12D3Telegram -AUTHORIZED_USER_ID=12345678 +AUTHORIZED_USER_ID=12345678 DEBUG=False ``` @@ -521,11 +537,13 @@ pip install -r requirements.txt ## On Linux/MacOS: +Start the bot from the folder /StreamingCommunity/TelegramHelp + ```bash python3 telegram_bot.py ``` -# Website Status +# Website Status | Website | Status | Command | |:-------------------|:------:|:--------:| @@ -540,14 +558,14 @@ python3 telegram_bot.py | [StreamingCommunity](https://streamingcommunity.paris/) | ✅ | -STR | -# Tutorials +# Tutorials - [Windows Tutorial](https://www.youtube.com/watch?v=mZGqK4wdN-k) - [Linux Tutorial](https://www.youtube.com/watch?v=0qUNXPE_mTg) - [Pypy Tutorial](https://www.youtube.com/watch?v=C6m9ZKOK0p4) - [Compiled .exe Tutorial](https://www.youtube.com/watch?v=pm4lqsxkTVo) -# To Do +# To Do - To Finish [website API](https://github.com/Arrowar/StreamingCommunity/tree/test_gui_1) - To finish [website API 2](https://github.com/hydrosh/StreamingCommunity/tree/test_gui_1) diff --git a/StreamingCommunity/Api/Site/altadefinizionegratis/film.py b/StreamingCommunity/Api/Site/altadefinizionegratis/film.py index d7ff463..46c0831 100644 --- a/StreamingCommunity/Api/Site/altadefinizionegratis/film.py +++ b/StreamingCommunity/Api/Site/altadefinizionegratis/film.py @@ -8,9 +8,7 @@ from StreamingCommunity.Util.console import console from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message from StreamingCommunity.Lib.Downloader import HLS_Downloader -from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance -from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId - +from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance # Logic class from StreamingCommunity.Api.Template.Class.SearchType import MediaItem @@ -38,17 +36,17 @@ def download_film(select_title: MediaItem) -> str: if TELEGRAM_BOT: bot = get_bot_instance() bot.send_message(f"Download in corso:\n{select_title.name}", None) - + # Get script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - updateScriptId(script_id, select_title.name) + TelegramSession.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) @@ -61,17 +59,17 @@ def download_film(select_title: MediaItem) -> str: # Download the film using the m3u8 playlist, and output filename r_proc = HLS_Downloader( - m3u8_url=master_playlist, + m3u8_url=master_playlist, output_path=os.path.join(mp4_path, title_name) ).start() if TELEGRAM_BOT: - + # Delete script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - deleteScriptId(script_id) - + TelegramSession.deleteScriptId(script_id) + if "error" in r_proc.keys(): try: os.remove(r_proc['path']) diff --git a/StreamingCommunity/Api/Site/animeunity/film_serie.py b/StreamingCommunity/Api/Site/animeunity/film_serie.py index ec10d95..5c9eb68 100644 --- a/StreamingCommunity/Api/Site/animeunity/film_serie.py +++ b/StreamingCommunity/Api/Site/animeunity/film_serie.py @@ -10,8 +10,7 @@ from StreamingCommunity.Util.console import console, msg from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message from StreamingCommunity.Lib.Downloader import MP4_downloader -from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance -from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId +from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance # Logic class @@ -43,7 +42,7 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so """ if TELEGRAM_BOT: bot = get_bot_instance() - + # Get information about the selected episode obj_episode = scrape_serie.get_info_episode(index_select) @@ -52,14 +51,14 @@ 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: bot.send_message(f"Download in corso:\nTitolo:{scrape_serie.series_name}\nEpisodio: {obj_episode.number}", None) # Get script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - updateScriptId(script_id, f"{scrape_serie.series_name} - E{obj_episode.number}") + TelegramSession.updateScriptId(script_id, f"{scrape_serie.series_name} - E{obj_episode.number}") # Collect mp4 url video_source.get_embed(obj_episode.id) @@ -75,15 +74,15 @@ def download_episode(index_select: int, scrape_serie: ScrapeSerieAnime, video_so mp4_path = os_manager.get_sanitize_path(os.path.join(MOVIE_FOLDER, scrape_serie.series_name)) # Create output folder - os_manager.create_path(mp4_path) + os_manager.create_path(mp4_path) # Start downloading path, kill_handler = MP4_downloader( url=str(video_source.src_mp4).strip(), path=os.path.join(mp4_path, title_name) ) - - return path, kill_handler + + return path, kill_handler else: logging.error(f"Skip index: {index_select} cant find info with api.") @@ -98,7 +97,7 @@ def download_series(select_title: MediaItem): - tv_name (str): The name of the TV series. """ start_message() - + if TELEGRAM_BOT: bot = get_bot_instance() @@ -123,9 +122,9 @@ def download_series(select_title: MediaItem): ) 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") + 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) @@ -140,16 +139,16 @@ def download_series(select_title: MediaItem): kill_handler = False for i_episode in list_episode_select: if kill_handler: - break + break _, kill_handler = download_episode(i_episode-1, scrape_serie, video_source) - + if TELEGRAM_BOT: bot.send_message(f"Finito di scaricare tutte le serie e episodi", None) # Get script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - deleteScriptId(script_id) + TelegramSession.deleteScriptId(script_id) def download_film(select_title: MediaItem): diff --git a/StreamingCommunity/Api/Site/streamingcommunity/film.py b/StreamingCommunity/Api/Site/streamingcommunity/film.py index 790c3ea..7b6ab8c 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/film.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/film.py @@ -8,8 +8,7 @@ from StreamingCommunity.Util.console import console from StreamingCommunity.Util.os import os_manager from StreamingCommunity.Util.message import start_message from StreamingCommunity.Lib.Downloader import HLS_Downloader -from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance -from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId +from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance # Logic class @@ -39,13 +38,13 @@ def download_film(select_title: MediaItem) -> str: bot = get_bot_instance() bot.send_message(f"Download in corso:\n{select_title.name}", None) - # Viene usato per lo screen + # Viene usato per lo screen console.print(f"## Download: [red]{select_title.name} ##") - + # Get script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - updateScriptId(script_id, select_title.name) + TelegramSession.updateScriptId(script_id, select_title.name) # Start message and display film information start_message() @@ -66,16 +65,16 @@ def download_film(select_title: MediaItem) -> str: # Download the film using the m3u8 playlist, and output filename r_proc = HLS_Downloader( - m3u8_url=master_playlist, + m3u8_url=master_playlist, output_path=os.path.join(mp4_path, title_name) ).start() if TELEGRAM_BOT: - + # Delete script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - deleteScriptId(script_id) + TelegramSession.deleteScriptId(script_id) if "error" in r_proc.keys(): try: diff --git a/StreamingCommunity/Api/Site/streamingcommunity/series.py b/StreamingCommunity/Api/Site/streamingcommunity/series.py index 6db8994..487e9ef 100644 --- a/StreamingCommunity/Api/Site/streamingcommunity/series.py +++ b/StreamingCommunity/Api/Site/streamingcommunity/series.py @@ -10,9 +10,7 @@ from StreamingCommunity.Util.console import console, msg from StreamingCommunity.Util.message import start_message from StreamingCommunity.Util.table import TVShowManager from StreamingCommunity.Lib.Downloader import HLS_Downloader -from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance -from StreamingCommunity.TelegramHelp.session import get_session, updateScriptId, deleteScriptId - +from StreamingCommunity.TelegramHelp.telegram_bot import TelegramSession, get_bot_instance # Logic class from .util.ScrapeSerie import ScrapeSerie @@ -50,7 +48,7 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra 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}", @@ -58,9 +56,9 @@ def download_video(index_season_selected: int, index_episode_selected: int, scra ) # Get script_id and update it - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - updateScriptId(script_id, f"{scrape_serie.series_name} - S{index_season_selected} - E{index_episode_selected} - {obj_episode.name}") + TelegramSession.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" @@ -70,13 +68,13 @@ 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_url=master_playlist, output_path=os.path.join(mp4_path, mp4_name) ).start() - + if "error" in r_proc.keys(): try: os.remove(r_proc['path']) @@ -204,9 +202,9 @@ def download_series(select_season: MediaItem, version: str) -> None: bot.send_message(f"Finito di scaricare tutte le serie e episodi", None) # Get script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - deleteScriptId(script_id) + TelegramSession.deleteScriptId(script_id) def display_episodes_list(scrape_serie) -> str: diff --git a/StreamingCommunity/Lib/Downloader/HLS/downloader.py b/StreamingCommunity/Lib/Downloader/HLS/downloader.py index 3d6f738..38572c1 100644 --- a/StreamingCommunity/Lib/Downloader/HLS/downloader.py +++ b/StreamingCommunity/Lib/Downloader/HLS/downloader.py @@ -60,11 +60,11 @@ class HLSClient: def request(self, url: str, return_content: bool = False) -> Optional[httpx.Response]: """ Makes HTTP GET requests with retry logic. - + Args: url: Target URL to request return_content: If True, returns response content instead of text - + Returns: Response content/text or None if all retries fail """ @@ -74,7 +74,7 @@ class HLSClient: response = 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: {str(e)}") time.sleep(1.5 ** attempt) @@ -103,7 +103,7 @@ class PathManager: root = config_manager.get('DEFAULT', 'root_path') hash_name = compute_sha1_hash(self.m3u8_url) + ".mp4" return os.path.join(root, "undefined", hash_name) - + if not path.endswith(".mp4"): path += ".mp4" @@ -148,7 +148,7 @@ class M3U8Manager: content = self.client.request(self.m3u8_url) if not content: raise ValueError("Failed to fetch M3U8 content") - + self.parser.parse_data(uri=self.m3u8_url, raw_content=content) self.url_fixer.set_playlist(self.m3u8_url) self.is_master = self.parser.is_master_playlist @@ -162,7 +162,7 @@ class M3U8Manager: self.video_url, self.video_res = self.m3u8_url, "0p" self.audio_streams = [] self.sub_streams = [] - + else: if FILTER_CUSTOM_REOLUTION != -1: self.video_url, self.video_res = self.parser._video.get_custom_uri(y_resolution=FILTER_CUSTOM_REOLUTION) @@ -266,7 +266,7 @@ class DownloadManager: """Downloads audio segments for a specific language track.""" if self.stopped: return True - + audio_full_url = self.url_fixer.generate_full_url(audio['uri']) audio_tmp_dir = os.path.join(self.temp_dir, 'audio', audio['language']) @@ -282,7 +282,7 @@ class DownloadManager: """Downloads and saves subtitle file for a specific language.""" if self.stopped: return True - + content = self.client.request(sub['uri']) if content: sub_path = os.path.join(self.temp_dir, 'subs', f"{sub['language']}.vtt") @@ -299,7 +299,7 @@ class DownloadManager: if not os.path.exists(video_file): if self.download_video(video_url): return True - + for audio in audio_streams: if self.stopped: break @@ -317,7 +317,7 @@ class DownloadManager: if not os.path.exists(sub_file): if self.download_subtitle(sub): return True - + return self.stopped @@ -340,7 +340,7 @@ class MergeManager: """ Merges downloaded streams into final video file. Returns path to the final merged file. - + Process: 1. If no audio/subs, just process video 2. If audio exists, merge with video @@ -383,7 +383,7 @@ class MergeManager: subtitles_list=sub_tracks, out_path=merged_subs_path ) - + return merged_file @@ -400,14 +400,14 @@ class HLS_Downloader: def start(self) -> Dict[str, Any]: """ Main execution flow with handling for both index and playlist M3U8s. - + Returns: Dict containing: - path: Output file path - url: Original M3U8 URL - is_master: Whether the M3U8 was a master playlist Or raises an exception if there's an error - """ + """ if TELEGRAM_BOT: bot = get_bot_instance() @@ -417,12 +417,12 @@ class HLS_Downloader: response = { 'path': self.path_manager.output_path, 'url': self.m3u8_url, - 'is_master': False, + 'is_master': False, 'error': 'File already exists', 'stopped': False } if TELEGRAM_BOT: - bot.send_message(response) + bot.send_message(f"Contenuto già scaricato!", None) return response self.path_manager.setup_directories() @@ -437,7 +437,7 @@ class HLS_Downloader: client=self.client, url_fixer=self.m3u8_manager.url_fixer ) - + # Check if download was stopped download_stopped = self.download_manager.download_all( video_url=self.m3u8_manager.video_url, @@ -478,7 +478,7 @@ class HLS_Downloader: error_msg = str(e) console.print(f"[red]Download failed: {error_msg}[/red]") logging.error("Download error", exc_info=True) - + return { 'path': None, 'url': self.m3u8_url, @@ -486,7 +486,7 @@ class HLS_Downloader: 'error': error_msg, 'stopped': False } - + def _print_summary(self): """Prints download summary including file size, duration, and any missing segments.""" if TELEGRAM_BOT: diff --git a/StreamingCommunity/TelegramHelp/request_manager.py b/StreamingCommunity/TelegramHelp/request_manager.py deleted file mode 100644 index 51dd5f3..0000000 --- a/StreamingCommunity/TelegramHelp/request_manager.py +++ /dev/null @@ -1,82 +0,0 @@ -# 04.02.25 -# Made by: @GiuPic - -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 - - def create_request(self, type: str) -> str: - request_data = { - "type": type, - "response": None, - "timestamp": time.time() - } - - 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 - - with open(self.json_file, "w") as f: - json.dump(data, f, indent=4) - - 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: - 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: - 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/StreamingCommunity/TelegramHelp/session.py b/StreamingCommunity/TelegramHelp/session.py deleted file mode 100644 index 7a26034..0000000 --- a/StreamingCommunity/TelegramHelp/session.py +++ /dev/null @@ -1,56 +0,0 @@ -# 04.02.25 -# Made by: @GiuPic - -import json - -session_data = {} - -def set_session(value): - session_data['script_id'] = value - -def get_session(): - return session_data.get('script_id', 'unknown') - -def updateScriptId(screen_id, titolo): - json_file = "scripts.json" - try: - with open(json_file, 'r') as f: - scripts_data = json.load(f) - except FileNotFoundError: - 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) - - return - - print(f"Screen_id {screen_id} non trovato.") - -def deleteScriptId(screen_id): - json_file = "scripts.json" - try: - with open(json_file, 'r') as f: - scripts_data = json.load(f) - except FileNotFoundError: - scripts_data = [] - - 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 - - print(f"Screen_id {screen_id} non trovato.") \ No newline at end of file diff --git a/StreamingCommunity/TelegramHelp/telegram_bot.py b/StreamingCommunity/TelegramHelp/telegram_bot.py index 6b09cac..c3b89ee 100644 --- a/StreamingCommunity/TelegramHelp/telegram_bot.py +++ b/StreamingCommunity/TelegramHelp/telegram_bot.py @@ -1,4 +1,4 @@ -# 04.02.25 +# 04.02.26 # Made by: @GiuPic import os @@ -9,16 +9,141 @@ import uuid import json import threading import subprocess - +import threading +from typing import Optional # External libraries import telebot +session_data = {} -# Fix import -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))) -from StreamingCommunity.TelegramHelp.request_manager import RequestManager +class TelegramSession: + def set_session(value): + session_data['script_id'] = value + + def get_session(): + return session_data.get('script_id', 'unknown') + + def updateScriptId(screen_id, titolo): + json_file = "../../scripts.json" + try: + with open(json_file, 'r') as f: + scripts_data = json.load(f) + except FileNotFoundError: + 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) + + return + + print(f"Screen_id {screen_id} non trovato.") + + def deleteScriptId(screen_id): + json_file = "../../scripts.json" + try: + with open(json_file, 'r') as f: + scripts_data = json.load(f) + except FileNotFoundError: + scripts_data = [] + + 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 + + print(f"Screen_id {screen_id} non trovato.") + +class TelegramRequestManager: + _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 + + def create_request(self, type: str) -> str: + request_data = { + "type": type, + "response": None, + "timestamp": time.time() + } + + 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 + + with open(self.json_file, "w") as f: + json.dump(data, f, indent=4) + + 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: + 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: + 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 # Funzione per caricare variabili da un file .env def load_env(file_path="../../.env"): @@ -44,13 +169,17 @@ class TelegramBot: 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"] - ) + + # Assicura che authorized_user_id venga trattato come una lista + authorized_users = config.get('authorized_user_id', []) + if isinstance(authorized_users, str): + authorized_users = [int(uid) for uid in authorized_users.split(",") if uid.strip().isdigit()] + + cls._instance = cls.init_bot(config['token'], authorized_users) + #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" - ) + raise Exception("Bot non ancora inizializzato. Chiamare prima init_bot() con token e authorized_user_id") return cls._instance @classmethod @@ -63,7 +192,8 @@ class TelegramBot: json.dump(config, f) return cls._instance - def __init__(self, token, authorized_user_id): + def __init__(self, token, authorized_users): + def monitor_scripts(): while True: try: @@ -132,10 +262,10 @@ class TelegramBot: ) self.token = token - self.authorized_user_id = authorized_user_id - self.chat_id = authorized_user_id + self.authorized_users = authorized_users + self.chat_id = authorized_users self.bot = telebot.TeleBot(token) - self.request_manager = RequestManager() + self.request_manager = TelegramRequestManager() # Registra gli handler self.register_handlers() @@ -171,7 +301,7 @@ class TelegramBot: self.handle_response(message) def is_authorized(self, user_id): - return user_id == self.authorized_user_id + return user_id in self.authorized_users def handle_get_id(self, message): if not self.is_authorized(message.from_user.id): @@ -194,7 +324,6 @@ class TelegramBot: screen_id = str(uuid.uuid4())[:8] debug_mode = os.getenv("DEBUG") - verbose = debug_mode if debug_mode == "True": subprocess.Popen(["python3", "../../test_run.py", screen_id]) @@ -407,6 +536,13 @@ class TelegramBot: temp_file = f"/tmp/screen_output_{screen_id}.txt" try: + # Verifica se lo screen con l'ID specificato esiste + existing_screens = subprocess.check_output(["screen", "-list"]).decode('utf-8') + if screen_id not in existing_screens: + print(f"⚠️ La sessione screen con ID {screen_id} non esiste.") + self.bot.send_message(message.chat.id, f"⚠️ La sessione screen con ID {screen_id} non esiste.") + return + # Cattura l'output della screen subprocess.run( ["screen", "-X", "-S", screen_id, "hardcopy", "-h", temp_file], @@ -440,51 +576,22 @@ class TelegramBot: "\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 + # Dentro cleaned_output c'è una stringa recupero quello che si trova tra ## ## + download_section = re.search(r"##(.*?)##", cleaned_output, re.DOTALL) + if download_section: + cleaned_output_0 = "Download: " + download_section.group(1).strip() - proc_matches = re.findall(r"Proc: ([\d\.]+%)", cleaned_output) + # Recupero tutto quello che viene dopo con #### + download_section_bottom = re.search(r"####(.*)", cleaned_output, re.DOTALL) + if download_section_bottom: + cleaned_output_1 = download_section_bottom.group(1).strip() - # 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") + # Unico i due risultati se esistono + if cleaned_output_0 and cleaned_output_1: + cleaned_output = f"{cleaned_output_0}\n{cleaned_output_1}" + # Rimuovo 'segments.py:302' e 'downloader.py:385' se presente + cleaned_output = re.sub(r'downloader\.py:\d+', '', cleaned_output) + cleaned_output = re.sub(r'segments\.py:\d+', '', cleaned_output) # Invia l'output pulito print(f"📄 Output della screen {screen_id}:\n{cleaned_output}") @@ -505,7 +612,16 @@ class TelegramBot: os.remove(temp_file) def send_message(self, message, choices): - if choices is None: + + formatted_message = message + if choices: + formatted_choices = "\n".join(choices) + formatted_message = f"{message}\n\n{formatted_choices}" + + for chat_id in self.authorized_users: + self.bot.send_message(chat_id, formatted_message) + + """ if choices is None: if self.chat_id: print(f"{message}") self.bot.send_message(self.chat_id, message) @@ -514,7 +630,7 @@ class TelegramBot: message = f"{message}\n\n{formatted_choices}" if self.chat_id: print(f"{message}") - self.bot.send_message(self.chat_id, message) + 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.""" @@ -527,16 +643,20 @@ class TelegramBot: if choices is None: print(f"{prompt_message}") - self.bot.send_message( + """ self.bot.send_message( self.chat_id, f"{prompt_message}", - ) + ) """ + for chat_id in self.authorized_users: # Manda a tutti gli ID autorizzati + self.bot.send_message(chat_id, f"{prompt_message}") else: print(f"{prompt_message}\n\nOpzioni: {', '.join(choices)}") - self.bot.send_message( + """ self.bot.send_message( self.chat_id, f"{prompt_message}\n\nOpzioni: {', '.join(choices)}", - ) + ) """ + for chat_id in self.authorized_users: # Manda a tutti gli ID autorizzati + self.bot.send_message(chat_id, f"{prompt_message}\n\nOpzioni: {', '.join(choices)}") start_time = time.time() while time.time() - start_time < timeout: @@ -546,7 +666,8 @@ class TelegramBot: time.sleep(1) print(f"⚠️ Timeout: nessuna risposta ricevuta.") - self.bot.send_message(self.chat_id, "⚠️ Timeout: nessuna risposta ricevuta.") + for chat_id in self.authorized_users: # Manda a tutti gli ID autorizzati + self.bot.send_message(chat_id, "⚠️ Timeout: nessuna risposta ricevuta.") self.request_manager.clear_file() return None @@ -558,4 +679,37 @@ class TelegramBot: def get_bot_instance(): - return TelegramBot.get_instance() \ No newline at end of file + return TelegramBot.get_instance() + +# Esempio di utilizzo +if __name__ == "__main__": + + # Usa le variabili + token = os.getenv("TOKEN_TELEGRAM") + authorized_users = os.getenv("AUTHORIZED_USER_ID") + + # Controlla se le variabili sono presenti + if not token: + print("Errore: TOKEN_TELEGRAM non è definito nel file .env.") + sys.exit(1) + + if not authorized_users: + print("Errore: AUTHORIZED_USER_ID non è definito nel file .env.") + sys.exit(1) + + try: + TOKEN = token # Inserisci il token del tuo bot Telegram sul file .env + AUTHORIZED_USER_ID = list(map(int, authorized_users.split(","))) # Inserisci il tuo ID utente Telegram sul file .env + except ValueError as e: + print(f"Errore nella conversione degli ID autorizzati: {e}. Controlla il file .env e assicurati che gli ID siano numeri interi separati da virgole.") + sys.exit(1) + + # 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/StreamingCommunity/Util/_jsonConfig.py b/StreamingCommunity/Util/_jsonConfig.py index 3c856cb..91b1923 100644 --- a/StreamingCommunity/Util/_jsonConfig.py +++ b/StreamingCommunity/Util/_jsonConfig.py @@ -4,17 +4,18 @@ import os import sys import json import logging +from pathlib import Path from typing import Any, List class ConfigManager: - def __init__(self, file_path: str = 'config.json') -> None: + def __init__(self, file_name: str = 'config.json') -> None: """Initialize the ConfigManager. Parameters: - file_path (str, optional): The path to the configuration file. Default is 'config.json'. """ - self.file_path = file_path + self.file_path = Path(__file__).parent.parent.parent / file_name self.config = {} self.cache = {} @@ -50,17 +51,17 @@ class ConfigManager: def download_requirements(self, url: str, filename: str): """ Download the requirements.txt file from the specified URL if not found locally using requests. - + Args: url (str): The URL to download the requirements file from. filename (str): The local filename to save the requirements file as. """ try: import requests - + logging.info(f"{filename} not found locally. Downloading from {url}...") response = requests.get(url) - + if response.status_code == 200: with open(filename, 'wb') as f: f.write(response.content) @@ -68,7 +69,7 @@ class ConfigManager: else: logging.error(f"Failed to download {filename}. HTTP Status code: {response.status_code}") sys.exit(0) - + except Exception as e: logging.error(f"Failed to download {filename}: {e}") sys.exit(0) @@ -89,15 +90,15 @@ class ConfigManager: if cache_key in self.cache: return self.cache[cache_key] - + if section in self.config and key in self.config[section]: value = self.config[section][key] else: raise ValueError(f"Key '{key}' not found in section '{section}'") - + value = self._convert_to_data_type(value, data_type) self.cache[cache_key] = value - + return value def _convert_to_data_type(self, value: str, data_type: type) -> Any: @@ -120,7 +121,7 @@ class ConfigManager: return None else: return value - + def get(self, section: str, key: str) -> Any: """Read a value from the configuration file. @@ -144,7 +145,7 @@ class ConfigManager: int: The integer value. """ return self.read_key(section, key, int) - + def get_float(self, section: str, key: str) -> int: """Read an float value from the configuration file. @@ -180,7 +181,7 @@ class ConfigManager: list: The list value. """ return self.read_key(section, key, list) - + def get_dict(self, section: str, key: str) -> dict: """Read a dictionary value from the configuration file. @@ -220,7 +221,7 @@ class ConfigManager: json.dump(self.config, f, indent=4) except Exception as e: print(f"Error writing configuration file: {e}") - + # Initialize config_manager = ConfigManager() diff --git a/StreamingCommunity/Util/os.py b/StreamingCommunity/Util/os.py index 08bb289..37b5b12 100644 --- a/StreamingCommunity/Util/os.py +++ b/StreamingCommunity/Util/os.py @@ -13,6 +13,7 @@ import subprocess import contextlib import urllib.request import importlib.metadata +from pathlib import Path # External library @@ -67,7 +68,7 @@ class OsManager: # Convert Windows separators to Unix normalized = path.replace('\\', '/') - + # Ensure absolute paths start with / if normalized.startswith('/'): return os.path.normpath(normalized) @@ -82,17 +83,17 @@ class OsManager: # Decode and sanitize decoded = unidecode(filename) sanitized = sanitize_filename(decoded) - + # Split name and extension name, ext = os.path.splitext(sanitized) - + # Calculate available length for name considering the '...' and extension max_name_length = self.max_length - len('...') - len(ext) - + # Truncate name if it exceeds the max name length if len(name) > max_name_length: name = name[:max_name_length] + '...' - + # Ensure the final file name includes the extension return name + ext @@ -103,7 +104,7 @@ class OsManager: # Decode unicode characters decoded = unidecode(path) - + # Basic path sanitization sanitized = sanitize_filepath(decoded) @@ -121,7 +122,7 @@ class OsManager: if part ]) return '\\'.join(sanitized_parts) - + # Handle drive letters elif len(path) >= 2 and path[1] == ':': drive = path[:2] @@ -132,7 +133,7 @@ class OsManager: if part ] return '\\'.join(path_parts) - + # Regular path else: parts = path.replace('/', '\\').split('\\') @@ -146,21 +147,21 @@ class OsManager: for part in parts if part ] - + result = '/'.join(sanitized_parts) if is_absolute: result = '/' + result - + return result - + def create_path(self, path: str, mode: int = 0o755) -> bool: """ Create directory path with specified permissions. - + Args: path (str): Path to create. mode (int, optional): Directory permissions. Defaults to 0o755. - + Returns: bool: True if path created successfully, False otherwise. """ @@ -168,7 +169,7 @@ class OsManager: sanitized_path = self.get_sanitize_path(path) os.makedirs(sanitized_path, mode=mode, exist_ok=True) return True - + except Exception as e: logging.error(f"Path creation error: {e}") return False @@ -176,21 +177,21 @@ class OsManager: def remove_folder(self, folder_path: str) -> bool: """ Safely remove a folder. - + Args: folder_path (str): Path of directory to remove. - + Returns: bool: Removal status. """ try: shutil.rmtree(folder_path) return True - + except OSError as e: logging.error(f"Folder removal error: {e}") return False - + def remove_files_except_one(self, folder_path: str, keep_file: str) -> None: """ Delete all files in a folder except for one specified file. @@ -203,11 +204,11 @@ class OsManager: try: # List all files in the folder files_in_folder = os.listdir(folder_path) - + # Iterate over each file in the folder for file_name in files_in_folder: file_path = os.path.join(folder_path, file_name) - + # Check if the file is not the one to keep and is a regular file if file_name != keep_file and os.path.isfile(file_path): os.remove(file_path) # Delete the file @@ -215,7 +216,7 @@ class OsManager: except Exception as e: logging.error(f"An error occurred: {e}") raise - + def check_file(self, file_path: str) -> bool: """ Check if a file exists at the given file path. @@ -229,7 +230,7 @@ class OsManager: try: logging.info(f"Check if file exists: {file_path}") return os.path.exists(file_path) - + except Exception as e: logging.error(f"An error occurred while checking file existence: {e}") return False @@ -299,7 +300,7 @@ class OsSummary: """Get the binary directory based on OS.""" system = platform.system().lower() home = os.path.expanduser('~') - + if system == 'windows': return os.path.join(os.path.splitdrive(home)[0] + os.path.sep, 'binary') elif system == 'darwin': @@ -315,41 +316,41 @@ class OsSummary: try: result = subprocess.check_output(command, text=True).strip() return result.split('\n')[0] if result else None - + except subprocess.CalledProcessError: return None def get_library_version(self, lib_name: str): """ Retrieve the version of a Python library. - + Args: lib_name (str): The name of the Python library. - + Returns: str: The library name followed by its version, or `-not installed` if not found. """ try: version = importlib.metadata.version(lib_name) return f"{lib_name}-{version}" - + except importlib.metadata.PackageNotFoundError: return f"{lib_name}-not installed" def download_requirements(self, url: str, filename: str): """ Download the requirements.txt file from the specified URL if not found locally using requests. - + Args: url (str): The URL to download the requirements file from. filename (str): The local filename to save the requirements file as. """ try: import requests - + logging.info(f"{filename} not found locally. Downloading from {url}...") response = requests.get(url) - + if response.status_code == 200: with open(filename, 'wb') as f: f.write(response.content) @@ -357,7 +358,7 @@ class OsSummary: else: logging.error(f"Failed to download {filename}. HTTP Status code: {response.status_code}") sys.exit(0) - + except Exception as e: logging.error(f"Failed to download {filename}: {e}") sys.exit(0) @@ -373,7 +374,7 @@ class OsSummary: console.print(f"Installing {lib_name}...", style="bold yellow") subprocess.check_call([sys.executable, "-m", "pip", "install", lib_name]) console.print(f"{lib_name} installed successfully!", style="bold green") - + except subprocess.CalledProcessError as e: console.print(f"Failed to install {lib_name}: {e}", style="bold red") sys.exit(1) @@ -400,7 +401,7 @@ class OsSummary: arch = platform.machine() os_info = platform.platform() glibc_version = 'glibc ' + '.'.join(map(str, platform.libc_ver()[1])) - + console.print(f"[cyan]Python[white]: [bold red]{python_version} ({python_implementation} {arch}) - {os_info} ({glibc_version})[/bold red]") logging.info(f"Python: {python_version} ({python_implementation} {arch}) - {os_info} ({glibc_version})") @@ -408,10 +409,10 @@ class OsSummary: binary_dir = self.get_binary_directory() system = platform.system().lower() arch = platform.machine().lower() - + # Map architecture names arch_map = { - 'amd64': 'x64', + 'amd64': 'x64', 'x86_64': 'x64', 'x64': 'x64', 'arm64': 'arm64', @@ -424,15 +425,15 @@ class OsSummary: # Check binary directory if os.path.exists(binary_dir): - + # Search for any file containing 'ffmpeg' and the architecture ffmpeg_files = glob.glob(os.path.join(binary_dir, f'*ffmpeg*{arch}*')) ffprobe_files = glob.glob(os.path.join(binary_dir, f'*ffprobe*{arch}*')) - + if ffmpeg_files and ffprobe_files: self.ffmpeg_path = ffmpeg_files[0] self.ffprobe_path = ffprobe_files[0] - + # Set executable permissions if needed if system != 'windows': os.chmod(self.ffmpeg_path, 0o755) @@ -451,15 +452,17 @@ class OsSummary: # Handle requirements.txt if not getattr(sys, 'frozen', False): requirements_file = 'requirements.txt' - + + requirements_file = Path(__file__).parent.parent.parent / requirements_file + if not os.path.exists(requirements_file): self.download_requirements( 'https://raw.githubusercontent.com/Arrowar/StreamingCommunity/refs/heads/main/requirements.txt', requirements_file ) - + optional_libraries = [line.strip().split("=")[0] for line in open(requirements_file, 'r', encoding='utf-8-sig')] - + for lib in optional_libraries: installed_version = self.get_library_version(lib.split("<")[0]) if 'not installed' in installed_version: @@ -468,7 +471,7 @@ class OsSummary: self.install_library(lib) else: logging.info(f"Library: {installed_version}") - + #console.print(f"[cyan]Libraries[white]: [bold red]{', '.join([self.get_library_version(lib) for lib in optional_libraries])}[/bold red]\n") logging.info(f"Libraries: {', '.join([self.get_library_version(lib) for lib in optional_libraries])}") @@ -495,6 +498,6 @@ def compute_sha1_hash(input_string: str) -> str: """ # Compute the SHA-1 hash hashed_string = hashlib.sha1(input_string.encode()).hexdigest() - + # Return the hashed string return hashed_string diff --git a/StreamingCommunity/run.py b/StreamingCommunity/run.py index cc9f154..0a05a3f 100644 --- a/StreamingCommunity/run.py +++ b/StreamingCommunity/run.py @@ -22,8 +22,7 @@ from StreamingCommunity.Util.logger import Logger # Telegram util -from StreamingCommunity.TelegramHelp.session import get_session, deleteScriptId -from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance +from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession # Config @@ -62,7 +61,7 @@ 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: @@ -72,7 +71,7 @@ def load_search_functions(): # Se il modulo è nella lista da escludere, saltalo if module_name in excluded_sites: continue - + logging.info(f"Load module name: {module_name}") try: @@ -176,7 +175,7 @@ def force_exit(): print("🚨 Uscita forzata con os._exit(0)") os._exit(0) - + def main(script_id = 0): @@ -332,7 +331,7 @@ def main(script_id = 0): if category in input_to_function: run_function(input_to_function[category]) else: - + if TELEGRAM_BOT: bot.send_message(f"Categoria non valida", None) @@ -347,6 +346,6 @@ def main(script_id = 0): bot.send_message(f"Chiusura in corso", None) # Delete script_id - script_id = get_session() + script_id = TelegramSession.get_session() if script_id != "unknown": - deleteScriptId(script_id) + TelegramSession.deleteScriptId(script_id) diff --git a/test_run.py b/test_run.py index 012c2d5..ad7dece 100644 --- a/test_run.py +++ b/test_run.py @@ -3,19 +3,17 @@ import sys from StreamingCommunity.run import main from StreamingCommunity.Util._jsonConfig import config_manager -from StreamingCommunity.TelegramHelp.request_manager import RequestManager -from StreamingCommunity.TelegramHelp.session import set_session +from StreamingCommunity.TelegramHelp.telegram_bot import TelegramRequestManager, TelegramSession # Svuoto il file TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot') if TELEGRAM_BOT: - request_manager = RequestManager() + request_manager = TelegramRequestManager() request_manager.clear_file() script_id = sys.argv[1] if len(sys.argv) > 1 else "unknown" - set_session(script_id) + TelegramSession.set_session(script_id) main(script_id) - else: main() \ No newline at end of file