Hubspot APIでコール記録を取得してみよう

Hubspotのコール記録をどうしても抜き出したいんです！と社内で言われたので、
遠い記憶を手繰り寄せながらコードを書いてみました。

今回使用したAPIはHubspot API V3のcallsです。
https://developers.hubspot.com/docs/api/crm/calls

2年ほど前はまだEngagement(コール、ミーティング、コメントなど)はV1しかなかったのですが、
いつの間にかV3がでていました。

そしてV3では「Search」が用意されているため、
簡単にデータを引っ張ってくることができました。

まずは完成品がこちら。

```javascript:engagement.js
function getEngagement() {
    const spreadsheet = SpreadsheetApp.openById("スプレッドシートのシートIDをいれる");
    const sheetHubspot = spreadsheet.getSheetByName('タブ名');

    //シート全クリア
    sheetHubspot.getRange(2,1,9999,20).clear();

    //HubspotのAPI接続設定
    const API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

    //更新する項目（列）数
    const lastColumn = 5;

    //フィルターに使用する日時（UNIXミリ秒）
    const date = 1643641200000; //2022-02-01

    //取得準備
    let ListCalls = [];//リストの配列
    let hasMore = true; //追加データが有る場合はtrue
    let after = 0; //一度で取得できなかった場合、続きから取得

    while(hasMore){
        //1秒経過してから処理開始
        Utilities.sleep(1000);

        //Hubspotからコンタクトを取得
        let url = "https://api.hubapi.com/crm/v3/objects/calls/search" + "?hapikey=" + API_KEY;
        let headers = {"content-type" : "application/json"}
        let payload = {
            "filterGroups": [
                {
                  "filters": [
                    {
                      "propertyName": "hs_timestamp",
                      "operator": "GTE",
                      "value": 1643641200000 //2022-02-01
                    }
                  ]
                }
            ],
            "sorts": ["hs_timestamp"],
            "properties": ["hs_call_body,hs_call_disposition,hs_timestamp,hubspot_owner_id"],
            "limit": 100,
            "after": after
        }
        payload = JSON.stringify(payload);
        let options = {
            "method": 'POST',
            "headers": headers,
            "payload": payload,
            "muteHttpExceptions": true
        };
        let response = UrlFetchApp.fetch(url, options);

        //スプレッドシートにマッピング
        if(response){
            //JSON→配列化
            let responseArr = JSON.parse(response.getContentText());
            
            //連想配列からkeyが"results"の中身を取得
            let arrContacts = responseArr.results;
            
            //連想配列からkeyが"properties"の中身を配列形式で取得
            let arrProperties = arrContacts.map(obj => obj.properties);

            //リストの配列
            for (const elem of arrProperties) {
                //アイテム単位の配列
                let ItemCalls = [];
                
                //A_コール担当者
                if(elem["hubspot_owner_id"]){
                    ItemCalls.push(elem["hubspot_owner_id"]);
                }
                else {
                    ItemCalls.push("");
                }

                //B_コール担当者の名前置換用の列
                ItemCalls.push("");
                
                //C_コール内容
                if(elem["hs_call_body"]){
                    ItemCalls.push(elem["hs_call_body"]);
                }
                else {
                    ItemCalls.push("");
                }

                //D_コール結果
                if(elem["hs_call_disposition"]){
                    ItemCalls.push(elem["hs_call_disposition"]);
                }
                else {
                    ItemCalls.push("");
                }

                //E_コール日
                if(elem["hs_timestamp"]){
                    ItemCalls.push(elem["hs_timestamp"]);
                }
                else {
                    ItemCalls.push("");
                }
                
                //配列に格納
                ListCalls.push(ItemCalls);

            }

            //hasMoreがない場合は終了
            if ("paging" in responseArr) {
                //afterを書き換え
                after = responseArr.paging.next.after;
            }
            else {
                hasMore = false;
            }
        }
        //responseがない場合は終了
        else {
            hasMore = false;
        }
    }

    //シートへの記入
    sheetHubspot.getRange(2, 1, ListCalls.length, lastColumn).setValues(ListCalls);
}
```

それでは解説をしていきたいと思います。

## 事前準備
### シートの定義
```javascript:engagement.js
    const spreadsheet = SpreadsheetApp.openById("スプレッドシートのシートIDをいれる");
    const sheetHubspot = spreadsheet.getSheetByName('タブ名');
```
https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/
のxxxxx...に当てはまる部分が「スプレッドシートのシートIDをいれる」に該当します。

そしてタブに任意の名前をつけて、そのタブを更新用のシートにします。

### シートの内容クリア
シートクリアは必要に応じて。
```javascript:engagement.js
    //シート全クリア
    sheetHubspot.getRange(2,1,9999,20).clear();
```

### HubspotのAPI
Hubspotの設定 > 連携 > APIキーに記載があります。
```javascript:engagement.js
    //HubspotのAPI接続設定
    const API_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

### 更新する列
今回の取得したいプロパティの数を記載しましょう。
```javascript:engagement.js
    //更新する項目（列）数
    const lastColumn = 5;
```

### フィルターに使用する日時
```javascript:engagement.js
    //フィルターに使用する日時（UNIXミリ秒）
    const date = 1643641200000; //2022-02-01
```

## Hubspotからの取得
### 準備
リストにデータを格納し、まとめてスプレッドシートに貼り付ける形式となります。

`hasMore`は、後述の`while`内で利用します。
`"paging"`という言葉が配列の中に格納されている場合は`after`(ページ番号)の数字を取得し、存在しない場合はループを終了させる、という役割を果たしています。
```javascript:engagement.js
    //取得準備
    let ListCalls = [];//リストの配列
    let hasMore = true; //追加データが有る場合はtrue
    let after = 0; //一度で取得できなかった場合、続きから取得
```

```javascript:engagement.js
            //hasMoreがない場合は終了
            if ("paging" in responseArr) {
                //afterを書き換え
                after = responseArr.paging.next.after;
            }
            else {
                hasMore = false;
            }
```

## 取得
`Utilities.sleep(1000);`を入れているのは、ループが高速すぎると制限に引っかかるからです。


```javascript:engagement.js
    while(hasMore){
        //1秒経過してから処理開始
        Utilities.sleep(1000);
```
フィルターのかけ方ですが、
```javascript:engagement.js
            "filterGroups": [
                {
                  "filters": [
                    {
                      "propertyName": "hs_timestamp",
                      "operator": "GTE",
                      "value": 1643641200000 //2022-02-01
                    }
                  ]
                }
            ],

```
公式ドキュメントには以下のように記述があります。

>複数のフィルタ条件を含めるには、フィルタを filterGroups でグループ化します。
>
>- 複数のフィルタが filterGroup 内に存在するときは、AND演算子で結合されます。
>- 複数の filterGroup が含まれる場合、それらはOR演算子を用いて結合されます。
>
>filterGroups は最大3つまで含めることができ、各グループには最大3つのフィルタがあります。たとえば、以下のリクエストは、ファーストネームがAliceで、ラストネームがSmithでない、またはプロパティemailの値を持っているすべてのコンタクトを検索します。

```javascript
    "filterGroups":[
      {
        "filters":[
          {
            "propertyName": "firstname",
            "operator": "EQ",
            "value": "Alice"
          }
        ]
      }
    ]
```

すなわち、AかつBにしたいときはfilterGroupsの中にfiltersを並列で配置すればよく、
AまたはBにしたいときはfilterGroupesを並列で配置する、ということですね。

|OPERATOR|DESCRIPTION|
|:-----------|:-----------|
|LT|未満（Less than）|
|LTE|以下（Less than or equal to）|
|GT|より大きい（Greater than）|
|GTE|以上（Greater than or equal to）|
|EQ|等しい（Equal to）|
|NEQ|等しくない（Not equal to）|
|BETWEEN|の間|
|IN|特定のリストに含まれる|
|NOT_IN|特定のリストに含まれない|
|HAS_PROPERTY|既知である|
|NOT_HAS_PROPERTY|不明である|

`Between`のみ`highValue`が必要になります。

```javascript
"filterGroups":[{
    "filters":[
        {
    "propertyName":"hs_lastmodifieddate",
     "operator":"BETWEEN",
     "highValue": "1642672800000",
      "value":"1579514400000"
        }
]
}
]
```

公式ドキュメントはこちら↓

https://developers.hubspot.com/docs/api/crm/search

フィルター内に登場する日時の指定は、UNIXミリ秒で指定しています。

```javascript
"value": 1643641200000 //2022-02-01
```
UNIXミリ秒は以下のサイトから調べることが可能です。

https://tools.m-bsys.com/development_tooles/unix_time.php

ざっとポイントを解説してみましたが、わからないことがあればコメントなどで気軽にご質問ください。
必要な情報など追記して、よりわかりやすくしていきたいと思います。

ちなみに前回の記事はこちらです。合わせて参考にしてみてください。

https://qiita.com/jeq/items/07a429d5fa7c0b692c78