From 1b0fa19bd0cf6dee0818b4f165a78648275dd262 Mon Sep 17 00:00:00 2001 From: DIYgod Date: Tue, 11 Jun 2019 17:59:36 +0800 Subject: [PATCH] feat: twitter following timeline --- docs/en/README.md | 14 +++++++++++- docs/en/install/README.md | 2 ++ docs/install/README.md | 2 ++ docs/social-media.md | 12 +++++++++++ lib/config.js | 5 +++++ lib/router.js | 1 + lib/routes/twitter/followings.js | 37 ++++++++++++++++++++++++++++++++ lib/routes/twitter/utils.js | 8 ++++--- 8 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 lib/routes/twitter/followings.js diff --git a/docs/en/README.md b/docs/en/README.md index 2b5475916..b82925c18 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -251,7 +251,19 @@ If no matching results were found, the server returns only a HTTP status code `2 #### User timeline - + + +### User following timeline + + + +::: warning + +User following timeline needs Twitter token corresponding id, so this route is only for self-hosted, see Install - Route-specific Configurations for details + +::: + + #### List timeline diff --git a/docs/en/install/README.md b/docs/en/install/README.md index ec47aec53..3186d753e 100644 --- a/docs/en/install/README.md +++ b/docs/en/install/README.md @@ -318,6 +318,8 @@ When adding feeds using RSS readers with HTTP Basic Authentication support, auth - `TWITTER_CONSUMER_SECRET`: Twitter Consumer Secret, support multiple keys, split them with `,` + - `TWITTER_TOKEN_{id}`: Twitter token corresponding id, replace `{id}` with id, the value is splitting consumer_key consumer_secret access_token access_token_secret with `,`, `{consumer_key},{consumer_secret},{access_token},{access_token_secret}` + - `youtube`: [API Key application](https://console.developers.google.com/) - `YOUTUBE_KEY`: YouTube API Key diff --git a/docs/install/README.md b/docs/install/README.md index d0751a717..969346ea7 100644 --- a/docs/install/README.md +++ b/docs/install/README.md @@ -384,6 +384,8 @@ RSSHub 支持 `memory` 和 `redis` 两种缓存方式 - `TWITTER_CONSUMER_SECRET`: Twitter Consumer Secret,支持多个 key,用英文逗号 `,` 隔开,顺序与 key 对应 + - `TWITTER_TOKEN_{id}`: 对应 id 的 Twitter token,`{id}` 替换为 id,值为 consumer_key consumer_secret access_token access_token_secret 用逗号隔开,即:`{consumer_key},{consumer_secret},{access_token},{access_token_secret}` + - youtube 全部路由: [申请地址](https://console.developers.google.com/) - `YOUTUBE_KEY`: YouTube API Key diff --git a/docs/social-media.md b/docs/social-media.md index f40131945..b30d20a37 100644 --- a/docs/social-media.md +++ b/docs/social-media.md @@ -362,6 +362,18 @@ pageClass: routes +### 用户关注时间线 + + + +::: warning 注意 + +用户关注时间线需要对应用户的 Twitter token, 所以只能自建,详情见部署页面的配置模块。 + +::: + + + ### 列表时间线 diff --git a/lib/config.js b/lib/config.js index 4df35f9db..a3f8c9908 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,9 +1,13 @@ const bilibili_cookies = {}; +const twitter_tokens = {}; const envs = process.env; for (const name in envs) { if (name.startsWith('BILIBILI_COOKIE_')) { const uid = name.slice(16); bilibili_cookies[uid] = envs[name]; + } else if (name.startsWith('TWITTER_TOKEN_')) { + const id = name.slice(14); + twitter_tokens[id] = envs[name]; } } @@ -42,6 +46,7 @@ module.exports = { twitter: { consumer_key: process.env.TWITTER_CONSUMER_KEY, consumer_secret: process.env.TWITTER_CONSUMER_SECRET, + tokens: twitter_tokens, }, youtube: { key: process.env.YOUTUBE_KEY, diff --git a/lib/router.js b/lib/router.js index 2ac65f6e6..ce4c00b5f 100644 --- a/lib/router.js +++ b/lib/router.js @@ -180,6 +180,7 @@ if (config.twitter && config.twitter.consumer_key && config.twitter.consumer_sec } else { logger.warn('Twitter RSS is disabled due to the lack of token, api key or relevant config in config.js.'); } +router.get('/twitter/followings/:id', require('./routes/twitter/followings')); // Instagram router.get('/instagram/user/:id', require('./routes/instagram/user')); diff --git a/lib/routes/twitter/followings.js b/lib/routes/twitter/followings.js new file mode 100644 index 000000000..719d60c86 --- /dev/null +++ b/lib/routes/twitter/followings.js @@ -0,0 +1,37 @@ +const utils = require('./utils'); +const config = require('@/config'); +const T = {}; +const Twit = require('twit'); + +module.exports = async (ctx) => { + const id = ctx.params.id; + const cookie = config.twitter.tokens[id]; + if (!cookie) { + throw Error(`lacking twitter token for user ${id}`); + } else if (!T[id]) { + const token = cookie.split(','); + T[id] = new Twit({ + consumer_key: token[0], + consumer_secret: token[1], + access_token: token[2], + access_token_secret: token[3], + }); + } + + const result = await T[id].get('statuses/home_timeline', { + tweet_mode: 'extended', + count: 100, + }); + const data = result.data; + + ctx.state.data = { + title: `${id} 的 Twitter 关注时间线`, + link: `https://twitter.com/${id}/`, + item: utils.ProcessFeed( + { + data, + }, + true + ), + }; +}; diff --git a/lib/routes/twitter/utils.js b/lib/routes/twitter/utils.js index 8fa43e92c..74c54e75e 100644 --- a/lib/routes/twitter/utils.js +++ b/lib/routes/twitter/utils.js @@ -2,7 +2,7 @@ const URL = require('url'); const config = require('@/config'); const Twit = require('twit'); -const ProcessFeed = ({ data = [] }) => { +const ProcessFeed = ({ data = [] }, showAuthor = false) => { const getQueryParams = (url) => URL.parse(url, true).query; const getOrigionImg = (url) => { // https://greasyfork.org/zh-CN/scripts/2312-resize-image-on-open-image-in-new-tab/code#n150 @@ -100,8 +100,10 @@ const ProcessFeed = ({ data = [] }) => { } return { - title: `${item.in_reply_to_screen_name ? 'Re ' : ''}${formatText(item.full_text)}`, - description: `${item.in_reply_to_screen_name ? 'Re ' : ''}${formatText(item.full_text)}${url}${img}${quote}`, + title: `${showAuthor ? item.user.name + ': ' : ''}${item.in_reply_to_screen_name ? 'Re ' : ''}${formatText(item.full_text)}`, + description: `${showAuthor ? `${item.user.name}:

` : ''}${ + item.in_reply_to_screen_name ? 'Re ' : '' + }${formatText(item.full_text)}${url}${img}${quote}`, pubDate: new Date(item.created_at).toUTCString(), link: `https://twitter.com/${item.user.screen_name}/status/${item.id_str}`, };