ヒロイン誕生日カレンダーを作った話

これはUEC Advent Calendar 2014における15日目の記事です。前日の記事はkuroshun96さんの「お詫び案件」でした。
今回は「ヒロイン誕生日カレンダーを作った話」というタイトルで記事を書きたいと思います。

ヒロイン誕生日カレンダーって何だ


ヒロイン誕生日カレンダーは読んで字のごとく、ヒロインの誕生日をカレンダーにしたものです。うん、よく分からん。
具体的には、Googleカレンダーにヒロインの誕生日を終日予定として新たなカレンダーシート上に登録していきます。
Screenshot-8

これを手動でポチポチ登録やってたらキリがない。あと管理しきれない。というかそもそも記事にするレベルでもないですね。
ということで、スクリプトを書いて簡易管理ツール的なものを作りました。
ヒロインの誕生日情報をGoogleドライブのスプレッドシート上で管理しておいて、ボタンひとつで自動的にGoogleカレンダーにシートを追加して誕生日情報を登録・更新してくれる(個人的に)めっちゃ便利な機能です。

実装のお話


実装といっても実は大したことなくて、コード量的には100行程度で済んでいたりします。
このスクリプトはGoogle Apps Script(以下GAS)を使って書いていて、GASというのはJavaScriptをベースにGoogleの各種APIを関数として扱えるように拡張を加えた言語です。というかJavaScriptです。
スプレッドシートをGASで取得して、読み込んだデータをカレンダー関係の関数に値をセットして放り込んでいるだけという単純なお仕事。どちらかというとエラー処理に労力が割かれる感じでしょうか。
GASにはweb上で開発ができる環境が用意されていて、Googleのアカウントを持っている人であれば誰でも利用することができます。しかもこのweb上の開発環境、補完機能までついていてめっちゃ便利。

Screenshot-9

スプレッドシートではこんな感じでヒロインの誕生日情報が管理されています。
name欄に名前、dateに日付、descriptionにアニメやゲームのタイトルや備考を入力して、1行につき1人を入力して書いていきます。その横のeventidはGoogleカレンダーでその予定情報のユニークID(もちろん画像のは全桁表示していません)、statusにはスクリプトの実行結果が自動で入ります。
ちなみに最初の1行目はカレンダーのシートのID(これももちろん画像は一部隠してますが)で、2回目以降の実行の際はこのIDからシートを判別して上書きしていきます。なお、このIDが空だった場合はスクリプトで新たなシートを生成した上でそこに追加されていきます。
そんなスプレッドシートですがスクリプトの実行方法はシンプルで、メニューバーの右端に普段はないはずの「カレンダー」というメニューから実行するだけです。とても簡単。

このメニューバーに項目を追加することもGASから実行できて、次のように書いてやるとメニューを追加することができます。

function onOpen() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var entries = [{
    name : "インポート",
    functionName : "execute_cal"
  }];
  spreadsheet.addMenu("カレンダー", entries);
};

ちなみにスプレッドシートの読み込みと、カレンダーIDの指定は次のように書いています。

  //シートの取得
  var sheet = SpreadsheetApp.getActiveSheet();
  var rows = sheet.getLastRow(); //行数取得

  //シート全体のデータを取得
  var data = sheet.getDataRange().getValues();
  
  //カレンダー作成
  var calid = data[0][1];
  try{ //カレンダーが存在しない、IDが間違ってるエラー処理
    var res = CalendarApp.getCalendarById(calid);
    if(res == null) throw new Error("No Calendar");
  }catch(e){
    var cal = CalendarApp.createCalendar("ヒロイン誕生日", {"color": "#16a765"})
    calid = cal.getId();
    sheet.getRange(1, 2).setValue(calid);
  }

部分的に抜粋しただけじゃ何書いてるか分からないですが…
メソッドとしては”SpreadsheetApp.getActiveSheet()”で対象のスプレッドシートを指定して読み込んでます。さらに”sheet.getDataRange().getValues()”とすることで、このスプレッドシートを2次元配列として扱うことができます。
そして”CalendarApp.getCalendarById(calid)”として、カレンダーIDを引数に取ってGoogleカレンダーを指定しています。ここで開いたカレンダーにヒロインの誕生日を予定情報として登録していくわけです。

    if(eid === ""){
      try{
        var eventSeries = CalendarApp.getCalendarById(calid).createAllDayEventSeries(name, date,
                                  CalendarApp.newRecurrence().addYearlyRule(), {description: des});
      }catch(e){
        sheet.getRange(i+1, 5).setValue("Failed");
      }
      
      if(eventSeries){
        sheet.getRange(i+1, 5).setValue("OK");
        sheet.getRange(i+1, 4).setValue(eventSeries.getId());
      }else{
        sheet.getRange(i+1, 5).setValue("Failed");
      }

    }

スプレッドシートの情報は配列として読み込んであるので、そこから必要な情報をカレンダーのAPIにセットしていきます。
“CalendarApp.getCalendarById(calid).createAllDayEventSeries(name, date,CalendarApp.newRecurrence().addYearlyRule(), {description: des})”という部分が該当部分ですが、ちょっと1行にまとめて書いていてさらに分からない感じになっているので、部分的にざっくり説明していきます。
“.getCalendarById(calid)”メソッドでカレンダーのIDを引数にカレンダーを取得します。ベースですね。
“.createAllDayEventSeries()”メソッドはカレンダー上に終日予定を追加するもので、引数に予定のタイトル、日付、その他のオプションを引数に指定します。ここでは繰り返し予定として毎年繰り返しを”.newRecurrence().addYearlyRule()”で指定しています。誕生日は毎年やってきますからね。あとはアニメやゲームタイトルなどの情報を”{description: des}”として引数に指定しています。

エッセンスとしてこんな感じですが、このスクリプトを実行するとGoogleカレンダー上では次のような予定として登録されます。
無題

きちんとタイトルにヒロインの名前、終日予定と繰り返し予定にチェックボックスが入っています。
また、カレンダーは「ヒロイン誕生日」という独立したカレンダーシート、説明欄にアニメ・ゲームタイトルが登録されています。
もちろん、予定登録時のAPIの指定によって、誕生日の日付が来たら通知を行うということも可能です。

今回は自分が使うだけなので、あまり他の機能等は考えないで単なるカレンダーの登録だけを考えて作りましたが、スプレッドシート上のGoogle Apps Scriptはアドオンとして配布することも可能です。
もしこんな残念な使い方のスクリプトでも、万が一使いたいとかいう人がいればきちんとアドオン化して配布することも考えるかもしません。

Googleアカウントを持っている人であれば誰でも使えるGAS、使える関数はかなり用意されていてスプレッドシートやカレンダー以外にもドライブ、さらにサイトにも用意されていて夢が広がりんぐって感じです。
ちなみに、もちろんこれら機能はGAS上だけでなくOAuth認証することで様々な言語から利用することができるAPIとして用意されていて、GASで用意されている機能よりもAPIが多彩です。

明日はzico1222さんの記事です。ありがとうございました。

5 thoughts on “ヒロイン誕生日カレンダーを作った話

  1. しし

    友人の誕生日をスプレッドシート管理していて、
    どうにかカレンダーにエクスポートできないか探していてたどり着きました。
    唐突に出てきたeidがよく分からず実行できずにいるので、捕捉をお願いしてもよいでしょうか?

    1. thunder_lab Post author

      まさか参考にしてくださる方がいるとは…!ありがとうございます。

      記事のコードですが、かなり部分的に貼り付けただけなので確かに変数が色々わかりにくいですね…
      Googleカレンダーでは1つ1つの予定(イベント)を登録する際、IDが自動で振られるのですが、”eid”はこのイベントIDを指しています。
      予定を登録する際、APIの返り値として記事中では”eventSeries”という変数に返り値を代入しているのですが、ここに”eventSeries.getId()”とgetIdメソッドを実行することで登録したイベントのIDを取得することができます。
      スプレッドシートにはこのイベントIDを一緒に保存しておいて、後々にシートのアップデートをする際にこのスプレッドシートに書いておいたイベントIDで登録済みの予定にアクセスしようというわけです。

      つまり、記事の”if(eid === “”){…” はスプレッドシート上のイベントIDがない(=まだカレンダーに登録されていない予定)ならば掲載しているコード通り予定を登録する、という意味になります。
      掲載はしていないのですが、この後にはelse文が続き、”eid”が存在する(=カレンダーにその予定が存在する)場合として、
      ”CalendarApp.getCalendarById(calId).getEventSeriesById(eid)”とすれば”eid”を使用して登録済みのイベントが取得できるので、
      同様に”setTitle”や”setDescription”メソッドを実行して予定のアップデート(上書き)を行っています。

      拙い説明ですが参考になれば幸いです。

  2. しし

    お返事ありがとうございます!納得しました!
    実行の度に既存のデータを上書きするための仕組みですね。
    ただ書き出すだけだとどんどん同じ予定が増えてしまいますし、理解しましたー!

  3. sankichi

    掲載はしていないのですが、この後にはelse文が続き、・・・

    突然すみません
    ずぶの素人です。
    仕事上、1000人近い従業員の誕生日をカレンダーに登録して、毎年繰り返し表記させ、管理事業部で共有したいと考えております。

    コピペさせていただいてカレンダーの繰り返し登録の参考にさせていただこうといろいろ動かしていますが、エラーでeidのところで先に進まなくて。お手数ですが、その先の表記もご教授いただけると助かります。

    1. thunder_lab Post author

      大変申し訳ありませんが当時のコードが既に残っていないため、そのままお教えすることができません。

      お伺いする限り、繰り返し予定が人数分登録できればよいのかと思いますが、そうなると本文3ブロック目3行目の
      ” CalendarApp.getCalendarById(calid).createAllDayEventSeries(name, date, CalendarApp.newRecurrence().addYearlyRule(), {description: des});”
      が人数分の回数実行できればよいかと思います。

      具体的なエラー内容が不明なので的確な内容でないかもしれませんが、”eid…”にかかる判定は予定の重複登録を防ぐための処理で予定の登録そのものとは別なので、一度切り離してif文を外して実行してみるとどうでしょう?
      予期しない実行結果で困らないためにも、コピペであってもきちんと中身を理解してから試すことをお勧めします。

しし へ返信する コメントをキャンセル

メールアドレスが公開されることはありません。