完成对数据库的翻新

This commit is contained in:
Art_Sakura 2025-04-27 17:00:58 +08:00
parent 72baa5d095
commit b5334dfc01
16 changed files with 358 additions and 92 deletions

View File

@ -6,7 +6,7 @@
## 如何安装
方法一(推荐):在小真寻后台的插件商店下载即可
方法一(推荐):在小真寻后台的插件商店下载即可<br>
方法二:下载源码放在小真寻`plugin`目录下
## 使用指令
@ -22,22 +22,32 @@
| 收获 | 收获成熟作物 | |
| 铲除 | 铲除荒废作物 | |
| 我的作物 | | |
| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 作物不填默认清空仓库 数量不填默认全部 |
| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 |
| @美波理 偷菜 | 偷别人的菜 | 每人每天只能偷5次 |
| 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% |
---
## 更新日志[详细](./log/log.md)
- 感谢[quanquan1014](https://github.com/quanquan1014)对农场名称中包含特殊字符的处理。
- 更正数据库写法但是会导致V1.0用户的作物和种子丢失
- 完善我的农场图片资源,现在会在左上角显示经验、等级、金币等详细信息了
- 完善出售作物逻辑,现在可以空置作物名称来一键出售全部作物了,也可以选择空置数量来一键出售仓库种指定作物
- 完善播种逻辑,现在可以空置数量来一键播种指定作物了
- 新增更改农场名指令
- 改进对土地开垦条件判断
---
## 待办事宜 `Todo` 列表
- [x] 完善我的农场图片,例如左上角显示用户数据
- [x] 完善升级数据、作物数据、作物图片
- [x] 签到功能
- [x] 添加渔场功能
- [x] 增加活动、交易行功能
- [x] 增加交易行总行功能
- [x] 添加其他游戏种子素材
- [x] 想不到了,想到再说
- [ ] 完善升级数据、作物数据、作物图片
- [ ] 签到功能
- [ ] 添加渔场功能
- [ ] 增加活动、交易行功能
- [ ] 增加交易行总行功能
- [ ] 添加其他游戏种子素材
- [ ] 想不到了,想到再说
---

View File

@ -24,18 +24,19 @@ __plugin_meta__ = PluginMetadata(
种子商店 [页数]
购买种子 [作物/种子名称] [数量]
我的种子
播种 [作物/种子名称] [数量]
播种 [作物/种子名称] [数量] (数量不填默认将最大可能播种
收获
铲除
我的作物
出售作物 [作物/种子名称] [数量] (不填写作物
偷菜 at
出售作物 [作物/种子名称] [数量] (不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售
偷菜 at (每人每天只能偷5次
开垦
购买农场币 [数量] 数量为消耗金币的数量
更改农场名 [新农场名]
""".strip(),
extra=PluginExtraData(
author="Art_Sakura",
version="1.0",
version="1.1",
commands=[Command(command="我的农场")],
menu_type="群内小游戏",
configs=[

View File

@ -1,9 +1,9 @@
from nonebot.adapters import Event, MessageTemplate
from nonebot.rule import to_me
from nonebot.typing import T_State
from nonebot_plugin_alconna import (Alconna, AlconnaQuery, Args, Arparma, At,
Match, MultiVar, Option, Query, Subcommand,
on_alconna, store_true)
from nonebot_plugin_alconna import (Alconna, AlconnaMatch, AlconnaQuery, Args,
Arparma, At, Match, MultiVar, Option,
Query, Subcommand, on_alconna, store_true)
from nonebot_plugin_uninfo import Uninfo
from nonebot_plugin_waiter import waiter
@ -46,18 +46,18 @@ async def handle_register(session: Uninfo):
# 获取原始用户名并安全处理
raw_name = str(session.user.name)
safe_name = sanitize_username(raw_name)
# 初始化用户信息
success = await g_pSqlManager.initUserInfoByUid(
uid=uid,
name=safe_name,
exp=0,
point=100
point=500
)
msg = (
"✅ 农场开通成功!\n💼 初始资金:100农场币"
if success
"✅ 农场开通成功!\n💼 初始资金:500农场币"
if success
else "⚠️ 开通失败,请稍后再试"
)
logger.info(f"用户注册 {'成功' if success else '失败'}{uid}")
@ -65,9 +65,9 @@ async def handle_register(session: Uninfo):
except Exception as e:
msg = "⚠️ 系统繁忙,请稍后再试"
logger.error(f"注册异常 | UID:{uid} | 错误:{str(e)}")
await MessageUtils.build_message(msg).send(reply_to=True)
def sanitize_username(username: str, max_length: int = 15) -> str:
"""
安全处理用户名
@ -81,14 +81,14 @@ def sanitize_username(username: str, max_length: int = 15) -> str:
# 处理空值
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',
@ -97,24 +97,24 @@ def sanitize_username(username: str, max_length: int = 15) -> str:
}
# 添加常用中文字符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 ''
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]
@ -130,24 +130,24 @@ diuse_farm = on_alconna(
Subcommand("harvest", help_text="收获"),
Subcommand("eradicate", help_text="铲除"),
Subcommand("my-plant", help_text="我的作物"),
# Subcommand("reclamation", Args["isBool?", str], help_text="开垦"),
Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"),
Subcommand("stealing", Args["target?", At], help_text="偷菜"),
Subcommand("buy-point", Args["num?", int], help_text="购买农场币"),
#Subcommand("sell-point", Args["num?", int], help_text="转换金币")
Subcommand("change-name", Args["name?", str], help_text="更改农场名")
),
priority=5,
block=True,
)
@diuse_farm.assign("$main")
async def _(session: Uninfo, nickname: str = UserName()):
async def _(session: Uninfo):
uid = str(session.user.id)
if await isRegisteredByUid(uid) == False:
return
image = await g_pFarmManager.drawFarmByUid(uid, nickname)
image = await g_pFarmManager.drawFarmByUid(uid)
await MessageUtils.build_message(image).send(reply_to=True)
diuse_farm.shortcut(
@ -339,12 +339,7 @@ diuse_farm.shortcut(
)
@diuse_farm.assign("sell-plant")
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1),):
if not name.available:
await MessageUtils.build_message(
"请在指令后跟需要出售的作物名称"
).finish(reply_to=True)
async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1),):
uid = str(session.user.id)
if await isRegisteredByUid(uid) == False:
@ -401,3 +396,32 @@ async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)):
result = await g_pFarmManager.buyPointByUid(uid, num.result)
await MessageUtils.build_message(result).send(reply_to=True)
diuse_farm.shortcut(
"更改农场名(?P<name>)",
command="我的农场",
arguments=["change-name", "{name}"],
prefix=True,
)
@diuse_farm.assign("change-name")
async def _(session: Uninfo, name: Match[str]):
if not name.available:
await MessageUtils.build_message(
"请在指令后跟需要更改的用户名"
).finish(reply_to=True)
uid = str(session.user.id)
if await isRegisteredByUid(uid) == False:
return
safeName = sanitize_username(name.result)
result = await g_pSqlManager.updateUserNameByUid(uid, safeName)
if result == True:
await MessageUtils.build_message("更新用户名成功!").send(reply_to=True)
else:
await MessageUtils.build_message("更新用户名失败!").send(reply_to=True)

View File

@ -213,6 +213,21 @@
"sell": false
},
"番茄":
{
"level": 6,
"buy": 251,
"limit": 0,
"experience": 22,
"harvest": 21,
"price": 26,
"time": 17,
"crop": 1,
"again": 0,
"phase": 6,
"general": true,
"sell": false
},
"ATTomato":
{
"level": 6,
"buy": 99999,

4
config/sign_in.json Normal file
View File

@ -0,0 +1,4 @@
{
"date": "202505",
""
}

View File

@ -143,12 +143,20 @@ class CSqlManager:
"count": "INTEGER NOT NULL DEFAULT 0",
"PRIMARY KEY": "(uid, seed)"
}
# 5. 用户道具明细表
userItem = {
"uid": "INTEGER NOT NULL",
"item": "TEXT NOT NULL",
"count": "INTEGER NOT NULL DEFAULT 0",
"PRIMARY KEY": "(uid, item)"
}
#建表(或增列)
await cls.ensureTableSchema("user", userInfo)
await cls.ensureTableSchema("soil", userSoilInfo)
await cls.ensureTableSchema("userPlant", userPlant)
await cls.ensureTableSchema("userSeed", userSeed)
await cls.ensureTableSchema("userItem", userItem)
return True
@ -175,7 +183,7 @@ class CSqlManager:
return False
@classmethod
async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 100):
async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 500):
"""初始化用户信息
Args:
@ -190,11 +198,6 @@ class CSqlManager:
INSERT INTO user (uid, name, exp, point, soil, stealing) VALUES ({uid}, '{name}', {exp}, {point}, 3, '{date.today()}|5')
"""
#用户仓库
userStorehouse = f"""
INSERT INTO storehouse (uid) VALUES ({uid});
"""
#用户土地
userSoilInfo = f"""
INSERT INTO soil (uid) VALUES ({uid});
@ -203,9 +206,6 @@ class CSqlManager:
if not await cls.executeDB(userInfo):
return False
if not await cls.executeDB(userStorehouse):
return False
if not await cls.executeDB(userSoilInfo):
return False
@ -244,6 +244,55 @@ class CSqlManager:
logger.warning(f"getUserInfoByUid查询失败: {e}")
return {}
@classmethod
async def getUserNameByUid(cls, uid: str) -> str:
"""根据用户uid查询用户名
Args:
uid (str): 用户uid
Returns:
str: 用户名如果失败返回空字符串
"""
if not uid:
return ""
try:
async with cls.m_pDB.execute(
"SELECT name FROM user WHERE uid = ?",
(uid,)
) as cursor:
row = await cursor.fetchone()
return row["name"] if row else ""
except Exception as e:
logger.warning(f"真寻农场getUserNameByUid查询失败: {e}")
return ""
@classmethod
async def updateUserNameByUid(cls, uid: str, name: str) -> bool:
"""根据用户uid修改用户名
Args:
uid (str): 用户uid
name (str): 新用户名
Returns:
bool: 是否更新成功
"""
if not uid or not name:
return False
try:
async with cls._transaction():
await cls.m_pDB.execute(
"UPDATE user SET name = ? WHERE uid = ?",
(name, uid)
)
return True
except Exception as e:
logger.warning(f"真寻农场updateUserNameByUid失败: {e}")
return False
@classmethod
async def getUserPointByUid(cls, uid: str) -> int:
"""根据用户Uid获取用户农场币
@ -709,4 +758,152 @@ class CSqlManager:
logger.warning(f"真寻农场deleteUserPlantByName 失败: {e}")
return False
@classmethod
async def getUserItemByName(cls, uid: str, item: str) -> Optional[int]:
"""根据道具名称查询某一项数量
Args:
uid (str): 用户uid
item (str): 道具名称
Returns:
Optional[int]: 数量不存在返回None
"""
if not uid or not item:
return None
try:
async with cls.m_pDB.execute(
"SELECT count FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
) as cursor:
row = await cursor.fetchone()
return row[0] if row else None
except Exception as e:
logger.warning(f"真寻农场getUserItemByName查询失败: {e}")
return None
@classmethod
async def getUserItemByUid(cls, uid: str) -> dict:
"""根据用户Uid获取全部道具信息
Args:
uid (str): 用户uid
Returns:
dict: {itemName: count, ...}
"""
if not uid:
return {}
try:
cursor = await cls.m_pDB.execute(
"SELECT item, count FROM userItem WHERE uid = ?",
(uid,)
)
rows = await cursor.fetchall()
return {row["item"]: row["count"] for row in rows}
except Exception as e:
logger.warning(f"真寻农场getUserItemByUid查询失败: {e}")
return {}
@classmethod
async def deleteUserItemByName(cls, uid: str, item: str) -> bool:
"""根据道具名删除道具
Args:
uid (str): 用户uid
item (str): 道具名称
Returns:
bool: 是否删除成功
"""
if not uid or not item:
return False
try:
async with cls._transaction():
await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
)
return True
except Exception as e:
logger.warning(f"真寻农场deleteUserItemByName失败: {e}")
return False
@classmethod
async def updateUserItemByName(cls, uid: str, item: str, count: int) -> bool:
"""根据道具名直接更新道具数量
Args:
uid (str): 用户uid
item (str): 道具名称
count (int): 要更新的新数量
Returns:
bool: 是否更新成功
"""
if not uid or not item:
return False
try:
async with cls._transaction():
if count <= 0:
await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
)
else:
await cls.m_pDB.execute(
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
(count, uid, item)
)
return True
except Exception as e:
logger.warning(f"真寻农场updateUserItemByName失败: {e}")
return False
@classmethod
async def addUserItemByUid(cls, uid: str, item: str, count: int = 1) -> bool:
"""根据用户uid添加道具信息
Args:
uid (str): 用户uid
item (str): 道具名称
count (int, optional): 数量.Defaults to 1.
Returns:
bool: 是否添加成功
"""
if not uid or not item:
return False
try:
async with cls._transaction():
async with cls.m_pDB.execute(
"SELECT count FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
) as cursor:
row = await cursor.fetchone()
if row:
newCount = row[0] + count
if newCount <= 0:
await cls.m_pDB.execute(
"DELETE FROM userItem WHERE uid = ? AND item = ?",
(uid, item)
)
else:
await cls.m_pDB.execute(
"UPDATE userItem SET count = ? WHERE uid = ? AND item = ?",
(newCount, uid, item)
)
else:
if count > 0:
await cls.m_pDB.execute(
"INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)",
(uid, item, count)
)
return True
except Exception as e:
logger.warning(f"真寻农场addUserItemByUid失败: {e}")
return False
g_pSqlManager = CSqlManager()

View File

@ -51,7 +51,7 @@ class CFarmManager:
return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{number}"
@classmethod
async def drawFarmByUid(cls, uid: str, name: str) -> bytes:
async def drawFarmByUid(cls, uid: str) -> bytes:
"""绘制用户农场
Args:
@ -72,7 +72,9 @@ class CFarmManager:
await grass.resize(0, soilSize[0], soilSize[1])
soilPos = g_pJsonManager.m_pSoil['soil']
soilUnlock = await g_pSqlManager.getUserSoilByUid(uid)
userInfo = await g_pSqlManager.getUserInfoByUid(uid)
soilUnlock = int(userInfo['soil'])
x = 0
y = 0
@ -128,7 +130,7 @@ class CFarmManager:
await img.paste(frame, (75, 44))
#用户名
nameImg = await BuildImage.build_text_image(name, size = 24, font_color = (77, 35, 4))
nameImg = await BuildImage.build_text_image(userInfo['name'], size = 24, font_color = (77, 35, 4))
await img.paste(nameImg, (300, 92))
#经验值
@ -148,12 +150,10 @@ class CFarmManager:
await img.paste(levelImg, (660, 187))
#金币
point = await g_pSqlManager.getUserPointByUid(uid)
pointImg = await BuildImage.build_text_image(str(point), size = 24, font_color = (253, 253, 253))
pointImg = await BuildImage.build_text_image(str(userInfo['point']), size = 24, font_color = (253, 253, 253))
await img.paste(pointImg, (330, 255))
#点券
bonds = await g_pSqlManager.getUserPointByUid(uid)
#点券 TODO
bondsImg = await BuildImage.build_text_image("0", size = 24, font_color = (253, 253, 253))
await img.paste(bondsImg, (570, 255))
@ -473,14 +473,14 @@ class CFarmManager:
if plant is None:
result = await ImageTemplate.table_page(
"作物仓库",
"播种示例:@小真寻 出售作物 大白菜 [数量]",
"出售示例:@小真寻 出售作物 大白菜 [数量]",
column_name,
data_list,
)
return result.pic2bytes()
sell = ""
for name, count in plant.items(): # 使用 .items() 来遍历字典
for name, count in plant.items():
plantInfo = g_pJsonManager.m_pPlant['plant'][name]
icon = ""
icon_path = g_sResourcePath / f"plant/{name}/icon.png"
@ -507,7 +507,7 @@ class CFarmManager:
result = await ImageTemplate.table_page(
"作物仓库",
"播种示例:@小真寻 出售作物 大白菜 [数量]",
"出售示例:@小真寻 出售作物 大白菜 [数量]",
column_name,
data_list,
)

View File

@ -23,7 +23,7 @@ class CShopManager:
column_name = [
"-",
"种子名称",
"种子单价"
"种子单价",
"解锁等级",
"果实单价",
"收获经验",
@ -132,39 +132,39 @@ class CShopManager:
Returns:
str:
"""
plantDict = await g_pSqlManager.getUserPlantByUid(uid)
if not plantDict:
if not isinstance(name, str) or name.strip() == "":
name = ""
plant = await g_pSqlManager.getUserPlantByUid(uid)
if not plant:
return "你仓库没有可以出售的作物"
totalPoint = 0
point = 0
totalSold = 0
isAll = (num == -1)
if not name:
for plantName, count in plantDict.items():
if name == "":
for plantName, count in plant.items():
plantInfo = g_pJsonManager.m_pPlant['plant'][plantName]
totalPoint += plantInfo['price'] * count
await g_pSqlManager.deleteUserPlantByName(uid, plantName)
point += plantInfo['price'] * count
await g_pSqlManager.updateUserPlantByName(uid, plantName, 0)
else:
currentCount = plantDict.get(name)
if currentCount is None:
return f"出售作物{name}出错:你没有这种作物"
if name not in plant:
return f"出售作物{name}出错:仓库中不存在该作物"
available = plant[name]
sellAmount = available if isAll else min(available, num)
if sellAmount <= 0:
return f"出售作物{name}出错:数量不足"
await g_pSqlManager.updateUserPlantByName(uid, name, available - sellAmount)
totalSold = sellAmount
if num == -1:
sellCount = currentCount
else:
if num > currentCount:
return f"出售作物{name}出错:数量不足"
sellCount = num
totalPoint = point if name == "" else totalSold * g_pJsonManager.m_pPlant['plant'][name]['price']
currentPoint = await g_pSqlManager.getUserPointByUid(uid)
await g_pSqlManager.updateUserPointByUid(uid, currentPoint + totalPoint)
plantInfo = g_pJsonManager.m_pPlant['plant'][name]
totalPoint = plantInfo['price'] * sellCount
await g_pSqlManager.addUserPlantByUid(uid, name, -sellCount)
point = await g_pSqlManager.getUserPointByUid(uid)
await g_pSqlManager.updateUserPointByUid(uid, point + totalPoint)
if not name:
return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{point + totalPoint}"
if name == "":
return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}"
else:
return f"成功出售{name},获得农场币:{totalPoint},当前农场币:{point + totalPoint}"
return f"成功出售{name},获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}"
g_pShopManager = CShopManager()

15
log/log.md Normal file
View File

@ -0,0 +1,15 @@
# 真寻农场更新日志
## V1.1
- 感谢[quanquan1014](https://github.com/quanquan1014)对农场名称中包含特殊字符的处理。
- 更正数据库写法但是会导致V1.0用户的作物和种子丢失
- 完善我的农场图片资源,现在会在左上角显示经验、等级、金币等详细信息了
- 完善出售作物逻辑,现在可以空置作物名称来一键出售全部作物了,也可以选择空置数量来一键出售仓库种指定作物
- 完善播种逻辑,现在可以空置数量来一键播种指定作物了
- 新增更改农场名指令
- 改进对土地开垦条件判断
## V1.0
世界的起源。

View File

@ -1,5 +1,4 @@
from email.mime import base
from unittest import result
import os
import httpx
@ -11,11 +10,11 @@ class CRequestManager:
@classmethod
async def download(cls, url: str, savePath: str, fileName: str) -> bool:
"""下载文件到指定路径
"""下载文件到指定路径并覆盖已存在的文件
Args:
url (str): 文件的下载链接
savePath (str): 保存文件的文件夹路径
savePath (str): 保存文件夹路径
fileName (str): 保存后的文件名
Returns:
@ -25,7 +24,8 @@ class CRequestManager:
async with httpx.AsyncClient(timeout=10.0) as client:
response = await client.get(url)
if response.status_code == 200:
fullPath = savePath.rstrip("/") + "/" + fileName
fullPath = os.path.join(savePath, fileName)
os.makedirs(os.path.dirname(fullPath), exist_ok=True)
with open(fullPath, "wb") as f:
f.write(response.content)
return True

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB