# n8n 연동 문서 이 문서는 `HUFS 컴퓨터공학부 공지 크롤러`를 `n8n`과 연결해 Discord Webhook으로 알림을 보내는 방법을 설명합니다. 관련 문서: - 서비스 개요: [`README.md`](/C:/Users/USER/Desktop/notice_crawler/README.md) - 운영/배포: [`README.operation.md`](/C:/Users/USER/Desktop/notice_crawler/README.operation.md) - 테스트: [`README.test.md`](/C:/Users/USER/Desktop/notice_crawler/README.test.md) ## 왜 n8n을 쓰는가 현재는 Discord Webhook으로 보내더라도, 나중에 Slack이나 Telegram으로 바뀔 수 있습니다. 권장 역할 분리: - `hufs-notice-crawler` - 크롤링과 데이터 정규화만 담당 - 채널별 포맷은 모름 - `n8n` - 백엔드 JSON을 받아 Discord/Slack/Telegram 형식으로 변환 이 구조의 장점: - 알림 채널이 바뀌어도 백엔드 코드 변경 최소화 - 백엔드 응답을 Discord 전용 형식으로 오염시키지 않음 - 같은 데이터를 여러 채널로 동시에 보낼 수 있음 즉, 백엔드는 `generic JSON provider`, n8n은 `channel adapter`로 두는 것이 맞습니다. ## 권장 워크플로우 1. `Schedule Trigger` 2. `Set` 3. `HTTP Request` 4. `IF - new_posts_count > 0` 5. `IF - test_mode == true` 6. `Code` 7. `HTTP Request (Discord Webhook)` 의미: 1. 스케줄에 따라 n8n 실행 2. `test_mode`를 켜거나 끔 3. `hufs-notice-crawler` 호출 4. 새 글이 있는지 확인 5. 새 글이 없으면 test mode 여부 확인 6. 상황에 맞는 Discord 메시지 포맷 생성 7. Webhook 전송 ## 노드별 설정 ### 1. Schedule Trigger 예시 cron: ```text 0 10,14,18 * * * ``` 의미: - 매일 10:00, 14:00, 18:00 실행 ### 2. Set 필드: - `test_mode` - Boolean - `true`: 테스트 기간 - `false`: 운영 모드 의미: - `true` - 새 글이 0개일 때도 "업데이트 없음 + 게시판별 최신 글" 메시지 전송 - `false` - 새 글이 0개면 아무 메시지도 보내지 않음 ### 3. HTTP Request 역할: - 백엔드 API 호출 권장 설정: - Method: `POST` - Response Format: `JSON` URL: - 내부 Docker network 직접 호출 - `http://hufs-notice-crawler:8000/api/v1/crawl` - nginx reverse proxy 경유 호출 -> nginx에 로그를 모으기 위해 채택 - `https://nkeystudy.site/api/hufs/crawl` ## 백엔드 응답에서 중요한 필드 - `bootstrap_mode` - 최초 실행 bootstrap 여부 - `bootstrap_inserted_count` - bootstrap 시 저장된 글 수 - `new_posts_count` - 실제 신규 글 수 - `new_posts` - 신규 글 목록 - `latest_posts_by_board` - 게시판별 최신 글 - `new_posts_count == 0`일 때만 포함 - 별도 추가 요청이 아니라 실제 크롤링 결과 재사용 ## IF 분기 ### IF 1: 새 글 여부 조건: - Left Value: `{{ $json.new_posts_count }}` - Operation: `larger` - Right Value: `0` 분기: - True - 새 글 알림용 Code 노드로 이동 - False - `test_mode` 확인용 IF 노드로 이동 ### IF 2: test mode 여부 조건: - Left Value: `{{ $('{Set노드의 이름}').item.json.test_mode }}` - Operation: `is true` 분기: - True - "업데이트 없음" 테스트 메시지 전송 - False - 아무 메시지 없이 종료 ## Code 노드 중요 사항 이 문서의 Code 노드 예시는 `Run Once for All Items` 기준입니다. 이유: - `HTTP Request` 응답 1개 안에 `new_posts` 배열이 들어 있음 - 이를 여러 Discord 메시지 item으로 펼쳐야 함 즉 Code 노드에서: - `Mode = Run Once for All Items` 를 권장합니다. `Run Once for Each Item`에서는 `return []` 또는 `map(...)`으로 여러 item을 반환할 때 에러가 날 수 있습니다. ## Code 예시 1: 새 글 알림용 Discord Embed ```javascript const data = $input.first().json; if (!data.new_posts || data.new_posts.length === 0) { return []; } return data.new_posts.map((post) => { const publishedAt = post.published_at ?? "날짜 없음"; const author = post.author ?? "작성자 없음"; const summary = post.summary ?? "요약 없음"; const attachments = (post.attachments || []) .map((file) => `- [${file.name}](${file.url})`) .join("\n"); const descriptionParts = [ `게시판: ${post.board_name}`, `작성자: ${author}`, `작성일: ${publishedAt}`, "", `요약: ${summary}`, ]; if (attachments) { descriptionParts.push("", "첨부파일:", attachments); } return { json: { discordPayload: { embeds: [ { title: post.title, url: post.post_url, description: descriptionParts.join("\n").slice(0, 4000), color: 3447003, footer: { text: `article_id: ${post.article_id}`, }, timestamp: post.published_at ?? undefined, }, ], }, }, }; }); ``` ## Code 예시 2: 새 글 있을 때 role mention 태그 ```javascript const roleMention = "<@&123456789012345678>"; const data = $input.first().json; if (!data.new_posts || data.new_posts.length === 0) { return []; } return data.new_posts.map((post) => ({ json: { discordPayload: { content: `${roleMention} 새 글이 올라왔습니다.`, embeds: [ { title: post.title, url: post.post_url, description: `게시판: ${post.board_name}\n작성일: ${post.published_at ?? "날짜 없음"}`, color: 3447003, }, ], }, }, })); ``` 역할 ID만 실제 Discord 서버 값으로 바꾸면 됩니다. ## Code 예시 3: 업데이트 없음 테스트 메시지 이 코드는: - `new_posts_count == 0` - `test_mode == true` 일 때만 실행하는 용도입니다. ```javascript const data = $input.first().json; const latestPosts = data.latest_posts_by_board || []; const lines = ["현재 새로 업데이트된 공지사항은 없습니다."]; if (data.bootstrap_mode) { lines.push( `초기 bootstrap 저장이 수행되었습니다. 저장된 글 수: ${data.bootstrap_inserted_count}`, ); } if (latestPosts.length > 0) { lines.push("", "게시판별 가장 최근 글:"); for (const post of latestPosts) { lines.push(`- [${post.board_name}] ${post.title}`); lines.push(` ${post.post_url}`); } } return [ { json: { discordPayload: { content: lines.join("\n"), }, }, }, ]; ``` 운영 모드에서는 이런 메시지를 보내지 않는 것을 권장합니다. ## Discord Webhook 노드 권장 설정: - Method: `POST` - URL: Discord Webhook URL - - Send Body: `Using JSON` - Body: ```json {{ $json.discordPayload }} ``` 주의: - Code 노드가 여러 item을 반환하면 Discord Webhook 노드는 게시글 수만큼 여러 번 실행됩니다. ## test mode 권장 동작 - 운영 모드 (`test_mode = false`) - 새 글 있을 때만 전송 - 0개일 때는 전송 안 함 - 테스트 모드 (`test_mode = true`) - 새 글 있으면 전송 - 새 글 0개여도 "업데이트 없음" 메시지 전송 - 게시판별 최신 글 표시 - bootstrap이면 bootstrap 정보도 함께 표시 가능 ## bootstrap과 n8n 최초 실행 시: - `bootstrap_mode = true` - `bootstrap_inserted_count > 0` - `new_posts_count = 0` 즉, 기존 글은 DB에 저장되지만 `new_posts`로 반환되지 않습니다. 그래서: - 첫 실행에서 예전 글 알림이 쏟아지지 않음 - test mode가 켜져 있으면 "업데이트 없음" 메시지로 상태만 확인 가능 ## 나중에 Slack으로 바뀌면 바뀌는 것: - Webhook URL - Code 노드의 메시지 포맷 - 마지막 전송 노드 안 바뀌는 것: - `hufs-notice-crawler` 응답 구조 - 크롤링 로직 - 신규 글 판별 로직 즉 채널 변경 비용을 `n8n` 수정으로 제한할 수 있습니다.