.'); txt = (await queryEle('article.afd-post__article > p, article > p', {maxWait: 1000}))?.textContent; } const m = txt?.trim().match(/#([^#]+)#/); const tag = m ? m[1].trim() : null; dbg('getCurrentTag: Afdian tag:', tag); return tag; } dbg('getCurrentTag: No matching site for tag extraction.'); return null; } async function jumpToSuwen() { // ... (jumpToSuwen implementation - no changes from 1.3.4) ... dbg('jumpToSuwen: Menu command triggered.'); const tag = await getCurrentTag(); if (!tag) { GM_notification({ text: '未检测到 #标签#,请确认首段包含标签', timeout: 3000 }); dbg('jumpToSuwen: No tag found, notification sent.'); return; } const url = `https://sooon.ai/home/read/feed?search=${encodeURIComponent(tag)}`; dbg('jumpToSuwen: Opening URL for tag:', tag, url); window.open(url, '_blank'); } const needMenu = /zhihu\.com\/question\/\d+\/answer\/\d+/.test(location.href) || /afdian\.com\/p\//.test(location.href); if (needMenu) { dbg('Registering menu command "🔗 跳转素问".'); GM_registerMenuCommand('🔗 跳转素问', jumpToSuwen); } function handleZhihu() { // ... (handleZhihu implementation - no changes from 1.3.4) ... dbg('handleZhihu: Initializing.'); const rootSel = '#root main .AnswerCard .RichContent--unescapable'; const addBtn = async () => { // dbg('handleZhihu - addBtn: Attempting to add button.'); const answerCards = document.querySelectorAll(rootSel); if (!answerCards.length) { return; } for (const root of answerCards) { if (root.querySelector('.go-sooon-link')) { continue; } // dbg('handleZhihu - addBtn: Processing card without button:', root); const tag = await getCurrentTag(); if (!tag) { // dbg('handleZhihu - addBtn: No tag found for this card/page.'); continue; } const first = root.querySelector('p[data-first-child], p:first-child'); if (!first) { // dbg('handleZhihu - addBtn: First paragraph not found in this card.'); continue; } const btn = document.createElement('button'); btn.className = 'go-sooon-link'; btn.textContent = '跳转素问'; btn.onclick = e => { const url = `https://sooon.ai/home/read/feed?search=${encodeURIComponent(tag)}`; dbg('handleZhihu - addBtn: Button clicked. Ctrl:', e.ctrlKey, 'URL:', url); e.ctrlKey ? window.open(url, '_blank') : (location.href = url); }; first.after(btn); dbg('handleZhihu - addBtn: Button added to card.'); } }; // dbg('handleZhihu: Setting up MutationObserver.'); new MutationObserver((mutations, observer) => { addBtn(); }).observe(document.body, { childList: true, subtree: true }); addBtn(); } function handleAfdian() { dbg('handleAfdian: Initializing.'); const addBtn = async () => { const tag = await getCurrentTag(); if (!tag) { dbg('handleAfdian - addBtn: No tag found.'); return; } const titleBox = await queryEle('.title-box h1, .main-post-title, h1.common-title.no-divider.mt0', { maxWait: 1500 }); if (titleBox && !titleBox.parentElement.querySelector('.go-sooon-link')) { const btn = document.createElement('button'); btn.className = 'go-sooon-link'; btn.textContent = '跳转素问'; btn.style.cssText = 'font-size:.75em;color:#946CE6;border:1px solid #946CE6;padding:2px 6px;margin-left:8px;border-radius:3px;vertical-align:middle;'; btn.onclick = e => { const url = `https://sooon.ai/home/read/feed?search=${encodeURIComponent(tag)}`; dbg('handleAfdian - addBtn: Button clicked. Ctrl:', e.ctrlKey, 'URL:', url); e.ctrlKey ? window.open(url, '_blank') : (location.href = url); }; titleBox.style.display = 'inline-block'; titleBox.after(btn); dbg('handleAfdian - addBtn: Button added after title.'); return; } const contentArea = await queryEle('div.feed-content.mt16.post-page.unlock, article.afd-post__article'); if (contentArea && !contentArea.querySelector('.go-sooon-link')) { const btn = document.createElement('button'); btn.className = 'go-sooon-link'; btn.textContent = '跳转素问'; btn.style.cssText = 'font-size:.75em;color:#946CE6;border:1px solid #946CE6;padding:2px 6px;margin-left:8px;border-radius:3px;vertical-align:middle;'; btn.onclick = e => { const url = `https://sooon.ai/home/read/feed?search=${encodeURIComponent(tag)}`; dbg('handleAfdian - addBtn: Button clicked. Ctrl:', e.ctrlKey, 'URL:', url); e.ctrlKey ? window.open(url, '_blank') : (location.href = url); }; contentArea.prepend(btn); dbg('handleAfdian - addBtn: Button prepended to content area.'); } else { dbg('handleAfdian - addBtn: No suitable location found or button already exists.'); } }; const observer = new MutationObserver(() => { addBtn(); }); observer.observe(document.body, { childList: true, subtree: true }); addBtn(); } function handleSooon() { const searchQuery = new URLSearchParams(location.search).get('search'); if (!searchQuery) { dbg('[Sooon] No "search" query parameter. Exiting Sooon handler.'); return; } dbg(`[Sooon] Initializing. Search Query: "${searchQuery}"`); let searching = false; let finished = false; const events = ['focus', 'visibilitychange', 'mousemove', 'keydown', 'scroll', 'touchstart']; async function doSearch(isInitialAttempt = false) { dbg(`[Sooon] doSearch: Entering. Query: "${searchQuery}", Initial: ${isInitialAttempt}, Searching: ${searching}, Finished: ${finished}`); if (searching || finished) { dbg('[Sooon] doSearch: Already searching or finished. Aborting.'); return; } searching = true; dbg('[Sooon] doSearch: Set searching = true.'); try { // Wrap core logic in try...finally to ensure `searching` is reset if (isInitialAttempt) { const initialDelay = 1500; dbg(`[Sooon] doSearch: Initial attempt, sleeping ${initialDelay}ms for page stabilization.`); await sleep(initialDelay); } const fastBtn = await queryEle('button[aria-label="快速搜索"]'); if (!fastBtn) { dbg('[Sooon] doSearch: "快速搜索" button not found.'); return; // searching will be set to false in finally } dbg('[Sooon] doSearch: "快速搜索" button found:', fastBtn); let searchModalInput = document.querySelector('form input[name="input"]'); let isModalAlreadyOpen = searchModalInput && searchModalInput.closest('form') && window.getComputedStyle(searchModalInput.closest('form')).display !== 'none'; if (!isModalAlreadyOpen) { dbg('[Sooon] doSearch: Search modal not open. Clicking "快速搜索".'); fastBtn.focus(); await sleep(50); fastBtn.click(); // Click to open modal await sleep(300); // Wait for modal animation / appearance if (!await queryEle('form input[name="input"]', {maxWait: 3000})) { dbg('[Sooon] doSearch: Search modal input did not appear after clicking "快速搜索".'); return; // searching will be set to false in finally } dbg('[Sooon] doSearch: Search modal input appeared.'); } else { dbg('[Sooon] doSearch: Search modal seems already open.'); } const formCtx = await queryEle('form:has(input[name="input"])'); if (!formCtx) { dbg('[Sooon] doSearch: Search form (form:has(input[name="input"])) not found.'); return; } dbg('[Sooon] doSearch: Search form context found:', formCtx); const input = await queryEle('input[name="input"]', {context: formCtx}); const submit = await queryEle('button[type="submit"]', {context: formCtx}); if (!input || !submit) { dbg('[Sooon] doSearch: Missing input or submit button in form.'); return; } dbg('[Sooon] doSearch: Input and submit button found:', input, submit); // --- Enhanced Input Simulation --- dbg('[Sooon] doSearch: Simulating input interaction.'); input.click(); // Click the input field await sleep(100); input.focus(); await sleep(100); dbg('[Sooon] doSearch: Clearing input field.'); input.value = ''; input.dispatchEvent(new Event('input', { bubbles: true, composed: true })); // composed: true for shadow DOM if any input.dispatchEvent(new Event('change', { bubbles: true })); // also dispatch change await sleep(150); // Longer pause after clearing dbg(`[Sooon] doSearch: Setting input value to "${searchQuery}".`); input.value = searchQuery; input.dispatchEvent(new Event('input', { bubbles: true, composed: true })); input.dispatchEvent(new Event('change', { bubbles: true })); await sleep(500); // Longer pause after setting value dbg('[Sooon] doSearch: Input value set. Input field:', input); // Optional: blur input before clicking submit, or directly focus submit // input.blur(); // await sleep(50); dbg('[Sooon] doSearch: Focusing and clicking submit button.'); submit.focus(); await sleep(100); // Ensure focus is registered submit.click(); dbg('[Sooon] doSearch: Submit button clicked.', submit); await sleep(800); // Increased wait for results after submit, give server/UI time const resultSelector = 'div.mantine-focus-never button span.inline-block'; const resultSpans = await queryEle(resultSelector, { all: true, context: formCtx, maxWait: 8000 }); let firstClickableResult = null; if (resultSpans && resultSpans.length > 0) { dbg(`[Sooon] doSearch: Found ${resultSpans.length} potential result spans.`); for (const span of resultSpans) { if (span.textContent.trim() === '') continue; const parentButton = span.closest('button'); if (parentButton && parentButton.offsetParent !== null) { firstClickableResult = parentButton; dbg('[Sooon] doSearch: Found clickable result:', parentButton.textContent.trim(), parentButton); break; } } } else { dbg(`[Sooon] doSearch: No result spans found with selector: "${resultSelector}".`); } if (firstClickableResult) { firstClickableResult.focus(); await sleep(50); firstClickableResult.click(); dbg('[Sooon] doSearch: Clicked first result. Text:', `"${firstClickableResult.textContent.trim()}"`); finished = true; dbg('[Sooon] doSearch: Set finished = true. Removing event listeners.'); events.forEach(ev => window.removeEventListener(ev, namedMaybeSearchWrapper)); } else { const allBtnsInForm = formCtx.querySelectorAll('div.mantine-focus-never button, div[role="listbox"] button'); dbg(`[Sooon] doSearch: No clickable result found. Query: "${searchQuery}". Candidate buttons in form: ${allBtnsInForm.length}`); } } catch (error) { dbg('[Sooon] doSearch: Error during execution:', error); // Potentially GM_notification or more robust error handling here } finally { searching = false; dbg('[Sooon] doSearch: Exiting. Searching set to false.'); } } const namedMaybeSearchWrapper = async (eventOrTrigger) => { const trigger = typeof eventOrTrigger === 'string' ? eventOrTrigger : (eventOrTrigger.type || 'unknown_event_type'); // Reduce log spam for frequent events like mousemove unless debugging them specifically if (trigger !== 'mousemove' || DEBUG) { dbg(`[Sooon] namedMaybeSearchWrapper: Triggered by "${trigger}". Query: "${searchQuery}", Searching: ${searching}, Finished: ${finished}, Visible: ${document.visibilityState}, HasFocus: ${document.hasFocus()}`); } if (trigger === 'init') { await sleep(1000); if (DEBUG) dbg(`[Sooon] namedMaybeSearchWrapper: Post 'init' delay. Visible: ${document.visibilityState}, HasFocus: ${document.hasFocus()}`); } // 强制检测是否卡住(搜索框已打开但没有点击结果) const stuckInModal = document.querySelector('form input[name="input"]') && !finished && document.visibilityState === 'visible' && document.hasFocus(); if ((!searching && !finished && document.visibilityState === 'visible' && document.hasFocus()) || stuckInModal) { dbg(`[Sooon] namedMaybeSearchWrapper: Trigger "${trigger}" - attempting doSearch. Stuck: ${stuckInModal}`); await doSearch(trigger === 'init' || stuckInModal); } else { // if (DEBUG || trigger !== 'mousemove') dbg('[Sooon] namedMaybeSearchWrapper: Conditions not met for doSearch. Skipping.'); } }; namedMaybeSearchWrapper('init'); events.forEach(ev => window.addEventListener(ev, namedMaybeSearchWrapper)); } (function addGlobalStyle() { // ... (addGlobalStyle implementation - no changes from 1.3.4) ... dbg('addGlobalStyle: Adding CSS.'); const css = ` .go-sooon-link{ background:none;font-size:1em;font-weight:500;color:#0066FF; border:1px solid #228BE6;padding:2px 8px;margin-left:10px;border-radius:3px; cursor:pointer;display:inline-block;vertical-align:middle } .go-sooon-link:hover{opacity:.8;color:gray;border-color:gray} `; const s = document.createElement('style'); s.textContent = css; document.head.appendChild(s); })(); function init() { // ... (init implementation - no changes from 1.3.4) ... dbg(`init: Script initializing. Host: ${location.hostname}, Path: ${location.pathname}, ReadyState: ${document.readyState}`); if (location.hostname.includes('zhihu.com')) handleZhihu(); else if (location.hostname.includes('afdian.com')) handleAfdian(); else if (location.hostname.includes('sooon.ai') && location.pathname.startsWith('/home/read/')) handleSooon(); else dbg('init: No matching site/path for handlers.'); } if (document.readyState === 'loading') { dbg('Document loading. Adding DOMContentLoaded listener.'); window.addEventListener('DOMContentLoaded', init); } else { dbg('Document already loaded. Calling init.'); init(); } dbg(`${SCRIPT_NAME} execution finished (initial setup phase).`); })();