@nomadamas/k-skill 0.1.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/bin/k-skill.js +60 -0
- package/package.json +37 -0
- package/skills/assembly-bill-vote-search/instruction.md +111 -0
- package/skills/assembly-bill-vote-search/skill.json +10 -0
- package/skills/biz-health-check/instruction.md +69 -0
- package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
- package/skills/biz-health-check/skill.json +10 -0
- package/skills/bok-ecos-stats/instruction.md +95 -0
- package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
- package/skills/bok-ecos-stats/skill.json +10 -0
- package/skills/building-register-search/instruction.md +69 -0
- package/skills/building-register-search/scripts/building_register.py +309 -0
- package/skills/building-register-search/scripts/building_register_xml.py +32 -0
- package/skills/building-register-search/skill.json +9 -0
- package/skills/bunjang-search/instruction.md +154 -0
- package/skills/bunjang-search/skill.json +10 -0
- package/skills/catchtable-sniper/instruction.md +270 -0
- package/skills/catchtable-sniper/skill.json +9 -0
- package/skills/cheap-gas-nearby/instruction.md +107 -0
- package/skills/cheap-gas-nearby/skill.json +10 -0
- package/skills/corporate-registration-consulting/instruction.md +117 -0
- package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
- package/skills/corporate-registration-consulting/skill.json +8 -0
- package/skills/coupang-product-search/instruction.md +219 -0
- package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
- package/skills/coupang-product-search/skill.json +10 -0
- package/skills/court-auction-notice-search/instruction.md +201 -0
- package/skills/court-auction-notice-search/skill.json +9 -0
- package/skills/court-payment-order-assistant/instruction.md +120 -0
- package/skills/court-payment-order-assistant/skill.json +10 -0
- package/skills/d2b-notice-search/instruction.md +117 -0
- package/skills/d2b-notice-search/skill.json +10 -0
- package/skills/daangn-cars-search/instruction.md +91 -0
- package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
- package/skills/daangn-cars-search/skill.json +9 -0
- package/skills/daangn-jobs-search/instruction.md +90 -0
- package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
- package/skills/daangn-jobs-search/skill.json +9 -0
- package/skills/daangn-realty-search/instruction.md +114 -0
- package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
- package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
- package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
- package/skills/daangn-realty-search/skill.json +8 -0
- package/skills/daangn-used-goods-search/instruction.md +90 -0
- package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
- package/skills/daangn-used-goods-search/skill.json +9 -0
- package/skills/daishin-report-search/instruction.md +138 -0
- package/skills/daishin-report-search/skill.json +10 -0
- package/skills/daiso-product-search/instruction.md +172 -0
- package/skills/daiso-product-search/skill.json +8 -0
- package/skills/danawa-price-search/instruction.md +183 -0
- package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
- package/skills/danawa-price-search/skill.json +8 -0
- package/skills/delivery-tracking/instruction.md +358 -0
- package/skills/delivery-tracking/skill.json +8 -0
- package/skills/donation-place-search/instruction.md +129 -0
- package/skills/donation-place-search/skill.json +9 -0
- package/skills/emergency-room-beds/instruction.md +82 -0
- package/skills/emergency-room-beds/skill.json +8 -0
- package/skills/ev-charger-nearby/instruction.md +80 -0
- package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
- package/skills/ev-charger-nearby/skill.json +10 -0
- package/skills/ev-subsidy-status/instruction.md +165 -0
- package/skills/ev-subsidy-status/skill.json +10 -0
- package/skills/express-bus-booking/instruction.md +207 -0
- package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
- package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
- package/skills/express-bus-booking/skill.json +8 -0
- package/skills/fine-dust-location/instruction.md +89 -0
- package/skills/fine-dust-location/skill.json +9 -0
- package/skills/flight-ticket-search/instruction.md +237 -0
- package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
- package/skills/flight-ticket-search/skill.json +9 -0
- package/skills/foresttrip-vacancy/instruction.md +167 -0
- package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
- package/skills/foresttrip-vacancy/skill.json +10 -0
- package/skills/fsc-corporate-info/instruction.md +57 -0
- package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
- package/skills/fsc-corporate-info/skill.json +10 -0
- package/skills/g2b-order-plan-search/instruction.md +131 -0
- package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
- package/skills/g2b-order-plan-search/skill.json +10 -0
- package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
- package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
- package/skills/g2b-sanctioned-supplier/skill.json +10 -0
- package/skills/gangnamunni-clinic-search/instruction.md +113 -0
- package/skills/gangnamunni-clinic-search/skill.json +9 -0
- package/skills/geeknews-search/instruction.md +69 -0
- package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
- package/skills/geeknews-search/skill.json +8 -0
- package/skills/gongsijiga-search/instruction.md +128 -0
- package/skills/gongsijiga-search/skill.json +9 -0
- package/skills/gov-overseas-trip-report/instruction.md +488 -0
- package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
- package/skills/gov-overseas-trip-report/skill.json +9 -0
- package/skills/han-river-water-level/instruction.md +83 -0
- package/skills/han-river-water-level/skill.json +10 -0
- package/skills/highway-traffic-status/instruction.md +88 -0
- package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
- package/skills/highway-traffic-status/skill.json +11 -0
- package/skills/hipass-receipt/instruction.md +97 -0
- package/skills/hipass-receipt/skill.json +10 -0
- package/skills/hola-poke-yeoksam/instruction.md +247 -0
- package/skills/hola-poke-yeoksam/skill.json +8 -0
- package/skills/household-waste-info/instruction.md +117 -0
- package/skills/household-waste-info/skill.json +10 -0
- package/skills/housing-official-price/instruction.md +177 -0
- package/skills/housing-official-price/skill.json +10 -0
- package/skills/hwp/instruction.md +206 -0
- package/skills/hwp/skill.json +8 -0
- package/skills/intercity-bus-booking/instruction.md +189 -0
- package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
- package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
- package/skills/intercity-bus-booking/skill.json +8 -0
- package/skills/iros-registry-automation/instruction.md +229 -0
- package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
- package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
- package/skills/iros-registry-automation/skill.json +9 -0
- package/skills/job-posting-match/instruction.md +130 -0
- package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
- package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
- package/skills/job-posting-match/skill.json +8 -0
- package/skills/jobkorea-talent-search/instruction.md +118 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
- package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
- package/skills/jobkorea-talent-search/skill.json +9 -0
- package/skills/joseon-sillok-search/instruction.md +76 -0
- package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
- package/skills/joseon-sillok-search/skill.json +8 -0
- package/skills/k-dart/instruction.md +406 -0
- package/skills/k-dart/skill.json +8 -0
- package/skills/k-schoollunch-menu/instruction.md +109 -0
- package/skills/k-schoollunch-menu/skill.json +9 -0
- package/skills/k-skill-cleaner/instruction.md +80 -0
- package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
- package/skills/k-skill-cleaner/skill.json +8 -0
- package/skills/k-skill-setup/instruction.md +253 -0
- package/skills/k-skill-setup/skill.json +11 -0
- package/skills/kakao-bar-nearby/instruction.md +76 -0
- package/skills/kakao-bar-nearby/skill.json +8 -0
- package/skills/kakao-map/instruction.md +176 -0
- package/skills/kakao-map/skill.json +10 -0
- package/skills/kakaotalk-mac/instruction.md +189 -0
- package/skills/kakaotalk-mac/skill.json +8 -0
- package/skills/kbl-results/instruction.md +89 -0
- package/skills/kbl-results/skill.json +9 -0
- package/skills/kbo-results/instruction.md +82 -0
- package/skills/kbo-results/skill.json +8 -0
- package/skills/keris-academic-search/instruction.md +81 -0
- package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
- package/skills/keris-academic-search/skill.json +10 -0
- package/skills/kleague-results/instruction.md +92 -0
- package/skills/kleague-results/skill.json +8 -0
- package/skills/kopis-performance-search/instruction.md +109 -0
- package/skills/kopis-performance-search/skill.json +10 -0
- package/skills/korea-weather/instruction.md +93 -0
- package/skills/korea-weather/skill.json +10 -0
- package/skills/korean-character-count/instruction.md +87 -0
- package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
- package/skills/korean-character-count/skill.json +8 -0
- package/skills/korean-cinema-search/instruction.md +177 -0
- package/skills/korean-cinema-search/skill.json +8 -0
- package/skills/korean-heritage-search/instruction.md +106 -0
- package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
- package/skills/korean-heritage-search/skill.json +8 -0
- package/skills/korean-holiday-calendar/instruction.md +97 -0
- package/skills/korean-holiday-calendar/skill.json +9 -0
- package/skills/korean-humanizer/instruction.md +389 -0
- package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
- package/skills/korean-humanizer/skill.json +8 -0
- package/skills/korean-jangbu-for/instruction.md +133 -0
- package/skills/korean-jangbu-for/scripts/install.sh +237 -0
- package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
- package/skills/korean-jangbu-for/skill.json +8 -0
- package/skills/korean-law-search/instruction.md +126 -0
- package/skills/korean-law-search/skill.json +10 -0
- package/skills/korean-marathon-schedule/instruction.md +111 -0
- package/skills/korean-marathon-schedule/skill.json +9 -0
- package/skills/korean-middle-korean/instruction.md +79 -0
- package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
- package/skills/korean-middle-korean/skill.json +8 -0
- package/skills/korean-patent-search/instruction.md +79 -0
- package/skills/korean-patent-search/scripts/patent_search.py +409 -0
- package/skills/korean-patent-search/skill.json +9 -0
- package/skills/korean-privacy-terms/instruction.md +128 -0
- package/skills/korean-privacy-terms/scripts/install.sh +108 -0
- package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
- package/skills/korean-privacy-terms/skill.json +8 -0
- package/skills/korean-scholarship-search/instruction.md +317 -0
- package/skills/korean-scholarship-search/references/report-format.md +40 -0
- package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
- package/skills/korean-scholarship-search/references/search-clues.md +58 -0
- package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
- package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
- package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
- package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
- package/skills/korean-scholarship-search/skill.json +8 -0
- package/skills/korean-slang-writing/instruction.md +181 -0
- package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
- package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
- package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
- package/skills/korean-slang-writing/skill.json +8 -0
- package/skills/korean-spell-check/instruction.md +105 -0
- package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
- package/skills/korean-spell-check/skill.json +9 -0
- package/skills/korean-stock-search/instruction.md +194 -0
- package/skills/korean-stock-search/skill.json +10 -0
- package/skills/korean-transit-route/instruction.md +113 -0
- package/skills/korean-transit-route/skill.json +10 -0
- package/skills/kosis-stats/instruction.md +232 -0
- package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
- package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
- package/skills/kosis-stats/skill.json +9 -0
- package/skills/kr-whois-lookup/instruction.md +107 -0
- package/skills/kr-whois-lookup/skill.json +10 -0
- package/skills/kstartup-search/instruction.md +186 -0
- package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
- package/skills/kstartup-search/skill.json +11 -0
- package/skills/ktx-booking/instruction.md +244 -0
- package/skills/ktx-booking/skill.json +10 -0
- package/skills/lck-analytics/instruction.md +192 -0
- package/skills/lck-analytics/scripts/_lib.js +103 -0
- package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
- package/skills/lck-analytics/scripts/build-match-report.js +44 -0
- package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
- package/skills/lck-analytics/skill.json +9 -0
- package/skills/lh-notice-search/instruction.md +206 -0
- package/skills/lh-notice-search/skill.json +10 -0
- package/skills/library-book-search/instruction.md +139 -0
- package/skills/library-book-search/skill.json +9 -0
- package/skills/local-election-candidate-search/instruction.md +77 -0
- package/skills/local-election-candidate-search/skill.json +8 -0
- package/skills/localdata-business-status/instruction.md +64 -0
- package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
- package/skills/localdata-business-status/skill.json +8 -0
- package/skills/lotto-results/instruction.md +80 -0
- package/skills/lotto-results/skill.json +8 -0
- package/skills/lovebug-report/instruction.md +185 -0
- package/skills/lovebug-report/skill.json +9 -0
- package/skills/market-kurly-search/instruction.md +125 -0
- package/skills/market-kurly-search/skill.json +8 -0
- package/skills/mfds-drug-safety/instruction.md +86 -0
- package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
- package/skills/mfds-drug-safety/skill.json +10 -0
- package/skills/mfds-food-safety/instruction.md +126 -0
- package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
- package/skills/mfds-food-safety/skill.json +10 -0
- package/skills/myrealtrip-search/instruction.md +239 -0
- package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
- package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
- package/skills/myrealtrip-search/skill.json +9 -0
- package/skills/naming-house/instruction.md +146 -0
- package/skills/naming-house/skill.json +8 -0
- package/skills/national-pension-workplace/instruction.md +64 -0
- package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
- package/skills/national-pension-workplace/skill.json +10 -0
- package/skills/naver-ad-performance/instruction.md +108 -0
- package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
- package/skills/naver-ad-performance/skill.json +9 -0
- package/skills/naver-blog-research/instruction.md +128 -0
- package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
- package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
- package/skills/naver-blog-research/scripts/naver_read.py +256 -0
- package/skills/naver-blog-research/scripts/naver_search.py +192 -0
- package/skills/naver-blog-research/skill.json +8 -0
- package/skills/naver-news-search/instruction.md +103 -0
- package/skills/naver-news-search/skill.json +10 -0
- package/skills/naver-shopping-search/instruction.md +94 -0
- package/skills/naver-shopping-search/skill.json +11 -0
- package/skills/nhis-care-checkup-search/instruction.md +116 -0
- package/skills/nhis-care-checkup-search/skill.json +10 -0
- package/skills/nts-business-registration/instruction.md +115 -0
- package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
- package/skills/nts-business-registration/skill.json +10 -0
- package/skills/nts-tax-delinquency/instruction.md +54 -0
- package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
- package/skills/nts-tax-delinquency/skill.json +8 -0
- package/skills/ohou-today-deal/instruction.md +182 -0
- package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
- package/skills/ohou-today-deal/skill.json +9 -0
- package/skills/olive-young-search/instruction.md +154 -0
- package/skills/olive-young-search/skill.json +8 -0
- package/skills/parking-lot-search/instruction.md +96 -0
- package/skills/parking-lot-search/skill.json +9 -0
- package/skills/popbill/instruction.md +146 -0
- package/skills/popbill/scripts/popbill_cli.py +284 -0
- package/skills/popbill/scripts/popbill_registry.py +54 -0
- package/skills/popbill/scripts/popbill_safety.py +37 -0
- package/skills/popbill/scripts/popbill_templates.py +71 -0
- package/skills/popbill/skill.json +9 -0
- package/skills/public-restroom-nearby/instruction.md +89 -0
- package/skills/public-restroom-nearby/skill.json +9 -0
- package/skills/real-estate-search/instruction.md +172 -0
- package/skills/real-estate-search/skill.json +10 -0
- package/skills/rhwp-advanced/instruction.md +145 -0
- package/skills/rhwp-advanced/skill.json +8 -0
- package/skills/rhwp-edit/instruction.md +153 -0
- package/skills/rhwp-edit/skill.json +8 -0
- package/skills/s2b-notice-search/instruction.md +66 -0
- package/skills/s2b-notice-search/skill.json +9 -0
- package/skills/saju-fortune/instruction.md +177 -0
- package/skills/saju-fortune/skill.json +8 -0
- package/skills/saramin-talent-search/instruction.md +119 -0
- package/skills/saramin-talent-search/skill.json +9 -0
- package/skills/seoul-bike/instruction.md +83 -0
- package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
- package/skills/seoul-bike/skill.json +10 -0
- package/skills/seoul-density/instruction.md +109 -0
- package/skills/seoul-density/scripts/seoul_density.py +271 -0
- package/skills/seoul-density/skill.json +10 -0
- package/skills/seoul-subway-arrival/instruction.md +85 -0
- package/skills/seoul-subway-arrival/skill.json +9 -0
- package/skills/sh-notice-search/instruction.md +150 -0
- package/skills/sh-notice-search/skill.json +9 -0
- package/skills/srt-booking/instruction.md +181 -0
- package/skills/srt-booking/scripts/srt_booking.py +272 -0
- package/skills/srt-booking/scripts/srt_seats.py +156 -0
- package/skills/srt-booking/skill.json +20 -0
- package/skills/subway-lost-property/instruction.md +93 -0
- package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
- package/skills/subway-lost-property/skill.json +8 -0
- package/skills/ticket-availability/instruction.md +175 -0
- package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
- package/skills/ticket-availability/skill.json +9 -0
- package/skills/toss-securities/instruction.md +116 -0
- package/skills/toss-securities/skill.json +9 -0
- package/skills/used-car-price-search/instruction.md +109 -0
- package/skills/used-car-price-search/skill.json +8 -0
- package/skills/yebigun-training/instruction.md +177 -0
- package/skills/yebigun-training/skill.json +9 -0
- package/skills/zipcode-search/instruction.md +159 -0
- package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
- package/skills/zipcode-search/skill.json +8 -0
- package/src/assemble.js +134 -0
- package/src/detect.js +18 -0
- package/templates/action-account.md +4 -0
- package/templates/action-booking.md +5 -0
- package/templates/action-commerce.md +5 -0
- package/templates/action-communication.md +5 -0
- package/templates/action-submission.md +5 -0
- package/templates/browser.md +4 -0
- package/templates/core.md +5 -0
- package/templates/hard-boundary.md +3 -0
- package/templates/local.md +2 -0
- package/templates/lookup.md +2 -0
- package/templates/proxy.md +2 -0
- package/templates/vault.md +4 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# KOBUS HTTP/API Probe Notes
|
|
2
|
+
|
|
3
|
+
Session-proven on 2026-05-08. Goal: avoid browser automation where possible.
|
|
4
|
+
|
|
5
|
+
## Base
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
https://www.kobus.co.kr
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use a desktop User-Agent, HTTP/1.1 if needed, a cookie jar, and referers.
|
|
12
|
+
|
|
13
|
+
## Tested Flow
|
|
14
|
+
|
|
15
|
+
### Route / Terminal Candidates
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
POST /mrs/readRotLinInf.ajax
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Observed JSON keys:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
tfrLen
|
|
25
|
+
tfrInfList
|
|
26
|
+
len
|
|
27
|
+
codeYn
|
|
28
|
+
rotInfList
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
One probe returned about 1,208 route records in `rotInfList`.
|
|
32
|
+
|
|
33
|
+
### Timetable
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
POST /mrs/alcnSrch.do
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Example tested route/date:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
서울경부(010) -> 부산(700), 2026-05-09
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Observed result:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
42 schedule links/cards
|
|
49
|
+
25 selectable seat snippets
|
|
50
|
+
first selectable: 00:30 / 천일고속 / 심야우등 / 10 seats
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Typical POST fields:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
deprCd=010
|
|
57
|
+
arvlCd=700
|
|
58
|
+
pathDvs=sngl
|
|
59
|
+
pathStep=1
|
|
60
|
+
deprDtm=YYYYMMDD
|
|
61
|
+
busClsCd=0
|
|
62
|
+
rtrpChc=1
|
|
63
|
+
timeLinkMin=00
|
|
64
|
+
timeLinkMax=23
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Parse `fnSatsChc(...)` onclick values for the next step. Example:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
fnSatsChc('20260509','003000','003000','010','700','3','07','0','Y','N','010','700','N','N','N','N')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Seat / Fare Stage
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
POST /mrs/satschc.do
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Send the original `alcnSrchFrm` hidden fields plus selected values from `fnSatsChc(...)`, including values such as:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
deprTime=003000
|
|
83
|
+
alcnDeprTime=003000
|
|
84
|
+
alcnDeprTrmlNo=010
|
|
85
|
+
alcnArvlTrmlNo=700
|
|
86
|
+
indVBusClsCd=3
|
|
87
|
+
cacmCd=07
|
|
88
|
+
dcDvsCd=0
|
|
89
|
+
prvtBbizEmpAcmtRt=N
|
|
90
|
+
chldSftySatsYn=N
|
|
91
|
+
dsprSatsYn=N
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Observed response contained `form#satsChcFrm` and hidden values:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"deprTime": "003000",
|
|
99
|
+
"alcnDeprTrmlNo": "010",
|
|
100
|
+
"alcnArvlTrmlNo": "700",
|
|
101
|
+
"adltFee": "47600",
|
|
102
|
+
"rmnSatsNum": "10",
|
|
103
|
+
"totSatsNum": "28"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Temporary Hold
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
POST /mrs/setPcpy.ajax
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Observed successful response markers:
|
|
114
|
+
|
|
115
|
+
```text
|
|
116
|
+
MSG_CD=S0000
|
|
117
|
+
pcpyNoAll
|
|
118
|
+
satsNoAll
|
|
119
|
+
ESTM_AMT
|
|
120
|
+
DC_AMT
|
|
121
|
+
TISSU_AMT
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Hold Cancellation
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
POST /mrs/cancPcpy.ajax
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Observed success marker:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
MSG_CD=S0000
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
2026-05-13 서울→광주 re-verification: `센트럴시티(서울)(021) -> 광주(유·스퀘어)(500)`, 2026-05-20 00:45 중앙고속 심야우등, seat 1. `/mrs/setPcpy.ajax` returned `MSG_CD=S0000`, `pcpyNoAll`, `satsNoAll=01`, `TISSU_AMT=36900`; `/mrs/stplcfmpym.do?keep=/mrs/pay` rendered the official payment-information page; `/mrs/cancPcpy.ajax` returned `MSG_CD=S0000`.
|
|
137
|
+
|
|
138
|
+
### Checkout Entry
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
POST /mrs/stplcfmpym.do
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The POST body must include the selected schedule/seat form values plus temporary hold identifiers and fare amounts. A helper page can auto-submit this form to the official KOBUS endpoint.
|
|
145
|
+
|
|
146
|
+
For mobile browsers, use:
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
POST /mrs/stplcfmpym.do?keep=/mrs/pay
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
This preserves the POST body while placing `/mrs/pay` in `location.href`, which avoids a KOBUS client-side mobile redirect condition observed in the common JavaScript.
|
|
153
|
+
|
|
154
|
+
## Interpretation
|
|
155
|
+
|
|
156
|
+
- Login was not required for route lookup, timetable lookup, seat-selection-page entry, temporary hold, or checkout-entry page display in the tested flow.
|
|
157
|
+
- Page HTML can include login or `grecaptchaToken` forms, but these did not block the tested lookup/seat-stage path.
|
|
158
|
+
- Final payment should remain a manual, explicitly confirmed stage.
|
|
159
|
+
- KOBUS mobile behavior is less stable than desktop because common JavaScript can redirect narrow screens to the mobile main page.
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""KOBUS timetable lookup and temporary hold helper.
|
|
3
|
+
|
|
4
|
+
Default mode searches timetables. With --hold-first-seat or --hold-seat it creates
|
|
5
|
+
a temporary seat hold through /mrs/setPcpy.ajax and saves a local auto-submit
|
|
6
|
+
HTML helper for the official KOBUS payment-information page. It never submits
|
|
7
|
+
card fields or final payment.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import html
|
|
13
|
+
import http.cookiejar
|
|
14
|
+
import json
|
|
15
|
+
import re
|
|
16
|
+
import ssl
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
import urllib.parse
|
|
20
|
+
import urllib.request
|
|
21
|
+
from dataclasses import asdict, dataclass
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Iterable
|
|
24
|
+
|
|
25
|
+
BASE_URL = "https://www.kobus.co.kr"
|
|
26
|
+
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/125 Safari/537.36"
|
|
27
|
+
FN_SATS_RE = re.compile(r"fnSatsChc\((.*?)\)", re.DOTALL)
|
|
28
|
+
ARG_RE = re.compile(r"'([^']*)'")
|
|
29
|
+
FORM_RE = re.compile(r"<form\b([^>]*)>(.*?)</form>", re.DOTALL | re.IGNORECASE)
|
|
30
|
+
INPUT_RE = re.compile(r"<input\b([^>]+)>", re.DOTALL | re.IGNORECASE)
|
|
31
|
+
ATTR_RE = re.compile(r"([\w:-]+)=[\"']([^\"']*)[\"']")
|
|
32
|
+
SEAT_RE = re.compile(r'<input\b([^>]*name=["\']seatBoxDtl["\'][^>]*)>', re.DOTALL | re.IGNORECASE)
|
|
33
|
+
TAG_RE = re.compile(r"<[^>]+>")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Schedule:
|
|
38
|
+
index: int
|
|
39
|
+
departure_time: str | None
|
|
40
|
+
company: str | None
|
|
41
|
+
bus_class: str | None
|
|
42
|
+
remaining_text: str | None
|
|
43
|
+
raw_args: list[str]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class Hold:
|
|
48
|
+
success: bool
|
|
49
|
+
pcpy_no_all: str | None
|
|
50
|
+
sats_no_all: str | None
|
|
51
|
+
seat: str | None
|
|
52
|
+
estm_amt: str | None
|
|
53
|
+
dc_amt: str | None
|
|
54
|
+
tissu_amt: str | None
|
|
55
|
+
checkout_helper_path: str | None
|
|
56
|
+
checkout_response_path: str | None
|
|
57
|
+
cancel_fields_path: str | None
|
|
58
|
+
raw_response: dict[str, object]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def opener() -> urllib.request.OpenerDirector:
|
|
62
|
+
jar = http.cookiejar.CookieJar()
|
|
63
|
+
ctx = ssl._create_unverified_context()
|
|
64
|
+
try:
|
|
65
|
+
ctx.set_ciphers("DEFAULT@SECLEVEL=1")
|
|
66
|
+
except ssl.SSLError:
|
|
67
|
+
pass
|
|
68
|
+
return urllib.request.build_opener(
|
|
69
|
+
urllib.request.HTTPCookieProcessor(jar),
|
|
70
|
+
urllib.request.HTTPSHandler(context=ctx),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def request(url: str, data: dict[str, str] | list[tuple[str, str]] | None = None, referer: str | None = None) -> urllib.request.Request:
|
|
75
|
+
headers = {"User-Agent": UA}
|
|
76
|
+
if referer:
|
|
77
|
+
headers["Referer"] = referer
|
|
78
|
+
if data is None:
|
|
79
|
+
return urllib.request.Request(url, headers=headers, method="GET")
|
|
80
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded"
|
|
81
|
+
return urllib.request.Request(url, data=urllib.parse.urlencode(data).encode(), headers=headers, method="POST")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def open_text(op: urllib.request.OpenerDirector, req: urllib.request.Request, timeout: int) -> str:
|
|
85
|
+
with op.open(req, timeout=timeout) as resp:
|
|
86
|
+
return resp.read().decode(resp.headers.get_content_charset() or "utf-8", errors="replace")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def attrs(fragment: str) -> dict[str, str]:
|
|
90
|
+
return {k.lower(): html.unescape(v) for k, v in ATTR_RE.findall(fragment)}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def strip_tags(s: str) -> str:
|
|
94
|
+
return re.sub(r"\s+", " ", html.unescape(TAG_RE.sub(" ", s))).strip()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def parse_form(body: str, form_id: str) -> list[tuple[str, str]]:
|
|
98
|
+
for attr_text, form_body in FORM_RE.findall(body):
|
|
99
|
+
a = attrs(attr_text)
|
|
100
|
+
if a.get("id") == form_id or a.get("name") == form_id:
|
|
101
|
+
fields = []
|
|
102
|
+
for input_text in INPUT_RE.findall(form_body):
|
|
103
|
+
ia = attrs(input_text)
|
|
104
|
+
if ia.get("name"):
|
|
105
|
+
fields.append((ia["name"], ia.get("value", "")))
|
|
106
|
+
return fields
|
|
107
|
+
return []
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def search(op: urllib.request.OpenerDirector, depart: str, arrive: str, date: str, timeout: int) -> tuple[str, list[Schedule]]:
|
|
111
|
+
open_text(op, request(f"{BASE_URL}/main.do"), timeout)
|
|
112
|
+
body = open_text(
|
|
113
|
+
op,
|
|
114
|
+
request(
|
|
115
|
+
f"{BASE_URL}/mrs/alcnSrch.do",
|
|
116
|
+
{
|
|
117
|
+
"deprCd": depart,
|
|
118
|
+
"arvlCd": arrive,
|
|
119
|
+
"pathDvs": "sngl",
|
|
120
|
+
"pathStep": "1",
|
|
121
|
+
"deprDtm": date,
|
|
122
|
+
"busClsCd": "0",
|
|
123
|
+
"rtrpChc": "1",
|
|
124
|
+
"timeLinkMin": "00",
|
|
125
|
+
"timeLinkMax": "23",
|
|
126
|
+
},
|
|
127
|
+
f"{BASE_URL}/main.do",
|
|
128
|
+
),
|
|
129
|
+
timeout,
|
|
130
|
+
)
|
|
131
|
+
schedules: list[Schedule] = []
|
|
132
|
+
for idx, m in enumerate(FN_SATS_RE.finditer(body), 1):
|
|
133
|
+
args = ARG_RE.findall(m.group(1))
|
|
134
|
+
context = strip_tags(body[max(0, m.start() - 900) : m.start() + 900])
|
|
135
|
+
departure = args[1][:2] + ":" + args[1][2:4] if len(args) > 1 and len(args[1]) >= 4 else None
|
|
136
|
+
schedules.append(
|
|
137
|
+
Schedule(
|
|
138
|
+
index=idx,
|
|
139
|
+
departure_time=departure,
|
|
140
|
+
company=(re.search(r"\((?:주|유)\)[^\s]+|[가-힣]+고속", context) or [None])[0],
|
|
141
|
+
bus_class=(re.search(r"심야우등|우등|프리미엄|고속", context) or [None])[0],
|
|
142
|
+
remaining_text=(re.search(r"잔여\s*\d+석|\d+\s*/\s*\d+", context) or [None])[0],
|
|
143
|
+
raw_args=args,
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
return body, schedules
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def seat_stage_fields(search_form: list[tuple[str, str]], schedule: Schedule) -> list[tuple[str, str]]:
|
|
150
|
+
a = schedule.raw_args
|
|
151
|
+
values = dict(search_form)
|
|
152
|
+
updates = {
|
|
153
|
+
"deprTime": a[1],
|
|
154
|
+
"alcnDeprTime": a[2],
|
|
155
|
+
"alcnDeprTrmlNo": a[3],
|
|
156
|
+
"alcnArvlTrmlNo": a[4],
|
|
157
|
+
"indVBusClsCd": a[5],
|
|
158
|
+
"cacmCd": a[6],
|
|
159
|
+
"dcDvsCd": a[7],
|
|
160
|
+
"prvtBbizEmpAcmtRt": a[8],
|
|
161
|
+
"chldSftySatsYn": a[12],
|
|
162
|
+
"dsprSatsYn": a[13],
|
|
163
|
+
}
|
|
164
|
+
return [(k, updates.get(k, v)) for k, v in search_form]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def hold(op: urllib.request.OpenerDirector, alcn_body: str, schedule: Schedule, seat: str | None, out: Path, timeout: int) -> Hold:
|
|
168
|
+
search_form = parse_form(alcn_body, "alcnSrchFrm")
|
|
169
|
+
seat_body = open_text(op, request(f"{BASE_URL}/mrs/satschc.do", seat_stage_fields(search_form, schedule), f"{BASE_URL}/mrs/alcnSrch.do"), timeout)
|
|
170
|
+
fields = parse_form(seat_body, "satsChcFrm")
|
|
171
|
+
field_map = dict(fields)
|
|
172
|
+
seats = []
|
|
173
|
+
for input_text in SEAT_RE.findall(seat_body):
|
|
174
|
+
a = attrs(input_text)
|
|
175
|
+
if "disabled" not in input_text and a.get("value"):
|
|
176
|
+
seats.append(a["value"])
|
|
177
|
+
selected = seat or (seats[0] if seats else None)
|
|
178
|
+
if not selected:
|
|
179
|
+
raise RuntimeError("No selectable KOBUS seat found")
|
|
180
|
+
|
|
181
|
+
def set_field(items: list[tuple[str, str]], key: str, val: str) -> list[tuple[str, str]]:
|
|
182
|
+
return [(k, val if k == key else v) for k, v in items]
|
|
183
|
+
|
|
184
|
+
for key, val in {
|
|
185
|
+
"selSeatNum": selected,
|
|
186
|
+
"selSeatCnt": "1",
|
|
187
|
+
"selAdltCnt": "1",
|
|
188
|
+
"selAdltDcCnt": "0",
|
|
189
|
+
"prmmDcDvsCd": field_map.get("prmmDcDvsCd") or "0",
|
|
190
|
+
}.items():
|
|
191
|
+
fields = set_field(fields, key, val)
|
|
192
|
+
raw = json.loads(open_text(op, request(f"{BASE_URL}/mrs/setPcpy.ajax", fields, f"{BASE_URL}/mrs/satschc.do"), timeout))
|
|
193
|
+
success = raw.get("MSG_CD") == "S0000"
|
|
194
|
+
if not success:
|
|
195
|
+
return Hold(False, None, None, selected, None, None, None, None, None, None, raw)
|
|
196
|
+
|
|
197
|
+
for key, val in {
|
|
198
|
+
"satsNoAll": str(raw.get("satsNoAll", "")),
|
|
199
|
+
"pcpyNoAll": str(raw.get("pcpyNoAll", "")),
|
|
200
|
+
"estmAmt": str(raw.get("ESTM_AMT", "")),
|
|
201
|
+
"dcAmt": str(raw.get("DC_AMT", "")),
|
|
202
|
+
"tissuAmt": str(raw.get("TISSU_AMT", "")),
|
|
203
|
+
"nonMbrsYn": "Y",
|
|
204
|
+
}.items():
|
|
205
|
+
fields = set_field(fields, key, val)
|
|
206
|
+
|
|
207
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
checkout = open_text(op, request(f"{BASE_URL}/mrs/stplcfmpym.do?keep=/mrs/pay", fields, f"{BASE_URL}/mrs/satschc.do"), timeout)
|
|
209
|
+
checkout_path = out / "kobus-checkout-response.html"
|
|
210
|
+
checkout_path.write_text(checkout)
|
|
211
|
+
helper_path = out / "kobus-payment-autosubmit.html"
|
|
212
|
+
inputs = "\n".join(f'<input type="hidden" name="{html.escape(k)}" value="{html.escape(v, quote=True)}">' for k, v in fields)
|
|
213
|
+
helper_path.write_text(f'<!doctype html><meta charset="utf-8"><p>공식 KOBUS 결제정보 입력 페이지로 이동합니다. 결제는 직접 진행하세요.</p><form id="f" method="post" action="{BASE_URL}/mrs/stplcfmpym.do?keep=/mrs/pay">{inputs}</form><script>document.getElementById("f").submit();</script>')
|
|
214
|
+
cancel_path = out / "kobus-cancel-fields.txt"
|
|
215
|
+
cancel_path.write_text("\n".join(f"{k}={v}" for k, v in fields))
|
|
216
|
+
return Hold(True, str(raw.get("pcpyNoAll")), str(raw.get("satsNoAll")), selected, str(raw.get("ESTM_AMT")), str(raw.get("DC_AMT")), str(raw.get("TISSU_AMT")), str(helper_path), str(checkout_path), str(cancel_path), raw)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def main(argv: Iterable[str] | None = None) -> int:
|
|
220
|
+
p = argparse.ArgumentParser()
|
|
221
|
+
p.add_argument("--depart-code", required=True)
|
|
222
|
+
p.add_argument("--arrive-code", required=True)
|
|
223
|
+
p.add_argument("--date", required=True)
|
|
224
|
+
p.add_argument("--select-index", type=int, default=1)
|
|
225
|
+
p.add_argument("--hold-first-seat", action="store_true")
|
|
226
|
+
p.add_argument("--hold-seat")
|
|
227
|
+
p.add_argument("--output-dir")
|
|
228
|
+
p.add_argument("--limit", type=int, default=20)
|
|
229
|
+
p.add_argument("--timeout", type=int, default=20)
|
|
230
|
+
args = p.parse_args(argv)
|
|
231
|
+
op = opener()
|
|
232
|
+
body, schedules = search(op, args.depart_code, args.arrive_code, args.date, args.timeout)
|
|
233
|
+
result: dict[str, object] = {"route": {"depart_code": args.depart_code, "arrive_code": args.arrive_code, "date": args.date}, "count": len(schedules), "items": [asdict(s) for s in schedules[: args.limit]]}
|
|
234
|
+
if (args.hold_first_seat or args.hold_seat) and schedules:
|
|
235
|
+
out = Path(args.output_dir) if args.output_dir else Path(tempfile.mkdtemp(prefix="kobus-hold-"))
|
|
236
|
+
result["hold"] = asdict(hold(op, body, schedules[args.select_index - 1], args.hold_seat, out, args.timeout))
|
|
237
|
+
result["payment_note"] = "Opened/saved the official KOBUS payment-information page; final card entry/payment remains manual."
|
|
238
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
sys.exit(main())
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "express-bus-booking",
|
|
3
|
+
"description": "Search and assist Korean 고속버스/KOBUS bookings using official HTTP/API-first flows; use for 고속버스 예매, 시간표, 좌석 조회, 임시 선점, and official checkout-entry handoff. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"action:booking"
|
|
6
|
+
],
|
|
7
|
+
"frontmatter": "name: express-bus-booking\ndescription: Search and assist Korean 고속버스/KOBUS bookings using official HTTP/API-first flows; use for 고속버스 예매, 시간표, 좌석 조회, 임시 선점, and official checkout-entry handoff. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: travel\n locale: ko-KR\n phase: v1"
|
|
8
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Fine Dust By Location
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
기본적으로 `https://k-skill-proxy.nomadamas.org/v1/fine-dust/report` 로 요청해서 PM10 / PM2.5 / 통합대기등급을 요약한다.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
- "지금 내 위치 미세먼지 어때?"
|
|
10
|
+
- "강남 쪽 초미세먼지 수치 알려줘"
|
|
11
|
+
- "여기 공기질 괜찮아?"
|
|
12
|
+
|
|
13
|
+
## Inputs
|
|
14
|
+
|
|
15
|
+
- 일반 입력: 지역명/행정구역 힌트
|
|
16
|
+
- 재조회 입력: 정확한 측정소명
|
|
17
|
+
|
|
18
|
+
## Region naming convention
|
|
19
|
+
|
|
20
|
+
지역명은 아래처럼 **측정소명에 가까운 한국어 행정구역 이름**을 우선 사용한다.
|
|
21
|
+
|
|
22
|
+
- 좋음: `강남구`, `서울 강남구`, `종로구`, `수원시`
|
|
23
|
+
- 애매함: `강남`, `서울 남쪽`, `코엑스 근처`
|
|
24
|
+
|
|
25
|
+
여러 토큰이 들어오면 helper / proxy 는 보통 **가장 구체적인 토큰**을 우선 본다. 예: `서울 강남구` → `강남구`.
|
|
26
|
+
|
|
27
|
+
## Default path
|
|
28
|
+
|
|
29
|
+
추가 client API 레이어는 불필요하다. 그냥 프록시 서버에 HTTP 요청만 넣으면 된다.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/fine-dust/report' \
|
|
33
|
+
--data-urlencode 'regionHint=서울 강남구'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
스크립트 helper 도 같은 report endpoint 를 기본 경로로 사용한다.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python3 scripts/fine_dust.py report --region-hint '서울 강남구' --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Ambiguous locations
|
|
43
|
+
|
|
44
|
+
입력한 지역명이 단일 측정소로 바로 확정되지 않으면 proxy 는 `ambiguous_location` 과 함께 후보 측정소 목록을 돌려준다.
|
|
45
|
+
|
|
46
|
+
예:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/fine-dust/report' \
|
|
50
|
+
--data-urlencode 'regionHint=광주 광산구'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
이때 응답의 `candidate_stations` 중 하나를 골라 다시 `stationName` 으로 조회한다.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
curl -fsS --get 'https://k-skill-proxy.nomadamas.org/v1/fine-dust/report' \
|
|
57
|
+
--data-urlencode 'stationName=우산동(광주)'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Detailed API paths
|
|
61
|
+
|
|
62
|
+
원본 AirKorea와 비슷한 passthrough 경로(`/B552584/...`)나 direct fallback 상세는 아래 문서만 참고한다.
|
|
63
|
+
|
|
64
|
+
- `docs/features/fine-dust-location.md`
|
|
65
|
+
- `docs/features/k-skill-proxy.md`
|
|
66
|
+
|
|
67
|
+
## Keep the answer compact
|
|
68
|
+
|
|
69
|
+
응답에는 아래만 먼저 정리한다.
|
|
70
|
+
|
|
71
|
+
- 측정소
|
|
72
|
+
- 조회 시각
|
|
73
|
+
- PM10 값과 등급
|
|
74
|
+
- PM2.5 값과 등급
|
|
75
|
+
- 통합대기등급
|
|
76
|
+
- 조회 방식(`fallback`)
|
|
77
|
+
|
|
78
|
+
## Failure modes
|
|
79
|
+
|
|
80
|
+
- regionHint 가 너무 넓거나 단일 측정소를 확정할 수 없는 경우
|
|
81
|
+
- 프록시 서버가 내려가 있거나 upstream key가 비어 있는 경우
|
|
82
|
+
- 측정소명과 지역명이 달라 직접 fallback 이 필요한 경우
|
|
83
|
+
|
|
84
|
+
## Notes
|
|
85
|
+
|
|
86
|
+
- 기본 경로는 항상 `k-skill-proxy.nomadamas.org` 의 report endpoint 다.
|
|
87
|
+
- 지역명 조회는 먼저 후보를 얻고, 필요하면 정확한 측정소명으로 재조회한다.
|
|
88
|
+
- passthrough / direct AirKorea 구현 세부는 스킬 본문에 길게 반복하지 않는다.
|
|
89
|
+
- free API 프록시는 공개 endpoint 를 기본으로 둔다.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fine-dust-location",
|
|
3
|
+
"description": "에어코리아 기반 미세먼지/초미세먼지를 지역명 또는 위치 힌트로 조회한다. 기본 경로는 k-skill-proxy의 report endpoint다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"proxy",
|
|
6
|
+
"lookup"
|
|
7
|
+
],
|
|
8
|
+
"frontmatter": "name: fine-dust-location\ndescription: 에어코리아 기반 미세먼지/초미세먼지를 지역명 또는 위치 힌트로 조회한다. 기본 경로는 k-skill-proxy의 report endpoint다.\nlicense: MIT\nmetadata:\n category: utility\n locale: ko-KR\n phase: v1"
|
|
9
|
+
}
|