Google Apps Scriptで共有の外部連絡先を管理する【GAS】

社内のGmail等やContactsに於いて連絡先は普段から利用する手段になっています。しかし、ディレクトリに表示されるのはあくまでもGoogle Workspaceにアカウント登録してる人のデータのみ。自分のお客様や取引先の連絡先データはGmail同様個人で管理するといったケースが多かったと思います。

しかし、Google Workspaceには共有外部連絡先というものが存在しており、ここで登録しておくことで個別ユーザで管理せずとも、組織全体で管理が出来るのだとか。ということでこの機能を調査してみました。

今回利用するスプレッドシート等

本APIを利用するに当たって簡単に利用することの出来る外部ライブラリもあるようです。今回は直接APIを叩いて、外部共有連絡先を操作してみたいと思います。また、プログラミングではなくGoogle Workspace用のアドオンとしてもいくつかリリースされてるようです。

社内のメンバーのディレクトリやメンバーの連絡先については、People APIで管理・操作が可能です。

※これを更に進化させて、Gmailアドオンとし登録したり検索したりユーザ所属部署によって絞り込みで表示したりなど出来れば尚便利になるかと。

Google Apps ScriptでContactsをPeople APIで弄る【GAS】

共有外部連絡先の概要

これまでの共有手段

例えば営業チームであったり管理部門であったり、個別のユーザがこれまで得た外部の連絡先(お客様やサポート担当、取引先窓口)について、通常は個別のユーザが保持し、これを組織内の別の人に共有するといったような手段を使っていました。チーム内や部門内で同じ共有連絡先を利用するのに、誰かにつくってもらって共有というのは、管理としては微妙で、消滅した場合には厄介です。

故に、別のソリューションで共有連絡先を管理するサービスを導入してみたりといったケースが現状多いのではないかと思います。以下はこれまでの共有手段の方法です。

  1. Google Workspaceの管理コンソールにて、管理者がディレクトリの設定に於いて、連絡先の共有がオンになってる必要があります。
  2. Google Contactsを開く
  3. 右上の歯車アイコンをクリックする⇒アクセスを委任を選択
  4. ダイアログが出てくるので、代理人を招待をクリック
  5. 対象者のメアドを入力して送信する
  6. これで代理人は本人に変わって連絡先の管理を行うことが可能

本来は組織として管理すべきリソースである外部連絡先を個人単位で管理は、業務引継ぎ等でも障害になりえるので、望ましくないです。かといって、CSVに出力しておいて、新たな担当がインポートというのも効率が悪い。

図:あくまでも委任です

共有外部連絡先とは?

Google Workspaceの組織として管理してる連絡帳でずいぶん昔から存在はしてるようです(XMLのSchemeを見ると、Google Talkの時代のものみたい)。ただ、公式の説明にもあるようにこの連絡帳こと共有外部連絡先を管理するためのGUIが管理コンソール上に存在しません。ADからの同期やDomain Shared Contacts APIを利用してでしか管理が出来ないようで、存在自体つい最近まで知りませんでした。Issue TrackerでもGUIとPeople APIに統合するよう要望が2022年に出ていますがぱったり途絶えています。

故に専任管理者や詳しい人が居ない組織でこの機能を使おうと思っても、手段がMarket Placeのアドオンだけという状況です。そこで、スプレッドシートを用意して実行するだけで、追加や削除・更新などができれば便利だと思い調査してみることにしました。

共有外部連絡先には現在以下のような制限があります。

  • 最大20万件まで登録可能
  • もしくは合計40MBに到達するまで登録可能
  • Business Starterから利用が可能

事前準備

管理コンソールで設定変更

Domain Shared Contacts APIでデータを送り込んで確かに存在するのだけれど、なぜかディレクトリには出てこないという現象があったので悩んでいました。しらべて見た所、管理コンソールにて以下の設定になっていないとディレクトリには出てこないようです。

  1. 管理コンソールにログインする
  2. 左サイドバーからディレクトリ⇒ディレクトリ設定を開く
  3. 共有設定を開く
  4. 連絡先の共有を開き、「連絡先管理ツールの参照可能な「ディレクトリ」に表示する項目を選択してください。」にて、「
    ドメインのプロフィールとドメインの共有の連絡先の両方を表示する」に変更し保存する
  5. 続けて、外部ディレクトリ共有を開き、「組織のデータと認証済みユーザーの基本的なプロフィール項目」に変更して保存する

これをしないと共有連絡先はディレクトリに表示されないようです。

図:連絡先共有の設定内容

図:外部ディレクトリ共有の設定

プロジェクトの移動

ちょっと特殊なAPIの利用となるので、Google Apps ScriptのプロジェクトをGCPと連結する必要があります。

  1. Google Cloud Consoleを開く
  2. 左上にある▼をクリックする
  3. ダイアログが出てくるので、新規プロジェクトを作るか?既存のプロジェクトを選択する。この時、Google Workspaceであれば選択元は「自分のドメイン」を選択する必要があります。
  4. プロジェクト情報パネルから「プロジェクト番号」をコピーする
  5. 対象のGoogle Apps Scriptのスクリプトエディタを開く
  6. サイドバーからプロジェクト設定を開く
  7. プロジェクトを変更ボタンをクリック
  8. GCPのプロジェクト番号に、4.の番号を入れてプロジェクトを設定をクリック

図:プロジェクト番号をコピーしておきます

図:プロジェクト変更画面

APIの有効化

前述のプロジェクト番号のプロジェクトに於いて、

GCP側で今回利用するAPIを有効化する必要性があります。

  1. GCPを開き、サイドバーからAPIとサービスを開きます。
  2. 上部にある「APIとサービスの有効化」をクリック
  3. 「Contacts」を検索し、Contacts APIクリックします。
  4. 有効化をクリックします。

appscript.jsonの編集

プロジェクト移動とAPIの有効化だけではどうにもうまく動かなかったのですが、appscripts.jsonを弄って手動でスコープの追加が必要だったようです。

スクリプトエディタの左サイドバーから「プロジェクト設定」を開き、「appsscript.json」マニフェスト ファイルをエディタで表示するにチェックを入れて、appsscript.jsonを表示する。その後そのファイルを開き、以下のように記述を行います(https://www.google.com/m8/feedsというのがソレ)。必須の作業です。これをしてしまうと、今後メソッドを追加したときに追加の認証は手動で、Scopeを入れてあげないと認証されないので要注意。

最後に適当に関数を実行して認証をする必要があります。

"oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/spreadsheets",
    "https://www.google.com/m8/feeds"
]

図:認証実行時の要求スコープ

ソースコード

このAPI自体がかなり昔から存在してるようで、他のAPIと比較するとちょっと古めかしいXMLを構築して送信するという面倒な仕組みになっています。JSONで構築したものをポーンと投げられるわけじゃないので、モダンなAPIとしてアップデートしてほしいなぁ。

共有連絡先一覧を取得する

APIで追加した共有連絡先の一覧を取得します。現在本来はContactsAppはDeprecatedなのですが、共有連絡先の取得に関してだけはDeprecatedになっていないというオカシナ状態ですが利用できるので使います。いずれPeople APIに統合されるでしょう。

まだ中途半端なコードですが、ここで取得できる一番最後のcontactidが更新時や削除時に必要になるので、スプレッドシートに記録するようにしたらよいでしょう。

//利用するドメイン
var domain = "ここに自社ドメイン名を入れる";

//現在登録済みの外部共有連絡先を取得する
function getContacts(){
  //ContactsAppでないと取得できないようで
  var contacts = ContactsApp.getContacts();

  //送信パラメータを設定する
  var params = {
    method      : "get",
    contentType : "application/atom+xml",
    headers     : {"Authorization": "Bearer " + ScriptApp.getOAuthToken(),"GData-Version": "3.0"},
    muteHttpExceptions  : true
  };
  
  //リクエスト用の変数
  var startIndex=1;
  var data,respCode,resp;

  //リクエストを実行
  resp = UrlFetchApp.fetch("https://www.google.com/m8/feeds/contacts/"+ domain +"/full?alt=json&start-index="+startIndex, params);
  
  //レスポンスコードを取得
  respCode=resp.getResponseCode();

  //共有連絡先一覧のレスポンスを取得する
  data = JSON.parse(resp.getContentText());
  
  //1個目だけとりあえずメアドのみ表示
  let entry = data.feed.entry[0]
  console.log(entry.gd$email[0].address)

  //entryからcontactidを取り出す
  let linkman = entry.link;
  for(var i = 0;i<linkman.length;i++){
    //レコードを一個取り出す
    let rec = linkman[i];

    //relがeditの場合がcontact_idとなる
    if(rec.rel == "edit"){
      //contactidを表示
      console.log(rec.href)
      break;
    }
  }
}

共有連絡先にデータを追加する

//連絡先を追加する
function addContact(){
  //スプレッドシートを取得
  let ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName("共有連絡先");
  let ssdata = sheet.getRange("A2:O").getValues();

  //選択があり、登録済みではないものだけチョイス
  for(var i = 0;i<ssdata.length;i++){
    //レコードを一個取り出す
    let rec = ssdata[i];

    //条件判定
    if(rec[1] == true){
      if(rec[0] == "" || rec[0] == undefined){
        //次の処理へ進む
      }else{
        //処理しない
        continue;
      }
    }else{
      //処理しない
      continue;
    }

    //Null値を訂正(登録エラーを防ぐため)
    let tempword = "--"
    if (rec[6] =='' || rec[6] == undefined || rec[6]==null) rec[6] = tempword;    //予備のメアド
    if (rec[7] =='' || rec[7] == undefined || rec[7]==null) prec[7] = tempword;   //電話番号
    if (rec[8] =='' || rec[8] == undefined || rec[8]==null) rec[8] = tempword;    //予備の電話番号
    if (rec[9] =='' || rec[9] == undefined || rec[9]==null) rec[9] = tempword;      //国名
    if (rec[10] =='' || rec[10] == undefined || rec[10]==null) rec[10] = tempword;  //郵便番号
    if (rec[11] =='' || rec[11] == undefined || rec[11]==null) rec[11] = tempword;  //県名
    if (rec[12] =='' || rec[12] == undefined || rec[12]==null) rec[12] = tempword;  //市町村
    if (rec[13] =='' || rec[13] == undefined || rec[13]==null) rec[13] = tempword;  //番地
    
    //登録用XMLを構築する
    let payload = "<atom:entry xmlns:atom='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'>"
                + "<atom:category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact' />"
                + "<gd:name>"
                + "<gd:givenName>" + rec[3] + "</gd:givenName>"
                + "<gd:familyName>" + rec[2] + "</gd:familyName>"
                + "<gd:fullName>" + rec[4] + "</gd:fullName>"
                + "</gd:name>"
                + "<atom:content type='text'>Notes</atom:content>"
                + "<gd:email rel='http://schemas.google.com/g/2005#work' primary='true' address='" + rec[5] + "' displayName='" + rec[4] +"' />"
                + "<gd:email rel='http://schemas.google.com/g/2005#home'    address='" + rec[6] + "' />"
                + "<gd:phoneNumber rel='http://schemas.google.com/g/2005#work' primary='true'>" + rec[7] + "</gd:phoneNumber>"
                + "<gd:phoneNumber rel='http://schemas.google.com/g/2005#home'>" + rec[8] + "</gd:phoneNumber>"
                + "<gd:im address='" + rec[5] + "'    protocol='http://schemas.google.com/g/2005#GOOGLE_TALK'    primary='true'    rel='http://schemas.google.com/g/2005#home' />"
                + "<gd:structuredPostalAddress rel='http://schemas.google.com/g/2005#work' primary='true'>"
                + "<gd:city>" + rec[12] + "</gd:city>"
                + "<gd:street>" + rec[13] + "</gd:street>"
                + "<gd:region>" + rec[11] + "</gd:region>"
                + "<gd:postcode>" + rec[10] + "</gd:postcode>"
                + "<gd:country>" + rec[9] + "</gd:country>"
                + "<gd:formattedAddress>" + rec[12] + " " + rec[13] + "</gd:formattedAddress>"
                + "</gd:structuredPostalAddress>"
                + "</atom:entry>"

    //リクエストパラメータの指定
    var params = {
      method      : "post",
      contentType : "application/atom+xml",
      headers     : {"Authorization": "Bearer " + ScriptApp.getOAuthToken(),"GData-Version": "3.0"},
      payload: payload,
      muteHttpExceptions  : true
    };
    
    //HTTPリクエスト
    var res = UrlFetchApp.fetch("https://www.google.com/m8/feeds/contacts/"+ domain +"/full", params);
    
    //レスポンスコードを取得
    var rescode = res.getResponseCode();

    //書き込みセル番地
    let cnt = i + 2;

    //処理内容を追記する
    if (rescode == '201' || rescode == '200') {
      sheet.getRange("O" + cnt).setValue("登録済み");
    }else{
      sheet.getRange("O" + cnt).setValue("エラー");
    }

    //次の処理前にスリープをいれる
    Utilities.sleep(500)
  }

  //B列を全部クリアする
  sheet.getRange("B2:B").clearContent();

  //終了メッセージ
  SpreadsheetApp.getUi().alert("処理が完了しました。")
}

スプレッドシートのデータを読み取って、O列の値が空のものだけを処理対象として追加するようにしています。また登録できたら「登録済み」とし、エラーの場合は「エラー」をO列に書き込みます。

また、登録する対象はB列でチェックを入れなければスルーします。

JSONで登録できるわけじゃないのでXML構築部分が非常に冗長ですが、それぞれセルの値を当て込んでXMLとし、UrlfetchAppでリクエストを投げるという仕組みになっています。

最後に処理が完了したら、B列のチェックをすべてクリアします。

実行結果

実行した結果、Google Contactsのディレクトリに対して外部のドメインのアドレスが表示されるようになりました。通常ディレクトリはテナント内の組織ユーザのみが表示される場所ですが、APIによって追加したユーザもここに表示されるようです。

また、Gmailの送信先でメアドを入れるとオートコンプリートにて、自身の連絡先に入れていないユーザなのに、APIで追加した連絡先がきちんと出てくるようになりました。

図:Google Contactsのディレクトリに表示された

図:Gmailのオートコンプリートにも出てくる

関連リンク

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)