Justfile for helping me with adding distro torrents to my qBittorrent instance
justfile edited
937 lines 31 kB view raw
1# "This will save me effort in the long run!", I tell myself. I have advanced experience in 2# the economics of effort. 3 4# MIT License 5# 6# Copyright (c) 2026 Jeffrey Serio <hyperreal@moonshadow.dev> 7# 8# Permission is hereby granted, free of charge, to any person obtaining a copy 9# of this software and associated documentation files (the "Software"), to deal 10# in the Software without restriction, including without limitation the rights 11# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12# copies of the Software, and to permit persons to whom the Software is 13# furnished to do so, subject to the following conditions: 14# 15# The above copyright notice and this permission notice shall be included in all 16# copies or substantial portions of the Software. 17# 18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24# SOFTWARE. 25 26 27host := `secret-tool lookup qbt-url admin` 28password := `secret-tool lookup qbt-password admin` 29 30# Add AlmaLinux torrents 31[group('torrent')] 32qbt-alma: 33 #!/usr/bin/env -S uv run --script 34 # /// script 35 # dependencies = [ 36 # "beautifulsoup4", 37 # "qbittorrent-api", 38 # "requests", 39 # ] 40 # /// 41 42 import os 43 import qbittorrentapi 44 import re 45 import requests 46 import subprocess 47 from bs4 import BeautifulSoup 48 from packaging.version import Version 49 50 resp = requests.get("https://almalinux-mirror.dal1.hivelocity.net/", timeout=30) 51 soup = BeautifulSoup(resp.text, "html.parser") 52 53 versions = [] 54 for link in soup.find_all("a"): 55 link_text = link.get("href") 56 res = bool(re.search(r'^\d.*\/$', link_text)) 57 if res: 58 versions.append(link.get("href").strip("/")) 59 versions.sort(key=Version) 60 61 major_versions = [] 62 for v in versions: 63 if v.isdigit(): 64 major_versions.append(v) 65 66 latest_versions = [] 67 for mv in major_versions: 68 full_version = [] 69 for v in versions: 70 if v.startswith(mv): 71 full_version.append(v) 72 full_version.sort(key=Version) 73 latest_versions.append(full_version[-1]) 74 75 urls = [] 76 archs = ["aarch64", "ppc64le", "s390x", "x86_64"] 77 for arch in archs: 78 for ver in latest_versions: 79 urls.append(f"https://almalinux-mirror.dal1.hivelocity.net/{ver}/isos/{arch}/AlmaLinux-{ver}-{arch}.torrent") 80 81 conn_info = dict( 82 host="{{ host }}", 83 username="admin", 84 password="{{ password }}", 85 ) 86 87 with qbittorrentapi.Client(**conn_info) as qbt_client: 88 try: 89 qbt_client.auth_log_in() 90 except qbittorrentapi.LoginFailed: 91 exit("Login failed. Please verify credentials are correct.") 92 93 for torrent in qbt_client.torrents_info(category="distro"): 94 if "Alma" in torrent.name: 95 try: 96 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 97 print(f"Deleted torrent {torrent.name}") 98 except Exception as ex: 99 print(f"Failed to delete torrent {torrent.name}") 100 print(ex) 101 102 for url in urls: 103 try: 104 qbt_client.torrents_add(url, category="distro") 105 print(f"Added torrent {os.path.basename(url)}") 106 except Exception as ex: 107 print(f"Failed to add torrent {os.path.basename(url)}") 108 print(ex) 109 110# Add antiX torrents 111[group('torrent')] 112qbt-antix: 113 #!/usr/bin/env -S uv run --script 114 # /// script 115 # dependencies = [ 116 # "beautifulsoup4", 117 # "qbittorrent-api", 118 # "requests", 119 # ] 120 # /// 121 122 import os 123 import qbittorrentapi 124 import requests 125 from bs4 import BeautifulSoup 126 127 base_url = "https://l2.mxrepo.com/torrents" 128 resp = requests.get(base_url, timeout=30) 129 soup = BeautifulSoup(resp.text, "html.parser") 130 131 urls = [] 132 for link in soup.find_all("a"): 133 if link.get("href").startswith("antiX"): 134 urls.append(f"{base_url}/{link.get('href')}") 135 136 conn_info = dict( 137 host="{{ host }}", 138 username="admin", 139 password="{{ password }}", 140 ) 141 142 with qbittorrentapi.Client(**conn_info) as qbt_client: 143 try: 144 qbt_client.auth_log_in() 145 except qbittorrentapi.LoginFailed: 146 exit("Login failed. Please verify credentials are correct.") 147 148 for torrent in qbt_client.torrents_info(category="distro"): 149 if "antiX" in torrent.name: 150 try: 151 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 152 print(f"Deleted torrent {torrent.name}") 153 except Exception as ex: 154 print(f"Failed to delete torrent {torrent.name}") 155 print(ex) 156 157 for url in urls: 158 try: 159 qbt_client.torrents_add(url, category="distro") 160 print(f"Added torrent {os.path.basename(url)}") 161 except Exception as ex: 162 print(f"Failed to add torrent {os.path.basename(url)}") 163 print(ex) 164 165# Add Debian torrents 166[group('torrent')] 167qbt-debian: 168 #!/usr/bin/env -S uv run --script 169 # /// script 170 # dependencies = [ 171 # "beautifulsoup4", 172 # "qbittorrent-api", 173 # "requests", 174 # ] 175 # /// 176 177 import os 178 import qbittorrentapi 179 import requests 180 from bs4 import BeautifulSoup 181 182 base_url = "https://cdimage.debian.org/debian-cd/current" 183 archs = ["amd64", "arm64", "armel", "armhf", "mips64el", "mipsel", "ppc64el", "s390x"] 184 urls = set() 185 arch_urls = [] 186 for arch in archs: 187 arch_urls.append(f"https://cdimage.debian.org/debian-cd/current/{arch}/bt-dvd") 188 for aurl in arch_urls: 189 resp = requests.get(aurl, timeout=30) 190 soup = BeautifulSoup(resp.text, "html.parser") 191 for link in soup.find_all("a"): 192 if link.get("href").endswith(".torrent"): 193 urls.add(f"{aurl}/{link.get('href')}") 194 195 conn_info = dict( 196 host="{{ host }}", 197 username="admin", 198 password="{{ password }}", 199 ) 200 201 with qbittorrentapi.Client(**conn_info) as qbt_client: 202 try: 203 qbt_client.auth_log_in() 204 except qbittorrentapi.LoginFailed: 205 exit("Login failed. Please verify credentials are correct.") 206 207 for torrent in qbt_client.torrents_info(category="distro"): 208 if "debian" in torrent.name: 209 try: 210 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 211 print(f"Deleted torrent {torrent.name}") 212 except Exception as ex: 213 print(f"Failed to delete torrent {torrent.name}") 214 print(ex) 215 216 for url in urls: 217 try: 218 qbt_client.torrents_add(url, category="distro") 219 print(f"Added torrent {os.path.basename(url)}") 220 except Exception as ex: 221 print(f"Failed to add torrent {os.path.basename(url)}") 222 print(ex) 223 224# Add Devuan torrents 225[group('torrent')] 226qbt-devuan: 227 #!/usr/bin/env -S uv run --script 228 # /// script 229 # dependencies = [ 230 # "beautifulsoup4", 231 # "qbittorrent-api", 232 # "requests", 233 # ] 234 # /// 235 236 import os 237 import qbittorrentapi 238 import requests 239 from bs4 import BeautifulSoup 240 241 base_url = "https://files.devuan.org" 242 resp = requests.get(base_url, timeout=30) 243 soup = BeautifulSoup(resp.text, "html.parser") 244 torrent_files = [] 245 for link in soup.find_all("a"): 246 if link.get("href").endswith(".torrent"): 247 if link.get("href") != "devuan_jessie.torrent": 248 torrent_files.append(link.get("href")) 249 url = f"{base_url}/{torrent_files[-1]}" 250 251 conn_info = dict( 252 host="{{ host }}", 253 username="admin", 254 password="{{ password }}", 255 ) 256 257 with qbittorrentapi.Client(**conn_info) as qbt_client: 258 try: 259 qbt_client.auth_log_in() 260 except qbittorrentapi.LoginFailed: 261 exit("Login failed. Please verify credentials are correct.") 262 263 for torrent in qbt_client.torrents_info(category="distro"): 264 if "devuan" in torrent.name: 265 try: 266 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 267 print(F"Deleted torrent {torrent.name}") 268 except Exception as ex: 269 print(f"Failed to delete torrent {torrent.name}") 270 print(ex) 271 272 try: 273 qbt_client.torrents_add(url, category="distro") 274 print(f"Added torrent {os.path.basename(url)}") 275 except Exception as ex: 276 print(f"Failed to add torrent {os.path.basename(url)}") 277 print(ex) 278 279# Add Fedora torrents 280[group('torrent')] 281qbt-fedora: 282 #!/usr/bin/env -S uv run --script 283 # /// script 284 # dependencies = [ 285 # "beautifulsoup4", 286 # "qbittorrent-api", 287 # "requests", 288 # ] 289 # /// 290 291 import os 292 import qbittorrentapi 293 import requests 294 from bs4 import BeautifulSoup 295 296 base_url = "https://torrent.fedoraproject.org/torrents" 297 urls = [] 298 resp = requests.get(base_url, timeout=30) 299 soup = BeautifulSoup(resp.text, "html.parser") 300 torrents = [] 301 for link in soup.find_all("a"): 302 if link.get("href").endswith(".torrent"): 303 torrents.append(link.get("href")) 304 305 relver = torrents[-1][-10:-8] 306 for link in soup.find_all("a"): 307 if link.get("href").endswith(f"{relver}.torrent"): 308 urls.append(f"{base_url}/{link.get('href')}") 309 310 conn_info = dict( 311 host="{{ host }}", 312 username="admin", 313 password="{{ password }}", 314 ) 315 316 with qbittorrentapi.Client(**conn_info) as qbt_client: 317 try: 318 qbt_client.auth_log_in() 319 except qbittorrentapi.LoginFailed: 320 exit("Login failed. Please verify credentials are correct.") 321 322 for torrent in qbt_client.torrents_info(category="distro"): 323 if "Fedora" in torrent.name: 324 try: 325 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 326 print(f"Deleted torrent {torrent.name}") 327 except Exception as ex: 328 print(f"Failed to delete torrent {torrent.name}") 329 print(ex) 330 331 for url in urls: 332 try: 333 qbt_client.torrents_add(url, category="distro") 334 print(f"Added torrent {os.path.basename(url)}") 335 except Exception as ex: 336 print(f"Failed to add torrent {os.path.basename(url)}") 337 print(ex) 338 339# Add FreeBSD torrents 340[group('torrent')] 341qbt-freebsd: 342 #!/usr/bin/env -S uv run --script 343 # /// script 344 # dependencies = [ 345 # "beautifulsoup4", 346 # "qbittorrent-api", 347 # "requests", 348 # ] 349 # /// 350 351 import os 352 import qbittorrentapi 353 import requests 354 from bs4 import BeautifulSoup 355 356 base_url = "https://people.freebsd.org/~jmg" 357 rel_url = "https://www.freebsd.org/releases" 358 rel_resp = requests.get(rel_url, timeout=30) 359 soup = BeautifulSoup(rel_resp.text, "html.parser") 360 sect2 = soup.find_all("div", class_="sect2") 361 # Lmao. I ought to get a bronze medal in the string gymnastics olympics 362 # for this monkey business. A banana would be nice, too. 363 versions = [] 364 for item in sect2: 365 if "Production Release" in item.get_text(): 366 versions.append(item.get_text().split(" ")[2]) 367 versions.append(item.get_text().split(" ")[17]) 368 369 magnet_urls = [] 370 for v in versions: 371 url = f"https://people.freebsd.org/~jmg/FreeBSD-{v}-R-magnet.txt" 372 reqs = requests.get(url, timeout=30) 373 data = reqs.text.split("\n") 374 375 for line in data: 376 if line.startswith("magnet:"): 377 magnet_urls.append(line) 378 379 conn_info = dict( 380 host="{{ host }}", 381 username="admin", 382 password="{{ password }}", 383 ) 384 385 with qbittorrentapi.Client(**conn_info) as qbt_client: 386 try: 387 qbt_client.auth_log_in() 388 except qbittorrentapi.LoginFailed: 389 exit("Login failed. Please verify credentials are correct.") 390 391 for torrent in qbt_client.torrents_info(category="distro"): 392 if "FreeBSD" in torrent.name: 393 try: 394 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 395 print(f"Deleted torrent {torrent.name}") 396 except Exception as ex: 397 print(f"Failed to delete torrent {torrent.name}") 398 print(ex) 399 400 for murl in magnet_urls: 401 try: 402 qbt_client.torrents_add(murl, category="distro") 403 print(f"Added torrent {murl}") 404 except Exception as ex: 405 print(f"Failed to add torrent {murl}") 406 print(ex) 407 408# Add Kali Linux torrents 409[group('torrent')] 410qbt-kali: 411 #!/usr/bin/env -S uv run --script 412 # /// script 413 # dependencies = [ 414 # "beautifulsoup4", 415 # "qbittorrent-api", 416 # "requests", 417 # ] 418 # /// 419 420 import os 421 import qbittorrentapi 422 import requests 423 from bs4 import BeautifulSoup 424 425 base_url = "https://kali.download/base-images/current" 426 resp = requests.get(base_url, timeout=30) 427 soup = BeautifulSoup(resp.text, "html.parser") 428 urls = [] 429 for link in soup.find_all("a"): 430 if link.get("href").endswith(".torrent"): 431 urls.append(f"{base_url}/{link.get('href')}") 432 433 conn_info = dict( 434 host="{{ host }}", 435 username="admin", 436 password="{{ password }}", 437 ) 438 439 with qbittorrentapi.Client(**conn_info) as qbt_client: 440 try: 441 qbt_client.auth_log_in() 442 except qbittorrentapi.LoginFailed: 443 exit("Login failed. Please verify credentials are correct.") 444 445 for torrent in qbt_client.torrents_info(category="distro"): 446 if "kali" in torrent.name: 447 try: 448 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 449 print(f"Deleted torrent {torrent.name}") 450 except Exception as ex: 451 print(f"Failed to delete torrent {torrent.name}") 452 print(ex) 453 454 for url in urls: 455 try: 456 qbt_client.torrents_add(url, category="distro") 457 print(f"Added torrent {os.path.basename(url)}") 458 except Exception as ex: 459 print(f"Failed to add torrent {os.path.basename(url)}") 460 print(ex) 461 462# Add KDE Neon torrents 463[group('torrent')] 464qbt-kdeneon: 465 #!/usr/bin/env -S uv run --script 466 # /// script 467 # dependencies = [ 468 # "beautifulsoup4", 469 # "qbittorrent-api", 470 # "requests", 471 # ] 472 # /// 473 474 import os 475 import qbittorrentapi 476 import requests 477 from bs4 import BeautifulSoup 478 479 user_base_url = "https://files.kde.org/neon/images/user" 480 resp = requests.get(user_base_url, timeout=30) 481 soup = BeautifulSoup(resp.text, "html.parser") 482 user_version = str() 483 for link in soup.find_all("a"): 484 if link.get("href")[0:8].isdigit(): 485 user_version = link.text.strip("/") 486 487 dev_base_url = "https://files.kde.org/neon/images/dev" 488 resp = requests.get(dev_base_url, timeout=30) 489 soup = BeautifulSoup(resp.text, "html.parser") 490 dev_version = str() 491 for link in soup.find_all("a"): 492 if link.get("href")[0:8].isdigit(): 493 dev_version = link.text.strip("/") 494 495 testing_base_url = "https://files.kde.org/neon/images/testing" 496 resp = requests.get(testing_base_url, timeout=30) 497 soup = BeautifulSoup(resp.text, "html.parser") 498 testing_version = str() 499 for link in soup.find_all("a"): 500 if link.get("href")[0:8].isdigit(): 501 testing_version = link.text.strip("/") 502 503 unstable_base_url = "https://files.kde.org/neon/images/unstable" 504 resp = requests.get(unstable_base_url, timeout=30) 505 soup = BeautifulSoup(resp.text, "html.parser") 506 unstable_version = str() 507 for link in soup.find_all("a"): 508 if link.get("href")[0:8].isdigit(): 509 unstable_version = link.text.strip("/") 510 511 urls = [ 512 f"https://files.kde.org/neon/images/user/{user_version}/neon-user-{user_version}.iso.torrent", 513 f"https://files.kde.org/neon/images/dev/{dev_version}/neon-dev-{dev_version}.iso.torrent", 514 f"https://files.kde.org/neon/images/testing/{testing_version}/neon-testing-{testing_version}.iso.torrent", 515 f"https://files.kde.org/neon/images/unstable/{unstable_version}/neon-unstable-{unstable_version}.iso.torrent", 516 ] 517 518 conn_info = dict( 519 host="{{ host }}", 520 username="admin", 521 password="{{ password }}", 522 ) 523 524 with qbittorrentapi.Client(**conn_info) as qbt_client: 525 try: 526 qbt_client.auth_log_in() 527 except qbittorrentapi.LoginFailed: 528 exit("Login failed. Please verify credentials are correct.") 529 530 for torrent in qbt_client.torrents_info(category="distro"): 531 if "neon" in torrent.name: 532 try: 533 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 534 print(f"Deleted torrent {torrent.name}") 535 except Exception as ex: 536 print(f"Failed to delete torrent {torrent.name}") 537 print(ex) 538 539 for url in urls: 540 try: 541 qbt_client.torrents_add(url, category="distro") 542 print(f"Added torrent {os.path.basename(url)}") 543 except Exception as ex: 544 print(f"Failed to add torrent {os.path.basename(url)}") 545 print(ex) 546 547# Add MX Linux torrents 548[group('torrent')] 549qbt-mx: 550 #!/usr/bin/env -S uv run --script 551 # /// script 552 # dependencies = [ 553 # "beautifulsoup4", 554 # "qbittorrent-api", 555 # "requests", 556 # ] 557 # /// 558 559 import os 560 import qbittorrentapi 561 import requests 562 from bs4 import BeautifulSoup 563 564 base_url = "https://l2.mxrepo.com/torrents" 565 resp = requests.get(base_url, timeout=30) 566 soup = BeautifulSoup(resp.text, "html.parser") 567 urls = [] 568 for link in soup.find_all("a"): 569 if link.get("href").startswith("MX-"): 570 urls.append(f"{base_url}/{link.get('href')}") 571 572 conn_info = dict( 573 host="{{ host }}", 574 username="admin", 575 password="{{ password }}", 576 ) 577 578 with qbittorrentapi.Client(**conn_info) as qbt_client: 579 try: 580 qbt_client.auth_log_in() 581 except qbittorrentapi.LoginFailed: 582 exit("Login failed. Please verify credentials are correct.") 583 584 for torrent in qbt_client.torrents_info(category="distro"): 585 if "MX-" in torrent.name: 586 try: 587 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 588 print(f"Deleted torrent {torrent.name}") 589 except Exception as ex: 590 print(f"Failed to delete torrent {torrent.name}") 591 print(ex) 592 593 for url in urls: 594 try: 595 qbt_client.torrents_add(url, category="distro") 596 print(f"Added torrent {os.path.basename(url)}") 597 except Exception as ex: 598 print(f"Failed to add torrent {os.path.basename(url)}") 599 print(ex) 600 601# Add NetBSD torrents 602[group('torrent')] 603qbt-netbsd: 604 #!/usr/bin/env -S uv run --script 605 # /// script 606 # dependencies = [ 607 # "beautifulsoup4", 608 # "qbittorrent-api", 609 # "requests", 610 # ] 611 # /// 612 613 import os 614 import qbittorrentapi 615 import re 616 import requests 617 from bs4 import BeautifulSoup 618 from packaging.version import Version 619 620 base_url = "https://cdn.netbsd.org/pub/NetBSD" 621 resp = requests.get(base_url, timeout=30) 622 soup = BeautifulSoup(resp.text, "html.parser") 623 versions = [] 624 for link in soup.find_all("a"): 625 link_text = link.get("href") 626 res = bool(re.search(r'^NetBSD-(\d+\.\d+)\/$', link_text)) 627 if res: 628 versions.append(link.get("href").strip("/")) 629 just_versions = [] 630 for v in versions: 631 just_versions.append(v[7:]) 632 just_versions.sort(key=Version) 633 latest_version_url = f"{base_url}/NetBSD-{just_versions[-1]}/images" 634 urls = [] 635 resp = requests.get(latest_version_url, timeout=30) 636 soup = BeautifulSoup(resp.text, "html.parser") 637 for link in soup.find_all("a"): 638 if link.get("href").endswith(".torrent"): 639 urls.append(f"{latest_version_url}/{link.get('href')}") 640 641 conn_info = dict( 642 host="{{ host }}", 643 username="admin", 644 password="{{ password }}", 645 ) 646 647 with qbittorrentapi.Client(**conn_info) as qbt_client: 648 try: 649 qbt_client.auth_log_in() 650 except qbittorrentapi.LoginFailed: 651 exit("Login failed. Please verify credentials are correct.") 652 653 for torrent in qbt_client.torrents_info(category="distro"): 654 if "NetBSD" in torrent.name: 655 try: 656 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 657 print(f"Deleted torrent {torrent.name}") 658 except Exception as ex: 659 print(f"Failed to delete torrent {torrent.name}") 660 print(ex) 661 662 for url in urls: 663 try: 664 qbt_client.torrents_add(url, category="distro") 665 print(f"Added torrent {os.path.basename(url)}") 666 except Exception as ex: 667 print(f"Failed to add torrent {os.path.basename(url)}") 668 print(ex) 669 670# Add NixOS torrents 671[group('torrent')] 672qbt-nixos: 673 #!/usr/bin/env -S uv run --script 674 # /// script 675 # dependencies = [ 676 # "beautifulsoup4", 677 # "qbittorrent-api", 678 # "requests", 679 # ] 680 # /// 681 682 import json 683 import os 684 import qbittorrentapi 685 import requests 686 687 base_url = "https://api.github.com/repos/AnimMouse/NixOS-ISO-Torrents/releases/latest" 688 resp = requests.get(base_url, timeout=30) 689 json_data = json.loads(resp.text) 690 691 urls = [] 692 for item in json_data["assets"]: 693 urls.append(item["browser_download_url"]) 694 695 conn_info = dict( 696 host="{{ host }}", 697 username="admin", 698 password="{{ password }}", 699 ) 700 701 with qbittorrentapi.Client(**conn_info) as qbt_client: 702 try: 703 qbt_client.auth_log_in() 704 except qbittorrentapi.LoginFailed: 705 exit("Login failed. Please verify credentials are correct.") 706 707 for torrent in qbt_client.torrents_info(category="distro"): 708 if "nixos" in torrent.name: 709 try: 710 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 711 print(f"Deleted torrent {torrent.name}") 712 except Exception as ex: 713 print(f"Failed to delete torrent {torrent.name}") 714 print(ex) 715 716 for url in urls: 717 try: 718 qbt_client.torrents_add(url, category="distros") 719 print(f"Added torrent {os.path.basename(url)}") 720 except Exception as ex: 721 print(f"Failed to add torrent {os.path.basename(url)}") 722 print(ex) 723 724# Add Qubes OS torrents 725[group('torrent')] 726qbt-qubes: 727 #!/usr/bin/env -S uv run --script 728 # /// script 729 # dependencies = [ 730 # "beautifulsoup4", 731 # "qbittorrent-api", 732 # "requests", 733 # ] 734 # /// 735 736 import os 737 import qbittorrentapi 738 import requests 739 from bs4 import BeautifulSoup 740 741 base_url = "https://mirrors.edge.kernel.org/qubes/iso" 742 resp = requests.get(base_url, timeout=30) 743 soup = BeautifulSoup(resp.text, "html.parser") 744 torrents = [] 745 for link in soup.find_all("a"): 746 if link.get("href").endswith(".torrent"): 747 torrents.append(link.get("href")) 748 url = f"{base_url}/{torrents[-1]}" 749 750 conn_info = dict( 751 host="{{ host }}", 752 username="admin", 753 password="{{ password }}", 754 ) 755 756 with qbittorrentapi.Client(**conn_info) as qbt_client: 757 try: 758 qbt_client.auth_log_in() 759 except qbittorrentapi.LoginFailed: 760 exit("Login failed. Please verify credentials are correct.") 761 762 for torrent in qbt_client.torrents_info(category="distro"): 763 if "Qubes" in torrent.name: 764 try: 765 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 766 print(f"Deleted torrent {torrent.name}") 767 except Exception as ex: 768 print(f"Failed to delete torrent {torrent.name}") 769 print(ex) 770 771 try: 772 qbt_client.torrents_add(url, category="distro") 773 print(f"Added torrent {os.path.basename(url)}") 774 except Exception as ex: 775 print(f"Failed to add torrent {os.path.basename(url)}") 776 print(ex) 777 778# Add Rocky Linux torrents 779[group('torrent')] 780qbt-rocky: 781 #!/usr/bin/env -S uv run --script 782 # /// script 783 # dependencies = [ 784 # "beautifulsoup4", 785 # "qbittorrent-api", 786 # "requests", 787 # ] 788 # /// 789 790 import os 791 import qbittorrentapi 792 import re 793 import requests 794 from bs4 import BeautifulSoup 795 from packaging.version import Version 796 797 base_url = "https://download.rockylinux.org/pub/rocky" 798 resp = requests.get(base_url, timeout=30) 799 soup = BeautifulSoup(resp.text, "html.parser") 800 versions = [] 801 for link in soup.find_all("a"): 802 link_text = link.get("href") 803 res = bool(re.search(r'^\d.*\/$', link_text)) 804 if res: 805 versions.append(link.get("href").strip("/")) 806 versions.sort(key=Version) 807 major_versions = [] 808 for v in versions: 809 if v.isdigit(): 810 major_versions.append(v) 811 latest_versions = [] 812 for mv in major_versions: 813 full_version = [] 814 for v in versions: 815 if v.startswith(mv): 816 full_version.append(v) 817 full_version.sort(key=Version) 818 latest_versions.append(full_version[-1]) 819 820 archs = ["aarch64", "ppc64le", "s390x", "x86_64"] 821 urls = [] 822 for arch in archs: 823 for lv in latest_versions: 824 urls.append(f"{base_url}/{lv}/isos/{arch}/Rocky-{lv}-{arch}-dvd.torrent") 825 826 conn_info = dict( 827 host="{{ host }}", 828 username="admin", 829 password="{{ password }}", 830 ) 831 832 with qbittorrentapi.Client(**conn_info) as qbt_client: 833 try: 834 qbt_client.auth_log_in() 835 except qbittorrentapi.LoginFailed: 836 exit("Login failed. Please verify credentials are correct.") 837 838 for torrent in qbt_client.torrents_info(category="distro"): 839 if "Rocky" in torrent.name: 840 try: 841 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 842 print(f"Deleted torrent {torrent.name}") 843 except Exception as ex: 844 print(f"Failed to delete torrent {torrent.name}") 845 print(ex) 846 847 for url in urls: 848 try: 849 qbt_client.torrents_add(url, category="distro") 850 print(f"Added torrent {os.path.basename(url)}") 851 except Exception as ex: 852 print(f"Failed to add torrent {os.path.basename(url)}") 853 print(ex) 854 855# Add Tails OS torrents 856[group('torrent')] 857qbt-tails: 858 #!/usr/bin/env -S uv run --script 859 # /// script 860 # dependencies = [ 861 # "beautifulsoup4", 862 # "qbittorrent-api", 863 # "requests", 864 # ] 865 # /// 866 867 import os 868 import qbittorrentapi 869 import requests 870 from bs4 import BeautifulSoup 871 872 base_url = "https://tails.net/torrents/files" 873 resp = requests.get(base_url, timeout=30) 874 soup = BeautifulSoup(resp.text, "html.parser") 875 urls = [] 876 for link in soup.find_all("a"): 877 if link.get("href").endswith(".torrent"): 878 urls.append(f"{base_url}/{link.get('href')}") 879 880 conn_info = dict( 881 host="{{ host }}", 882 username="admin", 883 password="{{ password }}", 884 ) 885 886 with qbittorrentapi.Client(**conn_info) as qbt_client: 887 try: 888 qbt_client.auth_log_in() 889 except qbittorrentapi.LoginFailed: 890 exit("Login failed. Please verify credentials are correct.") 891 892 for torrent in qbt_client.torrents_info(category="distro"): 893 if "tails" in torrent.name: 894 try: 895 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 896 print(f"Deleted torrent {torrent.name}") 897 except Exception as ex: 898 print(f"Failed to delete torrent {torrent.name}") 899 print(ex) 900 901 for url in urls: 902 try: 903 qbt_client.torrents_add(url, category="distro") 904 print(f"Added torrent {os.path.basename(url)}") 905 except Exception as ex: 906 print(f"Failed to add torrent {os.path.basename(url)}") 907 print(ex) 908 909# Destroy all the distro torrents! 910[group('torrent')] 911qbt-nuke: 912 #!/usr/bin/env -S uv run --script 913 # /// script 914 # dependencies = [ 915 # "beautifulsoup4", 916 # "qbittorrent-api", 917 # "requests", 918 # ] 919 # /// 920 921 import os 922 import qbittorrentapi 923 924 conn_info = dict( 925 host="{{ host }}", 926 username="admin", 927 password="{{ password }}", 928 ) 929 930 with qbittorrentapi.Client(**conn_info) as qbt_client: 931 for torrent in qbt_client.torrents_info(category="distro"): 932 try: 933 qbt_client.torrents_delete(torrent_hashes=torrent.hash, delete_files=True) 934 print(f"Deleted torrent {torrent.name}") 935 except Exception as ex: 936 print(f"Failed to delete torrent {torrent.name}") 937 print(ex)