Vanilla JavaScript实现LinkedIn自动消息发送文本框赋值失败问题
解决LinkedIn自动消息脚本无法插入文本框的问题
问题描述
我用原生JavaScript编写的LinkedIn自动消息发送脚本,除了无法将消息文本插入至输入框外,其余操作(如点击发送按钮等)均按预期执行。问题出在pasteNote函数中,控制台日志显示文本内容符合预期,但实际并未在文本框中显示。已尝试修改querySelector,问题仍未解决。
相关代码
Linkedin = { config: { scrollDelay: 2000, actionDelay: 3000, nextPageDelay: 5000, // 设置为-1表示无限制 maxRequests: -1, totalRequestsSent: 0, // 设置为false跳过邀请中的备注 // addNote: true, note: "Dear {{name}}, thank you for sharing your professional network." }, // 初始化脚本,延迟后调用scrollBottom init: function (data, config) { console.info("INFO: script initialized on the page..."); console.debug("DEBUG: scrolling to bottom in " + config.scrollDelay + " ms"); setTimeout(() => this.scrollBottom(data, config), config.actionDelay); }, // 平滑滚动到页面底部,延迟后调用scrollTop scrollBottom: function (data, config) { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); console.debug("DEBUG: scrolling to top in " + config.scrollDelay + " ms"); setTimeout(() => this.scrollTop(data, config), config.scrollDelay); }, // 平滑滚动回页面顶部,延迟后调用inspect scrollTop: function (data, config) { window.scrollTo({ top: 0, behavior: 'smooth' }); console.debug("DEBUG: inspecting elements in " + config.scrollDelay + " ms"); setTimeout(() => this.inspect(data, config), config.scrollDelay); }, // 检查页面搜索结果,调用compile处理 inspect: function (data, config) { var totalRows = this.totalRows(); console.debug("DEBUG: total search results found on page are " + totalRows); if (totalRows >= 0) { this.compile(data, config); } else { console.warn("WARN: end of search results!"); this.complete(config); } }, // 筛选页面上的"Message"按钮,获取联系人姓名 compile: function (data, config) { var elements = document.querySelectorAll('button'); data.pageButtons = [...elements].filter(function (element) { return element.textContent.trim() === "Message"; }); if (!data.pageButtons || data.pageButtons.length === 0) { console.warn("ERROR: no message buttons found on page!"); console.info("INFO: moving to next page..."); setTimeout(() => { this.nextPage(config) }, config.nextPageDelay); } else { data.pageButtonTotal = data.pageButtons.length; console.info("INFO: " + data.pageButtonTotal + " message buttons found"); data.pageButtonIndex = 0; var names = document.getElementsByClassName("entity-result__title-text"); names = [...names].filter(function (element) { return element.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.textContent.includes("Message\n"); }); data.connectNames = [...names].map(function (element) { return element.innerText.split(" ")[0]; }); console.debug("DEBUG: starting to send messages in " + config.actionDelay + " ms"); setTimeout(() => this.sendMessages(data, config), config.actionDelay); } }, // 点击Message按钮,延迟后调用pasteNote sendMessages: function (data, config) { console.debug("remaining requests " + config.maxRequests); if (config.maxRequests == 0) { console.info("INFO: max requests reached for the script run!"); this.complete(config); } else { console.debug('DEBUG: sending invite to ' + (data.pageButtonIndex + 1) + ' out of ' + data.pageButtonTotal); var button = data.pageButtons[data.pageButtonIndex]; button.click(); console.debug("DEBUG: pasting Message in popup, in " + config.actionDelay + " ms"); setTimeout(() => this.pasteNote(data, config), config.actionDelay); } }, // 插入消息文本的函数(原问题所在) pasteNote: function (data, config) { console.log("start of pasteNote" + noteTextBox); noteTextBox = document.querySelector("[class^='msg-form__msg-content-container--scrollable']"); console.log(noteTextBox.value); noteTextBox.value = config.note.replace("{{name}}", data.connectNames[data.pageButtonIndex]); noteTextBox.dispatchEvent(new Event('input', { bubbles: true })); window.onload = noteTextBox.value; console.log("end of pasteNote" + noteTextBox.value); console.debug("DEBUG: clicking send in popup, if present, in " + config.actionDelay + " ms"); setTimeout(() => this.clickDone(data, config), config.actionDelay); }, // 点击发送按钮 clickDone: function (data, config) { var buttons = document.querySelectorAll('button'); var doneButton = Array.prototype.filter.call(buttons, function (el) { return el.textContent.trim() === 'Send'; }); if (doneButton && doneButton[0]) { console.debug("DEBUG: clicking send button to close popup"); doneButton[0].click(); } else { console.debug("DEBUG: send button not found, clicking close on the popup in " + config.actionDelay); } setTimeout(() => this.clickClose(data, config), config.actionDelay); }, // 关闭弹出框,处理下一个联系人或下一页 clickClose: function (data, config) { var closeButton = document.getElementsByClassName('artdeco-modal__dismiss artdeco-button artdeco-button--circle artdeco-button--muted artdeco-button--2 artdeco-button--tertiary ember-view'); if (closeButton && closeButton[0]) { closeButton[0].click(); } console.info('INFO: invite sent to ' + (data.pageButtonIndex + 1) + ' out of ' + data.pageButtonTotal); config.maxRequests--; config.totalRequestsSent++; if (data.pageButtonIndex === (data.pageButtonTotal - 1)) { console.debug("DEBUG: all connections for the page done, going to next page in " + config.actionDelay + " ms"); setTimeout(() => this.nextPage(config), config.actionDelay); } else { data.pageButtonIndex++; console.debug("DEBUG: sending next invite in " + config.actionDelay + " ms"); setTimeout(() => this.sendMessages(data, config), config.actionDelay); } }, // 跳转到下一页 nextPage: function (config) { var pagerButton = document.getElementsByClassName('artdeco-pagination__button--next'); if (!pagerButton || pagerButton.length === 0 || pagerButton[0].hasAttribute('disabled')) { console.info("INFO: no next page button found!"); return this.complete(config); } console.info("INFO: Going to next page..."); pagerButton[0].children[0].click(); setTimeout(() => this.init({}, config), config.nextPageDelay); }, // 脚本完成提示 complete: function (config) { console.info('INFO: script completed after sending ' + config.totalRequestsSent + ' messages'); }, // 获取页面搜索结果数量 totalRows: function () { var search_results = document.getElementsByClassName('search-result'); if (search_results && search_results.length != 0) { return search_results.length; } else { return 0; } } } Linkedin.init({}, Linkedin.config);
控制台输出
INFO: script initialized on the page... debugger eval code:18:17 DEBUG: scrolling to bottom in 2000 ms debugger eval code:19:17 undefined DEBUG: scrolling to top in 2000 ms debugger eval code:26:17 DEBUG: inspecting elements in 2000 ms debugger eval code:32:17 DEBUG: total search results found on page are 0 debugger eval code:40:17 INFO: 1 message buttons found debugger eval code:61:21 DEBUG: starting to send messages in 3000 ms debugger eval code:70:21 remaining requests -1 debugger eval code:76:17 DEBUG: sending invite to 1 out of 1 debugger eval code:81:21 DEBUG: pasting Message in popup, in 3000 ms debugger eval code:86:21 start of pasteNote[object HTMLDivElement] debugger eval code:100:17 undefined debugger eval code:104:17 end of pasteNote Dear Valerio, I'm Valerio, thank you for sharing your professional network. debugger eval code:110:17 DEBUG: clicking send in popup, if present, in 3000 ms debugger eval code:112:17 DEBUG: clicking send button to close popup debugger eval code:122:21 INFO: invite sent to 1 out of 1 debugger eval code:134:17 DEBUG: all connections for the page done, going to next page in 3000 ms debugger eval code:142:21 INFO: no next page button found! debugger eval code:154:21 INFO: script completed after sending 1 messages debugger eval code:165:17
问题分析与修复
核心问题
从控制台输出start of pasteNote[object HTMLDivElement]可以明确:你选中的noteTextBox是一个外层Div容器,而非真正的输入元素。LinkedIn的消息输入区域是采用contenteditable="true"的Div实现的,这类元素没有value属性,直接赋值value不会生效。
修复后的pasteNote函数
pasteNote: function (data, config) { console.log("start of pasteNote"); // 定位真正的contenteditable输入框 const noteTextBox = document.querySelector("[contenteditable='true'].msg-form__contenteditable"); if (!noteTextBox) { console.error("ERROR: 未找到消息输入框"); return; } // 生成替换姓名后的消息 const message = config.note.replace("{{name}}", data.connectNames[data.pageButtonIndex]); // 清空原有内容并设置新消息 noteTextBox.textContent = ''; noteTextBox.textContent = message; // 触发输入和变更事件,确保LinkedIn前端框架识别内容变化 noteTextBox.dispatchEvent(new Event('input', { bubbles: true })); noteTextBox.dispatchEvent(new Event('change', { bubbles: true })); // 模拟焦点操作,更贴近真实用户行为 noteTextBox.focus(); console.log("end of pasteNote: " + noteTextBox.textContent); console.debug("DEBUG: clicking send in popup, if present, in " + config.actionDelay + " ms"); setTimeout(() => this.clickDone(data, config), config.actionDelay); }
额外优化建议
- 选择器稳定性:LinkedIn的类名可能会动态变更,建议结合
contenteditable属性和父容器层级来定位元素,避免依赖易变的类名前缀。 - 错误处理:添加元素存在性判断,防止脚本因找不到输入框而中断执行。
- 动态监听替代固定延迟:可以使用
MutationObserver监听弹出框的加载状态,待输入框完全渲染后再执行输入操作,比固定延迟更可靠,避免因页面加载慢导致的元素未就绪问题。
内容的提问来源于stack exchange,提问作者Valerio Viale
相关产品推荐
相关产品推荐

