diff --git a/zhenxun/builtin_plugins/chat_history/chat_message_handle.py b/zhenxun/builtin_plugins/chat_history/chat_message_handle.py index 10cfcf43..d9eae97f 100644 --- a/zhenxun/builtin_plugins/chat_history/chat_message_handle.py +++ b/zhenxun/builtin_plugins/chat_history/chat_message_handle.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from io import BytesIO from nonebot.plugin import PluginMetadata from nonebot_plugin_alconna import ( @@ -14,35 +15,38 @@ from nonebot_plugin_alconna import ( from nonebot_plugin_session import EventSession import pytz -from zhenxun.configs.utils import Command, PluginExtraData +from zhenxun.configs.config import Config +from zhenxun.configs.utils import Command, PluginExtraData, RegisterConfig from zhenxun.models.chat_history import ChatHistory from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import ImageTemplate +from zhenxun.utils.image_utils import BuildImage, ImageTemplate from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils __plugin_meta__ = PluginMetadata( name="消息统计", description="消息统计查询", usage=""" 格式: - 消息排行 ?[type [日,周,月,年]] ?[--des] + 消息排行 ?[type [日,周,月,季,年]] ?[--des] 快捷: - [日,周,月,年]消息排行 ?[数量] + [日,周,月,季,年]消息排行 ?[数量] 示例: 消息排行 : 所有记录排行 日消息排行 : 今日记录排行 - 周消息排行 : 今日记录排行 - 月消息排行 : 今日记录排行 - 年消息排行 : 今日记录排行 + 周消息排行 : 本周记录排行 + 月消息排行 : 本月记录排行 + 季消息排行 : 本季度记录排行 + 年消息排行 : 本年记录排行 消息排行 周 --des : 逆序周记录排行 """.strip(), extra=PluginExtraData( author="HibiKier", - version="0.1", + version="0.2", plugin_type=PluginType.NORMAL, menu_type="数据统计", commands=[ @@ -50,8 +54,19 @@ __plugin_meta__ = PluginMetadata( Command(command="日消息统计"), Command(command="周消息排行"), Command(command="月消息排行"), + Command(command="季消息排行"), Command(command="年消息排行"), ], + configs=[ + RegisterConfig( + module="chat_history", + key="SHOW_QUIT_MEMBER", + value=True, + help="是否在消息排行中显示已退群用户", + default_value=True, + type=bool, + ) + ], ).to_dict(), ) @@ -60,7 +75,7 @@ _matcher = on_alconna( Alconna( "消息排行", Option("--des", action=store_true, help_text="逆序"), - Args["type?", ["日", "周", "月", "年"]]["count?", int, 10], + Args["type?", ["日", "周", "月", "季", "年"]]["count?", int, 10], ), aliases={"消息统计"}, priority=5, @@ -68,7 +83,7 @@ _matcher = on_alconna( ) _matcher.shortcut( - r"(?P['日', '周', '月', '年'])?消息(排行|统计)\s?(?P\d+)?", + r"(?P['日', '周', '月', '季', '年'])?消息(排行|统计)\s?(?P\d+)?", command="消息排行", arguments=["{type}", "{cnt}"], prefix=True, @@ -96,20 +111,57 @@ async def _( date_scope = (time_now - timedelta(days=7), time_now) elif date in ["月"]: date_scope = (time_now - timedelta(days=30), time_now) - column_name = ["名次", "昵称", "发言次数"] + elif date in ["季"]: + date_scope = (time_now - timedelta(days=90), time_now) + column_name = ["名次", "头像", "昵称", "发言次数"] + show_quit_member = Config.get_config("chat_history", "SHOW_QUIT_MEMBER", True) + + fetch_count = count.result + if not show_quit_member: + fetch_count = count.result * 2 + if rank_data := await ChatHistory.get_group_msg_rank( - group_id, count.result, "DES" if arparma.find("des") else "DESC", date_scope + group_id, fetch_count, "DES" if arparma.find("des") else "DESC", date_scope ): idx = 1 data_list = [] + for uid, num in rank_data: - if user := await GroupInfoUser.filter( + if len(data_list) >= count.result: + break + + user_in_group = await GroupInfoUser.filter( user_id=uid, group_id=group_id - ).first(): - user_name = user.user_name + ).first() + + if not user_in_group and not show_quit_member: + continue + + if user_in_group: + user_name = user_in_group.user_name else: - user_name = uid - data_list.append([idx, user_name, num]) + user_name = f"{uid}(已退群)" + + avatar_size = 40 + try: + avatar_bytes = await PlatformUtils.get_user_avatar(str(uid), "qq") + if avatar_bytes: + avatar_img = BuildImage( + avatar_size, avatar_size, background=BytesIO(avatar_bytes) + ) + await avatar_img.circle() + avatar_tuple = (avatar_img, avatar_size, avatar_size) + else: + avatar_img = BuildImage(avatar_size, avatar_size, color="#CCCCCC") + await avatar_img.circle() + avatar_tuple = (avatar_img, avatar_size, avatar_size) + except Exception as e: + logger.warning(f"获取用户头像失败: {e}", "chat_history") + avatar_img = BuildImage(avatar_size, avatar_size, color="#CCCCCC") + await avatar_img.circle() + avatar_tuple = (avatar_img, avatar_size, avatar_size) + + data_list.append([idx, avatar_tuple, user_name, num]) idx += 1 if not date_scope: if date_scope := await ChatHistory.get_group_first_msg_datetime(group_id): @@ -132,13 +184,3 @@ async def _( ) await MessageUtils.build_message(A).finish(reply_to=True) await MessageUtils.build_message("群组消息记录为空...").finish() - - -# # @test.handle() -# # async def _(event: MessageEvent): -# # print(await ChatHistory.get_user_msg(event.user_id, "private")) -# # print(await ChatHistory.get_user_msg_count(event.user_id, "private")) -# # print(await ChatHistory.get_user_msg(event.user_id, "group")) -# # print(await ChatHistory.get_user_msg_count(event.user_id, "group")) -# # print(await ChatHistory.get_group_msg(event.group_id)) -# # print(await ChatHistory.get_group_msg_count(event.group_id)) diff --git a/zhenxun/builtin_plugins/mahiro_bank/__init__.py b/zhenxun/builtin_plugins/mahiro_bank/__init__.py new file mode 100644 index 00000000..8e82cf08 --- /dev/null +++ b/zhenxun/builtin_plugins/mahiro_bank/__init__.py @@ -0,0 +1,252 @@ +from datetime import datetime + +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Subcommand, on_alconna +from nonebot_plugin_apscheduler import scheduler +from nonebot_plugin_uninfo import Uninfo +from nonebot_plugin_waiter import prompt_until + +from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.services.log import logger +from zhenxun.utils.depends import UserName +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.utils import is_number + +from .data_source import BankManager + +__plugin_meta__ = PluginMetadata( + name="小真寻银行", + description=""" + 小真寻银行,提供高品质的存款!当好感度等级达到指初识时,小真寻会偷偷的帮助你哦。 + 存款额度与好感度有关,每日存款次数有限制。 + 基础存款提供基础利息 + 每日存款提供高额利息 + """.strip(), + usage=""" + 指令: + 存款 [金额] + 取款 [金额] + 银行信息 + 我的银行信息 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + menu_type="群内小游戏", + configs=[ + RegisterConfig( + key="sign_max_deposit", + value=100, + help="好感度换算存款金额比例,当值是100时,最大存款金额=好感度*100,存款的最低金额是100(强制)", + default_value=100, + type=int, + ), + RegisterConfig( + key="max_daily_deposit_count", + value=3, + help="每日最大存款次数", + default_value=3, + type=int, + ), + RegisterConfig( + key="rate_range", + value=[0.0005, 0.001], + help="小时利率范围", + default_value=[0.0005, 0.001], + type=list[float], + ), + RegisterConfig( + key="impression_event", + value=25, + help="到达指定好感度时随机提高或降低利率", + default_value=25, + type=int, + ), + RegisterConfig( + key="impression_event_range", + value=[0.00001, 0.0003], + help="到达指定好感度时随机提高或降低利率", + default_value=[0.00001, 0.0003], + type=list[float], + ), + RegisterConfig( + key="impression_event_prop", + value=0.3, + help="到达指定好感度时随机提高或降低利率触发概率", + default_value=0.3, + type=float, + ), + ], + ).to_dict(), +) + + +_matcher = on_alconna( + Alconna( + "mahiro-bank", + Subcommand("deposit", Args["amount?", int]), + Subcommand("withdraw", Args["amount?", int]), + Subcommand("user-info"), + Subcommand("bank-info"), + # Subcommand("loan", Args["amount?", int]), + # Subcommand("repayment", Args["amount?", int]), + ), + priority=5, + block=True, +) + +_matcher.shortcut( + r"存款\s*(?P\d+)?", + command="mahiro-bank", + arguments=["deposit", "{amount}"], + prefix=True, +) + +_matcher.shortcut( + r"取款\s*(?P\d+)?", + command="mahiro-bank", + arguments=["withdraw", "{withdraw}"], + prefix=True, +) + +_matcher.shortcut( + r"我的银行信息", + command="mahiro-bank", + arguments=["user-info"], + prefix=True, +) + +_matcher.shortcut( + r"银行信息", + command="mahiro-bank", + arguments=["bank-info"], + prefix=True, +) + + +async def get_amount(handle_type: str) -> int: + amount_num = await prompt_until( + f"请输入{handle_type}金币数量", + lambda msg: is_number(msg.extract_plain_text()), + timeout=60, + retry=3, + retry_prompt="输入错误,请输入数字。剩余次数:{count}", + ) + if not amount_num: + await MessageUtils.build_message( + "输入超时了哦,小真寻柜员以取消本次存款操作..." + ).finish() + return int(amount_num.extract_plain_text()) + + +@_matcher.assign("deposit") +async def _(session: Uninfo, arparma: Arparma, amount: Match[int]): + amount_num = amount.result if amount.available else await get_amount("存款") + if result := await BankManager.deposit_check(session.user.id, amount_num): + await MessageUtils.build_message(result).finish(reply_to=True) + _, rate, event_rate = await BankManager.deposit(session.user.id, amount_num) + result = ( + f"存款成功!\n此次存款金额为: {amount.result}\n" + f"当前小时利率为: {rate * 100:.2f}%" + ) + effective_hour = int(24 - datetime.now().hour) + if event_rate: + result += f"(小真寻偷偷将小时利率给你增加了 {event_rate:.2f}% 哦)" + result += ( + f"\n预计总收益为: {int(amount.result * rate * effective_hour) or 1} 金币。" + ) + logger.info( + f"小真寻银行存款:{amount_num},当前存款数:{amount.result},存款小时利率: {rate}", + arparma.header_result, + session=session, + ) + await MessageUtils.build_message(result).finish(at_sender=True) + + +@_matcher.assign("withdraw") +async def _(session: Uninfo, arparma: Arparma, amount: Match[int]): + amount_num = amount.result if amount.available else await get_amount("取款") + if result := await BankManager.withdraw_check(session.user.id, amount_num): + await MessageUtils.build_message(result).finish(reply_to=True) + try: + user = await BankManager.withdraw(session.user.id, amount_num) + result = ( + f"取款成功!\n当前取款金额为: {amount_num}\n当前存款金额为: {user.amount}" + ) + logger.info( + f"小真寻银行取款:{amount_num}, 当前存款数:{user.amount}," + f" 存款小时利率:{user.rate}", + arparma.header_result, + session=session, + ) + await MessageUtils.build_message(result).finish(reply_to=True) + except ValueError: + await MessageUtils.build_message("你的银行内的存款数量不足哦...").finish( + reply_to=True + ) + + +@_matcher.assign("user-info") +async def _(session: Uninfo, arparma: Arparma, uname: str = UserName()): + result = await BankManager.get_user_info(session, uname) + await MessageUtils.build_message(result).send() + logger.info("查看银行个人信息", arparma.header_result, session=session) + + +@_matcher.assign("bank-info") +async def _(session: Uninfo, arparma: Arparma): + result = await BankManager.get_bank_info() + await MessageUtils.build_message(result).send() + logger.info("查看银行信息", arparma.header_result, session=session) + + +# @_matcher.assign("loan") +# async def _(session: Uninfo, arparma: Arparma, amount: Match[int]): +# amount_num = amount.result if amount.available else await get_amount("贷款") +# if amount_num <= 0: +# await MessageUtils.build_message("贷款数量必须大于 0 啊笨蛋!").finish() +# try: +# user, event_rate = await BankManager.loan(session.user.id, amount_num) +# result = ( +# f"贷款成功!\n当前贷金额为: {user.loan_amount}" +# f"\n当前利率为: {user.loan_rate * 100}%" +# ) +# if event_rate: +# result += f"(小真寻偷偷将利率给你降低了 {event_rate}% 哦)" +# result += f"\n预计每小时利息为:{int(user.loan_amount * user.loan_rate)}金币。" +# logger.info( +# f"小真寻银行贷款: {amount_num}, 当前贷款数: {user.loan_amount}, " +# f"贷款利率: {user.loan_rate}", +# arparma.header_result, +# session=session, +# ) +# except ValueError: +# await MessageUtils.build_message( +# "贷款数量超过最大限制,请签到提升好感度获取更多额度吧..." +# ).finish(reply_to=True) + + +# @_matcher.assign("repayment") +# async def _(session: Uninfo, arparma: Arparma, amount: Match[int]): +# amount_num = amount.result if amount.available else await get_amount("还款") +# if amount_num <= 0: +# await MessageUtils.build_message("还款数量必须大于 0 啊笨蛋!").finish() +# user = await BankManager.repayment(session.user.id, amount_num) +# result = (f"还款成功!\n当前还款金额为: {amount_num}\n" +# f"当前贷款金额为: {user.loan_amount}") +# logger.info( +# f"小真寻银行还款:{amount_num},当前贷款数:{user.amount}, 贷款利率:{user.rate}", +# arparma.header_result, +# session=session, +# ) +# await MessageUtils.build_message(result).finish(at_sender=True) + + +@scheduler.scheduled_job( + "cron", + hour=0, + minute=0, +) +async def _(): + await BankManager.settlement() + logger.info("小真寻银行结算", "定时任务") diff --git a/zhenxun/builtin_plugins/mahiro_bank/data_source.py b/zhenxun/builtin_plugins/mahiro_bank/data_source.py new file mode 100644 index 00000000..b717e9a4 --- /dev/null +++ b/zhenxun/builtin_plugins/mahiro_bank/data_source.py @@ -0,0 +1,450 @@ +import asyncio +from datetime import datetime, timedelta +import random + +from nonebot_plugin_htmlrender import template_to_pic +from nonebot_plugin_uninfo import Uninfo +from tortoise.expressions import RawSQL +from tortoise.functions import Count, Sum + +from zhenxun.configs.config import Config +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.models.mahiro_bank import MahiroBank +from zhenxun.models.mahiro_bank_log import MahiroBankLog +from zhenxun.models.sign_user import SignUser +from zhenxun.models.user_console import UserConsole +from zhenxun.utils.enum import BankHandleType, GoldHandle +from zhenxun.utils.platform import PlatformUtils + +base_config = Config.get("mahiro_bank") + + +class BankManager: + @classmethod + async def random_event(cls, impression: float): + """随机事件""" + impression_event = base_config.get("impression_event") + impression_event_prop = base_config.get("impression_event_prop") + impression_event_range = base_config.get("impression_event_range") + if impression >= impression_event and random.random() < impression_event_prop: + """触发好感度事件""" + return random.uniform(impression_event_range[0], impression_event_range[1]) + return None + + @classmethod + async def deposit_check(cls, user_id: str, amount: int) -> str | None: + """检查存款是否合法 + + 参数: + user_id: 用户id + amount: 存款金额 + + 返回: + str | None: 存款信息 + """ + if amount <= 0: + return "存款数量必须大于 0 啊笨蛋!" + user, sign_user, bank_user = await asyncio.gather( + *[ + UserConsole.get_user(user_id), + SignUser.get_user(user_id), + cls.get_user(user_id), + ] + ) + sign_max_deposit: int = base_config.get("sign_max_deposit") + max_deposit = max(int(float(sign_user.impression) * sign_max_deposit), 100) + if user.gold < amount: + return f"金币数量不足,当前你的金币为:{user.gold}." + if bank_user.amount + amount > max_deposit: + return ( + f"存款超过上限,存款上限为:{max_deposit}," + f"当前你的还可以存款金额:{max_deposit - bank_user.amount}。" + ) + max_daily_deposit_count: int = base_config.get("max_daily_deposit_count") + today_deposit_count = len(await cls.get_user_deposit(user_id)) + if today_deposit_count >= max_daily_deposit_count: + return f"存款次数超过上限,每日存款次数上限为:{max_daily_deposit_count}。" + return None + + @classmethod + async def withdraw_check(cls, user_id: str, amount: int) -> str | None: + """检查取款是否合法 + + 参数: + user_id: 用户id + amount: 取款金额 + + 返回: + str | None: 取款信息 + """ + if amount <= 0: + return "取款数量必须大于 0 啊笨蛋!" + user = await cls.get_user(user_id) + data_list = await cls.get_user_deposit(user_id) + lock_amount = sum(data.amount for data in data_list) + if user.amount - lock_amount < amount: + return ( + "取款金额不足,当前你的存款为:" + f"{user.amount}({lock_amount}已被锁定)!" + ) + return None + + @classmethod + async def get_user_deposit( + cls, user_id: str, is_completed: bool = False + ) -> list[MahiroBankLog]: + """获取用户今日存款次数 + + 参数: + user_id: 用户id + + 返回: + list[MahiroBankLog]: 存款列表 + """ + return await MahiroBankLog.filter( + user_id=user_id, + handle_type=BankHandleType.DEPOSIT, + is_completed=is_completed, + ) + + @classmethod + async def get_user(cls, user_id: str) -> MahiroBank: + """查询余额 + + 参数: + user_id: 用户id + + 返回: + MahiroBank + """ + user, _ = await MahiroBank.get_or_create(user_id=user_id) + return user + + @classmethod + async def get_user_data( + cls, + user_id: str, + data_type: BankHandleType, + is_completed: bool = False, + count: int = 5, + ) -> list[MahiroBankLog]: + return ( + await MahiroBankLog.filter( + user_id=user_id, handle_type=data_type, is_completed=is_completed + ) + .order_by("-id") + .limit(count) + .all() + ) + + @classmethod + async def complete_projected_revenue(cls, user_id: str) -> int: + """预计收益 + + 参数: + user_id: 用户id + + 返回: + int: 预计收益金额 + """ + deposit_list = await cls.get_user_deposit(user_id) + if not deposit_list: + return 0 + return int( + sum( + deposit.rate * deposit.amount * deposit.effective_hour + for deposit in deposit_list + ) + ) + + @classmethod + async def get_user_info(cls, session: Uninfo, uname: str) -> bytes: + """获取用户数据 + + 参数: + session: Uninfo + uname: 用户id + + 返回: + bytes: 图片数据 + """ + user_id = session.user.id + user = await cls.get_user(user_id=user_id) + ( + rank, + deposit_count, + user_today_deposit, + projected_revenue, + sum_data, + ) = await asyncio.gather( + *[ + MahiroBank.filter(amount__gt=user.amount).count(), + MahiroBankLog.filter(user_id=user_id).count(), + cls.get_user_deposit(user_id), + cls.complete_projected_revenue(user_id), + MahiroBankLog.filter( + user_id=user_id, handle_type=BankHandleType.INTEREST + ) + .annotate(sum=Sum("amount")) + .values("sum"), + ] + ) + now = datetime.now() + end_time = ( + now + + timedelta(days=1) + - timedelta(hours=now.hour, minutes=now.minute, seconds=now.second) + ) + today_deposit_amount = sum(deposit.amount for deposit in user_today_deposit) + deposit_list = [ + { + "id": deposit.id, + "date": now.date(), + "start_time": str(deposit.create_time).split(".")[0], + "end_time": end_time.replace(microsecond=0), + "amount": deposit.amount, + "rate": f"{deposit.rate * 100:.2f}", + "projected_revenue": int( + deposit.amount * deposit.rate * deposit.effective_hour + ) + or 1, + } + for deposit in user_today_deposit + ] + platform = PlatformUtils.get_platform(session) + data = { + "name": uname, + "rank": rank + 1, + "avatar_url": PlatformUtils.get_user_avatar_url( + user_id, platform, session.self_id + ), + "amount": user.amount, + "deposit_count": deposit_count, + "today_deposit_count": len(user_today_deposit), + "cumulative_gain": sum_data[0]["sum"] or 0, + "projected_revenue": projected_revenue, + "today_deposit_amount": today_deposit_amount, + "deposit_list": deposit_list, + "create_time": now.replace(microsecond=0), + } + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "mahiro_bank").absolute()), + template_name="user.html", + templates={"data": data}, + pages={ + "viewport": {"width": 386, "height": 700}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + + @classmethod + async def get_bank_info(cls) -> bytes: + now = datetime.now() + now_start = now - timedelta( + hours=now.hour, minutes=now.minute, seconds=now.second + ) + ( + bank_data, + today_count, + interest_amount, + active_user_count, + date_data, + ) = await asyncio.gather( + *[ + MahiroBank.annotate( + amount_sum=Sum("amount"), user_count=Count("id") + ).values("amount_sum", "user_count"), + MahiroBankLog.filter( + create_time__gt=now_start, handle_type=BankHandleType.DEPOSIT + ).count(), + MahiroBankLog.filter(handle_type=BankHandleType.INTEREST) + .annotate(amount_sum=Sum("amount")) + .values("amount_sum"), + MahiroBankLog.filter( + create_time__gte=now_start - timedelta(days=7), + handle_type=BankHandleType.DEPOSIT, + ) + .annotate(count=Count("user_id", distinct=True)) + .values("count"), + MahiroBankLog.filter( + create_time__gte=now_start - timedelta(days=7), + handle_type=BankHandleType.DEPOSIT, + ) + .annotate(date=RawSQL("DATE(create_time)"), total_amount=Sum("amount")) + .group_by("date") + .values("date", "total_amount"), + ] + ) + date2cnt = {str(date["date"]): date["total_amount"] for date in date_data} + date = now.date() + e_date, e_amount = [], [] + for _ in range(7): + if str(date) in date2cnt: + e_amount.append(date2cnt[str(date)]) + else: + e_amount.append(0) + e_date.append(str(date)[5:]) + date -= timedelta(days=1) + e_date.reverse() + e_amount.reverse() + date = 1 + lasted_log = await MahiroBankLog.annotate().order_by("create_time").first() + if lasted_log: + date = now.date() - lasted_log.create_time.date() + date = (date.days or 1) + 1 + data = { + "amount_sum": bank_data[0]["amount_sum"], + "user_count": bank_data[0]["user_count"], + "today_count": today_count, + "day_amount": int(bank_data[0]["amount_sum"] / date), + "interest_amount": interest_amount[0]["amount_sum"] or 0, + "active_user_count": active_user_count[0]["count"] or 0, + "e_data": e_date, + "e_amount": e_amount, + "create_time": now.replace(microsecond=0), + } + return await template_to_pic( + template_path=str((TEMPLATE_PATH / "mahiro_bank").absolute()), + template_name="bank.html", + templates={"data": data}, + pages={ + "viewport": {"width": 450, "height": 750}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + + @classmethod + async def deposit( + cls, user_id: str, amount: int + ) -> tuple[MahiroBank, float, float | None]: + """存款 + + 参数: + user_id: 用户id + amount: 存款数量 + + 返回: + tuple[MahiroBank, float, float]: MahiroBank,利率,增加的利率 + """ + rate_range = base_config.get("rate_range") + rate = random.uniform(rate_range[0], rate_range[1]) + sign_user = await SignUser.get_user(user_id) + random_add_rate = await cls.random_event(float(sign_user.impression)) + if random_add_rate: + rate += random_add_rate + await UserConsole.reduce_gold(user_id, amount, GoldHandle.PLUGIN, "bank") + return await MahiroBank.deposit(user_id, amount, rate), rate, random_add_rate + + @classmethod + async def withdraw(cls, user_id: str, amount: int) -> MahiroBank: + """取款 + + 参数: + user_id: 用户id + amount: 取款数量 + + 返回: + MahiroBank + """ + await UserConsole.add_gold(user_id, amount, "bank") + return await MahiroBank.withdraw(user_id, amount) + + @classmethod + async def loan(cls, user_id: str, amount: int) -> tuple[MahiroBank, float | None]: + """贷款 + + 参数: + user_id: 用户id + amount: 贷款数量 + + 返回: + tuple[MahiroBank, float]: MahiroBank,贷款利率 + """ + rate_range = base_config.get("rate_range") + rate = random.uniform(rate_range[0], rate_range[1]) + sign_user = await SignUser.get_user(user_id) + user, _ = await MahiroBank.get_or_create(user_id=user_id) + if user.loan_amount + amount > sign_user.impression * 150: + raise ValueError("贷款数量超过最大限制,请签到提升好感度获取更多额度吧...") + random_reduce_rate = await cls.random_event(float(sign_user.impression)) + if random_reduce_rate: + rate -= random_reduce_rate + await UserConsole.add_gold(user_id, amount, "bank") + return await MahiroBank.loan(user_id, amount, rate), random_reduce_rate + + @classmethod + async def repayment(cls, user_id: str, amount: int) -> MahiroBank: + """还款 + + 参数: + user_id: 用户id + amount: 还款数量 + + 返回: + MahiroBank + """ + await UserConsole.reduce_gold(user_id, amount, GoldHandle.PLUGIN, "bank") + return await MahiroBank.repayment(user_id, amount) + + @classmethod + async def settlement(cls): + """结算每日利率""" + bank_user_list = await MahiroBank.filter(amount__gt=0).all() + log_list = await MahiroBankLog.filter( + is_completed=False, handle_type=BankHandleType.DEPOSIT + ).all() + user_list = await UserConsole.filter( + user_id__in=[user.user_id for user in bank_user_list] + ).all() + user_data = {user.user_id: user for user in user_list} + bank_data: dict[str, list[MahiroBankLog]] = {} + for log in log_list: + if log.user_id not in bank_data: + bank_data[log.user_id] = [] + bank_data[log.user_id].append(log) + log_create_list = [] + log_update_list = [] + # 计算每日默认金币 + for bank_user in bank_user_list: + if user := user_data.get(bank_user.user_id): + amount = bank_user.amount + if logs := bank_data.get(bank_user.user_id): + amount -= sum(log.amount for log in logs) + if not amount: + continue + # 计算每日默认金币 + gold = int(amount * bank_user.rate) + user.gold += gold + log_create_list.append( + MahiroBankLog( + user_id=bank_user.user_id, + amount=gold, + rate=bank_user.rate, + handle_type=BankHandleType.INTEREST, + is_completed=True, + ) + ) + # 计算每日存款金币 + for user_id, logs in bank_data.items(): + if user := user_data.get(user_id): + for log in logs: + gold = int(log.amount * log.rate * log.effective_hour) or 1 + user.gold += gold + log.is_completed = True + log_update_list.append(log) + log_create_list.append( + MahiroBankLog( + user_id=user_id, + amount=gold, + rate=log.rate, + handle_type=BankHandleType.INTEREST, + is_completed=True, + ) + ) + if log_create_list: + await MahiroBankLog.bulk_create(log_create_list, 10) + if log_update_list: + await MahiroBankLog.bulk_update(log_update_list, ["is_completed"], 10) + await UserConsole.bulk_update(user_list, ["gold"], 10) diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py b/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py index d621f087..4a88919e 100644 --- a/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py +++ b/zhenxun/builtin_plugins/platform/qq/group_handle/__init__.py @@ -141,7 +141,7 @@ async def _( group_id = str(event.group_id) if event.sub_type == "kick_me": """踢出Bot""" - await GroupManager.kick_bot(bot, user_id, group_id) + await GroupManager.kick_bot(bot, group_id, str(event.operator_id)) elif event.sub_type in ["leave", "kick"]: result = await GroupManager.run_user( bot, user_id, group_id, str(event.operator_id), event.sub_type diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/plugin.py b/zhenxun/builtin_plugins/superuser/bot_manage/plugin.py index df6d7f35..c5359951 100644 --- a/zhenxun/builtin_plugins/superuser/bot_manage/plugin.py +++ b/zhenxun/builtin_plugins/superuser/bot_manage/plugin.py @@ -110,7 +110,7 @@ async def enable_plugin( ) await BotConsole.enable_plugin(None, plugin.module) await MessageUtils.build_message( - f"已禁用全部 bot 的插件: {plugin_name.result}" + f"已开启全部 bot 的插件: {plugin_name.result}" ).finish() elif bot_id.available: logger.info( diff --git a/zhenxun/builtin_plugins/superuser/bot_manage/task.py b/zhenxun/builtin_plugins/superuser/bot_manage/task.py index 005ab188..501aec3d 100644 --- a/zhenxun/builtin_plugins/superuser/bot_manage/task.py +++ b/zhenxun/builtin_plugins/superuser/bot_manage/task.py @@ -92,7 +92,7 @@ async def enable_task( ) await BotConsole.enable_task(None, task.module) await MessageUtils.build_message( - f"已禁用全部 bot 的被动: {task_name.available}" + f"已开启全部 bot 的被动: {task_name.available}" ).finish() elif bot_id.available: logger.info( diff --git a/zhenxun/models/mahiro_bank.py b/zhenxun/models/mahiro_bank.py new file mode 100644 index 00000000..3880daa8 --- /dev/null +++ b/zhenxun/models/mahiro_bank.py @@ -0,0 +1,123 @@ +from datetime import datetime +from typing_extensions import Self + +from tortoise import fields + +from zhenxun.services.db_context import Model + +from .mahiro_bank_log import BankHandleType, MahiroBankLog + + +class MahiroBank(Model): + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_id = fields.CharField(255, description="用户id") + """用户id""" + amount = fields.BigIntField(default=0, description="存款") + """用户存款""" + rate = fields.FloatField(default=0.0005, description="小时利率") + """小时利率""" + loan_amount = fields.BigIntField(default=0, description="贷款") + """用户贷款""" + loan_rate = fields.FloatField(default=0.0005, description="贷款利率") + """贷款利率""" + update_time = fields.DatetimeField(auto_now=True) + """修改时间""" + create_time = fields.DatetimeField(auto_now_add=True) + """创建时间""" + + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] + table = "mahiro_bank" + table_description = "小真寻银行" + + @classmethod + async def deposit(cls, user_id: str, amount: int, rate: float) -> Self: + """存款 + + 参数: + user_id: 用户id + amount: 金币数量 + rate: 小时利率 + + 返回: + Self: MahiroBank + """ + effective_hour = int(24 - datetime.now().hour) + user, _ = await cls.get_or_create(user_id=user_id) + user.amount += amount + await user.save(update_fields=["amount", "rate"]) + await MahiroBankLog.create( + user_id=user_id, + amount=amount, + rate=rate, + effective_hour=effective_hour, + handle_type=BankHandleType.DEPOSIT, + ) + return user + + @classmethod + async def withdraw(cls, user_id: str, amount: int) -> Self: + """取款 + + 参数: + user_id: 用户id + amount: 金币数量 + + 返回: + Self: MahiroBank + """ + if amount <= 0: + raise ValueError("取款金额必须大于0") + user, _ = await cls.get_or_create(user_id=user_id) + if user.amount < amount: + raise ValueError("取款金额不能大于存款金额") + user.amount -= amount + await user.save(update_fields=["amount"]) + await MahiroBankLog.create( + user_id=user_id, amount=amount, handle_type=BankHandleType.WITHDRAW + ) + return user + + @classmethod + async def loan(cls, user_id: str, amount: int, rate: float) -> Self: + """贷款 + + 参数: + user_id: 用户id + amount: 贷款金额 + rate: 贷款利率 + + 返回: + Self: MahiroBank + """ + user, _ = await cls.get_or_create(user_id=user_id) + user.loan_amount += amount + user.loan_rate = rate + await user.save(update_fields=["loan_amount", "loan_rate"]) + await MahiroBankLog.create( + user_id=user_id, amount=amount, rate=rate, handle_type=BankHandleType.LOAN + ) + return user + + @classmethod + async def repayment(cls, user_id: str, amount: int) -> Self: + """还款 + + 参数: + user_id: 用户id + amount: 还款金额 + + 返回: + Self: MahiroBank + """ + if amount <= 0: + raise ValueError("还款金额必须大于0") + user, _ = await cls.get_or_create(user_id=user_id) + if user.loan_amount < amount: + raise ValueError("还款金额不能大于贷款金额") + user.loan_amount -= amount + await user.save(update_fields=["loan_amount"]) + await MahiroBankLog.create( + user_id=user_id, amount=amount, handle_type=BankHandleType.REPAYMENT + ) + return user diff --git a/zhenxun/models/mahiro_bank_log.py b/zhenxun/models/mahiro_bank_log.py new file mode 100644 index 00000000..433241d1 --- /dev/null +++ b/zhenxun/models/mahiro_bank_log.py @@ -0,0 +1,31 @@ +from tortoise import fields + +from zhenxun.services.db_context import Model +from zhenxun.utils.enum import BankHandleType + + +class MahiroBankLog(Model): + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_id = fields.CharField(255, description="用户id") + """用户id""" + amount = fields.BigIntField(default=0, description="存款") + """金币数量""" + rate = fields.FloatField(default=0, description="小时利率") + """小时利率""" + handle_type = fields.CharEnumField( + BankHandleType, null=True, description="处理类型" + ) + """处理类型""" + is_completed = fields.BooleanField(default=False, description="是否完成") + """是否完成""" + effective_hour = fields.IntField(default=0, description="有效小时") + """有效小时""" + update_time = fields.DatetimeField(auto_now=True) + """修改时间""" + create_time = fields.DatetimeField(auto_now_add=True) + """创建时间""" + + class Meta: # pyright: ignore [reportIncompatibleVariableOverride] + table = "mahiro_bank_log" + table_description = "小真寻银行日志" diff --git a/zhenxun/utils/enum.py b/zhenxun/utils/enum.py index 7e816b2b..2a908b96 100644 --- a/zhenxun/utils/enum.py +++ b/zhenxun/utils/enum.py @@ -13,6 +13,19 @@ class PriorityLifecycleType(StrEnum): """关闭""" +class BankHandleType(StrEnum): + DEPOSIT = "DEPOSIT" + """存款""" + WITHDRAW = "WITHDRAW" + """取款""" + LOAN = "LOAN" + """贷款""" + REPAYMENT = "REPAYMENT" + """还款""" + INTEREST = "INTEREST" + """利息""" + + class GoldHandle(StrEnum): """ 金币处理 diff --git a/zhenxun/utils/github_utils/func.py b/zhenxun/utils/github_utils/func.py index 19daf10d..b3f9a6f9 100644 --- a/zhenxun/utils/github_utils/func.py +++ b/zhenxun/utils/github_utils/func.py @@ -23,7 +23,6 @@ async def get_fastest_raw_formats() -> list[str]: formats: dict[str, str] = { "https://raw.githubusercontent.com/": RAW_CONTENT_FORMAT, "https://ghproxy.cc/": f"https://ghproxy.cc/{RAW_CONTENT_FORMAT}", - "https://mirror.ghproxy.com/": f"https://mirror.ghproxy.com/{RAW_CONTENT_FORMAT}", "https://gh-proxy.com/": f"https://gh-proxy.com/{RAW_CONTENT_FORMAT}", "https://cdn.jsdelivr.net/": "https://cdn.jsdelivr.net/gh/{owner}/{repo}@{branch}/{path}", } @@ -36,7 +35,6 @@ async def get_fastest_archive_formats() -> list[str]: formats: dict[str, str] = { "https://github.com/": ARCHIVE_URL_FORMAT, "https://ghproxy.cc/": f"https://ghproxy.cc/{ARCHIVE_URL_FORMAT}", - "https://mirror.ghproxy.com/": f"https://mirror.ghproxy.com/{ARCHIVE_URL_FORMAT}", "https://gh-proxy.com/": f"https://gh-proxy.com/{ARCHIVE_URL_FORMAT}", } return await __get_fastest_formats(formats) @@ -48,7 +46,6 @@ async def get_fastest_release_formats() -> list[str]: formats: dict[str, str] = { "https://objects.githubusercontent.com/": RELEASE_ASSETS_FORMAT, "https://ghproxy.cc/": f"https://ghproxy.cc/{RELEASE_ASSETS_FORMAT}", - "https://mirror.ghproxy.com/": f"https://mirror.ghproxy.com/{RELEASE_ASSETS_FORMAT}", "https://gh-proxy.com/": f"https://gh-proxy.com/{RELEASE_ASSETS_FORMAT}", } return await __get_fastest_formats(formats)