RSSHub/lib/routes/techflowpost/utils.ts

193 lines
5.5 KiB
TypeScript

import { config } from '@/config';
import type { DataItem } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { getAcwScV2ByArg1 } from '../5eplay/utils';
const rootUrl = 'https://www.techflowpost.com';
const apiRootUrl = `${rootUrl}/api`;
const uploadRootUrl = 'https://upload.techflowpost.com';
const locale = 'zh-CN';
let acwScV2Cookie = '';
type ApiListResponse<T> = {
data?: T[];
};
type Label = {
label?: string;
};
type Article = {
id: number;
title: string;
abstract?: string;
picture?: string;
author?: {
name?: string;
};
category?: {
name?: string;
};
labels?: Label[];
created_at?: string;
updated_at?: string;
};
type ArticleDetailResponse = {
article?: {
content?: string;
};
};
type Newsflash = {
id: number;
title: string;
abstract?: string;
url?: string;
created_at?: string;
updated_at?: string;
};
type NewsflashDetailResponse = {
content?: string;
};
function getHeaders(referer: string, cookie = acwScV2Cookie) {
return {
accept: 'application/json, text/plain, */*',
'accept-language': locale,
referer,
'user-agent': config.trueUA,
...(cookie ? { cookie } : {}),
};
}
function getAcwScV2Cookie(response: string) {
const arg1 = response.match(/var arg1='(.*?)';/)?.[1];
if (!arg1) {
return '';
}
return `acw_sc__v2=${getAcwScV2ByArg1(arg1)}`;
}
async function requestApi<T>(endpoint: string, referer: string, searchParams?: Record<string, string | number | boolean>) {
const request = () =>
got.get(`${apiRootUrl}${endpoint}`, {
searchParams,
headers: getHeaders(referer),
});
const { data } = await request();
if (typeof data !== 'string') {
return data as T;
}
const cookie = getAcwScV2Cookie(data);
if (!cookie) {
throw new Error('TechFlow API returned an unexpected non-JSON response.');
}
acwScV2Cookie = cookie;
const retryResponse = await request();
if (typeof retryResponse.data === 'string') {
throw new TypeError('TechFlow API still returned an anti-crawler challenge after retry.');
}
return retryResponse.data as T;
}
function getPictureUrl(picture?: string) {
if (!picture) {
return;
}
return new URL(picture, uploadRootUrl).href;
}
function getCategories(article: Article) {
return [...new Set([article.category?.name, ...(article.labels?.map((label) => label.label) ?? [])].filter(Boolean))] as string[];
}
function getArticleItem(article: Article, content?: string): DataItem {
const link = `${rootUrl}/${locale}/article/${article.id}`;
const categories = getCategories(article);
const image = getPictureUrl(article.picture);
return {
title: article.title,
author: article.author?.name,
link,
category: categories.length ? categories : undefined,
pubDate: article.created_at ? parseDate(article.created_at) : undefined,
updated: article.updated_at ? parseDate(article.updated_at) : undefined,
description: content || article.abstract,
image,
};
}
function getNewsflashItem(newsflash: Newsflash, content?: string): DataItem {
return {
title: newsflash.title,
link: `${rootUrl}/${locale}/newsletter/${newsflash.id}`,
pubDate: newsflash.created_at ? parseDate(newsflash.created_at) : undefined,
updated: newsflash.updated_at ? parseDate(newsflash.updated_at) : undefined,
description: content || newsflash.abstract,
};
}
async function getArticleItems({ category, limit }: { category?: string; limit: string | number }) {
const link = `${rootUrl}/${locale}/article`;
const searchParams: Record<string, string | number> = {
page: 1,
page_size: limit,
};
if (category) {
searchParams.category_id = category;
}
const response = await requestApi<ApiListResponse<Article>>('/client/articles', link, searchParams);
if (!Array.isArray(response.data)) {
throw new TypeError('TechFlow API returned an invalid article list.');
}
return await Promise.all(
response.data.map((article) =>
cache.tryGet(`techflowpost:article:${article.id}`, async () => {
const itemLink = `${rootUrl}/${locale}/article/${article.id}`;
const detail = await requestApi<ArticleDetailResponse>(`/client/articles/${article.id}`, itemLink);
return getArticleItem(article, detail.article?.content);
})
)
);
}
async function getNewsflashItems(limit: string | number) {
const link = `${rootUrl}/${locale}/newsletter`;
const response = await requestApi<ApiListResponse<Newsflash>>('/client/newsflashes', link, {
page: 1,
page_size: limit,
});
if (!Array.isArray(response.data)) {
throw new TypeError('TechFlow API returned an invalid newsflash list.');
}
return await Promise.all(
response.data.map((newsflash) =>
cache.tryGet(`techflowpost:newsflash:${newsflash.id}`, async () => {
const itemLink = `${rootUrl}/${locale}/newsletter/${newsflash.id}`;
const detail = await requestApi<NewsflashDetailResponse>(`/client/newsflashes/${newsflash.id}`, itemLink);
return getNewsflashItem(newsflash, detail.content);
})
)
);
}
export { getArticleItems, getNewsflashItems, rootUrl };