Greasy Fork

Greasy Fork is available in English.

Google Search Suggestions Collector

Collect Google search suggestions

当前为 2024-12-06 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Google Search Suggestions Collector
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Collect Google search suggestions
// @author       WWW
// @include      *://www.google.*/*
// @include      *://google.*/*
// @grant        GM_setClipboard
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';

    // 在全局作用域内添加状态变量
    let isCollecting = false;
    let shouldStop = false;

    function addStyles() {
        const style = document.createElement('style');
        style.textContent = `
            .suggest-collector-btn {
                position: fixed;
                right: 200px;
                top: 20px;
                width: 50px;
                height: 50px;
                border-radius: 25px;
                background: var(--collector-bg, #ffffff);
                border: 2px solid var(--collector-border, #e0e0e0);
                box-shadow: 0 2px 12px rgba(0,0,0,0.15);
                cursor: move;
                z-index: 10000;
                display: flex;
                align-items: center;
                justify-content: center;
                user-select: none;
            }

            .suggest-collector-panel {
                position: fixed;
                width: 300px;
                background: var(--collector-bg, #ffffff);
                border: 1px solid var(--collector-border, #e0e0e0);
                border-radius: 8px;
                padding: 15px;
                box-shadow: 0 2px 12px rgba(0,0,0,0.15);
                z-index: 9999;
                display: none;
            }

            .suggest-collector-panel input {
                width: 100%;
                padding: 8px;
                border: 1px solid var(--collector-border, #e0e0e0);
                border-radius: 4px;
                margin-bottom: 10px;
                background: var(--collector-input-bg, #ffffff);
                color: var(--collector-text, #333333);
            }

            .suggest-collector-panel button {
                padding: 8px 16px;
                border: none;
                border-radius: 4px;
                background: #4CAF50;
                color: white;
                cursor: pointer;
                transition: background 0.3s;
            }

            .suggest-collector-panel button:hover {
                background: #45a049;
            }

            .suggest-collector-panel textarea {
                background: var(--collector-input-bg, #ffffff);
                color: var(--collector-text, #333333);
                border: 1px solid var(--collector-border, #e0e0e0);
                border-radius: 4px;
            }

            @media (prefers-color-scheme: dark) {
                :root {
                    --collector-bg: #2d2d2d;
                    --collector-border: #404040;
                    --collector-text: #e0e0e0;
                    --collector-input-bg: #3d3d3d;
                }
            }

            .input-mode-selector {
                display: flex;
                gap: 20px;
                margin-bottom: 15px;
                padding: 0 10px;
            }

            .input-mode-selector label {
                display: flex;
                align-items: center;
                gap: 5px;
                cursor: pointer;
                color: var(--collector-text, #333333);
                min-width: 70px;
            }

            .input-mode-selector input[type="radio"],
            .filter-options input[type="checkbox"] {
                margin: 0;
                cursor: pointer;
                width: 16px;
                height: 16px;
            }

            .filter-options {
                margin-bottom: 15px;
                padding: 0 10px;
            }

            .filter-options label {
                display: flex;
                align-items: center;
                gap: 5px;
                cursor: pointer;
                color: var(--collector-text, #333333);
                justify-content: flex-end;
            }

            #singleInput {
                padding: 0 10px;
            }

            .depth-selector {
                margin-bottom: 15px;
                padding: 0 10px;
                display: flex;
                align-items: center;
                gap: 10px;
            }

            .depth-selector label {
                color: var(--collector-text, #333333);
            }

            .depth-selector select {
                padding: 5px;
                border-radius: 4px;
                border: 1px solid var(--collector-border, #e0e0e0);
                background: var(--collector-input-bg, #ffffff);
                color: var(--collector-text, #333333);
                cursor: pointer;
            }
        `;
        document.head.appendChild(style);
    }

    function createUI() {
        const btn = document.createElement('div');
        btn.className = 'suggest-collector-btn';
        btn.innerHTML = '🔍';
        document.body.appendChild(btn);

        const panel = document.createElement('div');
        panel.className = 'suggest-collector-panel';
        panel.innerHTML = `
            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
                <div class="input-mode-selector">
                    <label><input type="radio" name="inputMode" value="single" checked> single</label>
                    <label><input type="radio" name="inputMode" value="batch"> batch</label>
                </div>
                <div class="filter-options">
                    <label><input type="checkbox" id="onlyEnglish"> Only English</label>
                </div>
            </div>
            <div class="depth-selector">
                <label>Search Depth:</label>
                <select id="searchDepth">
                    <option value="1">1 letter</option>
                    <option value="2">2 letters</option>
                    <option value="3">3 letters</option>
                    <option value="4">4 letters</option>
                    <option value="5">5 letters</option>
                </select>
            </div>
            <div id="singleInput">
                <input type="text" id="baseKeyword" placeholder="type keyword">
            </div>
            <div id="batchInput" style="display: none;">
                <textarea id="batchKeywords" placeholder="type keyword in each line" style="width: 100%; height: 100px; margin-bottom: 10px;"></textarea>
            </div>
            <button id="startCollect">start collect</button>
            <div id="progress" style="display: none; margin-top: 10px;">
                <div style="margin-bottom: 8px;">
                    total progress: <span id="totalProgress">0/0</span>
                    <div style="background: var(--collector-border); height: 20px; border-radius: 10px;">
                        <div id="totalProgressBar" style="width: 0%; height: 100%; background: #4CAF50; border-radius: 10px; transition: width 0.3s;"></div>
                    </div>
                </div>
                <div style="margin-bottom: 8px;">
                    current keyword progress: <span id="progressText">0/26</span>
                    <div style="background: var(--collector-border); height: 20px; border-radius: 10px;">
                        <div id="progressBar" style="width: 0%; height: 100%; background: #4CAF50; border-radius: 10px; transition: width 0.3s;"></div>
                    </div>
                </div>
                <div>collected: <span id="collectedCount">0</span> items</div>
            </div>
            <div id="result" style="max-height: 300px; overflow-y: auto; margin-top: 10px;"></div>
        `;
        document.body.appendChild(panel);

        let isDragging = false;
        let currentX;
        let currentY;
        let initialX;
        let initialY;
        let xOffset = 0;
        let yOffset = 0;

        // 更新面板位置的函数
        function updatePanelPosition() {
            const btnRect = btn.getBoundingClientRect();
            panel.style.right = `${window.innerWidth - (btnRect.right + 20)}px`;
            panel.style.top = `${btnRect.bottom + 20}px`;
        }

        btn.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        function dragStart(e) {
            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;
            if (e.target === btn) {
                isDragging = true;
            }
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;
                xOffset = currentX;
                yOffset = currentY;
                btn.style.transform = `translate(${currentX}px, ${currentY}px)`;
                // 拖动时更新面板位置
                updatePanelPosition();
            }
        }

        function dragEnd() {
            isDragging = false;
        }

        btn.addEventListener('click', (e) => {
            if (!isDragging) {
                panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
                if (panel.style.display === 'block') {
                    updatePanelPosition();
                }
            }
        });

        const radioButtons = panel.querySelectorAll('input[name="inputMode"]');
        radioButtons.forEach(radio => {
            radio.addEventListener('change', (e) => {
                document.getElementById('singleInput').style.display = e.target.value === 'single' ? 'block' : 'none';
                document.getElementById('batchInput').style.display = e.target.value === 'batch' ? 'block' : 'none';
            });
        });
    }

    async function getSuggestions(keyword) {
        const response = await fetch(`https://suggestqueries.google.com/complete/search?client=chrome&q=${encodeURIComponent(keyword)}`);
        const data = await response.json();
        return data[1];
    }

    function updateProgress(current, total, collectedItems) {
        const progressBar = document.getElementById('progressBar');
        const progressText = document.getElementById('progressText');
        const collectedCount = document.getElementById('collectedCount');
        const progress = document.getElementById('progress');

        progress.style.display = 'block';
        const percentage = (current / total) * 100;
        progressBar.style.width = percentage + '%';
        progressText.textContent = `${current}/${total}`;
        collectedCount.textContent = collectedItems.size;
    }

    function generateCombinations(letters, depth) {
        if (depth === 1) return letters.map(letter => [letter]);

        const combinations = [];
        for (let i = 0; i < letters.length; i++) {
            const subCombinations = generateCombinations(letters.slice(i + 1), depth - 1);
            subCombinations.forEach(subComb => {
                combinations.push([letters[i], ...subComb]);
            });
        }
        return combinations;
    }

    async function collectSuggestions(baseKeyword) {
        const result = new Set();
        const letters = 'abcdefghijklmnopqrstuvwxyz'.split('');
        const resultDiv = document.getElementById('result');
        const onlyEnglish = document.getElementById('onlyEnglish').checked;
        const searchDepth = parseInt(document.getElementById('searchDepth').value);

        const isEnglishOnly = (text) => /^[A-Za-z0-9\s.,!?-]+$/.test(text);

        if (shouldStop) {
            return Array.from(result);
        }

        // 收集基础关键词的建议
        const baseSuggestions = await getSuggestions(baseKeyword);
        baseSuggestions.forEach(s => {
            if (!onlyEnglish || isEnglishOnly(s)) {
                result.add(s);
            }
        });

        // 生成所有可能的字母组合
        const allCombinations = [];
        for (let depth = 1; depth <= searchDepth; depth++) {
            const depthCombinations = generateCombinations(letters, depth);
            allCombinations.push(...depthCombinations);
        }

        // 更新进度条的总数
        const totalCombinations = allCombinations.length;
        updateProgress(0, totalCombinations, result);

        // 对每个组合进行查询
        for (let i = 0; i < allCombinations.length; i++) {
            if (shouldStop) {
                break;
            }

            const combination = allCombinations[i];
            const letterCombination = combination.join('');
            const suggestions = await getSuggestions(`${baseKeyword} ${letterCombination}`);

            suggestions.forEach(s => {
                if (!onlyEnglish || isEnglishOnly(s)) {
                    result.add(s);
                }
            });

            updateProgress(i + 1, totalCombinations, result);
            resultDiv.innerHTML = `<textarea style="width: 100%; height: 200px;">${Array.from(result).join('\n')}</textarea>`;

            await new Promise(resolve => setTimeout(resolve, 200));
        }

        return Array.from(result);
    }

    function init() {
        addStyles();
        createUI();

        const startCollectBtn = document.getElementById('startCollect');

        startCollectBtn.addEventListener('click', async () => {
            if (isCollecting) {
                // 如果正在收集,点击按钮则停止
                shouldStop = true;
                startCollectBtn.textContent = 'start collect';
                startCollectBtn.style.background = '#4CAF50';
                isCollecting = false;
                return;
            }

            const isBatchMode = document.querySelector('input[name="inputMode"]:checked').value === 'batch';
            let keywords = [];

            if (isBatchMode) {
                const batchText = document.getElementById('batchKeywords').value.trim();
                keywords = batchText.split('\n').filter(k => k.trim());
            } else {
                const singleKeyword = document.getElementById('baseKeyword').value.trim();
                if (singleKeyword) {
                    keywords = [singleKeyword];
                }
            }

            if (keywords.length === 0) {
                alert('Please enter a keyword');
                return;
            }

            // 开始收集
            isCollecting = true;
            shouldStop = false;
            startCollectBtn.textContent = 'stop collect';
            startCollectBtn.style.background = '#ff4444';

            const resultDiv = document.getElementById('result');
            resultDiv.innerHTML = 'Collecting...';
            document.getElementById('progress').style.display = 'block';

            try {
                const allSuggestions = new Set();
                const totalKeywords = keywords.length;

                for (let i = 0; i < keywords.length; i++) {
                    if (shouldStop) {
                        break;
                    }

                    const keyword = keywords[i];
                    document.getElementById('totalProgress').textContent = `${i + 1}/${totalKeywords}`;
                    document.getElementById('totalProgressBar').style.width = `${((i + 1) / totalKeywords) * 100}%`;

                    const suggestions = await collectSuggestions(keyword);
                    suggestions.forEach(s => allSuggestions.add(s));
                }

                const resultText = Array.from(allSuggestions).join('\n');
                resultDiv.innerHTML = `
                    <textarea style="width: 100%; height: 200px;">${resultText}</textarea>
                    <button id="copyBtn">Copy to Clipboard</button>
                `;

                document.getElementById('copyBtn').addEventListener('click', () => {
                    GM_setClipboard(resultText);
                    alert('Copied to clipboard!');
                });
            } catch (error) {
                resultDiv.innerHTML = 'Error occurred while collecting: ' + error.message;
            } finally {
                // 恢复按钮状态
                isCollecting = false;
                shouldStop = false;
                startCollectBtn.textContent = 'start collect';
                startCollectBtn.style.background = '#4CAF50';
            }
        });
    }

    init();
})();