From ef1aeb99e3f9235d77204e4069008ee4e9f23bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=87=89=E5=87=89?= Date: Thu, 13 Dec 2018 11:24:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0:=20twitter=20list=20(#1267)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #642 --- docs/README.md | 4 ++ router.js | 1 + routes/twitter/list.js | 23 +++++++++ routes/twitter/user.js | 107 ++------------------------------------ routes/twitter/utils.js | 110 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 104 deletions(-) create mode 100644 routes/twitter/list.js create mode 100644 routes/twitter/utils.js diff --git a/docs/README.md b/docs/README.md index 9ba80cfb09..fdbc1555d6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -495,6 +495,10 @@ RSSHub 提供下列 API 接口: + + +> 官方文档: [How to use Twitter Lists](https://help.twitter.com/en/using-twitter/twitter-lists) + ### Instagram diff --git a/router.js b/router.js index 306c60fa03..c0c68fa69e 100644 --- a/router.js +++ b/router.js @@ -236,6 +236,7 @@ if (config.disqus && config.disqus.api_key) { // Twitter if (config.twitter && config.twitter.consumer_key && config.twitter.consumer_secret && config.twitter.access_token && config.twitter.access_token_secret) { router.get('/twitter/user/:id', require('./routes/twitter/user')); + router.get('/twitter/list/:id/:name', require('./routes/twitter/list')); } else { logger.warn('Twitter RSS is disabled for lacking config.'); } diff --git a/routes/twitter/list.js b/routes/twitter/list.js new file mode 100644 index 0000000000..00990ddbef --- /dev/null +++ b/routes/twitter/list.js @@ -0,0 +1,23 @@ +const Twit = require('twit'); +const config = require('../../config'); +const utils = require('./utils'); + +const T = new Twit(config.twitter); + +module.exports = async (ctx) => { + const { id, name } = ctx.params; + const result = await T.get('lists/statuses', { + owner_screen_name: id, + slug: name, + tweet_mode: 'extended', + }); + const data = result.data; + + ctx.state.data = { + title: `Twitter List - ${id}/${name}`, + link: `https://twitter.com/${id}/lists/${name}`, + item: utils.ProcessFeed({ + data, + }), + }; +}; diff --git a/routes/twitter/user.js b/routes/twitter/user.js index 3a9e7f12c5..b5bff857d5 100644 --- a/routes/twitter/user.js +++ b/routes/twitter/user.js @@ -1,124 +1,23 @@ const Twit = require('twit'); -const URL = require('url'); const config = require('../../config'); +const utils = require('./utils'); const T = new Twit(config.twitter); module.exports = async (ctx) => { const id = ctx.params.id; - - 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 - let m = null; - if ((m = url.match(/^(https?:\/\/\w+\.twimg\.com\/media\/[^/:]+)\.(jpg|jpeg|gif|png|bmp|webp)(:\w+)?$/i))) { - let format = m[2]; - if (m[2] === 'jpeg') { - format = 'jpg'; - } - return `${m[1]}?format=${format}&name=orig`; - } else if ((m = url.match(/^(https?:\/\/\w+\.twimg\.com\/.+)(\?.+)$/i))) { - const pars = getQueryParams(url); - if (!pars.format || !pars.name) { - return url; - } - if (pars.name === 'orig') { - return url; - } - return m[1] + '?format=' + pars.format + '&name=orig'; - } else { - return url; - } - }; - - const formatText = (text) => text.replace(/https:\/\/t\.co(.*)/g, ''); - const formatVideo = (media) => { - let content = ''; - const video = media.video_info.variants.reduce((video, item) => { - if ((item.bitrate || 0) > (video.bitrate || 0)) { - video = item; - } - return video; - }, {}); - - if (video.url) { - content = `
`; - } - - return content; - }; - - const formatMedia = (item) => { - let img = ''; - item.extended_entities && - item.extended_entities.media.forEach((item) => { - // https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/extended-entities-object - let content = ''; - switch (item.type) { - case 'animated_gif': - case 'video': - content = formatVideo(item); - break; - - case 'photo': - default: - content = `
`; - break; - } - - img += content; - }); - - return img; - }; - const formatUrl = (item) => { - let url = ''; - item.entities.urls.forEach((u) => { - url += `${u.expanded_url}`; - }); - - return url; - }; - const result = await T.get('statuses/user_timeline', { screen_name: id, tweet_mode: 'extended', }); - const data = result.data; ctx.state.data = { title: `${data[0].user.name} 的 Twitter`, link: `https://twitter.com/${id}/`, description: data[0].user.description, - item: data.map((item) => { - item = item.retweeted_status || item; - item.full_text = formatText(item.full_text); - const img = formatMedia(item); - let url = ''; - let quote = ''; - - if (item.is_quote_status) { - const quoteData = item.quoted_status; - - if (quoteData) { - const author = quoteData.user; - quote += `

${author.name}: ${formatText(quoteData.full_text)}
`; - quote += formatMedia(quoteData); - quote += formatUrl(quoteData); - } else { - url = formatUrl(item); - } - } else { - url = formatUrl(item); - } - - return { - title: `${item.in_reply_to_screen_name ? 'Re ' : ''}${item.full_text}`, - description: `${item.in_reply_to_screen_name ? 'Re ' : ''}${item.full_text}${url}${img}${quote}`, - pubDate: new Date(item.created_at).toUTCString(), - link: `https://twitter.com/${id}/status/${item.id_str}`, - }; + item: utils.ProcessFeed({ + data, }), }; }; diff --git a/routes/twitter/utils.js b/routes/twitter/utils.js new file mode 100644 index 0000000000..b3cadaa694 --- /dev/null +++ b/routes/twitter/utils.js @@ -0,0 +1,110 @@ +const URL = require('url'); + +const ProcessFeed = ({ data = [] }) => { + 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 + let m = null; + if ((m = url.match(/^(https?:\/\/\w+\.twimg\.com\/media\/[^/:]+)\.(jpg|jpeg|gif|png|bmp|webp)(:\w+)?$/i))) { + let format = m[2]; + if (m[2] === 'jpeg') { + format = 'jpg'; + } + return `${m[1]}?format=${format}&name=orig`; + } else if ((m = url.match(/^(https?:\/\/\w+\.twimg\.com\/.+)(\?.+)$/i))) { + const pars = getQueryParams(url); + if (!pars.format || !pars.name) { + return url; + } + if (pars.name === 'orig') { + return url; + } + return m[1] + '?format=' + pars.format + '&name=orig'; + } else { + return url; + } + }; + + const formatText = (text) => text.replace(/https:\/\/t\.co(.*)/g, ''); + const formatVideo = (media) => { + let content = ''; + const video = media.video_info.variants.reduce((video, item) => { + if ((item.bitrate || 0) > (video.bitrate || 0)) { + video = item; + } + return video; + }, {}); + + if (video.url) { + content = `
`; + } + + return content; + }; + + const formatMedia = (item) => { + let img = ''; + item.extended_entities && + item.extended_entities.media.forEach((item) => { + // https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/extended-entities-object + let content = ''; + switch (item.type) { + case 'animated_gif': + case 'video': + content = formatVideo(item); + break; + + case 'photo': + default: + content = `
`; + break; + } + + img += content; + }); + + return img; + }; + const formatUrl = (item) => { + let url = ''; + item.entities.urls.forEach((u) => { + url += `${u.expanded_url}`; + }); + + return url; + }; + + return data.map((item) => { + item = item.retweeted_status || item; + item.full_text = formatText(item.full_text); + const img = formatMedia(item); + let url = ''; + let quote = ''; + + if (item.is_quote_status) { + const quoteData = item.quoted_status; + + if (quoteData) { + const author = quoteData.user; + quote += `

${author.name}: ${formatText(quoteData.full_text)}
`; + quote += formatMedia(quoteData); + quote += formatUrl(quoteData); + } else { + url = formatUrl(item); + } + } else { + url = formatUrl(item); + } + + return { + title: `${item.in_reply_to_screen_name ? 'Re ' : ''}${item.full_text}`, + description: `${item.in_reply_to_screen_name ? 'Re ' : ''}${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}`, + }; + }); +}; + +module.exports = { + ProcessFeed, +};