76f604a094d55347a04c98c4840c326ba3cf0a35
All checks were successful
baekjoon-bot-cicd / build_push_deploy (push) Successful in 5m40s
워크북 모드에서 (k/n) 진행도를 타이틀에 표시하고, 양쪽 모드 모두 하단 정기 알림 footer 제거 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
baekjoon-bot
매일 백준(BOJ) 문제를 추천하고 Discord embed payload로 반환하는 FastAPI 서비스. solved.ac 검색 기반 추천과 PostgreSQL 문제집(workbook) 기반 추천 두 가지 모드를 지원하며, n8n 등 외부 스케줄러와 연동하여 Discord 자동 알림을 구성할 수 있다.
주요 기능
- Search 모드: solved.ac API로 난이도/태그/언어 조건에 맞는 문제를 랜덤 추천
- Workbook 모드: DB에 저장된 문제집에서 아직 보내지 않은 문제를 순서대로 또는 랜덤으로 추천
- Discord embed: 모든 응답에 Discord webhook용 embed payload 포함
- Admin API: 문제집 메타데이터 보강(enrich), 진행상황 초기화(reset)
기술 스택
- Python 3.12+ / FastAPI / Uvicorn
- SQLAlchemy 2.0 (async) + asyncpg (PostgreSQL)
- httpx (비동기 HTTP) / requests (동기 HTTP)
- Docker / Gitea Actions CI/CD
빠른 시작
git clone https://nkeystudy.site/gitea/nkey/baekjoon-bot.git
cd baekjoon-bot
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
# .env 파일 생성
cat > .env <<'EOF'
DATABASE_URL=postgresql+asyncpg://USER:PASSWORD@HOST:5432/DBNAME
ADMIN_PASSWORD=change-me
EOF
uvicorn app:app --host 0.0.0.0 --port 8000
Docker로 실행:
docker build -f dockerfile -t baekjoon-bot:local .
docker run --rm -p 8000:8000 --env-file .env baekjoon-bot:local
API 엔드포인트
| Method | Endpoint | 설명 | 인증 |
|---|---|---|---|
| GET | / |
헬스체크 | - |
| GET | /today |
오늘의 추천 문제 | - |
| POST | /admin/workbooks/{id}/enrich |
문제집 메타데이터 보강 | O |
| DELETE | /admin/workbooks/{id}/reset |
문제집 발송 기록 초기화 | O |
사용 예시
# 기본 추천 (search 모드)
curl -s "http://localhost:8000/today"
# 난이도/태그/언어 지정
curl -s "http://localhost:8000/today?difficulty=6..10&tags=dp,graphs&lang=ko"
# 문제집 모드
curl -s "http://localhost:8000/today?source_mode=workbook&workbook_id=12345"
# 문제집 메타 보강
curl -s -X POST -H "X-Admin-Password: change-me" \
"http://localhost:8000/admin/workbooks/12345/enrich"
# 문제집 진행상황 초기화
curl -s -X DELETE -H "X-Admin-Password: change-me" \
"http://localhost:8000/admin/workbooks/12345/reset"
Workbook 모드 셋업
Workbook 모드를 사용하려면 DB에 문제집 데이터를 직접 넣어야 한다. (BOJ 자동 크롤링은 스크래핑 차단으로 현재 미지원)
1. 문제집 등록
-- id는 BOJ 문제집 URL의 번호 (acmicpc.net/workbook/view/12345)
INSERT INTO workbooks (id, title)
VALUES (12345, '내 문제집 이름');
2. 문제 목록 등록
-- 문제 번호만 넣으면 됨 (제목/난이도/태그는 3단계에서 자동으로 채워짐)
INSERT INTO workbook_problems (workbook_id, problem_id) VALUES
(12345, 1000),
(12345, 1001),
(12345, 1002);
3. 메타데이터 자동 보강
# solved.ac API로 제목, 난이도, 태그를 자동으로 가져와서 DB에 채움
curl -s -X POST \
-H "X-Admin-Password: change-me" \
"http://localhost:8000/admin/workbooks/12345/enrich"
이후 /today?source_mode=workbook&workbook_id=12345로 문제를 추천받을 수 있다.
모든 문제를 다 뽑은 뒤 다시 처음부터 시작하려면 reset API를 호출하면 된다.
환경변수
| 변수 | 설명 | 기본값 | 필수 |
|---|---|---|---|
DATABASE_URL |
PostgreSQL 비동기 연결 URL | - | O |
ADMIN_PASSWORD |
Admin API 비밀번호 | "" |
Admin API 사용 시 |
SOURCE_MODE_DEFAULT |
기본 소스 모드 (search/workbook) |
search |
|
WORKBOOK_ID_DEFAULT |
workbook 모드 기본 ID | - | |
DIFFICULTY_MODE_DEFAULT |
기본 난이도 모드 (easy/hard/all) |
easy |
|
TAG_MODE_DEFAULT |
기본 태그 모드 | easy |
|
LANG_DEFAULT |
기본 언어 필터 | all |
|
DIFFICULTY_EASY |
easy 난이도 범위 | 6..10 |
|
DIFFICULTY_HARD |
hard 난이도 범위 | 11..15 |
|
DIFFICULTY_ALL |
all 난이도 범위 | 1..30 |
|
TAGS_EASY / TAGS_HARD / TAGS_ALL |
태그 프리셋 (CSV) | "" |
|
TAG_PICK / TAG_PICK_EASY 등 |
태그 선택 정책 (random/none/전체) |
random |
|
TAGS_JOIN |
태그 결합 방식 (or/and) |
or |
프로젝트 구조
app.py # FastAPI 엔트리포인트, 라우팅
utils.py # 환경변수 헬퍼, solved.ac 검색 쿼리 빌드, HTTP 호출
db.py # SQLAlchemy async 엔진/세션
workbook_picker.py # 문제집에서 미발송 문제 1개 선택 + 발송 기록
workbook_enricher.py # solved.ac로 문제 메타데이터 채우기
workbook_importer.py # BOJ 문제집 크롤링 (현재 미사용)
dockerfile # Docker 이미지 정의
requirements.txt # Python 의존성
CI/CD
- Workflow:
.gitea/workflows/cicd.yml - Trigger:
main브랜치 push (단, 문서 파일만 변경된 경우 스킵) - Flow: 수동 checkout → Docker Hub 빌드/푸시 → 서버 배포(
compose.apps.yml) → Discord 알림
필요한 Secrets
| Secret | 용도 |
|---|---|
NKEY_PAT |
Gitea repo fetch 인증 |
DOCKERHUB_USERNAME |
Docker Hub 네임스페이스 |
DOCKERHUB_TOKEN |
Docker Hub 로그인 |
DISCORD_WEBHOOK |
CI/CD 결과 알림 |
상세 문서
Description
Languages
Python
100%