python-qdairlines-helper 0.1.4__py3-none-any.whl → 0.4.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.
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.4.3.dist-info}/METADATA +3 -3
- python_qdairlines_helper-0.4.3.dist-info/RECORD +36 -0
- qdairlines_helper/config/url_const.py +4 -0
- qdairlines_helper/controller/add_passenger.py +1 -1
- qdairlines_helper/controller/book_payment.py +22 -20
- qdairlines_helper/controller/book_search.py +70 -37
- qdairlines_helper/controller/cash_pax_info.py +1 -1
- qdairlines_helper/controller/home.py +1 -1
- qdairlines_helper/controller/nhlms_cashdesk.py +2 -2
- qdairlines_helper/controller/order_detail.py +43 -18
- qdairlines_helper/controller/order_query.py +1 -1
- qdairlines_helper/controller/order_verify.py +1 -1
- qdairlines_helper/controller/pay_success.py +9 -8
- qdairlines_helper/controller/user_login.py +35 -2
- qdairlines_helper/http/flight_order.py +5 -41
- qdairlines_helper/http/http_base.py +54 -0
- qdairlines_helper/http/user_login.py +43 -0
- qdairlines_helper/po/add_passenger_page.py +1 -1
- qdairlines_helper/po/book_search_page.py +48 -29
- qdairlines_helper/po/cash_pax_info_page.py +1 -1
- qdairlines_helper/po/nhlms_cash_desk_page.py +42 -1
- qdairlines_helper/po/order_verify_page.py +1 -1
- python_qdairlines_helper-0.1.4.dist-info/RECORD +0 -35
- qdairlines_helper/utils/exception_utils.py +0 -105
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.4.3.dist-info}/WHEEL +0 -0
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.4.3.dist-info}/licenses/LICENSE +0 -0
- {python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.4.3.dist-info}/top_level.txt +0 -0
{python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.4.3.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python_qdairlines_helper
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: qdairlines helper python package
|
|
5
5
|
Author-email: ckf10000 <ckf10000@sina.com>
|
|
6
6
|
License: Apache License
|
|
@@ -210,9 +210,9 @@ Project-URL: Issues, https://github.com/ckf10000/python-qdairlines-helper/issues
|
|
|
210
210
|
Requires-Python: >=3.12
|
|
211
211
|
Description-Content-Type: text/markdown
|
|
212
212
|
License-File: LICENSE
|
|
213
|
-
Requires-Dist: python_playwright_helper>=0.
|
|
213
|
+
Requires-Dist: python_playwright_helper>=0.4.0
|
|
214
214
|
Requires-Dist: python_http_helper>=0.2.1
|
|
215
|
-
Requires-Dist: flight_helper>=0.
|
|
215
|
+
Requires-Dist: flight_helper>=0.3.3
|
|
216
216
|
Dynamic: license-file
|
|
217
217
|
|
|
218
218
|
# python-qdairlines-helper
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
python_qdairlines_helper-0.4.3.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
|
|
2
|
+
qdairlines_helper/__init__.py,sha256=bN1FqEK_jvefO_HU_8HFJpjuOzHTsuhmRe5KAzmraR4,479
|
|
3
|
+
qdairlines_helper/config/__init__.py,sha256=JjIRocePx3gwFNpdauuiwBU6B2Ug565xaHLEMu5Oo0I,479
|
|
4
|
+
qdairlines_helper/config/url_const.py,sha256=KpvvZEPOsJ7FQdJLp79VfUdUSRuItR4OYYCzl8wfKZI,2194
|
|
5
|
+
qdairlines_helper/controller/__init__.py,sha256=oyeciEBlDLNpqHblB0R-nXQG3HsI8L5mmlWulQ7Y38Q,482
|
|
6
|
+
qdairlines_helper/controller/add_passenger.py,sha256=qixajF_MRzmMZ9htAFeGCP3L27iV1Xk-jnfPQEP48s8,5155
|
|
7
|
+
qdairlines_helper/controller/book_payment.py,sha256=9QdaakkIyi8qCjZ7TT687JX8Jqt5mp9SSQ307NYZ3tY,6270
|
|
8
|
+
qdairlines_helper/controller/book_search.py,sha256=XQQuI1xX6rO-MqdJ15_X5wX94Yeqj9UVat6p55beNu8,8977
|
|
9
|
+
qdairlines_helper/controller/cash_pax_info.py,sha256=AZ09ekt-zaYYs3uUSclul014mbCZEF5BVIghOeIGT5s,2507
|
|
10
|
+
qdairlines_helper/controller/home.py,sha256=2r75pe7d16kZ6tkJZ3zM5ycV-DE5iy3njhpE0xf1EZw,1390
|
|
11
|
+
qdairlines_helper/controller/nhlms_cashdesk.py,sha256=1gWURu9VnjjvvalAu-uF7eXVMSsiaBDWHPwG8EgDRh8,4308
|
|
12
|
+
qdairlines_helper/controller/order_detail.py,sha256=hhL37PiozS1jALMk_eB0vg0eigW_Rcl_mf0l-QrW21o,4337
|
|
13
|
+
qdairlines_helper/controller/order_query.py,sha256=7U6S5xlT89UJHFslDbUT5VRgGlAoPQSOi4NRtEKK9xQ,1903
|
|
14
|
+
qdairlines_helper/controller/order_verify.py,sha256=3_bc1R47_vaCIEJaPWrxyYrE49v2J1-KsGCmAEEloKc,2269
|
|
15
|
+
qdairlines_helper/controller/pay_success.py,sha256=G8Vf5MDDBgvDo3MIg-deEvyv4PJfxdzLhpHU9TGuejc,3295
|
|
16
|
+
qdairlines_helper/controller/user_login.py,sha256=ojW6mqZdiURpi5K1gMPdFlqCUieOkBhcXDJELW5Hktw,6210
|
|
17
|
+
qdairlines_helper/http/__init__.py,sha256=96AJPf2zgw_rm3Wv-boIBltWFgZH3Yo-fn5M9SRK280,489
|
|
18
|
+
qdairlines_helper/http/flight_order.py,sha256=7NTUA8xt6gwPBH7gdU3VjUoBa9LpPAvAEzkWguLPrNk,2066
|
|
19
|
+
qdairlines_helper/http/http_base.py,sha256=zWSgJfyMaekHyvV4lJD-KHiCX9BN3GGb8DperyVpRGQ,2492
|
|
20
|
+
qdairlines_helper/http/user_login.py,sha256=S_thG1RLXv5x5MQJmEG-oo38ReIF3_p17hVwGqs61A8,1815
|
|
21
|
+
qdairlines_helper/po/__init__.py,sha256=HoouLJGLu_dI6IAKTVjBRraGHMcAUgntayTph-BUBAE,481
|
|
22
|
+
qdairlines_helper/po/add_passenger_page.py,sha256=no_QC4hwO7xuZXKLzakNlcWj2R06oVZdzKHvFHsBG5Y,7996
|
|
23
|
+
qdairlines_helper/po/air_order_page.py,sha256=0Ux0B2hUGKCoFLRpJqGHS2w690o0A2PDnvRf9-UKfJE,930
|
|
24
|
+
qdairlines_helper/po/book_search_page.py,sha256=T2bxKFuvApY0rMlU7NHso0CwQ9XXOR0xz4bvIgI1x0k,10865
|
|
25
|
+
qdairlines_helper/po/cash_pax_info_page.py,sha256=yH-ZedYweby1x3Sw3JZX6a57-8PdtQHmNKqym7cn-Xo,3397
|
|
26
|
+
qdairlines_helper/po/home_page.py,sha256=KvoOZOYwKjlJjZOT522K_8ic3_2zaYOPWkuklUHVSlM,907
|
|
27
|
+
qdairlines_helper/po/login_page.py,sha256=SdWmkP_47GeosbFRcfvXLeQxxdM7b9UESvmajWq4-nA,2975
|
|
28
|
+
qdairlines_helper/po/nhlms_cash_desk_page.py,sha256=K3GCcmfHbMOhZnVB7hHKJflVy0DbiidDJBfJ3IMbMGc,6671
|
|
29
|
+
qdairlines_helper/po/order_verify_page.py,sha256=7boJVdzoV9IdIGtbjza3za9C26_4LFBjOmp27-0_ick,2924
|
|
30
|
+
qdairlines_helper/po/pay_success_page.py,sha256=XGddo8as8wf1kCCdvemqHIRWOPIPv1JG8iXEmpH4zck,1329
|
|
31
|
+
qdairlines_helper/utils/__init__.py,sha256=sti2S709puM2mQbQisk0KGq_YNjW6T4zz2vyYXonNB0,479
|
|
32
|
+
qdairlines_helper/utils/po_utils.py,sha256=74ZVyyxPmPk1tcGQoMauXhBE3qsZJej8urni1ch27io,2417
|
|
33
|
+
python_qdairlines_helper-0.4.3.dist-info/METADATA,sha256=J8UgJTIFyVdwztik2df4g_eVENNr_WY8wH6Vj7FqcdM,14424
|
|
34
|
+
python_qdairlines_helper-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
35
|
+
python_qdairlines_helper-0.4.3.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
|
|
36
|
+
python_qdairlines_helper-0.4.3.dist-info/RECORD,,
|
|
@@ -26,6 +26,10 @@ order_verify_url: str = "/book/orderVerify"
|
|
|
26
26
|
cash_pax_info_url: str = "/book/cashPaxInfoInput"
|
|
27
27
|
# https://nhlms.cloudpnr.com/nobel/WebEntry.do 汇付天下收银台
|
|
28
28
|
nhlms_cashdesk_url: str = "/nobel/WebEntry.do"
|
|
29
|
+
# https://nhlms.cloudpnr.com/nobel/n1026/error?RegionId=P
|
|
30
|
+
nhlms_error_url: str = "/nobel/n1026/error"
|
|
31
|
+
|
|
32
|
+
|
|
29
33
|
# https://excashier.alipay.com/standard/auth.htm?payOrderId=4e3606b1c842414c88f631c35952a88e.85 支付宝收银台
|
|
30
34
|
alipay_url: str = "/standard/auth.htm"
|
|
31
35
|
# https://www.qdairlines.com/book/payPreCash?orderNo=OW20260105B1154687 微信支付收银台
|
|
@@ -14,8 +14,8 @@ from logging import Logger
|
|
|
14
14
|
from playwright.async_api import Page
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
16
|
from flight_helper.models.dto.passenger import PassengerDTO
|
|
17
|
+
from flight_helper.utils.exception_utils import IPBlockError
|
|
17
18
|
from flight_helper.models.dto.booking import BookingInputDTO
|
|
18
|
-
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
19
19
|
from qdairlines_helper.po.add_passenger_page import AddPassengerPage
|
|
20
20
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
21
21
|
|
|
@@ -15,15 +15,15 @@ from playwright.async_api import Page
|
|
|
15
15
|
from typing import Any, List, Callable, Optional, Dict
|
|
16
16
|
from flight_helper.models.dto.passenger import PassengerDTO
|
|
17
17
|
from flight_helper.models.dto.itinerary import QueryItineraryRequestDTO
|
|
18
|
-
from qdairlines_helper.controller.pay_success import two_check_pay_success
|
|
19
|
-
from qdairlines_helper.utils.exception_utils import PaymentChannelMissError
|
|
20
18
|
from flight_helper.models.dto.booking import BookingInputDTO, OneWayBookingDTO
|
|
21
19
|
from qdairlines_helper.controller.book_search import open_book_search_page, book_search
|
|
22
20
|
from qdairlines_helper.controller.order_verify import load_order_verify_po, order_verify
|
|
21
|
+
from flight_helper.utils.exception_utils import PaymentChannelMissError, PaymentTypeError
|
|
23
22
|
from flight_helper.models.dto.payment import HFPaidAccountPaymentInputDTO, PaymentResultDTO
|
|
24
23
|
from qdairlines_helper.controller.add_passenger import add_passenger, load_add_passenger_po
|
|
25
24
|
from qdairlines_helper.controller.cash_pax_info import load_cash_pax_info_po, select_payment_channel
|
|
26
25
|
from qdairlines_helper.controller.nhlms_cashdesk import load_nhlms_cash_desk_po, hf_paid_account_payment
|
|
26
|
+
from qdairlines_helper.controller.pay_success import two_check_pay_success, check_hf_payment_is_success
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
async def book_payment_callback(
|
|
@@ -88,29 +88,31 @@ async def book_payment_callback(
|
|
|
88
88
|
domain=hf_paid_account_payment_dto.pay_domain, protocol=hf_paid_account_payment_dto.pay_protocol
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
+
if hf_paid_account_payment_dto.payment_type != "付款账户支付":
|
|
92
|
+
raise PaymentTypeError(payment_type=hf_paid_account_payment_dto.payment_type)
|
|
93
|
+
|
|
91
94
|
# 10. 汇付天下操作支付
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
logger.info(f"订单<{one_way_booking_dto.order_no}>,汇付天下操作支付结束")
|
|
100
|
-
payment_result_dto.pre_order_no = pre_order_no
|
|
95
|
+
payment_result_dto = await hf_paid_account_payment(
|
|
96
|
+
page=nhlms_cash_desk_po, logger=logger, order_no=one_way_booking_dto.order_no, timeout=timeout,
|
|
97
|
+
is_pay_completed_callback=is_pay_completed_callback,
|
|
98
|
+
hf_paid_account_payment_dto=hf_paid_account_payment_dto
|
|
99
|
+
)
|
|
100
|
+
logger.info(f"订单<{one_way_booking_dto.order_no}>,汇付天下操作支付结束")
|
|
101
|
+
payment_result_dto.pre_order_no = pre_order_no
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
query_dto = QueryItineraryRequestDTO(
|
|
104
|
+
payment_domain=book_input_dto.book_domain, payment_protocol=book_input_dto.book_protocol,
|
|
105
|
+
storage_state=qdair_cookie.get("storage_state"), token=qdair_cookie.get("token"),
|
|
106
|
+
pre_order_no=pre_order_no, user_id=qdair_cookie.get("user_id"), headers=qdair_cookie.get("headers")
|
|
107
|
+
)
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
# 11. 检查是否支付成功
|
|
110
|
+
if kwargs.get("暂时不走这个逻辑,改用轻逻辑验证"):
|
|
111
|
+
await two_check_pay_success(
|
|
110
112
|
page=page, logger=logger, timeout=timeout, query_dto=query_dto, retry=retry, enable_log=enable_log,
|
|
111
113
|
callback_get_proxy=callback_get_proxy, cookie_jar=cookie_jar
|
|
112
114
|
)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
await check_hf_payment_is_success(page=nhlms_cash_desk_po, logger=logger)
|
|
116
|
+
return payment_result_dto
|
|
115
117
|
else:
|
|
116
118
|
raise PaymentChannelMissError()
|
|
@@ -9,23 +9,25 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
+
import time
|
|
12
13
|
import asyncio
|
|
13
14
|
from logging import Logger
|
|
14
|
-
from typing import Dict, Any
|
|
15
|
+
from typing import Dict, Any, List, Optional
|
|
15
16
|
from playwright.async_api import Page, Locator
|
|
16
17
|
import qdairlines_helper.config.url_const as url_const
|
|
17
|
-
from qdairlines_helper.utils.exception_utils import IPBlockError
|
|
18
18
|
from qdairlines_helper.po.book_search_page import BookSearchPage
|
|
19
19
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
20
|
+
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
|
|
20
21
|
from flight_helper.models.dto.booking import OneWayBookingDTO, BookingInputDTO
|
|
21
|
-
from
|
|
22
|
+
from flight_helper.utils.exception_utils import NotEnoughTicketsError, ExcessiveProfitdError, ExcessiveLossesError, \
|
|
23
|
+
IPBlockError
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
async def open_book_search_page(
|
|
25
27
|
*, page: Page, logger: Logger, protocol: str, domain: str, timeout: float = 60.0
|
|
26
28
|
) -> BookSearchPage:
|
|
27
29
|
url_prefix = f"{protocol}://{domain}"
|
|
28
|
-
book_search_url = url_prefix + url_const.
|
|
30
|
+
book_search_url = url_prefix + url_const.book_search_url
|
|
29
31
|
await page.goto(book_search_url)
|
|
30
32
|
|
|
31
33
|
book_search_po = BookSearchPage(page=page, url=book_search_url)
|
|
@@ -37,65 +39,96 @@ async def open_book_search_page(
|
|
|
37
39
|
return book_search_po
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
async def _book_search_dialog_handle(
|
|
43
|
+
*, logger: Logger, page: BookSearchPage, flight_no: str, timeout: float = 60.0
|
|
44
|
+
) -> Locator:
|
|
45
|
+
end_time = time.time() + timeout
|
|
46
|
+
last_exception: Optional[Exception] = None
|
|
47
|
+
while time.time() < end_time:
|
|
48
|
+
try:
|
|
49
|
+
# 2. 点击继续购票按钮
|
|
50
|
+
continue_book_btn = await page.get_reminder_dialog_continue_book_btn(timeout=1)
|
|
51
|
+
await continue_book_btn.click(button="left")
|
|
52
|
+
logger.info("航班预订查询页面,出现温馨提醒弹框,【继续购票】按钮点击完成")
|
|
53
|
+
except (Exception,):
|
|
54
|
+
pass
|
|
55
|
+
try:
|
|
56
|
+
# 1.获取航班基本信息plane locator
|
|
57
|
+
flight_info_plane: Locator = await page.get_flight_info_plane(flight_no=flight_no, timeout=1)
|
|
58
|
+
return flight_info_plane
|
|
59
|
+
except (Exception,) as e:
|
|
60
|
+
last_exception = e
|
|
61
|
+
raise last_exception
|
|
62
|
+
|
|
63
|
+
|
|
40
64
|
async def book_search(
|
|
41
65
|
*, book_search_page: BookSearchPage, logger: Logger, passengers: int, book_input_dto: BookingInputDTO,
|
|
42
66
|
one_way_booking_dto: OneWayBookingDTO, timeout: float = 60.0
|
|
43
67
|
) -> None:
|
|
44
|
-
|
|
45
|
-
# 1. 判断是否存在温馨提醒弹框
|
|
46
|
-
continue_book_btn = await book_search_page.get_reminder_dialog_continue_book_btn(timeout=timeout)
|
|
47
|
-
await continue_book_btn.click(button="left")
|
|
48
|
-
logger.info("航班预订查询页面,出现温馨提醒弹框,【继续购票】按钮点击完成")
|
|
49
|
-
except (Exception,):
|
|
50
|
-
pass
|
|
51
|
-
|
|
52
|
-
# 2.搜索栏输入起飞城市
|
|
68
|
+
# 1.搜索栏输入起飞城市
|
|
53
69
|
depart_city_input = await book_search_page.get_depart_city_input(timeout=timeout)
|
|
54
|
-
await depart_city_input.fill(value=one_way_booking_dto.
|
|
55
|
-
await asyncio.sleep(delay=
|
|
70
|
+
await depart_city_input.fill(value=one_way_booking_dto.dep_code)
|
|
71
|
+
await asyncio.sleep(delay=2)
|
|
56
72
|
await depart_city_input.press("Enter")
|
|
57
|
-
logger.info(
|
|
73
|
+
logger.info(
|
|
74
|
+
f"航班预订查询页面,搜索栏-起飞城市<{one_way_booking_dto.dep_city} {one_way_booking_dto.dep_code}>输入完成")
|
|
58
75
|
|
|
59
|
-
#
|
|
76
|
+
# 2.搜索栏输入抵达城市
|
|
60
77
|
arrive_city_input = await book_search_page.get_arrive_city_input(timeout=timeout)
|
|
61
|
-
await arrive_city_input.fill(value=one_way_booking_dto.
|
|
62
|
-
await asyncio.sleep(delay=
|
|
78
|
+
await arrive_city_input.fill(value=one_way_booking_dto.arr_code)
|
|
79
|
+
await asyncio.sleep(delay=2)
|
|
63
80
|
await arrive_city_input.press("Enter")
|
|
64
|
-
logger.info(
|
|
81
|
+
logger.info(
|
|
82
|
+
f"航班预订查询页面,搜索栏-抵达城市<{one_way_booking_dto.arr_city} {one_way_booking_dto.arr_code}>输入完成")
|
|
65
83
|
|
|
66
|
-
#
|
|
84
|
+
# 3.搜索栏输入起飞时间
|
|
67
85
|
depart_date_input = await book_search_page.get_depart_date_input(timeout=timeout)
|
|
68
86
|
await depart_date_input.fill(value=one_way_booking_dto.dep_date)
|
|
69
87
|
await asyncio.sleep(delay=1)
|
|
70
88
|
await depart_date_input.press("Enter")
|
|
71
89
|
logger.info(f"航班预订查询页面,搜索栏-起飞日期<{one_way_booking_dto.dep_date}>输入完成")
|
|
72
90
|
|
|
73
|
-
#
|
|
91
|
+
# 4.点击【查询机票】按钮
|
|
74
92
|
flight_query_btn = await book_search_page.get_flight_query_btn(timeout=timeout)
|
|
75
93
|
await flight_query_btn.click(button="left")
|
|
76
94
|
logger.info(f"航班预订查询页面,【查询机票】按钮点击完成")
|
|
77
95
|
|
|
78
|
-
#
|
|
79
|
-
flight_info_plane: Locator = await
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
# 5.点击查询后,再次处理是否有弹框,并航班基本信息plane locator
|
|
97
|
+
flight_info_plane: Locator = await _book_search_dialog_handle(
|
|
98
|
+
logger=logger, page=book_search_page, timeout=timeout, flight_no=one_way_booking_dto.flight_no
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# 6.获取航班基本信息plane locator
|
|
102
|
+
# flight_info_plane: Locator = await book_search_page.get_flight_info_plane(
|
|
103
|
+
# flight_no=one_way_booking_dto.flight_no, timeout=timeout
|
|
104
|
+
# )
|
|
83
105
|
|
|
84
|
-
#
|
|
106
|
+
# 8. 获取产品类型
|
|
85
107
|
flight_product_nav: Locator = await book_search_page.get_flight_product_nav(
|
|
86
108
|
locator=flight_info_plane, product_type=book_input_dto.product_type, timeout=timeout
|
|
87
109
|
)
|
|
88
110
|
await flight_product_nav.click(button="left")
|
|
89
111
|
logger.info(f"航班预订查询页面,产品类型【{book_input_dto.product_type}】选择完成")
|
|
90
112
|
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
# 9. 点击获取更多产品和价格的按钮
|
|
114
|
+
more_product_btn = await book_search_page.get_more_product_btn(locator=flight_info_plane, timeout=timeout)
|
|
115
|
+
await more_product_btn.click(button="left")
|
|
116
|
+
logger.info(f"航班预订查询页面,航班<{one_way_booking_dto.flight_no}>产品列表,【更多舱位及价格】按钮点击完成")
|
|
117
|
+
|
|
118
|
+
# 9. 获取所有的产品
|
|
119
|
+
products: List[Dict[str, Any]] = await book_search_page.get_flight_products(
|
|
120
|
+
locator=flight_info_plane, flight_no=one_way_booking_dto.flight_no, logger=logger
|
|
121
|
+
)
|
|
122
|
+
# 取出需要预订的舱位产品
|
|
123
|
+
products = [x for x in products if x.get("cabin")]
|
|
124
|
+
# 根据价格升序排序(默认)
|
|
125
|
+
products = sorted(products, key=lambda x: x["amounts"]["amount"])
|
|
126
|
+
# 9.1 判断是否存在该舱位
|
|
127
|
+
if len(products) == 0:
|
|
96
128
|
raise RuntimeError(
|
|
97
129
|
f"航班预订查询页面,没有搜索到航班<{one_way_booking_dto.flight_no}>的{one_way_booking_dto.cabin}舱数据")
|
|
98
|
-
|
|
130
|
+
cabin_product = products[0]
|
|
131
|
+
# 9.2 判断余座
|
|
99
132
|
seats_status = cabin_product.get("seats_status")
|
|
100
133
|
logger.info(
|
|
101
134
|
f"航班预订查询页面,航班<{one_way_booking_dto.flight_no}>舱位<{one_way_booking_dto.cabin}>的座位情况:{seats_status}")
|
|
@@ -103,10 +136,10 @@ async def book_search(
|
|
|
103
136
|
raise NotEnoughTicketsError(
|
|
104
137
|
flight_no=one_way_booking_dto.flight_no, seats_status=seats_status, passengers=passengers
|
|
105
138
|
)
|
|
106
|
-
#
|
|
139
|
+
# 9.3. 判断货币符号,是否为 ¥(人民币结算)
|
|
107
140
|
# 目前都为国内航班,暂不考虑货币种类
|
|
108
141
|
|
|
109
|
-
#
|
|
142
|
+
# 9.4 判断销售价格是否满足预订需要
|
|
110
143
|
amounts: Dict[str, Any] = cabin_product.get("amounts")
|
|
111
144
|
amount: float = amounts.get("amount")
|
|
112
145
|
if amount > one_way_booking_dto.standard_price + book_input_dto.standard_increase_threshold:
|
|
@@ -133,7 +166,7 @@ async def book_search(
|
|
|
133
166
|
reduction_threshold=book_input_dto.sale_reduction_threshold, asset="销售价"
|
|
134
167
|
)
|
|
135
168
|
|
|
136
|
-
#
|
|
169
|
+
# 10. 点击【购票】按钮
|
|
137
170
|
booking_btn: Locator = cabin_product.get("booking_btn")
|
|
138
171
|
await booking_btn.click(button="left")
|
|
139
172
|
logger.info(f"航班预订查询页面,【购票】按钮点击完成")
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
from logging import Logger
|
|
13
13
|
from playwright.async_api import Page
|
|
14
14
|
import qdairlines_helper.config.url_const as url_const
|
|
15
|
-
from
|
|
15
|
+
from flight_helper.utils.exception_utils import IPBlockError
|
|
16
16
|
from qdairlines_helper.po.cash_pax_info_page import CashPaxInfoPage
|
|
17
17
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
18
18
|
|
|
@@ -13,7 +13,7 @@ from logging import Logger
|
|
|
13
13
|
from playwright.async_api import Page
|
|
14
14
|
from qdairlines_helper.po.home_page import HomePage
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
|
-
from
|
|
16
|
+
from flight_helper.utils.exception_utils import IPBlockError
|
|
17
17
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
18
18
|
|
|
19
19
|
|
|
@@ -14,9 +14,9 @@ from logging import Logger
|
|
|
14
14
|
from typing import Callable
|
|
15
15
|
from playwright.async_api import BrowserContext
|
|
16
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
|
+
from flight_helper.utils.exception_utils import DuplicatePaymentError
|
|
17
18
|
from qdairlines_helper.po.nhlms_cash_desk_page import NhlmsCashDeskPage
|
|
18
19
|
from playwright_helper.utils.browser_utils import switch_for_table_window
|
|
19
|
-
from qdairlines_helper.utils.exception_utils import DuplicatePaymentError
|
|
20
20
|
from flight_helper.models.dto.payment import HFPaidAccountPaymentInputDTO, PaymentResultDTO
|
|
21
21
|
|
|
22
22
|
|
|
@@ -37,7 +37,7 @@ async def load_nhlms_cash_desk_po(
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
async def hf_paid_account_payment(
|
|
40
|
-
*, page: NhlmsCashDeskPage, logger: Logger, order_no:
|
|
40
|
+
*, page: NhlmsCashDeskPage, logger: Logger, order_no: int, is_pay_completed_callback: Callable,
|
|
41
41
|
hf_paid_account_payment_dto: HFPaidAccountPaymentInputDTO, timeout: float = 60.0
|
|
42
42
|
) -> PaymentResultDTO:
|
|
43
43
|
# 1. 获取收银台支付流水
|
|
@@ -10,38 +10,60 @@
|
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
12
|
import re
|
|
13
|
+
import inspect
|
|
14
|
+
from logging import Logger
|
|
13
15
|
from aiohttp import CookieJar
|
|
14
|
-
from typing import Dict, Any, Optional, List
|
|
16
|
+
from typing import Dict, Any, Optional, List, Callable
|
|
15
17
|
from qdairlines_helper.http.flight_order import FlightOrder
|
|
16
|
-
from flight_helper.models.dto.
|
|
18
|
+
from flight_helper.models.dto.http_schema import HTTPRequestDTO
|
|
19
|
+
from flight_helper.models.dto.itinerary import QueryItineraryResponseDTO, QueryItineraryRequestDTO
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
async def get_order_detail(
|
|
20
|
-
*, query_dto: QueryItineraryRequestDTO,
|
|
21
|
-
|
|
23
|
+
*, query_dto: QueryItineraryRequestDTO, logger: Logger, callback_get_proxy: Callable,
|
|
24
|
+
timeout: Optional[int] = None, retry: Optional[int] = None, cookie_jar: Optional[CookieJar] = None,
|
|
25
|
+
enable_log: Optional[bool] = None
|
|
22
26
|
) -> Dict[str, Any]:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
http_request_dto = HTTPRequestDTO(
|
|
28
|
+
http_domain=query_dto.payment_domain, http_protocol=query_dto.payment_protocol,
|
|
29
|
+
storage_state=query_dto.storage_state, timeout=timeout, retry=retry, enable_log=enable_log,
|
|
30
|
+
token=query_dto.token
|
|
27
31
|
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
flight_order = FlightOrder(http_request_dto=http_request_dto, cookie_jar=cookie_jar)
|
|
33
|
+
last_order_info: Dict[str, Any] = dict()
|
|
34
|
+
for index in range(5):
|
|
35
|
+
proxy = None
|
|
36
|
+
is_end: bool = True if index + 1 == 5 else False
|
|
37
|
+
if index != 0:
|
|
38
|
+
if inspect.iscoroutinefunction(callback_get_proxy):
|
|
39
|
+
proxy = await callback_get_proxy(logger=logger)
|
|
40
|
+
else:
|
|
41
|
+
proxy = callback_get_proxy(logger=logger)
|
|
42
|
+
try:
|
|
43
|
+
last_order_info = await flight_order.get_order_details(
|
|
44
|
+
pre_order_no=query_dto.pre_order_no, user_id=query_dto.user_id, is_end=is_end, proxy=proxy,
|
|
45
|
+
headers=query_dto.headers,
|
|
46
|
+
)
|
|
47
|
+
await flight_order.http_client.close()
|
|
48
|
+
logger.info("查询青岛航空官网的订单详情数据成功")
|
|
49
|
+
break
|
|
50
|
+
except (Exception,):
|
|
51
|
+
pass
|
|
52
|
+
if isinstance(last_order_info, dict) and last_order_info.get("result") and last_order_info.get("code") == 1:
|
|
53
|
+
return last_order_info.get("result")
|
|
34
54
|
else:
|
|
35
|
-
raise RuntimeError(f"订单<{query_dto.pre_order_no}>,获取详情数据异常,返回值:{
|
|
55
|
+
raise RuntimeError(f"订单<{query_dto.pre_order_no}>,获取详情数据异常,返回值:{last_order_info}")
|
|
36
56
|
|
|
37
57
|
|
|
38
58
|
async def get_order_itinerary(
|
|
39
|
-
*, query_dto: QueryItineraryRequestDTO,
|
|
40
|
-
|
|
59
|
+
*, query_dto: QueryItineraryRequestDTO, logger: Logger, callback_get_proxy: Callable,
|
|
60
|
+
timeout: Optional[int] = None, retry: Optional[int] = None, cookie_jar: Optional[CookieJar] = None,
|
|
61
|
+
enable_log: Optional[bool] = None
|
|
41
62
|
) -> Optional[QueryItineraryResponseDTO]:
|
|
42
63
|
_order_info: Dict[str, Any] = dict(pre_order_no=query_dto.pre_order_no)
|
|
43
64
|
result = await get_order_detail(
|
|
44
|
-
query_dto=query_dto, timeout=timeout, retry=retry, cookie_jar=cookie_jar, enable_log=enable_log
|
|
65
|
+
query_dto=query_dto, timeout=timeout, retry=retry, cookie_jar=cookie_jar, enable_log=enable_log,
|
|
66
|
+
callback_get_proxy=callback_get_proxy, logger=logger
|
|
45
67
|
)
|
|
46
68
|
_order_info["cash_unit"] = result.get("cashUnit")
|
|
47
69
|
_order_info["order_status"] = result.get("orderStatus")
|
|
@@ -60,3 +82,6 @@ async def get_order_itinerary(
|
|
|
60
82
|
if order_itineraries:
|
|
61
83
|
_order_info["itinerary_info"] = order_itineraries
|
|
62
84
|
return QueryItineraryResponseDTO(**_order_info)
|
|
85
|
+
else:
|
|
86
|
+
logger.warning(
|
|
87
|
+
f'青岛航空官网订单<{query_dto.pre_order_no}>,当前状态:{result.get("orderStatus")},没有生成行程单信息')
|
|
@@ -14,7 +14,7 @@ from logging import Logger
|
|
|
14
14
|
from playwright.async_api import Page
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
16
|
from qdairlines_helper.po.air_order_page import AirOrderPage
|
|
17
|
-
from
|
|
17
|
+
from flight_helper.utils.exception_utils import IPBlockError
|
|
18
18
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
19
19
|
|
|
20
20
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
from logging import Logger
|
|
13
13
|
from playwright.async_api import Page
|
|
14
14
|
import qdairlines_helper.config.url_const as url_const
|
|
15
|
-
from
|
|
15
|
+
from flight_helper.utils.exception_utils import IPBlockError
|
|
16
16
|
from qdairlines_helper.po.order_verify_page import OrderVerifyPage
|
|
17
17
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
18
18
|
|
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
-
import inspect
|
|
13
12
|
from logging import Logger
|
|
14
13
|
from aiohttp import CookieJar
|
|
15
14
|
from playwright.async_api import Page
|
|
16
15
|
from typing import Dict, Any, Optional, Callable
|
|
17
16
|
import qdairlines_helper.config.url_const as url_const
|
|
18
17
|
from qdairlines_helper.po.pay_success_page import PaySuccessPage
|
|
18
|
+
from qdairlines_helper.po.nhlms_cash_desk_page import NhlmsCashDeskPage
|
|
19
19
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg
|
|
20
20
|
from qdairlines_helper.controller.order_detail import get_order_detail
|
|
21
21
|
from flight_helper.models.dto.itinerary import QueryItineraryRequestDTO
|
|
22
|
-
from
|
|
22
|
+
from flight_helper.utils.exception_utils import IPBlockError, PaymentFailedError
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
async def load_pay_success_page(
|
|
@@ -52,15 +52,16 @@ async def two_check_pay_success(
|
|
|
52
52
|
except Exception as e:
|
|
53
53
|
logger.error(e)
|
|
54
54
|
|
|
55
|
-
if inspect.iscoroutinefunction(callback_get_proxy):
|
|
56
|
-
query_dto.proxy = await callback_get_proxy(logger=logger)
|
|
57
|
-
else:
|
|
58
|
-
query_dto.proxy = callback_get_proxy(logger=logger)
|
|
59
|
-
|
|
60
55
|
# 2. 尝试取查询票号,看订单状态,是 BOOKED,还是 TICKETED
|
|
61
56
|
order_detail: Dict[str, Any] = await get_order_detail(
|
|
62
57
|
query_dto=query_dto, timeout=int(timeout), retry=retry, enable_log=enable_log, cookie_jar=cookie_jar,
|
|
58
|
+
callback_get_proxy=callback_get_proxy, logger=logger
|
|
63
59
|
)
|
|
64
60
|
order_status = (order_detail.get("orderStatus", "")).upper()
|
|
65
61
|
if order_status not in ("TICKED",):
|
|
66
|
-
raise PaymentFailedError(pre_order_no=query_dto.pre_order_no)
|
|
62
|
+
raise PaymentFailedError(pre_order_no=query_dto.pre_order_no, order_status=order_status)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def check_hf_payment_is_success(*, page: NhlmsCashDeskPage, logger: Logger) -> None:
|
|
66
|
+
# 1. 看支付成功页面是否加载完成
|
|
67
|
+
await page.check_is_pay_success(logger=logger)
|
|
@@ -9,13 +9,17 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
+
import inspect
|
|
12
13
|
from logging import Logger
|
|
13
|
-
from
|
|
14
|
+
from aiohttp import CookieJar
|
|
15
|
+
from typing import Dict, Any, Optional, Callable
|
|
14
16
|
from playwright.async_api import Page, BrowserContext
|
|
15
17
|
from qdairlines_helper.po.login_page import LoginPage
|
|
16
18
|
import qdairlines_helper.config.url_const as url_const
|
|
19
|
+
from qdairlines_helper.http.user_login import UserLogin
|
|
17
20
|
from qdairlines_helper.controller.home import load_home_po
|
|
18
|
-
from
|
|
21
|
+
from flight_helper.utils.exception_utils import IPBlockError
|
|
22
|
+
from flight_helper.models.dto.http_schema import HTTPRequestDTO
|
|
19
23
|
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
20
24
|
from qdairlines_helper.utils.po_utils import get_ip_access_blocked_msg, parse_user_info_from_page_response
|
|
21
25
|
|
|
@@ -94,3 +98,32 @@ async def user_login_callback(
|
|
|
94
98
|
user_info = parse_user_info_from_page_response(network_logs=network_logs)
|
|
95
99
|
user_info.update(dict(storage_state=await context.storage_state()))
|
|
96
100
|
return user_info
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def get_user_info(
|
|
104
|
+
*, logger: Logger, http_request_dto: HTTPRequestDTO, callback_get_proxy: Callable,
|
|
105
|
+
cookie_jar: Optional[CookieJar] = None
|
|
106
|
+
) -> Dict[str, Any]:
|
|
107
|
+
user_login = UserLogin(http_request_dto=http_request_dto, cookie_jar=cookie_jar)
|
|
108
|
+
last_user_info: Dict[str, Any] = dict()
|
|
109
|
+
for index in range(5):
|
|
110
|
+
proxy = None
|
|
111
|
+
is_end: bool = True if index + 1 == 5 else False
|
|
112
|
+
if index != 0:
|
|
113
|
+
if inspect.iscoroutinefunction(callback_get_proxy):
|
|
114
|
+
proxy = await callback_get_proxy(logger=logger)
|
|
115
|
+
else:
|
|
116
|
+
proxy = callback_get_proxy(logger=logger)
|
|
117
|
+
try:
|
|
118
|
+
last_user_info = await user_login.get_user_info(
|
|
119
|
+
is_end=is_end, proxy=proxy, headers=http_request_dto.headers
|
|
120
|
+
)
|
|
121
|
+
await user_login.http_client.close()
|
|
122
|
+
logger.info("获取青岛航空官网的用户详情数据成功")
|
|
123
|
+
break
|
|
124
|
+
except (Exception,):
|
|
125
|
+
pass
|
|
126
|
+
if isinstance(last_user_info, dict) and last_user_info.get("result") and last_user_info.get("code") == 1:
|
|
127
|
+
return last_user_info.get("result")
|
|
128
|
+
else:
|
|
129
|
+
raise RuntimeError(f"获取用户详情数据异常,返回值:{last_user_info}")
|
|
@@ -11,51 +11,15 @@
|
|
|
11
11
|
"""
|
|
12
12
|
from aiohttp import CookieJar
|
|
13
13
|
from typing import Optional, Dict, Any
|
|
14
|
+
from qdairlines_helper.http.http_base import HttpBase
|
|
14
15
|
import qdairlines_helper.config.url_const as url_const
|
|
15
|
-
from
|
|
16
|
+
from flight_helper.models.dto.http_schema import HTTPRequestDTO
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class FlightOrder:
|
|
19
|
+
class FlightOrder(HttpBase):
|
|
19
20
|
|
|
20
|
-
def __init__(
|
|
21
|
-
|
|
22
|
-
domain: Optional[str] = None, protocol: Optional[str] = None, timeout: Optional[int] = None,
|
|
23
|
-
retry: Optional[int] = None, enable_log: Optional[bool] = None, cookie_jar: Optional[CookieJar] = None
|
|
24
|
-
):
|
|
25
|
-
self._domain = domain or "127.0.0.1:18070"
|
|
26
|
-
self._protocol = protocol or "http"
|
|
27
|
-
self._timeout = timeout or 60
|
|
28
|
-
self._retry = retry or 0
|
|
29
|
-
self._token = token
|
|
30
|
-
self._origin = f"{self._protocol}://{self._domain}"
|
|
31
|
-
self._enable_log = enable_log or True
|
|
32
|
-
self._cookie_jar = cookie_jar or CookieJar()
|
|
33
|
-
self._proxy = proxy
|
|
34
|
-
self._playwright_state: Dict[str, Any] = playwright_state
|
|
35
|
-
self.http_client: Optional[_HttpClientFactory] = None
|
|
36
|
-
|
|
37
|
-
def _get_http_client(self) -> _HttpClientFactory:
|
|
38
|
-
"""延迟获取 HTTP 客户端"""
|
|
39
|
-
if self.http_client is None:
|
|
40
|
-
self.http_client = _HttpClientFactory(
|
|
41
|
-
protocol=self._protocol,
|
|
42
|
-
domain=self._domain,
|
|
43
|
-
timeout=self._timeout,
|
|
44
|
-
retry=self._retry,
|
|
45
|
-
enable_log=self._enable_log,
|
|
46
|
-
cookie_jar=self._cookie_jar,
|
|
47
|
-
playwright_state=self._playwright_state
|
|
48
|
-
)
|
|
49
|
-
return self.http_client
|
|
50
|
-
|
|
51
|
-
def _get_headers(self) -> Dict[str, str]:
|
|
52
|
-
headers = {
|
|
53
|
-
"content-type": "application/json;charset=utf-8",
|
|
54
|
-
"origin": self._domain,
|
|
55
|
-
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
|
56
|
-
"authorization": f"Bearer {self._token}"
|
|
57
|
-
}
|
|
58
|
-
return headers
|
|
21
|
+
def __init__(self, *, http_request_dto: HTTPRequestDTO, cookie_jar: Optional[CookieJar] = None):
|
|
22
|
+
super().__init__(http_request_dto=http_request_dto, cookie_jar=cookie_jar)
|
|
59
23
|
|
|
60
24
|
async def get_order_details(
|
|
61
25
|
self, *, pre_order_no: str, user_id: str, proxy: Optional[Dict[str, Any]] = None,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
+
# ProjectName: python-qdairlines-helper
|
|
5
|
+
# FileName: http_base.py
|
|
6
|
+
# Description: http基础模块
|
|
7
|
+
# Author: ASUS
|
|
8
|
+
# CreateDate: 2026/01/13
|
|
9
|
+
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
+
"""
|
|
12
|
+
from aiohttp import CookieJar
|
|
13
|
+
from typing import Optional, Dict, Any
|
|
14
|
+
from flight_helper.models.dto.http_schema import HTTPRequestDTO
|
|
15
|
+
from http_helper.client.async_proxy import HttpClientFactory as _HttpClientFactory
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class HttpBase(object):
|
|
19
|
+
|
|
20
|
+
def __init__(self, *, http_request_dto: HTTPRequestDTO, cookie_jar: Optional[CookieJar] = None):
|
|
21
|
+
self._domain = http_request_dto.http_domain or "127.0.0.1:18070"
|
|
22
|
+
self._protocol = http_request_dto.http_protocol or "http"
|
|
23
|
+
self._timeout = http_request_dto.timeout or 60
|
|
24
|
+
self._retry = http_request_dto.retry or 0
|
|
25
|
+
self._token = http_request_dto.token
|
|
26
|
+
self._origin = f"{self._protocol}://{self._domain}"
|
|
27
|
+
self._enable_log = http_request_dto.enable_log if http_request_dto.enable_log is not None else True
|
|
28
|
+
self._cookie_jar = cookie_jar or CookieJar()
|
|
29
|
+
self._proxy = http_request_dto.proxy
|
|
30
|
+
self._playwright_state: Dict[str, Any] = http_request_dto.storage_state
|
|
31
|
+
self.http_client: Optional[_HttpClientFactory] = None
|
|
32
|
+
|
|
33
|
+
def _get_http_client(self) -> _HttpClientFactory:
|
|
34
|
+
"""延迟获取 HTTP 客户端"""
|
|
35
|
+
if self.http_client is None:
|
|
36
|
+
self.http_client = _HttpClientFactory(
|
|
37
|
+
protocol=self._protocol,
|
|
38
|
+
domain=self._domain,
|
|
39
|
+
timeout=self._timeout,
|
|
40
|
+
retry=self._retry,
|
|
41
|
+
enable_log=self._enable_log,
|
|
42
|
+
cookie_jar=self._cookie_jar,
|
|
43
|
+
playwright_state=self._playwright_state
|
|
44
|
+
)
|
|
45
|
+
return self.http_client
|
|
46
|
+
|
|
47
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
48
|
+
headers = {
|
|
49
|
+
"content-type": "application/json;charset=utf-8",
|
|
50
|
+
"origin": self._domain,
|
|
51
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
|
|
52
|
+
"authorization": f"Bearer {self._token}"
|
|
53
|
+
}
|
|
54
|
+
return headers
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
+
# ProjectName: python-qdairlines-helper
|
|
5
|
+
# FileName: user_login.py
|
|
6
|
+
# Description: 用户登录模块
|
|
7
|
+
# Author: ASUS
|
|
8
|
+
# CreateDate: 2026/01/13
|
|
9
|
+
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
+
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
+
"""
|
|
12
|
+
from aiohttp import CookieJar
|
|
13
|
+
from typing import Optional, Dict, Any
|
|
14
|
+
from qdairlines_helper.http.http_base import HttpBase
|
|
15
|
+
import qdairlines_helper.config.url_const as url_const
|
|
16
|
+
from flight_helper.models.dto.http_schema import HTTPRequestDTO
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class UserLogin(HttpBase):
|
|
20
|
+
def __init__(self, *, http_request_dto: HTTPRequestDTO, cookie_jar: Optional[CookieJar] = None):
|
|
21
|
+
super().__init__(http_request_dto=http_request_dto, cookie_jar=cookie_jar)
|
|
22
|
+
|
|
23
|
+
async def get_user_info(
|
|
24
|
+
self, *, proxy: Optional[Dict[str, Any]] = None, headers: Dict[str, str] = None,
|
|
25
|
+
is_end: Optional[bool] = None
|
|
26
|
+
) -> Dict[str, Any]:
|
|
27
|
+
_headers = self._get_headers()
|
|
28
|
+
if headers is not None:
|
|
29
|
+
_headers.update(headers)
|
|
30
|
+
_headers["referer"] = f"{self._origin}{url_const.login_url}"
|
|
31
|
+
if is_end is None:
|
|
32
|
+
is_end = True
|
|
33
|
+
if proxy:
|
|
34
|
+
self._proxy = proxy
|
|
35
|
+
exception_keywords = [r'<h3[^>]*class="font-bold"[^>]*>([^<]+)</h3>']
|
|
36
|
+
return await self._get_http_client().request(
|
|
37
|
+
method="GET",
|
|
38
|
+
url=url_const.login_after_api_url,
|
|
39
|
+
headers=_headers,
|
|
40
|
+
is_end=is_end,
|
|
41
|
+
proxy_config=self._proxy or None,
|
|
42
|
+
exception_keywords=exception_keywords
|
|
43
|
+
)
|
|
@@ -13,7 +13,7 @@ from typing import Optional
|
|
|
13
13
|
from playwright.async_api import Page, Locator
|
|
14
14
|
from playwright_helper.libs.base_po import BasePo
|
|
15
15
|
import qdairlines_helper.config.url_const as url_const
|
|
16
|
-
from
|
|
16
|
+
from flight_helper.utils.exception_utils import PassengerTypeError
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class AddPassengerPage(BasePo):
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
12
|
import re
|
|
13
|
+
from logging import Logger
|
|
13
14
|
from typing import Optional, Dict, Any, List
|
|
14
15
|
from playwright.async_api import Page, Locator
|
|
15
16
|
from playwright_helper.libs.base_po import BasePo
|
|
16
17
|
import qdairlines_helper.config.url_const as url_const
|
|
17
|
-
from
|
|
18
|
+
from flight_helper.utils.exception_utils import ProductTypeError
|
|
18
19
|
from playwright_helper.utils.type_utils import convert_order_amount_text, safe_convert_advanced
|
|
19
20
|
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
20
21
|
|
|
@@ -27,6 +28,15 @@ class BookSearchPage(BasePo):
|
|
|
27
28
|
super().__init__(page, url or url_const.book_search_url)
|
|
28
29
|
self.__page = page
|
|
29
30
|
|
|
31
|
+
async def get_empyt_data_page(self, timeout: float = 5.0) -> Locator:
|
|
32
|
+
"""
|
|
33
|
+
打开搜索页后,看看当前默认状态是不是空数据页,非空数据页,会有弹框
|
|
34
|
+
:param timeout: 超时时间(秒)
|
|
35
|
+
:return: (是否存在, 错误信息 | 元素对象)
|
|
36
|
+
"""
|
|
37
|
+
selector: str = '//strong[contains(text(), "没有在飞航班")]'
|
|
38
|
+
return await self.get_locator(selector=selector, timeout=timeout)
|
|
39
|
+
|
|
30
40
|
async def get_reminder_dialog_continue_book_btn(self, timeout: float = 5.0) -> Locator:
|
|
31
41
|
"""
|
|
32
42
|
获取预订搜索页的温馨提醒弹框中的【继续购票】按钮
|
|
@@ -72,25 +82,18 @@ class BookSearchPage(BasePo):
|
|
|
72
82
|
selector: str = '//div[contains(@class, "flight_search_form")]//button[@class="search_btn"]'
|
|
73
83
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
74
84
|
|
|
75
|
-
async def get_flight_info_plane(self, timeout: float = 5.0) -> Locator:
|
|
85
|
+
async def get_flight_info_plane(self, flight_no: str, timeout: float = 5.0) -> Locator:
|
|
76
86
|
"""
|
|
77
87
|
获取预订搜索页航班内容栏航班基本信息
|
|
88
|
+
:param flight_no: 航班编号
|
|
78
89
|
:param timeout: 超时时间(秒)
|
|
79
90
|
:return: (是否存在, 错误信息|元素对象)
|
|
80
91
|
"""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
获取预订搜索页航班编号
|
|
87
|
-
:param timeout: 超时时间(秒)
|
|
88
|
-
:param locator: flight_info_plane Locator 对象
|
|
89
|
-
:return: (是否存在, 错误信息|元素对象)
|
|
90
|
-
"""
|
|
91
|
-
selector: str = 'xpath=(.//div[contains(@class, "flight_qda")]/span)[2]'
|
|
92
|
-
sub_locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
93
|
-
return (await sub_locator.inner_text(timeout=timeout)).strip()
|
|
92
|
+
try:
|
|
93
|
+
selector: str = f'//div[contains(@class, "flight_qda")]/span[contains(text(), "{flight_no}")]/../../../..'
|
|
94
|
+
return await self.get_locator(selector=selector, timeout=timeout)
|
|
95
|
+
except (Exception,):
|
|
96
|
+
raise RuntimeError(f"航班预订查询页面,没有搜索到航班<{flight_no}>数据")
|
|
94
97
|
|
|
95
98
|
async def get_flight_product_nav(self, locator: Locator, product_type: str, timeout: float = 5.0) -> Locator:
|
|
96
99
|
"""
|
|
@@ -111,26 +114,41 @@ class BookSearchPage(BasePo):
|
|
|
111
114
|
selector: str = f'xpath=(.//div[contains(@class, "nav_item el-col el-col-24")])[{index}]'
|
|
112
115
|
return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
113
116
|
|
|
114
|
-
async def
|
|
117
|
+
async def get_more_product_btn(self, locator: Locator, timeout: float = 5.0) -> Locator:
|
|
115
118
|
"""
|
|
116
|
-
|
|
119
|
+
获取预订搜索页航班列表更多产品
|
|
117
120
|
:param timeout: 超时时间(秒)
|
|
121
|
+
:param locator: flight_info_plane Locator 对象
|
|
122
|
+
:return: (是否存在, 错误信息|元素对象)
|
|
123
|
+
"""
|
|
124
|
+
selector: str = f'xpath=.//span[@class="fa el-icon-caret-bottom"]'
|
|
125
|
+
return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
126
|
+
|
|
127
|
+
async def get_flight_products(self, locator: Locator, logger: Logger, flight_no: str) -> List[Dict[str, Any]]:
|
|
128
|
+
"""
|
|
129
|
+
获取预订搜索页航班内容栏所有航班产品
|
|
130
|
+
:param locator: flight_info_plane Locator 对象
|
|
131
|
+
:param logger: Logger 对象,日志记录
|
|
132
|
+
:param flight_no: 航班编号
|
|
118
133
|
:return: (是否存在, 错误信息|元素对象)
|
|
119
134
|
"""
|
|
120
|
-
|
|
121
|
-
|
|
135
|
+
timeout: float = 1.0
|
|
136
|
+
selector: str = 'xpath=.//div[@class="text-center cabinClasses_info el-row" and not(@style="display: none;")]//table/tbody/tr[not(@class)]/td[@class]/..'
|
|
137
|
+
products_locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
122
138
|
locators: List[Locator] = await products_locator.all()
|
|
123
|
-
flight_products =
|
|
139
|
+
flight_products = list()
|
|
140
|
+
logger.info(f"当前找到了关于航班<{flight_no}>的<{len(locators)}>条产品数据")
|
|
124
141
|
for locator in locators:
|
|
125
142
|
try:
|
|
126
143
|
booking_btn: Locator = await self._get_flight_product_booking_btn(locator=locator, timeout=timeout)
|
|
127
144
|
amounts: Dict[str, Any] = await self._get_flight_product_price(locator=locator, timeout=timeout)
|
|
128
145
|
seats_status: int = await self._get_flight_product_seats_status(locator=locator, timeout=timeout)
|
|
129
146
|
cabin = await self._get_flight_product_cabin(locator=locator, timeout=timeout)
|
|
130
|
-
flight_products
|
|
147
|
+
flight_products.append(dict(
|
|
131
148
|
amounts=amounts, cabin=cabin, seats_status=seats_status, booking_btn=booking_btn
|
|
132
|
-
)
|
|
149
|
+
))
|
|
133
150
|
except (PlaywrightError, PlaywrightTimeoutError, EnvironmentError, RuntimeError, Exception) as e:
|
|
151
|
+
logger.error(e)
|
|
134
152
|
continue
|
|
135
153
|
return flight_products
|
|
136
154
|
|
|
@@ -143,7 +161,7 @@ class BookSearchPage(BasePo):
|
|
|
143
161
|
"""
|
|
144
162
|
selector: str = 'xpath=.//div[@class="ticket_clazz"]/div/span'
|
|
145
163
|
locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
146
|
-
text: str = (await locator.inner_text(timeout=timeout)).strip()
|
|
164
|
+
text: str = (await locator.inner_text(timeout=timeout * 1000)).strip()
|
|
147
165
|
match = re.search(r'\((.*?)舱\)', text)
|
|
148
166
|
if match:
|
|
149
167
|
return match.group(1).strip()
|
|
@@ -156,9 +174,9 @@ class BookSearchPage(BasePo):
|
|
|
156
174
|
:param locator: flight_product Locator 对象
|
|
157
175
|
:return: (是否存在, 错误信息|元素对象)
|
|
158
176
|
"""
|
|
159
|
-
selector: str = 'xpath=.//div[@class="price_flex_top"]
|
|
177
|
+
selector: str = 'xpath=.//div[@class="price_flex_top"]'
|
|
160
178
|
locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
161
|
-
amount_text: str = (await locator.inner_text(timeout=timeout)).strip()
|
|
179
|
+
amount_text: str = (await locator.inner_text(timeout=timeout * 1000)).strip()
|
|
162
180
|
return convert_order_amount_text(amount_text=amount_text)
|
|
163
181
|
|
|
164
182
|
async def _get_flight_product_booking_btn(self, locator: Locator, timeout: float = 5.0) -> Locator:
|
|
@@ -168,7 +186,7 @@ class BookSearchPage(BasePo):
|
|
|
168
186
|
:param locator: flight_product Locator 对象
|
|
169
187
|
:return: (是否存在, 错误信息|元素对象)
|
|
170
188
|
"""
|
|
171
|
-
selector: str = 'xpath
|
|
189
|
+
selector: str = 'xpath=(.//td[@class="ticket_num"]/div/span)[1]'
|
|
172
190
|
return await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
173
191
|
|
|
174
192
|
async def _get_flight_product_seats_status(self, locator: Locator, timeout: float = 5.0) -> int:
|
|
@@ -178,11 +196,12 @@ class BookSearchPage(BasePo):
|
|
|
178
196
|
:param locator: flight_product Locator 对象
|
|
179
197
|
:return: (是否存在, 错误信息|元素对象)
|
|
180
198
|
"""
|
|
181
|
-
selector: str = 'xpath
|
|
199
|
+
selector: str = 'xpath=(.//td[@class="ticket_num"]/div/span)[2]'
|
|
182
200
|
locator: Locator = await self.get_sub_locator(locator=locator, selector=selector, timeout=timeout)
|
|
183
|
-
more_seats_text: str = (await locator.inner_text(timeout=timeout)).strip()
|
|
201
|
+
more_seats_text: str = (await locator.inner_text(timeout=timeout * 1000)).strip()
|
|
184
202
|
match = re.search(r'\d+', more_seats_text)
|
|
185
203
|
if match:
|
|
186
|
-
|
|
204
|
+
# ⚠️ 正则 \d+ 没有捕获组,只能用 group(0) 或 group()
|
|
205
|
+
return safe_convert_advanced(value=match.group().strip())
|
|
187
206
|
else:
|
|
188
207
|
return 999999
|
|
@@ -14,7 +14,7 @@ from urllib.parse import urlparse, parse_qs
|
|
|
14
14
|
from playwright.async_api import Page, Locator
|
|
15
15
|
from playwright_helper.libs.base_po import BasePo
|
|
16
16
|
import qdairlines_helper.config.url_const as url_const
|
|
17
|
-
from
|
|
17
|
+
from flight_helper.utils.exception_utils import PaymentChannelError
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class CashPaxInfoPage(BasePo):
|
|
@@ -9,12 +9,17 @@
|
|
|
9
9
|
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
10
|
# ---------------------------------------------------------------------------------------------------------
|
|
11
11
|
"""
|
|
12
|
+
import asyncio
|
|
13
|
+
from logging import Logger
|
|
12
14
|
from typing import Optional, Any
|
|
15
|
+
from collections import OrderedDict
|
|
16
|
+
from datetime import datetime, timedelta
|
|
13
17
|
from playwright.async_api import Page, Locator
|
|
14
18
|
from playwright_helper.libs.base_po import BasePo
|
|
15
19
|
import qdairlines_helper.config.url_const as url_const
|
|
16
20
|
from playwright_helper.utils.type_utils import safe_convert_advanced
|
|
17
|
-
from
|
|
21
|
+
from flight_helper.utils.exception_utils import HFPaymentTypeError, PaymentFailError
|
|
22
|
+
from playwright.async_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError
|
|
18
23
|
|
|
19
24
|
|
|
20
25
|
class NhlmsCashDeskPage(BasePo):
|
|
@@ -93,3 +98,39 @@ class NhlmsCashDeskPage(BasePo):
|
|
|
93
98
|
"""
|
|
94
99
|
selector: str = 'xpath=//div[@class="content-bottom network-recharge"]//button[@id="submitBtn"]'
|
|
95
100
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
101
|
+
|
|
102
|
+
async def check_is_pay_success(self, logger: Logger) -> None:
|
|
103
|
+
"""检查支付是否成功"""
|
|
104
|
+
data = OrderedDict([
|
|
105
|
+
("支付成功", 'xpath=//img[@alt="支付成功"]'),
|
|
106
|
+
(PaymentFailError("账户余额不足"), 'xpath=//span[contains(text(), "账户余额不足")]'),
|
|
107
|
+
(
|
|
108
|
+
PaymentFailError(
|
|
109
|
+
"尊敬的旅客:您的IP由于频繁访问已受限,客票预定可前往青岛航空微信公众号预定。如您需要办理其他客票业务,请联系官方客服热线:0532-96630。"
|
|
110
|
+
),
|
|
111
|
+
'//h3[contains(text(), "您的IP")]'
|
|
112
|
+
)
|
|
113
|
+
])
|
|
114
|
+
timeout = 120
|
|
115
|
+
end_time = datetime.now() + timedelta(seconds=timeout)
|
|
116
|
+
|
|
117
|
+
while datetime.now() < end_time:
|
|
118
|
+
for index, (outcome, selector) in enumerate(data.items()):
|
|
119
|
+
try:
|
|
120
|
+
await self.get_locator(selector=selector, timeout=1)
|
|
121
|
+
if index == 0:
|
|
122
|
+
logger.info(f"支付结果检测:{outcome}")
|
|
123
|
+
return # 成功
|
|
124
|
+
else:
|
|
125
|
+
# 一旦匹配失败,立即抛出!
|
|
126
|
+
raise outcome
|
|
127
|
+
except (PlaywrightTimeoutError, PlaywrightError, RuntimeError):
|
|
128
|
+
# 只捕获“未找到元素”的异常,其他异常往上抛
|
|
129
|
+
continue
|
|
130
|
+
except Exception:
|
|
131
|
+
# 可选:记录未知异常,但不吞掉
|
|
132
|
+
raise # 或 log 后 re-raise
|
|
133
|
+
|
|
134
|
+
await asyncio.sleep(0.5) # 避免 CPU 占用过高
|
|
135
|
+
logger.error(f"支付结果检测:超时,当前页面url: {self.__page.url}")
|
|
136
|
+
raise PlaywrightTimeoutError(f"支付结果检测超时,{timeout}秒内未出现成功或已知失败状态")
|
|
@@ -35,7 +35,7 @@ class OrderVerifyPage(BasePo):
|
|
|
35
35
|
selector: str = 'xpath=//div[@class="order_info"]'
|
|
36
36
|
while attempt <= refresh_attempt:
|
|
37
37
|
try:
|
|
38
|
-
await self.__page.reload(timeout=timeout)
|
|
38
|
+
await self.__page.reload(timeout=timeout * 1000)
|
|
39
39
|
return await self.get_locator(selector=selector, timeout=timeout)
|
|
40
40
|
except (PlaywrightError, PlaywrightTimeoutError, EnvironmentError, RuntimeError, Exception) as e:
|
|
41
41
|
attempt += 1
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
python_qdairlines_helper-0.1.4.dist-info/licenses/LICENSE,sha256=WtjCEwlcVzkh1ziO35P2qfVEkLjr87Flro7xlHz3CEY,11556
|
|
2
|
-
qdairlines_helper/__init__.py,sha256=bN1FqEK_jvefO_HU_8HFJpjuOzHTsuhmRe5KAzmraR4,479
|
|
3
|
-
qdairlines_helper/config/__init__.py,sha256=JjIRocePx3gwFNpdauuiwBU6B2Ug565xaHLEMu5Oo0I,479
|
|
4
|
-
qdairlines_helper/config/url_const.py,sha256=Ro-8r3dRxuloJC5NIpwUMQ7Pezg2cDyZOqNUfTqCOFQ,2086
|
|
5
|
-
qdairlines_helper/controller/__init__.py,sha256=oyeciEBlDLNpqHblB0R-nXQG3HsI8L5mmlWulQ7Y38Q,482
|
|
6
|
-
qdairlines_helper/controller/add_passenger.py,sha256=GxaoSoLOH_XXTR79bDtSEbnmM_dSpBko6OzlrQtuT7U,5159
|
|
7
|
-
qdairlines_helper/controller/book_payment.py,sha256=ygIHu6LGnLLihDM4PrpNDr3ZkTEZiQb-taqk4DuvANk,6104
|
|
8
|
-
qdairlines_helper/controller/book_search.py,sha256=Q1KZKYeRmJh26nK1G9d8sWJF7_kCUS-9mJjTyoWCXpE,7598
|
|
9
|
-
qdairlines_helper/controller/cash_pax_info.py,sha256=V5QKlvbWp8eF6gRos818w-9MyuienjgECkKKZkxCys8,2511
|
|
10
|
-
qdairlines_helper/controller/home.py,sha256=KzTyl7vvI3_Blc-XQ8ItueGR1hnV6f9gEdYalfXG9Ws,1394
|
|
11
|
-
qdairlines_helper/controller/nhlms_cashdesk.py,sha256=Fai8zZiqhUJl5-RWdkaGOdB3GTd5UQ7fpXDt4Emv65w,4312
|
|
12
|
-
qdairlines_helper/controller/order_detail.py,sha256=6h6TyS_-yU2AHdFxbQS16TyjKWGWeCu1uVlsz9wANDE,3172
|
|
13
|
-
qdairlines_helper/controller/order_query.py,sha256=JzNrJj6HRHSJMQhszucWPf6nWQl4Y5B7oE1N9SpD47k,1907
|
|
14
|
-
qdairlines_helper/controller/order_verify.py,sha256=G9CC4a6KZg-VdWH6hzHDN7DkaoW2J9_yc7-mE2fL7Ms,2273
|
|
15
|
-
qdairlines_helper/controller/pay_success.py,sha256=UiIn6rMpD4BIsBbbiZH4xuWJd5eeuHp0sb-qmo3MJGg,3153
|
|
16
|
-
qdairlines_helper/controller/user_login.py,sha256=7PBbzP7doeUEy4M_Igb32IhjT4GzY_I-U3YimxekR_Q,4735
|
|
17
|
-
qdairlines_helper/http/__init__.py,sha256=96AJPf2zgw_rm3Wv-boIBltWFgZH3Yo-fn5M9SRK280,489
|
|
18
|
-
qdairlines_helper/http/flight_order.py,sha256=tC25YNk1pDzMmk4Zy6v120PwNuuaDhlHFh5xPBxtKco,3665
|
|
19
|
-
qdairlines_helper/po/__init__.py,sha256=HoouLJGLu_dI6IAKTVjBRraGHMcAUgntayTph-BUBAE,481
|
|
20
|
-
qdairlines_helper/po/add_passenger_page.py,sha256=XpaU6g7YSLhA8h3bHWJ_6CwnwgUmRtmLulPoRhkQ6_I,8000
|
|
21
|
-
qdairlines_helper/po/air_order_page.py,sha256=0Ux0B2hUGKCoFLRpJqGHS2w690o0A2PDnvRf9-UKfJE,930
|
|
22
|
-
qdairlines_helper/po/book_search_page.py,sha256=cHcPhHPPE0xuGfi6fnue-lH33COWU_nyLH2tVUQGI7U,9782
|
|
23
|
-
qdairlines_helper/po/cash_pax_info_page.py,sha256=iJlmIxD8IKnMyTNcylmoz9jV0k4u-B5ALXZEUcRajY4,3401
|
|
24
|
-
qdairlines_helper/po/home_page.py,sha256=KvoOZOYwKjlJjZOT522K_8ic3_2zaYOPWkuklUHVSlM,907
|
|
25
|
-
qdairlines_helper/po/login_page.py,sha256=SdWmkP_47GeosbFRcfvXLeQxxdM7b9UESvmajWq4-nA,2975
|
|
26
|
-
qdairlines_helper/po/nhlms_cash_desk_page.py,sha256=TwqszVbCc4iRSE684uafdlGJB1VC5wS9ZuGWvfeb8Fo,4542
|
|
27
|
-
qdairlines_helper/po/order_verify_page.py,sha256=omgs6CBPTPCGnO8cxRFd2ePFUrJZVD5cD_GBCXsZIes,2917
|
|
28
|
-
qdairlines_helper/po/pay_success_page.py,sha256=XGddo8as8wf1kCCdvemqHIRWOPIPv1JG8iXEmpH4zck,1329
|
|
29
|
-
qdairlines_helper/utils/__init__.py,sha256=sti2S709puM2mQbQisk0KGq_YNjW6T4zz2vyYXonNB0,479
|
|
30
|
-
qdairlines_helper/utils/exception_utils.py,sha256=rXPao6h9sW5uL3lUA36FS3WTlePAm5EZ6MBjWeJJTUs,3934
|
|
31
|
-
qdairlines_helper/utils/po_utils.py,sha256=74ZVyyxPmPk1tcGQoMauXhBE3qsZJej8urni1ch27io,2417
|
|
32
|
-
python_qdairlines_helper-0.1.4.dist-info/METADATA,sha256=SIlFOHl1g6nCQGSubFYF6rnaXyF1fq-zg8KmLY0RMvA,14424
|
|
33
|
-
python_qdairlines_helper-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
-
python_qdairlines_helper-0.1.4.dist-info/top_level.txt,sha256=MRbQkBMdSG4f5mx2RWfu6aXoDSyM-2KwL-YFqNBdbng,18
|
|
35
|
-
python_qdairlines_helper-0.1.4.dist-info/RECORD,,
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
# ---------------------------------------------------------------------------------------------------------
|
|
4
|
-
# ProjectName: python-qdairlines-helper
|
|
5
|
-
# FileName: exception_utils.py
|
|
6
|
-
# Description: 异常工具模块
|
|
7
|
-
# Author: ASUS
|
|
8
|
-
# CreateDate: 2026/01/07
|
|
9
|
-
# Copyright ©2011-2026. Hunan xxxxxxx Company limited. All rights reserved.
|
|
10
|
-
# ---------------------------------------------------------------------------------------------------------
|
|
11
|
-
"""
|
|
12
|
-
from typing import Literal
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class DuplicatePaymentError(Exception):
|
|
16
|
-
def __init__(self, order_no: str):
|
|
17
|
-
self.order_no = order_no
|
|
18
|
-
super().__init__(f"订单<{order_no}>重复支付")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class NotEnoughTicketsError(Exception):
|
|
22
|
-
def __init__(self, flight_no: str, seats_status: int, passengers: int):
|
|
23
|
-
self.flight_no = flight_no
|
|
24
|
-
self.seats_status = seats_status
|
|
25
|
-
self.passengers = passengers
|
|
26
|
-
super().__init__(f"青岛航空官网显示航班<{flight_no}>的余票<{seats_status}>少于乘客人数<{passengers}>")
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ExcessiveProfitdError(Exception):
|
|
30
|
-
def __init__(
|
|
31
|
-
self, flight_no: str, query_price: float, order_price: float, reduction_threshold: float,
|
|
32
|
-
asset: Literal["票面价", "销售价"] = "票面价"
|
|
33
|
-
):
|
|
34
|
-
self.flight_no = flight_no
|
|
35
|
-
self.query_price = query_price
|
|
36
|
-
self.order_price = order_price
|
|
37
|
-
self.reduction_threshold = reduction_threshold
|
|
38
|
-
self.asset = asset
|
|
39
|
-
super().__init__(
|
|
40
|
-
f"航班<{flight_no}>官网价:{query_price} 低于:订单{asset}[{order_price}] - 下降阈值[{reduction_threshold}],收益过高"
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class ExcessiveLossesError(Exception):
|
|
45
|
-
def __init__(
|
|
46
|
-
self, flight_no: str, query_price: float, order_price: float, increase_threshold: float,
|
|
47
|
-
asset: Literal["票面价", "销售价"] = "票面价"
|
|
48
|
-
):
|
|
49
|
-
self.flight_no = flight_no
|
|
50
|
-
self.query_price = query_price
|
|
51
|
-
self.order_price = order_price
|
|
52
|
-
self.increase_threshold = increase_threshold
|
|
53
|
-
self.asset = asset
|
|
54
|
-
super().__init__(
|
|
55
|
-
f"航班<{flight_no}>官网价:{query_price} 高于:订单{asset}[{order_price}] + 上浮阈值[{increase_threshold}],亏损太多"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
class PaymentChannelError(Exception):
|
|
60
|
-
def __init__(self, channel_name: str):
|
|
61
|
-
self.channel_name = channel_name
|
|
62
|
-
super().__init__(f"支付渠道<{channel_name}>暂不支持")
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class PaymentChannelMissError(Exception):
|
|
66
|
-
def __init__(self):
|
|
67
|
-
super().__init__(f"支付渠道参数丢失")
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
class PaymentTypeError(Exception):
|
|
71
|
-
def __init__(self, payment_type: str):
|
|
72
|
-
self.payment_type = payment_type
|
|
73
|
-
super().__init__(f"付款方式<{payment_type}>暂不支持")
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class PassengerTypeError(Exception):
|
|
77
|
-
def __init__(self, passenger_type: str):
|
|
78
|
-
self.passenger_type = passenger_type
|
|
79
|
-
super().__init__(f"乘客类型<{passenger_type}>暂不支持")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
class ProductTypeError(Exception):
|
|
83
|
-
def __init__(self, product_type: str):
|
|
84
|
-
self.product_type = product_type
|
|
85
|
-
super().__init__(f"产品类型<{product_type}>暂不支持")
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class HFPaymentTypeError(Exception):
|
|
89
|
-
def __init__(self, payment_type: str):
|
|
90
|
-
self.payment_type = payment_type
|
|
91
|
-
super().__init__(f"汇付天下的付款方式<{payment_type}>暂不支持")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
class PaymentFailedError(Exception):
|
|
95
|
-
def __init__(self, pre_order_no: str, order_status: str):
|
|
96
|
-
self.pre_order_no = pre_order_no
|
|
97
|
-
self.order_status = order_status
|
|
98
|
-
super().__init__(f"青岛航空官网订单<{pre_order_no}>支付失败,支付结束后的状态<{order_status}>")
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class IPBlockError(Exception):
|
|
102
|
-
|
|
103
|
-
def __init__(self, message: str):
|
|
104
|
-
self.message = message
|
|
105
|
-
super().__init__(message)
|
|
File without changes
|
|
File without changes
|
{python_qdairlines_helper-0.1.4.dist-info → python_qdairlines_helper-0.4.3.dist-info}/top_level.txt
RENAMED
|
File without changes
|