您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
根據網址(正規表達式)自動點選指定元素的函式庫,提供點選規則與點選任務的 CRUD 操作。
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/540647/1613497/%E8%87%AA%E5%8B%95%E9%BB%9E%E9%81%B8%E5%85%83%E7%B4%A0%E5%87%BD%E5%BC%8F%E5%BA%AB.js
/** name => 自動點選元素函式庫 description => 根據網址(正規表達式)自動點選指定元素的函式庫,提供點選規則與點選任務的 CRUD 操作。 version => 1.0.2 author => Max namespace => https://github.com/Max46656 license => MPL2.0 本程式具有以下依賴,須添加在你使用的腳本中 @grant GM_getValue @grant GM_setValue @grant GM_info */ class RuleManager { /** * 初始化規則管理器,從儲存中載入點選規則。 */ constructor() { this.clickRules = GM_getValue('clickRules', { rules: [] }); } /** * 添加新點選規則,檢查是否重複。 * @param {Object} newRule - 新規則物件。 * @param {string} newRule.ruleName - 規則名稱。 * @param {string} newRule.urlPattern - 網址正規表達式。 * @param {string} newRule.selector - 元素選擇器。 * @param {string} newRule.selectorType - 選擇器類型(css 或 xpath)。 * @param {number} [newRule.nthElement=1] - 點選第幾個符合元素(從 1 開始)。 * @param {number} [newRule.clickDelay=1000] - 點選間隔(毫秒)。 * @param {boolean} [newRule.keepClicking=false] - 是否持續點選。 * @param {boolean} [newRule.ifLinkOpen=false] - 若為連結是否跳轉。 * @returns {boolean} - 添加成功返回 true,已存在返回 false。 */ addRule(newRule) { const exists = this.clickRules.rules.some(rule => rule.ruleName === newRule.ruleName && rule.urlPattern === newRule.urlPattern && rule.selector === newRule.selector ); if (exists) { console.log(`${GM_info.script.name}: 規則 "${newRule.ruleName}" 已存在,跳過添加`); return false; } this.clickRules.rules.push(newRule); this.updateRules(); console.log(`${GM_info.script.name}: 規則 "${newRule.ruleName}" 添加成功`); return true; } /** * 更新指定索引的點擊規則。 * @param {number} index - 規則索引。 * @param {Object} updatedRule - 更新後的規則物件,結構同 addRule. * @throws {Error} 若索引無效,拋出錯誤。 */ updateRule(index, updatedRule) { if (index < 0 || index >= this.clickRules.rules.length) { throw new Error(`Invalid rule index: ${index}`); } this.clickRules.rules[index] = updatedRule; this.updateRules(); } /** * 刪除指定索引的點擊規則。 * @param {number} index - 規則索引。 * @throws {Error} 若索引無效,拋出錯誤。 */ deleteRule(index) { if (index < 0 || index >= this.clickRules.rules.length) { throw new Error(`Invalid rule index: ${index}`); } this.clickRules.rules.splice(index, 1); this.updateRules(); } /** * 獲取點擊規則,可按網址過濾。 * @param {string} [filter=null] - 過濾網址,若為 null 返回所有規則。 * @returns {Array<Object>} - 符合條件的規則陣列。 */ getRules(filter = null) { if (!filter) return this.clickRules.rules; return this.clickRules.rules.filter(rule => { try { return new RegExp(rule.urlPattern).test(filter); } catch (e) { console.warn(`${GM_info.script.name}: Invalid regex in rule "${rule.ruleName}": ${rule.urlPattern}`); return false; } }); } /** * 將當前規則儲存至持久化儲存。 */ updateRules() { GM_setValue('clickRules', this.clickRules); } } class ClickTaskManager { /** * 初始化任務管理器,關聯規則管理器。 * @param {RuleManager} ruleManager - 規則管理器實例。 */ constructor(ruleManager) { this.ruleManager = ruleManager; this.intervalIds = {}; this.isRunningTasks = false; this.runTasksTimeout = null; } /** * 為指定規則添加點擊任務,定期執行點擊。 * @param {number} ruleIndex - 規則索引。 * @param {string} [source='manual'] - 任務來源(用於日誌)。 * @returns {number} - 任務的 setInterval ID. * @throws {Error} 若規則索引無效或規則缺少必要屬性,拋出錯誤。 */ addTask(ruleIndex, source = 'manual') { const rule = this.ruleManager.getRules()[ruleIndex]; if (!rule) { throw new Error(`Invalid rule index: ${ruleIndex}`); } if (!rule.urlPattern || !rule.selector) { throw new Error(`Invalid rule: missing urlPattern or selector`); } if (this.intervalIds[ruleIndex]) { clearInterval(this.intervalIds[ruleIndex]); delete this.intervalIds[ruleIndex]; console.log(`${GM_info.script.name}: Cleared existing task for rule "${rule.ruleName}" (index: ${ruleIndex})`); } const intervalId = setInterval(() => { const clicked = this.autoClick(rule, ruleIndex); if (clicked && !rule.keepClicking) { clearInterval(this.intervalIds[ruleIndex]); delete this.intervalIds[ruleIndex]; console.log(`${GM_info.script.name}: Task stopped for rule "${rule.ruleName}" (index: ${ruleIndex}) due to successful click and keepClicking=false`); } }, rule.clickDelay || 1000); this.intervalIds[ruleIndex] = intervalId; console.log(`${GM_info.script.name}: Task started for rule "${rule.ruleName}" (index: ${ruleIndex}, intervalId: ${intervalId}, source: ${source})`); return intervalId; } /** * 為所有未執行任務的規則啟動點擊任務。 * @param {string} [source='manual'] - 任務來源(用於日誌)。 */ runTasks(source = 'manual') { if (this.isRunningTasks) { console.log(`${GM_info.script.name}: runTasks already in progress, skipping (source: ${source})`); return; } this.isRunningTasks = true; console.log(`${GM_info.script.name}: Starting runTasks (source: ${source}, rules: ${this.ruleManager.getRules().length})`); this.ruleManager.getRules().forEach((rule, index) => { if (!this.intervalIds[index]) { try { this.addTask(index, source); } catch (e) { console.warn(`${GM_info.script.name}: Failed to start task for rule "${rule.ruleName}" (index: ${index}): ${e.message}`); } } else { console.log(`${GM_info.script.name}: Skipped task for rule "${rule.ruleName}" (index: ${index}) as it is already running`); } }); this.isRunningTasks = false; } /** * 清除所有正在執行的點擊任務。 * @param {string} [source='manual'] - 任務來源(用於日誌)。 * @returns {number} - 清除的任務數量。 */ clearTasks(source = 'manual') { const clearedIds = Object.keys(this.intervalIds); clearedIds.forEach(index => { clearInterval(this.intervalIds[index]); console.log(`${GM_info.script.name}: Cleared task for rule index ${index} (intervalId: ${this.intervalIds[index]}, source: ${source})`); delete this.intervalIds[index]; }); if (this.runTasksTimeout) { clearTimeout(this.runTasksTimeout); this.runTasksTimeout = null; console.log(`${GM_info.script.name}: Cleared pending runTasks timeout (source: ${source})`); } return clearedIds.length; } /** * 防抖執行 runTasks,限制短時間內多次調用。 * @param {string} [source='manual'] - 任務來源(用於日誌)。 * @param {number} [delay=100] - 防抖延遲時間(毫秒)。 */ debounceRunTasks(source = 'manual', delay = 100) { if (this.runTasksTimeout) { clearTimeout(this.runTasksTimeout); console.log(`${GM_info.script.name}: Debounced previous runTasks (source: ${source})`); } this.runTasksTimeout = setTimeout(() => { this.runTasks(source); this.runTasksTimeout = null; }, delay); console.log(`${GM_info.script.name}: Scheduled debounced runTasks (source: ${source}, delay: ${delay}ms)`); } /** * 根據規則執行自動點擊。 * @param {Object} rule - 點擊規則物件。 * @param {number} ruleIndex - 規則索引。 * @returns {boolean} - 點擊成功返回 true,失敗返回 false. */ 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}: No elements found for rule "${rule.ruleName}": ${rule.selector}`); return false; } if (rule.nthElement < 1 || rule.nthElement > elements.length) { console.warn(`${GM_info.script.name}: Invalid nthElement for rule "${rule.ruleName}": ${rule.nthElement}, found ${elements.length} elements`); return false; } const targetElement = elements[rule.nthElement - 1]; if (targetElement) { console.log(`${GM_info.script.name}: Successfully clicked element for rule "${rule.ruleName}":`, targetElement); if (rule.ifLinkOpen && targetElement.tagName === "A" && targetElement.href) { window.location.href = targetElement.href; } else { targetElement.click(); } return true; } else { console.warn(`${GM_info.script.name}: Target element not found for rule "${rule.ruleName}"`); return false; } } catch (e) { console.warn(`${GM_info.script.name}: Failed to execute rule "${rule.ruleName}": ${e.message}`); return false; } } /** * 根據選擇器類型獲取 DOM 元素。 * @param {string} selectorType - 選擇器類型(css 或 xpath)。 * @param {string} selector - 選擇器表達式。 * @returns {Array<HTMLElement>} - 匹配的元素陣列。 */ 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}: Invalid selector "${selector}": ${e.message}`); return []; } } } class ClickController { /** * 初始化控制器,創建規則和任務管理器。 */ constructor() { this.ruleManager = new RuleManager(); this.clickTaskManager = new ClickTaskManager(this.ruleManager); } /** * 驗證規則的合法性。 * @param {Object} rule - 待驗證的規則物件。 * @param {string} rule.urlPattern - 網址正規表達式。 * @param {string} rule.selector - 元素選擇器。 * @param {string} rule.selectorType - 選擇器類型(css 或 xpath)。 * @returns {Object} - 驗證結果,{ success: boolean, error: string|null }。 */ validateRule(rule) { try { new RegExp(rule.urlPattern); } catch (e) { return { success: false, error: `無效的正規表達式: ${rule.urlPattern}` }; } if (!rule.selector || !['css', 'xpath'].includes(rule.selectorType)) { return { success: false, error: `無效的選擇器: ${rule.selector}` }; } return { success: true }; } /** * 添加新點擊規則,包含驗證和去重。 * @param {Object} rule - 新規則物件,結構同 RuleManager.addRule. * @returns {Object} - 添加結果,{ success: boolean, error: string|null }。 */ addRule(rule) { const validation = this.validateRule(rule); if (!validation.success) { return validation; } try { const added = this.ruleManager.addRule({ ruleName: rule.ruleName || `規則 ${this.ruleManager.getRules().length + 1}`, urlPattern: rule.urlPattern, selectorType: rule.selectorType, selector: rule.selector, nthElement: parseInt(rule.nthElement) || 1, clickDelay: parseInt(rule.clickDelay) || 1000, keepClicking: Boolean(rule.keepClicking), ifLinkOpen: Boolean(rule.ifLinkOpen) }); return { success: added, error: added ? null : `規則 "${rule.ruleName}" 已存在` }; } catch (e) { return { success: false, error: `添加規則失敗: ${e.message}` }; } } /** * 獲取點擊規則,可按網址過濾。 * @param {string} [filter=null] - 過濾網址,若為 null 返回所有規則。 * @returns {Object} - 獲取結果,{ success: boolean, data: Array<Object>, error: string|null }。 */ getRules(filter = null) { try { const rules = this.ruleManager.getRules(filter); return { success: true, data: rules }; } catch (e) { return { success: false, error: `獲取規則失敗: ${e.message}` }; } } /** * 更新指定索引的點擊規則,並重新啟動任務。 * @param {number} index - 規則索引。 * @param {Object} rule - 更新後的規則物件,結構同 addRule. * @returns {Object} - 更新結果,{ success: boolean, error: string|null }。 */ updateRule(index, rule) { const validation = this.validateRule(rule); if (!validation.success) { return validation; } try { this.ruleManager.updateRule(index, { ruleName: rule.ruleName || `規則 ${index + 1}`, urlPattern: rule.urlPattern, selectorType: rule.selectorType, selector: rule.selector, nthElement: parseInt(rule.nthElement) || 1, clickDelay: parseInt(rule.clickDelay) || 1000, keepClicking: Boolean(rule.keepClicking), ifLinkOpen: Boolean(rule.ifLinkOpen) }); this.clickTaskManager.clearTasks('updateRule'); this.clickTaskManager.debounceRunTasks('updateRule'); return { success: true }; } catch (e) { return { success: false, error: `更新規則失敗: ${e.message}` }; } } /** * 刪除指定索引的點擊規則,並重新啟動任務。 * @param {number} index - 規則索引。 * @returns {Object} - 刪除結果,{ success: boolean, error: string|null }。 */ deleteRule(index) { try { this.ruleManager.deleteRule(index); this.clickTaskManager.clearTasks('deleteRule'); this.clickTaskManager.debounceRunTasks('deleteRule'); return { success: true }; } catch (e) { return { success: false, error: `刪除規則失敗: ${e.message}` }; } } /** * 為指定規則添加點擊任務。 * @param {number} ruleIndex - 規則索引。 * @returns {Object} - 添加結果,{ success: boolean, taskId: number, error: string|null }。 */ addTask(ruleIndex) { try { const taskId = this.clickTaskManager.addTask(ruleIndex, 'addTask'); return { success: true, taskId }; } catch (e) { return { success: false, error: `添加任務失敗: ${e.message}` }; } } /** * 啟動所有點擊任務,啟用防抖。 * @returns {Object} - 執行結果,{ success: boolean, error: string|null }。 */ runTasks() { try { this.clickTaskManager.debounceRunTasks('runTasks'); return { success: true }; } catch (e) { return { success: false, error: `執行任務失敗: ${e.message}` }; } } /** * 清除所有點擊任務。 * @returns {Object} - 清除結果,{ success: boolean, data: { clearedCount: number }, error: string|null }。 */ clearTasks() { try { const clearedCount = this.clickTaskManager.clearTasks('clearTasks'); return { success: true, data: { clearedCount } }; } catch (e) { return { success: false, error: `清除任務失敗: ${e.message}` }; } } } window.ClickItForYou = ClickController;