diff --git a/package-lock.json b/package-lock.json index c7a3f48..2270dc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,8 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dayjs": "^1.11.7", - "imapflow": "^1.0.134", + "imap": "^0.8.19", + "mailparser-mit": "^1.0.0", "puppeteer": "^20.4.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", @@ -34,8 +35,8 @@ "@types/cron": "^2.0.1", "@types/express": "^4.17.13", "@types/imap": "^0.8.37", - "@types/imapflow": "^1.0.13", "@types/jest": "29.5.1", + "@types/mailparser-mit": "^1.0.1", "@types/node": "18.16.12", "@types/puppeteer": "^7.0.4", "@types/supertest": "^2.0.11", @@ -45,7 +46,6 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-simple-import-sort": "^10.0.0", - "imap": "^0.8.19", "jest": "29.5.0", "prettier": "^2.3.2", "source-map-support": "^0.5.20", @@ -2596,15 +2596,6 @@ "@types/node": "*" } }, - "node_modules/@types/imapflow": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/@types/imapflow/-/imapflow-1.0.13.tgz", - "integrity": "sha512-TD8h02jSTDsXTE79HL0cSkjrU/ufXmSIsZv96O2sNBDA1VluqzKkjV2LrXSPIuXE1tn7JhND7o0iyP4r7haNFw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -2650,6 +2641,15 @@ "integrity": "sha512-uKRI5QORDnrGFYgcdAVnHvEIvEZ8noTpP/Bg+HeUzZghwinDlIS87DEenV5r1YoOF9G4x600YsUXLWZ19rmTmg==", "dev": true }, + "node_modules/@types/mailparser-mit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/mailparser-mit/-/mailparser-mit-1.0.1.tgz", + "integrity": "sha512-OKfkaDuyIOTixMDv5a3ypKXPFb2oY3NVKUlLrhQGkqFh6VwiE1sfzGnKCiMPKcvBEUwi7sqyZpAzcTMP6zxqJw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", @@ -3152,17 +3152,6 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3211,6 +3200,11 @@ "node": ">=0.4.0" } }, + "node_modules/addressparser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz", + "integrity": "sha512-aQX7AISOMM7HFE0iZ3+YnD07oIeJqWGVnJ+ZIKaBZAk03ftmVYVqsGas/rbXKR21n4D/hKCSHypvcyOkds/xzg==" + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -3460,14 +3454,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/babel-jest": { "version": "29.5.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", @@ -4797,14 +4783,6 @@ "iconv-lite": "^0.6.2" } }, - "node_modules/encoding-japanese": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz", - "integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==", - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -5361,14 +5339,6 @@ "node": ">= 0.6" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -5544,6 +5514,11 @@ "node": ">= 0.8" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -5636,14 +5611,6 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, - "node_modules/fast-redact": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.2.0.tgz", - "integrity": "sha512-zaTadChr+NekyzallAMXATXLOR8MNx3zqpZ0MUF2aGf4EathnG0f32VLODNlY8IuGY3HoRO2L6/6fSzNsLaHIw==", - "engines": { - "node": ">=6" - } - }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -6337,6 +6304,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -6376,7 +6344,6 @@ "version": "0.8.19", "resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz", "integrity": "sha512-z5DxEA1uRnZG73UcPA4ES5NSCGnPuuouUx43OPX7KZx1yzq3N8/vx2mtXEShT5inxB3pRgnfG1hijfu7XN2YMw==", - "dev": true, "dependencies": { "readable-stream": "1.1.x", "utf7": ">=1.0.2" @@ -6388,14 +6355,12 @@ "node_modules/imap/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" }, "node_modules/imap/node_modules/readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -6406,24 +6371,7 @@ "node_modules/imap/node_modules/string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, - "node_modules/imapflow": { - "version": "1.0.134", - "resolved": "https://registry.npmjs.org/imapflow/-/imapflow-1.0.134.tgz", - "integrity": "sha512-giJ9Byi89fzt5T7WHdroZonOoSQ9k7Bi9cSmfRT6zGC/v6/gKFnNG0xyoYyxpiKsJvYRImUsG6MBEKVdlbp2Eg==", - "dependencies": { - "encoding-japanese": "2.0.0", - "iconv-lite": "0.6.3", - "libbase64": "1.2.1", - "libmime": "5.2.1", - "libqp": "2.0.1", - "mailsplit": "5.4.0", - "nodemailer": "6.9.4", - "pino": "8.14.1", - "socks": "2.7.1" - } + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -8576,32 +8524,11 @@ "node": ">= 0.8.0" } }, - "node_modules/libbase64": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz", - "integrity": "sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==" - }, - "node_modules/libmime": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.1.tgz", - "integrity": "sha512-A0z9O4+5q+ZTj7QwNe/Juy1KARNb4WaviO4mYeFC4b8dBT2EEqK2pkM+GC8MVnkOjqhl5nYQxRgnPYRRTNmuSQ==", - "dependencies": { - "encoding-japanese": "2.0.0", - "iconv-lite": "0.6.3", - "libbase64": "1.2.1", - "libqp": "2.0.1" - } - }, "node_modules/libphonenumber-js": { "version": "1.10.30", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.30.tgz", "integrity": "sha512-PLGc+xfrQrkya/YK2/5X+bPpxRmyJBHM+xxz9krUdSgk4Vs2ZwxX5/Ow0lv3r9PDlDtNWb4u+it8MY5rZ0IyGw==" }, - "node_modules/libqp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.0.1.tgz", - "integrity": "sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==" - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8774,25 +8701,26 @@ "node": ">=12" } }, - "node_modules/mailsplit": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.0.tgz", - "integrity": "sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==", + "node_modules/mailparser-mit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mailparser-mit/-/mailparser-mit-1.0.0.tgz", + "integrity": "sha512-sckRITNb3VCT1sQ275g47MAN786pQ5lU20bLY5f794dF/ARGzuVATQ64gO13FOw8jayjFT10e5ttsripKGGXcw==", "dependencies": { - "libbase64": "1.2.1", - "libmime": "5.2.0", - "libqp": "2.0.1" + "addressparser": "^1.0.1", + "iconv-lite": "~0.4.24", + "mime": "^1.6.0", + "uue": "^3.1.0" } }, - "node_modules/mailsplit/node_modules/libmime": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.0.tgz", - "integrity": "sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==", + "node_modules/mailparser-mit/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dependencies": { - "encoding-japanese": "2.0.0", - "iconv-lite": "0.6.3", - "libbase64": "1.2.1", - "libqp": "2.0.1" + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/make-dir": { @@ -9331,14 +9259,6 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz", "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==" }, - "node_modules/nodemailer": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz", - "integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -9399,11 +9319,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/on-exit-leak-free": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", - "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -9846,79 +9761,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pino": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/pino/-/pino-8.14.1.tgz", - "integrity": "sha512-8LYNv7BKWXSfS+k6oEc6occy5La+q2sPwU3q2ljTX5AZk7v+5kND2o5W794FyRaqha6DJajmkNRsWtPpFyMUdw==", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.1.1", - "on-exit-leak-free": "^2.1.0", - "pino-abstract-transport": "v1.0.0", - "pino-std-serializers": "^6.0.0", - "process-warning": "^2.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.2.0", - "safe-stable-stringify": "^2.3.1", - "sonic-boom": "^3.1.0", - "thread-stream": "^2.0.0" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", - "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", - "dependencies": { - "readable-stream": "^4.0.0", - "split2": "^4.0.0" - } - }, - "node_modules/pino-abstract-transport/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/pino-abstract-transport/node_modules/readable-stream": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", - "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", - "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" - }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -10010,24 +9852,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "node_modules/process-warning": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", - "integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==" - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -10261,11 +10090,6 @@ } ] }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10337,14 +10161,6 @@ "node": ">=8.10.0" } }, - "node_modules/real-require": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", - "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "engines": { - "node": ">= 12.13.0" - } - }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -10571,14 +10387,6 @@ } ] }, - "node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "engines": { - "node": ">=10" - } - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -10798,14 +10606,6 @@ "node": ">= 10" } }, - "node_modules/sonic-boom": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", - "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10823,14 +10623,6 @@ "source-map": "^0.6.0" } }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "engines": { - "node": ">= 10.x" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11259,14 +11051,6 @@ "node": ">=0.8" } }, - "node_modules/thread-stream": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.3.0.tgz", - "integrity": "sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==", - "dependencies": { - "real-require": "^0.2.0" - } - }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -12063,7 +11847,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", "integrity": "sha512-qQrPtYLLLl12NF4DrM9CvfkxkYI97xOb5dsnGZHE3teFr0tWiEZ9UdgMPczv24vl708cYMpe6mGXGHrotIp3Bw==", - "dev": true, "dependencies": { "semver": "~5.3.0" } @@ -12072,7 +11855,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==", - "dev": true, "bin": { "semver": "bin/semver" } @@ -12090,6 +11872,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uue": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/uue/-/uue-3.1.2.tgz", + "integrity": "sha512-axKLXVqwtdI/czrjG0X8hyV1KLgeWx8F4KvSbvVCnS+RUvsQMGRjx0kfuZDXXqj0LYvVJmx3B9kWlKtEdRrJLg==", + "dependencies": { + "escape-string-regexp": "~1.0.5", + "extend": "~3.0.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index 35960d9..f497b0b 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "class-validator": "^0.14.0", "dayjs": "^1.11.7", "imap": "^0.8.19", + "mailparser-mit": "^1.0.0", "puppeteer": "^20.4.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0", @@ -51,6 +52,7 @@ "@types/express": "^4.17.13", "@types/imap": "^0.8.37", "@types/jest": "29.5.1", + "@types/mailparser-mit": "^1.0.1", "@types/node": "18.16.12", "@types/puppeteer": "^7.0.4", "@types/supertest": "^2.0.11", diff --git a/src/app.module.ts b/src/app.module.ts index 4fc4788..68534dc 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,7 @@ import { LoggerModule } from './logger/module' import { RecurringReservationsModule } from './recurringReservations/module' import { ReservationsModule } from './reservations/module' import { RunnerModule } from './runner/module' +import { WaitingListModule } from './waitingList/module' @Module({ imports: [ @@ -48,6 +49,7 @@ import { RunnerModule } from './runner/module' RunnerModule, LoggerModule, EmailModule, + WaitingListModule, ], }) export class AppModule implements NestModule { diff --git a/src/email/client.ts b/src/email/client.ts index bc19db7..574fbf7 100644 --- a/src/email/client.ts +++ b/src/email/client.ts @@ -1,9 +1,8 @@ import { Inject, Injectable } from '@nestjs/common' import { ConfigService } from '@nestjs/config' -import { Dayjs } from 'dayjs' import * as Imap from 'imap' +import { MailParser, ParsedEmail } from 'mailparser-mit' -import dayjs from '../common/dayjs' import { LoggerService } from '../logger/service' import { Email } from './types' @@ -59,12 +58,12 @@ export class EmailClient { if (current > max) { throw Error(`Max attempts reached for ${label}`) } - this.loggerService.log(`Attempting ${label} [${current} / ${max}]`) + this.loggerService.debug(`Attempting ${label} [${current} / ${max}]`) try { fn() } catch (error: unknown) { - this.loggerService.warn( + this.loggerService.debug( `Attempting ${label} hit error at attempt ${current}`, ) setTimeout( @@ -85,6 +84,10 @@ export class EmailClient { messages: { total: totalMessages, new: newMessages }, } = mailbox + this.loggerService.debug( + `Total messages: ${totalMessages}; New messages: ${newMessages}; Received messages: ${numMessages}`, + ) + if (newMessages > 0) { const startingMessage = totalMessages - newMessages + 1 // starting message is inclusive const emails = await this.fetchMails(`${startingMessage}`, '*') @@ -132,47 +135,36 @@ export class EmailClient { }) } - private async generateEmailBody( - stream: NodeJS.ReadableStream, - ): Promise { - return new Promise((res, rej) => { - const bufferData: any[] = [] - stream.on('data', (chunk) => { - bufferData.push(chunk) - }) - stream.on('end', () => { - res(Buffer.concat(bufferData)) - }) - stream.on('error', (error: Error) => rej(error)) - }) - } - private async generateEmail(msg: Imap.ImapMessage): Promise { return new Promise((res, rej) => { - let id: number - let date: Dayjs - let bodyPromise: Promise + const mailParser = new MailParser({ defaultCharset: 'utf-8' }) + let id: string msg.on('body', async (stream) => { try { - bodyPromise = this.generateEmailBody(stream) + stream.pipe(mailParser) } catch (error: unknown) { rej(error) } }) msg.on('attributes', (attr) => { - id = attr.uid - date = dayjs(attr.date) + id = `${attr.uid}` }) msg.on('error', (error: Error) => rej(error)) - msg.on('end', async () => { - const body = (await bodyPromise).toString() + mailParser.on('error', (error: Error) => rej(error)) + + mailParser.on('end', (mail: ParsedEmail) => { + const { from = [], subject = '', text = '' } = mail + const fromString = from + .map(({ address, name }) => `${name} <${address}>`) + .join(';') res({ id, - date, - body, + from: fromString, + subject, + text, }) }) }) @@ -186,7 +178,7 @@ export class EmailClient { `${startingMessageId}:${endingMessageId}`, { bodies: '', - struct: true, + markSeen: true, }, ) @@ -220,6 +212,10 @@ export class EmailClient { this.loggerService.debug('email client end') this.setStatus(EmailClientStatus.NotReady) }) + + this.imapClient.on('error', (error: Error) => { + this.loggerService.error(`Error with imap client ${error.message}`) + }) } public addCustomListener( diff --git a/src/email/config.ts b/src/email/config.ts new file mode 100644 index 0000000..4a95083 --- /dev/null +++ b/src/email/config.ts @@ -0,0 +1 @@ +export const EMAILS_QUEUE_NAME = 'emails' diff --git a/src/email/module.ts b/src/email/module.ts index 82f4337..2416009 100644 --- a/src/email/module.ts +++ b/src/email/module.ts @@ -1,12 +1,17 @@ +import { BullModule } from '@nestjs/bull' import { Module } from '@nestjs/common' import { LoggerModule } from '../logger/module' import { EmailClient } from './client' +import { EMAILS_QUEUE_NAME } from './config' import { EmailProvider } from './provider' @Module({ + imports: [ + LoggerModule, + BullModule.registerQueue({ name: EMAILS_QUEUE_NAME }), + ], providers: [EmailClient, EmailProvider], - imports: [LoggerModule], exports: [EmailClient, EmailProvider], }) export class EmailModule {} diff --git a/src/email/provider.ts b/src/email/provider.ts index 178428f..c3d4480 100644 --- a/src/email/provider.ts +++ b/src/email/provider.ts @@ -1,8 +1,9 @@ +import { InjectQueue } from '@nestjs/bull' import { Inject, Injectable } from '@nestjs/common' -import { Box } from 'imap' +import { Queue } from 'bull' -import { LoggerService } from '../logger/service' import { EmailClient } from './client' +import { EMAILS_QUEUE_NAME } from './config' import { Email } from './types' @Injectable() @@ -11,14 +12,16 @@ export class EmailProvider { @Inject(EmailClient) private readonly emailClient: EmailClient, - @Inject(LoggerService) - private readonly loggerService: LoggerService, + @InjectQueue(EMAILS_QUEUE_NAME) + private readonly emailsQueue: Queue, ) { this.registerEmailListener() } private async handleReceivedEmails(emails: Email[]) { - this.loggerService.debug(JSON.stringify(emails)) + this.emailsQueue.addBulk( + emails.map((email) => ({ name: email.id, data: email })), + ) } public registerEmailListener() { diff --git a/src/email/types.ts b/src/email/types.ts index 1bac111..2c1add7 100644 --- a/src/email/types.ts +++ b/src/email/types.ts @@ -1,7 +1,6 @@ -import { Dayjs } from 'dayjs' - export interface Email { - id: number - date: Dayjs - body: string + id: string + from: string + subject: string + text: string } diff --git a/src/reservations/service.ts b/src/reservations/service.ts index 6d8ba74..ff70512 100644 --- a/src/reservations/service.ts +++ b/src/reservations/service.ts @@ -27,6 +27,14 @@ export class ReservationsService { .getMany() } + getByDateOnWaitingList(date = dayjs()) { + return this.reservationsRepository + .createQueryBuilder() + .where(`DATE(dateRangeStart, '-7 day') = DATE(:date)`, { date }) + .andWhere('waitListed = true') + .getMany() + } + create(reservation: Reservation) { return this.reservationsRepository.save(reservation) } diff --git a/src/waitingList/module.ts b/src/waitingList/module.ts new file mode 100644 index 0000000..83ffcbf --- /dev/null +++ b/src/waitingList/module.ts @@ -0,0 +1,20 @@ +import { BullModule } from '@nestjs/bull' +import { Module } from '@nestjs/common' +import { ReservationsModule } from 'src/reservations/module' + +import { EMAILS_QUEUE_NAME } from '../email/config' +import { LoggerModule } from '../logger/module' +import { RESERVATIONS_QUEUE_NAME } from '../reservations/config' +import { WaitingListService } from './service' + +@Module({ + imports: [ + LoggerModule, + ReservationsModule, + BullModule.registerQueue({ name: EMAILS_QUEUE_NAME }), + BullModule.registerQueue({ name: RESERVATIONS_QUEUE_NAME }), + ], + providers: [WaitingListService], + exports: [WaitingListService], +}) +export class WaitingListModule {} diff --git a/src/waitingList/service.ts b/src/waitingList/service.ts new file mode 100644 index 0000000..30b4b21 --- /dev/null +++ b/src/waitingList/service.ts @@ -0,0 +1,116 @@ +import { InjectQueue, Process, Processor } from '@nestjs/bull' +import { Inject } from '@nestjs/common' +import { Job, Queue } from 'bull' +import { ReservationsService } from 'src/reservations/service' + +import dayjs from '../common/dayjs' +import { EMAILS_QUEUE_NAME } from '../email/config' +import { Email } from '../email/types' +import { LoggerService } from '../logger/service' +import { RESERVATIONS_QUEUE_NAME } from '../reservations/config' +import { WaitingListDetails } from './types' + +const EMAIL_SUBJECT_REGEX = new RegExp( + /^personal waitinglist reservation free at/i, +) +const EMAIL_ADDRESS = 'Squash City ' +const EMAIL_DATE_REGEX = new RegExp( + /^Datum: ([0-9]{1,2}-[0-9]{1,2}-[0,9]{4})$/im, +) +const EMAIL_START_TIME_REGEX = new RegExp( + /^Begintijd: ([0-9]{1,2}:[0-9]{1,2})$/im, +) +const EMAIL_END_TIME_REGEX = new RegExp(/^Eindtijd: ([0-9]{1,2}:[0-9]{1,2})$/im) + +@Processor(EMAILS_QUEUE_NAME) +export class WaitingListService { + constructor( + @InjectQueue(RESERVATIONS_QUEUE_NAME) + private readonly reservationsQueue: Queue, + + @Inject(ReservationsService) + private readonly reservationsService: ReservationsService, + + @Inject(LoggerService) + private readonly loggerService: LoggerService, + ) {} + + @Process() + async processEmail(job: Job) { + const { data: email } = job + this.loggerService.log('Handling email', { + id: email.id, + from: email.from, + subject: email.subject, + }) + + if (!this.isRelevantEmail) return + + await this.handleWaitingListEmail(email) + } + + private async handleWaitingListEmail(email: Email) { + const { date, startTime } = this.getWaitingListDetails(email) + const dateRangeStart = dayjs(`${date} ${startTime}`) + const reservations = await this.reservationsService.getByDateOnWaitingList( + dateRangeStart, + ) + + if (reservations.length) { + this.loggerService.error('Found no reservations on waiting list') + return + } + + this.loggerService.log( + `Found ${reservations.length} reservations on waiting list`, + ) + + await this.reservationsQueue.addBulk(reservations.map((r) => ({ data: r }))) + } + + private getWaitingListDetails(email: Email): WaitingListDetails { + const dateResult = EMAIL_DATE_REGEX.exec(email.text) + const startTimeResult = EMAIL_START_TIME_REGEX.exec(email.text) + const endTimeResult = EMAIL_END_TIME_REGEX.exec(email.text) + + if (dateResult == null || dateResult[1] == null) { + throw new WaitingListDetailsError('Date not found') + } + + if (startTimeResult == null || startTimeResult[1] == null) { + throw new WaitingListDetailsError('Start time not found') + } + + if (endTimeResult == null || endTimeResult[1] == null) { + throw new WaitingListDetailsError('End time not found') + } + + return { + date: dateResult[1], + startTime: startTimeResult[1], + endTime: endTimeResult[1], + } + } + + private isRelevantEmail(email: Email): boolean { + if (!EMAIL_SUBJECT_REGEX.test(email.subject)) { + this.loggerService.log('Ignoring email, irrelevant subject', { + id: email.id, + subject: email.subject, + }) + return false + } + + if (EMAIL_ADDRESS !== email.from) { + this.loggerService.log('Ignoring email, irrelevant sender', { + id: email.id, + sender: email.from, + }) + return false + } + + return true + } +} + +export class WaitingListDetailsError extends Error {} diff --git a/src/waitingList/types.ts b/src/waitingList/types.ts new file mode 100644 index 0000000..610a4a1 --- /dev/null +++ b/src/waitingList/types.ts @@ -0,0 +1,5 @@ +export interface WaitingListDetails { + date: string + startTime: string + endTime: string +}