today.isangjeong/app/lib/schedule.ts
2025-05-09 20:11:41 +09:00

105 lines
3.3 KiB
TypeScript

import { writeFileSync, existsSync, readFileSync } from "fs";
import * as cheerio from "cheerio";
const CACHE_FILE = "./temp/scheduleCache.json";
const URLS = {
section1:
"https://isangjeong.icems.kr/schdList.do?section=1&m=021101&s=isangjeong",
section2:
"https://isangjeong.icems.kr/schdList.do?section=2&m=021101&s=isangjeong",
};
const MONTH_XPATH_MAP: Record<
string,
{ section: keyof typeof URLS; divIndex: number }
> = {
"03": { section: "section1", divIndex: 2 },
"04": { section: "section1", divIndex: 3 },
"05": { section: "section1", divIndex: 4 },
"06": { section: "section1", divIndex: 5 },
"07": { section: "section1", divIndex: 6 },
"08": { section: "section1", divIndex: 7 },
"09": { section: "section2", divIndex: 2 },
"10": { section: "section2", divIndex: 3 },
"11": { section: "section2", divIndex: 4 },
"12": { section: "section2", divIndex: 5 },
"01": { section: "section2", divIndex: 6 },
"02": { section: "section2", divIndex: 7 },
};
async function fetchSectionSchedule(
url: string,
divIndexes: { month: string; index: number }[]
): Promise<Record<string, { date: string; desc: string }[]>> {
const res = await fetch(url, {
headers: {
"User-Agent": "Mozilla/5.0 (compatible; today.isangjeong/1.0)",
},
});
const html = await res.text();
const $ = cheerio.load(html);
const sectionSchedule: Record<string, { date: string; desc: string }[]> = {};
for (const { month, index } of divIndexes) {
const selector = `#all-scroll > div > form > div > div:nth-of-type(2) > div:nth-of-type(${index}) > dl > dd > ul`;
const items: { date: string; desc: string }[] = [];
$(selector).find("li").each((_, li) => {
const text = $(li).text().trim().replace(/\u00A0/g, " ");
if (text) {
const [date, desc] = text.split(" : ");
if (date && desc) {
items.push({ date: date.trim(), desc: desc.trim() });
}
}
});
sectionSchedule[month] = items;
}
return sectionSchedule;
}
export async function getAllSchedules(
refresh = false
): Promise<Record<string, { date: string; desc: string }[]>> {
if (!refresh && existsSync(CACHE_FILE)) {
const cached = readFileSync(CACHE_FILE, "utf-8");
return JSON.parse(cached);
}
const sectionMap: Record<keyof typeof URLS, { month: string; index: number }[]> = {
section1: [],
section2: [],
};
for (const [month, { section, divIndex }] of Object.entries(MONTH_XPATH_MAP)) {
sectionMap[section].push({ month, index: divIndex });
}
// 병렬로 section1, section2 요청
const promises = (Object.keys(sectionMap) as (keyof typeof URLS)[]).map(
async (section) => {
const url = URLS[section];
try {
const sectionSchedule = await fetchSectionSchedule(url, sectionMap[section]);
console.log(`${section} 완료`);
return sectionSchedule;
} catch (err) {
console.error(`${section} 실패:`, err);
return Object.fromEntries(sectionMap[section].map(({ month }) => [month, []]));
}
}
);
const results = await Promise.all(promises);
const schedule = Object.assign({}, ...results);
writeFileSync(CACHE_FILE, JSON.stringify(schedule, null, 2), "utf-8");
console.log(`📦 캐시 저장됨 → ${CACHE_FILE}`);
return schedule;
}