diff --git a/lib/routes/0818tuan/index.ts b/lib/routes/0818tuan/index.ts
index 39fcc7f56..6605b42ef 100644
--- a/lib/routes/0818tuan/index.ts
+++ b/lib/routes/0818tuan/index.ts
@@ -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) =>
diff --git a/lib/routes/agirls/topic-list.ts b/lib/routes/agirls/topic-list.ts
index 5d4c48f5e..feeaac798 100644
--- a/lib/routes/agirls/topic-list.ts
+++ b/lib/routes/agirls/topic-list.ts
@@ -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()
diff --git a/lib/routes/agirls/topic.ts b/lib/routes/agirls/topic.ts
index ff67a670c..d8c9ae56c 100644
--- a/lib/routes/agirls/topic.ts
+++ b/lib/routes/agirls/topic.ts
@@ -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()
diff --git a/lib/routes/agirls/utils.ts b/lib/routes/agirls/utils.ts
index 31d2d05a9..b1cafcc48 100644
--- a/lib/routes/agirls/utils.ts
+++ b/lib/routes/agirls/utils.ts
@@ -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;
};
diff --git a/lib/routes/agirls/z-index.ts b/lib/routes/agirls/z-index.ts
index cfab56f9d..c1efd1a38 100644
--- a/lib/routes/agirls/z-index.ts
+++ b/lib/routes/agirls/z-index.ts
@@ -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);
diff --git a/lib/routes/anthropic/engineering.ts b/lib/routes/anthropic/engineering.ts
index 4fc6cdd56..c9528885f 100644
--- a/lib/routes/anthropic/engineering.ts
+++ b/lib/routes/anthropic/engineering.ts
@@ -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;
}),
diff --git a/lib/routes/byteclicks/index.ts b/lib/routes/byteclicks/index.ts
index 67865b163..741758e67 100644
--- a/lib/routes/byteclicks/index.ts
+++ b/lib/routes/byteclicks/index.ts
@@ -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,
};
diff --git a/lib/routes/byteclicks/tag.ts b/lib/routes/byteclicks/tag.ts
index 8a15daa89..0364e3458 100644
--- a/lib/routes/byteclicks/tag.ts
+++ b/lib/routes/byteclicks/tag.ts
@@ -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,
};
}
diff --git a/lib/routes/byteclicks/utils.ts b/lib/routes/byteclicks/utils.ts
index d8073f39e..11a48930e 100644
--- a/lib/routes/byteclicks/utils.ts
+++ b/lib/routes/byteclicks/utils.ts
@@ -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;
+ });
diff --git a/lib/routes/dcfever/reviews.ts b/lib/routes/dcfever/reviews.ts
index 84e49498f..9baa959fb 100644
--- a/lib/routes/dcfever/reviews.ts
+++ b/lib/routes/dcfever/reviews.ts
@@ -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)));
diff --git a/lib/routes/dcfever/utils.tsx b/lib/routes/dcfever/utils.tsx
index 7045d8fa4..070606487 100644
--- a/lib/routes/dcfever/utils.tsx
+++ b/lib/routes/dcfever/utils.tsx
@@ -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();
}
});
diff --git a/lib/routes/hakkatv/type.ts b/lib/routes/hakkatv/news.ts
similarity index 74%
rename from lib/routes/hakkatv/type.ts
rename to lib/routes/hakkatv/news.ts
index f386fbb48..680d66816 100644
--- a/lib/routes/hakkatv/type.ts
+++ b/lib/routes/hakkatv/news.ts
@@ -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', '
');
delete item.id;
diff --git a/lib/routes/immich/cursed-knowledge.ts b/lib/routes/immich/cursed-knowledge.ts
index 93eaffe43..ec3e6e2d9 100644
--- a/lib/routes/immich/cursed-knowledge.ts
+++ b/lib/routes/immich/cursed-knowledge.ts
@@ -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,
};
diff --git a/lib/routes/indienova/gamedb.ts b/lib/routes/indienova/gamedb.ts
index 34efc5a19..69a664b20 100644
--- a/lib/routes/indienova/gamedb.ts
+++ b/lib/routes/indienova/gamedb.ts
@@ -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;
})
diff --git a/lib/routes/meteor/index.ts b/lib/routes/meteor/index.ts
index 4ef131020..4243141cd 100644
--- a/lib/routes/meteor/index.ts
+++ b/lib/routes/meteor/index.ts
@@ -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 學生社群`,
diff --git a/lib/routes/meteor/utils.ts b/lib/routes/meteor/utils.ts
index 15930b981..acee692be 100644
--- a/lib/routes/meteor/utils.ts
+++ b/lib/routes/meteor/utils.ts
@@ -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, () =>
diff --git a/lib/routes/mhlw/monthly-labour-survey.ts b/lib/routes/mhlw/monthly-labour-survey.ts
index b994e062e..4f7b0d9ea 100644
--- a/lib/routes/mhlw/monthly-labour-survey.ts
+++ b/lib/routes/mhlw/monthly-labour-survey.ts
@@ -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;
diff --git a/lib/routes/netflix/research.ts b/lib/routes/netflix/research.ts
index 721de82c4..eba4a2970 100644
--- a/lib/routes/netflix/research.ts
+++ b/lib/routes/netflix/research.ts
@@ -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,
diff --git a/lib/routes/pubscholar/explore.ts b/lib/routes/pubscholar/explore.ts
index cee31ba77..576ff8043 100644
--- a/lib/routes/pubscholar/explore.ts
+++ b/lib/routes/pubscholar/explore.ts
@@ -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 + `
${item.links.map((link) => `${link.is_open_access ? '「Open Access」' : ''}${link.name}`).join('
')}`,
- 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 + `
${(item.links ?? []).map((link) => `${link.is_open_access ? '「Open Access」' : ''}${link.name}`).join('
')}`,
+ 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 公益学术平台',
diff --git a/lib/routes/pubscholar/types.ts b/lib/routes/pubscholar/types.ts
index 8d51adfbe..497ecb6fb 100644
--- a/lib/routes/pubscholar/types.ts
+++ b/lib/routes/pubscholar/types.ts
@@ -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 {
diff --git a/lib/routes/tingtingfm/program.tsx b/lib/routes/tingtingfm/program.tsx
index 0ff2db14b..330b04159 100644
--- a/lib/routes/tingtingfm/program.tsx
+++ b/lib/routes/tingtingfm/program.tsx
@@ -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,
};
diff --git a/lib/routes/tingtingfm/utils.ts b/lib/routes/tingtingfm/utils.ts
index 02d8ec5b7..899d55999 100644
--- a/lib/routes/tingtingfm/utils.ts
+++ b/lib/routes/tingtingfm/utils.ts
@@ -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();
}