Edited at

JXA(JavaScript for Automation)を使って,OSXの連絡先.appに新しい連絡先を追加する。(弁護士の連絡先を追加する作例)

Mac OSXのアプリケーションの多くは,OSAという仕組みを使ってスクリプトで操作できます。

OSAで使える言語は,かつてはAppleScriptだけでしたが,現在はJavaScriptも使うことができます。OSAで動かすJavaScriptはJXA (JavaScript for Automation)と呼ばれます。

つまり,JavaScriptを使って連絡先.appの住所録に連絡先を追加したりできるってことです。

ということで,試してみましょう。


下拵したごしらえ:WebAPIで取得したJSONをオブジェクト化する

どんな例が良いかなーと思ったのですが,弁護士登録番号から弁護士の連絡先を追加したいなーという場面にしばしば出くわすかと思われます。

弁護士登録番号から弁護士の情報を取得するAPIを作ってあるので,それを使ってみましょう。

ちなみに,氏名から弁護士登録番号を調べたいときは法技研弁護士検索が便利だよ!(宣伝)

http://lawyersearch.azurewebsites.net/infoOf/[登録番号(半角数字)]

たとえば http://lawyersearch.azurewebsites.net/infoOf/49702 とかやると,私の登録情報がJSONで帰ってきます。

app = Application.currentApplication(); //doShellScriptを使えるようにする。

app.includeStandardAdditions = true;

// 弁護士情報を取得
var num = app.displayDialog(
"弁護士登録番号を入力してください。",
{defaultAnswer:49702} //テストのためデフォルト値に私の登録番号を入れています。
).textReturned;
var resp = app.doShellScript("curl http://lawyersearch.azurewebsites.net/infoOf/"+num);
var lawyer = JSON.parse(resp);

OSAの言語にJavaScriptを選択すると,JavaScriptの機能を使えるので便利。JSON文字列をオブジェクト化するのもJSON.parse(jsonString)で一発です。今回AppleScriptじゃなくてJXAにしたのも,JSONのパースが楽だからだったり。閑話休題。

JSONの構造はこんな感じ。

{

"FAX番号":"012-345-6789",
"事務所住所":"都道府県 市町村町名1-2-3 建物名とか",
"事務所名":"さんぷる法律事務所",
"会員区分":"弁護士",
"弁護士会":"弁護士会名",
"性別":"●性",
"氏名":"苗字 名前",
"氏名かな":"みょうじ なまえ",
"現旧区分":"",
"登録番号":"49702",
"郵便番号":"1234567",
"電話番号":"012-345-6780"
}

ついでに,lawyer.事務所住所を適当に分解して,lawyer.state=都道府県名, lawyer.city=市町村名lawyer.street=町名以下とでもしておいて下さい。(末尾に私が適当に書いたコードの全文を載せますが,市町村名の取り出しに自信がなかったり…。)


新しい連絡先を作り,登録する。

連絡先.appにおける個々人の連絡先は,Personクラスで定義されています。Personオブジェクトは,first name,last name等のプロパティの他,Addressクラスの配列であるaddressesPhoneクラスの配列であるphonesを持っています。

詳細はスクリプトエディタの「用語説明を開く」の結果を参照。なお,JXAの場合,AppleScriptで複合語になっているものはcamelCaseで書けば参照できます(例:AppleScript:first name =>JavaScript:firstName)。

手順としては,var newPerson = Application("Contacts").Person().make();で作ったPersonオブジェクトnewPersonに,newPerson.lastName = "苗字";とかnewPerson.phones.push(phone);とかで情報を追加していきます。

そんでもって最後に,Application("Contacts").people.push(newPerson);した上で,Application("Contacts").save();すればOK。

(2019-02-20追記:pushしなくても追加されるっぽいし,pushすると名前無しのpersonまで追加されちゃう?)

連絡先.app(のUI?)に反映されるのはsave()した時です。

コードにするとだいたいこんな感じ。

// 連絡先への登録

var Contacts = Application("Contacts");
var newPerson = Contacts.Person().make();

props = { //扱いやすいように,Personの仕様に合わせた辞書を作る。
jobTitle: "弁護士",
lastName: lawyer.氏名.split(" ")[0],
phoneticLastName: lawyer.氏名かな.split(/\s/)[0],
firstName: lawyer.氏名.split(" ")[1],
phoneticFirstName: lawyer.氏名かな.split(/\s/)[1],
suffix: "先生",
organization: lawyer.事務所名
};
// そんでもって一気に登録する。
for(var theProp in props){
newPerson[theProp] = props[theProp];
}
//電話番号の登録。一定のラベル名は連絡先.appで日本語表示されます。
//PhoneクラスはContactInfoクラスを承継しているので,labelとvalueを持つ。
//Personのインスタンス化にはmake()が必要なのに,Phone等のインスタンス化はmake()が必要ない不思議。関数を持たない構造体に過ぎないから?
newPerson.phones.push(Contacts.Phone({label:"work", value:lawyer.電話番号}));
newPerson.phones.push(Contacts.Phone({label:"work fax", value:lawyer.FAX番号}));

//Addressクラスは内容が複雑。
newPerson.addresses.push(Contacts.Address({
label: "work",
zip: lawyer.郵便番号,
state: lawyer.state,
city: lawyer.city,
street: lawyer.street,
country: "日本"
}));

// Contacts.people.push(newPerson); //2019-02-20追記:pushしなくても追加される?
Contacts.save();

Contacts.selection.set(newPerson); //2019-02-20追記:追加した弁護士を表示

上記の作例で連絡先.appのだいたいの使い方がわかるかなーと思うので,これを入り口的な参考に,便利なスクリプトを作っていただければ幸いです(他力本願)。


(住所の下拵えも含む,あまり優れていない)コードの全文


app = Application.currentApplication();
app.includeStandardAdditions = true;

/* 弁護士情報を取得 */
var num = app.displayDialog("弁護士登録番号を入力してください。", {defaultAnswer:49702}).textReturned;
var resp = app.doShellScript("curl http://lawyersearch.azurewebsites.net/infoOf/"+num);
var lawyer = JSON.parse(resp);

app.displayDialog( lawyer.氏名+"を登録しますか?" );

/* 住所の下処理 */
//addressから市町村名を取り出す関数。きちゃない
function extractCity( address ){
var ExCities = ["武蔵村山市", "十日町市", "野々市市", "四日市市", "廿日市市","東村山市"];
var GunCities = ["郡山市", "郡上市", "蒲郡市", "上郡町", "大和郡山市", "小郡市"];
//郡部チェック
var reGun = /^.{1,4}郡.+?[町村]/;
if( reGun.test(address) ){
for( var i in GunCities ){
if( address.substring(0,2)==GunCities[i].substring(0,2) ){
return GunCities[i];
}
}
return address.match(reGun)[0];
}
//郡部では無い市区町村
var reCity = /^(.+?[市区町村])(.*?[市町村])?/;
var res = address.match(reCity);
if(!res[2]){ //紛らわしく無い場合
return res[1];
}else{
for( var i in ExCities ){
if( address.substring(0,3) == ExCities[i].substring(0,3) ){
return ExCities[i];
}
}
if( address.charAt(2).match(/[市町村]/)){
return address.substring(0,3);
}
return res[1];
}
return "";
}
var address = lawyer.事務所住所.split(" ");
lawyer.state = address.shift();
lawyer.city = extractCity(address[0]);
lawyer.street = address.join(" ").substring(lawyer.city.length);

/* 連絡先への登録 */
var Contacts = Application("Contacts");
var newPerson = Contacts.Person().make();

props = {
jobTitle: "弁護士",
lastName: lawyer.氏名.split(" ")[0],
phoneticLastName: lawyer.氏名かな.split(/\s/)[0],
firstName: lawyer.氏名.split(" ")[1],
phoneticFirstName: lawyer.氏名かな.split(/\s/)[1],
suffix: "先生",
organization: lawyer.事務所名
};
for(var theProp in props){
newPerson[theProp] = props[theProp];
}

newPerson.phones.push(Contacts.Phone({label:"work", value:lawyer.電話番号}));
newPerson.phones.push(Contacts.Phone({label:"work fax", value:lawyer.FAX番号}));
newPerson.addresses.push(Contacts.Address({
label: "work",
zip: lawyer.郵便番号,
state: lawyer.state,
city: lawyer.city,
street: lawyer.street,
country: "日本"
}));

// Contacts.people.push(newPerson); //2019-02-20追記:pushしなくても追加される?
Contacts.save();

Contacts.selection.set(newPerson); //2019-02-20追記:追加した弁護士を表示


おまけ:eFax用のメールアドレスを登録する

弊所ではFAXの送受信をPDFのメール送受信で行うeFaxを使っておりまして,それ用のメールアドレスも登録するようにしてみます。

下記のコードを適当な場所に追加。

// FAX番号文字列からeFax用メールアドレスを作成

function createEFaxAddressFromFaxNumber( numString ){
var suffix = "@efaxsend.com";
return "81" + numString.slice(1).split(/[^\d]/).join("") + suffix;
}

newPerson.emails.push(Contacts.Email({
label:"efax",
value:createEFaxAddressFromFaxNumber(lawyer.FAX番号)
}));


おまけ:日弁連の登録番号を記録してみる。

弁護士は全員,一意な整数の登録番号によって登録されています。最近は,一時的に弁護士登録を辞めても復帰するときに以前の番号が復活するとかなんとか。

で,これをどこかに記録しておけば後々で更新があったときに便利かなーと思うので,とりあえずこんな感じで。

newPerson.note = "日弁連登録番号:" + num;