Todoistって、Amazon Echoと連携できるんですね!
しかもWebAPIが充実しているので、さらに連携の輪が広がりそうです。
ちなみに、Todoistは、タスク管理ツールです。一般には、やることリストとか、買い物リストが挙げられますが、メモ的に使えて、かつ、期限を設定して予定を立てたり、失念するのを避けるのに役立ちます。
todoist
https://todoist.com/
今回作成する全体の構成はこんな感じです。
Image may be NSFW.
Clik here to view.
すでに、todoistはAlexaとの連携をサポートしていますし、AndroidやiPhone用のアプリもありますので、その部分は特に難しいところはありません。
今回は、やることリスト・買い物リストの表示をWebページとして表示します。todoistはオフィシャルでWebAPIが充実していますし、npmモジュールもあるので、活用の幅が広がります。
また、さらに、PWA化することでネイティブアプリ化し、それをAndroidで固定アプリに設定することで、やることリスト表示の専用機に仕立て上げます。
毎度のことですが、ソースコードもろもろをGitHubに上げておきました。
poruruba/todolist
https://github.com/poruruba/todolist
準備
todoistのアカウント登録
まずは、以下のURLから、todoistのアカウント登録をします。
todoist
https://todoist.com/ja
Image may be NSFW.
Clik here to view.
「はじめる」ボタンまたは右上のサインアップをクリックします。
Image may be NSFW.
Clik here to view.
ここでは、スマートフォンにインストールしているAmazon Alexaアプリに設定しているアカウントでサインアップし、認証を完了させます。
Image may be NSFW.
Clik here to view.
これでアカウントが登録されました。
todoistアプリをインストール
Google Playから、Todoistをインストールします。
Image may be NSFW.
Clik here to view.
todoistにアカウント登録したときのアカウントでログインします。
Image may be NSFW.
Clik here to view.
ログインが完了しました。
Image may be NSFW.
Clik here to view.
Alexa連携を設定
すでに、スマートフォンに、「Amazon Alexa」がインストールされている前提で進めます。
以降は、Androidでの操作です。
まずは、下の方の「その他」→「設定」→「リスト」を選択します。
Image may be NSFW.
Clik here to view.
そうすると、Any.do、AnyList、Todoistが表示されていますので、Todoistの右側の⊕をタッチします。
Image may be NSFW.
Clik here to view.
以下のように表示されるので、「有効にして使用する」ボタンを押下します。
Image may be NSFW.
Clik here to view.
リストへのアクセス権の確認が表示されますので、「アクセス権を保存」ボタンを押下します。
Image may be NSFW.
Clik here to view.
そうすると、todoistのサイトに飛んで、Agreeするかが聞かれますので、「Agree」ボタンを押下します。
Image may be NSFW.
Clik here to view.
これで、連携設定が完了しました。
Image may be NSFW.
Clik here to view.
もう一度、todoistアプリの方を開いてみましょう。
そうすると、左上の三のマークをタッチし、プロジェクトのところをタッチすると、「Alexa ToDo リスト」というのと、「Alexaの買い物リスト」が増えているのがわかります。
Image may be NSFW.
Clik here to view.
最後に、この「Alexa ToDo リスト」と「Alexaの買い物リスト」をお気に入りにしておきます。
「プロジェクト」→「プロジェクトを管理」をタッチし、「Alexa ToDo リスト」と「Alexaの買い物リスト」の右側にあるハートマークをタップし、赤色にします。これによって、この2つがお気に入りのプロジェクトとなりました。この意味はあとで、わかります。
Image may be NSFW.
Clik here to view.
あと、お好みで、todoistアプリをAndroidのホーム画面にウィジェットとして登録してもよいかと思います。
とりあえずAlexa+todoist連携を試してみる
以下に、発話例がありますので、試してみましょう。
Amazon Alexa で Todoist を使う
https://get.todoist.help/hc/ja/articles/360010721059-Amazon-Alexa-%E3%81%A7-Todoist-%E3%82%92%E4%BD%BF%E3%81%86
例えば以下をAmazon Echoに話しかけてみましょう。
・アレクサ、やることリストに洗濯を追加して。
・アレクサ、今日のやることリストは?
・アレクサ、やることリストの洗濯を完了にして
・アレクサ、買い物リストに納豆を追加して
・アレクサ、今日の買い物リストは?
・アレクサ、買い物リストの納豆を完了にして
めでたく、こんな感じで追加されました。
Image may be NSFW.
Clik here to view.
Node.jsサーバからtodoistのリストを取得する
たいていの方は上記まででよいのですが、勉強をかねて、拡張に挑戦します。
Node.jsからの操作には、以下のnpmモジュールを利用させていただきました。
romgrk/node-todoist
https://github.com/romgrk/node-todoist
以下も使っています。
node-fetch/node-fetch
https://github.com/node-fetch/node-fetch
リスト取得する前に、ユーザごとにAPIトークンの取得が必要です。
todoist Developer: Authorization
https://developer.todoist.com/sync/v8/#authorization
OAuthに似たやり取りで、ブラウザ側とサーバ側での連携が必要です。
まずは、サーバのURLをtodoistに登録しておく必要があります。以下のURLを開いて、「Create a new app」ボタンを押下します。
App Management Console
https://developer.todoist.com/appconsole.html
Image may be NSFW.
Clik here to view.
App display nameには適当な名前を、App service URLには、これから立ち上げるNode.jsのWebページのURLを指定しておきます。
次に作成したappを選択肢、OAuth redirect URLを指定します。後で作成するのですが、立ち上げるNode.jsのWebページと同じにしておきます。(Single Page Applicationとして実装するため)。最後に、「Save settings」ボタンを押下します。
Image may be NSFW.
Clik here to view.
その時に表示される、「Client ID」「Client secret」を覚えておきます。後で使うので。
①サーバ側に、ユーザ識別子.jsonというファイルを作成しておきます。
これがばれてしまうと、ログインされてしまいますので、ユーザ識別子は、推測されにくいランダムな値にしましょう。
②ブラウザからClient IDを指定して、ログインを開始する。
以下のように形成されるURLにジャンプします。
https://todoist.com/oauth/authorize?client_id=" + TODOIST_CLIENT_ID + "&scope=data:read&state=" + value
TODOIST_CLIENT_IDは、先ほど覚えておいたClient IDです。stateには正しくは乱数等推測されにくいものにするのですが、手を抜いていて、ユーザの識別子を指定します。①の通り、このユーザの識別子の名前+.jsonで、サーバ側にファイルが作成されている前提です。
③todoistのログインページが表示されるので、ログインする。
todoistのアカウント登録時に使った認証アカウントでログインします。
④認可コードを取得する
ログインが完了すると、OAuth redirect URLで指定したURLにジャンプしてきます。その時に、認可コードとstateが返ってきます。
stateは、②で指定した値のはずです。
⑤認可コードからAPIトークンを取得する
認可コードを取得したので、これを使って以降のtodoistのWebAPI呼び出しに必要なAPIトークンを取得します。子の取得には、Client secretが必要です。秘匿の値として扱う必要があるので、Nodeサーバに渡して、サーバ側で実施します。
exports.handler=async(event,context,callback)=>{varbody=JSON.parse(event.body);varapikey=event.requestContext.apikeyAuth.apikey;if(!checkAlnum(apikey))throw'apikey invalid';varconf=awaitreadConfigFile(apikey);if(event.path=='/todoist-callback'){varparam={client_id:TODOIST_CLIENT_ID,client_secret:TODOIST_CLIENT_SECRET,code:body.code};varjson=awaitdo_post("https://todoist.com/oauth/access_token",param);conf.token=json;awaitwriteConfigFile(apikey,conf);returnnewResponse({});}else
body.codeが、ブラウザから取得した認可コードです。
そうすると、todoistサーバから、APIトークンが返ってくるので、それをユーザ識別子ごとのファイルに保存します。ユーザ識別子はブラウザ側からAPI KeyとしてHTTPヘッダに指定してもらいます。
あとは、ブラウザからのリクエストに対して、todoistのnpmモジュールTodoistを使ってリストを取得します。取得には、APIトークンが必要ですので、ユーザ識別子ごとのファイルから取り出して使っています。
if(event.path=='/todoist-list'){consttodoist=Todoist(conf.token.access_token);awaittodoist.sync();constprojects=todoist.projects.get();varfavorite=projects.filter(item=>item.is_favorite);varfavorite_ids=favorite.map(item=>item.id);constitems=todoist.items.get();varfavorite_items=items.filter(item=>favorite_ids.includes(item.project_id));constnotes=todoist.notes.get();varitem_ids=favorite_items.map(item=>item.id);varfavorite_notes=notes.filter(item=>item_ids.includes(item.item_id));returnnewResponse({items:favorite_items,projects:projects,notes:favorite_notes});}
あと、細かな処理をしていますが、やっているのは、
・プロジェクトIDからプロジェクト名に変換するために、プロジェクト一覧を取得
・プロジェクトのリストから、お気に入りにしたプロジェクトのIDを取得
・すべてのリストを取得し、お気に入りのプロジェクトIDのものを抽出
・すべてのノートを取得し、お気に入りのプロジェクトIDのものを抽出
(参考) todoist Sync API
https://developer.todoist.com/sync/v8/
さきほどの、ブラウザ側とサーバ側の間の認可コードのやり取りの部分は以下です。
if(searchs.code){varparam={code:searchs.code};history.replaceState(null,null,'.');do_post_apikey(base_url+'/todoist-callback',param,searchs.state).then(json=>{console.log(json);Cookies.set("todo_apikey",searchs.state,{expires:EXPIRES});this.apikey=searchs.state;this.todo_list_update().then(()=>{setInterval(()=>{this.todo_list_update(true);},UPDARTE_INTERVAL*60*1000);});});
認証およびAPIキーがサーバ側で保持出来たら認証完了です。ブラウザ側では、ユーザ識別子をapikeyとしてCookieに保持しておきます。
リスト取得は以下の部分です。
todo_list_update:asyncfunction(silent){if(!this.apikey)return;try{if(!silent)this.progress_open();varparam={};varjson=awaitdo_post_apikey(base_url+'/todoist-list',param,this.apikey);this.todo_projects=json.projects;this.todo_notes=json.notes;vartoday=newDate();today.setHours(0,0,0,0)vartodayTime=today.getTime();vartomorrow=newDate();tomorrow.setHours(0,0,0,0)tomorrow.setDate(tomorrow.getDate()+1);vartomorrowTime=tomorrow.getTime();this.todo_list_expire=json.items.filter(item=>item.due&&Date.parse(item.due.date)<todayTime);this.todo_list_today=json.items.filter(item=>item.due&&Date.parse(item.due.date)>=todayTime&&Date.parse(item.due.date)<tomorrowTime);this.todo_list_other=json.items.filter(item=>item.due&&Date.parse(item.due.date)>=tomorrowTime);this.todo_list_someday=json.items.filter(item=>!item.due);}catch(error){console.error(error);alert(error);}finally{if(!silent)this.progress_close();}},
これまたいろいろやっていますが、要は、期限切れのタスクの抽出、今日のタスクの抽出、明日以降のタスクの抽出、期限が設定されていないタスクの抽出、をしています。
this.todo_list_XXXXという変数に格納していますが、あとはVueが表示をよろしくやってくれます。
ブラウザで表示してみましょう。
https://【立ち上げたサーバのURL】/index.html
最初に、API Keyの設定が必要です。「API Key」ボタンを押下して、ユーザ識別子を設定します。
Image may be NSFW.
Clik here to view.
さきほど追加した「洗濯」が表示されています。
期限を設定していなかったので、いつか のグループに入っています。
Image may be NSFW.
Clik here to view.
todoistのAndroidアプリでもいいですし、todoistのWebページからでもどちらでよいですが、「洗濯」タスクの期限を今日にしてみましょう
Image may be NSFW.
Clik here to view.
10分ごとに、リロードしておいたので、ちょっと?待てば、今日のタスクに移動するかと思います。
PWA化する
詳細は、以下を参考にしてください。今回はPush通知は使っていません。
以下を追記しています。
index.htmlに以下を追加。
<linkrel="manifest"href="manifest.json">
ページロード直後に以下を呼び出し。
if('serviceWorker'innavigator){navigator.serviceWorker.register('sw.js').then(async(registration)=>{console.log('ServiceWorker registration successful with scope: ',registration.scope);}).catch((err)=>{console.log('ServiceWorker registration failed: ',err);});}
sw.jsを作成し、public/sw.jsにデプロイ
これで、右上のアドレスバーのところの⊕を押せば、PWAアプリとしてインストールできるようになっているかと思います。
Image may be NSFW.
Clik here to view.
Androidでアプリ固定化
余っているAndroidタブレットを使って、常時リスト表示したいと思います。
Androidから同様にブラウザ(Chrome)から開き、メニューから「アプリをインストール」を選択して、アプリとしてインストールしておきます。
まずは、単独で起動でき、リストが表示されるところまで確認しておきます。
次に、アプリを固定化します。
(以降は、Androidのバージョンによって見え方は違うかもしれません。)
Image may be NSFW.
Clik here to view.
Androidの「設定」→「セキュリティ」→「アプリの固定」を選択します。
おそらくOFFになっているかと思いますので、ONにします。
次に、現在実行中のアプリ一覧を表示し、PWAアプリとして起動したものの上にあるアイコンをタッチします。
そうすると、「固定」があるので、それをタッチします。
Image may be NSFW.
Clik here to view.
こんな表示が出てきますので、よく読んで「OK」をタッチします。
これで、常時表示ができました。他のアプリが選択できなくなったと思います!
Image may be NSFW.
Clik here to view.
できました!
Image may be NSFW.
Clik here to view.
アプリ固定を解除して戻りたい時には、ホームボタンと戻るボタンを一緒に長押しすれば戻れます。
以上