refactor: 微博个人时间线接入util (#7827)

* refactor: 微博个人时间线接入util,支持可读性优化,同时打开emoji表情展示

* Update lib/routes/weibo/timeline.js

Co-authored-by: SettingDust <settingdust@gmail.com>

* Update lib/routes/weibo/timeline.js

Co-authored-by: SettingDust <settingdust@gmail.com>

* Update lib/routes/weibo/timeline.js

Co-authored-by: SettingDust <settingdust@gmail.com>

* Update lib/routes/weibo/timeline.js

Co-authored-by: SettingDust <settingdust@gmail.com>

* Revert "style: auto format"

This reverts commit cb9dde42fa.

* revert: lib/route/weibo/timeline.js

* revert

* Merge branch 'master' of https://github.com/DIYgod/RSSHub into weibo_home_timeline_refactor

* Merge branch 'master' of https://github.com/DIYgod/RSSHub into weibo_home_timeline_refactor

* refactor:  using optional chaining

* fix: using optional chaining

* fix: use parseDate()

* fix:  use cache.tryGet() instead of cache.set/cache.get

* fix:  use cache.tryGet() instead of cache.set/cache.get and use parseDate()

* fix:  use cache.tryGet() instead of cache.set/cache.get and use parseDate()

* fix:  use cache.tryGet() instead of cache.set/cache.get

Co-authored-by: SettingDust <settingdust@gmail.com>
This commit is contained in:
zytomorrow
2022-02-07 22:55:42 +08:00
committed by GitHub
co-authored by SettingDust
parent 839d3eafa5
commit 40c06ff977
4 changed files with 85 additions and 138 deletions
+2 -7
View File
@@ -1109,6 +1109,7 @@ rule
| heightOfPics | 微博配图高(生效取决于阅读器) | 不指定 / 数字 | 不指定 |
| sizeOfAuthorAvatar | 作者头像大小 | 数字 | 48 |
| displayVideo | 是否直接显示微博视频,只在博主 RSS 中有效 | 0/1/true/false | true |
| showEmojiInDescription | 是否展示正文中的 emoji 表情 | 0/1/true/false | true |
指定更多与默认值不同的参数选项可以改善 RSS 的可读性,如
@@ -1149,13 +1150,7 @@ rule
### 个人时间线
::: warning 注意
个人时间线暂不支持改善可读性。
:::
<Route author="zytomorrow DIYgod" example="/weibo/timeline/3306934123" path="/weibo/timeline/:uid/:feature?" :paramsDesc="['用户的uid', ' 过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。']" anticrawler="1" selfhost="1">
<Route author="zytomorrow DIYgod" example="/weibo/timeline/3306934123" path="/weibo/timeline/:uid/:feature?/:routeParams?" :paramsDesc="['用户的uid', ' 过滤类型ID,0:全部、1:原创、2:图片、3:视频、4:音乐,默认为0。', '额外参数;请参阅上面的说明和表格']" anticrawler="1" selfhost="1">
::: warning 注意
+1 -1
View File
@@ -2173,7 +2173,7 @@ router.get('/reuters/channel/:site/:channel', lazyloadRouteHandler('./routes/reu
router.get('/pgyer/:app?', lazyloadRouteHandler('./routes/pgyer/app'));
// 微博个人时间线
router.get('/weibo/timeline/:uid/:feature?', lazyloadRouteHandler('./routes/weibo/timeline'));
router.get('/weibo/timeline/:uid/:feature?/:routeParams?', lazyloadRouteHandler('./routes/weibo/timeline'));
// TAPTAP
router.get('/taptap/topic/:id/:label?', lazyloadRouteHandler('./routes/taptap/topic'));
+76 -128
View File
@@ -1,12 +1,37 @@
const querystring = require('querystring');
const got = require('@/utils/got');
const { parseDate } = require('@/utils/parse-date');
const config = require('@/config').value;
const weiboUtils = require('./utils');
const { fallback, queryToBoolean } = require('@/utils/readable-social');
module.exports = async (ctx) => {
const uid = ctx.params.uid;
const feature = ctx.params.feature || 0;
const routeParams = ctx.params.routeParams || undefined;
const token = await ctx.cache.get('weibotimelineuid' + uid, false);
let displayVideo = '1';
if (routeParams) {
if (routeParams === '1' || routeParams === '0') {
displayVideo = routeParams;
} else {
const routeParams = querystring.parse(ctx.params.routeParams);
displayVideo = fallback(undefined, queryToBoolean(routeParams.displayVideo), true) ? '1' : '0';
}
}
if (token) {
const containerResponse = await got({
method: 'get',
url: `https://m.weibo.cn/api/container/getIndex?type=uid&value=${uid}`,
headers: {
Referer: 'https://m.weibo.cn/',
},
});
const name = containerResponse.data.data.userInfo.screen_name;
const description = containerResponse.data.data.userInfo.description;
const profileImageUrl = containerResponse.data.data.userInfo.profile_image_url;
const response = await got.get(`https://api.weibo.com/2/statuses/home_timeline.json?access_token=${token}&count=100&feature=${feature}`);
// 检查token失效
if (response.data.error !== undefined) {
@@ -16,143 +41,66 @@ module.exports = async (ctx) => {
ctx.set({
'Cache-Control': 'no-cache',
});
ctx.redirect(`https://api.weibo.com/oauth2/authorize?client_id=${app_key}&redirect_uri=${redirect_url}`);
ctx.redirect(`https://api.weibo.com/oauth2/authorize?client_id=${app_key}&redirect_uri=${redirect_url}${routeParams ? `&state=${routeParams}` : ''}`);
}
// 获取所有表情
const weiboEmotion = await ctx.cache.tryGet('weiboEmotion', async () => {
const tmpEmotionList = {};
await Promise.all(
['face', 'ani', 'cartoon'].map(async (type) => {
try {
const rep = await got.get(`https://api.weibo.com/2/emotions.json?access_token=${token}&type=${type}`);
rep.data.forEach((item) => {
tmpEmotionList[item.phrase] = item.url;
});
return Promise.resolve(rep);
} catch (err) {
// pass
}
})
);
return tmpEmotionList;
});
const data = await Promise.all(
const resultItem = await Promise.all(
response.data.statuses.map(async (item) => {
const realContent = await ctx.cache.tryGet('weibotimelineurl' + item.user.id + item.id, async () => {
const res = [];
const videoList = [];
let picUrlList = [];
picUrlList = picUrlList.concat(item.pic_urls);
try {
const rep = await got.get(`https://m.weibo.cn/${item.user.id}/${item.id}`);
rep.body.replace(/"text": "([\s\S]*?)",/g, (s, value) => {
res.push(value.replace(/\\"/g, "'"));
});
rep.body.replace(/"mp4_720p_mp4": "([\s\S]*?)",/, (s, value) => {
videoList.push(value);
});
if (res.length > 1) {
const sub_rep = await got.get(`https://m.weibo.cn/${item.retweeted_status.user.id}/${item.retweeted_status.id}`);
sub_rep.body.replace(/"text": "([\s\S]*?)",/g, (s, value) => {
res[1] = value.replace(/\\"/g, "'");
});
sub_rep.body.replace(/"mp4_720p_mp4": "([\s\S]*?)",/, (s, value) => {
videoList.push(value);
});
picUrlList = picUrlList.concat(item.retweeted_status.pic_urls);
}
} catch (err) {
res.push(item.text);
if (item.retweeted_status) {
res.push(item.retweeted_status.text);
picUrlList = picUrlList.concat(item.retweeted_status.pic_urls);
}
// 非全文获取的微博内容需要替换表情
res.forEach((content, index) => {
const emotion = [];
content.replace(/(\[.*?\])/g, (s, value) => {
emotion.push(value);
});
if (content.length > 0) {
emotion.forEach((item) => {
res[index] = content.replace(new RegExp(`\\${item}`, 'g'), `<img alt="${item}" src="${weiboEmotion[item]}" style="width:1em; height:1em;" referrerpolicy="no-referrer">`);
});
}
});
}
let content = '';
try {
if (res.length >= 2) {
content = `${res[0]}<br><br><hr style="height:5px;border:none;border-top:5px ridge green;" /><span style="background-color:#71adec">${item.retweeted_status.user.screen_name}:<hr style="height:1px;border:none;border-top:1px solid #555555;" /><br>${res[1]}</span>`;
} else if (res.length === 1) {
content = res[0];
}
// 手机端reeder查看内容时,表情会导致换行-此处做转换
content.replace(/<span class='url-icon'>([\s\S]*?)<\/span>/g, (s, value) => {
content = content.replace(s, value);
});
} catch (error) {
// pass
}
let title = '';
if (res.length >= 2) {
title = `${item.user.name}--转发微博`;
} else if (item.pic_urls.length > 0) {
title = `${item.user.name}--原创图文微博`;
} else {
title = `${item.user.name}--原创微博`;
}
return {
title,
content,
author: item.user.name,
date: item.created_at,
link: `https://m.weibo.cn/detail/${item.id}`,
pic_urls: picUrlList,
videoList: Array.from(new Set(videoList)),
};
const key = `weibotimelineurl${item.user.id}${item.id}`;
const value = await ctx.cache.tryGet(key, async () => {
const _data = await weiboUtils.getShowData(uid, item.id);
return JSON.stringify(_data);
});
return Promise.resolve(realContent);
const data = JSON.parse(value);
// 是否通过api拿到了data
const isDataOK = data?.text;
if (isDataOK) {
item = data;
}
// 转发的长微博处理
const retweet = item.retweeted_status;
if (retweet?.isLongText) {
const retweetData = await weiboUtils.getShowData(retweet.user.id, retweet.id);
if (retweetData?.text) {
item.retweeted_status.text = retweetData.text;
}
}
const link = `https://weibo.com/${uid}/${item.id}`;
const formatExtended = weiboUtils.formatExtended(ctx, item);
let description = formatExtended.description;
const title = formatExtended.title;
const pubDate = isDataOK ? parseDate(data.created_at) : parseDate(item.created_at);
// 视频的处理
if (displayVideo === '1') {
description = weiboUtils.formatVideo(description, item);
}
return {
title,
description,
link,
pubDate,
author: item.user.screen_name,
};
})
);
ctx.state.data = {
title: '个人微博时间线',
link: 'https://weibo.com',
item: data.map((item) => {
let description = '';
if (item.content !== null) {
description = item.content;
}
item.pic_urls.forEach((pic) => {
description += `<img alt="配图" style="margin: 0 auto" src="${pic.thumbnail_pic.replace('thumbnail', 'large')}"><br>`;
});
if (item.videoList.length !== 0) {
item.videoList.forEach((url) => {
description += `<iframe height=720 width=1080 src='${url}' frameborder=0 'allowfullscreen'></iframe>`;
});
}
return {
description: description
.replace(/\b(url|href|src)\b=(['|"])http:/g, '$1=$2https:')
.replace('http://t.cn/', 'https://t.cn/')
.replace('http://m.weibo.', 'https://m.weibo.'),
link: item.link,
pubDate: item.date,
title: item.title,
author: item.author,
};
}),
title: `个人微博时间线--${name}`,
link: `http://weibo.com/${uid}/`,
description,
image: profileImageUrl,
item: resultItem,
};
} else if (uid === '0' || ctx.querystring) {
const { app_key = '', redirect_url = ctx.request.origin + '/weibo/timeline/0', app_secret = '' } = config.weibo;
const code = Object.values(ctx.query)[0];
const code = ctx.query.code;
const routeParams = ctx.query.state;
if (code) {
const rep = await got.post(`https://api.weibo.com/oauth2/access_token?client_id=${app_key}&client_secret=${app_secret}&code=${code}&redirect_uri=${redirect_url}&grant_type=authorization_code`);
const token = rep.data.access_token;
@@ -164,7 +112,7 @@ module.exports = async (ctx) => {
'Content-Type': 'text/html; charset=UTF-8',
'Cache-Control': 'no-cache',
});
ctx.body = `<script>window.location = '/weibo/timeline/${uid}'</script>`;
ctx.body = `<script>window.location = '/weibo/timeline/${uid}${routeParams ? `/${routeParams}` : ''}'</script>`;
}
} else {
const { app_key = '', redirect_url = ctx.request.origin + '/weibo/timeline/0' } = config.weibo;
@@ -173,6 +121,6 @@ module.exports = async (ctx) => {
ctx.set({
'Cache-Control': 'no-cache',
});
ctx.redirect(`https://api.weibo.com/oauth2/authorize?client_id=${app_key}&redirect_uri=${redirect_url}`);
ctx.redirect(`https://api.weibo.com/oauth2/authorize?client_id=${app_key}&redirect_uri=${redirect_url}${routeParams ? `&state=${feature}/${routeParams.replace(/&/g, '%26')}` : ''}`);
}
};
+6 -2
View File
@@ -22,6 +22,7 @@ const weiboUtils = {
widthOfPics: fallback(params.widthOfPics, queryToInteger(routeParams.widthOfPics), -1),
heightOfPics: fallback(params.heightOfPics, queryToInteger(routeParams.heightOfPics), -1),
sizeOfAuthorAvatar: fallback(params.sizeOfAuthorAvatar, queryToInteger(routeParams.sizeOfAuthorAvatar), 48),
showEmojiInDescription: fallback(params.showEmojiInDescription, queryToInteger(routeParams.showEmojiInDescription), true),
};
params = mergedParams;
@@ -41,17 +42,20 @@ const weiboUtils = {
widthOfPics,
heightOfPics,
sizeOfAuthorAvatar,
showEmojiInDescription,
} = params;
let retweeted = '';
// 长文章的处理
let htmlNewLineUnreplaced = (status.longText && status.longText.longTextContent) || status.text || '';
// 表情图标转换为文字
htmlNewLineUnreplaced = htmlNewLineUnreplaced.replace(/<span class="url-icon"><img.*?alt="?(.*?)"? [\s\S]*?\/><\/span>/g, '$1');
if (!showEmojiInDescription) {
htmlNewLineUnreplaced = htmlNewLineUnreplaced.replace(/<span class="url-icon"><img.*?alt="?(.*?)"? [\s\S]*?\/><\/span>/g, '$1');
}
// 去掉外部链接的图标
htmlNewLineUnreplaced = htmlNewLineUnreplaced.replace(/<span class=["|']url-icon["|']>.*?<\/span><span class="surl-text">(.*?)<\/span>/g, '$1');
// 去掉乱七八糟的图标
htmlNewLineUnreplaced = htmlNewLineUnreplaced.replace(/<span class=["|']url-icon["|']>(.*?)<\/span>/g, '');
// htmlNewLineUnreplaced = htmlNewLineUnreplaced.replace(/<span class=["|']url-icon["|']>(.*?)<\/span>/g, '');
// 去掉全文
htmlNewLineUnreplaced = htmlNewLineUnreplaced.replace(/全文<br>/g, '<br>');
htmlNewLineUnreplaced = htmlNewLineUnreplaced.replace(/<a href="(.*?)">全文<\/a>/g, '');