import type { WithAbortSignal } from "#Source/abort/index.ts" import { generalFetch } from "#Source/request/index.ts" import type { Lang } from "../base.ts" /** * @description 调用和风天气实时天气预警接口时使用的参数。 * * @example * ;``` * const options: WeatherAlertOptions = { * baseUrl: "https://abcxyz.qweatherapi.com", * jwt: "eyJhbGciOiJFZERTQSJ9.payload.signature", * latitude: 39.9, * longitude: 116.4, * localTime: true, * lang: "zh-hans", * timeout: 5_000, * } * ``` */ export interface WeatherAlertOptions extends WithAbortSignal { /** * @description 和风天气 API Base URL,例如 `https://abcxyz.qweatherapi.com`。 */ baseUrl: string /** * @description 用于 `Authorization: Bearer` 请求头的 JWT。 */ jwt: string /** * @description 查询位置的纬度,十进制格式,最多支持小数点后两位。 */ latitude: number | string /** * @description 查询位置的经度,十进制格式,最多支持小数点后两位。 */ longitude: number | string /** * @description 是否返回查询地点的本地时间,`false` 或省略时返回 UTC 时间。 */ localTime?: boolean | undefined /** * @description 返回结果的多语言代码。 */ lang?: Lang | undefined /** * @description 请求超时时间,单位为毫秒。 */ timeout?: number | undefined } /** * @description 实时天气预警响应中的元数据。 * * @example * ;``` * const metadata: WeatherAlertResultMetadata = { * tag: "ec71f87d59c5db45281fecc9f25d136f638ba414ff0a4c4e97258e6d30218aac", * zeroResult: false, * attributions: [ * "https://developer.qweather.com/attribution.html", * "当前预警数据可能存在延迟或信息过时,以官方数据发布为准。", * ], * } * ``` */ export interface WeatherAlertResultMetadata { /** * @description 数据标签。 */ tag: string /** * @description `true` 表示请求成功但当前查询地点没有返回预警数据。 */ zeroResult: boolean /** * @description 数据来源或声明信息,展示当前数据时应一并展示。 */ attributions: string[] } /** * @description 单条预警中的信息性质。 * * @example * ;``` * const messageType: WeatherAlertResultAlertMessageType = { * code: "update", * supersedes: ["202510181140100706230391"], * } * ``` */ export interface WeatherAlertResultAlertMessageType { /** * @description 预警信息性质代码,例如新发布、更新或取消。 */ code: string /** * @description 当前预警取代或取消的后续预警 ID 列表。 */ supersedes?: string[] | undefined } /** * @description 单条预警中的事件类型。 * * @example * ;``` * const eventType: WeatherAlertResultAlertEventType = { * name: "大风", * code: "1006", * } * ``` */ export interface WeatherAlertResultAlertEventType { /** * @description 预警事件类型名称。 */ name: string /** * @description 预警事件类型代码。 */ code: string } /** * @description 单条预警中的颜色信息。 * * @example * ;``` * const color: WeatherAlertResultAlertColor = { * code: "blue", * red: 30, * green: 50, * blue: 205, * alpha: 1, * } * ``` */ export interface WeatherAlertResultAlertColor { /** * @description 预警颜色代码。 */ code: string /** * @description 预警颜色的红色分量值,范围为 0-255。 */ red: number /** * @description 预警颜色的绿色分量值,范围为 0-255。 */ green: number /** * @description 预警颜色的蓝色分量值,范围为 0-255。 */ blue: number /** * @description 预警颜色的透明度分量值,范围为 0-1。 */ alpha: number } /** * @description 实时天气预警响应中的单条预警信息。 * * @example * ;``` * const alert: WeatherAlertResultAlert = { * id: "202510241119105837988676", * senderName: "临桂区气象台", * issuedTime: "2025-10-24T11:19+08:00", * messageType: { * code: "update", * supersedes: ["202510181140100706230391"], * }, * eventType: { * name: "大风", * code: "1006", * }, * urgency: null, * severity: "minor", * certainty: null, * icon: "1006", * color: { * code: "blue", * red: 30, * green: 50, * blue: 205, * alpha: 1, * }, * effectiveTime: "2025-10-24T11:19+08:00", * onsetTime: "2025-10-24T11:19+08:00", * expireTime: "2025-10-25T11:19+08:00", * headline: "临桂区气象台更新大风蓝色预警信号", * description: * "临桂区气象台24日11时19分继续发布大风蓝色预警信号:预计未来24小时内临桂将出现6级(或阵风7级)以上大风,请做好防范。", * criteria: * "24小时内可能受大风影响,平均风力可达6级以上,或者阵风7级以上;或者已经受大风影响,平均风力为6~7级,或者阵风7~8级并可能持续。", * instruction: * "1. 政府及有关部门按照职责做好防大风工作。\n2. 关好门窗,加固围板、棚架、广告牌等易被风吹动的搭建物。", * responseTypes: [], * } * ``` */ export interface WeatherAlertResultAlert { /** * @description 本条预警信息的唯一标识。 */ id: string /** * @description 预警发布机构名称,可能为空。 */ senderName: string | null /** * @description 原始预警信息生成时间。 */ issuedTime: string /** * @description 当前预警的消息性质。 */ messageType: WeatherAlertResultAlertMessageType /** * @description 当前预警的事件类型。 */ eventType: WeatherAlertResultAlertEventType /** * @description 预警信息的紧迫程度,可能为空。 */ urgency: string | null /** * @description 预警信息的严重程度。 */ severity: string /** * @description 预警信息的确定性或可信度,可能为空。 */ certainty: string | null /** * @description 预警对应的图标代码。 */ icon: string /** * @description 当前预警的颜色信息。 */ color: WeatherAlertResultAlertColor /** * @description 预警生效时间,可能为空。 */ effectiveTime: string | null /** * @description 预警事件预计开始时间,可能为空。 */ onsetTime: string | null /** * @description 预警失效时间。QWeather 示例 JSON 使用 `expireTime` 字段。 */ expireTime?: string | null | undefined /** * @description 预警失效时间。QWeather 字段说明中将该字段写作 `expiredTime`,保留以兼容文档差异。 */ expiredTime?: string | null | undefined /** * @description 预警信息的简要描述或标题。 */ headline: string /** * @description 预警信息的详细描述。 */ description: string /** * @description 当前预警的触发标准或条件,可能为空。 */ criteria: string | null /** * @description 针对当前预警的防御指南或行动指导,可能为空。 */ instruction: string | null /** * @description 当前预警对应的应对方式类型代码列表,可能为空数组。 */ responseTypes: string[] } /** * @description 和风天气实时天气预警接口的响应结果。 * * @example * ;``` * const result: WeatherAlertResult = { * metadata: { * tag: "ec71f87d59c5db45281fecc9f25d136f638ba414ff0a4c4e97258e6d30218aac", * zeroResult: false, * attributions: [ * "https://developer.qweather.com/attribution.html", * "当前预警数据可能存在延迟或信息过时,以官方数据发布为准。", * ], * }, * alerts: [ * { * id: "202510241119105837988676", * senderName: "临桂区气象台", * issuedTime: "2025-10-24T11:19+08:00", * messageType: { * code: "update", * supersedes: ["202510181140100706230391"], * }, * eventType: { * name: "大风", * code: "1006", * }, * urgency: null, * severity: "minor", * certainty: null, * icon: "1006", * color: { * code: "blue", * red: 30, * green: 50, * blue: 205, * alpha: 1, * }, * effectiveTime: "2025-10-24T11:19+08:00", * onsetTime: "2025-10-24T11:19+08:00", * expireTime: "2025-10-25T11:19+08:00", * headline: "临桂区气象台更新大风蓝色预警信号", * description: * "临桂区气象台24日11时19分继续发布大风蓝色预警信号:预计未来24小时内临桂将出现6级(或阵风7级)以上大风,请做好防范。", * criteria: * "24小时内可能受大风影响,平均风力可达6级以上,或者阵风7级以上;或者已经受大风影响,平均风力为6~7级,或者阵风7~8级并可能持续。", * instruction: * "1. 政府及有关部门按照职责做好防大风工作。\n2. 关好门窗,加固围板、棚架、广告牌等易被风吹动的搭建物。", * responseTypes: [], * }, * ], * } * ``` */ export interface WeatherAlertResult { /** * @description 响应元数据。 */ metadata: WeatherAlertResultMetadata /** * @description 当前查询地点生效中的天气预警列表。 */ alerts?: WeatherAlertResultAlert[] | undefined } const internalNormalizeWeatherAlertCoordinate = ( value: number | string, name: "latitude" | "longitude", ): string => { if (typeof value === "number") { if (Number.isFinite(value) === false) { throw new TypeError(`WeatherAlert ${name} must be a finite number.`) } return String(value) } const normalized = value.trim() if (normalized === "") { throw new TypeError(`WeatherAlert ${name} must not be empty.`) } return normalized } const internalValidateWeatherAlertOptions = ( options: WeatherAlertOptions, ): { baseUrl: string jwt: string latitude: string longitude: string } => { const baseUrl = options.baseUrl.trim() const jwt = options.jwt.trim() const latitude = internalNormalizeWeatherAlertCoordinate(options.latitude, "latitude") const longitude = internalNormalizeWeatherAlertCoordinate(options.longitude, "longitude") if (baseUrl === "") { throw new TypeError("WeatherAlert baseUrl must not be empty.") } if (jwt === "") { throw new TypeError("WeatherAlert jwt must not be empty.") } return { baseUrl, jwt, latitude, longitude, } } /** * @description 获取指定经纬度坐标的实时天气预警信息。 * * @see {@link https://dev.qweather.com/docs/api/warning/weather-alert/} */ export const weatherAlert = async (options: WeatherAlertOptions): Promise => { const { baseUrl, jwt, latitude, longitude } = internalValidateWeatherAlertOptions(options) const { localTime, lang, timeout, abortSignal } = options return await generalFetch< { query: { localTime?: boolean | undefined lang?: Lang | undefined } }, { type: "json" data: WeatherAlertResult } >({ baseUrl, path: `/weatheralert/v1/current/${latitude}/${longitude}`, method: "get", headers: { Authorization: `Bearer ${jwt}`, }, query: { localTime, lang, }, responseType: "json", timeout, abortSignal, }).getJson() }