SSAFY 멀티캠퍼스 20층 / 10층 식당 식단 정보를 자동으로 수집·가공·배포하는 시스템입니다.
20층 식단은 Welstory API 기반으로 수집하고, 10층 식단은 Mattermost에 업로드된 주간 식단표 PNG를 자동 수집한 뒤 Google Gemini API로 파싱합니다.
최종 데이터는 JSON으로 저장되며, Chrome Extension Bap Time with SSAFY에서 바로 사용할 수 있습니다.
- Chrome Web Store: Bap Time with SSAFY
작성 시점 기준
- 사용자 461명
- 평점 5.0 / 5.0 (리뷰 7개)
멀티캠퍼스 식단 정보는 층별로 제공 방식이 다릅니다.
- 20층 식단은 API 기반으로 조회 가능
- 10층 식단은 주간 식단표 이미지(PNG) 형태로 공유
이 프로젝트는 서로 다른 두 소스를 자동으로 수집하고, 확장 프로그램에서 바로 사용할 수 있는 통일된 JSON 데이터로 정규화하기 위해 만들었습니다.
- Welstory API 기반 식단 수집
- GitHub Actions로 매일 자동 실행
- 결과를
data/YYYY-MM-DD.json형식으로 저장 - 메뉴 이미지 URL 및 영양 정보까지 함께 관리
- Mattermost에 업로드된 최신 주간 식단표 PNG 자동 수집
images/에 임시 저장 후 Google Gemini API로 파싱- 결과를
data-10f/YYYY-MM-DD.json형식으로 저장 - 도시락 / 브런치 / 샐러드 등 코스 단위 식단과 개별 품목 리스트를 함께 관리
- 생성된 JSON 데이터를 Chrome Extension에서 바로 사용
- 사용자는 브라우저에서 손쉽게 식단 확인 가능
Welstory API
↓
GitHub Actions
↓
fetch-menu.js
↓
data/YYYY-MM-DD.json 생성
Mattermost 식단표 업로드
↓
GitHub Actions (fetch-ssafy-menu.yml)
↓
images/ 에 최신 10층 식단표 PNG 저장
↓
GitHub Actions (parse-10f-menu.yml)
↓
parse-10f-menu.js 실행
↓
data-10f/YYYY-MM-DD.json 생성
.
├── .github/
│ └── workflows/
│ ├── fetch-menu.yml
│ ├── fetch-ssafy-menu.yml
│ └── parse-10f-menu.yml
├── data/
├── data-10f/
├── images/
├── docs/
│ └── images/
│ ├── system-overview.png
│ ├── chrome-web-store.png
│ └── extension-popup.png
├── multicampus-menu-extension/
├── fetch-menu.js
└── parse-10f-menu.js
- workflow:
fetch-menu.yml - schedule: 매일 실행
- output:
data/YYYY-MM-DD.json
- workflow:
fetch-ssafy-menu.yml - schedule: 평일 오전 9시
동작 방식
Mattermost 로그인
→ 최신 식단표 게시글 조회
→ 10층 PNG 선택
→ images/ 저장
→ Git push
- workflow:
parse-10f-menu.yml - trigger:
images/**변경
동작 방식
PNG 파싱
→ JSON 생성
→ data-10f 저장
→ 성공 시 images PNG 삭제
WELSTORY_USERNAME
WELSTORY_PASSWORD
GEMINI_API_KEY
MM_LOGIN_JSON
GH_PAT
{
"login_id": "your_id",
"password": "your_password",
"token": "",
"deviceId": ""
}Repository access: Only this repository
Permissions: Contents -> Read and Write
GitHub Actions 탭에서 아래 워크플로를 실행합니다.
Fetch Menu Data
GitHub Actions에서 아래 워크플로를 실행합니다.
Fetch SSAFY 10F Menu Image
직접 이미지를 넣고 파싱을 트리거합니다.
cp "멀티캠퍼스(10층)_주간식단.png" images/
git add images/
git commit -m "Add 10F menu image"
git push{
"date": "2026-03-27",
"dayOfWeek": "금요일",
"restaurant": "멀티캠퍼스 10층",
"mealTime": "점심",
"meals": [
{
"name": "폭찹스테이크, 꽈리고추매추리알조림, 백미밥, 호박고추장찌개, 들깨콩나물, 포기김치",
"courseName": "도시락",
"setName": "10층 공존식단",
"items": [
"폭찹스테이크",
"꽈리고추매추리알조림",
"백미밥",
"호박고추장찌개",
"들깨콩나물",
"포기김치"
]
},
{
"name": "함박스테이크샌드위치, & 마카로니샐러드, & 딸기드레싱, & 음료",
"courseName": "브런치",
"setName": "10층 공존식단",
"items": [
"함박스테이크샌드위치",
"마카로니샐러드",
"딸기드레싱",
"음료"
]
},
{
"name": "참치마요바게트샐러드, & 딸기드레싱, & 음료",
"courseName": "샐러드",
"setName": "10층 공존식단",
"items": [
"참치마요바게트샐러드",
"딸기드레싱",
"음료"
]
}
],
"updatedAt": "2026-03-24T02:42:06.661Z"
}{
"date": "2026-04-01",
"restaurant": "멀티캠퍼스",
"mealTime": "점심",
"meals": [
{
"name": "햄버그스테이크",
"courseName": "A:한식",
"setName": "햄버그스테이크&토마토소스스파게티",
"photoUrl": "http://samsungwelstory.com/data/manager/recipe/main/100026/1386.png",
"nutrition": [
{
"name": "햄버그스테이크",
"isMain": true,
"calorie": 371,
"carbohydrate": 19,
"sugar": 4,
"fiber": 4,
"fat": 24,
"protein": 20
},
{
"name": "토마토소스스파게티",
"isMain": false,
"calorie": 283,
"carbohydrate": 59,
"sugar": 9,
"fiber": 3,
"fat": 2,
"protein": 9
},
{
"name": "미니현미밥&후리가케",
"isMain": false,
"calorie": 125,
"carbohydrate": 29,
"sugar": 0,
"fiber": 0,
"fat": 0,
"protein": 2
},
{
"name": "유부장국",
"isMain": false,
"calorie": 45,
"carbohydrate": 3,
"sugar": 2,
"fiber": 1,
"fat": 2,
"protein": 3
},
{
"name": "떡볶이",
"isMain": false,
"calorie": 354,
"carbohydrate": 74,
"sugar": 11,
"fiber": 1,
"fat": 2,
"protein": 7
},
{
"name": "반달감자튀김(웻지감자)",
"isMain": false,
"calorie": 168,
"carbohydrate": 22,
"sugar": 0,
"fiber": 2,
"fat": 8,
"protein": 2
},
{
"name": "더운채소",
"isMain": false,
"calorie": 67,
"carbohydrate": 5,
"sugar": 1,
"fiber": 1,
"fat": 5,
"protein": 1
},
{
"name": "스위트콘",
"isMain": false,
"calorie": 25,
"carbohydrate": 5,
"sugar": 2,
"fiber": 1,
"fat": 0,
"protein": 1
},
{
"name": "그린샐러드",
"isMain": false,
"calorie": 12,
"carbohydrate": 3,
"sugar": 1,
"fiber": 1,
"fat": 0,
"protein": 1
},
{
"name": "드레싱",
"isMain": false,
"calorie": 23,
"carbohydrate": 4,
"sugar": 4,
"fiber": 0,
"fat": 1,
"protein": 0
},
{
"name": "오이피클",
"isMain": false,
"calorie": 64,
"carbohydrate": 15,
"sugar": 16,
"fiber": 1,
"fat": 0,
"protein": 0
},
{
"name": "아이스티",
"isMain": false,
"calorie": 56,
"carbohydrate": 14,
"sugar": 14,
"fiber": 0,
"fat": 0,
"protein": 0
}
]
}
],
"updatedAt": "2026-03-23T21:25:38.759Z"
}export WELSTORY_USERNAME="your_username"
export WELSTORY_PASSWORD="your_password"
node fetch-menu.jsexport GEMINI_API_KEY="your_gemini_api_key"
node parse-10f-menu.js images/menu.pngimages/ 폴더는 임시 저장 폴더입니다.
파싱 성공 시 PNG는 삭제됩니다.
최종 데이터는 data-10f 폴더에 저장됩니다.
MM_LOGIN_JSON에는 비밀번호가 포함되므로
GitHub Secrets에만 저장해야 합니다.
GH_PAT는 workflow chain을 위해 사용됩니다.
권한은 최소로 설정하세요.
- Node.js
- GitHub Actions
- Welstory API
- Mattermost
- Google Gemini API
- JSON 기반 데이터 관리
- Chrome Extension 연동
MIT License