WeaveBit GFW 自动检测与报警：Python 教学示例
=========================================
需要 Python 3.10 或更新版本，只使用标准库，无需安装第三方依赖。

默认运行不联网、不读取密钥、不产生检测或费用：
  python3 gfw_monitor.py --config monitor-config.json --state ./private-monitor-state --max-cost 0.10

monitor-config.json 默认演示 DNS 污染检测。修改为自己获准检测的域名。
GFW 全量检测的配置示例：
  {"check_type":"gfw_full_domain_check","target":[{"domain":"example.com"}],"nodes":["当前有效节点ID"]}
节点 ID 从正式 API 或 Console 获取，不要使用显示名称；DNS 污染请求不传 nodes。

只有明确添加 --live，且在服务端环境中设置 WEAVEBIT_API_KEY 后才会访问正式 API。
创建批次可能计费。不要将密钥写进配置、仓库、网页、日志或 shell 历史。
--max-cost 是每批次的客户端预估预算，不是服务端扣费上限、每日限额或固定报价。
程序还会检查预估币种和可用余额；实际价格、余额和节点可能变化。

调度与恢复
----------
本示例不安装或启用任何定时任务。发布流水线或外部调度器可调用同一命令。
如果状态目录中有未完成批次，继续查询原批次；没有待处理批次时，--live 会创建新轮次。
不能通过反复调用创建接口等待结果。创建结果不明时复用已保存的请求和幂等 ID。
示例在创建状态达到 23 小时时保守停止重放，避免跨过 API 的 24 小时幂等窗口。
已知批次过期时也停止并要求人工核对，不自动再购买新一轮检测。

状态目录应位于网页根目录之外，使用私有权限和持久存储；不同配置使用不同目录。
本地锁防止单机可靠文件系统上的重叠执行。进程异常退出可能留下锁，确认进程已退出并
核对状态后再由管理员处理。POSIX 创建权限不能替代 Windows ACL 或多机数据库锁。
每次执行最多六轮查询，客户端查询等待预算为 120 秒；读完已完成批次的全部分页。
标准库请求超时并非操作系统级硬取消，慢速持续传输可能超出预期等待；严格期限需外部进程监管。
HTTP、网络和业务错误会停止当前调用，保留状态；按错误类别和 Retry-After 安排后续重试。
服务端执行期限、结果到期时间与客户端等待预算是不同概念，详见英文说明及官方 API 文档。

报警与通知
----------
这不是托管报警服务，也不是官方轻量 SDK 的替代品。示例以整个目标清单为报警单元：
明确异常、正常和无法判定分别处理；批次 completed 不能直接解释为正常。
每个独立完成的异常轮次输出 anomaly；重复查询不算新轮次，不增加异常确认次数。
null、缺失观测或范围变化不能清除旧异常；原范围全部正常才可能产生恢复事件。
若多个未解决的异常覆盖集合发生变化，需要人工复核，不能让新集合覆盖旧集合。
这里没有实现多轮阈值、提醒节流、逐域名事件管理和完整的生产通知队列。

程序只将事件 JSON 写到标准输出，不会发送 Telegram、邮件、Webhook 或其他消息。
结果文件 results/*.json 先持久化完整结果和稳定 event_id，不标记为已送达，也不自动删除。
通知桥接程序应维护持久化投递队列、确认记录与失败重试。
若输出丢失或投递失败，可以离线重放所有保存事件，不联网、不创建检测：
  python3 gfw_monitor.py --state ./private-monitor-state --replay-events
桥接程序按 event_id 检查投递确认，仅重试未确认事件；不要重建检测来重发通知。

退出码 0：已处理正常或异常轮次，仍须读取 verdict，不代表网站健康或通知已送达。
退出码 2：等待中或无法判定，保留状态并继续处理；退出码 1：程序/API/配置错误。
错误事件写到标准错误，应作为监控链路故障单独上报。

离线验证（全为模拟响应，不调用真实 API、不读取真实凭据、不发送通知）：
  python3 -m unittest -v test_gfw_monitor.py

免费体验入口：https://www.weavebit.com/zh/dns-check
正式 API 文档：https://www.weavebit.com/zh/docs/api
结果说明：https://www.weavebit.com/zh/docs/results
请勿自动化操作免费网页的人机验证，也不要将 /free-dns-api 当成正式接入接口。
本客户端只消费公开结果，不包含或透露 DNS 污染内部判断方式。
