Chrome扩展推文回复问题:如何在无textarea的div弹窗中输入内容并触发回复
Hey there, let's work through this problem together! You've already nailed clicking the reply icon, but the tricky part is dealing with that contenteditable div instead of a standard textarea. Here's how to get your reply text in there and hit that reply button:
1. Target the Contenteditable Input Div
Twitter/X uses a contenteditable div for reply inputs (instead of a textarea) to support rich text formatting. After clicking the reply icon, wait for the modal to load (it's dynamic), then target this div with a reliable selector:
const replyInput = document.querySelector('div[contenteditable="true"][role="textbox"]');
2. Insert Your Reply Text
You can't use .value here like you would with a textarea. Instead, focus the div, set its text content, and trigger an input event so Twitter recognizes the text (this is key to enabling the reply button):
if (replyInput) { // Focus the input to activate it replyInput.focus(); // Clear any placeholder text replyInput.textContent = ''; // Insert your custom reply replyInput.textContent = 'Your awesome reply goes here!'; // Dispatch an input event to let Twitter know content changed const inputEvent = new Event('input', { bubbles: true }); replyInput.dispatchEvent(inputEvent); }
3. Click the Reply Button
Once your text is in place, find and click the enabled reply button. Using the data-testid attribute is more reliable than class names (since Twitter changes those often):
const replyButton = document.querySelector('div[data-testid="tweetButton"]'); if (replyButton && !replyButton.disabled) { replyButton.click(); }
4. Full Working Code (With Dynamic Element Waiting)
Since the reply modal loads after clicking the icon, we need to wait for elements to exist instead of using flaky setTimeout. Here's the full code combining your existing click logic with the new input/button logic:
// Helper function to wait for a dynamic element to appear function waitForElement(selector, timeout = 5000) { return new Promise((resolve, reject) => { // Check if element exists immediately const element = document.querySelector(selector); if (element) { resolve(element); return; } // Use MutationObserver to watch for element additions const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { observer.disconnect(); resolve(element); } }); observer.observe(document.body, { childList: true, subtree: true }); // Timeout if element never appears setTimeout(() => { observer.disconnect(); reject(new Error(`Element "${selector}" not found within timeout`)); }, timeout); }); } // Your existing code to locate and click the reply icon var articles = $("article"); var reply_array = []; articles.each(function (increment, current) { var reply_g = ""; for (var i = 0; i < $(current).find("svg").length; i++) { reply_g = $(current).find("svg g path").eq(i).attr("d"); if ( reply_g == "M14.046 2.242l-4.148-.01h-.002c-4.374 0-7.8 3.427-7.8 7.802 0 4.098 3.186 7.206 7.465 7.37v3.828c0 .108.044.286.12.403.142.225.384.347.632.347.138 0 .277-.038.402-.118.264-.168 6.473-4.14 8.088-5.506 1.902-1.61 3.04-3.97 3.043-6.312v-.017c-.006-4.367-3.43-7.787-7.8-7.788zm3.787 12.972c-1.134.96-4.862 3.405-6.772 4.643V16.67c0-.414-.335-.75-.75-.75h-.396c-3.66 0-6.318-2.476-6.318-5.886 0-3.534 2.768-6.302 6.3-6.302l4.147.01h.002c3.532 0 6.3 2.766 6.302 6.296-.003 1.91-.942 3.844-2.514 5.176z" ) { var uniq_id = uniqID(20); $(current).find("svg").eq(i).parent().addClass(uniq_id); reply_array.push(uniq_id); } } }); $(" ." + reply_array[0]).trigger("click"); // Handle the reply input and button after modal loads waitForElement('div[contenteditable="true"][role="textbox"]') .then(replyInput => { replyInput.focus(); replyInput.textContent = 'Your custom reply text here!'; // Trigger input event to enable the reply button replyInput.dispatchEvent(new Event('input', { bubbles: true })); // Wait for the reply button to become enabled return waitForElement('div[data-testid="tweetButton"]:not([disabled])'); }) .then(replyButton => { replyButton.click(); console.log('Reply sent successfully!'); }) .catch(err => { console.error('Error handling reply:', err); });
Quick Notes:
- Selector Reliability: Twitter/X frequently updates its DOM, so using
data-testidattributes (likedata-testid="tweetButton") is more stable than relying on SVG paths or class names. - Permissions: Make sure your extension's
manifest.jsonincludes permission forhttps://x.com/*(orhttps://twitter.com/*if you're targeting the old domain). - Input Event: Don't skip dispatching the
inputevent—Twitter's UI uses this to detect content changes and enable the reply button.
内容的提问来源于stack exchange,提问作者Junaid Rehman

