UNPKG

rsshub

Version:
213 lines (198 loc) 8.99 kB
import { Route, ViewType } from '@/types'; import cache from '@/utils/cache'; import querystring from 'node:querystring'; import got from '@/utils/got'; import weiboUtils from './utils'; import { config } from '@/config'; import timezone from '@/utils/timezone'; import { parseDate } from '@/utils/parse-date'; import { fallback, queryToBoolean } from '@/utils/readable-social'; export const route: Route = { path: '/user/:uid/:routeParams?', categories: ['social-media'], view: ViewType.SocialMedia, example: '/weibo/user/1195230310', parameters: { uid: '用户 id, 博主主页打开控制台执行 `$CONFIG.oid` 获取', routeParams: '额外参数;请参阅上面的说明和表格;特别地,当 `routeParams=1` 时开启微博视频显示' }, features: { requireConfig: [ { name: 'WEIBO_COOKIES', optional: true, description: '', }, ], requirePuppeteer: false, antiCrawler: true, supportBT: false, supportPodcast: false, supportScihub: false, }, radar: [ { source: ['m.weibo.cn/u/:uid', 'm.weibo.cn/profile/:uid'], target: '/user/:uid', }, { source: ['weibo.com/u/:uid'], target: '/user/:uid', }, { source: ['www.weibo.com/u/:uid'], target: '/user/:uid', }, ], name: '博主', maintainers: ['DIYgod', 'iplusx', 'Rongronggg9', 'Konano'], handler, description: `::: warning 部分博主仅登录可见,未提供 Cookie 的情况下不支持订阅,可以通过打开 \`https://m.weibo.cn/u/:uid\` 验证 :::`, }; async function handler(ctx) { const uid = ctx.req.param('uid'); let displayVideo = '1'; let displayArticle = '0'; let displayComments = '0'; let showRetweeted = '1'; let showBloggerIcons = '0'; if (ctx.req.param('routeParams')) { if (ctx.req.param('routeParams') === '1' || ctx.req.param('routeParams') === '0') { displayVideo = ctx.req.param('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'; showRetweeted = fallback(undefined, queryToBoolean(routeParams.showRetweeted), false) ? '1' : '0'; showBloggerIcons = fallback(undefined, queryToBoolean(routeParams.showBloggerIcons), false) ? '1' : '0'; } } const containerData = await cache.tryGet( `weibo:user:index:${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/u/${uid}`, 'MWeibo-Pwa': 1, 'X-Requested-With': 'XMLHttpRequest', Cookie: config.weibo.cookies, }, }); return _r.data; }, config.cache.routeExpire, false ); const name = containerData.data.userInfo.screen_name; const description = containerData.data.userInfo.description; const profileImageUrl = containerData.data.userInfo.profile_image_url; const containerId = containerData.data.tabsInfo.tabs.find((item) => item.tab_type === 'weibo').containerid; const cards = await cache.tryGet( `weibo:user:cards:${uid}:${containerId}`, async () => { const _r = await got({ method: 'get', url: `https://m.weibo.cn/api/container/getIndex?type=uid&value=${uid}&containerid=${containerId}`, headers: { Referer: `https://m.weibo.cn/u/${uid}`, 'MWeibo-Pwa': 1, 'X-Requested-With': 'XMLHttpRequest', Cookie: config.weibo.cookies, }, }); return _r.data.data.cards; }, config.cache.routeExpire, false ); let resultItems = await Promise.all( cards .filter((item) => { if (item.mblog === undefined) { return false; } if (showRetweeted === '0' && item.mblog.retweeted_status) { return false; } return true; }) .map(async (item) => { // TODO: unify cache key and let weiboUtils.getShowData() handle the cache? It seems safe to do so. // Need more investigation, pending for now since the current version works fine. // TODO: getShowData() on demand? The API seems to return most things we need since 2022/05/21. // Need more investigation, pending for now since the current version works fine. let { bid } = item.mblog; const { retweeted_status, created_at } = item.mblog; if (bid === '') { const url = new URL(item.scheme); bid = url.searchParams.get('mblogid'); item.mblog.bid = bid; } const key = `weibo:user:${bid}`; const data = await cache.tryGet(key, () => weiboUtils.getShowData(uid, bid)); if (data && data.text) { item.mblog.text = data.text; item.mblog.created_at = parseDate(data.created_at); item.mblog.pics = data.pics; if (retweeted_status && data.retweeted_status) { retweeted_status.created_at = data.retweeted_status.created_at; } } else { item.mblog.created_at = timezone(created_at, +8); } // 转发的长微博处理 const retweet = retweeted_status; if (retweet && retweet.isLongText) { // TODO: unify cache key and ... const retweetData = await cache.tryGet(`weibo:retweeted:${retweet.user.id}:${retweet.bid}`, () => weiboUtils.getShowData(retweet.user.id, retweet.bid)); if (retweetData !== undefined && retweetData.text) { retweeted_status.text = retweetData.text; } } const formatExtended = weiboUtils.formatExtended(ctx, item.mblog, uid); let description = formatExtended.description; // 视频的处理 if (displayVideo === '1') { // 含被转发微博时需要从被转发微博中获取视频 description = retweeted_status ? weiboUtils.formatVideo(description, retweeted_status) : weiboUtils.formatVideo(description, item.mblog); } // 评论的处理 if (displayComments === '1') { description = await weiboUtils.formatComments(ctx, description, item.mblog, showBloggerIcons); } // 文章的处理 if (displayArticle === '1') { // 含被转发微博时需要从被转发微博中获取文章 description = await (retweeted_status ? weiboUtils.formatArticle(ctx, description, retweeted_status) : weiboUtils.formatArticle(ctx, description, item.mblog)); } return { ...formatExtended, description, isPinned: item.profile_type_id?.startsWith('proweibotop'), }; }) ); // remove pinned weibo if they are posted before the earliest **ordinary** weibo // there may be multiple pinned weibo at the same time, only remove the ones that meet the above condition const pinnedItems = resultItems.filter((item) => item.isPinned); const ordinaryItems = resultItems.filter((item) => !item.isPinned); if (pinnedItems.length > 0 && ordinaryItems.length > 0) { const earliestOrdinaryPostTime = Math.min(...ordinaryItems.map((i) => i.pubDate).filter(Boolean)); resultItems = ordinaryItems; for (const item of pinnedItems) { if (item.pubDate > earliestOrdinaryPostTime) { resultItems.unshift(item); } } } return weiboUtils.sinaimgTvax({ title: `${name}的微博`, link: `https://weibo.com/${uid}/`, description, image: profileImageUrl, item: resultItems, allowEmpty: true, }); }