Instagram から Graph API で画像を取得する

メチャクチャにめんどくさいです…… :frowning2:


## 他の手段

2019/10 に Instagram Basic Display API というのが出たようですが、トークンの有効期限が1時間だそうなので、サイトに画像を埋め込むような用途には使えません。

 * https://developers.facebook.com/docs/instagram-basic-display-api/
 * https://webty.jp/staffblog/production/post-2234/

## 参考にした記事

 * https://arrown-blog.com/instagram-graph-api/

こちらの記事が詳しく、非常に助かりました。 :pray: 

以下は、上の記事を読む前提での補足です。


## 構成

次のような構成にします。

<img alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2596/203f48d3-884b-0b66-c8cf-5e53badb1a7b.png">

## ダミーの Instagram アカウント、Facebook ページを使う

Instagram へのアクセス権限を得るには、ダミーの Facebook ページと Instagram アカウントを作るのが無難そうでした。その方が、運用・引き継ぎもしやすいのではないかと思います。

最初は顧客の Facebook ページに編集者で入れてもらって試しましたが、権限の制約なのかバグなのか、 `instagram_business_account` の ID 取得がうまくいきませんでした。

 * https://qiita.com/ryouhei156/items/215ff94fc430f75f73e3

> ・`instagram_business_account` が出力されないバグについて

専用の Instagram, FB ページを作ると、失敗しても何度でも試せるので安心です。

ダミーの Instagram ビジネス ID とアクセストークンを使って、全く関係ない第三者の Instagram 画像を取得できます。

## 開発モードで FB アプリを使う

Facebook アプリは開発モードのままで使います。

公開しようとすると、スクリーンキャプチャ動画を含むレビュー申請が発生してしまいます。。。

当初は、顧客のアカウントでアプリに OAuth ログインしてもらう選択肢もあるのでは、、と思っていましたが、レビューの手間に見合う気がしないので諦めました。

https://developers.facebook.com/docs/facebook-login/review/how-to-submit?locale=ja_JP

> 自分もしくはアプリ上で役割を持つ誰かが開発モードでのみアプリを使用する場合、アプリを申請する必要ありません。アプリダッシュボードの[役割]タブにリストされているアカウント(管理者、開発者、テスターなど)であれば、すべてのアクセス許可を使用できます。

とのことなので、開発モードのアプリを本番で使っても、おそらくは大丈夫なのではと思います。

## 運用・引き継ぎ

 * Instagram のアカウント情報は、複数のメンバーに教えておきます
 * FB アプリ、FB ページは、複数の管理者を設定しておきます
 * 担当者が外れる際は、別のメンバーにトークンを生成しなおしてもらいます

## トークン生成を半自動化する

有効期限なしのトークンを生成する PHP のスクリプトを作成しました。トークンを取得する手順の詳細は、[最初にご紹介した記事](https://arrown-blog.com/instagram-graph-api/)を参照してください。

### 第一段階目のトークンを手動で取得

https://developers.facebook.com/tools/explorer/ を使って、

 * `manage_pages`
 * `pages_show_list`
 * `business_management`
 * `instagram_basic`
 * `instagram_manage_comments`
 * `instagram_manage_insights`
 * `public_profile`

が許可されたアクセストークンを作成します。

### アクセストークンとその他の設定を JSON で保存

次に以下のような JSON を `get_token.json` という名前で保存します。

```json
{
  "appID": "1234567890", // FB アプリの ID
  "appSecret": "xxxxxxxxxxxxxx", // FB アプリのシークレットキー
  "pageName": "小関百貨店",  // ダミーの FB ページの名前 (複数ある場合の選択に使います)
  "userAccessToken": "xxxxxxxxxxxxxxx....xxxxxxxxxxxx" // 上で取得したトークン
}
```

### スクリプトを実行

上の JSON と同じ階層に、以下を `get_token.php` で保存。`$ php get_token.php` で実行します。同階層`get_images.json` に無期限のトークンが出力されます。

```php
<?php

$conf = json_decode(file_get_contents('get_token.json'));

$ver = 'v5.0';
$appID = $conf->appID;
$appSecret = $conf->appSecret;
$pageName = $conf->pageName;
$userAccessToken = $conf->userAccessToken;

$url = "https://graph.facebook.com/$ver/oauth/access_token?grant_type=fb_exchange_token&client_id=$appID&client_secret=$appSecret&fb_exchange_token=$userAccessToken";

echo "----------------------------------------------------------------\n";
echo "Bearer トークンを取得します。\n";
echo "URL: $url\n";
echo "----------------------------------------------------------------\n";

$json = file_get_contents($url);
$data = json_decode($json);

$accessToken = $data->access_token;
echo "Bearer トークン: $accessToken\n";

$url = "https://graph.facebook.com/$ver/me?fields=id,name,accounts&access_token=$accessToken";

echo "----------------------------------------------------------------\n";
echo "アカウント情報を取得します。\n";
echo "URL: $url\n";
echo "----------------------------------------------------------------\n";

$json = file_get_contents($url);
$data = json_decode($json);
// var_dump($data);

$userName = $data->name;
$userID = $data->id;

echo "トークンと紐付くユーザ名: $userName\n";
echo "ユーザID: $userID\n";

foreach ($data->accounts->data as $a) {
    echo "  FB Page: " . $a->name . "\n";
    if ($a->name == $pageName) {
        $pageID = $a->id;
        $pageAccessToken = $a->access_token;
    }
}
echo "FB Page ($pageName) のID: $pageID\n";
echo "FB Page のアクセストークン: $pageAccessToken\n";


$url = "https://graph.facebook.com/$ver/me?fields=instagram_business_account{id,name}&access_token=$pageAccessToken";

echo "----------------------------------------------------------------\n";
echo "Instagram の情報を取得します。\n";
echo "URL: $url\n";
echo "----------------------------------------------------------------\n";

$json = file_get_contents($url);
$data = json_decode($json);

$instagramAccountName = $data->instagram_business_account->name;
$instagramBusinessAccount = $data->instagram_business_account->id;
echo "Instagram ビジネスアカウント ($instagramAccountName) のID: $instagramBusinessAccount\n";

$json = "{\n  \"instagramBID\":\"$instagramBusinessAccount\",\n".
      "  \"accessToken\":\"$pageAccessToken\"\n}\n";

$out = fopen('get_images.json', 'w');
fputs($out, $json);
fclose($out);

echo "\n\nget_images.json にトークンを書き出しました。\n\n";
```


### 実際に画像を取得する

`get_images.json` と同じ階層に、以下を保存します。

```php
<?php

$conf = json_decode(file_get_contents('get_images.json'));

$instagramBID = $conf->instagramBID;
$accessToken = $conf->accessToken;

$targetUser = $argv[1];

$query = 'business_discovery.username(' . $targetUser . '){id,followers_count,media_count,ig_id,media{caption,media_url,media_type,like_count,comments_count,timestamp,id}}';

$url = "https://graph.facebook.com/v5.0/$instagramBID";
$url .=  "?fields=" . $query . "&access_token=" . $accessToken;

$data = json_decode(file_get_contents($url));
var_dump($data);
```

次のようにして実行し、指定したアカウントの画像データを取得できるのを確認します。

```console
$ php get_images.php Instagramアカウント名
```