diff --git a/.github/.site/css/style.css b/.github/.site/css/style.css index 35d95af..33d50fc 100644 --- a/.github/.site/css/style.css +++ b/.github/.site/css/style.css @@ -38,14 +38,11 @@ body { flex-direction: column; } -header { - background-color: var(--header-bg); - backdrop-filter: blur(10px); - position: fixed; - width: 100%; - padding: 15px 0; - z-index: 1000; - box-shadow: 0 2px 12px var(--shadow-color); +.container { + max-width: 1400px; + margin: 0 auto; + padding: 20px; + flex: 1; } .header-container { @@ -88,13 +85,6 @@ header { font-size: 1.1rem; } -.container { - max-width: 1400px; - margin: 0 auto; - padding: 20px; - flex: 1; -} - .site-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); @@ -166,78 +156,6 @@ header { color: var(--accent-color); } -.site-content { - text-align: center; - width: 100%; -} - -.domain { - color: var(--text-color); - opacity: 0.8; - font-size: 0.9rem; - margin-bottom: 1.5rem; - word-break: break-all; -} - -.site-item a { - margin-top: 1rem; - background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); - color: white; - text-decoration: none; - font-weight: 500; - padding: 12px 28px; - border-radius: 8px; - width: fit-content; - margin: 0 auto; - display: flex; - align-items: center; - gap: 8px; -} - -.site-item a:hover { - opacity: 0.9; - transform: translateY(-2px); -} - -.site-title { - opacity: 0; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: rgba(0, 0, 0, 0.8); - padding: 10px 20px; - border-radius: 8px; - transition: opacity 0.3s ease; - color: white; - font-size: 1.2rem; - text-align: center; - width: 80%; - pointer-events: none; - z-index: 2; -} - -.site-item:hover .site-title { - opacity: 1; -} - -.site-item::after { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - opacity: 0; - transition: opacity 0.3s ease; - pointer-events: none; -} - -.site-item:hover::after { - opacity: 1; -} - .site-info { display: flex; flex-direction: column; @@ -264,6 +182,211 @@ header { opacity: 1; } +.site-status { + position: absolute; + top: 10px; + right: 10px; + width: 12px; + height: 12px; + border-radius: 50%; + background: #4CAF50; +} + +.site-status.offline { + background: #f44336; +} + +.status-indicator { + position: fixed; + top: 20px; + right: 20px; + background: var(--card-background); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 15px 20px; + box-shadow: 0 4px 20px var(--shadow-color); + z-index: 1001; + min-width: 280px; + max-width: 400px; + transition: all 0.3s ease; +} + +.status-indicator.hidden { + opacity: 0; + transform: translateY(-20px); + pointer-events: none; +} + +.status-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 15px; + font-weight: 600; + color: var(--primary-color); +} + +.status-icon { + width: 20px; + height: 20px; + border: 2px solid var(--primary-color); + border-radius: 50%; + border-top-color: transparent; + animation: spin 1s linear infinite; +} + +.status-icon.ready { + border: none; + background: #4CAF50; + animation: none; + position: relative; +} + +.status-icon.ready::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 12px; + font-weight: bold; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.status-text { + color: var(--text-color); + font-size: 0.9rem; + margin-bottom: 10px; +} + +.checking-sites { + max-height: 200px; + overflow-y: auto; + background: var(--background-color); + border-radius: 8px; + padding: 10px; + border: 1px solid var(--border-color); +} + +.checking-site { + display: flex; + align-items: center; + justify-content: between; + gap: 10px; + padding: 6px 8px; + margin-bottom: 4px; + border-radius: 6px; + background: var(--card-background); + font-size: 0.8rem; + color: var(--text-color); + transition: all 0.2s ease; +} + +.checking-site.completed { + opacity: 0.6; + background: var(--card-hover); +} + +.checking-site.online { + border-left: 3px solid #4CAF50; +} + +.checking-site.offline { + border-left: 3px solid #f44336; +} + +.checking-site .site-name { + flex: 1; + font-weight: 500; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.checking-site .site-status-icon { + width: 12px; + height: 12px; + border-radius: 50%; + flex-shrink: 0; +} + +.checking-site .site-status-icon.checking { + background: var(--primary-color); + animation: pulse 1s infinite; +} + +.checking-site .site-status-icon.online { + background: #4CAF50; +} + +.checking-site .site-status-icon.offline { + background: #f44336; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.progress-bar { + width: 100%; + height: 6px; + background: var(--background-color); + border-radius: 3px; + overflow: hidden; + margin-top: 10px; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--primary-color), var(--accent-color)); + width: 0%; + transition: width 0.3s ease; + border-radius: 3px; +} + +.loader { + width: 48px; + height: 48px; + border: 3px solid var(--primary-color); + border-bottom-color: transparent; + border-radius: 50%; + display: inline-block; + position: relative; + box-sizing: border-box; + animation: rotation 1s linear infinite; +} + +.loader::after { + content: ''; + position: absolute; + box-sizing: border-box; + left: 0; + top: 0; + width: 48px; + height: 48px; + border-radius: 50%; + border: 3px solid transparent; + border-bottom-color: var(--accent-color); + animation: rotationBack 0.5s linear infinite; + transform: rotate(45deg); +} + +@keyframes rotation { + 0% { transform: rotate(0deg) } + 100% { transform: rotate(360deg) } +} + +@keyframes rotationBack { + 0% { transform: rotate(0deg) } + 100% { transform: rotate(-360deg) } +} + footer { background: var(--card-background); border-top: 1px solid var(--border-color); @@ -355,26 +478,6 @@ footer { transform: scale(1.2); } -.github-stats { - display: flex; - gap: 10px; - margin-top: 10px; - font-size: 0.8rem; -} - -.github-badge { - background-color: var(--background-color); - padding: 4px 8px; - border-radius: 4px; - display: flex; - align-items: center; - gap: 4px; -} - -.github-badge i { - color: var(--accent-color); -} - .footer-description { margin-top: 15px; font-size: 0.9rem; @@ -383,103 +486,13 @@ footer { line-height: 1.5; } -.update-info { - text-align: center; - margin-top: 30px; - padding-top: 30px; - border-top: 1px solid var(--border-color); -} - .update-note { color: var(--accent-color); font-size: 0.9rem; opacity: 0.9; } -.theme-toggle { - position: relative; - top: unset; - right: unset; - z-index: 1; -} - -.theme-toggle input { - display: none; -} - -.theme-toggle label { - cursor: pointer; - padding: 8px; - background: var(--background-color); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 0 10px var(--shadow-color); - border: 1px solid var(--border-color); - transition: all 0.3s ease; -} - -.theme-toggle label:hover { - border-color: var(--primary-color); - transform: translateY(-2px); -} - -.theme-toggle .fa-sun { - display: none; - color: #ffd700; -} - -.theme-toggle .fa-moon { - color: #8c52ff; -} - -.theme-toggle input:checked ~ label .fa-sun { - display: block; -} - -.theme-toggle input:checked ~ label .fa-moon { - display: none; -} - -.loader { - width: 48px; - height: 48px; - border: 3px solid var(--primary-color); - border-bottom-color: transparent; - border-radius: 50%; - display: inline-block; - position: relative; - box-sizing: border-box; - animation: rotation 1s linear infinite; -} - -.loader::after { - content: ''; - position: absolute; - box-sizing: border-box; - left: 0; - top: 0; - width: 48px; - height: 48px; - border-radius: 50%; - border: 3px solid transparent; - border-bottom-color: var(--accent-color); - animation: rotationBack 0.5s linear infinite; - transform: rotate(45deg); -} - -@keyframes rotation { - 0% { transform: rotate(0deg) } - 100% { transform: rotate(360deg) } -} - -@keyframes rotationBack { - 0% { transform: rotate(0deg) } - 100% { transform: rotate(-360deg) } -} - -/* Improved Responsiveness */ +/* Responsiveness */ @media (max-width: 768px) { .site-grid { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); @@ -496,11 +509,7 @@ footer { grid-template-columns: 1fr; gap: 20px; padding: 15px; - } - - .theme-toggle { - top: 10px; - right: 10px; + text-align: center; } .header-container { @@ -517,27 +526,6 @@ footer { width: 100%; justify-content: center; } -} - -@media (max-width: 480px) { - .site-grid { - grid-template-columns: 1fr; - } - - .site-item { - min-height: 220px; - } - - .container { - padding: 10px; - } -} - -@media (max-width: 768px) { - .footer-content { - grid-template-columns: 1fr; - text-align: center; - } .footer-title::after { left: 50%; @@ -557,83 +545,16 @@ footer { } } -.time-change { - color: var(--text-color); - opacity: 0.7; - font-size: 0.85rem; - margin-bottom: 0.5rem; - word-break: break-all; -} +@media (max-width: 480px) { + .site-grid { + grid-template-columns: 1fr; + } -.label { - color: var(--accent-color); - font-weight: 500; -} - -.controls-container { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 20px; - padding: 15px 20px; - background: var(--card-background); - border-radius: 12px; - border: 1px solid var(--border-color); -} - -.grid-controls { - display: flex; - align-items: center; - gap: 10px; -} - -.grid-controls label { - color: var(--text-color); - font-weight: 500; -} - -.grid-controls select { - padding: 8px 12px; - border-radius: 8px; - border: 1px solid var(--border-color); - background: var(--background-color); - color: var(--text-color); - cursor: pointer; - transition: all 0.3s ease; -} - -.grid-controls select:hover { - border-color: var(--primary-color); -} - -.sites-stats { - display: flex; - gap: 20px; - align-items: center; -} - -.total-sites, .last-update-global { - display: flex; - align-items: center; - gap: 8px; - color: var(--text-color); - font-size: 0.9rem; -} - -.total-sites i, .last-update-global i { - color: var(--primary-color); -} - -.site-status { - position: absolute; - top: 10px; - right: 10px; - width: 12px; - height: 12px; - border-radius: 50%; - background: #4CAF50; -} - -.site-status.offline { - background: #f44336; + .site-item { + min-height: 220px; + } + + .container { + padding: 10px; + } } \ No newline at end of file diff --git a/.github/.site/js/script.js b/.github/.site/js/script.js index 5a9f34c..e89eb9f 100644 --- a/.github/.site/js/script.js +++ b/.github/.site/js/script.js @@ -1,32 +1,82 @@ document.documentElement.setAttribute('data-theme', 'dark'); -function initGridControls() { - const gridSize = document.getElementById('grid-size'); - const siteGrid = document.querySelector('.site-grid'); - - gridSize.addEventListener('change', function() { - switch(this.value) { - case 'small': - siteGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(200px, 1fr))'; - break; - case 'medium': - siteGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(300px, 1fr))'; - break; - case 'large': - siteGrid.style.gridTemplateColumns = 'repeat(auto-fill, minmax(400px, 1fr))'; - break; - } - localStorage.setItem('preferredGridSize', this.value); - }); +let statusIndicator = null; +let checkingSites = new Map(); +let totalSites = 0; +let completedSites = 0; - const savedSize = localStorage.getItem('preferredGridSize'); - if (savedSize) { - gridSize.value = savedSize; - gridSize.dispatchEvent(new Event('change')); +function createStatusIndicator() { + statusIndicator = document.createElement('div'); + statusIndicator.className = 'status-indicator'; + statusIndicator.innerHTML = ` +
-
+
diff --git a/StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py b/StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py index 0dd36a7..d46f635 100644 --- a/StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +++ b/StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py @@ -31,7 +31,8 @@ class ScrapSerie: self.client = httpx.Client( cookies={"sessionId": self.session_id}, headers={"User-Agent": get_userAgent(), "csrf-token": self.csrf_token}, - base_url=full_url + base_url=full_url, + verify=False ) try: diff --git a/StreamingCommunity/Api/Site/raiplay/__init__.py b/StreamingCommunity/Api/Site/raiplay/__init__.py index d1b7e23..816d753 100644 --- a/StreamingCommunity/Api/Site/raiplay/__init__.py +++ b/StreamingCommunity/Api/Site/raiplay/__init__.py @@ -21,7 +21,7 @@ from .film import download_film # Variable indice = 5 _useFor = "Film_&_Serie" -_priority = 1 # NOTE: Site search need the use of tmbd obj +_priority = 0 _engineDownload = "hls" _deprecate = False diff --git a/StreamingCommunity/Api/Site/raiplay/site.py b/StreamingCommunity/Api/Site/raiplay/site.py index c4a4b1e..ef95cbc 100644 --- a/StreamingCommunity/Api/Site/raiplay/site.py +++ b/StreamingCommunity/Api/Site/raiplay/site.py @@ -1,9 +1,5 @@ # 21.05.24 -import threading -import queue - - # External libraries import httpx from rich.console import Console @@ -13,12 +9,9 @@ from rich.console import Console from StreamingCommunity.Util.config_json import config_manager from StreamingCommunity.Util.headers import get_userAgent from StreamingCommunity.Util.table import TVShowManager -from StreamingCommunity.Lib.TMBD.tmdb import tmdb - - -# Logic class from StreamingCommunity.Api.Template.config_loader import site_constant from StreamingCommunity.Api.Template.Class.SearchType import MediaManager +from .util.ScrapeSerie import GetSerieInfo # Variable @@ -26,76 +19,33 @@ console = Console() media_search_manager = MediaManager() table_show_manager = TVShowManager() max_timeout = config_manager.get_int("REQUESTS", "timeout") -MAX_THREADS = 12 -def determine_media_type(title): +def determine_media_type(item): """ - Use TMDB to determine if a title is a movie or TV show. + Determine if the item is a film or TV series by checking actual seasons count + using GetSerieInfo. """ try: - # First search as a movie - movie_results = tmdb._make_request("search/movie", {"query": title}) - movie_count = len(movie_results.get("results", [])) - - # Then search as a TV show - tv_results = tmdb._make_request("search/tv", {"query": title}) - tv_count = len(tv_results.get("results", [])) - - # If results found in only one category, use that - if movie_count > 0 and tv_count == 0: - return "film" - elif tv_count > 0 and movie_count == 0: - return "tv" - - # If both have results, compare popularity - if movie_count > 0 and tv_count > 0: - top_movie = movie_results["results"][0] - top_tv = tv_results["results"][0] - - return "film" if top_movie.get("popularity", 0) > top_tv.get("popularity", 0) else "tv" + # Extract program name from path_id + program_name = None + if item.get('path_id'): + parts = item['path_id'].strip('/').split('/') + if len(parts) >= 2: + program_name = parts[-1].split('.')[0] - return "film" + if not program_name: + return "film" + + scraper = GetSerieInfo(program_name) + scraper.collect_info_title() + return "tv" if scraper.getNumberSeason() > 0 else "film" except Exception as e: - console.log(f"Error determining media type with TMDB: {e}") + console.print(f"[red]Error determining media type: {e}[/red]") return "film" -def worker_determine_type(work_queue, result_dict, worker_id): - """ - Worker function to process items from queue and determine media types. - - Parameters: - - work_queue: Queue containing items to process - - result_dict: Dictionary to store results - - worker_id: ID of the worker thread - """ - while not work_queue.empty(): - try: - index, item = work_queue.get(block=False) - title = item.get('titolo', '') - media_type = determine_media_type(title) - - result_dict[index] = { - 'id': item.get('id', ''), - 'name': title, - 'type': media_type, - 'path_id': item.get('path_id', ''), - 'url': f"https://www.raiplay.it{item.get('url', '')}", - 'image': f"https://www.raiplay.it{item.get('immagine', '')}", - } - - work_queue.task_done() - - except queue.Empty: - break - - except Exception as e: - console.log(f"Worker {worker_id} error: {e}") - work_queue.task_done() - - def title_search(query: str) -> int: """ Search for titles based on a search query. @@ -141,33 +91,15 @@ def title_search(query: str) -> int: data = response.json().get('agg').get('titoli').get('cards') data = data[:15] if len(data) > 15 else data - # Use multithreading to determine media types in parallel - work_queue = queue.Queue() - result_dict = {} - - # Add items to the work queue - for i, item in enumerate(data): - work_queue.put((i, item)) - - # Create and start worker threads - threads = [] - for i in range(min(MAX_THREADS, len(data))): - thread = threading.Thread( - target=worker_determine_type, - args=(work_queue, result_dict, i), - daemon=True - ) - threads.append(thread) - thread.start() - - # Wait for all threads to complete - for thread in threads: - thread.join() - - # Add all results to media manager in correct order - for i in range(len(data)): - if i in result_dict: - media_search_manager.add_media(result_dict[i]) + # Process each item and add to media manager + for item in data: + media_search_manager.add_media({ + 'id': item.get('id', ''), + 'name': item.get('titolo', ''), + 'type': determine_media_type(item), + 'path_id': item.get('path_id', ''), + 'url': f"https://www.raiplay.it{item.get('url', '')}", + 'image': f"https://www.raiplay.it{item.get('immagine', '')}", + }) - # Return the number of titles found return media_search_manager.get_length() \ No newline at end of file diff --git a/StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py b/StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py index d54ec1f..b7bd863 100644 --- a/StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +++ b/StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py @@ -30,28 +30,48 @@ class GetSerieInfo: try: program_url = f"{self.base_url}/programmi/{self.program_name}.json" response = httpx.get(url=program_url, headers=get_headers(), timeout=max_timeout) + + # If 404, content is not yet available + if response.status_code == 404: + logging.info(f"Content not yet available: {self.program_name}") + return + response.raise_for_status() - json_data = response.json() # Look for seasons in the 'blocks' property - for block in json_data.get('blocks'): - if block.get('type') == 'RaiPlay Multimedia Block' and block.get('name', '').lower() == 'episodi': - self.publishing_block_id = block.get('id') - - # Extract seasons from sets array - for season_set in block.get('sets', []): - if 'stagione' in season_set.get('name', '').lower(): - self.seasons_manager.add_season({ - 'id': season_set.get('id', ''), - 'number': len(self.seasons_manager.seasons) + 1, - 'name': season_set.get('name', ''), - 'path': season_set.get('path_id', ''), - 'episodes_count': season_set.get('episode_size', {}).get('number', 0) - }) + for block in json_data.get('blocks', []): - except Exception as e: + # Check if block is a season block or episodi block + if block.get('type') == 'RaiPlay Multimedia Block': + if block.get('name', '').lower() == 'episodi': + self.publishing_block_id = block.get('id') + + # Extract seasons from sets array + for season_set in block.get('sets', []): + if 'stagione' in season_set.get('name', '').lower(): + self._add_season(season_set, block.get('id')) + + elif 'stagione' in block.get('name', '').lower(): + self.publishing_block_id = block.get('id') + + # Extract season directly from block's sets + for season_set in block.get('sets', []): + self._add_season(season_set, block.get('id')) + + except httpx.HTTPError as e: logging.error(f"Error collecting series info: {e}") + except Exception as e: + logging.error(f"Unexpected error collecting series info: {e}") + + def _add_season(self, season_set: dict, block_id: str): + self.seasons_manager.add_season({ + 'id': season_set.get('id', ''), + 'number': len(self.seasons_manager.seasons) + 1, + 'name': season_set.get('name', ''), + 'path': season_set.get('path_id', ''), + 'episodes_count': season_set.get('episode_size', {}).get('number', 0) + }) def collect_info_season(self, number_season: int) -> None: """Get episodes for a specific season.""" diff --git a/StreamingCommunity/Util/config_json.py b/StreamingCommunity/Util/config_json.py index bea1edc..08070cd 100644 --- a/StreamingCommunity/Util/config_json.py +++ b/StreamingCommunity/Util/config_json.py @@ -39,9 +39,6 @@ class ConfigManager: # Get the actual path of the module file current_file_path = os.path.abspath(__file__) - # Navigate upwards to find the project root - # Assuming this file is in a package structure like StreamingCommunity/Util/config_json.py - # We need to go up 2 levels to reach the project root base_path = os.path.dirname(os.path.dirname(os.path.dirname(current_file_path))) # Initialize file paths @@ -562,7 +559,6 @@ class ConfigManager: return section in config_source -# Helper function to check the platform def get_use_large_bar(): """ Determine if the large bar feature should be enabled. diff --git a/StreamingCommunity/Util/os.py b/StreamingCommunity/Util/os.py index 8da5de1..5e35f03 100644 --- a/StreamingCommunity/Util/os.py +++ b/StreamingCommunity/Util/os.py @@ -296,12 +296,6 @@ class InternManager(): "Google": ["8.8.8.8", "8.8.4.4"], "OpenDNS": ["208.67.222.222", "208.67.220.220"], "Quad9": ["9.9.9.9", "149.112.112.112"], - "AdGuard": ["94.140.14.14", "94.140.15.15"], - "Comodo": ["8.26.56.26", "8.20.247.20"], - "Level3": ["209.244.0.3", "209.244.0.4"], - "Norton": ["199.85.126.10", "199.85.127.10"], - "CleanBrowsing": ["185.228.168.9", "185.228.169.9"], - "Yandex": ["77.88.8.8", "77.88.8.1"] } try: