ErisPulse-MatrixAdapter 4.0.2__tar.gz → 4.2.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.
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/ErisPulse_MatrixAdapter.egg-info/PKG-INFO +22 -3
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/MatrixAdapter/Converter.py +12 -14
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/MatrixAdapter/Core.py +385 -104
- erispulse_matrixadapter-4.2.0/MatrixAdapter/__init__.py +3 -0
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/PKG-INFO +22 -3
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/README.md +21 -2
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/pyproject.toml +1 -1
- erispulse_matrixadapter-4.0.2/MatrixAdapter/__init__.py +0 -1
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/ErisPulse_MatrixAdapter.egg-info/SOURCES.txt +0 -0
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/ErisPulse_MatrixAdapter.egg-info/dependency_links.txt +0 -0
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/ErisPulse_MatrixAdapter.egg-info/entry_points.txt +0 -0
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/ErisPulse_MatrixAdapter.egg-info/top_level.txt +0 -0
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/setup.cfg +0 -0
- {erispulse_matrixadapter-4.0.2 → erispulse_matrixadapter-4.2.0}/test/test.py +0 -0
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse-MatrixAdapter
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: ErisPulse的Matrix协议适配模块
|
|
5
5
|
Author-email: wsu2059 <wsu2059@qq.com>
|
|
6
6
|
Project-URL: homepage, https://github.com/ErisPulse/ErisPulse-MatrixAdapter
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
<div align="center">
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
<img src=".github/assets/ErisPulseLogo.png" width="180" alt="ErisPulse MatrixAdapter" />
|
|
13
|
+
|
|
14
|
+
# ErisPulse MatrixAdapter
|
|
15
|
+
|
|
16
|
+
**Matrix protocol adapter — decentralized chat via Long Polling sync.**
|
|
17
|
+
|
|
18
|
+
A Matrix protocol adapter built on the ErisPulse framework. It receives events via the Long Polling Sync API and unifies private chats, groups and rooms into OneBot12 standard events, with full support for mentions, replies, HTML rich text, reactions and MXC media.
|
|
19
|
+
|
|
20
|
+
<p>
|
|
21
|
+
<a href="https://pypi.org/project/ErisPulse-MatrixAdapter/"><img src="https://img.shields.io/pypi/v/ErisPulse-MatrixAdapter?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
22
|
+
<a href="https://pypi.org/project/ErisPulse-MatrixAdapter/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
|
|
23
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
|
|
24
|
+
<a href="https://github.com/ErisPulse/ErisPulse-MatrixAdapter"><img src="https://img.shields.io/github/stars/ErisPulse/ErisPulse-MatrixAdapter?style=for-the-badge&logo=github&color=brightgreen" alt="Stars"></a>
|
|
25
|
+
<a href="https://pepy.tech/project/ErisPulse-MatrixAdapter"><img src="https://img.shields.io/pepy/dt/ErisPulse-MatrixAdapter?style=for-the-badge&color=blue" alt="Downloads"></a>
|
|
26
|
+
<a href="https://github.com/ErisPulse/ErisPulse"><img src="https://img.shields.io/badge/Powered_by-ErisPulse-FF6B9D?style=for-the-badge&logo=bookstack&logoColor=white" alt="ErisPulse"></a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
[English](#english) | [简体中文](#中文)
|
|
30
|
+
|
|
31
|
+
</div>
|
|
13
32
|
|
|
14
33
|
---
|
|
15
34
|
|
|
@@ -2,9 +2,12 @@ import time
|
|
|
2
2
|
import uuid
|
|
3
3
|
from typing import Dict, Optional, List
|
|
4
4
|
|
|
5
|
+
from ErisPulse.Core.Bases import BaseConverter
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
class MatrixConverter(BaseConverter):
|
|
7
9
|
def __init__(self, bot_user_id: str = ""):
|
|
10
|
+
super().__init__(platform="matrix")
|
|
8
11
|
self.bot_user_id = bot_user_id
|
|
9
12
|
self._dm_rooms: Dict[str, str] = {}
|
|
10
13
|
|
|
@@ -31,19 +34,13 @@ class MatrixConverter:
|
|
|
31
34
|
if sender == self.bot_user_id:
|
|
32
35
|
return None
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"platform": "matrix",
|
|
42
|
-
"user_id": self.bot_user_id,
|
|
43
|
-
},
|
|
44
|
-
"matrix_raw": raw_event,
|
|
45
|
-
"matrix_raw_type": event_type,
|
|
46
|
-
}
|
|
37
|
+
# 基础事件结构(BaseConverter 骨架 + Matrix 语义覆盖)
|
|
38
|
+
base_event = self.build_base_event(raw_event, event_type)
|
|
39
|
+
base_event["id"] = str(event_id)
|
|
40
|
+
base_event["time"] = event_time
|
|
41
|
+
base_event["type"] = ""
|
|
42
|
+
base_event["detail_type"] = ""
|
|
43
|
+
base_event["self"]["user_id"] = self.bot_user_id
|
|
47
44
|
|
|
48
45
|
if is_dm:
|
|
49
46
|
base_event["detail_type"] = "private"
|
|
@@ -61,6 +58,7 @@ class MatrixConverter:
|
|
|
61
58
|
|
|
62
59
|
def _handle_m_room_message(self, raw_event: Dict, base_event: Dict, room_id: str, is_dm: bool) -> Dict:
|
|
63
60
|
base_event["type"] = "message"
|
|
61
|
+
base_event["message_id"] = str(raw_event.get("event_id", ""))
|
|
64
62
|
base_event["user_id"] = raw_event.get("sender", "")
|
|
65
63
|
base_event["user_nickname"] = raw_event.get("sender", "")
|
|
66
64
|
|
|
@@ -7,11 +7,22 @@ from typing import Dict, List, Optional
|
|
|
7
7
|
|
|
8
8
|
from ErisPulse.Core import client
|
|
9
9
|
from ErisPulse.Core.Bases.adapter import BaseAdapter
|
|
10
|
+
from ErisPulse.Core.Bases import BotAccountConfig
|
|
10
11
|
from ErisPulse.Core.Event import register_event_mixin, unregister_platform_event_methods
|
|
11
|
-
from ErisPulse.
|
|
12
|
+
from ErisPulse.Core.i18n import i18n
|
|
12
13
|
|
|
13
14
|
from .Converter import MatrixConverter
|
|
14
15
|
|
|
16
|
+
try:
|
|
17
|
+
from ErisPulse.runtime.tasks import spawn_background
|
|
18
|
+
except ImportError: # pragma: no cover
|
|
19
|
+
spawn_background = None
|
|
20
|
+
|
|
21
|
+
__version__ = "4.2.0"
|
|
22
|
+
|
|
23
|
+
# 软依赖的框架最低版本(运行时检测,仅提示不强制)
|
|
24
|
+
MIN_FRAMEWORK_VERSION = (2, 7, 1)
|
|
25
|
+
|
|
15
26
|
|
|
16
27
|
# ============================================================================
|
|
17
28
|
# 配置声明(多账户)
|
|
@@ -21,43 +32,57 @@ class MatrixAccountConfig(BotAccountConfig):
|
|
|
21
32
|
homeserver: str = field(
|
|
22
33
|
default="https://matrix.org",
|
|
23
34
|
metadata={
|
|
24
|
-
"description": "
|
|
35
|
+
"description": {"i18n": "matrix.homeserver", "default": "Matrix 服务器地址"},
|
|
25
36
|
"required": True,
|
|
26
|
-
"
|
|
37
|
+
"ui": {
|
|
38
|
+
"widget": "text",
|
|
39
|
+
"group": "connection",
|
|
40
|
+
"order": 1,
|
|
41
|
+
"placeholder": {"i18n": "matrix.homeserver.ph", "default": "https://matrix.org"},
|
|
42
|
+
},
|
|
27
43
|
},
|
|
28
44
|
)
|
|
29
45
|
access_token: str = field(
|
|
30
46
|
default="",
|
|
31
47
|
metadata={
|
|
32
|
-
"description": "
|
|
48
|
+
"description": {"i18n": "matrix.access_token", "default": "Access Token(替代 user_id/password)"},
|
|
33
49
|
"secret": True,
|
|
34
|
-
"
|
|
50
|
+
"ui": {"widget": "password", "group": "basic", "order": 2},
|
|
35
51
|
},
|
|
36
52
|
)
|
|
37
53
|
user_id: str = field(
|
|
38
54
|
default="",
|
|
39
55
|
metadata={
|
|
40
|
-
"description": "
|
|
41
|
-
"
|
|
56
|
+
"description": {"i18n": "matrix.user_id", "default": "用户名(用于密码登录)"},
|
|
57
|
+
"ui": {"widget": "text", "group": "basic", "order": 3},
|
|
42
58
|
},
|
|
43
59
|
)
|
|
44
60
|
password: str = field(
|
|
45
61
|
default="",
|
|
46
62
|
metadata={
|
|
47
|
-
"description": "
|
|
63
|
+
"description": {"i18n": "matrix.password", "default": "密码(用于密码登录)"},
|
|
48
64
|
"secret": True,
|
|
49
|
-
"
|
|
65
|
+
"ui": {"widget": "password", "group": "basic", "order": 4},
|
|
50
66
|
},
|
|
51
67
|
)
|
|
52
68
|
auto_accept_invites: bool = field(
|
|
53
69
|
default=True,
|
|
54
70
|
metadata={
|
|
55
|
-
"description": "
|
|
56
|
-
"
|
|
71
|
+
"description": {"i18n": "matrix.auto_accept_invites", "default": "自动接受房间邀请"},
|
|
72
|
+
"ui": {"widget": "switch", "group": "behavior", "order": 5},
|
|
57
73
|
},
|
|
58
74
|
)
|
|
59
75
|
|
|
60
76
|
|
|
77
|
+
MatrixAccountConfig._schema_meta = {
|
|
78
|
+
"group_labels": {
|
|
79
|
+
"basic": {"i18n": "matrix.group.basic", "default": "基本设置"},
|
|
80
|
+
"connection": {"i18n": "matrix.group.connection", "default": "连接设置"},
|
|
81
|
+
"behavior": {"i18n": "matrix.group.behavior", "default": "行为设置"},
|
|
82
|
+
}
|
|
83
|
+
} # type: ignore[attr-defined]
|
|
84
|
+
|
|
85
|
+
|
|
61
86
|
# ============================================================================
|
|
62
87
|
# 事件 Mixin(平台扩展方法)
|
|
63
88
|
# ============================================================================
|
|
@@ -316,10 +341,190 @@ class MatrixAdapter(BaseAdapter):
|
|
|
316
341
|
self._heartbeat_meta_tasks: Dict[str, asyncio.Task] = {}
|
|
317
342
|
self._converters: Dict[str, MatrixConverter] = {}
|
|
318
343
|
self._running = False
|
|
344
|
+
self._message_targets: Dict[str, str] = {}
|
|
345
|
+
self._register_i18n()
|
|
346
|
+
self._check_framework_version()
|
|
347
|
+
self._get_logger().info(f"MatrixAdapter v{__version__} 已加载")
|
|
348
|
+
|
|
349
|
+
@staticmethod
|
|
350
|
+
def _parse_version(version_str: str) -> tuple:
|
|
351
|
+
"""解析版本号为可比较的三元组(忽略 dev/预发布后缀,如 2.8.0-dev.3 → (2, 8, 0))"""
|
|
352
|
+
parts = []
|
|
353
|
+
for piece in str(version_str).split("."):
|
|
354
|
+
digits = "".join(ch for ch in piece if ch.isdigit())
|
|
355
|
+
parts.append(int(digits) if digits else 0)
|
|
356
|
+
while len(parts) < 3:
|
|
357
|
+
parts.append(0)
|
|
358
|
+
return tuple(parts[:3])
|
|
359
|
+
|
|
360
|
+
def _check_framework_version(self):
|
|
361
|
+
"""软依赖检测:框架版本过低时打警告(不阻断加载)"""
|
|
362
|
+
try:
|
|
363
|
+
from importlib.metadata import version as _pkg_version
|
|
364
|
+
|
|
365
|
+
raw = _pkg_version("ErisPulse")
|
|
366
|
+
except Exception:
|
|
367
|
+
return
|
|
368
|
+
try:
|
|
369
|
+
if self._parse_version(raw) < MIN_FRAMEWORK_VERSION:
|
|
370
|
+
self._get_logger().warning(
|
|
371
|
+
f"当前 ErisPulse 版本 {raw} 过低:MatrixAdapter v{__version__} 需要 >= "
|
|
372
|
+
f"{'.'.join(map(str, MIN_FRAMEWORK_VERSION))}"
|
|
373
|
+
"(BaseConverter / Api DSL / spawn_background 等特性),"
|
|
374
|
+
"部分功能可能不可用,建议升级框架"
|
|
375
|
+
)
|
|
376
|
+
except Exception:
|
|
377
|
+
pass
|
|
378
|
+
|
|
379
|
+
def _register_i18n(self):
|
|
380
|
+
"""注册配置字段与日志消息的 i18n 翻译"""
|
|
381
|
+
try:
|
|
382
|
+
from ErisPulse.runtime.config_schema import register_config_i18n
|
|
383
|
+
register_config_i18n(MatrixAccountConfig, "zh-CN", domain="matrix")
|
|
384
|
+
register_config_i18n(MatrixAccountConfig, "en", {
|
|
385
|
+
"matrix.homeserver": "Matrix server address",
|
|
386
|
+
"matrix.homeserver.ph": "https://matrix.org",
|
|
387
|
+
"matrix.access_token": "Access Token (alternative to user_id/password)",
|
|
388
|
+
"matrix.user_id": "Username (used for password login)",
|
|
389
|
+
"matrix.password": "Password (used for password login)",
|
|
390
|
+
"matrix.auto_accept_invites": "Automatically accept room invitations",
|
|
391
|
+
"matrix.group.basic": "Basic",
|
|
392
|
+
"matrix.group.connection": "Connection",
|
|
393
|
+
"matrix.group.behavior": "Behavior",
|
|
394
|
+
}, domain="matrix")
|
|
395
|
+
except Exception:
|
|
396
|
+
pass
|
|
397
|
+
|
|
398
|
+
zh_CN = {
|
|
399
|
+
"matrix.old_config_detected": "检测到旧格式单账户配置,建议迁移到 Matrix_Adapter.accounts.default",
|
|
400
|
+
"matrix.save_default_failed": "保存默认配置失败: {error}",
|
|
401
|
+
"matrix.missing_credentials": "账户 '{name}' 缺少 access_token/user_id,已跳过",
|
|
402
|
+
"matrix.account_disabled": "账户 '{name}' 已加载但未启用(enabled=false),请在配置中将 [Matrix_Adapter.accounts.{name}] 的 enabled 设为 true",
|
|
403
|
+
"matrix.accounts_loaded": "Matrix适配器初始化完成,共加载 {count} 个账户",
|
|
404
|
+
"matrix.authenticated": "账户 {name} Matrix 已认证: {bot_id}",
|
|
405
|
+
"matrix.login_success": "账户 {name} Matrix 登录成功: {bot_id}",
|
|
406
|
+
"matrix.login_failed": "登录失败: {message}",
|
|
407
|
+
"matrix.login_error": "账户 {name} Matrix 登录失败: {error}",
|
|
408
|
+
"matrix.initial_sync_failed_nologin": "账户 {name} 初始同步失败且无法重新登录,停止同步",
|
|
409
|
+
"matrix.initial_sync_still_failed": "账户 {name} 重新登录后初始同步仍失败,停止同步",
|
|
410
|
+
"matrix.auth_failed": "账户 {name} 认证失败 ({count}/{max}): {message}",
|
|
411
|
+
"matrix.relogin_success": "账户 {name} 重新登录成功,继续同步",
|
|
412
|
+
"matrix.sync_failed": "账户 {name} 同步失败 ({count}/{max}): {message}",
|
|
413
|
+
"matrix.max_failures_stop": "账户 {name} 连续 {max} 次失败,停止同步循环",
|
|
414
|
+
"matrix.sync_loop_error": "账户 {name} 同步循环异常: {error}",
|
|
415
|
+
"matrix.max_exceptions_stop": "账户 {name} 连续 {max} 次异常,停止同步循环",
|
|
416
|
+
"matrix.relogin_attempt": "账户 {name} 尝试重新登录...",
|
|
417
|
+
"matrix.relogin_done": "账户 {name} 重新登录成功",
|
|
418
|
+
"matrix.relogin_failed": "账户 {name} 重新登录失败: {error}",
|
|
419
|
+
"matrix.initial_sync_done": "账户 {name} 初始同步完成, next_batch: {batch}",
|
|
420
|
+
"matrix.initial_sync_failed": "账户 {name} 初始同步失败: {message}",
|
|
421
|
+
"matrix.dm_discovered": "账户 {name} 发现 {count} 个 DM 房间",
|
|
422
|
+
"matrix.process_event": "账户 {name} 处理 Matrix 事件: {event}",
|
|
423
|
+
"matrix.process_event_failed": "处理事件失败: {error}",
|
|
424
|
+
"matrix.room_invite": "账户 {name} 收到房间邀请: {room}",
|
|
425
|
+
"matrix.room_joined": "账户 {name} 已自动加入房间: {room}",
|
|
426
|
+
"matrix.join_room_failed": "加入房间失败: {error}",
|
|
427
|
+
"matrix.download_failed": "账户 {name} 下载文件失败: HTTP {status}",
|
|
428
|
+
"matrix.download_error": "账户 {name} 下载文件异常: {error}",
|
|
429
|
+
"matrix.account_not_found": "账户 {name} 不存在,无法上传媒体",
|
|
430
|
+
"matrix.upload_failed": "账户 {name} 上传媒体失败: {data}",
|
|
431
|
+
"matrix.upload_error": "账户 {name} 上传媒体异常: {error}",
|
|
432
|
+
"matrix.api_timeout": "账户 {name} Matrix API 请求超时: {endpoint}",
|
|
433
|
+
"matrix.timeout_msg": "请求超时",
|
|
434
|
+
"matrix.api_call_failed": "账户 {name} 调用 Matrix API 失败: {error}",
|
|
435
|
+
"matrix.api_error": "API调用失败: {error}",
|
|
436
|
+
"matrix.no_enabled_accounts": "没有已启用的账户,Matrix 适配器将以空闲状态启动",
|
|
437
|
+
"matrix.no_bot_id": "账户 {name} 无法获取 bot user_id,请检查配置",
|
|
438
|
+
"matrix.account_started": "账户 {name} (bot_id: {bot_id}) Matrix 已启动",
|
|
439
|
+
"matrix.adapter_started": "Matrix 适配器启动完成,共 {count} 个账户",
|
|
440
|
+
"matrix.adapter_shutdown": "Matrix 适配器已关闭",
|
|
441
|
+
}
|
|
442
|
+
en = {
|
|
443
|
+
"matrix.old_config_detected": "Old single-account config detected, please migrate to Matrix_Adapter.accounts.default",
|
|
444
|
+
"matrix.save_default_failed": "Failed to save default config: {error}",
|
|
445
|
+
"matrix.missing_credentials": "Account '{name}' missing access_token/user_id, skipped",
|
|
446
|
+
"matrix.account_disabled": "Account '{name}' loaded but disabled (enabled=false), please set enabled=true in [Matrix_Adapter.accounts.{name}]",
|
|
447
|
+
"matrix.accounts_loaded": "Matrix adapter initialized, loaded {count} account(s)",
|
|
448
|
+
"matrix.authenticated": "Account {name} Matrix authenticated: {bot_id}",
|
|
449
|
+
"matrix.login_success": "Account {name} Matrix login success: {bot_id}",
|
|
450
|
+
"matrix.login_failed": "Login failed: {message}",
|
|
451
|
+
"matrix.login_error": "Account {name} Matrix login failed: {error}",
|
|
452
|
+
"matrix.initial_sync_failed_nologin": "Account {name} initial sync failed and re-login not possible, stopping sync",
|
|
453
|
+
"matrix.initial_sync_still_failed": "Account {name} initial sync still failing after re-login, stopping sync",
|
|
454
|
+
"matrix.auth_failed": "Account {name} auth failed ({count}/{max}): {message}",
|
|
455
|
+
"matrix.relogin_success": "Account {name} re-login success, resuming sync",
|
|
456
|
+
"matrix.sync_failed": "Account {name} sync failed ({count}/{max}): {message}",
|
|
457
|
+
"matrix.max_failures_stop": "Account {name} failed {max} consecutive times, stopping sync loop",
|
|
458
|
+
"matrix.sync_loop_error": "Account {name} sync loop error: {error}",
|
|
459
|
+
"matrix.max_exceptions_stop": "Account {name} {max} consecutive exceptions, stopping sync loop",
|
|
460
|
+
"matrix.relogin_attempt": "Account {name} attempting re-login...",
|
|
461
|
+
"matrix.relogin_done": "Account {name} re-login success",
|
|
462
|
+
"matrix.relogin_failed": "Account {name} re-login failed: {error}",
|
|
463
|
+
"matrix.initial_sync_done": "Account {name} initial sync done, next_batch: {batch}",
|
|
464
|
+
"matrix.initial_sync_failed": "Account {name} initial sync failed: {message}",
|
|
465
|
+
"matrix.dm_discovered": "Account {name} discovered {count} DM room(s)",
|
|
466
|
+
"matrix.process_event": "Account {name} processing Matrix event: {event}",
|
|
467
|
+
"matrix.process_event_failed": "Failed to process event: {error}",
|
|
468
|
+
"matrix.room_invite": "Account {name} received room invite: {room}",
|
|
469
|
+
"matrix.room_joined": "Account {name} auto-joined room: {room}",
|
|
470
|
+
"matrix.join_room_failed": "Failed to join room: {error}",
|
|
471
|
+
"matrix.download_failed": "Account {name} download failed: HTTP {status}",
|
|
472
|
+
"matrix.download_error": "Account {name} download error: {error}",
|
|
473
|
+
"matrix.account_not_found": "Account {name} not found, cannot upload media",
|
|
474
|
+
"matrix.upload_failed": "Account {name} upload media failed: {data}",
|
|
475
|
+
"matrix.upload_error": "Account {name} upload media error: {error}",
|
|
476
|
+
"matrix.api_timeout": "Account {name} Matrix API request timeout: {endpoint}",
|
|
477
|
+
"matrix.timeout_msg": "Request timeout",
|
|
478
|
+
"matrix.api_call_failed": "Account {name} Matrix API call failed: {error}",
|
|
479
|
+
"matrix.api_error": "API call failed: {error}",
|
|
480
|
+
"matrix.no_enabled_accounts": "No enabled accounts, Matrix adapter will start idle",
|
|
481
|
+
"matrix.no_bot_id": "Account {name} could not obtain bot user_id, please check config",
|
|
482
|
+
"matrix.account_started": "Account {name} (bot_id: {bot_id}) Matrix started",
|
|
483
|
+
"matrix.adapter_started": "Matrix adapter started, {count} account(s) total",
|
|
484
|
+
"matrix.adapter_shutdown": "Matrix adapter shut down",
|
|
485
|
+
}
|
|
486
|
+
try:
|
|
487
|
+
i18n.register("zh-CN", zh_CN, domain="matrix")
|
|
488
|
+
i18n.register("en", en, domain="matrix")
|
|
489
|
+
except Exception:
|
|
490
|
+
pass
|
|
319
491
|
|
|
320
492
|
def _get_config_key(self) -> str:
|
|
321
493
|
return "Matrix_Adapter"
|
|
322
494
|
|
|
495
|
+
async def _delete_message_by_id(self, message_id: str, account_id: Optional[str] = None) -> dict:
|
|
496
|
+
"""按消息登记表撤回消息(Matrix redact 需要 room_id + event_id)"""
|
|
497
|
+
room_id = self._message_targets.get(message_id)
|
|
498
|
+
if not room_id:
|
|
499
|
+
return self.make_error(retcode=34001, message=f"未找到消息 {message_id} 的目标上下文,无法撤回")
|
|
500
|
+
import uuid as _uuid
|
|
501
|
+
|
|
502
|
+
txn_id = _uuid.uuid4().hex
|
|
503
|
+
return await self.call_api(
|
|
504
|
+
f"/_matrix/client/v3/rooms/{room_id}/redact/{message_id}/{txn_id}",
|
|
505
|
+
method="PUT", _account_id=account_id,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
def _migrate_legacy_config(self):
|
|
509
|
+
"""将旧版单账户扁平配置(access_token 直接在 [Matrix_Adapter] 下)迁移到 accounts 结构"""
|
|
510
|
+
try:
|
|
511
|
+
from ErisPulse.Core import config as config_mgr
|
|
512
|
+
|
|
513
|
+
key = self._get_config_key()
|
|
514
|
+
data = config_mgr.getConfig(key)
|
|
515
|
+
if not isinstance(data, dict):
|
|
516
|
+
return
|
|
517
|
+
accounts = data.get("accounts")
|
|
518
|
+
if isinstance(accounts, dict) and accounts:
|
|
519
|
+
return
|
|
520
|
+
if not data.get("access_token"):
|
|
521
|
+
return
|
|
522
|
+
data["accounts"] = {"default": {**data, "enabled": True}}
|
|
523
|
+
config_mgr.setConfig(key, data)
|
|
524
|
+
self.logger.info("已将旧版单账户配置迁移到 accounts.default")
|
|
525
|
+
except Exception as e:
|
|
526
|
+
self.logger.debug(f"旧配置迁移检查跳过: {e}")
|
|
527
|
+
|
|
323
528
|
def _get_runtime(self, name: str) -> dict:
|
|
324
529
|
if name not in self._account_runtime:
|
|
325
530
|
self._account_runtime[name] = {
|
|
@@ -330,52 +535,8 @@ class MatrixAdapter(BaseAdapter):
|
|
|
330
535
|
}
|
|
331
536
|
return self._account_runtime[name]
|
|
332
537
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
from ErisPulse.runtime.config_schema import dict_to_dataclass
|
|
336
|
-
|
|
337
|
-
key = "Matrix_Adapter.accounts"
|
|
338
|
-
data = config_mgr.getConfig(key)
|
|
339
|
-
if not data:
|
|
340
|
-
# 兼容旧配置:如果存在旧的单账户 Matrix_Adapter 配置且有 access_token,迁移为 default 账户
|
|
341
|
-
old = config_mgr.getConfig("Matrix_Adapter")
|
|
342
|
-
if old and old.get("access_token"):
|
|
343
|
-
self.logger.warning(
|
|
344
|
-
"检测到旧格式单账户配置,建议迁移到 Matrix_Adapter.accounts.default"
|
|
345
|
-
)
|
|
346
|
-
data = {"default": {**old, "enabled": True}}
|
|
347
|
-
else:
|
|
348
|
-
data = {
|
|
349
|
-
"default": {
|
|
350
|
-
"homeserver": "https://matrix.org",
|
|
351
|
-
"access_token": "",
|
|
352
|
-
"user_id": "",
|
|
353
|
-
"password": "",
|
|
354
|
-
"auto_accept_invites": True,
|
|
355
|
-
"enabled": True,
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
try:
|
|
359
|
-
config_mgr.setConfig(key, data)
|
|
360
|
-
except Exception as e:
|
|
361
|
-
self.logger.error(f"保存默认配置失败: {e}")
|
|
362
|
-
accounts = {}
|
|
363
|
-
for name, account_data in data.items():
|
|
364
|
-
if not isinstance(account_data, dict):
|
|
365
|
-
continue
|
|
366
|
-
if not account_data.get("access_token") and not account_data.get("user_id"):
|
|
367
|
-
self.logger.warning(f"账户 '{name}' 缺少 access_token/user_id,已跳过")
|
|
368
|
-
continue
|
|
369
|
-
instance = dict_to_dataclass(MatrixAccountConfig, account_data)
|
|
370
|
-
instance.name = name
|
|
371
|
-
if not instance.enabled:
|
|
372
|
-
self.logger.warning(
|
|
373
|
-
f"账户 '{name}' 已加载但未启用(enabled=false),"
|
|
374
|
-
f"请在配置中将 [Matrix_Adapter.accounts.{name}] 的 enabled 设为 true"
|
|
375
|
-
)
|
|
376
|
-
accounts[name] = instance
|
|
377
|
-
self.logger.info(f"Matrix适配器初始化完成,共加载 {len(accounts)} 个账户")
|
|
378
|
-
return accounts
|
|
538
|
+
# 账户加载使用框架默认实现(AccountConfigClass 模板 + 校验);
|
|
539
|
+
# 旧版单账户扁平配置迁移见 _migrate_legacy_config;缺少凭据的账户由 _login_if_needed 失败自然跳过。
|
|
379
540
|
|
|
380
541
|
async def _login_if_needed(self, account_name: str, account: MatrixAccountConfig):
|
|
381
542
|
runtime = self._get_runtime(account_name)
|
|
@@ -392,10 +553,9 @@ class MatrixAdapter(BaseAdapter):
|
|
|
392
553
|
bot_id = result["data"].get("user_id", "")
|
|
393
554
|
runtime["bot_id"] = bot_id
|
|
394
555
|
runtime["access_token"] = account.access_token
|
|
395
|
-
account.bot_id = bot_id
|
|
396
556
|
if converter:
|
|
397
557
|
converter.bot_user_id = bot_id
|
|
398
|
-
self.logger.info(
|
|
558
|
+
self.logger.info(i18n.t("matrix.authenticated", name=account_name, bot_id=bot_id, default="账户 {name} Matrix 已认证: {bot_id}"))
|
|
399
559
|
return
|
|
400
560
|
except Exception:
|
|
401
561
|
pass
|
|
@@ -416,20 +576,18 @@ class MatrixAdapter(BaseAdapter):
|
|
|
416
576
|
if result.get("status") == "ok" and result.get("data"):
|
|
417
577
|
token = result["data"].get("access_token", "")
|
|
418
578
|
bot_id = result["data"].get("user_id", account.user_id)
|
|
419
|
-
# 密码登录获得的 token
|
|
420
|
-
account.access_token = token
|
|
579
|
+
# 密码登录获得的 token 写入运行时状态,供后续 call_api 使用
|
|
421
580
|
runtime["access_token"] = token
|
|
422
581
|
runtime["bot_id"] = bot_id
|
|
423
|
-
account.bot_id = bot_id
|
|
424
582
|
if converter:
|
|
425
583
|
converter.bot_user_id = bot_id
|
|
426
|
-
self.logger.info(
|
|
584
|
+
self.logger.info(i18n.t("matrix.login_success", name=account_name, bot_id=bot_id, default="账户 {name} Matrix 登录成功: {bot_id}"))
|
|
427
585
|
else:
|
|
428
586
|
raise Exception(
|
|
429
|
-
|
|
587
|
+
i18n.t("matrix.login_failed", message=result.get('message', 'Unknown error'), default="登录失败: {message}")
|
|
430
588
|
)
|
|
431
589
|
except Exception as e:
|
|
432
|
-
self.logger.error(
|
|
590
|
+
self.logger.error(i18n.t("matrix.login_error", name=account_name, error=e, default="账户 {name} Matrix 登录失败: {error}"))
|
|
433
591
|
raise
|
|
434
592
|
|
|
435
593
|
async def _sync_loop(self, account_name: str):
|
|
@@ -440,13 +598,13 @@ class MatrixAdapter(BaseAdapter):
|
|
|
440
598
|
if not await self._initial_sync(account_name):
|
|
441
599
|
if not await self._try_relogin(account_name, account):
|
|
442
600
|
self.logger.error(
|
|
443
|
-
|
|
601
|
+
i18n.t("matrix.initial_sync_failed_nologin", name=account_name, default="账户 {name} 初始同步失败且无法重新登录,停止同步")
|
|
444
602
|
)
|
|
445
603
|
await self._on_sync_loop_exit(account_name)
|
|
446
604
|
return
|
|
447
605
|
if not await self._initial_sync(account_name):
|
|
448
606
|
self.logger.error(
|
|
449
|
-
|
|
607
|
+
i18n.t("matrix.initial_sync_still_failed", name=account_name, default="账户 {name} 重新登录后初始同步仍失败,停止同步")
|
|
450
608
|
)
|
|
451
609
|
await self._on_sync_loop_exit(account_name)
|
|
452
610
|
return
|
|
@@ -476,23 +634,23 @@ class MatrixAdapter(BaseAdapter):
|
|
|
476
634
|
if is_auth_error:
|
|
477
635
|
consecutive_failures += 1
|
|
478
636
|
self.logger.warning(
|
|
479
|
-
|
|
637
|
+
i18n.t("matrix.auth_failed", name=account_name, count=consecutive_failures, max=max_failures, message=message, default="账户 {name} 认证失败 ({count}/{max}): {message}")
|
|
480
638
|
)
|
|
481
639
|
if await self._try_relogin(account_name, account):
|
|
482
640
|
self.logger.info(
|
|
483
|
-
|
|
641
|
+
i18n.t("matrix.relogin_success", name=account_name, default="账户 {name} 重新登录成功,继续同步")
|
|
484
642
|
)
|
|
485
643
|
consecutive_failures = 0
|
|
486
644
|
continue
|
|
487
645
|
else:
|
|
488
646
|
consecutive_failures += 1
|
|
489
647
|
self.logger.error(
|
|
490
|
-
|
|
648
|
+
i18n.t("matrix.sync_failed", name=account_name, count=consecutive_failures, max=max_failures, message=message, default="账户 {name} 同步失败 ({count}/{max}): {message}")
|
|
491
649
|
)
|
|
492
650
|
|
|
493
651
|
if consecutive_failures >= max_failures:
|
|
494
652
|
self.logger.error(
|
|
495
|
-
|
|
653
|
+
i18n.t("matrix.max_failures_stop", name=account_name, max=max_failures, default="账户 {name} 连续 {max} 次失败,停止同步循环")
|
|
496
654
|
)
|
|
497
655
|
break
|
|
498
656
|
|
|
@@ -513,11 +671,11 @@ class MatrixAdapter(BaseAdapter):
|
|
|
513
671
|
except asyncio.CancelledError:
|
|
514
672
|
break
|
|
515
673
|
except Exception as e:
|
|
516
|
-
self.logger.error(
|
|
674
|
+
self.logger.error(i18n.t("matrix.sync_loop_error", name=account_name, error=e, default="账户 {name} 同步循环异常: {error}"))
|
|
517
675
|
consecutive_failures += 1
|
|
518
676
|
if consecutive_failures >= max_failures:
|
|
519
677
|
self.logger.error(
|
|
520
|
-
|
|
678
|
+
i18n.t("matrix.max_exceptions_stop", name=account_name, max=max_failures, default="账户 {name} 连续 {max} 次异常,停止同步循环")
|
|
521
679
|
)
|
|
522
680
|
break
|
|
523
681
|
await asyncio.sleep(min(5 * consecutive_failures, 30))
|
|
@@ -529,7 +687,7 @@ class MatrixAdapter(BaseAdapter):
|
|
|
529
687
|
if not account or not account.user_id or not account.password:
|
|
530
688
|
return False
|
|
531
689
|
try:
|
|
532
|
-
self.logger.info(
|
|
690
|
+
self.logger.info(i18n.t("matrix.relogin_attempt", name=account_name, default="账户 {name} 尝试重新登录..."))
|
|
533
691
|
# 清空旧 token,强制走密码登录路径
|
|
534
692
|
old_token = account.access_token
|
|
535
693
|
account.access_token = ""
|
|
@@ -539,10 +697,10 @@ class MatrixAdapter(BaseAdapter):
|
|
|
539
697
|
# 恢复旧 token,避免状态污染
|
|
540
698
|
account.access_token = old_token
|
|
541
699
|
raise
|
|
542
|
-
self.logger.info(
|
|
700
|
+
self.logger.info(i18n.t("matrix.relogin_done", name=account_name, default="账户 {name} 重新登录成功"))
|
|
543
701
|
return True
|
|
544
702
|
except Exception as e:
|
|
545
|
-
self.logger.error(
|
|
703
|
+
self.logger.error(i18n.t("matrix.relogin_failed", name=account_name, error=e, default="账户 {name} 重新登录失败: {error}"))
|
|
546
704
|
return False
|
|
547
705
|
|
|
548
706
|
async def _on_sync_loop_exit(self, account_name: str):
|
|
@@ -572,12 +730,12 @@ class MatrixAdapter(BaseAdapter):
|
|
|
572
730
|
data = result["data"]
|
|
573
731
|
runtime["_next_batch"] = data.get("next_batch", "")
|
|
574
732
|
self.logger.info(
|
|
575
|
-
|
|
733
|
+
i18n.t("matrix.initial_sync_done", name=account_name, batch=runtime.get('_next_batch'), default="账户 {name} 初始同步完成, next_batch: {batch}")
|
|
576
734
|
)
|
|
577
735
|
return True
|
|
578
736
|
else:
|
|
579
737
|
self.logger.warning(
|
|
580
|
-
|
|
738
|
+
i18n.t("matrix.initial_sync_failed", name=account_name, message=result.get('message', 'Unknown error'), default="账户 {name} 初始同步失败: {message}")
|
|
581
739
|
)
|
|
582
740
|
return False
|
|
583
741
|
|
|
@@ -599,7 +757,7 @@ class MatrixAdapter(BaseAdapter):
|
|
|
599
757
|
runtime["_dm_rooms"] = dm_rooms
|
|
600
758
|
if converter:
|
|
601
759
|
converter.set_dm_rooms(dm_rooms)
|
|
602
|
-
self.logger.info(
|
|
760
|
+
self.logger.info(i18n.t("matrix.dm_discovered", name=account_name, count=len(dm_rooms), default="账户 {name} 发现 {count} 个 DM 房间"))
|
|
603
761
|
|
|
604
762
|
async def _process_sync_response(self, account_name: str, data: dict):
|
|
605
763
|
runtime = self._get_runtime(account_name)
|
|
@@ -614,7 +772,7 @@ class MatrixAdapter(BaseAdapter):
|
|
|
614
772
|
events = timeline.get("events", [])
|
|
615
773
|
|
|
616
774
|
for event in events:
|
|
617
|
-
self.logger.debug(
|
|
775
|
+
self.logger.debug(i18n.t("matrix.process_event", name=account_name, event=event, default="账户 {name} 处理 Matrix 事件: {event}"))
|
|
618
776
|
try:
|
|
619
777
|
if converter:
|
|
620
778
|
onebot_event = converter.convert(
|
|
@@ -623,9 +781,13 @@ class MatrixAdapter(BaseAdapter):
|
|
|
623
781
|
else:
|
|
624
782
|
onebot_event = None
|
|
625
783
|
if onebot_event:
|
|
784
|
+
if onebot_event.get("type") == "message" and onebot_event.get("message_id"):
|
|
785
|
+
self._message_targets[str(onebot_event["message_id"])] = str(room_id)
|
|
786
|
+
while len(self._message_targets) > 800:
|
|
787
|
+
self._message_targets.pop(next(iter(self._message_targets)), None)
|
|
626
788
|
await self.sdk.adapter.emit(onebot_event)
|
|
627
789
|
except Exception as e:
|
|
628
|
-
self.logger.error(
|
|
790
|
+
self.logger.error(i18n.t("matrix.process_event_failed", error=e, default="处理事件失败: {error}"))
|
|
629
791
|
|
|
630
792
|
invite_rooms = data.get("rooms", {}).get("invite", {})
|
|
631
793
|
for room_id, room_data in invite_rooms.items():
|
|
@@ -637,7 +799,7 @@ class MatrixAdapter(BaseAdapter):
|
|
|
637
799
|
content.get("membership") == "invite"
|
|
638
800
|
and event.get("state_key") == bot_id
|
|
639
801
|
):
|
|
640
|
-
self.logger.info(
|
|
802
|
+
self.logger.info(i18n.t("matrix.room_invite", name=account_name, room=room_id, default="账户 {name} 收到房间邀请: {room}"))
|
|
641
803
|
if getattr(
|
|
642
804
|
self.accounts.get(account_name), "auto_accept_invites", True
|
|
643
805
|
):
|
|
@@ -648,17 +810,17 @@ class MatrixAdapter(BaseAdapter):
|
|
|
648
810
|
_account_id=account_name,
|
|
649
811
|
)
|
|
650
812
|
self.logger.info(
|
|
651
|
-
|
|
813
|
+
i18n.t("matrix.room_joined", name=account_name, room=room_id, default="账户 {name} 已自动加入房间: {room}")
|
|
652
814
|
)
|
|
653
815
|
except Exception as e:
|
|
654
|
-
self.logger.error(
|
|
816
|
+
self.logger.error(i18n.t("matrix.join_room_failed", error=e, default="加入房间失败: {error}"))
|
|
655
817
|
|
|
656
818
|
async def _download_file(self, account_name: str, url: str) -> Optional[tuple]:
|
|
657
819
|
try:
|
|
658
820
|
resp = await client.get(url, timeout=300)
|
|
659
821
|
if resp.status != 200:
|
|
660
822
|
self.logger.error(
|
|
661
|
-
|
|
823
|
+
i18n.t("matrix.download_failed", name=account_name, status=resp.status, default="账户 {name} 下载文件失败: HTTP {status}")
|
|
662
824
|
)
|
|
663
825
|
return None
|
|
664
826
|
content_type = (
|
|
@@ -667,7 +829,7 @@ class MatrixAdapter(BaseAdapter):
|
|
|
667
829
|
data = await resp.read()
|
|
668
830
|
return (data, content_type)
|
|
669
831
|
except Exception as e:
|
|
670
|
-
self.logger.error(
|
|
832
|
+
self.logger.error(i18n.t("matrix.download_error", name=account_name, error=e, default="账户 {name} 下载文件异常: {error}"))
|
|
671
833
|
return None
|
|
672
834
|
|
|
673
835
|
async def _upload_media(
|
|
@@ -679,7 +841,7 @@ class MatrixAdapter(BaseAdapter):
|
|
|
679
841
|
) -> Optional[str]:
|
|
680
842
|
account = self.accounts.get(account_name)
|
|
681
843
|
if not account:
|
|
682
|
-
self.logger.error(
|
|
844
|
+
self.logger.error(i18n.t("matrix.account_not_found", name=account_name, default="账户 {name} 不存在,无法上传媒体"))
|
|
683
845
|
return None
|
|
684
846
|
if not content_type:
|
|
685
847
|
content_type_map = {
|
|
@@ -692,8 +854,11 @@ class MatrixAdapter(BaseAdapter):
|
|
|
692
854
|
|
|
693
855
|
homeserver = (account.homeserver or "https://matrix.org").rstrip("/")
|
|
694
856
|
url = f"{homeserver}/_matrix/media/v3/upload"
|
|
857
|
+
# 优先使用运行时 token(同 call_api)
|
|
858
|
+
runtime = self._get_runtime(account_name)
|
|
859
|
+
access_token = runtime.get("access_token") or account.access_token
|
|
695
860
|
headers = {
|
|
696
|
-
"Authorization": f"Bearer {
|
|
861
|
+
"Authorization": f"Bearer {access_token}",
|
|
697
862
|
"Content-Type": content_type,
|
|
698
863
|
}
|
|
699
864
|
|
|
@@ -703,10 +868,10 @@ class MatrixAdapter(BaseAdapter):
|
|
|
703
868
|
if resp.status == 200:
|
|
704
869
|
return data.get("content_uri", "")
|
|
705
870
|
else:
|
|
706
|
-
self.logger.error(
|
|
871
|
+
self.logger.error(i18n.t("matrix.upload_failed", name=account_name, data=data, default="账户 {name} 上传媒体失败: {data}"))
|
|
707
872
|
return None
|
|
708
873
|
except Exception as e:
|
|
709
|
-
self.logger.error(
|
|
874
|
+
self.logger.error(i18n.t("matrix.upload_error", name=account_name, error=e, default="账户 {name} 上传媒体异常: {error}"))
|
|
710
875
|
return None
|
|
711
876
|
|
|
712
877
|
async def call_api(
|
|
@@ -715,8 +880,11 @@ class MatrixAdapter(BaseAdapter):
|
|
|
715
880
|
account_name, account = self._resolve_account(_account_id)
|
|
716
881
|
homeserver = (account.homeserver or "https://matrix.org").rstrip("/")
|
|
717
882
|
url = f"{homeserver}{endpoint}"
|
|
883
|
+
# 优先使用运行时 token(密码登录后获得),避免 self.accounts 每次重新读取配置导致 token 丢失
|
|
884
|
+
runtime = self._get_runtime(account_name)
|
|
885
|
+
access_token = runtime.get("access_token") or account.access_token
|
|
718
886
|
headers = {
|
|
719
|
-
"Authorization": f"Bearer {
|
|
887
|
+
"Authorization": f"Bearer {access_token}",
|
|
720
888
|
"Content-Type": "application/json",
|
|
721
889
|
}
|
|
722
890
|
echo = params.pop("echo", None)
|
|
@@ -773,15 +941,15 @@ class MatrixAdapter(BaseAdapter):
|
|
|
773
941
|
return response
|
|
774
942
|
|
|
775
943
|
except asyncio.TimeoutError:
|
|
776
|
-
self.logger.error(
|
|
777
|
-
err = self.make_error(retcode=32000, message="请求超时", raw=None)
|
|
944
|
+
self.logger.error(i18n.t("matrix.api_timeout", name=account_name, endpoint=endpoint, default="账户 {name} Matrix API 请求超时: {endpoint}"))
|
|
945
|
+
err = self.make_error(retcode=32000, message=i18n.t("matrix.timeout_msg", default="请求超时"), raw=None)
|
|
778
946
|
if echo:
|
|
779
947
|
err["echo"] = echo
|
|
780
948
|
return err
|
|
781
949
|
except Exception as e:
|
|
782
|
-
self.logger.error(
|
|
950
|
+
self.logger.error(i18n.t("matrix.api_call_failed", name=account_name, error=e, default="账户 {name} 调用 Matrix API 失败: {error}"))
|
|
783
951
|
err = self.make_error(
|
|
784
|
-
retcode=33000, message=
|
|
952
|
+
retcode=33000, message=i18n.t("matrix.api_error", error=str(e), default="API调用失败: {error}"), raw=None
|
|
785
953
|
)
|
|
786
954
|
if echo:
|
|
787
955
|
err["echo"] = echo
|
|
@@ -796,11 +964,121 @@ class MatrixAdapter(BaseAdapter):
|
|
|
796
964
|
except asyncio.CancelledError:
|
|
797
965
|
pass
|
|
798
966
|
|
|
967
|
+
# ==================== Api DSL ====================
|
|
968
|
+
|
|
969
|
+
class Api(BaseAdapter.Api):
|
|
970
|
+
"""Matrix 标准 API 动作实现(ApiDSL,基于 Matrix Client-Server API)
|
|
971
|
+
|
|
972
|
+
{!--< tips >!--}
|
|
973
|
+
1. get_self_info → GET /account/whoami
|
|
974
|
+
2. get_user_info → GET /profile/{userId}
|
|
975
|
+
3. get_group_info → GET /rooms/{roomId}/state/m.room.name
|
|
976
|
+
4. get_group_list → GET /joined_rooms;get_group_member_list → GET /rooms/{roomId}/members
|
|
977
|
+
5. delete_message → PUT /rooms/{roomId}/redact/{eventId}/{txn}(登记表补全 roomId)
|
|
978
|
+
6. leave_group → POST /rooms/{roomId}/leave
|
|
979
|
+
{!--< /tips >!--}
|
|
980
|
+
"""
|
|
981
|
+
|
|
982
|
+
async def get_self_info(self) -> dict:
|
|
983
|
+
r = await self._adapter.call_api(
|
|
984
|
+
"/_matrix/client/v3/account/whoami", method="GET",
|
|
985
|
+
_account_id=self._account_id,
|
|
986
|
+
)
|
|
987
|
+
if r.get("status") != "ok":
|
|
988
|
+
return r
|
|
989
|
+
u = r.get("data") or {}
|
|
990
|
+
r["data"] = {"user_id": str(u.get("user_id", "")), "user_name": str(u.get("user_id", "")).split(":")[0].lstrip("@")}
|
|
991
|
+
return r
|
|
992
|
+
|
|
993
|
+
async def get_user_info(self, user_id: str) -> dict:
|
|
994
|
+
r = await self._adapter.call_api(
|
|
995
|
+
f"/_matrix/client/v3/profile/{user_id}", method="GET",
|
|
996
|
+
_account_id=self._account_id,
|
|
997
|
+
)
|
|
998
|
+
if r.get("status") != "ok":
|
|
999
|
+
return r
|
|
1000
|
+
u = r.get("data") or {}
|
|
1001
|
+
r["data"] = {
|
|
1002
|
+
"user_id": str(user_id),
|
|
1003
|
+
"user_name": u.get("displayname", ""),
|
|
1004
|
+
"user_avatar": u.get("avatar_url", ""),
|
|
1005
|
+
}
|
|
1006
|
+
return r
|
|
1007
|
+
|
|
1008
|
+
async def get_group_info(self, room_id: str) -> dict:
|
|
1009
|
+
r = await self._adapter.call_api(
|
|
1010
|
+
f"/_matrix/client/v3/rooms/{room_id}/state/m.room.name", method="GET",
|
|
1011
|
+
_account_id=self._account_id,
|
|
1012
|
+
)
|
|
1013
|
+
if r.get("status") != "ok":
|
|
1014
|
+
return r
|
|
1015
|
+
r["data"] = {"group_id": str(room_id), "group_name": (r.get("data") or {}).get("name", "")}
|
|
1016
|
+
return r
|
|
1017
|
+
|
|
1018
|
+
async def get_group_list(self) -> dict:
|
|
1019
|
+
r = await self._adapter.call_api(
|
|
1020
|
+
"/_matrix/client/v3/joined_rooms", method="GET", _account_id=self._account_id
|
|
1021
|
+
)
|
|
1022
|
+
if r.get("status") != "ok":
|
|
1023
|
+
return r
|
|
1024
|
+
rooms = (r.get("data") or {}).get("joined_rooms", []) or []
|
|
1025
|
+
r["data"] = [{"group_id": str(x)} for x in rooms]
|
|
1026
|
+
return r
|
|
1027
|
+
|
|
1028
|
+
async def get_group_member_list(self, room_id: str) -> dict:
|
|
1029
|
+
r = await self._adapter.call_api(
|
|
1030
|
+
f"/_matrix/client/v3/rooms/{room_id}/members",
|
|
1031
|
+
method="GET", _account_id=self._account_id,
|
|
1032
|
+
)
|
|
1033
|
+
if r.get("status") != "ok":
|
|
1034
|
+
return r
|
|
1035
|
+
chunk = (r.get("data") or {}).get("chunk", []) or []
|
|
1036
|
+
members = []
|
|
1037
|
+
for m in chunk:
|
|
1038
|
+
if isinstance(m, dict) and m.get("sender"):
|
|
1039
|
+
members.append({"user_id": str(m["sender"])})
|
|
1040
|
+
r["data"] = members
|
|
1041
|
+
return r
|
|
1042
|
+
|
|
1043
|
+
async def leave_group(self, room_id: str) -> dict:
|
|
1044
|
+
return await self._adapter.call_api(
|
|
1045
|
+
f"/_matrix/client/v3/rooms/{room_id}/leave", method="POST",
|
|
1046
|
+
_account_id=self._account_id,
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
async def delete_message(self, message_id: str) -> dict:
|
|
1050
|
+
return await self._adapter._delete_message_by_id(str(message_id), account_id=self._account_id)
|
|
1051
|
+
|
|
1052
|
+
async def get_status(self) -> dict:
|
|
1053
|
+
ad = self._adapter
|
|
1054
|
+
bots = []
|
|
1055
|
+
for name, runtime in ad._account_runtime.items():
|
|
1056
|
+
bots.append({
|
|
1057
|
+
"self": {"platform": ad.platform, "user_id": runtime.get("bot_id", ""), "account_id": name},
|
|
1058
|
+
"online": bool(runtime.get("bot_id")),
|
|
1059
|
+
})
|
|
1060
|
+
return ad.make_response(data={"good": any(b["online"] for b in bots), "bots": bots})
|
|
1061
|
+
|
|
1062
|
+
async def get_version(self) -> dict:
|
|
1063
|
+
from . import __version__
|
|
1064
|
+
|
|
1065
|
+
return self._adapter.make_response(
|
|
1066
|
+
data={"impl": "ErisPulse-MatrixAdapter", "version": __version__, "onebot_version": "12"}
|
|
1067
|
+
)
|
|
1068
|
+
|
|
1069
|
+
async def get_supported_actions(self) -> dict:
|
|
1070
|
+
actions = {
|
|
1071
|
+
"get_self_info", "get_user_info", "get_group_info", "get_group_list",
|
|
1072
|
+
"get_group_member_list", "leave_group", "delete_message",
|
|
1073
|
+
"get_status", "get_version", "get_supported_actions",
|
|
1074
|
+
}
|
|
1075
|
+
return self._adapter.make_response(data=sorted(actions))
|
|
1076
|
+
|
|
799
1077
|
async def start(self):
|
|
800
1078
|
self._running = True
|
|
801
1079
|
|
|
802
1080
|
if not self.enabled_accounts:
|
|
803
|
-
self.logger.warning("没有已启用的账户,Matrix 适配器将以空闲状态启动")
|
|
1081
|
+
self.logger.warning(i18n.t("matrix.no_enabled_accounts", default="没有已启用的账户,Matrix 适配器将以空闲状态启动"))
|
|
804
1082
|
return
|
|
805
1083
|
|
|
806
1084
|
for account_name, account in self.enabled_accounts.items():
|
|
@@ -819,23 +1097,26 @@ class MatrixAdapter(BaseAdapter):
|
|
|
819
1097
|
runtime = self._get_runtime(account_name)
|
|
820
1098
|
if not runtime.get("bot_id"):
|
|
821
1099
|
self.logger.error(
|
|
822
|
-
|
|
1100
|
+
i18n.t("matrix.no_bot_id", name=account_name, default="账户 {name} 无法获取 bot user_id,请检查配置")
|
|
823
1101
|
)
|
|
824
1102
|
continue
|
|
825
1103
|
|
|
826
1104
|
await self.emit_meta("connect", runtime.get("bot_id", ""))
|
|
827
|
-
|
|
828
|
-
|
|
1105
|
+
hb_coro = self._heartbeat_meta_loop(account_name)
|
|
1106
|
+
sync_coro = self._sync_loop(account_name)
|
|
1107
|
+
# 生命周期任务使用 spawn_background(owner 归属,shutdown 自动回收)
|
|
1108
|
+
self._heartbeat_meta_tasks[account_name] = (
|
|
1109
|
+
spawn_background(hb_coro) if spawn_background is not None else asyncio.create_task(hb_coro)
|
|
829
1110
|
)
|
|
830
|
-
self._sync_tasks[account_name] =
|
|
831
|
-
|
|
1111
|
+
self._sync_tasks[account_name] = (
|
|
1112
|
+
spawn_background(sync_coro) if spawn_background is not None else asyncio.create_task(sync_coro)
|
|
832
1113
|
)
|
|
833
1114
|
self.logger.info(
|
|
834
|
-
|
|
1115
|
+
i18n.t("matrix.account_started", name=account_name, bot_id=runtime.get('bot_id'), default="账户 {name} (bot_id: {bot_id}) Matrix 已启动")
|
|
835
1116
|
)
|
|
836
1117
|
|
|
837
1118
|
self.logger.info(
|
|
838
|
-
|
|
1119
|
+
i18n.t("matrix.adapter_started", count=len(self.enabled_accounts), default="Matrix 适配器启动完成,共 {count} 个账户")
|
|
839
1120
|
)
|
|
840
1121
|
|
|
841
1122
|
async def shutdown(self):
|
|
@@ -870,4 +1151,4 @@ class MatrixAdapter(BaseAdapter):
|
|
|
870
1151
|
unregister_platform_event_methods("matrix")
|
|
871
1152
|
except Exception:
|
|
872
1153
|
pass
|
|
873
|
-
self.logger.info("Matrix 适配器已关闭")
|
|
1154
|
+
self.logger.info(i18n.t("matrix.adapter_shutdown", default="Matrix 适配器已关闭"))
|
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse-MatrixAdapter
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: ErisPulse的Matrix协议适配模块
|
|
5
5
|
Author-email: wsu2059 <wsu2059@qq.com>
|
|
6
6
|
Project-URL: homepage, https://github.com/ErisPulse/ErisPulse-MatrixAdapter
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
<div align="center">
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
<img src=".github/assets/ErisPulseLogo.png" width="180" alt="ErisPulse MatrixAdapter" />
|
|
13
|
+
|
|
14
|
+
# ErisPulse MatrixAdapter
|
|
15
|
+
|
|
16
|
+
**Matrix protocol adapter — decentralized chat via Long Polling sync.**
|
|
17
|
+
|
|
18
|
+
A Matrix protocol adapter built on the ErisPulse framework. It receives events via the Long Polling Sync API and unifies private chats, groups and rooms into OneBot12 standard events, with full support for mentions, replies, HTML rich text, reactions and MXC media.
|
|
19
|
+
|
|
20
|
+
<p>
|
|
21
|
+
<a href="https://pypi.org/project/ErisPulse-MatrixAdapter/"><img src="https://img.shields.io/pypi/v/ErisPulse-MatrixAdapter?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
22
|
+
<a href="https://pypi.org/project/ErisPulse-MatrixAdapter/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
|
|
23
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
|
|
24
|
+
<a href="https://github.com/ErisPulse/ErisPulse-MatrixAdapter"><img src="https://img.shields.io/github/stars/ErisPulse/ErisPulse-MatrixAdapter?style=for-the-badge&logo=github&color=brightgreen" alt="Stars"></a>
|
|
25
|
+
<a href="https://pepy.tech/project/ErisPulse-MatrixAdapter"><img src="https://img.shields.io/pepy/dt/ErisPulse-MatrixAdapter?style=for-the-badge&color=blue" alt="Downloads"></a>
|
|
26
|
+
<a href="https://github.com/ErisPulse/ErisPulse"><img src="https://img.shields.io/badge/Powered_by-ErisPulse-FF6B9D?style=for-the-badge&logo=bookstack&logoColor=white" alt="ErisPulse"></a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
[English](#english) | [简体中文](#中文)
|
|
30
|
+
|
|
31
|
+
</div>
|
|
13
32
|
|
|
14
33
|
---
|
|
15
34
|
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src=".github/assets/ErisPulseLogo.png" width="180" alt="ErisPulse MatrixAdapter" />
|
|
4
|
+
|
|
5
|
+
# ErisPulse MatrixAdapter
|
|
6
|
+
|
|
7
|
+
**Matrix protocol adapter — decentralized chat via Long Polling sync.**
|
|
8
|
+
|
|
9
|
+
A Matrix protocol adapter built on the ErisPulse framework. It receives events via the Long Polling Sync API and unifies private chats, groups and rooms into OneBot12 standard events, with full support for mentions, replies, HTML rich text, reactions and MXC media.
|
|
10
|
+
|
|
11
|
+
<p>
|
|
12
|
+
<a href="https://pypi.org/project/ErisPulse-MatrixAdapter/"><img src="https://img.shields.io/pypi/v/ErisPulse-MatrixAdapter?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
13
|
+
<a href="https://pypi.org/project/ErisPulse-MatrixAdapter/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
|
|
14
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
|
|
15
|
+
<a href="https://github.com/ErisPulse/ErisPulse-MatrixAdapter"><img src="https://img.shields.io/github/stars/ErisPulse/ErisPulse-MatrixAdapter?style=for-the-badge&logo=github&color=brightgreen" alt="Stars"></a>
|
|
16
|
+
<a href="https://pepy.tech/project/ErisPulse-MatrixAdapter"><img src="https://img.shields.io/pepy/dt/ErisPulse-MatrixAdapter?style=for-the-badge&color=blue" alt="Downloads"></a>
|
|
17
|
+
<a href="https://github.com/ErisPulse/ErisPulse"><img src="https://img.shields.io/badge/Powered_by-ErisPulse-FF6B9D?style=for-the-badge&logo=bookstack&logoColor=white" alt="ErisPulse"></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
[English](#english) | [简体中文](#中文)
|
|
21
|
+
|
|
22
|
+
</div>
|
|
4
23
|
|
|
5
24
|
---
|
|
6
25
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .Core import MatrixAdapter
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|