rsshub
Version:
Make RSS Great Again!
119 lines (107 loc) • 4.57 kB
text/typescript
import { Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { art } from '@/utils/render';
import { parseDate } from '@/utils/parse-date';
import path from 'node:path';
const baseUrl = 'https://seekingalpha.com';
export const route: Route = {
path: '/:symbol/:category?',
categories: ['finance'],
example: '/seekingalpha/TSM/transcripts',
parameters: { symbol: 'Stock symbol', category: 'Category, see below, `news` by default' },
features: {
antiCrawler: true,
},
radar: [
{
source: ['seekingalpha.com/symbol/:symbol/:category', 'seekingalpha.com/symbol/:symbol/earnings/:category'],
target: '/:symbol/:category',
},
],
name: 'Summary',
maintainers: ['TonyRL'],
handler,
description: `| Analysis | News | Transcripts | Press Releases | Related Analysis |
| -------- | ---- | ----------- | -------------- | ---------------- |
| analysis | news | transcripts | press-releases | related-analysis |`,
};
const getMachineCookie = () =>
cache.tryGet('seekingalpha:machine_cookie', async () => {
const response = await ofetch.raw(baseUrl);
return response.headers.getSetCookie().map((c) => c.split(';')[0]);
});
const apiParams = {
article: {
slug: '/articles',
include: 'author,primaryTickers,secondaryTickers,otherTags,presentations,presentations.slides,author.authorResearch,author.userBioTags,co_authors,promotedService,sentiments',
},
news: {
slug: '/news',
include: 'author,primaryTickers,secondaryTickers,otherTags',
},
pr: {
slug: '/press_releases',
include: 'acquireService,primaryTickers',
},
};
async function handler(ctx) {
const { category = 'news', symbol } = ctx.req.param();
const pageUrl = `${baseUrl}/symbol/${symbol.toUpperCase()}/${category === 'transcripts' ? `earnings/${category}` : category}`;
const machineCookie = await getMachineCookie();
const response = await ofetch(`${baseUrl}/api/v3/symbols/${symbol.toUpperCase()}/${category}`, {
headers: {
cookie: machineCookie.join('; '),
},
query: {
'filter[since]': 0,
'filter[until]': 0,
id: symbol.toLowerCase(),
include: 'author,primaryTickers,secondaryTickers,sentiments',
'page[size]': ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : category === 'news' ? 40 : 20,
'page[number]': 1,
},
});
const list = response.data?.map((item) => ({
title: item.attributes.title,
link: new URL(item.links.self, baseUrl).href,
pubDate: parseDate(item.attributes.publishOn),
author: response.included.find((i) => i.id === item.relationships.author.data.id).attributes.nick,
id: item.id,
articleType: item.links.self.split('/')[1],
}));
const items = list
? await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const response = await ofetch(`${baseUrl}/api/v3${apiParams[item.articleType].slug}/${item.id}`, {
headers: {
cookie: machineCookie.join('; '),
},
query: {
include: apiParams[item.articleType].include,
},
});
item.category = response.included.filter((i) => i.type === 'tag').map((i) => (i.attributes.company ? `${i.attributes.company} (${i.attributes.name})` : i.attributes.name));
item.description =
(response.data.attributes.summary?.length
? art(path.join(__dirname, 'templates/summary.art'), {
summary: response.data.attributes.summary,
})
: '') + response.data.attributes.content;
item.updated = parseDate(response.data.attributes.lastModified);
return item;
})
)
)
: [];
return {
title: response.meta.page.title,
description: response.meta.page.description,
link: pageUrl,
image: 'https://seekingalpha.com/samw/static/images/favicon.svg',
item: items,
allowEmpty: true,
language: 'en-US',
};
}