添加 Theme 功能

This commit is contained in:
Azide
2023-10-13 23:18:39 +08:00
committed by felinae98
parent 6aaec45d15
commit f202071e9f
57 changed files with 1709 additions and 802 deletions
@@ -0,0 +1,10 @@
# Jinja模版与LOGO图片说明
## LOGO图片
- `templates/ceobecanteen_logo.png`
### 版权声明
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a><br />logo图片采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可。
本项目<img src="templates/ceobecanteen_logo.png" style="width:100px">使用已经过 [Ceobe Canteen](https://github.com/Enraged-Dun-Cookie-Development-Team) 授权许可使用。
@@ -0,0 +1,3 @@
from .build import CeobeCanteenTheme
__theme_meta__ = CeobeCanteenTheme()
@@ -0,0 +1,113 @@
from pathlib import Path
from datetime import datetime
from typing import TYPE_CHECKING, Literal
import jinja2
from pydantic import BaseModel, root_validator
from nonebot_plugin_saa import Text, Image, MessageSegmentFactory
from nonebot_bison.theme.utils import convert_to_qr
from nonebot_bison.theme import Theme, ThemeRenderError, ThemeRenderUnsupportError
if TYPE_CHECKING:
from nonebot_bison.post import Post
class CeobeInfo(BaseModel):
"""卡片的信息部分
datasource: 数据来源
time: 时间
"""
datasource: str
time: str
class CeoboContent(BaseModel):
"""卡片的内容部分
image: 图片链接
text: 文字内容
"""
image: str | None
text: str | None
@root_validator
def check(cls, values):
if values["image"] is None and values["text"] is None:
raise ValueError("image and text cannot be both None")
return values
class CeobeCard(BaseModel):
info: CeobeInfo
content: CeoboContent
qr: str | None
class CeobeCanteenTheme(Theme):
"""小刻食堂 分享卡片风格主题
需要安装`nonebot_plugin_htmlrender`插件
"""
name: Literal["ceobecanteen"] = "ceobecanteen"
need_browser: bool = True
template_path: Path = Path(__file__).parent / "templates"
template_name: str = "ceobe_canteen.html.jinja"
def parse(self, post: "Post") -> CeobeCard:
"""解析 Post 为 CeobeCard"""
if not post.nickname:
raise ThemeRenderUnsupportError("post.nickname is None")
if not post.timestamp:
raise ThemeRenderUnsupportError("post.timestamp is None")
info = CeobeInfo(
datasource=post.nickname, time=datetime.fromtimestamp(post.timestamp).strftime("%Y-%m-%d %H:%M:%S")
)
head_pic = post.images[0] if post.images else None
if head_pic is not None and not isinstance(head_pic, str):
raise ThemeRenderUnsupportError("post.images[0] is not str")
content = CeoboContent(image=head_pic, text=post.content)
return CeobeCard(info=info, content=content, qr=convert_to_qr(post.url or "No URL"))
async def render(self, post: "Post") -> list[MessageSegmentFactory]:
ceobe_card = self.parse(post)
from nonebot_plugin_htmlrender import get_new_page
template_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(self.template_path),
enable_async=True,
)
template = template_env.get_template(self.template_name)
html = await template.render_async(card=ceobe_card)
pages = {
"viewport": {"width": 1000, "height": 3000},
"base_url": self.template_path.as_uri(),
}
try:
async with get_new_page(**pages) as page:
await page.goto(self.template_path.as_uri())
await page.set_content(html)
await page.wait_for_timeout(1)
img_raw = await page.locator("#ceobecanteen-card").screenshot(
type="png",
)
except Exception as e:
raise ThemeRenderError(f"Render error: {e}") from e
msgs: list[MessageSegmentFactory] = [Image(img_raw)]
text = f"来源: {post.platform.name} {post.nickname or ''}\n"
if post.url:
text += f"详情: {post.url}"
msgs.append(Text(text))
if post.images:
msgs.extend(map(Image, post.images))
return msgs
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

@@ -0,0 +1,98 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>小刻食堂分享卡片</title>
</head>
<body>
<div id="ceobecanteen-card">
{% if card.content.image %}
<img src="{{ card.content.image }}" class="cover-img">
{% endif %}
{% if card.content.text %}
<div class="main-content">{{ card.content.text }}</div>
{% endif %}
<div class="footer">
<div class="datasource">
<div class="datasource-text">
<div class="datasource-name">{{ card.info.datasource }}</div>
<div class="time">{{ card.info.time }}</div>
</div>
<img class='qr' src="{{ card.qr }}">
</div>
<div class="source">
<img class='bison-logo' src="bison_logo.jpg">
<div class="source-text">
<div class="slogan">小刻吃到饼啦!</div>
<div class="linkage">bison&amp;小刻食堂联动</div>
<div class="description">来自小刻食堂再转发</div>
</div>
<img class='ceobe-logo' src="ceobecanteen_logo.png">
</div>
</div>
</div>
</body>
</html>
<style type="text/css">
#ceobecanteen-card {
width: 700px;
background-color: rgb(240, 236, 233);
}
.cover-img {
width: 100%;
}
.main-content {
padding: 30px;
white-space: pre-line;
}
.footer {
margin: 0 2%;
height: 80px;
border-top: 1px solid rgb(185, 181, 177);
display: flex;
padding: 10px 0;
}
.footer .datasource {
width: 45%;
padding-right: 5px;
border-right: 1px solid rgb(185, 181, 177);
display: flex;
justify-content: space-around;
}
.footer .datasource .datasource-text {
align-self: center;
}
.footer .datasource .datasource-text .datasource-name {
font-weight: bold;
font-size: 24px;
}
.footer .datasource .datasource-text .time {
font-size: 13px;
color: rgb(138, 136, 134);
}
.footer .datasource .qr {
width: 80px;
object-fit: contain;
}
.source {
width: 55%;
display: flex;
justify-content: space-evenly;
color: rgb(138, 136, 134);
font-size: 13px;
}
.footer .source .source-text {
align-self: center;
}
.source .slogan {
font-weight: bold;
font-size: 24px;
color: black;
}
.source .bison-logo,
.source .ceobe-logo {
width: 60px;
object-fit: contain;
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB