pixelarraylib 1.1.7__tar.gz → 1.1.8__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.
- {pixelarraylib-1.1.7/pixelarraylib.egg-info → pixelarraylib-1.1.8}/PKG-INFO +1 -1
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/__init__.py +1 -1
- pixelarraylib-1.1.8/pixelarraylib/monitor/feishu.py +573 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/scripts/__init__.py +1 -1
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8/pixelarraylib.egg-info}/PKG-INFO +1 -1
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pyproject.toml +1 -1
- pixelarraylib-1.1.7/pixelarraylib/monitor/feishu.py +0 -275
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/LICENSE +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/MANIFEST.in +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/README.md +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/__main__.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/__init__.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/acr.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/aliyun_email.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/billing.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/content_scanner.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/domain.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/eci.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/ecs.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/eip.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/fc.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/oss.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/sms.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/aliyun/sts.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/db_utils/mysql.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/db_utils/redis.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/decorators/__init__.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/decorators/decorators.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/gitlab/__init__.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/gitlab/code_analyzer.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/gitlab/pypi_package_manager.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/monitor/__init__.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/scripts/build_website.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/scripts/collect_code_to_txt.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/scripts/create_test_case_files.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/scripts/nginx_proxy_to_ecs.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/scripts/remove_empty_lines.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/scripts/tson_convert.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/system/__init__.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/system/common.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/system/cron_manager.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/system/tson.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib/utils/name_generator.py +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib.egg-info/SOURCES.txt +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib.egg-info/dependency_links.txt +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib.egg-info/entry_points.txt +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib.egg-info/requires.txt +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/pixelarraylib.egg-info/top_level.txt +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/requirements.txt +0 -0
- {pixelarraylib-1.1.7 → pixelarraylib-1.1.8}/setup.cfg +0 -0
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
import asyncio
|
|
4
|
+
import csv
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Feishu:
|
|
10
|
+
channel_map = {
|
|
11
|
+
"矩阵像素订阅群": "https://open.feishu.cn/open-apis/bot/v2/hook/6e368741-ab2e-46f4-a945-5c1182303f91",
|
|
12
|
+
"devtoolkit服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/b9e7cfa1-c63f-4a9f-9699-286f7316784b",
|
|
13
|
+
"baymax服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5d8d4fa6-67c4-4202-9122-389d1ec2b668",
|
|
14
|
+
"videodriver服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5359b92d-02ab-47ca-a617-58b8152eaa2d",
|
|
15
|
+
"arraycut服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/e610b1e8-f867-4670-8d4d-0553f64884f0",
|
|
16
|
+
"knowledgebase微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5f007914-235a-4287-8349-6c1dd7c70024",
|
|
17
|
+
"llm微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/54942aa6-24f1-4851-8fe9-d7c87572d00a",
|
|
18
|
+
"thirdparty微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/b1e6237a-1323-4ad9-96f4-d74de5cdc00f",
|
|
19
|
+
"picturebed服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/d3c2e68c-3ed3-4832-9b66-76db5bd69b42",
|
|
20
|
+
"picturetransform服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/e975aa0a-acef-4e3f-bee4-6dc507b87ebd",
|
|
21
|
+
"cloudstorage服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/a632d0bc-e400-40ce-a3bb-1b9b9ee634f4",
|
|
22
|
+
"deployengine服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/9a347a63-58fb-4e10-9a3d-ff4918ddb3a9",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
channel_name,
|
|
28
|
+
app_id: Optional[str] = None,
|
|
29
|
+
app_secret: Optional[str] = None,
|
|
30
|
+
):
|
|
31
|
+
"""
|
|
32
|
+
description:
|
|
33
|
+
初始化飞书告警客户端
|
|
34
|
+
parameters:
|
|
35
|
+
channel_name(str): 飞书频道名称
|
|
36
|
+
app_id(Optional[str]): 应用ID,可选
|
|
37
|
+
app_secret(Optional[str]): 应用密钥,可选
|
|
38
|
+
"""
|
|
39
|
+
self.webhook_url = self.channel_map[channel_name]
|
|
40
|
+
self.app_id = app_id
|
|
41
|
+
self.app_secret = app_secret
|
|
42
|
+
self._access_token = None
|
|
43
|
+
|
|
44
|
+
def send(self, text):
|
|
45
|
+
"""
|
|
46
|
+
description:
|
|
47
|
+
发送文本消息到飞书群
|
|
48
|
+
parameters:
|
|
49
|
+
text(str): 要发送的文本内容
|
|
50
|
+
return:
|
|
51
|
+
success(bool): 发送是否成功
|
|
52
|
+
"""
|
|
53
|
+
print(text)
|
|
54
|
+
headers = {"Content-Type": "application/json"}
|
|
55
|
+
data = {"msg_type": "text", "content": {"text": text}}
|
|
56
|
+
response = requests.post(
|
|
57
|
+
self.webhook_url, headers=headers, data=json.dumps(data)
|
|
58
|
+
)
|
|
59
|
+
return bool(
|
|
60
|
+
response
|
|
61
|
+
and response.json().get("StatusCode") == 0
|
|
62
|
+
and response.json().get("StatusMessage") == "success"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def send_async(self, text: str):
|
|
66
|
+
"""
|
|
67
|
+
description:
|
|
68
|
+
异步发送文本消息到飞书群
|
|
69
|
+
parameters:
|
|
70
|
+
text(str): 要发送的文本内容
|
|
71
|
+
return:
|
|
72
|
+
success(bool): 发送是否成功
|
|
73
|
+
"""
|
|
74
|
+
return await asyncio.to_thread(self.send, text)
|
|
75
|
+
|
|
76
|
+
def send_markdown(
|
|
77
|
+
self,
|
|
78
|
+
markdown_content: str,
|
|
79
|
+
title: str,
|
|
80
|
+
template: str = "turquoise",
|
|
81
|
+
):
|
|
82
|
+
"""
|
|
83
|
+
description:
|
|
84
|
+
发送Markdown格式的消息到飞书群
|
|
85
|
+
parameters:
|
|
86
|
+
markdown_content(str): Markdown格式的内容
|
|
87
|
+
title(str): 消息标题
|
|
88
|
+
template(str): 卡片模板颜色,默认为"turquoise"
|
|
89
|
+
return:
|
|
90
|
+
success(bool): 发送是否成功
|
|
91
|
+
"""
|
|
92
|
+
headers = {"Content-Type": "application/json; charset=utf-8"}
|
|
93
|
+
card = {
|
|
94
|
+
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
95
|
+
"header": {
|
|
96
|
+
"title": {"tag": "plain_text", "content": title},
|
|
97
|
+
"template": template,
|
|
98
|
+
},
|
|
99
|
+
"elements": [
|
|
100
|
+
{"tag": "div", "text": {"tag": "lark_md", "content": markdown_content}}
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
payload = {"msg_type": "interactive", "card": card}
|
|
104
|
+
resp = requests.post(
|
|
105
|
+
self.webhook_url, headers=headers, data=json.dumps(payload)
|
|
106
|
+
)
|
|
107
|
+
return bool(resp.ok and resp.json().get("StatusCode") == 0)
|
|
108
|
+
|
|
109
|
+
async def send_markdown_async(
|
|
110
|
+
self,
|
|
111
|
+
markdown_content: str,
|
|
112
|
+
title: str,
|
|
113
|
+
template: str = "turquoise",
|
|
114
|
+
):
|
|
115
|
+
"""
|
|
116
|
+
description:
|
|
117
|
+
异步发送Markdown格式的消息到飞书群
|
|
118
|
+
parameters:
|
|
119
|
+
markdown_content(str): Markdown格式的内容
|
|
120
|
+
title(str): 消息标题
|
|
121
|
+
template(str): 卡片模板颜色,默认为"turquoise"
|
|
122
|
+
return:
|
|
123
|
+
success(bool): 发送是否成功
|
|
124
|
+
"""
|
|
125
|
+
return await asyncio.to_thread(
|
|
126
|
+
self.send_markdown, markdown_content, title, template
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def send_table(
|
|
130
|
+
self,
|
|
131
|
+
headers: list,
|
|
132
|
+
rows: list,
|
|
133
|
+
title: str,
|
|
134
|
+
template: str = "turquoise",
|
|
135
|
+
):
|
|
136
|
+
"""
|
|
137
|
+
description:
|
|
138
|
+
发送表格消息到飞书群
|
|
139
|
+
parameters:
|
|
140
|
+
headers(list): 表头列表
|
|
141
|
+
rows(list): 表格行数据列表
|
|
142
|
+
title(str): 消息标题
|
|
143
|
+
template(str): 卡片模板颜色,默认为"turquoise"
|
|
144
|
+
return:
|
|
145
|
+
success(bool): 发送是否成功
|
|
146
|
+
"""
|
|
147
|
+
headers_req = {"Content-Type": "application/json; charset=utf-8"}
|
|
148
|
+
n_cols = len(headers)
|
|
149
|
+
columns = []
|
|
150
|
+
for ci in range(n_cols):
|
|
151
|
+
elements_in_col = []
|
|
152
|
+
# 表头
|
|
153
|
+
elements_in_col.append(
|
|
154
|
+
{
|
|
155
|
+
"tag": "div",
|
|
156
|
+
"text": {"tag": "lark_md", "content": f"**{headers[ci]}**"},
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
# 每一行的该列内容
|
|
160
|
+
for row in rows:
|
|
161
|
+
cell = row[ci] if ci < len(row) else ""
|
|
162
|
+
elements_in_col.append(
|
|
163
|
+
{"tag": "div", "text": {"tag": "lark_md", "content": cell}}
|
|
164
|
+
)
|
|
165
|
+
columns.append(
|
|
166
|
+
{
|
|
167
|
+
"tag": "column",
|
|
168
|
+
"width": "weighted",
|
|
169
|
+
"weight": 1,
|
|
170
|
+
"elements": elements_in_col,
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
elements = [{"tag": "column_set", "columns": columns}]
|
|
175
|
+
|
|
176
|
+
card = {
|
|
177
|
+
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
178
|
+
"header": {
|
|
179
|
+
"title": {"tag": "plain_text", "content": title},
|
|
180
|
+
"template": template,
|
|
181
|
+
},
|
|
182
|
+
"elements": elements,
|
|
183
|
+
}
|
|
184
|
+
payload = {"msg_type": "interactive", "card": card}
|
|
185
|
+
|
|
186
|
+
resp = requests.post(
|
|
187
|
+
self.webhook_url, headers=headers_req, data=json.dumps(payload)
|
|
188
|
+
)
|
|
189
|
+
try:
|
|
190
|
+
print("feishu send_table resp:", resp.status_code, resp.json())
|
|
191
|
+
except Exception:
|
|
192
|
+
print("feishu send_table resp:", resp.status_code, resp.text)
|
|
193
|
+
return bool(resp.ok and resp.json().get("StatusCode") == 0)
|
|
194
|
+
|
|
195
|
+
async def send_table_async(
|
|
196
|
+
self,
|
|
197
|
+
headers: list[str],
|
|
198
|
+
rows: list[list[str]],
|
|
199
|
+
title: str,
|
|
200
|
+
template: str = "turquoise",
|
|
201
|
+
):
|
|
202
|
+
"""
|
|
203
|
+
description:
|
|
204
|
+
异步发送表格消息到飞书群
|
|
205
|
+
parameters:
|
|
206
|
+
headers(list[str]): 表头列表
|
|
207
|
+
rows(list[list[str]]): 表格行数据列表
|
|
208
|
+
title(str): 消息标题
|
|
209
|
+
template(str): 卡片模板颜色,默认为"turquoise"
|
|
210
|
+
return:
|
|
211
|
+
success(bool): 发送是否成功
|
|
212
|
+
"""
|
|
213
|
+
return await asyncio.to_thread(self.send_table, headers, rows, title, template)
|
|
214
|
+
|
|
215
|
+
def send_file(
|
|
216
|
+
self,
|
|
217
|
+
file_url: Optional[str] = None,
|
|
218
|
+
title: Optional[str] = None,
|
|
219
|
+
template: str = "turquoise",
|
|
220
|
+
):
|
|
221
|
+
"""
|
|
222
|
+
description:
|
|
223
|
+
发送文件到飞书群
|
|
224
|
+
通过文件下载链接发送包含文件下载按钮的卡片消息
|
|
225
|
+
parameters:
|
|
226
|
+
file_url(str): 文件下载链接(如 OSS 链接),必需参数
|
|
227
|
+
title(str): 消息标题,如果为 None 则使用文件名
|
|
228
|
+
template(str): 卡片模板颜色,默认 "turquoise"
|
|
229
|
+
return:
|
|
230
|
+
bool: 发送是否成功
|
|
231
|
+
"""
|
|
232
|
+
if not file_url:
|
|
233
|
+
print("错误:必须提供 file_url(文件下载链接)")
|
|
234
|
+
return False
|
|
235
|
+
headers = {"Content-Type": "application/json; charset=utf-8"}
|
|
236
|
+
markdown_content = f"[点击下载文件]({file_url})"
|
|
237
|
+
|
|
238
|
+
card = {
|
|
239
|
+
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
240
|
+
"header": {
|
|
241
|
+
"title": {"tag": "plain_text", "content": title},
|
|
242
|
+
"template": template,
|
|
243
|
+
},
|
|
244
|
+
"elements": [
|
|
245
|
+
{"tag": "div", "text": {"tag": "lark_md", "content": markdown_content}}
|
|
246
|
+
],
|
|
247
|
+
}
|
|
248
|
+
payload = {"msg_type": "interactive", "card": card}
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
resp = requests.post(
|
|
252
|
+
self.webhook_url, headers=headers, data=json.dumps(payload)
|
|
253
|
+
)
|
|
254
|
+
if resp.ok:
|
|
255
|
+
result = resp.json()
|
|
256
|
+
return bool(result.get("StatusCode") == 0 or result.get("code") == 0)
|
|
257
|
+
else:
|
|
258
|
+
print(f"发送文件消息失败: {resp.status_code}, {resp.text}")
|
|
259
|
+
return False
|
|
260
|
+
except Exception as e:
|
|
261
|
+
print(f"发送文件消息异常: {e}")
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
async def send_file_async(self, file_url: str, title: str, template: str):
|
|
265
|
+
"""
|
|
266
|
+
description:
|
|
267
|
+
异步发送文件到飞书群
|
|
268
|
+
parameters:
|
|
269
|
+
file_url(str): 文件下载链接
|
|
270
|
+
title(str): 消息标题
|
|
271
|
+
template(str): 卡片模板颜色
|
|
272
|
+
return:
|
|
273
|
+
success(bool): 发送是否成功
|
|
274
|
+
"""
|
|
275
|
+
return await asyncio.to_thread(self.send_file, file_url, title, template)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class FeishuDocumentManager:
|
|
279
|
+
"""
|
|
280
|
+
飞书文档管理器,用于管理飞书文档的创建、查询、更新和删除
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
def __init__(
|
|
284
|
+
self,
|
|
285
|
+
app_id: Optional[str] = None,
|
|
286
|
+
app_secret: Optional[str] = None,
|
|
287
|
+
):
|
|
288
|
+
"""
|
|
289
|
+
description:
|
|
290
|
+
初始化飞书文档管理器
|
|
291
|
+
parameters:
|
|
292
|
+
app_id(Optional[str]): 飞书应用ID,如果不提供则从settings中读取
|
|
293
|
+
app_secret(Optional[str]): 飞书应用密钥,如果不提供则从settings中读取
|
|
294
|
+
"""
|
|
295
|
+
try:
|
|
296
|
+
from settings import FEISHU_APP_ID, FEISHU_APP_SECRET
|
|
297
|
+
self.app_id = app_id or FEISHU_APP_ID
|
|
298
|
+
self.app_secret = app_secret or FEISHU_APP_SECRET
|
|
299
|
+
except ImportError:
|
|
300
|
+
if not app_id or not app_secret:
|
|
301
|
+
raise ValueError(
|
|
302
|
+
"必须提供app_id和app_secret,或者在settings中配置FEISHU_APP_ID和FEISHU_APP_SECRET"
|
|
303
|
+
)
|
|
304
|
+
self.app_id = app_id
|
|
305
|
+
self.app_secret = app_secret
|
|
306
|
+
|
|
307
|
+
self.base_url = "https://open.feishu.cn/open-apis"
|
|
308
|
+
self._access_token = None
|
|
309
|
+
self._token_expire_time = 0
|
|
310
|
+
|
|
311
|
+
def _get_access_token(self) -> str:
|
|
312
|
+
"""
|
|
313
|
+
description:
|
|
314
|
+
获取飞书应用的访问令牌(带缓存机制)
|
|
315
|
+
return:
|
|
316
|
+
str: 访问令牌
|
|
317
|
+
"""
|
|
318
|
+
import time
|
|
319
|
+
|
|
320
|
+
# 如果token未过期,直接返回缓存的token
|
|
321
|
+
if self._access_token and time.time() < self._token_expire_time:
|
|
322
|
+
return self._access_token
|
|
323
|
+
|
|
324
|
+
url = f"{self.base_url}/auth/v3/app_access_token/internal"
|
|
325
|
+
headers = {"Content-Type": "application/json; charset=utf-8"}
|
|
326
|
+
data = {
|
|
327
|
+
"app_id": self.app_id,
|
|
328
|
+
"app_secret": self.app_secret,
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
try:
|
|
332
|
+
response = requests.post(url, headers=headers, json=data)
|
|
333
|
+
response.raise_for_status()
|
|
334
|
+
result = response.json()
|
|
335
|
+
|
|
336
|
+
if result.get("code") != 0:
|
|
337
|
+
raise Exception(
|
|
338
|
+
f"获取access_token失败: {result.get('msg', 'unknown error')}"
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
self._access_token = result.get("app_access_token")
|
|
342
|
+
# token有效期默认7200秒,提前300秒刷新
|
|
343
|
+
expire = result.get("expire", 7200)
|
|
344
|
+
self._token_expire_time = time.time() + expire - 300
|
|
345
|
+
|
|
346
|
+
return self._access_token
|
|
347
|
+
except requests.RequestException as e:
|
|
348
|
+
raise Exception(f"请求access_token时发生错误: {str(e)}")
|
|
349
|
+
|
|
350
|
+
def _get_headers(self) -> dict:
|
|
351
|
+
"""
|
|
352
|
+
description:
|
|
353
|
+
获取请求头,包含access_token
|
|
354
|
+
return:
|
|
355
|
+
dict: 请求头字典
|
|
356
|
+
"""
|
|
357
|
+
return {
|
|
358
|
+
"Authorization": f"Bearer {self._get_access_token()}",
|
|
359
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def list_documents(
|
|
363
|
+
self,
|
|
364
|
+
user_id: Optional[str] = None,
|
|
365
|
+
folder_token: Optional[str] = None,
|
|
366
|
+
page_size: int = 50,
|
|
367
|
+
page_token: Optional[str] = None,
|
|
368
|
+
order_by: str = "EditedTime",
|
|
369
|
+
direction: str = "DESC",
|
|
370
|
+
) -> tuple[dict, bool]:
|
|
371
|
+
"""
|
|
372
|
+
description:
|
|
373
|
+
列出文档列表(支持按用户或文件夹筛选)
|
|
374
|
+
parameters:
|
|
375
|
+
user_id(Optional[str]): 用户ID,如果提供则只返回该用户的文档
|
|
376
|
+
folder_token(Optional[str]): 文件夹token,如果提供则只返回该文件夹下的文档
|
|
377
|
+
page_size(int): 每页数量,默认50
|
|
378
|
+
page_token(Optional[str]): 分页token,用于获取下一页
|
|
379
|
+
order_by(str): 排序字段,默认为"EditedTime"(编辑时间)
|
|
380
|
+
direction(str): 排序方向,"ASC"或"DESC",默认为"DESC"
|
|
381
|
+
return:
|
|
382
|
+
result(dict): API返回的结果,包含文档列表
|
|
383
|
+
success(bool): 是否成功
|
|
384
|
+
"""
|
|
385
|
+
url = f"{self.base_url}/drive/v1/files"
|
|
386
|
+
headers = self._get_headers()
|
|
387
|
+
params = {
|
|
388
|
+
"page_size": page_size,
|
|
389
|
+
"order_by": order_by,
|
|
390
|
+
"direction": direction,
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if user_id:
|
|
394
|
+
params["user_id"] = user_id
|
|
395
|
+
if folder_token:
|
|
396
|
+
params["folder_token"] = folder_token
|
|
397
|
+
if page_token:
|
|
398
|
+
params["page_token"] = page_token
|
|
399
|
+
|
|
400
|
+
try:
|
|
401
|
+
response = requests.get(url, headers=headers, params=params)
|
|
402
|
+
response.raise_for_status()
|
|
403
|
+
result = response.json()
|
|
404
|
+
|
|
405
|
+
if result.get("code") != 0:
|
|
406
|
+
return result, False
|
|
407
|
+
|
|
408
|
+
return result, True
|
|
409
|
+
except requests.RequestException as e:
|
|
410
|
+
return {"code": -1, "msg": f"请求失败: {str(e)}"}, False
|
|
411
|
+
|
|
412
|
+
def create_document(
|
|
413
|
+
self,
|
|
414
|
+
folder_token: str,
|
|
415
|
+
title: str,
|
|
416
|
+
doc_type: str = "doc",
|
|
417
|
+
) -> tuple[dict, bool]:
|
|
418
|
+
"""
|
|
419
|
+
description:
|
|
420
|
+
创建新文档
|
|
421
|
+
parameters:
|
|
422
|
+
folder_token(str): 目标文件夹token
|
|
423
|
+
title(str): 文档标题
|
|
424
|
+
doc_type(str): 文档类型,"doc"表示文档,"sheet"表示电子表格,"bitable"表示多维表格,默认为"doc"
|
|
425
|
+
return:
|
|
426
|
+
result(dict): API返回的结果,包含创建的文档信息
|
|
427
|
+
success(bool): 是否成功
|
|
428
|
+
"""
|
|
429
|
+
url = f"{self.base_url}/drive/v1/files/create"
|
|
430
|
+
headers = self._get_headers()
|
|
431
|
+
data = {
|
|
432
|
+
"folder_token": folder_token,
|
|
433
|
+
"title": title,
|
|
434
|
+
"type": doc_type,
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
response = requests.post(url, headers=headers, json=data)
|
|
439
|
+
response.raise_for_status()
|
|
440
|
+
result = response.json()
|
|
441
|
+
|
|
442
|
+
if result.get("code") != 0:
|
|
443
|
+
return result, False
|
|
444
|
+
|
|
445
|
+
return result, True
|
|
446
|
+
except requests.RequestException as e:
|
|
447
|
+
return {"code": -1, "msg": f"请求失败: {str(e)}"}, False
|
|
448
|
+
|
|
449
|
+
def update_document(
|
|
450
|
+
self,
|
|
451
|
+
document_id: str,
|
|
452
|
+
title: Optional[str] = None,
|
|
453
|
+
content: Optional[str] = None,
|
|
454
|
+
) -> tuple[dict, bool]:
|
|
455
|
+
"""
|
|
456
|
+
description:
|
|
457
|
+
更新文档(支持更新标题和内容)
|
|
458
|
+
parameters:
|
|
459
|
+
document_id(str): 文档ID(doc_token)
|
|
460
|
+
title(Optional[str]): 新的文档标题,如果提供则更新标题
|
|
461
|
+
content(Optional[str]): 新的文档内容(Markdown格式),如果提供则更新内容
|
|
462
|
+
return:
|
|
463
|
+
result(dict): API返回的结果
|
|
464
|
+
success(bool): 是否成功
|
|
465
|
+
"""
|
|
466
|
+
success_count = 0
|
|
467
|
+
|
|
468
|
+
# 更新标题
|
|
469
|
+
if title:
|
|
470
|
+
url = f"{self.base_url}/drive/v1/files/{document_id}"
|
|
471
|
+
headers = self._get_headers()
|
|
472
|
+
data = {"name": title}
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
response = requests.patch(url, headers=headers, json=data)
|
|
476
|
+
response.raise_for_status()
|
|
477
|
+
result = response.json()
|
|
478
|
+
|
|
479
|
+
if result.get("code") == 0:
|
|
480
|
+
success_count += 1
|
|
481
|
+
else:
|
|
482
|
+
return result, False
|
|
483
|
+
except requests.RequestException as e:
|
|
484
|
+
return {"code": -1, "msg": f"更新标题失败: {str(e)}"}, False
|
|
485
|
+
|
|
486
|
+
# 更新内容(使用docx API)
|
|
487
|
+
if content:
|
|
488
|
+
url = f"{self.base_url}/docx/v1/documents/{document_id}/blocks/{document_id}"
|
|
489
|
+
headers = self._get_headers()
|
|
490
|
+
# 构建文档块内容
|
|
491
|
+
blocks = [
|
|
492
|
+
{
|
|
493
|
+
"block_id": document_id,
|
|
494
|
+
"block_type": 1, # 1表示文本块
|
|
495
|
+
"text": {
|
|
496
|
+
"elements": [
|
|
497
|
+
{
|
|
498
|
+
"text_run": {
|
|
499
|
+
"content": content,
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
},
|
|
504
|
+
}
|
|
505
|
+
]
|
|
506
|
+
data = {"document_revision_id": -1, "blocks": blocks}
|
|
507
|
+
|
|
508
|
+
try:
|
|
509
|
+
response = requests.patch(url, headers=headers, json=data)
|
|
510
|
+
response.raise_for_status()
|
|
511
|
+
result = response.json()
|
|
512
|
+
|
|
513
|
+
if result.get("code") == 0:
|
|
514
|
+
success_count += 1
|
|
515
|
+
else:
|
|
516
|
+
return result, False
|
|
517
|
+
except requests.RequestException as e:
|
|
518
|
+
return {"code": -1, "msg": f"更新内容失败: {str(e)}"}, False
|
|
519
|
+
|
|
520
|
+
if success_count > 0:
|
|
521
|
+
return {"code": 0, "msg": "success"}, True
|
|
522
|
+
else:
|
|
523
|
+
return {"code": -1, "msg": "没有提供需要更新的内容"}, False
|
|
524
|
+
|
|
525
|
+
def delete_document(self, file_token: str) -> tuple[dict, bool]:
|
|
526
|
+
"""
|
|
527
|
+
description:
|
|
528
|
+
删除文档
|
|
529
|
+
parameters:
|
|
530
|
+
file_token(str): 文件token(文档ID)
|
|
531
|
+
return:
|
|
532
|
+
result(dict): API返回的结果
|
|
533
|
+
success(bool): 是否成功
|
|
534
|
+
"""
|
|
535
|
+
url = f"{self.base_url}/drive/v1/files/{file_token}"
|
|
536
|
+
headers = self._get_headers()
|
|
537
|
+
|
|
538
|
+
try:
|
|
539
|
+
response = requests.delete(url, headers=headers)
|
|
540
|
+
response.raise_for_status()
|
|
541
|
+
result = response.json()
|
|
542
|
+
|
|
543
|
+
if result.get("code") != 0:
|
|
544
|
+
return result, False
|
|
545
|
+
|
|
546
|
+
return result, True
|
|
547
|
+
except requests.RequestException as e:
|
|
548
|
+
return {"code": -1, "msg": f"请求失败: {str(e)}"}, False
|
|
549
|
+
|
|
550
|
+
def get_document_info(self, document_id: str) -> tuple[dict, bool]:
|
|
551
|
+
"""
|
|
552
|
+
description:
|
|
553
|
+
获取文档详细信息
|
|
554
|
+
parameters:
|
|
555
|
+
document_id(str): 文档ID(file_token)
|
|
556
|
+
return:
|
|
557
|
+
result(dict): API返回的结果,包含文档信息
|
|
558
|
+
success(bool): 是否成功
|
|
559
|
+
"""
|
|
560
|
+
url = f"{self.base_url}/drive/v1/files/{document_id}"
|
|
561
|
+
headers = self._get_headers()
|
|
562
|
+
|
|
563
|
+
try:
|
|
564
|
+
response = requests.get(url, headers=headers)
|
|
565
|
+
response.raise_for_status()
|
|
566
|
+
result = response.json()
|
|
567
|
+
|
|
568
|
+
if result.get("code") != 0:
|
|
569
|
+
return result, False
|
|
570
|
+
|
|
571
|
+
return result, True
|
|
572
|
+
except requests.RequestException as e:
|
|
573
|
+
return {"code": -1, "msg": f"请求失败: {str(e)}"}, False
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
import json
|
|
3
|
-
import asyncio
|
|
4
|
-
import csv
|
|
5
|
-
import os
|
|
6
|
-
from typing import Optional
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Feishu:
|
|
10
|
-
channel_map = {
|
|
11
|
-
"矩阵像素订阅群": "https://open.feishu.cn/open-apis/bot/v2/hook/6e368741-ab2e-46f4-a945-5c1182303f91",
|
|
12
|
-
"devtoolkit服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/b9e7cfa1-c63f-4a9f-9699-286f7316784b",
|
|
13
|
-
"baymax服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5d8d4fa6-67c4-4202-9122-389d1ec2b668",
|
|
14
|
-
"videodriver服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5359b92d-02ab-47ca-a617-58b8152eaa2d",
|
|
15
|
-
"arraycut服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/e610b1e8-f867-4670-8d4d-0553f64884f0",
|
|
16
|
-
"knowledgebase微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/5f007914-235a-4287-8349-6c1dd7c70024",
|
|
17
|
-
"llm微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/54942aa6-24f1-4851-8fe9-d7c87572d00a",
|
|
18
|
-
"thirdparty微服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/b1e6237a-1323-4ad9-96f4-d74de5cdc00f",
|
|
19
|
-
"picturebed服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/d3c2e68c-3ed3-4832-9b66-76db5bd69b42",
|
|
20
|
-
"picturetransform服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/e975aa0a-acef-4e3f-bee4-6dc507b87ebd",
|
|
21
|
-
"cloudstorage服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/a632d0bc-e400-40ce-a3bb-1b9b9ee634f4",
|
|
22
|
-
"deployengine服务报警": "https://open.feishu.cn/open-apis/bot/v2/hook/9a347a63-58fb-4e10-9a3d-ff4918ddb3a9",
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
def __init__(
|
|
26
|
-
self,
|
|
27
|
-
channel_name,
|
|
28
|
-
app_id: Optional[str] = None,
|
|
29
|
-
app_secret: Optional[str] = None,
|
|
30
|
-
):
|
|
31
|
-
"""
|
|
32
|
-
description:
|
|
33
|
-
初始化飞书告警客户端
|
|
34
|
-
parameters:
|
|
35
|
-
channel_name(str): 飞书频道名称
|
|
36
|
-
app_id(Optional[str]): 应用ID,可选
|
|
37
|
-
app_secret(Optional[str]): 应用密钥,可选
|
|
38
|
-
"""
|
|
39
|
-
self.webhook_url = self.channel_map[channel_name]
|
|
40
|
-
self.app_id = app_id
|
|
41
|
-
self.app_secret = app_secret
|
|
42
|
-
self._access_token = None
|
|
43
|
-
|
|
44
|
-
def send(self, text):
|
|
45
|
-
"""
|
|
46
|
-
description:
|
|
47
|
-
发送文本消息到飞书群
|
|
48
|
-
parameters:
|
|
49
|
-
text(str): 要发送的文本内容
|
|
50
|
-
return:
|
|
51
|
-
success(bool): 发送是否成功
|
|
52
|
-
"""
|
|
53
|
-
print(text)
|
|
54
|
-
headers = {"Content-Type": "application/json"}
|
|
55
|
-
data = {"msg_type": "text", "content": {"text": text}}
|
|
56
|
-
response = requests.post(
|
|
57
|
-
self.webhook_url, headers=headers, data=json.dumps(data)
|
|
58
|
-
)
|
|
59
|
-
return bool(
|
|
60
|
-
response
|
|
61
|
-
and response.json().get("StatusCode") == 0
|
|
62
|
-
and response.json().get("StatusMessage") == "success"
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
async def send_async(self, text: str):
|
|
66
|
-
"""
|
|
67
|
-
description:
|
|
68
|
-
异步发送文本消息到飞书群
|
|
69
|
-
parameters:
|
|
70
|
-
text(str): 要发送的文本内容
|
|
71
|
-
return:
|
|
72
|
-
success(bool): 发送是否成功
|
|
73
|
-
"""
|
|
74
|
-
return await asyncio.to_thread(self.send, text)
|
|
75
|
-
|
|
76
|
-
def send_markdown(
|
|
77
|
-
self,
|
|
78
|
-
markdown_content: str,
|
|
79
|
-
title: str,
|
|
80
|
-
template: str = "turquoise",
|
|
81
|
-
):
|
|
82
|
-
"""
|
|
83
|
-
description:
|
|
84
|
-
发送Markdown格式的消息到飞书群
|
|
85
|
-
parameters:
|
|
86
|
-
markdown_content(str): Markdown格式的内容
|
|
87
|
-
title(str): 消息标题
|
|
88
|
-
template(str): 卡片模板颜色,默认为"turquoise"
|
|
89
|
-
return:
|
|
90
|
-
success(bool): 发送是否成功
|
|
91
|
-
"""
|
|
92
|
-
headers = {"Content-Type": "application/json; charset=utf-8"}
|
|
93
|
-
card = {
|
|
94
|
-
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
95
|
-
"header": {
|
|
96
|
-
"title": {"tag": "plain_text", "content": title},
|
|
97
|
-
"template": template,
|
|
98
|
-
},
|
|
99
|
-
"elements": [
|
|
100
|
-
{"tag": "div", "text": {"tag": "lark_md", "content": markdown_content}}
|
|
101
|
-
],
|
|
102
|
-
}
|
|
103
|
-
payload = {"msg_type": "interactive", "card": card}
|
|
104
|
-
resp = requests.post(
|
|
105
|
-
self.webhook_url, headers=headers, data=json.dumps(payload)
|
|
106
|
-
)
|
|
107
|
-
return bool(resp.ok and resp.json().get("StatusCode") == 0)
|
|
108
|
-
|
|
109
|
-
async def send_markdown_async(
|
|
110
|
-
self,
|
|
111
|
-
markdown_content: str,
|
|
112
|
-
title: str,
|
|
113
|
-
template: str = "turquoise",
|
|
114
|
-
):
|
|
115
|
-
"""
|
|
116
|
-
description:
|
|
117
|
-
异步发送Markdown格式的消息到飞书群
|
|
118
|
-
parameters:
|
|
119
|
-
markdown_content(str): Markdown格式的内容
|
|
120
|
-
title(str): 消息标题
|
|
121
|
-
template(str): 卡片模板颜色,默认为"turquoise"
|
|
122
|
-
return:
|
|
123
|
-
success(bool): 发送是否成功
|
|
124
|
-
"""
|
|
125
|
-
return await asyncio.to_thread(
|
|
126
|
-
self.send_markdown, markdown_content, title, template
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
def send_table(
|
|
130
|
-
self,
|
|
131
|
-
headers: list,
|
|
132
|
-
rows: list,
|
|
133
|
-
title: str,
|
|
134
|
-
template: str = "turquoise",
|
|
135
|
-
):
|
|
136
|
-
"""
|
|
137
|
-
description:
|
|
138
|
-
发送表格消息到飞书群
|
|
139
|
-
parameters:
|
|
140
|
-
headers(list): 表头列表
|
|
141
|
-
rows(list): 表格行数据列表
|
|
142
|
-
title(str): 消息标题
|
|
143
|
-
template(str): 卡片模板颜色,默认为"turquoise"
|
|
144
|
-
return:
|
|
145
|
-
success(bool): 发送是否成功
|
|
146
|
-
"""
|
|
147
|
-
headers_req = {"Content-Type": "application/json; charset=utf-8"}
|
|
148
|
-
n_cols = len(headers)
|
|
149
|
-
columns = []
|
|
150
|
-
for ci in range(n_cols):
|
|
151
|
-
elements_in_col = []
|
|
152
|
-
# 表头
|
|
153
|
-
elements_in_col.append(
|
|
154
|
-
{
|
|
155
|
-
"tag": "div",
|
|
156
|
-
"text": {"tag": "lark_md", "content": f"**{headers[ci]}**"},
|
|
157
|
-
}
|
|
158
|
-
)
|
|
159
|
-
# 每一行的该列内容
|
|
160
|
-
for row in rows:
|
|
161
|
-
cell = row[ci] if ci < len(row) else ""
|
|
162
|
-
elements_in_col.append(
|
|
163
|
-
{"tag": "div", "text": {"tag": "lark_md", "content": cell}}
|
|
164
|
-
)
|
|
165
|
-
columns.append(
|
|
166
|
-
{
|
|
167
|
-
"tag": "column",
|
|
168
|
-
"width": "weighted",
|
|
169
|
-
"weight": 1,
|
|
170
|
-
"elements": elements_in_col,
|
|
171
|
-
}
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
elements = [{"tag": "column_set", "columns": columns}]
|
|
175
|
-
|
|
176
|
-
card = {
|
|
177
|
-
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
178
|
-
"header": {
|
|
179
|
-
"title": {"tag": "plain_text", "content": title},
|
|
180
|
-
"template": template,
|
|
181
|
-
},
|
|
182
|
-
"elements": elements,
|
|
183
|
-
}
|
|
184
|
-
payload = {"msg_type": "interactive", "card": card}
|
|
185
|
-
|
|
186
|
-
resp = requests.post(
|
|
187
|
-
self.webhook_url, headers=headers_req, data=json.dumps(payload)
|
|
188
|
-
)
|
|
189
|
-
try:
|
|
190
|
-
print("feishu send_table resp:", resp.status_code, resp.json())
|
|
191
|
-
except Exception:
|
|
192
|
-
print("feishu send_table resp:", resp.status_code, resp.text)
|
|
193
|
-
return bool(resp.ok and resp.json().get("StatusCode") == 0)
|
|
194
|
-
|
|
195
|
-
async def send_table_async(
|
|
196
|
-
self,
|
|
197
|
-
headers: list[str],
|
|
198
|
-
rows: list[list[str]],
|
|
199
|
-
title: str,
|
|
200
|
-
template: str = "turquoise",
|
|
201
|
-
):
|
|
202
|
-
"""
|
|
203
|
-
description:
|
|
204
|
-
异步发送表格消息到飞书群
|
|
205
|
-
parameters:
|
|
206
|
-
headers(list[str]): 表头列表
|
|
207
|
-
rows(list[list[str]]): 表格行数据列表
|
|
208
|
-
title(str): 消息标题
|
|
209
|
-
template(str): 卡片模板颜色,默认为"turquoise"
|
|
210
|
-
return:
|
|
211
|
-
success(bool): 发送是否成功
|
|
212
|
-
"""
|
|
213
|
-
return await asyncio.to_thread(self.send_table, headers, rows, title, template)
|
|
214
|
-
|
|
215
|
-
def send_file(
|
|
216
|
-
self,
|
|
217
|
-
file_url: Optional[str] = None,
|
|
218
|
-
title: Optional[str] = None,
|
|
219
|
-
template: str = "turquoise",
|
|
220
|
-
):
|
|
221
|
-
"""
|
|
222
|
-
description:
|
|
223
|
-
发送文件到飞书群
|
|
224
|
-
通过文件下载链接发送包含文件下载按钮的卡片消息
|
|
225
|
-
parameters:
|
|
226
|
-
file_url(str): 文件下载链接(如 OSS 链接),必需参数
|
|
227
|
-
title(str): 消息标题,如果为 None 则使用文件名
|
|
228
|
-
template(str): 卡片模板颜色,默认 "turquoise"
|
|
229
|
-
return:
|
|
230
|
-
bool: 发送是否成功
|
|
231
|
-
"""
|
|
232
|
-
if not file_url:
|
|
233
|
-
print("错误:必须提供 file_url(文件下载链接)")
|
|
234
|
-
return False
|
|
235
|
-
headers = {"Content-Type": "application/json; charset=utf-8"}
|
|
236
|
-
markdown_content = f"[点击下载文件]({file_url})"
|
|
237
|
-
|
|
238
|
-
card = {
|
|
239
|
-
"config": {"wide_screen_mode": True, "enable_forward": True},
|
|
240
|
-
"header": {
|
|
241
|
-
"title": {"tag": "plain_text", "content": title},
|
|
242
|
-
"template": template,
|
|
243
|
-
},
|
|
244
|
-
"elements": [
|
|
245
|
-
{"tag": "div", "text": {"tag": "lark_md", "content": markdown_content}}
|
|
246
|
-
],
|
|
247
|
-
}
|
|
248
|
-
payload = {"msg_type": "interactive", "card": card}
|
|
249
|
-
|
|
250
|
-
try:
|
|
251
|
-
resp = requests.post(
|
|
252
|
-
self.webhook_url, headers=headers, data=json.dumps(payload)
|
|
253
|
-
)
|
|
254
|
-
if resp.ok:
|
|
255
|
-
result = resp.json()
|
|
256
|
-
return bool(result.get("StatusCode") == 0 or result.get("code") == 0)
|
|
257
|
-
else:
|
|
258
|
-
print(f"发送文件消息失败: {resp.status_code}, {resp.text}")
|
|
259
|
-
return False
|
|
260
|
-
except Exception as e:
|
|
261
|
-
print(f"发送文件消息异常: {e}")
|
|
262
|
-
return False
|
|
263
|
-
|
|
264
|
-
async def send_file_async(self, file_url: str, title: str, template: str):
|
|
265
|
-
"""
|
|
266
|
-
description:
|
|
267
|
-
异步发送文件到飞书群
|
|
268
|
-
parameters:
|
|
269
|
-
file_url(str): 文件下载链接
|
|
270
|
-
title(str): 消息标题
|
|
271
|
-
template(str): 卡片模板颜色
|
|
272
|
-
return:
|
|
273
|
-
success(bool): 发送是否成功
|
|
274
|
-
"""
|
|
275
|
-
return await asyncio.to_thread(self.send_file, file_url, title, template)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|