├── .gitignore ├── .npmignore ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── local_types └── xmldom │ └── index.d.ts ├── package.json ├── src ├── Constants.ts ├── HtmlQuotations.ts ├── Quotations.ts ├── Regexp.ts ├── Talon.ts └── Utils.ts ├── tests ├── .eslintrc ├── fixtures │ ├── front │ │ ├── crashers │ │ │ ├── crasher1.html │ │ │ ├── crasher2.html │ │ │ ├── crasher3.html │ │ │ ├── crasher4.html │ │ │ └── crasher5.html │ │ ├── email_error_line_break.html │ │ ├── email_error_quote.html │ │ ├── email_too_long.html │ │ ├── email_with_conditional_comments.html │ │ ├── email_with_from.html │ │ ├── email_with_no_doc.html │ │ ├── email_with_quote.html │ │ ├── email_with_signature.html │ │ └── email_with_table.html │ ├── nylas │ │ ├── email_1.html │ │ ├── email_10.html │ │ ├── email_10_stripped.html │ │ ├── email_11.html │ │ ├── email_11_stripped.html │ │ ├── email_12.html │ │ ├── email_12_stripped.html │ │ ├── email_13.html │ │ ├── email_13_stripped.html │ │ ├── email_14.html │ │ ├── email_14_stripped.html │ │ ├── email_15.html │ │ ├── email_15_stripped.html │ │ ├── email_16.html │ │ ├── email_16_stripped.html │ │ ├── email_17.html │ │ ├── email_17_stripped.html │ │ ├── email_18.html │ │ ├── email_18_stripped.html │ │ ├── email_1_stripped.html │ │ ├── email_2.html │ │ ├── email_2_stripped.html │ │ ├── email_3.html │ │ ├── email_3_stripped.html │ │ ├── email_4.html │ │ ├── email_4_stripped.html │ │ ├── email_5.html │ │ ├── email_5_stripped.html │ │ ├── email_6.html │ │ ├── email_6_stripped.html │ │ ├── email_7.html │ │ ├── email_7_stripped.html │ │ ├── email_8.html │ │ ├── email_8_stripped.html │ │ ├── email_9.html │ │ └── email_9_stripped.html │ └── talon │ │ ├── OLK_SRC_BODY_SECTION.html │ │ ├── html_replies │ │ ├── gmail.html │ │ ├── hotmail.html │ │ ├── mail_ru.html │ │ ├── ms_outlook_2003.html │ │ ├── ms_outlook_2007.html │ │ ├── ms_outlook_2010.html │ │ ├── thunderbird.html │ │ ├── windows_mail.html │ │ └── yandex_ru.html │ │ ├── reply-quotations-share-block.eml │ │ ├── reply-separated-by-hr.html │ │ └── standard_replies │ │ ├── android.eml │ │ ├── aol.eml │ │ ├── apple_mail.eml │ │ ├── apple_mail_2.eml │ │ ├── comcast.eml │ │ ├── gmail.eml │ │ ├── hotmail.eml │ │ ├── iphone.eml │ │ ├── iphone_reply_text │ │ ├── outlook.eml │ │ ├── sparrow.eml │ │ ├── sparrow_reply_text │ │ ├── thunderbird.eml │ │ └── yahoo.eml ├── html_quotations_tests.js ├── quotations_tests.js ├── regexp.js └── utils.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs. 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data. 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover. 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul. 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files). 18 | .grunt 19 | 20 | # node-waf configuration. 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html). 24 | build/Release 25 | 26 | # Dependency directories. 27 | node_modules 28 | jspm_packages 29 | 30 | # Optional npm cache directory. 31 | .npm 32 | 33 | # Optional REPL history. 34 | .node_repl_history 35 | 36 | # Typescript build directory. 37 | bin/ 38 | 39 | # Typescript definitions. 40 | typings/ 41 | 42 | # Vs Code. 43 | .vscode 44 | .vscode/* 45 | .DS_Store -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | src/ 3 | tests/ 4 | typings/ 5 | 6 | README.md 7 | 8 | .travis.yml 9 | tsconfig.json 10 | yarn.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '11' 4 | cache: 5 | directories: 6 | - node_modules 7 | deploy: 8 | skip_cleanup: true 9 | provider: npm 10 | email: quentin@frontapp.com 11 | api_key: 12 | secure: D7MoJCnUUMxMAZnMP2ageDITqDmd36AsAWpUpH8gPJzzUu9hAp0GLNI6veWn5o6PoEGRAsZC/AcXA2XVex1sgWkV0QatfkNPapTkT4k5RrKD2yeA+MRc/nxy/pi7V5ai42gVbzE6r7m456TnIi2boRzWV0jY4S4ReN/Uvqq2voXbtHNlkAqlr2B30dezZZWS2sq/vty9K6XqX0ATrO60vzOXo7vtMzE61pf7BnuSof5UW8XjUQY1+kWokETHhuFvss9jgYgHB4Q2TtSBOMKWw7JpBgR3lyf1/4WZwSSCcWOOGfLqaPjae7nk8/ozysK6wkJT6x9StDzIaqpPicNhDeAuMgKB51JJvu++vINTVfhNCjvT0oAkxC2jbh8YJSqw1kwghkw1/X0j+T94yGZFxQHiOToq7tCch7ic7p0Un+U94DVm+C2V7Ppa/tDLfQaOgFYxDkRqjoGt/2xQyjj4iB7awudtRT78GAvNo9ZufRozCI+SKZycr+nt2YumsHrMWkaLcghp2bBch4HxOvRsycfW3wc4VsmpXvF90//pVAVCzEq1YmIiR8aLCJ5NHmpQL+8schszWYDLc+M/0zhInA9BP2LzK4Aq28ncrwL6OGirBoa6YeCOxtGbb/DUWLObu2USOpiAj8uzzY9pImX1YUcba3vzKoylBXpY54EjFjo= 13 | on: 14 | repo: quentez/talonjs 15 | tags: true 16 | branch: master 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.DS_Store": true, 6 | "bin/": true, 7 | "node_modules/": true 8 | }, 9 | "editor.tabSize": 2 10 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Quentin Calvez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TalonJS 2 | 3 | [![Build Status](https://travis-ci.org/quentez/talonjs.svg?branch=master)](https://travis-ci.org/quentez/talonjs) 4 | -------------------------------------------------------------------------------- /local_types/xmldom/index.d.ts: -------------------------------------------------------------------------------- 1 | // Based on @types/xmldom 2 | 3 | declare module "xmldom" { 4 | namespace xmldom { 5 | var DOMParser: DOMParserStatic; 6 | var XMLSerializer: XMLSerializerStatic; 7 | var DOMImplementation: DOMImplementationStatic; 8 | 9 | interface DOMImplementationStatic { 10 | new(): DOMImplementation; 11 | } 12 | 13 | interface DOMParserStatic { 14 | new (): DOMParser; 15 | new (options: Options): DOMParser; 16 | } 17 | 18 | interface XMLSerializerStatic { 19 | new (): XMLSerializer; 20 | } 21 | 22 | interface DOMParser { 23 | parseFromString(xmlsource: string, mimeType?: string): Document; 24 | } 25 | 26 | interface XMLSerializer { 27 | serializeToString(node: Node, isHtml?: boolean): string; 28 | } 29 | 30 | interface Options { 31 | locator?: any; 32 | errorHandler?: ErrorHandlerFunction | ErrorHandlerObject; 33 | } 34 | 35 | interface ErrorHandlerFunction { 36 | (level: string, msg: any): any; 37 | } 38 | 39 | interface ErrorHandlerObject { 40 | warning?: (msg: any) => any; 41 | error?: (msg: any) => any; 42 | fatalError?: (msg: any) => any; 43 | } 44 | } 45 | 46 | export = xmldom; 47 | } 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "talonjs", 3 | "version": "1.0.28", 4 | "description": "Javascript port of the Mailgun/Talon library.", 5 | "main": "bin/Talon.js", 6 | "types": "bin/Talon.d.ts", 7 | "repository": "https://github.com/quentez/talonjs", 8 | "scripts": { 9 | "prepublish": "node node_modules/typescript/bin/tsc -d", 10 | "pretest": "node node_modules/typescript/bin/tsc", 11 | "test": "node node_modules/mocha/bin/_mocha -t 20000 tests/*.js" 12 | }, 13 | "author": "FrontApp Inc.", 14 | "license": "MIT", 15 | "resolutions": { 16 | "lodash": "4.17.15", 17 | "lodash.merge": "4.6.2" 18 | }, 19 | "devDependencies": { 20 | "@types/cheerio": "0.22.13", 21 | "chai": "4.2.0", 22 | "front-async": "0.9.2-1", 23 | "mailparser": "0.6.1", 24 | "mocha": "6.2.0", 25 | "retyped-xpath-tsd-ambient": "0.0.7-0", 26 | "typescript": "3.6.2" 27 | }, 28 | "dependencies": { 29 | "cheerio": "0.22.0", 30 | "xmldom": "0.1.27", 31 | "xpath": "0.0.27" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Constants.ts: -------------------------------------------------------------------------------- 1 | export type ContentType = "text/plain" | "text/html"; 2 | 3 | export const ContentTypeTextPlain: ContentType = "text/plain"; 4 | export const ContentTypeTextHtml: ContentType = "text/html"; 5 | 6 | export const SplitterMaxLines = 4; 7 | export const DefaultMaxLinesCount = 4000; 8 | export const DefaultNodeLimit = 4000; 9 | 10 | export const QuoteIds = ["OLK_SRC_BODY_SECTION"]; 11 | 12 | export const CheckpointPrefix = "#!%!"; 13 | export const CheckpointSuffix = "!%!#"; 14 | 15 | export const BlockTags = ["blockquote", "div", "p", "ul", "li", "h1", "h2", "h3"]; 16 | export const HardbreakTags = ["br", "hr", "tr"]; 17 | 18 | export enum NodeTypes { 19 | ELEMENT_NODE = 1, 20 | TEXT_NODE = 3, 21 | DOCUMENT_NODE = 9 22 | } 23 | -------------------------------------------------------------------------------- /src/HtmlQuotations.ts: -------------------------------------------------------------------------------- 1 | import * as XPath from 'xpath'; 2 | 3 | import { CheckpointPrefix, CheckpointSuffix, QuoteIds, NodeTypes, DefaultNodeLimit } from './Constants'; 4 | import { isStartOfForwardedMessage, matchStart } from './Utils'; 5 | 6 | 7 | export interface AddCheckpointOptions { 8 | count?: number; 9 | level?: number; 10 | nodeLimit?: number; 11 | } 12 | 13 | /** 14 | * Add checkpoints to an HTML element and all its descendants. 15 | * 16 | * @param {Document} document - The DOM document. 17 | * @param {Node} element - The HTML element to edit. 18 | * @param {AddCheckpointOptions} options - Optional count, level, and nodeLimit arguments. 19 | * @return {number} The total number of checkpoints in the document. 20 | */ 21 | export function addCheckpoint(document: Document, element: Node, options?: AddCheckpointOptions): number { 22 | let { count, level, nodeLimit } = { 23 | count: 0, 24 | level: 0, 25 | nodeLimit: DefaultNodeLimit, 26 | ...options 27 | } 28 | 29 | // Update the text for this element. 30 | if (element.firstChild && element.firstChild.nodeType === NodeTypes.TEXT_NODE) 31 | element.replaceChild(document.createTextNode(`${element.firstChild.nodeValue || ""}${CheckpointPrefix}${count}${CheckpointSuffix}`), element.firstChild); 32 | else 33 | element.nodeValue = `${CheckpointPrefix}${count}${CheckpointSuffix}`; 34 | 35 | count++; 36 | 37 | // Process recursively. 38 | if (count < nodeLimit) 39 | for (let index = 0; index < element.childNodes.length && count < nodeLimit; index++) { 40 | const node = element.childNodes.item(index); 41 | if (node.nodeType !== NodeTypes.ELEMENT_NODE) 42 | continue; 43 | 44 | count = addCheckpoint(document, node, {nodeLimit, count, level: level + 1}); 45 | } 46 | 47 | // Also update the following text node, if any. 48 | if (element.nextSibling && element.nextSibling.nodeType === NodeTypes.TEXT_NODE) 49 | element.parentNode.replaceChild(document.createTextNode(`${element.nextSibling.nodeValue || ""}${CheckpointPrefix}${count}${CheckpointSuffix}`), element.nextSibling); 50 | else if (element.parentNode) 51 | element.parentNode.appendChild(document.createTextNode(`${CheckpointPrefix}${count}${CheckpointSuffix}`)); 52 | 53 | count++; 54 | 55 | // Return the updated count. 56 | return count; 57 | }; 58 | 59 | export interface DeleteQuotationOptions { 60 | count?: number; 61 | level?: number; 62 | nodeLimit?: number; 63 | shouldPreserveTable?: boolean; 64 | } 65 | 66 | /** 67 | * Remove tags with quotation checkpoints from the provided HTML element and all its descendants. 68 | * 69 | * @param {Document} document - The DOM document. 70 | * @param {Node} element - The HTML element to edit. 71 | * @param {boolean[]} quotationCheckpoints - The checkpoints for the tags to remove. 72 | * @param {AddCheckpointOptions} options - Optional count, level, and nodeLimit arguments. 73 | * @return {object} The updated count, and whether this tag was part of a quote or not. 74 | */ 75 | export function deleteQuotationTags(document: Document, element: Node, quotationCheckpoints: boolean[], options?: DeleteQuotationOptions): { 76 | count: number, 77 | isTagInQuotation: boolean 78 | } { 79 | let isTagInQuotation = true; 80 | let { count, level, nodeLimit, shouldPreserveTable } = { 81 | count: 0, 82 | level: 0, 83 | shouldPreserveTable: false, 84 | nodeLimit: DefaultNodeLimit, 85 | ...options 86 | } 87 | 88 | // Check if this element is a quotation tag. 89 | if (quotationCheckpoints[count] && !shouldPreserveTable) { 90 | if (element.firstChild && element.firstChild.nodeType === NodeTypes.TEXT_NODE) 91 | element.replaceChild(document.createTextNode(""), element.firstChild); 92 | else 93 | element.nodeValue = ""; 94 | } else { 95 | isTagInQuotation = false; 96 | } 97 | count++; 98 | 99 | // If this a non-quote table, don't remove children. 100 | const shouldPreserveTableAnyway = shouldPreserveTable || (!isTagInQuotation && element.nodeName === 'table'); 101 | 102 | // Process recursively. 103 | const quotationChildren = new Array(); // Collection of children in quotation. 104 | 105 | if (count < nodeLimit) 106 | for (let index = 0; index < element.childNodes.length && count < nodeLimit; index++) { 107 | const node = element.childNodes.item(index); 108 | if (node.nodeType !== NodeTypes.ELEMENT_NODE) 109 | continue; 110 | 111 | let isChildTagInQuotation: boolean; 112 | ({ count, isTagInQuotation: isChildTagInQuotation } = deleteQuotationTags(document, node, quotationCheckpoints, { 113 | nodeLimit, 114 | count, 115 | level: level + 1, 116 | shouldPreserveTable: shouldPreserveTableAnyway 117 | })); 118 | 119 | if (!isChildTagInQuotation) 120 | continue; 121 | 122 | // If this child was part of a quote, keep it around. 123 | quotationChildren.push(node); 124 | } 125 | 126 | // If needed, clear the following text node. 127 | if (quotationCheckpoints[count] && !shouldPreserveTableAnyway) { 128 | if (element.nextSibling && element.nextSibling.nodeType === NodeTypes.TEXT_NODE) 129 | element.parentNode.replaceChild(document.createTextNode(""), element.nextSibling); 130 | } else { 131 | isTagInQuotation = false; 132 | } 133 | count++; 134 | 135 | // If this tag wasn't part of a quote, remove its children who were. 136 | if (!isTagInQuotation && !shouldPreserveTableAnyway) 137 | for (const node of quotationChildren) 138 | node.parentNode.removeChild(node); 139 | 140 | // Return the updated count, and whether this element was part of a quote or not. 141 | return { 142 | count, 143 | isTagInQuotation 144 | }; 145 | } 146 | 147 | export interface CutQuoteOptions { 148 | onlyRemoveEmptyBlocks?: boolean 149 | } 150 | 151 | function shouldNotRemoveQuote(node: Node, options?: CutQuoteOptions): Boolean { 152 | return Boolean(options && options.onlyRemoveEmptyBlocks && node.textContent.trim() !== ''); 153 | } 154 | 155 | /** 156 | * Cuts the outermost block element with the class "gmail_quote". 157 | * 158 | * @param {Document} document - The document to cut the element from. 159 | * @param {CutQuoteOptions} options - Extra options. 160 | * @return {boolean} Whether a corresponding quote was found or not. 161 | */ 162 | export function cutGmailQuote(document: Document, options?: CutQuoteOptions): boolean { 163 | // Find the first element that fits our criteria. 164 | const gmailQuote = XPath.select("//*[contains(@class, 'gmail_quote')]", document, true); 165 | 166 | // If no quote was found, or if that quote was a forward, return false. 167 | if (!gmailQuote || (gmailQuote.textContent && isStartOfForwardedMessage(gmailQuote.textContent)) || shouldNotRemoveQuote(gmailQuote, options)) 168 | return false; 169 | 170 | // Otherwise, remove the quote from the document and return. 171 | gmailQuote.parentNode.removeChild(gmailQuote); 172 | return true; 173 | }; 174 | 175 | /** 176 | * Cuts the Outlook splitter block and all the following block. 177 | * 178 | * @param {Document} document - The document to cut the elements from. 179 | * @param {CutQuoteOptions} options - Extra options. 180 | * @return {boolean} Whether a corresponding quote was found or not. 181 | */ 182 | export function cutMicrosoftQuote(document: Document, options?: CutQuoteOptions): boolean { 183 | let splitter = XPath.select( 184 | // Outlook 2007, 2010. 185 | "//*[local-name(.)='div' and @style='border:none;" + 186 | "border-top:solid #B5C4DF 1.0pt;" + 187 | "padding:3.0pt 0cm 0cm 0cm']|" + 188 | // Windows Mail. 189 | "//*[local-name(.)='div' and @style='padding-top: 5px; " + 190 | "border-top-color: rgb(229, 229, 229); " + 191 | "border-top-width: 1px; border-top-style: solid;']" 192 | , document, true); 193 | 194 | if (splitter) { 195 | // Outlook 2010. 196 | if (splitter.parentElement && splitter === splitter.parentElement.children[0]) 197 | splitter = splitter.parentNode 198 | } else { 199 | // Outlook 2003. 200 | splitter = XPath.select( 201 | "//*[local-name(.)='div']" + 202 | "/*[local-name(.)='div' and @class='MsoNormal' and @align='center' and @style='text-align:center']" + 203 | "/*[local-name(.)='font']" + 204 | "/*[local-name(.)='span']" + 205 | "/*[local-name(.)='hr' and @size='3' and @width='100%' and @align='center' and @tabindex='-1']" 206 | , document, true); 207 | 208 | if (splitter) { 209 | if (splitter.parentNode) 210 | splitter = splitter.parentNode; 211 | if (splitter.parentNode) 212 | splitter = splitter.parentNode; 213 | if (splitter.parentNode) 214 | splitter = splitter.parentNode; 215 | if (splitter.parentNode) 216 | splitter = splitter.parentNode; 217 | } 218 | } 219 | 220 | // If no splitter was found at this point, stop. 221 | if (!splitter|| shouldNotRemoveQuote(splitter, options)) 222 | return false; 223 | 224 | // Remove the splitter, and everything after it. 225 | while (splitter.nextSibling) 226 | splitter.parentNode.removeChild(splitter.nextSibling); 227 | 228 | splitter.parentNode.removeChild(splitter); 229 | return true; 230 | }; 231 | 232 | /** 233 | * Cuts a Zimbra quote block. 234 | * 235 | * @param {Document} document - The document to cut the element from. 236 | * @param {CutQuoteOptions} options - Extra options. 237 | * @return {boolean} Whether a corresponding quote was found or not. 238 | */ 239 | export function cutZimbraQuote(document: Document, options?: CutQuoteOptions): boolean { 240 | const splitter = XPath.select("//*[local-name(.)='hr' and @data-marker=\"__DIVIDER__\"]", document, true); 241 | if (!splitter || shouldNotRemoveQuote(splitter, options)) 242 | return false; 243 | 244 | splitter.parentNode.removeChild(splitter); 245 | return true; 246 | }; 247 | 248 | /** 249 | * Cuts all of the outermost block elements with known quote ids. 250 | * 251 | * @param {Document} document - The document to cut the element from. 252 | * @param {CutQuoteOptions} options - Extra options. 253 | * @return {boolean} Whether a corresponding quote was found or not. 254 | */ 255 | export function cutById(document: Document, options?: CutQuoteOptions): boolean { 256 | let found = false; 257 | 258 | // For each known Quote Id, remove any corresponding element. 259 | for (const quoteId of QuoteIds) { 260 | const quote = XPath.select(`//*[@id="${quoteId}"]`, document, true); 261 | if (!quote || shouldNotRemoveQuote(quote, options)) 262 | continue; 263 | 264 | found = true; 265 | quote.parentNode.removeChild(quote); 266 | } 267 | 268 | // Return whether we found at least one. 269 | return found; 270 | }; 271 | 272 | /** 273 | * Cuts the last non-nested blockquote with wrapping elements. 274 | * 275 | * @param {Document} document - The document to cut the element from. 276 | * @param {CutQuoteOptions} options - Extra options. 277 | * @return {boolean} Whether a corresponding quote was found or not. 278 | */ 279 | export function cutBlockquote(document: Document, options?: CutQuoteOptions): boolean { 280 | const quote = XPath.select( 281 | "(.//*[local-name(.)='blockquote'])" + 282 | "[not(@class=\"gmail_quote\") and not(ancestor::blockquote)]" + 283 | "[last()]" 284 | , document, true); 285 | 286 | if (!quote || shouldNotRemoveQuote(quote, options)) 287 | return false; 288 | 289 | quote.parentNode.removeChild(quote); 290 | return true; 291 | }; 292 | -------------------------------------------------------------------------------- /src/Regexp.ts: -------------------------------------------------------------------------------- 1 | import { CheckpointPrefix, CheckpointSuffix } from './Constants'; 2 | 3 | export const CheckPointRegexp = new RegExp(`${CheckpointPrefix}\\d+${CheckpointSuffix}`, "im"); 4 | 5 | export const DelimiterRegexp = new RegExp("\\r?\\n"); 6 | 7 | export const ForwardRegexp = new RegExp("^[-\\w]+[ ]*Forwarded message(:|[ ]*-+)$", "im"); 8 | 9 | export const OnDateSomebodyWroteRegexp = new RegExp( 10 | `-{0,100}[>]?[\\s]?(${ 11 | // Beginning of the line. 12 | [ 13 | "On", // English, 14 | "Le", // French 15 | "Il", // Italian 16 | "W dniu", // Polish 17 | "Op", // Dutch 18 | "Am", // German 19 | "På", // Norwegian 20 | "Den", // Swedish, Danish, 21 | "Em" , // Portuguese 22 | "El" // Spanish 23 | ].join("|") 24 | })[\\s].{0,100}(${ 25 | // Date and sender separator. 26 | [ 27 | ",", // Most languages separate date and sender address by comma. 28 | "użytkownik" // Polish date and sender address separator. 29 | ].join("|") 30 | })(.*\\n){0,2}.{0,100}(${ 31 | // Ending of the line. 32 | [ 33 | "wrote", "sent", // English 34 | "a écrit", // French 35 | "napisał", // Polish 36 | "schreef", "verzond", "geschreven", // Dutch 37 | "schrieb", // German 38 | "skrev" , // Norwegian, Swedish 39 | "escreveu", // Portuguese 40 | "escribió", // Spanish 41 | "ha scritto" // Italian 42 | ].join("|") 43 | }):?-{0,100}` 44 | ); 45 | 46 | export const OnDateWroteSomebodyRegexp = new RegExp( 47 | `-{0,100}[>]?[\\s]?(${ 48 | // Beginning of the line. 49 | [ 50 | "Op", 51 | "Am" // German 52 | ].join("|") 53 | })[\\s].{0,100}(.*\\n){0,2}.{0,100}(${ 54 | // Ending of the line. 55 | [ 56 | "schreef", "verzond", "geschreven", // Dutch 57 | "schrieb" // German 58 | ].join("|") 59 | }).{0,100}:` 60 | ); 61 | 62 | export const QuotationAfterSplitterRegexp = new RegExp( 63 | "(?:" + 64 | "(" + 65 | // Quotation border: splitter line. 66 | "s" + 67 | // Quotation lines could be marked as splitter or text, etc. 68 | ".*" + 69 | // But we expect it to end with a quotation marker line. 70 | "me*"+ 71 | ")" + 72 | // After quotations should be text only or nothing at all. 73 | "[te]*$" + 74 | ")" 75 | ); 76 | 77 | export const QuotationBlockRegexp = new RegExp( 78 | "(?:" + 79 | "(" + 80 | // Quotation border: a number of quotation marker lines. 81 | "(?:me*){2,}" + 82 | // Quotation lines could be marked as splitter or text, etc. 83 | ".*" + 84 | // But we expect it to end with a quotation marker line. 85 | "me*" + 86 | ")" + 87 | // After quotations should be nothing at all. 88 | "e*$" + 89 | ")" 90 | ); 91 | 92 | export const EmptyQuotationAfterSplitterRegexp = new RegExp( 93 | "(" + 94 | // Quotation border: splitter line. 95 | "(?:se*)+" + 96 | ")" + 97 | // Can have empty lines after quotation. 98 | "e*" 99 | ); 100 | 101 | export const EmptyQuotationBlockRegexp = new RegExp( 102 | "(" + 103 | // Quotation border: number of quotation marker lines. 104 | "(?:me*){2,}" + 105 | ")" + 106 | // Can only have empty lines after quotation. 107 | "e*$" 108 | ); 109 | 110 | // ------Original Message------ or ---- Reply Message ---- 111 | // With variations in other languages. 112 | export const OriginalMessageRegexp = new RegExp( 113 | `[\\s]*[-]+[ ]{0,100}(${ 114 | [ 115 | "Original Message", "Reply Message", // English 116 | "Ursprüngliche Nachricht", "Antwort Nachricht", // German 117 | "Oprindelig meddelelse" // Danish 118 | ].join("|") 119 | })[ ]{0,100}[-]+`, "i" 120 | ); 121 | 122 | export const FromColonRegexp = new RegExp( 123 | `(_+\\r?\\n)?[\\s]*(:?[*]?${ 124 | [ 125 | "From", 126 | "Van", 127 | "De", 128 | "Von", 129 | "Von", 130 | "Fra", 131 | "Från" // "From" in different languages. 132 | ].join("|") 133 | })[\\s]?:[\\S\\s]*(@|${ 134 | [ 135 | "Sent", 136 | "Verzonden", 137 | "Envoyé", // French 138 | "Gesendet", // German 139 | "Datum", // German 140 | "Sendt", // Norwegian 141 | "Skickat", // Swedish 142 | "Enviada em", // Portuguese 143 | "Inviato" // Italian 144 | ].join("|") 145 | })`, "i" 146 | ); 147 | 148 | export const DateColonRegexp = new RegExp( 149 | `(_+\\r?\\n)?[\\s]*(:?[*]?${ 150 | [ 151 | "Date", "Datum", "Envoyé", "Skickat", "Sendt" // "Date" in different languages. 152 | ].join("|") 153 | })[\\s]?:[*]? .*`, "i" 154 | ); 155 | 156 | export const SplitterRegexps = [ 157 | OriginalMessageRegexp, 158 | OnDateSomebodyWroteRegexp, 159 | OnDateWroteSomebodyRegexp, 160 | FromColonRegexp, 161 | DateColonRegexp, 162 | // 02.04.2012 14:20 пользователь "bob@example.com" < 163 | // bob@xxx.mailgun.org> написал: 164 | new RegExp("(\\d+\\/\\d+\\/\\d+|\\d+\\.\\d+\\.\\d+)[^]*@"), 165 | // 2014-10-17 11:28 GMT+03:00 Bob < 166 | // bob@example.com>: 167 | new RegExp("\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}\\s+GMT[^]*@"), 168 | // Thu, 26 Jun 2014 14:00:51 +0400 Bob : 169 | new RegExp("\\S{3,10}, \\d\\d? \\S{3,10} 20\\d\\d,? \\d\\d?:\\d\\d(:\\d\\d)?( \\S+){3,6}@\\S+:"), 170 | // Sent from Samsung MobileName wrote: 171 | new RegExp("Sent from Samsung .{0,100}@.{0,100}> wrote") 172 | ]; 173 | 174 | export const LinkRegexp = new RegExp("<(http://[^>]*)>"); 175 | export const NormalizedLinkRegexp = new RegExp("@@(http://[^>@]*)@@"); 176 | export const ParenthesisLinkRegexp = new RegExp("\\(https?://"); 177 | 178 | export const QuotePatternRegexp = new RegExp("^>+ ?"); 179 | export const NoQuoteLineRegexp = new RegExp("^[^>].*[\\S].*"); 180 | -------------------------------------------------------------------------------- /src/Talon.ts: -------------------------------------------------------------------------------- 1 | import * as Quotations from "./Quotations"; 2 | import * as Utils from "./Utils"; 3 | 4 | export = { 5 | quotations: Quotations, 6 | utils: Utils 7 | }; -------------------------------------------------------------------------------- /src/Utils.ts: -------------------------------------------------------------------------------- 1 | import * as XPath from 'xpath'; 2 | 3 | import { BlockTags, HardbreakTags, NodeTypes } from './Constants'; 4 | import { DelimiterRegexp, ForwardRegexp } from './Regexp'; 5 | 6 | /** 7 | * Find the line delimiter in the specified message body. 8 | * @param {string} messageBody - The message body to search in. 9 | * @return {string} The delimiter found in the body. 10 | */ 11 | export function findDelimiter(messageBody: string): string { 12 | var match = DelimiterRegexp.exec(messageBody); 13 | return match ? match[0] : "\n"; 14 | }; 15 | 16 | /** 17 | * Split a string in its multiples lines. 18 | * @param {string} str - The string to split. 19 | * @result {string[]} The array of splitted lines. 20 | */ 21 | export function splitLines(str: string): string[] { 22 | return str.split(/\r?\n/); 23 | }; 24 | 25 | /** 26 | * Return true if the ForwardRegexp matches the start of the given string. 27 | * @param {string} str - The base string. 28 | * @return {RegExpMatchArray} The resulting match, if any. 29 | */ 30 | export function isStartOfForwardedMessage(str: string): boolean { 31 | return Boolean(matchStart(str.trim(), ForwardRegexp)); 32 | }; 33 | 34 | /** 35 | * Match a Regexp with the beginning of a string. 36 | * @param {string} str - The base string. 37 | * @param {RegExp} regexp - The regular expression to match. 38 | * @return {RegExpMatchArray} The resulting match, if any. 39 | */ 40 | export function matchStart(str: string, regexp: RegExp): RegExpMatchArray { 41 | const startRegexp = new RegExp(`^${regexp.source}`, regexp.flags); 42 | let match: any = str.match(startRegexp); 43 | return !match || match.index > 0 ? null : match; 44 | }; 45 | 46 | interface ElementToTextOptions { 47 | ignoreBlockTags?: boolean 48 | } 49 | 50 | /** 51 | * Dead-simple HTML-to-text converter. 52 | * 53 | * "one
two
three" => "one\ntwo\nthree" 54 | * 55 | * @param {Node} element - The HTML element to stringify. 56 | * @param {ElementToTextOptions} options - Tweak the behavior of converter. 57 | * @return {string} The string representation of the provided element. 58 | */ 59 | export function elementToText(element: Node, {ignoreBlockTags}: ElementToTextOptions = {}): string { 60 | // Remove 113 | 116 | 120 | 121 | 122 | 123 |
124 |

From: Hey Yo [mailto:truc@haha.com]
Sent: Monday, March 07, 2015 1:14 PM
To: Recipient
Subject: This is very cool

125 |

127 |   128 |

129 |

FROM:           Good one

130 |

132 |   133 |

134 |

CONTACT:    This is cool 135 | 136 |

137 |
138 |

                        139 | Address / email@haha.com  140 | 141 |

142 |
143 |

GOOD

144 |

 

145 |

NOW

146 |

SHOULD THIS

148 |

what else

150 |

*****

152 |

Haha
Hello
good

153 |

***

155 |

truc

156 |

 

157 |

158 |   159 |

160 |

What now else go, now, haha.

161 |

 

163 |

Good’One 164 | 165 |

166 |

167 |   168 |

169 |

Now yes 170 | 171 |

172 |

173 |   174 |

175 |

What else?.

176 |

178 |   179 |

180 |

181 |   182 |

183 |

DATE:                       Good

184 |

 

186 |

TIME:                        Why?

187 |

                                    What – Else

189 |

                                    Again – Now

191 |

 

193 |

Haha:             This is

194 |

196 |   197 |

198 |

More

199 |

Good one so cool “Yo” Hoho.  Haha.  201 | 202 |

203 |

204 |   205 |

206 |

### 207 | 208 |

209 |

210 |   211 |

212 |
213 | 214 | 215 | -------------------------------------------------------------------------------- /tests/fixtures/front/email_with_quote.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
If you just read this 6 |
7 |
8 |

9 |
11 |
12 |
Too often, how we work with people outside our organization and how we collaborate internally with our team 13 | are like night and day. Different apps, different processes, different… everything. Keeping everyone in the 14 | loop is like playing telephone, and you waste time relaying updates instead of moving forward together.
15 |
16 |
17 |
18 |
19 |

20 |
you will not realize that you are missing text
21 |
22 |

23 |
24 |
On Fri, May 3, 2019 at 10:43 AM Ailian Gan <ailian@frontapp.com> wrote:
26 |
28 |
29 |
Too often, how we work with people outside our organization and how we collaborate internally with our team 30 | are like night and day. Different apps, different processes, different… everything. Keeping everyone in the 31 | loop is like playing telephone, and you waste time relaying updates instead of moving forward together.
32 |
33 |
34 |

35 |
And you will not realize there is text here
36 |

37 |
39 |
40 |
41 |
Now, you can invite guests to collaborate on your Front conversations or internal discussions, so no 42 | one's left out. If they’ve got an email address (we bet they do slightly_smiling_face), just add them. They’ll get instant access to the conversation where you 45 | can all work together in one thread. [or text here!!!!!] 46 |
47 |

48 |
Efficient collaboration with anyone you need? We think that’s how email should be. That’s why unlimited 49 | conversation guests are available on all Front plans. You can keep everyone you work with on the same page, 50 | right from your inbox.
51 |


52 |
54 |
55 |
Ailian Gan
56 |
Product @ Front
57 |
58 |
60 |
61 |
62 |
-------------------------------------------------------------------------------- /tests/fixtures/front/email_with_signature.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 13 |

BLA

14 |

15 |
16 |
17 |
18 | BLA 19 |

20 |
21 |
BLA!
22 |
23 |
24 |
25 |
On Sun, Jul 28, 2019 at 2:53 PM <test@frontapp.com> wrote:
27 |
28 |
30 |
31 |
32 |
Hello B,
33 |

34 |
35 |
BLA!
36 |
37 |
38 |
39 |
40 |

41 |
42 | --
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
Leo Vck
55 |
56 |
57 |
58 | Front 59 |
60 |
61 |

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /tests/fixtures/front/email_with_table.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 13 | 16 | 18 | 20 | 22 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 123 | 244 | 245 |
35 | 36 | 37 | 120 | 121 |
38 | 40 | 41 | 45 | 46 |
42 | Issue ( 43 | View Online)
44 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 67 | 68 | 69 | 70 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 86 | 87 |
Key:AGP-1234
Issue Type: 55 | 56 | Improvement 58 | Improvement 59 |
Status: 64 | Open Open 66 |
Priority: 71 | Major (3) Major (3) 73 |
Assignee: 78 | FName LName 79 |
Reporter: 84 | FName LName 85 |
88 |
89 | 91 | 92 | 94 | 95 |
93 | Operations
96 | 97 | 98 | 103 | 104 | 105 | 110 | 111 | 112 | 117 | 118 |
99 |   101 | View all 102 |
106 |   108 | View comments 109 |
113 |   115 | View history 116 |
119 |
122 |
124 | 125 | 126 | 241 | 242 |
127 | 128 | 129 | 146 | 147 |
130 | 131 | 132 | 143 | 144 |
133 | Super issue important  134 |
135 | 136 | Updated: 137 | 06/Jan/17 9:57 AM 138 |   Created: 139 | 05/Dec/16 10:30 AM 140 |   141 | 142 |
145 |
148 |
149 | 150 | 151 | 168 | 169 |
152 | 153 | 154 | 156 | 157 | 158 | 165 | 166 |
155 | The following issue has been updated.
159 | Updater: 160 | Björn Eide 162 |
163 | Date: 06/Jan/17 9:57 AM
164 |
167 |
170 |
171 | 172 | 173 | 199 | 200 |
174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 185 | 186 | 187 | 190 | 193 | 196 | 197 |
FieldOriginal ValueNew Value
182 | Change By 183 | FName LName on 06/Jan/17 9:57 AM 184 |
188 | Priority 189 | 191 | Minor (4) 192 | 194 | Major (3) 195 |
198 |
201 |
202 | 203 | 204 | 212 | 213 |
205 | 206 | 207 | 208 | 209 | 210 |
Project:Proj
211 |
214 |
215 | 216 | 217 | 220 | 221 | 222 |
218 |  Description  219 |  
223 | 224 | 225 | 238 | 239 |
226 | 227 | 228 | 235 | 236 |
229 |

Desc:

230 |

1. What else
2. Should we
3. Try to.
4. Do here.
5. I like it.

231 |

Let's try again.

232 |

Let's go:
233 | https://docs.google.com/a/domain.com/

234 |
237 |
240 |
243 |
246 | 247 | 248 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_10.html: -------------------------------------------------------------------------------- 1 | I saw it this weekend and will totally go 3 | again. :) One of the best movies I've seen recently! 

On Jul 6 2015, at 3:58 pm, Makala Keys 6 | <makala@nylas.com> wrote:
Hey Team,

7 |
Let's go see a movie! The latest Pixar movie, Inside Out, is supposed to 8 | be excellent (link to the trailor below). 
I'm 10 | taking a headcount for the AMC Van Ness 14 show on WED @ 7:45pm. +1s 11 | are welcome and tickets will be covered by the company. Please let me know by 12 | Wednesday at noon, so I can purchase tickets in advance!
13 |

Thanks, 
16 | Makala 


18 |
https://www.youtube.com/watch?v=seMwpP0yeu4
20 |

21 |
http://www.rottentomatoes.com/m/inside_out_2015/

22 |
23 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_10_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | I saw it this weekend and will totally go again. :) One of the best movies I've seen recently! 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_11.html: -------------------------------------------------------------------------------- 1 |

Hi folks

What is the best way to clear a Riak bucket of all key, values after running a test?
I am currently using the Java HTTP API.

-Abhishek Kona


_______________________________________________ riak-users mailing list riak-users@lists.basho.com http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com

2 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_11_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

Hi folks

5 |

What is the best way to clear a Riak bucket of all key, values after running a test? 6 |
I am currently using the Java HTTP API.

7 |

-Abhishek Kona

8 |
9 |

_______________________________________________ riak-users mailing list riak-users@lists.basho.com http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com

10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_12.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Hi,
4 |
5 | On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote: 6 |
7 | > Hi folks
8 | >
9 | > What is the best way to clear a Riak bucket of all key, values after 10 |
11 | > running a test?
12 | > I am currently using the Java HTTP API.
13 |
14 |
15 | 16 |

17 | You can list the keys for the bucket and call delete for each. Or if you 18 | put the keys (and kept track of them in your test) you can delete them 19 | one at a time (without incurring the cost of calling list first.) 20 |

21 |

22 | Something like: 23 |
24 |

25 | 
26 |         String bucket = "my_bucket";
27 |         BucketResponse bucketResponse = riakClient.listBucket(bucket);
28 |         RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();
29 |         
30 |         for(String key : bucketInfo.getKeys()) {
31 |             riakClient.delete(bucket, key);
32 |         }
33 | 
34 |

35 | 36 |

37 | would do it. 38 |
39 | See also 40 |
41 | http://wiki.basho.com/REST-API.html#Bucket-operations 42 |
43 | which says 44 |
45 | "At the moment there is no straightforward way to delete an entire 46 | Bucket. There is, however, an open ticket for the feature. To delete all 47 | the keys in a bucket, you’ll need to delete them all individually." 48 |

49 | 50 |
51 |
52 | >
53 | > -Abhishek Kona
54 | >
55 | >
56 | > _______________________________________________
57 | > riak-users mailing list
58 | > riak-users@lists.basho.com
59 | > http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com
60 |
61 |
62 |
63 |
64 |
65 |
66 | _______________________________________________ 67 | riak-users mailing list 68 | riak-users@lists.basho.com 69 | http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com 70 | 71 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_12_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Hi,
5 |
On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote:
> Hi folks
> 6 |
> What is the best way to clear a Riak bucket of all key, values after
> running a test? 7 |
> I am currently using the Java HTTP API.
8 |
9 |

You can list the keys for the bucket and call delete for each. Or if you put the keys (and kept track of 10 | them in your test) you can delete them one at a time (without incurring the cost of calling list first.) 11 |

12 |

Something like:
13 |

          String bucket = "my_bucket";         BucketResponse bucketResponse = riakClient.listBucket(bucket);         RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo();                  for(String key : bucketInfo.getKeys()) {             riakClient.delete(bucket, key);         } 
14 |

15 |

would do it.
See also
http://wiki.basho.com/REST-API.html#Bucket-operations
which says 16 |
"At the moment there is no straightforward way to delete an entire Bucket. There is, however, an open 17 | ticket for the feature. To delete all the keys in a bucket, you’ll need to delete them all individually." 18 |





_______________________________________________ riak-users mailing list 19 | riak-users@lists.basho.com http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_13.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Oh thanks.
4 | 5 | Having the function would be great.
6 | 7 | -Abhishek Kona
8 |
9 | 10 |
11 | On 01/03/11 7:07 PM, Russell Brown wrote: 12 | > Hi, 13 | > On Tue, 2011-03-01 at 18:02 +0530, Abhishek Kona wrote: 14 | >> Hi folks 15 | >> 16 | >> What is the best way to clear a Riak bucket of all key, values after 17 | >> running a test? 18 | >> I am currently using the Java HTTP API. 19 | > You can list the keys for the bucket and call delete for each. Or if you 20 | > put the keys (and kept track of them in your test) you can delete them 21 | > one at a time (without incurring the cost of calling list first.) 22 | > 23 | > Something like: 24 | > 25 | > String bucket = "my_bucket"; 26 | > BucketResponse bucketResponse = riakClient.listBucket(bucket); 27 | > RiakBucketInfo bucketInfo = bucketResponse.getBucketInfo(); 28 | > 29 | > for(String key : bucketInfo.getKeys()) { 30 | > riakClient.delete(bucket, key); 31 | > } 32 | > 33 | > 34 | > would do it. 35 | > 36 | > See also 37 | > 38 | > http://wiki.basho.com/REST-API.html#Bucket-operations 39 | > 40 | > which says 41 | > 42 | > "At the moment there is no straightforward way to delete an entire 43 | > Bucket. There is, however, an open ticket for the feature. To delete all 44 | > the keys in a bucket, you’ll need to delete them all individually." 45 | > 46 | >> -Abhishek Kona 47 | >> 48 | >> 49 | >> _______________________________________________ 50 | >> riak-users mailing list 51 | >> riak-users@lists.basho.com 52 | >> http://lists.basho.com/mailman/listinfo/riak-users_lists.basho.com 53 | > 54 | 55 |
56 | 57 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_13_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
Oh thanks. 5 |
Having the function would be great. 6 |
-Abhishek Kona 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_14.html: -------------------------------------------------------------------------------- 1 |

Recruiting Email Weekly Blastoff

Turn those cold leads into phone screens! You can make this go super fast by queueing up your drafts before hand and just sending them out during this time.
When
Fri Feb 27, 2015 5pm – 5:30pm Pacific Time
Calendar
Ben Gotow
Who
Michael Grinich - organizer
Kartik Talwar
team
Rob McQueen
Evan Morikawa
Christine Spang
Karim Hamidou
nylas.com@allusers.d.calendar.google.com
Makala Keys
Eben Freeman
Jennie Lees
Ben Gotow
Kavya Joshi

Going?    - -     

Invitation from Google Calendar

You are receiving this email at the account ben@nylas.com because you are subscribed for invitations on calendar Ben Gotow.

To stop receiving these emails, please log in to https://www.google.com/calendar/ and change your notification settings for this calendar.

2 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_14_stripped.html: -------------------------------------------------------------------------------- 1 |

Recruiting Email Weekly Blastoff

Turn those cold leads into phone screens! You can make this go super fast by queueing up your drafts before hand and just sending them out during this time.
When
Fri Feb 27, 2015 5pm – 5:30pm Pacific Time
Calendar
Ben Gotow
Who
Michael Grinich - organizer
Kartik Talwar
team
Rob McQueen
Evan Morikawa
Christine Spang
Karim Hamidou
nylas.com@allusers.d.calendar.google.com
Makala Keys
Eben Freeman
Jennie Lees
Ben Gotow
Kavya Joshi

Going?    - -     

Invitation from Google Calendar

You are receiving this email at the account ben@nylas.com because you are subscribed for invitations on calendar Ben Gotow.

To stop receiving these emails, please log in to https://www.google.com/calendar/ and change your notification settings for this calendar.

-------------------------------------------------------------------------------- /tests/fixtures/nylas/email_15.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Test 123


7 | 8 |
Quote level 2
9 | 10 |
11 | On Jul 6 2015, at 12:34 pm, spang@nylas.com <spang@nylas.com> wrote:
12 | Karim, Michael and I just discussed this. I'll send you a redwood docs diff later today to speed this along. Here's the summary for now. 13 |

14 |
15 |
We agreed on:
16 | - add a param to the create/update APIs for events instead of creating a new API
17 | - no body customization for now
18 | - sending notifications defaults to `false`
19 |
20 |

21 |
22 |
Differences from what we discussed:
23 |
- call the parameter `notify_participants` instead of `send_notifications` (it's more explicit about who is getting notified)
24 |
- only fail if event creation fails, not if the notifications fail to send*
25 |
- to make failure happen less often, we should validate event participants' email addresses and reject requests with bad email addresses as invalid
26 |
- on Google, use the `sendNotifications` parameter in the calendar API instead of sending out event notifications ourself, for consistency with expectations and increased reliability
27 |

28 |
29 |
* We can document that we make a best-effort to deliver notifications, but they may not always succeed. This is a tiny edge case and it shouldn't come up very often, so we shouldn't cause clients to complicate their logic because of it.
30 |

31 |
32 |
Please include the code for both create and update in your updated diff. It doesn't make sense to ship create only without update, and most of the code is there already.
33 |

34 |
35 |
Samples:
36 |

37 |
38 |
POST /n/<ns-id>/events?notify_participants=true -d '{ ... }'
39 |
PUT /n/<ns-id>/events?notify_participants=true -d '{ ... }'
40 |

41 |
42 |
Let's ship this and see what Lever thinks.
43 |
44 |

45 |
46 |
47 |
48 |
49 | On Jul 6 2015, at 9:55 am, Karim Hamidou <karim@nylas.com> wrote:
50 |
Christine,
51 |

52 |
53 |
Here are my notes about the quick chat we had.
54 |

55 |
56 |
1. We need an invite sending API. We could either:
57 |
- add a parameter to the event creation/update API to send emails to the participants
58 |
- or have a separate invite sending endpoint.
59 |

60 |
61 |
We chose to go with the former, because it's simpler.
62 |

63 |
64 |
2. How would this work? When creating/updating/deleting an event, an API user can set the `send_notifications` parameter to `true`. In this case, the API will generate an event invite email and will send it to the participants. 
65 |
66 |

67 |
68 |
Example: curl -X POST http://localhost:5555/n/namespace_id/events?send_notifications=true  "{ title: 'test event', start: ... }"
69 |
70 |

71 |
72 |
3. 73 | Error handling.  Invite sending can fail in a variety of ways because we're sending emails. Because API users need to know if an message went through or not, the API behaves a bit like the synchronous sending API. 
74 |

75 |
76 |
Here's what happens when a user creates an event with send_notifications=true:
77 |
78 |
1. We create the event in the db
79 |
2. We try sending emails
80 |
3. If it failed, we delete the event from the db and return the SMTP error.
81 |

82 |
83 |
Of course API users can try re-sending the same invite as often as they wish.
84 |

85 |
86 |
4. Limitations:
87 |
- it's not possible to define a custom body (though we could have define an ad-hoc `invite_body` field in the event JSON that could be used only for invite sending)
88 |
- no support for attached files 
89 |

90 |
91 |
Did I forget anything?
92 |

93 |
94 |
k
95 |

96 |
97 |

98 |
99 |
100 |
101 | 102 | 103 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_15_stripped.html: -------------------------------------------------------------------------------- 1 | Test 123


Quote level 2
-------------------------------------------------------------------------------- /tests/fixtures/nylas/email_16_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
Hi all,
10 |

11 |
Below is the sign up link for on-site massages tomorrow. The moussuse will arrive after lunch. Please sign up 12 | for your time if you wish to participate :).
13 |

14 |
Makala

15 | 195 |
196 | 197 | 198 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_17.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 33 | 34 | 35 | 39 | 40 | 49 | 50 | 51 | 59 | 60 | 61 | 64 | 65 | 66 | 77 | 78 | 79 | 82 | 83 | 84 | 87 | 88 | 89 | 90 | 91 | 95 | 98 | 99 | 100 |
5 |
6 |
10 |
 
Dear FOOBAR,
It is my sincere pleasure to inform you that you have been selected for = membership in the Honor Society of 28 | the School of General Studies. The Socie= ty was created in 1997 to celebrate the academic achievement of 29 | exceptional= GS scholars. Only juniors or seniors with a grade point average of 3.8 or = above who have completed 30 | at least 30 points at place are eligible for me= mbership. The chief aim of the Honor Society is to cultivate 31 | interaction am= ong students committed to intellectual discovery and the faculty who enjoy = teaching them. 32 |
Please join us for the Induction Ceremony, with a reception to follow. 38 |
43 |
44 |

Induction Ceremony
45 | Honor Society

46 |
Reception to follow.

47 |
48 |
The 54 | favor of a reply is requested by Friday, J= anuary 29 at 5 p.m. You are invited to bring one guest; business 57 | attire is = requested. 58 |
I look forward to celebrating with you soon.
69 |

Best wishes,
70 | ="Dean
FOO J. BAR
Dean
University 73 |
74 |
75 |
N.B. A printed letter concerning your selection has been mailed to = your local address.

76 |
 
92 |
94 |
96 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 109 | 110 | 111 | 140 | 141 | 142 |
113 | 114 | 115 | 116 | 136 | 137 | 138 |
119 | This email was sent to 120 | FOOBAR.BAZ@place.edu 123 |
124 | why did I get this?     127 | unsubscribe from this list&= nbsp;    129 | update subscription pref= erences 131 | 132 |
place 133 |
134 |
=20 135 |
139 |
143 |
-------------------------------------------------------------------------------- /tests/fixtures/nylas/email_17_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 66 |
8 |
 
Dear FOOBAR,
It is my sincere pleasure to inform you that you have been selected for = membership in the Honor Society of the 24 | School of General Studies. The Socie= ty was created in 1997 to celebrate the academic achievement of exceptional= 25 | GS scholars. Only juniors or seniors with a grade point average of 3.8 or = above who have completed at least 30 26 | points at place are eligible for me= mbership. The chief aim of the Honor Society is to cultivate interaction am= 27 | ong students committed to intellectual discovery and the faculty who enjoy = teaching them.
Please join us for the Induction Ceremony, with a reception to follow.
34 |
The 38 | favor of a reply is requested by Friday, J= anuary 29 at 5 p.m. You are invited to bring one guest; business 40 | attire is = requested.
I look forward to celebrating with you soon.
47 |

Best wishes,
<img src="http://gs.place.edu/files/gs/BAR_signature.jpg" alt="="Dean" foo="" j.="" bar"="" 48 | width="148" height="44" border="0" style="pa= dding-top:15px;padding-left:0px" title="place University School 49 | of Gen= eral Studies"/>
FOO J. BAR
Dean
University


N.B. A printed letter concerning 50 | your selection has been mailed to = your local address.

51 |
 
62 |
67 |






<table border="0" cellpadding="0" cellspacing="0" wid="t" h="100%" style="background-color:#ffffff;border-top:1px 68 | solid #e5e5e5" ==""> 69 | 70 | 71 | 72 | 73 | 74 | 75 | 83 | 84 | 85 |
76 | This email was sent to FOOBAR.BAZ@place.edu
77 | why did I get this?     <a href="="http://place.us6.list-manage1.com/unsubscribe?u=xxxxxxxxxxxxxxxxxxxx=" 79 | e32c6ce&id="3Dcebd346d3d&e=3D223ebffa5a&c=3Df2c0d84500"" style="3D="color:#404040!important"" 80 | target="_blank">unsubscribe from this list&= nbsp; 81 | update subscription pref= erences
place

=20
86 | 87 | 88 | 89 |
90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_18_stripped.html: -------------------------------------------------------------------------------- 1 |

README:

So this is an interesting test case. The email below looks like it failed quoted text detection. However, you can see that there is some plain text (the signature) at the bottom of the email after the blockquote. Unfortunately this looks arguably identical to someone who inline-replied to a message after a piece of quoted text. As such there's not a lot we can do about this until we can come up with an efficient way to inspect the bodies of previous messages. This is likely something that will have to happen server-side.
Hi,

TEXT

Regards,
FROM
-------------------------------------------------------------------------------- /tests/fixtures/nylas/email_1_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
Hi Jeff,  Quick update on the event bugs: - I fixed the bug where events would be incorrectly marked as read-only. - We expose RRULEs as valid JSON now.   We're currently testing the fixes, they should ship early next week.  Concerning timezones, an event should always be associated with a timezone. Having a NULL value instead is a bug on our end. I will be working on fixing this on Monday and will let you know when it's fixed.  Thanks for your detailed bug reports,
11 |
 Karim
12 |
From:  Kavya Joshi <kavya@nylas.com>
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

It's be great to talk with Jonathan-- feel free to connect us. Thanks.
9 |

10 |


11 |

12 |

The bug I mentioned manifested itself like this:
13 |

14 |


15 |

16 |
17 |
Traceback (most recent call last):
 18 |   File "/usr/local/lib/python2.7/dist-packages/gevent/greenlet.py", line 327, in run
 19 |     result = self._run(*self.args, **self.kwargs)
 20 |   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 190, in _run
 21 |     fail_classes=self.retry_fail_classes)
 22 |   File "/vagrant/inbox/util/concurrency.py", line 120, in retry_and_report_killed
 23 |     **reset_params)()
 24 |   File "/vagrant/inbox/util/concurrency.py", line 73, in wrapped
 25 |     return func(*args, **kwargs)
 26 |   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 217, in _run_impl
 27 |     self.state = self.state_handlers[old_state]()
 28 |   File "/vagrant/inbox/util/concurrency.py", line 73, in wrapped
 29 |     return func(*args, **kwargs)
 30 |   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 270, in initial_sync
 31 |     self.initial_sync_impl(crispin_client)
 32 |   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 293, in initial_sync_impl
 33 |     remote_uids = crispin_client.all_uids()
 34 |   File "/vagrant/inbox/crispin.py", line 489, in all_uids
 35 |     fetch_result = self.conn.search(['ALL', 'UID'])
 36 |   File "/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py", line 588, in search
 37 |     return self._search(normalise_search_criteria(criteria), charset)
 38 |   File "/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py", line 621, in _search
 39 |     for item in parse_response(data):
 40 |   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py", line 46, in parse_response
 41 |     return tuple(gen_parsed_response(data))
 42 |   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py", line 56, in gen_parsed_response
 43 |     for token in src:
 44 |   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py", line 118, in __iter__
 45 |     for tok in self.read_token_stream(iter(source)):
 46 |   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py", line 149, in __iter__
 47 |     return PushableIterator(six.iterbytes(self.src_text))
 48 |   File "/usr/local/lib/python2.7/dist-packages/imapclient/six.py", line 597, in iterbytes
 49 |     return (ord(byte) for byte in buf)
 50 | TypeError: 'NoneType' object is not iterable
 51 | <FolderSyncEngine at 0x5e4e550> failed with TypeError
52 |

53 |
54 |
55 |


56 |

57 |

But turns out Tom fixed it 58 | here.  I don't think it's yet on PyPI.
59 |

60 |


61 |

62 |


63 |

64 |


65 |

66 |
67 |
68 |
From: Menno Smits <menno@freshfoo.com>
69 | Sent: Wednesday, May 27, 2015 2:41 AM
70 | To: Michael Grinich
71 | Cc: Christine Spang
72 | Subject: Re: Thoughts
73 |
 
74 |
75 |
76 |
Hi Michael,
77 |
78 |
 
79 |
No problems about the delay. I know what it's like.
80 |
81 |
 
82 |
Another interesting development: after our call I put out my feelers to see if any of the developers that I know and trust would be interested in tackling this work, and I got a bite from a former colleague and good friend, Jonathan Hartley (http://tartley.com/). 83 | He's one of the smartest developers I know - with huge amounts of experience with Python - and is highly disciplined about testing and code quality. On top of that, he's in the process of arranging a move to the US (his wife his American) so that could work 84 | well too. He has no experience with IMAP or Go, but these are things I'm confident he could quickly pick up. Due to the pending move to the US, he currently on a short contract so could be available on fairly short notice.
85 |
86 |
 
87 |
Are you interested in talking to him? He could give you a full 5 days a week working on IMAPClient and related bits.
88 |
89 |
 
90 |
- Menno
91 |
92 |
 
93 |
p.s. Can you give me details on the bug that you found today?
94 |
95 |
 
96 |
 
97 |
On Wed, 27 May 2015, at 06:59, Michael Grinich wrote:
98 |
99 |
100 |
Hi Menno,
101 |
102 |
 
103 |
Sorry for the delay. 
104 |
105 |
 
106 |
Having you work dedicated 1 day a week for Nylas would be fantastic. We already have several low-hanging IMAPclient projects we need help with, and there are many places in our sync engine codebase that I think you could make huge contributions to.
107 |
108 |
 
109 |
So in short, yes. We'd be ready to get started immediately. (Already had a bug come up today where IMAPclient fails when folders have no items in them...)
110 |
111 |
 
112 |
--Michael
113 |
114 |
 
115 |
 
116 |
 
117 |
118 |
On May 21 2015, at 7:44 pm, Menno Smits <menno@freshfoo.com> wrote:
119 |
120 |

Hi Michael,
121 |

122 |

It was great to talk to you and Christine earlier this week.
123 |

124 |

I've been thinking about ways that we could make this work. I'm really
125 | not ready to leave my current position at Canonical but I'd be prepared
126 | to consider dropping my hours to work for Nylas part-time. Would you
127 | consider having me work for Nylas one full day a week if I could
128 | negotiate my hours down to 4 days a week at Canonical? I think they
129 | might be open to that (any less could be a struggle).

130 |

I realise this is probably less of a commitment from me than you'd
131 | probably like but one day a week would give me much more time to work on
132 | IMAPClient than I have now. You'll get you the features you need much
133 | sooner. If this seems workable to you I can start the conversation with
134 | my managers.

135 |

I'm also writing to a couple top notch developers that I trust about the
136 | possibility of working with you on this (they're both in London). I
137 | believe that one in particular could be thinking about leaving his
138 | current role.

139 |

Cheers,
140 | Menno

141 |
142 |
143 |
 
144 |
145 |
146 |
147 | 148 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_2_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 |
17 |

It's be great to talk with Jonathan-- feel free to connect us. Thanks. 18 |

19 |

20 |

21 |

The bug I mentioned manifested itself like this: 22 |

23 |

24 |

25 |
26 |
Traceback (most recent call last):   File "/usr/local/lib/python2.7/dist-packages/gevent/greenlet.py", line 327, in run     result = self._run(*self.args, **self.kwargs)   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 190, in _run     fail_classes=self.retry_fail_classes)   File "/vagrant/inbox/util/concurrency.py", line 120, in retry_and_report_killed     **reset_params)()   File "/vagrant/inbox/util/concurrency.py", line 73, in wrapped     return func(*args, **kwargs)   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 217, in _run_impl     self.state = self.state_handlers[old_state]()   File "/vagrant/inbox/util/concurrency.py", line 73, in wrapped     return func(*args, **kwargs)   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 270, in initial_sync     self.initial_sync_impl(crispin_client)   File "/vagrant/inbox/mailsync/backends/imap/generic.py", line 293, in initial_sync_impl     remote_uids = crispin_client.all_uids()   File "/vagrant/inbox/crispin.py", line 489, in all_uids     fetch_result = self.conn.search(['ALL', 'UID'])   File "/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py", line 588, in search     return self._search(normalise_search_criteria(criteria), charset)   File "/usr/local/lib/python2.7/dist-packages/imapclient/imapclient.py", line 621, in _search     for item in parse_response(data):   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py", line 46, in parse_response     return tuple(gen_parsed_response(data))   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_parser.py", line 56, in gen_parsed_response     for token in src:   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py", line 118, in __iter__     for tok in self.read_token_stream(iter(source)):   File "/usr/local/lib/python2.7/dist-packages/imapclient/response_lexer.py", line 149, in __iter__     return PushableIterator(six.iterbytes(self.src_text))   File "/usr/local/lib/python2.7/dist-packages/imapclient/six.py", line 597, in iterbytes     return (ord(byte) for byte in buf) TypeError: 'NoneType' object is not iterable <FolderSyncEngine at 0x5e4e550> failed with TypeError
27 |
28 |
29 |
30 |

31 |

32 |

But turns out Tom fixed it here. I don't think it's yet on PyPI. 33 |

34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

You should send these to team@ 
9 |

10 |


11 |

12 |

hope you feel better! 
13 |

14 |


15 |

16 |


17 |

18 |
19 |
20 |
From: Andrea Whiting
21 | Sent: Monday, April 20, 2015 9:48 AM
22 | To: Christine Spang; Michael Grinich
23 | Subject: WFH this morn
24 |
 
25 |
26 |
27 |
28 |

Morning!
29 |

30 |


31 |

32 |

I'm coughing up a post-pneumonia storm this morning, going to see if I can get a doctor's appt. in the AM and then potentially come in after lunch. My body might just need a day of rest after all that travel.
33 |

34 |


35 |

36 |

Not sure if WFH is a message to email team@ or post on slack in 'general' - lmk what works best.
37 |

38 |


39 |

40 |

Andrea
41 |

42 |
43 |
44 |
45 |
46 | 47 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_3_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 |
17 |

You should send these to team@ 18 |

19 |

20 |

21 |

hope you feel better! 22 |

23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 |
11 |

Hey All,

12 |


13 |

14 |

This was a long email but wanted to do a quick followup on the One Medical portion. I've only received a few responses so far.&n= 17 | bsp;

18 |


19 |

20 |

Signing up would not require you to change your current primary care doc= 21 | tor or pay additional insurance, rather it is a primary care center (think = 22 | Kaiser) where you can make all of your appointments. Super convenient right= 23 | !?

24 |


25 |

26 |

Get back to me if this is something you want to participate in. Nylas wo= 27 | uld cover the cost. 
28 |

29 |


30 |

31 |

Thanks,
32 |

33 |

Makala 
34 |

35 |


36 |

37 |


38 |

39 |
40 |
41 |
42 |
44 |
From: Makala Keys
46 | Sent: Friday, May 29, 2015 4:59 PM
47 | To: team
48 | Subject: Revisiting Health Insurance
49 |
 
50 |
51 |
Hi All! 52 |

54 |
55 |
A number of health insurance questions have come up recently. As we g= 57 | row I want to make sure that everyone is well-informed about insurance and = 58 | that you are getting the most out 59 | of your health benefits.   60 |
61 |

63 |
64 |
My original insurance presentation 66 | here , explains w= 69 | hat plans Nylas currently offers.
70 |
71 |

72 |
73 |
This week I also looked into how to set up a 76 | health savings account. A health savings account is a special tax= 78 | advantaged account that is used with a high-deductible health plan (HDHP),= 79 | and it allows you to pay for various qualified 80 | medical expenses tax-free. Nylas offers two high-deductible health plans. = 81 | You can see exactly which plan you are enrolled in, and if you qualify to s= 82 | et up an HSA, through your 83 | 85 | Zenefits account. 
86 |

88 |
89 |
Finally, I researched info about  One Medical Group.&nb= 92 | sp; which is an innovate 93 | 95 | primary care health organization.  One Medical group has seve= 96 | ral locations in San Francisco and a location in Berkeley. It offers an abu= 97 | ndance of services listed 98 | here, making it easier for you to make doctors appointments. = 100 | ;
101 |

103 |
104 |
Nylas is going to start offering One Medical Group membership on an o= 106 | pt-in basis. Take some time to look it over this weekend and see if this is= 107 | something you are interested in. 108 | Our One Medical membership plan  will also cover spouses and children= 109 | . 
110 |

111 |
112 |
As always, feel free to reach out with questions! 
113 |

114 |
115 |
Makala 
116 |

118 |
119 |

121 |
122 |

123 |
124 |

125 |
126 |
127 |

128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | 137 | 138 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_4_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 |
17 |

Hey All,

18 |


19 |

This was a long email but wanted to do a quick followup on the <a h="ref="http://www.onemedical.com/sf/doctors/?gclid=3DCJi_-9bP9sUCFUiGfgodMA=" 20 | uaxq"="">One Medical portion. I've only received a few responses so far.&n= bsp;

21 |


22 |

Signing up would not require you to change your current primary care doc= tor or pay additional insurance, rather 23 | it is a primary care center (think = Kaiser) where you can make all of your appointments. Super convenient right= 24 | !?

25 |


26 |

Get back to me if this is something you want to participate in. Nylas wo= uld cover the cost. 

27 |


28 |

Thanks,

29 |

Makala 

30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_5.html: -------------------------------------------------------------------------------- 1 |
Just an update on 4 | this guys, we had three other customers login this morning and it seems that 5 | their accounts are also listed as: "Sync not running" but some of their 6 | messages have been synced. Emails are below:

drew@a.com (which is somehow 8 | listed twice)
s.k@g.com
oprokhorenko@splunk.com

-Andrew
 
From: Jeff Meister <jeff@esper.com>
Date: Mon, Jun 22, 2015 at 16 | 4:00 PM
Subject: Nylas bug reports
To: support <support@nylas.com>
Cc: Michael Grinich <mg@nilas.com>, Christine 20 | Spang <spang@nylas.com>, Kavya Joshi <kavya@nylas.com>, Karim 23 | Hamidou <karim@nylas.com>


Hi Nylas 25 | folks,

I made a new thread because the old one was getting really 26 | long. We've noticed a few things when onboarding our first few Exchange 27 | customers:

1. One of our users signed in to Nylas 28 | successfully with email oprokhorenko@splunk.com, but I see in the dashboard that 30 | his status is "Sync not running", with a white circle rather than red or green. 31 | We haven't seen this one before. Clicking on his entry does show 84 messages 32 | synced, but that hasn't changed in a while. What should we 33 | do?

2. We may be experiencing event duplication issues again, 34 | but I'm still looking into this to see if we're causing the problem from our 35 | side. The affected account in this case is han@a.com. I'll send an update once I have 37 | something concrete, but I wanted to bring it up in case there are any quick 38 | checks you can run on his account in the 39 | meantime.

3. I noticed that drafts have started 40 | appearing in my delta sync. Maybe it was always this way and I didn't notice 41 | because my testing accounts didn't have a user actively emailing, but we're 42 | getting a lot of data that we don't need. We use exclude_types in our delta 43 | sync requests, with every type except "event", but if I add "draft" to this 44 | list, I get a bad request error from Nylas. Can you enable filtering of drafts 45 | here?

Thanks again, and see you on Wednesday!
Jeff
48 |

49 |
50 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_5_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 |
9 |
10 |
Just an update on this guys, we had three other customers login this morning and it seems that 11 | their accounts are also listed as: "Sync not running" but some of their messages have been synced. 12 | Emails are below:

drew@a.com (which is somehow listed 13 | twice)
s.k@g.com
oprokhorenko@splunk.com
15 |

16 |
-Andrew
17 |
18 |
19 |
20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Hello!

7 |

Here is a summary of the alerts in the past 24 hours:

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
Alert name# Critical# Warning
mt-st-helena.account-dead-check199.00
mt-st-helena.mysql-redwoodstaging-check02.0
mt-st-helena.mysql-redwoodproduction-check011.0
mt-st-helena.mysql-edgehillproduction-check01.0
mt-st-helena.mysql-edgehillstaging-check09.0
mt-st-helena.mysql-mailsyncstaging-check09.0
47 |

Have a good day!

48 | 49 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_6_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Hello!

8 |

Here is a summary of the alerts in the past 24 hours:

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
Alert name# Critical# Warning
mt-st-helena.account-dead-check199.00
mt-st-helena.mysql-redwoodstaging-check02.0
mt-st-helena.mysql-redwoodproduction-check011.0
mt-st-helena.mysql-edgehillproduction-check01.0
mt-st-helena.mysql-edgehillstaging-check09.0
mt-st-helena.mysql-mailsyncstaging-check09.0
48 |

Have a good day!

49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

Thanks for coming by today. :) Let me know next time you're around and settled and I'll show you a wicked awesome demo of the stuff we've been building! 
9 |

10 |


11 |

12 |

And congrats on YC! 
13 |

14 |


15 |

16 |


17 |

18 |
19 |
20 |
From: Ted Benson <eob@csail.mit.edu>
21 | Sent: Monday, April 27, 2015 10:51 AM
22 | To: Michael Grinich
23 | Subject: Re: James Tamplin intro?
24 |
 
25 |
26 |
Hey Michael,
27 |
28 | We're in you're neck of the woods and can meet whenever you're free -- or happy to swing by at 12 like planned if that fits your schedule better. 29 |
30 |
31 | Looking forward to it!
32 | Ted
33 |
On Fri, Apr 24, 2015 at 11:55 AM Michael Grinich <mg@nylas.com> wrote:
34 |
35 |
36 |
37 |

Sure-- bring him along too. See you then. :)
38 |

39 |


40 |

41 |

--mg
42 |

43 |


44 |

45 |
46 |
47 |
From: 48 | edward.benson@gmail.com <edward.benson@gmail.com> on behalf of Ted Benson <eob@csail.mit.edu>
49 | Sent: Friday, April 24, 2015 11:39 AM
50 | To: Michael Grinich
51 | Subject: Re: James Tamplin intro?
52 |
 
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
w/ James: Thanks a bunch!
63 |

64 |
65 |
Lunch: Great! Just sent you an invite. Noon OK?
66 |

67 |
68 |
If YC rejection: That doesn't worry me. We're doing this either way. And if we truly hit a brick wall then that's valuable data for us as well.
69 |

70 |
71 |
Looking forward to seeing you! Mind if Jake (cofounder) comes along for lunch?
72 |

73 |
74 |
75 |
76 |

77 |
On Fri, Apr 24, 2015 at 12:31 AM, Michael Grinich 78 | <mg@nylas.com> wrote:
79 |
80 |
Cool, I'll see what I can do wrt James. He might be in Google land these days. 
81 |

82 | Some VCs see YC rejection as a red flag. You likely need to have clearly demonstrated conviction and/or traction to raise if that happens.
83 |

84 |
85 |
Want to stop by for lunch on Monday? We're at 2030 Harrison St. in SF
86 |
87 |

88 |
89 |
90 |
91 |
On Wed, Apr 22, 2015 at 4:21 PM -0700, "Ted Benson" 92 | <eob@csail.mit.edu> wrote:
93 |
94 |
95 |
96 |
Hi Michael, 97 |

98 |
99 |
No worries about the delay! I've never been to PyCon but heard it's a good time.
100 |

101 |
102 |
Thanks -- I know it's just the beginning, but it feels like such a long road to have gotten this far already. Psyched for what's to come.
103 |

104 |
105 |
Connections:
106 |
Right now we're looking for advice and angel investment. 
107 |
- If we don't get YC, we're going to start a seed round
108 |
- If we do get YC, we're still really interested in meeting the kinds of people who can offer advice having already grown successful platform companies
109 |

110 |
111 |
In your judgement is that a reasonable way to plan out meetings for the post-YC days we're there. 
112 |

113 |
114 |
Would love an intro to James, his tech counterpart, or others you think could help us out along those lines! (Agree about their model, btw).
115 |

116 |
117 |
I'd love to drop by Monday or Tuesday and see what you all are cooking up!
118 |

119 |
120 |
Thanks again!
121 |
Ted
122 |

123 |
124 |
   
125 |
126 |

127 |
On Wed, Apr 22, 2015 at 5:03 AM, Michael Grinich 128 | <mgrinich@gmail.com> wrote:
129 |
130 |
131 | Hey Ted-- sorry for the delay. I was at PyCon in Montreal (we sponsored this year) and then our company retreat in Tahoe. Finally catching up back in SF. 132 |
133 |
134 | Congrats on making it to the interview circuit!
135 | regarding networking, are you looking for partnerships or YC advise or investment or something else?
136 |
137 | I've actually been coaching a couple startups, one of whichever is pitching YC this wkd. Anything specific you need there?
138 |
139 | Our first engineer came from Firebase (and MIT/PDOS before that), so I could get an intro to James. FYI he's more on the biz side since he's not a programmer. Also I'm not sure they're the best model for success honestly. 140 |
141 |
142 | In any case, want to grab a coffee and catch up? I'd love to show you some of the stuff we're building too. (The good stuff we haven't announced yet.) Happy to help on any of the mentioned points above. 143 |
144 |
145 | --mg
146 |
147 |

148 |
On Sun, Apr 19, 2015 at 8:45 AM Ted Benson <eob@csail.mit.edu> wrote:
149 |
150 |
Hah - I just realized I might have read Facebook wrong. It is WE who are friends, not you and James. 151 |

152 |
153 |
So I'll spin that ask around: do you know any good advisors or angels that you'd be comfortable putting us in touch with? Nearest neighbors to us are companies like IFTTT, Firebase, Ionic, Parse, Automattic.
154 |
155 |

156 |
On Sun, Apr 19, 2015 at 11:39 AM, Ted Benson 157 | <eob@csail.mit.edu> wrote:
158 |
159 |
Hey Michael, 160 |

161 |
162 |
Hope all is well! FB says your friends with James Tamplin. Do you know him well enough to introduce me to him so I could ask for some advice?
163 |

164 |
165 |
My cofounder and I are headed out to YC to interview next weekend, and we're trying to make the most of the trip. Firebase and James' other involvements are right up the alley of what we're doing, so I think he would be a really good person to meet. 
166 |

167 |
168 |
Company quickie, for context:
169 |

170 | Cloudstitch.io is a beginner-friendly reactive platform for making web apps that run off of everyday objects. Objects like Google Spreadsheets and Docs, all 171 | the way to physical objects like sensors and Amazon Dash-style buttons. Think IFTTT but for building apps.
172 |
173 |

174 |
175 |
Let me know -- and thanks either way! 
176 |

177 |
178 |
Cheers,
179 |
Ted
180 | 181 |

182 |
183 |

184 |
185 | --
186 |
187 |
:: Ted Benson
188 | :: http://people.csail.mit.edu/eob/
189 |
:: @edwardbenson
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |

200 |
201 | --
202 |
203 |
:: Ted Benson
204 | :: http://people.csail.mit.edu/eob/
205 |
:: @edwardbenson
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |

218 |
219 | --
220 |
221 |
:: Ted Benson
222 | :: http://people.csail.mit.edu/eob/
223 |
:: @edwardbenson
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |

236 |
237 | --
238 |
239 |
:: Ted Benson
240 | :: http://people.csail.mit.edu/eob/
241 |
:: @edwardbenson
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 | 255 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_7_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 |
17 |

Thanks for coming by today. :) Let me know next time you're around and settled and I'll show you a wicked awesome demo of the stuff we've been building! 18 |

19 |

20 |

21 |

And congrats on YC! 22 |

23 |
24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_8.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |

What about Haystack? https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf
9 |

10 |


11 |

12 |


13 |

14 |
15 |

16 |
17 |
18 |
From: Christine Spang
19 | Sent: Thursday, May 21, 2015 6:52 PM
20 | To: Nylas Study Group
21 | Subject: Intros & paper suggestions
22 |
 
23 |
24 |
hey folks, 25 |

26 |
27 |
Thought I'd let y'all know who's on this list. Current list members are:
28 |

29 |
30 |
Nylas team
31 |
Michael Grinich
32 |
me
33 |
Kavya Joshi
34 |
Eben Freeman
35 |
Jennie Lees
36 |
Karim Hamidou
37 |
Evan Morikawa
38 |
Ben Gotow
39 |
Rob McQueen
40 |
Kartik Talwar
41 |
Andrea Whiting
42 |
Makala Keys
43 |

44 |
45 |
Friends
46 |
Nelson Elhage
47 |
Deborah Hanus
48 |
Owen Derby
49 |
Marco Munizaga
50 |

51 |
52 |
Anyone have a paper they're really hankering to read or present in two weeks? :)
53 |
--spang
54 |
55 |
56 |
57 | 58 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_8_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 |
17 |

What about Haystack? https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Beaver.pdf 18 |

19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_9.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
Hi Christine,  7 |

8 |
9 |
My apologies, please use the below referral code when taking the Insights evaluation: 
10 |

11 |
12 |
13 |

Go To:  https://online.insights.com/evaluator/SNP/discovery  14 |   

15 |

Referral Code: Nylas2015

16 |


17 |

18 |

Please let us know if you have any questions!

19 |


20 |

21 |

Thank you,

22 |

Eva

23 |
24 |

25 |

26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 34 | -------------------------------------------------------------------------------- /tests/fixtures/nylas/email_9_stripped.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
Hi Christine, 8 |
9 |
10 |
My apologies, please use the below referral code when taking the Insights evaluation:
11 |
12 |
13 |
14 |

Go To: https://online.insights.com/evaluator/SNP/discovery 15 | 16 |

17 |

Referral Code: Nylas2015

18 |


19 |

Please let us know if you have any questions!

20 |


21 |

Thank you,

22 |

Eva

23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/fixtures/talon/OLK_SRC_BODY_SECTION.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Reply
4 | 5 |
6 | From: Bob <bob@example.com>
7 | Date: Tue, 01 Nov 2011 18:54:39 -0700
8 | To: Rob <rob@example.com>
9 | Subject: Test
10 |
11 |
12 | Hi 13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/gmail.html: -------------------------------------------------------------------------------- 1 |
Hi. I am fine.

Thanks,
Alex
2 |


On Thu, Jun 26, 2014 at 2:14 PM, Alexander L <a@example.com> wrote:
3 |
4 | Hello! How are you?

5 |
Thanks,
Sasha.
6 |

7 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/hotmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 |
Hi. I am fine.

Thanks,
Alex


Date: Thu, 26 Jun 2014 13:53:45 +0400
Subject: Test message
From: abc@example.com
To: alex.l@example.com

Hello! How are you?

Thanks,
17 | Sasha.
18 | 19 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/mail_ru.html: -------------------------------------------------------------------------------- 1 | 2 |

Hi. I am fine.

Thanks,
Alex




Thu, 26 Jun 2014 14:00:51 +0400 от Alexander L <abc@example.com>:
3 |
4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 |
40 | 41 | 42 |
Hello! How are you?

43 |
Thanks,
Sasha.
44 | 45 |
46 | 47 | 48 | 49 |
50 | 51 | 52 |
53 | 54 | 55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/ms_outlook_2003.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 43 | 49 | 50 | 51 | 52 | 53 |
54 | 55 |

Hi. I am fine.

57 | 58 |

 

60 | 61 |

Thanks,

63 | 64 |

Alex

66 | 67 |

 

69 | 70 |
71 | 72 |
74 | 75 |
76 | 77 |
78 | 79 |

From: 82 | Alexander L [mailto:abc@example.com]
83 | Sent: Friday, June 27, 2014 12:06 84 | PM
85 | To: Alexander
86 | Subject: Test message

88 | 89 |
90 | 91 |

 

93 | 94 |
95 | 96 |
97 | 98 |
99 | 100 |

Hello! How are you?

102 | 103 |
104 | 105 |
106 | 107 |

 

109 | 110 |
111 | 112 |
113 | 114 |

Thanks,

116 | 117 |
118 | 119 |
120 | 121 |

Sasha.

123 | 124 |
125 | 126 |
127 | 128 |
129 | 130 |
131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/ms_outlook_2007.html: -------------------------------------------------------------------------------- 1 |

Hi. I am fine.

Thanks,

Alex

 

From: Alexander L [mailto:abc@example.com]
Sent: Thursday, July 03, 2014 3:50 PM
To: alex.l@example.com
Subject: Test message

 

Hello! How are you?

 

Thanks,

Sasha.

43 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/ms_outlook_2010.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 70 | 71 | 72 |
73 |

Hi. I am fine.

74 |

Thanks,

75 |

Alex

76 |

From: Foo [mailto:foo@bar.com] 77 | On Behalf Of baz@bar.com
78 | Sent: Monday, January 01, 2000 12:00 AM
79 | To: john@bar.com
80 | Cc: jane@bar.io
81 | Subject: Conversation

82 |

 

83 |

Hello! How are you?

84 |

 

85 |
86 | 87 | 88 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/thunderbird.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hi. I am fine.
7 |
8 | Thanks,
9 | Alex
10 |
On 26.06.2014 14:41, Alexander L 11 | wrote:
12 |
13 |
16 |
17 |
18 |
Hello! How are you?
20 |

22 |
23 |
Thanks,
25 |
Sasha.
27 |
28 |
29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/windows_mail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 28 | 29 |
Hi. I am fine.

Thanks,
Alex


От: Alexander L
Отправлено: ‎четверг‎, ‎26‎ ‎июня‎ ‎2014‎ г. ‎15‎:‎05
Кому: Alex

Hello! How are you?

30 |
Thanks,
Sasha.
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /tests/fixtures/talon/html_replies/yandex_ru.html: -------------------------------------------------------------------------------- 1 |

Hi. I am fine.

Thanks,
Alex

26.06.2014, 14:41, "Alexander L" <abc@example.com>:

Hello! How are you?

Thanks,
Sasha.
2 | -------------------------------------------------------------------------------- /tests/fixtures/talon/reply-quotations-share-block.eml: -------------------------------------------------------------------------------- 1 | Content-Type: multipart/alternative; 2 | boundary="===============6853056845739363347==" 3 | MIME-Version: 1.0 4 | Date: Wed, 4 Apr 2012 22:22:42 -0700 (PDT) 5 | From: Joe Doe 6 | Subject: Re: You've got a new booking inquiry! 7 | 8 | --===============6853056845739363347== 9 | MIME-Version: 1.0 10 | Content-Type: text/plain; charset="utf-8" 11 | Content-Transfer-Encoding: base64 12 | 13 | SGkgS2F0aGFyaW5lLsKgIFNvdW5kcyBncmVhdC7CoCBBcmUgdGhlcmUgYW5kIGRpZXRyeSByZXN0cmljdGlvbnMgb3IgdGhpbmdzIHlvdXIgaHVzYmFuZCBkb2VzL2RvZXNuJ3QgbGlrZSB0byBlYXQ/wqAgV291bGQgeW91IGxpa2UgdG8gZG8gYSBmZXcgaG9ycyBkIG9ldXZyZXMgYW5kIHRoZW4gYcKgMyBvciA0wqBjb3Vyc2UgZGlubmVyP8KgIExldCBtZSBrbm93IHdoYXQgeW91IHRoaW5rIHdpbGwgd29yayBiZXN0IGFuZCBJIHdpbGwgc3RhcnQgd29ya2luZyBvbiBhIG1lbnUgYW5kIHByb3Bvc2FsLsKgIFRoYW5rcyBzbyBtdWNoIGFuZCBsb29rIGZvcndhcmQgdG8gaGVhcmluZyBmcm9tIHlvdSBzb29uLgrCoApKb2UgWFhYCgotLS0gT24gV2VkLCA0LzQvMTIsIHh4eEBleGFtcGxlLmNvbSA8eHh4QGV4YW1wbGUuY29tPiB3cm90ZToKCgpGcm9tOiB4eHhAZXhhbXBsZS5jb20gPHh4eEBleGFtcGxlLmNvbT4KU3ViamVjdDogWW91J3ZlIGdvdCBhIG5ldyBib29raW5nIGlucXVpcnkhClRvOiB4eHhAeWFob28uY29tCkRhdGU6IFdlZG5lc2RheSwgQXByaWwgNCwgMjAxMiwgMTA6MjMgUE0KCk5ldyBCb29raW5nIElucXVpcnkKCg== 14 | 15 | --===============6853056845739363347== 16 | MIME-Version: 1.0 17 | Content-Type: text/html; charset="utf-8" 18 | Content-Transfer-Encoding: base64 19 | 20 | PHRhYmxlPjx0cj48dGQ+PERJVj5IaSBLYXRoYXJpbmUuJm5ic3A7IFNvdW5kcyBncmVhdC4mbmJzcDsgQXJlIHRoZXJlIGFuZCBkaWV0cnkgcmVzdHJpY3Rpb25zIG9yIHRoaW5ncyB5b3VyIGh1c2JhbmQgZG9lcy9kb2Vzbid0IGxpa2UgdG8gZWF0PyZuYnNwOyBXb3VsZCB5b3UgbGlrZSB0byBkbyBhIGZldyBob3JzIGQgb2V1dnJlcyBhbmQgdGhlbiBhJm5ic3A7MyBvciA0Jm5ic3A7Y291cnNlIGRpbm5lcj8mbmJzcDsgTGV0IG1lIGtub3cgd2hhdCB5b3UgdGhpbmsgd2lsbCB3b3JrIGJlc3QgYW5kIEkgd2lsbCBzdGFydCB3b3JraW5nIG9uIGEgbWVudSBhbmQgcHJvcG9zYWwuJm5ic3A7IFRoYW5rcyBzbyBtdWNoIGFuZCBsb29rIGZvcndhcmQgdG8gaGVhcmluZyBmcm9tIHlvdSBzb29uLjwvRElWPgo8RElWPiZuYnNwOzwvRElWPgo8RElWPkpob24gRG9lPEJSPjxCUj4tLS0gT24gPEI+V2VkLCA0LzQvMTIsIHh4eEBleGFtcGxlLmNvbSA8ST4mbHQ7eHh4QGV4YW1wbGUuY29tJmd0OzwvST48L0I+IHdyb3RlOjxCUj48L0RJVj4KPEJMT0NLUVVPVEU+PEJSPkZyb206IHh4eEBleGFtcGxlLmNvbSAmbHQ7eHh4QGV4YW1wbGUuY29tJmd0OzxCUj5TdWJqZWN0OiBZb3UndmUgZ290IGEgbmV3IGJvb2tpbmcgaW5xdWlyeSE8QlI+VG86IHh4eEB5YWhvby5jb208QlI+RGF0ZTogV2VkbmVzZGF5LCBBcHJpbCA0LCAyMDEyLCAxMDoyMyBQTTxCUj48QlI+CjxESVY+CjxESVY+CjxDRU5URVI+CjxUQUJMRT4KPFRCT0RZPgo8VFI+CjxURD4KPFRBQkxFPgo8VEJPRFk+CjxUUj4KPFREPgo8VEFCTEU+CjxUQk9EWT4KPFRSPgo8VEQ+CjxESVY+TmV3IEJvb2tpbmcgSW5xdWlyeSA8L0RJVj48L1REPgo8VEQ+CjxESVY+WW91ciBwbGFjZSBpcyB0aGUgaG9tZSBvZiBiZXNwb2tlIGRpbmluZyA8L0RJVj48L1REPjwvVFI+PC9UQk9EWT48L1RBQkxFPjwvVEQ+PC9UUj48L1RCT0RZPjwvVEFCTEU+CjxUQUJMRT4KPFRCT0RZPgo8VFI+CjxURD4KPFRBQkxFPgo8VEJPRFk+CjxUUj4KPFREPiA8L1REPjwvVFI+PC9UQk9EWT48L1RBQkxFPjwvVEQ+PC9UUj4KPFRSPgo8VEQ+CjxUQUJMRT4KPFRCT0RZPgo8VFI+CjxURD4KPFRBQkxFPgo8VEJPRFk+CjxUUj4KPFREPgo8RElWPjxCUj5Hb29kIE5ld3MhPEJSPjxCUj4KPFA+RXZlbnQgRGV0YWlsczwvRElWPkRhdGU6IEFwcmlsIDI4LCAyMDEyPEJSPkxvY2F0aW9uOiB4eHg8QlI+SGVhZGNvdW50OiA2IHRvIDg8QlI+VGFyZ2V0IEJ1ZGdldDogJDUwIHBlciBwZXJzb248QlI+PEJSPkJlc3QgRGVzY3JpcHRpb24gb2YgVGFyZ2V0IEJ1ZGdldDogSSdkIGxvdmUgdG8gaGVhciB3aGF0IHRoZSBjaGVmIHRoaW5rcyBpcyBiZXN0IGZvciBteSBldmVudCwgcHJvdmlkZWQgd2Ugc3RheSBjbG9zZSB0byB0aGlzIGJ1ZGdldCA8QlI+PEJSPkV2ZW50IERlc2NyaXB0aW9uOiBJIGFtIHdhbnRpbmcgdG8gc3VycHJpc2UgbXkgaHVzYmFuZCB3aXRoIGEgY2FzdWFsIGRpbm5lciBwYXJ0eSBpbiBvdXIgaG9tZSBpbiB4eHguIFdlIGhhdmUgYW4gYW1hemluZyBraXRjaGVuICh0aGF0IEkgZG9uJ3QgZG8ganVzdGljZSB0byBidXQgSSBiZXQgeW91IGNvdWxkISksIGFuZCBhIHJlYWxseSBuaWNlIGdhcmRlbiBmb3IgZGluaW5nLiBJIGFtIGZseWluZyBzb21lIG9mIGhpcyBiZXN0IGZyaWVuZHMgaW4gdG8gY2VsZWJyYXRlIGhpbS4gV2UgaGF2ZSBzbWFsbCBraWRzICh3aG8gd2lsbCBiZSBzbGVlcGluZyEpLCBzbyBJJ20gaG9waW5nIGZvciBhIGNhc3VhbCBidXQgcm9tYW50aWMgZGlubmVyIHBhcnR5LiA8QlI+PEJSPlZpZXcgbW9yZSBpbnF1aXJ5IGRldGFpbHMgb24geW91ciBFdmVudCBEYXNoYm9hcmQuIElmIHlvdSBsaWtlIHdoYXQgeW91IHNlZSwgcGxlYXNlIGNyZWF0ZSBhIHByb3Bvc2FsIGZvciB0aGUgZXZlbnQuIDxCUj48QlI+SWYgeW91IGRvIG5vdCBoYXZlIHRoZSB0aW1lIHRvIG1ha2UgYSBmdWxsIHByb3Bvc2FsIHJpZ2h0IG5vdywgd2UgZW5jb3VyYWdlIHlvdSB0byBhdCBsZWFzdCByZXNwb25kIHRvIHRoZSBob3N0IHdpdGggYSBxdWljayBtZXNzYWdlIHRvIGNvbmZpcm0gdGhhdCB5b3UndmUgZ290dGVuIHRoaXMgaW5xdWlyeSBhbmQgaGF2ZSBiZWd1biB0aGlua2luZyBhYm91dCB0aGUgZXZlbnQuIDxCUj48QlI+PFNUUk9ORz5Zb3UgY2FuIHJlcGx5IGRpcmVjdGx5IHRvIHRoaXMgZW1haWwgYW5kIHlvdXIgbWVzc2FnZSB3aWxsIGdvIHRvIHRoZSBob3N0IG9uIHRoZSBldmVudCBkYXNoYm9hcmQuPC9TVFJPTkc+IDxCUj48QlI+UmVtZW1iZXIsIHlvdSBoYXZlIGV4Y2x1c2l2ZSBhY2Nlc3MgdG8gdGhpcyBpbnF1aXJ5IGZvciB0aGUgbmV4dCAyNCBob3Vycy4gUGxlYXNlIG1ha2UgYSBwcm9wb3NhbCBvciBzZW5kIGEgbWVzc2FnZSB0byB0aGUgaG9zdCBpbiB0aGF0IHRpbWUuIElmIHRoZSBob3N0IGhhcyBub3QgaGVhcmQgYW55dGhpbmcgZnJvbSB5b3UgaW4gMjQgaG91cnMsIHdlIHdpbGwKIGZvcndhcmQgdGhlIGhvc3RzIGlucXVpcnkgdG8gYSBzbWFsbCBudW1iZXIgb2YgYWRkaXRpb25hbCBjaGVmcywgYW5kIHRoZXkgd2lsbCBoYXZlIHRoZSBvcHBvcnR1bml0eSB0byBtYWtlIGEgcHJvcG9zYWwuIFdlIGRvIHRoaXMgYXMgYSBjb3VydGVzeSB0byB0aGUgaG9zdHMuIDxCUj48QlI+SWYgeW91IGNhbm5vdCBhY2NlcHQgdGhpcyBib29raW5nIG9yIGRvIG5vdCB3YW50IHRvIGZvciBhbnkgcmVhc29uLCBwbGVhc2UgdGFrZSB0aGUgdGltZSB0byBkZWNsaW5lIG9uIHRoZSBFdmVudCBEYXNoYm9hcmQuIDxCUj48QlI+VGltZSB0byBnZXQgY29va2luJyA8QlI+PEJSPjwvRElWPjwvVEQ+PC9UUj48L1RCT0RZPjwvVEFCTEU+PC9URD48L1RSPjwvVEJPRFk+PC9UQUJMRT48L1REPjwvVFI+CjxUUj4KPFREPgo8VEFCTEU+CjxUQk9EWT4KPFRSPgo8VEQ+CjxUQUJMRT4KPFRCT0RZPgo8VFI+CjxURD4KPERJVj4mbmJzcDs8QSBocmVmPSJodHRwOi8vZXhhbXBsZS5jb20iPmZvbGxvdyBvbiBUd2l0dGVyPC9BPiB8IDxBIGhyZWY9Imh0dHA6Ly94eHgiPmZyaWVuZCBvbiBGYWNlYm9vazwvQT4gfCA8QQogaHJlZj0iaHR0cDovL2V4YW1wbGUuY29tIj5Gb3J3YXJkIHRvIGEgRnJpZW5kPC9BPiZndDsmbmJzcDsgPC9ESVY+PC9URD48L1RSPgo8VFI+CjxURD4KPERJVj48RU0+Q29weXJpZ2g8L0VNPiA8L0RJVj48L1REPjwvVFI+PC9UQk9EWT48L1RBQkxFPjwvVEQ+PC9UUj48L1RCT0RZPjwvVEFCTEU+PC9URD48L1RSPjwvVEJPRFk+PC9UQUJMRT48QlI+PC9URD48L1RSPjwvVEJPRFk+PC9UQUJMRT48L0NFTlRFUj48SU1HIGFsdD0iIiBzcmM9Imh0dHA6Ly9leGFtcGxlLmNvbSI+IDwvRElWPjwvRElWPjwvQkxPQ0tRVU9URT48L3RkPjwvdHI+PC90YWJsZT4K 21 | 22 | --===============6853056845739363347==-- 23 | -------------------------------------------------------------------------------- /tests/fixtures/talon/reply-separated-by-hr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Hi 5 |
6 | there 7 |
8 |
9 | Bob 10 |
11 | From: bob@example.com
12 | To: xxx@comcast.net
13 | Sent: Friday, July 22, 2011 6:20:01 PM
14 | Subject: Hello

15 |

16 | Hello 17 |

18 |
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/android.eml: -------------------------------------------------------------------------------- 1 | Content-Type: multipart/alternative; 2 | boundary="===============0934372227844987316==" 3 | MIME-Version: 1.0 4 | Date: Mon, 2 Apr 2012 18:22:10 +0400 5 | Message-Id: 6 | Subject: Re: Test 7 | From: Sergey Obykhov 8 | To: "bob@xxx.mailgun.org" 9 | 10 | --===============0934372227844987316== 11 | MIME-Version: 1.0 12 | Content-Type: text/plain; charset="utf-8" 13 | Content-Transfer-Encoding: base64 14 | 15 | SGVsbG8KMDIuMDQuMjAxMiAxNDoyMCDQv9C+0LvRjNC30L7QstCw0YLQtdC70YwgImJvYkB4eHgubWFpbGd1bi5vcmciIDwKYm9iQHh4eC5tYWlsZ3VuLm9yZz4g0L3QsNC/0LjRgdCw0Ls6Cgo+IEhpCj4KCg== 16 | 17 | --===============0934372227844987316== 18 | MIME-Version: 1.0 19 | Content-Type: text/html; charset="utf-8" 20 | Content-Transfer-Encoding: base64 21 | 22 | PHA+SGVsbG88L3A+CjxkaXYgY2xhc3M9ImdtYWlsX3F1b3RlIj4wMi4wNC4yMDEyIDE0OjIwINC/0L7Qu9GM0LfQvtCy0LDRgtC10LvRjCAmcXVvdDs8YSBocmVmPSJtYWlsdG86Ym9iQHh4eC5tYWlsZ3VuLm9yZyI+Ym9iQHh4eC5tYWlsZ3VuLm9yZzwvYT4mcXVvdDsgJmx0OzxhIGhyZWY9Im1haWx0bzpib2JAeHh4Lm1haWxndW4ub3JnIj5ib2JAeHh4Lm1haWxndW4ub3JnPC9hPiZndDsg0L3QsNC/0LjRgdCw0Ls6PGJyIHR5cGU9ImF0dHJpYnV0aW9uIj4KPGJsb2NrcXVvdGUgY2xhc3M9ImdtYWlsX3F1b3RlIiBzdHlsZT0ibWFyZ2luOjAgMCAwIC44ZXg7Ym9yZGVyLWxlZnQ6MXB4ICNjY2Mgc29saWQ7cGFkZGluZy1sZWZ0OjFleCI+SGk8YnI+CjwvYmxvY2txdW90ZT48L2Rpdj4KCg== 23 | 24 | --===============0934372227844987316==-- 25 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/aol.eml: -------------------------------------------------------------------------------- 1 | Content-Type: multipart/alternative; 2 | boundary="===============7429987408351918371==" 3 | MIME-Version: 1.0 4 | To: bob@example.com 5 | Subject: Re: Test 6 | From: Megan Odin 7 | Message-Id: <8CEDEEFBEF4733B-1E5C-73DF@webmail-d070.sysops.aol.com> 8 | Date: Mon, 2 Apr 2012 09:57:58 -0400 (EDT) 9 | 10 | --===============7429987408351918371== 11 | Content-Type: text/plain; charset="us-ascii" 12 | MIME-Version: 1.0 13 | Content-Transfer-Encoding: 7bit 14 | 15 | Hello 16 | 17 | 18 | 19 | -----Original Message----- 20 | From: bob 21 | To: xxx ; xxx ; xxx ; xxx ; xxx ; xxx 22 | Sent: Mon, Apr 2, 2012 5:49 pm 23 | Subject: Test 24 | 25 | 26 | Hi 27 | 28 | 29 | 30 | --===============7429987408351918371== 31 | Content-Type: text/html; charset="us-ascii" 32 | MIME-Version: 1.0 33 | Content-Transfer-Encoding: 7bit 34 | 35 | Hello
36 | 37 |
38 |
39 | 40 |
-----Original Message-----
41 | From: bob <bob@example.com>
42 | To: xxx <xxx@gmail.com>; xxx <xxx@hotmail.com>; xxx <xxx@yahoo.com>; xxx <xxx@aol.com>; xxx <xxx@comcast.net>; xxx <xxx@nyc.rr.com>
43 | Sent: Mon, Apr 2, 2012 5:49 pm
44 | Subject: Test
45 | 46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | 56 |
Hi
57 | 
58 |
59 | 60 | 61 | 62 | 63 |
64 |
65 | --===============7429987408351918371==-- 66 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/apple_mail.eml: -------------------------------------------------------------------------------- 1 | Content-Type: text/plain; charset=iso-8859-1 2 | Mime-Version: 1.0 (Apple Message framework v1257) 3 | Subject: Re: Test 4 | From: xxx 5 | Date: Tue, 3 Apr 2012 16:55:26 +0400 6 | Content-Transfer-Encoding: 7bit 7 | Message-Id: <9A1EA6A5-4FD3-4AD0-8DFD-2420E670DB53@gmail.com> 8 | To: bob 9 | X-Mailer: Apple Mail (2.1257) 10 | 11 | Hello 12 | 13 | On Apr 3, 2012, at 4:19 PM, bob wrote: 14 | 15 | > Hi 16 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/apple_mail_2.eml: -------------------------------------------------------------------------------- 1 | Content-Type: text/plain; 2 | charset=us-ascii 3 | Mime-Version: 1.0 (Mac OS X Mail 8.2 \(2104\)) 4 | Subject: Re: Hello there 5 | X-Universally-Unique-Identifier: 85B1075D-5841-46A9-8565-FCB287A93AC4 6 | From: Adam Renberg 7 | In-Reply-To: 8 | Date: Sat, 22 Aug 2015 19:22:20 +0200 9 | Content-Transfer-Encoding: 7bit 10 | X-Smtp-Server: smtp.gmail.com:adam@tictail.com 11 | Message-Id: <68001B29-8EA4-444C-A894-0537D2CA5208@tictail.com> 12 | References: 13 | To: Adam Renberg 14 | 15 | Hello 16 | > On 22 Aug 2015, at 19:21, Adam Renberg wrote: 17 | > 18 | > Hi there! 19 | 20 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/comcast.eml: -------------------------------------------------------------------------------- 1 | Content-Type: multipart/alternative; 2 | boundary="===============3552566137977633461==" 3 | MIME-Version: 1.0 4 | Date: Mon, 2 Apr 2012 13:56:12 +0000 (UTC) 5 | From: xxx@comcast.net 6 | To: bob@xxx.mailgun.org 7 | Message-Id: <650787974.741595.1333374972389.JavaMail.root@sz0152a.westchester.pa.mail.comcast.net> 8 | Subject: Re: Test 9 | X-Mailer: Zimbra 6.0.13_GA_2944 (ZimbraWebClient - SAF3 (Linux)/6.0.13_GA_2944) 10 | 11 | --===============3552566137977633461== 12 | MIME-Version: 1.0 13 | Content-Type: text/plain; charset="us-ascii" 14 | Content-Transfer-Encoding: 7bit 15 | 16 | Hello 17 | 18 | ----- Original Message ----- 19 | From: bob@xxx.mailgun.org 20 | To: xxx@gmail.com, xxx@hotmail.com, xxx@yahoo.com, xxx@aol.com, xxx@comcast.net, lsloan6@nyc.rr.com 21 | Sent: Monday, April 2, 2012 5:44:22 PM 22 | Subject: Test 23 | 24 | Hi 25 | 26 | --===============3552566137977633461== 27 | MIME-Version: 1.0 28 | Content-Type: text/html; charset="us-ascii" 29 | Content-Transfer-Encoding: 7bit 30 | 31 |
Hello


From: bob@xxx.mailgun.org
To: xxx@gmail.com, xxx@hotmail.com, xxx@yahoo.com, xxx@aol.com, xxx@comcast.net, lsloan6@nyc.rr.com
Sent: Monday, April 2, 2012 5:44:22 PM
Subject: Test

Hi
32 | --===============3552566137977633461==-- 33 | 34 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/gmail.eml: -------------------------------------------------------------------------------- 1 | Content-Type: multipart/alternative; 2 | boundary="===============3455449757443551301==" 3 | MIME-Version: 1.0 4 | Date: Mon, 2 Apr 2012 20:21:52 +0400 5 | Message-Id: 6 | Subject: Re: Test 7 | From: Megan One 8 | To: bob@example.com 9 | 10 | --===============3455449757443551301== 11 | MIME-Version: 1.0 12 | Content-Type: text/plain; charset="us-ascii" 13 | Content-Transfer-Encoding: 7bit 14 | 15 | Hello 16 | 17 | On Mon, Apr 2, 2012 at 6:26 PM, Megan One wrote: 18 | 19 | > Hi 20 | 21 | --===============3455449757443551301== 22 | MIME-Version: 1.0 23 | Content-Type: text/html; charset="us-ascii" 24 | Content-Transfer-Encoding: 7bit 25 | 26 | Hello

On Mon, Apr 2, 2012 at 6:26 PM, Megan One <xxx@gmail.com> wrote:
27 | Hi 28 | 29 |

30 | 31 | --===============3455449757443551301==-- 32 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/hotmail.eml: -------------------------------------------------------------------------------- 1 | Content-Type: multipart/alternative; 2 | boundary="===============5499446768842282638==" 3 | MIME-Version: 1.0 4 | Message-Id: 5 | From: Alexey Q 6 | To: 7 | Subject: RE: Test 8 | Date: Mon, 2 Apr 2012 21:47:37 +0800 9 | X-Originalarrivaltime: 02 Apr 2012 13:47:37.0935 (UTC) 10 | FILETIME=[2A6C0DF0:01CD10D7] 11 | 12 | --===============5499446768842282638== 13 | MIME-Version: 1.0 14 | Content-Type: text/plain; charset="us-ascii" 15 | Content-Transfer-Encoding: 7bit 16 | 17 | 18 | Hello 19 | 20 | > Subject: Test 21 | > From: bob@xxx.mailgun.org 22 | > To: xxx@gmail.com; xxx@hotmail.com; xxx@yahoo.com; xxx@aol.com; xxx@comcast.net; xxx@nyc.rr.com 23 | > Date: Mon, 2 Apr 2012 17:44:22 +0400 24 | > 25 | > Hi 26 | 27 | --===============5499446768842282638== 28 | MIME-Version: 1.0 29 | Content-Type: text/html; charset="us-ascii" 30 | Content-Transfer-Encoding: 7bit 31 | 32 | 33 | 34 | 46 |
47 | Hello

> Subject: Test
> From: bob@xxx.mailgun.org
> To: xxx@gmail.com; xxx@hotmail.com; xxx@yahoo.com; xxx@aol.com; xxx@comcast.net; xxx@nyc.rr.com
> Date: Mon, 2 Apr 2012 17:44:22 +0400
>
> Hi
48 | 49 | 50 | --===============5499446768842282638==-- 51 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/iphone.eml: -------------------------------------------------------------------------------- 1 | Subject: Re: Test 2 | From: xxx 3 | Content-Type: text/plain; 4 | charset=us-ascii 5 | X-Mailer: iPhone Mail (9B176) 6 | Message-Id: <06C90B12-13B9-4C5F-A9EF-4A809D94C078@gmail.com> 7 | Date: Tue, 3 Apr 2012 16:23:59 +0400 8 | To: bob 9 | Content-Transfer-Encoding: quoted-printable 10 | Mime-Version: 1.0 (1.0) 11 | 12 | Hello 13 | 14 | Sent from my iPhone 15 | 16 | On Apr 3, 2012, at 4:19 PM, bob wr= 17 | ote: 18 | 19 | > Hi 20 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/iphone_reply_text: -------------------------------------------------------------------------------- 1 | Hello 2 | 3 | Sent from my iPhone 4 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/outlook.eml: -------------------------------------------------------------------------------- 1 | Subject: Test 2 | From: me@example.com 3 | To: you@example.com 4 | MIME-Version: 1.0 5 | Content-Type: multipart/alternative; boundary=0016364c440b2e8b63049acd5370 6 | X-Mailgun-Tag: tag 7 | X-Mailgun-Mailing-List-Id: 1q 8 | 9 | --0016364c440b2e8b63049acd5370 10 | Content-Type: text/plain; charset=ISO-8859-1 11 | 12 | Hello 13 | 14 | From: xxx@xxx.mailgun.org [mailto:xxx@xxx.mailgun.org] 15 | Sent: March-09-12 4:22 PM 16 | To: Dan Le 17 | Subject: The manager has commented on your Loop 18 | 19 | Hi dan.le@example.com, 20 | 21 | The manager's comment: 22 | "Hello Allan! Did you ask for some MIME? " 23 | 24 | Loop details: 25 | 26 | xxx at Dan 27 | I'm not happy 28 | "" 29 | 30 | Your Loop is here. 31 | 32 | We will be in touch again with any further updates, 33 | 34 | xxx 35 | 36 | If you did not sign up to receive emails from us you can use the link below to unsubscribe. We apologize for any inconvenience. 37 | 38 | Unsubscribe 39 | 40 | --0016364c440b2e8b63049acd5370 41 | Content-Type: text/html; charset=ISO-8859-1 42 | 43 |

Allo! Follow up MIME!

 

From: xxx@xxx.mailgun.org [mailto:xxx@xxx.mailgun.org]
Sent: March-09-12 4:22 PM
To: Dan Le
Subject: The manager has commented on your Loop

 

Hi dan.le@example.com,

The manager's comment:
"Hello Allan! Did you ask for some MIME? "

Loop details:

xxx at Dan
I'm not happy
""

Your Loop is here.

We will be in touch again with any further updates,

xxx

If you did not sign up to receive emails from us you can use the link below to unsubscribe. We apologize for any inconvenience.

Unsubscribe

84 | 85 | --0016364c440b2e8b63049acd5370-- -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/sparrow.eml: -------------------------------------------------------------------------------- 1 | Date: Tue, 3 Apr 2012 16:58:35 +0400 2 | From: xxx 3 | To: bob 4 | Message-ID: <5BB86EF4B6E24E4C9DA4BBEF59DA9809@gmail.com> 5 | Subject: Re: Test 6 | X-Mailer: sparrow 1.5 (build 1043) 7 | MIME-Version: 1.0 8 | Content-Type: multipart/alternative; boundary="4f7af3fb_749abb43_300" 9 | 10 | --4f7af3fb_749abb43_300 11 | Content-Type: text/plain; charset="utf-8" 12 | Content-Transfer-Encoding: 7bit 13 | Content-Disposition: inline 14 | 15 | Hello 16 | 17 | -- 18 | xxx 19 | Sent with Sparrow (http://www.sparrowmailapp.com/?sig) 20 | 21 | 22 | On Tuesday, April 3, 2012 at 4:55 PM, xxx wrote: 23 | 24 | > Hello 25 | > 26 | > On Apr 3, 2012, at 4:19 PM, bob wrote: 27 | > 28 | > > Hi 29 | 30 | 31 | --4f7af3fb_749abb43_300 32 | Content-Type: text/html; charset="utf-8" 33 | Content-Transfer-Encoding: quoted-printable 34 | Content-Disposition: inline 35 | 36 | 37 |
38 | Hello 39 |
40 |

-- 
xx= 41 | x
Sent with Sparrow

43 | =20 44 |

On Tuesday, April 3, 2= 45 | 012 at 4:55 PM, xxx wrote:

46 |
48 |
Hello

O= 49 | n Apr 3, 2012, at 4:19 PM, bob wrote:

Hi
51 | =20 52 | =20 53 | =20 54 | =20 55 | 56 | =20 57 |
58 |
59 |
60 | 61 | --4f7af3fb_749abb43_300-- 62 | -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/sparrow_reply_text: -------------------------------------------------------------------------------- 1 | Hello 2 | 3 | -- 4 | xxx 5 | Sent with Sparrow (http://www.sparrowmailapp.com/?sig) -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/thunderbird.eml: -------------------------------------------------------------------------------- 1 | MIME-Version: 1.0 2 | Message-Id: <4F79B73C.9030506@xxx.mailgun.org> 3 | Date: Mon, 02 Apr 2012 18:27:08 +0400 4 | From: bob 5 | User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; 6 | rv:1.9.2.28) Gecko/20120313 Thunderbird/3.1.20 7 | To: Megan One 8 | Subject: Re: Test 9 | Sender: bob@xxx.mailgun.org 10 | Content-Type: text/plain; charset="us-ascii"; format="flowed" 11 | Content-Transfer-Encoding: 7bit 12 | 13 | On 04/02/2012 06:26 PM, Megan One wrote: 14 | > Hi 15 | 16 | 17 | Hello -------------------------------------------------------------------------------- /tests/fixtures/talon/standard_replies/yahoo.eml: -------------------------------------------------------------------------------- 1 | Content-Type: text/plain; charset="us-ascii" 2 | MIME-Version: 1.0 3 | X-Mailer: YahooMailWebService/0.8.117.340979 4 | Message-Id: <1333374330.68772.YahooMailNeo@web114411.mail.gq1.yahoo.com> 5 | Date: Mon, 2 Apr 2012 06:45:30 -0700 (PDT) 6 | From: Alex Q 7 | Subject: Re: Test 8 | To: "bob@xxx.mailgun.org" 9 | In-Reply-To: <1333374262.7063.15.camel@mg5> 10 | Content-Transfer-Encoding: 7bit 11 | 12 | Hello 13 | 14 | 15 | ----- Original Message ----- 16 | From: "bob@xxx.mailgun.org" 17 | To: xxx@gmail.com; xxx@hotmail.com; xxx@yahoo.com; xxx@aol.com; xxx@comcast.net; xxx@nyc.rr.com 18 | Cc: 19 | Sent: Monday, April 2, 2012 5:44 PM 20 | Subject: Test 21 | 22 | Hi 23 | -------------------------------------------------------------------------------- /tests/regexp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const path = require("path"); 5 | const async = require("front-async"); 6 | const assert = require("chai").assert; 7 | const utils = require("./utils"); 8 | const { isStartOfForwardedMessage } = require("../bin/Talon").utils; 9 | 10 | describe("TalonJS Regexp", function () { 11 | 12 | describe("ForwardRegexp", function () { 13 | 14 | const matchingLines = [ 15 | "----- Forwarded message -----\n", // Gmail 16 | "Begin forwarded message:" // iOS 17 | ]; 18 | 19 | it("should match the following lines", function () { 20 | for (const line of matchingLines) { 21 | assert.isTrue(isStartOfForwardedMessage(line), line); 22 | } 23 | }); 24 | 25 | const nonMatchingLines = [ 26 | "Sally forwarded this message", 27 | "See forwarded message below:" 28 | ]; 29 | 30 | it("should not match the following lines", function () { 31 | for (const line of nonMatchingLines) 32 | assert.isFalse(isStartOfForwardedMessage(line), line); 33 | }); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /tests/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const fs = require("fs"); 4 | const MailParser = require("mailparser").MailParser; 5 | const Cheerio = require("cheerio"); 6 | const XmlDom = require("xmldom"); 7 | 8 | const talonUtils = require("../bin/Talon").utils; 9 | 10 | const xmlDomParser = new XmlDom.DOMParser(); 11 | 12 | exports.parseEmlText = function (filename, done) { 13 | // Parse the specified file. 14 | const mailparser = new MailParser(); 15 | 16 | // When the parser is done, return the text. 17 | mailparser.on("end", function (email) { 18 | return !email 19 | ? done("Couldn't open the email file.") 20 | : done(null, email.text); 21 | }); 22 | 23 | // Pipe the file in the parser. 24 | fs.createReadStream(filename).pipe(mailparser); 25 | }; 26 | 27 | exports.parseEmlHtml = function (filename, done) { 28 | // Parse the specified file. 29 | const mailparser = new MailParser(); 30 | 31 | // When the parser is done, return the text. 32 | mailparser.on("end", function (email) { 33 | return !email 34 | ? done("Couldn't open the email file.") 35 | : done(null, email.html); 36 | }); 37 | 38 | // Pipe the file in the parser. 39 | fs.createReadStream(filename).pipe(mailparser); 40 | }; 41 | 42 | exports.tryReadFile = function (filename, done) { 43 | return fs.readFile(filename, "utf-8", function (err, data) { 44 | return err ? done() : done(null, data); 45 | }); 46 | }; 47 | 48 | exports.htmlToText = function (html) { 49 | const document = Cheerio.load(html); 50 | const xmlDocument = xmlDomParser.parseFromString(document.xml()); 51 | 52 | return talonUtils.elementToText(xmlDocument); 53 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "module": "commonjs", 5 | "noErrorTruncation": false, 6 | "outDir": "bin", 7 | "preserveConstEnums": true, 8 | "removeComments": true, 9 | "sourceMap": true, 10 | "strict": true, 11 | "strictNullChecks": false, 12 | "target": "es2018", 13 | "typeRoots": [ 14 | "./node_modules/@types", 15 | "./local_types" 16 | ] 17 | }, 18 | "include": [ 19 | "src/**/*", 20 | "node_modules/retyped-xpath-tsd-ambient/xpath.d.ts" 21 | ] 22 | } 23 | --------------------------------------------------------------------------------