From 6c4f5f799e29a02a4115aa1b80d25e8c2ef29f0a Mon Sep 17 00:00:00 2001 From: imnyang Date: Sat, 31 May 2025 01:07:25 +0900 Subject: [PATCH] v2.1.0 --- app/index.ts | 28 +++- app/lib/image.ts | 16 ++- app/lib/meal.ts | 26 +++- app/playground.ts | 27 +++- temp.ics | 330 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 416 insertions(+), 11 deletions(-) create mode 100644 temp.ics diff --git a/app/index.ts b/app/index.ts index 50535cc..ad47a12 100644 --- a/app/index.ts +++ b/app/index.ts @@ -1,3 +1,4 @@ +import { Discord } from "./lib/discord"; import { CreateImage } from "./lib/image"; import { Login, Upload } from "./lib/instagram"; @@ -29,6 +30,7 @@ async function main() { const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); YYMMDD = tomorrow.toISOString().slice(0, 10).replace(/-/g, "").toString(); + YYMMDD = "20250602"; console.log("πŸ“… | date:", YYMMDD); @@ -51,7 +53,7 @@ async function main() { } try { console.time("πŸ“· | Create Post Image"); - await CreateImage.PostMeal(YYMMDD); + const NutritionInfo = await CreateImage.PostMeal(YYMMDD); console.timeEnd("πŸ“· | Create Post Image"); console.time("πŸ“± | Create Story Image"); @@ -59,15 +61,37 @@ async function main() { console.timeEnd("πŸ“± | Create Story Image"); console.time("πŸ“€ | Upload Post"); + + let NutritionInfoText = "" + + const entries = Object.entries(NutritionInfo ?? {}).filter(([_, value]) => value.toString().length > 0); + entries.forEach(([name, value], idx) => { + NutritionInfoText += `${name} : ${value.toString().replace(",", ", ")}`; + if (idx !== entries.length - 1) { + NutritionInfoText += "\n"; + } + }); + await Upload.Post( `./temp/${YYMMDD}.png`, - `#μΈμ²œμƒμ •μ€‘ν•™κ΅ #상정쀑학ꡐ #급식 \n${YYMMDD}일자 급식` + [ + `🍽️ | ${YYMMDD}일자 급식`, + "===========================", + "⚠️ μ•Œλ ˆλ₯΄κΈ° 유발 κ°€λŠ₯ 성뢄이 ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.", + NutritionInfoText, + "===========================", + "#μΈμ²œμƒμ •μ€‘ν•™κ΅ #상정쀑학ꡐ #급식" + ].join("\n") ); console.timeEnd("πŸ“€ | Upload Post"); console.time("πŸ“€ | Upload Story"); await Upload.Story(`./temp/${YYMMDD}-story.png`, YYMMDD); console.timeEnd("πŸ“€ | Upload Story"); + + console.time("πŸ€– | Discord Webhook"); + await Discord(YYMMDD); + console.timeEnd("πŸ€– | Discord Webhook"); } catch (error) { console.error("❌ | Error during image creation or upload:", error); } finally { diff --git a/app/lib/image.ts b/app/lib/image.ts index 1597579..0793962 100644 --- a/app/lib/image.ts +++ b/app/lib/image.ts @@ -1,7 +1,7 @@ import path from "path"; import { join } from "path"; import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas"; -import { getMealInfo, NameToEmoji } from "./meal"; // 이 ν•¨μˆ˜μ˜ λ‚΄μš©μ€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ κ·ΈλŒ€λ‘œ λ‘‘λ‹ˆλ‹€. +import { getMealInfo, getNutritionInfo, NameToEmoji, removeNutritionInfo } from "./meal"; // 이 ν•¨μˆ˜μ˜ λ‚΄μš©μ€ μ œκ³΅λ˜μ§€ μ•Šμ•˜μœΌλ―€λ‘œ κ·ΈλŒ€λ‘œ λ‘‘λ‹ˆλ‹€. import { getAllSchedules } from "./schedule"; import { isVTS } from "./vts"; @@ -9,7 +9,7 @@ GlobalFonts.registerFromPath('./template/Pretendard-Bold.ttf', 'Pretendard Bold' GlobalFonts.registerFromPath('./template/NotoColorEmoji-Regular.ttf', 'NotoColorEmoji Regular') export class CreateImage { - static async PostMeal(MLSV_YMD: string): Promise { + static async PostMeal(MLSV_YMD: string): Promise<{ [key: string]: any } | undefined> { const mealInfo = await getMealInfo(MLSV_YMD); const img = await loadImage(path.join("./template/skeleton.png")); const canvas = createCanvas(img.width, img.height); @@ -20,7 +20,7 @@ export class CreateImage { ctx.fillStyle = "white"; ctx.textAlign = "left"; - const lines = mealInfo.meal.split("\n").reverse(); + const lines = removeNutritionInfo(mealInfo.meal).split("\n").reverse(); let emojis = (await NameToEmoji(lines.toString())).split(","); if (lines.length !== emojis.length) { @@ -65,6 +65,16 @@ export class CreateImage { console.log("🍲 | Meal Info Image Saved"); } catch (error) { console.error("Error saving meal info image:", error); + } finally { + const meals = removeNutritionInfo(mealInfo.meal).split("\n"); + const nutritionInfo = getNutritionInfo(mealInfo.meal); + const retrunValue: { [key: string]: any } = {}; + for (let i = 0; i < meals.length; i++) { + const meal = (meals[i] ?? "").trim(); + retrunValue[meal] = nutritionInfo[i] || []; + } + + return retrunValue; } } diff --git a/app/lib/meal.ts b/app/lib/meal.ts index add6322..4140714 100644 --- a/app/lib/meal.ts +++ b/app/lib/meal.ts @@ -5,11 +5,31 @@ const KEY = process.env.NEIS_API_KEY; export function removeNutritionInfo(value: string): string { const lines = value.trim().split('\n'); - const cleanedLines = lines.map(line => line.replace(/\(.*?\)/g, '').trim()); + const cleanedLines = lines.map(line => line.replace(/\s*\([\d.,]+\)/g, '').trim()); const result = cleanedLines.join('\n'); return result; } +const nutritionList = [ + "λ‚œλ₯˜", "우유", "λ©”λ°€", "땅콩", "λŒ€λ‘", "λ°€", "κ³ λ“±μ–΄", "게", "μƒˆμš°", "돼지고기", + "λ³΅μˆ­μ•„", "ν† λ§ˆν† ", "μ•„ν™©μ‚°λ₯˜", "ν˜Έλ‘", "λ‹­κ³ κΈ°", "μ‡ κ³ κΈ°", "μ˜€μ§•μ–΄", "쑰개λ₯˜(κ΅΄, 전볡, 홍합 포함)", "잣" +]; + +export function getNutritionInfo(value: string): string[][] { + const lines = value.trim().split('\n'); + return lines.map(line => { + const indexes = line + .replace(/[()\s]/g, "") + .split(".") + .map(v => parseInt(v, 10) - 1) + .filter(i => i >= 0 && i < nutritionList.length); + return indexes + .map(i => nutritionList[i]) + .filter((item): item is string => typeof item === "string"); + }); +} + + export async function getMealInfo(MLSV_YMD: string): Promise<{ meal: string; date: string, kcal: string }> { const url = `https://open.neis.go.kr/hub/mealServiceDietInfo?Type=json&ATPT_OFCDC_SC_CODE=E10&SD_SCHUL_CODE=7331071&MLSV_YMD=${MLSV_YMD}&KEY=${KEY}`; const response = await fetch(url); @@ -17,7 +37,7 @@ export async function getMealInfo(MLSV_YMD: string): Promise<{ meal: string; dat // @ts-ignore const DDISH_NM = data.mealServiceDietInfo[1].row[0].DDISH_NM; return { - meal: removeNutritionInfo(DDISH_NM.replace(//gi, '\n')), + meal: DDISH_NM.replace(//gi, '\n'), date: MLSV_YMD, // @ts-ignore @@ -32,7 +52,7 @@ export async function NameToEmoji(name: string): Promise { throw new Error("GITHUB_TOKEN environment variable is not set."); } const endpoint = "https://models.github.ai/inference"; - const model = "openai/gpt-4.1"; + const model = "openai/gpt-4.1-mini"; const client = ModelClient( endpoint, diff --git a/app/playground.ts b/app/playground.ts index 374e72f..214e1dd 100644 --- a/app/playground.ts +++ b/app/playground.ts @@ -2,14 +2,24 @@ import { CreateImage } from "./lib/image"; //const YYMMDD = new Date().toISOString().slice(0, 10).replace(/-/g, "").toString(); -const YYMMDD = "20250509" +const YYMMDD = "20250530" //console.log(YYMMDD); //Discord("20250509") async function run() { console.time("Post"); - await CreateImage.PostMeal(YYMMDD); + const post = await CreateImage.PostMeal(YYMMDD); console.timeEnd("Post"); + console.log("Post created:", post); + let NutritionInfoText = ""; + const entries = Object.entries(post ?? {}).filter(([_, value]) => value.toString().length > 0); + entries.forEach(([name, value], idx) => { + NutritionInfoText += `${name} : ${value.toString().replace(",", ", ")}`; + if (idx !== entries.length - 1) { + NutritionInfoText += "\n"; + } + }); + console.log("Nutrition Info Text:", NutritionInfoText); console.time("Story"); await CreateImage.ConvertToStory(`./temp/${YYMMDD}.png`); @@ -18,6 +28,8 @@ async function run() { run(); +import { getAllSchedules } from "./lib/schedule"; + //import { CreateImage } from "./lib/image"; @@ -39,4 +51,13 @@ VTSList().then(results => { } else { console.log("\n5번째 ν–‰μ—μ„œ λ…Έλž€μƒ‰ 배경의 셀을 μ°Ύμ§€ λͺ»ν–ˆκ±°λ‚˜, 'V.T.S.' 쑰건 뢈좩쑱."); } -});*/ \ No newline at end of file +});*/ +/* +getAllSchedules() + .then((schedules) => { + console.log("학사 일정:", schedules); + }) + .catch((error) => { + console.error("Error fetching schedules:", error); + }); + */ \ No newline at end of file diff --git a/temp.ics b/temp.ics new file mode 100644 index 0000000..115df4b --- /dev/null +++ b/temp.ics @@ -0,0 +1,330 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Google Inc//Google Calendar 70.9054//EN +CALSCALE:GREGORIAN +METHOD:PUBLISH +X-WR-CALNAME:2025λ…„ 학사일정 +X-WR-TIMEZONE:Asia/Seoul +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250301 +DTEND;VALUE=DATE:20250302 +DTSTAMP:20250514T115950Z +UID:20250301-μ‚ΌμΌμ ˆ@today.isangjeong@al-1s.kr +SUMMARY:μ‚ΌμΌμ ˆ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250304 +DTEND;VALUE=DATE:20250305 +DTSTAMP:20250514T115950Z +UID:20250304-μž…ν•™μ‹@today.isangjeong@al-1s.kr +SUMMARY:μž…ν•™μ‹ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250305 +DTEND;VALUE=DATE:20250306 +DTSTAMP:20250514T115950Z +UID:20250305-창체-봉사@today.isangjeong@al-1s.kr +SUMMARY:창체/봉사 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250312 +DTEND;VALUE=DATE:20250313 +DTSTAMP:20250514T115950Z +UID:20250312-창체@today.isangjeong@al-1s.kr +SUMMARY:창체 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250313 +DTEND;VALUE=DATE:20250314 +DTSTAMP:20250514T115950Z +UID:20250313-동아리@today.isangjeong@al-1s.kr +SUMMARY:동아리 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250317 +DTEND;VALUE=DATE:20250318 +DTSTAMP:20250514T115950Z +UID:20250317-ν•™λΆ€λͺ¨μ΄νšŒ@today.isangjeong@al-1s.kr +SUMMARY:ν•™λΆ€λͺ¨μ΄νšŒ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250320 +DTEND;VALUE=DATE:20250321 +DTSTAMP:20250514T115950Z +UID:20250320-κΈ°μ΄ˆν•™λ ₯진단검사@today.isangjeong@al-1s.kr +SUMMARY:κΈ°μ΄ˆν•™λ ₯진단검사 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250326 +DTEND;VALUE=DATE:20250327 +DTSTAMP:20250514T115950Z +UID:20250326-1ν•™κΈ°-방과후학ꡐ-μ‹œμž‘@today.isangjeong@al-1s.kr +SUMMARY:1ν•™κΈ° 방과후학ꡐ μ‹œμž‘ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250331 +DTEND;VALUE=DATE:20250401 +DTSTAMP:20250514T115950Z +UID:20250331-창체-동아리@today.isangjeong@al-1s.kr +SUMMARY:창체/동아리 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250430 +DTEND;VALUE=DATE:20250502 +DTSTAMP:20250514T115950Z +UID:20250430-1ν•™κΈ°-1νšŒκ³ μ‚¬@today.isangjeong@al-1s.kr +SUMMARY:1ν•™κΈ° 1νšŒκ³ μ‚¬(2,3ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250506 +DTEND;VALUE=DATE:20250507 +DTSTAMP:20250514T115950Z +UID:20250506-λŒ€μ²΄νœ΄μΌ@today.isangjeong@al-1s.kr +SUMMARY:λŒ€μ²΄νœ΄μΌ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250509 +DTEND;VALUE=DATE:20250510 +DTSTAMP:20250514T115950Z +UID:20250509-동아리@today.isangjeong@al-1s.kr +SUMMARY:동아리 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250513 +DTEND;VALUE=DATE:20250514 +DTSTAMP:20250514T115950Z +UID:20250513-리더십캠프@today.isangjeong@al-1s.kr +SUMMARY:리더십캠프 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250516 +DTEND;VALUE=DATE:20250517 +DTSTAMP:20250514T115950Z +UID:20250516-μŠ€ν¬μΈ λŒ€μΆ•μ œ@today.isangjeong@al-1s.kr +SUMMARY:μŠ€ν¬μΈ λŒ€μΆ•μ œ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250521 +DTEND;VALUE=DATE:20250522 +DTSTAMP:20250514T115950Z +UID:20250521-μ§„λ‘œμ²΄ν—˜ν™œλ™@today.isangjeong@al-1s.kr +SUMMARY:μ§„λ‘œμ²΄ν—˜ν™œλ™(3ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250523 +DTEND;VALUE=DATE:20250524 +DTSTAMP:20250514T115950Z +UID:20250523-μ§„λ‘œμ²΄ν—˜ν™œλ™@today.isangjeong@al-1s.kr +SUMMARY:μ§„λ‘œμ²΄ν—˜ν™œλ™(1,2ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250530 +DTEND;VALUE=DATE:20250531 +DTSTAMP:20250514T115950Z +UID:20250530-창체-동아리@today.isangjeong@al-1s.kr +SUMMARY:창체/동아리 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250604 +DTEND;VALUE=DATE:20250605 +DTSTAMP:20250514T115950Z +UID:20250604-ꡐ과연계-μ²΄ν—˜ν•™μŠ΅@today.isangjeong@al-1s.kr +SUMMARY:ꡐ과연계 μ²΄ν—˜ν•™μŠ΅(2ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250605 +DTEND;VALUE=DATE:20250606 +DTSTAMP:20250514T115950Z +UID:20250605-μž¬λŸ‰νœ΄μ—…μΌ@today.isangjeong@al-1s.kr +SUMMARY:μž¬λŸ‰νœ΄μ—…μΌ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250627 +DTEND;VALUE=DATE:20250628 +DTSTAMP:20250514T115950Z +UID:20250627-1ν•™κΈ°-방과후학ꡐ-μ’…κ°•@today.isangjeong@al-1s.kr +SUMMARY:1ν•™κΈ° 방과후학ꡐ μ’…κ°• +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250707 +DTEND;VALUE=DATE:20250710 +DTSTAMP:20250514T115950Z +UID:20250707-1ν•™κΈ°-2νšŒκ³ μ‚¬@today.isangjeong@al-1s.kr +SUMMARY:1ν•™κΈ° 2νšŒκ³ μ‚¬(2,3ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250721 +DTEND;VALUE=DATE:20250722 +DTSTAMP:20250514T115950Z +UID:20250721-1ν•™λ…„-건강검진@today.isangjeong@al-1s.kr +SUMMARY:1ν•™λ…„ 건강검진 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250722 +DTEND;VALUE=DATE:20250723 +DTSTAMP:20250514T115950Z +UID:20250722-방학식@today.isangjeong@al-1s.kr +SUMMARY:방학식 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250723 +DTEND;VALUE=DATE:20250812 +DTSTAMP:20250514T115950Z +UID:20250723-여름방학@today.isangjeong@al-1s.kr +SUMMARY:여름방학 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250812 +DTEND;VALUE=DATE:20250813 +DTSTAMP:20250514T115950Z +UID:20250812-κ°œν•™μ‹@today.isangjeong@al-1s.kr +SUMMARY:κ°œν•™μ‹ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250827 +DTEND;VALUE=DATE:20250828 +DTSTAMP:20250514T115950Z +UID:20250827-2ν•™κΈ°-방과후학ꡐ-μ‹œμž‘@today.isangjeong@al-1s.kr +SUMMARY:2ν•™κΈ° 방과후학ꡐ μ‹œμž‘ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250903 +DTEND;VALUE=DATE:20250904 +DTSTAMP:20250514T115950Z +UID:20250903-ν•™λΆ€λͺ¨-κ³΅κ°œμˆ˜μ—…@today.isangjeong@al-1s.kr +SUMMARY:ν•™λΆ€λͺ¨ κ³΅κ°œμˆ˜μ—… +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250925 +DTEND;VALUE=DATE:20250926 +DTSTAMP:20250514T115950Z +UID:20250925-μ§„λ‘œμ²΄ν—˜ν™œλ™@today.isangjeong@al-1s.kr +SUMMARY:μ§„λ‘œμ²΄ν—˜ν™œλ™ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20250926 +DTEND;VALUE=DATE:20250927 +DTSTAMP:20250514T115950Z +UID:20250926-창체-동아리@today.isangjeong@al-1s.kr +SUMMARY:창체/동아리 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251010 +DTEND;VALUE=DATE:20251011 +DTSTAMP:20250514T115950Z +UID:20251010-μž¬λŸ‰νœ΄μ—…μΌ@today.isangjeong@al-1s.kr +SUMMARY:μž¬λŸ‰νœ΄μ—…μΌ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251016 +DTEND;VALUE=DATE:20251018 +DTSTAMP:20250514T115950Z +UID:20251016-2ν•™κΈ°-1νšŒκ³ μ‚¬@today.isangjeong@al-1s.kr +SUMMARY:2ν•™κΈ° 1νšŒκ³ μ‚¬(1,2ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251020 +DTEND;VALUE=DATE:20251021 +DTSTAMP:20250514T115950Z +UID:20251020-창체@today.isangjeong@al-1s.kr +SUMMARY:창체 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251029 +DTEND;VALUE=DATE:20251101 +DTSTAMP:20250514T115950Z +UID:20251029-2ν•™κΈ°-1νšŒκ³ μ‚¬@today.isangjeong@al-1s.kr +SUMMARY:2ν•™κΈ° 1νšŒκ³ μ‚¬(3ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251104 +DTEND;VALUE=DATE:20251105 +DTSTAMP:20250514T115950Z +UID:20251104-슀포츠-클럽데이@today.isangjeong@al-1s.kr +SUMMARY:슀포츠 클럽데이(1,2ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251107 +DTEND;VALUE=DATE:20251108 +DTSTAMP:20250514T115950Z +UID:20251107-슀포츠-클럽데이@today.isangjeong@al-1s.kr +SUMMARY:슀포츠 클럽데이(3ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251113 +DTEND;VALUE=DATE:20251114 +DTSTAMP:20250514T115950Z +UID:20251113-μž¬λŸ‰νœ΄μ—…μΌ@today.isangjeong@al-1s.kr +SUMMARY:μž¬λŸ‰νœ΄μ—…μΌ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251114 +DTEND;VALUE=DATE:20251115 +DTSTAMP:20250514T115950Z +UID:20251114-κ°œκ΅κΈ°λ…μΌ@today.isangjeong@al-1s.kr +SUMMARY:κ°œκ΅κΈ°λ…μΌ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251120 +DTEND;VALUE=DATE:20251121 +DTSTAMP:20250514T115950Z +UID:20251120-동아리@today.isangjeong@al-1s.kr +SUMMARY:동아리 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251121 +DTEND;VALUE=DATE:20251122 +DTSTAMP:20250514T115950Z +UID:20251121-μƒ˜μ†ŒμŠ¬μ œ@today.isangjeong@al-1s.kr +SUMMARY:μƒ˜μ†ŒμŠ¬μ œ +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251128 +DTEND;VALUE=DATE:20251129 +DTSTAMP:20250514T115950Z +UID:20251128-동아리@today.isangjeong@al-1s.kr +SUMMARY:동아리 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251205 +DTEND;VALUE=DATE:20251206 +DTSTAMP:20250514T115950Z +UID:20251205-2ν•™κΈ°-방과후학ꡐ-μ’…κ°•@today.isangjeong@al-1s.kr +SUMMARY:2ν•™κΈ° 방과후학ꡐ μ’…κ°• +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251206 +DTEND;VALUE=DATE:20251207 +DTSTAMP:20250514T115950Z +UID:20251206-상정-μ†Œν†΅μ˜-λ‚ @today.isangjeong@al-1s.kr +SUMMARY:상정 μ†Œν†΅μ˜ λ‚  +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251216 +DTEND;VALUE=DATE:20251219 +DTSTAMP:20250514T115950Z +UID:20251216-2ν•™κΈ°-2νšŒκ³ μ‚¬@today.isangjeong@al-1s.kr +SUMMARY:2ν•™κΈ° 2νšŒκ³ μ‚¬(1,2ν•™λ…„) +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20251224 +DTEND;VALUE=DATE:20251225 +DTSTAMP:20250514T115950Z +UID:20251224-창체@today.isangjeong@al-1s.kr +SUMMARY:창체 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20260107 +DTEND;VALUE=DATE:20260108 +DTSTAMP:20250514T115950Z +UID:20260107-쒅업식-및-쑸업식@today.isangjeong@al-1s.kr +SUMMARY:쒅업식 및 쑸업식 +END:VEVENT +BEGIN:VEVENT +DTSTART;VALUE=DATE:20260108 +DTEND;VALUE=DATE:20260228 +DTSTAMP:20250514T115950Z +UID:20260108-κ²¨μšΈλ°©ν•™@today.isangjeong@al-1s.kr +SUMMARY:κ²¨μšΈλ°©ν•™ +END:VEVENT +END:VCALENDAR \ No newline at end of file