mattermost로 메시지 보내기 세팅
This commit is contained in:
24
.env_sample
Normal file
24
.env_sample
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# 서버 도메인 (필수)
|
||||||
|
SERVER_DOMAIN=example.com
|
||||||
|
|
||||||
|
# 서버 프로토콜 (기본값: http)
|
||||||
|
#SERVER_PROTOCOL=https
|
||||||
|
|
||||||
|
# 웹훅 서버 설정
|
||||||
|
#WEBHOOK_SERVER_HOST=0.0.0.0
|
||||||
|
#WEBHOOK_SERVER_PORT=5000
|
||||||
|
|
||||||
|
# NOTION
|
||||||
|
NOTION_API_SECRET=abc123
|
||||||
|
#NOTION_WEBHOOK_SECRET=abc1234
|
||||||
|
|
||||||
|
# Mattermost
|
||||||
|
MATTERMOST_WEBHOOK_URL=https://mattermost.example.com/hooks/abc123
|
||||||
|
#MATTERMOST_BOT_TOKEN=abc123
|
||||||
|
#MATTERMOST_CH_ID=
|
||||||
|
|
||||||
|
# 디버그 모드 (기본값: false)
|
||||||
|
#DEBUG=false
|
||||||
|
|
||||||
|
# 로그 레벨 (기본값: INFO)
|
||||||
|
#LOG_LEVEL=INFO
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
.vscode/
|
||||||
|
**/__pycache__/**
|
||||||
100
README.md
Normal file
100
README.md
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Notion Notification System
|
||||||
|
|
||||||
|
노션(Notion)의 페이지 및 데이터베이스 변경 사항을 감지하고, 이를 Mattermost로 전송하는 웹훅 기반 알림 시스템입니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 주요 기능
|
||||||
|
|
||||||
|
- Notion Webhook 수신 서버 구현 (Flask 기반)
|
||||||
|
- 변경 이벤트를 파싱하여 메시지 생성
|
||||||
|
- Mattermost에 메시지를 전송 (웹훅 또는 봇 API 방식 모두 지원)
|
||||||
|
- .env 환경 설정 기반 유연한 구성
|
||||||
|
- 향후 다양한 웹훅 소스 확장을 고려한 구조
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 프로젝트 구조
|
||||||
|
|
||||||
|
```bash
|
||||||
|
notion_noti/
|
||||||
|
├── app.py # Flask 진입점
|
||||||
|
├── config.py # .env 기반 설정 로드 및 검증
|
||||||
|
├── views.py # 웹훅 라우트 정의
|
||||||
|
├── lib/
|
||||||
|
│ ├── notion_api.py # 노션 이벤트 파싱/처리
|
||||||
|
│ └── send_message.py # Mattermost 메시지 전송 기능
|
||||||
|
├── .env # 환경 설정 파일
|
||||||
|
├── requirements.txt # 의존 패키지 목록
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ 설치 및 실행
|
||||||
|
|
||||||
|
### 1. 의존 패키지 설치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m venv .venv
|
||||||
|
source .venv/bin/activate # (Windows: .venv\Scripts\activate)
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 환경 설정
|
||||||
|
`.env` 파일을 아래와 같은 형식으로 작성합니다.
|
||||||
|
|
||||||
|
```env
|
||||||
|
# 서버 설정
|
||||||
|
SERVER_DOMAIN=example.com
|
||||||
|
SERVER_PROTOCOL=https
|
||||||
|
WEBHOOK_SERVER_HOST=0.0.0.0
|
||||||
|
WEBHOOK_SERVER_PORT=5000
|
||||||
|
|
||||||
|
# Notion API
|
||||||
|
NOTION_API_SECRET=secret_xxxxx
|
||||||
|
#NOTION_WEBHOOK_SECRET=your_webhook_secret # (선택사항)
|
||||||
|
|
||||||
|
# Mattermost 설정 (둘 중 하나 이상)
|
||||||
|
MATTERMOST_WEBHOOK_URL=https://mattermost.example.com/hooks/abc123
|
||||||
|
# 또는
|
||||||
|
MATTERMOST_BOT_TOKEN=your_token
|
||||||
|
MATTERMOST_CH_ID=channel_id_here
|
||||||
|
|
||||||
|
# 기타
|
||||||
|
DEBUG=true
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 실행
|
||||||
|
```bash
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Flask 서버가 다음과 같이 실행됩니다.
|
||||||
|
```csharp
|
||||||
|
* Running on http://0.0.0.0:5000
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🧪 테스트
|
||||||
|
### curl로 웹훅 테스트
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:5000/webhook/notion \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"event": "test_event",
|
||||||
|
"page_id": "abc123",
|
||||||
|
"action": "created"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Postman 또는 테스트 스크립트도 사용 가능
|
||||||
|
|
||||||
|
## 📌 확장 계획
|
||||||
|
|
||||||
|
- Mattermost 슬래시 명령어 처리
|
||||||
|
- 다른 서비스(GitHub, Jira 등)의 웹훅 수신 처리
|
||||||
|
- 메시지 템플릿 설정 지원
|
||||||
|
- Docker 이미지 제공
|
||||||
|
- Synology NAS 환경에서 안정화 운영 지원
|
||||||
|
|
||||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
36
app/app.py
Normal file
36
app/app.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 📌 프로젝트 루트 경로 추가
|
||||||
|
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
from views import webhook_bp # views.py에 정의된 Blueprint 객체
|
||||||
|
from lib.config import Config
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
# 로그 레벨 설정
|
||||||
|
logging.basicConfig(
|
||||||
|
level=Config.LOG_LEVEL,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 웹훅 라우트 등록
|
||||||
|
app.register_blueprint(webhook_bp)
|
||||||
|
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
Config.validate()
|
||||||
|
app = create_app()
|
||||||
|
app.run(
|
||||||
|
host=Config.WEBHOOK_SERVER_HOST,
|
||||||
|
port=Config.WEBHOOK_SERVER_PORT,
|
||||||
|
debug=Config.DEBUG
|
||||||
|
)
|
||||||
0
lib/common.py
Normal file
0
lib/common.py
Normal file
63
lib/config.py
Normal file
63
lib/config.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
# 서버 설정
|
||||||
|
SERVER_DOMAIN = os.getenv('SERVER_DOMAIN', 'localhost')
|
||||||
|
SERVER_PROTOCOL = os.getenv('SERVER_PROTOCOL', 'http')
|
||||||
|
WEBHOOK_SERVER_HOST = os.getenv('WEBHOOK_SERVER_HOST', '0.0.0.0')
|
||||||
|
WEBHOOK_SERVER_PORT = int(os.getenv('WEBHOOK_SERVER_PORT', 5000))
|
||||||
|
|
||||||
|
# 노션 API
|
||||||
|
NOTION_API_SECRET = os.getenv('NOTION_API_SECRET')
|
||||||
|
|
||||||
|
# 웹훅 서명 검증용 비밀키 (옵션)
|
||||||
|
NOTION_WEBHOOK_SECRET = os.getenv('NOTION_WEBHOOK_SECRET', None)
|
||||||
|
|
||||||
|
# Mattermost 관련
|
||||||
|
MATTERMOST_SERVER_URL = os.getenv('MATTERMOST_SERVER_URL', '').rstrip('/')
|
||||||
|
MATTERMOST_WEBHOOK_URL = os.getenv('MATTERMOST_WEBHOOK_URL')
|
||||||
|
MATTERMOST_BOT_TOKEN = os.getenv('MATTERMOST_BOT_TOKEN')
|
||||||
|
MATTERMOST_CH_ID = os.getenv('MATTERMOST_CH_ID')
|
||||||
|
|
||||||
|
# 기타 설정
|
||||||
|
DEBUG = os.getenv('DEBUG', 'false').lower() in ['true', '1', 'yes']
|
||||||
|
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mattermost_post_api_url(cls):
|
||||||
|
if cls.MATTERMOST_SERVER_URL:
|
||||||
|
return f"{cls.MATTERMOST_SERVER_URL}/api/v4/posts"
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate(cls):
|
||||||
|
missing = []
|
||||||
|
|
||||||
|
# NOTION_API_SECRET 필수 확인
|
||||||
|
if not cls.NOTION_API_SECRET:
|
||||||
|
missing.append('NOTION_API_SECRET')
|
||||||
|
|
||||||
|
# Mattermost는 둘 중 하나 이상 설정 필수
|
||||||
|
if not cls.MATTERMOST_WEBHOOK_URL and not (cls.MATTERMOST_BOT_TOKEN and cls.MATTERMOST_CH_ID):
|
||||||
|
if not cls.MATTERMOST_WEBHOOK_URL:
|
||||||
|
missing.append('MATTERMOST_WEBHOOK_URL')
|
||||||
|
if not cls.MATTERMOST_BOT_TOKEN:
|
||||||
|
missing.append('MATTERMOST_BOT_TOKEN')
|
||||||
|
if not cls.MATTERMOST_CH_ID:
|
||||||
|
missing.append('MATTERMOST_CH_ID')
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
err_msg = (
|
||||||
|
f"환경변수 설정 오류: 아래 필수 항목이 누락되었습니다:\n"
|
||||||
|
+ "\n".join(f" - {var}" for var in missing)
|
||||||
|
+ "\n설정 후 다시 실행해주세요."
|
||||||
|
)
|
||||||
|
raise EnvironmentError(err_msg)
|
||||||
|
else:
|
||||||
|
print("필수 항목 검증 완료")
|
||||||
|
|
||||||
|
# 초기 검증 실행
|
||||||
|
Config.validate()
|
||||||
83
lib/send_message.py
Normal file
83
lib/send_message.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import os, sys
|
||||||
|
|
||||||
|
# 현재 파일 기준으로 프로젝트 루트 경로를 sys.path 에 추가
|
||||||
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from lib.config import Config
|
||||||
|
|
||||||
|
def send_message_to_mattermost(message: str) -> bool:
|
||||||
|
"""
|
||||||
|
설정된 방식에 따라 Mattermost로 메시지를 전송합니다.
|
||||||
|
- 웹훅 URL이 있으면 웹훅으로 전송
|
||||||
|
- 봇 토큰과 채널 ID가 있으면 API로 전송
|
||||||
|
- 둘 다 있으면 두 방식 모두 시도
|
||||||
|
"""
|
||||||
|
success = True
|
||||||
|
|
||||||
|
if Config.MATTERMOST_WEBHOOK_URL:
|
||||||
|
result = _send_via_webhook(message)
|
||||||
|
if not result:
|
||||||
|
logging.error("웹훅 전송 실패")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
if Config.MATTERMOST_BOT_TOKEN and Config.MATTERMOST_CH_ID:
|
||||||
|
result = _send_via_bot_api(message)
|
||||||
|
if not result:
|
||||||
|
logging.error("봇 API 전송 실패")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
if not Config.MATTERMOST_WEBHOOK_URL and not (Config.MATTERMOST_BOT_TOKEN and Config.MATTERMOST_CH_ID):
|
||||||
|
logging.error("Mattermost 전송 실패: 환경변수 설정이 없음")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _send_via_webhook(message: str) -> bool:
|
||||||
|
try:
|
||||||
|
resp = requests.post(
|
||||||
|
Config.MATTERMOST_WEBHOOK_URL,
|
||||||
|
json={"text": message},
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
if resp.status_code == 200:
|
||||||
|
return True
|
||||||
|
logging.error(f"[WEBHOOK ERROR] {resp.status_code}: {resp.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("[WEBHOOK EXCEPTION]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _send_via_bot_api(message: str) -> bool:
|
||||||
|
url = Config.mattermost_post_api_url()
|
||||||
|
if not url:
|
||||||
|
logging.error("MATTERMOST_SERVER_URL 설정이 필요합니다.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {Config.MATTERMOST_BOT_TOKEN}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
payload = {
|
||||||
|
"channel_id": Config.MATTERMOST_CH_ID,
|
||||||
|
"message": message,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
resp = requests.post(url, headers=headers, json=payload, timeout=5)
|
||||||
|
if resp.status_code == 201:
|
||||||
|
return True
|
||||||
|
logging.error(f"[BOT API ERROR] {resp.status_code}: {resp.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception("[BOT API EXCEPTION]")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_msg = "✅ 테스트 메시지입니다: 시스템 점검 완료"
|
||||||
|
success = send_message_to_mattermost(test_msg)
|
||||||
|
print("[RESULT]", "전송 성공" if success else "전송 실패")
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
flask>=2.2.0
|
||||||
|
requests>=2.31.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
31
run.py
Normal file
31
run.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 📌 프로젝트 루트 경로 추가
|
||||||
|
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
from lib.config import Config
|
||||||
|
from app.app import create_app
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 설정 검증
|
||||||
|
Config.validate()
|
||||||
|
|
||||||
|
# 로그 포맷 설정
|
||||||
|
logging.basicConfig(
|
||||||
|
level=Config.LOG_LEVEL,
|
||||||
|
format="%(asctime)s [%(levelname)s] %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Flask 앱 생성 및 실행
|
||||||
|
app = create_app()
|
||||||
|
app.run(
|
||||||
|
host=Config.WEBHOOK_SERVER_HOST,
|
||||||
|
port=Config.WEBHOOK_SERVER_PORT,
|
||||||
|
debug=Config.DEBUG
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user