Files
news-summary-bot/CLAUDE.md
sm4640 9c2bf7c1ce
All checks were successful
news-summary-bot-cicd / build_push_deploy (push) Successful in 9m14s
Fix: [3.0.2] 요약 응답 구조화 + Discord 임베드 디자인 개선
- 요약을 JSON으로 구조화: oneliner, main_points, conclusion 분리
- Claude에게 JSON 형식으로만 응답하도록 프롬프트 변경
- n8n Discord 임베드: 섹션별 필드 분리, 이모지, 타임스탬프 추가
- JSON.stringify Expression으로 특수문자 이스케이프 처리
- 전체 문서 API 응답 형식 업데이트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 17:18:31 +09:00

8.3 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

프로젝트 개요

YouTube 뉴스/경제 채널의 새 영상을 감지하면 자막을 추출하고 Claude API로 요약하는 봇.

  • 영상 감지: n8n (YouTube Data API v3 playlistItems.list)
  • 요약 처리: FastAPI 앱 (이 레포)
  • Discord 전송: n8n (FastAPI 응답을 받아서 Discord 웹훅으로 전송)
  • 배포: Docker Hub → OCI 서버에서 docker-compose pull

빌드 & 실행

# 로컬 개발
pip install -r requirements.txt
uvicorn app.main:app --reload

# Docker
docker build -t nkey/news-summary-bot .
docker compose up

아키텍처

n8n(YouTube API로 새 영상 감지) → POST /api/news/summarize → 자막 추출 → Claude 요약 → JSON 응답 → n8n이 Discord 웹훅 전송

  • app/main.py — FastAPI 엔드포인트 (/summarize, /health) — Nginx가 /api/news/ prefix를 strip
  • app/transcript.py — YouTube 자막 추출 (yt-dlp + 쿠키 인증)
  • app/summarizer.py — Claude Sonnet 4.6으로 요약 생성
  • app/config.py — 환경변수 설정 (pydantic-settings)

API 응답 형식

// 성공
{"video_url": "...", "title": "...", "channel_name": "...", "status": "ok", "oneliner": "한줄 요약", "main_points": "• 포인트1\n• 포인트2", "conclusion": "결론"}

// 스킵 (라이브/쇼츠)
{"video_url": "...", "title": "...", "channel_name": "...", "status": "skipped", "reason": "라이브/예정 영상은 요약 대상이 아닙니다"}

// 에러
{"video_url": "...", "title": "...", "channel_name": "...", "status": "error", "error_type": "ValueError", "error_message": "..."}

환경변수

ANTHROPIC_API_KEY 필수. API_SECRET은 선택(n8n → FastAPI 인증용).

쿠키 인증 (YouTube 봇 감지 우회)

OCI 등 클라우드 서버에서 YouTube 자막 추출 시 봇 감지 차단을 우회하기 위해 쿠키 파일이 필요.

  • 브라우저 확장(Get cookies.txt LOCALLY 등)으로 YouTube 쿠키를 cookies.txt로 export
  • 서버의 compose.apps.yml에서 ./news-summary-bot/cookies.txt:/app/cookies.txt:ro로 마운트
  • 쿠키 만료 시(6개월~1년) 재export 필요 → 에러 발생 시 쿠키 갱신 확인

n8n 워크플로우

전체 흐름

Schedule Trigger (매 정시) ─→ 머니코믹스 playlistItems ─→ Edit Fields (channel_name: 머니코믹스) ──┐
                           └→ 슈카월드 playlistItems   ─→ Edit Fields (channel_name: 슈카월드)   ──┤
                                                                                                    ↓
                                                                                              Merge (Append)
                                                                                                    ↓
                                                                                        Code (새 영상만 필터링)
                                                                                                    ↓
                                                                              HTTP Request (POST /api/news/summarize)
                                                                                                    ↓
                                                                                            Switch (status 분기)
                                                                                   ├→ ok → Discord 요약 전송
                                                                                   ├→ skipped → No Operation
                                                                                   └→ error → Discord 에러 알림

노드별 설정

1. Schedule Trigger

  • Type: Schedule Trigger
  • Rule: Every Hour, Minute=0

2. YouTube API - playlistItems (채널별 각 1개)

  • Type: HTTP Request
  • Method: GET
  • URL: https://www.googleapis.com/youtube/v3/playlistItems
  • Send Query Parameters: ON
  • Specify Query Parameters: Using Individual Fields
  • Query Parameters (Add Parameter로 추가):
    • part: snippet
    • playlistId: 채널 업로드 목록 ID (채널 ID의 UCUU로 변환)
    • maxResults: 5
    • key: YouTube Data API v3 키
  • 채널별 playlistId:
    • 머니코믹스: UUJo6G1u0e_-wS-JQn3T-zEw (채널 ID: UCJo6G1u0e_-wS-JQn3T-zEw)
    • 슈카월드: UUsJ6RuBiTVWRX156FVbeaGg (채널 ID: UCsJ6RuBiTVWRX156FVbeaGg)
  • 응답 예시:
{
  "items": [
    {
      "snippet": {
        "publishedAt": "2026-03-25T06:00:00Z",
        "title": "영상 제목",
        "resourceId": { "videoId": "abc123" }
      }
    }
  ]
}

3. Edit Fields (채널별 각 1개)

  • Type: Edit Fields (Set)
  • Mode: Manual Mapping
  • Fields to Set: channel_name (String) = 머니코믹스 또는 슈카월드
  • Include Other Input Fields: All (기존 데이터 유지)

4. Merge

  • Type: Merge
  • Mode: Append

5. Code (새 영상 필터링)

  • Type: Code
  • Language: JavaScript
  • Mode: Run Once for All Items
  • 최근 1시간 이내 발행된 영상만 필터링:
const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000);

return $input.all().filter(item => {
  const items = item.json.items || [];
  return items.some(i => new Date(i.snippet.publishedAt) > oneHourAgo);
}).flatMap(item => {
  const channelName = item.json.channel_name || '';
  return (item.json.items || [])
    .filter(i => new Date(i.snippet.publishedAt) > oneHourAgo)
    .map(i => ({
      json: {
        video_url: `https://www.youtube.com/watch?v=${i.snippet.resourceId.videoId}`,
        title: i.snippet.title,
        channel_name: channelName,
      }
    }));
});

6. HTTP Request (요약 API 호출)

  • Type: HTTP Request
  • Method: POST
  • URL: https://<서버주소>/api/news/summarize
  • Send Body: ON
  • Body Content Type: JSON
  • Specify Body: Using JSON
  • Body (Expression 모드):
{
  "video_url": "{{ $json.video_url }}",
  "title": "{{ $json.title }}",
  "channel_name": "{{ $json.channel_name }}"
}
  • Headers (API_SECRET 사용 시): X-Api-Secret: <시크릿값>
  • Options: "Always Output Data" 켜기 (에러 시에도 다음 노드로 전달)

7. Switch (응답 분기)

  • Type: Switch
  • Mode: Rules
  • Data Type: String
  • Value: {{ $json.status }}
  • Rules:
    • Rule 1: Equal ok → Discord 요약 전송
    • Rule 2: Equal skipped → No Operation (무시)
    • Rule 3: Equal error → Discord 에러 알림

8. Discord 요약 전송 (status=ok)

  • Type: HTTP Request
  • Method: POST
  • URL: Discord 웹훅 URL
  • Send Body: ON
  • Body Content Type: JSON
  • Specify Body: Using JSON
  • Body (Expression 모드):
{
  "embeds": [{
    "title": "📰 [{{ $json.channel_name }}] {{ $json.title }}",
    "url": "{{ $json.video_url }}",
    "description": "{{ $json.summary }}",
    "color": 2829105,
    "thumbnail": {
      "url": "https://img.youtube.com/vi/{{ $json.video_url.split('v=')[1] }}/hqdefault.jpg"
    },
    "footer": { "text": "YouTube 뉴스 요약 봇 • {{ $json.channel_name }}" }
  }]
}

9. Discord 에러 알림 (status=error)

  • Type: HTTP Request
  • Method: POST
  • URL: Discord 웹훅 URL
  • Send Body: ON
  • Body Content Type: JSON
  • Specify Body: Using JSON
  • Body (Expression 모드):
{
  "embeds": [{
    "title": "❌ [{{ $json.channel_name }}] 뉴스 요약 실패",
    "color": 15548997,
    "fields": [
      { "name": "영상 제목", "value": "{{ $json.title }}", "inline": false },
      { "name": "에러 타입", "value": "`{{ $json.error_type }}`", "inline": true },
      { "name": "에러 내용", "value": "```\n{{ $json.error_message }}\n```", "inline": false }
    ],
    "footer": { "text": "봇 요약 처리 에러 — FastAPI 응답" }
  }]
}

YouTube Data API 키 발급

  1. Google Cloud Console → 프로젝트 생성/선택
  2. "YouTube Data API v3" 검색 → 사용 설정
  3. 사용자 인증 정보 → API 키 생성
  4. (선택) 키 제한: YouTube Data API v3만 허용, IP 제한 등
  • 일일 쿼터: 10,000 units (playlistItems.list는 1 unit/요청 → 2채널 × 24회 = 48 units/일)