Compare commits

...

22 Commits
v1.4 ... main

Author SHA1 Message Date
术樱
ac495ad6e9
Merge pull request #11 from Mualamx/Beta
 添加数据库字段(是否为点券作物,点券作物单价);实现点券购买种子;我的种子查询页面添加点券单价。
2025-10-12 20:23:55 +08:00
Mualamx
f62008209f 🎨 '农场币换点券'改为'点券兑换',新增兑换点券阈值赠送规则。 2025-10-12 06:07:16 +08:00
Mualamx
34d4f698dd 添加数据库字段(是否为点券作物,点券作物单价);实现点券购买种子;我的种子查询页面添加点券单价。 2025-10-12 05:15:24 +08:00
Mualamx
4ab21b7f7b 🔥 删除后门 2025-10-12 03:35:14 +08:00
Mualamx
eb937dd309 实现农场币换点券、查看当前用户点券,偷偷加后门改农场币。 2025-10-10 13:32:45 +08:00
Mualamx
4841ceb871 Merge branch 'mx' into Beta 2025-10-10 10:59:04 +08:00
Mualamx
01fceaa4fe 实现农场币换点券、查看当前用户点券,偷偷加后门改农场币。 2025-10-10 10:58:46 +08:00
3d42c1d283 🐛 修复签到无法正确获取作物的BUG 2025-08-06 17:51:26 +08:00
aa2c5811de 🚑 修复新字段影响旧用户导致无法收获作物的BUG 2025-06-30 04:22:51 +08:00
69ddbe4669 🚑 修复无法正常铲除荒废作物的BUG 2025-06-30 03:43:44 +08:00
564d9858f5 🚑 修正了铲除作物会导致土地等级失效的BUG 2025-06-30 01:07:57 +08:00
ba06fd8967 🐛 修正部分指令忽视指令前缀的BUG 2025-06-29 23:35:38 +08:00
e48bdf03da 🚑 修复土地等级绘制出错的BUG 2025-06-29 22:38:51 +08:00
f4e192ff27 🐛 修复铲除荒废作物会清空地块的BUG 2025-06-29 21:31:09 +08:00
c6f24bc3c2 新增在线查漏补缺功能 2025-06-29 01:45:24 +08:00
ca6a414ba4 update .gitignore 2025-06-06 11:06:11 +08:00
ac13ab9280 🎨 将文本内容同一更新
🐛 修复偷菜无法正常计算的BUG
2025-06-06 10:56:22 +08:00
ba2ecf19fc 🚑 修复当作物阶段计算下标错误的BUG 2025-06-03 16:24:04 +08:00
776b408c9c Merge branch 'Beta' of https://github.com/Shu-Ying/zhenxun_plugin_farm into Beta 2025-06-03 15:32:27 +08:00
d42f0aa433 🐛 修复了迁移旧数据库时因枯萎作物报错的BUG 2025-06-03 15:32:14 +08:00
04a2cc7b4d 🐛 修复了作物无法正常绘制种子状态的BUG 2025-05-30 16:07:34 +08:00
475865f1e9 📝 更新了README文件 2025-05-30 12:19:05 +08:00
492 changed files with 2546 additions and 954 deletions

197
.gitignore vendored
View File

@ -1,3 +1,196 @@
__pycache__ /config/sign_in.json
./config/sign_in.json # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the enitre vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore

View File

@ -8,6 +8,9 @@
<a href="https://www.python.org"> <a href="https://www.python.org">
<img src="https://img.shields.io/badge/Python-3.10%2B-blue" alt="Python"> <img src="https://img.shields.io/badge/Python-3.10%2B-blue" alt="Python">
</a> </a>
<a href="https://github.com/astral-sh/ruff">
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" alt="ruff">
</a>
<a href="https://nonebot.dev/"> <a href="https://nonebot.dev/">
<img src="https://img.shields.io/badge/Nonebot-2.0.0%2B-black" alt="Nonebot"> <img src="https://img.shields.io/badge/Nonebot-2.0.0%2B-black" alt="Nonebot">
</a> </a>
@ -31,6 +34,7 @@
## 目录 ## 目录
- [真寻农场(zhenxun\_plugin\_farm)](#真寻农场zhenxun_plugin_farm) - [真寻农场(zhenxun\_plugin\_farm)](#真寻农场zhenxun_plugin_farm)
- [目录](#目录) - [目录](#目录)
- [农场界面](#农场界面)
- [如何安装](#如何安装) - [如何安装](#如何安装)
- [使用指令](#使用指令) - [使用指令](#使用指令)
- [更新日志(详细)](#更新日志详细) - [更新日志(详细)](#更新日志详细)
@ -43,6 +47,12 @@
--- ---
## 农场界面
![农场界面](./resource/1.png)
---
## 如何安装 ## 如何安装
方法一(推荐):在小真寻后台的插件商店下载即可<br> 方法一(推荐):在小真寻后台的插件商店下载即可<br>
@ -66,35 +76,34 @@
| 铲除 | 铲除荒废作物 | | | 铲除 | 铲除荒废作物 | |
| 我的作物 | 你的作物 | | | 我的作物 | 你的作物 | |
| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 | | 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 |
| @美波理 偷菜 | 偷别人的菜 | 每人每天只能偷5次 | | 偷菜 @美波理 | 偷别人的菜 | 每人每天只能偷5次 |
| 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为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", version="1.5.2",
commands=[Command(command="我的农场")], commands=[Command(command="我的农场")],
menu_type="群内小游戏", menu_type="群内小游戏",
configs=[ configs=[
@ -55,6 +58,12 @@ __plugin_meta__ = PluginMetadata(
help="金币兑换农场币的倍数 默认值为: 2倍", help="金币兑换农场币的倍数 默认值为: 2倍",
default_value="2", default_value="2",
), ),
RegisterConfig(
key="点券兑换倍数",
value="2000",
help="农场币兑换点券的倍数 比例为2000:1",
default_value="2000",
),
RegisterConfig( RegisterConfig(
key="手续费", key="手续费",
value="0.2", value="0.2",
@ -66,8 +75,8 @@ __plugin_meta__ = PluginMetadata(
value="http://diuse.work", value="http://diuse.work",
help="签到、交易行、活动等服务器地址", help="签到、交易行、活动等服务器地址",
default_value="http://diuse.work", default_value="http://diuse.work",
) ),
] ],
).to_dict(), ).to_dict(),
) )
driver = get_driver() driver = get_driver()
@ -84,6 +93,10 @@ async def start():
await g_pDBService.init() await g_pDBService.init()
# 检查作物文件是否缺失 or 更新
await g_pRequestManager.initPlantDBFile()
# 析构函数 # 析构函数
@driver.on_shutdown @driver.on_shutdown
async def shutdown(): async def shutdown():
@ -92,14 +105,10 @@ async def shutdown():
await g_pDBService.cleanup() await g_pDBService.cleanup()
@scheduler.scheduled_job( @scheduler.scheduled_job(trigger="cron", hour=4, minute=30, id="signInFile")
trigger="cron",
hour=0,
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

@ -1,10 +1,20 @@
import inspect import inspect
from nonebot.adapters import Event, MessageTemplate from nonebot.adapters import Event
from nonebot.rule import to_me from nonebot.rule import to_me
from nonebot_plugin_alconna import (Alconna, AlconnaMatch, AlconnaQuery, Args, from nonebot_plugin_alconna import (
Arparma, At, Match, MultiVar, Option, Alconna,
Query, Subcommand, on_alconna, store_true) AlconnaQuery,
Args,
At,
Match,
MultiVar,
Option,
Query,
Subcommand,
on_alconna,
store_true,
)
from nonebot_plugin_uninfo import Uninfo from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_waiter import waiter from nonebot_plugin_waiter import waiter
@ -12,114 +22,55 @@ from zhenxun.configs.config import BotConfig
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.message import MessageUtils from zhenxun.utils.message import MessageUtils
from .config import g_bSignStatus from .config import g_bSignStatus, g_sTranslation
from .dbService import g_pDBService from .dbService import g_pDBService
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
from .tool import g_pToolManager from .tool import g_pToolManager
async def isRegisteredByUid(uid: str) -> bool:
result = await g_pDBService.user.isUserExist(uid)
if not result:
await MessageUtils.build_message("尚未开通农场快at我发送 开通农场 开通吧").send()
return False
return True
diuse_register = on_alconna( diuse_register = on_alconna(
Alconna("开通农场"), Alconna("开通农场"),
priority=5, priority=5,
rule=to_me(), rule=to_me(),
block=True, block=True,
use_cmd_start=True,
) )
@diuse_register.handle() @diuse_register.handle()
async def handle_register(session: Uninfo): async def handle_register(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
user = await g_pDBService.user.getUserInfoByUid(uid) user = await g_pDBService.user.getUserInfoByUid(uid)
if user: if user:
await MessageUtils.build_message("🎉 您已经开通农场啦~").send(reply_to=True) await MessageUtils.build_message(g_sTranslation["register"]["repeat"]).send(
reply_to=True
)
return return
try: try:
raw_name = str(session.user.name) raw_name = str(session.user.name)
safe_name = sanitize_username(raw_name) safe_name = g_pToolManager.sanitize_username(raw_name)
# 初始化用户信息 # 初始化用户信息
success = await g_pDBService.user.initUserInfoByUid( success = await g_pDBService.user.initUserInfoByUid(
uid=uid, uid=uid, name=safe_name, exp=0, point=500
name=safe_name,
exp=0,
point=500
) )
msg = ( msg = (
"✅ 农场开通成功!\n💼 初始资金500农场币" g_sTranslation["register"]["success"].format(point=500)
if success if success
else "⚠️ 开通失败,请稍后再试" else g_sTranslation["register"]["error"]
) )
logger.info(f"用户注册 {'成功' if success else '失败'}{uid}") logger.info(f"用户注册 {'成功' if success else '失败'}{uid}")
except Exception as e: except Exception as e:
msg = "⚠️ 系统繁忙,请稍后再试" msg = g_sTranslation["register"]["error"]
logger.error(f"注册异常 | UID:{uid} | 错误:{str(e)}") logger.error(f"注册异常 | UID:{uid} | 错误:{e}")
await MessageUtils.build_message(msg).send(reply_to=True) await MessageUtils.build_message(msg).send(reply_to=True)
def sanitize_username(username: str, max_length: int = 15) -> str:
"""
安全处理用户名
功能
1. 移除首尾空白
2. 过滤危险字符
3. 转义单引号
4. 处理空值
5. 限制长度
"""
# 处理空值
if not username:
return "神秘农夫"
# 基础清洗
cleaned = username.strip()
# 允许的字符白名单(可自定义扩展)
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',
}
# 添加常用中文字符Unicode范围
safe_chars.update(chr(c) for c in range(0x4E00, 0x9FFF+1))
# 过滤危险字符
filtered = [
c if c in safe_chars or 0x4E00 <= ord(c) <= 0x9FFF
else ''
for c in cleaned
]
# 合并处理结果
safe_str = ''.join(filtered)
# 转义单引号(双重保障)
escaped = safe_str.replace("'", "''")
# 处理空结果
if not escaped:
return "神秘农夫"
# 长度限制
return escaped[:max_length]
diuse_farm = on_alconna( diuse_farm = on_alconna(
Alconna( Alconna(
@ -134,28 +85,35 @@ diuse_farm = on_alconna(
Subcommand("harvest", help_text="收获"), Subcommand("harvest", help_text="收获"),
Subcommand("eradicate", help_text="铲除"), Subcommand("eradicate", help_text="铲除"),
Subcommand("my-plant", help_text="我的作物"), Subcommand("my-plant", help_text="我的作物"),
Subcommand("lock-plant", Args["name?", str], help_text="作物加锁"),
Subcommand("unlock-plant", Args["name?", str], help_text="作物解锁"),
Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"), Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"),
Subcommand("stealing", Args["target?", At], help_text="偷菜"), Subcommand("stealing", Args["target?", At], help_text="偷菜"),
Subcommand("buy-point", Args["num?", int], help_text="购买农场币"), Subcommand("buy-point", Args["num?", int], help_text="购买农场币"),
#Subcommand("sell-point", Args["num?", int], help_text="转换金币") # Subcommand("sell-point", Args["num?", int], help_text="转换金币")
Subcommand("change-name", Args["name?", str], help_text="更改农场名"), Subcommand("change-name", Args["name?", str], help_text="更改农场名"),
Subcommand("sign-in", help_text="农场签到"), Subcommand("sign-in", help_text="农场签到"),
Subcommand("admin-up", Args["num?", int], help_text="农场下阶段"), Subcommand("admin-up", Args["num?", int], help_text="农场下阶段"),
Subcommand("point-to-vipPoint", Args["num?", int], help_text="点券兑换"),
Subcommand("my-vipPoint", help_text="我的点券"),
), ),
priority=5, priority=5,
block=True, block=True,
use_cmd_start=True,
) )
@diuse_farm.assign("$main") @diuse_farm.assign("$main")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
image = await g_pFarmManager.drawFarmByUid(uid) image = await g_pFarmManager.drawFarmByUid(uid)
await MessageUtils.build_message(image).send(reply_to=True) await MessageUtils.build_message(image).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"农场详述", "农场详述",
command="我的农场", command="我的农场",
@ -163,16 +121,20 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("detail") @diuse_farm.assign("detail")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
info = await g_pFarmManager.drawDetailFarmByUid(uid) info = await g_pFarmManager.drawDetailFarmByUid(uid)
await MessageUtils.alc_forward_msg([info], session.self_id, BotConfig.self_nickname).send() await MessageUtils.alc_forward_msg(
[info], session.self_id, BotConfig.self_nickname
).send()
diuse_farm.shortcut( diuse_farm.shortcut(
"我的农场币", "我的农场币",
@ -181,16 +143,20 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("my-point") @diuse_farm.assign("my-point")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
point = await g_pDBService.user.getUserPointByUid(uid) point = await g_pDBService.user.getUserPointByUid(uid)
if point < 0: if point < 0:
await MessageUtils.build_message("尚未开通农场快at我发送 开通农场 开通吧").send() await MessageUtils.build_message(g_sTranslation["basic"]["notFarm"]).send()
return False return False
await MessageUtils.build_message(f"你的当前农场币为: {point}").send(reply_to=True) await MessageUtils.build_message(
g_sTranslation["basic"]["point"].format(point=point)
).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"种子商店(.*?)", "种子商店(.*?)",
@ -199,11 +165,12 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("seed-shop") @diuse_farm.assign("seed-shop")
async def _(session: Uninfo, res: Match[tuple[str, ...]]): async def _(session: Uninfo, res: Match[tuple[str, ...]]):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
if res.result is inspect._empty: if res.result is inspect._empty:
@ -221,7 +188,12 @@ async def _(session: Uninfo, res: Match[tuple[str, ...]]):
else: else:
filterKey = first filterKey = first
if len(raw) >= 2 and raw[1] is not None and isinstance(raw[1], str) and raw[1].isdigit(): if (
len(raw) >= 2
and raw[1] is not None
and isinstance(raw[1], str)
and raw[1].isdigit()
):
page = int(raw[1]) page = int(raw[1])
if filterKey is None: if filterKey is None:
@ -231,6 +203,7 @@ async def _(session: Uninfo, res: Match[tuple[str, ...]]):
await MessageUtils.build_message(image).send() await MessageUtils.build_message(image).send()
diuse_farm.shortcut( diuse_farm.shortcut(
"购买种子(?P<name>.*?)", "购买种子(?P<name>.*?)",
command="我的农场", command="我的农场",
@ -238,21 +211,25 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("buy-seed") @diuse_farm.assign("buy-seed")
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1)): async def _(
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1)
):
if not name.available: if not name.available:
await MessageUtils.build_message( await MessageUtils.build_message(g_sTranslation["buySeed"]["notSeed"]).finish(
"请在指令后跟需要购买的种子名称" reply_to=True
).finish(reply_to=True) )
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pShopManager.buySeed(uid, name.result, num.result) result = await g_pShopManager.buySeed(uid, name.result, num.result)
await MessageUtils.build_message(result).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"我的种子", "我的种子",
command="我的农场", command="我的农场",
@ -260,16 +237,18 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("my-seed") @diuse_farm.assign("my-seed")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pFarmManager.getUserSeedByUid(uid) result = await g_pFarmManager.getUserSeedByUid(uid)
await MessageUtils.build_message(result).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"播种(?P<name>.*?)", "播种(?P<name>.*?)",
command="我的农场", command="我的农场",
@ -277,16 +256,19 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("sowing") @diuse_farm.assign("sowing")
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)): async def _(
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)
):
if not name.available: if not name.available:
await MessageUtils.build_message( await MessageUtils.build_message(g_sTranslation["sowing"]["notSeed"]).finish(
"请在指令后跟需要播种的种子名称" reply_to=True
).finish(reply_to=True) )
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pFarmManager.sowing(uid, name.result, num.result) result = await g_pFarmManager.sowing(uid, name.result, num.result)
@ -300,16 +282,18 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("harvest") @diuse_farm.assign("harvest")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pFarmManager.harvest(uid) result = await g_pFarmManager.harvest(uid)
await MessageUtils.build_message(result).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"铲除", "铲除",
command="我的农场", command="我的农场",
@ -317,11 +301,12 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("eradicate") @diuse_farm.assign("eradicate")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pFarmManager.eradicate(uid) result = await g_pFarmManager.eradicate(uid)
@ -335,47 +320,55 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("my-plant") @diuse_farm.assign("my-plant")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pFarmManager.getUserPlantByUid(uid) result = await g_pFarmManager.getUserPlantByUid(uid)
await MessageUtils.build_message(result).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
reclamation = on_alconna( diuse_farm.shortcut(
Alconna("开垦"), "作物加锁(?P<name>)",
priority=5, command="我的农场",
block=True, arguments=["lock-plant", "{name}"],
prefix=True,
) )
@reclamation.handle()
async def _(session: Uninfo): @diuse_farm.assign("lock-plant")
async def _(session: Uninfo, name: Match[str]):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
condition = await g_pFarmManager.reclamationCondition(uid) result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 1)
condition += "\n 回复是将执行开垦" await MessageUtils.build_message(result).send(reply_to=True)
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) diuse_farm.shortcut(
if resp is None: "作物解锁(?P<name>)",
await MessageUtils.build_message("等待超时").send(reply_to=True) command="我的农场",
return arguments=["unlock-plant", "{name}"],
if not resp == "": prefix=True,
)
@diuse_farm.assign("unlock-plant")
async def _(session: Uninfo, name: Match[str]):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return return
res = await g_pFarmManager.reclamation(uid) result = await g_pFarmManager.lockUserPlantByUid(uid, name.result, 0)
await MessageUtils.build_message(res).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"出售作物(?P<name>.*?)", "出售作物(?P<name>.*?)",
@ -384,16 +377,56 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("sell-plant") @diuse_farm.assign("sell-plant")
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)): async def _(
session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1)
):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pShopManager.sellPlantByUid(uid, name.result, num.result) result = await g_pShopManager.sellPlantByUid(uid, name.result, num.result)
await MessageUtils.build_message(result).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
reclamation = on_alconna(
Alconna("开垦"),
priority=5,
block=True,
use_cmd_start=True,
)
@reclamation.handle()
async def _(session: Uninfo):
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return
condition = await g_pFarmManager.reclamationCondition(uid)
condition += f"\n{g_sTranslation['reclamation']['confirm']}"
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["reclamation"]["timeOut"]).send(
reply_to=True
)
return
if not resp == "":
return
res = await g_pFarmManager.reclamation(uid)
await MessageUtils.build_message(res).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"偷菜", "偷菜",
command="我的农场", command="我的农场",
@ -401,26 +434,32 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("stealing") @diuse_farm.assign("stealing")
async def _(session: Uninfo, target: Match[At]): async def _(session: Uninfo, target: Match[At]):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
if not target.available: if not target.available:
await MessageUtils.build_message("请在指令后跟需要at的人").finish(reply_to=True) await MessageUtils.build_message(g_sTranslation["stealing"]["noTarget"]).finish(
reply_to=True
)
tar = target.result tar = target.result
result = await g_pDBService.user.isUserExist(tar.target) result = await g_pDBService.user.isUserExist(tar.target)
if not result: if not result:
await MessageUtils.build_message("目标尚未开通农场快邀请ta开通吧").send() await MessageUtils.build_message(
g_sTranslation["stealing"]["targetNotFarm"]
).send()
return None return None
result = await g_pFarmManager.stealing(uid, tar.target) result = await g_pFarmManager.stealing(uid, tar.target)
await MessageUtils.build_message(result).send(reply_to=True) await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut( diuse_farm.shortcut(
"购买农场币(.*?)", "购买农场币(.*?)",
command="我的农场", command="我的农场",
@ -428,16 +467,17 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("buy-point") @diuse_farm.assign("buy-point")
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)): async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
if num.result <= 0: if num.result <= 0:
await MessageUtils.build_message( await MessageUtils.build_message("请在指令后跟需要购买农场币的数量").finish(
"请在指令后跟需要购买农场币的数量" reply_to=True
).finish(reply_to=True) )
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
result = await g_pFarmManager.buyPointByUid(uid, num.result) result = await g_pFarmManager.buyPointByUid(uid, num.result)
@ -451,30 +491,38 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("change-name") @diuse_farm.assign("change-name")
async def _(session: Uninfo, name: Match[str]): async def _(session: Uninfo, name: Match[str]):
if not name.available: if not name.available:
await MessageUtils.build_message( await MessageUtils.build_message(g_sTranslation["changeName"]["noName"]).finish(
"请在指令后跟需要更改的农场名" reply_to=True
).finish(reply_to=True) )
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
safeName = sanitize_username(name.result) safeName = g_pToolManager.sanitize_username(name.result)
if safeName == "神秘农夫": if safeName == "神秘农夫":
await MessageUtils.build_message("农场名不支持特殊符号!").send(reply_to=True) await MessageUtils.build_message(g_sTranslation["changeName"]["error"]).send(
reply_to=True
)
return return
result = await g_pDBService.user.updateUserNameByUid(uid, safeName) result = await g_pDBService.user.updateUserNameByUid(uid, safeName)
if result == True: if result:
await MessageUtils.build_message("更新农场名成功!").send(reply_to=True) await MessageUtils.build_message(g_sTranslation["changeName"]["success"]).send(
reply_to=True
)
else: else:
await MessageUtils.build_message("更新农场名失败!").send(reply_to=True) await MessageUtils.build_message(g_sTranslation["changeName"]["error1"]).send(
reply_to=True
)
diuse_farm.shortcut( diuse_farm.shortcut(
"农场签到", "农场签到",
@ -483,16 +531,17 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("sign-in") @diuse_farm.assign("sign-in")
async def _(session: Uninfo): async def _(session: Uninfo):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
#判断签到是否正常加载 # 判断签到是否正常加载
if not g_bSignStatus: if not g_bSignStatus:
await MessageUtils.build_message("签到功能异常!").send() await MessageUtils.build_message(g_sTranslation["signIn"]["error"]).send()
return return
@ -500,39 +549,89 @@ async def _(session: Uninfo):
message = "" message = ""
status = await g_pDBService.userSign.sign(uid, toDay.strftime("%Y-%m-%d")) status = await g_pDBService.userSign.sign(uid, toDay.strftime("%Y-%m-%d"))
#如果完成签到 # 如果完成签到
if status == 1 or status == 2: if status == 1 or status == 2:
#获取签到总天数 # 获取签到总天数
signDay = await g_pDBService.userSign.getUserSignCountByDate(uid, toDay.strftime("%Y-%m")) signDay = await g_pDBService.userSign.getUserSignCountByDate(
exp, point = await g_pDBService.userSign.getUserSignRewardByDate(uid, toDay.strftime("%Y-%m-%d")) uid, toDay.strftime("%Y-%m")
)
exp, point = await g_pDBService.userSign.getUserSignRewardByDate(
uid, toDay.strftime("%Y-%m-%d")
)
message += f"签到成功!累计签到天数:{signDay}\n获得经验{exp},获得金币{point}" message += g_sTranslation["signIn"]["success"].format(
day=signDay, exp=exp, num=point
)
reward = g_pJsonManager.m_pSign['continuou'].get(f"{signDay}", None) reward = g_pJsonManager.m_pSign["continuou"].get(f"{signDay}", None)
if reward: if reward:
extraPoint = reward.get('point', 0) extraPoint = reward.get("point", 0)
extraExp = reward.get('exp', 0) extraExp = reward.get("exp", 0)
plant = reward.get('plant', {}) plant = reward.get("plant", {})
message += f"\n\n成功领取累计签到奖励:\n额外获得经验{extraExp},额外获得金币{extraPoint}" message += g_sTranslation["signIn"]["grandTotal"].format(
exp=extraExp, num=extraPoint
)
vipPoint = reward.get('vipPoint', 0) vipPoint = reward.get("vipPoint", 0)
if vipPoint > 0: if vipPoint > 0:
message += f",额外获得点券{vipPoint}" message += g_sTranslation["signIn"]["grandTotal1"].format(num=vipPoint)
if plant: if plant:
for key, value in plant.items(): for key, value in plant.items():
message += f"\n获得{key}种子 * {value}" message += g_sTranslation["signIn"]["grandTotal2"].format(
name=key, num=value
)
else: else:
message = "签到失败!未知错误" message = g_sTranslation["signIn"]["error1"]
await MessageUtils.build_message(message).send() await MessageUtils.build_message(message).send()
# 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,
use_cmd_start=True,
)
@soil_upgrade.handle()
async def _(session: Uninfo, index: Query[int] = AlconnaQuery("index", 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)
if not condition.startswith("将土地升级至:"):
return
@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="我的农场",
@ -540,11 +639,57 @@ diuse_farm.shortcut(
prefix=True, prefix=True,
) )
@diuse_farm.assign("admin-up") @diuse_farm.assign("admin-up")
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)): async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
uid = str(session.user.id) uid = str(session.user.id)
if not await isRegisteredByUid(uid): if not await g_pToolManager.isRegisteredByUid(uid):
return return
await g_pDBService.userSoil.nextPhase(uid, num.result) await g_pDBService.userSoil.nextPhase(uid, num.result)
diuse_farm.shortcut(
"点券兑换(.*?)",
command="我的农场",
arguments=["point-to-vipPoint"],
prefix=True,
)
@diuse_farm.assign("point-to-vipPoint")
async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
if num.result <= 0:
await MessageUtils.build_message("请在指令后跟需要购买点券的数量").finish(
reply_to=True
)
uid = str(session.user.id)
if not await g_pToolManager.isRegisteredByUid(uid):
return
result = await g_pFarmManager.pointToVipPointByUid(uid, num.result)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"我的点券",
command="我的农场",
arguments=["my-vipPoint"],
prefix=True,
)
@diuse_farm.assign("my-vipPoint")
async def _(session: Uninfo):
uid = str(session.user.id)
vipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
if not await g_pToolManager.isRegisteredByUid(uid):
return
await MessageUtils.build_message(
g_sTranslation["basic"]["vipPoint"].format(vipPoint=vipPoint)
).send(reply_to=True)

119
config.py
View File

@ -2,15 +2,132 @@ from pathlib import Path
from zhenxun.configs.path_config import DATA_PATH from zhenxun.configs.path_config import DATA_PATH
# 签到状态
g_bSignStatus = True
# 是否处于Debug模式
g_bIsDebug = False g_bIsDebug = False
# 数据库文件目录
g_sDBPath = DATA_PATH / "farm_db" g_sDBPath = DATA_PATH / "farm_db"
# 数据库文件路径
g_sDBFilePath = DATA_PATH / "farm_db/farm.db" g_sDBFilePath = DATA_PATH / "farm_db/farm.db"
# 农场资源文件目录
g_sResourcePath = Path(__file__).resolve().parent / "resource" g_sResourcePath = Path(__file__).resolve().parent / "resource"
# 农场作物数据库
g_sPlantPath = g_sResourcePath / "db/plant.db" g_sPlantPath = g_sResourcePath / "db/plant.db"
# 农场配置文件目录
g_sConfigPath = Path(__file__).resolve().parent / "config" g_sConfigPath = Path(__file__).resolve().parent / "config"
# 农场签到文件路径
g_sSignInPath = g_sConfigPath / "sign_in.json" g_sSignInPath = g_sConfigPath / "sign_in.json"
g_bSignStatus = True # 土地等级上限
g_iSoilLevelMax = 3
# 农场同一文本
g_sTranslation = {
"basic": {
"notFarm": "尚未开通农场快at我发送 开通农场 开通吧 🌱🚜",
"point": "你的当前农场币为: {point} 🌾💰",
"vipPoint": "你的当前点券为: {vipPoint} 🌾💰",
},
"register": {
"success": "✅ 农场开通成功!\n💼 初始资金:{point}农场币 🥳🎉",
"repeat": "🎉 您已经开通农场啦~ 😄",
"error": "⚠️ 开通失败,请稍后再试 💔",
},
"buySeed": {
"notSeed": "🌱 请在指令后跟需要购买的种子名称",
"notNum": "❗️ 请输入购买数量!",
"noLevel": "🔒 你的等级不够哦,努努力吧 💪",
"noPoint": "💰 你的农场币不够哦~ 快速速氪金吧!💸",
"noVipPoint": "💰 你的点券不够哦~ 快速速氪金吧!💸",
"success": "✅ 成功购买{name},花费{total}农场币,剩余{point}农场币 🌾",
"vipSuccess": "✅ 成功购买{name},花费{total}点券,剩余{point}点券 🌾",
"errorSql": "❌ 购买失败,执行数据库错误!🛑",
"error": "❌ 购买出错!请检查需购买的种子名称!🔍",
},
"sowing": {
"notSeed": "🌱 请在指令后跟需要播种的种子名称",
"noSeed": "❌ 没有在你的仓库发现{name}种子,快去买点吧!🛒",
"noNum": "⚠️ 播种失败!仓库中的{name}种子数量不足,当前剩余{num}个种子 🍂",
"success": "✅ 播种{name}成功!仓库剩余{num}个种子 🌱",
"success2": "✅ 播种数量超出开垦土地数量,已将可播种土地成功播种{name}!仓库剩余{num}个种子 🌾",
"error": "❌ 播种失败,请稍后重试!⏳",
},
"harvest": {
"append": "🌾 收获作物:{name},数量为:{num},经验为:{exp}",
"exp": "✨ 累计获得经验:{exp} 📈",
"no": "🤷‍♂️ 没有可收获的作物哦~ 不要试图拔苗助长 🚫",
"error": "❌ 收获失败,请稍后重试!⏳",
},
"eradicate": {
"success": "🗑️ 成功铲除荒废作物,累计获得经验:{exp}",
"error": "❌ 没有可以铲除的作物 🚜",
},
"reclamation": {
"confirm": "⚠️ 回复“是”将执行开垦 ⛏️",
"timeOut": "⏰ 等待开垦回复超时,请重试",
"perfect": "🌟 你已经开垦了全部土地 🎉",
"next": "🔜 下次开垦所需条件:等级:{level},农场币:{num} 💰",
"next2": "🔜 下次开垦所需条件:等级:{level},农场币:{num},物品:{item} 📦",
"nextLevel": "📈 当前用户等级{level},升级所需等级为{next}",
"noNum": "💰 当前用户农场币不足,升级所需农场币为{num} 💸",
"success": "✅ 开垦土地成功!🌱",
"error": "❌ 获取开垦土地条件失败!",
"error1": "❌ 执行开垦失败!",
"error2": "❌ 未知错误{e}💥",
},
"sellPlant": {
"no": "🤷‍♀️ 你仓库没有可以出售的作物 🌾",
"success": "💰 成功出售所有作物,获得农场币:{point},当前农场币:{num} 🎉",
"success1": "💰 成功出售{name},获得农场币:{point},当前农场币:{num} 🥳",
"error": "❌ 出售作物{name}出错:仓库中不存在该作物 🚫",
"error1": "❌ 出售作物{name}出错:数量不足 ⚠️",
},
"stealing": {
"noTarget": "🎯 请在指令后跟需要at的人 👤",
"targetNotFarm": "🚜 目标尚未开通农场快邀请ta开通吧 😉",
"max": "❌ 你今天可偷次数到达上限啦,手下留情吧 🙏",
"info": "🤫 成功偷到作物:{name},数量为:{num} 🍒",
"noPlant": "🌱 目标没有作物可以被偷 🌾",
"repeat": "🚫 你已经偷过目标啦,请手下留情 🙏",
},
"changeName": {
"noName": "✏️ 请在指令后跟需要更改的农场名",
"success": "✅ 更新农场名成功!🎉",
"error": "❌ 农场名不支持特殊符号!🚫",
"error1": "❌ 更新农场名失败!💔",
},
"signIn": {
"success": "📝 签到成功!累计签到天数:{day}\n🎁 获得经验{exp},获得金币{num} 💰",
"grandTotal": "\n🎉 成功领取累计签到奖励:\n✨ 额外获得经验{exp},额外获得金币{num} 🥳",
"grandTotal1": ",额外获得点券{num} 🎫",
"grandTotal2": "\n🌱 获得{name}种子 * {num} 🌟",
"error": "❗️ 签到功能异常!",
"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%",
},
"lockPlant": {
"noPlant": "很抱歉,你的作物仓库中没有该作物",
"lockPlant": "加锁成功,现在{name}不会被一键售卖了",
"unlockPlant": "解锁成功,现在{name}会被一键售卖了",
"error": "未知错误",
},
}

View File

@ -1,78 +0,0 @@
{
"date": "202505",
"exp_max": 50,
"exp_min": 5,
"point_max": 2000,
"point_min": 200,
"continuou":
{
"1":
{
"point": 3000,
"exp": 20
},
"3":
{
"point": 5000,
"exp": 25,
"plant":
{
"胡萝卜": 3
}
},
"5":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"7":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"10":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"15":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"20":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
},
"25":
{
"point": 7000,
"exp": 50,
"plant":
{
"胡萝卜": 3
}
}
}
}

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,8 +1,7 @@
import math
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
@ -46,7 +45,7 @@ class CSqlManager:
@classmethod @classmethod
async def getTableInfo(cls, tableName: str) -> list: async def getTableInfo(cls, tableName: str) -> list:
if not re.match(r'^[A-Za-z_][A-Za-z0-9_]*$', tableName): if not re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", tableName):
raise ValueError(f"Illegal table name: {tableName}") raise ValueError(f"Illegal table name: {tableName}")
try: try:
cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")') cursor = await cls.m_pDB.execute(f'PRAGMA table_info("{tableName}")')
@ -70,7 +69,7 @@ class CSqlManager:
""" """
info = await cls.getTableInfo(tableName) info = await cls.getTableInfo(tableName)
existing = {col['name']: col['type'].upper() for col in info} existing = {col["name"]: col["type"].upper() for col in info}
desired = {k: v.upper() for k, v in columns.items() if k != "PRIMARY KEY"} desired = {k: v.upper() for k, v in columns.items() if k != "PRIMARY KEY"}
primaryKey = columns.get("PRIMARY KEY", "") primaryKey = columns.get("PRIMARY KEY", "")
@ -83,7 +82,9 @@ class CSqlManager:
toAdd = [k for k in desired if k not in existing] toAdd = [k for k in desired if k not in existing]
toRemove = [k for k in existing if k not in desired] toRemove = [k for k in existing if k not in desired]
typeMismatch = [k for k in desired if k in existing and existing[k] != desired[k]] typeMismatch = [
k for k in desired if k in existing and existing[k] != desired[k]
]
if toAdd and not toRemove and not typeMismatch: if toAdd and not toRemove and not typeMismatch:
for col in toAdd: for col in toAdd:
@ -102,11 +103,18 @@ 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(f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";') await cls.m_pDB.execute(
f'ALTER TABLE "{tmpTable}" RENAME TO "{tableName}";'
)
return True return True
@classmethod @classmethod
@ -131,4 +139,5 @@ class CSqlManager:
logger.warning(f"数据库语句执行出错: {command}", e=e) logger.warning(f"数据库语句执行出错: {command}", e=e)
return False return False
g_pSqlManager = CSqlManager() g_pSqlManager = CSqlManager()

View File

@ -1,14 +1,13 @@
import ast
import os
import re
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from unittest import result 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:
@ -29,7 +28,9 @@ class CPlantManager:
_ = os.path.exists(g_sPlantPath) _ = os.path.exists(g_sPlantPath)
if g_bIsDebug: if g_bIsDebug:
cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath.parent / "plant-test.db")) cls.m_pDB = await aiosqlite.connect(
str(g_sPlantPath.parent / "plant-test.db")
)
else: else:
cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath)) cls.m_pDB = await aiosqlite.connect(str(g_sPlantPath))
@ -73,7 +74,6 @@ class CPlantManager:
logger.warning(f"数据库语句执行出错: {command}", e=e) logger.warning(f"数据库语句执行出错: {command}", e=e)
return False return False
@classmethod @classmethod
async def getPlantByName(cls, name: str) -> dict | None: async def getPlantByName(cls, name: str) -> dict | None:
"""根据作物名称查询记录 """根据作物名称查询记录
@ -113,14 +113,17 @@ class CPlantManager:
if not row: if not row:
return [] return []
phase = row[0].split(',') phase = row[0].split(",")
seen = set() seen = set()
result = [] result = []
for x in phase: for x in phase:
if x not in seen: num = int(x)
seen.add(int(x))
result.append(int(x)) if num not in seen:
seen.add(num)
result.append(num)
return result return result
except Exception as e: except Exception as e:
@ -146,9 +149,9 @@ class CPlantManager:
if not row: if not row:
return -1 return -1
phase = row[0].split(',') phase = row[0].split(",")
#去重 # 去重
seen = set() seen = set()
result = [] result = []
for x in phase: for x in phase:
@ -181,7 +184,7 @@ class CPlantManager:
if not row: if not row:
return -1 return -1
phase = row[0].split(',') phase = row[0].split(",")
again = phase[-1] - phase[3] / 60 / 60 again = phase[-1] - phase[3] / 60 / 60
return again return again
@ -238,9 +241,53 @@ class CPlantManager:
async def listPlants(cls) -> list[dict]: async def listPlants(cls) -> list[dict]:
"""查询所有作物记录""" """查询所有作物记录"""
try: try:
async with cls.m_pDB.execute("SELECT * FROM plant ORDER BY level") as cursor: async with cls.m_pDB.execute(
"SELECT * FROM plant ORDER BY level"
) as cursor:
rows = await cursor.fetchall() rows = await cursor.fetchall()
return [dict(r) for r in rows] return [dict(r) for r in rows]
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

@ -1,6 +1,4 @@
import math import math
from typing import List, Union
from unittest import result
from zhenxun.services.log import logger from zhenxun.services.log import logger
@ -13,19 +11,21 @@ class CUserDB(CSqlManager):
async def initDB(cls): async def initDB(cls):
"""初始化用户表结构确保user表存在且字段完整""" """初始化用户表结构确保user表存在且字段完整"""
userInfo = { userInfo = {
"uid": "TEXT PRIMARY KEY", #用户Uid "uid": "TEXT PRIMARY KEY", # 用户Uid
"name": "TEXT NOT NULL", #农场名称 "name": "TEXT NOT NULL", # 农场名称
"exp": "INTEGER DEFAULT 0", #经验值 "exp": "INTEGER DEFAULT 0", # 经验值
"point": "INTEGER DEFAULT 0", #金币 "point": "INTEGER DEFAULT 0", # 金币
"vipPoint": "INTEGER DEFAULT 0", #点券 "vipPoint": "INTEGER DEFAULT 0", # 点券
"soil": "INTEGER DEFAULT 3", #解锁土地数量 "soil": "INTEGER DEFAULT 3", # 解锁土地数量
"stealTime": "TEXT DEFAULT ''", #偷菜时间字符串 "stealTime": "TEXT DEFAULT ''", # 偷菜时间字符串
"stealCount": "INTEGER DEFAULT 0" #剩余偷菜次数 "stealCount": "INTEGER DEFAULT 0", # 剩余偷菜次数
} }
await cls.ensureTableSchema("user", userInfo) await cls.ensureTableSchema("user", userInfo)
@classmethod @classmethod
async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 500) -> Union[bool, str]: async def initUserInfoByUid(
cls, uid: str, name: str = "", exp: int = 0, point: int = 500
) -> bool | str:
"""初始化用户信息,包含初始偷菜时间字符串与次数 """初始化用户信息,包含初始偷菜时间字符串与次数
Args: Args:
@ -35,9 +35,9 @@ class CUserDB(CSqlManager):
point (int): 农场币 point (int): 农场币
Returns: Returns:
Union[bool, str]: False 表示失败字符串表示成功信息 bool | str: False 表示失败字符串表示成功信息
""" """
nowStr = g_pToolManager.dateTime().date().today().strftime('%Y-%m-%d') nowStr = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d")
sql = ( sql = (
f"INSERT INTO user (uid, name, exp, point, soil, stealTime, stealCount) " f"INSERT INTO user (uid, name, exp, point, soil, stealTime, stealCount) "
f"VALUES ({uid}, '{name}', {exp}, {point}, 3, '{nowStr}', 5)" f"VALUES ({uid}, '{name}', {exp}, {point}, 3, '{nowStr}', 5)"
@ -51,11 +51,11 @@ class CUserDB(CSqlManager):
return False return False
@classmethod @classmethod
async def getAllUsers(cls) -> List[str]: async def getAllUsers(cls) -> list[str]:
"""获取所有用户UID列表 """获取所有用户UID列表
Returns: Returns:
List[str]: 用户UID列表 list[str]: 用户UID列表
""" """
cursor = await cls.m_pDB.execute("SELECT uid FROM user") cursor = await cls.m_pDB.execute("SELECT uid FROM user")
rows = await cursor.fetchall() rows = await cursor.fetchall()
@ -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
@ -395,8 +396,7 @@ class CUserDB(CSqlManager):
return "" return ""
try: try:
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT stealTime FROM user WHERE uid = ?", (uid, "SELECT stealTime FROM user WHERE uid = ?", (uid,)
)
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
return row[0] if row and row[0] else "" return row[0] if row and row[0] else ""
@ -451,11 +451,14 @@ class CUserDB(CSqlManager):
return -1 return -1
@classmethod @classmethod
async def updateStealCountByUid(cls, uid: str, stealCount: int) -> bool: async def updateStealCountByUid(
cls, uid: str, stealTime: str, stealCount: int
) -> bool:
"""根据用户Uid更新剩余偷菜次数 """根据用户Uid更新剩余偷菜次数
Args: Args:
uid (str): 用户Uid uid (str): 用户Uid
stealTime (str): 偷菜日期
stealCount (int): 新剩余偷菜次数 stealCount (int): 新剩余偷菜次数
Returns: Returns:
@ -467,7 +470,8 @@ class CUserDB(CSqlManager):
try: try:
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE user SET stealCount = ? WHERE uid = ?", (stealCount, uid) "UPDATE user SET stealTime = ?, stealCount = ? WHERE uid = ?",
(stealTime, stealCount, uid),
) )
return True return True
except Exception as e: except Exception as e:

View File

@ -1,5 +1,3 @@
from typing import Optional
from zhenxun.services.log import logger from zhenxun.services.log import logger
from .database import CSqlManager from .database import CSqlManager
@ -9,16 +7,16 @@ class CUserItemDB(CSqlManager):
@classmethod @classmethod
async def initDB(cls): async def initDB(cls):
userItem = { userItem = {
"uid": "TEXT NOT NULL", #用户Uid "uid": "TEXT NOT NULL", # 用户Uid
"item": "TEXT NOT NULL", #物品名称 "item": "TEXT NOT NULL", # 物品名称
"count": "INTEGER NOT NULL DEFAULT 0", #数量 "count": "INTEGER NOT NULL DEFAULT 0", # 数量
"PRIMARY KEY": "(uid, item)" "PRIMARY KEY": "(uid, item)",
} }
await cls.ensureTableSchema("userItem", userItem) await cls.ensureTableSchema("userItem", userItem)
@classmethod @classmethod
async def getUserItemByName(cls, uid: str, item: str) -> Optional[int]: async def getUserItemByName(cls, uid: str, item: str) -> int | None:
"""根据道具名称查询某一项数量 """根据道具名称查询某一项数量
Args: Args:
@ -32,13 +30,12 @@ class CUserItemDB(CSqlManager):
return None return None
try: try:
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT count FROM userItem WHERE uid = ? AND item = ?", "SELECT count FROM userItem WHERE uid = ? AND item = ?", (uid, item)
(uid, item)
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
return row[0] if row else None return row[0] if row else None
except Exception as e: except Exception as e:
logger.warning(f"getUserItemByName查询失败", e=e) logger.warning("getUserItemByName查询失败", e=e)
return None return None
@classmethod @classmethod
@ -55,13 +52,12 @@ class CUserItemDB(CSqlManager):
return {} return {}
try: try:
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
"SELECT item, count FROM userItem WHERE uid = ?", "SELECT item, count FROM userItem WHERE uid = ?", (uid,)
(uid,)
) )
rows = await cursor.fetchall() rows = await cursor.fetchall()
return {row["item"]: row["count"] for row in rows} return {row["item"]: row["count"] for row in rows}
except Exception as e: except Exception as e:
logger.warning(f"getUserItemByUid查询失败", e=e) logger.warning("getUserItemByUid查询失败", e=e)
return {} return {}
@classmethod @classmethod
@ -80,12 +76,11 @@ class CUserItemDB(CSqlManager):
try: try:
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?", "DELETE FROM userItem WHERE uid = ? AND item = ?", (uid, item)
(uid, item)
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"deleteUserItemByName失败", e=e) logger.warning("deleteUserItemByName失败", e=e)
return False return False
@classmethod @classmethod
@ -106,17 +101,16 @@ class CUserItemDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
if count <= 0: if count <= 0:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?", "DELETE FROM userItem WHERE uid = ? AND item = ?", (uid, item)
(uid, item)
) )
else: else:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
(count, uid, item) (count, uid, item),
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"updateUserItemByName失败", e=e) logger.warning("updateUserItemByName失败", e=e)
return False return False
@classmethod @classmethod
@ -136,8 +130,7 @@ class CUserItemDB(CSqlManager):
try: try:
async with cls._transaction(): async with cls._transaction():
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT count FROM userItem WHERE uid = ? AND item = ?", "SELECT count FROM userItem WHERE uid = ? AND item = ?", (uid, item)
(uid, item)
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
@ -146,20 +139,20 @@ class CUserItemDB(CSqlManager):
if newCount <= 0: if newCount <= 0:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?", "DELETE FROM userItem WHERE uid = ? AND item = ?",
(uid, item) (uid, item),
) )
else: else:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
(newCount, uid, item) (newCount, uid, item),
) )
else: else:
if count > 0: if count > 0:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)", "INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)",
(uid, item, count) (uid, item, count),
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"addUserItemByUid失败", e=e) logger.warning("addUserItemByUid失败", e=e)
return False return False

View File

@ -1,5 +1,3 @@
from typing import Dict, Optional
from zhenxun.services.log import logger from zhenxun.services.log import logger
from .database import CSqlManager from .database import CSqlManager
@ -9,10 +7,11 @@ class CUserPlantDB(CSqlManager):
@classmethod @classmethod
async def initDB(cls): async def initDB(cls):
userPlant = { userPlant = {
"uid": "TEXT NOT NULL", #用户Uid "uid": "TEXT NOT NULL", # 用户Uid
"plant": "TEXT NOT NULL", #作物名称 "plant": "TEXT NOT NULL", # 作物名称
"count": "INTEGER NOT NULL DEFAULT 0", #数量 "count": "INTEGER NOT NULL DEFAULT 0", # 数量
"PRIMARY KEY": "(uid, plant)" "isLock": "INTEGER NOT NULL DEFAULT 0", # 是否上锁 0=没有非0=有
"PRIMARY KEY": "(uid, plant)",
} }
await cls.ensureTableSchema("userPlant", userPlant) await cls.ensureTableSchema("userPlant", userPlant)
@ -31,33 +30,33 @@ class CUserPlantDB(CSqlManager):
""" """
try: try:
async with cls._transaction(): async with cls._transaction():
#检查是否已存在该作物 # 检查是否已存在该作物
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?", "SELECT count FROM userPlant WHERE uid = ? AND plant = ?",
(uid, plant) (uid, plant),
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
if row: if row:
#如果作物已存在,则更新数量 # 如果作物已存在,则更新数量
new_count = row[0] + count new_count = row[0] + count
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?", "UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
(new_count, uid, plant) (new_count, uid, plant),
) )
else: else:
#如果作物不存在,则插入新记录 # 如果作物不存在,则插入新记录
await cls.m_pDB.execute( await cls.m_pDB.execute(
"INSERT INTO userPlant (uid, plant, count) VALUES (?, ?, ?)", "INSERT INTO userPlant (uid, plant, count) VALUES (?, ?, ?)",
(uid, plant, count) (uid, plant, count),
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"addUserPlantByUid 失败!", e=e) logger.warning("addUserPlantByUid 失败!", e=e)
return False return False
@classmethod @classmethod
async def getUserPlantByUid(cls, uid: str) -> Dict[str, int]: async def getUserPlantByUid(cls, uid: str) -> dict[str, int]:
"""根据用户uid获取全部作物信息 """根据用户uid获取全部作物信息
Args: Args:
@ -67,14 +66,13 @@ class CUserPlantDB(CSqlManager):
Dict[str, int]: 作物名称和数量 Dict[str, int]: 作物名称和数量
""" """
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
"SELECT plant, count FROM userPlant WHERE uid=?", "SELECT plant, count FROM userPlant WHERE uid=?", (uid,)
(uid,)
) )
rows = await cursor.fetchall() rows = await cursor.fetchall()
return {row["plant"]: row["count"] for row in rows} return {row["plant"]: row["count"] for row in rows}
@classmethod @classmethod
async def getUserPlantByName(cls, uid: str, plant: str) -> Optional[int]: async def getUserPlantByName(cls, uid: str, plant: str) -> int | None:
"""根据作物名称获取用户的作物数量 """根据作物名称获取用户的作物数量
Args: Args:
@ -86,15 +84,35 @@ class CUserPlantDB(CSqlManager):
""" """
try: try:
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT count FROM userPlant WHERE uid = ? AND plant = ?", "SELECT count FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
(uid, plant)
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
return row[0] if row else None return row[0] if row else None
except Exception as e: except Exception as e:
logger.warning(f"getUserPlantByName 查询失败!", e=e) logger.warning("getUserPlantByName 查询失败!", e=e)
return None return None
@classmethod
async def checkUserPlantByName(cls, uid: str, plant: str) -> bool:
"""根据作物名称判断用户作物仓库是否存在该作物
Args:
uid (str): 用户uid
plant (str): 作物名称
Returns:
bool: 是否存在
"""
try:
async with cls.m_pDB.execute(
"SELECT * FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
) as cursor:
row = await cursor.fetchone()
return True if row else False
except Exception as e:
logger.warning("checkUserPlantByName 查询失败!", e=e)
return False
@classmethod @classmethod
async def updateUserPlantByName(cls, uid: str, plant: str, count: int) -> bool: async def updateUserPlantByName(cls, uid: str, plant: str, count: int) -> bool:
"""更新 userPlant 表中某个作物的数量 """更新 userPlant 表中某个作物的数量
@ -114,11 +132,55 @@ class CUserPlantDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?", "UPDATE userPlant SET count = ? WHERE uid = ? AND plant = ?",
(count, uid, plant) (count, uid, plant),
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"updateUserPlantByName失败", e=e) logger.warning("updateUserPlantByName 失败!", e=e)
return False
@classmethod
async def lockUserPlantByName(cls, uid: str, plant: str, lock: int) -> bool:
"""给作物加锁,防止一键售卖
Args:
uid (str): 用户uid
plant (str): 作物名称
lock (int): 0为解锁非0均为加锁
Returns:
bool: 是否加锁成功
"""
try:
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE userPlant SET isLock = ? WHERE uid = ? AND plant = ?",
(lock, uid, plant),
)
return True
except Exception as e:
logger.warning("lockUserPlantByName 失败!", e=e)
return False
@classmethod
async def checkPlantLockByName(cls, uid: str, plant: str) -> bool:
"""根据作物名称判断是否加锁
Args:
uid (str): 用户uid
plant (str): 作物名称
Returns:
bool: 是否加锁
"""
try:
async with cls.m_pDB.execute(
"SELECT isLock FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
) as cursor:
row = await cursor.fetchone()
return row[0] > 0 if row else False
except Exception as e:
logger.warning("checkUserPlantByName 查询失败!", e=e)
return False return False
@classmethod @classmethod
@ -135,10 +197,9 @@ class CUserPlantDB(CSqlManager):
try: try:
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"DELETE FROM userPlant WHERE uid = ? AND plant = ?", "DELETE FROM userPlant WHERE uid = ? AND plant = ?", (uid, plant)
(uid, plant)
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"deleteUserPlantByName 失败!", e=e) logger.warning("deleteUserPlantByName 失败!", e=e)
return False return False

View File

@ -1,5 +1,3 @@
from typing import Optional
from zhenxun.services.log import logger from zhenxun.services.log import logger
from .database import CSqlManager from .database import CSqlManager
@ -9,10 +7,10 @@ class CUserSeedDB(CSqlManager):
@classmethod @classmethod
async def initDB(cls): async def initDB(cls):
userSeed = { userSeed = {
"uid": "TEXT NOT NULL", #用户Uid "uid": "TEXT NOT NULL", # 用户Uid
"seed": "TEXT NOT NULL", #种子名称 "seed": "TEXT NOT NULL", # 种子名称
"count": "INTEGER NOT NULL DEFAULT 0", #数量 "count": "INTEGER NOT NULL DEFAULT 0", # 数量
"PRIMARY KEY": "(uid, seed)" "PRIMARY KEY": "(uid, seed)",
} }
await cls.ensureTableSchema("userSeed", userSeed) await cls.ensureTableSchema("userSeed", userSeed)
@ -32,8 +30,7 @@ class CUserSeedDB(CSqlManager):
try: try:
async with cls._transaction(): async with cls._transaction():
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?", "SELECT count FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
(uid, seed)
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
@ -41,23 +38,22 @@ class CUserSeedDB(CSqlManager):
newCount = row[0] + count newCount = row[0] + count
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?", "UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(newCount, uid, seed) (newCount, uid, seed),
) )
else: else:
newCount = count newCount = count
await cls.m_pDB.execute( await cls.m_pDB.execute(
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)", "INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
(uid, seed, count) (uid, seed, count),
) )
if newCount <= 0: if newCount <= 0:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", "DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
(uid, seed)
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"addUserSeedByUid 失败!", e=e) logger.warning("addUserSeedByUid 失败!", e=e)
return False return False
@classmethod @classmethod
@ -72,7 +68,7 @@ class CUserSeedDB(CSqlManager):
else: else:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)", "INSERT INTO userSeed (uid, seed, count) VALUES (?, ?, ?)",
(uid, seed, newCount) (uid, seed, newCount),
) )
if newCount <= 0: if newCount <= 0:
@ -80,12 +76,11 @@ class CUserSeedDB(CSqlManager):
return True return True
except Exception as e: except Exception as e:
logger.warning(f"_addUserSeedByUid 失败!", e=e) logger.warning("_addUserSeedByUid 失败!", e=e)
return False return False
@classmethod @classmethod
async def getUserSeedByName(cls, uid: str, seed: str) -> Optional[int]: async def getUserSeedByName(cls, uid: str, seed: str) -> int | None:
"""根据种子名称获取种子数量 """根据种子名称获取种子数量
Args: Args:
@ -98,13 +93,12 @@ class CUserSeedDB(CSqlManager):
try: try:
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT count FROM userSeed WHERE uid = ? AND seed = ?", "SELECT count FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
(uid, seed)
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
return row[0] if row else None return row[0] if row else None
except Exception as e: except Exception as e:
logger.warning(f"getUserSeedByName 查询失败!", e=e) logger.warning("getUserSeedByName 查询失败!", e=e)
return None return None
@classmethod @classmethod
@ -119,8 +113,7 @@ class CUserSeedDB(CSqlManager):
""" """
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
"SELECT seed, count FROM userSeed WHERE uid=?", "SELECT seed, count FROM userSeed WHERE uid=?", (uid,)
(uid,)
) )
rows = await cursor.fetchall() rows = await cursor.fetchall()
return {row["seed"]: row["count"] for row in rows} return {row["seed"]: row["count"] for row in rows}
@ -144,11 +137,11 @@ class CUserSeedDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?", "UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(count, uid, seed) (count, uid, seed),
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"updateUserSeedByName失败", e=e) logger.warning("updateUserSeedByName失败", e=e)
return False return False
@classmethod @classmethod
@ -170,11 +163,11 @@ class CUserSeedDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?", "UPDATE userSeed SET count = ? WHERE uid = ? AND seed = ?",
(count, uid, seed) (count, uid, seed),
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"updateUserSeedByName失败", e=e) logger.warning("updateUserSeedByName失败", e=e)
return False return False
@classmethod @classmethod
@ -191,12 +184,11 @@ class CUserSeedDB(CSqlManager):
try: try:
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", "DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
(uid, seed)
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"deleteUserSeedByName 删除失败!", e=e) logger.warning("deleteUserSeedByName 删除失败!", e=e)
return False return False
@classmethod @classmethod
@ -212,10 +204,9 @@ class CUserSeedDB(CSqlManager):
""" """
try: try:
await cls.m_pDB.execute( await cls.m_pDB.execute(
"DELETE FROM userSeed WHERE uid = ? AND seed = ?", "DELETE FROM userSeed WHERE uid = ? AND seed = ?", (uid, seed)
(uid, seed)
) )
return True return True
except Exception as e: except Exception as e:
logger.warning(f"deleteUserSeedByName 删除失败!", e=e) logger.warning("deleteUserSeedByName 删除失败!", e=e)
return False return False

View File

@ -1,7 +1,6 @@
import calendar import calendar
from datetime import timedelta
import random import random
from datetime import date, datetime, timedelta
from typing import Optional
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
@ -16,27 +15,27 @@ from .database import CSqlManager
class CUserSignDB(CSqlManager): class CUserSignDB(CSqlManager):
@classmethod @classmethod
async def initDB(cls): async def initDB(cls):
#userSignLog 表结构,每条为一次签到事件 # userSignLog 表结构,每条为一次签到事件
userSignLog = { userSignLog = {
"uid": "TEXT NOT NULL", #用户ID "uid": "TEXT NOT NULL", # 用户ID
"signDate": "DATE NOT NULL", #签到日期 "signDate": "DATE NOT NULL", # 签到日期
"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)",
} }
#userSignSummary 表结构,每用户一行用于缓存签到状态 # userSignSummary 表结构,每用户一行用于缓存签到状态
userSignSummary = { userSignSummary = {
"uid": "TEXT PRIMARY KEY NOT NULL", #用户ID "uid": "TEXT PRIMARY KEY NOT NULL", # 用户ID
"totalSignDays": "INT NOT NULL DEFAULT 0", #累计签到天数 "totalSignDays": "INT NOT NULL DEFAULT 0", # 累计签到天数
"currentMonth": "CHAR(7) NOT NULL DEFAULT ''", #当前月份如2025-05 "currentMonth": "CHAR(7) NOT NULL DEFAULT ''", # 当前月份如2025-05
"monthSignDays": "INT NOT NULL DEFAULT 0", #本月签到次数 "monthSignDays": "INT NOT NULL DEFAULT 0", # 本月签到次数
"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)
@ -57,15 +56,15 @@ class CUserSignDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
async with cls.m_pDB.execute( async with cls.m_pDB.execute(
"SELECT exp, point FROM userSignLog WHERE uid=? AND signDate=?", "SELECT exp, point FROM userSignLog WHERE uid=? AND signDate=?",
(uid, date) (uid, date),
) as cursor: ) as cursor:
row = await cursor.fetchone() row = await cursor.fetchone()
if row is None: if row is None:
return 0, 0 return 0, 0
exp = row['exp'] exp = row["exp"]
point = row['point'] point = row["point"]
return exp, point return exp, point
except Exception as e: except Exception as e:
@ -114,7 +113,7 @@ class CUserSignDB(CSqlManager):
return False return False
@classmethod @classmethod
async def sign(cls, uid: str, signDate: str = '') -> int: async def sign(cls, uid: str, signDate: str = "") -> int:
"""签到 """签到
Args: Args:
@ -151,20 +150,34 @@ class CUserSignDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
"INSERT INTO userSignLog (uid, signDate, isSupplement, exp, point) VALUES (?, ?, ?, ?, ?)", "INSERT INTO userSignLog (uid, signDate, isSupplement, exp, point) VALUES (?, ?, ?, ?, ?)",
(uid, signDate, isSupplement, exp, point) (uid, signDate, isSupplement, exp, point),
) )
cursor = await cls.m_pDB.execute("SELECT * FROM userSignSummary WHERE uid=?", (uid,)) cursor = await cls.m_pDB.execute(
"SELECT * FROM userSignSummary WHERE uid=?", (uid,)
)
row = await cursor.fetchone() row = await cursor.fetchone()
currentMonth = signDate[:7] currentMonth = signDate[:7]
if row: if row:
monthSignDays = row['monthSignDays'] + 1 if row['currentMonth'] == currentMonth else 1 monthSignDays = (
totalSignDays = row['totalSignDays'] row["monthSignDays"] + 1
lastDate = row['lastSignDate'] if row["currentMonth"] == currentMonth
prevDate = (g_pToolManager.dateTime().strptime(signDate, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d") else 1
continuousDays = row['continuousDays'] + 1 if lastDate == prevDate else 1 )
supplementCount = row['supplementCount'] + 1 if isSupplement else row['supplementCount'] lastDate = row["lastSignDate"]
prevDate = (
g_pToolManager.dateTime().strptime(signDate, "%Y-%m-%d")
- timedelta(days=1)
).strftime("%Y-%m-%d")
continuousDays = (
row["continuousDays"] + 1 if lastDate == prevDate else 1
)
supplementCount = (
row["supplementCount"] + 1
if isSupplement
else row["supplementCount"]
)
await cls.m_pDB.execute( await cls.m_pDB.execute(
""" """
UPDATE userSignSummary UPDATE userSignSummary
@ -176,29 +189,42 @@ class CUserSignDB(CSqlManager):
supplementCount=? supplementCount=?
WHERE uid=? WHERE uid=?
""", """,
(currentMonth, monthSignDays, signDate, continuousDays, supplementCount, uid) (
currentMonth,
monthSignDays,
signDate,
continuousDays,
supplementCount,
uid,
),
) )
else: else:
totalSignDays = 1 monthSignDays = 1
await cls.m_pDB.execute( await cls.m_pDB.execute(
""" """
INSERT INTO userSignSummary INSERT INTO userSignSummary
(uid, totalSignDays, currentMonth, monthSignDays, lastSignDate, continuousDays, supplementCount) (uid, totalSignDays, currentMonth, monthSignDays, lastSignDate, continuousDays, supplementCount)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", """,
(uid, 1, currentMonth, 1, signDate, 1, 1 if isSupplement else 0) (
uid,
1,
currentMonth,
monthSignDays,
signDate,
1,
1 if isSupplement else 0,
),
) )
#计算累签奖励 # 计算累签奖励
reward = g_pJsonManager.m_pSign['continuou'].get(f"{totalSignDays}", None) reward = g_pJsonManager.m_pSign["continuou"].get(f"{monthSignDays}", None)
if reward: if reward:
point += reward.get('point', 0) point += reward.get("point", 0)
exp += reward.get('exp', 0) exp += reward.get("exp", 0)
vipPoint = reward.get('vipPoint', 0) vipPoint = reward.get("vipPoint", 0)
plant = reward.get('plant', {})
plant = reward.get("plant", {})
if plant: if plant:
for key, value in plant.items(): for key, value in plant.items():
await g_pDBService.userSeed.addUserSeedByUid(uid, key, value) await g_pDBService.userSeed.addUserSeedByUid(uid, key, value)
@ -206,7 +232,7 @@ class CUserSignDB(CSqlManager):
if g_bIsDebug: if g_bIsDebug:
exp += 9999 exp += 9999
#向数据库更新 # 向数据库更新
currentExp = await g_pDBService.user.getUserExpByUid(uid) currentExp = await g_pDBService.user.getUserExpByUid(uid)
await g_pDBService.user.updateUserExpByUid(uid, currentExp + exp) await g_pDBService.user.updateUserExpByUid(uid, currentExp + exp)
@ -215,7 +241,9 @@ class CUserSignDB(CSqlManager):
if vipPoint > 0: if vipPoint > 0:
currentVipPoint = await g_pDBService.user.getUserVipPointByUid(uid) currentVipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
await g_pDBService.user.updateUserVipPointByUid(uid, currentVipPoint + vipPoint) await g_pDBService.user.updateUserVipPointByUid(
uid, currentVipPoint + vipPoint
)
return 1 return 1
except Exception as e: except Exception as e:
@ -224,7 +252,7 @@ class CUserSignDB(CSqlManager):
@classmethod @classmethod
async def drawSignCalendarImage(cls, uid: str, year: int, month: int): async def drawSignCalendarImage(cls, uid: str, year: int, month: int):
#绘制签到图,自动提取数据库中该用户该月的签到天数 # 绘制签到图,自动提取数据库中该用户该月的签到天数
cellSize = 80 cellSize = 80
padding = 40 padding = 40
titleHeight = 80 titleHeight = 80
@ -242,7 +270,7 @@ class CUserSignDB(CSqlManager):
sql = "SELECT signDate FROM userSignLog WHERE uid=? AND signDate LIKE ?" sql = "SELECT signDate FROM userSignLog WHERE uid=? AND signDate LIKE ?"
async with cls.m_pDB.execute(sql, (uid, f"{monthStr}-%")) as cursor: async with cls.m_pDB.execute(sql, (uid, f"{monthStr}-%")) as cursor:
rows = await cursor.fetchall() rows = await cursor.fetchall()
signedDays = set(int(r[0][-2:]) for r in rows if r[0][-2:].isdigit()) signedDays = {int(r[0][-2:]) for r in rows if r[0][-2:].isdigit()}
except Exception as e: except Exception as e:
logger.warning("绘制签到图时数据库查询失败", e=e) logger.warning("绘制签到图时数据库查询失败", e=e)
signedDays = set() signedDays = set()

View File

@ -1,10 +1,9 @@
from typing import Optional import math
from zhenxun.services.log import logger from zhenxun.services.log import logger
from ..config import g_bIsDebug from ..config import g_bIsDebug
from ..dbService import g_pDBService from ..dbService import g_pDBService
from ..json import g_pJsonManager
from ..tool import g_pToolManager from ..tool import g_pToolManager
from .database import CSqlManager from .database import CSqlManager
@ -14,17 +13,18 @@ class CUserSoilDB(CSqlManager):
async def initDB(cls): async def initDB(cls):
userSoil = { userSoil = {
"uid": "TEXT NOT NULL", "uid": "TEXT NOT NULL",
"soilIndex": "INTEGER NOT NULL", #地块索引从1开始 "soilIndex": "INTEGER NOT NULL", # 地块索引从1开始
"plantName": "TEXT DEFAULT ''", #作物名称 "plantName": "TEXT DEFAULT ''", # 作物名称
"plantTime": "INTEGER DEFAULT 0", #播种时间 "plantTime": "INTEGER DEFAULT 0", # 播种时间
"matureTime": "INTEGER DEFAULT 0", #成熟时间 "matureTime": "INTEGER DEFAULT 0", # 成熟时间
"soilLevel": "INTEGER DEFAULT 0", #土地等级 0=普通地1=红土地2=黑土地3=金土地 "soilLevel": "INTEGER DEFAULT 0", # 土地等级 0=普通地1=红土地2=黑土地3=金土地
"wiltStatus": "INTEGER DEFAULT 0", #枯萎状态 0=未枯萎1=枯萎 "wiltStatus": "INTEGER DEFAULT 0", # 枯萎状态 0=未枯萎1=枯萎
"fertilizerStatus": "INTEGER DEFAULT 0",#施肥状态 0=未施肥1=施肥 2=增肥 "fertilizerStatus": "INTEGER DEFAULT 0", # 施肥状态 0=未施肥1=施肥 2=增肥
"bugStatus": "INTEGER DEFAULT 0", #虫害状态 0=无虫害1=有虫害 "bugStatus": "INTEGER DEFAULT 0", # 虫害状态 0=无虫害1=有虫害
"weedStatus": "INTEGER DEFAULT 0", #杂草状态 0=无杂草1=有杂草 "weedStatus": "INTEGER DEFAULT 0", # 杂草状态 0=无杂草1=有杂草
"waterStatus": "INTEGER DEFAULT 0", #缺水状态 0=不缺水1=缺水 "waterStatus": "INTEGER DEFAULT 0", # 缺水状态 0=不缺水1=缺水
"harvestCount": "INTEGER DEFAULT 0", #收获次数 "harvestCount": "INTEGER DEFAULT 0", # 收获次数
"isSoilPlanted": "INTEGER DEFAULT NULL", # 是否种植作物 0=没有1=有
"PRIMARY KEY": "(uid, soilIndex)", "PRIMARY KEY": "(uid, soilIndex)",
} }
@ -45,35 +45,59 @@ class CUserSoilDB(CSqlManager):
if not soilInfo: if not soilInfo:
return return
plantInfo = await g_pDBService.plant.getPlantByName(soilInfo['plantName']) plantInfo = await g_pDBService.plant.getPlantByName(soilInfo["plantName"])
if not plantInfo: if not plantInfo:
return return
currentTime = g_pToolManager.dateTime().now().timestamp() currentTime = g_pToolManager.dateTime().now().timestamp()
phaseList = await g_pDBService.plant.getPlantPhaseByName(soilInfo['plantName']) phaseList = await g_pDBService.plant.getPlantPhaseByName(soilInfo["plantName"])
if currentTime >= soilInfo['matureTime']: if currentTime >= soilInfo["matureTime"]:
return return
currentStage = 0 elapsedTime = currentTime - soilInfo["plantTime"]
elapsedTime = currentTime - soilInfo['plantTime'] currentStage = currentStage = sum(1 for thr in phaseList if elapsedTime >= thr)
for idx, thr in enumerate(phaseList, start=1): t = int(soilInfo["plantTime"]) - phaseList[currentStage]
if elapsedTime < thr: s = int(soilInfo["matureTime"]) - phaseList[currentStage]
currentStage = idx
break
t = int(soilInfo['plantTime']) - phaseList[currentStage] await cls.updateUserSoilFields(
s = int(soilInfo['matureTime']) - phaseList[currentStage] uid, soilIndex, {"plantTime": t, "matureTime": s}
)
await cls.updateUserSoilFields(uid, soilIndex, logger.debug(
{ f"当前阶段{currentStage}, 阶段时间{phaseList[currentStage]}, 播种时间{t}, 收获时间{s}"
"plantTime": t, )
"matureTime": s
})
logger.debug(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 = int(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:
@ -100,7 +124,7 @@ class CUserSoilDB(CSqlManager):
Returns: Returns:
bool: 如果旧表不存在则返回 False否则迁移并删除后返回 True bool: 如果旧表不存在则返回 False否则迁移并删除后返回 True
""" """
#检查旧表是否存在 # 检查旧表是否存在
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='soil'" "SELECT name FROM sqlite_master WHERE type='table' AND name='soil'"
) )
@ -117,9 +141,14 @@ class CUserSoilDB(CSqlManager):
data = farmInfo.get(key) data = farmInfo.get(key)
if not data: if not data:
continue continue
if data == ",,,4,":
continue
parts = data.split(",") parts = data.split(",")
if len(parts) < 3: if len(parts) < 3:
continue continue
name = parts[0] name = parts[0]
pt = int(parts[1]) pt = int(parts[1])
mt = int(parts[2]) mt = int(parts[2])
@ -154,8 +183,8 @@ class CUserSoilDB(CSqlManager):
INSERT INTO userSoil INSERT INTO userSoil
(uid, soilIndex, plantName, plantTime, matureTime, (uid, soilIndex, plantName, plantTime, matureTime,
soilLevel, wiltStatus, fertilizerStatus, bugStatus, soilLevel, wiltStatus, fertilizerStatus, bugStatus,
weedStatus, waterStatus, harvestCount) weedStatus, waterStatus, harvestCount, isSoilPlanted)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
""", """,
( (
soilInfo["uid"], soilInfo["uid"],
@ -170,6 +199,7 @@ class CUserSoilDB(CSqlManager):
soilInfo.get("weedStatus", 0), soilInfo.get("weedStatus", 0),
soilInfo.get("waterStatus", 0), soilInfo.get("waterStatus", 0),
soilInfo.get("harvestCount", 0), soilInfo.get("harvestCount", 0),
soilInfo.get("isSoilPlanted", 0),
), ),
) )
@ -188,8 +218,8 @@ class CUserSoilDB(CSqlManager):
INSERT INTO userSoil INSERT INTO userSoil
(uid, soilIndex, plantName, plantTime, matureTime, (uid, soilIndex, plantName, plantTime, matureTime,
soilLevel, wiltStatus, fertilizerStatus, bugStatus, soilLevel, wiltStatus, fertilizerStatus, bugStatus,
weedStatus, waterStatus, harvestCount) weedStatus, waterStatus, harvestCount, isSoilPlanted)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
""", """,
( (
soilInfo["uid"], soilInfo["uid"],
@ -204,11 +234,12 @@ class CUserSoilDB(CSqlManager):
soilInfo.get("weedStatus", 0), soilInfo.get("weedStatus", 0),
soilInfo.get("waterStatus", 0), soilInfo.get("waterStatus", 0),
soilInfo.get("harvestCount", 0), soilInfo.get("harvestCount", 0),
soilInfo.get("isSoilPlanted", 0),
), ),
) )
@classmethod @classmethod
async def getUserSoil(cls, uid: str, soilIndex: int) -> Optional[dict]: async def getUserSoil(cls, uid: str, soilIndex: int) -> dict:
"""获取指定用户某块土地的详细信息 """获取指定用户某块土地的详细信息
Args: Args:
@ -216,7 +247,7 @@ class CUserSoilDB(CSqlManager):
soilIndex (int): 土地索引 soilIndex (int): 土地索引
Returns: Returns:
Optional[dict]: 记录存在返回字段-值字典否则返回 None dict: 记录存在返回字段-值字典否则返回 None
""" """
async with cls._transaction(): async with cls._transaction():
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
@ -225,12 +256,12 @@ class CUserSoilDB(CSqlManager):
) )
row = await cursor.fetchone() row = await cursor.fetchone()
if not row: if not row:
return None return {}
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 @classmethod
async def _getUserSoil(cls, uid: str, soilIndex: int) -> Optional[dict]: async def _getUserSoil(cls, uid: str, soilIndex: int) -> dict | None:
"""获取指定用户某块土地的详细信息 """获取指定用户某块土地的详细信息
Args: Args:
@ -238,7 +269,7 @@ class CUserSoilDB(CSqlManager):
soilIndex (int): 土地索引 soilIndex (int): 土地索引
Returns: Returns:
Optional[dict]: 记录存在返回字段-值字典否则返回 None dict | None: 记录存在返回字段-值字典否则返回 None
""" """
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
"SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?", "SELECT * FROM userSoil WHERE uid = ? AND soilIndex = ?",
@ -250,6 +281,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):
"""更新指定用户土地的单个字段 """更新指定用户土地的单个字段
@ -288,7 +338,9 @@ class CUserSoilDB(CSqlManager):
) )
@classmethod @classmethod
async def updateUserSoilFields(cls, uid: str, soilIndex: int, updates: dict) -> bool: async def updateUserSoilFields(
cls, uid: str, soilIndex: int, updates: dict
) -> bool:
"""批量更新指定用户土地的多个字段 """批量更新指定用户土地的多个字段
Args: Args:
@ -299,11 +351,19 @@ class CUserSoilDB(CSqlManager):
Returns: Returns:
bool: 如果无可更新字段则返回 False否则更新成功返回 True bool: 如果无可更新字段则返回 False否则更新成功返回 True
""" """
#允许更新的列白名单 # 允许更新的列白名单
allowedFields = { allowedFields = {
"plantName", "plantTime", "matureTime", "soilLevel", "plantName",
"wiltStatus", "fertilizerStatus", "bugStatus", "plantTime",
"weedStatus", "waterStatus", "harvestCount" "matureTime",
"soilLevel",
"wiltStatus",
"fertilizerStatus",
"bugStatus",
"weedStatus",
"waterStatus",
"harvestCount",
"isSoilPlanted",
} }
setClauses = [] setClauses = []
values = [] values = []
@ -316,7 +376,7 @@ class CUserSoilDB(CSqlManager):
return False return False
values.extend([uid, soilIndex]) values.extend([uid, soilIndex])
sql = f'UPDATE userSoil SET {", ".join(setClauses)} WHERE uid = ? AND soilIndex = ?' sql = f"UPDATE userSoil SET {', '.join(setClauses)} WHERE uid = ? AND soilIndex = ?"
try: try:
async with cls._transaction(): async with cls._transaction():
@ -357,23 +417,6 @@ class CUserSoilDB(CSqlManager):
"DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", (uid, soilIndex) "DELETE FROM userSoil WHERE uid = ? AND soilIndex = ?", (uid, soilIndex)
) )
@classmethod
async def isSoilPlanted(cls, uid: str, soilIndex: int) -> bool:
"""判断指定用户的指定土地是否已种植
Args:
uid (str): 用户ID
soilIndex (int): 土地索引
Returns:
bool: 如果 plantName 不为空且 plantTime 大于 0则视为已种植返回 True否则 False
"""
soilInfo = await cls.getUserSoil(uid, soilIndex)
if not soilInfo:
return False
return bool(soilInfo.get("plantName")) and soilInfo.get("plantTime", 0) > 0
@classmethod @classmethod
async def sowingByPlantName(cls, uid: str, soilIndex: int, plantName: str) -> bool: async def sowingByPlantName(cls, uid: str, soilIndex: int, plantName: str) -> bool:
"""播种指定作物到用户土地区 """播种指定作物到用户土地区
@ -386,23 +429,30 @@ class CUserSoilDB(CSqlManager):
Returns: Returns:
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 soilInfo and soilInfo.get("plantName"):
return False return False
#获取植物配置 # 获取植物配置
plantCfg = await g_pDBService.plant.getPlantByName(plantName) plantCfg = await g_pDBService.plant.getPlantByName(plantName)
if not plantCfg: if not plantCfg:
logger.error(f"未知植物: {plantName}") logger.error(f"未知植物: {plantName}")
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 = math.floor(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(
{ {
@ -412,17 +462,18 @@ class CUserSoilDB(CSqlManager):
"plantTime": nowTs, "plantTime": nowTs,
"matureTime": matureTs, "matureTime": matureTs,
"soilLevel": prev.get("soilLevel", 0), "soilLevel": prev.get("soilLevel", 0),
"wiltStatus": prev.get("wiltStatus", 0), "wiltStatus": 0,
"fertilizerStatus": prev.get("fertilizerStatus", 0), "fertilizerStatus": 0,
"bugStatus": prev.get("bugStatus", 0), "bugStatus": 0,
"weedStatus": prev.get("weedStatus", 0), "weedStatus": 0,
"waterStatus": prev.get("waterStatus", 0), "waterStatus": 0,
"harvestCount": 0 "harvestCount": 0,
"isSoilPlanted": 1,
} }
) )
return True return True
except Exception as e: except Exception as e:
logger.error(f"播种失败!", e=e) logger.error("播种失败!", e=e)
return False return False
@classmethod @classmethod
@ -451,3 +502,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

@ -7,17 +7,19 @@ class CUserStealDB(CSqlManager):
@classmethod @classmethod
async def initDB(cls): async def initDB(cls):
userSteal = { userSteal = {
"uid": "TEXT NOT NULL", #被偷用户Uid "uid": "TEXT NOT NULL", # 被偷用户Uid
"soilIndex": "INTEGER NOT NULL", #被偷的地块索引 从1开始 "soilIndex": "INTEGER NOT NULL", # 被偷的地块索引 从1开始
"stealerUid": "TEXT NOT NULL", #偷菜用户Uid "stealerUid": "TEXT NOT NULL", # 偷菜用户Uid
"stealCount": "INTEGER NOT NULL", #被偷数量 "stealCount": "INTEGER NOT NULL", # 被偷数量
"stealTime": "INTEGER NOT NULL", #被偷时间 "stealTime": "INTEGER NOT NULL", # 被偷时间
"PRIMARY KEY": "(uid, soilIndex, stealerUid)" "PRIMARY KEY": "(uid, soilIndex, stealerUid)",
} }
await cls.ensureTableSchema("userSteal", userSteal) await cls.ensureTableSchema("userSteal", userSteal)
@classmethod @classmethod
async def addStealRecord(cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int) -> bool: async def addStealRecord(
cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int
) -> bool:
"""添加偷菜记录 """添加偷菜记录
Args: Args:
@ -34,7 +36,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
'INSERT INTO "userSteal"(uid, soilIndex, stealerUid, stealCount, stealTime) VALUES(?, ?, ?, ?, ?);', 'INSERT INTO "userSteal"(uid, soilIndex, stealerUid, stealCount, stealTime) VALUES(?, ?, ?, ?, ?);',
(uid, soilIndex, stealerUid, stealCount, stealTime) (uid, soilIndex, stealerUid, stealCount, stealTime),
) )
return True return True
except Exception as e: except Exception as e:
@ -55,7 +57,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
'SELECT soilIndex, stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=?;', 'SELECT soilIndex, stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=?;',
(uid,) (uid,),
) )
rows = await cursor.fetchall() rows = await cursor.fetchall()
return [ return [
@ -64,7 +66,7 @@ class CUserStealDB(CSqlManager):
"soilIndex": row[0], "soilIndex": row[0],
"stealerUid": row[1], "stealerUid": row[1],
"stealCount": row[2], "stealCount": row[2],
"stealTime": row[3] "stealTime": row[3],
} }
for row in rows for row in rows
] ]
@ -87,7 +89,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
'SELECT stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=? AND soilIndex=?;', 'SELECT stealerUid, stealCount, stealTime FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex) (uid, soilIndex),
) )
rows = await cursor.fetchall() rows = await cursor.fetchall()
return [ return [
@ -96,7 +98,7 @@ class CUserStealDB(CSqlManager):
"soilIndex": soilIndex, "soilIndex": soilIndex,
"stealerUid": row[0], "stealerUid": row[0],
"stealCount": row[1], "stealCount": row[1],
"stealTime": row[2] "stealTime": row[2],
} }
for row in rows for row in rows
] ]
@ -119,7 +121,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
'SELECT SUM(stealCount) FROM "userSteal" WHERE uid=? AND soilIndex=?;', 'SELECT SUM(stealCount) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex) (uid, soilIndex),
) )
row = await cursor.fetchone() row = await cursor.fetchone()
return row[0] or 0 # type: ignore return row[0] or 0 # type: ignore
@ -142,7 +144,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
'SELECT COUNT(DISTINCT stealerUid) FROM "userSteal" WHERE uid=? AND soilIndex=?;', 'SELECT COUNT(DISTINCT stealerUid) FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex) (uid, soilIndex),
) )
row = await cursor.fetchone() row = await cursor.fetchone()
return row[0] or 0 # type: ignore return row[0] or 0 # type: ignore
@ -166,7 +168,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
cursor = await cls.m_pDB.execute( cursor = await cls.m_pDB.execute(
'SELECT 1 FROM "userSteal" WHERE uid=? AND soilIndex=? AND stealerUid=? LIMIT 1;', 'SELECT 1 FROM "userSteal" WHERE uid=? AND soilIndex=? AND stealerUid=? LIMIT 1;',
(uid, soilIndex, stealerUid) (uid, soilIndex, stealerUid),
) )
row = await cursor.fetchone() row = await cursor.fetchone()
return bool(row) return bool(row)
@ -175,7 +177,9 @@ class CUserStealDB(CSqlManager):
return False return False
@classmethod @classmethod
async def updateStealRecord(cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int) -> bool: async def updateStealRecord(
cls, uid: str, soilIndex: int, stealerUid: str, stealCount: int, stealTime: int
) -> bool:
"""更新偷菜记录的数量和时间 """更新偷菜记录的数量和时间
Args: Args:
@ -192,7 +196,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
'UPDATE "userSteal" SET stealCount=?, stealTime=? WHERE uid=? AND soilIndex=? AND stealerUid=?;', 'UPDATE "userSteal" SET stealCount=?, stealTime=? WHERE uid=? AND soilIndex=? AND stealerUid=?;',
(stealCount, stealTime, uid, soilIndex, stealerUid) (stealCount, stealTime, uid, soilIndex, stealerUid),
) )
return True return True
except Exception as e: except Exception as e:
@ -214,7 +218,7 @@ class CUserStealDB(CSqlManager):
async with cls._transaction(): async with cls._transaction():
await cls.m_pDB.execute( await cls.m_pDB.execute(
'DELETE FROM "userSteal" WHERE uid=? AND soilIndex=?;', 'DELETE FROM "userSteal" WHERE uid=? AND soilIndex=?;',
(uid, soilIndex) (uid, soilIndex),
) )
return True return True
except Exception as e: except Exception as e:

View File

@ -1,6 +1,3 @@
from typing import Optional
class CDBService: class CDBService:
@classmethod @classmethod
async def init(cls): async def init(cls):
@ -37,11 +34,12 @@ class CDBService:
cls.userSign = CUserSignDB() cls.userSign = CUserSignDB()
await cls.userSign.initDB() await cls.userSign.initDB()
#迁移旧数据库 # 迁移旧数据库
await cls.userSoil.migrateOldFarmData() await cls.userSoil.migrateOldFarmData()
@classmethod @classmethod
async def cleanup(cls): async def cleanup(cls):
await cls.plant.cleanup() await cls.plant.cleanup()
g_pDBService = CDBService() g_pDBService = CDBService()

View File

@ -1,51 +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:
@ -54,7 +71,7 @@ class FarmEventManager:
num (int): 播种数量 num (int): 播种数量
""" """
self.m_afterPlant = Signal() m_afterPlant = Signal()
"""播种后信号 每块地播种都会触发该信号 """播种后信号 每块地播种都会触发该信号
Args: Args:
@ -63,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:
@ -80,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:
@ -95,9 +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()

File diff suppressed because it is too large Load Diff

117
farm/help.py Normal file
View File

@ -0,0 +1,117 @@
from pathlib import Path
from jinja2 import Template
from playwright.async_api import async_playwright
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.services.log import logger
from ..config import g_sResourcePath
def rendeerHtmlToFile(path: Path | str, context: dict, output: Path | str) -> None:
"""
使用 Jinja2 渲染 HTML 模板并保存到指定文件会自动创建父目录
Args:
path (str): 模板 HTML 路径
context (dict): 用于渲染的上下文字典
output (str): 输出 HTML 文件路径
"""
templatePath = str(path)
outputPath = str(output)
templateStr = Path(templatePath).read_text(encoding="utf-8")
template = Template(templateStr)
rendered = template.render(**context)
# 自动创建目录
Path(outputPath).parent.mkdir(parents=True, exist_ok=True)
Path(outputPath).write_text(rendered, encoding="utf-8")
async def screenshotHtmlToBytes(path: str) -> bytes:
"""
使用 Playwright 截图本地 HTML 文件并返回 PNG 图片字节数据
Args:
path (str): 本地 HTML 文件路径
Returns:
bytes: PNG 图片的原始字节内容
"""
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
file_url = Path(path).resolve().as_uri()
await page.goto(file_url)
image_bytes = await page.screenshot(full_page=True)
await browser.close()
return image_bytes
async def screenshotSave(path: str, save: str) -> None:
"""
使用 Playwright 渲染本地 HTML 并将截图保存到指定路径
Args:
path (str): HTML 文件路径
save (str): PNG 保存路径 output/image.png
"""
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
file_url = Path(path).resolve().as_uri()
await page.goto(file_url)
# 确保保存目录存在
Path(save).parent.mkdir(parents=True, exist_ok=True)
# 截图并保存到本地文件
await page.screenshot(path=save, full_page=True)
await browser.close()
async def createHelpImage() -> bool:
templatePath = g_sResourcePath / "html/help.html"
outputPath = DATA_PATH / "farm_res/html/help.html"
context = {
"title": "功能指令总览",
"data": [
{
"command": "开通农场",
"description": "首次进入游戏开通农场",
"tip": "",
},
{
"command": "购买种子",
"description": "从商店中购买可用种子",
"tip": "",
},
{"command": "播种", "description": "将种子种入土地中", "tip": "先开垦土地"},
{
"command": "收获",
"description": "收获成熟作物获得收益",
"tip": "",
},
{
"command": "偷菜",
"description": "从好友农场中偷取成熟作物",
"tip": "1",
},
],
}
try:
rendeerHtmlToFile(templatePath, context, outputPath)
image_bytes = await screenshot_html_to_bytes(html_output_path)
except Exception as e:
logger.warning("绘制农场帮助菜单失败", e=e)
return False
return True

View File

@ -1,19 +1,14 @@
import math import math
import plistlib
from itertools import islice
from zhenxun.services.log import logger
from zhenxun.utils._build_image import BuildImage
from zhenxun.utils.image_utils import ImageTemplate from zhenxun.utils.image_utils import ImageTemplate
from ..config import g_sResourcePath from ..config import g_sResourcePath, g_sTranslation
from ..dbService import g_pDBService from ..dbService import g_pDBService
from ..json import g_pJsonManager
class CShopManager: class CShopManager:
@classmethod @classmethod
async def getSeedShopImage(cls, filterKey: str|int = 1, num: int = 1) -> bytes: async def getSeedShopImage(cls, filterKey: str | int = 1, num: int = 1) -> bytes:
"""获取商店页面 """获取商店页面
Args: Args:
@ -37,14 +32,15 @@ class CShopManager:
columnName = [ columnName = [
"-", "-",
"种子名称", "种子名称",
"种子单价", "农场币",
"点券",
"解锁等级", "解锁等级",
"果实单价", "果实单价",
"收获经验", "收获经验",
"收获数量", "收获数量",
"成熟时间(小时)", "成熟时间(小时)",
"收获次数", "收获次数",
"是否可以上架交易行" "是否可以上架交易行",
] ]
# 查询所有可购买作物,并根据筛选关键字过滤 # 查询所有可购买作物,并根据筛选关键字过滤
@ -52,10 +48,10 @@ class CShopManager:
filteredPlants = [] filteredPlants = []
for plant in plants: for plant in plants:
# 跳过未解锁购买的种子 # 跳过未解锁购买的种子
if plant['isBuy'] == 0: if plant["isBuy"] == 0:
continue continue
# 字符串筛选 # 字符串筛选
if filterStr and filterStr not in plant['name']: if filterStr and filterStr not in plant["name"]:
continue continue
filteredPlants.append(plant) filteredPlants.append(plant)
@ -63,7 +59,7 @@ class CShopManager:
totalCount = len(filteredPlants) totalCount = len(filteredPlants)
pageCount = math.ceil(totalCount / 15) if totalCount else 1 pageCount = math.ceil(totalCount / 15) if totalCount else 1
startIndex = (page - 1) * 15 startIndex = (page - 1) * 15
pageItems = filteredPlants[startIndex: startIndex + 15] pageItems = filteredPlants[startIndex : startIndex + 15]
# 构建数据行 # 构建数据行
dataList = [] dataList = []
@ -75,20 +71,23 @@ class CShopManager:
icon = (iconPath, 33, 33) icon = (iconPath, 33, 33)
# 交易行标记 # 交易行标记
sell = "可以" if plant['sell'] else "不可以" sell = "可以" if plant["sell"] else "不可以"
dataList.append([ dataList.append(
[
icon, icon,
plant['name'], # 种子名称 plant["name"], # 种子名称
plant['buy'], # 种子单价 plant["buy"], # 农场币种子单价
plant['level'], # 解锁等级 plant["vipBuy"], # 点券种子单价
plant['price'], # 果实单价 plant["level"], # 解锁等级
plant['experience'], # 收获经验 plant["price"], # 果实单价
plant['harvest'], # 收获数量 plant["experience"], # 收获经验
plant['time'], # 成熟时间(小时) plant["harvest"], # 收获数量
plant['crop'], # 收获次数 plant["time"], # 成熟时间(小时)
sell # 是否可上架交易行 plant["crop"], # 收获次数
]) sell, # 是否可上架交易行
]
)
# 页码标题 # 页码标题
title = f"种子商店 页数: {page}/{pageCount}" title = f"种子商店 页数: {page}/{pageCount}"
@ -102,7 +101,6 @@ class CShopManager:
) )
return result.pic2bytes() return result.pic2bytes()
@classmethod @classmethod
async def buySeed(cls, uid: str, name: str, num: int = 1) -> str: async def buySeed(cls, uid: str, name: str, num: int = 1) -> str:
"""购买种子 """购买种子
@ -117,31 +115,46 @@ class CShopManager:
""" """
if num <= 0: if num <= 0:
return "请输入购买数量!" return g_sTranslation["buySeed"]["notNum"]
plantInfo = await g_pDBService.plant.getPlantByName(name) plantInfo = await g_pDBService.plant.getPlantByName(name)
if not plantInfo: if not plantInfo:
return "购买出错!请检查需购买的种子名称!" return g_sTranslation["buySeed"]["error"]
level = await g_pDBService.user.getUserLevelByUid(uid) level = await g_pDBService.user.getUserLevelByUid(uid)
if level[0] < int(plantInfo['level']): if level[0] < int(plantInfo["level"]):
return "你的等级不够哦,努努力吧" return g_sTranslation["buySeed"]["noLevel"]
point = await g_pDBService.user.getUserPointByUid(uid) """
total = int(plantInfo['buy']) * num logger.debug(
f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}"
logger.debug(f"用户:{uid}购买{name},数量为{num}。用户农场币为{point},购买需要{total}") )
"""
if point < total: if plantInfo["isVip"] == 1:
return "你的农场币不够哦~ 快速速氪金吧!" vipPoint = await g_pDBService.user.getUserVipPointByUid(uid)
total = int(plantInfo["vipBuy"]) * num
if vipPoint < total:
return g_sTranslation["buySeed"]["noVipPoint"]
await g_pDBService.user.updateUserVipPointByUid(uid, vipPoint - total)
else: else:
point = await g_pDBService.user.getUserPointByUid(uid)
total = int(plantInfo["buy"]) * num
if point < total:
return g_sTranslation["buySeed"]["noPoint"]
await g_pDBService.user.updateUserPointByUid(uid, point - total) await g_pDBService.user.updateUserPointByUid(uid, point - total)
if not await g_pDBService.userSeed.addUserSeedByUid(uid, name, num): if not await g_pDBService.userSeed.addUserSeedByUid(uid, name, num):
return "购买失败,执行数据库错误!" return g_sTranslation["buySeed"]["errorSql"]
return f"成功购买{name},花费{total}农场币, 剩余{point - total}农场币" if plantInfo["isVip"] == 1:
return g_sTranslation["buySeed"]["vipSuccess"].format(
name=name, total=total, point=vipPoint - total
)
else:
return g_sTranslation["buySeed"]["success"].format(
name=name, total=total, point=point - total
)
@classmethod @classmethod
async def sellPlantByUid(cls, uid: str, name: str = "", num: int = 1) -> str: async def sellPlantByUid(cls, uid: str, name: str = "", num: int = 1) -> str:
@ -158,28 +171,37 @@ class CShopManager:
plant = await g_pDBService.userPlant.getUserPlantByUid(uid) plant = await g_pDBService.userPlant.getUserPlantByUid(uid)
if not plant: if not plant:
return "你仓库没有可以出售的作物" return g_sTranslation["sellPlant"]["no"]
point = 0 point = 0
totalSold = 0 totalSold = 0
isAll = (num == -1) isAll = num == -1
if name == "": if name == "":
for plantName, count in plant.items(): for plantName, count in plant.items():
isLock = await g_pDBService.userPlant.checkPlantLockByName(
uid, plantName
)
if isLock:
continue
plantInfo = await g_pDBService.plant.getPlantByName(plantName) plantInfo = await g_pDBService.plant.getPlantByName(plantName)
if not plantInfo: if not plantInfo:
continue continue
point += plantInfo['price'] * count point += plantInfo["price"] * count
await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0) await g_pDBService.userPlant.updateUserPlantByName(uid, plantName, 0)
else: else:
if name not in plant: if name not in plant:
return f"出售作物{name}出错:仓库中不存在该作物" return g_sTranslation["sellPlant"]["error"].format(name=name)
available = plant[name] available = plant[name]
sellAmount = available if isAll else min(available, num) sellAmount = available if isAll else min(available, num)
if sellAmount <= 0: if sellAmount <= 0:
return f"出售作物{name}出错:数量不足" return g_sTranslation["sellPlant"]["error1"].format(name=name)
await g_pDBService.userPlant.updateUserPlantByName(uid, name, available - sellAmount) await g_pDBService.userPlant.updateUserPlantByName(
uid, name, available - sellAmount
)
totalSold = sellAmount totalSold = sellAmount
if name == "": if name == "":
@ -189,7 +211,7 @@ class CShopManager:
if not plantInfo: if not plantInfo:
price = 0 price = 0
else: else:
price = plantInfo['price'] price = plantInfo["price"]
totalPoint = totalSold * price totalPoint = totalSold * price
@ -197,8 +219,13 @@ class CShopManager:
await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint) await g_pDBService.user.updateUserPointByUid(uid, currentPoint + totalPoint)
if name == "": if name == "":
return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}" return g_sTranslation["sellPlant"]["success"].format(
point=totalPoint, num=currentPoint + totalPoint
)
else: else:
return f"成功出售{name},获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}" return g_sTranslation["sellPlant"]["success1"].format(
name=name, point=totalPoint, num=currentPoint + totalPoint
)
g_pShopManager = CShopManager() g_pShopManager = CShopManager()

View File

@ -1,5 +1,4 @@
import json import json
from pathlib import Path
from zhenxun.services.log import logger from zhenxun.services.log import logger
@ -80,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:
@ -98,4 +101,5 @@ class CJsonManager:
logger.warning(f"sign_in.json JSON格式错误: {e}") logger.warning(f"sign_in.json JSON格式错误: {e}")
return False return False
g_pJsonManager = CJsonManager() g_pJsonManager = CJsonManager()

View File

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

View File

@ -1,13 +1,20 @@
import json import json
import os import os
from datetime import datetime
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
@ -15,12 +22,14 @@ class CRequestManager:
m_sTokens = "xZ%?z5LtWV7H:0-Xnwp+bNRNQ-jbfrxG" m_sTokens = "xZ%?z5LtWV7H:0-Xnwp+bNRNQ-jbfrxG"
@classmethod @classmethod
async def download(cls, async def download(
cls,
url: str, url: str,
savePath: str, savePath: str,
fileName: str, fileName: str,
params: dict | None = None, params: dict | None = None,
jsonData: dict | None = None) -> bool: jsonData: dict | None = None,
) -> bool:
"""下载文件到指定路径并覆盖已存在的文件 """下载文件到指定路径并覆盖已存在的文件
Args: Args:
@ -29,30 +38,52 @@ 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:
logger.warning(
f"文件下载失败: HTTP {response.status_code} {response.text}"
)
return False
totalLength = int(response.headers.get("Content-Length", 0))
fullPath = os.path.join(savePath, fileName) fullPath = os.path.join(savePath, fileName)
os.makedirs(os.path.dirname(fullPath), exist_ok=True) 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: with open(fullPath, "wb") as f:
f.write(response.content) async for chunk in response.aiter_bytes(chunk_size=1024):
f.write(chunk)
progress.advance(task, len(chunk))
return True return True
else:
logger.warning(f"文件下载失败: HTTP {response.status_code} {response.text}")
return False
except Exception as e: except Exception as e:
logger.warning(f"下载文件异常: {e}") logger.warning(f"下载文件异常: {e}")
@ -74,7 +105,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:
@ -84,7 +115,9 @@ class CRequestManager:
if response.status_code == 200: if response.status_code == 200:
return response.json() return response.json()
else: else:
logger.warning(f"{name}请求失败: HTTP {response.status_code} {response.text}") logger.warning(
f"{name}请求失败: HTTP {response.status_code} {response.text}"
)
return {} return {}
except httpx.RequestError as e: except httpx.RequestError as e:
logger.warning(f"{name}请求异常", e=e) logger.warning(f"{name}请求异常", e=e)
@ -105,7 +138,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:
@ -115,7 +148,9 @@ class CRequestManager:
if response.status_code == 200: if response.status_code == 200:
return response.json() return response.json()
else: else:
logger.warning(f"{name}请求失败: HTTP {response.status_code} {response.text}") logger.warning(
f"{name}请求失败: HTTP {response.status_code} {response.text}"
)
return {} return {}
except httpx.RequestError as e: except httpx.RequestError as e:
logger.warning(f"{name}请求异常", e=e) logger.warning(f"{name}请求异常", e=e)
@ -128,7 +163,7 @@ class CRequestManager:
async def initSignInFile(cls) -> bool: async def initSignInFile(cls) -> bool:
if os.path.exists(g_sSignInPath): if os.path.exists(g_sSignInPath):
try: try:
with open(g_sSignInPath, "r", encoding="utf-8") as f: with open(g_sSignInPath, encoding="utf-8") as f:
content = f.read() content = f.read()
sign = json.loads(content) sign = json.loads(content)
@ -141,28 +176,117 @@ class CRequestManager:
else: else:
logger.warning("真寻农场签到文件检查失败, 即将下载") logger.warning("真寻农场签到文件检查失败, 即将下载")
return await cls.downloadSignInFile() return await cls.downloadSignInFile()
except json.JSONDecodeError as e: except json.JSONDecodeError:
logger.warning(f"真寻农场签到文件格式错误, 即将下载") logger.warning("真寻农场签到文件格式错误, 即将下载")
return await cls.downloadSignInFile() return await cls.downloadSignInFile()
else: else:
return await cls.downloadSignInFile() return await cls.downloadSignInFile()
@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()

BIN
resource/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

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

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

75
resource/html/help.html Normal file
View File

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
<style>
body {
background-color: #ffe4e9;
font-family: "Microsoft YaHei", sans-serif;
margin: 0;
padding: 0;
}
.container {
display: flex;
justify-content: center;
padding: 40px 20px;
}
.content-box {
background-color: #fff0f5;
border-radius: 24px;
padding: 30px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
width: 900px;
}
.title {
text-align: center;
font-size: 32px;
font-weight: bold;
margin-bottom: 40px;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background-color: #ffb6c1;
font-weight: bold;
padding: 12px;
text-align: center;
}
td {
padding: 12px;
text-align: center;
}
tr:not(:last-child) {
border-bottom: 1px solid #ddd;
}
</style>
</head>
<body>
<div class="container">
<div class="content-box">
<div class="title">{{ title }}</div>
<table>
<thead>
<tr>
<th>指令</th>
<th>描述</th>
<th>Tip</th>
</tr>
</thead>
<tbody>
{% for entry in data %}
<tr>
<td>{{ entry.command }}</td>
<td>{{ entry.description }}</td>
<td>{{ entry.tip }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
resource/plant/三七/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
resource/plant/三七/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
resource/plant/三七/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

BIN
resource/plant/三七/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
resource/plant/三七/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
resource/plant/丝瓜/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

BIN
resource/plant/丝瓜/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
resource/plant/丝瓜/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
resource/plant/丝瓜/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 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: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
resource/plant/冬瓜/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
resource/plant/冬瓜/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
resource/plant/冬瓜/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
resource/plant/冬瓜/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

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

Some files were not shown because too many files have changed in this diff Show More