mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-14 21:52:56 +08:00
Some checks failed
检查bot是否运行正常 / bot check (push) Waiting to run
Sequential Lint and Type Check / ruff-call (push) Waiting to run
Sequential Lint and Type Check / pyright-call (push) Blocked by required conditions
Release Drafter / Update Release Draft (push) Waiting to run
Force Sync to Aliyun / sync (push) Waiting to run
Update Version / update-version (push) Waiting to run
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Has been cancelled
CodeQL Code Security Analysis / Analyze (${{ matrix.language }}) (none, python) (push) Has been cancelled
* ♻️ refactor(UI): 重构UI渲染服务为组件化分层架构 ♻️ **架构重构** - UI渲染服务重构为组件化分层架构 - 解耦主题管理、HTML生成、截图功能 ✨ **新增功能** - `zhenxun.ui` 统一入口,提供 `render`、`markdown`、`vstack` 等API - `RenderableComponent` 基类和渲染协议抽象 - 新增主题管理器和截图引擎模块 ⚙️ **配置优化** - UI配置迁移至 `superuser/ui_manager.py` - 新增"重载UI主题"管理指令 🔧 **性能改进** - 优化渲染缓存,支持组件级透明缓存 - 所有UI组件适配新渲染流程 * 🚨 auto fix by pre-commit hooks --------- Co-authored-by: webjoin111 <455457521@qq.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
290 lines
7.7 KiB
Python
290 lines
7.7 KiB
Python
from datetime import datetime
|
|
import os
|
|
from pathlib import Path
|
|
import random
|
|
|
|
import aiofiles
|
|
import nonebot
|
|
from nonebot.drivers import Driver
|
|
from nonebot_plugin_uninfo import Uninfo
|
|
import pytz
|
|
|
|
from zhenxun import ui
|
|
from zhenxun.configs.config import BotConfig, Config
|
|
from zhenxun.models.sign_log import SignLog
|
|
from zhenxun.models.sign_user import SignUser
|
|
from zhenxun.utils.manager.priority_manager import PriorityLifecycle
|
|
from zhenxun.utils.platform import PlatformUtils
|
|
|
|
from .config import (
|
|
SIGN_TODAY_CARD_PATH,
|
|
level2attitude,
|
|
lik2level,
|
|
lik2relation,
|
|
)
|
|
|
|
assert (
|
|
len(level2attitude) == len(lik2level) == len(lik2relation)
|
|
), "好感度态度、等级、关系长度不匹配!"
|
|
|
|
AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160"
|
|
|
|
driver: Driver = nonebot.get_driver()
|
|
|
|
base_config = Config.get("sign_in")
|
|
|
|
|
|
MORNING_MESSAGE = [
|
|
"早上好,希望今天是美好的一天!",
|
|
"醒了吗,今天也要元气满满哦!",
|
|
"早上好呀,今天也要开心哦!",
|
|
"早安,愿你拥有美好的一天!",
|
|
]
|
|
|
|
LG_MESSAGE = [
|
|
"今天要早点休息哦~",
|
|
"可不要熬夜到太晚呀",
|
|
"请尽早休息吧!",
|
|
"不要熬夜啦!",
|
|
]
|
|
|
|
|
|
@PriorityLifecycle.on_startup(priority=5)
|
|
async def init_image():
|
|
SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True)
|
|
clear_sign_data_pic()
|
|
|
|
|
|
async def get_card(
|
|
user: SignUser,
|
|
session: Uninfo,
|
|
nickname: str,
|
|
add_impression: float,
|
|
gold: int | None,
|
|
gift: str,
|
|
is_double: bool = False,
|
|
is_card_view: bool = False,
|
|
) -> Path:
|
|
"""获取好感度卡片
|
|
|
|
参数:
|
|
user: SignUser
|
|
session: Uninfo
|
|
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
|
|
|
|
if card_file.exists():
|
|
return card_file
|
|
|
|
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
|
|
is_card_view = True
|
|
|
|
return await _generate_html_card(
|
|
user, session, nickname, add_impression, gold, gift, is_double, is_card_view
|
|
)
|
|
|
|
|
|
def get_level_and_next_impression(impression: float) -> tuple[int, int | float, int]:
|
|
"""获取当前好感等级与下一等级的差距
|
|
|
|
参数:
|
|
impression: 好感度
|
|
|
|
返回:
|
|
tuple[int, int, int]: 好感度等级,下一等级好感度要求,已达到的好感度要求
|
|
"""
|
|
|
|
keys = list(lik2level.keys())
|
|
level_int, next_impression, previous_impression = (
|
|
int(lik2level[keys[-1]]),
|
|
keys[-2],
|
|
keys[-1],
|
|
)
|
|
for i in range(len(keys)):
|
|
if impression >= keys[i]:
|
|
level_int, next_impression, previous_impression = (
|
|
int(lik2level[keys[i]]),
|
|
keys[i - 1],
|
|
keys[i],
|
|
)
|
|
if i == 0:
|
|
next_impression = impression
|
|
break
|
|
return level_int, next_impression, previous_impression
|
|
|
|
|
|
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)
|
|
|
|
|
|
async def _generate_html_card(
|
|
user: SignUser,
|
|
session: Uninfo,
|
|
nickname: str,
|
|
add_impression: float,
|
|
gold: int | None,
|
|
gift: str,
|
|
is_double: bool = False,
|
|
is_card_view: bool = False,
|
|
) -> Path:
|
|
"""使用渲染服务生成签到卡片
|
|
|
|
参数:
|
|
user: SignUser
|
|
session: Uninfo
|
|
nickname: 用户昵称
|
|
add_impression: 新增的好感度
|
|
gold: 金币
|
|
gift: 礼物
|
|
is_double: 是否触发双倍.
|
|
is_card_view: 是否为卡片视图.
|
|
|
|
返回:
|
|
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)
|
|
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:]}"
|
|
|
|
level, next_impression, previous_impression = get_level_and_next_impression(
|
|
impression
|
|
)
|
|
|
|
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)
|
|
)
|
|
|
|
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,
|
|
}
|
|
|
|
reward_info = None
|
|
rank = None
|
|
total_gold = None
|
|
last_sign_date_str = None
|
|
|
|
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 ui.render_template("pages/builtin/sign", data=card_data)
|
|
|
|
async with aiofiles.open(card_file, "wb") as f:
|
|
await f.write(image_bytes)
|
|
|
|
return card_file
|