您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds button to copy links to threads on Google Chat and adds button to messages to quote reply
// ==UserScript== // @name Google Chat 引用訊息轉貼到其他視窗 // @version 0.0.13 // @namespace Ymx1ZTMyNmNj // @description Adds button to copy links to threads on Google Chat and adds button to messages to quote reply // @author upman // @match https://chat.google.com/* // @grant none // @run-at document-idle // @license https://github.com/upman/gchat-copy // ==/UserScript== ; (function () { function main() { var scrollContainer = document.querySelector('c-wiz[data-group-id][data-is-client-side] > div:nth-child(1)'); var copyButtonInsertedCount = 0; const itemContainer = document.querySelector('div[class="CfUpN"]'); if (itemContainer) { if (!itemContainer.querySelector('[data-tooltip*="ForwardMsg"')) { const forwardButton = document.createElement('div'); // Quote svg icon forwardButton.innerHTML = ` <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-top: 4px"> <path d="M9.25045 7.78369L7.83624 6.36948L3.59363 10.6121L7.83627 14.8547L9.25048 13.4405L6.42205 10.6121L9.25045 7.78369Z" fill="currentColor" /> <path d="M13.4932 13.4405L12.0789 14.8547L7.83627 10.6121L12.0789 6.36948L13.4931 7.78369L11.6463 9.63049L16.4063 9.63049C18.6155 9.63049 20.4063 11.4214 20.4063 13.6305L20.4063 17.6305L18.4063 17.6305L18.4063 13.6305C18.4063 12.5259 17.5109 11.6305 16.4063 11.6305L11.6831 11.6305L13.4932 13.4405Z" fill="currentColor" /> </svg>`; forwardButton.className = itemContainer.children[1].className; forwardButton.setAttribute('data-tooltip', 'ForwardMsg'); const forwardMessage = window.localStorage.getItem('forwardMessage'); if (forwardMessage) { itemContainer.prepend(forwardButton); } forwardButton.addEventListener('click', () => { let input = document.querySelector('div[contenteditable="true"]'); input.innerHTML = window.localStorage.getItem('forwardMessage'); input.scrollIntoView(); input.click(); placeCaretAtEnd(input); window.localStorage.removeItem('forwardMessage'); forwardButton.remove(); }); } } // Iterating on threads and in the case of DMs, the whole message history is one thread document.querySelectorAll("c-wiz[data-topic-id][data-local-topic-id]") .forEach( function(e,t,i){ // Iterating on each message in the thread e.querySelectorAll('div[jscontroller="VXdfxd"]').forEach( // Adding quote message buttons function(addreactionButton) { if ( addreactionButton.parentElement.parentElement.querySelector('[data-tooltip*="Quote Message"') || // Quote reply button already exists addreactionButton.parentElement.parentElement.children.length === 1 // Add reaction button next to existing emoji reactions to a message ) { return; } const container = document.createElement('div'); // Quote svg icon container.innerHTML = ` <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-top: 4px;color:#9aa0a6;"> <path d="M14.7495 7.78369L16.1638 6.36948L20.4064 10.6121L16.1637 14.8547L14.7495 13.4405L17.5779 10.6121L14.7495 7.78369Z" fill="currentColor" /> <path d="M10.5068 13.4405L11.9211 14.8547L16.1637 10.6121L11.9211 6.36948L10.5069 7.78369L12.3537 9.63049L7.59366 9.63049C5.38452 9.63049 3.59366 11.4214 3.59366 13.6305L3.59366 17.6305L5.59366 17.6305L5.59366 13.6305C5.59366 12.5259 6.48909 11.6305 7.59366 11.6305L12.3169 11.6305L10.5068 13.4405Z" fill="currentColor" /> </svg> `; container.className=addreactionButton.className; container.setAttribute('data-tooltip', 'Quote Message'); const quoteSVG = container.children[0] const svg = addreactionButton.querySelector('svg'); if (svg) { svg.classList.forEach(c => quoteSVG.classList.add(c)); } else { return; } var elRef = addreactionButton; // Find parent container of the message // These messages are then grouped together when they are from the recipient // and the upper most one has the name and time of the message while(!(elRef.className && elRef.className.includes('nF6pT')) && elRef.parentElement) { elRef = elRef.parentElement; } if (elRef.className.includes('nF6pT')) { var messageIndex, name, msgId, space, time, room, absTime, chat; let threadLink = ''; [...elRef.parentElement.children].forEach((messageEl, index) => { if (messageEl === elRef) { messageIndex = index; } }); addreactionButton.parentElement.parentElement.appendChild(container); container.addEventListener('click', () => { while (messageIndex >= 0) { if (elRef.parentElement.children[messageIndex].className.includes('AnmYv')) { const nameContainer = elRef.parentElement.children[messageIndex].querySelector('[data-hovercard-id], [data-member-id]'); const idContainer = elRef.parentElement.children[messageIndex].querySelector('[data-message-id]'); const timeContainer = elRef.parentElement.children[messageIndex].querySelector('[data-absolute-timestamp]'); const chatContainer = elRef.parentElement.children[messageIndex]; time = timeContainer.getAttribute('data-absolute-timestamp'); absTime = getAbsoluteTime(parseInt(time)); name = `${nameContainer.getAttribute('data-name')} [${absTime}]`; msgId = idContainer.getAttribute('data-message-id'); space = nameContainer.getAttribute('jsdata').match(/(space|dm)\/([^;$]+)/); chat = chatContainer.getAttribute('jsdata').match(/(?<=,)[^,]+(?=,(space|dm)\/)/)[0]; if (space) { if (space[1] == 'space') { room = 'room'; } if (space[1] == 'dm') { room = 'dm'; } threadLink = `https://chat.google.com/${room}/${space[2]}/${chat}/${msgId}`; } showPopup(); break; } messageIndex -= 1; } var messageContainer = addreactionButton.parentElement.parentElement.parentElement.parentElement.parentElement.children[0]; var quoteText = getQuoteText(messageContainer); let inputEl = e.querySelector('div[contenteditable="true"]'); // This fetches the input element in channels let dmInput = document.body.querySelectorAll('div[contenteditable="true"]'); // This fetches the input in DMs inputEl = inputEl ? inputEl : dmInput[dmInput.length - 1]; if (!inputEl) { return; } let message = `${makeInputText(name, quoteText, inputEl, messageContainer)}${threadLink}`; window.localStorage.setItem('forwardMessage', message); }); } } ); } ); if (copyButtonInsertedCount > 1) { scrollContainer.scrollTop += 36; } } function makeInputText(name, quoteText, inputEl, messageContainer) { var isDM = window.location.href.includes('/dm/'); var selection = window.getSelection().toString(); var text = getText(messageContainer); var oneLineQuote = ''; if (selection && text.includes(selection) && selection.trim()) { var regexp = new RegExp('(.*)' + selection + '(.*)'); var matches = regexp.exec(text); if (matches[1]) { // Has text before the match oneLineQuote += '... '; } oneLineQuote += selection.trim(); if (matches[2]) { // Has text after the match oneLineQuote += ' ...'; } } if(isDM) { return oneLineQuote ? '`' + oneLineQuote + '`\n' : ("```\n" + quoteText + "\n```\n" + inputEl.innerHTML) } else { return oneLineQuote ? '`' + name + ': ' + oneLineQuote + '`\n' : ("```\n" + name + ":\n" + quoteText + "\n```\n" + inputEl.innerHTML); } } function showPopup() { const popup = document.createElement('div'); popup.innerHTML = '在任意視窗按<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-top: 4px"><path d="M9.25045 7.78369L7.83624 6.36948L3.59363 10.6121L7.83627 14.8547L9.25048 13.4405L6.42205 10.6121L9.25045 7.78369Z" fill="currentColor"/><path d="M13.4932 13.4405L12.0789 14.8547L7.83627 10.6121L12.0789 6.36948L13.4931 7.78369L11.6463 9.63049L16.4063 9.63049C18.6155 9.63049 20.4063 11.4214 20.4063 13.6305L20.4063 17.6305L18.4063 17.6305L18.4063 13.6305C18.4063 12.5259 17.5109 11.6305 16.4063 11.6305L11.6831 11.6305L13.4932 13.4405Z" fill="currentColor"/></svg>轉貼訊息'; popup.style.position = 'fixed'; popup.style.top = '50%'; popup.style.left = '50%'; popup.style.transform = 'translate(-50%, -50%)'; popup.style.backgroundColor = '#333'; // 深色背景 popup.style.color = '#fff'; // 文字颜色 popup.style.padding = '10px 20px'; popup.style.border = '1px solid #555'; // 边框颜色 popup.style.boxShadow = '0px 0px 10px rgba(0, 0, 0, 0.5)'; popup.style.zIndex = '9999'; document.body.appendChild(popup); setTimeout(function () { document.body.removeChild(popup); }, 2000); } function getQuoteText(messageContainer) { var regularText = getText(messageContainer); var videoCall = messageContainer.querySelector('a[href*="https://meet.google.com/"]'); var image = messageContainer.querySelector('a img[alt]'); var text = regularText || (videoCall ? "🎥: " + videoCall.href: null) || (image ? "📷: " + image.alt: null) || '...'; return truncateQuoteText(text); } function truncateQuoteText(text) { let splitText = text.split('\n'); let quoteText = splitText.slice(0,500).join('\n') + (splitText.length > 500 ? '\n...' : ''); if (quoteText.length > 5000) { quoteText = quoteText.slice(0, 5000) + ' ...'; } return quoteText; } function getText(messageContainer) { const multilineMarkdownClass = 'FMTudf'; let textContent = ''; const childNodes = messageContainer.children[0].childNodes; for (var i = 0; i < childNodes.length; i += 1) { if (childNodes[i].nodeType === Node.TEXT_NODE) { textContent += childNodes[i].textContent; } else if (childNodes[i].className === 'jn351e') { continue; } else if (childNodes[i].className === multilineMarkdownClass) { textContent += '...\n'; } else if (childNodes[i].tagName === 'IMG') { // emojis textContent += childNodes[i].alt; } else { textContent += childNodes[i].innerHTML + '</br>'; } } if (messageContainer.children[1] && messageContainer.children[1].getAttribute('jsname') == 'bgckF') { textContent = `\n${messageContainer.children[1].textContent}`; } return textContent; } function placeCaretAtEnd(el) { el.focus(); if (typeof window.getSelection != "undefined" && typeof document.createRange != "undefined") { var range = document.createRange(); range.selectNodeContents(el); range.collapse(false); var sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); range.insertNode(document.createElement('br')); range.collapse(); } else if (typeof document.body.createTextRange != "undefined") { var textRange = document.body.createTextRange(); textRange.moveToElementText(el); textRange.collapse(false); textRange.select(); } } function debounce(fn, delay) { var timeout = null; return function() { if(timeout) { return; } else { timeout = setTimeout(function() { fn(); timeout = null; }, delay); } } } function getAbsoluteTime(d) { d = new Date(new Date(d).getTime() - new Date(d).getTimezoneOffset() * 60 * 1000); return d.toISOString().replace('T', ' ').substr(0, 19); } main(); var el = document.documentElement; el.addEventListener('DOMSubtreeModified', debounce(main, 2000)); })();