Platesmania → Google Lens

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

// ==UserScript==
// @name         Platesmania → Google Lens
// @namespace    http://tampermonkey.net/
// @version      1.1.2
// @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%;height:23px;display:flex;align-items:center;justify-content:center;background-color:#3498db;color:#fff;border:0;cursor:pointer;border-radius:4px;font-weight:400;';

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