diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 0929ba1..78d1fd5 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -147,6 +147,9 @@ "fq.bodyRegex": { "message": "Body Regexp match" }, + "fq.subjectBodyRegex": { + "message": "Subject || Body Regexp match" + }, "fq.copyAsRead": { "message": "Copy As Read" }, diff --git a/content/filtaquilla-util.js b/content/filtaquilla-util.js index ecc68e8..be1705a 100644 --- a/content/filtaquilla-util.js +++ b/content/filtaquilla-util.js @@ -533,7 +533,7 @@ FiltaQuilla.Util = { let versionComparator = Components.classes["@mozilla.org/xpcom/version-comparator;1"] .getService(Components.interfaces.nsIVersionComparator); return (versionComparator.compare(a, b) < 0); - } , + }, bodyMimeMatch: function(aMsgHdr, searchValue, searchFlags) { const util = FiltaQuilla.Util; @@ -649,6 +649,362 @@ FiltaQuilla.Util = { } // Util +FiltaQuilla.RegexUtil = { + RegexCallback: function RegexCallback(matchRegex) { + this.regex = matchRegex; + this.foundBody = false; + this.foundSubject = false; + this.processed = false; + this.msgURI = null; + this.body = null; + this.subject = null; + this.alert = false; + this.error = null; + + + this.found = function found(){ + return this.foundBody || this.foundSubject; + } + }, + /** + * Normalize match status. + * + * @param {*} aSearchOp + * @param {*} isMatched + * @returns + */ + IsMatched: function IsMatched(aSearchOp, isMatched){ + if(((aSearchOp == Ci.nsMsgSearchOp.Matches) && isMatched) || ((aSearchOp == Ci.nsMsgSearchOp.DoesntMatch) && !isMatched)){ + return true; + } + return false; + }, + + triggerAlertControl: function triggerAlertControl(msgBody, messageId) { + let msg = msgBody+"\n"+messageId; + var retVal = confirm(msg); + //var notification = new Notification("", {body: msg}); + //setTimeout(function() {notification.close()}, 1000); + + regexShowAlertSuccessValueEnabled = false; + /*if( retVal == true ) { + //do nothing + } else { + //#todo delete message + }*/ + }, + + bodyMimeMatch: function(aMsgHdr, searchValue, searchFlags, aSearchOp, regexShowAlertSuccessValueEnabled) { + const util = FiltaQuilla.Util; + + var mimeConvert = Cc["@mozilla.org/messenger/mimeconverter;1"].getService(Ci.nsIMimeConverter), + decodedMessageId = mimeConvert.decodeMimeHeader(aMsgHdr.messageId, null, false, true); + + debugger; + + //util.logDebug("aSearchOp: "+ (aSearchOp == Matches)+ "; searchValue: "+searchValue+"; searchFlags: "+searchFlags+(regexShowAlertSuccessValueEnabled ? "a" : "")); + + let callbackObject = new ReadBodyCallback(new RegExp(searchValue, searchFlags)); + callbackObject.alert = regexShowAlertSuccessValueEnabled; + + // message must be available offline! + let isMatched = false; + callbackObject.parseBody_new(aMsgHdr); + isMatched = callbackObject.found(); + //first try a new solution -> undecodeable garbage + + if(FiltaQuilla.RegexUtil.IsMatched(aSearchOp, isMatched)){ + //console.log("found_new: ", callbackObject); + if(callbackObject.alert){ + triggerAlertControl(callbackObject.body, decodedMessageId); + } + return true; + } + + util.logDebug("!found: "+ callbackObject); + + return false;//not matched or failed + }, + + subjectBodyMimeMatch: function(aMsgHdr, searchValue, searchFlags, aSearchOp, regexShowAlertSuccessValueEnabled) { + const util = FiltaQuilla.Util; + const regexUtil = FiltaQuilla.RegexUtil; + + debugger; + let isMatched = false; + + var mimeConvert = Cc["@mozilla.org/messenger/mimeconverter;1"].getService(Ci.nsIMimeConverter), + decodedMessageId = mimeConvert.decodeMimeHeader(aMsgHdr.messageId, null, false, true); + var subject = aMsgHdr.mime2DecodedSubject; + + let subjectRegexCallback = new regexUtil.RegexCallback(RegExp(searchValue, searchFlags)); + subjectRegexCallback.alert = regexShowAlertSuccessValueEnabled; + subjectRegexCallback.handleSubject(aMsgHdr); + + isMatched = subjectRegexCallback.foundSubject; + + // early exit (only when found, not when not found!) + if(regexUtil.IsMatched(aSearchOp, isMatched)){ + if(subjectRegexCallback.alert){ + triggerAlertControl(subject, decodedMessageId); + } + return true; + } + + let callbackObject = new ReadBodyCallback(""); + callbackObject.regex = subjectRegexCallback.regex; + callbackObject.alert = subjectRegexCallback.alert; + + callbackObject.parseBody_new(aMsgHdr); + isMatched = callbackObject.found(); + + if(regexUtil.IsMatched(aSearchOp, isMatched)){ + if(callbackObject.alert){ + triggerAlertControl(callbackObject.body, decodedMessageId); + } + return true; + } + + util.logDebug("!found: "+ JSON.stringify(callbackObject)); + + return false; + } +} + +FiltaQuilla.RegexUtil.RegexCallback.prototype.parseBody = function parseBody(aMsgHdr, aMimeMessage){ + const regexUtil = FiltaQuilla.RegexUtil; + const util = FiltaQuilla.Util; + debugger; + + if (aMimeMessage == null) { + this.processed = true; + this.error = "failure parsing during MsgHdrToMimeMessage"; + return false; + } + + try { + this.msgURI = aMsgHdr.folder.generateMessageURI(aMsgHdr.messageKey); + // util.logDebug("amimemsg: "+JSON.stringify(aMimeMessage)); + + let msgBody; + if (aMimeMessage.body) { + msgBody = aMimeMessage.body; // just in case this exists too + + } else if (aMimeMessage.parts && aMimeMessage.parts.length) { + let origPart = aMimeMessage.parts[0]; + + if (origPart.body) { + msgBody = origPart.body; + // util.logDebug("found body element in parts[0]: "+JSON.stringify(origPart)); + + } else if (origPart.parts) { + for (let p = 0; p]+(>|$)/g,""); + //util.logDebug("parsed: " + this.body); + } + + }else{ + this.body = /*await*/ decodeURIComponent(aMimeMessage.coerceBodyToPlaintext(aMsgHdr.folder)); + } + + //let messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger); + + if (this.regex.test(this.body)) { + this.foundBody = true; + } + + this.processed = true; + + } catch(ex) { + util.logException("ReadBodyCallback_callback failed",ex); + this.processed = true; + this.error = ex.message; + } + + return this.foundBody; +} + +FiltaQuilla.RegexUtil.RegexCallback.prototype.parseBody_new = function parseBody_new(aMsgHdr){ + const util = FiltaQuilla.Util; + const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm"); + + debugger; + + let folder = aMsgHdr.folder; + + // see https://searchfox.org/comm-central/source/mail/extensions/openpgp/content/modules/filters.jsm#276-296 + + var stream; + var messageSize; + var data; + try { + stream = folder.getMsgInputStream(aMsgHdr, {}); + messageSize = folder.hasMsgOffline(aMsgHdr.messageKey) ? aMsgHdr.offlineMessageSize : aMsgHdr.messageSize; + + data = NetUtil.readInputStreamToString(stream, messageSize); + } + catch (ex) { + util.logDebug(ex); + this.error = ex.message; + this.processed = true; + + return false; // If we don't know, better to return false. + + }finally{ + if(stream != null) {stream.close();} + } + + let origPart, + isTested = false; + let BodyParts = [], BodyType = []; // if we need multiple bodys (e.g. plain text + html mixed) + + if (MimeParser.extractMimeMsg) { + // Tb 91 + let mimeMsg = MimeParser.extractMimeMsg(data, { + includeAttachments: false // ,getMimePart: partName + }); + if (!mimeMsg.parts || !mimeMsg.parts.length) { + isTested=true; + + } else { + if (mimeMsg.body) { + BodyParts.push(mimeMsg.body); // just in case this exists too + BodyType.push(mimeMsg.contentType || "?") + + } else if (mimeMsg.parts && mimeMsg.parts.length) { + origPart = mimeMsg.parts[0]; + + if (origPart.body) { + // util.logDebug("found body element in parts[0]"); + BodyParts.push(origPart.body); + BodyType.push(origPart.contentType || "?") + } + + if (origPart.parts) { //continue search in case it have multiple bodys + for (let p = 0; p") ? "html" : "mime-raw?") + try{ + BodyParts.push(decodeURIComponent(body)); // try to decode that + BodyType.push(body.includes("") ? "html" : "mime-decoded?") + + // util.logDebug("body_new bodydec: "+msgBody); + }catch(ex){ + util.logException("Failed to decode.",e); + this.error = ex.message; + + //return false; + } + + util.logDebug("Thunderbird 78 gives the raw undecoded body. So this is what we parse and if it is encoded I give no guarantee for the regex to find ANYTHING.") + util.logDebug("Thunderbird 91 will have a new function MimeParser.extractMimeMsg() which will enable proper body parsing ") + + } + this.processed = isTested; + + if(isTested || !BodyParts.length || BodyParts.length == 0){ + return false; + } + + let r = false; + + //iterate over parts, if any + for (let i=0; i)/g, '') + .replace(/<[^>]+>/g, '') + .replace(/(\r\n|\r|\n){2,}/g,"") + .replace(/(\t){2,}/g,""); + } + + let found = this.regex.test(p); + if (found) { + let ct = p.contentType || "unknown"; + util.logDebug("Found pattern " + this.regex + " with content type: " + BodyType[i] + "; ct: "+ct); + r = true; + this.body = p; + break; + } + } + + this.processed = true; + if(r === true){ + this.foundBody = true; + + util.logDebug("body_new matched"); + let results = this.regex.exec(this.body); + if (results.length) { + util.logDebug("new_Matches to: "+ results[0]); + } + } + + return this.foundBody; +} + +FiltaQuilla.RegexUtil.RegexCallback.prototype.handleSubject = function handleSubject(aMsgHdr){ + debugger; + + this.subject = aMsgHdr.mime2DecodedSubject; + if (this.regex.test(this.subject)) { + this.foundSubject = true; + } + // util.logDebug("subjectCalback: "+JSON.stringify(this)); + + return this.foundSubject; +} + + +function ReadBodyCallback(matchRegex) { + const regexUtil = FiltaQuilla.RegexUtil; + + regexUtil.RegexCallback.apply(this, arguments); + this.callback = regexUtil.RegexCallback.prototype.parseBody; + this.found = function found(){ + return this.foundBody; + } +} + +ReadBodyCallback.prototype = FiltaQuilla.RegexUtil.RegexCallback.prototype; +ReadBodyCallback.prototype.constructor = ReadBodyCallback; + // some scoping for globals //(function fq_firstRun() { diff --git a/content/filtaquilla.js b/content/filtaquilla.js index 95d3506..5026792 100644 --- a/content/filtaquilla.js +++ b/content/filtaquilla.js @@ -44,7 +44,9 @@ const Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils, - util = FiltaQuilla.Util; + util = FiltaQuilla.Util, + regexUtil = FiltaQuilla.RegexUtil + ; // parameters for MoveLater @@ -81,10 +83,11 @@ Matches = nsMsgSearchOp.Matches, DoesntMatch = nsMsgSearchOp.DoesntMatch; - const REGEX_CASE_SENSITIVE_FLAG = "c"; //use this to override global case insensitive flag + const REGEX_CASE_SENSITIVE_FLAG = "c", //use this to override global case insensitive flag //(js doesnt have that, but tcl does) - // REGEX_SHOW_ALERT_SUCCESS_VALUE = "a" //use this to trigger dialog box with matched value - + REGEX_SHOW_ALERT_SUCCESS_VALUE = "a" //use this to trigger dialog box with matched value + ; + let maxThreadScan = 20; // the largest number of thread messages that we will examine // Enabling of filter actions. @@ -119,9 +122,14 @@ SearchBccEnabled = false, ThreadHeadTagEnabled = false, ThreadAnyTagEnabled = false, - FolderNameEnabled = false, + FolderNameEnabled = false; + + let BodyRegexEnabled = false, - SubjectBodyRegexEnabled = false; + SubjectBodyRegexEnabled = false, + regexShowAlertSuccessValueEnabled = false + ; + // [#5] AG new condition - attachment name regex let AttachmentRegexEnabled = false, moveLaterTimers = {}, // references to timers used in moveLater action @@ -1220,16 +1228,18 @@ } }, match: function subjectRegEx_match(aMsgHdr, aSearchValue, aSearchOp) { - var subject = aMsgHdr.mime2DecodedSubject; let searchValue, searchFlags; [searchValue, searchFlags] = _getRegEx(aSearchValue); - - switch (aSearchOp) - { + let subjectRegexCallback = new regexUtil.RegexCallback(RegExp(searchValue, searchFlags)); + subjectRegexCallback.handleSubject(aMsgHdr); + + util.logDebug(_getRegEx(aSearchValue)); + util.logDebug(RegExp(_getRegEx(aSearchValue))); + switch (aSearchOp){ case Matches: - return RegExp(searchValue, searchFlags).test(subject); + return subjectRegexCallback.foundSubject; case DoesntMatch: - return !RegExp(searchValue, searchFlags).test(subject); + return !subjectRegexCallback.foundSubject; } }, }; @@ -1384,7 +1394,7 @@ } } }; - + self.bodyRegex = { id: "filtaquilla@mesquilla.com#bodyRegex", @@ -1404,25 +1414,17 @@ return [Matches, DoesntMatch]; }, match: function bodyRegEx_match(aMsgHdr, aSearchValue, aSearchOp) { - /*** SEARCH INIT **/ - let searchValue, searchFlags, reg; - [searchValue, searchFlags] = _getRegEx(aSearchValue); - - // see https://searchfox.org/comm-central/source/mail/extensions/openpgp/content/modules/filters.jsm#276-296 - let result = FiltaQuilla.Util.bodyMimeMatch(aMsgHdr, searchValue, searchFlags); - - switch (aSearchOp) - { - case Matches: - return result;//return RegExp(searchValue, searchFlags).test(subject); - case DoesntMatch: - return !result;//return !RegExp(searchValue, searchFlags).test(subject); - } + let [searchValue, searchFlags] = _getRegEx(aSearchValue); + + let result = regexUtil.bodyMimeMatch(aMsgHdr, searchValue, searchFlags, aSearchOp, regexShowAlertSuccessValueEnabled); + regexShowAlertSuccessValueEnabled = false; + + return result; } }; - + self.subjectBodyRegex = - { + { id: "filtaquilla@mesquilla.com#subjectBodyRegex", name: util.getBundleString("fq.subjectBodyRegex"), getEnabled: function subjectBodyRegex_getEnabled(scope, op) { @@ -1437,41 +1439,15 @@ return [Matches, DoesntMatch]; }, match: function subjectBodyRegex_match(aMsgHdr, aSearchValue, aSearchOp) { - var subject = aMsgHdr.mime2DecodedSubject, - subResult = false; - let isMatched = false; - - /*** SEARCH INIT **/ - let searchValue, searchFlags, reg; - [searchValue, searchFlags] = _getRegEx(aSearchValue); - - subResult = RegExp(searchValue, searchFlags).test(subject); - + let [searchValue, searchFlags] = _getRegEx(aSearchValue); - var mimeConvert = Cc["@mozilla.org/messenger/mimeconverter;1"].getService(Ci.nsIMimeConverter), - decodedMessageId = mimeConvert.decodeMimeHeader(aMsgHdr.messageId, null, false, true); - var subject = aMsgHdr.mime2DecodedSubject; + let result = regexUtil.subjectBodyMimeMatch(aMsgHdr, searchValue, searchFlags, aSearchOp, regexShowAlertSuccessValueEnabled); + regexShowAlertSuccessValueEnabled = false; - // early exit (only when found, not when not found!) - if((aSearchOp == Matches) && subResult){ - return true; - } - - let bodyResult = FiltaQuilla.Util.bodyMimeMatch(aMsgHdr, searchValue, searchFlags); - - switch (aSearchOp) - { - case Matches: - return bodyResult || subResult; - case DoesntMatch: - return !(bodyResult || subResult); - } - - return false;//not matched or failed + return result; } }; - // search using arbitrary javascript self.javascript = { @@ -2103,10 +2079,19 @@ searchValue = aSearchValue.substring(1, lastSlashIndex); searchFlags = aSearchValue.substring(lastSlashIndex + 1); } - - if (regexpCaseInsensitiveEnabled && !searchFlags.includes("i") && !searchFlags.includes(REGEX_CASE_SENSITIVE_FLAG)){ + + if(regexpCaseInsensitiveEnabled && !searchFlags.includes("i") && !searchFlags.includes(REGEX_CASE_SENSITIVE_FLAG)){ searchFlags += "i"; } + + //alert is useful for rss advertisment monitoring - alert for interested ads + //todo# delete via cancel button in interactive alerts/ ok trigger url redirection + if(searchFlags.includes(REGEX_SHOW_ALERT_SUCCESS_VALUE)){ + searchFlags = searchFlags.replace(REGEX_SHOW_ALERT_SUCCESS_VALUE,""); + regexShowAlertSuccessValueEnabled = true; + }else{ + regexShowAlertSuccessValueEnabled = false; + } return [searchValue, searchFlags]; } diff --git a/content/fq_FilterEditor.js b/content/fq_FilterEditor.js index 0b175af..c944d09 100644 --- a/content/fq_FilterEditor.js +++ b/content/fq_FilterEditor.js @@ -683,6 +683,7 @@ case "filtaquilla@mesquilla.com#attachmentRegex": // fall-through case "filtaquilla@mesquilla.com#headerRegex" : // fall-through case "filtaquilla@mesquilla.com#bodyRegex": // fall-through + case "filtaquilla@mesquilla.com#subjectBodyRegex": // fall-through case "filtaquilla@mesquilla.com#searchBcc" : // fall-through case "filtaquilla@mesquilla.com#folderName" : isPatched = patchFiltaQuillaTextbox(es); @@ -722,6 +723,7 @@ case "filtaquilla@mesquilla.com#attachmentRegex": // fall-through case "filtaquilla@mesquilla.com#headerRegex" : // fall-through case "filtaquilla@mesquilla.com#bodyRegex" : // fall-through + case "filtaquilla@mesquilla.com#subjectBodyRegex" :// fall-through case "filtaquilla@mesquilla.com#searchBcc" : // fall-through case "filtaquilla@mesquilla.com#folderName" : if (es.firstChild) { @@ -804,6 +806,7 @@ "filtaquilla@mesquilla.com#attachmentRegex", "filtaquilla@mesquilla.com#headerRegex", "filtaquilla@mesquilla.com#bodyRegex", + "filtaquilla@mesquilla.com#subjectBodyRegex", "filtaquilla@mesquilla.com#searchBcc", "filtaquilla@mesquilla.com#folderName"].includes(attType)) { if (el.firstChild.classList.contains("fq-textbox")) @@ -831,6 +834,7 @@ case "filtaquilla@mesquilla.com#attachmentRegex": // fall-through case "filtaquilla@mesquilla.com#headerRegex": // fall-through case "filtaquilla@mesquilla.com#bodyRegex": // fall-through + case "filtaquilla@mesquilla.com#subjectBodyRegex" :// fall-through case "filtaquilla@mesquilla.com#searchBcc": // fall-through case "filtaquilla@mesquilla.com#folderName": isPatched = patchFiltaQuillaTextbox(searchValueItem); diff --git a/content/options.xhtml b/content/options.xhtml index 37ccba5..1a31245 100644 --- a/content/options.xhtml +++ b/content/options.xhtml @@ -348,11 +348,15 @@ tooltiptext="__MSG_helpFeatureTip__" /> - - - + + + + + + + do so here -
    -
  • added option to set regexp for case insensitive match by default
  • +
      +
    • added option to set regexp for case insensitive match by default
    • added body regexp match (WIP)
    • -
    • [issue 53] Support saving files with Mime encoded subjects and file names with cyrillic / other characters.
    • +
    • [issue 53] Support saving files with Mime encoded subjects and file names with cyrillic / other characters.
    • options now accessible via wrench symbol in Add-ons Manager
    • Check the new Issue Tracker for open problems and to add feature requests.
    • Added Russian locale by ashed @ github
    • -
    • [issue 111] Make FiltaQuilla compatible with Thunderbird 91 - WIP
    • +
    • [issue 111] Make FiltaQuilla compatible with Thunderbird 91 - WIP
    Support My Work