pytbox 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pytbox might be problematic. Click here for more details.

pytbox/base.py CHANGED
@@ -10,9 +10,14 @@ from pytbox.alert.alert_handler import AlertHandler
10
10
  from pytbox.log.logger import AppLogger
11
11
  from pytbox.win.ad import ADClient
12
12
  from pytbox.network.meraki import Meraki
13
+ from pytbox.utils.env import get_env_by_os_environment
14
+ from pytbox.vmware import VMwareClient
15
+ from pytbox.pyjira import PyJira
16
+ from pytbox.mail.client import MailClient
13
17
 
14
18
  config = load_config_by_file(path='/workspaces/pytbox/tests/alert/config_dev.toml', oc_vault_id=os.environ.get('oc_vault_id'))
15
19
 
20
+
16
21
  def get_mongo(collection):
17
22
  return Mongo(
18
23
  host=config['mongo']['host'],
@@ -61,4 +66,21 @@ def get_logger(app):
61
66
  # password=config['ad']['prod']['AD_PASSWORD']
62
67
  # )
63
68
 
64
- meraki = Meraki(api_key=config['meraki']['api_key'], organization_id=config['meraki']['organization_id'])
69
+ env = get_env_by_os_environment(check_key='ENV')
70
+ meraki = Meraki(api_key=config['meraki']['api_key'], organization_id=config['meraki']['organization_id'])
71
+
72
+ vmware_test = VMwareClient(
73
+ host=config['vmware']['test']['host'],
74
+ username=config['vmware']['test']['username'],
75
+ password=config['vmware']['test']['password'],
76
+ version=config['vmware']['test']['version'],
77
+ proxies=config['vmware']['test']['proxies']
78
+ )
79
+
80
+ pyjira = PyJira(
81
+ base_url=config['jira']['base_url'],
82
+ token=config['jira']['token']
83
+ )
84
+
85
+ mail_163 = MailClient(mail_address=config['mail']['163']['mail_address'], password=config['mail']['163']['password'])
86
+ mail_qq = MailClient(mail_address=config['mail']['qq']['mail_address'], password=config['mail']['qq']['password'])
@@ -114,16 +114,11 @@ class BuildConfig:
114
114
  device_templates = glob(str(jinja2_dir / f'{device_type}_*.toml.j2'))
115
115
  if not device_templates:
116
116
  continue
117
-
118
117
  for tmpl_path in device_templates:
119
118
  tmpl_name = os.path.basename(tmpl_path)
120
- # 例如 h3c_system.toml.j2 -> h3c_system
121
119
  base_name = tmpl_name.replace('.toml.j2', '')
122
-
123
120
  template = self._get_template(f'input.snmp/{tmpl_name}')
124
- # 修复数据结构:模板期望的是数组,每个元素是字典
125
- # instances 是 [{"udp://10.1.1.1:161": {...}, "udp://10.1.1.2:161": {...}}, ...]
126
- render_data = template.render(instances=instances)
121
+ render_data = template.render(instances=instances, config=self.instances['snmp']['config'])
127
122
 
128
123
  target_dir = Path(self.output_dir) / 'input.snmp'
129
124
  if not target_dir.exists():
@@ -25,6 +25,9 @@
25
25
  "119.29.29.29_baidu.com" = { dns_server = "119.29.29.29", domains = "www.baidu.com", labels = { name = "x", env = "prod" } }
26
26
 
27
27
  [snmp]
28
+ [snmp.config]
29
+ timeout = '120s'
30
+ path = "/usr/share/snmp/mibs"
28
31
  # 支持 h3c,huawei,cisco,ruijie
29
32
  [[snmp.instances.h3c]]
30
33
  "10.1.1.1:161" = { version = 2, community = "public" }
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -17,9 +17,9 @@ priv_protocol = "{{detail.priv_protocol}}"
17
17
  priv_password = "{{detail.priv_password}}"
18
18
  sec_level = "{{detail.sec_level}}"
19
19
  {% endif %}
20
- timeout = "5s"
20
+ timeout = "{{config.timeout}}"
21
21
  retries = 3
22
- path = ["/usr/share/snmp/mibs"]
22
+ path = ["{{config.path}}"]
23
23
  translator = "gosmi"
24
24
  max_repetitions = 50
25
25
 
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import click
4
+ from pytbox.database.victoriametrics import VictoriaMetrics
5
+ from pytbox.utils.richutils import RichUtils
6
+
7
+ rich_utils = RichUtils()
8
+
9
+ @click.group()
10
+ def vm_group():
11
+ """VictoriaMetrics 查询工具"""
12
+ pass
13
+
14
+
15
+ @vm_group.command('query')
16
+ @click.option('--url', '-u', type=str, required=True)
17
+ @click.option('--query', '-q', type=str, required=True)
18
+ def query(url, query):
19
+ """查询 VM 数据"""
20
+ vm_client = VictoriaMetrics(url=url)
21
+ r = vm_client.query(query, output_format='json')
22
+ rich_utils.print(msg=r)
pytbox/cli/main.py CHANGED
@@ -5,6 +5,7 @@ Pytbox 主命令行入口
5
5
 
6
6
  import click
7
7
  from .categraf import categraf_group
8
+ from .commands.vm import vm_group
8
9
 
9
10
 
10
11
  @click.group()
@@ -16,6 +17,7 @@ def main():
16
17
 
17
18
  # 注册子命令组
18
19
  main.add_command(categraf_group, name='categraf')
20
+ main.add_command(vm_group, name='vm')
19
21
 
20
22
 
21
23
  if __name__ == "__main__":
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+ import json
3
4
  from typing import Literal, Optional
4
5
  import requests
5
6
  from ..utils.response import ReturnResponse
7
+ from ..utils.load_vm_devfile import load_dev_file
6
8
 
7
9
 
8
10
  class VictoriaMetrics:
@@ -16,13 +18,13 @@ class VictoriaMetrics:
16
18
  'Accept': 'application/json'
17
19
  })
18
20
 
19
- def query(self, query: str) -> ReturnResponse:
21
+ def query(self, query: str, output_format: Literal['json']=None) -> ReturnResponse:
20
22
  '''
21
23
  查询指标数据
22
-
24
+
23
25
  Args:
24
26
  query (str): 查询语句
25
-
27
+
26
28
  Returns:
27
29
  dict: 查询结果
28
30
  '''
@@ -32,13 +34,32 @@ class VictoriaMetrics:
32
34
  timeout=self.timeout,
33
35
  params={"query": query}
34
36
  )
35
- if r.json().get("status") == "success":
36
- if r.json()['data']['result']:
37
- return ReturnResponse(code=0, msg=f"[{query}] 查询成功!", data=r.json()['data']['result'])
37
+ res_json = r.json()
38
+ status = res_json.get("status")
39
+ result = res_json.get("data", {}).get("result", [])
40
+ is_json = output_format == 'json'
41
+
42
+ if status == "success":
43
+ if result:
44
+ code = 0
45
+ msg = f"[{query}] 查询成功!"
46
+ data = result
38
47
  else:
39
- return ReturnResponse(code=2, msg=f"[{query}] 没有查询到结果", data=r.json())
48
+ code = 2
49
+ msg = f"[{query}] 没有查询到结果"
50
+ data = res_json
40
51
  else:
41
- return ReturnResponse(code=1, msg=f"[{query}] 查询失败: {r.json().get('error')}", data=r.json())
52
+ code = 1
53
+ msg = f"[{query}] 查询失败: {res_json.get('error')}"
54
+ data = res_json
55
+
56
+ resp = ReturnResponse(code=code, msg=msg, data=data)
57
+
58
+ if is_json:
59
+ json_result = json.dumps(resp.__dict__, ensure_ascii=False)
60
+ return json_result
61
+ else:
62
+ return resp
42
63
 
43
64
  def get_labels(self, metric_name: str) -> ReturnResponse:
44
65
  url = f"{self.url}/api/v1/series?match[]={metric_name}"
@@ -49,9 +70,19 @@ class VictoriaMetrics:
49
70
  else:
50
71
  return ReturnResponse(code=1, msg=f"metric name: {metric_name} 查询失败")
51
72
 
52
- def check_ping_result(self, target: str, last_minute: int=10) -> ReturnResponse:
73
+ def check_ping_result(self, target: str, last_minute: int=10, env: str='prod', dev_file: str='') -> ReturnResponse:
53
74
  '''
54
75
  检查ping结果
76
+
77
+ Args:
78
+ target (str): 目标地址
79
+ last_minute (int, optional): 最近多少分钟. Defaults to 10.
80
+ env (str, optional): 环境. Defaults to 'prod'.
81
+ dev_file (str, optional): 开发文件. Defaults to ''.
82
+
83
+ Returns:
84
+ ReturnResponse:
85
+ code = 0 正常, code = 1 异常, code = 2 没有查询到数据, 建议将其判断为正常
55
86
  '''
56
87
  if target:
57
88
  # 这里需要在字符串中保留 {},同时插入 target,可以用双大括号转义
@@ -62,13 +93,18 @@ class VictoriaMetrics:
62
93
  if last_minute:
63
94
  query = query + f"[{last_minute}m]"
64
95
 
65
- r = self.query(query=query)
96
+ if env == 'dev':
97
+ r = load_dev_file(dev_file)
98
+ else:
99
+ r = self.query(query=query)
100
+
66
101
  if r.code == 0:
67
102
  values = r.data[0]['values']
68
103
  if len(values) == 2 and values[1] == "0":
69
104
  code = 0
70
105
  msg = f"已检查 {target} 最近 {last_minute} 分钟是正常的!"
71
106
  else:
107
+
72
108
  if all(str(item[1]) == "1" for item in values):
73
109
  code = 1
74
110
  msg = f"已检查 {target} 最近 {last_minute} 分钟是异常的!"
pytbox/excel.py ADDED
File without changes
@@ -254,9 +254,9 @@ class MessageEndpoint(Endpoint):
254
254
  body=payload
255
255
  )
256
256
  if r.code == 0:
257
- return ReturnResponse(code=0, message=f"{message_id} 回复 emoji [{emoji_type}] 成功")
257
+ return ReturnResponse(code=0, msg=f"{message_id} 回复 emoji [{emoji_type}] 成功")
258
258
  else:
259
- return ReturnResponse(code=1, message=f"{message_id} 回复 emoji [{emoji_type}] 失败")
259
+ return ReturnResponse(code=1, msg=f"{message_id} 回复 emoji [{emoji_type}] 失败")
260
260
 
261
261
  class BitableEndpoint(Endpoint):
262
262
 
@@ -432,10 +432,10 @@ class BitableEndpoint(Endpoint):
432
432
  resp = self.parent.request(path=f'/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}',
433
433
  method='PUT',
434
434
  body=payload)
435
- return ReturnResponse(code=resp.code, message=f"记录已存在, 进行更新", data=resp.data)
435
+ return ReturnResponse(code=resp.code, msg=f"记录已存在, 进行更新", data=resp.data)
436
436
  else:
437
437
  resp = self.add_record(app_token, table_id, fields)
438
- return ReturnResponse(code=resp.code, message=f"记录不存在, 进行创建", data=resp.data)
438
+ return ReturnResponse(code=resp.code, msg=f"记录不存在, 进行创建", data=resp.data)
439
439
 
440
440
  def query_name_by_record_id(self, app_token: str=None, table_id: str=None, field_names: list=None, record_id: str='', name: str=''):
441
441
  response = self.query_record(app_token=app_token, table_id=table_id, field_names=field_names)
@@ -967,9 +967,9 @@ class ExtensionsEndpoint(Endpoint):
967
967
  body=payload)
968
968
  if response.code == 0:
969
969
  if get == 'open_id':
970
- return ReturnResponse(code=0, message=f'根据用户输入的 {user_input}, 获取用户信息成功', data=response.data['user_list'][0]['user_id'])
970
+ return ReturnResponse(code=0, msg=f'根据用户输入的 {user_input}, 获取用户信息成功', data=response.data['user_list'][0]['user_id'])
971
971
  else:
972
- return ReturnResponse(code=response.code, message=f"获取时失败, 报错请见 data 字段", data=response.data)
972
+ return ReturnResponse(code=response.code, msg=f"获取时失败, 报错请见 data 字段", data=response.data)
973
973
 
974
974
  def format_rich_text(self, text: str, color: Literal['red', 'green', 'yellow', 'blue'], bold: bool=False):
975
975
  if bold:
pytbox/mail/client.py CHANGED
@@ -1,45 +1,11 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
3
 
4
- from dataclasses import dataclass
5
- from typing import Generator, Literal
6
-
4
+ from contextlib import contextmanager
7
5
  import yagmail
8
- from imbox import Imbox
9
- from imap_tools import MailBox, AND, MailMessageFlags
10
- from nb_log import get_logger
11
-
12
- from src.library.onepassword import my1p
13
-
14
-
15
- log = get_logger('src.library.mail.client')
16
-
17
-
18
- @dataclass
19
- class MailDetail:
20
- """
21
- 邮件详情数据类。
22
-
23
- Attributes:
24
- uid: 邮件唯一标识符
25
- send_from: 发件人邮箱地址
26
- send_to: 收件人邮箱地址列表
27
- cc: 抄送人邮箱地址列表
28
- subject: 邮件主题
29
- body_plain: 纯文本正文
30
- body_html: HTML格式正文
31
- attachment: 附件完整保存路径列表
32
- """
33
- uid: str=None
34
- send_from: str=None
35
- send_to: list=None
36
- date: str=None
37
- cc: list=None
38
- subject: str=None
39
- body_plain: str=None
40
- body_html: str=None
41
- attachment: list=None
42
- has_attachments: bool=False
6
+ from imap_tools import MailBox, MailMessageFlags, AND
7
+ from .mail_detail import MailDetail
8
+ from ..utils.response import ReturnResponse
43
9
 
44
10
 
45
11
  class MailClient:
@@ -47,114 +13,93 @@ class MailClient:
47
13
  _summary_
48
14
  '''
49
15
  def __init__(self,
50
- send_mail_address: Literal['service@tyun.cn', 'alert@tyun.cn', 'houmingming@tyun.cn', 'houmdream@163.com', 'houm01@foxmail.com'] = 'service@tyun.cn',
51
- authorization_code: bool=False,
16
+ mail_address: str=None,
52
17
  password: str=None
53
18
  ):
54
19
 
55
- self.mail_address = send_mail_address
56
-
57
- if password:
58
- self.password = password
59
- else:
60
- if authorization_code:
61
- self.password = my1p.get_item_by_title(title=send_mail_address)['授权码']
62
- else:
63
- self.password = my1p.get_item_by_title(title=send_mail_address)['password']
20
+ self.mail_address = mail_address
21
+ self.password = password
64
22
 
65
- if '163.com' in send_mail_address:
23
+ if '163.com' in mail_address:
66
24
  self.smtp_address = 'smtp.163.com'
67
25
  self.imap_address = 'imap.163.com'
68
- self.imbox_client = self._create_imbox_object(vendor='163')
69
- elif 'foxmail.com' in send_mail_address:
26
+ # self.imbox_client = self._create_imbox_object()
27
+ elif 'foxmail.com' in mail_address:
70
28
  self.smtp_address = 'smtp.qq.com'
71
29
  self.imap_address = 'imap.qq.com'
72
- self.imbox_client = self._create_imbox_object(vendor='qq')
30
+
73
31
  else:
74
- raise ValueError(f'不支持的邮箱地址: {send_mail_address}')
75
-
76
- self.mailbox = MailBox(self.imap_address).login(self.mail_address, self.password)
32
+ raise ValueError(f'不支持的邮箱地址: {mail_address}')
33
+
34
+ @contextmanager
35
+ def get_mailbox(self, readonly=False):
36
+ """
37
+ 创建并返回一个已登录的 MailBox 上下文管理器。
38
+
39
+ 使用方式:
40
+ with self.get_mailbox() as mailbox:
41
+ # 使用 mailbox 进行操作
42
+ pass
77
43
 
78
- def _create_imbox_object(self, vendor: Literal['aliyun', 'qq', '163']):
79
- return Imbox(self.imap_address,
80
- username=self.mail_address,
81
- password=self.password,
82
- ssl=True,
83
- ssl_context=None,
84
- starttls=False)
44
+ Args:
45
+ readonly (bool): 是否以只读模式打开邮箱,只读模式下不会将邮件标记为已读
46
+
47
+ Yields:
48
+ MailBox: 已登录的邮箱对象
49
+ """
50
+ mailbox = MailBox(self.imap_address).login(self.mail_address, self.password)
51
+ if readonly:
52
+ # 以只读模式选择收件箱,防止邮件被标记为已读
53
+ mailbox.folder.set('INBOX', readonly=True)
54
+ try:
55
+ yield mailbox
56
+ finally:
57
+ mailbox.logout()
85
58
 
86
- def send_mail(self, receiver: list=[], cc: list=['houmingming@tyun.cn'], subject: str='', contents: str='', attachments: list=[], tips: str=None):
59
+ def send_mail(self, receiver: list=None, cc: list=None, subject: str='', contents: str='', attachments: list=None, tips: str=None):
87
60
  '''
88
61
  _summary_
89
62
 
90
63
  Args:
91
- receiver (list, optional): _description_. Defaults to [].
92
- cc (list, optional): _description_. Defaults to ['houmingming@tyun.cn'].
64
+ receiver (list, optional): _description_. Defaults to None.
65
+ cc (list, optional): _description_. Defaults to None.
93
66
  subject (str, optional): _description_. Defaults to ''.
94
67
  contents (str, optional): _description_. Defaults to ''.
95
- attachments (list, optional): _description_. Defaults to [].
68
+ attachments (list, optional): _description_. Defaults to None.
96
69
  '''
97
- email_signature = """
98
- ————————————————————————————————————————
99
- 以专业成就客户
100
- 钛信(上海)信息科技有限公司
101
- Tai Xin(ShangHai) Information Technology Co.,Ltd.
102
- 中国上海市浦东新区达尔文路88号半岛科技园21栋2楼
103
- Tel:400-920-0057
104
- Web:www.tyun.cn
105
- ————————————————————————————————————————
106
- 信息安全声明:本邮件包含信息归发件人所在组织所有,发件人所在组织对该邮件拥有所有权利。请接收者注意保密,未经发件人书面许可,不得向任何第三方组织和个人透露本邮件所含信息的全部或部分。以上声明仅适用于工作邮件。
107
- Information Security Notice: The information contained in this mail is solely property of the sender's organization. This mail communication is confidential. Recipients named above are obligated to maintain secrecy and are not permitted to disclose the contents of this communication to others.
108
- """
109
- with yagmail.SMTP(user=self.mail_address, password=my1p.get_item_by_title(self.mail_address)['password'], port=465, host=self.smtp_address) as yag:
110
- log.info(f'receiver: {receiver}, cc: {cc}, subject: {subject}, contents: {contents}, attachments: {attachments}')
70
+ with yagmail.SMTP(user=self.mail_address, password=self.password, port=465, host=self.smtp_address) as yag:
111
71
  try:
112
72
  if tips:
113
- contents = contents + '\n' + '<p style="color: red;">本邮件为系统自动发送, 如有问题, 请联系 houmingming@tyun.cn</p>'
114
-
115
- contents = contents + email_signature
73
+ contents = contents + '\n' + '<p style="color: red;">本邮件为系统自动发送</p>'
116
74
  yag.send(to=receiver, cc=cc, subject=subject, contents=contents, attachments=attachments)
117
- log.info('发送成功!!!')
118
75
  return True
119
76
  except Exception as e:
120
- log.error(f'发送失败, 报错: {e}')
121
77
  return False
78
+
79
+ def get_mail_list(self, seen: bool=False, readonly: bool=True):
80
+ '''
81
+ 获取邮件
122
82
 
123
- def get_mail_list(self, is_read: bool=False) -> Generator:
124
- with MailBox(self.imap_address).login(self.mail_address, self.password) as mailbox:
125
- for msg in mailbox.fetch(criteria=AND(seen=is_read)):
126
- # 处理附件
127
- attachment_list = []
128
- for att in msg.attachments:
129
- att_dict = {}
130
- att_dict['filename'] = att.filename
131
- att_dict['payload'] = att.payload
132
- att_dict['size'] = att.size
133
- att_dict['content_id'] = att.content_id
134
- att_dict['content_type'] = att.content_type
135
- att_dict['content_disposition'] = att.content_disposition
136
- # att_dict['part'] = att.part
137
- att_dict['size'] = att.size
138
- attachment_list.append(att_dict)
139
-
140
- if attachment_list:
141
- is_has_attachments = True
142
- else:
143
- is_has_attachments = False
83
+ Args:
84
+ seen (bool, optional): 默认获取未读邮件, 如果为 True, 则获取已读邮件
85
+ readonly (bool, optional): 是否以只读模式获取邮件,默认为 True,防止邮件被标记为已读
144
86
 
87
+ Yields:
88
+ MailDetail: 邮件详情
89
+ '''
90
+ with self.get_mailbox(readonly=readonly) as mailbox:
91
+ for msg in mailbox.fetch(AND(seen=seen)):
145
92
  yield MailDetail(
146
93
  uid=msg.uid,
147
- send_from=msg.from_,
148
- send_to=msg.to,
94
+ sent_from=msg.from_,
95
+ sent_to=msg.to,
149
96
  date=msg.date,
150
97
  cc=msg.cc,
151
98
  subject=msg.subject,
152
99
  body_plain=msg.text,
153
- body_html=msg.html,
154
- has_attachments=is_has_attachments,
155
- attachment=attachment_list
156
- )
157
-
100
+ body_html=msg.html
101
+ )
102
+
158
103
  def mark_as_read(self, uid):
159
104
  """
160
105
  标记邮件为已读。
@@ -163,13 +108,12 @@ Information Security Notice: The information contained in this mail is solely pr
163
108
  uid (str): 邮件的唯一标识符
164
109
  """
165
110
  try:
166
- # 使用 imap_tools 的 flag 方法标记邮件为已读
167
- # 第一个参数是 uid,第二个参数是要设置的标志,第三个参数 True 表示添加标志
168
- self.mailbox.flag(uid, [MailMessageFlags.SEEN], True)
169
- # log.info(f'标注邮件{uid}为已读')
111
+ with self.get_mailbox() as mailbox:
112
+ # 使用 imap_tools flag 方法标记邮件为已读
113
+ # 第一个参数是 uid,第二个参数是要设置的标志,第三个参数 True 表示添加标志
114
+ mailbox.flag(uid, [MailMessageFlags.SEEN], True)
170
115
  except Exception as e:
171
- log.error(f'标记邮件{uid}为已读失败: {e}')
172
- raise
116
+ return ReturnResponse(code=1, msg='邮件删除失败', data=e)
173
117
 
174
118
  def delete(self, uid):
175
119
  """
@@ -179,14 +123,15 @@ Information Security Notice: The information contained in this mail is solely pr
179
123
  uid (str): 邮件的唯一标识符
180
124
  """
181
125
  try:
182
- # 使用 imap_tools 的 delete 方法删除邮件
183
- self.mailbox.delete(uid)
184
- log.info(f'删除邮件{uid}')
126
+ with self.get_mailbox() as mailbox:
127
+ # 使用 imap_tools 的 delete 方法删除邮件
128
+ mailbox.delete(uid)
129
+ # log.info(f'删除邮件{uid}')
185
130
  except Exception as e:
186
- log.error(f'删除邮件{uid}失败: {e}')
131
+ # log.error(f'删除邮件{uid}失败: {e}')
187
132
  raise
188
133
 
189
- def move(self, uid: str, destination_folder: str) -> None:
134
+ def move(self, uid: str, destination_folder: str) -> ReturnResponse:
190
135
  """
191
136
  移动邮件到指定文件夹。
192
137
 
@@ -198,24 +143,25 @@ Information Security Notice: The information contained in this mail is solely pr
198
143
  destination_folder (str): 目标文件夹名称。
199
144
 
200
145
  Returns:
201
- None
146
+ ReturnResponse: 移动邮件结果
202
147
 
203
148
  Raises:
204
149
  Exception: 移动过程中底层 imap 库抛出的异常。
205
150
  """
206
151
  try:
207
- # 使用 imap_tools 的 move 方法移动邮件
208
- self.mailbox.move(uid, destination_folder)
209
- log.info(f'邮件{uid}已移动到{destination_folder}')
152
+ with self.get_mailbox() as mailbox:
153
+ # 使用 imap_tools 的 move 方法移动邮件
154
+ mailbox.move(uid, destination_folder)
155
+ return ReturnResponse(code=0, msg=f'邮件 {uid} 移动到 {destination_folder} 成功', data=None)
210
156
  except Exception as e:
211
- log.error(f'移动邮件{uid}{destination_folder}失败: {e}')
212
- raise
157
+ return ReturnResponse(code=1, msg=f'邮件 {uid} 移动到 {destination_folder} 失败', data=e)
158
+
159
+ def get_folder_list(self):
160
+ '''
161
+ 获取文件夹列表
162
+ '''
163
+ with self.get_mailbox() as mailbox:
164
+ return ReturnResponse(code=0, msg='获取文件夹列表成功', data=mailbox.folder.list())
213
165
 
214
166
  if __name__ == '__main__':
215
- # ali_mail = MailClient(send_mail_address='houmingming@tyun.cn')
216
- # mail = MailClient(send_mail_address='houmingming@tyun.cn')
217
- # mail = MailClient(send_mail_address='houmdream@163.com', authorization_code=True)
218
- # mail = MailClient(send_mail_address='houm01@foxmail.com', password=my1p.get_item_by_title('QQ'))
219
- # 对于阿里云邮箱,使用兼容方法
220
- pass
221
- # mail.get_mail_list(attachment=True, attachment_path='/tmp')
167
+ pass