Thunderbirdのメール(メッセージ)を検索・選択するURLプロトコルの追加方法
昨晩から、自分では使わないThunderbirdの拡張機能周りを何故かいろいろ調べていたので、そのメモ。
やりたいこと
HTMLに記述された次のようなリンクが記述されているとする。
<a href="mailmsg://HelloWorld">件名に「HelloWorld」を含むメールを検索する</a>
このリンクをクリックすると、thunderbirdが立ち上がって(or 既に起動している場合、それにフォーカスが移って)、「HelloWorld」という文字列を件名(Subject)に含むメールが選択された状態になるようにしたい。
方法
以下の三つを組み合わせると、それっぽいのができそう。
- "mailmsg://..."というリンクがクリックされた場合に、thunderbirdコマンドが呼び出されるようにレジストリに設定を追加する。 ※ Windowsの場合
- thunderbirdコマンドにメール検索用のオプションを追加する ※ 検索文字列をコマンドラインから渡せるようにする
- thunderbirdにメール検索・選択用のアドオンを追加する ※ 上で取得した検索文字列を元に、メールの検索を行う
実装など
以下は、その実装や手順などのメモ。
※ ちなみにメールの検索にGoldaを用いているため、バージョン3未満のThunderbirdには非対応
・コマンドラインの拡張
まずは、thinderbirdコマンドに次のようなオプションを追加する。
# thunderbirdを起動して、件名に<検索文字列>を含むメールを選択する > thunderbird -srchmsg <検索文字列>
そのためには、C:\Program Files\Mozilla Thunderbird\components\に以下の内容のファイルを追加する*1。
/** * ファイル名: clh_setquery.js * *【参考URL】 * [CommandLineHandler] https://developer.mozilla.org/en/Chrome/Command_Line */ const nsISupports = Components.interfaces.nsISupports; const nsICategoryManager = Components.interfaces.nsICategoryManager; const nsIComponentRegistrar = Components.interfaces.nsIComponentRegistrar; const nsICommandLine = Components.interfaces.nsICommandLine; const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler; const nsIFactory = Components.interfaces.nsIFactory; const nsIModule = Components.interfaces.nsIModule; // contract id const clh_contractID = "@mozilla.org/commandlinehandler/set_message_search_query;1"; // use uuidgen to generate a unique ID const clh_CID = Components.ID("{cfd3d465-ddf2-45dd-8044-464dfcd32b13}"); // category names are sorted alphabetically. Typical command-line handlers use a // category that begins with the letter "m". const clh_category = "m-srchmsg"; /** * The XPCOM component that implements nsICommandLineHandler. * It also implements nsIFactory to serve as its own singleton factory. */ const myAppHandler = { /* nsISupports */ QueryInterface : function clh_QI(iid) { if (iid.equals(nsICommandLineHandler) || iid.equals(nsIFactory) || iid.equals(nsISupports)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; }, /* nsICommandLineHandler */ handle : function clh_handle(cmdLine) { try { // コマンドラインで、'-srchms <検索文字列>'形式のオプションが指定された場合の処理 var query = cmdLine.handleFlagWithParam("srchmsg", false); if (query) { this.helpInfo = query; // 簡単のため、検索文字列の保存には(元々用意されている)helpInfo変数を使うことにする cmdLine.preventDefault = false; } } catch (e) { Components.utils.reportError("incorrect parameter passed to -viewapp on the command line."); } }, /* ヘルプ文字列: 今回はこれを検索文字列保存のために流用するので、初期値は空 */ helpInfo : "", /* nsIFactory */ createInstance : function clh_CI(outer, iid) { if (outer != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return this.QueryInterface(iid); }, lockFactory : function clh_lock(lock) { /* no-op */ } }; /** * The XPCOM glue that implements nsIModule */ const myAppHandlerModule = { /* nsISupports */ QueryInterface : function mod_QI(iid) { if (iid.equals(nsIModule) || iid.equals(nsISupports)) return this; throw Components.results.NS_ERROR_NO_INTERFACE; }, /* nsIModule */ getClassObject : function mod_gch(compMgr, cid, iid) { if (cid.equals(clh_CID)) return myAppHandler.QueryInterface(iid); throw Components.results.NS_ERROR_NOT_REGISTERED; }, registerSelf : function mod_regself(compMgr, fileSpec, location, type) { compMgr.QueryInterface(nsIComponentRegistrar); compMgr.registerFactoryLocation(clh_CID, "myAppHandler", clh_contractID, fileSpec, location, type); var catMan = Components.classes["@mozilla.org/categorymanager;1"]. getService(nsICategoryManager); catMan.addCategoryEntry("command-line-handler", clh_category, clh_contractID, true, true); }, unregisterSelf : function mod_unreg(compMgr, location, type) { compMgr.QueryInterface(nsIComponentRegistrar); compMgr.unregisterFactoryLocation(clh_CID, location); var catMan = Components.classes["@mozilla.org/categorymanager;1"]. getService(nsICategoryManager); catMan.deleteCategoryEntry("command-line-handler", clh_category); }, canUnload : function (compMgr) { return true; } }; /* The NSGetModule function is the magic entry point that XPCOM uses to find what XPCOM objects * this component provides */ function NSGetModule(comMgr, fileSpec) { return myAppHandlerModule; }
ファイル追加後は、起動しているThunderbirdプロセスがあれば終了し、C:\Documents and Settings\<ログインユーザ名>\Application Data\Mozilla\Firefox\Profiles\<適当な文字列>.default\以下にあるcompreg.datというキャッシュファイルを削除する。
これで、thunderbirdコマンドに"srchmsg"オプションが追加された。
・起動時に指定された文字列でメールを検索・選択するアドオン
次は、上のコマンドオプションに指定された文字列を元に、メールの検索・選択を行うアドオンの実装。
※ アドオンの作り方は、次のサイトを参考にした: Building a Thunderbird extension 1: introduction - Mozilla | MDN
ディレクトリ構造:
- search_message/ - install.rdf - chrome.manifest - content/ - search_message.js - search_message.xul
install.rdf:
<?xml version="1.0"?> <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> <Description about="urn:mozilla:install-manifest"> <em:id>search_message@ab.cd</em:id> <em:name>Search Message</em:name> <em:version>1.0</em:version> <em:creator>sile</em:creator> <em:targetApplication> <Description> <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id> <em:minVersion>3.0</em:minVersion> <em:maxVersion>3.0.*</em:maxVersion> </Description> </em:targetApplication> </Description> </RDF>
chrome.manifest:
content search_message content/ overlay chrome://messenger/content/messenger.xul chrome://search_message/content/search_message.xul
search_message.xul:
<?xml version="1.0"?> <!-- search_message.jsをロードするだけ --> <overlay id="search_message" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <script type="application/x-javascript" src="chrome://search_message/content/search_message.js" /> </overlay>
search_message.js:
/** * このjavascriptは、"focus"イベントにメッセージ検索関数(search_message)を登録する。 * フォーカスが移るたびに、search_message関数が呼び出されることになるが、(かなり手抜きの)重複呼び出しチェックがあるので、 * 実質上コマンドの実行直後の一回しか、メッセージの検索・選択は行われない。 * * [注意1] 条件に該当するメールが複数ある場合でも、一つのメールしか選択されない。 * [注意2] 同じ文字列を続けて検索した場合、二度目以降は無視される。 ※ 重複チェックとの関係上 * *【参考URL】 * [Gloda] https://developer.mozilla.org/en/Thunderbird/Gloda_examples * [Gloda] https://developer.mozilla.org/en/Thunderbird/Creating_a_Gloda_message_query * [Message選択] https://developer.mozilla.org/en/Extensions/Thunderbird/HowTos/Common_Thunderbird_Use_Cases/Open_Folder * [JavaScriptからのXPCOM参照] https://developer.mozilla.org/en/How_to_Build_an_XPCOM_Component_in_Javascript */ // メッセージ検索用のリスナー srchListener = { onItemsAdded: function _onItemsAdded(aItems, aCollection) {}, onItemsModified: function _onItemsModified(aItems, aCollection) {}, onItemsRemoved: function _onItemsRemoved(aItems, aCollection) {}, onQueryCompleted: function _onQueryCompleted(msg_coll) { try{ // 最初の一つだけを選択する msg=msg_coll.items.pop(); gFolderTreeView.selectFolder(msg.folderMessage.folder); // フォルダを移動 gFolderDisplay.selectMessage(msg.folderMessage); // メッセージを選択 } catch(e) { alert("Error: "+e); } } } // 検索対象となる件名文字列を取得する var get_search_subject = function () { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var clh = Components.classes['@mozilla.org/commandlinehandler/set_message_search_query;1'] .createInstance(Components.interfaces.nsICommandLineHandler); return clh.helpInfo; } // 前回の検索した件名文字列を保存するグローバル変数 // ※ 現在は、'focus'イベントに検索関数を登録しているため、この変数が必要。 // これがないと、ユーザが画面を切り替えるたびに、メッセージの検索+選択が行われてしまう。 // (thunderbirdコマンド実行時にだけ、検索が行われてほしい)。 // ※ 以下の実装では、件名文字列をそのまま保存・比較しているが、'文字列+コマンド実行時のタイムスタンプ'などの形式の方が望ましい。 // ※ もしnsICommandLineHandlerから、thunderbirdのイベントを呼び出せるなら、この検索専用のイベントを作成・使用した方が良い。 g_prev_sbj=''; // メッセージを検索(+ 選択)する var search_message = function() { // 検索文字列を取得する sbj = decodeURIComponent(get_search_subject().replace(/^mailmsg:\/\//,"").replace(/\/$/,"")); if(sbj==g_prev_sbj) return; // 重複検索を避ける g_prev_sbj=sbj; try { // Glodaを用いて検索を行う query = Gloda.newQuery(Gloda.NOUN_MESSAGE); query.subjectMatches(sbj); query.getCollection(srchListener); } catch(e) { alert("ERROR: "+e); } } // イベントを登録する addEventListener('focus', search_message, false);
これらのファイルを、zip圧縮でまとめ、Thunderbirdのアドオン画面からインストールすれば、コマンドラインから(件名による)メールの検索・選択が行えるようになる。
※ 圧縮ファイルの拡張子はzipではなく、xpiにする。
※ 圧縮ディレクトリの直下に、install.rdfやchrome.manifestが配置されるようにする。
# 件名に"scheme"を含むメールを検索 > thunderbird -srchmsg scheme
・srchmsg URLプロトコルの追加
ここまででコマンドラインからのメールの検索・選択はできるようになったので、最後はmailtoなどと同様に、HTMLのリンクを使ってブラウザから同様のことを行えるようにする。
そのために必要なのは、以下のようなレジストリの設定のみ。
;; 基本的には、mailtoの設定とほぼ同様 + HKEY_LOCAL_MACHINE\SOFTWARE\Classes\mailmsg\ - (既定):REG_SZ "URL:mailmsg Protocol" - EditFlags:REG_BINARY 02 00 00 00 - URL Protocol:REG_SZ "" + DefaultIcon\ - (既定):REG_SZ "[thunderbird.exeのパス]" + shell\ + open\ + command\ - (既定):REG_SZ "[thunderbird.exeのパス] -srchmsg "%1"
以上で、冒頭に載せたようなHTMLリンクが使えるようになる(はず)。
追記(2010/02/22): テスト用HTML
<html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8" /> <title>メール選択テスト</title> </head> <body> <script> <!-- var srchmail = function() { location.href = "mailmsg://"+encodeURIComponent(document.getElementById("subject").value); } --> </script> Subject: <input type="text" id="subject" /><input type="button" value="メールを選択する" onclick="srchmail()" /> </body> </html>