2021-08-29 00:24:28 +08:00
import abc
from functools import partial
2024-12-01 12:31:11 +08:00
from typing import TYPE_CHECKING , Any , ClassVar , Optional , Protocol , Union
2021-08-29 00:24:28 +08:00
2024-10-26 15:36:01 +08:00
import anyio
from exceptiongroup import BaseExceptionGroup , catch
2021-08-29 00:24:28 +08:00
from nonebot . config import Config
2021-11-28 02:29:23 +08:00
from nonebot . exception import MockApiException
2024-12-01 12:31:11 +08:00
from nonebot . log import logger
2021-09-27 00:19:30 +08:00
from nonebot . typing import T_CalledAPIHook , T_CallingAPIHook
2024-12-01 12:31:11 +08:00
from nonebot . utils import flatten_exception_group
2021-08-29 00:24:28 +08:00
if TYPE_CHECKING :
2022-02-06 14:52:50 +08:00
from . adapter import Adapter
2024-12-01 12:31:11 +08:00
from . event import Event
2022-02-06 14:52:50 +08:00
from . message import Message , MessageSegment
2021-08-29 00:24:28 +08:00
2022-09-09 11:52:57 +08:00
class _ApiCall ( Protocol ) :
2024-02-06 12:48:23 +08:00
async def __call__ ( self , * * kwargs : Any ) - > Any : . . .
2021-08-29 00:24:28 +08:00
class Bot ( abc . ABC ) :
2022-01-20 14:49:46 +08:00
""" Bot 基类。
用于处理上报消息 , 并提供 API 调用接口 。
参数 :
adapter : 协议适配器实例
self_id : 机器人 ID
2021-08-29 00:24:28 +08:00
"""
2024-04-16 00:33:48 +08:00
_calling_api_hook : ClassVar [ set [ T_CallingAPIHook ] ] = set ( )
2022-01-20 14:49:46 +08:00
""" call_api 时执行的函数 """
2024-04-16 00:33:48 +08:00
_called_api_hook : ClassVar [ set [ T_CalledAPIHook ] ] = set ( )
2022-01-20 14:49:46 +08:00
""" call_api 后执行的函数 """
2021-08-29 00:24:28 +08:00
2021-12-06 22:19:05 +08:00
def __init__ ( self , adapter : " Adapter " , self_id : str ) :
2021-12-07 02:16:18 +08:00
self . adapter : " Adapter " = adapter
2022-01-20 14:49:46 +08:00
""" 协议适配器实例 """
2021-08-29 00:24:28 +08:00
self . self_id : str = self_id
""" 机器人 ID """
2022-09-09 11:52:57 +08:00
def __repr__ ( self ) - > str :
return f " Bot(type= { self . type !r} , self_id= { self . self_id !r} ) "
def __getattr__ ( self , name : str ) - > " _ApiCall " :
2022-11-19 08:14:03 +00:00
if name . startswith ( " __ " ) and name . endswith ( " __ " ) :
raise AttributeError (
f " ' { self . __class__ . __name__ } ' object has no attribute ' { name } ' "
)
2021-08-29 00:24:28 +08:00
return partial ( self . call_api , name )
@property
def type ( self ) - > str :
2022-01-20 14:49:46 +08:00
""" 协议适配器名称 """
2021-12-06 22:19:05 +08:00
return self . adapter . get_name ( )
2021-08-29 00:24:28 +08:00
2021-12-06 22:19:05 +08:00
@property
def config ( self ) - > Config :
2022-01-20 14:49:46 +08:00
""" 全局 NoneBot 配置 """
2021-12-06 22:19:05 +08:00
return self . adapter . config
2021-08-29 00:24:28 +08:00
async def call_api ( self , api : str , * * data : Any ) - > Any :
2022-01-20 14:49:46 +08:00
""" 调用机器人 API 接口,可以通过该函数或直接通过 bot 属性进行调用
2021-08-29 00:24:28 +08:00
2022-01-12 18:31:12 +08:00
参数 :
2022-01-12 19:10:29 +08:00
api : API 名称
2022-01-20 14:49:46 +08:00
data : API 数据
2021-08-29 00:24:28 +08:00
2022-01-12 18:53:30 +08:00
用法 :
` ` ` python
2021-08-29 00:24:28 +08:00
await bot . call_api ( " send_msg " , message = " hello world " )
await bot . send_msg ( message = " hello world " )
2022-01-12 18:53:30 +08:00
` ` `
2021-08-29 00:24:28 +08:00
"""
2021-11-28 02:29:23 +08:00
result : Any = None
skip_calling_api : bool = False
exception : Optional [ Exception ] = None
2024-10-26 15:36:01 +08:00
if self . _calling_api_hook :
logger . debug ( " Running CallingAPI hooks... " )
def _handle_mock_api_exception (
exc_group : BaseExceptionGroup [ MockApiException ] ,
) - > None :
nonlocal skip_calling_api , result
excs = [
exc
for exc in flatten_exception_group ( exc_group )
if isinstance ( exc , MockApiException )
]
if not excs :
return
elif len ( excs ) > 1 :
logger . warning (
" Multiple hooks want to mock API result. Use the first one. "
)
2021-11-28 02:29:23 +08:00
skip_calling_api = True
2024-10-26 15:36:01 +08:00
result = excs [ 0 ] . result
2021-11-28 02:29:23 +08:00
logger . debug (
2024-10-26 15:36:01 +08:00
f " Calling API { api } is cancelled. Return { result !r} instead. "
2021-11-22 23:21:26 +08:00
)
2021-08-29 00:24:28 +08:00
2024-10-26 15:36:01 +08:00
def _handle_exception ( exc_group : BaseExceptionGroup [ Exception ] ) - > None :
for exc in flatten_exception_group ( exc_group ) :
logger . opt ( colors = True , exception = exc ) . error (
" <r><bg #f8bbd0>Error when running CallingAPI hook. "
" Running cancelled!</bg #f8bbd0></r> "
)
with catch (
{
MockApiException : _handle_mock_api_exception ,
Exception : _handle_exception ,
}
) :
async with anyio . create_task_group ( ) as tg :
for hook in self . _calling_api_hook :
tg . start_soon ( hook , self , api , data )
2021-11-28 02:29:23 +08:00
if not skip_calling_api :
try :
2021-12-21 18:22:14 +08:00
result = await self . adapter . _call_api ( self , api , * * data )
2021-11-28 02:29:23 +08:00
except Exception as e :
exception = e
2021-08-29 00:24:28 +08:00
2024-10-26 15:36:01 +08:00
if self . _called_api_hook :
logger . debug ( " Running CalledAPI hooks... " )
def _handle_mock_api_exception (
exc_group : BaseExceptionGroup [ MockApiException ] ,
) - > None :
nonlocal result , exception
excs = [
exc
for exc in flatten_exception_group ( exc_group )
if isinstance ( exc , MockApiException )
]
if not excs :
return
elif len ( excs ) > 1 :
logger . warning (
" Multiple hooks want to mock API result. Use the first one. "
)
result = excs [ 0 ] . result
2023-09-25 11:02:50 +08:00
exception = None
2021-11-28 02:29:23 +08:00
logger . debug (
f " Calling API { api } result is mocked. Return { result } instead. "
)
2024-10-26 15:36:01 +08:00
def _handle_exception ( exc_group : BaseExceptionGroup [ Exception ] ) - > None :
for exc in flatten_exception_group ( exc_group ) :
logger . opt ( colors = True , exception = exc ) . error (
" <r><bg #f8bbd0>Error when running CalledAPI hook. "
" Running cancelled!</bg #f8bbd0></r> "
)
with catch (
{
MockApiException : _handle_mock_api_exception ,
Exception : _handle_exception ,
}
) :
async with anyio . create_task_group ( ) as tg :
for hook in self . _called_api_hook :
tg . start_soon ( hook , self , exception , api , data , result )
2021-08-29 00:24:28 +08:00
if exception :
raise exception
return result
@abc.abstractmethod
2021-11-22 23:21:26 +08:00
async def send (
2022-01-20 14:49:46 +08:00
self ,
event : " Event " ,
message : Union [ str , " Message " , " MessageSegment " ] ,
* * kwargs : Any ,
2021-11-22 23:21:26 +08:00
) - > Any :
2022-01-20 14:49:46 +08:00
""" 调用机器人基础发送消息接口
2021-08-29 00:24:28 +08:00
2022-01-12 18:31:12 +08:00
参数 :
2022-01-12 19:10:29 +08:00
event : 上报事件
message : 要发送的消息
2022-01-20 14:49:46 +08:00
kwargs : 任意额外参数
2021-08-29 00:24:28 +08:00
"""
raise NotImplementedError
@classmethod
def on_calling_api ( cls , func : T_CallingAPIHook ) - > T_CallingAPIHook :
2022-01-20 14:49:46 +08:00
""" 调用 api 预处理。
2021-08-29 00:24:28 +08:00
2022-01-23 11:48:35 +08:00
钩子函数参数 :
2022-01-20 14:49:46 +08:00
- bot : 当前 bot 对象
- api : 调用的 api 名称
- data : api 调用的参数字典
2021-08-29 00:24:28 +08:00
"""
cls . _calling_api_hook . add ( func )
return func
@classmethod
def on_called_api ( cls , func : T_CalledAPIHook ) - > T_CalledAPIHook :
2022-01-20 14:49:46 +08:00
""" 调用 api 后处理。
2021-08-29 00:24:28 +08:00
2022-01-23 11:48:35 +08:00
钩子函数参数 :
2022-01-20 14:49:46 +08:00
- bot : 当前 bot 对象
- exception : 调用 api 时发生的错误
- api : 调用的 api 名称
- data : api 调用的参数字典
- result : api 调用的返回
2021-08-29 00:24:28 +08:00
"""
cls . _called_api_hook . add ( func )
return func