5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AlexaとTodoistでやることリスト・お買い物リスト

Last updated at Posted at 2020-11-29

Todoistって、Amazon Echoと連携できるんですね!
しかもWebAPIが充実しているので、さらに連携の輪が広がりそうです。

ちなみに、Todoistは、タスク管理ツールです。一般には、やることリストとか、買い物リストが挙げられますが、メモ的に使えて、かつ、期限を設定して予定を立てたり、失念するのを避けるのに役立ちます。

todoist
 https://todoist.com/

今回作成する全体の構成はこんな感じです。

image.png

すでに、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.png

「はじめる」ボタンまたは右上のサインアップをクリックします。

image.png

ここでは、スマートフォンにインストールしているAmazon Alexaアプリに設定しているアカウントでサインアップし、認証を完了させます。

image.png

これでアカウントが登録されました。

##todoistアプリをインストール

Google Playから、Todoistをインストールします。

image.png

todoistにアカウント登録したときのアカウントでログインします。

image.png

ログインが完了しました。

image.png

##Alexa連携を設定

すでに、スマートフォンに、「Amazon Alexa」がインストールされている前提で進めます。
以降は、Androidでの操作です。

まずは、下の方の「その他」→「設定」→「リスト」を選択します。

image.png

そうすると、Any.do、AnyList、Todoistが表示されていますので、Todoistの右側の⊕をタッチします。

image.png

以下のように表示されるので、「有効にして使用する」ボタンを押下します。

image.png

リストへのアクセス権の確認が表示されますので、「アクセス権を保存」ボタンを押下します。

image.png

そうすると、todoistのサイトに飛んで、Agreeするかが聞かれますので、「Agree」ボタンを押下します。

image.png

これで、連携設定が完了しました。

image.png

もう一度、todoistアプリの方を開いてみましょう。
そうすると、左上の三のマークをタッチし、プロジェクトのところをタッチすると、「Alexa ToDo リスト」というのと、「Alexaの買い物リスト」が増えているのがわかります。

image.png

最後に、この「Alexa ToDo リスト」と「Alexaの買い物リスト」をお気に入りにしておきます。
「プロジェクト」→「プロジェクトを管理」をタッチし、「Alexa ToDo リスト」と「Alexaの買い物リスト」の右側にあるハートマークをタップし、赤色にします。これによって、この2つがお気に入りのプロジェクトとなりました。この意味はあとで、わかります。

image.png

あと、お好みで、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.png

#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.png

App display nameには適当な名前を、App service URLには、これから立ち上げるNode.jsのWebページのURLを指定しておきます。

次に作成したappを選択肢、OAuth redirect URLを指定します。後で作成するのですが、立ち上げるNode.jsのWebページと同じにしておきます。(Single Page Applicationとして実装するため)。最後に、「Save settings」ボタンを押下します。

image.png

その時に表示される、「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サーバに渡して、サーバ側で実施します。

api/controllers/todoist/index.js
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トークンが必要ですので、ユーザ識別子ごとのファイルから取り出して使っています。

api/contollers/todoist/index.js
	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/

さきほどの、ブラウザ側とサーバ側の間の認可コードのやり取りにおいて、ブラウザ側の部分は以下です。

public/js/start.js
        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に保持しておきます。

リスト取得は以下の部分です。

public/js/start.js
        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」ボタンを押下して、ユーザ識別子を設定します。

image.png

さきほど追加した「洗濯」が表示されています。
期限を設定していなかったので、いつか のグループに入っています。

image.png

todoistのAndroidアプリでもいいですし、todoistのWebページからでもどちらでよいですが、「洗濯」タスクの期限を今日にしてみましょう

image.png

10分ごとに、リロードしておいたので、ちょっと?待てば、今日のタスクに移動するかと思います。

PWA化する

詳細は、以下を参考にしてください。今回はPush通知は使っていません。

PWAを試してみよう

以下を追記しています。

index.htmlに以下を追加。

public/index.html
  <link rel="manifest" href="manifest.json">

ページロード直後に以下を呼び出し。

public/js/start.js
        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アプリとしてインストールできるようになっているかと思います。

image.png

#Androidでアプリ固定化

余っているAndroidタブレットを使って、常時リスト表示したいと思います。

Androidから同様にブラウザ(Chrome)から開き、メニューから「アプリをインストール」を選択して、アプリとしてインストールしておきます。
まずは、単独で起動でき、リストが表示されるところまで確認しておきます。

次に、アプリを固定化します。
(以降は、Androidのバージョンによって見え方は違うかもしれません。)

image.png

Androidの「設定」→「セキュリティ」→「アプリの固定」を選択します。
おそらくOFFになっているかと思いますので、ONにします。

次に、現在実行中のアプリ一覧を表示し、PWAアプリとして起動したものの上にあるアイコンをタッチします。
そうすると、「固定」があるので、それをタッチします。

image.png

こんな表示が出てきますので、よく読んで「OK」をタッチします。
これで、常時表示ができました。他のアプリが選択できなくなったと思います!

image.png

できました!

image.png

アプリ固定を解除して戻りたい時には、ホームボタンと戻るボタンを一緒に長押しすれば戻れます。

#終わりに

続編です。
  LINEボットでtodoist連携

以上

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?