您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Deletes the current Gemini conversation with a keyboard shortcut or button, provides a Tampermonkey menu command and a Ctrl+Shift+? shortcut to show script status, and a Ctrl+Shift+S shortcut to click the final action button. Uses MutationObserver instead of resize listener.
当前为
// ==UserScript== // @name Gemini Conversation Delete Shortcut // @namespace https://x.com/TakashiSasaki/greasyfork/533285 // @version 1.6.8 // @description Deletes the current Gemini conversation with a keyboard shortcut or button, provides a Tampermonkey menu command and a Ctrl+Shift+? shortcut to show script status, and a Ctrl+Shift+S shortcut to click the final action button. Uses MutationObserver instead of resize listener. // @author Takashi Sasasaki // @license MIT // @homepageURL https://x.com/TakashiSasaki // @match https://gemini.google.com/app/* // @match https://gemini.google.com/app // @icon https://www.gstatic.com/lamda/images/gemini_favicon_f069958c85030456e93de685481c559f160ea06b.png // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // --- Utility to check if an element is visible --- function isElementVisible(el) { if (!el) return false; const style = window.getComputedStyle(el); if (style.display === 'none') return false; if (style.visibility === 'hidden') return false; if (style.opacity === '0') return false; return el.offsetParent !== null; } // --- Function to show status (for menu command and shortcut) --- function showStatusDialog() { const headers = document.querySelectorAll('div.response-container-header'); const currentUrl = window.location.href; const menuButtonElement = document.querySelector(SELECTOR_MENU_BUTTON); const menuButtonIsDomPresent = !!menuButtonElement; const menuButtonIsCurrentlyVisible = isElementVisible(menuButtonElement); alert( `Gemini Conversation Delete Shortcut is active (version 1.6.8).\n` + // バージョン更新 `URL: ${currentUrl}\n` + `Found ${headers.length} elements matching div.response-container-header.\n` + `Conversation actions menu button (${SELECTOR_MENU_BUTTON}):\n` + ` - In DOM: ${menuButtonIsDomPresent}\n` + ` - Visible: ${menuButtonIsCurrentlyVisible}\n` + `Using MutationObserver for UI changes.` // 追記 ); console.log(`Delete Shortcut Status: URL=${currentUrl}, Found ${headers.length} headers. Menu button DOM present: ${menuButtonIsDomPresent}, Visible: ${menuButtonIsCurrentlyVisible}. Using MutationObserver.`); } // --- Register Tampermonkey menu command for status --- GM_registerMenuCommand('Show delete shortcut status', showStatusDialog); // --- Configuration --- const SHORTCUT_KEY_CODE = 'Backspace'; const USE_CTRL_KEY = true; const USE_SHIFT_KEY = true; const USE_ALT_KEY = false; const USE_META_KEY = false; const SELECTOR_MENU_BUTTON = '[data-test-id="conversation-actions-button"]'; const SELECTOR_DELETE_BUTTON_IN_MENU = '[data-test-id="delete-button"]'; const SELECTOR_CONFIRM_BUTTON_IN_DIALOG = '[data-test-id="confirm-button"]'; const SELECTOR_FINAL_BUTTON = '#app-root > main > div > button'; const WAIT_AFTER_MENU_CLICK = 100; const WAIT_AFTER_DELETE_CLICK = 100; const POLLING_INTERVAL = 50; const MAX_POLLING_TIME = 3000; const MAX_WIDTH_FOR_AUTOMATION = 960; // --- Utility functions --- function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function pollForElement(selector, maxTime, interval) { const startTime = Date.now(); while (Date.now() - startTime < maxTime) { const element = document.querySelector(selector); if (element) { return element; } await sleep(interval); } return null; } // --- Main automation sequence (for deleting conversation up to confirm button) --- async function performAutomationSequence() { if (window.innerWidth > MAX_WIDTH_FOR_AUTOMATION) { console.warn(`Automation sequence aborted: window width (${window.innerWidth}px) exceeds MAX_WIDTH_FOR_AUTOMATION (${MAX_WIDTH_FOR_AUTOMATION}px). This function is intended for narrower (mobile-like) views.`); // 幅が広い場合でもアラートは出さずにコンソール警告のみにするか、アラートを出すかは要検討 // alert(`ウィンドウ幅が ${MAX_WIDTH_FOR_AUTOMATION}px を超えているため、自動化シーケンスは実行されません。`); return; } try { const menuButton = document.querySelector(SELECTOR_MENU_BUTTON); if (!menuButton || !isElementVisible(menuButton)) { console.error(`Menu button (${SELECTOR_MENU_BUTTON}) not found or not visible. Automation sequence cannot proceed.`); alert('会話アクションメニューが見つからないか非表示のため、削除処理を実行できません。'); throw new Error('Menu button not found or not visible'); } menuButton.click(); await sleep(WAIT_AFTER_MENU_CLICK); const deleteBtn = await pollForElement(SELECTOR_DELETE_BUTTON_IN_MENU, MAX_POLLING_TIME, POLLING_INTERVAL); if (!deleteBtn || !isElementVisible(deleteBtn)) throw new Error('Delete button not found in menu or not visible'); deleteBtn.click(); await sleep(WAIT_AFTER_DELETE_CLICK); const confirmBtn = await pollForElement(SELECTOR_CONFIRM_BUTTON_IN_DIALOG, MAX_POLLING_TIME, POLLING_INTERVAL); if (!confirmBtn || !isElementVisible(confirmBtn)) throw new Error('Confirm button not found in dialog or not visible'); console.log(`Confirm button (${SELECTOR_CONFIRM_BUTTON_IN_DIALOG}) found. Focusing and highlighting.`); confirmBtn.focus({ preventScroll: false }); confirmBtn.style.backgroundColor = 'lightgreen'; confirmBtn.style.border = '3px solid green'; confirmBtn.style.color = 'black'; confirmBtn.style.outline = '2px dashed darkgreen'; console.log('The confirmation button is now highlighted. Please press Enter or click it to confirm deletion. Use Ctrl+Shift+S to click any subsequent final button.'); } catch (err) { console.error('Automation error:', err.message); // エラー発生時にもユーザーに通知する方が親切かもしれない // alert(`自動化シーケンス中にエラーが発生しました: ${err.message}`); } } // --- Keyboard shortcut listener --- document.addEventListener('keydown', event => { // Conversation Delete Shortcut (Ctrl+Shift+Backspace) if (event.code === SHORTCUT_KEY_CODE && event.ctrlKey === USE_CTRL_KEY && event.shiftKey === USE_SHIFT_KEY && event.altKey === USE_ALT_KEY && event.metaKey === USE_META_KEY) { event.preventDefault(); event.stopPropagation(); const menuButtonElement = document.querySelector(SELECTOR_MENU_BUTTON); if (!isElementVisible(menuButtonElement)) { console.log(`Shortcut activated for delete, but menu button (${SELECTOR_MENU_BUTTON}) is not visible or not present. Aborting.`); alert('会話アクションメニュー(︙)が見つからないか、現在表示されていません。\nそのため、ショートカットキーによる削除処理は実行できません。'); return; } // 幅チェックをここでも行う(オプションだが、ユーザー体験としては良いかも) if (window.innerWidth > MAX_WIDTH_FOR_AUTOMATION) { console.warn(`Shortcut activated, but window width (${window.innerWidth}px) exceeds limit (${MAX_WIDTH_FOR_AUTOMATION}px). Automation sequence will likely be aborted.`); // 必要ならアラート表示 // alert(`現在のウィンドウ幅 (${window.innerWidth}px) は自動化シーケンスの実行上限 (${MAX_WIDTH_FOR_AUTOMATION}px) を超えています。`); } performAutomationSequence(); } // Show Status Dialog Shortcut (Ctrl+Shift+?) else if (event.ctrlKey && event.shiftKey && event.key === '?') { event.preventDefault(); event.stopPropagation(); console.log('Shortcut activated for status dialog (Ctrl+Shift+?).'); showStatusDialog(); } // Click Final Button Shortcut (Ctrl+Shift+S) else if (event.ctrlKey && event.shiftKey && (event.key === 'S' || event.key === 's')) { event.preventDefault(); event.stopPropagation(); console.log(`Shortcut activated for clicking final button (Ctrl+Shift+S).`); const finalButton = document.querySelector(SELECTOR_FINAL_BUTTON); if (finalButton && isElementVisible(finalButton)) { console.log(`Final button (${SELECTOR_FINAL_BUTTON}) found and visible. Clicking.`); finalButton.click(); } else { console.warn(`Final button (${SELECTOR_FINAL_BUTTON}) not found or not visible for Ctrl+Shift+S shortcut.`); alert(`最終処理ボタン(${SELECTOR_FINAL_BUTTON} で指定されるボタン)が見つからないか、現在表示されていません。`); } } }, true); // --- Manual trigger button insertion with tracing --- function insertManualTriggerButton() { const menuButtonElement = document.querySelector(SELECTOR_MENU_BUTTON); const isMenuButtonCurrentlyVisible = isElementVisible(menuButtonElement); const existingShortcutButtons = document.querySelectorAll('.delete-shortcut-button'); if (isMenuButtonCurrentlyVisible) { // メニューボタンが表示されている場合、削除ボタンを挿入または確認する console.log(`Menu button (${SELECTOR_MENU_BUTTON}) found and is visible. Checking/inserting delete shortcut button.`); // ログ変更 const wrapperSelector = 'div.menu-button-wrapper'; // このセレクタがGeminiのUIで安定しているか要確認 let targetWrapper = null; // メニューボタンを含む、または隣接する可能性のあるラッパーを探す if (menuButtonElement.closest(wrapperSelector)) { targetWrapper = menuButtonElement.closest(wrapperSelector); console.log('Found wrapper via closest:', targetWrapper); } else if (menuButtonElement.parentElement && menuButtonElement.parentElement.classList.contains('menu-button-wrapper')) { // 古い構造へのフォールバック?(前のコードにあったロジック) targetWrapper = menuButtonElement.parentElement; console.log('Found wrapper via parentElement:', targetWrapper); } else { // 他の探し方も試す (例: menuButtonElementの親を辿る) let parent = menuButtonElement.parentElement; while(parent && parent !== document.body) { // `menu-button-wrapper` クラスを持つ要素を探す、または特定の構造を探す // この部分はGeminiのHTML構造に依存するため、調整が必要になる可能性が高い // 例: 特定のクラスを持つ親を探す if(parent.querySelector(SELECTOR_MENU_BUTTON)) { // さらに探索... wrapperらしき要素を見つけるロジック } // この例では単純化のため、見つからないケースとしておく parent = parent.parentElement; } console.log('Could not reliably determine the target wrapper near the menu button.'); } // 適切なラッパーが見つかった場合のみボタンを挿入 if (targetWrapper) { // ボタンがまだ挿入されていないか確認 let existingButton = targetWrapper.parentNode.querySelector('.delete-shortcut-button'); // 挿入位置が wrapper の隣であるかを確認するロジックを改善 // (targetWrapperのすぐ隣にボタンがあるべきか、など配置ルールによる) let shouldInsert = true; if (existingButton && existingButton.previousElementSibling === targetWrapper) { shouldInsert = false; // すでに正しい位置にある console.log('Delete button already exists next to the wrapper.'); } else if (existingButton) { // 存在はするが位置が違う場合、一旦削除して再挿入するか、何もしないか console.log('Delete button exists but maybe in the wrong place. Removing old one.'); existingButton.remove(); shouldInsert = true; } if (shouldInsert) { console.log(`Inserting delete button next to wrapper:`, targetWrapper); const btn = document.createElement('button'); btn.className = 'delete-shortcut-button'; btn.title = 'Delete conversation (Ctrl+Shift+Backspace)'; btn.textContent = '🗑️'; btn.style.marginLeft = '8px'; btn.style.padding = '4px'; btn.style.border = '1px solid red'; btn.style.background = 'yellow'; btn.style.cursor = 'pointer'; btn.style.zIndex = '9999'; // 必要に応じて調整 btn.addEventListener('click', event => { event.preventDefault(); event.stopPropagation(); // イベント伝播を止める performAutomationSequence(); }); // ラッパー要素の直後にボタンを挿入 targetWrapper.parentNode.insertBefore(btn, targetWrapper.nextSibling); console.log('Inserted delete button.'); } } else { console.log('No suitable wrapper found to insert the delete button near the menu button.'); // ラッパーが見つからない場合、既存のボタンがあれば削除する(孤立防止) existingShortcutButtons.forEach(btn => { console.log('Removing orphaned delete shortcut button as no suitable wrapper was found.'); btn.remove(); }); } } else { // メニューボタンが表示されていない場合、既存の削除ボタンがあれば削除する console.log(`Menu button (${SELECTOR_MENU_BUTTON}) not found or not visible. Removing any existing delete shortcut buttons.`); if (existingShortcutButtons.length > 0) { existingShortcutButtons.forEach(button => { button.remove(); console.log('Removed an existing delete shortcut button.'); }); } } } // --- MutationObserver setup --- // debounce処理を追加して、短時間に大量の変更があってもinsertManualTriggerButtonの呼び出しを抑制する let observerDebounceTimeout; const observer = new MutationObserver(mutationsList => { clearTimeout(observerDebounceTimeout); observerDebounceTimeout = setTimeout(() => { console.log('MutationObserver triggered (debounced), updating delete button status.'); insertManualTriggerButton(); }, 150); // 150msのデバウンス時間 (調整可能) }); // --- Start observing DOM changes --- // 監視対象はbody全体、子要素の追加削除、サブツリー全体、style/class属性の変更を監視 const targetNode = document.body; const config = { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }; observer.observe(targetNode, config); console.log('MutationObserver started observing document.body.'); // --- Initial button check --- // スクリプト実行時に一度ボタンの状態をチェック console.log('Performing initial check for delete button upon script load.'); // 少し遅延させてDOMの準備が整うのを待つ(より確実に) setTimeout(insertManualTriggerButton, 500); // 500ms待機 (調整可能) })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址