Keep parcel-tracking guidance aligned with the verified carrier flows

The delivery-tracking skill already existed on feature/#4, but the
CJ example still documented a naive Python HTTP path. This follow-up
switches the documented CJ flow to the verified curl+cookie+_csrf
sequence, then tightens the surrounding docs so the shipped scope and
future carrier-extension story stay consistent.

Constraint: CJ tracking-detail rejects naive direct HTTP calls without preserved session state
Constraint: Must keep the existing feature/#4 PR against dev accurate without widening scope
Rejected: Keep the urllib-only CJ example | live smoke test returned 403 and contradicted the docs
Confidence: high
Scope-risk: narrow
Directive: Re-run live carrier smoke tests before changing documented transport steps for CJ or 우체국
Tested: npm run ci; live CJ curl+cookie+_csrf verification for 1234567890; live 우체국 curl verification for 1234567890123
Not-tested: Real non-placeholder customer waybills
This commit is contained in:
Jeffrey (Dongkyu) Kim 2026-03-27 00:35:54 +09:00
commit 3c6a7d2e51
5 changed files with 71 additions and 48 deletions

View file

@ -24,7 +24,7 @@ Claude code, codex, opencode 등 각종 코딩 에이전트 지원합니다.
| 로또 당첨 확인 | 최신 회차, 특정 회차, 번호 대조 | 불필요 | [로또 결과 가이드](docs/features/lotto-results.md) |
| HWP 문서 처리 | `.hwp` → JSON/Markdown/HTML 변환, 이미지 추출, 배치 처리, Windows 직접 제어 선택 | 불필요 | [HWP 문서 처리 가이드](docs/features/hwp.md) |
| 우편번호 검색 | 주소 키워드로 공식 우체국 우편번호 조회 | 불필요 | [우편번호 검색 가이드](docs/features/zipcode-search.md) |
| 택배 배송조회 | CJ대한통운·우체국 공식 표면으로 배송 상태 조회 | 불필요 | [택배 배송조회 가이드](docs/features/delivery-tracking.md) |
| 택배 배송조회 | CJ대한통운·우체국 공식 표면으로 배송 상태 조회하고, carrier adapter 규칙으로 추가 택배사 확장을 준비 | 불필요 | [택배 배송조회 가이드](docs/features/delivery-tracking.md) |
> 참고: **KTX 예매는 현재 작동하지 않습니다.**

View file

@ -82,36 +82,40 @@ CJ대한통운과 우체국 공식 조회 표면을 사용해 송장 번호로
- 상세 endpoint: `https://www.cjlogistics.com/ko/tool/parcel/tracking-detail`
- 필수 필드: `_csrf`, `paramInvcNo`
기본 예시는 Python 표준 라이브러리만 사용한다.
기본 예시는 `curl``_csrf` 와 cookie를 유지하고, Python은 JSON 정리에만 쓴다.
```bash
python3 - <<'PY'
import json
tmp_body="$(mktemp)"
tmp_cookie="$(mktemp)"
tmp_json="$(mktemp)"
invoice="1234567890" # 공식 페이지 placeholder 성격의 smoke-test 값
curl -sS -L -c "$tmp_cookie" \
"https://www.cjlogistics.com/ko/tool/parcel/tracking" \
-o "$tmp_body"
csrf="$(python3 - <<'PY' "$tmp_body"
import re
import urllib.parse
import urllib.request
import sys
invoice = "1234567890" # 공식 페이지 placeholder 성격의 smoke-test 값
landing = urllib.request.urlopen(
"https://www.cjlogistics.com/ko/tool/parcel/tracking",
timeout=20,
).read().decode("utf-8", "ignore")
csrf = re.search(r'name="_csrf" value="([^"]+)"', landing).group(1)
text = open(sys.argv[1], encoding="utf-8", errors="ignore").read()
print(re.search(r'name="_csrf" value="([^"]+)"', text).group(1))
PY
)"
payload = urllib.parse.urlencode({
"_csrf": csrf,
"paramInvcNo": invoice,
}).encode()
curl -sS -L -b "$tmp_cookie" \
-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
--data-urlencode "_csrf=$csrf" \
--data-urlencode "paramInvcNo=$invoice" \
"https://www.cjlogistics.com/ko/tool/parcel/tracking-detail" \
-o "$tmp_json"
request = urllib.request.Request(
"https://www.cjlogistics.com/ko/tool/parcel/tracking-detail",
data=payload,
headers={"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
)
response = urllib.request.urlopen(request, timeout=20)
data = json.loads(response.read().decode("utf-8"))
python3 - <<'PY' "$tmp_json"
import json
import sys
events = data["parcelDetailResultMap"]["resultList"]
payload = json.load(open(sys.argv[1], encoding="utf-8"))
events = payload["parcelDetailResultMap"]["resultList"]
if not events:
raise SystemExit("조회 결과가 없습니다.")
@ -128,7 +132,7 @@ status_map = {
latest = events[-1]
print(json.dumps({
"carrier": "cj",
"invoice": invoice,
"invoice": payload["parcelDetailResultMap"]["paramInvcNo"],
"status_code": latest.get("crgSt"),
"status": status_map.get(latest.get("crgSt"), latest.get("scanNm") or "알수없음"),
"timestamp": latest.get("dTime"),
@ -137,6 +141,8 @@ print(json.dumps({
"event_count": len(events),
}, ensure_ascii=False, indent=2))
PY
rm -f "$tmp_body" "$tmp_cookie" "$tmp_json"
```
추가 smoke test 로는 `000000000000` 도 사용할 수 있다.

View file

@ -37,28 +37,38 @@
- 파라미터: `_csrf`, `paramInvcNo`
```bash
python3 - <<'PY'
import json
tmp_body="$(mktemp)"
tmp_cookie="$(mktemp)"
tmp_json="$(mktemp)"
invoice="1234567890"
curl -sS -L -c "$tmp_cookie" \
"https://www.cjlogistics.com/ko/tool/parcel/tracking" \
-o "$tmp_body"
csrf="$(python3 - <<'PY' "$tmp_body"
import re
import urllib.parse
import urllib.request
import sys
text = open(sys.argv[1], encoding="utf-8", errors="ignore").read()
print(re.search(r'name="_csrf" value="([^"]+)"', text).group(1))
PY
)"
invoice = "1234567890"
landing = urllib.request.urlopen(
"https://www.cjlogistics.com/ko/tool/parcel/tracking",
timeout=20,
).read().decode("utf-8", "ignore")
csrf = re.search(r'name="_csrf" value="([^"]+)"', landing).group(1)
curl -sS -L -b "$tmp_cookie" \
-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" \
--data-urlencode "_csrf=$csrf" \
--data-urlencode "paramInvcNo=$invoice" \
"https://www.cjlogistics.com/ko/tool/parcel/tracking-detail" \
-o "$tmp_json"
body = urllib.parse.urlencode({"_csrf": csrf, "paramInvcNo": invoice}).encode()
request = urllib.request.Request(
"https://www.cjlogistics.com/ko/tool/parcel/tracking-detail",
data=body,
headers={"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"},
)
payload = json.loads(urllib.request.urlopen(request, timeout=20).read().decode("utf-8"))
python3 - <<'PY' "$tmp_json"
import json
import sys
payload = json.load(open(sys.argv[1], encoding="utf-8"))
print(json.dumps(payload["parcelDetailResultMap"]["resultList"][-1], ensure_ascii=False, indent=2))
PY
rm -f "$tmp_body" "$tmp_cookie" "$tmp_json"
```
CJ는 JSON 응답이므로 `parcelDetailResultMap.resultList` 를 기준으로 상태를 읽는 편이 가장 안정적이다.

View file

@ -10,7 +10,7 @@
- 로또 당첨번호
- 서울 지하철 도착 정보
- 우편번호 검색
- 택배 배송조회 스킬 출시
- 택배 배송조회 스킬 출시 (CJ대한통운 / 우체국)
## v1.5 candidates
@ -56,6 +56,11 @@
- 장점: 사전 접수, 주변 병원 찾기, 지금 문 연 병원 찾기 흐름이 명확하다
- 이유: 실사용 가치가 높고 특히 부모층 체감이 크다
#### 택배 예약 / 추가 택배사 확장
- 장점: 기본 배송조회는 `delivery-tracking` 으로 선출시했고, 다음 단계는 예약/반품/추가 택배사 확장으로 자연스럽게 이어진다
- 이유: 한국 생활에서 반복 빈도가 높은 작업이라 조회 다음 액션까지 묶을 가치가 크다
#### 미세먼지/황사/대기질 알림
- 장점: 오늘/내일/모레 대기정보와 예보, 하루 4회 수준의 예보 갱신 같은 한국형 수요에 잘 맞는다

View file

@ -15,6 +15,8 @@
- 서울특별시 지하철 실시간 도착정보: https://www.data.go.kr/data/15058052/openapi.do
- 우체국 도로명주소 검색: https://parcel.epost.go.kr/parcel/comm/zipcode/comm_newzipcd_list.jsp
- CJ대한통운 배송조회: https://www.cjlogistics.com/ko/tool/parcel/tracking
- CJ대한통운 배송상세 JSON: https://www.cjlogistics.com/ko/tool/parcel/tracking-detail
- 우체국 배송조회: https://service.epost.go.kr/trace.RetrieveRegiPrclDeliv.postal?sid1=
- 우체국 배송상세 HTML: https://service.epost.go.kr/trace.RetrieveDomRigiTraceList.comm
- SOPS docs: https://getsops.io/docs/
- age: https://github.com/FiloSottile/age