您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在符合正則表達式的網址上自動點擊指定的元素。
// ==UserScript== // @name click it for you // @name:zh-TW 為你自動點擊 // @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. // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @grant GM_info // @version 1.0.5 // @author Max // @namespace https://github.com/Max46656 // @license MPL2.0 // ==/UserScript== class RuleManager { clickRules; constructor() { this.clickRules = GM_getValue('clickRules', { rules: [] }); } addRule(newRule) { this.clickRules.rules.push(newRule); this.updateRules(); } updateRule(index, updatedRule) { this.clickRules.rules[index] = updatedRule; this.updateRules(); } deleteRule(index) { this.clickRules.rules.splice(index, 1); this.updateRules(); } updateRules() { GM_setValue('clickRules', this.clickRules); } } class WebElementHandler { ruleManager; clickTaskManager; i18n = { 'zh-TW': { title: '自動點擊設定', matchingRules: '符合的規則', noMatchingRules: '當前網址無符合的規則。', addRuleSection: '新增規則', ruleName: '規則名稱:', urlPattern: '網址正則表達式:', selectorType: '選擇器類型:', selector: '選擇器:', nthElement: '第幾個元素(從 1 開始):', clickDelay: '點擊延遲(毫秒):', addRule: '新增規則', save: '儲存', delete: '刪除', ruleNamePlaceholder: '例如:我的規則', urlPatternPlaceholder: '例如:https://example\\.com/.*', selectorPlaceholder: '例如:button.submit 或 //button[@class="submit"]', invalidRegex: '無效的正則表達式', invalidSelector: '無效的選擇器' }, '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):', clickDelay: 'Click Delay (ms):', 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"]', invalidRegex: 'Invalid regular expression', invalidSelector: 'Invalid selector' }, 'ja': { title: '自動クリック設定', matchingRules: '一致するルール', noMatchingRules: '現在のURLに一致するルールはありません。', addRuleSection: '新しいルールを追加', ruleName: 'ルール名:', urlPattern: 'URLパターン(正規表現):', selectorType: 'セレクタタイプ:', selector: 'セレクタ:', nthElement: '何番目の要素(1から):', clickDelay: 'クリック遅延(ミリ秒):', addRule: 'ルールを追加', save: '儲存', delete: '削除', ruleNamePlaceholder: '例:マイルール', urlPatternPlaceholder: '例:https://example\\.com/.*', selectorPlaceholder: '例:button.submit または //button[@class="submit"]', invalidRegex: '無効な正規表現', invalidSelector: '無効なセレクター' }, '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):', clickDelay: 'Klickverzögerung (ms):', 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"]', invalidRegex: 'Ungültiger regulärer Ausdruck', invalidSelector: 'Ungültiger Selektor' }, '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):', clickDelay: 'Retraso de Clic (ms):', addRule: 'Agregar Regla', save: 'Guardar', delete: 'Eliminar', ruleNamePlaceholder: 'Ejemplo: Mi Regla', urlPatternPlaceholder: 'Ejemplo: https://example\\.com/.*', selectorPlaceholder: 'Ejemplo: button.submit o //button[@class="submit"]', invalidRegex: 'Expresión regular inválida', invalidSelector: 'Selector inválido' } }; constructor(ruleManager, clickTaskManager) { this.ruleManager = ruleManager; this.clickTaskManager = clickTaskManager; this.setupUrlChangeListener(); } // 獲取選單標題(用於 registerMenu) getMenuTitle() { return this.i18n[this.getLanguage()].title; } // 獲取當前語言 getLanguage() { const lang = navigator.language || navigator.userLanguage; if (lang.startsWith('zh')) return 'zh-TW'; if (lang.startsWith('ja')) return 'ja'; if (lang.startsWith('de')) return 'de'; if (lang.startsWith('es')) return 'es'; return 'en'; } // 驗證規則輸入 validateRule(rule) { const i18n = this.i18n[this.getLanguage()]; try { new RegExp(rule.urlPattern); } catch (e) { alert(`${i18n.invalidRegex}: ${rule.urlPattern}`); return false; } if (!rule.selector || !['css', 'xpath'].includes(rule.selectorType)) { alert(`${i18n.invalidSelector}: ${rule.selector}`); return false; } return true; } // 創建單個規則的 HTML 結構 createRuleElement(rule, ruleIndex) { const i18n = this.i18n[this.getLanguage()]; const ruleDiv = document.createElement('div'); ruleDiv.innerHTML = ` <div class="ruleHeader" id="ruleHeader${ruleIndex}"> <strong>${rule.ruleName || `規則 ${ruleIndex + 1}`}</strong> </div> <div class="readRule" id="readRule${ruleIndex}" style="display: none;"> <label>${i18n.ruleName}</label> <input type="text" id="updateRuleName${ruleIndex}" value="${rule.ruleName || ''}"> <label>${i18n.urlPattern}</label> <input type="text" id="updateUrlPattern${ruleIndex}" value="${rule.urlPattern}"> <label>${i18n.selectorType}</label> <select id="updateSelectorType${ruleIndex}"> <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="updateSelector${ruleIndex}" value="${rule.selector}"> <label>${i18n.nthElement}</label> <input type="number" id="updateNthElement${ruleIndex}" min="1" value="${rule.nthElement}"> <label>${i18n.clickDelay}</label> <input type="number" id="updateClickDelay${ruleIndex}" min="100" value="${rule.clickDelay || 200}"> <button id="updateRule${ruleIndex}">${i18n.save}</button> <button id="deleteRule${ruleIndex}">${i18n.delete}</button> </div> `; return ruleDiv; } // 創建組態選單 createMenuElement() { 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 .readRule { 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"> <label>${i18n.clickDelay}</label> <input type="number" id="clickDelay" min="50" value="10000"> <button id="addRule" style="margin-top: 10px;">${i18n.addRule}</button> </div> `; document.body.appendChild(menu); this.updateRulesElement(); document.getElementById('addRule').addEventListener('click', () => { const newRule = { ruleName: document.getElementById('ruleName').value || `規則 ${this.ruleManager.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, clickDelay: parseInt(document.getElementById('clickDelay').value) || 200 }; if (!this.validateRule(newRule)) return; this.ruleManager.addRule(newRule); this.updateRulesElement(); this.clickTaskManager.clearAutoClicks(); this.clickTaskManager.runAutoClicks(); document.getElementById('ruleName').value = ''; document.getElementById('urlPattern').value = ''; document.getElementById('selector').value = ''; document.getElementById('nthElement').value = '1'; document.getElementById('clickDelay').value = '200'; }); document.getElementById('closeMenu').addEventListener('click', () => { menu.remove(); }); } // 更新規則列表(僅顯示當前網址符合的規則) updateRulesElement() { 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.ruleManager.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) => { const ruleIndex = this.ruleManager.clickRules.rules.indexOf(rule); const ruleDiv = this.createRuleElement(rule, ruleIndex); rulesList.appendChild(ruleDiv); document.getElementById(`ruleHeader${ruleIndex}`).addEventListener('click', () => { const details = document.getElementById(`readRule${ruleIndex}`); details.style.display = details.style.display === 'none' ? 'block' : 'none'; }); document.getElementById(`updateRule${ruleIndex}`).addEventListener('click', () => { const updatedRule = { ruleName: document.getElementById(`updateRuleName${ruleIndex}`).value || `規則 ${ruleIndex + 1}`, urlPattern: document.getElementById(`updateUrlPattern${ruleIndex}`).value, selectorType: document.getElementById(`updateSelectorType${ruleIndex}`).value, selector: document.getElementById(`updateSelector${ruleIndex}`).value, nthElement: parseInt(document.getElementById(`updateNthElement${ruleIndex}`).value) || 1, clickDelay: parseInt(document.getElementById(`updateClickDelay${ruleIndex}`).value) || 200 }; if (!this.validateRule(updatedRule)) return; this.ruleManager.updateRule(ruleIndex, updatedRule); this.updateRulesElement(); this.clickTaskManager.clearAutoClicks(); this.clickTaskManager.runAutoClicks(); }); document.getElementById(`deleteRule${ruleIndex}`).addEventListener('click', () => { this.ruleManager.deleteRule(ruleIndex); this.updateRulesElement(); this.clickTaskManager.clearAutoClicks(); this.clickTaskManager.runAutoClicks(); }); }); } // 設置 URL 變更監聽器 setupUrlChangeListener() { const oldPushState = history.pushState; history.pushState = function pushState() { const ret = oldPushState.apply(this, arguments); window.dispatchEvent(new Event('pushstate')); window.dispatchEvent(new Event('locationchange')); return ret; }; const oldReplaceState = history.replaceState; history.replaceState = function replaceState() { const ret = oldReplaceState.apply(this, arguments); window.dispatchEvent(new Event('replacestate')); window.dispatchEvent(new Event('locationchange')); return ret; }; window.addEventListener('popstate', () => { window.dispatchEvent(new Event('locationchange')); }); window.addEventListener('locationchange', () => { this.clickTaskManager.clearAutoClicks(); this.clickTaskManager.runAutoClicks(); }); } } class ClickTaskManager { ruleManager; intervalIds = {}; constructor(ruleManager) { this.ruleManager = ruleManager; this.runAutoClicks(); } // 清除所有自動點擊任務 clearAutoClicks() { Object.keys(this.intervalIds).forEach(index => { clearInterval(this.intervalIds[index]); delete this.intervalIds[index]; }); } // 執行所有符合規則的自動點擊 runAutoClicks() { this.ruleManager.clickRules.rules.forEach((rule, index) => { if (rule.urlPattern && rule.selector && !this.intervalIds[index]) { const intervalId = setInterval(() => { const clicked = this.autoClick(rule, index); if (clicked) { clearInterval(this.intervalIds[index]); delete this.intervalIds[index]; } }, rule.clickDelay || 200); this.intervalIds[index] = intervalId; } else if (!rule.urlPattern || !rule.selector) { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 無效(索引 ${index}):缺少 urlPattern 或 selector`); } }); } // 執行單條規則的自動點擊,並返回是否成功 autoClick(rule, ruleIndex) { try { 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(); if (targetElement.tagName === "A" && targetElement.href) { window.location.href = targetElement.href; } return true; } else { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 未找到目標元素`); return false; } } catch (e) { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 執行失敗:`, e); return false; } } // 根據選擇器類型獲取元素 getElements(selectorType, selector) { try { 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 []; } catch (e) { console.warn(`${GM_info.script.name}: 選擇器 "${selector}" 無效:`, e); return []; } } } const Shirisaku = new RuleManager(); const Yubisaku = new ClickTaskManager(Shirisaku); const Mika = new WebElementHandler(Shirisaku, Yubisaku); GM_registerMenuCommand(Mika.getMenuTitle(), () => Mika.createMenuElement());