Slack Conversation Scraper & Custom Buttons

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

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