您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
根據網址(正規表達式)聆聽按鍵事件點選指定元素的函式庫,提供點選規則與快捷鍵的 CRUD 操作。
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/542910/1626267/%E5%BF%AB%E6%8D%B7%E9%8D%B5%E5%87%BD%E5%BC%8F%E5%BA%AB.js
/**name => ShortcutLibrary description => 根據網址(正規表達式)聆聽按鍵事件點選指定元素的函式庫,提供點選規則與快捷鍵的 CRUD 操作。 version => 1.0.0 author => Max namespace => https://github.com/Max46656 license => MPL2.0 本程式具有以下依賴,須添加在你使用的腳本中 @grant GM_getValue @grant GM_setValue @grant GM_info */ class ShortcutAPI { constructor() { // 初始化:設定規則管理器和快捷鍵處理器 this.ruleManager = new RuleManager(); this.shortcutHandler = new ShortcutHandler(this.ruleManager); } // 新增快捷鍵規則 // 輸入參數: rule (object) - 規則物件,包含 ruleName, urlPattern, selectorType, selector, nthElement, shortcut, ifLinkOpen, isEnabled // 返回值: boolean - 是否成功新增規則 addRule(rule) { if (!this.validateRule(rule)) { console.warn(`${GM_info.script.name}: 無效的規則物件: ${JSON.stringify(rule)}`); return false; } const conflicts = this.checkConflicts(rule, window.location.href); if (conflicts.length > 0) { conflicts.forEach(conflict => { console.warn(`${GM_info.script.name}: 新規則 "${rule.ruleName}" 檢測到${conflict.type === 'shortcut' ? '相同的快捷鍵組合' : '相同的目標元素'}: 與規則 "${conflict.rule.ruleName}" 衝突 (快捷鍵: ${conflict.rule.shortcut}, 選擇器: ${conflict.rule.selector}, 第幾個元素: ${conflict.rule.nthElement})`); }); } this.ruleManager.addRule(rule); return true; } // 更新指定索引的規則 // 輸入參數: index (number) - 規則索引 // rule (object) - 更新後的規則物件 // 返回值: boolean - 是否成功更新規則 updateRule(index, rule) { if (!this.validateRule(rule)) { console.warn(`${GM_info.script.name}: 無效的更新規則物件: ${JSON.stringify(rule)}`); return false; } const conflicts = this.checkConflicts(rule, window.location.href, index); if (conflicts.length > 0) { conflicts.forEach(conflict => { console.warn(`${GM_info.script.name}: 更新規則 "${rule.ruleName}" 檢測到${conflict.type === 'shortcut' ? '相同的快捷鍵組合' : '相同的目標元素'}: 與規則 "${conflict.rule.ruleName}" 衝突 (快捷鍵: ${conflict.rule.shortcut}, 選擇器: ${conflict.rule.selector}, 第幾個元素: ${conflict.rule.nthElement})`); }); } this.ruleManager.updateRule(index, rule); return true; } // 刪除指定索引的規則 // 輸入參數: index (number) - 規則索引 // 返回值: boolean - 是否成功刪除規則 deleteRule(index) { if (index < 0 || index >= this.ruleManager.clickRules.rules.length) { console.warn(`${GM_info.script.name}: 無效的規則索引: ${index}`); return false; } this.ruleManager.deleteRule(index); return true; } // 獲取所有規則 // 輸入參數: 無 // 返回值: array - 包含所有規則的陣列 getRules() { return this.ruleManager.clickRules.rules; } // 檢查規則是否與現有規則衝突 // 輸入參數: rule (object) - 待檢查的規則物件 // url (string) - 檢查衝突的網址 // excludeIndex (number, optional) - 排除檢查的規則索引 // 返回值: array - 包含衝突資訊的陣列 checkConflicts(rule, url, excludeIndex = -1) { return this.ruleManager.checkConflicts(rule, url, excludeIndex); } // 啟用指定規則 // 輸入參數: index (number) - 規則索引 // 返回值: boolean - 是否成功啟用規則 enableRule(index) { if (index < 0 || index >= this.ruleManager.clickRules.rules.length) { console.warn(`${GM_info.script.name}: 無效的規則索引: ${index}`); return false; } const rule = this.ruleManager.clickRules.rules[index]; rule.isEnabled = true; this.ruleManager.updateRules(); return true; } // 停用指定規則 // 輸入參數: index (number) - 規則索引 // 返回值: boolean - 是否成功停用規則 disableRule(index) { if (index < 0 || index >= this.ruleManager.clickRules.rules.length) { console.warn(`${GM_info.script.name}: 無效的規則索引: ${index}`); return false; } const rule = this.ruleManager.clickRules.rules[index]; rule.isEnabled = false; this.ruleManager.updateRules(); return true; } // 驗證規則是否有效 // 輸入參數: rule (object) - 規則物件 // 返回值: boolean - 是否為有效規則 validateRule(rule) { if (!rule || typeof rule !== 'object') return false; try { new RegExp(rule.urlPattern); } catch (e) { console.warn(`${GM_info.script.name}: 無效的正則表達式: ${rule.urlPattern}`); return false; } if (!rule.selector || !['css', 'xpath'].includes(rule.selectorType)) { console.warn(`${GM_info.script.name}: 無效的選擇器: ${rule.selector}`); return false; } if (!this.shortcutHandler.validateShortcut(rule.shortcut)) { console.warn(`${GM_info.script.name}: 無效的快捷鍵: ${rule.shortcut}`); return false; } return true; } } // 規則管理類,負責儲存、驗證和管理快捷鍵規則 class RuleManager { constructor() { // 初始化:從 GM_getValue 取得規則,若無則使用預設空規則集 this.clickRules = this.sanitizeRules(GM_getValue('clickRules', { rules: [] })); } // 清理並驗證規則,確保規則格式正確 // 輸入參數: clickRules (object) - 包含規則陣列的物件 // 返回值: object - 清理後的規則物件 sanitizeRules(clickRules) { const defaultRule = { ruleName: '', urlPattern: '.*', selectorType: 'css', selector: '', nthElement: 1, shortcut: 'Control+A', ifLinkOpen: false, isEnabled: true }; const validRules = clickRules.rules.filter(rule => { return rule && typeof rule === 'object' && rule.shortcut && this.isValidShortcut(rule.shortcut); }).map(rule => ({ ...defaultRule, ...rule, ruleName: rule.ruleName || `規則 ${clickRules.rules.indexOf(rule) + 1}`, isEnabled: rule.isEnabled !== undefined ? rule.isEnabled : true })); return { rules: validRules }; } // 驗證快捷鍵格式是否有效 // 輸入參數: shortcut (string) - 快捷鍵字串,例如 "Control+A" // 返回值: boolean - 是否為有效快捷鍵 isValidShortcut(shortcut) { const validModifiers = ['Control', 'Alt', 'Shift', 'CapsLock', 'NumLock']; if (!shortcut || typeof shortcut !== 'string') return false; const parts = shortcut.split('+'); if (parts.length < 2 || parts.length > 3) return false; const mainKey = parts[parts.length - 1]; const modifiers = parts.slice(0, -1); return modifiers.every(mod => validModifiers.includes(mod)) && mainKey.length === 1 && /^[a-zA-Z0-9]$/.test(mainKey); } // 檢查新規則是否與現有規則衝突 // 輸入參數: newRule (object) - 新規則物件 // currentUrl (string) - 當前網址 // excludeIndex (number) - 排除檢查的規則索引(用於更新時) // 返回值: array - 包含衝突資訊的陣列 checkConflicts(newRule, currentUrl, excludeIndex = -1) { const conflicts = []; this.clickRules.rules.forEach((rule, index) => { if (index === excludeIndex) return; try { if (new RegExp(rule.urlPattern).test(currentUrl)) { if (rule.shortcut.toLowerCase() === newRule.shortcut.toLowerCase()) { conflicts.push({ type: 'shortcut', rule, index }); } else if (rule.selector === newRule.selector && rule.nthElement === newRule.nthElement) { conflicts.push({ type: 'element', rule, index }); } } } catch (e) { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 的正則表達式無效: ${rule.urlPattern}`); } }); return conflicts; } // 新增規則到規則集 // 輸入參數: newRule (object) - 新規則物件 // 返回值: void addRule(newRule) { this.clickRules.rules.push(newRule); this.updateRules(); } // 更新指定索引的規則 // 輸入參數: index (number) - 規則索引 // updatedRule (object) - 更新後的規則物件 // 返回值: void updateRule(index, updatedRule) { this.clickRules.rules[index] = updatedRule; this.updateRules(); } // 刪除指定索引的規則 // 輸入參數: index (number) - 規則索引 // 返回值: void deleteRule(index) { this.clickRules.rules.splice(index, 1); this.updateRules(); } // 將規則集儲存到 GM_setValue // 輸入參數: 無 // 返回值: void updateRules() { GM_setValue('clickRules', this.clickRules); } } // 快捷鍵處理類,負責監聽鍵盤事件並執行點選動作 class ShortcutHandler { constructor(ruleManager) { // 初始化:設定規則管理器並綁定鍵盤事件監聽器 this.ruleManager = ruleManager; this.keydownHandler = (event) => this.handleKeydown(event); window.addEventListener('keydown', this.keydownHandler); } // 驗證快捷鍵格式是否有效 // 輸入參數: shortcut (string) - 快捷鍵字串 // 返回值: boolean - 是否為有效快捷鍵 validateShortcut(shortcut) { const validModifiers = ['Control', 'Alt', 'Shift', 'CapsLock', 'NumLock']; if (!shortcut) return false; const parts = shortcut.split('+'); if (parts.length < 2 || parts.length > 3) return false; const mainKey = parts[parts.length - 1]; const modifiers = parts.slice(0, -1); return modifiers.every(mod => validModifiers.includes(mod)) && mainKey.length === 1 && /^[a-zA-Z0-9]$/.test(mainKey); } // 處理鍵盤按下事件,檢查是否符合快捷鍵並執行動作 // 輸入參數: event (KeyboardEvent) - 鍵盤事件物件 // 返回值: void handleKeydown(event) { const currentUrl = window.location.href; this.ruleManager.clickRules.rules.forEach((rule, index) => { try { if (!rule.isEnabled || !new RegExp(rule.urlPattern).test(currentUrl)) return; const shortcutParts = rule.shortcut.split('+'); const mainKey = shortcutParts[shortcutParts.length - 1]; const modifiers = shortcutParts.slice(0, -1); const allModifiersPressed = modifiers.every(mod => event.getModifierState(mod)); const mainKeyPressed = event.key.toUpperCase() === mainKey.toUpperCase(); if (allModifiersPressed && mainKeyPressed) { event.preventDefault(); this.clickElement(rule, index); } } catch (e) { console.warn(`${GM_info.script.name}: 處理規則 "${rule.ruleName}" 時發生錯誤: ${e}`); } }); } // 執行點選指定元素的動作 // 輸入參數: rule (object) - 規則物件 // ruleIndex (number) - 規則索引 // 返回值: boolean - 是否成功點選元素 clickElement(rule, ruleIndex) { try { const elements = this.getElements(rule.selectorType, rule.selector); if (elements.length === 0) { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 未找到符合元素: ${rule.selector}`); return false; } let targetIndex; if (rule.nthElement > 0) { targetIndex = rule.nthElement - 1; } else if (rule.nthElement < 0) { targetIndexDr = elements.length + rule.nthElement; } else { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 的 nthElement 無效: 0 不允許`); return false; } if (targetIndex < 0 || targetIndex >= elements.length) { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 的 nthElement 無效: ${rule.nthElement}, 找到 ${elements.length} 個元素`); return false; } const targetElement = elements[targetIndex]; if (targetElement) { console.log(`${GM_info.script.name}: 規則 "${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}: 規則 "${rule.ruleName}" 的目標元素未找到`); return false; } } catch (e) { console.warn(`${GM_info.script.name}: 規則 "${rule.ruleName}" 執行失敗: ${e}`); return false; } } // 根據選擇器類型獲取元素 // 輸入參數: selectorType (string) - 選擇器類型 ('css' 或 'xpath') // selector (string) - 選擇器字串 // 返回值: array - 符合的元素陣列 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 []; } } } window.ShortcutLibrary = ShortcutAPI;