upload-assistant 3.9__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.
- bin/__init__.py +0 -0
- bin/download_integrity.py +58 -0
- bin/get_7z.py +146 -0
- bin/get_bdinfo.py +231 -0
- bin/get_bdinfo_docker.py +164 -0
- bin/get_dvd_mediainfo.py +189 -0
- bin/get_dynamic_hdr_tools.py +122 -0
- bin/get_ffmpeg.py +76 -0
- bin/get_ffmpeg_docker.py +116 -0
- bin/get_mediainfo.py +183 -0
- bin/get_mkbrr.py +367 -0
- bin/get_nyuu.py +169 -0
- bin/get_par2.py +149 -0
- bin/get_pesto.py +107 -0
- bin/get_playlist.py +122 -0
- config-generator.py +1244 -0
- data/__init__.py +18 -0
- data/example_config.py +3619 -0
- data/tags.json +21 -0
- data/templates/summary-mediainfo.csv +7 -0
- data/templates/user-args-template.json +47 -0
- src/__init__.py +0 -0
- src/add_comparison.py +181 -0
- src/app_paths.py +41 -0
- src/apply_overrides.py +196 -0
- src/args.py +1466 -0
- src/artwork.py +209 -0
- src/audio.py +695 -0
- src/audio_classifier.py +397 -0
- src/audio_spectrogram.py +384 -0
- src/bbcode.py +730 -0
- src/bdinfo_comparator.py +196 -0
- src/binaries.py +40 -0
- src/bluray_com.py +1794 -0
- src/book_extractors.py +803 -0
- src/book_prep.py +1078 -0
- src/btnid.py +249 -0
- src/check_requirements.py +114 -0
- src/cleanup.py +259 -0
- src/clients.py +755 -0
- src/cogs/__init__.py +0 -0
- src/cogs/redaction.py +192 -0
- src/config_helpers.py +36 -0
- src/configvalidator.py +879 -0
- src/console.py +343 -0
- src/cookie_auth.py +818 -0
- src/description_languages.py +150 -0
- src/description_review.py +86 -0
- src/disc_menus.py +401 -0
- src/discparse.py +1179 -0
- src/dupe_checking.py +1044 -0
- src/dynamic_hdr_plot.py +179 -0
- src/early_tasks.py +144 -0
- src/edition.py +428 -0
- src/exceptions.py +46 -0
- src/exportmi.py +568 -0
- src/genre_map.py +565 -0
- src/get_desc.py +2305 -0
- src/get_disc.py +90 -0
- src/get_name.py +705 -0
- src/get_source.py +97 -0
- src/get_tracker_data.py +572 -0
- src/getseasonep.py +529 -0
- src/google_books.py +149 -0
- src/igdb.py +175 -0
- src/imdb.py +954 -0
- src/is_scene.py +273 -0
- src/languages.py +491 -0
- src/manualpackage.py +109 -0
- src/media_extensions.py +7 -0
- src/mediainfo.py +127 -0
- src/meta.py +696 -0
- src/metadata_cache.py +153 -0
- src/metadata_searching.py +1026 -0
- src/music/__init__.py +7 -0
- src/music/analyzer.py +556 -0
- src/music/models.py +148 -0
- src/music/prep.py +447 -0
- src/music/sources.py +615 -0
- src/music/validation.py +114 -0
- src/myanonamouse.py +188 -0
- src/openlibrary.py +196 -0
- src/post_upload_hooks.py +197 -0
- src/prep.py +447 -0
- src/prep_game.py +890 -0
- src/prep_helpers.py +1801 -0
- src/qbitwait.py +378 -0
- src/queuemanage.py +762 -0
- src/radarr.py +129 -0
- src/region.py +2419 -0
- src/rehostimages.py +921 -0
- src/screenshot_manifest.py +126 -0
- src/screenshot_review.py +505 -0
- src/sonarr.py +143 -0
- src/tags.py +175 -0
- src/takescreens.py +2872 -0
- src/temp_paths.py +51 -0
- src/tmdb.py +2060 -0
- src/torrent_clients/__init__.py +13 -0
- src/torrent_clients/deluge.py +28 -0
- src/torrent_clients/path_utils.py +89 -0
- src/torrent_clients/qbittorrent.py +2170 -0
- src/torrent_clients/rtorrent.py +439 -0
- src/torrent_clients/transmission.py +43 -0
- src/torrentcreate.py +680 -0
- src/tracker_descriptions.py +102 -0
- src/tracker_images.py +35 -0
- src/trackerhandle.py +408 -0
- src/trackermeta.py +792 -0
- src/trackers/AVISTAZ/__init__.py +1350 -0
- src/trackers/AVISTAZ/avistaz.py +484 -0
- src/trackers/AVISTAZ/cinemaz.py +388 -0
- src/trackers/AVISTAZ/privatehd.py +651 -0
- src/trackers/AVISTAZ/routing.py +360 -0
- src/trackers/NEXUSPHP/__init__.py +454 -0
- src/trackers/NEXUSPHP/lajidui.py +406 -0
- src/trackers/NEXUSPHP/lemonhd.py +228 -0
- src/trackers/NEXUSPHP/longpt.py +236 -0
- src/trackers/NEXUSPHP/oneptba.py +275 -0
- src/trackers/NEXUSPHP/ptcafe.py +419 -0
- src/trackers/NEXUSPHP/ptfans.py +155 -0
- src/trackers/NEXUSPHP/ptgtk.py +192 -0
- src/trackers/NEXUSPHP/ptzone.py +233 -0
- src/trackers/NEXUSPHP/railgunpt.py +186 -0
- src/trackers/NEXUSPHP/xingyungept.py +247 -0
- src/trackers/UNIT3D/UNIT3D_TEMPLATE.py +118 -0
- src/trackers/UNIT3D/__init__.py +641 -0
- src/trackers/UNIT3D/aither.py +163 -0
- src/trackers/UNIT3D/asiancinema.py +195 -0
- src/trackers/UNIT3D/bitporn.py +238 -0
- src/trackers/UNIT3D/blutopia.py +290 -0
- src/trackers/UNIT3D/capybarabr.py +388 -0
- src/trackers/UNIT3D/cinematik.py +624 -0
- src/trackers/UNIT3D/darkpeers.py +657 -0
- src/trackers/UNIT3D/dreadvault.py +72 -0
- src/trackers/UNIT3D/emuwarez.py +637 -0
- src/trackers/UNIT3D/hawkeuno.py +352 -0
- src/trackers/UNIT3D/homiehelpdesk.py +238 -0
- src/trackers/UNIT3D/infinityhd.py +189 -0
- src/trackers/UNIT3D/itatorrents.py +185 -0
- src/trackers/UNIT3D/lastdigitalunderground.py +211 -0
- src/trackers/UNIT3D/latteam.py +321 -0
- src/trackers/UNIT3D/locadora.py +142 -0
- src/trackers/UNIT3D/lst.py +323 -0
- src/trackers/UNIT3D/luminarr.py +64 -0
- src/trackers/UNIT3D/midnightscene.py +250 -0
- src/trackers/UNIT3D/nordicquality.py +178 -0
- src/trackers/UNIT3D/oldtoonsworld.py +219 -0
- src/trackers/UNIT3D/onlyencodes.py +278 -0
- src/trackers/UNIT3D/peergarden.py +267 -0
- src/trackers/UNIT3D/polishtorrent.py +53 -0
- src/trackers/UNIT3D/portugas.py +186 -0
- src/trackers/UNIT3D/racing4everyone.py +137 -0
- src/trackers/UNIT3D/rastastugan.py +132 -0
- src/trackers/UNIT3D/reelflix.py +87 -0
- src/trackers/UNIT3D/retromoviesclub.py +203 -0
- src/trackers/UNIT3D/rockethd.py +385 -0
- src/trackers/UNIT3D/samaritano.py +176 -0
- src/trackers/UNIT3D/seedpool.py +239 -0
- src/trackers/UNIT3D/shareisland.py +1067 -0
- src/trackers/UNIT3D/skipthecommercials.py +86 -0
- src/trackers/UNIT3D/theoldschool.py +134 -0
- src/trackers/UNIT3D/tlzdigital.py +90 -0
- src/trackers/UNIT3D/torrentdesi.py +138 -0
- src/trackers/UNIT3D/torrenteros.py +148 -0
- src/trackers/UNIT3D/torrenthr.py +65 -0
- src/trackers/UNIT3D/ulcx.py +308 -0
- src/trackers/UNIT3D/utopia.py +204 -0
- src/trackers/UNIT3D/yuscene.py +174 -0
- src/trackers/UNIT3D/znth.py +499 -0
- src/trackers/USENET/__init__.py +0 -0
- src/trackers/USENET/curupira.py +508 -0
- src/trackers/USENET/drunkenslug.py +188 -0
- src/trackers/USENET/nzbgeek.py +195 -0
- src/trackers/USENET/search_helpers.py +238 -0
- src/trackers/USENET/suio.py +531 -0
- src/trackers/__init__.py +0 -0
- src/trackers/alpharatio.py +473 -0
- src/trackers/amigosshare.py +1296 -0
- src/trackers/anthelion.py +621 -0
- src/trackers/beyondhd.py +533 -0
- src/trackers/bithdtv.py +200 -0
- src/trackers/bjshare.py +1890 -0
- src/trackers/brasiltracker.py +1242 -0
- src/trackers/broadcasthenet.py +736 -0
- src/trackers/cathoderaytube.py +654 -0
- src/trackers/common.py +3516 -0
- src/trackers/digitalcore.py +314 -0
- src/trackers/filelist.py +478 -0
- src/trackers/flood.py +272 -0
- src/trackers/funfile.py +524 -0
- src/trackers/greatposterwall.py +1186 -0
- src/trackers/hdbits.py +929 -0
- src/trackers/hdspace.py +298 -0
- src/trackers/hdtorrents.py +292 -0
- src/trackers/immortalseed.py +331 -0
- src/trackers/iptorrents.py +489 -0
- src/trackers/makingoff.py +1900 -0
- src/trackers/mteam.py +631 -0
- src/trackers/nebulance.py +285 -0
- src/trackers/orpheus.py +608 -0
- src/trackers/passthepopcorn.py +1705 -0
- src/trackers/pterclub.py +477 -0
- src/trackers/ptskit.py +123 -0
- src/trackers/retroflix.py +444 -0
- src/trackers/speedapp.py +365 -0
- src/trackers/swarmazon.py +212 -0
- src/trackers/torrentleech.py +427 -0
- src/trackers/totheglory.py +379 -0
- src/trackers/tvchaosuk.py +958 -0
- src/trackersetup.py +1542 -0
- src/trackerstatus.py +406 -0
- src/tvdb.py +697 -0
- src/tvmaze.py +340 -0
- src/type_utils.py +29 -0
- src/uphelper.py +850 -0
- src/uploadscreens.py +1037 -0
- src/usenetcreate.py +1443 -0
- src/version.py +5 -0
- src/video.py +393 -0
- src/vs.py +161 -0
- src/webui_progress.py +115 -0
- src/xxx_keywords.py +43 -0
- src/xxx_keywords_list.py +4123 -0
- src/xxx_platforms.py +239 -0
- upload.py +3201 -0
- upload_assistant-3.9.dist-info/METADATA +503 -0
- upload_assistant-3.9.dist-info/RECORD +366 -0
- upload_assistant-3.9.dist-info/WHEEL +5 -0
- upload_assistant-3.9.dist-info/entry_points.txt +3 -0
- upload_assistant-3.9.dist-info/licenses/LICENSE +116 -0
- upload_assistant-3.9.dist-info/top_level.txt +6 -0
- web_ui/__init__.py +4 -0
- web_ui/access_log.py +182 -0
- web_ui/auth.py +640 -0
- web_ui/server.py +6133 -0
- web_ui/static/css/theme.css +261 -0
- web_ui/static/favicon.ico +0 -0
- web_ui/static/img/apple-touch-icon.png +0 -0
- web_ui/static/img/icon-192.png +0 -0
- web_ui/static/img/logo.png +0 -0
- web_ui/static/img/logo.svg +177 -0
- web_ui/static/img/providers/discogs.svg +20 -0
- web_ui/static/img/providers/douban.svg +1 -0
- web_ui/static/img/providers/google_books.svg +1 -0
- web_ui/static/img/providers/igdb.svg +13 -0
- web_ui/static/img/providers/igdb_light.svg +11 -0
- web_ui/static/img/providers/imdb.svg +3 -0
- web_ui/static/img/providers/mal.svg +21 -0
- web_ui/static/img/providers/musicbrainz.ico +0 -0
- web_ui/static/img/providers/openlibrary.svg +123 -0
- web_ui/static/img/providers/steam.svg +1 -0
- web_ui/static/img/providers/tmdb.svg +1 -0
- web_ui/static/img/providers/tvdb.svg +1 -0
- web_ui/static/img/providers/tvmaze.svg +17 -0
- web_ui/static/img/trackers/1ptba.png +0 -0
- web_ui/static/img/trackers/aither.png +0 -0
- web_ui/static/img/trackers/alpharatio.png +0 -0
- web_ui/static/img/trackers/amigosshare.png +0 -0
- web_ui/static/img/trackers/anthelion.png +0 -0
- web_ui/static/img/trackers/asiancinema.png +0 -0
- web_ui/static/img/trackers/avistaz.png +0 -0
- web_ui/static/img/trackers/beyondhd.png +0 -0
- web_ui/static/img/trackers/bithdtv.png +0 -0
- web_ui/static/img/trackers/bitporn.png +0 -0
- web_ui/static/img/trackers/bjshare.png +0 -0
- web_ui/static/img/trackers/blutopia.png +0 -0
- web_ui/static/img/trackers/brasiltracker.png +0 -0
- web_ui/static/img/trackers/broadcasthenet.png +0 -0
- web_ui/static/img/trackers/capybarabr.png +0 -0
- web_ui/static/img/trackers/cathoderaytube.png +0 -0
- web_ui/static/img/trackers/cinematik.png +0 -0
- web_ui/static/img/trackers/cinemaz.png +0 -0
- web_ui/static/img/trackers/curupira.png +0 -0
- web_ui/static/img/trackers/darkpeers.png +0 -0
- web_ui/static/img/trackers/desitorrents.png +0 -0
- web_ui/static/img/trackers/digitalcore.png +0 -0
- web_ui/static/img/trackers/dreadvault.png +0 -0
- web_ui/static/img/trackers/drunkenslug.png +0 -0
- web_ui/static/img/trackers/emuwarez.png +0 -0
- web_ui/static/img/trackers/filelist.png +0 -0
- web_ui/static/img/trackers/flood.png +0 -0
- web_ui/static/img/trackers/funfile.png +0 -0
- web_ui/static/img/trackers/greatposterwall.png +0 -0
- web_ui/static/img/trackers/hawkeuno.png +0 -0
- web_ui/static/img/trackers/hdbits.png +0 -0
- web_ui/static/img/trackers/hdspace.png +0 -0
- web_ui/static/img/trackers/hdtorrents.png +0 -0
- web_ui/static/img/trackers/homiehelpdesk.png +0 -0
- web_ui/static/img/trackers/immortalseed.png +0 -0
- web_ui/static/img/trackers/infinityhd.png +0 -0
- web_ui/static/img/trackers/iptorrents.png +0 -0
- web_ui/static/img/trackers/itatorrents.png +0 -0
- web_ui/static/img/trackers/lajidui.png +0 -0
- web_ui/static/img/trackers/lastdigitalunderground.png +0 -0
- web_ui/static/img/trackers/latteam.png +0 -0
- web_ui/static/img/trackers/lemonhd.png +0 -0
- web_ui/static/img/trackers/locadora.png +0 -0
- web_ui/static/img/trackers/longpt.png +0 -0
- web_ui/static/img/trackers/lst.png +0 -0
- web_ui/static/img/trackers/luminarr.png +0 -0
- web_ui/static/img/trackers/makingoff.png +0 -0
- web_ui/static/img/trackers/midnightscene.png +0 -0
- web_ui/static/img/trackers/mteam.png +0 -0
- web_ui/static/img/trackers/nebulance.png +0 -0
- web_ui/static/img/trackers/nordicquality.png +0 -0
- web_ui/static/img/trackers/nzbgeek.png +0 -0
- web_ui/static/img/trackers/oldtoonsworld.png +0 -0
- web_ui/static/img/trackers/onlyencodes.png +0 -0
- web_ui/static/img/trackers/orpheus.png +0 -0
- web_ui/static/img/trackers/passthepopcorn.png +0 -0
- web_ui/static/img/trackers/peergarden.png +0 -0
- web_ui/static/img/trackers/polishtorrent.png +0 -0
- web_ui/static/img/trackers/portugas.png +0 -0
- web_ui/static/img/trackers/privatehd.png +0 -0
- web_ui/static/img/trackers/ptcafe.png +0 -0
- web_ui/static/img/trackers/pterclub.png +0 -0
- web_ui/static/img/trackers/ptfans.png +0 -0
- web_ui/static/img/trackers/ptgtk.png +0 -0
- web_ui/static/img/trackers/ptskit.png +0 -0
- web_ui/static/img/trackers/ptzone.png +0 -0
- web_ui/static/img/trackers/racing4everyone.png +0 -0
- web_ui/static/img/trackers/railgunpt.png +0 -0
- web_ui/static/img/trackers/rastastugan.png +0 -0
- web_ui/static/img/trackers/reelflix.png +0 -0
- web_ui/static/img/trackers/retroflix.png +0 -0
- web_ui/static/img/trackers/retromoviesclub.png +0 -0
- web_ui/static/img/trackers/rockethd.png +0 -0
- web_ui/static/img/trackers/samaritano.png +0 -0
- web_ui/static/img/trackers/seedpool.png +0 -0
- web_ui/static/img/trackers/shareisland.png +0 -0
- web_ui/static/img/trackers/skipthecommercials.png +0 -0
- web_ui/static/img/trackers/speedapp.png +0 -0
- web_ui/static/img/trackers/swarmazon.png +0 -0
- web_ui/static/img/trackers/theleachzone.png +0 -0
- web_ui/static/img/trackers/theoldschool.png +0 -0
- web_ui/static/img/trackers/torrenteros.png +0 -0
- web_ui/static/img/trackers/torrenthr.png +0 -0
- web_ui/static/img/trackers/torrentleech.png +0 -0
- web_ui/static/img/trackers/totheglory.png +0 -0
- web_ui/static/img/trackers/tvchaosuk.png +0 -0
- web_ui/static/img/trackers/ulcx.png +0 -0
- web_ui/static/img/trackers/utopia.png +0 -0
- web_ui/static/img/trackers/xingyungept.png +0 -0
- web_ui/static/img/trackers/yuscene.png +0 -0
- web_ui/static/img/trackers/zenith.png +0 -0
- web_ui/static/img/webui-icons/book.svg +6 -0
- web_ui/static/img/webui-icons/file-structure.svg +2 -0
- web_ui/static/img/webui-icons/gamepad.svg +4 -0
- web_ui/static/img/webui-icons/movie.svg +7 -0
- web_ui/static/img/webui-icons/music.svg +9 -0
- web_ui/static/img/webui-icons/palette.svg +8 -0
- web_ui/static/img/webui-icons/screenshots.svg +17 -0
- web_ui/static/img/webui-icons/settings.svg +4 -0
- web_ui/static/img/webui-icons/tv.svg +20 -0
- web_ui/static/js/app.js +6626 -0
- web_ui/static/js/config_app.js +3988 -0
- web_ui/static/js/package-lock.json +3866 -0
- web_ui/static/js/package.json +20 -0
- web_ui/static/js/shared_utils.js +213 -0
- web_ui/static/js/web_ui/static/js/package-lock.json +6 -0
- web_ui/static/manifest.webmanifest +20 -0
- web_ui/templates/config.html +81 -0
- web_ui/templates/index.html +130 -0
- web_ui/templates/login.html +320 -0
- web_ui/templates/login_recovery.html +128 -0
bin/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Pinned SHA-256 verification for third-party executable downloads."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
SHA256_BY_ASSET = {
|
|
7
|
+
"ffmpeg-9.0.1-essentials_build.zip": "fec81ae03971d9dd4be3ebe02e263bd2ec1d789483f931bdba5f5715e65da2e9",
|
|
8
|
+
"MediaInfo_CLI_23.04_Windows_x64.zip": "b1beafae0a15168ca37db8a3061d55eba55c1a120d6a6423ac1d3f30ed869270",
|
|
9
|
+
"MediaInfo_CLI_26.05_Lambda_arm64.zip": "57268dcfc044cdcbe4641e26432392f002b7fa4bcb06e9d738be04cd047a2c1e",
|
|
10
|
+
"MediaInfo_CLI_26.05_Lambda_x86_64.zip": "1ae3744a78c93492b69f0b38bb2d1de1433c3eae04030ff1ea82ee1f60ac9a99",
|
|
11
|
+
"MediaInfo_CLI_26.05_Mac.dmg": "507605a7c8f1054a6996d99a4ef5b5a0711cfbf2f8ca2ef5161d6ee701ea8015",
|
|
12
|
+
"MediaInfo_CLI_26.05_Windows_ARM64.zip": "6b403fa1411730672adefa8d49d97cbf7163eed7fc5c1256c9a6e9f915fde1a8",
|
|
13
|
+
"MediaInfo_CLI_26.05_Windows_x64.zip": "f7f80620ce6d14f4995f0de6f98e3ef18ad29496db01899571152ee3311229f9",
|
|
14
|
+
"mkbrr_1.24.0_windows_x86_64.zip": "23b923a26d50e3afabcd99938ea70a510904a98365f698bbeaae057ec1a51711",
|
|
15
|
+
"mkbrr_1.24.0_darwin_arm64.tar.gz": "99c939d1b3e7329d1cf82c90adceb93291eefc34f72ca263577b9c4826172c1e",
|
|
16
|
+
"mkbrr_1.24.0_darwin_x86_64.tar.gz": "26092fa6b59ec79ede4be8d09f9f6f3135af1ccf0f4df312215b2ed92691e7dd",
|
|
17
|
+
"mkbrr_1.24.0_linux_x86_64.tar.gz": "bd23b2e2aa62a943eb5cfea23fa250e60b9ba2169a36e27aaeec980d82dc47a5",
|
|
18
|
+
"mkbrr_1.24.0_linux_arm64.tar.gz": "dada7d9aab0bd0854cdf3b5473b77d7f04b7dcf99ef519b45b522959f9db78e7",
|
|
19
|
+
"mkbrr_1.24.0_linux_arm.tar.gz": "7480a96b9dafd458be1769d8c743bc85c53034a9673252b831b3130e8c1b758d",
|
|
20
|
+
"mkbrr_1.24.0_freebsd_x86_64.tar.gz": "d3c2fe3bc9bad2467faa38124f7f1ebf3e70b8b32300486ee21f0667bd7fc6c7",
|
|
21
|
+
"mkbrr_1.18.0_linux_x86_64.tar.gz": "a796bd97dfb093e18a1a509c8986580498e65253582983a462b977b359f987b9",
|
|
22
|
+
"mkbrr_1.18.0_linux_arm64.tar.gz": "1c187ab2b860e637296d6f0deb4c2e7754a4c1e249b0226f0be671170689de24",
|
|
23
|
+
"mkbrr_1.18.0_linux_arm.tar.gz": "f622595f6afee302c72c89abdd9f31ad3197bd85d45a8b482f97ebd21930ac51",
|
|
24
|
+
"bdinfo_0.4.0_windows_amd64.zip": "2291df819388739287b042694adc5bc8e9b90583968802ff3a4ab1ace3f658f9",
|
|
25
|
+
"bdinfo_0.4.0_darwin_amd64.tar.gz": "ecb47628798aa1bde3188b4d47f135e9baeee628345921b2f993590b42e22b61",
|
|
26
|
+
"bdinfo_0.4.0_darwin_arm64.tar.gz": "27bb0f0dcc252850075bf6e30b65c0ce42a07c5afa9c52f42c69422df36c6428",
|
|
27
|
+
"bdinfo_0.4.0_linux_amd64.tar.gz": "78fd90627c5d9a2bddd3126875d036e73bb4178192fe786c29b0d6c6ccc3b3eb",
|
|
28
|
+
"bdinfo_0.4.0_linux_arm64.tar.gz": "b98a58f903984b86622387db45be9142485c6171dcd5de41c951c7a3de34d6bf",
|
|
29
|
+
"bdinfo_0.4.0_linux_arm.tar.gz": "94bd7a32d90ce6118695ab50940b048d91d42f9e48f3d9e879ac3d0721c83463",
|
|
30
|
+
"7zr.exe": "abcf64ae1cbafddb5395e4cdd3bdc7e3e0561d54a0c6380e3dd43bdbffe519a2",
|
|
31
|
+
"7z2601-mac.tar.xz": "0b6b930dbf82742e3f1014c35072a6b8b3aab183fece348e7f723675f1c5bea2",
|
|
32
|
+
"7z2601-linux-x64.tar.xz": "8ea0fc8a135e7b848e80a4116fe22dff56c8c4518dde1f43cce67f4e340b437a",
|
|
33
|
+
"7z2601-linux-arm64.tar.xz": "39f8c9070c300a63c7484d9a983119ef3edf841e1ddf69f1affae29fdec5f612",
|
|
34
|
+
"7z2601-linux-arm.tar.xz": "72c19911abb6964fcf85ebe213dfcee57bad892345e03bb940c5a27a1050b3bf",
|
|
35
|
+
"pesto-windows-x86_64.exe": "25306273eb6ed91abfb465d9a3e60adceb868883f7c456926d2de4eee1e57236",
|
|
36
|
+
"pesto-linux-x86_64": "4a357e37c5f867694fa95d025a7aba836d43f6cfa410f1ab78e86f817f4c656c",
|
|
37
|
+
"par2cmdline-turbo-1.4.0-win-x64.zip": "7905d1d6aced2b2ca30d824b4954e6bf740dc9d6cfec718b0ab146b5fc0d6327",
|
|
38
|
+
"par2cmdline-turbo-1.4.0-win-arm64.zip": "89870943c142b1360ab79bb287e47d5bffc686e159581750917207bbad2f6dcb",
|
|
39
|
+
"par2cmdline-turbo-1.4.0-macos-arm64.zip": "926139d3cf18f6c4e4aeb25d6fc12b758cdf4936788fb46acd18caf21ffa9a15",
|
|
40
|
+
"par2cmdline-turbo-1.4.0-macos-amd64.zip": "29ebb3629911a5b3ce4cdd8723a551a2877771b633630f443d99637559ef76be",
|
|
41
|
+
"par2cmdline-turbo-1.4.0-linux-amd64.zip": "0be495172b4b8aeabda39c493e47de652813fab88ae745c8633e901c05494281",
|
|
42
|
+
"par2cmdline-turbo-1.4.0-linux-arm64.zip": "1bb2acb2c549bb3a2e91be3ac6291b00d4b657a56ab23f763f2161ffe7df0fcd",
|
|
43
|
+
"nyuu-v0.4.2-win32.7z": "b14eb105e064ec2bd8f6d872c1c652a9943ea54d767c80554617f4eff6c801b8",
|
|
44
|
+
"nyuu-v0.4.2-linux-aarch64.tar.xz": "8a94f3f775996e4469736494074ac7663ff463748b0e302c2bc13d0ff4a88c0b",
|
|
45
|
+
"nyuu-v0.4.2-linux-amd64.tar.xz": "bbea69ffaf1d8ed3465935157e3842fe7a38bade2703504879eb8bc7c0a83dff",
|
|
46
|
+
"nyuu-v0.4.2-macos-x64.tar.xz": "040c56a486bc4ac7e3b0eed7a482ffce1bbf747ff731ad45ffd99d7230fcb2a0",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def verify_downloaded_asset(path: Path, asset: str) -> None:
|
|
51
|
+
"""Fail closed unless a downloaded executable archive matches its pinned hash."""
|
|
52
|
+
expected = SHA256_BY_ASSET.get(asset)
|
|
53
|
+
if expected is None:
|
|
54
|
+
raise RuntimeError(f"No SHA-256 checksum is pinned for {asset}")
|
|
55
|
+
with path.open("rb") as asset_file:
|
|
56
|
+
actual = hashlib.file_digest(asset_file, "sha256").hexdigest()
|
|
57
|
+
if actual != expected:
|
|
58
|
+
raise RuntimeError(f"SHA-256 checksum mismatch for {asset}")
|
bin/get_7z.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Upload Assistant © 2026 Audionut & wastaken7 — Licensed under UAPL v1.0
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
4
|
+
import shutil
|
|
5
|
+
import stat
|
|
6
|
+
import tarfile
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import aiofiles
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
from bin.download_integrity import verify_downloaded_asset
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from src.console import console, logger
|
|
16
|
+
except ImportError:
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
20
|
+
|
|
21
|
+
class SimpleConsole:
|
|
22
|
+
def print(self, message: str, markup: bool = False) -> None: # noqa: ARG002
|
|
23
|
+
print(message)
|
|
24
|
+
|
|
25
|
+
console = SimpleConsole()
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SevenZipBinaryManager:
|
|
30
|
+
"""Download 7-Zip binaries for the host architecture."""
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
async def ensure_7z_binary(base_dir: str | Path, version: str = "26.01") -> str:
|
|
34
|
+
system = platform.system().lower()
|
|
35
|
+
machine = platform.machine().lower()
|
|
36
|
+
logger.debug(f"[blue]7-Zip: Detected system: {system}, architecture: {machine}[/blue]")
|
|
37
|
+
|
|
38
|
+
platform_map: dict[str, dict[str, dict[str, str]]] = {
|
|
39
|
+
"windows": {
|
|
40
|
+
"x86_64": {"file": "7zr.exe", "folder": "windows/x86_64"},
|
|
41
|
+
"amd64": {"file": "7zr.exe", "folder": "windows/x86_64"},
|
|
42
|
+
"x86": {"file": "7zr.exe", "folder": "windows/x86"},
|
|
43
|
+
"arm64": {"file": "7zr.exe", "folder": "windows/arm64"},
|
|
44
|
+
},
|
|
45
|
+
"darwin": {
|
|
46
|
+
"arm64": {"file": "7z2601-mac.tar.xz", "folder": "macos/arm64"},
|
|
47
|
+
"x86_64": {"file": "7z2601-mac.tar.xz", "folder": "macos/x86_64"},
|
|
48
|
+
"amd64": {"file": "7z2601-mac.tar.xz", "folder": "macos/x86_64"},
|
|
49
|
+
},
|
|
50
|
+
"linux": {
|
|
51
|
+
"x86_64": {"file": "7z2601-linux-x64.tar.xz", "folder": "linux/amd64"},
|
|
52
|
+
"amd64": {"file": "7z2601-linux-x64.tar.xz", "folder": "linux/amd64"},
|
|
53
|
+
"arm64": {"file": "7z2601-linux-arm64.tar.xz", "folder": "linux/arm64"},
|
|
54
|
+
"aarch64": {"file": "7z2601-linux-arm64.tar.xz", "folder": "linux/arm64"},
|
|
55
|
+
"arm": {"file": "7z2601-linux-arm.tar.xz", "folder": "linux/arm"},
|
|
56
|
+
"armv7l": {"file": "7z2601-linux-arm.tar.xz", "folder": "linux/arm"},
|
|
57
|
+
"armv6l": {"file": "7z2601-linux-arm.tar.xz", "folder": "linux/arm"},
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if system not in platform_map or machine not in platform_map[system]:
|
|
62
|
+
raise Exception(f"Unsupported platform for 7z: {system} {machine}")
|
|
63
|
+
|
|
64
|
+
platform_info = platform_map[system][machine]
|
|
65
|
+
file_pattern = platform_info["file"]
|
|
66
|
+
folder_path = platform_info["folder"]
|
|
67
|
+
|
|
68
|
+
bin_dir = Path(base_dir) / "bin" / "7z" / folder_path
|
|
69
|
+
bin_dir.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
|
|
71
|
+
binary_name = "7zr.exe" if system == "windows" else "7zz"
|
|
72
|
+
binary_path = bin_dir / binary_name
|
|
73
|
+
version_path = bin_dir / version
|
|
74
|
+
|
|
75
|
+
binary_exists = binary_path.exists() and binary_path.is_file()
|
|
76
|
+
binary_executable = system == "windows" or os.access(binary_path, os.X_OK)
|
|
77
|
+
binary_valid = binary_exists and binary_executable
|
|
78
|
+
|
|
79
|
+
if version_path.exists() and version_path.is_file() and binary_valid:
|
|
80
|
+
logger.debug("[blue]7-Zip binary is up to date[/blue]")
|
|
81
|
+
return str(binary_path)
|
|
82
|
+
|
|
83
|
+
logger.info("[yellow]Binary '7z' not found. Attempting to download automatically...[/yellow]")
|
|
84
|
+
|
|
85
|
+
# Cleanup old files
|
|
86
|
+
if binary_path.exists():
|
|
87
|
+
binary_path.unlink()
|
|
88
|
+
if version_path.exists():
|
|
89
|
+
version_path.unlink()
|
|
90
|
+
|
|
91
|
+
download_url = f"https://github.com/ip7z/7zip/releases/download/{version}/{file_pattern}"
|
|
92
|
+
logger.debug(f"[blue]7-Zip Download URL: {download_url}[/blue]")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
async with (
|
|
96
|
+
httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client,
|
|
97
|
+
client.stream("GET", download_url, timeout=60.0) as response,
|
|
98
|
+
):
|
|
99
|
+
response.raise_for_status()
|
|
100
|
+
temp_file = bin_dir / f"temp_{file_pattern}"
|
|
101
|
+
async with aiofiles.open(temp_file, "wb") as f:
|
|
102
|
+
async for chunk in response.aiter_bytes(chunk_size=8192):
|
|
103
|
+
await f.write(chunk)
|
|
104
|
+
|
|
105
|
+
logger.debug(f"[green]Downloaded 7-Zip package: {file_pattern}[/green]")
|
|
106
|
+
verify_downloaded_asset(temp_file, file_pattern)
|
|
107
|
+
|
|
108
|
+
if file_pattern.endswith(".exe"):
|
|
109
|
+
# Windows 7zr.exe is a raw executable
|
|
110
|
+
shutil.move(str(temp_file), str(binary_path))
|
|
111
|
+
else:
|
|
112
|
+
# Linux/macOS are tar.xz archives
|
|
113
|
+
try:
|
|
114
|
+
with tarfile.open(temp_file, "r:xz") as tar_ref:
|
|
115
|
+
# Secure extract: prevent path traversal
|
|
116
|
+
for member in tar_ref.getmembers():
|
|
117
|
+
if member.islnk() or member.issym():
|
|
118
|
+
continue
|
|
119
|
+
if Path(member.name).is_absolute() or ".." in member.name or member.name.startswith("/"):
|
|
120
|
+
continue
|
|
121
|
+
full_path = os.path.realpath(Path(bin_dir) / member.name)
|
|
122
|
+
base_path = os.path.realpath(bin_dir)
|
|
123
|
+
if not full_path.startswith(base_path + os.sep) and full_path != base_path:
|
|
124
|
+
continue
|
|
125
|
+
tar_ref.extract(member, str(bin_dir))
|
|
126
|
+
|
|
127
|
+
# Locate 7zz binary in extracted output
|
|
128
|
+
if not binary_path.exists():
|
|
129
|
+
for p in bin_dir.rglob("7zz"):
|
|
130
|
+
if p.is_file():
|
|
131
|
+
shutil.move(str(p), str(binary_path))
|
|
132
|
+
break
|
|
133
|
+
finally:
|
|
134
|
+
if temp_file.exists():
|
|
135
|
+
temp_file.unlink()
|
|
136
|
+
|
|
137
|
+
if system != "windows" and binary_path.exists():
|
|
138
|
+
binary_path.chmod(binary_path.stat().st_mode | stat.S_IEXEC)
|
|
139
|
+
|
|
140
|
+
async with aiofiles.open(version_path, "w", encoding="utf-8") as version_file:
|
|
141
|
+
await version_file.write(f"7-Zip version {version} installed successfully.")
|
|
142
|
+
|
|
143
|
+
return str(binary_path)
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
raise Exception(f"Failed to setup 7z binary: {e}") from e
|
bin/get_bdinfo.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Upload Assistant © 2025 Audionut & wastaken7 — Licensed under UAPL v1.0
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import stat
|
|
7
|
+
import tarfile
|
|
8
|
+
import zipfile
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import aiofiles
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from bin.download_integrity import verify_downloaded_asset
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from src.console import console, logger
|
|
18
|
+
except ImportError:
|
|
19
|
+
import logging
|
|
20
|
+
|
|
21
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
22
|
+
|
|
23
|
+
class SimpleConsole:
|
|
24
|
+
def print(self, message: str, markup: bool = False) -> None: # noqa: ARG002
|
|
25
|
+
print(message)
|
|
26
|
+
|
|
27
|
+
console = SimpleConsole()
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BDInfoBinaryManager:
|
|
32
|
+
"""Download autobrr/go-bdinfo binaries for the host architecture.
|
|
33
|
+
|
|
34
|
+
Default version pinned (see https://github.com/autobrr/go-bdinfo/releases).
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
async def ensure_bdinfo_binary(base_dir: str | Path, version: str = "v0.4.0") -> str:
|
|
39
|
+
system = platform.system().lower()
|
|
40
|
+
machine = platform.machine().lower()
|
|
41
|
+
logger.debug(f"[blue]Detected system: {system}, architecture: {machine}[/blue]")
|
|
42
|
+
|
|
43
|
+
platform_map: dict[str, dict[str, dict[str, str]]] = {
|
|
44
|
+
"windows": {
|
|
45
|
+
"x86_64": {"asset": "windows_amd64.zip", "folder": "windows/x86_64"},
|
|
46
|
+
"amd64": {"asset": "windows_amd64.zip", "folder": "windows/x86_64"},
|
|
47
|
+
},
|
|
48
|
+
"darwin": {
|
|
49
|
+
"arm64": {"asset": "darwin_arm64.tar.gz", "folder": "macos/arm64"},
|
|
50
|
+
"x86_64": {"asset": "darwin_amd64.tar.gz", "folder": "macos/x86_64"},
|
|
51
|
+
"amd64": {"asset": "darwin_amd64.tar.gz", "folder": "macos/x86_64"},
|
|
52
|
+
},
|
|
53
|
+
"linux": {
|
|
54
|
+
"x86_64": {"asset": "linux_amd64.tar.gz", "folder": "linux/amd64"},
|
|
55
|
+
"amd64": {"asset": "linux_amd64.tar.gz", "folder": "linux/amd64"},
|
|
56
|
+
"arm64": {"asset": "linux_arm64.tar.gz", "folder": "linux/arm64"},
|
|
57
|
+
"aarch64": {"asset": "linux_arm64.tar.gz", "folder": "linux/arm64"},
|
|
58
|
+
"armv7l": {"asset": "linux_arm.tar.gz", "folder": "linux/arm"},
|
|
59
|
+
"armv6l": {"asset": "linux_arm.tar.gz", "folder": "linux/arm"},
|
|
60
|
+
"arm": {"asset": "linux_arm.tar.gz", "folder": "linux/arm"},
|
|
61
|
+
},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if system not in platform_map or machine not in platform_map[system]:
|
|
65
|
+
raise Exception(f"Unsupported platform: {system} {machine}")
|
|
66
|
+
|
|
67
|
+
platform_info = platform_map[system][machine]
|
|
68
|
+
release_version = version.removeprefix("v")
|
|
69
|
+
file_pattern = f"bdinfo_{release_version}_{platform_info['asset']}"
|
|
70
|
+
folder_path = platform_info["folder"]
|
|
71
|
+
logger.debug(f"[blue]Using file pattern: {file_pattern}[/blue]")
|
|
72
|
+
logger.debug(f"[blue]Target folder: {folder_path}[/blue]")
|
|
73
|
+
|
|
74
|
+
bin_dir = Path(base_dir) / "bin" / "bdinfo" / folder_path
|
|
75
|
+
bin_dir.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
logger.debug(f"[blue]Binary directory: {bin_dir}[/blue]")
|
|
77
|
+
|
|
78
|
+
binary_name = "bdinfo.exe" if system == "windows" else "bdinfo"
|
|
79
|
+
binary_path = bin_dir / binary_name
|
|
80
|
+
logger.debug(f"[blue]Binary path: {binary_path}[/blue]")
|
|
81
|
+
|
|
82
|
+
version_path = bin_dir / version
|
|
83
|
+
binary_exists = binary_path.exists() and binary_path.is_file()
|
|
84
|
+
binary_executable = system == "windows" or os.access(binary_path, os.X_OK)
|
|
85
|
+
binary_valid = binary_exists and binary_executable
|
|
86
|
+
|
|
87
|
+
def cleanup_old_version_files() -> None:
|
|
88
|
+
for candidate in bin_dir.iterdir():
|
|
89
|
+
if not candidate.is_file():
|
|
90
|
+
continue
|
|
91
|
+
if candidate.name == version or candidate.name == binary_name:
|
|
92
|
+
continue
|
|
93
|
+
if candidate.name.startswith("v"):
|
|
94
|
+
if system != "windows":
|
|
95
|
+
Path(candidate).chmod(0o644)
|
|
96
|
+
candidate.unlink()
|
|
97
|
+
logger.debug(f"[blue]Removed old version file at: {candidate}[/blue]")
|
|
98
|
+
|
|
99
|
+
if version_path.exists() and version_path.is_file() and binary_valid:
|
|
100
|
+
cleanup_old_version_files()
|
|
101
|
+
logger.debug("[blue]bdinfo version is up to date[/blue]")
|
|
102
|
+
return str(binary_path)
|
|
103
|
+
|
|
104
|
+
# Remove any old binary/version markers
|
|
105
|
+
if binary_path.exists() and binary_path.is_file():
|
|
106
|
+
if system != "windows":
|
|
107
|
+
Path(binary_path).chmod(0o600)
|
|
108
|
+
binary_path.unlink()
|
|
109
|
+
logger.debug(f"[blue]Removed existing binary at: {binary_path}[/blue]")
|
|
110
|
+
|
|
111
|
+
if version_path.exists():
|
|
112
|
+
if system != "windows":
|
|
113
|
+
Path(version_path).chmod(0o644)
|
|
114
|
+
version_path.unlink()
|
|
115
|
+
logger.debug(f"[blue]Removed existing version file at: {version_path}[/blue]")
|
|
116
|
+
|
|
117
|
+
cleanup_old_version_files()
|
|
118
|
+
|
|
119
|
+
# Construct download URL using autobrr/go-bdinfo release asset filename.
|
|
120
|
+
download_url = f"https://github.com/autobrr/go-bdinfo/releases/download/{version}/{file_pattern}"
|
|
121
|
+
logger.debug(f"[blue]Download URL: {download_url}[/blue]")
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
async with (
|
|
125
|
+
httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client,
|
|
126
|
+
client.stream("GET", download_url, timeout=60.0) as response,
|
|
127
|
+
):
|
|
128
|
+
response.raise_for_status()
|
|
129
|
+
temp_archive = bin_dir / f"temp_{file_pattern}"
|
|
130
|
+
async with aiofiles.open(temp_archive, "wb") as f:
|
|
131
|
+
async for chunk in response.aiter_bytes(chunk_size=8192):
|
|
132
|
+
await f.write(chunk)
|
|
133
|
+
logger.debug(f"[green]Downloaded {file_pattern}[/green]")
|
|
134
|
+
verify_downloaded_asset(temp_archive, file_pattern)
|
|
135
|
+
|
|
136
|
+
# Extract archive safely and ensure temporary archive is always removed.
|
|
137
|
+
try:
|
|
138
|
+
if file_pattern.endswith(".zip"):
|
|
139
|
+
with zipfile.ZipFile(temp_archive, "r") as zip_ref:
|
|
140
|
+
|
|
141
|
+
def safe_extract_zip(zip_file: zipfile.ZipFile, path: str = ".") -> None:
|
|
142
|
+
for member in zip_file.namelist():
|
|
143
|
+
info = zip_file.getinfo(member)
|
|
144
|
+
perm = info.external_attr >> 16
|
|
145
|
+
if stat.S_ISLNK(perm):
|
|
146
|
+
logger.debug(f"[yellow]Warning: Skipping symlink: {member}[/yellow]")
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
# Check for absolute paths and directory traversal
|
|
150
|
+
if Path(member).is_absolute() or ".." in member or member.startswith("/"):
|
|
151
|
+
logger.debug(f"[yellow]Warning: Skipping dangerous path: {member}[/yellow]")
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
# Verify final path is inside target directory
|
|
155
|
+
full_path = os.path.realpath(Path(path) / member)
|
|
156
|
+
base_path = os.path.realpath(path)
|
|
157
|
+
if not full_path.startswith(base_path + os.sep) and full_path != base_path:
|
|
158
|
+
logger.debug(f"[yellow]Warning: Skipping path outside target directory: {member}[/yellow]")
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
# Check for reasonable file sizes (prevent zip bombs)
|
|
162
|
+
try:
|
|
163
|
+
file_size = info.file_size
|
|
164
|
+
except Exception:
|
|
165
|
+
file_size = 0
|
|
166
|
+
|
|
167
|
+
if file_size > 100 * 1024 * 1024:
|
|
168
|
+
logger.debug(f"[yellow]Warning: Skipping oversized file: {member} ({file_size} bytes)[/yellow]")
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
# Extract the safe member
|
|
172
|
+
zip_file.extract(member, path)
|
|
173
|
+
logger.debug(f"[cyan]Extracted: {member}[/cyan]")
|
|
174
|
+
|
|
175
|
+
safe_extract_zip(zip_ref, str(bin_dir))
|
|
176
|
+
|
|
177
|
+
elif file_pattern.endswith(".tar.gz"):
|
|
178
|
+
with tarfile.open(temp_archive, "r:gz") as tar_ref:
|
|
179
|
+
|
|
180
|
+
def safe_extract_tar(tar_file: tarfile.TarFile, path: str = ".") -> None:
|
|
181
|
+
for member in tar_file.getmembers():
|
|
182
|
+
if member.islnk() or member.issym():
|
|
183
|
+
logger.debug(f"[yellow]Warning: Skipping link entry: {member.name}[/yellow]")
|
|
184
|
+
continue
|
|
185
|
+
if Path(member.name).is_absolute() or ".." in member.name or member.name.startswith("/"):
|
|
186
|
+
logger.debug(f"[yellow]Warning: Skipping dangerous path: {member.name}[/yellow]")
|
|
187
|
+
continue
|
|
188
|
+
full_path = os.path.realpath(Path(path) / member.name)
|
|
189
|
+
base_path = os.path.realpath(path)
|
|
190
|
+
if not full_path.startswith(base_path + os.sep) and full_path != base_path:
|
|
191
|
+
logger.debug(f"[yellow]Warning: Skipping path outside target directory: {member.name}[/yellow]")
|
|
192
|
+
continue
|
|
193
|
+
if member.size > 100 * 1024 * 1024:
|
|
194
|
+
logger.debug(f"[yellow]Warning: Skipping oversized file: {member.name} ({member.size} bytes)[/yellow]")
|
|
195
|
+
continue
|
|
196
|
+
tar_file.extract(member, path)
|
|
197
|
+
logger.debug(f"[cyan]Extracted: {member.name}[/cyan]")
|
|
198
|
+
|
|
199
|
+
safe_extract_tar(tar_ref, str(bin_dir))
|
|
200
|
+
|
|
201
|
+
# If extraction created a nested directory (common for GitHub release zips),
|
|
202
|
+
# search for the bdinfo executable and move it to the expected binary path.
|
|
203
|
+
if not binary_path.exists():
|
|
204
|
+
binary_basename = binary_name
|
|
205
|
+
found = None
|
|
206
|
+
for p in bin_dir.rglob(binary_basename):
|
|
207
|
+
if p.is_file():
|
|
208
|
+
found = p
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
if found:
|
|
212
|
+
# Move to target location
|
|
213
|
+
shutil.move(str(found), str(binary_path))
|
|
214
|
+
|
|
215
|
+
if system != "windows" and binary_path.exists():
|
|
216
|
+
binary_path.chmod(binary_path.stat().st_mode | stat.S_IEXEC)
|
|
217
|
+
|
|
218
|
+
async with aiofiles.open(version_path, "w", encoding="utf-8") as version_file:
|
|
219
|
+
await version_file.write(f"autobrr/go-bdinfo version {version} installed successfully.")
|
|
220
|
+
return str(binary_path)
|
|
221
|
+
finally:
|
|
222
|
+
try:
|
|
223
|
+
if temp_archive.exists():
|
|
224
|
+
temp_archive.unlink()
|
|
225
|
+
logger.debug(f"[blue]Removed temporary archive: {temp_archive}[/blue]")
|
|
226
|
+
except Exception as unlink_exc:
|
|
227
|
+
logger.debug(f"[yellow]Warning: Failed to remove temporary archive {temp_archive}: {unlink_exc}[/yellow]")
|
|
228
|
+
except httpx.RequestError as e:
|
|
229
|
+
raise Exception(f"Failed to download bdinfo binary: {e}") from e
|
|
230
|
+
except (zipfile.BadZipFile, tarfile.TarError) as e:
|
|
231
|
+
raise Exception(f"Failed to extract bdinfo binary: {e}") from e
|
bin/get_bdinfo_docker.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Upload Assistant © 2025 Audionut & wastaken7 — Licensed under UAPL v1.0
|
|
3
|
+
"""
|
|
4
|
+
Docker-specific script to download bdinfo binaries for Linux containers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
import tarfile
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import requests
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
17
|
+
|
|
18
|
+
from bin.download_integrity import verify_downloaded_asset
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from src.console import console, logger
|
|
22
|
+
except ImportError:
|
|
23
|
+
import logging
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
26
|
+
|
|
27
|
+
class SimpleConsole:
|
|
28
|
+
def print(self, message: str, markup: bool = False) -> None: # noqa: ARG002
|
|
29
|
+
print(message)
|
|
30
|
+
|
|
31
|
+
console = SimpleConsole()
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
BDINFO_VERSION = "v0.4.0"
|
|
36
|
+
BASE_RELEASE_URL = "https://github.com/autobrr/go-bdinfo/releases/download"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def download_file(url: str, output_path: Path) -> None:
|
|
40
|
+
logger.info(f"Downloading: {url}", extra={"markup": False})
|
|
41
|
+
resp = requests.get(url, stream=True, timeout=60)
|
|
42
|
+
resp.raise_for_status()
|
|
43
|
+
with Path(output_path).open("wb") as f:
|
|
44
|
+
for chunk in resp.iter_content(chunk_size=8192):
|
|
45
|
+
f.write(chunk)
|
|
46
|
+
logger.info(f"Downloaded: {output_path.name}", extra={"markup": False})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def secure_extract_tar(tar_path: Path, extract_to: Path) -> None:
|
|
50
|
+
with tarfile.open(tar_path, "r:gz") as tar_ref:
|
|
51
|
+
base_path = extract_to.resolve()
|
|
52
|
+
for member in tar_ref.getmembers():
|
|
53
|
+
if member.issym() or member.islnk():
|
|
54
|
+
logger.warning(f"Warning: Skipping link: {member.name}", extra={"markup": False})
|
|
55
|
+
continue
|
|
56
|
+
if Path(member.name).is_absolute() or ".." in Path(member.name).parts:
|
|
57
|
+
logger.warning(f"Warning: Skipping dangerous path: {member.name}", extra={"markup": False})
|
|
58
|
+
continue
|
|
59
|
+
try:
|
|
60
|
+
final_path = (base_path / member.name).resolve()
|
|
61
|
+
try:
|
|
62
|
+
os.path.commonpath([str(base_path), str(final_path)])
|
|
63
|
+
if not str(final_path).startswith(str(base_path) + os.sep) and final_path != base_path:
|
|
64
|
+
logger.warning(f"Warning: Path outside base directory: {member.name}", extra={"markup": False})
|
|
65
|
+
continue
|
|
66
|
+
except ValueError:
|
|
67
|
+
logger.warning(f"Warning: Invalid path resolution: {member.name}", extra={"markup": False})
|
|
68
|
+
continue
|
|
69
|
+
except (OSError, ValueError) as e:
|
|
70
|
+
logger.warning(f"Warning: Path resolution failed for {member.name}: {e}", extra={"markup": False})
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
if not (member.isfile() or member.isdir()):
|
|
74
|
+
logger.warning(f"Warning: Skipping non-regular file: {member.name}", extra={"markup": False})
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
if member.isfile() and member.size > 100 * 1024 * 1024:
|
|
78
|
+
logger.warning(f"Warning: Skipping oversized file: {member.name} ({member.size} bytes)", extra={"markup": False})
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
if member.isdir():
|
|
82
|
+
target_dir = base_path / member.name
|
|
83
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
target_dir.chmod(0o700)
|
|
85
|
+
elif member.isfile():
|
|
86
|
+
target_file = base_path / member.name
|
|
87
|
+
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
source = tar_ref.extractfile(member)
|
|
89
|
+
if source is not None:
|
|
90
|
+
with source, Path(target_file).open("wb") as out_f:
|
|
91
|
+
out_f.write(source.read())
|
|
92
|
+
target_file.chmod(0o600)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def download_bdinfo_for_docker(base_dir: Path = Path("/Upload-Assistant"), version: str = BDINFO_VERSION) -> str:
|
|
96
|
+
system = platform.system().lower()
|
|
97
|
+
machine = platform.machine().lower()
|
|
98
|
+
logger.info(f"System: {system}, Architecture: {machine}", extra={"markup": False})
|
|
99
|
+
|
|
100
|
+
if system != "linux":
|
|
101
|
+
raise Exception(f"This script is only for Linux containers, got: {system}")
|
|
102
|
+
|
|
103
|
+
if machine in ("amd64", "x86_64"):
|
|
104
|
+
asset = "linux_amd64.tar.gz"
|
|
105
|
+
folder = "linux/amd64"
|
|
106
|
+
elif machine in ("arm64", "aarch64"):
|
|
107
|
+
asset = "linux_arm64.tar.gz"
|
|
108
|
+
folder = "linux/arm64"
|
|
109
|
+
elif machine.startswith("arm"):
|
|
110
|
+
asset = "linux_arm.tar.gz"
|
|
111
|
+
folder = "linux/arm"
|
|
112
|
+
else:
|
|
113
|
+
raise Exception(f"Unsupported architecture: {machine}")
|
|
114
|
+
|
|
115
|
+
file_pattern = f"bdinfo_{version.removeprefix('v')}_{asset}"
|
|
116
|
+
bin_dir = base_dir / "bin" / "bdinfo" / folder
|
|
117
|
+
bin_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
binary_path = bin_dir / "bdinfo"
|
|
119
|
+
version_path = bin_dir / version
|
|
120
|
+
|
|
121
|
+
if version_path.exists() and binary_path.exists() and os.access(binary_path, os.X_OK):
|
|
122
|
+
logger.info(f"bdinfo {version} already installed", extra={"markup": False})
|
|
123
|
+
return str(binary_path)
|
|
124
|
+
|
|
125
|
+
download_url = f"{BASE_RELEASE_URL}/{version}/{file_pattern}"
|
|
126
|
+
logger.info(f"Downloading bdinfo from: {download_url}", extra={"markup": False})
|
|
127
|
+
|
|
128
|
+
temp_archive = bin_dir / f"temp_{file_pattern}"
|
|
129
|
+
download_file(download_url, temp_archive)
|
|
130
|
+
verify_downloaded_asset(temp_archive, file_pattern)
|
|
131
|
+
|
|
132
|
+
logger.info(f"Extracting {temp_archive} to {bin_dir}", extra={"markup": False})
|
|
133
|
+
secure_extract_tar(temp_archive, bin_dir)
|
|
134
|
+
temp_archive.unlink()
|
|
135
|
+
|
|
136
|
+
# Search for extracted bdinfo executable and move it into place if necessary
|
|
137
|
+
if not binary_path.exists():
|
|
138
|
+
found = None
|
|
139
|
+
for p in bin_dir.rglob("bdinfo"):
|
|
140
|
+
if p.is_file():
|
|
141
|
+
found = p
|
|
142
|
+
break
|
|
143
|
+
if found:
|
|
144
|
+
shutil.move(str(found), str(binary_path))
|
|
145
|
+
|
|
146
|
+
if not binary_path.exists():
|
|
147
|
+
raise Exception(f"Failed to extract bdinfo binary to {binary_path}")
|
|
148
|
+
|
|
149
|
+
Path(binary_path).chmod(0o700)
|
|
150
|
+
|
|
151
|
+
with Path(version_path).open("w", encoding="utf-8") as vf:
|
|
152
|
+
vf.write(f"autobrr/go-bdinfo version {version} installed successfully.")
|
|
153
|
+
|
|
154
|
+
logger.info(f"Installed bdinfo: {binary_path}", extra={"markup": False})
|
|
155
|
+
return str(binary_path)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
try:
|
|
160
|
+
download_bdinfo_for_docker()
|
|
161
|
+
logger.info("bdinfo installation completed successfully!", extra={"markup": False})
|
|
162
|
+
except Exception as exc:
|
|
163
|
+
logger.info(f"ERROR: Failed to install bdinfo: {exc}", extra={"markup": False})
|
|
164
|
+
raise SystemExit(1) from exc
|