콘텐츠로 이동

기숙사·학식 봇

오늘의 기숙사 식단, 학식, 기숙사 공지를 메시지 문자열로 만듭니다. 슬랙, 디스코드, 카카오 등 보내는 쪽에 붙이면 됩니다. 로그인이 필요 없습니다.

필드 이름은 Python snake_case 기준이고, Node.js는 camelCase 객체 인자({ rows: 3 }), Java는 위치 인자입니다. 로그인은 필요 없습니다.

메서드 파라미터 타입 필수 기본값 설명
dormitory_menu location 문자열 아니오 SJ 기숙사 위치 코드
cafeteria_menu place_id 문자열 예 학교의 식당 번호 (호출하는 쪽이 넘김)
cafeteria_places max_place_id 정수 아니오 10 식당 번호 1~이 값을 훑어 목록을 만듦. 식당이 늘면 올림
dormitory_notices rows 정수 아니오 10 한 번에 받을 공지 수
page 정수 아니오 1 공지 쪽 번호
keyword 문자열 아니오 빈 문자열 제목 검색어
location 문자열 아니오 SJ 기숙사 위치 코드

예시 코드가 메시지로 만드는 원본 데이터입니다. 필드 이름은 Python snake_case이고, Node.js는 camelCase(meal_name → mealName), Java는 getter(getMealName())입니다. 값은 형태를 보여 주기 위한 예시입니다.

dormitory_menu(): 하루 한 건, 최대 7일치 목록

[
{
"date": "2026-03-02",
"breakfast": "쌀밥 된장국 계란말이",
"lunch": "쌀밥 육개장 부추전 배추김치",
"dinner": "쌀밥 북어국 안동찜닭 배추김치",
"alternative": "식권 1장으로 간편식 교환"
}
]
필드 의미
date 날짜 (yyyy-MM-dd)
breakfast / lunch / dinner 조식 / 중식 / 석식. 학교 원문이라 반찬이 한 문자열에 이어져 있음
alternative 대체 메뉴 (없으면 빈 문자열)

cafeteria_menu(place_id): 메뉴 한 줄이 한 건. 식당에 따라 날짜가 있는 주간 메뉴이거나 날짜가 빈 상시 메뉴

[
{
"place_id": "1",
"place_name": "계절밥상",
"building_name": "군자관",
"location": "6층",
"date": "2026-03-02",
"day": "월",
"meal_type_id": "1",
"meal_name": "중식",
"menu_name": "된장국 · 제육볶음 · 배추김치",
"price": "7000"
},
{
"place_id": "3",
"place_name": "진관키친",
"building_name": "진관홀",
"location": "B1층",
"date": "",
"day": "",
"meal_type_id": "",
"meal_name": "",
"menu_name": "라면",
"price": "3300"
}
]
필드 의미
place_id / place_name 식당 번호 / 이름
building_name / location 건물 / 층
date / day 날짜 / 요일. 상시 메뉴 식당은 빈 문자열
meal_name 식사 구분 (중식, 석식 등). 상시 메뉴는 빈 문자열
menu_name 메뉴 (여러 개는 ·로 이어짐)
price 가격 (원, 문자열)

dormitory_notices(rows=3): 공지 목록. 고정 공지(pinned)와 일반 공지(items)가 나뉘어 있음

{
"total": 120,
"pinned": [],
"items": [
{
"id": "37570",
"title": "[기숙사] 정기소독 실시 안내",
"date": "2026-03-02",
"views": 426,
"file_name": "안내.png"
}
]
}
필드 의미
total 전체 공지 수 (정수)
pinned / items 고정 공지 / 일반 공지 목록. 항목 모양은 같음
id / title / date 공지 번호 / 제목 / 등록일
views / file_name 조회수 (정수) / 첨부 파일 이름 (없으면 빈 문자열)

전체 필드는 기숙사와 학식을 보세요.

실패 시: 학교 서버가 실패하면 예외 없이 마지막 캐시 또는 빈 목록(dormitory_notices는 total이 0인 빈 결과)을 돌려줍니다. 빈 목록은 「데이터 없음」과 구분되지 않으니 화면에 안내를 두세요.

  • 식당 번호는 학교 값입니다. 번호는 바뀔 수 있고, 어느 식당인지는 결과의 place_name으로 알 수 있습니다. 빈 목록은 번호가 틀렸을 때와 그날 메뉴가 없을 때 모두 나옵니다. 식당 목록은 cafeteria_places()로도 받을 수 있습니다. 학식
  • 식당에 따라 일주일치가 날짜와 함께 오거나, 날짜 없는 상시 메뉴가 옵니다. 예시는 둘 다 처리합니다.
  • 기숙사 식단은 7일치이며 dormitory_menu(location="SJ")입니다. 기숙사
  • 학교 서버에 부담을 주지 않도록 봇은 하루 몇 번만 갱신해도 충분합니다 (라이브러리 캐시가 이미 24시간).
import java.time.LocalDate;
import java.util.List;
import kr.suhsaechan.sejongpeia.publicdata.SejongPublicData;
import kr.suhsaechan.sejongpeia.publicdata.model.CafeteriaMenuItem;
import kr.suhsaechan.sejongpeia.publicdata.model.DormitoryMenuDay;
class CampusInfoBot {
static String dormitoryMenuMessage(SejongPublicData publicData) {
String today = LocalDate.now().toString();
List<DormitoryMenuDay> menuDays = publicData.dormitoryMenu();
DormitoryMenuDay menuDay = menuDays.stream().filter(day -> today.equals(day.getDate())).findFirst()
.orElse(menuDays.isEmpty() ? null : menuDays.get(0));
if (menuDay == null) {
return "기숙사 식단을 불러오지 못했습니다";
}
return "[기숙사 " + menuDay.getDate() + "]\n조식: " + menuDay.getBreakfast()
+ "\n중식: " + menuDay.getLunch() + "\n석식: " + menuDay.getDinner();
}
static String cafeteriaMessage(SejongPublicData publicData, String placeId) {
String today = LocalDate.now().toString();
List<CafeteriaMenuItem> items = publicData.cafeteriaMenu(placeId);
if (items.isEmpty()) {
return placeId + "번 식당: 메뉴 없음";
}
List<CafeteriaMenuItem> todaysItems = items.stream()
.filter(item -> item.getDate().isEmpty() || item.getDate().equals(today)).toList();
List<CafeteriaMenuItem> shownItems = todaysItems.isEmpty() ? items : todaysItems;
StringBuilder message = new StringBuilder("[" + shownItems.get(0).getPlaceName() + "]");
shownItems.stream().limit(5).forEach(item ->
message.append("\n").append(item.getMealName()).append(" ").append(item.getMenuName()).append(" ").append(item.getPrice()).append("원"));
return message.toString();
}
}