refactor: replace art template with jsx (#20777)

* refactor: replace art template with jsx

* fix: tsx files

* fix: renderToString import

* fix: hono html raw import

* feat: remove art
This commit is contained in:
DIYgod 2025-12-29 12:27:32 +08:00 committed by GitHub
parent 2cef8cf1cc
commit cf4e772de8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1442 changed files with 17691 additions and 15014 deletions

2
.github/labeler.yml vendored
View File

@ -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:

View File

@ -7,6 +7,7 @@ on:
- master
paths:
- 'lib/**/*.ts'
- 'lib/**/*.tsx'
jobs:
build:

View File

@ -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)'
'';

View File

@ -1,14 +0,0 @@
<iframe referrerpolicy="no-referrer" width=100% height=150vh frameborder=0 marginheight=0 marginwidth=0
style="border:0; margin:0; padding:0; width:100%; height:150vh;"
srcdoc="
<!DOCTYPE html>
<html>
<head>
<meta name=&quot;referrer&quot; content=&quot;no-referrer&quot;>
</head>
<body>
{{ content }}
</body>
</html>
">
</iframe>

View File

@ -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;
}
}

View File

@ -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('<br>') ? 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 = `<!DOCTYPE html>${html}`;
};

View File

@ -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,

View File

@ -1,4 +0,0 @@
<p>
<img src="{{ imageUrl }}" referrerpolicy="no-referrer">
<br>
</p>

View File

@ -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 ? (
<figure>
<img src={image} alt={title} />
</figure>
) : null}
{item.find('div.p-row').text() ? <blockquote>{item.find('div.p-row').text()}</blockquote> : null}
</>
);
return {
title,

View File

@ -1,27 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if !videos?.[0]?.src && image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
{{ if image.width }}
alt="{{ image.width }}"
{{ /if }}
{{ if image.height }}
alt="{{ image.height }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if intro }}
<blockquote>{{ intro }}</blockquote>
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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<Data> => {
const { filter } = ctx.req.param();
@ -38,7 +37,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
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<Data> => {
const $$: CheerioAPI = load(detailResponse);
const description: string | undefined =
art(path.join(__dirname, 'templates/description.art'), {
renderDescription({
images: $$('div.thumbs img')
.toArray()
.map((i) => {

View File

@ -1,50 +0,0 @@
{{ if category || catalogue || title || size || date }}
<table>
<tbody>
{{ if category }}
<tr>
<th>Category</th>
<td>{{@ category }}</td>
</tr>
{{ /if }}
{{ if catalogue }}
<tr>
<th>Catalogue</th>
<td>{{@ catalogue }}</td>
</tr>
{{ /if }}
{{ if title }}
<tr>
<th>Title</th>
<td>{{@ title }}</td>
</tr>
{{ /if }}
{{ if size }}
<tr>
<th>Size</th>
<td>{{@ size }}</td>
</tr>
{{ /if }}
{{ if date }}
<tr>
<th>Date</th>
<td>{{@ date }}</td>
</tr>
{{ /if }}
</tbody>
</table>
{{ /if }}
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}

View File

@ -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 ? (
<table>
<tbody>
{category ? (
<tr>
<th>Category</th>
<td>{raw(category)}</td>
</tr>
) : null}
{catalogue ? (
<tr>
<th>Catalogue</th>
<td>{raw(catalogue)}</td>
</tr>
) : null}
{title ? (
<tr>
<th>Title</th>
<td>{raw(title)}</td>
</tr>
) : null}
{size ? (
<tr>
<th>Size</th>
<td>{raw(size)}</td>
</tr>
) : null}
{date ? (
<tr>
<th>Date</th>
<td>{raw(date)}</td>
</tr>
) : null}
</tbody>
</table>
) : null}
{images?.map((image) =>
image?.src ? (
<figure>
<img src={image.src} alt={image.alt ?? undefined} />
</figure>
) : null
)}
</>
);

View File

@ -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<Data> => {
const { category = 'newslists', id } = ctx.req.param();
@ -33,7 +32,7 @@ export const handler = async (ctx: Context): Promise<Data> => {
const $aEl: Cheerio<Element> = $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<Data> => {
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];

View File

@ -1,7 +0,0 @@
{{ if intro }}
<blockquote>{{ intro }}</blockquote>
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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 ? <blockquote>{intro}</blockquote> : null}
{description ? <>{raw(description)}</> : null}
</>
);
export const renderDescription = (props: DescriptionProps): string => renderToString(<Description {...props} />);

View File

@ -1,17 +0,0 @@
{{ if description }}
<p>{{ description }}</p>
{{ /if }}
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}

View File

@ -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 ? <p>{description}</p> : null}
{images?.length
? images.map((image) =>
image?.src ? (
<figure key={image.src}>
<img src={image.src} alt={image.alt} />
</figure>
) : null
)
: null}
</>
);
export const handler = async (ctx: Context): Promise<Data> => {
const limit: number = Number.parseInt(ctx.req.query('limit') ?? '100', 10);
@ -29,13 +43,13 @@ export const handler = async (ctx: Context): Promise<Data> => {
.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}`;

View File

@ -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(
<>
<text>{trainInfo.trainNo}</text>
<br />
<text>
{trainInfo.fromStation} {trainInfo.toStation}
</text>
<br />
<text>{trainInfo.startTime}</text>
<br />
<text>{trainInfo.arriveTime}</text>
<br />
<text>
{trainInfo.duration} {trainInfo.today === 'N' && '次日达'}
</text>
<br />
<text>/{trainInfo.A9 || '无'}</text>
<br />
<text>{trainInfo.M || '无'}</text>
<br />
<text>/{trainInfo.O || '无'}</text>
<br />
<text>{trainInfo.A6 || '无'}</text>
<br />
<text>/{trainInfo.A4 || '无'}</text>
<br />
<text>{trainInfo.F || '无'}</text>
<br />
<text>/{trainInfo.A3 || '无'}</text>
<br />
<text>: {trainInfo.A2 || '无'}</text>
<br />
<text>: {trainInfo.A1 || '无'}</text>
<br />
<text>: {trainInfo.WZ || '无'}</text>
<br />
<text>: {trainInfo.QT || '无'}</text>
</>
);
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('|'),
};

View File

@ -1,31 +0,0 @@
<text>车次:{{ trainInfo.trainNo}}</text>
<br>
<text>始发站:{{ trainInfo.fromStation}} → {{ trainInfo.toStation}}</text>
<br>
<text>出发时间:{{ trainInfo.startTime}}</text>
<br>
<text>到达时间:{{ trainInfo.arriveTime}}</text>
<br>
<text>历时:{{ trainInfo.duration}} {{ trainInfo.today === 'N' ? '次日达' : '' }}</text>
<br>
<text>商务座/特等座:{{ trainInfo.A9 ? trainInfo.A9 : '无' }}</text>
<br>
<text>一等座:{{ trainInfo.M ? trainInfo.M : '无' }}</text>
<br>
<text>二等座/二等包座:{{ trainInfo.O ? trainInfo.O : '无' }}</text>
<br>
<text>高级软卧:{{ trainInfo.A6 ? trainInfo.A6 : '无' }}</text>
<br>
<text>软卧/一等卧:{{ trainInfo.A4 ? trainInfo.A4 : '无' }}</text>
<br>
<text>动卧:{{ trainInfo.F ? trainInfo.F : '无' }}</text>
<br>
<text>硬卧/二等卧:{{ trainInfo.A3 ? trainInfo.A3 : '无' }}</text>
<br>
<text>软座: {{ trainInfo.A2 ? trainInfo.A2 : '无' }}</text>
<br>
<text>硬座: {{ trainInfo.A1 ? trainInfo.A1 : '无' }}</text>
<br>
<text>无座: {{ trainInfo.WZ ? trainInfo.WZ : '无' }}</text>
<br>
<text>其他: {{ trainInfo.QT ? trainInfo.QT : '无' }}</text>

View File

@ -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(<JavDescription image={image} id={id} size={size} pubDate={pubDate} description={description} actresses={actresses} tags={tags} magnet={magnet} link={link} />),
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 ? <img src={image} /> : null}
<table>
<tbody>
<tr>
<th>ID</th>
<td>{id}</td>
</tr>
<tr>
<th>Size</th>
<td>{size}</td>
</tr>
<tr>
<th>Date</th>
<td>{pubDate}</td>
</tr>
<tr>
<th>Description</th>
<td>{description}</td>
</tr>
<tr>
<th>Actress</th>
<td>
{actresses.map((actress) => (
<>
<a href={`/actress/${actress}`}>{actress}</a>&nbsp;
</>
))}
</td>
</tr>
<tr>
<th>Tag</th>
<td>
{tags.map((tag) => (
<>
<a href={`/tag/${tag}`}>{tag}</a>&nbsp;
</>
))}
</td>
</tr>
<tr>
<th>Magnet torrent</th>
<td>
<a href={magnet}>Magnet torrent link</a>
</td>
</tr>
<tr>
<th>Download .torrent</th>
<td>
<a href={link}>Download torrent</a>
</td>
</tr>
</tbody>
</table>
</>
);

View File

@ -1,47 +0,0 @@
{{ if image }}
<img src="{{ image }}">
{{ /if }}
<table>
<tbody>
<tr>
<th>ID</th>
<td>{{ id }}</td>
</tr>
<tr>
<th>Size</th>
<td>{{ size }}</td>
</tr>
<tr>
<th>Date</th>
<td>{{ pubDate }}</td>
</tr>
<tr>
<th>Description</th>
<td>{{ description }}</td>
</tr>
<tr>
<th>Actress</th>
<td>
{{ each actresses actress }}
<a href="/actress/{{ actress }}">{{ actress }}</a>&nbsp
{{ /each }}
</td>
</tr>
<tr>
<th>Tag</th>
<td>
{{ each tags tag }}
<a href="/tag/{{ tag }}">{{ tag }}</a>&nbsp
{{ /each }}
</td>
</tr>
<tr>
<th>Magnet torrent</th>
<td><a href="{{ magnet }}">Magnet torrent link</a></td>
</tr>
<tr>
<th>Download .torrent</th>
<td><a href="{{ link }}">Download torrent</a></td>
</tr>
</tbody>
</table>

View File

@ -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 ? <img src={image} /> : null}
<table>
<tbody>
<tr>
<th>ID</th>
<td>{id}</td>
</tr>
<tr>
<th>Size</th>
<td>{size}</td>
</tr>
<tr>
<th>Date</th>
<td>{pubDate}</td>
</tr>
<tr>
<th>Description</th>
<td>{description}</td>
</tr>
<tr>
<th>Actress</th>
<td>
{actresses.map((actress) => (
<>
<a href={`/actress/${actress}`}>{actress}</a>&nbsp;
</>
))}
</td>
</tr>
<tr>
<th>Tag</th>
<td>
{tags.map((tag) => (
<>
<a href={`/tag/${tag}`}>{tag}</a>&nbsp;
</>
))}
</td>
</tr>
<tr>
<th>Magnet torrent</th>
<td>
<a href={magnet}>Magnet torrent link</a>
</td>
</tr>
<tr>
<th>Download .torrent</th>
<td>
<a href={link}>Download torrent</a>
</td>
</tr>
</tbody>
</table>
</>
),
author: actresses.join(', '),
category: [...tags, ...actresses],
enclosure_type: 'application/x-bittorrent',

View File

@ -1,47 +0,0 @@
{{ if image }}
<img src="{{ image }}">
{{ /if }}
<table>
<tbody>
<tr>
<th>ID</th>
<td>{{ id }}</td>
</tr>
<tr>
<th>Size</th>
<td>{{ size }}</td>
</tr>
<tr>
<th>Date</th>
<td>{{ pubDate }}</td>
</tr>
<tr>
<th>Description</th>
<td>{{ description }}</td>
</tr>
<tr>
<th>Actress</th>
<td>
{{ each actresses actress }}
<a href="/actress/{{ actress }}">{{ actress }}</a>&nbsp
{{ /each }}
</td>
</tr>
<tr>
<th>Tag</th>
<td>
{{ each tags tag }}
<a href="/tag/{{ tag }}">{{ tag }}</a>&nbsp
{{ /each }}
</td>
</tr>
<tr>
<th>Magnet torrent</th>
<td><a href="{{ magnet }}">Magnet torrent link</a></td>
</tr>
<tr>
<th>Download .torrent</th>
<td><a href="{{ link }}">Download torrent</a></td>
</tr>
</tbody>
</table>

View File

@ -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') ? <img src={media.url} /> : 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),
}));

View File

@ -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'),
})
);

View File

@ -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,

View File

@ -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(),

View File

@ -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(
<>
<img src={pg.coverUrl} />
<div>
{description.map((line) => (
<p>{line}</p>
))}
</div>
{info ? (
<div>
<audio src={`https://music.163.com/song/media/outer/url?id=${pg.mainTrackId}.mp3`} controls="controls"></audio>
<p>: {itunes_duration}</p>
<p>
<a href={`https://music.163.com/program/${pg.id}`}></a>
</p>
</div>
) : 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,

View File

@ -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(),

View File

@ -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(
<p>
{lines.map((line, index) => (
<>
{line}
{index < lines.length - 1 ? <br /> : null}
</>
))}
{pics.map((pic) => (
<img src={pic} />
))}
</p>
);
};
export const route: Route = {
path: '/music/user/events/:id',

View File

@ -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 ? <img src={image} /> : null}
{description?.length ? (
<div>
{description.map((line) => (
<p>{line}</p>
))}
</div>
) : null}
{src ? (
<div>
<a href={src}></a>
</div>
) : 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,

View File

@ -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(
<div>
{index + 1} {record.playCount} {record.score}
<br />
<a href={`http://music.163.com/song?id=${song.id}`}>{song.name}</a>
<br />
{song.ar.map((artist, artistIndex) => (
<>
<a href={`https://music.163.com/artist?id=${artist.id}`}>{artist.name}</a>
{artistIndex < song.ar.length - 1 ? ' / ' : null}
</>
))}
<br />
{song.al ? (
<>
<img src={song.al.picUrl} />
<br />
</>
) : null}
</div>
);
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}`,

View File

@ -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 ? (
<div class="toc">
{chapterList.map((chapter, chapterIndex) => (
<>
<h3>
{chapterIndex + 1} {chapter.title}
</h3>
{chapter.contentList.map((content, contentIndex) => (
<h4>
{contentIndex + 1} {content.title}
</h4>
))}
</>
))}
</div>
) : 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;
})

View File

@ -1,7 +0,0 @@
{{ text }}
{{ each medias }}
{{ if $value.mimeType.indexOf('image') > -1 }}
<img src="{{$value.url}}">
{{ /if }}
{{ /each }}

View File

@ -1,6 +0,0 @@
{{ if imgsrc }}
<img src="{{ imgsrc }}"><br>
{{ /if }}
{{ if postBody }}
{{@ postBody }}
{{ /if }}

View File

@ -1,11 +0,0 @@
{{ if image }}
<img src="{{ image }}">
{{ /if }}
{{ if video }}
<video controls>
<source src="{{ video }}" type="video/mp4">
</video>
{{ /if }}
{{ if digest }}
<p>{{ digest }}</p>
{{ /if }}

View File

@ -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 ? <img src={image} /> : null}
{video ? (
<video controls>
<source src={video} type="video/mp4" />
</video>
) : null}
{digest ? <p>{digest}</p> : null}
</>
);

View File

@ -1,13 +0,0 @@
<img src={{pg.coverUrl}} />
<div>
{{each description}}
<p>{{$value}}</p>
{{/each}}
</div>
{{ if info }}
<div>
<audio src="https://music.163.com/song/media/outer/url?id={{pg.mainTrackId}}.mp3" controls="controls"></audio>
<p>时长: {{itunes_duration}}</p>
<p><a href="https://music.163.com/program/{{pg.id}}">查看节目</a></p>
</div>
{{ /if }}

View File

@ -1,4 +0,0 @@
歌手:{{ singer }}<br>
专辑:{{ album }}<br>
{{ if date }}发行日期:{{ date }}<br>{{ /if }}
<img src="{{ picUrl }}">

View File

@ -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}
<br />
{album}
<br />
{date ? (
<>
{date}
<br />
</>
) : null}
<img src={picUrl} />
</>
);

View File

@ -1,7 +0,0 @@
<p>
{{ each description.split('\n') }}
{{$value}}{{if $index !== description.split('\n').length - 1}}<br>{{/if}}
{{/each}}
{{ each pics }}
<img src="{{$value}}">
{{ /each }}</p>

View File

@ -1,16 +0,0 @@
{{ if image }}
<img src="{{ image }}">
{{ /if }}
{{ if description }}
<div>
{{ each description d }}
<p>{{ d }}</p>
{{ /each }}
</div>
{{ /if }}
{{ if src }}
<div><a href="{{ src }}">查看歌单</a></div>
{{ /if }}

View File

@ -1,7 +0,0 @@
<div>
排行:{{ index + 1 }} 播放次数:{{ record.playCount }} 得分:{{ record.score }}<br>
歌曲:<a href="http://music.163.com/song?id={{ song.id }}">{{ song.name }}</a><br>
歌手:{{ each song.ar a i }}<a href="https://music.163.com/artist?id={{ a.id }}">{{ a.name }}</a> {{ if i < song.ar.length - 1 }}/ {{ /if }}{{ /each }}
<br>
{{ if song.al }}歌曲图:<img src={{ song.al.picUrl }}><br>{{ /if }}
</div>

View File

@ -1,12 +0,0 @@
{{ set chapterList = data.movieChapterList.length ? data.movieChapterList : data.audioChapterList; }}
{{ if chapterList }}
<div class="toc">
{{ each chapterList chapter chapterIndex }}
<h3>第{{ chapterIndex + 1 }}章 {{ chapter.title }}</h3>
{{ each chapter.contentList content contentIndex }}
<h4>{{ contentIndex + 1 }} {{ content.title }}</h4>
{{ /each }}
{{ /each }}
</div>
{{ /if }}
{{@ description }}

View File

@ -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 ? (
<>
<img src={imgsrc} />
<br />
</>
) : 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();

View File

@ -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`],

View File

@ -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`,

View File

@ -1,12 +0,0 @@
{{ if cover }}
<img src="{{ cover }}">
{{ /if }}
<p>
{{each category}}
<code>{{ $value }}</code>
{{/each}}
</p>
<p>{{ introduction }}</p>
{{ each images image }}
<img src="{{ image }}">
{{ /each }}

View File

@ -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 ? <img src={cover} /> : null}
<p>
{category.map((item) => (
<code>{item}</code>
))}
</p>
<p>{introduction}</p>
{images.map((image) => (
<img src={image} />
))}
</>
);

View File

@ -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()

View File

@ -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<Data> => {
const { category = 'newly' } = ctx.req.param();
@ -66,19 +64,15 @@ export const handler = async (ctx: Context): Promise<Data> => {
$$('div.entry-content img.alignnone').each((_, el) => {
const $el: Cheerio<Element> = $$(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(
<figure>
<img src={src} width={$el.attr('width')} height={$el.attr('height')} />
</figure>
)
: ''
);
});

View File

@ -1,20 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.width }}
width="{{ image.width }}"
{{ /if }}
{{ if image.height }}
height="{{ image.height }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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(
<>
<b></b>
{item.country}
<br />
<b></b>
{item.outname_w} {item.outname}
<br />
<b></b>
{item.plandegree}
<br />
<b></b>
{item.planmajor} - {item.planprogram}
<br />
<b></b>
{item.result}
<br />
<b></b>
{item.outtime}
<br />
<b></b>
{item.noticemethod}
<br />
<b>/</b>
{item.planfin}
<br />
<b></b>
{item.planterm}
<br />
<b></b>
{item.planyr}
<br />
<b></b>
{item.submittime}
</>
);

View File

@ -1 +0,0 @@
<img src="{{ url }}" height="{{ height }}" width="{{ width }}">

View File

@ -1,11 +0,0 @@
<b>国家:</b>{{ item.country }}<br>
<b>学校:</b>{{ item.outname_w }} {{ item.outname }}<br>
<b>录取学位:</b>{{ item.plandegree }}<br>
<b>录取项目:</b>{{ item.planmajor }} - {{ item.planprogram }}<br>
<b>录取结果:</b>{{ item.result }}<br>
<b>录取时间:</b>{{ item.outtime }}<br>
<b>通知方式:</b>{{ item.noticemethod }}<br>
<b>全奖/自费:</b>{{ item.planfin }}<br>
<b>申入学学期:</b>{{ item.planterm }}<br>
<b>申入学年度:</b>{{ item.planyr }}<br>
<b>提交时间:</b>{{ item.submittime }}

View File

@ -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
? '<br>' +
art(path.join(__dirname, 'templates/image.art'), {
url: a.url,
height: a.height,
width: a.width,
})
: '';
result.description += a.isimage === 1 ? '<br>' + 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(<img src={url} height={height} width={width} />);

View File

@ -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 ? (
<figure>
<img src={image} alt={title} />
</figure>
) : null}
{text ? <>{raw(text)}</> : null}
</>
);
const id = item.find('img[id]').prop('id').split(/-/).pop();
const guid = `1x-${id}`;

View File

@ -1,17 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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(<DownloadLinks magnet={magnet} torrent={item.enclosure_url} />));
} 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 }) => (
<>
<a href={magnet}></a> | <a href={torrent}></a>
</>
);

View File

@ -1 +0,0 @@
<a href="{{ magnet }}">磁力連結</a> | <a href="{{ torrent }}">下載檔案</a>

View File

@ -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<Data> {
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(
<ThreeKnsDescription
cover={$item.find('.entry-media img').attr('src')?.trim().replace('.', baseUrl)}
title={title}
tid={$item.find('.jb-chakan').text().trim()}
category={category}
language={$item.find('.jb-new').text().trim()}
pubDate={pubDate}
system={$item.find('.jb-youxxx').text().trim()}
score={$item.find('.shownamep').text().trim()}
version={$item.find('.jb-youxbb').text().trim()}
/>
) ?? '',
};
});
@ -119,3 +119,37 @@ async function handler(ctx: Context): Promise<Data> {
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;
}) => (
<>
<img src={cover} />
<h1>{title}</h1>
<p>TID{tid}</p>
<p>{category}</p>
<p>{language}</p>
<p>{pubDate}</p>
<p>{system}</p>
<p>{score}</p>
<p>{version}</p>
</>
);

View File

@ -1,9 +0,0 @@
<img src="{{ cover }}">
<h1>{{ title }}</h1>
<p>游戏TID{{ tid }}</p>
<p>类型:{{ category }}</p>
<p>语言:{{ language }}</p>
<p>更新日期:{{ pubDate }}</p>
<p>系统要求:{{ system }}</p>
<p>{{ score }}</p>
<p>游戏版本:{{ version }}</p>

View File

@ -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;

View File

@ -1,21 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if intro }}
<blockquote>{{ intro }}</blockquote>
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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 ? <figure>{image.alt ? <img src={image.src} alt={image.alt} /> : <img src={image.src} />}</figure> : null)) : null}
{intro ? <blockquote>{intro}</blockquote> : null}
{description ? raw(description) : null}
</>
);

View File

@ -1,3 +0,0 @@
<blockquote>{{ intro }}</blockquote>
<br>
{{@ content }}

View File

@ -1,3 +0,0 @@
{{ each images img }}
<img src="{{ img.url }}" alt="{{ img.alt }}"><br>
{{ /each }}

View File

@ -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(
<>
<blockquote>{intro}</blockquote>
<br />
{raw(content)}
</>
);
const renderImages = (images) =>
art(path.join(__dirname, 'templates/image.art'), {
images,
});
renderToString(
<>
{images.map((image) => (
<>
<img src={image.url} alt={image.alt} />
<br />
</>
))}
</>
);
export { getCategories, parseItem, parseList, renderDescription, renderImages };

View File

@ -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 ? (
<figure>
<img src={image.src} alt={image.alt ?? undefined} />
</figure>
) : null
)}
{title ? <h1>{title}</h1> : null}
{keys && details ? (
<table>
<tbody>
{keys.map((key) => (
<tr>
<th>{key}</th>
<td>{details[key]}</td>
</tr>
))}
</tbody>
</table>
) : null}
{description ? <p>{description}</p> : null}
{info ? <blockquote>{raw(info)}</blockquote> : null}
{links ? (
<table>
<tbody>
{links.map((link) => (
<tr>
<td>
<a href={link.link}>{link.title}</a>
</td>
<td>{link.tags?.join('') ?? ''}</td>
</tr>
))}
</tbody>
</table>
) : 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
? [
{

View File

@ -1,59 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if !videos?.[0]?.src && image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if title }}
<h1>{{ title }}</h1>
{{ /if }}
{{ if keys && details }}
<table>
<tbody>
{{ each keys key }}
<tr>
<th>
{{ key }}
</th>
<td>
{{ details[key] }}
</td>
</tr>
{{ /each }}
</tbody>
</table>
{{ /if }}
{{ if description }}
<p>{{ description }}</p>
{{ /if }}
{{ if info }}
<blockquote>{{@ info }}</blockquote>
{{ /if }}
{{ if links }}
<table>
<tbody>
{{ each links link }}
<tr>
<td>
<a href="{{ link.link }}">{{ link.title }}</a>
</td>
<td>
{{ link.tags?.join('') ?? '' }}
</td>
</tr>
{{ /each }}
</tbody>
</table>
{{ /if }}

View File

@ -1,6 +0,0 @@
{{ if item.description }}<p>{{ item.description }}</p>{{ /if }}
{{ if item.photos }}
{{ each item.photos p }}
<img src="{{ p.url.baseUrl }}!p5">
{{ /each }}
{{ /if }}

View File

@ -1,3 +0,0 @@
{{ if item.url }}
<img src="{{ item.url.baseUrl }}!p5">
{{ /if }}

View File

@ -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 ? <p>{item.description}</p> : null}
{item.photos ? item.photos.map((photo) => <img src={`${photo.url.baseUrl}!p5`} />) : null}
</>
),
author: item.uploaderInfo.nickName,
pubDate: parseDate(item.createdTime, 'x'),
link: `${baseUrl}/community/set/${item.id}/details`,

View File

@ -1,32 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if details }}
<table>
<tbody>
{{ each details detail }}
<tr>
<th>{{ detail.label }}</th>
<td>
{{ if detail.value?.href && detail.value?.text }}
<a href="{{ detail.value.href }}">{{ detail.value.text }}</a>
{{ else }}
{{ detail.value }}
{{ /if }}
</td>
</tr>
{{ /each }}
</tbody>
</table>
{{ /if }}

View File

@ -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 ? (
<figure key={`${image.src}-${index}`}>
<img src={image.src} alt={image.alt} />
</figure>
) : null
)}
{details ? (
<table>
<tbody>
{details.map((detail, index) => (
<tr key={`${detail.label}-${index}`}>
<th>{detail.label}</th>
<td>{detail.value?.href && detail.value?.text ? <a href={detail.value.href}>{detail.value.text}</a> : detail.value}</td>
</tr>
))}
</tbody>
</table>
) : null}
</>
);
export { fetchItems, rootUrl };

View File

@ -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(
<>
<img src={house.photo_list[0]} style="margin-bottom: 20px;" />
<table>
<tr>
<td></td>
<td>{house.kind_name}</td>
</tr>
<tr>
<td></td>
<td>{house.area} </td>
</tr>
<tr>
<td></td>
<td>{house.floor_str}</td>
</tr>
<tr>
<td></td>
<td>{house.community}</td>
</tr>
<tr>
<td></td>
<td>{house.location}</td>
</tr>
<tr>
<td></td>
<td>{house.refresh_time}</td>
</tr>
<tr>
<td></td>
<td>
{house.rent_tag.map((tag) => (
<code>{tag.name}</code>
))}
</td>
</tr>
</table>
<p>
<a href={`https://rent.591.com.tw/home/${house.post_id}`}>591 </a>
</p>
<br />
<h3></h3>
<br />
<div id="more-pictures">
{photoList.map((photo) => (
<img src={photo} style="margin-bottom: 20px;" />
))}
</div>
</>
);
};
export const route: Route = {
path: '/:country/rent/:query?',

View File

@ -1,52 +0,0 @@
{{set photoList = house.photo_list.slice(1)}}
<img src="{{house.photo_list[0]}}" style="margin-bottom: 20px;">
<table>
<tr>
<td>類型</td>
<td>{{house.kind_name}}</td>
</tr>
<tr>
<td>坪數</td>
<td>{{house.area}} 坪</td>
</tr>
<tr>
<td>樓層</td>
<td>{{house.floor_str}}</td>
</tr>
<tr>
<td>社區</td>
<td>{{house.community}}</td>
</tr>
<tr>
<td>地點</td>
<td>{{house.location}}</td>
</tr>
<tr>
<td>更新時間</td>
<td>{{house.refresh_time}}</td>
</tr>
<tr>
<td>標籤</td>
<td>
{{each house.rent_tag}}
<code>{{$value.name}}</code>
{{/each}}
</td>
</tr>
</table>
<p>更多資訊請見 <a href="https://rent.591.com.tw/home/{{house.post_id}}">591 租屋</a></p>
<br />
<h3>更多圖片</h3>
<br />
<div id="more-pictures">
{{each photoList}}
<img src="{{$value}}" style="margin-bottom: 20px;">
{{/each}}
</div>

View File

@ -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;

View File

@ -1,17 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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 ? <figure>{image.alt ? <img src={image.src} alt={image.alt} /> : <img src={image.src} />}</figure> : null)) : null}
{description ? raw(description) : null}
</>
);

View File

@ -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 ? `<video mute loop="loop" autoplay="autoplay" poster="${poster}"><source src="${video}"></video>` : '';
item.description = renderToString(
<>
{cover ? <img src={cover} /> : null}
{video ? (
<>
<br />
{raw(videoMarkup)}
<br />
</>
) : null}
{description ? raw(description) : null}
{images.map((image) => (image ? <img src={image} /> : null))}
</>
);
item.category = content('.categories a')
.toArray()

View File

@ -1,15 +0,0 @@
{{ if cover }}
<img src="{{ cover }}">
{{ /if }}
{{ if video }}
<br>
<video mute loop="loop" autoplay="autoplay" poster="{{ poster }}"><source src="{{ video }}"></video>
<br>
{{ /if }}
{{ if description }}{{@ description }}{{ /if }}
{{ each images }}
<img src="{{ $value }}">
{{ /each }}

View File

@ -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(
<figure>
<img src={content(this).prop('file')} alt={content(this).prop('alt')} />
</figure>
)
);
});

View File

@ -1,5 +0,0 @@
{{ if image }}
<figure>
<img src="{{ image.src }}" alt="{{ image.alt }}">
</figure>
{{ /if }}

View File

@ -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,
});

View File

@ -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,
});

View File

@ -1 +0,0 @@
<a href="{{ link }}"><img src="{{ poster }} "></a>

View File

@ -0,0 +1,13 @@
import { renderToString } from 'hono/jsx/dom/server';
type IndexTemplateData = {
link: string;
poster: string;
};
export const renderIndexDescription = ({ link, poster }: IndexTemplateData) =>
renderToString(
<a href={link}>
<img src={poster} />
</a>
);

View File

@ -1,3 +0,0 @@
{{ each images }}
<img src="{{ $value }}">
{{ /each }}

View File

@ -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) => (
<img src={image.split("'")[1].replaceAll(String.raw`\/`, '/')} />
))}
</>
);
return item;
})

View File

@ -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 =

View File

@ -1,17 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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 ? (
<figure>
<img src={image.src} alt={image.alt} />
</figure>
) : null
)}
{description ? raw(description) : null}
</>
);

View File

@ -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;

View File

@ -1,21 +0,0 @@
{{ if image }}
<figure>
<img src="{{ image.src }}" alt="{{ image.alt }}">
<figcaption>{{ image.alt }}</figcaption>
</figure>
{{ /if }}
{{ if enclosure }}
<{{ enclosure.type.split(/\//)[0] }} controls>
<source
src="{{ enclosure.src }}"
type="{{ enclosure.type }}">
<object data="{{ enclosure.src }}">
<embed src="{{ enclosure.src }}">
</object>
</{{ enclosure.type.split(/\//)[0] }}>
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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 ? (
<figure>
<img src={image.src} alt={image.alt} />
<figcaption>{image.alt}</figcaption>
</figure>
) : null}
{enclosure && enclosureTag ? (
<>
{(() => {
const EnclosureTag = enclosureTag;
return (
<EnclosureTag controls>
<source src={enclosure.src} type={enclosure.type} />
<object data={enclosure.src}>
<embed src={enclosure.src} />
</object>
</EnclosureTag>
);
})()}
</>
) : null}
{description ? raw(description) : null}
</>
);
};
export const renderDescription = (data: DescriptionData) => renderToString(<AbcDescription {...data} />);

View File

@ -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(),
});

View File

@ -1,27 +0,0 @@
{{ if images }}
{{ each images image }}
{{ if image?.src }}
<figure>
<img
{{ if image.alt }}
alt="{{ image.alt }}"
{{ /if }}
{{ if image.width }}
alt="{{ image.width }}"
{{ /if }}
{{ if image.height }}
alt="{{ image.height }}"
{{ /if }}
src="{{ image.src }}">
</figure>
{{ /if }}
{{ /each }}
{{ /if }}
{{ if intro }}
<blockquote>{{ intro }}</blockquote>
{{ /if }}
{{ if description }}
{{@ description }}
{{ /if }}

View File

@ -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 ? (
<figure>
<img alt={image.height ?? image.width ?? image.alt} src={image.src} />
</figure>
) : null
)
: null}
{intro ? <blockquote>{intro}</blockquote> : null}
{description ? raw(description) : null}
</>
);
export const renderDescription = (data: DescriptionData) => renderToString(<AccessBriefingDescription {...data} />);

Some files were not shown because too many files have changed in this diff Show More