RSSHub/lib/routes/weibo/timeline.ts

195 lines
8.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import querystring from 'node:querystring';
import { config } from '@/config';
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { fallback, queryToBoolean } from '@/utils/readable-social';
import weiboUtils from './utils';
export const route: Route = {
path: '/timeline/:uid/:feature?/:routeParams?',
categories: ['social-media'],
example: '/weibo/timeline/3306934123',
parameters: { uid: '用户的uid', feature: '过滤类型ID0全部、1原创、2图片、3视频、4音乐默认为0。', routeParams: '额外参数;请参阅上面的说明和表格' },
features: {
requireConfig: [
{
name: 'WEIBO_APP_KEY',
description: '',
},
{
name: 'WEIBO_REDIRECT_URL',
description: '',
},
],
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: '个人时间线',
maintainers: ['zytomorrow', 'DIYgod', 'Rongronggg9'],
handler,
description: `::: warning
需要对应用户打开页面进行授权生成 token 才能生成内容
自部署需要申请并配置微博 key具体见部署文档
:::`,
};
async function handler(ctx) {
const uid = ctx.req.param('uid');
const feature = ctx.req.param('feature') || 0;
const routeParams = ctx.req.param('routeParams') || undefined;
const token = await cache.get('weibotimelineuid' + uid, false);
let displayVideo = '1';
let displayArticle = '0';
let displayComments = '0';
let showBloggerIcons = '0';
if (routeParams) {
if (routeParams === '1' || routeParams === '0') {
displayVideo = routeParams;
} else {
const routeParams = querystring.parse(ctx.req.param('routeParams'));
displayVideo = fallback(undefined, queryToBoolean(routeParams.displayVideo), true) ? '1' : '0';
displayArticle = fallback(undefined, queryToBoolean(routeParams.displayArticle), false) ? '1' : '0';
displayComments = fallback(undefined, queryToBoolean(routeParams.displayComments), false) ? '1' : '0';
showBloggerIcons = fallback(undefined, queryToBoolean(routeParams.showBloggerIcons), false) ? '1' : '0';
}
}
if (token) {
const userInfo = await cache.tryGet(
`weibo:timeline:userInfo:${uid}`,
async () => {
const _r = await got({
method: 'get',
url: `https://m.weibo.cn/api/container/getIndex?type=uid&value=${uid}`,
headers: {
Referer: 'https://m.weibo.cn/',
},
});
return _r.data.data.userInfo;
},
config.cache.routeExpire,
false
);
const name = userInfo.screen_name;
const description = userInfo.description;
const profileImageUrl = userInfo.profile_image_url;
const response = await cache.tryGet(
`weibo:timeline:${uid}`,
async () => {
const _r = await got(`https://api.weibo.com/2/statuses/home_timeline.json?access_token=${token}&count=100&feature=${feature}`);
return _r.data;
},
config.cache.routeExpire,
false
);
// 检查token失效
if (response.error !== undefined) {
const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0' } = config.weibo;
ctx.status = 302;
ctx.set({
'Cache-Control': 'no-cache',
});
ctx.set('redirect', `https://api.weibo.com/oauth2/authorize?client_id=${app_key}&redirect_uri=${redirect_url}${routeParams ? `&state=${routeParams}` : ''}`);
return;
}
const resultItem = await Promise.all(
response.statuses.map(async (item) => {
const key = `weibotimelineurl${item.user.id}${item.id}`;
const data = await cache.tryGet(key, () => weiboUtils.getShowData(uid, item.id));
// 是否通过api拿到了data
const isDataOK = data?.text;
if (isDataOK) {
item = data;
}
// 转发的长微博处理
const retweet = item.retweeted_status;
if (retweet?.isLongText) {
const retweetData = await cache.tryGet(`weibo:retweeted:${retweet.user.id}:${retweet.id}`, () => weiboUtils.getShowData(retweet.user.id, retweet.id));
if (retweetData?.text) {
item.retweeted_status.text = retweetData.text;
}
}
const guid = `https://weibo.com/${uid}/${item.id}`;
// not using formatExtended.guid in order not to introduce breaking change
const formatExtended = weiboUtils.formatExtended(ctx, item, uid);
let description = formatExtended.description;
const pubDate = isDataOK ? parseDate(data.created_at) : parseDate(item.created_at);
// 视频的处理
if (displayVideo === '1') {
// 含被转发微博时需要从被转发微博中获取视频
description = item.retweeted_status ? weiboUtils.formatVideo(description, item.retweeted_status) : weiboUtils.formatVideo(description, item);
}
// 评论的处理
if (displayComments === '1') {
description = await weiboUtils.formatComments(ctx, description, item, showBloggerIcons);
}
// 文章的处理
if (displayArticle === '1') {
// 含被转发微博时需要从被转发微博中获取文章
description = await (item.retweeted_status ? weiboUtils.formatArticle(ctx, description, item.retweeted_status) : weiboUtils.formatArticle(ctx, description, item));
}
return {
...formatExtended,
guid,
description,
pubDate,
author: item.user.screen_name,
};
})
);
return weiboUtils.sinaimgTvax({
title: `个人微博时间线--${name}`,
link: `http://weibo.com/${uid}/`,
description,
image: profileImageUrl,
item: resultItem,
});
}
if (uid === '0' || ctx.req.query()) {
const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0', app_secret = '' } = config.weibo;
const code = ctx.req.query('code');
const routeParams = ctx.req.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;
const uid = rep.data.uid;
const expires_in = rep.data.expires_in;
await cache.set('weibotimelineuid' + uid, token, expires_in);
ctx.set({
'Content-Type': 'text/html; charset=UTF-8',
'Cache-Control': 'no-cache',
});
ctx.html(`<script>window.location = '/weibo/timeline/${uid}${routeParams ? `/${routeParams}` : ''}'</script>`);
}
} else {
const { app_key = '', redirect_url = ctx.req.origin + '/weibo/timeline/0' } = config.weibo;
ctx.status = 302;
ctx.set({
'Cache-Control': 'no-cache',
});
ctx.set('redirect', `https://api.weibo.com/oauth2/authorize?client_id=${app_key}&redirect_uri=${redirect_url}${routeParams ? `&state=${feature}/${routeParams.replaceAll('&', '%26')}` : ''}`);
}
}