Slack Conversation Scraper & Custom Buttons

Combines conversation scraper and custom message buttons with smooth UI and arrow key toggle animation.

目前為 2024-12-21 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Slack Conversation Scraper & Custom Buttons
  3. // @version 2.6
  4. // @description Combines conversation scraper and custom message buttons with smooth UI and arrow key toggle animation.
  5. // @author Mahmudul Hasan Shawon
  6. // @icon https://www.slack.com/favicon.ico
  7. // @match https://app.slack.com/client/*
  8. // @grant none
  9. // @namespace https://greasyfork.org/users/1392874
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. const buttons = [
  16. { id: 'goodMorningButton', text: 'GM!', message: 'Good morning!' },
  17. { id: 'okButton', text: 'Ok', message: 'Ok' },
  18. { id: 'scrapeConversationButton', text: '📋 Scrape All', message: '' },
  19. { id: 'copyConversationButton', text: '✨ Guess Reply', message: '' },
  20. ];
  21.  
  22. function waitForSlackInterface() {
  23. const checkInterval = setInterval(() => {
  24. const textBox = document.querySelector('[data-message-input="true"] .ql-editor');
  25. if (textBox) {
  26. clearInterval(checkInterval);
  27. addControlBox();
  28. }
  29. }, 1000);
  30. }
  31.  
  32. function addControlBox() {
  33. const controlBox = document.createElement('div');
  34. Object.assign(controlBox.style, {
  35. position: 'fixed',
  36. bottom: '30px',
  37. right: '100px',
  38. width: '200px',
  39. padding: '15px',
  40. backgroundColor: '#ffffff',
  41. borderRadius: '12px',
  42. boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
  43. fontFamily: 'Arial, sans-serif',
  44. zIndex: '9999',
  45. transition: 'transform 0.3s ease-in-out, opacity 0.3s ease-in-out',
  46. transform: 'translateY(0)',
  47. opacity: '1',
  48. });
  49. controlBox.id = 'slackControlBox';
  50.  
  51. const toggleButton = document.createElement('div');
  52. Object.assign(toggleButton.style, {
  53. position: 'fixed',
  54. bottom: '44px',
  55. right: '120px',
  56. width: '36px',
  57. height: '36px',
  58. backgroundColor: '#C3FF93',
  59. borderRadius: '50%',
  60. display: 'flex',
  61. alignItems: 'center',
  62. justifyContent: 'center',
  63. cursor: 'pointer',
  64. zIndex: '10000',
  65. });
  66. toggleButton.innerHTML = `<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 12h18M12 3v18" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
  67. toggleButton.title = 'Hide/Show Control Box';
  68. toggleButton.addEventListener('click', () => toggleControlBox(controlBox));
  69.  
  70. document.body.appendChild(toggleButton);
  71.  
  72. const sideBySideContainer = document.createElement('div');
  73. Object.assign(sideBySideContainer.style, {
  74. display: 'flex',
  75. justifyContent: 'space-between',
  76. gap: '10px',
  77. marginBottom: '10px',
  78. });
  79.  
  80. const goodMorningButton = createButton(buttons[0]);
  81. const okButton = createButton(buttons[1]);
  82. goodMorningButton.style.flex = '1';
  83. okButton.style.flex = '1';
  84.  
  85. sideBySideContainer.appendChild(goodMorningButton);
  86. sideBySideContainer.appendChild(okButton);
  87. controlBox.appendChild(sideBySideContainer);
  88.  
  89. buttons.slice(2).forEach((btn) => {
  90. const button = createButton(btn);
  91. controlBox.appendChild(button);
  92. });
  93.  
  94. const inputField = createInputField();
  95. controlBox.appendChild(inputField);
  96.  
  97. document.body.appendChild(controlBox);
  98.  
  99. // Add keyboard toggle functionality
  100. document.addEventListener('keydown', (e) => handleKeyPress(e, controlBox));
  101. }
  102.  
  103. function toggleControlBox(controlBox) {
  104. const isHidden = controlBox.style.opacity === '0';
  105. controlBox.style.opacity = isHidden ? '1' : '0';
  106. controlBox.style.transform = isHidden ? 'translateY(0)' : 'translateY(20px)';
  107. }
  108.  
  109. function handleKeyPress(event, controlBox) {
  110. if (event.key === 'ArrowDown') {
  111. controlBox.style.opacity = '0';
  112. controlBox.style.transform = 'translateY(20px)';
  113. } else if (event.key === 'ArrowUp') {
  114. controlBox.style.opacity = '1';
  115. controlBox.style.transform = 'translateY(0)';
  116. }
  117. }
  118.  
  119. function createButton({ id, text, message }) {
  120. const button = document.createElement('button');
  121. Object.assign(button.style, {
  122. display: 'block',
  123. width: '100%',
  124. marginBottom: '10px',
  125. padding: '8px 0',
  126. backgroundColor: '#f3f3f3',
  127. color: '#333',
  128. fontSize: '14px',
  129. border: 'none',
  130. borderRadius: '8px',
  131. cursor: 'pointer',
  132. transition: 'background-color 0.2s ease-in-out',
  133. });
  134. button.id = id;
  135. button.textContent = text;
  136. button.addEventListener('mouseenter', () => {
  137. button.style.backgroundColor = '#e0e0e0';
  138. });
  139. button.addEventListener('mouseleave', () => {
  140. button.style.backgroundColor = '#f3f3f3';
  141. });
  142.  
  143. button.addEventListener('click', () => {
  144. if (id === 'copyConversationButton') copyMessages();
  145. else if (id === 'scrapeConversationButton') scrapeConversation();
  146. else sendMessage(message);
  147. });
  148. return button;
  149. }
  150.  
  151. function createInputField() {
  152. const input = document.createElement('input');
  153. Object.assign(input.style, {
  154. width: '65%',
  155. padding: '8px',
  156. fontSize: '12px',
  157. border: '1px solid #ccc',
  158. borderRadius: '8px',
  159. textAlign: 'center',
  160. boxSizing: 'border-box',
  161. });
  162. input.id = 'messageCountInputField';
  163. input.type = 'number';
  164. input.placeholder = 'Number of messages';
  165. return input;
  166. }
  167.  
  168. function scrapeConversation() {
  169. const messageBlocks = Array.from(document.querySelectorAll('.c-message_kit__background'));
  170. let conversation = '', lastSender = null;
  171.  
  172. messageBlocks.forEach((block) => {
  173. const sender = block.querySelector('.c-message__sender_button')?.textContent.trim() || lastSender;
  174. if (sender) lastSender = sender;
  175.  
  176. const messageText = Array.from(block.querySelectorAll('.p-rich_text_section'))
  177. .map((el) => el.textContent.trim())
  178. .join(' ').trim();
  179.  
  180. if (messageText) {
  181. conversation += `${lastSender}: ${messageText}\n\n`;
  182. }
  183. });
  184.  
  185. if (conversation.trim()) {
  186. copyToClipboard(conversation, 'All conversations copied!');
  187. } else {
  188. showPopUp('No conversation found.');
  189. }
  190. }
  191.  
  192. function copyMessages() {
  193. const numberOfMessages = document.getElementById('messageCountInputField').value || 2;
  194. const messages = Array.from(document.querySelectorAll('.c-message_kit__blocks'))
  195. .slice(-numberOfMessages)
  196. .map((container) => {
  197. const sender = container.closest('.c-message_kit__background')
  198. ?.querySelector('.c-message__sender_button')?.textContent.trim();
  199. const messageText = container.querySelector('.p-rich_text_section')?.textContent.trim();
  200. return sender && messageText ? `\n${sender}: ${messageText}` : null;
  201. })
  202. .filter(Boolean);
  203.  
  204. if (!messages.length) {
  205. showPopUp(`Unable to copy ${numberOfMessages} messages.`);
  206. return;
  207. }
  208.  
  209. const formatted = `${messages.join('\n')}\n\nGuess reply:`;
  210. copyToClipboard(formatted, `Last ${numberOfMessages} messages copied!`);
  211. }
  212.  
  213. function copyToClipboard(text, message) {
  214. navigator.clipboard.writeText(text)
  215. .then(() => showPopUp(message))
  216. .catch(() => showPopUp('Failed to copy.'));
  217. }
  218.  
  219. function sendMessage(message) {
  220. const textBox = document.querySelector('[data-message-input="true"] .ql-editor');
  221. const sendButton = document.querySelector('[data-qa="texty_send_button"]');
  222.  
  223. if (textBox) {
  224. textBox.focus();
  225. document.execCommand('insertText', false, message);
  226. setTimeout(() => sendButton?.click(), 500);
  227. } else {
  228. showPopUp('Message box not found.');
  229. }
  230. }
  231.  
  232. function showPopUp(message) {
  233. const popUp = document.createElement('div');
  234. Object.assign(popUp.style, {
  235. position: 'fixed',
  236. bottom: '245px',
  237. right: '100px',
  238. backgroundColor: '#A294F9',
  239. color: '#FFFFFF',
  240. padding: '10px 15px',
  241. borderRadius: '16px 16px 0px 16px',
  242. fontSize: '14px',
  243. boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.3)',
  244. animation: 'fadeInOut 3s ease-in-out',
  245. zIndex: '9999',
  246. });
  247. popUp.textContent = message;
  248. document.body.appendChild(popUp);
  249.  
  250. setTimeout(() => popUp.remove(), 3000);
  251. }
  252.  
  253. const styleSheet = document.createElement('style');
  254. styleSheet.type = 'text/css';
  255. styleSheet.innerText = `
  256. @keyframes fadeInOut {
  257. 0% {
  258. opacity: 0;
  259. transform: translateY(20px);
  260. }
  261. 10% {
  262. opacity: 1;
  263. transform: translateY(0);
  264. }
  265. 90% {
  266. opacity: 1;
  267. transform: translateY(0);
  268. }
  269. 100% {
  270. opacity: 0;
  271. transform: translateY(20px);
  272. }
  273. }
  274. `;
  275. document.head.appendChild(styleSheet);
  276.  
  277. waitForSlackInterface();
  278. })();