mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-27 18:45:05 +08:00
🎨 format code using black and isort
This commit is contained in:
parent
602185a34e
commit
a98d98cd12
@ -1,2 +0,0 @@
|
||||
[style]
|
||||
based_on_style = google
|
@ -40,8 +40,7 @@ from nonebot.log import logger, default_filter
|
||||
from nonebot.drivers import Driver, ReverseDriver
|
||||
|
||||
try:
|
||||
_dist: pkg_resources.Distribution = pkg_resources.get_distribution(
|
||||
"nonebot2")
|
||||
_dist: pkg_resources.Distribution = pkg_resources.get_distribution("nonebot2")
|
||||
__version__ = _dist.version
|
||||
VERSION = _dist.parsed_version
|
||||
except pkg_resources.DistributionNotFound:
|
||||
@ -100,8 +99,8 @@ def get_app() -> Any:
|
||||
"""
|
||||
driver = get_driver()
|
||||
assert isinstance(
|
||||
driver,
|
||||
ReverseDriver), "app object is only available for reverse driver"
|
||||
driver, ReverseDriver
|
||||
), "app object is only available for reverse driver"
|
||||
return driver.server_app
|
||||
|
||||
|
||||
@ -128,8 +127,8 @@ def get_asgi() -> Any:
|
||||
"""
|
||||
driver = get_driver()
|
||||
assert isinstance(
|
||||
driver,
|
||||
ReverseDriver), "asgi object is only available for reverse driver"
|
||||
driver, ReverseDriver
|
||||
), "asgi object is only available for reverse driver"
|
||||
return driver.asgi
|
||||
|
||||
|
||||
@ -226,17 +225,23 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
|
||||
if not _driver:
|
||||
logger.success("NoneBot is initializing...")
|
||||
env = Env()
|
||||
config = Config(**kwargs,
|
||||
config = Config(
|
||||
**kwargs,
|
||||
_common_config=env.dict(),
|
||||
_env_file=_env_file or f".env.{env.environment}")
|
||||
_env_file=_env_file or f".env.{env.environment}",
|
||||
)
|
||||
|
||||
default_filter.level = (
|
||||
"DEBUG" if config.debug else
|
||||
"INFO") if config.log_level is None else config.log_level
|
||||
("DEBUG" if config.debug else "INFO")
|
||||
if config.log_level is None
|
||||
else config.log_level
|
||||
)
|
||||
logger.opt(colors=True).info(
|
||||
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>")
|
||||
f"Current <y><b>Env: {escape_tag(env.environment)}</b></y>"
|
||||
)
|
||||
logger.opt(colors=True).debug(
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}")
|
||||
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}"
|
||||
)
|
||||
|
||||
modulename, _, cls = config.driver.partition(":")
|
||||
module = importlib.import_module(modulename)
|
||||
@ -247,10 +252,7 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
|
||||
_driver = DriverClass(env, config)
|
||||
|
||||
|
||||
def run(host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
*args,
|
||||
**kwargs):
|
||||
def run(host: Optional[str] = None, port: Optional[int] = None, *args, **kwargs):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
|
@ -9,13 +9,13 @@ from typing import Iterable
|
||||
|
||||
try:
|
||||
import pkg_resources
|
||||
|
||||
pkg_resources.declare_namespace(__name__)
|
||||
del pkg_resources
|
||||
except ImportError:
|
||||
import pkgutil
|
||||
__path__: Iterable[str] = pkgutil.extend_path(
|
||||
__path__, # type: ignore
|
||||
__name__)
|
||||
|
||||
__path__: Iterable[str] = pkgutil.extend_path(__path__, __name__) # type: ignore
|
||||
del pkgutil
|
||||
except Exception:
|
||||
pass
|
||||
|
@ -15,7 +15,6 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class _ApiCall(Protocol):
|
||||
|
||||
async def __call__(self, **kwargs: Any) -> Any:
|
||||
...
|
||||
|
||||
@ -146,7 +145,8 @@ class Bot(abc.ABC):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running CallingAPI hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>")
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
exception = None
|
||||
result = None
|
||||
@ -157,8 +157,8 @@ class Bot(abc.ABC):
|
||||
exception = e
|
||||
|
||||
coros = list(
|
||||
map(lambda x: x(self, exception, api, data, result),
|
||||
self._called_api_hook))
|
||||
map(lambda x: x(self, exception, api, data, result), self._called_api_hook)
|
||||
)
|
||||
if coros:
|
||||
try:
|
||||
logger.debug("Running CalledAPI hooks...")
|
||||
@ -166,16 +166,17 @@ class Bot(abc.ABC):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running CalledAPI hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>")
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
if exception:
|
||||
raise exception
|
||||
return result
|
||||
|
||||
@abc.abstractmethod
|
||||
async def send(self, event: "Event", message: Union[str, "Message",
|
||||
"MessageSegment"],
|
||||
**kwargs) -> Any:
|
||||
async def send(
|
||||
self, event: "Event", message: Union[str, "Message", "MessageSegment"], **kwargs
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
|
@ -2,9 +2,8 @@ import abc
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from nonebot.utils import DataclassEncoder
|
||||
|
||||
from ._message import Message
|
||||
from nonebot.utils import DataclassEncoder
|
||||
|
||||
|
||||
class Event(abc.ABC, BaseModel):
|
||||
|
@ -1,8 +1,17 @@
|
||||
import abc
|
||||
from copy import deepcopy
|
||||
from dataclasses import field, asdict, dataclass
|
||||
from typing import (Any, Dict, List, Type, Union, Generic, Mapping, TypeVar,
|
||||
Iterable)
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
Generic,
|
||||
Mapping,
|
||||
TypeVar,
|
||||
Iterable,
|
||||
)
|
||||
|
||||
from ._template import MessageTemplate
|
||||
|
||||
@ -14,6 +23,7 @@ TM = TypeVar("TM", bound="Message")
|
||||
@dataclass
|
||||
class MessageSegment(Mapping, abc.ABC, Generic[TM]):
|
||||
"""消息段基类"""
|
||||
|
||||
type: str
|
||||
"""
|
||||
- 类型: ``str``
|
||||
@ -82,11 +92,12 @@ class MessageSegment(Mapping, abc.ABC, Generic[TM]):
|
||||
class Message(List[TMS], abc.ABC):
|
||||
"""消息数组"""
|
||||
|
||||
def __init__(self: TM,
|
||||
message: Union[str, None, Mapping, Iterable[Mapping], TMS, TM,
|
||||
Any] = None,
|
||||
def __init__(
|
||||
self: TM,
|
||||
message: Union[str, None, Mapping, Iterable[Mapping], TMS, TM, Any] = None,
|
||||
*args,
|
||||
**kwargs):
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
:参数:
|
||||
|
||||
@ -103,8 +114,7 @@ class Message(List[TMS], abc.ABC):
|
||||
self.extend(self._construct(message))
|
||||
|
||||
@classmethod
|
||||
def template(cls: Type[TM],
|
||||
format_string: Union[str, TM]) -> MessageTemplate[TM]:
|
||||
def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -156,8 +166,7 @@ class Message(List[TMS], abc.ABC):
|
||||
|
||||
@staticmethod
|
||||
@abc.abstractmethod
|
||||
def _construct(
|
||||
msg: Union[str, Mapping, Iterable[Mapping], Any]) -> Iterable[TMS]:
|
||||
def _construct(msg: Union[str, Mapping, Iterable[Mapping], Any]) -> Iterable[TMS]:
|
||||
raise NotImplementedError
|
||||
|
||||
def __add__(self: TM, other: Union[str, Mapping, Iterable[Mapping]]) -> TM:
|
||||
|
@ -1,8 +1,21 @@
|
||||
import inspect
|
||||
import functools
|
||||
from string import Formatter
|
||||
from typing import (TYPE_CHECKING, Any, Set, List, Type, Tuple, Union, Generic,
|
||||
Mapping, TypeVar, Sequence, cast, overload)
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Generic,
|
||||
Mapping,
|
||||
TypeVar,
|
||||
Sequence,
|
||||
cast,
|
||||
overload,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import Message, MessageSegment
|
||||
@ -15,14 +28,15 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
"""消息模板格式化实现类"""
|
||||
|
||||
@overload
|
||||
def __init__(self: "MessageTemplate[str]",
|
||||
template: str,
|
||||
factory: Type[str] = str) -> None:
|
||||
def __init__(
|
||||
self: "MessageTemplate[str]", template: str, factory: Type[str] = str
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def __init__(self: "MessageTemplate[TM]", template: Union[str, TM],
|
||||
factory: Type[TM]) -> None:
|
||||
def __init__(
|
||||
self: "MessageTemplate[TM]", template: Union[str, TM], factory: Type[TM]
|
||||
) -> None:
|
||||
...
|
||||
|
||||
def __init__(self, template, factory=str) -> None:
|
||||
@ -51,15 +65,15 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
elif isinstance(self.template, self.factory):
|
||||
template = cast("Message[MessageSegment]", self.template)
|
||||
for seg in template:
|
||||
msg += self.vformat(str(seg), args,
|
||||
kwargs) if seg.is_text() else seg
|
||||
msg += self.vformat(str(seg), args, kwargs) if seg.is_text() else seg
|
||||
else:
|
||||
raise TypeError('template must be a string or instance of Message!')
|
||||
raise TypeError("template must be a string or instance of Message!")
|
||||
|
||||
return msg
|
||||
|
||||
def vformat(self, format_string: str, args: Sequence[Any],
|
||||
kwargs: Mapping[str, Any]) -> TF:
|
||||
def vformat(
|
||||
self, format_string: str, args: Sequence[Any], kwargs: Mapping[str, Any]
|
||||
) -> TF:
|
||||
used_args = set()
|
||||
result, _ = self._vformat(format_string, args, kwargs, used_args, 2)
|
||||
self.check_unused_args(list(used_args), args, kwargs)
|
||||
@ -79,8 +93,9 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
|
||||
results: List[Any] = []
|
||||
|
||||
for (literal_text, field_name, format_spec,
|
||||
conversion) in self.parse(format_string):
|
||||
for (literal_text, field_name, format_spec, conversion) in self.parse(
|
||||
format_string
|
||||
):
|
||||
|
||||
# output the literal text
|
||||
if literal_text:
|
||||
@ -96,14 +111,16 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
if auto_arg_index is False:
|
||||
raise ValueError(
|
||||
"cannot switch from manual field specification to "
|
||||
"automatic field numbering")
|
||||
"automatic field numbering"
|
||||
)
|
||||
field_name = str(auto_arg_index)
|
||||
auto_arg_index += 1
|
||||
elif field_name.isdigit():
|
||||
if auto_arg_index:
|
||||
raise ValueError(
|
||||
"cannot switch from manual field specification to "
|
||||
"automatic field numbering")
|
||||
"automatic field numbering"
|
||||
)
|
||||
# disable auto arg incrementing, if it gets
|
||||
# used later on, then an exception will be raised
|
||||
auto_arg_index = False
|
||||
@ -132,8 +149,10 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
formatted_text = self.format_field(obj, str(format_control))
|
||||
results.append(formatted_text)
|
||||
|
||||
return self.factory(functools.reduce(self._add, results or
|
||||
[""])), auto_arg_index
|
||||
return (
|
||||
self.factory(functools.reduce(self._add, results or [""])),
|
||||
auto_arg_index,
|
||||
)
|
||||
|
||||
def format_field(self, value: Any, format_spec: str) -> Any:
|
||||
if issubclass(self.factory, str):
|
||||
@ -142,11 +161,20 @@ class MessageTemplate(Formatter, Generic[TF]):
|
||||
segment_class: Type[MessageSegment] = self.factory.get_segment_class()
|
||||
method = getattr(segment_class, format_spec, None)
|
||||
method_type = inspect.getattr_static(segment_class, format_spec, None)
|
||||
return (super().format_field(value, format_spec) if
|
||||
((method is None) or
|
||||
(not isinstance(method_type, (classmethod, staticmethod))
|
||||
return (
|
||||
(
|
||||
super().format_field(value, format_spec)
|
||||
if (
|
||||
(method is None)
|
||||
or (
|
||||
not isinstance(method_type, (classmethod, staticmethod))
|
||||
) # Only Call staticmethod or classmethod
|
||||
) else method(value)) if format_spec else value
|
||||
)
|
||||
else method(value)
|
||||
)
|
||||
if format_spec
|
||||
else value
|
||||
)
|
||||
|
||||
def _add(self, a: Any, b: Any) -> Any:
|
||||
try:
|
||||
|
@ -20,13 +20,17 @@ from ipaddress import IPv4Address
|
||||
from typing import Any, Set, Dict, Tuple, Union, Mapping, Optional
|
||||
|
||||
from pydantic import BaseSettings, IPvAnyAddress
|
||||
from pydantic.env_settings import (SettingsError, EnvSettingsSource,
|
||||
InitSettingsSource, SettingsSourceCallable,
|
||||
read_env_file, env_file_sentinel)
|
||||
from pydantic.env_settings import (
|
||||
SettingsError,
|
||||
EnvSettingsSource,
|
||||
InitSettingsSource,
|
||||
SettingsSourceCallable,
|
||||
read_env_file,
|
||||
env_file_sentinel,
|
||||
)
|
||||
|
||||
|
||||
class CustomEnvSettings(EnvSettingsSource):
|
||||
|
||||
def __call__(self, settings: BaseSettings) -> Dict[str, Any]:
|
||||
"""
|
||||
Build environment variables suitable for passing to the Model.
|
||||
@ -39,15 +43,24 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
env_vars = {k.lower(): v for k, v in os.environ.items()}
|
||||
|
||||
env_file_vars: Dict[str, Optional[str]] = {}
|
||||
env_file = self.env_file if self.env_file != env_file_sentinel else settings.__config__.env_file
|
||||
env_file_encoding = self.env_file_encoding if self.env_file_encoding is not None else settings.__config__.env_file_encoding
|
||||
env_file = (
|
||||
self.env_file
|
||||
if self.env_file != env_file_sentinel
|
||||
else settings.__config__.env_file
|
||||
)
|
||||
env_file_encoding = (
|
||||
self.env_file_encoding
|
||||
if self.env_file_encoding is not None
|
||||
else settings.__config__.env_file_encoding
|
||||
)
|
||||
if env_file is not None:
|
||||
env_path = Path(env_file)
|
||||
if env_path.is_file():
|
||||
env_file_vars = read_env_file(
|
||||
env_path,
|
||||
encoding=env_file_encoding,
|
||||
case_sensitive=settings.__config__.case_sensitive)
|
||||
case_sensitive=settings.__config__.case_sensitive,
|
||||
)
|
||||
env_vars = {**env_file_vars, **env_vars}
|
||||
|
||||
for field in settings.__fields__.values():
|
||||
@ -66,14 +79,12 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
try:
|
||||
env_val = settings.__config__.json_loads(env_val)
|
||||
except ValueError as e:
|
||||
raise SettingsError(
|
||||
f'error parsing JSON for "{env_name}"') from e
|
||||
raise SettingsError(f'error parsing JSON for "{env_name}"') from e
|
||||
d[field.alias] = env_val
|
||||
|
||||
if env_file_vars:
|
||||
for env_name, env_val in env_file_vars.items():
|
||||
if (env_val is None or
|
||||
len(env_val) == 0) and env_name in env_vars:
|
||||
if (env_val is None or len(env_val) == 0) and env_name in env_vars:
|
||||
env_val = env_vars[env_name]
|
||||
try:
|
||||
if env_val:
|
||||
@ -87,12 +98,10 @@ class CustomEnvSettings(EnvSettingsSource):
|
||||
|
||||
|
||||
class BaseConfig(BaseSettings):
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
return self.__dict__.get(name)
|
||||
|
||||
class Config:
|
||||
|
||||
@classmethod
|
||||
def customise_sources(
|
||||
cls,
|
||||
@ -101,10 +110,14 @@ class BaseConfig(BaseSettings):
|
||||
file_secret_settings: SettingsSourceCallable,
|
||||
) -> Tuple[SettingsSourceCallable, ...]:
|
||||
common_config = init_settings.init_kwargs.pop("_common_config", {})
|
||||
return (init_settings,
|
||||
CustomEnvSettings(env_settings.env_file,
|
||||
env_settings.env_file_encoding),
|
||||
InitSettingsSource(common_config), file_secret_settings)
|
||||
return (
|
||||
init_settings,
|
||||
CustomEnvSettings(
|
||||
env_settings.env_file, env_settings.env_file_encoding
|
||||
),
|
||||
InitSettingsSource(common_config),
|
||||
file_secret_settings,
|
||||
)
|
||||
|
||||
|
||||
class Env(BaseConfig):
|
||||
@ -135,6 +148,7 @@ class Config(BaseConfig):
|
||||
除了 NoneBot 的配置项外,还可以自行添加配置项到 ``.env.{environment}`` 文件中。
|
||||
这些配置将会在 json 反序列化后一起带入 ``Config`` 类中。
|
||||
"""
|
||||
|
||||
# nonebot configs
|
||||
driver: str = "nonebot.drivers.fastapi"
|
||||
"""
|
||||
@ -210,7 +224,7 @@ class Config(BaseConfig):
|
||||
|
||||
API_ROOT={"123456": "http://127.0.0.1:5700"}
|
||||
"""
|
||||
api_timeout: Optional[float] = 30.
|
||||
api_timeout: Optional[float] = 30.0
|
||||
"""
|
||||
- **类型**: ``Optional[float]``
|
||||
- **默认值**: ``30.``
|
||||
|
@ -21,9 +21,14 @@ from .models import Dependent as Dependent
|
||||
from nonebot.exception import SkippedException
|
||||
from .models import DependsWrapper as DependsWrapper
|
||||
from nonebot.typing import T_Handler, T_DependencyCache
|
||||
from nonebot.utils import (CacheLock, run_sync, is_gen_callable,
|
||||
run_sync_ctx_manager, is_async_gen_callable,
|
||||
is_coroutine_callable)
|
||||
from nonebot.utils import (
|
||||
CacheLock,
|
||||
run_sync,
|
||||
is_gen_callable,
|
||||
run_sync_ctx_manager,
|
||||
is_async_gen_callable,
|
||||
is_coroutine_callable,
|
||||
)
|
||||
|
||||
cache_lock = CacheLock()
|
||||
|
||||
@ -33,30 +38,27 @@ class CustomConfig(BaseConfig):
|
||||
|
||||
|
||||
def get_param_sub_dependent(
|
||||
*,
|
||||
param: inspect.Parameter,
|
||||
allow_types: Optional[List[Type[Param]]] = None) -> Dependent:
|
||||
*, param: inspect.Parameter, allow_types: Optional[List[Type[Param]]] = None
|
||||
) -> Dependent:
|
||||
depends: DependsWrapper = param.default
|
||||
if depends.dependency:
|
||||
dependency = depends.dependency
|
||||
else:
|
||||
dependency = param.annotation
|
||||
return get_sub_dependant(depends=depends,
|
||||
dependency=dependency,
|
||||
name=param.name,
|
||||
allow_types=allow_types)
|
||||
return get_sub_dependant(
|
||||
depends=depends, dependency=dependency, name=param.name, allow_types=allow_types
|
||||
)
|
||||
|
||||
|
||||
def get_parameterless_sub_dependant(
|
||||
*,
|
||||
depends: DependsWrapper,
|
||||
allow_types: Optional[List[Type[Param]]] = None) -> Dependent:
|
||||
*, depends: DependsWrapper, allow_types: Optional[List[Type[Param]]] = None
|
||||
) -> Dependent:
|
||||
assert callable(
|
||||
depends.dependency
|
||||
), "A parameter-less dependency must have a callable dependency"
|
||||
return get_sub_dependant(depends=depends,
|
||||
dependency=depends.dependency,
|
||||
allow_types=allow_types)
|
||||
return get_sub_dependant(
|
||||
depends=depends, dependency=depends.dependency, allow_types=allow_types
|
||||
)
|
||||
|
||||
|
||||
def get_sub_dependant(
|
||||
@ -64,29 +66,31 @@ def get_sub_dependant(
|
||||
depends: DependsWrapper,
|
||||
dependency: T_Handler,
|
||||
name: Optional[str] = None,
|
||||
allow_types: Optional[List[Type[Param]]] = None) -> Dependent:
|
||||
sub_dependant = get_dependent(func=dependency,
|
||||
name=name,
|
||||
use_cache=depends.use_cache,
|
||||
allow_types=allow_types)
|
||||
allow_types: Optional[List[Type[Param]]] = None,
|
||||
) -> Dependent:
|
||||
sub_dependant = get_dependent(
|
||||
func=dependency, name=name, use_cache=depends.use_cache, allow_types=allow_types
|
||||
)
|
||||
return sub_dependant
|
||||
|
||||
|
||||
def get_dependent(*,
|
||||
def get_dependent(
|
||||
*,
|
||||
func: T_Handler,
|
||||
name: Optional[str] = None,
|
||||
use_cache: bool = True,
|
||||
allow_types: Optional[List[Type[Param]]] = None) -> Dependent:
|
||||
allow_types: Optional[List[Type[Param]]] = None,
|
||||
) -> Dependent:
|
||||
signature = get_typed_signature(func)
|
||||
params = signature.parameters
|
||||
dependent = Dependent(func=func,
|
||||
name=name,
|
||||
allow_types=allow_types,
|
||||
use_cache=use_cache)
|
||||
dependent = Dependent(
|
||||
func=func, name=name, allow_types=allow_types, use_cache=use_cache
|
||||
)
|
||||
for param_name, param in params.items():
|
||||
if isinstance(param.default, DependsWrapper):
|
||||
sub_dependent = get_param_sub_dependent(param=param,
|
||||
allow_types=allow_types)
|
||||
sub_dependent = get_param_sub_dependent(
|
||||
param=param, allow_types=allow_types
|
||||
)
|
||||
dependent.dependencies.append(sub_dependent)
|
||||
continue
|
||||
|
||||
@ -111,16 +115,18 @@ def get_dependent(*,
|
||||
required = default_value == Required
|
||||
if param.annotation != param.empty:
|
||||
annotation = param.annotation
|
||||
annotation = get_annotation_from_field_info(annotation, field_info,
|
||||
param_name)
|
||||
annotation = get_annotation_from_field_info(annotation, field_info, param_name)
|
||||
dependent.params.append(
|
||||
ModelField(name=param_name,
|
||||
ModelField(
|
||||
name=param_name,
|
||||
type_=annotation,
|
||||
class_validators=None,
|
||||
model_config=CustomConfig,
|
||||
default=None if required else default_value,
|
||||
required=required,
|
||||
field_info=field_info))
|
||||
field_info=field_info,
|
||||
)
|
||||
)
|
||||
|
||||
return dependent
|
||||
|
||||
@ -131,24 +137,22 @@ async def solve_dependencies(
|
||||
_stack: Optional[AsyncExitStack] = None,
|
||||
_sub_dependents: Optional[List[Dependent]] = None,
|
||||
_dependency_cache: Optional[T_DependencyCache] = None,
|
||||
**params: Any) -> Tuple[Dict[str, Any], T_DependencyCache]:
|
||||
**params: Any,
|
||||
) -> Tuple[Dict[str, Any], T_DependencyCache]:
|
||||
values: Dict[str, Any] = {}
|
||||
dependency_cache = {} if _dependency_cache is None else _dependency_cache
|
||||
|
||||
# solve sub dependencies
|
||||
sub_dependent: Dependent
|
||||
for sub_dependent in chain(_sub_dependents or tuple(),
|
||||
_dependent.dependencies):
|
||||
for sub_dependent in chain(_sub_dependents or tuple(), _dependent.dependencies):
|
||||
sub_dependent.func = cast(Callable[..., Any], sub_dependent.func)
|
||||
sub_dependent.cache_key = cast(Callable[..., Any],
|
||||
sub_dependent.cache_key)
|
||||
sub_dependent.cache_key = cast(Callable[..., Any], sub_dependent.cache_key)
|
||||
func = sub_dependent.func
|
||||
|
||||
# solve sub dependency with current cache
|
||||
solved_result = await solve_dependencies(
|
||||
_dependent=sub_dependent,
|
||||
_dependency_cache=dependency_cache,
|
||||
**params)
|
||||
_dependent=sub_dependent, _dependency_cache=dependency_cache, **params
|
||||
)
|
||||
sub_values, sub_dependency_cache = solved_result
|
||||
# update cache?
|
||||
# dependency_cache.update(sub_dependency_cache)
|
||||
@ -162,8 +166,7 @@ async def solve_dependencies(
|
||||
_stack, AsyncExitStack
|
||||
), "Generator dependency should be called in context"
|
||||
if is_gen_callable(func):
|
||||
cm = run_sync_ctx_manager(
|
||||
contextmanager(func)(**sub_values))
|
||||
cm = run_sync_ctx_manager(contextmanager(func)(**sub_values))
|
||||
else:
|
||||
cm = asynccontextmanager(func)(**sub_values)
|
||||
solved = await _stack.enter_async_context(cm)
|
||||
@ -182,19 +185,17 @@ async def solve_dependencies(
|
||||
# usual dependency
|
||||
for field in _dependent.params:
|
||||
field_info = field.field_info
|
||||
assert isinstance(field_info,
|
||||
Param), "Params must be subclasses of Param"
|
||||
assert isinstance(field_info, Param), "Params must be subclasses of Param"
|
||||
value = field_info._solve(**params)
|
||||
if value == Undefined:
|
||||
value = field.get_default()
|
||||
_, errs_ = field.validate(value,
|
||||
values,
|
||||
loc=(str(field_info), field.alias))
|
||||
_, errs_ = field.validate(value, values, loc=(str(field_info), field.alias))
|
||||
if errs_:
|
||||
logger.debug(
|
||||
f"{field_info} "
|
||||
f"type {type(value)} not match depends {_dependent.func} "
|
||||
f"annotation {field._type_display()}, ignored")
|
||||
f"annotation {field._type_display()}, ignored"
|
||||
)
|
||||
raise SkippedException
|
||||
else:
|
||||
values[field.name] = value
|
||||
@ -202,9 +203,7 @@ async def solve_dependencies(
|
||||
return values, dependency_cache
|
||||
|
||||
|
||||
def Depends(dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True) -> Any:
|
||||
def Depends(dependency: Optional[T_Handler] = None, *, use_cache: bool = True) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
|
@ -9,7 +9,6 @@ from nonebot.typing import T_Handler
|
||||
|
||||
|
||||
class Param(abc.ABC, FieldInfo):
|
||||
|
||||
@classmethod
|
||||
@abc.abstractmethod
|
||||
def _check(cls, name: str, param: inspect.Parameter) -> bool:
|
||||
@ -21,11 +20,9 @@ class Param(abc.ABC, FieldInfo):
|
||||
|
||||
|
||||
class DependsWrapper:
|
||||
|
||||
def __init__(self,
|
||||
dependency: Optional[T_Handler] = None,
|
||||
*,
|
||||
use_cache: bool = True) -> None:
|
||||
def __init__(
|
||||
self, dependency: Optional[T_Handler] = None, *, use_cache: bool = True
|
||||
) -> None:
|
||||
self.dependency = dependency
|
||||
self.use_cache = use_cache
|
||||
|
||||
@ -36,15 +33,16 @@ class DependsWrapper:
|
||||
|
||||
|
||||
class Dependent:
|
||||
|
||||
def __init__(self,
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
func: Optional[T_Handler] = None,
|
||||
name: Optional[str] = None,
|
||||
params: Optional[List[ModelField]] = None,
|
||||
allow_types: Optional[List[Type[Param]]] = None,
|
||||
dependencies: Optional[List["Dependent"]] = None,
|
||||
use_cache: bool = True) -> None:
|
||||
use_cache: bool = True,
|
||||
) -> None:
|
||||
self.func = func
|
||||
self.name = name
|
||||
self.params = params or []
|
||||
|
@ -16,14 +16,14 @@ def get_typed_signature(func: T_Handler) -> inspect.Signature:
|
||||
kind=param.kind,
|
||||
default=param.default,
|
||||
annotation=get_typed_annotation(param, globalns),
|
||||
) for param in signature.parameters.values()
|
||||
)
|
||||
for param in signature.parameters.values()
|
||||
]
|
||||
typed_signature = inspect.Signature(typed_params)
|
||||
return typed_signature
|
||||
|
||||
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str,
|
||||
Any]) -> Any:
|
||||
def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str, Any]) -> Any:
|
||||
annotation = param.annotation
|
||||
if isinstance(annotation, str):
|
||||
annotation = ForwardRef(annotation)
|
||||
@ -31,7 +31,7 @@ def get_typed_annotation(param: inspect.Parameter, globalns: Dict[str,
|
||||
annotation = evaluate_forwardref(annotation, globalns, globalns)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).warning(
|
||||
f"Unknown ForwardRef[\"{param.annotation}\"] for parameter {param.name}"
|
||||
f'Unknown ForwardRef["{param.annotation}"] for parameter {param.name}'
|
||||
)
|
||||
return inspect.Parameter.empty
|
||||
return annotation
|
||||
|
@ -8,8 +8,17 @@
|
||||
import abc
|
||||
import asyncio
|
||||
from dataclasses import field, dataclass
|
||||
from typing import (TYPE_CHECKING, Any, Set, Dict, Type, Union, Callable,
|
||||
Optional, Awaitable)
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Set,
|
||||
Dict,
|
||||
Type,
|
||||
Union,
|
||||
Callable,
|
||||
Optional,
|
||||
Awaitable,
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.utils import escape_tag
|
||||
@ -90,12 +99,14 @@ class Driver(abc.ABC):
|
||||
"""
|
||||
if name in self._adapters:
|
||||
logger.opt(colors=True).debug(
|
||||
f'Adapter "<y>{escape_tag(name)}</y>" already exists')
|
||||
f'Adapter "<y>{escape_tag(name)}</y>" already exists'
|
||||
)
|
||||
return
|
||||
self._adapters[name] = adapter
|
||||
adapter.register(self, self.config, **kwargs)
|
||||
logger.opt(colors=True).debug(
|
||||
f'Succeeded to load adapter "<y>{escape_tag(name)}</y>"')
|
||||
f'Succeeded to load adapter "<y>{escape_tag(name)}</y>"'
|
||||
)
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
@ -121,7 +132,8 @@ class Driver(abc.ABC):
|
||||
* ``**kwargs``
|
||||
"""
|
||||
logger.opt(colors=True).debug(
|
||||
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>")
|
||||
f"<g>Loaded adapters: {escape_tag(', '.join(self._adapters))}</g>"
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_startup(self, func: Callable) -> Callable:
|
||||
@ -146,8 +158,7 @@ class Driver(abc.ABC):
|
||||
self._bot_connection_hook.add(func)
|
||||
return func
|
||||
|
||||
def on_bot_disconnect(
|
||||
self, func: T_BotDisconnectionHook) -> T_BotDisconnectionHook:
|
||||
def on_bot_disconnect(self, func: T_BotDisconnectionHook) -> T_BotDisconnectionHook:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -172,7 +183,8 @@ class Driver(abc.ABC):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running WebSocketConnection hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>")
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
|
||||
@ -189,7 +201,8 @@ class Driver(abc.ABC):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running WebSocketDisConnection hook. "
|
||||
"Running cancelled!</bg #f8bbd0></r>")
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
asyncio.create_task(_run_hook(bot))
|
||||
|
||||
@ -201,8 +214,8 @@ class ForwardDriver(Driver):
|
||||
|
||||
@abc.abstractmethod
|
||||
def setup_http_polling(
|
||||
self, setup: Union["HTTPPollingSetup",
|
||||
Callable[[], Awaitable["HTTPPollingSetup"]]]
|
||||
self,
|
||||
setup: Union["HTTPPollingSetup", Callable[[], Awaitable["HTTPPollingSetup"]]],
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
@ -217,8 +230,7 @@ class ForwardDriver(Driver):
|
||||
|
||||
@abc.abstractmethod
|
||||
def setup_websocket(
|
||||
self, setup: Union["WebSocketSetup",
|
||||
Callable[[], Awaitable["WebSocketSetup"]]]
|
||||
self, setup: Union["WebSocketSetup", Callable[[], Awaitable["WebSocketSetup"]]]
|
||||
) -> None:
|
||||
"""
|
||||
:说明:
|
||||
@ -288,6 +300,7 @@ class HTTPRequest(HTTPConnection):
|
||||
.. _asgi http scope:
|
||||
https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
|
||||
"""
|
||||
|
||||
method: str = "GET"
|
||||
"""The HTTP method name, uppercased."""
|
||||
body: bytes = b""
|
||||
@ -309,6 +322,7 @@ class HTTPResponse:
|
||||
.. _asgi http scope:
|
||||
https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope
|
||||
"""
|
||||
|
||||
status: int
|
||||
"""HTTP status code."""
|
||||
body: Optional[bytes] = None
|
||||
@ -416,5 +430,5 @@ class WebSocketSetup:
|
||||
"""URL"""
|
||||
headers: Dict[str, str] = field(default_factory=dict)
|
||||
"""HTTP headers"""
|
||||
reconnect_interval: float = 3.
|
||||
reconnect_interval: float = 3.0
|
||||
"""WebSocket 重连间隔"""
|
||||
|
@ -20,13 +20,16 @@ from nonebot.typing import overrides
|
||||
from nonebot.utils import escape_tag
|
||||
from nonebot.config import Env, Config
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import (HTTPRequest, ForwardDriver, WebSocketSetup,
|
||||
HTTPPollingSetup)
|
||||
from nonebot.drivers import (
|
||||
HTTPRequest,
|
||||
ForwardDriver,
|
||||
WebSocketSetup,
|
||||
HTTPPollingSetup,
|
||||
)
|
||||
|
||||
STARTUP_FUNC = Callable[[], Awaitable[None]]
|
||||
SHUTDOWN_FUNC = Callable[[], Awaitable[None]]
|
||||
HTTPPOLLING_SETUP = Union[HTTPPollingSetup,
|
||||
Callable[[], Awaitable[HTTPPollingSetup]]]
|
||||
HTTPPOLLING_SETUP = Union[HTTPPollingSetup, Callable[[], Awaitable[HTTPPollingSetup]]]
|
||||
WEBSOCKET_SETUP = Union[WebSocketSetup, Callable[[], Awaitable[WebSocketSetup]]]
|
||||
HANDLED_SIGNALS = (
|
||||
signal.SIGINT, # Unix signal 2. Sent by Ctrl+C.
|
||||
@ -146,7 +149,8 @@ class Driver(ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running startup function. "
|
||||
"Ignored!</bg #f8bbd0></r>")
|
||||
"Ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
async def main_loop(self):
|
||||
await self.should_exit.wait()
|
||||
@ -160,24 +164,20 @@ class Driver(ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running shutdown function. "
|
||||
"Ignored!</bg #f8bbd0></r>")
|
||||
"Ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
for task in self.connections:
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
tasks = [
|
||||
t for t in asyncio.all_tasks() if t is not asyncio.current_task()
|
||||
]
|
||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||
if tasks and not self.force_exit:
|
||||
logger.info("Waiting for tasks to finish. (CTRL+C to force quit)")
|
||||
while tasks and not self.force_exit:
|
||||
await asyncio.sleep(0.1)
|
||||
tasks = [
|
||||
t for t in asyncio.all_tasks()
|
||||
if t is not asyncio.current_task()
|
||||
]
|
||||
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
|
||||
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
@ -209,9 +209,7 @@ class Driver(ForwardDriver):
|
||||
self.should_exit.set()
|
||||
|
||||
async def _http_loop(self, setup: HTTPPOLLING_SETUP):
|
||||
|
||||
async def _build_request(
|
||||
setup: HTTPPollingSetup) -> Optional[HTTPRequest]:
|
||||
async def _build_request(setup: HTTPPollingSetup) -> Optional[HTTPRequest]:
|
||||
url = URL(setup.url)
|
||||
if not url.is_absolute() or not url.host:
|
||||
logger.opt(colors=True).error(
|
||||
@ -219,10 +217,15 @@ class Driver(ForwardDriver):
|
||||
)
|
||||
return
|
||||
host = f"{url.host}:{url.port}" if url.port else url.host
|
||||
return HTTPRequest(setup.http_version, url.scheme, url.path,
|
||||
url.raw_query_string.encode("latin-1"), {
|
||||
**setup.headers, "host": host
|
||||
}, setup.method, setup.body)
|
||||
return HTTPRequest(
|
||||
setup.http_version,
|
||||
url.scheme,
|
||||
url.path,
|
||||
url.raw_query_string.encode("latin-1"),
|
||||
{**setup.headers, "host": host},
|
||||
setup.method,
|
||||
setup.body,
|
||||
)
|
||||
|
||||
bot: Optional[Bot] = None
|
||||
request: Optional[HTTPRequest] = None
|
||||
@ -230,7 +233,8 @@ class Driver(ForwardDriver):
|
||||
|
||||
logger.opt(colors=True).info(
|
||||
f"Start http polling for <y>{escape_tag(setup.adapter.upper())} "
|
||||
f"Bot {escape_tag(setup.self_id)}</y>")
|
||||
f"Bot {escape_tag(setup.self_id)}</y>"
|
||||
)
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
@ -244,7 +248,8 @@ class Driver(ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error while parsing setup "
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>")
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
continue
|
||||
|
||||
@ -286,19 +291,22 @@ class Driver(ForwardDriver):
|
||||
)
|
||||
|
||||
try:
|
||||
async with session.request(request.method,
|
||||
async with session.request(
|
||||
request.method,
|
||||
setup_.url,
|
||||
data=request.body,
|
||||
headers=headers,
|
||||
timeout=timeout,
|
||||
version=version) as response:
|
||||
version=version,
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
data = await response.read()
|
||||
asyncio.create_task(bot.handle_message(data))
|
||||
except aiohttp.ClientResponseError as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Error occurred while requesting {escape_tag(setup_.url)}. "
|
||||
"Try to reconnect...</bg #f8bbd0></r>")
|
||||
"Try to reconnect...</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
await asyncio.sleep(setup_.poll_interval)
|
||||
|
||||
@ -307,7 +315,8 @@ class Driver(ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Unexpected exception occurred "
|
||||
"while http polling</bg #f8bbd0></r>")
|
||||
"while http polling</bg #f8bbd0></r>"
|
||||
)
|
||||
finally:
|
||||
if bot:
|
||||
self._bot_disconnect(bot)
|
||||
@ -327,7 +336,8 @@ class Driver(ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error while parsing setup "
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>")
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
continue
|
||||
|
||||
@ -346,17 +356,21 @@ class Driver(ForwardDriver):
|
||||
f"Bot {setup_.self_id} from adapter {setup_.adapter} connecting to {url}"
|
||||
)
|
||||
try:
|
||||
async with session.ws_connect(url,
|
||||
headers=headers,
|
||||
timeout=30.) as ws:
|
||||
async with session.ws_connect(
|
||||
url, headers=headers, timeout=30.0
|
||||
) as ws:
|
||||
logger.opt(colors=True).info(
|
||||
f"WebSocket Connection to <y>{escape_tag(setup_.adapter.upper())} "
|
||||
f"Bot {escape_tag(setup_.self_id)}</y> succeeded!"
|
||||
)
|
||||
request = WebSocket(
|
||||
"1.1", url.scheme, url.path,
|
||||
url.raw_query_string.encode("latin-1"), headers,
|
||||
ws)
|
||||
"1.1",
|
||||
url.scheme,
|
||||
url.path,
|
||||
url.raw_query_string.encode("latin-1"),
|
||||
headers,
|
||||
ws,
|
||||
)
|
||||
|
||||
BotClass = self._adapters[setup_.adapter]
|
||||
bot = BotClass(setup_.self_id, request)
|
||||
@ -365,25 +379,30 @@ class Driver(ForwardDriver):
|
||||
msg = await ws.receive()
|
||||
if msg.type == aiohttp.WSMsgType.text:
|
||||
asyncio.create_task(
|
||||
bot.handle_message(msg.data.encode()))
|
||||
bot.handle_message(msg.data.encode())
|
||||
)
|
||||
elif msg.type == aiohttp.WSMsgType.binary:
|
||||
asyncio.create_task(
|
||||
bot.handle_message(msg.data))
|
||||
asyncio.create_task(bot.handle_message(msg.data))
|
||||
elif msg.type == aiohttp.WSMsgType.error:
|
||||
logger.opt(colors=True).error(
|
||||
"<r><bg #f8bbd0>Error while handling websocket frame. "
|
||||
"Try to reconnect...</bg #f8bbd0></r>")
|
||||
"Try to reconnect...</bg #f8bbd0></r>"
|
||||
)
|
||||
break
|
||||
else:
|
||||
logger.opt(colors=True).error(
|
||||
"<r><bg #f8bbd0>WebSocket connection closed by peer. "
|
||||
"Try to reconnect...</bg #f8bbd0></r>")
|
||||
"Try to reconnect...</bg #f8bbd0></r>"
|
||||
)
|
||||
break
|
||||
except (aiohttp.ClientResponseError,
|
||||
aiohttp.ClientConnectionError) as e:
|
||||
except (
|
||||
aiohttp.ClientResponseError,
|
||||
aiohttp.ClientConnectionError,
|
||||
) as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Error while connecting to {escape_tag(str(url))}. "
|
||||
"Try to reconnect...</bg #f8bbd0></r>")
|
||||
"Try to reconnect...</bg #f8bbd0></r>"
|
||||
)
|
||||
finally:
|
||||
if bot:
|
||||
self._bot_disconnect(bot)
|
||||
@ -395,7 +414,8 @@ class Driver(ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Unexpected exception occurred "
|
||||
"while websocket loop</bg #f8bbd0></r>")
|
||||
"while websocket loop</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
@ -32,11 +32,15 @@ from nonebot.typing import overrides
|
||||
from nonebot.utils import escape_tag
|
||||
from nonebot.config import Config as NoneBotConfig
|
||||
from nonebot.drivers import WebSocket as BaseWebSocket
|
||||
from nonebot.drivers import (HTTPRequest, ForwardDriver, ReverseDriver,
|
||||
WebSocketSetup, HTTPPollingSetup)
|
||||
from nonebot.drivers import (
|
||||
HTTPRequest,
|
||||
ForwardDriver,
|
||||
ReverseDriver,
|
||||
WebSocketSetup,
|
||||
HTTPPollingSetup,
|
||||
)
|
||||
|
||||
HTTPPOLLING_SETUP = Union[HTTPPollingSetup,
|
||||
Callable[[], Awaitable[HTTPPollingSetup]]]
|
||||
HTTPPOLLING_SETUP = Union[HTTPPollingSetup, Callable[[], Awaitable[HTTPPollingSetup]]]
|
||||
WEBSOCKET_SETUP = Union[WebSocketSetup, Callable[[], Awaitable[WebSocketSetup]]]
|
||||
|
||||
|
||||
@ -44,6 +48,7 @@ class Config(BaseSettings):
|
||||
"""
|
||||
FastAPI 驱动框架设置,详情参考 FastAPI 文档
|
||||
"""
|
||||
|
||||
fastapi_openapi_url: Optional[str] = None
|
||||
"""
|
||||
:类型:
|
||||
@ -226,12 +231,14 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
self.websockets.append(setup)
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
def run(self,
|
||||
def run(
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
*,
|
||||
app: Optional[str] = None,
|
||||
**kwargs):
|
||||
**kwargs,
|
||||
):
|
||||
"""使用 ``uvicorn`` 启动 FastAPI"""
|
||||
super().run(host, port, app, **kwargs)
|
||||
LOGGING_CONFIG = {
|
||||
@ -243,10 +250,7 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"uvicorn.error": {
|
||||
"handlers": ["default"],
|
||||
"level": "INFO"
|
||||
},
|
||||
"uvicorn.error": {"handlers": ["default"], "level": "INFO"},
|
||||
"uvicorn.access": {
|
||||
"handlers": ["default"],
|
||||
"level": "INFO",
|
||||
@ -258,15 +262,16 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
host=host or str(self.config.host),
|
||||
port=port or self.config.port,
|
||||
reload=self.fastapi_config.fastapi_reload
|
||||
if self.fastapi_config.fastapi_reload is not None else
|
||||
(bool(app) and self.config.debug),
|
||||
if self.fastapi_config.fastapi_reload is not None
|
||||
else (bool(app) and self.config.debug),
|
||||
reload_dirs=self.fastapi_config.fastapi_reload_dirs,
|
||||
reload_delay=self.fastapi_config.fastapi_reload_delay,
|
||||
reload_includes=self.fastapi_config.fastapi_reload_includes,
|
||||
reload_excludes=self.fastapi_config.fastapi_reload_excludes,
|
||||
debug=self.config.debug,
|
||||
log_config=LOGGING_CONFIG,
|
||||
**kwargs)
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def _run_forward(self):
|
||||
for setup in self.http_pollings:
|
||||
@ -287,39 +292,49 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
logger.warning(
|
||||
f"Unknown adapter {adapter}. Please register the adapter before use."
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="adapter not found")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail="adapter not found"
|
||||
)
|
||||
|
||||
# 创建 Bot 对象
|
||||
BotClass = self._adapters[adapter]
|
||||
http_request = HTTPRequest(request.scope["http_version"],
|
||||
request.url.scheme, request.url.path,
|
||||
http_request = HTTPRequest(
|
||||
request.scope["http_version"],
|
||||
request.url.scheme,
|
||||
request.url.path,
|
||||
request.scope["query_string"],
|
||||
dict(request.headers), request.method, data)
|
||||
x_self_id, response = await BotClass.check_permission(
|
||||
self, http_request)
|
||||
dict(request.headers),
|
||||
request.method,
|
||||
data,
|
||||
)
|
||||
x_self_id, response = await BotClass.check_permission(self, http_request)
|
||||
|
||||
if not x_self_id:
|
||||
raise HTTPException(
|
||||
response and response.status or 401, response and
|
||||
response.body and response.body.decode("utf-8"))
|
||||
response and response.status or 401,
|
||||
response and response.body and response.body.decode("utf-8"),
|
||||
)
|
||||
|
||||
if x_self_id in self._clients:
|
||||
logger.warning("There's already a reverse websocket connection,"
|
||||
"so the event may be handled twice.")
|
||||
logger.warning(
|
||||
"There's already a reverse websocket connection,"
|
||||
"so the event may be handled twice."
|
||||
)
|
||||
|
||||
bot = BotClass(x_self_id, http_request)
|
||||
|
||||
asyncio.create_task(bot.handle_message(data))
|
||||
return Response(response and response.body,
|
||||
response and response.status or 200)
|
||||
return Response(response and response.body, response and response.status or 200)
|
||||
|
||||
async def _handle_ws_reverse(self, adapter: str,
|
||||
websocket: FastAPIWebSocket):
|
||||
ws = WebSocket(websocket.scope.get("http_version",
|
||||
"1.1"), websocket.url.scheme,
|
||||
websocket.url.path, websocket.scope["query_string"],
|
||||
dict(websocket.headers), websocket)
|
||||
async def _handle_ws_reverse(self, adapter: str, websocket: FastAPIWebSocket):
|
||||
ws = WebSocket(
|
||||
websocket.scope.get("http_version", "1.1"),
|
||||
websocket.url.scheme,
|
||||
websocket.url.path,
|
||||
websocket.scope["query_string"],
|
||||
dict(websocket.headers),
|
||||
websocket,
|
||||
)
|
||||
|
||||
if adapter not in self._adapters:
|
||||
logger.warning(
|
||||
@ -349,7 +364,8 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
await ws.accept()
|
||||
logger.opt(colors=True).info(
|
||||
f"WebSocket Connection from <y>{escape_tag(adapter.upper())} "
|
||||
f"Bot {escape_tag(self_id)}</y> Accepted!")
|
||||
f"Bot {escape_tag(self_id)}</y> Accepted!"
|
||||
)
|
||||
|
||||
self._bot_connect(bot)
|
||||
|
||||
@ -362,7 +378,8 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
break
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).error(
|
||||
"Error when receiving data from websocket.")
|
||||
"Error when receiving data from websocket."
|
||||
)
|
||||
break
|
||||
|
||||
asyncio.create_task(bot.handle_message(data.encode()))
|
||||
@ -370,9 +387,7 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
self._bot_disconnect(bot)
|
||||
|
||||
async def _http_loop(self, setup: HTTPPOLLING_SETUP):
|
||||
|
||||
async def _build_request(
|
||||
setup: HTTPPollingSetup) -> Optional[HTTPRequest]:
|
||||
async def _build_request(setup: HTTPPollingSetup) -> Optional[HTTPRequest]:
|
||||
url = httpx.URL(setup.url)
|
||||
if not url.netloc:
|
||||
logger.opt(colors=True).error(
|
||||
@ -380,9 +395,14 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
)
|
||||
return
|
||||
return HTTPRequest(
|
||||
setup.http_version, url.scheme, url.path, url.query, {
|
||||
**setup.headers, "host": url.netloc.decode("ascii")
|
||||
}, setup.method, setup.body)
|
||||
setup.http_version,
|
||||
url.scheme,
|
||||
url.path,
|
||||
url.query,
|
||||
{**setup.headers, "host": url.netloc.decode("ascii")},
|
||||
setup.method,
|
||||
setup.body,
|
||||
)
|
||||
|
||||
bot: Optional[Bot] = None
|
||||
request: Optional[HTTPRequest] = None
|
||||
@ -390,11 +410,11 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
|
||||
logger.opt(colors=True).info(
|
||||
f"Start http polling for <y>{escape_tag(setup.adapter.upper())} "
|
||||
f"Bot {escape_tag(setup.self_id)}</y>")
|
||||
f"Bot {escape_tag(setup.self_id)}</y>"
|
||||
)
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(http2=True,
|
||||
follow_redirects=True) as session:
|
||||
async with httpx.AsyncClient(http2=True, follow_redirects=True) as session:
|
||||
while not self.shutdown.is_set():
|
||||
|
||||
try:
|
||||
@ -405,7 +425,8 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error while parsing setup "
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>")
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
continue
|
||||
|
||||
@ -432,18 +453,21 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
f"Bot {setup_.self_id} from adapter {setup_.adapter} request {setup_.url}"
|
||||
)
|
||||
try:
|
||||
response = await session.request(request.method,
|
||||
response = await session.request(
|
||||
request.method,
|
||||
setup_.url,
|
||||
content=request.body,
|
||||
headers=headers,
|
||||
timeout=30.)
|
||||
timeout=30.0,
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.read()
|
||||
asyncio.create_task(bot.handle_message(data))
|
||||
except httpx.HTTPError as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Error occurred while requesting {escape_tag(setup_.url)}. "
|
||||
"Try to reconnect...</bg #f8bbd0></r>")
|
||||
"Try to reconnect...</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
await asyncio.sleep(setup_.poll_interval)
|
||||
|
||||
@ -452,7 +476,8 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Unexpected exception occurred "
|
||||
"while http polling</bg #f8bbd0></r>")
|
||||
"while http polling</bg #f8bbd0></r>"
|
||||
)
|
||||
finally:
|
||||
if bot:
|
||||
self._bot_disconnect(bot)
|
||||
@ -471,7 +496,8 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error while parsing setup "
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>")
|
||||
f"{escape_tag(repr(setup))}.</bg #f8bbd0></r>"
|
||||
)
|
||||
await asyncio.sleep(3)
|
||||
continue
|
||||
|
||||
@ -491,9 +517,11 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
async with connection as ws:
|
||||
logger.opt(colors=True).info(
|
||||
f"WebSocket Connection to <y>{escape_tag(setup_.adapter.upper())} "
|
||||
f"Bot {escape_tag(setup_.self_id)}</y> succeeded!")
|
||||
request = WebSocket("1.1", url.scheme, url.path,
|
||||
url.query, headers, ws)
|
||||
f"Bot {escape_tag(setup_.self_id)}</y> succeeded!"
|
||||
)
|
||||
request = WebSocket(
|
||||
"1.1", url.scheme, url.path, url.query, headers, ws
|
||||
)
|
||||
|
||||
BotClass = self._adapters[setup_.adapter]
|
||||
bot = BotClass(setup_.self_id, request)
|
||||
@ -506,12 +534,14 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
except ConnectionClosed:
|
||||
logger.opt(colors=True).error(
|
||||
"<r><bg #f8bbd0>WebSocket connection closed by peer. "
|
||||
"Try to reconnect...</bg #f8bbd0></r>")
|
||||
"Try to reconnect...</bg #f8bbd0></r>"
|
||||
)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Error while connecting to {url}. "
|
||||
"Try to reconnect...</bg #f8bbd0></r>")
|
||||
"Try to reconnect...</bg #f8bbd0></r>"
|
||||
)
|
||||
finally:
|
||||
if bot:
|
||||
self._bot_disconnect(bot)
|
||||
@ -523,21 +553,22 @@ class Driver(ReverseDriver, ForwardDriver):
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Unexpected exception occurred "
|
||||
"while websocket loop</bg #f8bbd0></r>")
|
||||
"while websocket loop</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebSocket(BaseWebSocket):
|
||||
websocket: Union[FastAPIWebSocket,
|
||||
WebSocketClientProtocol] = None # type: ignore
|
||||
websocket: Union[FastAPIWebSocket, WebSocketClientProtocol] = None # type: ignore
|
||||
|
||||
@property
|
||||
@overrides(BaseWebSocket)
|
||||
def closed(self) -> bool:
|
||||
if isinstance(self.websocket, FastAPIWebSocket):
|
||||
return (
|
||||
self.websocket.client_state == WebSocketState.DISCONNECTED or
|
||||
self.websocket.application_state == WebSocketState.DISCONNECTED)
|
||||
self.websocket.client_state == WebSocketState.DISCONNECTED
|
||||
or self.websocket.application_state == WebSocketState.DISCONNECTED
|
||||
)
|
||||
else:
|
||||
return self.websocket.closed
|
||||
|
||||
|
@ -30,8 +30,7 @@ try:
|
||||
from quart import Quart, Request, Response
|
||||
from quart import Websocket as QuartWebSocket
|
||||
except ImportError:
|
||||
raise ValueError(
|
||||
'Please install Quart by using `pip install nonebot2[quart]`')
|
||||
raise ValueError("Please install Quart by using `pip install nonebot2[quart]`")
|
||||
|
||||
_AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine])
|
||||
|
||||
@ -40,6 +39,7 @@ class Config(BaseSettings):
|
||||
"""
|
||||
Quart 驱动框架设置
|
||||
"""
|
||||
|
||||
quart_reload: Optional[bool] = None
|
||||
"""
|
||||
:类型:
|
||||
@ -111,11 +111,12 @@ class Driver(ReverseDriver):
|
||||
self.quart_config = Config(**config.dict())
|
||||
|
||||
self._server_app = Quart(self.__class__.__qualname__)
|
||||
self._server_app.add_url_rule("/<adapter>/http",
|
||||
methods=["POST"],
|
||||
view_func=self._handle_http)
|
||||
self._server_app.add_websocket("/<adapter>/ws",
|
||||
view_func=self._handle_ws_reverse)
|
||||
self._server_app.add_url_rule(
|
||||
"/<adapter>/http", methods=["POST"], view_func=self._handle_http
|
||||
)
|
||||
self._server_app.add_websocket(
|
||||
"/<adapter>/ws", view_func=self._handle_ws_reverse
|
||||
)
|
||||
|
||||
@property
|
||||
@overrides(ReverseDriver)
|
||||
@ -156,12 +157,14 @@ class Driver(ReverseDriver):
|
||||
return self.server_app.after_serving(func) # type: ignore
|
||||
|
||||
@overrides(ReverseDriver)
|
||||
def run(self,
|
||||
def run(
|
||||
self,
|
||||
host: Optional[str] = None,
|
||||
port: Optional[int] = None,
|
||||
*,
|
||||
app: Optional[str] = None,
|
||||
**kwargs):
|
||||
**kwargs,
|
||||
):
|
||||
"""使用 ``uvicorn`` 启动 Quart"""
|
||||
super().run(host, port, app, **kwargs)
|
||||
LOGGING_CONFIG = {
|
||||
@ -173,10 +176,7 @@ class Driver(ReverseDriver):
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"uvicorn.error": {
|
||||
"handlers": ["default"],
|
||||
"level": "INFO"
|
||||
},
|
||||
"uvicorn.error": {"handlers": ["default"], "level": "INFO"},
|
||||
"uvicorn.access": {
|
||||
"handlers": ["default"],
|
||||
"level": "INFO",
|
||||
@ -188,52 +188,69 @@ class Driver(ReverseDriver):
|
||||
host=host or str(self.config.host),
|
||||
port=port or self.config.port,
|
||||
reload=self.quart_config.quart_reload
|
||||
if self.quart_config.quart_reload is not None else
|
||||
(bool(app) and self.config.debug),
|
||||
if self.quart_config.quart_reload is not None
|
||||
else (bool(app) and self.config.debug),
|
||||
reload_dirs=self.quart_config.quart_reload_dirs,
|
||||
reload_delay=self.quart_config.quart_reload_delay,
|
||||
reload_includes=self.quart_config.quart_reload_includes,
|
||||
reload_excludes=self.quart_config.quart_reload_excludes,
|
||||
debug=self.config.debug,
|
||||
log_config=LOGGING_CONFIG,
|
||||
**kwargs)
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
async def _handle_http(self, adapter: str):
|
||||
request: Request = _request
|
||||
data: bytes = await request.get_data() # type: ignore
|
||||
|
||||
if adapter not in self._adapters:
|
||||
logger.warning(f'Unknown adapter {adapter}. '
|
||||
'Please register the adapter before use.')
|
||||
logger.warning(
|
||||
f"Unknown adapter {adapter}. " "Please register the adapter before use."
|
||||
)
|
||||
raise exceptions.NotFound()
|
||||
|
||||
BotClass = self._adapters[adapter]
|
||||
http_request = HTTPRequest(request.http_version, request.scheme,
|
||||
request.path, request.query_string,
|
||||
dict(request.headers), request.method, data)
|
||||
http_request = HTTPRequest(
|
||||
request.http_version,
|
||||
request.scheme,
|
||||
request.path,
|
||||
request.query_string,
|
||||
dict(request.headers),
|
||||
request.method,
|
||||
data,
|
||||
)
|
||||
|
||||
self_id, response = await BotClass.check_permission(self, http_request)
|
||||
|
||||
if not self_id:
|
||||
raise exceptions.Unauthorized(
|
||||
description=(response and response.body or b"").decode())
|
||||
description=(response and response.body or b"").decode()
|
||||
)
|
||||
if self_id in self._clients:
|
||||
logger.warning("There's already a reverse websocket connection,"
|
||||
"so the event may be handled twice.")
|
||||
logger.warning(
|
||||
"There's already a reverse websocket connection,"
|
||||
"so the event may be handled twice."
|
||||
)
|
||||
bot = BotClass(self_id, http_request)
|
||||
asyncio.create_task(bot.handle_message(data))
|
||||
return Response(response and response.body or "",
|
||||
response and response.status or 200)
|
||||
return Response(
|
||||
response and response.body or "", response and response.status or 200
|
||||
)
|
||||
|
||||
async def _handle_ws_reverse(self, adapter: str):
|
||||
websocket: QuartWebSocket = _websocket
|
||||
ws = WebSocket(websocket.http_version, websocket.scheme,
|
||||
websocket.path, websocket.query_string,
|
||||
dict(websocket.headers), websocket)
|
||||
ws = WebSocket(
|
||||
websocket.http_version,
|
||||
websocket.scheme,
|
||||
websocket.path,
|
||||
websocket.query_string,
|
||||
dict(websocket.headers),
|
||||
websocket,
|
||||
)
|
||||
|
||||
if adapter not in self._adapters:
|
||||
logger.warning(
|
||||
f'Unknown adapter {adapter}. Please register the adapter before use.'
|
||||
f"Unknown adapter {adapter}. Please register the adapter before use."
|
||||
)
|
||||
raise exceptions.NotFound()
|
||||
|
||||
@ -242,20 +259,22 @@ class Driver(ReverseDriver):
|
||||
|
||||
if not self_id:
|
||||
raise exceptions.Unauthorized(
|
||||
description=(response and response.body or b"").decode())
|
||||
description=(response and response.body or b"").decode()
|
||||
)
|
||||
|
||||
if self_id in self._clients:
|
||||
logger.opt(colors=True).warning(
|
||||
"There's already a websocket connection, "
|
||||
f"<y>{escape_tag(adapter.upper())} Bot {escape_tag(self_id)}</y> ignored."
|
||||
)
|
||||
raise exceptions.Forbidden(description='Client already exists.')
|
||||
raise exceptions.Forbidden(description="Client already exists.")
|
||||
|
||||
bot = BotClass(self_id, ws)
|
||||
await ws.accept()
|
||||
logger.opt(colors=True).info(
|
||||
f"WebSocket Connection from <y>{escape_tag(adapter.upper())} "
|
||||
f"Bot {escape_tag(self_id)}</y> Accepted!")
|
||||
f"Bot {escape_tag(self_id)}</y> Accepted!"
|
||||
)
|
||||
self._bot_connect(bot)
|
||||
|
||||
try:
|
||||
@ -267,7 +286,8 @@ class Driver(ReverseDriver):
|
||||
break
|
||||
except Exception as e:
|
||||
logger.opt(exception=e).error(
|
||||
"Error when receiving data from websocket.")
|
||||
"Error when receiving data from websocket."
|
||||
)
|
||||
break
|
||||
|
||||
asyncio.create_task(bot.handle_message(data.encode()))
|
||||
|
@ -157,6 +157,7 @@ class NoLogException(AdapterException):
|
||||
|
||||
指示 NoneBot 对当前 ``Event`` 进行处理但不显示 Log 信息,可在 ``get_log_string`` 时抛出
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -166,6 +167,7 @@ class ApiNotAvailable(AdapterException):
|
||||
|
||||
在 API 连接不可用时抛出。
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -175,6 +177,7 @@ class NetworkError(AdapterException):
|
||||
|
||||
在网络出现问题时抛出,如: API 请求地址不正确, API 请求无返回或返回状态非正常等。
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -184,4 +187,5 @@ class ActionFailed(AdapterException):
|
||||
|
||||
API 请求成功返回数据,但 API 操作失败。
|
||||
"""
|
||||
|
||||
pass
|
||||
|
@ -10,20 +10,27 @@ from contextlib import AsyncExitStack
|
||||
from typing import Any, Dict, List, Type, Callable, Optional
|
||||
|
||||
from nonebot.utils import get_name, run_sync
|
||||
from nonebot.dependencies import (Param, Dependent, DependsWrapper,
|
||||
get_dependent, solve_dependencies,
|
||||
get_parameterless_sub_dependant)
|
||||
from nonebot.dependencies import (
|
||||
Param,
|
||||
Dependent,
|
||||
DependsWrapper,
|
||||
get_dependent,
|
||||
solve_dependencies,
|
||||
get_parameterless_sub_dependant,
|
||||
)
|
||||
|
||||
|
||||
class Handler:
|
||||
"""事件处理器类。支持依赖注入。"""
|
||||
|
||||
def __init__(self,
|
||||
def __init__(
|
||||
self,
|
||||
func: Callable[..., Any],
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
dependencies: Optional[List[DependsWrapper]] = None,
|
||||
allow_types: Optional[List[Type[Param]]] = None):
|
||||
allow_types: Optional[List[Type[Param]]] = None,
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -64,19 +71,18 @@ class Handler:
|
||||
self.dependent = get_dependent(func=func, allow_types=self.allow_types)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Handler {self.name}({', '.join(map(str, self.dependent.params))})>"
|
||||
)
|
||||
return f"<Handler {self.name}({', '.join(map(str, self.dependent.params))})>"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
async def __call__(self,
|
||||
async def __call__(
|
||||
self,
|
||||
*,
|
||||
_stack: Optional[AsyncExitStack] = None,
|
||||
_dependency_cache: Optional[Dict[Callable[..., Any],
|
||||
Any]] = None,
|
||||
**params) -> Any:
|
||||
_dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None,
|
||||
**params,
|
||||
) -> Any:
|
||||
values, _ = await solve_dependencies(
|
||||
_dependent=self.dependent,
|
||||
_stack=_stack,
|
||||
@ -85,7 +91,8 @@ class Handler:
|
||||
for dependency in self.dependencies
|
||||
],
|
||||
_dependency_cache=_dependency_cache,
|
||||
**params)
|
||||
**params,
|
||||
)
|
||||
|
||||
if asyncio.iscoroutinefunction(self.func):
|
||||
return await self.func(**values)
|
||||
@ -98,7 +105,8 @@ class Handler:
|
||||
if dependency.dependency in self.sub_dependents:
|
||||
raise ValueError(f"{dependency} is already in dependencies")
|
||||
sub_dependant = get_parameterless_sub_dependant(
|
||||
depends=dependency, allow_types=self.allow_types)
|
||||
depends=dependency, allow_types=self.allow_types
|
||||
)
|
||||
self.sub_dependents[dependency.dependency] = sub_dependant
|
||||
|
||||
def prepend_dependency(self, dependency: DependsWrapper):
|
||||
|
@ -48,7 +48,6 @@ logger: "Logger" = loguru.logger
|
||||
|
||||
|
||||
class Filter:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.level: Union[int, str] = "DEBUG"
|
||||
|
||||
@ -58,13 +57,13 @@ class Filter:
|
||||
if module:
|
||||
module_name = getattr(module, "__module_name__", module_name)
|
||||
record["name"] = module_name.split(".")[0]
|
||||
levelno = logger.level(self.level).no if isinstance(self.level,
|
||||
str) else self.level
|
||||
levelno = (
|
||||
logger.level(self.level).no if isinstance(self.level, str) else self.level
|
||||
)
|
||||
return record["level"].no >= levelno
|
||||
|
||||
|
||||
class LoguruHandler(logging.Handler):
|
||||
|
||||
def emit(self, record):
|
||||
try:
|
||||
level = logger.level(record.levelname).name
|
||||
@ -76,8 +75,9 @@ class LoguruHandler(logging.Handler):
|
||||
frame = frame.f_back
|
||||
depth += 1
|
||||
|
||||
logger.opt(depth=depth,
|
||||
exception=record.exc_info).log(level, record.getMessage())
|
||||
logger.opt(depth=depth, exception=record.exc_info).log(
|
||||
level, record.getMessage()
|
||||
)
|
||||
|
||||
|
||||
logger.remove()
|
||||
@ -87,9 +87,12 @@ default_format = (
|
||||
"[<lvl>{level}</lvl>] "
|
||||
"<c><u>{name}</u></c> | "
|
||||
# "<c>{function}:{line}</c>| "
|
||||
"{message}")
|
||||
logger_id = logger.add(sys.stdout,
|
||||
"{message}"
|
||||
)
|
||||
logger_id = logger.add(
|
||||
sys.stdout,
|
||||
colorize=True,
|
||||
diagnose=False,
|
||||
filter=default_filter,
|
||||
format=default_format)
|
||||
format=default_format,
|
||||
)
|
||||
|
@ -10,8 +10,17 @@ from datetime import datetime
|
||||
from contextvars import ContextVar
|
||||
from collections import defaultdict
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import (TYPE_CHECKING, Any, Dict, List, Type, Union, Callable,
|
||||
NoReturn, Optional)
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Union,
|
||||
Callable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
)
|
||||
|
||||
from nonebot import params
|
||||
from nonebot.rule import Rule
|
||||
@ -19,14 +28,29 @@ from nonebot.log import logger
|
||||
from nonebot.handler import Handler
|
||||
from nonebot.dependencies import DependsWrapper
|
||||
from nonebot.permission import USER, Permission
|
||||
from nonebot.adapters import (Bot, Event, Message, MessageSegment,
|
||||
MessageTemplate)
|
||||
from nonebot.exception import (PausedException, StopPropagation,
|
||||
SkippedException, FinishedException,
|
||||
RejectedException)
|
||||
from nonebot.typing import (T_State, T_Handler, T_ArgsParser, T_TypeUpdater,
|
||||
T_StateFactory, T_DependencyCache,
|
||||
T_PermissionUpdater)
|
||||
from nonebot.adapters import (
|
||||
Bot,
|
||||
Event,
|
||||
Message,
|
||||
MessageSegment,
|
||||
MessageTemplate,
|
||||
)
|
||||
from nonebot.exception import (
|
||||
PausedException,
|
||||
StopPropagation,
|
||||
SkippedException,
|
||||
FinishedException,
|
||||
RejectedException,
|
||||
)
|
||||
from nonebot.typing import (
|
||||
T_State,
|
||||
T_Handler,
|
||||
T_ArgsParser,
|
||||
T_TypeUpdater,
|
||||
T_StateFactory,
|
||||
T_DependencyCache,
|
||||
T_PermissionUpdater,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.plugin import Plugin
|
||||
@ -57,9 +81,11 @@ class MatcherMeta(type):
|
||||
expire_time: Optional[datetime]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (f"<Matcher from {self.module_name or 'unknown'}, "
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, "
|
||||
f"type={self.type}, priority={self.priority}, "
|
||||
f"temp={self.temp}>")
|
||||
f"temp={self.temp}>"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
@ -67,6 +93,7 @@ class MatcherMeta(type):
|
||||
|
||||
class Matcher(metaclass=MatcherMeta):
|
||||
"""事件响应器类"""
|
||||
|
||||
plugin: Optional["Plugin"] = None
|
||||
"""
|
||||
:类型: ``Optional[Plugin]``
|
||||
@ -157,8 +184,11 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"""
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
params.BotParam, params.EventParam, params.StateParam,
|
||||
params.MatcherParam, params.DefaultParam
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.MatcherParam,
|
||||
params.DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
@ -169,7 +199,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<Matcher from {self.module_name or 'unknown'}, type={self.type}, "
|
||||
f"priority={self.priority}, temp={self.temp}>")
|
||||
f"priority={self.priority}, temp={self.temp}>"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
@ -180,8 +211,9 @@ class Matcher(metaclass=MatcherMeta):
|
||||
type_: str = "",
|
||||
rule: Optional[Rule] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
handlers: Optional[Union[List[T_Handler], List[Handler],
|
||||
List[Union[T_Handler, Handler]]]] = None,
|
||||
handlers: Optional[
|
||||
Union[List[T_Handler], List[Handler], List[Union[T_Handler, Handler]]]
|
||||
] = None,
|
||||
temp: bool = False,
|
||||
priority: int = 1,
|
||||
block: bool = False,
|
||||
@ -193,7 +225,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
default_state_factory: Optional[T_StateFactory] = None,
|
||||
default_parser: Optional[T_ArgsParser] = None,
|
||||
default_type_updater: Optional[T_TypeUpdater] = None,
|
||||
default_permission_updater: Optional[T_PermissionUpdater] = None
|
||||
default_permission_updater: Optional[T_PermissionUpdater] = None,
|
||||
) -> Type["Matcher"]:
|
||||
"""
|
||||
:说明:
|
||||
@ -221,46 +253,37 @@ class Matcher(metaclass=MatcherMeta):
|
||||
"""
|
||||
|
||||
NewMatcher = type(
|
||||
"Matcher", (Matcher,), {
|
||||
"plugin":
|
||||
plugin,
|
||||
"module":
|
||||
module,
|
||||
"plugin_name":
|
||||
plugin and plugin.name,
|
||||
"module_name":
|
||||
module and module.__name__,
|
||||
"type":
|
||||
type_,
|
||||
"rule":
|
||||
rule or Rule(),
|
||||
"permission":
|
||||
permission or Permission(),
|
||||
"Matcher",
|
||||
(Matcher,),
|
||||
{
|
||||
"plugin": plugin,
|
||||
"module": module,
|
||||
"plugin_name": plugin and plugin.name,
|
||||
"module_name": module and module.__name__,
|
||||
"type": type_,
|
||||
"rule": rule or Rule(),
|
||||
"permission": permission or Permission(),
|
||||
"handlers": [
|
||||
handler if isinstance(handler, Handler) else Handler(
|
||||
handler, allow_types=cls.HANDLER_PARAM_TYPES)
|
||||
handler
|
||||
if isinstance(handler, Handler)
|
||||
else Handler(handler, allow_types=cls.HANDLER_PARAM_TYPES)
|
||||
for handler in handlers
|
||||
] if handlers else [],
|
||||
"temp":
|
||||
temp,
|
||||
"expire_time":
|
||||
expire_time,
|
||||
"priority":
|
||||
priority,
|
||||
"block":
|
||||
block,
|
||||
"_default_state":
|
||||
default_state or {},
|
||||
"_default_state_factory":
|
||||
staticmethod(default_state_factory)
|
||||
if default_state_factory else None,
|
||||
"_default_parser":
|
||||
default_parser,
|
||||
"_default_type_updater":
|
||||
default_type_updater,
|
||||
"_default_permission_updater":
|
||||
default_permission_updater
|
||||
})
|
||||
]
|
||||
if handlers
|
||||
else [],
|
||||
"temp": temp,
|
||||
"expire_time": expire_time,
|
||||
"priority": priority,
|
||||
"block": block,
|
||||
"_default_state": default_state or {},
|
||||
"_default_state_factory": staticmethod(default_state_factory)
|
||||
if default_state_factory
|
||||
else None,
|
||||
"_default_parser": default_parser,
|
||||
"_default_type_updater": default_type_updater,
|
||||
"_default_permission_updater": default_permission_updater,
|
||||
},
|
||||
)
|
||||
|
||||
matchers[priority].append(NewMatcher)
|
||||
|
||||
@ -272,8 +295,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[Dict[Callable[..., Any],
|
||||
Any]] = None) -> bool:
|
||||
dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -289,8 +312,9 @@ class Matcher(metaclass=MatcherMeta):
|
||||
- ``bool``: 是否满足权限
|
||||
"""
|
||||
event_type = event.get_type()
|
||||
return (event_type == (cls.type or event_type) and
|
||||
await cls.permission(bot, event, stack, dependency_cache))
|
||||
return event_type == (cls.type or event_type) and await cls.permission(
|
||||
bot, event, stack, dependency_cache
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def check_rule(
|
||||
@ -299,8 +323,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[Dict[Callable[..., Any],
|
||||
Any]] = None) -> bool:
|
||||
dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -317,8 +341,9 @@ class Matcher(metaclass=MatcherMeta):
|
||||
- ``bool``: 是否满足匹配规则
|
||||
"""
|
||||
event_type = event.get_type()
|
||||
return (event_type == (cls.type or event_type) and
|
||||
await cls.rule(bot, event, state, stack, dependency_cache))
|
||||
return event_type == (cls.type or event_type) and await cls.rule(
|
||||
bot, event, state, stack, dependency_cache
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def args_parser(cls, func: T_ArgsParser) -> T_ArgsParser:
|
||||
@ -349,8 +374,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
return func
|
||||
|
||||
@classmethod
|
||||
def permission_updater(cls,
|
||||
func: T_PermissionUpdater) -> T_PermissionUpdater:
|
||||
def permission_updater(cls, func: T_PermissionUpdater) -> T_PermissionUpdater:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -365,12 +389,11 @@ class Matcher(metaclass=MatcherMeta):
|
||||
|
||||
@classmethod
|
||||
def append_handler(
|
||||
cls,
|
||||
handler: T_Handler,
|
||||
dependencies: Optional[List[DependsWrapper]] = None) -> Handler:
|
||||
handler_ = Handler(handler,
|
||||
dependencies=dependencies,
|
||||
allow_types=cls.HANDLER_PARAM_TYPES)
|
||||
cls, handler: T_Handler, dependencies: Optional[List[DependsWrapper]] = None
|
||||
) -> Handler:
|
||||
handler_ = Handler(
|
||||
handler, dependencies=dependencies, allow_types=cls.HANDLER_PARAM_TYPES
|
||||
)
|
||||
cls.handlers.append(handler_)
|
||||
return handler_
|
||||
|
||||
@ -418,8 +441,7 @@ class Matcher(metaclass=MatcherMeta):
|
||||
func_handler = cls.handlers[-1]
|
||||
func_handler.prepend_dependency(depend)
|
||||
else:
|
||||
cls.append_handler(
|
||||
func, dependencies=[depend] if cls.handlers else [])
|
||||
cls.append_handler(func, dependencies=[depend] if cls.handlers else [])
|
||||
|
||||
return func
|
||||
|
||||
@ -429,9 +451,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
def got(
|
||||
cls,
|
||||
key: str,
|
||||
prompt: Optional[Union[str, Message, MessageSegment,
|
||||
MessageTemplate]] = None,
|
||||
args_parser: Optional[T_ArgsParser] = None
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
args_parser: Optional[T_ArgsParser] = None,
|
||||
) -> Callable[[T_Handler], T_Handler]:
|
||||
"""
|
||||
:说明:
|
||||
@ -483,16 +504,16 @@ class Matcher(metaclass=MatcherMeta):
|
||||
func_handler.prepend_dependency(parser_depend)
|
||||
func_handler.prepend_dependency(get_depend)
|
||||
else:
|
||||
cls.append_handler(func,
|
||||
dependencies=[get_depend, parser_depend])
|
||||
cls.append_handler(func, dependencies=[get_depend, parser_depend])
|
||||
|
||||
return func
|
||||
|
||||
return _decorator
|
||||
|
||||
@classmethod
|
||||
async def send(cls, message: Union[str, Message, MessageSegment,
|
||||
MessageTemplate], **kwargs) -> Any:
|
||||
async def send(
|
||||
cls, message: Union[str, Message, MessageSegment, MessageTemplate], **kwargs
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -513,10 +534,11 @@ class Matcher(metaclass=MatcherMeta):
|
||||
return await bot.send(event=event, message=_message, **kwargs)
|
||||
|
||||
@classmethod
|
||||
async def finish(cls,
|
||||
message: Optional[Union[str, Message, MessageSegment,
|
||||
MessageTemplate]] = None,
|
||||
**kwargs) -> NoReturn:
|
||||
async def finish(
|
||||
cls,
|
||||
message: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -539,10 +561,11 @@ class Matcher(metaclass=MatcherMeta):
|
||||
raise FinishedException
|
||||
|
||||
@classmethod
|
||||
async def pause(cls,
|
||||
prompt: Optional[Union[str, Message, MessageSegment,
|
||||
MessageTemplate]] = None,
|
||||
**kwargs) -> NoReturn:
|
||||
async def pause(
|
||||
cls,
|
||||
prompt: Optional[Union[str, Message, MessageSegment, MessageTemplate]] = None,
|
||||
**kwargs,
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -565,10 +588,9 @@ class Matcher(metaclass=MatcherMeta):
|
||||
raise PausedException
|
||||
|
||||
@classmethod
|
||||
async def reject(cls,
|
||||
prompt: Optional[Union[str, Message,
|
||||
MessageSegment]] = None,
|
||||
**kwargs) -> NoReturn:
|
||||
async def reject(
|
||||
cls, prompt: Optional[Union[str, Message, MessageSegment]] = None, **kwargs
|
||||
) -> NoReturn:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -601,31 +623,38 @@ class Matcher(metaclass=MatcherMeta):
|
||||
self.block = True
|
||||
|
||||
# 运行handlers
|
||||
async def run(self,
|
||||
async def run(
|
||||
self,
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None):
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
):
|
||||
b_t = current_bot.set(bot)
|
||||
e_t = current_event.set(event)
|
||||
s_t = current_state.set(self.state)
|
||||
try:
|
||||
# Refresh preprocess state
|
||||
self.state = await self._default_state_factory(
|
||||
bot, event) if self._default_state_factory else self.state
|
||||
self.state = (
|
||||
await self._default_state_factory(bot, event)
|
||||
if self._default_state_factory
|
||||
else self.state
|
||||
)
|
||||
self.state.update(state)
|
||||
|
||||
while self.handlers:
|
||||
handler = self.handlers.pop(0)
|
||||
logger.debug(f"Running handler {handler}")
|
||||
try:
|
||||
await handler(matcher=self,
|
||||
await handler(
|
||||
matcher=self,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=self.state,
|
||||
_stack=stack,
|
||||
_dependency_cache=dependency_cache)
|
||||
_dependency_cache=dependency_cache,
|
||||
)
|
||||
except SkippedException:
|
||||
pass
|
||||
|
||||
@ -633,18 +662,13 @@ class Matcher(metaclass=MatcherMeta):
|
||||
self.handlers.insert(0, handler) # type: ignore
|
||||
updater = self.__class__._default_type_updater
|
||||
if updater:
|
||||
type_ = await updater(
|
||||
bot,
|
||||
event,
|
||||
self.state, # type: ignore
|
||||
self.type)
|
||||
type_ = await updater(bot, event, self.state, self.type) # type: ignore
|
||||
else:
|
||||
type_ = "message"
|
||||
|
||||
updater = self.__class__._default_permission_updater
|
||||
if updater:
|
||||
permission = await updater(bot, event, self.state,
|
||||
self.permission)
|
||||
permission = await updater(bot, event, self.state, self.permission)
|
||||
else:
|
||||
permission = USER(event.get_session_id(), perm=self.permission)
|
||||
|
||||
@ -662,23 +686,18 @@ class Matcher(metaclass=MatcherMeta):
|
||||
default_state=self.state,
|
||||
default_parser=self.__class__._default_parser,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
default_permission_updater=self.__class__.
|
||||
_default_permission_updater)
|
||||
default_permission_updater=self.__class__._default_permission_updater,
|
||||
)
|
||||
except PausedException:
|
||||
updater = self.__class__._default_type_updater
|
||||
if updater:
|
||||
type_ = await updater(
|
||||
bot,
|
||||
event,
|
||||
self.state, # type: ignore
|
||||
self.type)
|
||||
type_ = await updater(bot, event, self.state, self.type) # type: ignore
|
||||
else:
|
||||
type_ = "message"
|
||||
|
||||
updater = self.__class__._default_permission_updater
|
||||
if updater:
|
||||
permission = await updater(bot, event, self.state,
|
||||
self.permission)
|
||||
permission = await updater(bot, event, self.state, self.permission)
|
||||
else:
|
||||
permission = USER(event.get_session_id(), perm=self.permission)
|
||||
|
||||
@ -696,8 +715,8 @@ class Matcher(metaclass=MatcherMeta):
|
||||
default_state=self.state,
|
||||
default_parser=self.__class__._default_parser,
|
||||
default_type_updater=self.__class__._default_type_updater,
|
||||
default_permission_updater=self.__class__.
|
||||
_default_permission_updater)
|
||||
default_permission_updater=self.__class__._default_permission_updater,
|
||||
)
|
||||
except FinishedException:
|
||||
pass
|
||||
except StopPropagation:
|
||||
|
@ -17,9 +17,14 @@ from nonebot.handler import Handler
|
||||
from nonebot.utils import escape_tag
|
||||
from nonebot.matcher import Matcher, matchers
|
||||
from nonebot.exception import NoLogException, StopPropagation, IgnoredException
|
||||
from nonebot.typing import (T_State, T_DependencyCache, T_RunPreProcessor,
|
||||
T_RunPostProcessor, T_EventPreProcessor,
|
||||
T_EventPostProcessor)
|
||||
from nonebot.typing import (
|
||||
T_State,
|
||||
T_DependencyCache,
|
||||
T_RunPreProcessor,
|
||||
T_RunPostProcessor,
|
||||
T_EventPreProcessor,
|
||||
T_EventPostProcessor,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot, Event
|
||||
@ -30,15 +35,25 @@ _run_preprocessors: Set[Handler] = set()
|
||||
_run_postprocessors: Set[Handler] = set()
|
||||
|
||||
EVENT_PCS_PARAMS = [
|
||||
params.BotParam, params.EventParam, params.StateParam, params.DefaultParam
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.DefaultParam,
|
||||
]
|
||||
RUN_PREPCS_PARAMS = [
|
||||
params.MatcherParam, params.BotParam, params.EventParam, params.StateParam,
|
||||
params.DefaultParam
|
||||
params.MatcherParam,
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.DefaultParam,
|
||||
]
|
||||
RUN_POSTPCS_PARAMS = [
|
||||
params.MatcherParam, params.ExceptionParam, params.BotParam,
|
||||
params.EventParam, params.StateParam, params.DefaultParam
|
||||
params.MatcherParam,
|
||||
params.ExceptionParam,
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.DefaultParam,
|
||||
]
|
||||
|
||||
|
||||
@ -89,7 +104,8 @@ async def _check_matcher(
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None) -> None:
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
if Matcher.expire_time and datetime.now() > Matcher.expire_time:
|
||||
try:
|
||||
matchers[priority].remove(Matcher)
|
||||
@ -99,13 +115,13 @@ async def _check_matcher(
|
||||
|
||||
try:
|
||||
if not await Matcher.check_perm(
|
||||
bot, event, stack,
|
||||
dependency_cache) or not await Matcher.check_rule(
|
||||
bot, event, state, stack, dependency_cache):
|
||||
bot, event, stack, dependency_cache
|
||||
) or not await Matcher.check_rule(bot, event, state, stack, dependency_cache):
|
||||
return
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>")
|
||||
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>"
|
||||
)
|
||||
return
|
||||
|
||||
if Matcher.temp:
|
||||
@ -123,31 +139,38 @@ async def _run_matcher(
|
||||
event: "Event",
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[T_DependencyCache] = None) -> None:
|
||||
dependency_cache: Optional[T_DependencyCache] = None,
|
||||
) -> None:
|
||||
logger.info(f"Event will be handled by {Matcher}")
|
||||
|
||||
matcher = Matcher()
|
||||
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: x(matcher=matcher,
|
||||
lambda x: x(
|
||||
matcher=matcher,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
_stack=stack,
|
||||
_dependency_cache=dependency_cache),
|
||||
_run_preprocessors))
|
||||
_dependency_cache=dependency_cache,
|
||||
),
|
||||
_run_preprocessors,
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
except IgnoredException:
|
||||
logger.opt(colors=True).info(
|
||||
f"Matcher {matcher} running is <b>cancelled</b>")
|
||||
f"Matcher {matcher} running is <b>cancelled</b>"
|
||||
)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running RunPreProcessors. "
|
||||
"Running cancelled!</bg #f8bbd0></r>")
|
||||
"Running cancelled!</bg #f8bbd0></r>"
|
||||
)
|
||||
return
|
||||
|
||||
exception = None
|
||||
@ -163,14 +186,18 @@ async def _run_matcher(
|
||||
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: x(matcher=matcher,
|
||||
lambda x: x(
|
||||
matcher=matcher,
|
||||
exception=exception,
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
_stack=stack,
|
||||
_dependency_cache=dependency_cache),
|
||||
_run_postprocessors))
|
||||
_dependency_cache=dependency_cache,
|
||||
),
|
||||
_run_postprocessors,
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
try:
|
||||
await asyncio.gather(*coros)
|
||||
@ -217,12 +244,16 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
async with AsyncExitStack() as stack:
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: x(bot=bot,
|
||||
lambda x: x(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
_stack=stack,
|
||||
_dependency_cache=dependency_cache),
|
||||
_event_preprocessors))
|
||||
_dependency_cache=dependency_cache,
|
||||
),
|
||||
_event_preprocessors,
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
try:
|
||||
if show_log:
|
||||
@ -236,7 +267,8 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
"<r><bg #f8bbd0>Error when running EventPreProcessors. "
|
||||
"Event ignored!</bg #f8bbd0></r>")
|
||||
"Event ignored!</bg #f8bbd0></r>"
|
||||
)
|
||||
return
|
||||
|
||||
# Trie Match
|
||||
@ -251,13 +283,13 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
logger.debug(f"Checking for matchers in priority {priority}...")
|
||||
|
||||
pending_tasks = [
|
||||
_check_matcher(priority, matcher, bot, event, state.copy(),
|
||||
stack, dependency_cache)
|
||||
_check_matcher(
|
||||
priority, matcher, bot, event, state.copy(), stack, dependency_cache
|
||||
)
|
||||
for matcher in matchers[priority]
|
||||
]
|
||||
|
||||
results = await asyncio.gather(*pending_tasks,
|
||||
return_exceptions=True)
|
||||
results = await asyncio.gather(*pending_tasks, return_exceptions=True)
|
||||
|
||||
for result in results:
|
||||
if not isinstance(result, Exception):
|
||||
@ -272,12 +304,16 @@ async def handle_event(bot: "Bot", event: "Event") -> None:
|
||||
|
||||
coros = list(
|
||||
map(
|
||||
lambda x: x(bot=bot,
|
||||
lambda x: x(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
_stack=stack,
|
||||
_dependency_cache=dependency_cache),
|
||||
_event_postprocessors))
|
||||
_dependency_cache=dependency_cache,
|
||||
),
|
||||
_event_postprocessors,
|
||||
)
|
||||
)
|
||||
if coros:
|
||||
try:
|
||||
if show_log:
|
||||
|
@ -10,69 +10,61 @@ from nonebot.utils import generic_check_issubclass
|
||||
|
||||
|
||||
class BotParam(Param):
|
||||
|
||||
@classmethod
|
||||
def _check(cls, name: str, param: inspect.Parameter) -> bool:
|
||||
return generic_check_issubclass(
|
||||
param.annotation, Bot) or (param.annotation == param.empty and
|
||||
name == "bot")
|
||||
return generic_check_issubclass(param.annotation, Bot) or (
|
||||
param.annotation == param.empty and name == "bot"
|
||||
)
|
||||
|
||||
def _solve(self, bot: Bot, **kwargs: Any) -> Any:
|
||||
return bot
|
||||
|
||||
|
||||
class EventParam(Param):
|
||||
|
||||
@classmethod
|
||||
def _check(cls, name: str, param: inspect.Parameter) -> bool:
|
||||
return generic_check_issubclass(
|
||||
param.annotation, Event) or (param.annotation == param.empty and
|
||||
name == "event")
|
||||
return generic_check_issubclass(param.annotation, Event) or (
|
||||
param.annotation == param.empty and name == "event"
|
||||
)
|
||||
|
||||
def _solve(self, event: Event, **kwargs: Any) -> Any:
|
||||
return event
|
||||
|
||||
|
||||
class StateParam(Param):
|
||||
|
||||
@classmethod
|
||||
def _check(cls, name: str, param: inspect.Parameter) -> bool:
|
||||
return generic_check_issubclass(
|
||||
param.annotation, Dict) or (param.annotation == param.empty and
|
||||
name == "state")
|
||||
return generic_check_issubclass(param.annotation, Dict) or (
|
||||
param.annotation == param.empty and name == "state"
|
||||
)
|
||||
|
||||
def _solve(self, state: T_State, **kwargs: Any) -> Any:
|
||||
return state
|
||||
|
||||
|
||||
class MatcherParam(Param):
|
||||
|
||||
@classmethod
|
||||
def _check(cls, name: str, param: inspect.Parameter) -> bool:
|
||||
return generic_check_issubclass(
|
||||
param.annotation, Matcher) or (param.annotation == param.empty and
|
||||
name == "matcher")
|
||||
return generic_check_issubclass(param.annotation, Matcher) or (
|
||||
param.annotation == param.empty and name == "matcher"
|
||||
)
|
||||
|
||||
def _solve(self, matcher: Optional["Matcher"] = None, **kwargs: Any) -> Any:
|
||||
return matcher
|
||||
|
||||
|
||||
class ExceptionParam(Param):
|
||||
|
||||
@classmethod
|
||||
def _check(cls, name: str, param: inspect.Parameter) -> bool:
|
||||
return generic_check_issubclass(
|
||||
param.annotation, Exception) or (param.annotation == param.empty and
|
||||
name == "exception")
|
||||
return generic_check_issubclass(param.annotation, Exception) or (
|
||||
param.annotation == param.empty and name == "exception"
|
||||
)
|
||||
|
||||
def _solve(self,
|
||||
exception: Optional[Exception] = None,
|
||||
**kwargs: Any) -> Any:
|
||||
def _solve(self, exception: Optional[Exception] = None, **kwargs: Any) -> Any:
|
||||
return exception
|
||||
|
||||
|
||||
class DefaultParam(Param):
|
||||
|
||||
@classmethod
|
||||
def _check(cls, name: str, param: inspect.Parameter) -> bool:
|
||||
return param.default != param.empty
|
||||
|
@ -34,11 +34,10 @@ class Permission:
|
||||
from nonebot.utils import run_sync
|
||||
Permission(async_function, run_sync(sync_function))
|
||||
"""
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
params.BotParam, params.EventParam, params.DefaultParam
|
||||
]
|
||||
HANDLER_PARAM_TYPES = [params.BotParam, params.EventParam, params.DefaultParam]
|
||||
|
||||
def __init__(self, *checkers: Union[T_PermissionChecker, Handler]) -> None:
|
||||
"""
|
||||
@ -48,9 +47,11 @@ class Permission:
|
||||
"""
|
||||
|
||||
self.checkers = set(
|
||||
checker if isinstance(checker, Handler) else Handler(
|
||||
checker, allow_types=self.HANDLER_PARAM_TYPES)
|
||||
for checker in checkers)
|
||||
checker
|
||||
if isinstance(checker, Handler)
|
||||
else Handler(checker, allow_types=self.HANDLER_PARAM_TYPES)
|
||||
for checker in checkers
|
||||
)
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -66,8 +67,8 @@ class Permission:
|
||||
bot: Bot,
|
||||
event: Event,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[Dict[Callable[..., Any],
|
||||
Any]] = None) -> bool:
|
||||
dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -87,19 +88,24 @@ class Permission:
|
||||
if not self.checkers:
|
||||
return True
|
||||
results = await asyncio.gather(
|
||||
*(checker(bot=bot,
|
||||
*(
|
||||
checker(
|
||||
bot=bot,
|
||||
event=event,
|
||||
_stack=stack,
|
||||
_dependency_cache=dependency_cache)
|
||||
for checker in self.checkers))
|
||||
_dependency_cache=dependency_cache,
|
||||
)
|
||||
for checker in self.checkers
|
||||
)
|
||||
)
|
||||
return any(results)
|
||||
|
||||
def __and__(self, other) -> NoReturn:
|
||||
raise RuntimeError("And operation between Permissions is not allowed.")
|
||||
|
||||
def __or__(
|
||||
self, other: Optional[Union["Permission",
|
||||
T_PermissionChecker]]) -> "Permission":
|
||||
self, other: Optional[Union["Permission", T_PermissionChecker]]
|
||||
) -> "Permission":
|
||||
if other is None:
|
||||
return self
|
||||
elif isinstance(other, Permission):
|
||||
@ -155,15 +161,17 @@ def USER(*user: str, perm: Optional[Permission] = None):
|
||||
"""
|
||||
|
||||
async def _user(bot: Bot, event: Event) -> bool:
|
||||
return bool(event.get_session_id() in user and
|
||||
(perm is None or await perm(bot, event)))
|
||||
return bool(
|
||||
event.get_session_id() in user and (perm is None or await perm(bot, event))
|
||||
)
|
||||
|
||||
return Permission(_user)
|
||||
|
||||
|
||||
async def _superuser(bot: Bot, event: Event) -> bool:
|
||||
return (event.get_type() == "message" and
|
||||
event.get_user_id() in bot.config.superusers)
|
||||
return (
|
||||
event.get_type() == "message" and event.get_user_id() in bot.config.superusers
|
||||
)
|
||||
|
||||
|
||||
SUPERUSER = Permission(_superuser)
|
||||
|
@ -9,8 +9,9 @@ from typing import List, Optional
|
||||
from contextvars import ContextVar
|
||||
|
||||
_managers: List["PluginManager"] = []
|
||||
_current_plugin: ContextVar[Optional["Plugin"]] = ContextVar("_current_plugin",
|
||||
default=None)
|
||||
_current_plugin: ContextVar[Optional["Plugin"]] = ContextVar(
|
||||
"_current_plugin", default=None
|
||||
)
|
||||
|
||||
from .on import on as on
|
||||
from .manager import PluginManager
|
||||
|
@ -33,8 +33,7 @@ class Export(dict):
|
||||
return func
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
super().__setitem__(key,
|
||||
Export(value) if isinstance(value, dict) else value)
|
||||
super().__setitem__(key, Export(value) if isinstance(value, dict) else value)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
self[name] = Export(value) if isinstance(value, dict) else value
|
||||
|
@ -49,8 +49,9 @@ def load_plugins(*plugin_dir: str) -> Set[Plugin]:
|
||||
return manager.load_all_plugins()
|
||||
|
||||
|
||||
def load_all_plugins(module_path: Iterable[str],
|
||||
plugin_dir: Iterable[str]) -> Set[Plugin]:
|
||||
def load_all_plugins(
|
||||
module_path: Iterable[str], plugin_dir: Iterable[str]
|
||||
) -> Set[Plugin]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -90,8 +91,7 @@ def load_from_json(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
plugins = data.get("plugins")
|
||||
plugin_dirs = data.get("plugin_dirs")
|
||||
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
||||
assert isinstance(plugin_dirs,
|
||||
list), "plugin_dirs must be a list of directories"
|
||||
assert isinstance(plugin_dirs, list), "plugin_dirs must be a list of directories"
|
||||
return load_all_plugins(set(plugins), set(plugin_dirs))
|
||||
|
||||
|
||||
@ -120,14 +120,14 @@ def load_from_toml(file_path: str, encoding: str = "utf-8") -> Set[Plugin]:
|
||||
if nonebot_data:
|
||||
warnings.warn(
|
||||
"[nonebot.plugins] table are now deprecated. Use [tool.nonebot] instead.",
|
||||
DeprecationWarning)
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
raise ValueError("Cannot find '[tool.nonebot]' in given toml file!")
|
||||
plugins = nonebot_data.get("plugins", [])
|
||||
plugin_dirs = nonebot_data.get("plugin_dirs", [])
|
||||
assert isinstance(plugins, list), "plugins must be a list of plugin name"
|
||||
assert isinstance(plugin_dirs,
|
||||
list), "plugin_dirs must be a list of directories"
|
||||
assert isinstance(plugin_dirs, list), "plugin_dirs must be a list of directories"
|
||||
return load_all_plugins(plugins, plugin_dirs)
|
||||
|
||||
|
||||
@ -163,5 +163,5 @@ def require(name: str) -> Export:
|
||||
"""
|
||||
plugin = get_plugin(name) or load_plugin(name)
|
||||
if not plugin:
|
||||
raise RuntimeError(f"Cannot load plugin \"{name}\"!")
|
||||
raise RuntimeError(f'Cannot load plugin "{name}"!')
|
||||
return plugin.export
|
||||
|
@ -15,7 +15,6 @@ from . import _managers, _current_plugin
|
||||
|
||||
|
||||
class PluginManager:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
plugins: Optional[Iterable[str]] = None,
|
||||
@ -46,7 +45,8 @@ class PluginManager:
|
||||
return [
|
||||
*chain.from_iterable(
|
||||
[*manager.plugins, *manager.searched_plugins.keys()]
|
||||
for manager in _pre_managers)
|
||||
for manager in _pre_managers
|
||||
)
|
||||
]
|
||||
|
||||
def list_plugins(self) -> Set[str]:
|
||||
@ -57,13 +57,14 @@ class PluginManager:
|
||||
for module_info in pkgutil.iter_modules(self.search_path):
|
||||
if module_info.name.startswith("_"):
|
||||
continue
|
||||
if module_info.name in searched_plugins.keys(
|
||||
) or module_info.name in previous_plugins:
|
||||
if (
|
||||
module_info.name in searched_plugins.keys()
|
||||
or module_info.name in previous_plugins
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Plugin already exists: {module_info.name}! Check your plugin name"
|
||||
)
|
||||
module_spec = module_info.module_finder.find_spec(
|
||||
module_info.name, None)
|
||||
module_spec = module_info.module_finder.find_spec(module_info.name, None)
|
||||
if not module_spec:
|
||||
continue
|
||||
module_path = module_spec.origin
|
||||
@ -80,14 +81,15 @@ class PluginManager:
|
||||
if name in self.plugins:
|
||||
module = importlib.import_module(name)
|
||||
elif name not in self.searched_plugins:
|
||||
raise RuntimeError(
|
||||
f"Plugin not found: {name}! Check your plugin name")
|
||||
raise RuntimeError(f"Plugin not found: {name}! Check your plugin name")
|
||||
else:
|
||||
module = importlib.import_module(
|
||||
self._path_to_module_name(self.searched_plugins[name]))
|
||||
self._path_to_module_name(self.searched_plugins[name])
|
||||
)
|
||||
|
||||
logger.opt(colors=True).success(
|
||||
f'Succeeded to import "<y>{escape_tag(name)}</y>"')
|
||||
f'Succeeded to import "<y>{escape_tag(name)}</y>"'
|
||||
)
|
||||
return getattr(module, "__plugin__", None)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
@ -96,16 +98,17 @@ class PluginManager:
|
||||
|
||||
def load_all_plugins(self) -> Set[Plugin]:
|
||||
return set(
|
||||
filter(None,
|
||||
(self.load_plugin(name) for name in self.list_plugins())))
|
||||
filter(None, (self.load_plugin(name) for name in self.list_plugins()))
|
||||
)
|
||||
|
||||
|
||||
class PluginFinder(MetaPathFinder):
|
||||
|
||||
def find_spec(self,
|
||||
def find_spec(
|
||||
self,
|
||||
fullname: str,
|
||||
path: Optional[Sequence[Union[bytes, str]]],
|
||||
target: Optional[ModuleType] = None):
|
||||
target: Optional[ModuleType] = None,
|
||||
):
|
||||
if _managers:
|
||||
index = -1
|
||||
module_spec = PathFinder.find_spec(fullname, path, target)
|
||||
@ -119,10 +122,11 @@ class PluginFinder(MetaPathFinder):
|
||||
while -index <= len(_managers):
|
||||
manager = _managers[index]
|
||||
|
||||
if fullname in manager.plugins or module_path in manager.searched_plugins.values(
|
||||
if (
|
||||
fullname in manager.plugins
|
||||
or module_path in manager.searched_plugins.values()
|
||||
):
|
||||
module_spec.loader = PluginLoader(manager, fullname,
|
||||
module_origin)
|
||||
module_spec.loader = PluginLoader(manager, fullname, module_origin)
|
||||
return module_spec
|
||||
|
||||
index -= 1
|
||||
@ -130,7 +134,6 @@ class PluginFinder(MetaPathFinder):
|
||||
|
||||
|
||||
class PluginLoader(SourceFileLoader):
|
||||
|
||||
def __init__(self, manager: PluginManager, fullname: str, path) -> None:
|
||||
self.manager = manager
|
||||
self.loaded = False
|
||||
|
@ -10,8 +10,18 @@ from nonebot.matcher import Matcher
|
||||
from .manager import _current_plugin
|
||||
from nonebot.permission import Permission
|
||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory
|
||||
from nonebot.rule import (PREFIX_KEY, RAW_CMD_KEY, Rule, ArgumentParser, regex,
|
||||
command, keyword, endswith, startswith, shell_command)
|
||||
from nonebot.rule import (
|
||||
PREFIX_KEY,
|
||||
RAW_CMD_KEY,
|
||||
Rule,
|
||||
ArgumentParser,
|
||||
regex,
|
||||
command,
|
||||
keyword,
|
||||
endswith,
|
||||
startswith,
|
||||
shell_command,
|
||||
)
|
||||
|
||||
|
||||
def _store_matcher(matcher: Type[Matcher]) -> None:
|
||||
@ -30,7 +40,8 @@ def _get_matcher_module(depth: int = 1) -> Optional[ModuleType]:
|
||||
return sys.modules.get(module_name)
|
||||
|
||||
|
||||
def on(type: str = "",
|
||||
def on(
|
||||
type: str = "",
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
*,
|
||||
@ -40,7 +51,8 @@ def on(type: str = "",
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
state_factory: Optional[T_StateFactory] = None,
|
||||
_depth: int = 0) -> Type[Matcher]:
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -62,7 +74,8 @@ def on(type: str = "",
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
matcher = Matcher.new(type,
|
||||
matcher = Matcher.new(
|
||||
type,
|
||||
Rule() & rule,
|
||||
permission or Permission(),
|
||||
temp=temp,
|
||||
@ -72,12 +85,14 @@ def on(type: str = "",
|
||||
plugin=_current_plugin.get(),
|
||||
module=_get_matcher_module(_depth + 1),
|
||||
default_state=state,
|
||||
default_state_factory=state_factory)
|
||||
default_state_factory=state_factory,
|
||||
)
|
||||
_store_matcher(matcher)
|
||||
return matcher
|
||||
|
||||
|
||||
def on_metaevent(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
def on_metaevent(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
temp: bool = False,
|
||||
@ -85,7 +100,8 @@ def on_metaevent(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
state_factory: Optional[T_StateFactory] = None,
|
||||
_depth: int = 0) -> Type[Matcher]:
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -105,7 +121,8 @@ def on_metaevent(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
matcher = Matcher.new("meta_event",
|
||||
matcher = Matcher.new(
|
||||
"meta_event",
|
||||
Rule() & rule,
|
||||
Permission(),
|
||||
temp=temp,
|
||||
@ -115,12 +132,14 @@ def on_metaevent(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
plugin=_current_plugin.get(),
|
||||
module=_get_matcher_module(_depth + 1),
|
||||
default_state=state,
|
||||
default_state_factory=state_factory)
|
||||
default_state_factory=state_factory,
|
||||
)
|
||||
_store_matcher(matcher)
|
||||
return matcher
|
||||
|
||||
|
||||
def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
def on_message(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
permission: Optional[Permission] = None,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
@ -129,7 +148,8 @@ def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
block: bool = True,
|
||||
state: Optional[T_State] = None,
|
||||
state_factory: Optional[T_StateFactory] = None,
|
||||
_depth: int = 0) -> Type[Matcher]:
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -150,7 +170,8 @@ def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
matcher = Matcher.new("message",
|
||||
matcher = Matcher.new(
|
||||
"message",
|
||||
Rule() & rule,
|
||||
permission or Permission(),
|
||||
temp=temp,
|
||||
@ -160,12 +181,14 @@ def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
plugin=_current_plugin.get(),
|
||||
module=_get_matcher_module(_depth + 1),
|
||||
default_state=state,
|
||||
default_state_factory=state_factory)
|
||||
default_state_factory=state_factory,
|
||||
)
|
||||
_store_matcher(matcher)
|
||||
return matcher
|
||||
|
||||
|
||||
def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
def on_notice(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
temp: bool = False,
|
||||
@ -173,7 +196,8 @@ def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
state_factory: Optional[T_StateFactory] = None,
|
||||
_depth: int = 0) -> Type[Matcher]:
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -193,7 +217,8 @@ def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
matcher = Matcher.new("notice",
|
||||
matcher = Matcher.new(
|
||||
"notice",
|
||||
Rule() & rule,
|
||||
Permission(),
|
||||
temp=temp,
|
||||
@ -203,12 +228,14 @@ def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
plugin=_current_plugin.get(),
|
||||
module=_get_matcher_module(_depth + 1),
|
||||
default_state=state,
|
||||
default_state_factory=state_factory)
|
||||
default_state_factory=state_factory,
|
||||
)
|
||||
_store_matcher(matcher)
|
||||
return matcher
|
||||
|
||||
|
||||
def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
def on_request(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = None,
|
||||
temp: bool = False,
|
||||
@ -216,7 +243,8 @@ def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
block: bool = False,
|
||||
state: Optional[T_State] = None,
|
||||
state_factory: Optional[T_StateFactory] = None,
|
||||
_depth: int = 0) -> Type[Matcher]:
|
||||
_depth: int = 0,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -236,7 +264,8 @@ def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
matcher = Matcher.new("request",
|
||||
matcher = Matcher.new(
|
||||
"request",
|
||||
Rule() & rule,
|
||||
Permission(),
|
||||
temp=temp,
|
||||
@ -246,16 +275,19 @@ def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
plugin=_current_plugin.get(),
|
||||
module=_get_matcher_module(_depth + 1),
|
||||
default_state=state,
|
||||
default_state_factory=state_factory)
|
||||
default_state_factory=state_factory,
|
||||
)
|
||||
_store_matcher(matcher)
|
||||
return matcher
|
||||
|
||||
|
||||
def on_startswith(msg: Union[str, Tuple[str, ...]],
|
||||
def on_startswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -278,16 +310,16 @@ def on_startswith(msg: Union[str, Tuple[str, ...]],
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
return on_message(startswith(msg, ignorecase) & rule,
|
||||
**kwargs,
|
||||
_depth=_depth + 1)
|
||||
return on_message(startswith(msg, ignorecase) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_endswith(msg: Union[str, Tuple[str, ...]],
|
||||
def on_endswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = None,
|
||||
ignorecase: bool = False,
|
||||
_depth: int = 0,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -310,15 +342,15 @@ def on_endswith(msg: Union[str, Tuple[str, ...]],
|
||||
|
||||
- ``Type[Matcher]``
|
||||
"""
|
||||
return on_message(endswith(msg, ignorecase) & rule,
|
||||
**kwargs,
|
||||
_depth=_depth + 1)
|
||||
return on_message(endswith(msg, ignorecase) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_keyword(keywords: Set[str],
|
||||
def on_keyword(
|
||||
keywords: Set[str],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -343,11 +375,13 @@ def on_keyword(keywords: Set[str],
|
||||
return on_message(keyword(*keywords) & rule, **kwargs, _depth=_depth + 1)
|
||||
|
||||
|
||||
def on_command(cmd: Union[str, Tuple[str, ...]],
|
||||
def on_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -382,7 +416,8 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
|
||||
if not segment_text.startswith(state[PREFIX_KEY][RAW_CMD_KEY]):
|
||||
return
|
||||
new_message = message.__class__(
|
||||
segment_text[len(state[PREFIX_KEY][RAW_CMD_KEY]):].lstrip())
|
||||
segment_text[len(state[PREFIX_KEY][RAW_CMD_KEY]) :].lstrip()
|
||||
)
|
||||
for new_segment in reversed(new_message):
|
||||
message.insert(0, new_segment)
|
||||
|
||||
@ -390,18 +425,19 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
|
||||
handlers.insert(0, _strip_cmd)
|
||||
|
||||
commands = set([cmd]) | (aliases or set())
|
||||
return on_message(command(*commands) & rule,
|
||||
handlers=handlers,
|
||||
**kwargs,
|
||||
_depth=_depth + 1)
|
||||
return on_message(
|
||||
command(*commands) & rule, handlers=handlers, **kwargs, _depth=_depth + 1
|
||||
)
|
||||
|
||||
|
||||
def on_shell_command(cmd: Union[str, Tuple[str, ...]],
|
||||
def on_shell_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
parser: Optional[ArgumentParser] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -434,7 +470,8 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]],
|
||||
message = event.get_message()
|
||||
segment = message.pop(0)
|
||||
new_message = message.__class__(
|
||||
str(segment)[len(state[PREFIX_KEY][RAW_CMD_KEY]):].strip())
|
||||
str(segment)[len(state[PREFIX_KEY][RAW_CMD_KEY]) :].strip()
|
||||
)
|
||||
for new_segment in reversed(new_message):
|
||||
message.insert(0, new_segment)
|
||||
|
||||
@ -442,17 +479,21 @@ def on_shell_command(cmd: Union[str, Tuple[str, ...]],
|
||||
handlers.insert(0, _strip_cmd)
|
||||
|
||||
commands = set([cmd]) | (aliases or set())
|
||||
return on_message(shell_command(*commands, parser=parser) & rule,
|
||||
return on_message(
|
||||
shell_command(*commands, parser=parser) & rule,
|
||||
handlers=handlers,
|
||||
**kwargs,
|
||||
_depth=_depth + 1)
|
||||
_depth=_depth + 1,
|
||||
)
|
||||
|
||||
|
||||
def on_regex(pattern: str,
|
||||
def on_regex(
|
||||
pattern: str,
|
||||
flags: Union[int, re.RegexFlag] = 0,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = None,
|
||||
_depth: int = 0,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -503,8 +544,7 @@ class CommandGroup:
|
||||
- **说明**: 其他传递给 ``on_command`` 的参数默认值
|
||||
"""
|
||||
|
||||
def command(self, cmd: Union[str, Tuple[str, ...]],
|
||||
**kwargs) -> Type[Matcher]:
|
||||
def command(self, cmd: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -526,8 +566,9 @@ class CommandGroup:
|
||||
final_kwargs.update(kwargs)
|
||||
return on_command(cmd, **final_kwargs, _depth=1)
|
||||
|
||||
def shell_command(self, cmd: Union[str, Tuple[str, ...]],
|
||||
**kwargs) -> Type[Matcher]:
|
||||
def shell_command(
|
||||
self, cmd: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -708,8 +749,9 @@ class MatcherGroup:
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_startswith(self, msg: Union[str, Tuple[str, ...]],
|
||||
**kwargs) -> Type[Matcher]:
|
||||
def on_startswith(
|
||||
self, msg: Union[str, Tuple[str, ...]], **kwargs
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -739,8 +781,7 @@ class MatcherGroup:
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_endswith(self, msg: Union[str, Tuple[str, ...]],
|
||||
**kwargs) -> Type[Matcher]:
|
||||
def on_endswith(self, msg: Union[str, Tuple[str, ...]], **kwargs) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -799,10 +840,12 @@ class MatcherGroup:
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_command(self,
|
||||
def on_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -834,12 +877,13 @@ class MatcherGroup:
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_shell_command(self,
|
||||
def on_shell_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
aliases: Optional[Set[Union[str, Tuple[str,
|
||||
...]]]] = None,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
|
||||
parser: Optional[ArgumentParser] = None,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
**kwargs,
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -870,18 +914,15 @@ class MatcherGroup:
|
||||
final_kwargs = self.base_kwargs.copy()
|
||||
final_kwargs.update(kwargs)
|
||||
final_kwargs.pop("type", None)
|
||||
matcher = on_shell_command(cmd,
|
||||
aliases=aliases,
|
||||
parser=parser,
|
||||
**final_kwargs,
|
||||
_depth=1)
|
||||
matcher = on_shell_command(
|
||||
cmd, aliases=aliases, parser=parser, **final_kwargs, _depth=1
|
||||
)
|
||||
self.matchers.append(matcher)
|
||||
return matcher
|
||||
|
||||
def on_regex(self,
|
||||
pattern: str,
|
||||
flags: Union[int, re.RegexFlag] = 0,
|
||||
**kwargs) -> Type[Matcher]:
|
||||
def on_regex(
|
||||
self, pattern: str, flags: Union[int, re.RegexFlag] = 0, **kwargs
|
||||
) -> Type[Matcher]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
|
@ -7,8 +7,8 @@ from nonebot.permission import Permission
|
||||
from nonebot.rule import Rule, ArgumentParser
|
||||
from nonebot.typing import T_State, T_Handler, T_RuleChecker, T_StateFactory
|
||||
|
||||
|
||||
def on(type: str = "",
|
||||
def on(
|
||||
type: str = "",
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
*,
|
||||
@ -17,10 +17,8 @@ def on(type: str = "",
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_metaevent(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
@ -29,11 +27,10 @@ def on_metaevent(
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_message(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
permission: Optional[Permission] = ...,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
@ -41,32 +38,28 @@ def on_message(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_notice(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_notice(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_request(rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_request(
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
handlers: Optional[List[Union[T_Handler, Handler]]] = ...,
|
||||
temp: bool = ...,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_startswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
@ -78,11 +71,10 @@ def on_startswith(
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_endswith(msg: Union[str, Tuple[str, ...]],
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_endswith(
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
|
||||
ignorecase: bool = ...,
|
||||
*,
|
||||
@ -92,11 +84,10 @@ def on_endswith(msg: Union[str, Tuple[str, ...]],
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_keyword(keywords: Set[str],
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_keyword(
|
||||
keywords: Set[str],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
permission: Optional[Permission] = ...,
|
||||
@ -105,11 +96,10 @@ def on_keyword(keywords: Set[str],
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_command(cmd: Union[str, Tuple[str, ...]],
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
|
||||
*,
|
||||
@ -119,10 +109,8 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_shell_command(
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
@ -135,11 +123,10 @@ def on_shell_command(
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
|
||||
def on_regex(pattern: str,
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_regex(
|
||||
pattern: str,
|
||||
flags: Union[int, re.RegexFlag] = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
*,
|
||||
@ -149,13 +136,12 @@ def on_regex(pattern: str,
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
|
||||
class CommandGroup:
|
||||
|
||||
def __init__(self,
|
||||
def __init__(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
@ -165,10 +151,10 @@ class CommandGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...):
|
||||
...
|
||||
|
||||
def command(self,
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
): ...
|
||||
def command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
*,
|
||||
aliases: Optional[Set[Union[str, Tuple[str, ...]]]],
|
||||
@ -179,9 +165,8 @@ class CommandGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def shell_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
@ -195,13 +180,12 @@ class CommandGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
|
||||
class MatcherGroup:
|
||||
|
||||
def __init__(self,
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
type: str = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
@ -211,10 +195,10 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...):
|
||||
...
|
||||
|
||||
def on(self,
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
): ...
|
||||
def on(
|
||||
self,
|
||||
*,
|
||||
type: str = ...,
|
||||
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
|
||||
@ -224,9 +208,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_metaevent(
|
||||
self,
|
||||
*,
|
||||
@ -236,9 +219,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_message(
|
||||
self,
|
||||
*,
|
||||
@ -249,9 +231,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_notice(
|
||||
self,
|
||||
*,
|
||||
@ -261,9 +242,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_request(
|
||||
self,
|
||||
*,
|
||||
@ -273,9 +253,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_startswith(
|
||||
self,
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
@ -288,9 +267,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_endswith(
|
||||
self,
|
||||
msg: Union[str, Tuple[str, ...]],
|
||||
@ -303,9 +281,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_keyword(
|
||||
self,
|
||||
keywords: Set[str],
|
||||
@ -317,9 +294,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
@ -332,9 +308,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_shell_command(
|
||||
self,
|
||||
cmd: Union[str, Tuple[str, ...]],
|
||||
@ -348,9 +323,8 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
def on_regex(
|
||||
self,
|
||||
pattern: str,
|
||||
@ -363,5 +337,5 @@ class MatcherGroup:
|
||||
priority: int = ...,
|
||||
block: bool = ...,
|
||||
state: Optional[T_State] = ...,
|
||||
state_factory: Optional[T_StateFactory] = ...) -> Type[Matcher]:
|
||||
...
|
||||
state_factory: Optional[T_StateFactory] = ...,
|
||||
) -> Type[Matcher]: ...
|
||||
|
@ -15,6 +15,7 @@ plugins: Dict[str, "Plugin"] = {}
|
||||
@dataclass(eq=False)
|
||||
class Plugin(object):
|
||||
"""存储插件信息"""
|
||||
|
||||
name: str
|
||||
"""
|
||||
- **类型**: ``str``
|
||||
|
@ -3,15 +3,18 @@ from functools import reduce
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.plugin import on_command
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.adapters.cqhttp import (Message, MessageEvent, MessageSegment,
|
||||
unescape)
|
||||
from nonebot.adapters.cqhttp import (
|
||||
Message,
|
||||
MessageEvent,
|
||||
MessageSegment,
|
||||
unescape,
|
||||
)
|
||||
|
||||
say = on_command("say", to_me(), permission=SUPERUSER)
|
||||
|
||||
|
||||
@say.handle()
|
||||
async def say_unescape(event: MessageEvent):
|
||||
|
||||
def _unescape(message: Message, segment: MessageSegment):
|
||||
if segment.is_text():
|
||||
return message.append(unescape(str(segment)))
|
||||
|
@ -1,8 +1,11 @@
|
||||
from typing import Dict
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.message import (IgnoredException, run_preprocessor,
|
||||
run_postprocessor)
|
||||
from nonebot.message import (
|
||||
IgnoredException,
|
||||
run_preprocessor,
|
||||
run_postprocessor,
|
||||
)
|
||||
|
||||
_running_matcher: Dict[str, int] = {}
|
||||
|
||||
|
@ -17,8 +17,18 @@ from argparse import Namespace
|
||||
from contextlib import AsyncExitStack
|
||||
from typing_extensions import TypedDict
|
||||
from argparse import ArgumentParser as ArgParser
|
||||
from typing import (Any, Dict, List, Type, Tuple, Union, Callable, NoReturn,
|
||||
Optional, Sequence)
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Callable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
Sequence,
|
||||
)
|
||||
|
||||
from pygtrie import CharTrie
|
||||
|
||||
@ -33,10 +43,9 @@ PREFIX_KEY = "_prefix"
|
||||
SUFFIX_KEY = "_suffix"
|
||||
CMD_KEY = "command"
|
||||
RAW_CMD_KEY = "raw_command"
|
||||
CMD_RESULT = TypedDict("CMD_RESULT", {
|
||||
"command": Optional[Tuple[str, ...]],
|
||||
"raw_command": Optional[str]
|
||||
})
|
||||
CMD_RESULT = TypedDict(
|
||||
"CMD_RESULT", {"command": Optional[Tuple[str, ...]], "raw_command": Optional[str]}
|
||||
)
|
||||
|
||||
SHELL_ARGS = "_args"
|
||||
SHELL_ARGV = "_argv"
|
||||
@ -61,11 +70,14 @@ class Rule:
|
||||
from nonebot.utils import run_sync
|
||||
Rule(async_function, run_sync(sync_function))
|
||||
"""
|
||||
|
||||
__slots__ = ("checkers",)
|
||||
|
||||
HANDLER_PARAM_TYPES = [
|
||||
params.BotParam, params.EventParam, params.StateParam,
|
||||
params.DefaultParam
|
||||
params.BotParam,
|
||||
params.EventParam,
|
||||
params.StateParam,
|
||||
params.DefaultParam,
|
||||
]
|
||||
|
||||
def __init__(self, *checkers: Union[T_RuleChecker, Handler]) -> None:
|
||||
@ -76,9 +88,11 @@ class Rule:
|
||||
|
||||
"""
|
||||
self.checkers = set(
|
||||
checker if isinstance(checker, Handler) else Handler(
|
||||
checker, allow_types=self.HANDLER_PARAM_TYPES)
|
||||
for checker in checkers)
|
||||
checker
|
||||
if isinstance(checker, Handler)
|
||||
else Handler(checker, allow_types=self.HANDLER_PARAM_TYPES)
|
||||
for checker in checkers
|
||||
)
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -95,8 +109,8 @@ class Rule:
|
||||
event: Event,
|
||||
state: T_State,
|
||||
stack: Optional[AsyncExitStack] = None,
|
||||
dependency_cache: Optional[Dict[Callable[..., Any],
|
||||
Any]] = None) -> bool:
|
||||
dependency_cache: Optional[Dict[Callable[..., Any], Any]] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -117,12 +131,17 @@ class Rule:
|
||||
if not self.checkers:
|
||||
return True
|
||||
results = await asyncio.gather(
|
||||
*(checker(bot=bot,
|
||||
*(
|
||||
checker(
|
||||
bot=bot,
|
||||
event=event,
|
||||
state=state,
|
||||
_stack=stack,
|
||||
_dependency_cache=dependency_cache)
|
||||
for checker in self.checkers))
|
||||
_dependency_cache=dependency_cache,
|
||||
)
|
||||
for checker in self.checkers
|
||||
)
|
||||
)
|
||||
return all(results)
|
||||
|
||||
def __and__(self, other: Optional[Union["Rule", T_RuleChecker]]) -> "Rule":
|
||||
@ -156,8 +175,9 @@ class TrieRule:
|
||||
cls.suffix[suffix[::-1]] = value
|
||||
|
||||
@classmethod
|
||||
def get_value(cls, bot: Bot, event: Event,
|
||||
state: T_State) -> Tuple[CMD_RESULT, CMD_RESULT]:
|
||||
def get_value(
|
||||
cls, bot: Bot, event: Event, state: T_State
|
||||
) -> Tuple[CMD_RESULT, CMD_RESULT]:
|
||||
prefix = CMD_RESULT(command=None, raw_command=None)
|
||||
suffix = CMD_RESULT(command=None, raw_command=None)
|
||||
state[PREFIX_KEY] = prefix
|
||||
@ -180,8 +200,7 @@ class TrieRule:
|
||||
return prefix, suffix
|
||||
|
||||
|
||||
def startswith(msg: Union[str, Tuple[str, ...]],
|
||||
ignorecase: bool = False) -> Rule:
|
||||
def startswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -196,7 +215,8 @@ def startswith(msg: Union[str, Tuple[str, ...]],
|
||||
|
||||
pattern = re.compile(
|
||||
f"^(?:{'|'.join(re.escape(prefix) for prefix in msg)})",
|
||||
re.IGNORECASE if ignorecase else 0)
|
||||
re.IGNORECASE if ignorecase else 0,
|
||||
)
|
||||
|
||||
async def _startswith(bot: Bot, event: Event, state: T_State) -> bool:
|
||||
if event.get_type() != "message":
|
||||
@ -207,8 +227,7 @@ def startswith(msg: Union[str, Tuple[str, ...]],
|
||||
return Rule(_startswith)
|
||||
|
||||
|
||||
def endswith(msg: Union[str, Tuple[str, ...]],
|
||||
ignorecase: bool = False) -> Rule:
|
||||
def endswith(msg: Union[str, Tuple[str, ...]], ignorecase: bool = False) -> Rule:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -223,7 +242,8 @@ def endswith(msg: Union[str, Tuple[str, ...]],
|
||||
|
||||
pattern = re.compile(
|
||||
f"(?:{'|'.join(re.escape(prefix) for prefix in msg)})$",
|
||||
re.IGNORECASE if ignorecase else 0)
|
||||
re.IGNORECASE if ignorecase else 0,
|
||||
)
|
||||
|
||||
async def _endswith(bot: Bot, event: Event, state: T_State) -> bool:
|
||||
if event.get_type() != "message":
|
||||
@ -314,19 +334,22 @@ class ArgumentParser(ArgParser):
|
||||
setattr(self, "message", old_message)
|
||||
|
||||
def exit(self, status: int = 0, message: Optional[str] = None):
|
||||
raise ParserExit(status=status,
|
||||
message=message or getattr(self, "message", None))
|
||||
raise ParserExit(
|
||||
status=status, message=message or getattr(self, "message", None)
|
||||
)
|
||||
|
||||
def parse_args(self,
|
||||
def parse_args(
|
||||
self,
|
||||
args: Optional[Sequence[str]] = None,
|
||||
namespace: Optional[Namespace] = None) -> Namespace:
|
||||
namespace: Optional[Namespace] = None,
|
||||
) -> Namespace:
|
||||
setattr(self, "message", "")
|
||||
return super().parse_args(args=args,
|
||||
namespace=namespace) # type: ignore
|
||||
return super().parse_args(args=args, namespace=namespace) # type: ignore
|
||||
|
||||
|
||||
def shell_command(*cmds: Union[str, Tuple[str, ...]],
|
||||
parser: Optional[ArgumentParser] = None) -> Rule:
|
||||
def shell_command(
|
||||
*cmds: Union[str, Tuple[str, ...]], parser: Optional[ArgumentParser] = None
|
||||
) -> Rule:
|
||||
r"""
|
||||
:说明:
|
||||
|
||||
@ -361,8 +384,7 @@ def shell_command(*cmds: Union[str, Tuple[str, ...]],
|
||||
\:\:\:
|
||||
"""
|
||||
if not isinstance(parser, ArgumentParser):
|
||||
raise TypeError(
|
||||
"`parser` must be an instance of nonebot.rule.ArgumentParser")
|
||||
raise TypeError("`parser` must be an instance of nonebot.rule.ArgumentParser")
|
||||
|
||||
config = get_driver().config
|
||||
command_start = config.command_start
|
||||
@ -382,8 +404,7 @@ def shell_command(*cmds: Union[str, Tuple[str, ...]],
|
||||
async def _shell_command(event: Event, state: T_State) -> bool:
|
||||
if state[PREFIX_KEY][CMD_KEY] in commands:
|
||||
message = str(event.get_message())
|
||||
strip_message = message[len(state[PREFIX_KEY][RAW_CMD_KEY]
|
||||
):].lstrip()
|
||||
strip_message = message[len(state[PREFIX_KEY][RAW_CMD_KEY]) :].lstrip()
|
||||
state[SHELL_ARGV] = shlex.split(strip_message)
|
||||
if parser:
|
||||
try:
|
||||
|
@ -18,8 +18,17 @@
|
||||
https://docs.python.org/3/library/typing.html
|
||||
"""
|
||||
|
||||
from typing import (TYPE_CHECKING, Any, Dict, Union, TypeVar, Callable,
|
||||
NoReturn, Optional, Awaitable)
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
NoReturn,
|
||||
Optional,
|
||||
Awaitable,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.adapters import Bot, Event
|
||||
@ -29,10 +38,8 @@ T_Wrapped = TypeVar("T_Wrapped", bound=Callable)
|
||||
|
||||
|
||||
def overrides(InterfaceClass: object):
|
||||
|
||||
def overrider(func: T_Wrapped) -> T_Wrapped:
|
||||
assert func.__name__ in dir(
|
||||
InterfaceClass), f"Error method: {func.__name__}"
|
||||
assert func.__name__ in dir(InterfaceClass), f"Error method: {func.__name__}"
|
||||
return func
|
||||
|
||||
return overrider
|
||||
@ -80,7 +87,8 @@ T_CallingAPIHook = Callable[["Bot", str, Dict[str, Any]], Awaitable[None]]
|
||||
``bot.call_api`` 时执行的函数
|
||||
"""
|
||||
T_CalledAPIHook = Callable[
|
||||
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[None]]
|
||||
["Bot", Optional[Exception], str, Dict[str, Any], Any], Awaitable[None]
|
||||
]
|
||||
"""
|
||||
:类型: ``Callable[[Bot, Optional[Exception], str, Dict[str, Any], Any], Awaitable[None]]``
|
||||
|
||||
@ -193,8 +201,9 @@ T_DependencyCache = Dict[T_Handler, Any]
|
||||
|
||||
依赖缓存, 用于存储依赖函数的返回值
|
||||
"""
|
||||
T_ArgsParser = Callable[["Bot", "Event", T_State], Union[Awaitable[None],
|
||||
Awaitable[NoReturn]]]
|
||||
T_ArgsParser = Callable[
|
||||
["Bot", "Event", T_State], Union[Awaitable[None], Awaitable[NoReturn]]
|
||||
]
|
||||
"""
|
||||
:类型: ``Callable[[Bot, Event, T_State], Union[Awaitable[None], Awaitable[NoReturn]]]``
|
||||
|
||||
@ -210,8 +219,9 @@ T_TypeUpdater = Callable[["Bot", "Event", T_State, str], Awaitable[str]]
|
||||
|
||||
TypeUpdater 在 Matcher.pause, Matcher.reject 时被运行,用于更新响应的事件类型。默认会更新为 ``message``。
|
||||
"""
|
||||
T_PermissionUpdater = Callable[["Bot", "Event", T_State, "Permission"],
|
||||
Awaitable["Permission"]]
|
||||
T_PermissionUpdater = Callable[
|
||||
["Bot", "Event", T_State, "Permission"], Awaitable["Permission"]
|
||||
]
|
||||
"""
|
||||
:类型: ``Callable[[Bot, Event, T_State, Permission], Awaitable[Permission]]``
|
||||
|
||||
|
@ -8,8 +8,19 @@ from functools import wraps, partial
|
||||
from contextlib import asynccontextmanager
|
||||
from typing_extensions import GenericAlias # type: ignore
|
||||
from typing_extensions import ParamSpec, get_args, get_origin
|
||||
from typing import (Any, Type, Deque, Tuple, Union, TypeVar, Callable, Optional,
|
||||
Awaitable, AsyncGenerator, ContextManager)
|
||||
from typing import (
|
||||
Any,
|
||||
Type,
|
||||
Deque,
|
||||
Tuple,
|
||||
Union,
|
||||
TypeVar,
|
||||
Callable,
|
||||
Optional,
|
||||
Awaitable,
|
||||
AsyncGenerator,
|
||||
ContextManager,
|
||||
)
|
||||
|
||||
from nonebot.log import logger
|
||||
from nonebot.typing import overrides
|
||||
@ -37,15 +48,16 @@ def escape_tag(s: str) -> str:
|
||||
|
||||
|
||||
def generic_check_issubclass(
|
||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any],
|
||||
...]]) -> bool:
|
||||
cls: Any, class_or_tuple: Union[Type[Any], Tuple[Type[Any], ...]]
|
||||
) -> bool:
|
||||
try:
|
||||
return issubclass(cls, class_or_tuple)
|
||||
except TypeError:
|
||||
if get_origin(cls) is Union:
|
||||
for type_ in get_args(cls):
|
||||
if type_ is not type(None) and not generic_check_issubclass(
|
||||
type_, class_or_tuple):
|
||||
type_, class_or_tuple
|
||||
):
|
||||
return False
|
||||
return True
|
||||
elif isinstance(cls, GenericAlias):
|
||||
@ -104,7 +116,8 @@ def run_sync(func: Callable[P, R]) -> Callable[P, Awaitable[R]]:
|
||||
|
||||
@asynccontextmanager
|
||||
async def run_sync_ctx_manager(
|
||||
cm: ContextManager[T],) -> AsyncGenerator[T, None]:
|
||||
cm: ContextManager[T],
|
||||
) -> AsyncGenerator[T, None]:
|
||||
try:
|
||||
yield await run_sync(cm.__enter__)()
|
||||
except Exception as e:
|
||||
@ -122,7 +135,6 @@ def get_name(obj: Any) -> str:
|
||||
|
||||
|
||||
class CacheLock:
|
||||
|
||||
def __init__(self):
|
||||
self._waiters: Optional[Deque[asyncio.Future]] = None
|
||||
self._locked = False
|
||||
@ -144,8 +156,9 @@ class CacheLock:
|
||||
return self._locked
|
||||
|
||||
async def acquire(self):
|
||||
if (not self._locked and (self._waiters is None or
|
||||
all(w.cancelled() for w in self._waiters))):
|
||||
if not self._locked and (
|
||||
self._waiters is None or all(w.cancelled() for w in self._waiters)
|
||||
):
|
||||
self._locked = True
|
||||
return True
|
||||
|
||||
@ -223,6 +236,7 @@ def logger_wrapper(logger_name: str):
|
||||
|
||||
def log(level: str, message: str, exception: Optional[Exception] = None):
|
||||
return logger.opt(colors=True, exception=exception).log(
|
||||
level, f"<m>{escape_tag(logger_name)}</m> | " + message)
|
||||
level, f"<m>{escape_tag(logger_name)}</m> | " + message
|
||||
)
|
||||
|
||||
return log
|
||||
|
@ -12,8 +12,15 @@ from nonebot.typing import overrides
|
||||
from nonebot.message import handle_event
|
||||
from nonebot.adapters import Bot as BaseBot
|
||||
from nonebot.utils import DataclassEncoder, escape_tag
|
||||
from nonebot.drivers import (Driver, WebSocket, HTTPRequest, HTTPResponse,
|
||||
ForwardDriver, HTTPConnection, WebSocketSetup)
|
||||
from nonebot.drivers import (
|
||||
Driver,
|
||||
WebSocket,
|
||||
HTTPRequest,
|
||||
HTTPResponse,
|
||||
ForwardDriver,
|
||||
HTTPConnection,
|
||||
WebSocketSetup,
|
||||
)
|
||||
|
||||
from .utils import log, escape
|
||||
from .config import Config as CQHTTPConfig
|
||||
@ -49,15 +56,12 @@ async def _check_reply(bot: "Bot", event: "Event"):
|
||||
return
|
||||
|
||||
try:
|
||||
index = list(map(lambda x: x.type == "reply",
|
||||
event.message)).index(True)
|
||||
index = list(map(lambda x: x.type == "reply", event.message)).index(True)
|
||||
except ValueError:
|
||||
return
|
||||
msg_seg = event.message[index]
|
||||
try:
|
||||
event.reply = Reply.parse_obj(await
|
||||
bot.get_msg(message_id=msg_seg.data["id"]
|
||||
))
|
||||
event.reply = Reply.parse_obj(await bot.get_msg(message_id=msg_seg.data["id"]))
|
||||
except Exception as e:
|
||||
log("WARNING", f"Error when getting message reply info: {repr(e)}", e)
|
||||
return
|
||||
@ -68,8 +72,7 @@ async def _check_reply(bot: "Bot", event: "Event"):
|
||||
if len(event.message) > index and event.message[index].type == "at":
|
||||
del event.message[index]
|
||||
if len(event.message) > index and event.message[index].type == "text":
|
||||
event.message[index].data["text"] = event.message[index].data[
|
||||
"text"].lstrip()
|
||||
event.message[index].data["text"] = event.message[index].data["text"].lstrip()
|
||||
if not event.message[index].data["text"]:
|
||||
del event.message[index]
|
||||
if not event.message:
|
||||
@ -99,23 +102,24 @@ def _check_at_me(bot: "Bot", event: "Event"):
|
||||
else:
|
||||
|
||||
def _is_at_me_seg(segment: MessageSegment):
|
||||
return segment.type == "at" and str(segment.data.get(
|
||||
"qq", "")) == str(event.self_id)
|
||||
return segment.type == "at" and str(segment.data.get("qq", "")) == str(
|
||||
event.self_id
|
||||
)
|
||||
|
||||
# check the first segment
|
||||
if _is_at_me_seg(event.message[0]):
|
||||
event.to_me = True
|
||||
event.message.pop(0)
|
||||
if event.message and event.message[0].type == "text":
|
||||
event.message[0].data["text"] = event.message[0].data[
|
||||
"text"].lstrip()
|
||||
event.message[0].data["text"] = event.message[0].data["text"].lstrip()
|
||||
if not event.message[0].data["text"]:
|
||||
del event.message[0]
|
||||
if event.message and _is_at_me_seg(event.message[0]):
|
||||
event.message.pop(0)
|
||||
if event.message and event.message[0].type == "text":
|
||||
event.message[0].data["text"] = event.message[0].data[
|
||||
"text"].lstrip()
|
||||
event.message[0].data["text"] = (
|
||||
event.message[0].data["text"].lstrip()
|
||||
)
|
||||
if not event.message[0].data["text"]:
|
||||
del event.message[0]
|
||||
|
||||
@ -123,9 +127,11 @@ def _check_at_me(bot: "Bot", event: "Event"):
|
||||
# check the last segment
|
||||
i = -1
|
||||
last_msg_seg = event.message[i]
|
||||
if last_msg_seg.type == "text" and \
|
||||
not last_msg_seg.data["text"].strip() and \
|
||||
len(event.message) >= 2:
|
||||
if (
|
||||
last_msg_seg.type == "text"
|
||||
and not last_msg_seg.data["text"].strip()
|
||||
and len(event.message) >= 2
|
||||
):
|
||||
i -= 1
|
||||
last_msg_seg = event.message[i]
|
||||
|
||||
@ -161,8 +167,7 @@ def _check_nickname(bot: "Bot", event: "Event"):
|
||||
if nicknames:
|
||||
# check if the user is calling me with my nickname
|
||||
nickname_regex = "|".join(nicknames)
|
||||
m = re.search(rf"^({nickname_regex})([\s,,]*|$)", first_text,
|
||||
re.IGNORECASE)
|
||||
m = re.search(rf"^({nickname_regex})([\s,,]*|$)", first_text, re.IGNORECASE)
|
||||
if m:
|
||||
nickname = m.group(1)
|
||||
log("DEBUG", f"User is calling me {nickname}")
|
||||
@ -206,8 +211,9 @@ class ResultStore:
|
||||
|
||||
@classmethod
|
||||
def add_result(cls, result: Dict[str, Any]):
|
||||
if isinstance(result.get("echo"), dict) and \
|
||||
isinstance(result["echo"].get("seq"), int):
|
||||
if isinstance(result.get("echo"), dict) and isinstance(
|
||||
result["echo"].get("seq"), int
|
||||
):
|
||||
future = cls._futures.get(result["echo"]["seq"])
|
||||
if future:
|
||||
future.set_result(result)
|
||||
@ -228,6 +234,7 @@ class Bot(BaseBot):
|
||||
"""
|
||||
CQHTTP 协议 Bot 适配。继承属性参考 `BaseBot <./#class-basebot>`_ 。
|
||||
"""
|
||||
|
||||
cqhttp_config: CQHTTPConfig
|
||||
|
||||
@property
|
||||
@ -249,22 +256,25 @@ class Bot(BaseBot):
|
||||
elif isinstance(driver, ForwardDriver) and cls.cqhttp_config.ws_urls:
|
||||
for self_id, url in cls.cqhttp_config.ws_urls.items():
|
||||
try:
|
||||
headers = {
|
||||
"authorization":
|
||||
f"Bearer {cls.cqhttp_config.access_token}"
|
||||
} if cls.cqhttp_config.access_token else {}
|
||||
headers = (
|
||||
{"authorization": f"Bearer {cls.cqhttp_config.access_token}"}
|
||||
if cls.cqhttp_config.access_token
|
||||
else {}
|
||||
)
|
||||
driver.setup_websocket(
|
||||
WebSocketSetup("cqhttp", self_id, url, headers=headers))
|
||||
WebSocketSetup("cqhttp", self_id, url, headers=headers)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.opt(colors=True, exception=e).error(
|
||||
f"<r><bg #f8bbd0>Bad url {escape_tag(url)} for bot {escape_tag(self_id)} "
|
||||
"in cqhttp forward websocket</bg #f8bbd0></r>")
|
||||
"in cqhttp forward websocket</bg #f8bbd0></r>"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@overrides(BaseBot)
|
||||
async def check_permission(
|
||||
cls, driver: Driver,
|
||||
request: HTTPConnection) -> Tuple[Optional[str], HTTPResponse]:
|
||||
cls, driver: Driver, request: HTTPConnection
|
||||
) -> Tuple[Optional[str], HTTPResponse]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -286,22 +296,26 @@ class Bot(BaseBot):
|
||||
if not x_signature:
|
||||
log("WARNING", "Missing Signature Header")
|
||||
return None, HTTPResponse(401, b"Missing Signature")
|
||||
sig = hmac.new(secret.encode("utf-8"), request.body,
|
||||
"sha1").hexdigest()
|
||||
sig = hmac.new(secret.encode("utf-8"), request.body, "sha1").hexdigest()
|
||||
if x_signature != "sha1=" + sig:
|
||||
log("WARNING", "Signature Header is invalid")
|
||||
return None, HTTPResponse(403, b"Signature is invalid")
|
||||
|
||||
access_token = cqhttp_config.access_token
|
||||
if access_token and access_token != token and isinstance(
|
||||
request, WebSocket):
|
||||
if access_token and access_token != token and isinstance(request, WebSocket):
|
||||
log(
|
||||
"WARNING", "Authorization Header is invalid"
|
||||
if token else "Missing Authorization Header")
|
||||
"WARNING",
|
||||
"Authorization Header is invalid"
|
||||
if token
|
||||
else "Missing Authorization Header",
|
||||
)
|
||||
return None, HTTPResponse(
|
||||
403, b"Authorization Header is invalid"
|
||||
if token else b"Missing Authorization Header")
|
||||
return str(x_self_id), HTTPResponse(204, b'')
|
||||
403,
|
||||
b"Authorization Header is invalid"
|
||||
if token
|
||||
else b"Missing Authorization Header",
|
||||
)
|
||||
return str(x_self_id), HTTPResponse(204, b"")
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def handle_message(self, message: bytes):
|
||||
@ -320,7 +334,7 @@ class Bot(BaseBot):
|
||||
return
|
||||
|
||||
try:
|
||||
post_type = data['post_type']
|
||||
post_type = data["post_type"]
|
||||
detail_type = data.get(f"{post_type}_type")
|
||||
detail_type = f".{detail_type}" if detail_type else ""
|
||||
sub_type = data.get("sub_type")
|
||||
@ -352,17 +366,13 @@ class Bot(BaseBot):
|
||||
if isinstance(self.request, WebSocket):
|
||||
seq = ResultStore.get_seq()
|
||||
json_data = json.dumps(
|
||||
{
|
||||
"action": api,
|
||||
"params": data,
|
||||
"echo": {
|
||||
"seq": seq
|
||||
}
|
||||
},
|
||||
cls=DataclassEncoder)
|
||||
{"action": api, "params": data, "echo": {"seq": seq}},
|
||||
cls=DataclassEncoder,
|
||||
)
|
||||
await self.request.send(json_data)
|
||||
return _handle_api_result(await ResultStore.fetch(
|
||||
seq, self.config.api_timeout))
|
||||
return _handle_api_result(
|
||||
await ResultStore.fetch(seq, self.config.api_timeout)
|
||||
)
|
||||
|
||||
elif isinstance(self.request, HTTPRequest):
|
||||
api_root = self.config.api_root.get(self.self_id)
|
||||
@ -373,22 +383,25 @@ class Bot(BaseBot):
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if self.cqhttp_config.access_token is not None:
|
||||
headers[
|
||||
"Authorization"] = "Bearer " + self.cqhttp_config.access_token
|
||||
headers["Authorization"] = "Bearer " + self.cqhttp_config.access_token
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(headers=headers,
|
||||
follow_redirects=True) as client:
|
||||
async with httpx.AsyncClient(
|
||||
headers=headers, follow_redirects=True
|
||||
) as client:
|
||||
response = await client.post(
|
||||
api_root + api,
|
||||
content=json.dumps(data, cls=DataclassEncoder),
|
||||
timeout=self.config.api_timeout)
|
||||
timeout=self.config.api_timeout,
|
||||
)
|
||||
|
||||
if 200 <= response.status_code < 300:
|
||||
result = response.json()
|
||||
return _handle_api_result(result)
|
||||
raise NetworkError(f"HTTP request received unexpected "
|
||||
f"status code: {response.status_code}")
|
||||
raise NetworkError(
|
||||
f"HTTP request received unexpected "
|
||||
f"status code: {response.status_code}"
|
||||
)
|
||||
except httpx.InvalidURL:
|
||||
raise NetworkError("API root url invalid")
|
||||
except httpx.HTTPError:
|
||||
@ -418,11 +431,13 @@ class Bot(BaseBot):
|
||||
return await super().call_api(api, **data)
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def send(self,
|
||||
async def send(
|
||||
self,
|
||||
event: Event,
|
||||
message: Union[str, Message, MessageSegment],
|
||||
at_sender: bool = False,
|
||||
**kwargs) -> Any:
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -445,8 +460,9 @@ class Bot(BaseBot):
|
||||
- ``NetworkError``: 网络错误
|
||||
- ``ActionFailed``: API 调用失败
|
||||
"""
|
||||
message = escape(message, escape_comma=False) if isinstance(
|
||||
message, str) else message
|
||||
message = (
|
||||
escape(message, escape_comma=False) if isinstance(message, str) else message
|
||||
)
|
||||
msg = message if isinstance(message, Message) else Message(message)
|
||||
|
||||
at_sender = at_sender and bool(getattr(event, "user_id", None))
|
||||
|
@ -8,7 +8,6 @@ from nonebot.drivers import Driver, WebSocket
|
||||
from .event import Event
|
||||
from .message import Message, MessageSegment
|
||||
|
||||
|
||||
def get_auth_bearer(access_token: Optional[str] = ...) -> Optional[str]:
|
||||
...
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import Dict, Optional
|
||||
|
||||
from pydantic import Field, BaseModel, AnyUrl
|
||||
from pydantic import Field, AnyUrl, BaseModel
|
||||
|
||||
|
||||
# priority: alias > origin
|
||||
@ -14,11 +14,10 @@ class Config(BaseModel):
|
||||
- ``secret`` / ``cqhttp_secret``: CQHTTP HTTP 上报数据签名口令
|
||||
- ``ws_urls`` / ``cqhttp_ws_urls``: CQHTTP 正向 Websocket 连接 Bot ID、目标 URL 字典
|
||||
"""
|
||||
access_token: Optional[str] = Field(default=None,
|
||||
alias="cqhttp_access_token")
|
||||
|
||||
access_token: Optional[str] = Field(default=None, alias="cqhttp_access_token")
|
||||
secret: Optional[str] = Field(default=None, alias="cqhttp_secret")
|
||||
ws_urls: Dict[str, AnyUrl] = Field(default_factory=set,
|
||||
alias="cqhttp_ws_urls")
|
||||
ws_urls: Dict[str, AnyUrl] = Field(default_factory=set, alias="cqhttp_ws_urls")
|
||||
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
@ -5,12 +5,13 @@ from typing import TYPE_CHECKING, List, Type, Optional
|
||||
from pydantic import BaseModel
|
||||
from pygtrie import StringTrie
|
||||
|
||||
from .message import Message
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.utils import escape_tag
|
||||
from .exception import NoLogException
|
||||
from nonebot.adapters import Event as BaseEvent
|
||||
|
||||
from .message import Message
|
||||
from .exception import NoLogException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .bot import Bot
|
||||
|
||||
@ -22,6 +23,7 @@ class Event(BaseEvent):
|
||||
.. _CQHTTP 文档:
|
||||
https://github.com/howmanybots/onebot/blob/master/README.md
|
||||
"""
|
||||
|
||||
__event__ = ""
|
||||
time: int
|
||||
self_id: int
|
||||
@ -118,6 +120,7 @@ class Status(BaseModel):
|
||||
# Message Events
|
||||
class MessageEvent(Event):
|
||||
"""消息事件"""
|
||||
|
||||
__event__ = "message"
|
||||
post_type: Literal["message"]
|
||||
sub_type: str
|
||||
@ -144,8 +147,9 @@ class MessageEvent(Event):
|
||||
@overrides(Event)
|
||||
def get_event_name(self) -> str:
|
||||
sub_type = getattr(self, "sub_type", None)
|
||||
return f"{self.post_type}.{self.message_type}" + (f".{sub_type}"
|
||||
if sub_type else "")
|
||||
return f"{self.post_type}.{self.message_type}" + (
|
||||
f".{sub_type}" if sub_type else ""
|
||||
)
|
||||
|
||||
@overrides(Event)
|
||||
def get_message(self) -> Message:
|
||||
@ -170,20 +174,29 @@ class MessageEvent(Event):
|
||||
|
||||
class PrivateMessageEvent(MessageEvent):
|
||||
"""私聊消息"""
|
||||
|
||||
__event__ = "message.private"
|
||||
message_type: Literal["private"]
|
||||
|
||||
@overrides(Event)
|
||||
def get_event_description(self) -> str:
|
||||
return (f'Message {self.message_id} from {self.user_id} "' + "".join(
|
||||
return (
|
||||
f'Message {self.message_id} from {self.user_id} "'
|
||||
+ "".join(
|
||||
map(
|
||||
lambda x: escape_tag(str(x))
|
||||
if x.is_text() else f"<le>{escape_tag(str(x))}</le>",
|
||||
self.message)) + '"')
|
||||
if x.is_text()
|
||||
else f"<le>{escape_tag(str(x))}</le>",
|
||||
self.message,
|
||||
)
|
||||
)
|
||||
+ '"'
|
||||
)
|
||||
|
||||
|
||||
class GroupMessageEvent(MessageEvent):
|
||||
"""群消息"""
|
||||
|
||||
__event__ = "message.group"
|
||||
message_type: Literal["group"]
|
||||
group_id: int
|
||||
@ -196,8 +209,13 @@ class GroupMessageEvent(MessageEvent):
|
||||
+ "".join(
|
||||
map(
|
||||
lambda x: escape_tag(str(x))
|
||||
if x.is_text() else f"<le>{escape_tag(str(x))}</le>",
|
||||
self.message)) + '"')
|
||||
if x.is_text()
|
||||
else f"<le>{escape_tag(str(x))}</le>",
|
||||
self.message,
|
||||
)
|
||||
)
|
||||
+ '"'
|
||||
)
|
||||
|
||||
@overrides(MessageEvent)
|
||||
def get_session_id(self) -> str:
|
||||
@ -207,6 +225,7 @@ class GroupMessageEvent(MessageEvent):
|
||||
# Notice Events
|
||||
class NoticeEvent(Event):
|
||||
"""通知事件"""
|
||||
|
||||
__event__ = "notice"
|
||||
post_type: Literal["notice"]
|
||||
notice_type: str
|
||||
@ -214,12 +233,14 @@ class NoticeEvent(Event):
|
||||
@overrides(Event)
|
||||
def get_event_name(self) -> str:
|
||||
sub_type = getattr(self, "sub_type", None)
|
||||
return f"{self.post_type}.{self.notice_type}" + (f".{sub_type}"
|
||||
if sub_type else "")
|
||||
return f"{self.post_type}.{self.notice_type}" + (
|
||||
f".{sub_type}" if sub_type else ""
|
||||
)
|
||||
|
||||
|
||||
class GroupUploadNoticeEvent(NoticeEvent):
|
||||
"""群文件上传事件"""
|
||||
|
||||
__event__ = "notice.group_upload"
|
||||
notice_type: Literal["group_upload"]
|
||||
user_id: int
|
||||
@ -237,6 +258,7 @@ class GroupUploadNoticeEvent(NoticeEvent):
|
||||
|
||||
class GroupAdminNoticeEvent(NoticeEvent):
|
||||
"""群管理员变动"""
|
||||
|
||||
__event__ = "notice.group_admin"
|
||||
notice_type: Literal["group_admin"]
|
||||
sub_type: str
|
||||
@ -258,6 +280,7 @@ class GroupAdminNoticeEvent(NoticeEvent):
|
||||
|
||||
class GroupDecreaseNoticeEvent(NoticeEvent):
|
||||
"""群成员减少事件"""
|
||||
|
||||
__event__ = "notice.group_decrease"
|
||||
notice_type: Literal["group_decrease"]
|
||||
sub_type: str
|
||||
@ -280,6 +303,7 @@ class GroupDecreaseNoticeEvent(NoticeEvent):
|
||||
|
||||
class GroupIncreaseNoticeEvent(NoticeEvent):
|
||||
"""群成员增加事件"""
|
||||
|
||||
__event__ = "notice.group_increase"
|
||||
notice_type: Literal["group_increase"]
|
||||
sub_type: str
|
||||
@ -302,6 +326,7 @@ class GroupIncreaseNoticeEvent(NoticeEvent):
|
||||
|
||||
class GroupBanNoticeEvent(NoticeEvent):
|
||||
"""群禁言事件"""
|
||||
|
||||
__event__ = "notice.group_ban"
|
||||
notice_type: Literal["group_ban"]
|
||||
sub_type: str
|
||||
@ -325,6 +350,7 @@ class GroupBanNoticeEvent(NoticeEvent):
|
||||
|
||||
class FriendAddNoticeEvent(NoticeEvent):
|
||||
"""好友添加事件"""
|
||||
|
||||
__event__ = "notice.friend_add"
|
||||
notice_type: Literal["friend_add"]
|
||||
user_id: int
|
||||
@ -340,6 +366,7 @@ class FriendAddNoticeEvent(NoticeEvent):
|
||||
|
||||
class GroupRecallNoticeEvent(NoticeEvent):
|
||||
"""群消息撤回事件"""
|
||||
|
||||
__event__ = "notice.group_recall"
|
||||
notice_type: Literal["group_recall"]
|
||||
user_id: int
|
||||
@ -362,6 +389,7 @@ class GroupRecallNoticeEvent(NoticeEvent):
|
||||
|
||||
class FriendRecallNoticeEvent(NoticeEvent):
|
||||
"""好友消息撤回事件"""
|
||||
|
||||
__event__ = "notice.friend_recall"
|
||||
notice_type: Literal["friend_recall"]
|
||||
user_id: int
|
||||
@ -378,6 +406,7 @@ class FriendRecallNoticeEvent(NoticeEvent):
|
||||
|
||||
class NotifyEvent(NoticeEvent):
|
||||
"""提醒事件"""
|
||||
|
||||
__event__ = "notice.notify"
|
||||
notice_type: Literal["notify"]
|
||||
sub_type: str
|
||||
@ -395,6 +424,7 @@ class NotifyEvent(NoticeEvent):
|
||||
|
||||
class PokeNotifyEvent(NotifyEvent):
|
||||
"""戳一戳提醒事件"""
|
||||
|
||||
__event__ = "notice.notify.poke"
|
||||
sub_type: Literal["poke"]
|
||||
target_id: int
|
||||
@ -413,6 +443,7 @@ class PokeNotifyEvent(NotifyEvent):
|
||||
|
||||
class LuckyKingNotifyEvent(NotifyEvent):
|
||||
"""群红包运气王提醒事件"""
|
||||
|
||||
__event__ = "notice.notify.lucky_king"
|
||||
sub_type: Literal["lucky_king"]
|
||||
target_id: int
|
||||
@ -432,6 +463,7 @@ class LuckyKingNotifyEvent(NotifyEvent):
|
||||
|
||||
class HonorNotifyEvent(NotifyEvent):
|
||||
"""群荣誉变更提醒事件"""
|
||||
|
||||
__event__ = "notice.notify.honor"
|
||||
sub_type: Literal["honor"]
|
||||
honor_type: str
|
||||
@ -444,6 +476,7 @@ class HonorNotifyEvent(NotifyEvent):
|
||||
# Request Events
|
||||
class RequestEvent(Event):
|
||||
"""请求事件"""
|
||||
|
||||
__event__ = "request"
|
||||
post_type: Literal["request"]
|
||||
request_type: str
|
||||
@ -451,12 +484,14 @@ class RequestEvent(Event):
|
||||
@overrides(Event)
|
||||
def get_event_name(self) -> str:
|
||||
sub_type = getattr(self, "sub_type", None)
|
||||
return f"{self.post_type}.{self.request_type}" + (f".{sub_type}"
|
||||
if sub_type else "")
|
||||
return f"{self.post_type}.{self.request_type}" + (
|
||||
f".{sub_type}" if sub_type else ""
|
||||
)
|
||||
|
||||
|
||||
class FriendRequestEvent(RequestEvent):
|
||||
"""加好友请求事件"""
|
||||
|
||||
__event__ = "request.friend"
|
||||
request_type: Literal["friend"]
|
||||
user_id: int
|
||||
@ -472,9 +507,9 @@ class FriendRequestEvent(RequestEvent):
|
||||
return str(self.user_id)
|
||||
|
||||
async def approve(self, bot: "Bot", remark: str = ""):
|
||||
return await bot.set_friend_add_request(flag=self.flag,
|
||||
approve=True,
|
||||
remark=remark)
|
||||
return await bot.set_friend_add_request(
|
||||
flag=self.flag, approve=True, remark=remark
|
||||
)
|
||||
|
||||
async def reject(self, bot: "Bot"):
|
||||
return await bot.set_friend_add_request(flag=self.flag, approve=False)
|
||||
@ -482,6 +517,7 @@ class FriendRequestEvent(RequestEvent):
|
||||
|
||||
class GroupRequestEvent(RequestEvent):
|
||||
"""加群请求/邀请事件"""
|
||||
|
||||
__event__ = "request.group"
|
||||
request_type: Literal["group"]
|
||||
sub_type: str
|
||||
@ -499,20 +535,20 @@ class GroupRequestEvent(RequestEvent):
|
||||
return f"group_{self.group_id}_{self.user_id}"
|
||||
|
||||
async def approve(self, bot: "Bot"):
|
||||
return await bot.set_group_add_request(flag=self.flag,
|
||||
sub_type=self.sub_type,
|
||||
approve=True)
|
||||
return await bot.set_group_add_request(
|
||||
flag=self.flag, sub_type=self.sub_type, approve=True
|
||||
)
|
||||
|
||||
async def reject(self, bot: "Bot", reason: str = ""):
|
||||
return await bot.set_group_add_request(flag=self.flag,
|
||||
sub_type=self.sub_type,
|
||||
approve=False,
|
||||
reason=reason)
|
||||
return await bot.set_group_add_request(
|
||||
flag=self.flag, sub_type=self.sub_type, approve=False, reason=reason
|
||||
)
|
||||
|
||||
|
||||
# Meta Events
|
||||
class MetaEvent(Event):
|
||||
"""元事件"""
|
||||
|
||||
__event__ = "meta_event"
|
||||
post_type: Literal["meta_event"]
|
||||
meta_event_type: str
|
||||
@ -520,8 +556,9 @@ class MetaEvent(Event):
|
||||
@overrides(Event)
|
||||
def get_event_name(self) -> str:
|
||||
sub_type = getattr(self, "sub_type", None)
|
||||
return f"{self.post_type}.{self.meta_event_type}" + (f".{sub_type}" if
|
||||
sub_type else "")
|
||||
return f"{self.post_type}.{self.meta_event_type}" + (
|
||||
f".{sub_type}" if sub_type else ""
|
||||
)
|
||||
|
||||
@overrides(Event)
|
||||
def get_log_string(self) -> str:
|
||||
@ -530,6 +567,7 @@ class MetaEvent(Event):
|
||||
|
||||
class LifecycleMetaEvent(MetaEvent):
|
||||
"""生命周期元事件"""
|
||||
|
||||
__event__ = "meta_event.lifecycle"
|
||||
meta_event_type: Literal["lifecycle"]
|
||||
sub_type: str
|
||||
@ -537,6 +575,7 @@ class LifecycleMetaEvent(MetaEvent):
|
||||
|
||||
class HeartbeatMetaEvent(MetaEvent):
|
||||
"""心跳元事件"""
|
||||
|
||||
__event__ = "meta_event.heartbeat"
|
||||
meta_event_type: Literal["heartbeat"]
|
||||
status: Status
|
||||
@ -567,12 +606,28 @@ def get_event_model(event_name) -> List[Type[Event]]:
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Event", "MessageEvent", "PrivateMessageEvent", "GroupMessageEvent",
|
||||
"NoticeEvent", "GroupUploadNoticeEvent", "GroupAdminNoticeEvent",
|
||||
"GroupDecreaseNoticeEvent", "GroupIncreaseNoticeEvent",
|
||||
"GroupBanNoticeEvent", "FriendAddNoticeEvent", "GroupRecallNoticeEvent",
|
||||
"FriendRecallNoticeEvent", "NotifyEvent", "PokeNotifyEvent",
|
||||
"LuckyKingNotifyEvent", "HonorNotifyEvent", "RequestEvent",
|
||||
"FriendRequestEvent", "GroupRequestEvent", "MetaEvent",
|
||||
"LifecycleMetaEvent", "HeartbeatMetaEvent", "get_event_model"
|
||||
"Event",
|
||||
"MessageEvent",
|
||||
"PrivateMessageEvent",
|
||||
"GroupMessageEvent",
|
||||
"NoticeEvent",
|
||||
"GroupUploadNoticeEvent",
|
||||
"GroupAdminNoticeEvent",
|
||||
"GroupDecreaseNoticeEvent",
|
||||
"GroupIncreaseNoticeEvent",
|
||||
"GroupBanNoticeEvent",
|
||||
"FriendAddNoticeEvent",
|
||||
"GroupRecallNoticeEvent",
|
||||
"FriendRecallNoticeEvent",
|
||||
"NotifyEvent",
|
||||
"PokeNotifyEvent",
|
||||
"LuckyKingNotifyEvent",
|
||||
"HonorNotifyEvent",
|
||||
"RequestEvent",
|
||||
"FriendRequestEvent",
|
||||
"GroupRequestEvent",
|
||||
"MetaEvent",
|
||||
"LifecycleMetaEvent",
|
||||
"HeartbeatMetaEvent",
|
||||
"get_event_model",
|
||||
]
|
||||
|
@ -8,7 +8,6 @@ from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable
|
||||
|
||||
|
||||
class CQHTTPAdapterException(AdapterException):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("cqhttp")
|
||||
|
||||
@ -33,8 +32,11 @@ class ActionFailed(BaseActionFailed, CQHTTPAdapterException):
|
||||
self.info = kwargs
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ActionFailed " + ", ".join(
|
||||
f"{k}={v}" for k, v in self.info.items()) + ">"
|
||||
return (
|
||||
f"<ActionFailed "
|
||||
+ ", ".join(f"{k}={v}" for k, v in self.info.items())
|
||||
+ ">"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
@ -5,10 +5,11 @@ from base64 import b64encode
|
||||
from typing import Any, Type, Tuple, Union, Mapping, Iterable, Optional, cast
|
||||
|
||||
from nonebot.typing import overrides
|
||||
from .utils import log, _b2s, escape, unescape
|
||||
from nonebot.adapters import Message as BaseMessage
|
||||
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
||||
|
||||
from .utils import log, _b2s, escape, unescape
|
||||
|
||||
|
||||
class MessageSegment(BaseMessageSegment["Message"]):
|
||||
"""
|
||||
@ -27,23 +28,24 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
|
||||
# process special types
|
||||
if type_ == "text":
|
||||
return escape(
|
||||
data.get("text", ""), # type: ignore
|
||||
escape_comma=False)
|
||||
return escape(data.get("text", ""), escape_comma=False) # type: ignore
|
||||
|
||||
params = ",".join(
|
||||
[f"{k}={escape(str(v))}" for k, v in data.items() if v is not None])
|
||||
[f"{k}={escape(str(v))}" for k, v in data.items() if v is not None]
|
||||
)
|
||||
return f"[CQ:{type_}{',' if params else ''}{params}]"
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def __add__(self, other) -> "Message":
|
||||
return Message(self) + (MessageSegment.text(other) if isinstance(
|
||||
other, str) else other)
|
||||
return Message(self) + (
|
||||
MessageSegment.text(other) if isinstance(other, str) else other
|
||||
)
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def __radd__(self, other) -> "Message":
|
||||
return (MessageSegment.text(other)
|
||||
if isinstance(other, str) else Message(other)) + self
|
||||
return (
|
||||
MessageSegment.text(other) if isinstance(other, str) else Message(other)
|
||||
) + self
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def is_text(self) -> bool:
|
||||
@ -83,11 +85,13 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
return MessageSegment("forward", {"id": id_})
|
||||
|
||||
@staticmethod
|
||||
def image(file: Union[str, bytes, BytesIO, Path],
|
||||
def image(
|
||||
file: Union[str, bytes, BytesIO, Path],
|
||||
type_: Optional[str] = None,
|
||||
cache: bool = True,
|
||||
proxy: bool = True,
|
||||
timeout: Optional[int] = None) -> "MessageSegment":
|
||||
timeout: Optional[int] = None,
|
||||
) -> "MessageSegment":
|
||||
if isinstance(file, BytesIO):
|
||||
file = file.getvalue()
|
||||
if isinstance(file, bytes):
|
||||
@ -95,74 +99,85 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
elif isinstance(file, Path):
|
||||
file = f"file:///{file.resolve()}"
|
||||
return MessageSegment(
|
||||
"image", {
|
||||
"image",
|
||||
{
|
||||
"file": file,
|
||||
"type": type_,
|
||||
"cache": _b2s(cache),
|
||||
"proxy": _b2s(proxy),
|
||||
"timeout": timeout
|
||||
})
|
||||
"timeout": timeout,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def json(data: str) -> "MessageSegment":
|
||||
return MessageSegment("json", {"data": data})
|
||||
|
||||
@staticmethod
|
||||
def location(latitude: float,
|
||||
def location(
|
||||
latitude: float,
|
||||
longitude: float,
|
||||
title: Optional[str] = None,
|
||||
content: Optional[str] = None) -> "MessageSegment":
|
||||
content: Optional[str] = None,
|
||||
) -> "MessageSegment":
|
||||
return MessageSegment(
|
||||
"location", {
|
||||
"location",
|
||||
{
|
||||
"lat": str(latitude),
|
||||
"lon": str(longitude),
|
||||
"title": title,
|
||||
"content": content
|
||||
})
|
||||
"content": content,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def music(type_: str, id_: int) -> "MessageSegment":
|
||||
return MessageSegment("music", {"type": type_, "id": id_})
|
||||
|
||||
@staticmethod
|
||||
def music_custom(url: str,
|
||||
def music_custom(
|
||||
url: str,
|
||||
audio: str,
|
||||
title: str,
|
||||
content: Optional[str] = None,
|
||||
img_url: Optional[str] = None) -> "MessageSegment":
|
||||
img_url: Optional[str] = None,
|
||||
) -> "MessageSegment":
|
||||
return MessageSegment(
|
||||
"music", {
|
||||
"music",
|
||||
{
|
||||
"type": "custom",
|
||||
"url": url,
|
||||
"audio": audio,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"image": img_url
|
||||
})
|
||||
"image": img_url,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def node(id_: int) -> "MessageSegment":
|
||||
return MessageSegment("node", {"id": str(id_)})
|
||||
|
||||
@staticmethod
|
||||
def node_custom(user_id: int, nickname: str,
|
||||
content: Union[str, "Message"]) -> "MessageSegment":
|
||||
return MessageSegment("node", {
|
||||
"user_id": str(user_id),
|
||||
"nickname": nickname,
|
||||
"content": content
|
||||
})
|
||||
def node_custom(
|
||||
user_id: int, nickname: str, content: Union[str, "Message"]
|
||||
) -> "MessageSegment":
|
||||
return MessageSegment(
|
||||
"node", {"user_id": str(user_id), "nickname": nickname, "content": content}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def poke(type_: str, id_: str) -> "MessageSegment":
|
||||
return MessageSegment("poke", {"type": type_, "id": id_})
|
||||
|
||||
@staticmethod
|
||||
def record(file: Union[str, bytes, BytesIO, Path],
|
||||
def record(
|
||||
file: Union[str, bytes, BytesIO, Path],
|
||||
magic: Optional[bool] = None,
|
||||
cache: Optional[bool] = None,
|
||||
proxy: Optional[bool] = None,
|
||||
timeout: Optional[int] = None) -> "MessageSegment":
|
||||
timeout: Optional[int] = None,
|
||||
) -> "MessageSegment":
|
||||
if isinstance(file, BytesIO):
|
||||
file = file.getvalue()
|
||||
if isinstance(file, bytes):
|
||||
@ -170,13 +185,15 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
elif isinstance(file, Path):
|
||||
file = f"file:///{file.resolve()}"
|
||||
return MessageSegment(
|
||||
"record", {
|
||||
"record",
|
||||
{
|
||||
"file": file,
|
||||
"magic": _b2s(magic),
|
||||
"cache": _b2s(cache),
|
||||
"proxy": _b2s(proxy),
|
||||
"timeout": timeout
|
||||
})
|
||||
"timeout": timeout,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def reply(id_: int) -> "MessageSegment":
|
||||
@ -191,26 +208,27 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
return MessageSegment("shake", {})
|
||||
|
||||
@staticmethod
|
||||
def share(url: str = "",
|
||||
def share(
|
||||
url: str = "",
|
||||
title: str = "",
|
||||
content: Optional[str] = None,
|
||||
image: Optional[str] = None) -> "MessageSegment":
|
||||
return MessageSegment("share", {
|
||||
"url": url,
|
||||
"title": title,
|
||||
"content": content,
|
||||
"image": image
|
||||
})
|
||||
image: Optional[str] = None,
|
||||
) -> "MessageSegment":
|
||||
return MessageSegment(
|
||||
"share", {"url": url, "title": title, "content": content, "image": image}
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def text(text: str) -> "MessageSegment":
|
||||
return MessageSegment("text", {"text": text})
|
||||
|
||||
@staticmethod
|
||||
def video(file: Union[str, bytes, BytesIO, Path],
|
||||
def video(
|
||||
file: Union[str, bytes, BytesIO, Path],
|
||||
cache: Optional[bool] = None,
|
||||
proxy: Optional[bool] = None,
|
||||
timeout: Optional[int] = None) -> "MessageSegment":
|
||||
timeout: Optional[int] = None,
|
||||
) -> "MessageSegment":
|
||||
if isinstance(file, BytesIO):
|
||||
file = file.getvalue()
|
||||
if isinstance(file, bytes):
|
||||
@ -218,12 +236,14 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
elif isinstance(file, Path):
|
||||
file = f"file:///{file.resolve()}"
|
||||
return MessageSegment(
|
||||
"video", {
|
||||
"video",
|
||||
{
|
||||
"file": file,
|
||||
"cache": _b2s(cache),
|
||||
"proxy": _b2s(proxy),
|
||||
"timeout": timeout
|
||||
})
|
||||
"timeout": timeout,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def xml(data: str) -> "MessageSegment":
|
||||
@ -241,22 +261,22 @@ class Message(BaseMessage[MessageSegment]):
|
||||
return MessageSegment
|
||||
|
||||
@overrides(BaseMessage)
|
||||
def __add__(self, other: Union[str, Mapping,
|
||||
Iterable[Mapping]]) -> "Message":
|
||||
def __add__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> "Message":
|
||||
return super(Message, self).__add__(
|
||||
MessageSegment.text(other) if isinstance(other, str) else other)
|
||||
MessageSegment.text(other) if isinstance(other, str) else other
|
||||
)
|
||||
|
||||
@overrides(BaseMessage)
|
||||
def __radd__(self, other: Union[str, Mapping,
|
||||
Iterable[Mapping]]) -> "Message":
|
||||
def __radd__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> "Message":
|
||||
return super(Message, self).__radd__(
|
||||
MessageSegment.text(other) if isinstance(other, str) else other)
|
||||
MessageSegment.text(other) if isinstance(other, str) else other
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@overrides(BaseMessage)
|
||||
def _construct(
|
||||
msg: Union[str, Mapping,
|
||||
Iterable[Mapping]]) -> Iterable[MessageSegment]:
|
||||
msg: Union[str, Mapping, Iterable[Mapping]]
|
||||
) -> Iterable[MessageSegment]:
|
||||
if isinstance(msg, Mapping):
|
||||
msg = cast(Mapping[str, Any], msg)
|
||||
yield MessageSegment(msg["type"], msg.get("data") or {})
|
||||
@ -273,11 +293,12 @@ class Message(BaseMessage[MessageSegment]):
|
||||
r"\[CQ:(?P<type>[a-zA-Z0-9-_.]+)"
|
||||
r"(?P<params>"
|
||||
r"(?:,[a-zA-Z0-9-_.]+=[^,\]]+)*"
|
||||
r"),?\]", msg):
|
||||
r"),?\]",
|
||||
msg,
|
||||
):
|
||||
yield "text", msg[text_begin : cqcode.pos + cqcode.start()]
|
||||
text_begin = cqcode.pos + cqcode.end()
|
||||
yield cqcode.group("type"), cqcode.group("params").lstrip(
|
||||
",")
|
||||
yield cqcode.group("type"), cqcode.group("params").lstrip(",")
|
||||
yield "text", msg[text_begin:]
|
||||
|
||||
for type_, data in _iter_message(msg):
|
||||
@ -287,10 +308,11 @@ class Message(BaseMessage[MessageSegment]):
|
||||
yield MessageSegment(type_, {"text": unescape(data)})
|
||||
else:
|
||||
data = {
|
||||
k: unescape(v) for k, v in map(
|
||||
k: unescape(v)
|
||||
for k, v in map(
|
||||
lambda x: x.split("=", maxsplit=1),
|
||||
filter(lambda x: x, (
|
||||
x.lstrip() for x in data.split(","))))
|
||||
filter(lambda x: x, (x.lstrip() for x in data.split(","))),
|
||||
)
|
||||
}
|
||||
yield MessageSegment(type_, data)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.permission import Permission
|
||||
|
||||
from .event import GroupMessageEvent, PrivateMessageEvent
|
||||
|
||||
|
||||
@ -42,8 +43,7 @@ async def _group(event: Event) -> bool:
|
||||
|
||||
|
||||
async def _group_member(event: Event) -> bool:
|
||||
return isinstance(event,
|
||||
GroupMessageEvent) and event.sender.role == "member"
|
||||
return isinstance(event, GroupMessageEvent) and event.sender.role == "member"
|
||||
|
||||
|
||||
async def _group_admin(event: Event) -> bool:
|
||||
@ -76,6 +76,12 @@ GROUP_OWNER = Permission(_group_owner)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"PRIVATE", "PRIVATE_FRIEND", "PRIVATE_GROUP", "PRIVATE_OTHER", "GROUP",
|
||||
"GROUP_MEMBER", "GROUP_ADMIN", "GROUP_OWNER"
|
||||
"PRIVATE",
|
||||
"PRIVATE_FRIEND",
|
||||
"PRIVATE_GROUP",
|
||||
"PRIVATE_OTHER",
|
||||
"GROUP",
|
||||
"GROUP_MEMBER",
|
||||
"GROUP_ADMIN",
|
||||
"GROUP_OWNER",
|
||||
]
|
||||
|
@ -16,9 +16,7 @@ def escape(s: str, *, escape_comma: bool = True) -> str:
|
||||
* ``s: str``: 需要转义的字符串
|
||||
* ``escape_comma: bool``: 是否转义逗号(``,``)。
|
||||
"""
|
||||
s = s.replace("&", "&") \
|
||||
.replace("[", "[") \
|
||||
.replace("]", "]")
|
||||
s = s.replace("&", "&").replace("[", "[").replace("]", "]")
|
||||
if escape_comma:
|
||||
s = s.replace(",", ",")
|
||||
return s
|
||||
@ -34,10 +32,12 @@ def unescape(s: str) -> str:
|
||||
|
||||
* ``s: str``: 需要转义的字符串
|
||||
"""
|
||||
return s.replace(",", ",") \
|
||||
.replace("[", "[") \
|
||||
.replace("]", "]") \
|
||||
return (
|
||||
s.replace(",", ",")
|
||||
.replace("[", "[")
|
||||
.replace("]", "]")
|
||||
.replace("&", "&")
|
||||
)
|
||||
|
||||
|
||||
def _b2s(b: Optional[bool]) -> Optional[str]:
|
||||
|
@ -34,6 +34,21 @@ nonebot2 = { path = "../../", develop = true }
|
||||
# url = "https://mirrors.aliyun.com/pypi/simple/"
|
||||
# default = true
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
@ -16,10 +16,18 @@ from nonebot.drivers import Driver, HTTPRequest, HTTPResponse, HTTPConnection
|
||||
from .config import Config as DingConfig
|
||||
from .utils import log, calc_hmac_base64
|
||||
from .message import Message, MessageSegment
|
||||
from .exception import (ActionFailed, NetworkError, SessionExpired,
|
||||
ApiNotAvailable)
|
||||
from .event import (MessageEvent, ConversationType, GroupMessageEvent,
|
||||
PrivateMessageEvent)
|
||||
from .exception import (
|
||||
ActionFailed,
|
||||
NetworkError,
|
||||
SessionExpired,
|
||||
ApiNotAvailable,
|
||||
)
|
||||
from .event import (
|
||||
MessageEvent,
|
||||
ConversationType,
|
||||
GroupMessageEvent,
|
||||
PrivateMessageEvent,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.config import Config
|
||||
@ -31,6 +39,7 @@ class Bot(BaseBot):
|
||||
"""
|
||||
钉钉 协议 Bot 适配。继承属性参考 `BaseBot <./#class-basebot>`_ 。
|
||||
"""
|
||||
|
||||
ding_config: DingConfig
|
||||
|
||||
@property
|
||||
@ -48,8 +57,8 @@ class Bot(BaseBot):
|
||||
@classmethod
|
||||
@overrides(BaseBot)
|
||||
async def check_permission(
|
||||
cls, driver: Driver,
|
||||
request: HTTPConnection) -> Tuple[Optional[str], HTTPResponse]:
|
||||
cls, driver: Driver, request: HTTPConnection
|
||||
) -> Tuple[Optional[str], HTTPResponse]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -61,7 +70,8 @@ class Bot(BaseBot):
|
||||
# 检查连接方式
|
||||
if not isinstance(request, HTTPRequest):
|
||||
return None, HTTPResponse(
|
||||
405, b"Unsupported connection type, available type: `http`")
|
||||
405, b"Unsupported connection type, available type: `http`"
|
||||
)
|
||||
|
||||
# 检查 timestamp
|
||||
if not timestamp:
|
||||
@ -74,13 +84,15 @@ class Bot(BaseBot):
|
||||
log("WARNING", "Missing Signature Header")
|
||||
return None, HTTPResponse(400, b"Missing `sign` Header")
|
||||
sign_base64 = calc_hmac_base64(str(timestamp), secret)
|
||||
if sign != sign_base64.decode('utf-8'):
|
||||
if sign != sign_base64.decode("utf-8"):
|
||||
log("WARNING", "Signature Header is invalid")
|
||||
return None, HTTPResponse(403, b"Signature is invalid")
|
||||
else:
|
||||
log("WARNING", "Ding signature check ignored!")
|
||||
return (json.loads(request.body.decode())["chatbotUserId"],
|
||||
HTTPResponse(204, b''))
|
||||
return (
|
||||
json.loads(request.body.decode())["chatbotUserId"],
|
||||
HTTPResponse(204, b""),
|
||||
)
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def handle_message(self, message: bytes):
|
||||
@ -111,10 +123,9 @@ class Bot(BaseBot):
|
||||
return
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def _call_api(self,
|
||||
api: str,
|
||||
event: Optional[MessageEvent] = None,
|
||||
**data) -> Any:
|
||||
async def _call_api(
|
||||
self, api: str, event: Optional[MessageEvent] = None, **data
|
||||
) -> Any:
|
||||
if not isinstance(self.request, HTTPRequest):
|
||||
log("ERROR", "Only support http connection.")
|
||||
return
|
||||
@ -138,7 +149,8 @@ class Bot(BaseBot):
|
||||
if event:
|
||||
# 确保 sessionWebhook 没有过期
|
||||
if int(datetime.now().timestamp()) > int(
|
||||
event.sessionWebhookExpiredTime / 1000):
|
||||
event.sessionWebhookExpiredTime / 1000
|
||||
):
|
||||
raise SessionExpired
|
||||
|
||||
webhook = event.sessionWebhook
|
||||
@ -150,32 +162,37 @@ class Bot(BaseBot):
|
||||
if not message:
|
||||
raise ValueError("Message not found")
|
||||
try:
|
||||
async with httpx.AsyncClient(headers=headers,
|
||||
follow_redirects=True) as client:
|
||||
response = await client.post(webhook,
|
||||
async with httpx.AsyncClient(
|
||||
headers=headers, follow_redirects=True
|
||||
) as client:
|
||||
response = await client.post(
|
||||
webhook,
|
||||
params=params,
|
||||
json=message._produce(),
|
||||
timeout=self.config.api_timeout)
|
||||
timeout=self.config.api_timeout,
|
||||
)
|
||||
|
||||
if 200 <= response.status_code < 300:
|
||||
result = response.json()
|
||||
if isinstance(result, dict):
|
||||
if result.get("errcode") != 0:
|
||||
raise ActionFailed(errcode=result.get("errcode"),
|
||||
errmsg=result.get("errmsg"))
|
||||
raise ActionFailed(
|
||||
errcode=result.get("errcode"), errmsg=result.get("errmsg")
|
||||
)
|
||||
return result
|
||||
raise NetworkError(f"HTTP request received unexpected "
|
||||
f"status code: {response.status_code}")
|
||||
raise NetworkError(
|
||||
f"HTTP request received unexpected "
|
||||
f"status code: {response.status_code}"
|
||||
)
|
||||
except httpx.InvalidURL:
|
||||
raise NetworkError("API root url invalid")
|
||||
except httpx.HTTPError:
|
||||
raise NetworkError("HTTP request failed")
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def call_api(self,
|
||||
api: str,
|
||||
event: Optional[MessageEvent] = None,
|
||||
**data) -> Any:
|
||||
async def call_api(
|
||||
self, api: str, event: Optional[MessageEvent] = None, **data
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -199,13 +216,15 @@ class Bot(BaseBot):
|
||||
return await super().call_api(api, event=event, **data)
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def send(self,
|
||||
async def send(
|
||||
self,
|
||||
event: MessageEvent,
|
||||
message: Union[str, "Message", "MessageSegment"],
|
||||
at_sender: bool = False,
|
||||
webhook: Optional[str] = None,
|
||||
secret: Optional[str] = None,
|
||||
**kwargs) -> Any:
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -241,9 +260,11 @@ class Bot(BaseBot):
|
||||
params.update(kwargs)
|
||||
|
||||
if at_sender and event.conversationType != ConversationType.private:
|
||||
params[
|
||||
"message"] = f"@{event.senderId} " + msg + MessageSegment.atDingtalkIds(
|
||||
event.senderId)
|
||||
params["message"] = (
|
||||
f"@{event.senderId} "
|
||||
+ msg
|
||||
+ MessageSegment.atDingtalkIds(event.senderId)
|
||||
)
|
||||
else:
|
||||
params["message"] = msg
|
||||
|
||||
|
@ -12,6 +12,7 @@ class Config(BaseModel):
|
||||
- ``access_token`` / ``ding_access_token``: 钉钉令牌
|
||||
- ``secret`` / ``ding_secret``: 钉钉 HTTP 上报数据签名口令
|
||||
"""
|
||||
|
||||
secret: Optional[str] = Field(default=None, alias="ding_secret")
|
||||
access_token: Optional[str] = Field(default=None, alias="ding_access_token")
|
||||
|
||||
|
@ -69,6 +69,7 @@ class ConversationType(str, Enum):
|
||||
|
||||
class MessageEvent(Event):
|
||||
"""消息事件"""
|
||||
|
||||
msgtype: str
|
||||
text: TextMessage
|
||||
msgId: str
|
||||
@ -88,11 +89,10 @@ class MessageEvent(Event):
|
||||
def gen_message(cls, values: dict):
|
||||
assert "msgtype" in values, "msgtype must be specified"
|
||||
# 其实目前钉钉机器人只能接收到 text 类型的消息
|
||||
assert values[
|
||||
"msgtype"] in values, f"{values['msgtype']} must be specified"
|
||||
content = values[values['msgtype']]['content']
|
||||
assert values["msgtype"] in values, f"{values['msgtype']} must be specified"
|
||||
content = values[values["msgtype"]]["content"]
|
||||
# 如果是被 @,第一个字符将会为空格,移除特殊情况
|
||||
if content[0] == ' ':
|
||||
if content[0] == " ":
|
||||
content = content[1:]
|
||||
values["message"] = content
|
||||
return values
|
||||
@ -128,6 +128,7 @@ class MessageEvent(Event):
|
||||
|
||||
class PrivateMessageEvent(MessageEvent):
|
||||
"""私聊消息事件"""
|
||||
|
||||
chatbotCorpId: str
|
||||
senderStaffId: Optional[str]
|
||||
conversationType: ConversationType = ConversationType.private
|
||||
@ -135,6 +136,7 @@ class PrivateMessageEvent(MessageEvent):
|
||||
|
||||
class GroupMessageEvent(MessageEvent):
|
||||
"""群消息事件"""
|
||||
|
||||
atUsers: List[AtUsersItem]
|
||||
conversationType: ConversationType = ConversationType.group
|
||||
conversationTitle: str
|
||||
|
@ -1,9 +1,9 @@
|
||||
from typing import Optional
|
||||
|
||||
from nonebot.exception import (AdapterException, ActionFailed as
|
||||
BaseActionFailed, ApiNotAvailable as
|
||||
BaseApiNotAvailable, NetworkError as
|
||||
BaseNetworkError)
|
||||
from nonebot.exception import AdapterException
|
||||
from nonebot.exception import ActionFailed as BaseActionFailed
|
||||
from nonebot.exception import NetworkError as BaseNetworkError
|
||||
from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable
|
||||
|
||||
|
||||
class DingAdapterException(AdapterException):
|
||||
@ -29,15 +29,13 @@ class ActionFailed(BaseActionFailed, DingAdapterException):
|
||||
* ``errmsg: Optional[str]``: 错误信息
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
errcode: Optional[int] = None,
|
||||
errmsg: Optional[str] = None):
|
||||
def __init__(self, errcode: Optional[int] = None, errmsg: Optional[str] = None):
|
||||
super().__init__()
|
||||
self.errcode = errcode
|
||||
self.errmsg = errmsg
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ApiError errcode={self.errcode} errmsg=\"{self.errmsg}\">"
|
||||
return f'<ApiError errcode={self.errcode} errmsg="{self.errmsg}">'
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
@ -77,10 +77,9 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
def code(code_language: str, code: str) -> "Message":
|
||||
"""发送 code 消息段"""
|
||||
message = MessageSegment.text(code)
|
||||
message += MessageSegment.extension({
|
||||
"text_type": "code_snippet",
|
||||
"code_language": code_language
|
||||
})
|
||||
message += MessageSegment.extension(
|
||||
{"text_type": "code_snippet", "code_language": code_language}
|
||||
)
|
||||
return message
|
||||
|
||||
@staticmethod
|
||||
@ -95,16 +94,19 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def actionCardSingleBtn(title: str, text: str, singleTitle: str,
|
||||
singleURL) -> "MessageSegment":
|
||||
def actionCardSingleBtn(
|
||||
title: str, text: str, singleTitle: str, singleURL
|
||||
) -> "MessageSegment":
|
||||
"""发送 ``actionCardSingleBtn`` 类型消息"""
|
||||
return MessageSegment(
|
||||
"actionCard", {
|
||||
"actionCard",
|
||||
{
|
||||
"title": title,
|
||||
"text": text,
|
||||
"singleTitle": singleTitle,
|
||||
"singleURL": singleURL
|
||||
})
|
||||
"singleURL": singleURL,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def actionCardMultiBtns(
|
||||
@ -112,7 +114,7 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
text: str,
|
||||
btns: list,
|
||||
hideAvatar: bool = False,
|
||||
btnOrientation: str = '1',
|
||||
btnOrientation: str = "1",
|
||||
) -> "MessageSegment":
|
||||
"""
|
||||
发送 ``actionCardMultiBtn`` 类型消息
|
||||
@ -123,13 +125,15 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
* ``btns``: ``[{ "title": title, "actionURL": actionURL }, ...]``
|
||||
"""
|
||||
return MessageSegment(
|
||||
"actionCard", {
|
||||
"actionCard",
|
||||
{
|
||||
"title": title,
|
||||
"text": text,
|
||||
"hideAvatar": "1" if hideAvatar else "0",
|
||||
"btnOrientation": btnOrientation,
|
||||
"btns": btns
|
||||
})
|
||||
"btns": btns,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def feedCard(links: list) -> "MessageSegment":
|
||||
@ -144,7 +148,7 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
|
||||
@staticmethod
|
||||
def raw(data) -> "MessageSegment":
|
||||
return MessageSegment('raw', data)
|
||||
return MessageSegment("raw", data)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
# 让用户可以直接发送原始的消息格式
|
||||
@ -171,8 +175,8 @@ class Message(BaseMessage[MessageSegment]):
|
||||
@staticmethod
|
||||
@overrides(BaseMessage)
|
||||
def _construct(
|
||||
msg: Union[str, Mapping,
|
||||
Iterable[Mapping]]) -> Iterable[MessageSegment]:
|
||||
msg: Union[str, Mapping, Iterable[Mapping]]
|
||||
) -> Iterable[MessageSegment]:
|
||||
if isinstance(msg, Mapping):
|
||||
msg = cast(Mapping[str, Any], msg)
|
||||
yield MessageSegment(msg["type"], msg.get("data") or {})
|
||||
@ -187,10 +191,11 @@ class Message(BaseMessage[MessageSegment]):
|
||||
segment: MessageSegment
|
||||
for segment in self:
|
||||
# text 可以和 text 合并
|
||||
if segment.type == "text" and data.get("msgtype") == 'text':
|
||||
if segment.type == "text" and data.get("msgtype") == "text":
|
||||
data.setdefault("text", {})
|
||||
data["text"]["content"] = data["text"].setdefault(
|
||||
"content", "") + segment.data["content"]
|
||||
data["text"]["content"] = (
|
||||
data["text"].setdefault("content", "") + segment.data["content"]
|
||||
)
|
||||
else:
|
||||
data.update(segment.to_dict())
|
||||
return data
|
||||
|
@ -8,10 +8,10 @@ log = logger_wrapper("DING")
|
||||
|
||||
|
||||
def calc_hmac_base64(timestamp: str, secret: str):
|
||||
secret_enc = secret.encode('utf-8')
|
||||
string_to_sign = '{}\n{}'.format(timestamp, secret)
|
||||
string_to_sign_enc = string_to_sign.encode('utf-8')
|
||||
hmac_code = hmac.new(secret_enc,
|
||||
string_to_sign_enc,
|
||||
digestmod=hashlib.sha256).digest()
|
||||
secret_enc = secret.encode("utf-8")
|
||||
string_to_sign = "{}\n{}".format(timestamp, secret)
|
||||
string_to_sign_enc = string_to_sign.encode("utf-8")
|
||||
hmac_code = hmac.new(
|
||||
secret_enc, string_to_sign_enc, digestmod=hashlib.sha256
|
||||
).digest()
|
||||
return base64.b64encode(hmac_code)
|
||||
|
@ -34,6 +34,21 @@ nonebot2 = { path = "../../", develop = true }
|
||||
# url = "https://mirrors.aliyun.com/pypi/simple/"
|
||||
# default = true
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
@ -7,7 +7,7 @@ aiocache_logger.setLevel(logging.DEBUG)
|
||||
aiocache_logger.handlers.clear()
|
||||
aiocache_logger.addHandler(LoguruHandler())
|
||||
|
||||
from .bot import Bot as Bot
|
||||
from .event import *
|
||||
from .bot import Bot as Bot
|
||||
from .message import Message as Message
|
||||
from .message import MessageSegment as MessageSegment
|
||||
|
@ -1,24 +1,39 @@
|
||||
import re
|
||||
import json
|
||||
from typing import (TYPE_CHECKING, Any, Dict, Tuple, Union, Iterable, Optional,
|
||||
AsyncIterable, cast)
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Tuple,
|
||||
Union,
|
||||
Iterable,
|
||||
Optional,
|
||||
AsyncIterable,
|
||||
cast,
|
||||
)
|
||||
|
||||
import httpx
|
||||
from aiocache import Cache, cached
|
||||
from aiocache.serializers import PickleSerializer
|
||||
|
||||
from nonebot.log import logger
|
||||
from .utils import AESCipher, log
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.utils import escape_tag
|
||||
from nonebot.message import handle_event
|
||||
from .config import Config as FeishuConfig
|
||||
from nonebot.adapters import Bot as BaseBot
|
||||
from nonebot.drivers import Driver, HTTPRequest, HTTPResponse
|
||||
|
||||
from .utils import AESCipher, log
|
||||
from .config import Config as FeishuConfig
|
||||
from .message import Message, MessageSegment, MessageSerializer
|
||||
from .exception import ActionFailed, NetworkError, ApiNotAvailable
|
||||
from .event import (Event, MessageEvent, GroupMessageEvent, PrivateMessageEvent,
|
||||
get_event_model)
|
||||
from .event import (
|
||||
Event,
|
||||
MessageEvent,
|
||||
GroupMessageEvent,
|
||||
PrivateMessageEvent,
|
||||
get_event_model,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from nonebot.config import Config
|
||||
@ -47,8 +62,10 @@ def _check_at_me(bot: "Bot", event: "Event"):
|
||||
event.to_me = True
|
||||
|
||||
for index, segment in enumerate(message):
|
||||
if segment.type == "at" and segment.data.get(
|
||||
"user_name") in bot.config.nickname:
|
||||
if (
|
||||
segment.type == "at"
|
||||
and segment.data.get("user_name") in bot.config.nickname
|
||||
):
|
||||
event.to_me = True
|
||||
del event.event.message.content[index]
|
||||
return
|
||||
@ -57,7 +74,8 @@ def _check_at_me(bot: "Bot", event: "Event"):
|
||||
if mention["name"] in bot.config.nickname:
|
||||
event.to_me = True
|
||||
segment.data["text"] = segment.data["text"].replace(
|
||||
f"@{mention['name']}", "")
|
||||
f"@{mention['name']}", ""
|
||||
)
|
||||
segment.data["text"] = segment.data["text"].lstrip()
|
||||
break
|
||||
else:
|
||||
@ -92,8 +110,7 @@ def _check_nickname(bot: "Bot", event: "Event"):
|
||||
if nicknames:
|
||||
# check if the user is calling me with my nickname
|
||||
nickname_regex = "|".join(nicknames)
|
||||
m = re.search(rf"^({nickname_regex})([\s,,]*|$)", first_text,
|
||||
re.IGNORECASE)
|
||||
m = re.search(rf"^({nickname_regex})([\s,,]*|$)", first_text, re.IGNORECASE)
|
||||
if m:
|
||||
nickname = m.group(1)
|
||||
log("DEBUG", f"User is calling me {nickname}")
|
||||
@ -102,8 +119,9 @@ def _check_nickname(bot: "Bot", event: "Event"):
|
||||
|
||||
|
||||
def _handle_api_result(
|
||||
result: Union[Optional[Dict[str, Any]], str, bytes, Iterable[bytes],
|
||||
AsyncIterable[bytes]]
|
||||
result: Union[
|
||||
Optional[Dict[str, Any]], str, bytes, Iterable[bytes], AsyncIterable[bytes]
|
||||
]
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
@ -158,10 +176,10 @@ class Bot(BaseBot):
|
||||
cls, driver: Driver, request: HTTPRequest
|
||||
) -> Tuple[Optional[str], Optional[HTTPResponse]]:
|
||||
if not isinstance(request, HTTPRequest):
|
||||
log("WARNING",
|
||||
"Unsupported connection type, available type: `http`")
|
||||
log("WARNING", "Unsupported connection type, available type: `http`")
|
||||
return None, HTTPResponse(
|
||||
405, b"Unsupported connection type, available type: `http`")
|
||||
405, b"Unsupported connection type, available type: `http`"
|
||||
)
|
||||
|
||||
encrypt_key = cls.feishu_config.encrypt_key
|
||||
if encrypt_key:
|
||||
@ -174,16 +192,13 @@ class Bot(BaseBot):
|
||||
challenge = data.get("challenge")
|
||||
if challenge:
|
||||
return data.get("token"), HTTPResponse(
|
||||
200,
|
||||
json.dumps({
|
||||
"challenge": challenge
|
||||
}).encode())
|
||||
200, json.dumps({"challenge": challenge}).encode()
|
||||
)
|
||||
|
||||
schema = data.get("schema")
|
||||
if not schema:
|
||||
return None, HTTPResponse(
|
||||
400,
|
||||
b"Missing `schema` in POST body, only accept event of version 2.0"
|
||||
400, b"Missing `schema` in POST body, only accept event of version 2.0"
|
||||
)
|
||||
|
||||
headers = data.get("header")
|
||||
@ -196,15 +211,13 @@ class Bot(BaseBot):
|
||||
|
||||
if not token:
|
||||
log("WARNING", "Missing `verification token` in POST body")
|
||||
return None, HTTPResponse(
|
||||
400, b"Missing `verification token` in POST body")
|
||||
return None, HTTPResponse(400, b"Missing `verification token` in POST body")
|
||||
else:
|
||||
if token != cls.feishu_config.verification_token:
|
||||
log("WARNING", "Verification token check failed")
|
||||
return None, HTTPResponse(403,
|
||||
b"Verification token check failed")
|
||||
return None, HTTPResponse(403, b"Verification token check failed")
|
||||
|
||||
return app_id, HTTPResponse(200, b'')
|
||||
return app_id, HTTPResponse(200, b"")
|
||||
|
||||
async def handle_message(self, message: bytes):
|
||||
"""
|
||||
@ -245,28 +258,32 @@ class Bot(BaseBot):
|
||||
def _construct_url(self, path: str) -> str:
|
||||
return self.api_root + path
|
||||
|
||||
@cached(ttl=60 * 60,
|
||||
@cached(
|
||||
ttl=60 * 60,
|
||||
cache=Cache.MEMORY,
|
||||
key="_feishu_tenant_access_token",
|
||||
serializer=PickleSerializer())
|
||||
serializer=PickleSerializer(),
|
||||
)
|
||||
async def _fetch_tenant_access_token(self) -> str:
|
||||
try:
|
||||
async with httpx.AsyncClient(follow_redirects=True) as client:
|
||||
response = await client.post(
|
||||
self._construct_url(
|
||||
"auth/v3/tenant_access_token/internal/"),
|
||||
self._construct_url("auth/v3/tenant_access_token/internal/"),
|
||||
json={
|
||||
"app_id": self.feishu_config.app_id,
|
||||
"app_secret": self.feishu_config.app_secret
|
||||
"app_secret": self.feishu_config.app_secret,
|
||||
},
|
||||
timeout=self.config.api_timeout)
|
||||
timeout=self.config.api_timeout,
|
||||
)
|
||||
|
||||
if 200 <= response.status_code < 300:
|
||||
result = response.json()
|
||||
return result["tenant_access_token"]
|
||||
else:
|
||||
raise NetworkError(f"HTTP request received unexpected "
|
||||
f"status code: {response.status_code}")
|
||||
raise NetworkError(
|
||||
f"HTTP request received unexpected "
|
||||
f"status code: {response.status_code}"
|
||||
)
|
||||
except httpx.InvalidURL:
|
||||
raise NetworkError("API root url invalid")
|
||||
except httpx.HTTPError:
|
||||
@ -280,30 +297,37 @@ class Bot(BaseBot):
|
||||
raise ApiNotAvailable
|
||||
|
||||
headers = {}
|
||||
self.feishu_config.tenant_access_token = await self._fetch_tenant_access_token(
|
||||
self.feishu_config.tenant_access_token = (
|
||||
await self._fetch_tenant_access_token()
|
||||
)
|
||||
headers["Authorization"] = (
|
||||
"Bearer " + self.feishu_config.tenant_access_token
|
||||
)
|
||||
headers[
|
||||
"Authorization"] = "Bearer " + self.feishu_config.tenant_access_token
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=self.config.api_timeout,
|
||||
follow_redirects=True) as client:
|
||||
async with httpx.AsyncClient(
|
||||
timeout=self.config.api_timeout, follow_redirects=True
|
||||
) as client:
|
||||
response = await client.send(
|
||||
httpx.Request(data["method"],
|
||||
httpx.Request(
|
||||
data["method"],
|
||||
self.api_root + api,
|
||||
json=data.get("body", {}),
|
||||
params=data.get("query", {}),
|
||||
headers=headers))
|
||||
headers=headers,
|
||||
)
|
||||
)
|
||||
if 200 <= response.status_code < 300:
|
||||
if response.headers["content-type"].startswith(
|
||||
"application/json"):
|
||||
if response.headers["content-type"].startswith("application/json"):
|
||||
result = response.json()
|
||||
else:
|
||||
result = response.content
|
||||
return _handle_api_result(result)
|
||||
raise NetworkError(f"HTTP request received unexpected "
|
||||
raise NetworkError(
|
||||
f"HTTP request received unexpected "
|
||||
f"status code: {response.status_code} "
|
||||
f"response body: {response.text}")
|
||||
f"response body: {response.text}"
|
||||
)
|
||||
except httpx.InvalidURL:
|
||||
raise NetworkError("API root url invalid")
|
||||
except httpx.HTTPError:
|
||||
@ -333,11 +357,13 @@ class Bot(BaseBot):
|
||||
return await super().call_api(api, **data)
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def send(self,
|
||||
async def send(
|
||||
self,
|
||||
event: Event,
|
||||
message: Union[str, Message, MessageSegment],
|
||||
at_sender: bool = False,
|
||||
**kwargs) -> Any:
|
||||
**kwargs,
|
||||
) -> Any:
|
||||
msg = message if isinstance(message, Message) else Message(message)
|
||||
|
||||
if isinstance(event, GroupMessageEvent):
|
||||
@ -346,7 +372,8 @@ class Bot(BaseBot):
|
||||
receive_id, receive_id_type = event.get_user_id(), "open_id"
|
||||
else:
|
||||
raise ValueError(
|
||||
"Cannot guess `receive_id` and `receive_id_type` to reply!")
|
||||
"Cannot guess `receive_id` and `receive_id_type` to reply!"
|
||||
)
|
||||
|
||||
at_sender = at_sender and bool(event.get_user_id())
|
||||
|
||||
@ -357,14 +384,12 @@ class Bot(BaseBot):
|
||||
|
||||
params = {
|
||||
"method": "POST",
|
||||
"query": {
|
||||
"receive_id_type": receive_id_type
|
||||
},
|
||||
"query": {"receive_id_type": receive_id_type},
|
||||
"body": {
|
||||
"receive_id": receive_id,
|
||||
"content": content,
|
||||
"msg_type": msg_type
|
||||
}
|
||||
"msg_type": msg_type,
|
||||
},
|
||||
}
|
||||
|
||||
return await self.call_api(f"im/v1/messages", **params)
|
||||
|
@ -17,13 +17,16 @@ class Config(BaseModel):
|
||||
- ``is_lark`` / ``feishu_is_lark``: 是否使用Lark(飞书海外版),默认为 false
|
||||
|
||||
"""
|
||||
|
||||
app_id: Optional[str] = Field(default=None, alias="feishu_app_id")
|
||||
app_secret: Optional[str] = Field(default=None, alias="feishu_app_secret")
|
||||
encrypt_key: Optional[str] = Field(default=None, alias="feishu_encrypt_key")
|
||||
verification_token: Optional[str] = Field(default=None,
|
||||
alias="feishu_verification_token")
|
||||
verification_token: Optional[str] = Field(
|
||||
default=None, alias="feishu_verification_token"
|
||||
)
|
||||
tenant_access_token: Optional[str] = Field(
|
||||
default=None, alias="feishu_tenant_access_token")
|
||||
default=None, alias="feishu_tenant_access_token"
|
||||
)
|
||||
is_lark: Optional[str] = Field(default=False, alias="feishu_is_lark")
|
||||
|
||||
class Config:
|
||||
|
@ -1,12 +1,12 @@
|
||||
import inspect
|
||||
import json
|
||||
from typing import Any, Dict, List, Literal, Optional, Type
|
||||
import inspect
|
||||
from typing import Any, Dict, List, Type, Literal, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, root_validator
|
||||
from pygtrie import StringTrie
|
||||
from pydantic import Field, BaseModel, root_validator
|
||||
|
||||
from nonebot.adapters import Event as BaseEvent
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.adapters import Event as BaseEvent
|
||||
|
||||
from .message import Message, MessageDeserializer
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
from typing import Optional
|
||||
|
||||
from nonebot.exception import ActionFailed as BaseActionFailed
|
||||
from nonebot.exception import AdapterException
|
||||
from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable
|
||||
from nonebot.exception import ActionFailed as BaseActionFailed
|
||||
from nonebot.exception import NetworkError as BaseNetworkError
|
||||
from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable
|
||||
|
||||
|
||||
class FeishuAdapterException(AdapterException):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__("feishu")
|
||||
|
||||
@ -28,8 +27,11 @@ class ActionFailed(BaseActionFailed, FeishuAdapterException):
|
||||
self.info = kwargs
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ActionFailed " + ", ".join(
|
||||
f"{k}={v}" for k, v in self.info.items()) + ">"
|
||||
return (
|
||||
f"<ActionFailed "
|
||||
+ ", ".join(f"{k}={v}" for k, v in self.info.items())
|
||||
+ ">"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
@ -1,8 +1,18 @@
|
||||
import json
|
||||
import itertools
|
||||
from dataclasses import dataclass
|
||||
from typing import (Any, Dict, List, Type, Tuple, Union, Mapping, Iterable,
|
||||
Optional, cast)
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
List,
|
||||
Type,
|
||||
Tuple,
|
||||
Union,
|
||||
Mapping,
|
||||
Iterable,
|
||||
Optional,
|
||||
cast,
|
||||
)
|
||||
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.adapters import Message as BaseMessage
|
||||
@ -34,7 +44,7 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
"share_user": "[个人名片]",
|
||||
"system": "[系统消息]",
|
||||
"location": "[位置]",
|
||||
"video_chat": "[视频通话]"
|
||||
"video_chat": "[视频通话]",
|
||||
}
|
||||
|
||||
def __str__(self) -> str:
|
||||
@ -47,13 +57,15 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def __add__(self, other) -> "Message":
|
||||
return Message(self) + (MessageSegment.text(other) if isinstance(
|
||||
other, str) else other)
|
||||
return Message(self) + (
|
||||
MessageSegment.text(other) if isinstance(other, str) else other
|
||||
)
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def __radd__(self, other) -> "Message":
|
||||
return (MessageSegment.text(other)
|
||||
if isinstance(other, str) else Message(other)) + self
|
||||
return (
|
||||
MessageSegment.text(other) if isinstance(other, str) else Message(other)
|
||||
) + self
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def is_text(self) -> bool:
|
||||
@ -79,10 +91,7 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
|
||||
@staticmethod
|
||||
def interactive(title: str, elements: list) -> "MessageSegment":
|
||||
return MessageSegment("interactive", {
|
||||
"title": title,
|
||||
"elements": elements
|
||||
})
|
||||
return MessageSegment("interactive", {"title": title, "elements": elements})
|
||||
|
||||
@staticmethod
|
||||
def share_chat(chat_id: str) -> "MessageSegment":
|
||||
@ -94,28 +103,25 @@ class MessageSegment(BaseMessageSegment["Message"]):
|
||||
|
||||
@staticmethod
|
||||
def audio(file_key: str, duration: int) -> "MessageSegment":
|
||||
return MessageSegment("audio", {
|
||||
"file_key": file_key,
|
||||
"duration": duration
|
||||
})
|
||||
return MessageSegment("audio", {"file_key": file_key, "duration": duration})
|
||||
|
||||
@staticmethod
|
||||
def media(file_key: str, image_key: str, file_name: str,
|
||||
duration: int) -> "MessageSegment":
|
||||
def media(
|
||||
file_key: str, image_key: str, file_name: str, duration: int
|
||||
) -> "MessageSegment":
|
||||
return MessageSegment(
|
||||
"media", {
|
||||
"media",
|
||||
{
|
||||
"file_key": file_key,
|
||||
"image_key": image_key,
|
||||
"file_name": file_name,
|
||||
"duration": duration
|
||||
})
|
||||
"duration": duration,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def file(file_key: str, file_name: str) -> "MessageSegment":
|
||||
return MessageSegment("file", {
|
||||
"file_key": file_key,
|
||||
"file_name": file_name
|
||||
})
|
||||
return MessageSegment("file", {"file_key": file_key, "file_name": file_name})
|
||||
|
||||
@staticmethod
|
||||
def sticker(file_key) -> "MessageSegment":
|
||||
@ -133,22 +139,22 @@ class Message(BaseMessage[MessageSegment]):
|
||||
return MessageSegment
|
||||
|
||||
@overrides(BaseMessage)
|
||||
def __add__(self, other: Union[str, Mapping,
|
||||
Iterable[Mapping]]) -> "Message":
|
||||
def __add__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> "Message":
|
||||
return super(Message, self).__add__(
|
||||
MessageSegment.text(other) if isinstance(other, str) else other)
|
||||
MessageSegment.text(other) if isinstance(other, str) else other
|
||||
)
|
||||
|
||||
@overrides(BaseMessage)
|
||||
def __radd__(self, other: Union[str, Mapping,
|
||||
Iterable[Mapping]]) -> "Message":
|
||||
def __radd__(self, other: Union[str, Mapping, Iterable[Mapping]]) -> "Message":
|
||||
return super(Message, self).__radd__(
|
||||
MessageSegment.text(other) if isinstance(other, str) else other)
|
||||
MessageSegment.text(other) if isinstance(other, str) else other
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@overrides(BaseMessage)
|
||||
def _construct(
|
||||
msg: Union[str, Mapping,
|
||||
Iterable[Mapping]]) -> Iterable[MessageSegment]:
|
||||
msg: Union[str, Mapping, Iterable[Mapping]]
|
||||
) -> Iterable[MessageSegment]:
|
||||
if isinstance(msg, Mapping):
|
||||
msg = cast(Mapping[str, Any], msg)
|
||||
yield MessageSegment(msg["type"], msg.get("data") or {})
|
||||
@ -169,7 +175,8 @@ class Message(BaseMessage[MessageSegment]):
|
||||
for i, seg in enumerate(self):
|
||||
if seg.type == "text" and i != 0 and msg[-1].type == "text":
|
||||
msg[-1] = MessageSegment(
|
||||
"text", {"text": msg[-1].data["text"] + seg.data["text"]})
|
||||
"text", {"text": msg[-1].data["text"] + seg.data["text"]}
|
||||
)
|
||||
else:
|
||||
msg.append(seg)
|
||||
return Message(msg)
|
||||
@ -184,6 +191,7 @@ class MessageSerializer:
|
||||
"""
|
||||
飞书 协议 Message 序列化器。
|
||||
"""
|
||||
|
||||
message: Message
|
||||
|
||||
def serialize(self) -> Tuple[str, str]:
|
||||
@ -198,10 +206,12 @@ class MessageSerializer:
|
||||
else:
|
||||
if last_segment_type == "image":
|
||||
msg["content"].append([])
|
||||
msg["content"][-1].append({
|
||||
msg["content"][-1].append(
|
||||
{
|
||||
"tag": segment.type if segment.type != "image" else "img",
|
||||
**segment.data
|
||||
})
|
||||
**segment.data,
|
||||
}
|
||||
)
|
||||
last_segment_type = segment.type
|
||||
return "post", json.dumps({"zh_cn": {**msg}})
|
||||
|
||||
@ -214,6 +224,7 @@ class MessageDeserializer:
|
||||
"""
|
||||
飞书 协议 Message 反序列化器。
|
||||
"""
|
||||
|
||||
type: str
|
||||
data: Dict[str, Any]
|
||||
mentions: Optional[List[dict]]
|
||||
@ -227,14 +238,13 @@ class MessageDeserializer:
|
||||
if self.type == "post":
|
||||
msg = Message()
|
||||
if self.data["title"] != "":
|
||||
msg += MessageSegment("text", {'text': self.data["title"]})
|
||||
msg += MessageSegment("text", {"text": self.data["title"]})
|
||||
|
||||
for seg in itertools.chain(*self.data["content"]):
|
||||
tag = seg.pop("tag")
|
||||
if tag == "at":
|
||||
seg["user_name"] = dict_mention[seg["user_id"]]["name"]
|
||||
seg["user_id"] = dict_mention[
|
||||
seg["user_id"]]["id"]["open_id"]
|
||||
seg["user_id"] = dict_mention[seg["user_id"]]["id"]["open_id"]
|
||||
|
||||
msg += MessageSegment(tag if tag != "img" else "image", seg)
|
||||
|
||||
@ -242,7 +252,8 @@ class MessageDeserializer:
|
||||
elif self.type == "text":
|
||||
for key, mention in dict_mention.items():
|
||||
self.data["text"] = self.data["text"].replace(
|
||||
key, f"@{mention['name']}")
|
||||
key, f"@{mention['name']}"
|
||||
)
|
||||
self.data["mentions"] = dict_mention
|
||||
|
||||
return Message(MessageSegment(self.type, self.data))
|
||||
|
@ -9,16 +9,15 @@ log = logger_wrapper("FEISHU")
|
||||
|
||||
|
||||
class AESCipher(object):
|
||||
|
||||
def __init__(self, key):
|
||||
self.block_size = AES.block_size
|
||||
self.key = hashlib.sha256(AESCipher.str_to_bytes(key)).digest()
|
||||
|
||||
@staticmethod
|
||||
def str_to_bytes(data):
|
||||
u_type = type(b"".decode('utf8'))
|
||||
u_type = type(b"".decode("utf8"))
|
||||
if isinstance(data, u_type):
|
||||
return data.encode('utf8')
|
||||
return data.encode("utf8")
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
@ -32,4 +31,4 @@ class AESCipher(object):
|
||||
|
||||
def decrypt_string(self, enc):
|
||||
enc = base64.b64decode(enc)
|
||||
return self.decrypt(enc).decode('utf8')
|
||||
return self.decrypt(enc).decode("utf8")
|
||||
|
@ -36,6 +36,21 @@ nonebot2 = { path = "../../", develop = true }
|
||||
# url = "https://mirrors.aliyun.com/pypi/simple/"
|
||||
# default = true
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
@ -12,8 +12,15 @@ from nonebot.config import Config
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.adapters import Bot as BaseBot
|
||||
from nonebot.exception import ApiNotAvailable
|
||||
from nonebot.drivers import (Driver, WebSocket, HTTPResponse, ForwardDriver,
|
||||
ReverseDriver, HTTPConnection, WebSocketSetup)
|
||||
from nonebot.drivers import (
|
||||
Driver,
|
||||
WebSocket,
|
||||
HTTPResponse,
|
||||
ForwardDriver,
|
||||
ReverseDriver,
|
||||
HTTPConnection,
|
||||
WebSocketSetup,
|
||||
)
|
||||
|
||||
from .config import Config as MiraiConfig
|
||||
from .message import MessageChain, MessageSegment
|
||||
@ -23,16 +30,14 @@ from .utils import Log, process_event, argument_validation, catch_network_error
|
||||
|
||||
class SessionManager:
|
||||
"""Bot会话管理器, 提供API主动调用接口"""
|
||||
|
||||
sessions: Dict[int, Tuple[str, httpx.AsyncClient]] = {}
|
||||
|
||||
def __init__(self, session_key: str, client: httpx.AsyncClient):
|
||||
self.session_key, self.client = session_key, client
|
||||
|
||||
@catch_network_error
|
||||
async def post(self,
|
||||
path: str,
|
||||
*,
|
||||
params: Optional[Dict[str, Any]] = None) -> Any:
|
||||
async def post(self, path: str, *, params: Optional[Dict[str, Any]] = None) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -51,7 +56,7 @@ class SessionManager:
|
||||
path,
|
||||
json={
|
||||
**(params or {}),
|
||||
'sessionKey': self.session_key,
|
||||
"sessionKey": self.session_key,
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
@ -59,10 +64,9 @@ class SessionManager:
|
||||
return response.json()
|
||||
|
||||
@catch_network_error
|
||||
async def request(self,
|
||||
path: str,
|
||||
*,
|
||||
params: Optional[Dict[str, Any]] = None) -> Any:
|
||||
async def request(
|
||||
self, path: str, *, params: Optional[Dict[str, Any]] = None
|
||||
) -> Any:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -77,7 +81,7 @@ class SessionManager:
|
||||
path,
|
||||
params={
|
||||
**(params or {}),
|
||||
'sessionKey': self.session_key,
|
||||
"sessionKey": self.session_key,
|
||||
},
|
||||
timeout=3,
|
||||
)
|
||||
@ -98,7 +102,7 @@ class SessionManager:
|
||||
"""
|
||||
files = {k: v for k, v in params.items() if isinstance(v, BytesIO)}
|
||||
form = {k: v for k, v in params.items() if k not in files}
|
||||
form['sessionKey'] = self.session_key
|
||||
form["sessionKey"] = self.session_key
|
||||
response = await self.client.post(
|
||||
path,
|
||||
data=form,
|
||||
@ -109,25 +113,25 @@ class SessionManager:
|
||||
return response.json()
|
||||
|
||||
@classmethod
|
||||
async def new(cls, self_id: int, *, host: IPv4Address, port: int,
|
||||
auth_key: str) -> "SessionManager":
|
||||
async def new(
|
||||
cls, self_id: int, *, host: IPv4Address, port: int, auth_key: str
|
||||
) -> "SessionManager":
|
||||
session = cls.get(self_id)
|
||||
if session is not None:
|
||||
return session
|
||||
|
||||
client = httpx.AsyncClient(base_url=f'http://{host}:{port}',
|
||||
follow_redirects=True)
|
||||
response = await client.post('/auth', json={'authKey': auth_key})
|
||||
client = httpx.AsyncClient(
|
||||
base_url=f"http://{host}:{port}", follow_redirects=True
|
||||
)
|
||||
response = await client.post("/auth", json={"authKey": auth_key})
|
||||
response.raise_for_status()
|
||||
auth = response.json()
|
||||
assert auth['code'] == 0
|
||||
session_key = auth['session']
|
||||
response = await client.post('/verify',
|
||||
json={
|
||||
'sessionKey': session_key,
|
||||
'qq': self_id
|
||||
})
|
||||
assert response.json()['code'] == 0
|
||||
assert auth["code"] == 0
|
||||
session_key = auth["session"]
|
||||
response = await client.post(
|
||||
"/verify", json={"sessionKey": session_key, "qq": self_id}
|
||||
)
|
||||
assert response.json()["code"] == 0
|
||||
cls.sessions[self_id] = session_key, client
|
||||
|
||||
return cls(session_key, client)
|
||||
@ -152,7 +156,7 @@ class Bot(BaseBot):
|
||||
|
||||
"""
|
||||
|
||||
_type = 'mirai'
|
||||
_type = "mirai"
|
||||
|
||||
@property
|
||||
@overrides(BaseBot)
|
||||
@ -166,37 +170,42 @@ class Bot(BaseBot):
|
||||
if api is None:
|
||||
if isinstance(self.request, WebSocket):
|
||||
asyncio.create_task(self.request.close(1000))
|
||||
assert api is not None, 'SessionManager has not been initialized'
|
||||
assert api is not None, "SessionManager has not been initialized"
|
||||
return api
|
||||
|
||||
@classmethod
|
||||
@overrides(BaseBot)
|
||||
async def check_permission(
|
||||
cls, driver: Driver,
|
||||
request: HTTPConnection) -> Tuple[Optional[str], HTTPResponse]:
|
||||
cls, driver: Driver, request: HTTPConnection
|
||||
) -> Tuple[Optional[str], HTTPResponse]:
|
||||
if isinstance(request, WebSocket):
|
||||
return None, HTTPResponse(
|
||||
501, b'Websocket connection is not implemented')
|
||||
self_id: Optional[str] = request.headers.get('bot')
|
||||
return None, HTTPResponse(501, b"Websocket connection is not implemented")
|
||||
self_id: Optional[str] = request.headers.get("bot")
|
||||
if self_id is None:
|
||||
return None, HTTPResponse(400, b'Header `Bot` is required.')
|
||||
return None, HTTPResponse(400, b"Header `Bot` is required.")
|
||||
self_id = str(self_id).strip()
|
||||
await SessionManager.new(
|
||||
int(self_id),
|
||||
host=cls.mirai_config.host, # type: ignore
|
||||
port=cls.mirai_config.port, # type: ignore
|
||||
auth_key=cls.mirai_config.auth_key) # type: ignore
|
||||
return self_id, HTTPResponse(204, b'')
|
||||
auth_key=cls.mirai_config.auth_key, # type: ignore
|
||||
)
|
||||
return self_id, HTTPResponse(204, b"")
|
||||
|
||||
@classmethod
|
||||
@overrides(BaseBot)
|
||||
def register(cls,
|
||||
def register(
|
||||
cls,
|
||||
driver: Driver,
|
||||
config: "Config",
|
||||
qq: Optional[Union[int, List[int]]] = None):
|
||||
qq: Optional[Union[int, List[int]]] = None,
|
||||
):
|
||||
cls.mirai_config = MiraiConfig(**config.dict())
|
||||
if (cls.mirai_config.auth_key and cls.mirai_config.host and
|
||||
cls.mirai_config.port) is None:
|
||||
if (
|
||||
cls.mirai_config.auth_key
|
||||
and cls.mirai_config.host
|
||||
and cls.mirai_config.port
|
||||
) is None:
|
||||
raise ApiNotAvailable(cls._type)
|
||||
|
||||
super().register(driver, config)
|
||||
@ -209,17 +218,25 @@ class Bot(BaseBot):
|
||||
self_ids = [qq] if isinstance(qq, int) else qq
|
||||
|
||||
async def url_factory(qq: int):
|
||||
assert cls.mirai_config.host and cls.mirai_config.port and cls.mirai_config.auth_key
|
||||
assert (
|
||||
cls.mirai_config.host
|
||||
and cls.mirai_config.port
|
||||
and cls.mirai_config.auth_key
|
||||
)
|
||||
session = await SessionManager.new(
|
||||
qq,
|
||||
host=cls.mirai_config.host,
|
||||
port=cls.mirai_config.port,
|
||||
auth_key=cls.mirai_config.auth_key)
|
||||
auth_key=cls.mirai_config.auth_key,
|
||||
)
|
||||
return WebSocketSetup(
|
||||
adapter=cls._type,
|
||||
self_id=str(qq),
|
||||
url=(f'ws://{cls.mirai_config.host}:{cls.mirai_config.port}'
|
||||
f'/all?sessionKey={session.session_key}'))
|
||||
url=(
|
||||
f"ws://{cls.mirai_config.host}:{cls.mirai_config.port}"
|
||||
f"/all?sessionKey={session.session_key}"
|
||||
),
|
||||
)
|
||||
|
||||
for self_id in self_ids:
|
||||
driver.setup_websocket(partial(url_factory, qq=self_id))
|
||||
@ -234,13 +251,15 @@ class Bot(BaseBot):
|
||||
try:
|
||||
await process_event(
|
||||
bot=self,
|
||||
event=Event.new({
|
||||
event=Event.new(
|
||||
{
|
||||
**json.loads(message),
|
||||
'self_id': self.self_id,
|
||||
}),
|
||||
"self_id": self.self_id,
|
||||
}
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
Log.error(f'Failed to handle message: {message}', e)
|
||||
Log.error(f"Failed to handle message: {message}", e)
|
||||
|
||||
@overrides(BaseBot)
|
||||
async def _call_api(self, api: str, **data) -> NoReturn:
|
||||
@ -266,10 +285,12 @@ class Bot(BaseBot):
|
||||
|
||||
@overrides(BaseBot)
|
||||
@argument_validation
|
||||
async def send(self,
|
||||
async def send(
|
||||
self,
|
||||
event: Event,
|
||||
message: Union[MessageChain, MessageSegment, str],
|
||||
at_sender: bool = False):
|
||||
at_sender: bool = False,
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -284,23 +305,24 @@ class Bot(BaseBot):
|
||||
if not isinstance(message, MessageChain):
|
||||
message = MessageChain(message)
|
||||
if isinstance(event, FriendMessage):
|
||||
return await self.send_friend_message(target=event.sender.id,
|
||||
message_chain=message)
|
||||
return await self.send_friend_message(
|
||||
target=event.sender.id, message_chain=message
|
||||
)
|
||||
elif isinstance(event, GroupMessage):
|
||||
if at_sender:
|
||||
message = MessageSegment.at(event.sender.id) + message
|
||||
return await self.send_group_message(group=event.sender.group.id,
|
||||
message_chain=message)
|
||||
return await self.send_group_message(
|
||||
group=event.sender.group.id, message_chain=message
|
||||
)
|
||||
elif isinstance(event, TempMessage):
|
||||
return await self.send_temp_message(qq=event.sender.id,
|
||||
group=event.sender.group.id,
|
||||
message_chain=message)
|
||||
return await self.send_temp_message(
|
||||
qq=event.sender.id, group=event.sender.group.id, message_chain=message
|
||||
)
|
||||
else:
|
||||
raise ValueError(f'Unsupported event type {event!r}.')
|
||||
raise ValueError(f"Unsupported event type {event!r}.")
|
||||
|
||||
@argument_validation
|
||||
async def send_friend_message(self, target: int,
|
||||
message_chain: MessageChain):
|
||||
async def send_friend_message(self, target: int, message_chain: MessageChain):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -311,15 +333,13 @@ class Bot(BaseBot):
|
||||
* ``target: int``: 发送消息目标好友的 QQ 号
|
||||
* ``message_chain: MessageChain``: 消息链,是一个消息对象构成的数组
|
||||
"""
|
||||
return await self.api.post('sendFriendMessage',
|
||||
params={
|
||||
'target': target,
|
||||
'messageChain': message_chain.export()
|
||||
})
|
||||
return await self.api.post(
|
||||
"sendFriendMessage",
|
||||
params={"target": target, "messageChain": message_chain.export()},
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def send_temp_message(self, qq: int, group: int,
|
||||
message_chain: MessageChain):
|
||||
async def send_temp_message(self, qq: int, group: int, message_chain: MessageChain):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -331,18 +351,15 @@ class Bot(BaseBot):
|
||||
* ``group: int``: 临时会话群号
|
||||
* ``message_chain: MessageChain``: 消息链,是一个消息对象构成的数组
|
||||
"""
|
||||
return await self.api.post('sendTempMessage',
|
||||
params={
|
||||
'qq': qq,
|
||||
'group': group,
|
||||
'messageChain': message_chain.export()
|
||||
})
|
||||
return await self.api.post(
|
||||
"sendTempMessage",
|
||||
params={"qq": qq, "group": group, "messageChain": message_chain.export()},
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def send_group_message(self,
|
||||
group: int,
|
||||
message_chain: MessageChain,
|
||||
quote: Optional[int] = None):
|
||||
async def send_group_message(
|
||||
self, group: int, message_chain: MessageChain, quote: Optional[int] = None
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -354,12 +371,14 @@ class Bot(BaseBot):
|
||||
* ``message_chain: MessageChain``: 消息链,是一个消息对象构成的数组
|
||||
* ``quote: Optional[int]``: 引用一条消息的 message_id 进行回复
|
||||
"""
|
||||
return await self.api.post('sendGroupMessage',
|
||||
return await self.api.post(
|
||||
"sendGroupMessage",
|
||||
params={
|
||||
'group': group,
|
||||
'messageChain': message_chain.export(),
|
||||
'quote': quote
|
||||
})
|
||||
"group": group,
|
||||
"messageChain": message_chain.export(),
|
||||
"quote": quote,
|
||||
},
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def recall(self, target: int):
|
||||
@ -372,11 +391,12 @@ class Bot(BaseBot):
|
||||
|
||||
* ``target: int``: 需要撤回的消息的message_id
|
||||
"""
|
||||
return await self.api.post('recall', params={'target': target})
|
||||
return await self.api.post("recall", params={"target": target})
|
||||
|
||||
@argument_validation
|
||||
async def send_image_message(self, target: int, qq: int, group: int,
|
||||
urls: List[str]) -> List[str]:
|
||||
async def send_image_message(
|
||||
self, target: int, qq: int, group: int, urls: List[str]
|
||||
) -> List[str]:
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -396,13 +416,10 @@ class Bot(BaseBot):
|
||||
|
||||
- ``List[str]``: 一个包含图片imageId的数组
|
||||
"""
|
||||
return await self.api.post('sendImageMessage',
|
||||
params={
|
||||
'target': target,
|
||||
'qq': qq,
|
||||
'group': group,
|
||||
'urls': urls
|
||||
})
|
||||
return await self.api.post(
|
||||
"sendImageMessage",
|
||||
params={"target": target, "qq": qq, "group": group, "urls": urls},
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def upload_image(self, type: str, img: BytesIO):
|
||||
@ -416,11 +433,7 @@ class Bot(BaseBot):
|
||||
* ``type: str``: "friend" 或 "group" 或 "temp"
|
||||
* ``img: BytesIO``: 图片的BytesIO对象
|
||||
"""
|
||||
return await self.api.upload('uploadImage',
|
||||
params={
|
||||
'type': type,
|
||||
'img': img
|
||||
})
|
||||
return await self.api.upload("uploadImage", params={"type": type, "img": img})
|
||||
|
||||
@argument_validation
|
||||
async def upload_voice(self, type: str, voice: BytesIO):
|
||||
@ -434,11 +447,9 @@ class Bot(BaseBot):
|
||||
* ``type: str``: 当前仅支持 "group"
|
||||
* ``voice: BytesIO``: 语音的BytesIO对象
|
||||
"""
|
||||
return await self.api.upload('uploadVoice',
|
||||
params={
|
||||
'type': type,
|
||||
'voice': voice
|
||||
})
|
||||
return await self.api.upload(
|
||||
"uploadVoice", params={"type": type, "voice": voice}
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def fetch_message(self, count: int = 10):
|
||||
@ -452,7 +463,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``count: int``: 获取消息和事件的数量
|
||||
"""
|
||||
return await self.api.request('fetchMessage', params={'count': count})
|
||||
return await self.api.request("fetchMessage", params={"count": count})
|
||||
|
||||
@argument_validation
|
||||
async def fetch_latest_message(self, count: int = 10):
|
||||
@ -466,8 +477,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``count: int``: 获取消息和事件的数量
|
||||
"""
|
||||
return await self.api.request('fetchLatestMessage',
|
||||
params={'count': count})
|
||||
return await self.api.request("fetchLatestMessage", params={"count": count})
|
||||
|
||||
@argument_validation
|
||||
async def peek_message(self, count: int = 10):
|
||||
@ -481,7 +491,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``count: int``: 获取消息和事件的数量
|
||||
"""
|
||||
return await self.api.request('peekMessage', params={'count': count})
|
||||
return await self.api.request("peekMessage", params={"count": count})
|
||||
|
||||
@argument_validation
|
||||
async def peek_latest_message(self, count: int = 10):
|
||||
@ -495,8 +505,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``count: int``: 获取消息和事件的数量
|
||||
"""
|
||||
return await self.api.request('peekLatestMessage',
|
||||
params={'count': count})
|
||||
return await self.api.request("peekLatestMessage", params={"count": count})
|
||||
|
||||
@argument_validation
|
||||
async def messsage_from_id(self, id: int):
|
||||
@ -510,7 +519,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``id: int``: 获取消息的message_id
|
||||
"""
|
||||
return await self.api.request('messageFromId', params={'id': id})
|
||||
return await self.api.request("messageFromId", params={"id": id})
|
||||
|
||||
@argument_validation
|
||||
async def count_message(self):
|
||||
@ -519,7 +528,7 @@ class Bot(BaseBot):
|
||||
|
||||
使用此方法获取bot接收并缓存的消息总数,注意不包含被删除的
|
||||
"""
|
||||
return await self.api.request('countMessage')
|
||||
return await self.api.request("countMessage")
|
||||
|
||||
@argument_validation
|
||||
async def friend_list(self) -> List[Dict[str, Any]]:
|
||||
@ -532,7 +541,7 @@ class Bot(BaseBot):
|
||||
|
||||
- ``List[Dict[str, Any]]``: 返回的好友列表数据
|
||||
"""
|
||||
return await self.api.request('friendList')
|
||||
return await self.api.request("friendList")
|
||||
|
||||
@argument_validation
|
||||
async def group_list(self) -> List[Dict[str, Any]]:
|
||||
@ -545,7 +554,7 @@ class Bot(BaseBot):
|
||||
|
||||
- ``List[Dict[str, Any]]``: 返回的群列表数据
|
||||
"""
|
||||
return await self.api.request('groupList')
|
||||
return await self.api.request("groupList")
|
||||
|
||||
@argument_validation
|
||||
async def member_list(self, target: int) -> List[Dict[str, Any]]:
|
||||
@ -562,7 +571,7 @@ class Bot(BaseBot):
|
||||
|
||||
- ``List[Dict[str, Any]]``: 返回的群成员列表数据
|
||||
"""
|
||||
return await self.api.request('memberList', params={'target': target})
|
||||
return await self.api.request("memberList", params={"target": target})
|
||||
|
||||
@argument_validation
|
||||
async def mute(self, target: int, member_id: int, time: int):
|
||||
@ -577,12 +586,9 @@ class Bot(BaseBot):
|
||||
* ``member_id: int``: 指定群员QQ号
|
||||
* ``time: int``: 禁言时长,单位为秒,最多30天
|
||||
"""
|
||||
return await self.api.post('mute',
|
||||
params={
|
||||
'target': target,
|
||||
'memberId': member_id,
|
||||
'time': time
|
||||
})
|
||||
return await self.api.post(
|
||||
"mute", params={"target": target, "memberId": member_id, "time": time}
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def unmute(self, target: int, member_id: int):
|
||||
@ -596,11 +602,9 @@ class Bot(BaseBot):
|
||||
* ``target: int``: 指定群的群号
|
||||
* ``member_id: int``: 指定群员QQ号
|
||||
"""
|
||||
return await self.api.post('unmute',
|
||||
params={
|
||||
'target': target,
|
||||
'memberId': member_id
|
||||
})
|
||||
return await self.api.post(
|
||||
"unmute", params={"target": target, "memberId": member_id}
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def kick(self, target: int, member_id: int, msg: str):
|
||||
@ -615,12 +619,9 @@ class Bot(BaseBot):
|
||||
* ``member_id: int``: 指定群员QQ号
|
||||
* ``msg: str``: 信息
|
||||
"""
|
||||
return await self.api.post('kick',
|
||||
params={
|
||||
'target': target,
|
||||
'memberId': member_id,
|
||||
'msg': msg
|
||||
})
|
||||
return await self.api.post(
|
||||
"kick", params={"target": target, "memberId": member_id, "msg": msg}
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def quit(self, target: int):
|
||||
@ -633,7 +634,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``target: int``: 退出的群号
|
||||
"""
|
||||
return await self.api.post('quit', params={'target': target})
|
||||
return await self.api.post("quit", params={"target": target})
|
||||
|
||||
@argument_validation
|
||||
async def mute_all(self, target: int):
|
||||
@ -646,7 +647,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``target: int``: 指定群的群号
|
||||
"""
|
||||
return await self.api.post('muteAll', params={'target': target})
|
||||
return await self.api.post("muteAll", params={"target": target})
|
||||
|
||||
@argument_validation
|
||||
async def unmute_all(self, target: int):
|
||||
@ -659,7 +660,7 @@ class Bot(BaseBot):
|
||||
|
||||
* ``target: int``: 指定群的群号
|
||||
"""
|
||||
return await self.api.post('unmuteAll', params={'target': target})
|
||||
return await self.api.post("unmuteAll", params={"target": target})
|
||||
|
||||
@argument_validation
|
||||
async def group_config(self, target: int):
|
||||
@ -685,7 +686,7 @@ class Bot(BaseBot):
|
||||
"anonymousChat": true
|
||||
}
|
||||
"""
|
||||
return await self.api.request('groupConfig', params={'target': target})
|
||||
return await self.api.request("groupConfig", params={"target": target})
|
||||
|
||||
@argument_validation
|
||||
async def modify_group_config(self, target: int, config: Dict[str, Any]):
|
||||
@ -699,11 +700,9 @@ class Bot(BaseBot):
|
||||
* ``target: int``: 指定群的群号
|
||||
* ``config: Dict[str, Any]``: 群设置, 格式见 ``group_config`` 的返回值
|
||||
"""
|
||||
return await self.api.post('groupConfig',
|
||||
params={
|
||||
'target': target,
|
||||
'config': config
|
||||
})
|
||||
return await self.api.post(
|
||||
"groupConfig", params={"target": target, "config": config}
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def member_info(self, target: int, member_id: int):
|
||||
@ -726,15 +725,14 @@ class Bot(BaseBot):
|
||||
"specialTitle": "群头衔"
|
||||
}
|
||||
"""
|
||||
return await self.api.request('memberInfo',
|
||||
params={
|
||||
'target': target,
|
||||
'memberId': member_id
|
||||
})
|
||||
return await self.api.request(
|
||||
"memberInfo", params={"target": target, "memberId": member_id}
|
||||
)
|
||||
|
||||
@argument_validation
|
||||
async def modify_member_info(self, target: int, member_id: int,
|
||||
info: Dict[str, Any]):
|
||||
async def modify_member_info(
|
||||
self, target: int, member_id: int, info: Dict[str, Any]
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -746,9 +744,6 @@ class Bot(BaseBot):
|
||||
* ``member_id: int``: 群员QQ号
|
||||
* ``info: Dict[str, Any]``: 群员资料, 格式见 ``member_info`` 的返回值
|
||||
"""
|
||||
return await self.api.post('memberInfo',
|
||||
params={
|
||||
'target': target,
|
||||
'memberId': member_id,
|
||||
'info': info
|
||||
})
|
||||
return await self.api.post(
|
||||
"memberInfo", params={"target": target, "memberId": member_id, "info": info}
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
from ipaddress import IPv4Address
|
||||
from typing import Optional
|
||||
from ipaddress import IPv4Address
|
||||
|
||||
from pydantic import BaseModel, Extra, Field
|
||||
from pydantic import Extra, Field, BaseModel
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
@ -14,9 +14,10 @@ class Config(BaseModel):
|
||||
- ``mirai_host``: mirai-api-http 的地址
|
||||
- ``mirai_port``: mirai-api-http 的端口
|
||||
"""
|
||||
auth_key: Optional[str] = Field(None, alias='mirai_auth_key')
|
||||
host: Optional[IPv4Address] = Field(None, alias='mirai_host')
|
||||
port: Optional[int] = Field(None, alias='mirai_port')
|
||||
|
||||
auth_key: Optional[str] = Field(None, alias="mirai_auth_key")
|
||||
host: Optional[IPv4Address] = Field(None, alias="mirai_host")
|
||||
port: Optional[int] = Field(None, alias="mirai_port")
|
||||
|
||||
class Config:
|
||||
extra = Extra.ignore
|
||||
|
@ -5,25 +5,56 @@ r"""
|
||||
部分字段可能与文档在符号上不一致
|
||||
\:\:\:
|
||||
"""
|
||||
from .base import (Event, GroupChatInfo, GroupInfo, PrivateChatInfo,
|
||||
UserPermission)
|
||||
from .message import *
|
||||
from .notice import *
|
||||
from .message import *
|
||||
from .request import *
|
||||
from .base import (
|
||||
Event,
|
||||
GroupInfo,
|
||||
GroupChatInfo,
|
||||
UserPermission,
|
||||
PrivateChatInfo,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'Event', 'GroupChatInfo', 'GroupInfo', 'PrivateChatInfo', 'UserPermission',
|
||||
'MessageSource', 'MessageEvent', 'GroupMessage', 'FriendMessage',
|
||||
'TempMessage', 'NoticeEvent', 'MuteEvent', 'BotMuteEvent', 'BotUnmuteEvent',
|
||||
'MemberMuteEvent', 'MemberUnmuteEvent', 'BotJoinGroupEvent',
|
||||
'BotLeaveEventActive', 'BotLeaveEventKick', 'MemberJoinEvent',
|
||||
'MemberLeaveEventKick', 'MemberLeaveEventQuit', 'FriendRecallEvent',
|
||||
'GroupRecallEvent', 'GroupStateChangeEvent', 'GroupNameChangeEvent',
|
||||
'GroupEntranceAnnouncementChangeEvent', 'GroupMuteAllEvent',
|
||||
'GroupAllowAnonymousChatEvent', 'GroupAllowConfessTalkEvent',
|
||||
'GroupAllowMemberInviteEvent', 'MemberStateChangeEvent',
|
||||
'MemberCardChangeEvent', 'MemberSpecialTitleChangeEvent',
|
||||
'BotGroupPermissionChangeEvent', 'MemberPermissionChangeEvent',
|
||||
'RequestEvent', 'NewFriendRequestEvent', 'MemberJoinRequestEvent',
|
||||
'BotInvitedJoinGroupRequestEvent'
|
||||
"Event",
|
||||
"GroupChatInfo",
|
||||
"GroupInfo",
|
||||
"PrivateChatInfo",
|
||||
"UserPermission",
|
||||
"MessageSource",
|
||||
"MessageEvent",
|
||||
"GroupMessage",
|
||||
"FriendMessage",
|
||||
"TempMessage",
|
||||
"NoticeEvent",
|
||||
"MuteEvent",
|
||||
"BotMuteEvent",
|
||||
"BotUnmuteEvent",
|
||||
"MemberMuteEvent",
|
||||
"MemberUnmuteEvent",
|
||||
"BotJoinGroupEvent",
|
||||
"BotLeaveEventActive",
|
||||
"BotLeaveEventKick",
|
||||
"MemberJoinEvent",
|
||||
"MemberLeaveEventKick",
|
||||
"MemberLeaveEventQuit",
|
||||
"FriendRecallEvent",
|
||||
"GroupRecallEvent",
|
||||
"GroupStateChangeEvent",
|
||||
"GroupNameChangeEvent",
|
||||
"GroupEntranceAnnouncementChangeEvent",
|
||||
"GroupMuteAllEvent",
|
||||
"GroupAllowAnonymousChatEvent",
|
||||
"GroupAllowConfessTalkEvent",
|
||||
"GroupAllowMemberInviteEvent",
|
||||
"MemberStateChangeEvent",
|
||||
"MemberCardChangeEvent",
|
||||
"MemberSpecialTitleChangeEvent",
|
||||
"BotGroupPermissionChangeEvent",
|
||||
"MemberPermissionChangeEvent",
|
||||
"RequestEvent",
|
||||
"NewFriendRequestEvent",
|
||||
"MemberJoinRequestEvent",
|
||||
"BotInvitedJoinGroupRequestEvent",
|
||||
]
|
||||
|
@ -22,9 +22,10 @@ class UserPermission(str, Enum):
|
||||
* ``ADMINISTRATOR``: 群管理
|
||||
* ``MEMBER``: 普通群成员
|
||||
"""
|
||||
OWNER = 'OWNER'
|
||||
ADMINISTRATOR = 'ADMINISTRATOR'
|
||||
MEMBER = 'MEMBER'
|
||||
|
||||
OWNER = "OWNER"
|
||||
ADMINISTRATOR = "ADMINISTRATOR"
|
||||
MEMBER = "MEMBER"
|
||||
|
||||
|
||||
class NudgeSubjectKind(str, Enum):
|
||||
@ -36,8 +37,9 @@ class NudgeSubjectKind(str, Enum):
|
||||
* ``Group``: 群
|
||||
* ``Friend``: 好友
|
||||
"""
|
||||
Group = 'Group'
|
||||
Friend = 'Friend'
|
||||
|
||||
Group = "Group"
|
||||
Friend = "Friend"
|
||||
|
||||
|
||||
class GroupInfo(BaseModel):
|
||||
@ -48,7 +50,7 @@ class GroupInfo(BaseModel):
|
||||
|
||||
class GroupChatInfo(BaseModel):
|
||||
id: int
|
||||
name: str = Field(alias='memberName')
|
||||
name: str = Field(alias="memberName")
|
||||
permission: UserPermission
|
||||
group: GroupInfo
|
||||
|
||||
@ -71,6 +73,7 @@ class Event(BaseEvent):
|
||||
.. _mirai-api-http 事件类型:
|
||||
https://github.com/project-mirai/mirai-api-http/blob/master/docs/EventType.md
|
||||
"""
|
||||
|
||||
self_id: int
|
||||
type: str
|
||||
|
||||
@ -79,11 +82,12 @@ class Event(BaseEvent):
|
||||
"""
|
||||
此事件类的工厂函数, 能够通过事件数据选择合适的子类进行序列化
|
||||
"""
|
||||
type = data['type']
|
||||
type = data["type"]
|
||||
|
||||
def all_subclasses(cls: Type[Event]):
|
||||
return set(cls.__subclasses__()).union(
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
|
||||
[s for c in cls.__subclasses__() for s in all_subclasses(c)]
|
||||
)
|
||||
|
||||
event_class: Optional[Type[Event]] = None
|
||||
for subclass in all_subclasses(cls):
|
||||
@ -99,23 +103,25 @@ class Event(BaseEvent):
|
||||
return event_class.parse_obj(data)
|
||||
except ValidationError as e:
|
||||
logger.info(
|
||||
f'Failed to parse {data} to class {event_class.__name__}: '
|
||||
f'{e.errors()!r}. Fallback to parent class.')
|
||||
f"Failed to parse {data} to class {event_class.__name__}: "
|
||||
f"{e.errors()!r}. Fallback to parent class."
|
||||
)
|
||||
event_class = event_class.__base__ # type: ignore
|
||||
|
||||
raise ValueError(f'Failed to serialize {data}.')
|
||||
raise ValueError(f"Failed to serialize {data}.")
|
||||
|
||||
@overrides(BaseEvent)
|
||||
def get_type(self) -> Literal["message", "notice", "request", "meta_event"]:
|
||||
from . import meta, notice, message, request
|
||||
|
||||
if isinstance(self, message.MessageEvent):
|
||||
return 'message'
|
||||
return "message"
|
||||
elif isinstance(self, notice.NoticeEvent):
|
||||
return 'notice'
|
||||
return "notice"
|
||||
elif isinstance(self, request.RequestEvent):
|
||||
return 'request'
|
||||
return "request"
|
||||
else:
|
||||
return 'meta_event'
|
||||
return "meta_event"
|
||||
|
||||
@overrides(BaseEvent)
|
||||
def get_event_name(self) -> str:
|
||||
|
@ -1,7 +1,7 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from nonebot.typing import overrides
|
||||
|
||||
@ -16,7 +16,8 @@ class MessageSource(BaseModel):
|
||||
|
||||
class MessageEvent(Event):
|
||||
"""消息事件基类"""
|
||||
message_chain: MessageChain = Field(alias='messageChain')
|
||||
|
||||
message_chain: MessageChain = Field(alias="messageChain")
|
||||
source: Optional[MessageSource] = None
|
||||
sender: Any
|
||||
|
||||
@ -39,12 +40,13 @@ class MessageEvent(Event):
|
||||
|
||||
class GroupMessage(MessageEvent):
|
||||
"""群消息事件"""
|
||||
|
||||
sender: GroupChatInfo
|
||||
to_me: bool = False
|
||||
|
||||
@overrides(MessageEvent)
|
||||
def get_session_id(self) -> str:
|
||||
return f'group_{self.sender.group.id}_' + self.get_user_id()
|
||||
return f"group_{self.sender.group.id}_" + self.get_user_id()
|
||||
|
||||
@overrides(MessageEvent)
|
||||
def get_user_id(self) -> str:
|
||||
@ -57,6 +59,7 @@ class GroupMessage(MessageEvent):
|
||||
|
||||
class FriendMessage(MessageEvent):
|
||||
"""好友消息事件"""
|
||||
|
||||
sender: PrivateChatInfo
|
||||
|
||||
@overrides(MessageEvent)
|
||||
@ -65,7 +68,7 @@ class FriendMessage(MessageEvent):
|
||||
|
||||
@overrides(MessageEvent)
|
||||
def get_session_id(self) -> str:
|
||||
return 'friend_' + self.get_user_id()
|
||||
return "friend_" + self.get_user_id()
|
||||
|
||||
@overrides(MessageEvent)
|
||||
def is_tome(self) -> bool:
|
||||
@ -74,11 +77,12 @@ class FriendMessage(MessageEvent):
|
||||
|
||||
class TempMessage(MessageEvent):
|
||||
"""临时会话消息事件"""
|
||||
|
||||
sender: GroupChatInfo
|
||||
|
||||
@overrides(MessageEvent)
|
||||
def get_session_id(self) -> str:
|
||||
return f'temp_{self.sender.group.id}_' + self.get_user_id()
|
||||
return f"temp_{self.sender.group.id}_" + self.get_user_id()
|
||||
|
||||
@overrides(MessageEvent)
|
||||
def is_tome(self) -> bool:
|
||||
|
@ -3,29 +3,35 @@ from .base import Event
|
||||
|
||||
class MetaEvent(Event):
|
||||
"""元事件基类"""
|
||||
|
||||
qq: int
|
||||
|
||||
|
||||
class BotOnlineEvent(MetaEvent):
|
||||
"""Bot登录成功"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BotOfflineEventActive(MetaEvent):
|
||||
"""Bot主动离线"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BotOfflineEventForce(MetaEvent):
|
||||
"""Bot被挤下线"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BotOfflineEventDropped(MetaEvent):
|
||||
"""Bot被服务器断开或因网络问题而掉线"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BotReloginEvent(MetaEvent):
|
||||
"""Bot主动重新登录"""
|
||||
|
||||
pass
|
@ -2,88 +2,103 @@ from typing import Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .base import Event, GroupChatInfo, GroupInfo, NudgeSubject, UserPermission
|
||||
from .base import Event, GroupInfo, NudgeSubject, GroupChatInfo, UserPermission
|
||||
|
||||
|
||||
class NoticeEvent(Event):
|
||||
"""通知事件基类"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MuteEvent(NoticeEvent):
|
||||
"""禁言类事件基类"""
|
||||
|
||||
operator: GroupChatInfo
|
||||
|
||||
|
||||
class BotMuteEvent(MuteEvent):
|
||||
"""Bot被禁言"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BotUnmuteEvent(MuteEvent):
|
||||
"""Bot被取消禁言"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MemberMuteEvent(MuteEvent):
|
||||
"""群成员被禁言事件(该成员不是Bot)"""
|
||||
duration_seconds: int = Field(alias='durationSeconds')
|
||||
|
||||
duration_seconds: int = Field(alias="durationSeconds")
|
||||
member: GroupChatInfo
|
||||
operator: Optional[GroupChatInfo] = None
|
||||
|
||||
|
||||
class MemberUnmuteEvent(MuteEvent):
|
||||
"""群成员被取消禁言事件(该成员不是Bot)"""
|
||||
|
||||
member: GroupChatInfo
|
||||
operator: Optional[GroupChatInfo] = None
|
||||
|
||||
|
||||
class BotJoinGroupEvent(NoticeEvent):
|
||||
"""Bot加入了一个新群"""
|
||||
|
||||
group: GroupInfo
|
||||
|
||||
|
||||
class BotLeaveEventActive(BotJoinGroupEvent):
|
||||
"""Bot主动退出一个群"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class BotLeaveEventKick(BotJoinGroupEvent):
|
||||
"""Bot被踢出一个群"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class MemberJoinEvent(NoticeEvent):
|
||||
"""新人入群的事件"""
|
||||
|
||||
member: GroupChatInfo
|
||||
|
||||
|
||||
class MemberLeaveEventKick(MemberJoinEvent):
|
||||
"""成员被踢出群(该成员不是Bot)"""
|
||||
|
||||
operator: Optional[GroupChatInfo] = None
|
||||
|
||||
|
||||
class MemberLeaveEventQuit(MemberJoinEvent):
|
||||
"""成员主动离群(该成员不是Bot)"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class FriendRecallEvent(NoticeEvent):
|
||||
"""好友消息撤回"""
|
||||
author_id: int = Field(alias='authorId')
|
||||
message_id: int = Field(alias='messageId')
|
||||
|
||||
author_id: int = Field(alias="authorId")
|
||||
message_id: int = Field(alias="messageId")
|
||||
time: int
|
||||
operator: int
|
||||
|
||||
|
||||
class GroupRecallEvent(FriendRecallEvent):
|
||||
"""群消息撤回"""
|
||||
|
||||
group: GroupInfo
|
||||
operator: Optional[GroupChatInfo] = None
|
||||
|
||||
|
||||
class GroupStateChangeEvent(NoticeEvent):
|
||||
"""群变化事件基类"""
|
||||
|
||||
origin: Any
|
||||
current: Any
|
||||
group: GroupInfo
|
||||
@ -92,73 +107,85 @@ class GroupStateChangeEvent(NoticeEvent):
|
||||
|
||||
class GroupNameChangeEvent(GroupStateChangeEvent):
|
||||
"""某个群名改变"""
|
||||
|
||||
origin: str
|
||||
current: str
|
||||
|
||||
|
||||
class GroupEntranceAnnouncementChangeEvent(GroupStateChangeEvent):
|
||||
"""某群入群公告改变"""
|
||||
|
||||
origin: str
|
||||
current: str
|
||||
|
||||
|
||||
class GroupMuteAllEvent(GroupStateChangeEvent):
|
||||
"""全员禁言"""
|
||||
|
||||
origin: bool
|
||||
current: bool
|
||||
|
||||
|
||||
class GroupAllowAnonymousChatEvent(GroupStateChangeEvent):
|
||||
"""匿名聊天"""
|
||||
|
||||
origin: bool
|
||||
current: bool
|
||||
|
||||
|
||||
class GroupAllowConfessTalkEvent(GroupStateChangeEvent):
|
||||
"""坦白说"""
|
||||
|
||||
origin: bool
|
||||
current: bool
|
||||
|
||||
|
||||
class GroupAllowMemberInviteEvent(GroupStateChangeEvent):
|
||||
"""允许群员邀请好友加群"""
|
||||
|
||||
origin: bool
|
||||
current: bool
|
||||
|
||||
|
||||
class MemberStateChangeEvent(NoticeEvent):
|
||||
"""群成员变化事件基类"""
|
||||
|
||||
member: GroupChatInfo
|
||||
operator: Optional[GroupChatInfo] = None
|
||||
|
||||
|
||||
class MemberCardChangeEvent(MemberStateChangeEvent):
|
||||
"""群名片改动"""
|
||||
|
||||
origin: str
|
||||
current: str
|
||||
|
||||
|
||||
class MemberSpecialTitleChangeEvent(MemberStateChangeEvent):
|
||||
"""群头衔改动(只有群主有操作限权)"""
|
||||
|
||||
origin: str
|
||||
current: str
|
||||
|
||||
|
||||
class BotGroupPermissionChangeEvent(MemberStateChangeEvent):
|
||||
"""Bot在群里的权限被改变"""
|
||||
|
||||
origin: UserPermission
|
||||
current: UserPermission
|
||||
|
||||
|
||||
class MemberPermissionChangeEvent(MemberStateChangeEvent):
|
||||
"""成员权限改变的事件(该成员不是Bot)"""
|
||||
|
||||
origin: UserPermission
|
||||
current: UserPermission
|
||||
|
||||
|
||||
class NudgeEvent(NoticeEvent):
|
||||
"""戳一戳触发事件"""
|
||||
from_id: int = Field(alias='fromId')
|
||||
|
||||
from_id: int = Field(alias="fromId")
|
||||
target: int
|
||||
subject: NudgeSubject
|
||||
action: str
|
||||
|
@ -1,7 +1,7 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from typing_extensions import Literal
|
||||
|
||||
from pydantic import Field
|
||||
from typing_extensions import Literal
|
||||
|
||||
from .base import Event
|
||||
|
||||
@ -11,15 +11,17 @@ if TYPE_CHECKING:
|
||||
|
||||
class RequestEvent(Event):
|
||||
"""请求事件基类"""
|
||||
event_id: int = Field(alias='eventId')
|
||||
|
||||
event_id: int = Field(alias="eventId")
|
||||
message: str
|
||||
nick: str
|
||||
|
||||
|
||||
class NewFriendRequestEvent(RequestEvent):
|
||||
"""添加好友申请"""
|
||||
from_id: int = Field(alias='fromId')
|
||||
group_id: int = Field(0, alias='groupId')
|
||||
|
||||
from_id: int = Field(alias="fromId")
|
||||
group_id: int = Field(0, alias="groupId")
|
||||
|
||||
async def approve(self, bot: "Bot"):
|
||||
"""
|
||||
@ -31,19 +33,18 @@ class NewFriendRequestEvent(RequestEvent):
|
||||
|
||||
* ``bot: Bot``: 当前的 ``Bot`` 对象
|
||||
"""
|
||||
return await bot.api.post('/resp/newFriendRequestEvent',
|
||||
return await bot.api.post(
|
||||
"/resp/newFriendRequestEvent",
|
||||
params={
|
||||
'eventId': self.event_id,
|
||||
'groupId': self.group_id,
|
||||
'fromId': self.from_id,
|
||||
'operate': 0,
|
||||
'message': ''
|
||||
})
|
||||
"eventId": self.event_id,
|
||||
"groupId": self.group_id,
|
||||
"fromId": self.from_id,
|
||||
"operate": 0,
|
||||
"message": "",
|
||||
},
|
||||
)
|
||||
|
||||
async def reject(self,
|
||||
bot: "Bot",
|
||||
operate: Literal[1, 2] = 1,
|
||||
message: str = ''):
|
||||
async def reject(self, bot: "Bot", operate: Literal[1, 2] = 1, message: str = ""):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -60,21 +61,24 @@ class NewFriendRequestEvent(RequestEvent):
|
||||
* ``message: str``: 回复的信息
|
||||
"""
|
||||
assert operate > 0
|
||||
return await bot.api.post('/resp/newFriendRequestEvent',
|
||||
return await bot.api.post(
|
||||
"/resp/newFriendRequestEvent",
|
||||
params={
|
||||
'eventId': self.event_id,
|
||||
'groupId': self.group_id,
|
||||
'fromId': self.from_id,
|
||||
'operate': operate,
|
||||
'message': message
|
||||
})
|
||||
"eventId": self.event_id,
|
||||
"groupId": self.group_id,
|
||||
"fromId": self.from_id,
|
||||
"operate": operate,
|
||||
"message": message,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class MemberJoinRequestEvent(RequestEvent):
|
||||
"""用户入群申请(Bot需要有管理员权限)"""
|
||||
from_id: int = Field(alias='fromId')
|
||||
group_id: int = Field(alias='groupId')
|
||||
group_name: str = Field(alias='groupName')
|
||||
|
||||
from_id: int = Field(alias="fromId")
|
||||
group_id: int = Field(alias="groupId")
|
||||
group_name: str = Field(alias="groupName")
|
||||
|
||||
async def approve(self, bot: "Bot"):
|
||||
"""
|
||||
@ -86,19 +90,20 @@ class MemberJoinRequestEvent(RequestEvent):
|
||||
|
||||
* ``bot: Bot``: 当前的 ``Bot`` 对象
|
||||
"""
|
||||
return await bot.api.post('/resp/memberJoinRequestEvent',
|
||||
return await bot.api.post(
|
||||
"/resp/memberJoinRequestEvent",
|
||||
params={
|
||||
'eventId': self.event_id,
|
||||
'groupId': self.group_id,
|
||||
'fromId': self.from_id,
|
||||
'operate': 0,
|
||||
'message': ''
|
||||
})
|
||||
"eventId": self.event_id,
|
||||
"groupId": self.group_id,
|
||||
"fromId": self.from_id,
|
||||
"operate": 0,
|
||||
"message": "",
|
||||
},
|
||||
)
|
||||
|
||||
async def reject(self,
|
||||
bot: "Bot",
|
||||
operate: Literal[1, 2, 3, 4] = 1,
|
||||
message: str = ''):
|
||||
async def reject(
|
||||
self, bot: "Bot", operate: Literal[1, 2, 3, 4] = 1, message: str = ""
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -117,21 +122,24 @@ class MemberJoinRequestEvent(RequestEvent):
|
||||
* ``message: str``: 回复的信息
|
||||
"""
|
||||
assert operate > 0
|
||||
return await bot.api.post('/resp/memberJoinRequestEvent',
|
||||
return await bot.api.post(
|
||||
"/resp/memberJoinRequestEvent",
|
||||
params={
|
||||
'eventId': self.event_id,
|
||||
'groupId': self.group_id,
|
||||
'fromId': self.from_id,
|
||||
'operate': operate,
|
||||
'message': message
|
||||
})
|
||||
"eventId": self.event_id,
|
||||
"groupId": self.group_id,
|
||||
"fromId": self.from_id,
|
||||
"operate": operate,
|
||||
"message": message,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class BotInvitedJoinGroupRequestEvent(RequestEvent):
|
||||
"""Bot被邀请入群申请"""
|
||||
from_id: int = Field(alias='fromId')
|
||||
group_id: int = Field(alias='groupId')
|
||||
group_name: str = Field(alias='groupName')
|
||||
|
||||
from_id: int = Field(alias="fromId")
|
||||
group_id: int = Field(alias="groupId")
|
||||
group_name: str = Field(alias="groupName")
|
||||
|
||||
async def approve(self, bot: "Bot"):
|
||||
"""
|
||||
@ -143,14 +151,16 @@ class BotInvitedJoinGroupRequestEvent(RequestEvent):
|
||||
|
||||
* ``bot: Bot``: 当前的 ``Bot`` 对象
|
||||
"""
|
||||
return await bot.api.post('/resp/botInvitedJoinGroupRequestEvent',
|
||||
return await bot.api.post(
|
||||
"/resp/botInvitedJoinGroupRequestEvent",
|
||||
params={
|
||||
'eventId': self.event_id,
|
||||
'groupId': self.group_id,
|
||||
'fromId': self.from_id,
|
||||
'operate': 0,
|
||||
'message': ''
|
||||
})
|
||||
"eventId": self.event_id,
|
||||
"groupId": self.group_id,
|
||||
"fromId": self.from_id,
|
||||
"operate": 0,
|
||||
"message": "",
|
||||
},
|
||||
)
|
||||
|
||||
async def reject(self, bot: "Bot", message: str = ""):
|
||||
"""
|
||||
@ -163,11 +173,13 @@ class BotInvitedJoinGroupRequestEvent(RequestEvent):
|
||||
* ``bot: Bot``: 当前的 ``Bot`` 对象
|
||||
* ``message: str``: 邀请消息
|
||||
"""
|
||||
return await bot.api.post('/resp/botInvitedJoinGroupRequestEvent',
|
||||
return await bot.api.post(
|
||||
"/resp/botInvitedJoinGroupRequestEvent",
|
||||
params={
|
||||
'eventId': self.event_id,
|
||||
'groupId': self.group_id,
|
||||
'fromId': self.from_id,
|
||||
'operate': 1,
|
||||
'message': message
|
||||
})
|
||||
"eventId": self.event_id,
|
||||
"groupId": self.group_id,
|
||||
"fromId": self.from_id,
|
||||
"operate": 1,
|
||||
"message": message,
|
||||
},
|
||||
)
|
||||
|
@ -1,28 +1,29 @@
|
||||
from enum import Enum
|
||||
from typing import Any, List, Dict, Type, Iterable, Optional, Union
|
||||
from typing import Any, Dict, List, Type, Union, Iterable, Optional
|
||||
|
||||
from pydantic import validate_arguments
|
||||
|
||||
from nonebot.typing import overrides
|
||||
from nonebot.adapters import Message as BaseMessage
|
||||
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
||||
from nonebot.typing import overrides
|
||||
|
||||
|
||||
class MessageType(str, Enum):
|
||||
"""消息类型枚举类"""
|
||||
SOURCE = 'Source'
|
||||
QUOTE = 'Quote'
|
||||
AT = 'At'
|
||||
AT_ALL = 'AtAll'
|
||||
FACE = 'Face'
|
||||
PLAIN = 'Plain'
|
||||
IMAGE = 'Image'
|
||||
FLASH_IMAGE = 'FlashImage'
|
||||
VOICE = 'Voice'
|
||||
XML = 'Xml'
|
||||
JSON = 'Json'
|
||||
APP = 'App'
|
||||
POKE = 'Poke'
|
||||
|
||||
SOURCE = "Source"
|
||||
QUOTE = "Quote"
|
||||
AT = "At"
|
||||
AT_ALL = "AtAll"
|
||||
FACE = "Face"
|
||||
PLAIN = "Plain"
|
||||
IMAGE = "Image"
|
||||
FLASH_IMAGE = "FlashImage"
|
||||
VOICE = "Voice"
|
||||
XML = "Xml"
|
||||
JSON = "Json"
|
||||
APP = "App"
|
||||
POKE = "Poke"
|
||||
|
||||
|
||||
class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
@ -43,21 +44,24 @@ class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
@validate_arguments
|
||||
@overrides(BaseMessageSegment)
|
||||
def __init__(self, type: MessageType, **data: Any):
|
||||
super().__init__(type=type,
|
||||
data={k: v for k, v in data.items() if v is not None})
|
||||
super().__init__(
|
||||
type=type, data={k: v for k, v in data.items() if v is not None}
|
||||
)
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def __str__(self) -> str:
|
||||
return self.data['text'] if self.is_text() else repr(self)
|
||||
return self.data["text"] if self.is_text() else repr(self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return '[mirai:%s]' % ','.join([
|
||||
return "[mirai:%s]" % ",".join(
|
||||
[
|
||||
self.type.value,
|
||||
*map(
|
||||
lambda s: '%s=%r' % s,
|
||||
lambda s: "%s=%r" % s,
|
||||
self.data.items(),
|
||||
),
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
@overrides(BaseMessageSegment)
|
||||
def is_text(self) -> bool:
|
||||
@ -65,15 +69,21 @@ class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
|
||||
def as_dict(self) -> Dict[str, Any]:
|
||||
"""导出可以被正常json序列化的结构体"""
|
||||
return {'type': self.type.value, **self.data}
|
||||
return {"type": self.type.value, **self.data}
|
||||
|
||||
@classmethod
|
||||
def source(cls, id: int, time: int):
|
||||
return cls(type=MessageType.SOURCE, id=id, time=time)
|
||||
|
||||
@classmethod
|
||||
def quote(cls, id: int, group_id: int, sender_id: int, target_id: int,
|
||||
origin: "MessageChain"):
|
||||
def quote(
|
||||
cls,
|
||||
id: int,
|
||||
group_id: int,
|
||||
sender_id: int,
|
||||
target_id: int,
|
||||
origin: "MessageChain",
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -87,12 +97,14 @@ class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
* ``target_id: int``: 被引用回复的原消息的接收者者的QQ号(或群号)
|
||||
* ``origin: MessageChain``: 被引用回复的原消息的消息链对象
|
||||
"""
|
||||
return cls(type=MessageType.QUOTE,
|
||||
return cls(
|
||||
type=MessageType.QUOTE,
|
||||
id=id,
|
||||
groupId=group_id,
|
||||
senderId=sender_id,
|
||||
targetId=target_id,
|
||||
origin=origin.export())
|
||||
origin=origin.export(),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def at(cls, target: int):
|
||||
@ -144,10 +156,12 @@ class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
return cls(type=MessageType.PLAIN, text=text)
|
||||
|
||||
@classmethod
|
||||
def image(cls,
|
||||
def image(
|
||||
cls,
|
||||
image_id: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
path: Optional[str] = None):
|
||||
path: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -162,10 +176,12 @@ class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
return cls(type=MessageType.IMAGE, imageId=image_id, url=url, path=path)
|
||||
|
||||
@classmethod
|
||||
def flash_image(cls,
|
||||
def flash_image(
|
||||
cls,
|
||||
image_id: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
path: Optional[str] = None):
|
||||
path: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -175,16 +191,15 @@ class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
|
||||
同 ``image``
|
||||
"""
|
||||
return cls(type=MessageType.FLASH_IMAGE,
|
||||
imageId=image_id,
|
||||
url=url,
|
||||
path=path)
|
||||
return cls(type=MessageType.FLASH_IMAGE, imageId=image_id, url=url, path=path)
|
||||
|
||||
@classmethod
|
||||
def voice(cls,
|
||||
def voice(
|
||||
cls,
|
||||
voice_id: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
path: Optional[str] = None):
|
||||
path: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
:说明:
|
||||
|
||||
@ -196,10 +211,7 @@ class MessageSegment(BaseMessageSegment["MessageChain"]):
|
||||
* ``url: Optional[str]``: 语音的URL,发送时可作网络语音的链接
|
||||
* ``path: Optional[str]``: 语音的路径,发送本地语音
|
||||
"""
|
||||
return cls(type=MessageType.FLASH_IMAGE,
|
||||
imageId=voice_id,
|
||||
url=url,
|
||||
path=path)
|
||||
return cls(type=MessageType.FLASH_IMAGE, imageId=voice_id, url=url, path=path)
|
||||
|
||||
@classmethod
|
||||
def xml(cls, xml: str):
|
||||
@ -282,16 +294,14 @@ class MessageChain(BaseMessage[MessageSegment]):
|
||||
return [MessageSegment.plain(text=message)]
|
||||
return [
|
||||
*map(
|
||||
lambda x: x
|
||||
if isinstance(x, MessageSegment) else MessageSegment(**x),
|
||||
message)
|
||||
lambda x: x if isinstance(x, MessageSegment) else MessageSegment(**x),
|
||||
message,
|
||||
)
|
||||
]
|
||||
|
||||
def export(self) -> List[Dict[str, Any]]:
|
||||
"""导出为可以被正常json序列化的数组"""
|
||||
return [
|
||||
*map(lambda segment: segment.as_dict(), self.copy()) # type: ignore
|
||||
]
|
||||
return [*map(lambda segment: segment.as_dict(), self.copy())] # type: ignore
|
||||
|
||||
def extract_first(self, *type: MessageType) -> Optional[MessageSegment]:
|
||||
"""
|
||||
@ -311,4 +321,4 @@ class MessageChain(BaseMessage[MessageSegment]):
|
||||
return None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<{self.__class__.__name__} {[*self.copy()]}>'
|
||||
return f"<{self.__class__.__name__} {[*self.copy()]}>"
|
||||
|
@ -1,17 +1,17 @@
|
||||
import re
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING, Any, Callable, Coroutine, Optional, TypeVar
|
||||
from typing import TYPE_CHECKING, Any, TypeVar, Callable, Optional, Coroutine
|
||||
|
||||
import httpx
|
||||
from pydantic import Extra, ValidationError, validate_arguments
|
||||
|
||||
import nonebot.exception as exception
|
||||
from nonebot.log import logger
|
||||
import nonebot.exception as exception
|
||||
from nonebot.message import handle_event
|
||||
from nonebot.utils import escape_tag, logger_wrapper
|
||||
|
||||
from .event import Event, GroupMessage, MessageEvent, MessageSource
|
||||
from .message import MessageType, MessageSegment
|
||||
from .event import Event, GroupMessage, MessageEvent, MessageSource
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .bot import Bot
|
||||
@ -21,28 +21,27 @@ _AnyCallable = TypeVar("_AnyCallable", bound=Callable)
|
||||
|
||||
|
||||
class Log:
|
||||
|
||||
@staticmethod
|
||||
def log(level: str, message: str, exception: Optional[Exception] = None):
|
||||
logger = logger_wrapper('MIRAI')
|
||||
message = '<e>' + escape_tag(message) + '</e>'
|
||||
logger = logger_wrapper("MIRAI")
|
||||
message = "<e>" + escape_tag(message) + "</e>"
|
||||
logger(level=level.upper(), message=message, exception=exception)
|
||||
|
||||
@classmethod
|
||||
def info(cls, message: Any):
|
||||
cls.log('INFO', str(message))
|
||||
cls.log("INFO", str(message))
|
||||
|
||||
@classmethod
|
||||
def debug(cls, message: Any):
|
||||
cls.log('DEBUG', str(message))
|
||||
cls.log("DEBUG", str(message))
|
||||
|
||||
@classmethod
|
||||
def warn(cls, message: Any):
|
||||
cls.log('WARNING', str(message))
|
||||
cls.log("WARNING", str(message))
|
||||
|
||||
@classmethod
|
||||
def error(cls, message: Any, exception: Optional[Exception] = None):
|
||||
cls.log('ERROR', str(message), exception=exception)
|
||||
cls.log("ERROR", str(message), exception=exception)
|
||||
|
||||
|
||||
class ActionFailed(exception.ActionFailed):
|
||||
@ -53,12 +52,13 @@ class ActionFailed(exception.ActionFailed):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__('mirai')
|
||||
super().__init__("mirai")
|
||||
self.data = kwargs.copy()
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + '(%s)' % ', '.join(
|
||||
map(lambda m: '%s=%r' % m, self.data.items()))
|
||||
return self.__class__.__name__ + "(%s)" % ", ".join(
|
||||
map(lambda m: "%s=%r" % m, self.data.items())
|
||||
)
|
||||
|
||||
|
||||
class InvalidArgument(exception.AdapterException):
|
||||
@ -69,7 +69,7 @@ class InvalidArgument(exception.AdapterException):
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__('mirai')
|
||||
super().__init__("mirai")
|
||||
|
||||
|
||||
def catch_network_error(function: _AsyncCallable) -> _AsyncCallable:
|
||||
@ -90,11 +90,12 @@ def catch_network_error(function: _AsyncCallable) -> _AsyncCallable:
|
||||
try:
|
||||
data = await function(*args, **kwargs)
|
||||
except httpx.HTTPError:
|
||||
raise exception.NetworkError('mirai')
|
||||
logger.opt(colors=True).debug('<b>Mirai API returned data:</b> '
|
||||
f'<y>{escape_tag(str(data))}</y>')
|
||||
raise exception.NetworkError("mirai")
|
||||
logger.opt(colors=True).debug(
|
||||
"<b>Mirai API returned data:</b> " f"<y>{escape_tag(str(data))}</y>"
|
||||
)
|
||||
if isinstance(data, dict):
|
||||
if data.get('code', 0) != 0:
|
||||
if data.get("code", 0) != 0:
|
||||
raise ActionFailed(**data)
|
||||
return data
|
||||
|
||||
@ -109,10 +110,9 @@ def argument_validation(function: _AnyCallable) -> _AnyCallable:
|
||||
|
||||
会在参数出错时释放 ``InvalidArgument`` 异常
|
||||
"""
|
||||
function = validate_arguments(config={
|
||||
'arbitrary_types_allowed': True,
|
||||
'extra': Extra.forbid
|
||||
})(function)
|
||||
function = validate_arguments(
|
||||
config={"arbitrary_types_allowed": True, "extra": Extra.forbid}
|
||||
)(function)
|
||||
|
||||
@wraps(function)
|
||||
def wrapper(*args, **kwargs):
|
||||
@ -134,12 +134,12 @@ def process_source(bot: "Bot", event: MessageEvent) -> MessageEvent:
|
||||
def process_at(bot: "Bot", event: GroupMessage) -> GroupMessage:
|
||||
at = event.message_chain.extract_first(MessageType.AT)
|
||||
if at is not None:
|
||||
if at.data['target'] == event.self_id:
|
||||
if at.data["target"] == event.self_id:
|
||||
event.to_me = True
|
||||
else:
|
||||
event.message_chain.insert(0, at)
|
||||
if not event.message_chain:
|
||||
event.message_chain.append(MessageSegment.plain(''))
|
||||
event.message_chain.append(MessageSegment.plain(""))
|
||||
return event
|
||||
|
||||
|
||||
@ -147,13 +147,13 @@ def process_nick(bot: "Bot", event: GroupMessage) -> GroupMessage:
|
||||
plain = event.message_chain.extract_first(MessageType.PLAIN)
|
||||
if plain is not None:
|
||||
text = str(plain)
|
||||
nick_regex = '|'.join(filter(lambda x: x, bot.config.nickname))
|
||||
nick_regex = "|".join(filter(lambda x: x, bot.config.nickname))
|
||||
matched = re.search(rf"^({nick_regex})([\s,,]*|$)", text, re.IGNORECASE)
|
||||
if matched is not None:
|
||||
event.to_me = True
|
||||
nickname = matched.group(1)
|
||||
Log.info(f'User is calling me {nickname}')
|
||||
plain.data['text'] = text[matched.end():]
|
||||
Log.info(f"User is calling me {nickname}")
|
||||
plain.data["text"] = text[matched.end() :]
|
||||
event.message_chain.insert(0, plain)
|
||||
return event
|
||||
|
||||
@ -161,7 +161,7 @@ def process_nick(bot: "Bot", event: GroupMessage) -> GroupMessage:
|
||||
def process_reply(bot: "Bot", event: GroupMessage) -> GroupMessage:
|
||||
reply = event.message_chain.extract_first(MessageType.QUOTE)
|
||||
if reply is not None:
|
||||
if reply.data['senderId'] == event.self_id:
|
||||
if reply.data["senderId"] == event.self_id:
|
||||
event.to_me = True
|
||||
else:
|
||||
event.message_chain.insert(0, reply)
|
||||
|
@ -34,6 +34,21 @@ nonebot2 = { path = "../../", develop = true }
|
||||
# url = "https://mirrors.aliyun.com/pypi/simple/"
|
||||
# default = true
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
@ -7,8 +7,7 @@ from nonebot.log import logger
|
||||
def init():
|
||||
driver = nonebot.get_driver()
|
||||
try:
|
||||
_module = importlib.import_module(
|
||||
f"nonebot_plugin_docs.drivers.{driver.type}")
|
||||
_module = importlib.import_module(f"nonebot_plugin_docs.drivers.{driver.type}")
|
||||
except ImportError:
|
||||
logger.warning(f"Driver {driver.type} not supported")
|
||||
return
|
||||
@ -18,8 +17,9 @@ def init():
|
||||
port = driver.config.port
|
||||
if host in ["0.0.0.0", "127.0.0.1"]:
|
||||
host = "localhost"
|
||||
logger.opt(colors=True).info(f"Nonebot docs will be running at: "
|
||||
f"<b><u>http://{host}:{port}/docs/</u></b>")
|
||||
logger.opt(colors=True).info(
|
||||
f"Nonebot docs will be running at: " f"<b><u>http://{host}:{port}/docs/</u></b>"
|
||||
)
|
||||
|
||||
|
||||
init()
|
||||
|
@ -9,6 +9,4 @@ def register_route(driver: Driver):
|
||||
|
||||
static_path = str((Path(__file__).parent / ".." / "dist").resolve())
|
||||
|
||||
app.mount("/docs",
|
||||
StaticFiles(directory=static_path, html=True),
|
||||
name="docs")
|
||||
app.mount("/docs", StaticFiles(directory=static_path, html=True), name="docs")
|
||||
|
@ -18,6 +18,21 @@ nonebot2 = "^2.0.0-alpha.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
217
poetry.lock
generated
217
poetry.lock
generated
@ -151,6 +151,34 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
[package.dependencies]
|
||||
pytz = ">=2015.7"
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "21.11b1"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.1.2"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
pathspec = ">=0.9.0,<1"
|
||||
platformdirs = ">=2"
|
||||
regex = ">=2021.4.4"
|
||||
tomli = ">=0.2.6,<2.0.0"
|
||||
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
|
||||
typing-extensions = [
|
||||
{version = ">=3.10.0.0", markers = "python_version < \"3.10\""},
|
||||
{version = "!=3.10.0.1", markers = "python_version >= \"3.10\""},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.7.4)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
python2 = ["typed-ast (>=1.4.3)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.4"
|
||||
@ -297,7 +325,7 @@ python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "0.14.2"
|
||||
version = "0.14.3"
|
||||
description = "A minimal low-level HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
@ -406,6 +434,20 @@ docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
|
||||
perf = ["ipython"]
|
||||
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.10.1"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.1,<4.0"
|
||||
|
||||
[package.extras]
|
||||
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
|
||||
requirements_deprecated_finder = ["pipreqs", "pip-api"]
|
||||
colors = ["colorama (>=0.4.3,<0.5.0)"]
|
||||
plugins = ["setuptools"]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.0.1"
|
||||
@ -459,6 +501,14 @@ category = "main"
|
||||
optional = true
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "nonebot-adapter-cqhttp"
|
||||
version = "2.0.0-alpha.16"
|
||||
@ -544,14 +594,34 @@ python-socketio = ">=4.6.1,<5.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.2"
|
||||
version = "21.3"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3"
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.9.0"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.4.0"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "priority"
|
||||
@ -636,11 +706,14 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "2.4.7"
|
||||
version = "3.0.6"
|
||||
description = "Python parsing module"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
@ -722,6 +795,14 @@ werkzeug = ">=2.0.0"
|
||||
[package.extras]
|
||||
dotenv = ["python-dotenv"]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2021.11.10"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.26.0"
|
||||
@ -925,6 +1006,14 @@ category = "main"
|
||||
optional = true
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "1.2.2"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "tomlkit"
|
||||
version = "0.7.2"
|
||||
@ -933,6 +1022,14 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "typed-ast"
|
||||
version = "1.5.0"
|
||||
description = "a fork of Python 2 and 3 ast modules with type comment support"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.0.0"
|
||||
@ -1100,7 +1197,7 @@ quart = ["Quart"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7.3"
|
||||
content-hash = "537c91f98fd6598dbce8c2942530f18dee0858a896b6f393a684252a77dc76c6"
|
||||
content-hash = "9a64b2ba25ea3367e1636545241122d5b4d454ab3042865416193f84bf358fc3"
|
||||
|
||||
[metadata.files]
|
||||
aiocache = [
|
||||
@ -1221,6 +1318,10 @@ babel = [
|
||||
{file = "Babel-2.9.1-py2.py3-none-any.whl", hash = "sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9"},
|
||||
{file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"},
|
||||
]
|
||||
black = [
|
||||
{file = "black-21.11b1-py3-none-any.whl", hash = "sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac"},
|
||||
{file = "black-21.11b1.tar.gz", hash = "sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2"},
|
||||
]
|
||||
blinker = [
|
||||
{file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"},
|
||||
]
|
||||
@ -1471,8 +1572,8 @@ html2text = [
|
||||
{file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"},
|
||||
]
|
||||
httpcore = [
|
||||
{file = "httpcore-0.14.2-py3-none-any.whl", hash = "sha256:47d7c8f755719d4a57be0b6e022897e9e963bf9ce4b15b9cc006a38a1cfa2932"},
|
||||
{file = "httpcore-0.14.2.tar.gz", hash = "sha256:ff8f8b9434ec4823f95a30596fbe78039913e706d3e598b0b8955b1e1828e093"},
|
||||
{file = "httpcore-0.14.3-py3-none-any.whl", hash = "sha256:9a98d2416b78976fc5396ff1f6b26ae9885efbb3105d24eed490f20ab4c95ec1"},
|
||||
{file = "httpcore-0.14.3.tar.gz", hash = "sha256:d10162a63265a0228d5807964bd964478cbdb5178f9a2eedfebb2faba27eef5d"},
|
||||
]
|
||||
httptools = [
|
||||
{file = "httptools-0.2.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:79dbc21f3612a78b28384e989b21872e2e3cf3968532601544696e4ed0007ce5"},
|
||||
@ -1515,6 +1616,10 @@ importlib-metadata = [
|
||||
{file = "importlib_metadata-4.8.2-py3-none-any.whl", hash = "sha256:53ccfd5c134223e497627b9815d5030edf77d2ed573922f7a0b8f8bb81a1c100"},
|
||||
{file = "importlib_metadata-4.8.2.tar.gz", hash = "sha256:75bdec14c397f528724c1bfd9709d660b33a4d2e77387a3358f20b848bb5e5fb"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
|
||||
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"},
|
||||
{file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"},
|
||||
@ -1672,6 +1777,10 @@ multidict = [
|
||||
{file = "multidict-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:c9631c642e08b9fff1c6255487e62971d8b8e821808ddd013d8ac058087591ac"},
|
||||
{file = "multidict-5.2.0.tar.gz", hash = "sha256:0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
]
|
||||
nonebot-adapter-cqhttp = []
|
||||
nonebot-adapter-ding = []
|
||||
nonebot-adapter-feishu = []
|
||||
@ -1681,8 +1790,16 @@ nonebot-plugin-test = [
|
||||
{file = "nonebot_plugin_test-0.3.0-py3-none-any.whl", hash = "sha256:edb880340436323ccd0a13b31d48975136b6bdc71daa178601c4b05b068cc73e"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"},
|
||||
{file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"},
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
]
|
||||
pathspec = [
|
||||
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
|
||||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
|
||||
{file = "platformdirs-2.4.0.tar.gz", hash = "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2"},
|
||||
]
|
||||
priority = [
|
||||
{file = "priority-2.0.0-py3-none-any.whl", hash = "sha256:6f8eefce5f3ad59baf2c080a664037bb4725cd0a790d53d59ab4059288faf6aa"},
|
||||
@ -1793,8 +1910,8 @@ pygtrie = [
|
||||
{file = "pygtrie-2.4.2.tar.gz", hash = "sha256:43205559d28863358dbbf25045029f58e2ab357317a59b11f11ade278ac64692"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
|
||||
{file = "pyparsing-3.0.6-py3-none-any.whl", hash = "sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4"},
|
||||
{file = "pyparsing-3.0.6.tar.gz", hash = "sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81"},
|
||||
]
|
||||
python-dotenv = [
|
||||
{file = "python-dotenv-0.19.2.tar.gz", hash = "sha256:a5de49a31e953b45ff2d2fd434bbc2670e8db5273606c1e737cc6b93eff3655f"},
|
||||
@ -1851,6 +1968,57 @@ quart = [
|
||||
{file = "Quart-0.15.1-py3-none-any.whl", hash = "sha256:f35134fb1d81af61624e6d89bca33cd611dcedce2dc4e291f527ab04395f4e1a"},
|
||||
{file = "Quart-0.15.1.tar.gz", hash = "sha256:f80c91d1e0588662483e22dd9c368a5778886b62e128c5399d2cc1b1898482cf"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-win32.whl", hash = "sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a"},
|
||||
{file = "regex-2021.11.10-cp310-cp310-win_amd64.whl", hash = "sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-win32.whl", hash = "sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4"},
|
||||
{file = "regex-2021.11.10-cp36-cp36m-win_amd64.whl", hash = "sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-win32.whl", hash = "sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec"},
|
||||
{file = "regex-2021.11.10-cp37-cp37m-win_amd64.whl", hash = "sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-win32.whl", hash = "sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc"},
|
||||
{file = "regex-2021.11.10-cp38-cp38-win_amd64.whl", hash = "sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-win32.whl", hash = "sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a"},
|
||||
{file = "regex-2021.11.10-cp39-cp39-win_amd64.whl", hash = "sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29"},
|
||||
{file = "regex-2021.11.10.tar.gz", hash = "sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
|
||||
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
|
||||
@ -1908,10 +2076,35 @@ toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
tomli = [
|
||||
{file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"},
|
||||
{file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"},
|
||||
]
|
||||
tomlkit = [
|
||||
{file = "tomlkit-0.7.2-py2.py3-none-any.whl", hash = "sha256:173ad840fa5d2aac140528ca1933c29791b79a374a0861a80347f42ec9328117"},
|
||||
{file = "tomlkit-0.7.2.tar.gz", hash = "sha256:d7a454f319a7e9bd2e249f239168729327e4dd2d27b17dc68be264ad1ce36754"},
|
||||
]
|
||||
typed-ast = [
|
||||
{file = "typed_ast-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b310a207ee9fde3f46ba327989e6cba4195bc0c8c70a158456e7b10233e6bed"},
|
||||
{file = "typed_ast-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52ca2b2b524d770bed7a393371a38e91943f9160a190141e0df911586066ecda"},
|
||||
{file = "typed_ast-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:14fed8820114a389a2b7e91624db5f85f3f6682fda09fe0268a59aabd28fe5f5"},
|
||||
{file = "typed_ast-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:65c81abbabda7d760df7304d843cc9dbe7ef5d485504ca59a46ae2d1731d2428"},
|
||||
{file = "typed_ast-1.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:37ba2ab65a0028b1a4f2b61a8fe77f12d242731977d274a03d68ebb751271508"},
|
||||
{file = "typed_ast-1.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:49af5b8f6f03ed1eb89ee06c1d7c2e7c8e743d720c3746a5857609a1abc94c94"},
|
||||
{file = "typed_ast-1.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:e4374a76e61399a173137e7984a1d7e356038cf844f24fd8aea46c8029a2f712"},
|
||||
{file = "typed_ast-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ea517c2bb11c5e4ba7a83a91482a2837041181d57d3ed0749a6c382a2b6b7086"},
|
||||
{file = "typed_ast-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:51040bf45aacefa44fa67fb9ebcd1f2bec73182b99a532c2394eea7dabd18e24"},
|
||||
{file = "typed_ast-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:806e0c7346b9b4af8c62d9a29053f484599921a4448c37fbbcbbf15c25138570"},
|
||||
{file = "typed_ast-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a67fd5914603e2165e075f1b12f5a8356bfb9557e8bfb74511108cfbab0f51ed"},
|
||||
{file = "typed_ast-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:224afecb8b39739f5c9562794a7c98325cb9d972712e1a98b6989a4720219541"},
|
||||
{file = "typed_ast-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:155b74b078be842d2eb630dd30a280025eca0a5383c7d45853c27afee65f278f"},
|
||||
{file = "typed_ast-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:361b9e5d27bd8e3ccb6ea6ad6c4f3c0be322a1a0f8177db6d56264fa0ae40410"},
|
||||
{file = "typed_ast-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:618912cbc7e17b4aeba86ffe071698c6e2d292acbd6d1d5ec1ee724b8c4ae450"},
|
||||
{file = "typed_ast-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7e6731044f748340ef68dcadb5172a4b1f40847a2983fe3983b2a66445fbc8e6"},
|
||||
{file = "typed_ast-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e8a9b9c87801cecaad3b4c2b8876387115d1a14caa602c1618cedbb0cb2a14e6"},
|
||||
{file = "typed_ast-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:ec184dfb5d3d11e82841dbb973e7092b75f306b625fad7b2e665b64c5d60ab3f"},
|
||||
{file = "typed_ast-1.5.0.tar.gz", hash = "sha256:ff4ad88271aa7a55f19b6a161ed44e088c393846d954729549e3cde8257747bb"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.0.0-py3-none-any.whl", hash = "sha256:829704698b22e13ec9eaf959122315eabb370b0884400e9818334d8b677023d9"},
|
||||
{file = "typing_extensions-4.0.0.tar.gz", hash = "sha256:2cdf80e4e04866a9b3689a51869016d36db0814d84b8d8a568d22781d45d27ed"},
|
||||
|
@ -36,8 +36,9 @@ uvicorn = { version = "^0.15.0", extras = ["standard"] }
|
||||
aiohttp = { version = "^3.7.4", extras = ["speedups"], optional = true }
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
yapf = "^0.31.0"
|
||||
sphinx = "^4.1.1"
|
||||
isort = "^5.10.1"
|
||||
black = "^21.11b1"
|
||||
nonebot-plugin-test = "^0.3.0"
|
||||
nonebot-adapter-cqhttp = { path = "./packages/nonebot-adapter-cqhttp", develop = true }
|
||||
nonebot-adapter-ding = { path = "./packages/nonebot-adapter-ding", develop = true }
|
||||
@ -55,13 +56,21 @@ all = ["quart", "aiohttp"]
|
||||
# url = "https://mirrors.aliyun.com/pypi/simple/"
|
||||
# default = true
|
||||
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
target-version = ["py37", "py38", "py39", "py310"]
|
||||
include = '\.pyi?$'
|
||||
extend-exclude = '''
|
||||
'''
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 80
|
||||
length_sort = true
|
||||
skip_gitignore = true
|
||||
force_sort_within_sections = true
|
||||
known_local_folder = "nonebot"
|
||||
extra_standard_library = "typing_extensions"
|
||||
known_local_folder = ["nonebot"]
|
||||
extra_standard_library = ["typing_extensions"]
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry_core>=1.0.0"]
|
||||
|
@ -11,11 +11,9 @@ from nonebot.adapters.mirai import Bot as MiraiBot
|
||||
from nonebot.adapters.feishu import Bot as FeishuBot
|
||||
|
||||
# test custom log
|
||||
logger.add("error.log",
|
||||
rotation="00:00",
|
||||
diagnose=False,
|
||||
level="ERROR",
|
||||
format=default_format)
|
||||
logger.add(
|
||||
"error.log", rotation="00:00", diagnose=False, level="ERROR", format=default_format
|
||||
)
|
||||
|
||||
nonebot.init(custom_config2="config on init")
|
||||
app = nonebot.get_asgi()
|
||||
|
@ -1,7 +1,8 @@
|
||||
from nonebot.adapters.ding.event import GroupMessageEvent, PrivateMessageEvent
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.plugin import on_command
|
||||
from nonebot.adapters.ding import Bot as DingBot, MessageSegment, MessageEvent
|
||||
from nonebot.adapters.ding import Bot as DingBot
|
||||
from nonebot.adapters.ding import MessageEvent, MessageSegment
|
||||
from nonebot.adapters.ding.event import GroupMessageEvent, PrivateMessageEvent
|
||||
|
||||
helper = on_command("ding_helper", to_me())
|
||||
|
||||
@ -34,7 +35,7 @@ markdown = on_command("markdown", to_me())
|
||||
async def markdown_handler(bot: DingBot):
|
||||
message = MessageSegment.markdown(
|
||||
"Hello, This is NoneBot",
|
||||
"#### NoneBot \n> Nonebot 是一款高性能的 Python 机器人框架\n> ![screenshot](https://v2.nonebot.dev/logo.png)\n> [GitHub 仓库地址](https://github.com/nonebot/nonebot2) \n"
|
||||
"#### NoneBot \n> Nonebot 是一款高性能的 Python 机器人框架\n> ![screenshot](https://v2.nonebot.dev/logo.png)\n> [GitHub 仓库地址](https://github.com/nonebot/nonebot2) \n",
|
||||
)
|
||||
await markdown.finish(message)
|
||||
|
||||
@ -46,10 +47,10 @@ actionCardSingleBtn = on_command("actionCardSingleBtn", to_me())
|
||||
async def actionCardSingleBtn_handler(bot: DingBot):
|
||||
message = MessageSegment.actionCardSingleBtn(
|
||||
title="打造一间咖啡厅",
|
||||
text=
|
||||
"![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png) \n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
|
||||
text="![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png) \n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
|
||||
singleTitle="阅读全文",
|
||||
singleURL="https://www.dingtalk.com/")
|
||||
singleURL="https://www.dingtalk.com/",
|
||||
)
|
||||
await actionCardSingleBtn.finish(message)
|
||||
|
||||
|
||||
@ -58,26 +59,21 @@ actionCard = on_command("actionCard", to_me())
|
||||
|
||||
@actionCard.handle()
|
||||
async def actionCard_handler(bot: DingBot):
|
||||
message = MessageSegment.raw({
|
||||
message = MessageSegment.raw(
|
||||
{
|
||||
"msgtype": "actionCard",
|
||||
"actionCard": {
|
||||
"title":
|
||||
"乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
|
||||
"text":
|
||||
"![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png) \n\n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
|
||||
"hideAvatar":
|
||||
"0",
|
||||
"btnOrientation":
|
||||
"0",
|
||||
"btns": [{
|
||||
"title": "内容不错",
|
||||
"actionURL": "https://www.dingtalk.com/"
|
||||
}, {
|
||||
"title": "不感兴趣",
|
||||
"actionURL": "https://www.dingtalk.com/"
|
||||
}]
|
||||
"title": "乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
|
||||
"text": "![screenshot](https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png) \n\n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
|
||||
"hideAvatar": "0",
|
||||
"btnOrientation": "0",
|
||||
"btns": [
|
||||
{"title": "内容不错", "actionURL": "https://www.dingtalk.com/"},
|
||||
{"title": "不感兴趣", "actionURL": "https://www.dingtalk.com/"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
await actionCard.finish(message, at_sender=True)
|
||||
|
||||
|
||||
@ -86,26 +82,25 @@ feedCard = on_command("feedCard", to_me())
|
||||
|
||||
@feedCard.handle()
|
||||
async def feedCard_handler(bot: DingBot):
|
||||
message = MessageSegment.raw({
|
||||
message = MessageSegment.raw(
|
||||
{
|
||||
"msgtype": "feedCard",
|
||||
"feedCard": {
|
||||
"links": [{
|
||||
"title":
|
||||
"时代的火车向前开1",
|
||||
"messageURL":
|
||||
"https://www.dingtalk.com/",
|
||||
"picURL":
|
||||
"https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
|
||||
}, {
|
||||
"title":
|
||||
"时代的火车向前开2",
|
||||
"messageURL":
|
||||
"https://www.dingtalk.com/",
|
||||
"picURL":
|
||||
"https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
|
||||
}]
|
||||
"links": [
|
||||
{
|
||||
"title": "时代的火车向前开1",
|
||||
"messageURL": "https://www.dingtalk.com/",
|
||||
"picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png",
|
||||
},
|
||||
{
|
||||
"title": "时代的火车向前开2",
|
||||
"messageURL": "https://www.dingtalk.com/",
|
||||
"picURL": "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png",
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
await feedCard.finish(message)
|
||||
|
||||
|
||||
@ -115,7 +110,8 @@ atme = on_command("atme", to_me())
|
||||
@atme.handle()
|
||||
async def atme_handler(bot: DingBot, event: MessageEvent):
|
||||
message = f"@{event.senderId} manually at you" + MessageSegment.atDingtalkIds(
|
||||
event.senderId)
|
||||
event.senderId
|
||||
)
|
||||
await atme.send("matcher send auto at you", at_sender=True)
|
||||
await bot.send(event, "bot send auto at you", at_sender=True)
|
||||
await atme.finish(message)
|
||||
@ -143,12 +139,12 @@ async def textAdd_handler(bot: DingBot, event: MessageEvent):
|
||||
message = message + MessageSegment.text("第二段消息\n")
|
||||
await textAdd.send(message)
|
||||
|
||||
message = message + MessageSegment.text(
|
||||
"\n第三段消息\n") + "adfkasfkhsdkfahskdjasdashdkjasdf"
|
||||
message = message + MessageSegment.extension({
|
||||
"text_type": "code_snippet",
|
||||
"code_language": "C#"
|
||||
})
|
||||
message = (
|
||||
message + MessageSegment.text("\n第三段消息\n") + "adfkasfkhsdkfahskdjasdashdkjasdf"
|
||||
)
|
||||
message = message + MessageSegment.extension(
|
||||
{"text_type": "code_snippet", "code_language": "C#"}
|
||||
)
|
||||
await textAdd.send(message)
|
||||
|
||||
|
||||
@ -159,7 +155,8 @@ code = on_command("code", to_me())
|
||||
async def code_handler(bot: DingBot, event: MessageEvent):
|
||||
raw = MessageSegment.code("Python", 'print("hello world")')
|
||||
await code.send(raw)
|
||||
message = MessageSegment.text("""using System;
|
||||
message = MessageSegment.text(
|
||||
"""using System;
|
||||
|
||||
namespace HelloWorld
|
||||
{
|
||||
@ -170,11 +167,11 @@ namespace HelloWorld
|
||||
Console.WriteLine("Hello World!");
|
||||
}
|
||||
}
|
||||
}""")
|
||||
message += MessageSegment.extension({
|
||||
"text_type": "code_snippet",
|
||||
"code_language": "C#"
|
||||
})
|
||||
}"""
|
||||
)
|
||||
message += MessageSegment.extension(
|
||||
{"text_type": "code_snippet", "code_language": "C#"}
|
||||
)
|
||||
await code.finish(message)
|
||||
|
||||
|
||||
@ -196,12 +193,12 @@ hello = on_command("hello", to_me())
|
||||
|
||||
@hello.handle()
|
||||
async def hello_handler(bot: DingBot, event: MessageEvent):
|
||||
message = MessageSegment.raw({
|
||||
message = MessageSegment.raw(
|
||||
{
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": 'hello '
|
||||
},
|
||||
})
|
||||
"text": {"content": "hello "},
|
||||
}
|
||||
)
|
||||
message += MessageSegment.atDingtalkIds(event.senderId)
|
||||
await hello.send(message)
|
||||
|
||||
@ -216,22 +213,21 @@ hello = on_command("webhook", to_me())
|
||||
@hello.handle()
|
||||
async def webhook_handler(bot: DingBot, event: MessageEvent):
|
||||
print(event)
|
||||
message = MessageSegment.raw({
|
||||
message = MessageSegment.raw(
|
||||
{
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": 'hello from webhook,一定要注意安全方式的鉴权哦,否则可能发送失败的'
|
||||
},
|
||||
})
|
||||
"text": {"content": "hello from webhook,一定要注意安全方式的鉴权哦,否则可能发送失败的"},
|
||||
}
|
||||
)
|
||||
message += MessageSegment.atDingtalkIds(event.senderId)
|
||||
await hello.send(
|
||||
message,
|
||||
webhook=
|
||||
"https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
|
||||
secret="SECXXXXXXXXXXXXXXXXXXXXXXXXX")
|
||||
webhook="https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
|
||||
secret="SECXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
)
|
||||
|
||||
message = MessageSegment.text("TEST 123123 S")
|
||||
await hello.send(
|
||||
message,
|
||||
webhook=
|
||||
"https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
|
||||
webhook="https://oapi.dingtalk.com/robot/send?access_token=XXXXXXXXXXXXXX",
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
from nonebot.plugin import on_command
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.feishu import Bot as FeishuBot, MessageEvent
|
||||
from nonebot.plugin import on_command
|
||||
from nonebot.adapters.feishu import MessageEvent
|
||||
from nonebot.adapters.feishu import Bot as FeishuBot
|
||||
|
||||
helper = on_command("say")
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import nonebot
|
||||
|
||||
from .test_export import export
|
||||
|
||||
print(export, nonebot.require("test_export"))
|
||||
|
@ -4,4 +4,4 @@ from nonebot import CommandGroup, MatcherGroup
|
||||
cmd = CommandGroup("test", rule=to_me())
|
||||
match = MatcherGroup(priority=2)
|
||||
|
||||
from . import commands, matches
|
||||
from . import matches, commands
|
||||
|
@ -1,6 +1,5 @@
|
||||
from nonebot.adapters import Bot, Event
|
||||
|
||||
from . import cmd
|
||||
from nonebot.adapters import Bot, Event
|
||||
|
||||
test_1 = cmd.command("1", aliases={"test"})
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
from . import match
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.adapters.cqhttp import HeartbeatMetaEvent
|
||||
|
||||
from . import match
|
||||
|
||||
|
||||
async def heartbeat(bot: Bot, event: Event, state: T_State) -> bool:
|
||||
return isinstance(event, HeartbeatMetaEvent)
|
||||
|
@ -1,6 +1,6 @@
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.plugin import on_metaevent
|
||||
from nonebot.adapters import Bot, Event
|
||||
from nonebot.plugin import on_metaevent
|
||||
from nonebot.adapters.cqhttp import HeartbeatMetaEvent
|
||||
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
from nonebot.plugin import on_keyword, on_command
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.plugin import on_command, on_keyword
|
||||
from nonebot.adapters.mirai import Bot, MessageEvent
|
||||
|
||||
message_test = on_keyword({'reply'}, rule=to_me())
|
||||
message_test = on_keyword({"reply"}, rule=to_me())
|
||||
|
||||
|
||||
@message_test.handle()
|
||||
@ -11,7 +11,7 @@ async def _message(bot: Bot, event: MessageEvent):
|
||||
await bot.send(event, text, at_sender=True)
|
||||
|
||||
|
||||
command_test = on_command('miecho')
|
||||
command_test = on_command("miecho")
|
||||
|
||||
|
||||
@command_test.handle()
|
||||
|
@ -1,5 +1,5 @@
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, PrivateMessageEvent, GroupMessageEvent
|
||||
from nonebot.adapters.cqhttp import Bot, GroupMessageEvent, PrivateMessageEvent
|
||||
|
||||
overload = on_command("overload")
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
from nonebot.rule import to_me
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.plugin import on_startswith
|
||||
from nonebot.permission import SUPERUSER
|
||||
from nonebot.plugin import on_startswith
|
||||
from nonebot.adapters.ding import Bot as DingBot
|
||||
from nonebot.adapters.cqhttp import Bot as CQHTTPBot
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.typing import T_State
|
||||
from nonebot import on_shell_command
|
||||
from nonebot.rule import to_me, ArgumentParser
|
||||
from nonebot.rule import ArgumentParser, to_me
|
||||
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-a", action="store_true")
|
||||
|
Loading…
Reference in New Issue
Block a user