ErisPulse-MatrixAdapter 4.2.0__tar.gz → 4.4.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.
Files changed (17) hide show
  1. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0/ErisPulse_MatrixAdapter.egg-info}/PKG-INFO +107 -12
  2. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/ErisPulse_MatrixAdapter.egg-info/SOURCES.txt +5 -1
  3. erispulse_matrixadapter-4.4.0/ErisPulse_MatrixAdapter.egg-info/requires.txt +1 -0
  4. erispulse_matrixadapter-4.4.0/LICENSE +21 -0
  5. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/MatrixAdapter/Converter.py +52 -36
  6. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/MatrixAdapter/Core.py +389 -26
  7. erispulse_matrixadapter-4.4.0/MatrixAdapter/NioSession.py +278 -0
  8. erispulse_matrixadapter-4.2.0/README.md → erispulse_matrixadapter-4.4.0/PKG-INFO +113 -9
  9. erispulse_matrixadapter-4.2.0/ErisPulse_MatrixAdapter.egg-info/PKG-INFO → erispulse_matrixadapter-4.4.0/README.md +79 -18
  10. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/pyproject.toml +8 -4
  11. erispulse_matrixadapter-4.4.0/test/test_e2ee.py +418 -0
  12. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/ErisPulse_MatrixAdapter.egg-info/dependency_links.txt +0 -0
  13. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/ErisPulse_MatrixAdapter.egg-info/entry_points.txt +0 -0
  14. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/ErisPulse_MatrixAdapter.egg-info/top_level.txt +0 -0
  15. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/MatrixAdapter/__init__.py +0 -0
  16. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/setup.cfg +0 -0
  17. {erispulse_matrixadapter-4.2.0 → erispulse_matrixadapter-4.4.0}/test/test.py +0 -0
@@ -1,11 +1,36 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse-MatrixAdapter
3
- Version: 4.2.0
4
- Summary: ErisPulse的Matrix协议适配模块
3
+ Version: 4.4.0
4
+ Summary: ErisPulse Matrix protocol adapter with native end-to-end encryption support
5
5
  Author-email: wsu2059 <wsu2059@qq.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 ErisPulse
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
6
28
  Project-URL: homepage, https://github.com/ErisPulse/ErisPulse-MatrixAdapter
7
- Requires-Python: >=3.9
29
+ Requires-Python: >=3.10
8
30
  Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: matrix-nio[e2e]>=0.26.0
33
+ Dynamic: license-file
9
34
 
10
35
  <div align="center">
11
36
 
@@ -153,6 +178,41 @@ enabled = true
153
178
 
154
179
  ---
155
180
 
181
+ ### End-to-End Encryption (E2EE)
182
+
183
+ The adapter natively supports end-to-end encryption for encrypted rooms (based on [matrix-nio](https://github.com/matrix-nio/matrix-nio), powered by [vodozemac](https://pypi.org/project/vodozemac/)): messages and media in encrypted rooms are automatically decrypted on receive and encrypted on send — no changes required in your modules.
184
+
185
+ ```toml
186
+ [Matrix_Adapter.accounts.default]
187
+ homeserver = "https://matrix.org"
188
+ user_id = "@bot:matrix.org"
189
+ password = "YOUR_PASSWORD"
190
+ encryption_enabled = true # Enable E2EE (default false)
191
+ trust_all_devices = true # Auto-trust unverified devices (default true)
192
+ # device_id = "XXXXXXXXXX" # Optional: specify manually only if the server doesn't report one
193
+ # store_path = "data/matrix/default" # Optional: crypto session store directory
194
+ ```
195
+
196
+ **Encryption fields (per account):**
197
+ - `encryption_enabled`: Enable E2EE, defaults to `false` (existing plaintext deployments are unaffected)
198
+ - `trust_all_devices`: Automatically trust unverified devices, defaults to `true` (common bot practice). Set to `false` for strict mode — unverified devices will not receive room keys for new messages
199
+ - `device_id`: Device ID, auto-resolved in most cases (login response → `/whoami` → persisted). Manual specification is only needed on servers that don't report it
200
+ - `store_path`: Crypto session store directory, defaults to `data/matrix/<account_name>`
201
+
202
+ **How it works:**
203
+ - The `device_id` is resolved on login and persisted (in `store_path/device.json`); password re-login reuses the same device to avoid device pile-up
204
+ - Sync runs through the E2EE session: device key upload, one-time key replenishment, to-device messages and Megolm decryption are handled automatically
205
+ - Sending to an encrypted room automatically shares the Megolm session and encrypts the payload; media uploads in encrypted rooms are encrypted end-to-end (`m.file.encrypted`)
206
+ - Modules can call `event.is_encrypted()` to check whether a message came from an encrypted room; encrypted media segments carry `matrix_encrypted_file` (JWK), and ciphertext can be decrypted with `MatrixAdapter.decrypt_media(bytes, key, iv, sha256)`
207
+
208
+ **Important notes:**
209
+ 1. Requires Python >= 3.10 and `matrix-nio[e2e] >= 0.26` (installed automatically as a dependency)
210
+ 2. Cross-signing and server-side key backups are not supported; historical messages cannot be decrypted if the local store directory (`store_path`) is deleted — do not delete it
211
+ 3. Interactive SAS (emoji) verification is planned for a future release; the current trust model is "trust all devices" by default
212
+ 4. Events that fail to decryption are skipped with a warning logged
213
+
214
+ ---
215
+
156
216
  ### Matrix-Specific Features
157
217
 
158
218
  See the [Matrix platform features documentation](platform-features.md) for platform-specific capabilities, including the decentralized architecture, room concepts, Long Polling sync, MXC URIs, HTML rich text, reactions, message editing, and extension field descriptions.
@@ -221,6 +281,7 @@ async def handle_message(event):
221
281
  sender = event.get_matrix_sender() # Get the sender ID
222
282
  is_edited = event.is_edited() # Whether the message is an edit
223
283
  is_notice = event.is_notice() # Whether it is an m.notice type
284
+ is_encrypted = event.is_encrypted() # Whether it came from an E2EE room
224
285
  ```
225
286
 
226
287
  ### Notes
@@ -234,6 +295,7 @@ async def handle_message(event):
234
295
  7. Auto-accepting room invites is supported (can be disabled via the `auto_accept_invites` config).
235
296
  8. HTML-formatted messages are supported via the `.Html()` method.
236
297
  9. Message replies (`.Reply()`) and user mentions (`.At()`, `.AtAll()`) are supported.
298
+ 10. With end-to-end encryption enabled, do not delete the `store_path` directory (see the "End-to-End Encryption" section).
237
299
 
238
300
  ---
239
301
 
@@ -368,6 +430,41 @@ enabled = true
368
430
 
369
431
  ---
370
432
 
433
+ ## 端到端加密(E2EE)
434
+
435
+ 适配器原生支持加密房间的端到端加密(基于 [matrix-nio](https://github.com/matrix-nio/matrix-nio),底层使用 [vodozemac](https://pypi.org/project/vodozemac/)):加密房间的消息与媒体在接收时自动解密、发送时自动加密,模块无需任何改动。
436
+
437
+ ```toml
438
+ [Matrix_Adapter.accounts.default]
439
+ homeserver = "https://matrix.org"
440
+ user_id = "@bot:matrix.org"
441
+ password = "YOUR_PASSWORD"
442
+ encryption_enabled = true # 启用端到端加密(默认 false)
443
+ trust_all_devices = true # 自动信任未验证设备(默认 true)
444
+ # device_id = "XXXXXXXXXX" # 可选:仅在服务器不返回时手动指定
445
+ # store_path = "data/matrix/default" # 可选:加密会话存储目录
446
+ ```
447
+
448
+ **加密相关配置(每个账户):**
449
+ - `encryption_enabled`:启用端到端加密,默认 `false`(存量明文部署不受影响)
450
+ - `trust_all_devices`:自动信任未验证设备,默认 `true`(bot 场景惯例);设为 `false` 进入严格模式——未验证设备将无法解密新消息
451
+ - `device_id`:设备 ID,多数情况自动获取(登录响应 → `/whoami` → 本地持久化),仅服务器不返回时需手动指定
452
+ - `store_path`:加密会话存储目录,默认 `data/matrix/<账户名>`
453
+
454
+ **工作原理:**
455
+ - 登录时解析并持久化 `device_id`(`store_path/device.json`),密码重登会复用同一设备,避免设备堆积
456
+ - 同步走 E2EE 会话:设备密钥上传、一次性密钥补充、to-device 消息、Megolm 解密全部自动完成
457
+ - 向加密房间发送消息时自动分享 Megolm 会话并加密载荷;加密房间的媒体上传为端到端加密(`m.file.encrypted`)
458
+ - 模块可通过 `event.is_encrypted()` 判断消息是否来自加密房间;加密媒体段携带 `matrix_encrypted_file`(JWK),可配合 `MatrixAdapter.decrypt_media(bytes, key, iv, sha256)` 解密密文
459
+
460
+ **注意事项:**
461
+ 1. 需要 Python >= 3.10 与 `matrix-nio[e2e] >= 0.26`(已作为依赖自动安装)
462
+ 2. 不支持交叉签名与服务器端密钥备份;本地会话目录(`store_path`)删除后历史消息将无法解密,请勿删除
463
+ 3. 交互式 SAS(emoji)验证计划在后续版本提供,当前默认信任策略为"自动信任所有设备"
464
+ 4. 解密失败的事件会被跳过并记录警告日志
465
+
466
+ ---
467
+
371
468
  ## Matrix平台特有功能
372
469
 
373
470
  请参考 [Matrix平台特性文档](platform-features.md) 了解Matrix平台的特有功能,包括去中心化架构、房间概念、Long Polling同步、MXC URI、HTML富文本、表情回应、消息编辑、扩展字段说明等内容。
@@ -436,19 +533,17 @@ async def handle_message(event):
436
533
  sender = event.get_matrix_sender() # 获取发送者ID
437
534
  is_edited = event.is_edited() # 是否为编辑消息
438
535
  is_notice = event.is_notice() # 是否为 m.notice 类型
536
+ is_encrypted = event.is_encrypted() # 是否来自端到端加密房间
439
537
  ```
440
538
 
441
539
  ## 注意事项:
442
540
 
443
- 1. 确保在调用 `startup()` 前完成所有处理器的注册
444
- 2. Matrix 是去中心化协议,用户ID格式为 `@user:server.domain`,房间ID格式为 `!room_id:server.domain`
445
- 3. Matrix 不区分群聊和私聊,所有会话都是"房间"。适配器通过 DM 账户数据自动识别私聊
446
- 4. 适配器使用 Long Polling(`/sync` API)获取事件,而非 WebSocket
447
- 5. 媒体文件通过 `mxc://` URI 引用,适配器支持自动上传和下载
448
- 6. 程序退出时请调用 `shutdown()` 确保资源释放
449
- 7. 支持自动接受房间邀请(可通过 `auto_accept_invites` 配置关闭)
450
- 8. 支持发送 HTML 格式消息,使用 `.Html()` 方法
451
- 9. 支持消息回复(`.Reply()`)和用户提及(`.At()`、`.AtAll()`)
541
+ 1. Matrix 是去中心化协议,用户ID格式为 `@user:server.domain`,房间ID格式为 `!room_id:server.domain`
542
+ 2. Matrix 不区分群聊和私聊,所有会话都是"房间"。适配器通过 DM 账户数据自动识别私聊
543
+ 3. 适配器使用 Long Polling(`/sync` API)获取事件,而非 WebSocket
544
+ 4. 媒体文件通过 `mxc://` URI 引用,适配器支持自动上传和下载
545
+ 5. 支持自动接受房间邀请(可通过 `auto_accept_invites` 配置关闭)
546
+ 6. 启用端到端加密后,请勿删除 `store_path` 目录(详见「端到端加密」章节)
452
547
 
453
548
  ---
454
549
 
@@ -1,11 +1,15 @@
1
+ LICENSE
1
2
  README.md
2
3
  pyproject.toml
3
4
  ErisPulse_MatrixAdapter.egg-info/PKG-INFO
4
5
  ErisPulse_MatrixAdapter.egg-info/SOURCES.txt
5
6
  ErisPulse_MatrixAdapter.egg-info/dependency_links.txt
6
7
  ErisPulse_MatrixAdapter.egg-info/entry_points.txt
8
+ ErisPulse_MatrixAdapter.egg-info/requires.txt
7
9
  ErisPulse_MatrixAdapter.egg-info/top_level.txt
8
10
  MatrixAdapter/Converter.py
9
11
  MatrixAdapter/Core.py
12
+ MatrixAdapter/NioSession.py
10
13
  MatrixAdapter/__init__.py
11
- test/test.py
14
+ test/test.py
15
+ test/test_e2ee.py
@@ -0,0 +1 @@
1
+ matrix-nio[e2e]>=0.26.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ErisPulse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -186,51 +186,67 @@ class MatrixConverter(BaseConverter):
186
186
 
187
187
  elif msgtype == "m.image":
188
188
  url = content.get("url", "")
189
- segments.append({
190
- "type": "image",
191
- "data": {
192
- "url": url,
193
- "filename": body,
194
- "matrix_mxc": url,
195
- "info": content.get("info", {}),
196
- },
197
- })
189
+ enc_file = content.get("file") if not url else None
190
+ data = {
191
+ "filename": body,
192
+ "info": content.get("info", {}),
193
+ }
194
+ if enc_file:
195
+ data["url"] = enc_file.get("url", "")
196
+ data["matrix_encrypted_file"] = enc_file
197
+ data["encrypted"] = True
198
+ else:
199
+ data["url"] = url
200
+ data["matrix_mxc"] = url
201
+ segments.append({"type": "image", "data": data})
198
202
 
199
203
  elif msgtype == "m.audio":
200
204
  url = content.get("url", "")
201
- segments.append({
202
- "type": "voice",
203
- "data": {
204
- "url": url,
205
- "filename": body,
206
- "matrix_mxc": url,
207
- "info": content.get("info", {}),
208
- },
209
- })
205
+ enc_file = content.get("file") if not url else None
206
+ data = {
207
+ "filename": body,
208
+ "info": content.get("info", {}),
209
+ }
210
+ if enc_file:
211
+ data["url"] = enc_file.get("url", "")
212
+ data["matrix_encrypted_file"] = enc_file
213
+ data["encrypted"] = True
214
+ else:
215
+ data["url"] = url
216
+ data["matrix_mxc"] = url
217
+ segments.append({"type": "voice", "data": data})
210
218
 
211
219
  elif msgtype == "m.video":
212
220
  url = content.get("url", "")
213
- segments.append({
214
- "type": "video",
215
- "data": {
216
- "url": url,
217
- "filename": body,
218
- "matrix_mxc": url,
219
- "info": content.get("info", {}),
220
- },
221
- })
221
+ enc_file = content.get("file") if not url else None
222
+ data = {
223
+ "filename": body,
224
+ "info": content.get("info", {}),
225
+ }
226
+ if enc_file:
227
+ data["url"] = enc_file.get("url", "")
228
+ data["matrix_encrypted_file"] = enc_file
229
+ data["encrypted"] = True
230
+ else:
231
+ data["url"] = url
232
+ data["matrix_mxc"] = url
233
+ segments.append({"type": "video", "data": data})
222
234
 
223
235
  elif msgtype == "m.file":
224
236
  url = content.get("url", "")
225
- segments.append({
226
- "type": "file",
227
- "data": {
228
- "url": url,
229
- "filename": body,
230
- "matrix_mxc": url,
231
- "info": content.get("info", {}),
232
- },
233
- })
237
+ enc_file = content.get("file") if not url else None
238
+ data = {
239
+ "filename": body,
240
+ "info": content.get("info", {}),
241
+ }
242
+ if enc_file:
243
+ data["url"] = enc_file.get("url", "")
244
+ data["matrix_encrypted_file"] = enc_file
245
+ data["encrypted"] = True
246
+ else:
247
+ data["url"] = url
248
+ data["matrix_mxc"] = url
249
+ segments.append({"type": "file", "data": data})
234
250
 
235
251
  elif msgtype == "m.location":
236
252
  geo_uri = content.get("geo_uri", "")