add # 新增字体

This commit is contained in:
2026-10-26 11:35:04 +08:00
parent f9162b618e
commit 72e54c3573
14 changed files with 684 additions and 3 deletions
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "node \"D:/nonebot/farm_service/.agents/skills/comet/scripts/comet-hook-router.mjs\" --platform \"codex\" --project-root \"D:/nonebot/farm_service\""
}
]
}
]
}
}
+38
View File
@@ -0,0 +1,38 @@
# Comet 使用的配置格式版本,请勿修改此值。
schema: comet.project.v1
# `/comet` 默认进入的工作流;该值也必须出现在 workflows 中。
default_workflow: native
# 此项目启用的工作流,可填写 native、classic 或同时启用两者。
workflows:
- native
# 是否启用只读的环境感知恢复探针,同时作用于 Native 和 Classic;设为 false 可关闭自动工作流恢复。
# ambient_resume: true | false
ambient_resume: true
# 当前项目的个人记忆自动学习与注入策略。
memory:
# 是否允许当前项目通过工作流事件自动沉淀新的个人记忆。
# 可选值:true | false
learning: true
# 是否允许当前项目把个人记忆自动注入 Agent 上下文。
# 可选值:true | false
retrieval: true
# 普通 Comet 任务使用的项目知识检索 Provider。
knowledge:
# 项目知识 Provider。
# 可选值:local | remote
provider: local
# Native 工作流配置,不会改变 Classic 的状态或行为。
native:
# Native 规格和 change 的存放根目录;运行时数据始终位于 .comet。
artifact_root: docs
# Native 工作流文档使用的产物语言。
# 可选值:en | zh-CN
language: zh-CN
# Native 提问澄清问题的方式:batch 每轮一次提出当前所有可回答的问题(默认),sequential 每轮只问一个。
# 可选值:batch | sequential
clarification_mode: batch
# Native 归档检查成功后自动归档,或等待用户明确确认。
# 可选值:automatic | required
archive_confirmation: automatic
# 同一个已确认验收目标最多允许的 Verify 失败次数;达到上限后停止完成循环。
max_verify_failures: 5
+2
View File
@@ -8,3 +8,5 @@ RUST_LOG=farm_service=info
# Optional: leave unset to disable asset distribution. Sibling assets.zip required.
# The release files live in this repository's root assets/ directory.
# FARM_ASSET_MANIFEST=./assets/manifest.json
# Optional: independent Xiaolai font archive, including the complete OFL license.
# FARM_FONT_MANIFEST=./fonts/manifest.json
+5
View File
@@ -36,3 +36,8 @@ profraw
.DS_Store
Thumbs.db
Desktop.ini
# >>> Comet managed project state >>>
!/.comet/
/.comet/*
!/.comet/config.yaml
# <<< Comet managed project state <<<
+14
View File
@@ -9,6 +9,8 @@
- `GET /health`:公开就绪检查,实际检查数据库。
- `GET /v1/assets/manifest.json`:公开素材清单,含版本、SHA256、大小及三源绝对URL。
- `GET /v1/assets/{sha256}/assets.zip`:下载启动时校验过的素材快照,支持ETag。
- `GET /v1/fonts/manifest.json`:独立字体资源清单。
- `GET /v1/fonts/{sha256}/assets.zip`:原版小赖字体与完整 OFL 许可,支持 ETag。
- [资源分发接口与三端接入约定](docs/assets.md)。
- 素材发布文件位于根目录 `assets/manifest.json` 和 `assets/assets.zip`;
ZIP 仅由本服务仓库发布,三个插件分发包不包含 ZIP。
@@ -65,6 +67,7 @@ cargo run --locked
| `FARM_LOG_DIR` | `./logs`,保存每日滚动日志 |
| `RUST_LOG` | `farm_service=info` |
| `FARM_ASSET_MANIFEST` | 可选,建议 `./assets/manifest.json`;同目录须有assets.zip;未设置时资源接口返回503 |
| `FARM_FONT_MANIFEST` | 可选,设为 `./fonts/manifest.json`;同目录须有 assets.zip;未设置时字体接口返回503 |
相对目录以进程工作目录为基准;生产建议使用固定绝对路径。
`.env.example` 只是模板,程序**不会自动加载 .env**。
@@ -133,6 +136,13 @@ cargo clippy --locked --all-targets -- -D warnings
## 开源协议
字体资源独立于服务代码许可。`fonts/Xiaolai-Regular.ttf` 为未经修改的
小赖字体 v3.126,采用 SIL OFL 1.1;完整版权与许可见
[OFL-Xiaolai.txt](fonts/OFL-Xiaolai.txt)。`fonts/assets.zip` 同时包含字体
和许可,不得单独出售字体文件。字体包通过 GitHub/Gitea 的本仓库
`fonts/assets.zip` raw 地址或上述服务接口分发,插件缓存时校验摘要。
资源迁移仅准备本地文件,须发布此仓库或部署服务后远端才能提供新版资源。
Copyright (c) 2026 Shu-Ying。
本项目采用 GNU Affero General Public License v3.0,
SPDX 标识为 `AGPL-3.0-only`,完整条款见 [LICENSE](LICENSE)。
@@ -142,3 +152,7 @@ SPDX 标识为 `AGPL-3.0-only`,完整条款见 [LICENSE](LICENSE)。
显著提供免费取得该修改版本对应源码的机会。
源码地址应指向实际运行版本,不能仅指向不包含这些修改的上游仓库。
协议针对程序及其对应源码,不要求公开 API 令牌、用户资料或数据库内容。
## 致谢
- [小赖字体](https://github.com/lxgw/kose-font) *lxgw 提供的开源中文手写字体,采用 SIL OFL 1.1,分发时附带完整字体许可*
+94
View File
@@ -0,0 +1,94 @@
Copyright 2020-2024 LXGW (https://github.com/lxgw/kose-font)
Copyright 2014 Nozomi Seto (https://ja.osdn.net/projects/setofont/)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
BIN
View File
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
{
"schema": 1,
"version": "xiaolai-v3.126",
"sha256": "c2d1c1a5247e05b766d12a8d8410594d78b1a8b86031624a05b76ee0515c1335",
"size_bytes": 14741741,
"sources": {
"github": "https://raw.githubusercontent.com/Shu-Ying/farm_service/main/fonts/assets.zip",
"gitea": "https://gitea.diuse.work/Diuse/farm_service/raw/branch/main/fonts/assets.zip",
"farm_service": "https://farm.example/v1/fonts/c2d1c1a5247e05b766d12a8d8410594d78b1a8b86031624a05b76ee0515c1335/assets.zip"
}
}
+23 -2
View File
@@ -51,6 +51,10 @@ pub struct Manifest {
impl Manifest {
pub fn validate(&self) -> Result<()> {
self.validate_for("assets")
}
fn validate_for(&self, namespace: &str) -> Result<()> {
ensure!(self.schema == 1, "unsupported asset manifest schema");
ensure!(
!self.version.is_empty()
@@ -84,7 +88,7 @@ impl Manifest {
ensure!(
service_url
.path()
.ends_with(&format!("/v1/assets/{}/assets.zip", self.sha256)),
.ends_with(&format!("/v1/{namespace}/{}/assets.zip", self.sha256)),
"farm_service URL must include the bundle SHA256"
);
Ok(())
@@ -128,10 +132,19 @@ pub struct AssetBundle {
impl AssetBundle {
/// Load manifest.json and its sibling assets.zip, verifying both at startup.
pub fn load(manifest_path: &Path) -> Result<Self> {
Self::load_for(manifest_path, "assets")
}
/// Load an independent font archive with the same integrity contract.
pub fn load_font(manifest_path: &Path) -> Result<Self> {
Self::load_for(manifest_path, "fonts")
}
fn load_for(manifest_path: &Path, namespace: &str) -> Result<Self> {
let manifest: Manifest =
serde_json::from_slice(&read_bounded(manifest_path, MAX_MANIFEST_BYTES)?)
.context("invalid asset manifest JSON")?;
manifest.validate()?;
manifest.validate_for(namespace)?;
let content = read_bounded(
&manifest_path.with_file_name("assets.zip"),
MAX_BUNDLE_BYTES,
@@ -169,6 +182,14 @@ pub fn router(bundle: Option<AssetBundle>) -> Router {
.with_state(bundle)
}
/// Font distribution is independent of the picture bundle and profile APIs.
pub fn font_router(bundle: Option<AssetBundle>) -> Router {
Router::new()
.route("/v1/fonts/manifest.json", get(get_manifest))
.route("/v1/fonts/{sha256}/assets.zip", get(download))
.with_state(bundle)
}
async fn get_manifest(State(bundle): State<Option<AssetBundle>>) -> Response {
match bundle {
Some(bundle) => {
+3
View File
@@ -14,6 +14,8 @@ pub struct Config {
pub log_dir: PathBuf,
/// Optional verified asset manifest; sibling assets.zip is loaded once.
pub asset_manifest: Option<PathBuf>,
/// Optional font archive manifest; includes the original OFL license.
pub font_manifest: Option<PathBuf>,
}
impl Config {
@@ -33,6 +35,7 @@ impl Config {
.context("invalid FARM_BIND address")?,
api_token,
asset_manifest: env::var_os("FARM_ASSET_MANIFEST").map(PathBuf::from),
font_manifest: env::var_os("FARM_FONT_MANIFEST").map(PathBuf::from),
data_dir: env::var_os("FARM_DATA_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("data")),
+9 -1
View File
@@ -24,13 +24,21 @@ async fn main() -> Result<()> {
.map(assets::AssetBundle::load)
.transpose()
.context("asset startup verification failed")?;
let fonts = config
.font_manifest
.as_deref()
.map(assets::AssetBundle::load_font)
.transpose()
.context("font startup verification failed")?;
std::fs::create_dir_all(&config.data_dir)?;
let pool = db::open_database(&config.data_dir.join("farm_service.sqlite3"))
.await
.context("database startup failed")?;
let listener = tokio::net::TcpListener::bind(config.bind).await?;
tracing::info!(address = %listener.local_addr()?, "service_ready");
let app = api::router(pool.clone(), config.api_token).merge(assets::router(bundle));
let app = api::router(pool.clone(), config.api_token)
.merge(assets::router(bundle))
.merge(assets::font_router(fonts));
let result = axum::serve(listener, app)
.with_graceful_shutdown(shutdown())
.await;
+34
View File
@@ -39,6 +39,40 @@ fn save(directory: &tempfile::TempDir, manifest: &Manifest) {
.unwrap();
}
#[tokio::test]
async fn independent_font_archive_is_verified_and_public() {
let (directory, mut manifest, content) = fixture();
manifest.sources.farm_service = format!(
"https://farm.example/v1/fonts/{}/assets.zip",
manifest.sha256
);
save(&directory, &manifest);
let path = directory.path().join("manifest.json");
assert!(AssetBundle::load(&path).is_err());
let bundle = AssetBundle::load_font(&path).unwrap();
let app = assets::font_router(Some(bundle));
let uri = format!("/v1/fonts/{}/assets.zip", manifest.sha256);
let response = app
.clone()
.oneshot(Request::builder().uri(uri).body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(to_bytes(response.into_body(), 4096).await.unwrap(), content);
let response = app
.oneshot(
Request::builder()
.uri("/v1/fonts/unknown/assets.zip")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
std::fs::write(directory.path().join("assets.zip"), b"bad").unwrap();
assert!(AssetBundle::load_font(&path).is_err());
}
#[tokio::test]
async fn public_manifest_download_cache_and_snapshot() {
let (directory, manifest, content) = fixture();