⏰ 고등학교, 시간 관리의 중요성
작년까지만 해도 저는 학교를 다니고 있었어요. 하라는 공부는 안하고 여러가지 딴짓을 많이 했었죠. 하지만 하루는 고작 24시간, 인간이 딴짓을 할 수 있는 시간은 한정되어 있습니다. 이는 **수면 시간 보존의 법칙(Law of conservation of sleep hours)**에 의한 것인데요. 인간 중에서도 나약한 편에 속하는 저는 무슨 일이 있어도 8시간은 채워 자게 됩니다. 그리고 미인은 잠꾸러기인 법이니까요. 그렇기 때문에 제가 코딩하는 시간을 늘리려면 학교 수업 시간을 최대한 효율적으로, 즉 자는 데 사용해야 했습니다(25%만 자는 데 써도 2시간을 벌 수 있습니다).
고등학교 1학년을 마치면서 선생님들께서는 제게 이제 2학년이 되니 시간을 효율적으로 사용하라는 목표를 마음속에 새기라는, 어... 대충 멋진 말씀을 해 주셨습니다. 선생님 말씀을 잘 듣는 착한 아이인 저는 2020년이 되고 일년 목표를 시간 효율적으로 사용하기로 정했습니다.
하지만 제 멋진 계획은 모두 틀어지게 되었습니다. 코로나 덕분에 저희 학교도 온라인으로 개학을 하게 되면서 말이죠. 어떤 학교는 EBS로 수업 주르륵 들어놓으면 출석 인정되고 그러기도 했다는 것 같은데 아쉽게도 저희 학교는 줌(ZOOM)을 통해 수업이 실시간으로 진행되었습니다.
아아... 내 수면...!
🎁 학교에서 줌을 줌.
줌
이게 줌입니다. 보다시피 온라인 화상 회의 서비스죠(예상했겠지만 선생님을 제외하면 거의 아무도 카메라와 마이크를 켜지 않으려고 합니다. 물론 저도 안 켰습니다. 이걸 왜 키나요);;;
선생님이 회의실을 연 다음 링크를 공유해 주면 학생들이 들어가서 만나는 그런 구조로 수업이 이루어집니다. 생각보다 효율적이죠?
문제는 과목마다 선생님이 다르듯 선생님마다 줌 링크가 달랐던 것에 있습니다. 즉 학생들은 강의실을 찾아가듯이 매 시간 수업이 시작하기 전에 다른 선생님의 링크에 들어가야 했죠.
만약 중간에 졸아서 다음 수업에 들어가지 못하면 수업 시작 전에 인원체크를 하는데 거기서 없는 사람 찾아서 친구들이 전화해 주고 학교에서 연락하고 그래도 없으면 지각 처리라며 혼내고 상담... 으으


그래도 새삼 느낀 점은 제게 참 좋은 친구들이 많았다는 것! 친구들아 고마웠고 사랑한다!!!
맨날 침대에 누워서 자다가 보이스톡 소리를 듣고 일어나서 대답하고 다시 누웠었네요. 하지만 하루는 친구들에게 미안하기도 하고, 이왕 잘 거라면 방해받지 않고 푹 자고 싶어서(?) 이걸 좀 자동화해야겠다고 생각했습니다.
👻 수업 듣기를 자동화해 보자
🏫 수업 들어갈 수는 있어?
제 브라우저에는 더 이상 체크박스가 안 보여서 구글링으로 영어 버전 스크린샷을 가져왔습니다.
줌 클라이언트가 설치된 상태에서, zoommtg://로 시작하는 줌 딥링크를 웹 브라우저로 열면 위와 같은 프롬프트가 표시됩니다. 이때 항상 이러한 유형의 링크를 연결된 앱에서 열기 체크박스를 체크하면 다음부터는 질문 없이 바로 클라이언트가 열립니다.
딥링크를 자세히 보면 Query string으로 action, pwd, confno가 전달되고 있다는 걸 알 수 있습니다. 링크 몇 개를 대조해 보면서 쉽게 그 뜻을 유추해낼 수 있어요.
action은 클라이언트가 실행되고 나서 받은 회의실 정보로 어떤 작업을 수행할지를 말하는 것 같아요.join말고 다른 값으로 뭐가 있을지는 잘 모르겠지만, 저희는 강의실에 들어가는 게 목표기 때문에 그대로 사용하면 되겠네요.pwd는 강의실의 패스워드입니다. 저희 학교는 모든 강의실의 패스워드를dimigo로 통일했었기 때문에 그대로 하드코딩 했었어요.confno는 각각의 강의실에 할당되는 고유 ID인 것 같습니다. 강의실별로 이 값만 저장하면 되네요.
파이썬에는 webbrowser라는 모듈이 있습니다. 시스템 기본 브라우저로 URL을 열 수 있게 하는 함수들이 선언되어 있는데요. 이걸 이용하면 회의실 정보를 받아 자동으로 접속해 주는 함수 open_classroom을 만들 수 있겠네요!
def open_classroom(classroom: dict) -> None: name = classroom['name'] code = classroom['code'] webbrowser.open_new_tab(f'zoommtg://zoom.us/join?action=join&pwd=dimigo&confno={code}') console.print(f'✔ {name} 수업 접속 완료', style='dim')🗓 저... 저기 혹시 시간표 있어?
이제 저희가 만들 프로그램에서 사용할 데이터들을 모아볼게요!
수업 시간 데이터 저장하기
// periods.json{ "조회": ["08:42", "08:57"], "1교시": ["08:58", "09:50"], "2교시": ["09:58", "10:50"], "3교시": ["10:58", "11:50"], "4교시": ["11:58", "12:50"], "5교시": ["13:48", "14:40"], "6교시": ["14:48", "15:40"], "7교시": ["15:48", "16:40"], "종례": ["16:45", "17:55"]}아무래도 수업이 언제 시작해서 언제 끝나는지 알아야 하겠죠?
저희 학교에서는 조회와 종례 역시 담임 선생님 회의실에서 진행했는데요.
종례에 늦었다가는 반 전체가 저 하나를 기다릴 수 있으니, 빼놓지 말고 같이 추가해주겠습니다! 🔥
아 ㅋㅋㅋ 저거 저때 코드에 종례가 1시간으로 잡혀 있었네요... 어쩐지 계속 남아있는다 했어
시간표 데이터 저장하기
// timetable.json[ [ "화학 I", "운동과 건강", "수학 II", "정보보호 관리", "정보보호 관리", "성공적인 직업생활", "응용프로그래밍 화면구현" ], ...]대부분의 고등학교에서는 1주일마다 같은 시간표가 반복되고, 저희 학교 역시 예외가 아니였습니다.
하루에 진행되는 수업 과목명(6~7개)을 순서대로 배열에 넣어서 길이 5의 Nested Array를 만들면 끝!
회의실 데이터 저장하기
// classrooms.json[ { "name": "화학 I", "teacher": "샹크스", "code": 4361766687 }, { "name": "운동과 건강", "teacher": "몽키 D. 루피", "code": 7864104388 }, { "name": "수학 II", "teacher": "롤로노아 조로", "code": 7965443330 }, { "name": "HR", "teacher": "나미", "code": 7864104388, "homeroom": true }, ...]가장 중요한 강의실 목록. 들어갈 수업의 과목에 맞는 강의실을 열어 줄 수 있도록 하는 데이터입니다!
과목 이름인 name, 선생님 성함인 teacher, 그리고 줌 강의실의 고유 ID인 code로 이루어지는 Object Array를 만들면 되겠네요.
이때 조회와 종례가 이루어지는 담임선생님의 회의실은 homeroom 필드를 true로 설정해 저장해 줍시다. 우리의 종례는 소중하니까! 🙏
데이터 가져오기
이제 우리의 파이썬 스크립트에서 위에서 만든 JSON 데이터를 불러올 수 있게 해야겠네요.
def load_json_file(filename: str) -> dict: with open(filename) as file_stream: data = json.load(file_stream) return data여러 번 쓰기 귀찮으니 파일 이름을 받아, 적힌 JSON 데이터를 오브젝트로 만들어 반환하는 함수 load_json_file을 만들어 사용합시다.
timetable = load_json_file('./timetable.json')classrooms = load_json_file('./classrooms.json')periods = load_json_file('./periods.json')period_names, period_list = map(list, [periods.keys(), periods.values()])이렇게 시간표, 강의실, 수업 시작/종료 시간 데이터를 가져와 줍니다.
이때 periods.json에서 아이템 각각의 key와 value를 리스트로 분리해서 나중에 인덱스로 접근할 수 있도록 하자고요!
🧑🏫 수업 과목명에 따라 들어갈 회의실 구하기
def get_classroom_from_subject_name(subject_name: str) -> dict: for classroom in classrooms: if subject_name == classroom['name']: return classroom데이터가 모두 준비되었으니 이제 이 정도쯤은 쉽게 할 수 있죠! 😁 과목명으로 회의실 데이터를 검색하는 함수입니다.
🤖 일정 시간마다 알아서 들어가기
자 이제 가장 중요한 파트! 스크립트만 켜두면 자동으로 수업을 들어갈 수 있도록 만드는 과정입니다.
weekday = datetime.datetime.today().weekday()classrooms_for_today = [ get_classroom_from_subject_name(subject_name) for subject_name in timetable[weekday]]먼저 Weekday를 구합니다. Weekday는 오늘이 일주일에서 몇 번째 되는 날이냐를 뜻하는데요. 0부터 시작해서 월요일이 0이고, 금요일이 4입니다.
Weekday를 알면 그걸 timetable 리스트의 인덱스로 해서 오늘의 시간표를 구할 수 있겠네요!
순서대로 들어갈 회의실의 리스트를 classrooms_for_today에 저장하겠습니다.
def get_homeroom() -> dict: for classroom in classrooms: if 'homeroom' in classroom and classroom['homeroom']: return classroom
homeroom = get_homeroom()classrooms_for_today.insert(0, homeroom)classrooms_for_today.append(homeroom)아까 추가한 담임선생님 회의실을 구하는 get_homeroom 함수를 만들어 주고, 반환된 회의실 homeroom을 리스트의 앞뒤에 하나씩 붙여줍시다. 조회와 종례를 위해서죠!
for classroom in classrooms_for_today: schedule.every().day.at(classroom['start_time']).do(open_classroom, classroom=classroom)
console.print('수업이 끝나길 기다리는 중...', style='dim')while True: schedule.run_pending()schedule 모듈을 사용하면 특정 시간에 함수의 실행을 스케줄링 할 수 있어요.
수업이 시작되는 classroom['start_time']마다 classroom으로 open_classroom 함수를 실행해 주면 완성이네요! 🎉
😈 갑분싸 피하는 방법
이렇게 스크립트를 짠 다음 실행해 봤는데, 미처 고려하지 못한 부분이 있었어요.
😬 주노 안 나가고 뭐해..? 주노...? (어색한 정적)
방금 만든 스크립트는 수업이 시작되는 시간마다 새로운 회의실을 여는 구조였습니다. 그런데 "수업 끝~"을 외치고도 제때 방을 나가지 않는다면 선생님들께서 제가 자는지 의심하실 수 있더군요! 게다가 제 친구는 수업 때 자서 방을 빠져나오지 못했는데, 선생님께서 회의실에서 강제퇴장 시키는 법을 몰라서 눈을 떠 보니 다른 반 애들이 웃고 있었다는 이야기... 상상만 해도 끔찍한 사태가 일어날 수도 있겠군요. 숙면을 위해서 이런 실수는 용납할 수 없는 법! 제때 회의실을 빠져나가는 기능을 추가해 봅시다. 💪
🔍 현재 실행되고 있는 줌 클라이언트의 PID 가져오기
가장 쉬운 방법은 종료할 시간이 되면 줌 클라이언트의 프로세스를 찾아, 죽여버리는 것이죠. 이걸 위해서는 줌 클라이언트의 프로세스를 찾을 수 있어야 합니다. 간단한 스크립트를 돌려서 찾아볼게요.
import psutil
process_pid_list = psutil.pids()for process_pid in process_pid_list: process = psutil.Process(process_pid) print(process)psutil 모듈은 파이썬에서 현재 컴퓨터에서 실행되는 프로세스들을 다룰 수 있게 해줍니다.
psutil.pids 함수를 사용해서 현재 실행되는 모든 프로세스들의 정보를 출력해 봤어요.
...psutil.Process(pid=53193, name='Google Chrome Helper (Renderer)', status='running', started='04:23:39')psutil.Process(pid=53216, name='Google Chrome Helper (Renderer)', status='running', started='04:25:31')psutil.Process(pid=53279, name='LookupViewService', status='running', started='04:29:59')psutil.Process(pid=53410, name='bluetoothaudiod', status='running', started='04:42:05')psutil.Process(pid=53501, name='zoom.us', status='running', started='04:54:38')psutil.Process(pid=53505, name='com.apple.appkit.xpc.openAndSavePanelService', status='running', started='04:54:46')psutil.Process(pid=53506, name='QuickLookUIService', status='running', started='04:54:47')psutil.Process(pid=53507, name='ACCFinderSync', status='running', started='04:54:47')...찾았습니다! name이 zoom.us인 바로 저 프로세스(53501)네요.
import psutil
def get_zoom_process(): process_pid_list = psutil.pids() for process_pid in process_pid_list: process = psutil.Process(process_pid) process_name = str(process.name()) if 'zoom' in process_name: print(process) return process return None이제 줌 클라이언트의 프로세스를 반환하는 함수 get_zoom_process를 만들 수 있습니다.
🔫 줌 클라이언트 종료시키기
def kill_zoom_process(): try: process = get_zoom_process() if process != None: process.kill() return 1 except: return 0 return 0process.kill을 이용해 찾은 프로세스를 종료하는 함수 kill_zoom_process 입니다.
종료 성공 여부를 1(성공) 또는 0(실패)으로 반환하도록 했어요. if 문으로 실행 결과에 따른 처리를 하기 위해서지요!
🙌 결과 찍어주기
def close_classroom() -> None: if kill_zoom_process(): console.print(f'✘ 수업 나가기 완료', style='dim') else: console.print(f'✘ 수업 나가기 실패', style='dim')이렇게 예쁘게 결과까지 출력해 주는 함수로 감싸서 사용합시다.
for classroom in classrooms_for_today: schedule.every().day.at(classroom['start_time']).do(open_classroom, classroom=classroom) schedule.every().day.at(classroom['end_time']).do(close_classroom)
console.print('수업이 끝나길 기다리는 중...', style='dim')while True: schedule.run_pending()수업이 끝나는 시간인 classroom['end_time']에 자동으로 실행되도록 스케줄을 추가해 주면 끝! 👍
🚀 꿀잠 성공!
완성된 스크립트가 실행되는 모습
짠! 결국 저는 파이썬 덕분에 매일매일 푹 자고 일어나 코딩할 수 있었습니다.
그런데 너무 칼 같이 수업이 끝나자마자 종료되는 바람에 한번은 수학 선생님이 수업 끝나고도 하실 말씀이 있으니 기다리라고 하셨는데도 혼자 쌩 나가 버려서... 뒤늦게 선생님께 연락을 받고 매우 쫄았지만 컴퓨터가 수업이 끝나기 전에 튕겼다고 핑계를 대며 장문의 메세지를 보내고 넘어갔던 게 기억이 나네요. 이후 같은 실수를 하지 않기 위해(?) 조금씩 종료 시간을 늦춰 조정했습니다.
선생님, 거짓말이였어요. 죄송합니다. 🥰
그래도 저거 없이는 수업 못 들어갈 만큼 덕을 똑똑히 봤었네요! ⭐️
🎸 기타
- 위 코드에서 로그 찍을 때 제가
print함수 대신 사용한console.print는 rich 패키지에서 가져와 사용할 수 있습니다. 로그가 예뻐져요. - 스크린샷에 있는 테이블 역시 rich의
Table기능을 사용해서 만들었어요.
🙏 이제는 말할 수 있다.
이 밖에도 학교에서 IT 외길인생을 걸으며 살아남기 위해서 여러가지 것들을 만들었는데요.
이제는 말할 수 있다! 하고 몇 개 썰 풀어보겠습니다.
-
🐝 B 모 온라인 학습 시스템: 저희 학교에서 아침마다 몇 문제씩 풀도록 정해진 교내 학습 시스템인데요. 유효성 검사를 프론트엔드에서 하고 있다는 걸 발견해 문제를 풀지 않고도 정답을 가져올 수 있었습니다. 너무 대놓고 답을 맞추면 들키니까 bias를 줘서 일정 확률로 찍게 했던 기억이 나네요(제 실력처럼 세팅해둬서 사실상 거의 랜덤이였네요). TypeScript와 Puppeteer를 사용해서 만들었습니다.
-
🧑💻 인강실 신청 매크로: 저희 학교는 기숙사 특성화고라서 야자 시간이 존재하는데요. 야자 때 노트북을 사용하려면 학교 인트라넷으로 '인강실 좌석'을 신청해야 했습니다. 인원 제한이 있는데 1학년 초에는 너무 경쟁이 빡세서 신청 시작 시간에 요청을 자동으로 보내고, 신청에 실패했다면 누가 티켓을 포기할 때까지 대기했다 얼른 줍줍하는 매크로를 만들었습니다. 이건 들켜서 쌤들한테 혼나고 울었던 기억이 납니다. 선도 갈 뻔했네요 ㅎㅎ
-
⏰ 온라인 수업 시간에 맞춰서 웹 노티피케이션을 보내주는 서비스: 저 말고 다른 애들도 많이 잤었는데 이들을 위해서 만들었습니다. 백엔드를 공부 중이던 후배랑 같이 진행한 프로젝트였습니다.
-
📆 학급 시간표를 볼 수 있는 크롬 익스텐션: 1학년 때 친구가 노트북 하느라 수업 준비를 못하는 걸 보고 만들어준 크롬 익스텐션입니다.
🥑 여담
친구가 멋진 기능을 제안해 줬습니다.

🔥 JUST DO IT
비록 힘든 순간도 있었지만 학교에서만 할 수 있는 프로젝트들을 진행했다는 것 자체가 정말 재미있는 추억으로 남았던 것 같아요. 여러가지 웃픈 헤프닝도 있었고, 뿌듯했던 순간들도 있었으니까요.
그냥해
돌아보니 약간 컨셉끼 넘치는 글이 된 것 같지만, 재미있는 아이디어가 있는데도 주저하는 학생 분들이 계시다면 남들 눈치보지 말고 그냥 해보셨으면 좋겠다는 뜻에서 쓰게 되었습니다!
혹시 반복되는 회의나 수업으로 인해 고통받는 분이 계시다면, 오늘 다룬 코드의 전체 소스는 깃허브 여기에 있으니 포크해서 사용해보세요. 모든 종류의 기여도 환영합니다😎