fix(cma): fetch full bulletin content from NMC (#22830)
* fix(cma): handle SafeLine challenge * fix(cma): refine channel route handling
This commit is contained in:
parent
9b6585e29b
commit
1950616f04
|
|
@ -1,12 +1,51 @@
|
|||
import { createHash } from 'node:crypto';
|
||||
|
||||
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 ofetch from '@/utils/ofetch';
|
||||
import { parseDate } from '@/utils/parse-date';
|
||||
import timezone from '@/utils/timezone';
|
||||
|
||||
const solveChallenge = (prefix: string, leadingZeroBits: number) => {
|
||||
for (let count = 0; count < 100_000_000; count++) {
|
||||
const suffix = count.toString(16);
|
||||
const hash = createHash('sha1')
|
||||
.update(prefix + suffix)
|
||||
.digest();
|
||||
if (hash.readUInt32BE(0) >>> (32 - leadingZeroBits) === 0) {
|
||||
return suffix;
|
||||
}
|
||||
}
|
||||
throw new Error('Failed to solve the SafeLine challenge of weather.cma.cn');
|
||||
};
|
||||
|
||||
const fetchPageWithChallenge = async (url: string) => {
|
||||
const response = await ofetch.raw<string>(url);
|
||||
const html = response._data ?? '';
|
||||
const challenge = response.headers
|
||||
.getSetCookie()
|
||||
.find((cookie) => cookie.startsWith('safeline_bot_challenge='))
|
||||
?.split(';', 1)[0];
|
||||
const prefix = html.match(/var prefix = '(\w+)';/)?.[1];
|
||||
const leadingZeroBits = Number(html.match(/var leading_zero_bit = (\d+);/)?.[1]);
|
||||
|
||||
if (!challenge || !prefix || !leadingZeroBits) {
|
||||
return html;
|
||||
}
|
||||
|
||||
const answer = challenge.replace('safeline_bot_challenge=', 'safeline_bot_challenge_ans=') + solveChallenge(prefix, leadingZeroBits);
|
||||
|
||||
return ofetch<string>(url, {
|
||||
headers: {
|
||||
Cookie: `${challenge}; ${answer}`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const route: Route = {
|
||||
path: '/channel/:id?',
|
||||
categories: ['forecast'],
|
||||
|
|
@ -15,7 +54,7 @@ export const route: Route = {
|
|||
features: {
|
||||
requireConfig: false,
|
||||
requirePuppeteer: false,
|
||||
antiCrawler: false,
|
||||
antiCrawler: true,
|
||||
supportBT: false,
|
||||
supportPodcast: false,
|
||||
supportScihub: false,
|
||||
|
|
@ -65,11 +104,11 @@ async function handler(ctx) {
|
|||
},
|
||||
});
|
||||
|
||||
const data = response?.data?.pop() ?? {};
|
||||
const article = response?.data?.pop() ?? {};
|
||||
|
||||
data.image = data.image?.replace(/\?.*$/, '') ?? undefined;
|
||||
article.image = article.image?.replace(/\?.*$/, '');
|
||||
|
||||
const { data: currentResponse } = await got(currentUrl);
|
||||
const currentResponse = await fetchPageWithChallenge(currentUrl);
|
||||
|
||||
const $ = load(currentResponse);
|
||||
|
||||
|
|
@ -84,38 +123,32 @@ async function handler(ctx) {
|
|||
const descriptionHtml = $('div.xml').html();
|
||||
const image = new URL($('li.active a img').prop('src'), rootUrl).href;
|
||||
const icon = new URL($('link[rel="shortcut icon"]').prop('href'), rootUrl).href;
|
||||
const sourceElement = $('div.col-xs-8 span')
|
||||
.toArray()
|
||||
.findLast((element) => $(element).text().startsWith('来源'));
|
||||
const itemAuthor = $(sourceElement).text().split(':').pop() || author;
|
||||
|
||||
const items = data
|
||||
? [
|
||||
{
|
||||
title: `${data.title} ${data.releaseTime}`,
|
||||
link: new URL(data.link, rootUrl).href,
|
||||
description: renderToString(
|
||||
<>
|
||||
{data.image ? (
|
||||
<figure>
|
||||
<img src={new URL(data.image, rootUrl).href} alt={data.title} />
|
||||
</figure>
|
||||
) : null}
|
||||
{descriptionHtml ? raw(descriptionHtml) : null}
|
||||
</>
|
||||
),
|
||||
author:
|
||||
$(
|
||||
$('div.col-xs-8 span')
|
||||
.toArray()
|
||||
.findLast((a) => $(a).text().startsWith('来源'))
|
||||
)
|
||||
?.text()
|
||||
?.split(/:/)
|
||||
?.pop() || author,
|
||||
guid: `cma${data.link}#${data.releaseTime.replaceAll(/\s/g, '-')}`,
|
||||
pubDate: timezone(parseDate(data.releaseTime), 8),
|
||||
enclosure_url: new URL(data.image, rootUrl).href,
|
||||
enclosure_type: data.image ? `image/${data.image.split(/\./).pop()}` : undefined,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
const items = [
|
||||
{
|
||||
title: `${article.title} ${article.releaseTime}`,
|
||||
link: new URL(article.link, rootUrl).href,
|
||||
description: renderToString(
|
||||
<>
|
||||
{article.image ? (
|
||||
<figure>
|
||||
<img src={new URL(article.image, rootUrl).href} alt={article.title} />
|
||||
</figure>
|
||||
) : null}
|
||||
{descriptionHtml ? raw(descriptionHtml) : null}
|
||||
</>
|
||||
),
|
||||
author: itemAuthor,
|
||||
guid: `cma${article.link}#${article.releaseTime.replaceAll(/\s/g, '-')}`,
|
||||
pubDate: timezone(parseDate(article.releaseTime), 8),
|
||||
enclosure_url: new URL(article.image, rootUrl).href,
|
||||
enclosure_type: article.image ? `image/${article.image.split(/\./).pop()}` : undefined,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
item: items,
|
||||
|
|
|
|||
Loading…
Reference in New Issue