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.
Files changed (366) hide show
  1. bin/__init__.py +0 -0
  2. bin/download_integrity.py +58 -0
  3. bin/get_7z.py +146 -0
  4. bin/get_bdinfo.py +231 -0
  5. bin/get_bdinfo_docker.py +164 -0
  6. bin/get_dvd_mediainfo.py +189 -0
  7. bin/get_dynamic_hdr_tools.py +122 -0
  8. bin/get_ffmpeg.py +76 -0
  9. bin/get_ffmpeg_docker.py +116 -0
  10. bin/get_mediainfo.py +183 -0
  11. bin/get_mkbrr.py +367 -0
  12. bin/get_nyuu.py +169 -0
  13. bin/get_par2.py +149 -0
  14. bin/get_pesto.py +107 -0
  15. bin/get_playlist.py +122 -0
  16. config-generator.py +1244 -0
  17. data/__init__.py +18 -0
  18. data/example_config.py +3619 -0
  19. data/tags.json +21 -0
  20. data/templates/summary-mediainfo.csv +7 -0
  21. data/templates/user-args-template.json +47 -0
  22. src/__init__.py +0 -0
  23. src/add_comparison.py +181 -0
  24. src/app_paths.py +41 -0
  25. src/apply_overrides.py +196 -0
  26. src/args.py +1466 -0
  27. src/artwork.py +209 -0
  28. src/audio.py +695 -0
  29. src/audio_classifier.py +397 -0
  30. src/audio_spectrogram.py +384 -0
  31. src/bbcode.py +730 -0
  32. src/bdinfo_comparator.py +196 -0
  33. src/binaries.py +40 -0
  34. src/bluray_com.py +1794 -0
  35. src/book_extractors.py +803 -0
  36. src/book_prep.py +1078 -0
  37. src/btnid.py +249 -0
  38. src/check_requirements.py +114 -0
  39. src/cleanup.py +259 -0
  40. src/clients.py +755 -0
  41. src/cogs/__init__.py +0 -0
  42. src/cogs/redaction.py +192 -0
  43. src/config_helpers.py +36 -0
  44. src/configvalidator.py +879 -0
  45. src/console.py +343 -0
  46. src/cookie_auth.py +818 -0
  47. src/description_languages.py +150 -0
  48. src/description_review.py +86 -0
  49. src/disc_menus.py +401 -0
  50. src/discparse.py +1179 -0
  51. src/dupe_checking.py +1044 -0
  52. src/dynamic_hdr_plot.py +179 -0
  53. src/early_tasks.py +144 -0
  54. src/edition.py +428 -0
  55. src/exceptions.py +46 -0
  56. src/exportmi.py +568 -0
  57. src/genre_map.py +565 -0
  58. src/get_desc.py +2305 -0
  59. src/get_disc.py +90 -0
  60. src/get_name.py +705 -0
  61. src/get_source.py +97 -0
  62. src/get_tracker_data.py +572 -0
  63. src/getseasonep.py +529 -0
  64. src/google_books.py +149 -0
  65. src/igdb.py +175 -0
  66. src/imdb.py +954 -0
  67. src/is_scene.py +273 -0
  68. src/languages.py +491 -0
  69. src/manualpackage.py +109 -0
  70. src/media_extensions.py +7 -0
  71. src/mediainfo.py +127 -0
  72. src/meta.py +696 -0
  73. src/metadata_cache.py +153 -0
  74. src/metadata_searching.py +1026 -0
  75. src/music/__init__.py +7 -0
  76. src/music/analyzer.py +556 -0
  77. src/music/models.py +148 -0
  78. src/music/prep.py +447 -0
  79. src/music/sources.py +615 -0
  80. src/music/validation.py +114 -0
  81. src/myanonamouse.py +188 -0
  82. src/openlibrary.py +196 -0
  83. src/post_upload_hooks.py +197 -0
  84. src/prep.py +447 -0
  85. src/prep_game.py +890 -0
  86. src/prep_helpers.py +1801 -0
  87. src/qbitwait.py +378 -0
  88. src/queuemanage.py +762 -0
  89. src/radarr.py +129 -0
  90. src/region.py +2419 -0
  91. src/rehostimages.py +921 -0
  92. src/screenshot_manifest.py +126 -0
  93. src/screenshot_review.py +505 -0
  94. src/sonarr.py +143 -0
  95. src/tags.py +175 -0
  96. src/takescreens.py +2872 -0
  97. src/temp_paths.py +51 -0
  98. src/tmdb.py +2060 -0
  99. src/torrent_clients/__init__.py +13 -0
  100. src/torrent_clients/deluge.py +28 -0
  101. src/torrent_clients/path_utils.py +89 -0
  102. src/torrent_clients/qbittorrent.py +2170 -0
  103. src/torrent_clients/rtorrent.py +439 -0
  104. src/torrent_clients/transmission.py +43 -0
  105. src/torrentcreate.py +680 -0
  106. src/tracker_descriptions.py +102 -0
  107. src/tracker_images.py +35 -0
  108. src/trackerhandle.py +408 -0
  109. src/trackermeta.py +792 -0
  110. src/trackers/AVISTAZ/__init__.py +1350 -0
  111. src/trackers/AVISTAZ/avistaz.py +484 -0
  112. src/trackers/AVISTAZ/cinemaz.py +388 -0
  113. src/trackers/AVISTAZ/privatehd.py +651 -0
  114. src/trackers/AVISTAZ/routing.py +360 -0
  115. src/trackers/NEXUSPHP/__init__.py +454 -0
  116. src/trackers/NEXUSPHP/lajidui.py +406 -0
  117. src/trackers/NEXUSPHP/lemonhd.py +228 -0
  118. src/trackers/NEXUSPHP/longpt.py +236 -0
  119. src/trackers/NEXUSPHP/oneptba.py +275 -0
  120. src/trackers/NEXUSPHP/ptcafe.py +419 -0
  121. src/trackers/NEXUSPHP/ptfans.py +155 -0
  122. src/trackers/NEXUSPHP/ptgtk.py +192 -0
  123. src/trackers/NEXUSPHP/ptzone.py +233 -0
  124. src/trackers/NEXUSPHP/railgunpt.py +186 -0
  125. src/trackers/NEXUSPHP/xingyungept.py +247 -0
  126. src/trackers/UNIT3D/UNIT3D_TEMPLATE.py +118 -0
  127. src/trackers/UNIT3D/__init__.py +641 -0
  128. src/trackers/UNIT3D/aither.py +163 -0
  129. src/trackers/UNIT3D/asiancinema.py +195 -0
  130. src/trackers/UNIT3D/bitporn.py +238 -0
  131. src/trackers/UNIT3D/blutopia.py +290 -0
  132. src/trackers/UNIT3D/capybarabr.py +388 -0
  133. src/trackers/UNIT3D/cinematik.py +624 -0
  134. src/trackers/UNIT3D/darkpeers.py +657 -0
  135. src/trackers/UNIT3D/dreadvault.py +72 -0
  136. src/trackers/UNIT3D/emuwarez.py +637 -0
  137. src/trackers/UNIT3D/hawkeuno.py +352 -0
  138. src/trackers/UNIT3D/homiehelpdesk.py +238 -0
  139. src/trackers/UNIT3D/infinityhd.py +189 -0
  140. src/trackers/UNIT3D/itatorrents.py +185 -0
  141. src/trackers/UNIT3D/lastdigitalunderground.py +211 -0
  142. src/trackers/UNIT3D/latteam.py +321 -0
  143. src/trackers/UNIT3D/locadora.py +142 -0
  144. src/trackers/UNIT3D/lst.py +323 -0
  145. src/trackers/UNIT3D/luminarr.py +64 -0
  146. src/trackers/UNIT3D/midnightscene.py +250 -0
  147. src/trackers/UNIT3D/nordicquality.py +178 -0
  148. src/trackers/UNIT3D/oldtoonsworld.py +219 -0
  149. src/trackers/UNIT3D/onlyencodes.py +278 -0
  150. src/trackers/UNIT3D/peergarden.py +267 -0
  151. src/trackers/UNIT3D/polishtorrent.py +53 -0
  152. src/trackers/UNIT3D/portugas.py +186 -0
  153. src/trackers/UNIT3D/racing4everyone.py +137 -0
  154. src/trackers/UNIT3D/rastastugan.py +132 -0
  155. src/trackers/UNIT3D/reelflix.py +87 -0
  156. src/trackers/UNIT3D/retromoviesclub.py +203 -0
  157. src/trackers/UNIT3D/rockethd.py +385 -0
  158. src/trackers/UNIT3D/samaritano.py +176 -0
  159. src/trackers/UNIT3D/seedpool.py +239 -0
  160. src/trackers/UNIT3D/shareisland.py +1067 -0
  161. src/trackers/UNIT3D/skipthecommercials.py +86 -0
  162. src/trackers/UNIT3D/theoldschool.py +134 -0
  163. src/trackers/UNIT3D/tlzdigital.py +90 -0
  164. src/trackers/UNIT3D/torrentdesi.py +138 -0
  165. src/trackers/UNIT3D/torrenteros.py +148 -0
  166. src/trackers/UNIT3D/torrenthr.py +65 -0
  167. src/trackers/UNIT3D/ulcx.py +308 -0
  168. src/trackers/UNIT3D/utopia.py +204 -0
  169. src/trackers/UNIT3D/yuscene.py +174 -0
  170. src/trackers/UNIT3D/znth.py +499 -0
  171. src/trackers/USENET/__init__.py +0 -0
  172. src/trackers/USENET/curupira.py +508 -0
  173. src/trackers/USENET/drunkenslug.py +188 -0
  174. src/trackers/USENET/nzbgeek.py +195 -0
  175. src/trackers/USENET/search_helpers.py +238 -0
  176. src/trackers/USENET/suio.py +531 -0
  177. src/trackers/__init__.py +0 -0
  178. src/trackers/alpharatio.py +473 -0
  179. src/trackers/amigosshare.py +1296 -0
  180. src/trackers/anthelion.py +621 -0
  181. src/trackers/beyondhd.py +533 -0
  182. src/trackers/bithdtv.py +200 -0
  183. src/trackers/bjshare.py +1890 -0
  184. src/trackers/brasiltracker.py +1242 -0
  185. src/trackers/broadcasthenet.py +736 -0
  186. src/trackers/cathoderaytube.py +654 -0
  187. src/trackers/common.py +3516 -0
  188. src/trackers/digitalcore.py +314 -0
  189. src/trackers/filelist.py +478 -0
  190. src/trackers/flood.py +272 -0
  191. src/trackers/funfile.py +524 -0
  192. src/trackers/greatposterwall.py +1186 -0
  193. src/trackers/hdbits.py +929 -0
  194. src/trackers/hdspace.py +298 -0
  195. src/trackers/hdtorrents.py +292 -0
  196. src/trackers/immortalseed.py +331 -0
  197. src/trackers/iptorrents.py +489 -0
  198. src/trackers/makingoff.py +1900 -0
  199. src/trackers/mteam.py +631 -0
  200. src/trackers/nebulance.py +285 -0
  201. src/trackers/orpheus.py +608 -0
  202. src/trackers/passthepopcorn.py +1705 -0
  203. src/trackers/pterclub.py +477 -0
  204. src/trackers/ptskit.py +123 -0
  205. src/trackers/retroflix.py +444 -0
  206. src/trackers/speedapp.py +365 -0
  207. src/trackers/swarmazon.py +212 -0
  208. src/trackers/torrentleech.py +427 -0
  209. src/trackers/totheglory.py +379 -0
  210. src/trackers/tvchaosuk.py +958 -0
  211. src/trackersetup.py +1542 -0
  212. src/trackerstatus.py +406 -0
  213. src/tvdb.py +697 -0
  214. src/tvmaze.py +340 -0
  215. src/type_utils.py +29 -0
  216. src/uphelper.py +850 -0
  217. src/uploadscreens.py +1037 -0
  218. src/usenetcreate.py +1443 -0
  219. src/version.py +5 -0
  220. src/video.py +393 -0
  221. src/vs.py +161 -0
  222. src/webui_progress.py +115 -0
  223. src/xxx_keywords.py +43 -0
  224. src/xxx_keywords_list.py +4123 -0
  225. src/xxx_platforms.py +239 -0
  226. upload.py +3201 -0
  227. upload_assistant-3.9.dist-info/METADATA +503 -0
  228. upload_assistant-3.9.dist-info/RECORD +366 -0
  229. upload_assistant-3.9.dist-info/WHEEL +5 -0
  230. upload_assistant-3.9.dist-info/entry_points.txt +3 -0
  231. upload_assistant-3.9.dist-info/licenses/LICENSE +116 -0
  232. upload_assistant-3.9.dist-info/top_level.txt +6 -0
  233. web_ui/__init__.py +4 -0
  234. web_ui/access_log.py +182 -0
  235. web_ui/auth.py +640 -0
  236. web_ui/server.py +6133 -0
  237. web_ui/static/css/theme.css +261 -0
  238. web_ui/static/favicon.ico +0 -0
  239. web_ui/static/img/apple-touch-icon.png +0 -0
  240. web_ui/static/img/icon-192.png +0 -0
  241. web_ui/static/img/logo.png +0 -0
  242. web_ui/static/img/logo.svg +177 -0
  243. web_ui/static/img/providers/discogs.svg +20 -0
  244. web_ui/static/img/providers/douban.svg +1 -0
  245. web_ui/static/img/providers/google_books.svg +1 -0
  246. web_ui/static/img/providers/igdb.svg +13 -0
  247. web_ui/static/img/providers/igdb_light.svg +11 -0
  248. web_ui/static/img/providers/imdb.svg +3 -0
  249. web_ui/static/img/providers/mal.svg +21 -0
  250. web_ui/static/img/providers/musicbrainz.ico +0 -0
  251. web_ui/static/img/providers/openlibrary.svg +123 -0
  252. web_ui/static/img/providers/steam.svg +1 -0
  253. web_ui/static/img/providers/tmdb.svg +1 -0
  254. web_ui/static/img/providers/tvdb.svg +1 -0
  255. web_ui/static/img/providers/tvmaze.svg +17 -0
  256. web_ui/static/img/trackers/1ptba.png +0 -0
  257. web_ui/static/img/trackers/aither.png +0 -0
  258. web_ui/static/img/trackers/alpharatio.png +0 -0
  259. web_ui/static/img/trackers/amigosshare.png +0 -0
  260. web_ui/static/img/trackers/anthelion.png +0 -0
  261. web_ui/static/img/trackers/asiancinema.png +0 -0
  262. web_ui/static/img/trackers/avistaz.png +0 -0
  263. web_ui/static/img/trackers/beyondhd.png +0 -0
  264. web_ui/static/img/trackers/bithdtv.png +0 -0
  265. web_ui/static/img/trackers/bitporn.png +0 -0
  266. web_ui/static/img/trackers/bjshare.png +0 -0
  267. web_ui/static/img/trackers/blutopia.png +0 -0
  268. web_ui/static/img/trackers/brasiltracker.png +0 -0
  269. web_ui/static/img/trackers/broadcasthenet.png +0 -0
  270. web_ui/static/img/trackers/capybarabr.png +0 -0
  271. web_ui/static/img/trackers/cathoderaytube.png +0 -0
  272. web_ui/static/img/trackers/cinematik.png +0 -0
  273. web_ui/static/img/trackers/cinemaz.png +0 -0
  274. web_ui/static/img/trackers/curupira.png +0 -0
  275. web_ui/static/img/trackers/darkpeers.png +0 -0
  276. web_ui/static/img/trackers/desitorrents.png +0 -0
  277. web_ui/static/img/trackers/digitalcore.png +0 -0
  278. web_ui/static/img/trackers/dreadvault.png +0 -0
  279. web_ui/static/img/trackers/drunkenslug.png +0 -0
  280. web_ui/static/img/trackers/emuwarez.png +0 -0
  281. web_ui/static/img/trackers/filelist.png +0 -0
  282. web_ui/static/img/trackers/flood.png +0 -0
  283. web_ui/static/img/trackers/funfile.png +0 -0
  284. web_ui/static/img/trackers/greatposterwall.png +0 -0
  285. web_ui/static/img/trackers/hawkeuno.png +0 -0
  286. web_ui/static/img/trackers/hdbits.png +0 -0
  287. web_ui/static/img/trackers/hdspace.png +0 -0
  288. web_ui/static/img/trackers/hdtorrents.png +0 -0
  289. web_ui/static/img/trackers/homiehelpdesk.png +0 -0
  290. web_ui/static/img/trackers/immortalseed.png +0 -0
  291. web_ui/static/img/trackers/infinityhd.png +0 -0
  292. web_ui/static/img/trackers/iptorrents.png +0 -0
  293. web_ui/static/img/trackers/itatorrents.png +0 -0
  294. web_ui/static/img/trackers/lajidui.png +0 -0
  295. web_ui/static/img/trackers/lastdigitalunderground.png +0 -0
  296. web_ui/static/img/trackers/latteam.png +0 -0
  297. web_ui/static/img/trackers/lemonhd.png +0 -0
  298. web_ui/static/img/trackers/locadora.png +0 -0
  299. web_ui/static/img/trackers/longpt.png +0 -0
  300. web_ui/static/img/trackers/lst.png +0 -0
  301. web_ui/static/img/trackers/luminarr.png +0 -0
  302. web_ui/static/img/trackers/makingoff.png +0 -0
  303. web_ui/static/img/trackers/midnightscene.png +0 -0
  304. web_ui/static/img/trackers/mteam.png +0 -0
  305. web_ui/static/img/trackers/nebulance.png +0 -0
  306. web_ui/static/img/trackers/nordicquality.png +0 -0
  307. web_ui/static/img/trackers/nzbgeek.png +0 -0
  308. web_ui/static/img/trackers/oldtoonsworld.png +0 -0
  309. web_ui/static/img/trackers/onlyencodes.png +0 -0
  310. web_ui/static/img/trackers/orpheus.png +0 -0
  311. web_ui/static/img/trackers/passthepopcorn.png +0 -0
  312. web_ui/static/img/trackers/peergarden.png +0 -0
  313. web_ui/static/img/trackers/polishtorrent.png +0 -0
  314. web_ui/static/img/trackers/portugas.png +0 -0
  315. web_ui/static/img/trackers/privatehd.png +0 -0
  316. web_ui/static/img/trackers/ptcafe.png +0 -0
  317. web_ui/static/img/trackers/pterclub.png +0 -0
  318. web_ui/static/img/trackers/ptfans.png +0 -0
  319. web_ui/static/img/trackers/ptgtk.png +0 -0
  320. web_ui/static/img/trackers/ptskit.png +0 -0
  321. web_ui/static/img/trackers/ptzone.png +0 -0
  322. web_ui/static/img/trackers/racing4everyone.png +0 -0
  323. web_ui/static/img/trackers/railgunpt.png +0 -0
  324. web_ui/static/img/trackers/rastastugan.png +0 -0
  325. web_ui/static/img/trackers/reelflix.png +0 -0
  326. web_ui/static/img/trackers/retroflix.png +0 -0
  327. web_ui/static/img/trackers/retromoviesclub.png +0 -0
  328. web_ui/static/img/trackers/rockethd.png +0 -0
  329. web_ui/static/img/trackers/samaritano.png +0 -0
  330. web_ui/static/img/trackers/seedpool.png +0 -0
  331. web_ui/static/img/trackers/shareisland.png +0 -0
  332. web_ui/static/img/trackers/skipthecommercials.png +0 -0
  333. web_ui/static/img/trackers/speedapp.png +0 -0
  334. web_ui/static/img/trackers/swarmazon.png +0 -0
  335. web_ui/static/img/trackers/theleachzone.png +0 -0
  336. web_ui/static/img/trackers/theoldschool.png +0 -0
  337. web_ui/static/img/trackers/torrenteros.png +0 -0
  338. web_ui/static/img/trackers/torrenthr.png +0 -0
  339. web_ui/static/img/trackers/torrentleech.png +0 -0
  340. web_ui/static/img/trackers/totheglory.png +0 -0
  341. web_ui/static/img/trackers/tvchaosuk.png +0 -0
  342. web_ui/static/img/trackers/ulcx.png +0 -0
  343. web_ui/static/img/trackers/utopia.png +0 -0
  344. web_ui/static/img/trackers/xingyungept.png +0 -0
  345. web_ui/static/img/trackers/yuscene.png +0 -0
  346. web_ui/static/img/trackers/zenith.png +0 -0
  347. web_ui/static/img/webui-icons/book.svg +6 -0
  348. web_ui/static/img/webui-icons/file-structure.svg +2 -0
  349. web_ui/static/img/webui-icons/gamepad.svg +4 -0
  350. web_ui/static/img/webui-icons/movie.svg +7 -0
  351. web_ui/static/img/webui-icons/music.svg +9 -0
  352. web_ui/static/img/webui-icons/palette.svg +8 -0
  353. web_ui/static/img/webui-icons/screenshots.svg +17 -0
  354. web_ui/static/img/webui-icons/settings.svg +4 -0
  355. web_ui/static/img/webui-icons/tv.svg +20 -0
  356. web_ui/static/js/app.js +6626 -0
  357. web_ui/static/js/config_app.js +3988 -0
  358. web_ui/static/js/package-lock.json +3866 -0
  359. web_ui/static/js/package.json +20 -0
  360. web_ui/static/js/shared_utils.js +213 -0
  361. web_ui/static/js/web_ui/static/js/package-lock.json +6 -0
  362. web_ui/static/manifest.webmanifest +20 -0
  363. web_ui/templates/config.html +81 -0
  364. web_ui/templates/index.html +130 -0
  365. web_ui/templates/login.html +320 -0
  366. 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
@@ -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