feat(route): 增加几米创作最新消息 (#17924)
* feat(route): 增加几米创作最新消息 * chore: update namespace * Update lib/routes/jimmyspa/namespace.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * chore: update jimmyspa * chore: cache the content --------- Co-authored-by: Cedar <FYLSen@users.noreply.github.com>
This commit is contained in:
parent
e20c7a02c6
commit
40e3dad6f4
|
|
@ -0,0 +1,112 @@
|
|||
import { Route, ViewType } from '@/types';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import { art } from '@/utils/render';
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
import cache from '@/utils/cache';
|
||||
import path from 'node:path';
|
||||
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
export const route: Route = {
|
||||
path: '/books/:language',
|
||||
categories: ['design'],
|
||||
view: ViewType.Articles,
|
||||
example: '/jimmyspa/books/tw',
|
||||
parameters: {
|
||||
language: {
|
||||
description: '语言',
|
||||
options: [
|
||||
{ value: 'tw', label: '臺灣正體' },
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'jp', label: '日本語' },
|
||||
],
|
||||
},
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['www.jimmyspa.com/:language/Books'],
|
||||
},
|
||||
],
|
||||
name: 'Books',
|
||||
description: `
|
||||
| language | Description |
|
||||
| --- | --- |
|
||||
| tw | 臺灣正體 |
|
||||
| en | English |
|
||||
| jp | 日本語 |
|
||||
`,
|
||||
maintainers: ['FYLSen'],
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const language = ctx.req.param('language');
|
||||
const baseUrl = 'https://www.jimmyspa.com';
|
||||
const booksListUrl = new URL(`/${language}/Books/Ajax/changeList?year=&keyword=&categoryId=0&page=1`, baseUrl).href;
|
||||
|
||||
const listResponse = await got(booksListUrl);
|
||||
const listPage = load(listResponse.data.view);
|
||||
|
||||
const bookItems = listPage('ul#appendWork li.work_block')
|
||||
.toArray()
|
||||
.map(async (bookElement) => {
|
||||
const bookContent = load(bookElement);
|
||||
const bookTitle = bookContent('p.tit').text();
|
||||
const bookImageRelative = bookContent('div.work_img img').prop('src') || '';
|
||||
const bookImageUrl = bookImageRelative ? baseUrl + bookImageRelative : '';
|
||||
const bookDetailUrl = bookContent('li.work_block').prop('data-route');
|
||||
|
||||
const { renderedDescription, publishDate } = (await cache.tryGet(bookDetailUrl, async () => {
|
||||
const detailResponse = await got(bookDetailUrl);
|
||||
const detailPage = load(detailResponse.data);
|
||||
const bookDescription = detailPage('article.intro_cont').html() || '';
|
||||
const bookInfoWrap = detailPage('div.info_wrap').html() || '';
|
||||
|
||||
const processedDescription = bookDescription.replaceAll(/<img\b[^>]*>/g, (imgTag) =>
|
||||
imgTag.replaceAll(/\b(src|data-src)="(?!http|https|\/\/)([^"]*)"/g, (_, attrName, relativePath) => {
|
||||
const absoluteImageUrl = new URL(relativePath, baseUrl).href;
|
||||
return `${attrName}="${absoluteImageUrl}"`;
|
||||
})
|
||||
);
|
||||
|
||||
const publishDateMatch = bookInfoWrap.match(/<span>(首次出版|First Published|初版)<\/span>\s*<span class="num">([^<]+)<\/span>/);
|
||||
const publishDate = publishDateMatch ? parseDate(publishDateMatch[2] + '-02') : '';
|
||||
|
||||
const renderedDescription = art(path.join(__dirname, 'templates/description.art'), {
|
||||
images: bookImageUrl
|
||||
? [
|
||||
{
|
||||
src: bookImageUrl,
|
||||
alt: bookTitle,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
description: processedDescription,
|
||||
});
|
||||
|
||||
return {
|
||||
renderedDescription,
|
||||
publishDate,
|
||||
};
|
||||
})) as { renderedDescription: string; publishDate: string };
|
||||
|
||||
return {
|
||||
title: bookTitle,
|
||||
link: bookDetailUrl,
|
||||
description: renderedDescription,
|
||||
pubDate: publishDate,
|
||||
content: {
|
||||
html: renderedDescription,
|
||||
text: bookTitle,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
title: `幾米 - 幾米創作(${language})`,
|
||||
link: `${baseUrl}/${language}/Books`,
|
||||
allowEmpty: true,
|
||||
item: await Promise.all(bookItems),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import type { Namespace } from '@/types';
|
||||
|
||||
export const namespace: Namespace = {
|
||||
name: '幾米 JIMMY S.P.A. Official Website',
|
||||
url: 'www.jimmyspa.com',
|
||||
lang: 'zh-TW',
|
||||
};
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import { Route, ViewType } from '@/types';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import got from '@/utils/got';
|
||||
import { load } from 'cheerio';
|
||||
import { art } from '@/utils/render';
|
||||
import { getCurrentPath } from '@/utils/helpers';
|
||||
const __dirname = getCurrentPath(import.meta.url);
|
||||
import path from 'node:path';
|
||||
|
||||
export const route: Route = {
|
||||
path: '/news/:language',
|
||||
categories: ['design'],
|
||||
view: ViewType.Pictures,
|
||||
example: '/jimmyspa/news/tw',
|
||||
parameters: {
|
||||
language: {
|
||||
description: '语言',
|
||||
options: [
|
||||
{ value: 'tw', label: '臺灣正體' },
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'jp', label: '日本語' },
|
||||
],
|
||||
},
|
||||
},
|
||||
radar: [
|
||||
{
|
||||
source: ['www.jimmyspa.com/:language/News'],
|
||||
},
|
||||
],
|
||||
name: 'News',
|
||||
description: `
|
||||
| language | Description |
|
||||
| --- | --- |
|
||||
| tw | 臺灣正體 |
|
||||
| en | English |
|
||||
| jp | 日本語 |
|
||||
`,
|
||||
maintainers: ['FYLSen'],
|
||||
handler,
|
||||
};
|
||||
|
||||
async function handler(ctx) {
|
||||
const language = ctx.req.param('language');
|
||||
const rootUrl = 'https://www.jimmyspa.com';
|
||||
|
||||
const currentUrl = new URL(`/${language}/News/Ajax/changeList?year=&keyword=&categoryId=0&page=1`, rootUrl).href;
|
||||
|
||||
const responseData = await got(currentUrl);
|
||||
|
||||
const $ = load(responseData.data.view);
|
||||
|
||||
const items = $('ul#appendNews li.card_block')
|
||||
.toArray()
|
||||
.map((item) => {
|
||||
const $$ = load(item);
|
||||
const title = $$('a.news_card .info_wrap h3').text();
|
||||
const image = $$('a.news_card .card_img img').prop('src') || '';
|
||||
const link = $$('a.news_card').prop('data-route');
|
||||
const itemdate = $$('a.news_card div.date').html() || '';
|
||||
const pubDate = convertHtmlDateToStandardFormat(itemdate.toString());
|
||||
|
||||
const description = art(path.join(__dirname, 'templates/description.art'), {
|
||||
images: image
|
||||
? [
|
||||
{
|
||||
src: image,
|
||||
alt: title,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
description: $$('a.news_card .info_wrap p').text(),
|
||||
});
|
||||
|
||||
return {
|
||||
title,
|
||||
link,
|
||||
description,
|
||||
pubDate,
|
||||
content: {
|
||||
html: description,
|
||||
text: title,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
title: `幾米 - 最新消息(${language})`,
|
||||
link: `${rootUrl}/${language}/News`,
|
||||
allowEmpty: true,
|
||||
item: items,
|
||||
};
|
||||
}
|
||||
|
||||
function convertHtmlDateToStandardFormat(htmlContent: string): Date | undefined {
|
||||
const dateRegex = /<p>(\d{1,2})<\/p>\s*<p>(\d{1,2})\s*\.\s*([A-Za-z]{3})<\/p>/;
|
||||
const match = htmlContent.match(dateRegex);
|
||||
|
||||
if (match) {
|
||||
const day = Number.parseInt(match[1]) + 1;
|
||||
const year = match[2];
|
||||
const monthAbbreviation = match[3];
|
||||
|
||||
const monthMapping: { [key: string]: string } = {
|
||||
Jan: '01',
|
||||
Feb: '02',
|
||||
Mar: '03',
|
||||
Apr: '04',
|
||||
May: '05',
|
||||
Jun: '06',
|
||||
Jul: '07',
|
||||
Aug: '08',
|
||||
Sep: '09',
|
||||
Oct: '10',
|
||||
Nov: '11',
|
||||
Dec: '12',
|
||||
};
|
||||
|
||||
const month = monthMapping[monthAbbreviation] || '';
|
||||
|
||||
return parseDate(`20${year}-${month}-${day}`);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{{ if images }}
|
||||
{{ each images image }}
|
||||
{{ if image?.src }}
|
||||
<figure>
|
||||
<img
|
||||
{{ if image.alt }}
|
||||
alt="{{ image.alt }}"
|
||||
{{ /if }}
|
||||
src="{{ image.src }}">
|
||||
</figure>
|
||||
{{ /if }}
|
||||
{{ /each }}
|
||||
{{ /if }}
|
||||
|
||||
{{ if description }}
|
||||
{{@ description }}
|
||||
{{ /if }}
|
||||
Loading…
Reference in New Issue