VSCode Marketplace – Direct VSIX Download

Adds a “Download VSIX” button to VS Code Marketplace pages, keeps it there, and saves the file with a proper name.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         VSCode Marketplace – Direct VSIX Download
// @namespace    https://github.com/sreyemnayr
// @version      0.2.1
// @description  Adds a “Download VSIX” button to VS Code Marketplace pages, keeps it there, and saves the file with a proper name.
// @author       Ryan Meyers (sreyemnayr)
// @match        https://marketplace.visualstudio.com/items*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

async function injectDownloadButton () {
  /*—-1-— make sure we’re on an extension-detail page —*/
  const params   = new URLSearchParams(location.search);
  const itemName = params.get('itemName');                       // e.g. Balastrong.close-tabs
  if (!itemName || !itemName.includes('.')) return;

  /* If the button is already in the DOM, nothing to do */
  if (document.getElementById('vsix-download-container')) return;

  const [publisher, extension] = itemName.split('.');

  /*—-2-— pull metadata for latest VSIX —*/
  const apiUrl = `https://marketplace.visualstudio.com/_apis/public/gallery/vscode/${publisher}/${extension}/latest`;
  let latest, version, vsixUrl;
  try {
    const json = await fetch(apiUrl).then(r => r.json());
    latest  = json?.versions?.[0];
    version = latest?.version ?? '0.0.0';
    vsixUrl = latest?.files?.find(f => f.assetType === 'Microsoft.VisualStudio.Services.VSIXPackage')?.source;
    if (!vsixUrl) return;
  } catch (e) {
    console.error('[VSIX] metadata fetch failed:', e);
    return;
  }

  /*—-3-— find the Install button so we can clone its look —*/
  const install   = document.querySelector('.install');
  const container = install?.closest('.ux-oneclick-install-button-container')?.parentNode;
  if (!install || !container) return;

  const dlBtn = install.cloneNode(true);
  dlBtn.id = 'vsix-download-button';
  dlBtn.querySelector('*').textContent = 'Download VSIX';
  dlBtn.removeAttribute('href');               // we handle click ourselves
  dlBtn.style.pointerEvents = 'auto';

  dlBtn.addEventListener('click', async ev => {
    ev.preventDefault();
    const label = dlBtn.querySelector('*');
    const originalText = label.textContent;
    label.textContent = 'Fetching…';

    try {
      const buffer = await fetch(vsixUrl).then(r => r.arrayBuffer());
      const blob   = new Blob([buffer], { type: 'application/octet-stream' });
      const url    = URL.createObjectURL(blob);

      const tmp = document.createElement('a');
      tmp.href = url;
      tmp.download = `${publisher}.${extension}.${version}.vsix`;
      document.body.appendChild(tmp);
      tmp.click();
      tmp.remove();
      URL.revokeObjectURL(url);

      label.textContent = 'Downloaded ✔';
      setTimeout(() => (label.textContent = originalText), 2500);
    } catch (err) {
      console.error('[VSIX] download failed:', err);
      label.textContent = 'Error – try again';
      setTimeout(() => (label.textContent = originalText), 3000);
    }
  });

  const wrap = document.createElement('div');
  wrap.id = 'vsix-download-container';
  wrap.appendChild(dlBtn);
  container.appendChild(wrap);

  console.log('[VSIX] Download button added →', vsixUrl);
}

/*—-4-— keep the button present even if the page rewrites the DOM —*/
setInterval(injectDownloadButton, 1000);       // every second
window.addEventListener('popstate', injectDownloadButton); // SPA navigation support