neobot-adapter 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neobot_adapter-1.0.0/PKG-INFO +14 -0
- neobot_adapter-1.0.0/README.md +0 -0
- neobot_adapter-1.0.0/pyproject.toml +28 -0
- neobot_adapter-1.0.0/pyproject.toml.orig +24 -0
- neobot_adapter-1.0.0/src/neobot_adapter/__init__.py +21 -0
- neobot_adapter-1.0.0/src/neobot_adapter/eventing.py +178 -0
- neobot_adapter-1.0.0/src/neobot_adapter/factory.py +78 -0
- neobot_adapter-1.0.0/src/neobot_adapter/interfaces.py +94 -0
- neobot_adapter-1.0.0/src/neobot_adapter/listener/__init__.py +5 -0
- neobot_adapter-1.0.0/src/neobot_adapter/listener/manager.py +309 -0
- neobot_adapter-1.0.0/src/neobot_adapter/local/__init__.py +6 -0
- neobot_adapter-1.0.0/src/neobot_adapter/local/adapter.py +374 -0
- neobot_adapter-1.0.0/src/neobot_adapter/local/core.py +896 -0
- neobot_adapter-1.0.0/src/neobot_adapter/local/server.py +266 -0
- neobot_adapter-1.0.0/src/neobot_adapter/local/store.py +1066 -0
- neobot_adapter-1.0.0/src/neobot_adapter/local/websocket.py +91 -0
- neobot_adapter-1.0.0/src/neobot_adapter/mapping.py +55 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/__init__.py +0 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/basic.py +122 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/gengeral.py +9 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/message.py +385 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/meta_event.py +27 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/notice.py +199 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/request.py +38 -0
- neobot_adapter-1.0.0/src/neobot_adapter/model/response.py +613 -0
- neobot_adapter-1.0.0/src/neobot_adapter/onebot/__init__.py +8 -0
- neobot_adapter-1.0.0/src/neobot_adapter/onebot/adapter.py +492 -0
- neobot_adapter-1.0.0/src/neobot_adapter/onebot/receiver/__init__.py +7 -0
- neobot_adapter-1.0.0/src/neobot_adapter/onebot/receiver/core.py +799 -0
- neobot_adapter-1.0.0/src/neobot_adapter/onebot/receiver/settings.py +113 -0
- neobot_adapter-1.0.0/src/neobot_adapter/py.typed +0 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/__init__.py +0 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/_proxy.py +67 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/file.py +282 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/group.py +644 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/message.py +330 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/private.py +288 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/system.py +93 -0
- neobot_adapter-1.0.0/src/neobot_adapter/request/websocket.py +246 -0
- neobot_adapter-1.0.0/src/neobot_adapter/utils/__init__.py +0 -0
- neobot_adapter-1.0.0/src/neobot_adapter/utils/env.py +10 -0
- neobot_adapter-1.0.0/src/neobot_adapter/utils/logger.py +20 -0
- neobot_adapter-1.0.0/src/neobot_adapter/utils/net.py +17 -0
- neobot_adapter-1.0.0/src/neobot_adapter/utils/parse.py +290 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: neobot-adapter
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: wsrsq, tangtian
|
|
6
|
+
Author-email: wsrsq <wsrsq001@163.com>, tangtian <a14b@126.com>
|
|
7
|
+
Requires-Dist: websockets
|
|
8
|
+
Requires-Dist: aiohttp
|
|
9
|
+
Requires-Dist: pydantic>=2.7.4
|
|
10
|
+
Requires-Dist: loguru>=0.7.2
|
|
11
|
+
Requires-Dist: neobot-contracts
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "neobot-adapter"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"websockets",
|
|
9
|
+
"aiohttp",
|
|
10
|
+
"pydantic>=2.7.4",
|
|
11
|
+
"loguru>=0.7.2",
|
|
12
|
+
"neobot-contracts",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[[project.authors]]
|
|
16
|
+
name = "wsrsq"
|
|
17
|
+
email = "wsrsq001@163.com"
|
|
18
|
+
|
|
19
|
+
[[project.authors]]
|
|
20
|
+
name = "tangtian"
|
|
21
|
+
email = "a14b@126.com"
|
|
22
|
+
|
|
23
|
+
[tool.uv.sources.neobot-contracts]
|
|
24
|
+
workspace = true
|
|
25
|
+
|
|
26
|
+
[build-system]
|
|
27
|
+
requires = ["uv_build>=0.9.27,<0.10.0"]
|
|
28
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "neobot-adapter"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "wsrsq", email = "wsrsq001@163.com" },
|
|
8
|
+
{ name = "tangtian", email = "a14b@126.com" },
|
|
9
|
+
]
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"websockets",
|
|
13
|
+
"aiohttp",
|
|
14
|
+
"pydantic>=2.7.4",
|
|
15
|
+
"loguru>=0.7.2",
|
|
16
|
+
"neobot-contracts",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.uv.sources]
|
|
20
|
+
neobot-contracts = { workspace = true }
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["uv_build>=0.9.27,<0.10.0"]
|
|
24
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""NeoBot 适配器公开入口"""
|
|
2
|
+
|
|
3
|
+
from .factory import AdapterSettings, create_adapter
|
|
4
|
+
from .interfaces import CoreLike, RuntimeAdapter
|
|
5
|
+
from .local import LocalAdapter
|
|
6
|
+
from .mapping import map_to_incoming_message
|
|
7
|
+
from .onebot import AdapterCore, OneBotAdapter, Subscription
|
|
8
|
+
from .request.websocket import WebSocketAPI
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"OneBotAdapter",
|
|
12
|
+
"LocalAdapter",
|
|
13
|
+
"Subscription",
|
|
14
|
+
"AdapterSettings",
|
|
15
|
+
"create_adapter",
|
|
16
|
+
"CoreLike",
|
|
17
|
+
"RuntimeAdapter",
|
|
18
|
+
"map_to_incoming_message",
|
|
19
|
+
"AdapterCore",
|
|
20
|
+
"WebSocketAPI",
|
|
21
|
+
]
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
5
|
+
import threading
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Awaitable, Callable, Dict, Optional, get_type_hints
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from neobot_contracts.ports.logging import Logger, NullLogger
|
|
12
|
+
|
|
13
|
+
Rule = Callable[[Dict[str, Any]], bool | Awaitable[bool]]
|
|
14
|
+
EventHandlerFunc = Callable[..., Any]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class Subscription:
|
|
19
|
+
_unsubscribe: Callable[[], None]
|
|
20
|
+
_active: bool = True
|
|
21
|
+
|
|
22
|
+
def unsubscribe(self) -> None:
|
|
23
|
+
if not self._active:
|
|
24
|
+
return
|
|
25
|
+
self._unsubscribe()
|
|
26
|
+
self._active = False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class _HandlerRegistration:
|
|
31
|
+
handler: EventHandlerFunc
|
|
32
|
+
is_async: bool
|
|
33
|
+
post_type: Optional[str]
|
|
34
|
+
message_type: Optional[str]
|
|
35
|
+
notice_type: Optional[str]
|
|
36
|
+
request_type: Optional[str]
|
|
37
|
+
meta_event_type: Optional[str]
|
|
38
|
+
sub_type: Optional[str]
|
|
39
|
+
rule: Optional[Rule]
|
|
40
|
+
priority: int
|
|
41
|
+
event_model: Optional[type[BaseModel]] = field(default=None)
|
|
42
|
+
|
|
43
|
+
def matches(self, event: Dict[str, Any]) -> bool:
|
|
44
|
+
if self.post_type and event.get("post_type") != self.post_type:
|
|
45
|
+
return False
|
|
46
|
+
if self.message_type and event.get("message_type") != self.message_type:
|
|
47
|
+
return False
|
|
48
|
+
if self.notice_type and event.get("notice_type") != self.notice_type:
|
|
49
|
+
return False
|
|
50
|
+
if self.request_type and event.get("request_type") != self.request_type:
|
|
51
|
+
return False
|
|
52
|
+
if self.meta_event_type and event.get("meta_event_type") != self.meta_event_type:
|
|
53
|
+
return False
|
|
54
|
+
if self.sub_type and event.get("sub_type") != self.sub_type:
|
|
55
|
+
return False
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
def coerce(self, event: Dict[str, Any]) -> Any:
|
|
59
|
+
if self.event_model is not None:
|
|
60
|
+
return self.event_model.model_validate(event)
|
|
61
|
+
return event
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class EventDispatcher:
|
|
65
|
+
def __init__(self, logger: Optional[Logger] = None) -> None:
|
|
66
|
+
self._handlers: list[_HandlerRegistration] = []
|
|
67
|
+
self._lock = threading.RLock()
|
|
68
|
+
self._logger: Logger = logger if logger is not None else NullLogger()
|
|
69
|
+
|
|
70
|
+
def subscribe(self, registration: _HandlerRegistration) -> Subscription:
|
|
71
|
+
with self._lock:
|
|
72
|
+
self._handlers.append(registration)
|
|
73
|
+
self._handlers.sort(key=lambda item: item.priority, reverse=True)
|
|
74
|
+
|
|
75
|
+
def _unsubscribe() -> None:
|
|
76
|
+
with self._lock:
|
|
77
|
+
self._handlers = [
|
|
78
|
+
item for item in self._handlers if item is not registration
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
return Subscription(_unsubscribe)
|
|
82
|
+
|
|
83
|
+
async def publish(self, event: Dict[str, Any]) -> None:
|
|
84
|
+
with self._lock:
|
|
85
|
+
handlers: list[_HandlerRegistration] = []
|
|
86
|
+
for handler in self._handlers:
|
|
87
|
+
try:
|
|
88
|
+
if handler.matches(event):
|
|
89
|
+
handlers.append(handler)
|
|
90
|
+
except Exception as exc:
|
|
91
|
+
# 单个处理器的匹配逻辑(或形状异常的事件)不得让整条事件
|
|
92
|
+
# 分发失败:跳过该处理器并告警,其余处理器继续。
|
|
93
|
+
self._logger.error(
|
|
94
|
+
"事件匹配失败,已跳过该处理器 "
|
|
95
|
+
f"({handler.handler.__qualname__}): {exc}"
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
for handler in handlers:
|
|
99
|
+
if handler.rule is not None:
|
|
100
|
+
try:
|
|
101
|
+
rule_result = handler.rule(event)
|
|
102
|
+
if inspect.isawaitable(rule_result):
|
|
103
|
+
rule_result = await rule_result
|
|
104
|
+
if not rule_result:
|
|
105
|
+
continue
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
self._logger.error(f"事件规则执行失败: {exc}")
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
coerced = handler.coerce(event)
|
|
112
|
+
except Exception as exc:
|
|
113
|
+
self._logger.error(f"事件模型转换失败 ({handler.handler.__qualname__}): {exc}")
|
|
114
|
+
coerced = event
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
if handler.is_async:
|
|
118
|
+
await handler.handler(coerced)
|
|
119
|
+
else:
|
|
120
|
+
await asyncio.to_thread(handler.handler, coerced)
|
|
121
|
+
except Exception as exc:
|
|
122
|
+
self._logger.error(f"事件处理失败: {exc}")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def extract_event_model(handler: EventHandlerFunc) -> Optional[type[BaseModel]]:
|
|
126
|
+
try:
|
|
127
|
+
hints = get_type_hints(handler)
|
|
128
|
+
except Exception:
|
|
129
|
+
return None
|
|
130
|
+
params = list(inspect.signature(handler).parameters.values())
|
|
131
|
+
if not params:
|
|
132
|
+
return None
|
|
133
|
+
first_param = params[0]
|
|
134
|
+
annotation = hints.get(first_param.name)
|
|
135
|
+
if annotation is None:
|
|
136
|
+
return None
|
|
137
|
+
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
|
|
138
|
+
return annotation
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class EventNamespace:
|
|
143
|
+
def __init__(self, adapter: Any, path: tuple[str, ...] = ()) -> None:
|
|
144
|
+
self._adapter = adapter
|
|
145
|
+
self._path = path
|
|
146
|
+
|
|
147
|
+
def __getattr__(self, name: str) -> "EventNamespace":
|
|
148
|
+
return EventNamespace(self._adapter, self._path + (name,))
|
|
149
|
+
|
|
150
|
+
def __call__(
|
|
151
|
+
self,
|
|
152
|
+
func: Optional[EventHandlerFunc] = None,
|
|
153
|
+
*,
|
|
154
|
+
group: bool = False,
|
|
155
|
+
private: bool = False,
|
|
156
|
+
rule: Optional[Rule] = None,
|
|
157
|
+
priority: int = 0,
|
|
158
|
+
sub_type: Optional[str] = None,
|
|
159
|
+
) -> Any:
|
|
160
|
+
filters = self._adapter._filters_from_path(
|
|
161
|
+
self._path,
|
|
162
|
+
group=group,
|
|
163
|
+
private=private,
|
|
164
|
+
sub_type=sub_type,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
def decorator(handler: EventHandlerFunc) -> EventHandlerFunc:
|
|
168
|
+
self._adapter._register_handler(
|
|
169
|
+
handler,
|
|
170
|
+
rule=rule,
|
|
171
|
+
priority=priority,
|
|
172
|
+
**filters,
|
|
173
|
+
)
|
|
174
|
+
return handler
|
|
175
|
+
|
|
176
|
+
if func is not None:
|
|
177
|
+
return decorator(func)
|
|
178
|
+
return decorator
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from neobot_contracts.ports.logging import Logger, NullLogger
|
|
7
|
+
from neobot_contracts.ports.sandbox import SandboxDataPort
|
|
8
|
+
|
|
9
|
+
from neobot_adapter.interfaces import RuntimeAdapter
|
|
10
|
+
from neobot_adapter.local import LocalAdapter
|
|
11
|
+
from neobot_adapter.onebot import OneBotAdapter
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class AdapterSettings:
|
|
16
|
+
mode: str = "onebot"
|
|
17
|
+
local_host: str = "127.0.0.1"
|
|
18
|
+
local_port: int = 8090
|
|
19
|
+
local_auth_token: str = ""
|
|
20
|
+
bot_user_id: int = 0
|
|
21
|
+
bot_name: str = "Neo Bot"
|
|
22
|
+
# onebot 反向 WebSocket 服务端监听(空/0 表示未配置,回退环境变量与默认值)
|
|
23
|
+
reverse_ws_host: str = ""
|
|
24
|
+
reverse_ws_port: int = 0
|
|
25
|
+
# 反向 WebSocket 握手鉴权的 access token(OneBot 11 规范;留空表示不校验)
|
|
26
|
+
reverse_ws_access_token: str = ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def create_adapter(
|
|
30
|
+
settings: AdapterSettings | Any | None = None,
|
|
31
|
+
*,
|
|
32
|
+
logger: Logger | None = None,
|
|
33
|
+
packet_callback: Callable[[dict[str, Any]], None] | None = None,
|
|
34
|
+
sandbox_data: SandboxDataPort | None = None,
|
|
35
|
+
) -> RuntimeAdapter:
|
|
36
|
+
cfg = _coerce_settings(settings)
|
|
37
|
+
adapter_logger = logger or NullLogger()
|
|
38
|
+
mode = cfg.mode.strip().casefold()
|
|
39
|
+
if mode == "onebot":
|
|
40
|
+
return OneBotAdapter(
|
|
41
|
+
logger=adapter_logger,
|
|
42
|
+
packet_callback=packet_callback,
|
|
43
|
+
host=cfg.reverse_ws_host or None,
|
|
44
|
+
port=cfg.reverse_ws_port if cfg.reverse_ws_port else None,
|
|
45
|
+
access_token=cfg.reverse_ws_access_token,
|
|
46
|
+
)
|
|
47
|
+
if mode == "local":
|
|
48
|
+
return LocalAdapter(
|
|
49
|
+
host=cfg.local_host,
|
|
50
|
+
port=cfg.local_port,
|
|
51
|
+
auth_token=cfg.local_auth_token,
|
|
52
|
+
bot_user_id=cfg.bot_user_id,
|
|
53
|
+
bot_name=cfg.bot_name,
|
|
54
|
+
logger=adapter_logger,
|
|
55
|
+
packet_callback=packet_callback,
|
|
56
|
+
sandbox_data=sandbox_data,
|
|
57
|
+
)
|
|
58
|
+
raise ValueError(f"未知适配器模式: {cfg.mode}。可选值: onebot, local")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _coerce_settings(settings: AdapterSettings | Any | None) -> AdapterSettings:
|
|
62
|
+
if settings is None:
|
|
63
|
+
return AdapterSettings()
|
|
64
|
+
if isinstance(settings, AdapterSettings):
|
|
65
|
+
return settings
|
|
66
|
+
return AdapterSettings(
|
|
67
|
+
mode=str(getattr(settings, "mode", "onebot") or "onebot"),
|
|
68
|
+
local_host=str(getattr(settings, "local_host", "127.0.0.1") or "127.0.0.1"),
|
|
69
|
+
local_port=int(getattr(settings, "local_port", 8090) or 8090),
|
|
70
|
+
local_auth_token=str(getattr(settings, "local_auth_token", "") or ""),
|
|
71
|
+
bot_user_id=int(getattr(settings, "bot_user_id", 0) or 0),
|
|
72
|
+
bot_name=str(getattr(settings, "bot_name", "Neo Bot") or "Neo Bot"),
|
|
73
|
+
reverse_ws_host=str(getattr(settings, "reverse_ws_host", "") or ""),
|
|
74
|
+
reverse_ws_port=int(getattr(settings, "reverse_ws_port", 0) or 0),
|
|
75
|
+
reverse_ws_access_token=str(
|
|
76
|
+
getattr(settings, "reverse_ws_access_token", "") or ""
|
|
77
|
+
),
|
|
78
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from neobot_contracts.models import ConversationRef
|
|
6
|
+
|
|
7
|
+
from neobot_adapter.eventing import EventHandlerFunc, Subscription
|
|
8
|
+
from neobot_adapter.model.response import SendMsgResponse
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@runtime_checkable
|
|
12
|
+
class CoreLike(Protocol):
|
|
13
|
+
async def call_api(
|
|
14
|
+
self,
|
|
15
|
+
action: str,
|
|
16
|
+
params: dict[str, Any],
|
|
17
|
+
timeout: float = 5.0,
|
|
18
|
+
websocket: Any = None,
|
|
19
|
+
) -> dict[str, Any] | None: ...
|
|
20
|
+
|
|
21
|
+
def call_api_sync(
|
|
22
|
+
self,
|
|
23
|
+
action: str,
|
|
24
|
+
params: dict[str, Any],
|
|
25
|
+
timeout: float = 5.0,
|
|
26
|
+
websocket: Any = None,
|
|
27
|
+
) -> dict[str, Any] | None: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@runtime_checkable
|
|
31
|
+
class RuntimeAdapter(Protocol):
|
|
32
|
+
@property
|
|
33
|
+
def requires_connection_wait(self) -> bool: ...
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def connected(self) -> bool: ...
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def http_url(self) -> str:
|
|
40
|
+
"""本地适配器(local 模式)的 HTTP 地址;不适用时为空串。"""
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def ws_url(self) -> str:
|
|
45
|
+
"""本地适配器(local 模式)的 WebSocket 地址;不适用时为空串。"""
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
async def start(self) -> None: ...
|
|
49
|
+
|
|
50
|
+
async def stop(self) -> None: ...
|
|
51
|
+
|
|
52
|
+
def wait_for_connection(self, timeout: float | None = None) -> bool: ...
|
|
53
|
+
|
|
54
|
+
def subscribe(
|
|
55
|
+
self,
|
|
56
|
+
event_type: Any,
|
|
57
|
+
handler: EventHandlerFunc,
|
|
58
|
+
**filters: Any,
|
|
59
|
+
) -> Subscription: ...
|
|
60
|
+
|
|
61
|
+
async def call_api(
|
|
62
|
+
self,
|
|
63
|
+
action: str,
|
|
64
|
+
params: dict[str, Any],
|
|
65
|
+
timeout: float = 5.0,
|
|
66
|
+
wait_response: bool = True,
|
|
67
|
+
) -> dict[str, Any] | None: ...
|
|
68
|
+
|
|
69
|
+
async def send(
|
|
70
|
+
self,
|
|
71
|
+
conversation: ConversationRef,
|
|
72
|
+
message: str | list[dict[str, Any]],
|
|
73
|
+
timeout: float = 5.0,
|
|
74
|
+
wait_response: bool = True,
|
|
75
|
+
) -> SendMsgResponse: ...
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@runtime_checkable
|
|
79
|
+
class AdapterReconfigurable(Protocol):
|
|
80
|
+
"""可在运行期改监听设置(host / port / access token)的适配器能力。
|
|
81
|
+
|
|
82
|
+
只有「监听设置不固化在构造期」的适配器实现它(如 OneBotAdapter);
|
|
83
|
+
控制面用 ``isinstance(adapter, AdapterReconfigurable)`` 做能力查询,
|
|
84
|
+
而不是 ``hasattr`` 猜测 —— 能力应当是接口的一部分,不是隐藏的可选成员。
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def settings(self) -> Any:
|
|
89
|
+
"""当前解析后的监听设置(值对象,用于比较与展示)。"""
|
|
90
|
+
...
|
|
91
|
+
|
|
92
|
+
def reconfigure(self, settings: Any) -> None:
|
|
93
|
+
"""写入新的监听设置;不触碰运行中的接收线程。"""
|
|
94
|
+
...
|