python-qlv-helper 0.5.7__py3-none-any.whl → 0.6.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python_qlv_helper
3
- Version: 0.5.7
3
+ Version: 0.6.0
4
4
  Summary: qlv helper python package
5
5
  Author-email: ckf10000 <ckf10000@sina.com>
6
6
  License: Apache License
@@ -1,4 +1,4 @@
1
- python_qlv_helper-0.5.7.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
1
+ python_qlv_helper-0.6.0.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
2
2
  qlv_helper/__init__.py,sha256=5DCc5JhfdsgtIuFWgkxPOW5VVKZ8RPikQLGIuyZX6_Y,465
3
3
  qlv_helper/config/__init__.py,sha256=0pKLgui-sC6yMNBBuuTTLkUGhPybiJQSTKTbi66alvg,465
4
4
  qlv_helper/config/custom_exception.py,sha256=uYme0iseQt_dP-Y6-hZ_H2OA2OQR0Kp5PmmZvhEZlEc,805
@@ -6,12 +6,12 @@ qlv_helper/config/url_const.py,sha256=PbKKKH4heqP6SO186MTt-CEpf-Ix5ODlLQ2Aq2ZBZh
6
6
  qlv_helper/controller/__init__.py,sha256=cOJA0xMIytv17oICzPYqWLaSy-Ro2Ceeti0hHhsUj6Y,468
7
7
  qlv_helper/controller/domestic_activity_order.py,sha256=MlmsDVsMBWq2h4Yjh1rhO372Z3p8tu2-4IZGP-nkfr8,1136
8
8
  qlv_helper/controller/main_page.py,sha256=JtkB6BdKYHoNYZ4fkeALpSmAj-NAuiv1x6BvCcBtpic,1252
9
- qlv_helper/controller/order_detail.py,sha256=tOV80nrL8YDVNjvKi8YQP0hWqAcd8M1J0ToRK5YLWZ0,21701
9
+ qlv_helper/controller/order_detail.py,sha256=PjBS1YmiRzWaXg0brJef96k0-0uLr6oaEYz5zZzpjoo,22675
10
10
  qlv_helper/controller/order_table.py,sha256=4AgA9EO0_GVuwnW3ltXPoN74U_6YpOqkRJVdeLb6L1o,6082
11
11
  qlv_helper/controller/user_login.py,sha256=iyyDbOREsXtV5bqAFeXGwurvcCmDHmquh6ReWCfnOBE,5281
12
12
  qlv_helper/http/__init__.py,sha256=yDh1xi_o7ohXqDAzLu62qCWIGk4_aD1dhnUaCon3klM,484
13
13
  qlv_helper/http/main_page.py,sha256=LTpwrG8H_NqwCa3185irgcGd6JQkChAk7HQsDM-TNTI,1519
14
- qlv_helper/http/order_page.py,sha256=zIUZk1UiinoLQ4Y-Qwcv0ZmNAXtYoOGOYDdwdoA0RBI,13157
14
+ qlv_helper/http/order_page.py,sha256=uhCnRPvIvSg5ACMCfpw5_Z62iAAw-Dm-yDRloe5QBP8,16934
15
15
  qlv_helper/http/order_table_page.py,sha256=IaXn5wjqPi1aRXHz0kucdHEdZswUWAZfECC13y57y8k,14440
16
16
  qlv_helper/po/__init__.py,sha256=eDr06o0eYapBsYpOhA11bbxzs2F0dsuDjOKmxk_2HVE,480
17
17
  qlv_helper/po/domestic_activity_order_page.py,sha256=f-XSc2HD6GYisUJZwK2-u0Tr4W3B_vb5JpXvHnjfHOQ,5030
@@ -29,7 +29,7 @@ qlv_helper/utils/po_utils.py,sha256=SwQKL58HERGG2Weou_AwY_TQoYSvgi0gvaVCJBput_k,
29
29
  qlv_helper/utils/stealth_browser.py,sha256=srNOYJOboYo30TvW5OP5TaVpg4jgHm9GxqmYnuwcUQU,3140
30
30
  qlv_helper/utils/type_utils.py,sha256=S5FXUje2mbDuq27LU05WymxNu1VGOLBUV3tuqcx51dE,3792
31
31
  qlv_helper/utils/windows_utils.py,sha256=Cvedsk1c2ujgPNVxszz8XWANkvEr8G9kne6povtZRU4,2866
32
- python_qlv_helper-0.5.7.dist-info/METADATA,sha256=QcI93-dpNseb9ie1ciSpmZu2GMC45SVG--lmaK8ZkJ8,14853
33
- python_qlv_helper-0.5.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- python_qlv_helper-0.5.7.dist-info/top_level.txt,sha256=0pYdhD8SfBcC57LzLYGHY7cwwPqdqAkB1twysCJh5OA,11
35
- python_qlv_helper-0.5.7.dist-info/RECORD,,
32
+ python_qlv_helper-0.6.0.dist-info/METADATA,sha256=cG17GIsXVxDMdh92lqvgtJKpMI4Qn1dNjgtTrQbZMjc,14853
33
+ python_qlv_helper-0.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ python_qlv_helper-0.6.0.dist-info/top_level.txt,sha256=0pYdhD8SfBcC57LzLYGHY7cwwPqdqAkB1twysCJh5OA,11
35
+ python_qlv_helper-0.6.0.dist-info/RECORD,,
@@ -16,7 +16,8 @@ from datetime import datetime
16
16
  import qlv_helper.config.url_const as url_const
17
17
  from typing import Dict, Any, List, cast, Optional
18
18
  from qlv_helper.po.order_detail_page import OrderDetailPage
19
- from qlv_helper.http.order_page import parser_order_info, get_order_page_html, parser_order_flight_table
19
+ from qlv_helper.http.order_page import parser_order_info, get_order_page_html, parser_order_flight_table, \
20
+ fill_procurement_info_with_http
20
21
  from playwright.async_api import Page, Locator, Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
21
22
 
22
23
 
@@ -95,7 +96,7 @@ async def order_unlock(
95
96
  lock_state, lock_btn = await page.get_order_lock_state_btn(timeout=timeout)
96
97
  if "强制解锁" in lock_state:
97
98
  if is_force is False:
98
- raise EnvironmentError(f"订单<{order_id}>,被他人锁定,无需解锁处理")
99
+ raise EnvironmentError(f"被他人锁定,无需解锁处理")
99
100
  await lock_btn.click(button="left")
100
101
  logger.info(f"订单详情页面,用户操作【{lock_state}】按钮点击完成")
101
102
  try:
@@ -135,7 +136,7 @@ async def order_locked(
135
136
  lock_state, lock_btn = await page.get_order_lock_state_btn(timeout=timeout)
136
137
  if "强制解锁" in lock_state:
137
138
  if is_force is False:
138
- raise EnvironmentError(f"订单<{order_id}>,被他人锁定,不做加锁处理")
139
+ raise EnvironmentError(f"被他人锁定,不做加锁处理")
139
140
  await lock_btn.click(button="left")
140
141
  logger.info(f"订单详情页面,用户操作【{lock_state}】按钮点击完成")
141
142
  try:
@@ -317,7 +318,7 @@ async def fill_itinerary(
317
318
  diff = kwargs_passengers.difference(current_passengers)
318
319
  if diff:
319
320
  raise RuntimeError(
320
- f"订单<{order_id}>,传递回填票号的乘客证件信息<{kwargs_passengers}>与订单实际乘客信息<{current_passengers}>不一致"
321
+ f"传递回填票号的乘客证件信息<{kwargs_passengers}>与订单实际乘客信息<{current_passengers}>不一致"
321
322
  )
322
323
  passenger_itinerary_locator = cast(Locator, None)
323
324
  for passenger_locator in passenger_itinerary_locators:
@@ -333,7 +334,7 @@ async def fill_itinerary(
333
334
  await passenger_itinerary_locator.press("Enter")
334
335
  logger.info(f"订单<{order_id}>,本次的票号回填完成")
335
336
  else:
336
- raise RuntimeError(f"订单<{order_id}>,回填票号过程异常,回填失败")
337
+ raise RuntimeError(f"回填票号过程异常,回填失败")
337
338
 
338
339
 
339
340
  async def first_open_page_fill_itinerary(
@@ -413,6 +414,7 @@ async def first_open_page_my_order_unlock(
413
414
  lock_state, lock_btn = await order_detail_po.get_order_lock_state_btn(timeout=timeout)
414
415
  elif "锁定" in lock_state:
415
416
  logger.info(f"订单<{order_id}>,处于无锁状态,无需解锁处理")
417
+ return order_detail_po
416
418
  elif "操作" in lock_state:
417
419
  await lock_btn.click(button="left")
418
420
  await asyncio.sleep(2)
@@ -425,3 +427,17 @@ async def first_open_page_my_order_unlock(
425
427
  await asyncio.sleep(2)
426
428
  logger.info(f"订单详情页面,日志记录栏,【{lock_state}】按钮点击完成")
427
429
  return order_detail_po
430
+
431
+
432
+ async def fill_procurement_with_http(
433
+ *, order_id: int, qlv_domain: str, amount: float, pre_order_id: str, platform_user_id: str, user_password: str,
434
+ passengers: List[str], fids: str, pids: List[str], transaction_id: str, qlv_protocol: str = "http",
435
+ retry: int = 1, timeout: int = 5, enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None,
436
+ playwright_state: Dict[str, Any] = None, data_list: Optional[List[Dict[str, Any]]] = None
437
+ ) -> Dict[str, Any]:
438
+ return await fill_procurement_info_with_http(
439
+ order_id=order_id, qlv_domain=qlv_domain, amount=amount, pre_order_id=pre_order_id,
440
+ platform_user_id=platform_user_id, user_password=user_password, passengers=passengers, fids=fids, pids=pids,
441
+ transaction_id=transaction_id, qlv_protocol=qlv_protocol, retry=retry, timeout=timeout, enable_log=enable_log,
442
+ cookie_jar=cookie_jar, playwright_state=playwright_state, data_list=data_list
443
+ )
@@ -10,8 +10,10 @@
10
10
  # ---------------------------------------------------------------------------------------------------------
11
11
  """
12
12
  import re
13
+ import json
13
14
  import aiohttp
14
15
  from datetime import datetime
16
+ from urllib.parse import quote
15
17
  from bs4 import BeautifulSoup, Tag
16
18
  from collections import OrderedDict
17
19
  from typing import Dict, Any, Optional, List
@@ -22,8 +24,8 @@ from qlv_helper.utils.type_utils import get_key_by_index, get_value_by_index, sa
22
24
 
23
25
 
24
26
  async def get_order_page_html(
25
- order_id: int, domain: str, protocol: str = "http", retry: int = 1, timeout: int = 5, enable_log: bool = True,
26
- cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
27
+ *, order_id: int, domain: str, protocol: str = "http", retry: int = 1, timeout: int = 5,
28
+ enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None, playwright_state: Dict[str, Any] = None
27
29
  ) -> Dict[str, Any]:
28
30
  order_http_client = HttpClientFactory(
29
31
  protocol=protocol if protocol == "http" else "https",
@@ -41,6 +43,47 @@ async def get_order_page_html(
41
43
  )
42
44
 
43
45
 
46
+ async def fill_procurement_info_with_http(
47
+ *, order_id: int, qlv_domain: str, amount: float, pre_order_id: str, platform_user_id: str, user_password: str,
48
+ passengers: List[str], fids: str, pids: List[str], transaction_id: str, qlv_protocol: str = "http",
49
+ retry: int = 1, timeout: int = 5, enable_log: bool = True, cookie_jar: Optional[aiohttp.CookieJar] = None,
50
+ playwright_state: Dict[str, Any] = None, data_list: Optional[List[Dict[str, Any]]] = None
51
+ ) -> Dict[str, Any]:
52
+ client = HttpClientFactory(
53
+ protocol=qlv_protocol, domain=qlv_domain, timeout=timeout, enable_log=enable_log, retry=retry,
54
+ cookie_jar=cookie_jar or aiohttp.CookieJar(),
55
+ playwright_state=playwright_state
56
+ )
57
+
58
+ headers = {
59
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
60
+ "Referer": f"{qlv_protocol}://{qlv_domain}/OrderProcessing/NewTicket/{order_id}?&r={datetime.now().strftime("%Y%m%d%H%M%S")}",
61
+ }
62
+ if data_list:
63
+ data = data_list
64
+ else:
65
+ remark = f"{platform_user_id}/{user_password}"
66
+ pName = "," + ",".join(passengers) + ","
67
+ pids = ",".join(pids)
68
+ data = [
69
+ {"tradingDat": datetime.now().strftime("%Y-%m-%d %H:%M"), "outTktPF": "G航司官网", "outTktLoginCode": "",
70
+ "typeName": "VCC", "accountID": "8", "accountName": "VCC", "transactionAmount": f"{amount}",
71
+ "mainCheckNumber": "", "airCoOrderID": f"{pre_order_id}", "QuotaResultAmount": "0.00",
72
+ "remark": f"{quote(remark)}", "flightIdx": ",1,", "pName": f"{pName}", "orderID": f"{order_id}",
73
+ "businessTypeName": "机票", "tradingItems": "机票支出", "actualAmount": 0, "pType": "成人",
74
+ "fids": f"{fids}", "pids": f"{pids}", "iscandel": "true", "isbatch": "false",
75
+ "MainCheckNumberValus": f"{transaction_id}",
76
+ "OfficeNo": "", "PriceStdActual": "0.00", "ReturnAmount": "0.0000", "OffsetReturnAmount": "0.00",
77
+ "profitRemark": "", "preSaleType": "", "ErrorType": "", "OutTktPFTypeID": "34", "OutTicketAccount": "",
78
+ "OutTicketAccountID": "", "OutTicketPWD": "", "OutTicketTel": "", "OutTicketPNR": ""}
79
+ ]
80
+ data = f"list={json.dumps(data)}&isPayAll=true&delTransactionids=&OutTicketLossType&OutTicketLossRemark="
81
+ return await client.request(
82
+ method="POST", url="/OrderProcessing/PurchaseInfoSave",
83
+ headers=headers, is_end=True, data=data.encode("utf-8")
84
+ )
85
+
86
+
44
87
  def order_info_static_headers() -> OrderedDict[str, str]:
45
88
  return OrderedDict([
46
89
  ("receipted_ota", "OTA实收"), # 0
@@ -154,6 +197,8 @@ def flight_extend_headers() -> OrderedDict[str, str]:
154
197
  ("id_valid_dat", " 证件有效期"), # 9
155
198
  ("code_dep", " 起飞机场"), # 10
156
199
  ("code_arr", " 抵达机场"), # 11
200
+ ("pid", "乘客ID"), # 12
201
+ ("fid", "航段ID"), # 13
157
202
  ])
158
203
 
159
204
 
@@ -269,7 +314,20 @@ def parse_order_flight_table_passenger_info(raw: Tag, headers: OrderedDict[str,
269
314
  nationality = guobies[0].get_text(strip=True) if len(guobies) > 0 else ""
270
315
  issue_country = guobies[1].get_text(strip=True) if len(guobies) > 1 else ""
271
316
 
272
- return {
317
+ a1 = raw.find("a", id=lambda x: x and x.startswith("IDNo_"))
318
+ a2 = raw.find("a", id=lambda x: x and x.startswith("detrni_"))
319
+ a3 = raw.find("a", id=lambda x: x and x.startswith("detrnif_"))
320
+ pid = None
321
+ if a1:
322
+ full_id = a1["id"] # IDNo_279778
323
+ pid = full_id.split("_")[-1]
324
+ elif a2:
325
+ full_id = a2["id"] # detrni_279778
326
+ pid = full_id.split("_")[-1]
327
+ elif a3:
328
+ full_id = a3["id"] # detrnif_279778
329
+ pid = full_id.split("_")[-1]
330
+ result = {
273
331
  get_key_by_index(index=0, ordered_dict=headers): name, # 姓名
274
332
  get_key_by_index(index=1, ordered_dict=headers): ptype, # 类型: 成人/儿童
275
333
  get_key_by_index(index=2, ordered_dict=headers): id_type, # 证件类型
@@ -279,8 +337,12 @@ def parse_order_flight_table_passenger_info(raw: Tag, headers: OrderedDict[str,
279
337
  get_key_by_index(index=6, ordered_dict=headers): sex, # 性别
280
338
  get_key_by_index(index=7, ordered_dict=headers): nationality, # 国籍
281
339
  get_key_by_index(index=8, ordered_dict=headers): issue_country, # 签发国
282
- get_key_by_index(index=9, ordered_dict=headers): id_valid # 证件有效期
340
+ get_key_by_index(index=9, ordered_dict=headers): id_valid, # 证件有效期
283
341
  }
342
+ if pid is not None:
343
+ # 乘客ID
344
+ result[get_key_by_index(index=12, ordered_dict=headers)] = pid
345
+ return result
284
346
 
285
347
 
286
348
  def parse_order_flight_table_row(
@@ -288,8 +350,7 @@ def parse_order_flight_table_row(
288
350
  ) -> Dict[str, Any]:
289
351
  """解析航班表每一行的数据"""
290
352
  tds = tr.find_all("td", recursive=False)
291
- values = {}
292
-
353
+ values = {get_key_by_index(index=12, ordered_dict=extend_headers): tr["pid"]}
293
354
  for idx, td in enumerate(tds):
294
355
  if idx >= len(headers):
295
356
  continue
@@ -303,6 +364,14 @@ def parse_order_flight_table_row(
303
364
  else:
304
365
  raw = clean_order_flight_table(html=td)
305
366
  if "行程" in value:
367
+ fid = ""
368
+ input_tag = td.find('input', {'name': 'fid'})
369
+ fid_key = get_key_by_index(index=13, ordered_dict=extend_headers)
370
+ if input_tag:
371
+ match = re.search(r'\d+', input_tag.get('value'))
372
+ if match:
373
+ fid = match.group()
374
+ values[fid_key] = fid
306
375
  code_dep_key = get_key_by_index(index=10, ordered_dict=extend_headers)
307
376
  code_arr_key = get_key_by_index(index=11, ordered_dict=extend_headers)
308
377
  raw_slice = raw.split("-")
@@ -313,7 +382,6 @@ def parse_order_flight_table_row(
313
382
  values[key] = raw
314
383
  else:
315
384
  values[key] = safe_convert_advanced(raw)
316
-
317
385
  return values
318
386
 
319
387
 
@@ -326,6 +394,8 @@ def extract_structured_table_data(table: Tag) -> List[Optional[Dict[str, Any]]]:
326
394
  for tr in table.find_all("tr")[1:]: # 跳过表头
327
395
  rows.append(parse_order_flight_table_row(tr=tr, headers=headers, extend_headers=extend))
328
396
 
397
+ if rows:
398
+ rows = list({i["id_no"]: i for i in sorted(rows, key=lambda x: bool(x.get("fid")))}.values())
329
399
  return rows
330
400
 
331
401
 
@@ -333,14 +403,12 @@ def parser_order_flight_table(html: str) -> List[Optional[Dict[str, Any]]]:
333
403
  """解析航班表"""
334
404
  soup = BeautifulSoup(html, 'html.parser')
335
405
  # 三个主要的order_sort div
336
- order_sections = soup.find_all('div', class_='order_sort')
337
- section = order_sections[3] if len(order_sections) > 3 else Tag(name="")
406
+ table_sections = soup.find_all('table', class_='table table_border table_center')
407
+ table = table_sections[2] if len(table_sections) > 2 else Tag(name="")
338
408
  results = list()
339
409
 
340
- tables = section.find_all('table', class_='table table_border table_center')
341
- for table in tables:
342
- table_data = extract_structured_table_data(table)
343
- if table_data:
344
- results.extend(table_data)
410
+ table_data = extract_structured_table_data(table)
411
+ if table_data:
412
+ results.extend(table_data)
345
413
 
346
414
  return results