click it for you

在符合正則表達式的網址上自動點擊指定的元素。

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         click it for you
// @name:zh-TW   click it for you
// @name:ja      あなたのためにクリック
// @name:en      click it for you
// @name:de      Für dich klicken
// @name:es      Clic automático para ti
// @description  在符合正則表達式的網址上自動點擊指定的元素。
// @description:zh-TW 在符合正則表達式的網址上自動點擊指定的元素。
// @description:ja 正規表現に一致するURLで指定された要素を自動的にクリックします。
// @description:en Automatically clicks specified elements on URLs matching a regular expression.
// @description:de Klickt automatisch auf angegebene Elemente auf URLs, die mit einem regulären Ausdruck übereinstimmen.
// @description:es Hace clic automáticamente en elementos especificados en URLs que coinciden con una expresión regular.

// @author       Max
// @namespace    https://github.com/Max46656

// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_info
// @version      1.0.2
// @license MPL2.0
// ==/UserScript==

class AutoClickManager {

    clickRules;

    intervalIds = {}

    i18n = {
        'zh-TW': {
            title: '自動點擊設定',
            matchingRules: '符合的規則',
            noMatchingRules: '當前網址無符合的規則。',
            addRuleSection: '新增規則',
            ruleName: '規則名稱:',
            urlPattern: '網址正則表達式:',
            selectorType: '選擇器類型:',
            selector: '選擇器:',
            nthElement: '第幾個元素(從 1 開始):',
            addRule: '新增規則',
            save: '儲存',
            delete: '刪除',
            ruleNamePlaceholder: '例如:我的規則',
            urlPatternPlaceholder: '例如:https://example\\.com/.*',
            selectorPlaceholder: '例如:button.submit 或 //button[@class="submit"]'
        },
        'en': {
            title: 'Auto Click Configuration',
            matchingRules: 'Matching Rules',
            noMatchingRules: 'No rules match the current URL.',
            addRuleSection: 'Add New Rule',
            ruleName: 'Rule Name:',
            urlPattern: 'URL Pattern (Regex):',
            selectorType: 'Selector Type:',
            selector: 'Selector:',
            nthElement: 'Nth Element (1-based):',
            addRule: 'Add Rule',
            save: 'Save',
            delete: 'Delete',
            ruleNamePlaceholder: 'e.g., My Rule',
            urlPatternPlaceholder: 'e.g., https://example\\.com/.*',
            selectorPlaceholder: 'e.g., button.submit or //button[@class="submit"]'
        },
        'ja': {
            title: '自動クリック設定',
            matchingRules: '一致するルール',
            noMatchingRules: '現在のURLに一致するルールはありません。',
            addRuleSection: '新しいルールを追加',
            ruleName: 'ルール名:',
            urlPattern: 'URLパターン(正規表現):',
            selectorType: 'セレクタタイプ:',
            selector: 'セレクタ:',
            nthElement: '何番目の要素(1から):',
            addRule: 'ルールを追加',
            save: '儲存',
            delete: '削除',
            ruleNamePlaceholder: '例:マイルール',
            urlPatternPlaceholder: '例:https://example\\.com/.*',
            selectorPlaceholder: '例:button.submit または //button[@class="submit"]'
        },
        'de': {
            title: 'Automatische Klick-Einstellungen',
            matchingRules: 'Passende Regeln',
            noMatchingRules: 'Keine Regeln passen zur aktuellen URL.',
            addRuleSection: 'Neue Regel hinzufügen',
            ruleName: 'Regelname:',
            urlPattern: 'URL-Muster (Regulärer Ausdruck):',
            selectorType: 'Selektortyp:',
            selector: 'Selektor:',
            nthElement: 'N-tes Element (ab 1):',
            addRule: 'Regel hinzufügen',
            save: 'Speichern',
            delete: 'Löschen',
            ruleNamePlaceholder: 'Beispiel: Meine Regel',
            urlPatternPlaceholder: 'Beispiel: https://example\\.com/.*',
            selectorPlaceholder: 'Beispiel: button.submit oder //button[@class="submit"]'
        },
        'es': {
            title: 'Configuración de Clic Automático',
            matchingRules: 'Reglas Coincidentes',
            noMatchingRules: 'No hay reglas que coincidan con la URL actual.',
            addRuleSection: 'Agregar Nueva Regla',
            ruleName: 'Nombre de la Regla:',
            urlPattern: 'Patrón de URL (Regex):',
            selectorType: 'Tipo de Selector:',
            selector: 'Selector:',
            nthElement: 'N-ésimo Elemento (desde 1):',
            addRule: 'Agregar Regla',
            save: 'Guardar',
            delete: 'Eliminar',
            ruleNamePlaceholder: 'Ejemplo: Mi Regla',
            urlPatternPlaceholder: 'Ejemplo: https://example\\.com/.*',
            selectorPlaceholder: 'Ejemplo: button.submit o //button[@class="submit"]'
        }
    };

    constructor() {
        this.clickRules = GM_getValue('clickRules', {rules: []});
        this.registerMenu();
        this.runAutoClicks();
    }

    // 執行所有符合規則的自動點擊
    runAutoClicks() {
        this.clickRules.rules.forEach((rule, index) => {
            if (rule.urlPattern && rule.selector) {
                const intervalId = setInterval(() => {
                    const success = this.autoClick(rule, index);
                    if (success) {
                        clearInterval(this.intervalIds[index]);
                        delete this.intervalIds[index];
                    }
                }, 100);
                this.intervalIds[index] = intervalId;
            } else {
                console.warn(`${GM_info.script.name}:規則 "${rule.ruleName}" 無效(索引 ${index}):缺少 urlPattern 或 selector`);
            }
        });
    }

    // 執行單條規則的自動點擊,並返回是否成功
    autoClick(rule, ruleIndex) {
        const urlRegex = new RegExp(rule.urlPattern);
        if (!urlRegex.test(window.location.href)) {
            return false;
        }

        const elements = this.getElements(rule.selectorType, rule.selector);
        if (elements.length === 0) {
            console.warn(`${GM_info.script.name}:規則 "${rule.ruleName}" 未找到匹配元素:`, rule.selector);
            return false;
        }

        if (rule.nthElement < 1 || rule.nthElement > elements.length) {
            console.warn(`${GM_info.script.name}:規則 "${rule.ruleName}" 的 nthElement 無效:${rule.nthElement},找到 ${elements.length} 個元素`);
            return false;
        }

        const targetElement = elements[rule.nthElement - 1];
        if (targetElement) {
            console.log(`${GM_info.script.name}:規則 "${rule.ruleName}" 成功點擊元素:`, targetElement);
            targetElement.click();
            return true; // 點擊成功
        } else {
            console.warn(`${GM_info.script.name}:規則 "${rule.ruleName}" 未找到目標元素`);
            return false;
        }
    }

    // 根據選擇器類型獲取元素
    getElements(selectorType, selector) {
        if (selectorType === 'xpath') {
            const nodes = document.evaluate(selector, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
            const elements = [];
            for (let i = 0; i < nodes.snapshotLength; i++) {
                elements.push(nodes.snapshotItem(i));
            }
            return elements;
        } else if (selectorType === 'css') {
            return Array.from(document.querySelectorAll(selector));
        }
        return [];
    }

    // 創建配置選單
    createMenu() {
        const i18n = this.i18n[this.getLanguage()];
        const menu = document.createElement('div');
        menu.style.position = 'fixed';
        menu.style.top = '10px';
        menu.style.right = '10px';
        menu.style.background = 'rgb(36, 36, 36)';
        menu.style.color = 'rgb(204, 204, 204)';
        menu.style.border = '1px solid rgb(80, 80, 80)';
        menu.style.padding = '10px';
        menu.style.zIndex = '10000';
        menu.style.maxWidth = '400px';
        menu.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
        menu.innerHTML = `
        <style>
        #autoClickMenu {
            overflow-y: auto; /* 啟用垂直滾動條 */
            max-height: 80vh; /* 限制高度以啟用滾動 */
        }
        #autoClickMenu input, #autoClickMenu select, #autoClickMenu button {
            background: rgb(50, 50, 50);
            color: rgb(204, 204, 204);
            border: 1px solid rgb(80, 80, 80);
            margin: 5px 0;
            padding: 5px;
            width: 100%;
            box-sizing: border-box;
        }
        #autoClickMenu button {
            cursor: pointer; /* 設置滑鼠遊標為指針 */
        }
        #autoClickMenu button:hover {
            background: rgb(70, 70, 70); /* 滑鼠懸停時更改背景色 */
        }
        #autoClickMenu label {
            margin-top: 5px;
            display: block; /* 標籤顯示為塊元素 */
        }
        #autoClickMenu .ruleHeader {
            cursor: pointer; /* 設置滑鼠遊標為指針 */
            background: rgb(50, 50, 50);
            padding: 5px;
            margin: 5px 0;
            border-radius: 3px;
        }
        #autoClickMenu .ruleDetails {
            padding: 5px;
            border: 1px solid rgb(80, 80, 80);
            border-radius: 3px;
            margin-bottom: 5px;
        }
        #autoClickMenu .headerContainer {
            display: flex; /* 使用彈性佈局 */
            justify-content: space-between; /* 內容兩端對齊 */
            align-items: center; /* 垂直居中對齊 */
            margin-bottom: 10px;
        }
        #autoClickMenu .closeButton {
            width: auto; /* 根據內容設定最小寬度 */
            padding: 5px 10px; /* 一致的垂直內距,最小的水平內距 */
            margin: 0; /* 移除外距以正確對齊 */
        }
        </style>
        <div id="autoClickMenu">
            <div class="headerContainer">
                <h3>${i18n.title}</h3>
                <button id="closeMenu" class="closeButton">✕</button>
            </div>
            <div id="rulesList"></div>
            <h4>${i18n.addRuleSection}</h4>
            <label>${i18n.ruleName}</label>
            <input type="text" id="ruleName" placeholder="${i18n.ruleNamePlaceholder}">
            <label>${i18n.urlPattern}</label>
            <input type="text" id="urlPattern" placeholder="${i18n.urlPatternPlaceholder}">
            <label>${i18n.selectorType}</label>
            <select id="selectorType">
                <option value="css">CSS</option>
                <option value="xpath">XPath</option>
            </select>
            <label>${i18n.selector}</label>
            <input type="text" id="selector" placeholder="${i18n.selectorPlaceholder}">
            <label>${i18n.nthElement}</label>
            <input type="number" id="nthElement" min="1" value="1">
            <button id="addRule" style="margin-top: 10px;">${i18n.addRule}</button>
        </div>
            `;
        document.body.appendChild(menu);

        // 更新規則列表
        this.updateRulesList();

        // 新增規則按鈕事件
        document.getElementById('addRule').addEventListener('click', () => {
            const newRule = {
                ruleName: document.getElementById('ruleName').value || `規則 ${this.clickRules.rules.length + 1}`,
                urlPattern: document.getElementById('urlPattern').value,
                selectorType: document.getElementById('selectorType').value,
                selector: document.getElementById('selector').value,
                nthElement: parseInt(document.getElementById('nthElement').value) || 1,
            };
            this.clickRules.rules.push(newRule);
            this.saveConfigs();
            this.updateRulesList();
            document.getElementById('ruleName').value = '';
            document.getElementById('urlPattern').value = '';
            document.getElementById('selector').value = '';
            document.getElementById('nthElement').value = '1';
        });

        // 關閉選單按鈕事件
        document.getElementById('closeMenu').addEventListener('click', () => {
            menu.remove();
        });
    }

    // 更新規則列表(僅顯示當前網址匹配的規則)
    updateRulesList() {
        const rulesList = document.getElementById('rulesList');
        const i18n = this.i18n[this.getLanguage()];
        rulesList.innerHTML = `<h4>${i18n.matchingRules}</h4>`;
        const currentUrl = window.location.href;
        const matchingRules = this.clickRules.rules.filter(rule => {
            try {
                return new RegExp(rule.urlPattern).test(currentUrl);
            } catch (e) {
                console.warn(`${GM_info.script.name}:規則 "${rule.ruleName}" 的正則表達式無效:`, rule.urlPattern);
                return false;
            }
        });

        if (matchingRules.length === 0) {
            rulesList.innerHTML += `<p>${i18n.noMatchingRules}</p>`;
            return;
        }

        matchingRules.forEach((rule, index) => {
            const globalIndex = this.clickRules.rules.indexOf(rule);
            const lang = this.getLanguage();
            const i18n = this.i18n[lang];
            const ruleDiv = document.createElement('div');
            ruleDiv.innerHTML = `
                <div class="ruleHeader" id="ruleHeader${globalIndex}">
                    <strong>${rule.ruleName || `規則 ${globalIndex + 1}`}</strong>
                </div>
                <div class="ruleDetails" id="ruleDetails${globalIndex}" style="display: none;">
                    <label>${i18n.ruleName}</label>
                    <input type="text" id="editRuleName${globalIndex}" value="${rule.ruleName || ''}">
                    <label>${i18n.urlPattern}</label>
                    <input type="text" id="editUrlPattern${globalIndex}" value="${rule.urlPattern}">
                    <label>${i18n.selectorType}</label>
                    <select id="editSelectorType${globalIndex}">
                        <option value="css" ${rule.selectorType === 'css' ? 'selected' : ''}>CSS</option>
                        <option value="xpath" ${rule.selectorType === 'xpath' ? 'selected' : ''}>XPath</option>
                    </select>
                    <label>${i18n.selector}</label>
                    <input type="text" id="editSelector${globalIndex}" value="${rule.selector}">
                    <label>${i18n.nthElement}</label>
                    <input type="number" id="editNthElement${globalIndex}" min="1" value="${rule.nthElement}">
                    <button id="saveRule${globalIndex}">${i18n.save}</button>
                    <button id="deleteRule${globalIndex}">${i18n.delete}</button>
                </div>
            `;
            rulesList.appendChild(ruleDiv);

            // 為規則標題添加點擊事件(縮小/展開)
            document.getElementById(`ruleHeader${globalIndex}`).addEventListener('click', () => {
                const details = document.getElementById(`ruleDetails${globalIndex}`);
                details.style.display = details.style.display === 'none' ? 'block' : 'none';
            });

            // 為保存按鈕添加點擊事件
            document.getElementById(`saveRule${globalIndex}`).addEventListener('click', () => {
                // 停止該規則的自動點擊任務
                if (this.intervalIds[globalIndex]) {
                    clearInterval(this.intervalIds[globalIndex]);
                    delete this.intervalIds[globalIndex];
                }
                this.clickRules.rules[globalIndex] = {
                    ruleName: document.getElementById(`editRuleName${globalIndex}`).value || `規則 ${globalIndex + 1}`,
                    urlPattern: document.getElementById(`editUrlPattern${globalIndex}`).value,
                    selectorType: document.getElementById(`editSelectorType${globalIndex}`).value,
                    selector: document.getElementById(`editSelector${globalIndex}`).value,
                    nthElement: parseInt(document.getElementById(`editNthElement${globalIndex}`).value) || 1,
                };
                this.saveConfigs();
                this.updateRulesList();
            });

            // 重新啟動自動點擊任務
            if (this.clickRules.rules[globalIndex].urlPattern && this.clickRules.rules[globalIndex].selector) {
                const intervalId = setInterval(() => {
                    const success = this.autoClick(this.clickRules.rules[globalIndex], globalIndex);
                    if (success) {
                        clearInterval(this.intervalIds[globalIndex]);
                        delete this.intervalIds[globalIndex];
                    }
                }, 200);
                this.intervalIds[globalIndex] = intervalId;
            }

            // 為刪除按鈕添加點擊事件
            document.getElementById(`deleteRule${globalIndex}`).addEventListener('click', () => {
                // 停止該規則的自動點擊任務
                if (this.intervalIds[globalIndex]) {
                    clearInterval(this.intervalIds[globalIndex]);
                    delete this.intervalIds[globalIndex];
                }
                this.clickRules.rules.splice(globalIndex, 1);
                this.saveConfigs();
                this.updateRulesList();
            });
        });
    }

    // 獲取當前語言
    getLanguage() {
        const lang = navigator.language || navigator.userLanguage;
        if (lang.startsWith('zh')) return 'zh-TW';
        if (lang.startsWith('ja')) return 'ja';
        if (lang.startsWith('ko')) return 'ko';
        if (lang.startsWith('es')) return 'es';
        return 'en'; // 預設英文
    }

    // 保存配置至本地存儲
    saveRules() {
        GM_setValue('clickRules', this.clickRules);
    }

    // 註冊選單命令
    registerMenu() {
        GM_registerMenuCommand(this.i18n[this.getLanguage()].title, () => this.createMenu());
    }
}

const johnTheYubisaku = new AutoClickManager();