Greasy Fork

Greasy Fork is available in English.

Platesmania → Google Lens

Add a Google Lens button on Platesmania and auto-fill the image URL into the Google page

当前为 2025-08-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         Platesmania → Google Lens
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Add a Google Lens button on Platesmania and auto-fill the image URL into the Google page
// @author       You
// @match        https://platesmania.com/*/add*
// @match        https://www.google.com/*
// @match        https://www.google.*/*
// @grant        GM.setValue
// @grant        GM.getValue
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  // ---------- Utils ----------
  const q = (sel, root = document) => root.querySelector(sel);

  // Detect our special Google page via query params (so we don't run on every Google page).
  function isOurGooglePage() {
    try {
      const url = new URL(window.location.href);
      return url.hostname.startsWith('www.google')
        && url.searchParams.has('olud')
        && url.searchParams.get('src') === 'pm';
    } catch {
      return false;
    }
  }

  // ---------- Platesmania integration ----------
  function handlePlatesmania() {
    // Create the button
    const button = document.createElement('button');
    button.textContent = 'Google Lens';
    button.style.cssText =
      'margin-bottom:10px;width:100%;background-color:#3498db;color:#fff;border:0;cursor:pointer;padding:8px 10px;border-radius:4px;font-weight:600;';

    const targetContainer = q('#zoomimgid');

    if (targetContainer) {
      const existingContainer = targetContainer.previousElementSibling;
      if (existingContainer && existingContainer.style.width === '260px') {
        existingContainer.appendChild(button);
      } else {
        const container = document.createElement('div');
        container.style.cssText = 'margin-left:0;width:260px;display:inline-block;';
        container.appendChild(button);
        targetContainer.parentNode.insertBefore(container, targetContainer);
      }
    }

    // Save the image URL once and whenever it changes (no tight polling).
    const img = q('#zoomimg');
    if (img) {
      const save = () => GM.setValue('platesmaniaImage', img.src || '');
      save();
      new MutationObserver(save).observe(img, {
        attributes: true,
        attributeFilter: ['src'],
      });
    }

    // Open Google page with marker params so our other handler knows to run.
    button.addEventListener('click', () => {
      window.open('https://www.google.com/?olud&src=pm', '_blank');
    });
  }

  // ---------- Google page automation (locale-agnostic) ----------
  function handleGoogleImages() {
    let searchAttempted = false;
    let tries = 0;
    const MAX_TRIES = 120; // ~18s at 150ms

    async function attemptSearch() {
      if (searchAttempted) return;

      try {
        const imageData = await GM.getValue('platesmaniaImage', '');
        if (!imageData) return;

        // Prefer stable attributes, NOT localized placeholders or random classes.
        // Observed stable targets:
        // - URL input: input[jsname="W7hAGe"]
        // - Search button: div[role="button"][jsname="ZtOxCb"]
        let inputField =
          q('input[jsname="W7hAGe"]') ||
          q('input.cB9M7') ||  // fallback observed class
          q('input[type="text"]');

        let searchButton =
          q('div[role="button"][jsname="ZtOxCb"]') ||
          q('button[type="submit"]') ||
          q('button,div[role="button"]');

        if (!inputField || !searchButton) return;

        // Fill input robustly
        inputField.focus();
        inputField.value = imageData;
        inputField.dispatchEvent(new Event('input', { bubbles: true }));
        inputField.dispatchEvent(new Event('change', { bubbles: true }));

        // Click the button
        searchButton.click();

        // Fallback: press Enter if click didn’t trigger
        setTimeout(() => {
          if (!searchAttempted) {
            const ev = new KeyboardEvent('keydown', {
              bubbles: true,
              cancelable: true,
              key: 'Enter',
              code: 'Enter',
              which: 13,
              keyCode: 13,
            });
            inputField.dispatchEvent(ev);
          }
        }, 400);

        searchAttempted = true;
      } catch (error) {
        console.error('Error handling Google Images:', error);
      }
    }

    // Gentle polling with backoff cap
    const searchInterval = setInterval(() => {
      if (searchAttempted || tries++ > MAX_TRIES) {
        clearInterval(searchInterval);
      } else {
        attemptSearch();
      }
    }, 150);

    // Also try once on DOMContentLoaded (in case elements are already present)
    if (document.readyState !== 'loading') {
      attemptSearch();
    } else {
      document.addEventListener('DOMContentLoaded', attemptSearch, { once: true });
    }
  }

  // ---------- Router ----------
  const href = window.location.href;
  if (/https?:\/\/(?:www\.)?platesmania\.com\//.test(href)) {
    handlePlatesmania();
  } else if (isOurGooglePage()) {
    handleGoogleImages();
  }
})();