fixed # 修复工作流

This commit is contained in:
2026-10-23 19:19:01 +08:00
parent 685cff9293
commit dc17f7f65c
10 changed files with 135 additions and 2 deletions
+2
View File
@@ -47,6 +47,8 @@ jobs:
- uses: actions/setup-python@v7
with:
python-version: '3.10'
- name: Verify external asset manifest
run: python scripts/check_asset_manifest.py
- run: python scripts/build_astrbot.py --output dist/astrbot-preview
- name: Verify AstrBot package
shell: bash
+2
View File
@@ -47,6 +47,8 @@ jobs:
- uses: actions/setup-python@v7
with:
python-version: '3.10'
- name: Verify external asset manifest
run: python scripts/check_asset_manifest.py
- run: python scripts/build_nonebot.py --output dist/nonebot-preview
- run: >-
python scripts/verify_nonebot.py
+2
View File
@@ -57,6 +57,8 @@ jobs:
- uses: actions/setup-python@v7
with:
python-version: '3.12'
- name: Verify external asset manifest
run: python scripts/check_asset_manifest.py
- name: Build selected target only
run: >-
python scripts/build_zhenxun.py
+5
View File
@@ -32,6 +32,11 @@
## 待确认与已知限制
2026-10-23后续进展:已将三端发行包的assets.zip移至farm_service仓库
`assets/`,本地新包`dist/astrbot-external-assets-20261023/`大小4,599,804字节
(4.39 MiB),通过`--marketplace`大小及完整性检查;以下24.08 MiB为旧产物记录。
配置及服务说明以`farm_service/docs/assets.md`为准;尚未推送或验证远端素材URL。
1. **商店容量限制尚未满足**:官方普通投稿限制16MB,现有素材包
`src/farm_render/data/assets.zip`本身已21,264,561字节;完整ZIP24.08 MiB。
`--marketplace`已实际执行并拒绝。不能把本地构建成功当作市场发布就绪。
+12
View File
@@ -40,11 +40,23 @@
| `data_dir` | 空 | 留空使用 `data/plugin_data/astrbot_plugin_farm`;更改不会自动搬迁存档 |
| `image_format` | `AUTO` | `AUTO` 为 JPEG;也可选 `JPEG` 或 `PNG` |
| `image_quality` | `75` | 整数1~95,下一次图片请求生效;PNG不受影响 |
| `resource_source`(资源获取地址) | `Github` | `Github`、`Gitea`、`farm_service`,重载生效 |
| `farm_service_url`(farm_service 地址) | 空 | 独立HTTPS基础地址,供资源及后续活动服务共用;选farm_service下载时必填 |
除图片质量外,配置变更后请重载插件。高清农场的图片质量不低于85。
依赖 Pillow 和 aiohttp,由插件依赖声明安装;插件不捆绑中文字体。
当前重点适配 QQ/OneBot(`aiocqhttp`),其他平台尚未完成真实环境验证。
插件不携带大型`assets.zip`。启动时校验数据目录下缓存素材的SHA256,
缺失或不匹配时从所选源下载并校验后原子替换;GitHub/Gitea均从
`farm_service`仓库根目录`assets/`获取。默认Github,不自动跨源切换;
下载失败保留文字功能,调整来源后重载重试。两项配置始终同时存在,
活动功能尚未实现。已有正确缓存时不联网。
离线部署可将服务仓库的`assets/assets.zip`放到农场数据目录
`assets/<SHA256>/assets.zip`;SHA见插件的`_vendor/farm_render/data/asset-manifest.json`。
请先提交/发布服务仓库的素材,再发布依赖它的插件;不要把素材ZIP放回插件仓库。
## 玩法与旧账号兼容
- 新账号初始6块土地。`/开垦 条件` 查询下一块地的等级和费用,
+9
View File
@@ -23,6 +23,8 @@ FARM_FONT=C:/Windows/Fonts/msyh.ttc
FARM_IMAGE_FORMAT=AUTO
FARM_IMAGE_QUALITY=75
FARM_DEPLOYMENT=nonebot
FARM_RESOURCE_SOURCE=Github
FARM_SERVICE_URL=
```
`FARM_DATA_DIR` 可显式指定数据目录,默认沿用旧版插件 localstore 下的
@@ -30,6 +32,13 @@ FARM_DEPLOYMENT=nonebot
机器人和平台迁入 `farm.sqlite3`;遇到账号或平台冲突会拒绝覆盖。
日志写入该目录的 `farm-preview.log`。字体不可用时提供文字响应。
`FARM_RESOURCE_SOURCE`(资源获取地址)支持`Github`、`Gitea`、`farm_service`,
默认`Github`。`FARM_SERVICE_URL`(farm_service 地址)始终独立存在,
供资源及后续活动功能共用;选择farm_service下载时须填写HTTPS基础地址。
两项修改后重载。插件不包含素材ZIP:每次启动校验本地SHA256,缺失或不匹配
才从`farm_service`仓库根目录`assets/assets.zip`下载,校验成功后原子替换。
失败时保留文字功能,不自动跨源切换,重载后重试。活动功能尚未实施。
`FARM_IMAGE_QUALITY` 为整数1~95,默认75,统一控制全部农场图片
的编码质量;越高通常越清晰、文件越大,PNG无损格式不受此值影响。
启动时校验配置。只有“我的农场 高清”允许高清,使用1536宽,质量
+7
View File
@@ -3,6 +3,13 @@
# 真寻农场(zhenxun_plugin_farm)
<img src="./logo.png" width="400" alt="真寻农场 Logo">
> 资源配置:「资源获取地址」默认Github,可选Github、Gitea、farm_service;
> 「farm_service 地址」始终独立存在,供资源及后续活动服务共用。
> 选择farm_service下载时填写HTTPS基础地址,两项修改后重载。
> 插件不携带assets.zip,启动校验缓存SHA256,缺失或不匹配时从所选源获取;
> Github/Gitea均使用farm_service仓库根目录assets/。失败保留文字功能,重载重试。
> 活动服务尚未实现,请先发布服务仓库素材再发布插件。
<p align="center">
<a href="./LICENSE">
<img src="https://img.shields.io/badge/license-GPL3.0-FE7D37" alt="license">
+1 -1
View File
@@ -37,7 +37,7 @@ def checked_url(url):
parsed = urlsplit(url)
if (parsed.scheme != 'https' or not parsed.hostname or parsed.username
or parsed.password or parsed.query or parsed.fragment):
raise ValueError('Resource URL requires public HTTPS without credentials')
raise ValueError('Resource URL requires HTTPS without credentials')
return url
+92
View File
@@ -0,0 +1,92 @@
from concurrent.futures import ThreadPoolExecutor
from io import BytesIO
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from farm_render import bundle
ROOT = Path(__file__).resolve().parents[1]
ZIP = ROOT / 'src/farm_render/data/assets.zip'
class BundleTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.expected = bundle.expected_manifest()
def download(self, url, destination, limit):
content = ZIP.read_bytes()
self.assertEqual(limit, len(content))
destination.write(content)
def test_download_deduplicates_and_cache_hash_checked_each_start(self):
with patch.object(bundle, '_copy_remote',
side_effect=self.download) as fetch:
with ThreadPoolExecutor(4) as pool:
results = list(pool.map(
lambda _: bundle.ensure_assets(self.root), range(4),
))
self.assertEqual(len(set(results)), 1)
self.assertEqual(fetch.call_count, 1)
path = results[0]
content = bytearray(path.read_bytes())
content[-1] ^= 1
path.write_bytes(content)
bundle.ensure_assets(self.root)
self.assertEqual(fetch.call_count, 2)
self.assertEqual(path.read_bytes(), ZIP.read_bytes())
def test_gitea_and_service_use_service_repository_and_configured_base(self):
with patch.object(bundle, '_copy_remote',
side_effect=self.download) as fetch:
bundle.ensure_assets(self.root / 'gitea', 'Gitea')
self.assertEqual(fetch.call_args.args[0],
'https://gitea.diuse.work/Diuse/farm_service/'
'raw/branch/main/assets/assets.zip')
bundle.ensure_assets(self.root / 'service', 'farm_service',
'https://farm.example/prefix/')
self.assertEqual(fetch.call_args.args[0],
'https://farm.example/prefix/v1/assets/'
+ self.expected['sha256'] + '/assets.zip')
def test_failed_download_leaves_no_partial_and_wrong_hash_is_rejected(self):
def corrupt(url, destination, limit):
data = bytearray(ZIP.read_bytes())
data[-1] ^= 1
destination.write(data)
with patch.object(bundle, '_copy_remote', side_effect=corrupt):
with self.assertRaisesRegex(ValueError, 'SHA256'):
bundle.ensure_assets(self.root)
self.assertFalse(list(self.root.rglob('assets.zip')))
self.assertFalse(list(self.root.rglob('*.download')))
with patch.object(bundle, '_copy_remote', side_effect=OSError()):
with self.assertRaises(OSError):
bundle.ensure_assets(self.root)
self.assertFalse(list(self.root.rglob('*.download')))
def test_invalid_source_and_service_configuration(self):
for source, url in [('unknown', ''), ('farm_service', ''),
('farm_service', 'http://farm.example'),
('Github', 'https://user:pass@farm.example'),
('Gitea', 'https://farm.example/?token=secret')]:
with self.subTest(source=source, url=url):
with self.assertRaises(ValueError):
bundle.ensure_assets(self.root, source, url)
def test_download_enforces_response_limit_and_https_redirect(self):
class Response(BytesIO):
headers = {'Content-Length': '20'}
def geturl(self):
return 'https://farm.example/assets.zip'
with patch.object(bundle, 'urlopen', return_value=Response(b'x'*20)):
with self.assertRaisesRegex(ValueError, 'limit'):
bundle._copy_remote('https://farm.example/a', BytesIO(), 10)
response = Response(b'')
response.geturl = lambda: 'http://farm.example/a'
with patch.object(bundle, 'urlopen', return_value=response):
with self.assertRaises(ValueError):
bundle._copy_remote('https://farm.example/a', BytesIO(), 10)
+3 -1
View File
@@ -72,7 +72,9 @@ class ZhenxunPackageTests(unittest.TestCase):
self.assertIn("dirty", manifest["source"])
with ZipFile(self.bundle) as archive:
paths = archive.namelist()
self.assertTrue(any(p.endswith("assets.zip") for p in paths))
self.assertFalse(any(p.endswith("assets.zip") for p in paths))
self.assertTrue(any(p.endswith('asset-manifest.json')
for p in paths))
self.assertFalse(any("astrbot" in p or "legacy/" in p
for p in paths))
source = archive.read(next(