zhenxun_bot/zhenxun/builtin_plugins/sign_in/utils.py

290 lines
7.7 KiB
Python
Raw Normal View History

from datetime import datetime
import os
2024-02-25 03:18:34 +08:00
from pathlib import Path
import random
2024-02-25 03:18:34 +08:00
import aiofiles
2024-09-07 12:46:25 +08:00
import nonebot
2024-02-25 03:18:34 +08:00
from nonebot.drivers import Driver
from nonebot_plugin_uninfo import Uninfo
import pytz
2024-02-25 03:18:34 +08:00
from zhenxun.configs.config import BotConfig, Config
2024-02-25 03:18:34 +08:00
from zhenxun.models.sign_log import SignLog
from zhenxun.models.sign_user import SignUser
from zhenxun.services import renderer_service
:sparkles: 首次启动时提供使用web ui方式完全配置 (#1870) * :sparkles: 添加全局优先级hook * :sparkles: 添加基础配置api * :sparkles: 添加数据库连接测试 * :speech_balloon: 提示重启 * :adhesive_bandage: 填充过配置时友好提示 * :bug: 首次生成简易配置后自动加载 * :sparkles: 添加配置后重启接口 * :sparkles: 添加重启标志文件 * :sparkles: 添加重启脚本命令 * :sparkles: 添加重启系统限制 * :sparkles: 首次配置判断是否为win系统 * :fire: 移除bat * :sparkles: 添加关于菜单 * :sparkles: 支持整合包插件安装和添加整合包文档 * :adhesive_bandage: 检测数据库路径 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修复路径注入 * :art: 显示添加优先级 * :bug: 修改PriorityLifecycle字典类名称 * :zap: 修复路径问题 * :zap: 修复路径检测 * ✨ 新增路径验证功能,确保用户输入的路径安全并在项目根目录内 * ✨ 优化路径验证功能,增加对非法字符和路径长度的检查,确保用户输入的路径更加安全 * :rotating_light: auto fix by pre-commit hooks * ✨ 优化获取文件列表的代码格式 * :memo: 修改README中webui示例图 * ✨ 更新PriorityLifecycle.on_startup装饰器 * ✨ 简化安装依赖的命令构建逻辑 * :rotating_light: auto fix by pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-16 09:11:41 +08:00
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
from zhenxun.utils.platform import PlatformUtils
2024-02-25 03:18:34 +08:00
from .config import (
SIGN_TODAY_CARD_PATH,
level2attitude,
2024-02-25 03:18:34 +08:00
lik2level,
lik2relation,
)
2024-09-07 12:46:25 +08:00
assert (
len(level2attitude) == len(lik2level) == len(lik2relation)
), "好感度态度、等级、关系长度不匹配!"
2024-02-25 03:18:34 +08:00
2024-08-24 12:30:49 +08:00
AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160"
2024-02-25 03:18:34 +08:00
driver: Driver = nonebot.get_driver()
2024-08-24 12:30:49 +08:00
base_config = Config.get("sign_in")
MORNING_MESSAGE = [
"早上好,希望今天是美好的一天!",
"醒了吗,今天也要元气满满哦!",
"早上好呀,今天也要开心哦!",
"早安,愿你拥有美好的一天!",
]
LG_MESSAGE = [
"今天要早点休息哦~",
"可不要熬夜到太晚呀",
"请尽早休息吧!",
"不要熬夜啦!",
]
2024-02-25 03:18:34 +08:00
:sparkles: 首次启动时提供使用web ui方式完全配置 (#1870) * :sparkles: 添加全局优先级hook * :sparkles: 添加基础配置api * :sparkles: 添加数据库连接测试 * :speech_balloon: 提示重启 * :adhesive_bandage: 填充过配置时友好提示 * :bug: 首次生成简易配置后自动加载 * :sparkles: 添加配置后重启接口 * :sparkles: 添加重启标志文件 * :sparkles: 添加重启脚本命令 * :sparkles: 添加重启系统限制 * :sparkles: 首次配置判断是否为win系统 * :fire: 移除bat * :sparkles: 添加关于菜单 * :sparkles: 支持整合包插件安装和添加整合包文档 * :adhesive_bandage: 检测数据库路径 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修改数据库路径检测 * :adhesive_bandage: 修复路径注入 * :art: 显示添加优先级 * :bug: 修改PriorityLifecycle字典类名称 * :zap: 修复路径问题 * :zap: 修复路径检测 * ✨ 新增路径验证功能,确保用户输入的路径安全并在项目根目录内 * ✨ 优化路径验证功能,增加对非法字符和路径长度的检查,确保用户输入的路径更加安全 * :rotating_light: auto fix by pre-commit hooks * ✨ 优化获取文件列表的代码格式 * :memo: 修改README中webui示例图 * ✨ 更新PriorityLifecycle.on_startup装饰器 * ✨ 简化安装依赖的命令构建逻辑 * :rotating_light: auto fix by pre-commit hooks --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-06-16 09:11:41 +08:00
@PriorityLifecycle.on_startup(priority=5)
2024-02-25 03:18:34 +08:00
async def init_image():
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
clear_sign_data_pic()
async def get_card(
user: SignUser,
2024-10-18 18:57:55 +08:00
session: Uninfo,
2024-02-25 03:18:34 +08:00
nickname: str,
add_impression: float,
gold: int | None,
gift: str,
is_double: bool = False,
is_card_view: bool = False,
) -> Path:
"""获取好感度卡片
参数:
user: SignUser
2024-10-18 18:57:55 +08:00
session: Uninfo
2024-02-25 03:18:34 +08:00
nickname: 用户昵称
impression: 新增的好感度
gold: 金币
gift: 礼物
is_double: 是否触发双倍.
is_card_view: 是否展示好感度卡片.
返回:
Path: 卡片路径
"""
user_id = user.user_id
date = datetime.now().date()
_type = "view" if is_card_view else "sign"
file_name = f"{user_id}_{_type}_{date}.png"
card_file = SIGN_TODAY_CARD_PATH / file_name
2024-02-25 03:18:34 +08:00
if card_file.exists():
return card_file
2024-09-07 12:46:25 +08:00
if add_impression == -1:
view_name = f"{user_id}_view_{date}.png"
view_card_file = SIGN_TODAY_CARD_PATH / view_name
if view_card_file.exists():
return view_card_file
2024-09-07 12:46:25 +08:00
is_card_view = True
2024-02-25 03:18:34 +08:00
return await _generate_html_card(
user, session, nickname, add_impression, gold, gift, is_double, is_card_view
)
2024-02-25 03:18:34 +08:00
def get_level_and_next_impression(impression: float) -> tuple[int, int | float, int]:
2024-02-25 03:18:34 +08:00
"""获取当前好感等级与下一等级的差距
参数:
impression: 好感度
返回:
tuple[int, int, int]: 好感度等级下一等级好感度要求已达到的好感度要求
2024-02-25 03:18:34 +08:00
"""
2024-02-25 03:18:34 +08:00
keys = list(lik2level.keys())
level_int, next_impression, previous_impression = (
int(lik2level[keys[-1]]),
2024-09-07 12:46:25 +08:00
keys[-2],
keys[-1],
)
2024-02-25 03:18:34 +08:00
for i in range(len(keys)):
if impression >= keys[i]:
level_int, next_impression, previous_impression = (
int(lik2level[keys[i]]),
2024-09-07 12:46:25 +08:00
keys[i - 1],
keys[i],
)
if i == 0:
next_impression = impression
break
return level_int, next_impression, previous_impression
2024-02-25 03:18:34 +08:00
def clear_sign_data_pic():
"""
清空当前签到图片数据
"""
date = datetime.now().date()
for file in os.listdir(SIGN_TODAY_CARD_PATH):
if str(date) not in file:
os.remove(SIGN_TODAY_CARD_PATH / file)
2024-08-24 12:30:49 +08:00
async def _generate_html_card(
user: SignUser,
2024-10-18 18:57:55 +08:00
session: Uninfo,
2024-08-24 12:30:49 +08:00
nickname: str,
add_impression: float,
2024-08-24 12:30:49 +08:00
gold: int | None,
gift: str,
is_double: bool = False,
is_card_view: bool = False,
) -> Path:
"""使用渲染服务生成签到卡片
2024-08-24 12:30:49 +08:00
参数:
user: SignUser
2024-10-18 18:57:55 +08:00
session: Uninfo
2024-08-24 12:30:49 +08:00
nickname: 用户昵称
add_impression: 新增的好感度
2024-08-24 12:30:49 +08:00
gold: 金币
gift: 礼物
is_double: 是否触发双倍.
is_card_view: 是否为卡片视图.
2024-08-24 12:30:49 +08:00
返回:
Path: 卡片路径
"""
now = datetime.now()
date = now.date()
_type = "view" if is_card_view else "sign"
file_name = f"{user.user_id}_{_type}_{date}.png"
card_file = SIGN_TODAY_CARD_PATH / file_name
if card_file.exists():
return card_file
impression = float(user.impression)
2024-08-24 12:30:49 +08:00
user_console = await user.user_console
uid_str = (
f"{user_console.uid:08}"
if user_console and user_console.uid is not None
else "XXXXXXXX"
)
uid_formatted = f"{uid_str[:4]} {uid_str[4:]}"
2024-08-24 12:30:49 +08:00
level, next_impression, previous_impression = get_level_and_next_impression(
2024-09-07 12:46:25 +08:00
impression
2024-08-24 12:30:49 +08:00
)
attitude = level2attitude.get(str(level), "未知")
interpolation_val = max(0, next_impression - impression)
interpolation = f"{interpolation_val:.2f}"
denominator = next_impression - previous_impression
progress = (
100.0
if denominator == 0
else min(100.0, ((impression - previous_impression) / denominator) * 100)
2024-08-24 12:30:49 +08:00
)
hour = now.hour
if 6 < hour < 10:
bot_message = random.choice(MORNING_MESSAGE)
elif 0 <= hour < 6:
bot_message = random.choice(LG_MESSAGE)
else:
bot_message = f"{BotConfig.self_nickname}希望你开心!"
temperature = random.randint(1, 40)
weather_icon_name = f"{random.randint(0, 11)}.png"
tag_icon_name = f"{random.randint(0, 5)}.png"
user_info = {
"nickname": nickname,
"uid_str": uid_formatted,
"avatar_url": PlatformUtils.get_user_avatar_url(
user.user_id, PlatformUtils.get_platform(session), session.self_id
)
or "",
"sign_count": user.sign_count,
}
favorability_info = {
"current": impression,
"level": level,
"next_level_at": next_impression,
"previous_level_at": previous_impression,
2024-08-24 12:30:49 +08:00
}
reward_info = None
rank = None
total_gold = None
last_sign_date_str = None
2024-08-24 12:30:49 +08:00
if is_card_view:
value_list = (
await SignUser.annotate()
.order_by("-impression")
.values_list("user_id", flat=True)
)
rank = value_list.index(user.user_id) + 1 if user.user_id in value_list else 0
total_gold = user_console.gold if user_console else 0
last_log = (
await SignLog.filter(user_id=user.user_id).order_by("-create_time").first()
)
last_date = "从未"
if last_log:
last_date = str(
last_log.create_time.astimezone(pytz.timezone("Asia/Shanghai")).date()
)
last_sign_date_str = f"上次签到:{last_date}"
else:
reward_info = {
"impression_added": add_impression,
"gold_added": gold or 0,
"gift_received": gift,
"is_double": is_double,
}
page_info = {
"date_str": str(now.replace(microsecond=0)),
"weather_icon_name": weather_icon_name,
"temperature": temperature,
"tag_icon_name": tag_icon_name,
}
card_data = {
"is_card_view": is_card_view,
"user": user_info,
"favorability": favorability_info,
"reward": reward_info,
"page": page_info,
"bot_message": bot_message,
"attitude": attitude,
"interpolation": interpolation,
"progress": progress,
"rank": rank,
"total_gold": total_gold,
"last_sign_date_str": last_sign_date_str,
}
image_bytes = await renderer_service.render("pages/builtin/sign", data=card_data)
async with aiofiles.open(card_file, "wb") as f:
await f.write(image_bytes)
return card_file