weeb-cli 2.17.0__tar.gz → 3.0.0__tar.gz
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.
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/PKG-INFO +1 -11
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/pyproject.toml +1 -12
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/__init__.py +1 -1
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/serve.py +0 -8
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/serve_restful.py +0 -8
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/config.py +0 -1
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/constants.py +0 -1
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/main.py +0 -8
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/base.py +30 -58
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/sdk.py +0 -5
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/cache.py +5 -18
- weeb_cli-3.0.0/weeb_cli/services/download/manager.py +69 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/error_handler.py +2 -13
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/logger.py +6 -19
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/scraper.py +0 -5
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli.egg-info/PKG-INFO +1 -11
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli.egg-info/SOURCES.txt +0 -1
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli.egg-info/requires.txt +0 -11
- weeb_cli-2.17.0/weeb_cli/services/download/manager.py +0 -89
- weeb_cli-2.17.0/weeb_cli/services/telemetry.py +0 -417
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/LICENSE +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/README.md +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/setup.cfg +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_anilist_tracker.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_aniworld_provider.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_api.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_cache.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_exceptions.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_kitsu_tracker.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_mal_tracker.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_providers.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_restful_api.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_sanitizer.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_sanitizer_security.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/tests/test_sdk.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/__main__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/api.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/downloads.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/library.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search/anime_details.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search/download_flow.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search/episode_utils.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search/search_handlers.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search/stream_utils.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search/watch_flow.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/search.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_backup.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_cache.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_config.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_download.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_drives.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_menu.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_shortcuts.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings/settings_trackers.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/settings.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/setup.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/commands/watchlist.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/exceptions.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/i18n.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/locales/de.json +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/locales/en.json +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/locales/pl.json +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/locales/tr.json +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/de/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/de/aniworld.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/en/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/en/allanime.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/en/hianime.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/extractors/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/extractors/doodstream.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/extractors/filemoon.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/extractors/megacloud.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/extractors/streamtape.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/extractors/vidoza.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/extractors/voe.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/pl/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/pl/docchi.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/registry.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/tr/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/tr/animecix.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/tr/anizle.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/tr/turkanime.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/providers/tr/weeb.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/_base.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/_tracker_base.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/aniskip.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/database.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/dependency_manager.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/details.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/discord_rpc.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/context.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/queue.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/strategies/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/strategies/aria2.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/strategies/base.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/strategies/ffmpeg.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/strategies/generic.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/download/strategies/ytdlp.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/downloader.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/headless_downloader.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/local_library.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/notifier.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/player.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/progress.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/search.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/shortcuts.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/stream_validator.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/tracker.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/updater.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/services/watch.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/templates/anilist_error.html +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/templates/anilist_success.html +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/templates/mal_error.html +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/templates/mal_success.html +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/ui/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/ui/header.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/ui/menu.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/ui/prompt.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/utils/__init__.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/utils/decorators.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/utils/sanitizer.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli/utils/shortcuts.py +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli.egg-info/dependency_links.txt +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli.egg-info/entry_points.txt +0 -0
- {weeb_cli-2.17.0 → weeb_cli-3.0.0}/weeb_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weeb-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: No browser, no ads, no distractions. Just you and an unparalleled anime viewing experience.
|
|
5
5
|
Author-email: ewgsta <ewgst@proton.me>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -51,16 +51,6 @@ Requires-Dist: mkdocs-material>=9.7.6; extra == "docs"
|
|
|
51
51
|
Requires-Dist: mkdocstrings[python]>=1.0.4; extra == "docs"
|
|
52
52
|
Requires-Dist: pymdown-extensions>=10.21.2; extra == "docs"
|
|
53
53
|
Requires-Dist: mkdocs-static-i18n>=1.3.1; extra == "docs"
|
|
54
|
-
Provides-Extra: telemetry
|
|
55
|
-
Requires-Dist: opentelemetry-api<2.0,>=1.25.0; extra == "telemetry"
|
|
56
|
-
Requires-Dist: opentelemetry-sdk<2.0,>=1.25.0; extra == "telemetry"
|
|
57
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2.0,>=1.25.0; extra == "telemetry"
|
|
58
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2.0,>=1.25.0; extra == "telemetry"
|
|
59
|
-
Requires-Dist: opentelemetry-instrumentation-requests<1.0,>=0.46b0; extra == "telemetry"
|
|
60
|
-
Requires-Dist: opentelemetry-instrumentation-urllib3<1.0,>=0.46b0; extra == "telemetry"
|
|
61
|
-
Requires-Dist: opentelemetry-instrumentation-sqlite3<1.0,>=0.46b0; extra == "telemetry"
|
|
62
|
-
Requires-Dist: opentelemetry-instrumentation-flask<1.0,>=0.46b0; extra == "telemetry"
|
|
63
|
-
Requires-Dist: opentelemetry-instrumentation-logging<1.0,>=0.46b0; extra == "telemetry"
|
|
64
54
|
Dynamic: license-file
|
|
65
55
|
|
|
66
56
|
<p align="center">
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "weeb-cli"
|
|
7
|
-
version = "
|
|
7
|
+
version = "3.0.0"
|
|
8
8
|
description = "No browser, no ads, no distractions. Just you and an unparalleled anime viewing experience."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "ewgsta", email = "ewgst@proton.me" }]
|
|
@@ -40,17 +40,6 @@ serve-restful = ["flask>=3.0", "flask-cors>=4.0"]
|
|
|
40
40
|
shortcuts = ["pywin32; sys_platform == 'win32'", "winshell; sys_platform == 'win32'"]
|
|
41
41
|
dev = ["build", "pyinstaller", "pytest>=7.0.0", "pytest-cov>=7.1.0", "pytest-asyncio>=1.3.0", "pytest-mock>=3.15.1"]
|
|
42
42
|
docs = ["mkdocs>=1.6.1", "mkdocs-material>=9.7.6", "mkdocstrings[python]>=1.0.4", "pymdown-extensions>=10.21.2", "mkdocs-static-i18n>=1.3.1"]
|
|
43
|
-
telemetry = [
|
|
44
|
-
"opentelemetry-api>=1.25.0,<2.0",
|
|
45
|
-
"opentelemetry-sdk>=1.25.0,<2.0",
|
|
46
|
-
"opentelemetry-exporter-otlp-proto-http>=1.25.0,<2.0",
|
|
47
|
-
"opentelemetry-exporter-otlp-proto-grpc>=1.25.0,<2.0",
|
|
48
|
-
"opentelemetry-instrumentation-requests>=0.46b0,<1.0",
|
|
49
|
-
"opentelemetry-instrumentation-urllib3>=0.46b0,<1.0",
|
|
50
|
-
"opentelemetry-instrumentation-sqlite3>=0.46b0,<1.0",
|
|
51
|
-
"opentelemetry-instrumentation-flask>=0.46b0,<1.0",
|
|
52
|
-
"opentelemetry-instrumentation-logging>=0.46b0,<1.0",
|
|
53
|
-
]
|
|
54
43
|
|
|
55
44
|
[tool.setuptools.packages.find]
|
|
56
45
|
where = ["."]
|
|
@@ -259,10 +259,6 @@ def serve_torznab(
|
|
|
259
259
|
from weeb_cli.config import config as weeb_config
|
|
260
260
|
weeb_config.set_headless(True)
|
|
261
261
|
|
|
262
|
-
# Initialize telemetry
|
|
263
|
-
from weeb_cli.services.telemetry import init_telemetry
|
|
264
|
-
init_telemetry(environment="server-torznab")
|
|
265
|
-
|
|
266
262
|
logging.basicConfig(
|
|
267
263
|
level=logging.INFO,
|
|
268
264
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
@@ -304,10 +300,6 @@ def serve_torznab(
|
|
|
304
300
|
# Flask app
|
|
305
301
|
flask_app = Flask(__name__)
|
|
306
302
|
|
|
307
|
-
# Instrument Flask app for telemetry
|
|
308
|
-
from weeb_cli.services.telemetry import instrument_flask_app
|
|
309
|
-
instrument_flask_app(flask_app)
|
|
310
|
-
|
|
311
303
|
@flask_app.route("/api", methods=["GET"])
|
|
312
304
|
def torznab_api():
|
|
313
305
|
t = request.args.get("t", "")
|
|
@@ -102,10 +102,6 @@ def serve_restful(
|
|
|
102
102
|
from weeb_cli.config import config as weeb_config
|
|
103
103
|
weeb_config.set_headless(True)
|
|
104
104
|
|
|
105
|
-
# Initialize telemetry
|
|
106
|
-
from weeb_cli.services.telemetry import init_telemetry
|
|
107
|
-
init_telemetry(environment="server-restful")
|
|
108
|
-
|
|
109
105
|
# Setup logging
|
|
110
106
|
logging.basicConfig(
|
|
111
107
|
level=logging.DEBUG if debug else logging.INFO,
|
|
@@ -116,10 +112,6 @@ def serve_restful(
|
|
|
116
112
|
|
|
117
113
|
# Create Flask app
|
|
118
114
|
flask_app = Flask(__name__)
|
|
119
|
-
|
|
120
|
-
# Instrument Flask app for telemetry
|
|
121
|
-
from weeb_cli.services.telemetry import instrument_flask_app
|
|
122
|
-
instrument_flask_app(flask_app)
|
|
123
115
|
|
|
124
116
|
if enable_cors:
|
|
125
117
|
try:
|
|
@@ -62,12 +62,6 @@ def check_ffmpeg_silent():
|
|
|
62
62
|
|
|
63
63
|
@app.command()
|
|
64
64
|
def start():
|
|
65
|
-
from weeb_cli.services.telemetry import init_telemetry
|
|
66
|
-
init_telemetry(environment="cli")
|
|
67
|
-
|
|
68
|
-
from weeb_cli.services.logger import reload as reload_logger
|
|
69
|
-
reload_logger()
|
|
70
|
-
|
|
71
65
|
if not config.get("language"):
|
|
72
66
|
run_setup()
|
|
73
67
|
|
|
@@ -99,8 +93,6 @@ def start():
|
|
|
99
93
|
finally:
|
|
100
94
|
from weeb_cli.services.discord_rpc import discord_rpc
|
|
101
95
|
discord_rpc.disconnect()
|
|
102
|
-
from weeb_cli.services.telemetry import shutdown_telemetry
|
|
103
|
-
shutdown_telemetry()
|
|
104
96
|
|
|
105
97
|
def check_incomplete_downloads():
|
|
106
98
|
from weeb_cli.services.downloader import queue_manager
|
|
@@ -253,61 +253,33 @@ class BaseProvider(ABC):
|
|
|
253
253
|
import requests
|
|
254
254
|
import time
|
|
255
255
|
import random
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return response.json()
|
|
287
|
-
return response.text
|
|
288
|
-
|
|
289
|
-
except requests.RequestException as e:
|
|
290
|
-
debug(f"[HTTP] Request failed (attempt {attempt + 1}/{max_retries}): {url} - {e}")
|
|
291
|
-
|
|
292
|
-
if attempt < max_retries - 1:
|
|
293
|
-
# Skip retries for permanent errors
|
|
294
|
-
if isinstance(e, requests.HTTPError) and hasattr(e, 'response') and e.response is not None and e.response.status_code in [404, 403, 401]:
|
|
295
|
-
debug(f"[HTTP] Permanent error, skipping retries")
|
|
296
|
-
span.set_attribute("weeb.request.attempts", attempt + 1)
|
|
297
|
-
metrics.provider_requests.add(1, {
|
|
298
|
-
"weeb.provider.name": getattr(self, "name", "unknown"),
|
|
299
|
-
"weeb.request.status": "permanent_error",
|
|
300
|
-
})
|
|
301
|
-
return None
|
|
302
|
-
|
|
303
|
-
# Exponential backoff with jitter
|
|
304
|
-
delay = min(2 ** attempt, 10) + random.uniform(0, 1)
|
|
305
|
-
time.sleep(delay)
|
|
306
|
-
continue
|
|
307
|
-
|
|
308
|
-
record_exception(span, e)
|
|
309
|
-
metrics.provider_requests.add(1, {
|
|
310
|
-
"weeb.provider.name": getattr(self, "name", "unknown"),
|
|
311
|
-
"weeb.request.status": "failed",
|
|
312
|
-
})
|
|
313
|
-
return None
|
|
256
|
+
|
|
257
|
+
for attempt in range(max_retries):
|
|
258
|
+
try:
|
|
259
|
+
response = requests.get(
|
|
260
|
+
url,
|
|
261
|
+
headers=self.headers,
|
|
262
|
+
params=params,
|
|
263
|
+
timeout=15
|
|
264
|
+
)
|
|
265
|
+
response.raise_for_status()
|
|
266
|
+
|
|
267
|
+
if json_response:
|
|
268
|
+
return response.json()
|
|
269
|
+
return response.text
|
|
270
|
+
|
|
271
|
+
except requests.RequestException as e:
|
|
272
|
+
debug(f"[HTTP] Request failed (attempt {attempt + 1}/{max_retries}): {url} - {e}")
|
|
273
|
+
|
|
274
|
+
if attempt < max_retries - 1:
|
|
275
|
+
# Skip retries for permanent errors
|
|
276
|
+
if isinstance(e, requests.HTTPError) and hasattr(e, 'response') and e.response is not None and e.response.status_code in [404, 403, 401]:
|
|
277
|
+
debug(f"[HTTP] Permanent error, skipping retries")
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
# Exponential backoff with jitter
|
|
281
|
+
delay = min(2 ** attempt, 10) + random.uniform(0, 1)
|
|
282
|
+
time.sleep(delay)
|
|
283
|
+
continue
|
|
284
|
+
|
|
285
|
+
return None
|
|
@@ -55,7 +55,6 @@ Functions:
|
|
|
55
55
|
get_provider_info: Get metadata for a specific provider
|
|
56
56
|
"""
|
|
57
57
|
|
|
58
|
-
import os
|
|
59
58
|
from typing import List, Optional, Dict, Any
|
|
60
59
|
from pathlib import Path
|
|
61
60
|
|
|
@@ -105,10 +104,6 @@ class WeebSDK:
|
|
|
105
104
|
if headless:
|
|
106
105
|
config.set_headless(True)
|
|
107
106
|
debug("[SDK] Initialized in headless mode")
|
|
108
|
-
|
|
109
|
-
# Initialize telemetry if enabled in config
|
|
110
|
-
from weeb_cli.services.telemetry import init_telemetry
|
|
111
|
-
init_telemetry(environment="sdk")
|
|
112
107
|
|
|
113
108
|
def list_providers(self) -> List[Dict[str, Any]]:
|
|
114
109
|
"""List all available anime providers.
|
|
@@ -70,24 +70,14 @@ class CacheManager:
|
|
|
70
70
|
|
|
71
71
|
def _get_cache_key(self, key: str) -> str:
|
|
72
72
|
"""Generate SHA256 hash for cache key.
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
Args:
|
|
75
75
|
key: Original cache key.
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
Returns:
|
|
78
78
|
Hexadecimal hash string.
|
|
79
79
|
"""
|
|
80
80
|
return hashlib.sha256(key.encode()).hexdigest()
|
|
81
|
-
|
|
82
|
-
def _record_cache_metric(self, result: str, tier: str) -> None:
|
|
83
|
-
try:
|
|
84
|
-
from weeb_cli.services.telemetry import get_metrics
|
|
85
|
-
get_metrics().cache_operations.add(1, {
|
|
86
|
-
"weeb.cache.result": result,
|
|
87
|
-
"weeb.cache.tier": tier,
|
|
88
|
-
})
|
|
89
|
-
except Exception:
|
|
90
|
-
pass
|
|
91
81
|
|
|
92
82
|
def get(self, key: str, max_age: int = 3600) -> Optional[Any]:
|
|
93
83
|
"""Retrieve cached value if not expired.
|
|
@@ -110,15 +100,14 @@ class CacheManager:
|
|
|
110
100
|
if key in self._memory_cache:
|
|
111
101
|
value, timestamp = self._memory_cache[key]
|
|
112
102
|
if time.time() - timestamp < max_age:
|
|
113
|
-
self._record_cache_metric("hit", "memory")
|
|
114
103
|
return value
|
|
115
104
|
else:
|
|
116
105
|
del self._memory_cache[key]
|
|
117
|
-
|
|
106
|
+
|
|
118
107
|
# Check file cache
|
|
119
108
|
cache_key = self._get_cache_key(key)
|
|
120
109
|
cache_file = self.cache_dir / f"{cache_key}.cache"
|
|
121
|
-
|
|
110
|
+
|
|
122
111
|
if cache_file.exists():
|
|
123
112
|
age = time.time() - cache_file.stat().st_mtime
|
|
124
113
|
if age < max_age:
|
|
@@ -126,12 +115,10 @@ class CacheManager:
|
|
|
126
115
|
with open(cache_file, 'r', encoding='utf-8') as f:
|
|
127
116
|
value = json.load(f)
|
|
128
117
|
self._memory_cache[key] = (value, time.time())
|
|
129
|
-
self._record_cache_metric("hit", "file")
|
|
130
118
|
return value
|
|
131
119
|
except (json.JSONDecodeError, UnicodeDecodeError, OSError):
|
|
132
120
|
cache_file.unlink(missing_ok=True)
|
|
133
|
-
|
|
134
|
-
self._record_cache_metric("miss", "none")
|
|
121
|
+
|
|
135
122
|
return None
|
|
136
123
|
|
|
137
124
|
def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Download manager with strategy pattern."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from weeb_cli.services.download.context import DownloadContext
|
|
7
|
+
from weeb_cli.services.download.strategies import (
|
|
8
|
+
DownloadStrategy,
|
|
9
|
+
Aria2Strategy,
|
|
10
|
+
YtdlpStrategy,
|
|
11
|
+
FFmpegStrategy,
|
|
12
|
+
GenericStrategy
|
|
13
|
+
)
|
|
14
|
+
from weeb_cli.services.logger import debug
|
|
15
|
+
from weeb_cli.exceptions import DownloadError
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DownloadManager:
|
|
19
|
+
"""Manages download operations using multiple strategies.
|
|
20
|
+
|
|
21
|
+
Tries strategies in priority order until one succeeds.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.strategies: List[DownloadStrategy] = [
|
|
26
|
+
Aria2Strategy(),
|
|
27
|
+
YtdlpStrategy(),
|
|
28
|
+
FFmpegStrategy(),
|
|
29
|
+
GenericStrategy()
|
|
30
|
+
]
|
|
31
|
+
self.strategies.sort(key=lambda s: s.get_priority())
|
|
32
|
+
|
|
33
|
+
def download(self, url: str, output_path: str, item: dict = None) -> None:
|
|
34
|
+
"""Download a file using the best available strategy.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
url: Stream URL to download.
|
|
38
|
+
output_path: Path where file should be saved.
|
|
39
|
+
item: Optional queue item metadata for progress tracking.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
DownloadError: If all strategies fail.
|
|
43
|
+
"""
|
|
44
|
+
context = DownloadContext(
|
|
45
|
+
url=url,
|
|
46
|
+
output_path=output_path,
|
|
47
|
+
item=item
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
last_error = None
|
|
51
|
+
|
|
52
|
+
for strategy in self.strategies:
|
|
53
|
+
if not strategy.can_handle(url):
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
strategy_name = strategy.__class__.__name__
|
|
57
|
+
debug(f"[DownloadManager] Trying {strategy_name} for {url[:80]}...")
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
strategy.download(context)
|
|
61
|
+
debug(f"[DownloadManager] Success with {strategy_name}")
|
|
62
|
+
return
|
|
63
|
+
except Exception as e:
|
|
64
|
+
last_error = e
|
|
65
|
+
debug(f"[DownloadManager] {strategy_name} failed: {e}")
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
error_msg = f"All download strategies failed. Last error: {last_error}"
|
|
69
|
+
raise DownloadError(error_msg, code="ALL_STRATEGIES_FAILED")
|
|
@@ -9,22 +9,11 @@ console = Console()
|
|
|
9
9
|
|
|
10
10
|
def handle_error(e: Exception, context: str = "", user_message: Optional[str] = None) -> None:
|
|
11
11
|
is_debug = config.get("debug_mode", False)
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
if is_debug:
|
|
14
14
|
log_error(f"[{context}] {type(e).__name__}: {str(e)}")
|
|
15
15
|
log_error(traceback.format_exc())
|
|
16
|
-
|
|
17
|
-
try:
|
|
18
|
-
from opentelemetry import trace as _trace
|
|
19
|
-
span = _trace.get_current_span()
|
|
20
|
-
if span.is_recording():
|
|
21
|
-
from weeb_cli.services.telemetry import record_exception
|
|
22
|
-
record_exception(span, e)
|
|
23
|
-
if context:
|
|
24
|
-
span.set_attribute("weeb.error.context", context)
|
|
25
|
-
except (ImportError, Exception):
|
|
26
|
-
pass
|
|
27
|
-
|
|
16
|
+
|
|
28
17
|
if user_message:
|
|
29
18
|
console.print(f"[red]{user_message}[/red]")
|
|
30
19
|
else:
|
|
@@ -16,21 +16,21 @@ def _setup_logger():
|
|
|
16
16
|
logger = logging.getLogger("weeb-cli")
|
|
17
17
|
logger.setLevel(logging.DEBUG)
|
|
18
18
|
logger.handlers = []
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
if config.get("debug_mode", False):
|
|
21
21
|
log_dir = Path.home() / ".weeb-cli" / "logs"
|
|
22
22
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
try:
|
|
25
25
|
_clean_old_logs(log_dir)
|
|
26
26
|
except OSError:
|
|
27
27
|
pass
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
log_file = log_dir / f"debug_{datetime.now().strftime('%Y%m%d')}.log"
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
handler = logging.FileHandler(log_file, encoding='utf-8')
|
|
32
32
|
handler.setLevel(logging.DEBUG)
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
formatter = logging.Formatter(
|
|
35
35
|
'%(asctime)s | %(levelname)s | %(module)s:%(lineno)d | %(message)s',
|
|
36
36
|
datefmt='%H:%M:%S'
|
|
@@ -39,20 +39,7 @@ def _setup_logger():
|
|
|
39
39
|
logger.addHandler(handler)
|
|
40
40
|
else:
|
|
41
41
|
logger.addHandler(logging.NullHandler())
|
|
42
|
-
|
|
43
|
-
try:
|
|
44
|
-
from weeb_cli.services.telemetry import is_enabled
|
|
45
|
-
if is_enabled():
|
|
46
|
-
from opentelemetry._logs import get_logger_provider
|
|
47
|
-
from opentelemetry.sdk._logs import LoggingHandler
|
|
48
|
-
otel_handler = LoggingHandler(
|
|
49
|
-
level=logging.DEBUG,
|
|
50
|
-
logger_provider=get_logger_provider(),
|
|
51
|
-
)
|
|
52
|
-
logger.addHandler(otel_handler)
|
|
53
|
-
except (ImportError, Exception):
|
|
54
|
-
pass
|
|
55
|
-
|
|
42
|
+
|
|
56
43
|
return logger
|
|
57
44
|
|
|
58
45
|
def debug(msg, *args):
|
|
@@ -4,7 +4,6 @@ from weeb_cli.providers import get_provider, get_default_provider, list_provider
|
|
|
4
4
|
from weeb_cli.providers.base import AnimeResult, AnimeDetails, Episode, StreamLink
|
|
5
5
|
from weeb_cli.exceptions import ProviderError
|
|
6
6
|
from weeb_cli.services.error_handler import handle_provider_error
|
|
7
|
-
from weeb_cli.services.telemetry import traced
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class Scraper:
|
|
@@ -31,7 +30,6 @@ class Scraper:
|
|
|
31
30
|
|
|
32
31
|
return self._provider
|
|
33
32
|
|
|
34
|
-
@traced(name="scraper.search")
|
|
35
33
|
def search(self, query: str) -> List[AnimeResult]:
|
|
36
34
|
self.last_error = None
|
|
37
35
|
if not self.provider:
|
|
@@ -47,7 +45,6 @@ class Scraper:
|
|
|
47
45
|
handle_provider_error(e, self._provider_name)
|
|
48
46
|
return []
|
|
49
47
|
|
|
50
|
-
@traced(name="scraper.get_details")
|
|
51
48
|
def get_details(self, anime_id: str) -> Optional[AnimeDetails]:
|
|
52
49
|
self.last_error = None
|
|
53
50
|
if not self.provider:
|
|
@@ -63,7 +60,6 @@ class Scraper:
|
|
|
63
60
|
handle_provider_error(e, self._provider_name)
|
|
64
61
|
return None
|
|
65
62
|
|
|
66
|
-
@traced(name="scraper.get_episodes")
|
|
67
63
|
def get_episodes(self, anime_id: str) -> List[Episode]:
|
|
68
64
|
self.last_error = None
|
|
69
65
|
if not self.provider:
|
|
@@ -79,7 +75,6 @@ class Scraper:
|
|
|
79
75
|
handle_provider_error(e, self._provider_name)
|
|
80
76
|
return []
|
|
81
77
|
|
|
82
|
-
@traced(name="scraper.get_streams")
|
|
83
78
|
def get_streams(self, anime_id: str, episode_id: str) -> List[StreamLink]:
|
|
84
79
|
from weeb_cli.services.logger import debug
|
|
85
80
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: weeb-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: No browser, no ads, no distractions. Just you and an unparalleled anime viewing experience.
|
|
5
5
|
Author-email: ewgsta <ewgst@proton.me>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -51,16 +51,6 @@ Requires-Dist: mkdocs-material>=9.7.6; extra == "docs"
|
|
|
51
51
|
Requires-Dist: mkdocstrings[python]>=1.0.4; extra == "docs"
|
|
52
52
|
Requires-Dist: pymdown-extensions>=10.21.2; extra == "docs"
|
|
53
53
|
Requires-Dist: mkdocs-static-i18n>=1.3.1; extra == "docs"
|
|
54
|
-
Provides-Extra: telemetry
|
|
55
|
-
Requires-Dist: opentelemetry-api<2.0,>=1.25.0; extra == "telemetry"
|
|
56
|
-
Requires-Dist: opentelemetry-sdk<2.0,>=1.25.0; extra == "telemetry"
|
|
57
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2.0,>=1.25.0; extra == "telemetry"
|
|
58
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2.0,>=1.25.0; extra == "telemetry"
|
|
59
|
-
Requires-Dist: opentelemetry-instrumentation-requests<1.0,>=0.46b0; extra == "telemetry"
|
|
60
|
-
Requires-Dist: opentelemetry-instrumentation-urllib3<1.0,>=0.46b0; extra == "telemetry"
|
|
61
|
-
Requires-Dist: opentelemetry-instrumentation-sqlite3<1.0,>=0.46b0; extra == "telemetry"
|
|
62
|
-
Requires-Dist: opentelemetry-instrumentation-flask<1.0,>=0.46b0; extra == "telemetry"
|
|
63
|
-
Requires-Dist: opentelemetry-instrumentation-logging<1.0,>=0.46b0; extra == "telemetry"
|
|
64
54
|
Dynamic: license-file
|
|
65
55
|
|
|
66
56
|
<p align="center">
|
|
@@ -100,7 +100,6 @@ weeb_cli/services/scraper.py
|
|
|
100
100
|
weeb_cli/services/search.py
|
|
101
101
|
weeb_cli/services/shortcuts.py
|
|
102
102
|
weeb_cli/services/stream_validator.py
|
|
103
|
-
weeb_cli/services/telemetry.py
|
|
104
103
|
weeb_cli/services/tracker.py
|
|
105
104
|
weeb_cli/services/updater.py
|
|
106
105
|
weeb_cli/services/watch.py
|
|
@@ -44,14 +44,3 @@ flask-cors>=4.0
|
|
|
44
44
|
[shortcuts:sys_platform == "win32"]
|
|
45
45
|
pywin32
|
|
46
46
|
winshell
|
|
47
|
-
|
|
48
|
-
[telemetry]
|
|
49
|
-
opentelemetry-api<2.0,>=1.25.0
|
|
50
|
-
opentelemetry-sdk<2.0,>=1.25.0
|
|
51
|
-
opentelemetry-exporter-otlp-proto-http<2.0,>=1.25.0
|
|
52
|
-
opentelemetry-exporter-otlp-proto-grpc<2.0,>=1.25.0
|
|
53
|
-
opentelemetry-instrumentation-requests<1.0,>=0.46b0
|
|
54
|
-
opentelemetry-instrumentation-urllib3<1.0,>=0.46b0
|
|
55
|
-
opentelemetry-instrumentation-sqlite3<1.0,>=0.46b0
|
|
56
|
-
opentelemetry-instrumentation-flask<1.0,>=0.46b0
|
|
57
|
-
opentelemetry-instrumentation-logging<1.0,>=0.46b0
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
"""Download manager with strategy pattern."""
|
|
2
|
-
|
|
3
|
-
import time as _time
|
|
4
|
-
from typing import List
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
from weeb_cli.services.download.context import DownloadContext
|
|
8
|
-
from weeb_cli.services.download.strategies import (
|
|
9
|
-
DownloadStrategy,
|
|
10
|
-
Aria2Strategy,
|
|
11
|
-
YtdlpStrategy,
|
|
12
|
-
FFmpegStrategy,
|
|
13
|
-
GenericStrategy
|
|
14
|
-
)
|
|
15
|
-
from weeb_cli.services.logger import debug
|
|
16
|
-
from weeb_cli.exceptions import DownloadError
|
|
17
|
-
from weeb_cli.services.telemetry import get_tracer, get_metrics, record_exception
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class DownloadManager:
|
|
21
|
-
"""Manages download operations using multiple strategies.
|
|
22
|
-
|
|
23
|
-
Tries strategies in priority order until one succeeds.
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self):
|
|
27
|
-
self.strategies: List[DownloadStrategy] = [
|
|
28
|
-
Aria2Strategy(),
|
|
29
|
-
YtdlpStrategy(),
|
|
30
|
-
FFmpegStrategy(),
|
|
31
|
-
GenericStrategy()
|
|
32
|
-
]
|
|
33
|
-
self.strategies.sort(key=lambda s: s.get_priority())
|
|
34
|
-
|
|
35
|
-
def download(self, url: str, output_path: str, item: dict = None) -> None:
|
|
36
|
-
"""Download a file using the best available strategy.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
url: Stream URL to download.
|
|
40
|
-
output_path: Path where file should be saved.
|
|
41
|
-
item: Optional queue item metadata for progress tracking.
|
|
42
|
-
|
|
43
|
-
Raises:
|
|
44
|
-
DownloadError: If all strategies fail.
|
|
45
|
-
"""
|
|
46
|
-
tracer = get_tracer()
|
|
47
|
-
m = get_metrics()
|
|
48
|
-
start = _time.monotonic()
|
|
49
|
-
|
|
50
|
-
with tracer.start_as_current_span("download", attributes={
|
|
51
|
-
"weeb.download.url_prefix": url[:80],
|
|
52
|
-
"weeb.download.output": output_path,
|
|
53
|
-
}) as span:
|
|
54
|
-
context = DownloadContext(
|
|
55
|
-
url=url,
|
|
56
|
-
output_path=output_path,
|
|
57
|
-
item=item
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
last_error = None
|
|
61
|
-
|
|
62
|
-
for strategy in self.strategies:
|
|
63
|
-
if not strategy.can_handle(url):
|
|
64
|
-
continue
|
|
65
|
-
|
|
66
|
-
strategy_name = strategy.__class__.__name__
|
|
67
|
-
debug(f"[DownloadManager] Trying {strategy_name} for {url[:80]}...")
|
|
68
|
-
|
|
69
|
-
try:
|
|
70
|
-
strategy.download(context)
|
|
71
|
-
debug(f"[DownloadManager] Success with {strategy_name}")
|
|
72
|
-
duration = _time.monotonic() - start
|
|
73
|
-
span.set_attribute("weeb.download.strategy", strategy_name)
|
|
74
|
-
m.download_duration.record(duration, {"weeb.download.strategy": strategy_name, "weeb.download.status": "success"})
|
|
75
|
-
m.download_status.add(1, {"status": "success"})
|
|
76
|
-
return
|
|
77
|
-
except Exception as e:
|
|
78
|
-
last_error = e
|
|
79
|
-
debug(f"[DownloadManager] {strategy_name} failed: {e}")
|
|
80
|
-
continue
|
|
81
|
-
|
|
82
|
-
duration = _time.monotonic() - start
|
|
83
|
-
m.download_duration.record(duration, {"weeb.download.status": "failed"})
|
|
84
|
-
m.download_status.add(1, {"status": "failed"})
|
|
85
|
-
|
|
86
|
-
error_msg = f"All download strategies failed. Last error: {last_error}"
|
|
87
|
-
err = DownloadError(error_msg, code="ALL_STRATEGIES_FAILED")
|
|
88
|
-
record_exception(span, err)
|
|
89
|
-
raise err
|