diff --git a/manifest.json b/manifest.json index 9843c62..7bc945c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ -{ +{ "manifest_version": 3, "name": "Mailsuite Signature Stripper", - "version": "0.2.0", + "version": "0.2.1", "description": "Removes the Mailsuite/Mailtrack promotional signature from your outgoing Gmail messages. Read tracking keeps working.", "permissions": ["storage"], "host_permissions": ["https://mail.google.com/*"], diff --git a/src/detect.js b/src/detect.js index 76f1112..f7577fe 100644 --- a/src/detect.js +++ b/src/detect.js @@ -60,6 +60,33 @@ const UNSUB = /unsubscribe|opt[-\s]?out|darse de baja|se désabonner|abmelden|annulla iscrizione|cancelar subscri|wypisz|取消訂閱|配信停止|수신 거부|सदस्यता समाप्त/i; + /* Mailsuite writes the campaign unsubscribe URL as a placeholder and only + substitutes it when a Campaign actually goes out. An ordinary one-to-one + signature carries the placeholder verbatim: + + Opt out + + which is a dead link, not a recipient's unsubscribe. */ + const CAMPAIGN_PLACEHOLDER = '##mt_unsubscribe_link##'; + + /** + * Whether the block carries a real, working unsubscribe link. + * + * Matching on the words alone was wrong and made the extension useless on the + * two most common signature variants. Both 17 and 18 render an + * "Unsubscribe" / "Opt out" link into every ordinary email, so a text match + * refused to delete almost every signature there is. What matters is whether + * the link actually goes anywhere. + */ + const hasLiveUnsubscribe = (block) => { + for (const a of block.querySelectorAll('a')) { + const href = a.getAttribute('href') || ''; + if (!href || href.includes(CAMPAIGN_PLACEHOLDER)) continue; + if (UNSUB.test(a.textContent || '') || /unsub|opt-?out/i.test(href)) return true; + } + return false; + }; + const PUNCTUATION = /[\s .,;:·|—–()[\]-]/g; /** A link out to Mailsuite's own site, which is what the signature always is. */ @@ -165,7 +192,7 @@ /** Shared teardown: keep the beacon, close the gap, drop the block. */ const removeBlock = (block, root, keepUnsubscribe) => { - if (keepUnsubscribe && UNSUB.test(block.textContent || '')) return false; + if (keepUnsubscribe && hasLiveUnsubscribe(block)) return false; /* Trim first. rescueBeacons parks the pixel immediately before the block, which would otherwise stop trimBefore seeing the blank line above it. */ trimBefore(block); @@ -207,6 +234,8 @@ SIGNATURE_MARKERS, PHRASES, UNSUB, + CAMPAIGN_PLACEHOLDER, + hasLiveUnsubscribe, markedBlocks, promoAnchor, looksLikeBeacon, diff --git a/test/detect.test.js b/test/detect.test.js index 2b03665..bd1d025 100644 --- a/test/detect.test.js +++ b/test/detect.test.js @@ -187,6 +187,60 @@ test('a beacon inside the real signature survives it', () => { assert.equal(root.querySelectorAll('img[src*="/trace/mail/"]').length, 1); }); +/* + * Captured verbatim from a live Gmail compose window, Mailsuite 12.87.0, + * signature version 18. Note the opt-out href is the unsubstituted campaign + * placeholder, so it is a dead link, not a recipient's unsubscribe. + */ +const CAPTURED_V18 = `
|
+ |
+
+ Sender notified with Mailtrack · Opt out + |
+ 08/08/26, 10:33:25 PM | ++ + | +