freeflix-cli 2.1.0__tar.gz → 2.1.2__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.
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/.github/workflows/ci.yml +4 -4
- freeflix_cli-2.1.2/.github/workflows/macos.yml +36 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/CHANGELOG.md +26 -1
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/PKG-INFO +1 -1
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/SCORECARD-2.1.0.md +1 -1
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/pyproject.toml +1 -1
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/cli.py +17 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/install_health.py +1 -1
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/player_manager.py +34 -20
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_audit_fixes.py +31 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_core_units.py +18 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_recent_new_episodes.py +1 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_release_config.py +3 -1
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/uv.lock +1 -1
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/.github/workflows/binaries.yml +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/.github/workflows/release.yml +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/.gitignore +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/CONTRIBUTING.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/LICENSE +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/README.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/SECURITY.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/assets/logo.png +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/config/freeflix_position.lua +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/config/input.conf +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/config/mpv.conf +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/data/selectors.jsonc +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/data/source_portal.jsonc +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/ARCHITECTURE.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/AUDIT-FREEFLIX-2.0-2026-11-04.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/CORRECTIONS-2.1.0.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/README.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/RELEASE.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/TROUBLESHOOTING.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/docs/USAGE.md +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/scripts/build-binary.sh +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/scripts/install-mac.sh +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/scripts/install.ps1 +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/scripts/install.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/scripts/install.sh +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/scripts/wrappers/mpv +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/__init__.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/__main__.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/anilist.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/cli_utils.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/cloudflare.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/config_loader.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/defaults.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/doctor.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/__init__.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/anilist.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/anime_sama.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/coflix.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/french_manga.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/french_stream.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/goldenanime.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/goldenms.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/hianime.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/nyaa.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/papystreaming.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/playback.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/handlers/yts.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/health.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/history_ui.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/httpcache.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/i18n.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/icons.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/languages.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/logsetup.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/main.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/motion.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/mpv_ipc.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/neon.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/net_config.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/notifications.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/platform_android.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/preview_picker.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/progress.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/providers_registry.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/proxy.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/recent.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/__init__.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/anime_sama.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/anime_sama_planning.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/coflix.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/config.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/deobfuscate.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/eztv.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/french_manga.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/french_stream.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/goldenanime.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/goldenms.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/nyaa.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/objects.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/papystreaming.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/player.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/resilient.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/subtitles.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/utils.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/scraping/yts.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/setup_assistant.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/splash.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/subtitles.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/terminal_image.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/themes.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/tracker.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/ui.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/src/freeflix_cli/update_checker.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/conftest.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_android_setup.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_anilist_errors.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_anilist_token_input.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_anime_sama_planning.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_batch_download.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_coflix_parse.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_cover_cache.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_decoy_stream.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_download_fixes.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_download_integration.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_download_robustness.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_episode_parsing.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_fsvid_decode.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_health.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_httpcache.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_i18n_complete.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_keyboard.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_managed_binaries.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_mouse.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_mpv_ipc.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_navigation.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_nerd_font.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_nyaa.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_pack_a_polish.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_performance.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_platform_android.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_player_dash.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_polish.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_premium_players.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_providers_repair.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_proxy_endpoint.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_proxy_integration.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_proxy_resilient.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_proxy_ssrf.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_resilient.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_scraper_resilience.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_search_parity.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_sendvid.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_stream_analysis.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_stream_integrity.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_system_deps_cache.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_terminal_image.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_update_prompt.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_v1105_improvements.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tests/test_v2_aesthetic.py +0 -0
- {freeflix_cli-2.1.0 → freeflix_cli-2.1.2}/tools/audit_providers.py +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
name: CI
|
|
2
2
|
|
|
3
|
-
# Lint + tests on every push and PR, on Linux
|
|
4
|
-
#
|
|
3
|
+
# Lint + tests on every push and PR, on Linux and Windows. macOS lives in
|
|
4
|
+
# macos.yml: GitHub's macOS runners are frequently unavailable, and having
|
|
5
|
+
# them in this required matrix turned green code into red runs. macOS is still
|
|
6
|
+
# tested on demand / weekly.
|
|
5
7
|
on:
|
|
6
8
|
push:
|
|
7
9
|
branches: [main]
|
|
@@ -26,8 +28,6 @@ jobs:
|
|
|
26
28
|
os: [ubuntu-22.04, windows-latest]
|
|
27
29
|
python-version: ['3.9', '3.12']
|
|
28
30
|
include:
|
|
29
|
-
- os: macos-latest
|
|
30
|
-
python-version: '3.12'
|
|
31
31
|
- os: ubuntu-22.04
|
|
32
32
|
python-version: '3.11'
|
|
33
33
|
- os: ubuntu-22.04
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: macOS
|
|
2
|
+
|
|
3
|
+
# macOS is tested separately: the hosted macos-latest runners are often
|
|
4
|
+
# unavailable (job never acquired), which must not turn every push red.
|
|
5
|
+
# Run it manually before a release, or let the weekly schedule catch drift.
|
|
6
|
+
on:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '0 4 * * 1' # Monday 04:00 UTC
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
test:
|
|
16
|
+
name: Lint + tests (macos-latest, py3.12)
|
|
17
|
+
runs-on: macos-latest
|
|
18
|
+
timeout-minutes: 30
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v3
|
|
22
|
+
- uses: actions/setup-python@v5
|
|
23
|
+
with:
|
|
24
|
+
python-version: '3.12'
|
|
25
|
+
- name: Ruff
|
|
26
|
+
run: uvx ruff==0.17.3 check --select E,W,F,B --ignore E501,E402 src/freeflix_cli/
|
|
27
|
+
- name: Import every module
|
|
28
|
+
run: >
|
|
29
|
+
uv run --with . python -c "import pkgutil,importlib,freeflix_cli;
|
|
30
|
+
[importlib.import_module(m.name) for m in
|
|
31
|
+
pkgutil.walk_packages(freeflix_cli.__path__,'freeflix_cli.')];
|
|
32
|
+
print('all modules import OK')"
|
|
33
|
+
- name: Pytest
|
|
34
|
+
run: >
|
|
35
|
+
uv run --with . --with pytest --with pytest-cov --with pytest-timeout
|
|
36
|
+
-m pytest -q --timeout=120
|
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.1.2
|
|
4
|
+
|
|
5
|
+
- **Téléchargements Vidzy / fsvid / uqload réparés** : l'URL passée à yt-dlp
|
|
6
|
+
traverse toujours le proxy local curl_cffi (fingerprint navigateur), mais
|
|
7
|
+
elle porte maintenant le **jeton de capacité `?k=`** et le **jeton
|
|
8
|
+
d'en-têtes `?h=`** ajoutés en 2.1. Sans eux, le proxy répondait 403 : le
|
|
9
|
+
streaming fonctionnait, le téléchargement échouait.
|
|
10
|
+
- Vérifié en réel sur FROM S1E1 : fsvid et vidzy résolus, yt-dlp natif a
|
|
11
|
+
téléchargé 34/316 fragments via le proxy sans erreur.
|
|
12
|
+
- Test du flux « Nouveautés » isolé des séries réellement suivies (plus de
|
|
13
|
+
dépendance à l'état local de la machine).
|
|
14
|
+
- **CI macOS séparée** : les runners macOS GitHub sont souvent indisponibles
|
|
15
|
+
(« job was not acquired »), ce qui rendait la CI rouge sans bug de code.
|
|
16
|
+
macOS passe dans `macos.yml` (manuel + hebdomadaire) ; la CI de push reste
|
|
17
|
+
Linux + Windows.
|
|
18
|
+
- 588 tests au vert, ruff OK. Aucun changement d'interface : le code écran
|
|
19
|
+
reste celui de la 2.1.1.
|
|
20
|
+
|
|
21
|
+
## 2.1.1
|
|
22
|
+
|
|
23
|
+
- **Binaire Windows réparé** : `--doctor` (et le reste de l'interface) force
|
|
24
|
+
UTF-8 sur stdout/stderr quand la console est en cp1252 ; le smoke test du
|
|
25
|
+
binaire autonome passe à nouveau.
|
|
26
|
+
- 586 tests au vert, ruff OK.
|
|
27
|
+
|
|
3
28
|
## 2.1.0 — Fiabilité, sécurité et cohérence
|
|
4
29
|
|
|
5
30
|
Version de consolidation : l'audit complet de la 2.0.0 a été appliqué de bout
|
|
@@ -124,7 +149,7 @@ en bout, sans changement de fonctionnalité visible.
|
|
|
124
149
|
- Tests dédiés `cli.py`, `doctor.py`, `providers_registry`, `config_loader`,
|
|
125
150
|
`install_health`, thèmes/UI, métadonnées de reprise, noms Windows, garde-fous
|
|
126
151
|
de release et IPC Windows.
|
|
127
|
-
-
|
|
152
|
+
- 586 tests au vert, ruff OK.
|
|
128
153
|
|
|
129
154
|
## 2.0.0 — Cinéma Noir
|
|
130
155
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: freeflix-cli
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Watch movies, series, and anime from your terminal — multi-language, multi-source, no browser required. A maintained fork of autoflix-cli.
|
|
5
5
|
Project-URL: Homepage, https://github.com/freedy237/freeflix-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/freedy237/freeflix-cli.git
|
|
@@ -12,7 +12,7 @@ ouvert ; il ne s'agit pas d'une note « commerciale ».
|
|
|
12
12
|
| **Robustesse réseau** | 7,5 | **8,5** | placeholder <90 s y compris MP4 direct, plafond 5 min sans octet, budget analyse 55/60 s, cache qualité 5 min, TLS DoH vérifié, SSRF + jeton proxy, reprise Range | pas de budget adaptatif par type de lecteur |
|
|
13
13
|
| **Téléchargements** | 6,5 | **9** | reprise séquentielle réparée, staging + vérification + `os.replace`, `.part` atomique, annulation propagée + kill après 5 s, métadonnées sans cookies en 0600, noms Windows réservés, balayage des sous-titres temp | vérification taille seule si ffprobe absent |
|
|
14
14
|
| **Multi-OS** | 5,5 | **8,5** | CI Linux/Windows/macOS × 3.9→3.13, build wheel + smoke install, syntaxe PowerShell vérifiée, mpv arm64 macOS/Windows, IPC Windows non bloquant, installateurs tolérants, chemins mpv alignés | pas de validation physique Windows/macOS dans cette session |
|
|
15
|
-
| **Tests / CI** | 6 | **9** | **
|
|
15
|
+
| **Tests / CI** | 6 | **9** | **586 tests** verts, couverture **~45 %** (plancher 40), `pytest-timeout=120`, ruff épinglé, suite offline, 47 tests de non-régression/unitaires ajoutés, matrice 5 environnements | quelques assertions historiques faibles, pas de tests de rendu visuel |
|
|
16
16
|
| **Release / packaging** | 5 | **8,5** | release gardée par tests + version==tag + `twine check`, `SHA256SUMS`, binaires déclenchés explicitement (`workflow_dispatch`), `--add-data`, smoke `--doctor`, tests de config de release | chaîne binaires à exécuter au prochain tag réel |
|
|
17
17
|
| **Documentation** | 4 | **9** | README 2.1 (10 sources, planning, souris, dev), index `docs/`, USAGE, ARCHITECTURE, TROUBLESHOOTING, RELEASE, SECURITY, CONTRIBUTING, CHANGELOG détaillé | pas de captures d'écran/GIF |
|
|
18
18
|
| **Sécurité** | 7 | **9** | jeton de capacité proxy (SSRF local), SSRF DNS/IP, TLS DoH, cookies retirés des métadonnées, URLs signées masquées, toasts PowerShell sans interpolation, PATH registre, checksums d'artefacts | `enc-dec.app` tiers pour GoldenMS, `curl \| sh` de `uv` dans un installeur |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "freeflix-cli"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.2"
|
|
8
8
|
description = "Watch movies, series, and anime from your terminal — multi-language, multi-source, no browser required. A maintained fork of autoflix-cli."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9.2"
|
|
@@ -9,6 +9,22 @@ a few tens of milliseconds instead of paying the whole import tax.
|
|
|
9
9
|
import sys
|
|
10
10
|
|
|
11
11
|
|
|
12
|
+
def _utf8_console() -> None:
|
|
13
|
+
"""Force UTF-8 on stdout/stderr when the console defaults to cp1252.
|
|
14
|
+
|
|
15
|
+
The Windows standalone binary inherits a cp1252 console; --doctor (box
|
|
16
|
+
drawing, arrows) then crashed with UnicodeEncodeError before printing
|
|
17
|
+
anything. TUI output uses the same code path.
|
|
18
|
+
"""
|
|
19
|
+
for stream in (sys.stdout, sys.stderr):
|
|
20
|
+
try:
|
|
21
|
+
enc = (getattr(stream, "encoding", "") or "").lower().replace("-", "")
|
|
22
|
+
if enc not in ("utf8", "utf8mb4"):
|
|
23
|
+
stream.reconfigure(encoding="utf-8", errors="replace")
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
12
28
|
def _version() -> str:
|
|
13
29
|
try:
|
|
14
30
|
import importlib.metadata as _im
|
|
@@ -37,6 +53,7 @@ def print_help():
|
|
|
37
53
|
|
|
38
54
|
def main():
|
|
39
55
|
"""Entry point: fast flags first, then the real application."""
|
|
56
|
+
_utf8_console()
|
|
40
57
|
args = set(sys.argv[1:])
|
|
41
58
|
if args & {"--version", "-V"}:
|
|
42
59
|
print(_version())
|
|
@@ -1898,6 +1898,38 @@ def _purge_download_temp(temp_dir: str, keep_incomplete: bool = True) -> None:
|
|
|
1898
1898
|
pass
|
|
1899
1899
|
|
|
1900
1900
|
|
|
1901
|
+
def _proxy_download_url(stream_url: str, headers: dict, player_config: dict,
|
|
1902
|
+
is_mp4: bool) -> str:
|
|
1903
|
+
"""Route a fingerprint-gated HLS download through the local proxy.
|
|
1904
|
+
|
|
1905
|
+
Vidzy/fsvid/uqload reject yt-dlp's TLS fingerprint (or serve it a 30 s ad)
|
|
1906
|
+
even though the same URL streams fine through curl_cffi. The proxy URL must
|
|
1907
|
+
carry the per-process capability token AND the opaque header handle: a bare
|
|
1908
|
+
``?url=`` is rejected with 403 since 2.1.
|
|
1909
|
+
"""
|
|
1910
|
+
try:
|
|
1911
|
+
from urllib.parse import quote, urlparse
|
|
1912
|
+
from . import proxy
|
|
1913
|
+
|
|
1914
|
+
host = (urlparse(stream_url).hostname or "").lower()
|
|
1915
|
+
needs_proxy = (
|
|
1916
|
+
(player_config or {}).get("type") in ("fsvid", "uqload")
|
|
1917
|
+
or any(h in host for h in ("vidzy", "fsvid", "uqload"))
|
|
1918
|
+
)
|
|
1919
|
+
if not (".m3u8" in stream_url.lower() and needs_proxy):
|
|
1920
|
+
return stream_url
|
|
1921
|
+
proxy.ensure_started()
|
|
1922
|
+
if not proxy.PROXY_URL:
|
|
1923
|
+
return stream_url
|
|
1924
|
+
endpoint = _proxy_endpoint(stream_url, player_config or {}, bool(is_mp4))
|
|
1925
|
+
token = proxy._headers_token(headers or {})
|
|
1926
|
+
handle = f"&h={token}" if token else ""
|
|
1927
|
+
return (f"{proxy.PROXY_URL}/{endpoint}?url={quote(stream_url)}"
|
|
1928
|
+
f"{handle}&k={proxy.PROXY_TOKEN}")
|
|
1929
|
+
except Exception:
|
|
1930
|
+
return stream_url
|
|
1931
|
+
|
|
1932
|
+
|
|
1901
1933
|
def _download_headers(headers: dict, referer: str = "",
|
|
1902
1934
|
user_agent: str = "") -> dict:
|
|
1903
1935
|
"""Sanitised headers for yt-dlp/aria2c (cookies, signed-CDN tokens…)."""
|
|
@@ -2920,26 +2952,8 @@ def play_video(
|
|
|
2920
2952
|
# our curl_cffi proxy. Route the download through the SAME local
|
|
2921
2953
|
# proxy endpoint, so yt-dlp talks to localhost while the proxy
|
|
2922
2954
|
# fetches with the browser fingerprint and real headers/cookies.
|
|
2923
|
-
dl_url =
|
|
2924
|
-
|
|
2925
|
-
from urllib.parse import urlparse, quote
|
|
2926
|
-
dl_host = (urlparse(stream_url).hostname or "").lower()
|
|
2927
|
-
if ".m3u8" in stream_url.lower() and (
|
|
2928
|
-
player_config.get("type") in ("fsvid", "uqload")
|
|
2929
|
-
or any(h in dl_host for h in ("vidzy", "fsvid", "uqload"))
|
|
2930
|
-
):
|
|
2931
|
-
proxy.ensure_started()
|
|
2932
|
-
if proxy.PROXY_URL:
|
|
2933
|
-
endpoint = _proxy_endpoint(
|
|
2934
|
-
stream_url, player_config, bool(is_mp4)
|
|
2935
|
-
)
|
|
2936
|
-
dl_url = (
|
|
2937
|
-
f"{proxy.PROXY_URL}/{endpoint}"
|
|
2938
|
-
f"?url={quote(stream_url)}"
|
|
2939
|
-
f"&headers={quote(json.dumps(headers))}"
|
|
2940
|
-
)
|
|
2941
|
-
except Exception:
|
|
2942
|
-
dl_url = stream_url
|
|
2955
|
+
dl_url = _proxy_download_url(
|
|
2956
|
+
stream_url, headers, player_config, bool(is_mp4))
|
|
2943
2957
|
success = _download_stream(
|
|
2944
2958
|
stream_url=dl_url,
|
|
2945
2959
|
referer=referer,
|
|
@@ -221,3 +221,34 @@ def test_ffprobe_quality_flags_short_direct_mp4(monkeypatch):
|
|
|
221
221
|
q = pm._ffprobe_quality("https://cdn/ad.mp4", {})
|
|
222
222
|
assert q["height"] == 720 and q["duration"] == 30.0
|
|
223
223
|
assert pm.is_likely_ad_placeholder(q) is True
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_proxy_download_url_carries_token_and_hides_cookies(monkeypatch):
|
|
227
|
+
"""Vidzy/fsvid downloads must use the local proxy WITH ?k= and ?h=.
|
|
228
|
+
|
|
229
|
+
Regression found after 2.1: the download URL was built without the
|
|
230
|
+
capability token, so the proxy answered 403 while streaming worked.
|
|
231
|
+
"""
|
|
232
|
+
from urllib.parse import parse_qs, urlparse
|
|
233
|
+
|
|
234
|
+
from freeflix_cli import player_manager as pm
|
|
235
|
+
from freeflix_cli import proxy
|
|
236
|
+
|
|
237
|
+
monkeypatch.setattr(proxy, "PROXY_URL", "http://127.0.0.1:9999")
|
|
238
|
+
monkeypatch.setattr(proxy, "PROXY_TOKEN", "tok-123")
|
|
239
|
+
monkeypatch.setattr(proxy, "ensure_started", lambda: None)
|
|
240
|
+
headers = {"Referer": "https://vidzy.cc/", "Cookie": "vz_ok=secret"}
|
|
241
|
+
url = pm._proxy_download_url(
|
|
242
|
+
"https://cdn.vidzy.cc/master.m3u8?t=1", headers, {"type": "fsvid"}, False)
|
|
243
|
+
q = parse_qs(urlparse(url).query)
|
|
244
|
+
assert q["k"] == ["tok-123"]
|
|
245
|
+
assert "h" in q and "headers" not in q
|
|
246
|
+
assert "vz_ok" not in url and "secret" not in url
|
|
247
|
+
assert url.startswith("http://127.0.0.1:9999/")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_proxy_download_url_leaves_plain_hosts_alone():
|
|
251
|
+
from freeflix_cli import player_manager as pm
|
|
252
|
+
|
|
253
|
+
plain = "https://cdn.example/master.m3u8"
|
|
254
|
+
assert pm._proxy_download_url(plain, {}, {}, False) == plain
|
|
@@ -28,6 +28,24 @@ def test_cli_version_prints_a_version(monkeypatch, capsys):
|
|
|
28
28
|
assert out and out[0].isdigit()
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
def test_utf8_console_reconfigures_cp1252(monkeypatch):
|
|
32
|
+
class _Fake:
|
|
33
|
+
encoding = "cp1252"
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.kw = None
|
|
37
|
+
|
|
38
|
+
def reconfigure(self, **kw):
|
|
39
|
+
self.kw = kw
|
|
40
|
+
|
|
41
|
+
out, err = _Fake(), _Fake()
|
|
42
|
+
monkeypatch.setattr(cli.sys, "stdout", out)
|
|
43
|
+
monkeypatch.setattr(cli.sys, "stderr", err)
|
|
44
|
+
cli._utf8_console()
|
|
45
|
+
assert out.kw == {"encoding": "utf-8", "errors": "replace"}
|
|
46
|
+
assert err.kw == {"encoding": "utf-8", "errors": "replace"}
|
|
47
|
+
|
|
48
|
+
|
|
31
49
|
def test_cli_help_mentions_doctor(monkeypatch, capsys):
|
|
32
50
|
monkeypatch.setattr("sys.argv", ["freeflix", "--help"])
|
|
33
51
|
assert cli.main() == 0
|
|
@@ -77,6 +77,7 @@ def test_other_providers_are_ignored(monkeypatch):
|
|
|
77
77
|
"season_title": "S1", "episode_title": "E1",
|
|
78
78
|
"series_url": "https://x", "season_url": "https://x"}]
|
|
79
79
|
monkeypatch.setattr(tracker, "get_history", lambda: hist)
|
|
80
|
+
monkeypatch.setattr(tracker, "get_followed_series", lambda: [])
|
|
80
81
|
monkeypatch.setattr(tracker, "get_seen_releases", lambda: set())
|
|
81
82
|
recent._state["items"] = []
|
|
82
83
|
recent._state["ready"] = False
|
|
@@ -47,8 +47,10 @@ def test_ci_has_timeout_coverage_and_installer_checks():
|
|
|
47
47
|
assert "--timeout=120" in yml
|
|
48
48
|
assert "--cov-fail-under=40" in yml
|
|
49
49
|
assert "PowerShell syntax" in yml
|
|
50
|
-
assert "macos-latest" in yml
|
|
51
50
|
assert "ruff==0.17.3" in yml
|
|
51
|
+
# macOS is a separate, non-blocking workflow (runners are often unavailable).
|
|
52
|
+
macos = _read(".github/workflows/macos.yml")
|
|
53
|
+
assert "macos-latest" in macos and "workflow_dispatch:" in macos
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
def test_installer_scripts_are_tolerant_and_verified():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|