
내 첫 WhatsApp 메시지는 1995년 날짜로 표시됐다. macOS의 Emacs에서 채팅 기록을 읽다가 날짜 기준이 다르다는 걸 알아챘다.
나는 WhatsApp의 공식 API 없이 Emacs에서 대화하는 클라이언트를 만들었다. macOS의 WhatsApp 앱이 저장한 대화 데이터베이스를 읽고, 메시지 전송은 macOS 단축어 기능에 맡기는 방식이다. Emacs 안에서 메시지를 주고받고 싶은 사람이라면 구현 방식과 제약을 살펴볼 수 있다.
이 클라이언트는 어떻게 작동하나?
Emacs 클라이언트는 대화 목록과 대화별 버퍼를 제공한다. 채팅 기록은 로컬 데이터베이스에서 읽고, 메시지는 공식 WhatsApp 앱을 통해 보낸다. 기존 채팅 프로그램의 화면을 떠올리면 쉽다. Emacs의 각 대화 버퍼에 메시지를 시간순으로 보여주는 구조다.
나는 macOS에서 작업했다. 메타는 이 용도의 API를 제공하지 않고, macOS 데스크톱 앱에도 다른 프로그램이 연결할 수 있는 통로가 없었다. 비공식 클라이언트는 기기인 것처럼 WhatsApp 서버와 직접 통신하지만, 메타가 허가하지 않은 소프트웨어에 계정을 맡겨야 하고 번호가 차단될 수 있어 사용하지 않았다.
대화 내용은 어디서 읽나?
WhatsApp 앱은 모든 채팅을 로컬 SQLite 데이터베이스에 저장한다. 파일 경로는 다음과 같다.
~/Library/Group Containers/group.net.whatsapp.WhatsApp.shared/ChatStorage.sqlite
Emacs 29에는 SQLite 지원이 내장돼 있어 외부 라이브러리 없이 읽기 전용으로 열 수 있다.
(sqlite-open file t)
데이터베이스 구조는 문서화돼 있지 않아 직접 살펴봤다. 대화는 ZWACHATSESSION, 메시지는 ZWAMESSAGE, 그룹 구성원의 이름은 ZWAGROUPMEMBER와 ZWAPROFILEPUSHNAME에 들어 있다. 이름 앞의 Z는 애플의 데이터 저장 방식인 Core Data 데이터베이스라는 단서다.
Core Data 날짜는 1970년이 아니라 2001년 1월 1일부터 센다. 그래서 처음에는 메시지 날짜가 1995년으로 표시됐다. 두 기준의 차이를 상수로 더해 해결했다.
(defconst whatsapp--epoch-offset 978307200
"Seconds between the Unix epoch and the Core Data epoch (2001-01-01).")
고정한 대화는 목록 위에 남도록 미래 날짜가 저장된다. 대화의 마지막 메시지 내용도 인코딩돼 있다. 그래서 둘 다 대화 항목이 아니라 실제 메시지에서 읽는 편이 낫다.
macOS는 해당 폴더 접근을 보호한다. Emacs에 전체 디스크 접근 권한을 부여해야 한다.
메시지는 어떻게 보내나?
데이터베이스를 직접 수정해도 앱이 변경을 알아채지 못하고 메시지도 전송되지 않는다. 대신 macOS 단축어의 WhatsApp용 ‘메시지 보내기’ 동작을 이용했다. 단축어는 터미널에서도 실행할 수 있다.
shortcuts run "Send WhatsApp" --input-path message.json
이 동작은 전화번호가 아니라 주소록 연락처를 받는다. 나는 채팅 상대의 전화번호를 담은 vCard를 즉석에서 만들어 단축어에 전달했다. 단축어가 이를 임시 연락처로 처리하므로 주소록에는 저장되지 않는다.
Emacs는 vCard와 메시지 내용을 JSON 파일로 만들고, 단축어 프로세스를 시작한 뒤 완료될 때까지 기다린다.
(json-encode
`((vcard . ,(whatsapp--vcard name phone))
(text . ,text)))
전체 흐름은 다음과 같다.
flowchart LR
E[Emacs] -- reads --> DB[(ChatStorage.sqlite)]
E -- JSON with vCard --> S[Send WhatsApp shortcut]
S -- Send Message --> W[Official WhatsApp app]
W -- stores --> DB
단축어가 실행되는 동안 메시지는 (sending...)으로 표시된다. WhatsApp이 메시지를 데이터베이스에 저장하면 Emacs가 임시 표시를 실제 메시지로 바꾼다. 따라서 화면에는 Emacs가 보낸 것으로 간주한 내용이 아니라 WhatsApp 앱이 저장한 내용이 나타난다.
무엇이 제한되나?
나는 ERC나 rcirc 같은 화면을 만들었다. 대화 목록과 대화별 버퍼가 있고, 전체 클라이언트는 외부 의존성 없이 몇 줄의 Elisp로 구성된다. Emacs의 대화형 화면은 오래전부터 해결된 문제라 구현에서 가장 간단한 부분이었다.
제약은 다음과 같다.
- macOS에서만 작동한다.
- 단축어 동작이 사람 연락처만 받으므로 그룹에는 메시지를 보낼 수 없다.
- 일부 대화는 전화번호를 로컬에 저장하지 않아 읽기 전용으로 남는다.
- 알림은 없다. 새 메시지를 받으려면
C-c C-u를 눌러 가져와야 한다.
참고 자료는 whatsapp.el의 코드와 단축어 생성 안내, Emacs 29부터 제공되는 SQLite 지원, 애플의 명령줄 shortcuts 문서, 연락처 정보에 사용한 vCard 3.0(RFC 2426)이다. 원문은 지원되는 macOS 버전이나 WhatsApp 앱 버전은 밝히지 않는다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요