@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.
Files changed (350) hide show
  1. package/bin/k-skill.js +60 -0
  2. package/package.json +37 -0
  3. package/skills/assembly-bill-vote-search/instruction.md +111 -0
  4. package/skills/assembly-bill-vote-search/skill.json +10 -0
  5. package/skills/biz-health-check/instruction.md +69 -0
  6. package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
  7. package/skills/biz-health-check/skill.json +10 -0
  8. package/skills/bok-ecos-stats/instruction.md +95 -0
  9. package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
  10. package/skills/bok-ecos-stats/skill.json +10 -0
  11. package/skills/building-register-search/instruction.md +69 -0
  12. package/skills/building-register-search/scripts/building_register.py +309 -0
  13. package/skills/building-register-search/scripts/building_register_xml.py +32 -0
  14. package/skills/building-register-search/skill.json +9 -0
  15. package/skills/bunjang-search/instruction.md +154 -0
  16. package/skills/bunjang-search/skill.json +10 -0
  17. package/skills/catchtable-sniper/instruction.md +270 -0
  18. package/skills/catchtable-sniper/skill.json +9 -0
  19. package/skills/cheap-gas-nearby/instruction.md +107 -0
  20. package/skills/cheap-gas-nearby/skill.json +10 -0
  21. package/skills/corporate-registration-consulting/instruction.md +117 -0
  22. package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
  23. package/skills/corporate-registration-consulting/skill.json +8 -0
  24. package/skills/coupang-product-search/instruction.md +219 -0
  25. package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
  26. package/skills/coupang-product-search/skill.json +10 -0
  27. package/skills/court-auction-notice-search/instruction.md +201 -0
  28. package/skills/court-auction-notice-search/skill.json +9 -0
  29. package/skills/court-payment-order-assistant/instruction.md +120 -0
  30. package/skills/court-payment-order-assistant/skill.json +10 -0
  31. package/skills/d2b-notice-search/instruction.md +117 -0
  32. package/skills/d2b-notice-search/skill.json +10 -0
  33. package/skills/daangn-cars-search/instruction.md +91 -0
  34. package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
  35. package/skills/daangn-cars-search/skill.json +9 -0
  36. package/skills/daangn-jobs-search/instruction.md +90 -0
  37. package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
  38. package/skills/daangn-jobs-search/skill.json +9 -0
  39. package/skills/daangn-realty-search/instruction.md +114 -0
  40. package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
  41. package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
  42. package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
  43. package/skills/daangn-realty-search/skill.json +8 -0
  44. package/skills/daangn-used-goods-search/instruction.md +90 -0
  45. package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
  46. package/skills/daangn-used-goods-search/skill.json +9 -0
  47. package/skills/daishin-report-search/instruction.md +138 -0
  48. package/skills/daishin-report-search/skill.json +10 -0
  49. package/skills/daiso-product-search/instruction.md +172 -0
  50. package/skills/daiso-product-search/skill.json +8 -0
  51. package/skills/danawa-price-search/instruction.md +183 -0
  52. package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
  53. package/skills/danawa-price-search/skill.json +8 -0
  54. package/skills/delivery-tracking/instruction.md +358 -0
  55. package/skills/delivery-tracking/skill.json +8 -0
  56. package/skills/donation-place-search/instruction.md +129 -0
  57. package/skills/donation-place-search/skill.json +9 -0
  58. package/skills/emergency-room-beds/instruction.md +82 -0
  59. package/skills/emergency-room-beds/skill.json +8 -0
  60. package/skills/ev-charger-nearby/instruction.md +80 -0
  61. package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
  62. package/skills/ev-charger-nearby/skill.json +10 -0
  63. package/skills/ev-subsidy-status/instruction.md +165 -0
  64. package/skills/ev-subsidy-status/skill.json +10 -0
  65. package/skills/express-bus-booking/instruction.md +207 -0
  66. package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
  67. package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
  68. package/skills/express-bus-booking/skill.json +8 -0
  69. package/skills/fine-dust-location/instruction.md +89 -0
  70. package/skills/fine-dust-location/skill.json +9 -0
  71. package/skills/flight-ticket-search/instruction.md +237 -0
  72. package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
  73. package/skills/flight-ticket-search/skill.json +9 -0
  74. package/skills/foresttrip-vacancy/instruction.md +167 -0
  75. package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
  76. package/skills/foresttrip-vacancy/skill.json +10 -0
  77. package/skills/fsc-corporate-info/instruction.md +57 -0
  78. package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
  79. package/skills/fsc-corporate-info/skill.json +10 -0
  80. package/skills/g2b-order-plan-search/instruction.md +131 -0
  81. package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
  82. package/skills/g2b-order-plan-search/skill.json +10 -0
  83. package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
  84. package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
  85. package/skills/g2b-sanctioned-supplier/skill.json +10 -0
  86. package/skills/gangnamunni-clinic-search/instruction.md +113 -0
  87. package/skills/gangnamunni-clinic-search/skill.json +9 -0
  88. package/skills/geeknews-search/instruction.md +69 -0
  89. package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
  90. package/skills/geeknews-search/skill.json +8 -0
  91. package/skills/gongsijiga-search/instruction.md +128 -0
  92. package/skills/gongsijiga-search/skill.json +9 -0
  93. package/skills/gov-overseas-trip-report/instruction.md +488 -0
  94. package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
  95. package/skills/gov-overseas-trip-report/skill.json +9 -0
  96. package/skills/han-river-water-level/instruction.md +83 -0
  97. package/skills/han-river-water-level/skill.json +10 -0
  98. package/skills/highway-traffic-status/instruction.md +88 -0
  99. package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
  100. package/skills/highway-traffic-status/skill.json +11 -0
  101. package/skills/hipass-receipt/instruction.md +97 -0
  102. package/skills/hipass-receipt/skill.json +10 -0
  103. package/skills/hola-poke-yeoksam/instruction.md +247 -0
  104. package/skills/hola-poke-yeoksam/skill.json +8 -0
  105. package/skills/household-waste-info/instruction.md +117 -0
  106. package/skills/household-waste-info/skill.json +10 -0
  107. package/skills/housing-official-price/instruction.md +177 -0
  108. package/skills/housing-official-price/skill.json +10 -0
  109. package/skills/hwp/instruction.md +206 -0
  110. package/skills/hwp/skill.json +8 -0
  111. package/skills/intercity-bus-booking/instruction.md +189 -0
  112. package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
  113. package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
  114. package/skills/intercity-bus-booking/skill.json +8 -0
  115. package/skills/iros-registry-automation/instruction.md +229 -0
  116. package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
  117. package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
  118. package/skills/iros-registry-automation/skill.json +9 -0
  119. package/skills/job-posting-match/instruction.md +130 -0
  120. package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
  121. package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
  122. package/skills/job-posting-match/skill.json +8 -0
  123. package/skills/jobkorea-talent-search/instruction.md +118 -0
  124. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
  125. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
  126. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
  127. package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
  128. package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
  129. package/skills/jobkorea-talent-search/skill.json +9 -0
  130. package/skills/joseon-sillok-search/instruction.md +76 -0
  131. package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
  132. package/skills/joseon-sillok-search/skill.json +8 -0
  133. package/skills/k-dart/instruction.md +406 -0
  134. package/skills/k-dart/skill.json +8 -0
  135. package/skills/k-schoollunch-menu/instruction.md +109 -0
  136. package/skills/k-schoollunch-menu/skill.json +9 -0
  137. package/skills/k-skill-cleaner/instruction.md +80 -0
  138. package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
  139. package/skills/k-skill-cleaner/skill.json +8 -0
  140. package/skills/k-skill-setup/instruction.md +253 -0
  141. package/skills/k-skill-setup/skill.json +11 -0
  142. package/skills/kakao-bar-nearby/instruction.md +76 -0
  143. package/skills/kakao-bar-nearby/skill.json +8 -0
  144. package/skills/kakao-map/instruction.md +176 -0
  145. package/skills/kakao-map/skill.json +10 -0
  146. package/skills/kakaotalk-mac/instruction.md +189 -0
  147. package/skills/kakaotalk-mac/skill.json +8 -0
  148. package/skills/kbl-results/instruction.md +89 -0
  149. package/skills/kbl-results/skill.json +9 -0
  150. package/skills/kbo-results/instruction.md +82 -0
  151. package/skills/kbo-results/skill.json +8 -0
  152. package/skills/keris-academic-search/instruction.md +81 -0
  153. package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
  154. package/skills/keris-academic-search/skill.json +10 -0
  155. package/skills/kleague-results/instruction.md +92 -0
  156. package/skills/kleague-results/skill.json +8 -0
  157. package/skills/kopis-performance-search/instruction.md +109 -0
  158. package/skills/kopis-performance-search/skill.json +10 -0
  159. package/skills/korea-weather/instruction.md +93 -0
  160. package/skills/korea-weather/skill.json +10 -0
  161. package/skills/korean-character-count/instruction.md +87 -0
  162. package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
  163. package/skills/korean-character-count/skill.json +8 -0
  164. package/skills/korean-cinema-search/instruction.md +177 -0
  165. package/skills/korean-cinema-search/skill.json +8 -0
  166. package/skills/korean-heritage-search/instruction.md +106 -0
  167. package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
  168. package/skills/korean-heritage-search/skill.json +8 -0
  169. package/skills/korean-holiday-calendar/instruction.md +97 -0
  170. package/skills/korean-holiday-calendar/skill.json +9 -0
  171. package/skills/korean-humanizer/instruction.md +389 -0
  172. package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
  173. package/skills/korean-humanizer/skill.json +8 -0
  174. package/skills/korean-jangbu-for/instruction.md +133 -0
  175. package/skills/korean-jangbu-for/scripts/install.sh +237 -0
  176. package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
  177. package/skills/korean-jangbu-for/skill.json +8 -0
  178. package/skills/korean-law-search/instruction.md +126 -0
  179. package/skills/korean-law-search/skill.json +10 -0
  180. package/skills/korean-marathon-schedule/instruction.md +111 -0
  181. package/skills/korean-marathon-schedule/skill.json +9 -0
  182. package/skills/korean-middle-korean/instruction.md +79 -0
  183. package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
  184. package/skills/korean-middle-korean/skill.json +8 -0
  185. package/skills/korean-patent-search/instruction.md +79 -0
  186. package/skills/korean-patent-search/scripts/patent_search.py +409 -0
  187. package/skills/korean-patent-search/skill.json +9 -0
  188. package/skills/korean-privacy-terms/instruction.md +128 -0
  189. package/skills/korean-privacy-terms/scripts/install.sh +108 -0
  190. package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
  191. package/skills/korean-privacy-terms/skill.json +8 -0
  192. package/skills/korean-scholarship-search/instruction.md +317 -0
  193. package/skills/korean-scholarship-search/references/report-format.md +40 -0
  194. package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
  195. package/skills/korean-scholarship-search/references/search-clues.md +58 -0
  196. package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
  197. package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
  198. package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
  199. package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
  200. package/skills/korean-scholarship-search/skill.json +8 -0
  201. package/skills/korean-slang-writing/instruction.md +181 -0
  202. package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
  203. package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
  204. package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
  205. package/skills/korean-slang-writing/skill.json +8 -0
  206. package/skills/korean-spell-check/instruction.md +105 -0
  207. package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
  208. package/skills/korean-spell-check/skill.json +9 -0
  209. package/skills/korean-stock-search/instruction.md +194 -0
  210. package/skills/korean-stock-search/skill.json +10 -0
  211. package/skills/korean-transit-route/instruction.md +113 -0
  212. package/skills/korean-transit-route/skill.json +10 -0
  213. package/skills/kosis-stats/instruction.md +232 -0
  214. package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
  215. package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
  216. package/skills/kosis-stats/skill.json +9 -0
  217. package/skills/kr-whois-lookup/instruction.md +107 -0
  218. package/skills/kr-whois-lookup/skill.json +10 -0
  219. package/skills/kstartup-search/instruction.md +186 -0
  220. package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
  221. package/skills/kstartup-search/skill.json +11 -0
  222. package/skills/ktx-booking/instruction.md +244 -0
  223. package/skills/ktx-booking/skill.json +10 -0
  224. package/skills/lck-analytics/instruction.md +192 -0
  225. package/skills/lck-analytics/scripts/_lib.js +103 -0
  226. package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
  227. package/skills/lck-analytics/scripts/build-match-report.js +44 -0
  228. package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
  229. package/skills/lck-analytics/skill.json +9 -0
  230. package/skills/lh-notice-search/instruction.md +206 -0
  231. package/skills/lh-notice-search/skill.json +10 -0
  232. package/skills/library-book-search/instruction.md +139 -0
  233. package/skills/library-book-search/skill.json +9 -0
  234. package/skills/local-election-candidate-search/instruction.md +77 -0
  235. package/skills/local-election-candidate-search/skill.json +8 -0
  236. package/skills/localdata-business-status/instruction.md +64 -0
  237. package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
  238. package/skills/localdata-business-status/skill.json +8 -0
  239. package/skills/lotto-results/instruction.md +80 -0
  240. package/skills/lotto-results/skill.json +8 -0
  241. package/skills/lovebug-report/instruction.md +185 -0
  242. package/skills/lovebug-report/skill.json +9 -0
  243. package/skills/market-kurly-search/instruction.md +125 -0
  244. package/skills/market-kurly-search/skill.json +8 -0
  245. package/skills/mfds-drug-safety/instruction.md +86 -0
  246. package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
  247. package/skills/mfds-drug-safety/skill.json +10 -0
  248. package/skills/mfds-food-safety/instruction.md +126 -0
  249. package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
  250. package/skills/mfds-food-safety/skill.json +10 -0
  251. package/skills/myrealtrip-search/instruction.md +239 -0
  252. package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
  253. package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
  254. package/skills/myrealtrip-search/skill.json +9 -0
  255. package/skills/naming-house/instruction.md +146 -0
  256. package/skills/naming-house/skill.json +8 -0
  257. package/skills/national-pension-workplace/instruction.md +64 -0
  258. package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
  259. package/skills/national-pension-workplace/skill.json +10 -0
  260. package/skills/naver-ad-performance/instruction.md +108 -0
  261. package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
  262. package/skills/naver-ad-performance/skill.json +9 -0
  263. package/skills/naver-blog-research/instruction.md +128 -0
  264. package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
  265. package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
  266. package/skills/naver-blog-research/scripts/naver_read.py +256 -0
  267. package/skills/naver-blog-research/scripts/naver_search.py +192 -0
  268. package/skills/naver-blog-research/skill.json +8 -0
  269. package/skills/naver-news-search/instruction.md +103 -0
  270. package/skills/naver-news-search/skill.json +10 -0
  271. package/skills/naver-shopping-search/instruction.md +94 -0
  272. package/skills/naver-shopping-search/skill.json +11 -0
  273. package/skills/nhis-care-checkup-search/instruction.md +116 -0
  274. package/skills/nhis-care-checkup-search/skill.json +10 -0
  275. package/skills/nts-business-registration/instruction.md +115 -0
  276. package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
  277. package/skills/nts-business-registration/skill.json +10 -0
  278. package/skills/nts-tax-delinquency/instruction.md +54 -0
  279. package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
  280. package/skills/nts-tax-delinquency/skill.json +8 -0
  281. package/skills/ohou-today-deal/instruction.md +182 -0
  282. package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
  283. package/skills/ohou-today-deal/skill.json +9 -0
  284. package/skills/olive-young-search/instruction.md +154 -0
  285. package/skills/olive-young-search/skill.json +8 -0
  286. package/skills/parking-lot-search/instruction.md +96 -0
  287. package/skills/parking-lot-search/skill.json +9 -0
  288. package/skills/popbill/instruction.md +146 -0
  289. package/skills/popbill/scripts/popbill_cli.py +284 -0
  290. package/skills/popbill/scripts/popbill_registry.py +54 -0
  291. package/skills/popbill/scripts/popbill_safety.py +37 -0
  292. package/skills/popbill/scripts/popbill_templates.py +71 -0
  293. package/skills/popbill/skill.json +9 -0
  294. package/skills/public-restroom-nearby/instruction.md +89 -0
  295. package/skills/public-restroom-nearby/skill.json +9 -0
  296. package/skills/real-estate-search/instruction.md +172 -0
  297. package/skills/real-estate-search/skill.json +10 -0
  298. package/skills/rhwp-advanced/instruction.md +145 -0
  299. package/skills/rhwp-advanced/skill.json +8 -0
  300. package/skills/rhwp-edit/instruction.md +153 -0
  301. package/skills/rhwp-edit/skill.json +8 -0
  302. package/skills/s2b-notice-search/instruction.md +66 -0
  303. package/skills/s2b-notice-search/skill.json +9 -0
  304. package/skills/saju-fortune/instruction.md +177 -0
  305. package/skills/saju-fortune/skill.json +8 -0
  306. package/skills/saramin-talent-search/instruction.md +119 -0
  307. package/skills/saramin-talent-search/skill.json +9 -0
  308. package/skills/seoul-bike/instruction.md +83 -0
  309. package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
  310. package/skills/seoul-bike/skill.json +10 -0
  311. package/skills/seoul-density/instruction.md +109 -0
  312. package/skills/seoul-density/scripts/seoul_density.py +271 -0
  313. package/skills/seoul-density/skill.json +10 -0
  314. package/skills/seoul-subway-arrival/instruction.md +85 -0
  315. package/skills/seoul-subway-arrival/skill.json +9 -0
  316. package/skills/sh-notice-search/instruction.md +150 -0
  317. package/skills/sh-notice-search/skill.json +9 -0
  318. package/skills/srt-booking/instruction.md +181 -0
  319. package/skills/srt-booking/scripts/srt_booking.py +272 -0
  320. package/skills/srt-booking/scripts/srt_seats.py +156 -0
  321. package/skills/srt-booking/skill.json +20 -0
  322. package/skills/subway-lost-property/instruction.md +93 -0
  323. package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
  324. package/skills/subway-lost-property/skill.json +8 -0
  325. package/skills/ticket-availability/instruction.md +175 -0
  326. package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
  327. package/skills/ticket-availability/skill.json +9 -0
  328. package/skills/toss-securities/instruction.md +116 -0
  329. package/skills/toss-securities/skill.json +9 -0
  330. package/skills/used-car-price-search/instruction.md +109 -0
  331. package/skills/used-car-price-search/skill.json +8 -0
  332. package/skills/yebigun-training/instruction.md +177 -0
  333. package/skills/yebigun-training/skill.json +9 -0
  334. package/skills/zipcode-search/instruction.md +159 -0
  335. package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
  336. package/skills/zipcode-search/skill.json +8 -0
  337. package/src/assemble.js +134 -0
  338. package/src/detect.js +18 -0
  339. package/templates/action-account.md +4 -0
  340. package/templates/action-booking.md +5 -0
  341. package/templates/action-commerce.md +5 -0
  342. package/templates/action-communication.md +5 -0
  343. package/templates/action-submission.md +5 -0
  344. package/templates/browser.md +4 -0
  345. package/templates/core.md +5 -0
  346. package/templates/hard-boundary.md +3 -0
  347. package/templates/local.md +2 -0
  348. package/templates/lookup.md +2 -0
  349. package/templates/proxy.md +2 -0
  350. package/templates/vault.md +4 -0
@@ -0,0 +1,354 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only Danawa search/price comparison helper for Hermes.
3
+
4
+ Usage:
5
+ python scripts/danawa_search.py search "에어팟 프로 2세대" --limit 8
6
+ python scripts/danawa_search.py offers 28208783 --limit 10
7
+ python scripts/danawa_search.py compare "에어팟 프로 2세대" --limit 5 --offers 5
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import re
14
+ import sys
15
+ import time
16
+ import urllib.parse
17
+ import urllib.request
18
+ from html import unescape
19
+ from typing import Any, Dict, List, Optional
20
+
21
+ try:
22
+ from bs4 import BeautifulSoup
23
+ except ImportError as exc: # pragma: no cover - environment guard
24
+ raise SystemExit("beautifulsoup4 is required: python -m pip install beautifulsoup4") from exc
25
+
26
+ UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/121 Safari/537.36"
27
+
28
+
29
+ def fetch(url: str, *, method: str = "GET", data: Optional[dict] = None, referer: Optional[str] = None) -> str:
30
+ headers = {
31
+ "User-Agent": UA,
32
+ "Accept-Language": "ko-KR,ko;q=0.9,en;q=0.8",
33
+ }
34
+ body = None
35
+ if data is not None:
36
+ body = urllib.parse.urlencode(data).encode("utf-8")
37
+ headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
38
+ headers["X-Requested-With"] = "XMLHttpRequest"
39
+ if referer:
40
+ headers["Referer"] = referer
41
+ req = urllib.request.Request(url, data=body, headers=headers, method=method)
42
+ with urllib.request.urlopen(req, timeout=25) as resp:
43
+ return resp.read().decode("utf-8", "replace")
44
+
45
+
46
+ def soup_for(html: str) -> BeautifulSoup:
47
+ return BeautifulSoup(html, "html.parser")
48
+
49
+
50
+ def clean_text(s: Optional[str]) -> Optional[str]:
51
+ if s is None:
52
+ return None
53
+ return " ".join(unescape(s).split())
54
+
55
+
56
+ def parse_int(s: Optional[str]) -> Optional[int]:
57
+ if not s:
58
+ return None
59
+ digits = re.sub(r"\D", "", s)
60
+ return int(digits) if digits else None
61
+
62
+
63
+ def abs_url(url: Optional[str]) -> Optional[str]:
64
+ if not url:
65
+ return None
66
+ if url.startswith("//"):
67
+ return "https:" + url
68
+ if url.startswith("/"):
69
+ return "https://prod.danawa.com" + url
70
+ return url
71
+
72
+
73
+ def search(query: str, limit: int = 10) -> Dict[str, Any]:
74
+ url = "https://search.danawa.com/dsearch.php?query=" + urllib.parse.quote(query)
75
+ html = fetch(url)
76
+ soup = soup_for(html)
77
+ items: List[Dict[str, Any]] = []
78
+ for li in soup.select("li.prod_item"):
79
+ pid = (li.get("id") or "").replace("productItem", "") or None
80
+ name_el = li.select_one(".prod_name a") or li.select_one("p.prod_name a") or li.select_one('a[name="productName"]')
81
+ if not name_el:
82
+ continue
83
+ name = clean_text(name_el.get_text(" ", strip=True))
84
+ link = abs_url(name_el.get("href"))
85
+ min_input = li.select_one(f"#min_price_{pid}") if pid else None
86
+ price = parse_int(min_input.get("value") if min_input else None)
87
+ if price is None:
88
+ price_el = li.select_one(".price_sect strong") or li.select_one(".prod_pricelist strong")
89
+ price = parse_int(price_el.get_text() if price_el else None)
90
+ img = li.select_one(".thumb_image img")
91
+ image = abs_url((img.get("data-original") or img.get("src")) if img else None)
92
+ mall_el = li.select_one(".prod_pricelist .memory_sect") or li.select_one(".meta_item")
93
+ spec = " / ".join(clean_text(e.get_text(" ", strip=True)) or "" for e in li.select(".spec_list a, .spec_list span")[:10])
94
+ items.append(
95
+ {
96
+ "pcode": pid,
97
+ "title": name,
98
+ "price": price,
99
+ "price_text": f"{price:,}원" if price else None,
100
+ "mall_text": clean_text(mall_el.get_text(" ", strip=True)) if mall_el else None,
101
+ "url": link,
102
+ "image_url": image,
103
+ "spec": spec[:300] if spec else None,
104
+ }
105
+ )
106
+ if len(items) >= limit:
107
+ break
108
+ return {"query": query, "source_url": url, "count": len(items), "items": items, "meta": {"extraction": "danawa-search-html", "ts": int(time.time())}}
109
+
110
+
111
+ def js_value(html: str, key: str) -> str:
112
+ patterns = [
113
+ rf"{re.escape(key)}\s*:\s*\"([^\"]*)\"",
114
+ rf"{re.escape(key)}\s*:\s*'([^']*)'",
115
+ rf"{re.escape(key)}\s*:\s*([0-9]+)",
116
+ ]
117
+ for pat in patterns:
118
+ m = re.search(pat, html)
119
+ if m:
120
+ raw = m.group(1)
121
+ if "\\u" in raw or "\\/" in raw:
122
+ try:
123
+ return json.loads('"' + raw.replace('"', '\\"') + '"')
124
+ except Exception:
125
+ return raw.replace("\\/", "/")
126
+ return raw
127
+ return ""
128
+
129
+
130
+ def product_meta(pcode: str) -> Dict[str, str]:
131
+ url = f"https://prod.danawa.com/info/?pcode={urllib.parse.quote(str(pcode))}"
132
+ html = fetch(url)
133
+ meta = {
134
+ "pcode": str(pcode),
135
+ "source_url": url,
136
+ "cate1": js_value(html, "nCategoryCode1"),
137
+ "cate2": js_value(html, "nCategoryCode2"),
138
+ "cate3": js_value(html, "nCategoryCode3"),
139
+ "cate4": js_value(html, "nCategoryCode4") or "0",
140
+ "UICategoryCode": js_value(html, "nCategoryCode"),
141
+ "powerLinkKeyword": js_value(html, "powerLinkKeyword"),
142
+ "minPrice": js_value(html, "nMinPrice"),
143
+ "keyword": js_value(html, "sKeyword"),
144
+ "NaPm": js_value(html, "sNaPm"),
145
+ "sProductFullName": js_value(html, "sProductName"),
146
+ "makerCode": js_value(html, "makerCode"),
147
+ "makerName": js_value(html, "makerName"),
148
+ }
149
+ title = soup_for(html).select_one(".prod_tit .title")
150
+ if title:
151
+ meta["sProductFullName"] = clean_text(title.get_text(" ", strip=True)) or meta["sProductFullName"]
152
+ return meta
153
+
154
+
155
+ def offers(pcode: str, limit: int = 20, include_shipping: bool = False) -> Dict[str, Any]:
156
+ meta = product_meta(pcode)
157
+ post_price = "Y" if include_shipping else "N"
158
+ data = {
159
+ "pcode": meta["pcode"],
160
+ "cate1": meta.get("cate1", ""),
161
+ "cate2": meta.get("cate2", ""),
162
+ "cate3": meta.get("cate3", ""),
163
+ "cate4": meta.get("cate4", "0"),
164
+ "UICategoryCode": meta.get("UICategoryCode", "0"),
165
+ "powerLinkKeyword": meta.get("powerLinkKeyword", ""),
166
+ "minPrice": meta.get("minPrice", ""),
167
+ "keyword": meta.get("keyword", ""),
168
+ "NaPm": meta.get("NaPm", ""),
169
+ "bDeliveryLeftRightYN": "N",
170
+ "bQuickPostSortYN": "N",
171
+ "sSortType": "minPrice",
172
+ "sProductFullName": meta.get("sProductFullName", ""),
173
+ "bPostPriceYN": post_price,
174
+ "bBadgeDefaultYN": "N",
175
+ "bWarrantyDefaultYN": "N",
176
+ "nOpenMarketMoreCount": "30",
177
+ "nAffiliateMoreCount": "30",
178
+ "nOverseasShoppingMoreCount": "30",
179
+ "nGeneralAffiliateMoreCount": "3",
180
+ "sRelationMenuType": "",
181
+ "sRelationType": "",
182
+ "bCoupangSortYN": "N",
183
+ "makerCode": meta.get("makerCode", ""),
184
+ "makerName": meta.get("makerName", ""),
185
+ }
186
+ html = fetch("https://prod.danawa.com/info/ajax/getAllPriceCompareMallList.ajax.php", method="POST", data=data, referer=meta["source_url"])
187
+ soup = soup_for(html)
188
+ rows: List[Dict[str, Any]] = []
189
+ for div in soup.select(".diff_item"):
190
+ mall_img = div.select_one(".d_mall img")
191
+ mall = mall_img.get("alt") if mall_img else None
192
+ price_el = div.select_one("em.prc_c") or div.select_one("em.prc_t")
193
+ price = parse_int(price_el.get_text() if price_el else None)
194
+ if not mall or price is None:
195
+ continue
196
+ ship_el = div.select_one(".ship") or div.select_one(".stxt")
197
+ shipping = clean_text(ship_el.get_text(" ", strip=True)) if ship_el else None
198
+ shipping_fee = 0 if shipping and "무료" in shipping else parse_int(shipping)
199
+ card_line = div.select_one(".card_line")
200
+ card_price_el = card_line.select_one(".card_prc") if card_line else None
201
+ card_name_el = card_line.select_one(".txt") if card_line else None
202
+ card_price = parse_int(card_price_el.get_text() if card_price_el else None)
203
+ installment_el = div.select_one(".btn_foi .txt")
204
+ installment_detail_el = div.select_one(".foi_layer .ly_cont")
205
+ link = div.select_one("a.priceCompareBuyLink")
206
+ # 결제조건 ico만 캡처. 다른 ico(빠른배송, 안내, 상품리뷰 등)는 노이즈라 제외.
207
+ # 클래스만 있고 텍스트가 비어 있는 아이콘도 row 라벨이 누락되지 않도록
208
+ # 같은 정규화 테이블에서 표시 라벨/타입/boolean 필드를 모두 파생한다.
209
+ payment_condition_labels = {
210
+ "cash": "현금",
211
+ "point": "포인트",
212
+ "coupon": "쿠폰",
213
+ "card": "카드",
214
+ "discount": "할인",
215
+ "membership": "멤버십",
216
+ }
217
+ payment_condition_types: List[str] = []
218
+ payment_badges: List[str] = []
219
+ for el in div.select(".prc_line .ico, .d_dsc .ico"):
220
+ classes = set(el.get("class") or [])
221
+ text = clean_text(el.get_text(" ", strip=True)) or ""
222
+ matched_types = [
223
+ kind
224
+ for kind, label in payment_condition_labels.items()
225
+ if kind in classes or label in text
226
+ ]
227
+ if not matched_types:
228
+ continue
229
+ for kind in matched_types:
230
+ if kind not in payment_condition_types:
231
+ payment_condition_types.append(kind)
232
+ label = payment_condition_labels[kind]
233
+ if label not in payment_badges:
234
+ payment_badges.append(label)
235
+ cash_only = "cash" in payment_condition_types
236
+ point_only = "point" in payment_condition_types
237
+ coupon_only = "coupon" in payment_condition_types
238
+ card_only_badge = "card" in payment_condition_types
239
+ discount_badge = "discount" in payment_condition_types
240
+ membership_badge = "membership" in payment_condition_types
241
+ payment_condition_label = ", ".join(payment_badges) or None
242
+ is_conditional_price = bool(payment_condition_types)
243
+ rows.append(
244
+ {
245
+ "mall": clean_text(mall),
246
+ "price": price,
247
+ "price_text": f"{price:,}원",
248
+ "shipping": shipping,
249
+ "is_free_shipping": bool(shipping and "무료" in shipping),
250
+ "shipping_fee": shipping_fee,
251
+ "total_price": price + (shipping_fee or 0),
252
+ "total_price_text": f"{price + (shipping_fee or 0):,}원",
253
+ "card_price": card_price,
254
+ "card_price_text": f"{card_price:,}원" if card_price else None,
255
+ "card_name": clean_text(card_name_el.get_text(" ", strip=True)) if card_name_el else None,
256
+ "card_discount": (price - card_price) if card_price else None,
257
+ "card_discount_text": f"{price - card_price:,}원" if card_price else None,
258
+ "installment": clean_text(installment_el.get_text(" ", strip=True)) if installment_el else None,
259
+ "installment_detail": clean_text(installment_detail_el.get_text(" ", strip=True)) if installment_detail_el else None,
260
+ "payment_badges": payment_badges,
261
+ "cash_only": cash_only,
262
+ "point_only": point_only,
263
+ "coupon_only": coupon_only,
264
+ "card_only_badge": card_only_badge,
265
+ "discount_badge": discount_badge,
266
+ "membership_badge": membership_badge,
267
+ "payment_condition_types": payment_condition_types,
268
+ "payment_condition_label": payment_condition_label,
269
+ "is_conditional_price": is_conditional_price,
270
+ "url": abs_url(link.get("href") if link else None),
271
+ }
272
+ )
273
+ # 정렬은 단순히 배송비 포함 실구매가 오름차순. 결제조건(현금/쿠폰/포인트/특정카드)은
274
+ # 분리 그룹으로 묶지 않고 row 단위로 payment_badges / payment_condition_types /
275
+ # payment_condition_label 및 세부 boolean 플래그로 노출한다. 호출자(또는 사용자)는 자기 결제수단에 맞춰 판단한다.
276
+ rows.sort(key=lambda row: (
277
+ row["total_price"] is None,
278
+ row["total_price"] or row["price"],
279
+ row["price"],
280
+ row["mall"] or "",
281
+ ))
282
+ rows = rows[:limit]
283
+ return {
284
+ "pcode": str(pcode),
285
+ "title": meta.get("sProductFullName"),
286
+ "source_url": meta["source_url"],
287
+ "count": len(rows),
288
+ "normal_count": sum(1 for r in rows if not r.get("is_conditional_price")),
289
+ "conditional_count": sum(1 for r in rows if r.get("is_conditional_price")),
290
+ "offers": rows,
291
+ "meta": {
292
+ "extraction": "danawa-price-ajax",
293
+ "include_shipping": include_shipping,
294
+ "sort": "total_price",
295
+ "ts": int(time.time()),
296
+ },
297
+ }
298
+
299
+
300
+ def compare(query: str, limit: int, offer_limit: int) -> Dict[str, Any]:
301
+ result = search(query, limit=limit)
302
+ enriched = []
303
+ for item in result["items"]:
304
+ row = dict(item)
305
+ if item.get("pcode"):
306
+ try:
307
+ off = offers(item["pcode"], limit=offer_limit)
308
+ row["offers"] = off.get("offers", [])
309
+ except Exception as exc: # keep search result usable if a detail call fails
310
+ row["offers_error"] = f"{type(exc).__name__}: {exc}"
311
+ enriched.append(row)
312
+ result["items"] = enriched
313
+ result["meta"]["detail_extraction"] = "best-effort"
314
+ return result
315
+
316
+
317
+ def positive_int(raw: str) -> int:
318
+ value = int(raw)
319
+ if value < 1:
320
+ raise argparse.ArgumentTypeError("must be >= 1")
321
+ return value
322
+
323
+
324
+ def main() -> int:
325
+ ap = argparse.ArgumentParser()
326
+ sub = ap.add_subparsers(dest="cmd", required=True)
327
+ s = sub.add_parser("search")
328
+ s.add_argument("query")
329
+ s.add_argument("--limit", type=positive_int, default=10)
330
+ o = sub.add_parser("offers")
331
+ o.add_argument("pcode")
332
+ o.add_argument("--limit", type=positive_int, default=20)
333
+ o.add_argument("--include-shipping", action="store_true")
334
+ c = sub.add_parser("compare")
335
+ c.add_argument("query")
336
+ c.add_argument("--limit", type=positive_int, default=5)
337
+ c.add_argument("--offers", type=positive_int, default=5)
338
+ args = ap.parse_args()
339
+ try:
340
+ if args.cmd == "search":
341
+ out = search(args.query, args.limit)
342
+ elif args.cmd == "offers":
343
+ out = offers(args.pcode, args.limit, args.include_shipping)
344
+ else:
345
+ out = compare(args.query, args.limit, args.offers)
346
+ print(json.dumps(out, ensure_ascii=False, indent=2))
347
+ return 0
348
+ except Exception as exc:
349
+ print(json.dumps({"error": f"{type(exc).__name__}: {exc}"}, ensure_ascii=False), file=sys.stderr)
350
+ return 2
351
+
352
+
353
+ if __name__ == "__main__":
354
+ raise SystemExit(main())
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "danawa-price-search",
3
+ "description": "다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
4
+ "profiles": [
5
+ "action:commerce"
6
+ ],
7
+ "frontmatter": "name: danawa-price-search\ndescription: 다나와 공개 검색/가격비교 표면으로 상품 후보를 찾고, 쇼핑몰별 최저가·배송비 포함 실구매가·카드 할인가·무이자 할부 정보를 보수적으로 비교한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: retail\n locale: ko-KR\n phase: v1"
8
+ }