NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Proton Mail Signature Remover // @description Automatically removes email signature for free users of Proton Mail // @version 2.0.10 // @author guihkx // @match https://mail.protonmail.com/* // @match https://mail.proton.me/* // @run-at document-idle // @license MIT; https://opensource.org/licenses/MIT // @namespace https://github.com/guihkx // @icon https://mail.proton.me/assets/favicon.ico // @downloadURL https://raw.githubusercontent.com/guihkx/user-scripts/master/scripts/protonmail-signature-remover.user.js // @updateURL https://raw.githubusercontent.com/guihkx/user-scripts/master/scripts/protonmail-signature-remover.user.js // @homepageURL https://github.com/guihkx/user-scripts // @supportURL https://github.com/guihkx/user-scripts/issues // @noframes // ==/UserScript== /** * Changelog: * * v2.0.10 (2025-01-03): * - Replace the "onload()" event from the rich-text composer iframe by periodic setInterval() checks. * * v2.0.9 (2024-09-20): * - Fix compatibility with Proton Mail v5.0.47.8. * * v2.0.8 (2024-01-11): * - Avoid deleting the custom user signature. * * v2.0.7 (2024-01-09): * - Remove debug statement. * * v2.0.6 (2024-01-09): * - Update selector for the HTML composer. * - Use 'instanceof' where applicable. * * v2.0.5 (2022-10-27): * - Fix script icon. * * v2.0.4 (2022-10-27): * - Fix compatibility with Proton Mail v5.0.10. * - Prevent script from running twice due to nested frames. * * v2.0.3 (2022-05-25): * - Fixed signature in text-mode editor, again (why do they keep changing it?) * - Added support for their new domain (mail.proton.me) * - Updated @icon metadata * - Replaced 'ProtonMail' by 'Proton Mail' * * v2.0.2 (2022-04-19): * - Updated a bunch of class names, so now the script should work again. * - Fixed signature detection in the text-only editor. * * v2.0.1 (2022-02-08): * - Fixed signature not being removed in the HTML composer ("rich text editor"). * - Added logging for when a signature is found and removed. * * v2.0.0 (2021-10-03): * - Huge code rewrite * - Remove support for old.protonmail.com (Sorry!) * - Now removes the signature from both HTML and text-based emails (previously it could only do HTML-based ones) * - Updated script icon * * v1.0.0 (2020-04-28): * - First release */ ;(() => { const log = console.log.bind(console, '[Proton Mail Signature Remover]') log('Waiting for the composer container...') const id = setInterval(() => { const composerContainer = document.querySelector('div.app-root > div:not([class]):last-child') if (composerContainer === null) { return } clearInterval(id) log('Ready.') const composerContainerMonitor = new MutationObserver(observeComposerContainer) composerContainerMonitor.observe(composerContainer, { childList: true }) }, 500) function observeComposerContainer (mutations) { let composerFound = false let composerNode for (const mutation of mutations) { for (const addedNode of mutation.addedNodes) { if (!(addedNode instanceof HTMLElement)) { continue } if (!addedNode.classList.contains('composer')) { log('Unexpected non-composer node:', addedNode) continue } composerFound = true composerNode = addedNode break } if (composerFound) { break } } if (!composerFound) { return } // Loop until the email composer is fully rendered... const id = setInterval(() => { // Determine if this is a plain-text email composer. const textareaEmailBody = composerNode.querySelector('textarea[data-testid="editor-textarea"]') if (textareaEmailBody !== null) { clearInterval(id) // The is a pkain-text email composer. log('A plain-text email composer has been rendered.') // Although at this point the text-only email composer has been rendered, // the textarea that contains the email body will still be empty. // As I couldn't figure out a proper way to determine when the textarea has been filled, // this ugly, hacky approach keeps monitoring the textarea until its value is not empty anymore. removeTextSignature(textareaEmailBody) return } // Determine if this is rich-text email composer. const composerFrame = composerNode.querySelector('iframe[data-testid="rooster-iframe"]') if (composerFrame !== null) { // The is a rich-text email composer. clearInterval(id) const id2 = setInterval(() => { const roosterEditor = composerFrame.contentDocument.getElementById('rooster-editor') if (!roosterEditor) { log('Rich-text email composer not ready yet...') return } clearInterval(id2) log('A rich-text email composer has been rendered.') const signatureNode = roosterEditor.querySelector('div.protonmail_signature_block') if (signatureNode) { // Signature node has been already rendered, remove it directly. removeHTMLSignature(signatureNode) } else { // Signature node hasn't been rendered yet, so monitor it. setupHTMLComposerObserver(roosterEditor) } // roosterEditor is not ready, try again in 50ms... }, 50) } // The composer frame is not ready, try again in 50ms... }, 50) } function removeTextSignature (textareaEmailBody) { const textSignature = /\n\n^Sent with Proton Mail secure email\.$/m // Monitors the textarea containing the text-based email body. // Runs at every 100ms and stops once the textarea's value is not empty. const id = setInterval(() => { if (textareaEmailBody.value === '') { return } if (!textSignature.test(textareaEmailBody.value)) { clearInterval(id) return } clearInterval(id) textareaEmailBody.value = textareaEmailBody.value.replace(textSignature, '') log('Signature successfully removed from text-only email.') // Move the text cursor back to the beginning of the textarea. textareaEmailBody.setSelectionRange(0, 0) }, 100) } function setupHTMLComposerObserver (roosterEditor) { const htmlComposerMonitor = new MutationObserver(detectHTMLSignatureNode) htmlComposerMonitor.observe(roosterEditor, { childList: true }) } function detectHTMLSignatureNode (mutations, observer) { for (const mutation of mutations) { for (const addedNode of mutation.addedNodes) { if (!(addedNode instanceof addedNode.ownerDocument.defaultView.HTMLElement)) { continue } if (!addedNode.classList.contains('protonmail_signature_block')) { continue } // Found the signature node, stop observing and remove it. observer.disconnect() removeHTMLSignature(addedNode) return } } } function isBlankLine (node) { if (!node) { return false } if (!(node instanceof node.ownerDocument.defaultView.HTMLDivElement)) { return false } if (!node.firstElementChild) { return false } if (!(node.firstElementChild instanceof node.firstElementChild.ownerDocument.defaultView.HTMLBRElement)) { return false } return true } function removeHTMLSignature (signatureNode) { // Email composer structure with custom user signature: // // <div id="rooster-editor"> // <div><br></div> // <div><br></div> // <div class="protonmail_signature_block"> // <div class="protonmail_signature_block-user"> // <div>Thanks,</div> // <div>Guilherme<br></div> // </div> // <div><br></div> // <div class="protonmail_signature_block-proton"> // Sent with <a href="https://proton.me/">Proton Mail</a> secure email. // </div> // </div> // </div> // Email composer structure without custom user signature: // // <div id="rooster-editor"> // <div><br></div> // <div><br></div> // <div class="protonmail_signature_block"> // <div class="protonmail_signature_block-user protonmail_signature_block-empty"></div> // <div class="protonmail_signature_block-proton">Sent with <a href="https://proton.me/">Proton Mail</a> secure email.</div> // </div> // </div> const protonSignature = signatureNode.querySelector('.protonmail_signature_block-proton') if (!protonSignature) { log('BUG: Unable to find Proton\'s HTML signature to remove.') return } // Checks if there is a custom user signature. const userSignature = signatureNode.querySelector('.protonmail_signature_block-user:not(.protonmail_signature_block-empty)') if (!userSignature) { // Since there is no custom user signature, we can remove two blank lines added above the Proton signature. for (let i = 0; i < 2; i++) { if (isBlankLine(signatureNode.previousElementSibling)) { signatureNode.previousElementSibling.remove() } } } // Remove a blank line added below the Proton signature. if (isBlankLine(signatureNode.nextElementSibling)) { signatureNode.nextElementSibling.remove() } // Remove a blank line below the custom user signature. if (userSignature && isBlankLine(userSignature.nextElementSibling)) { userSignature.nextElementSibling.remove() } // Finally, remove the Proton signature node itself. protonSignature.remove() log('Signature successfully removed from HTML email.') } })()