献血バスのスケジュールをGoogleカレンダーに自動反映させてみた

## 初めに

「あ，学校前に献血バス来てる！，けど時間ない！」
ということはありませんか？
こんなとき，事前に献血バスが来ていることがわかっていれば，それを組み込んだ予定を立てられて，便利ですよね．
実は献血バスのスケジュールは，1か月前くらいから発表されています．
[献血バス運行スケジュール｜東京都赤十字血液センター](https://www.bs.jrc.or.jp/ktks/bbc/block/m1_04_index.html)
（東京都以外も調べれば出てきます．）
しかし，これを自動で通知してくれる機能は存在しません．
そこで，1か月に1回程度，自動で献血バスのスケジュールを取得し，Googleカレンダーに反映させる機能を作りました．

## 仕様

月に一度，献血バスのスケジュールを取得し，指定したキーワード（例えば「大岡山」）に合致する場所で開催される献血のスケジュールを，指定したGoogleカレンダーに，以下のような形式で追加します．
なお，URLは予約用のリンクです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648110/54e2bbe8-0955-b6db-32ed-266af4a0400a.png)


## 使い方

1. 後述のプログラムをGAS(Google Apps Script)にコピーする．
1. スケジュールが掲載されているサイトのurlを`target_url`に与える．
1. GoogleカレンダーのIDを`calendar_id`に与える．
（カレンダーIDの取得方法は適宜調べてください．）
1. 献血会場のキーワードを，`keyword_place_array`に与える．
（下図の赤で囲んだ部分に含まれるワードです．）
![献血会場のシステム概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648110/8b26df29-d602-4312-8bcc-886a5702b6bc.png)
1. 月1回程度（恐らく月末に翌月のスケジュールが発表される？）の時間主導型トリガーを設定する


## プログラム

```javascript
// 献血バスのスケジュールが掲載されているページのURL（東京以外の方は変えてください）
const target_url = 'https://www.bs.jrc.or.jp/ktks/tokyo/place/m1_03_kenbus.html';
// 対象カレンダーのID
const calendar_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@group.calendar.google.com';
// 場所のキーワード
const place_keyword_array = ["大岡山"]

// htmlを取得する関数
function get_html(url) {
  var response = UrlFetchApp.fetch(url);
  var htmlContent = response.getContentText("utf-8");
  return htmlContent;
}

// htmlから献血場所を配列として取得する関数
function get_table(html) {
  let doc = Parser.data(html).from('<article class="mod-iconIndent01">').to('</article>').build();
  doc = doc.replace(/\t/g,"");
  doc = doc.replace(/\n/g,"");
  let table_by_day = Parser.data(doc).from('<br><p>').to('</div>').iterate();
  console.log(table_by_day);

  // 日付ごとのループ
  let table = new Array();
  let index = 0;
  for (let i = 0; i < table_by_day.length; i++) {
    const date = Parser.data(table_by_day[i]).from('').to('</p>').build();
    const today = new Date();
    const year = today.getFullYear();
    const month = Number(Parser.data(date).from('').to('月').build());
    const day = Number(Parser.data(date).from('月').to('日').build());

    let table_by_place = Parser.data(table_by_day[i]).from('<tr><td class="tac vam">').to('</td></tr>').iterate();

    console.log(table_by_day[i]);

    // 場所ごとのループ
    for (let k = 0; k < table_by_place.length; k++) {
      table_by_place[k] = '<td class="tac vam">' + table_by_place[k] + '</td>';
      table[index] = {};
      table[index].place = Parser.data(table_by_place[k]).from('<li>').to('</li>').build();
      table[index].booking_url = Parser.data(table_by_place[k]).from('<li><a href="').to('">※Web予約はこちら').build();
      let temp = Parser.data(table_by_place[k]).from('<td class="tac vam">').to('</td>').iterate();
      console.log(temp);
      console.log(table[index]);
      table[index].municipalities = temp[0];
      table[index].time = temp[1].split("<br>");

      // 献血の開始時間，終了時間を取得
      table[index].date = new Array();
      for (let j = 0; j < table[index].time.length; j++) {
        table[index].time[j] = table[index].time[j].replace(/：/g,":"); // たまに，全角"："で区切っているところがあるので修正
        table[index].time[j] = table[index].time[j].replace("-","～"); // たまに，"-"で区切っているところがあるので修正
        //console.log(table[index].time[j]);
        const temp = table[index].time[j].split("～");
        
        table[index].date[j] = {};
        table[index].date[j].start_time = Utilities.parseDate(year+"/"+month+"/"+day+" "+temp[0], 'JST', 'yyyy/M/d HH:mm');
        table[index].date[j].end_time   = Utilities.parseDate(year+"/"+month+"/"+day+" "+temp[1], 'JST', 'yyyy/M/d HH:mm');

        //console.log(table[index].date[j])
      }
      index++;
    }
  }
  console.log(table);
  return table;
}

// カレンダーに反映する関数
function add_calendar(calendar_id, keyword, table) {
  let target_calendar = CalendarApp.getCalendarById(calendar_id);
  for (item of table) {
    if (item.place.includes(keyword)) {
      console.log(item.place);
      for (time_zone of item.date) {
        target_calendar.createEvent("献血バス "+item.place, time_zone.start_time, time_zone.end_time,{description:item.booking_url});
      }
    }
  }
}

function main() {
  // htmlを取得
  let html = get_html(target_url);

  // htmlから献血場所を配列として取得
  let table = get_table(html);

  // カレンダーに反映
  for (place_keyword of place_keyword_array) {
    add_calendar(calendar_id, place_keyword, table);
  }
}
```