新增在线查漏补缺功能

This commit is contained in:
Art_Sakura 2025-06-29 01:45:24 +08:00
parent ca6a414ba4
commit c6f24bc3c2
72 changed files with 807 additions and 208 deletions

View File

@ -77,31 +77,30 @@
| 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% | | 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% |
| 更改农场名 [新的农场名] | 改名 | 农场名称无法存储特殊字符 | | 更改农场名 [新的农场名] | 改名 | 农场名称无法存储特殊字符 |
| 农场签到 | 签到 | 需要注意,该项会从服务器拉取签到数据 | | 农场签到 | 签到 | 需要注意,该项会从服务器拉取签到数据 |
| 土地升级 [地块ID] | 将土地升级,带来收益提升 | 如果土地升级时,土地有播种作物,那么将直接成熟 |
--- ---
## 更新日志[(详细)](./log/log.md) ## 更新日志[(详细)](./log/log.md)
用户方面 用户方面
--- ---
- 新增种子商店筛选功能如果没有BUG的话后续我的种子、我的作物等也会加入筛选功能 - 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧
- 新增签到功能(测试 - 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件
- 新增农场详述功能,能通过该功能更加详细的观看农场数据 - 定时更新签到文件、作物资源从00:30调整至04:30
- 修复了迁移旧数据库无法正常迁移的BUG - 修正了部分土地资源错误的情况
- 修复了偷菜会导致偷自己的BUG - 修正了部分文本信息错误的情况
- 修正了作物阶段绘制不正确的BUG
代码方面 代码方面
--- ---
- 修正了多阶段作物成长素材计算逻辑 - 修正部分事件连接机制
- 修正了作物数据库字段错乱的问题 - 修正网络请求端口
- 作物新增offset字段用于以后偏移坐标和大小(尚未启用,该模式有商议
---
## 待办事宜 `Todo` 列表 ## 待办事宜 `Todo` 列表
- [x] 完善我的农场图片,例如左上角显示用户数据 - [x] 完善我的农场图片,例如左上角显示用户数据
- [ ] 完善升级数据、作物数据、作物图片 - [ ] 完善升级数据、作物数据、作物图片
- [x] 签到功能 - [x] 签到功能
- [x] 在线更新作物信息
- [ ] 添加渔场功能 - [ ] 添加渔场功能
- [ ] 增加活动、交易行功能 - [ ] 增加活动、交易行功能
- [ ] 增加交易行总行功能 - [ ] 增加交易行总行功能

View File

@ -9,6 +9,7 @@ from zhenxun.utils.message import MessageUtils
from .command import diuse_farm, diuse_register, reclamation from .command import diuse_farm, diuse_register, reclamation
from .database.database import g_pSqlManager from .database.database import g_pSqlManager
from .dbService import g_pDBService from .dbService import g_pDBService
from .event.event import g_pEventManager
from .farm.farm import g_pFarmManager from .farm.farm import g_pFarmManager
from .farm.shop import g_pShopManager from .farm.shop import g_pShopManager
from .json import g_pJsonManager from .json import g_pJsonManager
@ -22,6 +23,7 @@ __plugin_meta__ = PluginMetadata(
指令 指令
at 开通农场 at 开通农场
我的农场 我的农场
农场详述
我的农场币 我的农场币
种子商店 [筛选关键字] [页数] or [页数] 种子商店 [筛选关键字] [页数] or [页数]
购买种子 [作物/种子名称] [数量] 购买种子 [作物/种子名称] [数量]
@ -36,10 +38,11 @@ __plugin_meta__ = PluginMetadata(
购买农场币 [数量] 数量为消耗金币的数量 购买农场币 [数量] 数量为消耗金币的数量
更改农场名 [新农场名] 更改农场名 [新农场名]
农场签到 农场签到
土地升级 [地块ID]通过农场详述获取
""".strip(), """.strip(),
extra=PluginExtraData( extra=PluginExtraData(
author="Art_Sakura", author="Art_Sakura",
version="1.4.3", version="1.5.0",
commands=[Command(command="我的农场")], commands=[Command(command="我的农场")],
menu_type="群内小游戏", menu_type="群内小游戏",
configs=[ configs=[
@ -84,6 +87,9 @@ async def start():
await g_pDBService.init() await g_pDBService.init()
# 检查作物文件是否缺失 or 更新
await g_pRequestManager.initPlantDBFile()
# 析构函数 # 析构函数
@driver.on_shutdown @driver.on_shutdown
@ -93,9 +99,10 @@ async def shutdown():
await g_pDBService.cleanup() await g_pDBService.cleanup()
@scheduler.scheduled_job(trigger="cron", hour=0, minute=30, id="signInFile") @scheduler.scheduled_job(trigger="cron", hour=4, minute=30, id="signInFile")
async def signInFile(): async def signInFile():
try: try:
await g_pJsonManager.initSignInFile() await g_pJsonManager.initSignInFile()
except: await g_pRequestManager.initPlantDBFile()
logger.info("农场签到文件下载失败!") except Exception as e:
logger.error("农场定时检查出错", e=e)

View File

@ -58,7 +58,7 @@ async def handle_register(session: Uninfo):
) )
msg = ( msg = (
g_sTranslation["register"]["success"] g_sTranslation["register"]["success"].format(point=500)
if success if success
else g_sTranslation["register"]["error"] else g_sTranslation["register"]["error"]
) )
@ -549,6 +549,41 @@ async def _(session: Uninfo):
# await MessageUtils.alc_forward_msg([info], session.self_id, BotConfig.self_nickname).send(reply_to=True) # await MessageUtils.alc_forward_msg([info], session.self_id, BotConfig.self_nickname).send(reply_to=True)
soil_upgrade = on_alconna(
Alconna("土地升级", Args["index", int]),
priority=5,
block=True,
)
@soil_upgrade.handle()
async def _(session: Uninfo, index: Query[int] = AlconnaQuery("num", 1)):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return
condition = await g_pFarmManager.soilUpgradeCondition(uid, index.result)
await MessageUtils.build_message(condition).send(reply_to=True)
@waiter(waits=["message"], keep_session=True)
async def check(event: Event):
return event.get_plaintext()
resp = await check.wait(timeout=60)
if resp is None:
await MessageUtils.build_message(g_sTranslation["soilInfo"]["timeOut"]).send(
reply_to=True
)
return
if not resp == "":
return
res = await g_pFarmManager.soilUpgrade(uid, index.result)
await MessageUtils.build_message(res).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"农场下阶段(.*?)", "农场下阶段(.*?)",
command="我的农场", command="我的农场",

View File

@ -26,6 +26,9 @@ g_sConfigPath = Path(__file__).resolve().parent / "config"
# 农场签到文件路径 # 农场签到文件路径
g_sSignInPath = g_sConfigPath / "sign_in.json" g_sSignInPath = g_sConfigPath / "sign_in.json"
# 土地等级上限
g_iSoilLevelMax = 3
# 农场同一文本 # 农场同一文本
g_sTranslation = { g_sTranslation = {
"basic": { "basic": {
@ -106,4 +109,16 @@ g_sTranslation = {
"error": "❗️ 签到功能异常!", "error": "❗️ 签到功能异常!",
"error1": "❌ 签到失败!未知错误 💔", "error1": "❌ 签到失败!未知错误 💔",
}, },
"soilInfo": {
"success": "土地成功升级至{name},效果为:{text}",
"timeOut": "等待土地升级回复超时,请重试",
"error": "土地信息尚未查询到",
"error1": "该土地已经升至满级啦~",
"red": "增产+10%",
"black": "增产+20% 时间-20%",
"gold": "增产+28% 经验+28% 时间-20%",
"amethyst": "增产+30% 经验+30% 时间-25% 幸运+1%",
"aquamarine": "增产+32% 经验+32% 时间-28% 幸运+1%",
"blackcrystal": "增产+32% 经验+40% 时间-28% 幸运+2%",
},
} }

View File

@ -153,5 +153,104 @@
"x": 1451, "x": 1451,
"y": 1072 "y": 1072
} }
},
"upgrade":
{
"red": [
{ "level": 28, "point": 200000, "vipPoint": 0, "item":{}},
{ "level": 29, "point": 220000, "vipPoint": 0, "item":{}},
{ "level": 30, "point": 240000, "vipPoint": 0, "item":{}},
{ "level": 31, "point": 260000, "vipPoint": 0, "item":{}},
{ "level": 32, "point": 290000, "vipPoint": 0, "item":{}},
{ "level": 33, "point": 320000, "vipPoint": 0, "item":{}},
{ "level": 34, "point": 350000, "vipPoint": 0, "item":{}},
{ "level": 35, "point": 380000, "vipPoint": 0, "item":{}},
{ "level": 36, "point": 410000, "vipPoint": 0, "item":{}},
{ "level": 37, "point": 440000, "vipPoint": 0, "item":{}},
{ "level": 38, "point": 480000, "vipPoint": 0, "item":{}},
{ "level": 39, "point": 520000, "vipPoint": 0, "item":{}},
{ "level": 40, "point": 560000, "vipPoint": 0, "item":{}},
{ "level": 41, "point": 600000, "vipPoint": 0, "item":{}},
{ "level": 42, "point": 650000, "vipPoint": 0, "item":{}},
{ "level": 43, "point": 700000, "vipPoint": 0, "item":{}},
{ "level": 44, "point": 770000, "vipPoint": 0, "item":{}},
{ "level": 45, "point": 900000, "vipPoint": 0, "item":{}},
{ "level": 47, "point": 1500000, "vipPoint": 0, "item":{}},
{ "level": 49, "point": 2000000, "vipPoint": 0, "item":{}},
{ "level": 53, "point": 4000000, "vipPoint": 0, "item":{}},
{ "level": 53, "point": 4000000, "vipPoint": 0, "item":{}},
{ "level": 55, "point": 5500000, "vipPoint": 0, "item":{}},
{ "level": 57, "point": 6800000, "vipPoint": 0, "item":{}},
{ "level": 60, "point": 10000000, "vipPoint": 0, "item":{}},
{ "level": 64, "point": 15000000, "vipPoint": 0, "item":{}},
{ "level": 68, "point": 20000000, "vipPoint": 0, "item":{}},
{ "level": 73, "point": 30000000, "vipPoint": 0, "item":{}},
{ "level": 78, "point": 50000000, "vipPoint": 0, "item":{}},
{ "level": 83, "point": 80000000, "vipPoint": 0, "item":{}}
],
"black": [
{ "level": 40, "point": 500000, "vipPoint": 0, "item":{}},
{ "level": 41, "point": 600000, "vipPoint": 0, "item":{}},
{ "level": 42, "point": 700000, "vipPoint": 0, "item":{}},
{ "level": 43, "point": 800000, "vipPoint": 0, "item":{}},
{ "level": 44, "point": 900000, "vipPoint": 0, "item":{}},
{ "level": 45, "point": 1000000, "vipPoint": 0, "item":{}},
{ "level": 46, "point": 2000000, "vipPoint": 0, "item":{}},
{ "level": 47, "point": 2200000, "vipPoint": 0, "item":{}},
{ "level": 48, "point": 2400000, "vipPoint": 0, "item":{}},
{ "level": 49, "point": 2600000, "vipPoint": 0, "item":{}},
{ "level": 50, "point": 2800000, "vipPoint": 0, "item":{}},
{ "level": 51, "point": 3000000, "vipPoint": 0, "item":{}},
{ "level": 52, "point": 4000000, "vipPoint": 0, "item":{}},
{ "level": 53, "point": 4200000, "vipPoint": 0, "item":{}},
{ "level": 54, "point": 4400000, "vipPoint": 0, "item":{}},
{ "level": 55, "point": 4600000, "vipPoint": 0, "item":{}},
{ "level": 56, "point": 4800000, "vipPoint": 0, "item":{}},
{ "level": 57, "point": 5000000, "vipPoint": 0, "item":{}},
{ "level": 59, "point": 6000000, "vipPoint": 0, "item":{}},
{ "level": 61, "point": 6200000, "vipPoint": 0, "item":{}},
{ "level": 65, "point": 6600000, "vipPoint": 0, "item":{}},
{ "level": 65, "point": 6600000, "vipPoint": 0, "item":{}},
{ "level": 67, "point": 6800000, "vipPoint": 0, "item":{}},
{ "level": 69, "point": 7000000, "vipPoint": 0, "item":{}},
{ "level": 72, "point": 10000000, "vipPoint": 0, "item":{}},
{ "level": 76, "point": 16000000, "vipPoint": 0, "item":{}},
{ "level": 80, "point": 27000000, "vipPoint": 0, "item":{}},
{ "level": 85, "point": 43000000, "vipPoint": 0, "item":{}},
{ "level": 90, "point": 65000000, "vipPoint": 0, "item":{}},
{ "level": 95, "point": 93000000, "vipPoint": 0, "item":{}}
],
"gold": [
{ "level": 58, "point": 4000000, "vipPoint": 0, "item":{}},
{ "level": 59, "point": 4200000, "vipPoint": 0, "item":{}},
{ "level": 60, "point": 4400000, "vipPoint": 0, "item":{}},
{ "level": 61, "point": 4600000, "vipPoint": 0, "item":{}},
{ "level": 62, "point": 4800000, "vipPoint": 0, "item":{}},
{ "level": 63, "point": 5000000, "vipPoint": 0, "item":{}},
{ "level": 64, "point": 5200000, "vipPoint": 0, "item":{}},
{ "level": 65, "point": 5400000, "vipPoint": 0, "item":{}},
{ "level": 66, "point": 5600000, "vipPoint": 0, "item":{}},
{ "level": 67, "point": 5800000, "vipPoint": 0, "item":{}},
{ "level": 68, "point": 6000000, "vipPoint": 0, "item":{}},
{ "level": 69, "point": 6200000, "vipPoint": 0, "item":{}},
{ "level": 70, "point": 6600000, "vipPoint": 0, "item":{}},
{ "level": 71, "point": 7000000, "vipPoint": 0, "item":{}},
{ "level": 72, "point": 7400000, "vipPoint": 0, "item":{}},
{ "level": 73, "point": 7800000, "vipPoint": 0, "item":{}},
{ "level": 74, "point": 8200000, "vipPoint": 0, "item":{}},
{ "level": 75, "point": 8600000, "vipPoint": 0, "item":{}},
{ "level": 77, "point": 9500000, "vipPoint": 0, "item":{}},
{ "level": 79, "point": 10400000, "vipPoint": 0, "item":{}},
{ "level": 83, "point": 12200000, "vipPoint": 0, "item":{}},
{ "level": 83, "point": 12200000, "vipPoint": 0, "item":{}},
{ "level": 85, "point": 13100000, "vipPoint": 0, "item":{}},
{ "level": 87, "point": 14000000, "vipPoint": 0, "item":{}},
{ "level": 90, "point": 16000000, "vipPoint": 0, "item":{}},
{ "level": 94, "point": 20000000, "vipPoint": 0, "item":{}},
{ "level": 98, "point": 28000000, "vipPoint": 0, "item":{}},
{ "level": 103, "point": 44000000, "vipPoint": 0, "item":{}},
{ "level": 108, "point": 76000000, "vipPoint": 0, "item":{}},
{ "level": 113, "point": 1080000000, "vipPoint": 0, "item":{}}
]
} }
} }

View File

@ -1,7 +1,7 @@
import os
import re
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import os
from pathlib import Path from pathlib import Path
import re
import aiosqlite import aiosqlite
@ -103,9 +103,14 @@ class CSqlManager:
commonCols = [k for k in desired if k in existing] commonCols = [k for k in desired if k in existing]
if commonCols: if commonCols:
colsStr = ", ".join(f'"{c}"' for c in commonCols) colsStr = ", ".join(f'"{c}"' for c in commonCols)
await cls.m_pDB.execute(
f'INSERT INTO "{tmpTable}" ({colsStr}) SELECT {colsStr} FROM "{tableName}";' sql = (
f'INSERT INTO "{tmpTable}" ({colsStr}) '
f"SELECT {colsStr} "
f'FROM "{tableName}";'
) )
await cls.m_pDB.execute(sql)
await cls.m_pDB.execute(f'DROP TABLE "{tableName}";') await cls.m_pDB.execute(f'DROP TABLE "{tableName}";')
await cls.m_pDB.execute( await cls.m_pDB.execute(
f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";' f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";'

View File

@ -1,11 +1,13 @@
import os
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
import os
import aiosqlite import aiosqlite
from zhenxun.configs.config import Config
from zhenxun.services.log import logger from zhenxun.services.log import logger
from ..config import g_bIsDebug, g_sPlantPath from ..config import g_bIsDebug, g_sPlantPath, g_sResourcePath
from ..request import g_pRequestManager
class CPlantManager: class CPlantManager:
@ -247,3 +249,45 @@ class CPlantManager:
except Exception as e: except Exception as e:
logger.warning("查询所有作物失败", e=e) logger.warning("查询所有作物失败", e=e)
return [] return []
@classmethod
async def downloadPlant(cls) -> bool:
"""遍历所有作物下载各阶段图片及icon文件到指定文件夹
Returns:
bool: 全部下载完成返回True如有失败返回False
"""
success = True
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
baseUrl = baseUrl.rstrip("/") + ":8998/file"
try:
plants = await cls.listPlants()
for plant in plants:
name = plant["name"]
phaseCount = await cls.getPlantPhaseNumberByName(name)
saveDir = os.path.join(g_sResourcePath, "plant", name)
begin = 0 if plant["general"] == 0 else 1
for idx in range(begin, phaseCount + 1):
fileName = f"{idx}.png"
fullPath = os.path.join(saveDir, fileName)
if os.path.exists(fullPath):
continue
url = f"{baseUrl}/{name}/{idx}.png"
if not await g_pRequestManager.download(url, saveDir, f"{idx}.png"):
success = False
iconName = "icon.png"
iconPath = os.path.join(saveDir, iconName)
if not os.path.exists(iconPath):
iconUrl = f"{baseUrl}/{name}/{iconName}"
if not await g_pRequestManager.download(iconUrl, saveDir, iconName):
success = False
return success
except Exception as e:
logger.warning(f"下载作物资源异常: {e}")
return False

View File

@ -225,11 +225,11 @@ class CUserDB(CSqlManager):
@classmethod @classmethod
async def updateUserVipPointByUid(cls, uid: str, vipPoint: int) -> bool: async def updateUserVipPointByUid(cls, uid: str, vipPoint: int) -> bool:
"""根据用户Uid更新农场币数量 """根据用户Uid更新点券数量
Args: Args:
uid (str): 用户Uid uid (str): 用户Uid
vipPoint (int): 农场币数量 vipPoint (int): 点券数量
Returns: Returns:
bool: 是否更新成功 bool: 是否更新成功
@ -300,7 +300,8 @@ class CUserDB(CSqlManager):
uid (str): 用户Uid uid (str): 用户Uid
Returns: Returns:
tuple[int, int, int]: (当前等级, 升至下级还需经验, 当前等级已获经验)失败返回(-1, -1, -1) tuple[int, int, int]: 成功返回(当前等级, 升至下级还需经验, 当前等级已获经验)
失败返回(-1, -1, -1)
""" """
if not uid: if not uid:
return -1, -1, -1 return -1, -1, -1

View File

@ -1,6 +1,6 @@
import calendar import calendar
import random
from datetime import timedelta from datetime import timedelta
import random
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils._build_image import BuildImage from zhenxun.utils._build_image import BuildImage
@ -22,7 +22,7 @@ class CUserSignDB(CSqlManager):
"isSupplement": "TINYINT NOT NULL DEFAULT 0", # 是否补签 "isSupplement": "TINYINT NOT NULL DEFAULT 0", # 是否补签
"exp": "INT NOT NULL DEFAULT 0", # 当天签到经验 "exp": "INT NOT NULL DEFAULT 0", # 当天签到经验
"point": "INT NOT NULL DEFAULT 0", # 当天签到金币 "point": "INT NOT NULL DEFAULT 0", # 当天签到金币
"createdAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 创建时间 "createdAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 创建时间 # noqa: E501
"PRIMARY KEY": "(uid, signDate)", "PRIMARY KEY": "(uid, signDate)",
} }
@ -35,7 +35,7 @@ class CUserSignDB(CSqlManager):
"lastSignDate": "DATE DEFAULT NULL", # 上次签到日期 "lastSignDate": "DATE DEFAULT NULL", # 上次签到日期
"continuousDays": "INT NOT NULL DEFAULT 0", # 连续签到天数 "continuousDays": "INT NOT NULL DEFAULT 0", # 连续签到天数
"supplementCount": "INT NOT NULL DEFAULT 0", # 补签次数 "supplementCount": "INT NOT NULL DEFAULT 0", # 补签次数
"updatedAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 更新时间 "updatedAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime'))", # 更新时间 # noqa: E501
} }
await cls.ensureTableSchema("userSignLog", userSignLog) await cls.ensureTableSchema("userSignLog", userSignLog)

View File

@ -67,6 +67,35 @@ class CUserSoilDB(CSqlManager):
f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}" f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}"
) )
@classmethod
async def matureNow(cls, uid: str, soilIndex: int):
"""将指定地块的作物直接成熟
Args:
uid (str): 用户ID
soilIndex (int): 地块索引从1开始
"""
# 与 nextPhase 不同:无需调试模式检查,允许在任何模式下调用
soilInfo = await cls.getUserSoil(uid, soilIndex)
if not soilInfo:
return
plantName = soilInfo.get("plantName")
if not plantName:
return
plantInfo = await g_pDBService.plant.getPlantByName(plantName)
if not plantInfo:
return
currentTime = g_pToolManager.dateTime().now().timestamp()
# 如果当前时间已经超过或等于成熟时间,则作物已成熟或可收获
if currentTime >= soilInfo["matureTime"]:
return
# 将作物成熟时间直接更新为当前时间,实现立即成熟
await cls.updateUserSoilFields(uid, soilIndex, {"matureTime": currentTime})
@classmethod @classmethod
async def getUserFarmByUid(cls, uid: str) -> dict: async def getUserFarmByUid(cls, uid: str) -> dict:
"""获取指定用户的旧农场数据 """获取指定用户的旧农场数据
@ -247,6 +276,25 @@ class CUserSoilDB(CSqlManager):
columns = [description[0] for description in cursor.description] columns = [description[0] for description in cursor.description]
return dict(zip(columns, row)) return dict(zip(columns, row))
@classmethod
async def countSoilByLevel(cls, uid: str, soilLevel: int) -> int:
"""统计指定用户在指定土地等级的土地数量
Args:
uid (str): 用户ID
soilLevel (int): 土地等级
Returns:
int: 符合条件的土地数量
"""
async with cls._transaction():
cursor = await cls.m_pDB.execute(
"SELECT COUNT(*) FROM userSoil WHERE uid = ? AND soilLevel = ?",
(uid, soilLevel),
)
row = await cursor.fetchone()
return row[0] if row else 0
@classmethod @classmethod
async def updateUserSoil(cls, uid: str, soilIndex: int, field: str, value): async def updateUserSoil(cls, uid: str, soilIndex: int, field: str, value):
"""更新指定用户土地的单个字段 """更新指定用户土地的单个字段
@ -393,8 +441,8 @@ class CUserSoilDB(CSqlManager):
bool: 播种成功返回 True否则返回 False bool: 播种成功返回 True否则返回 False
""" """
# 校验土地区是否已种植 # 校验土地区是否已种植
soilRecord = await cls.getUserSoil(uid, soilIndex) soilInfo = await cls.getUserSoil(uid, soilIndex)
if soilRecord and soilRecord.get("plantName"): if not soilInfo:
return False return False
# 获取植物配置 # 获取植物配置
@ -404,11 +452,18 @@ class CUserSoilDB(CSqlManager):
return False return False
nowTs = int(g_pToolManager.dateTime().now().timestamp()) nowTs = int(g_pToolManager.dateTime().now().timestamp())
matureTs = nowTs + int(plantCfg.get("time", 0)) * 3600
time = int(plantCfg.get("time", 0))
percent = await cls.getSoilLevelTime(soilInfo.get("soilLevel", 0))
# 处理土地等级带来的时间缩短
time = time * (100 + percent) // 100
matureTs = nowTs + time * 3600
try: try:
async with cls._transaction(): async with cls._transaction():
prev = soilRecord or {} prev = soilInfo or {}
await cls._deleteUserSoil(uid, soilIndex) await cls._deleteUserSoil(uid, soilIndex)
await cls._insertUserSoil( await cls._insertUserSoil(
{ {
@ -457,3 +512,90 @@ class CUserSoilDB(CSqlManager):
status.append("缺水") status.append("缺水")
return ",".join(status) return ",".join(status)
@classmethod
async def getSoilLevel(cls, level: int) -> str:
"""获取土地等级英文文本
Args:
level (int): 土地等级
Returns:
str:
"""
if level == 1:
return "red"
elif level == 2:
return "black"
elif level == 3:
return "gold"
return "default"
@classmethod
async def getSoilLevelText(cls, level: int) -> str:
"""获取土地等级中文文本
Args:
level (int): 土地等级
Returns:
str:
"""
if level == 1:
return "红土地"
elif level == 2:
return "黑土地"
elif level == 3:
return "金土地"
return "草土地"
@classmethod
async def getSoilLevelHarvestNumber(cls, level: int) -> int:
"""获取土地等级收获数量增加比例
Args:
level (int): 土地等级
Returns:
int:
"""
if level == 2:
return 20
elif level == 3:
return 28
return 10
@classmethod
async def getSoilLevelHarvestExp(cls, level: int) -> int:
"""获取土地等级收获经验增加比例
Args:
level (int): 土地等级
Returns:
int:
"""
if level == 3:
return 28
return 0
@classmethod
async def getSoilLevelTime(cls, level: int) -> int:
"""获取土地等级播种减少时间消耗
Args:
level (int): 土地等级
Returns:
int:
"""
if level == 2:
return 20
elif level == 3:
return 20
return 0

View File

@ -1,49 +1,68 @@
import asyncio import inspect
import time import time
from zhenxun.services.log import logger from zhenxun.services.log import logger
class Signal: class Signal:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
bound = instance.__dict__.get(self.name)
if bound is None:
bound = _SignalBound()
instance.__dict__[self.name] = bound
return bound
class _SignalBound:
def __init__(self): def __init__(self):
self._slots = [] # 绑定的槽函数列表 self._slots = []
self._onceSlots = [] # 只触发一次的槽函数列表 self._onceSlots = []
def connect(self, slot, priority=0): def connect(self, func=None, *, priority=0):
if callable(slot) and not any(s[0] == slot for s in self._slots): if func is None:
self._slots.append((slot, priority)) return lambda f: self.connect(f, priority=priority)
if callable(func) and not any(s[0] == func for s in self._slots):
self._slots.append((func, priority))
self._slots.sort(key=lambda x: -x[1]) self._slots.sort(key=lambda x: -x[1])
return func
def connectOnce(self, slot, priority=0): def connect_once(self, func=None, *, priority=0):
if callable(slot) and not any(s[0] == slot for s in self._onceSlots): if func is None:
self._onceSlots.append((slot, priority)) return lambda f: self.connect_once(f, priority=priority)
if callable(func) and not any(s[0] == func for s in self._onceSlots):
self._onceSlots.append((func, priority))
self._onceSlots.sort(key=lambda x: -x[1]) self._onceSlots.sort(key=lambda x: -x[1])
return func
def disconnect(self, slot): def disconnect(self, func):
self._slots = [s for s in self._slots if s[0] != slot] self._slots = [s for s in self._slots if s[0] != func]
self._onceSlots = [s for s in self._onceSlots if s[0] != slot] self._onceSlots = [s for s in self._onceSlots if s[0] != func]
async def emit(self, *args, **kwargs): async def emit(self, *args, **kwargs):
slots = list(self._slots) slots = list(self._slots)
onceSlots = list(self._onceSlots) onceSlots = list(self._onceSlots)
self._onceSlots.clear() self._onceSlots.clear()
for slot, _ in slots + onceSlots: for slot, _ in slots + onceSlots:
startTime = time.time() start = time.time()
try: try:
if asyncio.iscoroutinefunction(slot): if inspect.iscoroutinefunction(slot):
await slot(*args, **kwargs) await slot(*args, **kwargs)
else: else:
slot(*args, **kwargs) slot(*args, **kwargs)
duration = (time.time() - startTime) * 1000 logger.debug(
logger.debug(f"事件槽 {slot.__name__} 执行完成,耗时 {duration:.2f} ms") f"【真寻农场】事件槽 {slot.__name__} 执行完成,耗时 {(time.time() - start) * 1000:.2f} ms"
)
except Exception as e: except Exception as e:
logger.warning(f"事件槽 {slot.__name__} 触发异常: {e}") logger.warning(f"事件槽 {slot.__name__} 触发异常: {e}")
class FarmEventManager: class FarmEventManager:
def __init__(self): m_beforePlant = Signal()
self.m_beforePlant = Signal()
"""播种前信号 """播种前信号
Args: Args:
@ -52,7 +71,7 @@ class FarmEventManager:
num (int): 播种数量 num (int): 播种数量
""" """
self.m_afterPlant = Signal() m_afterPlant = Signal()
"""播种后信号 每块地播种都会触发该信号 """播种后信号 每块地播种都会触发该信号
Args: Args:
@ -61,14 +80,14 @@ class FarmEventManager:
soilIndex (int): 播种地块索引 从1开始 soilIndex (int): 播种地块索引 从1开始
""" """
self.m_beforeHarvest = Signal() m_beforeHarvest = Signal()
"""收获前信号 """收获前信号
Args: Args:
uid (str): 用户Uid uid (str): 用户Uid
""" """
self.m_afterHarvest = Signal() m_afterHarvest = Signal()
"""收获后信号 每块地收获都会触发该信号 """收获后信号 每块地收获都会触发该信号
Args: Args:
@ -78,14 +97,14 @@ class FarmEventManager:
soilIndex (int): 收获地块索引 从1开始 soilIndex (int): 收获地块索引 从1开始
""" """
self.m_beforeEradicate = Signal() m_beforeEradicate = Signal()
"""铲除前信号 """铲除前信号
Args: Args:
uid (str): 用户Uid uid (str): 用户Uid
""" """
self.m_afterEradicate = Signal() m_afterEradicate = Signal()
"""铲除后信号 每块地铲除都会触发该信号 """铲除后信号 每块地铲除都会触发该信号
Args: Args:
@ -93,10 +112,12 @@ class FarmEventManager:
soilIndex (index): 铲除地块索引 从1开始 soilIndex (index): 铲除地块索引 从1开始
""" """
self.m_beforeExpand = Signal() m_beforeExpand = Signal()
self.m_afterExpand = Signal() m_afterExpand = Signal()
self.m_beforeSteal = Signal() m_beforeSteal = Signal()
self.m_afterSteal = Signal() m_afterSteal = Signal()
m_dit = Signal()
g_pEventManager = FarmEventManager() g_pEventManager = FarmEventManager()

View File

@ -9,7 +9,7 @@ from zhenxun.utils.enum import GoldHandle
from zhenxun.utils.image_utils import ImageTemplate from zhenxun.utils.image_utils import ImageTemplate
from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.platform import PlatformUtils
from ..config import g_bIsDebug, g_sResourcePath, g_sTranslation from ..config import g_bIsDebug, g_iSoilLevelMax, g_sResourcePath, g_sTranslation
from ..dbService import g_pDBService from ..dbService import g_pDBService
from ..event.event import g_pEventManager from ..event.event import g_pEventManager
from ..json import g_pJsonManager from ..json import g_pJsonManager
@ -37,7 +37,7 @@ class CFarmManager:
await UserConsole.reduce_gold( await UserConsole.reduce_gold(
uid, num, GoldHandle.PLUGIN, "zhenxun_plugin_farm" uid, num, GoldHandle.PLUGIN, "zhenxun_plugin_farm"
) # type: ignore )
await UserConsole.reduce_gold( await UserConsole.reduce_gold(
uid, fee, GoldHandle.PLUGIN, "zhenxun_plugin_farm" uid, fee, GoldHandle.PLUGIN, "zhenxun_plugin_farm"
) # type: ignore ) # type: ignore
@ -65,10 +65,6 @@ class CFarmManager:
soilSize = g_pJsonManager.m_pSoil["size"] soilSize = g_pJsonManager.m_pSoil["size"]
# TODO 缺少判断用户土地资源状况
soil = BuildImage(background=g_sResourcePath / "soil/普通土地.png")
await soil.resize(0, soilSize[0], soilSize[1])
grass = BuildImage(background=g_sResourcePath / "soil/草土地.png") grass = BuildImage(background=g_sResourcePath / "soil/草土地.png")
await grass.resize(0, soilSize[0], soilSize[1]) await grass.resize(0, soilSize[0], soilSize[1])
@ -88,6 +84,27 @@ class CFarmManager:
# 如果土地已经到达对应等级 # 如果土地已经到达对应等级
if index < soilUnlock: if index < soilUnlock:
soilUrl = ""
# TODO 缺少判断用户土地资源状况
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, index)
if not soilInfo:
soilUrl = "soil/普通土地.png"
else:
soilLevel = soilInfo.get("soilLevel", 0)
if soilLevel == 1:
soilUrl = "soil/红土地.png"
elif soilLevel == 2:
soilUrl = "soil/黑土地.png"
elif soilLevel == 3:
soilUrl = "soil/金土地.png"
else:
soilUrl = "soil/普通土地.png"
soil = BuildImage(background=g_sResourcePath / soilUrl)
await soil.resize(0, soilSize[0], soilSize[1])
await img.paste(soil, (x, y)) await img.paste(soil, (x, y))
isPlant, plant, isRipe, offsetX, offsetY = await cls.drawSoilPlant( isPlant, plant, isRipe, offsetX, offsetY = await cls.drawSoilPlant(
@ -211,6 +228,7 @@ class CFarmManager:
columnName = [ columnName = [
"-", "-",
"土地ID", "土地ID",
"土地等级",
"作物名称", "作物名称",
"成熟时间", "成熟时间",
"土地状态", "土地状态",
@ -226,7 +244,7 @@ class CFarmManager:
if soilInfo: if soilInfo:
if soilInfo["soilLevel"] == 1: if soilInfo["soilLevel"] == 1:
iconPath = g_sResourcePath / "soil/TODO.png" iconPath = g_sResourcePath / "soil/红土地.png"
else: else:
iconPath = g_sResourcePath / "soil/普通土地.png" iconPath = g_sResourcePath / "soil/普通土地.png"
@ -262,6 +280,9 @@ class CFarmManager:
[ [
icon, icon,
i, i,
await g_pDBService.userSoil.getSoilLevelText(
soilInfo["soilLevel"]
),
plantName, plantName,
matureTime, matureTime,
soilStatus, soilStatus,
@ -344,8 +365,11 @@ class CFarmManager:
return True, plant, True, offsetX, offsetY return True, plant, True, offsetX, offsetY
else: else:
# 如果是多阶段作物 且没有成熟 #早期思路 多阶段作物 直接是倒数第二阶段图片 # 如果是多阶段作物 且没有成熟 #早期思路 多阶段作物 直接是倒数第二阶段图片
# if soilInfo['harvestCount'] >= 1: # if soilInfo["harvestCount"] >= 1:
# plant = BuildImage(background = g_sResourcePath / f"plant/{soilInfo['plantName']}/{plantInfo['phase'] - 1s}.png") # plant = BuildImage(
# background=g_sResourcePath
# / f"plant/{soilInfo['plantName']}/{plantInfo['phase'] - 1}.png"
# )
# return True, plant, False, offsetX, offsetY # return True, plant, False, offsetX, offsetY
@ -466,7 +490,7 @@ class CFarmManager:
num = count num = count
# 发送播种前信号 # 发送播种前信号
await g_pEventManager.m_beforePlant.emit(uid=uid, name=name, num=num) await g_pEventManager.m_beforePlant.emit(uid=uid, name=name, num=num) # type: ignore
# 记录是否成功播种 # 记录是否成功播种
successCount = 0 successCount = 0
@ -484,7 +508,7 @@ class CFarmManager:
successCount += 1 successCount += 1
# 发送播种后信号 # 发送播种后信号
await g_pEventManager.m_afterPlant.emit( await g_pEventManager.m_afterPlant.emit( # type: ignore
uid=uid, name=name, soilIndex=i uid=uid, name=name, soilIndex=i
) )
@ -513,7 +537,7 @@ class CFarmManager:
str: 返回 str: 返回
""" """
try: try:
await g_pEventManager.m_beforeHarvest.emit(uid=uid) await g_pEventManager.m_beforeHarvest.emit(uid=uid) # type: ignore
soilNumber = await g_pDBService.user.getUserSoilByUid(uid) soilNumber = await g_pDBService.user.getUserSoilByUid(uid)
@ -530,6 +554,8 @@ class CFarmManager:
if not soilInfo: if not soilInfo:
continue continue
level = soilInfo.get("soilLevel", 0)
# 如果是枯萎状态 # 如果是枯萎状态
if soilInfo.get("wiltStatus", 1) == 1: if soilInfo.get("wiltStatus", 1) == 1:
continue continue
@ -550,20 +576,29 @@ class CFarmManager:
# 处理偷菜扣除数量 # 处理偷菜扣除数量
stealNum = await g_pDBService.userSteal.getTotalStolenCount(uid, i) stealNum = await g_pDBService.userSteal.getTotalStolenCount(uid, i)
number -= stealNum number -= stealNum
# 处理土地等级带来的数量增长 向下取整
percent = await g_pDBService.userSoil.getSoilLevelHarvestNumber(
level
)
number = number * (100 + percent) // 100
if number <= 0: if number <= 0:
continue continue
harvestCount += 1 harvestCount += 1
experience += plantInfo["experience"] experience += plantInfo["experience"]
# 处理土地等级带来的经验增长 向下取整
percent = await g_pDBService.userSoil.getSoilLevelHarvestExp(level)
experience = experience * (100 + percent) // 100
harvestRecords.append( harvestRecords.append(
g_sTranslation["harvest"]["append"].format( g_sTranslation["harvest"]["append"].format(
name=soilInfo["plantName"], name=soilInfo["plantName"],
num=number, num=number,
exp=plantInfo["experience"], exp=experience,
) )
) )
@ -597,7 +632,7 @@ class CFarmManager:
}, },
) )
await g_pEventManager.m_afterHarvest.emit( await g_pEventManager.m_afterHarvest.emit( # type: ignore
uid=uid, name=soilInfo["plantName"], num=number, soilIndex=i uid=uid, name=soilInfo["plantName"], num=number, soilIndex=i
) )
@ -631,7 +666,7 @@ class CFarmManager:
""" """
soilNumber = await g_pDBService.user.getUserSoilByUid(uid) soilNumber = await g_pDBService.user.getUserSoilByUid(uid)
await g_pEventManager.m_beforeEradicate.emit(uid=uid) await g_pEventManager.m_beforeEradicate.emit(uid=uid) # type: ignore
experience = 0 experience = 0
for i in range(1, soilNumber + 1): for i in range(1, soilNumber + 1):
@ -658,7 +693,7 @@ class CFarmManager:
# 铲除作物会将偷菜记录清空 # 铲除作物会将偷菜记录清空
await g_pDBService.userSteal.deleteStealRecord(uid, i) await g_pDBService.userSteal.deleteStealRecord(uid, i)
await g_pEventManager.m_afterEradicate.emit(uid=uid, soilIndex=i) await g_pEventManager.m_afterEradicate.emit(uid=uid, soilIndex=i) # type: ignore
if experience > 0: if experience > 0:
exp = await g_pDBService.user.getUserExpByUid(uid) exp = await g_pDBService.user.getUserExpByUid(uid)
@ -703,7 +738,7 @@ class CFarmManager:
if icon_path.exists(): if icon_path.exists():
icon = (icon_path, 33, 33) icon = (icon_path, 33, 33)
if plantInfo["sell"] == True: if plantInfo["sell"]:
sell = "可以" sell = "可以"
else: else:
sell = "不可以" sell = "不可以"
@ -863,6 +898,14 @@ class CFarmManager:
@classmethod @classmethod
async def reclamationCondition(cls, uid: str) -> str: async def reclamationCondition(cls, uid: str) -> str:
"""获取开垦条件
Args:
uid (str): 用户Uid
Returns:
str: 返回条件文本信息
"""
userInfo = await g_pDBService.user.getUserInfoByUid(uid) userInfo = await g_pDBService.user.getUserInfoByUid(uid)
rec = g_pJsonManager.m_pLevel["reclamation"] rec = g_pJsonManager.m_pLevel["reclamation"]
@ -892,6 +935,14 @@ class CFarmManager:
@classmethod @classmethod
async def reclamation(cls, uid: str) -> str: async def reclamation(cls, uid: str) -> str:
"""开垦
Args:
uid (str): 用户Uid
Returns:
str: _description_
"""
userInfo = await g_pDBService.user.getUserInfoByUid(uid) userInfo = await g_pDBService.user.getUserInfoByUid(uid)
level = await g_pDBService.user.getUserLevelByUid(uid) level = await g_pDBService.user.getUserLevelByUid(uid)
@ -905,7 +956,7 @@ class CFarmManager:
levelFileter = rec["level"] levelFileter = rec["level"]
point = rec["point"] point = rec["point"]
item = rec["item"] # item = rec["item"]
if level[0] < levelFileter: if level[0] < levelFileter:
return g_sTranslation["reclamation"]["nextLevel"].format( return g_sTranslation["reclamation"]["nextLevel"].format(
@ -923,5 +974,119 @@ class CFarmManager:
except Exception: except Exception:
return g_sTranslation["reclamation"]["error1"] return g_sTranslation["reclamation"]["error1"]
@classmethod
async def soilUpgradeCondition(cls, uid: str, soilIndex: int) -> str:
"""获取土地升级条件
Args:
uid (str): 用户Uid
soilIndex (str): 土地索引
Returns:
str: 返回土地升级条件
"""
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex)
if not soilInfo:
return g_sTranslation["soilInfo"]["error"]
soilLevel = soilInfo.get("soilLevel", 0) + 1
if soilLevel >= g_iSoilLevelMax:
return g_sTranslation["soilInfo"]["error1"]
# 获取用户当前土地 的下一级土地 数量
countSoil = await g_pDBService.userSoil.countSoilByLevel(uid, soilLevel)
# 获取升级所需
soilLevelText = await g_pDBService.userSoil.getSoilLevel(soilLevel)
fileter = g_pJsonManager.m_pSoil["upgrade"][soilLevelText][countSoil]
lines = ["升级该土地所需:"]
fields = [
("level", "等级"),
("point", "金币"),
("vipPoint", "点券"),
]
for key, label in fields:
value = fileter.get(key, 0)
if value > 0:
lines.append(f"{label}{value}")
items = fileter.get("item", {})
for name, qty in items.items():
if qty:
lines.append(f"{name}{qty}")
lines.append("回复“是”将执行升级")
return "\n".join(lines)
@classmethod
async def soilUpgrade(cls, uid: str, soilIndex: int) -> str:
"""土地升级
Args:
uid (str): 用户Uid
soilIndex (int): 土地索引
Returns:
str:
"""
userInfo = await g_pDBService.user.getUserInfoByUid(uid)
soilInfo = await g_pDBService.userSoil.getUserSoil(uid, soilIndex)
if not soilInfo:
return g_sTranslation["soilInfo"]["error"]
soilLevel = soilInfo.get("soilLevel", 0) + 1
if soilLevel >= g_iSoilLevelMax:
return g_sTranslation["soilInfo"]["error1"]
countSoil = await g_pDBService.userSoil.countSoilByLevel(uid, soilLevel)
soilLevelText = await g_pDBService.userSoil.getSoilLevel(soilLevel)
fileter = g_pJsonManager.m_pSoil["upgrade"][soilLevelText][countSoil]
getters = {
"level": (await g_pDBService.user.getUserLevelByUid(uid))[0],
"point": userInfo.get("point", 0),
"vipPoint": userInfo.get("vipPoint", 0),
}
requirements = {
"level": "等级",
"point": "金币",
"vipPoint": "点券",
}
for key, val in getters.items():
need = fileter.get(key, 0)
if val < need:
return f"你的{requirements[key]}不够哦~"
# 缺少item判断
# 更新数据库字段
await g_pDBService.userSoil.updateUserSoil(
uid, soilIndex, "soilLevel", soilLevel
)
# 如果有作物的话直接成熟
await g_pDBService.userSoil.matureNow(uid, soilIndex)
# 更新数据库字段
await g_pDBService.user.updateUserPointByUid(
uid, userInfo.get("point", 0) - fileter.get("point", 0)
)
await g_pDBService.user.updateUserPointByUid(
uid, userInfo.get("vipPoint", 0) - fileter.get("vipPoint", 0)
)
return g_sTranslation["soilInfo"]["success"].format(
name=await g_pDBService.userSoil.getSoilLevelText(soilLevel),
text=g_sTranslation["soilInfo"][soilLevelText],
)
g_pFarmManager = CFarmManager() g_pFarmManager = CFarmManager()

View File

@ -79,7 +79,11 @@ class CJsonManager:
return False return False
else: else:
return await self.initSign() result = await self.initSign()
config.g_bSignStatus = result
return result
async def initSign(self) -> bool: async def initSign(self) -> bool:
try: try:

View File

@ -1,5 +1,19 @@
# 真寻农场更新日志 # 真寻农场更新日志
## V1.5
用户方面
---
- 新增土地升级指令,快来将土地升级至红土地、黑土地、金土地吧
- 新增查漏补缺作物资源功能,自动更新最新作物和作物资源文件
- 定时更新签到文件、作物资源从00:30调整至04:30
- 修正了部分土地资源错误的情况
- 修正了部分文本信息错误的情况
代码方面
---
- 修正部分事件连接机制
- 修正网络请求端口
## V1.4 ## V1.4
用户方面 用户方面
--- ---

View File

@ -2,11 +2,20 @@ import json
import os import os
import httpx import httpx
from rich.progress import (
BarColumn,
DownloadColumn,
Progress,
TextColumn,
TimeRemainingColumn,
TransferSpeedColumn,
)
from zhenxun.configs.config import Config from zhenxun.configs.config import Config
from zhenxun.services.log import logger from zhenxun.services.log import logger
from .config import g_sSignInPath from .config import g_sPlantPath, g_sSignInPath
from .dbService import g_pDBService
from .tool import g_pToolManager from .tool import g_pToolManager
@ -22,7 +31,7 @@ class CRequestManager:
params: dict | None = None, params: dict | None = None,
jsonData: dict | None = None, jsonData: dict | None = None,
) -> bool: ) -> bool:
"""下载文件到指定路径并覆盖已存在的文件 """下载文件到指定路径并覆盖已存在的文件,并显示下载进度条
Args: Args:
url (str): 文件的下载链接 url (str): 文件的下载链接
@ -30,33 +39,53 @@ class CRequestManager:
fileName (str): 保存后的文件名 fileName (str): 保存后的文件名
params (dict | None): 可选的 URL 查询参数 params (dict | None): 可选的 URL 查询参数
jsonData (dict | None): 可选的 JSON 请求体 jsonData (dict | None): 可选的 JSON 请求体
Returns: Returns:
bool: 是否下载成功 bool: 是否下载成功
""" """
headers = {"token": cls.m_sTokens} headers = {"token": cls.m_sTokens}
try: try:
async with httpx.AsyncClient(timeout=10.0) as client: async with httpx.AsyncClient(timeout=30.0) as client:
requestArgs: dict = {"headers": headers} requestArgs: dict = {"headers": headers}
if params: if params:
requestArgs["params"] = params requestArgs["params"] = params
if jsonData: if jsonData:
requestArgs["json"] = jsonData requestArgs["json"] = jsonData
response = await client.request("GET", url, **requestArgs) response = await client.request(
"GET", url, **requestArgs, follow_redirects=True
)
if response.status_code == 200: if response.status_code != 200:
fullPath = os.path.join(savePath, fileName)
os.makedirs(os.path.dirname(fullPath), exist_ok=True)
with open(fullPath, "wb") as f:
f.write(response.content)
return True
else:
logger.warning( logger.warning(
f"文件下载失败: HTTP {response.status_code} {response.text}" f"文件下载失败: HTTP {response.status_code} {response.text}"
) )
return False return False
totalLength = int(response.headers.get("Content-Length", 0))
fullPath = os.path.join(savePath, fileName)
os.makedirs(os.path.dirname(fullPath), exist_ok=True)
with Progress(
TextColumn("[progress.description]{task.description}"),
BarColumn(),
DownloadColumn(),
TransferSpeedColumn(),
TimeRemainingColumn(),
transient=True,
) as progress:
task = progress.add_task(
f"[green]【真寻农场】正在下载 {fileName}", total=totalLength
)
with open(fullPath, "wb") as f:
async for chunk in response.aiter_bytes(chunk_size=1024):
f.write(chunk)
progress.advance(task, len(chunk))
return True
except Exception as e: except Exception as e:
logger.warning(f"下载文件异常: {e}") logger.warning(f"下载文件异常: {e}")
return False return False
@ -77,7 +106,7 @@ class CRequestManager:
dict: 返回请求结果的JSON数据 dict: 返回请求结果的JSON数据
""" """
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}" url = f"{baseUrl.rstrip('/')}:8998/{endpoint.lstrip('/')}"
headers = {"token": cls.m_sTokens} headers = {"token": cls.m_sTokens}
try: try:
@ -110,7 +139,7 @@ class CRequestManager:
dict: 返回请求结果的JSON数据 dict: 返回请求结果的JSON数据
""" """
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}" url = f"{baseUrl.rstrip('/')}:8998/{endpoint.lstrip('/')}"
headers = {"token": cls.m_sTokens} headers = {"token": cls.m_sTokens}
try: try:
@ -156,20 +185,110 @@ class CRequestManager:
@classmethod @classmethod
async def downloadSignInFile(cls) -> bool: async def downloadSignInFile(cls) -> bool:
"""下载签到文件,并重命名为 sign_in.json
Returns:
bool: 是否下载成功
"""
try: try:
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
url = f"{baseUrl.rstrip('/')}:8998/sign_in" url = f"{baseUrl.rstrip('/')}:8998/sign_in"
path = str(g_sSignInPath.parent.resolve(strict=False)) path = str(g_sSignInPath.parent.resolve(strict=False))
yearMonth = g_pToolManager.dateTime().now().strftime("%Y%m") yearMonth = g_pToolManager.dateTime().now().strftime("%Y%m")
await cls.download(url, path, "signTemp.json", jsonData={"date": yearMonth}) # 下载为 signTemp.json
g_pToolManager.renameFile(f"{path}/signTemp.json", "sign_in.json") success = await cls.download(
url=url,
savePath=path,
fileName="signTemp.json",
jsonData={"date": yearMonth},
)
if not success:
return False
# 重命名为 sign_in.json
g_pToolManager.renameFile(f"{path}/signTemp.json", "sign_in.json")
return True return True
except Exception as e: except Exception as e:
logger.error("下载签到文件失败", e=e) logger.error("下载签到文件失败", e=e)
return False return False
@classmethod
async def initPlantDBFile(cls) -> bool:
"""检查本地 plant.db 版本,如远程版本更新则重新下载
Returns:
bool: 是否为最新版或成功更新
"""
versionPath = os.path.join(os.path.dirname(g_sPlantPath), "version.json")
try:
with open(versionPath, encoding="utf-8") as f:
localVersion = json.load(f).get("version", 0)
except Exception as e:
logger.warning(f"读取本地版本失败默认版本为0: {e}")
localVersion = 0
remoteInfo = await cls.get("plant_version", name="版本检查")
remoteVersion = remoteInfo.get("version")
if remoteVersion is None:
logger.warning("获取远程版本失败")
return False
if float(remoteVersion) <= float(localVersion):
logger.debug("plant.db 已为最新版本")
return True
logger.warning(
f"发现新版本 plant.db远程: {remoteVersion} / 本地: {localVersion}),开始更新..."
)
# 先断开数据库连接
await g_pDBService.cleanup()
return await cls.downloadPlantDBFile(remoteVersion)
@classmethod
async def downloadPlantDBFile(cls, remoteVersion: float) -> bool:
"""下载最新版 plant.db 并更新本地 version.json
Args:
remoteVersion (float): 远程版本号
Returns:
bool: 是否下载并更新成功
"""
baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址")
savePath = os.path.dirname(g_sPlantPath)
success = await cls.download(
url=f"{baseUrl.rstrip('/')}:8998/file/plant.db",
savePath=savePath,
fileName="plantTemp.db",
)
if not success:
return False
# 重命名为 sign_in.json
g_pToolManager.renameFile(f"{savePath}/plantTemp.db", "plant.db")
versionPath = os.path.join(savePath, "version.json")
try:
with open(versionPath, "w", encoding="utf-8") as f:
json.dump({"version": remoteVersion}, f)
logger.debug("版本文件已更新")
except Exception as e:
logger.warning(f"写入版本文件失败: {e}")
return False
await g_pDBService.plant.init()
await g_pDBService.plant.downloadPlant()
return True
g_pRequestManager = CRequestManager() g_pRequestManager = CRequestManager()

Binary file not shown.

1
resource/db/version.json Normal file
View File

@ -0,0 +1 @@
{"version": 0.41}

BIN
resource/plant/南瓜/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
resource/plant/黄豆/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 822 B

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

90
tool.py
View File

@ -40,89 +40,17 @@ class CToolManager:
cleaned = username.strip() cleaned = username.strip()
# 允许的字符白名单(可自定义扩展) # 允许的字符白名单(可自定义扩展)
# fmt: off
safe_chars = { safe_chars = {
"_", "_", "-", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")",
"-", "+", "=", ".", ",", "~", "·", " ",
"!", "a","b","c","d","e","f","g","h","i","j","k","l","m",
"@", "n","o","p","q","r","s","t","u","v","w","x","y","z",
"#", "A","B","C","D","E","F","G","H","I","J","K","L","M",
"$", "N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
"%", "0","1","2","3","4","5","6","7","8","9",
"^",
"&",
"*",
"(",
")",
"+",
"=",
".",
",",
"~",
"·",
" ",
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
"t",
"u",
"v",
"w",
"x",
"y",
"z",
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
} }
# fmt: on
# 添加常用中文字符Unicode范围 # 添加常用中文字符Unicode范围
safe_chars.update(chr(c) for c in range(0x4E00, 0x9FFF + 1)) safe_chars.update(chr(c) for c in range(0x4E00, 0x9FFF + 1))