Intercomの会話データぜんぶ抜く


# TL;TR

https://gist.github.com/YoshiteruIwasaki/3edd71eb1d968fcfa4c56ef7a0bcfbd8

からファイルをダウンロードする。

` <YOUR API KEY>`  の箇所を書き換える。

IntercomのPHPのSDKを同じ階層にcomposer経由でダウンロードする。

```
composer require intercom/intercom-php php-http/guzzle6-adapter
```

以下のコマンドで実行する。

```
php index.php
```


# 解説

IntercomのAPIを純粋に片っ端から叩いてデータを集めてきて最後に配列にして1行ずつCSVに書き出しています。

Intercomの画面上から会社やユーザー情報はCSVエクスポートできます。
会話のデータもエクスポートできますが、過去2年分でサマリーのみになるため、細かな会話のやり取りを出力するにはAPIを使う方法しかありません。（個々の会話の画面から plain text でダウンロードするボタンもありますが、件数が多いと大変かと思います。）


## 処理に時間がかかる

IntercomのAPIはレスポンスがそんなに早くなく、4年ほどの会話データおよそ2万行を抽出するのに10時間くらいかかりました。


## RateLimitに引っかかることはなさそう

IntercomのAPIはたまにタイムアウトしてしまいます。

このスクリプトを回してる途中にRateLimitに引っかかるかなと思っていましたが、レスポンスが爆速でもないので引っかからずに最後まで処理できるかと思います。
https://developers.intercom.com/intercom-api-reference/reference#rate-limiting

タイムアウトするなど途中で処理が止まってしまうとまた最初からやり直しになってしまうため、会話データは最後にまとめてではなく1行ずつCSVに書き出すようにしています。



## APIの結果をファイルに保存する

https://gist.github.com/YoshiteruIwasaki/3edd71eb1d968fcfa4c56ef7a0bcfbd8#file-index-php-L69

```php
file_put_contents("admins.dat", serialize($admins));
```

のように配列を全部シリアライズしてファイルに書き出しているのは、途中でタイムアウトして失敗したときはそれ以前のAPIコール部分はファイルをデシリアライズしてそれを代替として使えるようにするためファイルに保存しています。


## 会話データのhtmlタグ除去

パラメータに `'display_as' => 'plaintext'`を指定することでタグが除去されます。

https://gist.github.com/YoshiteruIwasaki/3edd71eb1d968fcfa4c56ef7a0bcfbd8#file-index-php-L124-L126

```php
$result = $client->conversations->getConversations([
    'display_as' => 'plaintext'
]);
```

## IntercomのAPI設計は微妙

### ページングの処理が微妙

微妙というか過渡期というか、ページングの処理がまちまちです。

AWSをはじめ多くのAPIの設計が `nextToken` でページング処理をしていると思いますが、

https://developers.intercom.com/intercom-api-reference/reference#pagination

にあるようにパラメータに `page=` で渡すパターンが中心です。（v2.3時点）

contactsだけはなぜか

```json
    "next": {
      "page": 4,
      "starting_after": "1HaSB+xrOyyMXAkS/c1RteCL7BzOzTvYjmjakgTergIH31eoe2v4/sbLsJWP\nIncfQLD3ouPkZlCwJ86F\n"
    }
```

のようにしてページ番号とともにページ送りをするためのトークンがセットされています。

https://developers.intercom.com/intercom-api-reference/reference#pagination-cursor


contacts取得時のページ送りの処理

https://gist.github.com/YoshiteruIwasaki/3edd71eb1d968fcfa4c56ef7a0bcfbd8#file-index-php-L86-L88

```php

    if (! empty($result->pages->next)) {
        $result = $client->contacts->nextCursor($result->pages);
    }
```

とそれ以外のページ送りの処理


https://gist.github.com/YoshiteruIwasaki/3edd71eb1d968fcfa4c56ef7a0bcfbd8#file-index-php-L109-L111

```php

    if (! empty($result->pages->next)) {
        $result = $client->nextPage($result->pages);
    }
```

が異なっているのは上記の理由からです。

逆にcontactsではnextPage()が動きません。


#### Intercom PHP SDK v4.4.2 の nextCursorPage バグ

2020年11月19日 時点での最新版 v4.4.2 には nextCursorPageの処理にバグがあります。

https://github.com/intercom/intercom-php/pull/323

でプルリクが出ていますが、APIを叩いたレスポンスをモデルに変換する処理が二重で行われているためエラーになります。

https://github.com/intercom/intercom-php/pull/323/files

のように修正する必要があります。


### メインデータのレスポンスが微妙

管理者ユーザーの一覧データのレスポンスは

https://gist.github.com/YoshiteruIwasaki/3edd71eb1d968fcfa4c56ef7a0bcfbd8#file-index-php-L60-L62

```php
    foreach ($result->admins as $admin) {
        $admins[$admin->id] = $admin->name;
    }
```

ですが、会社の一覧データのレスポンスは

https://gist.github.com/YoshiteruIwasaki/3edd71eb1d968fcfa4c56ef7a0bcfbd8#file-index-php-L104-L106

```php
    foreach ($result->data as $company) {
        $companies[$company->id] = $company;
    }
```

だったりして、なぜか `$result->admins` のようなケースと `$result->data` のようなケースがあるので注意が必要です。


# 感想

IntercomのAPI叩いて会話のデータをCSV出力するのなんて世界のどこかに先人がいるだろう、と思っていましたが、Dockerのコンテナ上でCをコンパイルして会話データを取得するっぽいことをしてたりなんやかんやいいのがないので致し方なく自分でスクリプトを組みました。

とりあえず1回きりのデータ全出力、のつもりで作っているのでエラーハンドリングとかは無考慮です。

別にPHPじゃなくてPerlやRuby,Pythonでもいいんですが（JavaとNodeは下準備が多いので却下）
https://developers.intercom.com/building-apps/docs/sdks-plugins
用意されているSDKを見て、まあRubyより書き慣れているPHPで書いておくか、という判断をしました。