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.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
})();