개요
이모티콘 추가를 자동화하고, 단축키와 최근 사용 목록 표시 등 다양한 편의 기능을 제공하여 직원들의 메신저 사용 경험을 향상시켰습니다.
주요 기능
이모티콘 자동 추가
이모티콘 탭 관리
단축키 기능
최근 사용 이모티콘 표시
GUI 인터페이스
주요 기술적 도전과 해결
XML 파싱과 데이터 관리
WinAPI를 활용한 단축키 구현
실시간 채팅 기록 분석
멀티스레드 및 멀티프로세싱 활용
사용자 중심 UI 설계
회고
성과:
아쉬운 점:
링크: 내부망 프로젝트로 실사용 스크린샷과 실행 파일 공개는 불가.
사내 메신저에 이모티콘 관련 부가기능을 제공하는 파이썬 프로그램
사내 메신저에는 이모티콘의 수가 극히 적었습니다.
수동으로 추가할 수 있었으나 복잡하고, 상대방도 같은 방식으로 추가해야만 사용할 수 있었습니다.
모두가 쓸 수 있도록 쉽게 추가해주는 도구를 만들고 싶었습니다.
단순한 이모티콘 추가 매크로로 시작하여, 여러 편의 기능을 추가하였습니다.
python, selenium, tkinter, pyinstaller, sqlite3, pywin32
(사용 예시. 실제 배포된 버전에서는 더 많은 이모티콘과 미리보기가 표시됨.)
배경
이모티콘은 상대방도 추가해야 사용할 수 있음
쉽게 추가해주는 도구가 있어야겠다고 판단
탐색
메신저가 xml파일로 이모티콘 탭과 탭 소속 이미지들을 관리하고 있음을 확인
xml파일 예시:
<?xml version='1.0' encoding='utf-8'?>
<EmoticonList>
<EmoticonTabitem TabControlNM="shakyanimals">
<Tag>1</Tag>
<TabName>바들바들 동물콘</TabName>
<TabName_ENG>shaky-animals</TabName_ENG>
<Img_Def>tab_shaky-animals_main.bmp</Img_Def>
<Img_Down>tab_shaky-animals_select.bmp</Img_Down>
</EmoticonTabitem>
<EmoticonItem EmoticonType="sticker">
<TabName>shakyanimals</TabName>
<Name>shaky-animals (2).png</Name>
<ListName>shaky-animals (2).png</ListName>
<Kor>(바들바들 동물콘 2)</Kor>
<Eng>(shaky-animals 2)</Eng>
<Hint>정말고마워요</Hint>
</EmoticonItem>
</EmoticonList>
구현
이미지들을 복사하고 xml파일을 수정하는 매크로를 만들어 배포함
파일들을 알맞은 위치로 이동시키는 배치파일을 압축파일에 포함시킴
배경
시간이 지나 이모티콘이 점점 많아져 선택이 불편하다는 피드백
순서 변경, 탭 제거 기능이 필요해짐
탐색
xml파일 EmoticonTabitem 태그의 배치 순서와 이모티콘 선택창의 순서가 연동된다는 사실을 발견
xml파일을 사용자가 편하게 수정하는 기능을 만들자고 생각
구현
xml을 ElementTree를 이용해 딕셔너리로 파싱, 사용자의 입력을 받아 순서를 변경 후 다시 xml로 저장하여 순서 변경을 구현.
1. 이모티콘 A
2. 이모티콘 B
2. 이모티콘 C
4. 이모티콘 D
변경할 이모티콘과 순서를 입력하세요
>>> 4 1 # 4번 이모티콘을 첫 번째 순서로 변경
1. 이모티콘 D # 4번이었던 D가 첫 번째가 되었음
2. 이모티콘 A # 1번이었던 A,B,C는 순차적으로 2,3,4번으로 밀리게 됨
2. 이모티콘 B
4. 이모티콘 C
기술적 어려움 1
삭제를 위해서는 xml파일에서는 지워야 하나 다시 추가하기 위해서는 탭 정보를 별도로 저장하고 있어야 함.
추후 업데이트 시 정보가 덮어 씌워질 수 있으므로 삭제한 탭의 정보를 별도로 저장하기보다, 전체 탭 정보를 담은 레퍼런스 파일을 생성해, 레퍼런스에 없는 탭을 인식하는 방식으로 구현
기술적 어려움 2
xml을 파싱한 탭 데이터들에 접근할 때 탭 이름(딕셔너리처럼)으로 접근해야하는 경우와 정수 인덱스(리스트처럼)로 접근해야하는 두 가지 경우가 존재.
사용자가 직접 순서를 바꾸고 추가/삭제를 한 뒤 데이터를 저장해야 했으므로 두 가지 형태의 데이터를 모두 쓰는 것은 불가능.
리스트처럼 정수로 인덱싱 가능한 딕셔너리의 자식클래스를 만들어서 해결
class IndexableDict(dict):
"""Never use int as key"""
def __getitem__(self, key):
if isinstance(key, int):
return list(self.values())[key]
return super().__getitem__(key)
# 기타메소드 생략
# 사용 예시
tabdata = IndexableDict({'tabname1': <TabData1>, 'tabname2': <TabData2>})
tabdata[1] # <TabData2>
tabdata['tabname1'] # <TabData1>
파이썬 2.7부터는 딕셔너리도 입력 순서를 보장하기 때문에 가능
배경
처음에는 탭 변경 기능을 자주 사용하지 않을 것이라고 생각해서 텍스트 기반 ui로 구현
그러나 메신저와 크게 다른 ui 때문에 사용률이 몹시 저조
탐색
파이썬으로도 GUI를 구현하는 것이 가능하다는 것을 알게됨
PyQt가 더 기능이 다양했지만 메신저를 통한 파일 용량 제한이 있어, 용량을 줄이기 위해 표준 라이브러리였던 tkinter사용
구현
메신저의 UI와 유사하게 만들어 최대한 쉽게 사용할 수 있도록 구현
프로그램을 중복 실행하여 오작동하였다는 피드백이 있었으므로
중복 실행 시 기존의 윈도우를 맨 위로 올리고 중복프로세스를 종료하도록 함
사용자는 원하는 이모티콘을 선택해 버튼을 누르는 간단한 작업만으로 추가할 수 있게 됨.
별도의 프로그램이 아닌 메신저의 원래 있던 기능을 사용하는 것 같은 경험 제공.
기술적 어려움
멀티 스레딩/프로세싱 중 어느 것을 사용해야 하는 지 결정하기 어려웠다.
결론적으로 tkinter를 이용한 윈도우 ui만 멀티프로세싱을 사용하고 나머지는 멀티 스레드로 구현했다.
단축키 스레드, 최근사용탭 스레드는 사용자 설정이나 이벤트에 따라 껐다 켰다 해야하고 스레드 간 데이터를 주고받는 상황이 많았기 때문이다.
그러나 ui 프로세스는 사용자의 입력에 따라서만 실행/종료되고 프로세스에 오류가 발생해도 루트 프로세스에는 영향을 미치지 않는 것이 안전했기 때문에 분리했다.
배경
메신저가 단축키를 지원하지 않아 메시지 입력 중 이모티콘을 보내려면 반드시 마우스를 사용해야해 불편하다는 요청을 받아
키보드로만 이모티콘을 보내는 기능을 구현하려 함
탐색
메신저 등 프로그램이 winapi를 통해 입력 정보(메시지)를 받는다는 것을 알게됨
winapi는 c언어 기반이지만 파이썬에도 라이브러리가 있음을 확인
구현
사용자가 키보드 버튼을 입력했을 때 알맞는 winapi 메시지를 핸들로 전송하도록 하여 마우스를 옮기지 않으며 단축키를 구현.
기술적 어려움 1
메신저에 별도의 API가 존재하지 않아 처음엔 마우스 매크로로 구현하였음
하지만 테스트 사용해보니 입력이 제대로 되지 않는 경우가 많았으며
커서가 움직여 보기에도 별로이고 속도도 느렸음
커서 이동 등을 제거하고 메신저 프로그램으로 입력만 전달할 수 있다면 해결될 것이라고 생각하여
마우스 입력이 프로그램에 어떻게 전달되는가에 대해 공부하는 과정에서 winapi를 알게되었음
spy++(winapi메시지 후커)를 이용하여 마우스로 버튼을 클릭하면 해당 창의 핸들로 메시지(WM_LBUTTONDOWN)가 전송된다는 것을 알게되었고 이를 모방하면 커서를 움직이지 않고도 버튼을 누를 수 있을 것이라고 생각
적절한 핸들을 알아내고 메시지를 모방하는 클래스를 구현하여 사용함
class Messenger:
DPOS = {7: (60, 60), 8: (170, 60), 9: (280, 60),
4: (60, 160), 5: (170, 160), 6: (280, 160),
1: (60, 260), 2: (170, 260), 3: (280, 260)}
def __init__(self):
self.hwnd_chatroom = None
self.hwnd_emo = None
self.hwnd_tabs = None
self.hwnd_sheet = None
self.hwnd_textbox = None
self.pos = None
self.hwnd_tabarrow_left = None
self.hwnd_tabarrow_right = None
def Click(self, hwnd, x=0, y=0, up=False):
lParam = win32api.MAKELONG(x,y)
win32api.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lParam)
if up:
win32api.SendMessage(hwnd, win32con.WM_LBUTTONUP, None, lParam)
def GetChatroomHandle(self, wait=0):
start_time = time.time()
while True:
hwnd = win32gui.GetForegroundWindow()
if hwnd:
classname = win32gui.GetClassName(hwnd)
if classname == "TfrmDccChat":
self.hwnd_chatroom = hwnd
return self.hwnd_chatroom
else:
return 0
if time.time() - start_time > wait:
return 0
win32api.Sleep(150)
기술적 어려움 2
단축키 프로세스가 사용 중 먹통이 되는 경우가 자주 발생함.
메시지 발생 기능을 제외하고 키보드 리스너만 실행시켜보아도 발생하는 것을 확인하고 리스너의 문제라고 판단
키입력을 단순 출력하도록 켜놓고 로그를 분석하니 멈추는 경우가 두 가지 있었음
먼저 한자/한영 키가 입력되었을 때 이를 핸들링하려고 할 때 멈추어 예외 처리.
두 번째로는 (아마 보안 상 문제로) 윈도우 잠금해제 창에 진입하는 경우 리스너가 종료되진 않지만 작동이 멈추는 현상.
윈도우 세션 이벤트 리스너 코드를 웹에서 가져와 잠금해제 시 키보드 리스너를 재시작하도록 해 해결
배경
카카오톡처럼 최근 사용한 이모티콘 항목을 보여줬으면 좋겠다는 요청
탐색
채팅기록이 채팅방마다 파일로 저장되며, journal 파일을 생성하는 점에서 메신저가 sqlite를 이용해 채팅을 저장한다는 것을 알 수 있었음
다른 탭과 달리 즐겨찾기 탭은 항목의 추가/제거가 자유로웠으므로 즐겨찾기 탭에 최근 발신한 이모티콘을 추가해 두면 최근사용 이모티콘 탭처럼 사용할 수 있겠다는 생각
구현
파이썬 sqlite3를 이용하여 최근 업데이트된 채팅방을 주기적으로 검색하여 발신 기록을 가져오고
발신 기록에 이모티콘이 있다면 해당 이모티콘을 즐겨찾기 탭에 추가해주는 방식으로 카카오톡의 최근사용 기능을 똑같이 구현
기능을 활성화만하면 (로그인 중인 아이디 파악)-(활성화된 채팅창의 채팅 로그 모니터)-(이모티콘 발신 시 발신한 이모티콘 파악 후 즐겨찾기 탭에 푸시)와 같은 프로세스가 자동으로 이루어짐.
기술적 어려움
사용자가 발신한 메시지를 구분하려면 사용자의 아이디를 얻어야 하지만 사용자 아이디를 md5로 해시된 문자열만 알 수 있었음
반면 채팅 기록에는 base64로 인코딩 된 발신자 아이디가 그대로 남아있었으므로 채팅 기록에 존재하는 아이디들과 모두 대조하는 방식으로 사용자 아이디를 알아냄
또한 사용자가 메시지를 보내는 이벤트가 발생할 때 그 내용을 판단하는 훅을 구현해야 했는데 sqlite의 db파일은 보통의 파일시스템과 다르게 동작하여 이벤트 리스너를 사용할 수 없었음
멋진 방식은 아니지만 폴링방식으로 구현. 전체 파일을 조회하진 않고 파일 끝부분만 조회하여 변경되었을 경우에만 내용을 확인함.
배경
다른 사용자들에게 배포하기 위해 이모티콘 이미지를 수집/가공하고 xml파일을 작성하는데 시간이 많이 소요됨
구현
카카오톡 이모티콘 페이지에서 selenium을 이용해서 이미지를 자동으로 다운받을 수 있음
이미지 가공도 pillow를 이용해 자동으로 수행할 수 있었으므로, 카카오톡 이모티콘 링크만 입력하면 모든 과정을 자동으로 수행해주는 코드 작성
배운 점
최대한 사용자가 신경써야 하는 것이 없도록 만드는데 많은 시간을 사용했다.
되게 하는 것은 쉽지만 잘 되게 하는 것은 (매우) 어렵다는 말을 다시금 되새기는 계기가 되었다.
파이썬으로 상호작용이 가능한 프로그램을 만드는 것, 멀티스레드로 다양한 기능들을 담는 것 등 처음하는 것이 대부분이어서 많이 배울 수 있었다.
특히 API는 물론 프로그램에 대한 어떠한 정보조차 제공되지 않는 환경에서 많은 기능을 구현하였다는 것에서 자신감을 얻었다.
여러차례에 걸친 배포-피드백-개선작업으로 사용자의 요구를 파악하고 반영하는 과정을 경험
아쉬운 점
다만 git 등 버전 관리 시스템의 존재를 몰라 테스트/폐기 과정이 복잡했던 점
다른 사람이 볼 것이라는 상상을 못 해 주석을 거의 달지 않았다는 점
섣부른 최적화로 비효율을 초래했던 점은 아쉽다.
특히 indexabledict클래스를 구현하는 대신 단순히 순회 탐색 방식을 사용했어도 속도가 크게 차이나지 않았을 것 같다.