This commit is contained in:
qsc
2026-02-21 18:02:39 +08:00
parent 208426a9e8
commit fb4f5e49da
10 changed files with 214 additions and 9 deletions
+2 -4
View File
@@ -1,13 +1,11 @@
# pyright: reportArgumentType=false # pyright: reportArgumentType=false
import asyncio import asyncio
import json
import aiofiles
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger from apscheduler.triggers.interval import IntervalTrigger
from astrbot.api.event import filter, AstrMessageEvent, MessageEventResult, MessageChain from astrbot.api.event import MessageChain
from astrbot.api.star import Context, Star, register, StarTools from astrbot.api.star import Context
from astrbot.api import logger from astrbot.api import logger
from astrbot.api import AstrBotConfig from astrbot.api import AstrBotConfig
+41 -1
View File
@@ -1,6 +1,8 @@
from pathlib import Path from pathlib import Path
from datetime import datetime,date from datetime import datetime,date
import aiofiles import aiofiles
import base64
import os
async def load_template(template_name: str) -> str: async def load_template(template_name: str) -> str:
""" """
@@ -58,6 +60,7 @@ def week_to_num(week :str):
} }
return week_map.get(week,None) return week_map.get(week,None)
def compare_date_str(date_str: str) -> str: def compare_date_str(date_str: str) -> str:
""" """
date_str 格式:YYYY-MM-DD date_str 格式:YYYY-MM-DD
@@ -70,4 +73,41 @@ def compare_date_str(date_str: str) -> str:
elif d == today: elif d == today:
return "今天" return "今天"
else: else:
return "将来" return "将来"
def load_as_base64(icons_dir: str) -> dict[str, str]:
"""
将指定目录下的图标文件转换为 base64 字符串。
返回 {"文件名(不含扩展名)": "data:image/xxx;base64,xxx"} 的字典。
"""
supported_ext = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico"}
icons = {}
for filename in os.listdir(icons_dir):
name, ext = os.path.splitext(filename)
if ext.lower() not in supported_ext:
continue
# 用原始文件名(系统实际存储的)打开文件
filepath = os.path.join(icons_dir, filename)
with open(filepath, "rb") as f:
data = base64.b64encode(f.read()).decode()
# 尝试修正显示用的 key(如果文件名是 GBK 乱码)
display_name = name
try:
display_name = name.encode("latin-1").decode("gbk")
except (UnicodeEncodeError, UnicodeDecodeError):
pass # 本来就是正确的 UTF-8
if ext.lower() == ".svg":
mime = "image/svg+xml"
elif ext.lower() == ".jpg":
mime = "image/jpeg"
else:
mime = f"image/{ext.lower().lstrip('.')}"
icons[display_name] = f"data:{mime};base64,{data}"
return icons
+3 -2
View File
@@ -51,7 +51,8 @@ class JX3Service:
"code": 0, "code": 0,
"msg": "功能函数未执行", "msg": "功能函数未执行",
"data": {}, "data": {},
"temp": "" "temp": "",
"icons": {}
} }
@@ -1063,7 +1064,7 @@ class JX3Service:
# 4. 加载模板 # 4. 加载模板
try: try:
return_data["temp"] = await load_template("juesheqiyu.html") return_data["temp"] = await load_template("test.html")
except FileNotFoundError as e: except FileNotFoundError as e:
logger.error(f"加载模板失败: {e}") logger.error(f"加载模板失败: {e}")
return_data["msg"] = "系统错误:模板文件不存在" return_data["msg"] = "系统错误:模板文件不存在"
+3 -1
View File
@@ -13,11 +13,12 @@ from .bilei_data import BiLeidata
class MessageBuilder: class MessageBuilder:
"""回复消息构建""" """回复消息构建"""
def __init__(self, server: str, jx3api: JX3Service, bilei: BiLeidata, jx3at: AsyncTask): def __init__(self, server: str, jx3api: JX3Service, bilei: BiLeidata, jx3at: AsyncTask, icons: dict[str, dict[str, str]]):
self.server = server self.server = server
self.jx3api = jx3api self.jx3api = jx3api
self.bilei = bilei self.bilei = bilei
self.jx3at = jx3at self.jx3at = jx3at
self.icons = icons
async def html_render( async def html_render(
@@ -68,6 +69,7 @@ class MessageBuilder:
"omit_background": False, "omit_background": False,
"type": "jpeg" "type": "jpeg"
} }
data["data"]["icons"] = self.icons
url = await self.html_render(data["temp"], data["data"], options=options) url = await self.html_render(data["temp"], data["data"], options=options)
await event.send(event.image_result(url)) await event.send(event.image_result(url))
else: else:
+26 -1
View File
@@ -11,6 +11,7 @@ from .core.jx3_data import JX3Service
from .core.async_task import AsyncTask from .core.async_task import AsyncTask
from .core.bilei_data import BiLeidata from .core.bilei_data import BiLeidata
from .core.message import MessageBuilder from .core.message import MessageBuilder
from .core.fun_basic import load_as_base64
@register("astrbot_plugin_jx3", @register("astrbot_plugin_jx3",
"fxdyz", "fxdyz",
@@ -37,9 +38,12 @@ class Jx3ApiPlugin(Star):
# 获取数据文件路径 # 获取数据文件路径
self.get_data_path() self.get_data_path()
# 加载图片base64编码
self.load_local_base64()
# 构造所有类 # 构造所有类
self.create_all() self.create_all()
# 声明指令集 # 声明指令集
self.command_map = {} self.command_map = {}
@@ -95,6 +99,7 @@ class Jx3ApiPlugin(Star):
self.local_data_dir = StarTools.get_data_dir("astrbot_plugin_jx3") self.local_data_dir = StarTools.get_data_dir("astrbot_plugin_jx3")
# 插件数据存储路径 # 插件数据存储路径
self.plugin_data_dir = Path(__file__).parent / "data" self.plugin_data_dir = Path(__file__).parent / "data"
self.plugin_temp_dir = Path(__file__).parent /"templates"
# SQLite本地路径 # SQLite本地路径
self.local_data_path = self.local_data_dir / "local_data.db" self.local_data_path = self.local_data_dir / "local_data.db"
@@ -102,11 +107,31 @@ class Jx3ApiPlugin(Star):
self.plugin_data_path = self.plugin_data_dir /"plugin_data.db" self.plugin_data_path = self.plugin_data_dir /"plugin_data.db"
# API配置文件路径 # API配置文件路径
self.api_data_path = self.plugin_data_dir / "api_config.json" self.api_data_path = self.plugin_data_dir / "api_config.json"
# 图片文件路径
self.plugin_temp_img = self.plugin_temp_dir / "img"
self.plugin_temp_sect = self.plugin_temp_dir / "sect"
self.plugin_temp_serendipity = self.plugin_temp_dir / "serendipity"
# 数据路径打印 # 数据路径打印
logger.debug(f"本地数据路径: {self.local_data_path}") logger.debug(f"本地数据路径: {self.local_data_path}")
logger.debug(f"插件数据路径: {self.plugin_data_path}") logger.debug(f"插件数据路径: {self.plugin_data_path}")
logger.debug(f"API配置文件路径: {self.api_data_path}") logger.debug(f"API配置文件路径: {self.api_data_path}")
logger.debug(f"图片文件路径: {self.plugin_temp_img}")
logger.debug(f"图片文件路径: {self.plugin_temp_sect}")
logger.debug(f"图片文件路径: {self.plugin_temp_serendipity}")
def load_local_base64(self):
"""加载图片文件的base64编码"""
img = load_as_base64(str(self.plugin_temp_img))
sect = load_as_base64(str(self.plugin_temp_sect))
serendipity = load_as_base64(str(self.plugin_temp_serendipity))
self.icons = {
"img": img,
"sect": sect,
"serendipity": serendipity
}
logger.debug(f"图片base64编码加载完成: {self.icons}")
def create_all(self): def create_all(self):
@@ -119,7 +144,7 @@ class Jx3ApiPlugin(Star):
self.bilei = BiLeidata(self.local_sql_db) self.bilei = BiLeidata(self.local_sql_db)
self.jx3api = JX3Service(str(self.api_data_path), self.conf, self.plugin_sql_db) self.jx3api = JX3Service(str(self.api_data_path), self.conf, self.plugin_sql_db)
self.jx3at = AsyncTask(self.context, self.conf, self.jx3api, self.local_sql_db) self.jx3at = AsyncTask(self.context, self.conf, self.jx3api, self.local_sql_db)
self.jx3cmd = MessageBuilder(self.server, self.jx3api, self.bilei, self.jx3at) self.jx3cmd = MessageBuilder(self.server, self.jx3api, self.bilei, self.jx3at, self.icons)
async def init_bilei_data(self): async def init_bilei_data(self):

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

+139
View File
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>角色奇遇一览</title>
<style>
body {
margin: 0;
padding: 24px;
background: #f5f7fb;
color: #2c2c33;
font-family: "Microsoft YaHei", Arial, sans-serif;
}
.section {
margin-bottom: 32px;
}
.section-title {
font-size: 36px;
font-weight: bold;
margin-bottom: 16px;
border-left: 4px solid #4f7cff;
padding-left: 10px;
color: #1f2a44;
}
.card-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
.card {
background: #ffffff;
border-radius: 8px;
padding: 16px 12px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
min-height: 90px;
}
.card img {
width: 48px;
height: 48px;
object-fit: contain;
margin-bottom: 6px;
}
.event-name {
font-size: 22px;
font-weight: 600;
margin-bottom: 4px;
color: #2b3a67;
}
.time {
font-size: 22px;
color: #7a7f99;
}
.empty {
color: #999;
font-size: 14px;
padding: 10px 0;
}
</style>
</head>
<body>
<!-- 普通奇遇 -->
<div class="section">
<div class="section-title">普通奇遇({{ ptqy|length if ptqy else 0 }}</div>
{% if ptqy %}
<div class="card-container">
{% for item in ptqy %}
<div class="card">
{% if icons.get(item.event) %}
<img src="{{ icons.serendipity[item.event] }}" alt="{{ item.event }}">
{% else %}
<div class="event-name">{{ item.event }}</div>
{% endif %}
<div class="time">{{ item.time }}</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty">暂无普通奇遇</div>
{% endif %}
</div>
<!-- 绝世奇遇 -->
<div class="section">
<div class="section-title">绝世奇遇({{ jsqy|length if jsqy else 0 }}</div>
{% if jsqy %}
<div class="card-container">
{% for item in jsqy %}
<div class="card">
{% if icons.get(item.event) %}
<img src="{{ icons.serendipity[item.event] }}" alt="{{ item.event }}">
{% else %}
<div class="event-name">{{ item.event }}</div>
{% endif %}
<div class="time">{{ item.time }}</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty">暂无绝世奇遇</div>
{% endif %}
</div>
<!-- 宠物奇遇 -->
<div class="section">
<div class="section-title">宠物奇遇({{ cwqy|length if cwqy else 0 }}</div>
{% if cwqy %}
<div class="card-container">
{% for item in cwqy %}
<div class="card">
{% if icons.get(item.event) %}
<img src="{{ icons.serendipity[item.event] }}" alt="{{ item.event }}">
{% else %}
<div class="event-name">{{ item.event }}</div>
{% endif %}
<div class="time">{{ item.time }}</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty">暂无宠物奇遇</div>
{% endif %}
</div>
</body>
</html>