From 1d61677e761f6cfc2723609b7c496b80205acdda Mon Sep 17 00:00:00 2001 From: junbaor Date: Thu, 21 Nov 2019 10:59:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E9=A5=AD=E5=90=A6?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=97=B6=E9=97=B4=E7=BA=BF=E3=80=81=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E7=99=BB=E5=BD=95=E7=94=A8=E6=88=B7=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E7=BA=BF=E3=80=81=E7=94=A8=E6=88=B7=E6=94=B6=E8=97=8F?= =?UTF-8?q?=E3=80=81=E7=83=AD=E9=97=A8=E8=AF=9D=E9=A2=98=E3=80=81=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=20(#3427)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/install/README.md | 7 +++++ docs/social-media.md | 26 +++++++++++++++++++ lib/config.js | 6 +++++ lib/router.js | 7 +++++ lib/routes/fanfou/favorites.js | 39 ++++++++++++++++++++++++++++ lib/routes/fanfou/home_timeline.js | 35 +++++++++++++++++++++++++ lib/routes/fanfou/public_timeline.js | 36 +++++++++++++++++++++++++ lib/routes/fanfou/trends.js | 25 ++++++++++++++++++ lib/routes/fanfou/user_timeline.js | 38 +++++++++++++++++++++++++++ lib/routes/fanfou/utils.js | 34 ++++++++++++++++++++++++ package.json | 1 + yarn.lock | 30 ++++++++++++++++++--- 12 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 lib/routes/fanfou/favorites.js create mode 100644 lib/routes/fanfou/home_timeline.js create mode 100644 lib/routes/fanfou/public_timeline.js create mode 100644 lib/routes/fanfou/trends.js create mode 100644 lib/routes/fanfou/user_timeline.js create mode 100644 lib/routes/fanfou/utils.js diff --git a/docs/install/README.md b/docs/install/README.md index 0700d7252a..85b3b7508b 100644 --- a/docs/install/README.md +++ b/docs/install/README.md @@ -425,3 +425,10 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 - `WEIBO_APP_KEY`: 微博 App Key - `WEIBO_APP_SECRET`: 微博 App Secret - `WEIBO_REDIRECT_URL`: 微博登录授权回调地址,默认为 `RSSHub地址/weibo/timeline/0`,自定义回调地址请确保最后可以转跳到 `RSSHub地址/weibo/timeline/0?code=xxx` + +- 饭否 全部路由: [申请地址](https://github.com/FanfouAPI/FanFouAPIDoc/wiki/Oauth) + + - `FANFOU_CONSUMER_KEY`: 饭否 Consumer Key + - `FANFOU_CONSUMER_SECRET`: 饭否 Consumer Secret + - `FANFOU_USERNAME`: 饭否登录用户名、邮箱、手机号 + - `FANFOU_PASSWORD`: 饭否密码 diff --git a/docs/social-media.md b/docs/social-media.md index 67fba73cab..43ffda8fd4 100644 --- a/docs/social-media.md +++ b/docs/social-media.md @@ -510,6 +510,32 @@ pageClass: routes +## 饭否 + +::: warning 注意 +部署时需要申请并配置饭否 Consumer Key、Consumer Secret、用户名和密码,具体见部署文档 +::: + +### 用户动态 + + + +### 当前登录用户的时间线 + + + +### 用户收藏 + + + +### 热门话题 + + + +### 饭否搜索 + + + ## 方格子 ### 出版專題 diff --git a/lib/config.js b/lib/config.js index f5302a6205..a1f39b6423 100644 --- a/lib/config.js +++ b/lib/config.js @@ -100,6 +100,12 @@ const calculateValue = () => { app_secret: envs.WEIBO_APP_SECRET, redirect_url: envs.WEIBO_REDIRECT_URL, }, + fanfou: { + consumer_key: envs.FANFOU_CONSUMER_KEY, + consumer_secret: envs.FANFOU_CONSUMER_SECRET, + username: envs.FANFOU_USERNAME, + password: envs.FANFOU_PASSWORD, + }, }; }; calculateValue(); diff --git a/lib/router.js b/lib/router.js index 542632b4a9..53443f5997 100644 --- a/lib/router.js +++ b/lib/router.js @@ -1953,4 +1953,11 @@ router.get('/alter-cn/news', require('./routes/alter-cn/news')); // Visual Studio Code Marketplace router.get('/vscode/marketplace/:type?', require('./routes/vscode/marketplace')); +// 饭否 +router.get('/fanfou/user_timeline/:uid', require('./routes/fanfou/user_timeline')); +router.get('/fanfou/home_timeline', require('./routes/fanfou/home_timeline')); +router.get('/fanfou/favorites/:uid', require('./routes/fanfou/favorites')); +router.get('/fanfou/trends', require('./routes/fanfou/trends')); +router.get('/fanfou/public_timeline/:keyword', require('./routes/fanfou/public_timeline')); + module.exports = router; diff --git a/lib/routes/fanfou/favorites.js b/lib/routes/fanfou/favorites.js new file mode 100644 index 0000000000..49161c63f9 --- /dev/null +++ b/lib/routes/fanfou/favorites.js @@ -0,0 +1,39 @@ +const config = require('@/config').value; +const utils = require('./utils'); + +module.exports = async (ctx) => { + if (!config.fanfou || !config.fanfou.consumer_key || !config.fanfou.consumer_secret || !config.fanfou.username || !config.fanfou.password) { + throw 'Fanfou RSS is disabled due to the lack of relevant config'; + } + + const uid = ctx.params.uid; + const fanfou = await utils.getFanfou(); + const timeline = await fanfou.get(`/favorites/${uid}`, { id: uid, mode: 'lite', format: 'html' }); + + const result = await Promise.all( + timeline.map(async (item) => { + let img_html = ''; + if (item.photo) { + img_html = `
饭否动态图片`; + } + const voItem = { + title: item.text, + author: item.user.name, + description: item.text + img_html, + pubDate: item.created_at, + link: `https://fanfou.com/statuses/${item.id}`, + }; + return Promise.resolve(voItem); + }) + ); + + const users = await fanfou.get(`/users/show`, { id: uid }); + const authorName = users.screen_name; + + ctx.state.data = { + title: `${authorName}的饭否收藏`, + link: `https://fanfou.com/favorites/${uid}`, + description: `${authorName}的饭否收藏`, + item: result, + }; +}; diff --git a/lib/routes/fanfou/home_timeline.js b/lib/routes/fanfou/home_timeline.js new file mode 100644 index 0000000000..9ff4b7b8ae --- /dev/null +++ b/lib/routes/fanfou/home_timeline.js @@ -0,0 +1,35 @@ +const config = require('@/config').value; +const utils = require('./utils'); + +module.exports = async (ctx) => { + if (!config.fanfou || !config.fanfou.consumer_key || !config.fanfou.consumer_secret || !config.fanfou.username || !config.fanfou.password) { + throw 'Fanfou RSS is disabled due to the lack of relevant config'; + } + + const fanfou = await utils.getFanfou(); + const timeline = await fanfou.get('/statuses/home_timeline', { mode: 'lite', format: 'html' }); + + const result = await Promise.all( + timeline.map(async (item) => { + let img_html = ''; + if (item.photo) { + img_html = `
饭否动态图片`; + } + const voItem = { + title: item.plain_text, + author: item.user.name, + description: item.text + img_html, + pubDate: item.created_at, + link: `http://fanfou.com/statuses/${item.id}`, + }; + return Promise.resolve(voItem); + }) + ); + + ctx.state.data = { + title: `我的饭否动态`, + link: `https://fanfou.com/home`, + description: `我的饭否动态`, + item: result, + }; +}; diff --git a/lib/routes/fanfou/public_timeline.js b/lib/routes/fanfou/public_timeline.js new file mode 100644 index 0000000000..e04f649c16 --- /dev/null +++ b/lib/routes/fanfou/public_timeline.js @@ -0,0 +1,36 @@ +const config = require('@/config').value; +const utils = require('./utils'); + +module.exports = async (ctx) => { + if (!config.fanfou || !config.fanfou.consumer_key || !config.fanfou.consumer_secret || !config.fanfou.username || !config.fanfou.password) { + throw 'Fanfou RSS is disabled due to the lack of relevant config'; + } + + const keyword = ctx.params.keyword; + const fanfou = await utils.getFanfou(); + const timeline = await fanfou.get('/search/public_timeline', { q: keyword, mode: 'lite', format: 'html' }); + + const result = await Promise.all( + timeline.map(async (item) => { + let img_html = ''; + if (item.photo) { + img_html = `
饭否动态图片`; + } + const voItem = { + title: item.plain_text, + author: item.user.name, + description: item.text + img_html, + pubDate: item.created_at, + link: `https://fanfou.com/statuses/${item.id}`, + }; + return Promise.resolve(voItem); + }) + ); + + ctx.state.data = { + title: `饭否搜索-${keyword}`, + link: `https://fanfou.com/q/${keyword}`, + description: `饭否搜索-${keyword}`, + item: result, + }; +}; diff --git a/lib/routes/fanfou/trends.js b/lib/routes/fanfou/trends.js new file mode 100644 index 0000000000..68f6342945 --- /dev/null +++ b/lib/routes/fanfou/trends.js @@ -0,0 +1,25 @@ +const utils = require('./utils'); + +module.exports = async (ctx) => { + const fanfou = await utils.getFanfou(); + const response = await fanfou.get(`/trends/list`); + + const result = await Promise.all( + response.trends.map(async (item) => { + const voItem = { + title: item.name, + description: item.name, + pubDate: new Date(), + link: item.url, + }; + return Promise.resolve(voItem); + }) + ); + + ctx.state.data = { + title: `饭否热门话题`, + link: `https://fanfou.com/q/`, + description: `饭否热门话题`, + item: result, + }; +}; diff --git a/lib/routes/fanfou/user_timeline.js b/lib/routes/fanfou/user_timeline.js new file mode 100644 index 0000000000..f28260959f --- /dev/null +++ b/lib/routes/fanfou/user_timeline.js @@ -0,0 +1,38 @@ +const config = require('@/config').value; +const utils = require('./utils'); + +module.exports = async (ctx) => { + if (!config.fanfou || !config.fanfou.consumer_key || !config.fanfou.consumer_secret || !config.fanfou.username || !config.fanfou.password) { + throw 'Fanfou RSS is disabled due to the lack of relevant config'; + } + + const uid = ctx.params.uid; + const fanfou = await utils.getFanfou(); + const timeline = await fanfou.get('/statuses/user_timeline', { id: uid, mode: 'lite', format: 'html' }); + + const result = await Promise.all( + timeline.map(async (item) => { + let img_html = ''; + if (item.photo) { + img_html = `
饭否动态图片`; + } + const voItem = { + title: item.plain_text, + author: item.user.name, + description: item.text + img_html, + pubDate: item.created_at, + link: `https://fanfou.com/statuses/${item.id}`, + }; + return Promise.resolve(voItem); + }) + ); + + const authorName = result[0].author; + + ctx.state.data = { + title: `${authorName}的饭否`, + link: `https://fanfou.com/${uid}`, + description: `${authorName}的饭否`, + item: result, + }; +}; diff --git a/lib/routes/fanfou/utils.js b/lib/routes/fanfou/utils.js new file mode 100644 index 0000000000..a1353e385a --- /dev/null +++ b/lib/routes/fanfou/utils.js @@ -0,0 +1,34 @@ +const logger = require('@/utils/logger'); +const config = require('@/config').value; +const Fanfou = require('fanfou-sdk'); + +const consumerKey = config.fanfou.consumer_key; +const consumerSecret = config.fanfou.consumer_secret; +const username = config.fanfou.username; +const password = config.fanfou.password; + +let fanfou_client; +let authed = false; + +const getFanfou = async () => { + if (authed === true) { + return fanfou_client; + } else { + fanfou_client = new Fanfou({ + consumerKey, + consumerSecret, + username, + password, + }); + + await fanfou_client.xauth(); + logger.info('Fanfou login success.'); + + authed = true; + return fanfou_client; + } +}; + +module.exports = { + getFanfou, +}; diff --git a/package.json b/package.json index 5b8cef5470..a7d366b810 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "dotenv": "8.2.0", "emailjs-imap-client": "3.0.7", "etag": "1.8.1", + "fanfou-sdk": "4.0.5", "form-data": "3.0.0", "git-rev-sync": "1.12.0", "googleapis": "45.0.0", diff --git a/yarn.lock b/yarn.lock index cafbdb6eda..b57acade20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4458,6 +4458,18 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +fanfou-sdk@4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/fanfou-sdk/-/fanfou-sdk-4.0.5.tgz#5b3f2ab5ee8a383fc21ab58bc6c1ed97cbce7113" + integrity sha512-s3Ec5D3Am5daLQtu9IwkVWPSV40Tg//jPJVAYbLVMITLvCgq58H2ufJ5bgfsQAJrQXVL0ZhkN+mwhgp/4BJzXA== + dependencies: + form-data "^2.3.3" + got "^9.5.1" + he "^1.1.1" + hmacsha1 "^1.0.0" + oauth-1.0a "^2.2.5" + query-string "^6.2.0" + fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -4682,7 +4694,7 @@ form-data@3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@^2.3.1, form-data@^2.5.0: +form-data@^2.3.1, form-data@^2.3.3, form-data@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA== @@ -5046,7 +5058,7 @@ googleapis@45.0.0: google-auth-library "^5.2.0" googleapis-common "^3.1.0" -got@9.6.0: +got@9.6.0, got@^9.5.1: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" integrity sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q== @@ -5225,7 +5237,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.0, he@1.2.x, he@^1.1.0, he@^1.1.1, 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== @@ -5249,6 +5261,11 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hmacsha1@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/hmacsha1/-/hmacsha1-1.0.0.tgz#c1b7ae03a4ea116348090af14f8148c12938a917" + integrity sha1-wbeuA6TqEWNICQrxT4FIwSk4qRc= + hogan.js@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd" @@ -7871,6 +7888,11 @@ nwsapi@^2.0.7, nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== +oauth-1.0a@^2.2.5: + version "2.2.6" + resolved "https://registry.yarnpkg.com/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz#eadbccdb3bceea412d24586e6f39b2b412f0e491" + integrity sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ== + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -9039,7 +9061,7 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@6.9.0: +query-string@6.9.0, query-string@^6.2.0: version "6.9.0" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.9.0.tgz#1c3b727c370cf00f177c99f328fda2108f8fa3dd" integrity sha512-KG4bhCFYapExLsUHrFt+kQVEegF2agm4cpF/VNc6pZVthIfCc/GK8t8VyNIE3nyXG9DK3Tf2EGkxjR6/uRdYsA==