zhenxun_bot/zhenxun/utils/common_utils.py
Rumio 11524bcb04
Some checks failed
检查bot是否运行正常 / bot check (push) Has been cancelled
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
Sequential Lint and Type Check / ruff-call (push) Has been cancelled
Release Drafter / Update Release Draft (push) Has been cancelled
Force Sync to Aliyun / sync (push) Has been cancelled
Update Version / update-version (push) Has been cancelled
Sequential Lint and Type Check / pyright-call (push) Has been cancelled
♻️ refactor: 统一图片渲染架构并引入通用UI组件系统 (#2019)
* ♻️ refactor: 统一图片渲染架构并引入通用UI组件系统

🎨 **渲染服务重构**
- 统一图片渲染入口,引入主题系统支持
- 优化Jinja2环境管理,支持主题覆盖和插件命名空间
- 新增UI缓存机制和主题重载功能

 **通用UI组件系统**
- 新增 zhenxun.ui 模块,提供数据模型和构建器
- 引入BaseBuilder基类,支持链式调用
- 新增多种UI构建器:InfoCard, Markdown, Table, Chart, Layout等
- 新增通用组件:Divider, Badge, ProgressBar, UserInfoBlock

🔄 **插件迁移**
- 迁移9个内置插件至新渲染系统
- 移除各插件中分散的图片生成工具
- 优化数据处理和渲染逻辑

💥 **Breaking Changes**
- 移除旧的图片渲染接口和模板路径
- TEMPLATE_PATH 更名为 THEMES_PATH
- 插件需适配新的RendererService和zhenxun.ui模块

*  test(check): 更新自检插件测试中的渲染服务模拟

* ♻️ refactor(renderer): 将缓存文件名哈希算法切换到 SHA256

* ♻️ refactor(shop): 移除商店HTML图片生成模块

* 🚨 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>
2025-08-15 16:34:37 +08:00

138 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import re
from typing import overload
from nonebot.adapters import Bot
from nonebot_plugin_uninfo import Session, SupportScope, Uninfo, get_interface
from zhenxun.configs.config import BotConfig
from zhenxun.models.ban_console import BanConsole
from zhenxun.models.bot_console import BotConsole
from zhenxun.models.group_console import GroupConsole
from zhenxun.models.task_info import TaskInfo
from zhenxun.services.log import logger
class CommonUtils:
@classmethod
async def task_is_block(
cls, session: Uninfo | Bot, module: str, group_id: str | None = None
) -> bool:
"""判断被动技能是否可以发送
参数:
module: 被动技能模块名
group_id: 群组id
返回:
bool: 是否可以发送
"""
if isinstance(session, Bot):
if interface := get_interface(session):
info = interface.basic_info()
if info["scope"] == SupportScope.qq_api:
logger.info("q官bot放弃所有被动技能发言...")
"""q官bot放弃所有被动技能发言"""
return False
if session.scene == SupportScope.qq_api:
"""q官bot放弃所有被动技能发言"""
logger.info("q官bot放弃所有被动技能发言...")
return False
if not group_id and isinstance(session, Session):
group_id = session.group.id if session.group else None
if task := await TaskInfo.get_or_none(module=module):
"""被动全局状态"""
if not task.status:
return True
if not await BotConsole.get_bot_status(session.self_id):
"""bot是否休眠"""
return True
block_tasks = await BotConsole.get_tasks(session.self_id, False)
if module in block_tasks:
"""bot是否禁用被动"""
return True
if group_id:
if await GroupConsole.is_block_task(group_id, module):
"""群组是否禁用被动"""
return True
if g := await GroupConsole.get_group(group_id=group_id):
"""群组权限是否小于0"""
if g.level < 0:
return True
if await BanConsole.is_ban(None, group_id):
"""群组是否被ban"""
return True
return False
@staticmethod
def format(name: str) -> str:
return f"<{name},"
@overload
@classmethod
def convert_module_format(cls, data: str) -> list[str]: ...
@overload
@classmethod
def convert_module_format(cls, data: list[str]) -> str: ...
@classmethod
def convert_module_format(cls, data: str | list[str]) -> str | list[str]:
"""
在 `<aaa,<bbb,<ccc,` 和 `["aaa", "bbb", "ccc"]` 之间进行相互转换。
参数:
data (str | list[str]): 输入数据,可能是格式化字符串或字符串列表。
返回:
str | list[str]: 根据输入类型返回转换后的数据。
"""
if isinstance(data, str):
return [item.strip(",") for item in data.split("<") if item]
elif isinstance(data, list):
return "".join(cls.format(item) for item in data)
class SqlUtils:
@classmethod
def random(cls, query, limit: int = 1) -> str:
db_class_name = BotConfig.get_sql_type()
if "postgres" in db_class_name or "sqlite" in db_class_name:
query = f"{query.sql()} ORDER BY RANDOM() LIMIT {limit};"
elif "mysql" in db_class_name:
query = f"{query.sql()} ORDER BY RAND() LIMIT {limit};"
else:
logger.warning(
f"Unsupported database type: {db_class_name}", query.__module__
)
return query
@classmethod
def add_column(
cls,
table_name: str,
column_name: str,
column_type: str,
default: str | None = None,
not_null: bool = False,
) -> str:
sql = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}"
if default:
sql += f" DEFAULT {default}"
if not_null:
sql += " NOT NULL"
return sql
def format_usage_for_markdown(text: str) -> str:
"""
智能地将Python多行字符串转换为适合Markdown渲染的格式。
- 将单个换行符替换为Markdown的硬换行行尾加两个空格
- 保留两个或更多的连续换行符使其成为Markdown的段落分隔。
"""
if not text:
return ""
text = re.sub(r"\n{2,}", "<<PARAGRAPH_BREAK>>", text)
text = text.replace("\n", " \n")
text = text.replace("<<PARAGRAPH_BREAK>>", "\n\n")
return text