クライミングジムのインスタ投稿からセットスケジュールを抜き出してGoogleカレンダーに登録するまでの話-の続き

# これまでの話

前回の記事で、InstagramAPIからクライミングジムの投稿を抜き出すことができました

https://qiita.com/s-tsukamoto-bns/private/59a6a5d1011d2f78663c

# ここからの話

今回はスケジュールをGoogleカレンダーに登録して行きます
ざっくりとした作業の流れは

1. GASからInstagramAPIを呼び出す
2. 投稿内容からセットスケジュールを抜き出す
3. Googleカレンダーにスケジュールを登録する

となります
いろんな説明をすっ飛ばしたい方は、最後にコード全体を載せてありますのでそちらを利用してください

## GASってなーに

![20211221163618.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/f6977c20-cc98-7a4a-8062-b90bc3b683a7.jpeg)


GASとはGoogleAppsScriptの略で、Googleが提供しているお手軽Web開発環境のことです

* Webページの作成
* WebAPIの作成
* 外部APIの利用
* Googleの各種サービス/APIの利用

などがとても簡単にできるので、おすすめです

:::note info
ちなみに…
GAS環境で構築しなければいけない理由はありません
今後Googleカレンダーを使ったりスプレッドシートに情報を保存したり、GoogleVisonAPIを使ったりってことを考えているのでGASに実行環境を作っています
:::

# 実装していく
## GASでプロジェクトを作成する

GASでプログラムを書くときはまずプロジェクトを作成します


1. Googleアカウントを取得
2. [GASのホーム](https://script.google.com/home)へアクセス
3. 新しいプロジェクトを作成します

するとこんな画面になるのでプロジェクトの名前を適当に設定

![スクリーンショット 2024-06-27 15.09.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/ee4d3753-a3fd-9c21-c76c-e2f614efe06b.png)

## GASからInstagramAPIを呼び出す

以下のコードをmyFunctionの下に記述します

```js:getRecentPages
function getRecentPages(targetAccount){
  var instragramID = 'InstagramビジネスID';
  var ACCESS_TOKEN = 'APIアクセストークン';
  var facebook_url = 'https://graph.facebook.com/v20.0/'+instragramID+'?fields=business_discovery.username('+targetAccount+'){followers_count,media_count,media{id,caption}}&access_token='+ACCESS_TOKEN;
  var response = UrlFetchApp.fetch(encodeURI(facebook_url),{muteHttpExceptions:true}); //URLから情報を取得
  var jsonData = JSON.parse(response);//JSONデータをパース
  inData = jsonData['business_discovery']['media']['data'];
  for(let index in inData)
  {
    var caption = inData[index]['caption'];
    console.log(caption);
  }
}
```
* instragramID
* ACCESS_TOKEN
を[前の記事](https://qiita.com/s-tsukamoto-bns/private/59a6a5d1011d2f78663c)で取得した値に設定して
myFunctionからgetRecentPagesを呼び出します

```js:myFunction
function myFunction() {
  getRecentPages('bpumptokyo');
}
```
getRecentPagesの引数には取得したい**インスタグラムアカウントのID**を入力します
インスタグラムのアカウントIDはブラウザでアクセスした時のURL
`https://www.instagram.com/{この部分}/`
または@でアカウント表すときの
`@{この部分}`
を指定します

今回の例で使用しているのは東京/秋葉原にあるBPUMP TOKYOのアカウントです
InstagramAPIの公式はやけにブルーボトルコーヒーを見せたがりますが
こちらのアカウントも人気のジムです◎

### 実行してみる

実行するときはコードの上部にある実行ボタンを押すだけ（！）簡単ですね

![スクリーンショット 2024-06-28 7.31.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/0350dcb3-1b89-61b9-369e-a6b3d1be6ba4.png)

初回の実行時は以下のように権限の承認を求められると思うので承認を行います
![スクリーンショット 2024-06-27 15.12.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/6cf1b030-729a-b2a7-e8ad-a0bcc8b1b1af.png)

承認が完了するとプログラムが実行されます
getRecentPagesでは実行結果をコンソールに出力しているので実行結果が実行ログに以下のように表示されます
![スクリーンショット 2024-06-27 15.12.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/90b53da9-b62c-30db-658c-1d625fa9ec87.png)


## 投稿内容からセットスケジュールを抜き出す

投稿内容は取得できるようになりましたが
ジムのインスタの記事にはいろんな種類があるので
* セットスケジュールのお知らせ
* メンテ日のお知らせ
* 新商品のお知らせ
* 課題の紹介
* イベントのお知らせ
などなど…

今回はセットスケジュールが欲しいので、それっぽいことが書いてある投稿のみ抜き出します

```diff_javascript:InstagramAPIを呼び出すコード
function getRecentPages(targetAccount){
  var instragramID = 'InstagramビジネスID';
  var ACCESS_TOKEN = 'APIアクセストークン';
  var facebook_url = 'https://graph.facebook.com/v20.0/'+instragramID+'?fields=business_discovery.username('+targetAccount+'){followers_count,media_count,media{id,caption}}&access_token='+ACCESS_TOKEN;
  var response = UrlFetchApp.fetch(encodeURI(facebook_url),{muteHttpExceptions:true}); //URLから情報を取得
  var jsonData = JSON.parse(response);//JSONデータをパース
  inData = jsonData['business_discovery']['media']['data'];
  for(let index in inData)
  {
    var caption = inData[index]['caption'];
+   if(caption !== undefined)
+   if(caption.includes('Route') || caption.includes('Schedule') || caption.includes('セット')|| caption.includes('スケジュール'))
+   {
    console.log(caption);
+   }
  }
}
```
本文（caption）にそれっぽいワード（Route,Schedule,セット等）が入っているかどうかで判別しています
いろんなジムでスケジュールをお知らせする時に使う文言が違うので、
対象のジムを増やしたいときはここをメンテナンスする必要があります
Scheduleを含めてしまうと関係ないイベントも結構拾ってしまうんですがそれはご愛嬌としてます
もっと細かく設定したい場合は引数として受け取る店舗ごとに適切なフィルタを設定するのが良いと思います◎

### 実行してみる

この状態で実行するとスケジュール関連の投稿のみがコンソールに表示されます

![スクリーンショット 2024-06-28 7.59.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/f409097c-4206-9f7e-e045-b2fcbdce5f72.png)

無事スケジュール関連の投稿のみ抜き出すことができました◎

## Googleカレンダーにスケジュールを登録する

ここまででインスタグラムの投稿からスケジュールのテキストを抜き出すことができました
GASにはGoogleカレンダーに予定を登録するライブラリがすでにあるので、それを利用します（便利）

登録にはGoogleカレンダーのIDが必要になりますので作成して取得します

### GoogleカレンダーのIDを取得する

* [Googleカレンダー](https://calendar.google.com/calendar)にアクセス
* 設定->マイカレンダーの設定->カレンダーの統合->カレンダーID

取得してメモに保存しておきます

### Googleカレンダーに予定を登録する

以下にカレンダーへ登録する部分のソースコードを載せます
少し長いですがやっていることはざっくりいうと
* 本文から日付を取得する
* 取得した日付分スケジュールを登録する

です

細かい仕様を説明すると

* 一つの投稿に複数日付がある場合には全部取得して全部の日程に予定を登録
* アカウントIDを件名に設定
* 同じ件名で予定が登録されて**いない**場合に取得した日付でカレンダーを登録

という実装になっています

```javascript:カレンダーへ投稿する関数
function extractAndScheduleToCustomCalendar(targetAccount,postText) {
  var dates = [];
  
  // 日付を抽出する正規表現パターン
  var datePattern = /(\d{1,2})[\/月](\d{1,2})(?:\([月火水木金土日]\))?/g;
  
  // 投稿テキストから日付を抽出
  var match;
  while ((match = datePattern.exec(postText)) !== null) {
    var month = parseInt(match[1], 10); // 月（数値に変換）
    var day = parseInt(match[2], 10);   // 日（数値に変換）
    
    // 今年の日付としてDateオブジェクトを作成
    var currentYear = new Date().getFullYear();
    var date = new Date(currentYear, month - 1, day); // 月は0から始まるため、-1する
    
    // 日付が有効かどうかを確認（無効な日付は無視）
    if (!isNaN(date.getTime())) {
      dates.push(date);
    }
  }
  // 専用のカレンダーを指定してイベントを追加
  var calendarId = '{GoogleカレンダーのID}'; // GoogleカレンダーのID（カレンダー設定から確認できる）
  var calendar = CalendarApp.getCalendarById(calendarId);
  
  // 各日付に対してイベントを登録
  for (var i = 0; i < dates.length; i++) {
    var eventTitle = targetAccount; // イベントのタイトル
    var eventDescription = postText; // イベントの説明
    var events = calendar.getEventsForDay(dates[i]);
      // 同じ日に同じ件名の予定があるかどうかをチェック
    var isDuplicate = events.some(function(event) {
      return event.getTitle() === eventTitle;
    });
    if(!isDuplicate){
      var event = calendar.createAllDayEvent(eventTitle, dates[i], {
        description: eventDescription
      });
      console.log('Event ID: ' + event.getId()　+ ' targetAccount:' + targetAccount);
    }
  }
}
```

getRecentPagesからextractAndScheduleToCustomCalendarを呼び出すように修正します

```diff_javascript:extractAndScheduleToCustomCalendarを呼び出すコード
function getRecentPages(targetAccount){
  var instragramID = 'InstagramビジネスID';
  var ACCESS_TOKEN = 'APIアクセストークン';
  var facebook_url = 'https://graph.facebook.com/v20.0/'+instragramID+'?fields=business_discovery.username('+targetAccount+'){followers_count,media_count,media{id,caption}}&access_token='+ACCESS_TOKEN;
  var response = UrlFetchApp.fetch(encodeURI(facebook_url),{muteHttpExceptions:true}); //URLから情報を取得
  var jsonData = JSON.parse(response);//JSONデータをパース
  inData = jsonData['business_discovery']['media']['data'];
  for(let index in inData)
  {
    var caption = inData[index]['caption'];
   if(caption !== undefined)
   if(caption.includes('Route') || caption.includes('Schedule') || caption.includes('セット')|| caption.includes('スケジュール'))
   {
+    extractAndScheduleToCustomCalendar(targetAccount,caption);   
-    console.log(caption);
   }
  }
}
```
### 実行してみる

プログラム上部の実行ボタンから再実行すると
コンソールに実行ログが出ているのがわかると思います

![スクリーンショット 2024-06-28 11.31.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/83ee559b-5b88-41cf-d2e2-868f1a00d1e5.png)

無事スケジュールに登録することができました

![スクリーンショット 2024-06-28 11.31.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3824565/a07d2ac6-c764-d22a-9bfa-8c19ef685de6.png)

# 最後に

コード全体を載せておきます
うまく動かないときはここと見比べてみてください

```javascript:コード全体
function myFunction() {
  getRecentPages('bpumptokyo');
}

function getRecentPages(targetAccount){
  var instragramID = '{InstagramビジネスID}';
  var ACCESS_TOKEN = '{アクセストークン}';
  var facebook_url = 'https://graph.facebook.com/v20.0/'+instragramID+'?fields=business_discovery.username('+targetAccount+'){followers_count,media_count,media{id,caption}}&access_token='+ACCESS_TOKEN;
  var response = UrlFetchApp.fetch(encodeURI(facebook_url),{muteHttpExceptions:true}); //URLから情報を取得
  var jsonData = JSON.parse(response);//JSONデータをパース
  inData = jsonData['business_discovery']['media']['data'];
  for(let index in inData)
  {
    var caption = inData[index]['caption'];
    if(caption !== undefined)
    if(caption.includes('Route') || caption.includes('Schedule') || caption.includes('セット')|| caption.includes('スケジュール'))
    {
     extractAndScheduleToCustomCalendar(targetAccount,caption);
    }
  }
}
function extractAndScheduleToCustomCalendar(targetAccount,postText) {
  var dates = [];
  // 日付を抽出する正規表現パターン
  var datePattern = /(\d{1,2})[\/月](\d{1,2})(?:\([月火水木金土日]\))?/g;
  // 投稿テキストから日付を抽出
  var match;
  while ((match = datePattern.exec(postText)) !== null) {
    var month = parseInt(match[1], 10); // 月（数値に変換）
    var day = parseInt(match[2], 10);   // 日（数値に変換）
    
    // 今年の日付としてDateオブジェクトを作成
    var currentYear = new Date().getFullYear();
    var date = new Date(currentYear, month - 1, day); // 月は0から始まるため、-1する
    
    // 日付が有効かどうかを確認（無効な日付は無視）
    if (!isNaN(date.getTime())) {
      dates.push(date);
    }
  }
  // 専用のカレンダーを指定してイベントを追加
  var calendarId = '{GoogleカレンダーID}';
  var calendar = CalendarApp.getCalendarById(calendarId);
  
  // 各日付に対してイベントを登録
  for (var i = 0; i < dates.length; i++) {
    var eventTitle = targetAccount; // イベントのタイトル
    var eventDescription = postText; // イベントの説明
    var events = calendar.getEventsForDay(dates[i]);
      // 同じ日に同じ件名の予定があるかどうかをチェック
    var isDuplicate = events.some(function(event) {
      return event.getTitle() === eventTitle;
    });
    if(!isDuplicate){
      var event = calendar.createAllDayEvent(eventTitle, dates[i], {
        description: eventDescription
      });
      console.log('Event ID: ' + event.getId()　+ ' targetAccount:' + targetAccount);
    }
  }
}

```

# 今後の改修予定

カレンダーの色分けをしたいとなるとCalendarAppではなくGoogleのカレンダーAPIを利用することになります。GCPでプロジェクトを作ってAPIキーを取得すればすぐ利用できるようになるので、次のチャレンジとするかもしれません

お疲れ様でした