ErisPulse-EmailAdapter 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_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/EmailAdapter/Core.py +367 -112
- erispulse_emailadapter-4.2.0/EmailAdapter/__init__.py +3 -0
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0/ErisPulse_EmailAdapter.egg-info}/PKG-INFO +22 -3
- {erispulse_emailadapter-4.0.2/ErisPulse_EmailAdapter.egg-info → erispulse_emailadapter-4.2.0}/PKG-INFO +22 -3
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/README.md +21 -2
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/pyproject.toml +1 -1
- erispulse_emailadapter-4.0.2/EmailAdapter/__init__.py +0 -1
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/ErisPulse_EmailAdapter.egg-info/SOURCES.txt +0 -0
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/ErisPulse_EmailAdapter.egg-info/dependency_links.txt +0 -0
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/ErisPulse_EmailAdapter.egg-info/entry_points.txt +0 -0
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/ErisPulse_EmailAdapter.egg-info/top_level.txt +0 -0
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/LICENSE +0 -0
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/setup.cfg +0 -0
- {erispulse_emailadapter-4.0.2 → erispulse_emailadapter-4.2.0}/tests/test.py +0 -0
|
@@ -15,76 +15,94 @@ from typing import BinaryIO, Dict, List, Optional, Union
|
|
|
15
15
|
from ErisPulse.Core import adapter as adapter_mgr
|
|
16
16
|
from ErisPulse.Core import client
|
|
17
17
|
from ErisPulse.Core.Bases.adapter import BaseAdapter
|
|
18
|
+
from ErisPulse.Core.Bases import BaseConfig, BotAccountConfig
|
|
19
|
+
from ErisPulse.Core.Bases.config_schema import dict_to_dataclass
|
|
18
20
|
from ErisPulse.Core.config import config as config_mgr
|
|
19
|
-
from ErisPulse.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
from ErisPulse.Core.i18n import i18n
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from ErisPulse.runtime.tasks import spawn_background
|
|
25
|
+
except ImportError: # pragma: no cover
|
|
26
|
+
spawn_background = None
|
|
27
|
+
|
|
28
|
+
__version__ = "4.2.0"
|
|
29
|
+
|
|
30
|
+
# 软依赖的框架最低版本(运行时检测,仅提示不强制)
|
|
31
|
+
MIN_FRAMEWORK_VERSION = (2, 7, 1)
|
|
24
32
|
|
|
25
33
|
|
|
26
34
|
@dataclass
|
|
27
|
-
class EmailGlobalConfig(
|
|
35
|
+
class EmailGlobalConfig(BaseConfig):
|
|
28
36
|
"""Email 适配器全局配置(适用于所有账户的默认值)"""
|
|
29
37
|
|
|
30
38
|
imap_server: str = field(
|
|
31
39
|
default="imap.example.com",
|
|
32
40
|
metadata={
|
|
33
|
-
"description": "
|
|
34
|
-
"
|
|
41
|
+
"description": {"i18n": "email.imap_server", "default": "默认 IMAP 服务器地址"},
|
|
42
|
+
"ui": {"widget": "text", "group": "imap", "order": 1},
|
|
35
43
|
},
|
|
36
44
|
)
|
|
37
45
|
imap_port: int = field(
|
|
38
46
|
default=993,
|
|
39
47
|
metadata={
|
|
40
|
-
"description": "
|
|
41
|
-
"
|
|
48
|
+
"description": {"i18n": "email.imap_port", "default": "默认 IMAP 端口"},
|
|
49
|
+
"ui": {"widget": "number", "group": "imap", "order": 2},
|
|
42
50
|
},
|
|
43
51
|
)
|
|
44
52
|
smtp_server: str = field(
|
|
45
53
|
default="smtp.example.com",
|
|
46
54
|
metadata={
|
|
47
|
-
"description": "
|
|
48
|
-
"
|
|
55
|
+
"description": {"i18n": "email.smtp_server", "default": "默认 SMTP 服务器地址"},
|
|
56
|
+
"ui": {"widget": "text", "group": "smtp", "order": 3},
|
|
49
57
|
},
|
|
50
58
|
)
|
|
51
59
|
smtp_port: int = field(
|
|
52
60
|
default=465,
|
|
53
61
|
metadata={
|
|
54
|
-
"description": "
|
|
55
|
-
"
|
|
62
|
+
"description": {"i18n": "email.smtp_port", "default": "默认 SMTP 端口"},
|
|
63
|
+
"ui": {"widget": "number", "group": "smtp", "order": 4},
|
|
56
64
|
},
|
|
57
65
|
)
|
|
58
66
|
ssl: bool = field(
|
|
59
67
|
default=True,
|
|
60
68
|
metadata={
|
|
61
|
-
"description": "
|
|
62
|
-
"
|
|
69
|
+
"description": {"i18n": "email.ssl", "default": "是否默认启用 SSL"},
|
|
70
|
+
"ui": {"widget": "switch", "group": "basic", "order": 5},
|
|
63
71
|
},
|
|
64
72
|
)
|
|
65
73
|
timeout: int = field(
|
|
66
74
|
default=30,
|
|
67
75
|
metadata={
|
|
68
|
-
"description": "
|
|
69
|
-
"
|
|
76
|
+
"description": {"i18n": "email.timeout", "default": "默认连接超时(秒)"},
|
|
77
|
+
"ui": {"widget": "number", "group": "basic", "order": 6},
|
|
70
78
|
},
|
|
71
79
|
)
|
|
72
80
|
poll_interval: int = field(
|
|
73
81
|
default=60,
|
|
74
82
|
metadata={
|
|
75
|
-
"description": "
|
|
76
|
-
"
|
|
83
|
+
"description": {"i18n": "email.poll_interval", "default": "IMAP 轮询间隔(秒)"},
|
|
84
|
+
"ui": {"widget": "number", "group": "polling", "order": 7},
|
|
77
85
|
},
|
|
78
86
|
)
|
|
79
87
|
max_retries: int = field(
|
|
80
88
|
default=3,
|
|
81
89
|
metadata={
|
|
82
|
-
"description": "
|
|
83
|
-
"
|
|
90
|
+
"description": {"i18n": "email.max_retries", "default": "连接失败最大重试次数"},
|
|
91
|
+
"ui": {"widget": "number", "group": "polling", "order": 8},
|
|
84
92
|
},
|
|
85
93
|
)
|
|
86
94
|
|
|
87
95
|
|
|
96
|
+
EmailGlobalConfig._schema_meta = {
|
|
97
|
+
"group_labels": {
|
|
98
|
+
"basic": {"i18n": "email.group.basic", "default": "基本设置"},
|
|
99
|
+
"imap": {"i18n": "email.group.imap", "default": "IMAP 设置"},
|
|
100
|
+
"smtp": {"i18n": "email.group.smtp", "default": "SMTP 设置"},
|
|
101
|
+
"polling": {"i18n": "email.group.polling", "default": "轮询设置"},
|
|
102
|
+
}
|
|
103
|
+
} # type: ignore[attr-defined]
|
|
104
|
+
|
|
105
|
+
|
|
88
106
|
@dataclass
|
|
89
107
|
class EmailAccountConfig(BotAccountConfig):
|
|
90
108
|
"""Email 单个账户配置"""
|
|
@@ -92,72 +110,123 @@ class EmailAccountConfig(BotAccountConfig):
|
|
|
92
110
|
email: str = field(
|
|
93
111
|
default="",
|
|
94
112
|
metadata={
|
|
95
|
-
"description": "
|
|
113
|
+
"description": {"i18n": "email.account.email", "default": "邮箱地址"},
|
|
96
114
|
"required": True,
|
|
97
115
|
"secret": True,
|
|
98
|
-
"
|
|
116
|
+
"ui": {"widget": "text", "group": "basic", "order": 1},
|
|
99
117
|
},
|
|
100
118
|
)
|
|
101
119
|
password: str = field(
|
|
102
120
|
default="",
|
|
103
121
|
metadata={
|
|
104
|
-
"description": "
|
|
122
|
+
"description": {"i18n": "email.account.password", "default": "邮箱密码 / 授权码"},
|
|
105
123
|
"required": True,
|
|
106
124
|
"secret": True,
|
|
107
|
-
"
|
|
125
|
+
"ui": {"widget": "password", "group": "basic", "order": 2},
|
|
108
126
|
},
|
|
109
127
|
)
|
|
110
128
|
imap_server: Optional[str] = field(
|
|
111
129
|
default=None,
|
|
112
130
|
metadata={
|
|
113
|
-
"description": "
|
|
114
|
-
"
|
|
131
|
+
"description": {"i18n": "email.account.imap_server", "default": "IMAP 服务器地址(留空使用全局默认)"},
|
|
132
|
+
"ui": {"widget": "text", "group": "imap", "order": 3},
|
|
115
133
|
},
|
|
116
134
|
)
|
|
117
135
|
imap_port: Optional[int] = field(
|
|
118
136
|
default=None,
|
|
119
137
|
metadata={
|
|
120
|
-
"description": "
|
|
121
|
-
"
|
|
138
|
+
"description": {"i18n": "email.account.imap_port", "default": "IMAP 端口(留空使用全局默认)"},
|
|
139
|
+
"ui": {"widget": "number", "group": "imap", "order": 4},
|
|
122
140
|
},
|
|
123
141
|
)
|
|
124
142
|
smtp_server: Optional[str] = field(
|
|
125
143
|
default=None,
|
|
126
144
|
metadata={
|
|
127
|
-
"description": "
|
|
128
|
-
"
|
|
145
|
+
"description": {"i18n": "email.account.smtp_server", "default": "SMTP 服务器地址(留空使用全局默认)"},
|
|
146
|
+
"ui": {"widget": "text", "group": "smtp", "order": 5},
|
|
129
147
|
},
|
|
130
148
|
)
|
|
131
149
|
smtp_port: Optional[int] = field(
|
|
132
150
|
default=None,
|
|
133
151
|
metadata={
|
|
134
|
-
"description": "
|
|
135
|
-
"
|
|
152
|
+
"description": {"i18n": "email.account.smtp_port", "default": "SMTP 端口(留空使用全局默认)"},
|
|
153
|
+
"ui": {"widget": "number", "group": "smtp", "order": 6},
|
|
136
154
|
},
|
|
137
155
|
)
|
|
138
156
|
ssl: bool = field(
|
|
139
157
|
default=True,
|
|
140
158
|
metadata={
|
|
141
|
-
"description": "
|
|
142
|
-
"
|
|
159
|
+
"description": {"i18n": "email.account.ssl", "default": "启用 SSL"},
|
|
160
|
+
"ui": {"widget": "switch", "group": "basic", "order": 7},
|
|
143
161
|
},
|
|
144
162
|
)
|
|
145
163
|
timeout: int = field(
|
|
146
164
|
default=30,
|
|
147
165
|
metadata={
|
|
148
|
-
"description": "
|
|
149
|
-
"
|
|
166
|
+
"description": {"i18n": "email.account.timeout", "default": "连接超时(秒)"},
|
|
167
|
+
"ui": {"widget": "number", "group": "basic", "order": 8},
|
|
150
168
|
},
|
|
151
169
|
)
|
|
152
170
|
|
|
153
171
|
|
|
172
|
+
EmailAccountConfig._schema_meta = {
|
|
173
|
+
"group_labels": {
|
|
174
|
+
"basic": {"i18n": "email.group.basic", "default": "基本设置"},
|
|
175
|
+
"imap": {"i18n": "email.group.imap", "default": "IMAP 设置"},
|
|
176
|
+
"smtp": {"i18n": "email.group.smtp", "default": "SMTP 设置"},
|
|
177
|
+
}
|
|
178
|
+
} # type: ignore[attr-defined]
|
|
179
|
+
|
|
180
|
+
|
|
154
181
|
class EmailAdapter(BaseAdapter):
|
|
155
|
-
"""Email
|
|
182
|
+
"""Email 邮件适配器(多账户管理,基于 stdlib smtplib/imaplib 实现)"""
|
|
156
183
|
|
|
157
184
|
_platform = "email"
|
|
158
185
|
AccountConfigClass = EmailAccountConfig
|
|
159
186
|
ConfigClass = EmailGlobalConfig
|
|
160
187
|
|
|
188
|
+
# ==================== Api DSL(最小集) ====================
|
|
189
|
+
|
|
190
|
+
class Api(BaseAdapter.Api):
|
|
191
|
+
"""Email 标准 API 动作实现(最小集)
|
|
192
|
+
|
|
193
|
+
{!--< tips >!--}
|
|
194
|
+
1. get_self_info → 返回账户邮箱地址
|
|
195
|
+
2. Email 平台无群组/频道/撤回等概念,相关标准动作由默认实现返回 retcode=10002
|
|
196
|
+
{!--< /tips >!--}
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
async def get_self_info(self) -> dict:
|
|
200
|
+
account_name, account = self._adapter._resolve_account(self._account_id)
|
|
201
|
+
return self._adapter.make_response(
|
|
202
|
+
data={
|
|
203
|
+
"user_id": account.email,
|
|
204
|
+
"user_name": account.email.split("@")[0],
|
|
205
|
+
"user_displayname": account.email,
|
|
206
|
+
}
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
async def get_status(self) -> dict:
|
|
210
|
+
ad = self._adapter
|
|
211
|
+
bots = []
|
|
212
|
+
for name, account in ad.accounts.items():
|
|
213
|
+
bots.append({
|
|
214
|
+
"self": {"platform": ad.platform, "user_id": account.email, "account_id": name},
|
|
215
|
+
"online": ad._is_running and name in ad.poll_tasks,
|
|
216
|
+
})
|
|
217
|
+
return ad.make_response(data={"good": any(b["online"] for b in bots), "bots": bots})
|
|
218
|
+
|
|
219
|
+
async def get_version(self) -> dict:
|
|
220
|
+
from . import __version__
|
|
221
|
+
|
|
222
|
+
return self._adapter.make_response(
|
|
223
|
+
data={"impl": "ErisPulse-EmailAdapter", "version": __version__, "onebot_version": "12"}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
async def get_supported_actions(self) -> dict:
|
|
227
|
+
actions = {"get_self_info", "get_status", "get_version", "get_supported_actions"}
|
|
228
|
+
return self._adapter.make_response(data=sorted(actions))
|
|
229
|
+
|
|
161
230
|
class Send(BaseAdapter.Send):
|
|
162
231
|
"""邮件发送 DSL
|
|
163
232
|
|
|
@@ -434,7 +503,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
434
503
|
response["email_raw"] = raw_response
|
|
435
504
|
return response
|
|
436
505
|
except Exception as e:
|
|
437
|
-
adapter.logger.error(
|
|
506
|
+
adapter.logger.error(i18n.t("email.send_failed", error=e, default="发送邮件失败: {error}"))
|
|
438
507
|
err = adapter.make_error(retcode=34000, message=str(e), raw=None)
|
|
439
508
|
err["email_raw"] = None
|
|
440
509
|
return err
|
|
@@ -448,6 +517,127 @@ class EmailAdapter(BaseAdapter):
|
|
|
448
517
|
self.poll_tasks: Dict[str, asyncio.Task] = {}
|
|
449
518
|
# 运行状态
|
|
450
519
|
self._is_running = False
|
|
520
|
+
self._register_i18n()
|
|
521
|
+
self._check_framework_version()
|
|
522
|
+
self._get_logger().info(f"EmailAdapter v{__version__} 已加载")
|
|
523
|
+
|
|
524
|
+
@staticmethod
|
|
525
|
+
def _parse_version(version_str: str) -> tuple:
|
|
526
|
+
"""解析版本号为可比较的三元组(忽略 dev/预发布后缀,如 2.8.0-dev.3 → (2, 8, 0))"""
|
|
527
|
+
parts = []
|
|
528
|
+
for piece in str(version_str).split("."):
|
|
529
|
+
digits = "".join(ch for ch in piece if ch.isdigit())
|
|
530
|
+
parts.append(int(digits) if digits else 0)
|
|
531
|
+
while len(parts) < 3:
|
|
532
|
+
parts.append(0)
|
|
533
|
+
return tuple(parts[:3])
|
|
534
|
+
|
|
535
|
+
def _check_framework_version(self):
|
|
536
|
+
"""软依赖检测:框架版本过低时打警告(不阻断加载)"""
|
|
537
|
+
try:
|
|
538
|
+
from importlib.metadata import version as _pkg_version
|
|
539
|
+
|
|
540
|
+
raw = _pkg_version("ErisPulse")
|
|
541
|
+
except Exception:
|
|
542
|
+
return
|
|
543
|
+
try:
|
|
544
|
+
if self._parse_version(raw) < MIN_FRAMEWORK_VERSION:
|
|
545
|
+
self._get_logger().warning(
|
|
546
|
+
f"当前 ErisPulse 版本 {raw} 过低:EmailAdapter v{__version__} 需要 >= "
|
|
547
|
+
f"{'.'.join(map(str, MIN_FRAMEWORK_VERSION))}"
|
|
548
|
+
"(spawn_background 等特性),"
|
|
549
|
+
"部分功能可能不可用,建议升级框架"
|
|
550
|
+
)
|
|
551
|
+
except Exception:
|
|
552
|
+
pass
|
|
553
|
+
|
|
554
|
+
def _register_i18n(self):
|
|
555
|
+
"""注册配置字段与日志消息的 i18n 翻译"""
|
|
556
|
+
try:
|
|
557
|
+
from ErisPulse.runtime.config_schema import register_config_i18n
|
|
558
|
+
register_config_i18n(EmailGlobalConfig, "zh-CN", domain="email")
|
|
559
|
+
register_config_i18n(EmailAccountConfig, "zh-CN", domain="email")
|
|
560
|
+
register_config_i18n(EmailGlobalConfig, "en", {
|
|
561
|
+
"email.imap_server": "Default IMAP server address",
|
|
562
|
+
"email.imap_port": "Default IMAP port",
|
|
563
|
+
"email.smtp_server": "Default SMTP server address",
|
|
564
|
+
"email.smtp_port": "Default SMTP port",
|
|
565
|
+
"email.ssl": "Whether to enable SSL by default",
|
|
566
|
+
"email.timeout": "Default connection timeout (seconds)",
|
|
567
|
+
"email.poll_interval": "IMAP polling interval (seconds)",
|
|
568
|
+
"email.max_retries": "Max retries for failed connections",
|
|
569
|
+
"email.account.email": "Email address",
|
|
570
|
+
"email.account.password": "Email password / authorization code",
|
|
571
|
+
"email.account.imap_server": "IMAP server address (leave empty to use global default)",
|
|
572
|
+
"email.account.imap_port": "IMAP port (leave empty to use global default)",
|
|
573
|
+
"email.account.smtp_server": "SMTP server address (leave empty to use global default)",
|
|
574
|
+
"email.account.smtp_port": "SMTP port (leave empty to use global default)",
|
|
575
|
+
"email.account.ssl": "Enable SSL",
|
|
576
|
+
"email.account.timeout": "Connection timeout (seconds)",
|
|
577
|
+
"email.group.basic": "Basic",
|
|
578
|
+
"email.group.imap": "IMAP",
|
|
579
|
+
"email.group.smtp": "SMTP",
|
|
580
|
+
"email.group.polling": "Polling",
|
|
581
|
+
}, domain="email")
|
|
582
|
+
register_config_i18n(EmailAccountConfig, "en", {
|
|
583
|
+
"email.account.email": "Email address",
|
|
584
|
+
"email.account.password": "Email password / authorization code",
|
|
585
|
+
"email.account.imap_server": "IMAP server address (leave empty to use global default)",
|
|
586
|
+
"email.account.imap_port": "IMAP port (leave empty to use global default)",
|
|
587
|
+
"email.account.smtp_server": "SMTP server address (leave empty to use global default)",
|
|
588
|
+
"email.account.smtp_port": "SMTP port (leave empty to use global default)",
|
|
589
|
+
"email.account.ssl": "Enable SSL",
|
|
590
|
+
"email.account.timeout": "Connection timeout (seconds)",
|
|
591
|
+
"email.group.basic": "Basic",
|
|
592
|
+
"email.group.imap": "IMAP",
|
|
593
|
+
"email.group.smtp": "SMTP",
|
|
594
|
+
}, domain="email")
|
|
595
|
+
except Exception:
|
|
596
|
+
pass
|
|
597
|
+
|
|
598
|
+
zh_CN = {
|
|
599
|
+
"email.config_migrated": "已迁移旧版 [EmailAdapter.global] 配置到 [EmailAdapter]",
|
|
600
|
+
"email.old_config_detected": "检测到旧格式配置,建议迁移到 EmailAdapter.accounts 下",
|
|
601
|
+
"email.no_config_found": "未找到配置文件,创建默认账户配置",
|
|
602
|
+
"email.save_default_failed": "保存默认账户配置失败: {error}",
|
|
603
|
+
"email.account_missing_credentials": "账户 {name} 缺少 email 或 password 配置,已跳过",
|
|
604
|
+
"email.accounts_loaded": "Email 适配器初始化完成,共加载 {count} 个账户",
|
|
605
|
+
"email.no_enabled_accounts": "没有找到任何启用的 Email 账户配置",
|
|
606
|
+
"email.adapter_started": "Email 适配器启动完成,共 {count} 个账户",
|
|
607
|
+
"email.adapter_shutdown": "Email 适配器已关闭",
|
|
608
|
+
"email.smtp_error": "SMTP 错误: {error}",
|
|
609
|
+
"email.polling_error": "轮询 {name} 出错: {error}",
|
|
610
|
+
"email.reconnect_failed": "重连 {max} 次后仍失败",
|
|
611
|
+
"email.smtp_connect_failed": "SMTP 连接失败 {name}: {error}",
|
|
612
|
+
"email.send_failed": "发送邮件失败: {error}",
|
|
613
|
+
"email.unsupported_endpoint": "不支持的端点: {endpoint}",
|
|
614
|
+
"email.smtp_not_configured": "SMTP 服务器或端口未配置(账户 {name}),请检查全局配置或账户配置",
|
|
615
|
+
"email.imap_not_configured": "IMAP 服务器或端口未配置(账户 {name}),请检查全局配置或账户配置",
|
|
616
|
+
}
|
|
617
|
+
en = {
|
|
618
|
+
"email.config_migrated": "Migrated legacy [EmailAdapter.global] config to [EmailAdapter]",
|
|
619
|
+
"email.old_config_detected": "Old config format detected, please migrate to EmailAdapter.accounts",
|
|
620
|
+
"email.no_config_found": "No config found, creating default account config",
|
|
621
|
+
"email.save_default_failed": "Failed to save default account config: {error}",
|
|
622
|
+
"email.account_missing_credentials": "Account {name} missing email or password, skipped",
|
|
623
|
+
"email.accounts_loaded": "Email adapter initialized, loaded {count} account(s)",
|
|
624
|
+
"email.no_enabled_accounts": "No enabled Email account config found",
|
|
625
|
+
"email.adapter_started": "Email adapter started, {count} account(s) total",
|
|
626
|
+
"email.adapter_shutdown": "Email adapter shut down",
|
|
627
|
+
"email.smtp_error": "SMTP error: {error}",
|
|
628
|
+
"email.polling_error": "Polling error for {name}: {error}",
|
|
629
|
+
"email.reconnect_failed": "Failed to reconnect after {max} attempts",
|
|
630
|
+
"email.smtp_connect_failed": "Failed to connect SMTP for {name}: {error}",
|
|
631
|
+
"email.send_failed": "Failed to send email: {error}",
|
|
632
|
+
"email.unsupported_endpoint": "Unsupported endpoint: {endpoint}",
|
|
633
|
+
"email.smtp_not_configured": "SMTP server or port not configured (account {name}), please check global or account config",
|
|
634
|
+
"email.imap_not_configured": "IMAP server or port not configured (account {name}), please check global or account config",
|
|
635
|
+
}
|
|
636
|
+
try:
|
|
637
|
+
i18n.register("zh-CN", zh_CN, domain="email")
|
|
638
|
+
i18n.register("en", en, domain="email")
|
|
639
|
+
except Exception:
|
|
640
|
+
pass
|
|
451
641
|
|
|
452
642
|
def _get_config_key(self) -> str:
|
|
453
643
|
return "EmailAdapter"
|
|
@@ -461,7 +651,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
461
651
|
for k, v in old_global.items():
|
|
462
652
|
data.setdefault(k, v)
|
|
463
653
|
config_mgr.setConfig(key, data, immediate=True)
|
|
464
|
-
self.logger.info("已迁移旧版 [EmailAdapter.global] 配置到 [EmailAdapter]")
|
|
654
|
+
self.logger.info(i18n.t("email.config_migrated", default="已迁移旧版 [EmailAdapter.global] 配置到 [EmailAdapter]"))
|
|
465
655
|
return super()._load_config()
|
|
466
656
|
|
|
467
657
|
def _load_accounts(self) -> dict:
|
|
@@ -474,7 +664,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
474
664
|
old_config = config_mgr.getConfig("EmailAdapter")
|
|
475
665
|
if old_config and ("email" in old_config or "accounts" not in old_config):
|
|
476
666
|
self.logger.warning(
|
|
477
|
-
"检测到旧格式配置,建议迁移到 EmailAdapter.accounts 下"
|
|
667
|
+
i18n.t("email.old_config_detected", default="检测到旧格式配置,建议迁移到 EmailAdapter.accounts 下")
|
|
478
668
|
)
|
|
479
669
|
data = {
|
|
480
670
|
"default": {
|
|
@@ -484,7 +674,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
484
674
|
}
|
|
485
675
|
}
|
|
486
676
|
else:
|
|
487
|
-
self.logger.info("未找到配置文件,创建默认账户配置")
|
|
677
|
+
self.logger.info(i18n.t("email.no_config_found", default="未找到配置文件,创建默认账户配置"))
|
|
488
678
|
data = {
|
|
489
679
|
"default": {
|
|
490
680
|
"email": "user@example.com",
|
|
@@ -495,44 +685,47 @@ class EmailAdapter(BaseAdapter):
|
|
|
495
685
|
try:
|
|
496
686
|
config_mgr.setConfig(key, data)
|
|
497
687
|
except Exception as e:
|
|
498
|
-
self.logger.error(
|
|
688
|
+
self.logger.error(i18n.t("email.save_default_failed", error=e, default="保存默认账户配置失败: {error}"))
|
|
499
689
|
|
|
500
|
-
# 全局默认值(来自 ConfigClass
|
|
690
|
+
# 全局默认值(来自 ConfigClass 实例,实时读取)
|
|
501
691
|
global_defaults = {}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
692
|
+
try:
|
|
693
|
+
cfg = self.cfg
|
|
694
|
+
if cfg is not None:
|
|
695
|
+
for k in (
|
|
696
|
+
"imap_server",
|
|
697
|
+
"imap_port",
|
|
698
|
+
"smtp_server",
|
|
699
|
+
"smtp_port",
|
|
700
|
+
"ssl",
|
|
701
|
+
"timeout",
|
|
702
|
+
):
|
|
703
|
+
global_defaults[k] = getattr(cfg, k, None)
|
|
704
|
+
except Exception:
|
|
705
|
+
pass
|
|
513
706
|
|
|
514
707
|
accounts = {}
|
|
515
708
|
for name, account_data in data.items():
|
|
516
709
|
if not isinstance(account_data, dict):
|
|
517
710
|
continue
|
|
518
711
|
|
|
519
|
-
#
|
|
712
|
+
# 合并全局默认值(账户级配置优先,但 None 值不覆盖全局默认)
|
|
520
713
|
merged = dict(global_defaults)
|
|
521
714
|
# 兼容旧版嵌套 server 子结构
|
|
522
715
|
server_block = account_data.get("server", {})
|
|
523
716
|
if isinstance(server_block, dict):
|
|
524
717
|
merged.update({k: v for k, v in server_block.items() if v is not None})
|
|
525
|
-
merged.update({k: v for k, v in account_data.items() if k != "server"})
|
|
718
|
+
merged.update({k: v for k, v in account_data.items() if k != "server" and v is not None and v != ""})
|
|
526
719
|
|
|
527
720
|
if not merged.get("email") or not merged.get("password"):
|
|
528
|
-
self.logger.error(
|
|
721
|
+
self.logger.error(i18n.t("email.account_missing_credentials", name=name, default="账户 {name} 缺少 email 或 password 配置,已跳过"))
|
|
529
722
|
continue
|
|
530
723
|
|
|
531
724
|
instance = dict_to_dataclass(EmailAccountConfig, merged)
|
|
532
725
|
instance.name = name
|
|
533
726
|
accounts[name] = instance
|
|
534
727
|
|
|
535
|
-
self.logger.info(
|
|
728
|
+
self.logger.info(i18n.t("email.accounts_loaded", count=len(accounts), default="Email 适配器初始化完成,共加载 {count} 个账户"))
|
|
536
729
|
return accounts
|
|
537
730
|
|
|
538
731
|
async def _send_email(self, account_id: str, msg: MIMEMultipart) -> Dict:
|
|
@@ -552,7 +745,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
552
745
|
response["success"] = True
|
|
553
746
|
response["message"] = "Email sent successfully"
|
|
554
747
|
except Exception as e:
|
|
555
|
-
self.logger.error(
|
|
748
|
+
self.logger.error(i18n.t("email.smtp_error", error=e, default="SMTP 错误: {error}"))
|
|
556
749
|
# 尝试重新连接
|
|
557
750
|
try:
|
|
558
751
|
await self._connect_smtp(account_id)
|
|
@@ -570,12 +763,21 @@ class EmailAdapter(BaseAdapter):
|
|
|
570
763
|
async def _connect_smtp(self, account_id: str):
|
|
571
764
|
account = self.accounts[account_id]
|
|
572
765
|
|
|
573
|
-
#
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
766
|
+
# 账户级配置优先,缺失时回退到全局配置
|
|
767
|
+
try:
|
|
768
|
+
cfg = self.cfg
|
|
769
|
+
except Exception:
|
|
770
|
+
cfg = None
|
|
771
|
+
|
|
772
|
+
smtp_server = account.smtp_server or (cfg.smtp_server if cfg else None)
|
|
773
|
+
smtp_port = account.smtp_port or (cfg.smtp_port if cfg else None)
|
|
774
|
+
use_ssl = account.ssl if account.ssl is not None else (cfg.ssl if cfg else True)
|
|
775
|
+
timeout = account.timeout or (cfg.timeout if cfg else 30)
|
|
776
|
+
|
|
777
|
+
if not smtp_server or not smtp_port:
|
|
778
|
+
raise ValueError(
|
|
779
|
+
i18n.t("email.smtp_not_configured", name=account_id, default="SMTP 服务器或端口未配置(账户 {name}),请检查全局配置或账户配置")
|
|
780
|
+
)
|
|
579
781
|
|
|
580
782
|
if account_id in self.smtp_connections:
|
|
581
783
|
try:
|
|
@@ -585,21 +787,19 @@ class EmailAdapter(BaseAdapter):
|
|
|
585
787
|
|
|
586
788
|
context = ssl.create_default_context()
|
|
587
789
|
|
|
588
|
-
if
|
|
790
|
+
if use_ssl:
|
|
589
791
|
smtp = smtplib.SMTP_SSL(
|
|
590
|
-
host=
|
|
591
|
-
port=
|
|
592
|
-
timeout=
|
|
792
|
+
host=smtp_server,
|
|
793
|
+
port=smtp_port,
|
|
794
|
+
timeout=timeout,
|
|
593
795
|
context=context,
|
|
594
796
|
)
|
|
595
797
|
else:
|
|
596
798
|
smtp = smtplib.SMTP(
|
|
597
|
-
host=
|
|
598
|
-
port=
|
|
599
|
-
timeout=
|
|
799
|
+
host=smtp_server,
|
|
800
|
+
port=smtp_port,
|
|
801
|
+
timeout=timeout,
|
|
600
802
|
)
|
|
601
|
-
if account.ssl:
|
|
602
|
-
smtp.starttls(context=context)
|
|
603
803
|
|
|
604
804
|
smtp.login(account.email, account.password)
|
|
605
805
|
self.smtp_connections[account_id] = smtp
|
|
@@ -608,6 +808,20 @@ class EmailAdapter(BaseAdapter):
|
|
|
608
808
|
"""连接 IMAP 服务器"""
|
|
609
809
|
account = self.accounts[account_id]
|
|
610
810
|
|
|
811
|
+
# 账户级配置优先,缺失时回退到全局配置
|
|
812
|
+
try:
|
|
813
|
+
cfg = self.cfg
|
|
814
|
+
except Exception:
|
|
815
|
+
cfg = None
|
|
816
|
+
|
|
817
|
+
imap_server = account.imap_server or (cfg.imap_server if cfg else None)
|
|
818
|
+
imap_port = account.imap_port or (cfg.imap_port if cfg else None)
|
|
819
|
+
|
|
820
|
+
if not imap_server or not imap_port:
|
|
821
|
+
raise ValueError(
|
|
822
|
+
i18n.t("email.imap_not_configured", name=account_id, default="IMAP 服务器或端口未配置(账户 {name}),请检查全局配置或账户配置")
|
|
823
|
+
)
|
|
824
|
+
|
|
611
825
|
if account_id in self.imap_connections:
|
|
612
826
|
try:
|
|
613
827
|
self.imap_connections[account_id].logout()
|
|
@@ -617,7 +831,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
617
831
|
context = ssl.create_default_context()
|
|
618
832
|
|
|
619
833
|
imap = imaplib.IMAP4_SSL(
|
|
620
|
-
host=
|
|
834
|
+
host=imap_server, port=imap_port, ssl_context=context
|
|
621
835
|
)
|
|
622
836
|
|
|
623
837
|
imap.login(account.email, account.password)
|
|
@@ -627,12 +841,14 @@ class EmailAdapter(BaseAdapter):
|
|
|
627
841
|
async def _poll_emails(self, account_id: str):
|
|
628
842
|
poll_interval = 60
|
|
629
843
|
max_retries = 3
|
|
630
|
-
|
|
631
|
-
|
|
844
|
+
try:
|
|
845
|
+
cfg = self.cfg
|
|
632
846
|
poll_interval = (
|
|
633
847
|
getattr(cfg, "poll_interval", poll_interval) or poll_interval
|
|
634
848
|
)
|
|
635
849
|
max_retries = getattr(cfg, "max_retries", max_retries) or max_retries
|
|
850
|
+
except Exception:
|
|
851
|
+
pass
|
|
636
852
|
|
|
637
853
|
while self._is_running:
|
|
638
854
|
try:
|
|
@@ -661,7 +877,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
661
877
|
except asyncio.CancelledError:
|
|
662
878
|
return
|
|
663
879
|
except Exception as e:
|
|
664
|
-
self.logger.error(
|
|
880
|
+
self.logger.error(i18n.t("email.polling_error", name=account_id, error=e, default="轮询 {name} 出错: {error}"))
|
|
665
881
|
retries = 0
|
|
666
882
|
while retries < max_retries:
|
|
667
883
|
try:
|
|
@@ -671,7 +887,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
671
887
|
retries += 1
|
|
672
888
|
if retries >= max_retries:
|
|
673
889
|
self.logger.error(
|
|
674
|
-
|
|
890
|
+
i18n.t("email.reconnect_failed", max=max_retries, default="重连 {max} 次后仍失败")
|
|
675
891
|
)
|
|
676
892
|
raise
|
|
677
893
|
await asyncio.sleep(5)
|
|
@@ -682,21 +898,23 @@ class EmailAdapter(BaseAdapter):
|
|
|
682
898
|
"""
|
|
683
899
|
将邮件转换为 OneBot12 标准事件
|
|
684
900
|
|
|
685
|
-
|
|
686
|
-
-
|
|
687
|
-
-
|
|
901
|
+
遵循 ErisPulse 适配器标准化转换规范:
|
|
902
|
+
- user_id 为纯邮箱地址,user_nickname 为发件人显示名
|
|
903
|
+
- message 为标准 OB12 消息段(text + file)
|
|
904
|
+
- email_raw 保留完整原始数据(向后兼容)
|
|
688
905
|
"""
|
|
906
|
+
from email.utils import parseaddr
|
|
907
|
+
|
|
689
908
|
# 确定原始事件类型
|
|
690
|
-
# 检查是否为回复邮件
|
|
691
909
|
references = email_message.get("References", "")
|
|
692
910
|
in_reply_to = email_message.get("In-Reply-To", "")
|
|
693
911
|
|
|
694
912
|
if references or in_reply_to:
|
|
695
|
-
raw_event_type = "email_reply"
|
|
913
|
+
raw_event_type = "email_reply"
|
|
696
914
|
else:
|
|
697
|
-
raw_event_type = "email_new"
|
|
915
|
+
raw_event_type = "email_new"
|
|
698
916
|
|
|
699
|
-
#
|
|
917
|
+
# 解析邮件头
|
|
700
918
|
def decode_header(header):
|
|
701
919
|
from email.header import decode_header
|
|
702
920
|
|
|
@@ -716,7 +934,13 @@ class EmailAdapter(BaseAdapter):
|
|
|
716
934
|
to = decode_header(email_message.get("To", ""))
|
|
717
935
|
date = email_message.get("Date", "")
|
|
718
936
|
|
|
719
|
-
#
|
|
937
|
+
# 从 From 头提取纯邮箱地址和显示名
|
|
938
|
+
# "Saber" <wsu2059@qq.com> -> ("Saber", "wsu2059@qq.com")
|
|
939
|
+
display_name, clean_email = parseaddr(from_)
|
|
940
|
+
if not display_name:
|
|
941
|
+
display_name = clean_email
|
|
942
|
+
|
|
943
|
+
# 解析正文和附件
|
|
720
944
|
text_content = ""
|
|
721
945
|
html_content = ""
|
|
722
946
|
attachments = []
|
|
@@ -726,9 +950,9 @@ class EmailAdapter(BaseAdapter):
|
|
|
726
950
|
content_disposition = str(part.get("Content-Disposition"))
|
|
727
951
|
|
|
728
952
|
if "attachment" in content_disposition:
|
|
729
|
-
# 处理附件
|
|
730
953
|
filename = part.get_filename()
|
|
731
954
|
if filename:
|
|
955
|
+
filename = decode_header(filename)
|
|
732
956
|
attachments.append(
|
|
733
957
|
{
|
|
734
958
|
"filename": filename,
|
|
@@ -738,7 +962,6 @@ class EmailAdapter(BaseAdapter):
|
|
|
738
962
|
}
|
|
739
963
|
)
|
|
740
964
|
elif content_type == "text/plain":
|
|
741
|
-
# 纯文本内容
|
|
742
965
|
payload = part.get_payload(decode=True)
|
|
743
966
|
try:
|
|
744
967
|
text_content = payload.decode("utf-8")
|
|
@@ -748,7 +971,6 @@ class EmailAdapter(BaseAdapter):
|
|
|
748
971
|
except UnicodeDecodeError:
|
|
749
972
|
text_content = payload.decode("utf-8", errors="replace")
|
|
750
973
|
elif content_type == "text/html":
|
|
751
|
-
# HTML内容
|
|
752
974
|
payload = part.get_payload(decode=True)
|
|
753
975
|
try:
|
|
754
976
|
html_content = payload.decode("utf-8")
|
|
@@ -761,25 +983,56 @@ class EmailAdapter(BaseAdapter):
|
|
|
761
983
|
account = self.accounts.get(account_id)
|
|
762
984
|
self_email = account.email if account else account_id
|
|
763
985
|
|
|
986
|
+
# 构建 OB12 标准消息段
|
|
987
|
+
message_segments = []
|
|
988
|
+
|
|
989
|
+
# 正文文本段
|
|
990
|
+
if text_content:
|
|
991
|
+
message_segments.append(
|
|
992
|
+
{"type": "text", "data": {"text": text_content}}
|
|
993
|
+
)
|
|
994
|
+
elif html_content and not text_content:
|
|
995
|
+
# 无纯文本时,从 HTML 提取简单文本
|
|
996
|
+
import re
|
|
997
|
+
simple_text = re.sub(r"<[^>]+>", "", html_content).strip()
|
|
998
|
+
if simple_text:
|
|
999
|
+
message_segments.append(
|
|
1000
|
+
{"type": "text", "data": {"text": simple_text}}
|
|
1001
|
+
)
|
|
1002
|
+
|
|
1003
|
+
# 附件转为 OB12 file 消息段
|
|
1004
|
+
for att in attachments:
|
|
1005
|
+
message_segments.append(
|
|
1006
|
+
{
|
|
1007
|
+
"type": "file",
|
|
1008
|
+
"data": {
|
|
1009
|
+
"file_id": att["filename"],
|
|
1010
|
+
"file_name": att["filename"],
|
|
1011
|
+
"size": att["size"],
|
|
1012
|
+
},
|
|
1013
|
+
}
|
|
1014
|
+
)
|
|
1015
|
+
|
|
1016
|
+
# 确保至少有一个消息段
|
|
1017
|
+
if not message_segments:
|
|
1018
|
+
message_segments.append({"type": "text", "data": {"text": subject or ""}})
|
|
1019
|
+
|
|
764
1020
|
# 构建标准事件
|
|
765
1021
|
return {
|
|
766
1022
|
"id": email_message.get("Message-ID", ""),
|
|
767
1023
|
"time": self._parse_email_date(date),
|
|
768
1024
|
"type": "message",
|
|
769
|
-
"detail_type": "private",
|
|
1025
|
+
"detail_type": "private",
|
|
770
1026
|
"platform": "email",
|
|
771
1027
|
"self": {"platform": "email", "user_id": self_email},
|
|
772
|
-
"message":
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
"alt_message": f"邮件: {subject}",
|
|
781
|
-
"user_id": from_,
|
|
782
|
-
# 平台原始数据(包含完整邮件信息)
|
|
1028
|
+
"message": message_segments,
|
|
1029
|
+
"alt_message": subject or text_content[:50] if text_content else "",
|
|
1030
|
+
"user_id": clean_email or from_,
|
|
1031
|
+
"user_nickname": display_name,
|
|
1032
|
+
# 邮件扩展字段
|
|
1033
|
+
"email_subject": subject,
|
|
1034
|
+
"email_from": clean_email or from_,
|
|
1035
|
+
# 平台原始数据(向后兼容)
|
|
783
1036
|
"email_raw": {
|
|
784
1037
|
"subject": subject,
|
|
785
1038
|
"from": from_,
|
|
@@ -794,8 +1047,8 @@ class EmailAdapter(BaseAdapter):
|
|
|
794
1047
|
"html_content": html_content,
|
|
795
1048
|
"attachments": [att["filename"] for att in attachments],
|
|
796
1049
|
},
|
|
797
|
-
# 原始事件类型
|
|
798
1050
|
"email_raw_type": raw_event_type,
|
|
1051
|
+
# 向后兼容:保留顶层 attachments
|
|
799
1052
|
"attachments": attachments,
|
|
800
1053
|
}
|
|
801
1054
|
|
|
@@ -848,13 +1101,13 @@ class EmailAdapter(BaseAdapter):
|
|
|
848
1101
|
else:
|
|
849
1102
|
return self.make_error(
|
|
850
1103
|
retcode=10002,
|
|
851
|
-
message=
|
|
1104
|
+
message=i18n.t("email.unsupported_endpoint", endpoint=endpoint, default="不支持的端点: {endpoint}"),
|
|
852
1105
|
raw=None,
|
|
853
1106
|
)
|
|
854
1107
|
|
|
855
1108
|
async def start(self):
|
|
856
1109
|
if not self.enabled_accounts:
|
|
857
|
-
self.logger.warning("没有找到任何启用的 Email 账户配置")
|
|
1110
|
+
self.logger.warning(i18n.t("email.no_enabled_accounts", default="没有找到任何启用的 Email 账户配置"))
|
|
858
1111
|
return
|
|
859
1112
|
|
|
860
1113
|
self._is_running = True
|
|
@@ -865,7 +1118,7 @@ class EmailAdapter(BaseAdapter):
|
|
|
865
1118
|
await self._connect_smtp(account_name)
|
|
866
1119
|
except Exception as e:
|
|
867
1120
|
self.logger.error(
|
|
868
|
-
|
|
1121
|
+
i18n.t("email.smtp_connect_failed", name=account_name, error=e, default="SMTP 连接失败 {name}: {error}")
|
|
869
1122
|
)
|
|
870
1123
|
try:
|
|
871
1124
|
await self.emit_meta("connect", account.email)
|
|
@@ -875,12 +1128,14 @@ class EmailAdapter(BaseAdapter):
|
|
|
875
1128
|
# 启动轮询任务(仅对配置了 IMAP 的账户)
|
|
876
1129
|
for account_name, account in self.enabled_accounts.items():
|
|
877
1130
|
if account.imap_server:
|
|
878
|
-
|
|
879
|
-
|
|
1131
|
+
coro = self._poll_emails(account_name)
|
|
1132
|
+
# 生命周期任务使用 spawn_background(owner 归属,shutdown 自动回收)
|
|
1133
|
+
self.poll_tasks[account_name] = (
|
|
1134
|
+
spawn_background(coro) if spawn_background is not None else asyncio.create_task(coro)
|
|
880
1135
|
)
|
|
881
1136
|
|
|
882
1137
|
self.logger.info(
|
|
883
|
-
|
|
1138
|
+
i18n.t("email.adapter_started", count=len(self.enabled_accounts), default="Email 适配器启动完成,共 {count} 个账户")
|
|
884
1139
|
)
|
|
885
1140
|
|
|
886
1141
|
async def shutdown(self):
|
|
@@ -922,4 +1177,4 @@ class EmailAdapter(BaseAdapter):
|
|
|
922
1177
|
except Exception:
|
|
923
1178
|
pass
|
|
924
1179
|
|
|
925
|
-
self.logger.info("Email 适配器已关闭")
|
|
1180
|
+
self.logger.info(i18n.t("email.adapter_shutdown", default="Email 适配器已关闭"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse-EmailAdapter
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: ErisPulse邮箱适配器,支持多账号管理、HTML内容和附件发送
|
|
5
5
|
Author-email: wsu2059q <wsu2059@qq.com>
|
|
6
6
|
License: MIT
|
|
@@ -22,9 +22,28 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Dynamic: license-file
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
<div align="center">
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
<img src=".github/assets/ErisPulseLogo.png" width="180" alt="ErisPulse EmailAdapter" />
|
|
28
|
+
|
|
29
|
+
# ErisPulse EmailAdapter
|
|
30
|
+
|
|
31
|
+
**Send and receive emails as events — turn any mailbox into a Bot.**
|
|
32
|
+
|
|
33
|
+
A powerful email adapter for the ErisPulse framework. It polls mailboxes for new messages and converts them into standard OneBot12 events, while emails are sent via the chainable Send DSL — with multi-account, attachments and HTML support.
|
|
34
|
+
|
|
35
|
+
<p>
|
|
36
|
+
<a href="https://pypi.org/project/ErisPulse-EmailAdapter/"><img src="https://img.shields.io/pypi/v/ErisPulse-EmailAdapter?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
37
|
+
<a href="https://pypi.org/project/ErisPulse-EmailAdapter/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
|
|
38
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
|
|
39
|
+
<a href="https://github.com/ErisPulse/ErisPulse-EmailAdapter"><img src="https://img.shields.io/github/stars/ErisPulse/ErisPulse-EmailAdapter?style=for-the-badge&logo=github&color=brightgreen" alt="Stars"></a>
|
|
40
|
+
<a href="https://pepy.tech/project/ErisPulse-EmailAdapter"><img src="https://img.shields.io/pepy/dt/ErisPulse-EmailAdapter?style=for-the-badge&color=blue" alt="Downloads"></a>
|
|
41
|
+
<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>
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
[English](#english) | [简体中文](#中文)
|
|
45
|
+
|
|
46
|
+
</div>
|
|
28
47
|
|
|
29
48
|
---
|
|
30
49
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ErisPulse-EmailAdapter
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: ErisPulse邮箱适配器,支持多账号管理、HTML内容和附件发送
|
|
5
5
|
Author-email: wsu2059q <wsu2059@qq.com>
|
|
6
6
|
License: MIT
|
|
@@ -22,9 +22,28 @@ Description-Content-Type: text/markdown
|
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
Dynamic: license-file
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
<div align="center">
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
<img src=".github/assets/ErisPulseLogo.png" width="180" alt="ErisPulse EmailAdapter" />
|
|
28
|
+
|
|
29
|
+
# ErisPulse EmailAdapter
|
|
30
|
+
|
|
31
|
+
**Send and receive emails as events — turn any mailbox into a Bot.**
|
|
32
|
+
|
|
33
|
+
A powerful email adapter for the ErisPulse framework. It polls mailboxes for new messages and converts them into standard OneBot12 events, while emails are sent via the chainable Send DSL — with multi-account, attachments and HTML support.
|
|
34
|
+
|
|
35
|
+
<p>
|
|
36
|
+
<a href="https://pypi.org/project/ErisPulse-EmailAdapter/"><img src="https://img.shields.io/pypi/v/ErisPulse-EmailAdapter?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
37
|
+
<a href="https://pypi.org/project/ErisPulse-EmailAdapter/"><img src="https://img.shields.io/badge/Python-3.10+-FFD43B?style=for-the-badge&logo=python&logoColor=blue" alt="Python"></a>
|
|
38
|
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue?style=for-the-badge" alt="License"></a>
|
|
39
|
+
<a href="https://github.com/ErisPulse/ErisPulse-EmailAdapter"><img src="https://img.shields.io/github/stars/ErisPulse/ErisPulse-EmailAdapter?style=for-the-badge&logo=github&color=brightgreen" alt="Stars"></a>
|
|
40
|
+
<a href="https://pepy.tech/project/ErisPulse-EmailAdapter"><img src="https://img.shields.io/pepy/dt/ErisPulse-EmailAdapter?style=for-the-badge&color=blue" alt="Downloads"></a>
|
|
41
|
+
<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>
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
[English](#english) | [简体中文](#中文)
|
|
45
|
+
|
|
46
|
+
</div>
|
|
28
47
|
|
|
29
48
|
---
|
|
30
49
|
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src=".github/assets/ErisPulseLogo.png" width="180" alt="ErisPulse EmailAdapter" />
|
|
4
|
+
|
|
5
|
+
# ErisPulse EmailAdapter
|
|
6
|
+
|
|
7
|
+
**Send and receive emails as events — turn any mailbox into a Bot.**
|
|
8
|
+
|
|
9
|
+
A powerful email adapter for the ErisPulse framework. It polls mailboxes for new messages and converts them into standard OneBot12 events, while emails are sent via the chainable Send DSL — with multi-account, attachments and HTML support.
|
|
10
|
+
|
|
11
|
+
<p>
|
|
12
|
+
<a href="https://pypi.org/project/ErisPulse-EmailAdapter/"><img src="https://img.shields.io/pypi/v/ErisPulse-EmailAdapter?style=for-the-badge&logo=pypi&logoColor=white" alt="PyPI"></a>
|
|
13
|
+
<a href="https://pypi.org/project/ErisPulse-EmailAdapter/"><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-EmailAdapter"><img src="https://img.shields.io/github/stars/ErisPulse/ErisPulse-EmailAdapter?style=for-the-badge&logo=github&color=brightgreen" alt="Stars"></a>
|
|
16
|
+
<a href="https://pepy.tech/project/ErisPulse-EmailAdapter"><img src="https://img.shields.io/pepy/dt/ErisPulse-EmailAdapter?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 EmailAdapter
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|