Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

예약대기 기능 추가 #247

Merged
merged 12 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions SRT/srt.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
SRT_TICKETS = f"{SRT_MOBILE}/atc/selectListAtc14016_n.do"
SRT_TICKET_INFO = f"{SRT_MOBILE}/ard/selectListArd02017_n.do?"
SRT_CANCEL = f"{SRT_MOBILE}/ard/selectListArd02045_n.do"
SRT_STANDBY_OPTION = f"{SRT_MOBILE}/ata/selectListAta01135_n.do"

DEFAULT_HEADERS = {
"User-Agent": (
Expand Down Expand Up @@ -347,6 +348,161 @@ def reserve(
# if ticket not found, it's an error
raise SRTError("Ticket not found: check reservation status")

def reserve_standby(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jobId를 제외하면 대부분의 내용이 reserve함수와 동일한 듯하니, 아래와 같이 공통된 부분을 따로 빼고 재사용하면 좋을 것 같습니다. 혹은 reserve 함수 자체에 파라미터로 추가하는 것도 방법이겠으나, 유저가 기대하는 결과가 다르니 지금처럼 두 개의 reserve, reserve_standby 함수를 가지는 것은 좋아보여요.

def reserve():
    self._reserve(reserve_jobid, ...)

def reserve_standby():
    self._reserve(reserve_standby_jobid, ...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 :)
피드백 감사합니다 👍

불필요한 파라미터가 흘러 들어가는게 우려되어 별도로 분리하였으나, 중복되는 코드에 대한 고민이 충분치 않았습니다.

실제 예매 페이지에서도 공통되는 파라미터가 포함된 상태로 요청이 들어가고 있음을 확인했고,
결과적으로는 의미가 크지 않을거라 판단되어, 말씀주신 방향대로 srt.py 파일 내 _reserve 함수로 분리하여, 공통된 코드를 일원화 했습니다.

아래는 웹페이지 예메 시 전송되는 요청별 파라미터입니다. 참고해주세요.

[1101] 개인 예매 요청 시 param

type: 1
jobId: 1101
jrnyTpCd: 11
jrnyTpCd1: 
jrnyTpCd2: 
jrnyCnt: 1
grpDv: 0
rtnDv: 0
stlbTrnClsfCd1: 17
stlbTrnClsfCd2: 05
stndFlg: N
jrnySqno1: 001
jrnySqno2: 
trnGpCd1: 300
trnGpCd2: 
trnGpNm1: 전체
trnGpNm2: 
dptRsStnCd1: 0551
dptRsStnCd2: 
dptRsStnCdNm1: 수서
dptRsStnCdNm2: 
arvRsStnCd1: 0015
arvRsStnCd2: 
arvRsStnCdNm1: 동대구
arvRsStnCdNm2: 
dptDt1: 20230906
dptTm1: 224000
dptDt2: 
dptTm2: 
arvDt1: 
arvTm1: 002500
arvTm2: 
dptDtTmNm1: 2023년 9월 6일(수)
back_dptDt1: 20230906
back_dptTm1: 000000
back_dptDtTmNm1: 2023년 9월 6일(수)
totPrnb: 1
totPrnbNm: 1명
psgGridcnt: 1
psgTpCd1: 1
psgInfoPerPrnb1: 1
psgTpCd2: 
psgInfoPerPrnb2: 0
psgTpCd3: 
psgInfoPerPrnb3: 0
psgTpCd4: 
psgInfoPerPrnb4: 0
psgTpCd5: 
psgInfoPerPrnb5: 0
infantCnt: 0
smkSeatAttCd1: 000
dirSeatAttCd1: 009
locSeatAttCd1: 000
rqSeatAttCd1: 015
etcSeatAttCd1: 000
seatAttNm1: 일반/기본
smkSeatAttCd2: 000
dirSeatAttCd2: 009
locSeatAttCd2: 000
rqSeatAttCd2: 015
etcSeatAttCd2: 000
seatAttNm2: 일반/기본
seatNo1_1: 
seatNo1_2: 
seatNo1_3: 
seatNo1_4: 
seatNo1_5: 
seatNo1_6: 
seatNo1_7: 
seatNo1_8: 
seatNo1_9: 
seatNo2_1: 
seatNo2_2: 
seatNo2_3: 
seatNo2_4: 
seatNo2_5: 
seatNo2_6: 
seatNo2_7: 
seatNo2_8: 
seatNo2_9: 
scarGridcnt1: 
scarGridcnt2: 
trnNo1: 00379
trnNo2: 
runDt1: 20230906
runDt2: 
scarNo1: 
scarNo2: 
psrmClCd1: 1
psrmClCd2: 
seatAttCd: 
dptStnConsOrdr1: 000001
dptStnConsOrdr2: 
arvStnConsOrdr1: 000015
arvStnConsOrdr2: 
dptStnRunOrdr1: 000001
dptStnRunOrdr2: 
arvStnRunOrdr1: 000006
arvStnRunOrdr2: 
choiceSeatCount: 
pnrNo: 
jrnySqno: 
JRNYLIST_KEY: 
arvDt: 
arvRsStnCd: 
arvTm: 
dlayAcptFlg: 
dptDt: 
dptRsStnCd: 
dptTm: 000000
lumpStlTgtNo: 
proyStlTgtFlg: 
stlbTrnClsfCd: 
totSeatNum: 
trnGpCd: 109
trnNo: 
rcvdAmt: 
tmpJobSqno1: 
tmpJobSqno2: 
dcntKndCd: 
seatNo1: 
seatNo2: 
chtnDvCd: 
msgTxt: 
ymsAplFlg: 
reserveType: 11
reserveObjId: https://app.srail.or.kr/ara/selectListAra10007_n.do? (뒤에 query param이 더 붙는데 생략합니다.)
mblPhone: 

[1102] 예약대기 요청 시 param

type: 1
jobId: 1102
jrnyTpCd: 11
jrnyTpCd1: 
jrnyTpCd2: 
jrnyCnt: 1
grpDv: 0
rtnDv: 0
stlbTrnClsfCd1: 17
stlbTrnClsfCd2: 05
stndFlg: N
jrnySqno1: 001
jrnySqno2: 
trnGpCd1: 300
trnGpCd2: 
trnGpNm1: 전체
trnGpNm2: 
dptRsStnCd1: 0551
dptRsStnCd2: 
dptRsStnCdNm1: 수서
dptRsStnCdNm2: 
arvRsStnCd1: 0015
arvRsStnCd2: 
arvRsStnCdNm1: 동대구
arvRsStnCdNm2: 
dptDt1: 20230909
dptTm1: 053000
dptDt2: 
dptTm2: 
arvDt1: 
arvTm1: 071100
arvTm2: 
dptDtTmNm1: 2023년 9월 6일(수)
back_dptDt1: 20230906
back_dptTm1: 000000
back_dptDtTmNm1: 2023년 9월 6일(수)
totPrnb: 1
totPrnbNm: 1명
psgGridcnt: 1
psgTpCd1: 1
psgInfoPerPrnb1: 1
psgTpCd2: 
psgInfoPerPrnb2: 0
psgTpCd3: 
psgInfoPerPrnb3: 0
psgTpCd4: 
psgInfoPerPrnb4: 0
psgTpCd5: 
psgInfoPerPrnb5: 0
infantCnt: 0
smkSeatAttCd1: 000
dirSeatAttCd1: 009
locSeatAttCd1: 000
rqSeatAttCd1: 015
etcSeatAttCd1: 000
seatAttNm1: 일반/기본
smkSeatAttCd2: 000
dirSeatAttCd2: 009
locSeatAttCd2: 000
rqSeatAttCd2: 015
etcSeatAttCd2: 000
seatAttNm2: 일반/기본
seatNo1_1: 
seatNo1_2: 
seatNo1_3: 
seatNo1_4: 
seatNo1_5: 
seatNo1_6: 
seatNo1_7: 
seatNo1_8: 
seatNo1_9: 
seatNo2_1: 
seatNo2_2: 
seatNo2_3: 
seatNo2_4: 
seatNo2_5: 
seatNo2_6: 
seatNo2_7: 
seatNo2_8: 
seatNo2_9: 
scarGridcnt1: 
scarGridcnt2: 
trnNo1: 00301
trnNo2: 
runDt1: 20230909
runDt2: 
scarNo1: 
scarNo2: 
psrmClCd1: 1
psrmClCd2: 
seatAttCd: 
dptStnConsOrdr1: 000001
dptStnConsOrdr2: 
arvStnConsOrdr1: 000015
arvStnConsOrdr2: 
dptStnRunOrdr1: 000001
dptStnRunOrdr2: 
arvStnRunOrdr1: 000005
arvStnRunOrdr2: 
choiceSeatCount: 
pnrNo: 
jrnySqno: 
JRNYLIST_KEY: 
arvDt: 
arvRsStnCd: 
arvTm: 
dlayAcptFlg: 
dptDt: 
dptRsStnCd: 
dptTm: 000000
lumpStlTgtNo: 
proyStlTgtFlg: 
stlbTrnClsfCd: 
totSeatNum: 
trnGpCd: 109
trnNo: 
rcvdAmt: 
tmpJobSqno1: 
tmpJobSqno2: 
dcntKndCd: 
seatNo1: 
seatNo2: 
chtnDvCd: 
msgTxt: 
ymsAplFlg: 
reserveType: 
reserveObjId: https://app.srail.or.kr/ara/selectListAra10007_n.do? (뒤에 query param이 더 붙는데 생략합니다.)
smsSndFlg: 
mblPhone: 010-****-**** (임의 마스킹처리)

self,
train: SRTTrain,
passengers: list[Passenger] | None = None,
special_seat: SeatType = SeatType.GENERAL_FIRST,
mblPhone: str | None = None,
) -> SRTReservation:
"""예약대기 신청 합니다.

>>> trains = srt.search_train("수서", "부산", "210101", "000000")
>>> srt.reserve_standby(trains[0])

Args:
train (:class:`SRTrain`): 예약할 열차
passengers (list[:class:`Passenger`], optional): 예약 인원 (default: 어른 1명)
special_seat (:class:`SeatType`): 일반실/특실 선택 유형 (default: 일반실 우선)
mblPhone (str, optional): 휴대폰 번호

Returns:
:class:`SRTReservation`: 예약 내역
"""
if not self.is_login:
raise SRTNotLoggedInError()

if not isinstance(train, SRTTrain):
raise TypeError('"train" parameter must be a SRTTrain instance')

if train.train_name != "SRT":
raise ValueError(
f'"SRT" expected for a train name, {train.train_name} given'
)

if passengers is None:
passengers = [Adult()]
passengers = Passenger.combine(passengers)

if passengers is not None:
passengersCount = len(passengers)

# 일반식 / 특실 좌석 선택 옵션에 따라 결정.
is_special_seat = None
if special_seat == SeatType.GENERAL_ONLY: # 일반실만
is_special_seat = False
elif special_seat == SeatType.SPECIAL_ONLY: # 특실만
is_special_seat = True
elif special_seat == SeatType.GENERAL_FIRST: # 일반실 우선
if train.general_seat_available():
is_special_seat = False
else:
is_special_seat = True
elif special_seat == SeatType.SPECIAL_FIRST: # 특실 우선
if train.special_seat_available():
is_special_seat = True
else:
is_special_seat = False

url = SRT_RESERVE
data = {
"jobId": "1102", # 예약대기 jobID
"jrnyCnt": "1",
"jrnyTpCd": "11",
"jrnySqno1": "001",
"stndFlg": "N",
"trnGpCd1": "300", # 열차그룹코드 (좌석선택은 SRT만 가능하기때문에 무조건 300을 셋팅한다)"
"trnGpCd": "109", # 열차그룹코드
"grpDv": "0", # 단체 구분 (1: 단체)
"rtnDv": "0", # 편도 구분 (0: 편도, 1: 왕복)
"stlbTrnClsfCd1": train.train_code, # 역무열차종별코드1 (열차 목록 값)
"dptRsStnCd1": train.dep_station_code, # 출발역코드1 (열차 목록 값)
"dptRsStnCdNm1": train.dep_station_name, # 출발역이름1 (열차 목록 값)
"arvRsStnCd1": train.arr_station_code, # 도착역코드1 (열차 목록 값)
"arvRsStnCdNm1": train.arr_station_name, # 도착역이름1 (열차 목록 값)
"dptDt1": train.dep_date, # 출발일자1 (열차 목록 값)
"dptTm1": train.dep_time, # 출발일자1 (열차 목록 값)
"arvTm1": train.arr_time, # 도착일자1 (열차 목록 값)
"totPrnb": passengersCount, # 승차인원
"psgGridcnt": passengersCount, # 승차인원
"psgTpCd1": passengersCount, # 승차인원
"psgInfoPerPrnb1": passengersCount, # 승차인원
"trnNo1": "%05d" % int(train.train_number), # 열차번호1 (열차 목록 값)
"runDt1": train.dep_date, # 운행일자1 (열차 목록 값)
"psrmClCd1": "2" if is_special_seat is True else "1",
"dptStnConsOrdr1": train.dep_station_constitution_order, # 출발역구성순서1 (열차 목록 값)
"arvStnConsOrdr1": train.arr_station_constitution_order, # 도착역구성순서1 (열차 목록 값)
"dptStnRunOrdr1": train.dep_station_run_order, # 출발역운행순서1 (열차 목록 값)
"arvStnRunOrdr1": train.arr_station_run_order, # 도착역운행순서1 (열차 목록 값)
"smkSeatAttCd1": "000", # 흡연좌석속성코드1
"dirSeatAttCd1": "009", # 방향좌석속성코드
"locSeatAttCd1": "000", # 위치좌석속성코드1
"rqSeatAttCd1": "015", # 요구좌석속성코드1
"etcSeatAttCd1": "000", # 기타좌석속성코드1
"smkSeatAttCd2": "000", # 흡연좌석속성코드2
"dirSeatAttCd2": "009", # 방향좌석속성코드2
"rqSeatAttCd2": "015", # 요구좌석속성코드2
"mblPhone": mblPhone,
}

r = self._session.post(url=url, data=data)
parser = SRTResponseData(r.text)

if not parser.success():
raise SRTResponseError(parser.message())

self._log(parser.message())
reservation_result = parser.get_all()["reservListMap"][0]

# find corresponding ticket and return it
tickets = self.get_reservations()
for ticket in tickets:
if ticket.reservation_number == reservation_result["pnrNo"]:
return ticket

# if ticket not found, it's an error
raise SRTError("Ticket not found: check reservation status")

def reserve_standby_option_settings(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예약대기를 하면 기본 알림 설정이 어떻게 되나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아시다시피 해당 내용을 정확하게 파악하기 어려움이 있습니다 ㅠ

웹페이지 예약대기 프로세스 상 기준으로 작업했고, 아래와 같습니다.

예약대기 신청 -> 예약대기에 대한 설정(1: 일반실에 좌석이 없는 경우, 특실로 예약하기 | 2: 연락받을 전화번호)

기본적으로 실제 프로세스에서 예약대기 신청 이후, 진행하지 않은 경우에 따른 작동과 동일합니다.

이해를 돕기위해 간략히 프로세스에 대해 기록합니다.

1. 아래 이미지에서 예약하기 버튼을 클릭하면, 예약대기 신청(reserve_standby) 요청이 전송됩니다.

app srail or kr_ara_selectListAra10007_n do_jobId=1101 jrnyTpCd=11 jrnyCnt=1 grpDv=0 rtnDv=0 stlbTrnClsfCd1=05 stndFlg=N jrnySqno1=001 jrnySqno2= trnGpCd1=109 trnGpCd2= trnGpNm1=%EC%A0%84%EC%B2%B4 trnGpNm2= dptRsStnCd1=0551 dptRsStnCd2= (1)

2. 아래 이미지에서 동의버튼을 누르면, 네트워크 요청없이, 단순히 다음화면으로 진입합니다.

app srail or kr_ara_selectListAra10007_n do_jobId=1101 jrnyTpCd=11 jrnyCnt=1 grpDv=0 rtnDv=0 stlbTrnClsfCd1=05 stndFlg=N jrnySqno1=001 jrnySqno2= trnGpCd1=109 trnGpCd2= trnGpNm1=%EC%A0%84%EC%B2%B4 trnGpNm2= dptRsStnCd1=0551 dptRsStnCd2= (2)

3. 이전 화면에서 동의를 누른 경우, 전화번호가 자동으로 입력되며, 아니오를 누른 경우 전화번호는 비어있으며, textfield에 수동으로 입력이 불가합니다. | 신청하기 버튼을 누르면 예약대기 옵션(reserve_standby_option_settings)이 저장됩니다

app srail or kr_ard_selectListArd02017_n do_type=1 jobId=1102 jrnyTpCd=11 jrnyTpCd1= jrnyTpCd2= jrnyCnt=1 grpDv=0 rtnDv=0 stlbTrnClsfCd1=17 stlbTrnClsfCd2=05 stndFlg=N jrnySqno1=001 jrnySqno2= trnGpCd1=300 trnGpCd2= trnGpNm1=%EC%A0%84%EC%B2

self,
reservation: SRTReservation | int,
isAgreeSMS: bool,
isAgreeClassChange: bool,
telNo: str | None = None,
) -> bool:
"""예약대기 옵션을 적용 합니다.

>>> trains = srt.search_train("수서", "부산", "210101", "000000")
>>> srt.reserve_standby(trains[0])
>>> srt.reserve_standby_option_settings("1234567890", True, True, "010-1234-xxxx")

Args:
reservation (:class:`SRTReservation` or int): 예약 번호
isAgreeSMS (bool): SMS 수신 동의 여부
isAgreeClassChange (bool): 좌석등급 변경 동의 여부
telNo (str, optional): 휴대폰 번호
Returns:
bool: 예약대기 옵션 적용 성공 여부
"""
if not self.is_login:
raise SRTNotLoggedInError()

if isinstance(reservation, SRTReservation):
reservation = reservation.reservation_number

url = SRT_STANDBY_OPTION

data = {
"pnrNo": reservation,
"psrmClChgFlg": "Y" if isAgreeClassChange else "N",
"smsSndFlg": "Y" if isAgreeSMS else "N",
"telNo": telNo if isAgreeSMS else "",
}

r = self._session.post(url=url, data=data)

return r.status_code == 200

def get_reservations(self, paid_only: bool = False) -> list[SRTReservation]:
"""전체 예약 정보를 얻습니다.

Expand Down
12 changes: 11 additions & 1 deletion SRT/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ def __init__(self, data):
self.arr_station_name = STATION_NAME[self.arr_station_code]
self.general_seat_state = data["gnrmRsvPsbStr"]
self.special_seat_state = data["sprmRsvPsbStr"]
self.reserve_wait_possible_code = data["rsvWaitPsbCd"]
self.arr_station_run_order = data["arvStnRunOrdr"]
self.arr_station_constitution_order = data["arvStnConsOrdr"]
self.arr_station_constitution_order = data["arvStnConsOrdr"]
self.dep_station_run_order = data["dptStnRunOrdr"]
self.dep_station_constitution_order = data["dptStnConsOrdr"]

def __str__(self):
return self.dump()
Expand All @@ -33,7 +39,7 @@ def dump(self):
"{month}월 {day}일, "
"{dep}~{arr}"
"({dep_hour}:{dep_min}~{arr_hour}:{arr_min}) "
"특실 {special_state}, 일반실 {general_state}"
"특실 {special_state}, 일반실 {general_state}, 예약대기 {reserve_standby_state}"
).format(
name=self.train_name,
number=self.train_number,
Expand All @@ -47,6 +53,7 @@ def dump(self):
arr_min=self.arr_time[2:4],
special_state=self.special_seat_state,
general_state=self.general_seat_state,
reserve_standby_state="가능" if self.reserve_standby_available() else "불가능",
)

return d
Expand All @@ -57,5 +64,8 @@ def general_seat_available(self):
def special_seat_available(self):
return "예약가능" in self.special_seat_state

def reserve_standby_available(self):
return "9" in self.reserve_wait_possible_code # 9인 경우, 예약대기 가능한 상태임

def seat_available(self):
return self.general_seat_available() or self.special_seat_available()