11
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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"] = "系统错误:模板文件不存在"
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 |
@@ -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>
|
||||||