@nomadamas/k-skill 0.2.5 → 0.4.0
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.
- package/package.json +1 -1
- package/skills/bok-ecos-stats/instruction.md +1 -1
- package/skills/bunjang-search/references/DISCLAIMER.md +23 -0
- package/skills/coupang-product-search/references/DISCLAIMER.md +23 -0
- package/skills/daangn-cars-search/references/DISCLAIMER.md +23 -0
- package/skills/daangn-jobs-search/references/DISCLAIMER.md +23 -0
- package/skills/daangn-realty-search/references/DISCLAIMER.md +23 -0
- package/skills/daangn-used-goods-search/references/DISCLAIMER.md +23 -0
- package/skills/daishin-report-search/references/DISCLAIMER.md +23 -0
- package/skills/daiso-product-search/references/DISCLAIMER.md +23 -0
- package/skills/danawa-price-search/references/DISCLAIMER.md +23 -0
- package/skills/delivery-tracking/references/DISCLAIMER.md +23 -0
- package/skills/express-bus-booking/references/DISCLAIMER.md +23 -0
- package/skills/fine-dust-location/skill.json +1 -7
- package/skills/flight-ticket-search/references/DISCLAIMER.md +23 -0
- package/skills/foresttrip-vacancy/references/DISCLAIMER.md +23 -0
- package/skills/intercity-bus-booking/references/DISCLAIMER.md +23 -0
- package/skills/job-posting-match/references/DISCLAIMER.md +23 -0
- package/skills/jobkorea-talent-search/references/DISCLAIMER.md +23 -0
- package/skills/k-skill-setup/skill.json +1 -7
- package/skills/kakao-bar-nearby/references/DISCLAIMER.md +15 -0
- package/skills/kakao-map/references/DISCLAIMER.md +15 -0
- package/skills/kakaotalk-mac/references/DISCLAIMER.md +15 -0
- package/skills/kbl-results/references/DISCLAIMER.md +13 -0
- package/skills/kbo-results/references/DISCLAIMER.md +13 -0
- package/skills/keris-academic-search/references/DISCLAIMER.md +13 -0
- package/skills/keris-academic-search/references/TRADEMARK-LEGAL-STATEMENT.md +7 -0
- package/skills/kleague-results/references/DISCLAIMER.md +13 -0
- package/skills/kopis-performance-search/references/DISCLAIMER.md +13 -0
- package/skills/kopis-performance-search/references/TRADEMARK-LEGAL-STATEMENT.md +7 -0
- package/skills/kopis-performance-search/skill.json +3 -4
- package/skills/korean-cinema-search/references/DISCLAIMER.md +13 -0
- package/skills/korean-stock-search/references/DISCLAIMER.md +14 -0
- package/skills/korean-stock-search/references/TRADEMARK-LEGAL-STATEMENT.md +7 -0
- package/skills/korean-transit-route/references/DISCLAIMER.md +13 -0
- package/skills/ktx-booking/instruction.md +38 -221
- package/skills/ktx-booking/references/AUTOMATION-LEGAL-STATEMENT.md +10 -0
- package/skills/ktx-booking/references/DISCLAIMER.md +14 -0
- package/skills/ktx-booking/scripts/ktx_booking.py +176 -1274
- package/skills/ktx-booking/scripts/ktx_booking.py.lock +27 -0
- package/skills/ktx-booking/scripts/ktx_timetable.py +141 -0
- package/skills/ktx-booking/skill.json +3 -11
- package/skills/lck-analytics/references/DISCLAIMER.md +13 -0
- package/skills/lotto-results/references/DISCLAIMER.md +13 -0
- package/skills/market-kurly-search/references/DISCLAIMER.md +13 -0
- package/skills/myrealtrip-search/instruction.md +3 -1
- package/skills/myrealtrip-search/references/DISCLAIMER.md +13 -0
- package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +30 -8
- package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +62 -0
- package/skills/naver-ad-performance/references/DISCLAIMER.md +13 -0
- package/skills/naver-blog-research/references/DISCLAIMER.md +13 -0
- package/skills/naver-news-search/references/DISCLAIMER.md +13 -0
- package/skills/naver-shopping-search/references/DISCLAIMER.md +13 -0
- package/skills/ohou-today-deal/references/DISCLAIMER.md +13 -0
- package/skills/olive-young-search/references/DISCLAIMER.md +13 -0
- package/skills/popbill/references/DISCLAIMER.md +13 -0
- package/skills/saramin-talent-search/references/DISCLAIMER.md +13 -0
- package/skills/seoul-weather-risk/instruction.md +28 -35
- package/skills/seoul-weather-risk/scripts/seoul_weather_risk.py +15 -68
- package/skills/seoul-weather-risk/skill.json +2 -2
- package/skills/srt-booking/instruction.md +48 -154
- package/skills/srt-booking/references/AUTOMATION-LEGAL-STATEMENT.md +25 -0
- package/skills/srt-booking/references/DISCLAIMER.md +13 -0
- package/skills/srt-booking/scripts/srt_booking.py +134 -235
- package/skills/srt-booking/scripts/srt_booking.py.lock +207 -0
- package/skills/srt-booking/skill.json +3 -15
- package/skills/store-longevity-radar/scripts/__pycache__/store_longevity_download.cpython-312.pyc +0 -0
- package/skills/ticket-availability/references/DISCLAIMER.md +13 -0
- package/skills/{toss-securities → toss-investment}/instruction.md +1 -1
- package/skills/toss-investment/references/DISCLAIMER.md +14 -0
- package/skills/{toss-securities → toss-investment}/references/TRADEMARK-LEGAL-STATEMENT.md +1 -1
- package/skills/{toss-securities → toss-investment}/skill.json +2 -2
- package/src/assemble.js +1 -1
- package/src/execute.js +13 -3
- package/skills/lovebug-report/instruction.md +0 -185
- package/skills/lovebug-report/skill.json +0 -9
- package/skills/srt-booking/scripts/srt_seats.py +0 -156
|
@@ -1,272 +1,171 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env -S uv run --locked --script
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = ["SRTrain==2.6.7"]
|
|
5
|
+
# ///
|
|
6
|
+
"""Live, anonymous, read-only SRT timetable lookup through SRTrain."""
|
|
7
|
+
|
|
2
8
|
from __future__ import annotations
|
|
3
9
|
|
|
4
10
|
import argparse
|
|
5
|
-
import base64
|
|
6
|
-
import contextlib
|
|
7
|
-
import io
|
|
8
|
-
import importlib
|
|
9
11
|
import json
|
|
10
|
-
import
|
|
12
|
+
import re
|
|
11
13
|
import sys
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
|
|
15
|
-
from
|
|
16
|
-
|
|
17
|
-
SRT_SEAT_ENDPOINT = "https://etk.srail.kr/hpg/hra/01/selectPassengerResearchList.do"
|
|
18
|
-
TRAIN_ID_PREFIX = "srt:v1:"
|
|
19
|
-
TRAIN_ID_FIELDS = (
|
|
20
|
-
"train_number",
|
|
21
|
-
"dep_date",
|
|
22
|
-
"dep_time",
|
|
23
|
-
"arr_date",
|
|
24
|
-
"arr_time",
|
|
25
|
-
"train_code",
|
|
26
|
-
"dep_station_code",
|
|
27
|
-
"arr_station_code",
|
|
28
|
-
"dep_station_run_order",
|
|
29
|
-
"arr_station_run_order",
|
|
30
|
-
)
|
|
31
|
-
ROOM_CODE = {"general": "1", "special": "2"}
|
|
32
|
-
ROOM_NAME = {"general": "일반실", "special": "특실"}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class SrtTrainLike(Protocol):
|
|
36
|
-
train_number: str
|
|
37
|
-
dep_date: str
|
|
38
|
-
dep_time: str
|
|
39
|
-
arr_date: str
|
|
40
|
-
arr_time: str
|
|
41
|
-
train_code: str
|
|
42
|
-
train_name: str
|
|
43
|
-
dep_station_code: str
|
|
44
|
-
dep_station_name: str
|
|
45
|
-
arr_station_code: str
|
|
46
|
-
arr_station_name: str
|
|
47
|
-
dep_station_run_order: str
|
|
48
|
-
arr_station_run_order: str
|
|
49
|
-
general_seat_state: str
|
|
50
|
-
special_seat_state: str
|
|
51
|
-
reserve_wait_possible_code: str
|
|
52
|
-
|
|
53
|
-
def general_seat_available(self) -> bool: ...
|
|
54
|
-
|
|
55
|
-
def special_seat_available(self) -> bool: ...
|
|
56
|
-
|
|
57
|
-
def reserve_standby_available(self) -> bool: ...
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class ResponseLike(Protocol):
|
|
61
|
-
text: str
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class SessionLike(Protocol):
|
|
65
|
-
def get(self, url: str, params: dict[str, str]) -> ResponseLike: ...
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class SrtClientLike(Protocol):
|
|
69
|
-
_session: SessionLike
|
|
70
|
-
|
|
71
|
-
def search_train(
|
|
72
|
-
self,
|
|
73
|
-
dep: str,
|
|
74
|
-
arr: str,
|
|
75
|
-
date: str,
|
|
76
|
-
time: str,
|
|
77
|
-
time_limit: str | None = None,
|
|
78
|
-
available_only: bool = True,
|
|
79
|
-
) -> list[SrtTrainLike]: ...
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def load_srt_module() -> ModuleType:
|
|
83
|
-
try:
|
|
84
|
-
return importlib.import_module("SRT")
|
|
85
|
-
except ModuleNotFoundError as exc:
|
|
86
|
-
raise SystemExit("scripts/srt_booking.py requires SRTrain: python3 -m pip install SRTrain")
|
|
14
|
+
from contextlib import redirect_stdout
|
|
15
|
+
from datetime import date as calendar_date
|
|
16
|
+
from datetime import time
|
|
17
|
+
from typing import Any
|
|
87
18
|
|
|
19
|
+
from requests import RequestException
|
|
20
|
+
from SRT import SRT
|
|
21
|
+
from SRT.constants import API_ENDPOINTS, STATION_CODE
|
|
22
|
+
from SRT.errors import SRTError, SRTNetFunnelError
|
|
23
|
+
from SRT.netfunnel import NetFunnelHelper
|
|
88
24
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
srt_id = os.environ.get("KSKILL_SRT_ID", "")
|
|
92
|
-
srt_pw = os.environ.get("KSKILL_SRT_PASSWORD", "")
|
|
93
|
-
return srt_module.SRT(srt_id, srt_pw, auto_login=auto_login)
|
|
25
|
+
BOOKING_URL = "https://etk.srail.kr/hpg/hra/01/selectScheduleList.do?pageId=TK0101010000"
|
|
26
|
+
NETFUNNEL_URL = "https://nf.letskorail.com/ts.wseq"
|
|
94
27
|
|
|
95
28
|
|
|
96
|
-
def
|
|
97
|
-
|
|
29
|
+
def build_client() -> SRT:
|
|
30
|
+
NetFunnelHelper.NETFUNNEL_URL = NETFUNNEL_URL
|
|
31
|
+
return SRT("", "", auto_login=False)
|
|
98
32
|
|
|
99
33
|
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
34
|
+
def source_info() -> dict[str, str]:
|
|
35
|
+
return {
|
|
36
|
+
"mode": "live",
|
|
37
|
+
"transport": "SRTrain",
|
|
38
|
+
"operator": "주식회사 에스알",
|
|
39
|
+
"endpoint": API_ENDPOINTS["search_schedule"],
|
|
40
|
+
"queue_endpoint": NETFUNNEL_URL,
|
|
41
|
+
"authentication": "anonymous",
|
|
42
|
+
"mutation": "none; timetable search only",
|
|
43
|
+
"booking_url": BOOKING_URL,
|
|
44
|
+
}
|
|
104
45
|
|
|
105
46
|
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
raise SystemExit("train_id must start with srt:v1:")
|
|
109
|
-
encoded = train_id.removeprefix(TRAIN_ID_PREFIX)
|
|
110
|
-
padded = encoded + ("=" * ((4 - len(encoded) % 4) % 4))
|
|
111
|
-
try:
|
|
112
|
-
payload = json.loads(base64.urlsafe_b64decode(padded.encode()).decode())
|
|
113
|
-
except (ValueError, json.JSONDecodeError, UnicodeDecodeError) as exc:
|
|
114
|
-
raise SystemExit("train_id is invalid; rerun search and copy a fresh train_id") from exc
|
|
115
|
-
if not isinstance(payload, dict):
|
|
116
|
-
raise SystemExit("train_id is invalid; rerun search and copy a fresh train_id")
|
|
117
|
-
if any(not isinstance(payload.get(field), str) or not payload[field] for field in TRAIN_ID_FIELDS):
|
|
118
|
-
raise SystemExit("train_id is invalid; rerun search and copy a fresh train_id")
|
|
119
|
-
return {field: payload[field] for field in TRAIN_ID_FIELDS}
|
|
47
|
+
def format_time(value: str) -> str:
|
|
48
|
+
return f"{value[:2]}:{value[2:4]}"
|
|
120
49
|
|
|
121
50
|
|
|
122
|
-
def
|
|
123
|
-
|
|
124
|
-
|
|
51
|
+
def normalize_station(value: str) -> str:
|
|
52
|
+
"""Accept common "...역" input for a canonical SRT station name."""
|
|
53
|
+
name = value.strip()
|
|
54
|
+
if name in STATION_CODE:
|
|
55
|
+
return name
|
|
56
|
+
if name.endswith("역") and name[:-1] in STATION_CODE:
|
|
57
|
+
return name[:-1]
|
|
58
|
+
return name
|
|
125
59
|
|
|
126
60
|
|
|
127
|
-
def normalize_train(train:
|
|
61
|
+
def normalize_train(train: Any) -> dict[str, Any]:
|
|
128
62
|
return {
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
"
|
|
135
|
-
"
|
|
136
|
-
"
|
|
137
|
-
"
|
|
138
|
-
"arr_time": train.arr_time,
|
|
139
|
-
"has_general_seat": train.general_seat_available(),
|
|
140
|
-
"has_special_seat": train.special_seat_available(),
|
|
141
|
-
"has_waiting_list": train.reserve_standby_available(),
|
|
63
|
+
"train_no": str(train.train_number),
|
|
64
|
+
"train_type": str(train.train_name),
|
|
65
|
+
"dep": str(train.dep_station_name),
|
|
66
|
+
"arr": str(train.arr_station_name),
|
|
67
|
+
"dep_date": str(train.dep_date),
|
|
68
|
+
"dep_time": format_time(str(train.dep_time)),
|
|
69
|
+
"arr_time": format_time(str(train.arr_time)),
|
|
70
|
+
"general_seat_available": bool(train.general_seat_available()),
|
|
71
|
+
"special_seat_available": bool(train.special_seat_available()),
|
|
142
72
|
}
|
|
143
73
|
|
|
144
74
|
|
|
145
|
-
def
|
|
75
|
+
def search_live_timetable(
|
|
76
|
+
*,
|
|
77
|
+
dep: str,
|
|
78
|
+
arr: str,
|
|
79
|
+
date: str,
|
|
80
|
+
earliest: str,
|
|
81
|
+
latest: str,
|
|
82
|
+
limit: int,
|
|
83
|
+
) -> dict[str, Any]:
|
|
84
|
+
validate_date(date)
|
|
85
|
+
start = validate_time(earliest)
|
|
86
|
+
end = validate_time(latest)
|
|
87
|
+
if start > end:
|
|
88
|
+
raise ValueError("--time must not be later than --time-limit")
|
|
89
|
+
client = build_client()
|
|
90
|
+
with redirect_stdout(sys.stderr):
|
|
91
|
+
trains = client.search_train(
|
|
92
|
+
dep=normalize_station(dep),
|
|
93
|
+
arr=normalize_station(arr),
|
|
94
|
+
date=date,
|
|
95
|
+
time=start,
|
|
96
|
+
time_limit=end,
|
|
97
|
+
available_only=False,
|
|
98
|
+
)
|
|
146
99
|
return {
|
|
147
|
-
"
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"arvRsStnCd1": train.arr_station_code,
|
|
154
|
-
"dptStnRunOrdr1": train.dep_station_run_order,
|
|
155
|
-
"arvStnRunOrdr1": train.arr_station_run_order,
|
|
156
|
-
"seatAttCd1": "015",
|
|
157
|
-
"psrmClCd1": ROOM_CODE[room],
|
|
158
|
-
"index1": "0",
|
|
159
|
-
"scarNo1": "" if car_no is None else f"{car_no:04d}",
|
|
160
|
-
"chtnDvCd": "1",
|
|
161
|
-
"jrnySqno": "001",
|
|
162
|
-
"mode": "1",
|
|
163
|
-
"psgNum": "1",
|
|
164
|
-
"pageId": "",
|
|
100
|
+
"count": min(len(trains), limit),
|
|
101
|
+
"trains": [normalize_train(train) for train in trains[:limit]],
|
|
102
|
+
"date": date,
|
|
103
|
+
"source": source_info(),
|
|
104
|
+
"schedule_note": "실시간 시간표·좌석 가능 여부 조회이며 예약·좌석 선점은 실행하지 않습니다.",
|
|
105
|
+
"booking_url": BOOKING_URL,
|
|
165
106
|
}
|
|
166
107
|
|
|
167
108
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
with contextlib.redirect_stdout(io.StringIO()):
|
|
177
|
-
trains = client.search_train(args.dep, args.arr, args.date, args.time, args.time_limit, args.available_only)
|
|
178
|
-
print_json({"count": len(trains[: args.limit]), "trains": [normalize_train(train, index) for index, train in enumerate(trains[: args.limit], 1)]})
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def command_seats(args: argparse.Namespace) -> None:
|
|
182
|
-
client = build_client(auto_login=False)
|
|
183
|
-
with contextlib.redirect_stdout(io.StringIO()):
|
|
184
|
-
trains = client.search_train(args.dep, args.arr, args.date, args.time, args.time_limit, available_only=False)
|
|
185
|
-
train = find_train_by_id(trains, args.train_id)
|
|
186
|
-
if train is None:
|
|
187
|
-
raise SystemExit("train_id no longer matches any current search result; rerun search and choose a fresh train_id")
|
|
188
|
-
|
|
189
|
-
initial_html = fetch_seat_page(client, train, args.room, args.car_no)
|
|
190
|
-
cars = [car for car in parse_cars(initial_html) if car["room_class"] == ROOM_NAME[args.room]]
|
|
191
|
-
if args.car_no is not None:
|
|
192
|
-
cars = [car for car in cars if car["car_no"] == args.car_no]
|
|
193
|
-
else:
|
|
194
|
-
cars = [car for car in cars if car["available"]]
|
|
195
|
-
if not cars:
|
|
196
|
-
raise SystemExit(f"seat car data is unavailable for {args.room}; retry search or choose another train")
|
|
197
|
-
|
|
198
|
-
car_payloads: list[dict[str, object]] = []
|
|
199
|
-
for car in sort_cars_for_booking(cars, args.car_priority):
|
|
200
|
-
html = initial_html if args.car_no == car["car_no"] else fetch_seat_page(client, train, args.room, car["car_no"])
|
|
201
|
-
seats = parse_seats(html)
|
|
202
|
-
if args.seat:
|
|
203
|
-
seats = [seat for seat in seats if seat["seat"] == args.seat]
|
|
204
|
-
seats = sort_seats_for_booking(seats, args.seat_priority)
|
|
205
|
-
if args.available_only:
|
|
206
|
-
seats = [seat for seat in seats if seat["available"]]
|
|
207
|
-
available_seats = [seat for seat in seats if seat["available"]]
|
|
208
|
-
limited = seats[: args.limit]
|
|
209
|
-
payload = dict(car)
|
|
210
|
-
payload["available_seat_count"] = len(available_seats)
|
|
211
|
-
payload["available_seats"] = [seat["seat"] for seat in available_seats]
|
|
212
|
-
payload["shown_seat_count"] = len(limited)
|
|
213
|
-
payload["seats"] = limited
|
|
214
|
-
if args.seat:
|
|
215
|
-
payload["requested_seat"] = args.seat
|
|
216
|
-
payload["requested_seat_available"] = any(seat["available"] for seat in seats)
|
|
217
|
-
car_payloads.append(payload)
|
|
218
|
-
|
|
219
|
-
print_json({
|
|
220
|
-
"train": normalize_train(train, 1),
|
|
221
|
-
"room": args.room,
|
|
222
|
-
"available_only": args.available_only,
|
|
223
|
-
"car_priority": args.car_priority,
|
|
224
|
-
"seat_priority": args.seat_priority,
|
|
225
|
-
"cars": car_payloads,
|
|
226
|
-
})
|
|
109
|
+
def validate_date(value: str) -> str:
|
|
110
|
+
if not re.fullmatch(r"\d{8}", value):
|
|
111
|
+
raise ValueError("date must use YYYYMMDD")
|
|
112
|
+
try:
|
|
113
|
+
calendar_date(int(value[:4]), int(value[4:6]), int(value[6:]))
|
|
114
|
+
except ValueError as exc:
|
|
115
|
+
raise ValueError("date must use a valid YYYYMMDD value") from exc
|
|
116
|
+
return value
|
|
227
117
|
|
|
228
118
|
|
|
229
|
-
def
|
|
230
|
-
|
|
119
|
+
def validate_time(value: str) -> str:
|
|
120
|
+
if not re.fullmatch(r"\d{4}", value):
|
|
121
|
+
raise ValueError("time must use HHMM")
|
|
122
|
+
try:
|
|
123
|
+
time.fromisoformat(f"{value[:2]}:{value[2:]}")
|
|
124
|
+
except ValueError as exc:
|
|
125
|
+
raise ValueError("time must use a valid HHMM value") from exc
|
|
126
|
+
return value + "00"
|
|
231
127
|
|
|
232
128
|
|
|
233
129
|
def build_parser() -> argparse.ArgumentParser:
|
|
234
|
-
parser = argparse.ArgumentParser(description="SRT
|
|
235
|
-
|
|
236
|
-
search =
|
|
237
|
-
|
|
238
|
-
search.add_argument("--
|
|
239
|
-
search.add_argument("--
|
|
240
|
-
search.add_argument("--
|
|
241
|
-
search.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
add_trip_args(seats)
|
|
245
|
-
seats.add_argument("--train-id", required=True, help="search 결과에서 복사한 stable train_id")
|
|
246
|
-
seats.add_argument("--time-limit", default=None, help="조회 종료 시각 HHMMSS")
|
|
247
|
-
seats.add_argument("--room", choices=sorted(ROOM_CODE), default="general")
|
|
248
|
-
seats.add_argument("--car-no", type=int, default=None, help="특정 호차만 조회")
|
|
249
|
-
seats.add_argument("--seat", default=None, help="특정 좌석번호만 조회, 예: 6C")
|
|
250
|
-
seats.add_argument("--available-only", action="store_true", help="빈 좌석만 출력")
|
|
251
|
-
seats.add_argument("--car-priority", choices=("center", "low", "high"), default="center")
|
|
252
|
-
seats.add_argument("--seat-priority", choices=("forward-window", "window-forward", "row-low"), default="forward-window")
|
|
253
|
-
seats.add_argument("--limit", type=int, default=100, help="호차별 출력할 최대 좌석 수")
|
|
254
|
-
seats.set_defaults(func=command_seats)
|
|
130
|
+
parser = argparse.ArgumentParser(description="SRT live timetable lookup through SRTrain (read-only)")
|
|
131
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
132
|
+
search = commands.add_parser("search", help="query the current SRT timetable")
|
|
133
|
+
search.add_argument("--dep", required=True)
|
|
134
|
+
search.add_argument("--arr", required=True)
|
|
135
|
+
search.add_argument("--date", required=True, help="YYYYMMDD")
|
|
136
|
+
search.add_argument("--time", default="0000", help="earliest departure, HHMM")
|
|
137
|
+
search.add_argument("--time-limit", default="2359", help="latest departure, HHMM")
|
|
138
|
+
search.add_argument("--limit", type=int, default=10)
|
|
139
|
+
commands.add_parser("source", help="show the read-only live query endpoint")
|
|
255
140
|
return parser
|
|
256
141
|
|
|
257
142
|
|
|
258
|
-
def add_trip_args(parser: argparse.ArgumentParser) -> None:
|
|
259
|
-
parser.add_argument("dep", help="출발역")
|
|
260
|
-
parser.add_argument("arr", help="도착역")
|
|
261
|
-
parser.add_argument("date", help="출발일 YYYYMMDD")
|
|
262
|
-
parser.add_argument("time", help="희망 시작 시각 HHMMSS")
|
|
263
|
-
|
|
264
|
-
|
|
265
143
|
def main(argv: list[str] | None = None) -> int:
|
|
266
|
-
|
|
267
|
-
args.
|
|
268
|
-
|
|
144
|
+
parser = build_parser()
|
|
145
|
+
args = parser.parse_args(argv)
|
|
146
|
+
try:
|
|
147
|
+
if args.command == "source":
|
|
148
|
+
print(json.dumps(source_info(), ensure_ascii=False, indent=2))
|
|
149
|
+
return 0
|
|
150
|
+
if args.limit < 1 or args.limit > 50:
|
|
151
|
+
raise ValueError("--limit must be between 1 and 50")
|
|
152
|
+
result = search_live_timetable(
|
|
153
|
+
dep=args.dep,
|
|
154
|
+
arr=args.arr,
|
|
155
|
+
date=args.date,
|
|
156
|
+
earliest=args.time,
|
|
157
|
+
latest=args.time_limit,
|
|
158
|
+
limit=args.limit,
|
|
159
|
+
)
|
|
160
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
161
|
+
return 0
|
|
162
|
+
except (SRTNetFunnelError, RequestException):
|
|
163
|
+
parser.error(f"SRT timetable lookup unavailable; use the official page: {BOOKING_URL}")
|
|
164
|
+
return 2
|
|
165
|
+
except (SRTError, ValueError) as exc:
|
|
166
|
+
parser.error(str(exc))
|
|
167
|
+
return 2
|
|
269
168
|
|
|
270
169
|
|
|
271
170
|
if __name__ == "__main__":
|
|
272
|
-
|
|
171
|
+
sys.exit(main())
|