feat: 更清晰的目录结构,新的markdown构建

This commit is contained in:
2024-04-14 21:39:27 +08:00
parent 65dcf36fe7
commit 15a329029d
31 changed files with 269 additions and 141 deletions

View File

@ -10,9 +10,9 @@ __VERSION__ = "6.2.8" # 60201
import requests
from liteyuki.utils.config import load_from_yaml, config
from .log import init_log
from .data_manager import auto_migrate
from liteyuki.utils.base.config import load_from_yaml, config
from liteyuki.utils.base.log import init_log
from liteyuki.utils.base.data_manager import auto_migrate
major, minor, patch = map(int, __VERSION__.split("."))
__VERSION_I__ = major * 10000 + minor * 100 + patch

View File

@ -4,9 +4,9 @@ import nonebot
import yaml
from pydantic import BaseModel
from liteyuki.utils.data_manager import StoredConfig, common_db
from liteyuki.utils.ly_typing import T_Bot
from liteyuki.utils.tools import random_hex_string
from .data_manager import StoredConfig, common_db
from .ly_typing import T_Bot
from ..message.tools import random_hex_string
config = {} # 全局配置,确保加载后读取

View File

@ -2,7 +2,7 @@ import os
from pydantic import Field
from liteyuki.utils.data import LiteModel, Database as DB
from .data import LiteModel, Database as DB
DATA_PATH = "data/liteyuki"

View File

@ -2,13 +2,13 @@ import sys
import loguru
from typing import TYPE_CHECKING
from .config import load_from_yaml
from .language import Language, get_default_lang
from .language import get_default_lang
logger = loguru.logger
if TYPE_CHECKING:
# avoid sphinx autodoc resolve annotation failed
# because loguru module do not have `Logger` class actually
from loguru import Logger, Record
from loguru import Record
def default_filter(record: "Record"):

View File

@ -8,8 +8,8 @@ import psutil
import requests
from aiohttp import FormData
from . import __VERSION_I__, __VERSION__, __NAME__
from .config import config, load_from_yaml
from .. import __VERSION_I__, __VERSION__, __NAME__
from .config import load_from_yaml
class LiteyukiAPI:

View File

@ -1,7 +1,5 @@
from nonebot.adapters.onebot import v11
from liteyuki.utils.ly_typing import T_GroupMessageEvent, T_MessageEvent
GROUP_ADMIN = v11.GROUP_ADMIN
GROUP_OWNER = v11.GROUP_OWNER

View File

@ -42,7 +42,7 @@ def load_resource_from_dir(path: str):
metadata["path"] = path
metadata["folder"] = os.path.basename(path)
if os.path.exists(os.path.join(path, "lang")):
from liteyuki.utils.language import load_from_dir
from liteyuki.utils.base.language import load_from_dir
load_from_dir(os.path.join(path, "lang"))
_loaded_resource_packs.insert(0, ResourceMetadata(**metadata))

View File

View File

@ -0,0 +1,186 @@
import base64
from io import BytesIO
from urllib.parse import quote
import aiohttp
from PIL import Image
from ..base.config import get_config
from ..base.ly_typing import T_Bot
def markdown_escape(text: str) -> str:
"""
转义Markdown特殊字符
Args:
text: str: 文本
Returns:
str: 转义后文本
"""
text = text.replace("\n", r"\n").replace('"', r'\\\"')
spacial_chars = r"\`*_{}[]()#+-.!"
for char in spacial_chars:
text = text.replace(char, "\\" + char)
return text
def escape_decorator(func):
def wrapper(text: str):
return func(markdown_escape(text))
return wrapper
class MarkdownComponent:
@staticmethod
@escape_decorator
def heading(text: str, level: int = 1) -> str:
"""标题"""
assert 1 <= level <= 6, "标题级别应在 1-6 之间"
return f"{'#' * level} {text}"
@staticmethod
@escape_decorator
def bold(text: str) -> str:
"""粗体"""
return f"**{text}**"
@staticmethod
@escape_decorator
def italic(text: str) -> str:
"""斜体"""
return f"*{text}*"
@staticmethod
@escape_decorator
def strike(text: str) -> str:
"""删除线"""
return f"~~{text}~~"
@staticmethod
@escape_decorator
def code(text: str) -> str:
"""行内代码"""
return f"`{text}`"
@staticmethod
@escape_decorator
def code_block(text: str, language: str = "") -> str:
"""代码块"""
return f"```{language}\n{text}\n```"
@staticmethod
@escape_decorator
def quote(text: str) -> str:
"""引用"""
return f"> {text}"
@staticmethod
@escape_decorator
def link(text: str, url: str, symbol: bool = True) -> str:
"""
链接
Args:
text: 链接文本
url: 链接地址
symbol: 是否显示链接图标, mqqapi请使用False
"""
return f"[{'🔗' if symbol else ''}{text}]({quote(url)})"
@staticmethod
@escape_decorator
def image(url: str, *, size: tuple[int, int]) -> str:
"""
图片,本地图片不建议直接使用
Args:
url: 图片链接
size: 图片大小
Returns:
markdown格式的图片
"""
return f"![image #{size[0]}px #{size[1]}px]({url})"
@staticmethod
@escape_decorator
async def auto_image(image: str | bytes, bot: T_Bot) -> str:
"""
自动获取图片大小
Args:
image: 本地图片路径 | 图片url http/file | 图片bytes
bot: bot对象用于上传图片到图床
Returns:
markdown格式的图片
"""
if isinstance(image, bytes):
# 传入为二进制图片
image_obj = Image.open(BytesIO(image))
base64_string = base64.b64encode(image_obj.tobytes()).decode("utf-8")
url = await bot.call_api("upload_image", file=f"base64://{base64_string}")
size = image_obj.size
elif isinstance(image, str):
# 传入链接或本地路径
if image.startswith("http"):
# 网络请求
async with aiohttp.ClientSession() as session:
async with session.get(image) as resp:
image_data = await resp.read()
url = image
size = Image.open(BytesIO(image_data)).size
else:
# 本地路径/file://
image_obj = Image.open(image.replace("file://", ""))
base64_string = base64.b64encode(image_obj.tobytes()).decode("utf-8")
url = await bot.call_api("upload_image", file=f"base64://{base64_string}")
size = image_obj.size
else:
raise ValueError("图片类型错误")
return MarkdownComponent.image(url, size=size)
@staticmethod
@escape_decorator
def table(data: list[list[any]]) -> str:
"""
表格
Args:
data: 表格数据,二维列表
Returns:
markdown格式的表格
"""
# 表头
table = "|".join(map(str, data[0])) + "\n"
table += "|".join([":-:" for _ in range(len(data[0]))]) + "\n"
# 表内容
for row in data[1:]:
table += "|".join(map(str, row)) + "\n"
return table
class Mqqapi:
@staticmethod
@escape_decorator
def cmd(text: str, cmd: str, enter: bool = True, reply: bool = False, use_cmd_start: bool = True) -> str:
"""
生成点击回调文本
Args:
text: 显示内容
cmd: 命令
enter: 是否自动发送
reply: 是否回复
use_cmd_start: 是否使用配置的命令前缀
Returns:
[text](mqqapi://) markdown格式的可点击回调文本类似于链接
"""
if use_cmd_start:
command_start = get_config("command_start", [])
if command_start:
# 若命令前缀不为空,则使用配置的第一个命令前缀
cmd = f"{command_start[0]}{cmd}"
return f"[{text}](mqqapi://aio/inlinecmd?command={quote(cmd)}&reply={str(reply).lower()}&enter={str(enter).lower()})"

View File

@ -1,4 +1,3 @@
import asyncio
import base64
import io
from urllib.parse import quote
@ -8,12 +7,14 @@ from PIL import Image
import aiohttp
import nonebot
from nonebot import require
from nonebot.adapters.onebot import v11, v12
from typing import Any
from nonebot.adapters.onebot import v11
from typing import Any, Type
from . import load_from_yaml
from .ly_api import liteyuki_api
from .ly_typing import T_Bot, T_Message, T_MessageEvent
from nonebot.internal.adapter import MessageSegment
from nonebot.internal.adapter.message import TM
from .. import load_from_yaml
from ..base.ly_typing import T_Bot, T_Message, T_MessageEvent
require("nonebot_plugin_htmlrender")
from nonebot_plugin_htmlrender import md_to_pic
@ -28,12 +29,12 @@ async def broadcast_to_superusers(message: str | T_Message, markdown: bool = Fal
for bot in nonebot.get_bots().values():
for user_id in config.get("superusers", []):
if markdown:
await Markdown.send_md(message, bot, message_type="private", session_id=user_id)
await MarkdownMessage.send_md(message, bot, message_type="private", session_id=user_id)
else:
await bot.send_private_msg(user_id=user_id, message=message)
class Markdown:
class MarkdownMessage:
@staticmethod
async def send_md(
markdown: str,
@ -158,8 +159,8 @@ class Markdown:
if method == 2:
base64_string = base64.b64encode(image).decode("utf-8")
data = await bot.call_api("upload_image", file=f"base64://{base64_string}")
await Markdown.send_md(Markdown.image(data, Image.open(io.BytesIO(image)).size), bot, event=event, message_type=message_type,
session_id=session_id, **kwargs)
await MarkdownMessage.send_md(MarkdownMessage.image(data, Image.open(io.BytesIO(image)).size), bot, event=event, message_type=message_type,
session_id=session_id, **kwargs)
# 其他实现端方案
else:
@ -171,8 +172,8 @@ class Markdown:
))["message_id"]
image_url = (await bot.get_msg(message_id=image_message_id))["message"][0]["data"]["url"]
image_size = Image.open(io.BytesIO(image)).size
image_md = Markdown.image(image_url, image_size)
return await Markdown.send_md(image_md, bot, message_type=message_type, session_id=session_id, event=event, **kwargs)
image_md = MarkdownMessage.image(image_url, image_size)
return await MarkdownMessage.send_md(image_md, bot, message_type=message_type, session_id=session_id, event=event, **kwargs)
if data is None:
data = await bot.send_msg(
@ -251,7 +252,7 @@ class Markdown:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
image = Image.open(io.BytesIO(await resp.read()))
return Markdown.image(url, image.size)
return MarkdownMessage.image(url, image.size)
except Exception as e:
nonebot.logger.error(f"get image error: {e}")
return "[Image Error]"
@ -270,58 +271,3 @@ class Markdown:
for char in chars:
text = text.replace(char, f"\\\\{char}")
return text
@staticmethod
def H1(text: str, end="\n") -> str:
"""H1标题"""
return f"# {text}{end}"
@staticmethod
def H2(text: str, end="\n") -> str:
"""H2标题"""
return f"## {text}{end}"
@staticmethod
def H3(text: str, end="\n") -> str:
"""H3标题"""
return f"### {text}{end}"
@staticmethod
def H4(text: str, end="\n") -> str:
"""H4标题"""
return f"#### {text}{end}"
@staticmethod
def H5(text: str, end="\n") -> str:
"""H5标题"""
return f"##### {text}{end}"
@staticmethod
def H6(text: str, end="\n") -> str:
"""H6标题"""
return f"###### {text}{end}"
@staticmethod
def Bold(text: str) -> str:
"""加粗"""
return f"**{text}**"
@staticmethod
def Italic(text: str) -> str:
"""斜体"""
return f"*{text}*"
@staticmethod
def BoldItalic(text: str) -> str:
"""粗斜体"""
return f"***{text}***"
@staticmethod
def Underline(text: str) -> str:
"""下划线"""
return f"__{text}__"
@staticmethod
def Strike(text: str) -> str:
"""删除线"""
return f"~~{text}~~"