pytbox 0.0.7__py3-none-any.whl → 0.3.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.

Files changed (55) hide show
  1. pytbox/alert/alert_handler.py +27 -7
  2. pytbox/alert/ping.py +0 -1
  3. pytbox/alicloud/sls.py +9 -6
  4. pytbox/base.py +74 -1
  5. pytbox/categraf/build_config.py +95 -32
  6. pytbox/categraf/instances.toml +39 -0
  7. pytbox/categraf/jinja2/__init__.py +6 -0
  8. pytbox/categraf/jinja2/input.cpu/cpu.toml.j2 +5 -0
  9. pytbox/categraf/jinja2/input.disk/disk.toml.j2 +11 -0
  10. pytbox/categraf/jinja2/input.diskio/diskio.toml.j2 +6 -0
  11. pytbox/categraf/jinja2/input.dns_query/dns_query.toml.j2 +12 -0
  12. pytbox/categraf/jinja2/input.http_response/http_response.toml.j2 +9 -0
  13. pytbox/categraf/jinja2/input.mem/mem.toml.j2 +5 -0
  14. pytbox/categraf/jinja2/input.net/net.toml.j2 +11 -0
  15. pytbox/categraf/jinja2/input.net_response/net_response.toml.j2 +9 -0
  16. pytbox/categraf/jinja2/input.ping/ping.toml.j2 +11 -0
  17. pytbox/categraf/jinja2/input.prometheus/prometheus.toml.j2 +12 -0
  18. pytbox/categraf/jinja2/input.snmp/cisco_interface.toml.j2 +96 -0
  19. pytbox/categraf/jinja2/input.snmp/cisco_system.toml.j2 +41 -0
  20. pytbox/categraf/jinja2/input.snmp/h3c_interface.toml.j2 +96 -0
  21. pytbox/categraf/jinja2/input.snmp/h3c_system.toml.j2 +41 -0
  22. pytbox/categraf/jinja2/input.snmp/huawei_interface.toml.j2 +96 -0
  23. pytbox/categraf/jinja2/input.snmp/huawei_system.toml.j2 +41 -0
  24. pytbox/categraf/jinja2/input.snmp/ruijie_interface.toml.j2 +96 -0
  25. pytbox/categraf/jinja2/input.snmp/ruijie_system.toml.j2 +41 -0
  26. pytbox/categraf/jinja2/input.vsphere/vsphere.toml.j2 +211 -0
  27. pytbox/cli/commands/vm.py +22 -0
  28. pytbox/cli/main.py +2 -0
  29. pytbox/database/mongo.py +1 -1
  30. pytbox/database/victoriametrics.py +331 -40
  31. pytbox/excel.py +64 -0
  32. pytbox/feishu/endpoints.py +6 -6
  33. pytbox/log/logger.py +29 -12
  34. pytbox/mail/alimail.py +142 -0
  35. pytbox/mail/client.py +171 -0
  36. pytbox/mail/mail_detail.py +30 -0
  37. pytbox/mingdao.py +164 -0
  38. pytbox/network/meraki.py +537 -0
  39. pytbox/notion.py +731 -0
  40. pytbox/pyjira.py +612 -0
  41. pytbox/utils/cronjob.py +79 -0
  42. pytbox/utils/env.py +2 -2
  43. pytbox/utils/load_config.py +67 -21
  44. pytbox/utils/load_vm_devfile.py +45 -0
  45. pytbox/utils/richutils.py +11 -1
  46. pytbox/utils/timeutils.py +15 -57
  47. pytbox/vmware.py +120 -0
  48. pytbox/win/ad.py +30 -0
  49. {pytbox-0.0.7.dist-info → pytbox-0.3.1.dist-info}/METADATA +7 -4
  50. pytbox-0.3.1.dist-info/RECORD +72 -0
  51. pytbox/utils/ping_checker.py +0 -1
  52. pytbox-0.0.7.dist-info/RECORD +0 -39
  53. {pytbox-0.0.7.dist-info → pytbox-0.3.1.dist-info}/WHEEL +0 -0
  54. {pytbox-0.0.7.dist-info → pytbox-0.3.1.dist-info}/entry_points.txt +0 -0
  55. {pytbox-0.0.7.dist-info → pytbox-0.3.1.dist-info}/top_level.txt +0 -0
pytbox/mail/alimail.py ADDED
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Literal
4
+ from datetime import datetime, timedelta
5
+
6
+ import requests
7
+ from ..utils.response import ReturnResponse
8
+ from .mail_detail import MailDetail
9
+ from ..utils.timeutils import TimeUtils
10
+
11
+
12
+
13
+ class AliMail:
14
+ '''
15
+ _summary_
16
+ '''
17
+ def __init__(self, mail_address: str=None, client_id: str=None, client_secret: str=None, timeout: int=3):
18
+ self.email_address = mail_address
19
+ self.client_id = client_id
20
+ self.client_secret = client_secret
21
+ self.base_url = "https://alimail-cn.aliyuncs.com/v2"
22
+ self.timeout = timeout
23
+ self.headers = {
24
+ "Content-Type": "application/json",
25
+ "Authorization": f"bearer {self._get_access_token_by_request()}"
26
+ }
27
+ self.session = requests.Session()
28
+ self.session.headers.update(self.headers)
29
+
30
+
31
+ def _get_access_token_by_request(self):
32
+ '''
33
+ https://mailhelp.aliyun.com/openapi/index.html#/markdown/authorization.md
34
+
35
+ Raises:
36
+ ValueError: _description_
37
+
38
+ Returns:
39
+ _type_: _description_
40
+ '''
41
+ # 定义接口URL
42
+ interface_url = "https://alimail-cn.aliyuncs.com/oauth2/v2.0/token"
43
+ # 设置请求头,指定内容类型
44
+ headers = {'Content-Type': 'application/x-www-form-urlencoded'}
45
+ # 准备请求数据
46
+ data = {
47
+ "grant_type": "client_credentials",
48
+ "client_id": self.client_id,
49
+ "client_secret": self.client_secret
50
+ }
51
+ try:
52
+ response = requests.post(interface_url, headers=headers, data=data, timeout=self.timeout)
53
+ response_json = response.json()
54
+ current_time = datetime.now()
55
+ data = {
56
+ 'token_type': response_json["token_type"],
57
+ 'access_token': response_json["access_token"],
58
+ 'expires_in': response_json["expires_in"],
59
+ 'expiration_time': current_time + timedelta(seconds=response_json["expires_in"])
60
+ }
61
+ return data.get("access_token")
62
+ except requests.RequestException as e:
63
+ # 处理请求失败异常
64
+ raise e
65
+ except (KeyError, ValueError) as e:
66
+ # 处理解析响应失败异常
67
+ raise e
68
+
69
+ def get_mail_folders(self):
70
+ response = self.session.get(
71
+ url=f"{self.base_url}/users/{self.email_address}/mailFolders",
72
+ headers=self.headers
73
+ )
74
+ return response.json().get('folders')
75
+
76
+ def get_folder_id(self, folder_name: Literal['inbox']='inbox'):
77
+ folders = self.get_mail_folders()
78
+ for folder in folders:
79
+ if folder.get('displayName') == folder_name:
80
+ return folder.get('id')
81
+ return None
82
+
83
+ def get_mail_detail(self, mail_id: str):
84
+ params = {
85
+ "$select": "body,toRecipients,internetMessageId,internetMessageHeaders"
86
+ }
87
+ response = self.session.get(
88
+ url=f"{self.base_url}/users/{self.email_address}/messages/{mail_id}",
89
+ headers=self.headers,
90
+ params=params,
91
+ timeout=3
92
+ )
93
+ return response.json().get('message')
94
+
95
+ def get_mail_list(self, folder_name: str='inbox', size: int=100):
96
+ folder_id = self.get_folder_id(folder_name=folder_name)
97
+ params = {
98
+ "size": size,
99
+ # "$select": "toRecipients"
100
+ }
101
+ response = self.session.get(
102
+ url=f"{self.base_url}/users/{self.email_address}/mailFolders/{folder_id}/messages",
103
+ headers=self.headers,
104
+ params=params,
105
+ timeout=3
106
+ )
107
+ messages = response.json().get("messages")
108
+ sent_to_list = []
109
+ for message in messages:
110
+ mail_id = message.get("id")
111
+ detail = self.get_mail_detail(mail_id=mail_id)
112
+
113
+ for to_recipient in detail.get('toRecipients'):
114
+ sent_to_list.append(to_recipient.get('email'))
115
+
116
+ yield MailDetail(
117
+ uid=message.get('id'),
118
+ sent_from=message.get('from').get('email'),
119
+ sent_to=sent_to_list,
120
+ date=TimeUtils.convert_str_to_datetime(time_str=message.get('sentDateTime'), app='alimail'),
121
+ cc="",
122
+ subject=message.get('subject'),
123
+ body_plain=message.get('summary'),
124
+ body_html=""
125
+ )
126
+
127
+ def move(self, uid: str, destination_folder: str) -> ReturnResponse:
128
+ params = {
129
+ "ids": [uid],
130
+ "destinationFolderId": self.get_folder_id(destination_folder)
131
+ }
132
+ r = self.session.post(
133
+ url=f"{self.base_url}/users/{self.email_address}/messages/move",
134
+ params=params
135
+ )
136
+ if r.status_code == 200:
137
+ return ReturnResponse(code=0, msg=f'邮件移动到 {destination_folder} 成功', data=None)
138
+ else:
139
+ return ReturnResponse(code=1, msg=f'邮件移动到 {destination_folder} 失败', data=r.json())
140
+
141
+ if __name__ == '__main__':
142
+ ali_mail = AliMail()
pytbox/mail/client.py ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from contextlib import contextmanager
5
+ import yagmail
6
+ from imap_tools import MailBox, MailMessageFlags, AND
7
+ from .mail_detail import MailDetail
8
+ from ..utils.response import ReturnResponse
9
+
10
+
11
+ class MailClient:
12
+ '''
13
+ _summary_
14
+ '''
15
+ def __init__(self,
16
+ mail_address: str=None,
17
+ password: str=None
18
+ ):
19
+
20
+ self.mail_address = mail_address
21
+ self.password = password
22
+
23
+ if '163.com' in mail_address:
24
+ self.smtp_address = 'smtp.163.com'
25
+ self.imap_address = 'imap.163.com'
26
+ # self.imbox_client = self._create_imbox_object()
27
+ elif 'foxmail.com' in mail_address:
28
+ self.smtp_address = 'smtp.qq.com'
29
+ self.imap_address = 'imap.qq.com'
30
+
31
+ elif 'mail' in mail_address and 'cn' in mail_address:
32
+ self.smtp_address = "smtpdm.aliyun.com"
33
+ self.imap_address = ""
34
+
35
+ else:
36
+ raise ValueError(f'不支持的邮箱地址: {mail_address}')
37
+
38
+ @contextmanager
39
+ def get_mailbox(self, readonly=False):
40
+ """
41
+ 创建并返回一个已登录的 MailBox 上下文管理器。
42
+
43
+ 使用方式:
44
+ with self.get_mailbox() as mailbox:
45
+ # 使用 mailbox 进行操作
46
+ pass
47
+
48
+ Args:
49
+ readonly (bool): 是否以只读模式打开邮箱,只读模式下不会将邮件标记为已读
50
+
51
+ Yields:
52
+ MailBox: 已登录的邮箱对象
53
+ """
54
+ mailbox = MailBox(self.imap_address).login(self.mail_address, self.password)
55
+ if readonly:
56
+ # 以只读模式选择收件箱,防止邮件被标记为已读
57
+ mailbox.folder.set('INBOX', readonly=True)
58
+ try:
59
+ yield mailbox
60
+ finally:
61
+ mailbox.logout()
62
+
63
+ def send_mail(self, receiver: list=None, cc: list=None, subject: str='', contents: str='', attachments: list=None, tips: str=None):
64
+ '''
65
+ _summary_
66
+
67
+ Args:
68
+ receiver (list, optional): _description_. Defaults to None.
69
+ cc (list, optional): _description_. Defaults to None.
70
+ subject (str, optional): _description_. Defaults to ''.
71
+ contents (str, optional): _description_. Defaults to ''.
72
+ attachments (list, optional): _description_. Defaults to None.
73
+ '''
74
+ with yagmail.SMTP(user=self.mail_address, password=self.password, port=465, host=self.smtp_address) as yag:
75
+ try:
76
+ if tips:
77
+ contents = contents + '\n' + '<p style="color: red;">本邮件为系统自动发送</p>'
78
+ yag.send(to=receiver, cc=cc, subject=subject, contents=contents, attachments=attachments)
79
+ return True
80
+ except Exception as e:
81
+ return False
82
+
83
+ def get_mail_list(self, seen: bool=False, readonly: bool=True):
84
+ '''
85
+ 获取邮件
86
+
87
+ Args:
88
+ seen (bool, optional): 默认获取未读邮件, 如果为 True, 则获取已读邮件
89
+ readonly (bool, optional): 是否以只读模式获取邮件,默认为 True,防止邮件被标记为已读
90
+
91
+ Yields:
92
+ MailDetail: 邮件详情
93
+ '''
94
+ with self.get_mailbox(readonly=readonly) as mailbox:
95
+ for msg in mailbox.fetch(AND(seen=seen)):
96
+ yield MailDetail(
97
+ uid=msg.uid,
98
+ sent_from=msg.from_,
99
+ sent_to=msg.to,
100
+ date=msg.date,
101
+ cc=msg.cc,
102
+ subject=msg.subject,
103
+ body_plain=msg.text,
104
+ body_html=msg.html
105
+ )
106
+
107
+ def mark_as_read(self, uid):
108
+ """
109
+ 标记邮件为已读。
110
+
111
+ Args:
112
+ uid (str): 邮件的唯一标识符
113
+ """
114
+ try:
115
+ with self.get_mailbox() as mailbox:
116
+ # 使用 imap_tools 的 flag 方法标记邮件为已读
117
+ # 第一个参数是 uid,第二个参数是要设置的标志,第三个参数 True 表示添加标志
118
+ mailbox.flag(uid, [MailMessageFlags.SEEN], True)
119
+ except Exception as e:
120
+ return ReturnResponse(code=1, msg='邮件删除失败', data=e)
121
+
122
+ def delete(self, uid):
123
+ """
124
+ 删除邮件。
125
+
126
+ Args:
127
+ uid (str): 邮件的唯一标识符
128
+ """
129
+ try:
130
+ with self.get_mailbox() as mailbox:
131
+ # 使用 imap_tools 的 delete 方法删除邮件
132
+ mailbox.delete(uid)
133
+ # log.info(f'删除邮件{uid}')
134
+ except Exception as e:
135
+ # log.error(f'删除邮件{uid}失败: {e}')
136
+ raise
137
+
138
+ def move(self, uid: str, destination_folder: str) -> ReturnResponse:
139
+ """
140
+ 移动邮件到指定文件夹。
141
+
142
+ 注意:部分 IMAP 服务商(如 QQ 邮箱)在移动邮件时,实际上是"复制到目标文件夹并从原文件夹删除",
143
+ 这会导致邮件在原文件夹中消失,表现为"被删除"。但邮件会在目标文件夹中存在,并未彻底丢失。
144
+
145
+ Args:
146
+ uid (str): 邮件的唯一标识符。
147
+ destination_folder (str): 目标文件夹名称。
148
+
149
+ Returns:
150
+ ReturnResponse: 移动邮件结果
151
+
152
+ Raises:
153
+ Exception: 移动过程中底层 imap 库抛出的异常。
154
+ """
155
+ try:
156
+ with self.get_mailbox() as mailbox:
157
+ # 使用 imap_tools 的 move 方法移动邮件
158
+ mailbox.move(uid, destination_folder)
159
+ return ReturnResponse(code=0, msg=f'邮件 {uid} 移动到 {destination_folder} 成功', data=None)
160
+ except Exception as e:
161
+ return ReturnResponse(code=1, msg=f'邮件 {uid} 移动到 {destination_folder} 失败', data=e)
162
+
163
+ def get_folder_list(self):
164
+ '''
165
+ 获取文件夹列表
166
+ '''
167
+ with self.get_mailbox() as mailbox:
168
+ return ReturnResponse(code=0, msg='获取文件夹列表成功', data=mailbox.folder.list())
169
+
170
+ if __name__ == '__main__':
171
+ pass
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ from dataclasses import dataclass
5
+
6
+ @dataclass
7
+ class MailDetail:
8
+ """
9
+ 邮件详情数据类。
10
+
11
+ Attributes:
12
+ uid: 邮件唯一标识符
13
+ send_from: 发件人邮箱地址
14
+ send_to: 收件人邮箱地址列表
15
+ cc: 抄送人邮箱地址列表
16
+ subject: 邮件主题
17
+ body_plain: 纯文本正文
18
+ body_html: HTML格式正文
19
+ attachment: 附件完整保存路径列表
20
+ """
21
+ uid: str=None
22
+ sent_from: str=None
23
+ sent_to: list=None
24
+ date: str=None
25
+ cc: list=None
26
+ subject: str=None
27
+ body_plain: str=None
28
+ body_html: str=None
29
+ attachment: list=None
30
+ has_attachments: bool=False
pytbox/mingdao.py ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from typing import Literal, Type
4
+
5
+ import requests
6
+
7
+ from .utils.response import ReturnResponse
8
+
9
+
10
+ class Mingdao:
11
+ '''
12
+ _summary_
13
+ '''
14
+ def __init__(self, app_key: str=None, sign: str=None, timeout: int=5):
15
+ self.base_url = "https://api.mingdao.com"
16
+ self.headers = {
17
+ 'Content-Type': 'application/json;charset=UTF-8',
18
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36'
19
+ }
20
+ self.timeout = timeout
21
+ self.app_key = app_key
22
+ self.sign = sign
23
+
24
+ def _build_api_request(self, api_url: str, method: Literal['GET', 'POST'], params: dict=None, body: dict=None, api_version: Literal['v1', 'v2']='v2'):
25
+ body['appKey'] = self.app_key
26
+ body['sign'] = self.sign
27
+ if not api_url.startswith('/'):
28
+ url = f'{self.base_url}/{api_version}/{api_url}'
29
+ else:
30
+ url = f'{self.base_url}/{api_version}{api_url}'
31
+ params = {
32
+ "appKey": self.app_key,
33
+ "sign": self.sign,
34
+ }
35
+ return requests.request(method, url, params=params, headers=self.headers, json=body, timeout=self.timeout)
36
+
37
+ def get_app_info(self) -> ReturnResponse:
38
+ '''
39
+ _summary_
40
+
41
+ Returns:
42
+ ReturnResponse: _description_
43
+ '''
44
+ r = self._build_api_request(api_url='/open/app/get', method='GET', body={}, api_version='v1')
45
+ return ReturnResponse(code=0, msg='获取应用信息成功', data=r.json())
46
+
47
+ def get_work_sheet_info(self, worksheet_id: str=None, table_name: str=None, worksheet_name: str=None):
48
+ if worksheet_name:
49
+ worksheet_id = self.get_work_sheet_id_by_name(table_name=table_name, worksheet_name=worksheet_name)
50
+
51
+ r = self._build_api_request(
52
+ api_url='open/worksheet/getWorksheetInfo',
53
+ method='POST',
54
+ body={
55
+ "worksheetId": worksheet_id,
56
+ }
57
+ )
58
+ return r.json()
59
+
60
+ def get_project_info(self, worksheet_id: str, keywords: str):
61
+ r = self._build_api_request(
62
+ api_url='open/worksheet/getFilterRows',
63
+ method='POST',
64
+ body={
65
+ "pageIndex": 1,
66
+ "pageSize": 100,
67
+ "worksheetId": worksheet_id,
68
+ "keyWords": keywords,
69
+ }
70
+ )
71
+ return r.json()
72
+
73
+ def get_work_sheet_id_by_name(self, table_name: str, worksheet_name: str, child_section: bool=False):
74
+ r = self.get_app_info()
75
+ for i in r.data['data']['sections']:
76
+ if table_name == i['name']:
77
+ if child_section:
78
+ for child in i['childSections'][0]['items']:
79
+ if child['name'] == worksheet_name:
80
+ return child['id']
81
+ else:
82
+ for item in i['items']:
83
+ if item['name'] == worksheet_name:
84
+ return item['id']
85
+
86
+ def get_control_id(self, table_name: str=None, worksheet_name: str=None, control_name: str=None):
87
+ r = self.get_work_sheet_info(table_name=table_name, worksheet_name=worksheet_name)
88
+ for control in r['data']['controls']:
89
+ if control['controlName'] == control_name:
90
+ return control['controlId']
91
+ return None
92
+
93
+ def get_value(self, table_name: str=None, worksheet_name: str=None, control_name: str=None, value_name: str=None):
94
+ control_id = self.get_control_id(table_name=table_name, worksheet_name=worksheet_name, control_name=control_name)
95
+
96
+ r = self._build_api_request(
97
+ api_url='open/worksheet/getFilterRows',
98
+ method='POST',
99
+ body={
100
+ "pageIndex": 1,
101
+ "pageSize": 100,
102
+ "worksheetId": self.get_work_sheet_id_by_name(table_name=table_name, worksheet_name=worksheet_name),
103
+ # "filters": filters
104
+ }
105
+ )
106
+ for row in r.json()['data']['rows']:
107
+ if row[control_id] == value_name:
108
+ return row['rowid']
109
+
110
+ def get_work_record(self,
111
+ worksheet_id: str=None,
112
+ project_control_id: str=None,
113
+ project_value: str=None,
114
+ complete_date_control_id: str=None,
115
+ complete_date: Literal['Today', '上个月', 'Last7Day', 'Last30Day']=None,
116
+ parse_control_id: bool=False,
117
+ page_size: int=100,
118
+ ):
119
+
120
+ filters = []
121
+ if project_value:
122
+ filters.append({
123
+ "controlId": project_control_id,
124
+ "dataType": 29,
125
+ "spliceType": 1,
126
+ "filterType": 24,
127
+ "dateRange": 0,
128
+ "dateRangeType": 0,
129
+ "value": "",
130
+ "values": [
131
+ project_value
132
+ ],
133
+ "minValue": "",
134
+ "maxValue": ""
135
+ })
136
+
137
+ if complete_date:
138
+ if complete_date == '上个月':
139
+ data_range = 8
140
+ elif complete_date == 'Today':
141
+ data_range = 1
142
+ elif complete_date == 'Last7Day':
143
+ data_range = 21
144
+ else:
145
+ data_range = 1
146
+
147
+ filters.append({
148
+ "controlId": complete_date_control_id,
149
+ "dataType": 15,
150
+ "spliceType": 1,
151
+ "filterType": 17,
152
+ "dateRange": data_range,
153
+ })
154
+ r = self._build_api_request(
155
+ api_url='open/worksheet/getFilterRows',
156
+ method='POST',
157
+ body={
158
+ "pageIndex": 1,
159
+ "pageSize": page_size,
160
+ "worksheetId": worksheet_id,
161
+ "filters": filters
162
+ }
163
+ )
164
+ return r.json()