mirror of
https://github.com/Shu-Ying/main_plugin_farm.git
synced 2026-11-04 03:30:16 +08:00
fixed # 新增本地部署脚本,一键生成对应平台的压缩包内容
This commit is contained in:
@@ -1,7 +1,38 @@
|
||||
# AstrBot 接入预留位置
|
||||
# AstrBot 农场插件
|
||||
|
||||
发布目标:https://github.com/Shu-Ying/astrbot_plugin_farm.git
|
||||
负责 AstrBot 事件、消息、配置和生命周期,不导入 NoneBot 或真寻。
|
||||
未来生成 main.py、metadata.yaml、配置描述、依赖和私有核心。
|
||||
本目录尚不可安装;必须验证资源体积及实际安装。
|
||||
远程仓库本轮不再拉取或推送。
|
||||
基于Star插件接口,命令、交易、数据与图片复用主仓库共享核心,
|
||||
不导入NoneBot或真寻。此目录是接入源码,需构建生成完整插件包。
|
||||
|
||||
## 本地构建与安装
|
||||
|
||||
Windows双击主仓库根目录的`build_astrbot.cmd`,成功后控制台打印ZIP路径。
|
||||
亦可运行`python scripts/build_plugin.py astrbot`。每次输出独立新目录,
|
||||
不会覆盖上次产物,不访问远端,不安装依赖、不启动或修改机器人。
|
||||
版本由主仓库`version.txt`统一生成,不手工修改metadata.yaml版本。
|
||||
|
||||
在AstrBot管理面板的插件管理中选择本地ZIP安装,选择生成的
|
||||
`astrbot_plugin_farm-版本.zip`。插件依赖Pillow>=10.4,不包含AstrBot本体。
|
||||
插件源码及素材遵循所附GPLv3许可证;不捆绑字体。
|
||||
|
||||
## 配置与使用
|
||||
|
||||
管理面板支持`font_path`(中文字体)、`data_dir`、`image_format`及
|
||||
`image_quality`(整数1~95,默认75)。质量修改后下一次请求生效;
|
||||
其他配置修改后重载插件。默认数据目录为
|
||||
`data/plugin_data/astrbot_plugin_farm`,数据库与日志位于该目录,卸载
|
||||
插件前自行备份;更改目录不自动搬迁数据,旧数据不会被覆盖。
|
||||
|
||||
按AstrBot配置的唤醒前缀发送,例如`/开通农场`、`/农场帮助`、
|
||||
`/我的农场`、`/种子商店`、`/我的种子 文字`、`/播种 白萝卜`、`/收获`。
|
||||
仅`/我的农场 高清`支持高清;其他图片统一遵循质量配置。
|
||||
仓库保留分页和文字;偷菜通过`/偷菜 @目标`,不接受手输QQ号。
|
||||
不提供共享核心尚未实现的签到、开垦、改名、土地升级及外部货币兑换。
|
||||
|
||||
存档按AstrBot部署、平台实例、机器人及用户隔离,不自动合并NoneBot或
|
||||
真寻账号。缺少稳定消息ID时拒绝写操作。字体不可用时尽可能退回文字。
|
||||
本地模拟和ZIP校验不等于真实平台发图成功,需要部署者实际验证。
|
||||
|
||||
官方接口参考:
|
||||
[插件入口](https://docs.astrbot.app/en/dev/star/guides/simple.html)、
|
||||
[插件配置](https://docs.astrbot.app/en/dev/star/guides/plugin-config.html)。
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"font_path": {
|
||||
"description": "中文字体路径",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"hint": "部署环境可访问的中文字体文件;留空时尝试系统字体,找不到则退回文字。"
|
||||
},
|
||||
"data_dir": {
|
||||
"description": "农场数据目录",
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"hint": "留空使用data/plugin_data/astrbot_plugin_farm。修改后重载插件;不会自动搬迁数据。"
|
||||
},
|
||||
"image_format": {
|
||||
"description": "图片格式",
|
||||
"type": "string",
|
||||
"default": "AUTO",
|
||||
"options": ["AUTO", "JPEG", "PNG"],
|
||||
"hint": "AUTO使用压缩JPEG;修改后重载插件。"
|
||||
},
|
||||
"image_quality": {
|
||||
"description": "农场图片质量",
|
||||
"type": "int",
|
||||
"default": 75,
|
||||
"hint": "整数1~95,下一次图片请求生效;仅我的农场允许高清,质量不低于85。PNG不受影响。"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
"""AstrBot薄接入;业务、交易及绘图复用共享运行时。"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from astrbot.api import AstrBotConfig
|
||||
from astrbot.api.event import AstrMessageEvent, filter
|
||||
from astrbot.api.message_components import At, Image
|
||||
from astrbot.api.star import Context, Star, StarTools
|
||||
|
||||
from .runtime import COMMANDS, Runtime
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FarmPlugin(Star):
|
||||
def __init__(self, context: Context, config: AstrBotConfig):
|
||||
super().__init__(context)
|
||||
self.config = config
|
||||
self._lifecycle_lock = asyncio.Lock()
|
||||
self._closed = False
|
||||
configured = config.get('data_dir', '')
|
||||
data_dir = (Path(configured).expanduser() if configured else
|
||||
StarTools.get_data_dir('astrbot_plugin_farm'))
|
||||
image_format = str(config.get('image_format', 'AUTO')).upper()
|
||||
if image_format not in ('AUTO', 'JPEG', 'PNG'):
|
||||
raise ValueError('image_format must be AUTO, JPEG or PNG')
|
||||
self.runtime = Runtime(
|
||||
data_dir, config.get('font_path') or None, 'astrbot',
|
||||
image_format, font_setting='font_path',
|
||||
image_quality=config.get('image_quality', 75),
|
||||
image_quality_provider=lambda: self.config.get('image_quality', 75),
|
||||
)
|
||||
|
||||
async def initialize(self):
|
||||
async with self._lifecycle_lock:
|
||||
if not self._closed:
|
||||
await self.runtime.start()
|
||||
|
||||
@filter.command('农场帮助', alias=set(COMMANDS) - {'农场帮助'})
|
||||
async def farm(self, event: AstrMessageEvent):
|
||||
"""农场帮助、注册、种植、仓库、交易及图片查询。"""
|
||||
if self._closed:
|
||||
yield event.plain_result('农场插件已关闭。')
|
||||
return
|
||||
await self.initialize()
|
||||
parts = event.get_message_str().strip().split(maxsplit=1)
|
||||
if not parts or parts[0] not in COMMANDS:
|
||||
return
|
||||
action = COMMANDS[parts[0]]
|
||||
arguments = parts[1] if len(parts) == 2 else ''
|
||||
target = None
|
||||
if action == 'steal':
|
||||
mentions = [str(segment.qq) for segment in event.get_messages()
|
||||
if isinstance(segment, At)
|
||||
and str(segment.qq) != str(event.get_self_id())]
|
||||
if len(mentions) != 1 or mentions[0] in ('', 'all', '0'):
|
||||
yield event.plain_result('请@一个具体的偷菜目标。')
|
||||
return
|
||||
target = mentions[0]
|
||||
platform_id = event.get_platform_id()
|
||||
if not platform_id or not event.get_sender_id():
|
||||
yield event.plain_result('无法取得稳定的平台或用户标识,未执行。')
|
||||
return
|
||||
bot_id = json.dumps([str(platform_id), str(event.get_self_id())])
|
||||
response = await self.runtime.execute(
|
||||
action, arguments, bot_id=bot_id,
|
||||
platform=event.get_platform_name(), user_id=event.get_sender_id(),
|
||||
message_id=getattr(event.message_obj, 'message_id', None),
|
||||
display_name=event.get_sender_name() or '农场玩家',
|
||||
target_user_id=target,
|
||||
)
|
||||
if isinstance(response, bytes):
|
||||
yield event.chain_result([Image.fromBytes(response)])
|
||||
else:
|
||||
yield event.plain_result(response)
|
||||
|
||||
async def terminate(self):
|
||||
async with self._lifecycle_lock:
|
||||
if self._closed:
|
||||
return
|
||||
self._closed = True
|
||||
await self.runtime.close()
|
||||
logger.info('farm astrbot plugin terminated')
|
||||
@@ -0,0 +1 @@
|
||||
Pillow>=10.4
|
||||
@@ -21,6 +21,7 @@ runtime = Runtime(
|
||||
data_directory(store.get_plugin_data_dir(), config.farm_data_dir),
|
||||
config.farm_font, config.farm_deployment, config.farm_image_format,
|
||||
font_setting="FARM_FONT",
|
||||
image_quality=config.farm_image_quality,
|
||||
)
|
||||
driver.on_startup(runtime.start)
|
||||
driver.on_shutdown(runtime.close)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from pathlib import Path
|
||||
from typing import Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, conint
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
@@ -21,3 +21,7 @@ class Config(BaseModel):
|
||||
farm_image_format: Literal["AUTO", "JPEG", "PNG"] = Field(
|
||||
"AUTO", description="图片格式,AUTO 优先使用压缩 JPEG",
|
||||
)
|
||||
farm_image_quality: conint(strict=True, ge=1, le=95) = Field(
|
||||
75,
|
||||
description="全部农场图片编码质量;仅我的农场高清时至少85",
|
||||
)
|
||||
|
||||
@@ -7,3 +7,15 @@
|
||||
生成时修正为插件私有相对导入,本目录本身不是完整安装包。
|
||||
使用 scripts/build_zhenxun.py 生成ZIP,详见 docs/11-zhenxun-preview.md。
|
||||
本轮没有拉取、推送或操作真实机器人。
|
||||
|
||||
## 图片质量
|
||||
|
||||
真寻网页插件配置新增“农场图片质量”,整数1~95,默认75。
|
||||
它统一控制我的农场、地块编号、农场详述、种子商店、我的种子及
|
||||
我的作物图片的编码质量,修改后下一次图片请求生效。值越高通常
|
||||
越清晰、文件越大;不会改变默认图片尺寸,PNG无损格式不受影响。
|
||||
无效配置明确提示错误,不会自动截断或转换为合法值。
|
||||
|
||||
只有“我的农场 高清”接受高清,宽1536,质量取配置值与85的较大值。
|
||||
其他命令不接受高清。仓库继续支持“我的种子 2 文字”和
|
||||
“我的作物 2 文字”等分页文字查询。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""真寻农场独立预览插件入口。"""
|
||||
|
||||
from nonebot.plugin import PluginMetadata
|
||||
from zhenxun.configs.utils import Command, PluginExtraData
|
||||
from zhenxun.configs.utils import Command, PluginExtraData, RegisterConfig
|
||||
|
||||
from ._version import __version__
|
||||
from .runtime import COMMANDS, HELP
|
||||
@@ -14,6 +14,12 @@ __plugin_meta__ = PluginMetadata(
|
||||
extra=PluginExtraData(
|
||||
author="Shu-Ying", version=__version__, menu_type="群内小游戏",
|
||||
commands=[Command(command=name) for name in COMMANDS],
|
||||
configs=[RegisterConfig(
|
||||
key="农场图片质量", value=75, default_value=75, type=int,
|
||||
help="全部农场图片的编码质量,整数1~95,默认75;"
|
||||
"数值越高通常越清晰、文件越大。仅我的农场支持高清,"
|
||||
"高清质量不低于85。PNG为无损格式,不受此值影响。",
|
||||
)],
|
||||
).to_dict(),
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,15 @@ from .runtime import COMMANDS, Runtime
|
||||
from .data_paths import data_directory
|
||||
from .config_migration import migrate_config
|
||||
|
||||
|
||||
def image_quality():
|
||||
from zhenxun.configs.config import Config
|
||||
|
||||
return Config.get(__package__.split('.')[-1]).get(
|
||||
'农场图片质量', 75, build_model=False,
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
driver = get_driver()
|
||||
data_dir = data_directory(
|
||||
@@ -29,6 +38,7 @@ runtime = Runtime(
|
||||
getattr(driver.config, "farm_preview_deployment", "zhenxun-preview"),
|
||||
str(getattr(driver.config, "farm_preview_image_format", "AUTO")).upper(),
|
||||
font_dir=FONT_PATH,
|
||||
image_quality_provider=image_quality,
|
||||
)
|
||||
async def start_runtime():
|
||||
from zhenxun.configs.config import Config
|
||||
|
||||
@@ -34,15 +34,17 @@ COMMANDS = {
|
||||
}
|
||||
HELP = (
|
||||
"开通农场 [名称]|我的农场 [高清]|农场详述 [页码]\n"
|
||||
"地块编号 [高清]|查看当前土地编号及开垦位置\n"
|
||||
"地块编号|查看当前土地编号及开垦位置\n"
|
||||
"偷菜 @目标|每日最多5次成功偷取\n"
|
||||
"我的种子/我的作物 [页码] [高清/文字],默认标准压缩图。\n"
|
||||
"我的种子/我的作物 [页码] [文字],默认图片。\n"
|
||||
"全部图片使用农场图片质量配置;仅我的农场支持高清。\n"
|
||||
"种子商店 [页码]|购买种子 作物 数量|播种 作物 [数量]\n"
|
||||
"收获|铲除|农场除虫|我的种子 [页码]|我的作物 [页码]\n"
|
||||
"作物加锁 作物|作物解锁 作物|出售作物 [作物] [数量]\n"
|
||||
"作物可填写名称或 #编号;同名作物请使用商店或仓库中的编号。\n"
|
||||
"每成功播种1块地获得2经验;收获经验按作物结算。\n"
|
||||
"每铲除1块枯萎地块获得2经验;每清除1只虫获得1经验。\n"
|
||||
"新播种及下一季每半个基础生长周期生虫一次,间隔至少2小时。\n"
|
||||
"每只虫使生长速度降低5%;成熟时每只虫使本季减产5%。\n"
|
||||
"成熟后除虫不能恢复本季减产;生长期间可发送:农场除虫。\n"
|
||||
"我的农场按北京时间自动切换春夏秋冬背景;高清使用相同季节。\n"
|
||||
@@ -88,7 +90,11 @@ def operation_id(bot_id, platform, user_id, message_id, action):
|
||||
class Runtime:
|
||||
def __init__(self, data_dir, font_path=None, deployment="zhenxun-preview",
|
||||
image_format="AUTO", *, font_dir=None,
|
||||
font_setting="FARM_PREVIEW_FONT"):
|
||||
font_setting="FARM_PREVIEW_FONT", image_quality=75,
|
||||
image_quality_provider=None):
|
||||
RenderOptions(quality=image_quality)
|
||||
self.image_quality = image_quality
|
||||
self.image_quality_provider = image_quality_provider
|
||||
self.data_dir = Path(data_dir).resolve()
|
||||
self.font_path = font_path
|
||||
self.font_setting = font_setting
|
||||
@@ -198,6 +204,21 @@ class Runtime:
|
||||
)
|
||||
if action == "help":
|
||||
return HELP
|
||||
image_actions = ('farm', 'plot_ids', 'details', 'shop',
|
||||
'seeds', 'crops')
|
||||
if action in image_actions and action != 'farm' and '高清' in args:
|
||||
return '仅我的农场支持高清,请移除高清参数后重试。'
|
||||
quality = self.image_quality
|
||||
text_mode = (action in ('seeds', 'crops') and args
|
||||
and args[-1] == '文字')
|
||||
if action in image_actions and self.rendering and not text_mode:
|
||||
try:
|
||||
if self.image_quality_provider is not None:
|
||||
quality = self.image_quality_provider()
|
||||
RenderOptions(quality=quality)
|
||||
except (TypeError, ValueError):
|
||||
logger.warning('farm image quality config invalid')
|
||||
return '农场图片质量配置无效,请设置为1~95的整数。'
|
||||
if action == "shop":
|
||||
page = int(args[0]) if args else 1
|
||||
if len(args) > 1:
|
||||
@@ -216,6 +237,7 @@ class Runtime:
|
||||
return await self.rendering.seed_shop(
|
||||
chunk, account, page, pages,
|
||||
image_format=self.image_format,
|
||||
quality=quality,
|
||||
)
|
||||
return f'种子商店 第{page}/{pages}页\n' + '\n'.join(
|
||||
f'{p.name}(#{p.plant_id}):'
|
||||
@@ -243,10 +265,11 @@ class Runtime:
|
||||
return await self.rendering.farm_details(
|
||||
who, snapshot, now, page, season_for(now),
|
||||
image_format=self.image_format, avatar=avatar,
|
||||
quality=quality,
|
||||
)
|
||||
if action in ('crops', 'seeds'):
|
||||
title = '我的作物' if action == 'crops' else '我的种子'
|
||||
mode = args.pop() if args and args[-1] in ('高清', '文字') else ''
|
||||
mode = args.pop() if args and args[-1] == '文字' else ''
|
||||
if len(args) > 1:
|
||||
raise ValueError()
|
||||
try:
|
||||
@@ -263,7 +286,7 @@ class Runtime:
|
||||
return warehouse_text(rows, snapshot, action, page, pages)
|
||||
return await self.rendering.warehouse(
|
||||
rows, snapshot, action, page, pages, self.image_format,
|
||||
mode == '高清',
|
||||
quality=quality,
|
||||
)
|
||||
if action == "inventory":
|
||||
if args:
|
||||
@@ -280,9 +303,10 @@ class Runtime:
|
||||
theme = season_for(now)
|
||||
logger.info("farm preview seasonal theme=%s", theme)
|
||||
options = (
|
||||
RenderOptions.high(format=self.image_format, theme=theme)
|
||||
RenderOptions(width=1536, quality=max(quality, 85),
|
||||
format=self.image_format, theme=theme)
|
||||
if args else RenderOptions(format=self.image_format,
|
||||
theme=theme)
|
||||
theme=theme, quality=quality)
|
||||
)
|
||||
if action == "plot_ids":
|
||||
options = replace(options, show_plot_ids=True)
|
||||
|
||||
Reference in New Issue
Block a user