fix(route): housekeeping (#22851)

* fix(route): agirls

* fix(route): hakkatv

* fix(route): meteor

* fix(route): dcfever

* fix(route): netflix

* fix(route): immich

* fix(route): 0818tuan

* fix(route): mhlw

* fix(route): indienova

* fix(route): anthropic

* fix(route): tingtingfm

* fix(route): pubscholar

* fix(route): byteclicks

* fix: correct path format in GameDB route

* fix: add 'game' category to GameDB route

* fix: reduce concurrency in item parsing for Byteclicks route
This commit is contained in:
Tony 2026-07-28 00:44:30 +08:00 committed by GitHub
parent f8764e37aa
commit a1a21b59de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 266 additions and 182 deletions

View File

@ -44,7 +44,7 @@ async function handler(ctx) {
link: item.attr('href').startsWith('http') ? item.attr('href') : `${baseUrl}${item.attr('href')}`,
};
})
.filter((i) => !i.link.includes('m.0818tuan.com/tb1111.php'));
.filter((i) => !i.link.includes('m.0818tuan.com/tb1111.php') && !i.link.includes('www.0818tuan.com/pdd/zudui.php'));
const items = await Promise.all(
list.map((item) =>

View File

@ -1,7 +1,7 @@
import { load } from 'cheerio';
import type { Route } from '@/types';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { baseUrl } from './utils';
@ -33,9 +33,9 @@ async function handler() {
const category = 'topic';
const link = `${baseUrl}/${category}`;
const response = await got(`${baseUrl}/${category}`);
const response = await ofetch(`${baseUrl}/${category}`);
const $ = load(response.data);
const $ = load(response);
const items = $('.ag-topic')
.toArray()

View File

@ -2,7 +2,7 @@ import { load } from 'cheerio';
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { baseUrl, parseArticle } from './utils';
@ -32,9 +32,9 @@ export const route: Route = {
async function handler(ctx) {
const topic = ctx.req.param('topic');
const link = `${baseUrl}/topic/${topic}`;
const response = await got(link);
const response = await ofetch(link);
const $ = load(response.data);
const $ = load(response);
const ldJson = JSON.parse($('script[type="application/ld+json"]').text());
const list = $('.ag-post-item__link')
.toArray()

View File

@ -1,13 +1,13 @@
import { load } from 'cheerio';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
const baseUrl = 'https://agirls.aotter.net';
const parseArticle = async (item) => {
const detailResponse = await got(item.link);
const content = load(detailResponse.data);
const detailResponse = await ofetch(item.link);
const content = load(detailResponse);
item.category = [
...new Set(
@ -17,11 +17,12 @@ const parseArticle = async (item) => {
),
];
const ldJson = JSON.parse(content('script[type="application/ld+json"]').text());
const newsArticle = ldJson['@graph'].find((g) => g['@type'] === 'NewsArticle');
item.description = content('.ag-article__content').html();
item.pubDate = parseDate(ldJson['@graph'][0].datePublished); // 2023-07-05T12:11:36+08:00
item.updated = parseDate(ldJson['@graph'][0].dateModified); // 2023-07-05T12:11:36+08:00
item.author = ldJson['@graph'][0].author.map((a) => a.name).join(', ');
item.pubDate = parseDate(newsArticle.datePublished); // 2023-07-05T12:11:36+08:00
item.updated = parseDate(newsArticle.dateModified); // 2023-07-05T12:11:36+08:00
item.author = newsArticle.author.map((a) => a.name).join(', ');
return item;
};

View File

@ -2,7 +2,7 @@ import { load } from 'cheerio';
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { baseUrl, parseArticle } from './utils';
@ -36,11 +36,11 @@ export const route: Route = {
async function handler(ctx) {
const { category = '' } = ctx.req.param();
const link = `${baseUrl}/posts${category ? `/${category}` : ''}`;
const response = await got(link);
const response = await ofetch(link);
const $ = load(response.data);
const $ = load(response);
const list = $('.ag-post-item__link')
const list = $('.ag-post-list .ag-post-item__link')
.toArray()
.map((item) => {
item = $(item);

View File

@ -4,6 +4,7 @@ import pMap from 'p-map';
import type { DataItem, Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
export const route: Route = {
path: '/engineering',
@ -28,17 +29,17 @@ async function handler(ctx) {
const $ = load(response);
const limit = ctx.req.query('limit') ? Number(ctx.req.query('limit')) : 20;
const list: DataItem[] = $('a[class*="cardLink"]')
const list: DataItem[] = $('a[class$="cardLink"]')
.toArray()
.map((element) => {
const $e = $(element);
const href = $e.attr('href') ?? '';
const fullLink = href.startsWith('http') ? href : `${baseUrl}${href}`;
const pubDate = $e.find('div[class*="date"]').text();
const dateText = $e.find('div[class$="date"]').text();
return {
title: $e.find('h2, h3').text(),
link: fullLink,
pubDate,
pubDate: dateText ? parseDate(dateText, 'MMM D, YYYY') : undefined,
};
})
.filter((item) => item.title && item.link)
@ -51,7 +52,7 @@ async function handler(ctx) {
const response = await ofetch(item.link!);
const $ = load(response);
const content = $('article > div > div[class*="__body"]');
const content = $('article > div > div[class$="__body"]');
content.find('img').each((_, e) => {
const $e = $(e);
@ -65,6 +66,8 @@ async function handler(ctx) {
});
item.description = content.html();
const dateText = $('p[class$="date"]').text().replace('Published', '').trim();
item.pubDate ||= dateText ? parseDate(dateText, 'MMM D, YYYY') : undefined;
return item;
}),

View File

@ -1,38 +1,38 @@
import { load } from 'cheerio';
import pMap from 'p-map';
import type { Route } from '@/types';
import got from '@/utils/got';
import { PRESETS } from '@/utils/header-generator';
import ofetch from '@/utils/ofetch';
import { parseItem } from './utils';
const baseUrl = 'https://byteclicks.com';
import { baseUrl, parseItem, parseList } from './utils';
export const route: Route = {
path: '/',
categories: ['new-media'],
example: '/byteclicks',
radar: [
{
source: ['byteclicks.com/'],
target: '',
},
],
name: 'Unknown',
name: '首页',
maintainers: ['TonyRL'],
handler,
url: 'byteclicks.com/',
};
async function handler(ctx) {
const { data } = await got(`${baseUrl}/wp-json/wp/v2/posts`, {
searchParams: {
per_page: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 100,
},
});
const response = await ofetch(baseUrl, { headerGeneratorOptions: PRESETS.MODERN_WINDOWS_CHROME });
const $ = load(response);
const items = parseItem(data);
const list = parseList($).slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : undefined);
const items = await pMap(list, (item) => parseItem(item), { concurrency: 5 });
return {
title: '字节点击 - 聚合全球优质资源,跟踪世界前沿科技',
description:
'byteclicks.com 最专业的前沿科技网站。聚合全球优质资源跟踪世界前沿科技精选推荐一些很棒的互联网好资源好工具好产品。寻找有前景好项目、找论文、找报告、找数据、找课程、找电子书上byteclicksbyteclicks.com是投资人、科研学者、学生每天必看的网站。',
image: 'https://byteclicks.com/wp-content/themes/RK-Blogger/images/wbolt.ico',
title: $('head title').text(),
description: $('head meta[name="description"]').attr('content'),
image: $('head link[rel="shortcut icon"]').attr('href'),
link: baseUrl,
item: items,
};

View File

@ -1,23 +1,17 @@
import { load } from 'cheerio';
import pMap from 'p-map';
import type { Route } from '@/types';
import got from '@/utils/got';
import { PRESETS } from '@/utils/header-generator';
import ofetch from '@/utils/ofetch';
import { parseItem } from './utils';
const baseUrl = 'https://byteclicks.com';
import { baseUrl, parseItem, parseList } from './utils';
export const route: Route = {
path: '/tag/:tag',
categories: ['new-media'],
example: '/byteclicks/tag/人工智能',
parameters: { tag: '标签可在URL中找到' },
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['byteclicks.com/tag/:tag'],
@ -31,27 +25,18 @@ export const route: Route = {
async function handler(ctx) {
const tag = ctx.req.param('tag');
const { data: search } = await got(`${baseUrl}/wp-json/wp/v2/tags`, {
searchParams: {
search: tag,
per_page: 100,
},
});
const tagData = search.find((item) => item.name === tag);
const link = `${baseUrl}/tag/${tag}`;
const { data } = await got(`${baseUrl}/wp-json/wp/v2/posts`, {
searchParams: {
per_page: ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 100,
tags: tagData.id,
},
});
const response = await ofetch(link, { headerGeneratorOptions: PRESETS.MODERN_WINDOWS_CHROME });
const $ = load(response);
const items = parseItem(data);
const list = parseList($).slice(0, ctx.req.query('limit') ? Number(ctx.req.query('limit')) : undefined);
const items = await pMap(list, (item) => parseItem(item), { concurrency: 5 });
return {
title: `${tagData.name} - 字节点击`,
image: 'https://byteclicks.com/wp-content/themes/RK-Blogger/images/wbolt.ico',
link: tagData.link,
title: $('head title').text(),
image: $('head link[rel="shortcut icon"]').attr('href'),
link,
item: items,
};
}

View File

@ -1,11 +1,38 @@
import type { CheerioAPI } from 'cheerio';
import { load } from 'cheerio';
import type { DataItem } from '@/types';
import cache from '@/utils/cache';
import { PRESETS } from '@/utils/header-generator';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const parseItem = (data) =>
data.map((item) => ({
title: item.title.rendered,
description: item.content.rendered,
pubDate: parseDate(item.date_gmt),
link: item.link,
}));
export const baseUrl = 'https://byteclicks.com';
export { parseItem };
export const parseList = ($: CheerioAPI) =>
$('article.post')
.toArray()
.map((item) => {
const $item = $(item);
const a = $item.find('a.post-title');
return {
title: a.text(),
link: a.attr('href'),
pubDate: timezone(parseDate($item.find('.meta-item.primary em').text(), 'YYYY.MM.DD'), 8),
category: $item.find('.cate-tag').text(),
};
});
export const parseItem = (item: DataItem) =>
cache.tryGet(item.link!, async () => {
const response = await ofetch(item.link!, { headerGeneratorOptions: PRESETS.MODERN_WINDOWS_CHROME });
const $ = load(response);
const content = $('.article-detail');
content.find('.erphp-wppay, .copyright-message').remove();
item.author = $('.meta-author em').text().trim();
item.description = content.html()?.trim();
return item;
});

View File

@ -39,7 +39,8 @@ async function handler(ctx) {
title: item.text(),
link: new URL(item.attr('href'), link).href,
};
});
})
.filter((item, index, arr) => arr.findIndex((i) => i.link === item.link) === index);
const items = await Promise.all(list.map((item) => parseItem(item)));

View File

@ -81,16 +81,16 @@ const parseItem = (item) =>
});
content.find('p a').each((_, e) => {
e = $(e);
if (e.text().startsWith('下一頁為')) {
e.remove();
const $e = $(e);
if ($e.text().startsWith('下一頁為')) {
$e.remove();
}
});
content.find('iframe').each((_, e) => {
e = $(e);
if (e.attr('src').startsWith('https://www.facebook.com/plugins/like.php')) {
e.remove();
const $e = $(e);
if ($e.attr('src')?.startsWith('https://www.facebook.com/plugins/like.php')) {
$e.remove();
}
});

View File

@ -1,6 +1,6 @@
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
@ -41,41 +41,44 @@ async function handler(ctx) {
const allData = type
? (
await got(`${apiUrl}/api/news/index`, {
searchParams: {
per: 4,
await ofetch(`${apiUrl}/api/news/index`, {
query: {
per: 8,
'sort[created_at]': 'desc',
type,
keywords: '',
},
})
).data.data
).data
: await Promise.all(
typeMap.map(async (t) => {
const { data } = await got(`${apiUrl}/api/news/index`, {
searchParams: {
per: 4,
const { data } = await ofetch(`${apiUrl}/api/news/index`, {
query: {
per: 8,
'sort[created_at]': 'desc',
type: t,
keywords: '',
},
});
return data.data;
return data;
})
);
const list = allData.flat().map((item) => ({
title: item.title,
pubDate: timezone(parseDate(item.created_at), 8),
author: item.author,
link: `${baseUrl}/news-detail/${item.id}`,
id: item.id,
}));
const list = allData
.flat()
.filter((item, index, arr) => arr.findIndex((i) => i.id === item.id) === index)
.map((item) => ({
title: item.title,
pubDate: timezone(parseDate(item.created_at), 8),
author: item.author,
link: `${baseUrl}/news-detail/${item.id}`,
id: item.id,
}));
const items = await Promise.all(
list.map((item) =>
cache.tryGet(item.link, async () => {
const { data } = await got(`${apiUrl}/api/news/read/${item.id}`);
const data = await ofetch(`${apiUrl}/api/news/read/${item.id}`);
item.category = data.tag.map((t) => t.tag);
item.description = data.content.replaceAll('\n', '<br>');
delete item.id;

View File

@ -1,5 +1,3 @@
import { load } from 'cheerio';
import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
@ -19,31 +17,57 @@ export const route: Route = {
handler,
};
const ghType = {
pr: 'pull',
issue: 'issues',
discussion: 'discussions',
};
const parseGithubLink = (arg: string) => {
const number = arg.match(/(?:number: )?([\d_]+)/)?.[1].replaceAll('_', '');
const type = arg.match(/type: '(\w+)'/)?.[1] ?? 'pr';
return `https://github.com/immich-app/immich/${ghType[type]}/${number}`;
};
const matchString = (entry: string, key: string) => {
const m = entry.match(new RegExp(`${key}:\\s*(?:'((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)")`));
return (m?.[1] ?? m?.[2])?.replaceAll(/\\(.)/g, '$1');
};
async function handler() {
const baseUrl = 'https://immich.app';
const link = `${baseUrl}/cursed-knowledge/`;
const response = await ofetch(link);
const $ = load(response);
const [source, feed] = await Promise.all([
ofetch('https://raw.githubusercontent.com/immich-app/static-pages/main/apps/root.immich.app/src/routes/cursed-knowledge/+page.svelte'),
ofetch(`${baseUrl}/blog/feed.json`, { responseType: 'json' }),
]);
const items = $('div.justify-around ul li')
.toArray()
.map((item) => {
const $item = $(item);
const href = $item.find('a').attr('href');
const title = $item.find('section p').first().text();
return {
title,
description: $item.find('section p').last().text(),
link: href ?? `${link}#${title}`,
pubDate: parseDate($item.find('div.justify-start').text()),
};
});
const entries = source
.slice(source.indexOf('const items'))
.split(/\n {4}(?:withBlog\()?\{\n/)
.slice(1);
const items = entries.map((entry) => {
const blogId = entry.match(/id: '([^']+)'/)?.[1];
const blogPost = blogId && feed.items.find((post) => post.id.endsWith(blogId));
const gh = entry.match(/link: asGithubLink\(([^)]*)\)/);
const date = entry.match(/new Date\((\d+), (\d+), (\d+)\)/);
const title = matchString(entry, 'title');
return {
title,
description: matchString(entry, 'description'),
link: (gh ? parseGithubLink(gh[1]) : undefined) ?? entry.match(/href: '([^']+)'/)?.[1] ?? blogPost?.url,
guid: `${link}#${title}`,
pubDate: date ? parseDate(`${date[1]}-${Number(date[2]) + 1}-${date[3]}`, 'YYYY-M-D') : blogPost ? parseDate(blogPost.date_published) : undefined,
};
});
return {
title: $('head title').text(),
description: $('p.text-center').text(),
image: `${baseUrl}${$('head link[rel="icon"]').attr('href')}`,
title: 'Cursed Knowledge | Immich',
description: 'Cursed knowledge we have learned as a result of building Immich that we wish we never knew.',
image: `${baseUrl}/favicon.ico`,
link,
item: items,
};

View File

@ -3,12 +3,26 @@ import { load } from 'cheerio';
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { parseDate, parseRelativeDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
export const route: Route = {
path: '/gamedb/recent',
name: 'Unknown',
name: 'GameDB 游戏库',
path: '/gamedb/recent/:platform?',
example: '/indienova/gamedb/recent',
categories: ['game'],
parameters: {
platform: {
description: '平台,留空为 `all`',
options: [
{ value: 'all', label: '全部' },
{ value: 'ps4', label: 'PS4' },
{ value: 'xboxone', label: 'XBOX One' },
{ value: 'nintendo-switch', label: 'Nintendo Switch' },
],
default: 'all',
},
},
maintainers: ['TonyRL'],
handler,
};
@ -23,14 +37,15 @@ async function handler(ctx) {
const list = $('.related-game')
.toArray()
.map((item) => {
item = $(item);
const $item = $(item);
return {
title: item
title: $item
.find('span')
.contents()
.filter((_, el) => el.nodeType === 3)
.text(),
link: new URL(item.find('a').attr('href'), baseUrl).href,
link: new URL($item.find('a').attr('href')!, baseUrl).href,
pubDate: parseRelativeDate($item.find('small').first().text()),
};
});
@ -50,7 +65,7 @@ async function handler(ctx) {
article.find('#showHiddenText').remove();
item.description = $('.cover-image').prop('outerHTML') + $('.tab-container').html() + article.html();
item.pubDate = $('.gamedb-release').length ? timezone(parseDate($('.gamedb-release').text().replaceAll(/[()]/g, '')), 8) : null;
item.pubDate = $('.gamedb-release').length ? timezone(parseDate($('.gamedb-release').text().replaceAll(/[()]/g, '')), 8) : item.pubDate;
return item;
})

View File

@ -1,6 +1,5 @@
import type { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import { baseUrl, getBoards, renderDesc } from './utils';
@ -33,8 +32,9 @@ async function handler(ctx) {
board = boardInfo.id;
}
const { data: response } = await got.post(`${baseUrl}/article/get_new_articles`, {
json: {
const response = await ofetch(`${baseUrl}/article/get_new_articles`, {
method: 'POST',
body: {
boardId: board,
isCollege: false,
page: 0,
@ -44,17 +44,14 @@ async function handler(ctx) {
const result = JSON.parse(decodeURIComponent(response.result));
const items = await Promise.all(
result.map((item) =>
cache.tryGet(`meteor:${item.id}`, () => ({
title: item.title,
description: renderDesc(item.content),
link: `${baseUrl}/article/${item.shortId}`,
author: item.authorAlias,
pubDate: parseDate(item.createdAt),
}))
)
);
const items = result.map((item) => ({
title: item.title,
description: renderDesc(item.content),
link: `${baseUrl}/article/${item.shortId}`,
author: item.authorAlias,
pubDate: parseDate(item.createdAt),
category: item.tagNameList,
}));
return {
title: `${board === 'all' ? '全部看板' : boardInfo.title} | Meteor 學生社群`,

View File

@ -1,5 +1,5 @@
import cache from '@/utils/cache';
import got from '@/utils/got';
import ofetch from '@/utils/ofetch';
import { renderMedia } from './templates/desc';
@ -7,28 +7,32 @@ const baseUrl = 'https://meteor.today';
const getBoards = () =>
cache.tryGet('meteor:boards', async () => {
const { data: response } = await got.post(`${baseUrl}/board/get_boards`, {
json: {
const response = await ofetch(`${baseUrl}/board/get_boards`, {
method: 'POST',
body: {
isCollege: 'false',
},
});
return JSON.parse(decodeURIComponent(response.result)).map((item) => ({
title: `${item.category ? `${item.category} - ` : ''}${item.name}`,
description: item.id,
feedDescription: item.description,
category: item.articleCategory,
link: `${baseUrl}/board/${item.alias ?? item.name}`,
alias: item.alias,
imgUrl: item.imageUrl,
id: item.id,
}));
return JSON.parse(decodeURIComponent(response.result))
.map((item) => ({
title: `${item.category ? `${item.category} - ` : ''}${item.name}`,
description: item.id,
feedDescription: item.description,
category: item.articleCategory,
link: `${baseUrl}/board/${item.alias ?? item.name}`,
alias: item.alias,
imgUrl: item.imageUrl,
id: item.id,
}))
.filter((item, index, arr) => arr.findIndex((i) => i.link === item.link) === index);
});
const renderDesc = (desc) => {
const youTube = /(?:https?:\/\/)?(?:www\.)?youtu\.?be.*(?:v=|v\/|\/)([\w-]+)&?/g;
const matchYouTube = desc.match(youTube);
const matchImgur = desc.match(/https:\/\/i.imgur.com\/\w*.(jpg|png|gif|jpeg)/g);
const matchImage = desc.match(/https:\/\/storage\.meteor\.today\/image\/[\da-f]{24}\.(jpg|png)/g);
const matchVideo = desc.match(/(https:\/\/storage\.meteor\.today\/video\/[\da-f]{24}\.)(mp4|mov|avi|flv|wmv|mpeg|mkv)/gi);
const matchSticker = desc.match(/assets\/images\/stickers\/(duck|ep2|ep1)\/\w*.(jpg|png|gif|jpeg)/g);
const matchEmoji = desc.match(/assets\/images\/emoji\/\w*.(jpg|png|gif|jpeg)/g);
@ -49,6 +53,15 @@ const renderDesc = (desc) => {
);
}
}
if (matchImage) {
for (const img of matchImage) {
desc = desc.replace(img, () =>
renderMedia({
img,
})
);
}
}
if (matchVideo) {
for (const video of matchVideo) {
desc = desc.replace(video, () =>

View File

@ -22,6 +22,15 @@ export const route: Route = {
url: 'www.mhlw.go.jp/toukei/list/30-1a.html',
};
const parseJapaneseDate = (text: string) => {
const normalized = text
.replaceAll(/[^]+/g, '')
// oxlint-disable-next-line regexp/no-obscure-range
.replaceAll(/[-]/g, (c) => String.fromCodePoint(c.codePointAt(0)! - 0xfee0))
.replace(/^令和(\d+)年/, (_, year) => `${Number(year) + 2018}`);
return parseDate(normalized, 'YYYY年M月D日');
};
async function fetchPage(url: string) {
const raw = await ofetch(url, { responseType: 'arrayBuffer' });
const decoder = new TextDecoder('shift-jis');
@ -62,12 +71,11 @@ async function handler(ctx: Context) {
const $ = load(response);
const dateText = $('.prt-topContents .al-right').text();
const cleanedDate = dateText.replaceAll(/[^]+/g, '');
const content = $('#contentsInner');
content.find('.prt-topContents, .prt-linkNavi, .prt-plugin').remove();
item.title = $('h1#pageTitle').text() || item.title;
item.pubDate = timezone(parseDate(cleanedDate, 'YYYY年M月D日'), 9);
item.title = $('h1#pageTitle').text().trim() || item.title;
item.pubDate = timezone(parseJapaneseDate(dateText), 9);
item.description = content.html()?.trim();
return item;

View File

@ -1,6 +1,7 @@
import { load } from 'cheerio';
import type { Route } from '@/types';
import { collapseWhitespace } from '@/utils/common-utils';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
@ -72,6 +73,7 @@ async function handler() {
title: item.title,
description: item.description,
link: item.link,
guid: collapseWhitespace(item.title) ?? undefined,
pubDate: (item.date ?? item.startDate) ? parseDate(item.date ?? item.startDate) : undefined,
category: item.tags?.json,
image: item.image?.url,

View File

@ -47,14 +47,17 @@ async function handler(ctx) {
},
});
const list = response.content.map((item) => ({
title: (item.is_free || item.links.some((l) => l.is_open_access) ? '「Open Access」' : '') + sanitizeHtml(item.title, { allowedTags: [], allowedAttributes: {} }),
description: item.abstracts + `<br>${item.links.map((link) => `<a href="${link.url}">${link.is_open_access ? '「Open Access」' : ''}${link.name}</a>`).join('<br>')}`,
author: item.author.join('; '),
pubDate: parseDate(item.date),
category: item.keywords.map((keyword) => sanitizeHtml(keyword, { allowedTags: [], allowedAttributes: {} })),
link: `${baseUrl}/${category}/${getArticleLink(item.id)}`,
}));
const list = response.content.map((item) => {
const date = item.date ?? item.issue_date ?? item.year;
return {
title: (item.is_free || item.links?.some((l) => l.is_open_access) ? '「Open Access」' : '') + sanitizeHtml(item.title, { allowedTags: [], allowedAttributes: {} }),
description: item.abstracts + `<br>${(item.links ?? []).map((link) => `<a href="${link.url}">${link.is_open_access ? '「Open Access」' : ''}${link.name}</a>`).join('<br>')}`,
author: (item.author ?? item.inventors)?.join('; '),
pubDate: date ? parseDate(String(date), ['YYYY-MM-DD', 'YYYYMMDD', 'YYYY']) : undefined,
category: item.keywords?.map((keyword) => sanitizeHtml(keyword, { allowedTags: [], allowedAttributes: {} })),
link: `${baseUrl}/${category}/${getArticleLink(item.id)}`,
};
});
return {
title: 'PubScholar 公益学术平台',

View File

@ -5,10 +5,10 @@ interface Link {
}
interface Content {
date: string;
date?: string;
attachments: any[];
keywords: string[];
year: number;
keywords?: string[];
year?: number | string;
source: string;
title: string;
type: string;
@ -17,21 +17,23 @@ interface Content {
school: any[];
first_page: string;
local_links: any[];
links: Link[];
links?: Link[];
id: string;
graduation_institution: any[];
cn_type: string;
article_type: string;
issue: string;
abstracts: string;
author: string[];
author?: string[];
inventors?: string[];
issue_date?: string;
last_page: string;
degree: string;
tutor: any[];
semantic_entities: object;
volume: string;
source_list: string[];
is_free: boolean;
is_free?: boolean;
}
export interface Resource {

View File

@ -50,7 +50,7 @@ async function handler(ctx) {
const mobileBaseUrl = 'https://mobile.tingtingfm.com';
const params = {
version: 'h5_5.16',
version: 'h5_6.3.2',
client: getClientVal(30),
h_program_id: programId,
};

View File

@ -1,20 +1,20 @@
/* eslint-disable unicorn/prefer-code-point */
import md5 from '@/utils/md5';
const SALT = '1Ftjv0bfpVmqbE38';
const randomChar = () => {
const random = Math.floor(62 * Math.random());
if (random < 10) {
return random;
}
if (random < 36) {
return String.fromCodePoint(random + 55);
}
return String.fromCodePoint(random + 61);
};
const getClientVal = (length) => {
let result = '';
const randomChar = () => {
const random = Math.floor(62 * Math.random());
if (random < 10) {
return random;
}
if (random < 36) {
return String.fromCharCode(random + 55);
}
return String.fromCharCode(random + 61);
};
while (result.length < length) {
result += randomChar();
}