mirror of
https://github.com/DIYgod/RSSHub.git
synced 2026-09-24 23:12:34 +08:00
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:
co-authored by
SettingDust
parent
839d3eafa5
commit
40c06ff977
@@ -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
@@ -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
@@ -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')}` : ''}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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, '');
|
||||
|
||||
Reference in New Issue
Block a user