fix(routes/miyuki): format news titles with bracketed categories and keep full-text body-only (#21377)

* fix(routes/miyuki): format news titles with bracketed categories and keep full-text body-only

* fix(routes/miyuki): clean news body extraction and normalize images

- Updated lib/routes/miyuki/news.ts to keep item titles as plain article titles and store category information only in the `category` field.
- Switched the route to use the canonical Miyuki domain and added the route `url` metadata.
- Refined full-text extraction so `description` keeps only the main article body and excludes page header metadata such as section title, article title, date, and category.
- Fixed malformed article-body handling for pages like `10217`, where the source HTML structure breaks the original body selector.
- Normalized article images by deduplicating `for_pc` / `for_sp` duplicates, converting relative image URLs to absolute URLs, and replacing list-based photo blocks with plain containers so RSS readers do not render black bullets.
- Resolved the related CodeFactor/lint issues and verified the file with `oxlint` and `eslint`.

* feat(apple): add full-text Apple Newsroom route

Add /apple/newsroom for the official Apple Newsroom China feed.
Fetch article pages to provide full-text content with cached detail requests, and normalize article metadata and media URLs.
Remove gallery navigation, download buttons, video playback controls, empty wrapper nodes, and other non-content UI elements from articles.
Format image and video captions as distinct descriptions, and drop duplicate captions when their text matches the article body.

* fix(routes/miyuki): simplify image handling in news descriptions

* Remove unrelated changes from PR
This commit is contained in:
LinxHex 2026-03-13 23:57:36 +08:00 committed by GitHub
parent 75cb4a3fb9
commit df6572b659
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 63 additions and 9 deletions

View File

@ -6,13 +6,21 @@ import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import timezone from '@/utils/timezone';
const ORIGIN = 'https://miyuki.jp';
const ORIGIN = 'https://www.miyuki.jp';
const NEWS_LINK = `${ORIGIN}/s/y10/news/list`;
const DETAIL_HEADER_SELECTOR = [
'.pc__news_detail__title',
'.pc__news_detail__title__japanese',
'.news_detail__date',
'.news_detail__title',
'.news_detail__ganre',
].join(', ');
export const route: Route = {
path: '/news',
example: '/miyuki/news',
name: 'News',
url: 'www.miyuki.jp/s/y10/news/list',
categories: ['new-media'],
maintainers: ['KarasuShin'],
features: {
@ -33,21 +41,18 @@ async function handler() {
const items = await Promise.all(
$('.list__side_border li')
.toArray()
.map(async (item) => {
.map((item) => {
const $item = $(item);
const link = `${ORIGIN}${$item.find('a').attr('href')!}`;
return await cache.tryGet(link, async () => {
return cache.tryGet(link, async () => {
const category = $item.find('p span').last().text();
const title = $item.find('a').text();
return {
title: `${category} - ${$item.find('a').text()}`,
title,
link,
pubDate: timezone(parseDate($item.find('p span').first().text()), +9),
category: [category],
description: await cache.tryGet(link, async () => {
const detailHtml = await ofetch(link);
const $detail = load(detailHtml);
return $detail('.contents_area__inner').html()!;
}),
description: await getDescription(link),
} as DataItem;
});
})
@ -59,3 +64,52 @@ async function handler() {
item: items,
};
}
async function getDescription(link: string) {
const detailHtml = await ofetch(link);
const $ = load(detailHtml);
const content = $('.contents_area__inner');
content.children(DETAIL_HEADER_SELECTOR).remove();
normalizePhotoLists($, content);
content.children().each((_, element) => {
const child = $(element);
if (!hasMeaningfulHtml(child.html())) {
child.remove();
}
});
return content.html()?.trim() ?? '';
}
function hasMeaningfulHtml(html?: string | null) {
return Boolean(html?.replaceAll(/<br\s*\/?>/g, '').replaceAll('&nbsp;', '').trim());
}
function normalizePhotoLists($, content) {
content.find('.news_detail__photo_list').each((_, element) => {
const list = $(element);
const items = list
.children('li')
.toArray()
.flatMap((item) => {
const photoItem = $(item);
photoItem.find('img.for_sp').remove();
photoItem.find('img').each((__, image) => {
const img = $(image);
const src = img.attr('src');
if (!src) {
img.remove();
return;
}
img.attr('src', new URL(src, ORIGIN).href);
img.removeAttr('class');
});
const html = photoItem.html();
return hasMeaningfulHtml(html) ? [`<div>${html!.trim()}</div>`] : [];
});
list.replaceWith(items.join('<br /><br />'));
});
}