xtn-tools-pro 1.0.0.6.1__py3-none-any.whl → 1.0.0.6.3__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.
@@ -34,6 +34,8 @@ class GoFunTaskV2:
34
34
  restart_time = 30 * 60 if restart_time <= 0 else restart_time # 间隔x秒强制重启时间不传默认60分钟
35
35
  update_proxies_time = ini_dict.get('update_proxies_time', 0) # 间隔x秒更新代理
36
36
  upload_task_tine = ini_dict.get('upload_task_tine', 0) # 回写间隔
37
+ download_not_task_tine = ini_dict.get('download_not_task_tine', 0) # 当遇到下载任务接口返回空任务时,间隔x秒再继续请求,默认2秒
38
+ download_not_task_tine = 2 if download_not_task_tine <= 0 else download_not_task_tine # 当遇到下载任务接口返回空任务时,间隔x秒再继续请求,默认2秒
37
39
 
38
40
  # 拼接地址
39
41
  if port:
@@ -62,6 +64,7 @@ class GoFunTaskV2:
62
64
  "upload_url": upload_url, # 回写任务地址
63
65
  "update_proxy_url": update_proxy_url, # 更新代理地址
64
66
  "external_ip": external_ip,
67
+ "download_not_task_tine": download_not_task_tine,
65
68
  }
66
69
 
67
70
  logger.debug(
@@ -116,12 +119,14 @@ class GoFunTaskV2:
116
119
  download_queue = multiprocessing.Queue()
117
120
  upload_queue = multiprocessing.Queue()
118
121
  proxies_dict = manager.dict()
122
+ manager_ns = manager.dict()
123
+ manager_ns["gofun_task_kill_status"] = False
119
124
 
120
125
  # 下载回写代理
121
126
  download_and_upload_task_process = multiprocessing.Process(target=self._download_and_upload_task,
122
127
  name="_download_and_upload_task",
123
128
  args=(ini_info, logger, download_queue,
124
- upload_queue, proxies_dict)
129
+ upload_queue, proxies_dict, manager_ns)
125
130
  )
126
131
  download_and_upload_task_process.start()
127
132
 
@@ -135,9 +140,15 @@ class GoFunTaskV2:
135
140
 
136
141
  while True:
137
142
  logger.debug("进程正常...")
143
+ gofun_task_kill_status = manager_ns["gofun_task_kill_status"]
144
+ if gofun_task_kill_status:
145
+ go_task_fun_task_process.kill()
146
+ go_task_fun_task_process.terminate()
147
+ go_task_fun_task_process.join()
148
+
138
149
  time.sleep(10)
139
150
 
140
- def _download_and_upload_task(self, ini_info, logger, download_queue, upload_queue, proxies_dict):
151
+ def _download_and_upload_task(self, ini_info, logger, download_queue, upload_queue, proxies_dict, manager_ns):
141
152
  """
142
153
  使用3个线程 打开 获取任务、回写任务、代理维护
143
154
  :param ini_info:
@@ -155,7 +166,7 @@ class GoFunTaskV2:
155
166
 
156
167
  # 获取任务
157
168
  thread_download_task = threading.Thread(target=self.__download_task,
158
- args=(download_queue, ini_info, logger))
169
+ args=(download_queue, ini_info, logger, manager_ns))
159
170
  thread_download_task.start()
160
171
 
161
172
  # 回写任务
@@ -168,7 +179,7 @@ class GoFunTaskV2:
168
179
  args=(proxies_dict, ini_info, logger))
169
180
  thread_update_proxy.start()
170
181
 
171
- def __download_task(self, download_queue, ini_info, logger):
182
+ def __download_task(self, download_queue, ini_info, logger, manager_ns):
172
183
  """
173
184
  获取任务
174
185
  :param queue:
@@ -179,6 +190,8 @@ class GoFunTaskV2:
179
190
  task = ini_info["task"]
180
191
  headers = {"Authorization": auto}
181
192
  params = {"taskType": task}
193
+
194
+ download_queue_exist_cnt = 10
182
195
  while True:
183
196
  try:
184
197
  qsize = download_queue.qsize()
@@ -192,8 +205,16 @@ class GoFunTaskV2:
192
205
 
193
206
  if len(result_list) <= 0:
194
207
  # 判断任务响应是否为空
195
- time.sleep(2)
208
+ download_queue_exist_cnt -= 1
209
+ time.sleep(10)
210
+ if download_queue_exist_cnt <= 0 and manager_ns["gofun_task_kill_status"] == False:
211
+ manager_ns["gofun_task_kill_status"] = True
212
+ logger.warning("获取任务个数为0已超设置值,判断为无任务将关闭相关进程")
196
213
  continue
214
+
215
+ download_queue_exist_cnt = 10
216
+ manager_ns["gofun_task_kill_status"] = False
217
+
197
218
  for task_item in result_list:
198
219
  phone_item = task_item["phone"]
199
220
  if not phone_item.isdigit(): # 判断是否全是整数(不包括小数点或负号)
@@ -363,14 +384,15 @@ class GoFunTaskV2:
363
384
  :param block: 是否阻塞等待 True阻塞/False不阻塞
364
385
  :param timeout:
365
386
  :return:
387
+ error_code:1001 队列为空;
366
388
  """
367
389
  try:
368
390
  task_item = download_queue.get(block=block, timeout=timeout)
369
391
  return task_item
370
392
  except queue.Empty as e:
371
393
  # 捕获队列为空的异常
372
- self.logger.info(f"get_download_task 获取下载任务 {download_queue, block, timeout} 报错 队列为空: {e}")
373
- return False
394
+ # self.logger.info(f"get_download_task 获取下载任务 {download_queue, block, timeout} 报错 队列为空: {e}")
395
+ return {"error": "队列为空", "error_code": 1001}
374
396
  except Exception as e:
375
397
  self.logger.critical(f"get_download_task 获取下载任务 {download_queue, block, timeout} 报错 {e}")
376
398
  return False
@@ -0,0 +1,342 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # 说明:
5
+ # 程序说明xxxxxxxxxxxxxxxxxxx
6
+ # History:
7
+ # Date Author Version Modification
8
+ # --------------------------------------------------------------------------------------------------
9
+ # 2025/1/22 xiatn V00.01.000 新建
10
+ # --------------------------------------------------------------------------------------------------
11
+ import time
12
+ import queue
13
+ import random
14
+ import inspect
15
+ import requests
16
+ import threading
17
+ import multiprocessing
18
+ import concurrent.futures
19
+ from multiprocessing import Process
20
+ from xtn_tools_pro.utils.time_utils import get_time_now_timestamp
21
+
22
+
23
+ class GoFunTaskV3:
24
+ def __init__(self, ini_dict, logger, go_task_function):
25
+ self.logger = logger
26
+
27
+ # 读取配置信息
28
+ host = ini_dict.get('host', '') # 域名
29
+ port = ini_dict.get('port', 0) # 端口
30
+ task = ini_dict.get('task', '') # 任务
31
+ auto = ini_dict.get('auto', '') # token
32
+ processes_num = ini_dict.get('processes_num', 0) # 进程数
33
+ thread_num = ini_dict.get('thread_num', 0) # 线程数
34
+ restart_time = ini_dict.get('restart_time', 0) # 间隔x秒强制重启
35
+ restart_time = 30 * 60 if restart_time <= 0 else restart_time # 间隔x秒强制重启时间不传默认60分钟
36
+ update_proxies_time = ini_dict.get('update_proxies_time', 0) # 间隔x秒更新代理
37
+ upload_task_tine = ini_dict.get('upload_task_tine', 0) # 回写间隔
38
+ download_not_task_tine = ini_dict.get('download_not_task_tine', 0) # 当遇到下载任务接口返回空任务时,间隔x秒再继续请求,默认2秒
39
+ download_not_task_tine = 2 if download_not_task_tine <= 0 else download_not_task_tine # 当遇到下载任务接口返回空任务时,间隔x秒再继续请求,默认2秒
40
+ processes_num = 1 if processes_num <= 0 else processes_num
41
+ thread_num = 1 if thread_num <= 0 else thread_num
42
+
43
+ # 拼接地址
44
+ if port:
45
+ task_host = f"http://{host}:{port}"
46
+ else:
47
+ task_host = f"http://{host}"
48
+ download_url = task_host + "/filter_server/phone/get"
49
+ upload_url = task_host + "/filter_server/phone/update"
50
+ update_proxy_url = task_host + f"/filter_server/proxy/random/get?taskType={task}&limit=1"
51
+
52
+ # 获取网络ip
53
+ external_ip = self.__get_external_ip()
54
+
55
+ # 全部配置信息
56
+ self.__ini_info = {
57
+ "host": host,
58
+ "port": int(port),
59
+ "task": task,
60
+ "auto": auto,
61
+ "processes_num": int(processes_num),
62
+ "thread_num": int(thread_num),
63
+ "restart_time": int(restart_time),
64
+ "update_proxies_time": int(update_proxies_time),
65
+ "upload_task_tine": int(upload_task_tine),
66
+ "download_url": download_url, # 获取任务地址
67
+ "upload_url": upload_url, # 回写任务地址
68
+ "update_proxy_url": update_proxy_url, # 更新代理地址
69
+ "external_ip": external_ip,
70
+ "download_not_task_tine": download_not_task_tine,
71
+ }
72
+
73
+ logger.debug(
74
+ f"\n无敌框架来咯~~~当前设置配置如下:"
75
+ f"\n\t功能函数重启间隔:{restart_time};进程数:{processes_num};线程数:{thread_num}"
76
+ f"\n\t代理更新间隔:{update_proxies_time};回写间隔{upload_task_tine};\n"
77
+ )
78
+
79
+ # 共享任务队列
80
+ self.download_queue = multiprocessing.Queue()
81
+ self.upload_queue = multiprocessing.Queue()
82
+ manager = multiprocessing.Manager() # 进程1
83
+ self.manager_info = manager.dict()
84
+ self.proxies_dict = manager.dict()
85
+
86
+ # 获取任务
87
+ thread_download_task = threading.Thread(target=self.__download_task,
88
+ args=(self.download_queue, self.manager_info, self.__ini_info, logger))
89
+ thread_download_task.start()
90
+
91
+ # 回写任务
92
+ thread_upload_task = threading.Thread(target=self.__upload_task,
93
+ args=(self.upload_queue, self.__ini_info, logger))
94
+ thread_upload_task.start()
95
+
96
+ # 维护代理
97
+ thread_update_proxy = threading.Thread(target=self.__update_proxy,
98
+ args=(self.proxies_dict, self.manager_info, self.__ini_info, logger))
99
+ thread_update_proxy.start()
100
+
101
+ # go_task_fun_cnt = 0
102
+ # go_task_fun_task_process = None
103
+ self.manager_info["gofun_kill_status"] = False # 进程kill状态,True需要kill/False无需kill
104
+ self.manager_info["gofun_kill_th_status"] = False # 线程kill状态,True需要kill/False无需kill
105
+ self.manager_info["gofun_run_status_time"] = get_time_now_timestamp(is_time_10=True)
106
+
107
+ go_process_list = []
108
+ go_task_fun_cnt = 0
109
+ while True:
110
+ if not go_process_list and not self.manager_info["gofun_kill_status"]:
111
+ # 未启动gofun 且 无需kill 则启动多进程多线程
112
+ for i in range(processes_num):
113
+ p = Process(target=self._run_with_timeout,
114
+ args=(self.download_queue, self.upload_queue, self.proxies_dict, thread_num, logger,
115
+ go_task_function))
116
+ go_process_list.append(p)
117
+ p.start()
118
+
119
+ self.manager_info["gofun_run_status_time"] = get_time_now_timestamp(is_time_10=True)
120
+ go_task_fun_cnt += 1
121
+ logger.info(f"第{go_task_fun_cnt}次,进程数:{processes_num},线程数:{thread_num},等待{restart_time}秒强制下一次")
122
+
123
+ elif self.manager_info["gofun_kill_status"] and go_process_list:
124
+ # 需kill
125
+ for p in go_process_list:
126
+ # p.terminate()
127
+ p.join()
128
+ go_process_list = []
129
+
130
+ elif not self.manager_info["gofun_kill_status"] and go_process_list:
131
+ # 无需kill 且 任务进程启动 用于定时重启
132
+ if self.manager_info["gofun_run_status_time"] + restart_time <= get_time_now_timestamp(is_time_10=True):
133
+ self.manager_info["gofun_kill_status"] = True
134
+
135
+ logger.debug("主线程正常...")
136
+ time.sleep(10)
137
+
138
+ def __get_external_ip(self):
139
+ """
140
+ 获取当前网络ip
141
+ :return:
142
+ """
143
+ while True:
144
+ try:
145
+ rp = requests.get('https://httpbin.org/ip')
146
+ rp_json = rp.json()
147
+ self.logger.warning(f"当前网络ip --> {rp_json}")
148
+ return rp_json['origin']
149
+ except Exception as e:
150
+ self.logger.critical(f"获取当前网络ip{e}")
151
+
152
+ def __download_task(self, download_queue, manager_info, ini_info, logger):
153
+ """
154
+ 获取任务
155
+ :param queue:
156
+ :return:
157
+ """
158
+ download_url = ini_info["download_url"]
159
+ auto = ini_info["auto"]
160
+ task = ini_info["task"]
161
+ headers = {"Authorization": auto}
162
+ params = {"taskType": task}
163
+
164
+ download_queue_exist_cnt = 3
165
+ while True:
166
+ try:
167
+ qsize = download_queue.qsize()
168
+ logger.info(f"当前队列剩余任务数:{qsize}")
169
+ if qsize >= 10:
170
+ time.sleep(2)
171
+ continue
172
+ resp = requests.get(download_url, headers=headers, params=params, timeout=5)
173
+ json_data = resp.json()
174
+ result_list = json_data.get("result", [])
175
+ if len(result_list) <= 0:
176
+ # 判断任务响应是否为空
177
+ download_queue_exist_cnt -= 1
178
+ if download_queue_exist_cnt <= 0 and not manager_info["gofun_kill_status"]:
179
+ manager_info["gofun_kill_status"] = True
180
+ logger.warning("获取任务个数为0已超设置值,判断为无任务将关闭相关进程")
181
+ time.sleep(10)
182
+ continue
183
+
184
+ download_queue_exist_cnt = 10
185
+ manager_info["gofun_kill_status"] = False
186
+
187
+ for task_item in result_list:
188
+ phone_item = task_item["phone"]
189
+ if not phone_item.isdigit(): # 判断是否全是整数(不包括小数点或负号)
190
+ continue
191
+ download_queue.put(task_item)
192
+ logger.warning(f"成功获取任务个数:{len(result_list)}")
193
+ except Exception as e:
194
+ logger.critical(f"获取任务请求异常:{e}")
195
+ time.sleep(2)
196
+
197
+ def __upload_task(self, upload_queue, ini_info, logger):
198
+ """
199
+ 回写任务
200
+ :return:
201
+ """
202
+ upload_url = ini_info["upload_url"]
203
+ external_ip = ini_info["external_ip"]
204
+ auto = ini_info["auto"]
205
+ task = ini_info["task"]
206
+ upload_task_tine = ini_info["upload_task_tine"]
207
+ headers = {"Authorization": auto}
208
+ params = {"taskType": task}
209
+ while True:
210
+ # 判断队列是否有值
211
+ empty = upload_queue.empty()
212
+ if empty:
213
+ time.sleep(2)
214
+ continue
215
+
216
+ # 循环全部获取队列的任务
217
+ result_list = []
218
+ try:
219
+ while True:
220
+ task_item = upload_queue.get_nowait()
221
+ taskNo = task_item["taskNo"]
222
+ phone = task_item["phone"]
223
+ isRegistered = task_item["isRegistered"]
224
+ country_region = task_item["country_region"]
225
+ full_phone = f"{country_region}{phone}"
226
+ task_item = {
227
+ 'taskNo': taskNo,
228
+ 'phone': full_phone,
229
+ 'isRegistered': isRegistered
230
+ }
231
+ result_list.append(task_item)
232
+ except Exception as e:
233
+ logger.critical(f"循环全部获取队列的任务{e}")
234
+
235
+ # 回写任务
236
+ data = {"result": result_list, "remoteAddr": external_ip}
237
+ while True:
238
+ try:
239
+ resp = requests.post(upload_url, json=data, headers=headers, params=params, timeout=5)
240
+ json_data = resp.json()
241
+ # logger.warning(f"成功回写任务个数:{len(result_list)},{json_data},{data}")
242
+ logger.warning(f"成功回写任务个数:{len(result_list)},{json_data}")
243
+ break
244
+ except Exception as e:
245
+ logger.critical(f"回写异常,{len(result_list)},{e}")
246
+ time.sleep(2)
247
+
248
+ if not upload_task_tine:
249
+ # 一直执行 不退出
250
+ continue
251
+ time.sleep(upload_task_tine)
252
+
253
+ def __update_proxy(self, proxies_dict, manager_info, ini_info, logger):
254
+ """
255
+ 更新代理
256
+ :return:
257
+ """
258
+ update_proxy_url = ini_info["update_proxy_url"]
259
+ auto = ini_info["auto"]
260
+ update_proxies_time = ini_info["update_proxies_time"]
261
+ headers = {"Authorization": auto}
262
+
263
+ while True:
264
+ try:
265
+ if not manager_info.get("status"):
266
+ resp = requests.get(update_proxy_url, headers=headers, timeout=5)
267
+ json_data = resp.json()
268
+ status_code = resp.status_code
269
+ result_list = json_data.get("result", [])
270
+ if not result_list or status_code != 200:
271
+ logger.critical(f"获取代理响应异常:{status_code} {len(result_list)} {json_data}")
272
+ time.sleep(2)
273
+
274
+ proxies_dict['http'] = 'http://' + random.choice(result_list)
275
+ proxies_dict['https'] = 'http://' + random.choice(result_list)
276
+ manager_info['status'] = True
277
+ logger.warning(f"成功获取代理:{proxies_dict}")
278
+
279
+ if not update_proxies_time:
280
+ # 一直执行 不退出
281
+ time.sleep(2)
282
+ continue
283
+
284
+ time.sleep(update_proxies_time)
285
+ manager_info['status'] = False
286
+ except Exception as e:
287
+ logger.critical(f"获取代理请求异常:{e}")
288
+ time.sleep(2)
289
+
290
+ def _run_with_timeout(self, download_queue, upload_queue, proxies_dict, thread_num, logger, go_task_function):
291
+ caller = inspect.stack()[1] # 获取调用者的调用栈信息
292
+ caller_name = caller.function # 获取调用者的函数名
293
+ caller_class = caller.frame.f_locals.get('self', None) # 获取调用者的类实例
294
+ if caller_name != "run" or caller_class is None:
295
+ raise Exception("错误调用")
296
+
297
+ with concurrent.futures.ThreadPoolExecutor(max_workers=thread_num) as executor:
298
+ # 提交10个函数到线程池中执行
299
+ futures = [executor.submit(go_task_function, self, proxies_dict, logger)
300
+ for _ in range(thread_num)]
301
+
302
+ # 等待所有线程完成
303
+ for future in concurrent.futures.as_completed(futures):
304
+ future.result()
305
+
306
+ def get_gofun_task_status(self):
307
+ status = not self.manager_info["gofun_kill_status"]
308
+ # self.logger.debug(f"get_gofun_task_status {status}")
309
+ return status
310
+
311
+ def get_download_task(self, block, timeout):
312
+ """
313
+ 获取下载任务
314
+ :param block: 是否阻塞等待 True阻塞/False不阻塞
315
+ :param timeout:
316
+ :return:
317
+ error_code:1001 队列为空;
318
+ """
319
+ try:
320
+ task_item = self.download_queue.get(block=block, timeout=timeout)
321
+ task_item["success"] = True
322
+ return task_item
323
+ except queue.Empty as e:
324
+ # 捕获队列为空的异常
325
+ # self.logger.info(f"get_download_task 获取下载任务 {download_queue, block, timeout} 报错 队列为空: {e}")
326
+ return {"error": "队列为空", "error_code": 1001}
327
+ except Exception as e:
328
+ self.logger.critical(f"get_download_task 获取下载任务 {self.download_queue, block, timeout} 报错 {e}")
329
+ return False
330
+
331
+ def update_upload_task(self, task_item):
332
+ """
333
+ 更新任务
334
+ :param task_item:
335
+ :return:
336
+ """
337
+ try:
338
+ task_item = self.upload_queue.put(task_item)
339
+ return task_item
340
+ except Exception as e:
341
+ self.logger.critical(f"update_upload_task 更新任务 {self.upload_queue, task_item} 报错 {e}")
342
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: xtn-tools-pro
3
- Version: 1.0.0.6.1
3
+ Version: 1.0.0.6.3
4
4
  Summary: xtn 开发工具
5
5
  Author: xtn
6
6
  Author-email: czw011122@gmail.com
@@ -11,7 +11,8 @@ xtn_tools_pro/proxy/__init__.py,sha256=WRwh6s2lruMu5buh0ejo9EK54kWT_VQhCsFGNFAmc
11
11
  xtn_tools_pro/proxy/proxy.py,sha256=No6E1pFY5yx2F4976pXPrLtq-QEVp79KupzcufjSN58,8703
12
12
  xtn_tools_pro/task_pro/__init__.py,sha256=nK3U47hWwE1H875ieEToH9r-jzXHS-PXk8cDstOvRE8,418
13
13
  xtn_tools_pro/task_pro/go_fun.py,sha256=hWEt2uJ9FCvJH7PhVZttS-11A7J6zbRKwX7c5YLYQag,19144
14
- xtn_tools_pro/task_pro/go_fun_v2.py,sha256=et1rw9yXOVQtDxCi7cX-cKs925TyJWu0jbPZbZDbIgQ,17093
14
+ xtn_tools_pro/task_pro/go_fun_v2.py,sha256=SgcXgtEBGSVL1V2LyqO0z8Md2H8JZxucYrLLIwqtiLM,18489
15
+ xtn_tools_pro/task_pro/go_fun_v3.py,sha256=S3OW3n-BGHszHQJApK6nQwonuUaaQXo5hZGGbSHhtRU,15183
15
16
  xtn_tools_pro/utils/__init__.py,sha256=I1_n_NP23F2lBqlF4EOlnOdLYxM8M4pbn63UhJN1hRE,418
16
17
  xtn_tools_pro/utils/crypto.py,sha256=RZ5AET4udlraACWMeNF-17JiZ2R6Ahb47_j4tjkV7LE,3190
17
18
  xtn_tools_pro/utils/file_utils.py,sha256=VfdIxog4s1UW5NpKkCvQsUs9qHjLoNCnstZbnftkT4w,2046
@@ -20,8 +21,8 @@ xtn_tools_pro/utils/log.py,sha256=m0WtTWkkwtrki1ftP8vCDR8bMfK2gcfUGx5J2x2IlLQ,10
20
21
  xtn_tools_pro/utils/retry.py,sha256=0wjHsR5DBBKpv4naMfxiky8kprrZes4WURIfFQ4H708,1657
21
22
  xtn_tools_pro/utils/sql.py,sha256=EAKzbkZP7Q09j15Gm6o0_uq0qgQmcCQT6EAawbpp4v0,6263
22
23
  xtn_tools_pro/utils/time_utils.py,sha256=TUtzG61PeVYXhaQd6pBrXAdlz7tBispNIRQRcGhE2No,4859
23
- xtn_tools_pro-1.0.0.6.1.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- xtn_tools_pro-1.0.0.6.1.dist-info/METADATA,sha256=seinLYEoBi6O46fLzxS3xfyDL8RWny_vKQlpOX7obS0,455
25
- xtn_tools_pro-1.0.0.6.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
26
- xtn_tools_pro-1.0.0.6.1.dist-info/top_level.txt,sha256=jyB3FLDEr8zE1U7wHczTgIbvUpALhR-ULF7RVEO7O2U,14
27
- xtn_tools_pro-1.0.0.6.1.dist-info/RECORD,,
24
+ xtn_tools_pro-1.0.0.6.3.dist-info/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ xtn_tools_pro-1.0.0.6.3.dist-info/METADATA,sha256=HRSwWVKg0eLRnOeZ7z08TXWxNVjwEZ3dHrnSinyA9Rs,455
26
+ xtn_tools_pro-1.0.0.6.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
27
+ xtn_tools_pro-1.0.0.6.3.dist-info/top_level.txt,sha256=jyB3FLDEr8zE1U7wHczTgIbvUpALhR-ULF7RVEO7O2U,14
28
+ xtn_tools_pro-1.0.0.6.3.dist-info/RECORD,,