2018年8月18日土曜日

Google Apps ScriptでGoogleカレンダーとTODOリストを連携する

動機

最近、Google謹製のTODOリストを使っている。シンプルなのは非常にいいのだが、いくつか使いづらい点がある。そのうちの一つに、時間指定のタスク登録・通知ができないという点がある。そこで、Google Apps Scriptの勉強もかねて、GoogleカレンダーとGoogle TODOリストを連携させて、時間指定の通知をできるようにしたいと思う。

そんなのカレンダーに直接登録すればいいじゃん!という声も聞こえそうだが、TODOリストへの登録のしやすさは捨てがたいので、こういう面倒なことを思いつくのであった。

また、もう一つ強力なモチベーションとして、このスマートウォッチの購入がある。
NOKIA製のスマートウォッチ!ただ、こいつが通知してくれるのは、
  • 電話
  • メール
  • カレンダー
の三点に限られており、TODOをカレンダーに登録するのは必然なのであった。

実践

やりたいことをより具体的にすると、TODOリストに日限(時刻含む)が記載されているタスクを、Googleカレンダーの指定の時刻に登録、ということなので、Google謹製ソフトの連携だけなので簡単じゃん!…とはいかず。曲者はTODOリストのほうである。公式のReferenceは下記。
Google謹製のくせに標準でGoogle Apps Scriptのクラスには登録されておらずハブられているし、実行しようにもひと手間加えてあげないといけないという面倒なやつ。公式Referenceも英語だけなのはまだしも、肝心のGASベースでの記述方法が書いておらず、非常に使いづらい…

ということで行きついたのが下記のサイト
Tasksの使いはじめの設定から、リストの取得や変更まで詳しく書いてあり、このページでほとんど事足りるのだが、いかんせん作成されたのが2012年で、このころと仕様が若干変わっているらしく、命令などもちょっと違っている。

ということで、実際に書いたコードが下記である。
function tasksToCalender() {

  //defaultのタスクリスト読み込み
  var listitem = Tasks.Tasklists.list().items;
  var taskListId = listitem[0].id;
  var tasks = Tasks.Tasks.list(taskListId);

  //全タスクについて判定
  if (tasks.items) {
    for (var i = 0; i < tasks.items.length; i++) {
      
      //タスク読み込み
      var task = tasks.items[i];
      
      //日付判定。タスクの日付が今日以前もしくは期日設定なしの場合は処理終了
      var date = new Date(tasks.items[i].due);
      var today = new Date();
      if (date.getTime() < today.getTime() || !date.getTime()) {
        continue;
      }
      
      //なぜかnotesでStringの関数が使えないため変換
      var str = String(task.notes);
      
      //@がnotesにある場合はgoogleカレンダーに登録する
      if (str.match('@')) {

        //日本時刻に戻すために9時間引いた値を時間に設定
        date.setHours(date.getHours() + Number(str.substr(1,2))-9)

        //startTimeはさらに分を足して設定。アラーム目的なのでendTimeは+1hour
        var startTime = new Date(date.setMinutes(date.getMinutes() + Number(str.substr(3,2))));
        var endTime = new Date(date.setHours(date.getHours() + 1));
        CalendarApp.createEvent('TEST', startTime, endTime);
        
        //@を消して二重登録を防ぐ
        task.notes = str.replace('@','');
        Tasks.Tasks.update(task, taskListId, task.id);

      };
    }
  }

}

タスク登録は、下記のように詳細部分に@時間、のように指定する。
こうすることで、指定日時のカレンダーに登録されるようになる。

詳細

ちょっと面倒なところだけ説明を加えようかと思う。

1. TODOリストの取得
var listitem = Tasks.Tasklists.list().items;
var taskListId = listitem[0].id;
var tasks = Tasks.Tasks.list(taskListId);
やっていることは単純なんだが、先のリンクにも書いてある通り、まずはTasklistsでTODOリストのタスクリストを配列として取得して、その中から目的のタスクリスト(今回はdefaultなので配列の0番目)のidを取得、その上でタスクリスト内のタスクを配列で取得、となんとも回りくどいことをしないといけない。

2. 時間の判定
var str = String(task.notes);
      
if (str.match('@')) {

  … …

}
この1行目が曲者で、公式さんはTasksのnotesプロパティはStringだよ!!と言っているにもかかわらず、なぜかStringに使用できるmatch他のメソッドが使用できないというエラーが出るため、ここで強制的にString型に変換している。何とも不思議である。一体何型が返ってきているのだろうか??

で、まあ予定通りに@がついているものだけ処理するif文を入れてます。

3. タスクのupdate
task.notes = str.replace('@','');
Tasks.Tasks.update(task, taskListId, task.id);
タスクの2重登録を防ぐために、指定タスクのnotesから@を消す作業。この2行目のupdateメソッドについて、引数の数やらなにやらが公式さんに書いておらず(書いてあるのかもしれないがわからなかった)、下記のGAS Editorの補完から推定するしかなかった。
Tasks.Tasks.update(Task resource, String tasklist, String task)
これは、変更を加えたTaskオブジェクトを、あるタスクリストのあるタスクと置き換える、という意味だ(と思われる)。なので、1行目でtaskのnotesの@を削除して第1引数に(なぜかnotesにはStringが代入できる…!!)、タスクリストのidとタスクのidを2, 3引数にぶち込んでやることで、無事にタスクが置き換わった。

まとめ

ということで、連携完了。コード自体はすごく単純なんだが、教科書が頼りにならず、ググってもこんなマニアックなことやろうとする人はいないらしく検索できず、ほぼ自力で作成したので時間がかかってしまった。あとはこいつを適当な時間タイミングでリピートすればOKということですな。

デバッグ不十分なため、ミスあるかも。

2018年8月7日火曜日

GoogleシートでRPG風家計簿~目標仕様~

既存の家計簿&TODOリストって、機能が多すぎていまいち継続して使えなかったので、いっそ自分で作ってしまおうと思い立った。作成するにあたって、継続する仕組みとしてRPG風にして、家計簿&TODOリストを登録すると経験値やゴールドがたまるシステムにしたいと思っている。

最終的にはスマホアプリ化したいので、Java(というよりAndroid用Java)は秘密裏に勉強中だが、目的は"とりあえずアプリを作ること"ではなく、"シンプルかつ持続可能なシステム作成"なので、お題目の通り、既存のサービス、具体的にはWeb上の表計算ソフトであるGoogleシート他の機能を使用して、取り急ぎシステムを稼働させようと考えたのであった。

今回作成しようとしている家計簿&TODOリストの目標仕様は下記の通りである。

RPG的要素

続ければ続けるほどレベルが上がる、というシステムにしたい。そのため、RPGにある、敵と戦うとダメージ、など、ネガティブな要素は排除し、あくまでも実行=レベルアップにする。最終的にはアバターも工夫したいが、Googleシートは画像系の機能が弱いので(というか本来不要?)、初期はあまり入れ込まないことにする。具体的には、

  • 経験値とゴールド、武器・防具機能実装。家計簿やTODOをこなしていくと経験値やゴールドがもらえる。
  • 経験値がたまるとレベルが上がる。
  • レベルが上がるとボスが倒せる。
  • ゴールドがたまると武器・防具が買える。
  • 武器・防具でステータスが上がると、家計簿やTODOを実行したときの経験値やゴールドが増える。


家計簿

家計簿の目的は、金銭の出納管理ではなく、自分がいかにお金を使っているか、という意識づけのためだと考えている。そのため、ものすごいシンプル、というより通常の家計簿では必ずあるような仕様、例えば収入の管理や項目の設定すら削ってしまうつもりである。具体的には、

  • 家計簿をつけると経験値がたまる。
  • 項目は適当に記述式で入力。項目を選ぶ段階で、「これって何にあたるだろう?」という一瞬の思考の迷いにより継続意思が数pt削がれる。
  • 代わりに、「この支出は必要か不要か」だけ選ぶようにする。
  • 必要な支出と不要な支出、両方とも経験値がたまるが、必要な支出を記載したときのほうが経験値が上がるようにする。
  • ゴールドは「必要な支出-不要な支出」を基準として算出する。要は、不要な支出ばかり繰り返すとどんどんゴールドが減っていく。


TODOリスト

GTDベースが理想だが、プライベートをそこまで管理しなくてもやり切れるので、単にメモ帳レベルのTODOリストにする。


具体的な内容は少しずつアップしていきたいと思う。

次回:家計簿入力システム実装