feat(route)!: qweather api host change (#20042)

* feat(route)!: qweather api host change

* chore: typo
This commit is contained in:
la3rence 2025-09-12 18:37:16 +08:00 committed by GitHub
parent b67b4f6afb
commit a7469fe8a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 117 additions and 62 deletions

View File

@ -187,6 +187,7 @@ export type Config = {
};
hefeng: {
key?: string;
apiHost?: string;
};
infzm: {
cookie?: string;
@ -663,6 +664,7 @@ const calculateValue = () => {
},
hefeng: {
key: envs.HEFENG_KEY,
apiHost: envs.HEFENG_API_HOST,
},
infzm: {
cookie: envs.INFZM_COOKIE,

View File

@ -1,15 +1,9 @@
import { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { art } from '@/utils/render';
import path from 'node:path';
import { config } from '@/config';
import ConfigNotFoundError from '@/errors/types/config-not-found';
const WEATHER_API = 'https://devapi.qweather.com/v7/weather/3d';
const AIR_QUALITY_API = 'https://devapi.qweather.com/v7/air/5d';
const CIRY_LOOKUP_API = 'https://geoapi.qweather.com/v2/city/lookup';
import { render3DaysDescription, type WeatherForecastItem } from './util';
const author = 'QWeather';
export const route: Route = {
@ -21,7 +15,11 @@ export const route: Route = {
requireConfig: [
{
name: 'HEFENG_KEY',
description: '',
description: 'QWeather API KEY',
},
{
name: 'HEFENG_API_HOST',
description: 'This is required after 2026/01/01: https://blog.qweather.com/announce/public-api-domain-change-to-api-host/',
},
],
requirePuppeteer: false,
@ -37,10 +35,14 @@ export const route: Route = {
};
async function handler(ctx) {
if (!config.hefeng.key) {
if (!config.hefeng.key || !config.hefeng.apiHost) {
throw new ConfigNotFoundError('QWeather RSS is disabled due to the lack of <a href="https://docs.rsshub.app/zh/install/config#%E5%92%8C%E9%A3%8E%E5%A4%A9%E6%B0%94">relevant config</a>');
}
const WEATHER_API = `https://${config.hefeng.apiHost}/v7/weather/3d`;
const AIR_QUALITY_API = `https://${config.hefeng.apiHost}/v7/air/5d`;
const CIRY_LOOKUP_API = `https://${config.hefeng.apiHost}/geo/v2/city/lookup`;
const id = await cache.tryGet('qweather:' + ctx.req.param('location') + ':id', async () => {
const response = await got(`${CIRY_LOOKUP_API}?location=${ctx.req.param('location')}&key=${config.hefeng.key}`);
return response.data.location[0].id;
@ -67,7 +69,7 @@ async function handler(ctx) {
const combined = {
updateTime: weatherData.updateTime,
fxLink: weatherData.fxLink,
daily: weatherData.daily.map((weatherItem) => {
daily: weatherData.daily.map((weatherItem: WeatherForecastItem) => {
const dailyAirQuality = airQualityData.daily.find((airQualityItem) => airQualityItem.fxDate === weatherItem.fxDate);
if (dailyAirQuality) {
return {
@ -81,11 +83,9 @@ async function handler(ctx) {
return weatherItem;
}),
};
const items = combined.daily.map((item) => ({
const items = combined.daily.map((item: WeatherForecastItem) => ({
title: `${item.fxDate}: ${item.textDay === item.textNight ? item.textDay : item.textDay + '转' + item.textNight} ${item.tempMin}~${item.tempMax}`,
description: art(path.join(__dirname, 'templates/3days.art'), {
item,
}),
description: render3DaysDescription(item),
pubDate: combined.updateTime,
guid: '位置:' + ctx.req.param('location') + '--日期:' + item.fxDate,
link: combined.fxLink,

View File

@ -1,14 +1,10 @@
import { Route } from '@/types';
import cache from '@/utils/cache';
import got from '@/utils/got';
import { art } from '@/utils/render';
import path from 'node:path';
import { parseDate } from '@/utils/parse-date';
import { config } from '@/config';
import ConfigNotFoundError from '@/errors/types/config-not-found';
const rootUrl = 'https://devapi.qweather.com/v7/weather/now?';
import { renderNowDescription, type NowItem } from './util';
export const route: Route = {
path: '/now/:location',
@ -21,6 +17,10 @@ export const route: Route = {
name: 'HEFENG_KEY',
description: '访问 `https://www.qweather.com/` 注册开发 API Key。',
},
{
name: 'HEFENG_API_HOST',
description: 'This is required after 2026/01/01: https://blog.qweather.com/announce/public-api-domain-change-to-api-host/',
},
],
requirePuppeteer: false,
antiCrawler: false,
@ -34,16 +34,18 @@ export const route: Route = {
};
async function handler(ctx) {
if (!config.hefeng.key) {
if (!config.hefeng.key || !config.hefeng.apiHost) {
throw new ConfigNotFoundError('QWeather RSS is disabled due to the lack of <a href="https://docs.rsshub.app/zh/install/config#%E5%92%8C%E9%A3%8E%E5%A4%A9%E6%B0%94">relevant config</a>');
}
const NOW_WEATHER_API = `https://${config.hefeng.apiHost}/v7/weather/now`;
const CIRY_LOOKUP_API = `https://${config.hefeng.apiHost}/geo/v2/city/lookup`;
const id = await cache.tryGet('qweather:' + ctx.req.param('location') + ':id', async () => {
const response = await got(`https://geoapi.qweather.com/v2/city/lookup?location=${ctx.req.param('location')}&key=${config.hefeng.key}`);
const response = await got(`${CIRY_LOOKUP_API}?location=${ctx.req.param('location')}&key=${config.hefeng.key}`);
const data = response.data.location.map((loc) => loc);
return data[0].id;
});
const requestUrl = rootUrl + 'key=' + config.hefeng.key + '&location=' + id;
const requestUrl = `${NOW_WEATHER_API}?key=${config.hefeng.key}&location=${id}`;
const responseData = await cache.tryGet(
'qweather:' + ctx.req.param('location') + ':now',
async () => {
@ -63,9 +65,9 @@ async function handler(ctx) {
return {
title: ctx.req.param('location') + '实时天气',
description: ctx.req.param('location') + '实时天气状况',
item: data.map((item) => ({
item: data.map((item: NowItem) => ({
title: '观测时间:' + time_show,
description: art(path.join(__dirname, 'templates/now.art'), { item }),
description: renderNowDescription(item),
pubDate: timeObj,
guid: '位置:' + ctx.req.param('location') + '--时间:' + time_show,
link: responseData.fxLink,

View File

@ -1,22 +0,0 @@
<text>白天:{{item.textDay}}——夜间:{{item.textNight}}</text>
<br>
<text>气温:{{item.tempMin}}℃~{{item.tempMax}}℃</text>
<br>
<text>相对湿度:{{item.humidity}}%</text>
<br>
<text>空气质量指数:{{item.aqi}} ({{item.aqiCategory}})</text>
<br>
<text>大气压强:{{item.pressure}}百帕</text>
<br>
<text>紫外线强度:{{item.uvIndex}}</text>
<br>
<text>白天风向:{{item.windDirDay}} 风力:{{item.windScaleDay}}级 风速:{{item.windSpeedDay}}公里/小时</text>
<br>
<text>夜间风向:{{item.windDirNight}} 风力:{{item.windScaleNight}}级 风速:{{item.windSpeedNight}}公里/小时</text>
<br>
<text>能见度:{{item.vis}}公里</text>
<br>
<text>日出:{{item.sunrise}} 日落: {{item.sunset}}</text>
<br>
<text>月相:{{item.moonPhase}} 月出:{{item.sunrise}} 月落:{{item.moonset}}
<br>

View File

@ -1,16 +0,0 @@
<text>天气:{{item.text}}</text>
<br>
<text>气温:{{item.temp}}℃</text>
<br>
<text>体感温度:{{item.feelsLike}}℃</text>
<br>
<text>风向:{{item.windDir}}</text>
<br>
<text>风力:{{item.windScale}}级 风速:{{item.windSpeed}}km/h</text>
<br>
<text>湿度:{{item.humidity}}% 大气压强:{{item.pressure}}hPa</text>
<br>
<text>本小时降水量:{{item.precip}}mm</text>
<br>
<text>能见度:{{item.vis}}km</text>
<br>

View File

@ -0,0 +1,89 @@
import { renderToString } from 'hono/jsx/dom/server';
interface WeatherForecastItem {
fxDate: string;
textDay: string;
textNight: string;
tempMin: number;
tempMax: number;
humidity: number;
aqi: number;
aqiCategory: string;
pressure: number;
uvIndex: number;
windDirDay: string;
windScaleDay: number;
windSpeedDay: number;
windDirNight: string;
windScaleNight: number;
windSpeedNight: number;
vis: number;
sunrise: string;
sunset: string;
moonPhase: string;
moonset: string;
}
interface NowItem {
text: string;
temp: number;
feelsLike: number;
windDir: string;
windScale: number;
windSpeed: number;
humidity: number;
pressure: number;
precip: number;
vis: number;
}
const render3DaysDescription = (item: WeatherForecastItem) => renderToString(<WeatherForecast item={item} />);
const renderNowDescription = (item: NowItem) => renderToString(<Now item={item} />);
const WeatherForecast = ({ item }: { item: WeatherForecastItem }) => (
<p>
{item.textDay}{item.textNight}
<br />
{item.tempMin}~{item.tempMax}
<br />
湿{item.humidity}%
<br />
{item.aqi} ({item.aqiCategory})
<br />
{item.pressure}
<br />
线{item.uvIndex}
<br />
{item.windDirDay} {item.windScaleDay} {item.windSpeedDay}/
<br />
{item.windDirNight} {item.windScaleNight} {item.windSpeedNight}/
<br />
{item.vis}
<br />
{item.sunrise} {item.sunset}
<br />
{item.moonPhase} {item.sunrise} {item.moonset}
</p>
);
const Now = ({ item }: { item: NowItem }) => (
<p>
{item.text}
<br />
{item.temp}
<br />
{item.feelsLike}
<br />
{item.windDir}
<br />
{item.windScale} {item.windSpeed}km/h
<br />
湿{item.humidity}% {item.pressure}hPa
<br />
{item.precip}mm
<br />
{item.vis}km
</p>
);
export { render3DaysDescription, renderNowDescription, type WeatherForecastItem, type NowItem };