Google Drive: Open Direct Image from /view

Adds a floating “Open Image” button on Google Drive file preview pages (those ending in /view). Click it to open the full-size direct image URL in a new tab. Right-click and comments remain fully functional.

// ==UserScript==
// @name         Google Drive: Open Direct Image from /view
// @namespace    https://greasyfork.org/users/573dave2
// @version      1.3.0
// @description  Adds a floating “Open Image” button on Google Drive file preview pages (those ending in /view). Click it to open the full-size direct image URL in a new tab. Right-click and comments remain fully functional.
// @author       573dave2
// @license      MIT
// @match        https://drive.google.com/file/d/*/view*
// @match        https://drive.google.com/file/d/*/view
// @grant        GM_openInTab
// @run-at       document-idle
// @icon         https://ssl.gstatic.com/docs/doclist/images/drive_2022q3_32dp.png
// ==/UserScript==


(() => {
  "use strict";

  const BTN_ID = "gd-open-image-btn";
  let lastHref = location.href;
  let poll;

  // --- Utilities ---
  const byId = (id) => document.getElementById(id);
  const isView = () => /\/file\/d\/[^/]+\/view/.test(location.pathname);
  const getFileId = () => (location.pathname.match(/\/file\/d\/([^/]+)\//) || [])[1] || null;

  function mountBtn() {
    if (byId(BTN_ID)) return;

    const btn = document.createElement("button");
    btn.id = BTN_ID;
    btn.type = "button";
    btn.textContent = "Open Image";
    // Minimal inline styles; no classes, no shadow, no innerHTML (Trusted Types safe)
    Object.assign(btn.style, {
      position: "fixed",
      right: "16px",
      bottom: "16px",
      zIndex: "2147483647",
      padding: "10px 14px",
      fontFamily: "system-ui,-apple-system,'Segoe UI',Roboto,Arial,sans-serif",
      fontSize: "14px",
      fontWeight: "600",
      color: "#1a73e8",
      background: "#fff",
      border: "1px solid #dadce0",
      borderRadius: "8px",
      boxShadow: "0 2px 10px rgba(0,0,0,.12)",
      cursor: "pointer",
    });

    // Keep Drive’s right-click intact: we don't intercept any global events.
    // Only clicks on the button itself are handled.
    btn.addEventListener("click", onOpenClick, { passive: true });

    // Optional: right-click on the button should show the browser menu (do nothing).
    // (No listener needed; just don't preventDefault.)

    document.body.appendChild(btn);
  }

  function unmountBtn() {
    const b = byId(BTN_ID);
    if (b) b.remove();
  }

  async function onOpenClick() {
    const fileId = getFileId();
    if (!fileId) {
      alert("Could not detect Drive file ID from URL.");
      return;
    }
    const previewUrl = `https://drive.google.com/file/d/${fileId}/preview`;

    try {
      const html = await fetch(previewUrl, { credentials: "include" }).then((r) => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.text();
      });
      const best = pickLargestViewerImage(html);
      if (best) {
        GM_openInTab(best, { active: true, insert: true });
        return;
      }
    } catch {
      // ignore; use fallback below
    }

    const fallback = `https://drive.usercontent.google.com/uc?id=${encodeURIComponent(fileId)}&export=download`;
    GM_openInTab(fallback, { active: true, insert: true });
  }

  function pickLargestViewerImage(html) {
    // Extract all URLs; normalize \u003d -> '='
    const urls = [];
    const re = /(https:\/\/[^\s"'<>]+)/g;
    let m;
    while ((m = re.exec(html)) !== null) urls.push(m[1].replace(/\\u003d/g, "="));

    // Viewer variants: .../u/<n>/drive-viewer/...=s####(-rw-v1)?
    const candidates = urls.filter((u) =>
      /^https:\/\/drive\.google\.com\/u\/\d+\/drive-viewer\/[^?]*=s\d+(?:-rw-v1)?$/i.test(u)
    );
    if (!candidates.length) return null;

    let best = null;
    let bestSize = -1;
    for (const u of candidates) {
      const mm = u.match(/=s(\d+)(?:-rw-v1)?$/i);
      const sz = mm ? parseInt(mm[1], 10) : 0;
      if (sz > bestSize) {
        bestSize = sz;
        best = u;
      }
    }
    return best;
  }

  function tick() {
    const href = location.href;
    if (href !== lastHref) {
      lastHref = href;
      isView() ? mountBtn() : unmountBtn();
      return;
    }
    isView() ? mountBtn() : unmountBtn();
  }

  function start() {
    if (!poll) poll = setInterval(tick, 500);
    window.addEventListener(
      "beforeunload",
      () => {
        if (poll) clearInterval(poll);
        poll = null;
      },
      { passive: true }
    );
    tick();
  }

  start();
})();