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

IBM CloudのCloud Foundryデプロイの無料枠のメモリ制限

$
0
0

IBM Cloudを試してみてます。

無料枠を使って試してましたが2個目のアプリケーションのデプロイを試そうとしたら下記エラーでした。

You have exceeded your organization's memory limit: app requested more memory than available

無料枠の制限を超えたようですね。

$ ibmcloud cf push    
'cf push'を起動しています...

nobisuke@xxxx.xxx.xxx としてマニフェストから組織 n0bisuke / スペース dev にプッシュしています...
マニフェスト・ファイル /Users/n0bisuke/playground/get-started-node/manifest.yml を使用しています
アプリ情報を取得しています...
これらの属性でアプリを作成しています...
+ 名前:       n0bisuke-testapp2
  パス:       /Users/n0bisuke/playground/get-started-node
+ メモリー:   256M
  経路:
+   n0bisuke-testapp2-xxxxxxxxx.mybluemix.net

アプリ n0bisuke-testapp2 を作成しています...
経路をマップしています...
ローカル・ファイルをリモート・キャッシュと比較しています...
Packaging files to upload...
ファイルをアップロードしています...
 37.27 KiB / 37.27 KiB [=====================================================================================================================] 100.00% 1s

API がファイルの処理を完了するのを待機しています...

アプリをステージングし、ログをトレースしています...
You have exceeded your organization's memory limit: app requested more memory than available
失敗

256Mまでは無料らしいのでどう割り当てるか問題らしい

1個目のアプリケーションで256M/256Mを利用してしまっていたのでこうなる模様

スクリーンショット 2021-03-23 22.16.14.png

256Mアプリケーションを2つ作ろうとしていた

  • アプリ1
manifest.yml
---applications:-name:n0bisuke-testapprandom-route:truememory:256M

色々間省略するけど

$ ibmcloud cf push

一つ目はこれでデプロイ

  • アプリ2
manifest.yml
---applications:-name:n0bisuke-testapp2random-route:truememory:256M

二つ目も256Mでデプロイしてた

などにしてnameを別のものにして新規アプリケーションとしてデプロイしようとすると今回のようなエラーになりました。

メモリ再割り当て

  • アプリ1を64M
manifest.yml
---applications:-name:n0bisuke-testapprandom-route:truememory:64M
  • アプリ2を128M
manifest.yml
---applications:-name:n0bisuke-testapp2random-route:truememory:128M

こんな割り当てにしたら二つのアプリケーションを無料枠内でデプロイできまました。

64Mx4まで作れるか実験

  • アプリ1を64M
manifest.yml
---applications:-name:n0bisuke-testapprandom-route:truememory:64M
  • アプリ2を64M
manifest.yml
---applications:-name:n0bisuke-testapp2random-route:truememory:64M
  • アプリ3を64M
manifest.yml
---applications:-name:n0bisuke-testapp3random-route:truememory:64M
  • アプリ4を64M
manifest.yml
---applications:-name:n0bisuke-testapp4random-route:truememory:64M

スクリーンショット 2021-03-23 22.34.32.png

問題なく4つまでいけました。

5つ目を作ろうとするとエラーですね

You have exceeded your organization's memory limit: app requested more memory than available
失敗

まとめ

IBM CloudのPaaS無料枠 雰囲気掴んできました。

メモリが最大256Mまで割り当てで、一つのアプリの最低割り当てが64Mなので

  • 64M * 4つ
  • 128M * 2
  • 128M * 1 + 64M * 2
  • 256M * 1

など各自選択して利用できそうです。


Jest --watch Error: EMFILE: too many open files, watch

$
0
0

急にjest --watchができなくなったので、調べてみた。

現象

❯ yarn test:watch
yarn run v1.22.10
$cross-env NODE_ENV=test jest --config=jest.config.json --watch2021-03-23 20:35 node[59503] (FSEvents.framework) FSEventStreamStart: register_with_server: ERROR: f2d_register_rpc() =>(null)(-22)2021-03-23 20:35 node[59503] (FSEvents.framework) FSEventStreamStart: register_with_server: ERROR: f2d_register_rpc() =>(null)(-22)2021-03-23 20:35 node[59503] (FSEvents.framework) FSEventStreamStart: register_with_server: ERROR: f2d_register_rpc() =>(null)(-22)events.js:292
      throw er;// Unhandled 'error' event
      ^

Error: EMFILE: too many open files, watch
    at FSEvent.FSWatcher._handle.onchange (internal/fs/watchers.js:178:28)
Emitted 'error' event on FSWatcher instance at:
    at FSEvent.FSWatcher._handle.onchange (internal/fs/watchers.js:184:12) {
  errno: -24,
  syscall: 'watch',
  code: 'EMFILE',
  filename: null
}
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

環境

ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G14033

❯ node -v
v14.15.4
❯ yarn -v
1.22.10
❯ yarn jest --version
v20.0.4

最初はwatchmanはない。
yarnだとwatchmonがあるので間違いに注意。

❯ which watchman
watchman not found
❯ brew install watchman

長い更新時間

❯ which watchman
/usr/local/bin/watchman

実行結果

❯ yarn test:watch
yarn run v1.22.10
$ cross-env NODE_ENV=test jest --config=jest.config.json --watch

 PASS  src/tests/components/Header.test.js
 PASS  src/tests/components/ExpenseDashboardPage.test.js
 PASS  src/tests/components/AddExpensePage.test.js
 PASS  src/tests/actions/expenses.test.js
 PASS  src/tests/components/EditExpensePage.test.js
 PASS  src/tests/components/ConfModal.test.js
 PASS  src/tests/components/LoginPage.test.js
 PASS  src/tests/actions/auth.test.js

Test Suites: 8 passed, 8 total
Tests:       25 passed, 25 total
Snapshots:   6 passed, 6 total
Time:        7.874s, estimated 8s
Ran all test suites related to changed files.

✨  Done in 240.85s.

確かに実行できるようになった。

参考

JestのwatchがError watching file for changes: EMFILE でコケるときの対処
macOS Sierra #1767でウォッチモードが機能しなくなりました

express-sessionをMongodbで。

$
0
0

初めに

今回の記事は対して裏取りをしておりませんので憶測で物をいうことが多いと思います。気になりすぎて夜も眠れネェヨクソが!という方は"フッザケンじゃねぇよ!"を接頭辞にコメントを寄せてもらえればと思います。

connect-mongo

バージョンがアップしたのかどうなのかこちらのモジュールを日本語の記事を参考に使おうと思えば使えませんでした。というわけで、公式のページを参考にちょこっとだけ覚書していきます。解説はしません。というかできません。(まだべんきょうちゅー)

node.js
varsession=require('express-session');varMongoStore=require('connect-mongo')(session);⇓⇓⇓varsession=require('express-session');varMongoStore=require('connect-mongo');

上のようにしてrequireしてみようとすれば、

TypeError: Class constructor MongoStore cannot be invoked without 'new' at Object.<anonymous>

というエラーが出てきて、よくわからなかったので公式のページを見てみれば下の様に完結of完結に書いてありました。・・・よくわかんないけど、まぁ、いっか!バージョン違いとか多分そんなんでしょ。

node.js
varoptions={db:'example',host:'localhost',port:27017,...etc...clear_interval:60*60}⇓⇓⇓varoptions={mongoUrl:'mongodb://username:password@localhost:27017/database',autoRemove:'native'};

optionsと分けるか分けないかは個人の自由ですけれど、自分は分けたい派の人間ですので分けました。
このoptionsもhostとかdbとかportとかあった・・・のかな?あったみたいですが、mongoUrlと簡潔になりましたね。mongodbモジュールでアクセスするときと全く同じURLでいけるようです。

node.js
app.use(session({secret:'foo',store:newMongoStore(options)}));⇓⇓⇓app.use(session({secret:'foo',store:MongoStore.create(options)}));

sessionの中身です。new MongoStore(options)で出来たようですが、自分がやってみれば出来なかったですので、公式のページをみたところ、MongoStore.create(options)でできるようです。ほんといろいろと変わったんですね。できることなら変えないでほしかったんですけど。(楽々参考にできるドキュメントがなくなるから)

最後

というわけで、ほんとに最低限ですけど、この設定でエラーは起きませんでした。その代わり警告は出ますけどね。しかし優秀なプログラマーのあなた方であれば、警告の一つや二つは解決することなんて簡単でしょう!
というわけで終わりです。
困った人の助けに少しでもなればよかったのですが。

IBM CloudでNode.jsアプリケーションをデプロイするまでの流れ

$
0
0

IBM Cloudを使ってみようと思い触ってみたので主に自分用に残します。

終わってみるとそこまでハマる感じではないと思いますが、僕はハマったところがありました苦笑

1. Web上の管理画面側の設定

アカウントの作成

ライトアカウントという無料で作れるアカウントがあり、クレジットカード登録無しで登録できます。

https://www.ibm.com/jp-ja/cloud
スクリーンショット 2021-03-24 1.47.58.png

アカウント登録をしていきましょう。

アプリケーション作成まで

AWSやAzure、GCPなどと同様にIBM Cloudは色々なサービスの集合体の名称です。 今回はCloud Foundryというサービスを使っていきます。

リソースの作成から進めていきます。

スクリーンショット 2021-03-24 1.49.39.png

製品選択の画面になるので、Cloud Foundryを検索して選択します。

スクリーンショット 2021-03-24 1.52.12.png

Cloud Foundryの画面に遷移したら、作成ボタンからアプリケーション作成に進みます。

スクリーンショット 2021-03-24 1.53.42.png

Cloud Foundryアプリの作成

ロケーション選択ではとりあえずダラスを選択

ロケーション(リージョン)選択は現状だと、ダラス(us-south)以外は無料枠では利用できない模様です。ダラスを選択します。

料金プラン選択ではとりあえず64MBを選択

こちらの記事(IBM CloudのCloud Foundryデプロイの無料枠のメモリ制限)にもありますが256MBまで選択できますが、これを選択すると無料枠で他にアプリケーションが作れなくなってしまうので一旦64MBからスタートしていきます。

ランタイムの選択。今回はNode.jsで

ランタイム選択です。

スクリーンショット 2021-03-24 2.13.16.png

利用する言語を選択しましょう。

アプリ名を入力して作成

適当に名前をつけましょう。今回は、n0bisuke-testappとしました。

スクリーンショット 2021-03-24 2.14.15.png

この名前を後半のマニフェストファイル(後述)で指定します。

右側の作成ボタンがアクティブになると思うので作成します。

2. ローカルでの開発準備

ブラウザ上での作業は一旦ここまでにして、次はローカルで開発できるようにします。

まずはCLIツールをインストールします。

基本的な開発の流れはローカル開発 -> CLIツールでデプロイという流れになります。

CLIツールをインストールする

ibmcloudコマンドを使えるようにしますが、macOSは以下でインストールできます。

$ curl -sL https://ibm.biz/idt-installer | bash

補足: なんか内部でDocker使う模様ですが、Homebrew経由でインストールしてそうです。

スクリーンショット 2021-03-21 18.51.38.png

無事にインストール出来るとibmcloudコマンドが利用できるようになります。

$ ibmcloud 
名前:
  ibmcloud - IBM Cloud と対話するためのコマンド・ライン・ツール
詳しくは https://ibm.biz/cli-docs を参照してください

使用法:
  [environment variables] ibmcloud [global options] command[arguments...] [command options]

バージョン:
  1.4.0+4705d79-2021-02-24T21:58:03+00:00

省略

ibmcloud cfコマンドを利用できるようにする

最終的にibmcloud cf pushというコマンドでデプロイしますが、最初はibmcloud cfが利用できません。

ibmcloud cf installで追加インストールすることでibmcloudコマンドが機能拡張されます。

$ ibmcloud cf install

Cloud Foundry CLI をダウンロードしようとしています...
 8.93 MiB / 8.93 MiB [=======================================================] 100.00% 16s
9359027 バイトがダウンロードされました
/Users/n0bisuke/.bluemix/tmp/cf_845728579/cf-cli_6.53.0_osx.tgz に保存されました
Installing Cloud Foundry CLI...
OK
Cloud Foundry CLI is successfully installed

ロケーション(リージョン)をus-southに設定する

僕は初見でここの設定を間違えて永遠に先に進めませんでした苦笑

アプリケーション作成した際に、ロケーションをダラス(us-south)を設定していたので、ibmcloud target -rで設定しましょう。

$ ibmcloud target -r us-south

僕がみた公式チュートリアルにCLI側でのリージョン設定は載ってないのですが、設定しないと永遠と進めない雰囲気でした苦笑

CLIでログイン

アカウントにログインします。--ssoを付けるとブラウザ経由で認証します。--ssoオプション無しの場合そのままメールアドレスとパスワードを入力です。

$ ibmcloud login --sso

API エンドポイント: https://cloud.ibm.com
リージョン: us-south

https://identity-2.ap-north.iam.cloud.ibm.com/identity/passcode からワンタイム・コードを取得して、続行します。
デフォルトのブラウザーで URL を開きますか? [Y/n] >

表示されるURLをブラウザで開くとワンタイムパスコードが表示されるのでコピーしてCLIに貼り付けます。

認証されるとこんな感じでログインできます。

組織、スペースなどの単語が出ていますが次で設定していきます。

リソースグループは未設定でも大丈夫そうでした。

組織、スペースの設定

ibmcloud target --cfのコマンドで自動的に設定されます。

$ ibmcloud target --cf

補足: 別のやり方

ちなみにibmcloud target -o ORG_NAME -s SPACE_NAMEでも設定できます。(ibmcloud target --cfで自動設定してくれるので使わないかもしれないですが)

$ ibmcloud target -o n0bisuke -s dev

3. サンプルコードを作成

あとはNode.jsアプリケーションを作成していきます。

手元のNode.jsはv15.11.0を利用しています。

公式サンプルは色々載ってますが、シンプルなものを作っていきます。

Node.jsアプリケーションの作成

  • フォルダを作成して移動
$ mkdir ibm-cf-test
$ cd ibm-cf-test
  • Node.jsアプリケーション開発の準備としてnpm initとpackage.jsonを作成
$ npm init -y
  • Webサーバー用にexpressをインストール

利用するモジュールのexpressをインストールします。

$ npm i express
  • サンプルコードを作成

server.jsを作成し、簡単なWebサーバーを立ち上げるコードを書きます。

constexpress=require('express')constapp=express()app.get('/',function(req,res){res.send('Hello World')})constport=process.env.PORT||3000app.listen(port)

ここまでは通常のNode.jsアプリケーションと同様です。

マニフェストファイルを作成

ここが大事です。

manifest.ymlを作成し、以下を記述します。

このマニフェストファイルがあることでIBM Cloudにデプロイができます。

今回n0bisuke-testappというアプリ名でアプリを作成したので、nameにn0bisuke-testappと記述します。

また、作成時に64MBを指定したのでここも合わせて64MBにしましょう。

manifest.yml
---applications:-name:n0bisuke-testapprandom-route:truememory:64M

デプロイ

先程のibmcloud target --cfのコマンドで設定が完了していれば、以下のコマンドだけでデプロイできます。

$ ibmcloud cf push  

デプロイ時のログが表示されてデプロイが実行されていきます。
無事にデプロイされると以下のようなログが表示されます。

省略
・
・
・

アプリが開始するのを待機しています...

名前:                   n0bisuke-testapp
要求された状態:         started
経路:                   n0bisuke-testapp-xxxxxx
最終アップロード日時:   Wed 24 Mar 02:50:27 JST 2021
スタック:               cflinuxfs3
ビルドパック:           sdk-for-nodejs

タイプ:           web
インスタンス:     1/1
メモリー使用量:   64M
開始コマンド:     npm start
     状態   開始日時               cpu    メモリー       ディスク      詳細
#0   実行   2021-03-23T17:50:51Z   0.0%   64M の中の 0   1G の中の 0   

デプロイされたURLにアクセスするとハローワールドが表示されると思います。

スクリーンショット 2021-03-24 3.26.41.png

以上です、お疲れ様でした。

まとめ

振り返るとそんなにハマりポイントなさそうなのですが、僕の場合リージョン選択をなぜかCLI側で日本に設定してしまってずっとハマってました。

色々なリンクや古い情報なども散らばっていてけっこう苦労したり苦

初めて試す人は気をつけてください。

npm installなども普通にできるのでHerokuなどと同様の使い勝手ですね。

次はLINE BOT作成に進んでみようと思います。

Firestore: Reference型をJSONにするときの挙動を変える

$
0
0

前置き

例えば以下のようなデータをFunctionsでFirestoreのドキュメントusers/12345に入れたとする.

{"display_name":String,..."userDetail":Reference}

Functionsでこのデータを取ってきてJSONでクライアントに返すAPIを作る(error()とかsuccess()は別で作ってあるって言う体で...).

app.get("/users/:id",async(req,res,next)=>{constusers=awaitdb.doc(`accounts/${req.params.id}`).get();if(!users.exists){error(res,404,"users_created","You've not created first user.");return;}success(res,users.data());return;});

するとこんな感じのデータが返ってくる

{"display_name":"Bony_Chops",...."userDetail":{"_firestore":{"_settings":{"projectId":"nicha-nnct","firebaseVersion":"9.5.0","libName":"gccl","libVersion":"4.9.4 fire/9.5.0","ssl":false,....}....}...}}

Reference型がめっちゃでかい上ヤバそうな値を返してくる.これはまずい.

失敗例

Reference型の正体であるfirebase.firestore.DocumentReferenceクラスのtoJSONprototypeで上書きします(何故か補完でfirebase.default.firestore.DocumentReferenceだったのでそちらで...).とりあえず,そのReferenceが指すpathを返却するようにしてみます.

constfirebase=require("firebase");firebase.default.firestore.DocumentReference.prototype.toJSON=function(){returnthis.path;}

でもさっきの結果は変わりませんでした.なんで???

結論

Firestoreへのアクセスをdb = admin.firestore();で行っていたのでadmin.firestore.DocumentReferenceを使うのが適切だったみたいです.

constadmin=require('firebase-admin');admin.firestore.DocumentReference.prototype.toJSON=function(){returnthis.path;}

結果

{"display_name":"Bony_Chops",..."userDetail":"users_detail/12345"}

これでいい感じ.

Lambda関数から別アカウントのDynamoDBに接続

$
0
0

記事を書いた経緯

  • 初めてAWSを触った!記録取っておこう!
  • Lamdba関数から他アカウントのDynamoDBにアクセスを経験!個人的に詰まったところもあったので、同じミスはしないように記録として残そう!
  • ナレッジ化して共有できればいいなー

って感じです。初投稿なので、「ん?」って思うところがあるかもしれないですが、参考になれば幸いです。

環境

  • Typescript
  • DynamoDB
  • Lambda

まず驚いたこと

Lamdba関数からただ単にaws-sdkDynamoDB Clientを作ってアクセスをしたらResourceNotFoundExceptionになった。。。
なぜだ。。。

NGコード

import{DynamoDB}from"aws-sdk"constclient=newDynamoDB.DocumentClient()constparam={TableName:"sample",Key:{'id':1},}constresult=docClient.get(param)

結論

他アカウントにアクセスする際にAssume Roleを行い、権限を付与しなければいけなかった。
(今思えば、それはそうだよなーってなった。。endpointがわかれば誰でもアクセスできるなんてセキュリティアウトだろ!)

色々試行錯誤して、以下のコードで正常にレスポンスが返ってきた!
ヨシッ!

OKコード

import{DynamoDB,STS,Credentials}from"aws-sdk"conststs=newSTS()// Lambda実行アカウント情報constaccount=sts.getCallerIdentity((error:any,data:any)=>{if(error)returnerrorreturndata}).promise()// assume roleconstrole=sts.assumeRole({RoleArn:[IAM],//権限を付与するIAMのARNRoleSessionName:'sample',// ここはなんでもいいExternalId:account['Account']// 外部キーにアカウントIDを指定したケースなので、ここは任意}).promise()// クレデンシャルconstcredentials=newCredentials(role.Credentials.AccessKeyId,role.Credentials.SecretAccessKey,role.Credentials.SessionToken)// clientインスタンス作成constclient=newDynamoDB.DocumentClient({credentials})constparam={TableName:"sample",Key:{'id':1},}constresult=docClient.get(param)

今回省略した内容

  • Lamdba関数にAssume Roleの権限を与える
  • Assume Roleについて

参考資料

Cloud Foundry(IBM Cloud)でNode.jsの最新バージョンを利用する

$
0
0

IBM CloudのCloud Foundry、Node.jsの新しいバージョンを使うときに少し工夫が必要でした。

ibmcloud cf pushでデプロイした際のログから辿ってると、IBM Cloud側の対応状況と、Cloud Foundry側のドキュメントの対応状況が若干異なるみたいですね。

Cloud Foundryって元々独立してたけどIBMに吸収されたとかでしたっけ...

まずはNode.jsのバージョン指定無しの場合

特にバージョンを指定せずにデプロイすると以下のような警告が出ました。

デフォルトがNode.js v10系らしいです。流石に古いので警告が出てます。

   Downloading app package...
   Downloaded app package (635.9K)
   -----> IBM SDK for Node.js Buildpack v4.6-20210305-2036
          Based on Cloud Foundry Node.js Buildpack 1.7.44
   -----> Installing binaries
          engines.node (package.json): unspecified
          engines.npm (package.json): unspecified (use default)
          **WARNING** Node version not specified in package.json or .nvmrc. See: http://docs.cloudfoundry.org/buildpacks/node/node-tips.html
          Attempting to install: 10.24.0
   -----> Installing node 10.24.0
          Copy [/tmp/buildpacks/73dd77c5b9dcb13eb662490e81ac9cf2/nodejs/dependencies/aaf73e55b7db4c30b2da7af326e10a54/node_10.24.0_linux_x64_cflinuxfs3_db472e29.tgz]
          **WARNING** node 10.x.x will no longer be available in new buildpacks released after 2021-04-01.
          See: https://github.com/nodejs/Release
          Using default npm version: 6.14.11
   -----> Installing yarn 1.22.10

package.jsonに追記してNode.jsのバージョンを指定

**WARNING** Node version not specified in package.json or .nvmrc.**WARNING** node 10.x.x will no longer be available in new buildpacks released after 2021-04-01.とのことなので、バージョンをあげてみます。

package.jsonにenginesを追記します。

package.json
省略"engines":{"node":"14.x"},省略

ちなみに、v15にしようとしたら怒られました。まだ対応してない模様です。

**ERROR** Unable to install node: no match found for 15.x in [10.23.3 10.24.0 12.20.2 12.21.0 14.15.5 14.16.0]

manifest.ymlにbuildpacksの指定すると比較的新しいバージョンが利用できる

執筆時点だとNode.js v15.12.0が最新ですが、enginesに15.xを指定したら↑で書いたエラーになりました。

ただ、Cloud Foundryのnodejs-buildpackを指定することで最新が利用できそうです。

cloudfoundry/nodejs-buildpack

ここをみると、執筆時点でNode.js v15.11.0まで対応してそうでした。
(若干最新ではないけどほぼ最新に追従してそう)

先ほどはまでは、manifest.ymlにbuildpacksの指定をしないでデプロイしてましたが、以下のように指定するとbuildpacksにhttps://github.com/cloudfoundry/nodejs-buildpackを指定すると最新バージョンの利用できます。(現状だとNode.js v15.11.0まで対応)

manifest.yml
---applications:-name:n0bisuke-testapprandom-route:truememory:64Mbuildpacks:-https://github.com/cloudfoundry/nodejs-buildpack

これでv15系が使えるのでpackage.jsonでも指定します。

package.json
省略"engines":{"node":"15.x"},省略

ちなみに、process.versionsでバージョン情報を確認できるので、以下のようなコードを書きました。

server.js
constexpress=require('express')constapp=express()app.get('/',function(req,res){res.send(JSON.stringify(process.versions))})constport=process.env.PORT||3000app.listen(port)

デプロイして確認したらこんな感じでした。

スクリーンショット 2021-03-24 13.58.00.png

v15.11.0になってますね。

補足: 別の指定の仕方

IBM Cloud側のドキュメントにある指定の仕方だと以下のように記述できますが、こちらを利用した場合適用されたのがv15.7.0と若干ですが古いみたいでした。

https://cloud.ibm.com/docs/cloud-foundry?topic=cloud-foundry-deploy_apps&locale=ja

manifest.yml
---applications:-name:n0bisuke-testapprandom-route:truememory:64Mbuildpack:nodejs_buildpack

先ほどはbuildpacksでURL指定でしたが、こちらはbuildpackで名前指定という違いですね。

注意点: デプロイ時間が長いかも

体感的にですが、buildpacksを指定したデプロイの方がデプロイ時間が長い気がしています。

v15系でもv14系でも大丈夫って場合はbuildpacksを指定しないで14系を使った方がデプロイは早いかもしれません。

指定しないとSwiftのビルドパックなど関係なさそうなものもダウンロードしてそうなログが表示されるのですが、こっちの方がデプロイ時間が短い気がしているという謎状態 (体感なのでちゃんと調べてません。)

まとめ

現時点のバージョンですが以下のような対応状況でした。

  • 全く指定無し
    • v10になる
  • enginesに指定
    • v14系まで利用可能
  • manifest.jsonでビルドパック指定
    • buildpacksでURL指定: v15.11.0まで利用可能
    • buildpackで名前指定: v15.7.0まで利用可能

[AWS][nodejs] S3にある JPEG ファイルの EXIF 情報を削除して書き戻す

$
0
0

必要な npm パッケージは aws-sdk, file-type, piexif

S3.getObjectでデータ取得して、file-typeで JPEG 画像かチェックして、piexifで EXIF を削除して、S3.putObjectで書き戻す感じです。

constAWS=require('aws-sdk');constFyleType=require('file-type');constpiexif=require('piexif');constremoveExif=(region,bucket,key)=>{constS3=newAWS.S3({region:region});returnPromise.resolve({Bucket:bucket,Key:key}).then(params=>S3.getObject(params).promise()).then(data=>newPromise(async(resolve,reject)=>{// mime type のチェックconstfileInfo=awaitFyleType.fromBuffer(data.Body);if(fileInfo.mime==='image/jpeg'){data.ContentType=fileInfo.mime;resolve(data);}else{reject(Error("It's not jpeg format."));}})).then(data=>newPromise((resolve,reject)=>{// EXIF 情報の削除try{constnewBody=piexif.remove(data.Body.toString('binary'));resolve({Bucket:bucket,Key:key,ContentType:data.ContentType,Body:Buffer.from(newBody,'binary')});}catch(err){reject(err);}})).then(params=>S3.putObject(params).promise()).then(data=>Promise.resolve(data)).catch(err=>Promise.reject(err));}

Lambda ファンクションとして動くようにして、PUT のタイミングで EXIF 情報を削除するとか、Lambda@Edge で動くようにして EXIF 情報を秘匿するとかのサンプルにしてください。

Lambda@Edge で画像ファイルに対するレスポンスを編集するためのサンプルとしては HM さんのも参考になるかも
https://github.com/humanmade/tachyon


音声データからのキーワードを可視化する

$
0
0

はじめに

音声認識を使って何かアプリを作ってみたかったで、音声データからそのキーワードを抽出しすることができるツールを作ってみました。

平凡なアイデアかもしれないですが、、
録画・録音されたファイルから、その要約(キーワード)を抽出することができれば、情報のキャッチアップの効率が少しでも上がるのではないかと思います。
スクリーンショット 2021-03-24 7.12.41.png

前提

Node.js(Express)で実装

事前調査

使用API

  • Google Cloud Speech-to-Text API
  • COTOHA API

Google Cloud Speech-to-Text API

Speech-to-Text API はGoogleのAI技術を活用して、音声データをテキストに変換することができるAPIになります。使用の開始については、公式のドキュメントを参照してください。

  • マイクから入力されたストリーミング音声
  • wavやflacといった音声ファイル

音声認識の実行方法には上の二つが用意されており、このうち音声ファイルは、同期音声認識を使用して即時レスポンスを返す方法と、非同期音声認識を使用して長い音声ファイルを扱う方法があります。

また、音声ファイルの場合は、ローカルにあるファイルを読み込むか、Google Cloud Storage にアップロードされているファイルかを選択することができます。今回は、ローカルファイルから読み込む方法を使用し、Expressで実装しています。

COTOHA API

COTOHAとは自然言語処理や音声処理といった解析をAPIで手軽に利用出来るようにしたサービスです。

今回はこちらのキーワード抽出APIを使用します。このAPIは名称の通り、日本語で入力されたテキストの中から特徴的なフレーズ・単語を自動的に抽出し、文のキーワードとして出力することができます。

例えば「レストランで昼食を食べた」をキーワード抽出すると以下のようなレスポンスが帰ってきます。

{
  "result" : [ {
    "form" : "レストラン",
    "score" : 14.144054
  }, {
    "form" : "昼食",
    "score" : 12.1121
  } ],
  "status" : 0,
  "message" : ""
}

開発

Expressを使用して、「アップロードした音声データのキーワードを抽出し表示してくれるWebアプリ」を作っていこうと思います。

①ファイルをアップロードする準備

ローカルの音声ファイルを使用するために、まずはmulterモジュールを使用して、Expressにファイルをアップロードする仕組みを実装します。以下のコードはサンプルです。

アップロードさせる場所や、保存させるファイル名などを、この段階で設定します。

npm install multer
constmulter=require("multer")varstorage=multer.diskStorage({destination:function(req,file,cb){cb(null,{uploadさせるpath})},filename:function(req,file,cb){cb(null,file.originalName+'-'+Date.now())}})varupload=multer({storage:storage})app.post("/",upload.single({formで受け取るfile名}),{callback})

②音声認識をする

https://cloud.google.com/speech-to-text/docs/quickstart-client-libraries?hl=ja

ドキュメントのクイックスタートを元に、アップロードされたファイルを指定して読み込む非同期関数を作成します。base64エンコードされたファイルをcontentとし、任意の設定(config)と共にリクエストを送信します。

constspeech=require('@google-cloud/speech');constfs=require('fs');// 引数として保存したファイルの名前を与えるexports.getTranscription=asyncfunction(uploadFile){constclient=newspeech.SpeechClient();  // multer で設定したファイルの置き場を指定constfilename={path/uploadFile}constconfig={languageCode:'ja-JP',audioChannelCount:2};constaudio={content:fs.readFileSync(path).toString('base64'),};constrequest={config:config,audio:audio,};const[operation]=awaitclient.longRunningRecognize(request);const[response]=awaitoperation.promise();console.log(response.results)consttranscription=response.results.map(result=>result.alternatives[0].transcript).join('\n');returntranscription}

③キーワードを抽出する

Speech-to-Textで出力できたテキストから、COTOHA API を使用してキーワードを抽出します。

以下のコードでは、アクセストークンを取得する関数とキーワード抽出APIを使用する関数を別々に用意し、async関数の中で呼び出しています。

constwebClient=require("request")// キーワード抽出APIfunctioncotohaGetKeyword(text,token){returnnewPromise(resolve=>{varoptions={url:"https://api.ce-cotoha.com/api/dev/nlp/v1/keyword",headers:{"Content-Type":"application/json;charset=UTF-8","Authorization":`Bearer ${token}`},body:JSON.stringify({document:text,type:"default",do_segment:true})}webClient.post(options,(error,response,body)=>{resolve(body);})})}// アクセストークンの取得functioncotohaGetKeyword(){// 略}exports.getKeyword=asyncfunction(text){// アクセストークンの取得varresultToken=awaitcotohaGetAccessToke()resultToken=JSON.parse(resultToken)resultToken=resultToken.access_token;// キーワード抽出APIvarkeyword=awaitcotohaGetKeyword(text,resultToken);keyword=JSON.parse(keyword)returnkeyword}

成果物

音源の用意

作成したバックエンド側のロジックを使用しる前に、まずは音声ファイルの用意をします。
Speech-to-Text APIがサポートしている音声エンコーディングは以下を参照してください。
https://cloud.google.com/speech-to-text/docs/encoding?hl=ja
また、音声ファイルはローカルから読み取る場合、1分未満の長さである必要があります。1分以上の音声を解析する際には、Google Cloud Storageを使用して、音声ファイルをアップロードします。

今回はお笑い芸人のミルクボーイさんの漫才の一部分を録音し、これを作成したアプリケーションにアップロードしました。動画の開始から30秒ほどたった、「ウチのおかん〜」のくだりの開始から録音を始めています。勿論、抽出できるキーワードには「おかん」を期待しています。

https://youtu.be/EjTB9RDuGLA

まずは、この音源でキーワードが取得できるかを確認するために、これまでの工程で作成した関数の出力結果をログに表示させてみます。

キーワード抽出の結果は以下のようになりました。

{result:[{form:'湿布',score:26.1858},{form:'',score:16.5833},{form:'巨塔',score:12.1121},{form:'一緒',score:9.76073},{form:'完全',score:7.97694}],status:0,message:''}

おかんはいませんでした。

しかし、漫才中で声を大にしてツッコミを入れている「湿布」という単語は抜き出すことができています。
スコアが高いのを見ると、とても重要なこととCOTOHAは判断したようです。

可視化する

最後に、抽出したキーワードを可視化し、この音源の要約を作成します。
d3.js とキーワードのスコアの重みを使って、ワードクラウドのような形で画面に表示させます。
最終的に、図のようなアプリが完成しました。
スクリーンショット 2021-03-24 7.12.41.png

Node.jsでAWSのパラメータストアから値を取得する

$
0
0

はじめに

シークレット系の情報をAWSのパラメータストアに格納し,それをNode.jsで呼び出してみました。
この記事はその際の備忘録になります。
誤っている箇所や修正したほうがいい箇所などありましたらコメントいただけますと幸いです。

AWSのパラメータストアとは

AWS System Managerの機能の一つ。
パスワードなどのシークレット系の情報をセキュアに保存することができます。

公式ページ : https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-parameter-store.html

必要なモジュールのインストール

AWS SDK for JavaScriptをインストールします。

$npm install aws-sdk

AWS SDK for JavaScript とは,AWSのサービスをjavascriptで使用するためのAPIになります。
今回はこのAPIを使用して,パラメータストアから値を取得します。

AWSの認証情報の設定

Node.jsでAWSのサービスを利用するに当たって,認証情報を設定する必要があります。
認証情報の設定方法はいくつかありますが,今回は~/.aws/credentialsから読み取る方法を使用したいと思います。

今回 ~/.aws/credentialsには,defaultプロファイルとは別に,privateアカウントのプロファイルも記述しておきます。

~/.aws/credentials
[default]
aws_access_key_id = <ACCESS_KEY_ID>
aws_secret_access_key = <SECRET_ACCESS_KEY>

[private]
aws_access_key_id = <PRIVATE_ACCOUNT_ACCESS_KEY_ID>
aws_secret_access_key = <PRIVATE_ACCOUNT_SECRET_ACCESS_KEY>

また,~/.aws/configファイルには,regionを記述しておきます。

~/.aws/config
[default]
output = json
region = ap-northeast-1

[profile private]
output = json
region = ap-northeast-1

AWSで新規パラメータを作成

公式ドキュメントを参考に,新規パラメータを作成します。
今回は,~/.aws/credentials~/.aws/configにprivateというプロファイル名で記述したAWSアカウントのパラメータストアに,sampleというパラメータ名で 'sample' という文字列を保存しました。

実行ファイル作成

上記で作成したsampleというパラメータを取得します。

実行ファイルの完成形

sample.js
// aws-sdk をロードする前に,環境変数をセットする// ~/.aws/config の情報を参照できるようにするprocess.env.AWS_SDK_LOAD_CONFIG=true;// 使用するprofileを指定するprocess.env.AWS_PROFILE='private';// 環境変数セット完了// aws-sdk モジュールを読み込むconstAWS=require("aws-sdk");// 新規 AWS.SSM class を作成constssm=newAWS.SSM();// 'sample' という名前のパラメータを取得constparams={Name:'sample',WithDecryption:true};ssm.getParameter(params,(err,data)=>{if(err){console.log(err);}else{console.log(data);}});

下記で各処理について説明していきます。

環境変数の設定

sample.js(一部)
// aws-sdk をロードする前に,環境変数をセットする// ~/.aws/config の情報を参照できるようにするprocess.env.AWS_SDK_LOAD_CONFIG=true;// 使用するprofileを指定するprocess.env.AWS_PROFILE='private';// 環境変数セット完了// aws-sdk モジュールを読み込むconstAWS=require("aws-sdk");

aws-sdkモジュールを読み込む前に,環境変数をセットします。
そうすることで,セットした環境変数の設定を元に aws-sdkモジュールの読み込みが行われます。

セットする環境変数

  • AWS_SDK_LOAD_CONFIG : ~/.aws/configに記述された設定を参照できるようにします。(参考
  • AWS_PROFILE : 使用するprofileを指定します。(参考

新規 AWS.SSM クラスを作成

sample.js(一部)
// 新規 AWS.SSM class を作成constssm=newAWS.SSM();

AWS.SSM クラスは,Node.js内で AWS System Manager の機能を利用するためのクラスになります。(参考)

パラメータを取得

sample.js(一部)
// 'sample' という名前のパラメータを取得constparams={Name:'sample',WithDecryption:true// パラメータの値が暗号化されていた場合は復号};ssm.getParameter(params,(err,data)=>{if(err){console.log(err);}else{console.log(data);}});

AWS.SSM クラスに用意されている getParameterメソッドを使用して,パラメータストアから値を取得します。(参考

paramsに設定できるオプション

  • Name : パラメータ名
  • WithDecryption : パラメータの値が暗号化されていた場合に復号するかどうかを指定(パラメータの値が文字列または文字列リストの場合は,このオプションは無視されます)

実行

$node sample.js
{
  Parameter: {
    Name: 'sample',
    Type: 'String',
    Value: 'sample',
    Version: 1,
    LastModifiedDate: 2021-03-23T07:45:35.557Z,
    ARN: 'arn:aws:ssm:ap-northeast-1:xxxxxxxxxxxx:parameter/sample',
    DataType: 'text'
  }
}

参考

AWS 公式

その他

[メモ]Cloud Functions + Node.jsでさくっとAPI開発

$
0
0

たまにはfirebaseでAPIを作りたくなったので、作ってみました。
次気が向いたとき、さくっと作れるように。

firebase --version 
9.1.0

プロジェクトを作成する

まずGUIでプロジェクトを作成し、課金モードにしておくのが楽。

https://console.firebase.google.com/u/0/
cd desktop
mkdir api
cd api

連携/初期化

必要に応じて、有効化する。ひとまずFunctionsだけ有効化してみる。

$ mkdir api 
$ cd api
$ firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/user/Desktop/api

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. 
 ◯ Database: Configure Firebase Realtime Database and deploy rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
❯◉ Functions: Configure and deploy Cloud Functions
 ◯ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules
 ◯ Emulators: Set up local emulators for Firebase features
 ◯ Remote Config: Get, deploy, and rollback configurations for Remote Config
=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add, 
but for now we'll just set up a default project.

? Please select an option: (Use arrow keys)
❯ Use an existing project 
  Create a new project 
  Add Firebase to an existing Google Cloud Platform project 
  Don't set up a default project 
? Please select an option: Use an existing project
? Select a default Firebase project for this directory: 
  xxxxxxxx (yyyy) 
❯ api-189bf (api) 
  xxxxxxxx (yyyyy) 
  xxxxxxxx (yyyyy) 
=== Functions Setup

A functions directory will be created in your project with a Node.js
package pre-configured. Functions can be deployed with firebase deploy.

? What language would you like to use to write Cloud Functions? (Use arrow keys)
❯ JavaScript 
  TypeScript 
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
✔  Wrote functions/package.json
✔  Wrote functions/.eslintrc.json
✔  Wrote functions/index.js
✔  Wrote functions/.gitignore
? Do you want to install dependencies with npm now? Yes

> protobufjs@6.10.2 postinstall /Users/inoueyousuke/Desktop/api/functions/node_modules/protobufjs
> node scripts/postinstall

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

✔  Firebase initialization complete!

helloWorldをデプロイ

/functions/index.js
constfunctions=require('firebase-functions');// // Create and Deploy Your First Cloud Functions// // https://firebase.google.com/docs/functions/write-firebase-functions//// exports.helloWorld = functions.https.onRequest((request, response) => {//   functions.logger.info("Hello logs!", {structuredData: true});//   response.send("Hello from Firebase!");// });

コメントアウトを削除する

/functions/index.js
constfunctions=require('firebase-functions');// // Create and Deploy Your First Cloud Functions// // https://firebase.google.com/docs/functions/write-firebase-functionsexports.helloWorld=functions.https.onRequest((request,response)=>{functions.logger.info("Hello logs!",{structuredData:true});response.send("Hello from Firebase!");});

デプロイ!!

firebase deploy

=== Deploying to 'api-189bf'...

i  deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run lint

> functions@ lint /Users/user/Desktop/api/functions
> eslint .

✔  functions: Finished running predeploy script.
i  functions: ensuring required API cloudfunctions.googleapis.com is enabled...
i  functions: ensuring required API cloudbuild.googleapis.com is enabled...
⚠  functions: missing required API cloudbuild.googleapis.com. Enabling now...
✔  functions: required API cloudfunctions.googleapis.com is enabled
✔  functions: required API cloudbuild.googleapis.com is enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (33.28 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: creating Node.js 12 function helloWorld(us-central1)...
✔  functions[helloWorld(us-central1)]: Successful create operation. 
Function URL (helloWorld): https://us-central1-api-189bf.cloudfunctions.net/helloWorld

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/api-189bf/overview

生成されたエンドポイントにアクセスする

スクリーンショット 2021-03-26 3.15.50.png

完成

フロントから叩けるPOSTAPIを作る

CORSが面倒なので、以下参照で回避

functions/index.js
constfunctions=require("firebase-functions");exports.post=functions.https.onRequest(async(request,response)=>{//CORS用にAccess-Control-Allow系ヘッダを追加response.set("Access-Control-Allow-Origin","https://xxxxxx.com");//DELETEだけは拒否response.set("Access-Control-Allow-Methods","GET, HEAD, OPTIONS, POST");//Content-Typeのみを許可response.set("Access-Control-Allow-Headers","Content-Type");// dataフィールドに渡したい値は入れるresponse.json({data:"hello!!"});});

Cloud Funtionsでnpmモジュールを使う

./functions

npm install xxx

あとは普通にindex.jsを書くだけ。

Fire Storeとか使いたい

上の章を参考に導入。
こちらの記事参照

npm i firebase-admin --save
/functions/index.js
constfunctions=require('firebase-functions')// cloud functionでfirestoreを使うのに必要な設定は以下の2行constadmin=require('firebase-admin')admin.initializeApp(functions.config().firebase)// データベースの参照を作成varfireStore=admin.firestore()exports.helloWorld=functions.https.onRequest((request,response)=>{// 動作確認のため適当なデータをデータベースに保存varcitiesRef=fireStore.collection('cities');citiesRef.doc('SF').set({name:'San Francisco',state:'CA',country:'USA',capital:false,population:860000})varcityRef=fireStore.collection('cities').doc('SF')cityRef.get().then(doc=>{if(!doc.exists){response.send('No such document!')}else{response.send(doc.data())}}).catch(err=>{response.send('not found')})})

ひとまずこんな感じで幸せになれます!
エミュレータとかも書きたかったけど、また気が向いたときにやってみよう。

バイバイ!

参照

cloud functionsでfirestoreを使う
Markdown の画像に枠線をつける
FirebaseのCloud FunctionsでCORSが~とかAccess-Control-Allow-Originが~と言われたらこれ

Node.jsでLINEBOTを作ってみた①

$
0
0

はじめに

JavaScriptの学習の中で、サーバー側を書いてみたいと思い、以前から興味のあったLINEBOTの制作をしてみようと考えました!

作りたい予約システムを考える

  1. 「予約」と入力すると予約に関するメッセージをリプライ
  2. 予約は自動
  3. 次回予約を確認
  4. 予約のキャンセル
  5. 予約が確定された時の、管理画面の制作

システムの全体像

Line Messaging API (1).png

開発環境

  1. MacBook
  2. macOS Catalina 10.15.7
  3. Visual Studio Code

技術選択

  1. Node.js(Express)
  2. JavaScript
  3. Heroku
  4. PostgresSQL
  5. LINE MessagingAPI

LINEを使ったBOT構築はMessaging APIが必須となります。
インフラ構築は学習コストがかかると判断したので、簡単にプログラム公開できるHeroku。データベースはPostgreSQLを使います。

Heroku,PostgresSQL,MessagingAPI全て無料範囲内で納まりますので安心です。

※自動でやりとりする上で、すぐに返事が返ってきますよね?
Herokuだと30分サーバーにアクセスがないとスリープモードに切り替わってしまい、アクセスから立ち上がりまで時間がかかってしまうという問題が発生してしまいます。
ですが、スリープ問題をほんのり解決できる裏技もあるので、今後紹介していきます。

Expressのインストール

※Node.jsやnpmのインストールする記事はたくさんありますので割愛します。

Visual Studio Codeでcontrol+shift+@を押すとターミナル画面が開きます。

ターミナル.
npm init

npm initはnode.jsにおけるパッケージを管理するファイル「package.json」新規作成するためのコマンドです。ルートフォルダ下に「package.json」ファイルができたかと思います。そのファイルの中身を見ると、こんな感じになっています。

package.json
{"name":"hogemarukun","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"","license":"ISC"}

nameの部分はなんでもおけです!

次にExpressのインストールをします。

ターミナル.
npm install express --save

package.jsonをみてみましょう。

package.json
{"name":"hogemarukun","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"","license":"ISC","dependencies":{"express":"^4.17.1"}}

"dependencies": {
"express": "^4.17.1"
が追加されたと思います。

おわりに

パートを何回かに分けて書いていきますので
今回はこの辺りで!

Node.jsでLINEBOTを作ってみた②

$
0
0

はじめに

パート①

前回までにやったこと
・Node.jsのインストール
・npmのインストール
・npm initによるpackage.jsonファイル作成
・expressのインストール

Herokuにアプリを作成する

上記よりHerokuの新規登録orログイン
ターミナルでHerokuを操作するためにCLIのインストールが必要となります。

下記の記事を参考にHeroku CLIをインストールしてください。
https://note.com/makoto0419/n/n113b55afa395

Heroku上にアプリを作成してみましょう。

ターミナル.
heroku create hogemarukun

herokuのアプリ名は他の誰かが使っているアプリ名を指定した場合は、アプリが作成出来ませんので、違う名前で試してみてください。

LINE公式アカウントの作成

上記の記事を参考にアカウント作成をしてください。

  1. プロバイダーを作成。プロバイダー名はなんでもOK。
  2. 新規チャンネル作成→Messaging API
  3. チャンネルアイコン画像登録→LINEに表示されるBOT画像です。
  4. チャンネル名登録→LINEに表示されるBOTの名前です。
  5. その他必須項目を適当に入力して「作成」

<アカウント作成後>

  1. Webhook URLの入力→先ほど作成したHerokuアプリに紐付きますので、herokuアプリ名が「hogwmarukun」ならば以下のように入力します。 https://hogemarukun.herokuapp.com/hook/更にwebhookの利用をオンにします。
  2. チャンネルアクセストークン(長期)→「発行」
  3. 応答設定→応答モード:bot、あいさつメッセージ:オフ、応答メッセージ:オフ

おわりに

パート①、②が開発をするまでの環境構築までの流れとなります。

electron で "require is not defined" がどうしても消えないとき

$
0
0

electron でどうしても"require is not defined" が消えない!!

electron を使用していると"require is not defined" Errorが出ることがあります。原因はわかっておりGoogleで検索すると沢山記事は出ています。Node.jsのモジュールであるrequireをFrontEndで使えないことが原因です。electron側の仕様でwebPreferences のnodeIntegrationパラメターがdefaultでfalseになっている事が原因で、

mainWindow = new BrowserWindow({
    width:1100, 
    height: 1000,
    webPreferences: {
      nodeIntegration: true,
    },
  });

としたらとりあえずはOkですよと沢山の記事に書かれています。(セキュリティーの問題から公式では推奨されていない。)
しかし、僕の場合上記を試しても一向にエラーが消えない!なんでだ!!

electron v11からv12でまた仕様が変わったみたい

色々調べていると、どうもつい最近また仕様が変更になったみたいです。
v11からv12になった時に webPreferencesの 'contextIsolation'パラメターがdefaultでtrueになっており、その事で上の解決法を試しても上記エラーが消えなかったようです。

mainWindow = new BrowserWindow({
    width:1100, 
    height: 1000,
    webPreferences: {
      nodeIntegration: true,
      contextIsolation: false
    },
  });

これでやっと"require is not defined"が消えましたとさ。。。。

どなたかの参考になれば幸いです。

スーパー便利!nvmを使ってwindowsでNodeのバージョン管理を1秒で行う方法

$
0
0

皆さんこんちは!

ふとnpm i --save firebaseを実行したところ、今までは出来ていたのですが急にエラーが出てインストールが出来なくなりました。

どうやらNodeのバージョンが原因らしい。

node --version

v14.15.1

Firebaseがリクエストするバージョンは12である。

Nodeを再インストールしても良いのですが、またNodeの依存関係が出てきたとき面倒くさいので色々調べていたところnvmと言うものに出会いました。

これはNodeのバージョン管理を行うパッケージです。

コマンドも例えばnvm use 12と打てばNodeのバージョンが12の状態で実行されます。

早速インストールしましょう!

scoopの導入

Windowsではパッケージのバージョン管理を行う場合、「scoop」を使います。

Macで言うHomebrewみたいなものです。

まずはscoopをインストールしましょう。

※注意 scoopをインストールするには「PowerShell」を「管理者として実行」でコマンドを実行してください

Set-ExecutionPolicyRemoteSigned-scopeCurrentUser//実行ポリシーの変更について聞かれるので「Y」を選択iex(new-objectnet.webclient).downloadstring('https://get.scoop.sh')

また、scoopについて知りたい方はこちらの記事でも書いているので是非ご覧ください!

nodeのインストール

nvm、nodeのインストール

scoopnvmnvminstall12

nvmの環境変数を設定

$ENV:Path="C:\Users\ご自身の名前\scoop\apps\nvm\current;"+$ENV:Path

バージョンが返ってくればOK!

nvmversion1.1.7

後は使いたいときに下記のようにバージョンを指定。

nvmuse12

nvmコマンドは環境変数を通したので、コマンドプロンプトで実行してもOK!

このようにしてNodeのバージョン再インストールをしなくてもバージョン管理を行うことができます!

以上、「スーパー便利!nvmを使ってwindowsでNodeのバージョン管理を1秒で行う方法」でした!

Thank you for reading


Alexaスキル開発チュートリアル(AWS Lamda, node.js)

$
0
0

Alexaスキル開発チュートリアル(AWS Lamda, node.js)

個人で開発した、Alexaスキル開発チュートリアルを記載します。

今回は自分の目標設定をAlexaに記憶させて、次回起動時に目標期限を自動で算出して教えてくれるAlexaスキルを開発してみようと思います。

完成イメージ

◼︎ 初回起動時
(自分)アレクサ、目標リマインダーを開いて
(Alexa)こんにちは、目標リマインダーへようこそ。目標達成希望日時を教えてください。
(自分)2020年10月9日
(Alexa)ありがとうございます。目標達成日時は2020年10月9日ですね。

◼︎ 次回起動時
(自分)アレクサ、目標リマインダー
(Alexa)おかえりなさい、次の目標達成日時まで1年、残り12日です。目標達成に向けて本日も頑張りましょう

前提知識

1. AWS Lambdaについての最低限の知識
2. JavsaScriptについての最低限の知識
3. Alexa(スマートスピーカー)についての最低限の知識

STEP1 「ログイン」

まず、Amazon開発者アカウントを使って Alexa開発者コンソールにログインします。アカウントがない場合は、こちらで作成します。

STEP2 「スキルを作成」

スクリーンショット 2021-03-09 10.49.47.png
コンソールの右側にあるスキルの作成をクリックします。新しいページが表示されます。

FireShot Capture 020 - Alexa Developer Console - developer.amazon.com.png

  1. スキル名を入力します。このスキル名が公開された時に表示される名前になります。
  2. スキルに追加するモデルを選択します。今回はオリジナルAlexaスキルを開発するのでカスタムを選択します。
  3. スキルのバックエンドリソースをホスティングする方法を選択します。今回はAlexaにホストを選択します。今回の開発言語はNode.jsにする。(AWSの無料枠には制限があるため、スキルの使用頻度が上がってきたら、独自にホスティングするオプションに変更するのがオススメ)

STEP3 「呼び出し名を作成」

呼び出し名とは自分が開発したAlexaスキルを呼び出すときに発生するワードになります。

アレクサ、目標リマインダーを開いて

この時の {{ 目標リマインダー }}が呼び出し名になります。

STEP4 「コードエディタでコードを書く」

image.png

コードエディタタブをクリックします。コードエディタでindex.jsファイルが開きます。

今回開発した「目標設定リマインダー」の完成コードを記載します。

全てのコードを詳しく解説すると、永遠に終わらないので簡単な解説コメントのみ記載します

index.js
/* *
 * This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
 * Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
 * session persistence, api calls, and more.
 * *//* *
* ハンドラー関数について
* スキルがリクエストを受け取ると、各ハンドラーのcanHandle() 関数がそのハンドラーでリクエストに対応できるかを判断します。
* canHandle() 関数 -> ハンドラーが応答するリクエストを定義します(trueを返すことで、この処理を実行できる)
* handle() 関数 -> ユーザーに応答する内容を定義します
* const Alexa = require('ask-sdk-core');
* const persistenceAdapter = require('ask-sdk-s3-persistence-adapter');
* */constLaunchRequestHandler={canHandle(handlerInput){returnAlexa.getRequestType(handlerInput.requestEnvelope)==='LaunchRequest';},handle(handlerInput){//  起動時の挨拶constspeakOutput='こんにちは、目標リマインダーへようこそ。目標達成希望日時を教えてください。';// ユーザーが答えなかった場合に、もう一度たずねるconstrepromptOutput='私の目標達成日時は2020年10月1日です、あなたの目標達成希望日時を教えてください。';returnhandlerInput.responseBuilder.speak(speakOutput).reprompt(repromptOutput).getResponse();}};constHasDreamdayLaunchRequestHandler={// canHandle()関数はユーザーの目標達成日時がAmazon S3に保存されているかどうかをチェックします// 保存されていれば、ハンドラーはSDKに続行が可能である(ユーザーの誕生日が保存されているため、次の処理に進めると知らせます。// 保存されていない場合はLaunchRequestHandlerを呼び、目標達成日時の取得を行うcanHandle(handlerInput){constattributesManager=handlerInput.attributesManager;constsessionAttributes=attributesManager.getSessionAttributes()||{};constyear=sessionAttributes.hasOwnProperty('year')?sessionAttributes.year:0;constmonth=sessionAttributes.hasOwnProperty('month')?sessionAttributes.month:0;constday=sessionAttributes.hasOwnProperty('day')?sessionAttributes.day:0;returnhandlerInput.requestEnvelope.request.type==='LaunchRequest'&&year&&month&&day;},// handle()関数は、「おかえりなさい。Y歳の誕生日まであとX日です。」と言うようAlexaに指示します。// APIを使ってタイムゾーンを取得するため、応答を取得するまでに時間がかかる可能性がるので非同期処理にするasynchandle(handlerInput){//  ServiceClientファクトリーを作成constserviceClientFactory=handlerInput.serviceClientFactory;//  デバイスIDを取得constdeviceId=handlerInput.requestEnvelope.context.System.device.deviceId;constattributesManager=handlerInput.attributesManager;constsessionAttributes=attributesManager.getSessionAttributes()||{};constyear=sessionAttributes.hasOwnProperty('year')?sessionAttributes.year:0;constmonth=sessionAttributes.hasOwnProperty('month')?sessionAttributes.month:0;constday=sessionAttributes.hasOwnProperty('day')?sessionAttributes.day:0;// タイムゾーン取得、タイムゾーン取得失敗の時はエラーログ吐くletuserTimeZone;try{constupsServiceClient=serviceClientFactory.getUpsServiceClient();userTimeZone=awaitupsServiceClient.getSystemTimeZone(deviceId);}catch(error){if(error.name!=='ServiceError'){returnhandlerInput.responseBuilder.speak("サービスへの接続がうまく行きませんでした。").getResponse();}console.log('error',error.message);}// 現在の日付と時刻を取得しますconstcurrentDateTime=newDate(newDate().toLocaleString("ja-JP",{timeZone:userTimeZone}));// 日数計算の結果に影響するため、日付から時刻を取り除きますconstcurrentDate=newDate(currentDateTime.getFullYear(),currentDateTime.getMonth(),currentDateTime.getDate());// 現在年度を取得constcurrentYear=currentDate.getFullYear();// 目標達成日時の取得// todo: 変数名変更letnextBirthday=Date.parse(`${month}${day}, ${currentYear}`);// 目標達成日時が今年でなければ何年先かを求めるif(currentDate.getTime()>nextBirthday){consttermDay=(currentDate.getTime()-nextBirthday)/86400000;if(termDay>365){consttermYear=Math.floor(termDay/365)nextBirthday=Date.parse(`${month}${day}, ${currentYear+termYear}`);}}// 8640000(1日をミリ秒に換算したもの)constoneDay=24*60*60*1000;// 目標達成日時の場合はおめでとうと発生するletspeechText=`本日が目標達成日時になります。目標達成おめでとうございます。`;if(currentDate.getTime()!==nextBirthday){constdiffDays=Math.round(Math.abs((currentDate.getTime()-nextBirthday)/oneDay));speechText=`おかえりなさい、次の目標まで${currentYear-year}年、残り${diffDays}日です。目標達成に向けて本日も頑張りましょう`}returnhandlerInput.responseBuilder.speak(speechText).getResponse();}};constCaptureGoalIntentHandler={canHandle(handlerInput){returnhandlerInput.requestEnvelope.request.type==='IntentRequest'&&handlerInput.requestEnvelope.request.intent.name==='CaptureGoalIntent';},// 非同期処理で必要情報をs3保存asynchandle(handlerInput){constyear=handlerInput.requestEnvelope.request.intent.slots.year.value;constmonth=handlerInput.requestEnvelope.request.intent.slots.month.value;constday=handlerInput.requestEnvelope.request.intent.slots.day.value// ユーザーの目標達成日時を保存するconstattributesManager=handlerInput.attributesManager;// Amazon S3に値を保存する項目設定constdreamDayAttributes={"year":year,"month":month,"day":day};// ユーザーの情報がAmazon S3に送信されるまで実行完了を待つattributesManager.setPersistentAttributes(dreamDayAttributes);awaitattributesManager.savePersistentAttributes();constspeakOutput=`ありがとうございます。目標達成日時は${year}${month}${day}日ですね。`;returnhandlerInput.responseBuilder.speak(speakOutput)//.reprompt('add a reprompt if you want to keep the session open for the user to respond').getResponse();}};// ユーザ発話:「ヘルプ」と発した時の処理constHelpIntentHandler={canHandle(handlerInput){returnAlexa.getRequestType(handlerInput.requestEnvelope)==='IntentRequest'&&Alexa.getIntentName(handlerInput.requestEnvelope)==='AMAZON.HelpIntent';},handle(handlerInput){constspeakOutput='あなたの目標達成日時を教えてくれると私が目標達成日時までの残り時間を計算します。あなたの目標達成希望日時を教えてください。';returnhandlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).getResponse();}};// ユーザ発話:「キャンセル」と発した時の処理constCancelAndStopIntentHandler={canHandle(handlerInput){returnAlexa.getRequestType(handlerInput.requestEnvelope)==='IntentRequest'&&(Alexa.getIntentName(handlerInput.requestEnvelope)==='AMAZON.CancelIntent'||Alexa.getIntentName(handlerInput.requestEnvelope)==='AMAZON.StopIntent');},handle(handlerInput){constspeakOutput='さようなら〜';returnhandlerInput.responseBuilder.speak(speakOutput).getResponse();}};/* *
 * FallbackIntent triggers when a customer says something that doesn’t map to any intents in your skill
 * It must also be defined in the language model (if the locale supports it)
 * This handler can be safely added but will be ingnored in locales that do not support it yet 
 * */constFallbackIntentHandler={canHandle(handlerInput){returnAlexa.getRequestType(handlerInput.requestEnvelope)==='IntentRequest'&&Alexa.getIntentName(handlerInput.requestEnvelope)==='AMAZON.FallbackIntent';},handle(handlerInput){constspeakOutput='Sorry, I don\'t know about that. Please try again.';returnhandlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).getResponse();}};// ユーザ発話:「終了」と発した時の処理constSessionEndedRequestHandler={canHandle(handlerInput){returnAlexa.getRequestType(handlerInput.requestEnvelope)==='SessionEndedRequest';},handle(handlerInput){console.log(`~~~~ Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);// Any cleanup logic goes here.returnhandlerInput.responseBuilder.getResponse();// notice we send an empty response}};// ユーザ発話:「<カスタムインテントのサンプル発話(デバッグ用)>」constIntentReflectorHandler={canHandle(handlerInput){returnAlexa.getRequestType(handlerInput.requestEnvelope)==='IntentRequest';},handle(handlerInput){constintentName=Alexa.getIntentName(handlerInput.requestEnvelope);constspeakOutput=`You just triggered ${intentName}`;returnhandlerInput.responseBuilder.speak(speakOutput)//.reprompt('add a reprompt if you want to keep the session open for the user to respond').getResponse();}};// エラーハンドラconstErrorHandler={canHandle(){returntrue;},handle(handlerInput,error){constspeakOutput='すいません、ちょっと何言ってるかわからないです、もう一回話しかけてください';console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);returnhandlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).getResponse();}};// S3保存したデータを読み込む// Alexaがユーザーに目標日時をたずねる前にAmazon S3に保存したデータを読み込むconstLoadDreamdayInterceptor={asyncprocess(handlerInput){constattributesManager=handlerInput.attributesManager;constsessionAttributes=awaitattributesManager.getPersistentAttributes()||{};constyear=sessionAttributes.hasOwnProperty('year')?sessionAttributes.year:0;constmonth=sessionAttributes.hasOwnProperty('month')?sessionAttributes.month:0;constday=sessionAttributes.hasOwnProperty('day')?sessionAttributes.day:0;// データが存在-> s3に保存されている日時を取得してセットするif(year&&month&&day){attributesManager.setSessionAttributes(sessionAttributes);}}};/**
 * This handler acts as the entry point for your skill, routing all request and response
 * payloads to the handlers above. Make sure any new handlers or interceptors you've
 * defined are included below. The order matters - they're processed top to bottom 
 * */exports.handler=Alexa.SkillBuilders.custom().withApiClient(newAlexa.DefaultApiClient())// Amazon S3にデータを保存、読み込む設定.withPersistenceAdapter(newpersistenceAdapter.S3PersistenceAdapter({bucketName:process.env.S3_PERSISTENCE_BUCKET}))// 関数を呼び出す順に羅列する.addRequestHandlers(HasDreamdayLaunchRequestHandler,LaunchRequestHandler,CaptureGoalIntentHandler,HelpIntentHandler,CancelAndStopIntentHandler,FallbackIntentHandler,SessionEndedRequestHandler,IntentReflectorHandler)// インターセプターを登録するコードを追加して、SDKにその存在を知らせる.addRequestInterceptors(LoadDreamdayInterceptor)// エラーハンドリング.addErrorHandlers(ErrorHandler).withCustomUserAgent('sample/hello-world/v1.2').lambda();
package.json
{"name":"dream-time","version":"1.2.0","description":"alexa utility for quickly building skills","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"Amazon Alexa","license":"Apache License","dependencies":{"ask-sdk-core":"^2.7.0","ask-sdk-model":"^1.19.0","aws-sdk":"^2.326.0","ask-sdk-s3-persistence-adapter":"^2.0.0"}}

STEP5 「インテントとスロットを使って情報を取得する」

ユーザーがAlexaの質問にどう答えるかを解釈するインテントを作成します。

インテントとは、ユーザーの音声によるリクエストを満たすアクションのことです。

インテントでは、スロットという引数を任意で使用することもできます。

インテント追加方法

image.png

カスタムインテントを作成を選択し、インテント名としてCaptureGoalIntentを入力し、カスタムインテントを作成をクリックすると新規でインテントを作成できます。

CaptureGoalIntentはindex.jsで定義したインテント名になります。(これは自分で定義したカスタムインテントなので追加する必要があります)

スクリーンショット 2021-03-13 14.25.06.png

スロット作成方法

次にスロットを作成しましょう。

今回の目標達成日時では、年、月、日という3つの重要な情報を収集します。

これらの情報をスロットと呼びます。Alexaにどの単語がスロットで、どのタイプのスロットなのかを知らせる必要があります。

私は2021年11月7日までに目標を達成します

人によって、動的に変更される箇所を波括弧({ })で囲んで適切な変数名に変更します。

私は{year} 年{month}月{day}日までに目標を達成します

スクリーンショット 2021-03-13 14.31.57.png

スロットタイプドロップダウンメニューから、各スロットにスロットタイプを割り当てます。
image.png

必須のスロットには必須の設定をしましょう。

スロットに右側にある「ダイアログを編集」をクリックし、下記のように必須のチェックボックスをONにします。

Alexaの音声プロンプトにはユーザーがmonthスロットの値を提供しなかった場合にAlexaが言うテキストを入力します。

ユーザーの発音にユーザーが想定する発話を入力します。

スクリーンショット 2021-03-13 14.32.43.png

STEP6 「テストする」

最後の実際にスキルが正常に動くかテストしてみましょう。

テスト タブをクリックします。テストシミュレーターが開きます。
image.png

するとAlexaシュミレータが表示されるので、サンプル発話を入力していきましょう。(赤枠の箇所に入力もしくは発話でinputします)

1. まず呼び出し名の「目標リマインダー」と発話する
2. アレクサスキルが挨拶と、質問をしてくる
3. 目標日時を発話する
4. アレクサスキルが目標達成日時を記憶する。

次に再度、アクレさを起動させたときに以前に記憶させた日時を記憶しており、現在日時から目標達成日時までの残り期間を自動計算してレスポンス返却してくれるかを確認します。

もう一度、アレクサを呼び出すと目標達成日時までの残り期間を自動計算してレスポンス返却成功です。

スクリーンショット 2021-03-13 14.55.59.png

STEP7 「スキル公開」

ユーザーへの公開前にいくつかのステップを実施する必要があります。まず、Alexa開発者コンソールの公開タブで、必要な情報を入力します。

スキルのプレビュー設定

Alexaスキルストアにスキルがどう表示されるかに関する情報を入力します。(下記図参照)
image.png

プライバシーとコンプライアンス設定

プライバシーとコンプライアンス設定を行います(下記図参照)
image.png

公開範囲

Alexaスキルをどの範囲まで公開するかの設定を行います(下記図参照)
スクリーンショット 2021-03-14 17.08.09.png

STEP8 「スキルの認定と公開」

公開範囲の設定が終われば、実際にスキルを申請してみましょう。

申請が完了したた下記図のようにステータスが申請中になります。

結果は数日後にメールにて連絡がきます。
スクリーンショット 2021-03-20 21.45.23.png

申請に落ちた場合

審査に落ちた場合はAmazonより審査に落ちた理由をめちゃくちゃ丁寧に教えてくれるメールを送ってくれます。

下記の写真は自分が審査に落ちたときに送られてきたメールになります。(すごく丁寧に落ちた理由を教えてくれました)

指摘箇所を直し再審査可能なので、修正でき次第また審査に応募しましょう。

スクリーンショット 2021-03-20 21.48.06.png

申請に合格した場合

スキルのステータスや、認証審査プロセスに関する最新情報は、認定>申請から確認できます。

申請に合格すると、ステータスが「認定済み」に変更されます。

スクリーンショット 2021-03-23 12.26.22.png

また、スキルが公開される時期の目安がEメールで通知されます。(公開時期を後から設定にした場合は公開時間を設定してくださいと表記される)
スクリーンショット 2021-03-23 12.29.47.png

まとめ

今回はAWSアレクサの開発から公開までの流れをご紹介しました。

普段、利用しているAIスピーカーが裏側でどのように開発、運用されているかの勉強になりました。

興味ある方はぜひ開発に挑戦してみてください。

Node-REDにおけるnpm installの正しい位置

$
0
0
c:\users\<your username}\.node-red

です。参考例は

cd c:\Users\torisan\.node-red
npm install node-red-contrib-....

です。
npm globalの位置は

c:\users\<your username}\AppData\Roaming\npm

にあります。

discord.jsで音声合成機能を作る

$
0
0

今回つくるもの

1.discord上で読み上げるテキストを指定するコマンドを実行
2.メッセージから読みあげたいテキストを取得
3.GoogleのTTSAPIにリクエスト送信
4.結果を取得
5.1が送信されたチャンネルに結果を送信

必要なもの

・node-fetch(npm i node-fetch)
・stream(npm i stream)
・util(npm i util)
・discord.js(npm i discord.js);
discord.jsの実行環境の整備は、ここでは省きます。

コード

constprefix="!";const{createWriteStream}=require('fs');const{pipeline}=require('stream');const{promisify}=require('util');constfetch=require("node-fetch");client.on("message",asyncmessage=>{if(message.author.bot)return;constargs=message.content.slice(prefix.length).trim().split(/ +/g);constcommand=args.shift().toLowerCase();if(command=="voicetext"){constt=args.join("");constm=Math.random().toString(36).slice(2,12);conststreamPipeline=promisify(pipeline);constresponse=awaitfetch(`https://www.google.com/speech-api/v1/synthesize?text=${encodeURI(t)}&nc=mpeg&lang=ja&speed=0.5&client=lr-language-tts`);if(!response.ok)thrownewError(`unexpected response ${response.statusText}`);awaitstreamPipeline(response.body,createWriteStream(`./voices/${m}.mp3`));message.channel.send("結果:",{files:[`./voices/${m}.mp3`]});}});

結果

!voicetext <text>を実行すると、音声合成した結果が返ってきます!
スクリーンショット 2021-03-27 174757.png

Node.jsでLINEBOTを作ってみた③ 〜フォローしたら挨拶するBOTまで〜

$
0
0

はじめに

前回までのあらすじは下記を参照してください。

パート①はこちら

今回の目標はLINE公式アカウントをフォローしたら
挨拶が返ってくるまでの実装を目指します!!

@line/bot-sdkのインストール

LINEBOTをnode.jsで使うには、@line/bot-sdkというライブラリが必要となります。

「SDK」とは?
→ソフトウェア開発キットのことで、少ない労力でソフトを開発できるように
プログラムやサンプルコードなどをパッケージ化したものです。

開発をよりスムーズにしてくれるものだと思ってください。

step1:ターミナルで@line/bot-sdkをインストール

ターミナル.
$ npm i @line/bot-sdk

package.jsonのdependenciesに以下のように@line/bot-sdkがあればおけい!!

package.json
"dependencies":{"@line/bot-sdk":"^7.2.0","express":"^4.17.1",

step2:package.jsonを修正していきます。

package.json
{"name":"hogemarukun","version":"1.0.0","description":"hogemarukun","engines":{"node":"12.x","npm":"6.x"},"main":"index.js","scripts":{"start":"node index.js","test":"node test.js"},"author":"hogemaruchan","license":"ISC","dependencies":{"@line/bot-sdk":"^7.2.0","express":"^4.17.1",}}

enginesの追加とscriptsの中身を変え、
nodeやnpmは指定のバージョンで使いますよ〜という宣言です。
バージョンの確認はターミナルで以下のように確認してください。

ターミナル.
$ node -v
  v12.18.1

$ npm -v
  6.14.7

私が使用したバージョンがnode v12.18.1 npm 6.14.7なので
node 12.x npm 6.xと宣言しました。

コーディング

step1:ディレクトリ直下にindex.jsを作成!

step2:コードの追加

index.js
constexpress=require('express');constapp=express();constline=require('@line/bot-sdk');constPORT=process.env.PORT||5000constconfig={channelAccessToken:process.env.ACCESS_TOKEN,channelSecret:process.env.CHANNEL_SECRET};constclient=newline.Client(config);app.post('/hook',line.middleware(config),(req,res)=>lineBot(req,res)).listen(PORT,()=>console.log(`Listening on ${PORT}`));

グローバル変数で"express"と"@line/bot-sdk"のライブラリを読み込み、expressは"app"という命名でしておきます。

次のconfigオブジェクトは@line/bot-sdkに渡してあげる設定値のようなものです。

LINE公式アカウントを作成した時にチャンネルアクセストークンチャンネルシークレットをここで使います。

この2点はそのまま記述するとセキュリティ上NGなのでHerokuの環境変数に書き込みを行います。

step3:Heroku環境変数への書き込み

ターミナル.
$ heroku config:set ACCESS_TOKEN=xxx --app 作成したアプリ名
$ heroku config:set CHANNEL_SECRET=yyy --app 作成したアプリ名

xxxにチャンネルアクセストークン
yyyにチャンネルシークレット
アプリ名にはHerokuアプリ名を入力してください。

これで環境変数の設定は完了です!

lineBot関数のコーディング

step1:コードの追加

index.js
constlineBot=(req,res)=>{res.status(200).end();constevents=req.body.events;constpromises=[];for(leti=0;i<events.length;i++){constev=events[i];switch(ev.type){case'follow':promises.push(greeting_follow(ev));break;};console.log(ev);};Promise.all(promises).then(console.log('all promises passed')).catch(e=>console.error(e.stack));}

eventsをfor文でevに分解しておりますが、実際のevの中身は下記のようになります。

step2:heroku logs --tailで状況確認

ターミナル.
$ heroku logs --tail  //====ターミナルにこのコマンドを入力====//

step3:events内容の確認
ここで作成した公式LINEアカウントを自身のスマートフォンでフォローしてみてください。
すると以下のように表示されると思います。

ターミナル.
{
type: 'follow',
replyToken: 'xxxxxxxxxxxxx',
source: { userId: 'yyyyyyyyyyyy', type: 'user'},
timestamp: 1616834333031,
mode: 'active'
}

typeはイベントの種類を表し、今回は公式アカウントをフォローされた事を意味するfollowです。他にも、messageやpostbackなどあります。公式ドキュメント

switch文でev.typeを分けています。現在はフォローされた時のイベントしか実装していませんが、今後追加していきます。

確認がとれたら、せっかくフォローしたのに申し訳ないのですがアカウントをブロックしておいて下さい。次のフォローされた際のメッセージを返すイベントの時に必要になります。

greeting_follow関数のコーディング

step1:コードの追加

index.js
constgreeting_follow=async(ev)=>{constprofile=awaitclient.getProfile(ev.source.userId);returnclient.replyMessage(ev.replyToken,{"type":"text","text":`ういー、${profile.displayName}さん、フォローあざまーすっ!\uDBC0\uDC84`,});}

非同期通信でprofileを取得していくのでasync/awaitを使います。

"text"の\uDBC0\uDC84はちょっとした遊びで付けてみました!!参考にした記事はこちら

Herokuへデプロイ

前回に私はhogemarukunというHerokuアプリ名を作成しました。
このアプリをデプロイしたいと思います。

step1:Herokuへログインからリポジトリの紐付け

ターミナル.
$ heroku login  //=====このコマンドでHerokuへログインできる=====//

$ git init  //=====リポジトリの初期化と新規作成=====//

$ heroku git:remote -a 作成したHerokuアプリ名

私の場合アプリ名は、hogemarukunです。

step2:Herokuへデプロイ

ターミナル.
$ git add .

$ git commit -m "initial commit"

$ git push heroku master

デプロイに成功すると以下のような表示がされると思います。

ターミナル.
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote: 
remote: -----> Compressing...
remote:        Done: 23.1M
remote: -----> Launching...
remote:        Released v8

step3:メッセージの確認

表示の確認がとれたら、先ほどブロックした公式アカウントのブロックを解除してみてください。

するとメッセージが返ってきているのではないでしょうか?

もし返ってこなければ、自身のスマートフォンから公式アカウントを削除して再度LINEDeveloperからQRコードを読み取ってみましょう。

おわりに

ここまででフォローから挨拶を返すまでのBOTの作成となります!
参考になれば幸いです。
ありがとうございました!!!

Phoenixで Error: Node Sass does not yet support your current environmentになった話

$
0
0

先日phoenixでmix phx.server したらこんなエラーがでた

ERROR in ./css/app.scss
Module build failed (from ./node_modules/mini-css-extract-plugin/dist/loader.js):
ModuleBuildError: Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
Error: Node Sass does not yet support your current environment: OS X 64-bit with Unsupported runtime (88)
For more information on which environments are supported please see:
https://github.com/sass/node-sass/releases/tag/v4.14.1
......
......
......

急にこんなエラーが出たのでびっくりした...
nodeのバージョンがサポートされてない??

心当たり

強いて言えば、この間vueの勉強をしようとしてnode.jsを入れた。
バージョンを見てみる。

node -v
15.x.x

おそらくバージョンが高すぎてだめなのだろう。
ダウングレードするしかないな。

どうせならバージョン管理したいな

この記事良さそう

この記事通りにnodenvを入れます。

次に

nodenv install 14.x.x
nodenv rehash

それからそのプロジェクト内で

nodenv local 14.x.x

最後に

mix phx.server

すれば上手くいくと思います。

Viewing all 8825 articles
Browse latest View live