import reprlib from io import BytesIO from pathlib import Path from typing import TYPE_CHECKING from collections.abc import Sequence from dataclasses import fields, dataclass from nonebot.log import logger from nonebot_plugin_saa import MessageSegmentFactory from ..theme import theme_manager from .abstract_post import AbstractPost from ..plugin_config import plugin_config from .protocol import PlainContentSupport from ..theme.types import ThemeRenderError, ThemeRenderUnsupportError if TYPE_CHECKING: from ..platform import Platform @dataclass class Post(AbstractPost, PlainContentSupport): """最通用的Post,理论上包含所有常用的数据 对于更特殊的需要,可以考虑另外实现一个Post """ platform: "Platform" """来源平台""" content: str """文本内容""" title: str | None = None """标题""" images: Sequence[str | bytes | Path | BytesIO] | None = None """图片列表""" timestamp: float | None = None """发布/获取时间戳, 秒""" url: str | None = None """来源链接""" avatar: str | bytes | Path | BytesIO | None = None """发布者头像""" nickname: str | None = None """发布者昵称""" description: str | None = None """发布者个性签名等""" repost: "Post | None" = None """转发的Post""" def get_config_theme(self) -> str | None: """获取用户指定的theme""" return plugin_config.bison_platform_theme.get(self.platform.platform_name) def get_priority_themes(self) -> list[str]: """获取渲染所使用的theme名列表,按照优先级排序""" themes_by_priority: list[str] = [] # 最先使用用户指定的theme if user_theme := self.get_config_theme(): themes_by_priority.append(user_theme) # 然后使用平台默认的theme if self.platform.default_theme not in themes_by_priority: themes_by_priority.append(self.platform.default_theme) # 最后使用最基础的theme if "basic" not in themes_by_priority: themes_by_priority.append("basic") return themes_by_priority async def get_content(self): return self.content async def get_plain_content(self): return self.content async def generate(self) -> list[MessageSegmentFactory]: """生成消息""" themes = self.get_priority_themes() for theme_name in themes: if theme := theme_manager[theme_name]: try: logger.debug(f"Try to render Post with theme {theme_name}") return await theme.do_render(self) except ThemeRenderUnsupportError as e: logger.warning( f"Theme {theme_name} does not support Post of {self.platform.__class__.__name__}: {e}" ) continue except ThemeRenderError as e: logger.exception(f"Theme {theme_name} render error: {e}") continue else: logger.error(f"Theme {theme_name} not found") continue else: raise ThemeRenderError(f"No theme can render Post of {self.platform.__class__.__name__}") def __str__(self) -> str: aRepr = reprlib.Repr() aRepr.maxstring = 100 post_format = f"""## Post: {id(self):X} ## {self.content if len(self.content) < 200 else self.content[:200] + '...'} 来源: """ post_format += "附加信息:\n" for cls_field in fields(self): if cls_field.name in ("content", "platform", "repost"): continue else: value = getattr(self, cls_field.name) if value is not None: post_format += f"- {cls_field.name}: {aRepr.repr(value)}\n" if self.repost: post_format += "\n转发:\n" post_format += str(self.repost) return post_format