Pinterest Quick Pin

使用Ctrl+V自动从剪贴板中上传Pin图

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Pinterest Quick Pin
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  使用Ctrl+V自动从剪贴板中上传Pin图
// @author       Tz
// @license      MIT
// @match        https://*.pinterest.com/*
// @match        https://pinterest.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pinterest.com
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // === 配置常量 ===
    const CFG = {
        uploadUrl: 'https://www.pinterest.com/pin-creation-tool/',
        key: 'gemini_pin_cache',
        width: '1500px' // 上传窗口宽度
    };

    // === 通用工具函数 ===
    const setStyle = (el, styles) => Object.assign(el.style, styles);

    // 黑白纯文本提示框
    const showToast = (msg) => {
        let t = document.getElementById('g-toast');
        if (!t) {
            t = document.createElement('div');
            t.id = 'g-toast';
            setStyle(t, {
                position: 'fixed', top: '20px', left: '50%', transform: 'translateX(-50%)',
                background: 'rgba(0,0,0,0.85)', color: '#fff', padding: '8px 20px',
                borderRadius: '4px', zIndex: '9999999', fontSize: '14px', pointerEvents: 'none',
                transition: 'opacity 0.2s', opacity: '0', fontFamily: 'sans-serif'
            });
            document.body.appendChild(t);
        }
        t.innerText = msg;
        t.style.opacity = '1';
        clearTimeout(t.timer);
        t.timer = setTimeout(() => { t.style.opacity = '0'; }, 2000);
    };

    // =================================================================
    // 逻辑 A:主页面 (监听 Ctrl+V)
    // =================================================================
    if (window.top === window.self) {
        let modal, iframe;

        // 1. 状态指示灯 (左下角小灰点)
        const dot = document.createElement('div');
        setStyle(dot, {
            position: 'fixed', bottom: '10px', left: '10px', width: '6px', height: '6px',
            borderRadius: '50%', background: '#999', zIndex: '9999', opacity: '0.4', pointerEvents: 'none'
        });
        document.body.appendChild(dot);

        // 2. 弹出上传窗口
        const openModal = () => {
            if (!modal) {
                // 背景遮罩
                modal = document.createElement('div');
                setStyle(modal, {
                    position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh',
                    background: 'rgba(0,0,0,0.75)', zIndex: '999999', display: 'flex',
                    justifyContent: 'center', alignItems: 'center'
                });

                // 容器
                const box = document.createElement('div');
                setStyle(box, {
                    width: CFG.width, maxWidth: '98vw', height: '92vh', background: '#fff',
                    borderRadius: '20px', overflow: 'hidden', position: 'relative'
                });

                // Iframe
                iframe = document.createElement('iframe');
                iframe.src = CFG.uploadUrl;
                setStyle(iframe, { width: '100%', height: '100%', border: 'none' });

                box.appendChild(iframe);
                modal.appendChild(box);
                document.body.appendChild(modal);

                // 点击背景关闭
                modal.onclick = (e) => { if(e.target === modal) modal.style.display = 'none'; };
            }
            modal.style.display = 'flex';
            // 强制刷新 iframe 以触发自动填入逻辑
            if (iframe.contentWindow.location.href !== 'about:blank') iframe.contentWindow.location.reload();
        };

        // 3. 监听粘贴事件
        window.addEventListener('paste', (e) => {
            const tag = document.activeElement.tagName;
            if (['INPUT', 'TEXTAREA'].includes(tag) || document.activeElement.isContentEditable) return;

            const item = Array.from(e.clipboardData.items).find(i => i.kind === 'file' && i.type.startsWith('image/'));

            if (item) {
                e.preventDefault();
                const reader = new FileReader();
                reader.onload = () => {
                    try {
                        localStorage.setItem(CFG.key, reader.result);
                        openModal(); // 打开弹窗
                    } catch (err) { showToast('图片过大,无法缓存'); }
                };
                reader.readAsDataURL(item.getAsFile());
            } else {
                showToast('剪贴板中无图片');
            }
        });

        // 4. 监听 ESC 键退出
        document.addEventListener('keydown', (e) => {
            if (e.key === 'Escape' && modal) modal.style.display = 'none';
        });
    }

    // =================================================================
    // 逻辑 B:Iframe 内部 (自动填入)
    // =================================================================
    else if (window.location.href.includes('pin-creation-tool') || window.location.href.includes('pin-builder')) {

        //隐藏滚动条
        const style = document.createElement('style');
        style.innerHTML = `body { overflow: hidden !important; }`; // 强制隐藏 body 的滚动条
        document.head.appendChild(style);

        //轮询检测并填入
        const timer = setInterval(() => {
            const b64 = localStorage.getItem(CFG.key);
            const input = document.querySelector('input[type="file"]');

            if (b64 && input) {
                clearInterval(timer);
                // Base64 转 File
                const arr = b64.split(','), bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
                for (let i = 0; i < n; i++) u8arr[i] = bstr.charCodeAt(i);
                const file = new File([u8arr], "pasted_image.png", { type: arr[0].match(/:(.*?);/)[1] });

                // 触发上传
                const dt = new DataTransfer(); dt.items.add(file);
                input.files = dt.files;
                input.dispatchEvent(new Event('change', { bubbles: true }));

                localStorage.removeItem(CFG.key); // 清除缓存
                showToast('图片已自动填入');
            }
        }, 300);
    }
})();