pytbox 0.0.9__py3-none-any.whl → 0.1.1__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 +19 -3
- pytbox/categraf/build_config.py +99 -42
- pytbox/categraf/instances.toml +22 -2
- pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +12 -0
- pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +9 -0
- pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +41 -0
- pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +41 -0
- pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +41 -0
- pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +96 -0
- pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +41 -0
- pytbox/mail/alimail.py +129 -0
- pytbox/mail/client.py +221 -0
- pytbox/network/meraki.py +169 -0
- pytbox/win/ad.py +30 -0
- {pytbox-0.0.9.dist-info → pytbox-0.1.1.dist-info}/METADATA +4 -3
- {pytbox-0.0.9.dist-info → pytbox-0.1.1.dist-info}/RECORD +22 -10
- {pytbox-0.0.9.dist-info → pytbox-0.1.1.dist-info}/WHEEL +0 -0
- {pytbox-0.0.9.dist-info → pytbox-0.1.1.dist-info}/entry_points.txt +0 -0
- {pytbox-0.0.9.dist-info → pytbox-0.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{% for instance in instances %}
|
|
2
|
+
{% for agent, detail in instance.items() %}
|
|
3
|
+
[[instances]]
|
|
4
|
+
agents = [
|
|
5
|
+
"udp://{{agent}}"
|
|
6
|
+
]
|
|
7
|
+
{% if detail.version == 2 %}
|
|
8
|
+
version = {{detail.version}}
|
|
9
|
+
community = "{{detail.community}}"
|
|
10
|
+
{% endif %}
|
|
11
|
+
{% if detail.version == 3 %}
|
|
12
|
+
version = {{detail.version}}
|
|
13
|
+
sec_name = "{{detail.sec_name}}"
|
|
14
|
+
auth_protocol = "{{detail.auth_protocol}}"
|
|
15
|
+
auth_password = "{{detail.auth_password}}"
|
|
16
|
+
priv_protocol = "{{detail.priv_protocol}}"
|
|
17
|
+
priv_password = "{{detail.priv_password}}"
|
|
18
|
+
sec_level = "{{detail.sec_level}}"
|
|
19
|
+
{% endif %}
|
|
20
|
+
timeout = "5s"
|
|
21
|
+
retries = 3
|
|
22
|
+
path = ["/usr/share/snmp/mibs"]
|
|
23
|
+
translator = "gosmi"
|
|
24
|
+
max_repetitions = 50
|
|
25
|
+
|
|
26
|
+
[[instances.field]]
|
|
27
|
+
name = "sysName"
|
|
28
|
+
oid = "1.3.6.1.2.1.1.5.0"
|
|
29
|
+
is_tag = true
|
|
30
|
+
|
|
31
|
+
[[instances.table]]
|
|
32
|
+
name = "interface"
|
|
33
|
+
inherit_tags = ["sysName"]
|
|
34
|
+
index_as_tag = true
|
|
35
|
+
|
|
36
|
+
[[instances.table.field]]
|
|
37
|
+
name = "ifIndex"
|
|
38
|
+
oid = "1.3.6.1.2.1.2.2.1.1"
|
|
39
|
+
is_tag = true
|
|
40
|
+
|
|
41
|
+
[[instances.table.field]]
|
|
42
|
+
name = "ifName"
|
|
43
|
+
oid = "1.3.6.1.2.1.31.1.1.1.1"
|
|
44
|
+
is_tag = true
|
|
45
|
+
|
|
46
|
+
[[instances.table.field]]
|
|
47
|
+
name = "ifDescr"
|
|
48
|
+
oid = "1.3.6.1.2.1.2.2.1.2"
|
|
49
|
+
is_tag = true
|
|
50
|
+
|
|
51
|
+
[[instances.table.field]]
|
|
52
|
+
name = "ifSpeed"
|
|
53
|
+
oid = "1.3.6.1.2.1.2.2.1.5"
|
|
54
|
+
# is_tag = true
|
|
55
|
+
|
|
56
|
+
[[instances.table.field]]
|
|
57
|
+
name = "ifAdminStatus"
|
|
58
|
+
oid = "1.3.6.1.2.1.2.2.1.7"
|
|
59
|
+
# is_tag = true
|
|
60
|
+
|
|
61
|
+
[[instances.table.field]]
|
|
62
|
+
name = "ifOperStatus"
|
|
63
|
+
oid = "1.3.6.1.2.1.2.2.1.8"
|
|
64
|
+
# is_tag = true
|
|
65
|
+
|
|
66
|
+
[[instances.table.field]]
|
|
67
|
+
name = "ifInDiscards"
|
|
68
|
+
oid = "1.3.6.1.2.1.2.2.1.13"
|
|
69
|
+
|
|
70
|
+
[[instances.table.field]]
|
|
71
|
+
name = "ifInErrors"
|
|
72
|
+
oid = "1.3.6.1.2.1.2.2.1.14"
|
|
73
|
+
|
|
74
|
+
[[instances.table.field]]
|
|
75
|
+
name = "ifOutDiscards"
|
|
76
|
+
oid = "1.3.6.1.2.1.2.2.1.19"
|
|
77
|
+
|
|
78
|
+
[[instances.table.field]]
|
|
79
|
+
name = "ifOutErrors"
|
|
80
|
+
oid = "1.3.6.1.2.1.2.2.1.20"
|
|
81
|
+
|
|
82
|
+
[[instances.table.field]]
|
|
83
|
+
name = "ifAlias"
|
|
84
|
+
oid = "1.3.6.1.2.1.31.1.1.1.18"
|
|
85
|
+
is_tag = true
|
|
86
|
+
|
|
87
|
+
[[instances.table.field]]
|
|
88
|
+
name = "ifHCInOctets"
|
|
89
|
+
oid = "1.3.6.1.2.1.31.1.1.1.6"
|
|
90
|
+
|
|
91
|
+
[[instances.table.field]]
|
|
92
|
+
name = "ifHCOutOctets"
|
|
93
|
+
oid = "1.3.6.1.2.1.31.1.1.1.10"
|
|
94
|
+
|
|
95
|
+
{% endfor %}
|
|
96
|
+
{% endfor %}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{% for instance in instances %}
|
|
2
|
+
{% for agent, detail in instance.items() %}
|
|
3
|
+
[[instances]]
|
|
4
|
+
agents = [
|
|
5
|
+
"udp://{{agent}}"
|
|
6
|
+
]
|
|
7
|
+
{% if detail.version == 2 %}
|
|
8
|
+
version = {{detail.version}}
|
|
9
|
+
community = "{{detail.community}}"
|
|
10
|
+
{% endif %}
|
|
11
|
+
{% if detail.version == 3 %}
|
|
12
|
+
version = {{detail.version}}
|
|
13
|
+
sec_name = "{{detail.sec_name}}"
|
|
14
|
+
auth_protocol = "{{detail.auth_protocol}}"
|
|
15
|
+
auth_password = "{{detail.auth_password}}"
|
|
16
|
+
priv_protocol = "{{detail.priv_protocol}}"
|
|
17
|
+
priv_password = "{{detail.priv_password}}"
|
|
18
|
+
sec_level = "{{detail.sec_level}}"
|
|
19
|
+
{% endif %}
|
|
20
|
+
timeout = "5s"
|
|
21
|
+
retries = 3
|
|
22
|
+
path = ["/usr/share/snmp/mibs"]
|
|
23
|
+
translator = "gosmi"
|
|
24
|
+
max_repetitions = 50
|
|
25
|
+
|
|
26
|
+
[[instances.field]]
|
|
27
|
+
name = "sysName"
|
|
28
|
+
oid = "1.3.6.1.2.1.1.5.0"
|
|
29
|
+
is_tag = true
|
|
30
|
+
|
|
31
|
+
[[instances.field]]
|
|
32
|
+
oid = "1.3.6.1.2.1.1.1.0"
|
|
33
|
+
name = "sysDescr"
|
|
34
|
+
is_tag = true
|
|
35
|
+
|
|
36
|
+
[[instances.field]]
|
|
37
|
+
oid = "1.3.6.1.2.1.1.3.0"
|
|
38
|
+
name = "sysUpTime"
|
|
39
|
+
conversion = "float(2)"
|
|
40
|
+
{% endfor %}
|
|
41
|
+
{% endfor %}
|
pytbox/mail/alimail.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
from datetime import datetime, timedelta
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AliMail:
|
|
11
|
+
def __init__(self, email_address: str=None, client_id: str=None, client_secret: str=None):
|
|
12
|
+
|
|
13
|
+
self.email_address = email_address
|
|
14
|
+
self.client_id = auth['username']
|
|
15
|
+
self.client_secret = auth['password']
|
|
16
|
+
self.base_url = "https://alimail-cn.aliyuncs.com/v2"
|
|
17
|
+
self.headers = {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
"Authorization": f"bearer {self._get_access_token()}"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
def _get_access_token(self):
|
|
23
|
+
current_time = datetime.now()
|
|
24
|
+
# stored_token = mongo_alimail_token.find_one({"token_type": "bearer"})
|
|
25
|
+
if stored_token and stored_token.get('expiration_time'):
|
|
26
|
+
expiration_time = stored_token['expiration_time']
|
|
27
|
+
if current_time < expiration_time:
|
|
28
|
+
return stored_token['access_token']
|
|
29
|
+
else:
|
|
30
|
+
return self._get_access_token_by_request()
|
|
31
|
+
else:
|
|
32
|
+
return self._get_access_token_by_request()
|
|
33
|
+
|
|
34
|
+
def _get_access_token_by_request(self):
|
|
35
|
+
'''
|
|
36
|
+
https://mailhelp.aliyun.com/openapi/index.html#/markdown/authorization.md
|
|
37
|
+
|
|
38
|
+
Raises:
|
|
39
|
+
ValueError: _description_
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
_type_: _description_
|
|
43
|
+
'''
|
|
44
|
+
# 定义接口URL
|
|
45
|
+
interface_url = "https://alimail-cn.aliyuncs.com/oauth2/v2.0/token"
|
|
46
|
+
# 设置请求头,指定内容类型
|
|
47
|
+
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
|
48
|
+
# 准备请求数据
|
|
49
|
+
data = {
|
|
50
|
+
"grant_type": "client_credentials",
|
|
51
|
+
"client_id": self.client_id,
|
|
52
|
+
"client_secret": self.client_secret
|
|
53
|
+
}
|
|
54
|
+
try:
|
|
55
|
+
response = requests.post(interface_url, headers=headers, data=data, timeout=3)
|
|
56
|
+
response_json = response.json()
|
|
57
|
+
current_time = datetime.now()
|
|
58
|
+
data = {
|
|
59
|
+
'token_type': response_json["token_type"],
|
|
60
|
+
'access_token': response_json["access_token"],
|
|
61
|
+
'expires_in': response_json["expires_in"],
|
|
62
|
+
'expiration_time': current_time + timedelta(seconds=response_json["expires_in"])
|
|
63
|
+
}
|
|
64
|
+
mongo_alimail_token.update_one(
|
|
65
|
+
{"token_type": "bearer"},
|
|
66
|
+
{"$set": data},
|
|
67
|
+
upsert=True
|
|
68
|
+
)
|
|
69
|
+
return data.get("access_token")
|
|
70
|
+
except requests.RequestException as e:
|
|
71
|
+
# 处理请求失败异常
|
|
72
|
+
print(f"请求失败:{e}")
|
|
73
|
+
except (KeyError, ValueError) as e:
|
|
74
|
+
# 处理解析响应失败异常
|
|
75
|
+
print(f"解析响应失败: {e}")
|
|
76
|
+
|
|
77
|
+
def list_mail_folders(self):
|
|
78
|
+
response = requests.get(
|
|
79
|
+
url=f"{self.base_url}/users/{self.email_address}/mailFolders",
|
|
80
|
+
headers=self.headers
|
|
81
|
+
)
|
|
82
|
+
return response.json().get('folders')
|
|
83
|
+
|
|
84
|
+
def query_folder_id(self, folder_name: Literal['inbox']='inbox'):
|
|
85
|
+
folders = self.list_mail_folders()
|
|
86
|
+
for folder in folders:
|
|
87
|
+
if folder.get('displayName') == folder_name:
|
|
88
|
+
return folder.get('id')
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def get_mail_detail(self, mail_id: str):
|
|
92
|
+
params = {
|
|
93
|
+
"$select": "body,toRecipients,internetMessageId,internetMessageHeaders"
|
|
94
|
+
}
|
|
95
|
+
response = requests.get(
|
|
96
|
+
url=f"{self.base_url}/users/{self.email_address}/messages/{mail_id}",
|
|
97
|
+
headers=self.headers,
|
|
98
|
+
params=params,
|
|
99
|
+
timeout=3
|
|
100
|
+
)
|
|
101
|
+
return response.json().get('message')
|
|
102
|
+
|
|
103
|
+
def list_mail(self, folder_name: str='inbox', size: int=100):
|
|
104
|
+
folder_id = self.query_folder_id(folder_name=folder_name)
|
|
105
|
+
params = {
|
|
106
|
+
"size": size,
|
|
107
|
+
# "$select": "toRecipients"
|
|
108
|
+
}
|
|
109
|
+
response = requests.get(
|
|
110
|
+
url=f"{self.base_url}/users/{self.email_address}/mailFolders/{folder_id}/messages",
|
|
111
|
+
headers=self.headers,
|
|
112
|
+
params=params,
|
|
113
|
+
timeout=3
|
|
114
|
+
)
|
|
115
|
+
messages = response.json().get("messages")
|
|
116
|
+
for message in messages:
|
|
117
|
+
mail_id = message.get("id")
|
|
118
|
+
detail = self.get_mail_detail(mail_id=mail_id)
|
|
119
|
+
message.update(detail)
|
|
120
|
+
yield message
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
if __name__ == '__main__':
|
|
124
|
+
ali_mail = AliMail()
|
|
125
|
+
# print(ali_mail.query_folder_id())
|
|
126
|
+
# for i in ali_mail.list_mail(size=1):
|
|
127
|
+
# print(i)
|
|
128
|
+
# print(ali_mail.access_token)
|
|
129
|
+
print(ali_mail.get_mail_detail(mail_id='DzzzzzzMeuY'))
|
pytbox/mail/client.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Generator, Literal
|
|
6
|
+
|
|
7
|
+
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
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class MailClient:
|
|
46
|
+
'''
|
|
47
|
+
_summary_
|
|
48
|
+
'''
|
|
49
|
+
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,
|
|
52
|
+
password: str=None
|
|
53
|
+
):
|
|
54
|
+
|
|
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']
|
|
64
|
+
|
|
65
|
+
if '163.com' in send_mail_address:
|
|
66
|
+
self.smtp_address = 'smtp.163.com'
|
|
67
|
+
self.imap_address = 'imap.163.com'
|
|
68
|
+
self.imbox_client = self._create_imbox_object(vendor='163')
|
|
69
|
+
elif 'foxmail.com' in send_mail_address:
|
|
70
|
+
self.smtp_address = 'smtp.qq.com'
|
|
71
|
+
self.imap_address = 'imap.qq.com'
|
|
72
|
+
self.imbox_client = self._create_imbox_object(vendor='qq')
|
|
73
|
+
else:
|
|
74
|
+
raise ValueError(f'不支持的邮箱地址: {send_mail_address}')
|
|
75
|
+
|
|
76
|
+
self.mailbox = MailBox(self.imap_address).login(self.mail_address, self.password)
|
|
77
|
+
|
|
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)
|
|
85
|
+
|
|
86
|
+
def send_mail(self, receiver: list=[], cc: list=['houmingming@tyun.cn'], subject: str='', contents: str='', attachments: list=[], tips: str=None):
|
|
87
|
+
'''
|
|
88
|
+
_summary_
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
receiver (list, optional): _description_. Defaults to [].
|
|
92
|
+
cc (list, optional): _description_. Defaults to ['houmingming@tyun.cn'].
|
|
93
|
+
subject (str, optional): _description_. Defaults to ''.
|
|
94
|
+
contents (str, optional): _description_. Defaults to ''.
|
|
95
|
+
attachments (list, optional): _description_. Defaults to [].
|
|
96
|
+
'''
|
|
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}')
|
|
111
|
+
try:
|
|
112
|
+
if tips:
|
|
113
|
+
contents = contents + '\n' + '<p style="color: red;">本邮件为系统自动发送, 如有问题, 请联系 houmingming@tyun.cn</p>'
|
|
114
|
+
|
|
115
|
+
contents = contents + email_signature
|
|
116
|
+
yag.send(to=receiver, cc=cc, subject=subject, contents=contents, attachments=attachments)
|
|
117
|
+
log.info('发送成功!!!')
|
|
118
|
+
return True
|
|
119
|
+
except Exception as e:
|
|
120
|
+
log.error(f'发送失败, 报错: {e}')
|
|
121
|
+
return False
|
|
122
|
+
|
|
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
|
|
144
|
+
|
|
145
|
+
yield MailDetail(
|
|
146
|
+
uid=msg.uid,
|
|
147
|
+
send_from=msg.from_,
|
|
148
|
+
send_to=msg.to,
|
|
149
|
+
date=msg.date,
|
|
150
|
+
cc=msg.cc,
|
|
151
|
+
subject=msg.subject,
|
|
152
|
+
body_plain=msg.text,
|
|
153
|
+
body_html=msg.html,
|
|
154
|
+
has_attachments=is_has_attachments,
|
|
155
|
+
attachment=attachment_list
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def mark_as_read(self, uid):
|
|
159
|
+
"""
|
|
160
|
+
标记邮件为已读。
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
uid (str): 邮件的唯一标识符
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
# 使用 imap_tools 的 flag 方法标记邮件为已读
|
|
167
|
+
# 第一个参数是 uid,第二个参数是要设置的标志,第三个参数 True 表示添加标志
|
|
168
|
+
self.mailbox.flag(uid, [MailMessageFlags.SEEN], True)
|
|
169
|
+
# log.info(f'标注邮件{uid}为已读')
|
|
170
|
+
except Exception as e:
|
|
171
|
+
log.error(f'标记邮件{uid}为已读失败: {e}')
|
|
172
|
+
raise
|
|
173
|
+
|
|
174
|
+
def delete(self, uid):
|
|
175
|
+
"""
|
|
176
|
+
删除邮件。
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
uid (str): 邮件的唯一标识符
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
# 使用 imap_tools 的 delete 方法删除邮件
|
|
183
|
+
self.mailbox.delete(uid)
|
|
184
|
+
log.info(f'删除邮件{uid}')
|
|
185
|
+
except Exception as e:
|
|
186
|
+
log.error(f'删除邮件{uid}失败: {e}')
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
def move(self, uid: str, destination_folder: str) -> None:
|
|
190
|
+
"""
|
|
191
|
+
移动邮件到指定文件夹。
|
|
192
|
+
|
|
193
|
+
注意:部分 IMAP 服务商(如 QQ 邮箱)在移动邮件时,实际上是"复制到目标文件夹并从原文件夹删除",
|
|
194
|
+
这会导致邮件在原文件夹中消失,表现为"被删除"。但邮件会在目标文件夹中存在,并未彻底丢失。
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
uid (str): 邮件的唯一标识符。
|
|
198
|
+
destination_folder (str): 目标文件夹名称。
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
None
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
Exception: 移动过程中底层 imap 库抛出的异常。
|
|
205
|
+
"""
|
|
206
|
+
try:
|
|
207
|
+
# 使用 imap_tools 的 move 方法移动邮件
|
|
208
|
+
self.mailbox.move(uid, destination_folder)
|
|
209
|
+
log.info(f'邮件{uid}已移动到{destination_folder}')
|
|
210
|
+
except Exception as e:
|
|
211
|
+
log.error(f'移动邮件{uid}到{destination_folder}失败: {e}')
|
|
212
|
+
raise
|
|
213
|
+
|
|
214
|
+
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')
|
pytbox/network/meraki.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
import requests
|
|
5
|
+
from ..utils.response import ReturnResponse
|
|
6
|
+
|
|
7
|
+
class Meraki:
|
|
8
|
+
'''
|
|
9
|
+
Meraki Client
|
|
10
|
+
'''
|
|
11
|
+
def __init__(self, api_key: str=None, organization_id: str=None, timeout: int=10):
|
|
12
|
+
if not api_key:
|
|
13
|
+
raise ValueError("api_key is required")
|
|
14
|
+
if not organization_id:
|
|
15
|
+
raise ValueError("organization_id is required")
|
|
16
|
+
self.base_url = 'https://api.meraki.cn/api/v1'
|
|
17
|
+
self.headers = {
|
|
18
|
+
"Authorization": f"Bearer {api_key}",
|
|
19
|
+
"Accept": "application/json"
|
|
20
|
+
}
|
|
21
|
+
self.organization_id = organization_id
|
|
22
|
+
self.timeout = timeout
|
|
23
|
+
|
|
24
|
+
def get_networks(self, tags: list[str]=None) -> ReturnResponse:
|
|
25
|
+
'''
|
|
26
|
+
https://developer.cisco.com/meraki/api-v1/get-organization-networks/
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
tags (list): PROD STG NSO
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
list: _description_
|
|
33
|
+
'''
|
|
34
|
+
params = {}
|
|
35
|
+
if tags:
|
|
36
|
+
params['tags[]'] = tags
|
|
37
|
+
|
|
38
|
+
r = requests.get(
|
|
39
|
+
f"{self.base_url}/organizations/{self.organization_id}/networks",
|
|
40
|
+
headers=self.headers,
|
|
41
|
+
params=params,
|
|
42
|
+
timeout=self.timeout
|
|
43
|
+
)
|
|
44
|
+
if r.status_code == 200:
|
|
45
|
+
return ReturnResponse(code=0, msg=f"获取到 {len(r.json())} 个网络", data=r.json())
|
|
46
|
+
return ReturnResponse(code=1, msg=f"获取网络失败: {r.status_code} {r.text}")
|
|
47
|
+
|
|
48
|
+
def get_network_id_by_name(self, name: str) -> str | None:
|
|
49
|
+
'''
|
|
50
|
+
name 必须是唯一值,否则仅反馈第一个匹配到的 network
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
name (str): 网络名称, 是包含的关系, 例如实际 name 是 main office, 传入的 name 可以是 office
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
str | None: 网络ID
|
|
57
|
+
'''
|
|
58
|
+
r = self.get_networks()
|
|
59
|
+
if r.code == 0:
|
|
60
|
+
for network in r.data:
|
|
61
|
+
if name in network['name']:
|
|
62
|
+
return network['id']
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def get_devices(self, network_ids: Any = []) -> ReturnResponse:
|
|
66
|
+
'''
|
|
67
|
+
获取设备信息
|
|
68
|
+
https://developer.cisco.com/meraki/api-v1/get-organization-inventory-devices/
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
network_ids (list 或 str, 可选): 可以传入网络ID的列表,也可以直接传入单个网络ID字符串。默认为空列表,表示不指定网络ID。
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
返回示例(部分敏感信息已隐藏):
|
|
75
|
+
[
|
|
76
|
+
{
|
|
77
|
+
'mac': '00:00:00:00:00:00',
|
|
78
|
+
'serial': 'Q3AL-****-****',
|
|
79
|
+
'name': 'OFFICE-AP01',
|
|
80
|
+
'model': 'MR44',
|
|
81
|
+
'networkId': 'L_***************0076',
|
|
82
|
+
'orderNumber': None,
|
|
83
|
+
'claimedAt': '2025-02-26T02:20:00.853251Z',
|
|
84
|
+
'tags': ['MR44'],
|
|
85
|
+
'productType': 'wireless',
|
|
86
|
+
'countryCode': 'CN',
|
|
87
|
+
'details': []
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
'''
|
|
91
|
+
|
|
92
|
+
params = {}
|
|
93
|
+
if network_ids:
|
|
94
|
+
if isinstance(network_ids, str):
|
|
95
|
+
params['networkIds[]'] = [network_ids]
|
|
96
|
+
else:
|
|
97
|
+
params['networkIds[]'] = network_ids
|
|
98
|
+
|
|
99
|
+
r = requests.get(
|
|
100
|
+
f"{self.base_url}/organizations/{self.organization_id}/inventory/devices",
|
|
101
|
+
headers=self.headers,
|
|
102
|
+
params=params,
|
|
103
|
+
timeout=self.timeout
|
|
104
|
+
)
|
|
105
|
+
if r.status_code == 200:
|
|
106
|
+
return ReturnResponse(code=0, msg=f"获取到 {len(r.json())} 个设备", data=r.json())
|
|
107
|
+
return ReturnResponse(code=1, msg=f"获取设备失败: {r.status_code} {r.text}")
|
|
108
|
+
|
|
109
|
+
def get_device_detail(self, serial: str) -> ReturnResponse:
|
|
110
|
+
'''
|
|
111
|
+
获取指定序列号(serial)的 Meraki 设备详细信息
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
serial (str): 设备的序列号
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
ReturnResponse:
|
|
118
|
+
code: 0 表示成功,1 表示失败,3 表示设备未添加
|
|
119
|
+
msg: 结果说明
|
|
120
|
+
data: 设备详细信息,示例(部分敏感信息已隐藏):
|
|
121
|
+
{
|
|
122
|
+
'lat': 1.1,
|
|
123
|
+
'lng': 2.2,
|
|
124
|
+
'address': '(1.1, 2.2)',
|
|
125
|
+
'serial': 'Q3AL-****-****',
|
|
126
|
+
'mac': '00:00:00:00:00:00',
|
|
127
|
+
'lanIp': '10.1.1.1',
|
|
128
|
+
'tags': ['MR44'],
|
|
129
|
+
'url': 'https://n3.meraki.cn/xxx',
|
|
130
|
+
'networkId': '00000',
|
|
131
|
+
'name': 'OFFICE-AP01',
|
|
132
|
+
'details': [],
|
|
133
|
+
'model': 'MR44',
|
|
134
|
+
'firmware': 'wireless-31-1-6',
|
|
135
|
+
'floorPlanId': '00000'
|
|
136
|
+
}
|
|
137
|
+
'''
|
|
138
|
+
r = requests.get(
|
|
139
|
+
f"{self.base_url}/devices/{serial}",
|
|
140
|
+
headers=self.headers,
|
|
141
|
+
timeout=self.timeout
|
|
142
|
+
)
|
|
143
|
+
if r.status_code == 200:
|
|
144
|
+
return ReturnResponse(code=0, msg=f"获取设备详情成功: {r.json()}", data=r.json())
|
|
145
|
+
elif r.status_code == 404:
|
|
146
|
+
return ReturnResponse(code=3, msg=f"设备 {serial} 还未添加过", data=None)
|
|
147
|
+
return ReturnResponse(code=1, msg=f"获取设备详情失败: {r.status_code} - {r.text}", data=None)
|
|
148
|
+
|
|
149
|
+
def get_device_availabilities(self, network_id: str=None,
|
|
150
|
+
status: Literal['online', 'offline', 'dormant', 'alerting']=None,
|
|
151
|
+
serial: str=None) -> ReturnResponse:
|
|
152
|
+
params = {}
|
|
153
|
+
|
|
154
|
+
if status:
|
|
155
|
+
params["statuses[]"] = status
|
|
156
|
+
|
|
157
|
+
if serial:
|
|
158
|
+
params["serials[]"] = serial
|
|
159
|
+
|
|
160
|
+
if network_id:
|
|
161
|
+
params["networkIds[]"] = network_id
|
|
162
|
+
|
|
163
|
+
r = requests.get(
|
|
164
|
+
url=f"{self.base_url}/organizations/{self.organization_id}/devices/availabilities",
|
|
165
|
+
headers=self.headers,
|
|
166
|
+
params=params,
|
|
167
|
+
timeout=self.timeout
|
|
168
|
+
)
|
|
169
|
+
return ReturnResponse(code=0, msg=f"获取设备健康状态成功", data=r.json())
|
pytbox/win/ad.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from ldap3 import Server, Connection, ALL, SUBTREE, MODIFY_REPLACE, SUBTREE, MODIFY_ADD, MODIFY_DELETE
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ADClient:
|
|
8
|
+
'''
|
|
9
|
+
_summary_
|
|
10
|
+
'''
|
|
11
|
+
def __init__(self, server, base_dn, username, password):
|
|
12
|
+
self.server = Server(server, get_info=ALL)
|
|
13
|
+
self.conn = Connection(self.server, user=username, password=password, auto_bind=True)
|
|
14
|
+
self.base_dn = base_dn
|
|
15
|
+
|
|
16
|
+
def list_user(self):
|
|
17
|
+
'''
|
|
18
|
+
查询所有用户
|
|
19
|
+
|
|
20
|
+
Yields:
|
|
21
|
+
dict: 返回的是生成器, 字典类型
|
|
22
|
+
'''
|
|
23
|
+
# 搜索过滤条件
|
|
24
|
+
secarch_filter = '(objectCategory=person)' # 过滤所有用户
|
|
25
|
+
# SEARCH_ATTRIBUTES = ['cn', 'sAMAccountName', 'mail', 'userPrincipalName'] # 需要的用户属性
|
|
26
|
+
search_attributes = ["*"] # 需要的用户属性
|
|
27
|
+
# 搜索用户
|
|
28
|
+
if self.conn.search(search_base=self.base_dn, search_filter=secarch_filter, search_scope=SUBTREE, attributes=search_attributes):
|
|
29
|
+
for entry in self.conn.entries:
|
|
30
|
+
yield {k: v[0] if isinstance(v, list) else v for k, v in entry.entry_attributes_as_dict.items()}
|