PyPI package name and version fetcher

Fetches all the package names displayed in a PyPI search along with their last version and copies it to clipboard. Crafted for copy all OCA metapackages when searching for "odoo14-addons-oca"

  1. // ==UserScript==
  2. // @name PyPI package name and version fetcher
  3. // @version 1
  4. // @namespace https://coopdevs.coop
  5. // @license AGPL-3
  6. // @description Fetches all the package names displayed in a PyPI search along with their last version and copies it to clipboard. Crafted for copy all OCA metapackages when searching for "odoo14-addons-oca"
  7. // @description:es-ES Busca todos los nombres de paquetes que se muestran en una búsqueda de PyPI junto con su última versión y los copia al portapapeles. Hecho para copiar todos los metapaquetes OCA al buscar "odoo14-addons-oca"
  8. // @description:en-US Fetches all the package names displayed in a PyPI search along with their last version and copies it to clipboard. Crafted for copy all OCA metapackages when searching for "odoo14-addons-oca"
  9. // @description:pt-BR Busca todos os nomes de pacotes exibidos em uma pesquisa do PyPI, juntamente com sua última versão e os copia para a área de transferência. Feito para copiar todos os metapacotes OCA ao pesquisar por "odoo14-addons-oca"
  10. // @description:fr-FR Recherche tous les noms de paquets affichés dans une recherche PyPI ainsi que leur dernière version et les copie dans le presse-papiers. Fabriqué pour copier tous les méta-paquets OCA lors de la recherche de "odoo14-addons-oca"
  11. // @description:ru-RU Ищет все имена пакетов, отображаемые в поиске PyPI, а также их последнюю версию, и копирует их в буфер обмена. Сделано для копирования всех метапакетов OCA при поиске «odoo14-addons-oca»
  12. // @author laicoop
  13. // @match https://pypi.org/search/*
  14. // ==/UserScript==
  15. /* jshint esversion: 8 */
  16.  
  17. (function() {
  18. 'use strict';
  19.  
  20. let currentPage = 1;
  21. let maxPages = 1;
  22.  
  23. function addButton() {
  24. console.log("Button added");
  25. const button = document.createElement('div');
  26. button.className = 'projects';
  27. button.style = 'text-align: center; border-radius: 5px;';
  28. button.style.width = '30px';
  29. button.style.height = '30px';
  30. button.title = 'Click to copy version and name from all packages to clipboard';
  31. button.innerHTML = '📥';
  32. button.addEventListener('click', fetchAllPackages);
  33.  
  34. const container = document.querySelector('.search-form');
  35. container.appendChild(button);
  36. }
  37.  
  38. async function fetchAllPackages() {
  39. console.log(`Fetching all packages, currently on page ${currentPage}`);
  40. await fetchPackages();
  41. }
  42.  
  43. async function fetchPackages() {
  44. const foundPackagesElement = document.querySelector(".split-layout--table > div:nth-child(1) > p:nth-child(1) > strong:nth-child(1)");
  45. const foundPackages = parseInt(foundPackagesElement.innerText.replace(",", ""));
  46. const versioned_pkgs = [];
  47. console.log("fetchPackages...");
  48. console.log(`Found pagination with ${maxPages} pages`);
  49. // Loop through each page and get the package names and versions
  50. for (let cpage = 1; cpage <= maxPages; cpage++) {
  51. console.log(`Fetching page ${cpage}...`);
  52. const url = `${window.location.href}&page=${cpage}`;
  53. const response = await fetch(url);
  54. const html = await response.text();
  55. const parser = new DOMParser();
  56. const doc = parser.parseFromString(html, 'text/html');
  57. const pkgs = doc.querySelectorAll('.package-snippet__title');
  58. pkgs.forEach((currentValue, currentIndex, listObj) => {
  59. versioned_pkgs.push(pkgs[`${currentIndex}`].querySelector('.package-snippet__name').innerHTML + "==" + pkgs[`${currentIndex}`].querySelector('.package-snippet__version').innerHTML);
  60. });
  61. }
  62.  
  63. // Copy the package names and versions to the clipboard
  64. const packageList = versioned_pkgs.join('\n');
  65.  
  66. console.log(`Copied ${versioned_pkgs.length} of ${foundPackages}`);
  67. if (versioned_pkgs.length < foundPackages) {
  68. alert(`Not all packages copied (${versioned_pkgs.length}/${foundPackages}). Try again.`)
  69. }
  70.  
  71. navigator.clipboard.writeText(packageList)
  72. .then(() => console.log('Package names and versions copied to clipboard'))
  73. .catch(error => console.error(`Error copying package names and versions to clipboard: ${error}`));
  74. alert(` ${versioned_pkgs.length} packages copied. Paste it in your requirements.txt file`)
  75. }
  76. function init() {
  77. console.log('Initializing script');
  78. const pagination = document.querySelector('.button-group--pagination');
  79. if (pagination) {
  80. const buttons = pagination.querySelectorAll('.button-group__button');
  81. buttons.forEach(button => {
  82. const text = button.textContent.trim();
  83. const page = parseInt(text, 10);
  84. if (!isNaN(page) && page > maxPages) {
  85. maxPages = page;
  86. }
  87. });
  88. console.log(`Found pagination with ${maxPages} pages`);
  89. } else {
  90. console.log('No pagination found, defaulting to first page');
  91. }
  92. addButton();
  93.  
  94. const savedCurrentPage = GM_getValue('currentPage', 1);
  95. if (savedCurrentPage > 1) {
  96. console.log(`Restoring saved current page: ${savedCurrentPage}`);
  97. currentPage = savedCurrentPage;
  98. }
  99. }
  100.  
  101. init();
  102. })();