「Google Apps Script」と「Garoon API」を使って最新の掲示板を取得

#はじめに

今回自分が作成したものはGaroon APIを利用してGaroonに登録してある掲示板の最新の内容を取得して、その値をSlackになげるというプログラムを作りました。
このプログラムはGoogle Apps Scriptのトリガーを利用し、プログラムを30分ごとに繰り返しているので放置していても勝手にSlackに投げてくれます。

##注意
※この記事ではGoogle Apps Script(以下、GAS)を使っています。
※このプログラムは知り合いとともに作成したものです。似たようなプログラムをQiita内で見かけたら察してください。
※XMLの知識0の状態から始めたのでおかしい部分もあると思います。
※ソースコードだけ見たい方はまとめの部分をご覧ください。
※コードを実行するためにはスクリプトエディタから実行するか、トリガーの設定が必要です。
※try~catch()のcatch()部分以外のLogger.log()は値確認に使用しているだけです。


##ついでに豆知識
 - Chromeを使用中にF3を押すと右上に検索欄のようなものが出てきます。そこにキーワードを入力すると該当するキーワードが色付けされ分かりやすくなり、値の確認などに便利です。
 - XMLのRequestとResponseだけを試してみたい方はChromeの拡張機能の「Advanced REST client」がおススメです。URLとPOSTとPayloadとContent-Typeの「application/XML」を指定してRequestすれば簡単にResponseを取得できます。
 - 個人的に使った機能を簡単に書きました。GASが初見の方はついでに見てみてください。
  - [GAS（GoogleAppsScript）で使った機能について簡単に説明](http://qiita.com/y-ebihara/items/e10ebfe9d1a4842f2e90)



##ガルーンAPI
Cybouz株式会社の「Garoon API」を利用しました。Garoon APIは「Garoon」というグループウェアに対してユーザの登録や削除、値の取得等を可能にしたもので、今回は掲示板の取得のために利用しました。利用する際の注意点としてGaroon APIは「REST API方式」ではなく「SOAP API方式」ですのでデータの送信方法等に気を付けてください。
詳しくはこれらを参照してください。

- Garoonとは
 - https://garoon.cybozu.co.jp/
- Garoon APIの概要　(XMLやWSDLについて理解してないと苦戦すると思います。)
 - https://cybozudev.zendesk.com/hc/ja/categories/2001577604
- SOAP APIの共通仕様
 - https://cybozudev.zendesk.com/hc/ja/articles/202228464-SOAP-API%E3%81%AE%E5%85%B1%E9%80%9A%E4%BB%95%E6%A7%98

APIの概要にも書いてありますがSOAP APIの定義はの送信の定義ファイルがそれぞれ下記のURLにWSDLファイルとして書いてあります。このプログラムでは「Garoon on cybozu.com」のURLを想定しています。
GaroonのWSDLにはAPIの定義やNameSpace、スキーマ等について書かれているようです。完全に理解しているわけではないので説明はこの程度にしておきます。

```
Garoon on cybozu.com：
 https://（サブドメイン名）.cybozu.com/g/index.csp?WSDL
パッケージ版 Windows 環境：
 http://（インストールしたサーバーの IP アドレスまたはホスト名）/scripts/（インストール識別子）/grn.exe?WSDL
パッケージ版 Linux 環境：
 http://（インストールしたサーバーの IP アドレスまたはホスト名）/cgi-bin/（インストール識別子）/grn.cgi?WSDL

```

#Cybouzに送信するXMLのpayload部分を作成
注意）ソースコードのまとめでは最後のSlackに投げるメソッドの手前に記述しています。
　　　XMLの送信と取得を理解していないと何もできないので先に説明しています。

まず最初にXMLファイルは大まかにいうとHeader部分とpayload部分に分かれています。今回はHeader部分は記述せずCybouzに送るXMLのpayload部分のみを作成しています。
payloadとは送信するXMLのBodyのようなものだと思っていただければ大丈夫だと思います。
このメソッドでは「API機能名」とそれに付随する「Parameterタグ」を代入することにより使いまわしができるようにしています。
送信方法の指定は次の「更新通知を取得する」の「UrlFetchApp.fetch(url, options)」というコード部分で説明しているのでそちらを参照してください。
このソースのXML文は'(シングルクォーテーション)で囲み、+で次の行に繋げています。正規表現にするため\ (バックスラッシュ)又は￥マークを"(ダブルクォーテーション)の前に入れ、"(ダブルクォーテーション)をエスケープしています。


```lang:createXML()
/**
 *Cybouzに送るXMLのpayload部分を作成
 *@param {payload}  payload(データ本体)
 *
 **/
//()の中には'使いたいAPI機能名'と'＜parameter＞部分'を代入している。
function createXML(API,parameters){
    //xmlns等(Attribute)の前にはスペースor改行が必要なので注意
    var payload ='<?xml version=\"1.0\" encoding=\"UTF-8\"?>'+
'<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\"'+
 ' xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"'+
 ' xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"'+
 ' xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"'+
 ' xmlns:base_services=\"http://wsdl.cybozu.co.jp/base/2008\">'+
 '<SOAP-ENV:Header>'+
  '<Action SOAP-ENV:mustUnderstand=\"1\"'+
  ' xmlns=\"http://schemas.xmlsoap.org/ws/2003/03/addressing\">'+
   API+
  '</Action>'+
  '<Security xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\"'+
  'SOAP-ENV:mustUnderstand=\"1\"'+
  ' xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\">'+
   '<UsernameToken wsu:Id=\"id\">'+
    '<Username>登録名</Username>'+
    '<Password>パスワード</Password>'+
   '</UsernameToken>'+
  '</Security>'+
  '<Timestamp SOAP-ENV:mustUnderstand=\"1\" Id=\"id\"'+
  ' xmlns=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">'+
   '<Created>2010-08-12T14:45:00Z</Created>'+
   '<Expires>2037-08-12T14:45:00Z</Expires>'+
  '</Timestamp>'+
  '<Locale>jp</Locale>'+
 '</SOAP-ENV:Header>'+
 '<SOAP-ENV:Body>'+
  '<'+API+'>'+
    parameters+
  '</'+API+'>'+
 '</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>'
  
  return payload;
}
```
####createXMLメソッドの解説
まず引数として'使いたいAPI機能名'と'＜parameter＞部分'を取得しています。この2つは＜Action＞タグと＜SOAP-ENV:Body＞の中で使います。理由としては使いたいAPI機能ごとに記述する値が変化するため引数にしています。
今回は＜UsernameToken＞タグと＜Timestamp＞タグを変更していないのでpayloadで変更しなければいけないのはこの２つだけです。
時間の「2015-10-28T01:00:21.159Z」という表記については次の「更新通知を取得する」にて説明しています。

```lang:例）掲示板の更新通知を取得する場合のXMLの一部分

<Action SOAP-ENV:mustUnderstand="1"
  xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing">
   NotificationGetNotificationVersions
</Action>

～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～
～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～～

<SOAP-ENV:Body>
  <NotificationGetNotificationVersions>
    <parameters start="2015-10-28T01:00:21.159Z" end="2015-10-29T13:00:21.159Z" module_id="grn.bulletin">
      <notification_item version="0">
        <notification_id module_id="grn.bulletin" item="-1"/>
      </notification_item>
    </parameters>
  <NotificationGetNotificationVersions>
 <SOAP-ENV:Body>

```
#掲示板の最新の更新通知を取得する
※プログラムを実行する際はこのファンクションから実行すればSlackに投げるところまで実行されるようにしています。

このファンクションでは指定した時間内の掲示板IDを取得します。今回は最初に現在時刻を取得して30分以内の掲示板IDを取得しています。

```lang:BulletinRequest()

/**BulletinRequest()
 *  指定した日時内の通知を取得する
 *  NotificationGetNotificationVersionsというガルーンAPIを使用。
 *  @param {notification_items} 配列:通知idが中にある
 */
function BulletinRequest() {
  //↓現在時刻を取得してISO-8601 拡張形式に変換する
  var startdate = new Date(),//日本の現在日時をミリ秒まで取得
      enddate = new Date();
  startdate.setMinutes(startdate.getMinutes()-30);//開始日時を30分前にする
//  Logger.log('開始日時(日本時間):'+startdate);
  
  //ISO 日付形式に変換する↓
  startdate = startdate.toISOString();//開始日時//世界標準時
  enddate = enddate.toISOString();//終了日時//世界標準時
  
//  Logger.log('開始日時(ISO形式):'+startdate);
//  Logger.log('終了日時(ISO形式):'+enddate);
  
  //APIで取得する値を決める
  //API名(更新通知を取得する機能名)
  var API = 'NotificationGetNotificationVersions';
  //日時指定,機能名(掲示板),バージョン指定(0=全てのバージョン)などを行う
  var parameters = '<parameters start=\"' + startdate + '\" end=\"' + enddate + '\" module_id=\"grn.bulletin\">'+
      '<notification_item version=\"0\">'+
        '<notification_id module_id=\"grn.bulletin\" item=\"-1\"/>'+//item=-1にすることで必ず１番目に関係のない値が来るようにしている
      '</notification_item>'+
    '</parameters>';
  
  //createXMLメソッドからXMLのpayload部分を取得
  var payload = createXML(API,parameters);
  
  //送信方法を定義
  var options = {
    "method": "post",
    "contextType": "application/soap+xml",
    "muteHttpExceptions": true,
    "payload": payload
  };

  //使用する機能によってnotification部分が違う
  var url = "https://サブドメイン名.cybozu.com/g/cbpapi/notification/api.csp?";
  
  try {
    //レスポンスを取得
    var response = UrlFetchApp.fetch(url, options);
    //レスポンスをパースする
    var doc = XmlService.parse(response.getContentText());
    
    //ノード解析し,通知の配列を取得
    var notification_items = doc
      .getRootElement()
      .getChild('Body', XmlService.getNamespace('http://www.w3.org/2003/05/soap-envelope'))
      .getChild('NotificationGetNotificationVersionsResponse', XmlService.getNamespace('http://wsdl.cybozu.co.jp/notification/2008'))
      .getChild('returns', XmlService.getNamespace(''))
      .getChildren('notification_item');
    
    //掲示板の詳細を取得するメソッドに配列を送る。
    BulletinGetTopicByIds(notification_items);
    
    
  } catch (e) {
    Logger.log(e);
  }
}

```

####BulletinRequestメソッドの解説
Garoonに時間を送信する際はISO日付形式というものにする必要があるようなので、まず最初に「.toISOString()」を使い変換しています。これで変換すると日本時間だったものはISO形式で世界標準時に変わります。
次に変数APIに機能名を、変数parametersに欲しい値の範囲を指定しています。

```lang:例）メッセージの更新通知の場合

  //API名(更新通知を取得する機能名)
  var API = 'NotificationGetNotificationVersions';
  //日時指定,機能名(該当する名前を記述),バージョン指定(0=全てのバージョン)などを行う
  var parameters = '<parameters start=\"' + startdate + '\" end=\"' + enddate + '\" module_id=\"grn.message\">'+
      '<notification_item version=\"0\">'+
        '<notification_id module_id=\"grn.message\" item=\"34\"/>'+
      '</notification_item>'+
    '</parameters>';

```
例の場合はparameterのversion指定ではタイムスタンプを入力することにより取得する値が変化するようです。しかし、残念ながら自分は理解できなかったので全件取得する形にしています。

BulletinRequestメソッドのparameterしていではitem = -1　としていますがこれをやると必ず最初に関係のない値が来るのでそれを除外しています。本来ならResponseの中で関係のない値のみoperation="remove"となるのでif文で除外すべきだと思うのですが問題なく動いたため今回はif文では除外しておりません。

#####XML送信(UrlFetchApp)
変数payloadにcreateXML()の返り値を代入し、変数optionsに連想配列で代入しています。
その後に変数urlに自分のサブドメイン名が入ったURLを代入,UrlFetchApp.fetch()で送信しています。これの詳細は末尾の「その他に参考にしたサイト」でGoogleが紹介しています。

#####getChild(),getChildrenについて
UrlFetchApp.fetch()で取得した場合はBodyから順に単数の場合は.Child()か複数の場合は.Children()で順に取得できるはずです。
Responseの例文がみたい方はResponseサンプルを参照してください。

そして、最終的に取得した値を次のメソッド「BulletinGetTopicByIds()」に渡します。






#掲示板IDを使い詳細情報を取得する
このメソッドでは「BulletinRequest()」から送られてきた値から掲示板idを使い、該当する掲示板の詳細情報を取得します。

```lang:BulletinGetTopicById()

/**
 *  掲示板の詳細情報を取得する
 *  @param  {name}           ユーザ名
 *  @param  {category_name}  カテゴリ名
 *  @param  {subject}        タイトル名
 *  @param  {GaroonLink}     掲示板URL
 */
/**BulletinGetTopicByIds()
 *  指定した掲示板IDと一致した掲示板詳細を取得する
 *  BulletinGetTopicByIdsというガルーンAPIを使用。
 */
function BulletinGetTopicByIds(notification_items) {
  //通知idと掲示板idを定義
  var notification_id,
      topic_id;
      
  //通知の配列を利用し、for文でtopic_idごとの掲示板の詳細を取得
  //最初に関係のない値が来るので0ではなく1から
  for (var i = 1; i < notification_items.length; i++) {
    notification_id = notification_items[i].getChild('notification_id');
    topic_id = notification_id.getAttribute('item').getValue();
    
//    Logger.log('topic_id(確認):'+topic_id);
          
    //APIで取得する値を決める
    //API名(掲示板詳細を取得する機能名)
    var API = 'BulletinGetTopicByIds';
    //掲示板id指定や下書きかどうか判定(is_draft)などを行う
    var parameters = '<parameters>'+
        '<topics xmlns=\"\" topic_id=\"'+topic_id+'\" is_draft=\"false\"></topics>'+
      '</parameters>';
    
    //createXMLメソッドからXMLのpayload部分を取得
    var payload = createXML(API,parameters);
    //送信方法を定義
    var options = {
        "method": "post",
        "contextType": "application/soap+xml",
        "muteHttpExceptions": true,
        "payload": payload
    };
    //使用する機能によってbulletin部分が違う
    var url = "https://サブドメイン名.cybozu.com/g/cbpapi/bulletin/api.csp?";
  
    try {
      //レスポンスを取得
      var response = UrlFetchApp.fetch(url, options);
      //レスポンスをパースする
      var doc = XmlService.parse(response.getContentText());
      
      //ノード解析し,掲示板のidを取得
      var bulletin = doc
          .getRootElement()
          .getChild('Body', XmlService.getNamespace('http://www.w3.org/2003/05/soap-envelope'))
          .getChild('BulletinGetTopicByIdsResponse', XmlService.getNamespace('http://wsdl.cybozu.co.jp/bulletin/2008'))
          .getChild('returns', XmlService.getNamespace(''))
          .getChild('topic');
      
      //category_id , タイトル , ユーザ名取得
      var category_id = bulletin.getAttribute("category_id").getValue(),
          subject = bulletin.getAttribute("subject").getValue(),
          name = bulletin.getChild('creator',XmlService.getNamespace('http://schemas.cybozu.co.jp/bulletin/2008'))
          .getAttribute("name").getValue(),
          //categories取得
          categories =CategoriesRequest(),
          category_name;
      
      //catgory_name取得
      for (var j = 0; j < categories.length; j++) {
        //category_idが一致すればcategory_nameを代入
        if(category_id == categories[j][0]){
          category_name = categories[j][1];
        }
      }
      
//      //ログ
//      Logger.log('topic_id(再確認):'+bulletin.getAttribute("id").getValue());
//      Logger.log('カテゴリ:'+category_id);
//      Logger.log('カテゴリ名:'+category_name);
//      Logger.log('サブジェクト:'+subject);
//      Logger.log('名前:'+name);
      
      //取得した値でリンク用URLを作成
      var GaroonLink = 'https://サブドメイン名.cybozu.com/g/bulletin/view.csp?cid='+category_id+'&aid='+topic_id;
//      Logger.log('リンク:'+GaroonLink);
      
    } catch (e) {
      Logger.log(e);
    }
          
       /**
        * Slackにメールの中身を投げる
        */
        sendHttpPostSlack(
          " ```差出人:"+ name +"\nカテゴリ:"+category_name+"\nタイトル:"+ subject +"\n" + GaroonLink + "\n``` ",
          "けいじばんしゅとくん"); //引数username（好きな名前を記述）
    
   }//for文の終わり
}
```
####BulletinGetTopicByIdsメソッドの解説
ここはBulletinRequestメソッドでやったことをAPIの機能に合わせて書き換えた感じです。コメントも入れたので試せばだいたい分かると思います。
それ以外の違いは下記に該当する部分で「CategoriesRequest」メソッドを使いカテゴリ名を取得しているのと、

```
//categories取得
categories =CategoriesRequest(),
category_name;

//catgory_name取得
for (var j = 0; j < categories.length; j++) {
  //category_idが一致すればcategory_nameを代入
  if(category_id == categories[j][0]){
    category_name = categories[j][1];
  }
}
```
メソッドの最後に「sendHttpPostSlack」メソッドを使いSlackに表示する内容を投げるところが大きく違います。

```
/**
 * Slackにメールの中身を投げる
 */
sendHttpPostSlack(
  " ```差出人:"+ name +"\nカテゴリ:"+category_name+"\nタイトル:"+ subject +"\n" + GaroonLink + "\n``` ",
 "けいじばんしゅとくん"); //引数username（好きな名前を記述）
    
```

#掲示板のカテゴリを取得する
カテゴリ名を取得するためだけに作りました。多次元配列で値を返すので配列と間違えないよう注意が必要です。


```lang:CategoriesRequest()

/**CategoriesRequest()
 *  掲示板のカテゴリ詳細を取得する
 *  BulletinGetCategoriesというガルーンAPIを使用。
 *  @param {category_id}    カテゴリID
 *  @param {category_name}  カテゴリ名
 * 
 */
function CategoriesRequest(){
  //APIで取得する値を決める
  //API名(カテゴリを取得する機能名)
  var API = 'BulletinGetCategories';
  //日時指定,機能名(掲示板),バージョン指定(0=全てのバージョン)などを行う
  var parameters = ' ';
  
  //createXMLメソッドからXMLのpayload部分を取得
  var payload = createXML(API,parameters);
  
  //送信方法を定義
  var options = {
    "method": "post",
    "contextType": "application/soap+xml",
    "muteHttpExceptions": true,
    "payload": payload
  };

  //使用する機能によってnotification部分が違う
  var url = "https://サブドメイン名.cybozu.com/g/cbpapi/bulletin/api.csp?";
  
  try {
    //レスポンスを取得
    var response = UrlFetchApp.fetch(url, options);
    //レスポンスをパースする
    var doc = XmlService.parse(response.getContentText());
    
    //ノード解析し,rootを取得
    var root = doc
      .getRootElement()
      .getChild('Body', XmlService.getNamespace('http://www.w3.org/2003/05/soap-envelope'))
      .getChild('BulletinGetCategoriesResponse', XmlService.getNamespace('http://wsdl.cybozu.co.jp/bulletin/2008'))
      .getChild('returns', XmlService.getNamespace(''))
      .getChild('categories')
      .getChild('root');
    
//    Logger.log(root+"ルート");
    
    //category_paramsを多次元配列にして配列を追加
    //カテゴリのルートの頭を取得（他はルートの中に存在）
    var root_category = [root.getAttribute('id').getValue(),root.getChild('name').getText()],
        category_params =[root_category];
    
//    Logger.log(category_params[0]);
    
    //rootの下からcategory(配列)を取得
    var category = root
     .getChild('categories')
     .getChildren('category');
    
//    Logger.log(category);
    
    //category_paramsに入れる変数
    var category_id,
        category_name;
    
    //category_id,category_nameを配列にしてcategory_paramsに入れる
    for (var i = 0; i < category.length; i++) {
      category_id = category[i].getAttribute('id').getValue();
      category_name = category[i].getChild('name').getText();
      
//      Logger.log('ID:'+category_id);
//      Logger.log('NAME:'+category_name);
      
      category_params[i+1] = [category_id,category_name];
      
    }//for文終わり
    
    
//    //ログ確認用for文
//    for (var i = 0; i < category_params.length; i++) {
//        Logger.log('CATEGORY:'+category_params[i]);
//    }

    
    
  } catch (e) {
    Logger.log(e);
  }
  //多次元配列をリターン
  return category_params;
}
```
####CategoriesRequestメソッドの解説
Slackに値を投げた時に内容が分かりづらいなと思って後から追加しました。カテゴリ一覧を取得する際はリクエストが不要なのでparametersは指定していません。後述の「Responseサンプル」を見ればわかりますが「BulletinGetCategories」はResponseがめんどくさいです。ルートカテゴリの中にカテゴリが入っているので一度先頭のルートのIDとNameを取得してからその下のカテゴリをfor文で取得しています。多次元配列にして追加しているので取り出す際の処理には注意が必要です。

#Slackになげる

```lang:sendHttpPostSlack()

/**
 * 検索でヒットしたメールから取り出したデータ（message）とusernameを引数にし
 * JSON型にキャストしincoming webhocksのurlにデータを投げる関数
 *
 * @param {String} message 掲示板の内容
 * @param {String} username Slackで表示されるBotの名前
 */
function sendHttpPostSlack(message, username) {
  //incoming Webhocksで取得したurlを記述
　var postUrl_slack = "incoming Webhocksで取得したurl";
  
  //投稿したいslackのチャンネル名
  var postChannel = "#チャンネル名";

  var jsonData = {
    "channel": postChannel,
    "username": username,
    "text": message
  };
  
  var payload = JSON.stringify(jsonData);
  
  var options = {
    "method": "post",
    "contentType": "application/json",
    "payload": payload
  };
  
  UrlFetchApp.fetch(postUrl_slack, options); //incoming webhocksにリクエスト
}
```

####CategoriesRequestメソッドの解説
作成したmessageとチャンネル名,usernameをjsonデータに変換しpayloadに、optionsに送信方法とpayloadを設定し、Slackで設定したときに得られたurlとともにUrlFetchApp.fetch()で送信しました。
これを使うためにはSlackにincoming webhocksを入れて設定する必要があるそうです。
この辺りは知り合いが作成した部分なので自分でも100%は理解していませんがサンプルとして一緒にのせました。

#まとめ
全部をまとめたもの

```lang:最新掲示板取得プログラム

//関数BulletinRequestを実行してください。
//現在はトリガーを30分ごとに設定してます。
//XMLのpayloadの username,Password,Created,Expiresの部分は現在は変数にしていません。
//Logger.log()は確認用のコマンドです。
//
//

/**BulletinRequest()
 *  指定した日時内の通知を取得する
 *  NotificationGetNotificationVersionsというガルーンAPIを使用。
 *  @param {notification_items} 配列:通知idが中にある
 */
function BulletinRequest() {
  //↓現在時刻を取得してISO-8601 拡張形式に変換する
  var startdate = new Date(),//日本の現在日時をミリ秒まで取得
      enddate = new Date();
  startdate.setMinutes(startdate.getMinutes()-30);//開始日時を30分前にする
//  Logger.log('開始日時(日本時間):'+startdate);
  
  //ISO 日付形式に変換する↓
  startdate = startdate.toISOString();//開始日時//世界標準時
  enddate = enddate.toISOString();//終了日時//世界標準時
  
//  Logger.log('開始日時(ISO形式):'+startdate);
//  Logger.log('終了日時(ISO形式):'+enddate);
  
  //APIで取得する値を決める
  //API名(更新通知を取得する機能名)
  var API = 'NotificationGetNotificationVersions';
  //日時指定,機能名(掲示板),バージョン指定(0=全てのバージョン)などを行う
  var parameters = '<parameters start=\"' + startdate + '\" end=\"' + enddate + '\" module_id=\"grn.bulletin\">'+
      '<notification_item version=\"0\">'+
        '<notification_id module_id=\"grn.bulletin\" item=\"-1\"/>'+//item=-1にすることで必ず１番目に関係のない値が来るようにしている
      '</notification_item>'+
    '</parameters>';
  
  //createXMLメソッドからXMLのpayload部分を取得
  var payload = createXML(API,parameters);
  
  //送信方法を定義
  var options = {
    "method": "post",
    "contextType": "application/soap+xml",
    "muteHttpExceptions": true,
    "payload": payload
  };

  //使用する機能によってnotification部分が違う
  var url = "https://サブドメイン名.cybozu.com/g/cbpapi/notification/api.csp?";
  
  try {
    //レスポンスを取得
    var response = UrlFetchApp.fetch(url, options);
    //レスポンスをパースする
    var doc = XmlService.parse(response.getContentText());
    
    //ノード解析し,通知の配列を取得
    var notification_items = doc
      .getRootElement()
      .getChild('Body', XmlService.getNamespace('http://www.w3.org/2003/05/soap-envelope'))
      .getChild('NotificationGetNotificationVersionsResponse', XmlService.getNamespace('http://wsdl.cybozu.co.jp/notification/2008'))
      .getChild('returns', XmlService.getNamespace(''))
      .getChildren('notification_item');
    
    //掲示板の詳細を取得するメソッドに配列を送る。
    BulletinGetTopicByIds(notification_items);
    
    
  } catch (e) {
    Logger.log(e);
  }
}


/**
 *  掲示板の詳細情報を取得する
 *  @param  {name}           ユーザ名
 *  @param  {category_name}  カテゴリ名
 *  @param  {subject}        タイトル名
 *  @param  {GaroonLink}     掲示板URL
 */
/**BulletinGetTopicByIds()
 *  指定した掲示板IDと一致した掲示板詳細を取得する
 *  BulletinGetTopicByIdsというガルーンAPIを使用。
 */
function BulletinGetTopicByIds(notification_items) {
  //通知idと掲示板idを定義
  var notification_id,
      topic_id;
      
  //通知の配列を利用し、for文でtopic_idごとの掲示板の詳細を取得
  //最初に関係のない値が来るので0ではなく1から
  for (var i = 1; i < notification_items.length; i++) {
    notification_id = notification_items[i].getChild('notification_id');
    topic_id = notification_id.getAttribute('item').getValue();
    
//    Logger.log('topic_id(確認):'+topic_id);
          
    //APIで取得する値を決める
    //API名(掲示板詳細を取得する機能名)
    var API = 'BulletinGetTopicByIds';
    //掲示板id指定や下書きかどうか判定(is_draft)などを行う
    var parameters = '<parameters>'+
        '<topics xmlns=\"\" topic_id=\"'+topic_id+'\" is_draft=\"false\"></topics>'+
      '</parameters>';
    
    //createXMLメソッドからXMLのpayload部分を取得
    var payload = createXML(API,parameters);
    //送信方法を定義
    var options = {
        "method": "post",
        "contextType": "application/soap+xml",
        "muteHttpExceptions": true,
        "payload": payload
    };
    //使用する機能によってbulletin部分が違う
    var url = "https://サブドメイン名.cybozu.com/g/cbpapi/bulletin/api.csp?";
  
    try {
      //レスポンスを取得
      var response = UrlFetchApp.fetch(url, options);
      //レスポンスをパースする
      var doc = XmlService.parse(response.getContentText());
      
      //ノード解析し,掲示板のidを取得
      var bulletin = doc
          .getRootElement()
          .getChild('Body', XmlService.getNamespace('http://www.w3.org/2003/05/soap-envelope'))
          .getChild('BulletinGetTopicByIdsResponse', XmlService.getNamespace('http://wsdl.cybozu.co.jp/bulletin/2008'))
          .getChild('returns', XmlService.getNamespace(''))
          .getChild('topic');
      
      //category_id , タイトル , ユーザ名取得
      var category_id = bulletin.getAttribute("category_id").getValue(),
          subject = bulletin.getAttribute("subject").getValue(),
          name = bulletin.getChild('creator',XmlService.getNamespace('http://schemas.cybozu.co.jp/bulletin/2008'))
          .getAttribute("name").getValue(),
          //categories取得
          categories =CategoriesRequest(),
          category_name;
      
      //catgory_name取得
      for (var j = 0; j < categories.length; j++) {
        //category_idが一致すればcategory_nameを代入
        if(category_id == categories[j][0]){
          category_name = categories[j][1];
        }
      }
      
//      //ログ
//      Logger.log('topic_id(再確認):'+bulletin.getAttribute("id").getValue());
//      Logger.log('カテゴリ:'+category_id);
//      Logger.log('カテゴリ名:'+category_name);
//      Logger.log('サブジェクト:'+subject);
//      Logger.log('名前:'+name);
      
      //取得した値でリンク用URLを作成
      var GaroonLink = 'https://サブドメイン名.cybozu.com/g/bulletin/view.csp?cid='+category_id+'&aid='+topic_id;
//      Logger.log('リンク:'+GaroonLink);
      
    } catch (e) {
      Logger.log(e);
    }
          
       /**
        * Slackにメールの中身を投げる
        */
        sendHttpPostSlack(
          " ```差出人:"+ name +"\nカテゴリ:"+category_name+"\nタイトル:"+ subject +"\n" + GaroonLink + "\n``` ",
          "けいじばんしゅとくん"); //引数username（好きな名前を記述）
    
   }//for文の終わり
}




/**CategoriesRequest()
 *  掲示板のカテゴリ詳細を取得する
 *  BulletinGetCategoriesというガルーンAPIを使用。
 *  @param {category_id}    カテゴリID
 *  @param {category_name}  カテゴリ名
 * 
 */
function CategoriesRequest(){
  //APIで取得する値を決める
  //API名(カテゴリを取得する機能名)
  var API = 'BulletinGetCategories';
  //BulletinGetCategoriesはリクエストのparameterは不要
  var parameters = ' ';
  
  //createXMLメソッドからXMLのpayload部分を取得
  var payload = createXML(API,parameters);
  
  //送信方法を定義
  var options = {
    "method": "post",
    "contextType": "application/soap+xml",
    "muteHttpExceptions": true,
    "payload": payload
  };

  //使用する機能によってbulletin部分が違う
  var url = "https://サブドメイン名.cybozu.com/g/cbpapi/bulletin/api.csp?";
  
  try {
    //レスポンスを取得
    var response = UrlFetchApp.fetch(url, options);
    //レスポンスをパースする
    var doc = XmlService.parse(response.getContentText());
    
    //ノード解析し,rootを取得
    var root = doc
      .getRootElement()
      .getChild('Body', XmlService.getNamespace('http://www.w3.org/2003/05/soap-envelope'))
      .getChild('BulletinGetCategoriesResponse', XmlService.getNamespace('http://wsdl.cybozu.co.jp/bulletin/2008'))
      .getChild('returns', XmlService.getNamespace(''))
      .getChild('categories')
      .getChild('root');
    
//    Logger.log(root+"ルート");
    
    //category_paramsを多次元配列にして配列を追加
    //カテゴリのルートの頭を取得（他はルートの中に存在）
    var root_category = [root.getAttribute('id').getValue(),root.getChild('name').getText()],
        category_params =[root_category];
    
//    Logger.log(category_params[0]);
    
    //rootの下からcategory(配列)を取得
    var category = root
     .getChild('categories')
     .getChildren('category');
    
//    Logger.log(category);
    
    //category_paramsに入れる変数
    var category_id,
        category_name;
    
    //category_id,category_nameを配列にしてcategory_paramsに入れる
    for (var i = 0; i < category.length; i++) {
      category_id = category[i].getAttribute('id').getValue();
      category_name = category[i].getChild('name').getText();
      
//      Logger.log('ID:'+category_id);
//      Logger.log('NAME:'+category_name);
      
      category_params[i+1] = [category_id,category_name];
      
    }//for文終わり
    
    
//    //ログ確認用for文
//    for (var i = 0; i < category_params.length; i++) {
//        Logger.log('CATEGORY:'+category_params[i]);
//    }

    
    
  } catch (e) {
    Logger.log(e);
  }
  //多次元配列をリターン
  return category_params;
}







/**
 *Cybouzに送るXMLのpayload部分を作成
 *@param {payload}  payload(データ本体)
 *
 **/
function createXML(API,parameters){
    //xmlnsの前にはスペースor改行が必要なので注意
    var payload ='<?xml version=\"1.0\" encoding=\"UTF-8\"?>'+
'<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://www.w3.org/2003/05/soap-envelope\"'+
 ' xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"'+
 ' xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"'+
 ' xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"'+
 ' xmlns:base_services=\"http://wsdl.cybozu.co.jp/base/2008\">'+
 '<SOAP-ENV:Header>'+
  '<Action SOAP-ENV:mustUnderstand=\"1\"'+
  ' xmlns=\"http://schemas.xmlsoap.org/ws/2003/03/addressing\">'+
   API+
  '</Action>'+
  '<Security xmlns:wsu=\"http://schemas.xmlsoap.org/ws/2002/07/utility\"'+
  'SOAP-ENV:mustUnderstand=\"1\"'+
  ' xmlns=\"http://schemas.xmlsoap.org/ws/2002/12/secext\">'+
   '<UsernameToken wsu:Id=\"id\">'+
    '<Username>UserName</Username>'+
    '<Password>Password</Password>'+
   '</UsernameToken>'+
  '</Security>'+
  '<Timestamp SOAP-ENV:mustUnderstand=\"1\" Id=\"id\"'+
  ' xmlns=\"http://schemas.xmlsoap.org/ws/2002/07/utility\">'+
   '<Created>2010-08-12T14:45:00Z</Created>'+
   '<Expires>2037-08-12T14:45:00Z</Expires>'+
  '</Timestamp>'+
  '<Locale>jp</Locale>'+
 '</SOAP-ENV:Header>'+
 '<SOAP-ENV:Body>'+
  '<'+API+'>'+
    parameters+
  '</'+API+'>'+
 '</SOAP-ENV:Body>'+
'</SOAP-ENV:Envelope>'
  
  return payload;
}


/**
 * 検索でヒットしたメールから取り出したデータ（message）とusernameを引数にし
 * JSON型にキャストしincoming webhocksのurlにデータを投げる関数
 *
 * @param {String} message 掲示板の内容
 * @param {String} username Slackで表示されるBotの名前
 */
function sendHttpPostSlack(message, username) {
  //incoming Webhocksで取得したurlを記述
　var postUrl_slack = "incoming Webhocksで取得したurl";
  
  //投稿したいslackのチャンネル名
  var postChannel = "#チャンネル名";

  var jsonData = {
    "channel": postChannel,
    "username": username,
    "text": message
  };
  
  var payload = JSON.stringify(jsonData);
  
  var options = {
    "method": "post",
    "contentType": "application/json",
    "payload": payload
  };
  
  UrlFetchApp.fetch(postUrl_slack, options); //incoming webhocksにリクエスト
}

```
プログラムは以上です。

#作ってみて
今回始めた当初は主な知識がJavaだけの状態で四苦八苦しながら作成していました。XMLやガルーンAPIについて調べながらやりましたが「ガルーンAPIの概要」や「SOAP APIの共通仕様」のサイトを見ても何が何だかわからず一週間ほどXMLの送信方法を中心に色々と調べていました。XMLの送信方法さえ分かれば送信した値を取得してSlackにまた送るだけでしたので比較的容易にプログラムを作成することが出来た感じですね。
やってみて思ったのは自分の中で一番の鬼門だったXMLの送信と取得さえ出来れば後はプログラムをかじった事のある人なら誰でも簡単にガルーンAPIを使えるはずです。ガルーンAPIを紹介しているサイトは少ないのでこのサンプルを見てくださった方が新たに使い始めてくだされば段々と広がっていくんじゃないかなと思いました。






蛇足ですが…
これより下はカテゴリ一覧のRequestサンプルとResponseサンプルとその他の参考にさせていただいたサイトについてです。興味があったら見てください。

#Requestサンプル
※下のURLのbulletinの部分は自分が使いたいAPIの機能名
https://サブドメイン名.cybozu.com/g/cbpapi/bulletin/api.csp?
POST
Content-Type は application/XML

```lang:例）カテゴリ一覧request

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:base_services="http://wsdl.cybozu.co.jp/base/2008">
  <SOAP-ENV:Header>
    <Action SOAP-ENV:mustUnderstand="1"
     xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing">
      BulletinGetCategories
    </Action>
    <Security xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
     SOAP-ENV:mustUnderstand="1"
     xmlns="http://schemas.xmlsoap.org/ws/2002/12/secext">
      <UsernameToken wsu:Id="id">
        <Username>UserName</Username>
        <Password>Password</Password>
      </UsernameToken>
    </Security>
    <Timestamp SOAP-ENV:mustUnderstand="1" Id="id"
     xmlns="http://schemas.xmlsoap.org/ws/2002/07/utility">
      <Created>2010-08-12T14:45:00Z</Created>
      <Expires>2030-08-12T14:45:00Z</Expires>
    </Timestamp>
    <Locale>jp</Locale>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <BulletinGetCategories>
    </BulletinGetCategories>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
```
#Responseサンプル

```lang:例）カテゴリ一覧Response
<?xml version="1.0" encoding="utf-8" ?>
<soap:Envelope>
 <soap:Header>
  <vendor>Cybozu</vendor>
  <product>Garoon</product>
  <product_type>2</product_type>
  <version>4.2.0</version>
  <apiversion>1.6.0</apiversion>
 </soap:Header>
 <soap:Body>
  <bulletin:BulletinGetCategoriesResponse>
   <returns>
    <categories>
     <root id="1" code="ROOT_CATEGORY">
      <name>ABCBBS</name>
      <description />
      <creator_id />
      <creator_login_name />
      <creator_display_name>Administrator</creator_display_name>
      <create_time>2015-02-13T16:31:26+0900</create_time>
      <modifier_id>2</modifier_id>
      <modifier_login_name>UserName</modifier_login_name>
      <modifier_display_name>UserName</modifier_display_name>
      <modify_time>2015-06-16T09:48:02+0900</modify_time>
      <categories parent_id="1" parent_code="ROOT_CATEGORY">
       <category id="2" code="557f706728fa62.97945938" list_index="2147483647">
        <name>勤怠関連</name>
        <description />
        <creator_id>2</creator_id>
        <creator_login_name>UserName</creator_login_name>
        <creator_display_name>UserName</creator_display_name>
        <create_time>2015-06-16T09:40:26+0900</create_time>
        <modifier_id>2</modifier_id>
        <modifier_login_name>UserName</modifier_login_name>
        <modifier_display_name>UserName</modifier_display_name>
        <modify_time>2015-06-16T09:40:26+0900</modify_time>
       </category>
       <category id="3" code="557f707d39b4a4.08727840" list_index="2147483647">
        <name>News・情報連携等</name>
        <description />
        <creator_id>2</creator_id>
        <creator_login_name>UserName</creator_login_name>
        <creator_display_name>UserName</creator_display_name>
        <create_time>2015-06-16T09:41:24+0900</create_time>
        <modifier_id>2</modifier_id>
        <modifier_login_name>UserName</modifier_login_name>
        <modifier_display_name>UserName</modifier_display_name>
        <modify_time>2015-06-16T09:42:21+0900</modify_time>
       </category>
      </categories>
     </root>
    </categories>
   </returns>
  </bulletin:BulletinGetCategoriesResponse>
 </soap:Body>
</soap:Envelope>

```
上記の場合で「category」を取得する際に.getChildren()を使うと配列で取得できます。勤怠関連categoryの中の「id」や「name」を取得する際はそれぞれこんな感じです。

```
      category_id = category[0].getAttribute('id').getValue();
      category_name = category[0].getChild('name').getText();
```

#その他に参考にしたサイト
 - Google Developers UrlFetchApp
  - https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object)
 - Google Developers XmlService
  - https://developers.google.com/apps-script/reference/xml-service/xml-service
 - 参考にさせていただいた方のGitHub
  - https://gist.github.com/hagino3000/1145727