추가 2022.03.22 // macOS 12.3부터 Python이 기본 설치에서 빠지게 되어 스크립트가 동작하지 않을 수 있다. 해당 문제를 겪고 있다면 JS로 작성한 새 버전을 사용해보자.
알프레드(Alfred)라는 맥용 애플리케이션이 있다. 나 역시 편리해서 사용하고 있는데 이 알프레드에는 워크플로우(Workflow)라는 커스텀 기능을 만들 수 있는 강력한 기능이 있다. 스크립트를 지원하기 때문에 실력과 의지만 있다면 아주 다양하게 활용할 수 있을...테지만 지금까지는 딱히 필요한 기능이 없어서 잘 쓰지 못했었다.
그러다가 오늘 커뮤니티에서 누군가 알프레드 워크플로우로 특정 네이버 카페를 검색할 방법을 알려달라고 질문한 것을 보았다. 특이한 사항은 개별 네이버 카페는 아직도 EUC-KR 혹은 CP949라는 인코딩을 사용하고 있다는 것이다. 맥OS와 알프레드는 당연히 UTF-8을 사용하고 있기 때문에 알프레드에서 기본 지원하는 커스텀 검색 기능으로는 이 문제를 해결할 수 없다. 검색어의 인코딩만 바꾸면 되는 일이니까 쉬워보였고 마침 요즘 살짝 한가한 편이라 도전 정신이 발동했다.
빈 워크플로우 제작
제일 먼저 할 일은 워크플로우를 제작하는 일이다. 알프레드의 환경 설정창을 열고 Workflows 탭을 선택한다. 그 다음에 워크플로우 목록 하단에 있는 + 버튼을 클릭해서 새로운 워크플로우를 만든다.
+ 버튼을 클릭하면 다양한 종류의 워크플로우 템플릿을 고를 수 있는데 여기서는 바닥부터 만들어 볼 것이므로 빈 워크플로우(Blank workflow)를 선택한다. 이름(Name)과 설명(Description), 카테고리(Category)는 원하는대로 입력해도 된다. 번들 아이디(Bundle Id)는 다른 워크플로우와 겹치지 않으면 되는데 보통은 자신이 가진 도메인 이름의 순서를 뒤집어 사용하곤 한다. 예를 들어, 나는 워크플로우의 이름과 내 블로그 주소를 뒤집은 kim.taegon.ncafe
를 골랐다. 어차피 어디에 공개할 건 아니기때문에 그냥 편한대로 작성해도 된다. 대충 test.ncafe
처럼 써도 된다는 뜻이다.
제작자(Created By) 항목이나 웹사이트(Website) 항목도 적당히 기입하고, 원한다면 "위 영역에 워크플로우 아이콘을 드롭하세요(Drop workflow icon above)"라고 써있는 영역에 아이콘을 구해서 넣는다. 네이버 카페 로고를 원한다면 "네이버 카페 로고 png"라고 검색해보자. 가장 자리가 깔끔하게 처리된 png 파일을 구할 수 있다.
다 입력했으면 Create를 눌러서 빈 워크플로우를 작성하자. 아무 형태도 없는 워크플로우가 나타날 것이다. 이제 내용을 채워넣어볼 차례다.
브라우저에서 열기 기능
이 워크플로우의 최종 결과물은 웹 브라우저를 열어 네이버 카페를 검색하는 것이다. 이 때 필요한 오브젝트는 웹 브라우저에서 주어진 URL을 여는 Open URL 객체이다. 비어있는 워크플로우에서 마우스 오른쪽 버튼(혹은 Ctrl + 왼쪽 클릭)을 눌러 팝업 메뉴가 나타나면 Actions > Open URL을 선택하여 객체를 추가한다.
URL을 입력해주어야 하는데, 이 부분에서는 자신이 검색하고자 하는 네이버 카페의 고유 아이디를 알아야 한다. IoT 관련 동호회인 홈 어시스턴트 카페를 예로 들어 클럽 아이디를 알아내는 법을 살펴보자. 네이버 카페는 <iframe>
안에서 카페를 보여주고 있어서 웹 브라우저의 주소 표시줄은 항상 같은 주소만 보여준다. 홈 어시스턴트 카페라면 실제 위치에 상관없이 https://cafe.naver.com/koreassistant 만 주소 표시줄에 항상 표시된다.
따라서 실제 주소를 알아내려면 조금 다른 방법을 사용해야 하는데, 일단 여기서 필요한 건 검색 정도이므로 고유 아이디인 clubId
를 가져오는 방법만 알아보자. 방법은 간단하다. 카페 아무 게시판이나 가서 글 제목 링크의 주소를 복사하면 된다. 그러면 다음과 같은 주소를 볼 수 있는데 이 중 clubid=
뒷 부분이 바로 해당 카페의 고유 아이디이다.
https://cafe.naver.com/ArticleRead.nhn?clubid=29860180&articleid=6014&referrerAllArticles=true
네이버 카페에서 검색하는 URL은 다음과 같다. 아래 URL에서 "고유아이디" 부분만 자신이 검색을 원하는 카페의 고유 아이디로 변경하면 된다. {query}
라고 적힌 부분은 알프레드를 통해 입력한 검색어로 대체된다.
https://cafe.naver.com/ArticleSearchList.nhn?search.clubid=고유아이디&search.searchBy=0&search.query={query}
원하는 브라우저를 선택하고 "Save" 버튼을 눌러 OpenURL 오브젝트를 추가하자. 이제 이 오브젝트에 {query}
에 들어갈 검색어를 입력해주는 오브젝트를 하나 더 작성해야 한다.
맥 OS는 UTF-8을 기반으로 하고 있기 때문에 네이버 카페가 UTF-8 인코딩을 사용하고 있었다면 별다른 처리없이 알프레드에서 입력받은 텍스트를 바로 전달하면 된다. 이럴 때는 Inputs > Keyword 오브젝트를 사용하면 간단하게 처리할 수 있다. 하지만 앞에서 말했듯이 네이버 카페는 CP949를 기반으로 하고 있고, 알프레드를 통해 우리가 입력하는 텍스트는 UTF-8이라 서로 호환되지 않는다. 따라서 UTF-8으로 인코딩된 텍스트를 CP949로 변환하는 과정이 필요하다. 이 때 사용할 수 있는 것이 Script Filter이다.
변환 스크립트 작성
처음 오브젝트를 추가했던 방식대로 Inputs > Script Filter를 추가하자. Keyword 란에는 알프레드에서 카페 검색을 트리거할 키워드를 입력한다. 예를 들어 키워드를 cafe
라고 정해두면 알프레드 창을 열어 cafe
와 스페이스를 입력하면 바로 카페 검색 기능이 활성화된다.
스크립트 필터의 대화상자에는 스크립트 입력칸이 있어 코드를 작성할 수 있다. 프로그래밍 언어(Language)의 종류는 기본값이 bash로 되어있지만 python으로 바꿔주고 다음과 같이 코드를 입력한다.
#-*- coding:utf-8 --
import sys
import unicodedata
import urllib
import json
input = unicodedata.normalize('NFC', unicode(sys.argv[1], 'utf8'))
query = urllib.quote(str(input.encode('cp949')))
items = []
items.append({
'uid': 'search-ncafe',
'type': 'default',
'title': '네이버 카페 검색',
'subtitle': "네이버 카페에서 '%s' 검색하기" % input.encode('utf8'),
'arg': query,
'valid': True
})
print json.dumps({ 'items': items })
Python 코드에서 하는 일은 UTF-8으로 입력된 텍스트를 받아서 처리한 후 알프레드에서 지원하는 JSON 형식에 맞추어 결과를 출력하는 것이다. 특이한 부분은 unicodedata 모듈을 사용해 유니코드를 정규화(normalize)했다는 건데, 맥 OS에서 한글을 처리하는 방식이 독특해서 그대로 사용하기 어려웠기 때문이다. 정규화 과정을 거쳐야만 원하는 형태로 사용할 수 있었다.
JSON 형식에 대한 설명은 알프레드 공식 홈페이지에서 볼 수 있다. 코드를 보면 알 수 있듯이 여러 개의 결과를 보여줄 수도 있으므로 이 워크플로우를 조금 수정한다면 여러 카페를 지원하도록 만들 수도 있을 것이다. 위의 애니메이션을 보고 눈치챈 사람도 있겠지만, 키워드 이후에 검색어를 입력하기 시작하면 코드에서 입력한 title
과 subtitle
값이 각각 사용된다. 원한다면 다른 값으로 바꿔줘도 좋다. 모두 입력했다면 Save 버튼을 눌러 오브젝트를 만들자.
이 워크플로우는 기능이 간단한 편이라 필요한 오브젝트는 두 개뿐이다. 이제 스크립트 필터에서 작성한 검색어를 OpenURL 액션이 받아서 처리하도록 두 워크플로우를 연결해주자. 마우스를 클릭해 스크립트 필터를 선택하면 볼록 튀어나오는 손잡이가 나타나는데 이걸 드래그해서 OpenURL 필터로 연결해주면 된다.
이로써 워크플로우가 완성됐다. 바로 알프레드를 열어 아까 정해둔 키워드와 스페이스를 입력하면 기능을 사용할 수 있다.
PHP 버전
이왕 작성하는 거 다른 익숙한 언어인 PHP로도 작성해보았다. 가장 선호하는 언어는 JS지만 안타깝게도 JS에는 언어 인코딩 변환 모듈이 내장되어 있지 않아서 사용할 수 없었다.
<?php
$input = iconv( 'utf-8-mac', 'utf-8', $argv[1] );
$query = urlencode( iconv( 'utf-8', 'cp949', $input ) );
echo <<< EOS
{
"items": [
{
"uid": "search-ncafe",
"type": "default",
"title": "네이버 카페 검색",
"subtitle": "네이버 카페에서 '{$input}' 검색하기",
"arg": "{$query}",
"valid": true
}
]
}
EOS;
주목할만한 부분은 $input
입력을 받는 곳이다. Python에서 해주었던 정규화 과정을 PHP에서는 utf-8-mac
인코딩에서 utf-8
인코딩으로 변환하는 방식을 통해 처리했다. 이 과정이 없으면 $query
부분이 제대로 표현되지 않을 수 있다.
마치며
알프레드를 몇 년간이나 써오면서도 막상 워크플로우를 작성해봐야겠다는 생각은 해보지 않았는데 직접 작성해보니 그리 어렵지도 않고 코드 사용의 유연성도 있어서 활용할 방법이 꽤 있을 것 같았다. 생각나는 기능이 있으면 앞으로 틈틈이 만들어봐도 좋을 것 같다.
input = unicodedata.normalize('NFC', unicode(sys.argv[1], 'utf8'))
이 부분이 필요한 이유가
ㄱ+ㅏ+ㄴ+ㅏ+ㄷ+ㅏ => 조합형 글자
가나다 => 완성형 글자
조합형 글자를 완성형 글자로 변환하는 과정입니다.
저 부분을 고려하지 못하고 cp949로 변환하면 에러가 나더라구요 ㅋㅋㅋ
맞습니다. 맥에서는 자모를 분리해서 저장하는 NFD 방식을 내부적으로 사용하고 있으니 이를 자모 조합이 완성된 NFC로 정규화하는 과정이 필요한 거죠. 가끔 맥에서 저장한 한글 파일 이름이 윈도우에서는 자모가 분리된 것처럼 보이는 것도 동일한 이유때문이고요.
다만, 조합형, 완성형이라는 용어가 정확하지 않은 느낌인데, 같은 유니코드인데 정규화 방식의 차이라고 표현하는 편이 더 정확할 것 같습니다. 지금은 사용되지 않지만 한글 인코딩 형식에 '조합형'이 있기 때문에 그것과 헷갈릴 여지가 있어서요.
macOS 12.3 업데이트가 되면서 파이썬 2.7 버전이 제거되어 작동하지 않는데요. 파이썬 3로 수정할 수 있는 방법이 있을까요?
Python도 직접 설치해야 하는 것 같아서 아예 OS 차원에서 지원하는 JS를 사용해서 작성해보았습니다.
https://taegon.kim/archives/10419 글을 참고해주세요.