rsshub
Version:
Make RSS Great Again!
99 lines (89 loc) • 3.38 kB
text/typescript
import { Route, ViewType } from '@/types';
import { parseDate } from '@/utils/parse-date';
import sanitizeHtml from 'sanitize-html';
import puppeteer from '@/utils/puppeteer';
import logger from '@/utils/logger';
import cache from '@/utils/cache';
import { config } from '@/config';
export const route: Route = {
path: '/user/:id',
categories: ['social-media'],
example: '/sotwe/user/_RSSHub',
parameters: {
id: 'Twitter username',
},
features: {
requireConfig: false,
requirePuppeteer: true,
antiCrawler: true,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['www.sotwe.com/:id'],
target: '/user/:id',
},
],
name: 'User timeline - Sotwe',
maintainers: ['TonyRL'],
handler,
view: ViewType.Pictures,
};
const renderMedia = (mediaEntities) =>
mediaEntities
.map((e) => {
switch (e.type) {
case 'photo':
return `<img src="${e.mediaURL}">`;
case 'video': {
const video = e.videoInfo.variants.filter((v) => v.type === 'video/mp4').sort((a, b) => b.bitrate - a.bitrate)[0];
return `<video controls preload="metadata" poster="${e.mediaURL}"><source src="${video.url}" type="video/mp4"></video>`;
}
default:
return '';
}
})
.join('<br>');
const renderDescription = (item) =>
`${renderMedia(item.mediaEntities)}<br>${item.text.replaceAll('\n', '<br>')}${item.quotedStatus ? `<br>${renderDescription(item.quotedStatus)}` : ''}${item.retweetedStatus ? `<br>${renderDescription(item.retweetedStatus)}` : ''}`;
async function handler(ctx) {
const baseUrl = 'https://www.sotwe.com';
const { id } = ctx.req.param();
const data = await cache.tryGet(
`sotwe:user:${id}`,
async () => {
const browser = await puppeteer();
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', (request) => {
['document', 'script', 'xhr', 'fetch'].includes(request.resourceType()) ? request.continue() : request.abort();
});
const apiUrl = `${baseUrl}/api/v3/user/${id}/`;
logger.http(`Requesting ${apiUrl}`);
await page.goto(apiUrl, {
waitUntil: 'domcontentloaded',
});
const response = await page.evaluate(() => document.documentElement.textContent);
await page.close();
await browser.close();
return JSON.parse(response || '{}');
},
config.cache.routeExpire,
false
);
const items = data.data.map((item) => ({
title: sanitizeHtml(item.text.split('\n')[0], { allowedTags: [], allowedAttributes: {} }),
description: renderDescription(item),
link: `https://x.com/${id}/status/${item.id}`,
pubDate: parseDate(item.createdAt, 'x'),
}));
return {
title: `${data.info.name} @${data.info.screenName} - Twitter Profile | Sotwe`,
description: data.info.description,
link: `${baseUrl}/${id}`,
image: data.info.profileImageThumbnail,
item: items,
};
}