ryry-cli 6.17__tar.gz → 6.18__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.
Files changed (33) hide show
  1. {ryry_cli-6.17/ryry_cli.egg-info → ryry_cli-6.18}/PKG-INFO +1 -3
  2. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/constant.py +2 -2
  3. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_webapi.py +0 -10
  4. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/shared_memory.py +18 -0
  5. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/store.py +1 -1
  6. ryry_cli-6.18/ryry/taskUtils.py +496 -0
  7. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/upload.py +0 -28
  8. {ryry_cli-6.17 → ryry_cli-6.18/ryry_cli.egg-info}/PKG-INFO +1 -3
  9. {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/requires.txt +0 -3
  10. {ryry_cli-6.17 → ryry_cli-6.18}/setup.py +1 -2
  11. ryry_cli-6.17/ryry/taskUtils.py +0 -304
  12. {ryry_cli-6.17 → ryry_cli-6.18}/LICENSE +0 -0
  13. {ryry_cli-6.17 → ryry_cli-6.18}/README.md +0 -0
  14. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/__init__.py +0 -0
  15. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/daemon_base.py +0 -0
  16. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/daemon_manager.py +0 -0
  17. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/main.py +0 -0
  18. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/proxy_manager.py +0 -0
  19. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_server_socket.py +0 -0
  20. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_service.py +0 -0
  21. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/ryry_widget.py +0 -0
  22. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/__init__.py +0 -0
  23. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/daemon.py +0 -0
  24. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/main.py +0 -0
  25. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/script_template/run.py +0 -0
  26. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/server_func.py +0 -0
  27. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/task.py +0 -0
  28. {ryry_cli-6.17 → ryry_cli-6.18}/ryry/utils.py +0 -0
  29. {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/SOURCES.txt +0 -0
  30. {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/dependency_links.txt +0 -0
  31. {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/entry_points.txt +0 -0
  32. {ryry_cli-6.17 → ryry_cli-6.18}/ryry_cli.egg-info/top_level.txt +0 -0
  33. {ryry_cli-6.17 → ryry_cli-6.18}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 6.17
3
+ Version: 6.18
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -24,8 +24,6 @@ Requires-Dist: urlparser
24
24
  Requires-Dist: urllib3
25
25
  Requires-Dist: portalocker
26
26
  Requires-Dist: PyYAML
27
- Provides-Extra: with-mecord
28
- Requires-Dist: mecord-cli>=0.7.407; extra == "with-mecord"
29
27
 
30
28
  ryry Python Tool
31
29
  ===============================================
@@ -1,6 +1,6 @@
1
1
  #!!!!! do not change this file !!!!!
2
- app_version="6.17"
3
- app_bulld_anchor="Noh_2026-07-02 15:26:27.184223"
2
+ app_version="6.18"
3
+ app_bulld_anchor="Noh_2026-07-05 17:02:09.601187"
4
4
  app_name="ryry-cli"
5
5
  import sys, os
6
6
  if getattr(sys, 'frozen', False):
@@ -311,16 +311,6 @@ def upload(localFilePath, ext, keepItAlways=False, needTranscode=False, addition
311
311
  return _upload(localFilePath, ext, keepItAlways=keepItAlways)
312
312
  except:
313
313
  pass
314
- from mecord import upload as mecord_upload
315
- try:
316
- url = mecord_upload.upload(localFilePath,
317
- None,
318
- "sg",
319
- needTranscode=needTranscode,
320
- needAddtionUrl=True) #mecord会使用签名信息,强制needAddtionUrl
321
- return _changeDomain(url, "m.mecordai.com")
322
- except:
323
- pass
324
314
  def upload_oss(localFilePath, ext, bucket, endpoint):
325
315
  import hashlib
326
316
  import hmac
@@ -78,6 +78,23 @@ class SharedMemoryService:
78
78
  return 0o666
79
79
  return 0o600
80
80
 
81
+ def _path_permission_info(self, path: str) -> str:
82
+ try:
83
+ st = os.stat(path)
84
+ return f"mode={oct(st.st_mode & 0o7777)} uid={st.st_uid} gid={st.st_gid}"
85
+ except OSError as e:
86
+ return f"stat failed: {e}"
87
+
88
+ def _assert_shared_dir_writable(self):
89
+ if os.access(self.data_dir, os.W_OK | os.X_OK):
90
+ return
91
+ raise PermissionError(
92
+ f"shared memory dir is not writable: {self.data_dir} "
93
+ f"({self._path_permission_info(self.data_dir)}). "
94
+ f"fix: sudo chown -R $(id -u):$(id -g) {self.data_dir} "
95
+ f"or sudo chmod 1777 {self.data_dir}"
96
+ )
97
+
81
98
  def _prepare_existing_shared_file(self, path: str):
82
99
  if os.path.isdir(path):
83
100
  raise PermissionError(f"shared memory file path is a directory: {path}")
@@ -98,6 +115,7 @@ class SharedMemoryService:
98
115
  """确保固定共享目录和锁文件可写"""
99
116
  os.makedirs(self.data_dir, mode=self._shared_dir_mode(), exist_ok=True)
100
117
  self._chmod_if_possible(self.data_dir, self._shared_dir_mode())
118
+ self._assert_shared_dir_writable()
101
119
  self._touch_shared_file(self.lock_file)
102
120
  self._touch_shared_file(self.process_count_lock_file)
103
121
  self._prepare_existing_shared_file(self.data_file)
@@ -177,7 +177,7 @@ def extend():
177
177
  _genExtend()
178
178
  elif read_data["extend"].get("device_id", "") != read_data.get("deviceInfo", {}).get("device_id", ""):
179
179
  _genExtend()
180
- elif read_data["extend"].get("app", "") == "ryry-cli 6.17":
180
+ elif read_data["extend"].get("app", "") == "ryry-cli 6.18":
181
181
  _genExtend()
182
182
  elif any(k not in read_data["extend"] for k in ["device_id", "host_name", "cpu", "memory", "disk", "gpu"]):
183
183
  _genExtend()
@@ -0,0 +1,496 @@
1
+ import os
2
+ import requests
3
+ import datetime
4
+ import json
5
+ import socket
6
+ from io import BytesIO
7
+ from requests_toolbelt import MultipartEncoder
8
+ from urllib import parse
9
+ import base64
10
+ import calendar, time
11
+ import hashlib
12
+ from contextlib import contextmanager
13
+ from threading import RLock
14
+ from ryry import utils, constant
15
+
16
+ try:
17
+ import fcntl
18
+ except ImportError:
19
+ fcntl = None
20
+
21
+ # 获取版本信息的兼容函数
22
+ def get_version():
23
+ try:
24
+ from importlib.metadata import version
25
+ return version("ryry-cli")
26
+ except ImportError:
27
+ from importlib_metadata import version
28
+ return version("ryry-cli")
29
+
30
+ task_config_file = os.path.join(constant.base_path, f"task_config.txt")
31
+ def taskInfoWithFirstTask():
32
+ if os.path.exists(task_config_file):
33
+ with open(task_config_file, 'r', encoding='utf-8') as f:
34
+ data = json.load(f)
35
+ for it in data:
36
+ if it not in ["last_task_pts"]:
37
+ return it, data[it]
38
+ return None, None
39
+ def taskInfoWithUUID(taskUUID):
40
+ if os.path.exists(task_config_file):
41
+ with open(task_config_file, 'r', encoding='utf-8') as f:
42
+ task_config = json.load(f)
43
+ if taskUUID in task_config:
44
+ return task_config[taskUUID]
45
+ return None
46
+
47
+ WECHAT_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1b006560-7012-4bf0-8c5e-a23830bc0a0b"
48
+ def uploadFile2Wechat(filepath):
49
+ real_robot_url = WECHAT_URL
50
+ params = parse.parse_qs( parse.urlparse( real_robot_url ).query )
51
+ webHookKey=params['key'][0]
52
+ upload_url = f'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={webHookKey}&type=file'
53
+ headers = {"Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate",
54
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36"}
55
+ filename = os.path.basename(filepath)
56
+ try:
57
+ multipart = MultipartEncoder(
58
+ fields={'filename': filename, 'filelength': '', 'name': 'media', 'media': (filename, open(filepath, 'rb'), 'application/octet-stream')},
59
+ boundary='-------------------------acebdf13572468')
60
+ headers['Content-Type'] = multipart.content_type
61
+ resp = requests.post(upload_url, headers=headers, data=multipart, timeout=300)
62
+ json_res = resp.json()
63
+ if json_res.get('media_id'):
64
+ return json_res.get('media_id')
65
+ except Exception as e:
66
+ return ""
67
+ def notifyWechatRobot(param):
68
+ real_robot_url = WECHAT_URL
69
+ try:
70
+ s = requests.session()
71
+ s.headers.update({'Connection':'close'})
72
+ headers = dict()
73
+ headers['Content-Type'] = "application/json"
74
+ res = s.post(real_robot_url, json.dumps(param), headers=headers, verify=False, timeout=30)
75
+ s.close()
76
+ except Exception as e:
77
+ print(f"===== qyapi.weixin.qq.com fail ", True)
78
+
79
+ logs = {}
80
+ def taskPrint(taskUUID, msg):
81
+ global logs
82
+ if (taskUUID == None or len(taskUUID) == 0) and msg == None:
83
+ return
84
+ if taskUUID and msg == None:
85
+ del logs[taskUUID]
86
+ return
87
+ if taskUUID and msg:
88
+ if taskUUID not in logs:
89
+ logs[taskUUID] = []
90
+ logs[taskUUID].append(msg)
91
+ if taskUUID == None and msg:
92
+ for uuid in logs:
93
+ logs[uuid].append(msg)
94
+ print(msg)
95
+ def getTaskLog(taskUUID):
96
+ if taskUUID in logs:
97
+ return logs[taskUUID]
98
+ return []
99
+
100
+ def _uploadLog(taskUUID):
101
+ log_path = f"{constant.base_path}/log_{taskUUID}.log"
102
+ try:
103
+ with open(log_path, 'w', encoding='utf-8') as f:
104
+ f.write("\n".join(getTaskLog(taskUUID)))
105
+ notifyWechatRobot({
106
+ "msgtype": "file",
107
+ "file": {
108
+ "media_id": uploadFile2Wechat(log_path)
109
+ }
110
+ })
111
+ except:
112
+ pass
113
+ finally:
114
+ if os.path.exists(log_path):
115
+ os.remove(log_path)
116
+
117
+ def notifyTaskFail(taskUUID, reason):
118
+ try:
119
+ real_reason = ""
120
+ if len(reason) > 2010:
121
+ real_reason = f"{reason[0:1000]}\n...\n{reason[len(reason)-1000:]}"
122
+ else:
123
+ real_reason = reason
124
+ notifyWechatRobot({
125
+ "msgtype": "markdown",
126
+ "markdown": {
127
+ "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>失败\n<{real_reason}>"
128
+ }
129
+ })
130
+ _uploadLog(taskUUID)
131
+ except:
132
+ pass
133
+
134
+ def notifyServerError(taskUUID):
135
+ try:
136
+ notifyWechatRobot({
137
+ "msgtype": "markdown",
138
+ "markdown": {
139
+ "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>上报失败, retry..."
140
+ }
141
+ })
142
+ except:
143
+ pass
144
+
145
+ def notifyScriptError(taskUUID):
146
+ try:
147
+ notifyWechatRobot({
148
+ "msgtype": "markdown",
149
+ "markdown": {
150
+ "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>异常"
151
+ }
152
+ })
153
+ _uploadLog(taskUUID)
154
+ except:
155
+ pass
156
+
157
+ def idlingNotify(cnt):
158
+ device_id = utils.generate_unique_id()
159
+ machine_name = socket.gethostname()
160
+ hour = int(float(cnt)/(60.0*60.0))
161
+ if hour<72:
162
+ if hour not in [1, 2, 3, 10, 30, 50, 70]:
163
+ return
164
+ notifyWechatRobot({
165
+ "msgtype": "text",
166
+ "text": {
167
+ "content": f"机器<{machine_name}[{device_id}]> 空转{hour}小时"
168
+ }
169
+ })
170
+
171
+ def onlineNotify():
172
+ device_id = utils.generate_unique_id()
173
+ machine_name = socket.gethostname()
174
+ ver = get_version()
175
+ notifyWechatRobot({
176
+ "msgtype": "text",
177
+ "text": {
178
+ "content": f"机器<{machine_name}[{device_id}]>[{ver}] 上线"
179
+ }
180
+ })
181
+
182
+ def restartNotify(msg):
183
+ device_id = utils.generate_unique_id()
184
+ machine_name = socket.gethostname()
185
+ notifyWechatRobot({
186
+ "msgtype": "text",
187
+ "text": {
188
+ "content": f"机器<{machine_name}[{device_id}]> 即将下线,原因:{msg}"
189
+ }
190
+ })
191
+
192
+ def offlineNotify():
193
+ device_id = utils.generate_unique_id()
194
+ machine_name = socket.gethostname()
195
+ ver = get_version()
196
+ notifyWechatRobot({
197
+ "msgtype": "text",
198
+ "text": {
199
+ "content": f"机器<{machine_name}[{device_id}]> 下线"
200
+ }
201
+ })
202
+
203
+ #===================================================== counter ===============================================#
204
+ task_counter_file = os.path.join(constant.base_path, "task_counter.txt")
205
+ task_counter_lock_file = os.path.join(constant.base_path, "task_counter.lock")
206
+ counter_lock = RLock()
207
+
208
+ @contextmanager
209
+ def counterFileLock():
210
+ with counter_lock:
211
+ lock_fp = open(task_counter_lock_file, 'a+', encoding='utf-8')
212
+ try:
213
+ if fcntl:
214
+ fcntl.flock(lock_fp.fileno(), fcntl.LOCK_EX)
215
+ yield
216
+ finally:
217
+ if fcntl:
218
+ fcntl.flock(lock_fp.fileno(), fcntl.LOCK_UN)
219
+ lock_fp.close()
220
+
221
+ def _readCounterData():
222
+ if os.path.exists(task_counter_file) == False:
223
+ return {}
224
+ try:
225
+ with open(task_counter_file, 'r', encoding='utf-8') as f:
226
+ data = json.load(f)
227
+ if isinstance(data, dict):
228
+ return data
229
+ except:
230
+ pass
231
+ return {}
232
+
233
+ def _writeCounterData(data):
234
+ tmp_file = f"{task_counter_file}.tmp"
235
+ with open(tmp_file, 'w', encoding='utf-8') as f:
236
+ json.dump(data, f)
237
+ os.replace(tmp_file, task_counter_file)
238
+
239
+ def _hourKeys(data):
240
+ return [str(i) for i in range(0, 24) if str(i) in data and isinstance(data[str(i)], dict)]
241
+
242
+ def _resetCounterData(counter_date, last_report_date=None):
243
+ data = {
244
+ "_counter_date": counter_date
245
+ }
246
+ if last_report_date:
247
+ data["_last_report_date"] = last_report_date
248
+ return data
249
+
250
+ def _counterReportStats(data):
251
+ s_cnt = 0
252
+ f_cnt = 0
253
+ all_day_usage = 0
254
+ s_l = []
255
+ f_l = []
256
+ usage_l = []
257
+ for i in range(0,24):
258
+ usage = 0
259
+ if str(i) in data:
260
+ s_cnt += data[str(i)]["success"]
261
+ f_cnt += data[str(i)]["fail"]
262
+ s_l.append(data[str(i)]["success"])
263
+ f_l.append(data[str(i)]["fail"])
264
+ if "usage" in data[str(i)]:
265
+ usage = data[str(i)]["usage"]
266
+ all_day_usage += usage
267
+ else:
268
+ s_cnt += 0
269
+ f_cnt += 0
270
+ s_l.append(0)
271
+ f_l.append(0)
272
+ usage_l.append(int((float(usage)/float(60*60))*100) if usage > 0 else 0)
273
+ usage_percentage = int((float(all_day_usage)/float(24*60*60))*100)
274
+ return {
275
+ "success": s_cnt,
276
+ "fail": f_cnt,
277
+ "total": s_cnt + f_cnt,
278
+ "usage": usage_percentage,
279
+ "hour_success": s_l,
280
+ "hour_fail": f_l,
281
+ "hour_usage": usage_l
282
+ }
283
+
284
+ def _sendCounterMarkdownReport(report_date, stats):
285
+ notifyWechatRobot({
286
+ "msgtype": "markdown",
287
+ "markdown": {
288
+ "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> {report_date} 日报 \n\n\
289
+ >过去24小时执行任务<<font color=\"warning\">{stats['total']}</font>>个, 负载<<font color=\"warning\">{stats['usage']}%</font>> \n\
290
+ >成功<font color=\"warning\">{stats['success']}</font>个 \n\
291
+ >失败<font color=\"warning\">{stats['fail']}</font>个"
292
+ }
293
+ })
294
+
295
+ def _reportFont(size, bold=False):
296
+ try:
297
+ from PIL import ImageFont
298
+ font_paths = [
299
+ "/System/Library/Fonts/STHeiti Medium.ttc" if bold else "/System/Library/Fonts/STHeiti Light.ttc",
300
+ "/System/Library/Fonts/PingFang.ttc",
301
+ "/Library/Fonts/Arial Unicode.ttf",
302
+ "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
303
+ "/usr/share/fonts/opentype/noto/NotoSansCJKSC-Regular.otf",
304
+ "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
305
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
306
+ "C:/Windows/Fonts/msyhbd.ttc" if bold else "C:/Windows/Fonts/msyh.ttc",
307
+ ]
308
+ for font_path in font_paths:
309
+ if font_path and os.path.exists(font_path):
310
+ return ImageFont.truetype(font_path, size)
311
+ return ImageFont.load_default()
312
+ except:
313
+ return None
314
+
315
+ def _text(draw, xy, text, fill, font=None, **kwargs):
316
+ try:
317
+ draw.text(xy, text, fill=fill, font=font, **kwargs)
318
+ except TypeError:
319
+ kwargs.pop("anchor", None)
320
+ draw.text(xy, text, fill=fill, font=font, **kwargs)
321
+ except UnicodeEncodeError:
322
+ safe_text = text.encode("ascii", "ignore").decode("ascii")
323
+ try:
324
+ draw.text(xy, safe_text, fill=fill, font=font, **kwargs)
325
+ except TypeError:
326
+ kwargs.pop("anchor", None)
327
+ draw.text(xy, safe_text, fill=fill, font=font, **kwargs)
328
+
329
+ def _roundedRectangle(draw, box, radius, fill, outline=None):
330
+ if hasattr(draw, "rounded_rectangle"):
331
+ draw.rounded_rectangle(box, radius=radius, fill=fill, outline=outline)
332
+ else:
333
+ draw.rectangle(box, fill=fill, outline=outline)
334
+
335
+ def _counterReportImageBytes(report_date, stats):
336
+ from PIL import Image, ImageDraw
337
+
338
+ width = 1120
339
+ height = 700
340
+ margin = 54
341
+ bg = "#f6f8fb"
342
+ ink = "#172033"
343
+ muted = "#667085"
344
+ grid = "#d8dee8"
345
+ green = "#34a853"
346
+ red = "#ea4335"
347
+ blue = "#1a73e8"
348
+
349
+ img = Image.new("RGB", (width, height), bg)
350
+ draw = ImageDraw.Draw(img)
351
+ title_font = _reportFont(36, True)
352
+ card_font = _reportFont(22, True)
353
+ text_font = _reportFont(18)
354
+ small_font = _reportFont(14)
355
+
356
+ host = socket.gethostname()
357
+ _text(draw, (margin, 38), f"机器<{host}> {report_date} 日报", ink, title_font)
358
+ _text(draw, (margin, 88), "每日任务执行概览", muted, text_font)
359
+
360
+ cards = [
361
+ ("执行任务", stats["total"], blue),
362
+ ("成功", stats["success"], green),
363
+ ("失败", stats["fail"], red),
364
+ ("负载", f"{stats['usage']}%", "#f29900"),
365
+ ]
366
+ card_y = 126
367
+ card_w = 238
368
+ card_h = 92
369
+ gap = 18
370
+ for idx, (label, value, color) in enumerate(cards):
371
+ x = margin + idx * (card_w + gap)
372
+ _roundedRectangle(draw, (x, card_y, x + card_w, card_y + card_h), 16, "#ffffff", "#e4e8f0")
373
+ _text(draw, (x + 22, card_y + 18), label, muted, text_font)
374
+ _text(draw, (x + 22, card_y + 46), str(value), color, card_font)
375
+
376
+ chart_left = margin
377
+ chart_top = 272
378
+ chart_right = width - margin
379
+ chart_bottom = height - 86
380
+ chart_w = chart_right - chart_left
381
+ _roundedRectangle(draw, (chart_left - 16, chart_top - 26, chart_right + 16, chart_bottom + 48), 18, "#ffffff", "#e4e8f0")
382
+ _text(draw, (chart_left, chart_top - 6), "24小时任务分布", ink, card_font)
383
+ _text(draw, (chart_right - 220, chart_top - 2), "绿色=成功 红色=失败", muted, small_font)
384
+
385
+ plot_top = chart_top + 48
386
+ plot_bottom = chart_bottom - 36
387
+ plot_h = plot_bottom - plot_top
388
+ max_count = max([s + f for s, f in zip(stats["hour_success"], stats["hour_fail"])] + [1])
389
+ for i in range(0, 5):
390
+ y = plot_bottom - int(plot_h * i / 4)
391
+ draw.line((chart_left, y, chart_right, y), fill=grid, width=1)
392
+ tick = int(max_count * i / 4)
393
+ _text(draw, (chart_left - 36, y - 8), str(tick), muted, small_font)
394
+
395
+ slot = chart_w / 24.0
396
+ bar_w = max(12, int(slot * 0.54))
397
+ for hour in range(0, 24):
398
+ success = stats["hour_success"][hour]
399
+ fail = stats["hour_fail"][hour]
400
+ total = success + fail
401
+ x_center = chart_left + slot * hour + slot / 2
402
+ x0 = int(x_center - bar_w / 2)
403
+ x1 = int(x_center + bar_w / 2)
404
+ total_h = int((float(total) / max_count) * plot_h) if total > 0 else 0
405
+ y_total = plot_bottom - total_h
406
+ fail_h = int((float(fail) / max(total, 1)) * total_h) if total > 0 else 0
407
+ y_fail = y_total + fail_h
408
+ if success > 0:
409
+ draw.rectangle((x0, y_fail, x1, plot_bottom), fill=green)
410
+ if fail > 0:
411
+ draw.rectangle((x0, y_total, x1, y_fail), fill=red)
412
+ if total > 0:
413
+ _text(draw, (x_center, y_total - 18), str(total), ink, small_font, anchor="mm")
414
+ if hour % 2 == 0:
415
+ _text(draw, (x_center, plot_bottom + 18), str(hour), muted, small_font, anchor="mm")
416
+ if stats["hour_usage"][hour] > 0 and hour % 3 == 0:
417
+ _text(draw, (x_center, plot_bottom + 38), f"{stats['hour_usage'][hour]}%", muted, small_font, anchor="mm")
418
+
419
+ _text(draw, (chart_left, height - 50), "柱顶数字为任务数;底部百分比为该小时任务累计忙时。", muted, small_font)
420
+ output = BytesIO()
421
+ img.save(output, format="PNG", optimize=True)
422
+ return output.getvalue()
423
+
424
+ def _sendCounterImageReport(report_date, stats):
425
+ image_bytes = _counterReportImageBytes(report_date, stats)
426
+ md5 = hashlib.md5()
427
+ md5.update(image_bytes)
428
+ notifyWechatRobot({
429
+ "msgtype": "image",
430
+ "image": {
431
+ "base64": str(base64.b64encode(image_bytes), encoding='utf-8'),
432
+ "md5": md5.hexdigest()
433
+ }
434
+ })
435
+
436
+ def _notifyCounterWithData(data, report_date):
437
+ stats = _counterReportStats(data)
438
+ try:
439
+ _sendCounterImageReport(report_date, stats)
440
+ except:
441
+ _sendCounterMarkdownReport(report_date, stats)
442
+
443
+ def _rolloverCounterIfNeed(data, now):
444
+ today = now.strftime('%Y-%m-%d')
445
+ hour_keys = _hourKeys(data)
446
+ if "_counter_date" not in data:
447
+ # Legacy counters have no date and may already contain stale totals.
448
+ # Start a fresh dated counter so an old file cannot pollute tomorrow's report.
449
+ return _resetCounterData(today), True
450
+ counter_date = data.get("_counter_date")
451
+ if counter_date == today:
452
+ return data, False
453
+ if len(hour_keys) == 0:
454
+ return _resetCounterData(today, data.get("_last_report_date")), True
455
+ if data.get("_last_report_date") != counter_date:
456
+ _notifyCounterWithData(data, counter_date)
457
+ return _resetCounterData(today, counter_date), True
458
+
459
+ def notifyCounterIfNeed():
460
+ try:
461
+ with counterFileLock():
462
+ data = _readCounterData()
463
+ data, changed = _rolloverCounterIfNeed(data, datetime.datetime.now())
464
+ if changed:
465
+ _writeCounterData(data)
466
+ except:
467
+ pass
468
+
469
+ def saveCounter(taskUUID, duration, isSuccess):
470
+ try:
471
+ with counterFileLock():
472
+ now = datetime.datetime.now()
473
+ data = _readCounterData()
474
+ data, _ = _rolloverCounterIfNeed(data, now)
475
+ if "_counter_date" not in data:
476
+ data["_counter_date"] = now.strftime('%Y-%m-%d')
477
+ #update
478
+ now_hour = str(now.hour)
479
+ if now_hour in data:
480
+ if isSuccess:
481
+ data[now_hour]["success"] += 1
482
+ else:
483
+ data[now_hour]["fail"] += 1
484
+ if "usage" not in data[now_hour]:
485
+ data[now_hour]["usage"] = 0
486
+ data[now_hour]["usage"] += duration
487
+ else:
488
+ data[now_hour] = {
489
+ "success" : 1 if isSuccess else 0,
490
+ "fail" : 0 if isSuccess else 1,
491
+ "usage" : duration
492
+ }
493
+ #save
494
+ _writeCounterData(data)
495
+ except:
496
+ pass
@@ -59,12 +59,6 @@ def upload(src, taskUUID=None, needTranscode=False, keepItAlways=False, addition
59
59
  taskInfo = ryry_taskUtils.taskInfoWithUUID(taskUUID)
60
60
  if taskInfo:
61
61
  targetDomain = taskInfo.get("domain", None)
62
- if targetDomain == None or len(targetDomain) <= 0:
63
- from mecord import taskUtils as mecord_taskUtils
64
- uuid_has_country_info = mecord_taskUtils.taskCountryWithUUID(taskUUID)
65
- if uuid_has_country_info:
66
- #这里不区分meco还是mecord,请求meco/mecord后端接口时,根据本地状态来向不同服务器请求
67
- targetDomain = "https://m.mecoai.cn/"
68
62
 
69
63
  needDeleteSrc = False
70
64
  if needTranscode:
@@ -96,12 +90,6 @@ def uploadByDomain(src, targetDomain,
96
90
  ext = os.path.splitext(file_name)[-1][1:]
97
91
 
98
92
  parsed_domain, upload_path = parseDomainAndPath(targetDomain)
99
- if isMecordConfig(parsed_domain):
100
- from mecord import upload as mecord_upload
101
- return mecord_upload.upload(src, taskUUID,
102
- needTranscode=needTranscode,
103
- needAddtionUrl=True) #mecord会使用签名信息,强制needAddtionUrl
104
-
105
93
  if isMekoConfig(parsed_domain):
106
94
  return uploadToMeko(src, ext=ext)
107
95
 
@@ -338,8 +326,6 @@ DOMAIN_CONFIG = {
338
326
  "res.zjtemplate.com": {"type": "oss", "config": oss_res_config},
339
327
  "upload.zjtemplate.com": {"type": "oss", "config": oss_upload_config},
340
328
  "oss.zjtemplate.com": {"type": "oss", "config": oss_template_config},
341
- # "*.mecoai.cn": {"type": "mecord-cli", "config": {}},
342
- # "*.mecordai.com": {"type": "mecord-cli", "config": {}},
343
329
  "*.mekoapp.com": {"type": "", "config": {}},
344
330
  }
345
331
 
@@ -370,12 +356,6 @@ def getOssConfig(targetDomain):
370
356
  return config["config"]
371
357
  return None
372
358
 
373
- def isMecordConfig(targetDomain):
374
- config = get_domain_config(targetDomain)
375
- if config and config["type"] == "mecord-cli":
376
- return True
377
- return False
378
-
379
359
  def isMekoConfig(targetDomain):
380
360
  config = get_domain_config(targetDomain)
381
361
  if config:
@@ -418,14 +398,6 @@ def getFirstSupportSubdomain():
418
398
  for domain, config in DOMAIN_CONFIG.items():
419
399
  if config["type"] == "oss":
420
400
  return {"type": "oss", "config": config["config"], "domain": config["config"]["domain"]}
421
- if config["type"] == "mecord-cli":
422
- # 对于通配符域名,返回一个示例域名
423
- if domain.startswith("*."):
424
- # 将 *.mecoai.cn 转换为 example.mecoai.cn
425
- example_domain = "example" + domain[1:]
426
- return {"type": "mecord-cli", "config": {}, "domain": example_domain}
427
- else:
428
- return {"type": "mecord-cli", "config": {}, "domain": domain}
429
401
  elif config["type"] == "ftp":
430
402
  ftp_config = config["config"]
431
403
  if _can_connect_ftp(ftp_config["host"], ftp_config["port"], ftp_config["username"], ftp_config["password"], ftp_config["writepath"]):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ryry-cli
3
- Version: 6.17
3
+ Version: 6.18
4
4
  Summary: ryry tools
5
5
  Home-page: https://github.com/dalipenMedia
6
6
  Author: dalipen
@@ -24,8 +24,6 @@ Requires-Dist: urlparser
24
24
  Requires-Dist: urllib3
25
25
  Requires-Dist: portalocker
26
26
  Requires-Dist: PyYAML
27
- Provides-Extra: with-mecord
28
- Requires-Dist: mecord-cli>=0.7.407; extra == "with-mecord"
29
27
 
30
28
  ryry Python Tool
31
29
  ===============================================
@@ -11,6 +11,3 @@ urlparser
11
11
  urllib3
12
12
  portalocker
13
13
  PyYAML
14
-
15
- [with_mecord]
16
- mecord-cli>=0.7.407
@@ -3,7 +3,7 @@ import os
3
3
  import subprocess
4
4
  import datetime
5
5
 
6
- ryry_version = "6.17"
6
+ ryry_version = "6.18"
7
7
  cur_dir = os.path.dirname(os.path.abspath(__file__))
8
8
  constanspy = os.path.join(cur_dir, "ryry", "constant.py")
9
9
  try:
@@ -73,7 +73,6 @@ setuptools.setup(
73
73
  'PyYAML'
74
74
  ],
75
75
  extras_require={
76
- 'with_mecord': ['mecord-cli>=0.7.407'],
77
76
  },
78
77
  dependency_links=[],
79
78
  entry_points={
@@ -1,304 +0,0 @@
1
- import os
2
- import requests
3
- import datetime
4
- import json
5
- import socket
6
- from requests_toolbelt import MultipartEncoder
7
- from urllib import parse
8
- import base64
9
- import calendar, time
10
- import hashlib
11
- from ryry import utils, constant
12
-
13
- # 获取版本信息的兼容函数
14
- def get_version():
15
- try:
16
- from importlib.metadata import version
17
- return version("ryry-cli")
18
- except ImportError:
19
- from importlib_metadata import version
20
- return version("ryry-cli")
21
-
22
- task_config_file = os.path.join(constant.base_path, f"task_config.txt")
23
- def taskInfoWithFirstTask():
24
- if os.path.exists(task_config_file):
25
- with open(task_config_file, 'r', encoding='utf-8') as f:
26
- data = json.load(f)
27
- for it in data:
28
- if it not in ["last_task_pts"]:
29
- return it, data[it]
30
- return None, None
31
- def taskInfoWithUUID(taskUUID):
32
- if os.path.exists(task_config_file):
33
- with open(task_config_file, 'r', encoding='utf-8') as f:
34
- task_config = json.load(f)
35
- if taskUUID in task_config:
36
- return task_config[taskUUID]
37
- return None
38
-
39
- WECHAT_URL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=1b006560-7012-4bf0-8c5e-a23830bc0a0b"
40
- def uploadFile2Wechat(filepath):
41
- real_robot_url = WECHAT_URL
42
- params = parse.parse_qs( parse.urlparse( real_robot_url ).query )
43
- webHookKey=params['key'][0]
44
- upload_url = f'https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key={webHookKey}&type=file'
45
- headers = {"Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate",
46
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.100 Safari/537.36"}
47
- filename = os.path.basename(filepath)
48
- try:
49
- multipart = MultipartEncoder(
50
- fields={'filename': filename, 'filelength': '', 'name': 'media', 'media': (filename, open(filepath, 'rb'), 'application/octet-stream')},
51
- boundary='-------------------------acebdf13572468')
52
- headers['Content-Type'] = multipart.content_type
53
- resp = requests.post(upload_url, headers=headers, data=multipart, timeout=300)
54
- json_res = resp.json()
55
- if json_res.get('media_id'):
56
- return json_res.get('media_id')
57
- except Exception as e:
58
- return ""
59
- def notifyWechatRobot(param):
60
- real_robot_url = WECHAT_URL
61
- try:
62
- s = requests.session()
63
- s.headers.update({'Connection':'close'})
64
- headers = dict()
65
- headers['Content-Type'] = "application/json"
66
- res = s.post(real_robot_url, json.dumps(param), headers=headers, verify=False, timeout=30)
67
- s.close()
68
- except Exception as e:
69
- print(f"===== qyapi.weixin.qq.com fail ", True)
70
-
71
- logs = {}
72
- def taskPrint(taskUUID, msg):
73
- global logs
74
- if (taskUUID == None or len(taskUUID) == 0) and msg == None:
75
- return
76
- if taskUUID and msg == None:
77
- del logs[taskUUID]
78
- return
79
- if taskUUID and msg:
80
- if taskUUID not in logs:
81
- logs[taskUUID] = []
82
- logs[taskUUID].append(msg)
83
- if taskUUID == None and msg:
84
- for uuid in logs:
85
- logs[uuid].append(msg)
86
- print(msg)
87
- def getTaskLog(taskUUID):
88
- if taskUUID in logs:
89
- return logs[taskUUID]
90
- return []
91
-
92
- def _uploadLog(taskUUID):
93
- log_path = f"{constant.base_path}/log_{taskUUID}.log"
94
- try:
95
- with open(log_path, 'w', encoding='utf-8') as f:
96
- f.write("\n".join(getTaskLog(taskUUID)))
97
- notifyWechatRobot({
98
- "msgtype": "file",
99
- "file": {
100
- "media_id": uploadFile2Wechat(log_path)
101
- }
102
- })
103
- except:
104
- pass
105
- finally:
106
- if os.path.exists(log_path):
107
- os.remove(log_path)
108
-
109
- def notifyTaskFail(taskUUID, reason):
110
- try:
111
- real_reason = ""
112
- if len(reason) > 2010:
113
- real_reason = f"{reason[0:1000]}\n...\n{reason[len(reason)-1000:]}"
114
- else:
115
- real_reason = reason
116
- notifyWechatRobot({
117
- "msgtype": "markdown",
118
- "markdown": {
119
- "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>失败\n<{real_reason}>"
120
- }
121
- })
122
- _uploadLog(taskUUID)
123
- except:
124
- pass
125
-
126
- def notifyServerError(taskUUID):
127
- try:
128
- notifyWechatRobot({
129
- "msgtype": "markdown",
130
- "markdown": {
131
- "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>上报失败, retry..."
132
- }
133
- })
134
- except:
135
- pass
136
-
137
- def notifyScriptError(taskUUID):
138
- try:
139
- notifyWechatRobot({
140
- "msgtype": "markdown",
141
- "markdown": {
142
- "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> 执行任务<{taskUUID}>异常"
143
- }
144
- })
145
- _uploadLog(taskUUID)
146
- except:
147
- pass
148
-
149
- def idlingNotify(cnt):
150
- device_id = utils.generate_unique_id()
151
- machine_name = socket.gethostname()
152
- hour = int(float(cnt)/(60.0*60.0))
153
- if hour<72:
154
- if hour not in [1, 2, 3, 10, 30, 50, 70]:
155
- return
156
- notifyWechatRobot({
157
- "msgtype": "text",
158
- "text": {
159
- "content": f"机器<{machine_name}[{device_id}]> 空转{hour}小时"
160
- }
161
- })
162
-
163
- def onlineNotify():
164
- device_id = utils.generate_unique_id()
165
- machine_name = socket.gethostname()
166
- ver = get_version()
167
- notifyWechatRobot({
168
- "msgtype": "text",
169
- "text": {
170
- "content": f"机器<{machine_name}[{device_id}]>[{ver}] 上线"
171
- }
172
- })
173
-
174
- def restartNotify(msg):
175
- device_id = utils.generate_unique_id()
176
- machine_name = socket.gethostname()
177
- notifyWechatRobot({
178
- "msgtype": "text",
179
- "text": {
180
- "content": f"机器<{machine_name}[{device_id}]> 即将下线,原因:{msg}"
181
- }
182
- })
183
-
184
- def offlineNotify():
185
- device_id = utils.generate_unique_id()
186
- machine_name = socket.gethostname()
187
- ver = get_version()
188
- notifyWechatRobot({
189
- "msgtype": "text",
190
- "text": {
191
- "content": f"机器<{machine_name}[{device_id}]> 下线"
192
- }
193
- })
194
-
195
- #===================================================== counter ===============================================#
196
- task_counter_file = os.path.join(constant.base_path, "task_counter.txt")
197
- def notifyCounterIfNeed():
198
- if os.path.exists(task_counter_file) == False:
199
- return
200
- with open(task_counter_file, 'r', encoding='utf-8') as f:
201
- data = json.load(f)
202
- now_hour = datetime.datetime.now().hour
203
- if now_hour == 0 and len(data) > 2:
204
- yesterday = (datetime.datetime.now() + datetime.timedelta(days=-1)).strftime('%Y-%m-%d')
205
- s_cnt = 0
206
- f_cnt = 0
207
- all_day_usage = 0
208
- t_l = []
209
- s_l = []
210
- f_l = []
211
- for i in range(0,24):
212
- tips = ""
213
- if str(i) in data:
214
- s_cnt += data[str(i)]["success"]
215
- f_cnt += data[str(i)]["fail"]
216
- s_l.append(data[str(i)]["success"])
217
- f_l.append(data[str(i)]["fail"])
218
- if "usage" in data[str(i)]:
219
- all_day_usage += data[str(i)]["usage"]
220
- usage_percentage = int((float(data[str(i)]["usage"])/float(60*60))*100)
221
- tips = f"({usage_percentage}%)"
222
- else:
223
- s_cnt += 0
224
- f_cnt += 0
225
- s_l.append(0)
226
- f_l.append(0)
227
- t_l.append(f"{i}{tips}")
228
- usage_percentage = int((float(all_day_usage)/float(24*60*60))*100)
229
- notifyWechatRobot({
230
- "msgtype": "markdown",
231
- "markdown": {
232
- "content": f"机器<<font color=\"warning\">{socket.gethostname()}</font>> {yesterday} 日报 \n\n\
233
- >过去24小时执行任务<<font color=\"warning\">{s_cnt+f_cnt}</font>>个, 负载<<font color=\"warning\">{usage_percentage}%</font>> \n\
234
- >成功<font color=\"warning\">{s_cnt}</font>个 \n\
235
- >失败<font color=\"warning\">{f_cnt}</font>个"
236
- }
237
- })
238
- os.remove(task_counter_file)
239
-
240
- # import subprocess, platform
241
- # #darwin Command Line cannot create pyplot gui because not application with gui
242
- # if platform.system() != 'Darwin':
243
- # try:
244
- # import matplotlib.pyplot as plt
245
- # plt.figure(figsize=(8,3))
246
- # plt.rcParams.update({
247
- # 'font.size': 7
248
- # })
249
- # plt.bar(t_l, s_l, color='g', label='success')
250
- # plt.bar(t_l, f_l, bottom=s_l, color='r', label='fail')
251
- # plt.title(f'[{socket.gethostname()}] [{yesterday}] success/fail={s_cnt}/{f_cnt}')
252
- # plt.xlabel('time')
253
- # plt.xticks(ticks=t_l,rotation=45)
254
- # plt.ylabel('count')
255
- # plt.subplots_adjust(bottom=0.25)
256
- # plt.legend()
257
- # fff = os.path.join(constant.base_path, "plt.png")
258
- # plt.savefig(fff)
259
- # with open(fff, "rb") as f:
260
- # encode_string = str(base64.b64encode(f.read()), encoding='utf-8')
261
- # md5 = hashlib.md5()
262
- # md5.update(base64.b64decode(encode_string))
263
- # hash = md5.hexdigest()
264
- # notifyWechatRobot({
265
- # "msgtype": "image",
266
- # "image": {
267
- # "base64": encode_string,
268
- # "md5": hash
269
- # }
270
- # })
271
- # os.remove(fff)
272
- # os.remove(task_counter_file)
273
- # except:
274
- # pass
275
-
276
- def saveCounter(taskUUID, duration, isSuccess):
277
- try:
278
- notifyCounterIfNeed()
279
- if os.path.exists(task_counter_file) == False:
280
- with open(task_counter_file, 'w', encoding='utf-8') as f:
281
- json.dump({}, f)
282
- with open(task_counter_file, 'r', encoding='utf-8') as f:
283
- data = json.load(f)
284
- #update
285
- now_hour = str(datetime.datetime.now().hour)
286
- if now_hour in data:
287
- if isSuccess:
288
- data[now_hour]["success"] += 1
289
- else:
290
- data[now_hour]["fail"] += 1
291
- if "usage" not in data[now_hour]:
292
- data[now_hour]["usage"] = 0
293
- data[now_hour]["usage"] += duration
294
- else:
295
- data[now_hour] = {
296
- "success" : 1 if isSuccess else 0,
297
- "fail" : 0 if isSuccess else 1,
298
- "usage" : 0
299
- }
300
- #save
301
- with open(task_counter_file, 'w', encoding='utf-8') as f:
302
- json.dump(data, f)
303
- except:
304
- pass
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