diff --git a/README.md b/README.md index d2ceed6..56b2fe7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,16 @@ +
+ # 真寻农场(zhenxun_plugin_farm) +

+ + license + +

你是说可以种地对吧🤔? +
+ --- ## 如何安装 diff --git a/__init__.py b/__init__.py index bc7238f..12193d0 100644 --- a/__init__.py +++ b/__init__.py @@ -82,8 +82,6 @@ async def start(): await g_pDBService.init() - # await g_pRequestManager.initSignInFile() - # 析构函数 @driver.on_shutdown async def shutdown(): diff --git a/command.py b/command.py index 55097bb..53cded2 100644 --- a/command.py +++ b/command.py @@ -1,5 +1,3 @@ -from datetime import date, datetime, timedelta - from nonebot.adapters import Event, MessageTemplate from nonebot.rule import to_me from nonebot_plugin_alconna import (Alconna, AlconnaMatch, AlconnaQuery, Args, @@ -12,9 +10,12 @@ from zhenxun.configs.config import BotConfig from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils +from .config import g_bSignStatus from .dbService import g_pDBService from .farm.farm import g_pFarmManager from .farm.shop import g_pShopManager +from .json import g_pJsonManager +from .tool import g_pToolManager async def isRegisteredByUid(uid: str) -> bool: @@ -136,7 +137,7 @@ diuse_farm = on_alconna( Subcommand("buy-point", Args["num?", int], help_text="购买农场币"), #Subcommand("sell-point", Args["num?", int], help_text="转换金币") Subcommand("change-name", Args["name?", str], help_text="更改农场名"), - Subcommand("sing-in", help_text="农场签到"), + Subcommand("sign-in", help_text="农场签到"), ), priority=5, block=True, @@ -168,7 +169,7 @@ async def _(session: Uninfo): info = await g_pFarmManager.drawDetailFarmByUid(uid) - 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() diuse_farm.shortcut( "我的农场币", @@ -446,30 +447,55 @@ async def _(session: Uninfo, name: Match[str]): else: await MessageUtils.build_message("更新用户名失败!").send(reply_to=True) -reclamation = on_alconna( - Alconna("农场签到"), - priority=5, - block=True, +diuse_farm.shortcut( + "农场签到", + command="我的农场", + arguments=["sign-in"], + prefix=True, ) -@reclamation.handle() +@diuse_farm.assign("sign-in") async def _(session: Uninfo): uid = str(session.user.id) if await isRegisteredByUid(uid) == False: return - toDay = date.today() + #判断签到是否正常加载 + if g_bSignStatus == False: + await MessageUtils.build_message("签到功能异常!").send() + return + + toDay = g_pToolManager.dateTime().date().today() message = "" status = await g_pDBService.userSign.sign(uid, toDay.strftime("%Y-%m-%d")) - if status == True: - message = "签到成功" + #如果完成签到 + if status == 1 or status == 2: + #获取签到总天数 + signDay = await g_pDBService.userSign.getUserSignCountByDate(uid, toDay.strftime("%Y-%m")) + exp, point = await g_pDBService.userSign.getUserSignRewardByDate(uid, toDay.strftime("%Y-%m-%d")) - img = await g_pDBService.userSign.drawSignCalendarImage(uid, toDay.year, toDay.month) + message += f"签到成功!累计签到天数:{signDay}\n获得经验{exp},获得金币{point}" - await MessageUtils.build_message(img).send() + reward = g_pJsonManager.m_pSign['continuou'].get(f"{signDay}", None) + + if reward: + extraPoint = reward.get('point', 0) + extraExp = reward.get('exp', 0) + + plant = reward.get('plant', {}) + + message += f"\n\n成功领取累计签到奖励:\n额外获得经验{extraExp},额外获得金币{extraPoint}" + + if plant: + for key, value in plant.items(): + message += f"\n获得{key}种子 * {value}" + else: + message = "签到失败!未知错误" + + await MessageUtils.build_message(message).send() # await MessageUtils.alc_forward_msg([info], session.self_id, BotConfig.self_nickname).send(reply_to=True) diff --git a/config.py b/config.py index ffccb92..7a7a2bc 100644 --- a/config.py +++ b/config.py @@ -10,3 +10,5 @@ g_sPlantPath = g_sResourcePath / "db/plant.db" g_sConfigPath = Path(__file__).resolve().parent / "config" g_sSignInPath = g_sConfigPath / "sign_in.json" + +g_bSignStatus = True diff --git a/database/user.py b/database/user.py index fefa449..efd12a0 100644 --- a/database/user.py +++ b/database/user.py @@ -1,10 +1,10 @@ import math -from datetime import date, datetime, timedelta from typing import List, Union from zhenxun.services.log import logger from .database import CSqlManager +from ..tool import g_pToolManager class CUserDB(CSqlManager): @@ -35,7 +35,7 @@ class CUserDB(CSqlManager): Returns: Union[bool, str]: False 表示失败,字符串表示成功信息 """ - nowStr = date.today().strftime('%Y-%m-%d') + nowStr = g_pToolManager.dateTime().date().today().strftime('%Y-%m-%d') sql = ( f"INSERT INTO user (uid, name, exp, point, soil, stealTime, stealCount) " f"VALUES ({uid}, '{name}', {exp}, {point}, 3, '{nowStr}', 5)" diff --git a/database/userSign.py b/database/userSign.py index 2d21beb..e43b053 100644 --- a/database/userSign.py +++ b/database/userSign.py @@ -1,4 +1,5 @@ import calendar +import random from datetime import date, datetime, timedelta from typing import Optional @@ -8,6 +9,7 @@ from zhenxun.utils._build_image import BuildImage from ..dbService import g_pDBService from ..json import g_pJsonManager from .database import CSqlManager +from ..tool import g_pToolManager class CUserSignDB(CSqlManager): @@ -18,8 +20,9 @@ class CUserSignDB(CSqlManager): "uid": "TEXT NOT NULL", #用户ID "signDate": "DATE NOT NULL", #签到日期 "isSupplement": "TINYINT NOT NULL DEFAULT 0", #是否补签 - "rewardType": "VARCHAR(20) DEFAULT ''", #奖励类型 - "createdAt": "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP", #创建时间 + "exp": "INT NOT NULL DEFAULT 0", #当天签到经验 + "point": "INT NOT NULL DEFAULT 0", #当天签到金币 + "createdAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')",#创建时间 "PRIMARY KEY": "(uid, signDate)" } @@ -32,22 +35,52 @@ class CUserSignDB(CSqlManager): "lastSignDate": "DATE DEFAULT NULL", #上次签到日期 "continuousDays": "INT NOT NULL DEFAULT 0", #连续签到天数 "supplementCount": "INT NOT NULL DEFAULT 0", #补签次数 - "updatedAt": "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP", #更新时间 + "updatedAt": "DATETIME NOT NULL DEFAULT (datetime(CURRENT_TIMESTAMP, 'localtime')"#更新时间 } await cls.ensureTableSchema("userSignLog", userSignLog) await cls.ensureTableSchema("userSignSummary", userSignSummary) @classmethod - async def getUserSignCountByDate(cls, uid: str, monthStr: str) -> int: + async def getUserSignRewardByDate(cls, uid: str, date: str) -> tuple[int, int]: + """根据指定日期获取用户签到随机奖励 + + Args: + uid (str): 用户Uid + date (str): 用户签到日期 示例:2025-05-27 + + Returns: + tuple[int, int]: 经验、金币 """ + try: + async with cls._transaction(): + async with cls.m_pDB.execute( + "SELECT exp, point FROM userSignLog WHERE uid=? AND signDate=?", + (uid, date) + ) as cursor: + row = await cursor.fetchone() + + if row is None: + return 0, 0 + + exp = row['exp'] + point = row['point'] + + return exp, point + except Exception as e: + logger.warning("获取用户签到数据失败", e=e) + return 0, 0 + + @classmethod + async def getUserSignCountByDate(cls, uid: str, monthStr: str) -> int: + """根据日期查询用户签到总天数 Args: uid (str): 用户Uid monthStr (str): 需要查询的日期 示例: 2025-05 Returns: - int: _description_ + int: 查询月总签到天数 """ try: sql = "SELECT COUNT(*) FROM userSignLog WHERE uid=? AND signDate LIKE ?" @@ -80,21 +113,43 @@ class CUserSignDB(CSqlManager): return False @classmethod - async def sign(cls, uid: str, signDate: str = '') -> bool: + async def sign(cls, uid: str, signDate: str = '') -> int: + """签到 + + Args: + uid (int): 用户ID + signDate (str): 日期字符串 'YYYY-MM-DD' 不传默认当前系统日期 + + Returns: + bool: 0: 签到失败 1: 签到成功 2: 重复签到 + """ try: if not signDate: - signDate = date.today().strftime("%Y-%m-%d") + signDate = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d") if await cls.hasSigned(uid, signDate): - return False + return 2 - todayStr = date.today().strftime("%Y-%m-%d") + todayStr = g_pToolManager.dateTime().date().today().strftime("%Y-%m-%d") isSupplement = 0 if signDate == todayStr else 1 + expMax, expMin, pointMax, pointMin = [ + g_pJsonManager.m_pSign.get(key, default) + for key, default in ( + ("exp_max", 50), + ("exp_min", 5), + ("point_max", 2000), + ("point_min", 200), + ) + ] + + exp = random.randint(expMin, expMax) + point = random.randint(pointMin, pointMax) + async with cls._transaction(): await cls.m_pDB.execute( - "INSERT INTO userSignLog (uid, signDate, isSupplement, rewardType) VALUES (?, ?, ?, '')", - (uid, signDate, isSupplement) + "INSERT INTO userSignLog (uid, signDate, isSupplement, exp, point) VALUES (?, ?, ?, ?, ?)", + (uid, signDate, isSupplement, exp, point) ) cursor = await cls.m_pDB.execute("SELECT * FROM userSignSummary WHERE uid=?", (uid,)) @@ -103,8 +158,9 @@ class CUserSignDB(CSqlManager): currentMonth = signDate[:7] if row: monthSignDays = row['monthSignDays'] + 1 if row['currentMonth'] == currentMonth else 1 + totalSignDays = row['totalSignDays'] lastDate = row['lastSignDate'] - prevDate = (datetime.strptime(signDate, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d") + 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( @@ -121,6 +177,7 @@ class CUserSignDB(CSqlManager): (currentMonth, monthSignDays, signDate, continuousDays, supplementCount, uid) ) else: + totalSignDays = 1 await cls.m_pDB.execute( """ INSERT INTO userSignSummary @@ -129,10 +186,31 @@ class CUserSignDB(CSqlManager): """, (uid, 1, currentMonth, 1, signDate, 1, 1 if isSupplement else 0) ) - return True + + #计算累签奖励 + reward = g_pJsonManager.m_pSign['continuou'].get(f"{totalSignDays + 1}", None) + + if reward: + point += reward.get('point', 0) + exp += reward.get('exp', 0) + + plant = reward.get('plant', {}) + + if plant: + for key, value in plant.items(): + await g_pDBService.userSeed.addUserSeedByUid(uid, key, value) + + #向数据库更新 + currentExp = await g_pDBService.user.getUserExpByUid(uid) + await g_pDBService.user.updateUserExpByUid(uid, currentExp + exp) + + currentPoint = await g_pDBService.user.getUserPointByUid(uid) + await g_pDBService.user.updateUserPointByUid(uid, currentPoint + point) + + return 1 except Exception as e: logger.warning("执行签到失败", e=e) - return False + return 0 @classmethod async def drawSignCalendarImage(cls, uid: str, year: int, month: int): diff --git a/database/userSoil.py b/database/userSoil.py index a8cf066..5500637 100644 --- a/database/userSoil.py +++ b/database/userSoil.py @@ -1,4 +1,3 @@ -from datetime import date, datetime, timedelta from typing import Optional from zhenxun.services.log import logger @@ -6,6 +5,7 @@ from zhenxun.services.log import logger from ..dbService import g_pDBService from ..json import g_pJsonManager from .database import CSqlManager +from ..tool import g_pToolManager class CUserSoilDB(CSqlManager): @@ -312,7 +312,7 @@ class CUserSoilDB(CSqlManager): logger.error(f"未知植物: {plantName}") return False - nowTs = int(datetime.now().timestamp()) + nowTs = int(g_pToolManager.dateTime().now().timestamp()) matureTs = nowTs + int(plantCfg.get("time", 0)) * 3600 try: diff --git a/farm/farm.py b/farm/farm.py index 7d318c4..6708c55 100644 --- a/farm/farm.py +++ b/farm/farm.py @@ -1,7 +1,6 @@ import asyncio import math import random -from datetime import date, datetime from typing import Dict, List, Tuple from zhenxun.configs.config import Config @@ -16,6 +15,7 @@ from ..config import g_sResourcePath from ..dbService import g_pDBService from ..event.event import g_pEventManager from ..json import g_pJsonManager +from ..tool import g_pToolManager class CFarmManager: @@ -211,7 +211,7 @@ class CFarmManager: soilStatus = "-" plantNumber = "-" else: - matureTime = datetime.fromtimestamp(int(soilInfo.get("matureTime", 0))).strftime("%Y-%m-%d %H:%M:%S.%f") + matureTime = g_pToolManager.dateTime().fromtimestamp(int(soilInfo.get("matureTime", 0))).strftime("%Y-%m-%d %H:%M:%S.%f") soilStatus = await g_pDBService.userSoil.getUserSoilStatus(uid, i) num = await g_pDBService.userSteal.getTotalStolenCount(uid, i) @@ -286,8 +286,8 @@ class CFarmManager: logger.error(f"绘制植物资源失败: {soilInfo['plantName']}") return False, None, False #type: ignore - currentTime = datetime.now() - matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) + currentTime = g_pToolManager.dateTime().now() + matureTime = g_pToolManager.dateTime().fromtimestamp(int(soilInfo['matureTime'])) #如果当前时间大于成熟时间 说明作物成熟 if currentTime >= matureTime: @@ -303,7 +303,7 @@ class CFarmManager: return True, plant, False #如果没有成熟 则根据当前阶段进行绘制 - plantedTime = datetime.fromtimestamp(int(soilInfo['plantTime'])) + plantedTime = g_pToolManager.dateTime().fromtimestamp(int(soilInfo['plantTime'])) elapsedTime = currentTime - plantedTime elapsedHour = elapsedTime.total_seconds() / 3600 @@ -484,8 +484,8 @@ class CFarmManager: if not plantInfo: continue - currentTime = datetime.now() - matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) + currentTime = g_pToolManager.dateTime().now() + matureTime = g_pToolManager.dateTime().fromtimestamp(int(soilInfo['matureTime'])) if currentTime >= matureTime: number = plantInfo['harvest'] @@ -664,10 +664,10 @@ class CFarmManager: stealCount = int(userInfo['stealCount']) if stealTime == '': - stealTime = date.today().strftime('%Y-%m-%d') + stealTime = g_pToolManager.dateTime().date().today().strftime('%Y-%m-%d') stealCount = 5 - elif date.fromisoformat(stealTime) != date.today(): - stealTime = date.today().strftime('%Y-%m-%d') + elif g_pToolManager.dateTime().date().fromisoformat(stealTime) != g_pToolManager.dateTime().date().today(): + stealTime = g_pToolManager.dateTime().date().today().strftime('%Y-%m-%d') stealCount = 5 if stealCount <= 0: @@ -697,8 +697,8 @@ class CFarmManager: if not plantInfo: continue - currentTime = datetime.now() - matureTime = datetime.fromtimestamp(int(soilInfo['matureTime'])) + currentTime = g_pToolManager.dateTime().now() + matureTime = g_pToolManager.dateTime().fromtimestamp(int(soilInfo['matureTime'])) if currentTime >= matureTime: #如果偷过,则跳过该土地 @@ -728,10 +728,10 @@ class CFarmManager: await g_pDBService.userSoil.updateUserSoil(uid, i, "lastResetTime", int(currentTime.timestamp())) await g_pDBService.userSoil.updateUserSoil(uid, i, "matureTime", matureTs) - await g_pDBService.userSteal.addStealRecord(target, i, uid, randomNumber, int(datetime.now().timestamp())) + await g_pDBService.userSteal.addStealRecord(target, i, uid, randomNumber, int(g_pToolManager.dateTime().now().timestamp())) else: - await g_pDBService.userSteal.addStealRecord(target, i, uid, randomNumber, int(datetime.now().timestamp())) + await g_pDBService.userSteal.addStealRecord(target, i, uid, randomNumber, int(g_pToolManager.dateTime().now().timestamp())) if isStealingPlant <= 0 and isStealingNumber <= 0: return "目标没有作物可以被偷" diff --git a/json.py b/json.py index 422dcc0..15ecf7e 100644 --- a/json.py +++ b/json.py @@ -3,12 +3,16 @@ from pathlib import Path from zhenxun.services.log import logger +from . import config +from .request import g_pRequestManager + class CJsonManager: def __init__(self): self.m_pItem = {} self.m_pLevel = {} self.m_pSoil = {} + self.m_pSign = {} async def init(self) -> bool: if not await self.initItem(): @@ -20,14 +24,17 @@ class CJsonManager: if not await self.initSoil(): return False - return True + if not await g_pRequestManager.initSignInFile(): + config.g_bSignStatus = False + + return False + else: + return await self.initSign() async def initItem(self) -> bool: - current_file_path = Path(__file__) - try: with open( - current_file_path.resolve().parent / "config/item.json", + config.g_sConfigPath / "item.json", encoding="utf-8", ) as file: self.m_pItem = json.load(file) @@ -41,11 +48,9 @@ class CJsonManager: return False async def initLevel(self) -> bool: - current_file_path = Path(__file__) - try: with open( - current_file_path.resolve().parent / "config/level.json", + config.g_sConfigPath / "level.json", encoding="utf-8", ) as file: self.m_pLevel = json.load(file) @@ -59,11 +64,9 @@ class CJsonManager: return False async def initSoil(self) -> bool: - current_file_path = Path(__file__) - try: with open( - current_file_path.resolve().parent / "config/soil.json", + config.g_sConfigPath / "soil.json", encoding="utf-8", ) as file: self.m_pSoil = json.load(file) @@ -76,4 +79,20 @@ class CJsonManager: logger.warning(f"soil.json JSON格式错误: {e}") return False + async def initSign(self) -> bool: + try: + with open( + config.g_sSignInPath, + encoding="utf-8", + ) as file: + self.m_pSign = json.load(file) + + return True + except FileNotFoundError: + logger.warning("sign_in.json 打开失败") + return False + except json.JSONDecodeError as e: + logger.warning(f"sign_in.json JSON格式错误: {e}") + return False + g_pJsonManager = CJsonManager() diff --git a/request.py b/request.py index ae53da0..91967dc 100644 --- a/request.py +++ b/request.py @@ -36,7 +36,6 @@ class CRequestManager: try: async with httpx.AsyncClient(timeout=10.0) as client: - # 动态组装请求参数 requestArgs: dict = {"headers": headers} if params: requestArgs["params"] = params @@ -60,7 +59,7 @@ class CRequestManager: return False @classmethod - async def post(cls, endpoint: str, name: str = "", jsonData: dict = None) -> dict: + async def post(cls, endpoint: str, name: str = "", jsonData: dict = {}) -> dict: """发送POST请求到指定接口,统一调用,仅支持JSON格式数据 Args: @@ -74,9 +73,6 @@ class CRequestManager: Returns: dict: 返回请求结果的JSON数据 """ - if jsonData is None: - raise ValueError("post请求必须提供jsonData") - baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") url = f"{baseUrl.rstrip('/')}/{endpoint.lstrip('/')}" headers = {"token": cls.m_sTokens} @@ -129,43 +125,44 @@ class CRequestManager: return {} @classmethod - async def initSignInFile(cls): + async def initSignInFile(cls) -> bool: if os.path.exists(g_sSignInPath): - try: with open(g_sSignInPath, "r", encoding="utf-8") as f: content = f.read() sign = json.loads(content) date = sign.get("date", "") - yearMonth = datetime.now().strftime("%Y%m") + yearMonth = g_pToolManager.dateTime().now().strftime("%Y%m") if date == yearMonth: logger.debug("真寻农场签到文件检查完毕") + return True else: logger.warning("真寻农场签到文件检查失败, 即将下载") - await cls.downloadSignInFile() + return await cls.downloadSignInFile() except json.JSONDecodeError as e: logger.warning(f"真寻农场签到文件格式错误, 即将下载") - await cls.downloadSignInFile() + return await cls.downloadSignInFile() else: - await cls.downloadSignInFile() + return await cls.downloadSignInFile() @classmethod - async def downloadSignInFile(cls): - baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") + async def downloadSignInFile(cls) -> bool: + try: + baseUrl = Config.get_config("zhenxun_plugin_farm", "服务地址") - url = f"{baseUrl.rstrip('/')}/sign_in" - url = f"http://127.0.0.1:8998/sign_in" + url = f"{baseUrl.rstrip('/')}:8998/sign_in" + path = str(g_sSignInPath.parent.resolve(strict=False)) + yearMonth = g_pToolManager.dateTime().now().strftime("%Y%m") - path = str(g_sSignInPath.parent.resolve(strict=False)) - yearMonth = datetime.now().strftime("%Y%m") + await cls.download(url, path, "signTemp.json", jsonData={'date':yearMonth}) + g_pToolManager.renameFile(f"{path}/signTemp.json", "sign_in.json") - logger.info(f"{yearMonth}") - yearMonth = "202506" - - await cls.download(url, path, "signTemp.json", jsonData={'date':yearMonth}) - g_pToolManager.renameFile(f"{path}/signTemp.json", "sign_in.json") + return True + except Exception as e: + logger.error("下载签到文件失败", e=e) + return False g_pRequestManager = CRequestManager() diff --git a/tool.py b/tool.py index 640bb95..58df77a 100644 --- a/tool.py +++ b/tool.py @@ -1,4 +1,6 @@ import os +from datetime import datetime +from zoneinfo import ZoneInfo from zhenxun.services.log import logger @@ -29,4 +31,9 @@ class CToolManager: logger.warning(f"文件重命名失败: {e}") return False + @classmethod + def dateTime(cls) -> datetime: + tz = ZoneInfo("Asia/Shanghai") + return datetime.now(tz) + g_pToolManager = CToolManager()