boxim-sdk 3.0.0__py3-none-any.whl
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.
- boxim/__init__.py +563 -0
- boxim/boxim.py +5499 -0
- boxim/client.py +41 -0
- boxim/emoji_assets/0.23538cb7.gif +0 -0
- boxim/emoji_assets/1.41d8ebbe.gif +0 -0
- boxim/emoji_assets/10.d13dcac4.gif +0 -0
- boxim/emoji_assets/11.b0d85036.gif +0 -0
- boxim/emoji_assets/12.c86081d0.gif +0 -0
- boxim/emoji_assets/13.aaebd091.gif +0 -0
- boxim/emoji_assets/14.8734eba5.gif +0 -0
- boxim/emoji_assets/15.75820281.gif +0 -0
- boxim/emoji_assets/16.942bf804.gif +0 -0
- boxim/emoji_assets/18.c47c412d.gif +0 -0
- boxim/emoji_assets/19.89260df5.gif +0 -0
- boxim/emoji_assets/2.302b51b8.gif +0 -0
- boxim/emoji_assets/20.6428ddb3.gif +0 -0
- boxim/emoji_assets/21.ec8df612.gif +0 -0
- boxim/emoji_assets/22.1a731ef9.gif +0 -0
- boxim/emoji_assets/23.071999b8.gif +0 -0
- boxim/emoji_assets/24.52e37bff.gif +0 -0
- boxim/emoji_assets/25.6f582617.gif +0 -0
- boxim/emoji_assets/26.03b7a469.gif +0 -0
- boxim/emoji_assets/27.65ed7407.gif +0 -0
- boxim/emoji_assets/28.52524722.gif +0 -0
- boxim/emoji_assets/29.1659f59b.gif +0 -0
- boxim/emoji_assets/3.7abec26c.gif +0 -0
- boxim/emoji_assets/30.5bd2ccd2.gif +0 -0
- boxim/emoji_assets/31.4894333c.gif +0 -0
- boxim/emoji_assets/32.f35d7073.gif +0 -0
- boxim/emoji_assets/33.deb9bb7e.gif +0 -0
- boxim/emoji_assets/34.b4c0eba4.gif +0 -0
- boxim/emoji_assets/35.7e9e390a.gif +0 -0
- boxim/emoji_assets/36.c4e8fcb2.gif +0 -0
- boxim/emoji_assets/37.418a25ff.gif +0 -0
- boxim/emoji_assets/38.8f1726b9.gif +0 -0
- boxim/emoji_assets/39.a8484b5b.gif +0 -0
- boxim/emoji_assets/4.be67348c.gif +0 -0
- boxim/emoji_assets/40.2b8929bd.gif +0 -0
- boxim/emoji_assets/41.0b009f7b.gif +0 -0
- boxim/emoji_assets/42.02d062c5.gif +0 -0
- boxim/emoji_assets/43.62177cf1.gif +0 -0
- boxim/emoji_assets/44.00ef8c19.gif +0 -0
- boxim/emoji_assets/45.7bab77e0.gif +0 -0
- boxim/emoji_assets/46.e9ac968c.gif +0 -0
- boxim/emoji_assets/47.b4ac667a.gif +0 -0
- boxim/emoji_assets/48.20d1ce26.gif +0 -0
- boxim/emoji_assets/49.c881faa6.gif +0 -0
- boxim/emoji_assets/5.11e27819.gif +0 -0
- boxim/emoji_assets/50.7614f726.gif +0 -0
- boxim/emoji_assets/51.5d136c53.gif +0 -0
- boxim/emoji_assets/52.21a20728.gif +0 -0
- boxim/emoji_assets/53.a59af82b.gif +0 -0
- boxim/emoji_assets/54.2030cc4e.gif +0 -0
- boxim/emoji_assets/55.469289a4.gif +0 -0
- boxim/emoji_assets/56.33767a85.gif +0 -0
- boxim/emoji_assets/6.187a4467.gif +0 -0
- boxim/emoji_assets/7.fb655c96.gif +0 -0
- boxim/emoji_assets/8.89742749.gif +0 -0
- boxim/emoji_assets/9.0b2dd09a.gif +0 -0
- boxim/emoji_assets/fav.3c74a9d3.png +0 -0
- boxim/emoji_assets/no_data.e3b08d88.png +0 -0
- boxim/util/__init__.py +166 -0
- boxim/util/config.py +140 -0
- boxim/util/container.py +98 -0
- boxim/util/decorators.py +222 -0
- boxim/util/emoji.py +182 -0
- boxim/util/enums.py +203 -0
- boxim/util/env.py +162 -0
- boxim/util/events.py +138 -0
- boxim/util/exceptions.py +67 -0
- boxim/util/logging_util.py +38 -0
- boxim/util/message_builder.py +306 -0
- boxim/util/models.py +715 -0
- boxim/util/protocols.py +213 -0
- boxim/util/rtc.py +1213 -0
- boxim/util/streams.py +527 -0
- boxim/util/transport_http.py +595 -0
- boxim/util/transport_ws.py +461 -0
- boxim/util/uploader.py +192 -0
- boxim_sdk-3.0.0.dist-info/METADATA +822 -0
- boxim_sdk-3.0.0.dist-info/RECORD +83 -0
- boxim_sdk-3.0.0.dist-info/WHEEL +5 -0
- boxim_sdk-3.0.0.dist-info/top_level.txt +1 -0
boxim/__init__.py
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
import types
|
|
6
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from boxim.boxim import (
|
|
9
|
+
BoxIM,
|
|
10
|
+
aquick_login,
|
|
11
|
+
quick_login,
|
|
12
|
+
)
|
|
13
|
+
from boxim.util import (
|
|
14
|
+
AsyncAudioStream,
|
|
15
|
+
AsyncBytesStreamAdapter,
|
|
16
|
+
AsyncCallbackAudioStream,
|
|
17
|
+
AsyncMessageHandler,
|
|
18
|
+
AsyncStreamCallback,
|
|
19
|
+
AsyncVideoStream,
|
|
20
|
+
AudioStream,
|
|
21
|
+
AuthError,
|
|
22
|
+
BoxIMError,
|
|
23
|
+
BytesStreamAdapter,
|
|
24
|
+
CallbackAudioStream,
|
|
25
|
+
ChatType,
|
|
26
|
+
ComplaintType,
|
|
27
|
+
ConfigError,
|
|
28
|
+
Container,
|
|
29
|
+
EnvManager,
|
|
30
|
+
EventEmitter,
|
|
31
|
+
FileAudioStream,
|
|
32
|
+
FileUploader,
|
|
33
|
+
Friend,
|
|
34
|
+
FriendRequest,
|
|
35
|
+
FriendRequestStatus,
|
|
36
|
+
Group,
|
|
37
|
+
HTTPTransport,
|
|
38
|
+
MediaProcessor,
|
|
39
|
+
MediaProcessorWithVideo,
|
|
40
|
+
Message,
|
|
41
|
+
MessageBuilder,
|
|
42
|
+
MessageHandler,
|
|
43
|
+
MessageType,
|
|
44
|
+
NetworkError,
|
|
45
|
+
QRLoginInfo,
|
|
46
|
+
QRLoginStatus,
|
|
47
|
+
QueueAudioStream,
|
|
48
|
+
RTCError,
|
|
49
|
+
RTCMode,
|
|
50
|
+
RTCSessionInfo,
|
|
51
|
+
RTCState,
|
|
52
|
+
RegistrationMode,
|
|
53
|
+
SDKConfig,
|
|
54
|
+
SilenceAudioStream,
|
|
55
|
+
Sticker,
|
|
56
|
+
StickerAlbum,
|
|
57
|
+
StreamCallback,
|
|
58
|
+
StreamError,
|
|
59
|
+
SystemConfig,
|
|
60
|
+
SystemMessage,
|
|
61
|
+
TerminalType,
|
|
62
|
+
TimeoutError,
|
|
63
|
+
TokenInfo,
|
|
64
|
+
TokenStore,
|
|
65
|
+
ToneAudioStream,
|
|
66
|
+
UploadResult,
|
|
67
|
+
User,
|
|
68
|
+
UserSex,
|
|
69
|
+
ValidationError,
|
|
70
|
+
VideoStream,
|
|
71
|
+
WebSocketCommand,
|
|
72
|
+
WebSocketTransport,
|
|
73
|
+
async_require_login,
|
|
74
|
+
auto_retry,
|
|
75
|
+
emoji_util,
|
|
76
|
+
require_login,
|
|
77
|
+
setup_logging,
|
|
78
|
+
validate_params,
|
|
79
|
+
RTCCallSession,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
__version__ = "3.0.0"
|
|
83
|
+
__author__ = "nichengfuben"
|
|
84
|
+
__license__ = "MIT"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# ============================================================================
|
|
88
|
+
# 全局实例管理器
|
|
89
|
+
# ============================================================================
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class _InstanceManager:
|
|
93
|
+
"""线程安全的全局 BoxIM 实例管理器。
|
|
94
|
+
|
|
95
|
+
支持多命名实例管理和当前活跃实例切换。
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
_DEFAULT = "__default__"
|
|
99
|
+
|
|
100
|
+
def __init__(self) -> None:
|
|
101
|
+
self._lock = threading.RLock()
|
|
102
|
+
self._instances: Dict[str, BoxIM] = {}
|
|
103
|
+
self._current: Optional[str] = None
|
|
104
|
+
|
|
105
|
+
def init(
|
|
106
|
+
self,
|
|
107
|
+
username: str,
|
|
108
|
+
password: str,
|
|
109
|
+
name: Optional[str] = None,
|
|
110
|
+
config: Optional[SDKConfig] = None,
|
|
111
|
+
terminal: TerminalType = TerminalType.WEB,
|
|
112
|
+
**kw: Any,
|
|
113
|
+
) -> BoxIM:
|
|
114
|
+
"""初始化全局默认实例并同步登录。
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
username: 用户名
|
|
118
|
+
password: 密码
|
|
119
|
+
name: 实例名称(默认为 "__default__")
|
|
120
|
+
config: SDK 配置
|
|
121
|
+
terminal: 终端类型
|
|
122
|
+
**kw: 透传给 BoxIM 构造函数的额外参数
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
BoxIM: 已登录的实例
|
|
126
|
+
"""
|
|
127
|
+
return self._register(
|
|
128
|
+
BoxIM(config=config, **kw).login(
|
|
129
|
+
username, password, terminal=terminal
|
|
130
|
+
),
|
|
131
|
+
name,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def ainit(
|
|
135
|
+
self,
|
|
136
|
+
username: str,
|
|
137
|
+
password: str,
|
|
138
|
+
name: Optional[str] = None,
|
|
139
|
+
config: Optional[SDKConfig] = None,
|
|
140
|
+
terminal: TerminalType = TerminalType.WEB,
|
|
141
|
+
**kw: Any,
|
|
142
|
+
) -> BoxIM:
|
|
143
|
+
"""异步初始化全局默认实例并登录。
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
username: 用户名
|
|
147
|
+
password: 密码
|
|
148
|
+
name: 实例名称
|
|
149
|
+
config: SDK 配置
|
|
150
|
+
terminal: 终端类型
|
|
151
|
+
**kw: 透传给 BoxIM 构造函数的额外参数
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
BoxIM: 已登录的实例
|
|
155
|
+
"""
|
|
156
|
+
im = BoxIM(config=config, **kw)
|
|
157
|
+
await im.alogin(username, password, terminal=terminal)
|
|
158
|
+
return self._register(im, name)
|
|
159
|
+
|
|
160
|
+
def create(
|
|
161
|
+
self,
|
|
162
|
+
username: str,
|
|
163
|
+
password: str,
|
|
164
|
+
name: str = _DEFAULT,
|
|
165
|
+
config: Optional[SDKConfig] = None,
|
|
166
|
+
terminal: TerminalType = TerminalType.WEB,
|
|
167
|
+
set_current: bool = True,
|
|
168
|
+
**kw: Any,
|
|
169
|
+
) -> BoxIM:
|
|
170
|
+
"""创建并注册命名实例(同步登录)。
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
username: 用户名
|
|
174
|
+
password: 密码
|
|
175
|
+
name: 实例名称
|
|
176
|
+
config: SDK 配置
|
|
177
|
+
terminal: 终端类型
|
|
178
|
+
set_current: 是否设为当前活跃实例
|
|
179
|
+
**kw: 透传给 BoxIM 构造函数的额外参数
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
BoxIM: 已登录的实例
|
|
183
|
+
"""
|
|
184
|
+
im = BoxIM(config=config, **kw).login(
|
|
185
|
+
username, password, terminal=terminal
|
|
186
|
+
)
|
|
187
|
+
return self._register(im, name, set_current)
|
|
188
|
+
|
|
189
|
+
async def acreate(
|
|
190
|
+
self,
|
|
191
|
+
username: str,
|
|
192
|
+
password: str,
|
|
193
|
+
name: str = _DEFAULT,
|
|
194
|
+
config: Optional[SDKConfig] = None,
|
|
195
|
+
terminal: TerminalType = TerminalType.WEB,
|
|
196
|
+
set_current: bool = True,
|
|
197
|
+
**kw: Any,
|
|
198
|
+
) -> BoxIM:
|
|
199
|
+
"""异步创建并注册命名实例。
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
username: 用户名
|
|
203
|
+
password: 密码
|
|
204
|
+
name: 实例名称
|
|
205
|
+
config: SDK 配置
|
|
206
|
+
terminal: 终端类型
|
|
207
|
+
set_current: 是否设为当前活跃实例
|
|
208
|
+
**kw: 透传给 BoxIM 构造函数的额外参数
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
BoxIM: 已登录的实例
|
|
212
|
+
"""
|
|
213
|
+
im = BoxIM(config=config, **kw)
|
|
214
|
+
await im.alogin(username, password, terminal=terminal)
|
|
215
|
+
return self._register(im, name, set_current)
|
|
216
|
+
|
|
217
|
+
def register(
|
|
218
|
+
self,
|
|
219
|
+
im: BoxIM,
|
|
220
|
+
name: Optional[str] = None,
|
|
221
|
+
set_current: bool = True,
|
|
222
|
+
) -> BoxIM:
|
|
223
|
+
"""注册已有 BoxIM 实例。
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
im: 已初始化的 BoxIM 实例
|
|
227
|
+
name: 实例名称
|
|
228
|
+
set_current: 是否设为当前活跃实例
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
BoxIM: 传入的实例
|
|
232
|
+
"""
|
|
233
|
+
return self._register(im, name, set_current)
|
|
234
|
+
|
|
235
|
+
def _register(
|
|
236
|
+
self,
|
|
237
|
+
im: BoxIM,
|
|
238
|
+
name: Optional[str] = None,
|
|
239
|
+
set_current: bool = True,
|
|
240
|
+
) -> BoxIM:
|
|
241
|
+
"""内部注册逻辑(线程安全)。
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
im: BoxIM 实例
|
|
245
|
+
name: 实例名称
|
|
246
|
+
set_current: 是否设为当前活跃实例
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
BoxIM: 传入的实例
|
|
250
|
+
"""
|
|
251
|
+
key = name or self._DEFAULT
|
|
252
|
+
with self._lock:
|
|
253
|
+
self._instances[key] = im
|
|
254
|
+
if set_current or self._current is None:
|
|
255
|
+
self._current = key
|
|
256
|
+
return im
|
|
257
|
+
|
|
258
|
+
def get_instance(self, name: Optional[str] = None) -> BoxIM:
|
|
259
|
+
"""获取指定名称的实例,未初始化时抛出异常。
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
name: 实例名称;为 None 时获取当前活跃实例
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
BoxIM: 实例对象
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
BoxIMError: 实例不存在或未初始化
|
|
269
|
+
"""
|
|
270
|
+
with self._lock:
|
|
271
|
+
key = name or self._current
|
|
272
|
+
if key is None or key not in self._instances:
|
|
273
|
+
raise BoxIMError(
|
|
274
|
+
"BoxIM 未初始化,请先调用 boxim.init() 或 BoxIM().login()"
|
|
275
|
+
)
|
|
276
|
+
return self._instances[key]
|
|
277
|
+
|
|
278
|
+
# get 是 get_instance 的别名
|
|
279
|
+
get = get_instance
|
|
280
|
+
|
|
281
|
+
def use(self, name: str) -> BoxIM:
|
|
282
|
+
"""切换当前活跃实例。
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
name: 要切换到的实例名称
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
BoxIM: 切换后的活跃实例
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
BoxIMError: 实例名称不存在
|
|
292
|
+
"""
|
|
293
|
+
with self._lock:
|
|
294
|
+
if name not in self._instances:
|
|
295
|
+
raise BoxIMError(
|
|
296
|
+
f"实例 '{name}' 不存在,"
|
|
297
|
+
f"已注册: {list(self._instances.keys())}"
|
|
298
|
+
)
|
|
299
|
+
self._current = name
|
|
300
|
+
return self._instances[name]
|
|
301
|
+
|
|
302
|
+
def list_instances(self) -> List[str]:
|
|
303
|
+
"""列出所有已注册的实例名称。
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
List[str]: 实例名称列表
|
|
307
|
+
"""
|
|
308
|
+
with self._lock:
|
|
309
|
+
return list(self._instances.keys())
|
|
310
|
+
|
|
311
|
+
def has(self, name: str) -> bool:
|
|
312
|
+
"""检查指定名称的实例是否已注册。
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
name: 实例名称
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
bool: 是否已注册
|
|
319
|
+
"""
|
|
320
|
+
with self._lock:
|
|
321
|
+
return name in self._instances
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def is_initialized(self) -> bool:
|
|
325
|
+
"""是否已有任何实例完成初始化。"""
|
|
326
|
+
with self._lock:
|
|
327
|
+
return bool(self._instances)
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def current_name(self) -> Optional[str]:
|
|
331
|
+
"""当前活跃实例名称。"""
|
|
332
|
+
with self._lock:
|
|
333
|
+
return self._current
|
|
334
|
+
|
|
335
|
+
def remove(self, name: str) -> None:
|
|
336
|
+
"""移除并关闭指定实例(同步)。
|
|
337
|
+
|
|
338
|
+
Args:
|
|
339
|
+
name: 要移除的实例名称
|
|
340
|
+
"""
|
|
341
|
+
with self._lock:
|
|
342
|
+
im = self._instances.pop(name, None)
|
|
343
|
+
if self._current == name:
|
|
344
|
+
keys = list(self._instances.keys())
|
|
345
|
+
self._current = keys[0] if keys else None
|
|
346
|
+
if im is not None:
|
|
347
|
+
try:
|
|
348
|
+
im.close()
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
async def aremove(self, name: str) -> None:
|
|
353
|
+
"""异步移除并关闭指定实例。
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
name: 要移除的实例名称
|
|
357
|
+
"""
|
|
358
|
+
with self._lock:
|
|
359
|
+
im = self._instances.pop(name, None)
|
|
360
|
+
if self._current == name:
|
|
361
|
+
keys = list(self._instances.keys())
|
|
362
|
+
self._current = keys[0] if keys else None
|
|
363
|
+
if im is not None:
|
|
364
|
+
try:
|
|
365
|
+
await im.aclose()
|
|
366
|
+
except Exception:
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
def destroy(self) -> None:
|
|
370
|
+
"""销毁所有实例并重置管理器(同步)。"""
|
|
371
|
+
with self._lock:
|
|
372
|
+
instances = list(self._instances.values())
|
|
373
|
+
self._instances.clear()
|
|
374
|
+
self._current = None
|
|
375
|
+
for im in instances:
|
|
376
|
+
try:
|
|
377
|
+
im.close()
|
|
378
|
+
except Exception:
|
|
379
|
+
pass
|
|
380
|
+
|
|
381
|
+
async def adestroy(self) -> None:
|
|
382
|
+
"""异步销毁所有实例并重置管理器。"""
|
|
383
|
+
with self._lock:
|
|
384
|
+
instances = list(self._instances.values())
|
|
385
|
+
self._instances.clear()
|
|
386
|
+
self._current = None
|
|
387
|
+
for im in instances:
|
|
388
|
+
try:
|
|
389
|
+
await im.aclose()
|
|
390
|
+
except Exception:
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
def reset(self) -> None:
|
|
394
|
+
"""重置管理器(等同于 destroy)。"""
|
|
395
|
+
self.destroy()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
_mgr = _InstanceManager()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# ============================================================================
|
|
402
|
+
# 模块代理:将 boxim.xxx() 转发到当前活跃 BoxIM 实例
|
|
403
|
+
# ============================================================================
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
class _BoxIMModule(types.ModuleType):
|
|
407
|
+
"""模块级代理。
|
|
408
|
+
|
|
409
|
+
拦截属性访问,将未定义的属性自动代理到当前活跃 BoxIM 实例,
|
|
410
|
+
实现 ``import boxim; boxim.send_text(...)`` 的极简调用模式。
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
_MANAGER_METHODS = frozenset({
|
|
414
|
+
"init", "ainit", "create", "acreate", "register",
|
|
415
|
+
"get_instance", "get", "use", "list_instances", "has",
|
|
416
|
+
"is_initialized", "current_name", "remove", "aremove",
|
|
417
|
+
"destroy", "adestroy", "reset",
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
def __getattr__(self, name: str) -> Any:
|
|
421
|
+
# 1. 管理器方法直接暴露为模块函数
|
|
422
|
+
if name in self._MANAGER_METHODS:
|
|
423
|
+
attr = getattr(_mgr, name, None)
|
|
424
|
+
if attr is not None:
|
|
425
|
+
return attr
|
|
426
|
+
|
|
427
|
+
# 2. on_event 需要转发到当前活跃实例
|
|
428
|
+
if name == "on_event":
|
|
429
|
+
return _mgr.get_instance().on_event
|
|
430
|
+
|
|
431
|
+
# 3. listen 系列异步入口
|
|
432
|
+
if name == "listen":
|
|
433
|
+
async def _listen() -> None:
|
|
434
|
+
await _mgr.get_instance().listen()
|
|
435
|
+
return _listen
|
|
436
|
+
|
|
437
|
+
if name == "start_listening":
|
|
438
|
+
async def _start() -> BoxIM:
|
|
439
|
+
return await _mgr.get_instance().start_listening()
|
|
440
|
+
return _start
|
|
441
|
+
|
|
442
|
+
if name == "stop_listening":
|
|
443
|
+
async def _stop() -> BoxIM:
|
|
444
|
+
return await _mgr.get_instance().stop_listening()
|
|
445
|
+
return _stop
|
|
446
|
+
|
|
447
|
+
# 4. 代理到当前活跃实例的方法/属性
|
|
448
|
+
if _mgr.is_initialized:
|
|
449
|
+
try:
|
|
450
|
+
im = _mgr.get_instance()
|
|
451
|
+
return getattr(im, name)
|
|
452
|
+
except (BoxIMError, AttributeError):
|
|
453
|
+
pass
|
|
454
|
+
|
|
455
|
+
raise AttributeError(f"module 'boxim' has no attribute '{name}'")
|
|
456
|
+
|
|
457
|
+
def __dir__(self) -> List[str]:
|
|
458
|
+
"""支持 dir() 自动补全。"""
|
|
459
|
+
base = list(super().__dir__())
|
|
460
|
+
base.extend(self._MANAGER_METHODS)
|
|
461
|
+
if _mgr.is_initialized:
|
|
462
|
+
try:
|
|
463
|
+
im = _mgr.get_instance()
|
|
464
|
+
base.extend(
|
|
465
|
+
a for a in dir(im)
|
|
466
|
+
if not a.startswith("_") and a not in base
|
|
467
|
+
)
|
|
468
|
+
except BoxIMError:
|
|
469
|
+
pass
|
|
470
|
+
return base
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# ============================================================================
|
|
474
|
+
# 替换 sys.modules 中的模块对象为代理实例
|
|
475
|
+
# ============================================================================
|
|
476
|
+
|
|
477
|
+
_this_module = sys.modules[__name__]
|
|
478
|
+
_proxy_module = _BoxIMModule(__name__, __doc__)
|
|
479
|
+
|
|
480
|
+
# 将当前模块的所有已定义属性复制到代理模块
|
|
481
|
+
for _attr_name in list(vars(_this_module).keys()):
|
|
482
|
+
if not _attr_name.startswith("__") or _attr_name in (
|
|
483
|
+
"__version__",
|
|
484
|
+
"__author__",
|
|
485
|
+
"__license__",
|
|
486
|
+
"__all__",
|
|
487
|
+
"__file__",
|
|
488
|
+
"__spec__",
|
|
489
|
+
"__path__",
|
|
490
|
+
"__package__",
|
|
491
|
+
"__loader__",
|
|
492
|
+
"__builtins__",
|
|
493
|
+
):
|
|
494
|
+
try:
|
|
495
|
+
setattr(_proxy_module, _attr_name, getattr(_this_module, _attr_name))
|
|
496
|
+
except (AttributeError, TypeError):
|
|
497
|
+
pass
|
|
498
|
+
|
|
499
|
+
# 确保关键元属性存在
|
|
500
|
+
_proxy_module.__version__ = __version__
|
|
501
|
+
_proxy_module.__author__ = __author__
|
|
502
|
+
_proxy_module.__license__ = __license__
|
|
503
|
+
_proxy_module.__package__ = __package__
|
|
504
|
+
|
|
505
|
+
if hasattr(_this_module, "__path__"):
|
|
506
|
+
_proxy_module.__path__ = _this_module.__path__ # type: ignore[attr-defined]
|
|
507
|
+
if hasattr(_this_module, "__file__"):
|
|
508
|
+
_proxy_module.__file__ = _this_module.__file__
|
|
509
|
+
if hasattr(_this_module, "__spec__"):
|
|
510
|
+
_proxy_module.__spec__ = _this_module.__spec__
|
|
511
|
+
|
|
512
|
+
sys.modules[__name__] = _proxy_module
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
# ============================================================================
|
|
516
|
+
# __all__
|
|
517
|
+
# ============================================================================
|
|
518
|
+
|
|
519
|
+
__all__ = [
|
|
520
|
+
# ---- 核心类 ----
|
|
521
|
+
"BoxIM", "RTCCallSession", "quick_login", "aquick_login",
|
|
522
|
+
# ---- 全局管理 ----
|
|
523
|
+
"init", "ainit", "create", "acreate", "register",
|
|
524
|
+
"get_instance", "get", "use",
|
|
525
|
+
"list_instances", "has", "is_initialized", "current_name",
|
|
526
|
+
"remove", "aremove", "destroy", "adestroy", "reset",
|
|
527
|
+
# ---- 枚举 ----
|
|
528
|
+
"MessageType", "TerminalType", "FriendRequestStatus", "ComplaintType",
|
|
529
|
+
"UserSex", "RegistrationMode", "ChatType", "RTCMode", "RTCState",
|
|
530
|
+
"QRLoginStatus", "WebSocketCommand",
|
|
531
|
+
# ---- 异常 ----
|
|
532
|
+
"BoxIMError", "AuthError", "NetworkError", "ValidationError",
|
|
533
|
+
"RTCError", "StreamError", "ConfigError", "TimeoutError",
|
|
534
|
+
# ---- 数据模型 ----
|
|
535
|
+
"User", "Friend", "FriendRequest", "Group", "Message",
|
|
536
|
+
"SystemMessage", "CaptchaCode", "StickerAlbum", "Sticker",
|
|
537
|
+
"QRLoginInfo", "SystemConfig", "TokenInfo", "RTCSessionInfo",
|
|
538
|
+
"UploadResult",
|
|
539
|
+
# ---- 配置 ----
|
|
540
|
+
"SDKConfig", "EnvManager",
|
|
541
|
+
# ---- 协议 ----
|
|
542
|
+
"TokenStore", "AudioStream", "AsyncAudioStream",
|
|
543
|
+
"VideoStream", "AsyncVideoStream",
|
|
544
|
+
"MediaProcessor", "MediaProcessorWithVideo",
|
|
545
|
+
# ---- 事件 ----
|
|
546
|
+
"EventEmitter",
|
|
547
|
+
# ---- 流适配器 ----
|
|
548
|
+
"BytesStreamAdapter", "AsyncBytesStreamAdapter",
|
|
549
|
+
"CallbackAudioStream", "AsyncCallbackAudioStream",
|
|
550
|
+
"QueueAudioStream", "FileAudioStream",
|
|
551
|
+
"SilenceAudioStream", "ToneAudioStream",
|
|
552
|
+
# ---- 传输层 ----
|
|
553
|
+
"HTTPTransport", "WebSocketTransport",
|
|
554
|
+
# ---- 工具 ----
|
|
555
|
+
"FileUploader", "MessageBuilder", "Container", "emoji_util",
|
|
556
|
+
# ---- 装饰器 ----
|
|
557
|
+
"require_login", "async_require_login", "auto_retry", "validate_params",
|
|
558
|
+
# ---- 日志 ----
|
|
559
|
+
"setup_logging",
|
|
560
|
+
# ---- 类型 ----
|
|
561
|
+
"MessageHandler", "AsyncMessageHandler",
|
|
562
|
+
"StreamCallback", "AsyncStreamCallback",
|
|
563
|
+
]
|