상세 컨텐츠

본문 제목

파이썬으로 하는 Outlook - 일정 자동 생성

coding

by 서티제 2022. 1. 31. 21:02

본문

일정이 있는 아웃룩 메일의 경우 일정을 자동으로 생성하는 프로그램을 만들기.

1. 사용 프로그램: 파이썬
   아웃룩 제어를 위해 Microsoft Windows용 파이썬 API인 win32com.client을 사용하였으며 VBA 자체 코딩은 없음.

2. 일정 자동 생성 하기 (Appointment)
   1)아웃룩에서 메일을 선택하고 읽기 방법
    -.  win32com.client(VBA)의  Selection object 사용 
import win32com.client
ol_Exp = win32com.client.Dispatch("Outlook.Application").ActiveExplorer()  # 현재 선택된 폴더명.
ol_Sel_count = ol_Exp.Selection.Count     # 아웃룩에서 선택된 메일의 숫자
for idx in range(1, ol_Sel_count+1):    # 선택된 메일 순차적으로 읽기
    ol_mail = ol_Exp.Selection.Item(idx)
    mail_txt = ol_mail.Subject + '\n' + ol_mail.Body
    # 메일 제목과 메일 본문을 분석을 위해서 별도 저장
    # MailItem object의 상세설명은 MS의 VBA reference 참조
    # https://docs.microsoft.com/en-us/office/vba/api/overview/outlook/object-model


   2) 메일 문자열에서 날짜/시간에 관련된 문자 찾아서 datetime형식으로 리턴
   -. 정규표현식을 이용하여 년/월/일 (000년 00월 00일, 00월/00일, 00.00/00 등) 찾기
   -. 정규표현식을 이용하여 시/분  (00시00분, 00:00 등) 찾기
   -. 년월일과 시분을 합쳐서 datetime 형식으로 리턴함.
import re               #정규표현식 package
import datetime      #날짜시간 관련 package
import dateutil.parser as dparser    #문자열을 datetime형식으로 변경 pacage

# 정규 표현식을 별도의 sub로 선언
def datetime_complie_set() -> list:
    date_time_com_set = []
   # 년월일 파악 정규식
    date_time_com_set.append(re.compile(r'\d{2,4}\s?[.년/,-]\s?\d{1,2}\s?[.,/-]\s?\d{1,2}'))
    date_time_com_set.append(re.compile(r'\d{2,4}\s?[.년/,]\s?\d{1,2}\s?[월]\s?\d{1,2}\s?[일]'))
    date_time_com_set.append(re.compile(r'\d{1,2}\s?[./-]\s?\d{1,2}\s?'))
    date_time_com_set.append(re.compile(r'\d{1,2}\s?[월]\s?\d{1,2}\s?[일]'))
   # 시분 파악 정규식
    date_time_com_set.append(re.compile(r'[오AaPp][전후Mm]\s?\d{1,2}\s?[시:;]\s?\d{0,2}'))
    date_time_com_set.append(re.compile(r'\d{1,2}\s?[:시]\s?\d{0,2}'))
    return date_time_com_set

def return_datetime_in_str(strings: str, date_time_re_com: list, referdate: datetime.date):
   #strings: str, : 분석할 문자열
   #date_time_re_com: list,  : 사전 정의 정규표현식
   #referdate: datetime.date  : 리퍼런스 날짜시간, 시분만 있고 년월일이 없을 경우 메일 수신 날짜로 일정을 저장하기 위해서 필요
   #코드 상세 내용 첨부 참조
return schedule 

dt_re_cpl_set = datetime_complie_set()   # 정규 표현식 set
rf_dt = ol_mail.ReceivedTime     # 메일 수신 날짜
schdl_dt = return_datetime_in_str(sent, dt_re_cpl_set, rf_dt.date())  # 문자열을 datatime 형식으로 리턴함.


   3) 아웃룩 일정표에 기록하기
    -. CreateItem(1) 사용하여 새 일정을 만듬 (0: 메일, 1:일정, 2:주소록, 3:작업 등등)
ol_appoint_item = win32com.client.Dispatch("Outlook.Application").CreateItem(1)  # 새일정 만들기
ol_appoint_item.Subject = ol_mail.Subject     # 일정제목
ol_appoint_item.Start = schdl_dt + datetime.timedelta(hours=9)  # 일정의 시작시간
ol_appoint_item.Body = mail_body  # 일정 본문
ol_appoint_item.save()   # 일정 저장


3. 기타 사항 (첨부 파일)
  - 오전/오후/내일/명일등의 문자 처리
  - 분석한 날짜가 오늘 날짜와 차이가 많이 나는 경우 예외 처시
  - 년도가 없거나 2자리 일 경우 메일 수신 년도로 대체
  - 시/분 정보만 있고 년/월/일 정보가 없을 경우 처리 - 메일 수신 날짜 또는 +1 day 처리 
  ※불필요한 송신자 정보, 회신 메일 정보 등을 지우는 전처리 프로그램 필요..

아웃룩 보낸 메일함에서 메일 선택 후 파이썬 실행!!!

import win32com.client
import datetime
import tkinter
import os
import re
import dateutil.parser as dparser


def datetime_complie_set() -> list:
    date_time_com_set = []
    date_time_com_set.append(re.compile(r'\d{2,4}\s?[.년/,-]\s?\d{1,2}\s?[.,/-]\s?\d{1,2}'))
    date_time_com_set.append(re.compile(r'\d{2,4}\s?[.년/,]\s?\d{1,2}\s?[월]\s?\d{1,2}\s?[일]'))
    date_time_com_set.append(re.compile(r'\d{1,2}\s?[./-]\s?\d{1,2}\s?'))
    date_time_com_set.append(re.compile(r'\d{1,2}\s?[월]\s?\d{1,2}\s?[일]'))
    date_time_com_set.append(re.compile(r'[오AaPp][전후Mm]\s?\d{1,2}\s?[시:;]\s?\d{0,2}'))
    date_time_com_set.append(re.compile(r'\d{1,2}\s?[:시]\s?\d{0,2}'))
    return date_time_com_set


def date_str_modi(date: str) -> str:
    temp_date = date
    temp_date = temp_date.replace('년', '-')
    temp_date = temp_date.replace('월', '-')
    temp_date = temp_date.replace('일', ' ')
    while temp_date.__contains__(" "):
        temp_date = temp_date.replace(" ", '')
    while temp_date.__contains__('/'):
        temp_date = temp_date.replace('/', '-')
    while temp_date.__contains__('.'):
        temp_date = temp_date.replace('.', '-')
    return temp_date


def time_str_modi(time: str) -> str:
    temp_time = time
    temp_time = temp_time.replace('오전', 'AM')
    temp_time = temp_time.replace('오후', 'PM')
    temp_time = temp_time.replace('시', ':0')
    temp_time = temp_time.replace('분', ':0')
    temp_time = temp_time.replace('초', ' ')
    return temp_time


def return_datetime_in_str(strings: str, date_time_re_com: list, referdate: datetime.date):
    strings = strings.lower()
    date_ymd_en_complie = date_time_re_com[0]
    date_ymd_kr_complie = date_time_re_com[1]
    date_md_en_complie = date_time_re_com[2]
    date_md_kr_complie = date_time_re_com[3]
    time_12_complie = date_time_re_com[4]
    time_24_complie = date_time_re_com[5]
    date_ymd_srch = date_ymd_en_complie.search(strings)
    if date_ymd_srch:
        pass
    else:
        date_ymd_srch = date_ymd_kr_complie.search(strings)
    if date_ymd_srch:
        year_2d_chk = re.search(r'^\d{2}[.년/]', date_ymd_srch.group())
        if year_2d_chk:
            date_4ymd_str = '20' + date_ymd_srch.group()
        else:
            date_4ymd_str = date_ymd_srch.group()
    else:
        date_ymd_srch = date_md_en_complie.search(strings)
        if date_ymd_srch:
            date_4ymd_str = str(referdate.year) + '-' + date_ymd_srch.group()
        else:
            date_ymd_srch = date_md_kr_complie.search(strings)
            if date_ymd_srch:
                date_4ymd_str = str(referdate.year) + '-' + date_ymd_srch.group()
            elif strings.__contains__('내일') or strings.__contains__('명일'):
                date_4ymd_str = str(referdate + datetime.timedelta(days=1))
            else:
                date_4ymd_str = ""
                is_date_info = False
    ''' find time : hour, minute '''
    is_time_info = True
    time_srch = time_12_complie.search(strings)
    if time_srch:
        pass
    else:
        time_srch = time_24_complie.search(strings)
        if time_srch:
            pass
        else:
            is_time_info = False
    if is_time_info:
        time_str = time_srch.group()
    else:
        time_str = ""
    # change data type striing to datetime
    date_m = date_str_modi(date_4ymd_str)
    time_m = time_str_modi(time_str)
    date_and_time = date_m + ' ' + time_m
    # 문자열이 datetime으로 형 변환이 될 경우
    try:
        schedule = dparser.parse(date_and_time, fuzzy=True)
        if strings.__contains__('오후') or strings.__contains__('pm'):
            schedule = schedule + datetime.timedelta(hours=12)
        diff_dt = schedule - datetime.datetime.today()
        if not (-5< diff_dt.days < 60):  # 계산한 일정이 오늘 날짜와 차이가 많이 나는 경우 한번더 연산
            strings = strings.replace(date_ymd_srch.group(), '')
            date_4ymd_str, time_str = '', ''
            schedule, date_4ymd_str, time_str = return_datetime_in_str(strings, date_time_re_com, referdate)
    # datetime 형 변환시 에러가 발생하는 경우 한번 더 연산
    except:
        if date_ymd_srch:
            strings = strings.replace(date_ymd_srch.group(), '')
        if time_srch:
            strings = strings.replace(time_srch.group(), '')
        if date_ymd_srch or time_srch:
            date_4ymd_str, time_str = '', ''
            schedule, date_4ymd_str, time_str = return_datetime_in_str(strings, date_time_re_com, referdate)
        else:
            schedule = None
    return schedule, date_4ymd_str, time_str


schedule_voca_set =['교육일정','기한','까지','설명회','송부','실시','연기','요청','일 시','일 정','일시','일정',
                    '점검회','회신','회신기한','회신일','회신일자','회의일정','공유회', '마감', '지급', '회의']
cwd = os.getcwd()
dt_re_cpl_set = datetime_complie_set()
current = datetime.datetime.today()
outlook_app = win32com.client.Dispatch("Outlook.Application").GetNamespace("MAPI")
ol_Exp = win32com.client.Dispatch("Outlook.Application").ActiveExplorer()
ol_Sel_count = ol_Exp.Selection.Count
for idx in range(1, ol_Sel_count+1):
    ol_mail = ol_Exp.Selection.Item(idx)
    mail_txt = ol_mail.Subject + '\n' + ol_mail.Body
    mail_body = ol_mail.Body
    rf_dt = ol_mail.ReceivedTime
    schdl_dt = None
    for sent in mail_txt.split(sep='\n'):
        # 메일의 문장에서 일정에 관련되는 단어를 찾아서 datetime으로
        for vc in schedule_voca_set:
            if sent.__contains__(vc):
                schdl_dt, date_str, time_str = return_datetime_in_str(sent, dt_re_cpl_set, rf_dt.date())
                if not schdl_dt:
                    sent = sent.replace(date_str[-5:], ' ')
                    schdl_dt, date_str, time_str = return_datetime_in_str(sent, dt_re_cpl_set, rf_dt.date())
                if schdl_dt:
                    break  # 3번재 for문
        # 일정 날짜가 있다면 일정(appointment)를 새로 만들어서 저장함.
        if schdl_dt:
            ol_appoint_item = win32com.client.Dispatch("Outlook.Application").CreateItem(1)
            ol_appoint_item.Subject = ol_mail.Subject
            if schdl_dt.time().hour == 0:
                ol_appoint_item.AllDayEvent = True
            ol_appoint_item.Start = schdl_dt + datetime.timedelta(hours=9)  # 한국 타임존 보정
            mail_body = "------ This is Predicted! ------ \n일    정:   " + str(schdl_dt) + \
                         "\n수신시간:   " + str(ol_mail.ReceivedTime + datetime.timedelta(hours=0)) + \
                         '\n범    주:   ' + ol_mail.Categories + '\n실행시간:   ' + str(current) + \
                        '\n\n     ----- 메일 본문 -----\n' + mail_body
            ol_appoint_item.Body = mail_body
            ol_appoint_item.save()
            break  # 2번재 for 문

    disp_text = '제목: '+ ol_mail.Subject + '\n\n' +'범주: '+ ol_mail.Categories + '\n\n' +'약속시간: ' +str(schdl_dt)
    disp_text += '\n\n메일본문\n'+ mail_body
    disp_box = tkinter.Tk()
    disp_box.title('('+ str(idx)+'/'+str(ol_Sel_count)+')   ' + ol_mail.Subject)
    disp_box.resizable(True, True)
    t = tkinter.Text(disp_box, height=50, width=100)
    t.insert(tkinter.END, disp_text)
    t.pack()
    tkinter.mainloop()

'coding' 카테고리의 다른 글

파이썬으로 하는 outlook : 메일 분류 학습(1)  (0) 2022.02.18

관련글 더보기