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:
parent
f8764e37aa
commit
a1a21b59de
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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 最专业的前沿科技网站。聚合全球优质资源,跟踪世界前沿科技,精选推荐一些很棒的互联网好资源好工具好产品。寻找有前景好项目、找论文、找报告、找数据、找课程、找电子书上byteclicks!byteclicks.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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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 學生社群`,
|
||||
|
|
|
|||
|
|
@ -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, () =>
|
||||
|
|
|
|||
|
|
@ -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(/[0-9]/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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 公益学术平台',
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue