qrpa 1.0.32__py3-none-any.whl → 1.0.34__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/feishu_bot_app.py CHANGED
@@ -32,17 +32,17 @@ class FeishuBot:
32
32
  .build()
33
33
  return self._client
34
34
 
35
- def _get_chat_id(self, chat_alias: str) -> Optional[str]:
35
+ def _get_chat_id(self, bot_name: str) -> Optional[str]:
36
36
  """
37
37
  根据群组别名获取群组ID
38
38
 
39
39
  Args:
40
- chat_alias: 群组别名
40
+ bot_name: 群组别名
41
41
 
42
42
  Returns:
43
43
  群组ID,如果别名不存在则返回None
44
44
  """
45
- return self.config.dict_feishu_group.get(chat_alias)
45
+ return self.config.dict_feishu_group.get(bot_name)
46
46
 
47
47
  def _handle_response_error(self, response, operation_name: str):
48
48
  """
@@ -61,20 +61,20 @@ class FeishuBot:
61
61
  return True
62
62
  return False
63
63
 
64
- def send_text(self, content: str, chat_alias: str = 'test') -> bool:
64
+ def send_text(self, content: str, bot_name: str = 'test') -> bool:
65
65
  """
66
66
  发送文本消息
67
67
 
68
68
  Args:
69
69
  content: 文本内容
70
- chat_alias: 群组别名,默认为'test'
70
+ bot_name: 群组别名,默认为'test'
71
71
 
72
72
  Returns:
73
73
  发送是否成功
74
74
  """
75
- chat_id = self._get_chat_id(chat_alias)
75
+ chat_id = self._get_chat_id(bot_name)
76
76
  if not chat_id:
77
- lark.logger.error(f"未找到群组别名 '{chat_alias}' 对应的群组ID")
77
+ lark.logger.error(f"未找到群组别名 '{bot_name}' 对应的群组ID")
78
78
  return False
79
79
 
80
80
  message_content = {"text": content}
@@ -101,13 +101,13 @@ class FeishuBot:
101
101
  lark.logger.info(lark.JSON.marshal(response.data, indent=4))
102
102
  return True
103
103
 
104
- def send_image(self, file_path: str, chat_alias: str = 'test') -> bool:
104
+ def send_image(self, file_path: str, bot_name: str = 'test') -> bool:
105
105
  """
106
106
  发送图片消息
107
107
 
108
108
  Args:
109
109
  file_path: 图片文件路径
110
- chat_alias: 群组别名,默认为'test'
110
+ bot_name: 群组别名,默认为'test'
111
111
 
112
112
  Returns:
113
113
  发送是否成功
@@ -117,9 +117,9 @@ class FeishuBot:
117
117
  if not image_key:
118
118
  return False
119
119
 
120
- chat_id = self._get_chat_id(chat_alias)
120
+ chat_id = self._get_chat_id(bot_name)
121
121
  if not chat_id:
122
- lark.logger.error(f"未找到群组别名 '{chat_alias}' 对应的群组ID")
122
+ lark.logger.error(f"未找到群组别名 '{bot_name}' 对应的群组ID")
123
123
  return False
124
124
 
125
125
  message_content = {"image_key": image_key}
@@ -146,13 +146,13 @@ class FeishuBot:
146
146
  lark.logger.info(lark.JSON.marshal(response.data, indent=4))
147
147
  return True
148
148
 
149
- def send_excel(self, file_path: str, chat_alias: str = 'test') -> bool:
149
+ def send_excel(self, file_path: str, bot_name: str = 'test') -> bool:
150
150
  """
151
151
  发送Excel文件
152
152
 
153
153
  Args:
154
154
  file_path: Excel文件路径
155
- chat_alias: 群组别名,默认为'test'
155
+ bot_name: 群组别名,默认为'test'
156
156
 
157
157
  Returns:
158
158
  发送是否成功
@@ -162,9 +162,9 @@ class FeishuBot:
162
162
  if not file_key:
163
163
  return False
164
164
 
165
- chat_id = self._get_chat_id(chat_alias)
165
+ chat_id = self._get_chat_id(bot_name)
166
166
  if not chat_id:
167
- lark.logger.error(f"未找到群组别名 '{chat_alias}' 对应的群组ID")
167
+ lark.logger.error(f"未找到群组别名 '{bot_name}' 对应的群组ID")
168
168
  return False
169
169
 
170
170
  message_content = {"file_key": file_key}
qrpa/fun_excel.py CHANGED
@@ -90,6 +90,21 @@ def set_cell_prefix_red(cell, n, color_name):
90
90
  except Exception as e:
91
91
  print(f"设置字体颜色失败: {e}")
92
92
 
93
+ def wrap_column(sheet, columns=None, WrapText=True):
94
+ if columns is None:
95
+ return
96
+ used_range_col = sheet.range('A1').expand('right')
97
+ for j, cell in enumerate(used_range_col):
98
+ col = j + 1
99
+ col_name = index_to_column_name(col)
100
+ col_val = sheet.range(f'{col_name}1').value
101
+ if col_val is None:
102
+ continue
103
+ for c in columns:
104
+ if c in col_val:
105
+ log(f'设置[{c}] 换行 {WrapText}')
106
+ sheet.range(f'{col_name}:{col_name}').api.WrapText = WrapText
107
+
93
108
  def sort_by_column(data, col_index, header_rows=2, reverse=True):
94
109
  if not data or header_rows >= len(data):
95
110
  return data
@@ -2626,13 +2641,13 @@ def _execute_operations_batch(excel_path, operations):
2626
2641
  for sheet_name, operation_type, *args in operations:
2627
2642
  # 根据操作类型决定是否需要获取或创建工作表
2628
2643
  sheet = None
2629
-
2644
+
2630
2645
  # 删除操作不需要获取sheet对象
2631
2646
  if operation_type == 'delete':
2632
2647
  log(f'删除sheet: {sheet_name}')
2633
2648
  delete_sheet_if_exists(wb, sheet_name)
2634
2649
  continue
2635
-
2650
+
2636
2651
  # 其他操作需要获取或创建工作表
2637
2652
  if isinstance(sheet_name, str):
2638
2653
  sheet_names = [s.name.strip().lower() for s in wb.sheets]
qrpa/shein_excel.py CHANGED
@@ -10,10 +10,214 @@ import numpy as np
10
10
 
11
11
  class SheinExcel:
12
12
 
13
- def __init__(self, config):
13
+ def __init__(self, config, bridge=None):
14
14
  self.config = config
15
+ self.bridge = bridge
15
16
  pass
16
17
 
18
+ # 退货列表
19
+ def write_return_list(self, erp, start_date, end_date):
20
+ header = ['退货单号', '退货出库时间', '签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '商家SKU', '属性集', 'SKU退货数量', '平台SKU', 'ERP默认供货商', 'ERP成本', '包裹名', '包裹号', '退货计划单号', '订单号', '发货单', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址']
21
+ excel_data = [header]
22
+
23
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
24
+
25
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{start_date}_{end_date}.json'
26
+ dict = read_dict_from_file(cache_file)
27
+ for store_username, shein_back_list in dict.items():
28
+ for item in shein_back_list:
29
+
30
+ store_name = dict_store.get(store_username)
31
+
32
+ returnOrderId = item['id']
33
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
34
+ return_detail = read_dict_from_file(cache_file)
35
+ if len(return_detail) == 0:
36
+ continue
37
+
38
+ returnOrderNo = item['returnOrderNo']
39
+ returnOrderTypeName = item['returnOrderTypeName']
40
+ returnOrderStatusName = item['returnOrderStatusName']
41
+ returnReasonTypeName = item['returnReasonTypeName']
42
+ returnReason = item['returnReason']
43
+ waitReturnQuantity = item['waitReturnQuantity']
44
+ skcReturnQuantity = item['returnQuantity']
45
+ returnAmount = item['returnAmount']
46
+ currencyCode = item['currencyCode']
47
+ returnPlanNo = item['returnPlanNo']
48
+ sellerOrderNo = item['sellerOrderNo']
49
+ sellerDeliveryNo = item['sellerDeliveryNo']
50
+ completeTime = item['completeTime']
51
+ returnWayTypeName = item['returnWayTypeName']
52
+ returnExpressCompanyName = item['returnExpressCompanyName']
53
+ expressNoList = item['expressNoList']
54
+ returnAddress = item['returnAddress']
55
+ sellerContract = item['sellerContract']
56
+ sellerContractPhone = item['sellerContractPhone']
57
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
58
+ if item['returnScrapType'] == 1:
59
+ urls = item.get('qc_report_url', '-')
60
+ else:
61
+ urls = '\n'.join(item['rejectPicUrlList'])
62
+
63
+ for package_list in return_detail[0]['boxList']:
64
+ package_name = package_list['packageName']
65
+ package_no = package_list['returnBoxNo']
66
+ for skc_item in package_list['goods']:
67
+ skc_img = skc_item['imgPath']
68
+ skc = skc_item['skc']
69
+ supplierCode = skc_item['supplierCode']
70
+ for sku_item in skc_item['details']:
71
+ platformSku = sku_item['platformSku']
72
+ supplierSku = sku_item['supplierSku']
73
+ suffixZh = sku_item['suffixZh']
74
+ returnQuantity = sku_item['returnQuantity']
75
+
76
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}'
77
+ skc_info = f'SKC: {skc}\n供方货号: {supplierCode}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
78
+
79
+ row_item = []
80
+ row_item.append(returnOrderNo)
81
+ row_item.append(completeTime)
82
+ row_item.append(isSign)
83
+ row_item.append(store_info)
84
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
85
+ row_item.append(returnReasonTypeName)
86
+ row_item.append(returnReason)
87
+ row_item.append(skc_img)
88
+ row_item.append(skc_info)
89
+ row_item.append(supplierSku)
90
+ row_item.append(suffixZh)
91
+ row_item.append(returnQuantity)
92
+ row_item.append(platformSku)
93
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
94
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
95
+ row_item.append(package_name)
96
+ row_item.append(package_no)
97
+ row_item.append(returnPlanNo)
98
+ row_item.append(sellerOrderNo)
99
+ row_item.append(sellerDeliveryNo)
100
+ row_item.append(returnWayTypeName)
101
+ row_item.append(returnExpressCompanyName)
102
+ row_item.append(expressNoList)
103
+ row_item.append(returnAddress)
104
+ row_item.append(sellerContract)
105
+ row_item.append(sellerContractPhone)
106
+ row_item.append(urls)
107
+
108
+ excel_data.append(row_item)
109
+
110
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{start_date}_{end_date}.json'
111
+ write_dict_to_file(cache_file_excel, excel_data)
112
+
113
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{TimeUtils.today_date()}.json'
114
+ dict = read_dict_from_file(cache_file)
115
+ for store_username, shein_back_list in dict.items():
116
+ for item in shein_back_list:
117
+
118
+ store_name = dict_store.get(store_username)
119
+
120
+ returnOrderId = item['id']
121
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
122
+ return_detail = read_dict_from_file(cache_file)
123
+ if len(return_detail) == 0:
124
+ continue
125
+
126
+ returnOrderNo = item['returnOrderNo']
127
+ returnOrderTypeName = item['returnOrderTypeName']
128
+ returnOrderStatusName = item['returnOrderStatusName']
129
+ returnReasonTypeName = item['returnReasonTypeName']
130
+ returnReason = item['returnReason']
131
+ waitReturnQuantity = item['waitReturnQuantity']
132
+ skcReturnQuantity = item['returnQuantity']
133
+ returnAmount = item['returnAmount']
134
+ currencyCode = item['currencyCode']
135
+ returnPlanNo = item['returnPlanNo']
136
+ sellerOrderNo = item['sellerOrderNo']
137
+ sellerDeliveryNo = item['sellerDeliveryNo']
138
+ completeTime = item['completeTime']
139
+ returnWayTypeName = item['returnWayTypeName']
140
+ returnExpressCompanyName = item['returnExpressCompanyName']
141
+ expressNoList = item['expressNoList']
142
+ returnAddress = item['returnAddress']
143
+ sellerContract = item['sellerContract']
144
+ sellerContractPhone = item['sellerContractPhone']
145
+ isSign = ['已报废', '已签收', '待签收'][item['isSign']]
146
+ if item['returnScrapType'] == 1:
147
+ urls = item.get('qc_report_url', '-')
148
+ else:
149
+ urls = '\n'.join(item['rejectPicUrlList'])
150
+
151
+ for package_list in return_detail[0]['boxList']:
152
+ package_name = package_list['packageName']
153
+ package_no = package_list['returnBoxNo']
154
+ for skc_item in package_list['goods']:
155
+ skc_img = skc_item['imgPath']
156
+ skc = skc_item['skc']
157
+ supplierCode = skc_item['supplierCode']
158
+ for sku_item in skc_item['details']:
159
+ platformSku = sku_item['platformSku']
160
+ supplierSku = sku_item['supplierSku']
161
+ suffixZh = sku_item['suffixZh']
162
+ returnQuantity = sku_item['returnQuantity']
163
+
164
+ store_info = f'{store_username}\n{store_name}\n处理类型: {returnOrderTypeName}\n退货状态: {returnOrderStatusName}'
165
+ skc_info = f'SKC: {skc}\n供方货号: {supplierCode}\n预计退货数量/执行退货数量: {waitReturnQuantity}/{skcReturnQuantity}\n预计退货货值: {returnAmount} {currencyCode}'
166
+
167
+ row_item = []
168
+ row_item.append(returnOrderNo)
169
+ row_item.append(completeTime)
170
+ row_item.append(isSign)
171
+ row_item.append(store_info)
172
+ row_item.append(self.config.shein_store_manager.get(str(store_username).lower()))
173
+ row_item.append(returnReasonTypeName)
174
+ row_item.append(returnReason)
175
+ row_item.append(skc_img)
176
+ row_item.append(skc_info)
177
+ row_item.append(supplierSku)
178
+ row_item.append(suffixZh)
179
+ row_item.append(returnQuantity)
180
+ row_item.append(platformSku)
181
+ row_item.append(self.bridge.get_sku_supplier(supplierSku, erp))
182
+ row_item.append(self.bridge.get_sku_cost(supplierSku, erp))
183
+ row_item.append(package_name)
184
+ row_item.append(package_no)
185
+ row_item.append(returnPlanNo)
186
+ row_item.append(sellerOrderNo)
187
+ row_item.append(sellerDeliveryNo)
188
+ row_item.append(returnWayTypeName)
189
+ row_item.append(returnExpressCompanyName)
190
+ row_item.append(expressNoList)
191
+ row_item.append(returnAddress)
192
+ row_item.append(sellerContract)
193
+ row_item.append(sellerContractPhone)
194
+ row_item.append(urls)
195
+
196
+ excel_data.append(row_item)
197
+
198
+ sheet_name = '昨日退货列表'
199
+
200
+ cache_file_excel = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_excel_{TimeUtils.today_date()}.json'
201
+ write_dict_to_file(cache_file_excel, excel_data)
202
+
203
+ batch_excel_operations(self.config.excel_return_list, [
204
+ (sheet_name, 'write', excel_data, ['W', 'Z']),
205
+ (sheet_name, 'format', self.format_return_list)
206
+ ])
207
+
208
+ def format_return_list(self, sheet):
209
+ merge_by_column_v2(sheet, '退货单号', ['签收状态', '店铺信息', '店长', '退货类型', '退货原因', 'SKC图片', 'SKC信息', '包裹名', '包裹号', '退货计划单号', '订单号', '发货单', '退货出库时间', '退回方式', '快递名称', '运单号', '退货地址', '商家联系人', '商家手机号', '入库问题图片地址'])
210
+ beautify_title(sheet)
211
+ add_borders(sheet)
212
+ format_to_datetime(sheet, ['时间'])
213
+ format_to_money(sheet, ['单价', '金额', '成本'])
214
+ column_to_right(sheet, ['单价', '金额', '成本'])
215
+ wrap_column(sheet, ['退货原因', '退货地址', '入库问题图片地址'])
216
+ autofit_column(sheet, ['店铺别名', 'SKC信息'])
217
+ column_to_left(sheet, ['店铺信息', '商家SKU', '供方货号', '属性集', 'SKC信息', '退货地址'])
218
+ specify_column_width(sheet, ['退货原因', 'SKC信息', '商家SKU', '退货地址'], 200 / 6)
219
+ InsertImageV2(sheet, ['SKC图片'])
220
+
17
221
  def dealReturn(self, sheet):
18
222
  # 遍历可用行
19
223
  used_range_row = sheet.range('A1').expand('down')
@@ -1076,10 +1280,10 @@ class SheinExcel:
1076
1280
  excel_path = self.config.excel_shein_finance_month_report_summary
1077
1281
 
1078
1282
  sheet_name = '总表-算法1'
1079
- dict_store_manager_shein = read_dict_from_file(self.config.shein_store_alias)
1283
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
1080
1284
  total_data = []
1081
1285
  header = ['店铺账号', '店铺别名']
1082
- for mall_id, excel_data in dict_store_manager_shein.items():
1286
+ for mall_id, excel_data in dict_store.items():
1083
1287
  total_data += [[mall_id, excel_data]]
1084
1288
 
1085
1289
  log(total_data)
@@ -1099,7 +1303,7 @@ class SheinExcel:
1099
1303
  filtered_value = add_suffixed_column(filtered_value, '毛利', '')
1100
1304
 
1101
1305
  # 匹配店铺店长
1102
- dict_store_manager_shein = read_dict_from_file(self.config.shein_store_alias)
1306
+ dict_store_manager_shein = self.config.shein_store_manager
1103
1307
  for row in filtered_value:
1104
1308
  mall_name = row[0]
1105
1309
  if mall_name == '店铺账号':
@@ -1118,10 +1322,10 @@ class SheinExcel:
1118
1322
  wb.save()
1119
1323
  close_excel(app, wb)
1120
1324
 
1121
- dict_store_manager_shein = read_dict_from_file(self.config.shein_store_alias)
1325
+ dict_store = read_dict_from_file(self.config.shein_store_alias)
1122
1326
  total_data = []
1123
1327
  header = ['店铺账号', '店铺别名']
1124
- for mall_id, excel_data in dict_store_manager_shein.items():
1328
+ for mall_id, excel_data in dict_store.items():
1125
1329
  total_data += [[mall_id, excel_data]]
1126
1330
 
1127
1331
  filtered_value = [header] + total_data
@@ -1139,6 +1343,7 @@ class SheinExcel:
1139
1343
  filtered_value = add_suffixed_column(filtered_value, '毛利', '')
1140
1344
 
1141
1345
  # 匹配店铺店长
1346
+ dict_store_manager_shein = self.config.shein_store_manager
1142
1347
  for row in filtered_value:
1143
1348
  mall_name = row[0]
1144
1349
  if mall_name == '店铺账号':
@@ -1623,7 +1828,7 @@ class SheinExcel:
1623
1828
  for col in columns:
1624
1829
  column_range = sheet.range(f'{col}{start_row}:{col}{end_row}')
1625
1830
  for cell in column_range:
1626
- if cell.value is not None and isinstance(cell.value, (int, float)) and cell.value < 0:
1831
+ if cell.value is not None and cell.value < 0:
1627
1832
  cell.font.color = (255, 0, 0)
1628
1833
 
1629
1834
  def _format_daily_header(self, sheet, las_row):
qrpa/shein_lib.py CHANGED
@@ -119,6 +119,95 @@ class SheinLib:
119
119
 
120
120
  log('鉴权处理结束')
121
121
 
122
+ # 获取质检报告pdf地址
123
+ def get_qc_report_url(self, deliverCode, purchaseCode):
124
+ log(f'获取质检报告:{deliverCode} {purchaseCode}')
125
+ url = f"https://sso.geiwohuo.com/pfmp/returnPlan/queryQcReport"
126
+ payload = {
127
+ "deliverCode" : deliverCode,
128
+ "purchaseCode": purchaseCode
129
+ }
130
+ response_text = fetch(self.web_page, url, payload)
131
+ error_code = response_text.get('code')
132
+ if str(error_code) != '0':
133
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
134
+ qc_report_url = (response_text.get('info', {}).get('data') or [{'qcReportUrl': '质检报告生成中,请稍后查看'}])[0].get('qcReportUrl')
135
+ log(qc_report_url)
136
+ return qc_report_url
137
+
138
+ def get_return_order_box_detail(self, returnOrderId):
139
+ log(f'获取退货包裹详情: {returnOrderId}')
140
+ url = f"https://sso.geiwohuo.com/pfmp/returnOrder/getReturnOrderBoxDetail"
141
+ payload = {
142
+ "returnOrderId": returnOrderId,
143
+ "page" : 1,
144
+ "perPage" : 50
145
+ }
146
+ response_text = fetch(self.web_page, url, payload)
147
+ error_code = response_text.get('code')
148
+ if str(error_code) != '0':
149
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
150
+ list_item = response_text['info']['data']
151
+
152
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_box_detail_{returnOrderId}.json'
153
+ write_dict_to_file(cache_file, list_item)
154
+
155
+ def get_return_order_list(self, start_date, end_date, only_yesterday=1):
156
+
157
+ log(f'获取退货列表: {self.store_username} {self.store_name} {start_date} {end_date}')
158
+
159
+ page_num = 1
160
+ page_size = 200 # 列表最多返回200条数据 大了没有用
161
+
162
+ url = f"https://sso.geiwohuo.com/pfmp/returnOrder/page"
163
+ payload = {
164
+ "addTimeStart": f"{start_date} 00:00:00",
165
+ "addTimeEnd" : f"{end_date} 23:59:59",
166
+ "page" : page_num,
167
+ "perPage" : page_size
168
+ }
169
+ response_text = fetch(self.web_page, url, payload)
170
+ error_code = response_text.get('code')
171
+ if str(error_code) != '0':
172
+ raise send_exception(json.dumps(response_text, ensure_ascii=False))
173
+ list_item = response_text['info']['data']
174
+ total = response_text['info']['meta']['count']
175
+ totalPage = math.ceil(total / page_size)
176
+
177
+ for page in range(2, totalPage + 1):
178
+ log(f'获取退供列表 第{page}/{totalPage}页 共{total}条记录')
179
+ payload['page'] = page
180
+ response_text = fetch(self.web_page, url, payload)
181
+ spu_list_new = response_text['info']['data']
182
+ list_item += spu_list_new
183
+ time.sleep(0.1)
184
+
185
+ all_list_item = []
186
+ today_list_item = []
187
+ # 过滤 退货出库时间 是昨天的
188
+ for item in list_item:
189
+ has_valid_package = item.get('hasPackage') == 1
190
+ is_valid_yesterday = TimeUtils.is_yesterday(item['completeTime'], None) if item.get('completeTime') else False
191
+ if has_valid_package:
192
+ if int(item['returnScrapType']) == 1:
193
+ purchaseCode = item['sellerOrderNo']
194
+ delivery_code = item['sellerDeliveryNo']
195
+ item['qc_report_url'] = self.get_qc_report_url(delivery_code, purchaseCode)
196
+ returnOrderId = item['id']
197
+ self.get_return_order_box_detail(returnOrderId)
198
+
199
+ all_list_item.append(item)
200
+ if is_valid_yesterday:
201
+ today_list_item.append(item)
202
+
203
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{TimeUtils.today_date()}.json'
204
+ write_dict_to_file_ex(cache_file, {self.store_username: today_list_item}, [self.store_username])
205
+
206
+ cache_file = f'{self.config.auto_dir}/shein/cache/shein_return_order_list_{start_date}_{end_date}.json'
207
+ write_dict_to_file_ex(cache_file, {self.store_username: all_list_item}, [self.store_username])
208
+
209
+ return list_item
210
+
122
211
  # 获取希音退供明细 和 台账明细一个接口
123
212
  def get_back_list(self, source='mb'):
124
213
  page_num = 1
@@ -1693,7 +1782,8 @@ class SheinLib:
1693
1782
  sale_model = spu_info['saleModel']['name']
1694
1783
 
1695
1784
  # 过滤已经售罄
1696
- if status_cn in ['已售罄']:
1785
+ if shelf_status == 'SOLD_OUT':
1786
+ log(f'过滤已售罄: {skc} {category_name} {product_name}')
1697
1787
  continue
1698
1788
 
1699
1789
  for sku_info in spu_info['skuList']:
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qrpa
3
- Version: 1.0.32
3
+ Version: 1.0.34
4
4
  Summary: qsir's rpa library
5
5
  Author: QSir
6
6
  Author-email: QSir <1171725650@qq.com>
@@ -1,15 +1,15 @@
1
1
  qrpa/RateLimitedSender.py,sha256=hqvb1qspDFaW4RsLuVufylOrefkMgixANKeBaGEqYb4,1421
2
2
  qrpa/__init__.py,sha256=p4n9kXm1j9G-GWKDPWQa1WIyrDUeC33zeM27xky9otg,1019
3
3
  qrpa/db_migrator.py,sha256=2VmhzcMsU0MKpl-mNCwKyV8tLTqyEysSpP27-S_rQZ8,21862
4
- qrpa/feishu_bot_app.py,sha256=2oUEdfvROzFazKrNoZ0pRbww9YWSU9cTzTdwcCYS7Ug,9482
4
+ qrpa/feishu_bot_app.py,sha256=6r2YqCAMUN7y3F7onoABRmHC7S-UPOLHwbhsQnQ3IRc,9452
5
5
  qrpa/fun_base.py,sha256=qg6SvR-GEj2TclB1OL9eLu711jV-bysXJ5Eh2gW9pE8,10600
6
- qrpa/fun_excel.py,sha256=gWRNZ44dVSfPkIV6baObI84Bk-CV35Eb4EtYVqre8xI,114220
6
+ qrpa/fun_excel.py,sha256=kXR3fU0XqsdBuSEToqA3TzYWSP5Cgh1aZ1QPgaZ0y08,114758
7
7
  qrpa/fun_file.py,sha256=yzjDV16WL5vRys7J4uQcNzIFkX4D5MAlSCwxcD-mwQo,11966
8
8
  qrpa/fun_web.py,sha256=5QLQorAhRzMOGMRh4eCJ2UH8ZhVHvxkHwobWhmgU5qM,6286
9
9
  qrpa/fun_win.py,sha256=-LnTeocdTt72NVH6VgLdpAT9_C5oV9okeudXG6CftMA,8034
10
10
  qrpa/shein_daily_report_model.py,sha256=H8oZmIN5Pyqe306W1_xuz87lOqLQ_LI5RjXbaxDkIzE,12589
11
- qrpa/shein_excel.py,sha256=mDqnamzNi8e_T1RnTYZAJAeVhT3CDl9shBqZ1IetbSk,102632
12
- qrpa/shein_lib.py,sha256=1cDrkyHFIwXfFFwRMCXLZ5B0aAhAzOJkcKhe8_hYVjQ,101592
11
+ qrpa/shein_excel.py,sha256=qWEq0WZgbAIIVoygOd97EykXpPRDF3tFPEiqCX3zM8k,114965
12
+ qrpa/shein_lib.py,sha256=41EjsUglJk-2rf-YP8VM69vQgURe4ACtBe-m9z4uAO0,105902
13
13
  qrpa/shein_sqlite.py,sha256=ZQwD0Gz81q9WY7tY2HMEYvSF9r3N_G_Aur3bYfST9WY,5707
14
14
  qrpa/shein_ziniao.py,sha256=nSqqcEPh4nVQtUxUnIRzeZfTLyXywGPjPZn5pP-w57U,18309
15
15
  qrpa/temu_chrome.py,sha256=CbtFy1QPan9xJdJcNZj-EsVGhUvv3ZTEPVDEA4-im40,2803
@@ -17,8 +17,8 @@ qrpa/temu_excel.py,sha256=ssAQvhtRGaTOLAVM3gS-AnmHPkIiHCT6gTsK1hoi-_8,6990
17
17
  qrpa/temu_lib.py,sha256=hYB59zsLS3m3NTic_duTwPMOTSxlHyQVa8OhHnHm-1g,7199
18
18
  qrpa/time_utils.py,sha256=ef0hhbN_6b-gcnz5ETIVOoxemIMvcxGVGGIRnHnGaBo,29564
19
19
  qrpa/time_utils_example.py,sha256=shHOXKKF3QSzb0SHsNc34M61wEkkLuM30U9X1THKNS8,8053
20
- qrpa/wxwork.py,sha256=Vy8PGEtlTWt4-1laVhuqpJUGCFH2JymgbjvH00aaBog,10946
21
- qrpa-1.0.32.dist-info/METADATA,sha256=f5jlww0R2XS81ZzUV3Mk7cEmrtxPBrrVAtUDd8QZ0bQ,231
22
- qrpa-1.0.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- qrpa-1.0.32.dist-info/top_level.txt,sha256=F6T5igi0fhXDucPPUbmmSC0qFCDEsH5eVijfVF48OFU,5
24
- qrpa-1.0.32.dist-info/RECORD,,
20
+ qrpa/wxwork.py,sha256=gIytG19DZ5g7Tsl0-W3EbjfSnpIqZw-ua24gcB78YEg,11264
21
+ qrpa-1.0.34.dist-info/METADATA,sha256=G9wYjP5ED9eQgG32w8tw-UqQ6s0vGIsIdnCDMyyzLDM,231
22
+ qrpa-1.0.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ qrpa-1.0.34.dist-info/top_level.txt,sha256=F6T5igi0fhXDucPPUbmmSC0qFCDEsH5eVijfVF48OFU,5
24
+ qrpa-1.0.34.dist-info/RECORD,,
File without changes