Todoistって、Amazon Echoと連携できるんですね!
しかもWebAPIが充実しているので、さらに連携の輪が広がりそうです。
ちなみに、Todoistは、タスク管理ツールです。一般には、やることリストとか、買い物リストが挙げられますが、メモ的に使えて、かつ、期限を設定して予定を立てたり、失念するのを避けるのに役立ちます。
todoist
https://todoist.com/
今回作成する全体の構成はこんな感じです。
すでに、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
「はじめる」ボタンまたは右上のサインアップをクリックします。
ここでは、スマートフォンにインストールしているAmazon Alexaアプリに設定しているアカウントでサインアップし、認証を完了させます。
これでアカウントが登録されました。
##todoistアプリをインストール
Google Playから、Todoistをインストールします。
todoistにアカウント登録したときのアカウントでログインします。
ログインが完了しました。
##Alexa連携を設定
すでに、スマートフォンに、「Amazon Alexa」がインストールされている前提で進めます。
以降は、Androidでの操作です。
まずは、下の方の「その他」→「設定」→「リスト」を選択します。
そうすると、Any.do、AnyList、Todoistが表示されていますので、Todoistの右側の⊕をタッチします。
以下のように表示されるので、「有効にして使用する」ボタンを押下します。
リストへのアクセス権の確認が表示されますので、「アクセス権を保存」ボタンを押下します。
そうすると、todoistのサイトに飛んで、Agreeするかが聞かれますので、「Agree」ボタンを押下します。
これで、連携設定が完了しました。
もう一度、todoistアプリの方を開いてみましょう。
そうすると、左上の三のマークをタッチし、プロジェクトのところをタッチすると、「Alexa ToDo リスト」というのと、「Alexaの買い物リスト」が増えているのがわかります。
最後に、この「Alexa ToDo リスト」と「Alexaの買い物リスト」をお気に入りにしておきます。
「プロジェクト」→「プロジェクトを管理」をタッチし、「Alexa ToDo リスト」と「Alexaの買い物リスト」の右側にあるハートマークをタップし、赤色にします。これによって、この2つがお気に入りのプロジェクトとなりました。この意味はあとで、わかります。
あと、お好みで、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に話しかけてみましょう。
・アレクサ、やることリストに洗濯を追加して。
・アレクサ、今日のやることリストは?
・アレクサ、やることリストの洗濯を完了にして
・アレクサ、買い物リストに納豆を追加して
・アレクサ、今日の買い物リストは?
・アレクサ、買い物リストの納豆を完了にして
めでたく、こんな感じで追加されました。
#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
App display nameには適当な名前を、App service URLには、これから立ち上げるNode.jsのWebページのURLを指定しておきます。
次に作成したappを選択肢、OAuth redirect URLを指定します。後で作成するのですが、立ち上げるNode.jsのWebページと同じにしておきます。(Single Page Applicationとして実装するため)。最後に、「Save settings」ボタンを押下します。
その時に表示される、「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) => {
var body = JSON.parse(event.body);
var apikey = event.requestContext.apikeyAuth.apikey;
if( !checkAlnum(apikey) )
throw 'apikey invalid';
var conf = await readConfigFile(apikey);
if( event.path == '/todoist-callback' ){
var param = {
client_id: TODOIST_CLIENT_ID,
client_secret: TODOIST_CLIENT_SECRET,
code: body.code
};
var json = await do_post("https://todoist.com/oauth/access_token", param );
conf.token = json;
await writeConfigFile(apikey, conf);
return new Response({});
}else
body.codeが、ブラウザから取得した認可コードです。
そうすると、todoistサーバから、APIトークンが返ってくるので、それをユーザ識別子ごとのファイルに保存します。ユーザ識別子はブラウザ側からAPI KeyとしてHTTPヘッダに指定してもらいます。
あとは、ブラウザからのリクエストに対して、todoistのnpmモジュールTodoistを使ってリストを取得します。取得には、APIトークンが必要ですので、ユーザ識別子ごとのファイルから取り出して使っています。
if( event.path == '/todoist-list' ){
const todoist = Todoist(conf.token.access_token);
await todoist.sync();
const projects = todoist.projects.get();
var favorite = projects.filter(item => item.is_favorite );
var favorite_ids = favorite.map(item => item.id);
const items = todoist.items.get();
var favorite_items = items.filter(item => favorite_ids.includes(item.project_id));
const notes = todoist.notes.get();
var item_ids = favorite_items.map(item => item.id);
var favorite_notes = notes.filter(item => item_ids.includes(item.item_id));
return new Response({items: favorite_items, projects: projects, notes: favorite_notes });
}
あと、細かな処理をしていますが、やっているのは、
・プロジェクトIDからプロジェクト名に変換するために、プロジェクト一覧を取得
・プロジェクトのリストから、お気に入りにしたプロジェクトのIDを取得
・すべてのリストを取得し、お気に入りのプロジェクトIDのものを抽出
・すべてのノートを取得し、お気に入りのプロジェクトIDのものを抽出
(参考) todoist Sync API
https://developer.todoist.com/sync/v8/
さきほどの、ブラウザ側とサーバ側の間の認可コードのやり取りにおいて、ブラウザ側の部分は以下です。
if( searchs.code ){
var param = {
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: async function(silent){
if( !this.apikey )
return;
try{
if( !silent )
this.progress_open();
var param = {};
var json = await do_post_apikey(base_url + '/todoist-list', param, this.apikey);
this.todo_projects = json.projects;
this.todo_notes = json.notes;
var today = new Date();
today.setHours(0, 0, 0, 0)
var todayTime = today.getTime();
var tomorrow = new Date();
tomorrow.setHours(0, 0, 0, 0)
tomorrow.setDate(tomorrow.getDate() + 1);
var tomorrowTime = 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」ボタンを押下して、ユーザ識別子を設定します。
さきほど追加した「洗濯」が表示されています。
期限を設定していなかったので、いつか のグループに入っています。
todoistのAndroidアプリでもいいですし、todoistのWebページからでもどちらでよいですが、「洗濯」タスクの期限を今日にしてみましょう
10分ごとに、リロードしておいたので、ちょっと?待てば、今日のタスクに移動するかと思います。
PWA化する
詳細は、以下を参考にしてください。今回はPush通知は使っていません。
以下を追記しています。
index.htmlに以下を追加。
<link rel="manifest" href="manifest.json">
ページロード直後に以下を呼び出し。
if ('serviceWorker' in navigator) {
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アプリとしてインストールできるようになっているかと思います。
#Androidでアプリ固定化
余っているAndroidタブレットを使って、常時リスト表示したいと思います。
Androidから同様にブラウザ(Chrome)から開き、メニューから「アプリをインストール」を選択して、アプリとしてインストールしておきます。
まずは、単独で起動でき、リストが表示されるところまで確認しておきます。
次に、アプリを固定化します。
(以降は、Androidのバージョンによって見え方は違うかもしれません。)
Androidの「設定」→「セキュリティ」→「アプリの固定」を選択します。
おそらくOFFになっているかと思いますので、ONにします。
次に、現在実行中のアプリ一覧を表示し、PWAアプリとして起動したものの上にあるアイコンをタッチします。
そうすると、「固定」があるので、それをタッチします。
こんな表示が出てきますので、よく読んで「OK」をタッチします。
これで、常時表示ができました。他のアプリが選択できなくなったと思います!
できました!
アプリ固定を解除して戻りたい時には、ホームボタンと戻るボタンを一緒に長押しすれば戻れます。
#終わりに
続編です。
LINEボットでtodoist連携
以上