import asyncio from collections import deque from nonebot.adapters.onebot.v11.exception import ActionFailed from nonebot.log import logger from nonebot_plugin_saa import AggregatedMessageFactory, MessageFactory, PlatformTarget from nonebot_plugin_saa.auto_select_bot import refresh_bots from .plugin_config import plugin_config Sendable = MessageFactory | AggregatedMessageFactory QUEUE: deque[tuple[PlatformTarget, Sendable, int]] = deque() MESSGE_SEND_INTERVAL = 1.5 _MESSAGE_DISPATCH_TASKS: set[asyncio.Task] = set() async def _do_send(send_target: PlatformTarget, msg: Sendable): try: await msg.send_to(send_target) except ActionFailed: # TODO: catch exception of other adapters await refresh_bots() logger.warning("send msg failed, refresh bots") async def do_send_msgs(): if not QUEUE: return while True: # 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 not expected. # So, read from queue first then pop from it send_target, msg_factory, retry_time = QUEUE[0] try: await _do_send(send_target, msg_factory) except Exception as e: await asyncio.sleep(MESSGE_SEND_INTERVAL) QUEUE.popleft() if retry_time > 0: QUEUE.appendleft((send_target, msg_factory, retry_time - 1)) else: msg_str = str(msg_factory) if len(msg_str) > 50: msg_str = msg_str[:50] + "..." logger.warning(f"send msg err {e} {msg_str}") else: # sleeping after popping may also cause re-execution error like above mentioned await asyncio.sleep(MESSGE_SEND_INTERVAL) QUEUE.popleft() finally: if not QUEUE: return async def _send_msgs_dispatch(send_target: PlatformTarget, msg: Sendable): if plugin_config.bison_use_queue: QUEUE.append((send_target, msg, plugin_config.bison_resend_times)) # len(QUEUE) before append was 0 if len(QUEUE) == 1: task = asyncio.create_task(do_send_msgs()) _MESSAGE_DISPATCH_TASKS.add(task) task.add_done_callback(_MESSAGE_DISPATCH_TASKS.discard) else: await _do_send(send_target, msg) async def send_msgs(send_target: PlatformTarget, msgs: list[MessageFactory]): if not plugin_config.bison_use_pic_merge: for msg in msgs: await _send_msgs_dispatch(send_target, msg) return msgs = msgs.copy() if plugin_config.bison_use_pic_merge == 1: await _send_msgs_dispatch(send_target, msgs.pop(0)) if msgs: if len(msgs) == 1: # 只有一条消息序列就不合并转发 await _send_msgs_dispatch(send_target, msgs.pop(0)) else: forward_message = AggregatedMessageFactory(list(msgs)) await _send_msgs_dispatch(send_target, forward_message)