// ==UserScript== // @name 豆包自定义短语助手 // @namespace http://tampermonkey.net/ // @version 1.0.3 // @description 在豆包网页添加复制短语功能,支持轮换短语和自定义管理,提供三种复制模式:只复制、复制追加、复制追加并发送 // @author anypahsy // @match https://www.doubao.com/chat/* // @icon https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // ==/UserScript== (function () { "use strict"; // 添加自定义样式 GM_addStyle( ` .doubao-copy-container { position: fixed; bottom: 80px; right: 1px; z-index: 9999; background: white; border: 1px solid #ddd; border-radius: 8px; padding: 15px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 180px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; } .doubao-copy-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .doubao-copy-title { font-size: 16px; font-weight: 600; color: #333; padding-right: 5px; } .doubao-manage-btn { background: #4a90e2; color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; } .doubao-manage-btn:hover { background: #3a7bc8; } .doubao-current-phrase { background: #f5f5f5; border-radius: 6px; padding: 12px; margin: 10px 0; /* min-height: 60px; */ word-wrap: break-word; line-height: 1.4; font-size: 14px; color: #333; /* 新增单行省略核心样式 */ white-space: nowrap; /* 强制文字不换行 */ overflow: hidden; /* 隐藏超出容器的内容 */ text-overflow: ellipsis;/* 超出部分显示省略号 */ } .doubao-controls { display: flex; gap: 8px; margin-top: 10px; align-items: center; } .doubao-copy-buttons { display: flex; flex-direction: column; gap: 8px; flex: 1; } .doubao-copy-buttons .doubao-btn { flex: 0; } .doubao-btn { flex: 1; padding: 8px 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; transition: background 0.2s; } .doubao-copy-btn { background: #07c160; color: white; } .doubao-copy-btn:hover { background: #06ae56; } .doubao-prev-btn { background: #f0f0f0; color: #333; } .doubao-prev-btn:hover { background: #e0e0e0; } .doubao-next-btn { background: #f0f0f0; color: #333; } .doubao-next-btn:hover { background: #e0e0e0; } .doubao-index-display { text-align: center; font-size: 12px; color: #666; margin-top: 8px; } /* 管理面板样式 */ .doubao-manage-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border: 1px solid #ddd; border-radius: 8px; padding: 20px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); z-index: 10000; min-width: 400px; max-width: 500px; } .doubao-manage-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; } .doubao-phrase-list { max-height: 300px; overflow-y: auto; margin: 15px 0; border: 1px solid #eee; border-radius: 4px; padding: 10px; } .doubao-phrase-item { display: flex; align-items: center; padding: 8px; border-bottom: 1px solid #f0f0f0; } .doubao-phrase-text { flex: 1; margin-right: 10px; word-wrap: break-word; } .doubao-remove-btn { background: #ff4757; color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer; font-size: 12px; } .doubao-add-phrase { display: flex; gap: 8px; margin-top: 15px; } .doubao-add-input { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 4px; } .doubao-add-btn { background: #07c160; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } .doubao-manage-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .doubao-close-btn { background: #f0f0f0; color: #333; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } .doubao-save-btn { background: #4a90e2; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; } ` ); // 默认短语列表 const DEFAULT_PHRASES = [ "提取文字", ]; // 从存储中获取短语列表,如果没有则使用默认值 function getPhrases() { const savedPhrases = GM_getValue("phrases"); return savedPhrases && savedPhrases.length > 0 ? savedPhrases : DEFAULT_PHRASES; } // 获取当前选中的索引 function getCurrentIndex() { return GM_getValue("currentIndex", 0); } /** * * */ // 只复制到剪贴板 function copyOnly(text) { navigator.clipboard .writeText(text) .then(() => { showMessage("已复制到剪贴板!"); }) .catch((err) => { console.error("复制失败:", err); showMessage("复制失败,请手动复制"); }); } // 复制并追加到输入框 function copyAndAppend(text) { navigator.clipboard .writeText(text) .then(() => { showMessage("已复制并追加到输入框!"); // 聚焦到输入框并追加文本 const input = document.querySelector( 'textarea[data-testid="chat_input_input"]' ); if (input) { const currentText = input.value; const newText = currentText ? currentText + "\n" + text : text; // 使用原生 setter 设置值 const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLTextAreaElement.prototype, "value" ).set; nativeInputValueSetter.call(input, newText); // 触发 input 事件 const inputEvent = new Event("input", { bubbles: true, cancelable: true, }); input.dispatchEvent(inputEvent); input.focus(); } }) .catch((err) => { console.error("复制失败:", err); showMessage("复制失败,请手动复制"); }); } // 复制、追加并发送 function copyAppendAndSend(text) { navigator.clipboard .writeText(text) .then(() => { showMessage("已复制、追加并发送!"); // 聚焦到输入框并追加文本 const input = document.querySelector( 'textarea[data-testid="chat_input_input"]' ); if (input) { const currentText = input.value; const newText = currentText ? currentText + "\n" + text : text; // 使用原生 setter 设置值 const nativeInputValueSetter = Object.getOwnPropertyDescriptor( window.HTMLTextAreaElement.prototype, "value" ).set; nativeInputValueSetter.call(input, newText); // 触发 input 事件 const inputEvent = new Event("input", { bubbles: true, cancelable: true, }); input.dispatchEvent(inputEvent); input.focus(); // 模拟回车键发送 setTimeout(() => { const enterEvent = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: "Enter", code: "Enter", keyCode: 13, which: 13, }); input.dispatchEvent(enterEvent); // 触发 keyup 事件 const keyupEvent = new KeyboardEvent("keyup", { bubbles: true, cancelable: true, key: "Enter", code: "Enter", keyCode: 13, which: 13, }); input.dispatchEvent(keyupEvent); }, 100); } }) .catch((err) => { console.error("复制失败:", err); showMessage("复制失败,请手动复制"); }); } // 显示临时消息 function showMessage(message) { const msg = document.createElement("div"); msg.textContent = message; msg.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0,0,0,0.8); color: white; padding: 10px 20px; border-radius: 4px; z-index: 10001; font-size: 14px; `; document.body.appendChild(msg); setTimeout(() => msg.remove(), 1500); } // 创建主界面 function createMainUI() { const container = document.createElement("div"); container.className = "doubao-copy-container"; // 获取当前数据和索引 const phrases = getPhrases(); let currentIndex = getCurrentIndex(); if (currentIndex >= phrases.length) { currentIndex = 0; GM_setValue("currentIndex", 0); } // 标题和按钮 const header = document.createElement("div"); header.className = "doubao-copy-header"; const title = document.createElement("div"); title.className = "doubao-copy-title"; title.textContent = "短语助手"; const manageBtn = document.createElement("button"); manageBtn.className = "doubao-manage-btn"; manageBtn.textContent = "管理短语"; manageBtn.onclick = showManagePanel; header.appendChild(title); header.appendChild(manageBtn); // 当前短语显示 const phraseDisplay = document.createElement("div"); phraseDisplay.className = "doubao-current-phrase"; phraseDisplay.textContent = phrases[currentIndex]; // 索引显示 const indexDisplay = document.createElement("div"); indexDisplay.className = "doubao-index-display"; indexDisplay.textContent = `${currentIndex + 1} / ${phrases.length}`; // 控制按钮 const controls = document.createElement("div"); controls.className = "doubao-controls"; const prevBtn = document.createElement("button"); prevBtn.className = "doubao-btn doubao-prev-btn"; prevBtn.innerHTML = "↑"; prevBtn.onclick = () => { currentIndex = (currentIndex - 1 + phrases.length) % phrases.length; GM_setValue("currentIndex", currentIndex); phraseDisplay.textContent = phrases[currentIndex]; indexDisplay.textContent = `${currentIndex + 1} / ${phrases.length}`; }; const copyButtonsContainer = document.createElement("div"); copyButtonsContainer.className = "doubao-copy-buttons"; const copyOnlyBtn = document.createElement("button"); copyOnlyBtn.className = "doubao-btn doubao-copy-btn"; copyOnlyBtn.innerHTML = "📋"; copyOnlyBtn.title = "只复制"; copyOnlyBtn.onclick = () => { copyOnly(phrases[currentIndex]); }; const copyAppendBtn = document.createElement("button"); copyAppendBtn.className = "doubao-btn doubao-copy-btn"; copyAppendBtn.innerHTML = "📋+"; copyAppendBtn.title = "复制并追加"; copyAppendBtn.onclick = () => { copyAndAppend(phrases[currentIndex]); }; const copySendBtn = document.createElement("button"); copySendBtn.className = "doubao-btn doubao-copy-btn"; copySendBtn.innerHTML = "📋↵"; copySendBtn.title = "复制、追加并发送"; copySendBtn.onclick = () => { copyAppendAndSend(phrases[currentIndex]); }; copyButtonsContainer.appendChild(copyOnlyBtn); copyButtonsContainer.appendChild(copyAppendBtn); copyButtonsContainer.appendChild(copySendBtn); const nextBtn = document.createElement("button"); nextBtn.className = "doubao-btn doubao-next-btn"; nextBtn.innerHTML = "↓"; nextBtn.onclick = () => { currentIndex = (currentIndex + 1) % phrases.length; GM_setValue("currentIndex", currentIndex); phraseDisplay.textContent = phrases[currentIndex]; indexDisplay.textContent = `${currentIndex + 1} / ${phrases.length}`; }; controls.appendChild(prevBtn); controls.appendChild(copyButtonsContainer); controls.appendChild(nextBtn); // 组装容器 container.appendChild(header); container.appendChild(phraseDisplay); container.appendChild(controls); container.appendChild(indexDisplay); return container; } // 创建管理面板 function showManagePanel() { // 创建遮罩层 const overlay = document.createElement("div"); overlay.className = "doubao-manage-overlay"; overlay.onclick = () => { document.body.removeChild(overlay); document.body.removeChild(panel); }; // 创建面板 const panel = document.createElement("div"); panel.className = "doubao-manage-panel"; const title = document.createElement("div"); title.style.cssText = "font-size: 18px; font-weight: 600; margin-bottom: 20px;"; title.textContent = "管理短语"; // 短语列表 const phraseList = document.createElement("div"); phraseList.className = "doubao-phrase-list"; let phrases = getPhrases(); function renderPhraseList() { phraseList.innerHTML = ""; phrases.forEach((phrase, index) => { const item = document.createElement("div"); item.className = "doubao-phrase-item"; const textSpan = document.createElement("span"); textSpan.className = "doubao-phrase-text"; textSpan.textContent = phrase; const removeBtn = document.createElement("button"); removeBtn.className = "doubao-remove-btn"; removeBtn.textContent = "删除"; removeBtn.onclick = () => { phrases.splice(index, 1); renderPhraseList(); }; item.appendChild(textSpan); item.appendChild(removeBtn); phraseList.appendChild(item); }); } renderPhraseList(); // 添加新短语 const addContainer = document.createElement("div"); addContainer.className = "doubao-add-phrase"; const addInput = document.createElement("input"); addInput.className = "doubao-add-input"; addInput.type = "text"; addInput.placeholder = "输入新的短语..."; const addBtn = document.createElement("button"); addBtn.className = "doubao-add-btn"; addBtn.textContent = "添加"; addBtn.onclick = () => { const newPhrase = addInput.value.trim(); if (newPhrase) { phrases.push(newPhrase); addInput.value = ""; renderPhraseList(); } }; addContainer.appendChild(addInput); addContainer.appendChild(addBtn); // 底部按钮 const footer = document.createElement("div"); footer.className = "doubao-manage-footer"; const closeBtn = document.createElement("button"); closeBtn.className = "doubao-close-btn"; closeBtn.textContent = "取消"; closeBtn.onclick = () => { document.body.removeChild(overlay); document.body.removeChild(panel); }; const saveBtn = document.createElement("button"); saveBtn.className = "doubao-save-btn"; saveBtn.textContent = "保存"; saveBtn.onclick = () => { GM_setValue("phrases", phrases); // 重置当前索引(如果超过范围) let currentIndex = getCurrentIndex(); if (currentIndex >= phrases.length) { GM_setValue("currentIndex", 0); } document.body.removeChild(overlay); document.body.removeChild(panel); // 重新加载主界面 const oldContainer = document.querySelector(".doubao-copy-container"); if (oldContainer) { oldContainer.remove(); } document.body.appendChild(createMainUI()); }; footer.appendChild(closeBtn); footer.appendChild(saveBtn); // 组装面板 panel.appendChild(title); panel.appendChild(phraseList); panel.appendChild(addContainer); panel.appendChild(footer); // 添加到页面 document.body.appendChild(overlay); document.body.appendChild(panel); } // 等待页面加载完成后添加UI function init() { // 移除可能存在的旧容器 const oldContainer = document.querySelector(".doubao-copy-container"); if (oldContainer) { oldContainer.remove(); } const container = createMainUI(); document.body.appendChild(container); } // 页面加载后初始化 window.addEventListener("load", init); // 如果页面已经加载完成,直接初始化 if (document.readyState === "complete") { init(); } })();