qrpa 1.1.33__py3-none-any.whl → 1.1.35__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 qrpa might be problematic. Click here for more details.
- qrpa/RateLimitedSender.py +45 -45
- qrpa/__init__.py +31 -31
- qrpa/db_migrator.py +600 -600
- qrpa/feishu_bot_app.py +267 -267
- qrpa/fun_base.py +339 -339
- qrpa/fun_excel.py +3059 -3059
- qrpa/fun_file.py +318 -318
- qrpa/fun_web.py +258 -258
- qrpa/fun_win.py +198 -198
- qrpa/mysql_module/new_product_analysis_model.py +488 -0
- qrpa/mysql_module/shein_ledger_model.py +468 -468
- qrpa/mysql_module/shein_product_model.py +484 -484
- qrpa/mysql_module/shein_return_order_model.py +569 -569
- qrpa/mysql_module/shein_store_model.py +594 -0
- qrpa/shein_daily_report_model.py +375 -375
- qrpa/shein_excel.py +3125 -3125
- qrpa/shein_lib.py +3932 -3607
- qrpa/shein_mysql.py +22 -0
- qrpa/shein_sqlite.py +153 -153
- qrpa/shein_ziniao.py +529 -529
- qrpa/temu_chrome.py +56 -56
- qrpa/temu_excel.py +139 -139
- qrpa/temu_lib.py +154 -154
- qrpa/time_utils.py +882 -882
- qrpa/time_utils_example.py +243 -243
- qrpa/wxwork.py +318 -318
- {qrpa-1.1.33.dist-info → qrpa-1.1.35.dist-info}/METADATA +1 -1
- qrpa-1.1.35.dist-info/RECORD +33 -0
- qrpa-1.1.33.dist-info/RECORD +0 -31
- {qrpa-1.1.33.dist-info → qrpa-1.1.35.dist-info}/WHEEL +0 -0
- {qrpa-1.1.33.dist-info → qrpa-1.1.35.dist-info}/top_level.txt +0 -0
qrpa/wxwork.py
CHANGED
|
@@ -1,318 +1,318 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
-------------------------------------------------
|
|
4
|
-
@version : v1.0
|
|
5
|
-
@author : qsir
|
|
6
|
-
@contact : qsir@vxnote.com
|
|
7
|
-
@software : PyCharm
|
|
8
|
-
@filename : wxwork.py
|
|
9
|
-
@create time: 2025/08/03
|
|
10
|
-
@modify time: 2025/08/03
|
|
11
|
-
@describe :
|
|
12
|
-
-------------------------------------------------
|
|
13
|
-
"""
|
|
14
|
-
import json
|
|
15
|
-
import os
|
|
16
|
-
import hashlib
|
|
17
|
-
import base64
|
|
18
|
-
import requests
|
|
19
|
-
from requests_toolbelt import MultipartEncoder
|
|
20
|
-
from datetime import datetime
|
|
21
|
-
|
|
22
|
-
# 通过企微群机器人发送消息
|
|
23
|
-
class WxWorkBot:
|
|
24
|
-
def __init__(self, key):
|
|
25
|
-
self.key = key
|
|
26
|
-
|
|
27
|
-
def upload_media(self, filepath):
|
|
28
|
-
"""
|
|
29
|
-
上传临时素材,给企微群里发文件消息时需要先将文件上传至企微临时素材中
|
|
30
|
-
:param filepath:
|
|
31
|
-
:return: 临时素材的media_id
|
|
32
|
-
"""
|
|
33
|
-
try:
|
|
34
|
-
headers = {
|
|
35
|
-
'Content-Type': 'multipart/form-data',
|
|
36
|
-
}
|
|
37
|
-
with open(filepath, 'rb') as f:
|
|
38
|
-
files = {
|
|
39
|
-
'media': (os.path.basename(filepath), f.read())
|
|
40
|
-
}
|
|
41
|
-
response = requests.post(
|
|
42
|
-
f'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={self.key}&type=file',
|
|
43
|
-
headers=headers, files=files)
|
|
44
|
-
response_text = json.loads(response.text)
|
|
45
|
-
if str(response_text.get('errcode')) != '0':
|
|
46
|
-
raise Exception(response_text)
|
|
47
|
-
if response.status_code == 200:
|
|
48
|
-
result = json.loads(response.text)
|
|
49
|
-
return result['media_id']
|
|
50
|
-
else:
|
|
51
|
-
print("HTTP Error:", response.status_code)
|
|
52
|
-
return None
|
|
53
|
-
except Exception as err:
|
|
54
|
-
raise Exception("upload_media error", err)
|
|
55
|
-
|
|
56
|
-
def send_file(self, file_path):
|
|
57
|
-
if not os.path.exists(file_path):
|
|
58
|
-
print('文件不存在: ', file_path)
|
|
59
|
-
return
|
|
60
|
-
"""
|
|
61
|
-
发送文件到群里
|
|
62
|
-
:param file_path:
|
|
63
|
-
:return:
|
|
64
|
-
"""
|
|
65
|
-
media_id = self.upload_media(file_path)
|
|
66
|
-
data = {
|
|
67
|
-
"msgtype": "file",
|
|
68
|
-
"file" : {
|
|
69
|
-
"media_id": media_id
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return self.send_msg(data)
|
|
73
|
-
|
|
74
|
-
def send_text(self, content, mentioned_list=None, mentioned_mobile_list=None):
|
|
75
|
-
"""
|
|
76
|
-
发送文本消息
|
|
77
|
-
:param content:
|
|
78
|
-
:param mentioned_list: 需要@的人userid
|
|
79
|
-
:param mentioned_mobile_list: 需要@的人手机号
|
|
80
|
-
:return:
|
|
81
|
-
"""
|
|
82
|
-
data = {
|
|
83
|
-
"msgtype": "text",
|
|
84
|
-
"text" : {
|
|
85
|
-
"content": content
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if mentioned_list is not None and mentioned_list:
|
|
89
|
-
data['text'].update({"mentioned_list": mentioned_list})
|
|
90
|
-
if mentioned_mobile_list is not None and mentioned_mobile_list:
|
|
91
|
-
data['text'].update({"mentioned_mobile_list": mentioned_mobile_list})
|
|
92
|
-
|
|
93
|
-
self.send_msg(data)
|
|
94
|
-
|
|
95
|
-
def send_markdown(self, content):
|
|
96
|
-
"""
|
|
97
|
-
发送Markdown消息
|
|
98
|
-
:param content:
|
|
99
|
-
:return:
|
|
100
|
-
"""
|
|
101
|
-
data = {
|
|
102
|
-
"msgtype" : "markdown",
|
|
103
|
-
"markdown": {
|
|
104
|
-
"content": content
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
self.send_msg(data)
|
|
108
|
-
|
|
109
|
-
def send_notify(self, title, sub_title_list, data_list):
|
|
110
|
-
"""
|
|
111
|
-
发送Markdown消息
|
|
112
|
-
:param content:
|
|
113
|
-
:return:
|
|
114
|
-
"""
|
|
115
|
-
|
|
116
|
-
current_date = datetime.now().strftime("%Y-%m-%d")
|
|
117
|
-
header = f"{current_date} {title}\n\n"
|
|
118
|
-
|
|
119
|
-
arr_color = ['warning', 'info', 'warning']
|
|
120
|
-
arr_sub_header = [f"<font color='{arr_color[index]}'>{title}</font>" for index, title in enumerate(sub_title_list)]
|
|
121
|
-
sub_header = "\t".join(arr_sub_header) + "\n\n"
|
|
122
|
-
|
|
123
|
-
# 获取每个元素的行索引和列索引
|
|
124
|
-
arr_content = [
|
|
125
|
-
[
|
|
126
|
-
f'{value}' if col_idx == 0 else f"<font color='{arr_color[col_idx - 1]}'>{value}</font>"
|
|
127
|
-
for col_idx, value in enumerate(row)
|
|
128
|
-
] # 每行的元素组成一个子列表
|
|
129
|
-
for row_idx, row in enumerate(data_list) # 外层循环控制行
|
|
130
|
-
]
|
|
131
|
-
# 将二维数组转换为字符串
|
|
132
|
-
content = "\n".join(
|
|
133
|
-
# 对每行的元素列表使用 join(),用 \t 连接
|
|
134
|
-
"\t".join(row) for row in arr_content
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
data = {
|
|
138
|
-
"msgtype" : "markdown",
|
|
139
|
-
"markdown": {
|
|
140
|
-
"content": header + sub_header + content
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
self.send_msg(data)
|
|
144
|
-
|
|
145
|
-
def send_img(self, img_path):
|
|
146
|
-
"""
|
|
147
|
-
发送图片消息
|
|
148
|
-
图片(base64编码前)最大不能超过2M,支持JPG,PNG格式
|
|
149
|
-
:param img_path:
|
|
150
|
-
:return:
|
|
151
|
-
"""
|
|
152
|
-
data = {
|
|
153
|
-
"msgtype": "image",
|
|
154
|
-
"image" : {
|
|
155
|
-
"base64": self.img_to_base64(img_path),
|
|
156
|
-
"md5" : self.img_to_md5(img_path)
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
self.send_msg(data)
|
|
160
|
-
|
|
161
|
-
def send_news(self, title, description, url, picurl):
|
|
162
|
-
"""
|
|
163
|
-
发送图文消息
|
|
164
|
-
:param title: 标题
|
|
165
|
-
:param description: 描述
|
|
166
|
-
:param url: 跳转URL
|
|
167
|
-
:param picurl: 图文图片地址
|
|
168
|
-
:return:
|
|
169
|
-
"""
|
|
170
|
-
data = {
|
|
171
|
-
"msgtype": "news",
|
|
172
|
-
"news" : {
|
|
173
|
-
"articles": [
|
|
174
|
-
{
|
|
175
|
-
"title" : title,
|
|
176
|
-
"description": description,
|
|
177
|
-
"url" : url,
|
|
178
|
-
"picurl" : picurl
|
|
179
|
-
}
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
self.send_msg(data)
|
|
184
|
-
|
|
185
|
-
def send_msg(self, data):
|
|
186
|
-
"""
|
|
187
|
-
发送机器人通用消息到企微群
|
|
188
|
-
:param data: 消息内容json数据
|
|
189
|
-
:return:
|
|
190
|
-
"""
|
|
191
|
-
try:
|
|
192
|
-
header = {
|
|
193
|
-
"Content-Type": "application/json"
|
|
194
|
-
}
|
|
195
|
-
response = requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={self.key}", headers=header, data=json.dumps(data))
|
|
196
|
-
response_text = json.loads(response.text)
|
|
197
|
-
if str(response_text.get('errcode')) != '0':
|
|
198
|
-
raise Exception(response_text)
|
|
199
|
-
if response.status_code == 200:
|
|
200
|
-
result = json.loads(response.text)
|
|
201
|
-
return result
|
|
202
|
-
else:
|
|
203
|
-
print("HTTP Error:", response.status_code)
|
|
204
|
-
return None
|
|
205
|
-
except Exception as err:
|
|
206
|
-
raise Exception("Send Chat Message error", err)
|
|
207
|
-
|
|
208
|
-
def img_to_md5(self, img_path):
|
|
209
|
-
# 读取图片文件并计算MD5值
|
|
210
|
-
with open(img_path, 'rb') as image_file:
|
|
211
|
-
image_data = image_file.read()
|
|
212
|
-
return hashlib.md5(image_data).hexdigest()
|
|
213
|
-
|
|
214
|
-
def img_to_base64(self, img_path):
|
|
215
|
-
# 读取图片文件并转换为Base64编码
|
|
216
|
-
with open(img_path, 'rb') as image_file:
|
|
217
|
-
image_data = image_file.read()
|
|
218
|
-
return base64.b64encode(image_data).decode('utf-8')
|
|
219
|
-
|
|
220
|
-
# 通过企微应用发送消息
|
|
221
|
-
class WxWorkAppBot:
|
|
222
|
-
def __init__(self, corpid, corpsecret, agentid):
|
|
223
|
-
self.corpid = corpid
|
|
224
|
-
self.corpsecret = corpsecret
|
|
225
|
-
self.agentid = agentid
|
|
226
|
-
self.access_token = self._getToken()
|
|
227
|
-
|
|
228
|
-
def _getToken(self):
|
|
229
|
-
try:
|
|
230
|
-
if all([self.corpid, self.corpsecret]):
|
|
231
|
-
url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&corpsecret={corpsecret}".format(
|
|
232
|
-
corpid=self.corpid, corpsecret=self.corpsecret)
|
|
233
|
-
response = requests.get(url)
|
|
234
|
-
if response.status_code == 200:
|
|
235
|
-
result = json.loads(response.text)
|
|
236
|
-
return result['access_token']
|
|
237
|
-
else:
|
|
238
|
-
print("HTTP Error:", response.status_code)
|
|
239
|
-
return None
|
|
240
|
-
except Exception as err:
|
|
241
|
-
raise Exception("get WeChat access Token error", err)
|
|
242
|
-
|
|
243
|
-
def _send_msg(self, data):
|
|
244
|
-
self._check_token()
|
|
245
|
-
try:
|
|
246
|
-
send_url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}".format(
|
|
247
|
-
access_token=self.access_token)
|
|
248
|
-
response = requests.post(send_url, json.dumps(data))
|
|
249
|
-
if response.status_code == 200:
|
|
250
|
-
result = json.loads(response.text)
|
|
251
|
-
return result
|
|
252
|
-
else:
|
|
253
|
-
print("HTTP Error:", response.status_code)
|
|
254
|
-
return None
|
|
255
|
-
except Exception as err:
|
|
256
|
-
raise Exception("send WeChat Message error", err)
|
|
257
|
-
|
|
258
|
-
def _check_token(self):
|
|
259
|
-
if self.access_token is None:
|
|
260
|
-
self._getToken()
|
|
261
|
-
|
|
262
|
-
def send_msg(self, data):
|
|
263
|
-
return self._send_msg(data)
|
|
264
|
-
|
|
265
|
-
def upload_media(self, filetype, filepath, filename):
|
|
266
|
-
"""
|
|
267
|
-
上传临时素材到企微并获取media_id
|
|
268
|
-
:param filetype: 图片(image)、语音(voice)、视频(video),普通文件(file)
|
|
269
|
-
:param filepath:
|
|
270
|
-
:param filename:
|
|
271
|
-
:return: media_id
|
|
272
|
-
"""
|
|
273
|
-
try:
|
|
274
|
-
self._check_token()
|
|
275
|
-
post_file_url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type={filetype}".format(
|
|
276
|
-
filetype=filetype,
|
|
277
|
-
access_token=self.access_token)
|
|
278
|
-
|
|
279
|
-
m = MultipartEncoder(
|
|
280
|
-
fields={filename: (filename, open(filepath + filename, 'rb'), 'text/plain')},
|
|
281
|
-
)
|
|
282
|
-
response = requests.post(url=post_file_url, data=m, headers={'Content-Type': m.content_type})
|
|
283
|
-
if response.status_code == 200:
|
|
284
|
-
result = json.loads(response.text)
|
|
285
|
-
return result['media_id']
|
|
286
|
-
else:
|
|
287
|
-
print("HTTP Error:", response.status_code)
|
|
288
|
-
return None
|
|
289
|
-
except Exception as err:
|
|
290
|
-
raise Exception("upload media error", err)
|
|
291
|
-
|
|
292
|
-
def get_media(self, media_id):
|
|
293
|
-
"""
|
|
294
|
-
获取临时素材
|
|
295
|
-
:param media_id:
|
|
296
|
-
:return: 返回二进制形式
|
|
297
|
-
"""
|
|
298
|
-
try:
|
|
299
|
-
self._check_token()
|
|
300
|
-
url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"
|
|
301
|
-
params = {
|
|
302
|
-
"access_token": self.access_token,
|
|
303
|
-
"media_id" : media_id
|
|
304
|
-
}
|
|
305
|
-
response = requests.get(url=url, params=params)
|
|
306
|
-
if response.status_code == 200:
|
|
307
|
-
content_type = response.headers.get('Content-Type')
|
|
308
|
-
if content_type == 'application/json':
|
|
309
|
-
response_data = json.loads(response.text)
|
|
310
|
-
print("Error:", response_data.get("errmsg"))
|
|
311
|
-
return None
|
|
312
|
-
else:
|
|
313
|
-
return response.content
|
|
314
|
-
else:
|
|
315
|
-
print("HTTP Error:", response.status_code)
|
|
316
|
-
return None
|
|
317
|
-
except Exception as err:
|
|
318
|
-
raise Exception("get media error", err)
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
-------------------------------------------------
|
|
4
|
+
@version : v1.0
|
|
5
|
+
@author : qsir
|
|
6
|
+
@contact : qsir@vxnote.com
|
|
7
|
+
@software : PyCharm
|
|
8
|
+
@filename : wxwork.py
|
|
9
|
+
@create time: 2025/08/03
|
|
10
|
+
@modify time: 2025/08/03
|
|
11
|
+
@describe :
|
|
12
|
+
-------------------------------------------------
|
|
13
|
+
"""
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import hashlib
|
|
17
|
+
import base64
|
|
18
|
+
import requests
|
|
19
|
+
from requests_toolbelt import MultipartEncoder
|
|
20
|
+
from datetime import datetime
|
|
21
|
+
|
|
22
|
+
# 通过企微群机器人发送消息
|
|
23
|
+
class WxWorkBot:
|
|
24
|
+
def __init__(self, key):
|
|
25
|
+
self.key = key
|
|
26
|
+
|
|
27
|
+
def upload_media(self, filepath):
|
|
28
|
+
"""
|
|
29
|
+
上传临时素材,给企微群里发文件消息时需要先将文件上传至企微临时素材中
|
|
30
|
+
:param filepath:
|
|
31
|
+
:return: 临时素材的media_id
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
headers = {
|
|
35
|
+
'Content-Type': 'multipart/form-data',
|
|
36
|
+
}
|
|
37
|
+
with open(filepath, 'rb') as f:
|
|
38
|
+
files = {
|
|
39
|
+
'media': (os.path.basename(filepath), f.read())
|
|
40
|
+
}
|
|
41
|
+
response = requests.post(
|
|
42
|
+
f'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={self.key}&type=file',
|
|
43
|
+
headers=headers, files=files)
|
|
44
|
+
response_text = json.loads(response.text)
|
|
45
|
+
if str(response_text.get('errcode')) != '0':
|
|
46
|
+
raise Exception(response_text)
|
|
47
|
+
if response.status_code == 200:
|
|
48
|
+
result = json.loads(response.text)
|
|
49
|
+
return result['media_id']
|
|
50
|
+
else:
|
|
51
|
+
print("HTTP Error:", response.status_code)
|
|
52
|
+
return None
|
|
53
|
+
except Exception as err:
|
|
54
|
+
raise Exception("upload_media error", err)
|
|
55
|
+
|
|
56
|
+
def send_file(self, file_path):
|
|
57
|
+
if not os.path.exists(file_path):
|
|
58
|
+
print('文件不存在: ', file_path)
|
|
59
|
+
return
|
|
60
|
+
"""
|
|
61
|
+
发送文件到群里
|
|
62
|
+
:param file_path:
|
|
63
|
+
:return:
|
|
64
|
+
"""
|
|
65
|
+
media_id = self.upload_media(file_path)
|
|
66
|
+
data = {
|
|
67
|
+
"msgtype": "file",
|
|
68
|
+
"file" : {
|
|
69
|
+
"media_id": media_id
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return self.send_msg(data)
|
|
73
|
+
|
|
74
|
+
def send_text(self, content, mentioned_list=None, mentioned_mobile_list=None):
|
|
75
|
+
"""
|
|
76
|
+
发送文本消息
|
|
77
|
+
:param content:
|
|
78
|
+
:param mentioned_list: 需要@的人userid
|
|
79
|
+
:param mentioned_mobile_list: 需要@的人手机号
|
|
80
|
+
:return:
|
|
81
|
+
"""
|
|
82
|
+
data = {
|
|
83
|
+
"msgtype": "text",
|
|
84
|
+
"text" : {
|
|
85
|
+
"content": content
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if mentioned_list is not None and mentioned_list:
|
|
89
|
+
data['text'].update({"mentioned_list": mentioned_list})
|
|
90
|
+
if mentioned_mobile_list is not None and mentioned_mobile_list:
|
|
91
|
+
data['text'].update({"mentioned_mobile_list": mentioned_mobile_list})
|
|
92
|
+
|
|
93
|
+
self.send_msg(data)
|
|
94
|
+
|
|
95
|
+
def send_markdown(self, content):
|
|
96
|
+
"""
|
|
97
|
+
发送Markdown消息
|
|
98
|
+
:param content:
|
|
99
|
+
:return:
|
|
100
|
+
"""
|
|
101
|
+
data = {
|
|
102
|
+
"msgtype" : "markdown",
|
|
103
|
+
"markdown": {
|
|
104
|
+
"content": content
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
self.send_msg(data)
|
|
108
|
+
|
|
109
|
+
def send_notify(self, title, sub_title_list, data_list):
|
|
110
|
+
"""
|
|
111
|
+
发送Markdown消息
|
|
112
|
+
:param content:
|
|
113
|
+
:return:
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
current_date = datetime.now().strftime("%Y-%m-%d")
|
|
117
|
+
header = f"{current_date} {title}\n\n"
|
|
118
|
+
|
|
119
|
+
arr_color = ['warning', 'info', 'warning']
|
|
120
|
+
arr_sub_header = [f"<font color='{arr_color[index]}'>{title}</font>" for index, title in enumerate(sub_title_list)]
|
|
121
|
+
sub_header = "\t".join(arr_sub_header) + "\n\n"
|
|
122
|
+
|
|
123
|
+
# 获取每个元素的行索引和列索引
|
|
124
|
+
arr_content = [
|
|
125
|
+
[
|
|
126
|
+
f'{value}' if col_idx == 0 else f"<font color='{arr_color[col_idx - 1]}'>{value}</font>"
|
|
127
|
+
for col_idx, value in enumerate(row)
|
|
128
|
+
] # 每行的元素组成一个子列表
|
|
129
|
+
for row_idx, row in enumerate(data_list) # 外层循环控制行
|
|
130
|
+
]
|
|
131
|
+
# 将二维数组转换为字符串
|
|
132
|
+
content = "\n".join(
|
|
133
|
+
# 对每行的元素列表使用 join(),用 \t 连接
|
|
134
|
+
"\t".join(row) for row in arr_content
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
data = {
|
|
138
|
+
"msgtype" : "markdown",
|
|
139
|
+
"markdown": {
|
|
140
|
+
"content": header + sub_header + content
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
self.send_msg(data)
|
|
144
|
+
|
|
145
|
+
def send_img(self, img_path):
|
|
146
|
+
"""
|
|
147
|
+
发送图片消息
|
|
148
|
+
图片(base64编码前)最大不能超过2M,支持JPG,PNG格式
|
|
149
|
+
:param img_path:
|
|
150
|
+
:return:
|
|
151
|
+
"""
|
|
152
|
+
data = {
|
|
153
|
+
"msgtype": "image",
|
|
154
|
+
"image" : {
|
|
155
|
+
"base64": self.img_to_base64(img_path),
|
|
156
|
+
"md5" : self.img_to_md5(img_path)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
self.send_msg(data)
|
|
160
|
+
|
|
161
|
+
def send_news(self, title, description, url, picurl):
|
|
162
|
+
"""
|
|
163
|
+
发送图文消息
|
|
164
|
+
:param title: 标题
|
|
165
|
+
:param description: 描述
|
|
166
|
+
:param url: 跳转URL
|
|
167
|
+
:param picurl: 图文图片地址
|
|
168
|
+
:return:
|
|
169
|
+
"""
|
|
170
|
+
data = {
|
|
171
|
+
"msgtype": "news",
|
|
172
|
+
"news" : {
|
|
173
|
+
"articles": [
|
|
174
|
+
{
|
|
175
|
+
"title" : title,
|
|
176
|
+
"description": description,
|
|
177
|
+
"url" : url,
|
|
178
|
+
"picurl" : picurl
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
self.send_msg(data)
|
|
184
|
+
|
|
185
|
+
def send_msg(self, data):
|
|
186
|
+
"""
|
|
187
|
+
发送机器人通用消息到企微群
|
|
188
|
+
:param data: 消息内容json数据
|
|
189
|
+
:return:
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
header = {
|
|
193
|
+
"Content-Type": "application/json"
|
|
194
|
+
}
|
|
195
|
+
response = requests.post(f"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={self.key}", headers=header, data=json.dumps(data))
|
|
196
|
+
response_text = json.loads(response.text)
|
|
197
|
+
if str(response_text.get('errcode')) != '0':
|
|
198
|
+
raise Exception(response_text)
|
|
199
|
+
if response.status_code == 200:
|
|
200
|
+
result = json.loads(response.text)
|
|
201
|
+
return result
|
|
202
|
+
else:
|
|
203
|
+
print("HTTP Error:", response.status_code)
|
|
204
|
+
return None
|
|
205
|
+
except Exception as err:
|
|
206
|
+
raise Exception("Send Chat Message error", err)
|
|
207
|
+
|
|
208
|
+
def img_to_md5(self, img_path):
|
|
209
|
+
# 读取图片文件并计算MD5值
|
|
210
|
+
with open(img_path, 'rb') as image_file:
|
|
211
|
+
image_data = image_file.read()
|
|
212
|
+
return hashlib.md5(image_data).hexdigest()
|
|
213
|
+
|
|
214
|
+
def img_to_base64(self, img_path):
|
|
215
|
+
# 读取图片文件并转换为Base64编码
|
|
216
|
+
with open(img_path, 'rb') as image_file:
|
|
217
|
+
image_data = image_file.read()
|
|
218
|
+
return base64.b64encode(image_data).decode('utf-8')
|
|
219
|
+
|
|
220
|
+
# 通过企微应用发送消息
|
|
221
|
+
class WxWorkAppBot:
|
|
222
|
+
def __init__(self, corpid, corpsecret, agentid):
|
|
223
|
+
self.corpid = corpid
|
|
224
|
+
self.corpsecret = corpsecret
|
|
225
|
+
self.agentid = agentid
|
|
226
|
+
self.access_token = self._getToken()
|
|
227
|
+
|
|
228
|
+
def _getToken(self):
|
|
229
|
+
try:
|
|
230
|
+
if all([self.corpid, self.corpsecret]):
|
|
231
|
+
url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={corpid}&corpsecret={corpsecret}".format(
|
|
232
|
+
corpid=self.corpid, corpsecret=self.corpsecret)
|
|
233
|
+
response = requests.get(url)
|
|
234
|
+
if response.status_code == 200:
|
|
235
|
+
result = json.loads(response.text)
|
|
236
|
+
return result['access_token']
|
|
237
|
+
else:
|
|
238
|
+
print("HTTP Error:", response.status_code)
|
|
239
|
+
return None
|
|
240
|
+
except Exception as err:
|
|
241
|
+
raise Exception("get WeChat access Token error", err)
|
|
242
|
+
|
|
243
|
+
def _send_msg(self, data):
|
|
244
|
+
self._check_token()
|
|
245
|
+
try:
|
|
246
|
+
send_url = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={access_token}".format(
|
|
247
|
+
access_token=self.access_token)
|
|
248
|
+
response = requests.post(send_url, json.dumps(data))
|
|
249
|
+
if response.status_code == 200:
|
|
250
|
+
result = json.loads(response.text)
|
|
251
|
+
return result
|
|
252
|
+
else:
|
|
253
|
+
print("HTTP Error:", response.status_code)
|
|
254
|
+
return None
|
|
255
|
+
except Exception as err:
|
|
256
|
+
raise Exception("send WeChat Message error", err)
|
|
257
|
+
|
|
258
|
+
def _check_token(self):
|
|
259
|
+
if self.access_token is None:
|
|
260
|
+
self._getToken()
|
|
261
|
+
|
|
262
|
+
def send_msg(self, data):
|
|
263
|
+
return self._send_msg(data)
|
|
264
|
+
|
|
265
|
+
def upload_media(self, filetype, filepath, filename):
|
|
266
|
+
"""
|
|
267
|
+
上传临时素材到企微并获取media_id
|
|
268
|
+
:param filetype: 图片(image)、语音(voice)、视频(video),普通文件(file)
|
|
269
|
+
:param filepath:
|
|
270
|
+
:param filename:
|
|
271
|
+
:return: media_id
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
self._check_token()
|
|
275
|
+
post_file_url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token={access_token}&type={filetype}".format(
|
|
276
|
+
filetype=filetype,
|
|
277
|
+
access_token=self.access_token)
|
|
278
|
+
|
|
279
|
+
m = MultipartEncoder(
|
|
280
|
+
fields={filename: (filename, open(filepath + filename, 'rb'), 'text/plain')},
|
|
281
|
+
)
|
|
282
|
+
response = requests.post(url=post_file_url, data=m, headers={'Content-Type': m.content_type})
|
|
283
|
+
if response.status_code == 200:
|
|
284
|
+
result = json.loads(response.text)
|
|
285
|
+
return result['media_id']
|
|
286
|
+
else:
|
|
287
|
+
print("HTTP Error:", response.status_code)
|
|
288
|
+
return None
|
|
289
|
+
except Exception as err:
|
|
290
|
+
raise Exception("upload media error", err)
|
|
291
|
+
|
|
292
|
+
def get_media(self, media_id):
|
|
293
|
+
"""
|
|
294
|
+
获取临时素材
|
|
295
|
+
:param media_id:
|
|
296
|
+
:return: 返回二进制形式
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
self._check_token()
|
|
300
|
+
url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"
|
|
301
|
+
params = {
|
|
302
|
+
"access_token": self.access_token,
|
|
303
|
+
"media_id" : media_id
|
|
304
|
+
}
|
|
305
|
+
response = requests.get(url=url, params=params)
|
|
306
|
+
if response.status_code == 200:
|
|
307
|
+
content_type = response.headers.get('Content-Type')
|
|
308
|
+
if content_type == 'application/json':
|
|
309
|
+
response_data = json.loads(response.text)
|
|
310
|
+
print("Error:", response_data.get("errmsg"))
|
|
311
|
+
return None
|
|
312
|
+
else:
|
|
313
|
+
return response.content
|
|
314
|
+
else:
|
|
315
|
+
print("HTTP Error:", response.status_code)
|
|
316
|
+
return None
|
|
317
|
+
except Exception as err:
|
|
318
|
+
raise Exception("get media error", err)
|