diff --git a/.github/labeler.yml b/.github/labeler.yml index 1983ce1f8..4b6c2602e 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -5,7 +5,7 @@ 'Route': - changed-files: - - any-glob-to-any-file: ['lib/routes/**/*.ts'] + - any-glob-to-any-file: ['lib/routes/**/*.ts', 'lib/routes/**/*.tsx'] core enhancement: - changed-files: diff --git a/.github/workflows/build-assets.yml b/.github/workflows/build-assets.yml index ce50031de..70a2955eb 100644 --- a/.github/workflows/build-assets.yml +++ b/.github/workflows/build-assets.yml @@ -7,6 +7,7 @@ on: - master paths: - 'lib/**/*.ts' + - 'lib/**/*.tsx' jobs: build: diff --git a/flake.nix b/flake.nix index 03bab86f5..0ecedb3e0 100644 --- a/flake.nix +++ b/flake.nix @@ -47,7 +47,7 @@ 'if (process.env.BUILD_ROUTES_MODE) { modules = directoryImport({ targetDirectoryPath: path.join(__dirname, "./routes"), - importPattern: /\.ts$/, + importPattern: /\.tsx?$/, }) as typeof modules; } else if (config.isPackage)' ''; diff --git a/lib/middleware/templates/iframe.art b/lib/middleware/templates/iframe.art deleted file mode 100644 index 7529b5bcc..000000000 --- a/lib/middleware/templates/iframe.art +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/lib/registry.ts b/lib/registry.ts index 436276d31..0d7b5ca3b 100644 --- a/lib/registry.ts +++ b/lib/registry.ts @@ -1,7 +1,6 @@ import path from 'node:path'; import { serveStatic } from '@hono/node-server/serve-static'; -import { directoryImport } from 'directory-import'; import type { Handler } from 'hono'; import { Hono } from 'hono'; import { routePath } from 'hono/route'; @@ -12,6 +11,7 @@ import index from '@/routes/index'; import metrics from '@/routes/metrics'; import robotstxt from '@/routes/robots.txt'; import type { APIRoute, Namespace, Route } from '@/types'; +import { directoryImport } from '@/utils/directory-import'; import logger from '@/utils/logger'; const __dirname = import.meta.dirname; @@ -73,7 +73,7 @@ if (config.isPackage) { default: modules = directoryImport({ targetDirectoryPath: path.join(__dirname, './routes'), - importPattern: /\.ts$/, + importPattern: /\.tsx?$/, }) as typeof modules; } } diff --git a/lib/routes-deprecated/index.js b/lib/routes-deprecated/index.js index 4313ce8b5..dec8a5841 100644 --- a/lib/routes-deprecated/index.js +++ b/lib/routes-deprecated/index.js @@ -1,6 +1,7 @@ const config = require('@/config').value; -const art = require('art-template'); -const path = require('path'); +const { raw } = require('hono/html'); +const { jsx } = require('hono/jsx'); +const { renderToString } = require('hono/jsx/dom/server'); let gitHash; try { @@ -45,56 +46,124 @@ module.exports = (ctx) => { const duration = Date.now() - startTime; - ctx.body = art(path.resolve(__dirname, '../views/welcome.art'), { - showDebug, - disallowRobot, - debug: [ - nodeName - ? { - name: 'Node Name', - value: nodeName, - } - : null, - { - name: 'Git Hash', - value: gitHash, - }, - { - name: 'Request Amount', - value: ctx.debug.request, - }, - { - name: 'Request Frequency', - value: ((ctx.debug.request / (duration / 1000)) * 60).toFixed(3) + ' times/minute', - }, - { - name: 'Cache Hit Ratio', - value: ctx.debug.request ? (ctx.debug.hitCache / ctx.debug.request).toFixed(3) : 0, - }, - { - name: 'ETag Matched', - value: ctx.debug.etag, - }, - { - name: 'Run Time', - value: (duration / 3_600_000).toFixed(2) + ' hour(s)', - }, - { - name: 'Hot Routes', - value: hotRoutesValue, - }, - { - name: 'Hot Paths', - value: hotPathsValue, - }, - { - name: 'Hot Error Routes', - value: hotErrorRoutesValue, - }, - { - name: 'Hot Error Paths', - value: hotErrorPathsValue, - }, - ], - }); + const debugInfo = [ + nodeName + ? { + name: 'Node Name', + value: nodeName, + } + : null, + { + name: 'Git Hash', + value: gitHash, + }, + { + name: 'Request Amount', + value: ctx.debug.request, + }, + { + name: 'Request Frequency', + value: ((ctx.debug.request / (duration / 1000)) * 60).toFixed(3) + ' times/minute', + }, + { + name: 'Cache Hit Ratio', + value: ctx.debug.request ? (ctx.debug.hitCache / ctx.debug.request).toFixed(3) : 0, + }, + { + name: 'ETag Matched', + value: ctx.debug.etag, + }, + { + name: 'Run Time', + value: (duration / 3_600_000).toFixed(2) + ' hour(s)', + }, + { + name: 'Hot Routes', + value: hotRoutesValue, + }, + { + name: 'Hot Paths', + value: hotPathsValue, + }, + { + name: 'Hot Error Routes', + value: hotErrorRoutesValue, + }, + { + name: 'Hot Error Paths', + value: hotErrorPathsValue, + }, + ].filter(Boolean); + + const formatDebugValue = (value) => (typeof value === 'string' && value.includes('
') ? raw(value) : value); + + const debugItems = debugInfo.map((item) => + jsx( + 'div', + { class: 'debug-item' }, + jsx('strong', null, `${item.name}: `), + formatDebugValue(item.value) + ) + ); + + const html = renderToString( + jsx( + 'html', + { lang: 'en' }, + jsx( + 'head', + null, + jsx('meta', { charset: 'utf-8' }), + jsx('meta', { name: 'viewport', content: 'width=device-width, initial-scale=1' }), + jsx('title', null, 'RSSHub'), + disallowRobot ? jsx('meta', { name: 'robots', content: 'noindex, nofollow' }) : null, + jsx( + 'style', + null, + ` + body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + margin: 24px; + color: #111827; + background: #ffffff; + } + h1 { + font-size: 24px; + margin-bottom: 8px; + } + .debug { + margin-top: 24px; + padding: 16px; + border: 1px solid #e5e7eb; + border-radius: 8px; + background: #f9fafb; + } + .debug-item { + margin: 6px 0; + } + .debug-item strong { + display: inline-block; + min-width: 160px; + } + ` + ) + ), + jsx( + 'body', + null, + jsx('h1', null, 'RSSHub'), + jsx('p', null, 'RSSHub is running.'), + showDebug + ? jsx( + 'section', + { class: 'debug' }, + jsx('h2', null, 'Debug Info'), + debugItems.length ? debugItems : jsx('p', null, 'No debug data.') + ) + : null + ) + ) + ); + + ctx.body = `${html}`; }; diff --git a/lib/routes-deprecated/mofish/index.js b/lib/routes-deprecated/mofish/index.js index 924e0cb43..f8cff5dfb 100644 --- a/lib/routes-deprecated/mofish/index.js +++ b/lib/routes-deprecated/mofish/index.js @@ -1,7 +1,20 @@ const got = require('@/utils/got'); const { parseDate } = require('@/utils/parse-date'); -const { art } = require('@/utils/render'); -const path = require('path'); +const { renderToString } = require('hono/jsx/dom/server'); +const { jsx } = require('hono/jsx'); + +const renderImageDescription = (imageUrl) => + renderToString( + jsx( + 'p', + null, + jsx('img', { + src: imageUrl, + referrerpolicy: 'no-referrer', + }), + jsx('br', null) + ) + ); module.exports = async (ctx) => { const id = ctx.params.id; @@ -23,11 +36,7 @@ module.exports = async (ctx) => { link: 'https://mo.fish/', item: data.map((item) => { const isImage = Number(id) === 136 && item.Url.endsWith('.gif'); - const description = isImage - ? art(path.join(__dirname, 'templates/description.art'), { - imageUrl: item.Url, - }) - : title; + const description = isImage ? renderImageDescription(item.Url) : title; return { title: item.Title, diff --git a/lib/routes-deprecated/mofish/templates/description.art b/lib/routes-deprecated/mofish/templates/description.art deleted file mode 100644 index 27cc22527..000000000 --- a/lib/routes-deprecated/mofish/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -

- -
-

\ No newline at end of file diff --git a/lib/routes/005/index.ts b/lib/routes/005/index.tsx similarity index 89% rename from lib/routes/005/index.ts rename to lib/routes/005/index.tsx index f9d8148f5..9694c07ee 100644 --- a/lib/routes/005/index.ts +++ b/lib/routes/005/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const handler = async (ctx) => { @@ -31,17 +29,16 @@ export const handler = async (ctx) => { const title = item.find('h3').text(); const image = item.find('img').prop('src'); - const description = art(path.join(__dirname, 'templates/description.art'), { - intro: item.find('div.p-row').text(), - images: image - ? [ - { - src: image, - alt: title, - }, - ] - : undefined, - }); + const description = renderToString( + <> + {image ? ( +
+ {title} +
+ ) : null} + {item.find('div.p-row').text() ?
{item.find('div.p-row').text()}
: null} + + ); return { title, diff --git a/lib/routes/005/templates/description.art b/lib/routes/005/templates/description.art deleted file mode 100644 index d96cdbc56..000000000 --- a/lib/routes/005/templates/description.art +++ /dev/null @@ -1,27 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if !videos?.[0]?.src && image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
{{ intro }}
-{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/0xxx/index.ts b/lib/routes/0xxx/index.ts index 582ddd5dd..cc690d897 100644 --- a/lib/routes/0xxx/index.ts +++ b/lib/routes/0xxx/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { filter } = ctx.req.param(); @@ -38,7 +37,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('td.title').text(); const image: string | undefined = $el.find('a.screenshot').attr('rel'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -88,7 +87,7 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const description: string | undefined = - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: $$('div.thumbs img') .toArray() .map((i) => { diff --git a/lib/routes/0xxx/templates/description.art b/lib/routes/0xxx/templates/description.art deleted file mode 100644 index 305c4808e..000000000 --- a/lib/routes/0xxx/templates/description.art +++ /dev/null @@ -1,50 +0,0 @@ -{{ if category || catalogue || title || size || date }} - - - {{ if category }} - - - - - {{ /if }} - {{ if catalogue }} - - - - - {{ /if }} - {{ if title }} - - - - - {{ /if }} - {{ if size }} - - - - - {{ /if }} - {{ if date }} - - - - - {{ /if }} - -
Category{{@ category }}
Catalogue{{@ catalogue }}
Title{{@ title }}
Size{{@ size }}
Date{{@ date }}
-{{ /if }} - -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/0xxx/templates/description.tsx b/lib/routes/0xxx/templates/description.tsx new file mode 100644 index 000000000..d935e1293 --- /dev/null +++ b/lib/routes/0xxx/templates/description.tsx @@ -0,0 +1,65 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + category?: string; + catalogue?: string; + title?: string; + size?: string; + date?: string; + images?: DescriptionImage[]; +}; + +export const renderDescription = ({ category, catalogue, title, size, date, images }: DescriptionRenderOptions): string => + renderToString( + <> + {category || catalogue || title || size || date ? ( + + + {category ? ( + + + + + ) : null} + {catalogue ? ( + + + + + ) : null} + {title ? ( + + + + + ) : null} + {size ? ( + + + + + ) : null} + {date ? ( + + + + + ) : null} + +
Category{raw(category)}
Catalogue{raw(catalogue)}
Title{raw(title)}
Size{raw(size)}
Date{raw(date)}
+ ) : null} + {images?.map((image) => + image?.src ? ( +
+ {image.alt +
+ ) : null + )} + + ); diff --git a/lib/routes/10000link/info.ts b/lib/routes/10000link/info.ts index cf57a3481..da095db3b 100644 --- a/lib/routes/10000link/info.ts +++ b/lib/routes/10000link/info.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate, parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'newslists', id } = ctx.req.param(); @@ -33,7 +32,7 @@ export const handler = async (ctx: Context): Promise => { const $aEl: Cheerio = $el.find('dd h1 a'); const title: string = $aEl.attr('title') ?? $aEl.text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ intro: $el.find('dd.title_l').text(), }); const pubDateStr: string | undefined = $el.find('span.ymd_w').text(); @@ -78,15 +77,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('div.entity_title h1 a').text(); const image: string | undefined = $$('div.entity_thumb img.img-responsive').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - images: image - ? [ - { - src: image, - alt: title, - }, - ] - : undefined, + const description: string | undefined = renderDescription({ description: $$('div.entity_content').html(), }); const pubDateStr: string | undefined = detailResponse.match(/var\stime\s=\s"(.*?)";/)?.[1]; diff --git a/lib/routes/10000link/templates/description.art b/lib/routes/10000link/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/10000link/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
{{ intro }}
-{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/10000link/templates/description.tsx b/lib/routes/10000link/templates/description.tsx new file mode 100644 index 000000000..9259435df --- /dev/null +++ b/lib/routes/10000link/templates/description.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + intro?: string; + description?: string; +}; + +const Description = ({ intro, description }: DescriptionProps) => ( + <> + {intro ?
{intro}
: null} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/121/templates/description.art b/lib/routes/121/templates/description.art deleted file mode 100644 index 7fdb805f8..000000000 --- a/lib/routes/121/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if description }} -

{{ description }}

-{{ /if }} - -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/121/weather-live.ts b/lib/routes/121/weather-live.tsx similarity index 81% rename from lib/routes/121/weather-live.ts rename to lib/routes/121/weather-live.tsx index 4a1ad13c8..d576045d8 100644 --- a/lib/routes/121/weather-live.ts +++ b/lib/routes/121/weather-live.tsx @@ -1,14 +1,28 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const renderDescription = (description, images) => + renderToString( + <> + {description ?

{description}

: null} + {images?.length + ? images.map((image) => + image?.src ? ( +
+ {image.alt} +
+ ) : null + ) + : null} + + ); export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10); @@ -29,13 +43,13 @@ export const handler = async (ctx: Context): Promise => { .slice(0, limit) .map((item): DataItem => { const title: string = item.Title; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - description: item.Content, - images: item.Img?.map((img: string) => ({ + const description: string | undefined = renderDescription( + item.Content, + item.Img?.map((img: string) => ({ src: new URL(`WeChat/data/weiweb/images/lwspic/${img}`, imgBaseUrl).href, alt: title, - })), - }); + })) + ); const pubDate: number | string = item.DDatetime; const linkUrl: string | undefined = targetUrl; const guid: string = `121-${title}-${pubDate}`; diff --git a/lib/routes/12306/index.ts b/lib/routes/12306/index.tsx similarity index 72% rename from lib/routes/12306/index.ts rename to lib/routes/12306/index.tsx index feba70300..40187a330 100644 --- a/lib/routes/12306/index.ts +++ b/lib/routes/12306/index.tsx @@ -1,14 +1,54 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const rootUrl = 'https://kyfw.12306.cn'; +const renderTrainDescription = (trainInfo) => + renderToString( + <> + 车次:{trainInfo.trainNo} +
+ + 始发站:{trainInfo.fromStation} → {trainInfo.toStation} + +
+ 出发时间:{trainInfo.startTime} +
+ 到达时间:{trainInfo.arriveTime} +
+ + 历时:{trainInfo.duration} {trainInfo.today === 'N' && '次日达'} + +
+ 商务座/特等座:{trainInfo.A9 || '无'} +
+ 一等座:{trainInfo.M || '无'} +
+ 二等座/二等包座:{trainInfo.O || '无'} +
+ 高级软卧:{trainInfo.A6 || '无'} +
+ 软卧/一等卧:{trainInfo.A4 || '无'} +
+ 动卧:{trainInfo.F || '无'} +
+ 硬卧/二等卧:{trainInfo.A3 || '无'} +
+ 软座: {trainInfo.A2 || '无'} +
+ 硬座: {trainInfo.A1 || '无'} +
+ 无座: {trainInfo.WZ || '无'} +
+ 其他: {trainInfo.QT || '无'} + + ); + async function getJSESSIONID(linkUrl) { const res = await got({ method: 'get', @@ -114,9 +154,7 @@ async function handler(ctx) { return { title: `${trainInfo.fromStation} → ${trainInfo.toStation} ${trainInfo.startTime} ${trainInfo.arriveTime}`, - description: art(path.join(__dirname, 'templates/train.art'), { - trainInfo, - }), + description: renderTrainDescription(trainInfo), link: linkUrl, guid: Object.values(trainInfo).join('|'), }; diff --git a/lib/routes/12306/templates/train.art b/lib/routes/12306/templates/train.art deleted file mode 100644 index ea1522ff9..000000000 --- a/lib/routes/12306/templates/train.art +++ /dev/null @@ -1,31 +0,0 @@ -车次:{{ trainInfo.trainNo}} -
-始发站:{{ trainInfo.fromStation}} → {{ trainInfo.toStation}} -
-出发时间:{{ trainInfo.startTime}} -
-到达时间:{{ trainInfo.arriveTime}} -
-历时:{{ trainInfo.duration}} {{ trainInfo.today === 'N' ? '次日达' : '' }} -
-商务座/特等座:{{ trainInfo.A9 ? trainInfo.A9 : '无' }} -
-一等座:{{ trainInfo.M ? trainInfo.M : '无' }} -
-二等座/二等包座:{{ trainInfo.O ? trainInfo.O : '无' }} -
-高级软卧:{{ trainInfo.A6 ? trainInfo.A6 : '无' }} -
-软卧/一等卧:{{ trainInfo.A4 ? trainInfo.A4 : '无' }} -
-动卧:{{ trainInfo.F ? trainInfo.F : '无' }} -
-硬卧/二等卧:{{ trainInfo.A3 ? trainInfo.A3 : '无' }} -
-软座: {{ trainInfo.A2 ? trainInfo.A2 : '无' }} -
-硬座: {{ trainInfo.A1 ? trainInfo.A1 : '无' }} -
-无座: {{ trainInfo.WZ ? trainInfo.WZ : '无' }} -
-其他: {{ trainInfo.QT ? trainInfo.QT : '无' }} diff --git a/lib/routes/141jav/index.ts b/lib/routes/141jav/index.tsx similarity index 62% rename from lib/routes/141jav/index.ts rename to lib/routes/141jav/index.tsx index 1ccd6a54d..f8693934e 100644 --- a/lib/routes/141jav/index.ts +++ b/lib/routes/141jav/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:type/:keyword{.*}?', @@ -97,17 +95,7 @@ async function handler(ctx) { title: `${id} ${size}`, pubDate: parseDate(pubDate, 'YYYY/MM/DD'), link: new URL(item.find('a').first().attr('href'), rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - image, - id, - size, - pubDate, - description, - actresses, - tags, - magnet, - link, - }), + description: renderToString(), author: actresses.join(', '), category: [...tags, ...actresses], enclosure_type: 'application/x-bittorrent', @@ -121,3 +109,81 @@ async function handler(ctx) { item: items, }; } + +const JavDescription = ({ + image, + id, + size, + pubDate, + description, + actresses, + tags, + magnet, + link, +}: { + image?: string; + id: string; + size: string; + pubDate: string; + description: string; + actresses: string[]; + tags: string[]; + magnet?: string; + link?: string; +}) => ( + <> + {image ? : null} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID{id}
Size{size}
Date{pubDate}
Description{description}
Actress + {actresses.map((actress) => ( + <> + {actress}  + + ))} +
Tag + {tags.map((tag) => ( + <> + {tag}  + + ))} +
Magnet torrent + Magnet torrent link +
Download .torrent + Download torrent +
+ +); diff --git a/lib/routes/141jav/templates/description.art b/lib/routes/141jav/templates/description.art deleted file mode 100644 index 9f744864a..000000000 --- a/lib/routes/141jav/templates/description.art +++ /dev/null @@ -1,47 +0,0 @@ -{{ if image }} - -{{ /if }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID{{ id }}
Size{{ size }}
Date{{ pubDate }}
Description{{ description }}
Actress - {{ each actresses actress }} - {{ actress }}  - {{ /each }} -
Tag - {{ each tags tag }} - {{ tag }}  - {{ /each }} -
Magnet torrentMagnet torrent link
Download .torrentDownload torrent
\ No newline at end of file diff --git a/lib/routes/141ppv/index.ts b/lib/routes/141ppv/index.tsx similarity index 59% rename from lib/routes/141ppv/index.ts rename to lib/routes/141ppv/index.tsx index 032b982ff..8a360f917 100644 --- a/lib/routes/141ppv/index.ts +++ b/lib/routes/141ppv/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:type/:keyword{.*}?', @@ -100,17 +98,63 @@ async function handler(ctx) { title: `${id} ${size}`, pubDate: parseDate(pubDate, 'YYYY/MM/DD'), link: new URL(item.find('a').first().attr('href'), rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - image, - id, - size, - pubDate, - description, - actresses, - tags, - magnet, - link, - }), + description: renderToString( + <> + {image ? : null} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID{id}
Size{size}
Date{pubDate}
Description{description}
Actress + {actresses.map((actress) => ( + <> + {actress}  + + ))} +
Tag + {tags.map((tag) => ( + <> + {tag}  + + ))} +
Magnet torrent + Magnet torrent link +
Download .torrent + Download torrent +
+ + ), author: actresses.join(', '), category: [...tags, ...actresses], enclosure_type: 'application/x-bittorrent', diff --git a/lib/routes/141ppv/templates/description.art b/lib/routes/141ppv/templates/description.art deleted file mode 100644 index 9f744864a..000000000 --- a/lib/routes/141ppv/templates/description.art +++ /dev/null @@ -1,47 +0,0 @@ -{{ if image }} - -{{ /if }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ID{{ id }}
Size{{ size }}
Date{{ pubDate }}
Description{{ description }}
Actress - {{ each actresses actress }} - {{ actress }}  - {{ /each }} -
Tag - {{ each tags tag }} - {{ tag }}  - {{ /each }} -
Magnet torrentMagnet torrent link
Download .torrentDownload torrent
\ No newline at end of file diff --git a/lib/routes/163/ds.ts b/lib/routes/163/ds.tsx similarity index 78% rename from lib/routes/163/ds.ts rename to lib/routes/163/ds.tsx index 5dde58e46..4c502e1aa 100644 --- a/lib/routes/163/ds.ts +++ b/lib/routes/163/ds.tsx @@ -1,12 +1,19 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const root_url = 'https://inf.ds.163.com'; +const renderDescription = (text, medias) => + renderToString( + <> + {text} + {medias?.map((media) => (media.mimeType?.includes('image') ? : null))} + + ); + export const route: Route = { path: '/ds/:id', categories: ['game'], @@ -43,10 +50,7 @@ async function handler(ctx) { const list = data.map((feed) => ({ title: JSON.parse(feed.content).body.text, link: `https://ds.163.com/feed/${feed.id}`, - description: art(path.resolve(__dirname, 'templates/ds.art'), { - text: JSON.parse(feed.content).body.text, - medias: JSON.parse(feed.content).body.media, - }), + description: renderDescription(JSON.parse(feed.content).body.text, JSON.parse(feed.content).body.media), pubDate: parseDate(feed.updateTime), })); diff --git a/lib/routes/163/exclusive.ts b/lib/routes/163/exclusive.ts index c781cedcb..43029a88b 100644 --- a/lib/routes/163/exclusive.ts +++ b/lib/routes/163/exclusive.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderExclusiveDescription } from './templates/exclusive'; + const ids = { '': { id: 'BAI5E21O', @@ -151,7 +150,7 @@ async function handler(ctx) { const video = JSON.parse(detailResponse.data.match(/^videoList\((.*)\)$/)[1])?.mp4_url; - item.description = art(path.join(__dirname, 'templates/exclusive.art'), { + item.description = renderExclusiveDescription({ video, }); } else { @@ -166,7 +165,7 @@ async function handler(ctx) { content('.m-photo').each(function () { content(this).html( - art(path.join(__dirname, 'templates/exclusive.art'), { + renderExclusiveDescription({ image: content(this).find('img').attr('data-src'), }) ); diff --git a/lib/routes/163/music/artist-songs.ts b/lib/routes/163/music/artist-songs.ts index 629f2b76e..d60905d33 100644 --- a/lib/routes/163/music/artist-songs.ts +++ b/lib/routes/163/music/artist-songs.ts @@ -1,8 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderPlaylistDescription } from '../templates/music/playlist'; export const route: Route = { path: '/music/artist/songs/:id', @@ -42,7 +41,7 @@ async function handler(ctx) { const artist = data.songs.find(({ ar }) => ar[0].id === Number.parseInt(id)).ar[0]; const items = data.songs.map((song) => ({ title: `${song.name} - ${song.ar.map(({ name }) => name).join(' / ')}`, - description: art(path.join(__dirname, '../templates/music/playlist.art'), { + description: renderPlaylistDescription({ singer: song.ar.map(({ name }) => name).join(' / '), album: song.al.name, picUrl: song.al.picUrl, diff --git a/lib/routes/163/music/artist.ts b/lib/routes/163/music/artist.ts index 8beeb6db5..a555357f4 100644 --- a/lib/routes/163/music/artist.ts +++ b/lib/routes/163/music/artist.ts @@ -1,8 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderPlaylistDescription } from '../templates/music/playlist'; export const route: Route = { path: '/music/artist/:id', @@ -42,7 +41,7 @@ async function handler(ctx) { const singer = item.artists.length === 1 ? item.artists[0].name : item.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name); return { title: `${item.name} - ${singer}`, - description: art(path.join(__dirname, '../templates/music/playlist.art'), { + description: renderPlaylistDescription({ singer, album: item.name, date: new Date(item.publishTime).toLocaleDateString(), diff --git a/lib/routes/163/music/djradio.ts b/lib/routes/163/music/djradio.tsx similarity index 79% rename from lib/routes/163/music/djradio.ts rename to lib/routes/163/music/djradio.tsx index 3a02ac3e9..ab876b54b 100644 --- a/lib/routes/163/music/djradio.ts +++ b/lib/routes/163/music/djradio.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/music/djradio/:id/:info?', @@ -25,6 +24,27 @@ export const route: Route = { handler, }; +const renderDescription = (pg, description, itunes_duration, info) => + renderToString( + <> + +
+ {description.map((line) => ( +

{line}

+ ))} +
+ {info ? ( +
+ +

时长: {itunes_duration}

+

+ 查看节目 +

+
+ ) : null} + + ); + const ProcessFeed = (id, limit, offset) => cache.tryGet( `163:music:djradio:${id}:${limit}:${offset}`, @@ -64,12 +84,7 @@ async function handler(ctx) { const duration = Math.trunc(pg.duration / 1000); const mm_ss_duration = `${(duration / 60).toFixed(0).padStart(2, '0')}:${(duration % 60).toFixed(0).padStart(2, '0')}`; - const html = art(path.join(__dirname, '../templates/music/djradio-content.art'), { - pg, - description, - itunes_duration: mm_ss_duration, - info, - }); + const html = renderDescription(pg, description, mm_ss_duration, info); return { title: pg.name, diff --git a/lib/routes/163/music/playlist.ts b/lib/routes/163/music/playlist.ts index f3f9b4c4a..8585b1201 100644 --- a/lib/routes/163/music/playlist.ts +++ b/lib/routes/163/music/playlist.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; - import { config } from '@/config'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderPlaylistDescription } from '../templates/music/playlist'; export const route: Route = { path: '/music/playlist/:id', @@ -59,7 +58,7 @@ async function handler(ctx) { const singer = thisSong.artists.length === 1 ? thisSong.artists[0].name : thisSong.artists.reduce((prev, cur) => (prev.name || prev) + '/' + cur.name); return { title: `${thisSong.name} - ${singer}`, - description: art(path.join(__dirname, '../templates/music/playlist.art'), { + description: renderPlaylistDescription({ singer, album: thisSong.album.name, date: new Date(thisSong.album.publishTime).toLocaleDateString(), diff --git a/lib/routes/163/music/userevents.ts b/lib/routes/163/music/userevents.tsx similarity index 80% rename from lib/routes/163/music/userevents.ts rename to lib/routes/163/music/userevents.tsx index 39c7bf949..6174be14b 100644 --- a/lib/routes/163/music/userevents.ts +++ b/lib/routes/163/music/userevents.tsx @@ -1,10 +1,24 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; -const renderDescription = (info) => art(path.join(__dirname, '../templates/music/userevents.art'), info); +const renderDescription = ({ description, pics }) => { + const lines = (description ?? '').split('\n'); + return renderToString( +

+ {lines.map((line, index) => ( + <> + {line} + {index < lines.length - 1 ?
: null} + + ))} + {pics.map((pic) => ( + + ))} +

+ ); +}; export const route: Route = { path: '/music/user/events/:id', diff --git a/lib/routes/163/music/userplaylist.ts b/lib/routes/163/music/userplaylist.tsx similarity index 74% rename from lib/routes/163/music/userplaylist.ts rename to lib/routes/163/music/userplaylist.tsx index 4d272a18c..4dcf677fb 100644 --- a/lib/routes/163/music/userplaylist.ts +++ b/lib/routes/163/music/userplaylist.tsx @@ -1,8 +1,26 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +const renderDescription = (image, description, src) => + renderToString( + <> + {image ? : null} + {description?.length ? ( +
+ {description.map((line) => ( +

{line}

+ ))} +
+ ) : null} + {src ? ( + + ) : null} + + ); export const route: Route = { path: '/music/user/playlist/:uid', @@ -54,11 +72,7 @@ async function handler(ctx) { item: playlist.map((pl) => { const src = `http://music.163.com/playlist/${pl.id}`; - const html = art(path.join(__dirname, '../templates/music/userplaylist.art'), { - image: pl.coverImgUrl, - description: (pl.description || '').split('\n'), - src, - }); + const html = renderDescription(pl.coverImgUrl, (pl.description || '').split('\n'), src); return { title: pl.name, diff --git a/lib/routes/163/music/userplayrecords.ts b/lib/routes/163/music/userplayrecords.tsx similarity index 69% rename from lib/routes/163/music/userplayrecords.ts rename to lib/routes/163/music/userplayrecords.tsx index 7d9f4f2f4..41bf52104 100644 --- a/lib/routes/163/music/userplayrecords.ts +++ b/lib/routes/163/music/userplayrecords.tsx @@ -1,15 +1,39 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const headers = { cookie: config.ncm.cookies, Referer: 'https://music.163.com/', }; +const renderDescription = (record, song, index) => + renderToString( +
+ 排行:{index + 1} 播放次数:{record.playCount} 得分:{record.score} +
+ 歌曲: + {song.name} +
+ 歌手: + {song.ar.map((artist, artistIndex) => ( + <> + {artist.name} + {artistIndex < song.ar.length - 1 ? ' / ' : null} + + ))} +
+ {song.al ? ( + <> + 歌曲图: + +
+ + ) : null} +
+ ); function getItem(records) { if (!records || records.length === 0) { return [ @@ -24,11 +48,7 @@ function getItem(records) { const artists_paintext = song.ar.map((a) => a.name).join('/'); - const html = art(path.join(__dirname, '../templates/music/userplayrecords.art'), { - index, - record, - song, - }); + const html = renderDescription(record, song, index); return { title: `[${index + 1}] ${song.name} - ${artists_paintext}`, diff --git a/lib/routes/163/open/vip.ts b/lib/routes/163/open/vip.tsx similarity index 68% rename from lib/routes/163/open/vip.ts rename to lib/routes/163/open/vip.tsx index 3ed041646..4ee699dbc 100644 --- a/lib/routes/163/open/vip.ts +++ b/lib/routes/163/open/vip.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/open/vip', @@ -32,6 +31,32 @@ export const route: Route = { url: 'vip.open.163.com/', }; +const renderDescription = (data, description) => { + const chapterList = data.movieChapterList.length ? data.movieChapterList : data.audioChapterList; + + return renderToString( + <> + {chapterList?.length ? ( +
+ {chapterList.map((chapter, chapterIndex) => ( + <> +

+ 第{chapterIndex + 1}章 {chapter.title} +

+ {chapter.contentList.map((content, contentIndex) => ( +

+ {contentIndex + 1} {content.title} +

+ ))} + + ))} +
+ ) : null} + {description ? <>{raw(description)} : null} + + ); +}; + async function handler() { const url = 'https://vip.open.163.com'; @@ -73,10 +98,7 @@ async function handler() { }); item.category = [item.category, data.courseInfo.firstClassifyName, data.courseInfo.secondClassifyName]; - item.description = art(path.join(__dirname, '../templates/open.art'), { - data, - description: $.html(), - }); + item.description = renderDescription(data, $.html()); return item; }) diff --git a/lib/routes/163/templates/ds.art b/lib/routes/163/templates/ds.art deleted file mode 100644 index 8a493d218..000000000 --- a/lib/routes/163/templates/ds.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ text }} - -{{ each medias }} - {{ if $value.mimeType.indexOf('image') > -1 }} - - {{ /if }} -{{ /each }} diff --git a/lib/routes/163/templates/dy.art b/lib/routes/163/templates/dy.art deleted file mode 100644 index c78f313c9..000000000 --- a/lib/routes/163/templates/dy.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if imgsrc }} -
-{{ /if }} -{{ if postBody }} -{{@ postBody }} -{{ /if }} diff --git a/lib/routes/163/templates/exclusive.art b/lib/routes/163/templates/exclusive.art deleted file mode 100644 index ffe6b432c..000000000 --- a/lib/routes/163/templates/exclusive.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if image }} - -{{ /if }} -{{ if video }} - -{{ /if }} -{{ if digest }} -

{{ digest }}

-{{ /if }} \ No newline at end of file diff --git a/lib/routes/163/templates/exclusive.tsx b/lib/routes/163/templates/exclusive.tsx new file mode 100644 index 000000000..228397bb1 --- /dev/null +++ b/lib/routes/163/templates/exclusive.tsx @@ -0,0 +1,20 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: string; + video?: string; + digest?: string; +}; + +export const renderExclusiveDescription = ({ image, video, digest }: DescriptionData) => + renderToString( + <> + {image ? : null} + {video ? ( + + ) : null} + {digest ?

{digest}

: null} + + ); diff --git a/lib/routes/163/templates/music/djradio-content.art b/lib/routes/163/templates/music/djradio-content.art deleted file mode 100644 index a27f0b666..000000000 --- a/lib/routes/163/templates/music/djradio-content.art +++ /dev/null @@ -1,13 +0,0 @@ - -
- {{each description}} -

{{$value}}

- {{/each}} -
-{{ if info }} -
- -

时长: {{itunes_duration}}

-

查看节目

-
-{{ /if }} diff --git a/lib/routes/163/templates/music/playlist.art b/lib/routes/163/templates/music/playlist.art deleted file mode 100644 index f99b72696..000000000 --- a/lib/routes/163/templates/music/playlist.art +++ /dev/null @@ -1,4 +0,0 @@ -歌手:{{ singer }}
-专辑:{{ album }}
-{{ if date }}发行日期:{{ date }}
{{ /if }} - diff --git a/lib/routes/163/templates/music/playlist.tsx b/lib/routes/163/templates/music/playlist.tsx new file mode 100644 index 000000000..00f07fe54 --- /dev/null +++ b/lib/routes/163/templates/music/playlist.tsx @@ -0,0 +1,25 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type PlaylistData = { + singer?: string; + album?: string; + date?: string; + picUrl?: string; +}; + +export const renderPlaylistDescription = ({ singer, album, date, picUrl }: PlaylistData) => + renderToString( + <> + 歌手:{singer} +
+ 专辑:{album} +
+ {date ? ( + <> + 发行日期:{date} +
+ + ) : null} + + + ); diff --git a/lib/routes/163/templates/music/userevents.art b/lib/routes/163/templates/music/userevents.art deleted file mode 100644 index 1149522e0..000000000 --- a/lib/routes/163/templates/music/userevents.art +++ /dev/null @@ -1,7 +0,0 @@ -

-{{ each description.split('\n') }} - {{$value}}{{if $index !== description.split('\n').length - 1}}
{{/if}} -{{/each}} -{{ each pics }} - -{{ /each }}

diff --git a/lib/routes/163/templates/music/userplaylist.art b/lib/routes/163/templates/music/userplaylist.art deleted file mode 100644 index f5bf0c018..000000000 --- a/lib/routes/163/templates/music/userplaylist.art +++ /dev/null @@ -1,16 +0,0 @@ -{{ if image }} - -{{ /if }} - -{{ if description }} -
- {{ each description d }} -

{{ d }}

- {{ /each }} -
-{{ /if }} - - -{{ if src }} - -{{ /if }} diff --git a/lib/routes/163/templates/music/userplayrecords.art b/lib/routes/163/templates/music/userplayrecords.art deleted file mode 100644 index 303cc815d..000000000 --- a/lib/routes/163/templates/music/userplayrecords.art +++ /dev/null @@ -1,7 +0,0 @@ -
-排行:{{ index + 1 }} 播放次数:{{ record.playCount }} 得分:{{ record.score }}
-歌曲:{{ song.name }}
-歌手:{{ each song.ar a i }}{{ a.name }} {{ if i < song.ar.length - 1 }}/ {{ /if }}{{ /each }} -
-{{ if song.al }}歌曲图:
{{ /if }} -
diff --git a/lib/routes/163/templates/open.art b/lib/routes/163/templates/open.art deleted file mode 100644 index b6fae699d..000000000 --- a/lib/routes/163/templates/open.art +++ /dev/null @@ -1,12 +0,0 @@ -{{ set chapterList = data.movieChapterList.length ? data.movieChapterList : data.audioChapterList; }} -{{ if chapterList }} -
-{{ each chapterList chapter chapterIndex }} -

第{{ chapterIndex + 1 }}章 {{ chapter.title }}

- {{ each chapter.contentList content contentIndex }} -

{{ contentIndex + 1 }} {{ content.title }}

- {{ /each }} -{{ /each }} -
-{{ /if }} -{{@ description }} diff --git a/lib/routes/163/utils.ts b/lib/routes/163/utils.tsx similarity index 66% rename from lib/routes/163/utils.ts rename to lib/routes/163/utils.tsx index 695e6f620..df91f2032 100644 --- a/lib/routes/163/utils.ts +++ b/lib/routes/163/utils.tsx @@ -1,9 +1,21 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +const renderDescription = (imgsrc, postBody) => + renderToString( + <> + {imgsrc ? ( + <> + +
+ + ) : null} + {postBody ? <>{raw(postBody)} : null} + + ); const parseDyArticle = (item, tryGet) => tryGet(item.link, async () => { @@ -24,10 +36,7 @@ const parseDyArticle = (item, tryGet) => }); const imgsrc = item.imgsrc ? new URL(item.imgsrc).searchParams.get('url') : false; - item.description = art(path.join(__dirname, 'templates/dy.art'), { - imgsrc, - postBody: $('.post_body').html(), - }); + item.description = renderDescription(imgsrc, $('.post_body').html()); item.feedLink = $('.post_wemedia_name a').attr('href'); item.feedDescription = $('.post_wemedia_title').text(); diff --git a/lib/routes/18comic/album.ts b/lib/routes/18comic/album.ts index 54219ddc6..c2ecd580a 100644 --- a/lib/routes/18comic/album.ts +++ b/lib/routes/18comic/album.ts @@ -1,9 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; -import { art } from '@/utils/render'; +import { renderDescription } from './templates/description'; import { defaultDomain, getApiUrl, getRootUrl, processApiItems } from './utils'; export const route: Route = { @@ -58,7 +56,7 @@ async function handler(ctx) { pubDate: new Date(addTime * 1000), category, author, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ introduction: description, // 不取图片,因为专辑的图片会被分割排序,所以只取封面图 images: [`https://cdn-msp3.${domain}/media/albums/${id}_3x4.jpg`], @@ -81,7 +79,7 @@ async function handler(ctx) { result.pubDate = addTime; result.category = category; result.author = author; - result.description = art(path.join(__dirname, 'templates/description.art'), { + result.description = renderDescription({ introduction: description, // 不取图片,因为专辑的图片会被分割排序,所以只取封面图 images: [`https://cdn-msp3.${domain}/media/albums/${item.id}_3x4.jpg`], diff --git a/lib/routes/18comic/search.ts b/lib/routes/18comic/search.ts index 40226f1d4..59a591ac1 100644 --- a/lib/routes/18comic/search.ts +++ b/lib/routes/18comic/search.ts @@ -1,10 +1,8 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; +import { renderDescription } from './templates/description'; import { apiMapCategory, defaultDomain, getApiUrl, getRootUrl, processApiItems } from './utils'; export const route: Route = { @@ -72,7 +70,7 @@ async function handler(ctx) { result.pubDate = new Date(apiResult.addtime * 1000); result.category = apiResult.tags.map((tag) => tag); result.author = apiResult.author.map((a) => a).join(', '); - result.description = art(path.join(__dirname, 'templates/description.art'), { + result.description = renderDescription({ introduction: apiResult.description, images: [ `https://cdn-msp3.${domain}/media/albums/${item.id}_3x4.jpg`, diff --git a/lib/routes/18comic/templates/description.art b/lib/routes/18comic/templates/description.art deleted file mode 100644 index 1d69859b4..000000000 --- a/lib/routes/18comic/templates/description.art +++ /dev/null @@ -1,12 +0,0 @@ -{{ if cover }} - -{{ /if }} -

-{{each category}} -{{ $value }} -{{/each}} -

-

{{ introduction }}

-{{ each images image }} - -{{ /each }} \ No newline at end of file diff --git a/lib/routes/18comic/templates/description.tsx b/lib/routes/18comic/templates/description.tsx new file mode 100644 index 000000000..c80f9ee09 --- /dev/null +++ b/lib/routes/18comic/templates/description.tsx @@ -0,0 +1,24 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + cover?: string; + category?: string[]; + introduction?: string; + images?: string[]; +}; + +export const renderDescription = ({ cover, category = [], introduction, images = [] }: DescriptionData): string => + renderToString( + <> + {cover ? : null} +

+ {category.map((item) => ( + {item} + ))} +

+

{introduction}

+ {images.map((image) => ( + + ))} + + ); diff --git a/lib/routes/18comic/utils.ts b/lib/routes/18comic/utils.ts index 3d4d23325..878726780 100644 --- a/lib/routes/18comic/utils.ts +++ b/lib/routes/18comic/utils.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import CryptoJS from 'crypto-js'; @@ -9,7 +7,8 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import md5 from '@/utils/md5'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const defaultDomain = 'jmcomic1.me'; // list of address: https://jmcomic2.bet @@ -130,7 +129,7 @@ const ProcessItems = async (ctx, currentUrl, rootUrl) => { .toArray() .map((a) => $(a).text()) .join(', '); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ introduction: content('#intro-block .p-t-5').text(), images: content('.img_zoom_img img') .toArray() diff --git a/lib/routes/199it/index.ts b/lib/routes/199it/index.tsx similarity index 95% rename from lib/routes/199it/index.ts rename to lib/routes/199it/index.tsx index 08c8ace86..f6716883b 100644 --- a/lib/routes/199it/index.ts +++ b/lib/routes/199it/index.tsx @@ -1,16 +1,14 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const { category = 'newly' } = ctx.req.param(); @@ -66,19 +64,15 @@ export const handler = async (ctx: Context): Promise => { $$('div.entry-content img.alignnone').each((_, el) => { const $el: Cheerio = $$(el); - + const src = $el.attr('src'); $el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { - images: $el.attr('src') - ? [ - { - src: $el.attr('src'), - width: $el.attr('width'), - height: $el.attr('height'), - }, - ] - : undefined, - }) + src + ? renderToString( +
+ +
+ ) + : '' ); }); diff --git a/lib/routes/199it/templates/description.art b/lib/routes/199it/templates/description.art deleted file mode 100644 index f1aec25c5..000000000 --- a/lib/routes/199it/templates/description.art +++ /dev/null @@ -1,20 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/1point3acres/offer.ts b/lib/routes/1point3acres/offer.tsx similarity index 78% rename from lib/routes/1point3acres/offer.ts rename to lib/routes/1point3acres/offer.tsx index 87d1d05d4..b5dce3bf8 100644 --- a/lib/routes/1point3acres/offer.ts +++ b/lib/routes/1point3acres/offer.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/offer/:year?/:major?/:school?', @@ -93,12 +92,48 @@ async function handler(ctx) { link: 'https://offer.1point3acres.com', item: data.map((item) => ({ title: `${item.planyr}年${item.planmajor}@${item.outname_w}:${item.result} - 一亩三分地`, - description: art(path.join(__dirname, 'templates/offer.art'), { - item, - }), + description: renderDescription(item), pubDate: parseDate(item.dateline, 'X'), link: 'https://offer.1point3acres.com', guid: `1point3acres:offer:${year}:${major}:${school}:${item.id}`, })), }; } + +const renderDescription = (item): string => + renderToString( + <> + 国家: + {item.country} +
+ 学校: + {item.outname_w} {item.outname} +
+ 录取学位: + {item.plandegree} +
+ 录取项目: + {item.planmajor} - {item.planprogram} +
+ 录取结果: + {item.result} +
+ 录取时间: + {item.outtime} +
+ 通知方式: + {item.noticemethod} +
+ 全奖/自费: + {item.planfin} +
+ 申入学学期: + {item.planterm} +
+ 申入学年度: + {item.planyr} +
+ 提交时间: + {item.submittime} + + ); diff --git a/lib/routes/1point3acres/templates/image.art b/lib/routes/1point3acres/templates/image.art deleted file mode 100644 index fdad197fb..000000000 --- a/lib/routes/1point3acres/templates/image.art +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lib/routes/1point3acres/templates/offer.art b/lib/routes/1point3acres/templates/offer.art deleted file mode 100644 index 0bdf703a6..000000000 --- a/lib/routes/1point3acres/templates/offer.art +++ /dev/null @@ -1,11 +0,0 @@ -国家:{{ item.country }}
-学校:{{ item.outname_w }} {{ item.outname }}
-录取学位:{{ item.plandegree }}
-录取项目:{{ item.planmajor }} - {{ item.planprogram }}
-录取结果:{{ item.result }}
-录取时间:{{ item.outtime }}
-通知方式:{{ item.noticemethod }}
-全奖/自费:{{ item.planfin }}
-申入学学期:{{ item.planterm }}
-申入学年度:{{ item.planyr }}
-提交时间:{{ item.submittime }} diff --git a/lib/routes/1point3acres/utils.ts b/lib/routes/1point3acres/utils.tsx similarity index 89% rename from lib/routes/1point3acres/utils.ts rename to lib/routes/1point3acres/utils.tsx index b1c9c6f98..746732d26 100644 --- a/lib/routes/1point3acres/utils.ts +++ b/lib/routes/1point3acres/utils.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import bbobHTML from '@bbob/html'; import presetHTML5 from '@bbob/preset-html5'; import type { BBobCoreTagNodeTree } from '@bbob/types'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://instant.1point3acres.com'; const apiRootUrl = 'https://api.1point3acres.com'; @@ -116,15 +114,7 @@ const ProcessThreads = async (tryGet, apiUrl, order) => { if (!thread.message_bbcode.includes('[attach]') && thread.attachment_list.length > 0) { for (const a of thread.attachment_list) { - result.description += - a.isimage === 1 - ? '
' + - art(path.join(__dirname, 'templates/image.art'), { - url: a.url, - height: a.height, - width: a.width, - }) - : ''; + result.description += a.isimage === 1 ? '
' + renderAttachmentImage(a.url, a.height, a.width) : ''; } } } catch { @@ -140,3 +130,5 @@ const ProcessThreads = async (tryGet, apiUrl, order) => { }; export { apiRootUrl, ProcessThreads, rootUrl, types }; + +const renderAttachmentImage = (url: string, height?: number, width?: number): string => renderToString(); diff --git a/lib/routes/1x/index.ts b/lib/routes/1x/index.tsx similarity index 89% rename from lib/routes/1x/index.ts rename to lib/routes/1x/index.tsx index 8e0121b86..83e0f52d2 100644 --- a/lib/routes/1x/index.ts +++ b/lib/routes/1x/index.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const handler = async (ctx) => { const { category = 'latest/awarded' } = ctx.req.param(); @@ -36,17 +35,16 @@ export const handler = async (ctx) => { const text = `${title} by ${author}`; - const description = art(path.join(__dirname, 'templates/description.art'), { - images: image - ? [ - { - src: image, - alt: title, - }, - ] - : undefined, - description: text, - }); + const description = renderToString( + <> + {image ? ( +
+ {title} +
+ ) : null} + {text ? <>{raw(text)} : null} + + ); const id = item.find('img[id]').prop('id').split(/-/).pop(); const guid = `1x-${id}`; diff --git a/lib/routes/1x/templates/description.art b/lib/routes/1x/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/1x/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/2048/index.ts b/lib/routes/2048/index.tsx similarity index 95% rename from lib/routes/2048/index.ts rename to lib/routes/2048/index.tsx index c13c46df5..635f9566b 100644 --- a/lib/routes/2048/index.ts +++ b/lib/routes/2048/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -150,12 +148,7 @@ async function handler(ctx) { const magnet = torrent('.uk-button').first().attr('href'); - downloadLink.replaceWith( - art(path.join(__dirname, 'templates/download.art'), { - magnet, - torrent: item.enclosure_url, - }) - ); + downloadLink.replaceWith(renderToString()); } else if (copyLink?.startsWith('magnet')) { // copy link item.enclosure_url = copyLink; @@ -181,3 +174,9 @@ async function handler(ctx) { item: items, }; } + +const DownloadLinks = ({ magnet, torrent }: { magnet?: string; torrent?: string }) => ( + <> + 磁力連結 | 下載檔案 + +); diff --git a/lib/routes/2048/templates/download.art b/lib/routes/2048/templates/download.art deleted file mode 100644 index 57ae25422..000000000 --- a/lib/routes/2048/templates/download.art +++ /dev/null @@ -1 +0,0 @@ -磁力連結 | 下載檔案 \ No newline at end of file diff --git a/lib/routes/3kns/index.ts b/lib/routes/3kns/index.tsx similarity index 78% rename from lib/routes/3kns/index.ts rename to lib/routes/3kns/index.tsx index abbe118cf..b2f9eb7fa 100644 --- a/lib/routes/3kns/index.ts +++ b/lib/routes/3kns/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:filters?/:order?', @@ -98,17 +96,19 @@ async function handler(ctx: Context): Promise { pubDate: parseDate(pubDate ?? ''), category: [category], description: - art(path.join(__dirname, 'templates/description.art'), { - cover: $item.find('.entry-media img').attr('src')?.trim().replace('.', baseUrl), - title, - tid: $item.find('.jb-chakan').text().trim(), - category, - language: $item.find('.jb-new').text().trim(), - pubDate, - system: $item.find('.jb-youxxx').text().trim(), - score: $item.find('.shownamep').text().trim(), - version: $item.find('.jb-youxbb').text().trim(), - }) ?? '', + renderToString( + + ) ?? '', }; }); @@ -119,3 +119,37 @@ async function handler(ctx: Context): Promise { item: items, }; } + +const ThreeKnsDescription = ({ + cover, + title, + tid, + category, + language, + pubDate, + system, + score, + version, +}: { + cover?: string; + title: string; + tid: string; + category: string; + language: string; + pubDate: string; + system: string; + score: string; + version: string; +}) => ( + <> + +

{title}

+

游戏TID:{tid}

+

类型:{category}

+

语言:{language}

+

更新日期:{pubDate}

+

系统要求:{system}

+

{score}

+

游戏版本:{version}

+ +); diff --git a/lib/routes/3kns/templates/description.art b/lib/routes/3kns/templates/description.art deleted file mode 100644 index e579f34f4..000000000 --- a/lib/routes/3kns/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ - -

{{ title }}

-

游戏TID:{{ tid }}

-

类型:{{ category }}

-

语言:{{ language }}

-

更新日期:{{ pubDate }}

-

系统要求:{{ system }}

-

{{ score }}

-

游戏版本:{{ version }}

diff --git a/lib/routes/423down/index.ts b/lib/routes/423down/index.ts index 0a8f8c7b5..51bec7463 100644 --- a/lib/routes/423down/index.ts +++ b/lib/routes/423down/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { category = '' } = ctx.req.param(); @@ -37,7 +36,7 @@ export const handler = async (ctx) => { const title = item.find('h2').text(); const image = item.find('a.pic img').prop('src'); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -79,11 +78,7 @@ export const handler = async (ctx) => { const $$ = load(detailResponse); const title = $$('h1.meta-tit a').text(); - const description = - item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.entry').html(), - }); + const description = item.description + renderDescription({ description: $$('div.entry').html() }); item.title = title; item.description = description; diff --git a/lib/routes/423down/templates/description.art b/lib/routes/423down/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/423down/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
{{ intro }}
-{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/423down/templates/description.tsx b/lib/routes/423down/templates/description.tsx new file mode 100644 index 000000000..e8f519243 --- /dev/null +++ b/lib/routes/423down/templates/description.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData) => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
{image.alt ? {image.alt} : }
: null)) : null} + {intro ?
{intro}
: null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/4gamers/templates/description.art b/lib/routes/4gamers/templates/description.art deleted file mode 100644 index 5ba6f0220..000000000 --- a/lib/routes/4gamers/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -
{{ intro }}
-
-{{@ content }} diff --git a/lib/routes/4gamers/templates/image.art b/lib/routes/4gamers/templates/image.art deleted file mode 100644 index 8a2290cb2..000000000 --- a/lib/routes/4gamers/templates/image.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each images img }} -{{ img.alt }}
-{{ /each }} diff --git a/lib/routes/4gamers/utils.ts b/lib/routes/4gamers/utils.tsx similarity index 81% rename from lib/routes/4gamers/utils.ts rename to lib/routes/4gamers/utils.tsx index 5e7da9e82..2ab20b022 100644 --- a/lib/routes/4gamers/utils.ts +++ b/lib/routes/4gamers/utils.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const getCategories = (tryGet) => tryGet('4gamers:categories', async () => { @@ -57,13 +57,23 @@ const parseItem = async (item) => { }; const renderDescription = (intro, content) => - art(path.join(__dirname, 'templates/description.art'), { - intro, - content, - }); + renderToString( + <> +
{intro}
+
+ {raw(content)} + + ); const renderImages = (images) => - art(path.join(__dirname, 'templates/image.art'), { - images, - }); + renderToString( + <> + {images.map((image) => ( + <> + {image.alt} +
+ + ))} + + ); export { getCategories, parseItem, parseList, renderDescription, renderImages }; diff --git a/lib/routes/4ksj/forum.ts b/lib/routes/4ksj/forum.tsx similarity index 84% rename from lib/routes/4ksj/forum.ts rename to lib/routes/4ksj/forum.tsx index 15f505c98..edbe1d0ca 100644 --- a/lib/routes/4ksj/forum.ts +++ b/lib/routes/4ksj/forum.tsx @@ -1,15 +1,56 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import md5 from '@/utils/md5'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +const renderDescription = ({ images, title, keys, details, description, info, links }) => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
+ {image.alt +
+ ) : null + )} + {title ?

{title}

: null} + {keys && details ? ( + + + {keys.map((key) => ( + + + + + ))} + +
{key}{details[key]}
+ ) : null} + {description ?

{description}

: null} + {info ?
{raw(info)}
: null} + {links ? ( + + + {links.map((link) => ( + + + + + ))} + +
+ {link.title} + {link.tags?.join('') ?? ''}
+ ) : null} + + ); + export const route: Route = { path: '/:id?', name: '分类', @@ -177,7 +218,7 @@ async function handler(ctx) { : pubDateEl.find('span[title]').prop('title'); item.title = title; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ images: picture ? [ { diff --git a/lib/routes/4ksj/templates/description.art b/lib/routes/4ksj/templates/description.art deleted file mode 100644 index 160b5fb5c..000000000 --- a/lib/routes/4ksj/templates/description.art +++ /dev/null @@ -1,59 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if !videos?.[0]?.src && image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if title }} -

{{ title }}

-{{ /if }} - -{{ if keys && details }} - - - {{ each keys key }} - - - - - {{ /each }} - -
- {{ key }} - - {{ details[key] }} -
-{{ /if }} - -{{ if description }} -

{{ description }}

-{{ /if }} - -{{ if info }} -
{{@ info }}
-{{ /if }} - -{{ if links }} - - - {{ each links link }} - - - - - {{ /each }} - -
- {{ link.title }} - - {{ link.tags?.join('') ?? '' }} -
-{{ /if }} \ No newline at end of file diff --git a/lib/routes/500px/templates/tribeSet.art b/lib/routes/500px/templates/tribeSet.art deleted file mode 100644 index c9d7688fb..000000000 --- a/lib/routes/500px/templates/tribeSet.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if item.description }}

{{ item.description }}

{{ /if }} -{{ if item.photos }} - {{ each item.photos p }} - - {{ /each }} -{{ /if }} diff --git a/lib/routes/500px/templates/user.art b/lib/routes/500px/templates/user.art deleted file mode 100644 index 0499ef506..000000000 --- a/lib/routes/500px/templates/user.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ if item.url }} - -{{ /if }} diff --git a/lib/routes/500px/tribe-set.ts b/lib/routes/500px/tribe-set.tsx similarity index 79% rename from lib/routes/500px/tribe-set.ts rename to lib/routes/500px/tribe-set.tsx index 12f0c29d9..3d03497e9 100644 --- a/lib/routes/500px/tribe-set.ts +++ b/lib/routes/500px/tribe-set.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { baseUrl, getTribeDetail, getTribeSets } from './utils'; @@ -27,7 +26,12 @@ async function handler(ctx) { const items = tribeSets.map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/tribeSet.art'), { item }), + description: renderToString( + <> + {item.description ?

{item.description}

: null} + {item.photos ? item.photos.map((photo) => ) : null} + + ), author: item.uploaderInfo.nickName, pubDate: parseDate(item.createdTime, 'x'), link: `${baseUrl}/community/set/${item.id}/details`, diff --git a/lib/routes/56kog/templates/description.art b/lib/routes/56kog/templates/description.art deleted file mode 100644 index dccde741a..000000000 --- a/lib/routes/56kog/templates/description.art +++ /dev/null @@ -1,32 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if details }} - - - {{ each details detail }} - - - - - {{ /each }} - -
{{ detail.label }} - {{ if detail.value?.href && detail.value?.text }} - {{ detail.value.text }} - {{ else }} - {{ detail.value }} - {{ /if }} -
-{{ /if }} \ No newline at end of file diff --git a/lib/routes/56kog/util.ts b/lib/routes/56kog/util.tsx similarity index 77% rename from lib/routes/56kog/util.ts rename to lib/routes/56kog/util.tsx index eea4914fa..0748d86f1 100644 --- a/lib/routes/56kog/util.ts +++ b/lib/routes/56kog/util.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const rootUrl = 'https://www.56kog.com'; @@ -69,7 +67,7 @@ const fetchItems = async (limit, currentUrl, tryGet) => { const pubDate = details.find((detail) => detail.label === '更新').value; item.title = content('h1').contents().first().text(); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ images: [ { src: new URL(content('a.mohe-imgs img').prop('src'), rootUrl).href, @@ -107,4 +105,29 @@ const fetchItems = async (limit, currentUrl, tryGet) => { }; }; +const renderDescription = ({ images, details }: { images?: Array<{ src?: string; alt?: string }>; details?: Array<{ label: string; value: any }> }): string => + renderToString( + <> + {images?.map((image, index) => + image?.src ? ( +
+ {image.alt} +
+ ) : null + )} + {details ? ( + + + {details.map((detail, index) => ( + + + + + ))} + +
{detail.label}{detail.value?.href && detail.value?.text ? {detail.value.text} : detail.value}
+ ) : null} + + ); + export { fetchItems, rootUrl }; diff --git a/lib/routes/591/list.ts b/lib/routes/591/list.tsx similarity index 76% rename from lib/routes/591/list.ts rename to lib/routes/591/list.tsx index fcff9079e..746d37ef2 100644 --- a/lib/routes/591/list.ts +++ b/lib/routes/591/list.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { CookieJar } from 'tough-cookie'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import { isValidHost } from '@/utils/valid-host'; const cookieJar = new CookieJar(); @@ -103,7 +101,60 @@ async function getHouseList(houseListURL) { @property {string} distance - The distance to the surrounding. */ -const renderHouse = (house) => art(path.join(__dirname, 'templates/house.art'), { house }); +const renderHouse = (house) => { + const photoList = house.photo_list.slice(1); + + return renderToString( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
類型{house.kind_name}
坪數{house.area} 坪
樓層{house.floor_str}
社區{house.community}
地點{house.location}
更新時間{house.refresh_time}
標籤 + {house.rent_tag.map((tag) => ( + {tag.name} + ))} +
+

+ 更多資訊請見 591 租屋 +

+
+

更多圖片

+
+
+ {photoList.map((photo) => ( + + ))} +
+ + ); +}; export const route: Route = { path: '/:country/rent/:query?', diff --git a/lib/routes/591/templates/house.art b/lib/routes/591/templates/house.art deleted file mode 100644 index d4eaee3ac..000000000 --- a/lib/routes/591/templates/house.art +++ /dev/null @@ -1,52 +0,0 @@ -{{set photoList = house.photo_list.slice(1)}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
類型{{house.kind_name}}
坪數{{house.area}} 坪
樓層{{house.floor_str}}
社區{{house.community}}
地點{{house.location}}
更新時間{{house.refresh_time}}
標籤 - {{each house.rent_tag}} - {{$value.name}} - {{/each}} -
- -

更多資訊請見 591 租屋

- -
- -

更多圖片

- -
- -
- {{each photoList}} - - {{/each}} -
diff --git a/lib/routes/78dm/index.ts b/lib/routes/78dm/index.ts index d0ea3ca0f..30c03dd88 100644 --- a/lib/routes/78dm/index.ts +++ b/lib/routes/78dm/index.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx) => { const { category = 'news' } = ctx.req.param(); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10; @@ -33,7 +32,7 @@ export const handler = async (ctx) => { const src = item.find('a.card-image img').prop('data-src'); const image = src?.startsWith('//') ? `https:${src}` : src; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -83,7 +82,7 @@ export const handler = async (ctx) => { const image = src?.startsWith('//') ? `https:${src}` : src; el.parent().replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: image ? [ { @@ -99,8 +98,8 @@ export const handler = async (ctx) => { const title = $$('h2.title').text(); const description = item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.image-text-content').first().html(), + renderDescription({ + description: $$('div.image-text-content').first().html() || undefined, }); item.title = title; diff --git a/lib/routes/78dm/templates/description.art b/lib/routes/78dm/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/78dm/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/78dm/templates/description.tsx b/lib/routes/78dm/templates/description.tsx new file mode 100644 index 000000000..78065f45c --- /dev/null +++ b/lib/routes/78dm/templates/description.tsx @@ -0,0 +1,20 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src: string; + alt?: string; +}; + +type DescriptionProps = { + images?: Image[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionProps): string => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
{image.alt ? {image.alt} : }
: null)) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/7mmtv/index.ts b/lib/routes/7mmtv/index.tsx similarity index 76% rename from lib/routes/7mmtv/index.ts rename to lib/routes/7mmtv/index.tsx index c77db04fa..9e429346a 100644 --- a/lib/routes/7mmtv/index.ts +++ b/lib/routes/7mmtv/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:language?/:category?/:type?', @@ -89,15 +88,29 @@ async function handler(ctx) { const content = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/description.art'), { - cover: content('.content_main_cover img').attr('src'), - images: content('.owl-lazy') - .toArray() - .map((i) => content(i).attr('data-src')), - description: content('.video-introduction-images-text').html(), - poster: item.poster, - video: item.video, - }); + const cover = content('.content_main_cover img').attr('src'); + const images = content('.owl-lazy') + .toArray() + .map((i) => content(i).attr('data-src')); + const description = content('.video-introduction-images-text').html(); + const poster = item.poster ?? ''; + const video = item.video; + const videoMarkup = video ? `` : ''; + + item.description = renderToString( + <> + {cover ? : null} + {video ? ( + <> +
+ {raw(videoMarkup)} +
+ + ) : null} + {description ? raw(description) : null} + {images.map((image) => (image ? : null))} + + ); item.category = content('.categories a') .toArray() diff --git a/lib/routes/7mmtv/templates/description.art b/lib/routes/7mmtv/templates/description.art deleted file mode 100644 index ead6ae7c9..000000000 --- a/lib/routes/7mmtv/templates/description.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if cover }} - -{{ /if }} - -{{ if video }} -
- -
-{{ /if }} - -{{ if description }}{{@ description }}{{ /if }} - -{{ each images }} - -{{ /each }} diff --git a/lib/routes/8264/list.ts b/lib/routes/8264/list.tsx similarity index 93% rename from lib/routes/8264/list.ts rename to lib/routes/8264/list.tsx index 3e498aea0..4c27b4a8d 100644 --- a/lib/routes/8264/list.ts +++ b/lib/routes/8264/list.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -132,12 +130,11 @@ async function handler(ctx) { content('img').each(function () { content(this).replaceWith( - art(path.join(__dirname, 'templates/description.art'), { - image: { - src: content(this).prop('file'), - alt: content(this).prop('alt'), - }, - }) + renderToString( +
+ {content(this).prop('alt')} +
+ ) ); }); diff --git a/lib/routes/8264/templates/description.art b/lib/routes/8264/templates/description.art deleted file mode 100644 index f8634a8a2..000000000 --- a/lib/routes/8264/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if image }} -
- {{ image.alt }} -
-{{ /if }} \ No newline at end of file diff --git a/lib/routes/91porn/author.ts b/lib/routes/91porn/author.ts index 3fdfa9438..6ab2df39f 100644 --- a/lib/routes/91porn/author.ts +++ b/lib/routes/91porn/author.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; +import { renderIndexDescription } from './templates/index'; import { domainValidation } from './utils'; export const route: Route = { @@ -71,7 +69,7 @@ async function handler(ctx) { const $ = load(data); item.pubDate = parseDate($('.title-yakov').eq(0).text(), 'YYYY-MM-DD'); - item.description = art(path.join(__dirname, 'templates/index.art'), { + item.description = renderIndexDescription({ link: item.link, poster: item.poster, }); diff --git a/lib/routes/91porn/index.ts b/lib/routes/91porn/index.ts index 2a026e16a..2f76cefd4 100644 --- a/lib/routes/91porn/index.ts +++ b/lib/routes/91porn/index.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; +import { renderIndexDescription } from './templates/index'; import { domainValidation } from './utils'; export const route: Route = { @@ -74,7 +72,7 @@ async function handler(ctx) { const $ = load(data); item.pubDate = parseDate($('.title-yakov').eq(0).text(), 'YYYY-MM-DD'); - item.description = art(path.join(__dirname, 'templates/index.art'), { + item.description = renderIndexDescription({ link: item.link, poster: item.poster, }); diff --git a/lib/routes/91porn/templates/index.art b/lib/routes/91porn/templates/index.art deleted file mode 100644 index 09a735bf4..000000000 --- a/lib/routes/91porn/templates/index.art +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/routes/91porn/templates/index.tsx b/lib/routes/91porn/templates/index.tsx new file mode 100644 index 000000000..1291dd825 --- /dev/null +++ b/lib/routes/91porn/templates/index.tsx @@ -0,0 +1,13 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type IndexTemplateData = { + link: string; + poster: string; +}; + +export const renderIndexDescription = ({ link, poster }: IndexTemplateData) => + renderToString( + + + + ); diff --git a/lib/routes/95mm/templates/description.art b/lib/routes/95mm/templates/description.art deleted file mode 100644 index f60deb835..000000000 --- a/lib/routes/95mm/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each images }} - -{{ /each }} diff --git a/lib/routes/95mm/utils.ts b/lib/routes/95mm/utils.tsx similarity index 78% rename from lib/routes/95mm/utils.ts rename to lib/routes/95mm/utils.tsx index 33695a68c..49cb2562f 100644 --- a/lib/routes/95mm/utils.ts +++ b/lib/routes/95mm/utils.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const rootUrl = 'https://www.95mm.vip'; @@ -43,9 +41,13 @@ const ProcessItems = async (ctx, title, currentUrl) => { const images = detailResponse.data.match(/src": '(.*?)',"width/g); - item.description = art(path.join(__dirname, 'templates/description.art'), { - images: images.map((i) => i.split("'")[1].replaceAll(String.raw`\/`, '/')), - }); + item.description = renderToString( + <> + {images.map((image) => ( + + ))} + + ); return item; }) diff --git a/lib/routes/a9vg/index.ts b/lib/routes/a9vg/index.ts index dfbc158b9..5d3525c8b 100644 --- a/lib/routes/a9vg/index.ts +++ b/lib/routes/a9vg/index.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx) => { const { category = 'news/All' } = ctx.req.param(); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 15; @@ -34,7 +33,7 @@ export const handler = async (ctx) => { return { title, link: new URL(item.prop('href'), rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: image ? [ { @@ -60,7 +59,7 @@ export const handler = async (ctx) => { el = $$(el); el.parent().replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: el.prop('file') ? [ { @@ -74,7 +73,7 @@ export const handler = async (ctx) => { }); item.title = $$('h1.ts, div.c-article-main_content-title').first().text(); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ description: $$('td.t_f, div.c-article-main_contentraw').first().html(), }); item.author = diff --git a/lib/routes/a9vg/templates/description.art b/lib/routes/a9vg/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/a9vg/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/a9vg/templates/description.tsx b/lib/routes/a9vg/templates/description.tsx new file mode 100644 index 000000000..590c3f454 --- /dev/null +++ b/lib/routes/a9vg/templates/description.tsx @@ -0,0 +1,26 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: Image[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionData): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
+ {image.alt} +
+ ) : null + )} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/abc/index.ts b/lib/routes/abc/index.ts index 51d60073c..255367b0d 100644 --- a/lib/routes/abc/index.ts +++ b/lib/routes/abc/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/:category{.+}?', @@ -72,7 +71,7 @@ async function handler(ctx) { const item = { title: i.title.children ?? i.title, link: i.link.startsWith('https://') ? i.link : new URL(i.link, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: i.image ? { src: i.image.imgSrc.split(/\?/)[0], @@ -111,7 +110,7 @@ async function handler(ctx) { const element = content(this); if (element.prop('tagName').toLowerCase() === 'figure') { element.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: element.find('img').prop('src').split(/\?/)[0], alt: element.find('figcaption').text().trim(), @@ -140,7 +139,7 @@ async function handler(ctx) { item.enclosure_length = enclosureMatch[2]; item.enclosure_type = enclosureMatch[1]; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ enclosure: { src: item.enclosure_url, type: item.enclosure_type, @@ -149,7 +148,7 @@ async function handler(ctx) { } item.description = - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ description: (content('div[data-component="FeatureMedia"]').html() || '') + (content('#body div[data-component="LayoutContainer"] div').first().html() || ''), }) + item.description; diff --git a/lib/routes/abc/templates/description.art b/lib/routes/abc/templates/description.art deleted file mode 100644 index 480ced501..000000000 --- a/lib/routes/abc/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if image }} -
- {{ image.alt }} -
{{ image.alt }}
-
-{{ /if }} - -{{ if enclosure }} - <{{ enclosure.type.split(/\//)[0] }} controls> - - - - - -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/abc/templates/description.tsx b/lib/routes/abc/templates/description.tsx new file mode 100644 index 000000000..ab5f1a147 --- /dev/null +++ b/lib/routes/abc/templates/description.tsx @@ -0,0 +1,47 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: { + src: string; + alt?: string; + }; + enclosure?: { + src?: string; + type?: string; + }; + description?: string; +}; + +const AbcDescription = ({ image, enclosure, description }: DescriptionData) => { + const enclosureTag = enclosure?.type?.split('/')[0] as keyof JSX.IntrinsicElements | undefined; + + return ( + <> + {image ? ( +
+ {image.alt} +
{image.alt}
+
+ ) : null} + {enclosure && enclosureTag ? ( + <> + {(() => { + const EnclosureTag = enclosureTag; + return ( + + + + + + + ); + })()} + + ) : null} + {description ? raw(description) : null} + + ); +}; + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/accessbriefing/index.ts b/lib/routes/accessbriefing/index.ts index b613f516d..40b8937a8 100644 --- a/lib/routes/accessbriefing/index.ts +++ b/lib/routes/accessbriefing/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { category = 'latest/news' } = ctx.req.param(); @@ -38,7 +37,7 @@ export const handler = async (ctx) => { let items = response.slice(0, limit).map((item) => { const title = item.Article_Headline; const image = new URL(item.Image, rootUrl).href; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -79,7 +78,7 @@ export const handler = async (ctx) => { const title = $$('h1.khl-article-page-title').text(); const description = item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ description: $$('div.khl-article-page-storybody').html(), }); diff --git a/lib/routes/accessbriefing/templates/description.art b/lib/routes/accessbriefing/templates/description.art deleted file mode 100644 index cd725d1f5..000000000 --- a/lib/routes/accessbriefing/templates/description.art +++ /dev/null @@ -1,27 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
{{ intro }}
-{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/accessbriefing/templates/description.tsx b/lib/routes/accessbriefing/templates/description.tsx new file mode 100644 index 000000000..7e005bb11 --- /dev/null +++ b/lib/routes/accessbriefing/templates/description.tsx @@ -0,0 +1,33 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageData = { + src?: string; + alt?: string; + width?: number | string; + height?: number | string; +}; + +type DescriptionData = { + images?: ImageData[]; + intro?: string; + description?: string; +}; + +const AccessBriefingDescription = ({ images, intro, description }: DescriptionData) => ( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
+ {image.height +
+ ) : null + ) + : null} + {intro ?
{intro}
: null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/acs/journal.ts b/lib/routes/acs/journal.tsx similarity index 84% rename from lib/routes/acs/journal.ts rename to lib/routes/acs/journal.tsx index d04f778de..84a95988c 100644 --- a/lib/routes/acs/journal.ts +++ b/lib/routes/acs/journal.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; -import { art } from '@/utils/render'; export const route: Route = { path: '/journal/:id', @@ -69,10 +68,7 @@ async function handler(ctx) { .toArray() .map((a) => $(a).text()) .join(', '), - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.find('.issue-item_img').html(), - description: item.find('.hlFld-Abstract').html(), - }), + description: renderDescription(item.find('.issue-item_img').html(), item.find('.hlFld-Abstract').html()), }; }); }, @@ -88,3 +84,11 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (image: string | null, description: string | null): string => + renderToString( + <> + {image ? raw(image) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/acs/templates/description.art b/lib/routes/acs/templates/description.art deleted file mode 100644 index 4c02bd856..000000000 --- a/lib/routes/acs/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{@ image }} -{{@ description }} \ No newline at end of file diff --git a/lib/routes/adquan/case-library.ts b/lib/routes/adquan/case-library.ts index 6746cbdfa..d1525069a 100644 --- a/lib/routes/adquan/case-library.ts +++ b/lib/routes/adquan/case-library.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '24', 10); @@ -32,7 +31,7 @@ export const handler = async (ctx: Context): Promise => { const $el: Cheerio = $(el); const title: string = $el.find('p.article_2_p').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ intro: $el.find('div.article_1_fu p').first().text(), }); const pubDateStr: string | undefined = $el.find('div.article_1_fu p').last().text(); @@ -72,8 +71,8 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('p.infoTitle_left').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.articleContent').html(), + const description: string | undefined = renderDescription({ + description: $$('div.articleContent').html() ?? undefined, }); const pubDateStr: string | undefined = $$('p.time').text().split(/:/).pop(); const categoryEls: Element[] = $$('span.article_5').toArray(); diff --git a/lib/routes/adquan/index.ts b/lib/routes/adquan/index.ts index 1fd823337..646ed6008 100644 --- a/lib/routes/adquan/index.ts +++ b/lib/routes/adquan/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -32,7 +31,7 @@ export const handler = async (ctx: Context): Promise => { const $el: Cheerio = $(el); const title: string = $el.find('p.article_2_p').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ intro: $el.find('div.article_1_fu p').first().text(), }); const pubDateStr: string | undefined = $el.find('div.article_1_fu p').last().text(); @@ -72,8 +71,8 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('p.infoTitle_left').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.articleContent').html(), + const description: string | undefined = renderDescription({ + description: $$('div.articleContent').html() ?? undefined, }); const pubDateStr: string | undefined = $$('p.time').text().split(/:/).pop(); const categoryEls: Element[] = $$('span.article_5').toArray(); diff --git a/lib/routes/adquan/templates/description.art b/lib/routes/adquan/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/adquan/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
{{ intro }}
-{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/adquan/templates/description.tsx b/lib/routes/adquan/templates/description.tsx new file mode 100644 index 000000000..ca6a8f6d2 --- /dev/null +++ b/lib/routes/adquan/templates/description.tsx @@ -0,0 +1,15 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionRenderOptions = { + intro?: string; + description?: string; +}; + +export const renderDescription = ({ intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {intro ?
{intro}
: null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/aeaweb/index.ts b/lib/routes/aeaweb/index.tsx similarity index 85% rename from lib/routes/aeaweb/index.ts rename to lib/routes/aeaweb/index.tsx index 0efe029f4..97b6d4bfd 100644 --- a/lib/routes/aeaweb/index.ts +++ b/lib/routes/aeaweb/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:id', @@ -93,11 +91,13 @@ async function handler(ctx) { .map((a) => content(a).text().trim()) .join(', '); item.pubDate = parseDate(content('meta[name="citation_publication_date"]').attr('content'), 'YYYY/MM'); - item.description = art(path.join(__dirname, 'templates/description.art'), { - description: content('meta[name="twitter:description"]') - .attr('content') - .replace(/\(\w+ \d+\)( - )?/, ''), - }); + item.description = renderToString( + + ); return item; }) @@ -112,3 +112,5 @@ async function handler(ctx) { language: $('html').attr('lang'), }; } + +const AeawebDescription = ({ description }: { description?: string }) => (description ?

{description}

: null); diff --git a/lib/routes/aeaweb/templates/description.art b/lib/routes/aeaweb/templates/description.art deleted file mode 100644 index 1053a98f8..000000000 --- a/lib/routes/aeaweb/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ if description }} -

{{ description }}

-{{ /if }} \ No newline at end of file diff --git a/lib/routes/aeon/templates/essay.art b/lib/routes/aeon/templates/essay.art deleted file mode 100644 index 8fed51cad..000000000 --- a/lib/routes/aeon/templates/essay.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if banner.url }} -
- {{ banner.alt }} - {{ if banner.caption }} -
{{ banner.caption }}
- {{ /if }} -{{ /if }} -{{@ authorsBio }} -{{@ content }} diff --git a/lib/routes/aeon/templates/video.art b/lib/routes/aeon/templates/video.art deleted file mode 100644 index b16cf33d5..000000000 --- a/lib/routes/aeon/templates/video.art +++ /dev/null @@ -1,10 +0,0 @@ -{{ set video = article.hosterId }} -{{ if article.hoster === 'vimeo' }} - {{ set video = "https://player.vimeo.com/video/" + video + "?dnt=1" }} -{{ else if article.hoster === 'youtube' }} - {{ set video = "https://www.youtube-nocookie.com/embed/" + video }} -{{ /if }} - - -{{@ article.credits }} -{{@ article.description }} diff --git a/lib/routes/aeon/utils.ts b/lib/routes/aeon/utils.tsx similarity index 67% rename from lib/routes/aeon/utils.ts rename to lib/routes/aeon/utils.tsx index 2e1d05fe8..7cb63fba2 100644 --- a/lib/routes/aeon/utils.ts +++ b/lib/routes/aeon/utils.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const getBuildId = () => cache.tryGet( @@ -21,6 +20,38 @@ export const getBuildId = () => false ); +const renderVideoDescription = (article) => { + let video = article.hosterId; + + if (article.hoster === 'vimeo') { + video = `https://player.vimeo.com/video/${video}?dnt=1`; + } else if (article.hoster === 'youtube') { + video = `https://www.youtube-nocookie.com/embed/${video}`; + } + + return renderToString( + <> + + {article.credits ? raw(article.credits) : null} + {article.description ? raw(article.description) : null} + + ); +}; + +const renderEssayDescription = ({ banner, authorsBio, content }) => + renderToString( + <> + {banner?.url ? ( +
+ {banner.alt} + {banner.caption ?
{banner.caption}
: null} +
+ ) : null} + {authorsBio ? raw(authorsBio) : null} + {content ? raw(content) : null} + + ); + const getData = async (list) => { const items = await Promise.all( list.map((item) => @@ -34,7 +65,7 @@ const getData = async (list) => { item.pubDate = parseDate(data.publishedAt); if (type === 'video') { - item.description = art(path.join(__dirname, 'templates/video.art'), { article: data }); + item.description = renderVideoDescription(data); } else { if (data.audio?.id) { const response = await ofetch('https://api.aeonmedia.co/graphql', { @@ -70,7 +101,7 @@ const getData = async (list) => { const authorsBio = data.authors.map((author) => '

' + author.name + author.authorBio.replaceAll(/^

/g, ' ')).join(''); - item.description = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content: capture.html() }); + item.description = renderEssayDescription({ banner, authorsBio, content: capture.html() }); } return item; diff --git a/lib/routes/agri/index.ts b/lib/routes/agri/index.ts index 07cf69db9..cdacaf36f 100644 --- a/lib/routes/agri/index.ts +++ b/lib/routes/agri/index.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx) => { const { category = 'zx/zxfb/' } = ctx.req.param(); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10; @@ -32,7 +31,7 @@ export const handler = async (ctx) => { const title = a.text(); const image = item.find('img').first().prop('src') ? new URL(item.find('img').first().prop('src'), rootUrl).href : undefined; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ intro: item.find('p.con_text').text() || undefined, images: image ? [ @@ -67,8 +66,8 @@ export const handler = async (ctx) => { const $$ = load(detailResponse); const title = $$('div.detailCon_info_tit').text().trim(); - const description = art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.content_body_box').html(), + const description = renderDescription({ + description: $$('div.content_body_box').html() || undefined, }); item.title = title; diff --git a/lib/routes/agri/templates/description.art b/lib/routes/agri/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/agri/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -

- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
{{ intro }}
-{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/agri/templates/description.tsx b/lib/routes/agri/templates/description.tsx new file mode 100644 index 000000000..81ceaef91 --- /dev/null +++ b/lib/routes/agri/templates/description.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src: string; + alt?: string; +}; + +type DescriptionProps = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionProps): string => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
{image.alt ? {image.alt} : }
: null)) : null} + {intro ?
{intro}
: null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/aibase/templates/description.art b/lib/routes/aibase/templates/description.art deleted file mode 100644 index fae2782a3..000000000 --- a/lib/routes/aibase/templates/description.art +++ /dev/null @@ -1,100 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
- {{ image.alt }} -
- {{ /if }} - {{ /each }} -{{ /if }} - -{{ if item }} - - - - - - - - - - - - - - - - {{ if item.desc }} - {{ item.desc }} - {{ else }} - 无 - {{ /if }} - - - - - - - - - - - - - - - - - - -
名称{{ item.name }}
标签 - {{ each strToArray(item.tags) t }} - {{ t }}  - {{ /each }} -
类型 - {{ if item.proctypename }} - {{ item.proctypename }} - {{ else }} - 无 - {{ /if }} -
描述
需求人群 - {{ set list = strToArray(item.use) }} - {{ if list.length === 1 }} - {{ list[0] }} - {{ else }} - {{ each list l }} -
  • {{ l }}
  • - {{ /each }} - {{ /if }} -
    使用场景示例 - {{ set list = strToArray(item.example) }} - {{ if list.length === 1 }} - {{ list[0] }} - {{ else }} - {{ each list l }} -
  • {{ l }}
  • - {{ /each }} - {{ /if }} -
    产品特色 - {{ set list = strToArray(item.functions) }} - {{ if list.length === 1 }} - {{ list[0] }} - {{ else }} - {{ each list l }} -
  • {{ l }}
  • - {{ /each }} - {{ /if }} -
    站点 - {{ if item.url }} - - {{ item.url }} - - {{ else }} - 无 - {{ /if }} -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/aibase/util.ts b/lib/routes/aibase/util.tsx similarity index 58% rename from lib/routes/aibase/util.ts rename to lib/routes/aibase/util.tsx index 5926f304f..19364d0d3 100644 --- a/lib/routes/aibase/util.ts +++ b/lib/routes/aibase/util.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const defaultSrc = '_static/ee6af7e.js'; @@ -28,8 +26,6 @@ const strToArray = (str: string) => { return [str]; }; -art.defaults.imports.strToArray = strToArray; - /** * Retrieve a token asynchronously using a CheerioAPI instance. * @param $ - The CheerioAPI instance. @@ -79,7 +75,7 @@ const processItems = (items: any[]): any[] => items.map((item) => { const title = item.name; const image = item.imgurl; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -114,3 +110,73 @@ const processItems = (items: any[]): any[] => }); export { buildApiUrl, processItems, rootUrl }; + +const renderDescription = ({ images, item }: { images?: Array<{ src?: string; alt?: string }>; item?: any }): string => + renderToString( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {item ? ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    名称{item.name}
    标签 + {strToArray(item.tags).map((tag) => ( + <> + {tag}  + + ))} +
    类型{item.proctypename || '无'}
    描述{item.desc || '无'}
    需求人群{renderListText(item.use)}
    使用场景示例{renderListText(item.example)}
    产品特色{renderListText(item.functions)}
    站点{item.url ? {item.url} : '无'}
    + ) : null} + + ); + +const renderListText = (value: string | undefined) => { + if (!value) { + return '无'; + } + + const list = strToArray(value); + if (list.length === 1) { + return list[0]; + } + + return list.map((entry, index) =>
  • {entry}
  • ); +}; diff --git a/lib/routes/aicaijing/index.ts b/lib/routes/aicaijing/index.tsx similarity index 87% rename from lib/routes/aicaijing/index.ts rename to lib/routes/aicaijing/index.tsx index c57ee1cff..19d768e4b 100644 --- a/lib/routes/aicaijing/index.ts +++ b/lib/routes/aicaijing/index.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:category?/:id?', @@ -68,10 +68,12 @@ async function handler(ctx) { author: item.userInfo.nickname, pubDate: parseDate(item.createTime), category: [item.category.name, ...item.tags.map((t) => t.name)], - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.cover, - description: item.content, - }), + description: renderToString( + <> + {item.cover ? : null} + {item.content ? raw(item.content) : null} + + ), })); return { diff --git a/lib/routes/aicaijing/templates/description.art b/lib/routes/aicaijing/templates/description.art deleted file mode 100644 index c823516c9..000000000 --- a/lib/routes/aicaijing/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ if image }} - -{{ /if }} -{{@ description }} \ No newline at end of file diff --git a/lib/routes/aip/templates/description.art b/lib/routes/aip/templates/description.art deleted file mode 100644 index 3f368b9b1..000000000 --- a/lib/routes/aip/templates/description.art +++ /dev/null @@ -1,8 +0,0 @@ -

    - {{ title }}
    -

    -

    - {{ authors }}
    - https://doi.org/{{ doi }}
    - {{ if img }}{{ /if }} -

    \ No newline at end of file diff --git a/lib/routes/aip/utils.ts b/lib/routes/aip/utils.ts deleted file mode 100644 index 1590c8d02..000000000 --- a/lib/routes/aip/utils.ts +++ /dev/null @@ -1,27 +0,0 @@ -import path from 'node:path'; - -import { art } from '@/utils/render'; - -const puppeteerGet = async (url, browser) => { - const page = await browser.newPage(); - // await page.setExtraHTTPHeaders({ referer: host }); - await page.setRequestInterception(true); - page.on('request', (request) => { - request.resourceType() === 'document' ? request.continue() : request.abort(); - }); - await page.goto(url, { - waitUntil: 'domcontentloaded', - }); - const html = await page.evaluate(() => document.documentElement.innerHTML); - return html; -}; - -const renderDesc = (title, authors, doi, img) => - art(path.join(__dirname, 'templates/description.art'), { - title, - authors, - doi, - img, - }); - -export { puppeteerGet, renderDesc }; diff --git a/lib/routes/aip/utils.tsx b/lib/routes/aip/utils.tsx new file mode 100644 index 000000000..a18ba6b30 --- /dev/null +++ b/lib/routes/aip/utils.tsx @@ -0,0 +1,44 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +const puppeteerGet = async (url, browser) => { + const page = await browser.newPage(); + // await page.setExtraHTTPHeaders({ referer: host }); + await page.setRequestInterception(true); + page.on('request', (request) => { + request.resourceType() === 'document' ? request.continue() : request.abort(); + }); + await page.goto(url, { + waitUntil: 'domcontentloaded', + }); + const html = await page.evaluate(() => document.documentElement.innerHTML); + return html; +}; + +const renderDesc = (title, authors, doi, img) => + renderToString( + <> +

    + + {title} + +
    +

    +

    + + + {authors} + + +
    + + + https://doi.org/{doi} + + +
    + {img ? : null} +

    + + ); + +export { puppeteerGet, renderDesc }; diff --git a/lib/routes/ali213/news.ts b/lib/routes/ali213/news.ts index c54fa4c13..53a454f53 100644 --- a/lib/routes/ali213/news.ts +++ b/lib/routes/ali213/news.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { category = 'new' } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -41,7 +40,7 @@ export const handler = async (ctx: Context): Promise => { const intro: string = $item.find('div.lone_f_r_t').text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: imageEl ? [ { @@ -102,7 +101,7 @@ export const handler = async (ctx: Context): Promise => { media[mediaType] = { url: mediaUrl }; pEl.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: [ { src: mediaUrl, @@ -114,7 +113,7 @@ export const handler = async (ctx: Context): Promise => { }); } - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ description: $$('div#Content').html() ?? '', }); diff --git a/lib/routes/ali213/templates/description.art b/lib/routes/ali213/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/ali213/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ali213/templates/description.tsx b/lib/routes/ali213/templates/description.tsx new file mode 100644 index 000000000..2e77b346f --- /dev/null +++ b/lib/routes/ali213/templates/description.tsx @@ -0,0 +1,30 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData) => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/ali213/zl.ts b/lib/routes/ali213/zl.ts index 6c405624e..23e6839bb 100644 --- a/lib/routes/ali213/zl.ts +++ b/lib/routes/ali213/zl.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category } = ctx.req.param(); @@ -35,7 +34,7 @@ export const handler = async (ctx: Context): Promise => { .data.slice(0, limit) .map((item): DataItem => { const title: string = item.Title; - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ intro: item.GuideRead ?? '', }); const guid: string = `ali213-zl-${item.ID}`; @@ -98,7 +97,7 @@ export const handler = async (ctx: Context): Promise => { description += pageContents.join(''); - description = art(path.join(__dirname, 'templates/description.art'), { + description = renderDescription({ description, }); diff --git a/lib/routes/aljazeera/index.ts b/lib/routes/aljazeera/index.tsx similarity index 85% rename from lib/routes/aljazeera/index.ts rename to lib/routes/aljazeera/index.tsx index 40f93eb45..fb1f5a7cb 100644 --- a/lib/routes/aljazeera/index.ts +++ b/lib/routes/aljazeera/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; const languages = { arabic: { @@ -23,6 +22,18 @@ const languages = { }, }; +const renderDescription = (image, description) => + renderToString( + <> + {image ? ( +
    + <>{raw(image)} +
    + ) : null} + {description ? <>{raw(description)} : null} + + ); + export const route: Route = { path: '*', name: 'Unknown', @@ -84,10 +95,7 @@ async function handler(ctx) { item.title = content('h1').first().text(); item.author = content('.author').text(); item.pubDate = pubDate; - item.description = art(path.join(__dirname, 'templates/description.art'), { - image: content('.article-featured-image').html(), - description: content('.wysiwyg').html(), - }); + item.description = renderDescription(content('.article-featured-image').html(), content('.wysiwyg').html()); return item; }) diff --git a/lib/routes/aljazeera/templates/description.art b/lib/routes/aljazeera/templates/description.art deleted file mode 100644 index cbd5696ec..000000000 --- a/lib/routes/aljazeera/templates/description.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if image }} -
    -{{@ image }} -
    -{{ /if }} -{{ if description }} -{{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/amazfitwatchfaces/index.ts b/lib/routes/amazfitwatchfaces/index.ts index dd64d326b..1d6591b83 100644 --- a/lib/routes/amazfitwatchfaces/index.ts +++ b/lib/routes/amazfitwatchfaces/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { device, sort, searchParams } = ctx.req.param(); @@ -33,7 +32,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.prop('title'); const image: string | undefined = $el.find('img.wf-img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -88,7 +87,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('div.page-title h1').text(); const image: string | undefined = $$('img#watchface-preview').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -97,7 +96,7 @@ export const handler = async (ctx: Context): Promise => { }, ] : undefined, - description: $$('div.unicodebidi').html(), + description: $$('div.unicodebidi').html() ?? undefined, }); const pubDateStr: string | undefined = $$('i.fa-calendar').parent().find('span').text(); const linkUrl: string | undefined = $$('.title').attr('href'); diff --git a/lib/routes/amazfitwatchfaces/templates/description.art b/lib/routes/amazfitwatchfaces/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/amazfitwatchfaces/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/amazfitwatchfaces/templates/description.tsx b/lib/routes/amazfitwatchfaces/templates/description.tsx new file mode 100644 index 000000000..0dbe1008b --- /dev/null +++ b/lib/routes/amazfitwatchfaces/templates/description.tsx @@ -0,0 +1,26 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/amazon/kindle-software-updates.ts b/lib/routes/amazon/kindle-software-updates.tsx similarity index 87% rename from lib/routes/amazon/kindle-software-updates.ts rename to lib/routes/amazon/kindle-software-updates.tsx index 4af10e1fb..85510f647 100644 --- a/lib/routes/amazon/kindle-software-updates.ts +++ b/lib/routes/amazon/kindle-software-updates.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const host = 'https://www.amazon.com'; export const route: Route = { @@ -61,9 +59,13 @@ async function handler() { title: item.title + ' - ' + item.version, description: item.description + - art(path.join(__dirname, 'templates/software-description.art'), { - item, - }), + renderToString( +
    +
    +
    + Kindle Website +
    + ), guid: item.title + ' - ' + item.version, link: item.link, })), diff --git a/lib/routes/amazon/templates/software-description.art b/lib/routes/amazon/templates/software-description.art deleted file mode 100644 index b8f451600..000000000 --- a/lib/routes/amazon/templates/software-description.art +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/lib/routes/apnews/templates/description.art b/lib/routes/apnews/templates/description.art deleted file mode 100644 index aa54abacb..000000000 --- a/lib/routes/apnews/templates/description.art +++ /dev/null @@ -1,14 +0,0 @@ -{{ if media }} - {{ each media }} - {{ if $value.type === 'Photo' }} -
    - {{ $value.flattenedCaption }} -
    {{@ $value.caption }}
    -
    - {{ else if $value.type === 'YouTube' }} - - {{ if $value.caption }}{{@ $value.caption }}{{ /if }} - {{ /if }} - {{ /each }} -{{ /if }} -{{@ description }} diff --git a/lib/routes/app-center/release.ts b/lib/routes/app-center/release.tsx similarity index 66% rename from lib/routes/app-center/release.ts rename to lib/routes/app-center/release.tsx index ebe6106ea..83441a32d 100644 --- a/lib/routes/app-center/release.ts +++ b/lib/routes/app-center/release.tsx @@ -1,12 +1,94 @@ -import path from 'node:path'; - +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import MarkdownIt from 'markdown-it'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +type DescriptionProps = { + releaseDate: string; + fingerprint: string; + appOS: string; + minOS: string | null; + deviceFamily: string | null; + androidMinApiLevel: string | number | null; + bundleId: string | null; + downloadUrl: string; + installUrl: string; + fileExtension: string | null; + sizeInMBytes: string; + releaseNotes?: string; +}; + +const renderDescription = ({ releaseDate, fingerprint, appOS, minOS, deviceFamily, androidMinApiLevel, bundleId, downloadUrl, installUrl, fileExtension, sizeInMBytes, releaseNotes }: DescriptionProps) => { + const releaseNotesHtml = releaseNotes?.trim().replaceAll('\n', '
    '); + + return renderToString( + <> +

    + Release Date: {releaseDate} +
    + Fingerprint: {fingerprint} +
    + OS: {appOS} +
    + {minOS ? ( + <> + Minimum OS Version: {minOS} +
    + + ) : null} + {androidMinApiLevel ? ( + <> + Android Minimum API Level: {androidMinApiLevel} +
    + + ) : null} + {deviceFamily ? ( + <> + Device Family: {deviceFamily} +
    + + ) : null} + {bundleId ? ( + <> + Bundle ID: {bundleId} +
    + + ) : null} + {fileExtension ? ( + <> + File Extension: {fileExtension} +
    + + ) : null} + Size: {sizeInMBytes} MB +

    + {releaseNotesHtml ? ( + <> +

    + Release Notes: +

    + {raw(releaseNotesHtml)} + + ) : null} +

    + [ Download + {downloadUrl === installUrl ? ( + '' + ) : ( + <> + {' '} + | Install + + )}{' '} + ] +

    + + ); +}; export const route: Route = { path: '/release/:user/:app/:distribution_group', @@ -112,24 +194,20 @@ async function handler(ctx) { `Version ${version}`; item.link = link; // replace the link with the release page item.author = userName; - item.description = art( - path.join(__dirname, 'templates/description.art'), - { - releaseDate, - sizeInMBytes, - minOS, - deviceFamily, - androidMinApiLevel, - bundleId, - downloadUrl, - installUrl, - fingerprint, - appOS, - fileExtension, - releaseNotes: releaseNotes && md.render(releaseNotes), - }, - { minimize: true } - ); + item.description = renderDescription({ + releaseDate, + sizeInMBytes, + minOS, + deviceFamily, + androidMinApiLevel, + bundleId, + downloadUrl, + installUrl, + fingerprint, + appOS, + fileExtension, + releaseNotes: releaseNotes && md.render(releaseNotes), + }); item.guid = fingerprint; return item; diff --git a/lib/routes/app-center/templates/description.art b/lib/routes/app-center/templates/description.art deleted file mode 100644 index 97ad7121b..000000000 --- a/lib/routes/app-center/templates/description.art +++ /dev/null @@ -1,24 +0,0 @@ -

    -Release Date: {{releaseDate}}
    -Fingerprint: {{fingerprint}}
    -OS: {{appOS}}
    -{{if minOS}}Minimum OS Version: {{minOS}}
    {{/if}} -{{if androidMinApiLevel}}Android Minimum API Level: {{androidMinApiLevel}}
    {{/if}} -{{if deviceFamily}}Device Family: {{deviceFamily}}
    {{/if}} -{{if bundleId}}Bundle ID: {{bundleId}}
    {{/if}} -{{if fileExtension}}File Extension: {{fileExtension}}
    {{/if}} -Size: {{sizeInMBytes}} MB -

    -{{if releaseNotes}} -

    -Release Notes: -

    -{{@ releaseNotes.trim().replace(/\n/g, '
    ') }} -{{/if}} -

    -{{if downloadUrl===installUrl}} -[ Download ] -{{else}} -[ Download | Install ] -{{/if}} -

    diff --git a/lib/routes/app-sales/templates/description.art b/lib/routes/app-sales/templates/description.art deleted file mode 100644 index d57823de7..000000000 --- a/lib/routes/app-sales/templates/description.art +++ /dev/null @@ -1,120 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if appName }} - - - - - - - {{ if appDev }} - - - - - {{ /if }} - {{ if appNote }} - - - - - {{ /if }} - {{ if rating }} - - - - - {{ /if }} - {{ if downloads }} - - - - - {{ /if }} - {{ if bookmarks }} - - - - - {{ /if }} - {{ if priceNew }} - - - - - {{ /if }} - {{ if linkUrl }} - - - - - {{ /if }} - -
    - Name - - {{ appName }} -
    - Developer - - {{ appDev }} -
    - Note - - {{ appNote }} -
    - Rating - - {{ rating }} -
    - Downloads - - {{ downloads }} -
    - Bookmarks - - {{ bookmarks }} -
    - Price - - - - {{ priceNew }} - - - {{ if priceOld }} - - - - {{ priceOld }} - - - - {{ /if }} - {{ if priceDisco }} - - - {{ priceDisco }} - - - {{ /if }} -
    - Link - - - {{ linkUrl }} - -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/app-sales/util.ts b/lib/routes/app-sales/util.tsx similarity index 61% rename from lib/routes/app-sales/util.ts rename to lib/routes/app-sales/util.tsx index 8e81daae7..76657fa51 100644 --- a/lib/routes/app-sales/util.ts +++ b/lib/routes/app-sales/util.tsx @@ -1,14 +1,120 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; +import { renderToString } from 'hono/jsx/dom/server'; import type { DataItem } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; const baseUrl: string = 'https://www.app-sales.net'; +const renderDescription = ({ + images, + appName, + appDev, + appNote, + rating, + downloads, + bookmarks, + priceNew, + priceOld, + priceDisco, + linkUrl, +}: { + images?: { alt?: string; src?: string }[]; + appName?: string; + appDev?: string; + appNote?: string; + rating?: string; + downloads?: string; + bookmarks?: string; + priceNew?: string; + priceOld?: string; + priceDisco?: string; + linkUrl?: string; +}): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {appName ? ( + + + + + + + {appDev ? ( + + + + + ) : null} + {appNote ? ( + + + + + ) : null} + {rating ? ( + + + + + ) : null} + {downloads ? ( + + + + + ) : null} + {bookmarks ? ( + + + + + ) : null} + {priceNew ? ( + + + + + ) : null} + {linkUrl ? ( + + + + + ) : null} + +
    Name{appName}
    Developer{appDev}
    Note{appNote}
    Rating{rating}
    Downloads{downloads}
    Bookmarks{bookmarks}
    Price + + {priceNew} + + {priceOld ? ( + + + {priceOld} + + + ) : null} + {priceDisco ? ( + + {priceDisco} + + ) : null} +
    Link + + {linkUrl} + +
    + ) : null} + + ); /** * Formats price change information into a standardized tag @@ -47,7 +153,7 @@ const processItems = ($: CheerioAPI, selector: string): DataItem[] => const title: string = `${appName} ${formatPriceChangeTag(priceOld, priceNew, priceDisco)}`; const image: string | undefined = $el.find('div.app-icon img').attr('src'); const linkUrl: string | undefined = $el.find('div.sale-list-action a').attr('href'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { diff --git a/lib/routes/apple/security-releases.ts b/lib/routes/apple/security-releases.ts index 723441955..48b9a2432 100644 --- a/lib/routes/apple/security-releases.ts +++ b/lib/routes/apple/security-releases.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/security-releases'; export const handler = async (ctx: Context): Promise => { const { language = 'en-us' } = ctx.req.param(); @@ -38,7 +37,7 @@ export const handler = async (ctx: Context): Promise => { const titleEl: Cheerio = $el.find('td').first(); const title: string = titleEl.contents().first().text(); - const description: string | undefined = art(path.join(__dirname, 'templates/security-releases.art'), { + const description: string | undefined = renderDescription({ headers, infos: $el .find('td') @@ -83,7 +82,7 @@ export const handler = async (ctx: Context): Promise => { const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/security-releases.art'), { + renderDescription({ description: $$('div#sections').html(), }); const pubDateStr: string | undefined = detailResponse.match(/publish_date:\s"(\d{8})",/, '')?.[1]; diff --git a/lib/routes/apple/templates/security-releases.art b/lib/routes/apple/templates/security-releases.art deleted file mode 100644 index 9801d5ce5..000000000 --- a/lib/routes/apple/templates/security-releases.art +++ /dev/null @@ -1,28 +0,0 @@ -{{ if headers && infos }} - - - {{ if headers.length > 0 }} - - {{ each headers header }} - - {{ /each }} - - {{ /if }} - {{ if infos.length > 0 }} - - {{ each infos info }} - - {{ /each }} - - {{ /if }} - -
    - {{ header }} -
    - {{@ info }} -
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/apple/templates/security-releases.tsx b/lib/routes/apple/templates/security-releases.tsx new file mode 100644 index 000000000..3d7858f90 --- /dev/null +++ b/lib/routes/apple/templates/security-releases.tsx @@ -0,0 +1,36 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type SecurityReleasesData = { + headers?: string[]; + infos?: string[]; + description?: string; +}; + +const SecurityReleasesDescription = ({ headers, infos, description }: SecurityReleasesData) => ( + <> + {headers && infos ? ( + + + {headers.length > 0 ? ( + + {headers.map((header) => ( + + ))} + + ) : null} + {infos.length > 0 ? ( + + {infos.map((info) => ( + + ))} + + ) : null} + +
    {header}
    {info ? raw(info) : null}
    + ) : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: SecurityReleasesData) => renderToString(); diff --git a/lib/routes/appstorrent/programs.ts b/lib/routes/appstorrent/programs.tsx similarity index 54% rename from lib/routes/appstorrent/programs.ts rename to lib/routes/appstorrent/programs.tsx index 3e273623b..ced480153 100644 --- a/lib/routes/appstorrent/programs.ts +++ b/lib/routes/appstorrent/programs.tsx @@ -1,15 +1,13 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import dayjs from 'dayjs'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import type { Options } from '@/utils/got'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/programs', @@ -62,22 +60,37 @@ async function handler(ctx?: Context): Promise { link: item.link, category: item.category, pubDate, - description: art(path.join(__dirname, 'templates/description.art'), { - cover: baseUrl + $('.main-title img').attr('src')?.trim(), - title: item.title, - pubDate: dayjs(pubDate).format('YYYY-MM-DD'), - version: item.version, - architecture: item.architecture, - compatibility: $('div.right > div.info > div.right-container > div:nth-child(5) > div > span:nth-child(2) > a').text(), - size: item.size, - activation: $('div.right > div.info > div.right-container > div:nth-child(4) > div > span:nth-child(2) > a').text(), - description: $('.content .body-content').first().text(), - changelog: $('.content .body-content').last().text(), - screenshots: $('.screenshots img') - .toArray() - .map((img) => $(img).attr('src')) - .map((src) => baseUrl + src), - }), + description: renderToString( + <> +

    + +

    {item.title}

    +
    + Public Date: {dayjs(pubDate).format('YYYY-MM-DD')} +
    + Version: {item.version} +
    + Architecture: {item.architecture} +
    + Compactibility: {$('div.right > div.info > div.right-container > div:nth-child(5) > div > span:nth-child(2) > a').text()} +
    + Size: {item.size} +
    + Activation: {$('div.right > div.info > div.right-container > div:nth-child(4) > div > span:nth-child(2) > a').text()} +
    +

    + Description:

    {$('.content .body-content').first().text()}

    + Change Log:

    {$('.content .body-content').last().text()}

    + Screenshots + {$('.screenshots img') + .toArray() + .map((img) => $(img).attr('src')) + .map((src) => baseUrl + src) + .map((src) => ( + + ))} + + ), } as DataItem; }) as Promise ) diff --git a/lib/routes/appstorrent/templates/description.art b/lib/routes/appstorrent/templates/description.art deleted file mode 100644 index 2e86e1682..000000000 --- a/lib/routes/appstorrent/templates/description.art +++ /dev/null @@ -1,22 +0,0 @@ -

    - -

    {{ title }}


    -Public Date: {{pubDate}}
    -Version: {{version}}
    -Architecture: {{architecture}}
    -Compactibility: {{compatibility}}
    -Size: {{size}}
    -Activation: {{activation}}
    -

    -Description: -

    -{{ description }} -

    -Change Log: -

    -{{ changelog }} -

    -Screenshots -{{each screenshots}} - -{{/each}} \ No newline at end of file diff --git a/lib/routes/aqara/post.ts b/lib/routes/aqara/post.tsx similarity index 93% rename from lib/routes/aqara/post.ts rename to lib/routes/aqara/post.tsx index d64e1d96a..0906de8a1 100644 --- a/lib/routes/aqara/post.ts +++ b/lib/routes/aqara/post.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '*', @@ -61,11 +59,11 @@ async function handler(ctx) { const height = image.prop('data-rawheight') ?? image.prop('height'); content(this).replaceWith( - art(path.join(__dirname, 'templates/figure.art'), { - src, - width, - height, - }) + renderToString( +
    + +
    + ) ); }); diff --git a/lib/routes/aqara/templates/figure.art b/lib/routes/aqara/templates/figure.art deleted file mode 100644 index 60b9c69b9..000000000 --- a/lib/routes/aqara/templates/figure.art +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/lib/routes/arcteryx/new-arrivals.ts b/lib/routes/arcteryx/new-arrivals.ts index 6885aab2c..5b51ae16b 100644 --- a/lib/routes/arcteryx/new-arrivals.ts +++ b/lib/routes/arcteryx/new-arrivals.ts @@ -1,9 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; +import { renderProductDescription } from './templates/product-description'; import { generateRssData } from './utils'; export const route: Route = { @@ -68,9 +66,7 @@ async function handler(ctx) { item: items.map((item) => ({ title: item.name, link: productUrl + item.slug, - description: art(path.join(__dirname, 'templates/product-description.art'), { - item, - }), + description: renderProductDescription(item), })), }; } diff --git a/lib/routes/arcteryx/outlet.ts b/lib/routes/arcteryx/outlet.ts index 71020df27..634166974 100644 --- a/lib/routes/arcteryx/outlet.ts +++ b/lib/routes/arcteryx/outlet.ts @@ -1,9 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; +import { renderProductDescription } from './templates/product-description'; import { generateRssData } from './utils'; export const route: Route = { @@ -70,9 +68,7 @@ async function handler(ctx) { item: items.map((item) => ({ title: item.name, link: productUrl + item.slug, - description: art(path.join(__dirname, 'templates/product-description.art'), { - item, - }), + description: renderProductDescription(item), })), }; } diff --git a/lib/routes/arcteryx/regear-new-arrivals.ts b/lib/routes/arcteryx/regear-new-arrivals.tsx similarity index 78% rename from lib/routes/arcteryx/regear-new-arrivals.ts rename to lib/routes/arcteryx/regear-new-arrivals.tsx index a0dba4293..14219e875 100644 --- a/lib/routes/arcteryx/regear-new-arrivals.ts +++ b/lib/routes/arcteryx/regear-new-arrivals.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const host = 'https://www.regear.arcteryx.com'; function getUSDPrice(number) { @@ -59,9 +57,24 @@ async function handler() { regearPrice: item.priceRange[0] === item.priceRange[1] ? getUSDPrice(item.priceRange[0]) : `${getUSDPrice(item.priceRange[0])} - ${getUSDPrice(item.priceRange[1])}`, description: '', }; - data.description = art(path.join(__dirname, 'templates/regear-product-description.art'), { - data, - }); + data.description = renderToString( +
    + Available Sizes:  + {data.availableSizes.map((size) => ( + <>{size}  + ))} +
    + Color: {data.color} +
    + Original Price: {data.originalPrice} +
    + Regear Price: {data.regearPrice} +
    + +
    +
    +
    + ); return data; }); diff --git a/lib/routes/arcteryx/templates/product-description.art b/lib/routes/arcteryx/templates/product-description.art deleted file mode 100644 index a00005c73..000000000 --- a/lib/routes/arcteryx/templates/product-description.art +++ /dev/null @@ -1,12 +0,0 @@ -
    - {{if item.short_description}} - {{item.short_description}}
    - {{/if}} - {{if item.original_price}} - Original Price: {{item.original_price}}
    - {{/if}} - {{if item.price}} - Current Price: {{item.price}}
    - {{/if}} - -
    diff --git a/lib/routes/arcteryx/templates/product-description.tsx b/lib/routes/arcteryx/templates/product-description.tsx new file mode 100644 index 000000000..9ae186f71 --- /dev/null +++ b/lib/routes/arcteryx/templates/product-description.tsx @@ -0,0 +1,33 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type ProductItem = { + short_description?: string; + original_price?: string; + price?: string; + image: string; +}; + +export const renderProductDescription = (item: ProductItem): string => + renderToString( +
    + {item.short_description ? ( + <> + {item.short_description} +
    + + ) : null} + {item.original_price ? ( + <> + Original Price: {item.original_price} +
    + + ) : null} + {item.price ? ( + <> + Current Price: {item.price} +
    + + ) : null} + +
    + ); diff --git a/lib/routes/arcteryx/templates/regear-product-description.art b/lib/routes/arcteryx/templates/regear-product-description.art deleted file mode 100644 index cdaf7c33e..000000000 --- a/lib/routes/arcteryx/templates/regear-product-description.art +++ /dev/null @@ -1,16 +0,0 @@ -
    - Available Sizes:  - {{each data.availableSizes}} - {{$value}}  - {{/each}} -
    - Color: {{data.color}} -
    - Original Price: {{data.originalPrice}} -
    - Regear Price: {{data.regearPrice}} -
    - - -

    -
    diff --git a/lib/routes/artstation/templates/description.art b/lib/routes/artstation/templates/description.art deleted file mode 100644 index 4de8001c1..000000000 --- a/lib/routes/artstation/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if description }} - {{@ description }}
    -{{ /if }} - -{{ if image }} - {{ image.title }} -{{ /if }} - -{{ if assets }} - {{ each assets a }} - {{ if (a.asset_type === 'video' || a.asset_type === 'video_clip') && a.player_embedded }} - {{@ a.player_embedded }}
    - {{ else if a.asset_type === 'image' || a.asset_type === 'cover' }} -
    - {{ /if }} - {{ /each }} -{{ /if }} diff --git a/lib/routes/artstation/templates/description.tsx b/lib/routes/artstation/templates/description.tsx new file mode 100644 index 000000000..6153da1df --- /dev/null +++ b/lib/routes/artstation/templates/description.tsx @@ -0,0 +1,50 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + description?: string; + image?: { + src?: string; + title?: string; + }; + assets?: { + asset_type?: string; + player_embedded?: string; + image_url?: string; + }[]; +}; + +const ArtstationDescription = ({ description, image, assets }: DescriptionData) => ( + <> + {description ? ( + <> + {raw(description)} +
    + + ) : null} + {image ? {image.title} : null} + {assets?.map((asset) => { + if ((asset.asset_type === 'video' || asset.asset_type === 'video_clip') && asset.player_embedded) { + return ( + <> + {raw(asset.player_embedded)} +
    + + ); + } + + if (asset.asset_type === 'image' || asset.asset_type === 'cover') { + return ( + <> + +
    + + ); + } + + return null; + })} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/artstation/user.ts b/lib/routes/artstation/user.ts index 420b99aae..49ef9bd27 100644 --- a/lib/routes/artstation/user.ts +++ b/lib/routes/artstation/user.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { config } from '@/config'; 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/:handle', @@ -70,7 +69,7 @@ async function handler(ctx) { const list = projects.data.map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ description: item.description, image: { src: resolveImageUrl(item.cover.small_square_url), @@ -97,7 +96,7 @@ async function handler(ctx) { }, }); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ description: data.description, assets: data.assets, }); diff --git a/lib/routes/asiafruitchina/categories.ts b/lib/routes/asiafruitchina/categories.ts index ec596ac55..d3dcf7440 100644 --- a/lib/routes/asiafruitchina/categories.ts +++ b/lib/routes/asiafruitchina/categories.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'all' } = ctx.req.param(); @@ -33,7 +32,7 @@ export const handler = async (ctx: Context): Promise => { const $aEl: Cheerio = $el.find('div.storyDetails h3 a'); const title: string = $aEl.text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: $el.find('a.image img').length > 0 ? $el @@ -84,8 +83,8 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('div.story_title h1').text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.storytext').html(), + const description: string = renderDescription({ + description: $$('div.storytext').html() ?? undefined, }); const pubDateStr: string | undefined = $$('span.date').first().text().split(/:/).pop(); const categories: string[] = diff --git a/lib/routes/asiafruitchina/news.ts b/lib/routes/asiafruitchina/news.ts index facc192aa..bcd19804d 100644 --- a/lib/routes/asiafruitchina/news.ts +++ b/lib/routes/asiafruitchina/news.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -32,7 +31,7 @@ export const handler = async (ctx: Context): Promise => { const $aEl: Cheerio = $el.find('div.storyDetails h3 a'); const title: string = $aEl.text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: $el.find('a.image img').length > 0 ? $el @@ -83,8 +82,8 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('div.story_title h1').text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.storytext').html(), + const description: string = renderDescription({ + description: $$('div.storytext').html() ?? undefined, }); const pubDateStr: string | undefined = $$('span.date').first().text().split(/:/).pop(); const categories: string[] = diff --git a/lib/routes/asiafruitchina/templates/description.art b/lib/routes/asiafruitchina/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/asiafruitchina/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/asiafruitchina/templates/description.tsx b/lib/routes/asiafruitchina/templates/description.tsx new file mode 100644 index 000000000..0dbe1008b --- /dev/null +++ b/lib/routes/asiafruitchina/templates/description.tsx @@ -0,0 +1,26 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/asiantolick/index.ts b/lib/routes/asiantolick/index.ts index 6000d8151..8034e8d95 100644 --- a/lib/routes/asiantolick/index.ts +++ b/lib/routes/asiantolick/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/:category{.+}?', @@ -61,7 +60,7 @@ async function handler(ctx) { return { title: item.find('div.base_tt').text(), link: item.prop('href'), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: image ? [ { @@ -88,7 +87,7 @@ async function handler(ctx) { const content = load(detailResponse); item.title = content('h1').first().text(); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ description: content('#metadata_qrcode').html(), images: content('div.miniatura') .toArray() diff --git a/lib/routes/asiantolick/templates/description.art b/lib/routes/asiantolick/templates/description.art deleted file mode 100644 index 92d6edaea..000000000 --- a/lib/routes/asiantolick/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{@ description }} - -{{ if images }} - {{ each images image }} -
    - {{ image.alt }} -
    - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/asiantolick/templates/description.tsx b/lib/routes/asiantolick/templates/description.tsx new file mode 100644 index 000000000..a25bcddfc --- /dev/null +++ b/lib/routes/asiantolick/templates/description.tsx @@ -0,0 +1,25 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src: string; + alt?: string; +}; + +type DescriptionProps = { + description?: string; + images?: DescriptionImage[]; +}; + +const Description = ({ description, images }: DescriptionProps) => ( + <> + {description ? raw(description) : null} + {images?.map((image, index) => ( +
    + {image.alt} +
    + ))} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/asmr-200/index.ts b/lib/routes/asmr-200/index.tsx similarity index 66% rename from lib/routes/asmr-200/index.ts rename to lib/routes/asmr-200/index.tsx index 293f22ffb..ce33df4a6 100644 --- a/lib/routes/asmr-200/index.ts +++ b/lib/routes/asmr-200/index.tsx @@ -1,13 +1,46 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Result, Work } from '@/routes/asmr-200/type'; import type { DataItem, Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; -const render = (work: Work, link: string) => art(path.join(__dirname, 'templates/work.art'), { work, link }); +const render = (work: Work, link: string) => + renderToString( + <> + + {work.title} + +

    + {work.title} {work.source_id} +

    +

    + 发布者: + {work.name} +

    +

    + 评分: + {work.rate_average_2dp} | 评论数: + {work.review_count} | 总时长: + {work.duration} | 音频来源: + {work.source_type} +

    +

    + 价格: + {work.price} JPY | 销量: + {work.dl_count} +

    +

    + 分类: + {work.category} +

    +

    + 声优: + {work.cv} +

    + + ); export const route: Route = { path: '/works/:order?/:subtitle?/:sort?', diff --git a/lib/routes/asmr-200/templates/work.art b/lib/routes/asmr-200/templates/work.art deleted file mode 100644 index 759ad749f..000000000 --- a/lib/routes/asmr-200/templates/work.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ work.title }} -

    {{ work.title }} {{ work.source_id }}

    -

    发布者:{{ work.name }}

    -

    评分:{{ work.rate_average_2dp }} | 评论数:{{ work.review_count }} | 总时长:{{ work.duration }} | 音频来源:{{ work.source_type }}

    -

    价格:{{ work.price }} JPY | 销量:{{ work.dl_count }}

    -

    分类:{{ work.category }}

    -

    声优:{{ work.cv }}

    \ No newline at end of file diff --git a/lib/routes/asus/bios.ts b/lib/routes/asus/bios.tsx similarity index 77% rename from lib/routes/asus/bios.ts rename to lib/routes/asus/bios.tsx index ad34d46a8..f719b0871 100644 --- a/lib/routes/asus/bios.ts +++ b/lib/routes/asus/bios.tsx @@ -1,10 +1,10 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const endPoints = { zh: { @@ -111,10 +111,35 @@ async function handler(ctx) { const items = biosList.map((item) => ({ title: item.Title, - description: art(path.join(__dirname, 'templates/bios.art'), { - item, - language, - }), + description: renderToString( + language === 'zh' ? ( + <> +

    更新信息:

    + {raw(item.Description)} +

    版本: {item.Version}

    +

    大小: {item.FileSize}

    +

    + 下载链接: 中国下载 | 全球下载 +

    + + ) : ( + <> +

    + Changes: +

    + {raw(item.Description)} +

    + Version: {item.Version} +

    +

    + Size: {item.FileSize} +

    +

    + Download: {item.DownloadUrl.Global.split('/').pop().split('?')[0]} +

    + + ) + ), guid: productInfo.url + item.Version, pubDate: parseDate(item.ReleaseDate, 'YYYY/MM/DD'), link: productInfo.url, diff --git a/lib/routes/asus/templates/bios.art b/lib/routes/asus/templates/bios.art deleted file mode 100644 index 559dcc757..000000000 --- a/lib/routes/asus/templates/bios.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if language !== 'zh' }} -

    Changes:

    - {{@ item.Description}} -

    Version: {{item.Version}}

    -

    Size: {{item.FileSize}}

    -

    Download: {{ item.DownloadUrl.Global.split('/').pop().split('?')[0] }}

    -{{ else }} -

    更新信息:

    - {{@ item.Description}} -

    版本: {{item.Version}}

    -

    大小: {{item.FileSize}}

    -

    下载链接: 中国下载 | 全球下载

    -{{ /if }} diff --git a/lib/routes/augmentcode/blog.ts b/lib/routes/augmentcode/blog.tsx similarity index 87% rename from lib/routes/augmentcode/blog.ts rename to lib/routes/augmentcode/blog.tsx index 9d93acd56..a730faa54 100644 --- a/lib/routes/augmentcode/blog.ts +++ b/lib/routes/augmentcode/blog.tsx @@ -1,16 +1,34 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +const renderDescription = ({ images, description }: { images?: DescriptionImage[]; description?: string }) => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {description ? <>{raw(description)} : null} + + ); export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10); @@ -72,7 +90,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('article h1').text(); const image: string | undefined = $$('meta[property="og:image"]').attr('content') ?? item.image; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -81,7 +99,7 @@ export const handler = async (ctx: Context): Promise => { }, ] : undefined, - description: $$('div.prose').html(), + description: $$('div.prose').html() ?? undefined, }); const pubDateStr: string | undefined = $$('meta[property="article:published_time"]').attr('content'); const authorEls: Element[] = $$('meta[property="article:author"]').toArray(); diff --git a/lib/routes/augmentcode/templates/description.art b/lib/routes/augmentcode/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/augmentcode/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/baidu/gushitong/index.ts b/lib/routes/baidu/gushitong/index.tsx similarity index 72% rename from lib/routes/baidu/gushitong/index.ts rename to lib/routes/baidu/gushitong/index.tsx index 15e0d3174..1b9524ed2 100644 --- a/lib/routes/baidu/gushitong/index.ts +++ b/lib/routes/baidu/gushitong/index.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const STATUS_MAP = { up: '上涨', @@ -39,11 +38,24 @@ async function handler() { const response = await got('https://finance.pae.baidu.com/api/indexbanner?market=ab&finClientType=pc'); const item = response.data.Result.map((e) => ({ title: e.name, - description: art(path.join(__dirname, '../templates/gushitong.art'), { - ...e, - status: STATUS_MAP[e.status], - market: e.market.toUpperCase(), - }), + description: renderToString( +

    + 市场:{e.market.toUpperCase()} +
    + 代码:{e.code} +
    + 名称:{e.name} +
    + 收盘价:{e.price} +
    + 涨跌幅:{e.ratio} +
    + 涨跌额:{e.increase} +
    + 走势:{STATUS_MAP[e.status]} +
    +

    + ), link: `https://gushitong.baidu.com/index/${e.market}-${e.code}`, })); return { diff --git a/lib/routes/baidu/search.ts b/lib/routes/baidu/search.tsx similarity index 88% rename from lib/routes/baidu/search.ts rename to lib/routes/baidu/search.tsx index 0ea1539cd..72beec7aa 100644 --- a/lib/routes/baidu/search.ts +++ b/lib/routes/baidu/search.tsx @@ -1,14 +1,21 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; -const renderDescription = (description, images) => art(path.join(__dirname, './templates/description.art'), { description, images }); +const renderDescription = (description, images) => + renderToString( + <> + {description ? raw(description) : null} + {images?.map((image) => ( + + ))} + + ); export const route: Route = { path: '/search/:keyword', diff --git a/lib/routes/baidu/templates/description.art b/lib/routes/baidu/templates/description.art deleted file mode 100644 index 5f98f4ca3..000000000 --- a/lib/routes/baidu/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{@ description }} -{{if images}} - {{each images}} - - {{/each}} -{{/if}} diff --git a/lib/routes/baidu/templates/forum.art b/lib/routes/baidu/templates/forum.art deleted file mode 100644 index 63df37db5..000000000 --- a/lib/routes/baidu/templates/forum.art +++ /dev/null @@ -1 +0,0 @@ -

    {{ details }}

    {{@ medias }}

    作者:{{ author_name }}

    diff --git a/lib/routes/baidu/templates/gushitong.art b/lib/routes/baidu/templates/gushitong.art deleted file mode 100644 index 5b4e1d101..000000000 --- a/lib/routes/baidu/templates/gushitong.art +++ /dev/null @@ -1,9 +0,0 @@ -

    -市场:{{ market }}
    -代码:{{ code }}
    -名称:{{ name }}
    -收盘价:{{ price }}
    -涨跌幅:{{ ratio }}
    -涨跌额:{{ increase }}
    -走势:{{ status }}
    -

    diff --git a/lib/routes/baidu/templates/post.art b/lib/routes/baidu/templates/post.art deleted file mode 100644 index ae4992075..000000000 --- a/lib/routes/baidu/templates/post.art +++ /dev/null @@ -1,4 +0,0 @@ -

    {{@ pubContent }}


    -作者:{{ author }}
    -楼层:{{ num }}
    -{{ from }} diff --git a/lib/routes/baidu/templates/tieba_search.art b/lib/routes/baidu/templates/tieba_search.art deleted file mode 100644 index 5628bcb0e..000000000 --- a/lib/routes/baidu/templates/tieba_search.art +++ /dev/null @@ -1 +0,0 @@ -

    {{ details }}

    {{@ medias }}

    贴吧:{{ tieba }}
    作者:{{ author }}

    diff --git a/lib/routes/baidu/templates/top.art b/lib/routes/baidu/templates/top.art deleted file mode 100644 index c7f4b680b..000000000 --- a/lib/routes/baidu/templates/top.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if item.img }} -
    -{{ /if }} -{{ if item.show }} - {{ each item.show s }} - {{ s }}
    - {{ /each }} -{{ /if }} -{{ item.desc }} diff --git a/lib/routes/baidu/tieba/forum.ts b/lib/routes/baidu/tieba/forum.tsx similarity index 89% rename from lib/routes/baidu/tieba/forum.ts rename to lib/routes/baidu/tieba/forum.tsx index 04d912a24..8ba4ab809 100644 --- a/lib/routes/baidu/tieba/forum.ts +++ b/lib/routes/baidu/tieba/forum.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -66,11 +65,13 @@ async function handler(ctx) { return { title, - description: art(path.join(__dirname, '../templates/forum.art'), { - details, - medias, - author_name, - }), + description: renderToString( + <> +

    {details}

    +

    {raw(medias)}

    +

    作者:{author_name}

    + + ), pubDate: timezone(parseDate(time, ['HH:mm', 'M-D', 'YYYY-MM'], true), +8), link: `https://tieba.baidu.com/p/${id}`, }; diff --git a/lib/routes/baidu/tieba/post.ts b/lib/routes/baidu/tieba/post.tsx similarity index 87% rename from lib/routes/baidu/tieba/post.ts rename to lib/routes/baidu/tieba/post.tsx index 9ce26f3fc..cfc0a02dd 100644 --- a/lib/routes/baidu/tieba/post.ts +++ b/lib/routes/baidu/tieba/post.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; /** @@ -88,12 +87,17 @@ async function handler(ctx) { } return { title: `${author.user_name}回复了帖子《${title}》`, - description: art(path.join(__dirname, '../templates/post.art'), { - pubContent, - author: author.user_name, - num, - from, - }), + description: renderToString( + <> +

    {raw(pubContent)}

    +
    + 作者:{author.user_name} +
    + 楼层:{num} +
    + {from} + + ), pubDate: timezone(parseDate(time, 'YYYY-MM-DD hh:mm'), +8), link: `https://tieba.baidu.com/p/${id}?pid=${content.post_id}#${content.post_id}`, }; diff --git a/lib/routes/baidu/tieba/search.ts b/lib/routes/baidu/tieba/search.tsx similarity index 87% rename from lib/routes/baidu/tieba/search.ts rename to lib/routes/baidu/tieba/search.tsx index e2240fa3e..3af8d0328 100644 --- a/lib/routes/baidu/tieba/search.ts +++ b/lib/routes/baidu/tieba/search.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -77,12 +76,17 @@ async function handler(ctx) { return { title, - description: art(path.join(__dirname, '../templates/tieba_search.art'), { - details, - medias, - tieba, - author, - }), + description: renderToString( + <> +

    {details}

    +

    {raw(medias)}

    +

    + 贴吧:{tieba} +
    + 作者:{author} +

    + + ), author, pubDate: timezone(parseDate(time, 'YYYY-MM-DD HH:mm'), +8), link, diff --git a/lib/routes/baidu/top.ts b/lib/routes/baidu/top.tsx similarity index 71% rename from lib/routes/baidu/top.ts rename to lib/routes/baidu/top.tsx index fedce7cde..f62471298 100644 --- a/lib/routes/baidu/top.ts +++ b/lib/routes/baidu/top.tsx @@ -1,10 +1,29 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +const renderDescription = (item) => + renderToString( + <> + {item.img ? ( + <> + +
    + + ) : null} + {item.show + ? item.show.map((text) => ( + <> + {text} +
    + + )) + : null} + {item.desc} + + ); export const route: Route = { path: '/top/:board?', @@ -43,9 +62,7 @@ async function handler(ctx) { const items = data.cards[0].content.map((item) => ({ title: item.word, - description: art(path.join(__dirname, 'templates/top.art'), { - item, - }), + description: renderDescription(item), link: item.rawUrl, })); diff --git a/lib/routes/bandcamp/templates/weekly.art b/lib/routes/bandcamp/templates/weekly.art deleted file mode 100644 index 4e656e620..000000000 --- a/lib/routes/bandcamp/templates/weekly.art +++ /dev/null @@ -1 +0,0 @@ -

    {{ desc }}

    \ No newline at end of file diff --git a/lib/routes/bandcamp/weekly.ts b/lib/routes/bandcamp/weekly.tsx similarity index 76% rename from lib/routes/bandcamp/weekly.ts rename to lib/routes/bandcamp/weekly.tsx index 5e8d49d23..71c44b64e 100644 --- a/lib/routes/bandcamp/weekly.ts +++ b/lib/routes/bandcamp/weekly.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/weekly', @@ -41,10 +40,7 @@ async function handler() { title: item.title, link: `${rootUrl}/?show=${item.id}`, pubDate: parseDate(item.published_date), - description: art(path.join(__dirname, 'templates/weekly.art'), { - v2_image_id: item.v2_image_id, - desc: item.desc, - }), + description: renderToString(), })); return { @@ -53,3 +49,10 @@ async function handler() { item: items, }; } + +const BandcampWeekly = ({ v2ImageId, desc }: { v2ImageId: string; desc: string }) => ( + <> + +

    {desc}

    + +); diff --git a/lib/routes/bangumi.online/online.ts b/lib/routes/bangumi.online/online.tsx similarity index 81% rename from lib/routes/bangumi.online/online.ts rename to lib/routes/bangumi.online/online.tsx index 916614248..130925993 100644 --- a/lib/routes/bangumi.online/online.ts +++ b/lib/routes/bangumi.online/online.tsx @@ -1,9 +1,10 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const renderImage = (src, alt) => renderToString({alt}); export const route: Route = { path: '/', @@ -38,10 +39,7 @@ async function handler() { const items = list.map((item) => ({ title: `${item.title.zh ?? item.title.ja} - 第 ${item.volume} 集`, - description: art(path.join(__dirname, 'templates/image.art'), { - src: `https:${item.cover}`, - alt: `${item.title_zh} - 第 ${item.volume} 集`, - }), + description: renderImage(`https:${item.cover}`, `${item.title_zh} - 第 ${item.volume} 集`), link: `https://bangumi.online/watch/${item.vid}`, pubDate: parseDate(item.create_time), })); diff --git a/lib/routes/bangumi.online/templates/image.art b/lib/routes/bangumi.online/templates/image.art deleted file mode 100644 index 40140a947..000000000 --- a/lib/routes/bangumi.online/templates/image.art +++ /dev/null @@ -1 +0,0 @@ -{{ alt }} diff --git a/lib/routes/bangumi.tv/calendar/today.ts b/lib/routes/bangumi.tv/calendar/today.tsx similarity index 79% rename from lib/routes/bangumi.tv/calendar/today.ts rename to lib/routes/bangumi.tv/calendar/today.tsx index 0ff553425..a83fc1a51 100644 --- a/lib/routes/bangumi.tv/calendar/today.ts +++ b/lib/routes/bangumi.tv/calendar/today.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; -import { art } from '@/utils/render'; import getData from './_base'; @@ -30,6 +29,25 @@ export const route: Route = { url: 'bgm.tv/calendar', }; +const renderTodayDescription = (bgm, siteMeta) => + renderToString( + <> + +
      + {bgm.sites.map((site) => { + const url = site.url ?? siteMeta[site.site].urlTemplate.replace('{{id}}', site.id); + const title = siteMeta[site.site].title; + + return ( +
    • + {title} +
    • + ); + })} +
    + + ); + async function handler() { const [list, data] = await getData(cache.tryGet); const siteMeta = data.siteMeta; @@ -64,10 +82,7 @@ async function handler() { const link = `https://bangumi.tv/subject/${bgm.bgmId}`; const id = `${link}#${new Intl.DateTimeFormat('zh-CN').format(updated)}`; - const html = art(path.join(__dirname, '../templates/today.art'), { - bgm, - siteMeta, - }); + const html = renderTodayDescription(bgm, siteMeta); return { id, diff --git a/lib/routes/bangumi.tv/subject/ep.ts b/lib/routes/bangumi.tv/subject/ep.tsx similarity index 67% rename from lib/routes/bangumi.tv/subject/ep.ts rename to lib/routes/bangumi.tv/subject/ep.tsx index 60f33d0d6..fe54c6f94 100644 --- a/lib/routes/bangumi.tv/subject/ep.ts +++ b/lib/routes/bangumi.tv/subject/ep.tsx @@ -1,8 +1,8 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { getLocalName } from './utils'; @@ -17,10 +17,12 @@ const getEps = async (subjectID, showOriginalName) => { description: epsInfo.summary, item: activeEps.map((e) => ({ title: `ep.${e.sort} ${getLocalName(e, showOriginalName)}`, - description: art(path.join(__dirname, '../templates/ep.art'), { - e, - epsInfo, - }), + description: renderToString( + <> + {`ep.${e.sort} +

    {raw(e.desc.replaceAll('\r\n', '
    '))}

    + + ), pubDate: parseDate(e.airdate), link: e.url.replace('http:', 'https:'), })), diff --git a/lib/routes/bangumi.tv/templates/ep.art b/lib/routes/bangumi.tv/templates/ep.art deleted file mode 100644 index dd383d563..000000000 --- a/lib/routes/bangumi.tv/templates/ep.art +++ /dev/null @@ -1,2 +0,0 @@ -ep.{{ e.sort }} {{ e.name_cn || e.name }} -

    {{@ e.desc.replace(/\r\n/g, '
    ') }}

    diff --git a/lib/routes/bangumi.tv/templates/subject.art b/lib/routes/bangumi.tv/templates/subject.art deleted file mode 100644 index 089bf11f1..000000000 --- a/lib/routes/bangumi.tv/templates/subject.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if routeSubjectType === 'all' }}类型:{{ subjectTypeName }}
    {{ /if }} -{{ if subjectType === 2 }}看到:{{ epStatus }} / {{ subjectEps ? subjectEps: '???' }}
    {{ /if }} -{{ if subjectType === 1 }}读到:{{ epStatus }} / {{ subjectEps ? subjectEps: '???' }}
    {{ /if }} -评分:{{ score }}
    -放送时间:{{ date ? date : '未知' }}
    - diff --git a/lib/routes/bangumi.tv/templates/today.art b/lib/routes/bangumi.tv/templates/today.art deleted file mode 100644 index a0c4e0258..000000000 --- a/lib/routes/bangumi.tv/templates/today.art +++ /dev/null @@ -1,12 +0,0 @@ - -
      -{{ each bgm.sites site }} -{{ set url }} -{{ if site.url }} - {{ url = site.url }} -{{ else }} - <% url = siteMeta[site.site].urlTemplate.replace('{{id}}', site.id) %> -{{ /if }} -
    • {{ siteMeta[site.site].title }}
    • -{{ /each }} -
    diff --git a/lib/routes/bangumi.tv/user/collections.ts b/lib/routes/bangumi.tv/user/collections.tsx similarity index 86% rename from lib/routes/bangumi.tv/user/collections.ts rename to lib/routes/bangumi.tv/user/collections.tsx index 6514406bb..e285d576a 100644 --- a/lib/routes/bangumi.tv/user/collections.ts +++ b/lib/routes/bangumi.tv/user/collections.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; // 合并不同 subjectType 的 type 映射 @@ -50,6 +49,34 @@ const getTypeNames = (subjectType) => { } }; +const renderSubjectDescription = (data) => + renderToString( + <> + {data.routeSubjectType === 'all' && ( + <> + 类型:{data.subjectTypeName}
    + + )} + {data.subjectType === 2 && ( + <> + 看到:{data.epStatus} / {data.subjectEps || '???'} +
    + + )} + {data.subjectType === 1 && ( + <> + 读到:{data.epStatus} / {data.subjectEps || '???'} +
    + + )} + 评分:{data.score} +
    + 放送时间:{data.date || '未知'} +
    + + + ); + export const route: Route = { path: '/user/collections/:id/:subjectType/:type', categories: ['anime'], @@ -161,7 +188,7 @@ async function handler(ctx) { return { title: `${type === 'all' ? `${getTypeNames(item.subject_type)[item.type]}:` : ''}${titles}`, - description: art(path.join(__dirname, '../templates/subject.art'), { + description: renderSubjectDescription({ routeSubjectType: subjectType, subjectTypeName: subjectTypeNames[item.subject_type], subjectType: item.subject_type, diff --git a/lib/routes/banshujiang/index.ts b/lib/routes/banshujiang/index.ts index 9ade65daf..acf26a51d 100644 --- a/lib/routes/banshujiang/index.ts +++ b/lib/routes/banshujiang/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category } = ctx.req.param(); @@ -34,7 +33,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.text().trim(); const image: string | undefined = $el.find('meta[property="og:image"]').attr('content') ?? $el.find('img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -84,7 +83,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('div.ebook-title').text().trim(); const image: string | undefined = $$('div.span6 img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { diff --git a/lib/routes/banshujiang/templates/description.art b/lib/routes/banshujiang/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/banshujiang/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/banshujiang/templates/description.tsx b/lib/routes/banshujiang/templates/description.tsx new file mode 100644 index 000000000..6caa21852 --- /dev/null +++ b/lib/routes/banshujiang/templates/description.tsx @@ -0,0 +1,20 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionData) => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/banyuetan/index.ts b/lib/routes/banyuetan/index.ts index c953deeb0..829bfe24c 100644 --- a/lib/routes/banyuetan/index.ts +++ b/lib/routes/banyuetan/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { id = 'jinritan' } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -35,7 +34,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.text(); const image: string | undefined = $el.find('img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -81,8 +80,8 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('div.detail_tit h1').text(); const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div#detail_content').html(), + renderDescription({ + description: $$('div#detail_content').html() || undefined, }); const pubDateStr: string | undefined = $$('meta[property="og:release_date"]').attr('content'); const categories: string[] = $$('META[name="keywords"]').attr('content')?.split(/,/) ?? []; diff --git a/lib/routes/banyuetan/templates/description.art b/lib/routes/banyuetan/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/banyuetan/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/banyuetan/templates/description.tsx b/lib/routes/banyuetan/templates/description.tsx new file mode 100644 index 000000000..81ceaef91 --- /dev/null +++ b/lib/routes/banyuetan/templates/description.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src: string; + alt?: string; +}; + +type DescriptionProps = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionProps): string => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/baozimh/index.ts b/lib/routes/baozimh/index.tsx similarity index 84% rename from lib/routes/baozimh/index.ts rename to lib/routes/baozimh/index.tsx index 3cbf55fb8..7e1b2aa28 100644 --- a/lib/routes/baozimh/index.ts +++ b/lib/routes/baozimh/index.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const rootUrl = 'https://www.baozimh.com'; @@ -76,12 +74,16 @@ async function handler(ctx) { cache.tryGet(item.link, async () => { const detailResponse = await got(item.link); const $ = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/desc.art'), { - imgUrlList: $('.comic-contain') - .find('amp-img') - .toArray() - .map((item) => $(item).attr('src')), - }); + item.description = renderToString( + <> + {$('.comic-contain') + .find('amp-img') + .toArray() + .map((img) => ( + + ))} + + ); return item; }) diff --git a/lib/routes/baozimh/templates/desc.art b/lib/routes/baozimh/templates/desc.art deleted file mode 100644 index 69e88ee6f..000000000 --- a/lib/routes/baozimh/templates/desc.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each imgUrlList }} - -{{ /each }} diff --git a/lib/routes/bc3ts/list.ts b/lib/routes/bc3ts/list.tsx similarity index 76% rename from lib/routes/bc3ts/list.ts rename to lib/routes/bc3ts/list.tsx index 2cbdee43a..cb365ff06 100644 --- a/lib/routes/bc3ts/list.ts +++ b/lib/routes/bc3ts/list.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { Media, PostResponse } from './types'; @@ -29,7 +28,22 @@ export const route: Route = { const baseUrl = 'https://web.bc3ts.net'; -const renderMedia = (media: Media[]) => art(path.join(__dirname, 'templates/media.art'), { media }); +const renderMedia = (media: Media[]) => renderToString(); + +const MediaList = ({ media }: { media: Media[] }) => ( + <> +
    + {media.map((m) => + m.type === 0 ? ( + {m.name} + ) : m.type === 3 ? ( + + ) : null + )} + +); async function handler(ctx) { const { sort = '1' } = ctx.req.param(); diff --git a/lib/routes/bc3ts/templates/media.art b/lib/routes/bc3ts/templates/media.art deleted file mode 100644 index a0e2992fd..000000000 --- a/lib/routes/bc3ts/templates/media.art +++ /dev/null @@ -1,10 +0,0 @@ -
    -{{ each media m }} - {{ if m.type === 0 }} - {{ m.name }} - {{ else if m.type === 3 }} - - {{ /if }} -{{ /each }} diff --git a/lib/routes/bdys/index.ts b/lib/routes/bdys/index.tsx similarity index 77% rename from lib/routes/bdys/index.ts rename to lib/routes/bdys/index.tsx index 5a112d699..45c164ea9 100644 --- a/lib/routes/bdys/index.ts +++ b/lib/routes/bdys/index.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import pMap from 'p-map'; import { config } from '@/config'; @@ -9,7 +9,6 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; // Visit https://www.bdys.me for the list of domains @@ -159,12 +158,44 @@ async function handler(ctx) { content('svg').remove(); const torrents = content('.download-list .list-group'); - item.description = art(path.join(__dirname, 'templates/desc.art'), { - info: content('.row.mt-3').html(), - synopsis: content('#synopsis').html(), - links: downloadResponse.data, - torrents: torrents.html(), - }); + const info = content('.row.mt-3').html(); + const synopsis = content('#synopsis').html(); + const torrentsHtml = torrents.html(); + const links = downloadResponse.data; + item.description = renderToString( + <> + {info ? ( + <> + {raw(info)} +
    + + ) : null} + {synopsis ? ( + <> + {raw(synopsis)} +
    + + ) : null} + {links?.length ? ( +
    + 下载地址: + {links.map((link) => ( +
    +
    + {link.downloadCategory.name}: {link.url} +
    +
    + ))} +
    + ) : null} + {torrentsHtml ? ( +
    + 种子列表: + {raw(torrentsHtml)} +
    + ) : null} + + ); item.pubDate = timezone(parseDate(content('.bg-purple-lt').text().replace('更新时间:', '')), +8); item.guid = `${item.link}#${content('.card h1').text()}`; diff --git a/lib/routes/bdys/templates/desc.art b/lib/routes/bdys/templates/desc.art deleted file mode 100644 index ff49d93cf..000000000 --- a/lib/routes/bdys/templates/desc.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if info }} -{{@ info }}
    -{{ /if }} - -{{ if synopsis }} -{{@ synopsis }}
    -{{ /if}} - -{{ if links }} -
    下载地址: - {{ each links link }} -
    {{ link.downloadCategory.name }}: {{ link.url }}
    - {{ /each }} -
    -{{ /if }} - -{{ if torrents }} -
    种子列表: -{{@ torrents }} -
    -{{ /if }} diff --git a/lib/routes/behance/templates/description.art b/lib/routes/behance/templates/description.art deleted file mode 100644 index 698a6ff6b..000000000 --- a/lib/routes/behance/templates/description.art +++ /dev/null @@ -1,23 +0,0 @@ -{{ if description.length }} - {{ description }}
    -{{ /if }} - -{{ each modules module }} - {{ if module.__typename === 'ImageModule' }} -
    - {{ module.altText }} - {{ if module.caption.length }}
    {{ module.caption }}
    {{ /if }} -
    - {{ else if module.__typename === 'TextModule' }} - {{@ module.text }} - {{ else if module.__typename === 'MediaCollectionModule' }} - {{ each module.components comp }} - - {{ /each }} - {{ else if module.__typename === 'EmbedModule' }} - {{@ module.fluidEmbed || module.originalEmbed }} - {{ else }} - UNHANDLED MODULE: {{ module.__typename }} - {{ /if }} -
    -{{ /each }} diff --git a/lib/routes/behance/user.ts b/lib/routes/behance/user.tsx similarity index 64% rename from lib/routes/behance/user.ts rename to lib/routes/behance/user.tsx index 1d76c2b92..7cc26d5c7 100644 --- a/lib/routes/behance/user.ts +++ b/lib/routes/behance/user.tsx @@ -1,12 +1,13 @@ import crypto from 'node:crypto'; -import path from 'node:path'; + +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { getAppreciatedQuery, getProfileProjectsAndSelectionsQuery, getProjectPageQuery } from './queries'; @@ -52,6 +53,65 @@ const getUserProfile = async (nodes, user) => }); })) as { displayName: string; id: string; link: string; image: string }; +const renderDescription = (description, modules) => + renderToString( + <> + {description?.length ? ( + <> + {description} +
    + + ) : null} + {modules?.map((module) => { + if (module.__typename === 'ImageModule') { + return ( + <> +
    + {module.altText + {module.caption?.length ?
    {module.caption}
    : null} +
    +
    + + ); + } + if (module.__typename === 'TextModule') { + return ( + <> + {module.text ? raw(module.text) : null} +
    + + ); + } + if (module.__typename === 'MediaCollectionModule') { + return ( + <> + {module.components?.map((comp) => ( + + ))} +
    + + ); + } + if (module.__typename === 'EmbedModule') { + const embed = module.fluidEmbed || module.originalEmbed; + return ( + <> + {embed ? raw(embed) : null} +
    + + ); + } + + return ( + <> + UNHANDLED MODULE: {module.__typename} +
    + + ); + })} + + ); + async function handler(ctx) { const { user, type = 'projects' } = ctx.req.param(); @@ -102,10 +162,7 @@ async function handler(ctx) { }); const project = response.data.project; - item.description = art(path.join(__dirname, 'templates/description.art'), { - description: project.description, - modules: project.allModules, - }); + item.description = renderDescription(project.description, project.allModules); item.category = [...new Set([...(item.category || []), ...(project.tags?.map((tag) => tag.title.toLowerCase()) || [])])]; item.pubDate = item.pubDate || (project.publishedOn ? parseDate(project.publishedOn, 'X') : undefined); diff --git a/lib/routes/bestofjs/monthly.ts b/lib/routes/bestofjs/monthly.tsx similarity index 63% rename from lib/routes/bestofjs/monthly.ts rename to lib/routes/bestofjs/monthly.tsx index 8616904f1..6c9d063f7 100644 --- a/lib/routes/bestofjs/monthly.ts +++ b/lib/routes/bestofjs/monthly.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; const BASEURL = 'https://bestofjs.org/rankings/monthly'; @@ -42,7 +40,58 @@ export const route: Route = { ); const items = allNeededMonthlyRankings.flatMap((oneMonthlyRankings, i) => { const [year, month] = targetMonths[i].split('-'); - const description = art(path.join(__dirname, 'templates/description.art'), { items: oneMonthlyRankings }); + const description = renderToString( +
      + {oneMonthlyRankings.map((item, index) => ( + <> +
    • +

      + {`Rank ${index + 1}`} +

      + {item.logo ? {item.projectName} : null} + {item.projectName ? ( +

      + Project: {item.projectName} +

      + ) : null} + {item.description ?

      {item.description}

      : null} + {item.starCount ? ( +

      + Stars: {item.starCount} +

      + ) : null} + {item.additionalInfo ? ( +

      + Additional Info: {item.additionalInfo} +

      + ) : null} + {item.githubLink ? ( +

      + GitHub: {item.githubLink} +

      + ) : null} + {item.homepageLink ? ( +

      + Homepage: {item.homepageLink} +

      + ) : null} + {item.tags?.length ? ( +

      + Tags:{' '} + {item.tags.map((tag, tagIndex) => ( + <> + {tag} + {tagIndex < item.tags.length - 1 ? ', ' : ''} + + ))} +

      + ) : null} +
    • +
      + + ))} +
    + ); return { title: `Best of JS Monthly Rankings - ${year}/${month}`, description, diff --git a/lib/routes/bestofjs/templates/description.art b/lib/routes/bestofjs/templates/description.art deleted file mode 100644 index b7a6b51a5..000000000 --- a/lib/routes/bestofjs/templates/description.art +++ /dev/null @@ -1,36 +0,0 @@ -
      - {{each items item index}} -
    • -

      Rank {{index + 1}}

      - {{if item.logo}} - {{item.projectName}} - {{/if}} - {{if item.projectName}} -

      Project: {{item.projectName}}

      - {{/if}} - {{if item.description}} -

      {{item.description}}

      - {{/if}} - {{if item.starCount}} -

      Stars: {{item.starCount}}

      - {{/if}} - {{if item.additionalInfo}} -

      Additional Info: {{item.additionalInfo}}

      - {{/if}} - {{if item.githubLink}} -

      GitHub: {{item.githubLink}}

      - {{/if}} - {{if item.homepageLink}} -

      Homepage: {{item.homepageLink}}

      - {{/if}} - {{if item.tags && item.tags.length}} -

      Tags: - {{each item.tags tag tIndex}} - {{tag}}{{if tIndex < item.tags.length - 1}}, {{/if}} - {{/each}} -

      - {{/if}} -
    • -
      - {{/each}} -
    diff --git a/lib/routes/bgmlist/onair.ts b/lib/routes/bgmlist/onair.tsx similarity index 63% rename from lib/routes/bgmlist/onair.ts rename to lib/routes/bgmlist/onair.tsx index f2aeb21f7..f61d9bcd5 100644 --- a/lib/routes/bgmlist/onair.ts +++ b/lib/routes/bgmlist/onair.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/onair/:lang?', @@ -33,16 +32,24 @@ async function handler(ctx) { link: 'https://bgmlist.com/', item: data.items.map((item) => { item.sites.push({ site: 'dmhy', id: item.titleTranslate['zh-Hans']?.[0] ?? item.title }); + const mappedSites = item.sites.map((site) => ({ + title: sites[site.site].title, + url: sites[site.site].urlTemplate.replaceAll('{{id}}', site.id), + begin: site.begin, + })); return { title: item.titleTranslate[lang]?.[0] ?? item.title, link: item.officialSite, - description: art( - path.join(__dirname, 'templates/description.art'), - item.sites.map((site) => ({ - title: sites[site.site].title, - url: sites[site.site].urlTemplate.replaceAll('{{id}}', site.id), - begin: site.begin, - })) + description: renderToString( + <> + {mappedSites.map((site) => ( + <> + {site.title} + {site.begin ? <>(开播时间:{site.begin}) : null} +
    + + ))} + ), pubDate: parseDate(item.begin), guid: item.id, diff --git a/lib/routes/bgmlist/templates/description.art b/lib/routes/bgmlist/templates/description.art deleted file mode 100644 index 10dbb3d97..000000000 --- a/lib/routes/bgmlist/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{each}} -{{$value.title}}{{if $value.begin}}(开播时间:{{$value.begin}}){{/if}} -
    -{{/each}} diff --git a/lib/routes/bilibili/templates/description.art b/lib/routes/bilibili/templates/description.art deleted file mode 100644 index 5f6e5847d..000000000 --- a/lib/routes/bilibili/templates/description.art +++ /dev/null @@ -1,14 +0,0 @@ -{{ if embed }} -{{ if ugc }} - -{{ /if }} -{{ if ogv }} - -{{ /if }} -
    -{{ /if }} -{{ if img}} - -
    -{{ /if }} -{{@ description }} diff --git a/lib/routes/bilibili/templates/description.tsx b/lib/routes/bilibili/templates/description.tsx new file mode 100644 index 000000000..05c449823 --- /dev/null +++ b/lib/routes/bilibili/templates/description.tsx @@ -0,0 +1,36 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + embed: boolean; + ugc?: boolean; + ogv?: boolean; + aid?: string; + cid?: string; + bvid?: string; + seasonId?: string; + episodeId?: string; + img?: string; + description?: string; +}; + +const Description = ({ embed, ugc, ogv, aid, cid, bvid, seasonId, episodeId, img, description }: DescriptionProps) => ( + <> + {embed ? ( + <> + {ugc ? : null} + {ogv ? : null} +
    + + ) : null} + {img ? ( + <> + +
    + + ) : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/bilibili/utils.ts b/lib/routes/bilibili/utils.ts index bee611e0e..65bf08786 100644 --- a/lib/routes/bilibili/utils.ts +++ b/lib/routes/bilibili/utils.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import CryptoJS from 'crypto-js'; import { config } from '@/config'; import md5 from '@/utils/md5'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; +import { renderDescription } from './templates/description'; import type { MediaResult, ResultResponse, SeasonResult } from './types'; // a @@ -247,21 +245,21 @@ export const getBangumiItems = (id: string, cache): Promise => ) as Promise; /** - * 使用模板渲染 UGC(用户生成内容)描述。 + * Render the UGC (user-generated content) description. * - * @param {boolean} embed - 是否嵌入视频。 - * @param {string} img - 要包含在描述中的图片 URL。 - * @param {string} description - UGC 的文本描述。 - * @param {string} [aid] - 可选。UGC 的 aid。 - * @param {string} [cid] - 可选。UGC 的 cid。 - * @param {string} [bvid] - 可选。UGC 的 bvid。 - * @returns {string} 渲染的 UGC 描述。 + * @param {boolean} embed - Whether to embed the video. + * @param {string} img - Image URL to include in the description. + * @param {string} description - UGC text description. + * @param {string} [aid] - Optional UGC aid. + * @param {string} [cid] - Optional UGC cid. + * @param {string} [bvid] - Optional UGC bvid. + * @returns {string} Rendered UGC description. * - * @see https://player.bilibili.com/ 获取更多信息。 + * @see https://player.bilibili.com/ for details. */ export const renderUGCDescription = (embed: boolean, img: string, description: string, aid?: string, cid?: string, bvid?: string): string => { // docs: https://player.bilibili.com/ - const rendered = art(path.join(__dirname, 'templates/description.art'), { + const rendered = renderDescription({ embed, ugc: true, aid, @@ -274,20 +272,20 @@ export const renderUGCDescription = (embed: boolean, img: string, description: s }; /** - * 使用模板渲染 OGV(原创视频)描述。 + * Render the OGV (original video) description. * - * @param {boolean} embed - 是否嵌入视频。 - * @param {string} img - 要包含在描述中的图片 URL。 - * @param {string} description - OGV 的文本描述。 - * @param {string} [seasonId] - 可选。OGV 的季 ID。 - * @param {string} [episodeId] - 可选。OGV 的集 ID。 - * @returns {string} 渲染的 OGV 描述。 + * @param {boolean} embed - Whether to embed the video. + * @param {string} img - Image URL to include in the description. + * @param {string} description - OGV text description. + * @param {string} [seasonId] - Optional OGV season ID. + * @param {string} [episodeId] - Optional OGV episode ID. + * @returns {string} Rendered OGV description. * - * @see https://player.bilibili.com/ 获取更多信息。 + * @see https://player.bilibili.com/ for details. */ export const renderOGVDescription = (embed: boolean, img: string, description: string, seasonId?: string, episodeId?: string): string => { // docs: https://player.bilibili.com/ - const rendered = art(path.join(__dirname, 'templates/description.art'), { + const rendered = renderDescription({ embed, ogv: true, seasonId, diff --git a/lib/routes/bloomberg/templates/audio-media.tsx b/lib/routes/bloomberg/templates/audio-media.tsx new file mode 100644 index 000000000..8f8baa2be --- /dev/null +++ b/lib/routes/bloomberg/templates/audio-media.tsx @@ -0,0 +1,26 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type AudioMediaData = { + img?: string; + src?: string; + caption?: string; + credit?: string; +}; + +export const renderAudioMedia = ({ img, src, caption, credit }: AudioMediaData) => + renderToString( +
    +
    + + +
    +
    +
    {caption ? raw(caption) : null}
    +
    {credit ? raw(credit) : null}
    +
    +
    + ); diff --git a/lib/routes/bloomberg/templates/audio_media.art b/lib/routes/bloomberg/templates/audio_media.art deleted file mode 100644 index 8aee10132..000000000 --- a/lib/routes/bloomberg/templates/audio_media.art +++ /dev/null @@ -1,13 +0,0 @@ -
    -
    - - -
    -
    -
    {{@ caption }}
    -
    {{@ credit }}
    -
    -
    diff --git a/lib/routes/bloomberg/templates/chart-media.tsx b/lib/routes/bloomberg/templates/chart-media.tsx new file mode 100644 index 000000000..d00fc5313 --- /dev/null +++ b/lib/routes/bloomberg/templates/chart-media.tsx @@ -0,0 +1,44 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ChartData = { + title?: string; + subtitle?: string; + fallback?: string; + chartAlt?: string; + chartId?: string; + url?: string; + source?: string; + footnote?: string; +}; + +export const renderChartMedia = ({ chart }: { chart: ChartData }) => + renderToString( +
    + {chart.title ? <>{chart.title} : null} + {chart.subtitle ?

    {chart.subtitle}

    : null} + + + {chart.source ? ( +
    +
    {raw(chart.source)}
    + {chart.footnote ?

    {chart.footnote}

    : null} +
    + ) : null} +
    + ); diff --git a/lib/routes/bloomberg/templates/chart_media.art b/lib/routes/bloomberg/templates/chart_media.art deleted file mode 100644 index 255fbb7d6..000000000 --- a/lib/routes/bloomberg/templates/chart_media.art +++ /dev/null @@ -1,21 +0,0 @@ -
    - {{if chart.title}} - {{chart.title}} - {{/if}} - {{if chart.subtitle}} -

    {{chart.subtitle}}

    - {{/if}} - - - {{if chart.source}} -
    -
    {{@ chart.source }}
    - {{if chart.footnote}} -

    {{chart.footnote}}

    - {{/if}} -
    - {{/if}} -
    diff --git a/lib/routes/bloomberg/templates/image-figure.tsx b/lib/routes/bloomberg/templates/image-figure.tsx new file mode 100644 index 000000000..db94441ea --- /dev/null +++ b/lib/routes/bloomberg/templates/image-figure.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageFigureData = { + src?: string; + alt?: string; + caption?: string; + credit?: string; +}; + +export const renderImageFigure = ({ src, alt, caption, credit }: ImageFigureData) => + renderToString( +
    + {alt} + {caption || credit ? ( +
    +
    {caption ? raw(caption) : null}
    +
    {credit ? raw(credit) : null}
    +
    + ) : null} +
    + ); diff --git a/lib/routes/bloomberg/templates/image_figure.art b/lib/routes/bloomberg/templates/image_figure.art deleted file mode 100644 index 692533751..000000000 --- a/lib/routes/bloomberg/templates/image_figure.art +++ /dev/null @@ -1,9 +0,0 @@ -
    - {{ alt }} - {{if caption || credit}} -
    -
    {{@ caption }}
    -
    {{@ credit }}
    -
    - {{/if}} -
    diff --git a/lib/routes/bloomberg/templates/lede-media.tsx b/lib/routes/bloomberg/templates/lede-media.tsx new file mode 100644 index 000000000..fe8238bc2 --- /dev/null +++ b/lib/routes/bloomberg/templates/lede-media.tsx @@ -0,0 +1,40 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +import { renderVideoMedia } from './video-media'; + +type LedeMedia = { + kind?: string; + src?: string; + description?: string; + caption?: string; + credit?: string; + video?: { + stream?: string; + mp4?: string; + coverUrl?: string; + caption?: string; + }; +}; + +export const renderLedeMedia = (media: LedeMedia) => { + if (media?.kind === 'video') { + return renderVideoMedia(media.video ?? {}); + } + + if (media?.kind === 'image') { + return renderToString( +
    + {media.description} + {media.caption ? ( +
    +
    {raw(media.caption)}
    +
    {media.credit ? raw(media.credit) : null}
    +
    + ) : null} +
    + ); + } + + return ''; +}; diff --git a/lib/routes/bloomberg/templates/lede_media.art b/lib/routes/bloomberg/templates/lede_media.art deleted file mode 100644 index 4acd0cd7a..000000000 --- a/lib/routes/bloomberg/templates/lede_media.art +++ /dev/null @@ -1,14 +0,0 @@ -{{if (media.kind =='image') }} -
    - {{ media.description }} - {{if media.caption}} -
    -
    {{@ media.caption }}
    -
    {{@ media.credit }}
    -
    - {{/if}} -
    -{{/if}} -{{if (media.kind =='video') }} -{{include './video_media.art' media.video}} -{{/if}} diff --git a/lib/routes/bloomberg/templates/video-media.tsx b/lib/routes/bloomberg/templates/video-media.tsx new file mode 100644 index 000000000..8b8b7a147 --- /dev/null +++ b/lib/routes/bloomberg/templates/video-media.tsx @@ -0,0 +1,35 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type VideoMediaData = { + stream?: string; + mp4?: string; + coverUrl?: string; + caption?: string; +}; + +export const renderVideoMedia = ({ stream, mp4, coverUrl, caption }: VideoMediaData) => + renderToString( +
    + + {caption ? ( +
    +
    {raw(caption)}
    +
    + ) : null} +
    + ); diff --git a/lib/routes/bloomberg/templates/video_media.art b/lib/routes/bloomberg/templates/video_media.art deleted file mode 100644 index 355048bb8..000000000 --- a/lib/routes/bloomberg/templates/video_media.art +++ /dev/null @@ -1,25 +0,0 @@ -
    - - {{if caption}} -
    -
    {{@ caption }}
    -
    - {{/if}} -
    diff --git a/lib/routes/bloomberg/utils.ts b/lib/routes/bloomberg/utils.ts index ae2a85c8f..b1ed65570 100644 --- a/lib/routes/bloomberg/utils.ts +++ b/lib/routes/bloomberg/utils.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import { destr } from 'destr'; @@ -7,7 +5,12 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderAudioMedia } from './templates/audio-media'; +import { renderChartMedia } from './templates/chart-media'; +import { renderImageFigure } from './templates/image-figure'; +import { renderLedeMedia } from './templates/lede-media'; +import { renderVideoMedia } from './templates/video-media'; const rootUrl = 'https://www.bloomberg.com/feeds'; const idSel = 'script[id^="article-info"][type="application/json"], script[class^="article-info"][type="application/json"], script#dvz-config'; @@ -183,7 +186,7 @@ const parseVideoPage = async (res, api, item) => { title: video_story.headline.text || item.title, link: video_story.url || item.link, guid: `bloomberg:${video_story.id}`, - description: art(path.join(__dirname, 'templates/video_media.art'), desc), + description: renderVideoMedia(desc), pubDate: parseDate(video_story.publishedAt) || item.pubDate, media: { content: { url: video_story.video?.thumbnail.url || '' }, @@ -267,7 +270,7 @@ const processLedeMedia = async (story_json) => { src: story_json.ledeImageUrl, video: kind === 'video' && (await processVideo(story_json.ledeAttachment.bmmrId)), }; - return art(path.join(__dirname, 'templates/lede_media.art'), { media }); + return renderLedeMedia(media); } else if (story_json.lede) { const lede = story_json.lede; const image = { @@ -276,7 +279,7 @@ const processLedeMedia = async (story_json) => { caption: lede.caption?.replaceAll(capRegex, '') ?? '', credit: lede.credit?.replaceAll(capRegex, '') ?? '', }; - return art(path.join(__dirname, 'templates/image_figure.art'), image); + return renderImageFigure(image); } else if (story_json.imageAttachments) { const attachment = Object.values(story_json.imageAttachments)[0]; if (attachment) { @@ -286,7 +289,7 @@ const processLedeMedia = async (story_json) => { caption: attachment.caption?.replaceAll(capRegex, '') ?? '', credit: attachment.credit?.replaceAll(capRegex, '') ?? '', }; - return art(path.join(__dirname, 'templates/image_figure.art'), image); + return renderImageFigure(image); } return ''; } else if (story_json.type === 'Lede') { @@ -299,7 +302,7 @@ const processLedeMedia = async (story_json) => { credit: props.credit?.replaceAll(capRegex, '') ?? '', src: props.url, }; - return art(path.join(__dirname, 'templates/lede_media.art'), { media }); + return renderLedeMedia(media); } }; @@ -340,12 +343,12 @@ const processBody = async (body_html, story_json) => { credit: (episode.credits.map((c) => c.name).join(', ') ?? []) || ($(e).find('[class$="credit"]').html()?.trim() ?? ''), }; } - new_figure = art(path.join(__dirname, 'templates/audio_media.art'), audio); + new_figure = renderAudioMedia(audio); } else if (imageType === 'video') { if (story_json.videoAttachments) { const attachment = story_json.videoAttachments[$(e).data('id')]; const video = await processVideo(attachment.bmmrId); - new_figure = art(path.join(__dirname, 'templates/video_media.art'), video); + new_figure = renderVideoMedia(video); } } else if (imageType === 'photo' || imageType === 'image' || type === 'image') { let src, alt; @@ -360,7 +363,7 @@ const processBody = async (body_html, story_json) => { const caption = $(e).find('[class$="text"], .caption, .photo-essay__text').html()?.trim() ?? ''; const credit = $(e).find('[class$="credit"], .credit, .photo-essay__source').html()?.trim() ?? ''; const image = { src, alt, caption, credit }; - new_figure = art(path.join(__dirname, 'templates/image_figure.art'), image); + new_figure = renderImageFigure(image); } $(new_figure).insertAfter(e); $(e).remove(); @@ -502,7 +505,7 @@ const nodeRenderers = { chartAlt: e.alt, fallback: e.src, }; - return art(path.join(__dirname, 'templates/chart_media.art'), { chart }); + return renderChartMedia({ chart }); } const image = { alt: node.data.attachment?.footnote || '', @@ -510,14 +513,14 @@ const nodeRenderers = { credit: node.data.attachment?.source || '', src: node.data.chart?.fallback || '', }; - return art(path.join(__dirname, 'templates/image_figure.art'), image); + return renderImageFigure(image); } if (t === 'photo') { const h = node.data; let img = ''; if (h.attachment) { const image = { src: h.photo?.src, alt: h.photo?.alt, caption: h.photo?.caption, credit: h.photo?.credit }; - img = art(path.join(__dirname, 'templates/image_figure.art'), image); + img = renderImageFigure(image); } if (h.link && h.link.destination && h.link.destination.web) { const href = h.link.destination.web; @@ -530,7 +533,7 @@ const nodeRenderers = { const id = h.attachment?.id; if (id) { const desc = await processVideo(id, h.attachment?.title); - return art(path.join(__dirname, 'templates/video_media.art'), desc); + return renderVideoMedia(desc); } } if (t === 'audio' && node.data.attachment) { @@ -545,7 +548,7 @@ const nodeRenderers = { caption: P, credit: '', }; - return art(path.join(__dirname, 'templates/audio_media.art'), audio); + return renderAudioMedia(audio); } } return ''; diff --git a/lib/routes/bookwalker/search.ts b/lib/routes/bookwalker/search.tsx similarity index 89% rename from lib/routes/bookwalker/search.ts rename to lib/routes/bookwalker/search.tsx index 4ac2f6b2b..ba545a9c9 100644 --- a/lib/routes/bookwalker/search.ts +++ b/lib/routes/bookwalker/search.tsx @@ -1,14 +1,12 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const { filter = 'order=sell_desc' } = ctx.req.param(); @@ -36,16 +34,13 @@ export const handler = async (ctx: Context): Promise => { .find('img') .attr('data-src') ?.replace(/_\d+(\.\w+)$/, '$1'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - images: image - ? [ - { - src: image, - alt: name, - }, - ] - : undefined, - }); + const description: string | undefined = renderToString( + image ? ( +
    + {name} +
    + ) : null + ); const linkUrl: string | undefined = $el.find('div.bwbookitem a').attr('href'); const authors: DataItem['author'] = authorStr.split(/,/).map((a) => ({ name: a, diff --git a/lib/routes/bookwalker/templates/description.art b/lib/routes/bookwalker/templates/description.art deleted file mode 100644 index 0a7f83a6f..000000000 --- a/lib/routes/bookwalker/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/booru/mmda.ts b/lib/routes/booru/mmda.ts index 90b385306..dc0fb6a88 100644 --- a/lib/routes/booru/mmda.ts +++ b/lib/routes/booru/mmda.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import queryString from 'query-string'; @@ -7,7 +5,8 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/mmda/tags/:tags?', @@ -77,7 +76,7 @@ async function handler(ctx) { link: `${baseUrl}/${a.attr('href')}`, image: imageSrc, author: user, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ title, image: imageSrc, by: user, @@ -111,7 +110,7 @@ async function handler(ctx) { item.pubDate = parseDate(result.posted); } - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ title: item.title, image: bigImage ?? item.image, posted: item.pubDate ?? '', diff --git a/lib/routes/booru/templates/description.art b/lib/routes/booru/templates/description.art deleted file mode 100644 index dde6382de..000000000 --- a/lib/routes/booru/templates/description.art +++ /dev/null @@ -1,25 +0,0 @@ -
    - {{if image }} - {{ title }} - {{/if}} - - {{if posted }} -

    posted: {{ posted }}

    - {{/if}} - - {{if by }} -

    by: {{ by }}

    - {{/if}} - - {{if source }} -

    source: {{ source }}

    - {{/if}} - - {{if rating }} -

    rating: {{ rating }}

    - {{/if}} - - {{if score }} -

    score: {{ score }}

    - {{/if}} -
    diff --git a/lib/routes/booru/templates/description.tsx b/lib/routes/booru/templates/description.tsx new file mode 100644 index 000000000..f56489ff9 --- /dev/null +++ b/lib/routes/booru/templates/description.tsx @@ -0,0 +1,43 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: string; + title?: string; + posted?: string; + by?: string; + source?: string; + rating?: string; + score?: string; +}; + +export const renderDescription = ({ image, title, posted, by, source, rating, score }: DescriptionData) => + renderToString( +
    + {image ? {title} : null} + {posted ? ( +

    + posted: {posted} +

    + ) : null} + {by ? ( +

    + by: {by} +

    + ) : null} + {source ? ( +

    + source: {source} +

    + ) : null} + {rating ? ( +

    + rating: {rating} +

    + ) : null} + {score ? ( +

    + score: {score} +

    + ) : null} +
    + ); diff --git a/lib/routes/bsky/feeds.ts b/lib/routes/bsky/feeds.ts index 852b2ddd8..f13660c5c 100644 --- a/lib/routes/bsky/feeds.ts +++ b/lib/routes/bsky/feeds.ts @@ -1,11 +1,9 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; +import { renderPost } from './templates/post'; import { getFeed, getFeedGenerator, resolveHandle } from './utils'; export const route: Route = { @@ -41,7 +39,7 @@ async function handler(ctx) { const items = feeds.feed.map(({ post }) => ({ title: post.record.text.split('\n')[0], - description: art(path.join(__dirname, 'templates/post.art'), { + description: renderPost({ text: post.record.text.replaceAll('\n', '
    '), embed: post.embed, // embed.$type "app.bsky.embed.record#view" and "app.bsky.embed.recordWithMedia#view" are not handled diff --git a/lib/routes/bsky/posts.ts b/lib/routes/bsky/posts.ts index a6d6aea6e..062543654 100644 --- a/lib/routes/bsky/posts.ts +++ b/lib/routes/bsky/posts.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; import querystring from 'node:querystring'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; +import { renderPost } from './templates/post'; import { getAuthorFeed, getProfile, resolveHandle } from './utils'; export const route: Route = { @@ -59,7 +58,7 @@ async function handler(ctx) { const items = authorFeed.feed.map(({ post }) => ({ title: post.record.text.split('\n')[0], - description: art(path.join(__dirname, 'templates/post.art'), { + description: renderPost({ text: post.record.text.replaceAll('\n', '
    '), embed: post.embed, // embed.$type "app.bsky.embed.record#view" and "app.bsky.embed.recordWithMedia#view" are not handled diff --git a/lib/routes/bsky/templates/post.art b/lib/routes/bsky/templates/post.art deleted file mode 100644 index 80d41fea1..000000000 --- a/lib/routes/bsky/templates/post.art +++ /dev/null @@ -1,24 +0,0 @@ -{{ if text }} - {{@ text }}
    -{{ /if }} - -{{ if embed }} - {{ if embed.$type === 'app.bsky.embed.images#view' }} - {{ each embed.images i }} - {{ i.alt }}
    - {{ /each }} - {{ else if embed.$type === 'app.bsky.embed.video#view' }} -
    - {{ else if embed.$type === 'app.bsky.embed.external#view' }} - {{ embed.external.title }}
    - {{ embed.external.description }} -
    - {{ /if }} -{{ /if }} diff --git a/lib/routes/bsky/templates/post.tsx b/lib/routes/bsky/templates/post.tsx new file mode 100644 index 000000000..8d03535ed --- /dev/null +++ b/lib/routes/bsky/templates/post.tsx @@ -0,0 +1,67 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageEmbed = { + fullsize: string; + alt?: string | null; +}; + +type ExternalEmbed = { + uri: string; + title?: string; + description?: string; +}; + +type VideoEmbed = { + thumbnail?: string; + playlist?: string; +}; + +type Embed = { + $type?: string; + images?: ImageEmbed[]; + external?: ExternalEmbed; +} & VideoEmbed; + +type PostProps = { + text?: string; + embed?: Embed; +}; + +export const renderPost = ({ text, embed }: PostProps): string => + renderToString( + <> + {text ? ( + <> + {raw(text)} +
    + + ) : null} + {embed ? ( + <> + {embed.$type === 'app.bsky.embed.images#view' ? ( + embed.images?.map((image) => ( + + {image.alt +
    +
    + )) + ) : embed.$type === 'app.bsky.embed.video#view' ? ( + <> + +
    + + ) : embed.$type === 'app.bsky.embed.external#view' ? ( + + {embed.external?.title} +
    + {embed.external?.description} +
    + ) : null} + + ) : null} + + ); diff --git a/lib/routes/btzj/index.ts b/lib/routes/btzj/index.tsx similarity index 94% rename from lib/routes/btzj/index.ts rename to lib/routes/btzj/index.tsx index 0bc11e7e1..ffb65e5e9 100644 --- a/lib/routes/btzj/index.ts +++ b/lib/routes/btzj/index.tsx @@ -1,6 +1,5 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; @@ -8,11 +7,21 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const allowDomain = new Set(['2btjia.com', '88btbtt.com', 'btbtt15.com', 'btbtt20.com']); +const renderTorrents = (torrents) => + renderToString( + + {torrents.map((torrent) => ( + + + + ))} +
    {torrent}
    + ); + export const route: Route = { path: '/:category?', categories: ['multimedia'], @@ -139,9 +148,7 @@ async function handler(ctx) { item.pubDate = timezone(parseDate(content('.bg2 b').first().text()), +8); if (torrents.length > 0) { - item.description += art(path.join(__dirname, 'templates/torrents.art'), { - torrents: torrents.toArray().map((t) => content(t).parent().html()), - }); + item.description += renderTorrents(torrents.toArray().map((t) => content(t).parent().html())); item.enclosure_type = 'application/x-bittorrent'; item.enclosure_url = torrents.first().attr('href'); } diff --git a/lib/routes/btzj/templates/torrents.art b/lib/routes/btzj/templates/torrents.art deleted file mode 100644 index 4eee6c7da..000000000 --- a/lib/routes/btzj/templates/torrents.art +++ /dev/null @@ -1,5 +0,0 @@ - -{{ each torrents torrent }} - -{{ /each }} -
    {{ torrent }}
    \ No newline at end of file diff --git a/lib/routes/buaa/lib/space/newbook.ts b/lib/routes/buaa/lib/space/newbook.tsx similarity index 59% rename from lib/routes/buaa/lib/space/newbook.ts rename to lib/routes/buaa/lib/space/newbook.tsx index 303cb1e46..585f50ed7 100644 --- a/lib/routes/buaa/lib/space/newbook.ts +++ b/lib/routes/buaa/lib/space/newbook.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; interface Book { @@ -149,11 +148,88 @@ async function getItem(item: Book): Promise { const info = await getItemInfo(item.isbn); const holdings = JSON.parse(item.holdings) as Holding[]; const link = `https://space.lib.buaa.edu.cn/space/searchDetailLocal/${item.bibId}`; - const content = art(path.join(__dirname, 'templates/newbook.art'), { - item, - info, - holdings, - }); + const content = renderToString( + <> + {info?.imageUrl ? ( + + ) : null} +

    书籍信息

    +
    + + {item.callno?.at(0) || '无'} + {' '} + / {item.author} / {item.publisher} / {item.pub_year} +
    +

    简介

    +
    {info?.content}
    + + + + + + + + + + + + + + + +
    ISBN{item.isbn}
    语言{item.language}
    类型{item.docTypeDesc}
    + {info?.authorInfo ? ( + <> +

    作者简介

    +
    {info.authorInfo}
    + + ) : null} +

    馆藏信息

    + {item.onSelfDate ? ( + <> + 上架时间{item.onSelfDate} + + ) : null} +
    +

    馆藏地点

    + + + {holdings.map((holding) => ( + <> + + + + + + + + + + + + + + + + + + + + + + ))} + +
    所属馆藏地{holding.location}
    索书号{holding.callNo}
    条码号{holding.barCode}
    编号{holding.itemId}
    书刊状态{holding.status}
    + {info?.catalog ? ( + <> +

    目录

    +
    {raw(info.catalog)}
    + + ) : null} + + ); return { language: item.language === 'eng' ? 'en' : 'zh-CN', title: item.title, diff --git a/lib/routes/buaa/lib/space/templates/newbook.art b/lib/routes/buaa/lib/space/templates/newbook.art deleted file mode 100644 index 6068de6df..000000000 --- a/lib/routes/buaa/lib/space/templates/newbook.art +++ /dev/null @@ -1,44 +0,0 @@ -{{if info.imageUrl}} - -{{/if}} -

    书籍信息

    -
    - {{item.callno.at(0) || '无'}} / - {{item.author}} / - {{item.publisher}} / - {{item.pub_year}} -
    -

    简介

    -
    {{info?.content}}
    - - - - -
    ISBN{{item.isbn}}
    语言{{item.language}}
    类型{{item.docTypeDesc}}
    -{{if info.authorInfo}} -

    作者简介

    -
    {{info.authorInfo}}
    -{{/if}} -

    馆藏信息

    -{{if item.onSelfDate}} -上架时间: -{{item.onSelfDate}} -{{/if}} -
    -

    馆藏地点

    - - {{each holdings holding}} - - - - - - - - - {{/each}} -
    所属馆藏地{{holding.location}}
    索书号{{holding.callNo}}
    条码号{{holding.barCode}}
    编号{{holding.itemId}}
    书刊状态{{holding.status}}
    -{{if info.catalog}} -

    目录

    -
    {{@ info.catalog}}
    -{{/if}} \ No newline at end of file diff --git a/lib/routes/caai/templates/description.art b/lib/routes/caai/templates/description.art deleted file mode 100644 index 7755ee9f6..000000000 --- a/lib/routes/caai/templates/description.art +++ /dev/null @@ -1 +0,0 @@ -{{@ desc }} diff --git a/lib/routes/caai/utils.ts b/lib/routes/caai/utils.tsx similarity index 85% rename from lib/routes/caai/utils.ts rename to lib/routes/caai/utils.tsx index 0894a5ca8..dbbee5e7a 100644 --- a/lib/routes/caai/utils.ts +++ b/lib/routes/caai/utils.tsx @@ -1,20 +1,16 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const base = 'http://www.caai.cn'; const urlBase = (caty) => base + `/index.php?s=/home/article/index/id/${caty}.html`; -const renderDesc = (desc) => - art(path.join(__dirname, 'templates/description.art'), { - desc, - }); +const renderDesc = (desc) => renderToString(<>{desc ? raw(desc) : null}); const detailPage = (e, cache) => cache.tryGet(e.link, async () => { diff --git a/lib/routes/caareviews/templates/utils.art b/lib/routes/caareviews/templates/utils.art deleted file mode 100644 index ae6f69496..000000000 --- a/lib/routes/caareviews/templates/utils.art +++ /dev/null @@ -1,2 +0,0 @@ - -{{@ content}} diff --git a/lib/routes/caareviews/utils.ts b/lib/routes/caareviews/utils.tsx similarity index 82% rename from lib/routes/caareviews/utils.ts rename to lib/routes/caareviews/utils.tsx index 98a478178..dbeddcbf3 100644 --- a/lib/routes/caareviews/utils.ts +++ b/lib/routes/caareviews/utils.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'http://www.caareviews.org'; @@ -32,10 +31,12 @@ const getItems = (ctx, list) => const coverUrl = new URL($('div.cover > a').attr('href'), rootUrl).href; const content = $('div.content.full-review').html(); - item.description = art(path.join(__dirname, 'templates/utils.art'), { - coverUrl, - content, - }); + item.description = renderToString( + <> + + {raw(content ?? '')} + + ); $('div.review_heading').remove(); item.pubDate = parseDate($('div.header-text > div.clearfix').text()); item.doi = $('div.crossref > a').attr('href').replace('http://dx.doi.org/', ''); diff --git a/lib/routes/cahkms/index.ts b/lib/routes/cahkms/index.tsx similarity index 67% rename from lib/routes/cahkms/index.ts rename to lib/routes/cahkms/index.tsx index 7b7e6e5dd..62d9585ad 100644 --- a/lib/routes/cahkms/index.ts +++ b/lib/routes/cahkms/index.tsx @@ -1,10 +1,10 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const titles = { @@ -81,13 +81,31 @@ async function handler(ctx) { }); item.author = detailResponse.data.WZLY; - item.description = art(path.join(__dirname, 'templates/description.art'), { - rootUrl, - content: detailResponse.data.CONTENT, - image: detailResponse.data.URL, - files: detailResponse.data.fjlist, - video: detailResponse.data.VIDEO.indexOf('.mp4') > 0 ? detailResponse.data.VIDEO : null, - }); + const video = detailResponse.data.VIDEO.indexOf('.mp4') > 0 ? detailResponse.data.VIDEO : null; + item.description = renderToString( + <> + {detailResponse.data.URL ? : null} + {detailResponse.data.CONTENT ? raw(detailResponse.data.CONTENT) : null} + {video ? ( + + ) : null} + {detailResponse.data.fjlist?.length ? ( + <> +
    + 下载附件: +
    + {detailResponse.data.fjlist.map((file) => ( + <> + {file.FJMC} +
    + + ))} + + ) : null} + + ); item.link = `${rootUrl}/HKMAC/webView/mc/AboutUs_1.html?${category}&${titles[category]}`; return item; diff --git a/lib/routes/cahkms/templates/description.art b/lib/routes/cahkms/templates/description.art deleted file mode 100644 index 6382ac79e..000000000 --- a/lib/routes/cahkms/templates/description.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if image }} - -{{ /if }} -{{ if content }} -{{@ content }} -{{ /if }} -{{ if video }} - -{{ /if }} -{{ if files }} -
    下载附件:
    -{{ each files file }} -{{ file.FJMC }}
    -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/caixin/database.ts b/lib/routes/caixin/database.ts index 5909b3b7a..2c2180013 100644 --- a/lib/routes/caixin/database.ts +++ b/lib/routes/caixin/database.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderArticle } from './templates/article'; + export const route: Route = { path: '/database', categories: ['traditional-media'], @@ -57,7 +56,7 @@ async function handler() { const content = load(detailResponse.data); item.pubDate = timezone(parseDate(content('#pubtime_baidu').text()), +8); - item.description = art(path.join(__dirname, 'templates/article.art'), { + item.description = renderArticle({ item, $: content, }); diff --git a/lib/routes/caixin/templates/article.art b/lib/routes/caixin/templates/article.art deleted file mode 100644 index 25839ba2a..000000000 --- a/lib/routes/caixin/templates/article.art +++ /dev/null @@ -1,40 +0,0 @@ -{{ if item.audio }} -
    -{{ /if }} -{{ if $('.article .subhead').length }} -

    {{@ $('.article .subhead').html() }}

    -
    -{{ /if }} - -{{ if $('.article .media').length }} - {{@ $('.article .media').html() }} -
    -{{ /if }} - -{{ if $('.article .content_video').length }} - <% const video = $('script').text().match(/initPlayer\('(.*?)','(.*?)'\)/); %> - {{ if video}} - <% const videoUrl = video[1]; %> - <% const poster = video[2]; %> - -
    - {{ /if }} -{{ /if }} - -{{ if $('div#Main_Content_Val.text').length }} - {{@ $('div#Main_Content_Val.text').html() }} -{{ else }} - {{ if item.summary }} -

    {{ item.summary }}

    -
    - {{ /if }} - {{ if item.pics?.includes('#') }} - {{ each item.pics.split('#') pic }} - -
    - {{ /each }} - {{ else }} - -
    - {{ /if }} -{{ /if }} diff --git a/lib/routes/caixin/templates/article.tsx b/lib/routes/caixin/templates/article.tsx new file mode 100644 index 000000000..75337806b --- /dev/null +++ b/lib/routes/caixin/templates/article.tsx @@ -0,0 +1,81 @@ +import type { CheerioAPI } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ArticleData = { + item: any; + $: CheerioAPI; +}; + +export const renderArticle = ({ item, $ }: ArticleData) => { + const subhead = $('.article .subhead').length ? $('.article .subhead').html() : null; + const media = $('.article .media').length ? $('.article .media').html() : null; + const contentVideo = $('.article .content_video').length + ? $('script') + .text() + .match(/initPlayer\('(.*?)','(.*?)'\)/) + : null; + const mainContent = $('div#Main_Content_Val.text').length ? $('div#Main_Content_Val.text').html() : null; + const picsValue = item.pics; + const picsList = typeof picsValue === 'string' && picsValue.includes('#') ? picsValue.split('#') : null; + + return renderToString( + <> + {item.audio ? ( + <> + +
    + + ) : null} + {subhead ? ( + <> +
    +

    {raw(subhead)}

    +
    +
    + + ) : null} + {media ? ( + <> + {raw(media)} +
    + + ) : null} + {contentVideo ? ( + <> + +
    + + ) : null} + {mainContent ? ( + <>{raw(mainContent)} + ) : ( + <> + {item.summary ? ( + <> +
    +

    {item.summary}

    +
    +
    + + ) : null} + {picsList ? ( + <> + {picsList.map((pic) => ( + <> + +
    + + ))} + + ) : ( + <> + +
    + + )} + + )} + + ); +}; diff --git a/lib/routes/caixin/utils.ts b/lib/routes/caixin/utils.ts index 9268e05d8..4fb6f13d1 100644 --- a/lib/routes/caixin/utils.ts +++ b/lib/routes/caixin/utils.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderArticle } from './templates/article'; const parseArticle = async (item) => { if (/\.blog\.caixin\.com$/.test(new URL(item.link).hostname)) { @@ -13,7 +12,7 @@ const parseArticle = async (item) => { const $ = load(response); - item.description = art(path.join(__dirname, 'templates/article.art'), { + item.description = renderArticle({ item, $, }); diff --git a/lib/routes/cankaoxiaoxi/index.ts b/lib/routes/cankaoxiaoxi/index.tsx similarity index 87% rename from lib/routes/cankaoxiaoxi/index.ts rename to lib/routes/cankaoxiaoxi/index.tsx index b6bc80fb3..2f29816ff 100644 --- a/lib/routes/cankaoxiaoxi/index.ts +++ b/lib/routes/cankaoxiaoxi/index.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -79,10 +78,7 @@ async function handler(ctx) { items.map((item) => cache.tryGet(item.link, async () => { if (item.video) { - item.description = art(path.join(__dirname, 'templates/description.art'), { - video: item.video, - cover: item.cover, - }); + item.description = renderDescription(item.video, item.cover); } else { const detailResponse = await got({ method: 'get', @@ -108,3 +104,14 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (video: string | undefined, cover: string | undefined): string => + renderToString( + <> + {video ? ( + + ) : null} + + ); diff --git a/lib/routes/cankaoxiaoxi/templates/description.art b/lib/routes/cankaoxiaoxi/templates/description.art deleted file mode 100644 index 22843d674..000000000 --- a/lib/routes/cankaoxiaoxi/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if video }} - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/cara/likes.ts b/lib/routes/cara/likes.ts index 30b82896d..e877cf30e 100644 --- a/lib/routes/cara/likes.ts +++ b/lib/routes/cara/likes.ts @@ -1,10 +1,8 @@ -import path from 'node:path'; - import type { Data, DataItem, Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { API_HOST, CDN_HOST, HOST } from './constant'; +import { renderPost } from './templates/post'; import type { PostsResponse } from './types'; import { customFetch, parseUserData } from './utils'; @@ -34,7 +32,7 @@ async function handler(ctx): Promise { const timelineResponse = await customFetch(api); const items = timelineResponse.data.map((item) => { - const description = art(path.join(__dirname, 'templates/post.art'), { + const description = renderPost({ content: item.content, images: item.images.filter((i) => !i.isCoverImg).map((i) => ({ ...i, src: `${CDN_HOST}/${i.src}` })), }); diff --git a/lib/routes/cara/templates/post.art b/lib/routes/cara/templates/post.art deleted file mode 100644 index 2cceed7e5..000000000 --- a/lib/routes/cara/templates/post.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if content }} -

    {{ content }}

    -{{ /if }} -{{ each images image }} - -{{ /each }} diff --git a/lib/routes/cara/templates/post.tsx b/lib/routes/cara/templates/post.tsx new file mode 100644 index 000000000..9590f4777 --- /dev/null +++ b/lib/routes/cara/templates/post.tsx @@ -0,0 +1,20 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src: string; +}; + +type PostData = { + content?: string; + images?: Image[]; +}; + +export const renderPost = ({ content, images = [] }: PostData): string => + renderToString( + <> + {content ?

    {content}

    : null} + {images.map((image) => ( + + ))} + + ); diff --git a/lib/routes/cara/timeline.ts b/lib/routes/cara/timeline.ts index 00ff39209..523d0b100 100644 --- a/lib/routes/cara/timeline.ts +++ b/lib/routes/cara/timeline.ts @@ -1,10 +1,8 @@ -import path from 'node:path'; - import type { Data, DataItem, Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { API_HOST, CDN_HOST, HOST } from './constant'; +import { renderPost } from './templates/post'; import type { PostsResponse } from './types'; import { customFetch, parseUserData } from './utils'; @@ -34,7 +32,7 @@ async function handler(ctx): Promise { const timelineResponse = await customFetch(api); const items = timelineResponse.data.map((item) => { - const description = art(path.join(__dirname, 'templates/post.art'), { + const description = renderPost({ content: item.content, images: item.images.filter((i) => !i.isCoverImg).map((i) => ({ ...i, src: `${CDN_HOST}/${i.src}` })), }); diff --git a/lib/routes/cartoonmad/comic.ts b/lib/routes/cartoonmad/comic.tsx similarity index 88% rename from lib/routes/cartoonmad/comic.ts rename to lib/routes/cartoonmad/comic.tsx index 75eddc6f0..61e24bf89 100644 --- a/lib/routes/cartoonmad/comic.ts +++ b/lib/routes/cartoonmad/comic.tsx @@ -1,23 +1,28 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const baseUrl = 'https://www.cartoonmad.com'; const KEY = '5e585'; +const renderChapterImage = (url: string) => renderToString(); + +const ChapterImage = ({ url }: { url: string }) => ( + <> + +
    + +); + const loadContent = (id, { chapter, pages }) => { let description = ''; for (let page = 1; page <= pages; page++) { const url = `${baseUrl}/${KEY}/${id}/${chapter}/${String(page).padStart(3, '0')}.jpg`; - description += art(path.join(__dirname, 'templates/chapter.art'), { - url, - }); + description += renderChapterImage(url); } return description; }; diff --git a/lib/routes/cartoonmad/templates/chapter.art b/lib/routes/cartoonmad/templates/chapter.art deleted file mode 100644 index 62696502b..000000000 --- a/lib/routes/cartoonmad/templates/chapter.art +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/lib/routes/cbaigui/index.ts b/lib/routes/cbaigui/index.ts index fab210c7b..b440f86a2 100644 --- a/lib/routes/cbaigui/index.ts +++ b/lib/routes/cbaigui/index.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; +import { renderFigure } from './templates/figure'; import { apiSlug, GetFilterId, rootUrl } from './utils'; export const route: Route = { @@ -53,7 +51,7 @@ async function handler(ctx) { const height = image.prop('data-rawheight'); content(this).replaceWith( - art(path.join(__dirname, 'templates/figure.art'), { + renderFigure({ src, width, height, @@ -70,7 +68,7 @@ async function handler(ctx) { const height = image.prop('height'); content(this).replaceWith( - art(path.join(__dirname, 'templates/figure.art'), { + renderFigure({ src, width, height, diff --git a/lib/routes/cbaigui/templates/figure.art b/lib/routes/cbaigui/templates/figure.art deleted file mode 100644 index 6571027e2..000000000 --- a/lib/routes/cbaigui/templates/figure.art +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/lib/routes/cbaigui/templates/figure.tsx b/lib/routes/cbaigui/templates/figure.tsx new file mode 100644 index 000000000..85a8450d1 --- /dev/null +++ b/lib/routes/cbaigui/templates/figure.tsx @@ -0,0 +1,15 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type FigureProps = { + src: string; + width?: string; + height?: string; +}; + +const Figure = ({ src, width, height }: FigureProps) => ( +
    + +
    +); + +export const renderFigure = (props: FigureProps): string => renderToString(
    ); diff --git a/lib/routes/cbndata/information.ts b/lib/routes/cbndata/information.ts index 525964d9f..43f8a6d4a 100644 --- a/lib/routes/cbndata/information.ts +++ b/lib/routes/cbndata/information.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; @@ -9,7 +7,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { id = 'all' } = ctx.req.param(); @@ -35,7 +34,7 @@ export const handler = async (ctx: Context): Promise => { items = response.data.slice(0, limit).map((item): DataItem => { const title: string = item.title; const image: string | undefined = item.image; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -96,7 +95,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = data.title; const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ description: data.content, }); const pubDate: number | string = data.date; diff --git a/lib/routes/cbndata/templates/description.art b/lib/routes/cbndata/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/cbndata/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/cbndata/templates/description.tsx b/lib/routes/cbndata/templates/description.tsx new file mode 100644 index 000000000..018734888 --- /dev/null +++ b/lib/routes/cbndata/templates/description.tsx @@ -0,0 +1,27 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + description?: string; +}; + +const CbndataDescription = ({ images, description }: DescriptionData) => ( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/ccf/ccfcv/index.ts b/lib/routes/ccf/ccfcv/index.tsx similarity index 90% rename from lib/routes/ccf/ccfcv/index.ts rename to lib/routes/ccf/ccfcv/index.tsx index 4b038d0ea..1ad4f155c 100644 --- a/lib/routes/ccf/ccfcv/index.ts +++ b/lib/routes/ccf/ccfcv/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://tc.ccf.org.cn'; @@ -73,9 +71,11 @@ async function handler(ctx) { if (item.status !== 404) { const content = load(detailResponse.data); const pdfUrl = content('div.g-box1 p a').attr('href'); - item.description = art(path.join(__dirname, '../templates/ccfcv/description.art'), { - pdfUrl, - }); + item.description = renderToString( +
    + +
    + ); } delete item.status; diff --git a/lib/routes/ccf/templates/ccfcv/description.art b/lib/routes/ccf/templates/ccfcv/description.art deleted file mode 100644 index 402450ca5..000000000 --- a/lib/routes/ccf/templates/ccfcv/description.art +++ /dev/null @@ -1 +0,0 @@ -
    diff --git a/lib/routes/ccf/templates/tfbd/description.art b/lib/routes/ccf/templates/tfbd/description.art deleted file mode 100644 index 7755ee9f6..000000000 --- a/lib/routes/ccf/templates/tfbd/description.art +++ /dev/null @@ -1 +0,0 @@ -{{@ desc }} diff --git a/lib/routes/ccf/tfbd/utils.ts b/lib/routes/ccf/tfbd/utils.tsx similarity index 84% rename from lib/routes/ccf/tfbd/utils.ts rename to lib/routes/ccf/tfbd/utils.tsx index 544f2c9a7..bebaced5a 100644 --- a/lib/routes/ccf/tfbd/utils.ts +++ b/lib/routes/ccf/tfbd/utils.tsx @@ -1,20 +1,16 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const base = 'http://tfbd.ccf.org.cn'; const urlBase = (caty, id) => base + `/tfbd/${caty}/${id}/`; -const renderDesc = (desc) => - art(path.join(__dirname, '../templates/tfbd/description.art'), { - desc, - }); +const renderDesc = (desc) => renderToString(<>{desc ? raw(desc) : null}); const detailPage = (e, cache) => cache.tryGet(e.link, async () => { diff --git a/lib/routes/ccfa/index.ts b/lib/routes/ccfa/index.tsx similarity index 94% rename from lib/routes/ccfa/index.ts rename to lib/routes/ccfa/index.tsx index 83c7c988d..b90b202f7 100644 --- a/lib/routes/ccfa/index.ts +++ b/lib/routes/ccfa/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx) => { const { type = '1' } = ctx.req.param(); @@ -46,10 +45,14 @@ export const handler = async (ctx) => { const $$ = load(detailResponse); const title = $$('h2#title').text(); - const description = art(path.join(__dirname, 'templates/description.art'), { - intro: $$('div.artical_info_jianjie').html(), - description: $$('div.news_artical_txt').html(), - }); + const intro = $$('div.artical_info_jianjie').html(); + const descriptionHtml = $$('div.news_artical_txt').html(); + const description = renderToString( + <> + {intro ?
    {intro}
    : null} + {descriptionHtml ? raw(descriptionHtml) : null} + + ); const pubDate = $$('div.artical_info_left') diff --git a/lib/routes/ccfa/templates/description.art b/lib/routes/ccfa/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/ccfa/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ccg/index.ts b/lib/routes/ccg/index.ts index c272bcd2f..6d86a9016 100644 --- a/lib/routes/ccg/index.ts +++ b/lib/routes/ccg/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'news' } = ctx.req.param(); @@ -33,7 +32,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('h5').text(); const image: string | undefined = $el.find('div.huodong-img img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -42,7 +41,7 @@ export const handler = async (ctx: Context): Promise => { }, ] : undefined, - intro: $el.find('p').html(), + intro: $el.find('p').html() || undefined, }); const pubDateStr: string | undefined = $el.find('span').text(); const linkUrl: string | undefined = $el.find('a').attr('href'); @@ -82,8 +81,8 @@ export const handler = async (ctx: Context): Promise => { $$('div.pinpai-page h3').remove(); $$('div.pinpai-page span.time').remove(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.pinpai-page').html(), + const description: string | undefined = renderDescription({ + description: $$('div.pinpai-page').html() || undefined, }); const upDatedStr: string | undefined = pubDateStr; diff --git a/lib/routes/ccg/templates/description.art b/lib/routes/ccg/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/ccg/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ccg/templates/description.tsx b/lib/routes/ccg/templates/description.tsx new file mode 100644 index 000000000..81ceaef91 --- /dev/null +++ b/lib/routes/ccg/templates/description.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src: string; + alt?: string; +}; + +type DescriptionProps = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionProps): string => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/cde/templates/xxgk/breakthroughCure.art b/lib/routes/cde/templates/xxgk/breakthroughCure.art deleted file mode 100644 index ed4c29941..000000000 --- a/lib/routes/cde/templates/xxgk/breakthroughCure.art +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - -
    受理号药品名称注册申请人申请日期公示日期公示截止日期
    {{ item.acceptid }}{{ item.drgnamecn }}{{ item.company }}{{ item.applyDate }}{{ item.noticeDate }}{{ item.endNoticeDate }}
    diff --git a/lib/routes/cde/templates/xxgk/cliniCal.art b/lib/routes/cde/templates/xxgk/cliniCal.art deleted file mode 100644 index 487cd718b..000000000 --- a/lib/routes/cde/templates/xxgk/cliniCal.art +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - -
    受理号药品名称申请人名称适应症注册分类
    {{ item.acceptid }}{{ item.drgnamecn }}{{ item.companys }}{{ item.lcmsxkIndication }}{{ item.lcmsxkRegisterkind }}
    diff --git a/lib/routes/cde/templates/xxgk/priorityApproval.art b/lib/routes/cde/templates/xxgk/priorityApproval.art deleted file mode 100644 index 2a385a9d3..000000000 --- a/lib/routes/cde/templates/xxgk/priorityApproval.art +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - -
    受理号药品名称注册申请人承办日期申请日期公示日期
    {{ item.acceptid }}{{ item.drgnamecn }}{{ item.company }}{{ item.createdate }}{{ item.applyDate }}{{ item.noticeDate }}
    diff --git a/lib/routes/cde/xxgk.ts b/lib/routes/cde/xxgk.tsx similarity index 62% rename from lib/routes/cde/xxgk.ts rename to lib/routes/cde/xxgk.tsx index adf107dff..0d858d0ce 100644 --- a/lib/routes/cde/xxgk.ts +++ b/lib/routes/cde/xxgk.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import utils from './utils'; @@ -85,13 +84,13 @@ async function handler(ctx) { let description = ''; switch (category) { case 'priorityApproval': - description = art(path.join(__dirname, 'templates/xxgk/priorityApproval.art'), { item }); + description = renderToString(); break; case 'breakthroughCure': - description = art(path.join(__dirname, 'templates/xxgk/breakthroughCure.art'), { item }); + description = renderToString(); break; case 'cliniCal': - description = art(path.join(__dirname, 'templates/xxgk/cliniCal.art'), { item }); + description = renderToString(); break; default: description = ''; @@ -112,3 +111,64 @@ async function handler(ctx) { item: items, }; } + +const PriorityApprovalTable = ({ item }: { item: any }) => ( + + + + + + + + + + + + + + + + + +
    受理号药品名称注册申请人承办日期申请日期公示日期
    {item.acceptid}{item.drgnamecn}{item.company}{item.createdate}{item.applyDate}{item.noticeDate}
    +); + +const BreakthroughCureTable = ({ item }: { item: any }) => ( + + + + + + + + + + + + + + + + + +
    受理号药品名称注册申请人申请日期公示日期公示截止日期
    {item.acceptid}{item.drgnamecn}{item.company}{item.applyDate}{item.noticeDate}{item.endNoticeDate}
    +); + +const CliniCalTable = ({ item }: { item: any }) => ( + + + + + + + + + + + + + + + +
    受理号药品名称申请人名称适应症注册分类
    {item.acceptid}{item.drgnamecn}{item.companys}{item.lcmsxkIndication}{item.lcmsxkRegisterkind}
    +); diff --git a/lib/routes/cdzjryb/project-list.ts b/lib/routes/cdzjryb/project-list.tsx similarity index 60% rename from lib/routes/cdzjryb/project-list.ts rename to lib/routes/cdzjryb/project-list.tsx index f60cc6f8a..7f442a72f 100644 --- a/lib/routes/cdzjryb/project-list.ts +++ b/lib/routes/cdzjryb/project-list.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -57,10 +56,7 @@ async function handler() { }); return { title: item[3], - description: art(path.join(__dirname, 'templates/projectList.art'), { - item, - notice: notice.message, - }), + description: renderToString(), link: url, guid: `cdzjryb:zw:projectList:${item[0]}`, pubDate: timezone(parseDate(item[8]), 8), @@ -75,3 +71,35 @@ async function handler() { item: items, }; } + +const ProjectListDescription = ({ item, notice }: { item: string[]; notice: string }) => { + const details = item.slice(2, -1); + + return ( + <> + + + + + + + + + + + + + + + + + {details.map((value) => ( + + ))} + +
    区域项目名称预售证号预售范围住房套数开发商咨询电话登记开始时间登记结束时间名单外人员资格已释放时间名单内人员资格已释放时间预审码取得截止时间项目报名状态
    {value}
    +

    登记规则

    + {raw(notice)} + + ); +}; diff --git a/lib/routes/cdzjryb/templates/projectList.art b/lib/routes/cdzjryb/templates/projectList.art deleted file mode 100644 index 52b02f201..000000000 --- a/lib/routes/cdzjryb/templates/projectList.art +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - <% for(let i = 2; i < item.length - 1; i++){ %> - - <% } %> - -
    区域项目名称预售证号预售范围住房套数开发商咨询电话登记开始时间登记结束时间名单外人员资格已释放时间名单内人员资格已释放时间预审码取得截止时间项目报名状态
    <%= item[i] %>
    -

    登记规则

    -{{@ notice }} diff --git a/lib/routes/cebbank/all.ts b/lib/routes/cebbank/all.tsx similarity index 77% rename from lib/routes/cebbank/all.ts rename to lib/routes/cebbank/all.tsx index 8fc204ea9..66466fcaf 100644 --- a/lib/routes/cebbank/all.ts +++ b/lib/routes/cebbank/all.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import md5 from '@/utils/md5'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -51,12 +49,7 @@ async function handler(ctx) { const c = load(e, { decodeEntities: false }); return { title: c('td:nth-child(1)').text(), - description: art(path.join(__dirname, 'templates/allDes.art'), { - fcer: c('td:nth-child(2)').text(), - pmc: c('td:nth-child(3)').text(), - exrt: c('td:nth-child(4)').text(), - mc: c('td:nth-child(5)').text(), - }), + description: renderToString(), pubDate: timezone(parseDate($('#t_id span').text().slice(5), 'YYYY-MM-DD HH:mm', true), 8), guid: md5(c('td:nth-child(1)').text() + $('#t_id span').text().slice(5)), }; @@ -75,3 +68,14 @@ async function handler(ctx) { }); return ret; } + +const CebbankRateDescription = ({ fcer, pmc, exrt, mc }: { fcer: string; pmc: string; exrt: string; mc: string }) => ( + <> +

    + 购汇:{fcer},购钞:{pmc} +

    +

    + 结汇: {exrt},结钞:{mc} +

    + +); diff --git a/lib/routes/cebbank/history.ts b/lib/routes/cebbank/history.tsx similarity index 77% rename from lib/routes/cebbank/history.ts rename to lib/routes/cebbank/history.tsx index 5dc11efaf..c80a095b5 100644 --- a/lib/routes/cebbank/history.ts +++ b/lib/routes/cebbank/history.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import utils from './utils'; @@ -55,13 +53,9 @@ async function handler(ctx) { const c = load(e, { decodeEntities: false }); return { title: c('td:nth-child(1)').text(), - description: art(path.join(__dirname, 'templates/historyDes.art'), { - fcer: c('td:nth-child(2)').text(), - pmc: c('td:nth-child(3)').text(), - exrt: c('td:nth-child(4)').text(), - mc: c('td:nth-child(5)').text(), - time: c('td:nth-child(6)').text(), - }), + description: renderToString( + + ), }; }); items.pop(); @@ -75,3 +69,15 @@ async function handler(ctx) { ctx.set('json', ret); return ret; } + +const CebbankHistoryDescription = ({ time, fcer, pmc, exrt, mc }: { time: string; fcer: string; pmc: string; exrt: string; mc: string }) => ( + <> +

    更新时间: {time}

    +

    + 购汇:{fcer},购钞:{pmc} +

    +

    + 结汇: {exrt},结钞:{mc} +

    + +); diff --git a/lib/routes/cebbank/templates/allDes.art b/lib/routes/cebbank/templates/allDes.art deleted file mode 100644 index 4df396626..000000000 --- a/lib/routes/cebbank/templates/allDes.art +++ /dev/null @@ -1,2 +0,0 @@ -

    购汇:{{ fcer }},购钞:{{ pmc }}

    -

    结汇: {{ exrt }},结钞:{{ mc }}

    \ No newline at end of file diff --git a/lib/routes/cebbank/templates/historyDes.art b/lib/routes/cebbank/templates/historyDes.art deleted file mode 100644 index e6c5697b6..000000000 --- a/lib/routes/cebbank/templates/historyDes.art +++ /dev/null @@ -1,3 +0,0 @@ -

    更新时间: {{ time }} -

    购汇:{{ fcer }},购钞:{{ pmc }}

    -

    结汇: {{ exrt }},结钞:{{ mc }}

    \ No newline at end of file diff --git a/lib/routes/chaincatcher/home.ts b/lib/routes/chaincatcher/home.tsx similarity index 74% rename from lib/routes/chaincatcher/home.ts rename to lib/routes/chaincatcher/home.tsx index 619c9e72e..ad89be87a 100644 --- a/lib/routes/chaincatcher/home.ts +++ b/lib/routes/chaincatcher/home.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://www.chaincatcher.com'; @@ -47,10 +46,17 @@ async function handler() { if (item.categoryId !== 3) { const { data: response } = await got(item.link); const $ = load(response); - item.description = art(path.join(__dirname, 'templates/home.art'), { - summary: item.description, - article: $('.article-container').html(), - }); + item.description = renderToString( + <> + {item.description ? ( + <> +
    {item.description}
    +
    + + ) : null} + {$('.article-container').html() ? raw($('.article-container').html() as string) : null} + + ); } return item; diff --git a/lib/routes/chaincatcher/templates/home.art b/lib/routes/chaincatcher/templates/home.art deleted file mode 100644 index 9d419d8ad..000000000 --- a/lib/routes/chaincatcher/templates/home.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ if summary }} -
    {{ summary }}

    -{{ /if }} -{{@ article }} diff --git a/lib/routes/changba/templates/work_description.art b/lib/routes/changba/templates/work_description.art deleted file mode 100644 index bd033c34c..000000000 --- a/lib/routes/changba/templates/work_description.art +++ /dev/null @@ -1,2 +0,0 @@ -

    {{ desc }}

    - \ No newline at end of file diff --git a/lib/routes/changba/user.ts b/lib/routes/changba/user.tsx similarity index 90% rename from lib/routes/changba/user.ts rename to lib/routes/changba/user.tsx index 8886cc81a..c601b2d9e 100644 --- a/lib/routes/changba/user.ts +++ b/lib/routes/changba/user.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const headers = { 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1' }; @@ -78,10 +76,7 @@ async function handler(ctx) { return null; } const mp3 = `https://upscuw.changba.com/${workid}.mp3`; - const description = art(path.join(__dirname, 'templates/work_description.art'), { - desc: $('div.des').text(), - mp3url: mp3, - }); + const description = renderToString(); const itunes_item_image = $('div.work-cover').attr('style').replace(')', '').split('url(')[1]; return { title: $('.work-title').text(), @@ -108,3 +103,10 @@ async function handler(ctx) { itunes_category: '唱吧', }; } + +const ChangbaWorkDescription = ({ desc, mp3url }: { desc: string; mp3url: string }) => ( + <> +

    {desc}

    + + +); diff --git a/lib/routes/chaoxing/qk.ts b/lib/routes/chaoxing/qk.tsx similarity index 94% rename from lib/routes/chaoxing/qk.ts rename to lib/routes/chaoxing/qk.tsx index 0381ae90a..9fe9c5020 100644 --- a/lib/routes/chaoxing/qk.ts +++ b/lib/routes/chaoxing/qk.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/qk/:id/:needContent?', @@ -62,9 +60,7 @@ async function handler(ctx) { link: item.infos.read, category: [item.infos.C314, item.infos.C031], pubDate: parseDate(item.infos.C103, 'YYYYMMDD'), - description: art(path.join(__dirname, 'templates/description.art'), { - description: (item.infos.M305 ?? item.infos.C305 ?? '').trim(), - }), + description: renderToString(<>{(item.infos.M305 ?? item.infos.C305 ?? '').trim() ?

    {(item.infos.M305 ?? item.infos.C305 ?? '').trim()}

    : null}), })); items = await Promise.all( diff --git a/lib/routes/chaoxing/templates/description.art b/lib/routes/chaoxing/templates/description.art deleted file mode 100644 index 1053a98f8..000000000 --- a/lib/routes/chaoxing/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ if description }} -

    {{ description }}

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/chinacdc/index.ts b/lib/routes/chinacdc/index.ts index e541dc9de..b03b8b823 100644 --- a/lib/routes/chinacdc/index.ts +++ b/lib/routes/chinacdc/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'zxyw' } = ctx.req.param(); @@ -44,7 +43,7 @@ export const handler = async (ctx: Context): Promise => { pubDate = spanText ? parseDate(spanText) : undefined; } - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ intro: $item.find('p.zy').text(), }); @@ -85,8 +84,8 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const detailTitle: string = $$('h5').text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.TRS_Editor').html(), + const description: string = renderDescription({ + description: $$('div.TRS_Editor').html() || undefined, }); const detailDate = $$('span.fb em').text().trim(); diff --git a/lib/routes/chinacdc/templates/description.art b/lib/routes/chinacdc/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/chinacdc/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/chinacdc/templates/description.tsx b/lib/routes/chinacdc/templates/description.tsx new file mode 100644 index 000000000..5d54604fb --- /dev/null +++ b/lib/routes/chinacdc/templates/description.tsx @@ -0,0 +1,15 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + intro?: string; + description?: string; +}; + +export const renderDescription = ({ intro, description }: DescriptionProps): string => + renderToString( + <> + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/chinadaily/language.ts b/lib/routes/chinadaily/language.ts index a6f2a3d64..7bddbd827 100644 --- a/lib/routes/chinadaily/language.ts +++ b/lib/routes/chinadaily/language.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { category = 'thelatest' } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -36,7 +35,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.text(); const image: string | undefined = $el.find('a.gy_box_img img, a.a_img img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -126,8 +125,8 @@ export const handler = async (ctx: Context): Promise => { const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div#Content').html(), + renderDescription({ + description: $$('div#Content').html() ?? undefined, }); processedItem = { diff --git a/lib/routes/chinadaily/templates/description.art b/lib/routes/chinadaily/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/chinadaily/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/chinadaily/templates/description.tsx b/lib/routes/chinadaily/templates/description.tsx new file mode 100644 index 000000000..f7661df1e --- /dev/null +++ b/lib/routes/chinadaily/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/chinadegrees/province.ts b/lib/routes/chinadegrees/province.tsx similarity index 89% rename from lib/routes/chinadegrees/province.ts rename to lib/routes/chinadegrees/province.tsx index 8a762d1c6..3638ed5f5 100644 --- a/lib/routes/chinadegrees/province.ts +++ b/lib/routes/chinadegrees/province.tsx @@ -1,16 +1,28 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; -import { art } from '@/utils/render'; const baseUrl = 'http://www.chinadegrees.com.cn'; +const renderDescription = (title, pubDate) => + renderToString( + + + + + + + + + +
    学位授予单位名称最新上网批次
    {title}{pubDate}
    + ); + export const route: Route = { path: '/:province?', categories: ['study'], @@ -108,10 +120,7 @@ async function handler(ctx) { ); const items = data.items.map((item) => { - item.description = art(path.join(__dirname, 'templates/description.art'), { - title: item.title, - pubDate: item.pubDate, - }); + item.description = renderDescription(item.title, item.pubDate); item.pubDate = parseDate(item.pubDate, 'YYYY-MM-DD'); return item; }); diff --git a/lib/routes/chinadegrees/templates/description.art b/lib/routes/chinadegrees/templates/description.art deleted file mode 100644 index 590f40600..000000000 --- a/lib/routes/chinadegrees/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ - - - -
    学位授予单位名称最新上网批次
    {{ title }}{{ pubDate }}
    diff --git a/lib/routes/chuanliu/nice.ts b/lib/routes/chuanliu/nice.tsx similarity index 84% rename from lib/routes/chuanliu/nice.ts rename to lib/routes/chuanliu/nice.tsx index 9982f44ac..ecd758138 100644 --- a/lib/routes/chuanliu/nice.ts +++ b/lib/routes/chuanliu/nice.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import MarkdownIt from 'markdown-it'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const md = MarkdownIt({ html: true, @@ -68,14 +67,12 @@ async function handler(ctx) { return { title: `${isStar ? '[STAR] ' : ''}${title}`, link, - description: art(path.join(__dirname, 'templates/description.art'), { - description: md.render(contents?.join('\n\n') ?? ''), - images: item.resourceList.map((i) => ({ - src: i.externalLink, - alt: i.filename, - type: i.type, - })), - }), + description: renderToString( + <> + {item.resourceList.map((resource) => (resource.externalLink ?
    {resource.filename ? {resource.filename} : }
    : null))} + {raw(md.render(contents?.join('\n\n') ?? ''))} + + ), author, category: [category, isStar ? 'STAR' : undefined].filter(Boolean), guid: `chuanliu-nice#${item.id}`, diff --git a/lib/routes/chuanliu/templates/description.art b/lib/routes/chuanliu/templates/description.art deleted file mode 100644 index dfd22a693..000000000 --- a/lib/routes/chuanliu/templates/description.art +++ /dev/null @@ -1,23 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/cloudflarestatus/index.ts b/lib/routes/cloudflarestatus/index.tsx similarity index 90% rename from lib/routes/cloudflarestatus/index.ts rename to lib/routes/cloudflarestatus/index.tsx index 8c1611a7f..630e00ff6 100644 --- a/lib/routes/cloudflarestatus/index.ts +++ b/lib/routes/cloudflarestatus/index.tsx @@ -1,15 +1,14 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10); @@ -34,10 +33,12 @@ export const handler = async (ctx: Context): Promise => { const text: string = $el.find('span.whitespace-pre-wrap').first().text(); const title: string = `${type ? `${type} - ` : ''}${text}`; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - actualTitle, - description: $el.html(), - }); + const description: string | undefined = renderToString( + <> + {actualTitle ?

    {actualTitle}

    : null} + {$el.html() ? raw($el.html()) : null} + + ); const pubDateStr: string | undefined = $el.find('span.ago').attr('data-datetime-unix'); const linkUrl: string | undefined = $actualTitleEl.attr('href') ? new URL($actualTitleEl.attr('href') as string, baseUrl).toString() : undefined; const categories: string[] = [type].filter(Boolean); diff --git a/lib/routes/cloudflarestatus/templates/description.art b/lib/routes/cloudflarestatus/templates/description.art deleted file mode 100644 index 24fcca6fb..000000000 --- a/lib/routes/cloudflarestatus/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if actualTitle }} -

    {{ actualTitle}}

    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/cls/depth.ts b/lib/routes/cls/depth.ts index 28330d50d..89de631a8 100644 --- a/lib/routes/cls/depth.ts +++ b/lib/routes/cls/depth.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import InvalidParameterError from '@/errors/types/invalid-parameter'; @@ -7,8 +5,8 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; +import { renderDepthDescription } from './templates/depth'; import { getSearchParams, rootUrl } from './utils'; const categories = { @@ -90,9 +88,7 @@ async function handler(ctx) { const articleDetail = nextData.props.initialState.detail.articleDetail; item.author = articleDetail.author?.name ?? item.author ?? ''; - item.description = art(path.join(__dirname, 'templates/depth.art'), { - articleDetail, - }); + item.description = renderDepthDescription(articleDetail); return item; }) diff --git a/lib/routes/cls/hot.ts b/lib/routes/cls/hot.ts index 88c2ab1d5..0c34cc614 100644 --- a/lib/routes/cls/hot.ts +++ b/lib/routes/cls/hot.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; +import { renderDepthDescription } from './templates/depth'; import { getSearchParams, rootUrl } from './utils'; export const route: Route = { @@ -65,9 +63,7 @@ async function handler(ctx) { const articleDetail = nextData.props.initialState.detail.articleDetail; item.author = articleDetail.author?.name ?? item.author ?? ''; - item.description = art(path.join(__dirname, 'templates/depth.art'), { - articleDetail, - }); + item.description = renderDepthDescription(articleDetail); return item; }) diff --git a/lib/routes/cls/subject.ts b/lib/routes/cls/subject.ts index cd92e10aa..b8ad2cd45 100644 --- a/lib/routes/cls/subject.ts +++ b/lib/routes/cls/subject.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; +import { renderDescription } from './templates/description'; import { getSearchParams, rootUrl } from './utils'; export const handler = async (ctx) => { @@ -25,7 +23,7 @@ export const handler = async (ctx) => { let items = response.data.slice(0, limit).map((item) => { const title = item.article_title; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ intro: item.article_brief, }); const guid = `cls-${item.article_id}`; @@ -63,7 +61,7 @@ export const handler = async (ctx) => { } const title = data.title; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: data.images.map((i) => ({ src: i, alt: title, diff --git a/lib/routes/cls/telegraph.ts b/lib/routes/cls/telegraph.tsx similarity index 81% rename from lib/routes/cls/telegraph.ts rename to lib/routes/cls/telegraph.tsx index 46f034cac..a2c25a28e 100644 --- a/lib/routes/cls/telegraph.ts +++ b/lib/routes/cls/telegraph.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { getSearchParams, rootUrl } from './utils'; @@ -18,6 +17,21 @@ const categories = { hk: '港股', }; +const renderTelegraphDescription = (item) => + renderToString( + <> + {item.content ? <>{item.content} : null} + {item.images?.length ? ( + <> +
    + {item.images.map((image) => ( + + ))} + + ) : null} + + ); + export const route: Route = { path: '/telegraph/:category?', categories: ['finance'], @@ -69,9 +83,7 @@ async function handler(ctx) { const items = response.data.data.roll_data.slice(0, limit).map((item) => ({ title: item.title || item.content, link: item.shareurl, - description: art(path.join(__dirname, 'templates/telegraph.art'), { - item, - }), + description: renderTelegraphDescription(item), pubDate: parseDate(item.ctime * 1000), category: item.subjects?.map((s) => s.subject_name), })); diff --git a/lib/routes/cls/templates/depth.art b/lib/routes/cls/templates/depth.art deleted file mode 100644 index 615a7db30..000000000 --- a/lib/routes/cls/templates/depth.art +++ /dev/null @@ -1,10 +0,0 @@ -{{ if articleDetail.images }} -{{ each articleDetail.images i }} - -{{ /each }} -
    -{{ /if }} - -{{ if articleDetail.content }} -{{@ articleDetail.content }} -{{ /if }} diff --git a/lib/routes/cls/templates/depth.tsx b/lib/routes/cls/templates/depth.tsx new file mode 100644 index 000000000..d158a1c68 --- /dev/null +++ b/lib/routes/cls/templates/depth.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ArticleDetail = { + images?: string[]; + content?: string; +}; + +export const renderDepthDescription = (articleDetail: ArticleDetail) => + renderToString( + <> + {articleDetail.images?.length ? ( + <> + {articleDetail.images.map((image) => ( + + ))} +
    + + ) : null} + {articleDetail.content ? <>{raw(articleDetail.content)} : null} + + ); diff --git a/lib/routes/cls/templates/description.art b/lib/routes/cls/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/cls/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/cls/templates/description.tsx b/lib/routes/cls/templates/description.tsx new file mode 100644 index 000000000..2e77b346f --- /dev/null +++ b/lib/routes/cls/templates/description.tsx @@ -0,0 +1,30 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData) => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/cls/templates/telegraph.art b/lib/routes/cls/templates/telegraph.art deleted file mode 100644 index f7aa26705..000000000 --- a/lib/routes/cls/templates/telegraph.art +++ /dev/null @@ -1,10 +0,0 @@ -{{ if item.content }} -{{ item.content }} -{{ /if }} - -{{ if item.images.length !== 0 }} -
    -{{ each item.images i }} - -{{ /each }} -{{ /if }} diff --git a/lib/routes/cma/channel.ts b/lib/routes/cma/channel.tsx similarity index 87% rename from lib/routes/cma/channel.ts rename to lib/routes/cma/channel.tsx index a24f5ea29..a41b0784b 100644 --- a/lib/routes/cma/channel.ts +++ b/lib/routes/cma/channel.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -82,7 +81,7 @@ async function handler(ctx) { .map((li) => $(li).text()) ), ].join(' > '); - const description = $('div.xml').html(); + const descriptionHtml = $('div.xml').html(); const image = new URL($('li.active a img').prop('src'), rootUrl).href; const icon = new URL($('link[rel="shortcut icon"]').prop('href'), rootUrl).href; @@ -91,15 +90,16 @@ async function handler(ctx) { { title: `${data.title} ${data.releaseTime}`, link: new URL(data.link, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - description, - image: data.image - ? { - src: new URL(data.image, rootUrl).href, - alt: data.title, - } - : undefined, - }), + description: renderToString( + <> + {data.image ? ( +
    + {data.title} +
    + ) : null} + {descriptionHtml ? raw(descriptionHtml) : null} + + ), author: $( $('div.col-xs-8 span') diff --git a/lib/routes/cma/templates/description.art b/lib/routes/cma/templates/description.art deleted file mode 100644 index c1af0bff7..000000000 --- a/lib/routes/cma/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/cngal/entry.ts b/lib/routes/cngal/entry.tsx similarity index 81% rename from lib/routes/cngal/entry.ts rename to lib/routes/cngal/entry.tsx index 4929fd8a8..a9edfe536 100644 --- a/lib/routes/cngal/entry.ts +++ b/lib/routes/cngal/entry.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -42,9 +41,17 @@ async function handler(ctx) { link: `https://www.cngal.org/entries/index/${entryId}`, item: data.newsOfEntry.map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/entry-description.art'), item), + description: renderDescription(item), pubDate: timezone(parseDate(item.happenedTime), +8), link: item.link, })), }; } + +const renderDescription = (item): string => + renderToString( + <> +

    {item.briefIntroduction}

    + {item.image ? : null} + + ); diff --git a/lib/routes/cngal/templates/entry-description.art b/lib/routes/cngal/templates/entry-description.art deleted file mode 100644 index 4d12268e3..000000000 --- a/lib/routes/cngal/templates/entry-description.art +++ /dev/null @@ -1,4 +0,0 @@ -

    {{ briefIntroduction }}

    -{{ if image }} - -{{ /if }} diff --git a/lib/routes/cngal/templates/weekly-description.art b/lib/routes/cngal/templates/weekly-description.art deleted file mode 100644 index cb52f1c36..000000000 --- a/lib/routes/cngal/templates/weekly-description.art +++ /dev/null @@ -1,4 +0,0 @@ -

    {{@ briefIntroduction.trim().replace(/\n/g, '
    ') }}

    -{{ if mainImage }} - -{{ /if }} diff --git a/lib/routes/cngal/weekly.ts b/lib/routes/cngal/weekly.tsx similarity index 71% rename from lib/routes/cngal/weekly.ts rename to lib/routes/cngal/weekly.tsx index 82c1ef7b2..9716c493d 100644 --- a/lib/routes/cngal/weekly.ts +++ b/lib/routes/cngal/weekly.tsx @@ -1,10 +1,10 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/weekly', @@ -39,9 +39,20 @@ async function handler() { link: 'https://www.cngal.org/weeklynews', item: response.data.map((item) => ({ title: item.name, - description: art(path.join(__dirname, 'templates/weekly-description.art'), item), + description: renderDescription(item), pubDate: parseDate(item.lastEditTime), link: `https://www.cngal.org/articles/index/${item.id}`, })), }; } + +const renderDescription = (item): string => { + const intro = item.briefIntroduction ? item.briefIntroduction.trim().replaceAll('\n', '
    ') : ''; + + return renderToString( + <> +

    {intro ? raw(intro) : null}

    + {item.mainImage ? : null} + + ); +}; diff --git a/lib/routes/cnjxol/index.ts b/lib/routes/cnjxol/index.tsx similarity index 85% rename from lib/routes/cnjxol/index.ts rename to lib/routes/cnjxol/index.tsx index ba41317fc..c8dcb7fd5 100644 --- a/lib/routes/cnjxol/index.ts +++ b/lib/routes/cnjxol/index.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const categories = { jxrb: '嘉兴日报', @@ -88,14 +87,18 @@ async function handler(ctx) { const content = load(detailResponse.data); + const attachment = content('.attachment').html(); + const contentHtml = content('founder-content').html(); return { link: item, title: content('#Title').text(), pubDate: parseDate(content('date').text()), - description: art(path.join(__dirname, 'templates/description.art'), { - attachment: content('.attachment').html(), - content: content('founder-content').html(), - }), + description: renderToString( + <> + {attachment ? raw(attachment) : null} + {contentHtml ? raw(contentHtml) : null} + + ), }; }) ) diff --git a/lib/routes/cnjxol/templates/description.art b/lib/routes/cnjxol/templates/description.art deleted file mode 100644 index 80ea21796..000000000 --- a/lib/routes/cnjxol/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{@ attachment }} -{{@ content }} \ No newline at end of file diff --git a/lib/routes/cnki/debut.ts b/lib/routes/cnki/debut.ts index 0e5c6a722..a269a73fd 100644 --- a/lib/routes/cnki/debut.ts +++ b/lib/routes/cnki/debut.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/desc'; const rootUrl = 'https://chn.oversea.cnki.net'; @@ -64,7 +63,7 @@ async function handler(ctx) { cache.tryGet(item.link, async () => { const detailResponse = await got.get(item.link); const $ = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/desc.art'), { + item.description = renderDescription({ author: $('h3.author > span') .toArray() .map((item) => $(item).text()) diff --git a/lib/routes/cnki/templates/desc.art b/lib/routes/cnki/templates/desc.art deleted file mode 100644 index 5a539b173..000000000 --- a/lib/routes/cnki/templates/desc.art +++ /dev/null @@ -1,5 +0,0 @@ -作者:{{ author }} -
    -单位:{{ company }} -
    -{{ content }} diff --git a/lib/routes/cnki/templates/desc.tsx b/lib/routes/cnki/templates/desc.tsx new file mode 100644 index 000000000..759c01afb --- /dev/null +++ b/lib/routes/cnki/templates/desc.tsx @@ -0,0 +1,18 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + author: string; + company: string; + content: string; +}; + +export const renderDescription = ({ author, company, content }: DescriptionData): string => + renderToString( + <> + {`作者:${author} `} +
    + {`单位:${company} `} +
    + {content} + + ); diff --git a/lib/routes/cnki/utils.ts b/lib/routes/cnki/utils.ts index 1f55c34e4..3e70a2cb9 100644 --- a/lib/routes/cnki/utils.ts +++ b/lib/routes/cnki/utils.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/desc'; const ProcessItem = async (item) => { const detailResponse = await got(item.link); const $ = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/desc.art'), { + item.description = renderDescription({ author: $('h3.author > span') .toArray() .map((item) => $(item).text()) diff --git a/lib/routes/cntheory/paper.ts b/lib/routes/cntheory/paper.tsx similarity index 86% rename from lib/routes/cntheory/paper.ts rename to lib/routes/cntheory/paper.tsx index 159a4b324..ea3758051 100644 --- a/lib/routes/cntheory/paper.ts +++ b/lib/routes/cntheory/paper.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/paper/:id?', @@ -92,10 +91,12 @@ async function handler(ctx) { .attr('href') .match(/(\/images.*)/)[1] }`, - description: art(path.join(__dirname, 'templates/description.art'), { - resource: content('#reslist').html().replaceAll('display:none;', ''), - description: content('founder-content').html(), - }), + description: renderToString( + <> + {content('#reslist').html() ? raw(content('#reslist').html().replaceAll('display:none;', '')) : null} + {content('founder-content').html() ? raw(content('founder-content').html()) : null} + + ), }; }) ) diff --git a/lib/routes/cntheory/templates/description.art b/lib/routes/cntheory/templates/description.art deleted file mode 100644 index 9294c48ed..000000000 --- a/lib/routes/cntheory/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{@ resource }} -{{@ description }} diff --git a/lib/routes/cntv/column.ts b/lib/routes/cntv/column.tsx similarity index 80% rename from lib/routes/cntv/column.ts rename to lib/routes/cntv/column.tsx index c73c89420..3f02793ed 100644 --- a/lib/routes/cntv/column.ts +++ b/lib/routes/cntv/column.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:column', @@ -55,9 +54,18 @@ async function handler(ctx) { description: `${name} 栏目的视频更新`, item: data.map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/column.art'), { - item, - }), + description: renderToString( + <> +

    {item.brief}

    +

    时长:{item.length}

    +

    + +

    +

    + 在线观看 +

    + + ), pubDate: parseDate(item.time), link: item.url, })), diff --git a/lib/routes/cntv/templates/column.art b/lib/routes/cntv/templates/column.art deleted file mode 100644 index 3b127ecb4..000000000 --- a/lib/routes/cntv/templates/column.art +++ /dev/null @@ -1,4 +0,0 @@ -

    {{ item.brief }}

    -

    时长:{{ item.length }}

    -

    -

    在线观看

    diff --git a/lib/routes/codeforces/contests.ts b/lib/routes/codeforces/contests.tsx similarity index 81% rename from lib/routes/codeforces/contests.ts rename to lib/routes/codeforces/contests.tsx index dd16d8c45..fa1c9f686 100644 --- a/lib/routes/codeforces/contests.ts +++ b/lib/routes/codeforces/contests.tsx @@ -1,15 +1,13 @@ import 'dayjs/locale/zh-cn.js'; -import path from 'node:path'; - import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration.js'; import localizedFormat from 'dayjs/plugin/localizedFormat.js'; import relativeTime from 'dayjs/plugin/relativeTime.js'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; dayjs.extend(localizedFormat); dayjs.extend(duration); @@ -53,13 +51,14 @@ async function handler() { .map((contest) => { const title = String(contest.name); const date = dayjs.unix(Number.parseInt(contest.startTimeSeconds)); - const description = art(path.join(__dirname, 'templates/contest.art'), { - title, - startTime: date.format('LL LT'), - durationTime: sec2str(contest.durationSeconds), - // relativeTime: sec2str(contest.relativeTimeSeconds), - type: contest.type, - }); + const description = renderToString( + <> +

    比赛:{title}

    +

    开始时间:{date.format('LL LT')}

    +

    持续时间:{sec2str(contest.durationSeconds)}

    +

    比赛类型:{contest.type}

    + + ); return { title, diff --git a/lib/routes/codeforces/templates/contest.art b/lib/routes/codeforces/templates/contest.art deleted file mode 100644 index 81db4a165..000000000 --- a/lib/routes/codeforces/templates/contest.art +++ /dev/null @@ -1,5 +0,0 @@ -

    比赛:{{ title }}

    -

    开始时间:{{ startTime }}

    -

    持续时间:{{ durationTime }}

    - -

    比赛类型:{{ type }}

    diff --git a/lib/routes/comicskingdom/index.ts b/lib/routes/comicskingdom/index.tsx similarity index 92% rename from lib/routes/comicskingdom/index.ts rename to lib/routes/comicskingdom/index.tsx index 661e14890..4fbde3f71 100644 --- a/lib/routes/comicskingdom/index.ts +++ b/lib/routes/comicskingdom/index.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:name', @@ -60,9 +58,7 @@ async function handler(ctx) { const title = content('meta[property="og:description"]').attr('content'); const image = content('meta[property="og:image"]').attr('content'); - const description = art(path.join(__dirname, 'templates/desc.art'), { - image, - }); + const description = renderToString(); // Pull the date out of the URL const pubDate = parseDate(link.slice(link.lastIndexOf('/') + 1), 'YYYY-MM-DD'); diff --git a/lib/routes/comicskingdom/templates/desc.art b/lib/routes/comicskingdom/templates/desc.art deleted file mode 100644 index 81223ed27..000000000 --- a/lib/routes/comicskingdom/templates/desc.art +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/routes/commonhealth/index.ts b/lib/routes/commonhealth/index.tsx similarity index 81% rename from lib/routes/commonhealth/index.ts rename to lib/routes/commonhealth/index.tsx index 297c09242..ee2edcece 100644 --- a/lib/routes/commonhealth/index.ts +++ b/lib/routes/commonhealth/index.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const apiKey = 'Cah2snYi52eJjpshbIfof1Tpx8ZhzXqh'; @@ -49,10 +48,7 @@ async function handler() { }); const items = response.items.list.map((item) => { - const description = art(path.join(__dirname, 'templates/description.art'), { - image: item.image, - preface: item.preface, - }); + const description = renderDescription(item.image, item.preface); return { title: item.title, @@ -69,3 +65,11 @@ async function handler() { item: items, }; } + +const renderDescription = (image: string, preface: string): string => + renderToString( + <> + article image +

    {preface}

    + + ); diff --git a/lib/routes/commonhealth/templates/description.art b/lib/routes/commonhealth/templates/description.art deleted file mode 100644 index 660f54f79..000000000 --- a/lib/routes/commonhealth/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -article image -

    {{ preface }}

    diff --git a/lib/routes/coolbuy/index.ts b/lib/routes/coolbuy/index.tsx similarity index 58% rename from lib/routes/coolbuy/index.ts rename to lib/routes/coolbuy/index.tsx index 5957f53ba..8770c527e 100644 --- a/lib/routes/coolbuy/index.ts +++ b/lib/routes/coolbuy/index.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10); @@ -34,17 +32,49 @@ export const handler = async (ctx: Context): Promise => { const image: string | undefined = item.cover_image?.split(/\?/)?.[0]; const banner: string | undefined = item.display_image?.split(/\?/)?.[0]; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - summary: item.summary, - price: item.price, - original_price: item.original_price, - highest_price: item.highest_price, - highest_original_price: item.highest_original_price, - images: [banner, image].filter(Boolean).map((image) => ({ - src: image, - alt: title, - })), - }); + const images = [banner, image].filter(Boolean).map((image) => ({ + src: image, + alt: title, + })); + const description: string | undefined = renderToString( + <> + + + {item.summary ? ( + + + + + ) : null} + {item.price ? ( + + + + + ) : null} + {item.original_price ? ( + + + + + ) : null} + {item.highest_price ? ( + + + + + ) : null} + {item.highest_original_price ? ( + + + + + ) : null} + +
    简介{item.summary}
    价格{item.price}
    原价{item.original_price}
    价格(最高){item.highest_price}
    原价(最高){item.highest_original_price}
    + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + + ); const linkUrl: string | undefined = item.visit_url; const guid: string = `coolbuy-${item.id}#${item.price}`; diff --git a/lib/routes/coolbuy/templates/description.art b/lib/routes/coolbuy/templates/description.art deleted file mode 100644 index 616336bc0..000000000 --- a/lib/routes/coolbuy/templates/description.art +++ /dev/null @@ -1,48 +0,0 @@ - - - {{ if summary }} - - - - - {{ /if }} - {{ if price }} - - - - - {{ /if }} - {{ if original_price }} - - - - - {{ /if }} - {{ if highest_price }} - - - - - {{ /if }} - {{ if highest_original_price }} - - - - - {{ /if }} - -
    简介{{ summary }}
    价格{{ price }}
    原价{{ original_price }}
    价格(最高){{ highest_price }}
    原价(最高){{ highest_original_price }}
    - -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/coolidge/film-guide.ts b/lib/routes/coolidge/film-guide.ts index c0ffad2a6..a7cbed325 100644 --- a/lib/routes/coolidge/film-guide.ts +++ b/lib/routes/coolidge/film-guide.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const handler = async () => { const link = 'https://coolidge.org/film-guide'; @@ -22,7 +21,7 @@ const handler = async () => { const absoluteCover = cover ? new URL(cover, link).href : undefined; const absoluteItemLink = itemLink ? new URL(itemLink, link).href : undefined; - const rendered = art(path.join(__dirname, 'templates/description.art'), { + const rendered = renderDescription({ image: absoluteCover, intro: description, }); diff --git a/lib/routes/coolidge/news.ts b/lib/routes/coolidge/news.ts index 4be351d69..3209db5e8 100644 --- a/lib/routes/coolidge/news.ts +++ b/lib/routes/coolidge/news.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const handler = async () => { const link = 'https://coolidge.org/about-us/news-media'; @@ -26,7 +25,7 @@ const handler = async () => { const absoluteLink = href ? new URL(href, link).href : undefined; const absoluteImage = imageSrc ? new URL(imageSrc, link).href : undefined; - const rendered = art(path.join(__dirname, 'templates/description.art'), { + const rendered = renderDescription({ image: absoluteImage, intro: descriptionText, }); diff --git a/lib/routes/coolidge/templates/description.art b/lib/routes/coolidge/templates/description.art deleted file mode 100644 index 3afa8351d..000000000 --- a/lib/routes/coolidge/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{if image}} -

    -{{/if}} -

    {{ intro }}

    diff --git a/lib/routes/coolidge/templates/description.tsx b/lib/routes/coolidge/templates/description.tsx new file mode 100644 index 000000000..9d8a4da81 --- /dev/null +++ b/lib/routes/coolidge/templates/description.tsx @@ -0,0 +1,19 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: string; + intro?: string; +}; + +const CoolidgeDescription = ({ image, intro }: DescriptionData) => ( + <> + {image ? ( +

    + +

    + ) : null} +

    {intro}

    + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/coomer/index.ts b/lib/routes/coomer/index.tsx similarity index 71% rename from lib/routes/coomer/index.ts rename to lib/routes/coomer/index.tsx index 48b8757bb..f2ba15ddb 100644 --- a/lib/routes/coomer/index.ts +++ b/lib/routes/coomer/index.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const headers = { Accept: 'text/css' }; @@ -87,7 +85,7 @@ async function handler(ctx) { extension: attachment.path.replace(/.*\./, '').toLowerCase(), }); } - const filesHTML = art(path.join(__dirname, 'templates/source.art'), { i }); + const filesHTML = renderSource(i); let $ = load(filesHTML); const coomerFiles = $('img, a, audio, video').map(function () { return $(this).prop('outerHTML')!; @@ -150,6 +148,55 @@ async function handler(ctx) { }; } +const renderSource = (item): string => + renderToString( + <> + {item.files?.map((file, index) => { + if (['jpg', 'png', 'webp', 'jpeg', 'jfif'].includes(file.extension)) { + return ; + } + + if (['m4a', 'mp3', 'ogg'].includes(file.extension)) { + return ( + + ); + } + + if (['mp4', 'webm'].includes(file.extension)) { + return ( + + ); + } + + return ( + + {file.name} + + ); + })} + {item.embed ? ( + <> + {item.embed.type === 'image' ? : null} + {item.embed.type === 'link' ? ( + <> + {item.embed.thumbnail ? ( + + + + ) : null} + {item.embed.title} + {item.embed.description ?

    {item.embed.description}

    : null} + + ) : null} + + ) : null} + + ); + async function getAuthor(currentUrl) { const profileResponse = await got({ method: 'get', diff --git a/lib/routes/coomer/templates/source.art b/lib/routes/coomer/templates/source.art deleted file mode 100644 index 47f87559e..000000000 --- a/lib/routes/coomer/templates/source.art +++ /dev/null @@ -1,24 +0,0 @@ -{{ if i.files }} - {{ each i.files file }} - {{ if file.extension === 'jpg' || file.extension === 'png' || file.extension === 'webp' || file.extension === 'jpeg' || file.extension === 'jfif' }} - - {{ else if file.extension === 'm4a' || file.extension === 'mp3' || file.extension === 'ogg' }} - - {{ else if file.extension === 'mp4' || file.extension === 'webm' }} - - {{ else }} - {{file.name}} - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if i.embed }} - {{ if i.embed.type === 'image' }} - - {{ else if i.embed.type === 'link' }} - {{ if i.embed.thumbnail }} - - {{ /if }} - {{ i.embed.title }}{{ if i.embed.description }}

    {{ i.embed.description }}

    {{ /if }} - {{ /if }} -{{ /if }} diff --git a/lib/routes/copymanga/comic.ts b/lib/routes/copymanga/comic.tsx similarity index 90% rename from lib/routes/copymanga/comic.ts rename to lib/routes/copymanga/comic.tsx index 29791ccc3..67321cc65 100644 --- a/lib/routes/copymanga/comic.ts +++ b/lib/routes/copymanga/comic.tsx @@ -1,6 +1,5 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import pMap from 'p-map'; import { config } from '@/config'; @@ -8,7 +7,6 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/comic/:id/:chapterCnt?', @@ -118,10 +116,7 @@ async function handler(ctx) { link: chapter.link, guid: chapter.guid, title: chapter.title, - description: art(path.join(__dirname, './templates/comic.art'), { - size: chapter.size, - contents, - }), + description: renderDescription(chapter.size, contents), pubDate: chapter.pubDate, }; }; @@ -136,3 +131,14 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (size: number, contents: Array<{ url: string }>): string => + renderToString( + <> +

    {size}p

    +
    + {contents.map((image, index) => ( + + ))} + + ); diff --git a/lib/routes/copymanga/templates/comic.art b/lib/routes/copymanga/templates/comic.art deleted file mode 100644 index b137f7c71..000000000 --- a/lib/routes/copymanga/templates/comic.art +++ /dev/null @@ -1,4 +0,0 @@ -

    {{ size }}p


    -{{ each contents img }} - -{{ /each }} diff --git a/lib/routes/crac/exam.ts b/lib/routes/crac/exam.ts deleted file mode 100644 index 4d20e718c..000000000 --- a/lib/routes/crac/exam.ts +++ /dev/null @@ -1,61 +0,0 @@ -import path from 'node:path'; - -import type { Route } from '@/types'; -import got from '@/utils/got'; -import { art } from '@/utils/render'; - -export const route: Route = { - path: '/exam', - categories: ['government'], - example: '/crac/exam', - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - name: '考试信息', - maintainers: ['admxj'], - radar: [ - { - source: ['www.crac.org.cn/*'], - target: '/exam', - }, - ], - handler, -}; - -async function handler() { - const baseUrl = 'http://82.157.138.16:8091/CRAC'; - - const response = await got({ - method: 'post', - url: `${baseUrl}/app/exam_advice/examAdviceList`, - body: { req: { type: '0', page_no: '1', page_size: '10' } }, - }); - - const list = response.data.res.list.map((item) => { - const id = Buffer.from(item.id).toString('base64'); - const type = Buffer.from(item.type).toString('base64'); - const link = `${baseUrl}/crac/pages/list_detail.html?id=${id}&type=${type}`; - return { - title: item.name, - link, - id: item.id, - author: item.exam.organizer, - pubDate: item.createDate, - updated: item.updateDate, - startDate: item.exam.signUpStartDate, - category: [item.examType], - image: item.weixin, - description: art(path.join(__dirname, 'templates/exam.art'), { item }), - }; - }); - return { - title: '考试信息-中国无线电协会业余无线电分会', - link: 'http://82.157.138.16:8091/CRAC/crac/pages/list_examMsg.html', - item: list, - }; -} diff --git a/lib/routes/crac/exam.tsx b/lib/routes/crac/exam.tsx new file mode 100644 index 000000000..c26e8e512 --- /dev/null +++ b/lib/routes/crac/exam.tsx @@ -0,0 +1,115 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import got from '@/utils/got'; + +export const route: Route = { + path: '/exam', + categories: ['government'], + example: '/crac/exam', + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: '考试信息', + maintainers: ['admxj'], + radar: [ + { + source: ['www.crac.org.cn/*'], + target: '/exam', + }, + ], + handler, +}; + +async function handler() { + const baseUrl = 'http://82.157.138.16:8091/CRAC'; + + const response = await got({ + method: 'post', + url: `${baseUrl}/app/exam_advice/examAdviceList`, + body: { req: { type: '0', page_no: '1', page_size: '10' } }, + }); + + const list = response.data.res.list.map((item) => { + const id = Buffer.from(item.id).toString('base64'); + const type = Buffer.from(item.type).toString('base64'); + const link = `${baseUrl}/crac/pages/list_detail.html?id=${id}&type=${type}`; + return { + title: item.name, + link, + id: item.id, + author: item.exam.organizer, + pubDate: item.createDate, + updated: item.updateDate, + startDate: item.exam.signUpStartDate, + category: [item.examType], + image: item.weixin, + description: renderToString(), + }; + }); + return { + title: '考试信息-中国无线电协会业余无线电分会', + link: 'http://82.157.138.16:8091/CRAC/crac/pages/list_examMsg.html', + item: list, + }; +} + +const ExamDescription = ({ item }: { item: any }) => ( +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    组织者:{item.exam.organizer}报名开始日期:{item.exam.signUpStartDate}
    报名截止日期:{item.exam.signUpEndDate}补充材料截止日期:{item.exam.supplementEndDate}
    考试日期:{item.exam.examDate}最多参考人数:{item.exam.maxNum}
    联系方式:{item.exam.telephone}考试方式:{item.exam.mode === 0 ? '机上考试' : '纸上考试'}
    考试类型:{item.exam.type}类考试地点:{item.exam.examArea}
    电子邮箱:{item.exam.email}备注:{item.exam.remarks}
    微信群二维码: + +
    +
    + {item.content ? raw(item.content) : null} +
    +
    +); diff --git a/lib/routes/crac/templates/exam.art b/lib/routes/crac/templates/exam.art deleted file mode 100644 index 546f08c38..000000000 --- a/lib/routes/crac/templates/exam.art +++ /dev/null @@ -1,54 +0,0 @@ -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    组织者:{{item.exam.organizer}}报名开始日期:{{item.exam.signUpStartDate}}
    报名截止日期:{{item.exam.signUpEndDate}}补充材料截止日期:{{item.exam.supplementEndDate}}
    考试日期:{{item.exam.examDate}}最多参考人数:{{item.exam.maxNum}}
    联系方式:{{item.exam.telephone}}考试方式: - {{if item.exam.mode === 0 }} - 机上考试 - {{ else }} - 纸上考试 - {{ /if }} -
    考试类型:{{item.exam.type}}类考试地点:{{item.exam.examArea}}
    电子邮箱:{{item.exam.email}}备注:{{item.exam.remarks}}
    微信群二维码:
    -
    {{@item.content}}
    -
    \ No newline at end of file diff --git a/lib/routes/creative-comic/book.ts b/lib/routes/creative-comic/book.tsx similarity index 85% rename from lib/routes/creative-comic/book.ts rename to lib/routes/creative-comic/book.tsx index b03014465..8951f809c 100644 --- a/lib/routes/creative-comic/book.ts +++ b/lib/routes/creative-comic/book.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { apiHost, decrypt, getBook, getChapter, getChapters, getImgEncrypted, getImgKey, getRealKey, getUuid } from './utils'; @@ -69,11 +68,7 @@ async function handler(ctx) { return { title: c.vol_name, - description: art(path.join(__dirname, 'templates/chapter.art'), { - chapter: c, - pages, - cover: c.image1, - }), + description: renderChapterDescription(c, pages, c.image1), pubDate: parseDate(c.online_at), updated: parseDate(c.updated_at), link: `https://www.creative-comic.tw/reader_comic/${c.id}`, @@ -92,3 +87,17 @@ async function handler(ctx) { language: 'zh-hant', }; } + +const renderChapterDescription = (chapter, pages: string[] | undefined, cover?: string): string => + renderToString( + <> + {chapter ?

    {chapter.name}

    : null} + {pages?.map((page) => ( + <> +
    + + + ))} + {cover ? : null} + + ); diff --git a/lib/routes/creative-comic/templates/chapter.art b/lib/routes/creative-comic/templates/chapter.art deleted file mode 100644 index d65dacfbd..000000000 --- a/lib/routes/creative-comic/templates/chapter.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if chapter }} -

    {{ chapter.name }}

    -{{ /if }} -{{ if pages }} - {{ each pages page }} -
    - {{ /each }} -{{ /if }} -{{ if cover }} - -{{ /if }} diff --git a/lib/routes/cuilingmag/index.ts b/lib/routes/cuilingmag/index.ts index ba719c741..c490c05e3 100644 --- a/lib/routes/cuilingmag/index.ts +++ b/lib/routes/cuilingmag/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { category } = ctx.req.param(); @@ -32,7 +31,7 @@ export const handler = async (ctx) => { const src = item.find('img').first().prop('src'); const image = src ? new URL(src, rootUrl).href : undefined; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -71,7 +70,7 @@ export const handler = async (ctx) => { const description = item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: banner ? [ { diff --git a/lib/routes/cuilingmag/templates/description.art b/lib/routes/cuilingmag/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/cuilingmag/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/cuilingmag/templates/description.tsx b/lib/routes/cuilingmag/templates/description.tsx new file mode 100644 index 000000000..6caa21852 --- /dev/null +++ b/lib/routes/cuilingmag/templates/description.tsx @@ -0,0 +1,20 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionData) => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/curius/links.ts b/lib/routes/curius/links.ts deleted file mode 100644 index fec1d3f48..000000000 --- a/lib/routes/curius/links.ts +++ /dev/null @@ -1,68 +0,0 @@ -import path from 'node:path'; - -import type { Route } from '@/types'; -import got from '@/utils/got'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -export const route: Route = { - path: '/links/:name', - categories: ['social-media'], - example: '/curius/links/yuu-yuu', - parameters: { name: 'Username, can be found in URL' }, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['curius.app/:name'], - }, - ], - name: 'User', - maintainers: ['Ovler-Young'], - handler, -}; - -async function handler(ctx) { - const username = ctx.req.param('name'); - - const name_response = await got(`https://curius.app/api/users/${username}`, { - headers: { - Referer: `https://curius.app/${username}`, - }, - }); - - const data = name_response.data; - - const uid = data.user.id; - const name = `${data.user.firstName} ${data.user.lastName}`; - - const response = await got(`https://curius.app/api/users/${uid}/links?page=0`, { - headers: { - Referer: `https://curius.app/${username}`, - }, - }); - - const items = response.data.userSaved.map((item) => ({ - title: item.title, - description: art(path.join(__dirname, 'templates/description.art'), { - item, - }), - link: item.link, - pubDate: parseDate(item.createdDate), - guid: `curius:${username}:${item.id}`, - })); - - return { - title: `${name} - Curius`, - link: `https://curius.app/${username}`, - description: `${name} - Curius`, - allowEmpty: true, - item: items, - }; -} diff --git a/lib/routes/curius/links.tsx b/lib/routes/curius/links.tsx new file mode 100644 index 000000000..56d08d610 --- /dev/null +++ b/lib/routes/curius/links.tsx @@ -0,0 +1,111 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +export const route: Route = { + path: '/links/:name', + categories: ['social-media'], + example: '/curius/links/yuu-yuu', + parameters: { name: 'Username, can be found in URL' }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['curius.app/:name'], + }, + ], + name: 'User', + maintainers: ['Ovler-Young'], + handler, +}; + +async function handler(ctx) { + const username = ctx.req.param('name'); + + const name_response = await got(`https://curius.app/api/users/${username}`, { + headers: { + Referer: `https://curius.app/${username}`, + }, + }); + + const data = name_response.data; + + const uid = data.user.id; + const name = `${data.user.firstName} ${data.user.lastName}`; + + const response = await got(`https://curius.app/api/users/${uid}/links?page=0`, { + headers: { + Referer: `https://curius.app/${username}`, + }, + }); + + const items = response.data.userSaved.map((item) => ({ + title: item.title, + description: renderDescription(item), + link: item.link, + pubDate: parseDate(item.createdDate), + guid: `curius:${username}:${item.id}`, + })); + + return { + title: `${name} - Curius`, + link: `https://curius.app/${username}`, + description: `${name} - Curius`, + allowEmpty: true, + item: items, + }; +} + +const renderDescription = (item): string => { + const fullText = item.metadata?.full_text ? item.metadata.full_text.replaceAll(/\n/gm, '
    ') : ''; + const firstComment = item.comments?.length ? item.comments[0].text.slice(0, 100) : ''; + + return renderToString( + <> + {fullText ? ( + <> + 原文:{raw(fullText)} +
    +
    + + ) : null} + {firstComment ? ( + <> + 评论:{firstComment} +
    +
    + + ) : null} + {item.highlights?.length ? ( + <> + 评论: + {item.highlights.map((highlight, index) => + highlight.comment ? ( + + {highlight.highlight} +
    + 评论:{highlight.comment.text} +
    +
    + ) : ( + +
    + 标注:{highlight.highlight} +
    +
    + ) + )} + + ) : null} + + ); +}; diff --git a/lib/routes/curius/templates/description.art b/lib/routes/curius/templates/description.art deleted file mode 100644 index 97491e413..000000000 --- a/lib/routes/curius/templates/description.art +++ /dev/null @@ -1,16 +0,0 @@ -{{ if item.metadata?.full_text }} - 原文:{{@ item.metadata.full_text.replace(/\n/gm, '
    ') }}

    -{{ /if }} -{{ if item.comments.length }} - 评论:{{ item.comments[0].text.substring(0, 100) }}

    -{{ /if }} -{{ if item.highlights.length }} - 评论: - {{ each item.highlights highlight }} - {{ if highlight.comment }} - {{ highlight.highlight }}
    评论:{{ highlight.comment.text }}
    - {{ else }} -
    标注:{{ highlight.highlight }}
    - {{ /if }} - {{ /each }} -{{ /if }} diff --git a/lib/routes/cztv/daily.ts b/lib/routes/cztv/daily.tsx similarity index 84% rename from lib/routes/cztv/daily.ts rename to lib/routes/cztv/daily.tsx index 052d27f21..61c820a86 100644 --- a/lib/routes/cztv/daily.ts +++ b/lib/routes/cztv/daily.tsx @@ -1,14 +1,22 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; -const renderDesc = (item) => art(path.join(__dirname, 'templates/daily.art'), item); +const renderDesc = (item) => + renderToString( + <> + {item.list?.map((entry) => ( + <> + {entry.title} +
    + + ))} + + ); export const route: Route = { path: '/zjxwlb/daily', diff --git a/lib/routes/cztv/templates/daily.art b/lib/routes/cztv/templates/daily.art deleted file mode 100644 index ee32cbd19..000000000 --- a/lib/routes/cztv/templates/daily.art +++ /dev/null @@ -1,4 +0,0 @@ -{{each list item}} - {{item.title}} -
    -{{/each}} diff --git a/lib/routes/cztv/templates/zjxwlb.art b/lib/routes/cztv/templates/zjxwlb.art deleted file mode 100644 index 9eb64b4b1..000000000 --- a/lib/routes/cztv/templates/zjxwlb.art +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/lib/routes/cztv/zjxwlb.ts b/lib/routes/cztv/zjxwlb.tsx similarity index 88% rename from lib/routes/cztv/zjxwlb.ts rename to lib/routes/cztv/zjxwlb.tsx index 1f9247f1b..989e46d2d 100644 --- a/lib/routes/cztv/zjxwlb.ts +++ b/lib/routes/cztv/zjxwlb.tsx @@ -1,14 +1,17 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; -const renderDesc = (item) => art(path.join(__dirname, 'templates/zjxwlb.art'), item); +const renderDesc = (item) => + renderToString( + + ); export const route: Route = { path: '/zjxwlb', diff --git a/lib/routes/daily/templates/posts.art b/lib/routes/daily/templates/posts.art deleted file mode 100644 index b8d94338e..000000000 --- a/lib/routes/daily/templates/posts.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if image }} -
    -{{ /if }} - -{{ if content }} -{{@ content }} -{{ /if }} diff --git a/lib/routes/daily/utils.ts b/lib/routes/daily/utils.tsx similarity index 82% rename from lib/routes/daily/utils.ts rename to lib/routes/daily/utils.tsx index 5205f6f24..0d872befd 100644 --- a/lib/routes/daily/utils.ts +++ b/lib/routes/daily/utils.tsx @@ -1,11 +1,11 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { DataItem } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const baseUrl = 'https://app.daily.dev'; const gqlUrl = `https://api.daily.dev/graphql`; @@ -33,7 +33,18 @@ export const getData = async (graphqlQuery, source = false) => { return source ? response.data.source : response.data.page.edges; }; -const render = (data) => art(path.join(__dirname, 'templates/posts.art'), data); +const render = ({ image, content }: { image?: string; content?: string }) => + renderToString( + <> + {image ? ( + <> + +
    + + ) : null} + {content ? raw(content) : null} + + ); export const getList = (edges, innerSharedContent: boolean, dateSort: boolean) => edges.map(({ node }) => { diff --git a/lib/routes/damai/activity.ts b/lib/routes/damai/activity.tsx similarity index 81% rename from lib/routes/damai/activity.ts rename to lib/routes/damai/activity.tsx index b6113cb27..24edf2046 100644 --- a/lib/routes/damai/activity.ts +++ b/lib/routes/damai/activity.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/activity/:city/:category/:subcategory/:keyword?', @@ -58,9 +57,17 @@ async function handler(ctx) { item: list.map((item) => ({ title: item.nameNoHtml, author: item.actors ? load(item.actors, null, false).text() : '大麦网', - description: art(path.join(__dirname, 'templates/activity.art'), { - item, - }), + description: renderToString( + <> + +

    {item.description ? raw(item.description) : null}

    +

    + 地点:{item.venuecity} | {item.venue} +

    +

    时间:{item.showtime}

    +

    票价:{item.price_str}

    + + ), link: `https://detail.damai.cn/item.htm?id=${item.projectid}`, })), }; diff --git a/lib/routes/damai/templates/activity.art b/lib/routes/damai/templates/activity.art deleted file mode 100644 index 5e0e359c5..000000000 --- a/lib/routes/damai/templates/activity.art +++ /dev/null @@ -1,5 +0,0 @@ - -

    {{@ item.description }}

    -

    地点:{{ item.venuecity }} | {{ item.venue }}

    -

    时间:{{ item.showtime }}

    -

    票价:{{ item.price_str }}

    diff --git a/lib/routes/dbaplus/new.ts b/lib/routes/dbaplus/new.ts index ada104dc7..4eace458f 100644 --- a/lib/routes/dbaplus/new.ts +++ b/lib/routes/dbaplus/new.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { id = '9' } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -35,7 +34,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.text(); const image: string | undefined = $el.find('img.media-object').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -96,7 +95,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h2.title').text(); const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ description: $$('div.new-detailed').html(), }); const pubDateStr: string | undefined = $$('span.time').first().text(); diff --git a/lib/routes/dbaplus/templates/description.art b/lib/routes/dbaplus/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/dbaplus/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dbaplus/templates/description.tsx b/lib/routes/dbaplus/templates/description.tsx new file mode 100644 index 000000000..8168cc41b --- /dev/null +++ b/lib/routes/dbaplus/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/dcfever/templates/trading.art b/lib/routes/dcfever/templates/trading.art deleted file mode 100644 index fe549505a..000000000 --- a/lib/routes/dcfever/templates/trading.art +++ /dev/null @@ -1,14 +0,0 @@ -

    {{ info.find('.trading_item_title').text() }}

    -{{ info.find('.trading_item_type_tag').text() }} {{ info.find('.trading_item_price').text() }}
    - - - - - - - - -
    賣家{{ info.find('.clearfix .content').eq(0).text() }}
    查詢次數{{ info.find('.clearfix .content').eq(1).text() }}
    瀏覽次數{{ info.find('.clearfix .content').eq(2).text() }}
    刊登日期{{ info.find('.clearfix .content').eq(3).text() }}
    最後更新{{ info.find('.clearfix .content').eq(4).text() }}
    刊登期至{{ info.find('.clearfix .content').eq(5).text() }}
    刊登狀態{{ info.find('.clearfix .content').eq(6).text() }}

    - -{{@ description }}

    -{{@ photo }} diff --git a/lib/routes/dcfever/utils.ts b/lib/routes/dcfever/utils.tsx similarity index 59% rename from lib/routes/dcfever/utils.ts rename to lib/routes/dcfever/utils.tsx index fc532dd3e..487067ee3 100644 --- a/lib/routes/dcfever/utils.ts +++ b/lib/routes/dcfever/utils.tsx @@ -1,14 +1,57 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://www.dcfever.com'; +const renderTradeDescription = (info, description, photo) => + renderToString( + <> +

    {info.find('.trading_item_title').text()}

    + {info.find('.trading_item_type_tag').text()} {info.find('.trading_item_price').text()} +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    賣家{info.find('.clearfix .content').eq(0).text()}
    查詢次數{info.find('.clearfix .content').eq(1).text()}
    瀏覽次數{info.find('.clearfix .content').eq(2).text()}
    刊登日期{info.find('.clearfix .content').eq(3).text()}
    最後更新{info.find('.clearfix .content').eq(4).text()}
    刊登期至{info.find('.clearfix .content').eq(5).text()}
    刊登狀態{info.find('.clearfix .content').eq(6).text()}
    +
    + {description ? <>{raw(description)} : null} +
    +
    + {photo ? <>{raw(photo)} : null} + + ); + const parseItem = (item) => cache.tryGet(item.link, async () => { const response = await ofetch(item.link); @@ -81,11 +124,7 @@ const parseTradeItem = (item) => } }); - item.description = art(path.join(__dirname, 'templates/trading.art'), { - info: $('.info_col'), - description: $('.description_text').html(), - photo: $photo('.desktop_photo_selector').html(), - }); + item.description = renderTradeDescription($('.info_col'), $('.description_text').html(), $photo('.desktop_photo_selector').html()); return item; }); diff --git a/lib/routes/deadline/posts.ts b/lib/routes/deadline/posts.tsx similarity index 71% rename from lib/routes/deadline/posts.ts rename to lib/routes/deadline/posts.tsx index 6991508cc..dd9a52769 100644 --- a/lib/routes/deadline/posts.ts +++ b/lib/routes/deadline/posts.tsx @@ -1,11 +1,28 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const renderDescription = (embedded, desc) => + renderToString( + <> + {embedded?.['wp:featuredmedia']?.length + ? embedded['wp:featuredmedia'].map((media) => ( + <> +
    + {media.alt_text} +
    {media.media_details?.image_meta.caption}
    +
    +
    + + )) + : null} + {desc ? <>{raw(desc)} : null} + + ); export const route: Route = { path: '/', @@ -44,10 +61,7 @@ async function handler(ctx) { }); $('[class^="lrv-a-crop-"]').contents().unwrap(); - const description = art(path.join(__dirname, 'templates/desc.art'), { - desc: $.html(), - embedded, - }); + const description = renderDescription(embedded, $.html()); return { title: item.title.rendered, link: item.link, diff --git a/lib/routes/deadline/templates/desc.art b/lib/routes/deadline/templates/desc.art deleted file mode 100644 index 6f53b80f2..000000000 --- a/lib/routes/deadline/templates/desc.art +++ /dev/null @@ -1,10 +0,0 @@ -{{ if embedded['wp:featuredmedia'] }} - {{ each embedded['wp:featuredmedia'] media }} -
    - {{ media.alt_text }} -
    {{ media.media_details?.image_meta.caption }}
    -
    -
    - {{ /each }} -{{ /if }} -{{@ desc }} diff --git a/lib/routes/dedao/knowledge.ts b/lib/routes/dedao/knowledge.tsx similarity index 57% rename from lib/routes/dedao/knowledge.ts rename to lib/routes/dedao/knowledge.tsx index 9d2ccc81a..076b8e9a7 100644 --- a/lib/routes/dedao/knowledge.ts +++ b/lib/routes/dedao/knowledge.tsx @@ -1,9 +1,15 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const mentionPattern = /<\u2267\u2746>{"name":"(.*?)","uid":"\d+","at":"1"}<\/\u2266\u2746>/g; + +const formatNoteText = (text = '') => text.replaceAll('\n\n', '

    ').replaceAll(mentionPattern, ' @$1'); + +const extractImageUrl = (value?: string) => value?.match(/"url":"(.*?)"/)?.[1]; export const route: Route = { path: '/knowledge/:topic?/:type?', @@ -73,11 +79,40 @@ async function handler(ctx) { author: item.f_part.nick_name, link: `${rootUrl}/knowledge/note/${item.f_part.note_id_hazy}`, pubDate: parseDate(item.f_part.time_desc, 'MM-DD'), - description: art(path.join(__dirname, 'templates/knowledge.art'), { - rootUrl, - f_part: item.f_part, - s_part: item.s_part, - }), + description: renderToString( + <> + {item.f_part.note ?

    {raw(formatNoteText(item.f_part.note))}

    : null} + {item.f_part.images ? ( + <> +
    + {item.f_part.images.map((image) => { + const imageUrl = extractImageUrl(image); + return imageUrl ? : null; + })} + + ) : null} + {item.s_part ? ( + <> +
    +

    + 引用 {item.s_part.nick_name} + {item.s_part.v_info ? ` (${item.s_part.v_info})` : ''}: +

    +

    {raw(formatNoteText(item.s_part.note))}

    + [查看原文] + {item.s_part.images ? ( + <> +
    + {item.s_part.images.map((image) => { + const imageUrl = extractImageUrl(image); + return imageUrl ? : null; + })} + + ) : null} + + ) : null} + + ), })); return { diff --git a/lib/routes/dedao/templates/knowledge.art b/lib/routes/dedao/templates/knowledge.art deleted file mode 100644 index fe5facf22..000000000 --- a/lib/routes/dedao/templates/knowledge.art +++ /dev/null @@ -1,29 +0,0 @@ -{{ if f_part.note }} -

    {{@ f_part.note - .replace(/\n\n/g, '

    ') - .replace(/<≧❆>{"name":"(.*?)","uid":"\d+","at":"1"}<\/≦❆>/g, ' @$1') - }}

    -{{ /if }} -{{ if f_part.images }} -
    -{{ set f_part_images = f_part.images }} -{{ each f_part_images image }} - -{{ /each }} -{{ /if }} -{{ if s_part }} -
    -

    引用 {{ s_part.nick_name }}{{ if s_part.v_info }} ({{ s_part.v_info }}){{ /if }}:

    -

    {{@ s_part.note - .replace(/\n\n/g, '

    ') - .replace(/<≧❆>{"name":"(.*?)","uid":"\d+","at":"1"}<\/≦❆>/g, ' @$1') - }}

    -[查看原文] -{{ if s_part.images }} -
    -{{ set s_part_images = s_part.images }} -{{ each s_part_images image}} - -{{ /each }} -{{ /if }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dedao/templates/user.art b/lib/routes/dedao/templates/user.art deleted file mode 100644 index fc4525e23..000000000 --- a/lib/routes/dedao/templates/user.art +++ /dev/null @@ -1,38 +0,0 @@ -{{ if name }} -

    {{@ content - .replace(/\n\n/g, '

    ') - .replace(/<≧❆>{"name":"(.*?)","uid":"\d+","at":"1"}<\/≦❆>/g, ' @$1') - }}

    -
    -

    引用 {{ name }}{{ if vinfo }} ({{ vinfo }}){{ /if }}:

    -{{ /if }} -

    {{@ note - .replace(/\n\n/g, '

    ') - .replace(/<≧❆>{"name":"(.*?)","uid":"\d+","at":"1"}<\/≦❆>/g, ' @$1') - }}

    -{{ if extra }} -
    -{{ if extra.img }} -{{ if extra.share_ext }} - -{{ /if }} -
    - -
    {{ extra.title }}
    -
    -{{ if extra.share_ext }} -
    -{{ /if }} -{{ /if }} -{{ if extra.images }} -{{ set extra_images = extra.images }} -{{ each extra_images image }} - -{{ /each }} -{{ /if }} -{{ if video }} -
    - -
    -{{ /if }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dedao/user.ts b/lib/routes/dedao/user.ts deleted file mode 100644 index ede8cde81..000000000 --- a/lib/routes/dedao/user.ts +++ /dev/null @@ -1,102 +0,0 @@ -import path from 'node:path'; - -import type { Route } from '@/types'; -import got from '@/utils/got'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -const types = { - 0: '动态', - 7: '书评', - 12: '视频', -}; - -export const route: Route = { - path: '/user/:id/:type?', - categories: ['new-media'], - example: '/dedao/user/VkA5OqLX4RyGxmZRNBMlwBrDaJQ9og', - parameters: { id: '用户 id,可在对应用户主页 URL 中找到', type: '类型,见下表,默认为`0`,即动态' }, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - name: '用户主页', - maintainers: ['nczitzk'], - handler, - description: `| 动态 | 书评 | 视频 | -| ---- | ---- | ---- | -| 0 | 7 | 12 |`, -}; - -async function handler(ctx) { - const id = ctx.req.param('id') ?? ''; - const type = ctx.req.param('type') ? Number.parseInt(ctx.req.param('type')) : 0; - const count = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 100; - - const rootUrl = 'https://m.igetget.com'; - const currentUrl = `${rootUrl}/native/mine/account#/user/detail?enId=${id}`; - const apiUrl = `${rootUrl}/native/api/homePage/topicNote`; - const infoUrl = `${rootUrl}/native/api/homePage/userInfo`; - - const detailResponse = await got({ - method: 'post', - url: infoUrl, - json: { - hazy: id, - }, - }); - - const data = detailResponse.data; - - const author = data.c.nickname; - const image = data.c.avatar; - const description = `${data.c.v_info}: ${data.c.slogan}`; - - const response = await got({ - method: 'post', - url: apiUrl, - json: { - uid: null, - max_id_str: '0', - count, - max_createtime: 0, - is_only_repost: 0, - load_chain: true, - load_tag: 1, - source: 0, - note_type: type, - only_origin: false, - with_highlight: true, - hazy: id, - }, - }); - - const items = response.data.c.list.map((item) => ({ - author, - title: item.content || item.note || item.extra.title, - link: item.share_url, - pubDate: parseDate(item.create_time * 1000), - description: art(path.join(__dirname, 'templates/user.art'), { - rootUrl, - name: item.origin_notes_owner.name, - vinfo: item.origin_notes_owner.Vinfo, - content: item.content, - note: item.note, - extra: item.extra, - video: item.video.video_cover, - }), - })); - - return { - title: `${author}的得到主页 - ${types[type]}`, - link: currentUrl, - item: items, - image, - description, - allowEmpty: true, - }; -} diff --git a/lib/routes/dedao/user.tsx b/lib/routes/dedao/user.tsx new file mode 100644 index 000000000..164618039 --- /dev/null +++ b/lib/routes/dedao/user.tsx @@ -0,0 +1,150 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +const types = { + 0: '动态', + 7: '书评', + 12: '视频', +}; + +const mentionPattern = /<\u2267\u2746>{"name":"(.*?)","uid":"\d+","at":"1"}<\/\u2266\u2746>/g; + +const formatNoteText = (text = '') => text.replaceAll('\n\n', '

    ').replaceAll(mentionPattern, ' @$1'); + +const extractImageUrl = (value?: string) => value?.match(/"url":"(.*?)"/)?.[1]; + +export const route: Route = { + path: '/user/:id/:type?', + categories: ['new-media'], + example: '/dedao/user/VkA5OqLX4RyGxmZRNBMlwBrDaJQ9og', + parameters: { id: '用户 id,可在对应用户主页 URL 中找到', type: '类型,见下表,默认为`0`,即动态' }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: '用户主页', + maintainers: ['nczitzk'], + handler, + description: `| 动态 | 书评 | 视频 | +| ---- | ---- | ---- | +| 0 | 7 | 12 |`, +}; + +async function handler(ctx) { + const id = ctx.req.param('id') ?? ''; + const type = ctx.req.param('type') ? Number.parseInt(ctx.req.param('type')) : 0; + const count = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 100; + + const rootUrl = 'https://m.igetget.com'; + const currentUrl = `${rootUrl}/native/mine/account#/user/detail?enId=${id}`; + const apiUrl = `${rootUrl}/native/api/homePage/topicNote`; + const infoUrl = `${rootUrl}/native/api/homePage/userInfo`; + + const detailResponse = await got({ + method: 'post', + url: infoUrl, + json: { + hazy: id, + }, + }); + + const data = detailResponse.data; + + const author = data.c.nickname; + const image = data.c.avatar; + const description = `${data.c.v_info}: ${data.c.slogan}`; + + const response = await got({ + method: 'post', + url: apiUrl, + json: { + uid: null, + max_id_str: '0', + count, + max_createtime: 0, + is_only_repost: 0, + load_chain: true, + load_tag: 1, + source: 0, + note_type: type, + only_origin: false, + with_highlight: true, + hazy: id, + }, + }); + + const items = response.data.c.list.map((item) => ({ + author, + title: item.content || item.note || item.extra.title, + link: item.share_url, + pubDate: parseDate(item.create_time * 1000), + description: renderToString( + <> + {item.origin_notes_owner.name ? ( + <> +

    {raw(formatNoteText(item.content))}

    +
    +

    + 引用 {item.origin_notes_owner.name} + {item.origin_notes_owner.Vinfo ? ` (${item.origin_notes_owner.Vinfo})` : ''}: +

    + + ) : null} +

    {raw(formatNoteText(item.note))}

    + {item.extra ? ( + <> +
    + {item.extra.img ? ( + item.extra.share_ext ? ( + +
    + +
    {item.extra.title}
    +
    +
    + ) : ( +
    + +
    {item.extra.title}
    +
    + ) + ) : null} + {item.extra.images + ? item.extra.images.map((image) => { + const imageUrl = extractImageUrl(image); + return imageUrl ? : null; + }) + : null} + {item.video.video_cover + ? (() => { + const imageUrl = extractImageUrl(item.video.video_cover); + return imageUrl ? ( +
    + +
    + ) : null; + })() + : null} + + ) : null} + + ), + })); + + return { + title: `${author}的得到主页 - ${types[type]}`, + link: currentUrl, + item: items, + image, + description, + allowEmpty: true, + }; +} diff --git a/lib/routes/deepl/blog.ts b/lib/routes/deepl/blog.ts index 746a22898..c828101d5 100644 --- a/lib/routes/deepl/blog.ts +++ b/lib/routes/deepl/blog.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { lang = 'en' } = ctx.req.param(); @@ -33,7 +32,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('h4, h6').text(); const image: string | undefined = $el.find('img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -86,7 +85,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1[data-contentful-field-id="title"]').text(); const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ description: $$('div.my-redesign-3').html(), }); const pubDateStr: string | undefined = $$('time').first().attr('datetime'); diff --git a/lib/routes/deepl/templates/description.art b/lib/routes/deepl/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/deepl/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/deepl/templates/description.tsx b/lib/routes/deepl/templates/description.tsx new file mode 100644 index 000000000..8168cc41b --- /dev/null +++ b/lib/routes/deepl/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/deeplearning/templates/description.art b/lib/routes/deeplearning/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/deeplearning/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/deeplearning/templates/description.tsx b/lib/routes/deeplearning/templates/description.tsx new file mode 100644 index 000000000..f0e2d4a8f --- /dev/null +++ b/lib/routes/deeplearning/templates/description.tsx @@ -0,0 +1,29 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +const Description = ({ images, intro, description }: DescriptionProps) => ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/deeplearning/the-batch.ts b/lib/routes/deeplearning/the-batch.ts index bc822317d..c52be0919 100644 --- a/lib/routes/deeplearning/the-batch.ts +++ b/lib/routes/deeplearning/the-batch.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { tag } = ctx.req.param(); @@ -28,7 +27,7 @@ export const handler = async (ctx) => { let items = posts.slice(0, limit).map((item) => { const title = item.title; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: item.feature_image ? [ { @@ -85,7 +84,7 @@ export const handler = async (ctx) => { }); const title = post.title; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: post.feature_image ? [ { diff --git a/lib/routes/dehenglaw/index.ts b/lib/routes/dehenglaw/index.ts index 5a2902acc..f0d63a65d 100644 --- a/lib/routes/dehenglaw/index.ts +++ b/lib/routes/dehenglaw/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { language = 'CN', category = 'paper' } = ctx.req.param(); @@ -26,7 +25,7 @@ export const handler = async (ctx) => { item = $(item); const title = item.find('h2').text(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ intro: item.find('div.deheng_newscontent p').text(), }); @@ -47,7 +46,7 @@ export const handler = async (ctx) => { const description = item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ description: $$('div.news_content').html(), }); const image = $$('div.news_content img').prop('src'); diff --git a/lib/routes/dehenglaw/templates/description.art b/lib/routes/dehenglaw/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/dehenglaw/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dehenglaw/templates/description.tsx b/lib/routes/dehenglaw/templates/description.tsx new file mode 100644 index 000000000..bdfc2d996 --- /dev/null +++ b/lib/routes/dehenglaw/templates/description.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + intro?: string; + description?: string; +}; + +const DehenglawDescription = ({ intro, description }: DescriptionData) => ( + <> + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/dgtle/templates/description.art b/lib/routes/dgtle/templates/description.art deleted file mode 100644 index 5b807209c..000000000 --- a/lib/routes/dgtle/templates/description.art +++ /dev/null @@ -1,41 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if videos }} - {{ each videos video }} - {{ if video?.src }} - - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dgtle/templates/description.tsx b/lib/routes/dgtle/templates/description.tsx new file mode 100644 index 000000000..6824b89a0 --- /dev/null +++ b/lib/routes/dgtle/templates/description.tsx @@ -0,0 +1,50 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionVideo = { + src?: string; + poster?: string; + type?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + videos?: DescriptionVideo[]; + intro?: string; + description?: string; +}; + +const Description = ({ images, videos, intro, description }: DescriptionProps) => { + const fallbackPoster = images?.[0]?.src; + + return ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {videos?.map((video, index) => + video?.src ? ( + + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); +}; + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/dgtle/util.ts b/lib/routes/dgtle/util.ts index decfae353..98b4f34ec 100644 --- a/lib/routes/dgtle/util.ts +++ b/lib/routes/dgtle/util.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import MarkdownIt from 'markdown-it'; @@ -8,7 +6,8 @@ import type { DataItem } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const md = MarkdownIt({ html: true, @@ -23,7 +22,7 @@ const ProcessItems = async (limit: number, dataList: any): Promise = items = dataList.slice(0, limit).map((item): DataItem => { const title: string = item.title || item.content; const image: string | undefined = item.cover; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -90,7 +89,7 @@ const ProcessItems = async (limit: number, dataList: any): Promise = const $$el = $$(el); $$el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: [ { src: $$el @@ -103,7 +102,7 @@ const ProcessItems = async (limit: number, dataList: any): Promise = ); }); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ description: $$('div.whale_news_detail-daily-content, div#articleContent, div.forum-viewthread-article-box').html(), }); @@ -127,7 +126,7 @@ const ProcessFeedItems = (limit: number, dataList: any, $: CheerioAPI): DataItem const content: string = item.content ? md.render(item.content) : ''; const title: string = $(content).text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: item.imgs_url.map((src) => ({ src, })), diff --git a/lib/routes/dgtle/video.ts b/lib/routes/dgtle/video.ts index 9c7bf3e3e..933077166 100644 --- a/lib/routes/dgtle/video.ts +++ b/lib/routes/dgtle/video.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '18', 10); @@ -31,7 +30,7 @@ export const handler = async (ctx: Context): Promise => { items = response.data.list.slice(0, limit).map((item): DataItem => { const title: string = item.title; const image: string | undefined = item.cover?.split(/\?/)?.[0]; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -88,7 +87,7 @@ export const handler = async (ctx: Context): Promise => { const enclosureUrl: string | undefined = $$enclosureEl.attr('src'); const image: string | undefined = $$('div.video-play').attr('data-url'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ videos: enclosureUrl ? [ { diff --git a/lib/routes/diershoubing/news.ts b/lib/routes/diershoubing/news.tsx similarity index 71% rename from lib/routes/diershoubing/news.ts rename to lib/routes/diershoubing/news.tsx index 11e1daa44..0a5b62f7d 100644 --- a/lib/routes/diershoubing/news.ts +++ b/lib/routes/diershoubing/news.tsx @@ -1,11 +1,11 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; -const renderDesc = (data) => art(path.join(__dirname, 'templates/news.art'), data); +const renderDesc = (data: { description: string; type: string; acontent: any }) => renderToString(); export const route: Route = { path: '/news', @@ -75,3 +75,21 @@ async function handler(ctx) { item: items, }; } + +const DiershoubingDescription = ({ description, type, acontent }: { description: string; type: string; acontent: any }) => ( + <> + {raw(description)} + {type === 'imgs' ? ( + <> + {acontent.map((img) => ( + + ))} + + ) : type === 'bilibili' ? ( + <> + + + + ) : null} + +); diff --git a/lib/routes/diershoubing/templates/news.art b/lib/routes/diershoubing/templates/news.art deleted file mode 100644 index 5cbe59875..000000000 --- a/lib/routes/diershoubing/templates/news.art +++ /dev/null @@ -1,9 +0,0 @@ -{{@ description }} -{{ if type === 'imgs' }} -{{ each acontent img }} - -{{ /each }} -{{ else if type === 'bilibili' }} - - -{{ /if }} diff --git a/lib/routes/discord/channel.ts b/lib/routes/discord/channel.ts index 2b1e3139d..54642d021 100644 --- a/lib/routes/discord/channel.ts +++ b/lib/routes/discord/channel.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { DataItem, Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { baseUrl, getChannel, getChannelMessages, getGuild } from './discord-api'; +import { renderDescription } from './templates/message'; export const route: Route = { path: '/channel/:channelId', @@ -52,7 +50,7 @@ async function handler(ctx) { const messages = messagesRaw.map((message) => ({ title: message.content.split('\n')[0], - description: art(path.join(__dirname, 'templates/message.art'), { message, guildInfo }), + description: renderDescription({ message, guildInfo }), author: `${message.author.global_name ?? message.author.username}(${message.author.username})`, pubDate: parseDate(message.timestamp), updated: message.edited_timestamp ? parseDate(message.edited_timestamp) : undefined, diff --git a/lib/routes/discord/search.ts b/lib/routes/discord/search.ts index 521bd6486..c04a04657 100644 --- a/lib/routes/discord/search.ts +++ b/lib/routes/discord/search.ts @@ -1,15 +1,13 @@ -import path from 'node:path'; - import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; import { queryToBoolean } from '@/utils/readable-social'; -import { art } from '@/utils/render'; import type { HasType, SearchGuildMessagesParams } from './discord-api'; import { baseUrl, getGuild, searchGuildMessages, VALID_HAS_TYPES } from './discord-api'; +import { renderDescription } from './templates/message'; export const route: Route = { path: '/search/:guildId/:routeParams', @@ -82,7 +80,7 @@ async function handler(ctx) { const messages = searchResult.messages.flat().map((message) => ({ title: message.content.split('\n')[0] || '(no content)', - description: art(path.join(__dirname, 'templates/message.art'), { message, guildInfo }), + description: renderDescription({ message, guildInfo }), author: message.author.global_name ?? message.author.username, pubDate: parseDate(message.timestamp), updated: message.edited_timestamp ? parseDate(message.edited_timestamp) : undefined, diff --git a/lib/routes/discord/templates/message.art b/lib/routes/discord/templates/message.art deleted file mode 100644 index a3d874282..000000000 --- a/lib/routes/discord/templates/message.art +++ /dev/null @@ -1,62 +0,0 @@ -{{ if message.type === 7 }} - {{ message.author.global_name ?? message.author.username }} joined {{ guildInfo.name }}.
    -{{ /if }} - -{{ if message.content }} - {{@ message.content.replace(/\n/g, '
    ') }}
    -{{ /if }} - -{{ if message.attachments }} - {{ each message.attachments a }} -
    - {{ /each }} -{{ /if }} - -{{ if message.sticker_items }} - {{ each message.sticker_items sticker }} - {{ if sticker.format_type < 3 }} - {{ sticker.name }}
    - {{ /each }} -{{ /if }} - -{{ if message.embeds }} - {{ each message.embeds e }} - {{ if e.type === 'article' }} - {{ if e.url }} - {{ e.title || e.url }} - {{ if e.description }}
    {{@ e.description.replace(/\n/g, '
    ') }}{{ /if }} -
    - {{ /if }} - {{ if e.thumbnail }}{{ /if }} - - {{ else if e.type === 'gifv' }} - - - {{ else if e.type === 'image' }} - - - {{ else if e.type === 'rich' }} - {{ if e.author }}{{ e.author.name }}
    {{ /if }} - {{ e.title || e.url }} - {{ if e.description }}
    {{@ e.description.replace(/\n/g, '
    ') }}{{ /if }} -
    - {{ if e.image }}{{ /if }} - - {{ else if e.type === 'video' }} - {{ if e.url }} - {{ e.title }} - {{ if e.description }}
    {{@ e.description.replace(/\n/g, '
    ') }}{{ /if }} -
    - {{ /if }} - {{ if e.thumbnail }}{{ /if }} - - {{ /if }} -
    - {{ /each }} -{{ /if }} diff --git a/lib/routes/discord/templates/message.tsx b/lib/routes/discord/templates/message.tsx new file mode 100644 index 000000000..ec06552e2 --- /dev/null +++ b/lib/routes/discord/templates/message.tsx @@ -0,0 +1,107 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DiscordMessageData = { + message: any; + guildInfo: any; +}; + +const renderWithLineBreaks = (text?: string) => (text ? raw(text.replaceAll('\n', '
    ')) : null); + +const DiscordMessage = ({ message, guildInfo }: DiscordMessageData) => ( + <> + {message.type === 7 ? ( + <> + {message.author.global_name ?? message.author.username} joined {guildInfo.name}.
    + + ) : null} + {message.content ? ( + <> + {renderWithLineBreaks(message.content)} +
    + + ) : null} + {message.attachments?.map((attachment) => ( + <> + +
    + + ))} + {message.sticker_items?.map((sticker) => { + const src = sticker.format_type < 3 ? `https://cdn.discordapp.com/stickers/${sticker.id}.png` : sticker.format_type === 4 ? `https://media.discordapp.net/stickers/${sticker.id}.gif` : null; + + return src ? ( + <> + {sticker.name} +
    + + ) : null; + })} + {message.embeds?.map((embed) => ( + <> + {embed.type === 'article' ? ( + <> + {embed.url ? ( + <> + {embed.title || embed.url} + {embed.description ? ( + <> +
    + {renderWithLineBreaks(embed.description)} + + ) : null} +
    + + ) : null} + {embed.thumbnail ? : null} + + ) : null} + {embed.type === 'gifv' ? ( + + ) : null} + {embed.type === 'image' ? : null} + {embed.type === 'rich' ? ( + <> + {embed.author ? ( + <> + {embed.author.name} +
    + + ) : null} + {embed.title || embed.url} + {embed.description ? ( + <> +
    + {renderWithLineBreaks(embed.description)} + + ) : null} +
    + {embed.image ? : null} + + ) : null} + {embed.type === 'video' ? ( + <> + {embed.url ? ( + <> + {embed.title} + {embed.description ? ( + <> +
    + {renderWithLineBreaks(embed.description)} + + ) : null} +
    + + ) : null} + {embed.thumbnail ? : null} + + ) : null} +
    + + ))} + +); + +export const renderDescription = (data: DiscordMessageData) => renderToString(); diff --git a/lib/routes/dlnews/category.ts b/lib/routes/dlnews/category.tsx similarity index 65% rename from lib/routes/dlnews/category.ts rename to lib/routes/dlnews/category.tsx index d06b15deb..a30277817 100644 --- a/lib/routes/dlnews/category.ts +++ b/lib/routes/dlnews/category.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import pMap from 'p-map'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import { getData, getList } from './utils'; @@ -21,6 +20,50 @@ const topics = { snapshot: 'Snapshot', web3: 'Web3', }; +const renderDescription = (blocks) => + renderToString( + <> + {blocks.map((block) => { + switch (block.type) { + case 'custom_embed': + return ( +
      + {block.data.split('\n').map((line) => ( +
    • {raw(line)}
    • + ))} +
    + ); + case 'header': + return

    {raw(block.data)}

    ; + case 'list': + return block.list_type === 'unordered' ? ( +
      + {block.items.map((item) => ( +
    • {raw(item.content)}
    • + ))} +
    + ) : ( +
      + {block.items.map((item) => ( +
    1. {raw(item.content)}
    2. + ))} +
    + ); + case 'image': + return ( +
    + {block.alt} +
    {raw(block.caption)}
    +
    + ); + case 'text': + return

    {raw(block.data)}

    ; + default: + return null; + } + })} + + ); const extractArticle = (item) => cache.tryGet(item.link, async () => { const { data: response } = await got(item.link); @@ -57,7 +100,7 @@ const extractArticle = (item) => } } } - item.description = art(path.resolve(__dirname, 'templates/description.art'), filteredData); + item.description = renderDescription(filteredData); return item; }); diff --git a/lib/routes/dlnews/templates/description.art b/lib/routes/dlnews/templates/description.art deleted file mode 100644 index bcfe4d724..000000000 --- a/lib/routes/dlnews/templates/description.art +++ /dev/null @@ -1,24 +0,0 @@ -{{ each $data d }} - {{ if d.type == 'custom_embed' }} -
      - {{ each d.data.split('\n') line }} -
    • {{@ line }}
    • - {{ /each }} -
    - {{ else if d.type == 'header' }} -

    {{@ d.data }}

    - {{ else if d.type == 'list' }} - {{ if d.list_type == 'unordered' }}
      {{ else }}
        {{ /if }} - {{ each d.items item }} -
      1. {{@ item.content }}
      2. - {{ /each }} - {{ if d.list_type == 'unordered' }}
    {{ else }}{{ /if }} - {{ else if d.type == 'image' }} -
    - {{ d.alt }} -
    {{@ d.caption }}
    -
    - {{ else if d.type == 'text' }} -

    {{@ d.data }}

    - {{ /if }} -{{ /each }} \ No newline at end of file diff --git a/lib/routes/dlsite/templates/description.art b/lib/routes/dlsite/templates/description.art deleted file mode 100644 index b0a7d058b..000000000 --- a/lib/routes/dlsite/templates/description.art +++ /dev/null @@ -1,158 +0,0 @@ - - - {{ set hasOfficialPrice = false }} - {{ set haslocaleOfficialPrices = false }} - {{ set officialPrice = '' }} - {{ set localeOfficialPrices = {} }} - - {{ if detail.official_price_str }} - {{ set officialPrice = detail.official_price_str }} - {{ set hasOfficialPrice = true }} - {{ /if }} - - {{ if detail.locale_official_price_str }} - {{ set localeOfficialPrices = detail.locale_official_price_str }} - {{ set haslocaleOfficialPrices = true }} - {{ /if }} - - {{ set price = detail.price_str }} - {{ set localePrices = detail.locale_price_str }} - - {{ if detail.discount_rate }} - {{ set discountRate = detail.discount_rate }} - - - - - {{ /if }} - {{ if discountEndDate }} - - - - - {{ /if }} - {{ if price }} - - - - - {{ /if }} - {{ if localePrices.en_US }} - {{ set price = localePrices.en_US }} - {{ set officialPrice = localeOfficialPrices.en_US }} - - - - - {{ /if }} - {{ if localePrices.ko_KR }} - {{ set price = localePrices.ko_KR }} - {{ set officialPrice = localeOfficialPrices.ko_KR }} - - - - - {{ /if }} - {{ if localePrices.zh_CN }} - {{ set price = localePrices.zh_CN }} - {{ set officialPrice = localeOfficialPrices.zh_CN }} - - - - - {{ /if }} - {{ if localePrices.zh_TW }} - {{ set price = localePrices.zh_TW }} - {{ set officialPrice = localeOfficialPrices.zh_TW }} - - - - - {{ /if }} - {{ if detail.default_point_str }} - {{ set defaultPoint = detail.default_point_str }} - - - - - {{ /if }} - {{ if authors }} - - - - - {{ /if }} - {{ if updatedDate }} - - - - - {{ /if }} - {{ if pubDate }} - - - - - {{ /if }} - {{ if workCategories }} - - - - - {{ /if }} - {{ if searchTags }} - - - - - {{ /if }} - {{ if description }} - - - - - {{ /if }} - -
    割引{{ discountRate }}%
    割引終了時間{{ discountEndDate | formatDate 'YYYY-MM-DD HH:mm' }}
    価格 (JPY) - {{@ price }} 円   - {{ if hasOfficialPrice }} - {{@ officialPrice }} 円 - {{ /if }} -
    価格 (USD) - {{@ price }} - {{ if haslocaleOfficialPrices }} - {{@ officialPrice }} - {{ /if }} -
    価格 (KRW) - {{@ price }} - {{ if haslocaleOfficialPrices }} - {{@ officialPrice }} - {{ /if }} -
    価格 (RMB) - {{@ price }} - {{ if haslocaleOfficialPrices }} - {{@ officialPrice }} - {{ /if }} -
    価格 (TWD) - {{@ price }} - {{ if haslocaleOfficialPrices }} - {{@ officialPrice }} - {{ /if }} -
    ポイント{{ defaultPoint }} pt
    作者 - {{ each authors a }} - {{ a.name }} - {{ /each }} -
    発売日{{ updatedDate | formatDate 'YYYY-MM-DD HH:mm' }}
    販売日{{ pubDate | formatDate 'YYYY-MM-DD HH:mm' }}
    作品形式 - {{ each workCategories w }} - {{ w.text }},  - {{ /each }} -
    ジャンル - {{ each searchTags s }} - {{ s.text }},  - {{ /each }} -
    概要{{ description }}
    -{{ if images }} - {{ each images image }} - - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dlsite/templates/description.tsx b/lib/routes/dlsite/templates/description.tsx new file mode 100644 index 000000000..eda7deaa0 --- /dev/null +++ b/lib/routes/dlsite/templates/description.tsx @@ -0,0 +1,185 @@ +import dayjs from 'dayjs'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Author = { + name?: string; + link?: string; +}; + +type LinkItem = { + text?: string; + link?: string; +}; + +type DescriptionData = { + detail: any; + images?: string[]; + authors?: Author[]; + discountRate?: string; + discountEndDate?: Date; + updatedDate?: Date; + pubDate?: Date; + workCategories?: LinkItem[]; + searchTags?: LinkItem[]; + description?: string; +}; + +const formatDate = (date?: Date) => (date ? dayjs(date).format('YYYY-MM-DD HH:mm') : ''); + +export const renderDescription = ({ detail, images, authors, discountRate, discountEndDate, updatedDate, pubDate, workCategories, searchTags, description }: DescriptionData) => { + const localePrices = detail.locale_price_str ?? {}; + const localeOfficialPrices = detail.locale_official_price_str ?? {}; + const hasOfficialPrice = Boolean(detail.official_price_str); + const hasLocaleOfficialPrices = Boolean(detail.locale_official_price_str); + const price = detail.price_str; + + return renderToString( + <> + + + {discountRate ? ( + + + + + ) : null} + {discountEndDate ? ( + + + + + ) : null} + {price ? ( + + + + + ) : null} + {localePrices.en_US ? ( + + + + + ) : null} + {localePrices.ko_KR ? ( + + + + + ) : null} + {localePrices.zh_CN ? ( + + + + + ) : null} + {localePrices.zh_TW ? ( + + + + + ) : null} + {detail.default_point_str ? ( + + + + + ) : null} + {authors?.length ? ( + + + + + ) : null} + {updatedDate ? ( + + + + + ) : null} + {pubDate ? ( + + + + + ) : null} + {workCategories?.length ? ( + + + + + ) : null} + {searchTags?.length ? ( + + + + + ) : null} + {description ? ( + + + + + ) : null} + +
    割引{discountRate}%
    割引終了時間{formatDate(discountEndDate)}
    価格 (JPY) + {raw(price)} +  円   + {hasOfficialPrice ? ( + + + {raw(detail.official_price_str)} +  円 + + + ) : null} +
    価格 (USD) + {raw(localePrices.en_US)} + {hasLocaleOfficialPrices ? ( + + {raw(localeOfficialPrices.en_US)} + + ) : null} +
    価格 (KRW) + {raw(localePrices.ko_KR)} + {hasLocaleOfficialPrices ? ( + + {raw(localeOfficialPrices.ko_KR)} + + ) : null} +
    価格 (RMB) + {raw(localePrices.zh_CN)} + {hasLocaleOfficialPrices ? ( + + {raw(localeOfficialPrices.zh_CN)} + + ) : null} +
    価格 (TWD) + {raw(localePrices.zh_TW)} + {hasLocaleOfficialPrices ? ( + + {raw(localeOfficialPrices.zh_TW)} + + ) : null} +
    ポイント{detail.default_point_str} pt
    作者 + {authors.map((author) => ( + {author.name} + ))} +
    発売日{formatDate(updatedDate)}
    販売日{formatDate(pubDate)}
    作品形式 + {workCategories.map((category) => ( + <> + {category.text},  + + ))} +
    ジャンル + {searchTags.map((tag) => ( + <> + {tag.text},  + + ))} +
    概要{description}
    + {images?.length ? images.map((image) => ) : null} + + ); +}; diff --git a/lib/routes/dlsite/utils.ts b/lib/routes/dlsite/utils.ts index 1ee4e405a..069881a79 100644 --- a/lib/routes/dlsite/utils.ts +++ b/lib/routes/dlsite/utils.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import dayjs from 'dayjs'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + const rootUrl = 'https://www.dlsite.com'; const defaultFilters = { @@ -17,8 +16,6 @@ const defaultFilters = { per_page: 100, }; -const formatDate = (date, format) => dayjs(date).format(format); - const addFilters = (url, filters) => { const keys = Object.keys(filters); const filterStr = keys.map((k) => `/${k}/${filters[k]}`).join(''); @@ -46,8 +43,6 @@ const getDetails = async (works) => { }; const ProcessItems = async (ctx) => { - art.defaults.imports.formatDate = formatDate; - const subPath = getSubPath(ctx) === '/' ? '/home/new' : getSubPath(ctx); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 100; @@ -149,7 +144,7 @@ const ProcessItems = async (ctx) => { author: authors.map((a) => a.name).join(' / '), category: [...workCategories.map((i) => i.text), ...workGenres.map((i) => i.text), ...searchTags.map((i) => i.text), ...nameTags.map((i) => i.text)], guid: `dlsite-${guid}`, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ detail, images, authors, @@ -158,7 +153,6 @@ const ProcessItems = async (ctx) => { updatedDate, pubDate, workCategories, - workGenres, searchTags, description, }), diff --git a/lib/routes/dn/news.ts b/lib/routes/dn/news.ts index a96ba8729..5d70ba919 100644 --- a/lib/routes/dn/news.ts +++ b/lib/routes/dn/news.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/:language/news/:category?', categories: ['new-media'], @@ -63,7 +62,7 @@ async function handler(ctx) { return { title: item.find('h2.ellipse2').text(), link: new URL(item.prop('href'), rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: image ? { src: image.prop('src'), @@ -88,7 +87,7 @@ async function handler(ctx) { const content = load(detailResponse); item.title = content('h1.tit').text(); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ abstracts: content('div.abstract').html(), description: content('div.detail').html(), }); diff --git a/lib/routes/dn/templates/description.art b/lib/routes/dn/templates/description.art deleted file mode 100644 index d8a7f322b..000000000 --- a/lib/routes/dn/templates/description.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if image }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if abstracts }} - - {{@ abstracts }} - -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dn/templates/description.tsx b/lib/routes/dn/templates/description.tsx new file mode 100644 index 000000000..d879e64f4 --- /dev/null +++ b/lib/routes/dn/templates/description.tsx @@ -0,0 +1,25 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: { + src: string; + alt?: string; + }; + abstracts?: string; + description?: string; +}; + +const DnDescription = ({ image, abstracts, description }: DescriptionData) => ( + <> + {image ? ( +
    + {image.alt} +
    + ) : null} + {abstracts ? {raw(abstracts)} : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/douban/movie/coming.ts b/lib/routes/douban/movie/coming.tsx similarity index 67% rename from lib/routes/douban/movie/coming.ts rename to lib/routes/douban/movie/coming.tsx index 0a24716f1..8737401df 100644 --- a/lib/routes/douban/movie/coming.ts +++ b/lib/routes/douban/movie/coming.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/movie/coming', @@ -27,8 +26,26 @@ export const route: Route = { ], handler, }; -const renderDescription = (info: { title?: string; cover_url?: string; pubdate?: string[]; intro?: string; directors?: string[]; actors?: string[]; genres: string[]; wish_count?: number | string }) => - art(path.join(__dirname, '../templates/movie_coming.art'), info); +const renderDescription = (info: { title?: string; cover_url?: string; pubdate?: string[]; intro?: string; directors?: string[]; actors?: string[]; genres: string[]; wish_count?: number | string }): string => + renderToString( + <> + {info.cover_url && info.title ? {info.title} : null} +

    电影信息

    +
      + {info.directors?.length ?
    • 导演:{info.directors.join(', ')}
    • : null} + {info.actors?.length ?
    • 演员:{info.actors.join(', ')}
    • : null} + {info.genres?.length ?
    • 类型:{info.genres.join(' / ')}
    • : null} + {info.pubdate?.length ?
    • 上映日期:{info.pubdate.join(' / ')}
    • : null} + {info.wish_count ?
    • 想看:{info.wish_count}
    • : null} +
    + {info.intro ? ( + <> +

    剧情简介

    +

    {info.intro}

    + + ) : null} + + ); async function handler(ctx) { const response = await got({ diff --git a/lib/routes/douban/other/explore.ts b/lib/routes/douban/other/explore.tsx similarity index 80% rename from lib/routes/douban/other/explore.ts rename to lib/routes/douban/other/explore.tsx index e0f2d0577..69de1e29f 100644 --- a/lib/routes/douban/other/explore.ts +++ b/lib/routes/douban/other/explore.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/explore', @@ -55,7 +53,7 @@ async function handler() { return { title, author, - description: art(path.join(__dirname, '../templates/explore.art'), { + description: renderDescription({ author, desc, itemPic, @@ -65,3 +63,14 @@ async function handler() { }), }; } + +const renderDescription = ({ author, desc, itemPic }: { author: string; desc: string; itemPic?: string }): string => + renderToString( + <> + 作者:{author} +
    + 描述:{desc} +
    + {itemPic ? : null} + + ); diff --git a/lib/routes/douban/other/list.ts b/lib/routes/douban/other/list.ts index e4e36eb30..24a48bd27 100644 --- a/lib/routes/douban/other/list.ts +++ b/lib/routes/douban/other/list.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; import { fallback, queryToFloat, queryToInteger } from '@/utils/readable-social'; -import { art } from '@/utils/render'; + +import { renderListDescription } from '../templates/list-description'; export const route: Route = { path: '/list/:type?/:routeParams?', @@ -95,7 +94,7 @@ async function handler(ctx) { .map((item) => { const title = item.title; const link = item.url; - const description = art(path.join(__dirname, '../templates/list_description.art'), { + const description = renderListDescription({ ranking_value: item.ranking_value, title, original_title: item.original_title, diff --git a/lib/routes/douban/other/recommended.ts b/lib/routes/douban/other/recommended.ts index 34965de88..61f859716 100644 --- a/lib/routes/douban/other/recommended.ts +++ b/lib/routes/douban/other/recommended.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; import { fallback, queryToInteger } from '@/utils/readable-social'; -import { art } from '@/utils/render'; + +import { renderListDescription } from '../templates/list-description'; export const route: Route = { path: '/recommended/:type?/:routeParams?', @@ -76,7 +75,7 @@ async function handler(ctx) { .map((item) => { const title = item.title; const link = item.url; - const description = art(path.join(__dirname, '../templates/list_description.art'), { + const description = renderListDescription({ ranking_value: item.ranking_value, title, original_title: item.original_title, diff --git a/lib/routes/douban/other/weekly-best.ts b/lib/routes/douban/other/weekly-best.tsx similarity index 73% rename from lib/routes/douban/other/weekly-best.ts rename to lib/routes/douban/other/weekly-best.tsx index 00120e022..3909b5dd4 100644 --- a/lib/routes/douban/other/weekly-best.ts +++ b/lib/routes/douban/other/weekly-best.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/movie/weekly/:type?', @@ -61,7 +60,7 @@ async function handler(ctx) { return { title, - description: art(path.join(__dirname, '../templates/weekly_best.art'), { + description: renderDescription({ title, card_subtitle, description, @@ -74,3 +73,19 @@ async function handler(ctx) { }), }; } + +const renderDescription = ({ title, rate, card_subtitle, description, cover_url, photos }: { title: string; rate?: string; card_subtitle?: string; description?: string; cover_url?: string; photos?: string[] }): string => + renderToString( + <> +

    标题:{title}

    +

    评分:{rate}

    +

    标签:{card_subtitle}

    +

    影片信息:{description}

    +

    + {cover_url ? : null} + {photos?.map((photo, index) => ( + + ))} +

    + + ); diff --git a/lib/routes/douban/templates/explore.art b/lib/routes/douban/templates/explore.art deleted file mode 100644 index 13d5b89c6..000000000 --- a/lib/routes/douban/templates/explore.art +++ /dev/null @@ -1,7 +0,0 @@ -作者:{{author}} -
    -描述:{{desc}} -
    -{{ if itemPic }} - -{{ /if }} diff --git a/lib/routes/douban/templates/list-description.tsx b/lib/routes/douban/templates/list-description.tsx new file mode 100644 index 000000000..185bc5200 --- /dev/null +++ b/lib/routes/douban/templates/list-description.tsx @@ -0,0 +1,25 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type ListDescriptionProps = { + ranking_value?: string; + title: string; + original_title?: string; + rate?: string; + card_subtitle?: string; + description?: string; + cover?: string; +}; + +const ListDescription = ({ ranking_value, title, original_title, rate, card_subtitle, description, cover }: ListDescriptionProps) => ( + <> + {ranking_value ?

    {ranking_value}

    : null} +

    {title}

    + {original_title ?

    {original_title}

    : null} + {rate ?

    {rate}

    : null} + {card_subtitle ?

    {card_subtitle}

    : null} + {description ?

    {description}

    : null} + {cover ? : null} + +); + +export const renderListDescription = (props: ListDescriptionProps): string => renderToString(); diff --git a/lib/routes/douban/templates/list_description.art b/lib/routes/douban/templates/list_description.art deleted file mode 100644 index 169b03dd9..000000000 --- a/lib/routes/douban/templates/list_description.art +++ /dev/null @@ -1,25 +0,0 @@ -{{ if ranking_value }} -

    {{ ranking_value }}

    -{{ /if }} - -

    {{ title }}

    - -{{ if original_title }} -

    {{ original_title }}

    -{{ /if }} - -{{ if rate }} -

    {{ rate }}

    -{{ /if }} - -{{ if card_subtitle }} -

    {{ card_subtitle }}

    -{{ /if }} - -{{ if description }} -

    {{ description }}

    -{{ /if }} - -{{ if cover }} - -{{ /if }} diff --git a/lib/routes/douban/templates/movie_coming.art b/lib/routes/douban/templates/movie_coming.art deleted file mode 100644 index 9ecf3ae89..000000000 --- a/lib/routes/douban/templates/movie_coming.art +++ /dev/null @@ -1,27 +0,0 @@ -{{if cover_url && title}} -{{title}} -{{/if}} - -

    电影信息

    -
      -{{if directors}} -
    • 导演:{{directors.join(', ')}}
    • -{{/if}} -{{if actors}} -
    • 演员:{{actors.join(', ')}}
    • -{{/if}} -{{if genres}} -
    • 类型:{{genres.join(' / ')}}
    • -{{/if}} -{{if pubdate}} -
    • 上映日期:{{pubdate.join(' / ')}}
    • -{{/if}} -{{if wish_count}} -
    • 想看:{{wish_count}}
    • -{{/if}} -
    - -{{ if intro }} -

    剧情简介

    -

    {{ intro }}

    -{{/if}} diff --git a/lib/routes/douban/templates/weekly_best.art b/lib/routes/douban/templates/weekly_best.art deleted file mode 100644 index d7151b645..000000000 --- a/lib/routes/douban/templates/weekly_best.art +++ /dev/null @@ -1,10 +0,0 @@ -

    标题:{{title}}

    -

    评分:{{rate}}

    -

    标签:{{card_subtitle}}

    -

    影片信息:{{description}}

    -

    - - {{each photos}} - - {{/each}} -

    diff --git a/lib/routes/douyin/hashtag.ts b/lib/routes/douyin/hashtag.ts index b88b79ce5..9c0db5e78 100644 --- a/lib/routes/douyin/hashtag.ts +++ b/lib/routes/douyin/hashtag.ts @@ -5,7 +5,6 @@ import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; import { fallback, queryToBoolean } from '@/utils/readable-social'; -import { art } from '@/utils/render'; import { getOriginAvatar, proxyVideo, resolveUrl, templates } from './utils'; @@ -99,9 +98,9 @@ async function handler(ctx) { // render description const desc = post.desc && post.desc.replaceAll('\n', '
    '); - let media = art(embed && videoList ? templates.embed : templates.cover, { img, videoList, duration }); - media = embed && videoList && iframe ? art(templates.iframe, { content: media }) : media; // warp in iframe - const description = art(templates.desc, { desc, media }); + let media = (embed && videoList ? templates.embed : templates.cover)({ img, videoList, duration }); + media = embed && videoList && iframe ? templates.iframe({ content: media }) : media; // warp in iframe + const description = templates.desc({ desc, media }); return { title: post.desc, diff --git a/lib/routes/douyin/templates/cover.art b/lib/routes/douyin/templates/cover.art deleted file mode 100644 index 70173690d..000000000 --- a/lib/routes/douyin/templates/cover.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if img }} - {{ if videoList }} - - {{ /if }} - - {{ if videoList }} - - {{ /if }} -{{ /if }} -{{ if img && videoList }} -

    -{{ /if }} -{{ if videoList }} - 视频直链 -{{ /if }} diff --git a/lib/routes/douyin/templates/cover.tsx b/lib/routes/douyin/templates/cover.tsx new file mode 100644 index 000000000..4e72c8823 --- /dev/null +++ b/lib/routes/douyin/templates/cover.tsx @@ -0,0 +1,35 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type CoverData = { + img?: string; + videoList?: string[]; +}; + +export const renderCover = ({ img, videoList }: CoverData): string => { + const videoUrl = videoList?.[0]; + + return renderToString( + <> + {img ? ( + videoUrl ? ( + + + + ) : ( + + ) + ) : null} + {img && videoUrl ? ( + <> +
    +
    + + ) : null} + {videoUrl ? ( + + 视频直链 + + ) : null} + + ); +}; diff --git a/lib/routes/douyin/templates/desc.art b/lib/routes/douyin/templates/desc.art deleted file mode 100644 index fe68224ce..000000000 --- a/lib/routes/douyin/templates/desc.art +++ /dev/null @@ -1,3 +0,0 @@ -{{@ desc }} -

    -{{@ media }} diff --git a/lib/routes/douyin/templates/desc.tsx b/lib/routes/douyin/templates/desc.tsx new file mode 100644 index 000000000..d8a7957fa --- /dev/null +++ b/lib/routes/douyin/templates/desc.tsx @@ -0,0 +1,17 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescData = { + desc: string; + media: string; +}; + +export const renderDesc = ({ desc, media }: DescData): string => + renderToString( + <> + {raw(desc)} +
    +
    + {raw(media)} + + ); diff --git a/lib/routes/douyin/templates/embed.art b/lib/routes/douyin/templates/embed.art deleted file mode 100644 index e8f32e690..000000000 --- a/lib/routes/douyin/templates/embed.art +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/lib/routes/douyin/templates/embed.tsx b/lib/routes/douyin/templates/embed.tsx new file mode 100644 index 000000000..b0efdbbc5 --- /dev/null +++ b/lib/routes/douyin/templates/embed.tsx @@ -0,0 +1,16 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type EmbedData = { + img?: string; + duration?: string; + videoList?: string[]; +}; + +export const renderEmbed = ({ img, duration, videoList = [] }: EmbedData): string => + renderToString( + + ); diff --git a/lib/routes/douyin/templates/iframe.art b/lib/routes/douyin/templates/iframe.art deleted file mode 100644 index 792a73e8f..000000000 --- a/lib/routes/douyin/templates/iframe.art +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/lib/routes/douyin/templates/iframe.tsx b/lib/routes/douyin/templates/iframe.tsx new file mode 100644 index 000000000..42a965745 --- /dev/null +++ b/lib/routes/douyin/templates/iframe.tsx @@ -0,0 +1,11 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type IframeData = { + content: string; +}; + +export const renderIframe = ({ content }: IframeData): string => { + const srcdoc = `${content}`; + + return renderToString(); +}; diff --git a/lib/routes/douyin/user.ts b/lib/routes/douyin/user.ts index 2ac99cfec..179e1d5f7 100644 --- a/lib/routes/douyin/user.ts +++ b/lib/routes/douyin/user.ts @@ -6,7 +6,6 @@ import logger from '@/utils/logger'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; import { fallback, queryToBoolean } from '@/utils/readable-social'; -import { art } from '@/utils/render'; import type { PostData } from './types'; import { getOriginAvatar, proxyVideo, resolveUrl, templates } from './utils'; @@ -110,9 +109,9 @@ async function handler(ctx) { // render description const desc = post.desc?.replaceAll('\n', '
    '); - let media = art(embed && videoList ? templates.embed : templates.cover, { img, videoList, duration }); - media = embed && videoList && iframe ? art(templates.iframe, { content: media }) : media; // warp in iframe - const description = art(templates.desc, { desc, media }); + let media = (embed && videoList ? templates.embed : templates.cover)({ img, videoList, duration }); + media = embed && videoList && iframe ? templates.iframe({ content: media }) : media; // warp in iframe + const description = templates.desc({ desc, media }); return { title: post.desc.split('\n')[0], diff --git a/lib/routes/douyin/utils.ts b/lib/routes/douyin/utils.ts index 95bce56c5..4776659d1 100644 --- a/lib/routes/douyin/utils.ts +++ b/lib/routes/douyin/utils.ts @@ -1,10 +1,13 @@ -import path from 'node:path'; +import { renderCover } from './templates/cover'; +import { renderDesc } from './templates/desc'; +import { renderEmbed } from './templates/embed'; +import { renderIframe } from './templates/iframe'; const templates = { - desc: path.join(__dirname, 'templates/desc.art'), - cover: path.join(__dirname, 'templates/cover.art'), - embed: path.join(__dirname, 'templates/embed.art'), - iframe: path.join(__dirname, 'templates/iframe.art'), + desc: renderDesc, + cover: renderCover, + embed: renderEmbed, + iframe: renderIframe, }; const resolveUrl = (url, tls = true, forceResolve = false) => { diff --git a/lib/routes/douyu/group.ts b/lib/routes/douyu/group.ts index 3a4f26f6a..93893ebff 100644 --- a/lib/routes/douyu/group.ts +++ b/lib/routes/douyu/group.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/group/:id/:sort?', categories: ['bbs'], @@ -56,7 +55,7 @@ async function handler(ctx) { title: item.title, link: `${rootUrl}/p/${item.post_id}`, pubDate: timezone(parseDate(item.created_at_std), +8), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ content: item.describe, images: item.imglist.map((i) => ({ size: i.size, diff --git a/lib/routes/douyu/post.ts b/lib/routes/douyu/post.ts index 153690ba2..1dfa1364a 100644 --- a/lib/routes/douyu/post.ts +++ b/lib/routes/douyu/post.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/post/:id', @@ -59,7 +58,7 @@ async function handler(ctx) { title: `${item.nick_name}: ${item.content}`, link: `${currentUrl}#${item.comment_id}${item.sub_replies.length > 0 ? `+${item.sub_replies.map((r) => r.comment_id).join('+')}` : ''}`, pubDate: parseDate(item.created_ts * 1000), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ content: item.content, images: item.imglist.map((i) => ({ size: i.size, diff --git a/lib/routes/douyu/templates/description.art b/lib/routes/douyu/templates/description.art deleted file mode 100644 index f935538bd..000000000 --- a/lib/routes/douyu/templates/description.art +++ /dev/null @@ -1,14 +0,0 @@ -{{ if content }} -{{ content }} -{{ /if }} -{{ if images }} -{{ each images image }} - -{{ /each }} -{{ /if }} -{{ if replies }} -

    回复

    -{{ each replies reply }} -

    {{ reply.nickname }} [{{ reply.time }}]: {{ reply.content }}

    -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/douyu/templates/description.tsx b/lib/routes/douyu/templates/description.tsx new file mode 100644 index 000000000..66532ae95 --- /dev/null +++ b/lib/routes/douyu/templates/description.tsx @@ -0,0 +1,41 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + url?: string; + size?: { + w?: number | string; + h?: number | string; + }; +}; + +type DescriptionReply = { + nickname?: string; + time?: string; + content?: string; +}; + +type DescriptionRenderOptions = { + content?: string; + images?: DescriptionImage[]; + replies?: DescriptionReply[]; +}; + +export const renderDescription = ({ content, images, replies }: DescriptionRenderOptions): string => + renderToString( + <> + {content ? <>{content} : null} + {images?.map((image) => ( + + ))} + {replies?.length ? ( + <> +

    回复

    + {replies.map((reply) => ( +

    + {reply.nickname} [{reply.time}]: {reply.content} +

    + ))} + + ) : null} + + ); diff --git a/lib/routes/dribbble/templates/description.art b/lib/routes/dribbble/templates/description.art deleted file mode 100644 index 832abcd85..000000000 --- a/lib/routes/dribbble/templates/description.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if shotMedia }} - {{@ shotMedia }}
    -{{ /if }} - -{{ if description.length }} - {{@ description.html() }}
    -{{ /if }} - -{{ if shotData.likesCount }} - {{ shotData.likesCount }} likes
    -{{ /if }} - -{{ if shotData.savesCount }} - {{ shotData.savesCount }} saves -{{ /if }} diff --git a/lib/routes/dribbble/utils.ts b/lib/routes/dribbble/utils.tsx similarity index 83% rename from lib/routes/dribbble/utils.ts rename to lib/routes/dribbble/utils.tsx index 274a20f87..c7438b556 100644 --- a/lib/routes/dribbble/utils.ts +++ b/lib/routes/dribbble/utils.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const host = 'https://dribbble.com'; @@ -72,10 +71,10 @@ async function loadContent(link) { const shotDescription = $('.shot-description-container'); const author = `${shotData.shotUser.name}${shotData.shotUser.team.length ? ` for ${shotData.shotUser.team.name}` : ''}`; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderShotDescription({ shotMedia, shotData, - description: shotDescription, + descriptionHtml: shotDescription.length ? shotDescription.html() : undefined, }); // Get the text content of the element with class 'shot-date' and convert it to a UTC string representation of a date @@ -90,6 +89,31 @@ async function loadContent(link) { }; } +const renderShotDescription = ({ shotMedia, shotData, descriptionHtml }: { shotMedia?: string; shotData: any; descriptionHtml?: string }): string => + renderToString( + <> + {shotMedia ? ( + <> + {raw(shotMedia)} +
    + + ) : null} + {descriptionHtml ? ( + <> + {raw(descriptionHtml)} +
    + + ) : null} + {shotData.likesCount ? ( + <> + {shotData.likesCount} likes +
    + + ) : null} + {shotData.savesCount ? <>{shotData.savesCount} saves : null} + + ); + // Refactored code with comments for clarity function ProcessFeed(list) { diff --git a/lib/routes/duozhi/index.ts b/lib/routes/duozhi/index.ts index 2a6b73919..22646a90c 100644 --- a/lib/routes/duozhi/index.ts +++ b/lib/routes/duozhi/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { category } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -38,7 +37,7 @@ export const handler = async (ctx: Context): Promise => { .find('a.post-img') .attr('style') ?.match(/url\(['"]?(.*?)['"]?\)?/)?.[1]; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -98,7 +97,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1.subject-title').text(); const image: string | undefined = $$('div.subject-banner img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { diff --git a/lib/routes/duozhi/templates/description.art b/lib/routes/duozhi/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/duozhi/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/duozhi/templates/description.tsx b/lib/routes/duozhi/templates/description.tsx new file mode 100644 index 000000000..528e99d13 --- /dev/null +++ b/lib/routes/duozhi/templates/description.tsx @@ -0,0 +1,27 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + description?: string; +}; + +const Description = ({ images, description }: DescriptionProps) => ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/duozhuayu/search.ts b/lib/routes/duozhuayu/search.tsx similarity index 68% rename from lib/routes/duozhuayu/search.ts rename to lib/routes/duozhuayu/search.tsx index e61b89d29..35fed22ec 100644 --- a/lib/routes/duozhuayu/search.ts +++ b/lib/routes/duozhuayu/search.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import aesjs from 'aes-js'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/search/:wd', @@ -81,7 +79,37 @@ async function handler(ctx) { title: item.title, link: `${baseUrl}/books/${item.id}`, pubDate: parseDate(item.updated), // 2023-05-07T13:33:09+08:00 - description: art(path.join(__dirname, 'templates/book.art'), { item }), + description: renderToString( +
    + +
    + 书名:{item.title} {item.originalTitle} +
    + {item.subtitle ? ( + <> + {item.subtitle} +
    + + ) : null} + 作者:{item.rawAuthor} +
    + {item.translators?.length ? ( + <> + 译者:{item.translators.map((translator) => translator.name).join(' / ')} +
    + + ) : null} + ISBN:{item.isbn13} +
    + 出版社:{item.publisher} +
    + 出版时间:{item.publishDate} +
    + 豆瓣评分:{item.doubanRating} +
    + 价格:{(item.price / 100).toFixed(2)}元起 {(item.originalPrice / 100).toFixed(2)}元 +
    + ), })); return { diff --git a/lib/routes/duozhuayu/templates/book.art b/lib/routes/duozhuayu/templates/book.art deleted file mode 100644 index 4b59cdcbc..000000000 --- a/lib/routes/duozhuayu/templates/book.art +++ /dev/null @@ -1,10 +0,0 @@ -
    -书名:{{ item.title }} {{ item.originalTitle }}
    -{{ if item.subtitle }}{{ item.subtitle }}
    {{ /if }} -作者:{{ item.rawAuthor }}
    -{{ if item.translators && item.translators.length }}译者:{{ item.translators.map((t) => t.name).join(' / ') }}
    {{ /if }} -ISBN:{{ item.isbn13 }}
    -出版社:{{ item.publisher }}
    -出版时间:{{ item.publishDate }}
    -豆瓣评分:{{ item.doubanRating }}
    -价格:{{ (item.price / 100).toFixed(2) }}元起 {{ (item.originalPrice / 100).toFixed(2) }}元 diff --git a/lib/routes/dushu/fuzhou/index.ts b/lib/routes/dushu/fuzhou/index.tsx similarity index 76% rename from lib/routes/dushu/fuzhou/index.ts rename to lib/routes/dushu/fuzhou/index.tsx index f66d27f49..d72532ca5 100644 --- a/lib/routes/dushu/fuzhou/index.ts +++ b/lib/routes/dushu/fuzhou/index.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; const host = 'https://gateway-api-ipv4.dushu365.com/compose-orch/offlineActivity/v100/activity/list'; const itemLink = 'https://card.dushu.io/requirement/offline-activity/activity-detail/v/index.html'; @@ -59,9 +58,19 @@ async function handler() { item: data.map((item) => ({ title: item.activityName, link: itemLink + '?productId=' + item.activityId + '&type=' + item.type, - description: art(path.join(__dirname, 'templates/message.art'), { - item, - }), + description: renderToString( + <> + {`地区:${item.areaName}`} +
    + {`地点:${item.address}`} +
    + {`开始时间: ${item.startTime}`} +
    + {`结束时间: ${item.endTime}`} +
    + + + ), })), }; } diff --git a/lib/routes/dushu/fuzhou/templates/message.art b/lib/routes/dushu/fuzhou/templates/message.art deleted file mode 100644 index 1574c4e04..000000000 --- a/lib/routes/dushu/fuzhou/templates/message.art +++ /dev/null @@ -1,9 +0,0 @@ -地区:{{item.areaName}} -
    -地点:{{item.address}} -
    -开始时间: {{item.startTime}} -
    -结束时间: {{item.endTime}} -
    - diff --git a/lib/routes/dw/templates/description.art b/lib/routes/dw/templates/description.art deleted file mode 100644 index bc96a9c68..000000000 --- a/lib/routes/dw/templates/description.art +++ /dev/null @@ -1,23 +0,0 @@ -{{ if teaser }} -

    {{ teaser }}

    -{{ /if }} -{{ if video }} - {{@ video }} -{{ else if mainImage }} -
    - {{ mainImage.additionalInformation }} -
    - {{ mainImage.description }} - {{ imageI18n }}: {{ mainImage.target.licenserSupplement }} -
    -
    -{{ /if }} -{{ if text }} - {{@ text }} -{{ /if }} -{{ if liveblog }} - {{@ liveblog }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dw/templates/liveblog.art b/lib/routes/dw/templates/liveblog.art deleted file mode 100644 index d133a1ed3..000000000 --- a/lib/routes/dw/templates/liveblog.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if posts }} -{{ each posts }} -
    - {{ if $value.localizedContentDate }}

    {{ $value.localizedContentDate }}

    {{ /if }} - {{ if $value.title }}

    {{ $value.title }}

    {{ /if }} - {{ if $value.persons }} - {{ each $value.persons }} -

    {{ $value.fullName }}

    - {{ /each }} - {{ /if }} - {{ if $value.text }}{{@ $value.text }}{{ /if }} -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/dw/templates/video.art b/lib/routes/dw/templates/video.art deleted file mode 100644 index 7a6060404..000000000 --- a/lib/routes/dw/templates/video.art +++ /dev/null @@ -1,14 +0,0 @@ -{{ if hlsVideoSrc }} - -{{ /if }} diff --git a/lib/routes/dw/utils.ts b/lib/routes/dw/utils.tsx similarity index 69% rename from lib/routes/dw/utils.ts rename to lib/routes/dw/utils.tsx index 852251366..15eb2309f 100644 --- a/lib/routes/dw/utils.ts +++ b/lib/routes/dw/utils.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const formatId = '605'; @@ -86,6 +85,67 @@ const i18n = (word: string, lang: string) => { const m3u8tomp4 = (src: string) => src.replace('https://hlsvod.dw.com/i/', 'https://tvdownloaddw-a.akamaihd.net/').replace(',AVC_480x270,AVC_512x288,AVC_640x360,AVC_960x540,AVC_1280x720,AVC_1920x1080,.mp4.csmil/master.m3u8', 'AVC_1920x1080.mp4'); +const renderLiveblog = (posts) => + renderToString( + <> + {posts?.map((post) => ( + <> +
    + {post.localizedContentDate ? ( +

    + {post.localizedContentDate} +

    + ) : null} + {post.title ?

    {post.title}

    : null} + {post.persons + ? post.persons.map((person) => ( +

    + {person.fullName} +

    + )) + : null} + {post.text ? <>{raw(post.text)} : null} + + ))} + + ); + +const renderVideo = ({ hlsVideoSrc, mp4VideoSrc, posterImageUrl }) => + renderToString( + + ); + +const renderDescription = ({ teaser, video, mainImage, text, liveblog, imageI18n }) => + renderToString( + <> + {teaser ? ( +
    +

    + {teaser} +

    +
    + ) : null} + {video ? ( + <>{raw(video)} + ) : mainImage ? ( +
    + {mainImage.additionalInformation} +
    + {mainImage.description} + + {imageI18n}: {mainImage.target.licenserSupplement} + +
    +
    + ) : null} + {text ? <>{raw(text)} : null} + {liveblog ? <>{raw(liveblog)} : null} + + ); + const processHtml = ($: CheerioAPI, contentLinks) => { $('img').each((_, elem) => { try { @@ -123,24 +183,24 @@ const processContent = (item, content) => { processHtml($text, content.contentLinks); const liveblog = item.type === 'liveblog' && content.posts - ? art(path.join(__dirname, 'templates/liveblog.art'), { - posts: content.posts.map((post) => { + ? renderLiveblog( + content.posts.map((post) => { const $post = load(post.text); processHtml($post, content.contentLinks); post.text = $post.html(); return post; - }), - }) + }) + ) : undefined; const video = item.type === 'video' && content.hlsVideoSrc - ? art(path.join(__dirname, 'templates/video.art'), { + ? renderVideo({ hlsVideoSrc: content.hlsVideoSrc, mp4VideoSrc: m3u8tomp4(content.hlsVideoSrc), posterImageUrl: content.posterImageUrl, }) : undefined; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ teaser: content.teaser, video, mainImage: $text(`[data-id="${content.mainContentImageLink?.targetId}"]`).length === 0 ? content.mainContentImageLink : undefined, @@ -148,7 +208,6 @@ const processContent = (item, content) => { text: $text.html(), liveblog, imageI18n: i18n('Image', item.language), - formatId, }); if (content.trackingCategories) { item.category = content.trackingCategories; diff --git a/lib/routes/e-hentai/index.ts b/lib/routes/e-hentai/index.tsx similarity index 92% rename from lib/routes/e-hentai/index.ts rename to lib/routes/e-hentai/index.tsx index de5ec45c8..a426901f8 100644 --- a/lib/routes/e-hentai/index.ts +++ b/lib/routes/e-hentai/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:what?/:id?/:needTorrents?/:needImages?', @@ -115,7 +113,15 @@ async function handler(ctx) { ); cache.set(item.link, images); } - item.description += art(path.join(__dirname, 'templates/images.art'), { images }); + item.description += renderToString( + <> + {images.map((image) => ( +
    + +
    + ))} + + ); } return item; }) diff --git a/lib/routes/e-hentai/templates/images.art b/lib/routes/e-hentai/templates/images.art deleted file mode 100644 index ab31356f8..000000000 --- a/lib/routes/e-hentai/templates/images.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each images image }} -
    -{{ /each }} \ No newline at end of file diff --git a/lib/routes/eastmoney/report/index.ts b/lib/routes/eastmoney/report/index.tsx similarity index 64% rename from lib/routes/eastmoney/report/index.ts rename to lib/routes/eastmoney/report/index.tsx index aa22c95e9..f7839c753 100644 --- a/lib/routes/eastmoney/report/index.ts +++ b/lib/routes/eastmoney/report/index.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { getEpsOrPeStr, getRatingChangeStr } from '../utils'; @@ -99,7 +98,7 @@ async function handler(ctx) { const $ = load(response); if (category === 'stock') { - const { title, stockName, stockCode, emRatingName, ratingChange, orgSName, indvInduName } = tempOriginItem; + const { title, stockName, stockCode, emRatingName, ratingChange, indvInduName } = tempOriginItem; const ratingChangeStr = getRatingChangeStr(ratingChange); const currentYear = new Date().getFullYear(); const nextYear = currentYear + 1; @@ -111,23 +110,45 @@ async function handler(ctx) { const predictNextYearPe = getEpsOrPeStr(tempOriginItem.predictNextYearPe, 2); item.enclosure_url = enclosureUrl; - item.description = art(path.join(__dirname, '../templates/stock_description.art'), { - title, - stockName, - stockCode, - emRatingName, - ratingChangeStr, - description, - orgSName, - predictThisYearEps, - predictThisYearPe, - predictNextYearEps, - predictNextYearPe, - indvInduName, - currentYear, - nextYear, - enclosureUrl, - }); + item.description = renderToString( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    股票代码股票简称报告名称东财评级评级变动{currentYear}盈利预测{nextYear}盈利预测行业
    收益市盈率收益市盈率
    {stockCode}{stockName} + {title} + {emRatingName}{ratingChangeStr}{predictThisYearEps}{predictThisYearPe}{predictNextYearEps}{predictNextYearPe}{indvInduName}
    +
    {description ? raw(description) : null}
    + + ); } else { item.link = $('.pdf-link').attr('href'); item.description = $('.ctx-content').text(); diff --git a/lib/routes/eastmoney/templates/stock_description.art b/lib/routes/eastmoney/templates/stock_description.art deleted file mode 100644 index 45ff4a8b8..000000000 --- a/lib/routes/eastmoney/templates/stock_description.art +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    股票代码股票简称报告名称东财评级评级变动{{ currentYear }}盈利预测{{ nextYear }}盈利预测行业
    收益市盈率收益市盈率
    {{ stockCode }}{{ stockName }}{{ title }}{{ emRatingName }}{{ ratingChangeStr }}{{ predictThisYearEps }}{{ predictThisYearPe }}{{ predictNextYearEps }}{{ predictNextYearPe }}{{ indvInduName }}
    - -
    - {{@ description }} -
    diff --git a/lib/routes/ecnu/contest.ts b/lib/routes/ecnu/contest.tsx similarity index 84% rename from lib/routes/ecnu/contest.ts rename to lib/routes/ecnu/contest.tsx index d8b40dc4e..4cbe1394b 100644 --- a/lib/routes/ecnu/contest.ts +++ b/lib/routes/ecnu/contest.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/acm/contest/:category?', @@ -52,11 +50,13 @@ async function handler(ctx) { const link = rootUrl + $tdList.find('a').eq(0).attr('href'); return { title, - description: art(path.join(__dirname, 'templates/description.art'), { - title, - startTime, - duration, - }), + description: renderToString( + <> +

    {`Title: ${title}`}

    +

    {`Time: ${startTime} (China time)`}

    +

    {`Duration: ${duration}`}

    + + ), link, }; }); diff --git a/lib/routes/ecnu/templates/description.art b/lib/routes/ecnu/templates/description.art deleted file mode 100644 index 4186e5e90..000000000 --- a/lib/routes/ecnu/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -

    Title: {{title}}

    -

    Time: {{startTime}} (China time)

    -

    Duration: {{duration}}

    diff --git a/lib/routes/eeo/kuaixun.ts b/lib/routes/eeo/kuaixun.ts index 8eb0395ab..cb1419203 100644 --- a/lib/routes/eeo/kuaixun.ts +++ b/lib/routes/eeo/kuaixun.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; @@ -9,9 +7,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10); @@ -39,7 +38,7 @@ export const handler = async (ctx: Context): Promise => { items = response.data.slice(0, limit).map((item): DataItem => { const title: string = item.title; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ intro: item.description, description: item.content, }); @@ -86,8 +85,8 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1').first().text() || $$('h2.title').text() || item.title; const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.xx_boxsing, div#mainBody').html(), + renderDescription({ + description: $$('div.xx_boxsing, div#mainBody').html() || undefined, }); const pubDateStr: string | undefined = $$('h1').next().find('span').first().text() || $$('div.from').text(); const authors: DataItem['author'] = $$('h1').next().contents().first().text() || $$('span.showMoreAuthor').text() || item.author; diff --git a/lib/routes/eeo/templates/description.art b/lib/routes/eeo/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/eeo/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/eeo/templates/description.tsx b/lib/routes/eeo/templates/description.tsx new file mode 100644 index 000000000..5d54604fb --- /dev/null +++ b/lib/routes/eeo/templates/description.tsx @@ -0,0 +1,15 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + intro?: string; + description?: string; +}; + +export const renderDescription = ({ intro, description }: DescriptionProps): string => + renderToString( + <> + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/elsevier/issue.ts b/lib/routes/elsevier/issue.ts index 5d7d992c6..08b89c6fb 100644 --- a/lib/routes/elsevier/issue.ts +++ b/lib/routes/elsevier/issue.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import { CookieJar } from 'tough-cookie'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const cookieJar = new CookieJar(); @@ -50,10 +49,7 @@ async function handler(ctx) { }; }); - const renderDesc = (item) => - art(path.join(__dirname, 'templates/description.art'), { - item, - }); + const renderDesc = (item) => renderDescription(item); const items = await Promise.all( list.map((item) => cache.tryGet(item.link, async () => { diff --git a/lib/routes/elsevier/journal.ts b/lib/routes/elsevier/journal.ts index 63517fd88..c78999647 100644 --- a/lib/routes/elsevier/journal.ts +++ b/lib/routes/elsevier/journal.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import { CookieJar } from 'tough-cookie'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const cookieJar = new CookieJar(); @@ -61,10 +60,7 @@ async function handler(ctx) { }; }); - const renderDesc = (item) => - art(path.join(__dirname, 'templates/description.art'), { - item, - }); + const renderDesc = (item) => renderDescription(item); const items = await Promise.all( list.map((item) => cache.tryGet(item.link, async () => { diff --git a/lib/routes/elsevier/templates/description.art b/lib/routes/elsevier/templates/description.art deleted file mode 100644 index 5c66c4d7c..000000000 --- a/lib/routes/elsevier/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -

    - {{ item.title }}
    -

    -

    - {{ item.authors }}
    - https://doi.org/{{ item.doi }}
    - {{ item.issue }}
    -

    -

    - {{ item.abstract }}
    -

    \ No newline at end of file diff --git a/lib/routes/elsevier/templates/description.tsx b/lib/routes/elsevier/templates/description.tsx new file mode 100644 index 000000000..2688e2a31 --- /dev/null +++ b/lib/routes/elsevier/templates/description.tsx @@ -0,0 +1,45 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionItem = { + title: string; + authors: string; + doi: string; + issue: string; + abstract: string; +}; + +export const renderDescription = (item: DescriptionItem): string => + renderToString( + <> +

    + + {item.title} + +
    +

    +

    + + + {item.authors} + + +
    + + + {`https://doi.org/${item.doi}`} + + +
    + + + {item.issue} + + +
    +

    +

    + {item.abstract} +
    +

    + + ); diff --git a/lib/routes/epicgames/index.ts b/lib/routes/epicgames/index.tsx similarity index 93% rename from lib/routes/epicgames/index.ts rename to lib/routes/epicgames/index.tsx index 6711d06fe..a3beea383 100644 --- a/lib/routes/epicgames/index.ts +++ b/lib/routes/epicgames/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import dayjs from 'dayjs'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/freegames/:locale?/:country?', @@ -109,11 +107,13 @@ async function handler(ctx) { title: item.title, author: item.seller.name, link, - description: art(path.join(__dirname, 'templates/description.art'), { - description, - image, - endDate, - }), + description: renderToString( + <> +

    {description}

    + +

    {`Free Now to ${endDate}`}

    + + ), pubDate: parseDate(item.promotions.promotionalOffers[0].promotionalOffers[0].startDate), }; }); diff --git a/lib/routes/epicgames/templates/description.art b/lib/routes/epicgames/templates/description.art deleted file mode 100644 index 02f13056c..000000000 --- a/lib/routes/epicgames/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -

    {{ description }}

    - -

    Free Now to {{ endDate }}

    diff --git a/lib/routes/eprice/rss.ts b/lib/routes/eprice/rss.tsx similarity index 89% rename from lib/routes/eprice/rss.ts rename to lib/routes/eprice/rss.tsx index 2d65fc05b..28f0aa6bf 100644 --- a/lib/routes/eprice/rss.ts +++ b/lib/routes/eprice/rss.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import parser from '@/utils/rss-parser'; const allowRegion = new Set(['tw', 'hk']); @@ -76,10 +74,12 @@ async function handler(ctx) { e = $(e); if (e.attr('href') && e.attr('href').endsWith('.jpg')) { e.after( - art(path.join(__dirname, 'templates/image.art'), { - alt: e.attr('title'), - src: e.attr('href'), - }) + renderToString( +
    + {e.attr('title') +
    {e.attr('title') ?? ''}
    +
    + ) ); e.remove(); } diff --git a/lib/routes/eprice/templates/image.art b/lib/routes/eprice/templates/image.art deleted file mode 100644 index 0772f82cc..000000000 --- a/lib/routes/eprice/templates/image.art +++ /dev/null @@ -1,4 +0,0 @@ -
    - {{ alt }} -
    {{ alt }}
    -
    diff --git a/lib/routes/eshukan/academic.ts b/lib/routes/eshukan/academic.ts index a8493107e..480cd3040 100644 --- a/lib/routes/eshukan/academic.ts +++ b/lib/routes/eshukan/academic.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx) => { const { id = '1' } = ctx.req.param(); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10; @@ -36,7 +35,7 @@ export const handler = async (ctx) => { item.find('p span').remove(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ intro: item.find('p').text(), }); @@ -60,7 +59,7 @@ export const handler = async (ctx) => { const $$ = load(detailResponse); const title = $$('h1').text(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ intro: $$('div.summary').html(), description: $$('div.detail').html(), }); diff --git a/lib/routes/eshukan/templates/description.art b/lib/routes/eshukan/templates/description.art deleted file mode 100644 index e9f926d11..000000000 --- a/lib/routes/eshukan/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{@ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/eshukan/templates/description.tsx b/lib/routes/eshukan/templates/description.tsx new file mode 100644 index 000000000..ce5f10f36 --- /dev/null +++ b/lib/routes/eshukan/templates/description.tsx @@ -0,0 +1,15 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + intro?: string; + description?: string; +}; + +export const renderDescription = ({ intro, description }: DescriptionData) => + renderToString( + <> + {intro ?
    {raw(intro)}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/espn/news.ts b/lib/routes/espn/news.tsx similarity index 71% rename from lib/routes/espn/news.ts rename to lib/routes/espn/news.tsx index 9abe4a921..489224451 100644 --- a/lib/routes/espn/news.ts +++ b/lib/routes/espn/news.tsx @@ -1,27 +1,53 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; -const renderMedia = (media) => - art(path.join(__dirname, 'templates/media.art'), { - video: { - cover: media.posterImages?.full?.href || media.posterImages?.default?.href, - src: media.links?.source.mezzanine?.href || media.links?.source.HD?.href || media.links?.source.full?.href || media.links?.source.href, - title: media.title, - description: media.description, - }, - image: { - src: media.url, - alt: media.alt, - caption: media.caption, - credit: media.credit, - }, - }); +const renderMedia = (media) => { + const video = { + cover: media.posterImages?.full?.href || media.posterImages?.default?.href, + src: media.links?.source.mezzanine?.href || media.links?.source.HD?.href || media.links?.source.full?.href || media.links?.source.href, + title: media.title, + description: media.description, + }; + const image = { + src: media.url, + alt: media.alt, + caption: media.caption, + credit: media.credit, + }; + + return renderToString( + <> + {video.src ? ( + <> + + {video.title || video.description ? ( +
    + {video.title ? ( +
    + {video.title} +
    + ) : null} + {video.description ?

    {video.description}

    : null} +
    + ) : null} + + ) : null} + {image.src ? ( +
    + {image.alt ? {image.alt} : } + {image.caption ?
    {image.caption}
    : null} + {image.credit ? {image.credit} : null} +
    + ) : null} + + ); +}; const junkPattern = /inline\d+|alsosee/; const mediaPattern = /(photo|video)(\d+)/; diff --git a/lib/routes/espn/templates/media.art b/lib/routes/espn/templates/media.art deleted file mode 100644 index e1e52bccf..000000000 --- a/lib/routes/espn/templates/media.art +++ /dev/null @@ -1,19 +0,0 @@ -{{ if video.src }} - - {{ if video.title || video.description }} -
    - {{ if video.title }}
    {{ video.title }}
    {{ /if }} - {{ if video.description }}

    {{ video.description }}

    {{ /if }} -
    - {{ /if }} -{{ /if }} - -{{ if image.src }} -
    - {{ image.alt }} - {{ if image.caption }}
    {{ image.caption }}
    {{ /if }} - {{ if image.credit }}{{ image.credit }}{{ /if }} -
    -{{ /if }} diff --git a/lib/routes/esquirehk/tag.ts b/lib/routes/esquirehk/tag.ts deleted file mode 100644 index 05993c8b4..000000000 --- a/lib/routes/esquirehk/tag.ts +++ /dev/null @@ -1,102 +0,0 @@ -import path from 'node:path'; - -import * as cheerio from 'cheerio'; -import { destr } from 'destr'; - -import type { Route } from '@/types'; -import cache from '@/utils/cache'; -import ofetch from '@/utils/ofetch'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -const topics = new Set(['style', 'watches', 'lifestyle', 'health', 'money-investment', 'gear', 'people', 'watch', 'mens-talk']); - -const handler = async (ctx) => { - let { id = 'Fashion' } = ctx.req.param(); - - id = id.toLowerCase(); - - const rootUrl = 'https://www.esquirehk.com'; - - let currentUrl = `${rootUrl}/tag/${id}`; - if (topics.has(id)) { - currentUrl = `${rootUrl}/${id}`; - } - - const response = await ofetch(currentUrl); - - const $ = cheerio.load(response); - const list = [ - ...$('div[class^="max-w-[100%]"] > div > div:nth-child(2) > a') - .toArray() - .map((item) => { - item = $(item); - return { - title: item.text().trim(), - link: new URL(item.attr('href'), currentUrl).href, - }; - }), - ...$('div.list-item > div > div:nth-child(2) > a') - .toArray() - .map((item) => { - item = $(item); - return { - title: item.text().trim(), - link: new URL(item.attr('href'), currentUrl).href, - }; - }), - ] - .map((item) => ({ - ...item, - slug: item.link.replace(rootUrl, ''), - })) - .filter((item) => !item.slug.startsWith('/campaign')); - - const items = await Promise.all( - list.map((item) => - cache.tryGet(item.link, async () => { - const resp = await ofetch(`https://api.esquirehk.com${item.slug}`); - const response = destr(resp) as any; - if (response.status === '404') { - return item; - } - - item.description = - response.intro.raw + - art(path.join(__dirname, 'templates/subpages.art'), { - subpages: response.subpages, - }); - item.pubDate = parseDate(response.date.published, 'X'); - item.updated = parseDate(response.date.lastModified, 'X'); - item.author = response.author.name; - item.category = [...response.tags.topic.map((tag) => tag.name), ...response.tags.normal.map((tag) => tag.name)]; - - return item; - }) - ) - ); - - return { - title: `${$('head title').text()} - Esquirehk`, - description: $('head meta[name="description"]').attr('content'), - image: $('head meta[property="og:image"]').attr('content'), - logo: $('head meta[property="og:image"]').attr('content'), - link: currentUrl, - item: items, - }; -}; - -export const route: Route = { - path: '/tag/:id?', - categories: ['new-media'], - example: '/esquirehk/tag/Fashion', - parameters: { id: '标签,可在对应标签页 URL 中找到' }, - name: 'Tag', - maintainers: ['nczitzk'], - radar: [ - { - source: ['www.esquirehk.com/tag/:id', 'www.esquirehk.com/:id'], - }, - ], - handler, -}; diff --git a/lib/routes/esquirehk/tag.tsx b/lib/routes/esquirehk/tag.tsx new file mode 100644 index 000000000..15db4deaf --- /dev/null +++ b/lib/routes/esquirehk/tag.tsx @@ -0,0 +1,174 @@ +import * as cheerio from 'cheerio'; +import { destr } from 'destr'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; + +const topics = new Set(['style', 'watches', 'lifestyle', 'health', 'money-investment', 'gear', 'people', 'watch', 'mens-talk']); + +const handler = async (ctx) => { + let { id = 'Fashion' } = ctx.req.param(); + + id = id.toLowerCase(); + + const rootUrl = 'https://www.esquirehk.com'; + + let currentUrl = `${rootUrl}/tag/${id}`; + if (topics.has(id)) { + currentUrl = `${rootUrl}/${id}`; + } + + const response = await ofetch(currentUrl); + + const $ = cheerio.load(response); + const list = [ + ...$('div[class^="max-w-[100%]"] > div > div:nth-child(2) > a') + .toArray() + .map((item) => { + item = $(item); + return { + title: item.text().trim(), + link: new URL(item.attr('href'), currentUrl).href, + }; + }), + ...$('div.list-item > div > div:nth-child(2) > a') + .toArray() + .map((item) => { + item = $(item); + return { + title: item.text().trim(), + link: new URL(item.attr('href'), currentUrl).href, + }; + }), + ] + .map((item) => ({ + ...item, + slug: item.link.replace(rootUrl, ''), + })) + .filter((item) => !item.slug.startsWith('/campaign')); + + const items = await Promise.all( + list.map((item) => + cache.tryGet(item.link, async () => { + const resp = await ofetch(`https://api.esquirehk.com${item.slug}`); + const response = destr(resp) as any; + if (response.status === '404') { + return item; + } + + item.description = response.intro.raw + renderSubpages(response.subpages); + item.pubDate = parseDate(response.date.published, 'X'); + item.updated = parseDate(response.date.lastModified, 'X'); + item.author = response.author.name; + item.category = [...response.tags.topic.map((tag) => tag.name), ...response.tags.normal.map((tag) => tag.name)]; + + return item; + }) + ) + ); + + return { + title: `${$('head title').text()} - Esquirehk`, + description: $('head meta[name="description"]').attr('content'), + image: $('head meta[property="og:image"]').attr('content'), + logo: $('head meta[property="og:image"]').attr('content'), + link: currentUrl, + item: items, + }; +}; + +const renderSubpages = (subpages): string => + renderToString( + <> + {subpages?.map((page, index) => { + const blocks: Array = []; + + switch (page.type) { + case 'image': { + const image = page.image?.large || page.image?.desktop || page.image?.mobile; + blocks.push({image?.alt}); + + break; + } + case 'video_block': { + const videoId = page.source?.split('&')[0]; + blocks.push( + + ); + + break; + } + case 'ctc_product_list': + blocks.push( + + {page.products?.map((product, productIndex) => { + const img = product.image?.desktop || product.image?.mobile; + return ( + + {img?.alt} +
    + {product.brand} +
    + {product.name} +
    + HKD ${product.price} +
    + SHOP NOW +
    + ); + })} +
    + ); + + break; + + default: + blocks.push(UNHANDLED PAGE TYPE: {page.type}); + } + + if (page.title) { + blocks.push( +

    + {page.order ? `${page.order} ` : ''} + {page.title} +

    + ); + } + + if (page.description?.raw) { + blocks.push({raw(page.description.raw)}); + } + + return {blocks}; + })} + + ); + +export const route: Route = { + path: '/tag/:id?', + categories: ['new-media'], + example: '/esquirehk/tag/Fashion', + parameters: { id: '标签,可在对应标签页 URL 中找到' }, + name: 'Tag', + maintainers: ['nczitzk'], + radar: [ + { + source: ['www.esquirehk.com/tag/:id', 'www.esquirehk.com/:id'], + }, + ], + handler, +}; diff --git a/lib/routes/esquirehk/templates/subpages.art b/lib/routes/esquirehk/templates/subpages.art deleted file mode 100644 index ae2a5cf5b..000000000 --- a/lib/routes/esquirehk/templates/subpages.art +++ /dev/null @@ -1,30 +0,0 @@ -{{ if subpages }} - {{ each subpages page }} - {{ if page.type === 'image' }} - {{ set image = page.image.large || page.image.desktop || page.image.mobile }} - {{ image.alt }} - - {{ else if page.type === 'video_block' }} - - - {{ else if page.type === 'ctc_product_list' }} - {{ each page.products product }} - {{ set img = product.image.desktop || product.image.mobile }} - {{ img.alt }}
    - {{ product.brand }}
    - {{ product.name }}
    - HKD ${{ product.price }}
    - SHOP NOW - {{ /each }} - - {{ else }} - UNHANDLED PAGE TYPE: {{ page.type }} - {{ /if }} - - {{ if page.title }} -

    {{ if page.order }}{{ page.order }}{{ /if }} - {{ page.title }}

    - {{ /if }} - {{ if page.description }}{{@ page.description.raw }}{{ /if }} - {{ /each }} -{{ /if }} diff --git a/lib/routes/famitsu/category.ts b/lib/routes/famitsu/category.tsx similarity index 90% rename from lib/routes/famitsu/category.ts rename to lib/routes/famitsu/category.tsx index 8a218fd17..a1f95b9c5 100644 --- a/lib/routes/famitsu/category.ts +++ b/lib/routes/famitsu/category.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { ArticleDetail, Category, CategoryArticle } from './types'; @@ -45,8 +44,8 @@ function getBuildId() { ); } -function render(data) { - return art(path.join(__dirname, 'templates/description.art'), data); +function render(data: { bannerImage?: string; content?: string }) { + return renderToString(); } function renderJSON(c) { @@ -153,3 +152,15 @@ async function handler(ctx) { language: 'ja', }; } + +const FamitsuDescription = ({ bannerImage, content }: { bannerImage?: string; content?: string }) => ( + <> + {bannerImage ? ( + <> + +
    + + ) : null} + {content ? raw(content) : null} + +); diff --git a/lib/routes/famitsu/templates/description.art b/lib/routes/famitsu/templates/description.art deleted file mode 100644 index 7285d1d90..000000000 --- a/lib/routes/famitsu/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if bannerImage }} -
    -{{ /if }} - -{{ if content }} -{{@ content }} -{{ /if }} diff --git a/lib/routes/fanbox/templates/fanbox-post.art b/lib/routes/fanbox/templates/fanbox-post.art deleted file mode 100644 index ebf43dd4f..000000000 --- a/lib/routes/fanbox/templates/fanbox-post.art +++ /dev/null @@ -1,7 +0,0 @@ - -

    {{title}}

    - {{user.name}} -
    -
    - {{excerpt}} -
    diff --git a/lib/routes/fanbox/utils.ts b/lib/routes/fanbox/utils.tsx similarity index 91% rename from lib/routes/fanbox/utils.ts rename to lib/routes/fanbox/utils.tsx index b346d6b86..da6abc587 100644 --- a/lib/routes/fanbox/utils.ts +++ b/lib/routes/fanbox/utils.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { DataItem } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { ArticlePost, FilePost, ImagePost, PostDetailResponse, PostItem, TextPost, VideoPost } from './types'; @@ -23,12 +22,15 @@ function embedUrlMap(urlEmbed: ArticlePost['body']['urlEmbedMap'][string]) { case 'html': return urlEmbed.html; case 'fanbox.post': - return art(path.join(__dirname, 'templates/fanbox-post.art'), { - postUrl: `https://${urlEmbed.postInfo.creatorId}.fanbox.cc/posts/${urlEmbed.postInfo.id}`, - title: urlEmbed.postInfo.title, - user: urlEmbed.postInfo.user, - excerpt: urlEmbed.postInfo.excerpt, - }); + return renderToString( + +

    {urlEmbed.postInfo.title}

    + {urlEmbed.postInfo.user.name} +
    +
    + {urlEmbed.postInfo.excerpt} +
    + ); default: return ''; } diff --git a/lib/routes/fangchan/list.ts b/lib/routes/fangchan/list.tsx similarity index 97% rename from lib/routes/fangchan/list.ts rename to lib/routes/fangchan/list.tsx index 1b9085685..68d42daa9 100644 --- a/lib/routes/fangchan/list.ts +++ b/lib/routes/fangchan/list.tsx @@ -1,16 +1,14 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const handler = async (ctx: Context): Promise => { @@ -37,9 +35,7 @@ export const handler = async (ctx: Context): Promise => { items = response.data.slice(0, limit).map((item): DataItem => { const title: string = item.title; - const description: string = art(path.join(__dirname, 'templates/description.art'), { - intro: item.zhaiyao, - }); + const description: string = renderToString(item.zhaiyao ?
    {item.zhaiyao}
    : null); const pubDate: number | string = item.createtime; const linkUrl: string | undefined = item.url; const categories: string[] = [...new Set([item.topcolumn, item.subcolumn, ...(item.keyword?.split(/,/) ?? [])].filter(Boolean))]; diff --git a/lib/routes/fangchan/templates/description.art b/lib/routes/fangchan/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/fangchan/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/fansly/templates/media.art b/lib/routes/fansly/templates/media.art deleted file mode 100644 index 791f06adc..000000000 --- a/lib/routes/fansly/templates/media.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if poster && src }} - -{{ else if src }} - -{{ /if }} -
    diff --git a/lib/routes/fansly/templates/poll.art b/lib/routes/fansly/templates/poll.art deleted file mode 100644 index 26c2e99d9..000000000 --- a/lib/routes/fansly/templates/poll.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ title }}
    -{{ each options option }} - {{ option.voteCount }}/{{ version }} {{ option.title }}
    -{{ /each }} diff --git a/lib/routes/fansly/templates/tip-goal.art b/lib/routes/fansly/templates/tip-goal.art deleted file mode 100644 index ac932742b..000000000 --- a/lib/routes/fansly/templates/tip-goal.art +++ /dev/null @@ -1,2 +0,0 @@ -{{ label }}
    -{{ currentPercentage }}% ${{ currentAmount / 1000 }} / ${{ goalAmount / 1000 }} diff --git a/lib/routes/fansly/utils.ts b/lib/routes/fansly/utils.tsx similarity index 77% rename from lib/routes/fansly/utils.ts rename to lib/routes/fansly/utils.tsx index 7d35ce46a..3a0ca3d63 100644 --- a/lib/routes/fansly/utils.ts +++ b/lib/routes/fansly/utils.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const apiBaseUrl = 'https://apiv3.fansly.com'; const baseUrl = 'https://fansly.com'; @@ -134,10 +133,7 @@ const renderMedia = (media) => { case 'image/png': case 'video/mp4': case 'audio/mp4': - return art(path.join(__dirname, 'templates/media.art'), { - poster: media.mimetype === 'video/mp4' ? media.variants[0].locations[0] : null, - src: media.locations[0], - }); + return renderToString(); default: throw new Error(`Unhandled media type: ${media.mimetype}`); } @@ -145,21 +141,45 @@ const renderMedia = (media) => { const renderPoll = (pollId, polls) => { const poll = polls.find((poll) => poll.id === pollId); - return art(path.join(__dirname, 'templates/poll.art'), { - title: poll.question, - options: poll.options, - version: poll.version, - }); + return renderToString(); }; const renderTipGoal = (tipGoalId, tipGoals) => { const goal = tipGoals.find((goal) => goal.id === tipGoalId); - return art(path.join(__dirname, 'templates/tip-goal.art'), { - label: goal.label, - description: goal.description, - currentAmount: goal.currentAmount, - goalAmount: goal.goalAmount, - currentPercentage: goal.currentPercentage, - }); + return renderToString(); }; export { baseUrl, findAccountById, getAccountByUsername, getTagId, getTagSuggestion, getTimelineByAccountId, icon, parseAttachments, parseDescription, parseMedia, renderMedia, renderPoll, renderTipGoal }; + +const FanslyMedia = ({ poster, src }: { poster?: { location?: string } | null; src?: { location?: string } }) => ( + <> + {poster && src ? ( + + ) : src ? ( + + ) : null} +
    + +); + +const FanslyPoll = ({ title, options, version }: { title: string; options: any[]; version: string }) => ( + <> + {title} +
    + {options.map((option) => ( + <> + {option.voteCount}/{version} {option.title} +
    + + ))} + +); + +const FanslyTipGoal = ({ label, currentPercentage, currentAmount, goalAmount }: { label: string; currentPercentage: number; currentAmount: number; goalAmount: number }) => ( + <> + {label} +
    + {currentPercentage}% ${currentAmount / 1000} / ${goalAmount / 1000} + +); diff --git a/lib/routes/fantube/creator.ts b/lib/routes/fantube/creator.tsx similarity index 58% rename from lib/routes/fantube/creator.ts rename to lib/routes/fantube/creator.tsx index dc49ec7ce..9bd3e5156 100644 --- a/lib/routes/fantube/creator.ts +++ b/lib/routes/fantube/creator.tsx @@ -1,8 +1,8 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { baseUrl, getCreatorFragment, getCreatorPostReelList } from './utils'; @@ -29,13 +29,37 @@ export const route: Route = { handler, }; -const render = ({ description, thumbnailUrl, sampleVideoId, imageUrls }) => - art(path.join(__dirname, 'templates/post.art'), { - description, - thumbnailUrl, - sampleVideoId, - imageUrls, - }); +const renderDescription = ({ description, thumbnailUrl, sampleVideoId, imageUrls }): string => + renderToString( + <> + {thumbnailUrl ? ( + <> + +
    + + ) : null} + {imageUrls?.map((img, index) => ( + <> + +
    + + ))} + {sampleVideoId ? ( + <> +
    + +
    +
    + + ) : null} + {description ? raw(description.replaceAll('\n', '
    ')) : null} + + ); async function handler(ctx) { const { identifier } = ctx.req.param(); @@ -46,7 +70,7 @@ async function handler(ctx) { const items = posts.map((p) => ({ title: p.title.replaceAll('\n', ' ').trim(), - description: render({ + description: renderDescription({ description: p.description, thumbnailUrl: p.thumbnailUrl, sampleVideoId: p.sampleVideoId, diff --git a/lib/routes/fantube/templates/post.art b/lib/routes/fantube/templates/post.art deleted file mode 100644 index 4d1d38589..000000000 --- a/lib/routes/fantube/templates/post.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if thumbnailUrl }} -
    -{{ /if }} - -{{ if imageUrls }} - {{ each imageUrls img }} -
    - {{ /each }} -{{ /if }} - -{{ if sampleVideoId }} -

    -{{ /if }} - -{{ if description }} - {{@ description.replaceAll('\n', '
    ') }} -{{ /if }} diff --git a/lib/routes/fanxinzhui/index.ts b/lib/routes/fanxinzhui/index.tsx similarity index 85% rename from lib/routes/fanxinzhui/index.ts rename to lib/routes/fanxinzhui/index.tsx index 93b9bad32..d5b4916fd 100644 --- a/lib/routes/fanxinzhui/index.ts +++ b/lib/routes/fanxinzhui/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -86,16 +84,16 @@ async function handler(ctx) { const image = el.find('img').prop('src'); el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { - images: image + renderDescription( + image ? [ { src: image.replace(/@\d+,\d+\.\w+$/, ''), alt: content('div.resource_title h2').text(), }, ] - : undefined, - }) + : undefined + ) ); }); @@ -127,3 +125,16 @@ async function handler(ctx) { allowEmpty: true, }; } + +const renderDescription = (images: Array<{ src?: string; alt?: string }> | undefined): string => + renderToString( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + + ); diff --git a/lib/routes/fanxinzhui/templates/description.art b/lib/routes/fanxinzhui/templates/description.art deleted file mode 100644 index 0a7f83a6f..000000000 --- a/lib/routes/fanxinzhui/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/farmatters/index.ts b/lib/routes/farmatters/index.tsx similarity index 86% rename from lib/routes/farmatters/index.ts rename to lib/routes/farmatters/index.tsx index 3da082383..522efa3fd 100644 --- a/lib/routes/farmatters/index.ts +++ b/lib/routes/farmatters/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import MarkdownIt from 'markdown-it'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const md = MarkdownIt({ @@ -74,15 +73,16 @@ async function handler(ctx) { const items = response.data.list.slice(0, limit).map((item) => ({ title: item.title, link: new URL(`doc/${item.id}`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.headImageUrl - ? { - src: item.headImageUrl, - alt: item.title, - } - : undefined, - description: md.render(item.content ?? item.summary), - }), + description: renderToString( + <> + {item.headImageUrl ? ( +
    + {item.title} +
    + ) : null} + {item.content || item.summary ? raw(md.render(item.content ?? item.summary)) : null} + + ), author: item.author, category: [item.catalogName, item.subCatalogName ?? undefined, ...(item.tags?.map((t) => t.tagName) ?? [])].filter(Boolean), guid: `farmatters-${item.id}`, diff --git a/lib/routes/farmatters/templates/description.art b/lib/routes/farmatters/templates/description.art deleted file mode 100644 index d6ee9e471..000000000 --- a/lib/routes/farmatters/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if image }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/fashionnetwork/index.ts b/lib/routes/fashionnetwork/index.ts index e4d249846..4317381b9 100644 --- a/lib/routes/fashionnetwork/index.ts +++ b/lib/routes/fashionnetwork/index.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx) => { const { id = '0' } = ctx.req.param(); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; @@ -33,7 +32,7 @@ export const handler = async (ctx) => { const src = item.find('img.item__img').first().prop('src') ?? undefined; const image = src ? new URL(src, rootUrl).href : undefined; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -65,7 +64,7 @@ export const handler = async (ctx) => { const $$ = load(detailResponse); const title = $$('h1.newsTitle').text(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ description: $$('div.article-content').html(), }); diff --git a/lib/routes/fashionnetwork/templates/description.art b/lib/routes/fashionnetwork/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/fashionnetwork/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/fashionnetwork/templates/description.tsx b/lib/routes/fashionnetwork/templates/description.tsx new file mode 100644 index 000000000..528e99d13 --- /dev/null +++ b/lib/routes/fashionnetwork/templates/description.tsx @@ -0,0 +1,27 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + description?: string; +}; + +const Description = ({ images, description }: DescriptionProps) => ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/fastbull/news.ts b/lib/routes/fastbull/news.tsx similarity index 78% rename from lib/routes/fastbull/news.ts rename to lib/routes/fastbull/news.tsx index 4e53d2102..0dc3d54d1 100644 --- a/lib/routes/fastbull/news.ts +++ b/lib/routes/fastbull/news.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/news', @@ -69,10 +68,17 @@ async function handler() { const content = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/description.art'), { - tips: item.description, - description: content('.news-detail-content').html(), - }); + const detailHtml = content('.news-detail-content').html(); + item.description = renderToString( + <> + {item.description ? ( + <> + 摘要

    {item.description}

    + + ) : null} + {detailHtml ? raw(detailHtml) : null} + + ); return item; }) diff --git a/lib/routes/fastbull/templates/description.art b/lib/routes/fastbull/templates/description.art deleted file mode 100644 index 1458bc071..000000000 --- a/lib/routes/fastbull/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if tips }} -摘要: -

    {{ tips }}

    -{{ /if }} -{{ if description }} -{{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/feng/forum.ts b/lib/routes/feng/forum.tsx similarity index 72% rename from lib/routes/feng/forum.ts rename to lib/routes/feng/forum.tsx index 4e23c9d61..0509f19af 100644 --- a/lib/routes/feng/forum.ts +++ b/lib/routes/feng/forum.tsx @@ -1,11 +1,31 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { baseUrl, getForumMeta, getThread, getThreads } from './utils'; +const renderImages = (images?: string[]) => + renderToString( + <> + {images?.length ? ( + <> +
    + {images.map((image) => ( + + ))} + + ) : null} + + ); + +const deletedDescription = renderToString( + <> + 威锋 +
    帖子已被删除
    + +); + export const route: Route = { path: '/forum/:id/:type?', categories: ['bbs'], @@ -51,12 +71,10 @@ async function handler(ctx) { threads.map(async (item) => { const thread = await getThread(item.tid, topicId); if (thread.status.code === 0) { - const img = art(path.join(__dirname, 'templates/img.art'), { - images: thread.data.thread.fengTalkImage.length ? thread.data.thread.fengTalkImage : undefined, - }); + const img = renderImages(thread.data.thread.fengTalkImage.length ? thread.data.thread.fengTalkImage : undefined); item.description = thread.data.thread.message + img; } else { - item.description = art(path.join(__dirname, 'templates/deleted.art'), {}); + item.description = deletedDescription; } delete item.tid; return item; diff --git a/lib/routes/feng/templates/deleted.art b/lib/routes/feng/templates/deleted.art deleted file mode 100644 index e87f889f1..000000000 --- a/lib/routes/feng/templates/deleted.art +++ /dev/null @@ -1,2 +0,0 @@ -威锋 -
    帖子已被删除
    diff --git a/lib/routes/feng/templates/img.art b/lib/routes/feng/templates/img.art deleted file mode 100644 index a48c25f14..000000000 --- a/lib/routes/feng/templates/img.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if images }} -
    -{{ each images }} - -{{ /each }} -{{ /if }} diff --git a/lib/routes/ff14/ff14-global.ts b/lib/routes/ff14/ff14-global.ts index c7eb127b6..b9fff3d68 100644 --- a/lib/routes/ff14/ff14-global.ts +++ b/lib/routes/ff14/ff14-global.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { isValidHost } from '@/utils/valid-host'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: ['/global/:lang/:type?', '/ff14_global/:lang/:type?'], categories: ['game'], @@ -64,7 +63,7 @@ async function handler(ctx) { item: data.map(({ id, url, title, time, description, image }) => ({ title, link: url, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image, description, }), diff --git a/lib/routes/ff14/ff14-zh.ts b/lib/routes/ff14/ff14-zh.ts index 42322fffe..2cf2f51ff 100644 --- a/lib/routes/ff14/ff14-zh.ts +++ b/lib/routes/ff14/ff14-zh.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: ['/zh/:type?', '/ff14_zh/:type?'], categories: ['game'], @@ -63,7 +62,7 @@ async function handler(ctx) { item: data.map(({ Title, Summary, Author, PublishDate, HomeImagePath, Id }) => ({ title: Title, link: Author || `https://ff.web.sdo.com/web8/index.html#/newstab/newscont/${Id}`, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: HomeImagePath, description: Summary, }), diff --git a/lib/routes/ff14/templates/description.art b/lib/routes/ff14/templates/description.art deleted file mode 100644 index 6529ddc37..000000000 --- a/lib/routes/ff14/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ if image }} -
    -{{ /if }} -{{ description }} diff --git a/lib/routes/ff14/templates/description.tsx b/lib/routes/ff14/templates/description.tsx new file mode 100644 index 000000000..35372da16 --- /dev/null +++ b/lib/routes/ff14/templates/description.tsx @@ -0,0 +1,20 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + image?: string; + description?: string; +}; + +const Description = ({ image, description }: DescriptionProps) => ( + <> + {image ? ( + <> + +
    + + ) : null} + {description} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/fffdm/manhua/manhua.ts b/lib/routes/fffdm/manhua/manhua.tsx similarity index 83% rename from lib/routes/fffdm/manhua/manhua.ts rename to lib/routes/fffdm/manhua/manhua.tsx index cdd460b0a..f4a2ca88e 100644 --- a/lib/routes/fffdm/manhua/manhua.ts +++ b/lib/routes/fffdm/manhua/manhua.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const domain = 'manhua.fffdm.com'; const host = `https://${domain}`; @@ -61,7 +60,16 @@ async function handler(ctx) { const picContent = await get_pic(url); return { title: picContent.chapterTitle, - description: art(path.join(__dirname, '../templates/manhua.art'), { pic: picContent.pics, cdn }), + description: renderToString( +
    + {picContent.pics.map((value) => ( + <> + +
    + + ))} +
    + ), link: `${host}/${id}/${item.url}/`, comicTitle: picContent.comicTitle, pubDate: picContent.pubDate, diff --git a/lib/routes/fffdm/templates/manhua.art b/lib/routes/fffdm/templates/manhua.art deleted file mode 100644 index a5a76ff81..000000000 --- a/lib/routes/fffdm/templates/manhua.art +++ /dev/null @@ -1,5 +0,0 @@ -
    - {{ each pic }} -
    - {{/each }} -
    diff --git a/lib/routes/firefox/templates/description.art b/lib/routes/firefox/templates/description.art deleted file mode 100644 index fff86f8ef..000000000 --- a/lib/routes/firefox/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -{{@ header }} -
    -{{@ overview }} -
    -{{@ dataClasses }} diff --git a/lib/routes/firefox/templates/description.tsx b/lib/routes/firefox/templates/description.tsx new file mode 100644 index 000000000..7995603b3 --- /dev/null +++ b/lib/routes/firefox/templates/description.tsx @@ -0,0 +1,20 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + header?: string; + overview?: string; + dataClasses?: string; +}; + +const FirefoxDescription = ({ header, overview, dataClasses }: DescriptionData) => ( + <> + {header ? raw(header) : null} +
    + {overview ? raw(overview) : null} +
    + {dataClasses ? raw(dataClasses) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/fisher-spb/news.ts b/lib/routes/fisher-spb/news.tsx similarity index 77% rename from lib/routes/fisher-spb/news.ts rename to lib/routes/fisher-spb/news.tsx index 057aafce8..f603253b7 100644 --- a/lib/routes/fisher-spb/news.ts +++ b/lib/routes/fisher-spb/news.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/news', @@ -32,8 +30,20 @@ export const route: Route = { }; async function handler() { - const renderVideo = (link) => art(path.join(__dirname, './templates/video.art'), { link }); - const renderImage = (href) => art(path.join(__dirname, './templates/image.art'), { href }); + const renderVideo = (link) => + renderToString( + <> + +
    + + ); + const renderImage = (href) => + renderToString( + <> + +
    + + ); const rootUrl = 'https://fisher.spb.ru/news/'; const response = await got({ diff --git a/lib/routes/fisher-spb/templates/image.art b/lib/routes/fisher-spb/templates/image.art deleted file mode 100644 index de65644db..000000000 --- a/lib/routes/fisher-spb/templates/image.art +++ /dev/null @@ -1 +0,0 @@ -
    \ No newline at end of file diff --git a/lib/routes/fisher-spb/templates/video.art b/lib/routes/fisher-spb/templates/video.art deleted file mode 100644 index 1a6ed0977..000000000 --- a/lib/routes/fisher-spb/templates/video.art +++ /dev/null @@ -1 +0,0 @@ -
    \ No newline at end of file diff --git a/lib/routes/flyert/templates/description.art b/lib/routes/flyert/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/flyert/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/flyert/templates/description.tsx b/lib/routes/flyert/templates/description.tsx new file mode 100644 index 000000000..e8f519243 --- /dev/null +++ b/lib/routes/flyert/templates/description.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData) => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/flyert/util.ts b/lib/routes/flyert/util.ts index dea3b16a9..639480c3e 100644 --- a/lib/routes/flyert/util.ts +++ b/lib/routes/flyert/util.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + const rootUrl = 'https://www.flyert.com.cn'; /** @@ -23,7 +22,7 @@ const parseArticleList = ($: CheerioAPI, limit: number) => const title = item.find('div.wzbt').text().trim(); const image = item.find('div.wzpic img').prop('src'); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -108,7 +107,7 @@ const parsePostList = ($: CheerioAPI, limit: number) => */ const parseArticle = ($$: CheerioAPI, item) => { const title = $$('h1.ph').text().trim(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ intro: $$('div.s').text() || undefined, description: $$('div#artMain').html(), }); @@ -146,7 +145,7 @@ const parsePost = ($$: CheerioAPI, item) => { el = $$(el); el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: el.prop('zoomfile') || el.prop('file') ? [ diff --git a/lib/routes/focustaiwan/index.ts b/lib/routes/focustaiwan/index.tsx similarity index 88% rename from lib/routes/focustaiwan/index.ts rename to lib/routes/focustaiwan/index.tsx index d8b8615f7..da8b52e5a 100644 --- a/lib/routes/focustaiwan/index.ts +++ b/lib/routes/focustaiwan/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -81,10 +80,12 @@ async function handler(ctx) { item.itunes_item_image = image; } - item.description = art(path.join(__dirname, 'templates/article.art'), { - image, - description: content('.paragraph').html(), - }); + item.description = renderToString( + <> + +
    {content('.paragraph').html() ? raw(content('.paragraph').html() as string) : null}
    + + ); return item; }) diff --git a/lib/routes/focustaiwan/templates/article.art b/lib/routes/focustaiwan/templates/article.art deleted file mode 100644 index aca3499e9..000000000 --- a/lib/routes/focustaiwan/templates/article.art +++ /dev/null @@ -1 +0,0 @@ -
    {{@ description }}
    diff --git a/lib/routes/followin/templates/thread.art b/lib/routes/followin/templates/thread.art deleted file mode 100644 index 22f9b8f24..000000000 --- a/lib/routes/followin/templates/thread.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ each list l }} - {{@ l.translated_content.replaceAll('\n', '
    ') || l.content.replaceAll('\n', '
    ') }} -
    - {{ if l.images.length }} - {{ each l.images img }} - - {{ /each }} - {{ /if }} - {{ if l.link_previews.length }} - {{ each l.link_previews a }} - {{ a.title }}
    {{ a.content }}
    - {{ /each }} - {{ /if }} -
    -{{ /each }} diff --git a/lib/routes/followin/utils.ts b/lib/routes/followin/utils.tsx similarity index 66% rename from lib/routes/followin/utils.ts rename to lib/routes/followin/utils.tsx index 1f210423a..35f2b0bba 100644 --- a/lib/routes/followin/utils.ts +++ b/lib/routes/followin/utils.tsx @@ -1,15 +1,36 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const apiUrl = 'https://api.followin.io'; const baseUrl = 'https://followin.io'; const favicon = `${baseUrl}/favicon.ico`; +const renderThread = (list) => + renderToString( + <> + {list.map((item) => ( + <> + {raw((item.translated_content || item.content).replaceAll('\n', '
    '))} +
    + {item.images.length ? item.images.map((image) => ) : null} + {item.link_previews.length + ? item.link_previews.map((linkPreview) => ( + + {linkPreview.title} +
    + {linkPreview.content} +
    + )) + : null} +
    + + ))} + + ); const getBParam = (lang) => ({ a: 'web', @@ -60,11 +81,7 @@ const parseItem = (item, tryGet) => const { queries } = data.pageProps.dehydratedState; const info = queries.find((q) => q.queryKey[0] === '/feed/info').state; const thread = queries.find((q) => q.queryKey[0] === '/feed/thread'); - item.description = thread - ? art(path.join(__dirname, 'templates/thread.art'), { - list: thread.state.data.list, - }) - : info.data.translated_full_content || info.data.full_content; + item.description = thread ? renderThread(thread.state.data.list) : info.data.translated_full_content || info.data.full_content; item.updated = parseDate(info.dataUpdatedAt, 'x'); item.category = [...new Set([...item.category, ...info.data.tags.map((tag) => tag.name)])]; diff --git a/lib/routes/foresightnews/templates/description.art b/lib/routes/foresightnews/templates/description.art deleted file mode 100644 index f431eb576..000000000 --- a/lib/routes/foresightnews/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{@ description }} - -{{ if source }} -
    -来源链接 -
    -{{ /if }} - -{{ if image }} -
    - -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/foresightnews/util.ts b/lib/routes/foresightnews/util.tsx similarity index 77% rename from lib/routes/foresightnews/util.ts rename to lib/routes/foresightnews/util.tsx index 17e3c19d7..364ea529b 100644 --- a/lib/routes/foresightnews/util.ts +++ b/lib/routes/foresightnews/util.tsx @@ -1,9 +1,10 @@ -import path from 'node:path'; import zlib from 'node:zlib'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const constants = { labelHot: '热门', @@ -67,11 +68,23 @@ const processItems = async (apiUrl, limit, ...parameters) => { return { title: item.title, link, - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.img.split('?')[0], - description: item.content ?? item.brief, - source: item.source_link, - }), + description: renderToString( + <> + {raw(item.content ?? item.brief ?? '')} + {item.source_link ? ( + <> +
    + 来源链接 +
    + + ) : null} + {item.img ? ( +
    + +
    + ) : null} + + ), author: item.column?.title ?? item.author?.username ?? undefined, category: categories, guid: `foresightnews-${sourceType}#${item.id}`, diff --git a/lib/routes/fosshub/index.ts b/lib/routes/fosshub/index.tsx similarity index 59% rename from lib/routes/fosshub/index.ts rename to lib/routes/fosshub/index.tsx index bb71f52df..2fa4c1b1a 100644 --- a/lib/routes/fosshub/index.ts +++ b/lib/routes/fosshub/index.tsx @@ -1,11 +1,40 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const renderDescription = (links, changelog) => + renderToString( + <> + + + {links?.map((link) => ( + <> + + {link.map((item) => ( + + ))} + + + {link.map((item) => ( + + ))} + + + ))} + +
    {item.dt}
    {item.dd ? raw(item.dd) : null}
    + {changelog ? ( + <> +
    + {raw(changelog)} + + ) : null} + + ); export const route: Route = { path: '/:id', @@ -44,8 +73,8 @@ async function handler(ctx) { { title: version, link: `${currentUrl}#${version}`, - description: art(path.join(__dirname, 'templates/description.art'), { - links: $('.dwn-dl') + description: renderDescription( + $('.dwn-dl') .toArray() .map((l) => $(l) @@ -56,8 +85,8 @@ async function handler(ctx) { dd: $(w).find('dd').html(), })) ), - changelog: $('div[itemprop="releaseNotes"]').html(), - }), + $('div[itemprop="releaseNotes"]').html() + ), pubDate: parseDate($('.ma__upd .v').text(), 'MMM DD, YYYY'), }, ]; diff --git a/lib/routes/fosshub/templates/description.art b/lib/routes/fosshub/templates/description.art deleted file mode 100644 index 878b87d04..000000000 --- a/lib/routes/fosshub/templates/description.art +++ /dev/null @@ -1,20 +0,0 @@ - - -{{ each links link }} - -{{ each link l }} - -{{ /each }} - - -{{ each link l }} - -{{ /each }} - -{{ /each }} - -
    {{ l.dt }}
    {{@ l.dd }}
    -{{ if changelog }} -
    -{{@ changelog }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/freecomputerbooks/index.ts b/lib/routes/freecomputerbooks/index.tsx similarity index 92% rename from lib/routes/freecomputerbooks/index.ts rename to lib/routes/freecomputerbooks/index.tsx index 96fb249d5..d2ddeaa72 100644 --- a/lib/routes/freecomputerbooks/index.ts +++ b/lib/routes/freecomputerbooks/index.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const baseURL = 'https://freecomputerbooks.com/'; @@ -109,7 +108,15 @@ async function insertDescriptionInto(item) { content.find('img[src$="/hot.gif"]').remove(); content.find(':contains(Similar Books)').nextAll().addBack().remove(); - item.description = art(path.join(__dirname, 'templates/desc.art'), { imageURL, metadata, content }); + item.description = renderToString( + <> +
    + +
    + {raw(metadata.toString())} + {raw(content.toString())} + + ); return item; } diff --git a/lib/routes/freecomputerbooks/templates/desc.art b/lib/routes/freecomputerbooks/templates/desc.art deleted file mode 100644 index 5ce647418..000000000 --- a/lib/routes/freecomputerbooks/templates/desc.art +++ /dev/null @@ -1,3 +0,0 @@ -
    -{{@ metadata }} -{{@ content }} diff --git a/lib/routes/furstar/templates/author.art b/lib/routes/furstar/templates/author.art deleted file mode 100644 index 3d0443e32..000000000 --- a/lib/routes/furstar/templates/author.art +++ /dev/null @@ -1,8 +0,0 @@ -
    - - {{ if link !== null }} - {{name}} - {{ else }} - {{name}} - {{ /if }} -
    \ No newline at end of file diff --git a/lib/routes/furstar/templates/description.art b/lib/routes/furstar/templates/description.art deleted file mode 100644 index f28007637..000000000 --- a/lib/routes/furstar/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -

    {{ desc }}

    -{{ each pics }} - -{{/each}} -{{@ author }} \ No newline at end of file diff --git a/lib/routes/furstar/utils.ts b/lib/routes/furstar/utils.tsx similarity index 81% rename from lib/routes/furstar/utils.ts rename to lib/routes/furstar/utils.tsx index d63e92a18..4b5e67fe1 100644 --- a/lib/routes/furstar/utils.ts +++ b/lib/routes/furstar/utils.tsx @@ -1,21 +1,30 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const base = 'https://furstar.jp'; const langBase = (lang) => (lang ? `${base}/${lang}` : base); // en, cn, (none, for JP) -const renderAuthor = (author) => art(path.join(__dirname, 'templates/author.art'), author); +const renderAuthor = (author) => + renderToString( +
    + + {author.link === null ? {author.name} : {author.name}} +
    + ); const renderDesc = (desc, pics, author) => - art(path.join(__dirname, 'templates/description.art'), { - desc, - pics, - author: renderAuthor(author), - }); + renderToString( + <> +

    {desc}

    + {pics.map((pic) => ( + + ))} + {raw(renderAuthor(author))} + + ); const authorDetail = (el) => { const $ = load(el); diff --git a/lib/routes/futunn/main.ts b/lib/routes/futunn/main.ts index d61282025..92e3ddc0f 100644 --- a/lib/routes/futunn/main.ts +++ b/lib/routes/futunn/main.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: ['/main', '/'], @@ -43,7 +42,7 @@ async function handler(ctx) { link: item.url.split('?')[0], author: item.source, pubDate: parseDate(item.timestamp * 1000), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ abs: item.abstract, pic: item.pic, }), diff --git a/lib/routes/futunn/templates/description.art b/lib/routes/futunn/templates/description.art deleted file mode 100644 index c9cfd7c14..000000000 --- a/lib/routes/futunn/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if pic }} - -{{ /if }} -{{ if abs }} -

    {{ abs }}

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/futunn/templates/description.tsx b/lib/routes/futunn/templates/description.tsx new file mode 100644 index 000000000..d75402f0b --- /dev/null +++ b/lib/routes/futunn/templates/description.tsx @@ -0,0 +1,15 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + pic?: string; + abs?: string; +}; + +const FutunnDescription = ({ pic, abs }: DescriptionData) => ( + <> + {pic ? : null} + {abs ?

    {abs}

    : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/futunn/topic.ts b/lib/routes/futunn/topic.ts index 4f9a670ee..6451c34a5 100644 --- a/lib/routes/futunn/topic.ts +++ b/lib/routes/futunn/topic.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/topic/:id', @@ -69,7 +68,7 @@ async function handler(ctx) { link: item.url, author: item.source, pubDate: parseDate(item.time * 1000), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ abs: item.abstract, pic: item.pic, }), diff --git a/lib/routes/futunn/video.ts b/lib/routes/futunn/video.ts index 87de70a71..390140168 100644 --- a/lib/routes/futunn/video.ts +++ b/lib/routes/futunn/video.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/video', @@ -37,7 +36,7 @@ async function handler(ctx) { const items = response.data.data.videoList.list.map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ abs: item.abstract, pic: item.videoImg, }), diff --git a/lib/routes/gameapps/index.ts b/lib/routes/gameapps/index.tsx similarity index 86% rename from lib/routes/gameapps/index.ts rename to lib/routes/gameapps/index.tsx index ec64f0b50..f8268e1a6 100644 --- a/lib/routes/gameapps/index.ts +++ b/lib/routes/gameapps/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import parser from '@/utils/rss-parser'; export const route: Route = { @@ -71,10 +70,14 @@ async function handler() { content.append(pages); } - item.description = art(path.join(__dirname, 'templates/description.art'), { - intro: $('div.introduction.media.news-intro div.media-body').html()?.trim(), - desc: content.html()?.trim(), - }); + const intro = $('div.introduction.media.news-intro div.media-body').html()?.trim(); + const desc = content.html()?.trim(); + item.description = renderToString( + <> + {intro ? raw(intro) : null} + {desc ? raw(desc) : null} + + ); item.guid = item.guid.slice(0, item.link.lastIndexOf('/')); item.pubDate = parseDate(item.pubDate); item.enclosure_url = $('div.introduction.media.news-intro div.media-left').find('img').attr('src'); diff --git a/lib/routes/gameapps/templates/description.art b/lib/routes/gameapps/templates/description.art deleted file mode 100644 index 7ba352613..000000000 --- a/lib/routes/gameapps/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{@ intro }} -{{@ desc }} diff --git a/lib/routes/gamebase/news.ts b/lib/routes/gamebase/news.tsx similarity index 90% rename from lib/routes/gamebase/news.ts rename to lib/routes/gamebase/news.tsx index 2f626dcb1..7b6103e1f 100644 --- a/lib/routes/gamebase/news.ts +++ b/lib/routes/gamebase/news.tsx @@ -1,8 +1,8 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Route } from '@/types'; @@ -10,7 +10,6 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const types = { @@ -18,6 +17,23 @@ const types = { r18list: 'newsPornList', }; +const renderDescription = ({ images, intro, description }) => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); + export const handler = async (ctx: Context): Promise => { const { type = 'newslist', category = 'all' } = ctx.req.param(); @@ -65,7 +81,7 @@ export const handler = async (ctx: Context): Promise => { metaDesc = (detailResponse.match(/(\\u003C.*?)","/)?.[1] ?? '').replaceAll(String.raw`\"`, '"').replaceAll(/\\u([\da-f]{4})/gi, (match, hex) => String.fromCodePoint(Number.parseInt(hex, 16))); } - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: image && !metaDesc ? [ diff --git a/lib/routes/gamebase/templates/description.art b/lib/routes/gamebase/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/gamebase/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/gaoyu/blog.ts b/lib/routes/gaoyu/blog.ts index 3a3a9c855..77752dc8c 100644 --- a/lib/routes/gaoyu/blog.ts +++ b/lib/routes/gaoyu/blog.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '20', 10); @@ -39,7 +38,7 @@ export const handler = async (ctx: Context): Promise => { const $el: Cheerio = $(el); const title: string = $el.find('p.text-neutral-900').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ intro: $el.find('p.text-neutral-600').last().html(), }); const pubDateStr: string | undefined = $el.find('p.text-neutral-600').first().text(); @@ -76,7 +75,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1.title').text(); const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ description: $$('article.prose').html(), }); const pubDateStr: string | undefined = $$('meta[property="article:published_time"]').attr('content'); diff --git a/lib/routes/gaoyu/templates/description.art b/lib/routes/gaoyu/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/gaoyu/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/gaoyu/templates/description.tsx b/lib/routes/gaoyu/templates/description.tsx new file mode 100644 index 000000000..bce583090 --- /dev/null +++ b/lib/routes/gaoyu/templates/description.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + intro?: string; + description?: string; +}; + +const GaoyuDescription = ({ intro, description }: DescriptionData) => ( + <> + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/gcores/parser.ts b/lib/routes/gcores/parser.ts index e11f47127..596fc4bf4 100644 --- a/lib/routes/gcores/parser.ts +++ b/lib/routes/gcores/parser.ts @@ -1,6 +1,4 @@ -import path from 'node:path'; - -import { art } from '@/utils/render'; +import { renderDescription } from './templates/description'; interface Style { [key: string]: string; @@ -94,7 +92,7 @@ const createEntityElement = (entity: Entity, text: string): string => { case 'EMBED': return entity.data.content.startsWith('http') ? `${entity.data.content}` : entity.data.content; case 'IMAGE': - return art(path.join(__dirname, 'templates/description.art'), { + return renderDescription({ images: entity.data.path ? [ { @@ -110,7 +108,7 @@ const createEntityElement = (entity: Entity, text: string): string => { if (!entity.data.images || !Array.isArray(entity.data.images)) { return ''; } - return art(path.join(__dirname, 'templates/description.art'), { + return renderDescription({ images: entity.data.images.map((image: any) => ({ src: new URL(image.path, imageBaseUrl).href, alt: image.caption ?? entity.data.caption, diff --git a/lib/routes/gcores/radio.ts b/lib/routes/gcores/radio.tsx similarity index 72% rename from lib/routes/gcores/radio.ts rename to lib/routes/gcores/radio.tsx index e09b3dad3..34c451c35 100644 --- a/lib/routes/gcores/radio.ts +++ b/lib/routes/gcores/radio.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import md5 from '@/utils/md5'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/radios/:category?', @@ -64,9 +62,7 @@ async function handler(ctx) { const itunes_item_image = `https://image.gcores.com/${attributes.cover}`; const media_id = relationships.media.data.id; const enclosure_url = new URL(audios[media_id], 'https://alioss.gcores.com/uploads/audio/').toString(); - const description = art(path.join(__dirname, 'templates/content.art'), { - content: JSON.parse(attributes.content), - }); + const description = renderContentDescription(JSON.parse(attributes.content)); return { title: attributes.title, @@ -93,6 +89,27 @@ async function handler(ctx) { }; } +const renderContentDescription = (content): string => + renderToString( + content ? ( + <> + {content.blocks?.map((line, index) => (line.type === 'unstyled' ?

    {line.text}

    : null))} + {Object.values(content.entityMap ?? {}).map((ent: any, index) => + ent.type === 'WIDGET' ? ( +

    + {ent.data.title} +

    + ) : null + )} + + ) : ( + <> +

    机核从2010年开始一直致力于分享游戏玩家的生活,以及深入探讨游戏相关的文化。我们开发原创的播客以及视频节目,一直在不断寻找民间高质量的内容创作者。

    +

    我们坚信游戏不止是游戏,游戏中包含的科学,文化,历史等各个层面的知识和故事,它们同时也会辐射到二次元甚至电影的领域,这些内容非常值得分享给热爱游戏的您。

    + + ) + ); + const get = async (url) => { const response = await got({ method: 'get', diff --git a/lib/routes/gcores/templates/content.art b/lib/routes/gcores/templates/content.art deleted file mode 100644 index 5cce07e6b..000000000 --- a/lib/routes/gcores/templates/content.art +++ /dev/null @@ -1,16 +0,0 @@ -{{if content}} - {{each content.blocks line}} - {{if line.type === 'unstyled'}} -

    {{line.text}}

    - {{/if}} - {{/each}} - - {{each content.entityMap ent}} - {{if ent.type === 'WIDGET'}} -

    {{ent.data.title}}

    - {{/if}} - {{/each}} -{{else}} -

    机核从2010年开始一直致力于分享游戏玩家的生活,以及深入探讨游戏相关的文化。我们开发原创的播客以及视频节目,一直在不断寻找民间高质量的内容创作者。

    -

    我们坚信游戏不止是游戏,游戏中包含的科学,文化,历史等各个层面的知识和故事,它们同时也会辐射到二次元甚至电影的领域,这些内容非常值得分享给热爱游戏的您。

    -{{/if}} diff --git a/lib/routes/gcores/templates/description.art b/lib/routes/gcores/templates/description.art deleted file mode 100644 index 1ee123a8f..000000000 --- a/lib/routes/gcores/templates/description.art +++ /dev/null @@ -1,67 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if audios }} - {{ each audios audio }} - {{ if audio?.src }} - - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if videos }} - {{ each videos video }} - {{ if video?.src }} - {{ if video?.type?.endsWith('taptap') }} - - {{ else }} - - {{ /if }} - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/gcores/templates/description.tsx b/lib/routes/gcores/templates/description.tsx new file mode 100644 index 000000000..5e402791b --- /dev/null +++ b/lib/routes/gcores/templates/description.tsx @@ -0,0 +1,62 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; + width?: string | number; + height?: string | number; +}; + +type DescriptionMedia = { + src?: string; + type?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + audios?: DescriptionMedia[]; + videos?: DescriptionMedia[]; + intro?: string; + description?: string; +}; + +const Description = ({ images, audios, videos, intro, description }: DescriptionProps) => ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {audios?.map((audio, index) => + audio?.src ? ( + + ) : null + )} + {videos?.map((video, index) => + video?.src ? ( + video?.type?.endsWith('taptap') ? ( + + ) : ( + + ) + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/gcores/util.ts b/lib/routes/gcores/util.ts index 5236adb5b..a80c7bbd5 100644 --- a/lib/routes/gcores/util.ts +++ b/lib/routes/gcores/util.ts @@ -1,14 +1,12 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Data, DataItem } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { parseContent } from './parser'; +import { renderDescription } from './templates/description'; const baseUrl: string = 'https://www.gcores.com'; const imageBaseUrl: string = 'https://image.gcores.com'; @@ -113,7 +111,7 @@ const processItems = async (limit: number, query: any, apiUrl: string, targetUrl }; } - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: attributes.cover ? [ { diff --git a/lib/routes/geekpark/index.ts b/lib/routes/geekpark/index.ts index 9fddca5c9..d6522aa89 100644 --- a/lib/routes/geekpark/index.ts +++ b/lib/routes/geekpark/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { column } = ctx.req.param(); @@ -24,8 +23,8 @@ export const handler = async (ctx) => { const title = item.title; const image = item.cover_url; - const description = art(path.join(__dirname, 'templates/description.art'), { - image: image + const description = renderDescription({ + images: image ? [ { src: image, @@ -64,8 +63,8 @@ export const handler = async (ctx) => { const title = data.title; const image = data.cover_url; - const description = art(path.join(__dirname, 'templates/description.art'), { - image: image + const description = renderDescription({ + images: image ? [ { src: image, diff --git a/lib/routes/geekpark/templates/description.art b/lib/routes/geekpark/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/geekpark/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/geekpark/templates/description.tsx b/lib/routes/geekpark/templates/description.tsx new file mode 100644 index 000000000..2e77b346f --- /dev/null +++ b/lib/routes/geekpark/templates/description.tsx @@ -0,0 +1,30 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData) => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/gelbooru/templates/description.art b/lib/routes/gelbooru/templates/description.art deleted file mode 100644 index 85ad3a619..000000000 --- a/lib/routes/gelbooru/templates/description.art +++ /dev/null @@ -1,19 +0,0 @@ -
    - {{if isVideo}} - - {{else}} - - {{/if}} -
    -

    Info of #{{id}}:

    -

    - {{if isHttp}} - Source - {{else}} - Source: {{source}} - {{/if}} - ({{sourceHost}}) -

    -

    Upload by: {{owner}}

    -

    Score: {{score}}

    -

    Tags:

    {{tags}}

    diff --git a/lib/routes/gelbooru/utils.ts b/lib/routes/gelbooru/utils.tsx similarity index 55% rename from lib/routes/gelbooru/utils.ts rename to lib/routes/gelbooru/utils.tsx index 3d59a4e9a..70c53c1e1 100644 --- a/lib/routes/gelbooru/utils.ts +++ b/lib/routes/gelbooru/utils.tsx @@ -1,7 +1,6 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; -import { art } from '@/utils/render'; export function renderDesc(post, link, quality: 'sample' | 'orig') { const { id, source, owner, file_url: fileUrl, tags, score } = post; @@ -19,7 +18,7 @@ export function renderDesc(post, link, quality: 'sample' | 'orig') { contentURL = fileUrl; } - return art(path.join(__dirname, 'templates/description.art'), { + return renderDescription({ id, source, owner, @@ -33,6 +32,26 @@ export function renderDesc(post, link, quality: 'sample' | 'orig') { }); } +const renderDescription = ({ id, source, owner, tags, link, isHttp, sourceHost, contentURL, isVideo, score }): string => + renderToString( + <> +
    {isVideo ? : }
    +

    + Info of #{id}:{' '} +

    +

    + {isHttp ? Source : Source: {source}} ({sourceHost}) +

    +

    + Upload by: {owner} +

    +

    Score: {score}

    +

    + Tags:

    {tags}

    +

    + + ); + export function getAPIKeys() { return { apiKey: config.gelbooru.apiKey || '', diff --git a/lib/routes/gelonghui/live.ts b/lib/routes/gelonghui/live.tsx similarity index 76% rename from lib/routes/gelonghui/live.ts rename to lib/routes/gelonghui/live.tsx index 2fd2b6dbd..304f62f79 100644 --- a/lib/routes/gelonghui/live.ts +++ b/lib/routes/gelonghui/live.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://www.gelonghui.com'; @@ -41,9 +40,7 @@ async function handler() { const items = result.map((i) => ({ title: i.title || i.content, - description: art(path.join(__dirname, 'templates/live.art'), { - i, - }), + description: renderToString(), link: i.route, category: i.source, pubDate: parseDate(i.createTimestamp, 'X'), @@ -57,3 +54,17 @@ async function handler() { item: items, }; } + +const GelonghuiLiveDescription = ({ i }: { i: { content?: string; pictures?: string[] } }) => ( + <> + {i.content} + {i.pictures?.length ? ( + <> +
    + {i.pictures.map((p) => ( + + ))} + + ) : null} + +); diff --git a/lib/routes/gelonghui/templates/live.art b/lib/routes/gelonghui/templates/live.art deleted file mode 100644 index bd712f626..000000000 --- a/lib/routes/gelonghui/templates/live.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ i.content }} -{{ if i.pictures }} -
    - {{ each i.pictures p }} - - {{ /each }} -{{ /if }} diff --git a/lib/routes/gettr/templates/post.art b/lib/routes/gettr/templates/post.art deleted file mode 100644 index c5fe082f3..000000000 --- a/lib/routes/gettr/templates/post.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if post.txt }}{{@ post.txt.replace(/\n/g, '
    ') }}
    {{ /if }} - -{{ if post.previmg }}
    {{ /if }} - -{{ if post.ttl && post.prevsrc }}{{ post.ttl }}
    {{ /if }} - -{{ if post.dsc && post.prevsrc }}{{ post.dsc }}
    {{ /if }} - -{{ if post.imgs }} - {{ each post.imgs i }} - - {{ /each }} -{{ /if }} diff --git a/lib/routes/gettr/user.ts b/lib/routes/gettr/user.tsx similarity index 64% rename from lib/routes/gettr/user.ts rename to lib/routes/gettr/user.tsx index 1edc33906..d155b1557 100644 --- a/lib/routes/gettr/user.ts +++ b/lib/routes/gettr/user.tsx @@ -1,10 +1,10 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const actionMap = { pub_pst: 'Published a post: ', @@ -59,10 +59,7 @@ async function handler(ctx) { const items = posts.result.data.list.map((post) => { const title = posts.result.aux.post[post.activity.pstid].txt; - const description = art(path.join(__dirname, 'templates/post.art'), { - post: posts.result.aux.post[post.activity.pstid], - mediaHost, - }); + const description = renderToString(); return { title: `${actionMap[post.action]} ${title}`, description, @@ -81,3 +78,41 @@ async function handler(ctx) { item: items, }; } + +const GettrPostDescription = ({ post, mediaHost }: { post: any; mediaHost: string }) => ( + <> + {post.txt ? ( + <> + {raw(post.txt.replaceAll('\n', '
    '))} +
    + + ) : null} + {post.previmg ? ( + <> + +
    + + ) : null} + {post.ttl && post.prevsrc ? ( + <> + + {post.ttl} + +
    + + ) : null} + {post.dsc && post.prevsrc ? ( + <> + {post.dsc} +
    + + ) : null} + {post.imgs ? ( + <> + {post.imgs.map((img) => ( + + ))} + + ) : null} + +); diff --git a/lib/routes/github/pulse.ts b/lib/routes/github/pulse.tsx similarity index 75% rename from lib/routes/github/pulse.ts rename to lib/routes/github/pulse.tsx index ca21eb559..77ddf9ce2 100644 --- a/lib/routes/github/pulse.ts +++ b/lib/routes/github/pulse.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import md5 from '@/utils/md5'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/pulse/:user/:repo/:period?', @@ -103,11 +102,31 @@ async function handler(ctx) { { guid: md5(`${user}${repo}${period}${periodFrom}${periodTo}`), title: `${periodFrom} - ${periodTo}`, - description: art(path.join(__dirname, 'templates/pulse-description.art'), { - overviewItems, - commitActivity, - githubActivity, - }), + description: renderToString( + <> +

    Overview

    +
      + {overviewItems.map((item) => ( +
    • {item}
    • + ))} +
    + {commitActivity ? raw(commitActivity) : null} + {(githubActivity ?? []).map((activity) => ( + <> +

    {activity.heading}

    + {activity.paragraph ?

    {activity.paragraph}

    : null} +
      + {activity.items.map((item) => ( +
    • + {item.link.text} +

      {item.details}

      +
    • + ))} +
    + + ))} + + ), pubDate: parseDate(periodTo), }, ], diff --git a/lib/routes/github/templates/comments-description.art b/lib/routes/github/templates/comments-description.art deleted file mode 100644 index 7755ee9f6..000000000 --- a/lib/routes/github/templates/comments-description.art +++ /dev/null @@ -1 +0,0 @@ -{{@ desc }} diff --git a/lib/routes/github/templates/pulse-description.art b/lib/routes/github/templates/pulse-description.art deleted file mode 100644 index d5580e7dd..000000000 --- a/lib/routes/github/templates/pulse-description.art +++ /dev/null @@ -1,24 +0,0 @@ -

    Overview

    - -
      -{{each overviewItems item}} -
    • {{item}}
    • -{{/each}} -
    - -{{@ commitActivity}} - -{{each githubActivity activity}} -

    {{activity.heading}}

    - {{if activity.paragraph}} -

    {{activity.paragraph}}

    - {{/if}} -
      - {{each activity.items item}} -
    • - {{item.link.text}} -

      {{item.details}}

      -
    • - {{/each}} -
    -{{/each}} \ No newline at end of file diff --git a/lib/routes/github/templates/trending-description.art b/lib/routes/github/templates/trending-description.art deleted file mode 100644 index 4f8046781..000000000 --- a/lib/routes/github/templates/trending-description.art +++ /dev/null @@ -1,5 +0,0 @@ - -
    {{@ desc }} -

    Language: {{@ lang }} -
    Stars: {{@ stars }} -
    Forks: {{@ forks }} diff --git a/lib/routes/github/trending.ts b/lib/routes/github/trending.tsx similarity index 87% rename from lib/routes/github/trending.ts rename to lib/routes/github/trending.tsx index 4004eced9..a14ba30c9 100644 --- a/lib/routes/github/trending.ts +++ b/lib/routes/github/trending.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/trending/:since/:language/:spoken_language?', @@ -137,13 +136,20 @@ async function handler(ctx) { item: repos.map((r) => ({ title: r.nameWithOwner, author: r.owner, - description: art(path.join(__dirname, 'templates/trending-description.art'), { - cover: r.openGraphImageUrl, - desc: r.description, - forks: r.forkCount, - lang: r.primaryLanguage?.name || 'Unknown', - stars: r.stargazerCount, - }), + description: renderToString( + <> + +
    + {r.description ? raw(r.description) : null} +
    +
    + Language: {raw(r.primaryLanguage?.name || 'Unknown')} +
    + Stars: {raw(String(r.stargazerCount))} +
    + Forks: {raw(String(r.forkCount))} + + ), link: `https://github.com/${r.nameWithOwner}`, })), }; diff --git a/lib/routes/gitpod/blog.ts b/lib/routes/gitpod/blog.tsx similarity index 82% rename from lib/routes/gitpod/blog.ts rename to lib/routes/gitpod/blog.tsx index a4c262192..49fda43be 100644 --- a/lib/routes/gitpod/blog.ts +++ b/lib/routes/gitpod/blog.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { rootUrl } from './utils'; @@ -57,11 +56,13 @@ async function handler(ctx) { const content = load(detailResponse.data); const headerImg = content('img[class^=max-h]'); - item.description = art(path.join(__dirname, 'templates/description.art'), { - img: headerImg.attr('src'), - alt: headerImg.attr('alt'), - content: content('div[class^=content-blog]').html(), - }); + const contentHtml = content('div[class^=content-blog]').html(); + item.description = renderToString( + <> + {headerImg.attr('alt')} + {contentHtml ? raw(contentHtml) : null} + + ); item.author = content('span.avatars a') .toArray() .map((e) => content(e).text().trim()) diff --git a/lib/routes/gitpod/templates/description.art b/lib/routes/gitpod/templates/description.art deleted file mode 100644 index c52a110a2..000000000 --- a/lib/routes/gitpod/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{ alt }} -{{@ content }} \ No newline at end of file diff --git a/lib/routes/gitstar-ranking/index.ts b/lib/routes/gitstar-ranking/index.tsx similarity index 78% rename from lib/routes/gitstar-ranking/index.ts rename to lib/routes/gitstar-ranking/index.tsx index f8ac0ae69..3f58ab6d7 100644 --- a/lib/routes/gitstar-ranking/index.ts +++ b/lib/routes/gitstar-ranking/index.tsx @@ -1,14 +1,51 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; + +const renderDescription = ({ images, stargazersCount, language, description }) => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {stargazersCount ? ( + + + {stargazersCount ? ( + + + + + ) : null} + {language ? ( + + + + + ) : null} + {description ? ( + + + + + ) : null} + +
    Stars{stargazersCount}
    Language{language}
    Description{description}
    + ) : null} + + ); export const handler = async (ctx: Context): Promise => { const { category = 'repositories' } = ctx.req.param(); @@ -31,7 +68,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('span.hidden-xs').text()?.trim(); const image: string | undefined = $el.find('img.avatar_image_big').attr('src'); const language: string | undefined = $el.find('div.repo-language').text()?.trim(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { diff --git a/lib/routes/gitstar-ranking/templates/description.art b/lib/routes/gitstar-ranking/templates/description.art deleted file mode 100644 index 1a1e6ebad..000000000 --- a/lib/routes/gitstar-ranking/templates/description.art +++ /dev/null @@ -1,38 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if stargazersCount }} - - - {{ if stargazersCount }} - - - - - {{ /if }} - {{ if language }} - - - - - {{ /if }} - {{ if description }} - - - - - {{ /if }} - -
    Stars{{ stargazersCount }}
    Language{{ language }}
    Description{{ description }}
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/gofans/index.ts b/lib/routes/gofans/index.tsx similarity index 68% rename from lib/routes/gofans/index.ts rename to lib/routes/gofans/index.tsx index fb129ee64..3a95bd3ff 100644 --- a/lib/routes/gofans/index.ts +++ b/lib/routes/gofans/index.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:kind?', @@ -40,13 +40,7 @@ async function handler(ctx) { const items = response.data.map((item) => ({ title: `「${item.price === '0.00' ? '免费' : '降价'}」-「${item.kind === 1 ? 'macOS' : 'iOS'}」${item.name}`, - description: art(path.join(__dirname, 'templates/description.art'), { - icon: item.icon, - originalPrice: item.original_price, - price: item.price, - kind: item.kind, - description: item.description.replaceAll('\n', '
    '), - }), + description: renderToString(')} />), pubDate: parseDate(item.updated_at, 'X'), link: new URL(`/app/${item.uuid}`, baseUrl).href, category: item.primary_genre_name, @@ -59,3 +53,15 @@ async function handler(ctx) { item: items, }; } + +const GofansDescription = ({ icon, originalPrice, price, kind, description }: { icon: string; originalPrice: string; price: string; kind: number; description: string }) => ( + <> + +
    + 原价:¥{originalPrice} {'->'} 现价:¥{price} +
    + 平台:{kind === 1 ? 'macOS' : 'iOS'} +
    + {raw(description)} + +); diff --git a/lib/routes/gofans/templates/description.art b/lib/routes/gofans/templates/description.art deleted file mode 100644 index 1126b1bb6..000000000 --- a/lib/routes/gofans/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ - -
    -原价:¥{{ originalPrice }} -> 现价:¥{{ price }} -
    -平台:{{ kind === 1 ? 'macOS' : 'iOS' }} -
    -{{@ description }} diff --git a/lib/routes/google/fonts.ts b/lib/routes/google/fonts.tsx similarity index 74% rename from lib/routes/google/fonts.ts rename to lib/routes/google/fonts.tsx index 2d5dcda6f..d546321e4 100644 --- a/lib/routes/google/fonts.ts +++ b/lib/routes/google/fonts.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const titleMap = { date: 'Newest', @@ -66,11 +65,32 @@ async function handler(ctx) { data && data.map((item) => ({ title: item.family, - description: art(path.join(__dirname, './templates/fonts.art'), { - item, - }), + description: renderDescription(item), link: `https://fonts.google.com/specimen/${item.family.replaceAll(/\s/g, '+')}`, pubDate: parseDate(item.lastModified, 'YYYY-MM-DD'), })), }; } + +const renderDescription = (item): string => + renderToString( + <> + Family: {item.family} +
    + Category: {item.category} +
    + Subsets: {item.subsets?.join(',')} +
    + Version: {item.version} +
    + Last modified: {item.lastModified} +
    + File: +
    + {Object.entries(item.files ?? {}).map(([key, value]) => ( + <> + {key}   + + ))} + + ); diff --git a/lib/routes/google/news.ts b/lib/routes/google/news.tsx similarity index 88% rename from lib/routes/google/news.ts rename to lib/routes/google/news.tsx index 2bb0fc25e..a57fc2798 100644 --- a/lib/routes/google/news.ts +++ b/lib/routes/google/news.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://news.google.com'; @@ -90,10 +88,7 @@ async function handler(ctx) { return { title, - description: art(path.join(__dirname, 'templates/news.art'), { - img: item.find('img.Quavad').attr('src'), - brief: title, - }), + description: renderDescription(item.find('img.Quavad').attr('src'), title), pubDate: parseDate(item.find('time').attr('datetime')), author: authors, link: new URL(item.find('a.WwrzSb').first().attr('href'), baseUrl).href, @@ -106,3 +101,16 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (img: string | undefined, brief: string): string => + renderToString( + <> + {img ? ( + <> + +
    + + ) : null} + {brief} + + ); diff --git a/lib/routes/google/templates/fonts.art b/lib/routes/google/templates/fonts.art deleted file mode 100644 index ef3c0b40e..000000000 --- a/lib/routes/google/templates/fonts.art +++ /dev/null @@ -1,15 +0,0 @@ -Family: {{item.family}} -
    -Category: {{item.category}} -
    -Subsets: {{item.subsets.join(',')}} -
    -Version: {{item.version}} -
    -Last modified: {{item.lastModified}} -
    -File: -
    -{{each item.files}} -{{$index}}   -{{/each}} diff --git a/lib/routes/google/templates/news.art b/lib/routes/google/templates/news.art deleted file mode 100644 index 15c7fe381..000000000 --- a/lib/routes/google/templates/news.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if img }} - -
    -{{ /if }} -{{ brief }} diff --git a/lib/routes/gov/caac/cjwt.ts b/lib/routes/gov/caac/cjwt.tsx similarity index 71% rename from lib/routes/gov/caac/cjwt.ts rename to lib/routes/gov/caac/cjwt.tsx index 54f6c56c1..f06636429 100644 --- a/lib/routes/gov/caac/cjwt.ts +++ b/lib/routes/gov/caac/cjwt.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -54,9 +54,7 @@ async function handler(ctx) { .map((item) => ({ title: item.infoMess.replaceAll(/<\/?em>/g, ''), link: new URL(`index_180.html?info=${item.id}&type=id`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - item, - }), + description: renderDescription(item), author: `${item.gname}/${item.feedbackName}`, category: [item.messageType], guid: `caac-cjwt#${item.id}`, @@ -83,3 +81,28 @@ async function handler(ctx) { allowEmpty: true, }; } + +const renderDescription = (item): string => + renderToString( +
    +
    +
    + + {item.workUnit} + {item.gname} + {item.createDate} +
    +
    +

    {item.infoMess ? raw(item.infoMess) : null}

    +
    +
    + + {item.feedbackName} + {item.feedbackDate} +
    +
    +

    {item.feedback ? raw(item.feedback) : null}

    +
    +
    +
    + ); diff --git a/lib/routes/gov/caac/templates/description.art b/lib/routes/gov/caac/templates/description.art deleted file mode 100644 index 3e87d9271..000000000 --- a/lib/routes/gov/caac/templates/description.art +++ /dev/null @@ -1,25 +0,0 @@ -
    -
    -
    - - {{ item.workUnit }} - {{ item.gname }} - {{ item.createDate }} -
    -
    -

    - {{@ item.infoMess }} -

    -
    -
    - - {{ item.feedbackName }} - {{ item.feedbackDate }} -
    -
    -

    - {{@ item.feedback }} -

    -
    -
    -
    \ No newline at end of file diff --git a/lib/routes/gov/cmse/fxrw.ts b/lib/routes/gov/cmse/fxrw.ts index 18e025d9a..b1b6be4fb 100644 --- a/lib/routes/gov/cmse/fxrw.ts +++ b/lib/routes/gov/cmse/fxrw.ts @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/cmse/fxrw', categories: ['government'], @@ -52,7 +51,7 @@ async function handler() { title: item.find('.title').text().split(':').pop().trim(), link: new URL(item.attr('href'), currentUrl).href, pubDate: timezone(parseDate(item.find('.infoR').first().text().trim(), 'YYYY年M月D日H时m分'), +8), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: new URL(item.find('img').attr('src'), currentUrl).href, description: item.find('.info').html(), }), diff --git a/lib/routes/gov/cmse/index.ts b/lib/routes/gov/cmse/index.ts index 299b1e4eb..56c5dd59b 100644 --- a/lib/routes/gov/cmse/index.ts +++ b/lib/routes/gov/cmse/index.ts @@ -5,9 +5,10 @@ import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/cmse/*', name: 'Unknown', @@ -59,7 +60,7 @@ async function handler(ctx) { const detailPubTimeMatches = detailResponse.data.match(/__\$pubtime='(.*?)';var/); item.pubDate = detailPubTimeMatches ? timezone(parseDate(detailPubTimeMatches[1]), +8) : item.pubDate; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ video: content('#con_video').html(), description: content('.TRS_Editor, #content').html(), }); diff --git a/lib/routes/gov/cmse/templates/description.art b/lib/routes/gov/cmse/templates/description.art deleted file mode 100644 index e1f6cd135..000000000 --- a/lib/routes/gov/cmse/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if image }} - -
    -{{ /if }} -{{ if video }} -{{@ video }} -
    -{{ /if }} -{{ if description }} -{{@ description }} -{{ /if }} diff --git a/lib/routes/gov/cmse/templates/description.tsx b/lib/routes/gov/cmse/templates/description.tsx new file mode 100644 index 000000000..2364e8207 --- /dev/null +++ b/lib/routes/gov/cmse/templates/description.tsx @@ -0,0 +1,27 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: string; + video?: string; + description?: string; +}; + +export const renderDescription = ({ image, video, description }: DescriptionData) => + renderToString( + <> + {image ? ( + <> + +
    + + ) : null} + {video ? ( + <> + {raw(video)} +
    + + ) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/gov/csrc/news.ts b/lib/routes/gov/csrc/news.tsx similarity index 76% rename from lib/routes/gov/csrc/news.ts rename to lib/routes/gov/csrc/news.tsx index 2db23dd1a..4e7080155 100644 --- a/lib/routes/gov/csrc/news.ts +++ b/lib/routes/gov/csrc/news.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -42,7 +40,20 @@ async function handler(ctx) { out = data.data.data.results.map((item) => ({ title: item.title, - description: item.contentHtml + art(path.join(__dirname, 'templates/attachment.art'), { attachments: item.resList }), + description: + (item.contentHtml ?? '') + + renderToString( + <> + {item.resList?.map((attachment) => { + const href = attachment.filePath?.startsWith('/') ? `${baseUrl}${attachment.filePath}` : attachment.filePath; + return ( + + {attachment.fileName} + + ); + })} + + ), pubDate: parseDate(item.publishedTime, 'x'), link: item.url, })); diff --git a/lib/routes/gov/csrc/templates/attachment.art b/lib/routes/gov/csrc/templates/attachment.art deleted file mode 100644 index 772610f77..000000000 --- a/lib/routes/gov/csrc/templates/attachment.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each attachments a }} - {{ a.fileName }} -{{ /each }} diff --git a/lib/routes/gov/forestry/gjlckjdjt.ts b/lib/routes/gov/forestry/gjlckjdjt.ts index 8c6baef00..290032a59 100644 --- a/lib/routes/gov/forestry/gjlckjdjt.ts +++ b/lib/routes/gov/forestry/gjlckjdjt.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/forestry/gjlckjdjt/:category?', @@ -58,7 +57,7 @@ async function handler(ctx) { return { title, link, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: { src: item.find('img').prop('src'), alt: title, @@ -89,7 +88,7 @@ async function handler(ctx) { item.enclosure_url = item.enclosure_url ?? src; e.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ video: { src, }, diff --git a/lib/routes/gov/forestry/templates/description.art b/lib/routes/gov/forestry/templates/description.art deleted file mode 100644 index 3b9a231ca..000000000 --- a/lib/routes/gov/forestry/templates/description.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if image }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if video }} - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/gov/forestry/templates/description.tsx b/lib/routes/gov/forestry/templates/description.tsx new file mode 100644 index 000000000..8681dc1fe --- /dev/null +++ b/lib/routes/gov/forestry/templates/description.tsx @@ -0,0 +1,34 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageData = { + src?: string; + alt?: string; +}; + +type VideoData = { + src?: string; +}; + +type DescriptionData = { + image?: ImageData; + video?: VideoData; +}; + +export const renderDescription = ({ image, video }: DescriptionData) => + renderToString( + <> + {image ? ( +
    + {image.alt} +
    + ) : null} + {video ? ( + + ) : null} + + ); diff --git a/lib/routes/gov/general/general.ts b/lib/routes/gov/general/general.ts index 6f0d4e677..d060e356c 100644 --- a/lib/routes/gov/general/general.ts +++ b/lib/routes/gov/general/general.ts @@ -39,10 +39,11 @@ import { getSubPath } from '@/utils/common-utils'; // }; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { finishArticleItem } from '@/utils/wechat-mp'; +import { renderZcjdpt } from './templates/zcjdpt'; + const gdgov = async (info, ctx) => { const path = getSubPath(ctx) .split('/') @@ -183,7 +184,7 @@ const gdgov = async (info, ctx) => { return { link, title: data.art_title, - description: art(path.join(__dirname, 'templates/zcjdpt.art'), data), + description: renderZcjdpt(data), pubDate: timezone(parseDate(data.pub_time), +8), author: /(本|本网|本站)/.test(data.pub_unite) ? authorisme : data.pub_unite, }; diff --git a/lib/routes/gov/general/templates/zcjdpt.art b/lib/routes/gov/general/templates/zcjdpt.art deleted file mode 100644 index e75bd0026..000000000 --- a/lib/routes/gov/general/templates/zcjdpt.art +++ /dev/null @@ -1,128 +0,0 @@ -
    -
    -

    {{art_title}}

    -

    {{pub_unite}}

    -
    - {{if pub_time.length}}政策发布时间:{{pub_time}}{{/if}} - {{if expiry_date.length}} 政策有效期:{{expiry_date}}{{/if}} -
    -
    -
    -
    - 政策原文 -
    - {{if she_qi_items.has_money}} -
    - 本政策涉及资金支持 -
    - {{/if}} -
    -
    -

    {{zc_title}}

    -

    {{summary}}

    -
    - >>阅读原文 -
    - - {{if she_qi_items.id}} -
    -

    政策种类

    -
    - <% var zcType = she_qi_items.zc_type_text.split(','); %> - <% var zcTypeIcon = she_qi_items.zc_type_icon.split(','); %> - <% for(var i = 0; i < zcType.length; i++){ %> -
    <%= zcTypeIcon[i] %><%= zcType[i] %>
    - <% } %> -
    -
    - -
    -

    政策关键词

    -
    - <% var zcKey = she_qi_items.zc_key.split(','); %> - <% for (var i = 0; i < zcKey.length; i++) { %> - <% if (i % 2 == 0) { %> - {{zcKey[i]}} - <% } else { %> - {{zcKey[i]}} - <% } %> - <% } %> -
    -
    - -
    -

    适用群体

    -
    -
    - 企业类型 - <% var cType = she_qi_items.company_type_text.split(','); %> - {{each cType}} - {{$value}} - {{/each}} -
    -
    -
    -
    - 行业 - <% var iType = she_qi_items.industry_text.split(','); %> - {{each iType}} - {{$value}} - {{/each}} -
    -
    -
    -
    - 企业规模 - <% var cScale = she_qi_items.scale_text.split(','); %> - {{each cScale}} - {{$value}} - {{/each}} -
    -
    -
    - {{/if}} - - - {{if jie_du_items.length}} -
    - {{each jie_du_items}} -
    -

    {{$value.jd_title}}

    -
    -
    -
    -

    {{@ $value.jd_content}}

    -
    -
    -
    - {{if $value.attach_items.length}} -
    - {{each $value.attach_items}} - - {{/each}} -
    - {{/if}} -
    - {{/each}} -
    - {{/if}} - - - {{if wen_da_items.length}} -
    -
    -
    - {{each wen_da_items}} -
    -
    -

    问:{{$value.question}}

    -
    答:{{$value.answer}}
    -
    -
    - {{/each}} -
    -
    -
    - {{/if}} -
    -
    diff --git a/lib/routes/gov/general/templates/zcjdpt.tsx b/lib/routes/gov/general/templates/zcjdpt.tsx new file mode 100644 index 000000000..d47a60474 --- /dev/null +++ b/lib/routes/gov/general/templates/zcjdpt.tsx @@ -0,0 +1,245 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type SheQiItems = { + id?: string; + has_money?: boolean; + zc_type_text?: string; + zc_type_icon?: string; + zc_key?: string; + company_type_text?: string; + industry_text?: string; + scale_text?: string; +}; + +type JieDuItem = { + jd_title?: string; + jd_content?: string; + attach_items?: Array<{ file_url?: string }>; +}; + +type WenDaItem = { + question?: string; + answer?: string; +}; + +type ZcjdptData = { + art_title?: string; + pub_unite?: string; + pub_time?: string; + expiry_date?: string; + zc_title?: string; + summary?: string; + link?: string; + she_qi_items?: SheQiItems; + jie_du_items?: JieDuItem[]; + wen_da_items?: WenDaItem[]; +}; + +const splitValues = (value?: string) => (value ? value.split(',') : []); + +export const renderZcjdpt = (data: ZcjdptData) => { + const sheQiItems = data.she_qi_items ?? {}; + const zcType = splitValues(sheQiItems.zc_type_text); + const zcTypeIcon = splitValues(sheQiItems.zc_type_icon); + const zcKey = splitValues(sheQiItems.zc_key); + const cType = splitValues(sheQiItems.company_type_text); + const iType = splitValues(sheQiItems.industry_text); + const cScale = splitValues(sheQiItems.scale_text); + + return renderToString( +
    +
    +

    + {data.art_title} +

    +

    + {data.pub_unite} +

    +
    + {data.pub_time?.length ? ( + + 政策发布时间:{data.pub_time} + + ) : null} + {data.expiry_date?.length ? ( + + 政策有效期:{data.expiry_date} + + ) : null} +
    +
    +
    +
    + + 政策原文 + +
    + {sheQiItems.has_money ? ( +
    + 本政策涉及资金支持 +
    + ) : null} +
    +
    +

    {data.zc_title}

    +

    {data.summary}

    +
    + + >>阅读原文 + +
    + + {sheQiItems.id ? ( + <> +
    +

    + 政策种类 +

    +
    + {zcType.map((type, index) => ( +
    + {zcTypeIcon[index]} + {type} +
    + ))} +
    +
    + +
    +

    + 政策关键词 +

    +
    + {zcKey.map((key, index) => + index % 2 === 0 ? ( + {key} + ) : ( + + {key} + + ) + )} +
    +
    + +
    +

    + 适用群体 +

    +
    +
    + + 企业类型 + + {cType.map((item) => ( + {item} + ))} +
    +
    +
    +
    + + 行业 + + {iType.map((item) => ( + {item} + ))} +
    +
    +
    +
    + + 企业规模 + + {cScale.map((item) => ( + {item} + ))} +
    +
    +
    + + ) : null} + + {data.jie_du_items?.length ? ( +
    + {data.jie_du_items.map((item) => ( +
    +

    + {item.jd_title} +

    +
    +
    +
    +

    {item.jd_content ? raw(item.jd_content) : null}

    +
    +
    +
    + {item.attach_items?.length ? ( +
    + {item.attach_items.map((attach) => ( + + ))} +
    + ) : null} +
    + ))} +
    + ) : null} + + {data.wen_da_items?.length ? ( +
    +
    +
    + {data.wen_da_items.map((item) => ( +
    +
    +

    问:{item.question}

    +
    答:{item.answer}
    +
    +
    + ))} +
    +
    +
    + ) : null} +
    +
    + ); +}; diff --git a/lib/routes/gov/guangdong/tqyb/sncsyjxh.ts b/lib/routes/gov/guangdong/tqyb/sncsyjxh.tsx similarity index 83% rename from lib/routes/gov/guangdong/tqyb/sncsyjxh.ts rename to lib/routes/gov/guangdong/tqyb/sncsyjxh.tsx index 8321dfe75..7fe0291a8 100644 --- a/lib/routes/gov/guangdong/tqyb/sncsyjxh.ts +++ b/lib/routes/gov/guangdong/tqyb/sncsyjxh.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const rootUrl = 'http://www.tqyb.com.cn'; @@ -48,9 +47,15 @@ async function handler() { const items = data.map((item) => ({ title: item.cname + ' ' + item.sigtypename, link: `http://www.tqyb.com.cn/gz/weatherAlarm/otherCity/`, - description: art(path.join(__dirname, './templates/sncsyjxh.art'), { - item, - }), + description: renderToString( + <> + 地区: {item.cname} +
    + 等级: {item.sigtypename} +
    + 发布时间:{item.datetime} + + ), pubDate: timezone(parseDate(item.datetime, 'YYYY年MM月DD日 HH:mm'), +8), guid: timezone(parseDate(item.datetime, 'YYYY年MM月DD日 HH:mm'), +8) + item.cname + item.sigtypename, })); diff --git a/lib/routes/gov/guangdong/tqyb/templates/sncsyjxh.art b/lib/routes/gov/guangdong/tqyb/templates/sncsyjxh.art deleted file mode 100644 index 71b2bd659..000000000 --- a/lib/routes/gov/guangdong/tqyb/templates/sncsyjxh.art +++ /dev/null @@ -1,5 +0,0 @@ -地区: {{ item.cname }} -
    -等级: {{ item.sigtypename }} -
    -发布时间:{{ item.datetime }} diff --git a/lib/routes/gov/guangdong/tqyb/templates/tfxtq.art b/lib/routes/gov/guangdong/tqyb/templates/tfxtq.art deleted file mode 100644 index 072eb9e0a..000000000 --- a/lib/routes/gov/guangdong/tqyb/templates/tfxtq.art +++ /dev/null @@ -1 +0,0 @@ -

    {{ content }}

    diff --git a/lib/routes/gov/guangdong/tqyb/tfxtq.ts b/lib/routes/gov/guangdong/tqyb/tfxtq.tsx similarity index 88% rename from lib/routes/gov/guangdong/tqyb/tfxtq.ts rename to lib/routes/gov/guangdong/tqyb/tfxtq.tsx index 37f2a96c9..3dde4b33a 100644 --- a/lib/routes/gov/guangdong/tqyb/tfxtq.ts +++ b/lib/routes/gov/guangdong/tqyb/tfxtq.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'http://www.tqyb.com.cn'; @@ -41,9 +40,7 @@ async function handler() { title: item.title, link: 'http://www.tqyb.com.cn/gz/weatherAlarm/suddenWeather/', author: item.issuer, - description: art(path.join(__dirname, './templates/tfxtq.art'), { - content: item.content, - }), + description: renderToString(

    {item.content}

    ), pubDate: parseDate(item.ddate), guid: parseDate(item.ddate) + item.title, })); diff --git a/lib/routes/gov/hangzhou/templates/jbxx.art b/lib/routes/gov/hangzhou/templates/jbxx.art deleted file mode 100644 index 552356d59..000000000 --- a/lib/routes/gov/hangzhou/templates/jbxx.art +++ /dev/null @@ -1,173 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {{ if otherInfo.legalPersonThemeClassification}} - - - - - {{ /if }} - {{ if !otherInfo.legalPersonThemeClassification }} - - - {{ /if }} - - - - - - - - - - - - - - -
    办事信息
    服务对象{{serviceInfo.serviceTarget}}办理形式{{serviceInfo.processingMethods}}
    办理地点{{serviceInfo.processingLocation}}
    办理时间{{serviceInfo.processingTime}}
    申请信息
    受理条件{{applicationInfo.acceptanceConditions}}
    禁止性要求{{applicationInfo.prohibitedRequirements}}
    数量限制{{applicationInfo.quantityRestrictions}}
    结果信息
    审批结果名称{{resultInfo.approvalResult}}
    审批结果样本{{@ resultInfo.approvalSample }}审批结果类型{{resultInfo.approvalResultType}}
    收费信息
    是否收费{{feeInfo.isThereAFee}}是否支持网上支付{{feeInfo.isOnlinePaymentSupported}}
    审批信息
    权力来源{{approvalInfo.authoritySource}}
    行使层级{{approvalInfo.exerciseLevel}}实施主体性质{{approvalInfo.implementingEntity}}
    送达信息
    是否支持物流快递{{deliveryInfo.isLogisticsSupported}}送达时限{{deliveryInfo.deliveryTimeframe}}
    送达方式{{deliveryInfo.deliveryMethods}}
    中介服务信息
    中介服务事项名称{{agentService}}
    其他信息
    部门名称{{otherInfo.departmentName}}事项类型{{otherInfo.matterType}}
    受理机构{{otherInfo.acceptingInstitution}}
    基本编码{{otherInfo.basicCode}}实施编码{{otherInfo.implementationCode}}
    通办范围{{otherInfo.scopeOfGeneralHandling}}办件类型{{otherInfo.documentType}}
    决定机构{{otherInfo.decisionMakingAuthority}}委托部门{{otherInfo.delegatedDepartment}}
    网上办理深度{{otherInfo.onlineProcessingDepth}}事项审查类型{{otherInfo.reviewType}}
    是否进驻政务大厅{{otherInfo.isItAvailableInTheGovernmentServiceHall}}是否支持自助终端办理{{otherInfo.isSelfServiceTerminalProcessingSupported}}
    是否实行告知承诺{{otherInfo.isACommitmentSystemImplemented}}权力属性{{otherInfo.authorityAttribute}}
    是否支持预约办理{{otherInfo.isAppointmentBookingSupported}}是否网办{{otherInfo.isOnlineProcessingAvailable}}
    自然人主题分类{{otherInfo.naturalPersonThemeClassification}}法人主题分类{{otherInfo.legalPersonThemeClassification}}法人主题分类{{otherInfo.naturalPersonThemeClassification}}
    行政相对人权利和义务 - {{otherInfo.rightsAndObligationsOfAdministrativeCounterparties}} -
    适用对象说明{{otherInfo.applicableObjectDescription}}
    涉及的内容{{otherInfo.contentInvolved}}
    diff --git a/lib/routes/gov/hangzhou/zwfw.ts b/lib/routes/gov/hangzhou/zwfw.ts deleted file mode 100644 index 48a3d36c5..000000000 --- a/lib/routes/gov/hangzhou/zwfw.ts +++ /dev/null @@ -1,112 +0,0 @@ -import path from 'node:path'; - -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 { parseDate } from '@/utils/parse-date'; -import puppeteer from '@/utils/puppeteer'; -import { art } from '@/utils/render'; -import timezone from '@/utils/timezone'; - -import { analyzer, crawler } from './zjzwfw'; - -export const route: Route = { - path: '/hangzhou/zwfw', - categories: ['government'], - example: '/gov/hangzhou/zwfw', - features: { - requireConfig: false, - requirePuppeteer: true, - antiCrawler: true, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['hangzhou.gov.cn/col/col1256349/index.html'], - }, - ], - name: '政务服务公开', - maintainers: ['flynncao'], - handler, - url: 'hangzhou.gov.cn/col/col1256349/index.html', -}; - -async function handler() { - const host = 'https://www.hangzhou.gov.cn/col/col1256349/index.html'; - const response = await ofetch(host); - - const browser = await puppeteer(); - const link = host; - const formatted = response - .replace('', '') - .replaceAll('', '') - .replaceAll('', '') - .replaceAll('', '') - .replaceAll('', '') - .replaceAll('', ''); - const $ = load(formatted); - - const list = $('li.clearfix') - .toArray() - .map((item: any) => { - item = $(item); - const title = item.find('a').first().text(); - const time = timezone(parseDate(item.find('span').first().text(), 'YYYY-MM-DD'), 8); - const a = item.find('a').first().attr('href'); - const fullUrl = new URL(a, host).href; - - return { - title, - link: fullUrl, - pubDate: time, - }; - }) - .filter((item) => !item.title.includes('置顶')); - const items: any = await Promise.all( - list.map((item: any) => - cache.tryGet(item.link, async () => { - const host = new URL(item.link).hostname; - if (host === 'www.zjzwfw.gov.cn') { - // 来源为浙江政务服务网 - const content = await crawler(item, browser); - const $ = load(content); - item.description = art(path.resolve(__dirname, 'templates/jbxx.art'), analyzer($('.item-left .item .bg_box'))); - item.author = '浙江政务服务网'; - item.category = $('meta[name="ColumnType"]').attr('content'); - } else { - // 其他正常抓取 - const response = await got(item.link); - const $ = load(response.data); - if (host === 'police.hangzhou.gov.cn') { - // 来源为杭州市公安局 - item.description = $('.art-content .wz_con_content').html(); - item.author = $('meta[name="ContentSource"]').attr('content'); - item.category = $('meta[name="ColumnType"]').attr('content'); - } else { - // 缺省:来源为杭州市政府网 - item.description = $('.article').html(); - item.author = $('meta[name="ContentSource"]').attr('content'); - item.category = $('meta[name="ColumnType"]').attr('content'); - } - } - item.pubDate = $('meta[name="PubDate"]').length ? timezone(parseDate($('meta[name="PubDate"]').attr('content') as string, 'YYYY-MM-DD HH:mm'), 8) : item.pubDate; - return item; - }) - ) - ); - - await browser.close(); - return { - allowEmpty: true, - title: '杭州市人民政府-政务服务公开', - link, - item: items, - }; -} diff --git a/lib/routes/gov/hangzhou/zwfw.tsx b/lib/routes/gov/hangzhou/zwfw.tsx new file mode 100644 index 000000000..52d898934 --- /dev/null +++ b/lib/routes/gov/hangzhou/zwfw.tsx @@ -0,0 +1,288 @@ +import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +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 puppeteer from '@/utils/puppeteer'; +import timezone from '@/utils/timezone'; + +import { analyzer, crawler } from './zjzwfw'; + +const renderDescription = ({ serviceInfo, applicationInfo, resultInfo, feeInfo, approvalInfo, deliveryInfo, agentService, otherInfo }) => + renderToString( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {otherInfo.legalPersonThemeClassification ? ( + <> + + + + + + ) : ( + <> + + + + )} + + + + + + + + + + + + + + +
    办事信息
    服务对象{serviceInfo.serviceTarget}办理形式{serviceInfo.processingMethods}
    办理地点{serviceInfo.processingLocation}
    办理时间{serviceInfo.processingTime}
    申请信息
    受理条件{applicationInfo.acceptanceConditions}
    禁止性要求{applicationInfo.prohibitedRequirements}
    数量限制{applicationInfo.quantityRestrictions}
    结果信息
    审批结果名称{resultInfo.approvalResult}
    审批结果样本{resultInfo.approvalSample ? raw(resultInfo.approvalSample) : null}审批结果类型{resultInfo.approvalResultType}
    收费信息
    是否收费{feeInfo.isThereAFee}是否支持网上支付{feeInfo.isOnlinePaymentSupported}
    审批信息
    权力来源{approvalInfo.authoritySource}
    行使层级{approvalInfo.exerciseLevel}实施主体性质{approvalInfo.implementingEntity}
    送达信息
    是否支持物流快递{deliveryInfo.isLogisticsSupported}送达时限{deliveryInfo.deliveryTimeframe}
    送达方式{deliveryInfo.deliveryMethods}
    中介服务信息
    中介服务事项名称{agentService}
    其他信息
    部门名称{otherInfo.departmentName}事项类型{otherInfo.matterType}
    受理机构{otherInfo.acceptingInstitution}
    基本编码{otherInfo.basicCode}实施编码{otherInfo.implementationCode}
    通办范围{otherInfo.scopeOfGeneralHandling}办件类型{otherInfo.documentType}
    决定机构{otherInfo.decisionMakingAuthority}委托部门{otherInfo.delegatedDepartment}
    网上办理深度{otherInfo.onlineProcessingDepth}事项审查类型{otherInfo.reviewType}
    是否进驻政务大厅{otherInfo.isItAvailableInTheGovernmentServiceHall}是否支持自助终端办理{otherInfo.isSelfServiceTerminalProcessingSupported}
    是否实行告知承诺{otherInfo.isACommitmentSystemImplemented}权力属性{otherInfo.authorityAttribute}
    是否支持预约办理{otherInfo.isAppointmentBookingSupported}是否网办{otherInfo.isOnlineProcessingAvailable}
    自然人主题分类{otherInfo.naturalPersonThemeClassification}法人主题分类{otherInfo.legalPersonThemeClassification}法人主题分类{otherInfo.naturalPersonThemeClassification}
    行政相对人权利和义务{otherInfo.rightsAndObligationsOfAdministrativeCounterparties}
    适用对象说明{otherInfo.applicableObjectDescription}
    涉及的内容{otherInfo.contentInvolved}
    + ); + +export const route: Route = { + path: '/hangzhou/zwfw', + categories: ['government'], + example: '/gov/hangzhou/zwfw', + features: { + requireConfig: false, + requirePuppeteer: true, + antiCrawler: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['hangzhou.gov.cn/col/col1256349/index.html'], + }, + ], + name: '政务服务公开', + maintainers: ['flynncao'], + handler, + url: 'hangzhou.gov.cn/col/col1256349/index.html', +}; + +async function handler() { + const host = 'https://www.hangzhou.gov.cn/col/col1256349/index.html'; + const response = await ofetch(host); + + const browser = await puppeteer(); + const link = host; + const formatted = response + .replace('', '') + .replaceAll('', '') + .replaceAll('', '') + .replaceAll('', '') + .replaceAll('', '') + .replaceAll('', ''); + const $ = load(formatted); + + const list = $('li.clearfix') + .toArray() + .map((item: any) => { + item = $(item); + const title = item.find('a').first().text(); + const time = timezone(parseDate(item.find('span').first().text(), 'YYYY-MM-DD'), 8); + const a = item.find('a').first().attr('href'); + const fullUrl = new URL(a, host).href; + + return { + title, + link: fullUrl, + pubDate: time, + }; + }) + .filter((item) => !item.title.includes('置顶')); + const items: any = await Promise.all( + list.map((item: any) => + cache.tryGet(item.link, async () => { + const host = new URL(item.link).hostname; + if (host === 'www.zjzwfw.gov.cn') { + // 来源为浙江政务服务网 + const content = await crawler(item, browser); + const $ = load(content); + item.description = renderDescription(analyzer($('.item-left .item .bg_box'))); + item.author = '浙江政务服务网'; + item.category = $('meta[name="ColumnType"]').attr('content'); + } else { + // 其他正常抓取 + const response = await got(item.link); + const $ = load(response.data); + if (host === 'police.hangzhou.gov.cn') { + // 来源为杭州市公安局 + item.description = $('.art-content .wz_con_content').html(); + item.author = $('meta[name="ContentSource"]').attr('content'); + item.category = $('meta[name="ColumnType"]').attr('content'); + } else { + // 缺省:来源为杭州市政府网 + item.description = $('.article').html(); + item.author = $('meta[name="ContentSource"]').attr('content'); + item.category = $('meta[name="ColumnType"]').attr('content'); + } + } + item.pubDate = $('meta[name="PubDate"]').length ? timezone(parseDate($('meta[name="PubDate"]').attr('content') as string, 'YYYY-MM-DD HH:mm'), 8) : item.pubDate; + return item; + }) + ) + ); + + await browser.close(); + return { + allowEmpty: true, + title: '杭州市人民政府-政务服务公开', + link, + item: items, + }; +} diff --git a/lib/routes/gov/jiangsu/wlt/index.ts b/lib/routes/gov/jiangsu/wlt/index.tsx similarity index 80% rename from lib/routes/gov/jiangsu/wlt/index.ts rename to lib/routes/gov/jiangsu/wlt/index.tsx index 1b6ad453e..c8d35e376 100644 --- a/lib/routes/gov/jiangsu/wlt/index.ts +++ b/lib/routes/gov/jiangsu/wlt/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/jiangsu/wlt/:page?', @@ -75,13 +73,19 @@ async function handler(ctx) { const performanceName = $('td:contains("项目名称")').next().text().trim(); const performanceContent = $('td:contains("许可内容")').next().text().trim(); - item.description = art(path.join(__dirname, './templates/wlt.art'), { - dateText, - hostingUnit, - licenseNumber, - performanceName, - performanceContent, - }); + item.description = renderToString( + <> + 许可日期:{dateText} +
    + 行政名称:{hostingUnit} +
    + 许可编号:{licenseNumber} +
    + 项目名称:{performanceName} +
    + 许可内容:{performanceContent} + + ); item.pubDate = parseDate(dateText); return item; diff --git a/lib/routes/gov/jiangsu/wlt/templates/wlt.art b/lib/routes/gov/jiangsu/wlt/templates/wlt.art deleted file mode 100644 index b39127372..000000000 --- a/lib/routes/gov/jiangsu/wlt/templates/wlt.art +++ /dev/null @@ -1,9 +0,0 @@ -许可日期:{{ dateText }} -
    -行政名称:{{ hostingUnit }} -
    -许可编号:{{ licenseNumber }} -
    -项目名称:{{ performanceName }} -
    -许可内容:{{ performanceContent }} diff --git a/lib/routes/gov/safe/templates/message.art b/lib/routes/gov/safe/templates/message.art deleted file mode 100644 index bb909b2f6..000000000 --- a/lib/routes/gov/safe/templates/message.art +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - {{ if message }} - {{ set object = message }} - - - - - - {{ /if }} - {{ if reply }} - {{ set object = reply }} - - - - - - {{ /if }} - -
    留言人留言内容留言时间
    {{ object.author }}{{ object.content }}{{ object.date }}
    {{ object.author }}{{ object.content }}{{ object.date }}
    \ No newline at end of file diff --git a/lib/routes/gov/safe/util.ts b/lib/routes/gov/safe/util.tsx similarity index 68% rename from lib/routes/gov/safe/util.ts rename to lib/routes/gov/safe/util.tsx index ab48c67e1..5fe99857c 100644 --- a/lib/routes/gov/safe/util.ts +++ b/lib/routes/gov/safe/util.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://www.safe.gov.cn'; @@ -47,10 +45,31 @@ const processZxfkItems = async (site = 'beijing', category = 'ywzx', limit = '3' return { title: `${message.author}: ${message.content}`, link: currentUrl, - description: art(path.join(__dirname, 'templates/message.art'), { - message, - reply, - }), + description: renderToString( + + + + + + + + {message ? ( + + + + + + ) : null} + {reply ? ( + + + + + + ) : null} + +
    留言人留言内容留言时间
    {message.author}{message.content}{message.date}
    {reply.author}{reply.content}{reply.date}
    + ), author: `${message.author}/${reply.author}`, guid: `${currentUrl}#${message.author}(${message.date})/${reply.author}(${reply.date})`, pubDate: parseDate(message.date), diff --git a/lib/routes/gov/samr/templates/description.art b/lib/routes/gov/samr/templates/description.art deleted file mode 100644 index 7ffaaaefe..000000000 --- a/lib/routes/gov/samr/templates/description.art +++ /dev/null @@ -1,26 +0,0 @@ -{{ if item }} -
    - - - {{ item.lyr }} - -
    -

    {{ item.lybt }}

    -

    留言日期:{{ item.lysj }}

    -
    - {{ item.lynr }} -
    -
    -
    - - - {{ item.fzsjCn }} - -
    -

    时间:{{ item.pubtime }}

    -
    - {{ item.clyj }} -
    -
    -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/gov/samr/xgzlyhd.ts b/lib/routes/gov/samr/xgzlyhd.tsx similarity index 91% rename from lib/routes/gov/samr/xgzlyhd.ts rename to lib/routes/gov/samr/xgzlyhd.tsx index 70ceb4349..63f24b74f 100644 --- a/lib/routes/gov/samr/xgzlyhd.ts +++ b/lib/routes/gov/samr/xgzlyhd.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://xgzlyhd.samr.gov.cn'; const apiUrl = new URL('gjjly/message/getMessageList', rootUrl).href; @@ -186,9 +184,30 @@ async function handler(ctx) { const items = response.data.data.slice(0, limit).map((item) => ({ title: item.lybt, link: `${currentUrl}#${item.zj}`, - description: art(path.join(__dirname, 'templates/description.art'), { - item, - }), + description: renderToString( + item ? ( +
    + + + {item.lyr} + +
    +

    {item.lybt}

    +

    留言日期:{item.lysj}

    +
    {item.lynr}
    +
    +
    + + + {item.fzsjCn} + +
    +

    时间:{item.pubtime}

    +
    {item.clyj}
    +
    +
    + ) : null + ), author: `${item.lyr} ⇄ ${item.fzsjCn}`, category: [item.fzsjCn], guid: `${currentUrl}#${item.zj}`, diff --git a/lib/routes/gov/sh/fgw/index.ts b/lib/routes/gov/sh/fgw/index.tsx similarity index 89% rename from lib/routes/gov/sh/fgw/index.ts rename to lib/routes/gov/sh/fgw/index.tsx index 93e0c4966..44d983fd5 100644 --- a/lib/routes/gov/sh/fgw/index.ts +++ b/lib/routes/gov/sh/fgw/index.tsx @@ -1,14 +1,28 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +const renderDescription = ({ images, description }) => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {description ? <>{raw(description)} : null} + + ); export const handler = async (ctx) => { const { category = 'fgw_zxxxgk' } = ctx.req.param(); const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 20; @@ -53,7 +67,7 @@ export const handler = async (ctx) => { const title = $$('meta[name="ArticleTitle"]').prop('content'); const image = $$('div.pdf-content img').first().prop('src'); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { diff --git a/lib/routes/gov/sh/fgw/templates/description.art b/lib/routes/gov/sh/fgw/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/gov/sh/fgw/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/gov/sh/rsj/ksxm.ts b/lib/routes/gov/sh/rsj/ksxm.tsx similarity index 77% rename from lib/routes/gov/sh/rsj/ksxm.ts rename to lib/routes/gov/sh/rsj/ksxm.tsx index 2ed259465..cef610fa7 100644 --- a/lib/routes/gov/sh/rsj/ksxm.ts +++ b/lib/routes/gov/sh/rsj/ksxm.tsx @@ -1,14 +1,25 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const rootUrl = 'http://www.rsj.sh.gov.cn'; +const renderDescription = ({ name, type, date, registrationDeadline }) => + renderToString( + <> + 考试项目名称:{name} +
    + 考试类别:{type} +
    + 考试日期:{date} +
    + 报名起止日期:{registrationDeadline} + + ); + export const route: Route = { path: ['/sh/rsj/ksxm', '/shanghai/rsj/ksxm'], categories: ['government'], @@ -49,7 +60,7 @@ async function handler() { .map((item) => ({ title: $(item).find('kaosxmmc').text(), link: `http://www.rsj.sh.gov.cn/ksyzc/index801.jsp`, - description: art(path.join(__dirname, './templates/ksxm.art'), { + description: renderDescription({ name: $(item).find('kaosxmmc').text(), type: $(item).find('kaoslb_dmfy').text(), date: $(item).find('kaosrq').text(), diff --git a/lib/routes/gov/sh/rsj/templates/ksxm.art b/lib/routes/gov/sh/rsj/templates/ksxm.art deleted file mode 100644 index b6f06dc36..000000000 --- a/lib/routes/gov/sh/rsj/templates/ksxm.art +++ /dev/null @@ -1,7 +0,0 @@ -考试项目名称:{{ name }} -
    -考试类别:{{ type }} -
    -考试日期:{{ date }} -
    -报名起止日期:{{ registrationDeadline }} diff --git a/lib/routes/gov/sh/wgj/templates/wgj.art b/lib/routes/gov/sh/wgj/templates/wgj.art deleted file mode 100644 index 6ce7b6063..000000000 --- a/lib/routes/gov/sh/wgj/templates/wgj.art +++ /dev/null @@ -1,15 +0,0 @@ -举办单位:{{ hostingUnit }} -
    -许可证号:{{ licenseNumber }} -
    -演出名称:{{ performanceName }} -
    -演出日期:{{ performanceDate }} -
    -演出场所:{{ performanceVenue }} -
    -主要演员:{{ mainActors }} -
    -演员人数:{{ actorCount }} -
    -场次:{{ showCount }} diff --git a/lib/routes/gov/sh/wgj/wgj.ts b/lib/routes/gov/sh/wgj/wgj.tsx similarity index 76% rename from lib/routes/gov/sh/wgj/wgj.ts rename to lib/routes/gov/sh/wgj/wgj.tsx index c98257643..7309eea2a 100644 --- a/lib/routes/gov/sh/wgj/wgj.ts +++ b/lib/routes/gov/sh/wgj/wgj.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: ['/sh/wgj/:page?', '/shanghai/wgj/:page?'], @@ -76,16 +74,25 @@ async function handler(ctx) { const actorCount = $('td:contains("演员人数:")').next().text().trim(); const showCount = $('td:contains("场次:")').next().text().trim(); - item.description = art(path.join(__dirname, './templates/wgj.art'), { - hostingUnit, - licenseNumber, - performanceName, - performanceDate, - performanceVenue, - mainActors, - actorCount, - showCount, - }); + item.description = renderToString( + <> + 举办单位:{hostingUnit} +
    + 许可证号:{licenseNumber} +
    + 演出名称:{performanceName} +
    + 演出日期:{performanceDate} +
    + 演出场所:{performanceVenue} +
    + 主要演员:{mainActors} +
    + 演员人数:{actorCount} +
    + 场次:{showCount} + + ); item.pubDate = parseDate(dateText); return item; diff --git a/lib/routes/gov/sichuan/deyang/govpublicinfo.ts b/lib/routes/gov/sichuan/deyang/govpublicinfo.tsx similarity index 64% rename from lib/routes/gov/sichuan/deyang/govpublicinfo.ts rename to lib/routes/gov/sichuan/deyang/govpublicinfo.tsx index 8d968c058..89ed1f37c 100644 --- a/lib/routes/gov/sichuan/deyang/govpublicinfo.ts +++ b/lib/routes/gov/sichuan/deyang/govpublicinfo.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; // 各地区url信息 @@ -108,7 +107,55 @@ async function handler(ctx) { link: infoBasicUrl, item: items.map((item) => ({ title: item.title, - description: art(path.join(__dirname, './templates/govPublicInfo.art'), { item }), + description: renderToString( + item._isCompleteInfo ? ( + <> + + + + + + + + + + + + + + + + + + + + + + + {item.file?.length ? ( + + + + + ) : null} + +
    索引号{item.id}
    文号{item.infoNum}
    发文日期{item.date}
    关键词{item.keyWord}
    信息来源{item.source}
    附件 + {item.file.map((file) => ( + <> + {file.name} +
    + + ))} +
    +
    +
    {item.content ? raw(item.content) : null}
    + + ) : ( + + ) + ), link: item.link, pubDate: item.pubDate, })), diff --git a/lib/routes/gov/sichuan/deyang/mztoday.ts b/lib/routes/gov/sichuan/deyang/mztoday.tsx similarity index 96% rename from lib/routes/gov/sichuan/deyang/mztoday.ts rename to lib/routes/gov/sichuan/deyang/mztoday.tsx index 0fa37d6c3..c37ceb788 100644 --- a/lib/routes/gov/sichuan/deyang/mztoday.ts +++ b/lib/routes/gov/sichuan/deyang/mztoday.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const rootUrl = 'http://www.mztoday.gov.cn'; @@ -158,7 +157,7 @@ async function handler(ctx) { link: `${infoBasicUrl}1`, item: items.map((item) => ({ title: item.title, - description: art(path.join(__dirname, './templates/mztoday.art'), { item }), + description: renderToString(
    {item.content ? raw(item.content) : null}
    ), link: item.link, pubDate: item.pubDate, })), diff --git a/lib/routes/gov/sichuan/deyang/templates/govPublicInfo.art b/lib/routes/gov/sichuan/deyang/templates/govPublicInfo.art deleted file mode 100644 index 2df8c9b38..000000000 --- a/lib/routes/gov/sichuan/deyang/templates/govPublicInfo.art +++ /dev/null @@ -1,47 +0,0 @@ -{{if item._isCompleteInfo}} - - - - - - - - - - - - - - - - - - - - - - - {{if item.file.length}} - - - - - {{/if}} - -
    索引号{{item.id}}
    文号{{item.infoNum}}
    发文日期{{item.date}}
    关键词{{item.keyWord}}
    信息来源{{item.source}}
    附件 - {{each item.file}} - {{$value.name}}
    - {{/each}} -
    - -
    -
    - {{@ item.content }} -
    -{{else}} - -{{/if}} - - diff --git a/lib/routes/gov/sichuan/deyang/templates/mztoday.art b/lib/routes/gov/sichuan/deyang/templates/mztoday.art deleted file mode 100644 index 9fd19cf2d..000000000 --- a/lib/routes/gov/sichuan/deyang/templates/mztoday.art +++ /dev/null @@ -1,6 +0,0 @@ -
    - {{@ item.content}} -
    - - - diff --git a/lib/routes/gov/stats/index.ts b/lib/routes/gov/stats/index.tsx similarity index 83% rename from lib/routes/gov/stats/index.ts rename to lib/routes/gov/stats/index.tsx index 2036997d7..c7d31461d 100644 --- a/lib/routes/gov/stats/index.ts +++ b/lib/routes/gov/stats/index.tsx @@ -1,15 +1,44 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +type Attachment = { + link: string; + name: string; +}; + +type DescriptionProps = { + description?: string; + attachments?: Attachment[]; +}; + +const renderDescription = ({ description, attachments }: DescriptionProps): string => + renderToString( + <> + {description ? raw(description) : null} + {attachments?.length ? ( + <> +
    +

    附件:

    + + + ) : null} + + ); + export const route: Route = { path: '/stats/*', name: '国家统计局 通用', @@ -113,7 +142,7 @@ async function handler(ctx) { item.title = item.title || content('div.detail-title h1').text(); item.pubDate = timezone(parseDate(content('div.detail-title-des h2 p, .info').first().text().trim()), +8); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ description: content('.TRS_Editor').html() || content('.TRS_UEDITOR').html(), attachments: content('a[oldsrc]') .toArray() diff --git a/lib/routes/gov/stats/templates/description.art b/lib/routes/gov/stats/templates/description.art deleted file mode 100644 index 08297feaf..000000000 --- a/lib/routes/gov/stats/templates/description.art +++ /dev/null @@ -1,12 +0,0 @@ -{{@ description }} -{{ if attachments }} -
    -

    附件:

    - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/guduodata/daily.ts b/lib/routes/guduodata/daily.tsx similarity index 69% rename from lib/routes/guduodata/daily.ts rename to lib/routes/guduodata/daily.tsx index ca717ed26..dfa3574bb 100644 --- a/lib/routes/guduodata/daily.ts +++ b/lib/routes/guduodata/daily.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import dayjs from 'dayjs'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const host = 'http://d.guduodata.com'; @@ -58,7 +56,7 @@ async function handler() { const now = dayjs().valueOf(); // yestoday const yestoday = dayjs().subtract(1, 'day').format('YYYY-MM-DD'); - const renderRows = (rows) => art(path.join(__dirname, './templates/daily.art'), { rows }); + const renderRows = (rows) => renderToString(); const items = Object.keys(types).flatMap((key) => Object.keys(types[key].categories).map((category) => ({ type: key, @@ -89,3 +87,32 @@ async function handler() { ), }; } + +const GuduodataDailyTable = ({ rows }: { rows: any[] }) => ( + + + + + + + + + + + + + {rows.map((row, index) => ( + + + + + + + + + + + ))} + +
    排名剧名播放平台上映时间评论数百度指数豆瓣评分全网热度
    {index + 1}{row.name}{row.platforms}{row.release_date}{row.comment || ''}{row.baidu_index || ''}{row.douban || ''}{row.gdi}
    +); diff --git a/lib/routes/guduodata/templates/daily.art b/lib/routes/guduodata/templates/daily.art deleted file mode 100644 index 0c77f57a3..000000000 --- a/lib/routes/guduodata/templates/daily.art +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - {{each rows}} - - - - - - - - - - - {{/each}} - -
    排名剧名播放平台上映时间评论数百度指数豆瓣评分全网热度
    {{$index + 1}}{{$value.name}}{{$value.platforms}}{{$value.release_date}}{{$value.comment || ''}}{{$value.baidu_index || ''}}{{$value.douban || ''}}{{$value.gdi}}
    \ No newline at end of file diff --git a/lib/routes/gumroad/index.ts b/lib/routes/gumroad/index.tsx similarity index 67% rename from lib/routes/gumroad/index.ts rename to lib/routes/gumroad/index.tsx index 1f348988c..4fa10c06b 100644 --- a/lib/routes/gumroad/index.ts +++ b/lib/routes/gumroad/index.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import { isValidHost } from '@/utils/valid-host'; export const route: Route = { @@ -27,6 +26,18 @@ export const route: Route = { description: `\`https://afkmaster.gumroad.com/l/Eve10\` -> \`/gumroad/afkmaster/Eve10\``, }; +const renderDescription = (img, productsName, price, desc, stack) => + renderToString( + <> + +

    {productsName}

    +

    {price}

    + {desc ? <>{raw(desc)} : null} +
    + {stack ? <>{raw(stack)} : null} + + ); + async function handler(ctx) { const username = ctx.req.param('username'); const products = ctx.req.param('products'); @@ -44,13 +55,13 @@ async function handler(ctx) { { title, link: url, - description: art(path.join(__dirname, 'templates/products.art'), { - img: response.data.match(/data-preview-url="(.*?)"/)[1], - productsName: title, - price: $('div.price').text(), - desc: $('section.product-content.product-content__row > section:nth-child(3) > div').html(), - stack: $('div.product-info').find('ul.stack').html(), - }), + description: renderDescription( + response.data.match(/data-preview-url="(.*?)"/)[1], + title, + $('div.price').text(), + $('section.product-content.product-content__row > section:nth-child(3) > div').html(), + $('div.product-info').find('ul.stack').html() + ), }, ]; diff --git a/lib/routes/gumroad/templates/products.art b/lib/routes/gumroad/templates/products.art deleted file mode 100644 index cece45638..000000000 --- a/lib/routes/gumroad/templates/products.art +++ /dev/null @@ -1,7 +0,0 @@ - -

    {{productsName}}

    -

    {{price}}

    -{{@ desc}} -
    -{{@ stack}} - diff --git a/lib/routes/gzdaily/app.ts b/lib/routes/gzdaily/app.tsx similarity index 87% rename from lib/routes/gzdaily/app.ts rename to lib/routes/gzdaily/app.tsx index f226afddb..372c35d76 100644 --- a/lib/routes/gzdaily/app.ts +++ b/lib/routes/gzdaily/app.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -50,9 +48,16 @@ async function handler(ctx) { .filter((i) => i.newstype === 0) // Remove special report (专题) and articles from Guangzhou Converged Media Center (新花城). .map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/description.art'), { - thumb: item.picBig, - }), + description: renderToString( + <> + {item.picBig ? ( + <> + +
    + + ) : null} + + ), pubDate: timezone(parseDate(item.publishtime), +8), link: item.shareUrl, colName: item.colName, diff --git a/lib/routes/gzdaily/templates/description.art b/lib/routes/gzdaily/templates/description.art deleted file mode 100644 index 2113e7063..000000000 --- a/lib/routes/gzdaily/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ if thumb }} -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/hafu/templates/hafu.art b/lib/routes/hafu/templates/hafu.art deleted file mode 100644 index 5231e64c4..000000000 --- a/lib/routes/hafu/templates/hafu.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ if articleBody }} - {{@ articleBody }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/hafu/utils.ts b/lib/routes/hafu/utils.tsx similarity index 97% rename from lib/routes/hafu/utils.ts rename to lib/routes/hafu/utils.tsx index 75827d57a..77911f1e0 100644 --- a/lib/routes/hafu/utils.ts +++ b/lib/routes/hafu/utils.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const typeMap = { @@ -48,7 +47,7 @@ async function tryGetFullText(href, link, type) { articleBody = tryGetAttachments(articleData, articleBody, type); } - description = art(path.join(__dirname, 'templates/hafu.art'), articleBody)(); + description = articleBody ? renderToString(<>{raw(articleBody)}) : ''; } catch { description = href; } diff --git a/lib/routes/hashnode/blog.ts b/lib/routes/hashnode/blog.tsx similarity index 86% rename from lib/routes/hashnode/blog.ts rename to lib/routes/hashnode/blog.tsx index dde13dfe4..75ad8c51d 100644 --- a/lib/routes/hashnode/blog.ts +++ b/lib/routes/hashnode/blog.tsx @@ -1,11 +1,18 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseApiUrl = 'https://api.hashnode.com'; +const renderDescription = (image, brief) => + renderToString( + <> + + {brief ? <>{raw(brief)} : null} + + ); export const route: Route = { path: '/blog/:username', @@ -79,10 +86,7 @@ async function handler(ctx) { item: list .map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.coverImage, - brief: item.brief, - }), + description: renderDescription(item.coverImage, item.brief), pubDate: parseDate(item.dateAdded), link: `${userUrl}/${item.slug}`, })) diff --git a/lib/routes/hashnode/templates/description.art b/lib/routes/hashnode/templates/description.art deleted file mode 100644 index ad6ebf467..000000000 --- a/lib/routes/hashnode/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ - -{{@ brief }} diff --git a/lib/routes/hebtv/nong-bo-shi-zai-xing-dong.ts b/lib/routes/hebtv/nong-bo-shi-zai-xing-dong.tsx similarity index 85% rename from lib/routes/hebtv/nong-bo-shi-zai-xing-dong.ts rename to lib/routes/hebtv/nong-bo-shi-zai-xing-dong.tsx index 2f46b76f2..ad3cc8fcc 100644 --- a/lib/routes/hebtv/nong-bo-shi-zai-xing-dong.ts +++ b/lib/routes/hebtv/nong-bo-shi-zai-xing-dong.tsx @@ -1,16 +1,33 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const baseUrl = 'https://web.cmc.hebtv.com/cms/rmt0336/19/19js/st/ds/nmpd/nbszxd/index.shtml'; +const renderDescription = (image, video) => + renderToString( + <> + {image?.src ? ( +
    + {image.alt} +
    + ) : null} + {video?.src ? ( + + ) : null} + + ); + export const route: Route = { path: '/nbszxd', categories: ['traditional-media'], @@ -100,15 +117,16 @@ async function handler(ctx) { item.enclosure_type = item.enclosure_url ? `video/${item.enclosure_url?.split(/\./)?.pop()}` : undefined; } - item.description = art(path.join(__dirname, 'templates/description.art'), { - video: videoData + item.description = renderDescription( + undefined, + videoData ? { src: item.enclosure_url, type: item.enclosure_type, poster: item.itunes_item_image, } - : undefined, - }); + : undefined + ); return item; }) diff --git a/lib/routes/hebtv/templates/description.art b/lib/routes/hebtv/templates/description.art deleted file mode 100644 index 7acb0d018..000000000 --- a/lib/routes/hebtv/templates/description.art +++ /dev/null @@ -1,24 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if video?.src }} - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/hellogithub/report.ts b/lib/routes/hellogithub/report.ts deleted file mode 100644 index 81a886f17..000000000 --- a/lib/routes/hellogithub/report.ts +++ /dev/null @@ -1,69 +0,0 @@ -import path from 'node:path'; - -import type { Route } from '@/types'; -import got from '@/utils/got'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -const types = { - tiobe: '编程语言', - netcraft: '服务器', - 'db-engines': '数据库', -}; - -export const route: Route = { - path: '/ranking/:type?', - example: '/hellogithub/ranking', - name: '榜单报告', - maintainers: ['moke8', 'nczitzk'], - handler, - description: `| 编程语言 | 服务器 | 数据库 | -| -------- | -------- | ---------- | -| tiobe | netcraft | db-engines |`, -}; - -async function handler(ctx) { - let type = ctx.req.param('type') ?? 'tiobe'; - - type = type === 'webserver' ? 'netcraft' : type === 'db' ? 'db-engines' : type; - - const rootUrl = 'https://hellogithub.com'; - const currentUrl = `${rootUrl}/report/${type}`; - - const buildResponse = await got({ - method: 'get', - url: rootUrl, - }); - - const buildId = buildResponse.data.match(/"buildId":"(.*?)",/)[1]; - - const apiUrl = `${rootUrl}/_next/data/${buildId}/zh/report/${type}.json`; - - const response = await got({ - method: 'get', - url: apiUrl, - }); - - const data = response.data.pageProps; - - const items = [ - { - guid: `${type}:${data.year}${data.month}`, - title: `${data.year}年${data.month}月${types[type]}排行榜`, - link: currentUrl, - pubDate: parseDate(`${data.year}-${data.month}`, 'YYYY-M'), - description: art(path.join(__dirname, 'templates/report.art'), { - tiobe_list: type === 'tiobe' ? data.list : undefined, - active_list: data.active_list, - all_list: data.all_list, - db_list: type === 'db-engines' ? data.list : undefined, - }), - }, - ]; - - return { - title: `HelloGitHub - ${types[type]}排行榜`, - link: currentUrl, - item: items, - }; -} diff --git a/lib/routes/hellogithub/report.tsx b/lib/routes/hellogithub/report.tsx new file mode 100644 index 000000000..4e2d36f09 --- /dev/null +++ b/lib/routes/hellogithub/report.tsx @@ -0,0 +1,179 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +type ReportListItem = { + position: string | number; + name: string; + rating: string | number; + change?: string | number; + star?: string | number; + total?: string | number; + db_model?: string; +}; + +const renderReport = ({ tiobeList, activeList, allList, dbList }: { tiobeList?: ReportListItem[]; activeList?: ReportListItem[]; allList?: ReportListItem[]; dbList?: ReportListItem[] }) => + renderToString( + <> + {tiobeList?.length ? ( + + + + + + + + + + {tiobeList.map((item) => ( + + + + + + + + ))} + +
    排名编程语言流行度对比上月年度明星语言
    {item.position}{item.name}{item.rating}{item.change || '新上榜'}{item.star}
    + ) : null} + {allList?.length ? ( + <> +

    市场份额排名

    + + + + + + + + + + {allList.map((item) => ( + + + + + + + + ))} + +
    排名服务器占比对比上月总数
    {item.position}{item.name}{item.rating}{item.change || '新上榜'}{item.total}
    +
    + + ) : null} + {activeList?.length ? ( + <> +

    活跃网站排名

    + + + + + + + + + + {activeList.map((item) => ( + + + + + + + + ))} + +
    排名服务器占比对比上月总数
    {item.position}{item.name}{item.rating}{item.change || '新上榜'}{item.total}
    + + ) : null} + {dbList?.length ? ( + + + + + + + + + + {dbList.map((item) => ( + + + + + + + + ))} + +
    排名数据库分数对比上月类型
    {item.position}{item.name}{item.rating}{item.change || '新上榜'}{item.db_model}
    + ) : null} + + ); + +const types = { + tiobe: '编程语言', + netcraft: '服务器', + 'db-engines': '数据库', +}; + +export const route: Route = { + path: '/ranking/:type?', + example: '/hellogithub/ranking', + name: '榜单报告', + maintainers: ['moke8', 'nczitzk'], + handler, + description: `| 编程语言 | 服务器 | 数据库 | +| -------- | -------- | ---------- | +| tiobe | netcraft | db-engines |`, +}; + +async function handler(ctx) { + let type = ctx.req.param('type') ?? 'tiobe'; + + type = type === 'webserver' ? 'netcraft' : type === 'db' ? 'db-engines' : type; + + const rootUrl = 'https://hellogithub.com'; + const currentUrl = `${rootUrl}/report/${type}`; + + const buildResponse = await got({ + method: 'get', + url: rootUrl, + }); + + const buildId = buildResponse.data.match(/"buildId":"(.*?)",/)[1]; + + const apiUrl = `${rootUrl}/_next/data/${buildId}/zh/report/${type}.json`; + + const response = await got({ + method: 'get', + url: apiUrl, + }); + + const data = response.data.pageProps; + + const items = [ + { + guid: `${type}:${data.year}${data.month}`, + title: `${data.year}年${data.month}月${types[type]}排行榜`, + link: currentUrl, + pubDate: parseDate(`${data.year}-${data.month}`, 'YYYY-M'), + description: renderReport({ + tiobeList: type === 'tiobe' ? data.list : undefined, + activeList: data.active_list, + allList: data.all_list, + dbList: type === 'db-engines' ? data.list : undefined, + }), + }, + ]; + + return { + title: `HelloGitHub - ${types[type]}排行榜`, + link: currentUrl, + item: items, + }; +} diff --git a/lib/routes/hellogithub/templates/description.art b/lib/routes/hellogithub/templates/description.art deleted file mode 100644 index 3aac76caf..000000000 --- a/lib/routes/hellogithub/templates/description.art +++ /dev/null @@ -1,99 +0,0 @@ -{{ if image }} -
    - -
    -{{ /if }} - - - {{ if homepage }} - - - - - {{ /if }} - {{ if name && url }} - - - - - {{ /if }} - {{ if description }} - - - - - {{ /if }} - {{ if summary }} - - - - - {{ /if }} - {{ if stars }} - - - - - {{ /if }} - {{ if forks }} - - - - - {{ /if }} - {{ if subscribers }} - - - - - {{ /if }} - {{ if language }} - - - - - {{ /if }} - {{ if license }} - - - - - {{ /if }} - - - - - - - - - - - - - {{ if openIssues }} - - - - - {{ /if }} - -
    Homepage{{ homepage }}
    GitHub Repo{{ name }}
    Description{{ description }}
    Summary{{ summary }}
    Stars{{ stars }}
    Forks{{ forks }}
    Subscribers{{ subscribers }}
    Language{{ language }}
    License{{ license }}
    Is in Chinese - {{ if isChinese }} - Yes - {{ else }} - No - {{ /if }} -
    Is Organization - {{ if isOrganization }} - Yes - {{ else }} - No - {{ /if }} -
    Is Active - {{ if isActive }} - Yes - {{ else }} - No - {{ /if }} -
    Open Issues{{ openIssues }}
    \ No newline at end of file diff --git a/lib/routes/hellogithub/templates/report.art b/lib/routes/hellogithub/templates/report.art deleted file mode 100644 index 2ce6e4553..000000000 --- a/lib/routes/hellogithub/templates/report.art +++ /dev/null @@ -1,118 +0,0 @@ -{{ if tiobe_list }} - - - - - - - - - - {{ each tiobe_list l }} - - - - - - - - {{ /each }} - -
    排名编程语言流行度对比上月年度明星语言
    {{ l.position }}{{ l.name }}{{ l.rating }} - {{ if l.change }} - {{ l.change }} - {{ else }} - 新上榜 - {{ /if }} - {{ l.star }}
    -{{ /if }} - -{{ if all_list }} -

    市场份额排名

    - - - - - - - - - - {{ each all_list l }} - - - - - - - - {{ /each }} - -
    排名服务器占比对比上月总数
    {{ l.position }}{{ l.name }}{{ l.rating }} - {{ if l.change }} - {{ l.change }} - {{ else }} - 新上榜 - {{ /if }} - {{ l.total }}
    -
    -{{ /if }} - -{{ if active_list }} -

    活跃网站排名

    - - - - - - - - - - {{ each active_list l }} - - - - - - - - {{ /each }} - -
    排名服务器占比对比上月总数
    {{ l.position }}{{ l.name }}{{ l.rating }} - {{ if l.change }} - {{ l.change }} - {{ else }} - 新上榜 - {{ /if }} - {{ l.total }}
    -{{ /if }} - -{{ if db_list }} - - - - - - - - - - {{ each db_list l }} - - - - - - - - {{ /each }} - -
    排名数据库分数对比上月类型
    {{ l.position }}{{ l.name }}{{ l.rating }} - {{ if l.change }} - {{ l.change }} - {{ else }} - 新上榜 - {{ /if }} - {{ l.db_model }}
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/hellogithub/templates/volume.art b/lib/routes/hellogithub/templates/volume.art deleted file mode 100644 index 09423133d..000000000 --- a/lib/routes/hellogithub/templates/volume.art +++ /dev/null @@ -1,36 +0,0 @@ -{{ if data }} -{{ each data d }} -
    -

    {{ d.category_name }}

    - {{ each d.items item }} -
    -

    - {{ item.name }} -

    - - - - - - - - - - - - - - - -
    Stars{{ item.stars }}
    Forks{{ item.forks }}
    Watch{{ item.watch }}
    -

    {{@ item.description | render }}

    - {{ if item.image_url }} -
    - -
    - {{ /if }} -
    - {{ /each }} -
    -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/hellogithub/volume.ts b/lib/routes/hellogithub/volume.tsx similarity index 55% rename from lib/routes/hellogithub/volume.ts rename to lib/routes/hellogithub/volume.tsx index 892577a36..4fe4a13a6 100644 --- a/lib/routes/hellogithub/volume.ts +++ b/lib/routes/hellogithub/volume.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import MarkdownIt from 'markdown-it'; import { config } from '@/config'; @@ -8,15 +8,50 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const md = MarkdownIt({ html: true, }); -art.defaults.imports.render = function (string) { - return md.render(string); -}; +const renderVolume = (data) => + renderToString( + <> + {data?.map((category) => ( +
    +

    {category.category_name}

    + {category.items?.map((item) => ( +
    +

    + {item.name} +

    + + + + + + + + + + + + + + + +
    Stars{item.stars}
    Forks{item.forks}
    Watch{item.watch}
    +

    {item.description ? raw(md.render(item.description)) : null}

    + {item.image_url ? ( +
    + +
    + ) : null} +
    + ))} +
    + ))} + + ); export const route: Route = { path: '/volume', @@ -60,9 +95,7 @@ async function handler(ctx) { return { title: `《HelloGitHub》第 ${id} 期`, link: `${rootUrl}/periodical/volume/${id}`, - description: art(path.join(__dirname, 'templates/volume.art'), { - data: data.pageProps.volume.data, - }), + description: renderVolume(data.pageProps.volume.data), pubDate: parseDate(lastmod), }; }, diff --git a/lib/routes/hiring.cafe/jobs.ts b/lib/routes/hiring.cafe/jobs.tsx similarity index 71% rename from lib/routes/hiring.cafe/jobs.ts rename to lib/routes/hiring.cafe/jobs.tsx index 518da16fb..f7c0d5bdd 100644 --- a/lib/routes/hiring.cafe/jobs.ts +++ b/lib/routes/hiring.cafe/jobs.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; - import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; const CONFIG = { DEFAULT_PAGE_SIZE: 20, @@ -84,17 +83,40 @@ const fetchJobs = async (searchParams: SearchParams): Promise => { }); }; -const renderJobDescription = (jobInfo: JobInformation, processedData: ProcessedJobData): string => - art(path.join(__dirname, 'templates/jobs.art'), { - company_name: processedData.company_name, - location: processedData.formatted_workplace_location ?? 'Remote/Unspecified', - is_compensation_transparent: Boolean(processedData.is_compensation_transparent && processedData.yearly_min_compensation && processedData.yearly_max_compensation), - yearly_min_compensation_formatted: processedData.yearly_min_compensation?.toLocaleString() ?? '', - yearly_max_compensation_formatted: processedData.yearly_max_compensation?.toLocaleString() ?? '', - workplace_type: processedData.workplace_type ?? 'Not specified', - requirements_summary: processedData.requirements_summary ?? 'No requirements specified', - job_description: jobInfo.description ?? '', - }); +const renderJobDescription = (jobInfo: JobInformation, processedData: ProcessedJobData): string => { + const isCompensationTransparent = Boolean(processedData.is_compensation_transparent && processedData.yearly_min_compensation && processedData.yearly_max_compensation); + const companyInfoDescription = (jobInfo as { company_info_description?: string }).company_info_description; + const hasCompanyInfo = Boolean(companyInfoDescription); + + return renderToString( + <> +

    + Company: {processedData.company_name} +

    +

    + Location: {processedData.formatted_workplace_location ?? 'Remote/Unspecified'} +

    + {isCompensationTransparent ? ( +

    + Compensation: ${processedData.yearly_min_compensation?.toLocaleString()} - ${processedData.yearly_max_compensation?.toLocaleString()} per year +

    + ) : null} +

    + Workplace Type: {processedData.workplace_type ?? 'Not specified'} +

    +

    + Requirements: {processedData.requirements_summary ?? 'No requirements specified'} +

    +
    {jobInfo.description ? raw(jobInfo.description) : null}
    + {hasCompanyInfo ? ( + <> +

    About {processedData.company_name}

    + {raw(companyInfoDescription as string)} + + ) : null} + + ); +}; const transformJobItem = (item: JobResult) => { const { job_information: jobInfo, v5_processed_job_data: processedData, apply_url, id } = item; diff --git a/lib/routes/hiring.cafe/templates/jobs.art b/lib/routes/hiring.cafe/templates/jobs.art deleted file mode 100644 index bdc770463..000000000 --- a/lib/routes/hiring.cafe/templates/jobs.art +++ /dev/null @@ -1,18 +0,0 @@ -

    Company: {{ company_name }}

    -

    Location: {{ location }}

    - -{{if is_compensation_transparent}} -

    Compensation: ${{ yearly_min_compensation_formatted }} - ${{ yearly_max_compensation_formatted }} per year

    -{{/if}} - -

    Workplace Type: {{ workplace_type }}

    -

    Requirements: {{ requirements_summary }}

    - -
    - {{@ job_description }} -
    - -{{if has_company_info}} -

    About {{ company_name }}

    -{{@ company_info_description }} -{{/if}} diff --git a/lib/routes/hit/hitgs.ts b/lib/routes/hit/hitgs.ts index 19c91c286..430ee5f0a 100644 --- a/lib/routes/hit/hitgs.ts +++ b/lib/routes/hit/hitgs.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { id = 'tzgg' } = ctx.req.param(); @@ -32,7 +31,7 @@ export const handler = async (ctx: Context): Promise => { const $el: Cheerio = $(el); const title: string = $el.find('div.news_title, span.div.news_title, div.bttb2').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ intro: $el.find('div.news_text, div.jj5').text(), }); const pubDateStr: string | undefined = $('span.news_meta').text() || ($('span.news_days').text() ? `${$('span.news_days').text()}-${$('span.news_year').text()}` : `${$('div.tm-3').text()}-${$('div.tm-1').text()}`); @@ -66,7 +65,7 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('h1.arti_title').text() + $$('h2.arti_title').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ description: $$('div.wp_articlecontent').html(), }); const pubDateStr: string | undefined = $$('span.arti_update').text().split(/:/).pop()?.trim(); diff --git a/lib/routes/hit/templates/description.art b/lib/routes/hit/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/hit/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/hit/templates/description.tsx b/lib/routes/hit/templates/description.tsx new file mode 100644 index 000000000..a59dd9053 --- /dev/null +++ b/lib/routes/hit/templates/description.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + intro?: string; + description?: string; +}; + +const HitDescription = ({ intro, description }: DescriptionData) => ( + <> + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/hitcon/templates/zeroday.art b/lib/routes/hitcon/templates/zeroday.art deleted file mode 100644 index f051ea4fa..000000000 --- a/lib/routes/hitcon/templates/zeroday.art +++ /dev/null @@ -1,8 +0,0 @@ -
      -
    • {{ vender }}
    • -
    • ZDID: {{ code }}
    • -
    • 風險: {{ risk }}
    • -
    • 處理狀態: {{ status }}
    • -
    • 通報者: {{ reporter }}
    • -
    • 通報日期: {{ date }}
    • -
    diff --git a/lib/routes/hitcon/zeroday.ts b/lib/routes/hitcon/zeroday.tsx similarity index 87% rename from lib/routes/hitcon/zeroday.ts rename to lib/routes/hitcon/zeroday.tsx index f89505e8f..a64d34174 100644 --- a/lib/routes/hitcon/zeroday.ts +++ b/lib/routes/hitcon/zeroday.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import logger from '@/utils/logger'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; -import { art } from '@/utils/render'; export const route: Route = { name: '漏洞', @@ -82,14 +80,16 @@ async function handler(ctx: Context): Promise { const status = vulData.find('.status').text().replace('Status:', '').trim(); const date = vulData.find('.date').text().replace('Date:', '').trim(); const reporter = vulData.find('.zdui-author-badge').find('a>span').text(); - const description = art(path.join(__dirname, 'templates/zeroday.art'), { - code, - risk, - vender, - status, - date, - reporter, - }); + const description = renderToString( +
      +
    • {vender}
    • +
    • ZDID: {code}
    • +
    • 風險: {risk}
    • +
    • 處理狀態: {status}
    • +
    • 通報者: {reporter}
    • +
    • 通報日期: {date}
    • +
    + ); return { title: title.text(), diff --git a/lib/routes/hk01/templates/description.art b/lib/routes/hk01/templates/description.art deleted file mode 100644 index 8caf17bee..000000000 --- a/lib/routes/hk01/templates/description.art +++ /dev/null @@ -1,53 +0,0 @@ -{{ if image }} - -{{ /if }} -{{ if teasers }} - - {{ each teasers teaser }} -

    {{ teaser }}

    - {{ /each }} -
    -{{ /if }} -{{ each blocks block }} - {{ if block.blockType === 'summary' }} - - {{ set summaries = block.summary }} - {{ each summaries summary }} -

    {{ summary }}

    - {{ /each }} -
    - {{ else if block.blockType === 'text' }} - {{ set htmlTokens = block.htmlTokens }} - {{ each htmlTokens tokens }} - {{ each tokens token }} - {{ if token.type === 'text' }} -

    {{ token.content }}

    - {{ else if token.type === 'link' }} - {{ token.content }} - {{ else if token.type === 'boldText' }} - {{ token.content }} - {{ else if token.type === 'boldLink' }} - {{ token.content }} - {{ /if }} - {{ /each }} - {{ /each }} - {{ else if block.blockType === 'quote' }} - {{ set message = block.message }} - {{ set author = block.author }} - {{ message }} —— {{ author }} - {{ else if block.blockType === 'image' }} - {{ set image = block.image }} -
    - {{ image.caption }} - {{ image.caption }} -
    - {{ else if block.blockType === 'gallery' }} - {{ set images = block.images }} - {{ each images image }} -
    - {{ image.caption }} - {{ image.caption }} -
    - {{ /each }} - {{ /if }} -{{ /each }} \ No newline at end of file diff --git a/lib/routes/hk01/utils.ts b/lib/routes/hk01/utils.ts deleted file mode 100644 index 0e47008d4..000000000 --- a/lib/routes/hk01/utils.ts +++ /dev/null @@ -1,42 +0,0 @@ -import path from 'node:path'; - -import got from '@/utils/got'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -const rootUrl = 'https://hk01.com'; -const apiRootUrl = 'https://web-data.api.hk01.com'; - -const ProcessItems = (items, limit, tryGet) => - Promise.all( - items - .filter((item) => item.type !== 2) - .slice(0, limit ? Number.parseInt(limit) : 50) - .map((item) => ({ - title: item.data.title, - link: `${rootUrl}/sns/article/${item.data.articleId}`, - pubDate: parseDate(item.data.publishTime * 1000), - category: item.data.tags.map((t) => t.tagName), - author: item.data.authors.map((a) => a.publishName).join(', '), - })) - .map((item) => - tryGet(item.link, async () => { - const detailResponse = await got({ - method: 'get', - url: item.link, - }); - - const content = JSON.parse(detailResponse.data.match(/"__NEXT_DATA__" type="application\/json">({"props":.*})<\/script>/)[1]); - - item.description = art(path.join(__dirname, 'templates/description.art'), { - image: content.props.initialProps.pageProps.article.originalImage.cdnUrl, - teasers: content.props.initialProps.pageProps.article.teaser, - blocks: content.props.initialProps.pageProps.article.blocks, - }); - - return item; - }) - ) - ); - -export { apiRootUrl, ProcessItems, rootUrl }; diff --git a/lib/routes/hk01/utils.tsx b/lib/routes/hk01/utils.tsx new file mode 100644 index 000000000..863be3af2 --- /dev/null +++ b/lib/routes/hk01/utils.tsx @@ -0,0 +1,126 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +const rootUrl = 'https://hk01.com'; +const apiRootUrl = 'https://web-data.api.hk01.com'; + +const renderDescription = ({ image, teasers, blocks }) => + renderToString( + <> + {image ? : null} + {teasers?.length ? ( + + {teasers.map((teaser) => ( +

    {teaser}

    + ))} +
    + ) : null} + {blocks?.length + ? blocks.map((block) => { + if (block.blockType === 'summary') { + return ( + + {block.summary?.map((summary) => ( +

    {summary}

    + ))} +
    + ); + } + + if (block.blockType === 'text') { + return block.htmlTokens?.map((tokens) => + tokens.map((token) => { + if (token.type === 'text') { + return

    {token.content}

    ; + } + + if (token.type === 'link') { + return {token.content}; + } + + if (token.type === 'boldText') { + return {token.content}; + } + + if (token.type === 'boldLink') { + return ( + + {token.content} + + ); + } + + return null; + }) + ); + } + + if (block.blockType === 'quote') { + return ( + + {block.message} —— {block.author} + + ); + } + + if (block.blockType === 'image') { + const { image: blockImage } = block; + + return blockImage ? ( +
    + {blockImage.caption} + {blockImage.caption} +
    + ) : null; + } + + if (block.blockType === 'gallery') { + return block.images?.map((blockImage) => ( +
    + {blockImage.caption} + {blockImage.caption} +
    + )); + } + + return null; + }) + : null} + + ); + +const ProcessItems = (items, limit, tryGet) => + Promise.all( + items + .filter((item) => item.type !== 2) + .slice(0, limit ? Number.parseInt(limit) : 50) + .map((item) => ({ + title: item.data.title, + link: `${rootUrl}/sns/article/${item.data.articleId}`, + pubDate: parseDate(item.data.publishTime * 1000), + category: item.data.tags.map((t) => t.tagName), + author: item.data.authors.map((a) => a.publishName).join(', '), + })) + .map((item) => + tryGet(item.link, async () => { + const detailResponse = await got({ + method: 'get', + url: item.link, + }); + + const content = JSON.parse(detailResponse.data.match(/"__NEXT_DATA__" type="application\/json">({"props":.*})<\/script>/)[1]); + + item.description = renderDescription({ + image: content.props.initialProps.pageProps.article.originalImage.cdnUrl, + teasers: content.props.initialProps.pageProps.article.teaser, + blocks: content.props.initialProps.pageProps.article.blocks, + }); + + return item; + }) + ) + ); + +export { apiRootUrl, ProcessItems, rootUrl }; diff --git a/lib/routes/hkej/index.ts b/lib/routes/hkej/index.tsx similarity index 93% rename from lib/routes/hkej/index.ts rename to lib/routes/hkej/index.tsx index 1cf2c6fd7..eeeacd849 100644 --- a/lib/routes/hkej/index.ts +++ b/lib/routes/hkej/index.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { CookieJar } from 'tough-cookie'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate, parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const cookieJar = new CookieJar(); @@ -110,16 +109,18 @@ async function handler(ctx) { }; }); - const renderArticleImg = (pics) => - art(path.join(__dirname, 'templates/articleImg.art'), { - pics, - }); - const renderDesc = (pics, desc) => - art(path.join(__dirname, 'templates/description.art'), { - pics: renderArticleImg(pics), - desc, - }); + renderToString( + <> + {pics.map((pic) => ( +
    + {pic.title} +
    {pic.title}
    +
    + ))} + {raw(desc ?? '')} + + ); const items = await Promise.all( list && diff --git a/lib/routes/hkej/templates/articleImg.art b/lib/routes/hkej/templates/articleImg.art deleted file mode 100644 index 3be1bf3dd..000000000 --- a/lib/routes/hkej/templates/articleImg.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each pics }} -
    {{ $value.title }}
    {{ $value.title }}
    -{{ /each }} diff --git a/lib/routes/hkej/templates/description.art b/lib/routes/hkej/templates/description.art deleted file mode 100644 index 7a0e7673e..000000000 --- a/lib/routes/hkej/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{@ pics }} -{{@ desc }} diff --git a/lib/routes/hket/index.ts b/lib/routes/hket/index.tsx similarity index 96% rename from lib/routes/hket/index.ts rename to lib/routes/hket/index.tsx index f466fa41b..cf719bcdf 100644 --- a/lib/routes/hket/index.ts +++ b/lib/routes/hket/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const urlMap = { @@ -24,6 +22,14 @@ const urlMap = { }, }; +const renderImage = (alt, src) => + renderToString( +
    + {alt} +
    {alt}
    +
    + ); + export const route: Route = { path: '/:category?', categories: ['traditional-media'], @@ -203,12 +209,7 @@ async function handler(ctx) { // fix lazyload image and caption $('img').each((_, e) => { e = $(e); - e.replaceWith( - art(path.join(__dirname, 'templates/image.art'), { - alt: e.data('alt'), - src: e.data('src') ?? e.attr('src'), - }) - ); + e.replaceWith(renderImage(e.data('alt'), e.data('src') ?? e.attr('src'))); }); const ldJson = JSON.parse( diff --git a/lib/routes/hket/templates/image.art b/lib/routes/hket/templates/image.art deleted file mode 100644 index 6c32921bd..000000000 --- a/lib/routes/hket/templates/image.art +++ /dev/null @@ -1,4 +0,0 @@ -
    - {{ alt }} -
    {{ alt }}
    -
    diff --git a/lib/routes/hostmonit/cloudflareyes.ts b/lib/routes/hostmonit/cloudflareyes.tsx similarity index 73% rename from lib/routes/hostmonit/cloudflareyes.ts rename to lib/routes/hostmonit/cloudflareyes.tsx index 5815578b9..cb4086ab7 100644 --- a/lib/routes/hostmonit/cloudflareyes.ts +++ b/lib/routes/hostmonit/cloudflareyes.tsx @@ -1,13 +1,50 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +const renderTitle = ({ line, latency, loss, speed, node, ip }) => + renderToString( + <> + [{line} | {latency} | {loss} | {speed} | {node}] {ip} + + ); + +const renderDescription = ({ line, latency, loss, speed, node, ip }) => + renderToString( + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Line{line}
    Latency{latency}
    Loss{loss}
    Speed{speed}
    Node{node}
    IP{ip}
    + ); + const lines = { CM: '中国移动', CU: '中国联通', @@ -70,7 +107,7 @@ async function handler(ctx) { const pubDate = timezone(parseDate(item.time), +8); return { - title: art(path.join(__dirname, 'templates/title.art'), { + title: renderTitle({ line, latency, loss, @@ -79,7 +116,7 @@ async function handler(ctx) { ip, }), link: currentUrl, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ line, node, ip, diff --git a/lib/routes/hostmonit/templates/description.art b/lib/routes/hostmonit/templates/description.art deleted file mode 100644 index 52eddc7dd..000000000 --- a/lib/routes/hostmonit/templates/description.art +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Line{{ line }}
    Latency{{ latency }}
    Loss{{ loss }}
    Speed{{ speed }}
    Node{{ node }}
    IP{{ ip }}
    \ No newline at end of file diff --git a/lib/routes/hostmonit/templates/title.art b/lib/routes/hostmonit/templates/title.art deleted file mode 100644 index 6a4cefa67..000000000 --- a/lib/routes/hostmonit/templates/title.art +++ /dev/null @@ -1 +0,0 @@ -[{{ line }} | {{ latency }} | {{ loss }} | {{ speed }} | {{ node }}] {{ ip }} \ No newline at end of file diff --git a/lib/routes/houxu/events.ts b/lib/routes/houxu/events.tsx similarity index 72% rename from lib/routes/houxu/events.ts rename to lib/routes/houxu/events.tsx index 5793564b9..e1debaa6a 100644 --- a/lib/routes/houxu/events.ts +++ b/lib/routes/houxu/events.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/events', @@ -37,13 +37,15 @@ async function handler(ctx) { author: item.creator.name, category: item.tags, pubDate: parseDate(item.update_at), - description: art(path.join(__dirname, 'templates/events.art'), { - title: item.title, - description: item.description, - linkTitle: item.last_thread.link_title, - content: item.last_thread.title.replaceAll('\r\n', '
    '), - pubDate: item.update_at, - }), + description: renderToString( + <> +

    {item.title}

    +

    {item.description}

    + Latest: {item.last_thread.link_title} +

    {raw(item.last_thread.title.replaceAll('\r\n', '
    '))}

    + {item.update_at} + + ), })); return { diff --git a/lib/routes/houxu/index.ts b/lib/routes/houxu/index.tsx similarity index 59% rename from lib/routes/houxu/index.ts rename to lib/routes/houxu/index.tsx index 17ca07e39..f30373cc9 100644 --- a/lib/routes/houxu/index.ts +++ b/lib/routes/houxu/index.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { name: '热点', @@ -34,15 +34,18 @@ async function handler(ctx) { link: `${rootUrl}/lives/${item.object.id}`, author: item.object.last.link.source ?? item.object.last.link.media.name, pubDate: parseDate(item.object.news_update_at), - description: art(path.join(__dirname, 'templates/lives.art'), { - title: item.object.title, - description: item.object.summary, - url: item.object.last.link.url, - linkTitle: item.object.last.link.title, - source: item.object.last.link.source ?? item.object.last.link.media.name, - content: item.object.last.link.description.replaceAll('\r\n', '
    '), - pubDate: item.object.news_update_at, - }), + description: renderToString( + <> +

    {item.object.title}

    +

    {item.object.summary}

    + + Latest: {item.object.last.link.title} + {item.object.last.link.source || item.object.last.link.media.name ? <> ({item.object.last.link.source ?? item.object.last.link.media.name}) : null} + +

    {raw(item.object.last.link.description.replaceAll('\r\n', '
    '))}

    + {item.object.news_update_at} + + ), })); return { diff --git a/lib/routes/houxu/memory.ts b/lib/routes/houxu/memory.tsx similarity index 73% rename from lib/routes/houxu/memory.ts rename to lib/routes/houxu/memory.tsx index f0944690c..f60a5fada 100644 --- a/lib/routes/houxu/memory.ts +++ b/lib/routes/houxu/memory.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/memory', @@ -37,13 +36,16 @@ async function handler(ctx) { author: item.last.link.source, category: [item.title], pubDate: parseDate(item.last.create_at), - description: art(path.join(__dirname, 'templates/memory.art'), { - live: item.title, - url: item.last.link.url, - title: item.last.link.title, - source: item.last.link.source, - description: item.last.link.description, - }), + description: renderToString( + <> +

    {item.title}

    + + {item.last.link.title} + {item.last.link.source ? <> ({item.last.link.source}) : null} + +

    {item.last.link.description}

    + + ), })); return { diff --git a/lib/routes/houxu/templates/events.art b/lib/routes/houxu/templates/events.art deleted file mode 100644 index 0014cd805..000000000 --- a/lib/routes/houxu/templates/events.art +++ /dev/null @@ -1,5 +0,0 @@ -

    {{ title }}

    -

    {{ description }}

    -Latest: {{ linkTitle }} -

    {{@ content }}

    -{{ pubDate }} \ No newline at end of file diff --git a/lib/routes/houxu/templates/lives.art b/lib/routes/houxu/templates/lives.art deleted file mode 100644 index d2509f5d9..000000000 --- a/lib/routes/houxu/templates/lives.art +++ /dev/null @@ -1,5 +0,0 @@ -

    {{ title }}

    -

    {{ description }}

    -Latest: {{ linkTitle }}{{ if source }} ({{ source }}){{ /if }} -

    {{@ content }}

    -{{ pubDate }} \ No newline at end of file diff --git a/lib/routes/houxu/templates/memory.art b/lib/routes/houxu/templates/memory.art deleted file mode 100644 index 1a209ae7c..000000000 --- a/lib/routes/houxu/templates/memory.art +++ /dev/null @@ -1,3 +0,0 @@ -

    {{ live }}

    -{{ title }}{{ if source }} ({{ source }}){{ /if }} -

    {{ description }}

    \ No newline at end of file diff --git a/lib/routes/hoyolab/news.ts b/lib/routes/hoyolab/news.tsx similarity index 88% rename from lib/routes/hoyolab/news.ts rename to lib/routes/hoyolab/news.tsx index 5466ae0cb..c525a9636 100644 --- a/lib/routes/hoyolab/news.ts +++ b/lib/routes/hoyolab/news.tsx @@ -1,11 +1,11 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import logger from '@/utils/logger'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { HOST, LINK, NEW_LIST, OFFICIAL_PAGE_TYPE, POST_FULL, PRIVATE_IMG, PUBLIC_IMG } from './constant'; import { getI18nGameInfo, getI18nType } from './utils'; @@ -53,7 +53,7 @@ const getPostContent = (list, { language }) => if (content === language || !content) { content = post.content; } - const description = art(path.join(__dirname, 'templates/post.art'), { + const description = renderPostDescription({ hasCover: post.has_cover, coverList: row.cover_list, content: replaceImgDomain(content), @@ -140,3 +140,22 @@ async function handler(ctx) { logger.error(error); } } + +type CoverItem = { + url: string; +}; + +const renderPostDescription = ({ hasCover, coverList, content }: { hasCover: boolean; coverList?: CoverItem[]; content: string }): string => + renderToString( + <> + {hasCover + ? coverList?.map((cover) => ( + <> + +
    + + )) + : null} + {raw(content)} + + ); diff --git a/lib/routes/hoyolab/templates/post.art b/lib/routes/hoyolab/templates/post.art deleted file mode 100644 index 8744cb1e6..000000000 --- a/lib/routes/hoyolab/templates/post.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if hasCover }} - {{ each coverList c }} -
    - {{ /each }} -{{ /if }} - -{{@ content }} diff --git a/lib/routes/hupu/all.ts b/lib/routes/hupu/all.tsx similarity index 79% rename from lib/routes/hupu/all.ts rename to lib/routes/hupu/all.tsx index a49fc1e7b..5bc23807c 100644 --- a/lib/routes/hupu/all.ts +++ b/lib/routes/hupu/all.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate, parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -83,10 +82,19 @@ async function handler(ctx) { item.author = content('.bbs-user-wrapper-content-name-span').first().text(); item.pubDate = item.pubDate ?? timezone(parseRelativeDate(content('.second-line-user-info').first().text()), +8); - item.description = art(path.resolve(__dirname, 'templates/description.art'), { - videos, - description: content('.bbs-content').first().html(), - }); + const description = content('.bbs-content').first().html(); + item.description = renderToString( + <> + {videos.length + ? videos.map((video) => ( + + )) + : null} + {description ? raw(description) : null} + + ); } catch { // no-empty } diff --git a/lib/routes/hupu/bbs.ts b/lib/routes/hupu/bbs.tsx similarity index 70% rename from lib/routes/hupu/bbs.ts rename to lib/routes/hupu/bbs.tsx index 2d8661258..010657c40 100644 --- a/lib/routes/hupu/bbs.ts +++ b/lib/routes/hupu/bbs.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -91,12 +89,31 @@ async function handler(ctx) { const result = detailResponse.data.result; - item.description = art(path.join(__dirname, 'templates/match.art'), { - image: result.img, - description: result.beginContent, - keyEvent: result.keyEvent, - playerImage: result.playerScoreImg, - }); + item.description = renderToString( + <> + {result.img ? : null} + {result.beginContent ?

    {result.beginContent}

    : null} + {result.keyEvent?.length ? ( + <> +

    关键事件

    + {result.keyEvent.map((event) => ( + <> +

    {event.title}

    + {event.gifImgs?.map((gif) => ( + + ))} + + ))} + + ) : null} + {result.playerScoreImg ? ( + <> +

    球员评分

    + + + ) : null} + + ); } } catch { // no-empty diff --git a/lib/routes/hupu/templates/description.art b/lib/routes/hupu/templates/description.art deleted file mode 100644 index fe8a1d64f..000000000 --- a/lib/routes/hupu/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if videos }} -{{ each videos video }} - -{{ /each }} -{{ /if }} - -{{ if description }} -{{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/hupu/templates/match.art b/lib/routes/hupu/templates/match.art deleted file mode 100644 index 3d417ad52..000000000 --- a/lib/routes/hupu/templates/match.art +++ /dev/null @@ -1,22 +0,0 @@ -{{ if image }} - -{{ /if }} - -{{ if description }} -

    {{ description }}

    -{{ /if }} - -{{ if keyEvent }} -

    关键事件

    -{{ each keyEvent event }} -

    {{ event.title }}

    -{{ each event.gifImgs gif }} - -{{ /each }} -{{ /each }} -{{ /if }} - -{{ if playerImage }} -

    球员评分

    - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/huxiu/templates/description.art b/lib/routes/huxiu/templates/description.art deleted file mode 100644 index 298a0053a..000000000 --- a/lib/routes/huxiu/templates/description.art +++ /dev/null @@ -1,46 +0,0 @@ -{{ if (!video || !video.src) && image?.src }} -
    - -
    -{{ /if }} - -{{ if audio?.src }} - -{{ /if }} - -{{ if video?.src }} - -{{ /if }} - -{{ if preface }} - {{@ preface }} -{{ /if }} - -{{ if summary }} - {{@ summary }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/huxiu/templates/description.tsx b/lib/routes/huxiu/templates/description.tsx new file mode 100644 index 000000000..c3d6e51a5 --- /dev/null +++ b/lib/routes/huxiu/templates/description.tsx @@ -0,0 +1,55 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type MediaImage = { + src?: string; + width?: string | number; + height?: string | number; +}; + +type MediaAudio = { + src?: string; + type?: string; +}; + +type MediaVideo = { + src?: string; + type?: string; + poster?: string; +}; + +type DescriptionData = { + image?: MediaImage; + audio?: MediaAudio; + video?: MediaVideo; + preface?: string; + summary?: string; + description?: string; +}; + +export const renderDescription = ({ image, audio, video, preface, summary, description }: DescriptionData) => + renderToString( + <> + {!video?.src && image?.src ? ( +
    + +
    + ) : null} + {audio?.src ? ( + + ) : null} + {video?.src ? ( + + ) : null} + {preface ? <>{raw(preface)} : null} + {summary ? <>{raw(summary)} : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/huxiu/util.ts b/lib/routes/huxiu/util.ts index d07a2ee80..18ef04283 100644 --- a/lib/routes/huxiu/util.ts +++ b/lib/routes/huxiu/util.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import CryptoJS from 'crypto-js'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const domain = 'huxiu.com'; const rootUrl = `https://www.${domain}`; @@ -31,7 +30,7 @@ const cleanUpHTML = (data) => { e = $(e); if ((e.prop('src') ?? e.prop('_src')) !== undefined) { e.parent().replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: (e.prop('src') ?? e.prop('_src')).split(/\?/)[0], width: e.prop('data-w'), @@ -204,7 +203,7 @@ const fetchItem = async (item) => { const { processed: video, processedItem: videoItem = {} } = processVideoInfo(data.video_info); item.title = data.title ?? item.title; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ image: { src: data.pic_path, }, @@ -367,7 +366,7 @@ const processItems = async (items, limit, tryGet) => { ...videoItem, title: (item.title ?? item.summary ?? item.content)?.replaceAll(/<\/?(?:em|br)?>/g, ''), link, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: { src: item.origin_pic_path ?? item.pic_path ?? item.big_pic_path?.split(/\?/)[0] ?? undefined, }, diff --git a/lib/routes/hyperdash/templates/description.art b/lib/routes/hyperdash/templates/description.art deleted file mode 100644 index 76bdc7496..000000000 --- a/lib/routes/hyperdash/templates/description.art +++ /dev/null @@ -1,34 +0,0 @@ -

    Trader #{{ rank }}

    - -

    Address: {{ address }}

    -

    Account Value: {{ accountValue }}

    - -

    Main Position

    -

    Coin: {{ mainPosition.coin }}

    -

    Position Value: {{ mainPosition.value }}

    -

    Side: {{ mainPosition.side }}

    -

    Direction Bias: {{ directionBias }}

    - -

    PnL Performance

    - - - - - - - - - - - - - - - - - - - - - -
    PeriodPnL
    Day{{ pnl.day.value }}
    Week{{ pnl.week.value }}
    Month{{ pnl.month.value }}
    All-time{{ pnl.alltime.value }}
    diff --git a/lib/routes/hyperdash/top-traders.ts b/lib/routes/hyperdash/top-traders.ts deleted file mode 100644 index 89232f3c8..000000000 --- a/lib/routes/hyperdash/top-traders.ts +++ /dev/null @@ -1,86 +0,0 @@ -import path from 'node:path'; - -import type { Route } from '@/types'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -import { fetchTopTraders, formatCurrency, formatPnL } from './utils'; - -export const route: Route = { - path: '/top-traders', - categories: ['finance'], - example: '/hyperdash/top-traders', - parameters: {}, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['hyperdash.info/'], - }, - ], - name: 'Top Traders', - maintainers: ['pseudoyu'], - handler, - description: 'Get the latest top traders data from HyperDash', -}; - -async function handler() { - const traders = await fetchTopTraders(); - - const items = traders.map((trader, index) => { - const rank = index + 1; - - const title = trader.address; - - const description = art(path.join(__dirname, 'templates/description.art'), { - rank, - address: trader.address, - accountValue: formatCurrency(trader.account_value), - mainPosition: { - coin: trader.main_position.coin, - value: formatCurrency(trader.main_position.value), - side: trader.main_position.side, - }, - directionBias: trader.direction_bias !== null && trader.direction_bias !== undefined ? trader.direction_bias.toFixed(2) + '%' : 'N/A', - pnl: { - day: { - value: formatPnL(trader.perp_day_pnl), - }, - week: { - value: formatPnL(trader.perp_week_pnl), - }, - month: { - value: formatPnL(trader.perp_month_pnl), - }, - alltime: { - value: formatPnL(trader.perp_alltime_pnl), - }, - }, - }); - - const baseTime = new Date(); - const orderTimestamp = new Date(baseTime.getTime() - index * 1000); // Each item 1 second apart - - return { - title, - description, - link: `https://hyperdash.info/trader/${trader.address}`, - pubDate: parseDate(orderTimestamp.toISOString()), - guid: trader.address, - }; - }); - - return { - title: 'HyperDash Top Traders', - link: 'https://hyperdash.info/', - description: 'Top performing traders on HyperDash - real-time cryptocurrency derivatives trading analytics', - item: items, - language: 'en' as const, - }; -} diff --git a/lib/routes/hyperdash/top-traders.tsx b/lib/routes/hyperdash/top-traders.tsx new file mode 100644 index 000000000..c50dfaf9d --- /dev/null +++ b/lib/routes/hyperdash/top-traders.tsx @@ -0,0 +1,120 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import { parseDate } from '@/utils/parse-date'; + +import { fetchTopTraders, formatCurrency, formatPnL } from './utils'; + +export const route: Route = { + path: '/top-traders', + categories: ['finance'], + example: '/hyperdash/top-traders', + parameters: {}, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['hyperdash.info/'], + }, + ], + name: 'Top Traders', + maintainers: ['pseudoyu'], + handler, + description: 'Get the latest top traders data from HyperDash', +}; + +async function handler() { + const traders = await fetchTopTraders(); + + const items = traders.map((trader, index) => { + const rank = index + 1; + + const title = trader.address; + + const accountValue = formatCurrency(trader.account_value); + const mainPosition = { + coin: trader.main_position.coin, + value: formatCurrency(trader.main_position.value), + side: trader.main_position.side, + }; + const directionBias = trader.direction_bias !== null && trader.direction_bias !== undefined ? trader.direction_bias.toFixed(2) + '%' : 'N/A'; + const pnl = { + day: formatPnL(trader.perp_day_pnl), + week: formatPnL(trader.perp_week_pnl), + month: formatPnL(trader.perp_month_pnl), + alltime: formatPnL(trader.perp_alltime_pnl), + }; + const description = renderToString( + <> +

    Trader #{rank}

    +

    + Address: {trader.address} +

    +

    + Account Value: {accountValue} +

    +

    Main Position

    +

    + Coin: {mainPosition.coin} +

    +

    + Position Value: {mainPosition.value} +

    +

    + Side: {mainPosition.side} +

    +

    + Direction Bias: {directionBias} +

    +

    PnL Performance

    + + + + + + + + + + + + + + + + + + + + + +
    PeriodPnL
    Day{pnl.day}
    Week{pnl.week}
    Month{pnl.month}
    All-time{pnl.alltime}
    + + ); + + const baseTime = new Date(); + const orderTimestamp = new Date(baseTime.getTime() - index * 1000); // Each item 1 second apart + + return { + title, + description, + link: `https://hyperdash.info/trader/${trader.address}`, + pubDate: parseDate(orderTimestamp.toISOString()), + guid: trader.address, + }; + }); + + return { + title: 'HyperDash Top Traders', + link: 'https://hyperdash.info/', + description: 'Top performing traders on HyperDash - real-time cryptocurrency derivatives trading analytics', + item: items, + language: 'en' as const, + }; +} diff --git a/lib/routes/i-cable/news.ts b/lib/routes/i-cable/news.tsx similarity index 78% rename from lib/routes/i-cable/news.ts rename to lib/routes/i-cable/news.tsx index a75ecc46f..8640c8f50 100644 --- a/lib/routes/i-cable/news.ts +++ b/lib/routes/i-cable/news.tsx @@ -1,11 +1,11 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/news/:category?', @@ -53,10 +53,18 @@ async function handler(ctx) { const list = await got(`${root}/posts?_embed=1&categories=${metadata.id}&per_page=${limit}`); const items = list.data.map((item) => { - const description = art(path.join(__dirname, 'templates/description.art'), { - media: item._embedded['wp:featuredmedia'] ?? [], - content: item.content.rendered, - }); + const description = renderToString( + <> + {item._embedded['wp:featuredmedia']?.length + ? item._embedded['wp:featuredmedia'].map((media) => ( +
    + +
    + )) + : null} + {item.content.rendered ? raw(item.content.rendered) : null} + + ); return { title: item.title.rendered, link: item.link, diff --git a/lib/routes/i-cable/templates/description.art b/lib/routes/i-cable/templates/description.art deleted file mode 100644 index 1748ab422..000000000 --- a/lib/routes/i-cable/templates/description.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if media.length > 0 }} - {{ each media }} -
    - {{ /each }} -{{ /if }} -{{ if content }} - {{@ content }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ianspriggs/index.ts b/lib/routes/ianspriggs/index.ts index f296e83a2..44cea1e90 100644 --- a/lib/routes/ianspriggs/index.ts +++ b/lib/routes/ianspriggs/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/:category?', @@ -52,7 +51,7 @@ async function handler(ctx) { return { title: item.find('div.work-info').text(), link: item.find('a').prop('href'), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: image?.prop('src') ? [ { @@ -88,7 +87,7 @@ async function handler(ctx) { }); item.title = content('div.project-title').text(); - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ images, description: content('div.nectar-fancy-ul').html(), }); diff --git a/lib/routes/ianspriggs/templates/description.art b/lib/routes/ianspriggs/templates/description.art deleted file mode 100644 index afd87d6b9..000000000 --- a/lib/routes/ianspriggs/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ each images image }} -
    - {{ image.alt }} -
    -{{ /each }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ianspriggs/templates/description.tsx b/lib/routes/ianspriggs/templates/description.tsx new file mode 100644 index 000000000..b7f32afec --- /dev/null +++ b/lib/routes/ianspriggs/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionData) => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/idaily/index.ts b/lib/routes/idaily/index.tsx similarity index 77% rename from lib/routes/idaily/index.ts rename to lib/routes/idaily/index.tsx index 93be08d14..d8eb74217 100644 --- a/lib/routes/idaily/index.ts +++ b/lib/routes/idaily/index.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: ['/:language?'], @@ -43,8 +41,8 @@ async function handler(ctx) { return { title: `${item.ui_sets?.caption_subtitle} - ${item.title}`, link: item.link_share, - description: art(path.join(__dirname, 'templates/description.art'), { - images: image + description: renderDescription( + image ? [ { src: image, @@ -52,8 +50,8 @@ async function handler(ctx) { }, ] : undefined, - intro: item.content, - }), + item.content + ), author: item.location, category: item.tags?.map((c) => c.name), guid: `idaily-${item.guid}`, @@ -83,3 +81,23 @@ async function handler(ctx) { allowEmpty: true, }; } + +type IdailyImage = { + src?: string; + alt?: string; +}; + +const renderDescription = (images: IdailyImage[] | undefined, intro?: string) => renderToString(); + +const IdailyDescription = ({ images, intro }: { images?: IdailyImage[]; intro?: string }) => ( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {intro ?

    {intro}

    : null} + +); diff --git a/lib/routes/idaily/templates/description.art b/lib/routes/idaily/templates/description.art deleted file mode 100644 index a3f88c9b4..000000000 --- a/lib/routes/idaily/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -

    {{ intro }}

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ieee/author.ts b/lib/routes/ieee/author.ts index 289e97363..2056ca0aa 100644 --- a/lib/routes/ieee/author.ts +++ b/lib/routes/ieee/author.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { name: 'IEEE Author Articles', @@ -87,9 +86,3 @@ async function handler(ctx) { image, }; } - -function renderDescription(item: { title: string; authors: string; abstract: string; doi: string }) { - return art(path.join(__dirname, 'templates/description.art'), { - item, - }); -} diff --git a/lib/routes/ieee/journal.ts b/lib/routes/ieee/journal.ts index 7ef3e00c9..e1299e00f 100644 --- a/lib/routes/ieee/journal.ts +++ b/lib/routes/ieee/journal.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const ieeeHost = 'https://ieeexplore.ieee.org'; @@ -51,9 +50,7 @@ async function handler(ctx) { // 捕获等号右侧的 JSON(最小匹配直到紧随的分号) const m = code.match(/xplGlobal\.document\.metadata\s*=\s*(\{[\s\S]*?\})\s*;/); item.abstract = m ? ((JSON.parse(m[1]) as { abstract?: string }).abstract ?? ' ') : ' '; - item.description = art(path.join(__dirname, 'templates/description.art'), { - item, - }); + item.description = renderDescription(item); return item; }) diff --git a/lib/routes/ieee/templates/description.art b/lib/routes/ieee/templates/description.art deleted file mode 100644 index a9e8c5da2..000000000 --- a/lib/routes/ieee/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -

    - {{ item.title }}
    -

    -

    - {{ item.authors }}
    - https://doi.org/{{ item.doi }}
    - Volume {{ item.volume }}
    -

    -

    - {{ item.abstract }}
    -

    \ No newline at end of file diff --git a/lib/routes/ieee/templates/description.tsx b/lib/routes/ieee/templates/description.tsx new file mode 100644 index 000000000..9ac864aa6 --- /dev/null +++ b/lib/routes/ieee/templates/description.tsx @@ -0,0 +1,47 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionItem = { + title: string; + authors: string; + doi: string; + volume?: string | number; + abstract: string; +}; + +export const renderDescription = (item: DescriptionItem): string => + renderToString( + <> +

    + + {item.title} + +
    +

    +

    + + + {item.authors} + + +
    + + + + https://doi.org/{item.doi} + + + +
    + + + Volume {item.volume ?? ''} + + +
    +

    +

    + {item.abstract} +
    +

    + + ); diff --git a/lib/routes/ifeng/news.ts b/lib/routes/ifeng/news.tsx similarity index 56% rename from lib/routes/ifeng/news.ts rename to lib/routes/ifeng/news.tsx index 1134771d0..b8b86ec9d 100644 --- a/lib/routes/ifeng/news.ts +++ b/lib/routes/ifeng/news.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -49,10 +48,28 @@ async function handler(ctx) { item.author = detailResponse.data.match(/"editorName":"(.*?)",/)[1]; item.category = detailResponse.data.match(/},"keywords":"(.*?)",/)[1].split(','); - item.description = art(path.join(__dirname, 'templates/description.art'), { - image: item.description, - description: JSON.parse(detailResponse.data.match(/"contentList":(\[.*?]),/)[1]).map((content) => content.data), - }); + const image = item.description; + const description = JSON.parse(detailResponse.data.match(/"contentList":(\[.*?]),/)[1]).map((content) => content.data); + item.description = renderToString( + <> + {image ? ( +
    + +
    + ) : null} + {description?.length + ? description.map((entry) => + entry?.attachmentType === 'video' ? ( + + ) : typeof entry === 'string' ? ( + <>{raw(entry.replaceAll('data-lazyload=', 'src='))} + ) : null + ) + : null} + + ); return item; }) ) diff --git a/lib/routes/ifeng/templates/description.art b/lib/routes/ifeng/templates/description.art deleted file mode 100644 index 6e8aef870..000000000 --- a/lib/routes/ifeng/templates/description.art +++ /dev/null @@ -1,16 +0,0 @@ -{{ if image }} -
    - -
    -{{ /if }} -{{ if description }} -{{ each description d }} -{{ if d.attachmentType === 'video' }} - -{{ else }} -{{@ d.replace(/data-lazyload=/g, 'src=') }} -{{ /if }} -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ifeng/templates/video.art b/lib/routes/ifeng/templates/video.art deleted file mode 100644 index 510f5797b..000000000 --- a/lib/routes/ifeng/templates/video.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if videoInfo.mobileUrl }} - -{{ /if }} diff --git a/lib/routes/ifeng/utils.ts b/lib/routes/ifeng/utils.tsx similarity index 61% rename from lib/routes/ifeng/utils.ts rename to lib/routes/ifeng/utils.tsx index 0ef7347f4..d56a5041d 100644 --- a/lib/routes/ifeng/utils.ts +++ b/lib/routes/ifeng/utils.tsx @@ -1,6 +1,4 @@ -import path from 'node:path'; - -import { art } from '@/utils/render'; +import { renderToString } from 'hono/jsx/dom/server'; const extractDoc = (data) => data @@ -17,8 +15,12 @@ const extractDoc = (data) => .join('
    '); const renderVideo = (videoInfo) => - art(path.join(__dirname, 'templates/video.art'), { - videoInfo, - }); + renderToString( + videoInfo.mobileUrl ? ( + + ) : null + ); export { extractDoc, renderVideo }; diff --git a/lib/routes/ikea/cn/utils.ts b/lib/routes/ikea/cn/utils.ts deleted file mode 100644 index 1d067a190..000000000 --- a/lib/routes/ikea/cn/utils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import path from 'node:path'; - -import md5 from '@/utils/md5'; -import { art } from '@/utils/render'; - -const generateRequestHeaders = () => { - const now = Math.round(Date.now() / 1000); - return { - 'X-Client-Platform': 'WechatMiniprogram', - 'X-Client-DeviceId': md5(now.toString()), - }; -}; - -const generateProductItem = (product) => { - const { - productFullId, - name, - productType, - measureText, - priceDisplay: { currentPrice, originalPrice }, - images, - } = product; - const isFamilyOffer = currentPrice && originalPrice; - - return { - title: `${name} ${productType} - \u{000A5}${currentPrice}`, - description: art(path.join(__dirname, '../templates/cn/product.art'), { - isFamilyOffer, - name, - productType, - measureText, - currentPrice, - originalPrice, - images: images.map((image) => image.url), - }), - link: `https://www.ikea.cn/cn/zh/p/${productFullId}`, - }; -}; - -export { generateProductItem, generateRequestHeaders }; diff --git a/lib/routes/ikea/cn/utils.tsx b/lib/routes/ikea/cn/utils.tsx new file mode 100644 index 000000000..03c180810 --- /dev/null +++ b/lib/routes/ikea/cn/utils.tsx @@ -0,0 +1,50 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +import md5 from '@/utils/md5'; + +const generateRequestHeaders = () => { + const now = Math.round(Date.now() / 1000); + return { + 'X-Client-Platform': 'WechatMiniprogram', + 'X-Client-DeviceId': md5(now.toString()), + }; +}; + +const generateProductItem = (product) => { + const { + productFullId, + name, + productType, + measureText, + priceDisplay: { currentPrice, originalPrice }, + images, + } = product; + const isFamilyOffer = currentPrice && originalPrice; + + return { + title: `${name} ${productType} - \u{000A5}${currentPrice}`, + description: renderToString( + <> +

    名称:{name}

    +

    类型:{productType}

    +

    尺寸:{measureText}

    + {isFamilyOffer ? ( + <> +

    会员价格:\u00A5{currentPrice}

    +

    非会员价格:\u00A5{originalPrice}

    + + ) : ( +

    价格:\u00A5{currentPrice}

    + )} +

    + {images.map((image) => ( + + ))} +

    + + ), + link: `https://www.ikea.cn/cn/zh/p/${productFullId}`, + }; +}; + +export { generateProductItem, generateRequestHeaders }; diff --git a/lib/routes/ikea/gb/new.ts b/lib/routes/ikea/gb/new.tsx similarity index 81% rename from lib/routes/ikea/gb/new.ts rename to lib/routes/ikea/gb/new.tsx index ea374dbec..082e70bc4 100644 --- a/lib/routes/ikea/gb/new.ts +++ b/lib/routes/ikea/gb/new.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/gb/new', @@ -64,9 +63,17 @@ async function handler() { const items = products.map((p) => ({ title: `${p.name} ${p.typeName}, ${p.itemMeasureReferenceText}`, - description: art(path.join(__dirname, '../templates/new.art'), { - p, - }), + description: renderToString( + <> + {p.mainImageAlt} +
    + {p.name} +
    + {p.typeName}, {p.itemMeasureReferenceText} +
    + {p.salesPrice.current.prefix} {p.salesPrice.current.wholeNumber} + + ), link: p.pipUrl, category: p.categoryPath.map((c) => c.name), })); diff --git a/lib/routes/ikea/gb/offer.ts b/lib/routes/ikea/gb/offer.ts index 10387aae3..c961c96ec 100644 --- a/lib/routes/ikea/gb/offer.ts +++ b/lib/routes/ikea/gb/offer.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderOffer } from '../templates/offer'; export const route: Route = { path: '/gb/offer', @@ -51,7 +50,7 @@ async function handler() { searchParams.delete('itm_campaign'); return { title: title.text(), - description: art(path.join(__dirname, '../templates/offer.art'), { + description: renderOffer({ img: img.parent().html(), desc: title.next().parent().html(), }), @@ -78,7 +77,7 @@ async function handler() { searchParams.delete('itm_campaign'); return { title: title.text(), - description: art(path.join(__dirname, '../templates/offer.art'), { + description: renderOffer({ img: img.parent().html(), desc: title.parent().html(), }), diff --git a/lib/routes/ikea/templates/cn/product.art b/lib/routes/ikea/templates/cn/product.art deleted file mode 100644 index 7204bd3a1..000000000 --- a/lib/routes/ikea/templates/cn/product.art +++ /dev/null @@ -1,14 +0,0 @@ -

    名称:{{name}}

    -

    类型:{{productType}}

    -

    尺寸:{{measureText}}

    -{{if isFamilyOffer}} -

    会员价格:¥{{currentPrice}}

    -

    非会员价格:¥{{originalPrice}}

    -{{else}} -

    价格:¥{{currentPrice}}

    -{{/if}} -

    - {{each images}} - - {{/each}} -

    diff --git a/lib/routes/ikea/templates/new.art b/lib/routes/ikea/templates/new.art deleted file mode 100644 index 114f52d7c..000000000 --- a/lib/routes/ikea/templates/new.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ p.mainImageAlt }} -
    -{{ p.name }} -
    -{{ p.typeName }}, {{ p.itemMeasureReferenceText }} -
    -{{ p.salesPrice.current.prefix }} {{ p.salesPrice.current.wholeNumber }} diff --git a/lib/routes/ikea/templates/offer.art b/lib/routes/ikea/templates/offer.art deleted file mode 100644 index ce79ac73a..000000000 --- a/lib/routes/ikea/templates/offer.art +++ /dev/null @@ -1,3 +0,0 @@ -{{@ img }} -
    -{{@ desc }} diff --git a/lib/routes/ikea/templates/offer.tsx b/lib/routes/ikea/templates/offer.tsx new file mode 100644 index 000000000..f7627df04 --- /dev/null +++ b/lib/routes/ikea/templates/offer.tsx @@ -0,0 +1,17 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type OfferData = { + img?: string; + desc?: string; +}; + +const IkeaOffer = ({ img, desc }: OfferData) => ( + <> + {img ? raw(img) : null} +
    + {desc ? raw(desc) : null} + +); + +export const renderOffer = (data: OfferData) => renderToString(); diff --git a/lib/routes/iknowwhatyoudownload/daily.ts b/lib/routes/iknowwhatyoudownload/daily.tsx similarity index 63% rename from lib/routes/iknowwhatyoudownload/daily.ts rename to lib/routes/iknowwhatyoudownload/daily.tsx index 635d910d5..f4e360d19 100644 --- a/lib/routes/iknowwhatyoudownload/daily.ts +++ b/lib/routes/iknowwhatyoudownload/daily.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import dayjs from 'dayjs'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; interface TableData { key: string; @@ -86,11 +85,48 @@ async function handler(ctx) { content: $(item).find('ul').toString(), })); - const content = art(path.join(__dirname, 'templates/daily.art'), { - numStats, - tableData, - topList, - }); + const content = renderToString( +
    +
    +

    Torrent download statistics

    +
      + {numStats.map((stat) => ( +
    • + {stat.percent} {stat.desc} +
    • + ))} +
    +
    +
    +

    Table View

    + {tableData ? ( + + + + + + + {tableData.map((row) => ( + + + + + + ))} +
    CategoryCountPercent
    {row.key}{row.count}{row.percent}
    + ) : null} +
    +
    +

    Top List

    + {topList.map((entry) => ( + <> +

    {entry.title}

    + {raw(entry.content)} + + ))} +
    +
    + ); return { title: `Daily Torrents Statistics in ${country} for ${dateFormatted}`, diff --git a/lib/routes/iknowwhatyoudownload/templates/daily.art b/lib/routes/iknowwhatyoudownload/templates/daily.art deleted file mode 100644 index 1466aea09..000000000 --- a/lib/routes/iknowwhatyoudownload/templates/daily.art +++ /dev/null @@ -1,34 +0,0 @@ -
    -
    -

    Torrent download statistics

    -
      - {{each numStats}} -
    • {{$value.percent}} {{$value.desc}}
    • - {{/each}} -
    -
    - -
    -

    Table View

    - {{if tableData}} - - - {{each tableData}} - - - - - - {{/each}} -
    CategoryCountPercent
    {{$value.key}}{{$value.count}}{{$value.percent}}
    - {{/if}} -
    - -
    -

    Top List

    - {{each topList}} -

    {{$value.title}}

    - {{@ $value.content}} - {{/each}} -
    -
    diff --git a/lib/routes/imdb/chart.ts b/lib/routes/imdb/chart.tsx similarity index 74% rename from lib/routes/imdb/chart.ts rename to lib/routes/imdb/chart.tsx index 59e9a35b9..b33d0e8f1 100644 --- a/lib/routes/imdb/chart.ts +++ b/lib/routes/imdb/chart.tsx @@ -1,16 +1,34 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; import type { ChartTitleSearchConnection } from './types'; -const render = (data) => art(path.join(__dirname, 'templates/chart.art'), data); +const render = ({ primaryImage, originalTitleText, certificate, ratingsSummary, plot }) => + renderToString( + <> + {primaryImage?.url ? ( + <> +
    + {primaryImage.caption?.plainText} +
    {primaryImage.caption?.plainText}
    +
    +
    + + ) : null} + {`Original title: ${originalTitleText.text}`} +
    + {certificate ? `${certificate.rating} ` : null} + {ratingsSummary?.aggregateRating ? `IMDb RATING: ${ratingsSummary.aggregateRating}/10 (${ratingsSummary.voteCount})` : null} +
    +
    + {plot?.plotText?.plainText} + + ); export const route: Route = { path: '/chart/:chart?', diff --git a/lib/routes/imdb/templates/chart.art b/lib/routes/imdb/templates/chart.art deleted file mode 100644 index 57134175a..000000000 --- a/lib/routes/imdb/templates/chart.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if primaryImage.url }} -
    - {{ primaryImage.caption.plainText }} -
    {{ primaryImage.caption.plainText }}
    -
    -
    -{{ /if }} - -Original title: {{ originalTitleText.text }}
    - -{{ if certificate }}{{ certificate.rating }}{{ /if }} -{{ if ratingsSummary.aggregateRating }}IMDb RATING: {{ ratingsSummary.aggregateRating }}/10 ({{ ratingsSummary.voteCount }}){{ /if }} -

    - -{{ plot.plotText.plainText }} diff --git a/lib/routes/imiker/jinghua.ts b/lib/routes/imiker/jinghua.ts index bbd1f07df..9ff9586fe 100644 --- a/lib/routes/imiker/jinghua.ts +++ b/lib/routes/imiker/jinghua.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/ask/jinghua', @@ -51,7 +50,7 @@ async function handler(ctx) { let items = response.slice(0, limit).map((item) => ({ title: item.question_content, link: new URL(`question/${item.id}`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ headImage: item.headimage, author: item.nick_name, question: item.question_detail, @@ -74,7 +73,7 @@ async function handler(ctx) { const image = content(e).find('img'); content(e).replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: image.prop('data-original'), alt: image.prop('alt'), @@ -86,7 +85,7 @@ async function handler(ctx) { }); item.title = content('div.title h1').text(); - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ description: content('div#warp').html(), }); item.author = content('div.name').text(); diff --git a/lib/routes/imiker/templates/description.art b/lib/routes/imiker/templates/description.art deleted file mode 100644 index 97303d008..000000000 --- a/lib/routes/imiker/templates/description.art +++ /dev/null @@ -1,32 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if headImage }} -
    - - {{ if author }} -
    {{ author }}
    - {{ /if }} -
    -{{ /if }} - -{{ if question }} -
    {{ question }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/imiker/templates/description.tsx b/lib/routes/imiker/templates/description.tsx new file mode 100644 index 000000000..c080ca5a5 --- /dev/null +++ b/lib/routes/imiker/templates/description.tsx @@ -0,0 +1,41 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; + width?: string; + height?: string; +}; + +type DescriptionProps = { + image?: DescriptionImage; + headImage?: string; + author?: string; + question?: string; + description?: string; +}; + +const Description = ({ image, headImage, author, question, description }: DescriptionProps) => { + const imageAlt = image?.height ?? image?.width ?? image?.alt; + + return ( + <> + {image?.src ? ( +
    + {imageAlt} +
    + ) : null} + {headImage ? ( +
    + + {author ?
    {author}
    : null} +
    + ) : null} + {question ?
    {question}
    : null} + {description ? <>{raw(description)} : null} + + ); +}; + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/infoq/presentations.ts b/lib/routes/infoq/presentations.ts index dd5f1e7c1..3f1b35a63 100644 --- a/lib/routes/infoq/presentations.ts +++ b/lib/routes/infoq/presentations.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { conference } = ctx.req.param(); @@ -31,7 +30,7 @@ export const handler = async (ctx) => { const title = a.prop('title') || a.text().trim(); const image = item.find('img.card__image').prop('src'); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -95,7 +94,7 @@ export const handler = async (ctx) => { if (videoSrc) { $$('div.player').replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ videos: [ { src: videoSrc, @@ -121,7 +120,7 @@ export const handler = async (ctx) => { $$('div.article__content').nextAll().remove(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { diff --git a/lib/routes/infoq/templates/description.art b/lib/routes/infoq/templates/description.art deleted file mode 100644 index 5b807209c..000000000 --- a/lib/routes/infoq/templates/description.art +++ /dev/null @@ -1,41 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if videos }} - {{ each videos video }} - {{ if video?.src }} - - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/infoq/templates/description.tsx b/lib/routes/infoq/templates/description.tsx new file mode 100644 index 000000000..6824b89a0 --- /dev/null +++ b/lib/routes/infoq/templates/description.tsx @@ -0,0 +1,50 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionVideo = { + src?: string; + poster?: string; + type?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + videos?: DescriptionVideo[]; + intro?: string; + description?: string; +}; + +const Description = ({ images, videos, intro, description }: DescriptionProps) => { + const fallbackPoster = images?.[0]?.src; + + return ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {videos?.map((video, index) => + video?.src ? ( + + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); +}; + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/informs/index.ts b/lib/routes/informs/index.tsx similarity index 84% rename from lib/routes/informs/index.ts rename to lib/routes/informs/index.tsx index cfbb0aa74..951753229 100644 --- a/lib/routes/informs/index.ts +++ b/lib/routes/informs/index.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://pubsonline.informs.org'; @@ -77,10 +76,7 @@ async function handler(ctx) { }, }); const detail = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/content.art'), { - author: detail('div.accordion-tabbed.loa-accordion').text(), - content: detail('div.hlFld-Abstract').find('h2').replaceWith($('

    Abstract

    ')).end().html(), - }); + item.description = renderDescription(detail('div.accordion-tabbed.loa-accordion').text(), detail('div.hlFld-Abstract').find('h2').replaceWith($('

    Abstract

    ')).end().html()); return item; }) @@ -93,3 +89,12 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (author: string, content: string): string => + renderToString( + <> + {author} +
    + {raw(content)} + + ); diff --git a/lib/routes/informs/templates/content.art b/lib/routes/informs/templates/content.art deleted file mode 100644 index a2009f0f8..000000000 --- a/lib/routes/informs/templates/content.art +++ /dev/null @@ -1,3 +0,0 @@ - {{ author }} -
    -{{@ content }} diff --git a/lib/routes/instagram/common-utils.ts b/lib/routes/instagram/common-utils.ts index d2339c6bc..d78050061 100644 --- a/lib/routes/instagram/common-utils.ts +++ b/lib/routes/instagram/common-utils.ts @@ -1,7 +1,7 @@ -import path from 'node:path'; - import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderImages } from './templates/images'; +import { renderVideo } from './templates/video'; const renderItems = (items) => items.map((item) => { @@ -16,7 +16,7 @@ const renderItems = (items) => ...i.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0], alt: item.accessibility_caption, })); - description = art(path.join(__dirname, 'templates/images.art'), { + description = renderImages({ summary, images, }); @@ -24,15 +24,15 @@ const renderItems = (items) => } case 'clips': case 'igtv': - description = art(path.join(__dirname, 'templates/video.art'), { + description = renderVideo({ summary, - image: item.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0], + image: item.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0].url, video: item.video_versions[0], }); break; case 'feed': { const images = [{ ...item.image_versions2.candidates.toSorted((a, b) => b.width - a.width)[0], alt: item.accessibility_caption }]; - description = art(path.join(__dirname, 'templates/images.art'), { + description = renderImages({ summary, images, }); diff --git a/lib/routes/instagram/templates/images.art b/lib/routes/instagram/templates/images.art deleted file mode 100644 index ca6cc69ec..000000000 --- a/lib/routes/instagram/templates/images.art +++ /dev/null @@ -1,12 +0,0 @@ -{{ if summary }} - {{@ summary.replace(/\n/g, '
    ') }} -
    -{{ /if }} - -{{ each images i }} - {{ i.alt }} -{{ /each }} diff --git a/lib/routes/instagram/templates/images.tsx b/lib/routes/instagram/templates/images.tsx new file mode 100644 index 000000000..9862827dd --- /dev/null +++ b/lib/routes/instagram/templates/images.tsx @@ -0,0 +1,29 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageItem = { + url: string; + height?: number | string; + width?: number | string; + alt?: string; +}; + +type ImagesRenderOptions = { + summary?: string; + images: ImageItem[]; +}; + +export const renderImages = ({ summary, images }: ImagesRenderOptions): string => + renderToString( + <> + {summary ? ( + <> + {raw(summary.replaceAll('\n', '
    '))} +
    + + ) : null} + {images.map((image) => ( + {image.alt + ))} + + ); diff --git a/lib/routes/instagram/templates/video.art b/lib/routes/instagram/templates/video.art deleted file mode 100644 index 9a6d0481f..000000000 --- a/lib/routes/instagram/templates/video.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if summary }} - {{@ summary.replace(/\n/g, '
    ') }} -
    -{{ /if }} - - diff --git a/lib/routes/instagram/templates/video.tsx b/lib/routes/instagram/templates/video.tsx new file mode 100644 index 000000000..bec4126ec --- /dev/null +++ b/lib/routes/instagram/templates/video.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type VideoItem = { + url: string; + width?: number | string; +}; + +type VideoRenderOptions = { + summary?: string; + image?: string; + video: VideoItem; +}; + +export const renderVideo = ({ summary, image, video }: VideoRenderOptions): string => + renderToString( + <> + {summary ? ( + <> + {raw(summary.replaceAll('\n', '
    '))} +
    + + ) : null} + + + ); diff --git a/lib/routes/instagram/web-api/utils.ts b/lib/routes/instagram/web-api/utils.ts index 62b48eba1..560df8172 100644 --- a/lib/routes/instagram/web-api/utils.ts +++ b/lib/routes/instagram/web-api/utils.ts @@ -1,11 +1,11 @@ -import path from 'node:path'; - import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderImages } from '../templates/images'; +import { renderVideo } from '../templates/video'; const baseUrl = 'https://www.instagram.com'; const COOKIE_URL = baseUrl; @@ -134,8 +134,8 @@ const getTagsFeed = (tag, cookieJar) => ); const renderGuestItems = (items) => { - const renderVideo = (node, summary) => - art(path.join(__dirname, '../templates/video.art'), { + const renderVideoItem = (node, summary) => + renderVideo({ summary, image: node.display_url, video: { @@ -144,8 +144,8 @@ const renderGuestItems = (items) => { width: node.dimensions.width, }, }); - const renderImages = (node, summary) => - art(path.join(__dirname, '../templates/images.art'), { + const renderImagesItem = (node, summary) => + renderImages({ summary, images: [{ url: node.display_url, height: node.dimensions.height, width: node.dimensions.width }], }); @@ -164,9 +164,9 @@ const renderGuestItems = (items) => { const _type = node.__typename; switch (_type) { case 'GraphVideo': - return renderVideo(node, i === 0 ? summary : ''); + return renderVideoItem(node, i === 0 ? summary : ''); case 'GraphImage': - return renderImages(node, i === 0 ? summary : ''); + return renderImagesItem(node, i === 0 ? summary : ''); default: throw new Error(`Instagram: Unhandled carousel type: ${_type}`); } @@ -175,10 +175,10 @@ const renderGuestItems = (items) => { : renderImages(node, summary); break; case 'GraphVideo': - description = renderVideo(node, summary); + description = renderVideoItem(node, summary); break; case 'GraphImage': - description = renderImages(node, summary); + description = renderImagesItem(node, summary); break; default: throw new Error(`Instagram: Unhandled feed type: ${type}`); diff --git a/lib/routes/ipsw.dev/index.ts b/lib/routes/ipsw.dev/index.tsx similarity index 64% rename from lib/routes/ipsw.dev/index.ts rename to lib/routes/ipsw.dev/index.tsx index d499cde88..3a934b787 100644 --- a/lib/routes/ipsw.dev/index.ts +++ b/lib/routes/ipsw.dev/index.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/index/:productID', @@ -47,12 +45,28 @@ async function handler(ctx) { link: `https://ipsw.dev/download/${productID}/${build}`, pubDate: new Date(date).toLocaleDateString(), guid: build, - description: art(path.join(__dirname, 'templates/description.art'), { - version, - build, - date, - size, - }), + description: renderToString( + + + + + + + + + + + + + + + + + + + +
    Version{version}
    Build{build}
    Released{date}
    Size{size}
    + ), }; }); diff --git a/lib/routes/ipsw.dev/templates/description.art b/lib/routes/ipsw.dev/templates/description.art deleted file mode 100644 index c0faef7bf..000000000 --- a/lib/routes/ipsw.dev/templates/description.art +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - -
    Version{{ version }}
    Build{{ build }}
    Released{{ released }}
    Size{{ size }}
    \ No newline at end of file diff --git a/lib/routes/iqilu/program.ts b/lib/routes/iqilu/program.ts index 931cee632..7d307bbb5 100644 --- a/lib/routes/iqilu/program.ts +++ b/lib/routes/iqilu/program.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/v/:category{.+}?', @@ -40,7 +39,7 @@ async function handler(ctx) { return { title: a.prop('title'), link: a.prop('href'), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: { src: image.prop('src'), alt: image.prop('alt'), @@ -68,7 +67,7 @@ async function handler(ctx) { item.enclosure_url = content('#copy_mp4text').prop('value'); item.enclosure_type = item.enclosure_url ? `video/${item.enclosure_url.split(/\./).pop()}` : undefined; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ image: { src: item.itunes_item_image, alt: item.title, diff --git a/lib/routes/iqilu/templates/description.art b/lib/routes/iqilu/templates/description.art deleted file mode 100644 index df4b1523c..000000000 --- a/lib/routes/iqilu/templates/description.art +++ /dev/null @@ -1,24 +0,0 @@ -{{ if image && !video }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if video }} - -{{ /if }} - -{{ if description }} -

    {{ description }}

    -{{ /if }} diff --git a/lib/routes/iqilu/templates/description.tsx b/lib/routes/iqilu/templates/description.tsx new file mode 100644 index 000000000..b4af8c6ec --- /dev/null +++ b/lib/routes/iqilu/templates/description.tsx @@ -0,0 +1,37 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageData = { + src?: string; + alt?: string; +}; + +type VideoData = { + src?: string; + type?: string; +}; + +type DescriptionData = { + image?: ImageData; + video?: VideoData; + description?: string; +}; + +export const renderDescription = ({ image, video, description }: DescriptionData) => + renderToString( + <> + {image?.src && !video ? ( +
    + {image.alt} +
    + ) : null} + {video ? ( + + ) : null} + {description ?

    {description}

    : null} + + ); diff --git a/lib/routes/iqiyi/album.ts b/lib/routes/iqiyi/album.tsx similarity index 93% rename from lib/routes/iqiyi/album.ts rename to lib/routes/iqiyi/album.tsx index b3f6393af..319f78b0a 100644 --- a/lib/routes/iqiyi/album.ts +++ b/lib/routes/iqiyi/album.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/album/:id', @@ -69,9 +67,7 @@ async function handler(ctx) { const items = epgs.map((item) => ({ title: item.name, - description: art(path.join(__dirname, 'templates/album.art'), { - item, - }), + description: renderToString(), link: `https://www.iq.com/play/${item.playLocSuffix}`, pubDate: parseDate(item.initIssueTime), })); diff --git a/lib/routes/iqiyi/templates/album.art b/lib/routes/iqiyi/templates/album.art deleted file mode 100644 index 14b1e8dd5..000000000 --- a/lib/routes/iqiyi/templates/album.art +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/routes/iresearch/report.ts b/lib/routes/iresearch/report.ts index f547d5a1c..329c44649 100644 --- a/lib/routes/iresearch/report.ts +++ b/lib/routes/iresearch/report.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Context } from 'hono'; import type { Data, DataItem, Route } from '@/types'; @@ -7,9 +5,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + const types = { 1: { label: '最新报告', @@ -246,7 +245,7 @@ export const handler = async (ctx: Context): Promise => { })(); const images: string[] = [item.BigImg, item.SmallImg, item.reportpic].filter(Boolean) as string[]; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: images.map((src) => ({ src, alt: title, @@ -350,7 +349,7 @@ export const handler = async (ctx: Context): Promise => { (_, index) => `${imageBaseUrl}/${typeObj.imageSlug}/${item.detailId}/${index + 1}.jpg` ), ].filter(Boolean) as string[]; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: images.map((src) => ({ src, alt: title, diff --git a/lib/routes/iresearch/templates/description.art b/lib/routes/iresearch/templates/description.art deleted file mode 100644 index 3b8972571..000000000 --- a/lib/routes/iresearch/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} - -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/iresearch/templates/description.tsx b/lib/routes/iresearch/templates/description.tsx new file mode 100644 index 000000000..a09691be5 --- /dev/null +++ b/lib/routes/iresearch/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + intro?: string; + description?: string; + images?: DescriptionImage[]; +}; + +export const renderDescription = ({ intro, description, images }: DescriptionRenderOptions): string => + renderToString( + <> + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + + ); diff --git a/lib/routes/itch/devlog.ts b/lib/routes/itch/devlog.ts index b82cb7136..3e0c52f22 100644 --- a/lib/routes/itch/devlog.ts +++ b/lib/routes/itch/devlog.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import InvalidParameterError from '@/errors/types/invalid-parameter'; @@ -7,10 +5,11 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { isValidHost } from '@/utils/valid-host'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/devlog/:user/:id', categories: ['game'], @@ -78,7 +77,7 @@ async function handler(ctx) { const info = JSON.parse(content(infoJson).text()); item.author = info.author.name; item.pubDate = info.datePublished; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ images: content('.post_image') .toArray() .map((e) => content(e).attr('src')), diff --git a/lib/routes/itch/index.ts b/lib/routes/itch/index.ts index 4dc4bbc71..9ca5eb3ca 100644 --- a/lib/routes/itch/index.ts +++ b/lib/routes/itch/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '*', @@ -48,7 +47,7 @@ async function handler(ctx) { const content = load(detailResponse.data); item.author = content('title').text().split('by ').pop(); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ images: content('.screenshot') .toArray() .map((i) => content(i).attr('src')), diff --git a/lib/routes/itch/templates/description.art b/lib/routes/itch/templates/description.art deleted file mode 100644 index a71f634fa..000000000 --- a/lib/routes/itch/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ each images image }} - -{{ /each }} -{{@ description }} \ No newline at end of file diff --git a/lib/routes/itch/templates/description.tsx b/lib/routes/itch/templates/description.tsx new file mode 100644 index 000000000..7ae6e1763 --- /dev/null +++ b/lib/routes/itch/templates/description.tsx @@ -0,0 +1,17 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + images?: string[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionData): string => + renderToString( + <> + {images?.map((image) => ( + + ))} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/ithome/templates/description.art b/lib/routes/ithome/templates/description.art deleted file mode 100644 index 0a7f83a6f..000000000 --- a/lib/routes/ithome/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ithome/zt.ts b/lib/routes/ithome/zt.tsx similarity index 89% rename from lib/routes/ithome/zt.ts rename to lib/routes/ithome/zt.tsx index 68a44745c..766715a11 100644 --- a/lib/routes/ithome/zt.ts +++ b/lib/routes/ithome/zt.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const handler = async (ctx) => { @@ -66,16 +64,8 @@ export const handler = async (ctx) => { const src = el.prop('data-original'); if (src) { - el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { - images: [ - { - src, - alt: el.prop('alt'), - }, - ], - }) - ); + const alt = el.prop('alt'); + el.replaceWith(renderToString(
    {alt ? {alt} : }
    )); } }); diff --git a/lib/routes/iwara/subscriptions.ts b/lib/routes/iwara/subscriptions.ts index d45e4a037..5ad438728 100644 --- a/lib/routes/iwara/subscriptions.ts +++ b/lib/routes/iwara/subscriptions.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import MarkdownIt from 'markdown-it'; import { config } from '@/config'; @@ -8,7 +6,8 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderSubscriptionImages } from './templates/subscriptions'; const md = MarkdownIt({ html: true, @@ -150,9 +149,7 @@ async function handler() { const items = await Promise.all( list.map((item) => cache.tryGet(item.link, async () => { - let description = art(path.join(__dirname, 'templates/subscriptions.art'), { - images: [item.imageUrl], - }); + let description = renderSubscriptionImages([item.imageUrl]); if (item.private === true) { description += 'private'; @@ -173,9 +170,7 @@ async function handler() { }, }); - description = art(path.join(__dirname, 'templates/subscriptions.art'), { - images: response.files ? response.files.filter((f) => f.type === 'image')?.map((f) => `https://i.iwara.tv/image/original/${f.id}/${f.name}`) : [item.imageUrl], - }); + description = renderSubscriptionImages(response.files ? response.files.filter((f) => f.type === 'image')?.map((f) => `https://i.iwara.tv/image/original/${f.id}/${f.name}`) : [item.imageUrl]); const body = response.body ? md.render(response.body) : ''; description += body; diff --git a/lib/routes/iwara/templates/subscriptions.art b/lib/routes/iwara/templates/subscriptions.art deleted file mode 100644 index 220c2d774..000000000 --- a/lib/routes/iwara/templates/subscriptions.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each images image }} -
    -{{ /each }} diff --git a/lib/routes/iwara/templates/subscriptions.tsx b/lib/routes/iwara/templates/subscriptions.tsx new file mode 100644 index 000000000..3f13eae68 --- /dev/null +++ b/lib/routes/iwara/templates/subscriptions.tsx @@ -0,0 +1,16 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +export const renderSubscriptionImages = (images: Array) => { + const filteredImages = images.filter(Boolean); + + return renderToString( + <> + {filteredImages.map((image) => ( + <> + +
    + + ))} + + ); +}; diff --git a/lib/routes/ixigua/templates/userVideo.art b/lib/routes/ixigua/templates/userVideo.art deleted file mode 100644 index fb0830b0c..000000000 --- a/lib/routes/ixigua/templates/userVideo.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if !disableEmbed }} -
    -{{ /if }} -

    {{ i.abstract }}

    - diff --git a/lib/routes/ixigua/user-video.ts b/lib/routes/ixigua/user-video.tsx similarity index 76% rename from lib/routes/ixigua/user-video.ts rename to lib/routes/ixigua/user-video.tsx index 86af9560f..6e87671c0 100644 --- a/lib/routes/ixigua/user-video.ts +++ b/lib/routes/ixigua/user-video.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const host = 'https://www.ixigua.com'; @@ -63,13 +61,23 @@ async function handler(ctx) { description: userInfo.introduce, item: videoInfos.map((i) => ({ title: i.title, - description: art(path.join(__dirname, 'templates/userVideo.art'), { - i, - disableEmbed, - }), + description: renderToString(), link: `${host}/${i.groupId}`, pubDate: parseDate(i.publishTime * 1000), author: userInfo.name, })), }; } + +const IxiguaVideoDescription = ({ i, disableEmbed }: { i: any; disableEmbed?: string }) => ( + <> + {disableEmbed ? null : ( + <> + +
    + + )} + +

    {i.abstract}

    + +); diff --git a/lib/routes/jandan/templates/description.art b/lib/routes/jandan/templates/description.art deleted file mode 100644 index fd8506592..000000000 --- a/lib/routes/jandan/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -
    -

    {{ summary }}

    -
    - \ No newline at end of file diff --git a/lib/routes/jandan/templates/description.tsx b/lib/routes/jandan/templates/description.tsx new file mode 100644 index 000000000..c51018f70 --- /dev/null +++ b/lib/routes/jandan/templates/description.tsx @@ -0,0 +1,17 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + summary?: string; + image?: string; +}; + +const JandanDescription = ({ summary, image }: DescriptionData) => ( + <> +
    +

    {summary}

    +
    + + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/japanpost/templates/track_item_desc.art b/lib/routes/japanpost/templates/track_item_desc.art deleted file mode 100644 index 4b44d5d80..000000000 --- a/lib/routes/japanpost/templates/track_item_desc.art +++ /dev/null @@ -1,14 +0,0 @@ -{{ packageStatus }}
    -{{ if packageTrackRecord }}{{ packageTrackRecord }}
    {{ /if }} -{{ if packageOfficeZipCode }}{{ packageOfficeZipCode }} {{ /if }}{{ if packageOffice }}{{ packageOffice }} {{ /if }}{{ packageRegion }} -{{ if index === 0 }} - {{ if officeItemList }} -
    - {{ each officeItemList }} -
    {{ $value.officeType }} {{@ $value.officeName }} {{ $value.officeTel }} - {{ /each }} - {{ /if }} - {{ if packageService }} -
    {{ serviceText }}{{ packageService }} - {{ /if }} -{{ /if }} diff --git a/lib/routes/japanpost/track.ts b/lib/routes/japanpost/track.tsx similarity index 67% rename from lib/routes/japanpost/track.ts rename to lib/routes/japanpost/track.tsx index 0022fcfa3..1ff9bea15 100644 --- a/lib/routes/japanpost/track.ts +++ b/lib/routes/japanpost/track.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import utils from './utils'; @@ -77,17 +76,43 @@ export async function track(ctx) { const packageOfficeZipCode = listOdd.eq(index).find('td').eq(0).text().trim(); const itemTitle = `${packageStatus} ${packageOffice} ${packageRegion}`; const packageTrackRecord = itemTd.eq(2).text().trim(); - const itemDescription = art(path.join(__dirname, 'templates/track_item_desc.art'), { - packageStatus, - packageTrackRecord, - packageOfficeZipCode, - packageOffice, - packageRegion, - index, - officeItemList, - serviceText, - packageService, - }); + const itemDescription = renderToString( + <> + {packageStatus} +
    + {packageTrackRecord ? ( + <> + {packageTrackRecord} +
    + + ) : null} + {packageOfficeZipCode ? `${packageOfficeZipCode} ` : ''} + {packageOffice ? `${packageOffice} ` : ''} + {packageRegion} + {index === 0 ? ( + <> + {officeItemList?.length ? ( + <> +
    + {officeItemList.map((officeItem) => ( + <> +
    + {officeItem.officeType} {raw(officeItem.officeName)} {officeItem.officeTel} + + ))} + + ) : null} + {packageService ? ( + <> +
    + {serviceText} + {packageService} + + ) : null} + + ) : null} + + ); const itemPubDateText = itemTd.eq(0).text().trim(); const itemGuid = utils.generateGuid(reqCode + itemTitle + itemDescription + itemPubDateText); diff --git a/lib/routes/javbus/index.ts b/lib/routes/javbus/index.tsx similarity index 84% rename from lib/routes/javbus/index.ts rename to lib/routes/javbus/index.tsx index 2e03ee196..2ad806876 100644 --- a/lib/routes/javbus/index.ts +++ b/lib/routes/javbus/index.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; @@ -10,13 +10,49 @@ import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const toSize = (raw) => { const matches = raw.match(/(\d+(\.\d+)?)(\w+)/); return matches[3] === 'GB' ? matches[1] * 1024 : matches[1]; }; +const renderDescription = ({ info, videoSrc, videoPreview, magnets, thumbs }) => + renderToString( + <> + {info ? raw(info) : null} +
    + {videoSrc ? 觀看完整影片 : null} +
    + {videoPreview ? ( + + ) : null} +
    +

    磁力連結投稿

    + + + + + + + {magnets?.map((magnet) => ( + + + + + + ))} +
    磁力名稱檔案大小分享日期
    + {magnet.title} + {magnet.size}{magnet.date}
    +

    樣品圖像

    + {thumbs?.map((thumb) => ( + + ))} + + ); + const allowDomain = new Set(['javbus.com', 'javbus.org', 'javsee.icu', 'javsee.one']); export const route: Route = { @@ -188,7 +224,7 @@ async function handler(ctx) { item.author = cacheIn.author; item.title = cacheIn.title; item.category = cacheIn.category; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ info: cacheIn.info, thumbs: cacheIn.thumbs, magnets, diff --git a/lib/routes/javbus/templates/description.art b/lib/routes/javbus/templates/description.art deleted file mode 100644 index 8f05fc28b..000000000 --- a/lib/routes/javbus/templates/description.art +++ /dev/null @@ -1,29 +0,0 @@ -{{@ info }} -
    -{{if videoSrc}} -觀看完整影片 -{{ /if }} -
    -{{if videoPreview}} - -{{/if}} -
    -

    磁力連結投稿

    - - - - - - -{{each magnets magnet}} - - - - - -{{/each}} -
    磁力名稱檔案大小分享日期
    {{ magnet.title }}{{ magnet.size }}{{ magnet.date }}
    -

    樣品圖像

    -{{each thumbs}} - -{{/each}} \ No newline at end of file diff --git a/lib/routes/javlibrary/templates/description.art b/lib/routes/javlibrary/templates/description.art deleted file mode 100644 index dfd46e4a7..000000000 --- a/lib/routes/javlibrary/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ - - -{{@ info }} - -{{if comment}} -
    -{{@ comment }} -
    -{{/if}} - -{{each videos}} - -{{/each}} - -{{each thumbs}} - -{{/each}} \ No newline at end of file diff --git a/lib/routes/javlibrary/utils.ts b/lib/routes/javlibrary/utils.tsx similarity index 79% rename from lib/routes/javlibrary/utils.ts rename to lib/routes/javlibrary/utils.tsx index fbd5d8a76..e95f7ade6 100644 --- a/lib/routes/javlibrary/utils.ts +++ b/lib/routes/javlibrary/utils.tsx @@ -1,16 +1,37 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://www.javlibrary.com'; const defaultMode = '1'; const defaultGenre = 'amjq'; const defaultMaker = 'arlq'; const defaultLanguage = 'ja'; +const renderDescription = ({ cover, info, comment, videos, thumbs }) => + renderToString( + <> + + {info ? <>{raw(info)} : null} + {comment ? ( + <> +
    + {raw(comment)} +
    + + ) : null} + {videos?.length + ? videos.map((video) => ( + + )) + : null} + {thumbs?.length ? thumbs.map((thumb) => ) : null} + + ); const ProcessItems = async (language, currentUrl, tryGet) => { const response = await got({ method: 'get', @@ -67,7 +88,7 @@ const ProcessItems = async (language, currentUrl, tryGet) => { .toArray() .map((tag) => content(tag).text()) .filter((tag) => tag !== ''); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ cover: content('#video_jacket_img').attr('src'), info: content('#video_info').html().replaceAll('span>'), diff --git a/lib/routes/javtiful/templates/description.art b/lib/routes/javtiful/templates/description.art deleted file mode 100644 index 393036a44..000000000 --- a/lib/routes/javtiful/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if previewVideo }} - -{{else if poster}} - -{{ /if }} diff --git a/lib/routes/javtiful/utils.ts b/lib/routes/javtiful/utils.ts deleted file mode 100644 index 01c1ec4dd..000000000 --- a/lib/routes/javtiful/utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -import path from 'node:path'; - -import { parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -const renderDescription = (data) => art(path.join(__dirname, 'templates/description.art'), data); - -export const parseItems = (e) => ({ - title: e.find('a > img').attr('alt')!, - link: e.find('a').attr('href')!, - description: renderDescription({ - poster: e.find('a > img').data('src'), - previewVideo: e.find('a > span').data('trailer'), - }), - pubDate: parseRelativeDate(e.find('.video-addtime').text()), -}); diff --git a/lib/routes/javtiful/utils.tsx b/lib/routes/javtiful/utils.tsx new file mode 100644 index 000000000..52353e4f5 --- /dev/null +++ b/lib/routes/javtiful/utils.tsx @@ -0,0 +1,26 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +import { parseRelativeDate } from '@/utils/parse-date'; + +const renderDescription = (data): string => + renderToString( + <> + {data.previewVideo ? ( + + ) : data.poster ? ( + + ) : null} + + ); + +export const parseItems = (e) => ({ + title: e.find('a > img').attr('alt')!, + link: e.find('a').attr('href')!, + description: renderDescription({ + poster: e.find('a > img').data('src'), + previewVideo: e.find('a > span').data('trailer'), + }), + pubDate: parseRelativeDate(e.find('.video-addtime').text()), +}); diff --git a/lib/routes/javtrailers/templates/description.art b/lib/routes/javtrailers/templates/description.art deleted file mode 100644 index 36b47a102..000000000 --- a/lib/routes/javtrailers/templates/description.art +++ /dev/null @@ -1,31 +0,0 @@ -{{ if videoInfo.image }} -
    -{{ /if }} - -{{ if videoInfo.dvdId }}DVD ID: {{ videoInfo.dvdId }}
    {{ /if }} -{{ if videoInfo.contentId }}Content ID: {{ videoInfo.contentId }}
    {{ /if }} -{{ if videoInfo.releaseDate }}Release Date: {{ videoInfo.releaseDate }}
    {{ /if }} -{{ if videoInfo.duration }}Duration: {{ videoInfo.duration }} mins
    {{ /if }} -{{ if videoInfo.director }}Director: {{ videoInfo.director }} {{ videoInfo.jpDirector }}
    {{ /if }} -{{ if videoInfo.studio }}Studio: {{ videoInfo.studio.name }}
    {{ /if }} -{{ if videoInfo.categories }} - Categories: - {{ each videoInfo.categories c }} - {{ c.name }}, - {{ /each }} -
    -{{ /if }} -{{ if videoInfo.casts }} - Cast(s): - {{ each videoInfo.casts c }} - {{ c.name }} {{ c.jpName }} - {{ /each }} -
    -{{ /if }} - - -{{ if videoInfo.gallery }} - {{ each videoInfo.gallery g }} -
    - {{ /each }} -{{ /if }} diff --git a/lib/routes/javtrailers/templates/description.tsx b/lib/routes/javtrailers/templates/description.tsx new file mode 100644 index 000000000..0f38ebf00 --- /dev/null +++ b/lib/routes/javtrailers/templates/description.tsx @@ -0,0 +1,78 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +export const renderDescription = (videoInfo) => + renderToString( + <> + {videoInfo.image ? ( + <> + +
    + + ) : null} + {videoInfo.dvdId ? ( + <> + DVD ID: {videoInfo.dvdId} +
    + + ) : null} + {videoInfo.contentId ? ( + <> + Content ID: {videoInfo.contentId} +
    + + ) : null} + {videoInfo.releaseDate ? ( + <> + Release Date: {videoInfo.releaseDate} +
    + + ) : null} + {videoInfo.duration ? ( + <> + Duration: {videoInfo.duration} mins +
    + + ) : null} + {videoInfo.director ? ( + <> + Director: {videoInfo.director} {videoInfo.jpDirector} +
    + + ) : null} + {videoInfo.studio ? ( + <> + Studio: {videoInfo.studio.name} +
    + + ) : null} + {videoInfo.categories?.length ? ( + <> + Categories: + {videoInfo.categories.map((category) => ( + <> {category.name}, + ))} +
    + + ) : null} + {videoInfo.casts?.length ? ( + <> + Cast(s): + {videoInfo.casts.map((cast) => ( + <> + {' '} + {cast.name} {cast.jpName} + + ))} +
    + + ) : null} + {videoInfo.gallery?.length + ? videoInfo.gallery.map((image) => ( + <> + +
    + + )) + : null} + + ); diff --git a/lib/routes/javtrailers/utils.ts b/lib/routes/javtrailers/utils.ts index 528302622..a33b73ef7 100644 --- a/lib/routes/javtrailers/utils.ts +++ b/lib/routes/javtrailers/utils.ts @@ -1,9 +1,7 @@ -import path from 'node:path'; - import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; +import { renderDescription } from './templates/description'; import type { Video } from './types'; export const baseUrl = 'https://javtrailers.com'; @@ -37,9 +35,7 @@ export const getItem = async (item) => { const videoInfo: Video = response.video; videoInfo.gallery = hdGallery(videoInfo.gallery); - item.description = art(path.join(__dirname, 'templates/description.art'), { - videoInfo, - }); + item.description = renderDescription(videoInfo); item.author = videoInfo.casts.map((cast) => `${cast.name} ${cast.jpName}`).join(', '); item.category = videoInfo.categories.map((category) => `${category.name}/${category.jpName}/${category.zhName}`); diff --git a/lib/routes/jd/price.ts b/lib/routes/jd/price.tsx similarity index 74% rename from lib/routes/jd/price.ts rename to lib/routes/jd/price.tsx index aab4545cf..7c8a3d62a 100644 --- a/lib/routes/jd/price.ts +++ b/lib/routes/jd/price.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/price/:id', @@ -54,11 +53,19 @@ async function handler(ctx) { guid: data.p, title: data.p, link: currentUrl, - description: art(path.join(__dirname, 'templates/description.art'), { - p: data.p, - op: data.op, - m: data.m, - }), + description: renderToString( + <> +

    + 目前价格:{data.p} +

    +

    + 指导价:{data.op} +

    +

    + 最高价:{data.m} +

    + + ), }, ], }; diff --git a/lib/routes/jd/templates/description.art b/lib/routes/jd/templates/description.art deleted file mode 100644 index a99951bbd..000000000 --- a/lib/routes/jd/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -

    目前价格:{{ p }}

    -

    指导价:{{ op }}

    -

    最高价:{{ m }}

    \ No newline at end of file diff --git a/lib/routes/jiemian/common.ts b/lib/routes/jiemian/common.tsx similarity index 76% rename from lib/routes/jiemian/common.ts rename to lib/routes/jiemian/common.tsx index d34a0208c..bb18d2540 100644 --- a/lib/routes/jiemian/common.ts +++ b/lib/routes/jiemian/common.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx): Promise => { const { category = '' } = ctx.req.param(); @@ -46,7 +45,7 @@ export const handler = async (ctx): Promise => { const video = content('#video-player').first(); item.title = content('div.article-header h1').eq(0).text(); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ image: image ? { src: image.prop('src'), @@ -100,3 +99,38 @@ export const handler = async (ctx): Promise => { author: titleSplits.pop(), }; }; + +const renderDescription = ({ + image, + intro, + video, + description, +}: { + image?: { src?: string; alt?: string; width?: string; height?: string }; + intro?: string; + video?: { src?: string; poster?: string; type?: string }; + description?: string; +}): string => { + const imageAlt = image?.height ?? image?.width ?? image?.alt; + const videoPoster = video?.poster ?? image?.src; + + return renderToString( + <> + {!video?.src && image?.src ? ( +
    + {imageAlt} +
    + ) : null} + {intro ?

    {intro}

    : null} + {video?.src ? ( + + ) : null} + {description ? <>{raw(description)} : null} + + ); +}; diff --git a/lib/routes/jiemian/templates/description.art b/lib/routes/jiemian/templates/description.art deleted file mode 100644 index 83b5eb0b7..000000000 --- a/lib/routes/jiemian/templates/description.art +++ /dev/null @@ -1,39 +0,0 @@ -{{ if !video?.src && image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if intro }} -

    {{ intro }}

    -{{ /if }} - -{{ if video?.src }} - -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/jimmyspa/books.ts b/lib/routes/jimmyspa/books.ts index eece3e8e7..b0476a631 100644 --- a/lib/routes/jimmyspa/books.ts +++ b/lib/routes/jimmyspa/books.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; @@ -7,7 +5,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/books/:language', @@ -74,7 +73,7 @@ async function handler(ctx) { const publishDateMatch = bookInfoWrap.match(/(首次出版|First Published|初版)<\/span>\s*([^<]+)<\/span>/); const publishDate = publishDateMatch ? parseDate(publishDateMatch[2] + '-02') : ''; - const renderedDescription = art(path.join(__dirname, 'templates/description.art'), { + const renderedDescription = renderDescription({ images: bookImageUrl ? [ { diff --git a/lib/routes/jimmyspa/news.ts b/lib/routes/jimmyspa/news.ts index 40c22f409..4022a1c9e 100644 --- a/lib/routes/jimmyspa/news.ts +++ b/lib/routes/jimmyspa/news.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/news/:language', @@ -60,7 +59,7 @@ async function handler(ctx) { const itemdate = $$('a.news_card div.date').html() || ''; const pubDate = convertHtmlDateToStandardFormat(itemdate.toString()); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { diff --git a/lib/routes/jimmyspa/templates/description.art b/lib/routes/jimmyspa/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/jimmyspa/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/jimmyspa/templates/description.tsx b/lib/routes/jimmyspa/templates/description.tsx new file mode 100644 index 000000000..528e99d13 --- /dev/null +++ b/lib/routes/jimmyspa/templates/description.tsx @@ -0,0 +1,27 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + description?: string; +}; + +const Description = ({ images, description }: DescriptionProps) => ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/jin10/category.ts b/lib/routes/jin10/category.ts index fa77035b4..780485c51 100644 --- a/lib/routes/jin10/category.ts +++ b/lib/routes/jin10/category.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - import { config } from '@/config'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/category/:id', categories: ['finance'], @@ -205,10 +204,7 @@ async function handler(ctx) { return { title, - description: art(path.join(__dirname, 'templates/description.art'), { - content, - pic: item.data.pic, - }), + description: renderDescription(content, item.data.pic), pubDate: timezone(parseDate(item.time), 8), guid: `jin10:category:${item.id}`, }; diff --git a/lib/routes/jin10/index.ts b/lib/routes/jin10/index.ts index af613ec21..d4fa8cbd9 100644 --- a/lib/routes/jin10/index.ts +++ b/lib/routes/jin10/index.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - import { config } from '@/config'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/:important?', categories: ['finance'], @@ -69,10 +68,7 @@ async function handler(ctx) { return { title, - description: art(path.join(__dirname, 'templates/description.art'), { - content, - pic: item.data.pic, - }), + description: renderDescription(content, item.data.pic), pubDate: timezone(parseDate(item.time), 8), link: item.data.link, guid: `jin10:index:${item.id}`, diff --git a/lib/routes/jin10/templates/description.art b/lib/routes/jin10/templates/description.art deleted file mode 100644 index 9ab6a3596..000000000 --- a/lib/routes/jin10/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{ if content }}{{@ content }}{{ /if }} -{{ if pic }}
    {{ /if }} diff --git a/lib/routes/jin10/templates/description.tsx b/lib/routes/jin10/templates/description.tsx new file mode 100644 index 000000000..42f45fff5 --- /dev/null +++ b/lib/routes/jin10/templates/description.tsx @@ -0,0 +1,15 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +export const renderDescription = (content?: string, pic?: string) => + renderToString( + <> + {content ? <>{raw(content)} : null} + {pic ? ( + <> +
    + + + ) : null} + + ); diff --git a/lib/routes/jinse/catalogue.ts b/lib/routes/jinse/catalogue.ts index 166fceca3..587e5d836 100644 --- a/lib/routes/jinse/catalogue.ts +++ b/lib/routes/jinse/catalogue.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const categories = { zhengce: '政策', @@ -66,7 +65,7 @@ async function handler(ctx) { let items = response.list.slice(0, limit).map((item) => ({ title: item.title, link: item.extra.topic_url, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: item.extra.thumbnails_pics.length > 0 ? item.extra.thumbnails_pics.map((p) => ({ @@ -94,7 +93,7 @@ async function handler(ctx) { const content = load(detailResponse); - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ description: content('section.js-article-content').html() || content('div.js-article').html(), }); item.category = content('section.js-article-tag_state_1 a span') diff --git a/lib/routes/jinse/lives.ts b/lib/routes/jinse/lives.ts index a6dc2ad13..7390ab188 100644 --- a/lib/routes/jinse/lives.ts +++ b/lib/routes/jinse/lives.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const categories = { 0: '全部', @@ -72,7 +71,7 @@ async function handler(ctx) { .map((item) => ({ title: item.content_prefix, link: new URL(`lives/${item.id}.html`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: item.images?.map((i) => ({ src: i.url.replace(/_[^\W_]+(\.\w+)$/, '_true$1'), diff --git a/lib/routes/jinse/templates/description.art b/lib/routes/jinse/templates/description.art deleted file mode 100644 index 210b63ad2..000000000 --- a/lib/routes/jinse/templates/description.art +++ /dev/null @@ -1,34 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} - -{{ if original?.link }} -

    - {{ original.name }}: - {{ original.link }} -

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/jinse/templates/description.tsx b/lib/routes/jinse/templates/description.tsx new file mode 100644 index 000000000..9d8593e89 --- /dev/null +++ b/lib/routes/jinse/templates/description.tsx @@ -0,0 +1,44 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; + width?: string | number; + height?: string | number; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; + original?: { + name?: string; + link?: string; + }; +}; + +const JinseDescription = ({ images, intro, description, original }: DescriptionData) => ( + <> + {images?.map((image) => { + if (!image?.src) { + return null; + } + const altValue = image.height ?? image.width ?? image.alt; + return ( +
    + {altValue +
    + ); + })} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + {original?.link ? ( +

    + {original.name}: {original.link} +

    + ) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/jinse/timeline.ts b/lib/routes/jinse/timeline.ts index 9fcaf070c..7be784bb2 100644 --- a/lib/routes/jinse/timeline.ts +++ b/lib/routes/jinse/timeline.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; @@ -7,7 +5,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/timeline/:category?', @@ -77,7 +76,7 @@ async function handler(ctx) { return { title: item.title, link: item.jump_url, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: item.cover ? [ { @@ -109,7 +108,7 @@ async function handler(ctx) { const content = load(detailResponse); - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ description: content('section.js-article-content').html() || content('div.js-article').html(), }); item.category = content('section.js-article-tag_state_1 a span') diff --git a/lib/routes/jiuyangongshe/community.ts b/lib/routes/jiuyangongshe/community.tsx similarity index 91% rename from lib/routes/jiuyangongshe/community.ts rename to lib/routes/jiuyangongshe/community.tsx index aaed2a28d..c8dbc44a1 100644 --- a/lib/routes/jiuyangongshe/community.ts +++ b/lib/routes/jiuyangongshe/community.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, Route } from '@/types'; import { ViewType } from '@/types'; import md5 from '@/utils/md5'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; interface User { @@ -93,7 +91,18 @@ interface Community { serverTime: number; } -const render = (data) => art(path.join(__dirname, 'templates/community-description.art'), data); +const render = (data) => + renderToString( + <> + {data.cover ? ( + <> + +
    + + ) : null} + {data.content} + + ); export const route: Route = { path: '/community', diff --git a/lib/routes/jiuyangongshe/templates/community-description.art b/lib/routes/jiuyangongshe/templates/community-description.art deleted file mode 100644 index 6059320b5..000000000 --- a/lib/routes/jiuyangongshe/templates/community-description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ if cover }} -
    -{{ /if }} -{{ content }} diff --git a/lib/routes/jjwxc/author.ts b/lib/routes/jjwxc/author.tsx similarity index 64% rename from lib/routes/jjwxc/author.ts rename to lib/routes/jjwxc/author.tsx index 161715711..b2f6053bc 100644 --- a/lib/routes/jjwxc/author.ts +++ b/lib/routes/jjwxc/author.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -56,13 +54,38 @@ async function handler(ctx) { { title, link: bookUrl, - description: art(path.join(__dirname, 'templates/author.art'), { - bookName, - bookUrl, - bookStatus, - bookWords, - bookUpdatedTime, - }), + description: renderToString( + + + {bookName ? ( + + + + + ) : null} + {bookStatus ? ( + + + + + ) : null} + {bookWords ? ( + + + + + ) : null} + {bookUpdatedTime ? ( + + + + + ) : null} + +
    最近更新作品 + {bookName} +
    作品状态{bookStatus}
    作品字数{bookWords}
    最后更新时间{bookUpdatedTime}
    + ), author, category: [bookStatus], guid: `jjwxc-${id}-${bookId}#${bookWords}`, diff --git a/lib/routes/jjwxc/book.ts b/lib/routes/jjwxc/book.ts index 0e3b76fb0..61d781b0c 100644 --- a/lib/routes/jjwxc/book.ts +++ b/lib/routes/jjwxc/book.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import iconv from 'iconv-lite'; @@ -8,9 +6,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderBookDescription } from './templates/book'; + export const route: Route = { path: '/book/:id?', categories: ['reading'], @@ -69,7 +68,7 @@ async function handler(ctx) { return { title: `${chapterName} ${chapterIntro}`, link: chapterUrl, - description: art(path.join(__dirname, 'templates/book.art'), { + description: renderBookDescription({ chapterId, chapterName, chapterIntro, @@ -103,8 +102,8 @@ async function handler(ctx) { content('span.favorite_novel').parent().remove(); - item.description += art(path.join(__dirname, 'templates/book.art'), { - description: content('div.novelbody').html(), + item.description += renderBookDescription({ + description: content('div.novelbody').html() || undefined, }); } diff --git a/lib/routes/jjwxc/templates/author.art b/lib/routes/jjwxc/templates/author.art deleted file mode 100644 index ae5a659c1..000000000 --- a/lib/routes/jjwxc/templates/author.art +++ /dev/null @@ -1,28 +0,0 @@ - - - {{ if bookName }} - - - - - {{ /if }} - {{ if bookStatus }} - - - - - {{ /if }} - {{ if bookWords }} - - - - - {{ /if }} - {{ if bookUpdatedTime }} - - - - - {{ /if }} - -
    最近更新作品{{ bookName }}
    作品状态{{ bookStatus }}
    作品字数{{ bookWords }}
    最后更新时间{{ bookUpdatedTime }}
    \ No newline at end of file diff --git a/lib/routes/jjwxc/templates/book.art b/lib/routes/jjwxc/templates/book.art deleted file mode 100644 index 5938bd37d..000000000 --- a/lib/routes/jjwxc/templates/book.art +++ /dev/null @@ -1,44 +0,0 @@ -{{ if description }} - {{@ description }} -{{ else }} - - - {{ if chapterId }} - - - - - {{ /if }} - {{ if chapterName }} - - - - - {{ /if }} - {{ if chapterIntro }} - - - - - {{ /if }} - {{ if chapterWords }} - - - - - {{ /if }} - {{ if chapterClicks }} - - - - - {{ /if }} - {{ if chapterUpdatedTime }} - - - - - {{ /if }} - -
    章节{{ chapterId }}
    标题{{ chapterName }}
    内容提要{{ chapterIntro }}
    字数{{ chapterWords }}
    点击{{ chapterClicks }}
    更新时间{{ chapterUpdatedTime }}
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/jjwxc/templates/book.tsx b/lib/routes/jjwxc/templates/book.tsx new file mode 100644 index 000000000..93cddb1b7 --- /dev/null +++ b/lib/routes/jjwxc/templates/book.tsx @@ -0,0 +1,67 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type BookDescriptionProps = { + description?: string; + chapterId?: string; + chapterName?: string; + chapterIntro?: string; + chapterUrl?: string; + chapterWords?: string; + chapterClicks?: string; + chapterUpdatedTime?: string; +}; + +export const renderBookDescription = ({ description, chapterId, chapterName, chapterIntro, chapterUrl, chapterWords, chapterClicks, chapterUpdatedTime }: BookDescriptionProps): string => + renderToString( + description ? ( + <>{raw(description)} + ) : ( + + + {chapterId ? ( + + + + + ) : null} + {chapterName ? ( + + + + + ) : null} + {chapterIntro ? ( + + + + + ) : null} + {chapterWords ? ( + + + + + ) : null} + {chapterClicks ? ( + + + + + ) : null} + {chapterUpdatedTime ? ( + + + + + ) : null} + +
    章节 + {chapterId} +
    标题 + {chapterName} +
    内容提要 + {chapterIntro} +
    字数{chapterWords}
    点击{chapterClicks}
    更新时间{chapterUpdatedTime}
    + ) + ); diff --git a/lib/routes/joins/chinese.ts b/lib/routes/joins/chinese.tsx similarity index 91% rename from lib/routes/joins/chinese.ts rename to lib/routes/joins/chinese.tsx index 330250502..745af34c8 100644 --- a/lib/routes/joins/chinese.ts +++ b/lib/routes/joins/chinese.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const handler = async (ctx) => { @@ -48,23 +47,25 @@ export const handler = async (ctx) => { $$('div.view-copyright, div.ad-template, div.view-editors, div.tag-group').remove(); const title = $$('div.article-head-title, div.viewer-titles').text(); - const description = art(path.join(__dirname, 'templates/description.art'), { - images: - $$('div.photo-box').length === 0 - ? undefined + const description = renderToString( + <> + {$$('div.photo-box').length === 0 + ? null : $$('div.photo-box') .toArray() .map((i) => { const image = $$(i).find('img'); + const src = image.prop('src'); - return image.prop('src') - ? { - src: image.prop('src'), - } - : undefined; - }), - description: $$('div#article-view-content-div').html(), - }); + return src ? ( +
    + +
    + ) : null; + })} + {$$('div#article-view-content-div').html() ? raw($$('div#article-view-content-div').html()) : null} + + ); const image = $$('meta[property="og:image"]').prop('content'); item.title = title; diff --git a/lib/routes/joins/templates/description.art b/lib/routes/joins/templates/description.art deleted file mode 100644 index e8cc00cbc..000000000 --- a/lib/routes/joins/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} diff --git a/lib/routes/joneslanglasalle/index.ts b/lib/routes/joneslanglasalle/index.ts index 21278824b..f08770056 100644 --- a/lib/routes/joneslanglasalle/index.ts +++ b/lib/routes/joneslanglasalle/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const cleanHtml = (html: string, preservedTags: string[]): string => { const $ = load(html); @@ -54,7 +53,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $item.text(); const link: string | undefined = aEl.prop('href'); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ intro: aEl.find('p.ti-teaser').text(), }); @@ -109,7 +108,7 @@ export const handler = async (ctx: Context): Promise => { if (src) { $$el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: [ { src, @@ -139,7 +138,7 @@ export const handler = async (ctx: Context): Promise => { }) .filter((link): link is { url: string; type: string; content_html: string } => true); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ description: cleanHtml($$('div.page-section').eq(1).html() ?? $$('div.copy-block').html() ?? '', ['div.richtext p', 'h3', 'h4', 'h5', 'h6', 'figure', 'img', 'ul', 'li', 'span', 'b']), }); diff --git a/lib/routes/joneslanglasalle/templates/description.art b/lib/routes/joneslanglasalle/templates/description.art deleted file mode 100644 index aced21ab9..000000000 --- a/lib/routes/joneslanglasalle/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if !videos?.[0]?.src && image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/joneslanglasalle/templates/description.tsx b/lib/routes/joneslanglasalle/templates/description.tsx new file mode 100644 index 000000000..7a40f8331 --- /dev/null +++ b/lib/routes/joneslanglasalle/templates/description.tsx @@ -0,0 +1,27 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionVideo = { + src?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + videos?: DescriptionVideo[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, videos, intro, description }: DescriptionData) => + renderToString( + <> + {images?.length ? images.map((image) => (!videos?.[0]?.src && image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/jpxgmn/templates/description.art b/lib/routes/jpxgmn/templates/description.art deleted file mode 100644 index f60deb835..000000000 --- a/lib/routes/jpxgmn/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each images }} - -{{ /each }} diff --git a/lib/routes/jpxgmn/utils.ts b/lib/routes/jpxgmn/utils.tsx similarity index 83% rename from lib/routes/jpxgmn/utils.ts rename to lib/routes/jpxgmn/utils.tsx index 97e370faf..0eee6553e 100644 --- a/lib/routes/jpxgmn/utils.ts +++ b/lib/routes/jpxgmn/utils.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const indexUrl = 'http://mei8.vip/'; @@ -35,9 +33,14 @@ const getArticleDesc = async (articleUrl) => { return getImages(load(pageResponse.data)); }) ); - return art(path.join(__dirname, 'templates/description.art'), { - images: [...images, ...otherImages.flat()], - }); + const allImages = [...images, ...otherImages.flat()]; + return renderToString( + <> + {allImages.map((src) => ( + + ))} + + ); }; export { getArticleDesc, getOriginUrl }; diff --git a/lib/routes/jump/discount.ts b/lib/routes/jump/discount.tsx similarity index 53% rename from lib/routes/jump/discount.ts rename to lib/routes/jump/discount.tsx index 2622c41a3..db3f72e7e 100644 --- a/lib/routes/jump/discount.ts +++ b/lib/routes/jump/discount.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const discountUrl = 'https://switch.jumpvg.com/jump/discount/find4Discount/5/v2'; // const detailUrl = 'https://switch.jumpvg.com/jump/game/detail'; @@ -50,6 +49,67 @@ const filterName = { dl: '独立', }; +const renderDescription = (item) => + renderToString( +
    + + + + + + + {item.subName ? ( + + + + + ) : null} + + + + + {item.lowestPriceCountry ? ( + + + + + ) : null} + + + + + {item.pubDate ? ( + + + + + ) : null} + + + + + + + + + + + + + + + + + {item.mcScore ? ( + + + + + ) : null} +
    名称{item.name}
    别名{item.subName}
    中文支持{item.chinese}
    史低地区{item.lowestPriceCountry}
    史低{item.isLowest ? '是' : '否'}
    发布日期{item.pubDate}
    当前价格¥{item.price}
    原价¥{item.originPrice}
    折扣地区{item.priceCountry}
    折扣{item.cutOff}%
    metacritic评分{item.mcScore}
    +
    + ); + const getDiscountNum = async (platform) => { const response = await got.get(`https://switch.jumpvg.com/jump/platform/order/v2?needCount=1&needFilter=1&version=3`); const data = response.data.data; @@ -147,7 +207,7 @@ async function handler(ctx) { description: 'jump 发现游戏', item: allDiscountItem.map((item) => ({ title: `${item.name}-${item.cutOff}%-¥${item.price}`, - description: art(path.resolve(__dirname, './templates/discount.art'), { item }), + description: renderDescription(item), link: item.banner, guid: `${platform}-${item.oldGameId}-${item.cutOff}`, // 平台-打折id-打折率 })), diff --git a/lib/routes/jump/templates/discount.art b/lib/routes/jump/templates/discount.art deleted file mode 100644 index 919955209..000000000 --- a/lib/routes/jump/templates/discount.art +++ /dev/null @@ -1,111 +0,0 @@ -
    - - - - - - -{{if item.subName}} - - - - -{{/if}} - - - - -{{if item.lowestPriceCountry}} - - - - -{{/if}} -{{if item.isLowest}} - - - - -{{else}} - - - - -{{/if}} -{{if item.pubDate}} - - - - -{{/if}} - - - - - - - - - - - - - - - - -{{if item.mcScore}} - - - - -{{/if}} - \ No newline at end of file diff --git a/lib/routes/kadokawa/blog.ts b/lib/routes/kadokawa/blog.ts index 33b35ba04..a2b95da2c 100644 --- a/lib/routes/kadokawa/blog.ts +++ b/lib/routes/kadokawa/blog.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit'), 10) : 10; @@ -28,7 +27,7 @@ export const handler = async (ctx) => { const image = item.find('div.List-item-excerpt img').prop('src')?.split(/\?/)[0] ?? undefined; const title = item.find('h2.List-item-title').text(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ images: image ? [ { @@ -66,7 +65,7 @@ export const handler = async (ctx) => { const $$ = load(detailResponse); const title = $$('h1.Post-title').text().trim(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ description: $$('div.Post-content').html(), }); const image = $$('meta[property="og:image"]').prop('content')?.split(/\?/)[0] ?? undefined; diff --git a/lib/routes/kadokawa/templates/description.art b/lib/routes/kadokawa/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/kadokawa/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/kadokawa/templates/description.tsx b/lib/routes/kadokawa/templates/description.tsx new file mode 100644 index 000000000..8168cc41b --- /dev/null +++ b/lib/routes/kadokawa/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/kamen-rider-official/news.ts b/lib/routes/kamen-rider-official/news.ts index d2222b1c1..20ea40e73 100644 --- a/lib/routes/kamen-rider-official/news.ts +++ b/lib/routes/kamen-rider-official/news.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/news/:category?', @@ -87,14 +86,14 @@ async function handler(ctx) { let items = response.news_articles.slice(0, limit).map((item) => ({ title: item.list_title, link: new URL(item.path, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.list_image_path + description: renderDescription( + item.list_image_path ? { src: new URL(item.list_image_path, rootUrl).href, alt: item.list_title, } - : undefined, - }), + : undefined + ), author: item.author, category: [item.category_name, item.category_2_name].filter(Boolean), guid: `kamen-rider-official-${item.id}`, @@ -114,10 +113,8 @@ async function handler(ctx) { content('img').each(function () { content(this).replaceWith( - art(path.join(__dirname, 'templates/description.art'), { - image: { - src: content(this).prop('src'), - }, + renderDescription({ + src: content(this).prop('src'), }) ); }); diff --git a/lib/routes/kamen-rider-official/templates/description.art b/lib/routes/kamen-rider-official/templates/description.art deleted file mode 100644 index a56b27f6e..000000000 --- a/lib/routes/kamen-rider-official/templates/description.art +++ /dev/null @@ -1,10 +0,0 @@ -{{ if image }} -
    - {{ image.alt }} -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/kamen-rider-official/templates/description.tsx b/lib/routes/kamen-rider-official/templates/description.tsx new file mode 100644 index 000000000..461a920ee --- /dev/null +++ b/lib/routes/kamen-rider-official/templates/description.tsx @@ -0,0 +1,17 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +export const renderDescription = (image?: DescriptionImage): string => + renderToString( + <> + {image?.src ? ( +
    + {image.alt +
    + ) : null} + + ); diff --git a/lib/routes/kantarworldpanel/index.ts b/lib/routes/kantarworldpanel/index.tsx similarity index 84% rename from lib/routes/kantarworldpanel/index.ts rename to lib/routes/kantarworldpanel/index.tsx index aea7380b7..afd417183 100644 --- a/lib/routes/kantarworldpanel/index.ts +++ b/lib/routes/kantarworldpanel/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:region?/:category{.+}?', @@ -41,18 +40,21 @@ async function handler(ctx) { let link = a.prop('href'); link = link === '#' ? currentUrl : link; + const description = item.find('p.gowhite').text(); + const imageSrc = image.prop('src'); return { title, link, - description: art(path.join(__dirname, 'templates/description.art'), { - description: item.find('p.gowhite').text(), - image: image.prop('src') - ? { - src: image.prop('src'), - alt: image.prop('alt'), - } - : undefined, - }), + description: renderToString( + <> + {description ? raw(description) : null} + {imageSrc ? ( +
    + +
    + ) : null} + + ), guid: link.startsWith(rootUrl) ? `${link}#${title}` : link, pubDate: parseDate(item.find('p.medGrey').text(), 'DD/MM/YYYY'), }; diff --git a/lib/routes/kantarworldpanel/templates/description.art b/lib/routes/kantarworldpanel/templates/description.art deleted file mode 100644 index 67f879084..000000000 --- a/lib/routes/kantarworldpanel/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if description }} - {{@ description }} -{{ /if }} - -{{ if image }} -
    - -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/kcna/news.ts b/lib/routes/kcna/news.tsx similarity index 87% rename from lib/routes/kcna/news.ts rename to lib/routes/kcna/news.tsx index d56b22ab3..6f367206d 100644 --- a/lib/routes/kcna/news.ts +++ b/lib/routes/kcna/news.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import pMap from 'p-map'; import sanitizeHtml from 'sanitize-html'; @@ -8,7 +8,6 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { fetchPhoto, fetchVideo, fixDesc } from './utils'; @@ -111,7 +110,23 @@ async function handler(ctx) { }) ); - item.description = art(path.join(__dirname, 'templates/news.art'), { description, photo, video }); + item.description = renderToString( + <> + {description ? raw(description) : null} + {photo ? ( + <> +
    + {raw(photo)} + + ) : null} + {video ? ( + <> +
    + {raw(video)} + + ) : null} + + ); return item; }), diff --git a/lib/routes/kcna/templates/news.art b/lib/routes/kcna/templates/news.art deleted file mode 100644 index 28ac97d87..000000000 --- a/lib/routes/kcna/templates/news.art +++ /dev/null @@ -1,9 +0,0 @@ -{{@ description }} -{{ if photo }} -
    -{{@ photo }} -{{ /if }} -{{ if video }} -
    -{{@ video }} -{{ /if }} diff --git a/lib/routes/keep/templates/user.art b/lib/routes/keep/templates/user.art deleted file mode 100644 index c5c30c4cf..000000000 --- a/lib/routes/keep/templates/user.art +++ /dev/null @@ -1,18 +0,0 @@ -项目: -{{ if item.meta.name === item.meta.workoutName }} - {{ item.meta.name }} -{{ else }} - {{ item.meta.name }} - {{ item.meta.workoutName }} -{{ /if }} -
    -时长:{{ minute }}分{{ second }}秒 -{{ if item.content }} -
    - 备注:{{ item.content }} -{{ /if }} -{{ if images }} -
    - {{ each images image }} - - {{ /each }} -{{ /if }} diff --git a/lib/routes/keep/user.ts b/lib/routes/keep/user.tsx similarity index 66% rename from lib/routes/keep/user.ts rename to lib/routes/keep/user.tsx index 9255155fa..4341b1092 100644 --- a/lib/routes/keep/user.ts +++ b/lib/routes/keep/user.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const route: Route = { path: '/user/:id', @@ -57,12 +56,7 @@ async function handler(ctx) { pubDate: item.created, link: `https://show.gotokeep.com/entries/${item.id}`, author: item.author.username, - description: art(path.join(__dirname, 'templates/user.art'), { - item, - minute, - second, - images, - }), + description: renderDescription(item, minute, second, images), }; }) ); @@ -74,3 +68,28 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (item: any, minute: number, second: number, images: string[]) => renderToString(); + +const KeepDescription = ({ item, minute, second, images }: { item: any; minute: number; second: number; images: string[] }) => ( + <> + 项目: + {item.meta.name === item.meta.workoutName ? item.meta.name : `${item.meta.name} - ${item.meta.workoutName}`} +
    + 时长:{minute}分{second}秒 + {item.content ? ( + <> +
    + 备注:{item.content} + + ) : null} + {images ? ( + <> +
    + {images.map((image) => ( + + ))} + + ) : null} + +); diff --git a/lib/routes/kemono/index.ts b/lib/routes/kemono/index.tsx similarity index 79% rename from lib/routes/kemono/index.ts rename to lib/routes/kemono/index.tsx index 81625c629..e6571ad73 100644 --- a/lib/routes/kemono/index.ts +++ b/lib/routes/kemono/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { KEMONO_API_URL, KEMONO_ROOT_URL, MIME_TYPE_MAP } from './const'; import type { DiscordMessage, KemonoFile, KemonoPost } from './types'; @@ -191,6 +189,79 @@ function generateEnclosureInfo(htmlContent: string): { enclosure_url?: string; e return enclosureInfo; } +const renderDiscordMessage = (message: DiscordMessage) => + renderToString( + <> + {message.content ?

    {message.content}

    : null} + {message.attachments?.map((attachment) => ( + + ))} + {message.embeds?.map((embed) => { + if (embed.type === 'image') { + return ; + } + if (embed.type === 'link') { + return ( + <> + {embed.thumbnail ? ( + + + + ) : null} + {embed.title} + {embed.description ?

    {embed.description}

    : null} + + ); + } + return null; + })} + + ); + +const renderPostFiles = (post: KemonoPost & { files?: KemonoFile[] }) => + renderToString( + <> + {post.files?.map((file) => { + const extension = file.extension; + const typeSuffix = file.extention ?? ''; + + if (['jpg', 'png', 'webp', 'jpeg', 'jfif'].includes(extension)) { + return ; + } + if (['m4a', 'mp3', 'ogg'].includes(extension)) { + return ( + + ); + } + if (['mp4', 'webm'].includes(extension)) { + return ( + + ); + } + return {file.name}; + })} + {post.embed ? ( + post.embed.type === 'image' ? ( + + ) : post.embed.type === 'link' ? ( + <> + {post.embed.thumbnail ? ( + + + + ) : null} + {post.embed.title} + {post.embed.description ?

    {post.embed.description}

    : null} + + ) : null + ) : null} + + ); + async function processDiscordMessages(channels: any[], limit: number) { const items = await Promise.all( channels.map((channel) => @@ -207,7 +278,7 @@ async function processDiscordMessages(channels: any[], limit: number) { .slice(0, limit) .map((message: DiscordMessage) => ({ title: message.content || 'Discord Message', - description: art(path.join(__dirname, 'templates/discord.art'), { i: message }), + description: renderDiscordMessage(message), author: `${message.author.username}#${message.author.discriminator}`, pubDate: parseDate(message.published), category: channel.name, @@ -262,7 +333,7 @@ function processPosts(posts: KemonoPost[], authorName: string, limit: number) { const files = processPostFiles(post); const postWithFiles = { ...post, files }; - const filesHtml = art(path.join(__dirname, 'templates/source.art'), { i: postWithFiles }); + const filesHtml = renderPostFiles(postWithFiles); let description = post.content ? `
    ${post.content}
    ` : ''; const $ = load(description); diff --git a/lib/routes/kemono/templates/discord.art b/lib/routes/kemono/templates/discord.art deleted file mode 100644 index c7da459b3..000000000 --- a/lib/routes/kemono/templates/discord.art +++ /dev/null @@ -1,22 +0,0 @@ -{{ if i.content }} -

    {{ i.content }}

    -{{ /if }} - -{{ if i.attachments }} - {{ each i.attachments a }} - - {{ /each }} -{{ /if }} - -{{ if i.embeds }} - {{ each i.embeds e }} - {{ if e.type === 'image' }} - - {{ else if e.type === 'link' }} - {{ if e.thumbnail }} - - {{ /if }} - {{ e.title }}{{ if e.description }}

    {{ e.description }}

    {{ /if }} - {{ /if }} - {{ /each }} -{{ /if }} diff --git a/lib/routes/kemono/templates/source.art b/lib/routes/kemono/templates/source.art deleted file mode 100644 index e96e9049f..000000000 --- a/lib/routes/kemono/templates/source.art +++ /dev/null @@ -1,24 +0,0 @@ -{{ if i.files }} - {{ each i.files file }} - {{ if file.extension === 'jpg' || file.extension === 'png' || file.extension === 'webp' || file.extension === 'jpeg' || file.extension === 'jfif' }} - - {{ else if file.extension === 'm4a' || file.extension === 'mp3' || file.extension === 'ogg' }} - - {{ else if file.extension === 'mp4' || file.extension === 'webm' }} - - {{ else }} - {{file.name}} - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if i.embed }} - {{ if i.embed.type === 'image' }} - - {{ else if i.embed.type === 'link' }} - {{ if i.embed.thumbnail }} - - {{ /if }} - {{ i.embed.title }}{{ if i.embed.description }}

    {{ i.embed.description }}

    {{ /if }} - {{ /if }} -{{ /if }} diff --git a/lib/routes/kepu/live.ts b/lib/routes/kepu/live.tsx similarity index 75% rename from lib/routes/kepu/live.ts rename to lib/routes/kepu/live.tsx index b18531d8b..19439f70f 100644 --- a/lib/routes/kepu/live.ts +++ b/lib/routes/kepu/live.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -77,18 +76,25 @@ async function handler(ctx) { item.enclosure_type = `video/${item.enclosure_url.split(/\./).pop()}`; } - item.description = art(path.join(__dirname, 'templates/description.art'), { - image: { - src: item.itunes_item_image, - alt: item.title, - }, - video: { - src: item.enclosure_url, - type: item.enclosure_type, - poster: item.itunes_item_image, - }, - description: item.description, - }); + const poster = item.itunes_item_image; + item.description = renderToString( + <> + {item.itunes_item_image ? ( +
    + {item.title} +
    + ) : null} + {item.enclosure_url ? ( + + ) : null} + {item.description ?

    {item.description}

    : null} + + ); return item; }) diff --git a/lib/routes/kepu/templates/description.art b/lib/routes/kepu/templates/description.art deleted file mode 100644 index b07f03ccd..000000000 --- a/lib/routes/kepu/templates/description.art +++ /dev/null @@ -1,33 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if intro }} -

    {{ intro }}

    -{{ /if }} - -{{ if video?.src }} - -{{ /if }} - -{{ if description }} -

    {{ description }}

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/koyso/index.ts b/lib/routes/koyso/index.tsx similarity index 94% rename from lib/routes/koyso/index.ts rename to lib/routes/koyso/index.tsx index 75b4ce9ce..e4acd8bd4 100644 --- a/lib/routes/koyso/index.ts +++ b/lib/routes/koyso/index.tsx @@ -1,15 +1,31 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +const renderDescription = (images?: DescriptionImage[]) => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + + ); export const handler = async (ctx: Context): Promise => { const { category = '0', sort = 'latest' } = ctx.req.param(); @@ -32,16 +48,16 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('div.game_info').text(); const image: string | undefined = $el.find('div.game_media img').attr('data-src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - images: image + const description: string | undefined = renderDescription( + image ? [ { src: image, alt: title, }, ] - : undefined, - }); + : undefined + ); const linkUrl: string | undefined = $el.attr('href'); const processedItem: DataItem = { diff --git a/lib/routes/koyso/templates/description.art b/lib/routes/koyso/templates/description.art deleted file mode 100644 index 0a7f83a6f..000000000 --- a/lib/routes/koyso/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/kpmg/insights.ts b/lib/routes/kpmg/insights.tsx similarity index 86% rename from lib/routes/kpmg/insights.ts rename to lib/routes/kpmg/insights.tsx index 07f608dc0..4d97b9e54 100644 --- a/lib/routes/kpmg/insights.ts +++ b/lib/routes/kpmg/insights.tsx @@ -1,14 +1,13 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://kpmg.com'; const payload = { @@ -58,7 +57,25 @@ const endpoints = { api: `${baseUrl}/esearch/cn-zh`, }, }; -const render = (data) => art(path.join(__dirname, 'templates/description.art'), data); +const render = (data: { image?: string; alt?: string; content?: string; pdf?: string }) => renderToString(); + +const KpmgDescription = ({ image, alt, content, pdf }: { image?: string; alt?: string; content?: string; pdf?: string }) => ( + <> + {image ? {alt} : null} + {content ? ( + <> +
    + {raw(content)} + + ) : null} + {pdf ? ( + <> +
    + {raw(pdf)} + + ) : null} + +); const handler = async (ctx: Context) => { const { lang = 'en' } = ctx.req.param(); diff --git a/lib/routes/kpmg/templates/description.art b/lib/routes/kpmg/templates/description.art deleted file mode 100644 index 78ed654a1..000000000 --- a/lib/routes/kpmg/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if image }} - -{{ /if }} - -{{ if content }} -
    {{@ content }} -{{ /if }} - -{{ if pdf }} -
    {{@ pdf }} -{{ /if }} diff --git a/lib/routes/kpopping/kpics.ts b/lib/routes/kpopping/kpics.ts index 4443544a7..55629ebd4 100644 --- a/lib/routes/kpopping/kpics.ts +++ b/lib/routes/kpopping/kpics.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { filter } = ctx.req.param(); @@ -32,7 +31,7 @@ export const handler = async (ctx: Context): Promise => { const $el: Cheerio = $(el); const title: string = $el.find('figcaption section').text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: $el.find('a.picture img').attr('src') ? [ { @@ -75,7 +74,7 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('h1').contents().first().text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ description: $$('div.pics').first().html(), }); const pubDateStr: string | undefined = $$('meta[property="article:published_time"]').attr('content'); diff --git a/lib/routes/kpopping/news.ts b/lib/routes/kpopping/news.ts index f5dac1490..6ab9e1447 100644 --- a/lib/routes/kpopping/news.ts +++ b/lib/routes/kpopping/news.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { filter } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '2', 10); @@ -77,7 +76,7 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('h1').contents().first().text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: $$('figure.opening img').attr('src') ? [ { diff --git a/lib/routes/kpopping/templates/description.art b/lib/routes/kpopping/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/kpopping/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/kpopping/templates/description.tsx b/lib/routes/kpopping/templates/description.tsx new file mode 100644 index 000000000..6caa21852 --- /dev/null +++ b/lib/routes/kpopping/templates/description.tsx @@ -0,0 +1,20 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionData) => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/kyodonews/index.ts b/lib/routes/kyodonews/index.tsx similarity index 91% rename from lib/routes/kyodonews/index.ts rename to lib/routes/kyodonews/index.tsx index 328bfb121..070d9d80b 100644 --- a/lib/routes/kyodonews/index.ts +++ b/lib/routes/kyodonews/index.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import InvalidParameterError from '@/errors/types/invalid-parameter'; @@ -8,7 +8,6 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const resolveRelativeLink = (link, baseUrl) => (link.startsWith('http') ? link : `${baseUrl}${link}`); @@ -115,10 +114,18 @@ async function handler(ctx) { articleBody = articleBody ? articleBody.trim().replace(/(完)(?=<\/p>\s*$)/m, '') : ''; // render description - item.description = art(path.join(__dirname, 'templates/article.art'), { - mainPic, - articleBody, - }); + item.description = renderToString( + <> + {mainPic ? ( + <> + {raw(mainPic)} +
    +
    + + ) : null} + {articleBody ? raw(articleBody) : null} + + ); const ldJson = $('script[type="application/ld+json"]').html(); const pubDate_match = ldJson && ldJson.match(/"datePublished":"([\d\s-:]*?)"/); diff --git a/lib/routes/kyodonews/templates/article.art b/lib/routes/kyodonews/templates/article.art deleted file mode 100644 index ac7417502..000000000 --- a/lib/routes/kyodonews/templates/article.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ if mainPic }} -{{@ mainPic }}

    -{{ /if }} -{{@ articleBody }} diff --git a/lib/routes/lang/room.ts b/lib/routes/lang/room.tsx similarity index 87% rename from lib/routes/lang/room.ts rename to lib/routes/lang/room.tsx index 046490eb0..f67abb5a4 100644 --- a/lib/routes/lang/room.ts +++ b/lib/routes/lang/room.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/live/room/:id', @@ -48,9 +47,7 @@ async function handler(ctx) { title: `${name} 开播了`, link: url, guid: `lang:live:room:${id}:${data.live_info.live_id}`, - description: art(path.join(__dirname, 'templates/room.art'), { - live_info: data.live_info, - }), + description: renderToString(), }, ]; } diff --git a/lib/routes/lang/templates/room.art b/lib/routes/lang/templates/room.art deleted file mode 100644 index ba36695c5..000000000 --- a/lib/routes/lang/templates/room.art +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/routes/lanqiao/templates/courseDesc.art b/lib/routes/lanqiao/templates/courseDesc.art deleted file mode 100644 index 702d0e9d5..000000000 --- a/lib/routes/lanqiao/templates/courseDesc.art +++ /dev/null @@ -1 +0,0 @@ -
    {{ desc }} \ No newline at end of file diff --git a/lib/routes/lanqiao/utils.ts b/lib/routes/lanqiao/utils.ts deleted file mode 100644 index 2d53043a6..000000000 --- a/lib/routes/lanqiao/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import path from 'node:path'; - -import { art } from '@/utils/render'; - -const courseDesc = (picurl, desc) => - art(path.join(__dirname, 'templates/courseDesc.art'), { - picurl, - desc, - }); - -export default { courseDesc }; diff --git a/lib/routes/lanqiao/utils.tsx b/lib/routes/lanqiao/utils.tsx new file mode 100644 index 000000000..aea33c95d --- /dev/null +++ b/lib/routes/lanqiao/utils.tsx @@ -0,0 +1,12 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +const courseDesc = (picurl, desc) => + renderToString( + <> + +
    + {desc} + + ); + +export default { courseDesc }; diff --git a/lib/routes/learnku/templates/topic.art b/lib/routes/learnku/templates/topic.art deleted file mode 100644 index 0dd3dce47..000000000 --- a/lib/routes/learnku/templates/topic.art +++ /dev/null @@ -1,14 +0,0 @@ -
    -

    🦕正文

    -
    -
    - {{@ article }} -
    -
    -{{if comment }} -
    -

    👨‍💻评论

    -
    - {{@ comment }} -
    -{{/if}} diff --git a/lib/routes/learnku/topic.ts b/lib/routes/learnku/topic.tsx similarity index 65% rename from lib/routes/learnku/topic.ts rename to lib/routes/learnku/topic.tsx index 0285d6684..a52cdf701 100644 --- a/lib/routes/learnku/topic.ts +++ b/lib/routes/learnku/topic.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:community/:category?', @@ -73,10 +72,24 @@ async function handler(ctx) { return { title, - description: art(path.join(__dirname, 'templates/topic.art'), { - article, - comment, - }), + description: renderToString( + <> +
    +

    🦕正文

    +
    +
    + {article ? raw(article) : null} +
    +
    + {comment ? ( +
    +

    👨‍💻评论

    +
    + {raw(comment)} +
    + ) : null} + + ), category: categoryName, link: itemLink, pubDate: parseDate($('.timeago').attr('title'), 'YYYY/MM/DD'), diff --git a/lib/routes/leetcode/dailyquestion-cn.ts b/lib/routes/leetcode/dailyquestion-cn.ts index 40632c3b0..e0ccacbc1 100644 --- a/lib/routes/leetcode/dailyquestion-cn.ts +++ b/lib/routes/leetcode/dailyquestion-cn.ts @@ -1,8 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderQuestionDescription } from './templates/question-description'; const host = 'https://leetcode.cn'; @@ -109,7 +108,7 @@ async function handler() { const rssData = { title: question.frontedId + '.' + question.titleSlug, - description: art(path.join(__dirname, 'templates/question-description.art'), { + description: renderQuestionDescription({ question, }), link: question.link, diff --git a/lib/routes/leetcode/dailyquestion-en.ts b/lib/routes/leetcode/dailyquestion-en.ts index 02a014bb4..cf94c4bf4 100644 --- a/lib/routes/leetcode/dailyquestion-en.ts +++ b/lib/routes/leetcode/dailyquestion-en.ts @@ -1,8 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderQuestionDescription } from './templates/question-description'; const host = 'https://leetcode.com'; @@ -110,7 +109,7 @@ async function handler() { const rssData = { title: question.frontedId + '.' + question.titleSlug, - description: art(path.join(__dirname, 'templates/question-description.art'), { + description: renderQuestionDescription({ question, }), link: question.link, diff --git a/lib/routes/leetcode/templates/question-description.art b/lib/routes/leetcode/templates/question-description.art deleted file mode 100644 index a2ee54365..000000000 --- a/lib/routes/leetcode/templates/question-description.art +++ /dev/null @@ -1,6 +0,0 @@ -
    - {{question.difficulty}} {{question.date}} -

    - {{question.tags}} -

    -
    diff --git a/lib/routes/leetcode/templates/question-description.tsx b/lib/routes/leetcode/templates/question-description.tsx new file mode 100644 index 000000000..1e74bb087 --- /dev/null +++ b/lib/routes/leetcode/templates/question-description.tsx @@ -0,0 +1,22 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type QuestionDescriptionProps = { + question: { + difficulty: string; + date: string; + tags: string; + }; +}; + +const QuestionDescription = ({ question }: QuestionDescriptionProps) => ( +
    + {question.difficulty} {question.date} +
    +
    + {question.tags} +
    +
    +
    +); + +export const renderQuestionDescription = (props: QuestionDescriptionProps): string => renderToString(); diff --git a/lib/routes/lenovo/drive.ts b/lib/routes/lenovo/drive.tsx similarity index 60% rename from lib/routes/lenovo/drive.ts rename to lib/routes/lenovo/drive.tsx index cf2cb94fb..9b5a6e2fe 100644 --- a/lib/routes/lenovo/drive.ts +++ b/lib/routes/lenovo/drive.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/drive/:selName', @@ -47,13 +46,7 @@ export async function handler(ctx) { ({ title: `${item.DriverName} ${item.Version}`, link: `https://newsupport.lenovo.com.cn/driveDownloads_detail.html?driveId=${item.DriverEdtionId}`, - description: art(path.join(__dirname, 'templates/drive.art'), { - driveName: item.DriverName, - driveCode: item.DriverCode, - driveVersion: item.Version, - downloadFileName: item.FileName, - downloadFilePath: item.FilePath, - }), + description: renderToString(), pubDate: parseDate(item.CreateTime, +8), }) as DataItem ); @@ -64,3 +57,29 @@ export async function handler(ctx) { language: 'zh-CN', } as Data; } + +const DriveDescription = ({ driveName, driveCode, driveVersion, downloadFileName, downloadFilePath }: { driveName: string; driveCode: string; driveVersion: string; downloadFileName: string; downloadFilePath: string }) => ( +
    +

    驱动信息

    +
      +
    • + 驱动名称: + {driveName} +
    • +
    • + 驱动编码: + {driveCode} +
    • +
    • + 驱动版本: + {driveVersion} +
    • +
    • + 下载地址: + + {downloadFileName} + +
    • +
    +
    +); diff --git a/lib/routes/lenovo/templates/drive.art b/lib/routes/lenovo/templates/drive.art deleted file mode 100644 index 4d53dbd1e..000000000 --- a/lib/routes/lenovo/templates/drive.art +++ /dev/null @@ -1,9 +0,0 @@ -
    -

    驱动信息

    -
      -
    • 驱动名称:{{ driveName }}
    • -
    • 驱动编码:{{ driveCode }}
    • -
    • 驱动版本:{{ driveVersion }}
    • -
    • 下载地址:{{ downloadFileName }}
    • -
    -
    diff --git a/lib/routes/lfsyd/templates/card.art b/lib/routes/lfsyd/templates/card.art deleted file mode 100644 index 30e812387..000000000 --- a/lib/routes/lfsyd/templates/card.art +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/lib/routes/lfsyd/templates/video.art b/lib/routes/lfsyd/templates/video.art deleted file mode 100644 index 5aab94d67..000000000 --- a/lib/routes/lfsyd/templates/video.art +++ /dev/null @@ -1 +0,0 @@ -

    {{ url }}

    \ No newline at end of file diff --git a/lib/routes/lfsyd/utils.ts b/lib/routes/lfsyd/utils.tsx similarity index 91% rename from lib/routes/lfsyd/utils.ts rename to lib/routes/lfsyd/utils.tsx index 0c98e0d15..160de968b 100644 --- a/lib/routes/lfsyd/utils.ts +++ b/lib/routes/lfsyd/utils.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import md5 from '@/utils/md5'; -import { art } from '@/utils/render'; const rootUrl = 'https://www.iyingdi.com'; const infoUrL = 'https://api.iyingdi.com/web/post/info'; @@ -63,7 +61,7 @@ const cleanHtml = (htmlString) => { $(e).find('.card-status').remove(); $(e) .find('.card-info') - .wrap(art(path.join(__dirname, 'templates/card.art'), { url })); + .wrap(renderToString()); }); $('.yingdi-image.gif').each((i, e) => { @@ -82,7 +80,13 @@ const cleanHtml = (htmlString) => { .match(/bvid=(.*?)&/)[1]; if (bvid) { const url = `https://www.bilibili.com/video/${bvid}`; - $(e).after(art(path.join(__dirname, 'templates/video.art'), { url })); + $(e).after( + renderToString( +

    + {url} +

    + ) + ); } }); diff --git a/lib/routes/linkedin/cn/utils.ts b/lib/routes/linkedin/cn/utils.tsx similarity index 73% rename from lib/routes/linkedin/cn/utils.ts rename to lib/routes/linkedin/cn/utils.tsx index 8ed987c56..a72ca4c59 100644 --- a/lib/routes/linkedin/cn/utils.ts +++ b/lib/routes/linkedin/cn/utils.tsx @@ -1,9 +1,10 @@ import crypto from 'node:crypto'; -import path from 'node:path'; + +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import { parseAttr } from './renderer'; @@ -113,7 +114,28 @@ const parseJobPosting = (ctx, jobPosting) => { title: `${jobPosting.companyName} 正在找 ${jobPosting.title}`, link: `https://www.linkedin.cn/incareer/jobs/view/${entityUrn.split(':').pop()}`, guid: `linkedincn:${entityUrn}`, - description: art(path.join(__dirname, '../templates/cn/posting.art'), job), + description: renderToString( + <> +

    {job.title}

    +

    {job.applyMethod?.instantOffsiteApply ? 点击申请 : null}

    +

    {job.applyMethod?.basicOffsiteApply ? 点击申请 : null}

    +

    + 已有{job.numApplies}人申请此职位, {job.numViews}人查看此职位 +

    + {job.compensationDescription ?

    薪资:{job.compensationDescription || 'N/A'}

    : null} +

    工作地点: {job.geo?.defaultLocalizedName ?? ''}

    + {job.company ? ( + <> +

    公司介绍

    +

    {job.company.name}

    +

    员工人数:{job.company.employeeCount}

    +

    {job.company.localizedDescription}

    + + ) : null} +

    职位介绍

    +
    {job.desc ? raw(job.desc) : null}
    + + ), pubDate: jobPosting.listedAt, }; }); diff --git a/lib/routes/linkedin/templates/cn/posting.art b/lib/routes/linkedin/templates/cn/posting.art deleted file mode 100644 index 441113989..000000000 --- a/lib/routes/linkedin/templates/cn/posting.art +++ /dev/null @@ -1,25 +0,0 @@ -

    {{ title }}

    -

    - {{ if (applyMethod.instantOffsiteApply)}} - 点击申请 - {{ /if }} -

    -

    - {{ if (applyMethod.basicOffsiteApply)}} - 点击申请 - {{ /if }} -

    -

    已有{{numApplies}}人申请此职位, {{numViews}}人查看此职位

    -{{ if(compensationDescription) }} -

    薪资:{{ compensationDescription || 'N/A' }}

    -{{ /if }} -

    工作地点: {{ geo.defaultLocalizedName }}

    - -{{ if (company) }} -

    公司介绍

    -

    {{company.name}}

    -

    员工人数:{{company.employeeCount}}

    -

    {{company.localizedDescription}}

    -{{ /if }} -

    职位介绍

    -
    {{@ desc}}
    diff --git a/lib/routes/linkresearcher/index.ts b/lib/routes/linkresearcher/index.tsx similarity index 88% rename from lib/routes/linkresearcher/index.ts rename to lib/routes/linkresearcher/index.tsx index 5cc151f58..db96cd9f6 100644 --- a/lib/routes/linkresearcher/index.ts +++ b/lib/routes/linkresearcher/index.tsx @@ -1,7 +1,7 @@ import crypto from 'node:crypto'; -import path from 'node:path'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Data, DataItem, Route } from '@/types'; @@ -9,11 +9,21 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { DetailResponse, SearchResultItem } from './types'; -const templatePath = path.join(__dirname, 'templates/bilingual.art'); +const renderBilingual = (zh, en) => + renderToString( + <> + {en.map((enText, index) => ( + <> + {index === 0 ? null :
    } +

    {enText}

    +

    {zh[index]}

    + + ))} + + ); const baseURL = 'https://www.linkresearcher.com'; const apiURL = `${baseURL}/api`; @@ -108,13 +118,7 @@ async function handler(ctx: Context): Promise { pubDate: parseDate(response.onlineTime), link, image: response.cover, - description: - 'zhTextList' in response && 'enTextList' in response - ? art(templatePath, { - zh: response.zhTextList, - en: response.enTextList, - }) - : response.content, + description: 'zhTextList' in response && 'enTextList' in response ? renderBilingual(response.zhTextList, response.enTextList) : response.content, }; if ('paperList' in response) { diff --git a/lib/routes/linkresearcher/templates/bilingual.art b/lib/routes/linkresearcher/templates/bilingual.art deleted file mode 100644 index be7d6c1ba..000000000 --- a/lib/routes/linkresearcher/templates/bilingual.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ each en }} -{{ if $index !== 0 }} -
    -{{ /if }} -

    {{ $value }}

    -

    {{ zh[$index] }}

    -{{ /each }} diff --git a/lib/routes/lkong/forum.ts b/lib/routes/lkong/forum.ts index d79f313ce..e41245903 100644 --- a/lib/routes/lkong/forum.ts +++ b/lib/routes/lkong/forum.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { viewForum, viewThread } from './query'; +import { renderContent } from './templates/content'; export const route: Route = { path: '/forum/:id?/:digest?', @@ -51,9 +49,7 @@ async function handler(ctx) { item.author = detailResponse.data.data.thread?.author.name; item.pubDate = parseDate(detailResponse.data.data.thread?.dateline); - item.description = art(path.join(__dirname, 'templates/content.art'), { - content: JSON.parse(detailResponse.data.data.posts[0].content), - }); + item.description = renderContent(JSON.parse(detailResponse.data.data.posts[0].content)); delete item.guid; return item; diff --git a/lib/routes/lkong/templates/content.art b/lib/routes/lkong/templates/content.art deleted file mode 100644 index fddcdfd7f..000000000 --- a/lib/routes/lkong/templates/content.art +++ /dev/null @@ -1,18 +0,0 @@ -{{ each content paragraph }} -{{ if paragraph.type == 'paragraph' }} -

    -{{ each paragraph.children child }} -{{ if child.text }} - -{{ if child.bold }}{{ /if }} -{{ child.text }} -{{ if child.bold }}{{ /if }} - -{{ /if }} -{{ if child.type == 'emotion' }} - -{{ /if }} -{{ /each }} -

    -{{ /if }} -{{ /each }} \ No newline at end of file diff --git a/lib/routes/lkong/templates/content.tsx b/lib/routes/lkong/templates/content.tsx new file mode 100644 index 000000000..a07aaed13 --- /dev/null +++ b/lib/routes/lkong/templates/content.tsx @@ -0,0 +1,36 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type ContentChild = { + text?: string; + color?: string; + bold?: boolean; + type?: string; + id?: string | number; +}; + +type ContentParagraph = { + type?: string; + children?: ContentChild[]; +}; + +const LkongContent = ({ content }: { content: ContentParagraph[] }) => ( + <> + {content?.map((paragraph) => + paragraph.type === 'paragraph' ? ( +

    + {paragraph.children?.map((child) => { + if (child.text) { + return {child.bold ? {child.text} : child.text}; + } + if (child.type === 'emotion') { + return ; + } + return null; + })} +

    + ) : null + )} + +); + +export const renderContent = (content: ContentParagraph[]) => renderToString(); diff --git a/lib/routes/lkong/templates/quote.art b/lib/routes/lkong/templates/quote.art deleted file mode 100644 index 9d4f34a8c..000000000 --- a/lib/routes/lkong/templates/quote.art +++ /dev/null @@ -1,23 +0,0 @@ -
    - - -{{ author }}: -{{@ content }} -
    - - \ No newline at end of file diff --git a/lib/routes/lkong/thread.ts b/lib/routes/lkong/thread.tsx similarity index 53% rename from lib/routes/lkong/thread.ts rename to lib/routes/lkong/thread.tsx index ff458c64a..f85d6e255 100644 --- a/lib/routes/lkong/thread.ts +++ b/lib/routes/lkong/thread.tsx @@ -1,11 +1,12 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { countReplies, viewThread } from './query'; +import { renderContent } from './templates/content'; export const route: Route = { path: '/thread/:id', @@ -49,18 +50,8 @@ async function handler(ctx) { link: `${rootUrl}/thread/${id}?pid=${item.pid}`, pubDate: parseDate(item.dateline), description: - (item.quote - ? art(path.join(__dirname, 'templates/quote.art'), { - target: `${rootUrl}/thread/${id}?pid=${item.quote.pid}`, - author: item.quote.author.name, - content: art(path.join(__dirname, 'templates/content.art'), { - content: JSON.parse(item.quote.content), - }), - }) - : '') + - art(path.join(__dirname, 'templates/content.art'), { - content: JSON.parse(item.content), - }), + (item.quote ? renderToString() : '') + + renderContent(JSON.parse(item.content)), })); return { @@ -69,3 +60,35 @@ async function handler(ctx) { item: items, }; } + +const quoteStyles = ` +.quote { + margin: 15px 0px 15px; + width: 100%; + border: 1px solid #eee; + background-color: #f5f5f5; + border-radius: 4px; + padding: 8px 14px; + cursor: pointer; +} + +.quote-link { + color: #1890ff; + text-decoration: none; +} +`; + +const LkongQuote = ({ target, author, content }: { target: string; author: string; content: string }) => ( + <> +
    + + + + + {author} + + :{raw(content)} +
    + + +); diff --git a/lib/routes/lmu/jobs.ts b/lib/routes/lmu/jobs.tsx similarity index 64% rename from lib/routes/lmu/jobs.ts rename to lib/routes/lmu/jobs.tsx index 5d8403407..98ac5e77b 100644 --- a/lib/routes/lmu/jobs.ts +++ b/lib/routes/lmu/jobs.tsx @@ -1,16 +1,15 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const apiUrl = 'https://jobs.b-ite.com/api/v1/postings/search'; -// 辅助函数:根据 value 查找对应的 label +// Helper: find the display label for a value. function findLabel(value: string, options: Array<{ value: string; label: string }>): string { const option = options.find((option) => option.value === value); - return option?.label ?? value; // 如果找不到匹配项,返回 value 本身 + return option?.label ?? value; // Fall back to the raw value if no match. } async function handler() { @@ -50,16 +49,11 @@ async function handler() { const items = jobPostings.map((job) => { const pubDate = parseDate(job.createdOn, 'YYYY-MM-DDTHH:mm:ssZ'); - // 获取 Institution 的 label + // Resolve Institution label. const institutionLabel = findLabel(job.custom.bereich, bereichOptions); const RemunerationGroupLabel = findLabel(job.custom.verguetung, verguetungOptions); - // 渲染模板 - const description = art(path.join(__dirname, 'templates/jobPosting.art'), { - institutionLabel, - RemunerationGroupLabel, - job, - }); + const description = renderDescription(institutionLabel, RemunerationGroupLabel, job); return { title: job.title, @@ -76,6 +70,42 @@ async function handler() { }; } +const renderDescription = (institutionLabel: string, RemunerationGroupLabel: string, job): string => + renderToString( + <> +

    + Institution: {institutionLabel} +

    +

    + Remuneration: {RemunerationGroupLabel} +

    +

    + Application deadline: {job.endsOn} +

    +

    + Job Details: +

    +

    + About us: +

    + {job.custom?.das_sind_wir || ''} +
    +

    + Your qualifications: +

    + {job.custom?.das_sind_sie || ''} +
    +

    + Benefits: +

    + {job.custom?.das_ist_unser_angebot || ''} +
    +

    + Contact: {job.custom?.kontakt || ''} +

    + + ); + export const route: Route = { path: '/jobs', name: 'Job Openings', diff --git a/lib/routes/lmu/templates/jobPosting.art b/lib/routes/lmu/templates/jobPosting.art deleted file mode 100644 index b89f75f2c..000000000 --- a/lib/routes/lmu/templates/jobPosting.art +++ /dev/null @@ -1,11 +0,0 @@ -

    Institution: {{ institutionLabel }}

    -

    Remuneration: {{ RemunerationGroupLabel }}

    -

    Application deadline: {{ job.endsOn }}

    -

    Job Details:

    -

    About us:

    -{{ job.custom.das_sind_wir || '' }}
    -

    Your qualifications:

    -{{ job.custom.das_sind_sie || '' }}
    -

    Benefits:

    -{{ job.custom.das_ist_unser_angebot || '' }}
    -

    Contact: {{ job.custom.kontakt || '' }}

    diff --git a/lib/routes/logclub/index.ts b/lib/routes/logclub/index.ts index 9636f287a..298882f05 100644 --- a/lib/routes/logclub/index.ts +++ b/lib/routes/logclub/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/:category{.+}?', @@ -38,7 +37,7 @@ async function handler(ctx) { return { title: a.text(), link: new URL(a.prop('href'), rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: { src: image, alt: a.text(), @@ -63,7 +62,7 @@ async function handler(ctx) { content('img').each((_, el) => { el = content(el); el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: el.prop('src')?.split(/\?/)[0] ?? undefined, alt: el.prop('title'), @@ -79,7 +78,7 @@ async function handler(ctx) { item.enclosure_type = `video/${item.enclosure_url.split(/\./).pop()}`; } - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ video: { poster: item.itunes_item_image, src: item.enclosure_url, diff --git a/lib/routes/logclub/report.ts b/lib/routes/logclub/report.ts index 62e1a54f2..852fec5f4 100644 --- a/lib/routes/logclub/report.ts +++ b/lib/routes/logclub/report.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: ['/lc_report/:id?', '/report/:id?'], categories: ['new-media'], @@ -47,7 +46,7 @@ async function handler(ctx) { let items = response.list.slice(0, limit).map((item) => ({ title: item.title, link: new URL(`front/lc_report/get_report_info/${item.id}`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: { src: item.img_url?.split(/\?/)[0] ?? undefined, alt: item.title, @@ -69,7 +68,7 @@ async function handler(ctx) { content('img').each((_, el) => { el = content(el); el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: el.prop('src')?.split(/\?/)[0] ?? undefined, alt: el.prop('title'), @@ -79,7 +78,7 @@ async function handler(ctx) { }); item.title = content('h1').first().text(); - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ description: content('div.article-cont').html(), }); item.author = content('div.lc-infos a') diff --git a/lib/routes/logclub/templates/description.art b/lib/routes/logclub/templates/description.art deleted file mode 100644 index eedb4179b..000000000 --- a/lib/routes/logclub/templates/description.art +++ /dev/null @@ -1,33 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if intro }} -

    {{ intro }}

    -{{ /if }} - -{{ if video?.src }} - -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/logclub/templates/description.tsx b/lib/routes/logclub/templates/description.tsx new file mode 100644 index 000000000..dac8d4cbd --- /dev/null +++ b/lib/routes/logclub/templates/description.tsx @@ -0,0 +1,37 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type MediaImage = { + src?: string; + alt?: string; +}; + +type MediaVideo = { + src?: string; + type?: string; + poster?: string; +}; + +type DescriptionData = { + image?: MediaImage; + intro?: string; + video?: MediaVideo; + description?: string; +}; + +export const renderDescription = ({ image, intro, video, description }: DescriptionData) => + renderToString( + <> + {image?.src ?
    {image.alt ? {image.alt} : }
    : null} + {intro ?

    {intro}

    : null} + {video?.src ? ( + + ) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/logonews/index.ts b/lib/routes/logonews/index.tsx similarity index 83% rename from lib/routes/logonews/index.ts rename to lib/routes/logonews/index.tsx index 1aeafe518..853b5c7bc 100644 --- a/lib/routes/logonews/index.ts +++ b/lib/routes/logonews/index.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: ['/work/tags/:tag', '/tag/:tag', '*'], @@ -76,11 +75,7 @@ async function handler(ctx) { .toArray() .map((c) => content(c).text().replaceAll(' · ', '')); - item.description = art(path.join(__dirname, 'templates/description.art'), { - isWork, - image: content('meta[property="og:image"]').attr('content'), - description: content('.This_Article_content, .w_info').html(), - }); + item.description = renderDescription(isWork, content('meta[property="og:image"]').attr('content'), content('.This_Article_content, .w_info').html()); return item; }) @@ -93,3 +88,11 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (isWork: boolean, image: string | undefined, description: string | null): string => + renderToString( + <> + {isWork ? : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/logonews/templates/description.art b/lib/routes/logonews/templates/description.art deleted file mode 100644 index 946e8f84c..000000000 --- a/lib/routes/logonews/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{if isWork}} - -{{/if}} -{{ description }} \ No newline at end of file diff --git a/lib/routes/loltw/news.ts b/lib/routes/loltw/news.tsx similarity index 78% rename from lib/routes/loltw/news.ts rename to lib/routes/loltw/news.tsx index af261ce9f..aeab08b7e 100644 --- a/lib/routes/loltw/news.ts +++ b/lib/routes/loltw/news.tsx @@ -1,10 +1,10 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/news/:category?', @@ -46,7 +46,7 @@ async function handler(ctx) { list.map((item) => cache.tryGet(item.link, async () => { const detailResponse = await got(`${baseUrl}/api/news/detail?news_id=${item.guid}`); - item.description = art(path.join(__dirname, 'templates/news.art'), detailResponse.data.data.news_detail); + item.description = renderToString(); return item; }) ) @@ -58,3 +58,10 @@ async function handler(ctx) { item: items, }; } + +const LoltwNewsDescription = ({ img, content }: { img?: string; content?: string }) => ( +
    + {img ? : null} + {content ? raw(content) : null} +
    +); diff --git a/lib/routes/loltw/templates/news.art b/lib/routes/loltw/templates/news.art deleted file mode 100644 index d009bbd37..000000000 --- a/lib/routes/loltw/templates/news.art +++ /dev/null @@ -1,6 +0,0 @@ -
    -{{if img}} - -{{/if}} -{{@ content }} -
    diff --git a/lib/routes/lorientlejour/index.ts b/lib/routes/lorientlejour/index.tsx similarity index 89% rename from lib/routes/lorientlejour/index.ts rename to lib/routes/lorientlejour/index.tsx index faee81760..1bbdf8a4e 100644 --- a/lib/routes/lorientlejour/index.ts +++ b/lib/routes/lorientlejour/index.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { FetchError } from 'ofetch'; import { config } from '@/config'; @@ -8,7 +8,6 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const key = '3d5_f6A(S$G_FD=2S(Dr6%7BW_h37@rE'; @@ -168,11 +167,22 @@ async function handler(ctx) { } }); } - item.description = art(path.join(__dirname, 'templates/description.art'), { - summary: item.summary, - attachments: item.attachments, - article: article.html(), - }); + item.description = renderToString( + <> + {item.summary ?
    {raw(item.summary)}
    : null} + {item.attachments + ? item.attachments.map((attachment) => + attachment.url ? ( +
    + + {attachment.description ?
    {attachment.description}
    : null} +
    + ) : null + ) + : null} + {article.html() ? raw(article.html()) : null} + + ); return item; }); diff --git a/lib/routes/lorientlejour/templates/description.art b/lib/routes/lorientlejour/templates/description.art deleted file mode 100644 index 3fd2ae9c4..000000000 --- a/lib/routes/lorientlejour/templates/description.art +++ /dev/null @@ -1,18 +0,0 @@ -{{ if summary }} -
    {{@ summary }}
    -{{ /if }} -{{ if attachments}} - {{ each attachments }} - {{if $value.url }} -
    - - {{ if $value.description }} -
    {{ $value.description }}
    - {{ /if }} -
    - {{ /if }} - {{ /each }} -{{ /if }} -{{ if article }} - {{@ article }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/lovelive-anime/news.ts b/lib/routes/lovelive-anime/news.tsx similarity index 96% rename from lib/routes/lovelive-anime/news.ts rename to lib/routes/lovelive-anime/news.tsx index 3476b6ab1..15563ecc9 100644 --- a/lib/routes/lovelive-anime/news.ts +++ b/lib/routes/lovelive-anime/news.tsx @@ -1,16 +1,14 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; -const renderDescription = (desc) => art(path.join(__dirname, 'templates/description.art'), desc); +const renderDescription = (desc: { imglink: string }) => renderToString(); export const route: Route = { path: '/news/:abbr?/:category?/:option?', diff --git a/lib/routes/lovelive-anime/templates/description.art b/lib/routes/lovelive-anime/templates/description.art deleted file mode 100644 index 41ce31a11..000000000 --- a/lib/routes/lovelive-anime/templates/description.art +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/routes/lovelive-anime/templates/scheduleDesc.art b/lib/routes/lovelive-anime/templates/scheduleDesc.art deleted file mode 100644 index d512a9b37..000000000 --- a/lib/routes/lovelive-anime/templates/scheduleDesc.art +++ /dev/null @@ -1,2 +0,0 @@ -{{startTime}}   ~   {{endTime}}

    -{{desc}} diff --git a/lib/routes/lrepacks/index.ts b/lib/routes/lrepacks/index.ts index 220c9ebf7..3422c54a8 100644 --- a/lib/routes/lrepacks/index.ts +++ b/lib/routes/lrepacks/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx) => { const { category = '' } = ctx.req.param(); @@ -28,7 +27,7 @@ export const handler = async (ctx) => { item = $(item); const title = item.find('h3.entry-title').text(); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ intro: item.find('div.entry-content').text(), }); @@ -57,7 +56,7 @@ export const handler = async (ctx) => { el = $$(el); el.parent().replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: [ { src: el.prop('href'), @@ -71,8 +70,8 @@ export const handler = async (ctx) => { const title = $$('h2.entry-title').text(); const description = item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.entry-content').html(), + renderDescription({ + description: $$('div.entry-content').html() ?? undefined, }); const image = $$('meta[property="og:image"]').prop('content'); diff --git a/lib/routes/lrepacks/templates/description.art b/lib/routes/lrepacks/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/lrepacks/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/lrepacks/templates/description.tsx b/lib/routes/lrepacks/templates/description.tsx new file mode 100644 index 000000000..f7661df1e --- /dev/null +++ b/lib/routes/lrepacks/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/ltaaa/article.ts b/lib/routes/ltaaa/article.ts index e7487e8f3..5fb699ce5 100644 --- a/lib/routes/ltaaa/article.ts +++ b/lib/routes/ltaaa/article.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -33,7 +32,7 @@ export const handler = async (ctx: Context): Promise => { const $aEl: Cheerio = $el.find('div.li-title a'); const title: string = $aEl.text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ intro: $el.find('div.dbody p').first().text(), }); const pubDateStr: string | undefined = $el.find('i.icon-time').next().text().trim(); @@ -109,7 +108,7 @@ export const handler = async (ctx: Context): Promise => { $$('div.post-param, div.post-title, div.post-keywords').remove(); $$('div.attitude, div.clear').remove(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ description: $$('div.post-body').html(), }); diff --git a/lib/routes/ltaaa/templates/description.art b/lib/routes/ltaaa/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/ltaaa/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ltaaa/templates/description.tsx b/lib/routes/ltaaa/templates/description.tsx new file mode 100644 index 000000000..9259435df --- /dev/null +++ b/lib/routes/ltaaa/templates/description.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + intro?: string; + description?: string; +}; + +const Description = ({ intro, description }: DescriptionProps) => ( + <> + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/luolei/index.ts b/lib/routes/luolei/index.tsx similarity index 82% rename from lib/routes/luolei/index.ts rename to lib/routes/luolei/index.tsx index dd76bb7dc..49e7a0822 100644 --- a/lib/routes/luolei/index.ts +++ b/lib/routes/luolei/index.tsx @@ -1,20 +1,45 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +type DescriptionImage = { + src?: string; + alt?: string; + width?: string; + height?: string; +}; + +type DescriptionVideo = { + src?: string; +}; + +const renderDescription = ({ images, videos }: { images?: DescriptionImage[]; videos?: DescriptionVideo[] }) => + renderToString( + <> + {images?.length + ? images.map((image) => { + if (videos?.[0]?.src || !image?.src) { + return null; + } + + const alt = image.height ?? image.width ?? image.alt; + return
    {alt ? {alt} : }
    ; + }) + : null} + + ); const unblurImages = ($: CheerioAPI) => { $('img[data-original-src]').each((_, el) => { el = $(el); el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ images: [ { src: el.prop('data-original-src'), diff --git a/lib/routes/luolei/templates/description.art b/lib/routes/luolei/templates/description.art deleted file mode 100644 index baf0f0223..000000000 --- a/lib/routes/luolei/templates/description.art +++ /dev/null @@ -1,19 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if !videos?.[0]?.src && image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/lvv2/news.ts b/lib/routes/lvv2/news.ts index f2c2eb4ca..085c98fb5 100644 --- a/lib/routes/lvv2/news.ts +++ b/lib/routes/lvv2/news.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderOutlink } from './templates/outlink'; + const rootUrl = 'https://lvv2.com'; const titleMap = { @@ -86,9 +85,7 @@ async function handler(ctx) { return description; }) - : art(path.join(__dirname, 'templates/outlink.art'), { - outlink: item.link, - }); + : renderOutlink(item.link); return item; }) diff --git a/lib/routes/lvv2/templates/outlink.art b/lib/routes/lvv2/templates/outlink.art deleted file mode 100644 index e89a59ec1..000000000 --- a/lib/routes/lvv2/templates/outlink.art +++ /dev/null @@ -1 +0,0 @@ -文章链接 diff --git a/lib/routes/lvv2/templates/outlink.tsx b/lib/routes/lvv2/templates/outlink.tsx new file mode 100644 index 000000000..76df4aae6 --- /dev/null +++ b/lib/routes/lvv2/templates/outlink.tsx @@ -0,0 +1,8 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +export const renderOutlink = (outlink: string): string => + renderToString( + + 文章链接 + + ); diff --git a/lib/routes/lvv2/top.ts b/lib/routes/lvv2/top.ts index b1f12b073..6e61f31a8 100644 --- a/lib/routes/lvv2/top.ts +++ b/lib/routes/lvv2/top.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderOutlink } from './templates/outlink'; + const rootUrl = 'https://lvv2.com'; const titleMap = { @@ -88,9 +87,7 @@ async function handler(ctx) { return description; }) - : art(path.join(__dirname, 'templates/outlink.art'), { - outlink: link, - }); + : renderOutlink(link); return item; }) diff --git a/lib/routes/lxixsxa/discography.ts b/lib/routes/lxixsxa/discography.tsx similarity index 81% rename from lib/routes/lxixsxa/discography.ts rename to lib/routes/lxixsxa/discography.tsx index fdba89bb9..0e9f646c9 100644 --- a/lib/routes/lxixsxa/discography.ts +++ b/lib/routes/lxixsxa/discography.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { parseJSONP } from './jsonp-helper'; @@ -65,13 +65,15 @@ async function handler() { // the article title title: item.title, // the article content - description: art(path.join(__dirname, 'templates/disco.art'), { - comment: item.comment, - type: item.type, - price: item.price, - image: item.imageLink, - description: item.description, - }), + description: renderToString( + <> + + {item.comment} Type: {item.type} Price: {item.price} + + {item.imageLink ? : null} + {item.description ? raw(item.description) : null} + + ), // the article publish time pubDate: parseDate(item.releaseDate), // the article link diff --git a/lib/routes/lxixsxa/information.ts b/lib/routes/lxixsxa/information.tsx similarity index 83% rename from lib/routes/lxixsxa/information.ts rename to lib/routes/lxixsxa/information.tsx index 761a7fe22..652de8ef9 100644 --- a/lib/routes/lxixsxa/information.ts +++ b/lib/routes/lxixsxa/information.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { parseJSONP } from './jsonp-helper'; @@ -62,10 +62,12 @@ async function handler() { // the article title title: item.title, // the article content - description: art(path.join(__dirname, 'templates/info.art'), { - category: item.category, - description: item.description.replaceAll('\n', '
    '), - }), + description: renderToString( + <> + {item.category ? Category: {item.category} : null} + {raw(item.description.replaceAll('\n', '
    '))} + + ), // the article publish time pubDate: parseDate(item.date), // the article link diff --git a/lib/routes/lxixsxa/templates/disco.art b/lib/routes/lxixsxa/templates/disco.art deleted file mode 100644 index 0e495a922..000000000 --- a/lib/routes/lxixsxa/templates/disco.art +++ /dev/null @@ -1,5 +0,0 @@ -{{comment}} Type: {{type}} Price: {{price}} -{{ if image }} - -{{ /if }} -{{@ description }} \ No newline at end of file diff --git a/lib/routes/lxixsxa/templates/info.art b/lib/routes/lxixsxa/templates/info.art deleted file mode 100644 index 851917bd8..000000000 --- a/lib/routes/lxixsxa/templates/info.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ if category }} - Category: {{category}} -{{ /if }} -{{@ description }} \ No newline at end of file diff --git a/lib/routes/m4/index.ts b/lib/routes/m4/index.ts index 434245e4b..7fe380128 100644 --- a/lib/routes/m4/index.ts +++ b/lib/routes/m4/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import InvalidParameterError from '@/errors/types/invalid-parameter'; @@ -7,10 +5,11 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { isValidHost } from '@/utils/valid-host'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '/:id?/:category{.+}?', name: 'Unknown', @@ -43,7 +42,7 @@ async function handler(ctx) { return { title: a.text(), link: a.prop('href'), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: [ { src: item.parent().find('div.aimg0 a img').prop('src'), @@ -67,9 +66,9 @@ async function handler(ctx) { const content = load(detailResponse); item.title = content('h1').first().text(); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ intro: content('div.aintro1, p.cont-summary').text(), - description: content('div.content0, div.cont-detail').html(), + description: content('div.content0, div.cont-detail').html() ?? undefined, }); item.category = content('span.dd0 a, a[rel="category"]') .toArray() diff --git a/lib/routes/m4/templates/description.art b/lib/routes/m4/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/m4/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/m4/templates/description.tsx b/lib/routes/m4/templates/description.tsx new file mode 100644 index 000000000..f7661df1e --- /dev/null +++ b/lib/routes/m4/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/maccms/index.ts b/lib/routes/maccms/index.tsx similarity index 75% rename from lib/routes/maccms/index.ts rename to lib/routes/maccms/index.tsx index ddc794a04..44c9b06d0 100644 --- a/lib/routes/maccms/index.ts +++ b/lib/routes/maccms/index.tsx @@ -1,13 +1,60 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Result, Vod } from '@/routes/maccms/type'; import type { DataItem, Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; -const render = (vod: Vod, link: string) => art(path.join(__dirname, 'templates/vod.art'), { vod, link }); +const render = (vod: Vod, link: string) => renderToString(); + +const VodDescription = ({ vod, link }: { vod: Vod; link: string }) => ( + <> + + {vod.vod_name} + +

    + {vod.vod_name} {vod.vod_remarks} +

    +

    + 别名: + {vod.vod_sub} +

    +

    + 导演: + {vod.vod_director} +

    +

    + 主演: + {vod.vod_actor} +

    +

    + 类型: + {vod.vod_class} +

    +

    + 年份: + {vod.vod_year} +

    +

    + 地区: + {vod.vod_area} +

    +

    + 开播时间: + {vod.vod_pubdate} +

    +

    + 更新时间: + {vod.vod_time} +

    +

    + 资源主页: + {link} +

    +

    剧情介绍

    + +); export const route: Route = { path: '/:domain/:type?/:size?', diff --git a/lib/routes/maccms/templates/vod.art b/lib/routes/maccms/templates/vod.art deleted file mode 100644 index cea9457f9..000000000 --- a/lib/routes/maccms/templates/vod.art +++ /dev/null @@ -1,12 +0,0 @@ -{{ vod.vod_name }} -

    {{ vod.vod_name }} {{ vod.vod_remarks }}

    -

    别名:{{ vod.vod_sub }}

    -

    导演:{{ vod.vod_director }}

    -

    主演:{{ vod.vod_actor }}

    -

    类型:{{ vod.vod_class }}

    -

    年份:{{ vod.vod_year }}

    -

    地区:{{ vod.vod_area }}

    -

    开播时间:{{ vod.vod_pubdate }}

    -

    更新时间:{{ vod.vod_time }}

    -

    资源主页:{{ link }}

    -

    剧情介绍

    \ No newline at end of file diff --git a/lib/routes/magazinelib/latest-magazine.ts b/lib/routes/magazinelib/latest-magazine.tsx similarity index 91% rename from lib/routes/magazinelib/latest-magazine.ts rename to lib/routes/magazinelib/latest-magazine.tsx index 39ddb9d3a..c0fa7afd8 100644 --- a/lib/routes/magazinelib/latest-magazine.ts +++ b/lib/routes/magazinelib/latest-magazine.tsx @@ -1,12 +1,16 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const host = 'https://magazinelib.com'; +const renderImage = (imgUrl) => + renderToString( +
    + +
    + ); export const route: Route = { path: '/latest-magazine/:query?', categories: ['reading'], @@ -58,7 +62,7 @@ async function handler(ctx) { content.find('img[src="https://magazinelib.com/wp-includes/images/media/default.png"]').remove(); data.content = content.html(); const imgUrl = obj._embedded['wp:featuredmedia'][0].source_url; - data.description = data.content + art(path.join(__dirname, 'templates/magazine-description.art'), { imgUrl }); + data.description = data.content + renderImage(imgUrl); data.categories = obj._embedded['wp:term'][0].map((item) => item.name); return data; }); diff --git a/lib/routes/magazinelib/templates/magazine-description.art b/lib/routes/magazinelib/templates/magazine-description.art deleted file mode 100644 index 43f9138e6..000000000 --- a/lib/routes/magazinelib/templates/magazine-description.art +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    diff --git a/lib/routes/manhuagui/subscribe.ts b/lib/routes/manhuagui/subscribe.tsx similarity index 91% rename from lib/routes/manhuagui/subscribe.ts rename to lib/routes/manhuagui/subscribe.tsx index efce8d8a9..25834b196 100644 --- a/lib/routes/manhuagui/subscribe.ts +++ b/lib/routes/manhuagui/subscribe.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const web_url = 'https://www.manhuagui.com/user/book/shelf/1'; @@ -70,10 +68,12 @@ async function handler() { const manga_title = $(item).find('.co_1.c_space').first().text(); // 最新的一话题目 const title = $(item).find('img').attr('alt'); // 漫画的名字 const link = $(item).find('.co_1.c_space').first().children().attr('href'); // 漫画最新的链接 - const description = art(path.join(__dirname, 'templates/manga.art'), { - manga_title, - img_src, - }); + const description = renderToString( + <> +

    {manga_title}

    + + + ); const pubDate = $(item).find('.co_1.c_space').first().next().text(); const publishDate = parseRelativeDate(pubDate); // 处理相对时间 const single = { diff --git a/lib/routes/manhuagui/templates/manga.art b/lib/routes/manhuagui/templates/manga.art deleted file mode 100644 index a6f0d14d5..000000000 --- a/lib/routes/manhuagui/templates/manga.art +++ /dev/null @@ -1,2 +0,0 @@ -

    {{manga_title}}

    - diff --git a/lib/routes/manyvids/templates/video.art b/lib/routes/manyvids/templates/video.art deleted file mode 100644 index 63d221442..000000000 --- a/lib/routes/manyvids/templates/video.art +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/lib/routes/manyvids/video.ts b/lib/routes/manyvids/video.tsx similarity index 85% rename from lib/routes/manyvids/video.ts rename to lib/routes/manyvids/video.tsx index 810f24808..82516d92f 100644 --- a/lib/routes/manyvids/video.ts +++ b/lib/routes/manyvids/video.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; import type { UserProfile, Videos } from './types'; @@ -26,7 +25,12 @@ export const route: Route = { const getProfileById = (uid: string) => cache.tryGet(`manyvids:profile:${uid}`, () => ofetch(`https://www.manyvids.com/bff/profile/profiles/${uid}`)) as Promise; -const render = (data) => art(path.join(__dirname, 'templates/video.art'), data); +const render = ({ poster, src }: { poster: string; src: string }) => + renderToString( + + ); async function handler(ctx) { const { uid } = ctx.req.param(); diff --git a/lib/routes/mathpix/blog.ts b/lib/routes/mathpix/blog.tsx similarity index 90% rename from lib/routes/mathpix/blog.ts rename to lib/routes/mathpix/blog.tsx index f441115a1..74b474b26 100644 --- a/lib/routes/mathpix/blog.ts +++ b/lib/routes/mathpix/blog.tsx @@ -1,16 +1,14 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -45,17 +43,16 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('a.articles__title').text(); const image: string | undefined = $el.find('div.articles__image img').attr('srcset') ? new URL($el.find('div.articles__image img').attr('srcset') as string, baseUrl).href : undefined; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - images: image - ? [ - { - src: image, - alt: title, - }, - ] - : undefined, - intro: $el.find('div.articles__text').text(), - }); + const description: string | undefined = renderToString( + <> + {image ? ( +
    + {title} +
    + ) : null} + {$el.find('div.articles__text').text() ?
    {$el.find('div.articles__text').text()}
    : null} + + ); const pubDateStr: string | undefined = $el.find('time.articles__date').attr('datetime'); const linkUrl: string | undefined = $el.find('a.articles__title').attr('href'); const categoryIds: string[] = diff --git a/lib/routes/mathpix/templates/description.art b/lib/routes/mathpix/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/mathpix/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/mcmod/index.ts b/lib/routes/mcmod/index.tsx similarity index 82% rename from lib/routes/mcmod/index.ts rename to lib/routes/mcmod/index.tsx index f7ab6af52..8713a6fca 100644 --- a/lib/routes/mcmod/index.ts +++ b/lib/routes/mcmod/index.tsx @@ -1,15 +1,35 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; -const render = (mod) => art(path.join(__dirname, 'templates/mod.art'), { mod }); +const render = (mod) => + renderToString( + <> + + {mod.label.map((label) => ( +

    {label}

    + ))} + {mod.support?.length ? ( + <> +

    支持的MC版本:

    +
      + {mod.support.map((support) => ( +
    • + {support.label} + {support.versions} +
    • + ))} +
    + + ) : null} +
    + + ); export const route: Route = { path: '/:type', diff --git a/lib/routes/mcmod/templates/mod.art b/lib/routes/mcmod/templates/mod.art deleted file mode 100644 index 90042d547..000000000 --- a/lib/routes/mcmod/templates/mod.art +++ /dev/null @@ -1,13 +0,0 @@ - -{{ each mod.label l }} -

    {{ l }}

    -{{ /each }} -{{ if mod.support.length > 0 }} -

    支持的MC版本:

    -
      - {{ each mod.support s }} -
    • {{ s.label }}{{ s.versions }}
    • - {{ /each }} -
    -{{ /if }} -
    \ No newline at end of file diff --git a/lib/routes/mdpi/journal.ts b/lib/routes/mdpi/journal.tsx similarity index 70% rename from lib/routes/mdpi/journal.ts rename to lib/routes/mdpi/journal.tsx index ff92cbab5..b4457c5d9 100644 --- a/lib/routes/mdpi/journal.ts +++ b/lib/routes/mdpi/journal.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { CookieJar } from 'tough-cookie'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const cookieJar = new CookieJar(); @@ -75,9 +73,41 @@ async function handler(ctx) { }); const renderDesc = (item) => - art(path.join(__dirname, 'templates/description.art'), { - item, - }); + renderToString( + <> +

    + + {item.title} + +
    +

    +

    + + + {item.authors} + + +
    + + + {`https://doi.org/${item.doi}`} + + +
    + + + {item.issue} + + +
    + +

    +

    + {item.abstract} +
    +

    + + ); const items = list.map((item) => { item.description = renderDesc(item); return item; diff --git a/lib/routes/mdpi/templates/description.art b/lib/routes/mdpi/templates/description.art deleted file mode 100755 index 8e9b7767d..000000000 --- a/lib/routes/mdpi/templates/description.art +++ /dev/null @@ -1,12 +0,0 @@ -

    - {{ item.title }}
    -

    -

    - {{ item.authors }}
    - https://doi.org/{{ item.doi }}
    - {{ item.issue }}
    - -

    -

    - {{ item.abstract }}
    -

    \ No newline at end of file diff --git a/lib/routes/mercari/templates/item.art b/lib/routes/mercari/templates/item.art deleted file mode 100644 index 37f102571..000000000 --- a/lib/routes/mercari/templates/item.art +++ /dev/null @@ -1,46 +0,0 @@ -

    ¥{{ data.price}}

    - -

    -{{each data.photos}} - -{{/each}} -

    - -

    商品の説明

    -
    <%- data.description.replaceAll(`\n`,'
    ') %>
    -

    商品の情報

    -
    - 名称 - - {{item.name}} -
    - 别名 - - {{item.subName}} -
    - 中文支持 - - {{item.chinese}} -
    - 史低地区 - - {{item.lowestPriceCountry}} -
    - 史低 - - 是 -
    - 史低 - - 否 -
    - 发布日期 - - {{item.pubDate}} -
    - 当前价格 - - ¥{{item.price}} -
    - 原价 - - ¥{{item.originPrice}} -
    - 折扣地区 - - {{item.priceCountry}} -
    - 折扣 - - {{item.cutOff}}% -
    - metacritic评分 - - {{item.mcScore}} -
    - - - - - - - - - - - - - - - - - - - - - - - - -
    カテゴリー{{ data.item_category.root_category_name }} > {{ data.item_category.parent_category_name }} > {{data.item_category.name}}
    商品の状態 {{data.item_condition.name}}
    配送料の負担 {{data.shipping_payer.name}}
    配送の方法 {{data.shipping_method.name}}
    発送元の地域 {{data.shipping_from_area.name}}
    発送までの日数 {{data.shipping_duration.name}}
    - -

    出品者

    -
    - -

    {{data.seller.name}}

    -
    \ No newline at end of file diff --git a/lib/routes/mercari/templates/shopItem.art b/lib/routes/mercari/templates/shopItem.art deleted file mode 100644 index 806e4755d..000000000 --- a/lib/routes/mercari/templates/shopItem.art +++ /dev/null @@ -1,46 +0,0 @@ -

    ¥{{ price }}

    - -

    -{{each productDetail.photos}} - -{{/each}} -

    - -

    商品の説明

    -
    <%- productDetail.description.replaceAll(`\n`,'
    ') %>
    -

    商品の情報

    - - - - - - - - - - - - - - - - - - - - - - - - - -
    カテゴリー {{productDetail.categories.reverse().map(item => item.displayName).join(" > ")}}
    商品の状態 {{productDetail.condition.displayName}}
    配送料の負担 {{productDetail.shippingPayer.displayName}}
    配送の方法 {{productDetail.shippingMethod.displayName}}
    発送元の地域 {{productDetail.shippingFromArea.displayName}}
    発送までの日数 {{productDetail.shippingDuration.displayName}}
    - -

    出品者

    -
    - -

    {{productDetail.shop.displayName}}

    -
    \ No newline at end of file diff --git a/lib/routes/mercari/util.ts b/lib/routes/mercari/util.tsx similarity index 69% rename from lib/routes/mercari/util.ts rename to lib/routes/mercari/util.tsx index 203b9b9a1..9de7a2000 100644 --- a/lib/routes/mercari/util.ts +++ b/lib/routes/mercari/util.tsx @@ -1,12 +1,13 @@ import { Buffer } from 'node:buffer'; import crypto from 'node:crypto'; -import path from 'node:path'; + +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { DataItem } from '@/types'; import logger from '@/utils/logger'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { ItemDetail, SearchResponse, ShopItemDetail } from './types'; @@ -37,6 +38,106 @@ const MercariOrder = { asc: 'ORDER_ASC', } as const; +const renderItemDescription = (data: ItemDetail['data']) => + renderToString( + <> +

    ¥{data.price}

    +

    + {data.photos?.map((photo) => ( + + ))} +

    +

    商品の説明

    +
    {raw(data.description.replaceAll('\n', '
    '))}
    +

    商品の情報

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    カテゴリー + {data.item_category.root_category_name} > {data.item_category.parent_category_name} > {data.item_category.name} +
    商品の状態 {data.item_condition.name}
    配送料の負担 {data.shipping_payer.name}
    配送の方法 {data.shipping_method.name}
    発送元の地域 {data.shipping_from_area.name}
    発送までの日数 {data.shipping_duration.name}
    +

    出品者

    +
    + +

    {data.seller.name}

    +
    + + ); + +const renderShopItemDescription = (detail: ShopItemDetail) => + renderToString( + <> +

    ¥{detail.price}

    +

    + {detail.productDetail.photos?.map((photo) => ( + + ))} +

    +

    商品の説明

    +
    {raw(detail.productDetail.description.replaceAll('\n', '
    '))}
    +

    商品の情報

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    カテゴリー + {' '} + {[...detail.productDetail.categories] + .toReversed() + .map((item) => item.displayName) + .join(' > ')}{' '} +
    商品の状態 {detail.productDetail.condition.displayName}
    配送料の負担 {detail.productDetail.shippingPayer.displayName}
    配送の方法 {detail.productDetail.shippingMethod.displayName}
    発送元の地域 {detail.productDetail.shippingFromArea.displayName}
    発送までの日数 {detail.productDetail.shippingDuration.displayName}
    +

    出品者

    +
    + +

    {detail.productDetail.shop.displayName}

    +
    + + ); + function bytesToBase64URL(b: Buffer): string { return b.toString('base64').replaceAll('+', '-').replaceAll('/', '_').replaceAll('=', ''); } @@ -160,7 +261,7 @@ function generateDPOP({ uuid, method, url }: { uuid: string; method: string; url return `${signingInput}.${signature}`; } -const fetchFromMercari = async (url: string, data: any, method: 'POST' | 'GET' = 'POST'): Promise => { +const fetchFromMercari = async function fetchFromMercari(url: string, data: any, method: 'POST' | 'GET' = 'POST'): Promise { const DPOP = generateDPOP({ uuid: uuidv4(), method, @@ -296,7 +397,7 @@ const formatItemDetail = (detail: ItemDetail | ShopItemDetail): DataItem => { const shopItemDetail = detail as ShopItemDetail; return { title: shopItemDetail.displayName, - description: art(path.join(__dirname, 'templates/shopItem.art'), shopItemDetail), + description: renderShopItemDescription(shopItemDetail), pubDate: parseDate(shopItemDetail.createTime), guid: shopItemDetail.name, link: `${rootShopProductURL}${shopItemDetail.name}`, @@ -310,7 +411,7 @@ const formatItemDetail = (detail: ItemDetail | ShopItemDetail): DataItem => { const itemDetail = detail as ItemDetail; return { title: itemDetail.data.name, - description: art(path.join(__dirname, 'templates/item.art'), itemDetail), + description: renderItemDescription(itemDetail.data), pubDate: parseDate(itemDetail.data.created * 1000), guid: itemDetail.data.id, link: `${rootProductURL}${itemDetail.data.id}`, diff --git a/lib/routes/metacritic/index.ts b/lib/routes/metacritic/index.tsx similarity index 87% rename from lib/routes/metacritic/index.ts rename to lib/routes/metacritic/index.tsx index 3193d46da..9f402f8ce 100644 --- a/lib/routes/metacritic/index.ts +++ b/lib/routes/metacritic/index.tsx @@ -1,14 +1,30 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { sorts, types } from './util'; +const renderDescription = (image, description, score) => + renderToString( + <> + {image ? ( +
    + {image.alt} +
    + ) : null} + {description ?

    {description}

    : null} + {score ? ( + <> + Metascore: + {score} + + ) : null} + + ); + export const route: Route = { path: '/:type?/:sort?/:filter?', name: 'Unknown', @@ -103,16 +119,16 @@ async function handler(ctx) { const items = response.data.items.slice(0, limit).map((item) => ({ title: item.title, link: new URL(`${type}/${item.slug}`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.image + description: renderDescription( + item.image ? { src: new URL(`a/img/catalog${item.image.bucketPath}`, rootUrl).href, alt: item.image.alt, } : undefined, - description: item.description, - score: item.criticScoreSummary?.score ?? undefined, - }), + item.description, + item.criticScoreSummary?.score ?? undefined + ), category: item.genres?.map((c) => c.name), guid: `metacritic-${item.id}`, pubDate: parseDate(item.releaseDate), diff --git a/lib/routes/metacritic/templates/description.art b/lib/routes/metacritic/templates/description.art deleted file mode 100644 index 1ee8bd077..000000000 --- a/lib/routes/metacritic/templates/description.art +++ /dev/null @@ -1,19 +0,0 @@ -{{ if image }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if description }} -

    {{ description }}

    -{{ /if }} - -{{ if score }} - Metascore: - {{ score }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/meteor/templates/desc.art b/lib/routes/meteor/templates/desc.art deleted file mode 100644 index 96c2464f3..000000000 --- a/lib/routes/meteor/templates/desc.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if youTube }} - -{{ else if img }} - -{{ else if video }} - -{{ /if }} diff --git a/lib/routes/meteor/templates/desc.tsx b/lib/routes/meteor/templates/desc.tsx new file mode 100644 index 000000000..916b91ea2 --- /dev/null +++ b/lib/routes/meteor/templates/desc.tsx @@ -0,0 +1,21 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type MediaProps = { + youTube?: string; + img?: string; + video?: string; +}; + +const Media = ({ youTube, img, video }: MediaProps) => ( + <> + {youTube ? ( + + ) : img ? ( + + ) : video ? ( + + ) : null} + +); + +export const renderMedia = (props: MediaProps): string => renderToString(); diff --git a/lib/routes/meteor/utils.ts b/lib/routes/meteor/utils.ts index 4facb1f54..8b7de2293 100644 --- a/lib/routes/meteor/utils.ts +++ b/lib/routes/meteor/utils.ts @@ -1,7 +1,6 @@ -import path from 'node:path'; - import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderMedia } from './templates/desc'; const baseUrl = 'https://meteor.today'; @@ -36,7 +35,7 @@ const renderDesc = (desc) => { if (matchYouTube) { desc = desc.replaceAll( youTube, - art(path.join(__dirname, 'templates/desc.art'), { + renderMedia({ youTube: '$1', }) ); @@ -45,7 +44,7 @@ const renderDesc = (desc) => { for (const img of matchImgur) { desc = desc.replace( img, - art(path.join(__dirname, 'templates/desc.art'), { + renderMedia({ img, }) ); @@ -55,7 +54,7 @@ const renderDesc = (desc) => { for (const video of matchVideo) { desc = desc.replace( video, - art(path.join(__dirname, 'templates/desc.art'), { + renderMedia({ video, }) ); @@ -65,7 +64,7 @@ const renderDesc = (desc) => { for (const sticker of matchSticker) { desc = desc.replace( sticker, - art(path.join(__dirname, 'templates/desc.art'), { + renderMedia({ img: sticker, }) ); @@ -75,7 +74,7 @@ const renderDesc = (desc) => { for (const emoji of matchEmoji) { desc = desc.replace( emoji, - art(path.join(__dirname, 'templates/desc.art'), { + renderMedia({ img: emoji, }) ); diff --git a/lib/routes/mi/templates/crowdfunding.art b/lib/routes/mi/templates/crowdfunding.art deleted file mode 100644 index a58e19aca..000000000 --- a/lib/routes/mi/templates/crowdfunding.art +++ /dev/null @@ -1,28 +0,0 @@ - -
    -{{ project_name }} -
    -{{ project_desc }} -
    -众筹价:{{ price }} 元,建议零售价:{{ product_market_price }} 元 -
    -众筹开始:{{ start_time_desc }},众筹结束:{{ end_time_desc }} -
    -物流:{{ send_info }} -
    - - - - - - - - {{ each support_list }} - - - - - - {{ /each }} - -
    档位价格描述
    {{ $value.name }}{{ $value.price }} 元{{ $value.support_desc }}
    diff --git a/lib/routes/mi/utils.ts b/lib/routes/mi/utils.tsx similarity index 57% rename from lib/routes/mi/utils.ts rename to lib/routes/mi/utils.tsx index cfe391239..464c60aa6 100644 --- a/lib/routes/mi/utils.ts +++ b/lib/routes/mi/utils.tsx @@ -1,15 +1,13 @@ import 'dayjs/locale/zh-cn.js'; -import path from 'node:path'; - import dayjs from 'dayjs'; import localizedFormat from 'dayjs/plugin/localizedFormat.js'; import timezone from 'dayjs/plugin/timezone.js'; import utc from 'dayjs/plugin/utc.js'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; import type { CrowdfundingData, CrowdfundingDetailData, CrowdfundingDetailInfo, CrowdfundingItem, CrowdfundingList, DataResponse } from './types'; @@ -18,9 +16,9 @@ dayjs.extend(timezone); dayjs.extend(utc); /** - * 获取众筹项目列表 + * Fetch the list of crowdfunding projects. * - * @returns {Promise} 众筹项目列表。 + * @returns {Promise} The crowdfunding project list. */ export const getCrowdfundingList = async (): Promise => { const response = await ofetch>('https://m.mi.com/v1/crowd/crowd_home', { @@ -33,10 +31,10 @@ export const getCrowdfundingList = async (): Promise => { }; /** - * 获取众筹项目详情并缓存 + * Fetch and cache crowdfunding project details. * - * @param {CrowdfundingItem} item - 众筹项目。 - * @returns {Promise} 众筹项目详情。 + * @param {CrowdfundingItem} item - Crowdfunding item. + * @returns {Promise} Crowdfunding item details. */ export const getCrowdfundingItem = (item: CrowdfundingItem): Promise => cache.tryGet(`mi:crowdfunding:${item.project_id}`, async () => { @@ -49,28 +47,61 @@ export const getCrowdfundingItem = (item: CrowdfundingItem): Promise; +const CrowdfundingDescription = ({ item }: { item: CrowdfundingDetailInfo }) => ( + <> + +
    + {item.project_name} +
    + {item.project_desc} +
    + 众筹价:{item.price} 元,建议零售价:{item.product_market_price} 元 +
    + 众筹开始:{item.start_time_desc},众筹结束:{item.end_time_desc} +
    + 物流:{item.send_info} +
    + + + + + + + + {item.support_list.map((support, index) => ( + + + + + + ))} + +
    档位价格描述
    {support.name}{support.price} 元{support.support_desc}
    + +); + /** - * 渲染众筹项目模板 + * Render the crowdfunding item description. * - * @param {CrowdfundingDetailInfo} item - 众筹项目详情。 - * @returns {string} 渲染后的众筹项目模板字符串。 + * @param {CrowdfundingDetailInfo} item - Crowdfunding item details. + * @returns {string} Rendered description HTML. */ -export const renderCrowdfunding = (item: CrowdfundingDetailInfo): string => art(path.join(__dirname, 'templates/crowdfunding.art'), item); +export const renderCrowdfunding = (item: CrowdfundingDetailInfo): string => renderToString(); const formatDate = (timestamp: number): string => dayjs.unix(timestamp).tz('Asia/Shanghai').locale('zh-cn').format('lll'); diff --git a/lib/routes/mihoyo/bbs/follow-list.ts b/lib/routes/mihoyo/bbs/follow-list.ts index e5e3a86cb..219f85dc8 100644 --- a/lib/routes/mihoyo/bbs/follow-list.ts +++ b/lib/routes/mihoyo/bbs/follow-list.ts @@ -1,13 +1,9 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; +import { renderDescription } from '../templates/description'; import cache from './cache'; -const renderDescription = (description, images) => art(path.join(__dirname, '../templates/description.art'), { description, images }); - export const route: Route = { path: '/bbs/follow-list/:uid', categories: ['game'], diff --git a/lib/routes/mihoyo/bbs/official.ts b/lib/routes/mihoyo/bbs/official.ts index 180f5489c..fc3a6c4b0 100644 --- a/lib/routes/mihoyo/bbs/official.ts +++ b/lib/routes/mihoyo/bbs/official.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import logger from '@/utils/logger'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderOfficialDescription } from '../templates/official'; // 游戏id const GITS_MAP = { @@ -86,11 +85,7 @@ const getPostContent = async (row, default_gid = '2') => { const author = fullRow?.user?.nickname || ''; const content = fullRow?.post?.content || ''; const tags = fullRow?.topics?.map((item) => item.name) || []; - const description = art(path.join(__dirname, '../templates/official.art'), { - hasCover: post.has_cover, - coverList: row.cover_list, - content, - }); + const description = renderOfficialDescription(post.has_cover, row.cover_list, content); return { // 文章标题 title: post.subject, diff --git a/lib/routes/mihoyo/bbs/utils.ts b/lib/routes/mihoyo/bbs/utils.ts index f170fbe19..2f1662a85 100644 --- a/lib/routes/mihoyo/bbs/utils.ts +++ b/lib/routes/mihoyo/bbs/utils.ts @@ -1,9 +1,6 @@ -import path from 'node:path'; - import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; -const renderDescription = (description, images) => art(path.join(__dirname, '../templates/description.art'), { description, images }); +import { renderDescription } from '../templates/description'; const post2item = (e) => { const author = e.user.nickname; diff --git a/lib/routes/mihoyo/templates/description.art b/lib/routes/mihoyo/templates/description.art deleted file mode 100644 index c195aad3b..000000000 --- a/lib/routes/mihoyo/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ description }} -{{if images}} - {{each images}} - - {{/each}} -{{/if}} diff --git a/lib/routes/mihoyo/templates/description.tsx b/lib/routes/mihoyo/templates/description.tsx new file mode 100644 index 000000000..6e31f3bef --- /dev/null +++ b/lib/routes/mihoyo/templates/description.tsx @@ -0,0 +1,9 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +export const renderDescription = (description: string, images?: string[]) => + renderToString( + <> + {description} + {images?.length ? images.map((image) => ) : null} + + ); diff --git a/lib/routes/mihoyo/templates/official.art b/lib/routes/mihoyo/templates/official.art deleted file mode 100644 index 8744cb1e6..000000000 --- a/lib/routes/mihoyo/templates/official.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if hasCover }} - {{ each coverList c }} -
    - {{ /each }} -{{ /if }} - -{{@ content }} diff --git a/lib/routes/mihoyo/templates/official.tsx b/lib/routes/mihoyo/templates/official.tsx new file mode 100644 index 000000000..958957ed4 --- /dev/null +++ b/lib/routes/mihoyo/templates/official.tsx @@ -0,0 +1,21 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type CoverItem = { + url?: string; +}; + +export const renderOfficialDescription = (hasCover: boolean, coverList: CoverItem[], content: string) => + renderToString( + <> + {hasCover + ? coverList.map((cover) => ( + <> + +
    + + )) + : null} + {content ? <>{raw(content)} : null} + + ); diff --git a/lib/routes/mindmeister/example.ts b/lib/routes/mindmeister/example.tsx similarity index 89% rename from lib/routes/mindmeister/example.ts rename to lib/routes/mindmeister/example.tsx index 069d61028..d5187ab91 100644 --- a/lib/routes/mindmeister/example.ts +++ b/lib/routes/mindmeister/example.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const baseUrl = 'https://www.mindmeister.com'; @@ -73,12 +71,10 @@ async function handler(ctx) { .match(/url\('(.*)'\);/)[1] ).href; + const title = item.find('.title').text(); return { - title: item.find('.title').text(), - description: art(path.join(__dirname, 'templates/image.art'), { - src: imageUrl.split('?')[0], - alt: item.find('.title').text().trim(), - }), + title, + description: renderToString({title.trim()}), link: item.find('.title').attr('href'), author: item.find('.author').text().trim().replace(/^by/, ''), category: item.find('.fw-bold').text(), diff --git a/lib/routes/mindmeister/templates/image.art b/lib/routes/mindmeister/templates/image.art deleted file mode 100644 index 40140a947..000000000 --- a/lib/routes/mindmeister/templates/image.art +++ /dev/null @@ -1 +0,0 @@ -{{ alt }} diff --git a/lib/routes/mingpao/index.ts b/lib/routes/mingpao/index.tsx similarity index 86% rename from lib/routes/mingpao/index.ts rename to lib/routes/mingpao/index.tsx index b79613afb..b2273ea22 100644 --- a/lib/routes/mingpao/index.ts +++ b/lib/routes/mingpao/index.tsx @@ -1,24 +1,38 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import parser from '@/utils/rss-parser'; -const renderFanBox = (media) => - art(path.join(__dirname, 'templates/fancybox.art'), { - media, - }); +const renderFanBox = (media): string => + renderToString( + <> + {media?.map((item, index) => + item.video ? ( + + ) : ( +
    + {item.title} +
    {item.title}
    +
    + ) + )} + + ); -const renderDesc = (media, desc) => - art(path.join(__dirname, 'templates/description.art'), { - media: renderFanBox(media), - desc, - }); +const renderDesc = (media, desc): string => + renderToString( + <> + {raw(renderFanBox(media))} + {raw(desc)} + + ); const fixFancybox = (element, $) => { const $e = $(element); diff --git a/lib/routes/mingpao/templates/description.art b/lib/routes/mingpao/templates/description.art deleted file mode 100644 index 3439085cd..000000000 --- a/lib/routes/mingpao/templates/description.art +++ /dev/null @@ -1,2 +0,0 @@ -{{@ media }} -{{@ desc }} diff --git a/lib/routes/mingpao/templates/fancybox.art b/lib/routes/mingpao/templates/fancybox.art deleted file mode 100644 index 07e8ce97b..000000000 --- a/lib/routes/mingpao/templates/fancybox.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ each media }} - {{ if $value.video }} - - {{ else }} -
    {{ $value.title }}
    {{ $value.title }}
    - {{ /if }} -{{ /each }} diff --git a/lib/routes/missav/new.ts b/lib/routes/missav/new.tsx similarity index 88% rename from lib/routes/missav/new.ts rename to lib/routes/missav/new.tsx index d8c170654..a023c8d74 100644 --- a/lib/routes/missav/new.ts +++ b/lib/routes/missav/new.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - // import ofetch from '@/utils/ofetch'; import * as cheerio from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; // import { config } from '@/config'; import puppeteer from '@/utils/puppeteer'; -import { art } from '@/utils/render'; const urlPath = 'dm514/new'; @@ -72,11 +70,11 @@ async function handler() { return { title: title.text().trim(), link: title.attr('href'), - description: art(path.join(__dirname, 'templates/preview.art'), { - poster: poster.href, - video, - type: video.split('.').pop(), - }), + description: renderToString( + + ), }; }); diff --git a/lib/routes/missav/templates/preview.art b/lib/routes/missav/templates/preview.art deleted file mode 100644 index 5cad91c8d..000000000 --- a/lib/routes/missav/templates/preview.art +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/lib/routes/misskey/templates/note.art b/lib/routes/misskey/templates/note.art deleted file mode 100644 index b4922d5ac..000000000 --- a/lib/routes/misskey/templates/note.art +++ /dev/null @@ -1,29 +0,0 @@ -{{ if reply }} -
    -

    {{ reply.text }}

    -
    -{{ /if }} - -{{ if text }} -

    {{ text.replace(/\n/g, '
    ') }}

    -{{ /if }} - -{{ each files file }} -
    - {{ if file.type.includes('image') }} - - {{ else if file.type.includes('video') }} - - {{ else if file.type.includes('audio') }} - - {{ else }} - {{ file.name }} - {{ /if }} - {{ if file.comment }} -

    {{ file.comment }}

    - {{ /if }} -{{ /each }} diff --git a/lib/routes/misskey/utils.ts b/lib/routes/misskey/utils.tsx similarity index 75% rename from lib/routes/misskey/utils.ts rename to lib/routes/misskey/utils.tsx index db788ab6d..11b66ae82 100644 --- a/lib/routes/misskey/utils.ts +++ b/lib/routes/misskey/utils.tsx @@ -1,14 +1,46 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { MisskeyNote, MisskeyUser } from './types'; const allowSiteList = ['misskey.io', 'madost.one', 'mk.nixnet.social']; +const renderDescription = ({ reply, text, files }) => + renderToString( + <> + {reply ? ( +
    +

    {reply.text}

    +
    + ) : null} + {text ?

    {text.replaceAll('\n', '
    ')}

    : null} + {(files ?? []).map((file) => ( + <> +
    + {file.type.includes('image') ? ( + + ) : file.type.includes('video') ? ( + + ) : file.type.includes('audio') ? ( + + ) : ( + + {file.name} + + )} + {file.comment ?

    {file.comment}

    : null} + + ))} + + ); + const parseNotes = (data: MisskeyNote[], site: string, simplifyAuthor: boolean = false) => data.map((item: MisskeyNote) => { const isRenote = item.renote && Object.keys(item.renote).length > 0; @@ -18,11 +50,10 @@ const parseNotes = (data: MisskeyNote[], site: string, simplifyAuthor: boolean = const host = noteToUse.user.host ?? site; const author = simplifyAuthor ? String(noteToUse.user.name) : `${noteToUse.user.name} (${noteToUse.user.username}@${host})`; - const description = art(path.join(__dirname, 'templates/note.art'), { + const description = renderDescription({ text: noteToUse.text, files: noteToUse.files, reply: item.reply, - site, }); let title = ''; diff --git a/lib/routes/mittrchina/index.ts b/lib/routes/mittrchina/index.tsx similarity index 84% rename from lib/routes/mittrchina/index.ts rename to lib/routes/mittrchina/index.tsx index b4fba8b5c..762ce6afa 100644 --- a/lib/routes/mittrchina/index.ts +++ b/lib/routes/mittrchina/index.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:type?', @@ -73,13 +72,15 @@ async function handler(ctx) { category: article.typeName, description: type === 'video' - ? art(path.join(__dirname, 'templates/movie.art'), { - poster: article.img, - video: { - address: article.address, - type: article.address.split('.').pop(), - }, - }) + ? renderToString( + + ) : type === 'breaking' ? article.content : article.summary, @@ -123,3 +124,10 @@ async function handler(ctx) { item: items, }; } + +const VideoDescription = ({ poster, video }: { poster?: string; video?: { address?: string; type?: string } }) => + video ? ( + + ) : null; diff --git a/lib/routes/mittrchina/templates/movie.art b/lib/routes/mittrchina/templates/movie.art deleted file mode 100644 index c0a1384d8..000000000 --- a/lib/routes/mittrchina/templates/movie.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if video }} - -{{ /if }} diff --git a/lib/routes/modelscope/community.ts b/lib/routes/modelscope/community.tsx similarity index 80% rename from lib/routes/modelscope/community.ts rename to lib/routes/modelscope/community.tsx index 5f0db5747..341202c55 100644 --- a/lib/routes/modelscope/community.ts +++ b/lib/routes/modelscope/community.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -33,6 +32,20 @@ export const route: Route = { url: 'community.modelscope.cn/', }; +const renderDescription = (thumb, quote, content) => + renderToString( + <> + {thumb ? ( + <> + +
    + + ) : null} + {quote ?
    {quote}
    : null} + {content ? <>{raw(content)} : null} + + ); + async function handler(ctx) { const baseUrl = 'https://community.modelscope.cn'; @@ -62,11 +75,7 @@ async function handler(ctx) { .match(/window\.__INITIAL_STATE__\s*=\s*({.*?});/)[1] ); - item.description = art(path.join(__dirname, 'templates/community.art'), { - thumb: item.thumb, - quote: item.description, - content: initialData.pageData.detail.ext.content, - }); + item.description = renderDescription(item.thumb, item.description, initialData.pageData.detail.ext.content); return item; }) diff --git a/lib/routes/modelscope/datasets.ts b/lib/routes/modelscope/datasets.ts index e8910596c..ae20dbc93 100644 --- a/lib/routes/modelscope/datasets.ts +++ b/lib/routes/modelscope/datasets.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import MarkdownIt from 'markdown-it'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/desc'; const md = MarkdownIt({ html: true, @@ -66,7 +65,7 @@ async function handler(ctx) { const { data } = await got(`${baseUrl}/api/v1/datasets${item.slug}`); const content = data.Data.ReadmeContent.replaceAll(/img src="(?!http)(.*?)"/g, `img src="${baseUrl}/api/v1/datasets${item.slug}/repo?Revision=master&FilePath=$1&View=true"`); - item.description = art(path.join(__dirname, 'templates/desc.art'), { + item.description = renderDescription({ description: item.description, md: md.render(content), }); diff --git a/lib/routes/modelscope/studios.ts b/lib/routes/modelscope/studios.ts index 46b5faacb..496aa85a9 100644 --- a/lib/routes/modelscope/studios.ts +++ b/lib/routes/modelscope/studios.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import MarkdownIt from 'markdown-it'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/desc'; const md = MarkdownIt({ html: true, @@ -66,7 +65,7 @@ async function handler(ctx) { const { data } = await got(`${baseUrl}/api/v1/studio${item.slug}`); const content = data.Data.ReadMeContent; - item.description = art(path.join(__dirname, 'templates/desc.art'), { + item.description = renderDescription({ coverImage: item.coverImage, description: item.description, md: md.render(content), diff --git a/lib/routes/modelscope/templates/community.art b/lib/routes/modelscope/templates/community.art deleted file mode 100644 index 7dafedaa7..000000000 --- a/lib/routes/modelscope/templates/community.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if thumb }} -
    -{{ /if }} - -{{ if quote }} -
    {{ quote }}
    -{{ /if }} - -{{ if content }} - {{@ content }} -{{ /if }} diff --git a/lib/routes/modelscope/templates/desc.art b/lib/routes/modelscope/templates/desc.art deleted file mode 100644 index 5a0d9d950..000000000 --- a/lib/routes/modelscope/templates/desc.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if coverImage }} -
    -{{ /if }} - -{{ if description }} - {{ description }}
    -{{ /if }} - -{{@ md }} diff --git a/lib/routes/modelscope/templates/desc.tsx b/lib/routes/modelscope/templates/desc.tsx new file mode 100644 index 000000000..357aa5f60 --- /dev/null +++ b/lib/routes/modelscope/templates/desc.tsx @@ -0,0 +1,27 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + coverImage?: string; + description?: string; + md?: string; +}; + +export const renderDescription = ({ coverImage, description, md }: DescriptionData) => + renderToString( + <> + {coverImage ? ( + <> + +
    + + ) : null} + {description ? ( + <> + {description} +
    + + ) : null} + {md ? <>{raw(md)} : null} + + ); diff --git a/lib/routes/modrinth/templates/version.art b/lib/routes/modrinth/templates/version.art deleted file mode 100644 index 6841cd360..000000000 --- a/lib/routes/modrinth/templates/version.art +++ /dev/null @@ -1,8 +0,0 @@ -

    {{ name }} - {{ version_number }}

    -

    Loaders: {{ each loaders }}{{ $value }} {{ /each }}

    -

    Game Versions: {{ each game_versions }}{{ $value }} {{ /each }}

    - -{{@ changelog }} - -

    Files:

    -{{ each files }}

    {{ $value.filename }}

    {{ /each }} diff --git a/lib/routes/modrinth/versions.ts b/lib/routes/modrinth/versions.tsx similarity index 79% rename from lib/routes/modrinth/versions.ts rename to lib/routes/modrinth/versions.tsx index 887be99bb..15eab1f37 100644 --- a/lib/routes/modrinth/versions.ts +++ b/lib/routes/modrinth/versions.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import MarkdownIt from 'markdown-it'; import { config } from '@/config'; @@ -8,7 +8,6 @@ import type { Author, Project, Version } from '@/routes/modrinth/api'; import type { Route } from '@/types'; import _ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const ofetch = _ofetch.create({ headers: { @@ -21,6 +20,30 @@ const md = MarkdownIt({ html: true, }); +const renderVersion = (version: Version & { changelog?: string }) => + renderToString( + <> +

    + {version.name} - {version.version_number} +

    +

    + Loaders: + {version.loaders?.map((loader) => `${loader} `)} +

    +

    + Game Versions: + {version.game_versions?.map((gameVersion) => `${gameVersion} `)} +

    + {version.changelog ? raw(version.changelog) : null} +

    Files:

    + {version.files?.map((file) => ( +

    + {file.filename} +

    + ))} + + ); + export const route: Route = { path: '/project/:id/versions/:routeParams?', categories: ['game'], @@ -67,12 +90,10 @@ export const route: Route = { }; async function handler(ctx: Context) { - const { id, routeParams } = < - { - id: string; - routeParams?: string; - } - >ctx.req.param(); + const { id, routeParams } = ctx.req.param() as { + id: string; + routeParams?: string; + }; /** * /@type {{ @@ -96,7 +117,7 @@ async function handler(ctx: Context) { ids: JSON.stringify([...new Set(versions.map((it) => it.author_id))]), }, }); - const groupedAuthors = >{}; + const groupedAuthors: Record = {}; for (const author of authors) { groupedAuthors[author.id] = author; } @@ -109,7 +130,7 @@ async function handler(ctx: Context) { title: `${it.name} for ${it.loaders.join('/')} on ${[...new Set([it.game_versions[0], it.game_versions.at(-1)])].join('-')}`, link: `https://modrinth.com/project/${id}/version/${it.version_number}`, pubDate: parseDate(it.date_published), - description: art(path.join(__dirname, 'templates/version.art'), { + description: renderVersion({ ...it, changelog: md.render(it.changelog), }), diff --git a/lib/routes/musikguru/news.ts b/lib/routes/musikguru/news.ts index 0b8fc652a..8409768c3 100644 --- a/lib/routes/musikguru/news.ts +++ b/lib/routes/musikguru/news.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); @@ -35,7 +34,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('h5.card-title').text(); const image: string | undefined = $el.find('img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -75,11 +74,8 @@ export const handler = async (ctx: Context): Promise => { const $$: CheerioAPI = load(detailResponse); const title: string = $$('div.article h1').text(); - const description: string | undefined = - item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: ($$('p.lead').html() ?? '') + ($$('div.lead').html() ?? ''), - }); + const leadHtml = ($$('p.lead').html() ?? '') + ($$('div.lead').html() ?? ''); + const description: string | undefined = item.description + renderDescription({ description: leadHtml || undefined }); const pubDateStr: string | undefined = $$('div.article div.text-muted').text().split(/\sUhr/)?.[0]; const image: string | undefined = $$('div.article img').first().attr('src'); const upDatedStr: string | undefined = pubDateStr; diff --git a/lib/routes/musikguru/templates/description.art b/lib/routes/musikguru/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/musikguru/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/musikguru/templates/description.tsx b/lib/routes/musikguru/templates/description.tsx new file mode 100644 index 000000000..f7661df1e --- /dev/null +++ b/lib/routes/musikguru/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/mydrivers/index.ts b/lib/routes/mydrivers/index.tsx similarity index 88% rename from lib/routes/mydrivers/index.ts rename to lib/routes/mydrivers/index.tsx index ef18a3721..9611b8fdc 100644 --- a/lib/routes/mydrivers/index.ts +++ b/lib/routes/mydrivers/index.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { categories, convertToQueryString, getInfo, processItems, rootUrl, title } from './util'; @@ -82,9 +80,15 @@ async function handler(ctx) { return { title: item.find('div.news_title').text(), link: new URL(item.find('div.news_title span.newst a').prop('href'), rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { - image: item.find('a.newsimg img').prop('src'), - }), + description: renderToString( + <> + {item.find('a.newsimg img').prop('src') ? ( +
    + +
    + ) : null} + + ), author: item.find('p.tname').text(), guid: item.prop('data-id'), pubDate: timezone(parseDate(item.find('p.ttime').text()), +8), diff --git a/lib/routes/mydrivers/templates/description.art b/lib/routes/mydrivers/templates/description.art deleted file mode 100644 index 779c2f675..000000000 --- a/lib/routes/mydrivers/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if image }} -
    - -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/myfans/post.ts b/lib/routes/myfans/post.tsx similarity index 73% rename from lib/routes/myfans/post.ts rename to lib/routes/myfans/post.tsx index 14aafa7c7..424abee90 100644 --- a/lib/routes/myfans/post.ts +++ b/lib/routes/myfans/post.tsx @@ -1,8 +1,8 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { baseUrl, getPostByAccountId, showByUsername } from './utils'; @@ -30,11 +30,20 @@ export const route: Route = { handler, }; -const render = (postImages, body) => - art(path.join(__dirname, 'templates/post.art'), { - postImages, - body, - }); +const renderDescription = (postImages, body: string): string => + renderToString( + <> + {postImages + ? postImages.map((image) => ( + <> + +
    + + )) + : null} + {body ? raw(body) : null} + + ); async function handler(ctx) { const { username } = ctx.req.param(); @@ -44,7 +53,7 @@ async function handler(ctx) { const items = posts.map((p) => ({ title: p.body?.replaceAll('\r\n', ' ').trim().split(' ')[0], - description: render(p.post_images, p.body?.replaceAll('\r\n', '
    ')), + description: renderDescription(p.post_images, p.body?.replaceAll('\r\n', '
    ')), pubDate: parseDate(p.published_at), link: `${baseUrl}/posts/${p.id}`, author: p.user.name, diff --git a/lib/routes/myfans/templates/post.art b/lib/routes/myfans/templates/post.art deleted file mode 100644 index a4a138169..000000000 --- a/lib/routes/myfans/templates/post.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if postImages }} - {{ each postImages img }} -
    - {{ /each }} -{{ /if }} -{{ if body }} - {{@ body }} -{{ /if }} diff --git a/lib/routes/myfigurecollection/activity.ts b/lib/routes/myfigurecollection/activity.tsx similarity index 87% rename from lib/routes/myfigurecollection/activity.ts rename to lib/routes/myfigurecollection/activity.tsx index 8a488e60c..e80830773 100644 --- a/lib/routes/myfigurecollection/activity.ts +++ b/lib/routes/myfigurecollection/activity.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { isValidHost } from '@/utils/valid-host'; @@ -98,9 +96,9 @@ async function handler(ctx) { link: `${rootUrl}${item.find('.stamp-anchor .tbx-tooltip').attr('href')}`, pubDate: timezone(parseDate(item.find('.activity-time span').attr('title')), +0), author: item.find('.user-anchor').text(), - description: art(path.join(__dirname, 'templates/activity.art'), { - changelog: item.find('.changelog').text(), - pictures: item + description: renderDescription( + item.find('.changelog').text(), + item .find('.picture-icon') .toArray() .map((image) => @@ -108,8 +106,8 @@ async function handler(ctx) { .html() .match(/url\((.*)\)/)[1] .replace(/\/thumbnails/, '') - ), - }), + ) + ), }; }); @@ -121,3 +119,13 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (changelog: string, pictures: string[]): string => + renderToString( + <> + {changelog ? <>Changed field: {changelog} : null} + {pictures?.map((picture, index) => ( + + ))} + + ); diff --git a/lib/routes/myfigurecollection/index.ts b/lib/routes/myfigurecollection/index.tsx similarity index 71% rename from lib/routes/myfigurecollection/index.ts rename to lib/routes/myfigurecollection/index.tsx index 3c81d92a4..3fc964f95 100644 --- a/lib/routes/myfigurecollection/index.ts +++ b/lib/routes/myfigurecollection/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import { isValidHost } from '@/utils/valid-host'; const shortcuts = { @@ -84,17 +82,15 @@ async function handler(ctx) { const content = load(detailResponse.data); item.title = content('.headline').text(); - item.description = art(path.join(__dirname, 'templates/description.art'), { - pictures: /myfigurecollection\.net\/picture\//.test(item.link) - ? [{ src: content('meta[property="og:image"]').attr('content') }] - : JSON.parse(decodeURIComponent(content('meta[name="pictures"]').attr('content'))), - fields: content('.form-field') + item.description = renderDescription( + /myfigurecollection\.net\/picture\//.test(item.link) ? [{ src: content('meta[property="og:image"]').attr('content') }] : JSON.parse(decodeURIComponent(content('meta[name="pictures"]').attr('content'))), + content('.form-field') .toArray() .map((f) => ({ key: content(f).find('.form-label').text(), value: content(f).find('.form-input').text(), - })), - }); + })) + ); } catch { item.title = `Item #${item.link.split('/').pop()}`; } @@ -112,3 +108,24 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (pictures: Array<{ src?: string; w?: string; h?: string }>, fields: Array<{ key: string; value: string }>): string => + renderToString( + <> + {pictures?.map((picture, index) => ( + + ))} + {fields.length ? ( + + + {fields.map((field, index) => ( + + + + + ))} + +
    {field.key}{field.value}
    + ) : null} + + ); diff --git a/lib/routes/myfigurecollection/templates/activity.art b/lib/routes/myfigurecollection/templates/activity.art deleted file mode 100644 index 5a1870800..000000000 --- a/lib/routes/myfigurecollection/templates/activity.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if changelog }} -Changed field: {{ changelog }} -{{ /if }} -{{ if pictures }} -{{ each pictures picture }} - -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/myfigurecollection/templates/description.art b/lib/routes/myfigurecollection/templates/description.art deleted file mode 100644 index 19fb4983d..000000000 --- a/lib/routes/myfigurecollection/templates/description.art +++ /dev/null @@ -1,24 +0,0 @@ -{{ if pictures }} -{{ each pictures picture }} - -{{ /each }} -{{ /if }} -{{ if fields.length != 0 }} - - -{{ each fields field }} - - - - -{{ /each }} - -
    {{ field.key }}{{ field.value }}
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/mymusicsheet/templates/description.art b/lib/routes/mymusicsheet/templates/description.art deleted file mode 100644 index ad498e8de..000000000 --- a/lib/routes/mymusicsheet/templates/description.art +++ /dev/null @@ -1,40 +0,0 @@ -
    - {{if youtubeId}} - - {{/if}} - - {{if content.musicName}} -

    Music Name: {{content.musicName}}

    - {{/if}} - - {{if content.musicMemo}} -

    Music Memo: {{content.musicMemo}}

    - {{/if}} - - {{if content.musicianName}} -

    Musician Name: {{content.musicianName}}

    - {{/if}} - - {{if content.instruments && content.instruments.length}} -

    Instruments: - {{each content.instruments}}{{$value}} {{/each}} -

    - {{/if}} - - {{if content.status}} -

    Status: {{content.status}}

    - {{/if}} - - {{if content.price}} -

    Price: {{content.price}}

    - {{/if}} -
    diff --git a/lib/routes/mymusicsheet/usersheets.ts b/lib/routes/mymusicsheet/usersheets.tsx similarity index 81% rename from lib/routes/mymusicsheet/usersheets.ts rename to lib/routes/mymusicsheet/usersheets.tsx index caaa5b8f6..2de5d3e5a 100644 --- a/lib/routes/mymusicsheet/usersheets.ts +++ b/lib/routes/mymusicsheet/usersheets.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/user/sheets/:username/:iso?/:freeOnly?', @@ -196,10 +195,7 @@ async function handler(ctx) { link: `${baseUrl}/${username}/${item.sheetId}`, guid: `https://www.mymusicsheet.com/${username}/${item.sheetId}`, itunes_item_image: item.author.profileUrl, - description: art(path.join(__dirname, 'templates/description.art'), { - youtubeId, - content, - }), + description: renderToString(), author: item.author.name, pubDate: parseDate(item.createdAt), }; @@ -213,3 +209,44 @@ async function handler(ctx) { item: items, }; } + +const MymusicSheetDescription = ({ + youtubeId, + content, +}: { + youtubeId?: string; + content: { + musicName?: string; + musicMemo?: string; + musicianName?: string; + instruments?: string[]; + status?: string; + price?: string; + }; +}) => ( +
    + {youtubeId ? ( + + ) : null} + {content.musicName ?

    Music Name: {content.musicName}

    : null} + {content.musicMemo ?

    Music Memo: {content.musicMemo}

    : null} + {content.musicianName ?

    Musician Name: {content.musicianName}

    : null} + {content.instruments && content.instruments.length ? ( +

    + Instruments: + {content.instruments.map((instrument) => ` ${instrument}`)} +

    + ) : null} + {content.status ?

    Status: {content.status}

    : null} + {content.price ?

    Price: {content.price}

    : null} +
    +); diff --git a/lib/routes/natgeo/dailyphoto.ts b/lib/routes/natgeo/dailyphoto.tsx similarity index 78% rename from lib/routes/natgeo/dailyphoto.ts rename to lib/routes/natgeo/dailyphoto.tsx index ac69a0052..347b66384 100644 --- a/lib/routes/natgeo/dailyphoto.ts +++ b/lib/routes/natgeo/dailyphoto.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; @@ -8,7 +8,17 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const renderDescription = (img) => + renderToString( + <> + {img?.altText} +
    +

    {img?.ttl ? raw(img.ttl) : null}

    +

    {img?.dsc ? raw(img.dsc) : null}

    +

    {img?.crdt ? raw(img.crdt) : null}

    + + ); export const route: Route = { path: '/dailyphoto', @@ -46,9 +56,7 @@ async function handler() { const items = media.map((item) => ({ title: item.meta.title, - description: art(path.join(__dirname, 'templates/dailyPhoto.art'), { - img: item.img, - }), + description: renderDescription(item.img), link: rootUrl + item.locator, pubDate: parseDate(item.caption.preHeading), author: item.img.crdt, diff --git a/lib/routes/natgeo/templates/dailyPhoto.art b/lib/routes/natgeo/templates/dailyPhoto.art deleted file mode 100644 index 004525a04..000000000 --- a/lib/routes/natgeo/templates/dailyPhoto.art +++ /dev/null @@ -1,5 +0,0 @@ -{{@ img.altText }} -
    -

    {{@ img.ttl }}

    -

    {{@ img.dsc }}

    -

    {{@ img.crdt }}

    diff --git a/lib/routes/nationalgeographic/latest-stories.ts b/lib/routes/nationalgeographic/latest-stories.ts deleted file mode 100644 index 27bdd3793..000000000 --- a/lib/routes/nationalgeographic/latest-stories.ts +++ /dev/null @@ -1,84 +0,0 @@ -import path from 'node:path'; - -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 { art } from '@/utils/render'; - -const findNatgeo = ($) => - JSON.parse( - $('script') - .text() - .match(/\['__natgeo__']=({.*?});/)[1] - ); - -export const route: Route = { - path: '/latest-stories', - categories: ['travel'], - example: '/nationalgeographic/latest-stories', - parameters: {}, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['www.nationalgeographic.com/pages/topic/latest-stories'], - }, - ], - name: 'Latest Stories', - maintainers: ['miles170'], - handler, - url: 'www.nationalgeographic.com/pages/topic/latest-stories', -}; - -async function handler() { - const currentUrl = 'https://www.nationalgeographic.com/pages/topic/latest-stories'; - const response = await got(currentUrl); - const $ = load(response.data); - const items = await Promise.all( - findNatgeo($) - .page.content.hub.frms.flatMap((e) => e.mods) - .flatMap((m) => m.tiles?.filter((t) => t.ctas[0]?.text === 'natgeo.ctaText.read')) - .filter(Boolean) - .map((i) => ({ - title: i.title, - link: i.ctas[0].url, - category: i.tags.map((t) => t.name), - })) - .map((item) => - cache.tryGet(item.link, async () => { - const response = await got(item.link); - const $ = load(response.data); - const mods = findNatgeo($).page.content.prismarticle.frms.find((f) => f.cmsType === 'ArticleBodyFrame').mods; - const bodyTile = mods.find((m) => m.edgs[0].cmsType === 'ArticleBodyTile').edgs[0]; - - item.author = bodyTile.cntrbGrp - .flatMap((c) => c.contributors) - .map((c) => c.displayName) - .join(', '); - item.description = art(path.join(__dirname, 'templates/stories.art'), { - ldMda: bodyTile.ldMda, - description: bodyTile.dscrptn, - body: bodyTile.bdy, - }); - item.pubDate = parseDate(bodyTile.pbDt); - - return item; - }) - ) - ); - - return { - title: $('meta[property="og:title"]').attr('content'), - link: currentUrl, - item: items.filter((item) => item !== null), - }; -} diff --git a/lib/routes/nationalgeographic/latest-stories.tsx b/lib/routes/nationalgeographic/latest-stories.tsx new file mode 100644 index 000000000..afda5cbc6 --- /dev/null +++ b/lib/routes/nationalgeographic/latest-stories.tsx @@ -0,0 +1,189 @@ +import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +const findNatgeo = ($) => + JSON.parse( + $('script') + .text() + .match(/\['__natgeo__']=({.*?});/)[1] + ); + +type StoryMedia = { + src?: string; + altText?: string; +}; + +type StoryImage = { + image?: StoryMedia; + altText?: string; + caption?: string; +}; + +type StoryInlineContent = { + cmsType?: string; + image?: StoryMedia; + caption?: string; + images?: StoryImage[]; + note?: string; + text?: string; + quote?: string; + src?: string; + title?: string; + description?: string; +}; + +type StoryBlock = { + type?: string; + cntnt?: { + mrkup?: string; + cmsType?: string; + } & StoryInlineContent; +}; + +type StoryData = { + ldMda?: { + image?: StoryMedia; + caption?: string; + }; + description?: string; + body?: StoryBlock[]; +}; + +const renderStoriesDescription = ({ ldMda, description, body }: StoryData) => + renderToString( + <> + {ldMda?.image?.src ? ( +
    + {ldMda.image.altText} +
    {ldMda.caption}
    +
    + ) : null} + {description ? ( +

    + {description} +

    + ) : null} + {body?.length + ? body.map((block) => { + if (block.type === 'p') { + return

    {block.cntnt?.mrkup ? raw(block.cntnt.mrkup) : null}

    ; + } + if (block.type !== 'inline') { + return null; + } + + const content = block.cntnt; + switch (content?.cmsType) { + case 'image': + return content.image?.src ? ( +
    + {content.image.altText} +
    {content.caption ? raw(content.caption) : null}
    +
    + ) : null; + case 'imagegroup': + return content.images?.map((image) => + image.image?.src ? ( +
    + {image.image.altText} +
    {image.caption ? raw(image.caption) : null}
    +
    + ) : null + ); + case 'editorsNote': + return content.note ?

    {raw(content.note)}

    : null; + case 'listicle': + return content.text ?

    {raw(content.text)}

    : null; + case 'pullquote': + return content.quote ? {raw(content.quote)} : null; + case 'source': + return content.src ? {content.src} : null; + case 'video': + return content.image?.src ? ( +
    + {content.image.altText} + {content.title ?
    {raw(content.title)}
    : null} + {content.description ?
    {raw(content.description)}
    : null} +
    + ) : null; + default: + return null; + } + }) + : null} + + ); + +export const route: Route = { + path: '/latest-stories', + categories: ['travel'], + example: '/nationalgeographic/latest-stories', + parameters: {}, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.nationalgeographic.com/pages/topic/latest-stories'], + }, + ], + name: 'Latest Stories', + maintainers: ['miles170'], + handler, + url: 'www.nationalgeographic.com/pages/topic/latest-stories', +}; + +async function handler() { + const currentUrl = 'https://www.nationalgeographic.com/pages/topic/latest-stories'; + const response = await got(currentUrl); + const $ = load(response.data); + const items = await Promise.all( + findNatgeo($) + .page.content.hub.frms.flatMap((e) => e.mods) + .flatMap((m) => m.tiles?.filter((t) => t.ctas[0]?.text === 'natgeo.ctaText.read')) + .filter(Boolean) + .map((i) => ({ + title: i.title, + link: i.ctas[0].url, + category: i.tags.map((t) => t.name), + })) + .map((item) => + cache.tryGet(item.link, async () => { + const response = await got(item.link); + const $ = load(response.data); + const mods = findNatgeo($).page.content.prismarticle.frms.find((f) => f.cmsType === 'ArticleBodyFrame').mods; + const bodyTile = mods.find((m) => m.edgs[0].cmsType === 'ArticleBodyTile').edgs[0]; + + item.author = bodyTile.cntrbGrp + .flatMap((c) => c.contributors) + .map((c) => c.displayName) + .join(', '); + item.description = renderStoriesDescription({ + ldMda: bodyTile.ldMda, + description: bodyTile.dscrptn, + body: bodyTile.bdy, + }); + item.pubDate = parseDate(bodyTile.pbDt); + + return item; + }) + ) + ); + + return { + title: $('meta[property="og:title"]').attr('content'), + link: currentUrl, + item: items.filter((item) => item !== null), + }; +} diff --git a/lib/routes/nationalgeographic/templates/stories.art b/lib/routes/nationalgeographic/templates/stories.art deleted file mode 100644 index 039e73047..000000000 --- a/lib/routes/nationalgeographic/templates/stories.art +++ /dev/null @@ -1,44 +0,0 @@ -{{ if ldMda }} -
    - {{ ldMda.image.altText }} -
    {{ ldMda.caption }}
    -
    -{{ /if }} - -{{ if description }} -

    {{ description }}

    -{{ /if }} - -{{ each body b }} - {{ if b.type === 'p' }} -

    {{@ b.cntnt.mrkup }}

    - {{ else if b.type === 'inline' }} - {{ if b.cntnt.cmsType === 'image' && b.cntnt.image?.src }} -
    - {{ b.cntnt.image.altText }} -
    {{@ b.cntnt.caption }}
    -
    - {{ else if b.cntnt.cmsType === 'imagegroup' }} - {{ each b.cntnt.images img }} -
    - {{ img.image.altText }} -
    {{@ img.caption }}
    -
    - {{ /each }} - {{ else if b.cntnt.cmsType === 'editorsNote' }} -

    {{@ b.cntnt.note }}

    - {{ else if b.cntnt.cmsType === 'listicle' }} -

    {{@ b.cntnt.text }}

    - {{ else if b.cntnt.cmsType === 'pullquote' }} - {{@ b.cntnt.quote }} - {{ else if b.cntnt.cmsType === 'source' }} - {{ b.cntnt.src }} - {{ else if b.cntnt.cmsType === 'video' }} -
    - {{ b.cntnt.image.altText }} -
    {{@ b.cntnt.title }}
    -
    {{@ b.cntnt.description }}
    -
    - {{ /if }} - {{ /if }} -{{ /each }} diff --git a/lib/routes/nautil/templates/description.art b/lib/routes/nautil/templates/description.art deleted file mode 100644 index 8f54b3c7a..000000000 --- a/lib/routes/nautil/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if head.og_image }} -{{ each head.og_image img }} - -{{ /each }} -{{ /if }} -{{@ rendered }} diff --git a/lib/routes/nautil/topics.ts b/lib/routes/nautil/topics.tsx similarity index 88% rename from lib/routes/nautil/topics.ts rename to lib/routes/nautil/topics.tsx index 437f8b6e7..443d46d99 100644 --- a/lib/routes/nautil/topics.ts +++ b/lib/routes/nautil/topics.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://nautil.us'; @@ -69,10 +68,12 @@ async function handler(ctx) { return { title: item.title.rendered, author: item.yoast_head_json.author, - description: art(path.join(__dirname, 'templates/description.art'), { - head, - rendered: $.html(), - }), + description: renderToString( + <> + {head.og_image?.length ? head.og_image.map((image) => ) : null} + {raw($.html())} + + ), link: item.link, pubDate: parseDate(item.date_gmt), }; diff --git a/lib/routes/nber/common.ts b/lib/routes/nber/common.tsx similarity index 82% rename from lib/routes/nber/common.ts rename to lib/routes/nber/common.tsx index dc57d5eef..821f2c1dc 100644 --- a/lib/routes/nber/common.ts +++ b/lib/routes/nber/common.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; async function getData(url) { const response = await ofetch(url); @@ -33,10 +32,12 @@ export async function handler(ctx) { pubDate: parseDate($('meta[name="citation_publication_date"]').attr('content'), 'YYYY/MM/DD'), link, doi: $('meta[name="citation_doi"]').attr('content'), - description: art(path.join(__dirname, 'template/description.art'), { - fullAbstract, - downloadLink, - }), + description: renderToString( + <> + {fullAbstract ? raw(fullAbstract) : null} + {downloadLink ? Download PDF : null} + + ), }; }); }) diff --git a/lib/routes/nber/template/description.art b/lib/routes/nber/template/description.art deleted file mode 100644 index c013cb155..000000000 --- a/lib/routes/nber/template/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if fullAbstract }} -{{@ fullAbstract }} -{{ /if}} -{{ if downloadLink }} -Download PDF -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ncc-cma/cmdp.ts b/lib/routes/ncc-cma/cmdp.tsx similarity index 97% rename from lib/routes/ncc-cma/cmdp.ts rename to lib/routes/ncc-cma/cmdp.tsx index 2dafbe79f..39dc973af 100644 --- a/lib/routes/ncc-cma/cmdp.ts +++ b/lib/routes/ncc-cma/cmdp.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx) => { const { id = 'RPJQWQYZ' } = ctx.req.param(); @@ -47,16 +45,13 @@ export const handler = async (ctx) => { titles.push(title); } - const description = art(path.join(__dirname, 'templates/description.art'), { - images: image - ? [ - { - src: image, - alt: `${title} ${date}`, - }, - ] - : undefined, - }); + const description = renderToString( + image ? ( +
    + {`${title} +
    + ) : null + ); const guid = `ncc-cma#${id}#${date}`; return { diff --git a/lib/routes/ncc-cma/templates/description.art b/lib/routes/ncc-cma/templates/description.art deleted file mode 100644 index baa091693..000000000 --- a/lib/routes/ncc-cma/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/netflav/index.ts b/lib/routes/netflav/index.tsx similarity index 75% rename from lib/routes/netflav/index.ts rename to lib/routes/netflav/index.tsx index d8db0403e..048f9dbcf 100644 --- a/lib/routes/netflav/index.ts +++ b/lib/routes/netflav/index.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/', @@ -37,10 +35,7 @@ async function handler() { const items = [...initialState.censored.docs, ...initialState.uncensored.docs, ...initialState.chinese.docs, ...initialState.trending.docs].map((item) => ({ title: item.title, - description: art(path.join(__dirname, 'templates/description.art'), { - description: item.description, - images: [...new Set([item.preview_hp, item.preview, item.previewImagesUrl, ...(item.previewImages || [])])].filter(Boolean), - }), + description: renderDescription([...new Set([item.preview_hp, item.preview, item.previewImagesUrl, ...(item.previewImages || [])])].filter(Boolean), item.description), link: `https://netflav.com/video?id=${item.videoId}`, pubDate: parseDate(item.sourceDate), author: [...new Set(item.actors.map((a) => a.replace(/^(\w{2}:)/, '')))].join(', '), @@ -57,3 +52,13 @@ async function handler() { allowEmpty: true, }; } + +const renderDescription = (images: string[], description: string): string => + renderToString( + <> + {images?.map((img, index) => ( + + ))} + {description ?

    {description}

    : null} + + ); diff --git a/lib/routes/netflav/templates/description.art b/lib/routes/netflav/templates/description.art deleted file mode 100644 index 55626c1b2..000000000 --- a/lib/routes/netflav/templates/description.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if images }} - {{ each images img }} - - {{ /each }} -{{ /if }} -{{ if description }} -

    {{ description }}

    -{{ /if }} diff --git a/lib/routes/news/templates/description.art b/lib/routes/news/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/news/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/news/templates/description.tsx b/lib/routes/news/templates/description.tsx new file mode 100644 index 000000000..e8f519243 --- /dev/null +++ b/lib/routes/news/templates/description.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionData) => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/news/xhsxw.ts b/lib/routes/news/xhsxw.ts index 85d7a074c..94c31207e 100644 --- a/lib/routes/news/xhsxw.ts +++ b/lib/routes/news/xhsxw.ts @@ -1,14 +1,13 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: ['/xhsxw', '/whxw'], categories: ['new-media'], @@ -56,7 +55,7 @@ async function handler(ctx) { let items = response.slice(0, limit).map((item) => ({ title: item.title, link: new URL(item.publishUrl, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ images: item.shareImages?.map((i) => ({ src: i.imageUrl, @@ -78,7 +77,7 @@ async function handler(ctx) { const content = load(detailResponse); - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ description: content('#detailContent').html(), }); } catch { diff --git a/lib/routes/newslaundry/templates/description.art b/lib/routes/newslaundry/templates/description.art deleted file mode 100644 index a5126e29b..000000000 --- a/lib/routes/newslaundry/templates/description.art +++ /dev/null @@ -1,28 +0,0 @@ -{{if subheadline}} -

    {{subheadline}}

    -{{/if}} - -{{if heroImage}} -
    - {{heroAlt}} -
    {{heroCaption}}{{if heroAttribution}} ({{heroAttribution}}){{/if}}
    -
    -{{/if}} - -{{each elements}} - {{if $value.type === 'text'}} - {{@ $value.text}} - {{else if $value.type === 'image'}} -
    - {{$value.alt}} -
    {{$value.title}}
    -
    - {{else if $value.type === 'jsembed'}} - {{@ $value.content}} - {{else if $value.type === 'youtube-video'}} -
    - -
    Watch on YouTube
    -
    - {{/if}} -{{/each}} diff --git a/lib/routes/newslaundry/utils.ts b/lib/routes/newslaundry/utils.tsx similarity index 67% rename from lib/routes/newslaundry/utils.ts rename to lib/routes/newslaundry/utils.tsx index c8843657b..2e4afb1fb 100644 --- a/lib/routes/newslaundry/utils.ts +++ b/lib/routes/newslaundry/utils.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const rootUrl = 'https://www.newslaundry.com'; @@ -80,14 +80,56 @@ function processStory(story: any): DataItem { ) || []; // Render content using template - const content = art(path.join(__dirname, 'templates/description.art'), { - heroImage, - heroAlt: story['hero-image-alt-text'] || '', - heroCaption: story['hero-image-caption'] || '', - heroAttribution: story['hero-image-attribution'], - elements, - subheadline: story.subheadline, - }); + const heroCaption = story['hero-image-caption'] || ''; + const heroAttribution = story['hero-image-attribution']; + const content = renderToString( + <> + {story.subheadline ? ( +

    + {story.subheadline} +

    + ) : null} + {heroImage ? ( +
    + {story['hero-image-alt-text'] +
    {heroAttribution ? `${heroCaption} (${heroAttribution})` : heroCaption}
    +
    + ) : null} + {elements.map((element) => { + if (element.type === 'text') { + return raw(element.text); + } + + if (element.type === 'image') { + return ( +
    + {element.alt} +
    {element.title}
    +
    + ); + } + + if (element.type === 'jsembed') { + return raw(element.content); + } + + if (element.type === 'youtube-video') { + return ( +
    + +
    + + Watch on YouTube + +
    +
    + ); + } + + return null; + })} + + ); // Extract author information const authors = diff --git a/lib/routes/newzmz/templates/description.art b/lib/routes/newzmz/templates/description.art deleted file mode 100644 index 6ea2ee582..000000000 --- a/lib/routes/newzmz/templates/description.art +++ /dev/null @@ -1,59 +0,0 @@ -{{ if image }} -
    - {{ nameZh }}{{ if nameEn }} - {{ nameEn }}{{ /if }} -
    -{{ /if }} - - - {{ if nameZh }} - - - - - {{ /if }} - {{ if nameEn }} - - - - - {{ /if }} - {{ if alias }} - - - - - {{ /if }} - {{ if update }} - - - - - {{ /if }} - {{ if links }} - {{ each links link }} - - - - - {{ /each }} - {{ /if }} - {{ if categories }} - - - - - {{ /if }} - {{ if downLinks }} - {{ each downLinks link }} - - - - - {{ /each }} - {{ /if }} - -
    中文名{{ nameZh }}
    英文名{{ nameEn }}
    又名{{ alias.join(' / ') }}
    更新频率{{ update }}
    {{ link.title }} - {{ link.link }} -
    标签{{ categories.join(' / ') }}
    {{ link.title }} - {{ link.link }} -
    \ No newline at end of file diff --git a/lib/routes/newzmz/templates/description.tsx b/lib/routes/newzmz/templates/description.tsx new file mode 100644 index 000000000..13d997c95 --- /dev/null +++ b/lib/routes/newzmz/templates/description.tsx @@ -0,0 +1,85 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type LinkItem = { + title?: string; + link?: string; +}; + +type DescriptionData = { + image?: string; + nameZh?: string; + nameEn?: string; + alias?: string[]; + update?: string; + links?: LinkItem[]; + categories?: string[]; + downLinks?: LinkItem[]; +}; + +export const renderDescription = ({ image, nameZh, nameEn, alias, update, links, categories, downLinks }: DescriptionData) => { + const alt = `${nameZh ?? ''}${nameEn ? ` - ${nameEn}` : ''}`; + + return renderToString( + <> + {image ? ( +
    + {alt} +
    + ) : null} + + + {nameZh ? ( + + + + + ) : null} + {nameEn ? ( + + + + + ) : null} + {alias?.length ? ( + + + + + ) : null} + {update ? ( + + + + + ) : null} + {links?.length + ? links.map((link) => ( + + + + + )) + : null} + {categories?.length ? ( + + + + + ) : null} + {downLinks?.length + ? downLinks.map((link) => ( + + + + + )) + : null} + +
    中文名{nameZh}
    英文名{nameEn}
    又名{alias.join(' / ')}
    更新频率{update}
    {link.title} + {link.link} +
    标签{categories.join(' / ')}
    {link.title} + {link.link} +
    + + ); +}; diff --git a/lib/routes/newzmz/util.ts b/lib/routes/newzmz/util.ts index 8dfc1405c..a8fdb810a 100644 --- a/lib/routes/newzmz/util.ts +++ b/lib/routes/newzmz/util.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const rootUrl = 'https://nzmz.xyz'; @@ -144,9 +143,8 @@ const processItems = async (i, downLinkType, itemSelector, categorySelector, dow guid, title, link: i.link, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ ...i.description, - categories, downLinks, }), diff --git a/lib/routes/nhentai/templates/desc.art b/lib/routes/nhentai/templates/desc.art deleted file mode 100644 index c8f15fadc..000000000 --- a/lib/routes/nhentai/templates/desc.art +++ /dev/null @@ -1,4 +0,0 @@ -

    {{ length }} pages


    -{{ each images i }} -
    -{{ /each }} diff --git a/lib/routes/nhentai/util.ts b/lib/routes/nhentai/util.tsx similarity index 90% rename from lib/routes/nhentai/util.ts rename to lib/routes/nhentai/util.tsx index 3bd5d9bb4..3201a4158 100644 --- a/lib/routes/nhentai/util.ts +++ b/lib/routes/nhentai/util.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import got from '@/utils/got'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://nhentai.net'; @@ -140,11 +138,22 @@ const getDetail = async (simple) => { ...simple, title: $('div#info > h2').text() || $('div#info > h1').text(), pubDate: parseDate($('time').attr('datetime')), - description: art(path.join(__dirname, 'templates/desc.art'), { - length: galleryImgs.length, - images: galleryImgs, - }), + description: renderDescription(galleryImgs.length, galleryImgs), }; }; +const renderDescription = (length: number, images: string[]): string => + renderToString( + <> +

    {length} pages

    +
    + {images.map((image, index) => ( + + +
    +
    + ))} + + ); + export { baseUrl, getDetails, getSimple, getTorrents }; diff --git a/lib/routes/nhk/news-web-easy.ts b/lib/routes/nhk/news-web-easy.tsx similarity index 89% rename from lib/routes/nhk/news-web-easy.ts rename to lib/routes/nhk/news-web-easy.tsx index 7a30870aa..8598c0385 100644 --- a/lib/routes/nhk/news-web-easy.ts +++ b/lib/routes/nhk/news-web-easy.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -77,10 +76,13 @@ async function handler(ctx) { let items = Object.values(dates).flatMap((articles) => articles.map((article) => ({ title: article.title, - description: art(path.join(__dirname, 'templates/news_web_easy.art'), { - title: article.title_with_ruby, - image: article.news_web_image_uri, - }), + description: renderToString( + <> + {article.title_with_ruby ?

    {raw(article.title_with_ruby)}

    : null} + {article.news_web_image_uri ? : null} +
    + + ), guid: article.news_id, pubDate: timezone(parseDate(article.news_prearranged_time), +9), link: `https://news.web.nhk/news/easy/${article.news_id}/${article.news_id}.html`, diff --git a/lib/routes/nhk/news.ts b/lib/routes/nhk/news.tsx similarity index 82% rename from lib/routes/nhk/news.ts rename to lib/routes/nhk/news.tsx index ddd28b739..018dff62c 100644 --- a/lib/routes/nhk/news.ts +++ b/lib/routes/nhk/news.tsx @@ -1,11 +1,11 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://www3.nhk.or.jp'; const apiUrl = 'https://api.nhkworld.jp'; @@ -80,10 +80,20 @@ async function handler(ctx) { cache.tryGet(item.link, async () => { const { data } = await got(`${apiUrl}/nwapi/rdnewsweb/v6b/${lang}/detail/${item.id}.json`); item.category = Object.values(data.data.categories); - item.description = art(path.join(__dirname, 'templates/news.art'), { - img: data.data.thumbnails, - description: data.data.detail.replaceAll('\n\n', '

    '), - }); + const img = data.data.thumbnails; + const imageSrc = img?.large || img?.middle || img?.small || img?.min; + const description = data.data.detail.replaceAll('\n\n', '

    '); + item.description = renderToString( + <> + {imageSrc ? ( + <> + {img?.alt} +
    + + ) : null} + {description ? raw(description) : null} + + ); delete item.id; return item; }) diff --git a/lib/routes/nhk/templates/news.art b/lib/routes/nhk/templates/news.art deleted file mode 100644 index e24a33059..000000000 --- a/lib/routes/nhk/templates/news.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if img }} -{{ img.alt }} -
    -{{ /if }} -{{@ description }} diff --git a/lib/routes/nhk/templates/news_web_easy.art b/lib/routes/nhk/templates/news_web_easy.art deleted file mode 100644 index 81fa52883..000000000 --- a/lib/routes/nhk/templates/news_web_easy.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if title }} -

    {{@ title }}

    -{{ /if }} -{{ if image }} - -{{ /if }} -
    \ No newline at end of file diff --git a/lib/routes/nicovideo/templates/video.art b/lib/routes/nicovideo/templates/video.art deleted file mode 100644 index 457b2e399..000000000 --- a/lib/routes/nicovideo/templates/video.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if embed }} - -{{ else }} - -{{ /if }} -
    -{{@ video.shortDescription }} diff --git a/lib/routes/nicovideo/utils.ts b/lib/routes/nicovideo/utils.tsx similarity index 77% rename from lib/routes/nicovideo/utils.ts rename to lib/routes/nicovideo/utils.tsx index d23fe8ae0..7e0d2b4ef 100644 --- a/lib/routes/nicovideo/utils.ts +++ b/lib/routes/nicovideo/utils.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; import type { Essential, Mylist, UserInfo, VideoItem } from './types'; @@ -58,4 +58,15 @@ export const getMylist = (id: string): Promise => false ); -export const renderVideo = (video: Essential, embed: boolean) => art(path.join(__dirname, 'templates/video.art'), { video, embed }); +export const renderVideo = (video: Essential, embed: boolean) => + renderToString( + <> + {embed ? ( +
    -{{ /if }} diff --git a/lib/routes/npm/package.ts b/lib/routes/npm/package.tsx similarity index 71% rename from lib/routes/npm/package.ts rename to lib/routes/npm/package.tsx index 6afb0fc57..b451b4d52 100644 --- a/lib/routes/npm/package.ts +++ b/lib/routes/npm/package.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const route: Route = { path: '/package/:name{(@[a-z0-9-~][a-z0-9-._~]*/)?[a-z0-9-~][a-z0-9-._~]*}', @@ -45,12 +44,21 @@ async function handler(ctx) { item: [ { title: `${name} - npm`, - description: art(path.join(__dirname, 'templates/package.art'), { - packageDownloadCountLastMonth: downloadCountLastMonthRes.downloads, - packageDownloadCountLastWeek: downloadCountLastWeekRes.downloads, - packageDownloadCountLastDay: downloadCountLastDayRes.downloads, - packageVersion: packageVersionList, - }), + description: renderToString( + <> +

    Download

    +

    Last Day: {downloadCountLastDayRes.downloads}

    +

    Last week: {downloadCountLastWeekRes.downloads}

    +

    Last month: {downloadCountLastMonthRes.downloads}

    +
    +

    Version

    + {packageVersionList.map((version) => ( +

    + {version.version}: {version.time} +

    + ))} + + ), link: `https://www.npmjs.com/package/${name}`, guid: `https://www.npmjs.com/package/${name}${packageVersion.modified}`, }, diff --git a/lib/routes/npm/templates/package.art b/lib/routes/npm/templates/package.art deleted file mode 100644 index db7b75a36..000000000 --- a/lib/routes/npm/templates/package.art +++ /dev/null @@ -1,10 +0,0 @@ -

    Download

    -

    Last Day: {{packageDownloadCountLastDay}}

    -

    Last week: {{packageDownloadCountLastWeek}}

    -

    Last month: {{packageDownloadCountLastMonth}}

    -
    -

    Version

    -{{ each packageVersion}} -

    {{$value.version}}: {{$value.time}}

    -{{/each}} - diff --git a/lib/routes/nytimes/daily-briefing-chinese.ts b/lib/routes/nytimes/daily-briefing-chinese.tsx similarity index 91% rename from lib/routes/nytimes/daily-briefing-chinese.ts rename to lib/routes/nytimes/daily-briefing-chinese.tsx index f41dbc03d..45d5e04c0 100644 --- a/lib/routes/nytimes/daily-briefing-chinese.ts +++ b/lib/routes/nytimes/daily-briefing-chinese.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/daily_briefing_chinese', @@ -74,9 +72,11 @@ async function handler() { let i = 0; content('figure').each(function () { content(this).html( - art(path.join(__dirname, 'templates/image.art'), { - url: images[i++], - }) + renderToString( +
    + +
    + ) ); }); diff --git a/lib/routes/nytimes/templates/image.art b/lib/routes/nytimes/templates/image.art deleted file mode 100644 index f86c4f723..000000000 --- a/lib/routes/nytimes/templates/image.art +++ /dev/null @@ -1,3 +0,0 @@ -
    - -
    \ No newline at end of file diff --git a/lib/routes/oceanengine/arithmetic-index.ts b/lib/routes/oceanengine/arithmetic-index.tsx similarity index 85% rename from lib/routes/oceanengine/arithmetic-index.ts rename to lib/routes/oceanengine/arithmetic-index.tsx index e07116501..67f24c93d 100644 --- a/lib/routes/oceanengine/arithmetic-index.ts +++ b/lib/routes/oceanengine/arithmetic-index.tsx @@ -1,7 +1,8 @@ import { createDecipheriv } from 'node:crypto'; -import path from 'node:path'; import dayjs from 'dayjs'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import InvalidParameterError from '@/errors/types/invalid-parameter'; @@ -9,7 +10,6 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; // Parameters @@ -73,14 +73,16 @@ const searchLinkUrls = (keyword) => [ const searchLinkNames = ['今日热榜', '百度', '谷歌', '知乎', '微博', '抖音', '头条']; const createContent = (keyword, queryList, queryListText) => - art(path.join(__dirname, 'templates/content.art'), { - keyword, - queryListText, - queries: queryList.map((query) => ({ - links: searchLinkUrls(encodeURIComponent(query)).map((url, index) => `${searchLinkNames[index]}`), - key: query, - })), - }); + renderToString( + ({ + links: searchLinkUrls(encodeURIComponent(query)).map((url, index) => `${searchLinkNames[index]}`), + key: query, + }))} + /> + ); export const route: Route = { path: '/index/:keyword/:channel?', @@ -156,3 +158,22 @@ async function handler(ctx) { item, }; } + +const OceanengineContent = ({ queryListText, queries }: { queryListText: string; queries: { key: string; links: string[] }[] }) => ( +
    +

    关键词:

    + {queryListText} +       +
    + {queries.map((query) => ( + <> +

    {query.key}

    +

    + {query.links.map((link) => ( + <>{raw(link)}   + ))} +

    + + ))} +
    +); diff --git a/lib/routes/oceanengine/templates/content.art b/lib/routes/oceanengine/templates/content.art deleted file mode 100644 index e6bf96c60..000000000 --- a/lib/routes/oceanengine/templates/content.art +++ /dev/null @@ -1,14 +0,0 @@ -
    -

    关键词:

    - {{queryListText}} -       -
    - {{each queries q}} -

    {{q.key}}

    -

    - {{each q.links l}} - {{@l}}   - {{/each}} -

    - {{/each}} -
    \ No newline at end of file diff --git a/lib/routes/oeeee/app/channel.ts b/lib/routes/oeeee/app/channel.ts index a35932058..e8540bd74 100644 --- a/lib/routes/oeeee/app/channel.ts +++ b/lib/routes/oeeee/app/channel.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from '../templates/description'; import { parseArticle } from '../utils'; export const route: Route = { @@ -26,7 +24,7 @@ async function handler(ctx) { .filter((i) => i.url) // Remove banner and sticky articles. .map((item) => ({ title: item.title, - description: art(path.join(__dirname, '../templates/description.art'), { + description: renderDescription({ thumb: item.titleimg.replaceAll(/\?x-oss-process=.*/g, ''), description: item.summary, }), diff --git a/lib/routes/oeeee/app/reporter.ts b/lib/routes/oeeee/app/reporter.ts index da2a5f33c..963c0779d 100644 --- a/lib/routes/oeeee/app/reporter.ts +++ b/lib/routes/oeeee/app/reporter.ts @@ -1,10 +1,8 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; +import { renderDescription } from '../templates/description'; import { parseArticle } from '../utils'; export const route: Route = { @@ -34,7 +32,7 @@ async function handler(ctx) { const list = response.data.list.map((item) => ({ title: '【' + item.media_nickname + '】' + item.title, - description: art(path.join(__dirname, '../templates/description.art'), { + description: renderDescription({ thumb: item.titleimg, description: item.summary, }), diff --git a/lib/routes/oeeee/templates/description.art b/lib/routes/oeeee/templates/description.art deleted file mode 100644 index 8768b47a5..000000000 --- a/lib/routes/oeeee/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if thumb }} -
    -{{ /if }} -{{ if description }} -

    {{ description }}

    -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/oeeee/templates/description.tsx b/lib/routes/oeeee/templates/description.tsx new file mode 100644 index 000000000..6e828c017 --- /dev/null +++ b/lib/routes/oeeee/templates/description.tsx @@ -0,0 +1,27 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + thumb?: string; + description?: string; +}; + +const OeeeeDescription = ({ thumb, description }: DescriptionData) => ( + <> + {thumb ? ( + <> + +
    + + ) : null} + {description ? ( + <> +
    +

    {description}

    +
    +
    + + ) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/oeeee/web.ts b/lib/routes/oeeee/web.ts index 81ee21e04..962d33594 100644 --- a/lib/routes/oeeee/web.ts +++ b/lib/routes/oeeee/web.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; import { parseArticle } from './utils'; export const route: Route = { @@ -37,7 +35,7 @@ async function handler(ctx) { const list = response.data.map((item) => ({ title: '【' + item.channel_name + '】' + item.title, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ thumb: item.img, description: item.summary, }), diff --git a/lib/routes/oncc/index.ts b/lib/routes/oncc/index.tsx similarity index 88% rename from lib/routes/oncc/index.ts rename to lib/routes/oncc/index.tsx index 07198123d..45f4ec3e5 100644 --- a/lib/routes/oncc/index.ts +++ b/lib/routes/oncc/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://hk.on.cc'; @@ -89,7 +88,7 @@ async function handler(ctx) { const $ = load(detailResponse.data); const imageUrl = rootUrl + $('img').eq(0).attr('src'); const content = $('div.breakingNewsContent').html(); - const description = art(path.join(__dirname, 'templates/article.art'), { + const description = renderArticleDescription({ imageUrl, content, }); @@ -108,3 +107,11 @@ async function handler(ctx) { item: items, }; } + +const renderArticleDescription = ({ imageUrl, content }: { imageUrl: string; content?: string }): string => + renderToString( + <> + + {content ? raw(content) : null} + + ); diff --git a/lib/routes/oncc/money18.ts b/lib/routes/oncc/money18.ts index 436b70643..391506af7 100644 --- a/lib/routes/oncc/money18.ts +++ b/lib/routes/oncc/money18.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import dayjs from 'dayjs'; @@ -7,9 +5,10 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/money18'; + const sections = { exp: '新聞總覽', fov: '全日焦點', @@ -85,7 +84,7 @@ async function handler(ctx) { title: item.title, author: item.authorname, link: `${rootUrl}/finnews/content/${id}/${item.articleId}.html`, - description: art(path.join(__dirname, 'templates/money18.art'), { + description: renderDescription({ images: item.hasHdPhoto ? [`https://hk.on.cc/hk/bkn${item.hdEnlargeThumbnail}`] : undefined, description: item.content, }), @@ -119,7 +118,7 @@ async function handler(ctx) { const content = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/money18.art'), { + item.description = renderDescription({ images: content('.photo img') .toArray() .map((i) => content(i).attr('src')), diff --git a/lib/routes/oncc/templates/article.art b/lib/routes/oncc/templates/article.art deleted file mode 100644 index 4fd443f47..000000000 --- a/lib/routes/oncc/templates/article.art +++ /dev/null @@ -1,2 +0,0 @@ - -{{@ content }} diff --git a/lib/routes/oncc/templates/money18.art b/lib/routes/oncc/templates/money18.art deleted file mode 100644 index dcb463751..000000000 --- a/lib/routes/oncc/templates/money18.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if images }} -{{ each images image }} - -{{ /each }} -{{ /if }} -{{@ description }} diff --git a/lib/routes/oncc/templates/money18.tsx b/lib/routes/oncc/templates/money18.tsx new file mode 100644 index 000000000..d7861f66b --- /dev/null +++ b/lib/routes/oncc/templates/money18.tsx @@ -0,0 +1,18 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + images?: string[]; + description?: string; +}; + +const Description = ({ images, description }: DescriptionProps) => ( + <> + {images?.map((image, index) => ( + + ))} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/onet/news.ts b/lib/routes/onet/news.tsx similarity index 81% rename from lib/routes/onet/news.ts rename to lib/routes/onet/news.tsx index fc456648a..74b556556 100644 --- a/lib/routes/onet/news.ts +++ b/lib/routes/onet/news.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import parser from '@/utils/rss-parser'; import { parseArticleContent, parseMainImage } from './utils'; @@ -53,11 +52,7 @@ async function handler() { const mainImage = parseMainImage($); - const description = art(path.join(__dirname, 'templates/article.art'), { - mainImage, - lead: $('#lead').text()?.trim(), - content: content.html()?.trim(), - }); + const description = renderDescription($('#lead').text()?.trim(), mainImage, content.html()?.trim()); const author = $('.authorNameWrapper span[itemprop="name"]').text()?.trim(); const category = $('span.relatedTopic').text()?.trim(); @@ -84,3 +79,16 @@ async function handler() { image: 'https://ocdn.eu/wiadomosciucs/static/logo2017/onet2017big_dark.png', }; } + +const renderDescription = (lead: string | undefined, mainImage: string, content: string | undefined): string => + renderToString( + <> + {lead ? ( +

    + {lead} +

    + ) : null} + {raw(mainImage)} + {content ? raw(content) : null} + + ); diff --git a/lib/routes/onet/templates/article.art b/lib/routes/onet/templates/article.art deleted file mode 100644 index 36ed5db53..000000000 --- a/lib/routes/onet/templates/article.art +++ /dev/null @@ -1,5 +0,0 @@ -{{if lead }} -

    {{ lead }}

    -{{/if}} -{{@ mainImage }} -{{@ content }} diff --git a/lib/routes/onet/templates/image.art b/lib/routes/onet/templates/image.art deleted file mode 100644 index a4a8c8724..000000000 --- a/lib/routes/onet/templates/image.art +++ /dev/null @@ -1,9 +0,0 @@ -
    - {{ alt }} - - {{if caption }} - {{ caption }} - - {{/if}} - {{ author }} - -
    diff --git a/lib/routes/onet/templates/image.tsx b/lib/routes/onet/templates/image.tsx new file mode 100644 index 000000000..f32592f74 --- /dev/null +++ b/lib/routes/onet/templates/image.tsx @@ -0,0 +1,24 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageProps = { + url?: string; + alt?: string; + caption?: string; + author?: string; +}; + +const ImageFigure = ({ url, alt, caption, author }: ImageProps) => ( +
    + {alt} + + {caption ? ( + <> + {caption} -{' '} + + ) : null} + {author} + +
    +); + +export const renderImage = (props: ImageProps): string => renderToString(); diff --git a/lib/routes/onet/utils.ts b/lib/routes/onet/utils.ts index 3db03167c..d90710018 100644 --- a/lib/routes/onet/utils.ts +++ b/lib/routes/onet/utils.ts @@ -1,6 +1,4 @@ -import path from 'node:path'; - -import { art } from '@/utils/render'; +import { renderImage } from './templates/image'; const parseMainImage = ($) => { const mainImage = $('figure.mainPhoto'); @@ -8,7 +6,7 @@ const parseMainImage = ($) => { const author = mainImage.find('span.copyright'); const caption = mainImage.find('span.imageDescription'); - return art(path.join(__dirname, 'templates/image.art'), { + return renderImage({ url: img.attr('src'), alt: img.attr('alt')?.trim(), author: author.text()?.trim(), @@ -34,7 +32,7 @@ const parseArticleContent = ($) => { const img = $(el).find('img'); const author = $(el).find('span.author'); const caption = $(el).find('span.caption'); - const html = art(path.join(__dirname, 'templates/image.art'), { + const html = renderImage({ url: img.attr('src'), alt: img.attr('alt')?.trim(), caption: caption.text()?.trim(), diff --git a/lib/routes/openai/common.ts b/lib/routes/openai/common.tsx similarity index 93% rename from lib/routes/openai/common.ts rename to lib/routes/openai/common.tsx index daf84c936..b3857d6d6 100644 --- a/lib/routes/openai/common.ts +++ b/lib/routes/openai/common.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { DataItem } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const BASE_URL = new URL('https://openai.com'); @@ -119,11 +118,12 @@ const parseArticle = (ctx, rootUrl, attributes) => const imageSrc = attributes.seo.ogImageSrc; const imageAlt = attributes.seo.ogImageAlt; - const article = art(path.join(__dirname, 'templates/article.art'), { - content, - imageSrc, - imageAlt, - }); + const article = renderToString( + <> + {imageAlt + {raw(content.toString())} + + ); // Not all article has tags attributes.tags = attributes.tags || []; diff --git a/lib/routes/openai/templates/article.art b/lib/routes/openai/templates/article.art deleted file mode 100644 index 3d661adfb..000000000 --- a/lib/routes/openai/templates/article.art +++ /dev/null @@ -1,2 +0,0 @@ -{{ imageAlt }} -{{@ content }} diff --git a/lib/routes/openrice/chart.ts b/lib/routes/openrice/chart.tsx similarity index 87% rename from lib/routes/openrice/chart.ts rename to lib/routes/openrice/chart.tsx index 390e8d9c5..56a5a2cd6 100644 --- a/lib/routes/openrice/chart.ts +++ b/lib/routes/openrice/chart.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; const baseUrl = 'https://www.openrice.com'; @@ -48,11 +46,13 @@ async function handler(ctx) { const title = $item.find('.pcmgidtr-left-section-poi-info-name .link').text() ?? ''; const link = $item.find('.pcmgidtr-left-section-poi-info-name .link').attr('href') ?? ''; const coverImg = $item.find('.pcmgidtr-left-section-door-photo img').attr('src') ?? null; - const description = art(path.join(__dirname, 'templates/chart.art'), { - description: desTagsArray ?? [], - rankNumber, - image: coverImg, - }); + const description = renderToString( + <> +

    {`Rank: ${rankNumber} / ${title}`}

    +

    {desTagsArray.join(' ')}

    + {coverImg ? : null} + + ); return { title, description, diff --git a/lib/routes/openrice/offers.ts b/lib/routes/openrice/offers.ts index 5eccbff19..736898afb 100644 --- a/lib/routes/openrice/offers.ts +++ b/lib/routes/openrice/offers.ts @@ -1,8 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const baseUrl = 'https://www.openrice.com'; @@ -60,7 +59,7 @@ async function handler(ctx) { const link = baseUrl + item.urlUI; const coverImg = item.doorPhotoUI.urls.full ?? ''; const descriptionText = item.couponType === 0 ? item.poiNameUI : `${item.desc} (${item.startTimeUI} - ${item.expireTimeUI}) [${item.multiplePoiDistrictName}]`; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ description: descriptionText, image: coverImg, }); diff --git a/lib/routes/openrice/promos.ts b/lib/routes/openrice/promos.ts index 05f0e1bf5..357991aca 100644 --- a/lib/routes/openrice/promos.ts +++ b/lib/routes/openrice/promos.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const baseUrl = 'https://www.openrice.com'; @@ -55,7 +54,7 @@ async function handler(ctx) { .find('.cover-photo') .attr('style') ?.match(/url\(['"]?(.*?)['"]?\)/)?.[1] ?? null; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ description: $item.find('.article-details .desc').text() ?? '', image: coverImg, }); diff --git a/lib/routes/openrice/templates/chart.art b/lib/routes/openrice/templates/chart.art deleted file mode 100644 index 7433821a1..000000000 --- a/lib/routes/openrice/templates/chart.art +++ /dev/null @@ -1,9 +0,0 @@ -

    Rank: {{ rankNumber }} / {{ title }}

    -

    -{{ each description }} -{{ $value }} -{{ /each }} -

    -{{ if image }} - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/openrice/templates/description.art b/lib/routes/openrice/templates/description.art deleted file mode 100644 index 9aa75c6f8..000000000 --- a/lib/routes/openrice/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ description }} -{{ if image }} - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/openrice/templates/description.tsx b/lib/routes/openrice/templates/description.tsx new file mode 100644 index 000000000..3af15df16 --- /dev/null +++ b/lib/routes/openrice/templates/description.tsx @@ -0,0 +1,14 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + description: string; + image?: string | null; +}; + +export const renderDescription = ({ description, image }: DescriptionData): string => + renderToString( + <> + {description} + {image ? : null} + + ); diff --git a/lib/routes/orcid/index.ts b/lib/routes/orcid/index.tsx similarity index 73% rename from lib/routes/orcid/index.ts rename to lib/routes/orcid/index.tsx index 1d928cb0a..488950640 100644 --- a/lib/routes/orcid/index.ts +++ b/lib/routes/orcid/index.tsx @@ -1,8 +1,8 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:id', @@ -50,14 +50,18 @@ async function handler(ctx) { const info = { title: work.title.value, link: work.url, - description: art(path.join(__dirname, 'templates/description.art'), { - title: work.title.value, - journalTitle: work.journalTitle?.value, - publicationDate: work.publicationDate, - workType: work.workType.value, - Str, - sourceName: work.sourceName, - }), + description: renderToString( + <> +

    {work.title.value}

    + {work.journalTitle?.value ?

    {work.journalTitle.value}

    : null} + + {[work.publicationDate?.year, work.publicationDate?.month, work.publicationDate?.day].filter(Boolean).join('-')} | {work.workType.value} + +
    + {raw(Str)} + Source: {work.sourceName} + + ), guid: work.putCode.value, }; out.push(info); diff --git a/lib/routes/orcid/templates/description.art b/lib/routes/orcid/templates/description.art deleted file mode 100644 index 8f6970f4d..000000000 --- a/lib/routes/orcid/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -

    {{ title }}

    {{ if journalTitle }}

    {{ journalTitle }}

    {{ /if }} -{{ if publicationDate.year }}{{ publicationDate.year }}{{ /if }}{{ if publicationDate.month }}-{{ publicationDate.month }}{{ /if }}{{ if publicationDate.day }}-{{ publicationDate.day }}{{ /if }} | {{ workType }}
    -{{@ Str }}Source: {{ sourceName }} diff --git a/lib/routes/oreno3d/main.ts b/lib/routes/oreno3d/main.tsx similarity index 81% rename from lib/routes/oreno3d/main.ts rename to lib/routes/oreno3d/main.tsx index ff3403434..949b34831 100644 --- a/lib/routes/oreno3d/main.ts +++ b/lib/routes/oreno3d/main.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import get_sec_page_data from './get-sec-page-data'; @@ -93,17 +91,42 @@ async function handler(ctx) { const iwara_link = sec_data.iwara_link; const oreno3d_link = sec_data.oreno3d_link; // 打包,缓存HTML - const description = art(path.join(__dirname, 'templates/description.art'), { - raw_pic_link, - video_name, - authors, - origins, - characters, - tags, - desc, - iwara_link, - oreno3d_link, - }); + const description = renderToString( + <> + +

    + 标题:{video_name} +

    +

    + 作者:{authors} +

    +

    + 原作:{origins} +

    +

    + 角色:{characters} +

    +

    + 标签:{tags} +

    +

    简介:

    +
    + {desc} +
    +

    + iwara链接: + + {iwara_link} + +

    +

    + Oreno3D链接: + + {oreno3d_link} + +

    + + ); const title = `${video_name} - ${authors}`; const realData = await cache.tryGet(oreno3d_link, () => { const result = { diff --git a/lib/routes/oreno3d/templates/description.art b/lib/routes/oreno3d/templates/description.art deleted file mode 100644 index 846d7abc5..000000000 --- a/lib/routes/oreno3d/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ - -

    标题:{{ video_name }}

    -

    作者:{{ authors }}

    -

    原作:{{ origins }}

    -

    角色:{{ characters }}

    -

    标签:{{ tags }}

    -

    简介:

    -
    -{{ desc }} -
    -

    iwara链接:{{ iwara_link }}

    -

    Oreno3D链接:{{ oreno3d_link }}

    - diff --git a/lib/routes/ornl/all-news.ts b/lib/routes/ornl/all-news.ts index ceac0a6b0..833e94ad1 100644 --- a/lib/routes/ornl/all-news.ts +++ b/lib/routes/ornl/all-news.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); @@ -34,7 +33,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.text(); const image: string | undefined = $imgEl.attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -82,7 +81,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1.page-title').text(); const image: string | undefined = $$imgEl.attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { diff --git a/lib/routes/ornl/templates/description.art b/lib/routes/ornl/templates/description.art deleted file mode 100644 index bfb1a0ff6..000000000 --- a/lib/routes/ornl/templates/description.art +++ /dev/null @@ -1,27 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/ornl/templates/description.tsx b/lib/routes/ornl/templates/description.tsx new file mode 100644 index 000000000..9e931a47f --- /dev/null +++ b/lib/routes/ornl/templates/description.tsx @@ -0,0 +1,31 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; + width?: string | number; + height?: string | number; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +const OrnlDescription = ({ images, intro, description }: DescriptionData) => ( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/oschina/column.ts b/lib/routes/oschina/column.ts index 6cd4c0128..8efe261a0 100644 --- a/lib/routes/oschina/column.ts +++ b/lib/routes/oschina/column.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,9 +8,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { id } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); @@ -34,7 +33,7 @@ export const handler = async (ctx: Context): Promise => { const $el: Cheerio = $(el); const title: string = $el.find('div.title').text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ intro: $el.find('div.description p.line-clamp').text(), }); const pubDateStr: string | undefined = $el.find('inddiv.item').contents().last().text().trim(); @@ -84,7 +83,7 @@ export const handler = async (ctx: Context): Promise => { $$('.ad-wrap').remove(); const title: string = $$('h1.article-box__title').text(); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ description: $$('div.content').html(), }); const pubDateEl: Element = $$('div.article-box__meta div.item-list div.item') diff --git a/lib/routes/oschina/event.ts b/lib/routes/oschina/event.ts index 135d5fe77..b25c37558 100644 --- a/lib/routes/oschina/event.ts +++ b/lib/routes/oschina/event.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'latest' } = ctx.req.param(); @@ -42,7 +41,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $el.find('a.summary').text(); const image: string | undefined = $el.find('header.item-banner img').attr('data-delay'); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: image ? [ { @@ -100,7 +99,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1').text(); const image: string | undefined = $$('div.event-img img').attr('src'); - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: image ? [ { diff --git a/lib/routes/oschina/templates/description.art b/lib/routes/oschina/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/oschina/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/oschina/templates/description.tsx b/lib/routes/oschina/templates/description.tsx new file mode 100644 index 000000000..f0e2d4a8f --- /dev/null +++ b/lib/routes/oschina/templates/description.tsx @@ -0,0 +1,29 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionProps = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +const Description = ({ images, intro, description }: DescriptionProps) => ( + <> + {images?.map((image, index) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/oshwhub/explore.ts b/lib/routes/oshwhub/explore.ts index 01238d37e..f5b81381a 100644 --- a/lib/routes/oshwhub/explore.ts +++ b/lib/routes/oshwhub/explore.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const originOptions = [ { @@ -39,44 +38,11 @@ const findNamesByUuids = (data: any[], uuids: string[]): string[] => { return uuids.flatMap((uuid) => allItems.filter((item) => item.uuid === uuid || item.name === uuid).map((item) => item.name)).filter(Boolean); }; -const escapeHTML = (input) => { - if (input === undefined) { - return ''; - } - const str = String(input); - const escapeMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - }; - return str.replaceAll(/[&<>"']/g, (char) => escapeMap[char] || char); -}; - -const formatObject = (obj) => { - if (typeof obj !== 'object' || obj === null) { - return escapeHTML(obj); - } - - let result = ''; - for (const key in obj) { - if (obj[key] !== null && obj[key] !== '') { - result += `
    ${escapeHTML(key)}: ${escapeHTML(obj[key])}
    `; - } - } - - return result || '无数据'; -}; - const md = MarkdownIt({ html: true, linkify: true, }); -art.defaults.imports.escapeHTML = escapeHTML; -art.defaults.imports.formatObject = formatObject; - export const handler = async (ctx: Context): Promise => { const { type = 'new', origin = 'all', projectTag } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '15', 10); @@ -109,7 +75,7 @@ export const handler = async (ctx: Context): Promise => { items = response.result.lists.slice(0, limit).map((item): DataItem => { const title: string = item.name; const image: string | undefined = item.thumb?.startsWith('https:') ? item.thumb : `https:${item.thumb}`; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -196,7 +162,7 @@ export const handler = async (ctx: Context): Promise => { const attachments = result.attachments; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { diff --git a/lib/routes/oshwhub/templates/description.art b/lib/routes/oshwhub/templates/description.art deleted file mode 100644 index 03d8e218f..000000000 --- a/lib/routes/oshwhub/templates/description.art +++ /dev/null @@ -1,156 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if title || origin || tags || license || pubDate || upDated || intro }} - - - {{ if title }} - - - - - {{ /if }} - {{ if origin }} - - - - - {{ /if }} - {{ if tags }} - - - - - {{ /if }} - {{ if license }} - - - - - {{ /if }} - {{ if pubDate }} - - - - - {{ /if }} - {{ if upDated }} - - - - - {{ /if }} - {{ if intro }} - - - - - {{ /if }} - -
    名称{{ title }}
    版本{{ origin }}
    标签 - {{ each tags tag }} - {{ if $index !== 0 }}/ {{ /if }}{{ tag }} - {{ /each }} -
    开源协议{{ license }}
    创建时间{{ pubDate }}
    更新时间{{ upDated }}
    简介{{ intro }}
    -{{ /if }} - -{{ if description }} -

    描述

    - {{@ description }} -{{ /if }} - -{{ if documents }} -

    设计图

    -
    -
      - {{ each documents document }} -
    • - {{ if document.title }} -

      {{ document.title }}

      - {{ /if }} - {{ if document.description }} -

      {{ document.description }}

      - {{ /if }} - {{ if document.thumb }} -
      - {{ document.title }} -
      - {{ /if }} -
    • - {{ /each }} -
    -
    -{{ /if }} - -{{ if boms }} -

    BOM

    - - - - {{ set headers = boms[0] }} - {{ each headers header }} - - {{ /each }} - - - - {{ set rows = boms.slice(1) }} - {{ each rows row }} - - {{ each row td }} - {{ if $index === row.length - 1 }} - - {{ else }} - - {{ /if }} - {{ /each }} - - {{ /each }} - -
    {{ header }}
    {{@ formatObject(td) }}{{ td | escapeHTML }}
    -{{ /if }} - -{{ if attachments }} -

    附件

    - - - - - - - - {{ each attachments attachment }} - - - - - - {{ /each }} - -
    序号文件名称文件大小
    - {{ $index + 1 }} - - {{ if attachment.src }} - {{ attachment.name || '下载链接' }} - {{ else }} - {{ attachment.name || '无文件链接' }} - {{ /if }} - - {{ attachment.size }} -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/oshwhub/templates/description.tsx b/lib/routes/oshwhub/templates/description.tsx new file mode 100644 index 000000000..b7cecc2b0 --- /dev/null +++ b/lib/routes/oshwhub/templates/description.tsx @@ -0,0 +1,200 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DocumentItem = { + title?: string; + description?: string; + thumb?: string; +}; + +type AttachmentItem = { + src?: string; + name?: string; + size?: string | number; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + title?: string; + origin?: string; + tags?: string[]; + license?: string; + pubDate?: string; + upDated?: string; + intro?: string; + description?: string; + documents?: DocumentItem[]; + boms?: unknown[]; + attachments?: AttachmentItem[]; +}; + +const escapeHTML = (input: unknown) => { + if (input === undefined) { + return ''; + } + const str = String(input); + const escapeMap: Record = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + }; + return str.replaceAll(/[&<>"']/g, (char) => escapeMap[char] || char); +}; + +const formatObject = (obj: unknown) => { + if (typeof obj !== 'object' || obj === null) { + return escapeHTML(obj); + } + + let result = ''; + for (const key in obj as Record) { + if (Object.hasOwn(obj, key)) { + const value = (obj as Record)[key]; + if (value !== null && value !== '') { + result += `
    ${escapeHTML(key)}: ${escapeHTML(value)}
    `; + } + } + } + + return result || '无数据'; +}; + +const OshwhubDescription = ({ images, title, origin, tags, license, pubDate, upDated, intro, description, documents, boms, attachments }: DescriptionData) => { + const headers = Array.isArray(boms) ? (boms[0] as unknown[] | undefined) : undefined; + const rows = Array.isArray(boms) ? boms.slice(1) : []; + + return ( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {title || origin || tags || license || pubDate || upDated || intro ? ( + + + {title ? ( + + + + + ) : null} + {origin ? ( + + + + + ) : null} + {tags?.length ? ( + + + + + ) : null} + {license ? ( + + + + + ) : null} + {pubDate ? ( + + + + + ) : null} + {upDated ? ( + + + + + ) : null} + {intro ? ( + + + + + ) : null} + +
    名称{title}
    版本{origin}
    标签{tags.join(' / ')}
    开源协议{license}
    创建时间{pubDate}
    更新时间{upDated}
    简介{intro}
    + ) : null} + {description ? ( + <> +

    描述

    + {raw(description)} + + ) : null} + {documents?.length ? ( + <> +

    设计图

    +
    +
      + {documents.map((document) => ( +
    • + {document.title ?

      {document.title}

      : null} + {document.description ?

      {document.description}

      : null} + {document.thumb ? ( +
      + {document.title} +
      + ) : null} +
    • + ))} +
    +
    + + ) : null} + {Array.isArray(boms) ? ( + <> +

    BOM

    + + + + {(headers ?? []).map((header) => ( + + ))} + + + + {rows.map((row) => ( + {(Array.isArray(row) ? row : []).map((td, index, rowValues) => (index === rowValues.length - 1 ? : ))} + ))} + +
    {String(header)}
    {raw(formatObject(td))}{td}
    + + ) : null} + {attachments?.length ? ( + <> +

    附件

    + + + + + + + + {attachments.map((attachment, index) => ( + + + + + + ))} + +
    序号文件名称文件大小
    {index + 1}{attachment.src ? {attachment.name || '下载链接'} : attachment.name || '无文件链接'}{attachment.size}
    + + ) : null} + + ); +}; + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/osu/beatmaps/latest-ranked.ts b/lib/routes/osu/beatmaps/latest-ranked.tsx similarity index 82% rename from lib/routes/osu/beatmaps/latest-ranked.ts rename to lib/routes/osu/beatmaps/latest-ranked.tsx index e860eee78..82527d925 100644 --- a/lib/routes/osu/beatmaps/latest-ranked.ts +++ b/lib/routes/osu/beatmaps/latest-ranked.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Data, DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const actualParametersDescTable = ` | Name | Default | Description | @@ -288,7 +286,61 @@ async function handler(ctx): Promise { }; // Create a description with beatmap details and a table of difficulties - const description = art(path.join(__dirname, 'templates/beatmapset.art'), { ...beatmapset, readableTotalLength, modeLiteralToDisplayNameMap }); + const cover = beatmapset.covers['cover@2x'] || beatmapset.covers.cover; + const description = renderToString( + <> + {beatmapset.title} +

    Song Info

    +
      +
    • + English Title: {beatmapset.title} +
    • +
    • + Artist: {`${beatmapset.artist_unicode} (${beatmapset.artist})`} +
    • +
    • + Length: {readableTotalLength} +
    • +
    • + BPM: {beatmapset.bpm} +
    • +
    +

    Beatmapset Info

    +
      +
    • + Mode: {modeLiteralToDisplayNameMap[beatmapset.beatmaps[0].mode]} +
    • +
    • + Creator: {beatmapset.creator} +
    • +
    +

    Difficulties

    + + + + + + + + + + + {beatmapset.beatmaps.map((beatmap) => ( + + + + + + + ))} + +
    VersionRatingARDrain
    + + {beatmap.version} + + {beatmap.difficulty_rating.toFixed(2)}{beatmap.ar.toFixed(1)}{beatmap.drain}
    + + ); return { title: `${modeInTitle === 'true' ? `[${modeLiteralToDisplayNameMap[beatmapset.beatmaps[0].mode]}] ` : ``}${beatmapset.title_unicode ?? beatmapset.title}`, diff --git a/lib/routes/osu/beatmaps/templates/beatmapset.art b/lib/routes/osu/beatmaps/templates/beatmapset.art deleted file mode 100644 index 5d9c50f3d..000000000 --- a/lib/routes/osu/beatmaps/templates/beatmapset.art +++ /dev/null @@ -1,37 +0,0 @@ -{{ title }} - -

    Song Info

    -
      -
    • English Title: {{ title }}
    • -
    • Artist: {{ artist_unicode }} ({{ artist }})
    • -
    • Length: {{ readableTotalLength }}
    • -
    • BPM: {{ bpm }}
    • -
    - -

    Beatmapset Info

    -
      -
    • Mode: {{ modeLiteralToDisplayNameMap[beatmaps[0].mode] }}
    • -
    • Creator: {{ creator }}
    • -
    - -

    Difficulties

    - - - - - - - - - - - {{ each beatmaps as beatmap }} - - - - - - - {{ /each }} - -
    VersionRatingARDrain
    {{ beatmap.version }}{{ beatmap.difficulty_rating.toFixed(2) }}{{ beatmap.ar.toFixed(1) }}{{ beatmap.drain }}
    diff --git a/lib/routes/otobanana/templates/description.art b/lib/routes/otobanana/templates/description.art deleted file mode 100644 index 72a6921cf..000000000 --- a/lib/routes/otobanana/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if cast }} - -
    - -
    -💬 {{ cast.comment_count }} ❤️ {{ cast.like_count }} 🍌 {{ cast.gift_banana }} {{ cast.play_count }} 再生 -
    -{{@ cast.text.replace(/\n/g, '
    ') }} -{{ /if }} diff --git a/lib/routes/otobanana/utils.ts b/lib/routes/otobanana/utils.tsx similarity index 77% rename from lib/routes/otobanana/utils.ts rename to lib/routes/otobanana/utils.tsx index b1cb2fc25..7601341f7 100644 --- a/lib/routes/otobanana/utils.ts +++ b/lib/routes/otobanana/utils.tsx @@ -1,8 +1,8 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const domain = 'otobanana.com'; const apiBase = `https://api.${domain}`; @@ -16,7 +16,19 @@ const getUserInfo = (id, tryGet) => const renderCast = (cast) => ({ title: cast.title, - description: art(path.join(__dirname, 'templates/description.art'), { cast }), + description: renderToString( + <> + +
    + +
    + {`💬 ${cast.comment_count} ❤️ ${cast.like_count} 🍌 ${cast.gift_banana} ${cast.play_count} 再生`} +
    + {cast.text ? raw(cast.text.replaceAll('\n', '
    ')) : null} + + ), pubDate: parseDate(cast.created_at), link: `https://otobanana.com/cast/${cast.id}`, author: `${cast.user.name} (@${cast.user.username})`, diff --git a/lib/routes/oup/index.ts b/lib/routes/oup/index.tsx similarity index 88% rename from lib/routes/oup/index.ts rename to lib/routes/oup/index.tsx index 8d76cec88..6667a3431 100644 --- a/lib/routes/oup/index.ts +++ b/lib/routes/oup/index.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = 'https://academic.oup.com'; @@ -62,9 +60,12 @@ async function handler(ctx) { const $ = load(detailResponse); item.author = $('.al-authors-list button').text(); - item.description = art(path.join(__dirname, 'templates/article.art'), { - abstractContent: $('section.abstract > p.chapter-para').text(), - }); + item.description = renderToString( + <> +

    Abstract

    +

    {$('section.abstract > p.chapter-para').text()}

    + + ); item.pubDate = parseDate($('div.citation-date').text()); item.category = $('div.kwd-group > a') .toArray() diff --git a/lib/routes/oup/templates/article.art b/lib/routes/oup/templates/article.art deleted file mode 100644 index 05639ab23..000000000 --- a/lib/routes/oup/templates/article.art +++ /dev/null @@ -1,2 +0,0 @@ -

    Abstract

    -

    {{abstractContent}}

    diff --git a/lib/routes/papers/category.ts b/lib/routes/papers/category.ts index 745a53178..9c5fe4ee7 100644 --- a/lib/routes/papers/category.ts +++ b/lib/routes/papers/category.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -9,9 +7,10 @@ import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { id } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '50', 10); @@ -80,7 +79,7 @@ export const handler = async (ctx: Context): Promise => { }; } - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ pdfUrl: enclosureUrl, kimiUrl: `${targetUrl.replace(/[a-zA-Z0-9.]+$/, 'kimi')}?paper=${doi}`, authors, diff --git a/lib/routes/papers/query.ts b/lib/routes/papers/query.ts index 4adad380a..ee007da06 100644 --- a/lib/routes/papers/query.ts +++ b/lib/routes/papers/query.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import parser from '@/utils/rss-parser'; +import { renderDescription } from './templates/description'; + const pdfUrlGenerators = { arxiv: (id: string) => `https://arxiv.org/pdf/${id}.pdf`, }; @@ -32,11 +31,9 @@ export const handler = async (ctx) => { const pdfUrl = Object.hasOwn(pdfUrlGenerators, site) ? pdfUrlGenerators[site](id) : undefined; const authorString = item.author; - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ pdfUrl, - siteUrl: item.link, kimiUrl, - authorString, summary: item.summary, }); diff --git a/lib/routes/papers/templates/description.art b/lib/routes/papers/templates/description.art deleted file mode 100644 index 93aac7b3c..000000000 --- a/lib/routes/papers/templates/description.art +++ /dev/null @@ -1,22 +0,0 @@ -{{ if pdfUrl }} - [PDF] -{{ /if }} - -{{ if kimiUrl }} - [Kimi] -{{ /if }} - -{{ if authors }} -

    - Authors: - {{ each authors author }} - - {{ author.name }} - , - {{ /each }} -

    -{{ /if }} - -{{ if summary }} -

    {{ summary }}

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/papers/templates/description.tsx b/lib/routes/papers/templates/description.tsx new file mode 100644 index 000000000..46f917121 --- /dev/null +++ b/lib/routes/papers/templates/description.tsx @@ -0,0 +1,33 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type AuthorData = { + name?: string; + url?: string; +}; + +type DescriptionData = { + pdfUrl?: string; + kimiUrl?: string; + authors?: AuthorData[]; + summary?: string; +}; + +const PapersDescription = ({ pdfUrl, kimiUrl, authors, summary }: DescriptionData) => ( + <> + {pdfUrl ? [PDF] : null} + {kimiUrl ? [Kimi] : null} + {authors?.length ? ( +

    + Authors: + {authors.map((author) => ( + <> + {author.name}, + + ))} +

    + ) : null} + {summary ?

    {summary}

    : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/parliament.uk/petitions.ts b/lib/routes/parliament.uk/petitions.tsx similarity index 94% rename from lib/routes/parliament.uk/petitions.ts rename to lib/routes/parliament.uk/petitions.tsx index 15baa2318..afae92e78 100644 --- a/lib/routes/parliament.uk/petitions.ts +++ b/lib/routes/parliament.uk/petitions.tsx @@ -1,14 +1,12 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const { state = 'all' } = ctx.req.param(); @@ -33,10 +31,12 @@ export const handler = async (ctx: Context): Promise => { const attributes = item.attributes; const title = attributes.action; - const description = art(path.join(__dirname, 'templates/description.art'), { - intro: attributes.background, - description: attributes.additional_details, - }); + const description = renderToString( + <> + {attributes.background ?
    {attributes.background}
    : null} + {attributes.additional_details ?

    {attributes.additional_details}

    : null} + + ); const guid = `parliament.uk-petition-${item.id}`; const author: DataItem['author'] = attributes.creator_name; diff --git a/lib/routes/parliament.uk/templates/description.art b/lib/routes/parliament.uk/templates/description.art deleted file mode 100644 index eee77a054..000000000 --- a/lib/routes/parliament.uk/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} -

    {{ description }}

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/patagonia/new-arrivals.ts b/lib/routes/patagonia/new-arrivals.tsx similarity index 92% rename from lib/routes/patagonia/new-arrivals.ts rename to lib/routes/patagonia/new-arrivals.tsx index a20cd4b58..65c80f57b 100644 --- a/lib/routes/patagonia/new-arrivals.ts +++ b/lib/routes/patagonia/new-arrivals.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const host = 'https://www.patagonia.com'; const categoryMap = { @@ -69,9 +67,11 @@ async function handler(ctx) { const price = $(element).find('[itemprop="price"]').eq(0).text(); data.description = price + - art(path.join(__dirname, 'templates/product-description.art'), { - imgUrl, - }); + renderToString( +

    + +
    + ); return data; }); return { diff --git a/lib/routes/patagonia/templates/product-description.art b/lib/routes/patagonia/templates/product-description.art deleted file mode 100644 index 3ed24c4a4..000000000 --- a/lib/routes/patagonia/templates/product-description.art +++ /dev/null @@ -1,4 +0,0 @@ -
    - -
    - diff --git a/lib/routes/patreon/feed.ts b/lib/routes/patreon/feed.tsx similarity index 64% rename from lib/routes/patreon/feed.ts rename to lib/routes/patreon/feed.tsx index 8c9a7ff54..cf3bec59d 100644 --- a/lib/routes/patreon/feed.ts +++ b/lib/routes/patreon/feed.tsx @@ -1,16 +1,87 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { CreatorData, MediaRelation, PostData } from './types'; +const renderDescription = ({ attributes, relationships, included }) => { + const postType = attributes.post_type; + const imageOrder = attributes.post_metadata?.image_order ?? []; + const previewImage = attributes.image?.url ?? attributes.meta_image_url; + const audioUrl = relationships.audio?.attributes?.download_url || relationships.audio_preview?.attributes?.download_url; + const imageItems = imageOrder.map((mediaIdStr) => included.find((item) => item.id === mediaIdStr)).filter(Boolean); + + return renderToString( + <> + {postType === 'image_file' ? ( + <> + {imageItems.map((image) => ( + <> + {image.attributes.file_name} +
    + + ))} + + ) : postType === 'video_external_file' ? ( + attributes.video_preview ? ( + <> + +
    + + ) : null + ) : postType === 'audio_file' || postType === 'podcast' ? ( + <> + {attributes.thumbnail?.url ? ( + <> + +
    + + ) : null} + {audioUrl ? ( + <> + +
    + + ) : null} + + ) : postType === 'video_embed' || postType === 'link' ? ( + previewImage ? ( + <> + +
    + + ) : null + ) : postType === 'text_only' ? null : ( + <> + Post type: "{postType}" is not supported. +
    + + )} + + {attributes.content || attributes.teaser_text ? raw(attributes.content || attributes.teaser_text) : null} + + {relationships.attachments_media?.length + ? relationships.attachments_media.map((media) => ( + <> + {media.attributes.file_name} +
    + + )) + : null} + + ); +}; + export const route: Route = { path: '/:creator', categories: ['new-media'], @@ -102,7 +173,7 @@ async function handler(ctx) { return { title: attributes.title, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ attributes, relationships, included: posts.included, diff --git a/lib/routes/patreon/templates/description.art b/lib/routes/patreon/templates/description.art deleted file mode 100644 index 3d043a13b..000000000 --- a/lib/routes/patreon/templates/description.art +++ /dev/null @@ -1,41 +0,0 @@ -{{ if attributes.post_type === 'image_file' }} - {{ each attributes.post_metadata.image_order mediaIdStr }} - {{ set img = included.find((i) => i.id === mediaIdStr) }} - {{ if img }} - {{ img.attributes.file_name }}
    - {{ /if }} - {{ /each }} - -{{ else if attributes.post_type === 'video_external_file' }} - {{ if attributes.video_preview }} -
    - {{ /if }} - -{{ else if attributes.post_type === 'audio_file' || attributes.post_type === 'podcast' }} -
    - {{ set url = relationships.audio.attributes.download_url || relationships.audio_preview.attributes.download_url }} -
    - -{{ else if attributes.post_type === 'video_embed' || attributes.post_type === 'link' }} -
    - -{{ else if attributes.post_type === 'text_only' }} - -{{ else }} -Post type: "{{ attributes.post_type }}" is not supported.
    - -{{ /if }} - -{{ if attributes.content || attributes.teaser_text }} - {{@ attributes.content || attributes.teaser_text }} -{{ /if }} - -{{ if relationships.attachments_media }} - {{ each relationships.attachments_media media }} - {{ media.attributes.file_name }}
    - {{ /each }} -{{ /if }} diff --git a/lib/routes/penguin-random-house/templates/articleHeader.art b/lib/routes/penguin-random-house/templates/articleHeader.art deleted file mode 100644 index 4d9ece759..000000000 --- a/lib/routes/penguin-random-house/templates/articleHeader.art +++ /dev/null @@ -1,5 +0,0 @@ -

    -{{ imageAlt }} -
    -{{ description }} -

    \ No newline at end of file diff --git a/lib/routes/penguin-random-house/templates/book.art b/lib/routes/penguin-random-house/templates/book.art deleted file mode 100644 index c03b8a134..000000000 --- a/lib/routes/penguin-random-house/templates/book.art +++ /dev/null @@ -1,4 +0,0 @@ -{{ imageAlt }} -

    {{ title }}

    -

    {{ author }}

    -

    {{ description }}

    \ No newline at end of file diff --git a/lib/routes/penguin-random-house/utils.ts b/lib/routes/penguin-random-house/utils.tsx similarity index 78% rename from lib/routes/penguin-random-house/utils.ts rename to lib/routes/penguin-random-house/utils.tsx index a1303943d..c6586c7c8 100644 --- a/lib/routes/penguin-random-house/utils.ts +++ b/lib/routes/penguin-random-house/utils.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const parseBookInList = (element) => { const $ = load(element); @@ -19,13 +17,7 @@ const parseBookInList = (element) => { imageAlt = $('img.img-responsive').attr('alt'); } - return art(path.join(__dirname, 'templates/book.art'), { - title, - author, - description, - imageSrc, - imageAlt, - }); + return renderBookDescription(imageSrc, imageAlt, title, author, description); }; const parsePubDate = (data) => { @@ -75,11 +67,7 @@ const parseArticle = (element) => { const imageAlt = $('div.img-block>img').first().attr('alt'); let mainBlock = ''; - const descriptionBlock = art(path.join(__dirname, 'templates/articleHeader.art'), { - description, - imageSrc, - imageAlt, - }); + const descriptionBlock = renderArticleHeader(imageSrc, imageAlt, description); $('div.main-content>p,div.main-content>ul').map((i, element) => { const appending = load(element); @@ -115,4 +103,23 @@ const parseList = (items, ctx, contentParser) => ) ); +const renderBookDescription = (imageSrc: string, imageAlt: string, title: string, author: string, description: string): string => + renderToString( + <> + {imageAlt} +

    {title}

    +

    {author}

    +

    {description}

    + + ); + +const renderArticleHeader = (imageSrc: string, imageAlt: string, description: string): string => + renderToString( +

    + {imageAlt} +
    + {description} +

    + ); + export default { parseList, parseBooks, parseArticle }; diff --git a/lib/routes/picuki/profile.ts b/lib/routes/picuki/profile.ts index 5525cbf0b..96b9d3286 100644 --- a/lib/routes/picuki/profile.ts +++ b/lib/routes/picuki/profile.ts @@ -1,14 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import { config } from '@/config'; import NotFoundError from '@/errors/types/not-found'; +import { renderUserEmbed } from '@/routes/tiktok/templates/user'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { getPuppeteerPage } from '@/utils/puppeteer'; -import { art } from '@/utils/render'; export const route: Route = { path: '/profile/:id/:type?/:functionalFlag?', @@ -159,7 +157,7 @@ async function handler(ctx) { const items: DataItem[] = data.items.map((item) => ({ ...item, - description: art(path.join(__dirname, '../tiktok/templates/user.art'), { + description: renderUserEmbed({ poster: item.renderData.poster, source: item.renderData.source, useIframe, diff --git a/lib/routes/picuki/templates/post.art b/lib/routes/picuki/templates/post.art deleted file mode 100644 index d5ab8cfd3..000000000 --- a/lib/routes/picuki/templates/post.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if media }}{{@ media.replace(/\n/g, '') }}{{ /if }} -{{ if desc }}

    {{@ desc.replace(/\n/g, '
    ') }}

    {{ /if }} -{{ if locationLink.length && locationLink.attr('href') }} -

    📍 {{ locationLink.text() }}

    -{{ else if locationLink.length }} -

    📍 {{ locationLink.text() }}

    -{{ /if }} diff --git a/lib/routes/picuki/templates/post.tsx b/lib/routes/picuki/templates/post.tsx new file mode 100644 index 000000000..1d48d4740 --- /dev/null +++ b/lib/routes/picuki/templates/post.tsx @@ -0,0 +1,37 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type LocationLink = { + length: number; + attr: (key: string) => string | undefined; + text: () => string; +}; + +type PostData = { + media?: string; + desc?: string; + locationLink?: LocationLink; +}; + +const PicukiPost = ({ media, desc, locationLink }: PostData) => ( + <> + {media ? raw(media.replaceAll('\n', '')) : null} + {desc ?

    {raw(desc.replaceAll('\n', '
    '))}

    : null} + {locationLink?.length ? ( + locationLink.attr('href') ? ( +

    + 📍{' '} + + {locationLink.text()} + +

    + ) : ( +

    + 📍 {locationLink.text()} +

    + ) + ) : null} + +); + +export const renderPost = (data: PostData) => renderToString(); diff --git a/lib/routes/picuki/templates/video.art b/lib/routes/picuki/templates/video.art deleted file mode 100644 index 9bd163bb6..000000000 --- a/lib/routes/picuki/templates/video.art +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/lib/routes/picuki/templates/video.tsx b/lib/routes/picuki/templates/video.tsx new file mode 100644 index 000000000..4e781403d --- /dev/null +++ b/lib/routes/picuki/templates/video.tsx @@ -0,0 +1,16 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type VideoData = { + videoPoster?: string; + videoSrcs?: string[]; +}; + +const PicukiVideo = ({ videoPoster, videoSrcs }: VideoData) => ( + +); + +export const renderVideo = (data: VideoData) => renderToString(); diff --git a/lib/routes/pikabu/templates/video.art b/lib/routes/pikabu/templates/video.art deleted file mode 100644 index 291266421..000000000 --- a/lib/routes/pikabu/templates/video.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if videoId }} - -{{ else if webm || mp4 }} - -{{ /if }} diff --git a/lib/routes/pikabu/templates/video.tsx b/lib/routes/pikabu/templates/video.tsx new file mode 100644 index 000000000..20161d460 --- /dev/null +++ b/lib/routes/pikabu/templates/video.tsx @@ -0,0 +1,21 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type VideoData = { + videoId?: string; + preview?: string; + width?: string | number; + mp4?: string; + webm?: string; +}; + +export const renderVideo = ({ videoId, preview, width, mp4, webm }: VideoData): string => + renderToString( + videoId ? ( + + ) : webm || mp4 ? ( + + ) : null + ); diff --git a/lib/routes/pikabu/utils.ts b/lib/routes/pikabu/utils.ts index 6664a291f..db1cc27f5 100644 --- a/lib/routes/pikabu/utils.ts +++ b/lib/routes/pikabu/utils.ts @@ -1,6 +1,4 @@ -import path from 'node:path'; - -import { art } from '@/utils/render'; +import { renderVideo } from './templates/video'; const baseUrl = 'https://pikabu.ru'; @@ -25,12 +23,12 @@ const fixVideo = (element) => { if (dataType === 'video') { const videoId = element.attr('data-source').match(/\/embed\/(.+)$/)[1]; - videoHtml = art(path.join(__dirname, 'templates/video.art'), { videoId }); + videoHtml = renderVideo({ videoId }); } else if (dataType === 'video-file') { const width = element.find('.player__svg-stretch').attr('width'); const mp4 = `${element.attr('data-source')}.mp4`; const webm = element.attr('data-webm'); - videoHtml = art(path.join(__dirname, 'templates/video.art'), { preview, width, mp4, webm }); + videoHtml = renderVideo({ preview, width, mp4, webm }); } else { throw new Error(`Unknown video type: ${dataType}`); } diff --git a/lib/routes/pixabay/search.ts b/lib/routes/pixabay/search.tsx similarity index 89% rename from lib/routes/pixabay/search.ts rename to lib/routes/pixabay/search.tsx index 864e658c2..fb400f24d 100644 --- a/lib/routes/pixabay/search.ts +++ b/lib/routes/pixabay/search.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/search/:q/:order?', @@ -77,7 +76,7 @@ async function handler(ctx) { .substring(pageURL.lastIndexOf('/', pageURL.lastIndexOf('/') - 1) + 1, pageURL.lastIndexOf('/')) .replace(/(-\d+)$/, '') .replaceAll('-', ' '), - description: art(path.join(__dirname, 'templates/img.art'), { item }), + description: renderToString(), link: pageURL, category: tags.split(', '), author: user, @@ -93,3 +92,5 @@ async function handler(ctx) { item: items, }; } + +const PixabayImage = ({ item }: { item: { largeImageURL?: string; webformatURL?: string; previewURL?: string } }) => ; diff --git a/lib/routes/pixabay/templates/img.art b/lib/routes/pixabay/templates/img.art deleted file mode 100644 index 6ad14ac3e..000000000 --- a/lib/routes/pixabay/templates/img.art +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/routes/pixelstech/index.ts b/lib/routes/pixelstech/index.ts index c2a776f0b..ecf8a6962 100644 --- a/lib/routes/pixelstech/index.ts +++ b/lib/routes/pixelstech/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { topic } = ctx.req.param(); @@ -34,7 +33,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.text(); const image: string | undefined = $el.attr('data-bg-image'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -86,8 +85,8 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1').text(); const description: string | undefined = (item.description ?? '') + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('article.content-article').html(), + renderDescription({ + description: $$('article.content-article').html() ?? undefined, }); const linkUrl: string | undefined = $$('span.source-text a').attr('href'); diff --git a/lib/routes/pixelstech/templates/description.art b/lib/routes/pixelstech/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/pixelstech/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/pixelstech/templates/description.tsx b/lib/routes/pixelstech/templates/description.tsx new file mode 100644 index 000000000..f7661df1e --- /dev/null +++ b/lib/routes/pixelstech/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/pnas/index.ts b/lib/routes/pnas/index.tsx similarity index 74% rename from lib/routes/pnas/index.ts rename to lib/routes/pnas/index.tsx index 28a92fffa..f4a7d2358 100644 --- a/lib/routes/pnas/index.ts +++ b/lib/routes/pnas/index.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { CookieJar } from 'tough-cookie'; import type { Route } from '@/types'; @@ -10,7 +10,6 @@ import logger from '@/utils/logger'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; import { setCookies } from '@/utils/puppeteer-utils'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:topicPath{.+}?', @@ -89,18 +88,24 @@ async function handler(ctx) { item.category = [...keywords, topic]; item.author = PNASdataLayer.page.pageInfo.author; item.doi = PNASdataLayer.page.pageInfo.DOI; - item.description = art(path.join(__dirname, 'templates/article.art'), { - access: PNASdataLayer.user.access === 'yes', - // - abstracts: $('#abstracts .core-container').html(), - // - articleBody: $('[property=articleBody]').html(), - // - dataAvailability: $('#data-availability').html(), - acknowledgments: $('#acknowledgments').html(), - supplementaryMaterials: $('#supplementary-materials').html(), - bibliography: $('#bibliography').html(), - }); + const access = PNASdataLayer.user.access === 'yes'; + const abstracts = $('#abstracts .core-container').html(); + const articleBody = $('[property=articleBody]').html(); + const dataAvailability = $('#data-availability').html(); + const acknowledgments = $('#acknowledgments').html(); + const supplementaryMaterials = $('#supplementary-materials').html(); + const bibliography = $('#bibliography').html(); + + item.description = renderToString( + <> + {abstracts ? raw(abstracts) : null} + {access && articleBody ? raw(articleBody) : null} + {dataAvailability ? raw(dataAvailability) : null} + {acknowledgments ? raw(acknowledgments) : null} + {supplementaryMaterials ? raw(supplementaryMaterials) : null} + {bibliography ? raw(bibliography) : null} + + ); return item; }) diff --git a/lib/routes/pnas/templates/article.art b/lib/routes/pnas/templates/article.art deleted file mode 100644 index e947183a9..000000000 --- a/lib/routes/pnas/templates/article.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if abstracts }}{{@ abstracts }}{{ /if }} - -{{ if access }}{{@ articleBody }}{{ /if }} - -{{ if dataAvailability }}{{@ dataAvailability }}{{ /if }} -{{ if acknowledgments }}{{@ acknowledgments }}{{ /if }} -{{ if supplementaryMaterials }}{{@ supplementaryMaterials }}{{ /if }} -{{ if bibliography }}{{@ bibliography }}{{ /if }} diff --git a/lib/routes/pornhub/templates/description.art b/lib/routes/pornhub/templates/description.art deleted file mode 100644 index 94b9466bd..000000000 --- a/lib/routes/pornhub/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if previewVideo }} - -{{ /if }} - -{{ if thumbs }} - {{ each thumbs t }} - - {{ /each }} -{{ /if }} diff --git a/lib/routes/pornhub/utils.ts b/lib/routes/pornhub/utils.tsx similarity index 72% rename from lib/routes/pornhub/utils.ts rename to lib/routes/pornhub/utils.tsx index 0d65e0ee3..51607f380 100644 --- a/lib/routes/pornhub/utils.ts +++ b/lib/routes/pornhub/utils.tsx @@ -1,9 +1,7 @@ -import path from 'node:path'; - import dayjs from 'dayjs'; +import { renderToString } from 'hono/jsx/dom/server'; import { parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const defaultDomain = 'https://www.pornhub.com'; @@ -12,7 +10,19 @@ const headers = { hasVisited: 1, }; -const renderDescription = (data) => art(path.join(__dirname, 'templates/description.art'), data); +const renderDescription = (data): string => + renderToString( + <> + {data.previewVideo ? ( + + ) : null} + {data.thumbs?.map((thumb, index) => ( + + ))} + + ); const extractDateFromImageUrl = (imageUrl) => { const matchResult = imageUrl.match(/(\d{6})\/(\d{2})/); return matchResult ? matchResult.slice(1, 3).join('') : null; diff --git a/lib/routes/producereport/index.ts b/lib/routes/producereport/index.ts index ff6746d66..e5aac57e8 100644 --- a/lib/routes/producereport/index.ts +++ b/lib/routes/producereport/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'produce/fresh-fruits/apples' } = ctx.req.param(); @@ -39,7 +38,7 @@ export const handler = async (ctx: Context): Promise => { ?.replace(/styles\/thumbnail\/public/, '') ?.split(/\?/)?.[0]; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -85,7 +84,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('meta[property="og:title"]').attr('content') ?? item.title; const image: string | undefined = $$('meta[property="og:image"]').attr('content'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -94,7 +93,7 @@ export const handler = async (ctx: Context): Promise => { }, ] : undefined, - description: $$('div[property="content:encoded"]').html(), + description: $$('div[property="content:encoded"]').html() ?? undefined, }); const pubDateStr: string | undefined = $$('div.pane-node-created').text()?.trim(); const categoryEls: Element[] = $$('div.pane-node-field-topics a').toArray(); diff --git a/lib/routes/producereport/templates/description.art b/lib/routes/producereport/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/producereport/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/producereport/templates/description.tsx b/lib/routes/producereport/templates/description.tsx new file mode 100644 index 000000000..f7661df1e --- /dev/null +++ b/lib/routes/producereport/templates/description.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionRenderOptions = { + images?: DescriptionImage[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt +
    + ) : null + )} + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/producthunt/templates/description.art b/lib/routes/producthunt/templates/description.art deleted file mode 100644 index d765cf023..000000000 --- a/lib/routes/producthunt/templates/description.art +++ /dev/null @@ -1,19 +0,0 @@ -{{ if tagline }} -
    {{ tagline }}
    -{{ /if }} - -{{ if description }} -
    {{ description }}
    -{{ /if }} - -{{ if media }} - {{ each media m }} - {{ if m.mediaType === 'image' }} -
    - {{ else if m.mediaType === 'video' }} - {{ if m.metadata.platform === 'youtube' }} - - {{ /if }} - {{ /if }} - {{ /each }} -{{ /if }} diff --git a/lib/routes/producthunt/today.ts b/lib/routes/producthunt/today.tsx similarity index 66% rename from lib/routes/producthunt/today.ts rename to lib/routes/producthunt/today.tsx index 75ce3e89d..4409f7cb9 100644 --- a/lib/routes/producthunt/today.ts +++ b/lib/routes/producthunt/today.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/today', @@ -89,7 +87,7 @@ async function handler() { const post = response.data.post; item.author = post.user.name; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ tagline: post.tagline, description: post.description, media: post.media, @@ -106,3 +104,53 @@ async function handler() { item: items, }; } + +type MediaItem = { + mediaType?: string; + imageUuid?: string; + metadata?: { + platform?: string; + videoId?: string; + }; +}; + +type DescriptionProps = { + tagline?: string; + description?: string; + media?: MediaItem[]; +}; + +const renderDescription = ({ tagline, description, media }: DescriptionProps): string => + renderToString( + <> + {tagline ?
    {tagline}
    : null} + {description ?
    {description}
    : null} + {media?.map((item) => { + if (item.mediaType === 'image' && item.imageUuid) { + return ( + <> + +
    + + ); + } + + if (item.mediaType === 'video' && item.metadata?.platform === 'youtube' && item.metadata.videoId) { + return ( + + ); + } + + return null; + })} + + ); diff --git a/lib/routes/ps/monthly-games.ts b/lib/routes/ps/monthly-games.tsx similarity index 80% rename from lib/routes/ps/monthly-games.ts rename to lib/routes/ps/monthly-games.tsx index 01dc05c6b..ec7bfd417 100644 --- a/lib/routes/ps/monthly-games.ts +++ b/lib/routes/ps/monthly-games.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/monthly-games', @@ -32,6 +30,14 @@ export const route: Route = { url: 'www.playstation.com/en-sg/ps-plus/whats-new', }; +const renderDescription = (img, text) => + renderToString( + <> + + {text} + + ); + async function handler() { const baseUrl = 'https://www.playstation.com/en-sg/ps-plus/whats-new/'; @@ -44,10 +50,7 @@ async function handler() { const item = $(e); return { title: item.find('h3').text(), - description: art(path.join(__dirname, 'templates/monthly-games.art'), { - img: item.find('.media-block__img source').attr('srcset'), - text: item.find('h3 + p').text(), - }), + description: renderDescription(item.find('.media-block__img source').attr('srcset'), item.find('h3 + p').text()), link: item.find('.btn--cta').attr('href'), }; }); diff --git a/lib/routes/ps/templates/monthly-games.art b/lib/routes/ps/templates/monthly-games.art deleted file mode 100644 index f14428b9b..000000000 --- a/lib/routes/ps/templates/monthly-games.art +++ /dev/null @@ -1 +0,0 @@ -{{ text }} diff --git a/lib/routes/psyche/templates/essay.art b/lib/routes/psyche/templates/essay.art deleted file mode 100644 index 5a2fab892..000000000 --- a/lib/routes/psyche/templates/essay.art +++ /dev/null @@ -1,3 +0,0 @@ - -{{@ authorsBio }} -{{@ content}} \ No newline at end of file diff --git a/lib/routes/psyche/templates/essay.tsx b/lib/routes/psyche/templates/essay.tsx new file mode 100644 index 000000000..ced4c8ea6 --- /dev/null +++ b/lib/routes/psyche/templates/essay.tsx @@ -0,0 +1,17 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type EssayData = { + banner?: string; + authorsBio?: string; + content?: string; +}; + +export const renderEssay = ({ banner, authorsBio, content }: EssayData) => + renderToString( + <> + + {authorsBio ? raw(authorsBio) : null} + {content ? raw(content) : null} + + ); diff --git a/lib/routes/psyche/templates/video.art b/lib/routes/psyche/templates/video.art deleted file mode 100644 index cd45ad886..000000000 --- a/lib/routes/psyche/templates/video.art +++ /dev/null @@ -1,10 +0,0 @@ -{{ set video = article.hosterId }} -{{ if article.hoster === 'vimeo' }} - {{ set video = "https://player.vimeo.com/video/" + video + "?dnt=1"}} -{{ else if article.hoster == 'youtube' }} - {{ set video = "https://www.youtube-nocookie.com/embed/" + video }} -{{ /if }} - - -{{@ article.credits}} -{{@ article.description}} diff --git a/lib/routes/psyche/templates/video.tsx b/lib/routes/psyche/templates/video.tsx new file mode 100644 index 000000000..724727bae --- /dev/null +++ b/lib/routes/psyche/templates/video.tsx @@ -0,0 +1,19 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +export const renderVideo = (article) => { + let videoUrl = article.hosterId; + if (article.hoster === 'vimeo') { + videoUrl = `https://player.vimeo.com/video/${videoUrl}?dnt=1`; + } else if (article.hoster === 'youtube') { + videoUrl = `https://www.youtube-nocookie.com/embed/${videoUrl}`; + } + + return renderToString( + <> + + {article.credits ? raw(article.credits) : null} + {article.description ? raw(article.description) : null} + + ); +}; diff --git a/lib/routes/psyche/utils.ts b/lib/routes/psyche/utils.ts index 8e6cdc205..08f35f6b0 100644 --- a/lib/routes/psyche/utils.ts +++ b/lib/routes/psyche/utils.ts @@ -1,10 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; + +import { renderEssay } from './templates/essay'; +import { renderVideo } from './templates/video'; const getImageById = async (id) => { const response = await ofetch('https://api.aeonmedia.co/graphql', { @@ -31,7 +31,7 @@ function format(article) { switch (type) { case 'film': - block = art(path.join(__dirname, 'templates/video.art'), { article }); + block = renderVideo(article); break; @@ -50,7 +50,7 @@ function format(article) { }) .join(''); - block = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content }); + block = renderEssay({ banner, authorsBio, content }); break; } @@ -60,7 +60,7 @@ function format(article) { const capture = load(article.body); capture('p.pullquote').remove(); - block = art(path.join(__dirname, 'templates/essay.art'), { banner, authorsBio, content: capture.html() }); + block = renderEssay({ banner, authorsBio, content: capture.html() }); break; } diff --git a/lib/routes/pts/curations.ts b/lib/routes/pts/curations.ts index 3bdded723..74c4c751d 100644 --- a/lib/routes/pts/curations.ts +++ b/lib/routes/pts/curations.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/curations', @@ -55,7 +54,7 @@ async function handler() { title: item.text(), link: item.attr('href'), pubDate: parseDate(projectDiv.find('time').text()), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: projectDiv.parent().find('.cover-fit').attr('src'), }), }; diff --git a/lib/routes/pts/index.ts b/lib/routes/pts/index.ts index d8899e01c..55ba5dbd6 100644 --- a/lib/routes/pts/index.ts +++ b/lib/routes/pts/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; @@ -7,9 +5,10 @@ import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const route: Route = { path: '*', name: 'Unknown', @@ -61,7 +60,7 @@ async function handler(ctx) { .toArray() .map((t) => content(t).text()) .filter((t) => t !== '...'); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ image: content('meta[property="og:image"]').attr('content'), description: content('.post-article').html(), }); diff --git a/lib/routes/pts/live.ts b/lib/routes/pts/live.ts index bb7bcd471..ac9deb5ee 100644 --- a/lib/routes/pts/live.ts +++ b/lib/routes/pts/live.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderLive } from './templates/live'; export const route: Route = { path: '/live/:id', @@ -57,7 +56,7 @@ async function handler(ctx) { item.title = data.title; item.pubDate = parseDate(data.updatedDate); - item.description = art(path.join(__dirname, 'templates/live.art'), { + item.description = renderLive({ images: data.content.filter((d) => d.type === 'img').map((i) => `${imageRootUrl}/${i.imgFileUrl}`), texts: data.content.filter((d) => d.type === 'text').map((t) => t.content), }); diff --git a/lib/routes/pts/projects.ts b/lib/routes/pts/projects.ts index fc74e190b..419a46a93 100644 --- a/lib/routes/pts/projects.ts +++ b/lib/routes/pts/projects.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/projects', @@ -54,7 +53,7 @@ async function handler() { title: item.text(), link: item.attr('href'), pubDate: parseDate(projectDiv.find('time').text()), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: projectDiv.parent().find('.cover-fit')?.attr('src') ?? projectDiv.parent().parent().find('.cover-fit').attr('src'), description: description ? `

    ${description}

    ` : '', }), diff --git a/lib/routes/pts/templates/description.art b/lib/routes/pts/templates/description.art deleted file mode 100644 index 69e93d20c..000000000 --- a/lib/routes/pts/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if image }} - -{{ /if }} -{{ if description }} -{{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/pts/templates/description.tsx b/lib/routes/pts/templates/description.tsx new file mode 100644 index 000000000..7d331d4c4 --- /dev/null +++ b/lib/routes/pts/templates/description.tsx @@ -0,0 +1,15 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: string; + description?: string; +}; + +export const renderDescription = ({ image, description }: DescriptionData): string => + renderToString( + <> + {image ? : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/pts/templates/live.art b/lib/routes/pts/templates/live.art deleted file mode 100644 index 2278835f8..000000000 --- a/lib/routes/pts/templates/live.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ each images image }} - -{{ /each }} -{{ each texts text }} -{{@ text }} -{{ /each }} \ No newline at end of file diff --git a/lib/routes/pts/templates/live.tsx b/lib/routes/pts/templates/live.tsx new file mode 100644 index 000000000..ec1989c6b --- /dev/null +++ b/lib/routes/pts/templates/live.tsx @@ -0,0 +1,19 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type LiveData = { + images: string[]; + texts: string[]; +}; + +export const renderLive = ({ images, texts }: LiveData): string => + renderToString( + <> + {images.map((image) => ( + + ))} + {texts.map((text) => ( + <>{raw(text)} + ))} + + ); diff --git a/lib/routes/pubmed/templates/description.art b/lib/routes/pubmed/templates/description.art deleted file mode 100644 index c4b8e232b..000000000 --- a/lib/routes/pubmed/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if authors }} -{{@ authors }} -{{ /if}} -
    -{{ if abs }} -{{@ abs }} -{{ /if}} \ No newline at end of file diff --git a/lib/routes/pubmed/trending.ts b/lib/routes/pubmed/trending.tsx similarity index 78% rename from lib/routes/pubmed/trending.ts rename to lib/routes/pubmed/trending.tsx index b9db7b59d..8897a7aa3 100644 --- a/lib/routes/pubmed/trending.ts +++ b/lib/routes/pubmed/trending.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/trending/:filters?', @@ -51,10 +50,13 @@ async function handler(ctx) { item.doi = content('meta[name="citation_doi"]').attr('content'); item.pubDate = parseDate(content('meta[name="citation_date"]').attr('content')); - item.description = art(path.join(__dirname, 'templates/description.art'), { - authors: content('.authors-list').html(), - abs: content('#enc-abstract').html(), - }); + item.description = renderToString( + <> + {content('.authors-list').html() ? raw(content('.authors-list').html()) : null} +
    + {content('#enc-abstract').html() ? raw(content('#enc-abstract').html()) : null} + + ); return item; }) diff --git a/lib/routes/qidian/author.ts b/lib/routes/qidian/author.tsx similarity index 76% rename from lib/routes/qidian/author.ts rename to lib/routes/qidian/author.tsx index f7cb6d697..f3827d73f 100644 --- a/lib/routes/qidian/author.ts +++ b/lib/routes/qidian/author.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate, parseRelativeDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -49,10 +47,7 @@ async function handler(ctx) { title: messageItem.find('.author-item-title').text().trim(), author: authorName, category: messageItem.find('.author-item-exp a').first().text().trim(), - description: art(path.join(__dirname, 'templates/description.art'), { - description: messageItem.find('.author-item-update a').attr('title'), - image: item.find('a img').attr('src'), - }), + description: renderDescription(messageItem.find('.author-item-update a').attr('title'), item.find('a img').attr('src')), pubDate: timezone(/(今|昨)/.test(updatedDate) ? parseRelativeDate(updatedDate) : parseDate(updatedDate, 'YYYY-MM-DD HH:mm'), +8), link: messageItem.find('.author-item-update a').attr('href'), }; @@ -65,3 +60,13 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (description?: string, image?: string, author?: string) => renderToString(); + +const QidianDescription = ({ description, image, author }: { description?: string; image?: string; author?: string }) => ( + <> +

    {description}

    + {image ? : null} + {author ?? null} + +); diff --git a/lib/routes/qidian/templates/description.art b/lib/routes/qidian/templates/description.art deleted file mode 100644 index 33a6ef7e6..000000000 --- a/lib/routes/qidian/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -

    {{ description }}

    -{{ if image }} - -{{ /if }} -{{ author }} \ No newline at end of file diff --git a/lib/routes/qoo-app/apps/comment.ts b/lib/routes/qoo-app/apps/comment.ts index e8db39377..253e707b0 100644 --- a/lib/routes/qoo-app/apps/comment.ts +++ b/lib/routes/qoo-app/apps/comment.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderComment } from '../templates/comment'; import { appsUrl } from '../utils'; export const route: Route = { @@ -50,7 +48,7 @@ async function handler(ctx) { return { title: `${author} ▶ ${item.find('.qoo-clearfix .name a').eq(1).text()}`, link: item.find('a.bg-click-wrap').attr('href'), - description: art(path.join(__dirname, '../templates/comment.art'), { + description: renderComment({ rating: item.find('.qoo-rating-bar').text().trim(), text: item.find('.text-view').html(), }), diff --git a/lib/routes/qoo-app/notes/note.ts b/lib/routes/qoo-app/notes/note.ts index d86332157..296586d25 100644 --- a/lib/routes/qoo-app/notes/note.ts +++ b/lib/routes/qoo-app/notes/note.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; +import { renderNote } from '../templates/note'; import { notesUrl, ssoUrl } from '../utils'; export const route: Route = { @@ -47,7 +45,7 @@ async function handler(ctx) { const items = data.data.map((item) => ({ title: item.content, - description: art(path.join(__dirname, '../templates/note.art'), { + description: renderNote({ content: item.content, picture: item.picture, }), diff --git a/lib/routes/qoo-app/templates/comment.art b/lib/routes/qoo-app/templates/comment.art deleted file mode 100644 index 6e0b42c6c..000000000 --- a/lib/routes/qoo-app/templates/comment.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ rating }}/5.0 -
    -{{@ text }} diff --git a/lib/routes/qoo-app/templates/comment.tsx b/lib/routes/qoo-app/templates/comment.tsx new file mode 100644 index 000000000..c31010f01 --- /dev/null +++ b/lib/routes/qoo-app/templates/comment.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type CommentData = { + rating?: string | number; + text?: string; +}; + +export const renderComment = ({ rating, text }: CommentData) => + renderToString( + <> + {rating}/5.0 +
    + {text ? raw(text) : null} + + ); diff --git a/lib/routes/qoo-app/templates/note.art b/lib/routes/qoo-app/templates/note.art deleted file mode 100644 index db298159c..000000000 --- a/lib/routes/qoo-app/templates/note.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ content }} -{{ if picture }} -
    - -{{ /if }} diff --git a/lib/routes/qoo-app/templates/note.tsx b/lib/routes/qoo-app/templates/note.tsx new file mode 100644 index 000000000..5e190055b --- /dev/null +++ b/lib/routes/qoo-app/templates/note.tsx @@ -0,0 +1,19 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type NoteData = { + content?: string; + picture?: string; +}; + +export const renderNote = ({ content, picture }: NoteData) => + renderToString( + <> + {content} + {picture ? ( + <> +
    + + + ) : null} + + ); diff --git a/lib/routes/qoo-app/user/app-comment.ts b/lib/routes/qoo-app/user/app-comment.ts index 9f050ea44..d1ba7dc88 100644 --- a/lib/routes/qoo-app/user/app-comment.ts +++ b/lib/routes/qoo-app/user/app-comment.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderComment } from '../templates/comment'; import { appsUrl, userUrl } from '../utils'; export const route: Route = { @@ -45,7 +43,7 @@ async function handler(ctx) { const items = data.list.map((item) => ({ title: `${username} ▶ ${item.app.name}`, link: `${appsUrl}/comment-detail/${item.comment.id}`, - description: art(path.join(__dirname, '../templates/comment.art'), { + description: renderComment({ rating: item.score, text: item.comment.content, }), diff --git a/lib/routes/qq/ac/templates/description.art b/lib/routes/qq/ac/templates/description.art deleted file mode 100644 index eaa42448e..000000000 --- a/lib/routes/qq/ac/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if image !== '' }} - -{{ /if }} -{{ if description !== '' }} -

    {{ description }}

    -{{ /if }} -{{ each chapters chapter }} - -{{/each}} \ No newline at end of file diff --git a/lib/routes/qq/ac/utils.ts b/lib/routes/qq/ac/utils.tsx similarity index 55% rename from lib/routes/qq/ac/utils.ts rename to lib/routes/qq/ac/utils.tsx index 6499a677f..28cebab54 100644 --- a/lib/routes/qq/ac/utils.ts +++ b/lib/routes/qq/ac/utils.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const rootUrl = 'https://ac.qq.com'; const mobileRootUrl = 'https://m.ac.qq.com'; @@ -44,17 +42,19 @@ const ProcessItems = async (ctx, currentUrl, time, title) => { .toArray() .map((a) => $(a).text().trim()) .join(', '); - item.description = art(path.join(__dirname, 'templates/description.art'), { - image: content('.head-cover')?.attr('src') ?? '', - description: content('.head-info-desc')?.text() ?? '', - chapters: content('.reverse .bottom-chapter-item .chapter-link') - .toArray() - .map((chapter) => ({ - link: content(chapter).attr('href'), - title: content(chapter).find('.comic-title')?.text() ?? '', - image: content(chapter).find('.cover-image')?.attr('src') ?? '', - })), - }); + item.description = renderToString( + ({ + link: content(chapter).attr('href'), + title: content(chapter).find('.comic-title')?.text() ?? '', + image: content(chapter).find('.cover-image')?.attr('src') ?? '', + }))} + /> + ); return item; }) @@ -69,3 +69,16 @@ const ProcessItems = async (ctx, currentUrl, time, title) => { }; export { mobileRootUrl, ProcessItems, rootUrl }; + +const QqAcDescription = ({ image, description, chapters }: { image: string; description: string; chapters: { link?: string; title?: string; image?: string }[] }) => ( + <> + {image === '' ? null : } + {description === '' ? null :

    {description}

    } + {chapters.map((chapter) => ( + + ))} + +); diff --git a/lib/routes/qq/fact/index.ts b/lib/routes/qq/fact/index.tsx similarity index 69% rename from lib/routes/qq/fact/index.ts rename to lib/routes/qq/fact/index.tsx index 3c79365e3..ca93a9689 100644 --- a/lib/routes/qq/fact/index.ts +++ b/lib/routes/qq/fact/index.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import CryptoJS from 'crypto-js'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const getRequestToken = () => { const e = 'sgn51n6r6q97o6g3'; @@ -71,9 +70,7 @@ async function handler() { const nextData = JSON.parse($('#__NEXT_DATA__').text()); const { initialState } = nextData.props.pageProps; - item.description = art(path.join(__dirname, '../templates/article.art'), { - data: initialState, - }); + item.description = renderToString(); item.pubDate = parseDate(initialState.createdAt); return item; @@ -87,3 +84,25 @@ async function handler() { item: items, }; } + +const QqFactDescription = ({ data }: { data: any }) => { + const cover = data.cover?.startsWith('//') ? `https:${data.cover}` : data.cover?.startsWith('http') ? data.cover : data.cover ? `https://${data.cover}` : undefined; + + return ( + <> + {cover ? : null} + {data.rumor ?
    流传说法:{data.rumor}
    : null} + {data.abstract ? ( + <> + 查证要点: +
      + {data.abstract.map((item) => ( +
    1. {item.content}
    2. + ))} +
    + + ) : null} + {data.content ?
    {raw(data.content)}
    : null} + + ); +}; diff --git a/lib/routes/qq/templates/article.art b/lib/routes/qq/templates/article.art deleted file mode 100644 index 12b6f6163..000000000 --- a/lib/routes/qq/templates/article.art +++ /dev/null @@ -1,19 +0,0 @@ -{{ if data.cover }} - -{{ /if }} - -{{ if data.rumor }} -
    流传说法:{{ data.rumor }}
    -{{ /if }} - -{{ if data.abstract }}查证要点: -
      - {{ each data.abstract item }} -
    1. {{ item.content }}
    2. - {{ /each }} -
    -{{ /if }} - -{{ if data.content }} -
    {{@ data.content }}
    -{{ /if }} diff --git a/lib/routes/questmobile/report.ts b/lib/routes/questmobile/report.ts index b34deddae..13da2f466 100644 --- a/lib/routes/questmobile/report.ts +++ b/lib/routes/questmobile/report.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; /** * Parses a tree array and returns an array of objects containing the key-value pairs. @@ -194,7 +193,7 @@ async function handler(ctx) { let items = response.data.slice(0, limit).map((item) => ({ title: item.title, link: new URL(`research/report/${item.id}`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: { src: item.coverImgUrl, alt: item.title, @@ -217,7 +216,7 @@ async function handler(ctx) { content('div.text div.daoyu').remove(); item.title = content('div.title h1').text(); - item.description += art(path.join(__dirname, 'templates/description.art'), { + item.description += renderDescription({ description: content('div.text').html(), }); item.author = content('div.source') diff --git a/lib/routes/questmobile/templates/description.art b/lib/routes/questmobile/templates/description.art deleted file mode 100644 index dd4ad1216..000000000 --- a/lib/routes/questmobile/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if introduction }} -
    {{ introduction }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/questmobile/templates/description.tsx b/lib/routes/questmobile/templates/description.tsx new file mode 100644 index 000000000..8b2b415a4 --- /dev/null +++ b/lib/routes/questmobile/templates/description.tsx @@ -0,0 +1,25 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + image?: { + src?: string; + alt?: string; + }; + introduction?: string; + description?: string; +}; + +const QuestMobileDescription = ({ image, introduction, description }: DescriptionData) => ( + <> + {image?.src ? ( +
    + {image.alt} +
    + ) : null} + {introduction ?
    {introduction}
    : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/radio/album.ts b/lib/routes/radio/album.ts index daf54a41f..4d8113da9 100644 --- a/lib/routes/radio/album.ts +++ b/lib/routes/radio/album.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + const audio_types = { m3u8: 'x-mpegURL', mp3: 'mpeg', @@ -83,10 +82,7 @@ async function handler(ctx) { guid: item.id, title: item.name, link: `${rootUrl}/share/albumPlay?correlateId=${item.id}&columnId=${id}`, - description: art(path.join(__dirname, 'templates/description.art'), { - enclosure_url, - enclosure_type, - }), + description: renderDescription({ enclosure_url, enclosure_type }), pubDate: timezone(parseDate(item.createTime), +8), enclosure_url, enclosure_type, diff --git a/lib/routes/radio/index.ts b/lib/routes/radio/index.ts index f09525aa7..8a646cba5 100644 --- a/lib/routes/radio/index.ts +++ b/lib/routes/radio/index.ts @@ -1,9 +1,8 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/:id', @@ -60,7 +59,7 @@ async function handler(ctx) { guid: item.id, title: item.name, link: item.streams[0].url, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ description: item.description, enclosure_url, enclosure_type, diff --git a/lib/routes/radio/templates/description.art b/lib/routes/radio/templates/description.art deleted file mode 100644 index 4844f516f..000000000 --- a/lib/routes/radio/templates/description.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if description }} -

    {{ description }}

    -{{ /if }} -{{ if enclosure_url && enclosure_type }} - -{{ /if }} diff --git a/lib/routes/radio/templates/description.tsx b/lib/routes/radio/templates/description.tsx new file mode 100644 index 000000000..e9f633d38 --- /dev/null +++ b/lib/routes/radio/templates/description.tsx @@ -0,0 +1,20 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + description?: string; + enclosure_url?: string; + enclosure_type?: string; +}; + +const RadioDescription = ({ description, enclosure_url, enclosure_type }: DescriptionData) => ( + <> + {description ?

    {description}

    : null} + {enclosure_url && enclosure_type ? ( + + ) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/radio/zhibo.ts b/lib/routes/radio/zhibo.ts index e53bdd0a6..9f56c7a14 100644 --- a/lib/routes/radio/zhibo.ts +++ b/lib/routes/radio/zhibo.ts @@ -1,11 +1,10 @@ -import path from 'node:path'; - import CryptoJS from 'crypto-js'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const audio_types = { m3u8: 'x-mpegURL', @@ -84,11 +83,7 @@ async function handler(ctx) { guid: item.id, title: `${dateString} ${item.name}`, link: enclosure_url, - description: art(path.join(__dirname, 'templates/description.art'), { - description: item.des, - enclosure_url, - enclosure_type, - }), + description: renderDescription({ description: item.des, enclosure_url, enclosure_type }), pubDate: parseDate(item.startTime), enclosure_url, enclosure_type, diff --git a/lib/routes/raspberrypi/magazine.ts b/lib/routes/raspberrypi/magazine.ts index df8a315e0..f17657be0 100644 --- a/lib/routes/raspberrypi/magazine.ts +++ b/lib/routes/raspberrypi/magazine.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '12', 10); @@ -35,7 +34,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.text()?.trim(); const image: string | undefined = $el.find('div.o-media__fixed a.c-link img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -83,8 +82,8 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('h1.rspec-issue__heading').text().split(/-/).pop()?.trim() ?? item.title; const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.rspec-issue__description').html(), + renderDescription({ + description: $$('div.rspec-issue__description').html() || undefined, }); const pubDateStr: string | undefined = $$('time.rspec-issue__publication-month').attr('datetime'); const image: string | undefined = $$('img.c-figure__image').attr('src'); diff --git a/lib/routes/raspberrypi/templates/description.art b/lib/routes/raspberrypi/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/raspberrypi/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/raspberrypi/templates/description.tsx b/lib/routes/raspberrypi/templates/description.tsx new file mode 100644 index 000000000..81ceaef91 --- /dev/null +++ b/lib/routes/raspberrypi/templates/description.tsx @@ -0,0 +1,22 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src: string; + alt?: string; +}; + +type DescriptionProps = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionProps): string => + renderToString( + <> + {images?.length ? images.map((image) => (image?.src ?
    {image.alt ? {image.alt} : }
    : null)) : null} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/rattibha/templates/description.art b/lib/routes/rattibha/templates/description.art deleted file mode 100644 index e68717eaf..000000000 --- a/lib/routes/rattibha/templates/description.art +++ /dev/null @@ -1,12 +0,0 @@ -{{ if media }} - {{ if media.type === 1 }} - - {{ else if media.type === 2 }} - - {{ /if }} -
    -{{ /if }} - -{{@ text }} diff --git a/lib/routes/rattibha/user.ts b/lib/routes/rattibha/user.tsx similarity index 71% rename from lib/routes/rattibha/user.ts rename to lib/routes/rattibha/user.tsx index 52be2f03c..b99b540db 100644 --- a/lib/routes/rattibha/user.ts +++ b/lib/routes/rattibha/user.tsx @@ -1,14 +1,11 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; -import { getCurrentPath } from '@/utils/helpers'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -const __dirname = getCurrentPath(import.meta.url); export const route: Route = { path: '/user/:user', @@ -66,10 +63,23 @@ async function handler(ctx) { updated: parseDate(item.thread.updated_at), author: userData.name, category: item.thread.categories.map((category) => category.tag.name), - description: art(path.join(__dirname, 'templates/description.art'), { - text: item.thread.t.info.text.replaceAll('\n', '
    '), - media: item.thread.m, - }), + description: renderToString( + <> + {item.thread.m ? ( + <> + {item.thread.m.type === 1 ? ( + + ) : item.thread.m.type === 2 ? ( + + ) : null} +
    + + ) : null} + {raw(item.thread.t.info.text.replaceAll('\n', '
    '))} + + ), })); return { diff --git a/lib/routes/rawkuma/manga.ts b/lib/routes/rawkuma/manga.tsx similarity index 91% rename from lib/routes/rawkuma/manga.ts rename to lib/routes/rawkuma/manga.tsx index 7daae2cd6..cbf47ff3b 100644 --- a/lib/routes/rawkuma/manga.ts +++ b/lib/routes/rawkuma/manga.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/manga/:id', @@ -77,9 +75,13 @@ async function handler(ctx) { const images = imageMatches ? JSON.parse(imageMatches[1]) : []; item.title = content('div.chpnw').text().trim(); - item.description = art(path.join(__dirname, 'templates/description.art'), { - images, - }); + item.description = renderToString( + <> + {images.map((image) => ( + + ))} + + ); item.author = author; item.category = category; item.pubDate = parseDate(content('time.entry-date').prop('datetime').replace(/WIB/, 'T')); diff --git a/lib/routes/rawkuma/templates/description.art b/lib/routes/rawkuma/templates/description.art deleted file mode 100644 index 53c7f7b39..000000000 --- a/lib/routes/rawkuma/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each images image }} - -{{ /each }} \ No newline at end of file diff --git a/lib/routes/readhub/index.ts b/lib/routes/readhub/index.ts index a405411bc..881568460 100644 --- a/lib/routes/readhub/index.ts +++ b/lib/routes/readhub/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; @@ -7,7 +5,8 @@ import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { apiTopicUrl, art, processItems, rootUrl } from './util'; +import { renderDescription } from './templates/description'; +import { apiTopicUrl, processItems, rootUrl } from './util'; export const route: Route = { path: '/:category?', @@ -51,10 +50,11 @@ async function handler(ctx) { let items = response.data.items.slice(0, limit).map((item) => ({ title: item.title, link: item.url ?? new URL(`topic/${item.uid}`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ description: item.summary, news: item.newsAggList, timeline: item.timeline, + rootUrl, }), author: item.siteNameDisplay, category: [...(item.entityList.map((c) => c.name) ?? []), ...(item.tagList.map((c) => c.name) ?? [])], diff --git a/lib/routes/readhub/templates/description.art b/lib/routes/readhub/templates/description.art deleted file mode 100644 index a7570ea87..000000000 --- a/lib/routes/readhub/templates/description.art +++ /dev/null @@ -1,40 +0,0 @@ -{{ if description }} -

    {{ description }}

    -{{ /if }} - -{{ if news }} -

    媒体报道

    - - - {{ each news n }} - - - - - {{ /each }} - -
    - {{ n.title }} - - {{ n.siteNameDisplay }} -
    -{{ /if }} - -{{ if timeline }} -

    事件追踪

    - - - {{ set topics = timeline.topics }} - {{ each topics t }} - - - - - {{ /each }} - -
    - {{ t.publishDate | formatDate 'YYYY-MM-DD HH:mm:ss' }} - - {{ t.title }} -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/readhub/templates/description.tsx b/lib/routes/readhub/templates/description.tsx new file mode 100644 index 000000000..3c716a045 --- /dev/null +++ b/lib/routes/readhub/templates/description.tsx @@ -0,0 +1,70 @@ +import dayjs from 'dayjs'; +import { renderToString } from 'hono/jsx/dom/server'; + +type NewsItem = { + url?: string; + title?: string; + siteNameDisplay?: string; +}; + +type TimelineTopic = { + publishDate?: string; + uid?: string; + title?: string; +}; + +type TimelineData = { + topics?: TimelineTopic[]; +}; + +type DescriptionData = { + description?: string; + news?: NewsItem[]; + timeline?: TimelineData; + rootUrl: string; +}; + +export const renderDescription = ({ description, news, timeline, rootUrl }: DescriptionData) => + renderToString( + <> + {description ?

    {description}

    : null} + {news ? ( + <> +

    媒体报道

    + + + {news.map((item, index) => ( + + + + + ))} + +
    + {item.title} + + {item.siteNameDisplay} +
    + + ) : null} + {timeline ? ( + <> +

    事件追踪

    + + + {timeline.topics?.map((topic, index) => ( + + + + + ))} + +
    + {topic.publishDate ? dayjs(topic.publishDate).format('YYYY-MM-DD HH:mm:ss') : ''} + + {topic.title} +
    + + ) : null} + + ); diff --git a/lib/routes/readhub/util.ts b/lib/routes/readhub/util.ts index 08e6c9f62..2bb28b445 100644 --- a/lib/routes/readhub/util.ts +++ b/lib/routes/readhub/util.ts @@ -1,26 +1,13 @@ -import path from 'node:path'; - -import dayjs from 'dayjs'; - import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const domain = 'readhub.cn'; const rootUrl = `https://${domain}`; const apiRootUrl = `https://api.${domain}`; const apiTopicUrl = new URL('topic/list', apiRootUrl).href; -const formatDate = (date, format) => dayjs(date).format(format); -const toTopicUrl = (id) => new URL(`topic/${id}`, rootUrl).href; - -art.defaults.imports = { - ...art.defaults.imports, - - formatDate, - toTopicUrl, -}; - /** * Process items asynchronously. * @@ -43,10 +30,11 @@ const processItems = async (items, tryGet) => item.title = data.title; item.link = data.url ?? new URL(`topic/${data.uid}`, rootUrl).href; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ description: data.summary, news: data.newsAggList, timeline: data.timeline, + rootUrl, }); item.author = data.siteNameDisplay; item.category = [...(data.entityList.map((c) => c.name) ?? []), ...(data.tagList.map((c) => c.name) ?? [])]; @@ -62,5 +50,3 @@ const processItems = async (items, tryGet) => ); export { apiRootUrl, apiTopicUrl, processItems, rootUrl }; - -export { art } from '@/utils/render'; diff --git a/lib/routes/reuters/common.ts b/lib/routes/reuters/common.tsx similarity index 73% rename from lib/routes/reuters/common.ts rename to lib/routes/reuters/common.tsx index b7489c707..d114c0855 100644 --- a/lib/routes/reuters/common.ts +++ b/lib/routes/reuters/common.tsx @@ -1,13 +1,115 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +type ReutersContent = { + result: { + summary?: Array<{ description?: string }>; + related_content?: { + galleries?: Array<{ + content_elements?: Array<{ + type?: string; + renditions?: { original?: Record }; + alt_text?: string; + caption?: string; + thumbnail?: { url?: string }; + source?: { mp4?: string }; + description?: string; + }>; + }>; + images?: Array<{ + type?: string; + renditions?: { original?: Record }; + alt_text?: string; + caption?: string; + }>; + }; + content_elements?: Array<{ + type?: string; + content?: string; + level?: number; + }>; + sign_off?: string; + title?: string; + display_time?: string; + authors?: Array<{ name: string }>; + taxonomy?: { keywords?: string[] }; + }; +}; + +const renderDescription = ({ result }: ReutersContent): string => { + const contentElements = result.content_elements ?? []; + + const description = ( + <> + {result.summary ? ( + <> + Summary: +
      + {result.summary.map((summary, index) => ( +
    • {summary.description}
    • + ))} +
    + + ) : null} + {result.related_content ? ( + <> + {result.related_content.galleries?.flatMap((gallery, galleryIndex) => + (gallery.content_elements ?? []).map((element, elementIndex) => + element.type === 'image' ? ( +
    + {element.alt_text} +
    {element.caption}
    +
    + ) : null + ) + )} + {result.related_content.images?.map((image, index) => + image.type === 'image' ? ( +
    + {image.alt_text} +
    {image.caption}
    +
    + ) : null + )} + {result.related_content.galleries?.flatMap((gallery, galleryIndex) => + (gallery.content_elements ?? []).map((element, elementIndex) => + element.type === 'video' ? ( +
    + + {element.description} +
    + ) : null + ) + )} + + ) : null} + {contentElements.map((element, index) => { + if (element.type === 'paragraph') { + return

    {element.content ? raw(element.content) : null}

    ; + } + + if (element.type === 'header') { + const HeaderTag = `h${element.level ?? 1}` as keyof JSX.IntrinsicElements; + return {element.content ? raw(element.content) : null}; + } + + return null; + })} + {result.sign_off ? {result.sign_off} : null} + + ); + + return renderToString(description); +}; export const route: Route = { path: '/:category/:topic?', @@ -191,7 +293,7 @@ async function handler(ctx) { const data = JSON.parse(matches[1]); item.title = data.result.title || item.title; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ result: data.result, }); item.pubDate = parseDate(data.result.display_time); diff --git a/lib/routes/reuters/templates/description.art b/lib/routes/reuters/templates/description.art deleted file mode 100644 index da5686643..000000000 --- a/lib/routes/reuters/templates/description.art +++ /dev/null @@ -1,44 +0,0 @@ -{{ if result.summary }} -Summary: -
      {{ each result.summary s }} -
    • {{ s.description }}
    • -{{ /each }}
    -{{ /if }} - -{{ if result.related_content }} -{{ each result.related_content.galleries g }} - {{ each g.content_elements e }} - {{ if e.type === 'image' }} -
    {{ e.alt_text }} -
    {{ e.caption }}
    -
    - {{ /if }} - {{ /each }} -{{ /each }} -{{ each result.related_content.images i }} - {{ if i.type === 'image' }} -
    {{ i.alt_text }} -
    {{ i.caption }}
    -
    - {{ /if }} -{{ /each }} -{{ each result.related_content.galleries v }} - {{ each v.content_elements c }} - {{ if c.type === 'video' }} - - {{ c.description }} - {{ /if }} - {{ /each }} -{{ /each }} -{{ /if }} - -{{ if result.content_elements }} -{{ each result.content_elements e }} - {{ if e.type === 'paragraph' }}

    {{@ e.content }}

    {{ /if }} - {{ if e.type === 'header' }}{{@ e.content }}{{ /if }} -{{ /each }} -{{ /if }} - -{{ if result.sign_off }}{{ result.sign_off }}{{ /if }} diff --git a/lib/routes/rockthejvm/articles.ts b/lib/routes/rockthejvm/articles.ts index 9dd905168..e1844ddab 100644 --- a/lib/routes/rockthejvm/articles.ts +++ b/lib/routes/rockthejvm/articles.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -34,7 +33,7 @@ export const handler = async (ctx: Context): Promise => { const $aEl: Cheerio = $el.find('h2 a'); const title: string = $aEl.text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ intro: $el.find('p.text-content').first().text(), }); const pubDateStr: string | undefined = $el.find('time').attr('datetime'); @@ -84,8 +83,8 @@ export const handler = async (ctx: Context): Promise => { const title: string = $$('meta[property="og:title"]').attr('content') ?? item.title; const description: string | undefined = item.description + - art(path.join(__dirname, 'templates/description.art'), { - description: $$('div.prose').html(), + renderDescription({ + description: $$('div.prose').html() ?? undefined, }); const pubDateStr: string | undefined = $$('meta[property="article:published_time"]').attr('content'); const categoryEls: Element[] = $$('meta[property="article:tag"]').toArray(); diff --git a/lib/routes/rockthejvm/templates/description.art b/lib/routes/rockthejvm/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/rockthejvm/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/rockthejvm/templates/description.tsx b/lib/routes/rockthejvm/templates/description.tsx new file mode 100644 index 000000000..ca6a8f6d2 --- /dev/null +++ b/lib/routes/rockthejvm/templates/description.tsx @@ -0,0 +1,15 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionRenderOptions = { + intro?: string; + description?: string; +}; + +export const renderDescription = ({ intro, description }: DescriptionRenderOptions): string => + renderToString( + <> + {intro ?
    {intro}
    : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/routledge/book-series.ts b/lib/routes/routledge/book-series.tsx similarity index 84% rename from lib/routes/routledge/book-series.ts rename to lib/routes/routledge/book-series.tsx index 248e85d2c..2385837a7 100644 --- a/lib/routes/routledge/book-series.ts +++ b/lib/routes/routledge/book-series.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:bookName/book-series/:bookId', @@ -76,10 +75,7 @@ async function handler(ctx) { description.find('button.accordion-button').contents().unwrap(); description.find('.fa-shopping-cart').parent().parent().remove(); - item.description = art(path.join(__dirname, 'templates/description.art'), { - image, - description: description.html(), - }); + item.description = renderDescription(image, description.html()); return item; }) ) @@ -92,3 +88,16 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (image: string | undefined, description: string | null): string => + renderToString( + <> + {image ? ( + <> + +
    + + ) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/routledge/templates/description.art b/lib/routes/routledge/templates/description.art deleted file mode 100644 index d7f9b4d48..000000000 --- a/lib/routes/routledge/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if image }} -
    -{{ /if }} - -{{ if description }} -{{@ description }} -{{ /if }} diff --git a/lib/routes/rsc/journal.ts b/lib/routes/rsc/journal.tsx similarity index 94% rename from lib/routes/rsc/journal.ts rename to lib/routes/rsc/journal.tsx index 47ae3828d..0360471b4 100644 --- a/lib/routes/rsc/journal.ts +++ b/lib/routes/rsc/journal.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -54,10 +52,15 @@ async function handler(ctx) { let $ = load(response); $('div.capsule__article-image').each(function () { + const image = $(this).find('img').prop('data-original'); $(this).replaceWith( - art(path.join(__dirname, 'templates/image.art'), { - image: $(this).find('img').prop('data-original'), - }) + renderToString( + image ? ( +
    + +
    + ) : null + ) ); }); diff --git a/lib/routes/rsc/templates/image.art b/lib/routes/rsc/templates/image.art deleted file mode 100644 index 779c2f675..000000000 --- a/lib/routes/rsc/templates/image.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if image }} -
    - -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/saraba1st/digest.ts b/lib/routes/saraba1st/digest.tsx similarity index 71% rename from lib/routes/saraba1st/digest.ts rename to lib/routes/saraba1st/digest.tsx index 48c849afa..f208e21e3 100644 --- a/lib/routes/saraba1st/digest.ts +++ b/lib/routes/saraba1st/digest.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -87,15 +86,17 @@ async function fetchContent(url) { .find('div[id*="post_"] ') .each(function () { if (subind(this).find('td[id*="postmessage_"]').length > 0) { - const section = art(path.join(__dirname, 'templates/digest.art'), { - author: { - link: subind(this).find('.pls.favatar div.authi a').attr('href'), - name: subind(this).find('.pls.favatar div.authi').text(), - postinfo: subind(this).find('div.authi em[id*=authorposton]').text(), - }, - msg: subind(this).find('td[id*="postmessage_"]').html(), - host: config.saraba1st.host, - }); + const section = renderToString( + + ); stubS.append(section); } }); @@ -114,3 +115,17 @@ async function fetchContent(url) { return stubS.html(); } + +const DigestSection = ({ author, msg, host }: { author: { link?: string; name: string; postinfo: string }; msg?: string; host: string }) => ( + <> +
    + + {author.name} + + {author.postinfo} +
    +
    + {msg ? raw(msg) : null} +
    + +); diff --git a/lib/routes/saraba1st/templates/digest.art b/lib/routes/saraba1st/templates/digest.art deleted file mode 100644 index ef7f64954..000000000 --- a/lib/routes/saraba1st/templates/digest.art +++ /dev/null @@ -1,5 +0,0 @@ -
    - {{ author.name }} - {{ author.postinfo }} -
    -
    {{@ msg }}
    diff --git a/lib/routes/science/cover.ts b/lib/routes/science/cover.tsx similarity index 88% rename from lib/routes/science/cover.ts rename to lib/routes/science/cover.tsx index f8970f06c..107b7e897 100644 --- a/lib/routes/science/cover.ts +++ b/lib/routes/science/cover.tsx @@ -1,6 +1,6 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; // journals form AAAS publishing group @@ -13,7 +13,6 @@ import type { Route } from '@/types'; // stm: Science Translational Medicine import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { baseUrl } from './utils'; @@ -79,10 +78,7 @@ async function handler() { return { title: `${name} | Volume ${volume} Issue ${issue}`, - description: art(path.join(__dirname, 'templates/cover.art'), { - coverUrl, - content, - }), + description: renderDescription(coverUrl, content), link: `${baseUrl}/${link}/${volume}/${issue}`, pubDate: parseDate(date), }; @@ -97,3 +93,11 @@ async function handler() { item: items, }; } + +const renderDescription = (coverUrl: string, content: string | null): string => + renderToString( + <> + + {content ? raw(content) : null} + + ); diff --git a/lib/routes/science/templates/article.art b/lib/routes/science/templates/article.art deleted file mode 100644 index 5ae800457..000000000 --- a/lib/routes/science/templates/article.art +++ /dev/null @@ -1,2 +0,0 @@ -{{ if abs }}{{@ abs }}{{ /if }} -{{ if content }}
    {{@ content }}{{ /if }} diff --git a/lib/routes/science/templates/cover.art b/lib/routes/science/templates/cover.art deleted file mode 100644 index 80f4e05e9..000000000 --- a/lib/routes/science/templates/cover.art +++ /dev/null @@ -1,2 +0,0 @@ - -{{@ content }} diff --git a/lib/routes/science/utils.ts b/lib/routes/science/utils.tsx similarity index 81% rename from lib/routes/science/utils.ts rename to lib/routes/science/utils.tsx index e616d1fb9..7489961e8 100644 --- a/lib/routes/science/utils.ts +++ b/lib/routes/science/utils.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://www.science.org'; @@ -34,10 +33,7 @@ const fetchDesc = (list, browser, tryGet) => ? '' : $('section#bodymatter').html(); - item.description = art(path.join(__dirname, 'templates/article.art'), { - abs: abstract, - content, - }); + item.description = renderDescription(abstract, content); return item; }) @@ -60,3 +56,16 @@ const getItem = (item, $) => { }; export { baseUrl, fetchDesc, getItem }; + +const renderDescription = (abs: string | null, content: string | null): string => + renderToString( + <> + {abs ? raw(abs) : null} + {content ? ( + <> +
    + {raw(content)} + + ) : null} + + ); diff --git a/lib/routes/sciencedirect/call-for-paper.ts b/lib/routes/sciencedirect/call-for-paper.tsx similarity index 81% rename from lib/routes/sciencedirect/call-for-paper.ts rename to lib/routes/sciencedirect/call-for-paper.tsx index c3954a869..d43200ef0 100644 --- a/lib/routes/sciencedirect/call-for-paper.ts +++ b/lib/routes/sciencedirect/call-for-paper.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; export const route: Route = { path: '/call-for-paper/:subject', @@ -54,13 +52,19 @@ async function handler(ctx) { const items = cfpList.map((cfp) => { const link = `https://www.sciencedirect.com/special-issue/${cfp.contentId}/${cfp.url}`; - const description = art(path.join(__dirname, 'templates/description.art'), { - summary: cfp.summary, - submissionDeadline: cfp.submissionDeadline, - displayName: cfp.journal.displayName, - impactFactor: cfp.journal.impactFactor, - citeScore: cfp.journal.citeScore, - }); + const description = renderToString( +
    +

    + Summary: {cfp.summary} +

    +

    + Submission Deadline: {cfp.submissionDeadline} +

    +

    + Journal: {`${cfp.journal.displayName} (IF: ${cfp.journal.impactFactor}, CiteScore: ${cfp.journal.citeScore})`} +

    +
    + ); return { title: cfp.title, diff --git a/lib/routes/sciencedirect/templates/description.art b/lib/routes/sciencedirect/templates/description.art deleted file mode 100644 index d1ecbf0ab..000000000 --- a/lib/routes/sciencedirect/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -
    -

    Summary: {{summary}}

    -

    Submission Deadline: {{submissionDeadline}}

    -

    Journal: {{displayName}} (IF: {{impactFactor}}, CiteScore: {{citeScore}})

    -
    diff --git a/lib/routes/scientificamerican/podcast.ts b/lib/routes/scientificamerican/podcast.ts index 07e98a824..38a931eb0 100644 --- a/lib/routes/scientificamerican/podcast.ts +++ b/lib/routes/scientificamerican/podcast.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; @@ -9,9 +7,10 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + export const handler = async (ctx: Context): Promise => { const { id } = ctx.req.param(); const limit: number = Number.parseInt(ctx.req.query('limit') ?? '12', 10); @@ -31,7 +30,7 @@ export const handler = async (ctx: Context): Promise => { ? parsedData.initialData.props.results.slice(0, limit).map((item): DataItem => { const title: string = item.title; const image: string | undefined = item.image_url; - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: image ? [ { @@ -114,7 +113,7 @@ export const handler = async (ctx: Context): Promise => { const title: string = articleData.title; const image: string | undefined = articleData.image_url; - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ images: image ? [ { diff --git a/lib/routes/scientificamerican/templates/description.art b/lib/routes/scientificamerican/templates/description.art deleted file mode 100644 index ec6912ddf..000000000 --- a/lib/routes/scientificamerican/templates/description.art +++ /dev/null @@ -1,31 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} - {{@ intro }} -{{ /if }} - -{{ if content }} - {{ each content c }} - <{{ c.tag }}> - {{@ c.content }} - - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/scientificamerican/templates/description.tsx b/lib/routes/scientificamerican/templates/description.tsx new file mode 100644 index 000000000..bc1599bf3 --- /dev/null +++ b/lib/routes/scientificamerican/templates/description.tsx @@ -0,0 +1,43 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; + width?: string | number; + height?: string | number; +}; + +type ContentBlock = { + tag: string; + content: string; +}; + +type DescriptionData = { + images?: DescriptionImage[]; + intro?: string; + content?: ContentBlock[]; +}; + +const ScientificAmericanDescription = ({ images, intro, content }: DescriptionData) => ( + <> + {images?.map((image) => { + if (!image?.src) { + return null; + } + const altValue = image.height ?? image.width ?? image.alt; + return ( +
    + {altValue +
    + ); + })} + {intro ? raw(intro) : null} + {content?.map((block) => { + const Tag = block.tag as keyof JSX.IntrinsicElements; + return {raw(block.content)}; + })} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/scitechvista/index.ts b/lib/routes/scitechvista/index.tsx similarity index 86% rename from lib/routes/scitechvista/index.ts rename to lib/routes/scitechvista/index.tsx index bbcfbba7b..c4805ad81 100644 --- a/lib/routes/scitechvista/index.ts +++ b/lib/routes/scitechvista/index.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { namespace } from './namespace'; @@ -77,10 +76,16 @@ async function handler(): Promise { const snippet = node.find('div.kf-txt').first().text().trim() || undefined; - const description = art(path.join(__dirname, 'templates/description.art'), { - image, - description: snippet, - }); + const description = renderToString( + <> + {image ? ( +

    + +

    + ) : null} + {snippet ?

    {raw(snippet)}

    : null} + + ); return { title, diff --git a/lib/routes/scitechvista/templates/description.art b/lib/routes/scitechvista/templates/description.art deleted file mode 100644 index 4df64baba..000000000 --- a/lib/routes/scitechvista/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{if image}} -

    -{{/if}} -{{if description}} -

    {{@ description }}

    -{{/if}} diff --git a/lib/routes/scoop/apps.ts b/lib/routes/scoop/apps.tsx similarity index 72% rename from lib/routes/scoop/apps.ts rename to lib/routes/scoop/apps.tsx index 8d547483c..ad5548053 100644 --- a/lib/routes/scoop/apps.ts +++ b/lib/routes/scoop/apps.tsx @@ -1,14 +1,12 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const orderbys = (desc: string) => { const base = { @@ -100,9 +98,7 @@ export const handler = async (ctx: Context): Promise => { const repositorySplits: string[] = item.Metadata.Repository.split(/\//); const repositoryName: string = repositorySplits.slice(-2).join('/'); const title: string = `${item.Name} ${item.Version} in ${repositoryName}`; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - item, - }); + const description: string | undefined = renderToString(); const pubDate: number | string = item.Metadata.Committed; const linkUrl: string | undefined = item.Homepage; const authors: DataItem['author'] = [ @@ -148,6 +144,73 @@ export const handler = async (ctx: Context): Promise => { }; }; +const ScoopDescription = ({ item }: { item: any }) => { + const repositoryName = item.Metadata.Repository.split(/\//).slice(-2).join('/'); + + return ( + + + {item.Name ? ( + + + + + ) : null} + {item.Repository ? ( + + + + + ) : null} + {item.Committed ? ( + + + + + ) : null} + {item.Version ? ( + + + + + ) : null} + {item.Description ? ( + + + + + ) : null} + {item.Homepage ? ( + + + + + ) : null} + {item.License ? ( + + + + + ) : null} + {item.Note ? ( + + + + + ) : null} + +
    Name{item.Name}
    Repository + {repositoryName} +
    Committed + {item.Metadata.Committed} +
    Version + v{item.Version} +
    Description{item.Description}
    Homepage + {item.Homepage} +
    License{item.License}
    Note{item.Note}
    + ); +}; + export const route: Route = { path: '/apps/:query?', name: 'Apps', diff --git a/lib/routes/scoop/templates/description.art b/lib/routes/scoop/templates/description.art deleted file mode 100644 index f0deeaa62..000000000 --- a/lib/routes/scoop/templates/description.art +++ /dev/null @@ -1,56 +0,0 @@ -{{ if item }} - - - {{ if item.Name }} - - - - - {{ /if }} - {{ if item.Repository }} - - - - - {{ /if }} - {{ if item.Committed }} - - - - - {{ /if }} - {{ if item.Version }} - - - - - {{ /if }} - {{ if item.Description }} - - - - - {{ /if }} - {{ if item.Homepage }} - - - - - {{ /if }} - {{ if item.License }} - - - - - {{ /if }} - {{ if item.Note }} - - - - - {{ /if }} - -
    Name{{ item.Name }}
    Repository - {{ item.Metadata.Repository.split(/\//).slice(-2).join('/') }} -
    Committed{{ item.Metadata.Committed }}
    Versionv{{ item.Version }}
    Description{{ item.Description }}
    Homepage{{ item.Homepage }}
    License{{ item.License }}
    Note{{ item.Note }}
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/sctv/programme.ts b/lib/routes/sctv/programme.tsx similarity index 94% rename from lib/routes/sctv/programme.ts rename to lib/routes/sctv/programme.tsx index 3d62e9771..379d57e74 100644 --- a/lib/routes/sctv/programme.ts +++ b/lib/routes/sctv/programme.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -114,10 +113,11 @@ async function handler(ctx) { title: item.programmeTitle, link: item.programmeUrl, pubDate: timezone(parseDate(item.pubTime), +8), - description: art(path.join(__dirname, 'templates/description.art'), { - cover: item.programmeImage, - video: item.programmeUrl, - }), + description: renderToString( + + ), })); let currentFullItems = []; diff --git a/lib/routes/sctv/templates/description.art b/lib/routes/sctv/templates/description.art deleted file mode 100644 index d802a1ac6..000000000 --- a/lib/routes/sctv/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/lib/routes/sdo/ff14risingstones/templates/duties-party.art b/lib/routes/sdo/ff14risingstones/templates/duties-party.art deleted file mode 100644 index ccee1b353..000000000 --- a/lib/routes/sdo/ff14risingstones/templates/duties-party.art +++ /dev/null @@ -1,41 +0,0 @@ -
    -

    进度:{{ progress }}

    -

    攻略:{{ strategy }}

    -

    时间:{{ fb_time }}

    -

    标签:{{ labelInfo.map(i => i.name).join(',') }}

    - {{ if team_composition === '团队' }} -

    队伍构成

    -

    A队:{{ team_position.A }}

    -

    B队:{{ team_position.B }}

    -

    C队:{{ team_position.C }}

    - {{ else if team_composition === '满编小队' }} -

    队伍构成

    -

    MT:{{ MT }}

    -

    ST:{{ ST }}

    -

    H1:{{ H1 }}

    -

    H2:{{ H2 }}

    -

    D1:{{ D1 }}

    -

    D2:{{ D2 }}

    -

    D3:{{ D3 }}

    -

    D4:{{ D4 }}

    - {{ else if team_composition === '轻锐小队' }} -

    队伍构成

    -

    T:{{ T }}

    -

    H:{{ H }}

    -

    D1:{{ D1 }}

    -

    D2:{{ D2 }}

    - {{ /if }} - - {{ if need_job }} -

    需求职业:{{ need_job }}

    - {{ /if }} - {{ if team_detail_mask }} -

    队伍详情:{{ team_detail_mask }}

    - {{ /if }} - {{ if recruit_require_mask }} -

    招募要求:{{ recruit_require_mask }}

    - {{ /if }} - {{ if strategy_desc_mask }} -

    攻略说明:{{ strategy_desc_mask }}

    - {{ /if }} -
    diff --git a/lib/routes/sdo/ff14risingstones/templates/fc-party.art b/lib/routes/sdo/ff14risingstones/templates/fc-party.art deleted file mode 100644 index 812675629..000000000 --- a/lib/routes/sdo/ff14risingstones/templates/fc-party.art +++ /dev/null @@ -1,26 +0,0 @@ -{{ if cover_pic }} - -{{ /if }} - -

    部队名称:{{ guild_name }} <{{ guild_tag }}>

    -

    区服:{{ area_name }} {{ group_name }}

    -

    活跃成员:{{ active_member_num }}

    -

    招募人数:{{ target_recruit_num }}

    -

    活跃时间:工作日 {{ weekday_time }}    休息日 {{ weekend_time }}

    -{{ if guild_address }} -

    部队地址:{{ guild_address }}

    -{{ /if }} -{{ if create_time }} -

    成立时间:{{ create_time }}

    -{{ /if }} -

    部队标签:{{ labelInfo.map(i => i.name).join(',') }}

    - -
    -{{@ detail_mask }} -
    - -{{ if foot_pic }} -{{ each foot_pic.split(',') url }} - -{{ /each }} -{{ /if }} diff --git a/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art b/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art deleted file mode 100644 index b0287600c..000000000 --- a/lib/routes/sdo/ff14risingstones/templates/novice-network-party.art +++ /dev/null @@ -1,9 +0,0 @@ -{{@ detail_mask }} - -
    - {{ if weekday_time && weekend_time }} -

    活跃时间:工作日 {{ weekday_time }}    休息日 {{ weekend_time }}

    - {{ /if }} -

    游戏风格:{{ styles }}

    -

    招募区服:{{ target }}

    -
    diff --git a/lib/routes/sdo/ff14risingstones/templates/rp-party.art b/lib/routes/sdo/ff14risingstones/templates/rp-party.art deleted file mode 100644 index 2a450a941..000000000 --- a/lib/routes/sdo/ff14risingstones/templates/rp-party.art +++ /dev/null @@ -1,15 +0,0 @@ -{{ if cover_pic }} - -{{ /if }} - -

    开放时间:{{ open_time }}

    -

    RP 类型:{{ rp_type }}

    -

    创立时间:{{ create_time }}

    -

    区服:{{ area }}

    -

    地址:{{ address }}

    -

    标签:{{ custom_label }}

    -

    简介:{{ profile }}

    - -
    -{{@ detail_mask }} -
    diff --git a/lib/routes/sdo/ff14risingstones/utils.ts b/lib/routes/sdo/ff14risingstones/utils.tsx similarity index 70% rename from lib/routes/sdo/ff14risingstones/utils.ts rename to lib/routes/sdo/ff14risingstones/utils.tsx index dc8b3b160..0d937660a 100644 --- a/lib/routes/sdo/ff14risingstones/utils.ts +++ b/lib/routes/sdo/ff14risingstones/utils.tsx @@ -1,17 +1,115 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { DataItem } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import { getDutiesRecruitDetail, getFreeCompanyRecruitDetail, getNoviceNetworkRecruitDetail, getPostsDetail } from './api'; import { DynamicSource, INDEX_URL, JOB, NoviceNetworkIdentity, PLAY_STYLE } from './constant'; import type { BaseResponse, DutiesPartyDetail, FreeCompanyPartyDetail, NoviceNetworkParty, PostDetail, UserDynamic, UserPost } from './types'; +const renderNoviceNetworkParty = ({ detail_mask, weekday_time, weekend_time, styles, target }) => + renderToString( + <> + {detail_mask ? raw(detail_mask) : null} +
    + {weekday_time && weekend_time ? ( +

    + 活跃时间:工作日 {weekday_time}    休息日 {weekend_time} +

    + ) : null} +

    游戏风格:{styles}

    +

    招募区服:{target}

    +
    + + ); + +const renderDutiesParty = ({ progress, strategy, fb_time, labelInfo, team_composition, team_position, MT, ST, T, H, H1, H2, D1, D2, D3, D4, need_job, team_detail_mask, recruit_require_mask, strategy_desc_mask }) => + renderToString( +
    +

    进度:{progress}

    +

    攻略:{strategy}

    +

    时间:{fb_time}

    +

    标签:{labelInfo.map((i) => i.name).join(',')}

    + {team_composition === '团队' ? ( + <> +

    队伍构成

    +

    A队:{team_position.A}

    +

    B队:{team_position.B}

    +

    C队:{team_position.C}

    + + ) : null} + {team_composition === '满编小队' ? ( + <> +

    队伍构成

    +

    MT:{MT}

    +

    ST:{ST}

    +

    H1:{H1}

    +

    H2:{H2}

    +

    D1:{D1}

    +

    D2:{D2}

    +

    D3:{D3}

    +

    D4:{D4}

    + + ) : null} + {team_composition === '轻锐小队' ? ( + <> +

    队伍构成

    +

    T:{T}

    +

    H:{H}

    +

    D1:{D1}

    +

    D2:{D2}

    + + ) : null} + {need_job ?

    需求职业:{need_job}

    : null} + {team_detail_mask ?

    队伍详情:{team_detail_mask}

    : null} + {recruit_require_mask ?

    招募要求:{recruit_require_mask}

    : null} + {strategy_desc_mask ?

    攻略说明:{strategy_desc_mask}

    : null} +
    + ); + +const renderFreeCompanyParty = ({ cover_pic, guild_name, guild_tag, area_name, group_name, active_member_num, target_recruit_num, weekday_time, weekend_time, guild_address, create_time, labelInfo, detail_mask, foot_pic }) => + renderToString( + <> + {cover_pic ? : null} +

    + 部队名称:{guild_name} <{guild_tag}> +

    +

    + 区服:{area_name} {group_name} +

    +

    活跃成员:{active_member_num}

    +

    招募人数:{target_recruit_num}

    +

    + 活跃时间:工作日 {weekday_time}    休息日 {weekend_time} +

    + {guild_address ?

    部队地址:{guild_address}

    : null} + {create_time ?

    成立时间:{create_time}

    : null} +

    部队标签:{labelInfo.map((i) => i.name).join(',')}

    +
    {detail_mask ? raw(detail_mask) : null}
    + {foot_pic ? foot_pic.split(',').map((url) => ) : null} + + ); + +const renderRolePlayParty = ({ cover_pic, open_time, rp_type, create_time, area, address, custom_label, profile, detail_mask }) => + renderToString( + <> + {cover_pic ? : null} +

    开放时间:{open_time}

    +

    RP 类型:{rp_type}

    +

    创立时间:{create_time}

    +

    区服:{area}

    +

    地址:{address}

    +

    标签:{custom_label}

    +

    简介:{profile}

    +
    {detail_mask ? raw(detail_mask) : null}
    + + ); + export function checkConfig() { if (!config.sdo.ff14risingstones || !config.sdo.ua) { throw new ConfigNotFoundError('ff14risingstones RSS is disabled due to the lack of relevant config'); @@ -82,7 +180,7 @@ export async function generateDynamicFeeds(dynamics: UserDynamic[]) { title += `[找${dynamic.from_info.identity === NoviceNetworkIdentity.Mentor ? '豆芽' : '导师'}] ${dynamic.from_info.title}`; link = `${INDEX_URL}#/recruit/beginner?id=${dynamic.from_info.id}`; detail = await getNoviceNetworkRecruitDetail(dynamic.from_info.id); - description = art(path.join(__dirname, 'templates/novice-network-party.art'), { + description = renderNoviceNetworkParty({ detail_mask: dynamic.from_info.detail_mask, styles: dynamic.from_info.style.map((i) => PLAY_STYLE[i]).join(','), target: `${dynamic.from_info.target_area_name} ${dynamic.from_info.target_group_name ?? '全区'}`, @@ -99,10 +197,9 @@ export async function generateDynamicFeeds(dynamics: UserDynamic[]) { link = `${INDEX_URL}#/recruit/party?id=${dynamic.from_info.id}`; detail = await getDutiesRecruitDetail(dynamic.from_info.id); - description = art(path.join(__dirname, 'templates/duties-party.art'), { + description = renderDutiesParty({ progress: dynamic.from_info.progress, strategy: dynamic.from_info.strategy, - fb_name: dynamic.from_info.fb_name, fb_time: dynamic.from_info.fb_time, labelInfo: dynamic.from_info.labelInfo, team_composition: dynamic.from_info.team_composition, @@ -147,7 +244,7 @@ export async function generateDynamicFeeds(dynamics: UserDynamic[]) { link = `${INDEX_URL}#/recruit/guild/detail/${dynamic.from_info.id}`; detail = await getFreeCompanyRecruitDetail(dynamic.from_info.id); - description = art(path.join(__dirname, 'templates/fc-party.art'), { + description = renderFreeCompanyParty({ cover_pic: dynamic.from_info.cover_pic, guild_name: dynamic.from_info.guild_name, guild_tag: dynamic.from_info.guild_tag, @@ -171,7 +268,7 @@ export async function generateDynamicFeeds(dynamics: UserDynamic[]) { } title += dynamic.from_info.rp_name; link = `${INDEX_URL}#/recruit/roleplay/detail/${dynamic.from_info.id}`; - description = art(path.join(__dirname, 'templates/rp-party.art'), { + description = renderRolePlayParty({ cover_pic: dynamic.from_info.cover_pic, open_time: dynamic.from_info.open_time, rp_type: `${dynamic.from_info.rp_type diff --git a/lib/routes/secretsanfrancisco/rss.ts b/lib/routes/secretsanfrancisco/rss.tsx similarity index 83% rename from lib/routes/secretsanfrancisco/rss.ts rename to lib/routes/secretsanfrancisco/rss.tsx index d50f56fc5..20d069784 100644 --- a/lib/routes/secretsanfrancisco/rss.ts +++ b/lib/routes/secretsanfrancisco/rss.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/:category?', @@ -80,12 +79,17 @@ async function handler(ctx) { const single = { title: item.title.rendered, - description: art(path.join(__dirname, 'templates/description.art'), { - content: item.content.rendered, - image, - altText, - caption: caption?.text() || '', - }), + description: renderToString( + <> + {image ? ( +
    + {altText ? {altText} : } +
    {caption?.text() || ''}
    +
    + ) : null} + {item.content?.rendered ? raw(item.content.rendered) : null} + + ), link: item.link, pubDate: parseDate(item.date_gmt), updated: parseDate(item.modified_gmt), diff --git a/lib/routes/secretsanfrancisco/templates/description.art b/lib/routes/secretsanfrancisco/templates/description.art deleted file mode 100644 index 7b0c2f498..000000000 --- a/lib/routes/secretsanfrancisco/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if image }} -
    - {{ altText }} -
    {{ caption }}
    -
    -{{ /if }} -{{ if content }} -{{@ content }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/seekingalpha/index.ts b/lib/routes/seekingalpha/index.tsx similarity index 84% rename from lib/routes/seekingalpha/index.ts rename to lib/routes/seekingalpha/index.tsx index e99b83d21..1b4f14ec1 100644 --- a/lib/routes/seekingalpha/index.ts +++ b/lib/routes/seekingalpha/index.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const baseUrl = 'https://seekingalpha.com'; @@ -93,12 +92,20 @@ async function handler(ctx) { }); item.category = response.included.filter((i) => i.type === 'tag').map((i) => (i.attributes.company ? `${i.attributes.company} (${i.attributes.name})` : i.attributes.name)); - item.description = - (response.data.attributes.summary?.length - ? art(path.join(__dirname, 'templates/summary.art'), { - summary: response.data.attributes.summary, - }) - : '') + response.data.attributes.content; + const summary = response.data.attributes.summary; + const summaryDescription = summary?.length + ? renderToString( + <> +

    Summary

    +
      + {summary.map((entry) => ( +
    • {entry}
    • + ))} +
    + + ) + : ''; + item.description = summaryDescription + response.data.attributes.content; item.updated = parseDate(response.data.attributes.lastModified); return item; diff --git a/lib/routes/seekingalpha/templates/summary.art b/lib/routes/seekingalpha/templates/summary.art deleted file mode 100644 index dd0f1c408..000000000 --- a/lib/routes/seekingalpha/templates/summary.art +++ /dev/null @@ -1,4 +0,0 @@ -

    Summary

    -
      -{{ each summary s }}
    • {{ s }}
    • {{ /each }} -
    diff --git a/lib/routes/semiconductors/index.ts b/lib/routes/semiconductors/index.ts index 54f0baffd..e442ccb78 100644 --- a/lib/routes/semiconductors/index.ts +++ b/lib/routes/semiconductors/index.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const { category = 'news-events/latest-news' } = ctx.req.param(); @@ -38,7 +37,7 @@ export const handler = async (ctx: Context): Promise => { .find('img') .attr('src') ?.replace(/-\d+x\d+\./, '.'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -94,7 +93,7 @@ export const handler = async (ctx: Context): Promise => { const image: string | undefined = $$('meta[property="og:image"]') .attr('content') ?.replace(/-scaled\./, '.'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ images: image ? [ { @@ -103,7 +102,7 @@ export const handler = async (ctx: Context): Promise => { }, ] : undefined, - description: $$('main#main').html(), + description: $$('main#main').html() || undefined, }); const pubDateStr: string | undefined = $$('meta[property="article:published_time"]').attr('content'); const authorEls: Element[] = $$('meta[name="author"]').toArray(); diff --git a/lib/routes/semiconductors/templates/description.art b/lib/routes/semiconductors/templates/description.art deleted file mode 100644 index bfb1a0ff6..000000000 --- a/lib/routes/semiconductors/templates/description.art +++ /dev/null @@ -1,27 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/semiconductors/templates/description.tsx b/lib/routes/semiconductors/templates/description.tsx new file mode 100644 index 000000000..510068a30 --- /dev/null +++ b/lib/routes/semiconductors/templates/description.tsx @@ -0,0 +1,32 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src: string; + alt?: string; + width?: string | number; + height?: string | number; +}; + +type DescriptionProps = { + images?: Image[]; + intro?: string; + description?: string; +}; + +export const renderDescription = ({ images, intro, description }: DescriptionProps): string => + renderToString( + <> + {images?.length + ? images.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + ) + : null} + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/sensortower/blog.ts b/lib/routes/sensortower/blog.ts index 3029779f6..bf5c6533c 100644 --- a/lib/routes/sensortower/blog.ts +++ b/lib/routes/sensortower/blog.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/blog/:language?', @@ -71,20 +70,16 @@ async function handler(ctx) { content('img').each(function () { const image = (content(this).attr('srcset') ?? content(this).attr('src')).split('?w=')[0]; - content(this).replaceWith( - art(path.join(__dirname, 'templates/description.art'), { - image, - }) - ); + content(this).replaceWith(renderDescription({ image })); }); item.title = detail.title; item.author = detail.author.name; item.pubDate = parseDate(detail.pubDate, 'MMMM YYYY'); item.category = [...(detail.tags?.map((t) => t.title) ?? []), ...(detail.category?.map((c) => c.title) ?? [])]; - item.description = art(path.join(__dirname, 'templates/description.art'), { - header: content('header[data-csk-entry-type="blog"]').html(), - description: content('div[data-csk-entry-type="blog"] div[data-testid="Text-root"]').html(), + item.description = renderDescription({ + header: content('header[data-csk-entry-type="blog"]').html() ?? undefined, + description: content('div[data-csk-entry-type="blog"] div[data-testid="Text-root"]').html() ?? undefined, }); return item; diff --git a/lib/routes/sensortower/templates/description.art b/lib/routes/sensortower/templates/description.art deleted file mode 100644 index 112e33db7..000000000 --- a/lib/routes/sensortower/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if image }} - -{{ /if }} -{{ if header }} -{{@ header }} -{{ /if }} -{{ if description }} -{{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/sensortower/templates/description.tsx b/lib/routes/sensortower/templates/description.tsx new file mode 100644 index 000000000..0ae927e29 --- /dev/null +++ b/lib/routes/sensortower/templates/description.tsx @@ -0,0 +1,17 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionRenderOptions = { + image?: string; + header?: string; + description?: string; +}; + +export const renderDescription = ({ image, header, description }: DescriptionRenderOptions): string => + renderToString( + <> + {image ? : null} + {header ? <>{raw(header)} : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/shcstheatre/programs.ts b/lib/routes/shcstheatre/programs.ts deleted file mode 100644 index 6d5d6d966..000000000 --- a/lib/routes/shcstheatre/programs.ts +++ /dev/null @@ -1,67 +0,0 @@ -import path from 'node:path'; - -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 { art } from '@/utils/render'; -import timezone from '@/utils/timezone'; - -export const route: Route = { - path: '/programs', - categories: ['shopping'], - example: '/shcstheatre/programs', - parameters: {}, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['www.shcstheatre.com/Program/programList.aspx'], - }, - ], - name: '节目列表', - maintainers: ['fuzy112'], - handler, - url: 'www.shcstheatre.com/Program/programList.aspx', -}; - -async function handler() { - const url = 'https://www.shcstheatre.com/Program/programList.aspx'; - const res = await got.get(url); - const $ = load(res.data); - const items = await Promise.all( - $('#datarow .program-name a').map((_, elem) => { - const link = new URL($(elem).attr('href'), url); - return cache.tryGet(link.toString(), async () => { - const id = link.searchParams.get('id'); - const res2 = await got.post('https://www.shcstheatre.com/webapi.ashx?op=GettblprogramCache', { - headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, - form: { id }, - }); - const data = res2.data.data.tblprogram[0]; - return { - title: data.SCS_WEB_BRIEFNAME, - link: link.toString(), - description: art(path.join(__dirname, 'templates/description.art'), data), - pubDate: timezone(parseDate(data.SJ_DATE_PC), +8), - }; - }); - }) - ); - const image = $('.menu-logo img').attr('src'); - - return { - title: '上海文化广场 - 节目列表', - link: url, - image, - item: items, - }; -} diff --git a/lib/routes/shcstheatre/programs.tsx b/lib/routes/shcstheatre/programs.tsx new file mode 100644 index 000000000..c3d6eb0a3 --- /dev/null +++ b/lib/routes/shcstheatre/programs.tsx @@ -0,0 +1,159 @@ +import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import cache from '@/utils/cache'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; +import timezone from '@/utils/timezone'; + +export const route: Route = { + path: '/programs', + categories: ['shopping'], + example: '/shcstheatre/programs', + parameters: {}, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['www.shcstheatre.com/Program/programList.aspx'], + }, + ], + name: '节目列表', + maintainers: ['fuzy112'], + handler, + url: 'www.shcstheatre.com/Program/programList.aspx', +}; + +async function handler() { + const url = 'https://www.shcstheatre.com/Program/programList.aspx'; + const res = await got.get(url); + const $ = load(res.data); + const baseUrl = 'https://www.shcstheatre.com'; + const splitImages = (value?: string) => + value + ?.split(';') + .map((item) => item.trim()) + .filter(Boolean) ?? []; + const renderDescription = (data) => { + const { + SCS_PC_YMXQ_PIC, + SCS_WEBPERCYCLE, + SCS_EINLASSNAME, + SCS_PERFORMANCE_TYPENAME, + SCS_LENGTH, + SCS_PERLANGUAGENAME, + SCS_PC_LUNBO_YCJS_EDITOR, + SCS_PC_LUNBO_YCJS_PIC, + SCS_PC_LUNBO_ZCTD_EDITOR, + SCS_PC_LUNBO_ZCTD_PIC, + SCS_PC_LUNBO_JQGG_EDITOR, + SCS_PC_LUNBO_JQGG_PIC, + SCS_PC_LUNBO_HJJL_EDITOR, + SCS_PC_LUNBO_HJJL_PIC, + SCS_PC_LUNBO_MTPL_EDITOR, + SCS_PC_LUNBO_MTPL_PIC, + } = data; + + return renderToString( +
    + {splitImages(SCS_PC_YMXQ_PIC).map((src) => ( + + ))} + +
      +
    • 演出日期:{SCS_WEBPERCYCLE}
    • +
    • 入场时间:{SCS_EINLASSNAME}
    • +
    • 演出类型:{SCS_PERFORMANCE_TYPENAME}
    • +
    • 演出时长:{SCS_LENGTH}分钟
    • +
    • 演出语言:{SCS_PERLANGUAGENAME}
    • +
    + + {SCS_PC_LUNBO_YCJS_EDITOR ? ( + <> +

    演出介绍

    + {splitImages(SCS_PC_LUNBO_YCJS_PIC).map((src) => ( + + ))} +
    {raw(SCS_PC_LUNBO_YCJS_EDITOR)}
    + + ) : null} + + {SCS_PC_LUNBO_ZCTD_EDITOR ? ( + <> +

    主创团队

    + {splitImages(SCS_PC_LUNBO_ZCTD_PIC).map((src) => ( + + ))} +
    {raw(SCS_PC_LUNBO_ZCTD_EDITOR)}
    + + ) : null} + + {SCS_PC_LUNBO_JQGG_EDITOR ? ( + <> +

    剧情梗概

    + {splitImages(SCS_PC_LUNBO_JQGG_PIC).map((src) => ( + + ))} +
    {raw(SCS_PC_LUNBO_JQGG_EDITOR)}
    + + ) : null} + + {SCS_PC_LUNBO_HJJL_EDITOR ? ( + <> +

    获奖记录

    + {splitImages(SCS_PC_LUNBO_HJJL_PIC).map((src) => ( + + ))} +
    {raw(SCS_PC_LUNBO_HJJL_EDITOR)}
    + + ) : null} + + {SCS_PC_LUNBO_MTPL_EDITOR ? ( + <> +

    媒体评论

    + {splitImages(SCS_PC_LUNBO_MTPL_PIC).map((src) => ( + + ))} +
    {raw(SCS_PC_LUNBO_MTPL_EDITOR)}
    + + ) : null} +
    + ); + }; + + const items = await Promise.all( + $('#datarow .program-name a').map((_, elem) => { + const link = new URL($(elem).attr('href'), url); + return cache.tryGet(link.toString(), async () => { + const id = link.searchParams.get('id'); + const res2 = await got.post('https://www.shcstheatre.com/webapi.ashx?op=GettblprogramCache', { + headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, + form: { id }, + }); + const data = res2.data.data.tblprogram[0]; + return { + title: data.SCS_WEB_BRIEFNAME, + link: link.toString(), + description: renderDescription(data), + pubDate: timezone(parseDate(data.SJ_DATE_PC), +8), + }; + }); + }) + ); + const image = $('.menu-logo img').attr('src'); + + return { + title: '上海文化广场 - 节目列表', + link: url, + image, + item: items, + }; +} diff --git a/lib/routes/shcstheatre/templates/description.art b/lib/routes/shcstheatre/templates/description.art deleted file mode 100644 index fc5a21449..000000000 --- a/lib/routes/shcstheatre/templates/description.art +++ /dev/null @@ -1,65 +0,0 @@ -
    - {{ if SCS_PC_YMXQ_PIC }} - {{ each SCS_PC_YMXQ_PIC.split(';').map(s => s.trim() )}} - - {{ /each }} - {{ /if }} - -
      -
    • 演出日期:{{ SCS_WEBPERCYCLE }}
    • -
    • 入场时间:{{ SCS_EINLASSNAME }}
    • -
    • 演出类型:{{ SCS_PERFORMANCE_TYPENAME }}
    • -
    • 演出时长:{{ SCS_LENGTH }}分钟
    • -
    • 演出语言:{{ SCS_PERLANGUAGENAME }}
    • -
    - - {{ if SCS_PC_LUNBO_YCJS_EDITOR }} -

    演出介绍

    - {{ if SCS_PC_LUNBO_YCJS_PIC }} - {{ each SCS_PC_LUNBO_YCJS_PIC.split(';').map(s => s.trim()) }} - - {{ /each }} - {{ /if }} -
    {{@ SCS_PC_LUNBO_YCJS_EDITOR }}
    - {{ /if }} - - {{ if SCS_PC_LUNBO_ZCTD_EDITOR }} -

    主创团队

    - {{ if SCS_PC_LUNBO_ZCTD_PIC }} - {{ each SCS_PC_LUNBO_ZCTD_PIC.split(';').map(s => s.trim()) }} - - {{ /each }} - {{ /if }} -
    {{@ SCS_PC_LUNBO_ZCTD_EDITOR }}
    - {{ /if }} - - {{ if SCS_PC_LUNBO_JQGG_EDITOR }} -

    剧情梗概

    - {{ if SCS_PC_LUNBO_JQGG_PIC }} - {{ each SCS_PC_LUNBO_JQGG_PIC.split(';').map(s => s.trim()) }} - - {{ /each }} - {{ /if }} -
    {{@ SCS_PC_LUNBO_JQGG_EDITOR }}
    - {{ /if }} - - {{ if SCS_PC_LUNBO_HJJL_EDITOR }} -

    获奖记录

    - {{ if SCS_PC_LUNBO_HJJL_PIC }} - {{ each SCS_PC_LUNBO_HJJL_PIC.split(';').map(s => s.trim()) }} - - {{ /each }} - {{ /if }} -
    {{@ SCS_PC_LUNBO_HJJL_EDITOR }}
    - {{ /if }} - - {{ if SCS_PC_LUNBO_MTPL_EDITOR }} -

    媒体评论

    - {{ if SCS_PC_LUNBO_MTPL_PIC }} - {{ each SCS_PC_LUNBO_MTPL_PIC.split(';').map(s => s.trim()) }} - - {{ /each }} - {{ /if }} -
    {{@ SCS_PC_LUNBO_MTPL_EDITOR }}
    - {{ /if }} -
    diff --git a/lib/routes/shiep/index.ts b/lib/routes/shiep/index.tsx similarity index 94% rename from lib/routes/shiep/index.ts rename to lib/routes/shiep/index.tsx index 3c95bf790..2abd509f7 100644 --- a/lib/routes/shiep/index.ts +++ b/lib/routes/shiep/index.tsx @@ -1,14 +1,13 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import dayjs from 'dayjs'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { config } from './config'; import { radar } from './radar'; @@ -99,12 +98,7 @@ async function handler(ctx) { const response = await got(item.link); const $ = load(response.data); - item.description = - $(descriptionSelector).length > 0 - ? art(path.resolve(__dirname, 'templates/description.art'), { - description: $(descriptionSelector).html(), - }) - : '请进行统一身份认证后查看内容'; + item.description = $(descriptionSelector).length > 0 ? renderToString(<>{$(descriptionSelector).html() ? raw($(descriptionSelector).html()) : null}) : '请进行统一身份认证后查看内容'; } catch { item.description = '请在校内或通过校园VPN查看内容'; } diff --git a/lib/routes/shiep/templates/description.art b/lib/routes/shiep/templates/description.art deleted file mode 100644 index d1254e8e7..000000000 --- a/lib/routes/shiep/templates/description.art +++ /dev/null @@ -1 +0,0 @@ -{{@ description }} diff --git a/lib/routes/shmtu/portal.ts b/lib/routes/shmtu/portal.tsx similarity index 62% rename from lib/routes/shmtu/portal.ts rename to lib/routes/shmtu/portal.tsx index d520a7a13..499d64891 100644 --- a/lib/routes/shmtu/portal.ts +++ b/lib/routes/shmtu/portal.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const bootstrapHost = 'https://weixin.shmtu.edu.cn/dynamic/shmtuHttps'; @@ -23,6 +22,38 @@ const loadDetail = async (link) => { return JSON.parse(response.data); }; +const renderDescription = (body, images, files) => + renderToString( + <> + {body ? <>{raw(body)} : null} + {images?.length ? ( + <> + 图片: + {images.map((image) => ( +
    + {image.filename} +
    {image.alt}
    +
    + ))} + + ) : null} + {files?.length ? ( + <> + 附件: + {files.map((file) => ( +

    + + + {file.filename} + + +

    + ))} + + ) : null} + + ); + const processFeed = (list, caches) => Promise.all( list.map((item) => @@ -30,11 +61,11 @@ const processFeed = (list, caches) => const detail = await loadDetail(item.link); const files = detail.field_file; const images = detail.field_image; - item.description = art(path.join(__dirname, 'templates/portal.art'), { - body: detail.body.und[0].safe_value, - images: images.length !== 0 && Object.keys(images).length !== 0 ? images.und : null, - files: files.length !== 0 && Object.keys(files).length !== 0 ? files.und : null, - }); + item.description = renderDescription( + detail.body.und[0].safe_value, + images.length !== 0 && Object.keys(images).length !== 0 ? images.und : null, + files.length !== 0 && Object.keys(files).length !== 0 ? files.und : null + ); item.link = detail.path; return item; }) diff --git a/lib/routes/shmtu/templates/portal.art b/lib/routes/shmtu/templates/portal.art deleted file mode 100644 index b4bfd91fe..000000000 --- a/lib/routes/shmtu/templates/portal.art +++ /dev/null @@ -1,30 +0,0 @@ -{{@ body}} -{{if images}} - - 图片: - - {{each images image}} -
    - {{image.filename}} -
    - {{image.alt}} -
    -
    - {{/each}} -{{/if}} -{{if files}} - - 附件: - - {{each files file}} -

    - - - {{file.filename}} - - -

    - {{/each}} -{{/if}} diff --git a/lib/routes/shoac/recent-show.ts b/lib/routes/shoac/recent-show.tsx similarity index 63% rename from lib/routes/shoac/recent-show.ts rename to lib/routes/shoac/recent-show.tsx index 61d7308c1..b29cfc193 100644 --- a/lib/routes/shoac/recent-show.ts +++ b/lib/routes/shoac/recent-show.tsx @@ -1,10 +1,10 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/recent-show', @@ -79,11 +79,40 @@ async function handler() { }, }); - item.description = art(path.join(__dirname, 'templates/detail.art'), { - item, - detail: detail.data, - show: show.data, - }); + item.description = renderToString( + <> + {detail.data.img ? ( + <> + +
    + + ) : null} + + + + + + + + + + + + + + + + + +
    类型:{detail.data.productSubtypeName}
    时间:{detail.data.showStartToEndTime}
    地点: + {detail.data.showPlaceName}-{item.placeCname} +
    + {item.minPrice}-{item.maxPrice} +
    +
    + {detail.data.projectDesp ? raw(detail.data.projectDesp) : null} + + ); item.pubDate = show.data.showInfoDetailList ? parseDate(show.data.showInfoDetailList[0].saleBeginTime, 'x') : null; return item; diff --git a/lib/routes/shoac/templates/detail.art b/lib/routes/shoac/templates/detail.art deleted file mode 100644 index 4cf80a6ee..000000000 --- a/lib/routes/shoac/templates/detail.art +++ /dev/null @@ -1,25 +0,0 @@ -{{ if detail.img }} -
    -{{ /if }} - - - - - - - - - - - - - - - - - -
    类型:{{ detail.productSubtypeName }}
    时间:{{ detail.showStartToEndTime }}
    地点:{{ detail.showPlaceName }}-{{ item.placeCname }}
    {{ item.minPrice }}-{{ item.maxPrice }}
    -
    -{{ if detail.projectDesp }} -{{@ detail.projectDesp }} -{{ /if }} diff --git a/lib/routes/shuiguopai/index.ts b/lib/routes/shuiguopai/index.tsx similarity index 84% rename from lib/routes/shuiguopai/index.ts rename to lib/routes/shuiguopai/index.tsx index c4f00defe..4813401b2 100644 --- a/lib/routes/shuiguopai/index.ts +++ b/lib/routes/shuiguopai/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -89,10 +88,16 @@ async function handler(ctx) { item.author = data.map((d) => d.actor).join(' '); } - item.description = art(path.join(__dirname, 'templates/description.art'), { - videos, - description: content('.content').html(), - }); + item.description = renderToString( + <> + {videos?.map((video) => ( + + ))} + {raw(content('.content').html())} + + ); return item; }) diff --git a/lib/routes/shuiguopai/templates/description.art b/lib/routes/shuiguopai/templates/description.art deleted file mode 100644 index 4d4411a43..000000000 --- a/lib/routes/shuiguopai/templates/description.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if videos }} -{{ each videos video }} - -{{ /each }} -{{ /if }} -{{@ description }} diff --git a/lib/routes/simpleinfo/index.ts b/lib/routes/simpleinfo/index.tsx similarity index 87% rename from lib/routes/simpleinfo/index.ts rename to lib/routes/simpleinfo/index.tsx index 8130f2c78..3240e2551 100644 --- a/lib/routes/simpleinfo/index.ts +++ b/lib/routes/simpleinfo/index.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -71,7 +70,7 @@ async function handler(ctx) { const content = load(result.data); item.author = content('meta[property="article:author"]').attr('content'); item.pubDate = timezone(parseDate(content('meta[property="article:published_time"]').attr('content')), +8); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ image: content('meta[property="og:image"]').attr('content'), description: content('.article-content').first().html(), }); @@ -87,3 +86,11 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = ({ image, description }: { image?: string; description?: string }): string => + renderToString( + <> + {image ? : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/simpleinfo/templates/description.art b/lib/routes/simpleinfo/templates/description.art deleted file mode 100644 index 78c9f99df..000000000 --- a/lib/routes/simpleinfo/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if image }} - -{{ /if }} -{{ if description }} -{{@ description }} -{{ /if }} diff --git a/lib/routes/sina/templates/slide.art b/lib/routes/sina/templates/slide.art deleted file mode 100644 index 235e1526a..000000000 --- a/lib/routes/sina/templates/slide.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each slideData.images img }} - {{ img.intro }} -{{ /each }} diff --git a/lib/routes/sina/templates/video.art b/lib/routes/sina/templates/video.art deleted file mode 100644 index c86a810b5..000000000 --- a/lib/routes/sina/templates/video.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if videoUrl }} - -{{ /if }} diff --git a/lib/routes/sina/utils.ts b/lib/routes/sina/utils.tsx similarity index 85% rename from lib/routes/sina/utils.ts rename to lib/routes/sina/utils.tsx index 977510e5f..e1a7c3025 100644 --- a/lib/routes/sina/utils.ts +++ b/lib/routes/sina/utils.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const getRollNewsList = (pageid, lid, limit) => @@ -52,7 +50,13 @@ const parseArticle = (item, tryGet) => .text() .match(/var slide_data = ({.*?})\s/)[1] ); - item.description = art(path.join(__dirname, 'templates/slide.art'), { slideData }); + item.description = renderToString( + <> + {slideData.images.map((img) => ( + {img.intro} + ))} + + ); } else if (item.link.startsWith('https://video.sina.com.cn/')) { const videoId = $('script') .text() @@ -83,7 +87,15 @@ const parseArticle = (item, tryGet) => const videoData = videoResponse.data; const poster = videoData.image; const videoUrl = videoData.videos.find((v) => v.type === 'mp4').dispatch_result.url; - item.description = art(path.join(__dirname, 'templates/video.art'), { poster, videoUrl }); + item.description = renderToString( + <> + {videoUrl ? ( + + ) : null} + + ); item.pubDate = parseDate(videoData.create_time, 'X'); } else if (item.link.startsWith('https://news.sina.com.cn/') || item.link.startsWith('https://mil.news.sina.com.cn/')) { item.description = $('#article').html(); diff --git a/lib/routes/sinchew/index.ts b/lib/routes/sinchew/index.tsx similarity index 85% rename from lib/routes/sinchew/index.ts rename to lib/routes/sinchew/index.tsx index 200c7b77a..e14e767cb 100644 --- a/lib/routes/sinchew/index.ts +++ b/lib/routes/sinchew/index.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import { getSubPath } from '@/utils/common-utils'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -64,10 +62,12 @@ async function handler(ctx) { content('figure').each(function () { content(this).replaceWith( - art(path.join(__dirname, 'templates/images.art'), { - image: content(this).find('img').attr('src'), - caption: content(this).find('figcaption').text(), - }) + renderToString( +
    + +
    {content(this).find('figcaption').text()}
    +
    + ) ); }); diff --git a/lib/routes/sinchew/templates/images.art b/lib/routes/sinchew/templates/images.art deleted file mode 100644 index 4e53d4340..000000000 --- a/lib/routes/sinchew/templates/images.art +++ /dev/null @@ -1,4 +0,0 @@ -
    - -
    {{ caption }}
    -
    \ No newline at end of file diff --git a/lib/routes/sjtu/templates/activity.art b/lib/routes/sjtu/templates/activity.art deleted file mode 100644 index edf60c750..000000000 --- a/lib/routes/sjtu/templates/activity.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ e.name }}
    -开始时间: {{ e.sign_start_time }}
    -结束时间: {{ e.sign_end_time }}
    -地点: {{ e.location }}
    -报名人数: {{ e.member_count }}/{{ e.max_member }}
    -来自{{ e.source }} diff --git a/lib/routes/sjtu/tongqu/activity.ts b/lib/routes/sjtu/tongqu/activity.tsx similarity index 79% rename from lib/routes/sjtu/tongqu/activity.ts rename to lib/routes/sjtu/tongqu/activity.tsx index 3290b1eef..dc2d1fca7 100644 --- a/lib/routes/sjtu/tongqu/activity.ts +++ b/lib/routes/sjtu/tongqu/activity.tsx @@ -1,8 +1,7 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const urlRoot = 'https://tongqu.sjtu.edu.cn'; @@ -56,7 +55,7 @@ async function handler(ctx) { title: e.name, link: new URL(`/act/${e.actid}`, urlRoot).href, category: e.typename, - description: art(path.join(__dirname, '../templates/activity.art'), { e }), + description: renderDescription(e), })); return { @@ -65,3 +64,20 @@ async function handler(ctx) { item: feeds, }; } + +const renderDescription = (e): string => + renderToString( + <> + {e.name} +
    + 开始时间: {e.sign_start_time} +
    + 结束时间: {e.sign_end_time} +
    + 地点: {e.location} +
    + 报名人数: {e.member_count}/{e.max_member} +
    + 来自{e.source} + + ); diff --git a/lib/routes/skeb/templates/creator.art b/lib/routes/skeb/templates/creator.art deleted file mode 100644 index bb64c9a1b..000000000 --- a/lib/routes/skeb/templates/creator.art +++ /dev/null @@ -1,8 +0,0 @@ -{{ if avatarUrl }} - -{{ /if }} -

    委託狀況(Accepting Commissions):{{ acceptingCommissions }}

    -

    NSFW:{{ nsfwAcceptable }}

    -{{ if skills }} -

    類型(Genre):{{ skills }}

    -{{ /if }} diff --git a/lib/routes/skeb/templates/work.art b/lib/routes/skeb/templates/work.art deleted file mode 100644 index 6c0903453..000000000 --- a/lib/routes/skeb/templates/work.art +++ /dev/null @@ -1,10 +0,0 @@ -{{ if imageUrl }} -
    -{{ /if }} -{{ if audioUrl }} -
    -{{ /if }} -{{ body }} diff --git a/lib/routes/skeb/utils.ts b/lib/routes/skeb/utils.tsx similarity index 73% rename from lib/routes/skeb/utils.ts rename to lib/routes/skeb/utils.tsx index 3d03bc9e3..5d99818ea 100644 --- a/lib/routes/skeb/utils.ts +++ b/lib/routes/skeb/utils.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { DataItem } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const baseUrl = 'https://skeb.jp'; @@ -66,11 +65,7 @@ export function processWork(work: Work): DataItem | null { const audioUrl = work.genre === 'music' || work.genre === 'voice' ? work.preview_url : null; - const renderedHtml = art(path.join(__dirname, 'templates/work.art'), { - imageUrl, - body, - audioUrl, - }); + const renderedHtml = renderToString(); return { title: work.path || '', @@ -110,12 +105,7 @@ export function processCreator(creator: Creator): DataItem | null { .join(', '); } - renderedHtml = art(path.join(__dirname, 'templates/creator.art'), { - avatarUrl, - acceptingCommissions, - nsfwAcceptable, - skills, - }); + renderedHtml = renderToString(); } return { @@ -151,3 +141,33 @@ export async function getFollowingsItems(username: string, path: 'friend_works' } return followings_data[path].map((item) => processWork(item)).filter(Boolean) as DataItem[]; } + +const SkebWorkDescription = ({ imageUrl, body, audioUrl }: { imageUrl?: string; body: string; audioUrl?: string | null }) => ( + <> + {imageUrl ? ( + <> + +
    + + ) : null} + {audioUrl ? ( + <> + +
    + + ) : null} + {body} + +); + +const SkebCreatorDescription = ({ avatarUrl, acceptingCommissions, nsfwAcceptable, skills }: { avatarUrl?: string; acceptingCommissions: string; nsfwAcceptable: string; skills?: string }) => ( + <> + {avatarUrl ? : null} +

    委託狀況(Accepting Commissions):{acceptingCommissions}

    +

    NSFW:{nsfwAcceptable}

    + {skills ?

    類型(Genre):{skills}

    : null} + +); diff --git a/lib/routes/snowpeak/templates/new-arrivals.art b/lib/routes/snowpeak/templates/new-arrivals.art deleted file mode 100644 index d1b610e5b..000000000 --- a/lib/routes/snowpeak/templates/new-arrivals.art +++ /dev/null @@ -1,11 +0,0 @@ -
    - Variant: -
    - {{each product.variants}} - {{$value.name}} -
    - {{/each}} - {{each product.images}} - - {{/each}} -
    diff --git a/lib/routes/snowpeak/us-new-arrivals.ts b/lib/routes/snowpeak/us-new-arrivals.tsx similarity index 76% rename from lib/routes/snowpeak/us-new-arrivals.ts rename to lib/routes/snowpeak/us-new-arrivals.tsx index 782636929..93f700df5 100644 --- a/lib/routes/snowpeak/us-new-arrivals.ts +++ b/lib/routes/snowpeak/us-new-arrivals.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const host = 'https://www.snowpeak.com'; export const route: Route = { @@ -52,9 +50,21 @@ async function handler() { data.variants = product.variants.map((item) => item.name); data.description = product.description + - art(path.join(__dirname, 'templates/new-arrivals.art'), { - product, - }); + renderToString( +
    + Variant: +
    + {product.variants.map((variant) => ( + <> + {variant.name} +
    + + ))} + {product.images.map((image) => ( + + ))} +
    + ); return data; }); diff --git a/lib/routes/sogou/search.ts b/lib/routes/sogou/search.tsx similarity index 88% rename from lib/routes/sogou/search.ts rename to lib/routes/sogou/search.tsx index 9ed582c81..b51cbc100 100644 --- a/lib/routes/sogou/search.ts +++ b/lib/routes/sogou/search.tsx @@ -1,15 +1,22 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; -const renderDescription = (description, images) => art(path.join(__dirname, './templates/description.art'), { description, images }); +const renderDescription = (description, images) => + renderToString( + <> + {description ? raw(description) : null} + {images?.map((src) => ( + + ))} + + ); export const route: Route = { path: '/search/:keyword', diff --git a/lib/routes/sogou/templates/description.art b/lib/routes/sogou/templates/description.art deleted file mode 100644 index 5f98f4ca3..000000000 --- a/lib/routes/sogou/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{@ description }} -{{if images}} - {{each images}} - - {{/each}} -{{/if}} diff --git a/lib/routes/sohu/mp.ts b/lib/routes/sohu/mp.tsx similarity index 92% rename from lib/routes/sohu/mp.ts rename to lib/routes/sohu/mp.tsx index f01cdc54a..a99761c50 100644 --- a/lib/routes/sohu/mp.ts +++ b/lib/routes/sohu/mp.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; import CryptoJS from 'crypto-js'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -68,13 +66,20 @@ function fetchArticle(item) { const videoSrc = $('script') .text() .match(/\s*url: "(.*?)",/)?.[1]; - item.description = art(path.join(__dirname, 'templates/video.art'), { - poster: $('script') - .text() - .match(/cover: "(.*?)",/)?.[1], - src: videoSrc, - type: videoSrc?.split('.').pop()?.toLowerCase(), - }); + const poster = $('script') + .text() + .match(/cover: "(.*?)",/)?.[1]; + const type = videoSrc?.split('.').pop()?.toLowerCase(); + const source = type ? : ; + item.description = renderToString( + poster ? ( + + ) : ( + + ) + ); } else { const article = $('#mp-editor'); diff --git a/lib/routes/sohu/templates/video.art b/lib/routes/sohu/templates/video.art deleted file mode 100644 index d6e27f87c..000000000 --- a/lib/routes/sohu/templates/video.art +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/lib/routes/sony/templates/software-description.art b/lib/routes/sony/templates/software-description.art deleted file mode 100644 index 1538d42cf..000000000 --- a/lib/routes/sony/templates/software-description.art +++ /dev/null @@ -1,4 +0,0 @@ -
    -

    - Release Date: {{ item.pubDate }} -
    diff --git a/lib/routes/southcn/nfapp/column.ts b/lib/routes/southcn/nfapp/column.ts index 17743309c..853488f28 100644 --- a/lib/routes/southcn/nfapp/column.ts +++ b/lib/routes/southcn/nfapp/column.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from '../templates/description'; import { parseArticle } from './utils'; export const route: Route = { @@ -54,7 +52,7 @@ async function handler(ctx) { .filter((i) => i.articleType === 0) .map((item) => ({ title: '【' + item.columnName + '】' + item.title, - description: art(path.join(__dirname, '../templates/description.art'), { + description: renderDescription({ thumb: item.picMiddle, description: item.summary === '详见内文' ? '' : item.summary, }), diff --git a/lib/routes/southcn/nfapp/reporter.ts b/lib/routes/southcn/nfapp/reporter.ts index b6934b94b..2a740f01e 100644 --- a/lib/routes/southcn/nfapp/reporter.ts +++ b/lib/routes/southcn/nfapp/reporter.ts @@ -1,12 +1,10 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from '../templates/description'; import { parseArticle } from './utils'; export const route: Route = { @@ -36,7 +34,7 @@ async function handler(ctx) { const list = response.data.reportInfo.articleInfo.map((item) => ({ title: '【' + item.releaseColName + '】' + item.title, - description: art(path.join(__dirname, '../templates/description.art'), { + description: renderDescription({ thumb: item.picMiddle, description: item.attAbstract, }), diff --git a/lib/routes/southcn/templates/description.art b/lib/routes/southcn/templates/description.art deleted file mode 100644 index e94386a36..000000000 --- a/lib/routes/southcn/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if thumb }} - -{{ /if }} -{{ if description }} -

    {{ description }}

    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/southcn/templates/description.tsx b/lib/routes/southcn/templates/description.tsx new file mode 100644 index 000000000..0e9adff5b --- /dev/null +++ b/lib/routes/southcn/templates/description.tsx @@ -0,0 +1,19 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + thumb?: string; + description?: string; +}; + +const SouthcnDescription = ({ thumb, description }: DescriptionData) => ( + <> + {thumb ? : null} + {description ? ( +
    +

    {description}

    +
    + ) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/spankbang/new-videos.ts b/lib/routes/spankbang/new-videos.tsx similarity index 87% rename from lib/routes/spankbang/new-videos.ts rename to lib/routes/spankbang/new-videos.tsx index 57ff9b146..a0ef0dcba 100644 --- a/lib/routes/spankbang/new-videos.ts +++ b/lib/routes/spankbang/new-videos.tsx @@ -1,15 +1,22 @@ -import path from 'node:path'; - import * as cheerio from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Data, Route } from '@/types'; import cache from '@/utils/cache'; import logger from '@/utils/logger'; import puppeteer from '@/utils/puppeteer'; -import { art } from '@/utils/render'; -const render = (data) => art(path.join(__dirname, 'templates/video.art'), data); +const render = ({ preview, cover }) => + renderToString( + <> + {preview ? ( + + ) : null} + + ); const handler = async () => { const baseUrl = 'https://spankbang.com'; diff --git a/lib/routes/spankbang/templates/video.art b/lib/routes/spankbang/templates/video.art deleted file mode 100644 index c30942421..000000000 --- a/lib/routes/spankbang/templates/video.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if preview }} - -{{ /if }} diff --git a/lib/routes/springer/journal.ts b/lib/routes/springer/journal.tsx similarity index 76% rename from lib/routes/springer/journal.ts rename to lib/routes/springer/journal.tsx index 93cbd1952..18ece1901 100644 --- a/lib/routes/springer/journal.ts +++ b/lib/routes/springer/journal.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const route: Route = { path: '/journal/:journal', @@ -94,9 +92,41 @@ async function handler(ctx) { }); const renderDesc = (item) => - art(path.join(__dirname, 'templates/description.art'), { - item, - }); + renderToString( + <> +

    + + {item.title} + +
    +

    +

    + + + {item.authors} + + +
    + + + https://doi.org/{item.doi} + + +
    + + + {item.issue} + + +
    + +

    +

    + {item.abstract} +
    +

    + + ); const items = await Promise.all( list.map((item) => cache.tryGet(item.link, async () => { diff --git a/lib/routes/springer/templates/description.art b/lib/routes/springer/templates/description.art deleted file mode 100755 index 8e9b7767d..000000000 --- a/lib/routes/springer/templates/description.art +++ /dev/null @@ -1,12 +0,0 @@ -

    - {{ item.title }}
    -

    -

    - {{ item.authors }}
    - https://doi.org/{{ item.doi }}
    - {{ item.issue }}
    - -

    -

    - {{ item.abstract }}
    -

    \ No newline at end of file diff --git a/lib/routes/sse/inquire.ts b/lib/routes/sse/inquire.tsx similarity index 69% rename from lib/routes/sse/inquire.ts rename to lib/routes/sse/inquire.tsx index 2e404426e..8dc4b5009 100644 --- a/lib/routes/sse/inquire.ts +++ b/lib/routes/sse/inquire.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/inquire', @@ -55,9 +54,7 @@ async function handler() { const items = response.data.result.map((item) => ({ title: item.extGSJC, - description: art(path.resolve(__dirname, 'templates/inquire.art'), { - item, - }), + description: renderToString(), pubDate: parseDate(item.createTime), link: `https://${item.docURL}`, author: item.extGSJC, @@ -69,3 +66,30 @@ async function handler() { item: items, }; } + +const SseInquireDescription = ({ item }: { item: any }) => ( + + + + + + + + + + + + + + + + + + + + + +
    公司代码 : {item.stockcode}
    公司简称 : {item.extGSJC}
    发函日期 : {item.createTime}
    监管问询类型 : {item.extWTFL}
    标题 : + {item.docTitle} +
    +); diff --git a/lib/routes/sse/renewal.ts b/lib/routes/sse/renewal.ts deleted file mode 100644 index 053a54c5d..000000000 --- a/lib/routes/sse/renewal.ts +++ /dev/null @@ -1,86 +0,0 @@ -import 'dayjs/locale/zh-cn.js'; - -import path from 'node:path'; - -import dayjs from 'dayjs'; -import localizedFormat from 'dayjs/plugin/localizedFormat.js'; - -import type { Route } from '@/types'; -import got from '@/utils/got'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -dayjs.extend(localizedFormat); - -const currStatusName = ['全部', '已受理', '已询问', '通过', '未通过', '提交注册', '补充审核', '注册结果', '中止', '终止']; - -export const route: Route = { - path: '/renewal', - categories: ['finance'], - example: '/sse/renewal', - parameters: {}, - features: { - requireConfig: false, - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - radar: [ - { - source: ['kcb.sse.com.cn/home', 'kcb.sse.com.cn/'], - }, - ], - name: '科创板项目动态', - maintainers: ['Jeason0228'], - handler, - url: 'kcb.sse.com.cn/home', -}; - -async function handler() { - const pageUrl = 'https://kcb.sse.com.cn/renewal/'; - const host = `https://kcb.sse.com.cn`; - - const response = await got('https://query.sse.com.cn/statusAction.do', { - searchParams: { - isPagination: true, - sqlId: 'SH_XM_LB', - 'pageHelp.pageSize': 20, - offerType: '', - commitiResult: '', - registeResult: '', - province: '', - csrcCode: '', - currStatus: '', - order: 'updateDate|desc,stockAuditNum|desc', - keyword: '', - auditApplyDateBegin: '', - auditApplyDateEnd: '', - _: Date.now(), - }, - headers: { - Referer: pageUrl, - }, - }); - - // console.log(response.data.result); - const items = response.data.result.map((item) => ({ - title: `【${currStatusName[item.currStatus]}】${item.stockAuditName}`, - description: art(path.resolve(__dirname, 'templates/renewal.art'), { - item, - currStatus: currStatusName[item.currStatus], - updateDate: dayjs(item.updateDate, 'YYYYMMDDHHmmss').locale('zh-cn').format('lll'), - auditApplyDate: dayjs(item.auditApplyDate, 'YYYYMMDDHHmmss').locale('zh-cn').format('lll'), - }), - pubDate: parseDate(item.updateDate, 'YYYYMMDDHHmmss'), - link: `${host}/renewal/xmxq/index.shtml?auditId=${item.stockAuditNum}`, - author: item.stockAuditName, - })); - - return { - title: '上海证券交易所 - 科创板项目动态', - link: pageUrl, - item: items, - }; -} diff --git a/lib/routes/sse/renewal.tsx b/lib/routes/sse/renewal.tsx new file mode 100644 index 000000000..be4e0d3ec --- /dev/null +++ b/lib/routes/sse/renewal.tsx @@ -0,0 +1,159 @@ +import 'dayjs/locale/zh-cn.js'; + +import dayjs from 'dayjs'; +import localizedFormat from 'dayjs/plugin/localizedFormat.js'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; + +dayjs.extend(localizedFormat); + +const currStatusName = ['全部', '已受理', '已询问', '通过', '未通过', '提交注册', '补充审核', '注册结果', '中止', '终止']; + +export const route: Route = { + path: '/renewal', + categories: ['finance'], + example: '/sse/renewal', + parameters: {}, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['kcb.sse.com.cn/home', 'kcb.sse.com.cn/'], + }, + ], + name: '科创板项目动态', + maintainers: ['Jeason0228'], + handler, + url: 'kcb.sse.com.cn/home', +}; + +async function handler() { + const pageUrl = 'https://kcb.sse.com.cn/renewal/'; + const host = `https://kcb.sse.com.cn`; + + const response = await got('https://query.sse.com.cn/statusAction.do', { + searchParams: { + isPagination: true, + sqlId: 'SH_XM_LB', + 'pageHelp.pageSize': 20, + offerType: '', + commitiResult: '', + registeResult: '', + province: '', + csrcCode: '', + currStatus: '', + order: 'updateDate|desc,stockAuditNum|desc', + keyword: '', + auditApplyDateBegin: '', + auditApplyDateEnd: '', + _: Date.now(), + }, + headers: { + Referer: pageUrl, + }, + }); + + // console.log(response.data.result); + const items = response.data.result.map((item) => ({ + title: `【${currStatusName[item.currStatus]}】${item.stockAuditName}`, + description: renderToString( + + ), + pubDate: parseDate(item.updateDate, 'YYYYMMDDHHmmss'), + link: `${host}/renewal/xmxq/index.shtml?auditId=${item.stockAuditNum}`, + author: item.stockAuditName, + })); + + return { + title: '上海证券交易所 - 科创板项目动态', + link: pageUrl, + item: items, + }; +} + +const SseRenewalDescription = ({ item, currStatus, updateDate, auditApplyDate }: { item: any; currStatus: string; updateDate: string; auditApplyDate: string }) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {item.intermediary[3] ? ( + + + + + ) : null} + + + + + + + + + + + + +
    + 发行人全称 + {item.stockAuditName}
    + 审核状态 + {currStatus}
    + 注册地 + {item.stockIssuer[0].s_province}
    + 证监会行业 + {item.stockIssuer[0].s_csrcCodeDesc}
    + 保荐机构 + {item.intermediary[0].i_intermediaryName}
    + 律师事务所 + {item.intermediary[2].i_intermediaryName}
    + 会计师事务所 + {item.intermediary[1].i_intermediaryName}
    + 评估机构 + {item.intermediary[3].i_intermediaryName}
    + 更新日期 + {updateDate}
    + 受理日期 + {auditApplyDate}
    详细链接 + 查看详情 +
    +); diff --git a/lib/routes/sse/templates/inquire.art b/lib/routes/sse/templates/inquire.art deleted file mode 100644 index a857dd03c..000000000 --- a/lib/routes/sse/templates/inquire.art +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - -
    公司代码 : {{ item.stockcode }}
    公司简称 : {{ item.extGSJC }}
    发函日期 : {{ item.createTime }}
    监管问询类型 : {{ item.extWTFL }}
    标题 : {{ item.docTitle }}
    diff --git a/lib/routes/sse/templates/renewal.art b/lib/routes/sse/templates/renewal.art deleted file mode 100644 index 23f639432..000000000 --- a/lib/routes/sse/templates/renewal.art +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - -{{ if item.intermediary[3] }}{{ /if }} - - - -
    发行人全称{{ item.stockAuditName }}
    审核状态{{ currStatus }}
    注册地{{ item.stockIssuer[0].s_province }}
    证监会行业{{ item.stockIssuer[0].s_csrcCodeDesc }}
    保荐机构{{ item.intermediary[0].i_intermediaryName }}
    律师事务所{{ item.intermediary[2].i_intermediaryName }}
    会计师事务所{{ item.intermediary[1].i_intermediaryName }}
    评估机构{{ item.intermediary[3].i_intermediaryName }}
    更新日期{{ updateDate }}
    受理日期{{ auditApplyDate }}
    详细链接查看详情
    diff --git a/lib/routes/ssm/news.ts b/lib/routes/ssm/news.tsx similarity index 86% rename from lib/routes/ssm/news.ts rename to lib/routes/ssm/news.tsx index 8a5160c98..23b74638b 100644 --- a/lib/routes/ssm/news.ts +++ b/lib/routes/ssm/news.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rootUrl = `https://www.ssm.gov.mo`; const newsUrl = `${rootUrl}/apps1/content/ch/973/itemlist.aspx?defaultcss=false&dlimit=20&showdate=true&dorder=cridate%20desc,displaydate%20desc&withattach=true`; @@ -43,9 +41,7 @@ async function handler() { const title = $(item).find('a').text(); const link = $(item).find('a').attr('href'); const pubDate = parseDate($(item).find('small').text().split(':')[1].trim(), 'DD/MM/YYYY'); - const desc = art(path.join(__dirname, 'templates/news.art'), { - link, - }); + const desc = renderToString(); return { title, @@ -61,3 +57,5 @@ async function handler() { item, }; } + +const SsmNewsDescription = ({ link }: { link?: string }) => - {{ /if }} -{{ /if }} diff --git a/lib/routes/theverge/templates/header.tsx b/lib/routes/theverge/templates/header.tsx new file mode 100644 index 000000000..c8351f967 --- /dev/null +++ b/lib/routes/theverge/templates/header.tsx @@ -0,0 +1,55 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type HeaderRenderOptions = { + featuredImage?: { + image?: { + originalUrl?: string; + title?: string; + alt?: string; + }; + }; + ledeMediaData?: { + __typename?: string; + embedHtml?: string; + image?: { + thumbnails?: { + horizontal?: { + url?: string; + }; + }; + title?: string; + credit?: { + plaintext?: string; + }; + }; + video?: { + volumeUuid?: string; + }; + }; +}; + +export const renderHeader = ({ featuredImage, ledeMediaData }: HeaderRenderOptions): string => + renderToString( + <> + {featuredImage?.image?.originalUrl ? ( +
    + {featuredImage.image.alt +
    {featuredImage.image.title}
    +
    + ) : null} + + {ledeMediaData ? ( + ledeMediaData.__typename === 'LedeMediaEmbedType' ? ( + <>{ledeMediaData.embedHtml ? raw(ledeMediaData.embedHtml) : null} + ) : ledeMediaData.__typename === 'LedeMediaImageType' && !featuredImage ? ( +
    + {ledeMediaData.image?.title +
    {ledeMediaData.image?.credit?.plaintext || ledeMediaData.image?.title}
    +
    + ) : ledeMediaData.__typename === 'LedeMediaVideoType' ? ( + + ) : null + ) : null} + + ); diff --git a/lib/routes/thewirehindi/templates/description.art b/lib/routes/thewirehindi/templates/description.art deleted file mode 100644 index 43cb44900..000000000 --- a/lib/routes/thewirehindi/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{@ excerpt }} -

    -{{ if image }} -{{ altText }} -

    -{{ /if }} -{{ if content }} -{{@ content }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/thewirehindi/utils.ts b/lib/routes/thewirehindi/utils.tsx similarity index 60% rename from lib/routes/thewirehindi/utils.ts rename to lib/routes/thewirehindi/utils.tsx index 730ded2bc..30a5335ed 100644 --- a/lib/routes/thewirehindi/utils.ts +++ b/lib/routes/thewirehindi/utils.tsx @@ -1,8 +1,25 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { DataItem } from '@/types'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const renderDescription = (excerpt, image, altText, content) => + renderToString( + <> + {excerpt ? <>{raw(excerpt)} : null} +
    +
    + {image ? ( + <> + {altText} +
    +
    + + ) : null} + {content ? <>{raw(content)} : null} + + ); export function mapPostToItem(post): DataItem { const featuredMedia = post._embedded?.['wp:featuredmedia']?.find((v) => v.id === post.featured_media); @@ -10,12 +27,7 @@ export function mapPostToItem(post): DataItem { const altText = featuredMedia?.alt_text || featuredMedia?.title?.rendered || 'Featured Image'; return { title: post.title.rendered, - description: art(path.join(__dirname, 'templates/description.art'), { - excerpt: post.excerpt.rendered, - content: post.content.rendered, - image, - altText, - }), + description: renderDescription(post.excerpt.rendered, image, altText, post.content.rendered), link: post.link, pubDate: parseDate(post.date_gmt), updated: parseDate(post.modified_gmt), diff --git a/lib/routes/thoughtco/index.ts b/lib/routes/thoughtco/index.ts index f3bbe0458..531046f14 100644 --- a/lib/routes/thoughtco/index.ts +++ b/lib/routes/thoughtco/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/:category?', @@ -359,7 +358,7 @@ async function handler(ctx) { const image = e.find('img'); e.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: image.prop('data-src'), width: image.prop('width'), @@ -370,7 +369,7 @@ async function handler(ctx) { }); item.title = content('meta[property="og:title"]').prop('content'); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ image: { src: content('meta[property="og:image"]').prop('content'), }, diff --git a/lib/routes/thoughtco/templates/description.art b/lib/routes/thoughtco/templates/description.art deleted file mode 100644 index 1eac078ae..000000000 --- a/lib/routes/thoughtco/templates/description.art +++ /dev/null @@ -1,16 +0,0 @@ -{{ if image }} -
    - -
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/thoughtco/templates/description.tsx b/lib/routes/thoughtco/templates/description.tsx new file mode 100644 index 000000000..28ce68e1c --- /dev/null +++ b/lib/routes/thoughtco/templates/description.tsx @@ -0,0 +1,25 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageData = { + src?: string; + width?: string | number; + height?: string | number; +}; + +type DescriptionData = { + image?: ImageData; + description?: string; +}; + +export const renderDescription = ({ image, description }: DescriptionData) => + renderToString( + <> + {image ? ( +
    + +
    + ) : null} + {description ? <>{raw(description)} : null} + + ); diff --git a/lib/routes/tiktok/templates/user.art b/lib/routes/tiktok/templates/user.art deleted file mode 100644 index 0c582bfae..000000000 --- a/lib/routes/tiktok/templates/user.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if useIframe }} - -{{ else }} - -{{ /if }} diff --git a/lib/routes/tiktok/templates/user.tsx b/lib/routes/tiktok/templates/user.tsx new file mode 100644 index 000000000..36fb4772c --- /dev/null +++ b/lib/routes/tiktok/templates/user.tsx @@ -0,0 +1,21 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type UserEmbedProps = { + useIframe?: boolean; + id: string; + poster: string; + source: string; +}; + +export const renderUserEmbed = ({ useIframe, id, poster, source }: UserEmbedProps): string => + renderToString( + <> + {useIframe ? ( + + ) : ( + + )} + + ); diff --git a/lib/routes/tiktok/user.ts b/lib/routes/tiktok/user.ts index 463d833cc..8f87788c6 100644 --- a/lib/routes/tiktok/user.ts +++ b/lib/routes/tiktok/user.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import { config } from '@/config'; @@ -8,8 +6,8 @@ import cache from '@/utils/cache'; import { parseDate } from '@/utils/parse-date'; import puppeteer from '@/utils/puppeteer'; import { queryToBoolean } from '@/utils/readable-social'; -import { art } from '@/utils/render'; +import { renderUserEmbed } from './templates/user'; import type { Item } from './types'; const baseUrl = 'https://www.tiktok.com'; @@ -79,7 +77,7 @@ async function handler(ctx) { const items = itemList.itemList.map((item: Item) => ({ title: item.desc, - description: art(path.join(__dirname, 'templates/user.art'), { + description: renderUserEmbed({ poster: item.video.cover, source: item.video.playAddr, useIframe, diff --git a/lib/routes/tingshuitz/shenzhen.ts b/lib/routes/tingshuitz/shenzhen.tsx similarity index 71% rename from lib/routes/tingshuitz/shenzhen.ts rename to lib/routes/tingshuitz/shenzhen.tsx index 6bc5792b8..3e3420a3d 100644 --- a/lib/routes/tingshuitz/shenzhen.ts +++ b/lib/routes/tingshuitz/shenzhen.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -45,9 +44,17 @@ async function handler() { link: 'https://www.sz-water.com.cn/', item: data.map((item) => ({ title: `${item.position}${item.stoptime}`, - description: art(path.join(__dirname, 'templates/shenzhen.art'), { - item, - }), + description: renderToString( + <> +

    {item.title}

    +

    + {item.reginName ? `【${item.reginName}】` : null} + (影响用户{item.affectUser}), + {item.stopwaterType ? ` [${item.stopwaterType}]` : null} + 原因:{item.reason},停水开始时间{item.stopStartTime},停水结束时间{item.stopEndTime} +

    + + ), pubDate: timezone(parseDate(item.createdOn, 'YYYY-MM-DD HH:mm:ss'), +8), link: 'https://szgk.sz-water.com.cn/wechat_web/Water_stop.html', guid: `${item.position}${item.stopStartTime}`, diff --git a/lib/routes/tingshuitz/templates/shenzhen.art b/lib/routes/tingshuitz/templates/shenzhen.art deleted file mode 100644 index 7fabd864f..000000000 --- a/lib/routes/tingshuitz/templates/shenzhen.art +++ /dev/null @@ -1,8 +0,0 @@ -

    {{ item.title }}

    -

    {{if item.reginName }} - 【{{ item.reginName }}】 -{{ /if }} -(影响用户{{ item.affectUser }}), -{{if item.stopwaterType }} - [{{ item.stopwaterType }}] -{{ /if }}原因:{{ item.reason }},停水开始时间{{ item.stopStartTime }},停水结束时间{{ item.stopEndTime }}

    \ No newline at end of file diff --git a/lib/routes/tingtingfm/program.ts b/lib/routes/tingtingfm/program.tsx similarity index 92% rename from lib/routes/tingtingfm/program.ts rename to lib/routes/tingtingfm/program.tsx index f2cfdfdc6..2ce5038d1 100644 --- a/lib/routes/tingtingfm/program.ts +++ b/lib/routes/tingtingfm/program.tsx @@ -1,4 +1,4 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; @@ -6,10 +6,20 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { getClientVal, sign } from './utils'; +const renderAudio = (url) => + renderToString( + <> + {url ? ( + + ) : null} + + ); + export const route: Route = { path: '/program/:programId', categories: ['multimedia'], @@ -110,9 +120,7 @@ async function handler(ctx) { const items = list.map((audio) => ({ title: audio.title, link: `${mobileBaseUrl}/v3/vod/2/${audio.h_audio_id}`, - description: art(path.join(__dirname, 'templates/audio.art'), { - url: audio.play_url, - }), + description: renderAudio(audio.play_url), pubDate: parseDate(audio.add_time, 'X'), itunes_item_image: radioCover, itunes_duration: audio.duration, diff --git a/lib/routes/tingtingfm/templates/audio.art b/lib/routes/tingtingfm/templates/audio.art deleted file mode 100644 index b6c8e4e2a..000000000 --- a/lib/routes/tingtingfm/templates/audio.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if url }} - -{{ /if }} diff --git a/lib/routes/tmtpost/templates/description.art b/lib/routes/tmtpost/templates/description.art deleted file mode 100644 index 57498ab45..000000000 --- a/lib/routes/tmtpost/templates/description.art +++ /dev/null @@ -1,7 +0,0 @@ -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/tmtpost/templates/description.tsx b/lib/routes/tmtpost/templates/description.tsx new file mode 100644 index 000000000..dd257fcfe --- /dev/null +++ b/lib/routes/tmtpost/templates/description.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionData = { + intro?: string; + description?: string; +}; + +const TmtpostDescription = ({ intro, description }: DescriptionData) => ( + <> + {intro ?
    {intro}
    : null} + {description ? raw(description) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/tmtpost/util.ts b/lib/routes/tmtpost/util.ts index 8f9044006..2a3b2f281 100644 --- a/lib/routes/tmtpost/util.ts +++ b/lib/routes/tmtpost/util.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; @@ -7,7 +5,8 @@ import type { Data, DataItem } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const baseUrl: string = 'https://www.tmtpost.com'; const apiBaseUrl: string = 'https://api.tmtpost.com'; @@ -34,7 +33,7 @@ const processItems = async (limit: number, query: Record, apiUrl: s items = response.data.slice(0, limit).map((item): DataItem => { const title: string = item.title; - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ intro: item.summary, }); const pubDate: number | string = item.time_published; @@ -86,7 +85,7 @@ const processItems = async (limit: number, query: Record, apiUrl: s } const title: string = data.title; - const description: string = art(path.join(__dirname, 'templates/description.art'), { + const description: string = renderDescription({ intro: data.summary, description: data.main, }); diff --git a/lib/routes/tophub/list.ts b/lib/routes/tophub/list.tsx similarity index 74% rename from lib/routes/tophub/list.ts rename to lib/routes/tophub/list.tsx index 14dd039fe..8a4c60db6 100644 --- a/lib/routes/tophub/list.ts +++ b/lib/routes/tophub/list.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import xxhash from 'xxhash-wasm'; import { config } from '@/config'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; -import { art } from '@/utils/render'; export const route: Route = { path: '/list/:id', @@ -60,7 +58,28 @@ async function handler(ctx) { heatRate: $(e).find('td:nth-child(3)').text().trim(), })); const combinedTitles = items.map((item) => item.title).join(''); - const renderRank = art(path.join(__dirname, 'templates/rank.art'), { items }); + const renderRank = renderToString( + + + + + + + + + + {items.map((item, index) => ( + + + + + + ))} + +
    排名标题热度
    {index + 1} + {item.title} + {item.heatRate}
    + ); return { title, diff --git a/lib/routes/tophub/templates/rank.art b/lib/routes/tophub/templates/rank.art deleted file mode 100644 index 59e87d79d..000000000 --- a/lib/routes/tophub/templates/rank.art +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - {{each items}} - - - - - - {{/each}} - -
    排名标题热度
    {{ $index + 1 }} - - {{ $value.title }} - - {{ $value.heatRate }}
    \ No newline at end of file diff --git a/lib/routes/toutiao/templates/video.art b/lib/routes/toutiao/templates/video.art deleted file mode 100644 index 1b007c35c..000000000 --- a/lib/routes/toutiao/templates/video.art +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/lib/routes/toutiao/user.ts b/lib/routes/toutiao/user.tsx similarity index 91% rename from lib/routes/toutiao/user.ts rename to lib/routes/toutiao/user.tsx index c72224bd6..6b63a0e76 100644 --- a/lib/routes/toutiao/user.ts +++ b/lib/routes/toutiao/user.tsx @@ -1,4 +1,4 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import RejectError from '@/errors/types/reject'; @@ -7,11 +7,17 @@ import cache from '@/utils/cache'; import { generateHeaders, PRESETS } from '@/utils/header-generator'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import { generate_a_bogus } from './a-bogus'; import type { Feed } from './types'; +const renderVideo = (url, poster) => + renderToString( + + ); + export const route: Route = { path: '/user/token/:token', categories: ['new-media'], @@ -62,10 +68,7 @@ async function handler(ctx) { const video = item.video.play_addr_list.toSorted((a, b) => b.bitrate - a.bitrate)[0]; return { title: item.title, - description: art(path.join(__dirname, 'templates/video.art'), { - poster: item.video.origin_cover.url_list[0], - url: item.video.play_addr_list.toSorted((a, b) => b.bitrate - a.bitrate)[0].play_url_list[0], - }), + description: renderVideo(item.video.play_addr_list.toSorted((a, b) => b.bitrate - a.bitrate)[0].play_url_list[0], item.video.origin_cover.url_list[0]), link: `https://www.toutiao.com/video/${item.id}/`, pubDate: parseDate(item.publish_time, 'X'), author: item.user?.info.name ?? item.source, diff --git a/lib/routes/tradingview/blog.ts b/lib/routes/tradingview/blog.ts index 02d3b62c9..d935ae0c5 100644 --- a/lib/routes/tradingview/blog.ts +++ b/lib/routes/tradingview/blog.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import pMap from 'p-map'; @@ -7,7 +5,8 @@ import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/blog/:category{.+}?', @@ -38,7 +37,7 @@ async function handler(ctx) { return { title, link: item.find('a.articles-grid-link').prop('href'), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ image: { src: item .find('div.articles-grid-img img') @@ -68,20 +67,18 @@ async function handler(ctx) { .find('img') .each((_, e) => { content(e).replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: content(e) .prop('src') .replace(/-\d+x\d+\./, '.'), - width: content(e).prop('width'), - height: content(e).prop('height'), }, }) ); }); item.title = content('meta[property="og:title"]').prop('content'); - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ image: { src: content('meta[property="og:image"]').prop('content'), alt: item.title, diff --git a/lib/routes/tradingview/templates/description.art b/lib/routes/tradingview/templates/description.art deleted file mode 100644 index a89e118b2..000000000 --- a/lib/routes/tradingview/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if image?.src }} -
    - {{ image.alt }} -
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/tradingview/templates/description.tsx b/lib/routes/tradingview/templates/description.tsx new file mode 100644 index 000000000..2ee44ff49 --- /dev/null +++ b/lib/routes/tradingview/templates/description.tsx @@ -0,0 +1,20 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionImage = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + image?: DescriptionImage; + description?: string; +}; + +export const renderDescription = ({ image, description }: DescriptionData) => + renderToString( + <> + {image?.src ?
    {image.alt ? {image.alt} : }
    : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/transcriptforest/index.ts b/lib/routes/transcriptforest/index.ts index 7fcea4d0e..254f5492c 100644 --- a/lib/routes/transcriptforest/index.ts +++ b/lib/routes/transcriptforest/index.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; const bakeTimestamp = (seconds) => { const hours = Math.floor(seconds / 3600); @@ -63,7 +62,7 @@ async function handler(ctx) { title: item.episode_name, link: new URL(`${defaultLocale}/${item.channel_id}/${item.episode_id}`, rootUrl).href, detailUrl: new URL(`_next/data/${buildId}/${defaultLocale}/${item.channel_id}/${item.episode_id}.json`, rootUrl).href, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ texts: item.episode_description.split(/\n\n/).map((text) => ({ text, })), @@ -85,7 +84,7 @@ async function handler(ctx) { const { data: textResponse } = await got(detailResponse.pageProps.currentEpisode.ps4_url); item.description = - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ audios: [ { src: detailResponse.pageProps.currentEpisode.media, @@ -94,7 +93,7 @@ async function handler(ctx) { ], }) + item.description + - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ texts: textResponse.map((t) => ({ startTime: bakeTimestamp(t.startTime), endTime: bakeTimestamp(t.endTime), diff --git a/lib/routes/transcriptforest/templates/description.art b/lib/routes/transcriptforest/templates/description.art deleted file mode 100644 index c62849a76..000000000 --- a/lib/routes/transcriptforest/templates/description.art +++ /dev/null @@ -1,23 +0,0 @@ -{{ if audios }} - {{ each audios audio }} - {{ if audio?.src }} - - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if texts }} - {{ each texts t }} - {{ if t.startTime && t.endTime }} - {{ t.startTime }} - {{ t.endTime }} - {{ /if }} -

    {{ t.text }}

    - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/transcriptforest/templates/description.tsx b/lib/routes/transcriptforest/templates/description.tsx new file mode 100644 index 000000000..4e3faf752 --- /dev/null +++ b/lib/routes/transcriptforest/templates/description.tsx @@ -0,0 +1,44 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type AudioData = { + src?: string; + type?: string; +}; + +type TextData = { + startTime?: string; + endTime?: string; + text?: string; +}; + +type DescriptionData = { + audios?: AudioData[]; + texts?: TextData[]; +}; + +const TranscriptForestDescription = ({ audios, texts }: DescriptionData) => ( + <> + {audios?.map((audio) => + audio?.src ? ( + + ) : null + )} + {texts?.map((text) => ( + <> + {text.startTime && text.endTime ? ( + + {text.startTime} - {text.endTime} + + ) : null} +

    {text.text}

    + + ))} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/transformer-circuits/index.ts b/lib/routes/transformer-circuits/index.tsx similarity index 71% rename from lib/routes/transformer-circuits/index.ts rename to lib/routes/transformer-circuits/index.tsx index 1586228b9..67619f04b 100644 --- a/lib/routes/transformer-circuits/index.ts +++ b/lib/routes/transformer-circuits/index.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import logger from '@/utils/logger'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; // Define the main route path export const route: Route = { @@ -109,13 +108,79 @@ async function fetchArticleContent(url) { } // Create an HTML fragment (not a full document) for the RSS description - return art(path.join(__dirname, 'templates/article.art'), { - content, - link: url, - }); + return renderToString(); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error(`Error fetching article content from ${url}: ${errorMessage}`); return null; // Return null on error, we'll fall back to description } } + +const articleStyles = ` + .content-wrapper { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + line-height: 1.6; + color: #333; + } + img { + max-width: 100%; + height: auto; + } + pre, code { + background-color: #f5f5f5; + border-radius: 3px; + padding: 0.2em 0.4em; + overflow-x: auto; + } + a { + color: #0366d6; + text-decoration: none; + } + a:hover { + text-decoration: underline; + } + h1, h2, h3, h4, h5, h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: 600; + line-height: 1.25; + } + p, ul, ol { + margin-bottom: 16px; + } + .read-original { + margin-top: 30px; + margin-bottom: 30px; + text-align: center; + padding: 10px; + background-color: #f7f7f7; + border-radius: 4px; + } + /* Support for custom elements used on transformer-circuits website */ + d-figure, figure { + margin: 20px 0; + text-align: center; + } + d-byline { + font-size: 0.9em; + color: #666; + margin: 15px 0; + } + .gdoc-image img { + max-width: 100%; + display: block; + margin: 0 auto; + } +`; + +const TransformerCircuitsArticle = ({ content, link }: { content: string; link: string }) => ( + <> + +
    {raw(content)}
    + + +); diff --git a/lib/routes/transformer-circuits/templates/article.art b/lib/routes/transformer-circuits/templates/article.art deleted file mode 100644 index 80de0e0b3..000000000 --- a/lib/routes/transformer-circuits/templates/article.art +++ /dev/null @@ -1,62 +0,0 @@ - -
    - {{@ content }} -
    - \ No newline at end of file diff --git a/lib/routes/tribalfootball/latest.ts b/lib/routes/tribalfootball/latest.tsx similarity index 85% rename from lib/routes/tribalfootball/latest.ts rename to lib/routes/tribalfootball/latest.tsx index 149f87e89..6a20aff52 100644 --- a/lib/routes/tribalfootball/latest.ts +++ b/lib/routes/tribalfootball/latest.tsx @@ -1,15 +1,26 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const rssUrl = 'https://www.tribalfootball.com/rss/mediafed/general/rss.xml'; +const renderDescription = (desc, headerImage) => + renderToString( + <> + {headerImage ? ( +

    + +

    + ) : null} + {desc ? <>{raw(desc)} : null} + + ); + export const route: Route = { path: '/', radar: [ @@ -63,10 +74,7 @@ async function handler() { ad.parent().remove(); } desc = desc.html(); - desc = art(path.join(__dirname, 'templates/plus_header.art'), { - desc, - header_image: item._header_image, - }); + desc = renderDescription(desc, item._header_image); item.title = title || item.title; item.description = desc || item.description; diff --git a/lib/routes/tribalfootball/templates/plus_header.art b/lib/routes/tribalfootball/templates/plus_header.art deleted file mode 100644 index dceaf5868..000000000 --- a/lib/routes/tribalfootball/templates/plus_header.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if header_image }} -

    - -

    -{{ /if }} -{{@ desc }} diff --git a/lib/routes/tvb/news.ts b/lib/routes/tvb/news.tsx similarity index 89% rename from lib/routes/tvb/news.ts rename to lib/routes/tvb/news.tsx index f610bea1d..749c69dac 100644 --- a/lib/routes/tvb/news.ts +++ b/lib/routes/tvb/news.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const titles = { focus: { @@ -104,10 +104,12 @@ async function handler(ctx) { link: `${linkRootUrl}/${language}/${category}/${item.id}`, pubDate: parseDate(item.publish_datetime), category: [...item.category.map((c) => c.title), ...item.tags], - description: art(path.join(__dirname, 'templates/description.art'), { - description: item.desc, - images: item.media.image?.map((i) => i.thumbnail.replace(/_\d+x\d+\./, '.')) ?? [], - }), + description: renderToString( + <> + {item.desc ? raw(item.desc) : null} + {item.media.image?.map((image) => ) ?? null} + + ), })); return { diff --git a/lib/routes/tvb/templates/description.art b/lib/routes/tvb/templates/description.art deleted file mode 100644 index cbe2c57fe..000000000 --- a/lib/routes/tvb/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{@ description }} -{{ if images }} -{{ each images image }} - -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/tvtropes/featured.ts b/lib/routes/tvtropes/featured.tsx similarity index 83% rename from lib/routes/tvtropes/featured.ts rename to lib/routes/tvtropes/featured.tsx index 9e9677f5b..062fb629f 100644 --- a/lib/routes/tvtropes/featured.ts +++ b/lib/routes/tvtropes/featured.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; const categories = { today: 'left', @@ -57,16 +55,11 @@ async function handler(ctx) { const image = el.find('img'); el.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { - images: [ - { - src: image.prop('src'), - alt: image.prop('alt'), - width: image.prop('width'), - height: image.prop('height'), - }, - ], - }) + renderToString( +
    + {image.prop('alt')} +
    + ) ); }); diff --git a/lib/routes/tvtropes/templates/description.art b/lib/routes/tvtropes/templates/description.art deleted file mode 100644 index 48df396d7..000000000 --- a/lib/routes/tvtropes/templates/description.art +++ /dev/null @@ -1,23 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/twreporter/fetch-article.ts b/lib/routes/twreporter/fetch-article.ts index fa1d40c51..450407635 100644 --- a/lib/routes/twreporter/fetch-article.ts +++ b/lib/routes/twreporter/fetch-article.ts @@ -1,8 +1,8 @@ -import path from 'node:path'; - import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderImage } from './templates/image'; +import { renderYouTube } from './templates/youtube'; export default async function fetch(slug: string) { const url = `https://go-api.twreporter.org/v2/posts/${slug}?full=true`; @@ -38,7 +38,7 @@ export default async function fetch(slug: string) { const bannerDescription = imageSource?.description ?? ''; const ogDescription = post.og_description; // Only render the banner if we successfully found an image URL - const banner = imageSource ? art(path.join(__dirname, 'templates/image.art'), { image: bannerImage, description: bannerDescription, caption }) : ''; + const banner = imageSource ? renderImage({ image: bannerImage, description: bannerDescription, caption }) : ''; function format(type, content) { let block = ''; @@ -46,7 +46,7 @@ export default async function fetch(slug: string) { switch (type) { case 'image': case 'slideshow': - block = content.map((image) => art(path.join(__dirname, 'templates/image.art'), { image: image.desktop.url, description: image.description, caption: image.description })).join('
    '); + block = content.map((image) => renderImage({ image: image.desktop.url, description: image.description, caption: image.description })).join('
    '); break; @@ -74,7 +74,7 @@ export default async function fetch(slug: string) { case 'youtube': { const video = content[0].youtubeId; const id = video.split('?')[0]; - block = art(path.join(__dirname, 'templates/youtube.art'), { video: id }); + block = renderYouTube({ video: id }); break; } diff --git a/lib/routes/twreporter/templates/image.art b/lib/routes/twreporter/templates/image.art deleted file mode 100644 index 74fd8bd73..000000000 --- a/lib/routes/twreporter/templates/image.art +++ /dev/null @@ -1,3 +0,0 @@ - -{{ description }} -
    {{ caption }}
    \ No newline at end of file diff --git a/lib/routes/twreporter/templates/image.tsx b/lib/routes/twreporter/templates/image.tsx new file mode 100644 index 000000000..d535ef7c4 --- /dev/null +++ b/lib/routes/twreporter/templates/image.tsx @@ -0,0 +1,16 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageProps = { + image: string; + description?: string; + caption?: string; +}; + +const ImageBlock = ({ image, description, caption }: ImageProps) => ( + <> + {description} +
    {caption}
    + +); + +export const renderImage = (props: ImageProps): string => renderToString(); diff --git a/lib/routes/twreporter/templates/youtube.art b/lib/routes/twreporter/templates/youtube.art deleted file mode 100644 index 5e2aefb31..000000000 --- a/lib/routes/twreporter/templates/youtube.art +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/routes/twreporter/templates/youtube.tsx b/lib/routes/twreporter/templates/youtube.tsx new file mode 100644 index 000000000..d0558ef8c --- /dev/null +++ b/lib/routes/twreporter/templates/youtube.tsx @@ -0,0 +1,11 @@ +import { renderToString } from 'hono/jsx/dom/server'; + +type YoutubeProps = { + video: string; +}; + +const YoutubeEmbed = ({ video }: YoutubeProps) => ( + +); + +export const renderYouTube = (props: YoutubeProps): string => renderToString(); diff --git a/lib/routes/txrjy/fornumtopic.ts b/lib/routes/txrjy/fornumtopic.tsx similarity index 66% rename from lib/routes/txrjy/fornumtopic.ts rename to lib/routes/txrjy/fornumtopic.tsx index 110329840..18ecdd689 100644 --- a/lib/routes/txrjy/fornumtopic.ts +++ b/lib/routes/txrjy/fornumtopic.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const rootUrl = 'https://www.txrjy.com'; @@ -64,24 +63,31 @@ async function handler(ctx) { item.description = content('div.c_table') .toArray() - .map((item) => - art(path.join(__dirname, 'templates/fornumtopic.art'), { - content: content(item) - .find('td.t_f') - .find('div.a_pr') - .remove() - .end() - .html() - ?.replaceAll(/()/g, '$1$2') - .replaceAll(/()/g, '$1src$2'), - pattl: content(item) - .find('div.pattl') - .html() - ?.replaceAll(/()/g, '$1$2') - .replaceAll(/()/g, '$1src$2'), - author: content(item).find('a.xw1').text().trim(), - }) - ) + .map((item) => { + const contentHtml = content(item) + .find('td.t_f') + .find('div.a_pr') + .remove() + .end() + .html() + ?.replaceAll(/()/g, '$1$2') + .replaceAll(/()/g, '$1src$2'); + const pattlHtml = content(item) + .find('div.pattl') + .html() + ?.replaceAll(/()/g, '$1$2') + .replaceAll(/()/g, '$1src$2'); + const author = content(item).find('a.xw1').text().trim(); + + return renderToString( + <> +

    {author}

    + {contentHtml ? raw(contentHtml) : null} + {pattlHtml ? raw(pattlHtml) : null} +
    + + ); + }) .join('\n'); return item; diff --git a/lib/routes/txrjy/templates/fornumtopic.art b/lib/routes/txrjy/templates/fornumtopic.art deleted file mode 100644 index f75b8b695..000000000 --- a/lib/routes/txrjy/templates/fornumtopic.art +++ /dev/null @@ -1,6 +0,0 @@ -

    {{ author }}

    -{{@ content }} -{{ if pattl }} -{{@ pattl }} -{{ /if }} -
    diff --git a/lib/routes/udn/breaking-news.ts b/lib/routes/udn/breaking-news.tsx similarity index 92% rename from lib/routes/udn/breaking-news.ts rename to lib/routes/udn/breaking-news.tsx index a8a2cb104..871602aaf 100644 --- a/lib/routes/udn/breaking-news.ts +++ b/lib/routes/udn/breaking-news.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -73,10 +71,14 @@ async function handler(ctx) { let description = ''; if (data.image) { - description += art(path.join(__dirname, 'templates/figure.art'), { - src: data.image.contentUrl, - alt: data.image.name, - }); + description += renderToString( +
    + + {data.image.name} + + {data.image.name} +
    + ); } if (content.length) { description += content.html(); diff --git a/lib/routes/udn/templates/figure.art b/lib/routes/udn/templates/figure.art deleted file mode 100644 index b6b596dff..000000000 --- a/lib/routes/udn/templates/figure.art +++ /dev/null @@ -1,4 +0,0 @@ -
    -{{ -{{ alt }} -
    \ No newline at end of file diff --git a/lib/routes/uptimerobot/rss.ts b/lib/routes/uptimerobot/rss.tsx similarity index 77% rename from lib/routes/uptimerobot/rss.ts rename to lib/routes/uptimerobot/rss.tsx index ad7d4ff1a..6b1afae07 100644 --- a/lib/routes/uptimerobot/rss.ts +++ b/lib/routes/uptimerobot/rss.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import dayjs from 'dayjs'; +import { renderToString } from 'hono/jsx/dom/server'; import Parser from 'rss-parser'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import { fallback, queryToBoolean } from '@/utils/readable-social'; -import { art } from '@/utils/render'; const titleRegex = /(.+)\s+is\s+([A-Z]+)\s+\((.+)\)/; @@ -128,19 +126,43 @@ async function handler(ctx) { throw new InvalidParameterError('Unexpected status, please open an issue.'); } - const desc = art(path.join(__dirname, 'templates/rss.art'), { - status, - link, - id: showID ? id : null, - duration: formatTime(duration), - uptime: formatTime(monitor.uptime), - downtime: formatTime(monitor.downtime), - uptime_ratio: Number(monitor.uptimeRatio()).toLocaleString(undefined, { - style: 'percent', - minimumFractionDigits: 2, - }), - details: item.content, - }); + const desc = renderToString( + <> + Already {status} for {formatTime(duration)} +
    +
    + {showID && id ? ( + <> + Monitor ID:{' '} + {link ? ( + + {id} + + ) : ( + id + )} +
    +
    + + ) : null} + Uptime: {formatTime(monitor.uptime)} +
    + Downtime: {formatTime(monitor.downtime)} +
    + Availability:{' '} + {Number(monitor.uptimeRatio()).toLocaleString(undefined, { + style: 'percent', + minimumFractionDigits: 2, + })} + {item.content && item.content.trim() !== 'Alert Details:' ? ( + <> +
    +
    + {item.content} + + ) : null} + + ); return { ...item, diff --git a/lib/routes/uptimerobot/templates/rss.art b/lib/routes/uptimerobot/templates/rss.art deleted file mode 100644 index 856b346c0..000000000 --- a/lib/routes/uptimerobot/templates/rss.art +++ /dev/null @@ -1,20 +0,0 @@ -Already {{ status }} for {{ duration }} -

    -{{ if id }} - Monitor ID: - {{ if link }} - {{ id }} - {{ else }} - {{ id }} - {{ /if }} -

    -{{ /if }} -Uptime: {{ uptime }} -
    -Downtime: {{ downtime }} -
    -Availability: {{ uptime_ratio }} -{{ if details && details.trim() !== 'Alert Details:' }} -

    - {{ details }} -{{ /if }} diff --git a/lib/routes/urbandictionary/random.ts b/lib/routes/urbandictionary/random.tsx similarity index 62% rename from lib/routes/urbandictionary/random.ts rename to lib/routes/urbandictionary/random.tsx index 405ba1d69..52c54c887 100644 --- a/lib/routes/urbandictionary/random.ts +++ b/lib/routes/urbandictionary/random.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/random', @@ -35,7 +34,27 @@ async function handler() { const items = data.list.map((item) => ({ title: item.word, - description: art(path.join(__dirname, 'templates/definition.art'), { item }), + description: renderToString( + <> + {item.definition ? ( + <> + {item.definition} +
    + + ) : null} + {item.example ? ( + <> + {item.example} +
    + + ) : null} + {item.author ? ( + <> + by {item.author} + + ) : null} + + ), link: `${baseUrl}/define.php?term=${item.word}`, guid: item.permalink, pubDate: parseDate(item.written_on), diff --git a/lib/routes/urbandictionary/templates/definition.art b/lib/routes/urbandictionary/templates/definition.art deleted file mode 100644 index 9b3285f08..000000000 --- a/lib/routes/urbandictionary/templates/definition.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if item.definition }} - {{ item.definition }}
    -{{ /if }} - -{{ if item.example }} - {{ item.example }}
    -{{ /if }} - -{{ if item.author }} - by {{ item.author }} -{{ /if }} diff --git a/lib/routes/utgd/templates/description.art b/lib/routes/utgd/templates/description.art deleted file mode 100644 index 8ce3b4866..000000000 --- a/lib/routes/utgd/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if image }} - -
    -{{ /if }} -{{ if membership }} -
    -UNTAG Premium -{{ /if }} -{{ if description }} -{{@ description }} -{{ /if }} diff --git a/lib/routes/utgd/utils.ts b/lib/routes/utgd/utils.tsx similarity index 59% rename from lib/routes/utgd/utils.ts rename to lib/routes/utgd/utils.tsx index 85223d93a..94fb3f6ad 100644 --- a/lib/routes/utgd/utils.ts +++ b/lib/routes/utgd/utils.tsx @@ -1,11 +1,10 @@ -import path from 'node:path'; - +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import MarkdownIt from 'markdown-it'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const md = MarkdownIt({ @@ -29,13 +28,28 @@ export const parseArticle = (item) => cache.tryGet(`untag-${item.id}`, async () => { const data = await ofetch(`${apiRootUrl}/api/v2/article/${item.id}/`); - item.description = art(path.join(__dirname, 'templates/description.art'), { - membership: data.article_for_membership, - image: data.article_image, - description: md.render(data.article_content), - }); + item.description = renderDescription(data.article_image, data.article_for_membership, md.render(data.article_content)); item.category = [...data.article_category.map((c) => c.category_name), ...data.article_tag.map((t) => t.tag_name)]; return item; }); + +const renderDescription = (image: string | undefined, membership: boolean, description: string): string => + renderToString( + <> + {image ? ( + <> + +
    + + ) : null} + {membership ? ( + <> +
    + UNTAG Premium + + ) : null} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/vcb-s/category.ts b/lib/routes/vcb-s/category.ts index 83d90138f..bfd8880b9 100644 --- a/lib/routes/vcb-s/category.ts +++ b/lib/routes/vcb-s/category.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/post'; const rootUrl = 'https://vcb-s.com'; const cateAPIUrl = `${rootUrl}/wp-json/wp/v2/categories`; @@ -59,7 +58,7 @@ async function handler(ctx) { const data = response.data; const items = data.map((item) => { - const description = art(path.join(__dirname, 'templates/post.art'), { + const description = renderDescription({ post: item.content.rendered.replaceAll(/
    (.*?)<\/pre>/gs, '
    $1
    ').replaceAll(/(.*?)<\/div>/gs, '
    $1
    '), medias: item._embedded['wp:featuredmedia'], }); diff --git a/lib/routes/vcb-s/templates/post.art b/lib/routes/vcb-s/templates/post.art deleted file mode 100644 index 2f026e84c..000000000 --- a/lib/routes/vcb-s/templates/post.art +++ /dev/null @@ -1,9 +0,0 @@ - -{{ if medias }} -{{ each medias media }} -
    -{{ /each }} -{{ /if }} -{{ if post }} -{{@ post }} -{{ /if }} diff --git a/lib/routes/vcb-s/templates/post.tsx b/lib/routes/vcb-s/templates/post.tsx new file mode 100644 index 000000000..2c1288666 --- /dev/null +++ b/lib/routes/vcb-s/templates/post.tsx @@ -0,0 +1,28 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Media = { + media_details?: { + width?: string | number; + height?: string | number; + }; + source_url?: string; +}; + +type DescriptionData = { + medias?: Media[]; + post?: string; +}; + +const VcbPostDescription = ({ medias, post }: DescriptionData) => ( + <> + {medias?.map((media) => ( +
    + +
    + ))} + {post ? raw(post) : null} + +); + +export const renderDescription = (data: DescriptionData) => renderToString(); diff --git a/lib/routes/vice/templates/article.art b/lib/routes/vice/templates/article.art deleted file mode 100644 index 0bfa7bf84..000000000 --- a/lib/routes/vice/templates/article.art +++ /dev/null @@ -1,19 +0,0 @@ -{{ if image }} -
    - {{ image.alt }} -
    {{ image.caption || image.credit || image.alt }}
    -
    -{{ /if }} - -{{ if body }} -

    {{@ body.html }}

    -{{ /if }} - -{{ if heading2 }} -
    -

    {{@ heading2.html }}

    -{{ /if }} - -{{ if oembed }} -{{@ oembed.html }} -{{ /if }} diff --git a/lib/routes/vice/topic.ts b/lib/routes/vice/topic.tsx similarity index 83% rename from lib/routes/vice/topic.ts rename to lib/routes/vice/topic.tsx index 43110228f..0c0da7f3c 100644 --- a/lib/routes/vice/topic.ts +++ b/lib/routes/vice/topic.tsx @@ -1,14 +1,31 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; -const render = (data) => art(path.join(__dirname, 'templates/article.art'), data); +const render = (data) => + renderToString( + <> + {data.image ? ( +
    + {data.image.alt} +
    {data.image.caption || data.image.credit || data.image.alt}
    +
    + ) : null} + {data.body ?

    {raw(data.body.html)}

    : null} + {data.heading2 ? ( + <> +
    +

    {raw(data.heading2.html)}

    + + ) : null} + {data.oembed ? raw(data.oembed.html) : null} + + ); export const route: Route = { path: '/topic/:topic/:language?', diff --git a/lib/routes/vimeo/category.ts b/lib/routes/vimeo/category.ts index a36ada8ab..f590fdab4 100644 --- a/lib/routes/vimeo/category.ts +++ b/lib/routes/vimeo/category.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { Route } from '@/types'; @@ -7,7 +5,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/category/:category/:staffpicks?', @@ -79,7 +78,7 @@ async function handler(ctx) { description: feedDescription, item: vimeojs.map((item) => ({ title: item.name, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ videoUrl: item.uri.replace(`/videos`, ''), vdescription: item.description || '', }), diff --git a/lib/routes/vimeo/channel.ts b/lib/routes/vimeo/channel.ts index 606632a27..8d6d169f5 100644 --- a/lib/routes/vimeo/channel.ts +++ b/lib/routes/vimeo/channel.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/channel/:channel', @@ -84,7 +83,7 @@ async function handler(ctx) { const author = item.find('.meta a').text(); return { title, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ videoUrl: item.find('.more').attr('href'), vdescription: description[index] || '', }), diff --git a/lib/routes/vimeo/templates/description.art b/lib/routes/vimeo/templates/description.art deleted file mode 100644 index 91ed43daf..000000000 --- a/lib/routes/vimeo/templates/description.art +++ /dev/null @@ -1,4 +0,0 @@ - -{{ if vdescription }} -

    {{@ vdescription }}

    -{{ /if }} diff --git a/lib/routes/vimeo/templates/description.tsx b/lib/routes/vimeo/templates/description.tsx new file mode 100644 index 000000000..3b092c953 --- /dev/null +++ b/lib/routes/vimeo/templates/description.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + videoUrl: string; + vdescription?: string; +}; + +const Description = ({ videoUrl, vdescription }: DescriptionProps) => ( + <> + + {vdescription ?

    {raw(vdescription)}

    : null} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/vimeo/usr-videos.ts b/lib/routes/vimeo/usr-videos.ts index 13fab5bea..00bb7db86 100644 --- a/lib/routes/vimeo/usr-videos.ts +++ b/lib/routes/vimeo/usr-videos.ts @@ -1,10 +1,9 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const route: Route = { path: '/user/:username/:cat?', @@ -91,7 +90,7 @@ async function handler(ctx) { return { title: picked ? item.clip.name : item.name, - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ videoUrl: picked ? item.clip.uri.replace('/videos', '') : item.uri.replace('/videos', ''), vdescription: vdescription ? vdescription.replaceAll('\n', '
    ') : '', }), diff --git a/lib/routes/visionias/templates/description-sub.art b/lib/routes/visionias/templates/description-sub.art deleted file mode 100644 index e947d6315..000000000 --- a/lib/routes/visionias/templates/description-sub.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if heading }} -

    {{ heading }}

    -{{ /if }} -{{ if articleContent }} - {{@ articleContent }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/visionias/templates/description-sub.tsx b/lib/routes/visionias/templates/description-sub.tsx new file mode 100644 index 000000000..115e3df7d --- /dev/null +++ b/lib/routes/visionias/templates/description-sub.tsx @@ -0,0 +1,16 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionSubProps = { + heading?: string; + articleContent?: string; +}; + +const DescriptionSub = ({ heading, articleContent }: DescriptionSubProps) => ( + <> + {heading ?

    {heading}

    : null} + {articleContent ? raw(articleContent) : null} + +); + +export const renderDescriptionSub = (props: DescriptionSubProps): string => renderToString(); diff --git a/lib/routes/visionias/templates/description.art b/lib/routes/visionias/templates/description.art deleted file mode 100644 index 2f92e6a1c..000000000 --- a/lib/routes/visionias/templates/description.art +++ /dev/null @@ -1,12 +0,0 @@ -{{ if heading }} -

    {{ heading }}

    -{{ /if }} -{{ if subItems }} - {{ each subItems item }} - {{ if item?.description }} - {{@ item.description }} - {{ /if }} - {{ /each }} -{{else}} - {{@ articleContent }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/visionias/templates/description.tsx b/lib/routes/visionias/templates/description.tsx new file mode 100644 index 000000000..b3ae2e271 --- /dev/null +++ b/lib/routes/visionias/templates/description.tsx @@ -0,0 +1,17 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type DescriptionProps = { + heading?: string; + subItems?: Array<{ description?: string }>; + articleContent?: string; +}; + +const Description = ({ heading, subItems, articleContent }: DescriptionProps) => ( + <> + {heading ?

    {heading}

    : null} + {subItems ? <>{subItems.map((item, index) => (item?.description ? {raw(item.description)} : null))} : <>{articleContent ? raw(articleContent) : null}} + +); + +export const renderDescription = (props: DescriptionProps): string => renderToString(); diff --git a/lib/routes/visionias/utils.ts b/lib/routes/visionias/utils.ts index 411e489c2..ea0d1d133 100644 --- a/lib/routes/visionias/utils.ts +++ b/lib/routes/visionias/utils.ts @@ -1,12 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import type { DataItem } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; +import { renderDescriptionSub } from './templates/description-sub'; export const baseUrl = 'https://visionias.in'; @@ -39,7 +39,7 @@ export async function extractNews(item, selector) { ?.nextAll('li') .toArray() .map((tag) => $$(tag).text()); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ heading: title, articleContent: htmlContent, }); @@ -55,7 +55,7 @@ export async function extractNews(item, selector) { return items; } else if (sections.length === 0) { const htmlContent = extractArticle(mainGroup.html()); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ heading, articleContent: htmlContent, }); @@ -73,13 +73,13 @@ export async function extractNews(item, selector) { const mainDiv = $$(element); const title = mainDiv.find('a > div > h2').text().trim(); const htmlContent = extractArticle(mainDiv.html(), 'div.ck-content'); - const description = art(path.join(__dirname, 'templates/description-sub.art'), { + const description = renderDescriptionSub({ heading: title, articleContent: htmlContent, }); return { description }; }); - const description = art(path.join(__dirname, 'templates/description.art'), { + const description = renderDescription({ heading, subItems: items, }); diff --git a/lib/routes/wainao/templates/description.art b/lib/routes/wainao/templates/description.art deleted file mode 100644 index 1cb5fc8fa..000000000 --- a/lib/routes/wainao/templates/description.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if elements.length > 0 }} - {{ each elements element }} - {{ if element.type === 'text' }} -

    {{ element.content }}

    - {{ else if element.type === 'raw_html' }} - {{@ element.content }} - {{ /if }} - {{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/wainao/topics.ts b/lib/routes/wainao/topics.tsx similarity index 91% rename from lib/routes/wainao/topics.ts rename to lib/routes/wainao/topics.tsx index 2aba40e01..5ae9aaacb 100644 --- a/lib/routes/wainao/topics.ts +++ b/lib/routes/wainao/topics.tsx @@ -1,14 +1,30 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +const renderDescription = (elements) => + renderToString( + <> + {elements?.length + ? elements.map((element) => { + if (element.type === 'text') { + return

    {element.content}

    ; + } + if (element.type === 'raw_html') { + return <>{raw(element.content ?? '')}; + } + return null; + }) + : null} + + ); export const handler = async (ctx: Context): Promise => { const { id = 'hotspot' } = ctx.req.param(); @@ -40,9 +56,7 @@ export const handler = async (ctx: Context): Promise => { .slice(0, limit) .map((item): DataItem => { const title: string = item.headlines.basic; - const description: string = art(path.join(__dirname, 'templates/description.art'), { - elements: item.content_elements, - }); + const description: string = renderDescription(item.content_elements); const pubDate: number | string = item.publish_date; const linkUrl: string | undefined = item.website_url; const categories: string[] = [item.taxonomy?.primary_section?.name].filter(Boolean); diff --git a/lib/routes/wallpaperhub/index.ts b/lib/routes/wallpaperhub/index.tsx similarity index 76% rename from lib/routes/wallpaperhub/index.ts rename to lib/routes/wallpaperhub/index.tsx index a7cc669b2..7093c47fe 100644 --- a/lib/routes/wallpaperhub/index.ts +++ b/lib/routes/wallpaperhub/index.tsx @@ -1,9 +1,8 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/', @@ -28,10 +27,12 @@ async function handler() { const list = response.data.entities.map((item) => ({ title: item.entity.title, - description: art(path.join(__dirname, 'templates/description.art'), { - description: item.entity.description, - img: item.entity.variations[0].resolutions[0].url || item.entity.thumbnail, - }), + description: renderToString( + <> +

    {item.entity.description}

    + + + ), pubDate: parseDate(item.entity.created), link: `https://wallpaperhub.app/wallpapers/${item.entity.id}`, })); diff --git a/lib/routes/wallpaperhub/templates/description.art b/lib/routes/wallpaperhub/templates/description.art deleted file mode 100644 index 8afb4dab3..000000000 --- a/lib/routes/wallpaperhub/templates/description.art +++ /dev/null @@ -1 +0,0 @@ -

    {{ description }}

    diff --git a/lib/routes/wallstreetcn/live.ts b/lib/routes/wallstreetcn/live.tsx similarity index 83% rename from lib/routes/wallstreetcn/live.ts rename to lib/routes/wallstreetcn/live.tsx index 3ccdc4a47..bad579754 100644 --- a/lib/routes/wallstreetcn/live.ts +++ b/lib/routes/wallstreetcn/live.tsx @@ -1,9 +1,9 @@ -import path from 'node:path'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const titles = { global: '要闻', @@ -63,11 +63,13 @@ async function handler(ctx) { title: item.title || item.content_text, pubDate: parseDate(item.display_time * 1000), author: item.author?.display_name ?? '', - description: art(path.join(__dirname, 'templates/description.art'), { - description: item.content, - more: item.content_more, - images: item.images, - }), + description: renderToString( + <> + {item.content ? raw(item.content) : null} + {item.content_more ? raw(item.content_more) : null} + {item.images?.length ? item.images.map((image) => ) : null} + + ), })); return { diff --git a/lib/routes/wallstreetcn/templates/description.art b/lib/routes/wallstreetcn/templates/description.art deleted file mode 100644 index ee1f9e6f2..000000000 --- a/lib/routes/wallstreetcn/templates/description.art +++ /dev/null @@ -1,11 +0,0 @@ -{{ if description }} -{{@ description }} -{{ /if }} -{{ if more }} -{{@ more }} -{{ /if }} -{{ if images }} -{{ each images image }} - -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/warthunder/news.ts b/lib/routes/warthunder/news.tsx similarity index 85% rename from lib/routes/warthunder/news.ts rename to lib/routes/warthunder/news.tsx index e92685166..3a452a9c5 100644 --- a/lib/routes/warthunder/news.ts +++ b/lib/routes/warthunder/news.tsx @@ -1,14 +1,20 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; -const renderDescription = (desc) => art(path.join(__dirname, 'templates/description.art'), desc); +const renderDescription = (desc: { description?: string; imglink?: string }) => renderToString(); + +const WarthunderDescription = ({ description, imglink }: { description?: string; imglink?: string }) => ( + <> + {description} +
    + + +); export const route: Route = { path: '/news', diff --git a/lib/routes/warthunder/templates/description.art b/lib/routes/warthunder/templates/description.art deleted file mode 100644 index 48f298238..000000000 --- a/lib/routes/warthunder/templates/description.art +++ /dev/null @@ -1 +0,0 @@ -{{description}}
    \ No newline at end of file diff --git a/lib/routes/washingtonpost/app.ts b/lib/routes/washingtonpost/app.ts deleted file mode 100644 index dc443a9aa..000000000 --- a/lib/routes/washingtonpost/app.ts +++ /dev/null @@ -1,118 +0,0 @@ -import path from 'node:path'; - -import dayjs from 'dayjs'; -import advancedFormat from 'dayjs/plugin/advancedFormat.js'; -import timezone from 'dayjs/plugin/timezone.js'; -import utc from 'dayjs/plugin/utc.js'; -import { FetchError } from 'ofetch'; - -import type { Route } from '@/types'; -import cache from '@/utils/cache'; -import got from '@/utils/got'; -import { art } from '@/utils/render'; - -export const route: Route = { - path: '/app/:category{.+}?', - categories: ['traditional-media'], - example: '/washingtonpost/app/national', - parameters: { - category: 'Category from the path of the URL of the corresponding site, see below', - }, - features: { - requirePuppeteer: false, - antiCrawler: false, - supportBT: false, - supportPodcast: false, - supportScihub: false, - }, - name: 'App', - maintainers: ['quiniapiezoelectricity'], - radar: [ - { - source: ['www.washingtonpost.com/:category'], - target: '/app/:category', - }, - ], - handler, - description: `::: tip -For example, the category for https://www.washingtonpost.com/national/investigations would be /national/investigations. -:::`, -}; - -function handleDuplicates(array) { - const objects = {}; - for (const obj of array) { - objects[obj.id] = objects[obj.id] ? Object.assign(objects[obj.id], obj) : obj; - } - return Object.values(objects); -} - -async function handler(ctx) { - const category = ctx.req.param('category') ?? ''; - const headers = { - Accept: '*/*', - Connection: 'keep-alive', - 'User-Agent': 'Classic/6.70.0', - }; - dayjs.extend(utc); - dayjs.extend(timezone); - dayjs.extend(advancedFormat); - art.defaults.imports.dayjs = dayjs; - - const url = `https://jsonapp1.washingtonpost.com/fusion_prod/v2/${category}`; - const response = await got.get(url, { headers }); - const title = response.data.tracking.page_title.includes('Washington Post') ? response.data.tracking.page_title : `The Washington Post - ${response.data.tracking.page_title}`; - const link = 'https://washingtonpost.com' + response.data.tracking.page_path; - const mains = response.data.regions[0].items.filter((item) => item.items); - const list = mains.flatMap((main) => - main.items[0].items - .filter((item) => item.is_from_feed === true) - .map((item) => { - const object = { - id: item.id, - title: item.headline.text, - link: item.link.url, - pubDate: item.link.display_date, - updated: item.link.last_modified, - }; - if (item.blurbs?.items[0]?.text) { - object.description = item.blurbs?.items[0]?.text; - } - return object; - }) - ); - const feed = handleDuplicates(list); - const items = await Promise.all( - feed.map((item) => - cache.tryGet(item.link, async () => { - let response; - try { - response = await got(`https://rainbowapi-a.wpdigital.net/rainbow-data-service/rainbow/content-by-url.json?followLinks=false&url=${item.link}`, { headers }); - } catch (error) { - if (error instanceof FetchError && error.statusCode === 415) { - // Interactive or podcast contents will return 415 Unsupported Media Type. Keep calm and carry on. - return item; - } else { - throw error; - } - } - item.title = response.data.title ?? item.title; - item.author = - response.data.items - .filter((entry) => entry.type === 'byline') - ?.flatMap((entry) => entry.authors.map((author) => author.name)) - ?.join(', ') ?? ''; - item.description = art(path.join(__dirname, 'templates/description.art'), { - content: response.data.items, - }); - return item; - }) - ) - ); - - return { - title, - link, - item: items, - }; -} diff --git a/lib/routes/washingtonpost/app.tsx b/lib/routes/washingtonpost/app.tsx new file mode 100644 index 000000000..7fca7dbd1 --- /dev/null +++ b/lib/routes/washingtonpost/app.tsx @@ -0,0 +1,220 @@ +import dayjs from 'dayjs'; +import advancedFormat from 'dayjs/plugin/advancedFormat.js'; +import timezone from 'dayjs/plugin/timezone.js'; +import utc from 'dayjs/plugin/utc.js'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; +import { FetchError } from 'ofetch'; + +import type { Route } from '@/types'; +import cache from '@/utils/cache'; +import got from '@/utils/got'; + +export const route: Route = { + path: '/app/:category{.+}?', + categories: ['traditional-media'], + example: '/washingtonpost/app/national', + parameters: { + category: 'Category from the path of the URL of the corresponding site, see below', + }, + features: { + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + name: 'App', + maintainers: ['quiniapiezoelectricity'], + radar: [ + { + source: ['www.washingtonpost.com/:category'], + target: '/app/:category', + }, + ], + handler, + description: `::: tip +For example, the category for https://www.washingtonpost.com/national/investigations would be /national/investigations. +:::`, +}; + +function handleDuplicates(array) { + const objects = {}; + for (const obj of array) { + objects[obj.id] = objects[obj.id] ? Object.assign(objects[obj.id], obj) : obj; + } + return Object.values(objects); +} + +async function handler(ctx) { + const category = ctx.req.param('category') ?? ''; + const headers = { + Accept: '*/*', + Connection: 'keep-alive', + 'User-Agent': 'Classic/6.70.0', + }; + dayjs.extend(utc); + dayjs.extend(timezone); + dayjs.extend(advancedFormat); + + const url = `https://jsonapp1.washingtonpost.com/fusion_prod/v2/${category}`; + const response = await got.get(url, { headers }); + const title = response.data.tracking.page_title.includes('Washington Post') ? response.data.tracking.page_title : `The Washington Post - ${response.data.tracking.page_title}`; + const link = 'https://washingtonpost.com' + response.data.tracking.page_path; + const mains = response.data.regions[0].items.filter((item) => item.items); + const list = mains.flatMap((main) => + main.items[0].items + .filter((item) => item.is_from_feed === true) + .map((item) => { + const object = { + id: item.id, + title: item.headline.text, + link: item.link.url, + pubDate: item.link.display_date, + updated: item.link.last_modified, + }; + if (item.blurbs?.items[0]?.text) { + object.description = item.blurbs?.items[0]?.text; + } + return object; + }) + ); + const feed = handleDuplicates(list); + const items = await Promise.all( + feed.map((item) => + cache.tryGet(item.link, async () => { + let response; + try { + response = await got(`https://rainbowapi-a.wpdigital.net/rainbow-data-service/rainbow/content-by-url.json?followLinks=false&url=${item.link}`, { headers }); + } catch (error) { + if (error instanceof FetchError && error.statusCode === 415) { + // Interactive or podcast contents will return 415 Unsupported Media Type. Keep calm and carry on. + return item; + } else { + throw error; + } + } + item.title = response.data.title ?? item.title; + item.author = + response.data.items + .filter((entry) => entry.type === 'byline') + ?.flatMap((entry) => entry.authors.map((author) => author.name)) + ?.join(', ') ?? ''; + item.description = renderDescription(response.data.items); + return item; + }) + ) + ); + + return { + title, + link, + item: items, + }; +} + +const renderDescription = (content): string => + renderToString( + <> + {content?.map((entry, index) => { + if (!entry) { + return null; + } + + if (entry.type === 'title' && entry.subtype !== 'h1') { + const TitleTag = (entry.subtype || 'h2') as keyof JSX.IntrinsicElements; + return {entry.mime === 'text/html' ? raw(entry.content) : entry.content}; + } + + if (entry.type === 'sanitized_html') { + if (entry.subtype === 'paragraph') { + return ( +

    + {entry.mime === 'text/html' ? raw(entry.content) : entry.content} + {entry.oembed ? raw(entry.oembed) : null} +

    + ); + } + + if (entry.subtype === 'subhead') { + const SubheadTag = `h${entry.subhead_level || 4}` as keyof JSX.IntrinsicElements; + return ( + + {entry.mime === 'text/html' ? raw(entry.content) : entry.content} + {entry.oembed ? raw(entry.oembed) : null} + + ); + } + } + + if (entry.type === 'deck') { + return ( +
    +

    {entry.mime === 'text/html' ? raw(entry.content) : entry.content}

    +
    + ); + } + + if (entry.type === 'image') { + return ( +
    + {entry.blurb} +
    {entry.fullcaption}
    +
    + ); + } + + if (entry.type === 'video') { + if (entry.content?.html) { + return {raw(entry.content.html)}; + } + + if (entry.mediaURL) { + return ( +
    + + {entry.fullcaption ?
    {entry.fullcaption}
    : null} +
    + ); + } + } + + if (entry.type === 'list') { + const ListTag = entry.subtype === 'ordered' ? 'ol' : 'ul'; + return ( + + {(entry.content ?? []).map((listItem, itemIndex) => ( +
  • {entry.mime === 'text/html' ? raw(listItem) : listItem}
  • + ))} +
    + ); + } + + if (entry.type === 'divider') { + return ( + +
    +
    +
    +
    + ); + } + + if (entry.type === 'byline' && (entry.subtype === 'live-update' || entry.subtype === 'live-reporter-insight')) { + return ( +

    + {entry.mime === 'text/html' ? raw(entry.content) : entry.content} +

    + ); + } + + if (entry.type === 'date' && entry.subtype === 'live-update') { + return entry.content ? {dayjs.tz(entry.content, 'America/New_York').locale('en').format('dddd, MMMM D, YYYY h:mm A z')} : null; + } + + return null; + })} + + ); diff --git a/lib/routes/washingtonpost/templates/description.art b/lib/routes/washingtonpost/templates/description.art deleted file mode 100644 index fc7382329..000000000 --- a/lib/routes/washingtonpost/templates/description.art +++ /dev/null @@ -1,59 +0,0 @@ -{{ if content }} -{{ each content }} - {{ if $value.type == 'title' && $value.subtype != 'h1'}} - <{{ if $value.subtype }}{{ $value.subtype }}{{ else }}h2{{ /if }}> - {{ if $value.mime == 'text/html' }}{{@ $value.content }}{{ /if }} - {{ if $value.mime == 'text/plain' }}{{ $value.content }}{{ /if }} - - {{ /if }} - {{ if $value.type == 'sanitized_html' }} - {{ if $value.subtype == 'paragraph' }}

    {{ else if $value.subtype == 'subhead' }}{{ /if }} - {{ if $value.mime == 'text/html' }}{{@ $value.content }}{{ /if }} - {{ if $value.mime == 'text/plain' }}{{ $value.content }}{{ /if }} - {{ if $value.oembed }}{{@ $value.oembed }}{{ /if }} - {{ if $value.subtype == 'paragraph' }}

    {{ else if $value.subtype == 'subhead' }}{{ /if }} - {{ /if }} - {{ if $value.type == 'deck' }} -

    - {{ if $value.mime == 'text/html' }}{{@ $value.content }}{{ /if }} - {{ if $value.mime == 'text/plain' }}{{ $value.content }}{{ /if }} -

    - {{ /if }} - {{ if $value.type == 'image' }} -
    {{ $value.blurb }}
    {{ $value.fullcaption }}
    - {{ /if }} - {{ if $value.type == 'video' }} - {{ if $value.content && $value.content.html }}{{@ $value.content.html }} - {{ else if $value.mediaURL }} -
    - - {{ if $value.fullcaption }}
    {{ $value.fullcaption }}
    {{ /if }} -
    - {{ /if }} - {{ /if }} - {{ if $value.type == 'list' }} - {{ if $value.subtype == 'ordered' }}
      {{ else }}
        {{ /if }} - {{ if $value.mime == 'text/html' }}{{ each $value.content }}
      • {{@ $value }}
      • {{ /each }}{{ /if }} - {{ if $value.mime == 'text/plain' }}{{ each $value.content }}
      • {{ $value }}
      • {{ /each }}{{ /if }} - {{ if $value.subtype == 'ordered' }}
    {{ else }}{{ /if }} - {{ /if }} - {{ if $value.type == 'divider' }}


    {{ /if }} - {{ if $value.type == 'byline' }} - {{ if $value.subtype == 'live-update' || $value.subtype == 'live-reporter-insight' }} -

    - {{ if $value.mime == 'text/html' }}{{@ $value.content }}{{ /if }} - {{ if $value.mime == 'text/plain' }}{{ $value.content }}{{ /if }} -

    - {{ /if}} - {{ /if }} - {{ if $value.type == 'date' }} - {{ if $value.subtype == 'live-update'}} - {{ if $value.content }}{{ $imports.dayjs.tz($value.content,"America/New_York").locale('en').format('dddd, MMMM D, YYYY h:mm A z') }}{{ /if }} - {{ /if }} - {{ /if }} -{{ /each }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/wdfxw/bookfree.ts b/lib/routes/wdfxw/bookfree.tsx similarity index 95% rename from lib/routes/wdfxw/bookfree.ts rename to lib/routes/wdfxw/bookfree.tsx index 26ebe02b0..f6a6e2b53 100644 --- a/lib/routes/wdfxw/bookfree.ts +++ b/lib/routes/wdfxw/bookfree.tsx @@ -1,16 +1,14 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const { id } = ctx.req.param(); @@ -34,16 +32,20 @@ export const handler = async (ctx: Context): Promise => { const title: string = $aEl.attr('title') ?? $aEl.text(); const image: string | undefined = $el.find('div.img img').attr('data-original') ?? $el.find('div.img img').attr('src'); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - images: image - ? [ - { - src: image, - alt: title, - }, - ] - : undefined, - }); + const description: string | undefined = renderToString( + + ); const linkUrl: string | undefined = $aEl.attr('href'); const processedItem: DataItem = { @@ -528,3 +530,20 @@ export const route: Route = { ], view: ViewType.Articles, }; + +type WdfxwImage = { + src?: string; + alt?: string; +}; + +const WdfxwDescription = ({ images }: { images?: WdfxwImage[] }) => ( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + +); diff --git a/lib/routes/wdfxw/templates/description.art b/lib/routes/wdfxw/templates/description.art deleted file mode 100644 index b6263ea05..000000000 --- a/lib/routes/wdfxw/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} diff --git a/lib/routes/weibo/search/hot.ts b/lib/routes/weibo/search/hot.tsx similarity index 85% rename from lib/routes/weibo/search/hot.ts rename to lib/routes/weibo/search/hot.tsx index b39308b99..f2b1042ab 100644 --- a/lib/routes/weibo/search/hot.ts +++ b/lib/routes/weibo/search/hot.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; import weiboUtils from '../utils'; @@ -146,6 +145,34 @@ function seekContent(clist) { const $ = load('
    '); const stub = $('#wbcontent'); + const renderDigest = ({ author, msg, link, postinfo, pics }) => + renderToString( + <> + +
    {msg ? raw(msg) : null}
    + {pics.length ? ( + <> +
    +
    + {pics.map((pic) => ( + + + + ))} +
    + + ) : null} +
    + + ); + // To for..of per reviewers comment // Need to find one clist with 'type ==9' for (const curitem of clist) { @@ -160,7 +187,7 @@ function seekContent(clist) { } else { curcontent('img').remove(); } - const section = art(path.join(__dirname, 'template/digest.art'), { + const section = renderDigest({ author: { link: curitem.mblog.user.profile_url, name: curitem.mblog.user.screen_name, @@ -168,7 +195,6 @@ function seekContent(clist) { msg: curcontent.html(), link: curitem.scheme, postinfo: curitem.mblog.created_at, - picnum: wpic === 'true' ? curitem.mblog.pic_num : 0, pics: wpic === 'true' && curitem.mblog.pic_num > 0 ? curitem.mblog.pics.map((item) => { diff --git a/lib/routes/weibo/search/template/digest.art b/lib/routes/weibo/search/template/digest.art deleted file mode 100644 index 065bd12f6..000000000 --- a/lib/routes/weibo/search/template/digest.art +++ /dev/null @@ -1,17 +0,0 @@ - -
    -{{@ msg }} -
    - -{{if picnum > 0 }} -
    -
    - {{each pics}} - - {{/each}} -
    -{{/if}} -
    diff --git a/lib/routes/wellcee/rent.ts b/lib/routes/wellcee/rent.tsx similarity index 71% rename from lib/routes/wellcee/rent.ts rename to lib/routes/wellcee/rent.tsx index 612f40a49..c7b00f519 100644 --- a/lib/routes/wellcee/rent.ts +++ b/lib/routes/wellcee/rent.tsx @@ -1,17 +1,44 @@ -import path from 'node:path'; - import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import type { Route } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import type { District, House } from './types'; import { baseUrl, getCitys, getDistricts } from './utils'; -const render = (data) => art(path.join(__dirname, 'templates/house.art'), data); +const render = (item: House) => + renderToString( + <> + 租金: {item.rent} +
    + {item.dailyRent ? ( + <> + 日租: {item.dailyRent} +
    + + ) : null} +
    + {item.video ? ( + <> + +
    + + ) : null} + {item.imgs?.length + ? item.imgs.map((img) => ( + <> + +
    + + )) + : null} + + ); export const route: Route = { path: '/rent/:city/:district?', @@ -63,7 +90,7 @@ async function handler(ctx: Context) { const items = (response.data.list as House[]).map((item) => ({ title: item.address, link: `${baseUrl}/rent-apartment/${item.id}`, - description: render({ item }), + description: render(item), pubDate: parseDate(item.loginTime, 'X'), author: item.userInfo.name, category: [...item.tags, ...item.typeTags], diff --git a/lib/routes/wellcee/templates/house.art b/lib/routes/wellcee/templates/house.art deleted file mode 100644 index 2fb3b7379..000000000 --- a/lib/routes/wellcee/templates/house.art +++ /dev/null @@ -1,18 +0,0 @@ -租金: {{ item.rent }}
    -{{ if item.dailyRent }} -日租: {{ item.dailyRent }}
    -{{ /if }} -
    - -{{ if item.video }} - -
    -{{ /if }} - -{{ if item.imgs }} - {{ each item.imgs img }} -
    - {{ /each }} -{{ /if }} diff --git a/lib/routes/whu/news.ts b/lib/routes/whu/news.ts index dd2c9461c..66bd1c861 100644 --- a/lib/routes/whu/news.ts +++ b/lib/routes/whu/news.ts @@ -1,13 +1,11 @@ -import path from 'node:path'; - 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 { art } from '@/utils/render'; +import { renderDescription } from './templates/description'; import { domain, getMeta, processItems, processMeta } from './util'; export const route: Route = { @@ -70,7 +68,7 @@ async function handler(ctx) { title: item.prop('title') ?? item.find('h4.eclips').text(), link: new URL(item.prop('href'), rootUrl).href, pubDate: parseDate(item.find('time').text(), ['YYYY.MM.DD', 'DDYYYY.MM']), - description: art(path.join(__dirname, 'templates/description.art'), { + description: renderDescription({ description: item.find('div.txt p').html(), image: image.prop('src') ? { diff --git a/lib/routes/whu/templates/description.art b/lib/routes/whu/templates/description.art deleted file mode 100644 index 2de99c292..000000000 --- a/lib/routes/whu/templates/description.art +++ /dev/null @@ -1,39 +0,0 @@ -{{ if description }} - {{@ description }} -{{ /if }} - -{{ if image }} -
    - {{ -
    -{{ /if }} - -{{ if video }} - -{{ /if }} - -{{ if attachments && attachments.length > 0 }} - 附件 - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/whu/templates/description.tsx b/lib/routes/whu/templates/description.tsx new file mode 100644 index 000000000..729e37837 --- /dev/null +++ b/lib/routes/whu/templates/description.tsx @@ -0,0 +1,55 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type ImageData = { + src: string; + alt?: string; + width?: string | number; +}; + +type VideoData = { + src: string; + width?: string | number; + height?: string | number; +}; + +type Attachment = { + link: string; + title: string; +}; + +type DescriptionData = { + description?: string; + image?: ImageData; + video?: VideoData; + attachments?: Attachment[]; +}; + +export const renderDescription = ({ description, image, video, attachments }: DescriptionData): string => + renderToString( + <> + {description ? raw(description) : null} + {image ? ( +
    + {image.alt} +
    + ) : null} + {video ? ( + + ) : null} + {attachments?.length ? ( + <> + 附件 + + + ) : null} + + ); diff --git a/lib/routes/whu/util.ts b/lib/routes/whu/util.ts index b7557c840..12e5ca908 100644 --- a/lib/routes/whu/util.ts +++ b/lib/routes/whu/util.ts @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; +import { renderDescription } from './templates/description'; + const domain = 'whu.edu.cn'; /** @@ -48,11 +47,11 @@ const getItemDetail = async (item, rootUrl) => { // Missing the `src` properties for the images. // The `src` property should be replaced with the value of `orisrc` to show the image. - // Replace images in the content with custom art template. + // Replace images in the content with custom JSX template. content('p.vsbcontent_img').each(function () { const image = content(this).find('img'); content(this).replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ image: { src: new URL(image.prop('orisrc'), rootUrl).href, width: image.prop('width'), @@ -63,11 +62,11 @@ const getItemDetail = async (item, rootUrl) => { // Missing the `src` properties for the videos. // The `src` property should be replaced with the value of `vurl` to play the video. - // Replace videos in the content with custom art template. + // Replace videos in the content with custom JSX template. content('script[name="_videourl"]').each(function () { const video = content(this); video.replaceWith( - art(path.join(__dirname, 'templates/description.art'), { + renderDescription({ video: { src: new URL(video.prop('vurl').split('?')[0], rootUrl).href, width: content(video).prop('vwidth'), @@ -99,7 +98,7 @@ const getItemDetail = async (item, rootUrl) => { const meta = processMeta(detailResponse); item.title = getMeta(meta, 'ArticleTitle') ?? item.title; - item.description = art(path.join(__dirname, 'templates/description.art'), { + item.description = renderDescription({ description, attachments, }); diff --git a/lib/routes/wikipedia/current-events.ts b/lib/routes/wikipedia/current-events.ts index 427e1e931..c10ec147f 100644 --- a/lib/routes/wikipedia/current-events.ts +++ b/lib/routes/wikipedia/current-events.ts @@ -71,7 +71,7 @@ function parseCurrentEventsTemplate(wikitext: string): string | null { function stripTemplates(wikitext: string): string { // Remove MediaWiki template delimiters {{...}} but keep the content - // This prevents conflicts with art-template's {{ }} delimiters in RSS generation + // This prevents conflicts with template delimiters during JSX-based rendering return wikitext.replaceAll(/\{\{([^}]+)\}\}/g, '$1'); } @@ -228,7 +228,7 @@ export function wikiToHtml(wikitext: string): string { let html = wikitext; // Apply transformations in order - html = stripTemplates(html); // Must be first to prevent art-template conflicts + html = stripTemplates(html); // Must be first to prevent template delimiter conflicts html = convertWikiLinks(html); html = convertExternalLinks(html); html = convertTextFormatting(html); diff --git a/lib/routes/windsurf/blog.ts b/lib/routes/windsurf/blog.tsx similarity index 88% rename from lib/routes/windsurf/blog.ts rename to lib/routes/windsurf/blog.tsx index 0a18baa48..9efdd4cf1 100644 --- a/lib/routes/windsurf/blog.ts +++ b/lib/routes/windsurf/blog.tsx @@ -1,14 +1,12 @@ -import path from 'node:path'; - import type { CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Context } from 'hono'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Data, DataItem, Route } from '@/types'; import { ViewType } from '@/types'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '10', 10); @@ -34,17 +32,16 @@ export const handler = async (ctx: Context): Promise => { const items: DataItem[] = response.posts.slice(0, limit).map((item): DataItem => { const title: string = item.title; const image: string | undefined = item.images?.[0]; - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { - images: image - ? [ - { - src: image, - alt: title, - }, - ] - : undefined, - intro: item.summary, - }); + const description: string | undefined = renderToString( + <> + {image ? ( +
    + {title} +
    + ) : null} + {item.summary ?
    {item.summary}
    : null} + + ); const pubDate: number | string = item.date; const linkUrl: string | undefined = item.slug; const categories: string[] = item.tags; diff --git a/lib/routes/windsurf/templates/description.art b/lib/routes/windsurf/templates/description.art deleted file mode 100644 index 249654e7e..000000000 --- a/lib/routes/windsurf/templates/description.art +++ /dev/null @@ -1,21 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if intro }} -
    {{ intro }}
    -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/winstall/templates/desc.art b/lib/routes/winstall/templates/desc.art deleted file mode 100644 index 998a2ff2c..000000000 --- a/lib/routes/winstall/templates/desc.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if installers }} - {{ each installers installer }} - {{ installer }}
    - {{ /each }} -{{ /if }} diff --git a/lib/routes/winstall/update.ts b/lib/routes/winstall/update.tsx similarity index 81% rename from lib/routes/winstall/update.ts rename to lib/routes/winstall/update.tsx index 49320057b..8b7778d60 100644 --- a/lib/routes/winstall/update.ts +++ b/lib/routes/winstall/update.tsx @@ -1,12 +1,22 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import { config } from '@/config'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +const renderDesc = (installers?: string[]) => + renderToString( + <> + {installers?.map((installer) => ( + <> + {installer} +
    + + ))} + + ); export const route: Route = { path: '/:appId', @@ -51,9 +61,7 @@ async function handler(ctx) { const { app } = response.pageProps; const items = app.versions.map((item) => ({ title: `${app.name} ${item.version}`, - description: art(path.join(__dirname, 'templates/desc.art'), { - installers: item.installers, - }), + description: renderDesc(item.installers), author: app.publisher, category: app.tags, guid: `winstall:${appId}:${item.version}`, diff --git a/lib/routes/wise/pair.ts b/lib/routes/wise/pair.tsx similarity index 71% rename from lib/routes/wise/pair.ts rename to lib/routes/wise/pair.tsx index 352b4adc8..0338a02cc 100644 --- a/lib/routes/wise/pair.ts +++ b/lib/routes/wise/pair.tsx @@ -1,16 +1,41 @@ -import path from 'node:path'; - import dayjs from 'dayjs'; import customParseFormat from 'dayjs/plugin/customParseFormat.js'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; dayjs.extend(customParseFormat); -const renderDesc = (content) => art(path.join(__dirname, 'templates/description.art'), content); +const renderDesc = (content) => + renderToString( + <> +

    + {content.source} to {content.target} +

    + + + + + + + + + + + + + + + +
    + Date + + Rate +
    {content.yesterday}{content.yRate}
    {content.dayBefore}{content.byRate}
    + + ); export const route: Route = { path: '/pair/:source/:target', diff --git a/lib/routes/wise/templates/description.art b/lib/routes/wise/templates/description.art deleted file mode 100644 index 7a49bce5f..000000000 --- a/lib/routes/wise/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -

    {{ source }} to {{ target }}

    - - - - - - - - - - - - - - - -
    DateRate
    {{ yesterday }}{{ yRate }}
    {{ dayBefore }}{{ byRate }}
    diff --git a/lib/routes/wmc-bj/publish.ts b/lib/routes/wmc-bj/publish.tsx similarity index 84% rename from lib/routes/wmc-bj/publish.ts rename to lib/routes/wmc-bj/publish.tsx index ec56268dd..e16afd072 100644 --- a/lib/routes/wmc-bj/publish.ts +++ b/lib/routes/wmc-bj/publish.tsx @@ -1,11 +1,9 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -39,11 +37,13 @@ async function handler(ctx) { { title: `${datetime} ${title}`, link: currentUrl, - description: art(path.join(__dirname, 'templates/description.art'), { - image: { - src: img.prop('src').replace(/\/medium\//, '/'), - }, - }), + description: renderToString( + img.prop('src') ? ( +
    + +
    + ) : null + ), category: categories, guid: `${currentUrl}#${datetime}`, pubDate: timezone(parseDate(/^[A-Za-z]{3}/.test(datetime) ? datetime.replace(/^\w+/, '') : datetime, ['DD MMM HH:mm', 'MM/DD HH:mm']), +0), diff --git a/lib/routes/wmc-bj/templates/description.art b/lib/routes/wmc-bj/templates/description.art deleted file mode 100644 index f287a9799..000000000 --- a/lib/routes/wmc-bj/templates/description.art +++ /dev/null @@ -1,5 +0,0 @@ -{{ if image?.src }} -
    - -
    -{{ /if }} diff --git a/lib/routes/wnacg/common.ts b/lib/routes/wnacg/common.tsx similarity index 88% rename from lib/routes/wnacg/common.ts rename to lib/routes/wnacg/common.tsx index 600a615ea..16cf18bfb 100644 --- a/lib/routes/wnacg/common.ts +++ b/lib/routes/wnacg/common.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import InvalidParameterError from '@/errors/types/invalid-parameter'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const categories = { 1: '同人誌 漢化', @@ -92,10 +91,13 @@ export async function handler(ctx) { item.author = author; item.category = category; - item.description = art(path.join(__dirname, 'templates/manga.art'), { - description, - imgList, - }); + item.description = renderToString( + <> + {description ? raw(description) : null} +
    + {imgList ? imgList.map((img) => {img.caption}) : null} + + ); return item; }) diff --git a/lib/routes/wnacg/templates/manga.art b/lib/routes/wnacg/templates/manga.art deleted file mode 100644 index b3ae22822..000000000 --- a/lib/routes/wnacg/templates/manga.art +++ /dev/null @@ -1,9 +0,0 @@ -{{ if description }} - {{@ description }} -{{ /if }} -
    -{{ if imgList }} - {{ each imgList img }} - {{ img.caption }} - {{ /each }} -{{ /if }} diff --git a/lib/routes/wsj/templates/article-description.art b/lib/routes/wsj/templates/article-description.art deleted file mode 100644 index 53569ee56..000000000 --- a/lib/routes/wsj/templates/article-description.art +++ /dev/null @@ -1,6 +0,0 @@ -
    - {{@ item.subTitle}} - {{@ item.article}} - -
    - diff --git a/lib/routes/wsj/utils.ts b/lib/routes/wsj/utils.tsx similarity index 90% rename from lib/routes/wsj/utils.ts rename to lib/routes/wsj/utils.tsx index 37fdee011..109c3a785 100644 --- a/lib/routes/wsj/utils.ts +++ b/lib/routes/wsj/utils.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { PRESETS } from '@/utils/header-generator'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const parseArticle = (item) => cache.tryGet(item.link, async () => { @@ -89,9 +88,7 @@ const parseArticle = (item) => $(e).remove(); }); item.article = article.html(); - item.description = art(path.join(__dirname, 'templates/article-description.art'), { - item, - }); + item.description = renderToString(); return { title: item.title, @@ -108,3 +105,10 @@ const parseArticle = (item) => }); export { parseArticle }; + +const WsjDescription = ({ subTitle, article }: { subTitle?: string; article?: string }) => ( +
    + {subTitle ? raw(subTitle) : null} + {article ? raw(article) : null} +
    +); diff --git a/lib/routes/x-mol/news.ts b/lib/routes/x-mol/news.tsx similarity index 80% rename from lib/routes/x-mol/news.ts rename to lib/routes/x-mol/news.tsx index a05e06d32..7280dacea 100644 --- a/lib/routes/x-mol/news.ts +++ b/lib/routes/x-mol/news.tsx @@ -1,12 +1,10 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; import utils from './utils'; @@ -55,10 +53,17 @@ async function handler(ctx) { return { title: a.text(), link: new URL(a.attr('href'), utils.host).href, - description: art(path.join(__dirname, 'templates/description.art'), { - image: element.find('img').attr('src').split('?')[0], - text: element.find('.thsis-div a').text().trim(), - }), + description: renderToString( + <> + {element.find('img').attr('src') ? ( + <> + +
    + + ) : null} + {element.find('.thsis-div a').text().trim() ?

    {element.find('.thsis-div a').text().trim()}

    : null} + + ), author, pubDate: span.next().length ? timezone(parseDate(span.next().text().trim()), 8) : undefined, }; diff --git a/lib/routes/x-mol/templates/description.art b/lib/routes/x-mol/templates/description.art deleted file mode 100644 index e18d50115..000000000 --- a/lib/routes/x-mol/templates/description.art +++ /dev/null @@ -1,6 +0,0 @@ -{{ if image }} -
    -{{ /if }} -{{ if text }} -

    {{ text }}

    -{{ /if }} diff --git a/lib/routes/x410/news.ts b/lib/routes/x410/news.ts index 0b81a9f8d..457377d79 100644 --- a/lib/routes/x410/news.ts +++ b/lib/routes/x410/news.ts @@ -1,5 +1,3 @@ -import path from 'node:path'; - import type { Cheerio, CheerioAPI } from 'cheerio'; import { load } from 'cheerio'; import type { Element } from 'domhandler'; @@ -10,7 +8,8 @@ import { ViewType } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; + +import { renderDescription } from './templates/description'; export const handler = async (ctx: Context): Promise => { const limit: number = Number.parseInt(ctx.req.query('limit') ?? '30', 10); @@ -32,7 +31,7 @@ export const handler = async (ctx: Context): Promise => { const $aEl: Cheerio = $el.find('h4 a'); const title: string = $aEl.text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ description: $el.find('div#cookbook').html(), }); const pubDateStr: string | undefined = $el.find('span.updated').text(); @@ -81,7 +80,7 @@ export const handler = async (ctx: Context): Promise => { }); const title: string = $$('.title').text(); - const description: string | undefined = art(path.join(__dirname, 'templates/description.art'), { + const description: string | undefined = renderDescription({ description: $$('div#cookbook').html(), }); const pubDateStr: string | undefined = $$('meta[property="article:published_time"]').attr('content'); diff --git a/lib/routes/x410/templates/description.art b/lib/routes/x410/templates/description.art deleted file mode 100644 index dfab19230..000000000 --- a/lib/routes/x410/templates/description.art +++ /dev/null @@ -1,17 +0,0 @@ -{{ if images }} - {{ each images image }} - {{ if image?.src }} -
    - {{ image.alt }} -
    - {{ /if }} - {{ /each }} -{{ /if }} - -{{ if description }} - {{@ description }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/x410/templates/description.tsx b/lib/routes/x410/templates/description.tsx new file mode 100644 index 000000000..590c3f454 --- /dev/null +++ b/lib/routes/x410/templates/description.tsx @@ -0,0 +1,26 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Image = { + src?: string; + alt?: string; +}; + +type DescriptionData = { + images?: Image[]; + description?: string; +}; + +export const renderDescription = ({ images, description }: DescriptionData): string => + renderToString( + <> + {images?.map((image) => + image?.src ? ( +
    + {image.alt} +
    + ) : null + )} + {description ? raw(description) : null} + + ); diff --git a/lib/routes/xiaomiyoupin/crowdfunding.ts b/lib/routes/xiaomiyoupin/crowdfunding.ts index 54178e4fb..ae5f4dbc4 100644 --- a/lib/routes/xiaomiyoupin/crowdfunding.ts +++ b/lib/routes/xiaomiyoupin/crowdfunding.ts @@ -1,8 +1,7 @@ -import path from 'node:path'; - import type { Route } from '@/types'; import got from '@/utils/got'; -import { art } from '@/utils/render'; + +import { renderGoods } from './templates/goods'; const base_url = 'https://m.xiaomiyoupin.com'; export const route: Route = { @@ -55,7 +54,7 @@ async function handler() { return { title: goods.name, guid: `xiaomiyoupin:${goods.gid}`, - description: art(path.join(__dirname, 'templates/goods.art'), goods), + description: renderGoods(goods), link: goods.jump_url, pubDate: new Date(goods.fist_release_time * 1000).toUTCString(), }; diff --git a/lib/routes/xiaomiyoupin/templates/goods.art b/lib/routes/xiaomiyoupin/templates/goods.art deleted file mode 100644 index f19b082e0..000000000 --- a/lib/routes/xiaomiyoupin/templates/goods.art +++ /dev/null @@ -1,27 +0,0 @@ -
    - {{ alt }} - {{ if videos_url }} - - {{/if}} - - {{ if name }} -
    -
    {{@ name }}
    -
    {{@ summary }}
    -
    原始价格:{{@ market_price / 100 }}元
    -
    实际价格:{{@ (price_min || flash_price) / 100 }}元
    -
    - {{/if}} - -
    diff --git a/lib/routes/xiaomiyoupin/templates/goods.tsx b/lib/routes/xiaomiyoupin/templates/goods.tsx new file mode 100644 index 000000000..fa183d670 --- /dev/null +++ b/lib/routes/xiaomiyoupin/templates/goods.tsx @@ -0,0 +1,54 @@ +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; + +type Goods = { + pic_url?: string; + img_square?: string; + imgs?: { img800?: string }; + alt?: string; + videos_url?: string[]; + name?: string; + summary?: string; + market_price?: number; + price_min?: number; + flash_price?: number; +}; + +const GoodsFigure = (goods: Goods) => { + const imageUrl = goods.pic_url || goods.img_square || goods.imgs?.img800; + const marketPrice = goods.market_price ? goods.market_price / 100 : undefined; + const salePrice = goods.price_min ?? goods.flash_price; + const finalPrice = salePrice ? salePrice / 100 : undefined; + + return ( +
    + {goods.alt} + {goods.videos_url ? ( + + ) : null} + {goods.name ? ( +
    +
    {raw(goods.name)}
    +
    {raw(goods.summary ?? '')}
    +
    原始价格:{marketPrice}元
    +
    实际价格:{finalPrice}元
    +
    + ) : null} +
    + ); +}; + +export const renderGoods = (goods: Goods): string => renderToString(); diff --git a/lib/routes/xiaomiyoupin/utils.ts b/lib/routes/xiaomiyoupin/utils.ts index 903f45abf..79a5e399b 100644 --- a/lib/routes/xiaomiyoupin/utils.ts +++ b/lib/routes/xiaomiyoupin/utils.ts @@ -1,6 +1,4 @@ -import path from 'node:path'; - -import { art } from '@/utils/render'; +import { renderGoods } from './templates/goods'; const parseModule = (floors, module_key) => floors.find((floor) => floor.module_key === module_key); @@ -11,7 +9,7 @@ const parseFloorItem = (floor) => title: i.name, link: i.jump_url, guid: `xiaomiyoupin:${i.gid}`, - description: art(path.join(__dirname, 'templates/goods.art'), i), + description: renderGoods(i), pubDate: (i.start || i.start_time) * 1000, }; }); diff --git a/lib/routes/xinpianchang/templates/description.art b/lib/routes/xinpianchang/templates/description.art deleted file mode 100644 index 3a8645dba..000000000 --- a/lib/routes/xinpianchang/templates/description.art +++ /dev/null @@ -1,13 +0,0 @@ -{{ if content }} -

    {{ content }}

    -{{ /if }} - -{{ if enclousure }} - -{{ /if }} \ No newline at end of file diff --git a/lib/routes/xinpianchang/util.ts b/lib/routes/xinpianchang/util.tsx similarity index 87% rename from lib/routes/xinpianchang/util.ts rename to lib/routes/xinpianchang/util.tsx index 894b9d68e..4e568cc01 100644 --- a/lib/routes/xinpianchang/util.ts +++ b/lib/routes/xinpianchang/util.tsx @@ -1,10 +1,8 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; const appKey = '61a2f329348b3bf77'; @@ -12,6 +10,18 @@ const domain = 'xinpianchang.com'; const rootUrl = `https://www.${domain}`; const rootApiUrl = `https://mod-api.${domain}`; +const renderDescription = (content, cover, enclousure) => + renderToString( + <> + {content ?

    {content}

    : null} + {enclousure ? ( + + ) : null} + + ); + /** * Retrieves information from a given URL using a provided tryGet function. * @@ -81,11 +91,7 @@ const processItems = async (items, tryGet) => { const enclousure = data.resource?.progressive ? data.resource.progressive[0] : undefined; item.title = data.title ?? item.title; - item.description = art(path.join(__dirname, 'templates/description.art'), { - content: item.description, - cover: data.cover ?? item.itunes_item_image, - enclousure, - }); + item.description = renderDescription(item.description, data.cover ?? item.itunes_item_image, enclousure); item.author = data.owner.username ?? item.author; item.category = [...new Set([...item.category, ...(data.categories ?? []), ...(data.keywords ?? [])])]; diff --git a/lib/routes/xjtu/job.ts b/lib/routes/xjtu/job.tsx similarity index 87% rename from lib/routes/xjtu/job.ts rename to lib/routes/xjtu/job.tsx index 0e64986f8..99e7e13e9 100644 --- a/lib/routes/xjtu/job.ts +++ b/lib/routes/xjtu/job.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; const baseUrl = 'https://job.xjtu.edu.cn'; @@ -87,9 +86,7 @@ async function handler(ctx) { rejectUnauthorized: false, }, }); - attachments = art(path.join(__dirname, 'templates/attachments.art'), { - items: attachmentData.data.items, - }); + attachments = renderToString(); } item.author = response.data.data[0].CZZXM; @@ -105,3 +102,16 @@ async function handler(ctx) { item: items, }; } + +const XjtuAttachments = ({ items }: { items: { fileUrl: string; name: string }[] }) => ( + <> + {items.map((item) => ( + <> + + {item.name} + +
    + + ))} + +); diff --git a/lib/routes/xjtu/std.ts b/lib/routes/xjtu/std.tsx similarity index 80% rename from lib/routes/xjtu/std.ts rename to lib/routes/xjtu/std.tsx index 74f3bb3a3..994bcc1e2 100644 --- a/lib/routes/xjtu/std.ts +++ b/lib/routes/xjtu/std.tsx @@ -1,12 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -64,10 +63,7 @@ async function handler(ctx) { const content = load(detailResponse.data); - item.description = art(path.join(__dirname, 'templates/std.art'), { - description: content('#vsb_newscontent').html(), - attachments: content('#vsb_newscontent').parent().next().next().next().html(), - }); + item.description = renderToString(); item.pubDate = timezone(parseDate(content('#vsb_newscontent').parent().prev().prev().text().split(' ')[0], 'YYYY年MM月DD日 HH:mm'), +8); return item; @@ -81,3 +77,10 @@ async function handler(ctx) { item: items, }; } + +const XjtuStdDescription = ({ description, attachments }: { description?: string; attachments?: string }) => ( + <> + {description ? raw(description) : null} + {attachments ? raw(attachments) : null} + +); diff --git a/lib/routes/xjtu/templates/attachments.art b/lib/routes/xjtu/templates/attachments.art deleted file mode 100644 index 8211878e9..000000000 --- a/lib/routes/xjtu/templates/attachments.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ each items }} -{{ $value.name }}
    -{{ /each }} diff --git a/lib/routes/xjtu/templates/std.art b/lib/routes/xjtu/templates/std.art deleted file mode 100644 index 1e4e0a5f7..000000000 --- a/lib/routes/xjtu/templates/std.art +++ /dev/null @@ -1,4 +0,0 @@ -{{@ description }} -{{ if attachments }} -{{@ attachments }} -{{ /if }} \ No newline at end of file diff --git a/lib/routes/xkb/index.ts b/lib/routes/xkb/index.tsx similarity index 86% rename from lib/routes/xkb/index.ts rename to lib/routes/xkb/index.tsx index 653aebffd..0d41be767 100644 --- a/lib/routes/xkb/index.ts +++ b/lib/routes/xkb/index.tsx @@ -1,10 +1,9 @@ -import path from 'node:path'; +import { renderToString } from 'hono/jsx/dom/server'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; import timezone from '@/utils/timezone'; export const route: Route = { @@ -50,9 +49,16 @@ async function handler(ctx) { .filter((i) => i.contentUrl) // Remove "专题报道" (special report) .map((item) => ({ title: item.listTitle, - description: art(path.join(__dirname, 'templates/description.art'), { - thumb: item.shareImg, - }), + description: renderToString( + <> + {item.shareImg ? ( + <> + +
    + + ) : null} + + ), pubDate: timezone(parseDate(item.operTime), +8), link: 'https://www.xkb.com.cn/detail?id=' + item.id, contentUrl: item.contentUrl, diff --git a/lib/routes/xkb/templates/description.art b/lib/routes/xkb/templates/description.art deleted file mode 100644 index 2113e7063..000000000 --- a/lib/routes/xkb/templates/description.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ if thumb }} -
    -{{ /if }} \ No newline at end of file diff --git a/lib/routes/xueqiu/stock-comments.ts b/lib/routes/xueqiu/stock-comments.tsx similarity index 69% rename from lib/routes/xueqiu/stock-comments.ts rename to lib/routes/xueqiu/stock-comments.tsx index 9bcdf1fed..031b253e1 100644 --- a/lib/routes/xueqiu/stock-comments.ts +++ b/lib/routes/xueqiu/stock-comments.tsx @@ -1,13 +1,12 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { raw } from 'hono/html'; +import { renderToString } from 'hono/jsx/dom/server'; import sanitizeHtml from 'sanitize-html'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/stock_comments/:id', @@ -60,7 +59,25 @@ async function handler(ctx) { if (item.quote_cards) { link = item.quote_cards[0].target_url; } - const description = art(path.join(__dirname, 'templates/comments_description.art'), { item }); + const description = renderToString( + <> + +
    {item.user.screen_name}:
    +
    +
    + {item.text ? raw(item.text) : null} +
    + {item.retweeted_status ? ( + <> +
    + {raw(item.retweeted_status.text)} + + ) : ( +
    + )} +
    ----来源于:{item.source}
    + + ); return { title: item.title || sanitizeHtml(item.text, { allowedTags: [], allowedAttributes: {} }), description, diff --git a/lib/routes/xueqiu/templates/comments_description.art b/lib/routes/xueqiu/templates/comments_description.art deleted file mode 100644 index 69352716c..000000000 --- a/lib/routes/xueqiu/templates/comments_description.art +++ /dev/null @@ -1,10 +0,0 @@ -
    {{ item.user.screen_name }}:
    -
    -{{@ item.text }} -
    -{{ if item.retweeted_status }} -
    {{@ item.retweeted_status.text }} -{{ else }} -
    -{{ /if }} -
    ----来源于:{{ item.source }}
    diff --git a/lib/routes/xys/new.ts b/lib/routes/xys/new.tsx similarity index 86% rename from lib/routes/xys/new.ts rename to lib/routes/xys/new.tsx index 51816aee3..921714e76 100644 --- a/lib/routes/xys/new.ts +++ b/lib/routes/xys/new.tsx @@ -1,13 +1,11 @@ -import path from 'node:path'; - import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; import iconv from 'iconv-lite'; import type { Route } from '@/types'; import cache from '@/utils/cache'; import got from '@/utils/got'; import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; export const route: Route = { path: '/new', @@ -73,7 +71,7 @@ async function handler(ctx) { const matchYoutube = item.link.match(youTube); if (matchYoutube) { - item.description = art(path.join(__dirname, 'templates/desc.art'), { youTube: item.link.slice(32) }); + item.description = renderDescription(item.link.slice(32)); } else { const detailResponse = await got({ method: 'get', @@ -100,3 +98,6 @@ async function handler(ctx) { item: items, }; } + +const renderDescription = (youTube: string): string => + renderToString(<>{youTube ? : null}); diff --git a/lib/routes/xys/templates/desc.art b/lib/routes/xys/templates/desc.art deleted file mode 100644 index eef799519..000000000 --- a/lib/routes/xys/templates/desc.art +++ /dev/null @@ -1,3 +0,0 @@ -{{ if youTube }} - -{{ /if }} diff --git a/lib/routes/xyzrank/index.ts b/lib/routes/xyzrank/index.ts deleted file mode 100644 index 117823fbd..000000000 --- a/lib/routes/xyzrank/index.ts +++ /dev/null @@ -1,128 +0,0 @@ -import path from 'node:path'; - -import { load } from 'cheerio'; - -import type { Route } from '@/types'; -import got from '@/utils/got'; -import { parseDate } from '@/utils/parse-date'; -import { art } from '@/utils/render'; - -export const route: Route = { - path: '/:category?', - radar: [ - { - source: ['xyzrank.com/'], - target: '', - }, - ], - name: 'Unknown', - maintainers: [], - handler, - url: 'xyzrank.com/', -}; - -async function handler(ctx) { - const category = ctx.req.param('category') ?? ''; - - const rootUrl = 'https://xyzrank.com'; - const currentUrl = `${rootUrl}/#/${category}`; - - let response = await got({ - method: 'get', - url: rootUrl, - }); - - const $ = load(response.data); - - response = await got({ - method: 'get', - url: response.data.match(/