非event卡推送,以及手动查看

This commit is contained in:
2026-10-21 18:16:43 +08:00
parent 32f5d434b1
commit 113c8eb203
4 changed files with 204 additions and 33 deletions
+2
View File
@@ -10,6 +10,8 @@ from .config import pconfig
from .lib import rlib from .lib import rlib
from .sync import sync from .sync import sync
from . import command
driver = nonebot.get_driver() driver = nonebot.get_driver()
+47
View File
@@ -0,0 +1,47 @@
from pathlib import Path
from nonebot import Bot, logger
from nonebot.adapters.onebot.v11 import Message, MessageSegment
from nonebot.internal.matcher import Matcher
from nonebot.plugin.on import on_command
from . import rlib, pconfig
handler = on_command("烤leak最近活动")
@handler.handle()
async def _(matcher: Matcher, bot: Bot):
try:
data = rlib.refetch_event().unwrap()
message = Message()
inner = [MessageSegment.text(f"活动 {data.event_id} 的卡牌数据")]
if len(data.cards) == 0:
inner.append(MessageSegment.text("没有新的卡牌。"))
# else
for card in data.cards:
inner.append(MessageSegment.text(card.text))
for item in card.path:
p = Path(item)
n = MessageSegment.image(file=item)
n.data["summary"] = f"[{p.name}]"
inner.append(n)
for node in inner:
message.append(MessageSegment.node_custom(
user_id=int(bot.self_id),
nickname=pconfig.nickname,
content=Message([node])
))
await matcher.send(message)
for card in data.cards:
for path in card.path:
try:
Path(path).unlink()
except Exception as e:
logger.error(f"删除文件 {path} 失败: {e}")
except Exception as e:
logger.error(f"获取活动数据失败: {e}")
await matcher.send(f"获取活动数据失败: {e}")
return
+122 -7
View File
@@ -3,7 +3,7 @@ import json
import os import os
import sys import sys
from dataclasses import field, dataclass from dataclasses import field, dataclass
from typing import Optional, Dict, Any, List from typing import Optional, Dict, Any, List, Union, TypeVar, Generic, Callable
def get_lib_filename(base_name): def get_lib_filename(base_name):
@@ -15,26 +15,30 @@ def get_lib_filename(base_name):
return f"lib{base_name}.so" return f"lib{base_name}.so"
T = TypeVar("T")
@dataclass @dataclass
class FetchResultCard: class FetchResultCard:
text: str = "" text: str = ""
path: List[str] = field(default_factory=list) path: List[str] = field(default_factory=list)
@classmethod @classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'FetchResultCard': def from_dict(cls, data: Dict[str, Any]) -> "FetchResultCard":
return cls( return cls(
text=data.get("text", ""), text=data.get("text", ""),
path=data.get("path", []), # 兼容 Rust 中 PathBuf 序列化出的路径字符串列表
path=[str(p) for p in data.get("path", [])],
) )
@dataclass @dataclass
class FetchResult: class FetchResultEvent:
event_id: int = 0 event_id: int = 0
cards: List[FetchResultCard] = field(default_factory=list) cards: List[FetchResultCard] = field(default_factory=list)
@classmethod @classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'FetchResult': def from_dict(cls, data: Dict[str, Any]) -> "FetchResultEvent":
return cls( return cls(
event_id=data.get("event_id", 0), event_id=data.get("event_id", 0),
cards=[ cards=[
@@ -44,6 +48,95 @@ class FetchResult:
) )
@dataclass
class FetchResultUnlinkedCards:
cards: List[FetchResultCard] = field(default_factory=list)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "FetchResultUnlinkedCards":
return cls(
cards=[
FetchResultCard.from_dict(card)
for card in data.get("cards", [])
],
)
# 对应 Rust 中的 FetchResult 枚举
FetchResultType = Union[FetchResultEvent, FetchResultUnlinkedCards]
class FetchResult:
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> FetchResultType:
tag = data.get("type")
content = data.get("data", {})
if tag == "Event":
return FetchResultEvent.from_dict(content)
elif tag == "UnlinkedCard":
return FetchResultUnlinkedCards.from_dict(content)
else:
raise ValueError(f"Unknown FetchResult type: {tag}")
@dataclass
class ApiResultSuccess(Generic[T]):
data: T
@staticmethod
def is_ok() -> bool:
return True
@staticmethod
def is_err() -> bool:
return False
def unwrap(self) -> T:
return self.data
@classmethod
def from_dict(cls, data: Dict[str, Any], parser: Callable[[Any], T] | None = None) -> "ApiResultSuccess[T]":
data = parser(data) if parser else data
return cls(data=data)
@dataclass
class ApiResultError(Generic[T]):
error: str
@staticmethod
def is_ok() -> bool:
return False
@staticmethod
def is_err() -> bool:
return True
def unwrap(self) -> T:
raise Exception(f"ApiResultError: {self.error}")
@classmethod
def from_dict(cls, data: str) -> "ApiResultError[T]":
return cls(error=data)
class ApiResult(Generic[T]):
@classmethod
def from_dict(cls, data: Dict[str, Any], parser: Callable[[Any], T] | None = None) -> ApiResultSuccess[T] | \
ApiResultError[T]:
tag = data.get("type")
content = data.get("data", {})
if tag == "Ok":
return ApiResultSuccess.from_dict(content, parser=parser)
elif tag == "Err":
return ApiResultError.from_dict(content)
else:
raise ValueError(f"Unknown ApiResult type: {tag}")
class SekaiSyncLib: class SekaiSyncLib:
def __init__(self): def __init__(self):
self.lib = ctypes.CDLL(os.path.join(os.path.dirname(__file__), get_lib_filename("sekai_sync_lib"))) self.lib = ctypes.CDLL(os.path.join(os.path.dirname(__file__), get_lib_filename("sekai_sync_lib")))
@@ -79,6 +172,9 @@ class SekaiSyncLib:
self.lib.fetch_data.argtypes = [] self.lib.fetch_data.argtypes = []
self.lib.fetch_data.restype = ctypes.c_void_p self.lib.fetch_data.restype = ctypes.c_void_p
self.lib.refetch_event.argtypes = []
self.lib.refetch_event.restype = ctypes.c_void_p
# free_string(s: *mut c_char) # free_string(s: *mut c_char)
self.lib.free_string.argtypes = [ctypes.c_void_p] self.lib.free_string.argtypes = [ctypes.c_void_p]
self.lib.free_string.restype = None self.lib.free_string.restype = None
@@ -122,7 +218,7 @@ class SekaiSyncLib:
""" """
return self.lib.stop() return self.lib.stop()
def fetch_data(self) -> Optional[FetchResult]: def fetch_data(self) -> None | list[FetchResultEvent | FetchResultUnlinkedCards]:
# 1. 获取裸指针 # 1. 获取裸指针
ptr = self.lib.fetch_data() ptr = self.lib.fetch_data()
@@ -138,7 +234,26 @@ class SekaiSyncLib:
# 3. 解码并反序列化 JSON # 3. 解码并反序列化 JSON
json_str = json_bytes.decode('utf-8') json_str = json_bytes.decode('utf-8')
return FetchResult.from_dict(json.loads(json_str)) lst = json.loads(json_str)
return [FetchResult.from_dict(item) for item in lst]
finally:
# 4. 无论是否发生 JSON 解析异常,绝对保证释放 Rust 内存!
self.lib.free_string(ptr)
def refetch_event(self) -> ApiResultSuccess[FetchResultEvent] | ApiResultError[FetchResultEvent]:
# 1. 获取裸指针
ptr = self.lib.refetch_event()
try:
# 2. 将 void_p 转换回 c_char_p 并读取里面的 bytes
json_bytes = ctypes.cast(ptr, ctypes.c_char_p).value
if not json_bytes:
return ApiResultError(error="调用失败")
# 3. 解码并反序列化 JSON
json_str = json_bytes.decode('utf-8')
return ApiResult.from_dict(json.loads(json_str), parser=FetchResultEvent.from_dict)
finally: finally:
# 4. 无论是否发生 JSON 解析异常,绝对保证释放 Rust 内存! # 4. 无论是否发生 JSON 解析异常,绝对保证释放 Rust 内存!
+11 -4
View File
@@ -8,7 +8,7 @@ from nonebot.permission import SUPERUSER
from nonebot.plugin.on import on_command from nonebot.plugin.on import on_command
from .config import pconfig from .config import pconfig
from .lib import rlib from .lib import rlib, FetchResultEvent
group_file = Path("config") / "nonebot_sekai_update_notify" / "group.json" group_file = Path("config") / "nonebot_sekai_update_notify" / "group.json"
group_file.parent.mkdir(parents=True, exist_ok=True) group_file.parent.mkdir(parents=True, exist_ok=True)
@@ -34,9 +34,16 @@ async def sync():
data = rlib.fetch_data() data = rlib.fetch_data()
if data is None: if data is None:
return return
for component in data:
message = Message() message = Message()
inner = [MessageSegment.text(f"活动 {data.event_id} 的卡牌更新了!")] if component is FetchResultEvent:
for card in data.cards: inner = [MessageSegment.text(f"活动 {component.event_id} 更新了!")]
else:
inner = [MessageSegment.text("发现了新的非活动卡牌!")]
if len(component.cards) == 0:
inner.append(MessageSegment.text("但没有新的卡牌。"))
# else
for card in component.cards:
inner.append(MessageSegment.text(card.text)) inner.append(MessageSegment.text(card.text))
for item in card.path: for item in card.path:
p = Path(item) p = Path(item)
@@ -56,7 +63,7 @@ async def sync():
except Exception as e: except Exception as e:
logger.error(f"发送消息到群组 {group} 失败: {e}") logger.error(f"发送消息到群组 {group} 失败: {e}")
for card in data.cards: for card in component.cards:
for path in card.path: for path in card.path:
try: try:
Path(path).unlink() Path(path).unlink()