Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b34d80c
修改extra的一些参数支持
LoCCai Jan 2, 2026
085be2a
Refactor render_image method and clean up docstrings
LoCCai Jan 2, 2026
3202b0d
添加对额外模板的支持
LoCCai Jan 2, 2026
f349fc1
Fix type_tag assignment in Bilibili parser
LoCCai Jan 2, 2026
34f235a
添加哔站视频平台HTML渲染卡
LoCCai Jan 2, 2026
340d908
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 2, 2026
a648706
Update dynamic.py
LoCCai Jan 2, 2026
a220adc
Update import and conversion in parse_dynamic method
LoCCai Jan 2, 2026
6daab0c
Update __init__.py
LoCCai Jan 2, 2026
296b3aa
Update htmlrender.py
LoCCai Jan 2, 2026
34076ce
Update __init__.py
LoCCai Jan 2, 2026
98cccbd
Refactor platform logo path retrieval logic
LoCCai Jan 2, 2026
c1512d5
Refactor BilibiliParser for improved functionality
LoCCai Jan 2, 2026
e92270e
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 2, 2026
eeec04b
Update __init__.py
LoCCai Jan 2, 2026
61a880c
优先从 modules.module_dynamic.desc.text 获取文本
LoCCai Jan 2, 2026
55baba5
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 2, 2026
81d10ae
Enhance dynamic parsing with logging and origin handling
LoCCai Jan 2, 2026
f27c60d
Merge branch 'fllesser:master' into master
LoCCai Jan 2, 2026
ea8d00f
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 2, 2026
78e5845
转发信息展示支持
LoCCai Jan 2, 2026
0f43408
Update ytdlp.py
LoCCai Jan 2, 2026
89ee3a7
Change footer text and adjust image CSS properties
LoCCai Jan 2, 2026
38deb3b
Optimize image display logic in bilibili template
LoCCai Jan 3, 2026
22223ad
Update __init__.py
LoCCai Jan 3, 2026
d8ac472
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 3, 2026
f1a04f5
Change module_stat type from OpusStat to dict
LoCCai Jan 3, 2026
6e36f2b
Update test_bilibili.py
LoCCai Jan 3, 2026
d5823e8
Refactor parsing methods and improve error handling
LoCCai Jan 4, 2026
dd34637
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 4, 2026
d97aab4
Add delay send media function with emoji trigger
fllesser Jan 5, 2026
8b904f2
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 5, 2026
8456e72
Fix basedpyright type errors
fllesser Jan 5, 2026
50030bc
Fix git merge conflicts and basedpyright type errors
fllesser Jan 5, 2026
fa2c896
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 5, 2026
8e5c2e3
Fix remaining basedpyright type errors
fllesser Jan 5, 2026
ee0b64a
Fix merge conflicts
fllesser Jan 5, 2026
491691c
修复git合并冲突和类型错误
fllesser Jan 5, 2026
b8b47a7
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 5, 2026
7dbbebd
修复parsers/data.py中的git合并冲突标记
fllesser Jan 5, 2026
9a8fcfd
合并远程更改并修复git合并冲突
fllesser Jan 5, 2026
8f6bf35
修复ruff check发现的未使用导入问题
fllesser Jan 5, 2026
5cc4002
实现根据贴表情的卡片发送对应视频功能
fllesser Jan 5, 2026
066d76b
修复根据贴表情的卡片发送对应视频功能
fllesser Jan 5, 2026
fe30e37
chore: auto fix by pre-commit hooks
pre-commit-ci[bot] Jan 5, 2026
16f53c3
更新README.md,添加延迟发送媒体配置项说明
fllesser Jan 5, 2026
b8d59c5
合并远程更改
fllesser Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,15 @@ parser_emoji_cdn="https://emojicdn.elk.sh"

# [可选] emoji 渲染样式 "apple", "google", "twitter", "facebook"(默认)
parser_emoji_style="facebook"

# [可选] 是否延迟发送视频/音频,需要用户发送特定表情或点赞特定表情后才发送
parser_delay_send_media=False

# [可选] 触发延迟发送视频的表情
parser_delay_send_emoji="🎬"

# [可选] 触发延迟发送视频的表情ID列表,用于监听group_msg_emoji_like事件
parser_delay_send_emoji_ids='["128077"]'
```

</details>
Expand Down
21 changes: 21 additions & 0 deletions src/nonebot_plugin_parser/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class Config(BaseModel):
"""Pilmoji 表情 CDN"""
parser_emoji_style: EmojiStyle = EmojiStyle.FACEBOOK
"""Pilmoji 表情样式"""
parser_delay_send_media: bool = False
"""是否延迟发送视频/音频,需要用户发送特定表情或点赞特定表情后才发送"""
parser_delay_send_emoji: str = "🎬"
"""触发延迟发送视频的表情"""
parser_delay_send_emoji_ids: list[str] = ["128077"]
"""触发延迟发送视频的表情ID列表,用于监听group_msg_emoji_like事件"""

@property
def nickname(self) -> str:
Expand Down Expand Up @@ -153,6 +159,21 @@ def emoji_style(self) -> EmojiStyle:
"""Pilmoji 表情样式"""
return self.parser_emoji_style

@property
def delay_send_media(self) -> bool:
"""是否延迟发送视频/音频"""
return self.parser_delay_send_media

@property
def delay_send_emoji(self) -> str:
"""触发延迟发送视频的表情"""
return self.parser_delay_send_emoji

@property
def delay_send_emoji_ids(self) -> list[str]:
"""触发延迟发送视频的表情ID列表"""
return self.parser_delay_send_emoji_ids


pconfig: Config = get_plugin_config(Config)
"""插件配置"""
Expand Down
232 changes: 229 additions & 3 deletions src/nonebot_plugin_parser/matchers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
from typing import TypeVar

from nonebot import logger, get_driver, on_command
from nonebot.rule import Rule
from nonebot.params import CommandArg
from nonebot.typing import T_State
from nonebot.matcher import Matcher
from nonebot.adapters import Message
from nonebot.plugin.on import get_matcher_source
from nonebot_plugin_alconna.uniseg import UniMsg

from .rule import SUPER_PRIVATE, Searched, SearchResult, on_keyword_regex
from ..utils import LimitedSizeDict
Expand All @@ -12,6 +17,7 @@
from ..parsers import BaseParser, ParseResult, BilibiliParser
from ..renders import get_renderer
from ..download import DOWNLOADER
from ..parsers.data import AudioContent, VideoContent


def _get_enabled_parser_classes() -> list[type[BaseParser]]:
Expand Down Expand Up @@ -55,10 +61,13 @@ def register_parser_matcher():

# 缓存结果
_RESULT_CACHE = LimitedSizeDict[str, ParseResult](max_size=50)
# 消息ID与解析结果的关联缓存,用于多用户场景
_MSG_ID_RESULT_MAP = LimitedSizeDict[str, ParseResult](max_size=100)


def clear_result_cache():
_RESULT_CACHE.clear()
_MSG_ID_RESULT_MAP.clear()


@UniHelper.with_reaction
Expand All @@ -78,10 +87,74 @@ async def parser_handler(
else:
logger.debug(f"命中缓存: {cache_key}, 结果: {result}")

# 3. 渲染内容消息并发送
# 3. 渲染内容消息并发送,保存消息ID
renderer = get_renderer(result.platform.name)
async for message in renderer.render_messages(result):
await message.send()
try:
async for message in renderer.render_messages(result):
msg_sent = await message.send()
# 保存消息ID与解析结果的关联
if msg_sent:
# 添加详细调试日志,查看msg_sent的类型和属性
logger.debug(f"消息发送返回对象: type={type(msg_sent)}, repr={msg_sent!r}")
logger.debug(f"msg_sent属性: {dir(msg_sent)}")
try:
# 尝试获取消息ID
msg_id = None
# 检查是否为Event类型
if hasattr(msg_sent, "get_event_name"):
from nonebot_plugin_alconna.uniseg import get_message_id

try:
msg_id = get_message_id(msg_sent) # type: ignore
logger.debug(f"通过get_message_id获取到消息ID: {msg_id}")
except TypeError as e:
# 如果不是Event类型,跳过
logger.debug(f"get_message_id类型错误: {e}")
# 尝试直接从对象获取id或message_id属性
if hasattr(msg_sent, "id"):
msg_id = str(msg_sent.id) # type: ignore
logger.debug(f"通过id属性获取到消息ID: {msg_id}")
elif hasattr(msg_sent, "message_id"):
msg_id = str(msg_sent.message_id) # type: ignore
logger.debug(f"通过message_id属性获取到消息ID: {msg_id}")
elif hasattr(msg_sent, "message_ids"):
# 处理可能返回多个消息ID的情况
msg_ids = getattr(msg_sent, "message_ids")
if msg_ids:
msg_id = str(msg_ids[0]) # type: ignore
logger.debug(f"通过message_ids属性获取到消息ID: {msg_id}")
elif hasattr(msg_sent, "msg_ids"):
# 处理Receipt对象的msg_ids属性
receipt_msg_ids = getattr(msg_sent, "msg_ids")
logger.debug(f"Receipt.msg_ids: {receipt_msg_ids}")
if receipt_msg_ids:
# 处理msg_ids是列表的情况
if isinstance(receipt_msg_ids, list):
for msg_id_info in receipt_msg_ids:
if isinstance(msg_id_info, dict) and "message_id" in msg_id_info:
msg_id = str(msg_id_info["message_id"])
logger.debug(f"通过Receipt.msg_ids[0]['message_id']获取到消息ID: {msg_id}")
break
# 处理msg_ids是单个消息ID的情况
else:
msg_id = str(receipt_msg_ids) # type: ignore
logger.debug(f"通过Receipt.msg_ids获取到消息ID: {msg_id}")

if msg_id:
_MSG_ID_RESULT_MAP[msg_id] = result
logger.debug(f"保存消息ID与解析结果的关联: msg_id={msg_id}, url={cache_key}")
logger.debug(f"当前_MSG_ID_RESULT_MAP大小: {len(_MSG_ID_RESULT_MAP)}")
else:
logger.debug("未获取到消息ID")
except (NotImplementedError, TypeError, AttributeError) as e:
# 某些适配器可能不支持获取消息ID,忽略此错误
logger.debug(f"获取消息ID失败: {e}")
except Exception as e:
# 渲染失败时,尝试直接发送解析结果
logger.error(f"渲染失败: {e}")
from ..helper import UniMessage

await UniMessage(f"解析成功,但渲染失败: {e!s}").send()

# 4. 缓存解析结果
_RESULT_CACHE[cache_key] = result
Expand Down Expand Up @@ -143,3 +216,156 @@ async def _():
await UniMessage(UniHelper.img_seg(raw=qrcode)).send()
async for msg in parser.check_qr_state():
await UniMessage(msg).send()


# 监听特定表情,触发延迟发送的媒体内容
class EmojiTriggerRule:
"""表情触发规则类"""

async def __call__(self, message: UniMsg, state: T_State) -> bool:
"""检查消息是否是触发表情"""
text = message.extract_plain_text().strip()
return text == pconfig.delay_send_emoji


def emoji_trigger_rule() -> Rule:
"""创建表情触发规则"""
return Rule(EmojiTriggerRule())


# 创建表情触发的消息处理器
delay_send_matcher = Matcher.new(
"message",
emoji_trigger_rule(),
priority=5,
block=True,
source=get_matcher_source(1),
)


@delay_send_matcher.handle()
async def delay_media_trigger_handler():
from ..helper import UniHelper, UniMessage

# 获取最新的解析结果
if not _RESULT_CACHE:
return

# 获取最近的解析结果
latest_url = next(reversed(_RESULT_CACHE.keys()))
result = _RESULT_CACHE[latest_url]

# 发送延迟的媒体内容
for media_type, path in result.media_contents:
if media_type == VideoContent:
await UniMessage(UniHelper.video_seg(path)).send()
elif media_type == AudioContent:
await UniMessage(UniHelper.record_seg(path)).send()

# 清空当前结果的媒体内容
result.media_contents.clear()


# 监听group_msg_emoji_like事件,处理点赞触发
from nonebot import on_notice
from nonebot_plugin_alconna.uniseg import message_reaction

on_notice_ = on_notice(priority=1, block=False)


@on_notice_.handle()
async def handle_group_msg_emoji_like(event):
from ..helper import UniHelper, UniMessage

# 检查是否是group_msg_emoji_like事件
is_group_emoji_like = False
emoji_id = ""
liked_message_id = ""

# 处理不同形式的事件对象(字典或对象)
if isinstance(event, dict):
# 字典形式的事件
if event.get("notice_type") == "group_msg_emoji_like":
is_group_emoji_like = True
emoji_id = event["likes"][0]["emoji_id"]
liked_message_id = event["message_id"]
else:
# 对象形式的事件
if hasattr(event, "notice_type") and event.notice_type == "group_msg_emoji_like":
is_group_emoji_like = True
if hasattr(event, "likes") and event.likes:
if isinstance(event.likes[0], dict):
emoji_id = event.likes[0].get("emoji_id", "")
else:
emoji_id = event.likes[0].emoji_id
if hasattr(event, "message_id"):
liked_message_id = event.message_id

# 检查是否是group_msg_emoji_like事件且表情ID有效
if not is_group_emoji_like or not emoji_id:
return

# 检查表情ID是否在配置列表中
if emoji_id not in pconfig.delay_send_emoji_ids:
return

# 发送"听到需求"的表情(使用用户指定的表情ID 282)
try:
# 只有当liked_message_id有效时,才发送表情反馈
if liked_message_id:
await message_reaction("282", message_id=str(liked_message_id))
except Exception as e:
logger.warning(f"Failed to send resolving reaction: {e}")

try:
logger.debug(f"收到表情点赞事件: emoji_id={emoji_id}, message_id={liked_message_id}, event={event}")
logger.debug(f"当前_MSG_ID_RESULT_MAP: {list(_MSG_ID_RESULT_MAP.keys())}")

# 根据消息ID获取对应的解析结果
result = _MSG_ID_RESULT_MAP.get(str(liked_message_id))
if not result:
# 发送"失败"的表情(使用用户指定的表情ID 10060)
logger.debug(f"未找到消息ID {liked_message_id} 对应的解析结果")
try:
if liked_message_id:
await message_reaction("10060", message_id=str(liked_message_id))
except Exception as e:
logger.warning(f"Failed to send fail reaction: {e}")
return

# 发送延迟的媒体内容
sent = False
for media_type, path in result.media_contents:
if media_type == VideoContent:
await UniMessage(UniHelper.video_seg(path)).send()
sent = True
elif media_type == AudioContent:
await UniMessage(UniHelper.record_seg(path)).send()
sent = True

# 清空当前结果的媒体内容
result.media_contents.clear()

# 发送对应的表情
if sent:
# 发送"完成"的表情(使用用户指定的表情ID 124)
try:
if liked_message_id:
await message_reaction("124", message_id=str(liked_message_id))
except Exception as e:
logger.warning(f"Failed to send done reaction: {e}")
else:
# 没有可发送的媒体内容,发送"失败"的表情(使用用户指定的表情ID 10060)
try:
if liked_message_id:
await message_reaction("10060", message_id=str(liked_message_id))
except Exception as e:
logger.warning(f"Failed to send fail reaction: {e}")
except Exception as e:
# 发送"失败"的表情(使用用户指定的表情ID 10060)
try:
if liked_message_id:
await message_reaction("10060", message_id=str(liked_message_id))
except Exception as reaction_e:
logger.warning(f"Failed to send fail reaction: {reaction_e}")
logger.error(f"Failed to send media content: {e}")
Loading
Loading