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

SORACOM GPS マルチユニット のデータを kintone に保存する

$
0
0

概要

発表と同時に注文していた「GPS マルチユニット SORACOM Edition スターターキット」が届いたので早速 kintone へのデータ保存を試してみました。
GPS マルチユニット
SORACOMエンジニアブログによると、GPSマルチユニットは Unified Endpoint を経由して、Harbest Data や、Beame、Funnel、Funk で利用できます。
Qiita
SORACOMエンジニアブログ(4 つのセンサーと省電力通信 LTE-M 内蔵「GPS マルチユニット SORACOM Edition」発売開始!)より引用

そこで、今回は以下のようにGPSマルチユニットから、Unified Endpoint、SORACOM Funk を経由して、AWS Lambda をキックして kintone にレコードを追加する方法を試してみました。
GPS マルチユニット->Unified Endpoint->SORACOM Funk->AWS Lambda->kintone

GPS マルチユニットの準備

「GPS マルチユニット SORACOM Edition スターターキット」には、GPS マルチユニット SORACOM Edition、SORACOM 特定地域向け IoT SIMカード (plan-D サイズ:ナノ / データ通信のみ) 、充電/給電用マイクロUSBケーブル 1本が同梱されています。
GPS マルチユニット
すでにSORACOMのサイトに一通りのドキュメントがあり、それを参考に本体にSIMを入れた後に前面にあるボタンを1秒以上押すか、同梱されているUSBケーブルで充電すると本体が起動します。

GPS マルチユニット SORACOM Edition 製品仕様
https://dev.soracom.io/jp/gps_multiunit/specification/
GPS マルチユニット SORACOM Edition デバイスの使い方
https://dev.soracom.io/jp/gps_multiunit/how-to-use/

kintone の準備

今回はGPS マルチユニットのデータをkintoneに保存しますので、以下のドキュメントを参考にGPSマルチユニットのデータを全て保管するkintoneのアプリを用意します。

GPS マルチユニット SORACOM Edition 機能の説明
https://dev.soracom.io/jp/gps_multiunit/how-it-works/

kintoneに以下のようなフォームを持つアプリを追加します。
q01.png

フィールド名タイプフィードコート・要素ID
日時日時datetime
緯度(Y)数値(少数9桁)lat
経度(X)数値(少数9桁)lon
電池残量数値(少数0桁)bat
アンテナ感度数値(少数0桁)rs
温度数値(少数1桁)temp
湿度数値(少数1桁)humi
加速度X数値(少数1桁)x
加速度Y数値(少数1桁)y
加速度Z数値(少数1桁)z
タイプ数値(少数0桁)type

あとアプリの設定画面で、レコード追加権限を持つAPIトークンを追加、控えておきます。
完成したアプリは以下です。
q02.png

AWS アカウントの追加

次に、GPS マルチユニットからのデータを受信処理する AWS Lambda をキック(起動)するための、AWS アカウントを追加します。AWS コンソール IAMのユーザ画面で、ユーザを追加します。
q09.png
ユーザの詳細設定で「ユーザ名」を入力し、「アクセスの種類」にプログラムによるアクセスを選択し、次のステップに移ります。
q10.png
アクセス許可の設定で「既存ポリシーを直接アタッチ」を選択し、「ポリシーのフィルタ」に AWSLambdaFullAcessを選択して、次のステップに進みます。
q11.png
本来は先に AWS Lambda 側の設定を行った後に以下のような「ポリシーの作成」を行った方が良いのですが、今回は試験的に試すだけですのでAWSLambdaFullAcessにしています。
q12.png
ユーザの追加に成功したら、アクセスキーIDと、シークレットアクセスキーを控えておきます。
q13.png
以上でアカウントの追加は完了です。

AWS Lambda の準備

続いて、GPS マルチユニットからのデータを受信処理する AWS Lambda の設定を行います。「関数の作成」で「関数名」を入力し、ランタイムが Node.js 12.x、その他の項目は変更せず関数を作成します。(写真はソースコード他の設定を終えた状態。)
画面右上の ARN(写真はグレーで消している)は、GPS マルチユニットの通信設定に必要なので控えておきます。
q03.png
作成した関数の基本設定の「メモリ(MB)」を256MBに、「タイムアウト」を30秒に設定します。(メモリは初期値の128MBでも良いですが、タイムアウト初期値3秒は kintone API のレスポンスに遅延が発生する場合も想定して10秒以上に変更した方が良いでしょう。)
q04.png
作成した関数の非同期呼び出しの「イベントの最大有効時間」は1分程度に、「再試行」は0で行なわないようにします。(今回は、通信のタイムアウトなどで処理が中断した場合は再実行しない設定にしています。)
q05.png
GPS マルチユニット SORACOM Edition 機能の説明で紹介しているGPS マルチユニットから送信されるbase64をデコードしたフォーマットを参考に、関数のテストを追加します。
q06.png

{
    "lat":35.000000,
    "lon":139.000000,
    "bat":3,
    "rs":4,
    "temp":16.0,
    "humi":32.4,
    "x":0.0,
    "y":-64.0,
    "z":-960.0,
    "type":0
}

最初うっかりGPS マルチユニット SORACOM Edition 機能の説明で紹介している base64 ペイロードデータを処理するプログラムを作成し、base64 ペイロードデータテストを設定していましたが、実際は SORACOM Funkから Lambda にデータが渡される時にはbase64デコードしているため上記の設定になります。
q07.png
テスト実行後、kintone にレコードが追加されているのが確認できます。
q08.png

AWS Lambda のプログラム

AWS Lambda のプログラムは、以下の node.js のパッケージ request-promise と moment を追加する必要があります。
https://www.npmjs.com/package/request-promise
https://www.npmjs.com/package/moment

Lambda で node.js のパッケージを使う説明は割愛しますが、以下などを参考に設定すると良いでしょう。
Lambda の Node.js でもっといろんなパッケージを使いたいとき
https://tech-lab.sios.jp/archives/9017
AWS Lambda Layersでnode_modulesを使う
https://xp-cloud.jp/blog/2019/01/12/4630/

Lambda ではGPS マルチユニットからのデータは Json 形式で簡単に受け取れるため、実装は kintone API の Json フォーマットに変換して POST するだけです。

index.js
'use strict';constrequest=require('request-promise');constmoment=require("moment");constDomain="cybozu.com";constSubdomain="SUBDOMAIN";constPath="/k/v1/record.json";constProtocol="https://";letUrl=Protocol+Subdomain+'.'+Domain+Path;constAppId="KINTONE_APP_ID";constToken="KINTONE_TOKEN";exports.handler=asyncfunction(event,context,callback){console.log('Function Start.');vardateTime=moment().format("YYYY-MM-DDTHH:mm:ssZ");varjson={"dateTime":{"value":dateTime},"lat":{"value":event.lat},"lon":{"value":event.lon},"bat":{"value":event.bat},"rs":{"value":event.rs},"temp":{"value":event.temp},"humi":{"value":event.humi},"x":{"value":event.x},"y":{"value":event.y},"z":{"value":event.z},"type":{"value":event.type},};awaitPostKintoneRecode(request,Url,AppId,Token,json);console.log('Function Stop.');};// kintone のデータを追加するasyncfunctionPostKintoneRecode(request,url,appId,token,json){try{varoptions={url:url,method:'POST',headers:{'Content-type':'application/json','X-Cybozu-API-Token':token},json:{app:appId,record:json},};awaitrequest(options);returntrue;}catch(err){console.error(JSON.stringify(err));returnfalse;}}

GPS マルチユニットの設定

最後に、いよいよGPS マルチユニットの設定に入ります。(翌日気づいたのですが、すでにSORACOMさんの丁寧なドキュメントが公開されていましたので、以下を参考にすると良いでしょう。)
GPS マルチユニット SORACOM Editionを使用して、定期的に位置情報を送信する
https://dev.soracom.io/jp/start/gps_multiunit_location/

SIMの有効化

SORACOM のコンソールにログインして、以下の画面に遷移してGPS マルチユニットに付属していた SIM(plan-D ナノサイズ)を有効化します。
Menu->発注->注文履歴->「受け取り確認」ボタン
q14.png

SIMグループの追加と設定

SIM 有効化後、SIM グループの画面でグループを追加します。
q15.png
追加したグループの Unified Endpoint を設定します。今回は SORACOM Funk を利用しますので「フォーマット」に SORACOM Funkを選択します。
q16.png
追加したグループの SORACOM Funk を以下のように設定します。「サービス」は AWS Lambdaを選択、「送信データ作成」は JSONを選択、「認証情報」を追加(追加方法は後述)、「関数のARN」は先に控えておいた Lambda の ARN を入力します。
q17.png
「認証情報」の追加は、「認証情報」の項目にマウスを移動すると以下が表示されるので、クリックすると追加画面が表示されます。
q18.png
「認証情報」追加画面の「認証情報ID」と「概要」を入力し、「識別」はAWS 認証情報を選択、「AWS Access Key ID」と「AWS Secret Access Key」はAWS アカウントの追加で保管したアクセスキーIDとシークレットアクセスキーを入力し、登録します。
q19.png
以上のグループの設定が完了したら SIM 管理画面に戻り、先に有効化した SIM にこのグループを紐づけます。
q20.png

GPS マルチユニット本体の設定

SIM と SIMグ ループの登録・紐づけ後、以下の画面に遷移して GPS マルチユニット本体を設定します。
Menu->ガジェット管理->GPS マルチユニット
GPS マルチユニットの画面から、新規デバイス設定を行います。
使用するSIMを選択する画面で先に登録したSIMを選択し「次へ」遷移、設定を保存するグループを選択する画面で既存のグループを利用を選択し先ほど作成したグループを選択して「設定を編集」画面に遷移します。

GPS マルチユニットの設定画面で、送信内容を全てチェックします。
q21.png
送信先の「SORACOM Harvest(Lagoon)」をチェックします。(SORACOM Funk は最初からチェックされて、こちらでは変更できません。)
q22.png
送信モードは「定期送信 - 手動モード」を選択します。
q23.png
定期送信 - 手動モード詳細設定は毎日10分毎に設定します。(最初のテスト時は最短の1分毎に設定していました。)
q24.png
加速度割り込みはお好みで良いでしょう。
q25.png
以上の設定を完了したら「保存」します。

GPS マルチユニットからデータ送信の確認

GPS マルチユニット本体の設定が完了した後、「データを確認」で GPS マルチユニット本体から送られてきたデータを確認できます。
q26.png
加速度Z の値が極端なため自動ではちょっと見ずらいですが、データが取得できていることが確認できます。
q27.png

結果

kintone アプリと AWS Lambda、GPS マルチユニットの全ての設定を終えると、フィールドにGPS マルチユニットの値がセットされたkintoneアプリのレコードが追加されます。(2020/02/21 自宅の地図は面白くないので外出時に差し替え。)
q104.png

折角 GPS 情報が保管されていますので、住所/緯度経度変換プラグインで地図を表示させてみました。(2020/02/21 こちらの地図も差し替え。)
q101.png
q103.png
住所/緯度経度変換プラグイン
https://www.tis2010.jp/geocoding/

これで以前からやってみたいと思っていた kintone で移動環境計測のトレースができるので、2020/02/21朝に車の移動で早速試してみました。
IMG_7197.jpg
その結果が以下です。
q102.png
う~ん、車の移動ではデータが取得できない場合が多いようですね。週末はラーメン博があるので、食べ歩きで試してみるつもりです。

参考

「GPS マルチユニット SORACOM Edition スターターキット」の出荷を開始いたしました!
https://blog.soracom.jp/blog/2020/02/18/shipping-gps-multiunit-soracom-edition/
GPSマルチユニットSORACOM Edition ユーザーガイド
https://dev.soracom.io/jp/gps_multiunit/what-is-gps_multiunit/
SORACOM Funk を利用して AWS Lambda を呼び出し Slack へ通知する
https://dev.soracom.io/jp/start/funk_aws_lambda/
SORACOM LTE-M Button を押したログを kintone に保管
https://qiita.com/yukataoka/items/9f9daac2dc05194bbc63


NW.jsでHTMLをWindowsデスクトップアプリにする

$
0
0

NW.jsとは?

ざっくりとした理解としては、Chromiumエンジン使ってhtmlを表示するデスクトップアプリを作れるフレームワークです。

Electronとの違いなど、詳しい記事は、こちら
NW.jsでデスクトップアプリの夢を見る!

1分でWindowsデスクトップアプリをつくる

いろいろ複雑なことやっている記事が多いですが、本当に簡単なので、1分でアプリ起動できます!

手順

  1. 公式サイト( https://nwjs.io/)から、左側の「NORMAL」をダウンロードします。
    image.png

  2. zip展開し、src というフォルダを作成。そこに起動するHTMLファイル一式を格納します。

  3. package.json を作成して同フォルダに入れます。最低限の設定は以下。

package.json
{"name":"test.application","version":"1.0.0","main":"src/index.html"}

4.nw.exeを実行!!
image.png

以上。超簡単です。

NW.js Tips

package.json

ウインドウの最低サイズとアイコンの指定

package.json
{"name":"test.application","version":"1.0.0","main":"src/index.html""window":{"min_width":1200,"min_height":760,"icon":"assets/icon32.png"}}

JavaScript

いろいろな情報の寄せ集めですが、動いたコードを紹介。

exeをキックする

varspawn=require('child_process').spawn,child=spawn('C:\\windows\\notepad.exe',["C:/Windows/System32/Drivers/etc/hosts"]);child.stdout.on('data',function(data){console.log('stdout: '+data);});child.stderr.on('data',function(data){console.log('stderr: '+data);});child.on('close',function(code){console.log('child process exited with code '+code);});

エクスプローラーでフォルダーを開く

require('child_process').exec('start "" "c:\\windows"');

htmlと同フォルダーにあるファイルを関連付けられたアプリケーションで開く

varpath=require('path');varstartDir=path.dirname(process.execPath);vargui=window.require('nw.gui');gui.Shell.openItem(startDir+'\\src\\test.pdf');

標準のブラウザでURLを開く

vargui=window.require('nw.gui');gui.Shell.openExternal('https://google.com/');

AzureServiceBusを色々と試してみた

$
0
0

はじめに

アーキテクチャに弾力性を持たせるためにはキューの存在は必須なわけですがAzureにもAWS SQSと同じようなサービスが存在します。それがAzureServiceBusです。
今回も仕事で使うためにAzureServiceBusについて調べてみました。
似たものとしてBlobStorageQueueがあるのですが、そのあたりの違いについては公式のドキュメントをご覧ください。

ServiceBusの各プランの機能と料金についてはこちらをご覧ください。

準備

サンプルプログラムを試すためのServiceBusとキューはAzurePortalから予め作っておいてください。
キューの名前は何でも構いませんがサンプルプログラム内ではqueueという名前で作成されたものを使っています。

また、キューにアクセスするためのポリシーの作成と接続文字列はプログラム内で使いますのでメモしておいてください。
私は「管理」「送信」「リッスン」それぞれのポリシーを作っておきました。

image.png

簡単なメッセージの送信

簡単なプログラムを使ってメッセージを送ってみます。

send-message.js
const{ServiceBusClient}=require("@azure/service-bus");constconnectionString=process.env.SERVICE_BUS_CONNECTION_STRING;constqueueName=process.env.QUEUE_NAME;constmain=async()=>{constsbClient=ServiceBusClient.createFromConnectionString(connectionString);constqueueClient=sbClient.createQueueClient(queueName);constsender=queueClient.createSender();constmessage={body:'Hello. Service Bus',label:'test',userProperties:{testPropertyName:'property value'}};console.log('send message start');try{awaitsender.send(message);awaitqueueClient.close();console.log('send message end');}finally{awaitsbClient.close();}};main().catch(err=>{console.log(err);});

実行してみましょう

set SERVICE_BUS_CONNECTION_STRING=<ConnectionString>
set QUEUE_NAME=<QueueName>
C:\Users\uzres\products\azure-servicebus-transaction>node send-message.js
send message start
send message end

ServiceBus Explorerで確認

こちらからServiceBus Explorerをダウンロードします

File → Connectを選択し、ConnectionStringを入力します。
ConnectionStringのポリシーは「管理」である必要があります。

image.png

左メニューのqueueを選択し、Message→OKをクリックするとメッセージを見ることができます。

image.png

カスタムプロパティもみることができますね。

image.png

メッセージの受信

今度はメッセージを受信してみましょう。

receive-message.js
const{ServiceBusClient,ReceiveMode}=require("@azure/service-bus");constconnectionString=process.env.SERVICE_BUS_CONNECTION_STRING;constqueueName=process.env.QUEUE_NAME;constmain=async()=>{constsbClient=ServiceBusClient.createFromConnectionString(connectionString);constqueueClient=sbClient.createQueueClient(queueName);constreceiver=queueClient.createReceiver(ReceiveMode.receiveAndDelete)console.log('receive message start');try{constmessages=awaitreceiver.receiveMessages(5);console.log(messages.map(message=>message.body));awaitqueueClient.close();console.log('receive message end');}finally{awaitsbClient.close();}};main().catch(err=>{console.log(err);});

実行するためには、「リッスン」権限が必要です。

C:\Users\uzres\products\azure-servicebus-transaction>node receive-message.js
receive message start
[ 'Hello. Service Bus' ]
receive message end

実行してみるとわかりますが結構待たされます。
これはメッセージを受信するときに60秒間待つからです。
短くするにはreceveMessagesの2番目の引数に待つ秒数を指定します。

constmessages=awaitreceiver.receiveMessages(5,1);

Azure Functionで受信

先ほどの例ではとあるタイミングでプログラムを実行しQueueからメッセージを受信しましたが、Queueに入ったタイミングでFunctionを動かすことができます。
これでQueueをポーリングする手間がなくなりますね。素晴らしい・・・。

関数の引数であるmySbMsgにメッセージが入ってきます。

module.exports = async function(context, mySbMsg) {
    context.log('JavaScript ServiceBus queue trigger function processed message', mySbMsg);
    context.log('EnqueuedTimeUtc =', context.bindingData.enqueuedTimeUtc);
    context.log('DeliveryCount =', context.bindingData.deliveryCount);
    context.log('MessageId =', context.bindingData.messageId);
};
functions.json
{"bindings":[{"name":"mySbMsg","type":"serviceBusTrigger","direction":"in","queueName":"queue","connection":""}]}

Functionを設定するときの注意点

  • connectionのところを空にした場合は、アプリケーション設定の追加で、AzureWebJobsServiceBusという名前でQueueへの接続文字列を設定しておきます。
  • function.jsonにqueueNameを指定しているので接続文字列からはEntityPath=queueを削除しておかないとエラーになります。
  • このへんの設定はトリガーとバインド ServiceBusを確認してください。

Azure Functionで送信

Bindingという機能を使うとServiceBusにメッセージを送ることができます。
先ほど作ったfuctionで受け取ったメッセージをoutputqueueというQueueに投入してみたいと思います。

まずはfunction.jsonにdirectionがoutの要素を追記します。

function.json
{"bindings":[{"name":"mySbMsg","type":"serviceBusTrigger","direction":"in","queueName":"queue","connection":""},{"name":"output","type":"serviceBus","queueName":"outputqueue","connection":"OutputConnection","direction":"out"}]}

プログラムも少し変えましょう。受信したメッセージにoutput を付けて、outputqueueにメッセージを入れてみます。
bindingsの後の文字列はfunction.jsonのnameで指定した文字列になります。

index.js
module.exports=asyncfunction(context,mySbMsg){context.log('JavaScript ServiceBus queue trigger function processed message',mySbMsg);context.log('EnqueuedTimeUtc =',context.bindingData.enqueuedTimeUtc);context.log('DeliveryCount =',context.bindingData.deliveryCount);context.log('MessageId =',context.bindingData.messageId);constoutputMessage='output'+mySbMsg;context.bindings.output=outputMessage;};

今回はfunction.jsonのconnectionにOutputConnectionを指定したので、Functionの環境変数にっはAzureWebJobsOutputConnectionという名前で接続文字列を指定します。

image.png

実行してoutputqueueの中身を見るとメッセージが配信されていることがわかりますね。

image.png

PeekLockとReceiveAndDelete

AzureFunctionでメッセージを受信したとき既定の動作はPeekLockです。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-service-bus?tabs=csharp#trigger---peeklock-behavior

処理が正常に終了すれば、FunctionからCompleteを呼び出しQueueからメッセージが削除されるようになっています。ReceiveAndDeleteに設定することもできますが、これは受信した瞬間にメッセージが削除されるモードです。これはエラー発生時にメッセージがロストしてしまう可能性があります。

DeadLetter

MaxDeliveryCountに指定されている回数分処理が失敗することや、TimeToLiveの超過が起こるとDeadLetterQueue(DLQ)にメッセージが送信されます。
これはアプリケーションで明示的に指定することも可能です。

DLQの中身は自動的にクリーンアップされないため、DLQに移ったメッセージはログに出力するなどして確実に処理する必要があります。

早速試してみましょう。

DeadLetterQueueにメッセージを移す簡単な方法は、先ほど作ったFunctionの出力先であるoutputqueueを削除したあとにメッセージを送りましょう。

少し経ってFunctionのログを見ると10回失敗していることがわかります。

image.png

ExplorerでDeadLetterQueueに移っていることも確認できます。

image.png

DLQのメッセージを取得してみたいと思います。

receive-DLQ-message.js
const{ServiceBusClient,ReceiveMode}=require("@azure/service-bus");constconnectionString=process.env.SERVICE_BUS_CONNECTION_STRING;constqueueName=process.env.QUEUE_NAME+"/$DeadLetterQueue";constmain=async()=>{constsbClient=ServiceBusClient.createFromConnectionString(connectionString);constqueueClient=sbClient.createQueueClient(queueName);constreceiver=queueClient.createReceiver(ReceiveMode.peekLock)console.log('receive DLQ message start');try{constmessages=awaitreceiver.receiveMessages(5,1);for(leti=0;i<messages.length;i++){constmessage=messages[i];console.log(message.body)awaitmessage.complete();}awaitqueueClient.close();console.log('receive DLQ message end');}finally{awaitsbClient.close();}};main().catch(err=>{console.log(err);});

DLQはQueueNameの後ろに/$DeadLetterQueueを付けただけです。
今回は、PeekLockで取得するのでメッセージ取得後complete()を呼び出しDLQからメッセージを削除しています。

おわりに

シンプルなメッセージのやり取りからFunctionを使った送受信、DLQまで試してみましたがいかがだったでしょうか。割と簡単に処理できることがお分かりいただけたかなと思います。

npmコマンドで順番を無視した引数を使う方法

$
0
0

npmコマンドを書いている中で、オプションをprocess.argv[2]と取りますが、
これの順番を決め打ちで取りに行かない方法を探した結果、argvを使うことにしました。

前提条件

  • npmがインストールされていること
  • package.jsonがあること

インストール

$ npm install-D argv

使い方

ファイル作成

touchコマンドでファイルを作成します。

$ touch test.js option.json

test.js

argvモジュールの読み込みと、option.jsonの読み込み、
argvのメソッドで引数のデータを取り込みます。

constargv=require('argv')constoption=require('./option')constargs=argv.option(option).run()console.log(args)

option.json

引数で使うための名前と型、を設定するオブジェクトを設定します。
配列になっているため、必要な数だけオプションを増やすことが可能です。

[{"name":"option","short":"o","type":"string","description":"あなたのスクリプトのオプションを定義します","example":"'script --option=value' or 'script -o value'"}]

package.json

npm run testコマンドでtest.jsを動かすように定義します。
オプションの設定をされていない引数は配列でtargetsとして記録されます。

{"scripts":{"test":"node test sample -o aaa"}}

実行

npmコマンドを実行します。

$ npm run test

出力結果

{ targets: [‘sample’ ], options: { option: 'aaa'}}

オブジェクト形式でデータが取れているので、特定の値を取るときは、
args.options.optionという形式でaaaと取ることができます。

参考文献

この記事は以下の情報を参考にして執筆しました。

puppeteerでThis relative module was not found: ~ ws ~ WebSocketTransport.js

$
0
0

環境

・windows10
・vue-cli3
・electron-builder

現象

electronでpuppeteerを使おうとした。
npm run electron:serveしたら、

This dependency was not found:

* ws in ./node_modules/puppeteer/lib/WebSocketTransport.js

To install it, you can run: npm install --save ws

対応

To install it, you can run: npm install --save ws

とあるので言われたとおりにコマンドを打つ。
で、再度build。
しかし、同じエラーが…。

解決策

node_modules/puppeteer/lib/WebSocketTransport.jsをいじる。

const WebSocket = require('ws');

const WebSocket = require('../node_modules/ws');

に修正する。(wsをnpm installした後に)

備考

issueはこちら
https://github.com/puppeteer/puppeteer/issues/3487

自分の環境ではこの方法で助かったが、リンク先は割と紛糾しているみたい?
この分野(electron+αの開発)は日本語の参考記事も少ないので、じぶんみたいなぺーぺーだとわりと簡単なところで結構つまづく。

CLIP STUDIO PAINTのファイルからNode.jsでサムネイル画像を出力する

$
0
0

CLIP STUDIO PAINTファイル(.clip)のサムネイル画像をNode.js上で出力してみました。

CLIPSTUDIO PAINTのファイルからサムネイル画像を取得

CLIP STUDIO PAINTで作成した漫画データのサムネール出力の為の調査

こちらの記事のように、Pythonやシェル、Goで実装されたライブラリ等はあったものの、JavaScript(TypeScript)で実装されたものが無かったので調べてみました。

概要

  • clipファイルからSQLite部分のデータを切り出し
  • 切り出したSQLiteファイルからサムネイル画像のデータを抽出

次節から実装になりますが、ファイルの読み書きにfs.readFileSync/writeFileSyncを使用しています。
また、単なる出力テストなので、実装する場合はfs.readFile/writeFileの使用及び適宜例外処理をお願いします。

コード

とりあえず全体像。TypeScriptで記述しています。
外部モジュールとしてsqlite3をインストールしてください。

$ npm i sqlite3
import*asfsfrom"fs"import*assqlite3from"sqlite3"// .clipファイルパスconstpath="../sample_asset/illust1"constclipPath=path+".clip"// Bufferの読み込みconstclipBuffer=fs.readFileSync(clipPath)// SQLiteのデータは「SQLite format 3」から始まるらしいconstsearchText="SQLite format 3"// Uint8Arrayに変換constuint8Array=newUint8Array(Buffer.from(searchText))// clipからSQLiteのデータのスタート位置を取得constfindIndex=clipBuffer.indexOf(uint8Array)// SQLite部分のみ切り出しconstdbBuf=clipBuffer.slice(findIndex,clipBuffer.length)// 一時書き出し用のSQLiteファイルパスconstdbPath=path+".sqlite"// いったんsqliteファイルに書き出しfs.writeFileSync(dbPath,dbBuf)// sqlite3モジュールでSQLiteファイルを展開constdb=newsqlite3.Database(dbPath)db.serialize(()=>{// CanvasPreviewテーブルからImageData(png)を抽出db.get("SELECT ImageData FROM CanvasPreview",(err,row)=>{if(err){console.log(err)return}if(row.length>0){// png出力パスconstbinaryPath=path+".png"// rowのImageDataプロパティからpngを生成fs.writeFileSync(binaryPath,row.ImageData)}})})

解説

clipファイルの読み込み

// .clipファイルパスconstpath="..\\sample_asset\\illust1"constclipPath=path+".clip"// Bufferの読み込みconstclipBuffer=fs.readFileSync(clipPath)

fs.readFileSyncでclipファイルの読み込みを行う。
fs.readFileSyncBufferを返す。

SQLiteのデータの位置を探索

clipファイルは何らかのメタデータ+SQLiteで構成されているらしく、
そのままでは下記のとおりSQLiteとして展開できない。

// PowerShellで確認
$ C:\...> sqlite3 illust1.clip
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.

// テーブルリストを出力
$ sqlite> .table
Error: file is not a database

そこで、SQLiteのデータだけ切り出してやる必要がある。
噂によるとSQLiteのデータは「SQLite format 3」から始まるらしい。

// SQLiteのデータは「SQLite format 3」から始まるらしいconstsearchText="SQLite format 3"// Uint8Arrayに変換constunit8Array=newUint8Array(Buffer.from(searchText))// clipからSQLiteのデータのスタート位置を取得constfindIndex=clipBuffer.indexOf(uint8Array)// SQLite部分のみ切り出しconstdbBuf=clipBuffer.slice(findIndex,clipBuffer.length)// 一時書き出し用のSQLiteファイルパスconstdbPath=path+".sqlite"// いったんsqliteファイルに書き出しfs.writeFileSync(dbPath,dbBuf)

Buffer#indexOfにて、SQLiteのインデックスを取得する。
インデックスが分かったら、そこから終点までsliceしてやる。

取得したバッファをfs.writeFileSyncでSQLiteファイルとして書き出し。

SQLiteの展開とpngの書き出し

SQLiteファイルのテーブルは次のようになっており、
CanvasPreviewテーブルにプレビュー画像のデータが保持されている。

// PowerShellで確認
$ C\...> sqlite3 illust1.sqlite //←上で生成したやつ
$ sqlite3> .table
AnimationCutBank            Layer
Canvas                      LayerThumbnail
CanvasItem                  Mipmap
CanvasItemBank              MipmapInfo
CanvasPreview               Offscreen
ElemScheme                  ParamScheme
ExternalChunk               Project
ExternalTableAndColumnName  RemovedExternal

sqlite3モジュールにより、先ほど生成したSQLiteファイルを展開する。

// sqlite3モジュールでSQLiteファイルを展開constdb=newsqlite3.Database(dbPath)db.serialize(()=>{// CanvasPreviewテーブルからImageData(png)を抽出db.get("select ImageData from CanvasPreview",(err,row)=>{if(err){console.log(err)return}if(row.length>0){// png出力パスconstbinaryPath=path+".png"// row.ImageDataプロパティからpngを生成fs.writeFileSync(binaryPath,row.ImageData)}})})

クエリにてCanvasPreviewからImageDataを取得する。
このとき出力結果のrowには、次の形でデータが格納されている。

{ImageData:<Buffer89504e470d0a...>}

つまり、row.ImageDataで目的のサムネイル画像のデータを取得することが可能。
最後に、fs.writeFileSyncによりillust1.pngrow.ImageDataを書き出してやれば終了である。

参考

CLIP STUDIO PAINTで作成した漫画データのサムネール出力の為の調査

CLIPSTUDIO PAINTのファイルからサムネイル画像を取得

CLIP STUDIO PAINTの.lipファイルをハックして作業動画を書き出すWindowsアプリを作った

Node.js で TextEncoder や SHA512 を作る

MDN - オブジェクトでの作業

Node.js で SQLite を扱う

Writing to Files in Node.js

Get list of tables from SQLite in Node.js

主にPowerShell関係

MS - certutil

Windows10でバイナリ<->HEXテキスト変換

PowerShellからデータを出力するパターンまとめ

cutコマンドについてまとめました 【Linuxコマンド集】

Windows PowerShell : UNIXの cut コマンド相当の作業をする。

【NestJS】dotenvを使って環境変数を読み込む

$
0
0

やりたいこと

環境変数で値を設定する。

環境

NestJS用に、内部的にdotenvを使っている@nestjs/configもありますが、dotenvそのものを使うほうが簡便だと思っています。

インストール

yarn add dotenv

// or

npm install dotenv

設定

ここでは、アプリ内で統一して環境変数を読み込む場所として、src/config/app.config.tsを想定しています。

src/config/app.config.ts
require('dotenv').config()if(!process.env.DB_PASSWORD){thrownewError('DB_PASSWORD must be set.')}exportconstDB_PASSWORD=process.env.DB_PASSWORD

【NestJS】@nestjs/configを使って環境変数を読み込む

$
0
0

やりたいこと

環境変数で設定した値をNestJSアプリ内で使う。

環境

@nestjs/configは、内部的にdotenvを使っています。
両方を使ってみて、dotenvそのものを使うほうが簡便なように感じましたが、備忘録のため設定手順を残しておきます。

dotenvを使う場合の手順はこちら

インストール

yarn add @nestjs/config

// or

npm install @nestjs/config

使い方

まずsrc/app.module.tsで読みこみます。

src/app.module.ts
import{ConfigModule}from'@nestjs/config'// この行と:@Module({imports:[ConfigModule.forRoot()],// この行を追加controllers:[AppController],providers:[AppService],})exportclassAppModule{}

global moduleとして読み込む場合は、{ isGlobal: true }を渡します。

src/app.module.ts
imports:[ConfigModule.forRoot({isGlobal:true})],

実際に環境変数を使いたい場所でconfig.getを使って読み込みます

src/foo/foo.service.ts
import{ConfigService}from'@nestjs/config'@Injectable()exportclassFoo{constructor(privatereadonlyconfig:ConfigService,){constval=this.config.get('SOME_VALUE')

isGlobal: trueを設定している場合は以上ですが、設定していない場合は対応するmoduleでinjectします。

src/foo/foo.module.ts
import{ConfigModule,ConfigService}from'@nestjs/config'// ここと@Module({imports:[FooModule,ConfigModule],// こことcontrollers:[FooController],providers:[FooService,FooInvoker,ConfigService],// ここ

ドキュメント

https://docs.nestjs.com/techniques/configuration


COTOHA アクセストークンの取得 (Node.js)

$
0
0

COTOHA API Portalの使用例です。

次と同じことを Node.js で行いました。

COTOHA アクセストークンの取得

access_token.js
#! /usr/bin/node
// ---------------------------------------------------------------////  access_token.js////                  Feb/22/2020//// ---------------------------------------------------------------console.error("*** 開始 ***")constdotenv=require('dotenv')varClient=require('node-rest-client').Clientdotenv.config()varconfig={client_id:`${process.env.CLIENT_ID}`,client_secret:`${process.env.CLIENT_SECRET}`,developer_api_base:`${process.env.DEVELOPER_API_BASE_URL}`,access_token_publish_url:`${process.env.ACCESS_TOKEN_PUBLISH_URL}`,}constdata={"grantType":"client_credentials","clientId":config.client_id,"clientSecret":config.client_secret}consturl=config.access_token_publish_urlconstheaders={"Content-Type":"application/json"}constargs={data:data,headers:headers}varclient=newClient()client.post(url,args,function(data,response){console.log(data)console.error("*** 終了 ***")})// ---------------------------------------------------------------

実行コマンド

export NODE_PATH=/usr/lib/node_modules
./access_token.js

使ったバージョン

$ node --version
v13.9.0

5分で作れるWebAPIテストサーバー(自動デプロイも)

$
0
0

はじめに

5分で作れるかは分からない(笑)
WebAPIのテスト用のサーバーがほしいので作ってみた。
「node.js + Express + App Service(Azure)」 の構成で作ります。
レスポンス値を変更したときにデプロイするのは面倒くさいので、gitへのpushをキーにデプロイするようにします。

作ろうと思った経緯

ローカルテスト環境がなく、お客さんに許可を取らないと(他の会社さんが作っている)WebAPIを含む機能のテストができない。
許可申請から承認が下りるまでに1週間かかり、その間は待機しているらしい。
テスト用のダミーサーバーを作って試験を進められるようにしたい。

前提条件

  • nodeのインストール済(検証環境はnodistインストール済だった)
  • Azureのサブスクリプション、リソースグループは作成済

検証環境

  • Windows10
  • VSCode 1.42.1
  • nodist: 0.9.1
  • node: 10.15.3
  • npm: 6.9.0

とりあえずローカルで作ってみる

Azureのチュートリアルに沿って作成すればOK。
* クイック スタート: Node.js Web アプリを作成する | Microsoft Docs
以下からのコマンドは、VSCodeのターミナル(bash)で実施している。

てきとうなディレクトリを作成し、移動する

cd azure-app-service

expressプロジェクトの作成

コマンド

npx express-generator myExpressApp --view pug --git

実行結果

$npx express-generator myExpressApp --view pug --gitnpx: 10個のパッケージを20.703秒でインストールしました。

   create : myExpressApp\
   create : myExpressApp\public\
   create : myExpressApp\public\javascripts\
   create : myExpressApp\public\images\
   create : myExpressApp\public\stylesheets\
   create : myExpressApp\public\stylesheets\style.css
   create : myExpressApp\routes\
   create : myExpressApp\routes\index.js
   create : myExpressApp\routes\users.js
   create : myExpressApp\views\
   create : myExpressApp\views\error.pug
   create : myExpressApp\views\index.pug
   create : myExpressApp\views\layout.pug
   create : myExpressApp\.gitignore
   create : myExpressApp\app.js
   create : myExpressApp\package.json
   create : myExpressApp\bin\
   create : myExpressApp\bin\www

   change directory:
     $cd myExpressApp

   install dependencies:
     $npm install
   run the app:
     $DEBUG=myexpressapp:* npm start

npmインストール

コマンド

cd myExpressApp
npm install

実行結果

$npm installnpm WARN deprecated core-js@2.6.11: core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.
npm WARN lifecycle The node binary used for scripts is C:\Program Files (x86)\Nodist\bin\node.exe but npm is using C:\Program Files (x86)\Nodist\v-x64\10.15.3\node.exe itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed 
with.

>core-js@2.6.11 postinstall C:\workspace\azure-app-service\myExpressApp\node_modules\core-js
>node -e"try{require('./postinstall')}catch(e){}"
Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!

The project needs your help! Please consider supporting of core-js on Open Collective or Patreon: 
>https://opencollective.com/core-js 
>https://www.patreon.com/zloirock 

Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)

npm notice created a lockfile as package-lock.json. You should commit this file.
added 118 packages from 174 contributors and audited 247 packages in 18.009s
found 1 low severity vulnerability
  run `npm audit fix` to fix them, or `npm audit` for details

サーバー起動

ここまでexpressプロジェクトができているので、サーバーが起動できる。

コマンド

npm start

実行結果

$npm start
npm WARN lifecycle The node binary used for scripts is C:\Program Files (x86)\Nodist\bin\node.exe but npm is using C:\Program Files (x86)\Nodist\v-x64\10.15.3\node.exe itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed 
with.

>myexpressapp@0.0.0 start C:\workspace\azure-app-service\myExpressApp
>node ./bin/www

ブラウザで確認

ブラウザで localhost:3000にアクセス。

001.png

動いているのが確認できた。

AzureでAppServiceを作成する

AppServiceを作成する

AzurePortalを開く。

追加をクリック。

002.png

サービスの設定

各項目を入力する。

003.png

  • サブスクリプション:事前に用意したもの
  • リソースグループ:事前に用意したもの
  • 名前:任意。ここの入力名がURLの1部になる
  • ランタイムスタック:今回はnodeを使うので、最新の「Node 12 LTS」にした
  • 地域:とりあえず東日本
  • SKUとサイズ:初期値はいい感じのスペックのものにされているので、1番安価な「F1」に変更

入力したら「確認および作成」を押下 → 確認画面に遷移するのでそのまま「作成」

問題なければ、デプロイが開始される。

1分くらいでデプロイが完了する。
004.png

自動デプロイの準備

githubでデプロイ用のリポジトリを作成する

自分は、「azure-app-service」を作成した。

ローカルにクローンする

任意の場所に先ほど作成したリポジトリをクローンする。

資材の移動

ローカルでの検証時に作成した「myExpressApp」の中身をリポジトリの直下にコピーする。

移動後のディレクトリ構成イメージ

006.png

動作確認

この状態で動くか確認する

npm start

して

localhost:3000

に接続してExpressの画面が出ればOK

コミット

動作が確認できたので、念のためこの状態でコミット→プッシュしておく。

Azureにデプロイする

「自動デプロイの準備」で作成したプロジェクトをVSCodeを開く。

拡張機能をインストール

Azure App Serviceを操作するための、拡張機能「Azure App Service」をインストールする。

インストールが完了すると左部のメニューに追加されるので選択する。
005.png

上部の「↑」を選択

上部の「↑」(Deplay To Web App...)を選択する。

  • Select the folder to zip and deploy: Expressプロジェクトのルートディレクトリを選択(azure-app-service)
  • Select subscription: AppServiceを作成したサブスクリプション
  • Select Web App: 作成したAppServiceを選択
    • 上書きしてよいか確認ダイアログが出るので、「OK」→「Deploy」

デプロイが始まるので、完了するまで待つ(10秒くらい)。

デプロイが完了したら実際に接続してみる

「https://<作成したAppService名>.azurewebsites.net/」にブラウザで接続

デプロイができていれば下記のようにローカルと同じ画面が出る。
007.png

これでデプロイはOK。

Azureに自動デプロイする

デプロイ設定を行う

AzurePortalで作成したAppServiceを開き、画面左部メニューから「デプロイセンター」を選択する。

008.png

デプロイ設定

  1. ソース管理:Githubを選択(初回は認証がかかるかも)
  2. ビルドプロバイダー:App Serviceのビルドサービス
  3. 構成:
    • 組織:自分のgithubアカウント
    • リポジトリ:作成したリポジトリ
    • ブランチ:master
  4. 概要:内容を確認して、完了を押下

デプロイ設定もこれで完了。

今後はmasterブランチにプッシュされたタイミングでビルドが走るようになる。

(参考)作業を行ったgithubリポジトリ

おわりに

今回はじめてwebサーバを作成したが、こんなにも簡単にできるんだという感想。
自分は今までプログラミングがメインでAzureを使ったり、サーバを構築したりという作業をしたことがなかった。
作ったものが自分のPC内だけで動いているのと、全世界からつながっているのはやっぱり感動が違う。
今回はテスト用のレスポンスサーバーが目的だったが、Webサービスも簡単に作れるようになっているのはすごいと思った。
Webサービスを公開する予定はないが、このように簡単に構築できることを知れて良かった。

nodebrewでnodeをインストール

$
0
0

homebrewを使ってnodeをインストールする

brew install nodebrew

ちゃんと入ったか確認
nodebrew -v
セットアップ
nodebrew setup
pathを設定しろと言われるのでbash_profileに追加
export PATH=$HOME/.nodebrew/current/bin:$PATH

再読み込み
. ~/.bash_profile

nodeをインストール

利用可能な一覧
ls-remote
使用可能なバージョンがリストされるので好きなものをインストール(記載時点の最新)
nodebrew install-binary v13.9.0
ちゃんとあるか確認
nodebrew ls
v13.9.0が表示されればOK

nodebrew use v13.9.0
use v13.9.0 が表示される

node --version
v13.9.0が表示されればOK

花粉症対策デジタル医療相談Botの開発 ユーザーIDと位置情報をFirestoreで管理

$
0
0

概要

耳鼻咽喉科の開業医をしながらデジタルテクノロジーを使った医療の効率化や患者さん向けサービスの開発研究を行っています。

スギ花粉の飛散量が増えてきました。花粉症の方にはつらい季節ですね。
忙しくて医療機関を受診できなかったり、新型コロナウイルスが心配で受診を控えている方も多いのではないでしょうか?

最近薬局や通販で購入できる医療用医薬品(医療機関で処方されるものと同成分)が増えてきたのはご存じでしょうか?これらの薬を上手に利用できれば医療機関を受診できなくても花粉シーズンを乗り越えることが出来るかもしれません。

上手に利用するには自分の花粉症状がどの程度重症なのかや、利用しようとする薬の特性を知っていないといけませんが、その辺を教えてくれるサービスがなかったので作成してみました。現在(2020年2月19日~3月4日)クラウドファンディングプラットフォームCAMPFIREでテスト版ユーザー募集しています。プロジェクトはサクセスしましたが、たくさんの花粉症の方に使って頂いて、サービス向上のためご意見ご感想をいただきたいと思っています。

CAMPFIREのプロジェクトページはこちら
LINEで花粉症の重症度や最適な市販薬がわかるデジタル医療相談【アレルナビ】

このサービスではユーザーが特定した地点のピンポイント花粉飛散予測を返す機能があります。ユーザーから送っていただいた位置情報とLINE IDはFirestoreで管理しましたのでその辺りをまとめました。

FirestoreのDatabase

・コレクションに位置情報をまとめた「locations」とLINE IDをまとめた「users」が作成されています。
・コレクション「locations」と「users」のドキュメントはユーザーがLINEを使うときに取得できるidで紐づけられています。
・コレクション「locations」のフィールドはユーザーから位置情報が送られてくるたびに更新されます(latitude緯度、longitude経度)。
・コレクション「users」のフィールドはユーザーのLINE IDが入ります。
image.png

image.png

作成方法

1. Firebaseで新規プロジェクトを作成
・Googleにログインしている状態で、Firebase公式ページの右上にある「コンソールへ移動」ボタンから、ユーザーページに移動。
・「プロジェクトを追加」から新規プロジェクトを作成。

2. Firestoreを作成
・プロジェクトメインページ左のメニューバーから「Database」を選び、「データベースの作成」に進む。
・「テストモードで開始」を選択し、「有効にする」をクリックしデータベースを作成。

3. Firebaseとnode.jsで開発したアプリを連携
・Firebaseのプロジェクトのメインページから、「アプリを追加」→「ウェブ」に進む。
・任意のアプリ名を入力し、「アプリを登録」をクリックし連携に必要なコードを表示する。

4. Firebase SDK を追加して Firebase を初期化
こちらを参考にしました。
Firebase を JavaScript プロジェクトに追加する

5. プログラム作成
ユーザーからメッセージが来たらユーザーIDが登録されているかを判定
登録されてなければFirebaseに登録

letuserRef=db.collection('users');letsnapshot=awaituserRef.where("line_user_id","==",event.source.userId).get();letuser_id="";if(snapshot.empty){user_id=awaituserRef.add({line_user_id:event.source.userId}).then(ref=>ref.id);}else{user_id=snapshot.docs[0].id;}console.log(user_id);

位置情報が送られてきたらFirebaseの位置情報をidに紐づけて更新し
「位置情報が登録されました」をユーザーに返す

if(event.message.type==="location"){client.replyMessage(event.replyToken,{type:'text',text:"位置情報が登録されました。"});letlocationsRef=db.collection('locations').doc(user_id);letsetAda=locationsRef.set({latitude:event.message.latitude,longitude:event.message.longitude,});returnPromise.resolve(null);}

完成図

ピンポイント花粉情報がユーザーに返されます
IMG-0997.PNG

考察

最初はユーザーから送られるすべてのIDと位置情報をFirebaseに登録しif文で取得していたため、ユーザーが増えると処理に時間がかかりそうでした。idは重複がないように、位置情報は最新のものだけを登録できたのでスッキリして気持ちがよいですね。今後は内服薬やアレルギーの重症度、花粉飛散の実測値を登録・分析することによってユーザーの住んでいる場所の予測飛散量とユーザーの重症度から適切な治療薬を推奨できるようにしていきたいと思っています。

COTOHA API で構文解析 (Node.js)

$
0
0

COTOHA API Portalの使用例です。

次と同じことを Node.js で行いました。

COTOHA API で構文解析

フォルダー構造

$ tree -a
.
├── .env
├── get_config.js
├── get_token.js
└── parsing.js
parsing.js
#! /usr/bin/node
// ---------------------------------------------------------------////  parsing.js////                  Feb/23/2020//// ---------------------------------------------------------------varget_config=require('./get_config.js')varget_token=require('./get_token.js')// ---------------------------------------------------------------functionparse_proc(config,sentence){varClient=require('node-rest-client').Clientconstheaders={"Content-Type":"application/json","Authorization":"Bearer "+config.access_token}constdata={"sentence":sentence,"type":"default"}consturl=config.developer_api_base+"v1/parse"constargs={data:data,headers:headers}varclient=newClient()client.post(url,args,function(data,response){for(itindata["result"]){for(jtindata["result"][it].tokens){consttoken=data["result"][it].tokens[jt]console.log(token.form+"\t"+token.pos)}}console.error("*** 終了 ***")})}// ---------------------------------------------------------------console.error("*** 開始 ***")constsentence="特急はくたか"constconfig=get_config.get_config_proc()get_token.get_token_proc(config,sentence,parse_proc)// ---------------------------------------------------------------
get_config.js
// ---------------------------------------------------------------////  get_config.js////                  Feb/23/2020//// ---------------------------------------------------------------exports.get_config_proc=function(){constdotenv=require('dotenv')dotenv.config()constconfig={client_id:`${process.env.CLIENT_ID}`,client_secret:`${process.env.CLIENT_SECRET}`,developer_api_base:`${process.env.DEVELOPER_API_BASE_URL}`,access_token_publish_url:`${process.env.ACCESS_TOKEN_PUBLISH_URL}`,}returnconfig}// ---------------------------------------------------------------
get_token.js
// ---------------------------------------------------------------////  get_token.js////                  Feb/23/2020//// ---------------------------------------------------------------exports.get_token_proc=function(config,sentence,callback){varClient=require('node-rest-client').Clientconstdata={"grantType":"client_credentials","clientId":config.client_id,"clientSecret":config.client_secret}consturl=config.access_token_publish_urlconstheaders={"Content-Type":"application/json"}constargs={data:data,headers:headers}varclient=newClient()client.post(url,args,function(data,response){config['access_token']=data.access_tokencallback(config,sentence)})}// ---------------------------------------------------------------

実行コマンド

export NODE_PATH=/usr/lib/node_modules
./parsing.js

COTOHA で固有名詞の抽出 (Node.js)

$
0
0

COTOHA API Portalの使用例です。

次と同じことを Node.js で行いました。

COTOHA で固有名詞の抽出

フォルダー構造

$ tree -a
.
├── .env
├── get_config.js
├── get_token.js
└── proper_noun.js
proper_noun.js
#! /usr/bin/node
// ---------------------------------------------------------------////  proper_noun.js////                  Feb/23/2020//// ---------------------------------------------------------------varget_config=require('./get_config.js')varget_token=require('./get_token.js')// ---------------------------------------------------------------functionproper_noun_proc(config,sentence){varClient=require('node-rest-client').Clientconstheaders={"Content-Type":"application/json","Authorization":"Bearer "+config.access_token}constdata={"sentence":sentence,"type":"default"}consturl=config.developer_api_base+"v1/ne"constargs={data:data,headers:headers}varclient=newClient()client.post(url,args,function(data,response){llx=data["result"].lengthconsole.log("llx = "+llx)for(itindata["result"]){constunit=data["result"][it]console.log(unit['form'])}console.error("*** 終了 ***")})}// ---------------------------------------------------------------console.error("*** 開始 ***")constsentence="特急はくたかで富山に向かいます。それから、金沢に行って、兼六園に行きます。"constconfig=get_config.get_config_proc()get_token.get_token_proc(config,sentence,proper_noun_proc)// ---------------------------------------------------------------

get_config.js get_token.js はこちら
COTOHA API で構文解析 (Node.js)

実行コマンド

export NODE_PATH=/usr/lib/node_modules
./proper_noun.js

COTOHA でキーワードの抽出 (Node.js)

$
0
0

COTOHA API Portalの使用例です。

次と同じことを Node.js で行いました。

COTOHA でキーワードの抽出

フォルダー構造

$ tree -a
.
├── akai_rousoku.txt
├── .env
├── get_config.js
├── get_token.js
└── key_word.js
key_word.js
#! /usr/bin/node
// ---------------------------------------------------------------////  key_word.js////                  Feb/23/2020//// ---------------------------------------------------------------varfs=require("fs")varget_config=require('./get_config.js')varget_token=require('./get_token.js')// ---------------------------------------------------------------functionkey_word_proc(config,doc){varClient=require('node-rest-client').Clientconstheaders={"Content-Type":"application/json","Authorization":"Bearer "+config.access_token}constdata={"document":doc,"type":"default"}conststr_json=JSON.stringify(data)consturl=config.developer_api_base+"v1/keyword"constargs={data:str_json,headers:headers}varclient=newClient()client.post(url,args,function(data,response){llx=data["result"].lengthconsole.log("llx = "+llx)for(itindata["result"]){constunit=data["result"][it]console.log(unit['form']+"\t"+unit['score'])}console.error("*** 終了 ***")})}// ---------------------------------------------------------------console.error("*** 開始 ***")constfilename=process.argv[2]console.error(filename)if(fs.existsSync(filename)){constdoc=fs.readFileSync(filename,'utf8')constconfig=get_config.get_config_proc()try{get_token.get_token_proc(config,doc,key_word_proc)}catch(error){console.error("*** error *** from get_token_proc ***")console.error(error)}}else{console.error("*** error *** "+filename+" doesn't exist. ***")}// ---------------------------------------------------------------

get_config.js get_token.js はこちら
COTOHA API で構文解析 (Node.js)

実行コマンド

export NODE_PATH=/usr/lib/node_modules
./key_word.js akai_rousoku.txt

DialogFlowで会話をし、MobileNetで画像認識を行う自家製チャットサイトの構築

$
0
0

はじめに

 第一回 LINE風チャットサイトの構築(基本編)
 第二回 LINE風チャットサイトの構築(完成編)
 ということで、第三回目はこの自家製チャットサイトにボット機能と画像認識機能を加えたいと思います。

ボット機能(DialogFlow)画像認識機能(MobileNet)
10.png07.png

Dialogflow

 googleのdialogflowを使います。会話の設定などはあらかじめ作成したものがあるので、それを読み込んで使います。
 ここではDialogflowの詳しい解説は行いませんが、サンプルエージェント(英語版)を手軽に読み込めますので学習の手始めにいいと思います。なお今回のエージェントはそのサンプルエージェント coffee-shopを大雑把に日本語翻訳したものです。

エージェント構築手順

まずは私の方で作成したエクスポートzipファイルを、ご自分のローカルマシンにダウンロードしてください。

CafeMaster.zip

ファイル名はそのままでOKです。

そしてDialogflowコンソールへアクセスします
https://dialogflow.cloud.google.com/?hl=ja#/login

中央の「Sign-in with Google」をクリックしてログインします。

1Dialogflow-login.png

IDの指定と連携確認が行われた後、下記の画面になります。
上部のお知らせは消してしまいましょう
サービス規約を確認します

2Dialogflow-login2.png
ACCEPTをクリックするとWelcome画面になります
早速エージェント作成ボタンを押します
3Welcome.png
エージェント作成画面になりますので
1. エージェント名に「CafeMaster」
2. 言語設定に「Japanese-ja」を選択
3. CREATEをクリック
4CreateAgent.png
しばらくするとインテント作成画面になりますが、ここではあらかじめ作成しておいたエージェントを読み込むために左上の三本線メニューを選択します
5CreateAgent2.png
左のメニューが開いたら、CafeMasterと書かれている右横の歯車マークをクリックしてエージェント設定画面に移ります
6ClickDialog.png
エージェント設定画面が開いたら、真ん中あたりの「Export and Import」タブを選択します
7MasterEntry.png
メニューから「RESTORE FROM ZIP」を選択します
8selectMenu.png
アップロードするファイルの選択画面になりますので、先ほどダウンロードしたCafeMaster.zipをドラッグ&ドロップしてください
9selectFile.png
ファイルが選択されると下記の画面になります。ここで①の部分に「RESTORE」と打ち込み、②のRESTOREボタンを押します
10RestoreFile.png
zipファイルが読み込まれ学習が実行されます。しばらくすると完了するので「DONE」を押します
11FinishRestore.png
これで、設定は完了です
12CreatedAgent.png

参考にさせていただいたサイト

Qiita:Dialogflow入門
Geekfeed:DialogFlowのFulfillmentを使ったチャットボット作成
Qiita:サルにもわかる Dialogflow FAQ
ゆたかみわーく:【Dialogflowの使い方】Fulfillmentを使ってみる(超基礎編)

キーファイルの作成

 このAgentをプログラムから利用するため GCPのサービス アカウントを作成し、秘密鍵ファイルをダウンロードします。
 公式の案内   https://cloud.google.com/dialogflow/docs/quick/setup

GCPからのログインして作成することもできますが、ここではDialogflowコンソールから作成してみたいと思います。

まずはエージェントの設定画面のところにあるGoogle Projectの欄を見てください。ここに自動発行されたProject IDとService Accountが表示されています。ここで、このService Accountをクリックします。

51.png
初回時は規約への同意画面を経てGCPのコンソール画面へと遷移します。
この画面から、先ほどの自動設定されたService Accountの右横にある操作欄の…ボタンから「編集」を選択します
52.png

サービスアカウントの設定画面になりますので「+キーを作成」を選択します
53.png
秘密鍵の作成ダイアログに代わりますので、作成タイプがJSONであることを確認して「作成」ボタンを押します
54.png
秘密鍵ファイルが作成されると「秘密鍵がパソコンに保存されました」メッセージが出て、cafemaster-xxxxxxxxx~.jsonがローカルのパソコンに自動でダウンロードされます。

このファイルを後ほどチャットサーバーにアップロードし、プログラムから参照させるため、ファイル名を dialogflow.jsonに変更してください。

ファイル名を変更したら保存ボタンを押してください。

55.png

次にこの画面からDialogflow APIの利用許可の設定を確認します。

GCPコンソールのメニューから 「APIとサービス」を選び「ライブラリ」を選択します
56.png
APIようこそ画面に遷移しますので、中央の検索ボックスに「dialogflow」と入力します。
57.png

検索の結果からDialogflow APIを選択します
58.png

下記の画面になっていればOKですのでチャットサーバーの構築に手順を移してください。

追加01.png


もしもAPIの有効化設定がされていない場合

プロジェクトを作成と書かれているドロップダウンリストから「CafeMaster」を選択します
59.png

「続行」をクリック
60.png

画面にAPIは有効になってます。と表示がでればOKです
61.png

そのまま×で閉じます

62.png

以上でDialogflowの秘密鍵の作成とAPIの有効化が完了です。

サーバー環境構築

前回に引き続きお手軽なクラウドサービスを使って環境構築を行います。
いつものようにPaizaを使います。

Paiza Cloud

paiza表紙.png
Paiza Cloudeにアクセスしてメールアドレスを登録すると、すぐに環境構築ができるようになります。

リンク先
https://paiza.cloud/ja/

サーバー作成

アカウントを作成したらサーバー作成ボタンを押しましょう
まだサーバーはありません.png
新規サーバー作成のポップアップで、Node.jsとMongoDBを選択してください。
サーバー設定.png
数秒間待っているとサーバー環境ができあがります。
ちなみに無料プランの場合は
* サーバーの最長利用時間は24時間
* サービスは外部へ公開されない
逆に言うと練習にはもってこいという事でしょうか。

アプリケーション構築

次に各種インストールを行い、アプリケーションの実行環境を構築します。
まずは画面からターミナルのアイコンをクリックしてください。

s_ターミナル.png

起動したターミナルに下記のコマンドを入れます
① git clone によるファイル展開

git clone https://github.com/nstshirotays/chatapp-shot3.git

② ディレクトリを移動し
③ npmによるパッケージのインストール
これにより実行に必要なモジュールなどがpackage.jsonに従って自動的にインストールされます。

cd chatapp-shot3
npm install

01.png

次に、先ほど作成したDialogflowに接続するための秘密鍵ファイルをアップロードします。
/home/ubuntuと書かれているところを右クリックするとメニューが表示されますので、そこから前の工程で作成したdialogflow.jsonファイルをアップロードします。

02.png

ファイル名は必ずdialogflow.jsonとして/home/ubuntu/に配置されていることを確認してください

03.png

以上で必要な準備が整いましたので、あとはnodejsを起動してアプリを立ち上げます。

npm start

エラーがでなければ、左側に緑色のブラウザアイコンが新しく点滅し始めます。
s_ブラウザ3000.png

このアイコンをクリックするとチャットアプリが起動します。

アプリ実行

ログイン画面

まずはログイン画面です。
login.png
初回は誰も登録されていないので、Create an Account を押してユーザー登録画面に移ります。

ユーザー登録画面

NickNameとPassCodeを入れてユーザーを登録しましょう。
NickNameは英文字で4から12文字。PassCodeは数字で6から12文字です。
regisit.png

お好みでFaceIconを変更(png 32kbまで)できます。

ユーザーを登録したら実際にログインしてみましょう。

友達選択画面

04.png

 前回はEchoさんだけでしたが、今回はCafeMasterが追加されています。
 このCafeMasterさんの実体がDialogflowとなっています。

DialogFlowによる会話生成

 それでは、実際にCafeMasterさんと会話してみましょう。

05.png

 最初の「いらっしゃいませ。コーヒーはいかがですか?」はチャットサイト側で出力しており、次の「アメリカンコーヒーを一杯お願いします」というTEXTからDialogflowにて処理をしています。
 Dialogflowでは渡されたTEXTから該当するインテントを割り出し処理をしていきます。ここではおそらくアメリカンコーヒーという単語に反応してorder.drinkインテントが発動されています。
 なので、店主の挨拶の後に「いつもの」などと入れるとorder.lastインテントが発動します。
 その他、Dialogflow側の処理については、DialogFlow Console 画面の右側にチャットボックスがあり自由にテストができますので、いろいろと試してみてください。

プログラム解説

 それでは早速Dialogflowとの連動についてプログラムを見てみましょう。

起動時の処理

 Google APIを利用するためには秘密鍵による認証が必要です。先に配置したdialogflow.jsonファイルがそれです。
 この秘密鍵ファイルは環境変数の設定により必要とされるプログラムからアクセスされます。
 このため起動する前に当該環境変数の設定をする必要があります。ここでは、npm startで最初に参照されている package.jsonで設定を行っています。

package.json
{"name":"chatapp2","version":"0.0.0","private":true,"scripts":{"start":"GOOGLE_APPLICATION_CREDENTIALS='/home/ubuntu/dialogflow.json' node ./bin/www"},"dependencies":{"@tensorflow-models/mobilenet":"^2.0.4","@tensorflow/tfjs":"^1.5.2","@tensorflow/tfjs-node":"^1.5.2","basic-auth-connect":"^1.0.0","bcryptjs":"^2.4.3","cookie-parser":"~1.4.4","debug":"~2.6.9","dialogflow":"^1.2.0","ejs":"~2.6.1","express":"~4.16.1","express-validator":"^6.2.0","http-errors":"~1.6.3","jsonwebtoken":"^8.5.1","mongo-sanitize":"^1.0.1","mongoose":"^5.7.6","mongoose-sequence":"^5.2.2","morgan":"~1.9.1","pb-util":"^0.1.2","randomstring":"^1.1.5","redis":"^2.8.0","save":"^2.4.0","uuid":"^3.4.0"}}

Startの値に環境変数の設定を追加しています。

GOOGLE_APPLICATION_CREDENTIALS='/home/ubuntu/dialogflow.json'

会話の送信と受信

チャットサーバーとDialogFlowとのやりとりは helper/bot.cafe.jsにて実装してあります。

helper/bot.cafe.js
// CafeMasterの処理vardb=require('../helper/db');varChat=db.Chat;constchatsv=require('../models/chat.service');//DialogFlowの設定constdialogflow=require('dialogflow');constuuid=require('uuid');varsessionId="";//console.log(process.env.GOOGLE_APPLICATION_CREDENTIALS);varfs=require("fs");varuse_readFile_json=JSON.parse(fs.readFileSync(process.env.GOOGLE_APPLICATION_CREDENTIALS,'utf8'));constproject_id=use_readFile_json.project_id;//console.log(project_id);exports.start=function(){//  最初の挨拶を登録if(FrName=='CafeMaster'){// CafeMastersessionId=uuid.v4();console.log('Session ID = '+sessionId);chatsv.create({fromAddress:FrID,toAddress:MyID,message:'いらっしゃいませ。コーヒーはいかがですか?'});// 2秒ごとに新しいメッセージを検索するbotTimer=setInterval(function(){serachNewMessages();},2000);}};varresentMsg="";asyncfunctionserachNewMessages(){if(FrName=='CafeMaster'){// CafeMasterの最後の発言を取得するvarquery={"fromAddress":FrID,"toAddress":MyID};Chat.find(query,{},{sort:{timeStamp:-1},limit:1},function(err,data){if(err){console.log("serachNewMessages err",err);}if(data.length>0){// CafeMasterの最後の発言以降の自分の発言を取得する.varlastMessageTime=data[0].timeStamp;query={"fromAddress":MyID,"toAddress":FrID,"timeStamp":{$gt:lastMessageTime}};Chat.find(query,{},{sort:{timeStamp:-1},limit:1},asyncfunction(err,data){if(err){console.log("serachNewMessages err",err);}if(data.length>0){// 応答メッセージの設定varresMessage="";const{struct}=require('pb-util');// Create a new sessionconstsessionClient=newdialogflow.SessionsClient();constsessionPath=sessionClient.sessionPath(project_id,sessionId);// The text query request.constrequest={session:sessionPath,queryInput:{text:{// The query to send to the dialogflow agenttext:data[0].message,// The language used by the client (en-US)languageCode:'ja-JP',//'ja'},},};//DialogFlowへ会話を送って返事を待つconstresponses=awaitsessionClient.detectIntent(request);constresult=responses[0].queryResult;constparameters=JSON.stringify(struct.decode(result.parameters));varobj=JSON.parse(parameters);console.log(`  Query: ${result.queryText}`);console.log(`  Response: ${result.fulfillmentText}`);resMessage=result.fulfillmentText;// 退避メッセージと異なってれば発話するif(resMessage!==""&&resentMsg!=data[0].timeStamp+data[0].message){resentMsg=data[0].timeStamp+data[0].message;chatsv.create({fromAddress:FrID,toAddress:MyID,message:resMessage});}}});}});}}

 この処理は友達選択でCafeMasterが呼ばれると起動されます。
 初期処理にて、先ほどの環境変数で示された鍵ファイルからproject_idを取得し、ランダムに得られた番号をセッション番号としてDialogflowに接続します。
 最初の挨拶として「いらっしゃいませ。コーヒーはいかがですか?」を発話したのち、serachNewMessage関数を2秒ごとに実行しユーザーの会話を待ちます。

 新しい会話を検知すると、その内容をDialogFlowに渡して返事を待ちます。そして返事がきたらそのTEXTをmongoDBに書き込みます。

 以上によりチャットサイトとDialogFlowが連動して会話処理を行うことができます。

Fulfillmentの設定

 さて、このdorder.drinkインテントですが、ここでは「飲み物」「サイズ」「デリバリー」の3要素を必須入力としています。この3要素が揃うと次のorder.drink-yesというインテントが発動し、いつもの支払い方法でよいか聞かれます。ここで「はい」と答えるとorder.drink.same_cardに移動し最終確認が行われます。
 初期段階ではFulfillmentが設定されていませんので、単純にこのインテントに設定されたResponse「この度はご注文ありがとうございました。またのご来店をお待ちしております。」というメッセージのみが戻されます。

08.png

 本来であれば実際の注文処理が動くと思いますので、ここでFulfillmentによる外部サーバーの呼び出しを行ってみます。
 ここでは注文処理用の外部サーバー処理を先ほどのチャットサーバー上に同居させています。このためまずは呼び出すサーバーアドレスを取得します。
 なお、この設定を行うためには本チャットサーバーがインターネットに公開されていることが前提となります(Paiza.cloudの無料枠では利用できません)。
 まず公開されているアドレスを控えておきます。

09.png

次にDialogFlow Consoleの左側のメニューからFulfillmentを選択します

webhookスイッチをオンにして、呼び出すサーバーの設定を行います。
① 先ほど控えたURLの末尾に cafeを追加して設定
② IDに「userid」英字6文字を設定
③ PWDに「passcode」英字8文字を設定
④ 最後に「SAVE」ボタンをクリック

01.png

 そして、設定されたWebhookを呼び出すタイミングをインテントに指定します。

 Intentsを選択し、 order.drink -> order.drink - yes を展開し order.drink.samecardインテントを選択します。

02.png

 そして、①画面の下方にあるfuifillmentのタブを開いて②、「Enable webhook call for this intent」のスイッチをオン③にします。
 それから最後に④のSAVEを押して内容を反映させます。

03.png

 これでFulfillmentによるwebhookが設定できましたので、先ほどのCafeMasterさんを再度よびだしてみると、最終の挙動が変わっているのがわかると思います。

10.png

webhookプログラムの解説

 それでは、受け取り側のwebhookプログラムについて解説します。実装はroutes/cafe.jsです。

routes/cafe.js 
varexpress=require('express');varrouter=express.Router();vardb=require('../helper/db');varUser=db.User;varChat=db.Chat;constverifyToken=require('../helper/VerifyToken');var{check,validationResult}=require('express-validator');varfs=require('fs');constchatsv=require('../models/chat.service');varbasicAuth=require('basic-auth-connect');//DialogFlowからのコールバックを処理する//ベーシック認証router.post('/',basicAuth('userid','passcode'),function(req,res,next){//console.log("req.body.session "+ req.body.session);   //console.log(req.body.queryResult.outputContexts[1].parameters.drink);//console.log(req.body.queryResult.outputContexts[1].parameters.size);//console.log(req.body.queryResult.outputContexts[1].parameters.delivery);varcafeImageFile=req.body.queryResult.outputContexts[1].parameters.drink+".png";varbaseStr="data:image/png;base64,";varcafeImage;fs.readFile('./public/files/'+cafeImageFile,function(err,data){if(err)throwerr;cafeImage=baseStr.concat(newBuffer(data).toString('base64'));chatsv.create({fromAddress:FrID,toAddress:MyID,image:cafeImage,})});letresponseObj={"fulfillmentText":'おまたせいたしました。'}returnres.json(responseObj);});module.exports=router;

 ここでDialogFlowからのpost要求を受け付けます。ここではbasic認証を入れてあります。本来であればmTLSによるサーバーの相互認証(これにより接続してきたDialogflow側の証明書を認証することができる)をすべきなのですが、paiza.cloudが仮想環境による実装のため、サーバー証明書を入れることができませんので実装は試せませんでした。
 処理的にはシンプルでpostされたデータにより画像ファイルを選択してmongoDBに格納しています。そしてDialogFlowへの返却値として「おまたせしました」というTEXTを送り返しています。

 以上がDialogFlowとの連動プログラムです。プログラム自体は単純なのですが、Dialogflow側の設定は慣れないと自分が何をしているかわからなくなります。このあたりはDialogflowのサンプルエージェントを眺めてみるるとその実装形態の思想が見えてくると思います。

MobileNetによる画像識別

 2017年に誕生した畳み込み画像認識のmobileNetは、小型軽量でモバイル等での利用も盛んです。今回はこれをnode.jsに組み込んでいます。
 友達選択からEchoさんを選択し、画像を送ると1000個の分類から識別してくれます。

11.png

 このように識別してくれていますが、分類名称が英語ですので、これを日本語ファイルで置き換えます。日本語ファイルはいろいろなサイトを参考にして作成しました。

06.png

 chatapp-shot3/imagenet_classes.jsが和訳した内容ですので、node_module/@tensorflow-modules/mobilenet/distにある英文内容と差し替えます。
 差し替えたのちに npm startで再度起動すると和訳文が読み込まれます。

12.png

 余談ですが、この1000分類を見ていただくとわかりますが、昆虫と魚と犬が多いなと思いました。
犬種に関してはかなりマニアックな感じがしました。

07.png

プログラム解説

 実装は helper/bot.echo.jsにあります。

helper/bot.echo.js
// Echoさんの処理vardb=require('../helper/db');varChat=db.Chat;constchatsv=require('../models/chat.service');//tensorflow.jsの設定varrandomstring=require("randomstring");consttf=require('@tensorflow/tfjs');constmobilenet=require('@tensorflow-models/mobilenet');consttfnode=require('@tensorflow/tfjs-node');varfs=require('fs');exports.start=function(){//  最初の挨拶を登録if(FrName=='Echo'){// echochatsv.create({fromAddress:FrID,toAddress:MyID,message:'こんにちは'});// 1秒ごとに新しいメッセージを検索するbotTimer=setInterval(function(){serachNewMessages();},1000);}};varresentMsg="";functionserachNewMessages(){if(FrName=='Echo'){// Echo さんの最後の発言を取得するvarquery={"fromAddress":FrID,"toAddress":MyID};Chat.find(query,{},{sort:{timeStamp:-1},limit:1},function(err,data){if(err){console.log("serachNewMessages err",err);}if(data.length>0){// Echoさん最後の発言以降の自分の発言を取得する.varlastMessageTime=data[0].timeStamp;query={"fromAddress":MyID,"toAddress":FrID,"timeStamp":{$gt:lastMessageTime}};Chat.find(query,{},{sort:{timeStamp:-1},limit:1},asyncfunction(err,data){if(err){console.log("serachNewMessages err",err);}if(data.length>0){varresMessage="";// 応答メッセージの設定if(data[0].image.length>0){// MobileNetの呼び出しvarmatches=data[0].image.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/),imageBuffer={};imageBuffer.type=matches[1];imageBuffer.data=newBuffer(matches[2],'base64');constmobilenetModel=awaitmobilenet.load({version:2,alpha:1.0});constpredictions=awaitmobilenetModel.classify(tfnode.node.decodeImage(imageBuffer.data));resMessage=predictions[0].className+"ですね";}elseif(data[0].stampTitle!==undefined){resMessage=data[0].stampTitle+"ですね";}elseif(data[0].message.length>0){resMessage=data[0].message+"ですね";}else{resMessage="無言ですね";}// 退避メッセージと異なってれば発話するif(resMessage!==""&&resentMsg!=data[0].timeStamp+data[0].message){resentMsg=data[0].timeStamp+data[0].message;chatsv.create({fromAddress:FrID,toAddress:MyID,message:resMessage});}}});}});}}

 このボットが1秒ごとにメッセージを検索し、TEXTであれば末尾に「ですね」を付け加え、スタンプであればそのファイル名を返却し、画像であればmobilenetで画像識別を行ってその結果を書き込んでいます。
コードにすればほんの数行で画像認識を行うことができます(モデルの読み込みはここでなくてもよいかもしれません)。

 その他にもいろいろと呼び出せるライブラリがあります。

  TensorFlow.js

最後に

 いかがだったでしょうか。相変わらず前回から間が空いてしまいましたが、Echoさんが画像の識別をしてくれるようになり、ボットサイトを使った会話もできるようになりました。
 今回はGoogleのDialogflowを利用する関係で手順がとても多くなってしまいました。Step by Stepで記載しましたが、半年後には画面も変わったりするので何時ごろまで有効かはわかりませんが、ひとまずはこの手順で動くと思います。
 いずれにせよ、外部のサイトと連携したり、開発されたライブラリを活用することで、簡単に機能追加ができるのが実感できました。

次回は...

 次回はこの画像識別をさらにパワーアップさせて、画像のキャプションを自動生成するロジックを追加したいと思っています。

記事一覧

WEBでLINE風のチャットサイトを作る-その1
WEBでLINE風のチャットサイトを作る-その2

javascript(node.js)のクエリーストリング(qs)を解説

$
0
0

javascript(node.js)では、受け取ったデータは実はそのままでは使えません。クライアントからは、クエリーテキストと呼ばれる形式で送られてくるので、それをエンコードしておかないといけないのです。

それを行っているのが、qsオブジェクトの「parse」です。qs.parse(string)により、受け取ったデータ(string)をエンコードし、それぞれのパラメーターの値を整理したオブジェクトに変換してくれます。後は、このオブジェクトから必須な値を取り出して利用するだけです。

node + javascript で今日の日付を文字列に変換する、YMD形式で出力する例 #javascript #node

$
0
0

なにやら strftime 的な YMD 形式でうまく区切るメソッドはなさそう?

node


> new Date().toISOString().split('T')[0]
'2020-02-22'

> new Date().toDateString()
'Sat Feb 22 2020'

> new Date().toDateString()
'Sat Feb 22 2020'

> new Date().toGMTString()
'Sat, 22 Feb 2020 08:43:15 GMT'

> new Date().toISOString()
'2020-02-22T08:43:22.536Z'

> new Date().toJSON()
'2020-02-22T08:43:31.867Z'

> new Date().toLocaleDateString()
'2/22/2020'

> new Date().toLocaleTimeString()
'5:43:53 PM'

> new Date().toLocaleString()
'2/22/2020, 5:44:00 PM'

> new Date().toString()
'Sat Feb 22 2020 17:44:09 GMT+0900 (Japan Standard Time)'

> new Date().toTimeString()
'17:44:15 GMT+0900 (Japan Standard Time)'

> new Date().toUTCString()
'Sat, 22 Feb 2020 08:44:22 GMT'

こんな prototype の関数が見つかるが、採用されなかったのだろうか

Date.prototype.toLocaleFormat() - JavaScript | MDN

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3001

#node js – javascript で 日付を1ヶ月ずつ引き算する

$
0
0

2020-01-22 -> 2019-12-22 -> 2019-11-22

varx=newDate();date.setMonth(x.getMonth()-1)date// 2020-01-22T08:55:29.971Zdate.setMonth(x.getMonth()-1)date// 2019-12-22T08:55:29.971Zdate.setMonth(x.getMonth()-1)date// 2019-11-22T08:55:29.971Z

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3002

#node で version4 の uuid を生成する

$
0
0
npm install -g uuid
$ node
Welcome to Node.js v13.8.0.
Type ".help" for more information.

> const uuidv4 = require('uuid/v4');
undefined

> uuidv4();
'83f370cd-188d-4bc1-b07c-4be39ef16f50'

> uuidv4();
'81084168-d009-4e31-ba59-a52785369afc'

> uuidv4();
'9be2153b-4308-4813-a7ae-705edf97abce'

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3003

Viewing all 8833 articles
Browse latest View live