mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-10-28 05:00:01 +08:00
fixed # 修复工作流
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`已实际执行并拒绝。不能把本地构建成功当作市场发布就绪。
|
||||
|
||||
@@ -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块土地。`/开垦 条件` 查询下一块地的等级和费用,
|
||||
|
||||
@@ -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宽,质量
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user