StreamingCommunity 3.2.8__py3-none-any.whl → 3.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of StreamingCommunity might be problematic. Click here for more details.
- StreamingCommunity/Api/Player/Helper/Vixcloud/util.py +2 -1
- StreamingCommunity/Api/Player/hdplayer.py +2 -2
- StreamingCommunity/Api/Player/sweetpixel.py +5 -8
- StreamingCommunity/Api/Site/altadefinizione/__init__.py +32 -15
- StreamingCommunity/Api/Site/altadefinizione/film.py +10 -8
- StreamingCommunity/Api/Site/altadefinizione/series.py +9 -7
- StreamingCommunity/Api/Site/altadefinizione/site.py +1 -1
- StreamingCommunity/Api/Site/animeunity/__init__.py +31 -15
- StreamingCommunity/Api/Site/animeunity/serie.py +2 -2
- StreamingCommunity/Api/Site/animeworld/__init__.py +33 -7
- StreamingCommunity/Api/Site/animeworld/site.py +3 -5
- StreamingCommunity/Api/Site/animeworld/util/ScrapeSerie.py +8 -10
- StreamingCommunity/Api/Site/crunchyroll/__init__.py +44 -12
- StreamingCommunity/Api/Site/crunchyroll/film.py +9 -7
- StreamingCommunity/Api/Site/crunchyroll/series.py +9 -7
- StreamingCommunity/Api/Site/crunchyroll/site.py +16 -1
- StreamingCommunity/Api/Site/guardaserie/__init__.py +36 -10
- StreamingCommunity/Api/Site/guardaserie/series.py +8 -6
- StreamingCommunity/Api/Site/guardaserie/site.py +0 -3
- StreamingCommunity/Api/Site/guardaserie/util/ScrapeSerie.py +1 -2
- StreamingCommunity/Api/Site/mediasetinfinity/__init__.py +37 -12
- StreamingCommunity/Api/Site/mediasetinfinity/film.py +10 -16
- StreamingCommunity/Api/Site/mediasetinfinity/series.py +12 -18
- StreamingCommunity/Api/Site/mediasetinfinity/site.py +18 -3
- StreamingCommunity/Api/Site/mediasetinfinity/util/ScrapeSerie.py +214 -180
- StreamingCommunity/Api/Site/mediasetinfinity/util/get_license.py +2 -31
- StreamingCommunity/Api/Site/raiplay/__init__.py +47 -12
- StreamingCommunity/Api/Site/raiplay/film.py +42 -10
- StreamingCommunity/Api/Site/raiplay/series.py +53 -11
- StreamingCommunity/Api/Site/raiplay/site.py +4 -1
- StreamingCommunity/Api/Site/raiplay/util/ScrapeSerie.py +2 -1
- StreamingCommunity/Api/Site/raiplay/util/get_license.py +40 -0
- StreamingCommunity/Api/Site/streamingcommunity/__init__.py +5 -8
- StreamingCommunity/Api/Site/streamingcommunity/film.py +7 -5
- StreamingCommunity/Api/Site/streamingcommunity/series.py +9 -7
- StreamingCommunity/Api/Site/streamingcommunity/site.py +8 -3
- StreamingCommunity/Api/Site/streamingcommunity/util/ScrapeSerie.py +5 -2
- StreamingCommunity/Api/Site/streamingwatch/__init__.py +43 -9
- StreamingCommunity/Api/Site/streamingwatch/film.py +7 -5
- StreamingCommunity/Api/Site/streamingwatch/series.py +8 -6
- StreamingCommunity/Api/Site/streamingwatch/site.py +3 -1
- StreamingCommunity/Api/Site/streamingwatch/util/ScrapeSerie.py +3 -3
- StreamingCommunity/Api/Template/Util/__init__.py +10 -1
- StreamingCommunity/Api/Template/Util/manage_ep.py +4 -4
- StreamingCommunity/Api/Template/__init__.py +5 -1
- StreamingCommunity/Api/Template/site.py +10 -6
- StreamingCommunity/Lib/Downloader/DASH/cdm_helpher.py +13 -12
- StreamingCommunity/Lib/Downloader/DASH/decrypt.py +1 -1
- StreamingCommunity/Lib/Downloader/DASH/downloader.py +24 -22
- StreamingCommunity/Lib/Downloader/DASH/parser.py +1 -1
- StreamingCommunity/Lib/Downloader/DASH/segments.py +4 -3
- StreamingCommunity/Lib/Downloader/HLS/downloader.py +11 -9
- StreamingCommunity/Lib/Downloader/HLS/segments.py +4 -9
- StreamingCommunity/Lib/Downloader/MP4/downloader.py +25 -6
- StreamingCommunity/Lib/Downloader/TOR/downloader.py +3 -5
- StreamingCommunity/Lib/Downloader/__init__.py +9 -1
- StreamingCommunity/Lib/FFmpeg/__init__.py +10 -1
- StreamingCommunity/Lib/FFmpeg/command.py +4 -6
- StreamingCommunity/Lib/FFmpeg/util.py +1 -1
- StreamingCommunity/Lib/M3U8/__init__.py +9 -1
- StreamingCommunity/Lib/M3U8/decryptor.py +8 -4
- StreamingCommunity/Lib/M3U8/estimator.py +0 -6
- StreamingCommunity/Lib/M3U8/parser.py +1 -1
- StreamingCommunity/Lib/M3U8/url_fixer.py +1 -1
- StreamingCommunity/Lib/TMBD/__init__.py +6 -1
- StreamingCommunity/TelegramHelp/config.json +1 -5
- StreamingCommunity/TelegramHelp/telegram_bot.py +9 -10
- StreamingCommunity/Upload/update.py +2 -2
- StreamingCommunity/Upload/version.py +1 -1
- StreamingCommunity/Util/config_json.py +139 -59
- StreamingCommunity/Util/http_client.py +201 -0
- StreamingCommunity/Util/message.py +1 -1
- StreamingCommunity/Util/os.py +8 -5
- StreamingCommunity/Util/table.py +3 -3
- StreamingCommunity/__init__.py +9 -1
- StreamingCommunity/run.py +394 -258
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/METADATA +147 -45
- streamingcommunity-3.3.0.dist-info/RECORD +110 -0
- StreamingCommunity/Api/Site/cb01new/__init__.py +0 -72
- StreamingCommunity/Api/Site/cb01new/film.py +0 -62
- StreamingCommunity/Api/Site/cb01new/site.py +0 -78
- streamingcommunity-3.2.8.dist-info/RECORD +0 -111
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/WHEEL +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/entry_points.txt +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/licenses/LICENSE +0 -0
- {streamingcommunity-3.2.8.dist-info → streamingcommunity-3.3.0.dist-info}/top_level.txt +0 -0
StreamingCommunity/run.py
CHANGED
|
@@ -8,9 +8,11 @@ import logging
|
|
|
8
8
|
import platform
|
|
9
9
|
import argparse
|
|
10
10
|
import importlib
|
|
11
|
-
import threading
|
|
11
|
+
import threading
|
|
12
|
+
import asyncio
|
|
13
|
+
import subprocess
|
|
12
14
|
from urllib.parse import urlparse
|
|
13
|
-
from typing import Callable
|
|
15
|
+
from typing import Callable, Dict, Tuple
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
# External library
|
|
@@ -22,18 +24,24 @@ from rich.prompt import Prompt
|
|
|
22
24
|
from .global_search import global_search
|
|
23
25
|
from StreamingCommunity.Util.message import start_message
|
|
24
26
|
from StreamingCommunity.Util.config_json import config_manager
|
|
25
|
-
from StreamingCommunity.Util.os import os_summary, internet_manager
|
|
27
|
+
from StreamingCommunity.Util.os import os_summary, internet_manager, os_manager
|
|
26
28
|
from StreamingCommunity.Util.logger import Logger
|
|
27
|
-
from StreamingCommunity.Upload.update import update as git_update
|
|
28
29
|
from StreamingCommunity.Lib.TMBD import tmdb
|
|
30
|
+
from StreamingCommunity.Upload.update import update as git_update
|
|
29
31
|
from StreamingCommunity.TelegramHelp.telegram_bot import get_bot_instance, TelegramSession
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
# Config
|
|
33
35
|
SHOW_TRENDING = config_manager.get_bool('DEFAULT', 'show_trending')
|
|
34
|
-
NOT_CLOSE_CONSOLE = config_manager.get_bool('DEFAULT', 'not_close')
|
|
35
36
|
TELEGRAM_BOT = config_manager.get_bool('DEFAULT', 'telegram_bot')
|
|
36
37
|
BYPASS_DNS = config_manager.get_bool('DEFAULT', 'bypass_dns')
|
|
38
|
+
COLOR_MAP = {
|
|
39
|
+
"anime": "red",
|
|
40
|
+
"film_&_serie": "yellow",
|
|
41
|
+
"serie": "blue",
|
|
42
|
+
"torrent": "white"
|
|
43
|
+
}
|
|
44
|
+
CATEGORY_MAP = {1: "anime", 2: "film_&_serie", 3: "serie", 4: "torrent"}
|
|
37
45
|
|
|
38
46
|
|
|
39
47
|
# Variable
|
|
@@ -42,14 +50,7 @@ msg = Prompt()
|
|
|
42
50
|
|
|
43
51
|
|
|
44
52
|
def run_function(func: Callable[..., None], close_console: bool = False, search_terms: str = None) -> None:
|
|
45
|
-
"""
|
|
46
|
-
Run a given function indefinitely or once, depending on the value of close_console.
|
|
47
|
-
|
|
48
|
-
Parameters:
|
|
49
|
-
func (Callable[..., None]): The function to run.
|
|
50
|
-
close_console (bool, optional): Whether to close the console after running the function once. Defaults to False.
|
|
51
|
-
search_terms (str, optional): Search terms to use for the function. Defaults to None.
|
|
52
|
-
"""
|
|
53
|
+
"""Run function once or indefinitely based on close_console flag."""
|
|
53
54
|
if close_console:
|
|
54
55
|
while 1:
|
|
55
56
|
func(search_terms)
|
|
@@ -57,124 +58,246 @@ def run_function(func: Callable[..., None], close_console: bool = False, search_
|
|
|
57
58
|
func(search_terms)
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
modules = []
|
|
61
|
+
def load_search_functions() -> Dict[str, Tuple]:
|
|
62
|
+
"""Load and return all available search functions from site modules."""
|
|
63
63
|
loaded_functions = {}
|
|
64
|
-
|
|
65
|
-
# Lista dei siti da escludere se TELEGRAM_BOT è attivo
|
|
66
64
|
excluded_sites = {"cb01new", "guardaserie", "ilcorsaronero", "mostraguarda"} if TELEGRAM_BOT else set()
|
|
67
|
-
|
|
68
|
-
#
|
|
69
|
-
if getattr(sys, 'frozen', False)
|
|
70
|
-
base_path = os.path.join(sys._MEIPASS, "StreamingCommunity")
|
|
71
|
-
else:
|
|
72
|
-
base_path = os.path.dirname(__file__)
|
|
73
|
-
|
|
65
|
+
|
|
66
|
+
# Determine base path
|
|
67
|
+
base_path = os.path.join(sys._MEIPASS, "StreamingCommunity") if getattr(sys, 'frozen', False) else os.path.dirname(__file__)
|
|
74
68
|
api_dir = os.path.join(base_path, 'Api', 'Site')
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for init_file in
|
|
79
|
-
|
|
80
|
-
# Get folder name as module name
|
|
69
|
+
|
|
70
|
+
# Get all modules with their indices and sort them
|
|
71
|
+
modules = []
|
|
72
|
+
for init_file in glob.glob(os.path.join(api_dir, '*', '__init__.py')):
|
|
81
73
|
module_name = os.path.basename(os.path.dirname(init_file))
|
|
82
|
-
|
|
83
|
-
# Se il modulo è nella lista da escludere, saltalo
|
|
74
|
+
|
|
84
75
|
if module_name in excluded_sites:
|
|
85
76
|
continue
|
|
86
|
-
|
|
87
|
-
logging.info(f"Load module name: {module_name}")
|
|
88
|
-
|
|
77
|
+
|
|
89
78
|
try:
|
|
90
|
-
# Dynamically import the module
|
|
91
79
|
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
use_for = getattr(mod, '_useFor')
|
|
96
|
-
|
|
97
|
-
if not getattr(mod, '_deprecate'):
|
|
98
|
-
modules.append((module_name, indice, use_for))
|
|
99
|
-
|
|
80
|
+
if not getattr(mod, '_deprecate', False):
|
|
81
|
+
modules.append((module_name, getattr(mod, 'indice'), getattr(mod, '_useFor')))
|
|
82
|
+
logging.info(f"Load module name: {module_name}")
|
|
100
83
|
except Exception as e:
|
|
101
84
|
console.print(f"[red]Failed to import module {module_name}: {str(e)}")
|
|
102
|
-
|
|
103
|
-
# Sort
|
|
104
|
-
modules
|
|
105
|
-
|
|
106
|
-
# Load search functions in the sorted order
|
|
107
|
-
for module_name, _, use_for in modules:
|
|
108
|
-
|
|
109
|
-
# Construct a unique alias for the module
|
|
110
|
-
module_alias = f'{module_name}_search'
|
|
111
|
-
|
|
85
|
+
|
|
86
|
+
# Sort by index and load search functions
|
|
87
|
+
for module_name, _, use_for in sorted(modules, key=lambda x: x[1]):
|
|
112
88
|
try:
|
|
113
|
-
|
|
114
|
-
# Dynamically import the module
|
|
115
89
|
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
|
|
116
|
-
|
|
117
|
-
# Get the search function from the module (assuming the function is named 'search' and defined in __init__.py)
|
|
118
|
-
search_function = getattr(mod, 'search')
|
|
119
|
-
|
|
120
|
-
# Add the function to the loaded functions dictionary
|
|
121
|
-
loaded_functions[module_alias] = (search_function, use_for)
|
|
122
|
-
|
|
90
|
+
loaded_functions[f'{module_name}_search'] = (getattr(mod, 'search'), use_for)
|
|
123
91
|
except Exception as e:
|
|
124
92
|
console.print(f"[red]Failed to load search function from module {module_name}: {str(e)}")
|
|
125
|
-
|
|
93
|
+
|
|
126
94
|
return loaded_functions
|
|
127
95
|
|
|
128
96
|
|
|
129
97
|
def initialize():
|
|
130
|
-
|
|
131
|
-
# Get start message
|
|
98
|
+
"""Initialize the application with system checks and setup."""
|
|
132
99
|
start_message()
|
|
133
|
-
|
|
134
|
-
# Get system info
|
|
135
100
|
os_summary.get_system_summary()
|
|
136
|
-
|
|
137
|
-
#
|
|
101
|
+
|
|
102
|
+
# Windows 7 terminal size fix
|
|
138
103
|
if platform.system() == "Windows" and "7" in platform.version():
|
|
139
104
|
os.system('mode 120, 40')
|
|
140
|
-
|
|
141
|
-
#
|
|
105
|
+
|
|
106
|
+
# Python version check
|
|
142
107
|
if sys.version_info < (3, 7):
|
|
143
108
|
console.log("[red]Install python version > 3.7.16")
|
|
144
109
|
sys.exit(0)
|
|
145
|
-
|
|
146
|
-
#
|
|
110
|
+
|
|
111
|
+
# Show trending content
|
|
147
112
|
if SHOW_TRENDING:
|
|
148
113
|
print()
|
|
149
114
|
tmdb.display_trending_films()
|
|
150
115
|
tmdb.display_trending_tv_shows()
|
|
151
|
-
|
|
152
|
-
#
|
|
116
|
+
|
|
117
|
+
# Attempt GitHub update
|
|
153
118
|
try:
|
|
154
119
|
git_update()
|
|
155
|
-
except:
|
|
156
|
-
console.log("[red]Error with loading github
|
|
120
|
+
except Exception as e:
|
|
121
|
+
console.log(f"[red]Error with loading github: {str(e)}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _expand_user_path(path: str) -> str:
|
|
125
|
+
"""Expand '~' and environment variables and normalize the path."""
|
|
126
|
+
if not path:
|
|
127
|
+
return path
|
|
128
|
+
return os.path.normpath(os.path.expandvars(os.path.expanduser(path)))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _should_run_on_current_os(hook: dict) -> bool:
|
|
132
|
+
"""Check if a hook is allowed on current OS."""
|
|
133
|
+
allowed_systems = hook.get('os')
|
|
134
|
+
if not allowed_systems:
|
|
135
|
+
return True
|
|
136
|
+
try:
|
|
137
|
+
normalized = [str(s).strip().lower() for s in allowed_systems]
|
|
138
|
+
except Exception:
|
|
139
|
+
return True
|
|
140
|
+
return os_manager.system in normalized
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _build_command_for_hook(hook: dict) -> Tuple[list, dict]:
|
|
144
|
+
"""Build the subprocess command and environment for a hook definition."""
|
|
145
|
+
hook_type = str(hook.get('type', '')).strip().lower()
|
|
146
|
+
script_path = hook.get('path')
|
|
147
|
+
inline_command = hook.get('command')
|
|
148
|
+
args = hook.get('args', [])
|
|
149
|
+
env = hook.get('env') or {}
|
|
150
|
+
workdir = hook.get('cwd')
|
|
151
|
+
|
|
152
|
+
if isinstance(args, str):
|
|
153
|
+
args = [a for a in args.split(' ') if a]
|
|
154
|
+
elif not isinstance(args, list):
|
|
155
|
+
args = []
|
|
156
|
+
|
|
157
|
+
if script_path:
|
|
158
|
+
script_path = _expand_user_path(script_path)
|
|
159
|
+
if not os.path.isabs(script_path):
|
|
160
|
+
script_path = os.path.abspath(script_path)
|
|
161
|
+
|
|
162
|
+
if workdir:
|
|
163
|
+
workdir = _expand_user_path(workdir)
|
|
164
|
+
|
|
165
|
+
base_env = os.environ.copy()
|
|
166
|
+
for k, v in env.items():
|
|
167
|
+
base_env[str(k)] = str(v)
|
|
168
|
+
|
|
169
|
+
if hook_type == 'python':
|
|
170
|
+
if not script_path:
|
|
171
|
+
raise ValueError("Missing 'path' for python hook")
|
|
172
|
+
command = [sys.executable, script_path] + args
|
|
173
|
+
return ([c for c in command if c], {'env': base_env, 'cwd': workdir})
|
|
174
|
+
|
|
175
|
+
if os_manager.system in ('linux', 'darwin'):
|
|
176
|
+
if hook_type in ('bash', 'sh', 'shell'):
|
|
177
|
+
if inline_command:
|
|
178
|
+
command = ['/bin/bash', '-lc', inline_command]
|
|
179
|
+
else:
|
|
180
|
+
if not script_path:
|
|
181
|
+
raise ValueError("Missing 'path' for bash/sh hook")
|
|
182
|
+
command = ['/bin/bash', script_path] + args
|
|
183
|
+
return (command, {'env': base_env, 'cwd': workdir})
|
|
184
|
+
|
|
185
|
+
if os_manager.system == 'windows':
|
|
186
|
+
if hook_type in ('bat', 'cmd', 'shell'):
|
|
187
|
+
if inline_command:
|
|
188
|
+
command = ['cmd', '/c', inline_command]
|
|
189
|
+
else:
|
|
190
|
+
if not script_path:
|
|
191
|
+
raise ValueError("Missing 'path' for bat/cmd hook")
|
|
192
|
+
command = ['cmd', '/c', script_path] + args
|
|
193
|
+
return (command, {'env': base_env, 'cwd': workdir})
|
|
194
|
+
|
|
195
|
+
raise ValueError(f"Unsupported hook type '{hook_type}' on OS '{os_manager.system}'")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _iter_hooks(stage: str):
|
|
199
|
+
"""Yield hook dicts for a given stage ('pre_run' | 'post_run')."""
|
|
200
|
+
try:
|
|
201
|
+
hooks_section = config_manager.config.get('HOOKS', {})
|
|
202
|
+
hooks_list = hooks_section.get(stage, []) or []
|
|
203
|
+
if not isinstance(hooks_list, list):
|
|
204
|
+
return
|
|
205
|
+
for hook in hooks_list:
|
|
206
|
+
if not isinstance(hook, dict):
|
|
207
|
+
continue
|
|
208
|
+
yield hook
|
|
209
|
+
except Exception:
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def execute_hooks(stage: str) -> None:
|
|
214
|
+
"""Execute configured hooks for the given stage. Stage can be 'pre_run' or 'post_run'."""
|
|
215
|
+
stage = str(stage).strip().lower()
|
|
216
|
+
if stage not in ('pre_run', 'post_run'):
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
for hook in _iter_hooks(stage):
|
|
220
|
+
name = hook.get('name') or f"{stage}_hook"
|
|
221
|
+
enabled = hook.get('enabled', True)
|
|
222
|
+
continue_on_error = hook.get('continue_on_error', True)
|
|
223
|
+
timeout = hook.get('timeout')
|
|
224
|
+
|
|
225
|
+
if not enabled:
|
|
226
|
+
logging.info(f"Skip hook (disabled): {name}")
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
if not _should_run_on_current_os(hook):
|
|
230
|
+
logging.info(f"Skip hook (OS filter): {name}")
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
command, popen_kwargs = _build_command_for_hook(hook)
|
|
235
|
+
logging.info(f"Running hook: {name} -> {' '.join(command)}")
|
|
236
|
+
result = None
|
|
237
|
+
if timeout is not None:
|
|
238
|
+
result = subprocess.run(command, check=False, capture_output=True, text=True, timeout=int(timeout), **popen_kwargs)
|
|
239
|
+
else:
|
|
240
|
+
result = subprocess.run(command, check=False, capture_output=True, text=True, **popen_kwargs)
|
|
241
|
+
|
|
242
|
+
stdout = (result.stdout or '').strip()
|
|
243
|
+
stderr = (result.stderr or '').strip()
|
|
244
|
+
if stdout:
|
|
245
|
+
logging.info(f"Hook '{name}' stdout: {stdout}")
|
|
246
|
+
try:
|
|
247
|
+
console.print(f"[cyan][hook:{name} stdout][/cyan]\n{stdout}")
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
if stderr:
|
|
251
|
+
logging.warning(f"Hook '{name}' stderr: {stderr}")
|
|
252
|
+
try:
|
|
253
|
+
console.print(f"[yellow][hook:{name} stderr][/yellow]\n{stderr}")
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
if result.returncode != 0:
|
|
258
|
+
message = f"Hook '{name}' exited with code {result.returncode}"
|
|
259
|
+
if continue_on_error:
|
|
260
|
+
logging.error(message + " (continuing)")
|
|
261
|
+
continue
|
|
262
|
+
else:
|
|
263
|
+
logging.error(message + " (stopping)")
|
|
264
|
+
raise SystemExit(result.returncode)
|
|
265
|
+
|
|
266
|
+
except subprocess.TimeoutExpired:
|
|
267
|
+
message = f"Hook '{name}' timed out"
|
|
268
|
+
if continue_on_error:
|
|
269
|
+
logging.error(message + " (continuing)")
|
|
270
|
+
continue
|
|
271
|
+
else:
|
|
272
|
+
logging.error(message + " (stopping)")
|
|
273
|
+
raise SystemExit(124)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
message = f"Hook '{name}' failed: {str(e)}"
|
|
276
|
+
if continue_on_error:
|
|
277
|
+
logging.error(message + " (continuing)")
|
|
278
|
+
continue
|
|
279
|
+
else:
|
|
280
|
+
logging.error(message + " (stopping)")
|
|
281
|
+
raise
|
|
157
282
|
|
|
158
283
|
|
|
159
284
|
def restart_script():
|
|
160
|
-
"""
|
|
285
|
+
"""Restart script with same command line arguments."""
|
|
161
286
|
print("\nRiavvio dello script...\n")
|
|
162
|
-
|
|
163
|
-
os.execv(python, [python] + sys.argv)
|
|
287
|
+
os.execv(sys.executable, [sys.executable] + sys.argv)
|
|
164
288
|
|
|
165
289
|
|
|
166
290
|
def force_exit():
|
|
167
|
-
"""
|
|
168
|
-
|
|
291
|
+
"""Force script termination in any context."""
|
|
169
292
|
print("\nChiusura dello script in corso...")
|
|
170
|
-
|
|
171
|
-
#
|
|
293
|
+
|
|
294
|
+
# Close all threads except main
|
|
172
295
|
for t in threading.enumerate():
|
|
173
296
|
if t is not threading.main_thread():
|
|
174
297
|
print(f"Chiusura thread: {t.name}")
|
|
175
298
|
t.join(timeout=1)
|
|
176
|
-
|
|
177
|
-
#
|
|
299
|
+
|
|
300
|
+
# Stop asyncio if active
|
|
178
301
|
try:
|
|
179
302
|
loop = asyncio.get_event_loop()
|
|
180
303
|
if loop.is_running():
|
|
@@ -182,146 +305,112 @@ def force_exit():
|
|
|
182
305
|
loop.stop()
|
|
183
306
|
except RuntimeError:
|
|
184
307
|
pass
|
|
185
|
-
|
|
186
|
-
#
|
|
308
|
+
|
|
309
|
+
# Exit gracefully or force
|
|
187
310
|
try:
|
|
188
311
|
print("Uscita con sys.exit(0)")
|
|
189
312
|
sys.exit(0)
|
|
190
313
|
except SystemExit:
|
|
191
314
|
pass
|
|
192
|
-
|
|
315
|
+
|
|
193
316
|
print("Uscita forzata con os._exit(0)")
|
|
194
317
|
os._exit(0)
|
|
195
318
|
|
|
196
319
|
|
|
197
|
-
def
|
|
198
|
-
"""
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
1
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if TELEGRAM_BOT:
|
|
219
|
-
bot = get_bot_instance()
|
|
220
|
-
bot.send_message(f"Avviato script {script_id}", None)
|
|
221
|
-
|
|
222
|
-
start = time.time()
|
|
223
|
-
|
|
224
|
-
# Create logger
|
|
225
|
-
log_not = Logger()
|
|
226
|
-
initialize()
|
|
227
|
-
|
|
228
|
-
# Get all site hostname
|
|
229
|
-
hostname_list = [hostname for site_info in config_manager.configSite.values() if (hostname := _extract_hostname(site_info.get('full_url')))]
|
|
230
|
-
|
|
231
|
-
if not BYPASS_DNS:
|
|
232
|
-
if not internet_manager.check_dns_resolve(hostname_list):
|
|
233
|
-
console.print("[red] ERROR: DNS configuration is required!")
|
|
234
|
-
console.print("[red]The program cannot function correctly without proper DNS settings.")
|
|
235
|
-
console.print("[yellow]Please configure one of these DNS servers:")
|
|
236
|
-
console.print("[red]• Cloudflare (1.1.1.1) 'https://developers.cloudflare.com/1.1.1.1/setup/windows/'")
|
|
237
|
-
console.print("[red]• Quad9 (9.9.9.9) 'https://docs.quad9.net/Setup_Guides/Windows/Windows_10/'")
|
|
238
|
-
console.print("\n[yellow]⚠️ The program will not work until you configure your DNS settings.")
|
|
239
|
-
|
|
240
|
-
os._exit(0)
|
|
320
|
+
def check_dns_and_exit_if_needed():
|
|
321
|
+
"""Check DNS configuration and exit if required."""
|
|
322
|
+
if BYPASS_DNS:
|
|
323
|
+
return
|
|
324
|
+
|
|
325
|
+
hostname_list = [
|
|
326
|
+
urlparse(site_info.get('full_url')).hostname
|
|
327
|
+
for site_info in config_manager.configSite.values()
|
|
328
|
+
if urlparse(site_info.get('full_url')).hostname
|
|
329
|
+
]
|
|
330
|
+
|
|
331
|
+
if not internet_manager.check_dns_resolve(hostname_list):
|
|
332
|
+
console.print("[red]\nERROR: DNS configuration is required!")
|
|
333
|
+
console.print("[red]The program cannot function correctly without proper DNS settings.")
|
|
334
|
+
console.print("\n[yellow]Please configure one of these DNS servers:")
|
|
335
|
+
console.print("[red]• Cloudflare (1.1.1.1) 'https://developers.cloudflare.com/1.1.1.1/setup/windows/'")
|
|
336
|
+
console.print("[red]• Quad9 (9.9.9.9) 'https://docs.quad9.net/Setup_Guides/Windows/Windows_10/'")
|
|
337
|
+
console.print("\n[yellow]-> The program will not work until you configure your DNS settings.")
|
|
338
|
+
sys.exit(0)
|
|
241
339
|
|
|
242
|
-
# Load search functions
|
|
243
|
-
search_functions = load_search_functions()
|
|
244
|
-
logging.info(f"Load module in: {time.time() - start} s")
|
|
245
340
|
|
|
246
|
-
|
|
341
|
+
def setup_argument_parser(search_functions):
|
|
342
|
+
"""Setup and return configured argument parser."""
|
|
343
|
+
# Build help text
|
|
344
|
+
module_info = {}
|
|
345
|
+
for alias, (_func, _use_for) in search_functions.items():
|
|
346
|
+
module_name = alias.split("_")[0].lower()
|
|
347
|
+
try:
|
|
348
|
+
mod = importlib.import_module(f'StreamingCommunity.Api.Site.{module_name}')
|
|
349
|
+
module_info[module_name] = int(getattr(mod, 'indice'))
|
|
350
|
+
except Exception:
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
available_names = ", ".join(sorted(module_info.keys()))
|
|
354
|
+
available_indices = ", ".join([f"{idx}={name.capitalize()}" for name, idx in sorted(module_info.items(), key=lambda x: x[1])])
|
|
355
|
+
|
|
247
356
|
parser = argparse.ArgumentParser(
|
|
248
|
-
description='Script to download movies and series from the internet.
|
|
357
|
+
description='Script to download movies and series from the internet.',
|
|
358
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
359
|
+
epilog=f"Available sites by name: {available_names}\nAvailable sites by index: {available_indices}"
|
|
249
360
|
)
|
|
250
|
-
|
|
361
|
+
|
|
362
|
+
# Add arguments
|
|
251
363
|
parser.add_argument("script_id", nargs="?", default="unknown", help="ID dello script")
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
parser.add_argument(
|
|
255
|
-
|
|
256
|
-
)
|
|
257
|
-
parser.add_argument(
|
|
258
|
-
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
# Add arguments for M3U8 configuration
|
|
262
|
-
parser.add_argument(
|
|
263
|
-
'--default_video_worker', type=int, help='Number of workers for video during M3U8 download (default: 12).'
|
|
264
|
-
)
|
|
265
|
-
parser.add_argument(
|
|
266
|
-
'--default_audio_worker', type=int, help='Number of workers for audio during M3U8 download (default: 12).'
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
# Add options for audio and subtitles
|
|
270
|
-
parser.add_argument(
|
|
271
|
-
'--specific_list_audio', type=str, help='Comma-separated list of specific audio languages to download (e.g., ita,eng).'
|
|
272
|
-
)
|
|
273
|
-
parser.add_argument(
|
|
274
|
-
'--specific_list_subtitles', type=str, help='Comma-separated list of specific subtitle languages to download (e.g., eng,spa).'
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
# Add global search option
|
|
278
|
-
parser.add_argument(
|
|
279
|
-
'--global', action='store_true', help='Perform a global search across multiple sites.'
|
|
280
|
-
)
|
|
281
|
-
|
|
282
|
-
# Add category selection argument
|
|
283
|
-
parser.add_argument(
|
|
284
|
-
'--category', type=int, help='Select category directly (1: anime, 2: film_&_serie, 3: serie, 4: torrent).'
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
# Add arguments for search functions
|
|
364
|
+
parser.add_argument('--add_siteName', type=bool, help='Enable/disable adding site name to file name')
|
|
365
|
+
parser.add_argument('--not_close', type=bool, help='Keep console open after execution')
|
|
366
|
+
parser.add_argument('--default_video_worker', type=int, help='Video workers for M3U8 download (default: 12)')
|
|
367
|
+
parser.add_argument('--default_audio_worker', type=int, help='Audio workers for M3U8 download (default: 12)')
|
|
368
|
+
parser.add_argument('--specific_list_audio', type=str, help='Audio languages (e.g., ita,eng)')
|
|
369
|
+
parser.add_argument('--specific_list_subtitles', type=str, help='Subtitle languages (e.g., eng,spa)')
|
|
370
|
+
parser.add_argument('--global', action='store_true', help='Global search across sites')
|
|
371
|
+
parser.add_argument('--category', type=int, help='Category (1: anime, 2: film_&_serie, 3: serie, 4: torrent)')
|
|
288
372
|
parser.add_argument('-s', '--search', default=None, help='Search terms')
|
|
373
|
+
parser.add_argument('--auto-first', action='store_true', help='Auto-download first result (use with --site and --search)')
|
|
374
|
+
parser.add_argument('--site', type=str, help='Site by name or index')
|
|
289
375
|
|
|
290
|
-
|
|
291
|
-
args = parser.parse_args()
|
|
376
|
+
return parser
|
|
292
377
|
|
|
293
|
-
search_terms = args.search
|
|
294
|
-
# Map command-line arguments to the config values
|
|
295
|
-
config_updates = {}
|
|
296
378
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
379
|
+
def apply_config_updates(args):
|
|
380
|
+
"""Apply command line arguments to configuration."""
|
|
381
|
+
config_updates = {}
|
|
382
|
+
|
|
383
|
+
arg_mappings = {
|
|
384
|
+
'add_siteName': 'DEFAULT.add_siteName',
|
|
385
|
+
'not_close': 'DEFAULT.not_close',
|
|
386
|
+
'default_video_worker': 'M3U8_DOWNLOAD.default_video_worker',
|
|
387
|
+
'default_audio_worker': 'M3U8_DOWNLOAD.default_audio_worker'
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for arg_name, config_key in arg_mappings.items():
|
|
391
|
+
if getattr(args, arg_name) is not None:
|
|
392
|
+
config_updates[config_key] = getattr(args, arg_name)
|
|
393
|
+
|
|
394
|
+
# Handle list arguments
|
|
305
395
|
if args.specific_list_audio is not None:
|
|
306
396
|
config_updates['M3U8_DOWNLOAD.specific_list_audio'] = args.specific_list_audio.split(',')
|
|
307
397
|
if args.specific_list_subtitles is not None:
|
|
308
398
|
config_updates['M3U8_DOWNLOAD.specific_list_subtitles'] = args.specific_list_subtitles.split(',')
|
|
309
|
-
|
|
310
|
-
# Apply
|
|
399
|
+
|
|
400
|
+
# Apply updates
|
|
311
401
|
for key, value in config_updates.items():
|
|
312
402
|
section, option = key.split('.')
|
|
313
403
|
config_manager.set_key(section, option, value)
|
|
404
|
+
|
|
405
|
+
if config_updates:
|
|
406
|
+
config_manager.save_config()
|
|
314
407
|
|
|
315
|
-
config_manager.save_config()
|
|
316
|
-
|
|
317
|
-
# Check if global search is requested
|
|
318
|
-
if getattr(args, 'global'):
|
|
319
|
-
global_search(search_terms)
|
|
320
|
-
return
|
|
321
408
|
|
|
322
|
-
|
|
409
|
+
def build_function_mappings(search_functions):
|
|
410
|
+
"""Build mappings between indices/names and functions."""
|
|
323
411
|
input_to_function = {}
|
|
324
412
|
choice_labels = {}
|
|
413
|
+
module_name_to_function = {}
|
|
325
414
|
|
|
326
415
|
for alias, (func, use_for) in search_functions.items():
|
|
327
416
|
module_name = alias.split("_")[0]
|
|
@@ -330,84 +419,131 @@ def main(script_id = 0):
|
|
|
330
419
|
site_index = str(getattr(mod, 'indice'))
|
|
331
420
|
input_to_function[site_index] = func
|
|
332
421
|
choice_labels[site_index] = (module_name.capitalize(), use_for.lower())
|
|
422
|
+
module_name_to_function[module_name.lower()] = func
|
|
333
423
|
except Exception as e:
|
|
334
424
|
console.print(f"[red]Error mapping module {module_name}: {str(e)}")
|
|
425
|
+
|
|
426
|
+
return input_to_function, choice_labels, module_name_to_function
|
|
335
427
|
|
|
336
|
-
if args.category:
|
|
337
|
-
selected_category = category_map.get(args.category)
|
|
338
|
-
category_sites = []
|
|
339
|
-
for key, label in choice_labels.items():
|
|
340
|
-
if label[1] == selected_category:
|
|
341
|
-
category_sites.append((key, label[0]))
|
|
342
428
|
|
|
429
|
+
def handle_direct_site_selection(args, input_to_function, module_name_to_function, search_terms):
|
|
430
|
+
"""Handle direct site selection via command line."""
|
|
431
|
+
if not args.site:
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
site_key = str(args.site).strip().lower()
|
|
435
|
+
func_to_run = input_to_function.get(site_key) or module_name_to_function.get(site_key)
|
|
436
|
+
|
|
437
|
+
if func_to_run is None:
|
|
438
|
+
available_sites = ", ".join(sorted(module_name_to_function.keys()))
|
|
439
|
+
console.print(f"[red]Unknown site:[/red] '{args.site}'. Available: [yellow]{available_sites}[/yellow]")
|
|
440
|
+
return False
|
|
441
|
+
|
|
442
|
+
# Handle auto-first option
|
|
443
|
+
if args.auto_first and search_terms:
|
|
444
|
+
try:
|
|
445
|
+
database = func_to_run(search_terms, get_onlyDatabase=True)
|
|
446
|
+
if database and hasattr(database, 'media_list') and database.media_list:
|
|
447
|
+
first_item = database.media_list[0]
|
|
448
|
+
item_dict = first_item.__dict__.copy() if hasattr(first_item, '__dict__') else {}
|
|
449
|
+
func_to_run(direct_item=item_dict)
|
|
450
|
+
return True
|
|
451
|
+
else:
|
|
452
|
+
console.print("[yellow]No results found. Falling back to interactive mode.[/yellow]")
|
|
453
|
+
except Exception as e:
|
|
454
|
+
console.print(f"[red]Auto-first failed:[/red] {str(e)}")
|
|
455
|
+
|
|
456
|
+
run_function(func_to_run, search_terms=search_terms)
|
|
457
|
+
return True
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def get_user_site_selection(args, choice_labels):
|
|
461
|
+
"""Get site selection from user (interactive or category-based)."""
|
|
462
|
+
bot = get_bot_instance() if TELEGRAM_BOT else None
|
|
463
|
+
|
|
464
|
+
if args.category:
|
|
465
|
+
selected_category = CATEGORY_MAP.get(args.category)
|
|
466
|
+
category_sites = [(key, label[0]) for key, label in choice_labels.items() if label[1] == selected_category]
|
|
467
|
+
|
|
343
468
|
if len(category_sites) == 1:
|
|
344
|
-
category = category_sites[0][0]
|
|
345
469
|
console.print(f"[green]Selezionato automaticamente: {category_sites[0][1]}[/green]")
|
|
346
|
-
|
|
470
|
+
return category_sites[0][0]
|
|
471
|
+
|
|
472
|
+
# Multiple sites in category
|
|
473
|
+
color = COLOR_MAP.get(selected_category, 'white')
|
|
474
|
+
prompt_items = [f"[{color}]({k}) {v}[/{color}]" for k, v in category_sites]
|
|
475
|
+
prompt_line = ", ".join(prompt_items)
|
|
476
|
+
|
|
477
|
+
if TELEGRAM_BOT:
|
|
478
|
+
console.print(f"\nInsert site: {prompt_line}")
|
|
479
|
+
return bot.ask("select_site", f"Insert site: {prompt_line}", None)
|
|
347
480
|
else:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
sito_prompt_line = ", ".join(sito_prompt_items)
|
|
351
|
-
|
|
352
|
-
if TELEGRAM_BOT:
|
|
353
|
-
console.print(f"\nInsert site: {sito_prompt_line}")
|
|
354
|
-
category = bot.ask(
|
|
355
|
-
"select_site",
|
|
356
|
-
f"Insert site: {sito_prompt_line}",
|
|
357
|
-
None
|
|
358
|
-
)
|
|
359
|
-
else:
|
|
360
|
-
category = msg.ask(f"\n[cyan]Insert site: {sito_prompt_line}", choices=[k for k, _ in category_sites], show_choices=False)
|
|
361
|
-
|
|
481
|
+
return msg.ask(f"\n[cyan]Insert site: {prompt_line}", choices=[k for k, _ in category_sites], show_choices=False)
|
|
482
|
+
|
|
362
483
|
else:
|
|
363
|
-
|
|
484
|
+
# Show all sites
|
|
485
|
+
legend_text = " | ".join([f"[{color}]{cat.capitalize()}[/{color}]" for cat, color in COLOR_MAP.items()])
|
|
364
486
|
console.print(f"\n[bold cyan]Category Legend:[/bold cyan] {legend_text}")
|
|
365
|
-
|
|
366
|
-
prompt_message = "[cyan]Insert site: " + ", ".join(
|
|
367
|
-
[f"[{color_map.get(label[1], 'white')}]({key}) {label[0]}[/{color_map.get(label[1], 'white')}]"
|
|
368
|
-
for key, label in choice_labels.items()]
|
|
369
|
-
)
|
|
370
|
-
|
|
487
|
+
|
|
371
488
|
if TELEGRAM_BOT:
|
|
372
|
-
|
|
373
|
-
|
|
489
|
+
category_legend = "Categorie: \n" + " | ".join([cat.capitalize() for cat in COLOR_MAP.keys()])
|
|
490
|
+
prompt_message = "Inserisci il sito:\n" + "\n".join([f"{key}: {label[0]}" for key, label in choice_labels.items()])
|
|
491
|
+
console.print(f"\n{prompt_message}")
|
|
492
|
+
return bot.ask("select_provider", f"{category_legend}\n\n{prompt_message}", None)
|
|
493
|
+
else:
|
|
494
|
+
prompt_message = "[cyan]Insert site: " + ", ".join([
|
|
495
|
+
f"[{COLOR_MAP.get(label[1], 'white')}]({key}) {label[0]}[/{COLOR_MAP.get(label[1], 'white')}]"
|
|
496
|
+
for key, label in choice_labels.items()
|
|
374
497
|
])
|
|
498
|
+
return msg.ask(prompt_message, choices=list(choice_labels.keys()), default="0", show_choices=False, show_default=False)
|
|
375
499
|
|
|
376
|
-
prompt_message_telegram = "Inserisci il sito:\n" + "\n".join(
|
|
377
|
-
[f"{key}: {label[0]}" for key, label in choice_labels.items()]
|
|
378
|
-
)
|
|
379
500
|
|
|
380
|
-
|
|
501
|
+
def main(script_id=0):
|
|
502
|
+
if TELEGRAM_BOT:
|
|
503
|
+
get_bot_instance().send_message(f"Avviato script {script_id}", None)
|
|
381
504
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
)
|
|
505
|
+
start = time.time()
|
|
506
|
+
Logger()
|
|
507
|
+
execute_hooks('pre_run')
|
|
508
|
+
initialize()
|
|
387
509
|
|
|
388
|
-
|
|
389
|
-
|
|
510
|
+
try:
|
|
511
|
+
check_dns_and_exit_if_needed()
|
|
390
512
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
run_function(input_to_function[category], search_terms=search_terms)
|
|
394
|
-
|
|
395
|
-
else:
|
|
396
|
-
if TELEGRAM_BOT:
|
|
397
|
-
bot.send_message(f"Categoria non valida", None)
|
|
513
|
+
search_functions = load_search_functions()
|
|
514
|
+
logging.info(f"Load module in: {time.time() - start} s")
|
|
398
515
|
|
|
399
|
-
|
|
516
|
+
parser = setup_argument_parser(search_functions)
|
|
517
|
+
args = parser.parse_args()
|
|
400
518
|
|
|
401
|
-
|
|
402
|
-
restart_script()
|
|
519
|
+
apply_config_updates(args)
|
|
403
520
|
|
|
404
|
-
|
|
405
|
-
|
|
521
|
+
if getattr(args, 'global'):
|
|
522
|
+
global_search(args.search)
|
|
523
|
+
return
|
|
406
524
|
|
|
525
|
+
input_to_function, choice_labels, module_name_to_function = build_function_mappings(search_functions)
|
|
526
|
+
|
|
527
|
+
if handle_direct_site_selection(args, input_to_function, module_name_to_function, args.search):
|
|
528
|
+
return
|
|
529
|
+
|
|
530
|
+
category = get_user_site_selection(args, choice_labels)
|
|
531
|
+
|
|
532
|
+
if category in input_to_function:
|
|
533
|
+
run_function(input_to_function[category], search_terms=args.search)
|
|
534
|
+
else:
|
|
407
535
|
if TELEGRAM_BOT:
|
|
408
|
-
|
|
536
|
+
get_bot_instance().send_message("Categoria non valida", None)
|
|
537
|
+
console.print("[red]Invalid category.")
|
|
409
538
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
539
|
+
if getattr(args, 'not_close'):
|
|
540
|
+
restart_script()
|
|
541
|
+
else:
|
|
542
|
+
force_exit()
|
|
543
|
+
if TELEGRAM_BOT:
|
|
544
|
+
get_bot_instance().send_message("Chiusura in corso", None)
|
|
545
|
+
script_id = TelegramSession.get_session()
|
|
546
|
+
if script_id != "unknown":
|
|
547
|
+
TelegramSession.deleteScriptId(script_id)
|
|
548
|
+
finally:
|
|
549
|
+
execute_hooks('post_run')
|