johnflux

Gmail script to warn when emailing certain domains

Sep 23rd, 2026
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 5.05 KB | Software | 0 0
  1. // ==UserScript==
  2. // @name         Gmail Protected Recipients
  3. // @namespace    local.gmail.protected-recipients
  4. // @version      1.3
  5. // @description  Warn before emailing protected domains in Gmail
  6. // @match        https://mail.google.com/*
  7. // @run-at       document-start
  8. // ==/UserScript==
  9.  
  10. (() => {
  11.   'use strict';
  12.  
  13.   const PROTECTED_DOMAINS = [
  14.     'example.com'
  15.   ];
  16.  
  17.   const LOG_PREFIX = '[Gmail Protected Recipients]';
  18.  
  19.   function log(...args) {
  20.     console.log(LOG_PREFIX, ...args);
  21.   }
  22.  
  23.   function isProtectedEmail(email) {
  24.     const normalized = email.trim().toLowerCase();
  25.  
  26.     return PROTECTED_DOMAINS.some(domain =>
  27.       normalized.endsWith(`@${domain.toLowerCase()}`)
  28.     );
  29.   }
  30.  
  31.   function extractEmails(root) {
  32.     const found = new Set();
  33.  
  34.     if (!root) return [];
  35.  
  36.     const emailRegex =
  37.       /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
  38.  
  39.     root.querySelectorAll('*').forEach(el => {
  40.       const attrs = [
  41.         'email',
  42.         'data-hovercard-id',
  43.         'data-email',
  44.         'aria-label',
  45.         'title',
  46.         'name',
  47.       ];
  48.  
  49.       for (const attr of attrs) {
  50.         const value = el.getAttribute?.(attr);
  51.  
  52.         if (!value) continue;
  53.  
  54.         value.match(emailRegex)?.forEach(email => {
  55.           found.add(email.toLowerCase());
  56.         });
  57.       }
  58.  
  59.       if (
  60.         el instanceof HTMLInputElement ||
  61.         el instanceof HTMLTextAreaElement
  62.       ) {
  63.         el.value.match(emailRegex)?.forEach(email => {
  64.           found.add(email.toLowerCase());
  65.         });
  66.       }
  67.     });
  68.  
  69.     return [...found];
  70.   }
  71.  
  72.   function findComposeArea(sendButton) {
  73.     let node = sendButton;
  74.  
  75.     while (node && node !== document.body) {
  76.       const emails = extractEmails(node);
  77.  
  78.       log(
  79.         'Checking ancestor:',
  80.         node,
  81.         'emails:',
  82.         emails
  83.       );
  84.  
  85.       if (emails.length > 0) {
  86.         log('Using compose area:', node);
  87.         return node;
  88.       }
  89.  
  90.       node = node.parentElement;
  91.     }
  92.  
  93.     log('Could not find recipient-containing ancestor');
  94.     return null;
  95.   }
  96.  
  97.   function getProtectedRecipients(root) {
  98.     const emails = extractEmails(root);
  99.  
  100.     log('Detected emails:', emails);
  101.  
  102.     const protectedRecipients =
  103.       emails.filter(isProtectedEmail);
  104.  
  105.     log(
  106.       'Protected recipients:',
  107.       protectedRecipients
  108.     );
  109.  
  110.     return protectedRecipients;
  111.   }
  112.  
  113.   function confirmProtectedSend(root) {
  114.     const recipients =
  115.       getProtectedRecipients(root);
  116.  
  117.     if (!recipients.length) {
  118.       return true;
  119.     }
  120.  
  121.     return window.confirm(
  122.       '⚠️ WARNING: PROTECTED RECIPIENT ⚠️\n\n' +
  123.       'You are about to send email to:\n\n' +
  124.       recipients.map(x => `    ${x}`).join('\n') +
  125.       '\n\nAre you ABSOLUTELY SURE?'
  126.     );
  127.   }
  128.  
  129.   function getSendButton(element) {
  130.     const button = element.closest(
  131.       '[role="button"], button, [data-tooltip], [aria-label]'
  132.     );
  133.  
  134.     if (!button) return null;
  135.  
  136.     const labels = [
  137.       button.textContent,
  138.       button.getAttribute('data-tooltip'),
  139.       button.getAttribute('aria-label'),
  140.       button.getAttribute('title'),
  141.     ].filter(Boolean);
  142.  
  143.     const isSend = labels.some(value =>
  144.       /^\s*send(?:\s|$)/i.test(value)
  145.     );
  146.  
  147.     return isSend ? button : null;
  148.   }
  149.  
  150.   document.addEventListener(
  151.     'click',
  152.     event => {
  153.       if (!(event.target instanceof Element)) return;
  154.  
  155.       const sendButton =
  156.         getSendButton(event.target);
  157.  
  158.       if (!sendButton) return;
  159.  
  160.       log('Send clicked:', sendButton);
  161.  
  162.       const composeArea =
  163.         findComposeArea(sendButton);
  164.  
  165.       if (!composeArea) {
  166.         log(
  167.           'No compose area found — allowing send'
  168.         );
  169.         return;
  170.       }
  171.  
  172.       if (!confirmProtectedSend(composeArea)) {
  173.         log('Send cancelled');
  174.  
  175.         event.preventDefault();
  176.         event.stopPropagation();
  177.         event.stopImmediatePropagation();
  178.       }
  179.     },
  180.     true
  181.   );
  182.  
  183.   document.addEventListener(
  184.     'keydown',
  185.     event => {
  186.       if (
  187.         event.key !== 'Enter' ||
  188.         (!event.metaKey && !event.ctrlKey)
  189.       ) {
  190.         return;
  191.       }
  192.  
  193.       if (!(event.target instanceof Element)) {
  194.         return;
  195.       }
  196.  
  197.       log('Cmd/Ctrl+Enter detected');
  198.  
  199.       // For keyboard sends, start walking upward
  200.       // from the currently focused compose element.
  201.       let node = event.target;
  202.       let composeArea = null;
  203.  
  204.       while (node && node !== document.body) {
  205.         const emails = extractEmails(node);
  206.  
  207.         if (emails.length > 0) {
  208.           composeArea = node;
  209.           break;
  210.         }
  211.  
  212.         node = node.parentElement;
  213.       }
  214.  
  215.       if (!composeArea) {
  216.         log(
  217.           'Could not find compose area for keyboard send'
  218.         );
  219.         return;
  220.       }
  221.  
  222.       if (!confirmProtectedSend(composeArea)) {
  223.         log('Keyboard send cancelled');
  224.  
  225.         event.preventDefault();
  226.         event.stopPropagation();
  227.         event.stopImmediatePropagation();
  228.       }
  229.     },
  230.     true
  231.   );
  232.  
  233.   log('Userscript loaded');
  234. })();
Advertisement
Add Comment
Please, Sign In to add comment