Skip to content
devgyurakPublic

About

πŸš„ KTX ν‘œ, 파이썬이 λŠμŠ΅λ‹ˆλ‹€ β€” μ—΄μ°¨ 쑰회 Β· 예맀 Β· 결제 Β· ν™˜λΆˆ Β· μ˜ˆμ•½λŒ€κΈ° Β· μ·¨μ†Œν‘œ κ°μ‹œλ₯Ό μ½”λ“œ λͺ‡ μ€„λ‘œ. μ½”λ ˆμΌν†‘ API 비곡식 Python SDK (Unofficial Korail / KTX API client)

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

46 stars

Watchers

2 watching

Forks

Repository files navigation

pykorail β€” 파이썬으둜 KTX ν‘œλ₯Ό μ‘°νšŒν•˜κ³  μ˜ˆλ§€ν•©λ‹ˆλ‹€

μ½”λ ˆμΌ 슀마트 예맀(μ½”λ ˆμΌν†‘) API λ₯Ό 감싼 비곡식 ν΄λΌμ΄μ–ΈνŠΈ

CI Coverage PyPI Python License

λ¬Έμ„œ μ‚¬μ΄νŠΈ Β· λΉ λ₯Έ μ‹œμž‘ Β· ν•  수 μžˆλŠ” 것 Β· API 레퍼런슀 Β· κΈ°μ—¬ν•˜κΈ°


λΉ λ₯Έ μ‹œμž‘

pip install pykorail
from datetime import datetime, timedelta, timezone
from pykorail import Korail

kst = timezone(timedelta(hours=9))
# 내일 μ˜€μ „ 9μ‹œ(KST) μΆœλ°œμ„ μ°ΎμŠ΅λ‹ˆλ‹€.
departure = (datetime.now(kst) + timedelta(days=1)).replace(hour=9, minute=0, second=0, microsecond=0)

with Korail.logged_in("me@example.com", "password") as korail:
    trains = korail.trains.search("μ„œμšΈ", "λΆ€μ‚°", depart_after=departure)
    for train in trains:
        print(train)
[KTX 101]  04/01 09:00~12:30  μ„œμšΈ~λΆ€μ‚°  νŠΉμ‹€ κ°€λŠ₯, μΌλ°˜μ‹€ κ°€λŠ₯ (3μ‹œκ°„ 30λΆ„)
[KTX 103]  04/01 10:00~13:30  μ„œμšΈ~λΆ€μ‚°  νŠΉμ‹€ λ§€μ§„, μΌλ°˜μ‹€ κ°€λŠ₯ (3μ‹œκ°„ 30λΆ„)
[KTX 105]  04/01 11:00~14:35  μ„œμšΈ~λΆ€μ‚°  νŠΉμ‹€ κ°€λŠ₯, μΌλ°˜μ‹€ λ§€μ§„ (3μ‹œκ°„ 35λΆ„)

λΉ„λ™κΈ°λ‘œλ„ μ“Έ 수 μžˆμŠ΅λ‹ˆλ‹€. λ¦¬μ†ŒμŠ€μ™€ μ‹œκ·Έλ‹ˆμ²˜κ°€ κ°™κ³ , 호좜만 await μž…λ‹ˆλ‹€. μ•„λž˜λŠ” async ν•¨μˆ˜ μ•ˆμ—μ„œ λΆ€λ₯΄λŠ” λΆ€λΆ„λ§Œ μ μ—ˆμŠ΅λ‹ˆλ‹€.

from pykorail import AsyncKorail

async with await AsyncKorail.logged_in("me@example.com", "password") as korail:
    trains = await korail.trains.search("μ„œμšΈ", "λΆ€μ‚°", depart_after=departure)

asyncio μ „μš©μ΄κ³  curl_cffi κ°€ ν•„μš”ν•©λ‹ˆλ‹€. 이벀트 λ£¨ν”„Β·λ™μ‹œμ„± 같은 λ‚˜λ¨Έμ§€ μ œμ•½μ€ API λ ˆνΌλŸ°μŠ€μ— μžˆμŠ΅λ‹ˆλ‹€.

Caution

승차ꢌ 재판맀 λͺ©μ μœΌλ‘œ μ“°μ§€ λ§ˆμ„Έμš”. pykorail 은 본인이 νƒˆ ν‘œλ₯Ό μ˜ˆλ§€ν•˜κΈ° μœ„ν•œ λ„κ΅¬μž…λ‹ˆλ‹€. μŠΉμ°¨κΆŒμ„ μ›ƒλˆμ„ λ°›κ³  μƒμŠ΅μ μœΌλ‘œ λ˜νŒŒλŠ” ν–‰μœ„λŠ” μ² λ„μ‚¬μ—…λ²•μœΌλ‘œ κΈˆμ§€λ˜μ–΄ 있고, 상업적 이득을 μ–»λŠ” 데 μ“°λŠ” 것은 λΌμ΄μ„ μŠ€λ‘œλ„ κΈˆμ§€λ©λ‹ˆλ‹€.

μ½”λ ˆμΌκ³Ό 아무 κ΄€λ ¨ μ—†λŠ” 비곡식 λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€. λ¬Έμ„œν™”λ˜μ§€ μ•Šμ€ μ•± API λ₯Ό μ“°κΈ° λ•Œλ¬Έμ— μ½”λ ˆμΌμ΄ 앱을 λ°”κΎΈλ©΄ 예고 없이 멈좜 수 μžˆμŠ΅λ‹ˆλ‹€. 이용 μ•½κ΄€κ³Ό κ΄€λ ¨ 법령을 μ§€ν‚€λŠ” 것은 μ‚¬μš©μž μ±…μž„μ΄λ©°, κ³Όλ„ν•œ μžλ™ μš”μ²­μ€ 계정 제재둜 μ΄μ–΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.


ν•  수 μžˆλŠ” 것

ν‘œ μ˜ˆλ§€ν•˜κ³  κ²°μ œν•˜κΈ°

from pykorail import AdultPassenger, Card, ChildPassenger

trains = korail.trains.search(
    "μ„œμšΈ",
    "λΆ€μ‚°",
    depart_after=departure,
    passengers=[AdultPassenger(2), ChildPassenger(1)],
)

reservation = korail.reservations.create(trains[0])
print(reservation)
# [KTX 101] … 119600원(3석), κ΅¬μž…κΈ°ν•œ 4μ›” 1일 09:20

korail.reservations.pay(
    reservation,
    Card(number="1234567812345678", password="12", verify_number="900101", expire="2812"),
)
λ’€μͺ½ μ—΄μ°¨κΉŒμ§€ μ΄μ–΄μ„œ 보기

μ„œλ²„λŠ” 쑰회 ν•œ λ²ˆμ— ν•œ μͺ½(보톡 μ—΄ 편)만 μ€λ‹ˆλ‹€. max_pages λ₯Ό 올리면 λ§ˆμ§€λ§‰ μ—΄μ°¨μ˜ 좜발 μ‹œκ°λΆ€ν„° λ‹€μ‹œ μ‘°νšŒν•΄ 이어 λΆ™μž…λ‹ˆλ‹€.

trains = korail.trains.search("μ„œμšΈ", "λΆ€μ‚°", include_no_seats=True, max_pages=5)
print(len(trains), trains[-1].dep_time)

μͺ½λ§ˆλ‹€ μš”μ²­μ΄ ν•œ λ²ˆμ”© 더 λ‚˜κ°‘λ‹ˆλ‹€. μƒˆ μ—΄μ°¨κ°€ 더 μ—†μœΌλ©΄ κ·Έ 전에 멈μΆ₯λ‹ˆλ‹€.

맀진일 λ•Œ μ˜ˆμ•½λŒ€κΈ° κ±ΈκΈ°
trains = korail.trains.search("μ„œμšΈ", "λΆ€μ‚°", include_waiting_list=True)
waitable = [t for t in trains if not t.has_seat() and t.has_waiting_list()]

reservation = korail.reservations.create(waitable[0])
assert reservation.is_waiting
μΈκ·Όμ—­μ—μ„œ μΆœλ°œΒ·λ„μ°©ν•˜λŠ” μ—΄μ°¨κΉŒμ§€ 보기

μ•±μ˜ "인접역" μ˜΅μ…˜μž…λ‹ˆλ‹€. μš©μ‚° β†’ λŒ€μ „μ„ 찾으면 μ„œμšΈ β†’ λŒ€μ „ Β· μš©μ‚° β†’ μ„œλŒ€μ „ νŽΈλ„ ν•¨κ»˜ λ‚˜μ˜΅λ‹ˆλ‹€. 직톡이 μ—†λŠ” ꡬ간은 이걸 μΌœμ§€ μ•ŠμœΌλ©΄ μ‘°νšŒν•  방법이 μ—†μŠ΅λ‹ˆλ‹€.

trains = korail.trains.search("μš©μ‚°", "λŒ€μ „", include_nearby_stations=True)

for train in trains:
    print(train.dep_name, "β†’", train.arr_name)
# μ„œμšΈ β†’ λŒ€μ „ / μš©μ‚° β†’ μ„œλŒ€μ „ / μ„œμšΈ β†’ μ„œλŒ€μ „ …

# μΆœλ°œμ€ μš©μ‚°μ—μ„œλ§Œ ν•˜κ³  μ‹Άλ‹€λ©΄ κ²°κ³Όμ—μ„œ κ³ λ₯΄λ©΄ λ©λ‹ˆλ‹€
from_yongsan = [t for t in trains if t.dep_name == "μš©μ‚°"]

κ²°κ³Όκ°€ λ”ν•΄μ§€λŠ” 것이 μ•„λ‹ˆλΌ 후보가 λ„“μ–΄μ§€λŠ” 것이라, 켜면 인근역 편이 μ‹œκ°„μˆœμœΌλ‘œ 끼어듀며 λ’€μͺ½ μ§ν†΅νŽΈμ„ λ°€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ 기본값은 κΊΌμ§μž…λ‹ˆλ‹€.

ν™˜μŠΉ 쑰회 Β· μ˜ˆλ§€ν•˜κΈ°

직톡이 μ—†λŠ” ꡬ간은 ν•œ 번 κ°ˆμ•„νƒ€λŠ” μ—¬μ •μœΌλ‘œ μ°ΎμŠ΅λ‹ˆλ‹€. μ‘°νšŒλŠ” λ‘œκ·ΈμΈν•˜μ§€ μ•Šμ•„λ„ λ©λ‹ˆλ‹€.

transfers = korail.trains.search_transfers("강릉", "λͺ©ν¬")

for transfer in transfers:
    first, second = transfer.legs
    print(first.train_no, "β†’", transfer.station_name, "β†’", second.train_no)

first, second = korail.reservations.create_transfer(transfers[0])

print(first.rsv_id == second.rsv_id)  # True β€” μ˜ˆμ•½λ²ˆν˜ΈλŠ” ν•˜λ‚˜μž…λ‹ˆλ‹€

ꡬ간을 λ”°λ‘œ create() ν•˜λ©΄ 첫 κ΅¬κ°„λ§Œ 작히고 λ‘˜μ§Έκ°€ 맀진될 수 μžˆμŠ΅λ‹ˆλ‹€. 이μͺ½μ€ ν•œ ꡬ간이라도 μ•ˆ 되면 μ„œλ²„κ°€ ν†΅μ§Έλ‘œ κ±°μ ˆν•˜κ³ , cancel() 은 μ˜ˆμ•½λ²ˆν˜Έ λ‹¨μœ„λΌ λ‘˜ 쀑 μ–΄λŠ 것을 λ„˜κ²¨λ„ 두 ꡬ간이 ν•¨κ»˜ μ·¨μ†Œλ©λ‹ˆλ‹€.

μš΄μž„ 쑰회

μ˜ˆμ•½ν•˜κΈ° 전에 값을 λ΄…λ‹ˆλ‹€. λ‘œκ·ΈμΈν•˜μ§€ μ•Šμ•„λ„ λ©λ‹ˆλ‹€.

train = korail.trains.search("μ„œμšΈ", "λΆ€μ‚°")[0]

for fare in korail.trains.fares(train):
    print(fare.seat_class_name, fare.total)
# μΌλ°˜μ‹€ 59800
# νŠΉμ‹€ 83700

할인 μ „μ˜ κΈ°μ€€ μš΄μž„μž…λ‹ˆλ‹€. μ‹€μ œλ‘œ λ‚Ό κΈˆμ•‘μ€ μ˜ˆμ•½ν•œ λ’€ Reservation.price 에 μžˆμŠ΅λ‹ˆλ‹€.

호차 Β· μ’Œμ„μ„ 골라 μ˜ˆλ§€ν•˜κΈ°

create() λŠ” μ’Œμ„μ„ μ„œλ²„κ°€ μ •ν•©λ‹ˆλ‹€. 자리λ₯Ό 직접 κ³ λ₯΄λ €λ©΄ 호차 λͺ©λ‘ λ‹€μŒμ— μ’Œμ„ 배치λ₯Ό λ°›μ•„ create_with_seats() 에 λ„˜κΉλ‹ˆλ‹€. λ‘˜ λ‹€ 둜그인이 ν•„μš”ν•©λ‹ˆλ‹€.

from pykorail import AdultPassenger

train = korail.trains.search("μ„œμšΈ", "λΆ€μ‚°")[0]

# μ’Œμ„μ€ 승객 ν•œ λͺ…에 ν•˜λ‚˜μ”©μ΄λΌ, 두 μžλ¦¬κ°€ 남은 호차λ₯Ό κ³ λ¦…λ‹ˆλ‹€.
car = next(car for car in korail.trains.cars(train) if car.remaining_seats >= 2)
picked = [seat for seat in korail.trains.seats(train, car) if seat.is_available][:2]

reservation = korail.reservations.create_with_seats(train, picked, [AdultPassenger(2)])

μ’Œμ„ μˆ˜κ°€ 승객 μˆ˜μ™€ λ‹€λ₯΄κ±°λ‚˜, 같은 μ’Œμ„μ„ 두 번 λ„˜κΈ°κ±°λ‚˜, 객싀이 μ„žμ΄λ©΄ μš”μ²­ 전에 ValueError 둜 λ§‰μŠ΅λ‹ˆλ‹€.

λ‚΄ μ˜ˆμ•½Β·μŠΉμ°¨κΆŒ 보기
for reservation in korail.reservations.all():
    print(reservation.train.dep_name, "β†’", reservation.train.arr_name, reservation.price)

for ticket in korail.tickets.all():
    print(ticket.ticket_no, ticket.car_no, ticket.seat_no)
    korail.tickets.refund(ticket)  # ν™˜λΆˆ
κ³΅ν•­λ²„μŠ€ μ‹œκ°„ν‘œ Β· μ’Œμ„ 보기

둜그인 없이 λ©λ‹ˆλ‹€. μ½”λ ˆμΌμ΄ νŒŒλŠ” 것은 κ΄‘λͺ…μ—­ β†’ μΈμ²œκ³΅ν•­(T1 Β· T2) λ°©ν–₯λΏμž…λ‹ˆλ‹€.

buses = korail.airport_bus.search("κ΄‘λͺ…μ—­", "μΈμ²œκ³΅ν•­T1")
bus = next(bus for bus in buses if bus.has_seat())

print(bus, bus.price)
print([seat.seat_no for seat in korail.airport_bus.seats(bus) if seat.is_available])
λ§ˆμΌλ¦¬μ§€ Β· 할인쿠폰 보기
mileage = korail.membership.mileage()
print(mileage.total, "λ§ˆμΌλ¦¬μ§€,", mileage.expiring, "이번 달 μ†Œλ©Έ μ˜ˆμ •")

for entry in mileage.entries:  # 졜근 30일의 적립 Β· μ‚¬μš© λ‚΄μ—­
    print(entry.date, entry.movement, entry.amount)  # 20261003 적립 2990

for coupon in korail.membership.coupons():
    print(coupon.title, coupon.valid_until)
κΈ°κΈ° ν”„λ‘œνŒŒμΌ κ³ μ •ν•˜κΈ° β€” μ—¬λŸ¬ 번 μ‹€ν–‰ν•œλ‹€λ©΄

ν”„λ‘œνŒŒμΌλ§ˆλ‹€ ν•©μ„± Android IDκ°€ ν•˜λ‚˜ μƒμ„±λ˜λ©°, 같은 ν”„λ‘œνŒŒμΌμ„ μ“°λŠ” ν΄λΌμ΄μ–ΈνŠΈλŠ” κ·Έ IDλ₯Ό μž¬μ‚¬μš©ν•©λ‹ˆλ‹€. μž¬μ‹œμž‘ 후에도 μœ μ§€ν•˜λ €λ©΄ id와 android_idλ₯Ό ν•¨κ»˜ μ €μž₯ν•˜μ„Έμš”. pykorail은 νŒŒμΌμ„ μžλ™μœΌλ‘œ μ½κ±°λ‚˜ μ“°μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

from pykorail.device import profile_by_id, random_profile

profile = profile_by_id(saved_id, android_id=saved_android_id) or random_profile()
# 호좜 μ•±μ˜ 섀정에 profile.id와 profile.android_idλ₯Ό ν•¨κ»˜ μ €μž₯ν•©λ‹ˆλ‹€.
korail = Korail(device_profile=profile)

전체 ν”„λ‘œνŒŒμΌμ„ μ €μž₯ν•˜λ €λ©΄ dataclasses.asdict(profile)을 μ§λ ¬ν™”ν•œ λ’€ DeviceProfile(**saved_data)둜 볡원할 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. IDλŠ” Android 14의 SSAID 계산 방식과 μ½”λ ˆμΌ 곡개 μ„œλͺ… μΈμ¦μ„œλ‘œ μƒμ„±ν•œ ν•©μ„± 값이며, μ‹€μ œ κΈ°κΈ°μ—μ„œ μΆ”μΆœν•œ 값은 μ•„λ‹™λ‹ˆλ‹€. ν”„λ‘œνŒŒμΌμ„ μƒλž΅ν•˜λ©΄ ν΄λΌμ΄μ–ΈνŠΈλ§ˆλ‹€ μƒˆ IDκ°€ μƒμ„±λ©λ‹ˆλ‹€. μ΄λ•Œλ„ korail.android_idλ₯Ό μ €μž₯ν•˜κ³  Korail(android_id=saved_android_id)둜 볡원할 수 μžˆμŠ΅λ‹ˆλ‹€. ν”„λ‘œνŒŒμΌ μœ λ¬΄μ™€ λ¬΄κ΄€ν•˜κ²Œ User-AgentλŠ” korailtalkμž…λ‹ˆλ‹€.


μ•Œμ•„λ‘λ©΄ 쒋은 것

  • 역은 μ΄λ¦„μœΌλ‘œ β€” "μ„œμšΈμ—­" 이 μ•„λ‹ˆλΌ "μ„œμšΈ". μ˜€νƒ€λ©΄ μš”μ²­ 전에 막고 λΉ„μŠ·ν•œ 역을 μ•Œλ €μ€λ‹ˆλ‹€.
  • μ‹œκ°μ€ 항상 KST β€” tz μ—†λŠ” datetime 은 μ‹€ν–‰ λ¨Έμ‹ μ˜ νƒ€μž„μ‘΄κ³Ό λ¬΄κ΄€ν•˜κ²Œ ν•œκ΅­μ‹œκ°„μœΌλ‘œ ν•΄μ„λ©λ‹ˆλ‹€.
  • μ§€λ‚œ μ‹œκ°μ€ κ±°λΆ€ β€” μ„œλ²„κ°€ 과거에도 빈 결과만 μ€˜μ„œ "λ– λ‚œ μ—΄μ°¨"와 "μ—΄μ°¨ μ—†μŒ"이 κ΅¬λΆ„λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • μ‹€νŒ¨λŠ” μ˜ˆμ™Έλ‘œ β€” cancel() Β· pay() λŠ” 성곡 μ‹œ 아무것도 λ°˜ν™˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
from pykorail import KorailError, SoldOutError

try:
    korail.reservations.create(train)
except SoldOutError:
    ...  # λ§€μ§„ β€” λ‹€μŒ μ—΄μ°¨λ‘œ
except KorailError as exc:
    print(exc.msg, exc.code)  # μ½”λ ˆμΌμ΄ μ€€ λ©”μ‹œμ§€μ™€ μ½”λ“œ

λ¬Έμ„œ


자주 λ¬»λŠ” 것

둜그인이 μ•ˆ λ©λ‹ˆλ‹€

μ„€μΉ˜ν•  λ•Œ κ²½κ³ κ°€ λ–΄λ‹€λ©΄ curl_cffi λŒ€μ‹  requests 둜 돌고 μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€. μ½”λ ˆμΌμ€ TLS 지문을 보기 λ•Œλ¬Έμ— μ΄λ•Œ 둜그인이 거뢀될 수 μžˆμŠ΅λ‹ˆλ‹€. pip install curl_cffi 둜 ν•΄κ²°λ˜λŠ” κ²½μš°κ°€ λŒ€λΆ€λΆ„μž…λ‹ˆλ‹€.

νœ΄λŒ€ν° λ²ˆν˜ΈλŠ” 010-1234-5678κ³Ό 01012345678 λͺ¨λ‘ μž…λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ν΄λΌμ΄μ–ΈνŠΈκ°€ νœ΄λŒ€ν° 둜그인으둜 λΆ„λ₯˜ν•˜κ³  ν•˜μ΄ν”ˆμ„ μ œκ±°ν•΄ μ „μ†‘ν•©λ‹ˆλ‹€.

PasswordChangeRequiredError κ°€ λ‚œλ‹€λ©΄ 아이디 Β· λΉ„λ°€λ²ˆν˜ΈλŠ” λ§žμŠ΅λ‹ˆλ‹€. 같은 λΉ„λ°€λ²ˆν˜Έλ₯Ό 6κ°œμ›” λ„˜κ²Œ μ¨μ„œ μ½”λ ˆμΌμ΄ λ³€κ²½ μ•ˆλ‚΄λ₯Ό λ„μš΄ μƒνƒœμ΄κ³ , κ·Έλ™μ•ˆμ€ 둜그인이 λλ‚˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ½”λ ˆμΌν†‘ μ•±μ΄λ‚˜ ν™ˆνŽ˜μ΄μ§€μ—μ„œ μ•ˆλ‚΄λ₯Ό μ²˜λ¦¬ν•œ λ’€ λ‹€μ‹œ μ‹€ν–‰ν•˜μ„Έμš”.

Windows μ—μ„œ μ„€μΉ˜κ°€ μ•ˆ λ©λ‹ˆλ‹€

curl_cffi 의 libcurl DLL λ‘œλ“œκ°€ μ‹€νŒ¨ν•˜λŠ” ν™˜κ²½μ΄ μžˆμŠ΅λ‹ˆλ‹€. pip install "pykorail[fallback]" 둜 requests 폴백을 ν•¨κ»˜ μ„€μΉ˜ν•˜μ„Έμš”. λ‹€λ§Œ μœ„μ— 적은 이유둜 둜그인이 λ§‰νž 수 μžˆμŠ΅λ‹ˆλ‹€.

μ–΄μ œκΉŒμ§€ 되던 게 였늘 μ•ˆ λ©λ‹ˆλ‹€

μ½”λ ˆμΌμ΄ μ„œλ²„λ₯Ό 바꿨을 수 μžˆμŠ΅λ‹ˆλ‹€. API λ³€κ²½ 이슈 둜 μ•Œλ €μ£Όμ‹œλ©΄ λŒ€μ‘ν•˜κ² μŠ΅λ‹ˆλ‹€. κ°€μž₯ 도움이 λ˜λŠ” κΈ°μ—¬μž…λ‹ˆλ‹€.

μ˜ˆλ§€κ°€ ν™•μ‹€νžˆ λ˜λ‚˜μš”?

이 λΌμ΄λΈŒλŸ¬λ¦¬λŠ” μ•±κ³Ό 같은 μš”μ²­μ„ 보낼 뿐이고, μ’Œμ„ 배정은 μ½”λ ˆμΌ μ„œλ²„κ°€ ν•©λ‹ˆλ‹€. λͺ…μ ˆ 예맀처럼 경쟁이 μ‹¬ν•œ μƒν™©μ—μ„œ 성곡을 보μž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

ν”„λ‘μ‹œλ₯Ό 넣을 κ³„νšμ€ μ—†λ‚˜μš”?

μ—†κ³ , μ•žμœΌλ‘œλ„ λ„£μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. ν”„λ‘μ‹œλ‘œ 좜처λ₯Ό λ°”κΏ” κ°€λ©° μš”μ²­ν•˜λŠ” 것은 μ½”λ ˆμΌ μ„œλ²„λ₯Ό 속여 정상 μ΄μš©μžλ³΄λ‹€ μœ λ¦¬ν•˜κ²Œ μ˜ˆλ§€ν•˜λ €λŠ” λΆ€μ •ν•œ λ°©λ²•μž…λ‹ˆλ‹€.


개발

파이썬 3.11 이상 (3.11 – 3.15 μ—μ„œ ν…ŒμŠ€νŠΈν•©λ‹ˆλ‹€).

uv sync --all-extras
uv run ruff format && uv run ruff check && uv run ty check && uv run pytest

λ„€ 개λ₯Ό μ „λΆ€ 톡과해야 ν•©λ‹ˆλ‹€. 컀버리지 ν•˜ν•œ 90%λŠ” pytest κ°€ κ°•μ œν•˜κ³ , μ§€κΈˆ μˆ˜μΉ˜λŠ” CI μ—μ„œ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

κΈ°μ—¬λ₯Ό ν™˜μ˜ν•©λ‹ˆλ‹€ β€” CONTRIBUTING.md λΆ€ν„° λ³΄μ„Έμš”. μƒˆ μ—”λ“œν¬μΈνŠΈλŠ” 곡식 μ½”λ ˆμΌν†‘+ APK λ₯Ό λ””μ»΄νŒŒμΌν•΄ κ²½λ‘œμ™€ 폼 ν•„λ“œλ₯Ό ν™•μΈν•œ λ’€μ—λ§Œ μΆ”κ°€ν•©λ‹ˆλ‹€. 릴리슀 μ ˆμ°¨λŠ” CONTRIBUTING 의 릴리슀 μ ˆμ— μžˆμŠ΅λ‹ˆλ‹€.


λΌμ΄μ„ μŠ€

MIT 에 상업적 이득 κΈˆμ§€ 쑰항을 λ”ν•œ 자체 λΌμ΄μ„ μŠ€μž…λ‹ˆλ‹€. κ·Έλž˜μ„œ MIT 도, OSI κΈ°μ€€μ˜ μ˜€ν”ˆμ†ŒμŠ€λ„ μ•„λ‹™λ‹ˆλ‹€.

  • κΈˆμ§€ β€” 이 νŒ¨ν‚€μ§€λ‘œ ν™•λ³΄ν•œ 승차ꢌ Β· μ˜ˆμ•½μ„ μ‚° 값보닀 λΉ„μ‹Έκ²Œ λ˜νŒŒλŠ” 것, 수수료 Β· κ΅¬λ…λ£Œ Β· κ΄‘κ³  μˆ˜μ΅μ„ λ°›λŠ” 예맀 Β· μ˜ˆμ•½λŒ€κΈ° Β· ν‘œ 확보 μ„œλΉ„μŠ€, 이 νŒ¨ν‚€μ§€μ— μ „λΆ€ λ˜λŠ” 상당 λΆ€λΆ„ κΈ°λŒ€λŠ” μƒν’ˆ Β· μ„œλΉ„μŠ€λ₯Ό νŒŒλŠ” 것
  • ν—ˆμš© β€” 본인의 μ—¬ν–‰, λ‹€λ₯Έ μ‚¬λžŒμ˜ ν‘œλ₯Ό 수수료 없이 μ •κ°€λ‘œ λŒ€μ‹  μ˜ˆλ§€ν•˜λŠ” 것, 단체가 μ†Œμ†μ›μ˜ 여행에 μ“°λŠ” 것, 연ꡬ Β· ꡐ윑 Β· λΉ„μ˜λ¦¬ 개인 ν”„λ‘œμ νŠΈ

이 μš”μ•½μ€ 참고용이고 영문 LICENSE κ°€ μš°μ„ ν•©λ‹ˆλ‹€. 0.3.0 κΉŒμ§€μ˜ 배포본은 MIT κ·ΈλŒ€λ‘œμž…λ‹ˆλ‹€.


μ½”λ ˆμΌκ³Ό λ¬΄κ΄€ν•œ 비곡식 ν”„λ‘œμ νŠΈ

About

πŸš„ KTX ν‘œ, 파이썬이 λŠμŠ΅λ‹ˆλ‹€ β€” μ—΄μ°¨ 쑰회 Β· 예맀 Β· 결제 Β· ν™˜λΆˆ Β· μ˜ˆμ•½λŒ€κΈ° Β· μ·¨μ†Œν‘œ κ°μ‹œλ₯Ό μ½”λ“œ λͺ‡ μ€„λ‘œ. μ½”λ ˆμΌν†‘ API 비곡식 Python SDK (Unofficial Korail / KTX API client)

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

46 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages