Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
token: ${{ secrets.GIST_TOKEN }}
gist_id: 862db28df04be7523476d10b61b128f2
gist_description: " iRingo: 🌤 WeatherKit β"
file_path: dist/iRingo.WeatherKit.dev.plugin
file_path: dist/iRingo.WeatherKit.dev.lpx
- name: Deploy Quantumult X Snippet
uses: exuanbo/actions-deploy-gist@main
with:
Expand Down
43 changes: 43 additions & 0 deletions .github/workflows/loon-validation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Loon validation

on:
workflow_dispatch:
pull_request:
paths: ["src/**", "tests/**", "scripts/**", "template/loon.handlebars", "arguments-builder.*.config.ts", "package*.json", "rollup.config.mjs", ".github/workflows/loon-validation.yml"]
push:
branches: ["sync/**"]

permissions:
contents: read
packages: read

jobs:
test-and-build:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install locked dependencies
run: npm ci --ignore-scripts --no-audit --no-fund
env:
NODE_AUTH_TOKEN: ${{ github.token }}
- name: Run regressions
run: npm test
- name: Set fixture-only build version
run: npm pkg set version=0.0.0-test
- name: Build and verify native Loon artifacts
run: npm run build:loon
- uses: actions/upload-artifact@v4
with:
name: weatherkit-loon-validation
path: |
dist/iRingo.WeatherKit.lpx
dist/*.loon.bundle.js
dist/LICENSE.txt
if-no-files-found: error
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
### 🆕 New Features
* none
* 为 Loon 增加有界供应商缓存、相同请求合并、失败冷却与可选运行统计,减少重复处理与额外查询。

### 🛠️ Bug Fixes
* 修复 iOS 27.3 Beta 主天气 App 的 WeatherKit v3 兼容:协商 Apple JSON,适配原生读取器需要的状态与降水结构,逐产品校验并保留未知数据。
* 修复内置空气质量算法的单位、无效值、零浓度、插值与分级边界,保留六项有效污染物浓度;修正原始 Apple QWeather CO 假标、加拿大 AQHI 换算与美标 InstantCast 精度。
* 修复来源失败隔离、预警网页错误与过期记录处理;等待原生 HTTP 回调后单次完成,保留既有 Apple 数据。
* 修复 WeatherKit Response 处理范围未同时受用户设置和 URL `dataSets` 约束的问题:现在只处理两者的交集,避免地图天气小组件只请求当前天气与空气质量时扩展处理其他数据集。
* 修复和风天气历史空气质量接口路径多余斜杠的问题,确保请求使用正确的 `/v7/historical/air` 路径。

### 🔣 Dependencies
* 增加 @noble/hashes@1.8.0,用于无 Node/WebCrypto 依赖的缓存键哈希。
* 将 FlatBuffer Root 处理器迁移至已发布的 `@nsnanocat/flatbuffer-root@1.0.0`,移除仓库内重复实现。

### ‼️ Breaking Changes
* none
* Loon 原生条件与参数对象要求 3.5.1(983);v3 替换限定主天气 App,其他后台客户端保留原传输。

### 🔄 Other Changes
* Loon 使用独立的 request.loon.bundle.js、response.loon.bundle.js 和 scale.loon.bundle.js;其他客户端沿用原版 generic bundle。
* 开发版 Loon 文件统一为 .lpx,更新生成与部署引用;增加源码回归、实际压缩脚本和原生表达式检查。
* 从 BoxJs 设置中隐藏仅供插件参数使用的 `Storage` 选项,保留运行时配置行为。
* 补充数据集交集、用户设置过滤、FlatBuffer 根槽映射和 QWeather 接口路径的回归测试。
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
#  iRingo: 🌤 WeatherKit

Loon WeatherKit v3 主应用兼容、空气质量算法与有界供应商处理的实现和验证边界见 [Loon v3 兼容说明](docs/loon-v3-compatibility.md)。版本、插件名称和下载地址沿用本项目发布流程。

2026-11-14 后续修复移除了附加请求缓存、在途合并、失败冷却、统计、额外地区推断和备用查询;保留原有设置和来源,新增默认 Auto 的预警正文语言偏好。失败保留、periodic、304/ETag、超时单位与未知预警到期值的回归及本次上游候选构建结果见 [后续修复与验证](docs/verified-followup-2026-11-14.md)。
3 changes: 2 additions & 1 deletion arguments-builder.dev.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// User-facing weather parameters; retain the original defaults.
import { defineConfig } from "@iringo/arguments-builder";
import { airQualityFull, api, calculateFull, dataSetsFull, logLevel, nextHourFull, storage, weatherAlerts, weatherFull } from "./arguments-builder.full.config";

Expand All @@ -8,7 +9,7 @@ export default defineConfig({
template: "./template/surge.dev.handlebars",
},
loon: {
path: "./dist/iRingo.WeatherKit.dev.plugin",
path: "./dist/iRingo.WeatherKit.dev.lpx",
template: "./template/loon.dev.handlebars",
},
customItems: [
Expand Down
16 changes: 15 additions & 1 deletion arguments-builder.full.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// WeatherKit user settings; preserve original parameters.
import { defineConfig } from "@iringo/arguments-builder";

type Arg = {
Expand Down Expand Up @@ -127,7 +128,20 @@ const weatherAlertsProvider: Arg = {
description: "使用选定的数据源处理天气预警数据。",
};

export const weatherAlerts = [weatherAlertsProvider];
const weatherAlertsLanguage: Arg = {
key: "WeatherAlerts.Language",
name: "[天气预警] 正文语言偏好",
defaultValue: "Auto",
type: "string",
options: [
{ key: "Auto", label: "自动(沿用请求语言)" },
{ key: "zh-Hans", label: "简体中文(来源支持时)" },
{ key: "en", label: "English(来源支持时)" },
],
description: "仅向所选来源请求预警语言,不使用第三方翻译。来源不支持或返回其他语言时保留原文、签发单位与出处;Apple 原始标题和页面标签可能仍随系统语言。",
};

export const weatherAlerts = [weatherAlertsProvider, weatherAlertsLanguage];

const nextHourProvider: Arg = {
key: "NextHour.Provider",
Expand Down
1 change: 1 addition & 0 deletions arguments-builder.release.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// User-facing weather parameters; retain the original defaults.
import { defineConfig } from "@iringo/arguments-builder";
import { airQuality, api, calculate, dataSets, logLevel, nextHour, output, storage, weather, weatherAlerts } from "./arguments-builder.full.config";

Expand Down
37 changes: 37 additions & 0 deletions docs/loon-v3-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Loon WeatherKit v3 兼容与供应商处理边界

2026-11-05 核对 [Loon Rewrite](https://nsloon.app/docs/Rewrite/rewrite_v2/)、[Script](https://nsloon.app/docs/Script/script_v2/)、[Script API](https://nsloon.app/docs/Script/script_api/) 与[插件](https://nsloon.app/docs/Plugin/);2026-11-14 重新核对 Script API 与插件参数约定。最低 Loon 版本保持 3.5.1(983)。本文描述源码与自动验证范围,不包含个人订阅、设备授权或原始位置记录;最新固定基准及检查见 [后续修复记录](verified-followup-2026-11-14.md)。

## v3 主应用传输

原生 Rewrite 只为 HTTPS Apple v3 天气 GET 请求、主天气 App 的 iOS/macOS User-Agent 前缀协商 Apple JSON。响应还要求 200 和 application/json,使用必要 URL Guard、一个 script Action 及原生参数对象。其余后台客户端与未识别的 WK3 二进制保留 Apple 原传输;不使用 WK2 schema 解码 WK3。

Apple 的 WK3 FlatBuffer JSON 表示与主应用 JSON 读取器不同。后者要求字符串天气状态、标量降水和每日降水总量;原生 Mac 日志曾明确报告数组类型不匹配及缺字段,并清缓存走 CloudKit 回退。现将已知原始 Apple 产品和供应商结果适配为该读取器所需表示,也处理未选择数据替换的已知分时产品;未知产品和字段保留。独立 condition 记录数组(包括空数组)不当作字符串概述处理。

供应商整数、时间、Unicode 与有限数值按已知字段校验;异常按产品回退。没有改动时保留原正文、空白与缓存验证头;改写后移除旧 bodyBytes、长度和校验标记,设置 no-store。JSON 最多 1,048,576 UTF-16 码元及 4 MiB UTF-8,每产品累计数组元素 8192、深度 64、节点 100000。它们是脚本工作边界,无法限制宿主已接收的 HTTP 缓冲。

## 空气质量与请求开销

11 种内置算法与 None 保留六项有效物理浓度,按模型定义参与指数计算。修复空值、负数、非有限值、零浓度、单位、分级与插值边界;加拿大 AQHI 使用正确 ppb 换算及最近整数取整,美标 InstantCast 保留 WAQI 臭氧分段并按浓度精度截断。研究模型和当前浓度换算不等于官方日均发布值,不新增后台采样。

原始 Apple QWeather 的 CO 存在 mg 数值配 µg 标签的历史格式,只有该入口修复假标;已归一化供应商结果依据明确单位处理,不按浓度大小猜测。已有有效浓度优先,只补缺项;完整六项数据没有补充请求。模型未定义的污染物不填 index=-1,物理浓度仍保留。先匹配原版指数目标列表再应用算法,默认 HJ6332012。默认保留已有昨日对比,缺失或明确开启重算时使用所选的独立昨日来源;污染物补齐不绕过指数目标。

2026-11-14 移除附加的供应商请求缓存、在途合并、队列/发起预算、失败冷却、摘要统计、额外地区推断和备用查询。旧保存值不能重新激活这些行为。无状态 ProviderTransport 只校验当前请求的状态、格式及正文边界;原有位置网格条件请求和昨日对比缓存保留。来源失败或无有效实体的 304 不会制造可用数据,单产品错误不阻止其他健康产品;正常单请求超时仍保留,所有已发请求等待原生回调后只完成一次。

原有 12 项参数和默认值保持,新增第 13 项 WeatherAlerts.Language,默认 Auto。它只向当前预警来源请求中文或英文,不调用外部翻译;来源不支持时保留原文、签发单位和出处。显式偏好使用 no-store 详情响应及 und 消息语言标记,Auto 保留原请求语言和旧行为,不改变天气/空气质量等其他产品语言。未知到期值使用安全的 UTC 午夜占位字符串,真实时间原样保留,不把未知值制造成事件结束时间。

原有按需解码、实际修改产品编码和预警延迟复制保留。完整六项污染物与原生功能列表无需额外供应商工作;普通空气质量标尺通过原生 Rewrite 处理,HK/CN 自定义图例使用独立小脚本。这里的处理边界不构成 CPU、内存或耗电改善比例的实测结论。

## 构建与验证

Loon 使用 request.loon.bundle.js、response.loon.bundle.js、scale.loon.bundle.js;原版其他客户端的 generic bundle 文件名和模板继续保留。版本由原版发布流程决定,本改动不指定 Fork 版本或下载地址。CI 中的 0.0.0-test 仅用于生成验证夹具,不是可订阅发行版本。

构建同时生成 dist/LICENSE.txt,保留项目 Apache-2.0。已删除的缓存/离线地理实现不再打入运行文件,其旧出处记录保留为历史;原 LICENSE、贡献者与源码中引用工作的署名不变。

当前 `npm test` 通过 430 项回归,覆盖算法、供应商失败、保留的条件缓存、WK2 编解码、WK3 开发/发布入口、预警语言及未知时间。`npm run build:loon` 编译本 PR 的原生入口并生成 Loon 文件,52 个实际编译脚本 VM 场景与 15 个表达式/路由/jq 检查通过;原 12 项默认值与新增 Auto 同时核对。这些检查不等于 Loon 真实配置解析器或设备耗电测量。

Native macOS observations on 2026-11-06 and 07 covered all eleven algorithms and None, six physical concentrations, the original target-standard restriction, and disabled datasets. Warning banners and four full warning details were visible. The system-log snapshot contained no decoding errors, invalid weather or CloudKit fallback. The actual candidate runtime files were byte-identical to the three artifacts from commit 4d1f127. See [Mac native observations](mac-native-validation.md) for the exact scope and limitations.

The earlier locked Node v22.23.4 CI passed 335 regressions and 40 compiled VM scenarios on the older artifact. Current local validation uses Node v22.23.5 and the separate counts above; it does not turn older device evidence into validation of this rebuilt artifact. The 0.0.0-test version is only a build fixture. Valid dedicated QWeather credentials, the final all-WeatherKit source combination, background/watch customization, long-running VPN operation and device battery or memory percentages remain outside comprehensive native acceptance.

Original behavior and default settings are documented in [the original behavior audit](original-behavior-audit.md).
32 changes: 32 additions & 0 deletions docs/mac-native-validation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Mac native observations

Observed on 2026-11-06 and 07, macOS 27.3 (26C5035r), Loon for Mac 0.5.0 (1011). The installed candidate was fetched from a fixed GitHub commit; exactly one WeatherKit installation was enabled. Values below describe public test-city previews at the time of observation.

| City | Algorithm | Displayed index/category | CO (ug/m3) | Concentrations |
| --- | --- | --- | --- | --- |
| Binhai | EU_EAQI | 不佳 | 400 | Six present |
| Wuxi | UBA | 普通 | 400 | Six present |
| Ningbo | WAQI_InstantCast_US | 107 | 1100 | Six present |
| Guiyang | WAQI_InstantCast_CN | 39 | 200 | Six present |
| Xianyang | WAQI_InstantCast_CN_25_DRAFT | 107 | 1200 | Six present |
| Mianyang | CA_AQHI | 5 | 700 | Six present |
| Foshan | HK_AQHI | 4 | 1000 | Six present |
| Zhuzhou | AQHI_Multi_CN | 2 | 500 | Six present |
| Huanggang | AQHI_Multi_CN_HK | 3 | 400 | Six present |
| Luoyang | CN_DEATH_AQHI | 3 | 400 | Six present |
| Wuhu | CN_DEATH_HK_AQHI | 3 | 800 | Six present |
| Yantai | None | 17 | 400 | Six present |

Binhai showed warning banners. Native Safari displayed four full warning descriptions, issuers and source links. Manchester retained UK DAQI 3, BreezoMeter and the original comparison while CA_AQHI was selected, with no provider requests. With all replacement datasets disabled, Laoshan retained Apple current weather, hourly and ten-day forecasts, AQI 22 and two original warnings (marine strong wind and cold wave).

The 129411-line system-log snapshot contained zero WeatherAlert/other decoding errors, invalidWeather records or CloudKit fallback. The last script snapshot recorded 72 executions, 72 finished and zero exceptions. These are snapshots at different times, not a long-running stability guarantee.

[Source CI 4d1f127](https://github.com/hhhoratioxu/WeatherKit/actions/runs/42234270265) performed a locked Node v22.23.4 install and passed 335 regressions, 40 compiled scenarios, 15 expression checks and twelve original defaults. The downloaded five-file ZIP SHA-256 was 2c564605fd9ee25762a2bb9effefa17ed77d82d40c018c8bec0990ddfb51cd67. Its three runtime files were byte-identical to the actual Mac candidate:

- request: d8e75cb50be818668e26fa7c08b66c471d5fac2b8d63db73a1e5eb6eed333bfd
- response: 1408002e7fa6cdded1e5aef824c1e6daeb88367e19b9651e2ef0d1b89db544a0
- scale: 5896bada8857312ebf5a70b7519452d21c8fbd6131e0df882d8d5f83b813016f

## Limits

Valid dedicated QWeather credentials were not configured, so authentication-failure fallback is not reported as successful authorized API acceptance. The final combination with every source set to WeatherKit was not independently repeated on this candidate. Widgets, watch/background custom replacement, long-running VPN operation and device battery/memory percentages were not comprehensively accepted. No original device bodies, authentication headers, precise coordinates or personal configuration are included.
Loading