From d520444e9f5f8ce9a7142cddd14e33ae62995dbb Mon Sep 17 00:00:00 2001
From: glidea <740696441@qq.com>
Date: Thu, 5 Jun 2025 23:29:37 +0800
Subject: [PATCH] add rss & crawl & webhook
---
Makefile | 9 +-
README-en.md | 6 +-
README.md | 19 +-
docs/config-zh.md | 69 ++--
docs/config.md | 267 +++++++------
docs/crawl-zh.md | 88 +++++
docs/images/folo-html.png | Bin 0 -> 161022 bytes
docs/model-selection-zh.md | 12 +
docs/roadmap-zh.md | 19 +
docs/rss-api-zh.md | 59 +++
docs/tech/rewrite-zh.md | 4 +-
docs/wehook-zh.md | 148 +++++++
go.mod | 4 +-
go.sum | 11 +-
main.go | 43 +-
pkg/api/api.go | 69 ++--
pkg/api/http/http.go | 23 +-
pkg/api/rss/rss.go | 231 +++++++++++
pkg/config/config.go | 35 +-
pkg/llm/llm.go | 83 +++-
pkg/llm/openai.go | 12 +-
pkg/llm/prompt/prompt.go | 156 ++++++++
pkg/model/model.go | 71 ++++
pkg/notify/channel/channel.go | 7 +-
pkg/notify/channel/email.go | 20 +-
pkg/notify/channel/webhook.go | 14 +-
pkg/notify/notify.go | 12 +-
pkg/notify/route/route.go | 60 +--
pkg/rewrite/rewrite.go | 373 +++++-------------
pkg/rewrite/rewrite_test.go | 4 +
pkg/schedule/rule/periodic.go | 2 +-
pkg/schedule/rule/rule.go | 6 +-
pkg/scrape/scraper/rss.go | 1 -
pkg/scrape/scraper/scraper.go | 6 +-
pkg/storage/feed/block/block.go | 62 +--
.../feed/block/index/inverted/inverted.go | 24 +-
.../block/index/inverted/inverted_test.go | 46 ++-
pkg/storage/kv/kv.go | 24 +-
pkg/telemetry/log/log.go | 5 +-
pkg/telemetry/server/server.go | 137 +++++++
pkg/util/crawl/crawl.go | 176 +++++++++
pkg/util/{rpc/rpc.go => jsonrpc/jsonrpc.go} | 34 +-
.../rpc_test.go => jsonrpc/jsonrpc_test.go} | 9 +-
43 files changed, 1757 insertions(+), 703 deletions(-)
create mode 100644 docs/crawl-zh.md
create mode 100644 docs/images/folo-html.png
create mode 100644 docs/model-selection-zh.md
create mode 100644 docs/roadmap-zh.md
create mode 100644 docs/rss-api-zh.md
create mode 100644 docs/wehook-zh.md
create mode 100644 pkg/api/rss/rss.go
create mode 100644 pkg/llm/prompt/prompt.go
create mode 100644 pkg/telemetry/server/server.go
create mode 100644 pkg/util/crawl/crawl.go
rename pkg/util/{rpc/rpc.go => jsonrpc/jsonrpc.go} (75%)
rename pkg/util/{rpc/rpc_test.go => jsonrpc/jsonrpc_test.go} (96%)
diff --git a/Makefile b/Makefile
index 8a8a492..ff676c4 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@ REGISTRY ?= glidea
FULL_IMAGE_NAME = $(REGISTRY)/$(IMAGE_NAME)
-.PHONY: test push build-installer
+.PHONY: test push dev-push
test:
go test -race -v -coverprofile=coverage.out -coverpkg=./... ./...
@@ -16,3 +16,10 @@ push:
-t $(FULL_IMAGE_NAME):$(VERSION) \
-t $(FULL_IMAGE_NAME):latest \
--push .
+
+dev-push:
+ docker buildx create --use --name multi-platform-builder || true
+ docker buildx build --platform linux/amd64,linux/arm64 \
+ --build-arg VERSION=$(VERSION) \
+ -t $(FULL_IMAGE_NAME):$(VERSION) \
+ --push .
diff --git a/README-en.md b/README-en.md
index 66de421..684790a 100644
--- a/README-en.md
+++ b/README-en.md
@@ -73,7 +73,7 @@ Just for the exquisite email styles, install and use it now!
### 1. Installation
-By default, uses SiliconFlow's Qwen/Qwen2.5-7B-Instruct (free) and Pro/BAAI/bge-m3. If you don't have a SiliconFlow account yet, use this [invitation link](https://cloud.siliconflow.cn/i/U2VS0Q5A) to get a ¥14 credit.
+By default, uses SiliconFlow's Qwen/Qwen3-8B (free) and Pro/BAAI/bge-m3. If you don't have a SiliconFlow account yet, use this [invitation link](https://cloud.siliconflow.cn/i/U2VS0Q5A) to get a ¥14 credit.
Support for other vendors or models is available; follow the instructions below.
@@ -84,7 +84,7 @@ curl -L -O https://raw.githubusercontent.com/glidea/zenfeed/main/docker-compose.
# If you need to customize more configuration parameters, directly edit docker-compose.yml#configs.zenfeed_config.content BEFORE running the command below.
# Configuration Docs: https://github.com/glidea/zenfeed/blob/main/docs/config.md
-API_KEY=your_apikey TZ=your_local_IANA LANG=English docker-compose -p zenfeed up -d
+API_KEY=your_apikey TZ=your_local_IANA LANGUAGE=English docker-compose -p zenfeed up -d
```
#### Windows
@@ -94,7 +94,7 @@ Invoke-WebRequest -Uri "https://raw.githubusercontent.com/glidea/zenfeed/main/do
# If you need to customize more configuration parameters, directly edit docker-compose.yml#configs.zenfeed_config.content BEFORE running the command below.
# Configuration Docs: https://github.com/glidea/zenfeed/blob/main/docs/config.md
-$env:API_KEY = "your_apikey"; $env:TZ = "your_local_IANA"; $env:LANG = "English"; docker-compose -p zenfeed up -d
+$env:API_KEY = "your_apikey"; $env:TZ = "your_local_IANA"; $env:LANGUAGE = "English"; docker-compose -p zenfeed up -d
```
### 2. Using the Web UI
diff --git a/README.md b/README.md
index 9661a80..2607d73 100644
--- a/README.md
+++ b/README.md
@@ -6,16 +6,19 @@
**1. AI 版 RSS 阅读器**
+* 在线服务
+ * https://zenfeed.xyz
+ * 或 Folo 搜索 zenfeed
+
**2. 实时 “新闻” 知识库**
**3. 帮你时刻关注 “指定事件” 的秘书(如 “关税政策变化”,“xx 股票波动”)**,并支持整理研究报告
-开箱即用的公共服务站:https://zenfeed.xyz (集成 Hacker News,Github Trending,V2EX 热榜等常见公开信源)
-
-每日研究报告(包含播客)(实验性质)
+每日研究报告(包含播客)(实验性质) -- 已暂停更新
* [V2EX](https://v2ex.analysis.zenfeed.xyz/)
* [LinuxDO](https://linuxdo.analysis.zenfeed.xyz/)
+---
技术说明文档见:[HLD](docs/tech/hld-zh.md)
## 前言
@@ -98,7 +101,7 @@ zenfeed 是你的智能信息助手。它自动收集、筛选并总结关注的
### 1. 安装
> 最快 1min 拉起
-默认使用硅基流动的 Qwen/Qwen2.5-7B-Instruct(免费) 和 Pro/BAAI/bge-m3。如果你还没有硅基账号,使用 [邀请链接](https://cloud.siliconflow.cn/i/U2VS0Q5A) 得 14 元额度
+默认使用硅基流动的 Qwen/Qwen3-8B (免费) 和 Pro/BAAI/bge-m3。如果你还没有硅基账号,使用 [邀请链接](https://cloud.siliconflow.cn/i/U2VS0Q5A) 得 14 元额度
如果需要使用其他厂商或模型,或自定义部署:请编辑下方 **docker-compose.yml**#configs.zenfeed_config.content.
参考 [配置文档](https://github.com/glidea/zenfeed/blob/main/docs/config-zh.md)
@@ -142,6 +145,14 @@ $env:API_KEY = "硅基流动apikey"; docker-compose -p zenfeed up -d
以 Cherry Studio 为例,配置 MCP 并连接到 Zenfeed,见 [Cherry Studio MCP](docs/cherry-studio-mcp.md)
> 默认地址 http://localhost:1301/sse
+### 后续
+
+zenfeed 提供了超多的自定义配置,还有很多玩法等待你挖掘。详细请查阅[文档](/docs/)
+
+### Roadmap
+
+[Roadmap](/docs/roadmap-zh.md)
+
## 欢迎加群讨论
> 使用问题请提 Issue,谢绝微信私聊。帮助有类似问题的朋友
diff --git a/docs/config-zh.md b/docs/config-zh.md
index 380c226..9240669 100644
--- a/docs/config-zh.md
+++ b/docs/config-zh.md
@@ -1,19 +1,22 @@
-| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
-| :--------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :------------- |
-| `timezone` | `string` | 应用的时区。例如 `Asia/Shanghai`。 | 服务器本地时区 | 否 |
-| `log` | `object` | 日志配置。详见下方的 **日志配置** 部分。 | (见具体字段) | 否 |
-| `api` | `object` | API 配置。详见下方的 **API 配置** 部分。 | (见具体字段) | 否 |
-| `llms` | `列表` | 大语言模型 (LLM) 配置。会被其他配置部分引用。详见下方的 **LLM 配置** 部分。 | `[]` | 是 (至少 1 个) |
-| `scrape` | `object` | 抓取配置。详见下方的 **抓取配置** 部分。 | (见具体字段) | 否 |
-| `storage` | `object` | 存储配置。详见下方的 **存储配置** 部分。 | (见具体字段) | 否 |
-| `scheduls` | `object` | 用于监控 Feed 的调度配置 (也称为监控规则)。详见下方的 **调度配置** 部分。 | (见具体字段) | 否 |
-| `notify` | `object` | 通知配置。它接收来自调度模块的结果,通过路由配置进行分组,并通过通知渠道发送给通知接收者。详见下方的 **通知配置**, **通知路由**, **通知接收者**, **通知渠道** 部分。 | (见具体字段) | 是 |
+| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
+| :---------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- | :------------- |
+| `timezone` | `string` | 应用的时区。例如 `Asia/Shanghai`。 | 服务器本地时区 | 否 |
+| `telemetry` | `object` | Telemetry 配置。详见下方的 **Telemetry 配置** 部分。 | (见具体字段) | 否 |
+| `api` | `object` | API 配置。详见下方的 **API 配置** 部分。 | (见具体字段) | 否 |
+| `llms` | `列表` | 大语言模型 (LLM) 配置。会被其他配置部分引用。详见下方的 **LLM 配置** 部分。 | `[]` | 是 (至少 1 个) |
+| `jina` | `object` | Jina AI 配置。详见下方的 **Jina AI 配置** 部分。 | (见具体字段) | 否 |
+| `scrape` | `object` | 抓取配置。详见下方的 **抓取配置** 部分。 | (见具体字段) | 否 |
+| `storage` | `object` | 存储配置。详见下方的 **存储配置** 部分。 | (见具体字段) | 否 |
+| `scheduls` | `object` | 用于监控 Feed 的调度配置 (也称为监控规则)。详见下方的 **调度配置** 部分。 | (见具体字段) | 否 |
+| `notify` | `object` | 通知配置。它接收来自调度模块的结果,通过路由配置进行分组,并通过通知渠道发送给通知接收者。详见下方的 **通知配置**, **通知路由**, **通知接收者**, **通知渠道** 部分。 | (见具体字段) | 是 |
-### 日志配置 (`log`)
+### Telemetry 配置 (`telemetry`)
-| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
-| :---------- | :------- | :--------------------------------------------------------- | :----- | :------- |
-| `log.level` | `string` | 日志级别, 可选值为 `debug`, `info`, `warn`, `error` 之一。 | `info` | 否 |
+| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
+| :-------------------- | :------- | :----------------------------------------------------------------------------- | :----------- | :------- |
+| `telemetry.address` | `string` | 暴露 Prometheus 指标 & pprof。 | | 否 |
+| `telemetry.log` | `object` | Telemetry 相关的日志配置。 | (见具体字段) | 否 |
+| `telemetry.log.level` | `string` | Telemetry 相关消息的日志级别, 可选值为 `debug`, `info`, `warn`, `error` 之一。 | `info` | 否 |
### API 配置 (`api`)
@@ -40,6 +43,14 @@
| `llms[].embedding_model` | `string` | LLM 的 Embedding 模型。例如 `text-embedding-3-small`。如果用于 Embedding,则不能为空。如果此 LLM 被使用,则不能与 `model` 同时为空。**注意:** 初次使用后请勿直接修改,应添加新的 LLM 配置。 | | 条件性必需 |
| `llms[].temperature` | `float32` | LLM 的温度 (0-2)。 | `0.0` | 否 |
+### Jina AI 配置 (`jina`)
+
+此部分用于配置 Jina AI Reader API 的相关参数,主要供重写规则中的 `crawl_by_jina` 类型使用。
+
+| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
+| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----- | :------- |
+| `jina.token` | `string` | Jina AI 的 API Token。从 [Jina AI API Dashboard](https://jina.ai/api-dashboard/) 获取。提供 Token 可以获得更高的服务速率限制。如果留空,将以匿名用户身份请求,速率限制较低。 | | 否 |
+
### 抓取配置 (`scrape`)
| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
@@ -88,15 +99,16 @@
定义在存储前处理 Feed 的规则。规则按顺序应用。
-| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
-| :--------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------- | :--------------------------------------------- |
-| `...rewrites[].source_label` | `string` | 用作转换源文本的 Feed 标签。默认标签包括: `type`, `source`, `title`, `link`, `pub_time`, `content`。 | `content` | 否 |
-| `...rewrites[].skip_too_short_threshold` | `*int` | 如果设置,`source_label` 文本长度低于此阈值的 Feed 将被此规则跳过 (处理将继续进行下一条规则,如果没有更多规则则进行 Feed 存储)。有助于过滤掉过短/信息量不足的 Feed。 | `300` | 否 |
-| `...rewrites[].transform` | `object` | 配置如何转换 `source_label` 文本。详见下方的 **重写规则转换配置**。如果未设置,则直接使用 `source_label` 文本进行匹配。 | `nil` | 否 |
-| `...rewrites[].match` | `string` | 用于匹配 (转换后) 文本的简单字符串。不能与 `match_re` 同时设置。 | | 否 (使用 `match` 或 `match_re`) |
-| `...rewrites[].match_re` | `string` | 用于匹配 (转换后) 文本的正则表达式。 | `.*` (匹配所有) | 否 (使用 `match` 或 `match_re`) |
-| `...rewrites[].action` | `string` | 匹配时执行的操作: `create_or_update_label` (使用匹配/转换后的文本添加/更新标签), `drop_feed` (完全丢弃该 Feed)。 | `create_or_update_label` | 否 |
-| `...rewrites[].label` | `string` | 要创建或更新的 Feed 标签名称。 | | 是 (如果 `action` 是 `create_or_update_label`) |
+| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
+| :--------------------------------------- | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------- | :--------------------------------------------- |
+| `...rewrites[].if` | `字符串列表` | 用于匹配 Feed 的条件配置。如果未设置,则表示匹配所有 Feed。类似于标签过滤器,例如 `["source=github", "title!=xxx"]`。如果条件不满足,则跳过此规则。 | `[]` (匹配所有) | 否 |
+| `...rewrites[].source_label` | `string` | 用作转换源文本的 Feed 标签。默认标签包括: `type`, `source`, `title`, `link`, `pub_time`, `content`。 | `content` | 否 |
+| `...rewrites[].skip_too_short_threshold` | `*int` | 如果设置,`source_label` 文本长度低于此阈值的 Feed 将被此规则跳过 (处理将继续进行下一条规则,如果没有更多规则则进行 Feed 存储)。有助于过滤掉过短/信息量不足的 Feed。 | `300` | 否 |
+| `...rewrites[].transform` | `object` | 配置如何转换 `source_label` 文本。详见下方的 **重写规则转换配置**。如果未设置,则直接使用 `source_label` 文本进行匹配。 | `nil` | 否 |
+| `...rewrites[].match` | `string` | 用于匹配 (转换后) 文本的简单字符串。不能与 `match_re` 同时设置。 | | 否 (使用 `match` 或 `match_re`) |
+| `...rewrites[].match_re` | `string` | 用于匹配 (转换后) 文本的正则表达式。 | `.*` (匹配所有) | 否 (使用 `match` 或 `match_re`) |
+| `...rewrites[].action` | `string` | 匹配时执行的操作: `create_or_update_label` (使用匹配/转换后的文本添加/更新标签), `drop_feed` (完全丢弃该 Feed)。 | `create_or_update_label` | 否 |
+| `...rewrites[].label` | `string` | 要创建或更新的 Feed 标签名称。 | | 是 (如果 `action` 是 `create_or_update_label`) |
### 重写规则转换配置 (`storage.feed.rewrites[].transform`)
@@ -106,10 +118,13 @@
### 重写规则转换为文本配置 (`storage.feed.rewrites[].transform.to_text`)
-| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
-| :------------------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------- | :------- |
-| `...to_text.llm` | `string` | 用于转换的 LLM 名称 (来自 `llms` 部分)。 | `llms` 部分中的默认 LLM | 否 |
-| `...to_text.prompt` | `string` | 用于转换的 Prompt。源文本将被注入。可以使用 Go 模板语法引用内置 Prompt: `{{ .summary }}`, `{{ .category }}`, `{{ .tags }}`, `{{ .score }}`, `{{ .comment_confucius }}`, `{{ .summary_html_snippet }}`。 | | 是 |
+此配置定义了如何将 `source_label` 的文本进行转换。
+
+| 字段 | 类型 | 描述 | 默认值 | 是否必需 |
+| :------------------ | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | :--------------------------- |
+| `...to_text.type` | `string` | 转换的类型。可选值:
- `prompt` (默认): 使用 LLM 和指定的 Prompt 转换源文本。
- `crawl`: 将源文本视为 URL,直接抓取该 URL 指向的网页内容,并将其转换为 Markdown 格式。此方式为本地抓取,会尝试遵循 `robots.txt`。
- `crawl_by_jina`: 将源文本视为 URL,通过 [Jina AI Reader API](https://jina.ai/reader/) 抓取和处理网页内容,并返回 Markdown。功能可能更强大,例如处理动态页面,但依赖 Jina AI 服务。
| `prompt` | 否 |
+| `...to_text.llm` | `string` | **仅当 `type` 为 `prompt` 时有效。** 用于转换的 LLM 名称 (来自 `llms` 部分)。如果未指定,将使用在 `llms` 部分中标记为 `default: true` 的 LLM。 | `llms` 部分中的默认 LLM | 否 |
+| `...to_text.prompt` | `string` | **仅当 `type` 为 `prompt` 时有效。** 用于转换的 Prompt。源文本将被注入。可以使用 Go 模板语法引用内置 Prompt: `{{ .summary }}`, `{{ .category }}`, `{{ .tags }}`, `{{ .score }}`, `{{ .comment_confucius }}`, `{{ .summary_html_snippet }}`。 | | 是 (如果 `type` 是 `prompt`) |
### 调度配置 (`scheduls`)
diff --git a/docs/config.md b/docs/config.md
index 1358094..79fffa8 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1,181 +1,196 @@
-| Field | Type | Description | Default | Required |
-| :------- | :----- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | :-------- |
-| timezone | string | The timezone of the app. e.g. `Asia/Shanghai`. | server's local timezone | No |
-| log | object | The log config. See **Log Configuration** section below. | (see fields) | No |
-| api | object | The API config. See **API Configuration** section below. | (see fields) | No |
-| llms | list | The LLMs config. Refered by other config sections. See **LLM Configuration** section below. | `[]` | Yes (>=1) |
-| scrape | object | The scrape config. See **Scrape Configuration** section below. | (see fields) | No |
-| storage | object | The storage config. See **Storage Configuration** section below. | (see fields) | No |
-| scheduls | object | The scheduls config for monitoring feeds (aka monitoring rules). See **Scheduls Configuration** section below. | (see fields) | No |
-| notify | object | The notify config. It receives results from scheduls, groups them via route config, and sends to receivers via channels. See **Notify Configuration**, **Notify Route**, **Notify Receiver**, **Notify Channels** sections below. | (see fields) | Yes |
+| Field | Type | Description | Default Value | Required |
+| :---------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------- | :--------------- |
+| `timezone` | `string` | The application's timezone. E.g., `Asia/Shanghai`. | Server local time | No |
+| `telemetry` | `object` | Telemetry configuration. See the **Telemetry Configuration** section below. | (See specific fields) | No |
+| `api` | `object` | API configuration. See the **API Configuration** section below. | (See specific fields) | No |
+| `llms` | `list` | Large Language Model (LLM) configuration. Referenced by other configuration sections. See the **LLM Configuration** section below. | `[]` | Yes (at least 1) |
+| `jina` | `object` | Jina AI configuration. See the **Jina AI Configuration** section below. | (See specific fields) | No |
+| `scrape` | `object` | Scrape configuration. See the **Scrape Configuration** section below. | (See specific fields) | No |
+| `storage` | `object` | Storage configuration. See the **Storage Configuration** section below. | (See specific fields) | No |
+| `scheduls` | `object` | Scheduling configuration for monitoring feeds (also known as monitoring rules). See the **Scheduling Configuration** section below. | (See specific fields) | No |
+| `notify` | `object` | Notification configuration. It receives results from the scheduling module, groups them via routing configuration, and sends them to notification receivers via notification channels. See the **Notification Configuration**, **Notification Routing**, **Notification Receivers**, **Notification Channels** sections below. | (See specific fields) | Yes |
-### Log Configuration (`log`)
+### Telemetry Configuration (`telemetry`)
-| Field | Type | Description | Default | Required |
-| :---------- | :----- | :-------------------------------------------------- | :------ | :------- |
-| `log.level` | string | Log level, one of `debug`, `info`, `warn`, `error`. | `info` | No |
+| Field | Type | Description | Default Value | Required |
+| :-------------------- | :------- | :--------------------------------------------------------------------------------- | :-------------------- | :------- |
+| `telemetry.address` | `string` | Exposes Prometheus metrics & pprof. | | No |
+| `telemetry.log` | `object` | Log configuration related to telemetry. | (See specific fields) | No |
+| `telemetry.log.level` | `string` | Log level for telemetry-related messages, one of `debug`, `info`, `warn`, `error`. | `info` | No |
-**API Configuration (`api`)**
+### API Configuration (`api`)
-| Field | Type | Description | Default | Required |
-| :----------------- | :----- | :------------------------------------------------------------------------------------------------------------------ | :---------------------------- | :------------------------------------- |
-| `api.http` | object | The HTTP API config. | (see fields) | No |
-| `api.http.address` | string | The address (`[host]:port`) of the HTTP API. e.g. `0.0.0.0:1300`. Cannot be changed after the app is running. | `:1300` | No |
-| `api.mcp` | object | The MCP API config. | (see fields) | No |
-| `api.mcp.address` | string | The address (`[host]:port`) of the MCP API. e.g. `0.0.0.0:1301`. Cannot be changed after the app is running. | `:1301` | No |
-| `api.llm` | string | The LLM name for summarizing feeds. e.g. `my-favorite-gemini-king`. Refers to an LLM defined in the `llms` section. | default LLM in `llms` section | Yes (if summarization feature is used) |
+| Field | Type | Description | Default Value | Required |
+| :----------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | :--------------------- |
+| `api.http` | `object` | HTTP API configuration. | (See specific fields) | No |
+| `api.http.address` | `string` | Address for the HTTP API (`[host]:port`). E.g., `0.0.0.0:1300`. Cannot be changed after the application starts. | `:1300` | No |
+| `api.mcp` | `object` | MCP API configuration. | (See specific fields) | No |
+| `api.mcp.address` | `string` | Address for the MCP API (`[host]:port`). E.g., `0.0.0.0:1301`. Cannot be changed after the application starts. | `:1301` | No |
+| `api.llm` | `string` | Name of the LLM used for summarizing feeds. E.g., `my-favorite-gemini-king`. Refers to an LLM defined in the `llms` section. | Default LLM in `llms` section | Yes (if using summary) |
### LLM Configuration (`llms[]`)
-This section defines a list of available Large Language Models. At least one LLM configuration is required.
+This section defines the list of available Large Language Models. At least one LLM configuration is required.
-| Field | Type | Description | Default | Required |
-| :----------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------- | :------------------------------------------------------------- |
-| `llms[].name` | string | The name (or 'id') of the LLM. e.g. `my-favorite-gemini-king`. Used to refer to this LLM in other sections (`api.llm`, `storage.feed.embedding_llm`, etc.). | | Yes |
-| `llms[].default` | bool | Whether this LLM is the default LLM. Only one LLM can be the default. | `false` | No (but one must be `true` if default behavior is relied upon) |
-| `llms[].provider` | string | The provider of the LLM, one of `openai`, `openrouter`, `deepseek`, `gemini`, `volc`, `siliconflow`. e.g. `openai`. | | Yes |
-| `llms[].endpoint` | string | The custom endpoint of the LLM. e.g. `https://api.openai.com/v1`. | (provider specific default) | No |
-| `llms[].api_key` | string | The API key of the LLM. | | Yes |
-| `llms[].model` | string | The model of the LLM. e.g. `gpt-4o-mini`. Cannot be empty if used for generation tasks (like summarization). Cannot be empty with `embedding_model` at same time if this LLM is used. | | Conditionally Yes |
-| `llms[].embedding_model` | string | The embedding model of the LLM. e.g. `text-embedding-3-small`. Cannot be empty if used for embedding. Cannot be empty with `model` at same time if this LLM is used. **NOTE:** Do not modify after initial use; add a new LLM config instead. | | Conditionally Yes |
-| `llms[].temperature` | float32 | The temperature (0-2) of the LLM. | `0.0` | No |
+| Field | Type | Description | Default Value | Required |
+| :----------------------- | :-------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------- | :--------------------------------------------------------- |
+| `llms[].name` | `string` | Name (or 'id') of the LLM. E.g., `my-favorite-gemini-king`. Used to refer to this LLM in other configuration sections (e.g., `api.llm`, `storage.feed.embedding_llm`). | | Yes |
+| `llms[].default` | `bool` | Whether this LLM is the default LLM. Only one LLM can be the default. | `false` | No (but one must be `true` if relying on default behavior) |
+| `llms[].provider` | `string` | Provider of the LLM, one of `openai`, `openrouter`, `deepseek`, `gemini`, `volc`, `siliconflow`. E.g., `openai`. | | Yes |
+| `llms[].endpoint` | `string` | Custom endpoint for the LLM. E.g., `https://api.openai.com/v1`. | (Provider-specific default) | No |
+| `llms[].api_key` | `string` | API key for the LLM. | | Yes |
+| `llms[].model` | `string` | Model of the LLM. E.g., `gpt-4o-mini`. Cannot be empty if used for generation tasks (e.g., summarization). If this LLM is used, cannot be empty along with `embedding_model`. | | Conditionally Required |
+| `llms[].embedding_model` | `string` | Embedding model of the LLM. E.g., `text-embedding-3-small`. Cannot be empty if used for embedding. If this LLM is used, cannot be empty along with `model`. **Note:** Do not modify directly after initial use; add a new LLM configuration instead. | | Conditionally Required |
+| `llms[].temperature` | `float32` | Temperature of the LLM (0-2). | `0.0` | No |
+
+### Jina AI Configuration (`jina`)
+
+This section configures parameters related to the Jina AI Reader API, primarily used by the `crawl_by_jina` type in rewrite rules.
+
+| Field | Type | Description | Default Value | Required |
+| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :------- |
+| `jina.token` | `string` | API Token for Jina AI. Obtain from [Jina AI API Dashboard](https://jina.ai/api-dashboard/). Providing a token grants higher service rate limits. If left empty, requests will be made as an anonymous user with lower rate limits. | | No |
### Scrape Configuration (`scrape`)
-| Field | Type | Description | Default | Required |
-| :----------------------- | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :-------------------------------- |
-| `scrape.past` | duration | The lookback time window for scraping feeds. e.g. `1h` means only scrape feeds in the past 1 hour. | `24h` | No |
-| `scrape.interval` | duration | How often to scrape each source (global default). e.g. `1h`. | `1h` | No |
-| `scrape.rsshub_endpoint` | string | The endpoint of the RSSHub. You can deploy your own or use a public one (see [RSSHub Docs](https://docs.rsshub.app/guide/instances)). e.g. `https://rsshub.app`. | | Yes (if `rsshub_route_path` used) |
-| `scrape.sources` | list of objects | The sources for scraping feeds. See **Scrape Source Configuration** below. | `[]` | Yes (at least one) |
+| Field | Type | Description | Default Value | Required |
+| :----------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :----------------------------------- |
+| `scrape.past` | `time.Duration` | Time window to look back when scraping feeds. E.g., `1h` means only scrape feeds from the past 1 hour. | `24h` | No |
+| `scrape.interval` | `time.Duration` | Frequency to scrape each source (global default). E.g., `1h`. | `1h` | No |
+| `scrape.rsshub_endpoint` | `string` | Endpoint for RSSHub. You can deploy your own RSSHub server or use a public instance (see [RSSHub Documentation](https://docs.rsshub.app/guide/instances)). E.g., `https://rsshub.app`. | | Yes (if `rsshub_route_path` is used) |
+| `scrape.sources` | `list of objects` | List of sources to scrape feeds from. See **Scrape Source Configuration** below. | `[]` | Yes (at least one) |
### Scrape Source Configuration (`scrape.sources[]`)
Describes each source to be scraped.
-| Field | Type | Description | Default | Required |
-| :-------------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------- | :-------------- | :-------------------------- |
-| `scrape.sources[].interval` | duration | How often to scrape this specific source. Overrides the global `scrape.interval`. | global interval | No |
-| `scrape.sources[].name` | string | The name of the source. Used for labeling feeds. | | Yes |
-| `scrape.sources[].labels` | map[string]string | Additional key-value labels to add to feeds from this source. | `{}` | No |
-| `scrape.sources[].rss` | object | The RSS config for this source. See **Scrape Source RSS Configuration** below. Only one source type (e.g., RSS) can be set per source. | `nil` | Yes (if source type is RSS) |
+| Field | Type | Description | Default Value | Required |
+| :-------------------------- | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------ | :---------------- | :-------------------------- |
+| `scrape.sources[].interval` | `time.Duration` | Frequency to scrape this specific source. Overrides global `scrape.interval`. | Global `interval` | No |
+| `scrape.sources[].name` | `string` | Name of the source. Used to tag feeds. | | Yes |
+| `scrape.sources[].labels` | `map[string]string` | Additional key-value labels to attach to feeds from this source. | `{}` | No |
+| `scrape.sources[].rss` | `object` | RSS configuration for this source. See **Scrape Source RSS Configuration** below. Each source can only have one type set (e.g., RSS). | `nil` | Yes (if source type is RSS) |
### Scrape Source RSS Configuration (`scrape.sources[].rss`)
-| Field | Type | Description | Default | Required |
-| :--------------------------------------- | :----- | :------------------------------------------------------------------------------------------------------------------------------------ | :------ | :---------------------------------------------------- |
-| `scrape.sources[].rss.url` | string | The full URL of the RSS feed. e.g. `http://localhost:1200/github/trending/daily/any`. Cannot be set if `rsshub_route_path` is set. | | Yes (unless `rsshub_route_path` is set) |
-| `scrape.sources[].rss.rsshub_route_path` | string | The RSSHub route path. e.g. `github/trending/daily/any`. Will be joined with `scrape.rsshub_endpoint`. Cannot be set if `url` is set. | | Yes (unless `url` is set, requires `rsshub_endpoint`) |
+| Field | Type | Description | Default Value | Required |
+| :--------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :-------------------------------------------------------- |
+| `scrape.sources[].rss.url` | `string` | Full URL of the RSS feed. E.g., `http://localhost:1200/github/trending/daily/any`. Cannot be set if `rsshub_route_path` is set. | | Yes (unless `rsshub_route_path` is set) |
+| `scrape.sources[].rss.rsshub_route_path` | `string` | RSSHub route path. E.g., `github/trending/daily/any`. Will be concatenated with `scrape.rsshub_endpoint` to form the final URL. Cannot be set if `url` is set. | | Yes (unless `url` is set, and requires `rsshub_endpoint`) |
### Storage Configuration (`storage`)
-| Field | Type | Description | Default | Required |
-| :------------- | :----- | :------------------------------------------------------------------------------- | :----------- | :------- |
-| `storage.dir` | string | The base directory for all storages. Cannot be changed after the app is running. | `./data` | No |
-| `storage.feed` | object | The feed storage config. See **Feed Storage Configuration** below. | (see fields) | No |
+| Field | Type | Description | Default Value | Required |
+| :------------- | :------- | :------------------------------------------------------------------------------ | :-------------------- | :------- |
+| `storage.dir` | `string` | Base directory for all storage. Cannot be changed after the application starts. | `./data` | No |
+| `storage.feed` | `object` | Feed storage configuration. See **Feed Storage Configuration** below. | (See specific fields) | No |
### Feed Storage Configuration (`storage.feed`)
-| Field | Type | Description | Default | Required |
-| :---------------------------- | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | :------- |
-| `storage.feed.rewrites` | list of objects | How to process each feed before storing it. Inspired by Prometheus relabeling. See **Rewrite Rule Configuration** below. | `[]` | No |
-| `storage.feed.flush_interval` | duration | How often to flush feed storage to the database. Higher value risks data loss but improves performance. | `200ms` | No |
-| `storage.feed.embedding_llm` | string | The name of the LLM (from `llms` section) used for embedding feeds. Affects semantic search accuracy. **NOTE:** If changing, keep the old LLM config defined as past data relies on it. | default LLM in `llms` section | No |
-| `storage.feed.retention` | duration | How long to keep a feed. | `8d` | No |
-| `storage.feed.block_duration` | duration | How long to keep each time-based feed storage block (similar to Prometheus TSDB Block). | `25h` | No |
+| Field | Type | Description | Default Value | Required |
+| :---------------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------- | :------- |
+| `storage.feed.rewrites` | `list of objects` | How to process each feed before storing it. Inspired by Prometheus relabeling. See **Rewrite Rule Configuration** below. | `[]` | No |
+| `storage.feed.flush_interval` | `time.Duration` | Frequency to flush feed storage to the database. Higher values risk more data loss but reduce disk operations and improve performance. | `200ms` | No |
+| `storage.feed.embedding_llm` | `string` | Name of the LLM used for feed embedding (from `llms` section). Significantly impacts semantic search accuracy. **Note:** If switching, keep the old LLM configuration as past data is implicitly associated with it, otherwise past data cannot be semantically searched. | Default LLM in `llms` section | No |
+| `storage.feed.retention` | `time.Duration` | Retention duration for feeds. | `8d` | No |
+| `storage.feed.block_duration` | `time.Duration` | Retention duration for each time-based feed storage block (similar to Prometheus TSDB Block). | `25h` | No |
### Rewrite Rule Configuration (`storage.feed.rewrites[]`)
-Defines rules to process feeds before storage. Rules are applied in order.
+Defines rules to process feeds before storage. Rules are applied sequentially.
-| Field | Type | Description | Default | Required |
-| :--------------------------------------- | :----- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------- | :-------------------------------------------- |
-| `...rewrites[].source_label` | string | The feed label to use as the source text for transformation. Default labels: `type`, `source`, `title`, `link`, `pub_time`, `content`. | `content` | No |
-| `...rewrites[].skip_too_short_threshold` | *int | If set, feeds where the `source_label` text length is below this threshold are skipped by this rule (processing continues with the next rule or feed storage if no more rules). Helps filter short/uninformative feeds. | `300` | No |
-| `...rewrites[].transform` | object | Configures how to transform the `source_label` text. See **Rewrite Rule Transform Configuration** below. If unset, the `source_label` text is used directly for matching. | `nil` | No |
-| `...rewrites[].match` | string | A simple string to match against the (transformed) text. Cannot be set with `match_re`. | | No (use `match` or `match_re`) |
-| `...rewrites[].match_re` | string | A regular expression to match against the (transformed) text. | `.*` (matches all) | No (use `match` or `match_re`) |
-| `...rewrites[].action` | string | Action to perform if matched: `create_or_update_label` (adds/updates a label with the matched/transformed text), `drop_feed` (discards the feed entirely). | `create_or_update_label` | No |
-| `...rewrites[].label` | string | The feed label name to create or update. | | Yes (if `action` is `create_or_update_label`) |
+| Field | Type | Description | Default Value | Required |
+| :--------------------------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------- | :-------------------------------------------- |
+| `...rewrites[].if` | `list of strings` | Conditions to match feeds. If not set, matches all feeds. Similar to label filters, e.g., `["source=github", "title!=xxx"]`. If conditions are not met, this rule is skipped. | `[]` (matches all) | No |
+| `...rewrites[].source_label` | `string` | Feed label used as the source text for transformation. Default labels include: `type`, `source`, `title`, `link`, `pub_time`, `content`. | `content` | No |
+| `...rewrites[].skip_too_short_threshold` | `*int` | If set, feeds where the `source_label` text length is below this threshold will be skipped by this rule (processing continues to the next rule, or feed storage if no more rules). Helps filter out feeds that are too short/uninformative. | `300` | No |
+| `...rewrites[].transform` | `object` | Configures how to transform the `source_label` text. See **Rewrite Rule Transform Configuration** below. If not set, the `source_label` text is used directly for matching. | `nil` | No |
+| `...rewrites[].match` | `string` | Simple string to match against the (transformed) text. Cannot be set with `match_re`. | | No (use `match` or `match_re`) |
+| `...rewrites[].match_re` | `string` | Regular expression to match against the (transformed) text. | `.*` (matches all) | No (use `match` or `match_re`) |
+| `...rewrites[].action` | `string` | Action to perform on match: `create_or_update_label` (adds/updates a label with the matched/transformed text), `drop_feed` (discards the feed entirely). | `create_or_update_label` | No |
+| `...rewrites[].label` | `string` | Name of the feed label to create or update. | | Yes (if `action` is `create_or_update_label`) |
### Rewrite Rule Transform Configuration (`storage.feed.rewrites[].transform`)
-| Field | Type | Description | Default | Required |
-| :--------------------- | :----- | :---------------------------------------------------------------------------------------------------------- | :------ | :------- |
-| `...transform.to_text` | object | Transform the source text to text using an LLM. See **Rewrite Rule Transform To Text Configuration** below. | `nil` | No |
+| Field | Type | Description | Default Value | Required |
+| :--------------------- | :------- | :--------------------------------------------------------------------------------------------- | :------------ | :------- |
+| `...transform.to_text` | `object` | Transforms source text to text using an LLM. See **Rewrite Rule To Text Configuration** below. | `nil` | No |
-### Rewrite Rule Transform To Text Configuration (`storage.feed.rewrites[].transform.to_text`)
+### Rewrite Rule To Text Configuration (`storage.feed.rewrites[].transform.to_text`)
-| Field | Type | Description | Default | Required |
-| :------------------ | :----- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------- | :------- |
-| `...to_text.llm` | string | The name of the LLM (from `llms` section) to use for transformation. | default LLM in `llms` section | No |
-| `...to_text.prompt` | string | The prompt used for transformation. The source text is injected. Go template syntax can refer to built-in prompts: `{{ .summary }}`, `{{ .category }}`, `{{ .tags }}`, `{{ .score }}`, `{{ .comment_confucius }}`, `{{ .summary_html_snippet }}`. | | Yes |
+This configuration defines how to transform the text from `source_label`.
-### Scheduls Configuration (`scheduls`)
+| Field | Type | Description | Default Value | Required |
+| :------------------ | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | :-------------------------- |
+| `...to_text.type` | `string` | Type of transformation. Options: - `prompt` (default): Uses an LLM and a specified prompt to transform the source text.
- `crawl`: Treats the source text as a URL, directly crawls the web page content pointed to by the URL, and converts it to Markdown format. This method performs local crawling and attempts to follow `robots.txt`.
- `crawl_by_jina`: Treats the source text as a URL, crawls and processes web page content via the [Jina AI Reader API](https://jina.ai/reader/), and returns Markdown. Potentially more powerful, e.g., for handling dynamic pages, but relies on the Jina AI service.
| `prompt` | No |
+| `...to_text.llm` | `string` | **Only valid if `type` is `prompt`.** Name of the LLM used for transformation (from `llms` section). If not specified, the LLM marked as `default: true` in the `llms` section will be used. | Default LLM in `llms` section | No |
+| `...to_text.prompt` | `string` | **Only valid if `type` is `prompt`.** Prompt used for transformation. The source text will be injected. You can use Go template syntax to reference built-in prompts: `{{ .summary }}`, `{{ .category }}`, `{{ .tags }}`, `{{ .score }}`, `{{ .comment_confucius }}`, `{{ .summary_html_snippet }}`. | | Yes (if `type` is `prompt`) |
+
+### Scheduling Configuration (`scheduls`)
Defines rules for querying and monitoring feeds.
-| Field | Type | Description | Default | Required |
-| :--------------- | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :------- |
-| `scheduls.rules` | list of objects | The rules for scheduling feeds. Each rule's result (matched feeds) is sent to the notify route. See **Scheduls Rule Configuration** section below. | `[]` | No |
+| Field | Type | Description | Default Value | Required |
+| :--------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :------- |
+| `scheduls.rules` | `list of objects` | List of rules for scheduling feeds. The results of each rule (matched feeds) will be sent to notification routes. See **Scheduling Rule Configuration** below. | `[]` | No |
-### Scheduls Rule Configuration (`scheduls.rules[]`)
+### Scheduling Rule Configuration (`scheduls.rules[]`)
-| Field | Type | Description | Default | Required |
-| :-------------------------------- | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------ | :--------------------------------------- |
-| `scheduls.rules[].name` | string | The name of the rule. | | Yes |
-| `scheduls.rules[].query` | string | The semantic query to find relevant feeds. Optional. | | No |
-| `scheduls.rules[].threshold` | float32 | Relevance score threshold (0-1) to filter semantic query results. Only works if `query` is set. | `0.6` | No |
-| `scheduls.rules[].label_filters` | list of strings | Filters based on feed labels (exact match or non-match). e.g. `["category=tech", "source!=github"]`. | `[]` | No |
-| `scheduls.rules[].every_day` | string | Query range relative to the end of each day. Format: `start~end` (HH:MM). e.g., `00:00~23:59` (today), `-22:00~07:00` (yesterday 22:00 to today 07:00). Cannot be set with `watch_interval`. | | No (use `every_day` or `watch_interval`) |
-| `scheduls.rules[].watch_interval` | duration | How often to run the query. e.g. `10m`. Cannot be set with `every_day`. | `10m` | No (use `every_day` or `watch_interval`) |
+| Field | Type | Description | Default Value | Required |
+| :-------------------------------- | :---------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------ | :--------------------------------------- |
+| `scheduls.rules[].name` | `string` | Name of the rule. | | Yes |
+| `scheduls.rules[].query` | `string` | Semantic query to find relevant feeds. Optional. | | No |
+| `scheduls.rules[].threshold` | `float32` | Relevance score threshold (0-1) for filtering semantic query results. Only effective if `query` is set. | `0.6` | No |
+| `scheduls.rules[].label_filters` | `list of strings` | Filters based on feed labels (equals or not equals). E.g., `["category=tech", "source!=github"]`. | `[]` | No |
+| `scheduls.rules[].every_day` | `string` | Query range relative to the end of each day. Format: `start~end` (HH:MM). E.g., `00:00~23:59` (today), `-22:00~07:00` (yesterday 22:00 to today 07:00). Cannot be set with `watch_interval`. | | No (use `every_day` or `watch_interval`) |
+| `scheduls.rules[].watch_interval` | `time.Duration` | Frequency to run the query. E.g., `10m`. Cannot be set with `every_day`. | `10m` | No (use `every_day` or `watch_interval`) |
-### Notify Configuration (`notify`)
+### Notification Configuration (`notify`)
-| Field | Type | Description | Default | Required |
-| :----------------- | :-------------- | :------------------------------------------------------------------------------------------------------------- | :----------- | :---------------------- |
-| `notify.route` | object | The main notify routing configuration. See **Notify Route Configuration** below. | (see fields) | Yes |
-| `notify.receivers` | list of objects | Defines the notification receivers (e.g., email addresses). See **Notify Receiver Configuration** below. | `[]` | Yes (at least one) |
-| `notify.channels` | object | Configures the notification channels (e.g., email SMTP settings). See **Notify Channels Configuration** below. | (see fields) | Yes (if using channels) |
+| Field | Type | Description | Default Value | Required |
+| :----------------- | :---------------- | :-------------------------------------------------------------------------------------------------------------- | :-------------------- | :---------------------- |
+| `notify.route` | `object` | Main notification routing configuration. See **Notification Routing Configuration** below. | (See specific fields) | Yes |
+| `notify.receivers` | `list of objects` | Defines notification receivers (e.g., email addresses). See **Notification Receiver Configuration** below. | `[]` | Yes (at least one) |
+| `notify.channels` | `object` | Configures notification channels (e.g., email SMTP settings). See **Notification Channel Configuration** below. | (See specific fields) | Yes (if using channels) |
-### Notify Route Configuration (`notify.route` and `notify.route.sub_routes[]`)
+### Notification Routing Configuration (`notify.route` and `notify.route.sub_routes[]`)
-This structure can be nested using `sub_routes`. A feed is matched against sub-routes first; if no sub-route matches, the parent route's configuration applies.
+This structure can be nested using `sub_routes`. Feeds will first try to match sub-routes; if no sub-route matches, the parent route's configuration is applied.
-| Field | Type | Description | Default | Required |
-| :--------------------------------- | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | :------------------- |
-| `...matchers` (only in sub-routes) | list of strings | Label matchers to determine if a feed belongs to this sub-route. e.g. `["category=tech", "source!=github"]`. | `[]` | Yes (for sub-routes) |
-| `...receivers` | list of strings | Names of the receivers (defined in `notify.receivers`) to send notifications for feeds matching this route. | `[]` | Yes (at least one) |
-| `...group_by` | list of strings | Labels to group feeds by before sending notifications. Each group results in a separate notification. e.g., `["source", "category"]`. | `[]` | Yes (at least one) |
-| `...source_label` | string | The source label to extract the content from each feed, and summarize them. Default are all labels. It is very recommended to set it to 'summary' to reduce context length. | all labels | No |
-| `...summary_prompt` | string | The prompt to summarize the feeds of each group. | | No |
-| `...llm` | string | The LLM name to use. Default is the default LLM in `llms` section. A large context length LLM is recommended. | default LLM in `llms` section | No |
-| `...compress_by_related_threshold` | *float32 | If set, compresses highly similar feeds (based on semantic relatedness) within a group, sending only one representative. Threshold (0-1). Higher means more similar. | `0.85` | No |
-| `...sub_routes` | list of objects | Nested routes. Allows defining more specific routing rules. Each object follows the **Notify Route Configuration**. | `[]` | No |
+| Field | Type | Description | Default Value | Required |
+| :--------------------------------- | :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------- | :-------------------- |
+| `...matchers` (sub-routes only) | `list of strings` | Label matchers to determine if a feed belongs to this sub-route. E.g., `["category=tech", "source!=github"]`. | `[]` | Yes (sub-routes only) |
+| `...receivers` | `list of strings` | List of receiver names (defined in `notify.receivers`) to send notifications for feeds matching this route. | `[]` | Yes (at least one) |
+| `...group_by` | `list of strings` | List of labels to group feeds by before sending notifications. Each group results in a separate notification. E.g., `["source", "category"]`. | `[]` | Yes (at least one) |
+| `...source_label` | `string` | Source label to extract content from each feed for summarization. Defaults to all labels. Strongly recommended to set to 'summary' to reduce context length. | All labels | No |
+| `...summary_prompt` | `string` | Prompt to summarize feeds for each group. | | No |
+| `...llm` | `string` | Name of the LLM to use. Defaults to the default LLM in the `llms` section. Recommended to use an LLM with a large context length. | Default LLM in `llms` section | No |
+| `...compress_by_related_threshold` | `*float32` | If set, compresses highly similar feeds within a group based on semantic relatedness, sending only one representative. Threshold (0-1), higher means more similar. | `0.85` | No |
+| `...sub_routes` | `list of objects` | List of nested routes. Allows defining more specific routing rules. Each object follows **Notification Routing Configuration**. | `[]` | No |
-### Notify Receiver Configuration (`notify.receivers[]`)
+### Notification Receiver Configuration (`notify.receivers[]`)
Defines *who* receives notifications.
-| Field | Type | Description | Default | Required |
-| :------------------------- | :----- | :----------------------------------------------- | :------ | :------------------- |
-| `notify.receivers[].name` | string | The unique name of the receiver. Used in routes. | | Yes |
-| `notify.receivers[].email` | string | The email address of the receiver. | | Yes (if using email) |
+| Field | Type | Description | Default Value | Required |
+| :------------------------- | :------- | :------------------------------------------- | :------------ | :------------------- |
+| `notify.receivers[].name` | `string` | Unique name of the receiver. Used in routes. | | Yes |
+| `notify.receivers[].email` | `string` | Email address of the receiver. | | Yes (if using Email) |
-### Notify Channels Configuration (`notify.channels`)
+### Notification Channel Configuration (`notify.channels`)
Configures *how* notifications are sent.
-| Field | Type | Description | Default | Required |
-| :---------------------- | :----- | :--------------------------------------------------------------------------------- | :------ | :------------------- |
-| `notify.channels.email` | object | The global email channel config. See **Notify Channel Email Configuration** below. | `nil` | Yes (if using email) |
+| Field | Type | Description | Default Value | Required |
+| :---------------------- | :------- | :------------------------------------------------------------------------------------------ | :------------ | :------------------- |
+| `notify.channels.email` | `object` | Global Email channel configuration. See **Notification Channel Email Configuration** below. | `nil` | Yes (if using Email) |
-### Notify Channel Email Configuration (`notify.channels.email`)
+### Notification Channel Email Configuration (`notify.channels.email`)
-| Field | Type | Description | Default | Required |
-| :------------------------------------ | :----- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------- | :------- |
-| `...email.smtp_endpoint` | string | The SMTP server endpoint. e.g. `smtp.gmail.com:587`. | | Yes |
-| `...email.from` | string | The sender email address. | | Yes |
-| `...email.password` | string | The application password for the sender email. (For Gmail, see [Google App Passwords](https://support.google.com/mail/answer/185833)). | | Yes |
-| `...email.feed_markdown_template` | string | Markdown template for formatting each feed in the email body. Default renders the feed content. Cannot be set with `feed_html_snippet_template`. Available template variables depend on feed labels. | `{{ .content }}` | No |
-| `...email.feed_html_snippet_template` | string | HTML snippet template for formatting each feed. Cannot be set with `feed_markdown_template`. Available template variables depend on feed labels. | | No |
\ No newline at end of file
+| Field | Type | Description | Default Value | Required |
+| :------------------------------------ | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------- | :------- |
+| `...email.smtp_endpoint` | `string` | SMTP server endpoint. E.g., `smtp.gmail.com:587`. | | Yes |
+| `...email.from` | `string` | Sender's email address. | | Yes |
+| `...email.password` | `string` | App-specific password for the sender's email. (For Gmail, see [Google App Passwords](https://support.google.com/mail/answer/185833)). | | Yes |
+| `...email.feed_markdown_template` | `string` | Markdown template for formatting each feed in the email body. Renders feed content by default. Cannot be set with `feed_html_snippet_template`. Available template variables depend on feed labels. | `{{ .content }}` | No |
+| `...email.feed_html_snippet_template` | `string` | HTML snippet template for formatting each feed. Cannot be set with `feed_markdown_template`. Available template variables depend on feed labels. | | No |
diff --git a/docs/crawl-zh.md b/docs/crawl-zh.md
new file mode 100644
index 0000000..2969d79
--- /dev/null
+++ b/docs/crawl-zh.md
@@ -0,0 +1,88 @@
+# 使用 Zenfeed 爬虫功能
+
+Zenfeed 提供了将网页内容抓取并转换为 Markdown 格式的功能。这主要通过重写规则 (`rewrites` rule) 中的 `transform.to_text.type` 配置项实现。
+
+## 如何使用
+
+在你的配置文件中,找到 `storage.feed.rewrites` 部分。当你定义一条重写规则时,可以通过 `transform` 字段来启用爬虫功能。
+
+具体配置如下:
+
+```yaml
+storage:
+ feed:
+ rewrites:
+ - if: ["source=xxx", ...]
+ source_label: "link" # 指定包含 URL 的标签,例如 feed 中的 'link' 标签
+ transform:
+ to_text:
+ type: "crawl" # 或 "crawl_by_jina"
+ # llm: "your-llm-name" # crawl 类型不需要 llm
+ # prompt: "your-prompt" # crawl 类型不需要 prompt
+ # match: ".*" # 可选:对抓取到的 Markdown 内容进行匹配
+ action: "create_or_update_label" # 对抓取到的内容执行的动作
+ label: "crawled_content" # 将抓取到的 Markdown 存储到这个新标签
+ # ... 其他配置 ...
+jina: # 如果使用 crawl_by_jina,并且需要更高的速率限制(匿名ip: 20 RPM),请配置 Jina API Token
+ token: "YOUR_JINA_AI_TOKEN" # 从 https://jina.ai/api-dashboard/ 获取
+```
+
+### 转换类型 (`transform.to_text.type`)
+
+你有以下几种选择:
+
+1. **`crawl`**:
+ * Zenfeed 将使用内置的本地爬虫尝试抓取 `source_label` 中指定的 URL。
+ * 它会尝试遵循目标网站的 `robots.txt` 协议。
+ * 适用于静态网页或结构相对简单的网站。
+
+2. **`crawl_by_jina`**:
+ * Zenfeed 将通过 [Jina AI Reader API](https://jina.ai/reader/) 来抓取和处理 `source_label` 中指定的 URL。
+ * Jina AI 可能能更好地处理动态内容和复杂网站结构。
+ * 同样遵循目标网站的 `robots.txt` 协议。
+ * **依赖 Jina AI 服务**:
+ * 建议在配置文件的顶层添加 `jina.token` (如上示例) 来提供你的 Jina AI API Token,以获得更高的服务速率限制。
+ * 如果未提供 Token,将以匿名用户身份请求,速率限制较低。
+ * 请查阅 Jina AI 的服务条款和隐私政策。
+
+### 关键配置说明
+
+* `source_label`: 此标签的值**必须是一个有效的 URL**。例如,如果你的 RSS Feed 中的 `link` 标签指向的是一篇包含完整文章的网页,你可以将 `source_label` 设置为 `link`。
+* `action`: 通常设置为 `create_or_update_label`,将抓取并转换后的 Markdown 内容存入一个新的标签中(由 `label` 字段指定)。
+* `label`: 指定存储抓取到的 Markdown 内容的新标签名称。
+
+## 使用场景
+
+**全文内容提取**:
+很多 RSS 源只提供文章摘要和原文链接。使用爬虫功能可以将原文完整内容抓取下来,转换为 Markdown 格式,方便后续的 AI 处理(如总结、打标签、分类等)或直接阅读。
+
+## 免责声明
+
+**在使用 Zenfeed 的爬虫功能(包括 `crawl` 和 `crawl_by_jina` 类型)前,请仔细阅读并理解以下声明。您的使用行为即表示您已接受本声明的所有条款。**
+
+1. **用户责任与授权**:
+ * 您将对使用爬虫功能的所有行为承担全部责任。
+ * 您必须确保拥有访问、抓取和处理所提供 URL 内容的合法权利。
+ * 请严格遵守目标网站的 `robots.txt` 协议、服务条款 (ToS)、版权政策以及所有相关的法律法规。
+ * 不得使用本功能处理、存储或分发任何非法、侵权、诽谤、淫秽或其他令人反感的内容。
+
+2. **内容准确性与完整性**:
+ * 网页抓取和 Markdown 转换过程的结果可能不准确、不完整或存在偏差。这可能受到目标网站结构、反爬虫机制、动态内容渲染、网络问题等多种因素的影响。
+ * Zenfeed 项目作者和贡献者不对抓取内容的准确性、完整性、及时性或质量作任何保证。
+
+3. **第三方服务依赖 (`crawl_by_jina`)**:
+ * `crawl_by_jina` 功能依赖于 Jina AI 提供的第三方服务。
+ * Jina AI 服务的可用性、性能、数据处理政策、服务条款以及可能的费用(超出免费额度后)均由 Jina AI 自行决定。
+ * 项目作者和贡献者不对 Jina AI 服务的任何方面负责。请在使用前查阅 [Jina AI 的相关条款](https://jina.ai/terms/) 和 [隐私政策](https://jina.ai/privacy/)。
+
+4. **无间接或后果性损害赔偿**:
+ * 在任何情况下,无论基于何种法律理论,项目作者和贡献者均不对因使用或无法使用爬虫功能而导致的任何直接、间接、偶然、特殊、惩戒性或后果性损害负责,包括但不限于利润损失、数据丢失、商誉损失或业务中断。
+
+5. **法律与合规风险**:
+ * 未经授权抓取、复制、存储、处理或传播受版权保护的内容,或违反网站服务条款的行为,可能违反相关法律法规,并可能导致法律纠纷或处罚。
+ * 用户需自行承担因使用爬虫功能而产生的所有法律风险和责任。
+
+6. **"按原样"提供**:
+ * 爬虫功能按"现状"和"可用"的基础提供,不附带任何形式的明示或默示担保。
+
+**强烈建议您在启用和配置爬虫功能前,仔细评估相关风险,并确保您的使用行为完全合法合规。对于任何因用户滥用或不当使用本软件(包括爬虫功能)而引起的法律纠纷、损失或损害,Zenfeed 项目作者和贡献者不承担任何责任。**
diff --git a/docs/images/folo-html.png b/docs/images/folo-html.png
new file mode 100644
index 0000000000000000000000000000000000000000..4910feb650222aeb64ff13cd97ba6fa5850c45a8
GIT binary patch
literal 161022
zcmeFZWk6J2+cpdch(QP_(xo8MEkg?mh=d^B5<`#D9ipJLq|}gtG%|D}NW;)wN(?YC
zG(+?4@w(sZjz=G#AK%aW55nxd*Q~wP8OM2?$MU_ZvK--6s;gL7ScD4lvT9gZm!Vi#
zmr4onflu-uxZlIV!b!1uuI;3)^b}+YvEwo^gS;^3aF2m0;CIZ&+)NBVZ*j5_XVO+uWsre5m@^1*@o@1lNnB+Rb1<_2smVV6
z$HTy1;!KuKPA@^++^(*!T(10F5Cr@bRHW{AJW
zkTrKSb+CHrWCgKjz>I0~0^;l>&cp;R=AMi6z8Ij2p9>wpRZbl3|byemTEUc?vtYlR2?7|R<`C?Sn}^<<1ar~pSTqWuU}@G=Gb17EVF8|wf2~^q2M{O#5nkK)iF@AI;z
zPxLi~fSO4J=gqhM)hoWa(R-DNSE3R*G^5@i4)|=`R15Dn(U_5=n_sqi}(
z?q2hjxltUNJh7RZe|on^MVm?hF4kvz&cPbu#k`!5^8JP=oYKmq$9=g(?Ch@dU>tsr
zBNJRNs-@d35Y8{$!fp$FoB!}GlI5!S?cE+yu04h)VUexT)lU`9>K3;?yZ8Ng#1*?2
zN44A-xxo1T+N1QS`jEAy|(Wj
z9-ND^@;i75sHk#WtL7*#`^1Pl&<{6R5|Z9~C0}z4xC{RHb>|usOmgLR)75b-(M79Z
zH{Zq8YhwJ>A_+fw-3mw-uevLACJ5j=EK~`wq}o+{v@ka@zvg#%-sepR$%UawGYpO1
z6{g@eDbztW-fCMCVHI{`N3QCQ
zlu_Ofe3Jg;Gk#u>PoT04MVgH&_Z}A2>$Y^^e(-I)Xk$EW+R>n16Ht%e+4q-jBFVVL
zfxGiKZle0wV_)UIYaZfwT|xz2{vN+Yel;yi^?AF7iim?rE=8zkHUBAt@))cynKF
zBqLFk_Otyk?J$Ei&i<9KN9z3oWo(mIHbTOsU<3IB$o{GR*cFfy`!VS;kq3eJ`}1H%
z(_tQ#+j7^*$ifu9nwehZUHaJa@m7!dQ%MWlUAmBOYG3fRJqx&n@RME}1k41^1lhH@
zyzu#I5HAjB#M~S1cHw#W
z_mi#6L#x*u(h>9W1WYdps^5@)Wqf$~`z@0DtI%X_zk!;goW
zPo6x{>yo}N8_ZfRo%e99YFHzeF<0e|dlYYH0J$9Lhl=R%NRR@tJfzdCRAM5GMzQY>
zgmo;MJ1R9w@`-v@Z&tz}&FcQ3#$e%~_h7LZ0iOro^@N*z?tDvptmgKePdiA>_RYo3
zbV`wC`DQBJv(YJ0nH@S^c%7fR?V@cWYgvR@BBICQ(NTyFiVpgi#(R=arJp(7qaznG
z=U*aO;xm`U&NohLdM^L$p5}w>{!E_Sd&QQSw{t&cYJYOmd=(j-v8J9dEMR?QAJ^IM
zSVzI&eR`|LWZrPraL%5z`-jpbKCNo`YMpAMBiL1?Z;E#{%yXV3b&+Y5<$bY!XI-)8
zGt!+$7D9eYg!2XWeO;#)12Tfzz1p15?ZPId%ag3t*QnMc)^PUg*C<9<1wK>RQ?XH*
zQy~P}1lba~62*G?dUq4KErSK{Im0xiYh{y$)rNLPNUcUJ_bruds4e9zA%pg#J{dB@
z>Bu{4F{@6iPuHy1@Yj}+LTeJk%qs)?QTqog&PeZm;r@uhxs17NNsBbwCV>uqO93}q
zc_Al3eLHO%w=n`6S>Y#wlVGQ*+d>tB%0fka4dysW;|W_&4-)pc+BjUadyMpJ520+G
zF(!%Tla?T}$AZ^*rp!hy4!ZQm#74bx+$)c?uIYscgvi{A;8@~3;}F(y(Qf#{SMfqm
zLuaO(MbA^OxYEAT$IkRcOZf!EaW3u!wPo$MitdAul=_tZqE7p1!!e23hRJg}1An@w
zbB`O>{9e6u)!uyB_B}c?#WZ{2O1osr(-gH7s^qq~+Z^AyM9R#@b*2)Q^cTcd9y-2v
zJsL3Y*XWL2(nTKkD7A+zxQyGyn6%29(3sabRKBFBsjTwddby{K99Vt7v$xH$v%VdL
z4&KV!^H?j~NLid&5nnY$!rF3%C>D^LwL@q+^^{JFQGV95^|R4qgB{kRpd+ng$!k1B
zB=}CaU;TajYjNngNA$XkCU4=s{wQ7h8j4GeTTFC*7nhopM(yT~*f5`fWSM9vX$4;a
zxueMvF6=*jUB_B!)zM)qe!Y4k30K%
zy3chTK@#E;X97mh)UL*|C#D*Gd4~S)(^Pq?CSUENMxr13e*b>`o~ip|&uko~82jMs
zkLKP7mg<-0l}cF1S)}%S=>CqF?h)*pNfb>)q@ahW_NWdLi@XX?!;WNZC~a&;rIDng
z&V^$-MkPLG0r0?@u-33#YTp^NFS$-bwY|@hM6V8C6C%Ivi`X?VO?OH!*_yqT!+E(u
z&KG{t0M6eZt=DV3^(wfCLXSM20&Z&xsXY%^BEBis$_svE4EJ~}Yyy-*8tyy$0ajE
z*&m=cyk?Jbm8B7_Z?mqo7_isU8H6^tq18G*LA%f6iw(CM9KIwCc%SF(j_{As6sI&O
zH_p1WpdKTYrjj@tC(kDbO~-~jy3aLF?1H9f>lHoV`_%^qRSWKjE`#M6>NCX?&&y2s
zsJW0@VYgIA-*xH9fH7hiowDTQ*6rP@mG|q>J2}=WHopAFh3@_KTkDaC{$8*NoQ@C{
zWONSQKIxHfcHG(BdDc5{%$xs(e_?i^(ul*8cSmi@!6RdfxLPvLh_V5+pEH#z%$mt6
zXL!xX*Auo;*fqtxX*=tzgr_3zoqFzbEHfY%8{wR~l3F7v=S`Q=2W{?s?!|jNxIJ=#
zT!5_3HC)ktI9fn1#dt~iDeipl4Xh4cELHnUQu;zU#bCn36BQoCdCBc${8T|vtng|^
zU0i91*>H#;!A{cj6+i7;Z)>r~Cw2Lj%eM;bDfBop-tT@pCZ;;z7{@v{aZEht_P!Xp
zq%hKW^}!IROlHUlwg>nLS6iLkni>J+&0Cs)gL*%+ko$R5LW>q87IM5Zo1$
zEQF`k#zrEAqmwW*R9%xe+t*o_I~<0EL%`tozx}ZEs;H_O4yNPLO`I50)vZy=
z%ZsK~P+_^{%>2JTj~RXSPKnhJOKmlaMN&Zpf+hcjYrVU>`+q5S8KV65NR=-)%7-N4POyljJ6
z3?=zv8hEFrfb*X>fdE&|ugUQx3XfCzcXRpMHwM*!TXkoJsQzrE{~h(eEAZbH`0on*
zcLi|e7=#}vSxv|7agwhZsSXAD;Ek|Z^_>3TK!^>+gYpah_iT^3YIQeqz#mK~6tVt{
zv&J@bxUxj3$@`hghs$xJxEWU-3b)P7C;utiMShlQ!-vXg`_G1WjNvulZed8kp%iI%Bnjw4B^6+=P+rDmzlXA?NB-$s{rvP87I5Iw#Gs_4%obl8
zJ%v9$A=Qt^cYmv(d3y4DL4c2q_2i`XuF7Sjpj&^+Wm2lRz=P9`4ENTzD^1Oa1E<4{
zLZbfkef_s_s>5Clwt>K)eHWp}lM6bMspJVXaeqBGtYi!qN5>e}dvqdFeYqsh4^2~u
zP@{!Odz*8Tiyt8kM~@p0e}qJe3H^2j`Sch}ao{Hvz3bGyBR`RuSy9vY7gO^H@q-fV
zy!?D~Y<>e*US+Y|t5XQ}T2NQB_5%l!C$&;BAStI#c-5HNU76Ym6OFTuBy#BExzj(1`GJ=b{
zKYj-_|h2W63?JJowpli&4OXT)_3z4yz_l7m4v08nG(
ze~DO3_4r`JW^>3{Db?pV*K0?iH-x8#ZqaE_f%UCmaaq~(G6dv)G?DuT(fxKud(OEE
zJ1SxwQure^{J(M6zr!X=CLBsKx7kVq=dYCG1Xm90?q!A&!r
z!fRO`ExNhddvP(|hsg%1M%8Uyo@eFpWT5^2beZ+0-OmGKEKXi!0n^f8&dM=vAL)4!
zs@o?dQfbn-PfLzoU*p;;RJk;O@}_#@vyvXP?pL;n9rjVgxXG^shNOYdo?aq)(1fBa
ztPd5uSMRp{LZ{ZnZv2bcFV^lK?lI3A$?Fep42fXnuI(-wT$^NF&zLaqoa!zZeY;Jw
zrpJ=6;lJoQqqGx2bzhI=O!~g>VKJooIT-5MH?a@;bP%Kb_&D|E;kaXjcyeB?%j%B;
z*lVDM5h(M+PtH+EHAGkok`$(<;iaMHe0>L?ZL?X9cU2Pjz9rF?)_WZ}i0lum%j~?a
z*dA2m{$@LPg*BN3b{+4xF15xjH7^oqa-QB8U2x0pMa&apS06_!!2_KMw*lxC8k#y8
z3pg}7=COuW(82TK9b49=tmna4IFxK@;vZ+~Kh_giSlUHH0_TLLY6P$?H_i%D+mz#t
zeB8e(b->{&huV#ooZU8eb{rZF7y)N(VuW5h6g_=%BPG5=$dB(IoGeYg7
zd^%4~le;VAdw!Jh5&UGC2F|OZj4`e>Y+NS1lwJxN*dDaYi*g4)y_X{S1GAh5vBHFl
zw6y
zR_Od1EiF#>F4MZ*|r3+L!)u
zIcL5@2Zk7T6waA{o{WR>WdVPyq3j7G%W1-XZozG@F9VgEJruV$DX7n)vYeC7P{-%aE#EPucAQESlQ3ZyXc
z$5EJOx4UoBNR50LbL7l}9HmrPoP@jFAeCPEPEyv0(zl-${RQvzbszKN(03=-!cMLq
z-<6MWChoL*4M#cTYHtGJ`#P0C2l_d^&tbZoqz_)D{mfeY41;^Tp-1lC`UHOgS0mjd
zyJi6aJEC_>xqE&FF7)eq7+0@pin4?HY51@QLD0(behow4;z;Q`|RP1*);XYz{7_xJfvzH*H=WG^pGkViDK@h5C}pCX3+`Q
z0`+KUwId=JKYwO*7cS`v5gI>!bA@biB{L!}T18@lD7+T&a@MA}{%havjO*Onr4RnU
zW*L-Jnoa}r8gs1q6r(rmU!XTjYKj?s7WSb58#Px*+`?P(_rB;A%sX;-A|1<~LncVt
zZ+r-esYjQYxo&W3us4S5e}2C{z_2yrsJ<~ne-zF6&HoHUOhRCffOmZ
zH8k!LOB|+cvH@w3JnmxC_d=8dZnlRUgBOfixsMsRb~gE6+Ay6gw_#P!e)^%CtF|Y%
z1@d_V(s(vHLBIK=hLD$T)AP
zSi6-$`I_j2g*O$G`NMt@HIi{SWaYs4RYo(Iki&);OlEu_8YEiWfN&i925=`?IesVC
z-W&SQQIMi}rfcT(+RnZ`zBD#lo$P#)86ok6O*Ki#&TMaGpaC&F+h=ZWZgS$(ft;T1
zFTMnF+OKop{n}N-5A{8FKM9%1)5xEmxG@F?AvsuAY_HOKZ8bX_ujX_QsYu#^-8~I#
zmVbN;QF`@j*I|gn##ye^-W4sm@r)7+TZkF(s#g`Ec
zi2IznxudEPr@?O)y#YAk@a?9Syo-yA{Q2i+nX>3Y{c5!`iZW3vEd8m6zb5nidq{a2
zGZW`Ddev@DBX#>?dO6aK4LSY6sV7nOro>j>C+rfQKf3eucRtvr_~_IJEroB|cCrgP
zt&q&6=huUiH-NO~NsD|nn}xlmqbXuwO-X3&*=^N;Ux*~VKDy)6iCePol^u%qs_CtR
z`*3LSNeO@eaGy%Rkjr`wpnoF?C_H_%(8Y153LtLz9@v?Lku~t
z%1wP&0?7(r!N%oNY?_JI@#H$_v_0j}t3#~oRRE;q;o#P7yCFxzs3N`}BLJBd)8(y|JNWwc
zT1%JZ=IX13wzo^|qCFL^pSQH@%UhIX@qZJK)ueoloG;p}sVLdz*1;1GaB!jGs3SR|
z57YJdZVt$Xgt1NBd}=M_8RDly%mN)q3W8B%NETg>N2t-3h~aB_X?wDl2cwxATPS{P
zM~Ds(p3Ti7SV4Xp>2%aTi@Mqd8yOwhJcLnC4w8KEp_;>SE3wQA%2rn_^uPO
zqqQy@_k(C%K5nK{3){zx8G0(=6Vq|zKt1v-Nj#eTCVdyUYF3yA47+EumevwI_Es3V
z>$YB7dS-kA!qwAL557w4k^Ac@V(Gn_SSLLxKb9_IX|r<;R}R#+c#@motGAZiKTFQm
zVb^7)w^%SS>@V%b6)DQ(6L+`OYj+lNEOC
zDw1bq`aVZXCgNX}ygz;Vl(7p!ZcPqE^=+R12;qLr{$qU?A9{u}rEH@4Z%FFzSj}LE
z>qM^5_~DL{p;+TUcJDVK|3Ff=gX;GqkMi&2m%!lAL)iVs3#P(=K95fp%FBnvpgOYx
zsS>($={Z>sI?o73HyV@*5e|Xut*ryu(WvF_U{}kHj`G$@Iiw@;HtpQA?RYAFi_o4F
zQLP%M)fe>7b#&N(qOU=G(*b7D`}O{$>zwz-EZju2AQYVHJ<2V52Lb@Xj22^JN>T{}
zX+uLOCW<*AQe3fr5!7<0(GG*i09mnbOtwZMD
zONW0zNq+&-E&LRv$cfW%n>ZH9Fs{N*q1f~aZ~Ki33eljE
z2Ns5@K0-DzEa<%Uyc%tI;mZvxEnOG+G*xZ`4i4gk)U2|+zr}D4
z_dO?b2{j5la5*?|8)aFSu#Z!(A_gvT($f$lN9Qh!$(FU~ZHbfhg$A)X=M-YltYMq3
zKhZ*=^s*vX^_>C$}3^8owt3}o1&)A5*@ZS(U
z#aUz7qC;vQ{JQHSRf78hFGa})uqWJ<-oeEr2M%@iCe^9jefvT1Nl#+F$cIHj{C0k`
ziaGbX{lnOaxvOtH)iX8@Mq2>Ti)+N7%YzAej1>KmMyk`&C`&s6E2&G6x{LD@KFj{J
zcjeY34|Iu_-d)xeEd%cd5KF9(R|9eWQ&v{%M0i;SHd0>3-v(kiL(jes1pBOaqm@9v
z>bHCe!y(Q#eoAlb3Zx1(_o}`fGh0@v2n24SW6cI*uaD#VMU>~8=Nuk-d6ndftJAfG
z=8Gne$iy@zX_m*-br33aH}5idqeQ^0UfwVZ_=j3?f3rf!azUe9f#c^9I>?SqW##oL
z0)~D8T&F{XyaGC5-m%QVr=8wy-e0)_t-YI_qMmj`>tNP%^Bp+#!K)Xo-rMURc})Wi
z3T(!Tb^x&6?)XmMWA)RGfNoCRvJy|F_O-VYT;hj&_q}Y-Urn&kPMwy)UT$4qN}`
z;#4|K)B*?ivCM*STxjoR31C9Zv~oTU0K#OQPaG-AEW5p})j9i$;$#J=5Ml8XGcNI*
zWS3c0O@~CxDsEnPx$Ig_;gDrqvRvDp_Zr_n6EA#5O;=MA1?|h0fY9nb4w#?K+PKT;
zC$)TaC7uLLK?3Q25En{UOy8I#o-`w#QXaZIvQOjV%o+tGhRX?j=E0lkli4@fH4jnw
zirH5r+8v+nF87*m&97WoKwT!ScA72`cK|&$?|cppj+oc4Uq^axOqNHt(|Pvv4OK%g
z_HrcO8`dgZyT!qi&w=4o{wF2Gz<`756F*KWJaL`URuVApg_NUeUeI=*2i|1ZO7;Vv
ze+Pr4!RYTI^#}2Fmm=bKRE(U#;Ncp4FXuiB2#?$6n|mqs;vF{2PA>7h^0Outjt&kz
zTyvj{ioxRX(3asPyNKM+T+;TA*D)^Ui+x7=qQP%v(2Fe>8N2!=o37Ec9w1SoSE^6f
ziWQ-K$kW4xh!A|w`donAc^pQy)T?}aKqx%TP+dLH7`W1iEj;H13#H_L-1lJNdM6z2
zI{B%21*p0~Q>fS(m#N;_;ifWt=x>4_i7oco#&gk*JCpA1rw4h`U>MyxyM1C|$ax54
zsBYQAKx3BMms>>q;Jvn-3%XvG@4;39k5994FTFM1o6JuWn#Pk;>eZ#Ce2b9laDR;=rR}&hvw4OU>h`dVwZy3A
z{1|DDNiVfgaXBro=w0=DEg8QkJG9=AlaJ>zSjPu=9|?^@O4yj5gbbPGJYv5z+_7^Z#;#%G^KLpLYQ<6Uu|&x$wAV5}
zF$~{{Ou-`={{u1#sK7l=fz_l|Q+oF5RY!$=D3!NeN?*>#kZGMXswB7*XvMRz%@g8`
z-SxATnr>gAcGE=wnAi+{qi|Oi*`C~9kYuV}Jv_e-l&@?lAm<(N4WN8w*=)RM*laMf
zP)-u+b+t!GUL3(qa(cVEND0F|hc+tbxV}98wndkTy^fysR5{;{@Eu2Yh(4{X6a)PJ
zH$Ca?Yd~E)*TtqIv55zssaGps(_`~IdF84(Sx_7B4Ye=JEfrAH2y#*F@q1aoum!|t
zR=o=Q@}%vSou!@AYuv{|3UCT`EW0Ab?o|*56l*n|EGOYVTu_>vnh+1IxWa?nUM>_f
zNIX4m_(TDzty=2A(G^f5oHG{;-?@v;qg`0x$
zA=EV#CAG@5#~!{&QxC0nvePrCX0XHLYQWOC5iN7^IZ|+&sD7itwopaVL)npQrWg@|
z&_~s1%VGIvbEo;63_0pt>a0N#%ID;NWelQWie^;_e-N}&bwr17*3W6B%-~KA!lHAnhnQm?4A#{E9|Bc
z0aO((in<*|u`w4D`P%k(n@iA2-tn=?@#%Zet$P<+sTrxK%ohx?b6!lbl3YcmgxeIU
zM(ZgP56(>ko)6*%A}6x}=Ffd~No&nXI|Gq7q;M}3=)iEMNn5yXniGm44>~?DXBx&=
zUWVZp*1JDrm;a%h0zVqngT$IfhztGDagsp5`atYUyEK~(CnyJ?eee!HrrxS8l(G~+
z_M&!`FBIRwyDRj?0(bzMF9e4~jIQN2d?Pw;9B|
z{^^0vd}Y=Qh@HPKEz;;PoV{kIO0ewltx=i$8$=^Q(LZmEVoz&UArwNk
z-ywS^qzHjqs`t&DOe}u#sJ0wnIyyhuG>h((d3F8d2$A|puhPD+qNvPj
zsO|iXnf=^WgX+9i;P2h}5aZ5vOCKvZybL=JKT;JxS5KI$-o_Q-2TDbfR6qPkxzO7;
z`1UGJ&}3Uy@8!ypQ?m4InzB+4C17SF@ymuSgEIA()Vm2Gu>}I+kh+$$kFfH
zcy#UFzkh$RZnrPm89CayKW4PqGq*lQlHz$(?0c3!$K^?6pVF?2fE{EvUYu?205^ET
zyepjzEgNS?8{T&Q^nqW2k#}s7&t&ZKA1g5kD{`$-f{4wY=8{wNGl)nd$TJ;5z%-3(
z{jNwMvlHMG*Q@O!U-Pt9ScT|t=~wlmW(s8NC3k!HS!$c(XQ3n8P~)YpSPTTN3YI)t
zjK*+sifGi##*UHqe(bdKn86KCg`_7z*D0B(K1wV`%49hr`l4brPQE7KHzMGv+?cYz
zz^HMS<}2xM?GhWe)B}x5873&`8~SXupJ=S4DVC9gD|Gl&1%ZFaophf>N($s$kTC26
zXxS_oB1`=6A@#c~zDG;(CM_PUUa#+6K3Ex$=>xdK1VNj)0_`tMPZV4jf0G3XT1kN_
zNPL|MfVyEC0_kUz)NY!E4Vq80eWhRG^iOu{Aks(g0_lz9b1{}b;`pkA*zZ^gEF2{{
zh6#T}D7E#}S!y7@n@wO@AL$0Rom%7TEEgQ%oGjuNtdceb5f1azID=lCuxyy{}1cm2w2Hs;l1~NzIC2iYJTGKTaMoZ=zp1j{~;bg
z$3q?|{P|XFdVl>?vH#2KC#FfcxHufMu;z@ixLvBc^M4rI7u!SZq&70-kMAZR;=i`G
zs81>mjb3-(tJ`}R+I2P~XUz-p3dw5%Wf%liDA1^#Vde2)QBY@4>`
zBLDUEe=!mHaA4OKtJK*4Xkd}NfGP4XYdWU?`K^AIxb3mJe>5=vP|Qp5RwiEi^IJs$
zRwE)(Gz|21|1blyP~fFPt4H+j{pn7INE&P$c;~B6ntwC`22;Qlz&(Y_5C8mDi@>fb
zhqIUec~dDdFJ)>kNb%>lx`UB!I_T$O^b~(@^)G+xxDC7%rDJ}z>YwhEVh{m#^}nY2
z|GTM1CM8?{FFWO*%^VpUJL1F@$d0MR6aA!|ot??5g0r2k;6jr|&latJxv`jSr;=)-
zwPdLpz)E|LDHrZY`+=QFRgupCZMet$tKV9@)DY>*%enxZO^W3R+m17Q`}VEWZb}HJA3yI#94?d^
zJ4OAsu7WEk70@>+qa05w>Jszy>%*K^0CX<*9I}Gf0Hxai)MYQ*(M=n(ifZ@M;8uhn
z#M06P^Rt!D=+h`y8!
zI;Tmo4Urt|(9+UUzp^-spAf>5K__TpG=d0L2wy!t^*VXWNSpi6h-Oe~51{>5&ZY@2
zs!81Jah;TZn+p4@a8PM5d^Swy0i7{qqN`PGXm?qFs`72}&$5Mq4%=H=K6MJyR8md>
zO>mQJlnp8G08QZc;kxMxsYiQ0;c_q^^HQYi1G!%_!I&Tku=60f&sRW@$ufn8i*wJ{
zeXE|LRw4ksUgCG)Hwvzj=yrZ!?_Gi9hsJ<_kdX^pe)`+cicRto?K1kkTu>_AKRA|M
zYmk`>3mwFQS&{cwEsHI@s8vitzP0`8`C+~ayd-5-faFVY2;to&BNZvnQzD{MlsmE=C4HZ3nM`~3Oydn4X>
zO#9ZX$j`kE$#0Z;#&Vck&D2@%0OX+kPcFZU*8W%oAB_Wu8YfH56>A>><{t}~BUV}X
z&&4OWBO{f%eOxe>1^3}Ee0?O$NQvlK1YHpW@5~@3f0L{Huaor~%K*n_*7IlU+#LfA
z#N#p8#tiD`K8l2;`Z+pmN&>jxSWWbI8B|INS71IZ6>}Vg^FAjhp?tUH&o-@8DQ*w2
z)Mv|c{$?rcL&&>Z$*UK5FVB8sS5o){AHQqttL^UWtg~}*bHn}E5u})X>$$}P_t;(6
zB1AV{NhlmD(NdYOHWAPLz1te&py=#tpwZE$l_dbDYRvgbfUV13fv^w|*pNPhizQLlG%6+$2n5-~(;kQYE12DZ)@L!dk
z6c(Pc$P#%qss?#SwASKf6woGnYUuX;ZC2&;OT9ofU+V~!qxXIu$8ChN9dlipv?-3I
zbs3i~s`_y?;lZnny3?I*6AdDI3H^f^S0hoQfZeui=dyE*Y5ULJAyG{jx`OrtN@^JCZiN_Fw2^0
zID*vg1Dy(S-Lf$l3*o~?fW}tS)m_tTNatf!O1wiXz89&Ug0I*fPK&f11Al_dHMsQa
zAT1|KKRKj{Nk~
zEPt#E{5xotk#n5H>0N<|NidBnCs3T49S_bM{1~S$ijHOXjgq5Fp&Wg%VW+9u&&46B
z`M9>i?o*2MG_fQ{{3pfuD+@o`NlhgI;ZKNE-s^~M4_iROQs%l9!=}D2BY@=%hz7^8
zSKN><?!*zErS5D8d+Cke~LoqbY}w{9^%?p>-!FiHv}EQ$G1MqW#iXI
zh@Zdl7;eI#d#Nfv3B4cQ32i5u`UML>|MjcNZMpD1$Y~dQaRjaGY%a1&y?q2kB^
zv~K?0de-jq7&l>g-YEeT6r4pe=xIk4k;D0bC4gv)ao=6;TqbDjY}258``2Ug834S$
zT}7o$H^KbHus2(Hul*ZS>YZDT?*>2@GqtG6?kgah_W{S~XgT@>Mhg80YF*vk8vzau
z4yVNSY12Y>y27eT&jWyXm-X1VH&O6_{?&H!#;170C?Z3g$J?{}bIiW<=
zG@JDCD27TR;=*>jiPCSm?1NnZ8=dUuoW+;h)3`UN_|2P>>d<>3OxP=_KU;~K)pu*Y
z*#X!_ObaR1_gn`<$GB=vBSB>~3INq2_&+;m@`--u1{Xl@O+IylHqgllMq%VNfc6si
zJSdZs9%?k28G2-8;_a`SvpASrFaKB2!c_p$SZM7)BSW1Q3_nwFpS>U2C#Wzcw^y*S
zzm6iDfnNm?b9eDQif%YPnA$Y^+Nv;0QkYrjGcJgj>0Enz(9uHNMGtT6Cz9|zn!D&C
zdMo;-%p8FdomdjKoxVpMEaN%kyxHI=Kk;o;H7{v_!0Qc7>(2o1$C>8KGYSr$a|<8g
z^MJ~?$lEm4F%I6;(!T+tBD_NkXf4^$XXrUaAiK2E8oRh07s%cRFE<{D+_8vL7BLwF
zKvIu&CbzQyX}LmKyjP|X7U92J1IgCS{`h`CM92wZN>KQaT>)DDXlgcH^@=oiH!?g>
z$Zk@={a^xY{`Jk3Y|uZHZ~`?Lo2ZoXWH&0S?|CGiUuf*8#4zdkk<#IW$>z`wGANR!
zo?_^`prljoQ9kXAz#ZTgOx)XeK;}i~-RsP#M70ytywkqhSH27ASC|1ZA9`xOChjn9
za>;D(GrLSz!}hWspU>A6M)v`s`|RvXHOThkTa=PM$&1UwWh$Q3vm&eZa}MAX=WH6c
zChUfjj-Ncx=On*_z#<#&4SDcw-WPAG5Q+!vfUpFO;i_SZh{T4aIKAYJK-~eU6F|#A
zHV6Z$&43W)3REkCkffwK`R=gMZXLyVuCOvapnGXP#X{O6v6>Za*7K3K6i|9oN_e@o
zI+pwgqsCB=yLWd15FvG)dkblH1A?oUz(niRZNCNIbSbt>3AX_x{ZZ=5B9D&In~=?m
znN1(oXci^Xv$NPYKjE4b39dDhkF=$N)Kr>`8M1xPL8!Y%z|~`ME(fWml+x|9{VksF
z$B%Ba<1)|WdHbMn+N_ciRVOz!*ZbwKNkDSBL;-mh=p-_g5=j)_&DfhefF!va=6LbF
zFTA?p)zD*|t%-+o4%EW7f?Z(XrBy7wysud!~eE6TF~PMPl4XXiDdq4l=x6v$Q1
z=U4^}Ue!;bVj^aZbAfTZEm|kEWVw~FTt)aqtt$jZIX2icRL~MO=X0{b8!?bx;>>m5
z^%#&D$zB=-N&u(QeNGf>s@LGeLD35R0C0TRLcStF;;R{^X9=T3?^G>>lby9j
z1EzK7c)8jL1f=nGA!|`gpH9?GU*ERA`Ia2$;&gW@BgER!c?#IVN4_!lI61=>o<#e4Je>s+9a-09vcC>vV3p>Eo(nusK$o
zO6D|bM`&hK0^?eJL#A93fUUR3{{jC~iF0JwMy@?Z%_fpZDawOy`#N@q48&K*L9-!A`Ke(>=#aOi&!7
zf8kw%HvktpX4gO4!|eGygea`g{{`V~K!AqI!d|xOE?t*`c{bAVPapSc?+R2$9QFA0tU-b+zsf`d*yG>Wh1X#>B*Q03B_c)SXtc%bglp`gG`3*NqXybH1aZ$esK`
z!y%bI3E?{tguu?Fu@OwWErbnLLX1w-&JQXZKi$Fl>~FEroG+t>?1wj=?-v7-c*FM{
z?m(Yc^h8sa`El!25R(2AXtA7yma8T`>Y&Dcs-jRlz6^hLM&VMYH*ns-FeNdc*3T)?AMgR;Wk|Af-p8+DOaPzztweyWF94kOBHAe}dTm`YF`_y3B=sqn2s_8&I+VU9^`;Z%K$53%)YvV~W2VLH
zpjO2^H52J{vHez>xuFD;3TMQ*_i?L6UnLVCrXs*6e~C_ZE*73=^U6<@^lhN=SV=!<
zKo&R7(n)kfpxJ=)zN6gHy#^fHT4>m><_8*>%zy^h3EbH$JhzSul5wdL+EYTN%LF~~
z@o8Y!T0iyOv+`*<-~Kj+I}W4v>f-Pu$E
zCLrS~01OC~lu)L88@1P{Upan?;HpC(&4spFWKnE|EQZXVX`c}Fl2+mdvof~@57(hW*KnX3&1&wTTEP^DhxjdB-{BlFt$k^_%XtktSl3tx$7MI5sDdn$+w&h~~>f@0sZ${>P+
zWA;vckB3qhKWBzJ^nEfPE7SuMBhP?F9rs|mix0O=*7axs)%A@d+k;g#gpiP<29kc1
z%2G(7coR6b5HB&>tuU#0H0=nF1!R4#>&lkydjoPov^l=5Hd2zao=b)E!|E!8ogQns
zIS(rqCfqRRpF}dk-p(Qb0fYclh7AMb{v`tUNM=6Jbd6?)*!DVZRmD=$&S?Fh;v*N-&
zt+ZzIjgfVw#Q2VqagWf^Ri*8{O@xy6fU6#3mYc
zvvoRs^gbVU-A~`MrtozarazoHG4v4GDJkxg{_FVX0~{Y2kf^9Q`*F|`AZSrx)w-w}
zu6_-;eGR6=nZj{~Ly>#AME!F(JE(o18dAqdl_n^yy5W9?ZoHWBQ54ne*HJ+O8Ez6h
zVnt_Pzd!npv!8AWNOGkxqk!V&WR_iS-=h$nSQuPlN=L=)(b=?kbUe3_y;dgTbb76hT;D89z@Kcor=VAD9%y%I`DG+w^bNF+Lqh?Y9Z;kufb~Z7i
zmMZ3Go~eC8QkyLu$?Qs4(HK#CO0wd*B_Vp1Vx^OYQ(ByuhE*BSEEPED$uanUPVBb^*;@b*MR%&e_h9|2Kt
zLpT>;CP`eqLF@Z~1kemfC(lynv<%%piRYAS7C@|RBCH{U-?Oy}b_M6%<$
z_wmUl(6(g(ao&`HTwb6Xyo$?b(1aVV6aj__9JK|d!9G|fD&oM|s)~o=bY$%Hb|2g1
zCCJpU+)jC|-tHX?EUPRBwVhFGfWByOaL8hAPU@?r;UcKZ+UB?GEoZTUwl-M~czAdY
zZL2vWbRATHcaf0z=c?xjtxPk6%Jz&djv@&(YiMa9Rn0+Qw&eV|fSPH@w#
z6R!d6-DNS-E!;JJ0J;pEtLaOgT+7_J^B;tqfE_kE1j({eFvJaz8ghMaks%Wxss#Lu
zeY6^GSlj<4WMWt(fg(Rwd-b#0VOl4l
z!l429;|RKqP+XVA4|1e#LJt6~Cj$u%ONZ_2K8xHi$723E7Y2K+0O8jn(8_4*CLPJM
ztJWSYHWhPTaKcxYA0K?KfG-g`2+PjswnyDH>b^|h=P1FhSrn@4{K)*R{(~Pc5W5GW
z}1P-P!TJV=!{R?Fpa*Dp!}i)Nk?5mX*INxZw&=nNWz{)zrNZDl(`Yoy8nOTsLXx
z?Gr^+wOo0JDX6IJs~FAV4|<9qgLEjC)t6Jtc(~E+{~^jqTfMS!t$k@}sqbw%*A}ew47wK=7sq8_9~X2h?aehe(k1rWJ)(ir
z-S(Q9T+VcAz$wYA=BKF;;Uq>ybV>0EdX?sk7^2IG{a8F{`7UrET(Pz`YDG+SeFPHnuN8wL+AD5~)}xmcQ?T
zjLVdrFxV4dpboYZc`f@HJU_3yd9cm_d{xH9Y=VHxx;D(3+xa$xmPs!F0gBiIj!-jz
zvRuoYfP6eDUZO2<>rM^j1o%q=-;LzP2n@Eva
z#+q_l(;SY@C{fnaiAqYKHx*F@NBeCg(1M%@CT8YF@;S|dz|6A_(3Wvb@tv6L*XJwd
zee5)c95jKohfq
zU*242GL!X?zGRWXLOo${$2xE-sk7K;pB>F;f&|4>;MXd70QgxJ@y#Iz2u>4@Y)P}<
zybI#for*TPxf`bqILr)!_#IhO0&3F(Th9T&w87X|ehwVmobsmnN0st7>yh~XG54MU
zO=ervuoNk30L3zb6dQ;LsPqmZ(m?@HIw%l2NQY2uSgFzpU3xD;N&rO#q_+SGO(_9F
z00{)?-+A!Py)(}EzVFZPpUf!9bIy6r-h1t}*3J&p9B~786Yh?v^SB(|$#rpx9Wh^`
zi2*sw#7}WGwMGN0FW1bea>>3WqItGMt1jXcp%}Z-y&kLKLYpDnB{dOfyE$>fO{8f?
z1}3M(dc&~OyyFB=D)lAvtHEN!_OP=f%wlud>5~xFW$Zc9UrEU(g81@==PC%8-1`WI
zms8>rM4eEEdd9|lcZ}Vm-b6=#9_ax+!3T^^1)ALv44n=;)&njqGa-HVx1tRl!-46R
zct4v<^J(%{r{+`m)C=CiuqOvkV5?-eS0bgpe`HQN^4zI97#HIH`g!t`v%hpB9-d1P
z&?Jm~i6;ZV?*VPBh9u~1@Bw8fa_9{Z%8ls5fgYBLE-jEIxha%G0Gs-eI_=S*CbsV~
z(3p%aSW`Cm9W1l>Og>!#dytX(PB>7?L0_X7Yk1>P=LvlUFS|f}LgC35HJP8QSDuMD
zD3g~b)=5a6nXrL{D!Gx(ja8x@qr%+Lkm8;Xb38;%lI(gPdNzNJfJiAT%6TrNP9v?|f=+s!IsUmV;kB01(m;H#-z26+6rIbrW*i!xq|9O<{hVgVqrl~`vf
zmjNKdS(<9zix=%DfMBJg(2%h&Wfknzd5NQy?2DS%8vVtWaPDZ_}R}#eBKTxTyYnrh2Sc
zi%zZ;DTjMnj1=u_waQYnu+b$h+Ae>>#JMi6_sJRSw)kP_}5^Ml>`
zW8QeA!t@^)wv9x=9Ke<}-)xsza{)dYr7lydp3!9xX#NFCVVoYXwZ2X
zJGvd4p`e!Fon#71pt12%vPj4qz`Yg-m0Eu0wlFN7@vQn2%R}`?TaRcNQEcr2%o4S6
zsQW-SL*`gL9c92Oan4gJr%=Hv*|kIbyRO>FU-pLUoHK~;t$jHZz-34fXR!zWOq|vmdndNIx}|my*+9~joFt>_VU`|Mkc&)O*8xy@d!6OVtgiK#Wlfh)jqnBK;D;`cfB+#$nh&-zW|47VPL
zI!H(u&x6UkBLLbm8C%OS^==QFJ2dSSsrs?FRlrj9gyWpVcL}Du5ugm_%K%oVC5;+M=jYO}1cSImK5+?Z3E860~Wg01-A#7SC
zkBA@`PJBOyzM~M#P=T-z1sx8VjFc2P_JFMiHSqN@0Gh=10|hP`u@(&i#`C)I3J7+9
zBwnYoC>-~i!~ZQyfaHaKMyi8r_JY}HM*0I|kQ0)h8J#fkN}cJ#F2wY?@8X>59kk9K
zn{~SHgtY(JA`iO=x@PP)Fc!q4c7t;U4MRFlM$)~jg+F>^>?@zn${i>pljqQe4xFI%
zz_=_TI!!hYhK0%3%gE#p$5+@jE(=Xma&L2;+impbc=B_3Evg%UDtTU(bNkdv=w}FJ
zTu@}?naOiKLvZe|(eLy`;;X=&rg@v?0^qv_*fF+jgS70+$LGvQzi_?s$_g^42uZnW
zH-+zF>F8(G$Q9rR(+ttvdkkPpa~yNn)@ptgMEvfwXniXkK+b)HH-F-A{h%vj
z+u5iNP7zriUjEvo02&fg_Y#2>VFb;EupmACOYspX
zUbPRqQslL`(~YnFr2^{a0gBvHrIQs_Hd`BMhsm$NQ;e9cs*j=^60C2^A@1AsaJqmd
z3t%`Iv>MsPRr=L%1(2rsTyR8Bu_;sE9gcg`jeQfHtRhDKTUVCDu4qYLX<~4*9_l>K
z|E7Duh+9Hy_7!cG%5C=w-Md6d@>Vh`83<c#0rtVNkCmCO4vB!j}2*01Y)SX(Q
zag}gFpTdIqF~*NY9g|XKdQ~1i0-syq9k}UzR>|m(BW{?$g;mjEi#q|t*lw5kqL2rg
z{nhSv-CAiXyBs~#ITa?JUHXW8#e)(`5?KuGBOM1sY7m;h6UYFK{g2usP>D0ia=9p39ZDis|nU^ydyM3sOvj&PC&yReMaM5S@ce_`?pC<`6xlSH0ZjxD|5GqY7FQ
z?7ox7T$~r)80t&3ZmhycT#cQ8ndM%;-BGige#~BE7WE)NB+uQ1-?VOc?GISyt7PY_
zMHvYH{quwV^-n1esAs;PYBUc1Txi#0Rz`oFpbcb_eeMNCg;VY_RxmL}8PcKk);`3C
z>3chP*Up#z*t^omP|1ZmrM;Sx`i+K_B?uY3zJd*Ya#}e=Sl5P3UQcbmOl2j;;)yWv
zowbDP;C*){iVs?Rdn}pX;1MnyI_23>r_6i5kd@Z~+qd!#bGlw5erNp9Nv|K~GBZ;fHKEx1nN_@N;s8q8Hc(V
zz3LU7Szutpwqh#?EajRP74`+K95xHVuTrDZgVwL|YsvD)xe)
zP;{WoCde8+H&@!e8wn1$mri>Do!B3{QFfPsdTZkWV_X4&^wN&9-QY=Kf=jiuuY9yL
zZTYmHQNDPN7By1GspI6oZwBWerPX6a2g?rfEsjSc_YC&%?wcGGr2K(yckiKWnTV25
zOYliISj5;(bXmw(MiQ~}Bf;j0~G>-K?M1s{chbXA7ma*35o8$k0u1Ke8Vtc;9vE%0<7=rjj?
zKH7yxbNP`YBY5S;tCH%tei(JyikgaPB2rBBg6@g^aC=nGx#+~Go`
zQoOr7?ZwcbkwX77to{PFKtHxHcZo~4*CtgDSG(u=m4c86niBrYn(FO)|Kv5GGJ5Sj
zVEyJ8W!Y#p%>EuX_tNl3#|tsPAl?vs^Ya6M0wOAE$^zIfeF2;#BJsnAYuw2{+lraz
z&62)vv<<-PRTcu6)!AD&fCjR#{~LTB8OJ3cpN3nQ^2HVKvSvX&3jhSotAn=$}i`hAM*pJ>p;f1f=fjTEL12LrhCM
z!^6iH1*m8$Kw0##-^^fdiGV&vrgwFy5L(F1ZPqa7sZ)R40<4MaEFS;<8zCG_n4)*0
z{n9|8v0dlp5TJ9+Vk>y1lM}EoybnOS)2Uac&iF+W1dZ`-7)be0Udwbc=_t%Tf-_qi}uYTjz`0c7tP)D3ik*9^J&S*#Sw|@Czu{zgJ
z4$EiB24u#{N;Os0h+ev%gn#81fgQ8?jdpfgaF_&Gs^srn#1O)^nY(%c=uhDpr2!SMc+{d@E-
z&Ucg>A8wkPrvfhT;OoAx6`-|q0z?$H=^>6`UX|VGPlu#DiJ1PmsbOs=r8jxPBguJh
z(`rAk3-}ohi6GGWWz)Ap?w|PG_JOVw^W`8~<2i0gNkhQnn&0;XIFUPl-LW63zAfT3
zYQMW+Y+~XF*s&8oDK9xEx?-LJ4$nD267;{74f+JQiCAF=9)5ll;NIfPWpM4q%^$YwDPyZ2Y=k<6y9=tRsTQ#6&z>rt2%%15JM?S2`F}+;
zi?JNlVV1xyi-*lDDP2;;sang{;|TvQaDQOy!<3Dg0UsE7sdyYXkcQ7BbdD0HbavN`PmD{iVyzojseWo
zW0!;ILE|ifp$P52?ITH7AP+|h=mxv=vwL8BL-wx0E-y-nfU*#|)J|LEMS01lcz6{e
zN8o67@pa=jP@|q^&BN
zqho~kAtMv+uevRm!3vN9kk3A#%JL38&a@+#{g&LjFUai}=ezJi&k&EjEG9#hv#^20
zm6zq(f;oavc5sMg)xJ~<3C=DFn1Z*?!Pj_2TSswk)lOu+;i*scQphcOPWlDv
z{Lh_6p-we(ZI}mmVfJ4Ox6Vlly_9$2$KH
zzfkB!U`R0AX`2}_HI#vZJwiuM4^{4f({mvSIXbL5jE?R%IFTvprXhqyocp~8!vBJ*
zh6bnV_|SnfckZYRp&UE}tUMV(zFg_pi(UA7ivFXqjP}Xpf6w^-vT_vEJ*J#
zx3zDjHrfxIgK^n~P7e^4$KDwg6$MQ9
zJbP}I(Ik}>tS8z4Ncp%%4qwa#
zCI_#M7C-gRE<`20#k>@g@VQ>K%EQZBNxIte=;e*St;OH&yC5!Jrt?er6p_SaVvN(h
zN^$Wj;Yap%PouvtZOJ0mC^!_mWt_KE1Rft%wmx%!uJ+=?7wEIc^NV05LEE1S2fOaf
z6OUb2yv1|;W5|yk)2|ixK2cue{BiF+|3x-!`q{0uxXTIXR$~|Xs!9H>&z+XQW&rM4
ze?oLvT2fF^krg0p4Mjq=c@G{uh=@o$r=p@Fd^Rw6Evy{6diwarhtIB>$G#gBd+y3U
z_+a7Y^izEUgNv+cs>s9VFA_$R|Bnhrxi@ruC%rQCy0D65jPLB^Ck6Zydh1;bi*F}yx*}u@U`E3ON
zt*rH}APK6@2UfARNSf*o-nVJPkwXchYdyV2#0D2Y<^g?*cdL!w?%38U2LsmL$jgaM
zsSeDRk9#ZP@G|E(qme6jZPDYvFLTTZfL=;uFwvN`4yD1M{-
zP8ma^L!@K>o#%pX_Mi%xCFYyYI&&c##m}PoA{6hh*94BXVkOpu(3RnTJ7j-5@Zg%aK1#>#*fQ%9WCcHyN(n11lz&
zn@);~RrbSo1qEy@Eu#Px5Hm|}Jk4T_c7F22-OJ%&2fr{ts;&2f+Gc(j_kVs4A?P`r
z>f(i9%b#`EOi@7=FrgF2k4m5u^3)5`K3oHK)sG^J3R7k6uJtFd+NLQc^c{UV;CMzY
zUVdLt^N}v|%2jryU$Dx*Ckbg9DWfWikzfW3NUTaztsiM!t^z!WFRE%9MKh~aRSCg^
zg>}BHZm8DGYu)D7kviqHB2eCHjimGm<>ldX%@oYGN1kZD{V!MQt&1sa>Z5%s1V4Isml*}9x@ec33VsA_
zFCR|>y72s}IN1JQcl#!v*$vA6ZaI1}5-+5GyPm&qZLZnJ4*dT7PUrLI!T1uNr;}4t
zdQC~l4{zQag-dtF5vo4sq-q`2-K7xe^qmdm+PR-!FNWQWhxWbPg91I^aAEX
z%%+g>m;DCb!M7S%tuYLDZES3&H21q3#v6+DOgwq^MDj}?tc?G!Yyy@wg6TK|Lr3Z>`GNmczZsG-
zcdD&}WAqO^V`=8YEy}Xt9**bF={*k2@;SWN1{Zoz^KobP(QbrEj^O0pPW*j80Y%cA
z+P>jhXeJxgRYAe?XV0d4Gj!bAJH^+IcZp@xt(Z70_O7q5XEx@4<|l3^?sIF-(9kOx
zZ77r0_PzGd0zcWD8^w#PLbfJm(b;zmtvnEMl`7INyAgr>S{#WS?c(X^HPskW5P$C-
z@d{csdp=5^&a+Fk6}32{J#qo~Z~xn=a^P0YBP5sFdJ2zN0;Wi7Z(h87so^9+)X!oo
zEze?me<&vS5cmgO`IJ7%m=xEC%$0rN7ubgll##UkLjTU
zJ7_@t4)4IXC^YcTtBH@t4Do4J+)6r(>r2`|
zj2HvNh-fYc%@4|Kp=0Ii=+qOvcc@d0!gXy|6BuK9Fg5S62is}yI@Sp@V5*P`@TbcwxB
zP)hgkJv>j48PAoHYP_p~0qbG7BYs+W>zYvT)h6(dl@E_6o#0ud#_wrI!TJ5)wiSS~
z>?l&wQ$(Ti`0?XkK+De2r@MkonBIW0TMSo1spCpk=;Z~TsMNBg$)f#eJ4ImJnRmA?yNyWSo
zBMIU01K#Cga0dJ(c&!_;Flj-3kPR<*ikvC2W_k86PxilO-1}2IUrG=yOnv4mH+K`8
z2?9{}+x;${+I#Ui)hT_nDjOXMXkc8&*n8RrbNE@2ntA)2u%^>Ab@G+2JObk3mktg*
zb=I0|lP23+<4up!)NJJ!FGW?W1eMA=NW&~TyX6N?j+VT`yyfFpy=>X5_qGpvoPNLO
zh*;u*@-U8e%amTwP9Z?~nm5B+6n{<=SO*CPXI
zINfwR!5ig@l@QIQN&k5T6)4Cp&iXh(cHC71CypMCm2)G!t8PZW0D;{H+q!GOFV5z2
zu5q^e(!Jd!8Wt*#NP2C!L_l_h<6FQ!51hKeO*g_40nB->-YvyF^~7Fy`SKg`&co~4
zOx5sC_RKpzH+-ty7J9@7En}D8yqSzAcF2DC5Wd3U*=y=w^1iVgaxko#t|l}-Q2kvF
zP;ghSKC&-Ejn%EAwe`LxUT-VO)dsu@s|RKhJAFbs-EKHx5Osdd%DXg6#kait1;E8g
zFs}dxQ0v1Y=z`u&am8;F%xxT2V>-F?nxDlK9GeKdGBR^iovGA`er&k|Zn9-X%#V{$
zSKof3>XP%P1>O$1gB&{aZdk_tJZK6|Le~iA1
zSGJID>Jm?0?eBVnM2>VBpUK==3Sm`52+*pclt|xT^gZ<&Rfgwp`Wa#((8-}Pw#A5$
z__*zb8X1B{1(=9-WIXjTwt)LQcRzmy^*?TgpWlxzGLZO3Iz=$JC|%!t`*shcH3xeS
z+Z>1fddl1VE9^yUKIlb)TJSLDdJ%r9R`4`!QlsmSW(RkpPRicF?r)0_y$R#fLa1ID8v`=y)Tv@U()~8joFJl#Zq}-%RSx
zgRlEtK7I|}Khi<-o05<7D8+W2u4I=r@Etl}d17uaC|oZI93=?PQ7nw!o;oU|L-X#%@17#xNC2&XKJ-WXa)#
zq-FP2F2!1UzZ~9rd&2Y*QN#&%v-~~ZTIUK8cSk~k^umsDbALzJ`0H7l#WAkmjZ_Kr
zU(6}W$e25Pa(y9uj2>FJ)q{fw^ysF;*2}M`Mp7NK7uj`{!^99FE-G>}pTZhPg{4W!
z!@u&F_VYjq-;wzKv0ii_)nbSvAoflQRrH;{Ln?KSBh_-Z)rX!17;z#c-LI~HXOum|
z5wL92Rc-%ku>P~2xpugV^ASw!+_`hH#$Q_P>dFqih5cu}k^|8LI7Gb(MG)A-P++}W
zxFe6Z;mxCJO1k*^3zoACCPUE1KgFbYyz9N9#BpA~BV$(Nm_#G}uwQ5We6D^{z=C*B
z-T(uass3%tXv1aM#14YATQkq{%mlMvNIG*RuH0wUHld(mgXV9B;95>8DFvTwsbh}Ct!+VVoZ`86T&AaEkt+mrU6yn
zSAV(Q|NK!-jgHCLn%+GG3jRVx`_?w5Net3ygTDB_GfK&Ra9Dexhjb@->soKw1@(;V1Zr-r)@Y4{%W*FGIl>+9y@az+4
zz>Evv8>s=0iFE912RVGk4Yok!-ZmOM0r>ttr*wuAqU<3LKlyJPD1v#phEoleAZ|2!
z2+wgraDf>*BTb3#i!%LI+iNf+&q$W~
z9R^mnY^|ZK{Re$9XkG_x9m_Vq-3YMsH%7fk8jD<_yJX3-sT3;4H!^-c38g~tcZ;ky
zj0K=BB^l$LL0TrAAJlOvN%BTgC6*#CK_^pA=fDhYPFzwjJie5;zXQDr=m?HCBRPDR
zf{YNviZpbD@Q8CGY)>IckRI3;`#rm}{QS0+d<=J9MU1;bFXY?F
z%&oaHMC?*vgykr$LfRo(Hn*^vB7Y`-pn4lwt-i{VI0j}K8Q&iK)gmG=G|;mSo(wZL
zn(cY}avty~rd^jg0;oA)E`ZOP=8gkQEXWP84Gcn=(I*1i#Zn+%h?DemjPx~489N4D
zv|JkT7B{$hsW={dJe;XclCZoF8W~nlCL-5&p#0-lVD=|lK*I6z=-Q%c*m0H2%cF^N
z|KWgB8cctp`!!d1xr`#Pz#AEh!z(
z!LnPt$r#>919Kr9cN(6Fu6g2rk{IYaS_N-qkX(5a%n>(US>dxs=s(Iil
zrs>5CzAH&L+T%E1V)*zOOpBZylA(&_ZqP-1YOJ#AdIyz6`i<8Sfb!Fo7(L4BR6`VD
z4jkmIp;apVFA6ZJ=n0UCy-;4SZP|&Kms(usjY3BAF{$5s|4CIg2Ut!yzpmf>
zR##H9eyJjOIWbiBJCEc{1C_<_;1vqI-{{OKQV=csa?Yah`bt@6ZC=Il1I@7E%ribE
z`UfB~h7|=eg0nAXH>a=C6jWxr{HF^bb)bxbzD=hEa?wUDg63hCdfYd`_7Au37@r3-
zehho;UlLoHU+~@N4EhC-WZ}GNV1Nz8!t!!;*c@$o!pF6>bL&}mms#O9J@r&L(5K4F-^<=2B6cd9+wDHT--BD%3QRDsZMeH+B>Gy2p2O!o
z$J~SI&w!U|wUmg$=`og}y+2qGw|%>x9rW`Ukbjr#HD{B2b@2t+(WJ`dZKA~F(Mqox
zVs*>pz@Z1K&xeIoiws~QxM#W@Pb^dqmPeI!E)xMY`f-?
zOeqG3UtyLmT}nW5E!Wc7U{kQN$~^hm2iP;n;L43E3C&>=hbl#9*O)b;Alv0i9^%xz
z#)cX2r{@r;nJ<0m9fmF~?Wu{|^BV*)d}gf!o0^-W4RAccjkhG*T$=PMf1d%Smj^_Hd|n6`QZ`c>lovt*fR!A33CawA6dOj
zzEaohT_1MLUHe=TMC#T)KQ+34VbhjPI)v?-5k9ylrOeY5h9Wo*j%JY5`-KPIR66>3
z?|K)wCuIYNST%^^++BYgI9=HJX~YsGUR7pxCLR_9MzQyoxBd}ZSoW{DZh(HSpXT;d
zjHw8Tn}E02O|5Y6D|J8(-B?~;Zu4!Gk;Igwk%W^G3sn`iuYf}bpuSbRhRa;`Z-j})
zN(6KGztf+yjt4UQ<)OtVCrmB}!HN2QVANR)Z&B^HGUQKYzznmprsi?B_wRDFf!jv=
zS~qpl_Vv6+ANrp(N^U%4Tr9ukhDb3-ZO7-h_LEZz%X*GGZf%hSd=0w3-Yl&DdIR6O
zBRz@DQ`$IGl6x#m-TOn9e{s}+RfkJbAk#^ie2E`Yo#Gu4PLzz@1-s8SKAeo6>Iw?}
zA(W*h{U(N%f$RweF1xYYXdF8hB~Q3LFusS=$X%e^2n_ST(6?En0nDuJWbxIQ&OQnDuYBJ>!!&L|1TPwHHAC|%K3g7|$qOqf4emTEd^~s&1vt}#<
z^P)EsRc&o^^*g_O$*s^pNZIw!wrsBnvOe6J0Yc~XHV$i7&=A-74)~~|WqrZ@4nx=A
z=hSvr0I5H=dtl|%N)RyX*}0Yv0R#m)a=TWuLWd;gZe%}iC`*1r^TfeS{AQ;`O}kU&
zjiUHbOy0vz-jSK7kLG_emp6*^y+f7bA|wtsJwBw|QzXbgqfRGjX=xXuuyxF76bx
z#~AylbOGNOPmW4|`BWz-S5GR4JHd!QEdMaA<;5_UZzkZCTEI|@r90RwbWE;#MbKguzJSQ?
zm|1n8?+u892lC}qWCu2#5(MEPkXC&eD3cej!@|
z%BmE?w?pI(Zikv*l#m5-BQtic0g=7YZ(l2a7XRJ6OkXbG*b+_JR652&fmmI?!q!q*
z!4(8xf9}iz)u;zH{qmYloOd6!_3C9lBfDWT)-yFF##~)94|d3Efv1utPx5Gq{5WdW
zu~9UWSLpUwP)aSKU5nsN0Mx^}4(B3)W`nE~sDK^^kqXzOC04{({bc1>KuqOW9N+%^
z{fph-it16f??S>I^TS=I^MNU~6&Azo=N=!Fsky_^1)g8V8>Fi3=57mIyTqNQF(9%V
zPAPNjl|zy5F}!hm@d!*YOUOA4bRt~LThA~4v9;As;_rgVs>`cs_sZ-
zLyOyD9olUu^QF|r-~{AI<}Dfy(Rx2{T?SqXqMVyhk_%F+MvqIlVw!k&7D~T|`m!r9
zkPMGP+J`F4a)cWHgI0i`u?x-E^GXAgU2R?6Nt$Tj^gju<0hNHv!h8YBGxozUby1Po
zTFz81HW3`d?Cmib*m|&rpF_#7-d#b_M`OiZGks^(wPrL}!Z7QR?(F!N{MF4hEpcZa
zyolS?m#t*vkQs(2(3BC>unP>5^W%EG;T@15lf)Ku9E^$Vx6;&X$8`^sd#->3$KG3K*>4Af+0xpceN*a)bL72ayVPfdIs~`0({JJ
z>-s?pC3r_zdAZ26GumT@$Io+g2<|`ADp1ph9INVyV03iqV_49LTc6NY}x-sSR`yJm~sIxL*9pa*1qJ4-d-PcMM1
z#Npv#16=uuQh&x2lHJB4-r?-s9Y{D%)r#skC{W7@#u}u}D9kpA_wv|6(^3F^lE77D
z`G^K~RlqOIn5sV>m|XF9clQD$s_m!=G|_Kn#s=aX?B>)8AcZ{J{%bKCoD4bM
z)GG+Q3;ZaREEF
z{T1QlM&d`SKb+vt7N?bi#dnq8Eqx&QJ+&}i+&VkmQL_jbyPIo^c&YDty*Tek!JAw6
zL$!{={xJ2J{j_ieO%eJYm?PB?v123T*{tLid0O`ILWCfYJ%qE5)BppEM1ec6YYjjm
zP~|D;4M98l`wMHARlDyLXK?hbDBQaMx#A0IF%vj!U<{C4x^F$}r9j;v0MVI@M^j1z
zjCb9MQCo9)MXDfFjc8!Y41xzSj3J^UZjpxU7t+!1SBHgWhjl^8DeS$SN>U$)a(i|h
z>51JrdrtSnc3^=D0q31ZkYK?53aC7DqwxC!M4~m3_mhA$Zd#gXL+hSV3P!ay;rrar
zp@78#)IvH1N{VU0^`5tCQv{~0vr@p3#uwXrj98N2=m58{c$eG_@$oB@U^nc7JTEvA
z-K8Ce#n!>qW<&1n9(1glI_@b8%1!{RT02dF=}Eo&2IXA*-ot@%+_P`ACu5rVZlxod
zRQaiL!ajmoeW9!55#q>(pZ$aHG@c^~&tJZDmk7fwlq-&Vmqjc}b#~kwg3YhyD_A2U
zX`_z|E#N3)^k@2gE-z
zTm|h601cR@*BdFET?_cmaVD)SLOmH{JYXVQ)1v2r
zfEE>0U}QE_YH5{ik8mn&HG2==O(`3F^ss;Ai%p})dcy9dcSM4M=IG*UHMQ0@tA_11D>DvkeqH0dvy$$gW!!frH9GUD?a27oRq^`xXH}ze)NZx%P^RglT5`8KGc{$
zqKX)uFp^r}aCRf~6|k$T>@YoUSedGZjB#u8Ta(x|%k}lJ#j#1EpY$yt1GU`HBZ{ZAj6(v
zIrkzHbo~6Yjnj9j#x~CUTIn!3P>L{}>L6iE+T;CQr{N0=uQ+pNL1Yv@n=}h|LoVto
z-6r`resn*_hD03-MQ+tZu-*^(0W>V67VF_b*KrhE#;E)VDJE>#22YWQ=(
zt5=cT!nQX+gNGkjsu3Iq1l>Qp>?jWtR&b4piFw(vov)%qR34PFJs$*D(B5vW2NI@x
z43-R5yX@`n$=wYet{IJttmk%rG(C|)hpfKy)+{RS79C?jd5ac0Pg`q_ZM}Tkgd1;@
zblJ{k6((>Ir}B&Lhl(3KWsttDop{<5JzHN?e>z!g_s930M&1_s@YZ?kvnRCO
zM$WIW_hr041FtNoV$G?x?#Z2;zHU)_#YOJ+Qpbsoejh`(_IgHx)d2UZYIAvk12gX73R
zQtO6v5JL?bjAih|0p*{%2}fxFh|I$-PM;;OCR8RurK8!SHjx?o7*t}4>*Q8Qx!nN&
zOp7(`1>*I&WIbTFn(4nJprukkZy>65u5b6y88>69My%lw*#y;U7Bp!V)14@tK!sHB
z_m?k7A;p+>#9UbmJ!qFgjyhTN#RRc9^=g*g?%@Y8#8Icfv_X1pSb-lsc~8Zal4oZY
zIkfnt51tu!4|d^%1bP#j`8IAIfqQ9$gkP5{7`R@v$EZ0u?rquoOHKOYh_s;oqZu?&4sg|tLqWS!?rgYK`j|Db4;
zAEzKX)P6WA+>;5+RmNWVW?EudhZ2DIWNn^2DXv2|7-XkOL!b{JK|(&PTn1*2?U!+o
zLuBnL#&CgaL`$is56XXDki7^X*FOYuCh8Jkz2sB&nghblj44H$?H_e`4
z+_P{zxB}SgpClQt4qYD~QnfdMaDNGa``cjF&ZCBaOYt;+x+`SQ4leXTG8$~_ID8fLoF+b`es@UmUxSOqn<+Pt%Vs>upK
zASPc4UD0)X_mxI)?1g92%uU>2j}ObGffzD2Ny0ssm2QCWU5lJ!$CnHYO@@KcH1i9P
zp%Gr9sXvAiw$nxyf8J@?`0g;>vD#Ko3D@Wq_lI&QT`f;4RYjs
zeQRgdDB`hVI>&66?r?C*e*cK=Wx^0Ro%g_9A~joBi_Nb=g{tH2+w-qw%r?nDUk-E~{6kQ+aS%!3_Za?OM!dK&rc=mk2!VW=pcdHE4fwMcGB9KTvr`qo~3S9oteX}m0
zgbbF}DNT!tva_3K6{Fc#YJ#c{sj8_>Jv$rX{E)sP_&N+Vrg*~GLS$Aswo`g7QL6$>
znE{3aI_hznU)_P%%MbfKjeoY9+@sEoIY17WX}pxt%tR-&ox0X0l#&<`F-IMFzisT?
zqDVXUxpM%zapxZS_6?>vGA9)T58$bHmkcO7PB7|RUA3>9hL4rBZO7mL>5w0kLb;(!
z(hwb)WH7hnoeQo56-HNEx9{+SrlEU~y{IK5)z>-$uAvsb&`foJc8`3msl~Hhnpr4N
zVE=1+1;sb(V?dK3YTASAkOirHRHUXvi&2QC+7EDDet|^FuHHa}cz8RcL9*f-fYJA)
z&0x+_ui(%^S6B*3AC80GP_>sXJ7UDY4i3JsC
z1*W)K)ZUmzQeh?lIWyrSWa813s_~*mS=ut
z%R`9UabgnynBwrg3W>)5d;m_NAb#WOSlMip2dHm3+Cj9z96D=RIjbF9Q>v2O*PEI-
zrv|yP`>rns_J@d1{~->Jq~fFE4cgHJP{%TgFV>Q-%Z34eK4cmP8FCSk`JUhCj07m~
zRf}dOZvfg(0T;v7h0)3N!NEZ;r(S}->BNHv!n@-+Mf|v5aCN357dH;Z$?IAEddz5ZkD6qO;AHvIF+@FIO^
zDloz&WuRm}5tAb#BP~I|7HR4iFA#E{>uWhAXhpa|Vwxbc>;YMSxpwXSpzf^;fRn!P
zWF?+~28McZl==ggR7Jd8MG$->Q+`}7`MtZC!{)xti1TVJ`*Rv?aaG8QARu-gQV*~U
zR)_V?jP34tQg#NJU4U5CY07f-o3LoN9XxQ;#@FDEkfbX_i}N?%MBVmm>$gDwjTE8x
z5Ewbb@E;KkU44V_fYJS@?F??`w$lqamffD^%W+(EPX?@!Dp1I4XxSm*Ev$zyj4wej
zs9g`-03+QE0pe397fhdH59Xu0bjj21FEAXF;(YJX*4{2Gzh;I*1N_^+DiMI((>Aum
z5r;^LZP11Bi28>v>@aT%I(+p#X*B4SY9<#97gC8xb*!U#fD&$2&lGn)kR7Rp*8r
zsxm81uWP(QhSRYZbTu4M@@k8o2PBA5AzRZLEV^B^&A@=^&Md%#=_3e@n^@NBE7JLB
zhe#;bV6xT^(&+dqD&EVhYn@V6-sg7YfaS#}FJ8Ft6lI5fczCV5o_SQ41S_l*IzsXp
ztxFHR@lD@F6Uv@?>3)pnC1h$C&|h2v_5|yPD%i@u9k=hgKez(yg%VOrR-aoYlq;2!
z9l_O`)z0B%Xfm)2`np%q1rVBA1r?51#vLy$CosAyUiDf$2H7#Z0kwf$KS0Nx7>h5dblE
z=@7@q$`z4i3iD`}L9aaHw5tR{dS#Oi>7ov3hU2J0f^P>fSWI6xkw<9bXeWRe-xM;J
z9Wu^3R0EPow8qbmdv=Bb`K?>l`qa!y`xqUw1wRwU|yW7WoiH&&fV4%6PtG|j7xR)DU(elvgImT6B`n#1Pn
zlPrcsYO+7(B8PqK9n=s8gsnUO(5t7(k3+^bptG&wc1~bB&9t7%3pjWwa-bNZ-O4W$
zdHg`sQm%9yq15X|9TzY$!s7#KCgO$d+7w21(qmvi+fKTidmClj^;TxQFu5Dp?(rAZ
zZP<4C{N&g?^9{OrpQ&YIDbiK^c}Mmid3TCD1TGEkTtWM+9FE1ZN&D2UxTf}Wwo=d4xG)KU>9rN@nTWq%y|3Pqfk(@Vw&mA3Kf5$IhHk+fNjVz
z7HQD*!T2@(7X0`kmKi%*0w=ry<*f7A!NYjf$=S!0)0TBoK*ZEalwmI^$YY=
z$C;cL+^3TUD1)cN{v@#01C=%pgn7#wJIy
z{yL%dTH(J8o_KbM0!i|=OBfTpIw^h-eASe7>J<~MY#~AnQGlKIPN?dGzS$rs39OgYWd$jmP!JUVcb}
zmFB2sm2p8p3PcOF@o_r)E2siBOCFmx&~Wth^n3`o90L8!*_B2~HgQ_q=JUB)ur+?`
zq!-5A+P%l_VQ0ZsWn`;@8SmlC@^F08XC|Cs3u}iZ)a?1&_REzWoJmabFwrtTe&R%e
ztFneh)W?q>tzL^!olMhicI7
zTS+cabmayVY$k?_mOfhl{>`;Gxwt+%7)kB};@Q|$z=f?$gmxoPj&P)S%?pi3NCtTC
za+MeIIu7MM$2OFe&I%}-_y@hQzhtAY7y=bRLf7OLjeQtNJ0*741?M4e!v8gpe*TFm
z4JfqBq=%PDq;`|II8c*B9JzN;cTW-Z>^KFwk|1po2|j9<;d-n4^VgHYH_ISPEMEaodxXl2u(3*-$wlRZ!
zEc>M5+X|zr1AyBd&Oc5ax5}swMhxkSDcBI4v6hzo0mEmu`^07c`SPb!(9f(1u{TZ8~{5{Q3
zV|<8ZRzTIpCeC~4>VJ&me_d7R%wz|O4yLRF2JQ=*3&0JY*SOj(9GaTlC%>mqyxKPGq7KfWmkrVzm>62zxcggq
zt6T#Zmc-!k?)C`auUv%UC{TtjkeE3_nlL+P_9WWdRH6+<4n(
z1R5^+DKd*tGt0G5zU{LNcO%(Aajx6M!02A31Ao`>D_a~fU+A*=qgD}R^H%%u5Bz-51x^F{dsph?I}M}B
z#9Q;#{6uM7^!ivCQRY%3FwPLKt=p3o{_B}x+D{qu@EA&6Yb+u3Ve8kU5ZDABb2NE7
z#t=C{0BuJD-Jq{OV=M*wHz=c&x`HNA`U#ZR4cz90J6uMDm1^^|?@U(@;O
zh@FQHOVG0;&t7MjeYN=4%;c|M7s}Sami3`66EFX6&;Gm@IGOZ;hsjyO_PG83_4fv4
zGXuMCcc+uwzh-&={cV4~CUn=M0mZi2M^=pWcL)8y3(YwP>^qc33_sKV_V)Vk5`~`!
zTU|QJ*a0=Z{{GCr{`d@=2ZF!>;V?NQlJZtU;UKj;Jh|Fg7b
zrtRFGi1@gGZ#G%XE5!OeV?
zqx>K^xa~NrDKRS5D-uikTz%-K)Ml7QISxZp#p
zyK_>?Y;|1B0s+O%PD7fU+_WJE^eaG23Er-cm`M)GuuEynNimO4I_5ZFE}rzCH!
z%)`&$KAK&+F5lx;+&`Lopd38zm4)}<&nD}d%D?K#B0?(Ix5fBY?#7M46F4cwd8GkV
z3~G7lIPpSON=jg@>?s^FuXQagjZ33!i4ufe_1dpz>x$n{Qwtp(7n)XdTYBSjmV-kU
zsEr0+(szXR!0;SfP7!HoY}&=EFg#MZgHm%5DqxxXmo0EeX^-Vq?x?z#lSRlUPFGhH
z$HkUY{ChA?ii?X^kB@$N!K?N2d4${g!P1%D^d?bSR@bLD9hc@caplkmYLua5!G~7w
z)cUXwW)JdOL>Pu%&=}4iILghbq&&qVugL7d!fziXvGJ{aJXqkKq-%G?uB4Rb&BXcejG`P$~cPebI`gr&U_=dGBi+AGgU%GVZ@_3a?-$02%
z)!KFUwZ!#(8-zRki(NB09sK)>d3-TrD#|xQE&7Y>ls(#$CVdX{c{N3h_#>|SBa)oM
zt$6P6p{llK#7l=8CgZAjNX{wR3OrNLN;6ho1ULkit1a2VQf2(O$Voye!l@xBo^DT{
zQyp?92?o9QyrNNBFMlM?e~oVcx5N&E`-Er>(Bv~0O>{91N9&Pv4T#-=(bJTp*oWP0n#_Y{ArOXlh
z2~m@pK-g#Yk@-HgJG91hnnkt*+JBLe@pN4zI2cNfk^Ivw(l@3ni(S`yw0jCYq%>x~
z8AvRoagW0EK9Gdy!rBuiG3_@&6Ny9=@L_fhR338j;zvI&My9(QO?H0X-fTZE9J3ay
z-jub-aA8U4G>lq6o@rX;xbd|qas^xrzc$4wr>iwh!dr_JrH-GZMm-d&4wSelP{V5!
z?tz|gQNhXOA6>COEw?}X$Tm$TS6_ceQ?@TNmjkd*XB^(NpXs6SE4=63L?n+yl%p~z
zg8H8-s3UQBoXz!@1o=aED3|&~rV;8^RX+)POEYcTz<>6FcGkr8$f$&e6wMGOM0!1nU
z*ZQ^xRQPYWx;AA$qmU=`p(#abzjp*jK1dpj*vfKUm@rbbF0&&KL5=<-!`OQItDgJ3
zs7BSpXA&d(-a!|Wx;1XI=c7+Oz@@pN4G)6&6USthkCRO1&O!>@FA2^X21`xFrBgv1
zi1FsdijIlf?h^*;Wxs>=|1UxKzb*9JA>49=3~IEt+((og$CtxwRuvVksRR#yVK?D*
zW@=@#GLsv0Ux}_*!(hj)FY{m42l(d!N0YVYi+zhYzQDY^Nsf~O9^)sIH##aeU%hD#
zqV24-7Gx8j3-oSUK1VX&_~O5qn$Ar^aOmUoQP$Bb-B=gr1$yPkg=f^&Z#TB>0_uf3
z12lHruBc^cj|t(T71ruv-QSYHZ=1^-v)0W$AN$23OXr_Yor4O(d1Vsd^iJj4k2^cZ
z8I>iz&nCr$?ks+pG8p+%^U>8T&Uy0;{b-&7ul;sd-`SNu@h{14yX!su`chw<^(0M?
zi+T*1T899T_%`BrF=ul?jm@FYlLP3^D-X;9#(3lyJ21-?9~~{_T!@5?joC*1zP)sE
z*RO*$#%j1$B{(4^3)?(zS5f!Dx^G1HEZ`r}CxQS>R`m>Hah2h^xQ(=#w5CeAy_|VakM9+t%{xa2VFNZB@lLz#W%kAmv)_GEARDa9saz~
zx5yXy)4Y9uQAlTc*H^v9u#FBNCalHtp(;FaVae4$lg@bawzc$f2Zt|`4sFm79(@83
zXQ!=P&`Vy!M9^(O&s}&7lu8Y;?_e6}^^E#+vm!_m@7S!g-efRxoF8jmgAvyCZM#_=
zNvyNEWa#n9LwLFXj~b)$tPC6TrS-o+5B{{2o@&Jbg#Tb+CScATHQTN(BIq5nMxyht
zSO!=P2is>EWoFq(bqQWAA|Q|}mRV2oSMDqim?@VMFsLRz-%y5Q$08mi>?{ipI{A+b
z50By#C>lX%n;oolUg2BX2W;^*SNdvf)C?7eoIo*cz*H-EXTC00zcv1|B+~enNTy;W
z=LWHBu#XVC+F`od@V;I-JX+v}1R)~Xm9fOqhHeH#CF-w*Y4h?`T~s_me?&1FMDi7R
z%T_P1t>N@sms>;JqC^V><1*{9AF)SQx4KR0ShnP^Hbmtvf}GLAcmI@~0$lqb86yXY
zao2VSbO85028bS89)|IIYMP8OO7rI$^8kX5f5jw~C`6x%YYQzO176U&A=`oT@+JmR
zhO$`FOrn1=TW01_-lJn$)0tFI)aiL&r%SiaPqCL?Oem>LIKQa1c(yrykA-EPj1kXN
zaU(AT&$ZdcQ1XNOri6Ch?<_KV^j!k}prM?Wkuf1vCH5#`9vReZkooa3HaIqxuPb0Z
zSz@VXE9r)d-k`&5|7Csr$#_o_%UAQ-zQ4j}c8wD74{z98z&yo9{+JRPrbBJ);s|F6
z%6A2bsLDM!Y1Jz
zdEol?#;M?`>=hP6Ep&q0hJ8{O(9@#^iZWeH8`U$c7T@U$v`CPWApNiPc>^bt#Lu1k
z+>i8$K}0(R%a%@UM?fUq*O!HO$H)9Q*Mq1z<=mf;RjvlD)-i87skf=hh|@?!7s)%M
zOIvGWRUy>%%&Dfed$f{x>2^aH#c8_`RWIB3c}v!j?63^#SlnFRVyQO7D&JL_%v7K&
z)7eQ%(w$rh|NO>(^0Yb-92!#J%Y!#>Q>puF?AVY;Guaq(eaX~Y!uFB`%JAUdtv
z)zY`Uft*-(Tk#9QT9#kq;^H2C&LoR^kf!M1q>R?anyu9Ou!g3hCCtda%|8~brcIA}
zS0HYWA6i=UdqYaAs`Sb_0Od>4Dwj!4y>g50lznKqvbJj!%F_Icji{Z_10RMiw#_}1
z)3fxp%}tFVHqDhS-&UAY-gtK9pZAC&-D9ntXAF9IJ(`X6L9F!R
z=*8keHxA)JYpqN%&A3{-ADDq%lbD~#z8}9g{+f%~|1Q1BLD{LyI7ML7#?4<%u7fks
zC%@iE42$@PG>gd0UJJbu>n^08gxHAswzjI0TWJy-E^)ie4vX2BfNR?L<%^U=Eus+Q
zqFTEk8X45glsBI8*gS1n&9B8PvLm5_>we@f^wAp2vJgGcwzfuYUx>LZDXB}rB6f?E
zp=2U814s8g7K|e(LX&AgRZdZnsIfAb#iX8bZ$d>@T_DG`?SMzNss&Y4^Elv{lInHs
zz}!O+Cn|%ZK6?vWw1+T(Sl*`8U&1qu0n|45G2JRfG_n%tBK0*_XhZi#k7R3A>%ti4
zO;1PV#)=rIl5;<$?B!%a<<$e4j3GFvA*yK0`gt;#IRS=iOn*%$~
z^!I0%fYT9pf%e%oZQ^!x55wEu*}@4rE#M^Z7>AMr6%KQIZ{6JjUB&VMZcYp?W!vZ5`c+c}tGDm|2yTrFJDduV%{dJa8+o
zZx$zv=If{rL*UNfN6&F~5e1BzCrA})a@4NGzFL2A
z_K#ocXjd7VDW91WeH1P1Ogfwl>^0c2n@ayUJ~69{K|I{thsI
z^pZK|546D|izm9UJ88LH@zI`B#HR~@z*7xTe;K94l(*j
z6&D69Jeulm^L!rlEKLLd#LDza&U>ZBv?PG}KgF(iMYs``+3puJDE+G|lcG@!qiSfm
z43GsBt|*G$tGUwkUIsUQb4P2z@7uKf>#v*{!&>VA{6Mdy`Lqa&So1)IO>p7_oxVj~24Wcx1
ztrHJexHOvU1`re(_pK;1c9496JaW-xVzds<4rY<7tNXfEHvpZU*O+~IjIVN4J9A2a
zY4xvaGQT&UeuOw+8j=JR729j(StB}SMi09a1rKGWmU=gK%F=8uPG#?KtiK8f2#d(B
zoK$nhMrb)rmr(W-lVifC$J1RHyJ)%8@7^u36IhJkXRy_o$m70Vh7z1(K2Mw}^wz3q
zJFrjqTBHy`v{l`00@xzQ+{oq8N-mmW;*0!dZ9i?i@HKnRLU%Xz^1r;VLn+6h0XQlw
z1LJso7aq#gbO9-vP{pVjEcsY|=E#dYYb;@P1vMm!^G*h;GYgxIc
zhj8u;|HtHexB&O(>WG@9%86
z_Bu}1*S|%DaZe!I-NvQ2N6=CRn2avB$;pOH;eaebJxJ_AwVgg2kUAfQlRkH>8aO%D
z?z#ZWb?g)(%_k8<1I+X{uU&>t$BNk-44tM_fKyEesoFT!sQwksk8`K1obN3uq9SMa
zJX0gD|NN)q^iMw)ynxoop>E_MO`#PpjLx(~v!}h~OMnkKk$kPg_us4(V3mhnumEzQ0e=$bO;RUZfoD2*C`{Tu=
zO_wi?hTBLN|I*TCK9#`WplB9->d*hRWB!^MT=kdMzmEU8M%Jgx-bGBfb*=k!Inw6)
z^vOzKJbZo0v8l7Zg8iIamyalN=*gF*bHaOWjI9_nzE8=yVL#x+;gC+Bwt7ym;O46m=+7KPVv*;>Ikdo;T)%63t8d}eg0(0Tf5<_H
z-_!Z#Y-N}3!Ur3dwGL{7b3#ID%YBcYWatzt+1{9uo+vZR4h6#b!Gii=r@a_3+YBro
z2tQUZgGX70tWh7Q=w~(djzLijK6?1jRmcNMaYMwJGdiU67iLvq5VHV^7X;h4HJSH{
z)rM8h-wkH}lR3k%eqW4m_2Hxy>lps0_J($~q%ix0pNr_#7tTW~J(bhf8-vseh_jLM
z;jWaNl9KUm>UCKINrNy#(G>d?WtL%;>?tAa>bI>7ndaNV181oj+zph7fI*iJvH`v$f2$wK)QJ+A3Gjj7Tvb^4(t
zSG=NZmM@;XJgT31K0C0Hw^}~N)%tCFnCO>->0slXVA@nG0YL*K_kV_?y{901ZEex)
z=N#&2Hp{r0MVDDOwke0k5D?Yqn=YP9&wk%GLC$i1q7UtP$5neyuZvvC$~9;)Hl|$I
zBu+?5YO_|eFiL#I#s;4#wK;?!kKGi-=@@$riXAPZh_9R&5sd}?%WX)hDmv@`t!aFT
ztoDr4VVhS?4nH&A93X6+Xg!^a9O`E6r+8=Q_LRr`(;e?-2Hqtaf1&sYF^5vzz@>!`
zi|DVLGfUt>;5mStbSdMz1m>4m%l%bK`c-uQ5_>Hy%_1SMk-%(Q=$86veM3ru>pLKA1G=DupeAWhp%IHS>R|djh
z(A^MW6ve9&AU#pdzoQHo#HOUSw|8Q>oOuBE6=dF(iQ>$*^(upA
zS=eJbiBXKpcmxhW;%PpG_S8%fYu4MYu?UZ~F?BfSK&V-l&vMHEGWj*7IEizK>x_h=
zytd10#NxrLgX;fQUA#Ges2$)@k-Sw8L_Pv8s(ms|Q0cDltGCesJyJjQhby$)&u1sx
zX30plwet(zNnNCyI@Dh4e8vTx1K>aQk|`eQbA#`$P?F>uwFJe>E8iz4qlgC0IsLs%
zBDW@c?U(?YAW)f0nCtMIymlu-*UZ^j%Lc?Z`kKt5YQztCjvv8?kGx)-eV-`9`m;&a
z;*iKcguj*rGNABY;>ZQJj-052aHQO`gO={R1Y4s+%=rECUg><=rp&b)bV8Knv<;xo
zPzD1_*_DmDp52#DOEB4YS_%FR`~9<_6#j~H-4nUfvC~n_no3W4^nC38Emi1dgmc;Q
zd-=^AC+a;te`d}8glr@8{c`G`~P-xqz4Eff^*#5zBM+s)qYWkf1R_oSd*QMh!`JQa>CKth&){rd9XKLc8uSIn^_GEB~{qv-!U;r()e6jFQp
zHb}9*A&m>VMRI=f|FFn@zpyN6HMOKq=DOTp`Pk;B8v_4j+(;?_Qq~Ogr~x4LLlr~yp+8RZ?9o5vPJj2~z9V1j>)&*DM_#mXaw<(#Q#oSH
zM}p=3Wxf6R@2vLOIugJX^U3jjF?;iF{lH}Xe#3wNOzx9Ik$Em%N4#9qOm3(Y1PuTF
z|NYl*(e(r`td~97IqynH1xf|#Sxr72CHMdE$!(I*zreM`#Kb%cg62-sz8JHYxBlyQ
z|NSo+I&-Vy-934Ed3}-w%9#B#R2>s0`*-{{%juY5bQtl70
zeg8EmKF`Z{{13}_KNn2-&eJgc;nTY;LTc
zT3+DVkcJO>ncZ@(TWcmZJ6ji|F*Y3@?0yP*8UnPY&%A?qKWS03y(L#d5bxH1{c5G0
z3q5YAm9({^n&biR76mrcGi6PVQ%4yX8i8nODf|!aPH~;2eqW&_55o_ogqA~lc1lr|
zdq(dQI@Ig1SHx~HajRtn!eFD#T>j?GU~f9f?tKL?S<{)PsIn^|09z~489?y#G{$DV
zHfTg5j6B)Vbh|%x%{y#G=jfVqe|^Q>k>ns2L<&3cdnQRTb&wgK<>aj27hz*#(^LeK
zCXG;RaFbM{1;PGz0$ZG&p;xV2K>K%)4x<1d3xa88B84x=XaXYovg6$S9w?{WCQU4jfNsnwDLJt3
zJ(8Sf79auUz*fvNY+~fRa6tgI{}-*(yJIwYPQ&Ut*CERO>orgV2vj?iS7*}3AI4ge
zQy_YjrYg$~318PqM|T))DWb}7IG|sI^BbDd(0MzYnHEpb0Rr*o2%~r-uDZX|8xZ@
zEG|m5(|(Krp9J|?WW-4%3wx+cdmWF~fu
zW+jKxxn`yOcYZly_~a>#*Dr_r_U-|vMvM;StG&o{JH9lvbPO`})hoMB(|#9U)g3#}
zcrPY$1N75MvQ6|#sU!z(pwGpI{%s#>&T0LX5yGf%C6PlEw<1xE^AjG(i3Smtj&jXw
z6Qvu(DI+Kda^dy|bzKO8T1L@!=0LG2vL0)|n}qL`b+KV7=)OM=a8_Pe$+LdZO8&=D!)V0so6lbLkd?}LkL8okVTKNM>kd3R>3HddWs!Y4<=c_
za_UR$^iS$owhIF!mGdQ_abmNr+hIU9P+$SFrI*N~-ArJCi_hj6G)}{QD7^IOOE+{m
zHVu>hcw7IS$916K9CS`-9(Y;QXHCsui#b+cJn-Sm&ZF>V$
zhn+FcxP!iZ3%|sl)n)sZt5Y+i`%1P!px5#mgKojrazDqxR}O@>Waoy5eiW?HLsn
z6`FO)mthgyV%b9;+=&v={1R|V&xF~?uhtc)peSdlbmM#8{w(6x9ehzcz$Y{Un3cam
zKTBp{27|4wOPNzYgWwGRuAWdod7AGBW4d=`WX)HcvxH6o!(%2Q(C8ya$J`UU0+jQ`
zVjyVF;LE2gZ2GK+Kd>`dMwO(s<&iPB+V+QVb=?1Ks)cl_-MM?zH3H${&*HNA(jMLK
zKv7Bi2tg%oJAG(hJhlueC_-LNDqKz;xEU?h*|}JY15$D?Xa(Fg(Mud~2OWuN;4fcH
z#Hyv4DXRFbWaNg+`ZJsCrKBxDOYQUt(ascu1Vff>-%WMAZU$?!7P2tJ4H$a@4fRL;
zN#NqY-#LZ6aAZ-uH=_9H7Sn2Gmxy>YWzDf#q15uo@@rr_w~i4q<6dRi0xp1crR553
z-W4!)F3ZmM*8)^rFn;{~q9i`UPnKm(3qU0V?Mil;Lv>+^3Sqqf&T8V3TO)(9d&&K$v9PMJ2(V5|srie(0oA$H-&cTDN$D
z1bTXnDv6lI@*beR_bZopk!0uoP*waf&(C9n8;QhEevi6PEJ!Ftrs+aGv05ij^8)Fq4;6I
zdW2cq`9fBV$F3PSbNxp|%9sQSKeSxN-T?e-xtv4MQOSicEm@qj*z
zS>>H4C1sRHZ_1ZBKO-k!9=Z~sZylb~ya7(X-(AxpqV4-GS*WOPxE|eC!wE}FT8P}4
zIB9@2&-XLQFxpD_u8h*lm&KO}P}n40Lqt&0Fb130sb{A>pi8s6$hCXdB8LJ>z^T-?h^Iv~v4?b2b{3T-=7U4B&leYaKMtpi)qGQA7vv{gR6O8V*g)NDIp}s%Dz)Lg
zT#$K7YI1U~_6X6&OS<$`2ot~lm7wM?~EZE5_P>s_BKk^)FsKsgT7uyI}{FaYi>(PwO$}mhL
zfWKxBkMIu9j95{(tBn@RrZPN)3bT@}rS^R&5YZ+Q+Uq*ah-P!L)SAh0`FtQhwwV8G
zIHGq14Ifk642ZKBV=2NC91O+18}7iXO*%zI%HaP#VwRqTK=XX?`olTVFt6qb$u?#B
zw+?mNirKo4%M;U*<>7?X)$YG}{HU?g1*cFW&B0O?4hF}Vix1*^EOIqrN|9unCok?Y
z{Iaf&uns%w6VH#wx!c$MxQ-rR??mbBI3qSPH1em5uroKDn-zGgS}+<$EXBy_lE?P5
zm1o{!7P+CYpwobG-s6z8#HwZ&I`GY<@B
zpEZ8_$L}Sr$Yt`&0ifNaS?VQt6c02zrZc5!AI*yC!t@p+Q%EahUUda0a(ms3$C+{tvfq0mpvxkjt$aryJ;4!ejj09nK1Il6kMTQyri||Nb+XmCCQr~W=O)+}I28C_;_8;&
zz{vI(PI=lB3e}BV(dzVTGgkJT1
ziRxHW6)L3O0PC7R6fmN-$5ge0iuw_ox>{APrP*KpYA^ktpz-5R);Az>MDm@vgqLu<
znN&McalZn$8drG7j*i!lE-6Wr$cSvO+f!g;{ULb!HqoXgJdsO
zquS;eB|)~fA$#LlTTXu;&|r4bvfAn3Zbhm0?LsR
zq8Tn7Z)CgUf|L%1ZGD?5Czuuw*!yJaR`yarMUrXJlb5LDI#0fg!JcvZasK_&T4M`5
z=lRMaMWgJAws@v9djOt6Dl7ksd6ryjZv2L+1|n53voZdsOtKHHu}wn#pJu9T!gWCW
z@4ye)EKr<^6({nQrI0x&Ij?Z+3B)sUHavVYIGB4Xr;cxYu
zPR;PwU*7gizHd-sJ|i}7)hVhrT&2o0TZm)LaUw$8gYH7LMxr#*dbq4OM0p
z==NLe%hGuX0wW?fu$3LnjMmS$Hd=iY>t2V>daTy+mdqVtx)@j8Z`EJ;J?W9ZoPzqIlA9vVobg7L}M!wv@wO87J{uhuQOTPhX&liFptQK
zwd3g6Q@q^anFAdPp%-#pdWqmldYT3OR~A>!StomjfE
zQ+{K871tK6P=@Xi0`*D~6GyvHPQSF2uY)Fy-#}gGE1vU;%kCS~NTz*tPLmTxH}{O+
z9UIt~DSk7%*`>tf`ii7k6yvc??sD$Lsega0rKbbCl9gjS?VLD4M8(P{_EI{AjJ^tmj
zGxE*^yiurXt}BbL~~Zw
zeX$#Lc^#UHU+k8BGMgT)J-WQRLcs4ey)_AF8)^ASk*xV8=|ksOQ3n5V*)SAAk-hJ2
zPLjm^gWv6ML5tB7;?woHa|?(69J&6|R`4{35y}7mTmQfK1f*rotR8gzF--jX!))>{
z8y{b`QowIWk~EGSXlqOoqzPgSUqwJh!O6y~KdG?LyypW*IqwPif4)
zc$wE=BXkecEDDWVKXizug9;L8t>oJyqfU@+rpA1%Aw*4Amz1Cto>K_zh5{Q2lQx;;
zo_mH=jMN6RdU7G2IEyJ;YG={ngIO1Kj`k#<&_IrHKh#^w8*A)yh1@28WkQ61w~K%l
z<^I(t&(egh|eueVnA4kqK8P)0x-FEa1BOx8kNGdLT?
zjU3WIL_d