Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all 8898 articles
Browse latest View live

Vue.jsのコンポーネントのimport文をdynamic importに変換するcliコマンドを作りました

$
0
0

Vue.jsのコンポーネントのimportをdynamic importに変換するcliコマンドを作りました。
特定のディレクトリ配下のvueファイルを全てdynamic importに変換します。

ソースはこちらで公開しています。
https://github.com/harhogefoo/dynamic-import-converter

通常のcomponentのimport文
<template><div><hoge/><piyo/></div></template><script>importHogefrom"@/components/Hoge.vue"importPiyofrom"@/components/Piyo.vue"exportdefault{components:{Hoge,Piyo}}</script>
dynamic_importに変換
<template><div><hoge/><piyo/></div></template><script>exportdefault{components:{Hoge:()=>import("@/components/Hoge.vue"),Piyo:()=>import("@/components/Piyo.vue")}}</script>

使い方

$ yarn global add dynamic-import-converter
or
$ npm install-g dynamic-import-converter

$ dynamic-import-converter ./Vueファイルが格納されたディレクトリのパス/

バグ、改善要望などは、リポジトリのissueまで!
https://github.com/harhogefoo/dynamic-import-converter/issues


Slackで匿名で投稿できるチャンネルを作ろうとしたら少しだけ苦労した話

$
0
0

はじめに

研究室でSlackを導入してから2年くらい経ちました。
話題でチャンネルを分けれるので非常に便利です。

雑談用のチャンネルもあるのですが、特定の人ばかり話していて盛り上がりに欠けます。
「匿名ならみんな発言してくれるかも」と思ったのがきっかけで、匿名用のチャンネルを作りました。

Googleで検索したら3年前のQiitaの以下の記事がヒットしました。
「超簡単にSlackで匿名の意見を投稿できるようにする」 @shibukk

「超簡単」とありますが、記事通りにやっても上手くいかず、少しだけ苦労しました。
BotKitのバージョンが上がって中身が変わっていたのが原因でした。

修正した部分を自分の備忘録としてまとめておきます。

実行環境はUbuntu16.04。
node.js, javascriptが動けばどこでも大丈夫なはずです。

SlackのBotの取得

ここは本家と一緒です。
「Botを追加する」

ここをクリックすると、現在ログインしているワークスペースでBotを作ることができます。
(「ワークスペースの管理者」じゃないと作れないかも・・・)

今回はBotの名前を"anonymous_bot"にします。

API Tokenを後で使うのでコピーしておいてください。
漏れると悪用される恐れがあるので扱いには注意してください。(もしもの場合は再発行してください)

チャンネルIDをの取得

ここから本家と手順が少し変わります。

以下のURLから匿名で会話したいチャンネルのIDを探してください。
https://slack.com/api/channels.list?token=さっき取得したAPI_Token

ちなみにチャンネル以外にも、特定のユーザーやプライベートチャンネルもできます。
詳しくは本家を参考にしてください。

BotKitのインストール

次にBotKitをインストールします。
以下の手順に従ってください。

//ディレクトリの作成
$ cd ~
$ mkdir slack_anonymous_bot 
$ cd slack_anonymous_bot

//botkitをclone
$ git clone https://github.com/howdyai/botkit.git
$ cd botkit

//branchを移動
$ git checkout origin/legacy 

//インストール
$ npm install

//もしインストールできなかったら・・・
$ npm audit fix
$ npm install

~/botkit/example/slack_bot.js が生成されていれば成功です。

プログラムの作成

以下の手順に従ってください。

//ディレクトリの移動
$ cd ~
$ cd slack_anonymous_bot/botkit/

//anonymous_bot.jsの作成
$ touch anonymous_bot.js

anonymous_bot.jsに以下のコードをコピペしてください。

anonymous_bot.js
varBotkit=require("./lib/Botkit.js");//パス注意varos=require("os");varcontroller=Botkit.slackbot({debug:true,});varbot=controller.spawn({token:"先ほど取得したAPI_TOKEN"}).startRTM();controller.on("direct_message",(bot,message)=>{varnow=newDate();//時刻の取得varuser_name="名無しさん: "+now.getFullYear()+"/"+(now.getMonth()+1)+"/"+now.getDate()+"/ "+now.getHours()+":"+now.getMinutes()+":"+now.getSeconds();bot.reply(message,"匿名で投稿しました.");bot.startConversation({channel:"先ほど取得したチャンネルID"},(err,convo)=>{varsend_message={type:"message",channel:"先ほど取得したチャンネルID",text:message.text,username:user_name,thread_ts:null,reply_broadcast:null,parse:null,link_names:null,attachments:null,unfurl_links:null,unfurl_media:null,icon_url:null,icon_emoji:":robot_face:",as_user:true}convo.say(send_message);});});

スクリプトの実行方法

//デバッグしたいときは
$ cd ~/slack_anonymous_bot/botkit
$ node anonymous_bot.js

//通常時
$ cd ~/slack_anonymous_bot/botkit
$ forever start slack_bot.js
$ forever stop slack_bot.js ←止めたい場合

実行結果

Botが起動している間は、Botの名前の横の○が緑色になります。
Screenshot from 2019-12-15 02-18-28.png

anonymous_botにDMを送ると一瞬で返事が来て、
Screenshot from 2019-12-15 02-17-33.png

チャンネルIDを登録したチャンネルで匿名で投稿されます。(チャンネルのアプリにanonymous_botを追加するのをお忘れなく)
Screenshot from 2019-12-15 02-15-26.png

終わりに

無事に匿名で発言できるようになりました。
研究室のメンバーにも好評でした。
(若干チャンネルが荒れましたが・・・)

続・実録 Node-REDノード作成 24時

$
0
0

こんにちは、ポキオです。

IoTLT Advent Calendar 2019enebular Advent Calendar 2019の15日目の記事です。
手抜きです、ごめんなさい。

ポキオ Node-RED ノード作成

tl;dr

  • 京急ノードを作ってみました
  • Node-REDのノードライブラリに反映されるまで時間がかかることがあります
  • 一度公開したあとも、ノードのメンテは必須です
  • Node-RED、だぁいすき!

話の発端:Node-RED向けの京急ノードを作りたかった

この記事をご覧の諸兄姉にとっては釈迦に説法かもしれませんが、Node-REDはグラフィカルなUIで、ノンコーディングでもプログラミングができてしまう、素晴らしいツールでございます。

Node-REDで部品として動くパーツであるノードは色々準備されていたり、ノードライブラリでも種々のノードが公開されていて、Node-REDの可能性を無限に広げてくれています。

ただし、なかなか日本向けのノードがないのに玉に瑕で、だからこそ自分でノードを作って公開しようというモチベーションが湧いてきたわけです。とりわけ、私は京急が大好きなので、京急にまつわるノードを作ろうと思い立ったわけです。

で、作ったのがこれです。

node-red-contrib-keikyu
https://flows.nodered.org/node/node-red-contrib-keikyu

image.png

京急の運行情報が取得できる、すばらしいノードに仕上がっています(笑)

問題①:なかなか公開できない!

詳しい経緯はこちらで公開していますが、公開作業をしている段階で一つの問題にぶち当たりました。

image.png

ノードがノードライブラリで公開されるまで、やることは色々あるわけですが、とりあえずコーディングやnpmjs.comでの公開までは順調に進んだわけです。

image.png

ただ、npmjs.comでnpmモジュールとしてノードを公開したあと、なかなかノードライブラリに反映されないという問題に陥りました。通常は数時間で反映されるわけなのですが、そのときは全く反映されませんでした。

よくある原因としては、

  • package.jsonのkeywordsに「node-red」がない
  • プレフィックス「node-red-contrib-」を用いて命名されてない
  • README.mdがない
  • LICENSEがない
  • npmで公開されてない
  • npm versionしたあとにgit pushし忘れてる

などなどありますが、それはすべてOK。結局、npmjs.comから一度ノードを削除して、再度公開しました・・・。削除後は24時間経たないと再公開できないという制限がありましたが、なんとか再公開後にノードライブラリに反映されました・・・。もし同じようなことで困っている方がいらっしゃいましたら、お試しくださいませ。

問題②:京急ノードが動かなくなった!

公開して、一安心してたんですが、ある日突然ノードが使えなくなっていました。

image.png

結論から言ってしまえば、京急の運行情報ページのレイアウトが更新されていて、いままで使っていたパースのロジックがワークしなくなり、運行情報の取得ができなくなっていました・・・。

image.png

もともとパースのロジックは、かなりのクソコードだったので致し方ないとおもいつつ、とりあえずコードを修正して、再度公開しました。

image.png

また、二度と同じようなことがないように、自分が作ったノードが正しく動作しているか、enebular上でCIのように定期的に動かし、ノードの状態を監視する仕組みを作りました。

image.png

こんな感じでステータスが表示されます。これで完璧ですね!(笑)

現在平常通り運転しています。

というわけで、今年もいろいろとお世話になりました。
来年もポキオと京急を何卒よろしくおねがいします!

宣伝

ポキオとドライブをしながらIoTとかTechな話をする、ポキオ・カープール

image.png

ぜひご覧ください!
一緒にドライブしながら喋ってくれる方も大募集中です!

Node.js + Expressで稼働中のサービスにTypeScriptを導入する

$
0
0

Node.js + Expressで稼働中のサービスにシームレスにTypeScriptを導入する

GlobalMobilityService株式会社でバックエンドエンジニアとして活動しているEkeMinusYouです。

弊社で開発しているプラットフォームでは、Node.jsを全面的に採用しているのですが、TypeScriptで開発できる環境を整えたので、手順やハマったことについてご紹介します。

概要

Node.js + Express で稼働しているシステムがあり、これをTypeScriptで書けるようにしました。

TypeScriptにしたかった理由としては、以下の点が挙げられます。

  • 保守性が高い良質なコードを生み出していきたい
  • チーム規模拡大に向けて、型を使えるようにしたかった
  • ただTypeScript書きたかった

なお、TypeScriptを導入するにあたり以下の環境も手を入れました。

  • テストも TypeScriptで開発できるようにした
    • Jestを採用しているので、ts-jestを導入
  • 今まで使用していたESLint環境もTypeScriptに対応させた

なるべく、ストレスなくTypeScriptで開発できる環境を用意するのが目標です。

TypeScript導入

typescriptパッケージのインストール

typescriptをインストールします。本番環境では不要なパッケージなので、devにします。

$ npm i --save-dev typescript

tsconfig.jsonを生成

tsconfig.jsonはコンパイルオプションを詰め込んだファイルです。

以下のコマンドで生成します。

$ npx tsc --init

tsconfig.jsonをチューニング

デフォルトの設定から変更していきます。

target

targetは変換後のECMA Versionを指定できるようです。

導入するサービスはnode12なので、ES2019にしました。(参考: https://node.green/)

"target":"ES2019",/*SpecifyECMAScripttargetversion:'ES3'(default),'ES5','ES2015','ES2016','ES2017','ES2018','ES2019'or'ESNEXT'.*/

allowJs

allowJsを有効にすることで、既存のJSもコンパイル対象に含めます。

既存のJSファイルもそのまま動かしたいので、有効にします。

"allowJs":true,/*Allowjavascriptfilestobecompiled.*/

outDir

outDirで出力先フォルダを指定します。 distに設定します。

"outDir":"./dist",/*Redirectoutputstructuretothedirectory.*/

strict

strictを無効にしました。これがデフォルトの有効だと以下の設定がまとめて有効になりますが、 alwaysStrictこれが罠でした。。。

//"noImplicitAny":true,/*Raiseerroronexpressionsanddeclarationswithanimplied'any'type.*///"strictNullChecks":true,/*Enablestrictnullchecks.*///"strictFunctionTypes":true,/*Enablestrictcheckingoffunctiontypes.*///"strictBindCallApply":true,/*Enablestrict'bind','call',and'apply'methodsonfunctions.*///"strictPropertyInitialization":true,/*Enablestrictcheckingofpropertyinitializationinclasses.*///"noImplicitThis":true,/*Raiseerroron'this'expressionswithanimplied'any'type.*///"alwaysStrict":true,/*Parseinstrictmodeandemit"use strict"foreachsourcefile.*/

alwaysStrict は有効にすると、生成されるJSファイルに必ず 'use strict'が付与されるという便利オプションです。 しかし、予想外だったのが、allowJsが有効になっている場合は変換元のファイルがTSだけでなくJSの場合も対象になっていたことです。

そのため、'use strict'がもともと付いていなかったJSファイルまで、変換後に勝手に 'use strict'付与されてエラーが発生してしまいました…

このときコンパイル時にエラーが起きず、実行する段階でしか補足できないので、影響範囲が想定しにくいです。そもそも、そんなコードがあるのが悪いという話はある

なので、なくなくstrictは無効にしました:cry:

bin/wwwの編集

変換先のapp.jsを使用したいので以下のように変更します。

- var app = require('../app');
+ var app = require('../dist/app');

package.jsonにビルドするスクリプトを追加

ビルドするスクリプトと、(開発しやすいように)watchとcleanを追加します。

"scripts":{"build":"tsc","build:watch":"tsc --watch","clean":"rm -Rf dist",}

これで $ npm run buildでビルドできるようになりました :clap:

実際にTypeScriptで開発したときのTIPS

Expressの型定義を導入する

expressモジュールには型定義が含まれていないので、型定義を別途インストールします。

$ npm i --save-dev @types/express

こんな感じで書けるようになります。

importExpressfrom'express';constrouter=Express.Router();router.use('/',async(req:Express.Request,res:Express.Response,next:Express.NextFunction)=>{next();});

req.userの型を有効にする

認証時にreq.userにユーザー情報に関する幾つかの値を格納しているのですが、 Express.Requestはそこまで考慮してくれません。(当然ですが)

そのため、 Express.Requestを継承する型を作成し、それを使うようにしました。

importExpressfrom'express';exportinterfaceOriginalRequestextendsExpress.Request{user:{id:number,name:string,}}

以下のように使用します。

importExpressfrom'express';import{OriginalRequest}from'../types/request';constrouter=Express.Router();router.get('/',async(req:OriginalRequest,res:Express.Response)=>{constname=OriginalRequest.user.name;returnreq.send(name).end();});

テストもTypeScriptで書けるようにする

TypeScriptで開発できるようにしたなら、テストもTypeScriptで書きたいので導入しました。(shirubaさんが頑張ってくれた)

関連パッケージの導入

Jestを採用しているのですが、ts-jestを導入すればTypeScriptが書けるようです。また、jestの型 @types/jestもインストールします。

$ npm i --save-dev ts-jest @types/jest

jest.config.jsでts-jestを有効にする

presetが用意されているので、特別な設定が必要なければこれを使用するのが楽だと思います。

tsは変換して、jsはそのままテストする 'ts-jest/presets/js-with-babel'を有効にしました。

変換時には同じ階層のtsconfigを自動的に使用してくれるようです。

module.exports={preset:'ts-jest/presets/js-with-babel',}

CircleCIで実施するときハマったところ

CIでPUSH時の自動テストを実行しているのですが、ts-jestを導入したら何故かテストができないという現象が起こりました。(ローカルでは大丈夫だったのに)

実施ログは以下が出ており、一つもテストできていませんでした。

Too long with no output (exceeded 10m0s)

色々と調べたところ、JestをCIで実施するときには --runInBandを設定することが推奨されているという情報を発見しました。

テストメタデータの収集

メモ:Jest テストの実行時には、--runInBandフラグを使用してください。 このフラグがない場合、Jest は、ジョブを実行している仮想マシン全体に CPU リソースを割り当てようとします。 --runInBandを使用すると、Jest は、仮想マシン内の仮想化されたビルド環境のみを使用するようになります。

--runInBandはJestをシリアルで実行するオプションらしく、これを設定したら成功しました!

おそらく、ts-jest対応で負荷が増大し、CIではシリアルでないとテストが実行できなかったのでしょう。

ESLintもTypeScriptに対応させる

今まで使っていたESLintをそのまま使えるようにします。

TSLintというのも存在するようですが、ESLintのプラグインに統合されていく流れのようです。

https://qiita.com/suzuki_sh/items/fe9b60c4f9e1dbc5d903

関連パッケージのインストール

$ npm i --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser

eslintrcを編集

以下のようにoverridesを書き加えます。

no-unused-varsはTypeScriptでは想定どおりに機能しなかったので、TypeScript専用の設定を書き加えました。

module.exports={overrides:[{files:'*.ts',plugins:["@typescript-eslint"],parser:'@typescript-eslint/parser',rules:{"no-unused-vars":'off',"@typescript-eslint/no-unused-vars":["error",{"vars":"all","args":"none"}]},}],};

おわりに

Node.js + Expressで開発しているサービスに、TypeScript環境を導入する手順と、関連する環境の整備についてご紹介しました。
TypeScript でガンガン開発していきましょう!

GMSでは一緒に開発する仲間を募集中です。

https://www.wantedly.com/companies/company_8204706

続・実録 Node-REDノード作成 24時

$
0
0

こんにちは、ポキオです。

IoTLT Advent Calendar 2019enebular Advent Calendar 2019の15日目の記事です。
手抜きです、ごめんなさい。

ポキオ Node-RED ノード作成

tl;dr

  • 京急ノードを作ってみました
  • Node-REDのノードライブラリに反映されるまで時間がかかることがあります
  • 一度公開したあとも、ノードのメンテは必須です
  • Node-RED、だぁいすき!

話の発端:Node-RED向けの京急ノードを作りたかった

この記事をご覧の諸兄姉にとっては釈迦に説法かもしれませんが、Node-REDはグラフィカルなUIで、ノンコーディングでもプログラミングができてしまう、素晴らしいツールでございます。

Node-REDで部品として動くパーツであるノードは色々準備されていたり、ノードライブラリでも種々のノードが公開されていて、Node-REDの可能性を無限に広げてくれています。

ただし、なかなか日本向けのノードがないのに玉に瑕で、だからこそ自分でノードを作って公開しようというモチベーションが湧いてきたわけです。とりわけ、私は京急が大好きなので、京急にまつわるノードを作ろうと思い立ったわけです。

で、作ったのがこれです。

node-red-contrib-keikyu
https://flows.nodered.org/node/node-red-contrib-keikyu

image.png

京急の運行情報が取得できる、すばらしいノードに仕上がっています(笑)

問題①:なかなか公開できない!

詳しい経緯はこちらで公開していますが、公開作業をしている段階で一つの問題にぶち当たりました。

image.png

ノードがノードライブラリで公開されるまで、やることは色々あるわけですが、とりあえずコーディングやnpmjs.comでの公開までは順調に進んだわけです。

image.png

ただ、npmjs.comでnpmモジュールとしてノードを公開したあと、なかなかノードライブラリに反映されないという問題に陥りました。通常は数時間で反映されるわけなのですが、そのときは全く反映されませんでした。

よくある原因としては、

  • package.jsonのkeywordsに「node-red」がない
  • プレフィックス「node-red-contrib-」を用いて命名されてない
  • README.mdがない
  • LICENSEがない
  • npmで公開されてない
  • npm versionしたあとにgit pushし忘れてる

などなどありますが、それはすべてOK。結局、npmjs.comから一度ノードを削除して、再度公開しました・・・。削除後は24時間経たないと再公開できないという制限がありましたが、なんとか再公開後にノードライブラリに反映されました・・・。もし同じようなことで困っている方がいらっしゃいましたら、お試しくださいませ。

問題②:京急ノードが動かなくなった!

公開して、一安心してたんですが、ある日突然ノードが使えなくなっていました。

image.png

結論から言ってしまえば、京急の運行情報ページのレイアウトが更新されていて、いままで使っていたパースのロジックがワークしなくなり、運行情報の取得ができなくなっていました・・・。

image.png

もともとパースのロジックは、かなりのクソコードだったので致し方ないとおもいつつ、とりあえずコードを修正して、再度公開しました。

image.png

また、二度と同じようなことがないように、自分が作ったノードが正しく動作しているか、enebular上でCIのように定期的に動かし、ノードの状態を監視する仕組みを作りました。

image.png

こんな感じでステータスが表示されます。これで完璧ですね!(笑)

現在平常通り運転しています。

というわけで、今年もいろいろとお世話になりました。
来年もポキオと京急を何卒よろしくおねがいします!

宣伝

ポキオとドライブをしながらIoTとかTechな話をする、ポキオ・カープール

image.png

ぜひご覧ください!
一緒にドライブしながら喋ってくれる方も大募集中です!

reveal.jsの環境構築で躓いた話(windows 10)

$
0
0

はじめに

最近、私の所属しているサークルでLT会をやっていこうという流れがあったので、Markdownからスライドを作れる「reveal.js」について紹介しました。
そのためにreveal.jsをセットアップしようとしたのですが、公式ドキュメントでは3行だったのが1時間以上かかってしまったので、その備忘録です。

Markdownとは

markdownは簡単な記法で記事などの文章を構成出来るマークアップ言語です。
qiitaやはてなブログなどで記事を上げている諸兄には馴染み深いものかもしれません。

記述例:

# 見出し1
## 見出し2
--- 線
**強調**

実行例:

見出し1

見出し2


強調

reveal.jsとは

かっこいいスライドを簡単に作れるフレームワークです。
htmlに書き込んで作りますが、ちょちょっと設定すると、Markdownを書くだけでそれがスライドになります。
パワーポイントをマウスクリックしながら無限に時間を溶かすことも、揃ってないデザインを先輩に怒られることもなくなります。

セットアップする

reveal.js のGitHubページ

reveal.jsのセットアップには2つの方法があります。

1) 簡単に使うだけなら、githubのページからプロジェクトをローカルに保存するだけで大丈夫です。

2) 拡張マークダウンを使ったり、スピーカーノート(発表者のメモを隣に表示する機能)を使う場合には、Node.jsを使いローカルにサーバーを建てるFull Setupが必要です。

今回はこの(2)のFull Setupの方法を解説します。

1. reveal.js をクローン OR ダウンロードする。

reveal.js のGitHubページ

2. Node.js をインストールする

公式サイトからNode.jsをダウンロード&インストールします。

3. npmを使ってインストールします

やることは、Node.js についてくるパッケージマネージャnpmを使って環境を整えるだけです。
package.jsonファイルに設定はしてくれてあるので、これに従うだけです。

カレントディレクトリをreveal.jsのフォルダに移動してから

$ npm install
$ npm start

でサーバーをインストールして起動して終了なんですが、そうは問屋がおろしませんでした

4.MSBuild.exeに関するエラーが出たら

VisualStudioを入れていないか、versionが異なっています。
もしVSを入れていなかったら入れましょう。
または、VS2019を入れていてエラーが出たらバージョンの設定が違うので、

$ npm config set msvs_version 2019

でコンフィグを設定します。

5. Windows Build Toolsをインストールする

Windows-Build-Toolsが入っていない場合はこれをインストールします

$ npm install -g windows-build-tools

これでOKです。

6. npm-check-updatesを入れる

パッケージを確認するためにnpm-check-updatesを入れて、パッケージのバージョンを整理してもらいます。

$ npm install -g npm-check-updates

ncuコマンドを使います

npm-check-updatesを実行するコマンド、ncuを実行します

$ ncu

するとncu -uをしてアップグレードをするように促されるので、仰せの通りにします。

$ ncu -u

7. npm installをしなおす

これでnpm installをするように促されるので、仰せの通りにします。

$ npm install

「Binary is fine」と出れば成功です!

reveal.js を使う

インストールが終わったのでnpm startでサーバーを起動します。

$ npm start

ブラウザで見る

これで http://localhost:8000にサーバーが立っています。
晴れて、reveal.jsを使えるようになりました!

終わりに

ここでは、Windows10でつまづきまくった環境構築について説明しました。
この記事ではその使い方、Markdownの書き方までは解説しなかったです。
(これについては非常に分かりやすい記事が沢山あるので)]

以下の記事をご参考にしてください。
reveal.jsでスライド作り。

reveal.jsを使うと非常に楽にスライドを作ることが出来るので、是非是非活用してください

Temporal dead zoneと消えない変数

$
0
0

ChromeやNodeJSのJavascriptコンソール画面で動作確認する場合、
以下の様に間違ってエラーになってしまうことがあります。

constobj=JSON.parse("");// JSON形式じゃない文字列を指定// Uncaught SyntaxError: Unexpected end of JSON input

JSON形式の文字列で指定するところに空文字を指定した場合ですが、Uncaught SyntaxErrorとなってしまいます。

じゃあ間違えたのだからと訂正して再度実行すると

constobj=JSON.parse("[]");// JSON形式の文字列を指定// Uncaught SyntaxError: Identifier 'obj' has already been declared

既に宣言済みなのでエラーとなります。

それでは、既に宣言済みなら変数が存在するのだと思って参照してみると

console.log(obj);// Uncaught ReferenceError: obj is not defined

宣言されていないとエラーとなります。

グローバルに変数が残っているかなと思ってdeleteを実行しても消えている様子は無いです。

deleteobj;// falseconstobj=JSON.parse("[]");// Uncaught SyntaxError: Identifier 'obj' has already been declared

MDNのlet変数やconst変数の説明を見てみるとTemporal dead zoneの存在について紹介されています。

undefined の値で始まる var 変数と異なり、 let 変数は定義が評価されるまで初期化されません。変数を宣言より前で参照することは ReferenceError を引き起こします。ブロックの始めから変数宣言が実行されるまで、変数は "temporal dead zone" の中にいるのです。

どうやら、宣言されたconstlet変数はTemporal dead zoneの中に残ってしまっていて参照できない状態になっているようです。

varletconstなどJavascriptの変数はどこに宣言したとしても、スコープの先頭で宣言されてしまったことになる。つまり変数の巻き上げが行われます。
エラーが発生するなどして変数の初期化が行われなかった場合、letconstについてはTemporal dead zoneに変数があるため、宣言はされているが、参照出来ない状態となるらしいです。この状態だと変数を再使用することは出来なくなってしまいます。

対応策としては、
リロードなどをして最初から実行し直すか、
もしくは、varを使うか、

もしくはスコープを指定するか、

{constobj=JSON.parse("");}

もしくはletを使っていったん宣言だけすれば最初にundefinedがセットされます。

letobj;// undefinedobj=JSON.parse("");// Uncaught SyntaxError: Unexpected end of JSON inputobj=JSON.parse("");// Uncaught SyntaxError: Unexpected end of JSON input

DevToolやNodeJSのコンソール画面で操作するときにぐらいしか意識しないかと思いますが。

闇の魔術とはちょっと違うかも知れないけれど、Temporal dead☠️ zoneの呼び方が闇っぽいのでここに載せておきました。

Node.jsからSendGridを使ってメールを送る

$
0
0

久しぶりにSendGridを使ってみたのでメモ。

SendGridの注意点

  • ユーザ名はメールアドレスではなく、代理店である構造計画研究所から独自に振られたxxx@kke.comというやつ

久しぶりですっかりID忘れてました。

準備

API KEYの取得

利用するにはAPI KEYが必要です。到達率を上げるためにはドメイン認証やらいろいろやったほうがいい。
API KEYはSettingsの中にある。

スクリーンショット 2019-12-15 14.58.57.png

作業場の準備

mkdir sendmail
cd sendmail
touch index.js

npm init -f

npm install --save @sendgrid/mail

実装

難しくない。本家サイトにユースケースの紹介があるのでそれを見ながら実装する。
まずはSend a sigle email to single recipientを試す。

API_KEYやらその他情報は各自環境に合わせて変更。

const sgMail = require('@sendgrid/mail');

const API_KEY = "XX.O1c1v3QNQzed41lkvQKNIw.7DYw-coFLLfoLqEuWADNzKH_xxxxxxxxxxxxxxxxxx";
sgMail.setApiKey(API_KEY);

const msg = {
    to: 'to@mail.com',
    from: 'from@mail.com',
    subject: 'test mail from sg',
    text: 'hoge hoge',
    html: '<p>foo bar</p>'
}

sgMail.send(msg).then(res => {
    console.log(res);
}).catch(e => {
    console.log(e);
});

複数人に送る場合はアドレスの配列を作り、sgMail.sendMultiple(msg)としてやるみたい。

動作確認

実行してみる。

node index.js

届いたみたい。


lambdaのNode.jsバージョンを上げるときはログのフォーマット変更にも注意

$
0
0

バージョンでログ出力が違うから注意

2019年末にlambdaのNode.js 8.10がEOLを迎えます。
ログ出力の部分で微妙に動作が違うので念の為確認してからバージョンアップしましょう。
特にログ出力をライブラリで行っている場合、そちらの実装がどうなっているか見ておいた方が良いです。
kibanaとかでパースするロジックに変更が必要になるかも。

Node.js 8.10

関数コード

exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    // デフォルトのテンプレートにconsole.log入れただけ
    console.log(`"This is Node.js 8.10 log."`);
    return response;
};

Execution Result

Function Logs:
START RequestId: fcfedef6-ed16-4236-a407-5cc3d38bb2fe Version: $LATEST
2019-12-15T06:42:13.112Z fcfedef6-ed16-4236-a407-5cc3d38bb2fe "This is Node.js 8.10 log."
END RequestId: fcfedef6-ed16-4236-a407-5cc3d38bb2fe
REPORT RequestId: fcfedef6-ed16-4236-a407-5cc3d38bb2fe Duration: 0.49 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 58 MB

2019-12-15T06:42:13.112Z fcfedef6-ed16-4236-a407-5cc3d38bb2fe "This is Node.js 8.10 log."

実行日時 リクエストID console.logで出力した文字列

になってますね。

Node.js 10.x

関数コード

exports.handler = async (event) => {
    // TODO implement
    const response = {
        statusCode: 200,
        body: JSON.stringify('Hello from Lambda!'),
    };
    console.log(`"This is Node.js 10.x log."`);
    return response;
};

Execution Result

Function Logs:
START RequestId: b7b8a64d-0bcf-405e-8257-0fc6d3f62419 Version: $LATEST
2019-12-15T06:46:11.635Z b7b8a64d-0bcf-405e-8257-0fc6d3f62419 INFO "This is Node.js 10.x log."
END RequestId: b7b8a64d-0bcf-405e-8257-0fc6d3f62419
REPORT RequestId: b7b8a64d-0bcf-405e-8257-0fc6d3f62419 Duration: 59.33 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 76 MB Init Duration: 174.25 ms

2019-12-15T06:46:11.635Z b7b8a64d-0bcf-405e-8257-0fc6d3f62419 INFO "This is Node.js 10.x log."

実行日時 リクエストID ログレベル console.logで出力した文字列

と、出力されるようになっています。
8.10と比較するとログレベルが出力されるようになっていることに注意しましょう。

Node.js 12.x

Execution Result

Function Logs:
START RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f Version: $LATEST
2019-12-15T06:47:52.767Z 786d807b-6bb9-4267-88f7-4df1f4955a7f INFO "This is Node.js 12.x log."END RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f
REPORT RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f Duration: 2.80 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 70 MB Init Duration: 111.41 ms

2019-12-15T06:47:52.767Z 786d807b-6bb9-4267-88f7-4df1f4955a7f INFO "This is Node.js 12.x log."END RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f

実行日時 リクエストID ログレベル console.logで出力した文字列END RequestId: リクエストID

と、10.xとも違う、おそらくAWS側も意図していない動作をしてるように見受けられます。
(コピペミスとかでもなかったです)

2019-12-15-15-52-35.png

とはいえ、CloudWatchには意図どおりに出力されてそうなので特に問題にはならないと思います。

補足

consoleオブジェクトの関数に応じてログレベルを出力するようになっているようです。

Node.js 8.10

関数コード

exports.handler = async (event) => {
  // TODO implement
  const response = {
      statusCode: 200,
      body: JSON.stringify('Hello from Lambda!'),
  };
  console.log(`"This is Node.js 8.10 log."`);
  console.info(`"This is Node.js 8.10 info log."`);
  console.warn(`"This is Node.js 8.10 warn log."`);
  console.error(`"This is Node.js 8.10 error log."`);
  return response;
};

Execution Result

Function Logs:
START RequestId: c2a0c056-7623-4c4f-bbad-6b458800cd7d Version: $LATEST
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 log."
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 info log."
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 warn log."
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 error log."
END RequestId: c2a0c056-7623-4c4f-bbad-6b458800cd7d
REPORT RequestId: c2a0c056-7623-4c4f-bbad-6b458800cd7d Duration: 0.50 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 58 MB

ログを見てもどんなレベルのログかが分からない。

Node.js 10.x

関数コード

exports.handler = async (event) => {
  // TODO implement
  const response = {
      statusCode: 200,
      body: JSON.stringify('Hello from Lambda!'),
  };
  console.log(`"This is Node.js 10.x log."`);
  console.info(`"This is Node.js 10.x info log."`);
  console.trace(`"This is Node.js 10.x trace log."`);
  console.debug(`"This is Node.js 10.x debug log."`);
  console.warn(`"This is Node.js 10.x warn log."`);
  console.error(`"This is Node.js 10.x error log."`);
  console.fatal(`"This is Node.js 10.x fatal log."`);
  return response;
};

Execution Result

Function Logs:
START RequestId: d12b8a86-62eb-4caa-9d09-ed66b1ec08ed Version: $LATEST
2019-12-15T07:15:02.460Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed INFO "This is Node.js 10.x log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed INFO "This is Node.js 10.x info log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed TRACE "This is Node.js 10.x trace log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed DEBUG "This is Node.js 10.x debug log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed WARN "This is Node.js 10.x warn log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed ERROR "This is Node.js 10.x error log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed FATAL "This is Node.js 10.x fatal log."
END RequestId: d12b8a86-62eb-4caa-9d09-ed66b1ec08ed
REPORT RequestId: d12b8a86-62eb-4caa-9d09-ed66b1ec08ed Duration: 47.48 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 76 MB Init Duration: 151.14 ms

どんなレベルのログが一目瞭然だし、ログのパースするときに便利そう。
8.10と比較すると種類も増えてます。他にもあるかも。

リンク

TwilioのAuthyの2FA(電話番号認証)機能をnode.jsから使う

$
0
0

呼び出し方は本家のAPI使う方法や先人のnpmパッケージ使う方法とかあるが、今回は先人のnpmパッケージを利用してみる。

ちなみにTwilioでは2つの電話番号認証機能があり、どちらを使うかはケースバーケースのようです。

Authy API

Authyが提供するAPIはSMS送信以外での2FAに対応していますが、今回はSMS送信による認証を試してみます。

準備

Twilioに登録

Twilioに登録して利用できるようにしておく。無料枠もあるがすぐに消費してしまいます・・・。

Authyのプロジェクト作成とAPIの取得

左メニューからAuthyを選択肢、新規プロジェクトを作成、設定にてPRODUCTION API KEYを取得します。

APIの機能(利用の流れ)

利用方法は簡単で、主な利用APIは下記の2つ。

  • verification_start()でSMS経由でCode送信
  • verification_check()でCode検証

あとは、

  • verification_status()というAPIで状態を確認

することもできる。

実装

まず、モジュールをインストールします。

npm install--save authy

で、実装。

以下のコードではめんどくさいの一気に記述していますが1)と2)は同時に実行することはできません。
まず、2)をコメントアウトして実行し、SMSが届くか見ましょう。そして、1)をコメントアウトして、Codeを設定した状態で2)をコメントインして実行します。

//取得したAPIで初期化varauthy=require('authy')('MswmXqxxxxxxxxxxxxxxxxxxxxxxxxxxx');constphoneNumber='9012345678';constcountryCode='81';constverificationCode='000000';//受け取ったものを入れる//1)smsでcode送信authy.phones().verification_start(phoneNumber,countryCode,{via:'sms',locale:'ja',code_length:'6'},(err,res)=>{if(err)throwerr;console.log(res);});//2)受け取ったコード認証authy.phones().verification_check(phoneNumber,countryCode,verificationCode,(err,res)=>{if(err)throwerr;console.log(res);});//3)ステータス確認authy.phones().verification_status(phoneNumber,countryCode,(err,res)=>{if(err)throwerr;console.log(res);})

スムーズにうまく動きました。

メモ

  • 一度認証しても、再度送信するとstatusはpendingになるようです(まあ、当然ですが)。
  • 料金は国内3キャリア向けに$0.08円(まあ10円)。
  • あくまで電話番号の実在確認なので、会員管理システム等は別途用意しておく必要がある。
  • 専門サービスだけあって090と記述しても送信してくれる。090-1234-5678とハイフンがあっても処理してくれる。

Go、Node.jsのプログラム間でRPC通信をする

$
0
0

概要

gRPCを使用して、Go、Node.jsのプログラム間でRPC通信をします。
クライアント側をGo、サーバ側をNode.jsが担当します。

環境
MacOS Catalina: 10.15.1
Go: 1.13.4
Node.js: 10.15.3

クライアント(Go)の作成

クライアント側のディレクトリを作成します。

$ mkdir grpc-test-go

クライアント側のディレクトリ構成は最終的に以下のようになります。

$ cd grpc-test-go
$ tree
.
├── bridge
│   ├── bridge.pb.go
│   ├── bridge.proto
│   └── go.mod
├── client.go
└── go.mod

.protoファイルの作成

protoファイルを作成して、仕様を定義します。
型にrepeatedをつけると配列になります。
公式ページを参照してください。

bridge.proto
syntax = "proto3";

package bridge;

service BridgeService {
    rpc PostData (Data) returns (Reply) {}
} 

message Data {
    string key = 1;
    repeated string data = 2;
}

message Reply {
    string response = 1;
}

.protoファイルからコードを生成

定義した.protoファイルからクライアント、サーバー共通で使用するコードを生成します。
まず、コードを生成するために必要なprotobufパッケージをインストールします。

$ brew install protobuf
$ protoc bridge/bridge.proto --go_out=plugins=grpc:.

これで、bridge.pb.goが作成されました

module周りを整理

ローカルでbridge.pb.goを参照したいので、色々します。

$ go mod init grpc-test-go
$ cd bridge 
$ go mod init bridge

grpc-test-goの方のgo.modファイルを編集

go.mod
module grpc-test-go

go 1.13

require (
    github.com/[username]/grpc-test2/bridge v0.0.0
    google.golang.org/grpc v1.25.1
)

replace github.com/[username]/grpc-test-go/bridge => ./bridge

クライアント側コードの作成

client.go
packagemainimport("context""fmt""google.golang.org/grpc"pb"github.com/melonattacker/grpc-test-go/bridge")funcRpcPost(keystring,Data[]string)(string,error){conn,err:=grpc.Dial("127.0.0.1:50051",grpc.WithInsecure())iferr!=nil{return"",err}deferconn.Close()client:=pb.NewBridgeServiceClient(conn)message:=&pb.Data{Key:key,Data:Data}res,err:=client.PostData(context.TODO(),message)response:=res.Responseiferr!=nil{return"",err}returnresponse,nil}funcmain(){data:=[]string{"apple","orange","lemon"}result,err:=RpcPost("fruit",data);iferr!=nil{fmt.Println(err)}fmt.Println(result)}
$ go build

コンパイルが通るはずです。

サーバ(Node.js)の作成

サーバ側のディレクトリを作成します。
クライアント側と依存しない形で作成しましょう。

$ mkdir grps-test-node
$ cd grps-test-node

.protoファイルの作成

上で作成したbridge.protoをコピーしてきます。

bridge.proto
syntax = "proto3";

package bridge;

service BridgeService {
    rpc PostData (Data) returns (Reply) {}
} 

message Data {
    string key = 1;
    repeated string data = 2;
}

message Reply {
    string response = 1;
}

必要なnpmパッケージのインストール

$ npm init -y
$ npm install grpc @grpc/proto-loader --save

サーバ側コードの作成

server.js
constgrpc=require('grpc')constprotoLoader=require('@grpc/proto-loader')constPROTO_PATH=__dirname+'/bridge.proto'constpackageDefinition=protoLoader.loadSync(PROTO_PATH,{keepCase:true,longs:String,enums:String,defaults:true,oneofs:true})constBridgeProto=grpc.loadPackageDefinition(packageDefinition)constserver=newgrpc.Server()constPostData=(call,callback)=>{console.log(call.request);callback(null,{response:"Data was sent to server with key: "+call.request.key})}server.addService(BridgeProto.bridge.BridgeService.service,{PostData:PostData,})server.bind('127.0.0.1:50051',grpc.ServerCredentials.createInsecure())console.log('Listening on 127.0.0.1:50051...')server.start()

実行

サーバ(Node.js)

$ cd grpc-test-node
$ node main.js
Listening on 127.0.0.1:50051...

クライアント(Go)

$ cd grpc-test-go
$ go run client.go

サーバ(Node.js)

{ data: [ 'apple', 'orange', 'lemon' ], key: 'fruit' }

クライアント(Go)

Data was sent to server with key: fruit

無事データが送られました!
以上です!

create-react-appで作ったアプリがhttpsだと動かない

$
0
0

問題点

create-react-appで作成したアプリケーションにhttpsでアクセスすると、以下のようにエラーとなりました。
SecurityError: Failed to construct 'WebSocket': An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.
d1fb5456080f4fbb895c367aae298593.jpeg
httpだと問題なく動きます。
create-react-appで作ったアプリを試しにHerokuに上げてみたときに、この問題を踏みました。

原因

以下でIssuesが上がっていました。
https://github.com/facebook/create-react-app/issues/8075
https://github.com/facebook/create-react-app/pull/8079

WebSocketsを利用している箇所で、httpsの場合はwss(WebSockets over SSL)を利用しなくてはいけないところ、wsを利用してしまっているためのようです。

解決法

問題が発生しているときのpackage.jsonの依存関係は以下です。

package.json
"dependencies":{"@testing-library/jest-dom":"^4.2.4","@testing-library/react":"^9.3.2","@testing-library/user-event":"^7.1.2","react":"^16.12.0","react-dom":"^16.12.0","react-scripts":"3.3.0"}

react-scriptの3.3.0で発生している問題なので、3.2.0にバージョンダウンすると、一旦動作するようになります。

$ npm install react-scripts@3.2.0

react-scriptsのバージョン変更がpackage.jsonにも反映され、httpsでも動作するようになりました。

package.json(更新後)
"dependencies":{"@testing-library/jest-dom":"^4.2.4","@testing-library/react":"^9.4.0","@testing-library/user-event":"^7.1.2","react":"^16.12.0","react-dom":"^16.12.0","react-scripts":"3.2.0"}

2cd9968afd83f4f2863a9ff10f724a74.jpeg

上記Issuesは、react-scriptの3.3.1で修正予定(2019/12/15時点)のようなので、バージョン下げは暫定対応とし、修正されたら3.3.1に上げるのがよいと思います。

Next.jsでcookieをシンプルに扱うことができるライブラリ nookies を紹介

$
0
0

Next.jsでcookieを扱うのは大変

Next.jsなどのサーバーサイドレンダリング(以下SSR)をしているフレームワークでcookieを扱うのは面倒くさいですよね。
その理由の一つとして、同じコードでもSSRの場合とクライアントでレンダリングしている場合で挙動が違うということがあります。
例をお見せしましょう

クライアントでレンダリングしている場合

console.log(document.cookie); // accessToken=test1234;

SSRの場合

console.log(document.cookie); // ReferenceError: document is not defined

原因

クライアントサイド(ブラウザ)でレンダリングしている時は、ブラウザに保存されているcookieにアクセスできるが,
SSRの時はブラウザに保存されているcookieにアクセスできません。

SSRの時にcookieを扱うには

SSRでcookieの情報はここに入っています

index.tsx
constTestPage:NextPage<Props>=(props)=>{return<div>test</div>
}TestPage.getInitialProps(ctx){// ここconsole.log(ctx.req.headers.cookie)// accessToken=test1234;     return{};}

同じライブラリをクライアントとSSRで共有していたりすると、条件分岐などが大変ですね
そんな時に nookies を使います
https://www.npmjs.com/package/nookies

使い方

以下の例で示すようにクライアントサイドの場合ctxを渡さずに、SSRならctxを渡せば、cookieをオブジェクトに整形して返してくれます。

tool.ts
import{parseCookies}from'nookies';import{NextPageContext}from'next';exportfunctionprintCookie(ctx?:NextPageContext){constcookie=parseCookies(ctx);console.log(cookie)// { accessToken: 'test1234' }}

また、cookieの追加もクライアントとSSR分け隔てなく行ってくれます

set_cookie.ts
import{setCookie,destoroyCookie}from'nookies';import{NextPageContext}from'next';exportfunctionsetCookie(ctx?:NextPageContext,token:string){setCookie(ctx,'accessToken',token,{maxAge:30*24*60*60,});}// ついでにcookie削除(動作確認してません)exportfunctiondestoroyCookie(ctx?:NextPageContext){destroyCookie(ctx,'accesstToken')}

ライブラリを読んでみた(箇条書きです!)

https://github.com/maticzav/nookies

nookies/src/index.ts
constisBrowser=()=>typeofwindow!=='undefined'// 今の環境がSSRかクライアントサイドレンダリングか調べてるらしいです..if(ctx&&ctx.req&&ctx.req.headers&&ctx.req.headers.cookie){returncookie.parse(ctx.req.headers.cookieasstring,options)// SSRだったらctx.req.headers.cookieに入っているcookieをparseして返却}..if(isBrowser()){returncookie.parse(document.cookie,options)//クライアントだったらdocument.cookieにあるcookieをparseして返却}..ctx.res.setHeader('Set-Cookie',cookiesToSet)// SSRならレスポンスヘッダーにcookieをセットする..if(isBrowser()){|document.cookie=cookie.serialize(name,value,options)// クライアントならクッキーをセット}

まとめ

以上です。いかがでしたでしょうか?
参考になりましたら幸いです。

SlackのWebhookをプロキシする仕組みを作る

$
0
0

Slackはさまざまなカスタマイズ機能を持っているのが魅力のツールです。例えばBotを作ったり、カスタムのslash commandを作ったりすることで、プラットフォームの拡張ができます。

Slack Botの作り方はいくつかあるのですが、Slackのリッチな機能を最大限に引き出すには、SlackからのWebhookを受けることが必要になってきます。すなわち、ボタンなどが付いたリッチなメッセージの投稿は難しくないのですが、投稿したメッセージのボタンやメニュー操作は、SlackからWebhookの形で通知される仕組みになっています。

※ この辺りの仕組みの詳細については、まとまっている記事がいくつもあるので省略します。

Slackは当然publicなサービスなので、インターネット経由でWebhookが飛んできます。これを受けて処理するためには、Bot用の公開APIサーバを書くことになるでしょう。

公開サーバ? 何か問題でも?

それじゃあ、単にサーバを立てればいいじゃないかというのはもっともですが、サーバを公開するのはいくつか面倒な点があります。

  • 公開サーバはセキュリティリスクがある
    • もちろん、ちゃんと管理すればいいのですが、publicに露出するものが増えると相応にリスクが増加してしまうのはどうしようもないことです。できればやりたくありません
  • とりわけ会社業務で使う場合、イントラネットにある社内システムや社内ネットワークとの接続性を考慮する必要が出てくる
    • 当然、Botであるからには、社内のいろいろなものと連携させたくなります。

SlackのRTM APIというWebSocketベースのAPIでchatメッセージを受信する場合には、こういった悩みは発生しませんでした(メッセージをpullするAPIを使うので、基本的にインターネットへ接続できるBot実行環境がありさえすればよかった)。できる限りそのお手軽さを保ったまま、セキュリティなどの煩雑な問題をコントロールする方法がないか、ということを考えたくなります。

そうだプロキシしよう

というわけで、次のようなシステムを考えることにしました。

  • Webhookを直接Botサーバに流すのではなく、プロキシ(リバースプロキシ)を経由させる
  • プロキシは単にリクエストを横流しするのではなく、リクエストの検証を行い、不正なリクエストを弾く
    • Slackから事前に払い出された秘密鍵を元に、指定の手順でHMACを計算することで、リクエストの正当性の検証が可能です
    • Verifying requests from Slack
    • SDKがあるので、Slack社オフィシャルの実装をそのまま引っこ抜いてくることができます

具体的には、AWSのAPI GatewayとLambdaを組み合わせた、簡単なプロキシを書きます。

slack-to-intranet.png

パターンとして変則的ではありますが、結果的にAWSのDDoS対策ホワイトペーパーで述べられている、API Gatewayに公開箇所を絞るというプラクティスに沿っているように思います。

Typically, when you must expose an API to the public, there is a risk that the API frontend could be targeted by a DDoS attack. To help reduce the risk, you can use Amazon API Gateway as a “front door” to applications running on Amazon EC2, AWS Lambda, or elsewhere.
By using Amazon API Gateway, you don’t need your own servers for the API frontend and you can obfuscate other components of your application. By making it harder to detect your application’s components, you can help prevent those AWS resources from being targeted by a DDoS attack.

実装例

今回は(ちょっと慣れない言語なのですが)Node.jsでlambdaを書いてみることにします。
フレームワークとしてserverless frameworkを使うとお手軽にAPI Gateway + Lambdaの構成を作ることができました。

https://github.com/saka1/slack-webhook-gatekeeper

バックエンドのSlack App名に対応したURLパスに対してWebhookが飛んでくると、秘密鍵を使ってWebhookの検証をします。あらかじめ、Slack App名と秘密鍵の対応関係はparameter storeに入れておきます。正当なWebhookだった場合にはupstreamにリクエストを飛ばしてプロキシ動作をします。URLパスは複数登録することができるように作りました。

学んだこと

  • AWSのparameter storeはあんまり高速ではなさそう(getに数百msかかる?)だったので、lambdaでキャッシュしたほうが無難そうでした
  • プロキシとBotは1:Nの関係にあるため、プロキシシステムは全てのBot Appの秘密情報を管理しないといけなくなります(そうしなければリクエストの検証ができません)
    • 理想的ではないので、何かうまい回避方法があればなあと思っています

API Gatewayからlambdaを呼ぶには「lambda統合」「lambdaプロキシ統合」の2つがあるらしいのですが、前者を使ったほうがよさそうでした。というか飛んでくるWebhookの検証でHMAC計算が入るため、1バイトでもゴミが入ると正しく動きません。そもそも前者は用途に対して複雑すぎました。

まとめ

この記事では、Slack Botの前段に置くプロキシシステムを検討し、その実装までをやってみました。ざっくり机上で検討 → ざっと実装した割には案外使えそうなものが考案できて、個人的には満足しています。

WASI (WebAssembly system interface) を Wasmtime と Node.js で試す

$
0
0

この記事は ZOZO テクノロジーズ Advent Calendar 2019 #1, #2, #3, #4, #5のうち #4 の16日目の記事です。

昨日は @jon20さんの「Buildkitを使ってDockerfile以外からビルドする」でした。

Buildkit触ったことなかったので参考になりました!

本日は ZOZO テクノロジーズ来年4月入社予定の @takewellがお送りします。

2019 年自分の中で一番注目のテクノロジーはなんだろうかと考えてみたのですが、WebAssembly関連の記事は目に入ればだいたい読むようにしてると気づきました。

WebAssembly が掲げている能率的で高速, メモリセーフ, オープンに標準化されたテキストフォーマットによるデバッグやテストのやりやすさはどれもワクワクさせられる特性です。
Web 信者としてはブラウザがネイティブアプリと遜色ない環境になるかもしれないという面で期待が膨らんでいます。

ということで、今回は表題の通り、WebAssembly の中でも WASI とはなんであるかを WASI tutorialを参考に実際に手をうごかして試してみました。

WASI (WebAssembly system interface)

WebAssembly は Web ブラウザで新たな機能と大幅なパフォーマンス向上を提供する新しい種類のコードですが、今年に入ってStandardizing WASI: A system interface to run WebAssembly outside the webが発表されました。

この WASI とは何か、一言でいうと、セキュリテーとポータビリティに主眼において標準化、開発されているシステムインターフェースです。詳しい内容は wasi.devにまとまっており、標準化も進んでいるところのようです。こちら要ウォッチですね。

WebAssembly そのものはガベコレやスレッド管理など未実装なものも残しており、速度の面でまだ物足りないと言われることがあります。さらに既存の JS Engine が十分高速なためプロダクションで有意差が出しにくい面があるため、ブラウザ環境では存在感はありません。一部 eBay (WebAssembly at eBay: A Real-World Use Case) がプロダクションで使っているらしく皆無というわけではありません。

一方ブラウザの外では WASI のコンセプトは既存のものと比べて画期的なものであるように感じます。Rust公式ガイドの著者 Steve Klabnik さんのブログで述べられているように Edge Computing や 組み込みデバイスの分野などの方がむしろ先に WebAssembly の存在感を出してくるかもしれないなどの妄想が膨らみます。

それでは本当に WASI が様々な環境で実行可能かを試していきたいと思います。

実行環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.6
BuildVersion:   18G87
$ rustup --version
rustup 1.18.3 (435397f48 2019-05-22)
$ cargo --version
cargo 1.37.0 (9edd08916 2019-08-02)
$ node -v
v13.3.0

WebAssembly 形式(.wasm) にコンパイルする

まず C, C++, Rust, Go, Kotlin, Lua などのプログラミング言語、または AssemblyScript(TypeScript ライクな言語) で .wasm 形式にコンパイルします。
LLVM が WebAssembly に対応しているので、LLVM が基盤になっている言語は対応される可能性が高いと思われます。
私は Rust からコンパイルしました。
以下のように wasm32-wasiというツールチェインの追加してビルドするだけです。

$ cargo --bin new demo
$ rustup target add wasm32-wasi
$ cargo build --target wasm32-wasi
$ file target/wasm32-wasi/debug/demo.wasm
target/wasm32-wasi/debug/demo.wasm: , created: xxxx
fnmain(){println!("Hello, world!");}

これで上記コードを demo.wasmにコンパイルできました。

WebAssembly Runtime で実行

もちろん WebAssembly の主な実行環境はブラウザと Node.js ですが、それ以外にもAwesome WebAssembly Runtimesにまとまっているだけで 26 個(2019/12/16 日時点)もあります。盛り上がってますね!
WASI を推進する文脈で Mozila, Fastly, Intel, RedHat が Bytecode Allianceというファウンデーションを作って結んでいます。このファウンデーションで開発されているランタイムが wasmtimeです。今回はこれを使います。

以下でインストールと実行ができます。

$ curl https://wasmtime.dev/install.sh -sSf | bash
$ source ~/.bash_profile
$ wasmtime ./target/wasm32-wasi/debug/demo.wasm
Hello, world!

本当に動きました!

WebAssembly のテキストフォーマット (.wat)

先ほど、WebAssembly にコンパイルできる言語をいくつか挙げましたが、それに加えて WebAssembly にはS式ベースのテキスト表現も存在します。
コンパイルすると hello, worldと表示する hello.watです。

hello.wat
(module
    (import "wasi_unstable" "fd_write" (func $fd_write (param i32 i32 i32 i32) (result i32)))
    (memory 1)
    (export "memory" (memory 0))
    (data (i32.const 8) "hello world\n")
    (func $main (export "_start")
        (i32.store (i32.const 0) (i32.const 8))
        (i32.store (i32.const 4) (i32.const 12))
        (call $fd_write
            (i32.const 1) 
            (i32.const 0) 
            (i32.const 1) 
            (i32.const 20)
        )
        drop
    )
)

wat から wasm へのコンパイルには wabtを使います。

$ git clone --recursive https://github.com/WebAssembly/wabt
$ cd wabt
$ make
$ bin/wat2wasm ./hello.wat
$ wasmtime hello.wasm

逆にこれを .wasmから .watにデコンパイルしたりもできます。

$ bin/wasm2wat (imputwasmpath) > (output.wat)
output.wat
(module
  (type (;0;) (func (param i32 i32 i32 i32) (result i32)))
  (type (;1;) (func))
  (import "wasi_unstable" "fd_write" (func (;0;) (type 0)))
  (func (;1;) (type 1)
    i32.const 0
    i32.const 8
    i32.store
    i32.const 4
    i32.const 12
    i32.store
    i32.const 1
    i32.const 0
    i32.const 1
    i32.const 20
    call 0
    drop)
  (memory (;0;) 1)
  (export "memory" (memory 0))
  (export "_start" (func 1))
  (data (;0;) (i32.const 8) "hello world\0a"))

これを再コンパイルしてみましたが、error になってしまいました。可換性があるのだとしたらすごいと思って試してみたのですが、流石に無理みたいです。

Node.js で実行

node.js では version 8 からデフォルトで WebAssembly オブジェクトを実行できますが、最新版 v13.3.0系で WASI APIが追加されました。これをを使ってでコンパイルした demo.wasm実行します。

'use strict';constfs=require('fs');const{WASI}=require('wasi');constwasi=newWASI();constimportObject={wasi_unstable:wasi.wasiImport};(async()=>{constwasm=awaitWebAssembly.compile(fs.readFileSync('./demo.wasm'));constinstance=awaitWebAssembly.instantiate(wasm,importObject);wasi.start(instance);})();

まだ安定してない機能なので、実行時にオプションをつける必要があります。

$ node --experimental-wasi-unstable-preview0 --experimental-wasm-bigint wasm.js 
(node:94068) ExperimentalWarning: WASI is an experimental feature. This feature could change at any time
Hello, world!

実行できました!

所感

今回は WASI のポータビリティーについてを Rust => Wasm => Wasmtime & Node.js と試してみました。まだ Hello world だけですが実行できました。

速度比較や、サンドボックス化された環境でのセキュリティー性能など、他にも手を動かせる余地はありますが、今回は扱いません。
また機会があれば続きをやります。(そもそもすぐやり方が変わりそうな領域ではありますが...)

今回手を動かしたことで WASI の未来がますます楽しみになりました。
なんとか何かしらに使えないか野心を燃やしていきたいと思います!

それでは以上。
明日は @kurararararaさんになります。


Node.jsとSocket.ioで簡易的なチャットを実現させる

$
0
0

簡易的なWebチャットを実装する

はじめに

この記事はSLP KBIT Advent Calender 2019の16日目の記事である。
Socket.ioを用いた開発をまだやったことがなかったので今回はそういった開発を行っていきたいと思います。
かなり前に作ったので実装過程をかなり忘れてしまいました…。

環境

・Windows 10(64bit)
・Node.js : v10.15.3

準備

この開発ではNode.js、Socket.ioの他に、ローカルサーバーを簡単に立ち上げれるExpressを使うのでもしインストールしていない人は以下のコマンドでインストールしてみましょう。

$ npm install express

実装

サーバをたてる準備

create.js
varexpress=require('express');//expressを使用varapp=express();varhttp=require('http').Server(app);constpath=require('path');constio=require('socket.io')(http);constPORT=process.env.PORT||4649;app.get('/',function(req,res){res.sendFile(__dirname+'/chat_room/chat.html');//chat.htmlへ移動});io.on('connection',function(socket){// メッセージ送信処理socket.on('chat message',function(msg,user){//io.emit('chat message', msg);io.emit('chat message',{userName:user,message:msg});});});http.listen(PORT,function(){console.log('server listening. Port(・v・)/:'+PORT);//PORT番ポートに接続});app.use(express.static(path.join(__dirname,'chat_room')));//chat_roomディレクトリを公開

ちなみにchat_roomディレクトリにチャットを表示するhtmlとcssファイルを用意しているので最後の行で

app.use(express.static(path.join(__dirname, 'chat_room')));

と記述しています。(これがないとchat_roomの中にあるcssを読み込んでくれなかった)

HTMLとCSS

チャットを表示するためのHTMLとCSSを作ります

chat.html
<!DOCTYPE html><html><head><metahttp-equiv="content-type"content="text/html charset=UTF-8"><title>Socketテスト</title><linkrel="stylesheet"type="text/css"href="./style.css"/></head><body><h1>チャットテスト(・v・)/</h1><!-- メッセージ入力欄 --><formaction="#"id="chatForm"><inputid="u"autocomplete="off"placeholder="ユーザ名"/><inputid="m"autocomplete="off"placeholder="テキスト"/><button>Send</button></form><hr><!-- メッセージの表示 --><ulid="messages"></ul><!--必須モジュール2つ--><script src="/socket.io/socket.io.js"></script><script src="https://code.jquery.com/jquery-1.11.1.js"></script><script>varsocket=io();//var userName = 'test';$(function(){varuserName='';// メッセージを送信する$('form').submit(function(){socket.emit('chat message',$('#m').val(),$('#u').val());$('#m').val('');returnfalse;});// 受信したメッセージを表示// ul id ="messages"の部分socket.on('chat message',function(data){varchat=data.userName+""+data.message+"";if((data.message!='')&&(data.userName!='')){//空じゃない場合$('#messages').append($('<p id="chat">').text(chat));//ユーザとテキストは前提条件}});});</script></body></html>
style.css
#messages{padding:0.5em1em;margin:2em0;border:solid3px#9facd4;}#chat{margin-top:1px;padding:2px;background-color:rgb(218,246,255);color:#292929;}

実際に使ってみる

nodeコマンドでサーバを立ち上げます。
ad1.png

ローカルホストで指定したポートにアクセスすると以下のような画面が出ます。

ad2.png

ユーザ名とテキストを入力してsendを押すと
ad3.png

ad4.png
きちんとセリフっぽくなりましたね!

ウインドウを2つ以上開いた場合

・送信前
ad5.png
・送信後
ad6.png
片方が送信すると両方のウインドウに反映されましたね。
これでローカル環境でチャットが出来そうです。

おわりに

チャット機能の実装だけを目標にしていたので見た目がシンプルになってしまいましたが最後まできちんとやりきることができてよかったです。アイコンを実装したりもう少し見た目をよくすると本格的なチャットが楽しめるかなと思います。

Docker composeでstrapi+PostgreSQL開発環境を作る

$
0
0

理想の形になっていないのでそのうちブラッシュアップする…

フォルダ・ファイル構成

.
├── app //空フォルダ
├── db
│   └── pgsql-data //空フォルダ
└── docker-compose.yml

docker-compose.ymlを用意

2019年12月時点では下記が入ります。
Node.js : v13.2.0
PostgreSQL : psql (PostgreSQL) 10.11

docker-compose.yml
version:'3'services:postgres:image:postgres:10-alpinecontainer_name:"test-postgres-db"ports:-5432:5432environment:POSTGRES_PASSWORD:secretvolumes:-./db/pgsql-data:/var/lib/postgresql/dataapp:image:node:13.2.0ports:-"8080:8080"-"21337:1337"volumes:-./app:/srcworking_dir:/srctty:truedepends_on:-postgres

起動

docker-compose.ymlのあるディレクトリで。

$ docker-compose up -d

コンテナに入る

docker-compose.ymlのあるディレクトリで。

$  docker-compose exec app /bin/bash

strapiをインストール

appコンテナに入り、strapiをインストールしたいディレクトリで。
※このdocker-composeではworking_dirとしてホストの./appをマウントしている/srcを指定しているので、入ったそのまま/srcが楽だと思います。

npx create-strapi-app test-app

strapi設定

dockerコンテナの設定に合わせる。Hostをdocker-compose.yml内のサービスの名前に合わせると接続できる。

? Choose your installation type Custom (manual settings)
? Choose your default database client postgres
? Database name: postgres
? Host: postgres
? Port: 5432
? Username: postgres
? Password: ******
? Enable SSL connection: No

ログ

Creating a project with custom database options.
Creating files.
Dependencies installed successfully.

Your application was created at /src/test-app.

Available commands in your project:

  yarn develop
  Start Strapi in watch mode.

  yarn start
  Start Strapi without watch mode.

  yarn build
  Build Strapi admin panel.

  yarn strapi
  Display all available commands.

You can start by doing:

  cd /src/test-app
  yarn develop

DB接続周りでエラーが出ている場合は、postgresのコンテナがうまく起動していないなどをうたがう・

strapiを起動

cd test-app
yarn develop

ログ

$ strapi develop
Building your admin UI with development configuration ...

✔ Webpack
  Compiled successfully in 42.05s

[2019-12-13T11:59:45.458Z] info File created: /src/test-app/extensions/users-permissions/config/jwt.json

 Project information                                                          

┌────────────────────┬──────────────────────────────────────────────────┐
│ Time               │ Fri Dec 13 2019 11:59:45 GMT+0000 (Coordinated … │
│ Launched in        │ 12783 ms                                         │
│ Environment        │ development                                      │
│ Process PID        │ 8151                                             │
│ Version            │ 3.0.0-beta.17.8 (node v13.2.0)                   │
└────────────────────┴──────────────────────────────────────────────────┘

 Actions available                                                            

One more thing...
Create your first administrator 💻 by going to the administration panel at:

┌─────────────────────────────┐
│ http://localhost:1337/admin │
└─────────────────────────────┘

[2019-12-13T11:59:46.015Z] debug HEAD index.html (90 ms) 200
[2019-12-13T11:59:45.999Z] info ⏳ Opening the admin panel...

docker-compose.ymlで21337へのアクセスを1337につながるようにしてあるので、このコンテナのhttp://localhost:1337/adminにホスト(Mac)側からアクセスするには、http://localhost:21337/adminでアクセス出来るようになっている。

docker-compose.ymlに変更を加える

次回以降docker-compose up -dでstrapiの起動まで行えるようにdocker-compose.ymlを変更しておく。
TODO: 変更したくないし、Dockerfileも書きたくないので、になんとかできないか模索する

docker-compose.yml
version:'3'services:postgres:image:postgres:10-alpinecontainer_name:"test-postgres-db"ports:-5432:5432environment:POSTGRES_PASSWORD:secretvolumes:-./db/pgsql-data:/var/lib/postgresql/dataapp:image:node:13.2.0ports:-"8080:8080"-"21337:1337"volumes:-./app:/srcworking_dir:/src/test-app#strapiに合わせて変更tty:truedepends_on:-postgrescommand:yarn develop# develop起動用にコマンドを指定

ストップする

docker-compose.ymlのあるディレクトリで。

docker-compose stop

(おまけ)ホストからDBにアクセス

基本的にデフォルトです。パスワードはdocker-compose.ymlPOSTGRES_PASSWORDに入力したものが使われます。
コンテナ間の通信とはホスト設定が違います。

項目
Host127.0.0.1
Port5432
Userpostgres
Passwordsecret
Databasepostgres

(おまけ)APP(GUI)を活用する

Docker composeはKitematicを、ホストからのDB接続はTablePlus(or 他のGUIクライアント)の利用を推奨。
Kitematicを使えば、上記の[コンテナに入る]はボタンクリックで出来るようになり、コマンドやサービス名を覚えていなくてもいいですし、DBはちまちまコマンド叩くよりGUIで情報量を増やすほうが作業効率がいいと思います。

まとめ

これはstrapiのサンプルですが、もちろんNode.js製のフレームワークであればなんでも応用可能です。
なにかやってみたいと思ったタイミングで、プロジェクトのディレクトリを作る→このdocker-compose.ymlを置いて微調整(nodeのバージョンなど)、いらなくなったら即捨てる、でいかがでしょうか。

LINE Messaging APIのテンプレートメッセージでチャットBOTを作る

$
0
0

この記事は室蘭工業大学データサイエンス研究室の DSL Advent Calendar 2019 16日目の記事です。M1の @romorimoriが担当します。

はじめに

LINEが提供しているMessaging APIには様々なメッセージタイプのテンプレートが用意されていて、個人でも簡単にチャットBOTを作ることができます。

この記事では確認テンプレートやポストバックアクションなどの機能を使って、晩ご飯をレコメンドしてくれるチャットBOTを作っていきます。

完成品

image.png
チャットBOTに"晩ご飯"とメッセージを送ると、室蘭工業大学から徒歩圏内のお店をレコメンドしてくれます。
レコメンド機能に関しては作り込んでいないので、リストからランダムに選ばれるだけです。

構成図

image.png
(引用:LINE API ドキュメント )

環境&使うもの

今回はNode.jsでBOTサーバーを開発していきます。
また、開発したBOTサーバーはngrokで外部公開してテストします。
ngrokの解説はわかりやすい日本語記事がありましたので、こちらを引用させていただきます。(テストサーバーへのアップが面倒なときはngrokでローカル環境を外部公開してみよう

MacOS Catalina 10.15.1
node v11.14.0
npm 6.7.0
LINEアカウント(普段使いのものでOK)
ngrok version 2.3.34

開発

以下のような流れで開発していきます。

  • チャットBOTのChannelの開設&設定
  • チャットサーバーを開発
  • ngrokで公開&Webhook設定
  • スマホアプリからメッセージを送ってみる

Channelの開設

チャットBOT用のLINEアカウントを作成します。アカウントはLINE Developer コンソールではChannelとして管理されます。
まずLINE Developer コンソールで自分の普段使いしているLINEアカウントでログインします。初回は開発者登録が求められるので画面に従って必要事項を入力し登録します。

登録が済んだら、最初にプロバイダーを作成します。プロバイダーとはこれから作成するチャットBOTの提供元として表示される情報です。

プロバイダーのChannel管理画面
スクリーンショット 2019-12-15 15.07.13.png

次にChannelを作成します。今回はMessaging APIを選択します。
アプリ名などの必要項目を入力していき、Channelを作成します。
スクリーンショット 2019-12-15 15.18.10.png

これでChannelが開設できました。
次に、BOTサーバーの開発に必要な設定を行っていきます。

アクセストークンとChannel Secretの発行

Messaging API設定タブに移動し、チャネルアクセストークンを発行します。このトークンは自分で開発したチャットサーバーからMessaging APIを呼び出すのに必要になります。
アクセストークン発行.png

Channel Secretも同様に、API呼び出しに必要になります。基本設定タブにあります。
image.png

アクセストークンとChannel Secretを環境変数へセット

MacOS環境の場合、exportコマンドで環境変数をセットできます。

$export BANGOHAN_RECOMMEND_ACCESS_TOKEN="xxxxxxxxxxxxx"$export BANGOHAN_RECOMMEND_SECRET_KEY="xxxxxxxxxxxx"

LINE公式アカウント機能の設定

自動応答メッセージ友達追加時あいさつは使わないので、「利用しない」を選択しておきます。

これでChannelの設定はほぼ完了です。これからチャットBOTの開発をしていくのですが、Webhook URLの設定をあとから行うので、このページは開いたまま開発を進めていきます。

チャットBOTサーバーを開発

今回はNode.jsを使って開発していきます。
プロジェクトディレクトリを作ってnpm initします。

$mkdir button-message
$cd button-message/
$ npm init --yes

開発に必要なnpmパッケージをインストールしておきます。

$ npm install--save express @line/bot-sdk
  • Express : Node.js向けのWebアプリケーションフレームワーク。リクエストの処理が楽になります。
  • @line/bot-sdk : 公式が提供しているLINE Messaging APIのSDK(Software Development Kit)のNode.js版。Messaging APIを組み込んだBOTアプリの開発が簡単にできるようになります。

次に、プログラムの雛形を記述します。

constexpress=require('express');// expressインポートconstapp=express();constline=require('@line/bot-sdk');// sdkインポートconstconfig={channelAccessToken:process.env.BANGOHAN_RECOMMEND_ACCESS_TOKEN,// 環境変数からアクセストークンをセットchannelSecret:process.env.BANGOHAN_RECOMMEND_SECRET_KEY// 環境変数からChannel Secretをセット};constclient=newline.Client(config);// APIコールのためのクライアントインスタンスを作成constport=process.env.PORT||3000;// ポートを環境変数or3000で設定app.listen(port,()=>console.log(`Listening on port ${port}...`));// WebHookのエンドポイントapp.post('/hook',line.middleware(config),(req,res)=>lineBot(req,res));// line bot 本体functionlineBot(req,res){res.status(200).end();// すぐにLINE側にステータスコード200でレスポンス// チャットボットの処理をかいていく}
  • configには、先ほどLINE DevelopersのChannel管理画面で取得したアクセストークンとChannel Secretを設定します。これらはセキュリティの面から環境変数に設定してそこから取得しています。
  • Webhookを受け取るためのエンドポイントは/hookにします。 WebhookはLINEでBotに関連するイベントが発生した場合に、そのイベントをチャットBOTに通知してもらうためのアクセスポイント(URL)です。
  • チャットBOTのメインの処理を行う関数では、大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャを参考に、リクエストを受け取るとすぐにステータスコード200でレスポンスを行います。

チャットBOT本体のメソッド

今回実装したい機能は

  • Botに”晩ご飯”とメッセージを送ると、お店のリストからランダムに確認テンプレート形式のメッセージで返信する。
  • Botの返信に対して"NO"ボタンをタップすると、postbackアクションを実行して別の案を返信する。
  • Botの返信に対して”YES”ボタンをタップすると、messageアクションを実行する。

LINE APIのメッセージオブジェクトのメッセージタイプは9種類あり、今回はテンプレートメッセージのひとつである確認テンプレートを使っていきます。テンプレートメッセージには事前定義されたレイアウトがいくつか用意されています。また、アクションを使うことでユーザとのやりとりができたりします。
確認テンプレートは下のような形式です。(引用元:LINE Developersドキュメント
image.png

本体の処理を以下のように記述します。

// 室蘭工業大学の学生の晩ご飯一覧constbangohanList=["SAINO","章吉","もっちゃん","なかよし","学食","夕月庵","チャイナ","コンビニ","自炊"];// line bot 本体functionlineBot(req,res){res.status(200).end();// すぐにLINE側にステータスコード200でレスポンスconstpromises=[];// すべてのイベント処理のpromiseを格納する配列constevents=req.body.events;// イベントオブジェクト// イベントオブジェクトを処理events.forEach((event)=>{// イベントタイプが"message"で、typeが"text"だった場合if(event.type==="message"&&event.message.type=="text"){// メッセージの内容が”晩ご飯”なら晩ご飯をレコメンドするif(event.message.text==="晩ご飯"){constbangohan=bangohanList[Math.floor(Math.random()*bangohanList.length)];// 晩ご飯リストからランダムに要素を取得promises.push(client.replyMessage(event.replyToken,{"type":"template","altText":"晩ご飯をレコメンドします","template":{"type":"confirm","text":`今日の晩ご飯は${bangohan}でどう?`,"actions":[{"type":"postback",//"NO"が押されたらpostbackアクション"label":"NO","data":JSON.stringify({"action":"no"})},{"type":"message",//"YES"が押されたらmessageアクション"label":"YES","text":`今日の晩ご飯は${bangohan}で決まり!`}]}}));}}// イベントタイプが"postback"だった場合elseif(event.type==="postback"){// noボタンが押されていた場合if(JSON.parse(event.postback.data).action==="no"){constbangohan=bangohanList[Math.floor(Math.random()*bangohanList.length)];promises.push(client.replyMessage(event.replyToken,{"type":"template","altText":"晩ご飯をレコメンドします","template":{"type":"confirm","text":`それなら${bangohan}はどう?`,"actions":[{"type":"postback","label":"NO","data":JSON.stringify({"action":"no"})},{"type":"message","label":"YES","text":`今日の晩ご飯は${bangohan}で決まり!`}]}}));}}});// 全プロミスを処理したらログを出力Promise.all(promises).then(console.log(`promise completed\n`));}

この関数では、ユーザが送ってきたメッセージやアクションのリクエストに対してメッセージを作成し、返信します。

具体的には、リクエストからイベントオブジェクトを取り出し、イベントのタイプがmessagepostbackかを判別して、それぞれ別のメッセージオブジェクトを返信します。
@line/bot-sdkのクライアントインスタンスを使うことで、メッセージイベントに対しての返信をclient.replyMessage( "リプライトークン", {メッセージオブジェクト} )というように書くことができます。

メッセージオブジェクト

今回使用したメッセージオブジェクトを表にまとめてみます。メッセージタイプによって必須のプロパティなどは変わってきます。詳しくはLINE Developersドキュメントに乗っています。

プロパティ説明
typeメッセージタイプを設定する。今回はテンプレートメッセージが使いたいので"template"を設定
altText代替テキスト、通知とかで表示される文。
template テンプレートオブジェクト

テンプレートオブジェクト

次に、今回使ったテンプレートオブジェクトのプロパティを表にまとめます。今回は確認テンプレートをつかいましたが、こちらもテンプレートの種類によってプロパティは変わります。

プロパティ説明
typeテンプレートタイプを設定する。今回は確認テンプレートが使いたいので"confirm"を設定
textテンプレートの本文。今回はレコメンドの内容を設定しました。
actionsアクションオブジェクト。今回使った確認テンプレートには2つのボタンがあり、それぞれが押された時のアクションを設定できます。

アクションオブジェクト

アクションのタイプは"type"プロパティで設定できます。今回使ったもの以外にも日時選択アクションやカメラアクションなど、様々な機能があります。

・postbackアクション
このアクションが関連づけられたコントロールがタップされると、dataプロパティに指定された文字列を含むポストバックイベントが、Webhookを介して返されます。
今回の場合は、NOボタンをタップすると文字列{"action":"no"}が返されます。JSON文字列で送信することで、そのあとのイベント解析が楽になります。

・messageアクション
このアクションが関連づけられたコントロールがタップされると、textプロパティの文字列がユーザーからのメッセージとして送信されます。

ngrokで公開する

チャットBOTサーバーを実行します。
ローカル環境の3000ポートでサーバーが動いているので、3000ポートをngrokを使って公開します。
image.png
HTTPSのアドレスが取得できるので、Channel管理画面を開き、これをWebhook設定の部分に設定し、Webhookの利用をONにします。
image.png

完成したもの

Channel管理画面でQRコードを読み取ることでチャットBOTを友達追加できるので、追加してメッセージを送ってみます。
image.png

うまく動いてくれました。

  • ”晩ご飯”とメッセージを送ると、”今日のご飯は〜でどう?”と確認テンプレートを使った提案をしてくれています。
  • 写真では伝わりにくいですが、NOボタンをタップすると”それなら〜はどう?”と再提案してくれます。
  • YESをタップするとmessageアクションが実行されて、ユーザ側から”今日の晩ご飯は〜で決まり!”と提携文が返されます。
  • また、スタンプはtextタイプではないので何も実行されません。

おわりに

簡単にですが、LINE APIを使ったチャットBOT開発の流れを書かせていただきました。LINE APIには、ここで紹介した以外にもたくさんの機能があり、おもしろいWEBサービスを作ることができるとおもいます。

今年もあと少しなので、がんばりましょう!!!!!!!!!!!!!!!!

参考

LINE Developersドキュメント
大量メッセージが来ても安心なLINE BOTサーバのアーキテクチャ
Webhookとは?
テストサーバーへのアップが面倒なときはngrokでローカル環境を外部公開してみよう

フロントエンジニアにおくるstrapiことはじめ[2019/12版]

$
0
0

Strapi

strapiはNode.js製のオープンソースHeadlessCMSです。3.0.0-alphaからbetaになったことで、導入手順などが変わったようなので、現時点での最新版の導入方法のメモになります。

公式のクイックスタートガイド

下準備

  • Node.js
  • データベース(文中ではPostgresQL)

ちなみに下記環境で書いています。
- macOS Catalina 10.15.1
- Chrome バージョン: 79.0.3945.79

strapi公式の求める3.0.0-beta時点での各種バージョン

# Node.js
Node.js >= 10.x
NPM >= 6.x

# Database
MongoDB >= 3.6
MySQL >= 5.6
MariaDB >= 10.1
PostgreSQL >= 10
SQLite >= 3

https://strapi.io/documentation/3.0.0-beta.x/getting-started/install-requirements.html

(おまけ)docker-composeで環境を用意する記事を書きました

Docker composeでstrapi+PostgreSQL開発環境を作る

このチュートリアルと合わせて読んで頂く場合、ホストやURL辺りは少し読み替えていただく必要があります。が、手っ取り早いのでおすすめ。このチュートリアルやった直後に捨てられます!(要らなくなったコンテナやイメージの捨て方はおググりくださいませ…)

Node.js

ローカルマシンのグローバルにNode.jsをインストールされる場合は、nodenv, nodebrewなどでバージョン管理推奨です。
僕は最近管理が億劫になってきたので、Dockerでローカル開発環境作ってます。インストール済みNode.jsを使ってローカルで初める方が多いですかね。

今回は13.2.0の環境を用意してみました。

# node -v
v13.2.0

DB

DBこそMacにインストールして、というのはなにかと面倒になりがちなので、Dockerで開発環境を作ることをおすすめします。

こちらでとてもシンプルなPostgreSQLをDockerで立ち上げる方法を紹介してくださってます。
公式Dockerイメージ PostgreSQL メモ - Qiita

僕もだいたいこの流れでPostgresのDockerを立ち上げています。

後述しますがこのようなDBは必須ではなく、お試しするだけの場合はquickstartで用意されるSQLiteでもよいです。僕は以前にすごく後悔したので、このチュートリアルではPostgresを使う流れで進めます。

# psql --version
psql (PostgreSQL) 10.11

strapiのapp作成

strapiのドキュメントではyarn推しですが、npm(npx)でも問題ないようです。

Dockerの場合はコンテナの中の任意のディレクトリで、ローカルではプロジェクトを管理しやすい場所で、下記を実行。app名のディレクトリが作成され、自動的にセットアップされます。

npx create-strapi-app <app名>

今回は自作Google拡張「still-useful」のためのバックエンドを構築する想定なので以下のようにしました。

npx create-strapi-app still-useful

実行すると対話型で必要事項を入力していくようになります。前述のようにDockerなどでローカルにDBを立ち上げた場合はこのような感じになるかと。(両方をDockerにした場合は、Host設定に注意)

$ npx create-strapi-app still-useful
npx: 85個のパッケージを11.033秒でインストールしました。
Creating a new Strapi application at /Users/teixan/htdocs/still-useful/still-useful.

? Choose your installation type Custom (manual settings)
? Choose your default database client postgres
? Database name: postgres
? Host: 127.0.0.1
? Port: 5432
? Username: postgres
? Password: ******
? Enable SSL connection: No

数分で準備が完了します。以下ログ。

Creating a project with custom database options.
Creating files.
Dependencies installed successfully.Your application was created at /Users/teixan/htdocs/still-useful/still-useful.
Available commands in your project:

  yarn develop
  Start Strapi in watch mode.

  yarn start
  Start Strapi without watch mode.

  yarn build
  Build Strapi admin panel.

  yarn strapi
  Display all available commands.

You can start by doing:

  cd /Users/teixan/htdocs/still-useful/still-useful
  yarn develop

(余談)なぜ--quickstartを使わないのか

strapiには--quickstartというファーストステップ用の素晴らしいオプションが用意されています。

npx create-strapi-app <app名> --quickstart

何も設定の必要がなく、コマンドを実行するだけで管理画面を開くところまでやってくれます。初めて触る際にはとてもおすすめです。
quickstartはデータベースにSQLiteを用いており、お手軽で別途DBを用意する必要がないのがお手軽でよいです。しかし、このappはHerokuにて運用する予定で、HerokuではSQLiteのデータ永続化が出来ないようです。なので、Postgresを使うためにquickstartは使いません。

SQLite on Heroku | Heroku Dev Center

create-strapi-appのこのステップでpostgresを選ぶ(quickstartでは省略される)

? Choose your default database client

起動

前述のcreate-strapi-appの最後にこうあるので、

〜略〜
You can start by doing:

  cd /Users/teixan/htdocs/still-useful/still-useful
  yarn develop

導入したフォルダに移動して、yarn develop(もしくはnpx strapi develop(もしくはnpm run strapi develop))してみましょう。

$ npx strapi develop

> test-app@0.1.0 strapi /Users/teixan/htdocs/test-postgres/test-app
> strapi "develop"


 Project information                                                          

┌────────────────────┬──────────────────────────────────────────────────┐
│ Time               │ Fri Dec 13 2019 18:32:41 GMT+0900 (GMT+09:00)    │
│ Launched in        │ 5909 ms                                          │
│ Environment        │ development                                      │
│ Process PID        │ 7799                                             │
│ Version            │ 3.0.0-beta.17.8 (node v10.16.3)                  │
└────────────────────┴──────────────────────────────────────────────────┘

 Actions available                                                            

Welcome back!
To manage your project 🚀, go to the administration panel at:
http://localhost:1337/admin

To access the server ⚡️, go to:
http://localhost:1337

(もしかしたら初回はちょっと表示内容が違ったかもしれません。)
導入手順としては終わりです。もうコマンド実行などの手順は、コミットやデプロイまでありません。

管理画面(http://localhost:1337/admin)のURLが案内されています。アクセスしてみると、管理者登録フォームが表示されます。管理者を作成してログインしましょう。
register.jpg

以降は同じURLでログインフォームが表示されるようになります。

login.jpg

「コンテンツタイプ」を作成する

コンテンツタイプとは、どのような入出力項目を持った入れ物を作っておくかという感じで、WPで言うところのカスタム投稿タイプ、PHPのフレームワークで言うところのModel(+Controller)的なものです。一般的なブログに、[タイトル][本文][カテゴリ]と入力するようになっているのは、そういう入れ物があるからです。

Strapiでは管理画面上で新規作成や管理が可能ですが、本番環境(Production)では、コンテンツタイプを作成することができません。
なので、ローカル(Development)で予め作成->デプロイが必要です。

サイドナビ[コンテンツタイプ作成] - [コンテンツタイプを追加]

add-content-type-2.jpg

この後はフィールドを追加していきますが、設計などに不慣れな方はとりあえず[String] - 名前に[Title]など入力し、[Done]でいいです。

忘れがちな[保存]

save-2.jpg

忘れないで!

アクセス権限の設定

サイドナビ[ロールと権限] - [Public]

今作ったコンテンツタイプに対して、どのようなアクセスを許可するかを設定します。
少しわかりにくいですが、今回は外からの(Publicな)アクセスを許可しておく(誰でもAPIにアクセスできるようにしておく)必要があるので、[ロールと権限] - [Public]をクリック。
role-1.jpg

そうすると、下部の権限の枠に先程作ったコンテントタイプが表示されているかと思います。僕は今回はfind, create, updateなどをオンにしました。

role-2.jpg

保存を忘れずに。

サンプルデータを入力してみる

サイドナビ上の、作ったコンテントタイプをクリック

一覧部分に「〜〜はありません」と表示されていると思います。
[〜〜を追加]をクリック

post-sample_01-2.jpg

先程作ったコンテントタイプのフィールドが表示されています。
適当にサンプルデータを入力しておきます。
[保存]をクリック
post-sample_02-2.jpg

前画面に戻り、今入力したものが表示されています。
post-sample_03.jpg

コンテンツの準備が完了。

アクセスを試してみる

権限の設定時にお気づきかもしれませんが、find, createなどの各アクションの項目をクリックした際に右下にメソッド(GET, POSTなど)とルート(URL)が表示されています。歯車アイコンをクリックすることでも確認できます。

GET

例えば、この例ではコンテンツタイプpostsに対してfindを実行するルートが、GET/postsだと表示されています。(このサンプルのコンテントタイプがpostsPOSTと混同しやすく恐縮です…)
url.jpg

パブリック設定でGETなのでそのままブラウザで実行して確認できます。
今回はベースのURLがhttp://localhost:1337/なので、http://localhost:1337/postsになります。

http://localhost:1337/postsにブラウザでアクセス
access.jpg

先程入力したデータ(+日付)のデータが表示されました。
Chrome拡張であるJSONViewなどをインストールしておくとこのように整えて表示してくれるのでおすすめです。

コンテンツをこの管理画面上でしか入力せず、APIとしてはGETリクエストしか扱わない場合はこれでバッチリです。個別のエントリーを取得する場合は末尾にidをつけたりすることになりますが、デフォルトで用意されているものに関しては下記リファレンスを読めばだいたい分かるようになっています。

API Endpoints | Strapi Documentation

PUTやPOST

フロントエンドに依存しますので、具体的な手法は省きますが、確認だけはしておきましょう。
バックエンド構築時の確認などにはPostmanというAppが便利です。

Postman | The Collaboration Platform for API Development

少しUIの情報量が多くややこしいかもしれませんが、左上[New]から[Request]作成。

postman.jpg

[メソッド](GET or POST or etc)、[URL]を入力。
GETならそのまま[Send]で結果が表示されます。(先程のブラウザでの確認と同様です。)

今回はPOSTを試してみますので、リクエストの内容が必要になります。

[メソッド]部分をPOSTに、URLはhttp://localhost:1337/postsでした。
あとは[Header]と[Body]があればリクエストできます。

[Headerタブ]にてこちらをセット。

KeyValue
Content-Typeapplication/json

[Bodyタブ]では、[raw]を選択。作成したコンテントタイプのフィールドに合わせて。

{
    "title": "Yahoo",
    "url": "www.yahoo.com",
    "score": 888
}

[Send]をクリックで結果が表示されます。

うまく行かない場合は、権限の設定が間違っているか、[Header]の記述のミスなどが考えられますが、レスポンスの内容を確認すれば手がかりはあると思います。

権限の設定が間違っていて、403エラーとなっている例
{"statusCode":403,"error":"Forbidden","message":"Forbidden"}

うまく行っていれば、先程の管理画面上の一覧にも新しいエントリーが表示されます。

Postmanで開発を進めていくなら、Collectionなども活用し効率的に検証できるようにしておくととてもはかどります。ちなみに大きな開発で使う場合は有料となりますので、ご注意ください。
Postman | Plans & Pricing

完成

自前のAPIにデータを登録したり、そのデータを動的に読み出すことが出来るようになりました。

まとめ

さて、これで自分の定義した、コンテンツを格納でき / 読み出せるAPIが用意できました。これをインターネット上でアクセス出来るようにすれば、はれてオリジナルのAPIの公開が出来るわけです。

なぜフロントエンドエンジニアを対象にしたかというと、Node.js製のフロントエンドフレームワークを使えるのに(もしくは勉強中なのに)、バックエンドを作ることが出来ないがゆえに、「サービス」のイメージを出来ないままチュートリアルノマドしているフロントエンドエンジニアが多いのでは?と思った次第です。

もちろん、数多くのチュートリアルを完結したくさんの専門書を読了し、優れたコードで優れたUXを提供できるようになることはめちゃくちゃ重要で、それがフロントエンドエンジニアの本分であるということには、大賛成です。道をはずれてもらいたいわけではなく、少し幅を広げてもらいたいなぁと。

現場にいればフロントエンドエンジニアにはAPIの仕様書や既存のガッチガチに固まったJSONデータのリストなどがどこからともなく降ってくることもしばしばだと思いますが、それだけだとまぁ面白くないじゃないですか。とりあえずこんな感じで最低限のバックエンドは簡単につくれますし、Node.js製なのでカスタマイズも出来ます。「エンドポイントってこんな感じで作られてたんか〜」って思っていただくくらいでもいいかとは思うんですが、これをカスタマイズできればサービス・アプリを構築できることを覚えておいてもらえたらなぁという想いです。

以前作ったChrome拡張もstrapiで簡単なバックエンドを構築していますが、これにユーザー登録機能を実装して、マイページ作ったりしたらサービスになりそうじゃないですか?「デザインと仕様書から画面作る係」でいるのがそろそろ退屈だなぁって感じている方はこういう辺りを深めてもらって新しい世界に進んでもらえたらなぁと思います。

ところでフロントエンドエンジニア略してフロントエンジニアってことでいいんですかね?

金曜の夜になったら会社の Slack 通知を自動でミュートしたい

$
0
0

はじめに

「休日は会社の Slack をミュートしておきたい!」という要望は普通にあると思うのですが、
2019年12月15日現在、Slackには特定の曜日に自動で「おやすみモード」にする機能はありません。

そこで色々と試してみたのですが Zapier(または IFTTT)で Slack APIを叩く方法が無料かつ最も簡単にできたので、
本記事ではその手順を解説していきます。

手順

まず大まかにやることをまとめると、

  • Slack 側で API を利用できるよう設定する
  • IFTTT / Zapier 側で「時刻が金曜の21時であるとき」「Webhook / JavaScriptから API を叩く」アプレットを作る

の2つです。

Slack側の設定

Slack 側では Web API の利用を許可し OAuth トークンを取得する必要があります。

そのためには Slack App を作成しなくてはならないので、以下では必要最低限な App の作り方を解説します。

まず Slack APIのページにアクセスし、中央の [Start Building] ボタンを押しましょう。

スクリーンショット 2019-12-15 1.14.03.png

APP 名を適当に入力し、どのワークスペースに作成するかを選択後、右下の [Create App] を押します。

スクリーンショット 2019-12-15 1.14.56.png

すると何かゴチャゴチャしたページに飛ぶので、Add features and functionality 節の右下辺りの [Permissions] を選択します。

スクリーンショット 2019-12-15 1.20.38.png

このページでどういう API の使用を許可するのかを設定します。

Scope 節の [Add an OAuth Scope] ボタンを押しましょう。
(大きい緑のボタンではなく下の白いボタンの方なので注意)

スクリーンショット 2019-12-15 1.23.14.png

今回利用するAPI の仕様によると、権限として dnd:write が必要と書いてあるので、検索し選択します。

スクリーンショット 2019-12-15 1.26.09.png

その後ページの一番上に戻ると、App がインストールできるようになっています。

スクリーンショット 2019-12-15 1.26.48.png

インストールに成功すると API を叩くのに必要な OAuth トークンが表示されます。

このトークンを知っている人は誰でも API を叩けるので管理には一定の注意が必要です。
(今回の場合はおやすみモードの切り替えができるだけと思いますが一応)

Slack 側の設定はこれで完了です。トークンだけ後々使用します。

IFTTT / Zapier側の手順

IFTTT と Zapier、どちらを選ぶべきか

どちらも似たようなサービスですが、基本的にはZapierの方が高機能と言えます。

IFTTT はアクションが一つしか登録できないなど制限は多いですが、
Webhook が無料で利用でき UI もわかりやすいため、単純な用途であれば IFTTT をオススメします。

Zapier はよりカスタマイズ性が高く、何よりNode.js や Python のコードを実行することができます
機能を細かく調整したい場合や拡張性を持たせたい場合は Zapier がいいと思います。

IFTTT の Webhook の使い方はググればたくさん出てくると思うので、
本記事では Zapier の Node.js からAPIを叩く方法をご紹介します。

Zapierの手順

Zapier への登録方法は割愛します。

ログイン後、右上の [Make a Zap!] ボタンを押してください。

スクリーンショット 2019-12-15 1.46.04.png

まずはトリガーとなる App を選択してと言われるので、時刻をトリガーとする [Schedule by Zapier] を選択します。

スクリーンショット 2019-12-15 12.34.41.png

次に、どういう条件でトリガーさせるかを聞かれます。

トリガーさせたい曜日が1つだけの場合は every week, 複数ある場合は everyday を選択するといいです。

今回は説明のため everyday を選択します。

スクリーンショット 2019-12-15 12.36.43.png

上で everyday を選択するとトリガーさせる時刻を聞かれます。
金曜の午後9時以降は会社のことを忘れたいので「9pm」を選択し、CONTINUE します。

スクリーンショット 2019-12-15 12.38.50.png

次の画面で [TEST & CONTINUE] というボタンが出るので、押下してトリガーの設定を完了します。

次はアクションの設定です。下の [Do this...] をクリックしましょう。

スクリーンショット 2019-12-15 12.42.17.png

App は Code by Zapier を選択します。

スクリーンショット 2019-12-15 12.44.30.png

Node.js の ver 10.x.x を選択します。

スクリーンショット 2019-12-15 12.45.29.png

次に実行したいコードを記述していくのですが、その前に Zapier の Code のしくみをざっくり解説します。

Input Data

Code の設定項目には「Input Data」と「Code」があるのですが、まずはInput Dataから説明していきます。

スクリーンショット 2019-12-15 12.53.34.png

Input Dataでは、一つ前のトリガーやアクションからどういうデータをどのように受け取るかを設定します。

具体的にはここで「データのプロパティ名」と「データの種類」を入力しておくと、
コード内でinputData.プロパティ名の形でそのデータを取得できるようになります。

今回はスケジュールトリガーから曜日データを受けとりたいので、Pretty Day Of Week(整形された曜日情報)を、dayOfWeekのプロパティ名で受け取れるように設定しています。

Code

Code 節には実行するコードを書くのですが、少しクセがあって、

  • コード全体が async function にラップされている
  • オブジェクトまたはオブジェクトの配列をoutputという定義済み変数に入れなければならない

となっています。

outputに入ったものが次のアクションに渡されるしくみになっていて、
次のアクションがない場合でも必ず値を入れなければなりません。

また async function なのでawaitを使用することが可能です。
というか非同期の場合にawaitを使わないとoutputに何も入ってないよ!とエラーになる可能性があります。

実際のコードは以下のようになりました。

code
consthttps=require('https');constendpoint='https://slack.com/api/dnd.setSnooze'consttoken='<Slackで取得したトークンを入れてね!>'if(inputData.dayOfWeek==='Friday'){constnumMinutes=60*(48+9)// 2 days and 9 hours.consturl=`${endpoint}?token=${token}&num_minutes=${numMinutes}`// オブジェクトまたはオブジェクトの配列を確実に返さないとエラーになるoutput=awaitfetch(url).catch(error=>{returnerror})}

※細かいことを言えば、トークンを直書きしているのでZapier の中の人が見ようと思えば見ることができます。それが気になる方はセキュリティ強化された Zapier Platformを利用するなどしてください。

上記コードを入力後、[CONTINUE] を押して次のような表示が出れば・・

スクリーンショット 2019-12-15 13.17.00.png

あとは作ったアプレットを ON にするだけで全て完了です!
お疲れ様でした。

補足

  • シンプルな要件のわりにはそれなりに実現が大変でした。公式で機能を作ってくれるといいですね。

  • 今回の方法だと週末以外の休日には対応できませんが、カレンダーと連携させるとより柔軟にミュートができるかもしれません。

Viewing all 8898 articles
Browse latest View live