♻️ use saa

This commit is contained in:
felinae98
2023-03-24 21:17:52 +08:00
parent 4118329bb0
commit da8e988ee9
15 changed files with 223 additions and 308 deletions
+10 -4
View File
@@ -15,7 +15,12 @@ from nonebot.params import Depends, EventPlainText, EventToMe
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from nonebot.typing import T_State
from nonebot_plugin_saa import PlatformTarget, TargetQQGroup, extract_target
from nonebot_plugin_saa import (
MessageFactory,
PlatformTarget,
TargetQQGroup,
extract_target,
)
from .apis import check_sub_target
from .config import config
@@ -220,7 +225,7 @@ def do_query_sub(query_sub: Type[Matcher]):
query_sub.handle()(ensure_user_info(query_sub))
@query_sub.handle()
async def _(state: T_State):
async def _(bot: Bot, state: T_State):
user_info = state["target_user_info"]
assert isinstance(user_info, PlatformTarget)
sub_list = await config.list_subscribe(user_info)
@@ -242,7 +247,8 @@ def do_query_sub(query_sub: Type[Matcher]):
if platform.enable_tag:
res += " {}".format(", ".join(sub.tags))
res += "\n"
await query_sub.finish(Message(await parse_text(res)))
await MessageFactory(await parse_text(res)).send()
await query_sub.finish()
def do_del_sub(del_sub: Type[Matcher]):
@@ -285,7 +291,7 @@ def do_del_sub(del_sub: Type[Matcher]):
res += " {}".format(", ".join(sub.tags))
res += "\n"
res += "请输入要删除的订阅的序号\n输入'取消'中止"
await bot.send(event=event, message=Message(await parse_text(res)))
await MessageFactory(await parse_text(res)).send()
@del_sub.receive()
async def do_del(event: Event, state: T_State):
+11 -9
View File
@@ -3,7 +3,7 @@ from dataclasses import dataclass, field
from functools import reduce
from typing import Optional
from nonebot.adapters.onebot.v11.message import Message, MessageSegment
from nonebot_plugin_saa import MessageFactory, MessageSegmentFactory
from ..plugin_config import plugin_config
@@ -11,13 +11,13 @@ from ..plugin_config import plugin_config
@dataclass
class BasePost:
@abstractmethod
async def generate_text_messages(self) -> list[MessageSegment]:
"Generate Message list from this instance"
async def generate_text_messages(self) -> list[MessageSegmentFactory]:
"Generate MessageFactory list from this instance"
...
@abstractmethod
async def generate_pic_messages(self) -> list[MessageSegment]:
"Generate Message list from this instance with `use_pic`"
async def generate_pic_messages(self) -> list[MessageSegmentFactory]:
"Generate MessageFactory list from this instance with `use_pic`"
...
@@ -27,7 +27,7 @@ class OptionalMixin:
override_use_pic: Optional[bool] = None
compress: bool = False
extra_msg: list[Message] = field(default_factory=list)
extra_msg: list[MessageFactory] = field(default_factory=list)
def _use_pic(self):
if not self.override_use_pic is None:
@@ -37,17 +37,19 @@ class OptionalMixin:
@dataclass
class AbstractPost(OptionalMixin, BasePost):
async def generate_messages(self) -> list[Message]:
async def generate_messages(self) -> list[MessageFactory]:
if self._use_pic():
msg_segments = await self.generate_pic_messages()
else:
msg_segments = await self.generate_text_messages()
if msg_segments:
if self.compress:
msgs = [reduce(lambda x, y: x.append(y), msg_segments, Message())]
msgs = [
reduce(lambda x, y: x.append(y), msg_segments, MessageFactory([]))
]
else:
msgs = list(
map(lambda msg_segment: Message([msg_segment]), msg_segments)
map(lambda msg_segment: MessageFactory([msg_segment]), msg_segments)
)
else:
msgs = []
+22 -24
View File
@@ -1,10 +1,10 @@
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
from nonebot.adapters.onebot.v11.message import Message, MessageSegment
from nonebot.adapters.onebot.v11 import MessageSegment
from nonebot.log import logger
from nonebot.plugin import require
from nonebot_plugin_saa import Image, MessageFactory, MessageSegmentFactory, Text
from .abstract_post import AbstractPost, BasePost
@@ -12,37 +12,35 @@ from .abstract_post import AbstractPost, BasePost
@dataclass
class _CustomPost(BasePost):
message_segments: list[MessageSegment] = field(default_factory=list)
ms_factories: list[MessageSegmentFactory] = field(default_factory=list)
css_path: Optional[str] = None # 模板文件所用css路径
async def generate_text_messages(self) -> list[MessageSegment]:
return self.message_segments
async def generate_text_messages(self) -> list[MessageSegmentFactory]:
return self.ms_factories
async def generate_pic_messages(self) -> list[MessageSegment]:
async def generate_pic_messages(self) -> list[MessageSegmentFactory]:
require("nonebot_plugin_htmlrender")
from nonebot_plugin_htmlrender import md_to_pic
pic_bytes = await md_to_pic(md=self._generate_md(), css_path=self.css_path)
return [MessageSegment.image(pic_bytes)]
return [Image(pic_bytes)]
def _generate_md(self) -> str:
md = ""
for message_segment in self.message_segments:
if message_segment.type == "text":
md += "{}<br>".format(message_segment.data.get("text", ""))
elif message_segment.type == "image":
# 先尝试获取file的值,没有再尝试获取url的值,都没有则为空
pic_res = message_segment.data.get("file") or message_segment.data.get(
"url", ""
)
if not pic_res:
logger.warning("无法获取到图片资源:MessageSegment.image中file/url字段均为空")
else:
md += "![Image]({})\n".format(pic_res)
else:
logger.warning("custom_post不支持处理类型:{}".format(message_segment.type))
continue
for message_segment in self.ms_factories:
match message_segment:
case Text(data={"text": text}):
md += "{}<br>".format(text)
case Image(data={"image": image}):
# use onebot v11 to convert image into url
ob11_image = MessageSegment.image(image)
md += "![Image]({})\n".format(ob11_image.data["file"])
case _:
logger.warning(
"custom_post不支持处理类型:{}".format(type(message_segment))
)
continue
return md
@@ -52,14 +50,14 @@ class CustomPost(_CustomPost, AbstractPost):
"""基于 markdown 语法的,自由度较高的推送内容格式
简介:
支持处理text/image两种MessageSegment,
支持处理text/image两种MessageSegmentFactory,
通过将text/image转换成对应的markdown语法以生成markdown文本。
理论上text类型中可以直接使用markdown语法,例如`##第一章`。
但会导致不启用`override_use_pic`时, 发送不会被渲染的纯文本消息。
图片渲染最终由htmlrender执行。
注意:
每一个MessageSegment元素都会被解释为单独的一行
每一个MessageSegmentFactory元素都会被解释为单独的一行
可选参数:
`override_use_pic`:是否覆盖`bison_use_pic`全局配置
+12 -11
View File
@@ -3,8 +3,9 @@ from functools import reduce
from io import BytesIO
from typing import Optional, Union
from nonebot.adapters.onebot.v11.message import Message, MessageSegment
import nonebot_plugin_saa as saa
from nonebot.log import logger
from nonebot_plugin_saa.utils import MessageFactory, MessageSegmentFactory
from PIL import Image
from ..utils import http_client, parse_text
@@ -20,8 +21,8 @@ class _Post(BasePost):
target_name: Optional[str] = None
pics: list[Union[str, bytes]] = field(default_factory=list)
_message: Optional[list[MessageSegment]] = None
_pic_message: Optional[list[MessageSegment]] = None
_message: Optional[list[MessageSegmentFactory]] = None
_pic_message: Optional[list[MessageSegmentFactory]] = None
async def _pic_url_to_image(self, data: Union[str, bytes]) -> Image.Image:
pic_buffer = BytesIO()
@@ -99,11 +100,11 @@ class _Post(BasePost):
self.pics = self.pics[matrix[0] * matrix[1] :]
self.pics.insert(0, target_io.getvalue())
async def generate_text_messages(self) -> list[MessageSegment]:
async def generate_text_messages(self) -> list[MessageSegmentFactory]:
if self._message is None:
await self._pic_merge()
msg_segments: list[MessageSegment] = []
msg_segments: list[MessageSegmentFactory] = []
text = ""
if self.text:
text += "{}".format(
@@ -116,17 +117,17 @@ class _Post(BasePost):
text += " {}".format(self.target_name)
if self.url:
text += " \n详情: {}".format(self.url)
msg_segments.append(MessageSegment.text(text))
msg_segments.append(saa.Text(text))
for pic in self.pics:
msg_segments.append(MessageSegment.image(pic))
msg_segments.append(saa.Image(pic))
self._message = msg_segments
return self._message
async def generate_pic_messages(self) -> list[MessageSegment]:
async def generate_pic_messages(self) -> list[MessageSegmentFactory]:
if self._pic_message is None:
await self._pic_merge()
msg_segments: list[MessageSegment] = []
msg_segments: list[MessageSegmentFactory] = []
text = ""
if self.text:
text += "{}".format(self.text)
@@ -136,9 +137,9 @@ class _Post(BasePost):
text += " {}".format(self.target_name)
msg_segments.append(await parse_text(text))
if not self.target_type == "rss" and self.url:
msg_segments.append(MessageSegment.text(self.url))
msg_segments.append(saa.Text(self.url))
for pic in self.pics:
msg_segments.append(MessageSegment.image(pic))
msg_segments.append(saa.Image(pic))
self._pic_message = msg_segments
return self._pic_message
+1 -2
View File
@@ -115,8 +115,7 @@ class Scheduler:
else:
await send_msgs(
bot,
user.user,
user.user_type,
user,
await send_post.generate_messages(),
)
+25 -62
View File
@@ -1,45 +1,28 @@
import asyncio
from asyncio.tasks import sleep
from collections import deque
from typing import Deque, Literal, Union
from typing import Deque
from nonebot.adapters.onebot.v11.bot import Bot
from nonebot.adapters import Bot
from nonebot.adapters.onebot.v11.exception import ActionFailed
from nonebot.adapters.onebot.v11.message import Message, MessageSegment
from nonebot.log import logger
from nonebot_plugin_saa import AggregatedMessageFactory, MessageFactory, PlatformTarget
from .plugin_config import plugin_config
from .utils.get_bot import refresh_bots
QUEUE: Deque[
tuple[
Bot,
int,
Literal["private", "group", "group-forward"],
Union[str, Message],
int,
]
] = deque()
Sendable = MessageFactory | AggregatedMessageFactory
QUEUE: Deque[tuple[Bot, PlatformTarget, Sendable, int]] = deque()
MESSGE_SEND_INTERVAL = 1.5
async def _do_send(
bot: "Bot",
user: int,
user_type: Literal["group", "private", "group-forward"],
msg: Union[str, Message],
):
async def _do_send(bot: "Bot", send_target: PlatformTarget, msg: Sendable):
try:
if user_type == "group":
await bot.send_group_msg(group_id=user, message=msg)
elif user_type == "private":
await bot.send_private_msg(user_id=user, message=msg)
elif user_type == "group-forward":
await bot.send_group_forward_msg(group_id=user, messages=msg)
except ActionFailed:
await msg.send_to(send_target, bot)
except ActionFailed: # TODO: catch exception of other adapters
await refresh_bots()
logger.warning(f"send msg failed, refresh bots")
logger.warning("send msg failed, refresh bots")
async def do_send_msgs():
@@ -49,18 +32,18 @@ async def do_send_msgs():
# why read from queue then pop item from queue?
# if there is only 1 item in queue, pop it and await send
# the length of queue will be 0.
# At that time, adding items to queue will trigger a new execution of this func, which is wrong.
# At that time, adding items to queue will trigger a new execution of this func, which is not expected.
# So, read from queue first then pop from it
bot, user, user_type, msg, retry_time = QUEUE[0]
bot, send_target, msg_factory, retry_time = QUEUE[0]
try:
await _do_send(bot, user, user_type, msg)
await _do_send(bot, send_target, msg_factory)
except Exception as e:
await asyncio.sleep(MESSGE_SEND_INTERVAL)
QUEUE.popleft()
if retry_time > 0:
QUEUE.appendleft((bot, user, user_type, msg, retry_time - 1))
QUEUE.appendleft((bot, send_target, msg_factory, retry_time - 1))
else:
msg_str = str(msg)
msg_str = str(msg_factory)
if len(msg_str) > 50:
msg_str = msg_str[:50] + "..."
logger.warning(f"send msg err {e} {msg_str}")
@@ -73,47 +56,27 @@ async def do_send_msgs():
return
async def _send_msgs_dispatch(
bot: Bot,
user,
user_type: Literal["private", "group", "group-forward"],
msg: Union[str, Message],
):
async def _send_msgs_dispatch(bot: Bot, send_target: PlatformTarget, msg: Sendable):
if plugin_config.bison_use_queue:
QUEUE.append((bot, user, user_type, msg, plugin_config.bison_resend_times))
QUEUE.append((bot, send_target, msg, plugin_config.bison_resend_times))
# len(QUEUE) before append was 0
if len(QUEUE) == 1:
asyncio.create_task(do_send_msgs())
else:
await _do_send(bot, user, user_type, msg)
await _do_send(bot, send_target, msg)
async def send_msgs(
bot: Bot, user, user_type: Literal["private", "group"], msgs: list[Message]
):
if not plugin_config.bison_use_pic_merge or user_type == "private":
async def send_msgs(bot: Bot, send_target: PlatformTarget, msgs: list[MessageFactory]):
if not plugin_config.bison_use_pic_merge:
for msg in msgs:
await _send_msgs_dispatch(bot, user, user_type, msg)
await _send_msgs_dispatch(bot, send_target, msg)
return
msgs = msgs.copy()
if plugin_config.bison_use_pic_merge == 1:
await _send_msgs_dispatch(bot, user, "group", msgs.pop(0))
await _send_msgs_dispatch(bot, send_target, msgs.pop(0))
if msgs:
if len(msgs) == 1: # 只有一条消息序列就不合并转发
await _send_msgs_dispatch(bot, user, "group", msgs.pop(0))
await _send_msgs_dispatch(bot, send_target, msgs.pop(0))
else:
group_bot_info = await bot.get_group_member_info(
group_id=user, user_id=int(bot.self_id), no_cache=True
) # 调用api获取群内bot的相关参数
forward_msg = Message(
[
MessageSegment.node_custom(
group_bot_info["user_id"],
nickname=group_bot_info["card"] or group_bot_info["nickname"],
content=msg,
)
for msg in msgs
]
)
await _send_msgs_dispatch(bot, user, "group-forward", forward_msg)
forward_message = AggregatedMessageFactory(list(msgs))
await _send_msgs_dispatch(bot, send_target, forward_message)
+4 -4
View File
@@ -4,9 +4,9 @@ from typing import Union
import nonebot
from bs4 import BeautifulSoup as bs
from nonebot.adapters.onebot.v11.message import MessageSegment
from nonebot.log import default_format, logger
from nonebot.plugin import require
from nonebot_plugin_saa import Image, MessageSegmentFactory, Text
from ..plugin_config import plugin_config
from .context import ProcessContext
@@ -33,15 +33,15 @@ class Singleton(type):
return cls._instances[cls]
async def parse_text(text: str) -> MessageSegment:
async def parse_text(text: str) -> MessageSegmentFactory:
"return raw text if don't use pic, otherwise return rendered opcode"
if plugin_config.bison_use_pic:
require("nonebot_plugin_htmlrender")
from nonebot_plugin_htmlrender import text_to_pic as _text_to_pic
return MessageSegment.image(await _text_to_pic(text))
return Image(await _text_to_pic(text))
else:
return MessageSegment.text(text)
return Text(text)
if not plugin_config.bison_skip_browser_check: