20201207のNode.jsに関する記事は5件です。

SAMを使った環境毎のデプロイ方法のまとめ

はじめに

SAMを使ったサーバーレス環境の構築時に、1つのtemplate.yamlを使って、develop, staging, production毎に環境変数を切り替える方法を忘れるため、備忘もかねて記事にしました

目的

SAMのtemplateを使って、各環境毎に変数値を変えてlambdaを実行できるようにする

どうやるのか?

SAMテンプレートの中の、ParametersとMappingsを使うと、一つの環境変数でKey-Value方式で、複数の値を切り替えることができる

実例

まず、ParametersとMappingを記述する

Parameters:
  Environment:
    Type: String
    AllowedValues:
      - development # 開発環境
      - staging # ステージング環境
      - production # 本番環境
    Default: development

Mappings:
 EnvironmentMap:
   development:
     HELLO: 'developmen Hello World'
   staging:
     HELLO: 'staging Hello World'
   production:
     HELLO: 'production Hello World'

それを使いたい時は、以下のように指定すれば、環境変数がKeyになり、Valueが取得できる

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
      Environment:
        Variables:
          SAMPLE_ENV: !FindInMap [ EnvironmentMap, !Ref Environment, HELLO]

動作確認

# ビルド
sam build

# 実行
sam local start-api --parameter-overrides Environment={書き換えたい環境変数}

アクセスして確認する

curl 'http://localhost:3000/hello'

CI/CDをどうするか?

CIに関しては、一つのtemplate.yamlをビルドするだけで良い
CDの時に、 --parameter-overrides を使って、Parametersを書き換えてデプロイするフローになる

❯ sam deploy --guided

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Found
        Reading default arguments  :  Success

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [SAMPLE-ENV]: 
        AWS Region [ap-northeast-1]: 
        Parameter Environment [staging]: ← ここで使いたい環境を指定するだけで良い
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
---- 省略 ---- 

まとめ

かなり雑にまとめてしまいましたが、templateのParametersだけを使う場合だと、環境変数が多くなると管理が大変になるので、Mappingsを活用すると使いたい環境を指定するだけで、セットアップができる

誰かのご参考になれば幸いです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Static Web AppsでGoogle Spread Sheetのデータを取得して表示したメモ

概要

スプレッドシートをWebAPI化するサービスの作り方をみて、簡単に取得できるかもと試してみた。
デプロイ先には簡単に関数をおけそうなAzureStaticWebAppsを選んだ。ベータ版だけど、無料で使えるので。

結果、そう簡単にはいかなかった。ハマリポイントは以下。

Googleのサービスアカウントの認証情報をどう渡すか

  • サンプルの通り、Credentials のJSONファイルを読み込ませようとしたが失敗。
    • → Azure FunctionsにGitHub Actionsからどうデプロイしてどう読み込ませるか分からない。gitに含めるわけにもいかんし。
  • 環境変数で読み込ませることで一応対応できた。
    • \\\になる仕様であった。プログラム側で置換して対応。。。

ソース

作業メモ

サービスアカウントの作成とスプレッドシートの許可

サービスアカウントを作る手順はリンク先を参照。

サービスアカウントの Credentials は以下のようなものが取得できる。

keyFile.json
{
  "type": "service_account",
  "project_id": "プロジェクトID",
  "private_key_id": "省略",
  "private_key": "-----BEGIN PRIVATE KEY-----\n省略\n-----END PRIVATE KEY-----\n",
  "client_email": "hoge@fuga.iam.gserviceaccount.com",
  "client_id": "00000000000",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/fuga.iam.gserviceaccount.com"
}

Spread Sheetの共有許可

image.png

  • Credentialsのclient_emailに書かれているメールアドレスを入力すると、サービスアカウントが出てくるはず。共有設定をしてやる image.png

環境変数の設定

構成から設定できる。が、そのままやると、はまる。はまった。
image.png

この画面で設定すると、以下のようなエラーが発生。
秘密鍵がちゃんとしてないぽい。
ローカルではちゃんと動いたのに。

{
    "library": "PEM routines",
    "function": "get_name",
    "reason": "no start line",
    "code": "ERR_OSSL_PEM_NO_START_LINE"
}

process.envを出力してみた。
環境変数は、以下のようになっている。\\\\ってめっちゃ多いな。

{
  "env": {
    "SERVICE_ACCOUNT_PRIVATE_KEY": "-----BEGIN PRIVATE KEY-----\\\\n省略\\\\n-----END PRIVATE KEY-----\\\\n"

高度な編集で、JSONを見られるので見てみる。
image.png

この段階で、以下のようになっていた。

{
  "SERVICE_ACCOUNT_PRIVATE_KEY": "-----BEGIN PRIVATE KEY-----\\n省略\\n-----END PRIVATE KEY-----\\n"
}

直接編集して、一つは減らすことができたが、それ以上の対応は思いつかず。
ソースコードを以下のように修正して対応した。

+ const private_key = process.env.SERVICE_ACCOUNT_PRIVATE_KEY.replace('\\\\','\\')

  const auth = await google.auth.getClient({
    credentials: {
      client_email: process.env.SERVICE_ACCOUNT_CLIENT_EMAIL,
      private_key,
    },
    scopes: SCOPES
  })

参考

Azure Static Web Apps
スプレッドシートをWebAPI化するサービスの作り方
Googleスプレッドシートをプログラムから操作

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.jsを改めて理解する

前提

Node.jsを理解する前に、いくつか確認しておきます。

そもそもJavaScriptとは

HTMLやCSSで作られたWebページをプログラム(JavaScript)で制御することができるのが特徴です。
通常、クライアントサイドで用いられ、次のような場面でJavaScriptは使われています。
・ブラウザ上で画像をクリックすると拡大する
・メールアドレスを入力フォームに入力した時に、適切な形式で入力されているかチェックをする
...etc

クライアントサイドとサーバサイドって?

クライアントサイド

クライアントサイドプログラムは、Webブラウザ上で動作するプログラムのことです。
代表的なものとしてはJavaScriptがあります。
クライアントサイドプログラムの仕組みとして、クライアント側(ブラウザ)がサーバにリクエストすることによって、Webサーバー上のプログラムがクライアント側に送信され、そのプログラムをWebブラウザが実行するという形です。

サーバサイド

サーバサイドプログラムは、Webサーバー上で動作するプログラムのことです。
代表的なものにPHP、Ruby、Pythonなどがあります。
クライアントからのリクエストに対し、サーバ側でプログラムを実行し、実行結果をクライアント側へ送るという仕組みです。

クライアントサイドとサーバサイドの違い

クライアントサイドプログラムとサーバサイドプログラムの違いは、ブラウザ上で動作するのか、それともサーバ上で動作するのかの違いです。

本題

いよいよNode.jsの説明です。

Node.jsとは

Node.jsとは、一言で言うとサーバサイドのJavaScript実行環境(プラットフォーム)のことで、Node.jsを使用する上で使用する言語はJavaScriptです。

Node.jsとは、通常クライアントサイドで使用するJavaScriptをサーバサイドで実行できるようにしてくれるものです。
サーバサイドで動かせると言うと、PHPやRubyなどと同じように思われるかもしれませんが、厳密には少し違います。
Node.jsは単にサーバサイドでアプリケーションを動かすことができるだけでなく、Node.jsではアクセスされたURLを識別して、そのURLごとに別々の要素を表示させるなど、データベースで行う動作も実装できます。

特徴

  • 大量のデータ処理が可能
  • メモリの消費が少なく動作も速い
  • 処理速度が非常に速い
  • サーバサイドで使える

npmとは

npmは、Node.jsのパッケージ管理ツールです。
PHPにおけるcomposer、Rubyにおけるgemのようなものです。

Node.jsを使う場面とは

メモリ消費量が少ないため、小さな規模の開発・運用時に、他の環境と比べるとよりパフォーマンスを出すことが可能です。同時に大量のトラフィックを捌くことができ、動画配信やチャットアプリなどで使われます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeScriptのアップデート中に EEXIST のエラーが表示されてちょっと詰まったことの解決手順

今回は今までなぜかグローバルのTSのアプデができずに無視してたところついに立ち向かった(面倒くさがり)
そのせいでバージョン 1.5.3 に甘んじていました。

npm i -g typescript@latest

で最新版のTypeScriptにアプデしようとしたら案の定エラーが表示。

npm ERR! code EEXIST
npm ERR! syscall symlink
npm ERR! path ../lib/node_modules/typescript/bin/tsc
npm ERR! dest /Users/myuser/.nodenv/versions/12.16.0/bin/tsc
npm ERR! errno -17
npm ERR! EEXIST: file already exists, symlink '../lib/node_modules/typescript/bin/tsc' -> '/Users/myuser/.nodenv/versions/12.16.0/bin/tsc'
npm ERR! File exists: /Users/myuser/.nodenv/versions/12.16.0/bin/tsc
npm ERR! Remove the existing file and try again, or run npm
npm ERR! with --force to overwrite files recklessly.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/myuser/.npm/_logs/2020-12-06T17_00_41_920Z-debug.log

nodenvを使っている人が遭遇したことあるかもしれないエラー。npm installなどでも遭遇する人がいるらしいが詳しくは見ていない。

再インストールであっさり解決?

結果としては

npm ERR! Remove the existing file and try again, or run npm

これの言う通りにして解決した。このエラーで言うところの existing file は

npm ERR! dest /Users/myuser/.nodenv/versions/12.16.0/bin/tsc

のことだから、直接 tsc に対して " rm -rf tsc " で削除した。
これでOK。なはず。

typescriptアップデートの再チャレンジ

しかしここで再びアプデコマンドを打ったところなんとまた同じエラーが発生。
今度は、「tscフォルダ」ではなく「tsserverフォルダ」に対して怒られた。そのため tsc と同じように削除した。

3度目の正直でついに

その後、再び

npm i -g typescript@latest

で無事に最新版にアプデできた。バージョンは 4.1.2。
1.5.3から4.1.2はなかなかの跳躍。

npm ERR! syscall symlink ってなんだ

タイトルにもある通り、つまづいた理由はこのエラーがよくわからなくて放置。ちゃんと調べてなかったのでよくわからなかった笑
調べたところ symlink とはショートカットのディレクトリ名を意味しており、プログラミングとは関係なく日常でもショートカットのファイルなどを作成する機会があると思うがそれと同じものになるとの理解であってるかと。

つまりsymlinkによってAというショートカットのディレクトリ(中身は入っていない)を呼び出すことで、本体であるBを呼び出すことができるらしい。

今回のケースで言えば具体的には
Aがこれ(npm ERR! path ../lib/node_modules/typescript/bin/tsc)
Bがこれ(npm ERR! dest /Users/myuser/.nodenv/versions/12.16.0/bin/tsc)
に当たると思われる。というか path は文字通りの意味で、 dest が目的地であり本体のことを指しているはず。

そのため今回は本体のフォルダを削除することで再インストールできたということになります。
nodenvの場合は各nodeバージョン毎に入っているライブラリバージョンも異なるためショートカットという形で呼び出しているのかなと思います。

問題は解決できたけれど...

しかし結局のところなぜエラーが生じたのかわからかった。。
他のライブラリやモジュールでも同じエラー生じるはずなので。

npm i -g typescript@latest

バージョンが古すぎてそのまま上書きアプデできなかったとか??

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LINEボットでtodoist連携

todoistにやることリストを追加したり、今日のやることリストを取得してくれるLINEボットを作成します。
前回の投稿「 AlexaとTodoistでやることリスト・お買い物リスト 」の応用です。

image.png

今回作成するのは、Node.jsで作成するLINEボットサーバです。
図の点線矢印は、最初の設定のときのみで、それが終わってしまえば、あとは、何回でもLINEアプリからtodoistの操作ができるようになります。

とりあえず、LINEアプリからは以下ができるようにします。これ以外は、直接todoistアプリから操作してください。

・タスクの新規登録、および担当者のアサイン、期限の設定
・今日、明日、明後日以降、期限切れのタスクの一覧を取得
・タスクの詳細を表示
・タスクの完了を設定

毎度の通り、ソースコード一式をGitHubに上げておきました。

poruruba/line-todoist
 https://github.com/poruruba/line_todoist

(修正) 2020/12/08
todoist認証において、stateに推測されにくい乱数を指定するようにしました。コメントありがとうございました。

LINEボットの設定

LINEデベロッパーコンソールから登録します。

LINEデベロッパーコンソール
 https://developers.line.biz/console/

プロバイダを選択または新規作成したのち、チャネル設定の新規チャネルを選択します。

image.png

チャネルの種類は、Messaging APIです。

image.png

適当に入力し、利用規約に同意して、「作成」ボタンを押下します。

image.png

作成後に表示されるチャネル基本設定にあるチャネルシークレットとMessaging API設定にあるチャネルアクセストークン(長期)の発行ボタンで生成されるチャネルアクセストークン(長期)を覚えておきます。

また、Messaging API設定の応答メッセージは無効に変更しておきます。グループ・複数人チャットへの参加を許可するも有効にしておきます。

Node.jsの立ち上げ

GitHubからZIPダウンロードしてください。
 https://github.com/poruruba/line_todoist

> unzip line_todoist-master.zip
> cd line_todoist
> mkdir data
> mkdir data\line-todoist
> mkdir cert
> npm install
> node app.js

HTTPSである必要があるため、certフォルダにSSL証明書を格納します。ファイル名は、app.jpを参照してください。

まずは、LINEボットとして応答できるようにするためには、最低限以下の記載があればよいです。

api/controllers/line-todoist/index.js
'use strict';

const config = {
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN || '【LINEチャネルアクセストークン(長期)】',
  channelSecret: process.env.LINE_CHANNEL_SECRET || '【LINEチャネルシークレット】',
};

const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/';

const line = require('@line/bot-sdk');
const LineUtils = require(HELPER_BASE + 'line-utils');
const app = new LineUtils(line, config);

app.message(async (event, client) =>{
  console.log(event);
  var text = event.message.text

  const echo = { type: 'text', text: text + 'だよ' };
  return client.replyMessage(event.replyToken, echo);
});

exports.fulfillment = app.lambda();

Swaggerファイルには以下を最低限記載します。

api/controllers/line-todoist/swagger.yaml
swagger: '2.0'
info:
  version: 'first version'
  title: Lambda Laboratory Server

paths:
  /line-todoist:
    post:
      x-handler: fulfillment
      parameters:
        - in: body
          name: body
          schema:
            $ref: "#/definitions/CommonRequest"
      responses:
        200:
          description: Success
          schema:
            $ref: "#/definitions/CommonResponse"

【LINEチャネルアクセストークン(長期)】と【LINEチャネルシークレット】のところに、先ほど覚えておいた値を付記しておきます。

細かな処理は、api/helpers/line-utils.js で処理させています。以下のnpmモジュールを使っています。

line/line-bot-sdk-nodejs
 https://github.com/line/line-bot-sdk-nodejs

LINEボットのWebhook設定

LINEアプリでの発話をnode.jsサーバに通知してもらうためにWebhookの設定をします。
LINEデベロッパーの、Messagin API設定に、Webhook設定があります。
ここに、立ち上げたNode.jsサーバのURLを指定します。以下のような感じです。

 https://【Node.jsサーバのホスト名】/line-todoist

これで、Webhookの利用をOKにして、検証ボタンを押下すると、「成功」と表示されるかと思います。
ついでに、同じページに表示されているQRコードから、LINEアプリでこのLINEボットを友達として登録しておきましょう。

LINEとtodoistの連携

LINEとtodoistの連携は、このLINEボットが担います。
連携のための設定情報は、Node.jsサーバの data/line-todoist/ フォルダにJSONファイルとして保存します。
目標とするConfigファイル名と中身は以下の通りです。

ファイル名:user-【LINEのユーザID】 またはgroup-【LINEのグループID】
ファイルの中身:
{
  "token": {
    "access_token": "【todoistのAPIトークン】",
    "token_type": "Bearer"
  },
  "apikey": "【本ファイルをアクセスするためのAPIキー】",
  "project_id": 【todoistの対象プロジェクトID】
}

まず、ファイル名がLINEのユーザIDまたはグループIDとなっています。
LINEボットをお友達にしたのち直接LINEボットと会話する場合と、LINEのグループにLINEボットを招待して会話する場合の両方があるためです。対象となるLINEのIDは、前者がユーザID、後者がグループIDとなります。

それらのLINEのIDとtodoistをどうやって結びつけるかというと、いったんLINEボットからLINEのIDとともに、これから立ち上げるWebページに飛ばし、そこでユーザによりtodoistと認証した結果をLINEボットに戻すことで、行っています。

todoistのアプリ登録

todoistで認証できるようにアプリ登録します。

Todoist App Management
 https://developer.todoist.com/appconsole.html

「Create a new app」ボタンを押下します。

image.png

大事なのは、OAuth redirect URLの部分です。
さきほど立ち上げたNode.jsサーバのURLを指定します。

 https://【Node.jsサーバのホスト名】/admin/index.html

public/admin/フォルダに、静的ページを用意しておきました。
todoist認証完了後、todoistの認可コード付きでこのページに戻るようになります。

Client IDとClient secretも使うので覚えておきます。

流れはこんな感じです。

①LINEアプリからtodoist認証ページへ切り替える
以下の部分です。
todoistのAPIトークンがすでにある場合は、直接静的ページ/admin/index.htmlに飛びますが、ない場合は、todoistの認証URLを返しています。

api/controllers/line-todoist/index.js
      var message;
      if( !conf.token ){
        // 新規登録
        conf.state = sourceId + '_' + Buffer.from(crypto.randomBytes(12)).toString('hex');
        await writeConfigFile(sourceId, conf);
        message = {
          type: "template",
          altText: "設定",
          template: {
            type: "buttons",
            text: "まだTodoistが設定されていません。",
            actions: [
              {
                type: "uri",
                label: "設定ページ",
                uri: "https://todoist.com/oauth/authorize?client_id=" + TODOIST_CLIENT_ID + "&scope=data:read_write&state=" + conf.state + "&openExternalBrowser=1"
              }
            ]
          }
        }
      }else{
        // 登録済み
        message = {
          type: "template",
          altText: "設定",
          template: {
            type: "buttons",
            text: "すでにTodoistが設定されています。",
            actions: [
              {
                type: "uri",
                label: "設定ページ",
                uri: ADMIN_PAGE_URL + "?openExternalBrowser=1"
              }
            ]
          }
        }
      }
      return client.replyMessage(event.replyToken, message);

上記処理は、LINEチャットから以下を入力することで起動します。

/todoist config

ちなみに、「/(スラッシュ)」で続くチャットのみ、今回作成するLINEボットで扱うようにしています。

②認可コードをNode.jsサーバにわたす。

todoistのユーザ認証が完了すると、認可コードとともに、静的ページに戻ってきます。
静的ページでは、LINEのID、todoistの認可コード、パスワードとして使うapikeyをNode.jsに渡します。以下の部分です。

public/admin/js/start.js
        if( searchs.code ){
            // todoist認証後の認可コード
            var state = searchs.state;
            var code = searchs.code;
            history.replaceState(null, null, '.');
            var apikey = prompt("API Keyを指定してください。");
            if( !apikey )
                return;

            // todoistのトークン設定
            var param = {
                code: code,
                state: state
            };
            do_post_apikey(base_url + '/line-todoist-callback', param, apikey)
            .then(json =>{
                this.apikey = apikey;
                this.sourceId = json.sourceId;
                Cookies.set("line_apikey", this.apikey, { expires: EXPIRES });
                Cookies.set("line_sourceId", this.sourceId, { expires: EXPIRES });

                this.get_config();
            });
        }else{

③認可コードからAPIトークンを取得する

まずは、ブラウザから、POST呼び出しを受け付けるために、HTTP POSTエンドポイントを作成します。
以下の部分です。

api/controllers/line-todoist/index.js
exports.handler = async (event, context, callback) => {
  var body = JSON.parse(event.body);

if( event.path == '/line-todoist-callback' ){
・・・
  }
};

また、swagger.xmlにエンドポイントを定義します。

api/controllers/line-todoist/swagger.yaml
paths:
・・・
  /line-todoist-callback:
    post:
      security:
        - apikeyAuth: []
      parameters:
        - in: body
          name: body
          schema:
            $ref: "#/definitions/CommonRequest"
      responses:
        200:
          description: Success
          schema:
            $ref: "#/definitions/CommonResponse"

で、認可コードを受け取って、todoistのWebAPIを使ってAPIトークンの取得する処理は以下の部分です。

api/controllers/line-todoist/index.js
const TODOIST_CLIENT_ID = process.env.TODOIST_CLIENT_ID || "【todoistのClient ID】";
const TODOIST_CLIENT_SECRET = process.env.TODOIST_CLIENT_SECRET || "【todoistのClient secret】";

・・・
    // トークン取得処理
    var apikey = event.requestContext.apikeyAuth.apikey;
    var params = body.state.split('_', 2)
    var sourceId = params[0];

    var conf = await readConfigFile(sourceId);
    if( !conf )
      throw "invalid state";

    if( conf.state != body.state )
      throw "invalid state";

    // トークン取得呼び出し
    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 );

    // sourceIdのConfigファイル更新
    if( !conf.apikey ){
      conf.apikey = apikey;
    }else
    if( conf.apikey != apikey ){
        throw 'invalid apikey';
    }

    conf.token = json;
    await writeConfigFile(sourceId, conf);

    return new Response({ sourceId: sourceId });

④LINEボットがタスク登録するtodoistプロジェクトの選択

静的ページには、todoistに作成してあるプロジェクト一覧が表示されているかと思います。
そのうちから、LINEボットが登録する先のプロジェクトをセレクトボックスから選択して、「Select Project」ボタンを押下します。

image.png

以下の部分が呼ばれて、project_idがConfigファイルに設定されます。

api/controllers/line-todoist/index.js
  if( event.path == '/line-todoist-set-config' ){
    // project_id設定要求
    var apikey = event.requestContext.apikeyAuth.apikey;
    var sourceId = body.sourceId;

    // sourceIdのConfigファイル更新
    var conf = await readConfigFile(sourceId);
    if( conf.apikey != apikey )
      throw 'apikey mismatch';

    conf.project_id = body.project_id;
    await writeConfigFile(sourceId, conf);

    return new Response({});

これで、Configファイルに必要な値が埋まりました。

タスク(やることリスト)の登録

LINEチャットで以下のように入力します。

/todo add タスク名

以下の部分が呼ばれます。

api/controllers/line-todoist/index.js
const Todoist = require('todoist').v8;

・・・

  if( command.cmd == 'todo' && command.ope == 'add' ){
    // タスクの追加

    // sourceIdからConfigファイル取得
    var conf = await readConfigFile(sourceId);
    if( !conf || !conf.token )
      return client.replyMessage(event.replyToken, { type: 'text', text: 'todoistが設定されていません。' });

    // 指定プロジェクトにタスクの追加
    const todoist = Todoist(conf.token.access_token);
    const newItem = await todoist.items.add({ content: command.param, project_id: conf.project_id });

    var item_id = newItem.id;
    return client.replyMessage(event.replyToken, make_item_suggestion(item_id, "やることリストに追加しました。"));

Configファイルに保存しておいたtodoistのAPIトークンを使ってタスクを登録しています。
npm node-todoistを利用していますが、使い方は以下を参照してください。

romgrk/node-todoist
 https://github.com/romgrk/node-todoist

「やることリストに追加しました」と返答がかえってきました。
合わせて、続けて設定できるように「アサイン設定」「期限(日付)」がサジェスチョンされてます。
「アサイン設定」は後述の処理を動かすためのポストバック、「期限(日付)」はユーザに日付を選択してもらうための日時選択アクションをサジェスチョンに設定しています。

image.png

アサイン設定のサジェスチョンを選択すると以下の処理が走ります。

api/controllers/line-todoist/index.js
  if( data[0] == 'todo_asign_select'){
    // アサイン選択要求(todo_asign_select,item_id)
    var item_id = parseInt(data[1]);

    // todoist.sync()呼び出し
    const todoist = Todoist(conf.token.access_token);

    // タスクの検索
    await todoist.sync();
    var task = todoist.items.get().find(item => item.id == item_id);

    const collaborators = todoist.sharing.collaborators();
    const collaboratorStates = todoist.state.collaborator_states;
    // 指定プロジェクトの共有ユーザリストの抽出
    var members = collaboratorStates.filter(item => item.project_id == conf.project_id );

    if( members.length > 0){
      var message = {
        type: 'text',
        text: "アサインする人を選択してください。",
        quickReply: {
          items: []
        }
      };
      members.forEach(member =>{
        const user = collaborators.find(item => member.user_id == item.id);
        const param = {
          type: "action",
          action: {
            type: "postback",
            label: user.full_name,
            data: "todo_asign," + item_id + ',' + user.id,
            displayText: user.full_name
          }
        }
        message.quickReply.items.push(param);
      });

      return client.replyMessage(event.replyToken, message);
    }else{
      return client.replyMessage(event.replyToken, make_item_suggestion(item_id, "アサインできる人がいません。", (task.due) ? task.due.date : undefined));
    }

対象プロジェクトの共有者をリストアップし、アサイン候補者としてサジェスチョンを表示してます。

image.png

期限(日付)のサジェスチョンを選択した場合、以下の処理が動きます。ユーザが日付を選択した後に呼ばれます。

api/controllers/line-todoist/index.js
  if(data[0] == 'todo_duedate'){
    // 期限(日付)設定要求(todo_duedate,item_id)
    var item_id = parseInt(data[1]);
    const todoist = Todoist(conf.token.access_token);
    todoist.items.update({ id: item_id, due: { date: event.postback.params.date, lang: 'ja' }});

    return client.replyMessage(event.replyToken, make_item_suggestion(item_id, "変更しました。", event.postback.params.date));

image.png

さっそく、todoistアプリから覗いてみましょう。
上記のタスクが登録されているのではないでしょうか。

image.png

今日のタスクの一覧取得

タスク一覧を取得します。
操作しやすいように、LINEアプリにチャットとして「/」を入力することで、「今日」「明日」「明後日以降」「期限切れ」「いつか」を選択できるサジェスチョンを表示し、それを選択してもらうと使いやすいかと思いました。

「/」がチャットされたときには以下が動きます。

api/controllers/line-todoist/index.js
  if( command.cmd == 'default' ){
    return client.replyMessage(event.replyToken, make_tasksearch_suggestion());

そして、ユーザによりサジェスチョンを選択されると、以下が動きます。

api/controllers/line-todoist/index.js
  if( data[0] == 'todo_today' || data[0] == 'todo_tommorow' || data[0] == 'todo_other' || data[0] == 'todo_someday' || data[0] == 'todo_expire'){
    // タスクリスト取得要求(todo_xxx)

    // todoist.sync()呼び出し
    const todoist = Todoist(conf.token.access_token);
    await todoist.sync();
    const collaborators = todoist.sharing.collaborators();
    var items = todoist.items.get();
    if( conf.project_id )
      items = items.filter(item => item.project_id == conf.project_id); // 対象プロジェクトにフィルタリング

    // 境界時間の算出
    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();
    var aftertomorrow = new Date(); // 明日の開始
    aftertomorrow.setHours(0, 0, 0, 0);
    aftertomorrow.setDate(aftertomorrow.getDate() + 2);
    var aftertomorrowTime = aftertomorrow.getTime(); // 明後日の開始

    var target_list;
    var title;
    if( data[0] == 'todo_today' ){
      // 期限が今日の始めから明日の始め
      title = "今日";
      target_list = items.filter( item => item.due && Date.parse(item.due.date) >= todayTime && Date.parse(item.due.date) < tomorrowTime );
    }else
    if( data[0] == 'todo_tommorow' ){
      // 期限が明日の初めから明後日の始め
      title = "明日";
      target_list = items.filter( item => item.due && Date.parse(item.due.date) >= tomorrowTime && Date.parse(item.due.date) < aftertomorrowTime );
    }else
    if( data[0] == 'todo_other' ){
      // 期限が明後日の始め以降
      title = "明後日以降";
      target_list = items.filter( item => item.due && Date.parse(item.due.date) >= aftertomorrowTime );
    }else
    if( data[0] == 'todo_expire' ){
      // 期限が今日の始め以前
      title = "期限切れ";
      target_list = items.filter( item => item.due && Date.parse(item.due.date) < todayTime );
    }else{
      // 期限が未設定
      title = 'いつか';
      target_list = items.filter( item => !item.due );
    }
    console.log(target_list);

    var message = make_todolist_message(title + "のやることリスト", target_list, collaborators);
    return client.replyMessage(event.replyToken, message);

例えば、期限を明日に設定していると、明日を選択すると以下のように、リストの中の1つとして表示されています。

image.png

サジェスチョンに、項目番号をいれておきました。それを選択すると、そのタスクの詳細が表示されるようにしました。

image.png

以下の部分です。

api/controllers/line-todoist/index.js
  if( data[0] == 'todo_detail'){
    // タスクの詳細表示要求(todo_detail,item_id)
    var item_id = parseInt(data[1]);

    // todoist.sync()呼び出し
    const todoist = Todoist(conf.token.access_token);
    await todoist.sync();
    var items = todoist.items.get();
    const collaborators = todoist.sharing.collaborators();
    var notes = todoist.notes.get();

    // タスクの検索
    var task = items.find(item => item.id == item_id );

    var message = make_tododetail_message(task, collaborators, notes);
    return client.replyMessage(event.replyToken, message);

もし、コメントを残しているようであれば、それも表示するようにしています。あと、ラベルも表示するようにしています。

「完了する」というサジェスチョンも用意しておきました。言葉どおり、タスクを完了状態にします。
以下の処理が動きます。

api/controllers/line-todoist/index.js
  if( data[0] == 'todo_complete'){
    // タスク完了要求(todo_complete,item_id)
    var item_id = parseInt(data[1]);
    const todoist = Todoist(conf.token.access_token);
    todoist.items.complete({ id: item_id} );

    return client.replyMessage(event.replyToken, { type: 'text', text: "完了にしました" });

終わりに

ちょっと説明が少なく、わかりにくかったかもしれません。
機能拡張の機会があれば、その時にもう少し補足しようかな。。。

以下も参考にしてください。

 AlexaとTodoistでやることリスト・お買い物リスト
 Dialogflowと連携してLINE Botを作る
 LINE Beaconを自宅に住まわせる

以上

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む