AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Snu Tennis Reserve

skill-jinukmoon-agent-skills-snu-tennis-reserve · by JinukMoon

서울대 스포츠진흥원 본부테니스코트(잔디코트) 예약을 월요일 9시 30분 오픈 순간에 자동으로 잡는다. 사용자가 "테니스 예약", "코트 예약 잡아줘", "월요일 9시 반 예약", "테니스 매크로 돌려줘", "예약 걸어줘", "예약 취소", "/snu-tennis-reserve" 라고 하거나, 월요일 아침에 테니스/코트 예약 관련해서 말을 걸면 반드시 이 skill을 쓴다. 사용자가 원하는 날짜·시간을 말로 하면 우선순위 목록(targets.txt)으로 바꿔서 9:30:00 정각에 밀리초 단위로 발사하고, 목록의 항목들을 위에서부터 전부 예약한다. 예약 조회·취소도 처리한다.

No reviews yet
0 installs
28 views
0.0% view→install

Install

$ agentstack add skill-jinukmoon-agent-skills-snu-tennis-reserve

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-jinukmoon-agent-skills-snu-tennis-reserve)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Snu Tennis Reserve? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Tennis Court Reservation (SNU 스포츠진흥원)

월요일 오전 9시 30분에 열리는 본부테니스코트 예약을, 오픈 순간 자동으로 낚아채는 skill. 사용자는 말로 우선순위만 알려주면 되고, 나머지(timelist 고속 스캔, 폼 채우기, 멤버 인증, 동의 체크, 정각 발사, 코트 순회, 제출, 결과 검증)는 전부 자동이다.

스크립트 경로: 이 skill 폴더의 scripts/ (~/.claude/skills/snu-tennis-reserve/scripts/). 모든 명령은 이 디렉토리에서 실행한다. 세션 파일(state.json), 브라우저 프로필 (browser_profile/), 동반자 정보(members.local.json), 우선순위(targets.txt)도 전부 이 폴더 안에 생긴다 — skill 폴더 밖으로 나가는 개인정보는 없다.

첫 사용 설정 (필수 — 이 컴퓨터에서 처음일 때만)

skill을 처음 쓸 때, 아래 항목들을 사용자에게 직접 물어봐서 설정한다. 한 번 설정하면 파일로 남으므로 다음부터는 묻지 않는다.

  1. Python 환경 확인 — Playwright가 설치된 Python이 필요하다:

``bash python -c "import playwright" 2>/dev/null && echo OK || echo MISSING ``

  • MISSING이면 사용자에게 알리고 설치를 진행한다:

pip install playwright && playwright install chromium

  • 사용자가 특정 conda env를 쓰면 그 env의 python 경로를 기억해두고 이후 모든 명령에 사용.
  1. 동반자 정보scripts/members.local.json 이 없으면 묻는다:

"이 컴퓨터에서 처음이네요. 같이 예약할 동반자 한 분의 이름 / 학번 / 전화번호 / 학과를 알려주세요. (본인 정보는 로그인으로 자동 입력돼요. 이 정보는 이 컴퓨터에만 저장되고 공유되지 않아요.)" 받은 정보로 파일을 만든다: ``json [{"name": "홍길동", "userno": "2024-12345", "mobile": "010-1234-5678", "dept": "재료공학부"}] `` 동반자가 여러 명이면 배열에 객체를 추가. 최소 동반자 1명(본인 포함 2명)이어야 예약 가능. 저장 후 "저장했어요. 다음부터는 안 물어봐요."라고 알린다.

핵심 제약 (먼저 이해할 것)

  • 세션 수명 ~50분: 로그인은 발사 직전(9:30 기준 20분 이내)에 해야 한다. 일찍 하면 만료됨.
  • 재로그인은 휴대폰 모바일 인증(MFA) 필요: 완전 무인 로그인은 불가능. 사용자가 직접 1회 인증.
  • 9:30 전에는 폼이 안 열림: 다음 주 날짜는 9:30:00이 되어야 유효. 그래서 정각 발사 + 서버오픈 폴링 구조다.
  • reCAPTCHA: 제출 직전 "로봇이 아닙니다"가 뜰 수 있음. 첫 1회만 사용자가 풀면, 이후엔 세션이 신뢰돼서 보통 자동 통과(0.5초)된다. → 발사는 반드시 헤드 모드(창 표시)로 하고, 사용자는 화면 앞에서 첫 캡챠를 대기.
  • ★ 1인 1일 1예약 규칙: 한 사람은 하루에 코트 1개만 예약 가능. 같은 날 여러 항목은 1개만 잡히고 나머진 "충돌"로 즉시 다음 우선순위로 넘어간다. → 멀티 예약은 서로 다른 날로 짜야 의미 있다.

예약 동작 규칙 (reserve.py 내부 — 사용자에게 설명할 때 참고)

  • 목록을 위에서부터 전부 예약한다. 우선순위 번호 = 처리 순서. 한 항목이 성공해도 멈추지 않고 다음으로 간다 (코트 하나당 1건).
  • 다음 우선순위로 넘어가는 경우는 딱 세 가지뿐: (a) 예약 확정 검증됨, (b) 그 날짜·시간 14개 코트 모두 매진, (c) 1일1예약 충돌. 그 외(빈 코트 있는데 실패)면 넘어가지 않고 멈춰서 알린다 (잘못된 건너뜀 방지).
  • 빈자리 탐지는 timelist JSON 고속 스캔 (코트당 ~0.4초). 무거운 폼은 빈 코트에만 로드.
  • 성공은 결과 페이지 리다이렉트로 검증된 것만 인정.

진행 흐름 (이 순서대로 사용자를 안내한다)

Step 0. 첫 사용 설정

위 "첫 사용 설정" 섹션 참고. scripts/members.local.json 존재 여부로 판단:

ls ~/.claude/skills/snu-tennis-reserve/scripts/members.local.json

있으면 건너뛴다.

Step 1. 시각 확인

지금이 월요일 9:30 직전인지 확인한다 (date 명령).

  • 9:10~9:30 사이면 정상. 바로 진행.
  • 너무 이르면(예: 9시 이전) 알려준다: "세션이 50분이면 만료돼서 9:10 이후에 로그인하는 게 안전해요. 지금 미리 준비만 해둘까요, 아니면 9:10에 다시 부르실래요?"
  • 이미 9:30이 지났으면 알려주고, 그래도 시도할지 묻는다(취소분이 풀렸을 수 있으니 --now로 즉시 시도 가능).

Step 2. 로그인 안내 — 사용자의 "했어" 신호로 진행

세션부터 확인한다:

cd ~/.claude/skills/snu-tennis-reserve/scripts && ./run.sh check
  • 이미 유효(OK)하면 로그인 생략하고 Step 3로.
  • 만료(FAIL)면 로그인 창을 background로 띄운다:

``bash ./run.sh login `` 그리고 사용자에게: "새로 뜬 창에서 모바일 인증으로 로그인해주세요. (평소 쓰는 브라우저 말고 이 창이어야 해요.) 다 되면 '했어'라고 말씀해주세요."

자동감지에 의존하지 말 것. 사용자가 직접 로그인하므로, 진행 신호는 사용자의 "했어 / 됐어 / 완료" 다. 단, 우리 브라우저(./run.sh login)로 로그인해야 우리 세션(state.json)에 저장된다 — 사용자가 "본인 평소 브라우저에서 이미 로그인했다"고 하면, 그건 우리 세션과 별개임을 알리고 반드시 띄워준 창에서 다시 로그인하게 한다.

  • 사용자가 "했어" 하면 ./run.sh check로 검증한다:
  • OK → Step 3.
  • 여전히 FAIL → 보통 (a) 띄워준 창이 아닌 다른 브라우저에서 했거나, (b) 인증이 덜 끝난 경우. 어느 쪽인지 짚어주고 그 창에서 마저 로그인하게 한 뒤 다시 "했어"를 기다린다.

(login 잡은 background로 계속 살아있으면서, 사용자가 로그인된 athletics 페이지에 도달하면 state.json을 저장한다. 그래서 "했어" 시점엔 보통 check가 OK가 된다.)

Step 3. 우선순위 물어보기

사용자에게 묻는다: "이번 주 뭐뭐 예약할까요? (날짜+시간으로, 위에서부터 순서대로 알려주세요. 예: 토 20-22, 일 19-21)"

이미 사용자가 메시지에서 우선순위를 말했으면 다시 묻지 말고 그대로 쓴다. 목록의 항목들을 전부 예약한다는 점, 그리고 같은 날 여러 개는 1개만 된다(1일1예약)는 점을 필요하면 알려준다.

Step 4. 말 → 24시간제로 해석하고 반드시 한 번 확인받기

사용자의 자연어를 우선순위 목록으로 옮긴다. 각 줄 형식은 요일 시작-종료(예: 토 20-22) 또는 날짜 직접 지정 MM-DD 시작-종료(예: 06-20 14-16). 같은 요일이 두 번 열리는 주(토/일)나 특정 날짜를 콕 집을 때는 날짜 형식을 쓴다. 변환 규칙은 아래 "시간 해석 규칙" 참고.

이 단계의 핵심은 확인 게이트다. 사용자의 말(특히 "저녁 8시", "8시부터" 같은 표현)은 12/24시간제나 종료시간이 애매할 수 있어서, 내가 이해한 걸 24시간제로 풀어서 보여주고 딱 한 번 확인을 받은 뒤에만 진행한다. 멋대로 발사하지 않는다.

확인 멘트는 이런 식으로 — 내가 어떻게 이해했는지 24시간 기준으로 명시하고, 진행 여부를 묻는다:

> "24시간 기준으로 이렇게 이해했어요: > 1순위 토 20-22시 (토요일 저녁 8시~10시) > 2순위 일 20-22시 > 이렇게 진행할까요?"

  • 사용자가 OK 하면 targets.txt를 그 목록으로 덮어쓰고 Step 5로.
  • 사용자가 고치면 (예: "아니 9시부터") 다시 해석해서 또 한 번 보여주고 확인. 확실해질 때까지 발사하지 않는다.
  • 해석이 모호한 항목이 하나라도 있으면, 추측해서 넘어가지 말고 그 항목을 콕 집어 되묻는다.

Step 5. 발사

헤드 모드로 9:30:00 정각 발사를 건다. 백그라운드로 실행하고 출력 로그를 따라가며 사용자에게 중계한다:

./run.sh reserve -- --fire 09:30:00
  • --fire 시각은 서버시간 기준이다. 스크립트가 athletics 서버의 HTTP Date로 로컬-서버 시계 오프셋을 측정해 보정하고, 발사 5초 전부터 카운트다운하다가 서버가 09:30:00 되는 순간 발사한다. (로컬 시계가 몇 초 빠르거나 느려도 정확)
  • 발사 시각이 이미 지났으면 --now로 바꿔 즉시 시도.
  • 실행 후 사용자에게: "걸었어요. 9:30:00에 자동 발사됩니다. reCAPTCHA 이미지 챌린지가 뜨면 창에서 풀어주세요."
  • 로그를 지켜보다가:
  • >>> 이미지 챌린지가 떴다면... → 사용자에게 지금 창에서 reCAPTCHA 풀어달라고 즉시 알림. (첫 1회만, 이후 자동 통과)
  • ✅ 예약 성공(검증됨) → 각 건 성공을 전달. 목록이 여러 개면 다음 항목으로 계속 진행됨.
  • ⚠️ 멈춤 → 빈 코트가 있는데 예약 실패가 반복돼 안전상 멈춘 것. 창을 확인하라고 알리고, 원인(인증 실패 등) 파악.
  • 마지막 ===== 결과 요약 ===== → 예약 완료/매진/충돌/스킵 건수를 그대로 사용자에게 전달.

시간 해석 규칙

목표는 사용자의 자연스러운 말을 요일 시작-종료(24시간제)로 정확히 옮기는 것. 예약은 최대 2시간.

  • 오후/저녁/밤 + 시간 → 24시간제로 변환. "저녁 8시"=20, "오후 7시"=19, "밤 9시"=21, "오전 10시"=10.
  • 종료 시간을 안 말하면 시작+2시간으로 채운다 (최대 2시간이 보통 기본). "토 저녁 8시" → 토 20-22.
  • 오전/오후가 없는 애매한 시간(예: "8-10시", "7시부터")은 저녁(황금시간대)으로 기본 추정한다. 테니스 인기 시간대가 저녁이고 실제로 저녁 슬롯부터 매진되기 때문. 그래서 "8-10시" → 20-22, "7시부터" → 19-21로 보되, 확인 멘트에 "(저녁 8~10시)"처럼 풀어 적고 "혹시 오전이면 말씀해주세요"를 덧붙여 한 번 확인한다. 사용자가 오전/낮을 명시하면 그대로 따른다.
  • 여러 개를 우선순위로 말하면 말한 순서대로 줄을 쌓는다. 목록의 항목들은 전부 예약된다(폴백 아님). 단 같은 날 여러 개는 1일1예약 규칙상 1개만 된다.
  • 요일은 한 글자로: 월 화 수 목 금 토 일. 특정 날짜는 MM-DD(예: 06-20 14-16). 같은 요일이 두 번 열리는 주거나 사용자가 날짜를 콕 집으면 날짜 형식을 쓴다.

변환 예시:

Input: "토요일 저녁 8시부터 2시간" Output: 토 20-22

Input: "토 20-22, 안 되면 일 20-22, 그것도 안 되면 월 19-21" Output:

토 20-22
일 20-22
월 19-21

Input: "주말 저녁 8시 위주로, 토 먼저 일 나중" Output:

토 20-22
일 20-22

명령어 빠른 참조

| 목적 | 명령 (scripts/ 디렉토리에서) | |------|------| | 세션 확인 | ./run.sh check | | 로그인 (창 띄움) | ./run.sh login | | 정각 발사 | ./run.sh reserve -- --fire 09:30:00 | | 즉시 시도 (취소분) | ./run.sh reserve -- --now | | 빈자리 확인만 | ./run.sh reserve -- --dry --headless |

발사/로그인은 창이 떠야 하므로 --headless를 붙이지 않는다. 확인/스캔만 할 땐 붙여도 된다. WSL에서는 WSLg가 있어야 창이 뜬다 (Windows 11 기본 포함).

예약 조회 / 취소

  • 조회: 마이페이지(/mypage)를 읽어 예약 목록(시설명/일시/상태)을 보여준다.
  • 취소 (사용자가 명시적으로 요청할 때만): 마이페이지에서 각 예약의 apply_idx를 찾아

/mypage?mode=cancel&target=facility&apply_idx={idx} 로 이동하면 취소된다(alert "예약을 취소하였습니다"). 취소 전 어느 예약인지 사용자에게 확인하고, 취소 후 마이페이지로 검증한다. 사용자가 시키지 않은 예약은 절대 취소하지 않는다.

  • ⚠️ mypage 페이지네이션: 마이페이지는 한 번에 5개만 보여준다. "더보기" 버튼을 끝까지

눌러야(page.locator("text=더보기").click() 반복) 전체 예약이 나온다. 조회·취소 시 이걸 안 하면 뒤쪽 예약을 놓친다. 또 같은 슬롯이 "사용승인"과 "예약취소" 두 행으로 보일 수 있으니 상태가 사용승인인 행만 활성 예약으로 센다.

주의사항

  • 동반자 명단은 로컬 파일 scripts/members.local.json에만 있다 (PII는 skill 코드에 없음). 본인(로그인 계정)은 자동 입력.

사용자가 동반자를 바꾸거나 추가하려 하면 그 파일을 수정한다. 최소 예약인원 2명(본인+동반자 1명). 첫 사용이면 Step 0.

  • 발사를 백그라운드로 돌릴 때, 9:30까지 대기하는 동안 사용자가 다른 얘기를 해도 발사 잡(job)은 계속 살아있다. 완료 알림이 오면 결과를 전한다.
  • 발사 중 사용자가 창을 닫으면 스크립트가 TargetClosedError로 죽는다. reCAPTCHA 대기 중엔 창을 닫지 말라고 미리 안내한다.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.