diff --git a/docs/en/install/README.md b/docs/en/install/README.md index e8e5734b39..7f05658b9f 100644 --- a/docs/en/install/README.md +++ b/docs/en/install/README.md @@ -338,6 +338,10 @@ When adding feeds using RSS readers with HTTP Basic Authentication support, auth - `GITHUB_ACCESS_TOKEN`: GitHub Access Token +- `mail`: + + - `EMAIL_CONFIG_{email}`: Mail setting, replace `{email}` with email account, like `EMAIL_CONFIG_xxx@qq.com`. the value format is `password=password&host=server&port=port`, like `password=123456&host=imap.qq.com&port=993` + ### Access Control Access control includes a whitelist and a blacklist, which is configured via `middleware/access-control.js` or environment variables. diff --git a/docs/install/README.md b/docs/install/README.md index 05582459d2..7e395cda23 100644 --- a/docs/install/README.md +++ b/docs/install/README.md @@ -411,3 +411,7 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 - 语雀 全部路由: [注册地址](https://www.yuque.com/register) - `YUQUE_TOKEN`: 语雀 Token,[获取地址](https://www.yuque.com/settings/tokens)。语雀接口做了访问频率限制,为保证正常访问建议配置 Token,详见[语雀开发者文档](https://www.yuque.com/yuque/developer/api#5b3a1535)。 + +- 邮箱 邮件列表路由: + + - `EMAIL_CONFIG_{email}`: 邮箱设置,替换 {email} 为 邮箱账号,例如 `EMAIL_CONFIG_xxx@qq.com`。内容格式为 `password=密码&host=服务器&port=端口`,例如 `password=123456&host=imap.qq.com&port=993`。 diff --git a/docs/other.md b/docs/other.md index 82e34acb0f..1be48f6029 100644 --- a/docs/other.md +++ b/docs/other.md @@ -1187,6 +1187,14 @@ type 为 all 时,category 参数不支持 cost 和 free +## 邮箱 + +### 邮件列表 + +> 仅支持 IMAP 协议 + + + ## 语雀 ### 知识库 diff --git a/lib/config.js b/lib/config.js index ac38ca56f1..499ae98d3c 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,5 +1,6 @@ const bilibili_cookies = {}; const twitter_tokens = {}; +const email_config = {}; const envs = process.env; for (const name in envs) { if (name.startsWith('BILIBILI_COOKIE_')) { @@ -8,6 +9,9 @@ for (const name in envs) { } else if (name.startsWith('TWITTER_TOKEN_')) { const id = name.slice(14); twitter_tokens[id] = envs[name]; + } else if (name.startsWith('EMAIL_CONFIG_')) { + const id = name.slice(13); + email_config[id] = envs[name]; } } @@ -83,4 +87,7 @@ module.exports = { blacklist: process.env.BLACKLIST && process.env.BLACKLIST.split(','), whitelist: process.env.WHITELIST && process.env.WHITELIST.split(','), enableCluster: process.env.ENABLE_CLUSTER, + email: { + config: email_config, + }, }; diff --git a/lib/router.js b/lib/router.js index 0cdea8faed..927f37c9c0 100644 --- a/lib/router.js +++ b/lib/router.js @@ -1650,4 +1650,7 @@ router.get('/jskou/:type?', require('./routes/jskou/index')); router.get('/cneb/yjxx', require('./routes/cneb/yjxx')); router.get('/cneb/guoneinews', require('./routes/cneb/guoneinews')); +// 邮箱 +router.get('/mail/imap/:email', require('./routes/mail/imap')); + module.exports = router; diff --git a/lib/routes/mail/imap.js b/lib/routes/mail/imap.js new file mode 100644 index 0000000000..f327579968 --- /dev/null +++ b/lib/routes/mail/imap.js @@ -0,0 +1,65 @@ +const ImapClient = require('emailjs-imap-client').default; +const queryString = require('query-string'); +const config = require('@/config'); +const parser = require('mailparser').simpleParser; + +module.exports = async (ctx) => { + const email = ctx.params.email; + const mailConfig = Object.assign( + { + username: email, + password: '', + host: '', + port: 993, + }, + queryString.parse(config.email.config[email]) + ); + + if (!mailConfig.username || !mailConfig.password || !mailConfig.host || !mailConfig.port) { + throw Error('please config email password, host, port in .env file'); + } + + const imap = new ImapClient(mailConfig.host, mailConfig.port, { + logLevel: 'error', + auth: { + user: mailConfig.username, + pass: mailConfig.password, + }, + }); + + const mails = await imap + .connect() + .then(() => imap.selectMailbox('INBOX')) + .then((mailbox) => imap.listMessages('INBOX', `${Math.max(mailbox.exists - 9, 1)}:*`, ['uid', 'flags', 'envelope', 'body[]'])) + .then((messages) => messages) + .catch((error) => console.log(error)) + .finally(() => { + imap.close(); + }); + + const items = await Promise.all( + mails.reverse().map(async (item) => { + const cache = await ctx.cache.get(item.envelope['message-id']); + if (cache) { + return Promise.resolve(JSON.parse(cache)); + } + const description = await parser(item['body[]']); + const single = { + title: item.envelope.subject, + description: description.html || description.textAsHtml, + guid: item.envelope['message-id'], + pubDate: item.envelope.date, + author: `${item.envelope.from[0].name}(${item.envelope.from[0].address})`, + }; + ctx.cache.set(item.envelope['message-id'], JSON.stringify(single)); + return Promise.resolve(single); + }) + ); + + ctx.state.data = { + title: `${email}的邮件列表`, + link: ``, + description: `${email}的邮件列表`, + item: items, + }; +}; diff --git a/package.json b/package.json index c34083fcb0..076363e6f9 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "currency-symbol-map": "4.0.4", "dayjs": "1.8.15", "dotenv": "8.1.0", + "emailjs-imap-client": "3.0.7", "etag": "1.8.1", "form-data": "2.5.0", "git-rev-sync": "1.12.0", @@ -74,6 +75,7 @@ "koa-router": "7.4.0", "lru-cache": "5.1.1", "lz-string": "1.4.4", + "mailparser": "2.7.1", "markdown-it": "9.1.0", "module-alias": "2.2.1", "node-fetch": "2.6.0", diff --git a/test/config.js b/test/config.js index 17b8ad5c6c..582cb945ee 100644 --- a/test/config.js +++ b/test/config.js @@ -30,4 +30,18 @@ describe('config', () => { delete process.env.TWITTER_TOKEN_12; delete process.env.TWITTER_TOKEN_34; }); + + it('email config', async () => { + process.env['EMAIL_CONFIG_xx@qq.com'] = 'token1'; + process.env['EMAIL_CONFIG_oo@qq.com'] = 'token2'; + + const config = require('../lib/config'); + expect(config.email.config).toMatchObject({ + 'xx@qq.com': 'token1', + 'oo@qq.com': 'token2', + }); + + delete process.env['EMAIL_CONFIG_xx@qq.com']; + delete process.env['EMAIL_CONFIG_oo@qq.com']; + }); }); diff --git a/yarn.lock b/yarn.lock index c44fd7e290..b8e4fddab8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3629,6 +3629,61 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" +emailjs-addressparser@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/emailjs-addressparser/-/emailjs-addressparser-2.0.2.tgz#fa8e6ff989667da8ae41d7316a7dfe6abe3c9d51" + integrity sha1-+o5v+YlmfaiuQdcxan3+ar48nVE= + +emailjs-base64@^1.1.2, emailjs-base64@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/emailjs-base64/-/emailjs-base64-1.1.4.tgz#392fa38cb6aa35dccd3af3637ffc14c1c7ce9612" + integrity sha1-OS+jjLaqNdzNOvNjf/wUwcfOlhI= + +emailjs-imap-client@3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/emailjs-imap-client/-/emailjs-imap-client-3.0.7.tgz#d76f1e7279c8d0d519d82fde1f5e3dc06b1a2a95" + integrity sha1-128ecnnI0NUZ2C/eH149wGsaKpU= + dependencies: + emailjs-addressparser "^2.0.2" + emailjs-base64 "^1.1.2" + emailjs-imap-handler "^3.0.2" + emailjs-mime-codec "^2.0.7" + emailjs-tcp-socket "^2.0.0" + emailjs-utf7 "^4.0.1" + pako "^1.0.6" + ramda "^0.25.0" + +emailjs-imap-handler@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/emailjs-imap-handler/-/emailjs-imap-handler-3.0.2.tgz#ab4e6d58ffa46b1cc0f20e14ca8b093b69cda136" + integrity sha1-q05tWP+kaxzA8g4UyosJO2nNoTY= + dependencies: + ramda "^0.25.0" + +emailjs-mime-codec@^2.0.7: + version "2.0.9" + resolved "https://registry.yarnpkg.com/emailjs-mime-codec/-/emailjs-mime-codec-2.0.9.tgz#d184451b6f2e55c5868b0f0a82d18fe2b82f0c97" + integrity sha1-0YRFG28uVcWGiw8KgtGP4rgvDJc= + dependencies: + emailjs-base64 "^1.1.4" + ramda "^0.26.1" + text-encoding "^0.7.0" + +emailjs-tcp-socket@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/emailjs-tcp-socket/-/emailjs-tcp-socket-2.0.2.tgz#e89be8d331b5ab5dddca47428e5f64c1ac5154f4" + integrity sha1-6Jvo0zG1q13dykdCjl9kwaxRVPQ= + dependencies: + node-forge "^0.7.5" + ramda "^0.25.0" + +emailjs-utf7@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/emailjs-utf7/-/emailjs-utf7-4.0.1.tgz#ca6bad6b409605d11b4108d8785c70e42644f4b9" + integrity sha1-ymuta0CWBdEbQQjYeFxw5CZE9Lk= + dependencies: + emailjs-base64 "^1.1.2" + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -4877,7 +4932,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@1.2.0, he@1.2.x, he@^1.1.0: +he@1.2.0, he@1.2.x, he@^1.1.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -4969,7 +5024,17 @@ html-tags@^2.0.0: resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos= -htmlparser2@^3.10.0, htmlparser2@^3.3.0, htmlparser2@^3.9.1: +html-to-text@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-5.1.1.tgz#2d89db7bf34bc7bcb7d546b1b228991a16926e87" + integrity sha1-LYnbe/NLx7y31UaxsiiZGhaSboc= + dependencies: + he "^1.2.0" + htmlparser2 "^3.10.1" + lodash "^4.17.11" + minimist "^1.2.0" + +htmlparser2@^3.10.0, htmlparser2@^3.10.1, htmlparser2@^3.3.0, htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== @@ -6404,7 +6469,26 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -linkify-it@^2.0.0: +libbase64@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/libbase64/-/libbase64-1.0.3.tgz#de3023234abeefeb9d49378804c8a94404f5c98c" + integrity sha1-3jAjI0q+7+udSTeIBMipRAT1yYw= + +libmime@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/libmime/-/libmime-4.1.1.tgz#eb7a60c775ba78c3715ddaec5cd52596a047f8d1" + integrity sha1-63pgx3W6eMNxXdrsXNUllqBH+NE= + dependencies: + iconv-lite "0.4.24" + libbase64 "1.0.3" + libqp "1.1.0" + +libqp@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/libqp/-/libqp-1.1.0.tgz#f5e6e06ad74b794fb5b5b66988bf728ef1dedbe8" + integrity sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g= + +linkify-it@2.1.0, linkify-it@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db" integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg== @@ -6659,6 +6743,29 @@ lz-string@1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= +mailparser@2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/mailparser/-/mailparser-2.7.1.tgz#c83d1880cf2300d19eccd8c529bf978b04a0fc97" + integrity sha1-yD0YgM8jANGezNjFKb+XiwSg/Jc= + dependencies: + he "1.2.0" + html-to-text "5.1.1" + iconv-lite "0.4.24" + libmime "4.1.1" + linkify-it "2.1.0" + mailsplit "4.4.1" + nodemailer "6.1.1" + tlds "1.203.1" + +mailsplit@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/mailsplit/-/mailsplit-4.4.1.tgz#838603af3c1561e27aedd8599cec2a59c097faa6" + integrity sha1-g4YDrzwVYeJ67dhZnOwqWcCX+qY= + dependencies: + libbase64 "1.0.3" + libmime "4.1.1" + libqp "1.1.0" + make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -7186,6 +7293,11 @@ node-forge@0.7.5: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" integrity sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ== +node-forge@^0.7.5: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha1-/fO0GK7h+U8O9kLNY0hsd8qXJKw= + node-forge@^0.8.0: version "0.8.4" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.4.tgz#d6738662b661be19e2711ef01aa3b18212f13030" @@ -7271,6 +7383,11 @@ nodejieba@^2.2.1: dependencies: nan "~2.10.0" +nodemailer@6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.1.1.tgz#09e88ef4b3646f01089c5d84d007b872141fb575" + integrity sha1-CeiO9LNkbwEInF2E0Ae4chQftXU= + nodemon@1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.1.tgz#576f0aad0f863aabf8c48517f6192ff987cd5071" @@ -7681,7 +7798,7 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" -pako@~1.0.5: +pako@^1.0.6, pako@~1.0.5: version "1.0.10" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== @@ -8614,6 +8731,16 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== +ramda@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.25.0.tgz#8fdf68231cffa90bc2f9460390a0cb74a29b29a9" + integrity sha1-j99oIxz/qQvC+UYDkKDLdKKbKak= + +ramda@^0.26.1: + version "0.26.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" + integrity sha1-jUE1HrgRHFU1Nhf8O7/62OTTXQY= + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -9999,6 +10126,11 @@ test-exclude@^5.2.3: read-pkg-up "^4.0.0" require-main-filename "^2.0.0" +text-encoding@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643" + integrity sha1-+JXoNuRZkGJAhmAXmOqY6PNu5kM= + text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" @@ -10068,6 +10200,11 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== +tlds@1.203.1: + version "1.203.1" + resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.203.1.tgz#4dc9b02f53de3315bc98b80665e13de3edfc1dfc" + integrity sha1-TcmwL1PeMxW8mLgGZeE94+38Hfw= + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"