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:
parent
2cef8cf1cc
commit
cf4e772de8
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ on:
|
|||
- master
|
||||
paths:
|
||||
- 'lib/**/*.ts'
|
||||
- 'lib/**/*.tsx'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
|
|||
|
|
@ -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)'
|
||||
'';
|
||||
|
|
|
|||
|
|
@ -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="referrer" content="no-referrer">
|
||||
</head>
|
||||
<body>
|
||||
{{ content }}
|
||||
</body>
|
||||
</html>
|
||||
">
|
||||
</iframe>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
<p>
|
||||
<img src="{{ imageUrl }}" referrerpolicy="no-referrer">
|
||||
<br>
|
||||
</p>
|
||||
|
|
@ -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,
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
{{ if intro }}
|
||||
<blockquote>{{ intro }}</blockquote>
|
||||
{{ /if }}
|
||||
|
||||
{{ if description }}
|
||||
{{@ description }}
|
||||
{{ /if }}
|
||||
|
|
@ -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} />);
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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}`;
|
||||
|
|
@ -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('|'),
|
||||
};
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
</>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<td>
|
||||
{tags.map((tag) => (
|
||||
<>
|
||||
<a href={`/tag/${tag}`}>{tag}</a>
|
||||
</>
|
||||
))}
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
|
|
@ -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> 
|
||||
{{ /each }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<td>
|
||||
{{ each tags tag }}
|
||||
<a href="/tag/{{ tag }}">{{ tag }}</a> 
|
||||
{{ /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>
|
||||
|
|
@ -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>
|
||||
</>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<td>
|
||||
{tags.map((tag) => (
|
||||
<>
|
||||
<a href={`/tag/${tag}`}>{tag}</a>
|
||||
</>
|
||||
))}
|
||||
</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',
|
||||
|
|
@ -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> 
|
||||
{{ /each }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Tag</th>
|
||||
<td>
|
||||
{{ each tags tag }}
|
||||
<a href="/tag/{{ tag }}">{{ tag }}</a> 
|
||||
{{ /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>
|
||||
|
|
@ -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),
|
||||
}));
|
||||
|
||||
|
|
@ -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'),
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -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,
|
||||
|
|
@ -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}`,
|
||||
|
|
@ -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;
|
||||
})
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{{ text }}
|
||||
|
||||
{{ each medias }}
|
||||
{{ if $value.mimeType.indexOf('image') > -1 }}
|
||||
<img src="{{$value.url}}">
|
||||
{{ /if }}
|
||||
{{ /each }}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{{ if imgsrc }}
|
||||
<img src="{{ imgsrc }}"><br>
|
||||
{{ /if }}
|
||||
{{ if postBody }}
|
||||
{{@ postBody }}
|
||||
{{ /if }}
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
歌手:{{ singer }}<br>
|
||||
专辑:{{ album }}<br>
|
||||
{{ if date }}发行日期:{{ date }}<br>{{ /if }}
|
||||
<img src="{{ picUrl }}">
|
||||
|
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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();
|
||||
|
|
@ -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`],
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
: ''
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
|
|
@ -1 +0,0 @@
|
|||
<img src="{{ url }}" height="{{ height }}" width="{{ width }}">
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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} />);
|
||||
|
|
@ -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}`;
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
@ -1 +0,0 @@
|
|||
<a href="{{ magnet }}">磁力連結</a> | <a href="{{ torrent }}">下載檔案</a>
|
||||
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<blockquote>{{ intro }}</blockquote>
|
||||
<br>
|
||||
{{@ content }}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{{ each images img }}
|
||||
<img src="{{ img.url }}" alt="{{ img.alt }}"><br>
|
||||
{{ /each }}
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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
|
||||
? [
|
||||
{
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{{ if item.url }}
|
||||
<img src="{{ item.url.baseUrl }}!p5">
|
||||
{{ /if }}
|
||||
|
|
@ -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`,
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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?',
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
|
|
@ -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()
|
||||
|
|
@ -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 }}
|
||||
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{{ if image }}
|
||||
<figure>
|
||||
<img src="{{ image.src }}" alt="{{ image.alt }}">
|
||||
</figure>
|
||||
{{ /if }}
|
||||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
<a href="{{ link }}"><img src="{{ poster }} "></a>
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
{{ each images }}
|
||||
<img src="{{ $value }}">
|
||||
{{ /each }}
|
||||
|
|
@ -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;
|
||||
})
|
||||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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} />);
|
||||
|
|
@ -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(),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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
Loading…
Reference in New Issue