ErisPulse-EmailAdapter 4.2.0__tar.gz → 4.3.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.
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import email
3
+ import html as _html
3
4
  import imaplib
4
5
  import smtplib
5
6
  import ssl
@@ -25,12 +26,79 @@ try:
25
26
  except ImportError: # pragma: no cover
26
27
  spawn_background = None
27
28
 
28
- __version__ = "4.2.0"
29
+ __version__ = "4.3.0"
29
30
 
30
31
  # 软依赖的框架最低版本(运行时检测,仅提示不强制)
31
32
  MIN_FRAMEWORK_VERSION = (2, 7, 1)
32
33
 
33
34
 
35
+ def _forward_node_text(node: Dict) -> str:
36
+ """提取合并转发节点 content 中全部 text 段文本(段间直接连接)"""
37
+ data = node.get("data") or {}
38
+ return "".join(
39
+ str(seg.get("data", {}).get("text") or "")
40
+ for seg in (data.get("content") or [])
41
+ if isinstance(seg, dict) and seg.get("type") == "text"
42
+ )
43
+
44
+
45
+ def _forward_nodes_plain(nodes, separator: str = "\n") -> str:
46
+ """合并转发节点降级纯文本:每条「昵称: 正文」"""
47
+ lines = []
48
+ for node in nodes if isinstance(nodes, list) else [nodes]:
49
+ if not isinstance(node, dict):
50
+ continue
51
+ data = node.get("data") or {}
52
+ nickname = str(data.get("nickname") or "").strip()
53
+ text = _forward_node_text(node).strip()
54
+ if nickname and text:
55
+ lines.append(f"{nickname}: {text}")
56
+ elif text:
57
+ lines.append(text)
58
+ return separator.join(lines)
59
+
60
+
61
+ def _build_forward_html(nodes, title: str = "合并聊天记录") -> str:
62
+ """渲染合并转发折叠 HTML 卡片(<details> 折叠 + CSS 系统色,自动适配明暗主题)"""
63
+ bubbles = []
64
+ for node in nodes if isinstance(nodes, list) else [nodes]:
65
+ if not isinstance(node, dict):
66
+ continue
67
+ data = node.get("data") or {}
68
+ nickname = str(data.get("nickname") or "")
69
+ text = _forward_node_text(node).strip()
70
+ if not text:
71
+ continue
72
+ body = _html.escape(text).replace("\n", "<br>")
73
+ bubbles.append(
74
+ '<div style="margin:8px 0;padding:10px 12px;border:1px solid GrayText;border-radius:8px;'
75
+ 'background:Canvas;color:CanvasText;">'
76
+ '<div style="font-size:12px;line-height:1.4;margin-bottom:6px;color:GrayText;">'
77
+ f"<strong>{_html.escape(nickname)}</strong></div>"
78
+ '<div style="font-size:14px;line-height:1.6;word-break:break-word;color:CanvasText;">' + body + "</div></div>"
79
+ )
80
+ if not bubbles:
81
+ return ""
82
+ return (
83
+ '<div style="color:CanvasText;color-scheme:light dark;">'
84
+ '<details style="width:100%;box-sizing:border-box;border:1px solid GrayText;border-radius:10px;'
85
+ 'background:Canvas;color:CanvasText;overflow:hidden;">'
86
+ '<summary style="list-style:none;cursor:pointer;padding:10px 12px;background:Canvas;'
87
+ 'border-bottom:1px solid GrayText;font-size:14px;font-weight:600;color:CanvasText;">'
88
+ f"{_html.escape(title)}</summary>"
89
+ '<div style="padding:8px 10px;">' + "".join(bubbles) + "</div></details></div>"
90
+ )
91
+
92
+
93
+ def _wrap_email_html(body: str) -> str:
94
+ """包装为完整 HTML 文档(声明 charset,避免邮件客户端乱码)"""
95
+ return (
96
+ '<!DOCTYPE html><html><head><meta charset="utf-8">'
97
+ '<meta name="viewport" content="width=device-width, initial-scale=1">'
98
+ "</head><body>" + body + "</body></html>"
99
+ )
100
+
101
+
34
102
  @dataclass
35
103
  class EmailGlobalConfig(BaseConfig):
36
104
  """Email 适配器全局配置(适用于所有账户的默认值)"""
@@ -255,6 +323,32 @@ class EmailAdapter(BaseAdapter):
255
323
  self._html = html
256
324
  return asyncio.create_task(self._send())
257
325
 
326
+ def MergeForward(self, nodes, title: str = "合并聊天记录"):
327
+ """发送合并转发邮件(正文为折叠 HTML 卡片)
328
+
329
+ :param nodes: go-cqhttp 扩展的 OneBot v11 合并转发节点数组(或单节点 dict)
330
+ :param title: 折叠卡标题(未设置主题时同时作为邮件主题)
331
+ :return: asyncio.Task
332
+ """
333
+ if isinstance(nodes, dict):
334
+ nodes = [nodes]
335
+ return asyncio.create_task(
336
+ self._do_send_merge_forward(
337
+ [n for n in nodes if isinstance(n, dict)], title
338
+ )
339
+ )
340
+
341
+ async def _do_send_merge_forward(self, nodes, title: str = "合并聊天记录"):
342
+ html = _build_forward_html(nodes, title)
343
+ if not html:
344
+ return self._adapter.make_error(
345
+ retcode=10006, message="MergeForward: 节点无有效文本内容"
346
+ )
347
+ if not self._subject:
348
+ self._subject = title
349
+ self._html = _wrap_email_html(html)
350
+ return await self._send()
351
+
258
352
  def Text(self, text: str):
259
353
  """设置纯文本内容并发送邮件"""
260
354
  self._text = text
@@ -297,6 +391,7 @@ class EmailAdapter(BaseAdapter):
297
391
  "video",
298
392
  "file",
299
393
  "audio",
394
+ "node",
300
395
  ]:
301
396
  segments = [message]
302
397
  elif isinstance(message, list):
@@ -306,6 +401,12 @@ class EmailAdapter(BaseAdapter):
306
401
  "Invalid message format, expected OneBot12 message segment or array"
307
402
  )
308
403
 
404
+ # node 段解析:纯 node 列表自动路由合并转发邮件;混合列表降级为纯文本
405
+ if segments and all(
406
+ isinstance(seg, dict) and seg.get("type") == "node" for seg in segments
407
+ ):
408
+ return await self._do_send_merge_forward(segments)
409
+
309
410
  # 解析消息段
310
411
  for segment in segments:
311
412
  seg_type = segment.get("type")
@@ -313,6 +414,8 @@ class EmailAdapter(BaseAdapter):
313
414
 
314
415
  if seg_type == "text":
315
416
  self._text += data.get("text", "")
417
+ elif seg_type == "node":
418
+ self._text += _forward_nodes_plain(segment)
316
419
  elif seg_type == "image":
317
420
  self._add_attachment_from_segment(data, "image")
318
421
  elif seg_type == "video":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse-EmailAdapter
3
- Version: 4.2.0
3
+ Version: 4.3.0
4
4
  Summary: ErisPulse邮箱适配器,支持多账号管理、HTML内容和附件发送
5
5
  Author-email: wsu2059q <wsu2059@qq.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ErisPulse-EmailAdapter
3
- Version: 4.2.0
3
+ Version: 4.3.0
4
4
  Summary: ErisPulse邮箱适配器,支持多账号管理、HTML内容和附件发送
5
5
  Author-email: wsu2059q <wsu2059@qq.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ErisPulse-EmailAdapter"
3
- version = "4.2.0"
3
+ version = "4.3.0"
4
4
  description = "ErisPulse邮箱适配器,支持多账号管理、HTML内容和附件发送"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"