Skill: garibong-labs/olympic-alert (version 0.1.4)

--- name: olympic-alert description: 올림픽 경기 알림. 경기 15분 전 알림 발송, 일정 관리(추가/삭제), 중계 링크 포함. 2026 밀라노 동계올림픽 한국팀 기본 설정 포함. metadata: openclaw: requires: bins: ["python3"] triggers: - 올림픽 - Olympic - 동계올림픽 - 경기 알림 - 밀라노 --- # Olympic Alert Skill 올림픽 경기 알림을 15분 전에 발송합니다. 한국팀 경기 기본 설정 포함. ## 포함 파일 | 파일 | 설명 | |------|------| | `SKILL.md` | 이 문서 | | `scripts/check_olympic.py` | 메인 스크립트 (Python 3.6+, 표준 라이브러리만 사용) | | `scripts/events.json` | 경기 일정 데이터 (2026 밀라노 동계올림픽 한국팀 기본값) | ## 의존성 - Python 3.6+ (표준 라이브러리만 사용, 추가 패키지 불필요) ## 사용법 스킬 디렉토리 기준 상대경로로 실행합니다: ```bash SKILL_DIR="<workspace>/skills/olympic-alert" # 알림 체크 (HEARTBEAT에서 호출) python3 "$SKILL_DIR/scripts/check_olympic.py" # 다가오는 경기 목록 python3 "$SKILL_DIR/scripts/check_olympic.py" list # 경기 추가 python3 "$SKILL_DIR/scripts/check_olympic.py" add "2026-02-15 14:00" "🏒 쇼트트랙 준결승" "최민정" # 경기 삭제 (이름 패턴 매칭) python3 "$SKILL_DIR/scripts/check_olympic.py" remove "준결승" ``` ## 설정 ### events.json `scripts/events.json` 파일에서 경기 일정 관리: ```json { "country": "Korea", "flag": "🇰🇷", "links": { "네이버 스포츠": "https://m.sports.naver.com/milanocortina2026", "치지직": "https://chzzk.naver.com/search?query=올림픽" }, "events": [ {"time": "2026-02-10 18:00", "name": "🏒 쇼트트랙", "athletes": "최민정"} ] } ``` ### 상태 파일 `~/.config/olympic-alert/state.json` — 알림 발송 기록 (중복 방지) ## HEARTBEAT.md 설정 ```markdown ## 올림픽 경기 알림 (every heartbeat) On each heartbeat: 1. Run `python3 <skill_dir>/scripts/check_olympic.py` 2. If output is not "알림 없음" → 사용자에게 알림 전송 ``` ## 일정 업데이트 예선 결과에 따라 일정 변경 필요: - 진출 시: `add` 명령으로 준결승/결승 추가 - 탈락 시: `remove` 명령으로 해당 경기 삭제 - 또는 `events.json` 직접 편집 ## 알림 예시 ``` 🇰🇷 10분 후 🏒 쇼트트랙 여자1500m 결승 👤 최민정 3연속 금 도전 📺 네이버 스포츠 | 치지직 ``` ## 다른 국가/대회 적용 `events.json`의 `country`, `flag`, `links`, `events`를 수정하여 다른 국가나 대회에 적용 가능. ## 문의 / Feedback 버그 리포트, 기능 요청, 피드백은 아래로 보내주세요. - Email: [REDACTED_SECRET] - Developer: Garibong Labs (가리봉랩스)

path: scripts/check_olympic.py
content: #!/usr/bin/env python3
"""
Olympic Event Alert Script
Sends alerts 30 minutes before scheduled events.
Events are loaded from events.json in the same directory.
"""

import json
import os
import sys
from datetime import datetime
from pathlib import Path

SCRIPT_DIR = Path(__file__).parent
EVENTS_FILE = SCRIPT_DIR / "events.json"
STATE_DIR = Path(os.path.expanduser("~/.config/olympic-alert"))
STATE_FILE = STATE_DIR / "state.json"

def load_events():
    """Load events from events.json"""
    if not EVENTS_FILE.exists():
        return {"country": "Unknown", "events": [], "links": {}}
    with open(EVENTS_FILE, 'r', encoding='utf-8') as f:
        return json.load(f)

def load_state():
    if STATE_FILE.exists():
        with open(STATE_FILE, 'r') as f:
            return json.load(f)
    return {"notified": []}

def save_state(state):
    STATE_DIR.mkdir(parents=True, exist_ok=True)
    with open(STATE_FILE, 'w') as f:
        json.dump(state, f, indent=2)

def check_upcoming():
    data = load_events()
    events = data.get("events", [])
    links = data.get("links", {})
    country_flag = data.get("flag", "🏅")
    
    now = datetime.now()
    state = load_state()
    alerts = []
    
    for event in events:
        event_time_str = event["time"]
        event_name = event["name"]
        athletes = event.get("athletes", "")
        
        event_time = datetime.strptime(event_time_str, "%Y-%m-%d %H:%M")
        event_id = f"{event_time_str}_{event_name}"
        
        time_until = event_time - now
        minutes_until = time_until.total_seconds() / 60
        
        if 0 <= minutes_until <= 15 and event_id not in state["notified"]:
            mins = int(minutes_until)
            time_msg = "지금 시작!" if mins == 0 else f"{mins}분 후"
            
            alert = f"{country_flag} {time_msg}\n{event_name}"
            if athletes:
                alert += f"\n👤 {athletes}"
            
            # Add streaming links
            if links:
                link_parts = [f"[{name}]({url})" for name, url in links.items()]
                alert += f"\n\n📺 {' | '.join(link_parts)}"
            
            alerts.append(alert)
            state["notified"].append(event_id)
    
    if alerts:
        save_state(state)
    
    return alerts

def list_upcoming():
    """List upcoming events"""
    data = load_events()
    events = data.get("events", [])
    
    now = datetime.now()
    upcoming = []
    
    for event in events:
        event_time = datetime.strptime(event["time"], "%Y-%m-%d %H:%M")
        if event_time > now:
            time_until = event_time - now
            days = time_until.days
            hours = time_until.seconds // 3600
            
            if days > 0:
                time_str = f"D-{days}"
            elif hours > 0:
                time_str = f"{hours}시간 후"
            else:
                mins = time_until.seconds // 60
                time_str = f"{mins}분 후"
            
            line = f"• {event_time.strftime('%m/%d %H:%M')} {event['name']}"
            if event.get("athletes"):
                line += f" ({event['athletes']})"
            upcoming.append((event_time, line))
    
    upcoming.sort(key=lambda x: x[0])
    return [line for _, line in upcoming[:15]]

def add_event(time_str: str, name: str, athletes: str = ""):
    """Add a new event to events.json"""
    data = load_events()
    data["events"].append({
        "time": time_str,
        "name": name,
        "athletes": athletes
    })
    # Sort by time
    data["events"].sort(key=lambda x: x["time"])
    with open(EVENTS_FILE, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    print(f"Added: {time_str} {name}")

def remove_event(name_pattern: str):
    """Remove events matching pattern"""
    data = load_events()
    original_count = len(data["events"])
    data["events"] = [e for e in data["events"] if name_pattern.lower() not in e["name"].lower()]
    removed = original_count - len(data["events"])
    with open(EVENTS_FILE, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    print(f"Removed {removed} event(s) matching '{name_pattern}'")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        # Default: check for alerts
        alerts = check_upcoming()
        if alerts:
            for alert in alerts:
                print(alert)
                print()
        else:
            print("알림 없음")
    elif sys.argv[1] == "list":
        events = list_upcoming()
        if events:
            print("📅 다가오는 경기:\n")
            print("\n".join(events))
        else:
            print("남은 경기 없음")
    elif sys.argv[1] == "add" and len(sys.argv) >= 4:
        # add "2026-02-10 18:00" "Event Name" "Athletes (optional)"
        athletes = sys.argv[4] if len(sys.argv) > 4 else ""
        add_event(sys.argv[2], sys.argv[3], athletes)
    elif sys.argv[1] == "remove" and len(sys.argv) >= 3:
        remove_event(sys.argv[2])
    else:
        print("Usage:")
        print("  check_olympic.py          # Check for upcoming alerts")
        print("  check_olympic.py list     # List upcoming events")
        print("  check_olympic.py add <time> <name> [athletes]")
        print("  check_olympic.py remove <name_pattern>")


path: scripts/events.json
content: {
  "country": "Korea",
  "flag": "🇰🇷",
  "links": {
    "네이버 스포츠": "https://m.sports.naver.com/milanocortina2026",
    "치지직": "https://chzzk.naver.com/search?query=올림픽"
  },
  "events": [
    {"time": "2026-02-07 03:30", "name": "🎭 개막식", "athletes": "대한민국 선수단 입장"},
    {"time": "2026-02-08 00:00", "name": "⛸️ 스피드스케이팅 남자 5000m", "athletes": ""},
    {"time": "2026-02-08 20:50", "name": "🏂 스노보드 평행대회전 결승", "athletes": "이상호"},
    {"time": "2026-02-10 01:30", "name": "⛸️ 스피드스케이팅 여자 1000m", "athletes": ""},
    {"time": "2026-02-10 18:00", "name": "🏒 쇼트트랙 여자500m/남자1000m 예선, 혼성계주", "athletes": "최민정, 김길리, 황대헌"},
    {"time": "2026-02-11 02:10", "name": "⛸️ 피겨 남자 쇼트프로그램", "athletes": "차준환, 김현겸"},
    {"time": "2026-02-11 18:00", "name": "🏂 스노보드 하프파이프 여자 예선", "athletes": "최가온"},
    {"time": "2026-02-12 03:30", "name": "🏂 스노보드 하프파이프 남자 예선", "athletes": "이채운"},
    {"time": "2026-02-13 02:50", "name": "🥌 컬링 여자 단체전 예선 vs 이탈리아", "athletes": "팀 5G"},
    {"time": "2026-02-13 03:30", "name": "🏂 스노보드 하프파이프 여자 결선", "athletes": "최가온"},
    {"time": "2026-02-13 04:15", "name": "🏒 쇼트트랙 여자500m/남자1000m 결승", "athletes": "최민정, 황대헌"},
    {"time": "2026-02-14 03:00", "name": "⛸️ 피겨 남자 프리스케이팅", "athletes": "차준환, 김현겸"},
    {"time": "2026-02-14 03:30", "name": "🏂 스노보드 하프파이프 남자 결선", "athletes": "이채운"},
    {"time": "2026-02-15 21:50", "name": "🥌 컬링 여자 단체전 예선 vs 일본", "athletes": "팀 5G"},
    {"time": "2026-02-16 01:00", "name": "⛸️ 스피드스케이팅 여자 500m 결승", "athletes": ""},
    {"time": "2026-02-18 02:30", "name": "⛸️ 피겨 여자 쇼트프로그램", "athletes": "이해인, 신지아"},
    {"time": "2026-02-20 00:30", "name": "⛸️ 스피드스케이팅 여자 1500m", "athletes": ""},
    {"time": "2026-02-20 03:00", "name": "⛸️ 피겨 여자 프리스케이팅", "athletes": "이해인, 신지아"},
    {"time": "2026-02-21 03:00", "name": "🏒 쇼트트랙 여자1500m + 남자5000m계주 결승", "athletes": "최민정 3연속 금 도전"},
    {"time": "2026-02-21 23:00", "name": "⛸️ 스피드스케이팅 매스스타트", "athletes": ""},
    {"time": "2026-02-22 04:00", "name": "⛸️ 피겨 갈라쇼", "athletes": ""},
    {"time": "2026-02-22 19:05", "name": "🥌 컬링 여자 결승", "athletes": "팀 5G"}
  ]
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)