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"