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.
- {python_qlv_helper-0.5.7.dist-info → python_qlv_helper-0.6.0.dist-info}/METADATA +1 -1
- {python_qlv_helper-0.5.7.dist-info → python_qlv_helper-0.6.0.dist-info}/RECORD +7 -7
- qlv_helper/controller/order_detail.py +21 -5
- qlv_helper/http/order_page.py +82 -14
- {python_qlv_helper-0.5.7.dist-info → python_qlv_helper-0.6.0.dist-info}/WHEEL +0 -0
- {python_qlv_helper-0.5.7.dist-info → python_qlv_helper-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {python_qlv_helper-0.5.7.dist-info → python_qlv_helper-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
python_qlv_helper-0.
|
|
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=
|
|
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=
|
|
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.
|
|
33
|
-
python_qlv_helper-0.
|
|
34
|
-
python_qlv_helper-0.
|
|
35
|
-
python_qlv_helper-0.
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
+
)
|
qlv_helper/http/order_page.py
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
table_data
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|