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

Scratch Offline Editor for Linuxを作る

$
0
0

ScratchはOffline Editorを提供しているため,オンラインでなくても使うことができます.
Scratch - Scratch Offline Editor
image.png

しかし,上記リンクを見れば分かるとおり,Linux向けがありません.残念...

ところが,ScratchはEditorのGUIをオープンソースで公開しています.
LLK/scratch-gui

これを自分でビルドしてローカルで実行すれば,オフライン版Scratchとして使えそうです.

準備

以下を揃えてください.

  • Node.js
  • npm
  • git
  • 適当なブラウザ(ChromeとかChromiumとかFirefoxとか)

以下はUbuntuでの例です.

Ubuntuでの例

パッケージをインストールします.

Gitをインストールします.

sudo apt install git

次に,以下の記事を参考にしてNode.jsの環境を入れてください(引用させていただきます.ありがとうございます).
Ubuntuに最新のNode.jsを難なくインストールする

いざ

まず,Gitからリポジトリをクローン,依存関係のインストールを行います.結構時間かかるかも.

git clone --depth=1 https://github.com/LLK/scratch-gui.git
cd scratch-gui
npm install

ScratchはReactで作られているので,これでnpm startとすれば起動できますが,起動するたびにコンパイルされるのは少し時間がかかってしまいます.ということで,先にコンパイルしておきます.
結構時間かかります.

npm run-script build

終わると,builds/内にindex.htmlが生成されていると思います.これをChrome等で開き,正常に動作すればオフライン版完成.
image.png


Watson MLのWebサービスをNode-REDから呼び出す(v2対応済み)

$
0
0

はじめに

Node-REDから、Watson ML上にdeployしたWebサービスを呼び出す方法としては

https://flows.nodered.org/node/node-red-contrib-watson-machine-learning

に部品があるのですが、コードを調べてみたところ古いインターフェースのままで、バージョンアップも止まっていました。
(というか、API仕様が根本的に変わったので 、ML v2対応しようとすると全部作り直しになりそう)

ということで、昨年9月にリリースした新しいWatson ML API仕様v2に対応するにはどうしたらいいか、考えてみました。
頑張って全部JavaScriptで実装して、新しいNode-REDの部品も作れなくないはずですが、しばらくJavaScriptから離れているため、コールバック先の関数から値を戻すのをどうしたらいいか思い出せず。。。
あまりきれいでないのですが、ひな形のフローファイルを作ってお茶を濁すことにしました。

完成イメージ

下の図が、完成イメージです。

screen-01.png

呼び出し先のAPIは、別記事Watson Studioでscikit-learn機械学習モデルをWebサービス化するで作ったものです。
(Iris datasetを学習データにした分類モデル)
こちらに関しては、Webサービスのデプロイまで済んでいることが前提になりますので、その点はご注意下さい。

フローファイルは、以下のGithubサイトにアップしてあります。

https://github.com/makaishi2/sample-data/blob/master/node-red/wml-flows-share.json

このフローをNode-REDに取り込んで、2箇所ノードの設定(apikeyとscroring_url)を変更すると、Watson MLのAPIを呼び出せるようになります。

サンプルの動かし方

設定値の取得

API_KEYの値
1.3 API Keyの取得を参照して下さい。

scoring_urlの値
6.3 URL取得を参照して下さい。Node-REDから呼び出す場合は、バージョンIDも付加したscoring_url2の方を使う必要があるので、その点を注意して下さい。

Node-RED上の設定

取り込んだフローの中で修正が必要なのは
① 「token取得準備」
② 「API呼び出し」
の二つのノードです。

screen-02.png

「token取得準備」ノードの設定

下記が、該当functionノードのコードです。

// header設定msg.headers={};msg.headers['Accept']="application/json";msg.headers['Content-Type']="application/x-www-form-urlencoded";// apikey設定varapikey="xxxx";msg.payload="apikey="+apikey+"&grant_type=urn:ibm:params:oauth:grant-type:apikey";returnmsg;

この中で、

varapikey="xxxx";

の行に調べたAPI_KEYの値を設定します。

「API呼び出し」ノードの設定

下記が「API呼び出し」の設定ノードです。

screen-03.png

この中でURLの欄に、事前に調べたscring_urlの値を貼り付けます。

テスト

これで準備はすべて整いました。正しく設定ができていれば、一番左のinjectノードのクリックで、Watson MLのAPIが呼び出せるはずです。

フロー解説

最後に、フローの各ノードで何をやっているか解説します。

screen-02.png

基本的には6. 予測(Watsonライブラリを使わない方法とまったく同じことを、Node-RED上でやっているだけです。
元記事も合わせて読んでいただけると、より理解が深まると思います。

Token取得準備

コードは1章で一度載せていますが、再掲します。

// header設定msg.headers={};msg.headers['Accept']="application/json";msg.headers['Content-Type']="application/x-www-form-urlencoded";// apikey設定varapikey="xxxx";msg.payload="apikey="+apikey+"&grant_type=urn:ibm:params:oauth:grant-type:apikey";returnmsg;

この後のHTTPリクエストノードでの準備をしています。
リクエストヘッダはmsg.headersに、データはmsg.payloadにそれぞれ設定しています。

Token取得

http requestノードで、メソッドをPOSTに、URLをhttps://iam.cloud.ibm.com/identity/tokenにしています。

screen-04.png

それ以外のパラメータはその前のノードで設定済みなので、これだけでTokenが取得できます。

API呼び出し準備

下記が、API呼び出し準備用のfunctionノードの内容です。

// token取得value=JSON.parse(msg.payload);console.log(value);console.log(value["access_token"]);vartoken=value.access_token;// header設定msg.headers={};msg.headers['Content-Type']="application/json";msg.headers['Authorization']='Bearer '+token;// data設定msg.payload='{"input_data": [{"values": [[6.9, 3.1, 5.4, 2.1], [4.4, 2.9, 1.4, 0.2]]}]}'returnmsg;

まず、token取得APIの戻り値をパースした後で、キー値access_tokenに対応するvalueを取得します。
この値が、次のAPI呼び出しで必要なtokenになります。

header設定で、Bearerの後に、今取得したtokenを追加し、これをAuthorizationの値にします。

最後のmsg.payloadは、APIの引数になる入力データ部分です。
今は、iris datasetの入力値2行分をベタうちにしてしまっていますが、実際のアプリでは、他からのデータを使ってこの部分を組み立てることになります。

戻り値の読み取り方

最後にWatson MLの戻り値の解説をします。
リクエストの戻りをパースすると、次のような結果になります。

screen-05.png

戻り値は、赤枠で示した予測値(prediction)と青枠で示した確率値(probability)に分けられます。
このモデルの目的は、アヤメの花の種類(0か1か2)を区別することなのでが、モデルの予測結果としての種類がpredictionの値です。
もう一つのprobabilityとは、それぞれの判断結果に対する確信度だと思って下さい。
今回の2件の予測結果に関して言うと、「1件目の予測結果が2である」ことは100%自信があるが、「2件目の予測結果が0である」ことに対しては、確信度が99%で、多少自信がないということになります。

node.jsのモジュールをHTMLで読み込む

$
0
0

背景

HTMLファイルから3rd Partyのnode.jsのモジュールを利用しようとして、「require is not defined」というエラーにぶち当たった。

そこで、node.jsのモジュールをライブラリとしてHTMLに読み込む方法があるかを調べてみたところ、browserifyというものがあるのを知ったので、試してみた。

ちなみに、HTML用のjsファイルが用意されているモジュールもあるっぽいので、その場合は、それをそのまま読み込めばよいと思う。

「require is not defined」エラーについて

例えば、下記のようなhtmlとjsファイルを作ったとき。

test.html
<!DOCTYPE html><html><head><metacharset="UTF-8"/><title>Test</title></head><body><p>Test</p><div><textareaid="token"rows="8"cols="120">{hoge: 'hogehoge'}</textarea><textareaid="signed"rows="12"cols="120"></textarea></div><buttonid="sign">Sign</button></body><script src="test.js"></script></html>
test.js
// 注意:このコードは動作しません。varjwt=require('jsonwebtoken');document.getElementById("sign").onclick=()=>{constjwt=document.getElementById("token").value;constsignedJwt=sign(jwt);document.getElementById("signed").innerText=signedJwt;};functionsign(payload){returnjwt.sign(payload,'secret');}

ブラウザで開くと、見た目はこんな感じ。

image.png

「Sign」ボタンを押すと、上のテキストエリアにあるJSON形式のテキストに署名をして、下のテキストエリアに表示しようとしたコード。

実際には、このコードは動かなくて、以下のようなエラーになる。

image.png

まぁ、当たり前なのかもしれないけど、ただのhtmlなので、require('jsonwebtoken')はエラーになる。

ちなみに、jsonwebtokenはJWTとかに署名できるライブラリで、nodejsであれば「npm install jsonwebtoken」とかでインストール後、上記のような呼び出しで利用できるようになる。

browserifyの利用

ということで、これを動くようにするために、browserifyを使って頑張ってみる。

browserifyは、node.jsのコードをブラウザで動作するようにしてくれるらしい。

最終的にはhtmlとjsだけで動くようにする予定だが、とりあえず、開発にはnode.jsの環境が必要なので環境を作る。

> mkdir browserify
> cd browserify
> yarn init

適当に作ったnode環境のプロジェクト直下に、さきほどのtest.htmlとtest.jsを置く。

続いて、jsonwebtokenをインストール。

> yarn add jsonwebtoken

もちろん、これだけでは状況は変わらない。

ということで、browserifyをインストール。

> yarn global add browserify

browserifyを使って実行してみる。

> browserify test.js -o test2.js

生成された「test2.js」は下記のような感じ。

test2.js
(function(){functionr(e,n,t){functiono(i,f){if(!n[i]){if(!e[i]){varc="function"==typeofrequire&&require;......(約3万行)document.getElementById("sign").onclick=()=>{constjwt=document.getElementById("token").innerText;constsignedJwt=sign(jwt);document.getElementById("signed").innerText=signedJwt;};functionsign(payload){returnjwt.sign(payload,'secret');}},{"jsonwebtoken":208}]},{},[232]);

3万行ぐらいのファイルが生成された。

依存のファイルがすべて一つにまとめられた模様。

test.htmlを以下のように編集して、動作確認。

test.html
<!DOCTYPE html><html>
  ...

  <script src="test2.js"></script></html>

再度、test.htmlをブラウザで開いてみる。

image.png

「Sign」ボタンを押すと、署名されたっぽい文字列が表示された。

とりあえず、それっぽいものが表示されたけど、何か変な気もする。

このままだと、デバッグしようにも、test.jsを書き換えるたびに、browserifyでファイル生成が必要となる。

やはり「jsonwebtoken」部分だけをライブラリとしてhtmlに読み込んで、test.jsから利用するようにしたい。

node.jsのモジュールをライブラリとしてHTMLで読み込む

ということで、「jsonwebtoken」をライブラリとしてHTMLで読み込める形に出来ないか試してみた。

色々試行錯誤してみたところ、下記のようなjsファイル(jsonwebtoken-wrapper.js)を用意し、browserifyをかけることで、それっぽく動作した。

jsonwebtoken-wrapper.js
(function(factory){typeofdefine==='function'&&define.amd?define(factory):factory();}((function(){'use strict';// 以降に、元のライブラリのI/Fをそのままそのまま呼び出すだけのfuntionを追加varjwt=require('jsonwebtoken');functionsign(payload,secretOrPrivateKey,options,callback){returnjwt.sign(payload,secretOrPrivateKey,options,callback);} functionverify(jwtString,secretOrPublicKey,options,callback){returnjwt.verify(jwtString,secretOrPublicKey,options,callback);}functiondecode(jjwt,options){returnjwt.decode(jwt,options);}// 以降で外から呼び出せるように定義する。if(window){if(typeofwindow.define=="function"&&window.define.amd){window.define("jwt_sign",function(){returnsign;});window.define("jwt_verify",function(){returnverify;});window.define("jwt_decode",function(){returndecode;});}else{window.jwt_sign=sign;window.jwt_verify=verify;window.jwt_decode=decode;}}})));

browserifyを使って、htmlから読み込み可能にしておく。

> browserify jsonwebtoken-wrapper.js -o jsonwebtoken.js

jsonwebtoken.jsというファイルが生成される。

test.htmlは、下記のように修正。

test.html
<!DOCTYPE html><html>
  ...
 <body><script src="./jsonwebtoken.js"></script>
    ... 
  </body><script src="test.js"></script></html>

ライブラリとして、生成したファイルを読み込むようにした。

また、さきほど、test2.jsに変更した部分をtest.jsに戻しておく。

test.jsは以下のように書き換え。

test.js
// var jwt = require('jsonwebtoken'); この行を削除...functionsign(payload){returnjwt_sign(payload,'secret');// jwt.sign(..)をjwt_sign(..)に変更}

ブラウザにtest.htmlを表示して確認。

image.png

とりあえず正しく動作している模様。

ちなみに、「test.html」、「test.js」、「jsonwebtoken.js」の3つだけを適当なフォルダにおいて実行しても、同じように動作した(nodeから切り離された環境でも動作した)。

ターミナルから爆速で回線速度を計測する

$
0
0

はじめに

インターネットの回線の速度を計測する際に便利な Fast.comという Netflix が展開するサービスがあります。

回線速度を計測したいときにブラウザを使って測定サイトにアクセスしなくても、ターミナルから Fast.com を経由して爆速でインターネット回線速度を図る方法が便利だったためまとめてみました。

公式コマンドラインツール「fast-cli」をインストール

node.js (バージョン 8 以降)がインストールされている場合、npm や Yarn などのパッケージマネージャーから簡単に導入できます。以下のコマンドをターミナルにて実行しグローバルにインストールすることで使えるようになります。

npm でインストール

npm install--global fast-cli

Yarn でインストール

yarn global add fast-cli

実行してみる

ダウンロード速度のみの計測

fast

上のコマンドで回線のダウンロードの速度計測が始まります。--upload , -uオプションを付けないとダウンロードのみの計測となります。

ダウンロードとアップロード速度を計測

fast --upload

ダウンロード速度とアップロード速度を計測します。

ダウンロード速度を計測して一行表示

fast --single-line

一行でシンプルにダウンロード速度を計測します。

fast-cli のヘルプを表示

fast --help

使用できるコマンドと使用例を表示します。

おわりに

ターミナルから簡単に回線の速度を図れるので便利です。公式のコマンドラインツールということもあり、計測も安定していて精度も良さげです。

是非活用してみてください。

以上、ここまで読んでいただきありがとうございました。

参考

sindresorhus / fast-cli
回線速度のテストができる「fast」コマンドが便利だった
windows10 fast.com のコマンドラインツール「fast-cli」を使って回線速度を計測する

Node.jsでhelloworld

$
0
0

ゴール

Node.jsを使用して、コンソールにhelloworldを表示させる

前提

Visual Studio Code(以下 VSCode)使用
DockerでNode.jsのコンテナを使用

Node.jsとは

  • 本来クライアント側の言語であるjavascriptを、サーバー側でも使えるようにした仕組み(※1)
  • webサイトやwebアプリ、スマートフォンサイト、ゲームなど様々なものをつくることができる =>paypalやuverなどもNode.jsでつくられているそう!
  • 大量のデータ処理が得意で、処理スピードも早いのが特徴

※1 progate「Node.jsとは」より引用 https://prog-8.com/nodejs/study/1/1#/2
参考:https://udemy.benesse.co.jp/development/system/node-js.html

DockerにNode.jsをダウンロードする

VScodeの拡張機能のDockerをあらかじめインストールしておく。(※2)

docker-compose.ymlファイルの作成

Nodejs-sample-app(任意のフォルダ名でよい)フォルダを作成。
その下にdocker-compose.ymlファイルを作成。
こちらはdockerにコンテナをダウンロードする時のお決まりのファイルとなるので、ファイル名は固定。ymlファイルは、YAMLという書き方に沿って記載されたテキストファイルで、データの受け渡しなどに使用される。
今回は以下のように記載した。

docker-compose.yml
version:"3"services:node:image:node:14.15volumes:-.:/projecttty:trueworking_dir:/projectcommand:bash

それぞれの項目の意味

※2 詳しくはこちらのページがおすすめです
@Teach「VSCodeでDocker入門」 https://qiita.com/Teach/items/ca09b8882f519dca600c

docekerにイメージをダウンロード

VSCodeのターミナル機能を使い、Nodejs-sample-appフォルダの中に入り、以下のコマンドを打つと、docerにNode.jsがダウンロードされる。

docker-compose up -d

イメージ
スクリーンショット_2021-01-16_10_53_46.jpg

dockerを開いて、このような表示になった成功です!
 スクリーンショット_2021-01-16_10_54_20.jpg

コンテナの中に入るためにはAttach shellを選択します(写真参照)
スクリーンショット_2021-01-16_12_33_56.jpg

ローカル内のターミナルとコンテナ内のターミナルはこちらで切り替えができます
スクリーンショット_2021-01-16_12_38_45.jpg

app.jsファイルの作成

docker-compose.ymlと同階層にapp.jsファイルを作成。
今回は以下のように記載

app.js
console.log("Hello nodejs");

コンテナ内のターミナルで
$ node app.js
とコマンドを打ち、以下のように表示されたら、Node.jsの環境構築は完了です!

スクリーンショット_2021-01-16_12_47_28.jpg

Node.js でためしたこと

Zoom Web SDK を試す

$
0
0

この記事は、Zoom Web SDK を試してみた手順のメモです。

●Web - Client SDKs - Zoom Software Development Kit (Zoom SDK) - Zoom Developer - Technical Documentation and Reference
 https://marketplace.zoom.us/docs/sdk/native-sdks/web

Zoom Web SDK の概要

公式ページの記載を引用して見てみます。

The Web SDK enables the development of video applications powered by Zoom’s core framework inside an HTML5 web client through a highly optimized WebAssembly module.
As an extension of the Zoom browser client, this SDK is intended for implementations where the end user has a low-bandwidth environment, is behind a network firewall, or has restrictions on their machine which would prevent them from installing the Zoom Desktop or Mobile Clients.

引用元: 公式ドキュメントより

説明はいろいろと書いていますが、この SDK を使うと、ブラウザ上で Zoomクライアントを立ち上げることができるもののようです。

Zoom Web SDK をとりあえず試す

公式ドキュメントの「Quick install」を見ると、以下手順でサクッと試せるようです。

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

まずは、任意のディレクトリでパッケージのインストールを行います。
この手順を試す時、最初は Node.js のバージョンを 12 にしていて途中の手順でエラーが出ました。その後、バージョンを 14 にして試してエラーが出なくなったので、補足しておきます。

npm i @zoomus/websdk

2つのキーの取得について

公式ドキュメントによると Web SDK を使う際に API Key と Secret を取得する必要があるようです。

事前準備についての記述.png

Zoom のApp Marketplaceにアクセスしてログインをし、画面右上の「Develop」のメニューから「Build App」を選択します。

Zoomマーケットプレイス.png

その後の手順については以下の記事などを参照して、JWT の「API Key」と「API Secret」の 2つを取得してください。以下は、Zoom API についての記事ですが、取得するキーは今回の Zoom SDK を用いる場合と同じでした。

●Zoom APIをPostmanで試す | ヤマムギ
 https://www.yamamanx.com/zoom-api-postman/

サンプルアプリのソースの取得

そして、公式ドキュメントの Sample Appsの部分に書かれた以下の手順を進めていきます。

サンプルアプリ.png

まずは最初の2つのコマンドを実行して、ソースを取得し、取得したソースのサブフォルダへと移動します。

git clone https://github.com/zoom/sample-app-web.git --branch master --depth 1
cd sample-app-web/Local

サンプルアプリのソースの書きかえ(キーの設定)

ここで、取得したソースの中の一部を書きかえます。具体的には、先ほど準備した 2つのキーを設定します。

取得したソースの「Local」フォルダ内のさらに下に「js」フォルダがあり、その中に「index.js」というファイルがあります。ソースの取得元の情報でいうと、以下の URL の「13行目」と「19行目」です。

●sample-app-web/index.js at master · zoom/sample-app-web
 https://github.com/zoom/sample-app-web/blob/master/Local/js/index.js

具体的には以下の部分で、const API_KEY = "YOUR_API_KEY";の部分と const API_SECRET = "YOUR_API_SECRET";の部分の式の右側に、先ほど取得しておいたキーを設定します。

キーの設定.png

それが完了したら、以下のコマンドを順次実行していきます。

npm install
npm run start

ブラウザでミーティング設定画面を開く

上記のコマンドを入力すると、以下のような文字などが出力されます。

コマンドの実行結果.png

ローカルでポート番号「9999」でアクセスできる状態になったようです。
「0.0.0.0」や「localhost」にポート番号 9999 を指定して、ブラウザでアクセスしてみてください。

そうすると、以下の画面が表示されるので「Meeting Number」と「Meeting Password」のそれぞれに、参加したい Zoomミーティングの ID とパスワードを入力してください。
また、言語設定が「English」となっている部分は「Japanese 日本語」という選択肢があるので、プルダウンから選択して変更します。

Zoom_WebSDKの表示.png

その後、画面のメニュー右端にある「Join」ボタンを押しましょう。
そうすると、以下のように自分が用意したブラウザ上のアプリから Zoom ミーティングに参加できます。

この時、「Meeting Number」の数字の中に半角スペースが入るとうまくいかないようなので、ご注意ください。うまく接続できたら、別タブが開き、以下のように Zoomミーティングへ参加した際の画面が表示されます。

ブラウザでZoom参加の画面へ.png

Zoomミーティングに入った後は、ブラウザ上の UI で画面共有の操作なども行うことができます。

おわりに

今回の内容は単にブラウザ上でZoomミーティング へ参加しただけで、これだけだと、Zoom 標準のブラウザからのミーティング参加機能と同じですが、ソースコードに手を入れれば拡張とかもできるはずです。

例えば、ドキュメントの以下の内容などを見て、ソースコードに手を入れたりもしてみようと思います。

●Manage Video
 https://marketplace.zoom.us/docs/sdk/custom/web/essential/video
●Session Chat
 https://marketplace.zoom.us/docs/sdk/custom/web/essential/chat

next.js heroku デプロイ Error: Couldn't find that app.

$
0
0

next.jsをherokuにデプロイする日本語記事が見当たらなかったため投稿しました。

環境:
node.js
react
next.js

herokuへpushまでは省略します。
push後urlへアクセスすると拒絶されたところから
ログを確認してから出直せと言われたので、

heroku logs -tail
Error: Couldn't find that app.
 ›
 ›   Error ID: not_found

で、結論から
https://github.com/mars/heroku-nextjs
を参考にしました。

package.json
{"name":"next-yaninavi","version":"1.0.0","description":"","main":"index.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1","dev":"next","build":"next build","start":"next start -p $PORT"},"keywords":[],"author":"","license":"ISC","dependencies":{"next":"^10.0.5","react":"^17.0.1","react-dom":"^17.0.1"}}
npm run build
NODE_EV=production npm run start

heroku触ったことなかったのでビクビクしてたけど、ngrokみたいな感じかな
恐らく、localhostとurlを繋いでるけどそのルート記述が間違ってたのかな。。

理解したら追記します!


MySQLで取得したデータを、EJSでWebブラウザに出力する

$
0
0

はじめに

こちらは、エンジニアの新たな学びキャンペーンに向けた記事となります。

Node.js + Express で作る Webアプリケーション 実践講座を参考にしながら、
データベース(以下DB)内のデータを、Webブラウザに表示する方法を記事にしました。

なお、ここではNode.jsのテンプレートエンジンであるEJSと、RDB(リレーショナルデータベース)であるMariaDB(MySQL)を利用します。

(上記講座ではMongoDBが利用されていますが、本記事ではMySQLに置き換えました。)

実行環境

  • Node.js v12.16.3
  • Express 4.16.1
  • 10.4.11-MariaDB

対象者

  • JavaScriptの文法自体は学んだけど、Web技術はまだほとんど学べていない人
  • かんたんなCRUD操作に関するSQLを理解している人

本記事でわかること

  • サーバーサイド言語Node.js & フレームワークExpressを使い、Hello Worldする方法
  • ExpressとMariaDB(MySQL)の連携方法
  • ExpressとMariaDB(MySQL)を使い、DB内のデータをWebブラウザ上に出力する方法

対象のUdemy講座で学んだこと

対象の講座で学んだことのうち、特に本記事へ反映する内容は以下となります。

  • パッケージマネージャであるnpmを使い、ミドルウェアやフレームワークを導入する方法
  • EJSの構文の使い方
  • Expressでのルーティング方法(特に関数の引数に関して)

EJSとは

EJSは、テンプレートエンジンと呼ばれるもののひとつで、
テンプレートエンジンはHTMLの中にプログラム言語を埋め込むことができます。

特にEJSにおいては、HTML文書の中に<% %>, <%= %>タグなどを埋め込み、この中にプログラムを記述します。

EJSの基本構文

EJSの基本的な書き方にきれいにまとまっていたので、
こちらを参照すると幸せになれます。

EJSの利点

EJSは、サーバーサイドで保持している変数の値を併用してHTMLを記述するとき、書きやすさ・読みやすさの点で非常に強力です。

例えばサーバーサイド言語のみでHTML文書を書く場合、次のようなソースコードになります。

app.js
constexpress=require("express");constapp=express();app.get("/",(req,res)=>{consttext="Hello World";letdata="<!DOCTYPE html>\r\n";data+="<html>\r\n";data+="<head>\r\n";data+="<meta charset='UTF-8'>\r\n";data+="<title>hoge</title>\r\n";data+="</head>\r\n";data+="<body>\r\n";data+="<p>";data+=textdata+="</p>\r\n";data+="</body>\r\n";data+="</html>";res.send(data);});app.listen(3000);

一方で、テンプレートエンジンを併用した場合は、次のようになります。
クォーテーションや改行を意味する\r\nなどが消え、読み書きしやすくなっているのが分かります。

app.js
constexpress=require("express");constapp=express();app.set("view engine","ejs");app.get("/",(req,res)=>{consttext="Hello World";res.render("index",{text});});app.listen(3000);
index.ejs
<!DOCTYPE html>
<html>
  <head>
    <meta charset='UTF-8'>
    <title>hoge</title>
  </head>
  <body>
    <p><%= text %></p>
  </body>
</html>

実行の準備

さて、まずは環境の構築を行います。
Node.js, MariaDB(MySQL)はインストールされているものとします。

サーバーサイドの準備

以下のコマンドを順に実行して、フレームワークやミドルウェアを導入します。

  • $ npm init
  • $ npm install express --save,
  • $ npm install mysql
  • $ npm install ejs -- save

MariaDB(MySQL)の準備

以下のSQLを順に実行します。
この操作により、Webサイトの名前・URLに関するテーブルを作成し、データの追加も行います。

  • create database website
  • create table website(name varchar(255), url varchar(255));
  • insert into website(name, url) values ("google", "https://www.google.com/"), ("amazon", "https://www.amazon.co.jp/"), ("apple", "https://www.apple.com/"), ("facebook", "https://www.facebook.com/");

実装する

次のような順序で、簡単なことから実装していきます。

  1. EJSを使い、Webブラウザ上でHello Worldする
  2. DB接続を行いデータを取り出し、ターミナル上にデータを出力する
  3. DB接続を行い、EJSファイルを利用してWebブラウザ上にデータを出力する(ここでは一度失敗してみる)
  4. DB接続を行い、EJSファイルを利用してWebブラウザ上にデータを出力する(再チャレンジし、成功する)

1. EJSでHello Worldしてみよう

まずはDBのことは一旦 忘れて、EJSを使ってHello Worldをしてみます。

下記ソースコードを保存後、ターミナル上で$ node app.jsと入力し、
Webブラウザでhttp://localhost:3000/にアクセスします。

次の画像のように表示されたら成功です。
ちなみに、
app.jsのソースコード内のapp.get()の第一引数はリクエストURL、
第二引数はリクエストが送られたときに実行されるコールバック関数を指します。

この処理を口語的に説明するなら、
http://localhost:3000/が呼ばれたら次のコールバック関数を実行してね!
そしてそのコールバック関数には、index.ejsを表示して!っていう命令も含まれてるよ!
といったところでしょうか。

image.png

実行するソースコード

app.js
constexpress=require("express");constapp=express();app.set("view engine","ejs");app.get("/",(req,res)=>{res.render("index.ejs");// デフォルトでは /viewsからの相対パスで表すので注意})app.listen(3000);
views/index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

2. DBに接続してみよう

次に、DB内のデータをターミナル上で表示する実装を行います。

app.jsを次のように書き換えます。
なお、mysql.createConnection()の各種ユーザ情報は、必要に応じて書きかえてください。

実行するソースコード

app.js
constexpress=require("express");constapp=express();constmysql=require("mysql");constconnection=mysql.createConnection({host:"localhost",user:"root",password:"1234",database:"website"});app.set("view engine","ejs");app.get("/",(req,res)=>{constsql="select * from website";connection.query(sql,(err,result,fields)=>{if(err)throwerr;console.log(result);})res.render("index.ejs");// デフォルトでは "/views"ディレクトリからの相対パスで表すので注意})app.listen(3000);

ターミナル上で$ node app.jsと入力し、Webブラウザでhttp://localhost:3000/にアクセスします。
前回と同様にHello WorldがWebブラウザに表示されており、
更にターミナル上に、次のような表示があれば成功です。

ここではconsole.log(result)の実行による、ターミナルのデータ出力結果から、
変数resultにはDBから取り出したデータが入っていることが確認できます。

$node app.js
[
  RowDataPacket { name: 'google', url: 'https://www.google.com/' },
  RowDataPacket { name: 'amazon', url: 'https://www.amazon.co.jp/' },
  RowDataPacket { name: 'apple', url: 'https://www.apple.com/' },
  RowDataPacket { name: 'facebook', url: 'https://www.facebook.com/' }
]

3. DB内のデータをWebブラウザに表示してみる

『1.EJSでHello Worldしてみよう』, 『2.DBに接続してみよう』では、
res.render("index.ejs")とレンダリング先を表記し、ルーティングを設定していました。

今回はレンダリング先にデータを渡すために、
app.get()内にレンダリング先だけではなく、DBから取得したデータも記述する必要があります。
そこでres.render()の第二引数に、レンダリング先に送るデータを記述します。

より具体的にはいえば、
『2. DBに接続してみよう』で、変数resultにDBのデータが格納されることが確認できていました。
このresultをres.render()の第二引数に指定します。
従って、ここではres.render("index", { web: result})と記述します。

ところで{ web: result}という記述に、ややこしさを感じるかもしれません。
これはresultからwebへ名前を置換してから、データを送るという処理を含んでいます。

EJSに対して、resultという変数名をそのままに渡してしまうと、
「result?結果?いや何の結果を表す変数なのか、なんのこっちゃわからん」と、
フロントエンドエンジニアが困惑することになってしまいます。

(webという変数名ならば適切なのかという問題はさておき。)

実行するソースコード

DBに保存しているWebサイト名やURLをWebブラウザ上に出力するため、
app.jsindex.ejsを、それぞれ次のように書き換えます。

app.js
constexpress=require("express");constapp=express();constmysql=require("mysql");constconnection=mysql.createConnection({host:"localhost",user:"root",password:"1234",database:"website"});app.set("view engine","ejs");app.get("/",(req,res)=>{constsql="select * from website";connection.query(sql,(err,result,fields)=>{if(err)throwerr;console.log(result);res.render("index",{web:result});})})app.listen(3000);
/views/index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello World</h1>
    <%= web %>
  </body>
</html>

ターミナルにnode app.jsを入力し、Webブラウザでhttp://localhost:3000/にアクセスします。
次の画像のような、Hello WorldとObjectという文字の羅列が確認できるでしょうか?
半分だけ成功です!

image.png

本来であればGoogleAppleといったWebサイト名や、
https://www.google.com/のようなURLがほしかったところですが、
DBから何らかのデータを取り出すことには、ひとまず成功したのではないでしょうか。
[]で囲まれたよくわからないものは4つで、DBに登録したレコードもちょうど4つでしたしね。

次の項で、この問題を解決します。

4. DB内のデータをWebブラウザに表示してみる(再挑戦)

前項ではDBから、どうやら何らかのデータを取り出すことには成功しましたが、
Webサイトの名前やURLを取得することはできませんでした。

この原因について考えます。

http://localhost:3000/にアクセスしたとき、
console.log(result)の実行によって、ターミナルに何か表示されたことは覚えているでしょうか?

それは、次のような内容でした。

$node app.js
[
  RowDataPacket { name: 'google', url: 'https://www.google.com/' },
  RowDataPacket { name: 'amazon', url: 'https://www.amazon.co.jp/' },
  RowDataPacket { name: 'apple', url: 'https://www.apple.com/' },
  RowDataPacket { name: 'facebook', url: 'https://www.facebook.com/' }
]

一見するとオブジェクトですが、
出力されたデータは[]で囲まれているため、これはオブジェクトたちを格納している『配列』です

従って、例えばgoogleを取得する場合はweb[0]["name"]と記述します。

実行するソースコード

例として、googlehttps://www.google.com/をWebブラウザ上に表示してみます。
次のようにindex.ejsを書き換え、再度 $ node app.jsで実行します。
下記画像のように、Webサイト名とURLが表示されたら、成功です。

/views/index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Hello World</h1>
    <%= web[0]["name"] %>
    <br>
    <%= web[0]["url"] %>
  </body>
</html>

image.png

この他にも応用として、
for文を利用するなどして、一度に複数のデータを出力すること、
あるいはSQLを変更してデータの更新・追加・削除することも可能です。

おわりに

プログラミングを本格的に始めて1年も満たない未熟者の言葉ではありますが、
プログラミング言語の文法を修めるだけでは、Webのシステム開発は正直 不可能です。

私はJavaScriptの文法を学習した後、サーバーサイド言語としてのJavaScript(Node.js)に入門したのですが、
すぐにHTTPリクエスト、ルーティングなどといったWeb特有の専門用語に悩まされました。

Webアプリケーションのサーバーサイドへ入門する前に、
『この一冊で全部わかるWeb技術の基本』, 『Webを支える技術』, 『Web技術速習テキスト』といったWeb周りの情報に触れておくことを、強くおすすめします。

参考

M5Core2のLCDにWebページのスクリーンショットを表示する

$
0
0

M5Core2のLCDにいろんな情報を表示する際に、画面レイアウトを試行錯誤しながらコンパイル・書き込み・実行を繰り返すのは手間なので、HTMLで画面を作成してそのスクリーンショットをM5Core2のLCDに表示するようにします。

image.png

もろもろのソースコードをGitHubに上げておきました。

poruruba/WebSnapshot
 https://github.com/poruruba/WebSnapshot

スクリーンショット生成

スクリーンショットには「puppeteer」を使います。

puppeteer/puppeteer
 https://github.com/puppeteer/puppeteer

(参考)呼び出し方
 https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md

サーバの実装は以下の通りです。

api/controllers/screenshot/index.js
'use strict';constHELPER_BASE=process.env.HELPER_BASE||'../../helpers/';constResponse=require(HELPER_BASE+'response');constBinResponse=require(HELPER_BASE+'binresponse');const{URL,URLSearchParams}=require('url');constfetch=require('node-fetch');constHeaders=fetch.Headers;constpuppeteer=require('puppeteer');exports.handler=async(event,context,callback)=>{if(event.path=='/screenshot'){varurl=event.queryStringParameters.url;varwait=0;varwidth=640;varheight=480;varscale=1.0;vartype=event.queryStringParameters.type||'png';// png or jpegif(event.queryStringParameters.width)width=parseInt(event.queryStringParameters.width);if(event.queryStringParameters.height)height=parseInt(event.queryStringParameters.height);if(event.queryStringParameters.scale)scale=parseFloat(event.queryStringParameters.scale);if(event.queryStringParameters.wait)wait=parseInt(event.queryStringParameters.wait);console.log(width,height,scale,url);varbrowser=awaitpuppeteer.launch();varpage=awaitbrowser.newPage();awaitpage.setViewport({width:width,height:height,deviceScaleFactor:scale,});awaitpage.goto(url,{waitUntil:"load"});if(event.queryStringParameters.waitfor){try{awaitpage.waitForFunction("vue.render.loaded");}catch(error){console.log(error);}}if(wait>0)awaitpage.waitForTimeout(wait);varbuffer=awaitpage.screenshot({type:type});browser.close();returnnewBinResponse('image/'+type,buffer);}elseif(event.path=='/screenshot-weather'){varlocation=parseInt(event.queryStringParameters.location);varweather=awaitdo_get_weather(location);returnnewResponse({weather});}};/* location: 13:東京、14:神奈川 */functiondo_get_weather(location){returnfetch('https://www.drk7.jp/weather/json/'+location+'.js',{method:'GET'}).then((response)=>{returnresponse.text();}).then(text=>{text=text.trim();if(text.startsWith('drk7jpweather.callback('))text=text.slice(23,-2);returnJSON.parse(text);});}

少し解説します。
puppeteerは、内部的にはChrome(またはFirefox)を使っています。
以下の部分で、ブラウザの起動と、ページタブの生成をしています。

api/controllers/screenshot/index.js
varbrowser=awaitpuppeteer.launch();varpage=awaitbrowser.newPage();

この部分で、ブラウザの表示サイズを変更しています。

api/controllers/screenshot/index.js
awaitpage.setViewport({width:width,height:height,deviceScaleFactor:scale,});

M5Core2は、320×240であるため、width=320、height=240、scale=1.0で良いかと思います。場合によっては、解像度が小さすぎてHTMLレイアウトが難しい場合は、例えば、width=640、height=480、scale=0.5 のようにして、いったん大きい画面でレンダリングしたのち、縮小表示して320×240に合わせるというやり方も可能です。

以下の部分でURLで示されるWebページを取得しレンダリングします。

api/controllers/screenshot/index.js
awaitpage.goto(url,{waitUntil:"load"});if(event.queryStringParameters.waitfor){try{awaitpage.waitForFunction("vue.render.loaded");}catch(error){console.log(error);}}if(wait>0)awaitpage.waitForTimeout(wait);

その中で、必要に応じてwaitFor*** を呼び出しています。
Javascriptで画面を制御している場合、Javascriptの処理が終わった後に画面キャプチャするためのものです。
waitForTimeoutはウェイト時間を決めてその時間経過後に画面キャプチャするもので、waitForFunctionはWebページ中のJavascriptの条件式で待ち終了させます。
waitForFunctionの方は、WebページのJavascriptの実装に依存するので、お好みで変えてください。

以下の部分が、画面キャプチャする部分です。

api/controllers/screenshot/index.js
varbuffer=awaitpage.screenshot({type:type});

JpegかPNGが選べます。

最後に、ブラウザを閉じて終わりです

api/controllers/screenshot/index.js
browser.close();

この画像バイナリを呼び出し元に返します。

api/helpers/binresponse.js
classBinResponse{constructor(content_type,context){this.statusCode=200;this.headers={'Access-Control-Allow-Origin':'*','Cache-Control':'no-cache','Content-Type':content_type};this.isBase64Encoded=true;if(context)this.set_body(context);elsethis.body="";}set_filename(fname){this.headers['Content-Disposition']='attachment; filename="'+fname+'"';returnthis;}set_error(error){this.body=JSON.stringify({"err":error});returnthis;}set_body(content){this.body=content.toString('base64');returnthis;}get_body(){returnBuffer.from(this.body,'base64');}}module.exports=BinResponse;

以下の部分は、スクリーンショット生成には関係しませんが、のちほどスクリーンショット対象のWebページで使っているものです。

api/controllers/screenshot/index.js
varweather=awaitdo_get_weather(location);

M5Core2の実装

M5Core2側の実装です。

WebSnapshot/src/main.cpp
#include <WiFi.h>
#include "M5Lite.h"
#include <HTTPClient.h>
constchar*wifi_ssid="【WiFiアクセスポイントのSSID】";constchar*wifi_password="【WiFiアクセスポイントのパスワード】";constchar*screenshot_url="【Node.jsサーバのURL】/screenshot";constchar*target_url="【スクリーンショット対象のWebページのURL】";#define SCREENSHOT_INTERVAL   (10 * 60 * 1000) //スクリーンショット取得の間隔
#define DISPLAY_WIDTH   320  //LCDの横解像度
#define DISPLAY_HEIGHT  240 //LCDの縦解像度
#define SCREENSHOT_SCALE  1.0 //スクリーンショットの表示倍率
#define BUFFER_SIZE   20000 //画像受信のバッファサイズ
unsignedcharbuffer[BUFFER_SIZE];voidwifi_connect(constchar*ssid,constchar*password);Stringurlencode(Stringstr);longdoHttpGet(Stringurl,uint8_t*p_buffer,unsignedlong*p_len,unsignedshorttimeout);voidsetup(){M5Lite.begin();Serial.begin(9600);Serial.println("setup");wifi_connect(wifi_ssid,wifi_password);Serial.println("connected");}voidloop(){M5Lite.update();Stringurl=screenshot_url;url+="?type=jpeg&width="+String((int)(DISPLAY_WIDTH/SCREENSHOT_SCALE))+"&height="+String((int)(DISPLAY_HEIGHT/SCREENSHOT_SCALE))+"&scale="+String(SCREENSHOT_SCALE);//  url += "&waitfor=true";url+="&wait=5000";url+="&url="+urlencode(target_url);Serial.println(url);unsignedlonglength=sizeof(buffer);longret=doHttpGet(url,buffer,&length,5000);if(ret==0){M5Lite.Lcd.drawJpg(buffer,length);}delay(SCREENSHOT_INTERVAL);}voidwifi_connect(constchar*ssid,constchar*password){Serial.println("");Serial.print("WiFi Connenting");M5Lite.Lcd.println("WiFi Connectiong");WiFi.begin(ssid,password);while(WiFi.status()!=WL_CONNECTED){Serial.print(".");M5Lite.Lcd.print(".");delay(1000);}Serial.println("");Serial.print("Connected : ");Serial.println(WiFi.localIP());M5Lite.Lcd.println("");M5Lite.Lcd.print("Connected : ");M5Lite.Lcd.println(WiFi.localIP());}Stringurlencode(Stringstr){StringencodedString="";charc;charcode0;charcode1;//    char code2;for(inti=0;i<str.length();i++){c=str.charAt(i);if(c==' '){encodedString+='+';}elseif(isalnum(c)){encodedString+=c;}else{code1=(c&0xf)+'0';if((c&0xf)>9){code1=(c&0xf)-10+'A';}c=(c>>4)&0xf;code0=c+'0';if(c>9){code0=c-10+'A';}//        code2 = '\0';encodedString+='%';encodedString+=code0;encodedString+=code1;//encodedString+=code2;}yield();}returnencodedString;}longdoHttpGet(Stringurl,uint8_t*p_buffer,unsignedlong*p_len,unsignedshorttimeout){HTTPClienthttp;http.setTimeout(timeout+5000);Serial.print("[HTTP] GET begin...\n");// configure traged server and urlhttp.begin(url);Serial.print("[HTTP] GET...\n");// start connection and send HTTP headerinthttpCode=http.GET();unsignedlongindex=0;// httpCode will be negative on errorif(httpCode>0){// HTTP header has been send and Server response header has been handledSerial.printf("[HTTP] GET... code: %d\n",httpCode);// file found at serverif(httpCode==HTTP_CODE_OK){// get tcp streamWiFiClient*stream=http.getStreamPtr();// get lenght of document (is -1 when Server sends no Content-Length header)intlen=http.getSize();Serial.printf("[HTTP] Content-Length=%d\n",len);if(len!=-1&&len>*p_len){Serial.printf("[HTTP] buffer size over\n");http.end();return-1;}// read all data from serverwhile(http.connected()&&(len>0||len==-1)){// get available data sizesize_tsize=stream->available();if(size>0){// read up to 128 byteif((index+size)>*p_len){Serial.printf("[HTTP] buffer size over\n");http.end();return-1;}intc=stream->readBytes(&p_buffer[index],size);index+=c;if(len>0){len-=c;}}delay(1);}}}else{http.end();Serial.printf("[HTTP] GET... failed, error: %s\n",http.errorToString(httpCode).c_str());return-1;}http.end();*p_len=index;return0;}

大した処理はしていないです。
なぜならば、ESP32 Lite Pack Library に含まれているLovyanGFXがすべてをやってくれているからです。
本来であれば、M5Core2の回路を見てLCDに表示できるようにしたり、Jpegを解析したりと、いろいろやらないといけないのですが、機種判別やらJpegのLCD表示やらをすべてやってくれているからです。

以下は、環境に合わせて以下を変更してください。

WebSnapshot/src/main.cpp
constchar*wifi_ssid="【WiFiアクセスポイントのSSID】";constchar*wifi_password="【WiFiアクセスポイントのパスワード】";constchar*screenshot_url="【Node.jsサーバのURL】/screenshot";constchar*target_url="【スクリーンショット対象のWebページのURL】";#define SCREENSHOT_INTERVAL   (10 * 60 * 1000) //スクリーンショット取得の間隔
#define DISPLAY_WIDTH   320  //LCDの横解像度
#define DISPLAY_HEIGHT  240 //LCDの縦解像度
#define SCREENSHOT_SCALE  1.0 //スクリーンショットの表示倍率
#define BUFFER_SIZE   20000 //画像受信のバッファサイズ

platformio.iniは以下にしました。

WebSnapshot/platformio.ini
[env:m5stack-core2]platform=espressif32board=m5stack-fireframework=arduinoupload_port=COM8monitor_port=COM8lib_deps=https://github.com/m5stack/M5Core2.gittanakamasayuki/ESP32LitePackLibrary@^1.3.2

M5Core2に表示するWebページの作成

HTMLはもちろん、CSSやJavascriptも使えますので、通常のWebページ作成時の要領と同じです。
ただし、小さい解像度である320x240では表示が崩れてしまう可能性大なので、Chromeブラウザ上で試行錯誤したいところ。
Chromeの開発ツールを使えばよいです。

image.png

F12キーを押して開発ツールを表示させ、Elementsタブの左にある□2つのアイコンをクリックします。
次に、以下の部分でResponsiveが選択された状態にすると、表示領域の縦横の解像度を変更できるようになるので、ここで320x240とします。

image.png

これで、Webページのレイアウト作成がやりやすくなるかと思います。
完成したら、同じURLをM5Core2からNode.jsサーバに渡すとChromeの開発ツールで見えている状態のままのJpeg画像が取得され表示されます。

以上

Node.js(express)でpostgresSQLのSSL認証が通らないことについて

$
0
0

はじめに

店舗予約の受付をするLineBotの開発をするために
Node.js(express)を開発言語にしました。

PostgresSQLでDB作成をしたさいに
SSL接続が原因でエラーとバトルした件について解説します。

少しでもお役に立てれば幸いです。

誤った情報だと感じましたら教えてください!

開発環境

(1)macOS 10.15.7
(2)Node.js 12.19.0
(3)express 4.17.1
(4)line/bot-sdk" 7.2.0
(5)Postgres 8.5.1
(6)Heroku

エラー内容

index.js
const { Client } = require('pg');

//Postgres環境変数を設定//

const connection = new Client({
  user: process.env.PG_USER,
  host: process.env.PG_HOST,
  database: process.env.PG_DATABASE,
  password: process.env.PG_PASSWORD,
  port: ****
});
connection.connect();

//クエリ文//

const userTable = {
    text:'CREATE TABLE IF NOT EXISTS users (id SERIAL NOT NULL, line_uid VARCHAR(255), display_name VARCHAR(255), timestamp VARCHAR(255), trigger SMALLINT, remedy SMALLINT, pelvis SMALLINT);'
 };

//クエリを実行するためのコード//

connection.query(userTable)
   .then(()=>{
       console.log('table users created successfully!!');
   })
   .catch(e => console.log(e));

テーブルができているか確認をするために

ターミナル
$ heroku pg:psql

データベースモードになったら

ターミナル
DATABASE=> select * from users; 
ここで怒られます。 orz
ターミナル
ERROR:  relation "users" does not exist  //"users"との関係性がありません

LINE 1: select * from "users";

((((;゚Д゚)))))))

解決策

エラー文を検索しても出てくる記事が

1、select * from "users"とダブルクォーテーションで囲め!!

2、大文字と小文字が〜〜〜

など変更しても解決にならず。

どうしても分からず質問したところ
Postgres環境変数を修正しましょう!!これで解決できます!!と素敵なアドバイスをもらいました♫

index.js
//Postgres環境変数を設定//

const connection = new Client({
  connectionString: process.env.DATABASE_URL,
  ssl: {
    rejectUnauthorized: false    //SSL接続なくても許可するよーと宣言
  }
});
connection.connect();

コードの変更をしたので確認します!!!

先程と同じようにデータベースモードにします。

ターミナル
$ heroku pg:psql
ターミナル
DATABASE=> select * from users;

上記にコードを修正した結果

ターミナル
ファイル名::DATABASE=> select * from users;
 id | line_uid | display_name | timestamp | trigger | remedy | pelvis 
----+----------+--------------+-----------+---------+--------+--------
(0 rows)

おけ👍

考察

今回のエラーはSSL接続が許可されておらず認証されないことによって起きたと考えています。

(1)最近仕様が変更になった出来事なのか?

(2)SSL接続がデフォルトに変更されたのか?

参考文献が見つからず原因がわかる方がいらっしゃいましたら、ご教授ください。

Node.js で車とシリアル通信する

$
0
0

先日車が壊れたのを機に車について調べていたら OBD (On-board diagnostics) という診断機能を知りました。大抵の車には OBD のコネクタが搭載されていて、これに ELM327というマイコンを使った製品をつなぐと Bluetooth のシリアル通信などで簡単に情報をやりとりできるそうです。おもしろそうなので試してみました。

重要このポストと同じ方法で車の診断ログをリセットしたりすることもできます。診断ログが正しくない状態になると、修理の適切なタイミングなどがわからなくなるはずです。コマンドを車に送る前によく考えた上で自己責任で試してください。


ELM327 のドングルを手に入れる

これらの製品は Amazon等で廉価に入手できるようです。全製品で固定っぽい Bluetooth の接続パスワードが書かれていたので念のため黒塗りにしました。

connector1.jpg

車のOBDポートを探す

運転席のどこかにある場合が多いようです。私の車はここにありました。

connector2.jpg

ここに購入した上記のドングル型デバイスを接続します。

通信方法を確認

送信

AT コマンドというものを送るとマイコンを操作することができるようです。今回は以下の2つのコマンドを使いました。

  • AT Z: 状態をリセットする
  • AT SP 0: OBDの通信プロトコルを自動で検出

通信プロトコルは様々なものがあるようですが、私の車で試した限りでは AT SP 0の自動検出でうまく通信できました。

AT コマンドの他に OBD コマンドというものがあり、これで様々な OBD の情報を取得できました。Wikipedia にコマンドのリストがあったため、これを参照しました。

OBD コマンドは Modeと PIDから構成されています。現在のデータを読み取る 01というモードを今回は使いました。取得できるデータごとに PIDが振られており、例えば PID0Cのエンジン回転数の情報を読み取る OBD コマンドは 010Cになります。

受信

データは、41 0C AA BB ...のようにスペース区切りの16進数の文字列で帰ってきます。1つめが Modeの値に40を足したもの、2つめが PIDで、それ以降が実際のデータになります。例えばエンジン回転数の場合は値が4倍されているなど、それぞれ決まりがあるようです。

Node.js でシリアル通信をする

serialportというライブラリが著名そうだったので利用しました。

Node.js のランタイムでシリアルポートをオープンする TypeScript のコードはこんな感じ。ELM327 のデバイスに Bluetooth で接続した後にコードを実行すると、SerialPortのインスタンスが得られます。

importSerialPortfrom'serialport'constDEVICE_PATH='/dev/tty.OBDII-SPPslave'exportconstconnect=()=>newPromise<SerialPort>((resolve,reject)=>{constport=newSerialPort(DEVICE_PATH,{baudRate:9600,autoOpen:false})console.log(`[SP] opening on ${DEVICE_PATH}..`)port.open((error)=>{if(error){console.log('[SP] port opening error.')reject(error)}else{console.log('[SP] port opened.')resolve(port)}})})

ポートに書き込むには writeメソッドを使います。コマンドのデリミタはキャリッジリターンなので注意してください。

// `AT Z` の AT コマンドを送出port.write('AT Z\r')

データはイベントリスナーを使って取得できます。
文字列を処理して、目的の値を抽出します。

port.on('data',(data:string)=>{const[mode,pid,...values]=data.split('')letmodeNumtry{modeNum=parseInt(mode,16)-40}catch{return}if(modeNum===1){consthexValue=parseInt(values.filter(value=>value!=='').join(''),16)if(pid==='0C'){console.log('EngineRPM: '+hexValue/4)}}})

WebSocket でデータをブラウザに送信できるようにする

上記でデータが取得できる準備が整ったので、あとは WebSocket を使ってブラウザにデータを送ってみます。wsというライブラリを使います。

WebSocket サーバーを立ち上げるコードはこんな感じ。

importWebSocketfrom'ws'exportconstcreateSocket=()=>newPromise<WebSocket>((resolve,reject)=>{console.log('[WS] connecting...')constwss=newWebSocket.Server({port:8081})wss.on('connection',(ws)=>{console.log('[WS] connected.')resolve(ws)})})

WebSocketのインスタンスを使って、シリアル通信で得られたデータを送ります。上記のシリアル通信のイベントリスナーに socket.send(data)を差し込めば OK です。

クライアント側は、こんな感じ。

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title><style>html,body{margin:0;padding:0;width:100%;height:100%;}#display{position:fixed;width:500px;height:100px;top:10px;left:10px;font-size:2em;}</style></head><body><textareaid="display"></textarea><script>constws=newWebSocket('ws://localhost:8081')constdisplay=document.getElementById('display')letcurrentData={}ws.onmessage=(event)=>{currentData={...currentData,...JSON.parse(event.data)}constnextValue="エンジン回転数: "+currentData.engineRPM+'rpm\n'display.value=nextValue}</script></body></html>

動作させる

以上で全ての材料が揃ったので、これを組み込んでいきます。今回は Mac 上で以下の全てのプロセスを立ち上げて試してみました。

  • 車とシリアル通信をする WebSocket サーバー
  • クライアントを表示する ウェブサーバー

また、エンジン回転数以外にも現在の運転速度と冷却水温度も取得してみました。

image.png
https://youtu.be/jtPIO3J6cEs

動画を撮りながら走ってみると、いい感じにデータが取れているようです✌️


上記のコードは以下のリポジトリにあります。
https://github.com/kamataryo/parse-obd-serial

【Promiseは規格?みたいなもの】

$
0
0

【Promiseとは】

Promiseはrejectedかfulfilledという結果が帰ってくることを約束してくれる。

そのため、それらに対するハンドリングがしやすいといったメリットがあると勉強していて感じた。実際、Node.jsの各メソッドをPromiseインスタンスに変換できるようなものが導入されたりとそのうちPromiseの規格に統一されるかもしれない。

【Promiseの挙動】

Promiseは、Promiseインスタンスを返す。

その時、Promiseインスタンスは最終的に、rejectedかfulfilledを返すが、それは以下のようにpendingな状態をreject()かresolve()で解決しないといけない。

         pending
       ↓
      / \
    reject()   resolve()
       ↓         ↓
  rejected     fulfilled    <=  これらの状態をsettledという

【Promiseインスタンスをsettled状態にしてみる】

まずPromiseはnew Promise()のようにして一度pendingな状態にする方法と、

pending状態を経ずに直接fulfilled、rejected状態にする2パターンある。

// 一度pendingにする// settledにするには一度変数に入れて、setTimeoutで3000経過させるなどしないといけないfunctionasyncFunc(){returnnewPromise((resolve,reject)=>{setTimeout(()=>{try{resolve('成功')}catch(err){reject(err)}},3000)})}console.log(asyncFunc())=>Promise{<pending>}// 直接resolve、rejectedPromise.resolve('成功')=>Promise{'成功'}Promise.reject(newError('エラー'))=>Promise{<rejected>Error:エラー~~(省略)

【settled状態を利用して次の処理をおこなう】

.then()、.catch()、を使えば、Promiseインスタンスがsettledになったら、実行するコールバック処理を設置できる。

以下の処理は、Promiseが1でfulfilled状態になり、その結果を.then()のnumで受け取っている。

Promiseでコールバックヘルが解決できるのはこういったことができるからだ。

Promise.resolve(1).then(num=>console.log('resolve',num))

上記の.then()は省略されており、第二引数には.then()で起きたエラーの処理を受け取れるようになっている。しかし、これは.then()が多くなると長くなる。

.catch()を最後につけることで.then()で起きたエラーを全て一箇所で受け取れる。

// 同じことをしているPromise.resolve(1).then(num=>console.log('resolve',num),err=>newError('エラー'))Promise.resolve(1).then(num=>console.log('resolve',num)).catch(err=>newError('エラー'))

【複数のPromiseを並行に処理してみる】

・ Promise.all 

中のPromiseインスタンスが全部fulfilledになったら、その値でfulfilledを返す。

これだと各処理が平行に実行されるので効率がいい。

constall=Promise.all([1,Promise.resolve('成功'),Promise.resolve('fulfilled')])all=>Promise{[1,'成功','fulfilled']}

・Promise.allSettled

中のPromiseインスタンスがfulfilledかrejectedかに関わらずそれぞれの結果を返す。

とりあえず結果をきにせず平行に処理したい時に便利かも。

constallSettled=Promise.allSettled([Promise.resolve('成功'),Promise.reject(newError('エラー'))])allSettled=>Promise{[{status:'fulfilled',value:'成功'},{status:'rejected',reason:Error:エラー~(省略)

Node.jsでよく使うasync awaitもPromiseインスタンスをawaitの直前に置くことで実装できるように設計されているためPromiseの理解が重要。

Azure IoT Hub に Node.js で publish

$
0
0

ライブラリーのインストール

sudo npm install-g azure-iot-device-mqtt
sudo npm install-g azure-iot-device
azure_publish.js
#! /usr/bin/node
// ---------------------------------------------------------------//  azure_publish.js////                  Jan/17/2021//// ---------------------------------------------------------------'use strict'vardevicemqtt=require('azure-iot-device-mqtt')vardevice=require('azure-iot-device')//// ---------------------------------------------------------------functionprintResultFor(op){returnfunctionprintResult(err,res){if(err)console.log(op+' error: '+err.toString())if(res)console.log(op+' status: '+res.constructor.name)}}// ---------------------------------------------------------------functiondefine_data_proc(){constdevice_id="pansy"constjikan=newDate()consthour=jikan.getHours()constminute=jikan.getMinutes()constsecond=jikan.getSeconds()constcurrent_time=""+hour+":"+minute+":"+secondconsttt=(20+(Math.random()*15))*10consttemperature=Math.round(tt)/10consthh=(60+(Math.random()*20))*10consthumidity=Math.round(hh)/10constddx={device_id:device_id,time:current_time,temperature:temperature,humidity:humidity}constdata=JSON.stringify(ddx)returndata}// ---------------------------------------------------------------console.error("*** 開始 ***")//varMessage=device.MessageconstconnectionString="HostName=iot-aa.azure-devices.net;DeviceId=pansy;SharedAccessKey=SvsFWVsCy2bJkH0QuPJeIabcdefgh8mo6S6vNCjom92="varclient=devicemqtt.clientFromConnectionString(connectionString)varconnectCallback=function(err){if(err){console.error('Could not connect: '+err);}else{console.log('Client connected')constdata=define_data_proc()varmessage=newMessage(data)console.log("Sending message: "+message.getData())client.sendEvent(message,printResultFor('send'))}}client.open(connectCallback)console.error("*** 終了 ***")// ---------------------------------------------------------------

プライマリ接続文字列 をポータルで調べる必要があります。
primary_jan17.png

実行コマンド

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

Node.js × Expressで始めるGraphQL入門

$
0
0

概要

GraphQLを初めて実装してみた時の手順と、サンプルソースコードを記載しています。

環境

  • macOS Big Sur 11.1
  • node v14.12.0
  • yarn 1.22.10
  • express 4.16.1
  • Docker version 19.03.13
  • docker-compose version 1.27.4

サンプルソースコード

hayatoiwashita/express-graphql

手順

1. プロジェクトの雛形を作成

express-generatorを使用した。
--gitオプションは.gitignoreを追加するためのオプション。

$ express --view=pug --git express-graphql

参考: Express のアプリケーション生成プログラム

2. ライブラリのインストール

GraphQL関連のライブラリは最低限下記があればOK。

$ yarn add graphql
$ yarn add express-graphql

サンプルでは下記も追加している(本題から逸れるので割愛)

# ホットリロード
$ yarn add --dev nodemon
# コード整形
$ yarn add prettier --dev --exact
$ yarn add eslint --dev
$ yarn add --dev eslint-config-prettier
$ yarn add --dev eslint-plugin-prettier
# commit時にコード整形を実行
$ yarn add --dev lint-staged
$ yarn add --dev husky

3. サンプルデータの用意

/data/users.jsを追加して、DBの代わりに使用した。
(内容はサンプルソースコードのusers.jsを参照)

4. schemaを定義

/schema/schema.graphqlを作成してschemaを定義。
今回は参照系クエリのみ実装した。
データ構造は、ユーザとそれに紐づく投稿といった形。

schema.graphql
const{buildSchema}=require("graphql");constschema=buildSchema(`typeQuery{users:[User!]!,user(id:Int!):User!}typeUser{id:ID!name:String!email:Stringposts:[Post!]}typePost{id:ID!title:String!published:Boolean!link:Stringauthor:User!}`);module.exports=schema;

5. リゾルバを作成

データ操作を行う/src/resolvers.jsを作成。
上述の通り、今回は参照系のみ。

resolvers.js
const{Users}=require('../data/users');constresolvers={users:async(_)=>{console.log('come here');returnUsers;},user:async({id},context)=>{returnUsers.find((user)=>user.id==id);},};module.exports=resolvers;

6. app.js を修正

エンドポイント/graphqlを定義。

app.js
// var createError = require('http-errors');varexpress=require('express');var{graphqlHTTP}=require('express-graphql');varresolvers=require('./src/resolvers');varschema=require('./schema/schema.graphql');varpath=require('path');varcookieParser=require('cookie-parser');varlogger=require('morgan');varapp=express();// view engine setupapp.set('views',path.join(__dirname,'views'));app.set('view engine','pug');app.use(logger('dev'));app.use(express.json());app.use(express.urlencoded({extended:false}));app.use(cookieParser());app.use(express.static(path.join(__dirname,'public')));app.use('/graphql',graphqlHTTP({schema,rootValue:resolvers,graphiql:true,}));module.exports=app;

7. [参考] コンテナ化

Dockerfiledocker-compose.ymlを追加。

FROM node:14.15.4-slimWORKDIR /usr/local/src/express-graphqlADD . .RUN yarn installCMD [ "yarn", "start" ]
docker-compose.yml
version:'3'services:graphql:build:.container_name:graphql-containerports:-"3000:3000"

8. Expressの起動

起動コマンドを実行。

# コンテナで実行
$ docker-compose up

# コンテナを使用せず実行
$ yarn start

9. クエリを実行

http://localhost:3000/graphqlをブラウザで開く。
screencapture-localhost-3000-graphql-2021-01-16-16_08_50.png

サンプルクエリ
内容としては、ユーザID=1のユーザの名前と、そのユーザの投稿(posts)のIDとタイトルを取得するというもの。

{
  user(id:1) {
    name
    posts {
      id
      title
    }
  }
}

実行結果

{
  "data": {
    "user": {
      "name": "Fikayo Adepoju",
      "posts": [
        {
          "id": "1",
          "title": "Debugging an Ionic Android App Using Chrome Dev Tools"
        },
        {
          "id": "2",
          "title": "Hosting a Laravel Application on Azure Web App"
        }
      ]
    }
  }
}

screencapture-localhost-3000-graphql-2021-01-17-15_09_38.png

参考


Amazon Transcribeをストリーミングで実行する

$
0
0

はじめに

  • Amazon Transcribeは音声データをテキスト化する(文字起こしとも言われる)サービスです
  • 音声ファイルを準備してバッチ処理するタイプと、音声をストリーミングして、リアルタイム(逐次的に)処理するタイプがあります
  • 日本語については、2020年11月にリアルタイム処理タイプに対応しました
  • AWSコンソール上ではブラウザの音声をTranscribeへストリーミングすることで機能を確認することができます。レスポンス良くテキスト化されます
  • 実際の業務等ではマネジメントコンソールで使うことはなく、アプリケーション等に組み込むことになると思います
  • ということで、サンプル的にストリーミング処理を実装してみました
    • そして自分への備忘も兼ねて処理内容の解釈を記述します
  • なお、SDK v3はpreviewなので、これからリリースされる正式バージョンと異なる(動作しない)可能性があります

環境

  • Ubuntu 20.04 on WSL2
  • Node.js v14.15.4
  • AWS SDK for JavaScript version 3(preview)

サンプルコード

下記コードをタタキ台として説明します

実行手順

オーディオファイルを準備する

  • バッチ処理ではなくリアルタイムストリーム処理、と言ったわりには音声ファイルを準備します
  • 音声入力部分も何らかのストリームにする想定ですが、今回はTranscribeとのストリーム部分にフォーカスし、シンプルにしました

    • (手元ではKinesisVideoStreamからの音声をTranscribeへパイプできることまで確認しています)
  • オーディオファイルの仕様

    • 符号付き16ビット PCM 8KHz RAW(ヘッダ無)
    • 指定がある場合はリトルエンディアンを選択
  • Audacityを利用して変換するのが簡単です

image.png

実行環境の準備

  • Node.jsの環境作成
    • 各自準備してください
  • githubからソースファイルを手元へコピー
    • git cloneなどで
  • パッケージインストール
    • npm install

実行

  • ニュース動画の音声で試してみました
  • 下記のようになります
  • 音声が30秒ぐらいで、処理時間も30秒+αかかります
  • アナウンサーの発声ということもあってか、正確にテキスト化されています
    • 来月 な の か?部分が正確には 来月七日ですが、他は誤変換もなく素晴らしい精度です
$ node transcription.js sample_audio.raw
0.08: きょう 東京 都心 は ぽかぽか 陽気 と なり 最高 気温 は 昨日 より 十 一 度、 以上 高い 十 八、 七 度 と か
8.39: 四 月 中旬 並み の 暖か さ と なり まし た
11.98: 江戸川 区 の 葛西 臨海 公園 に は 多く の 家族 連れ の 姿 が。
17.54: 東京 都 で は 緊急 事態 宣言 の 解除 が 予定 さ れ て いる 来月 な の か? まで
23.15: 上野 動物 園 や 葛西 臨海 水 族 園 など 都立 の 動物 園 や 水 族 館 を 臨時 休業 に し て い ます

コードの解説

const parseTranscribeStream...部分

  • Transcribeからのレスポンス解釈部です
  • Transcribeからのレスポンスオブジェクトを表示用に編集しています
  • IsPartial == trueの場合は変換途中を示していますので表示から省いています
    • 条件を外して実行してみると挙動がわかると思います

const audioSource = createReadStream部分

  • 音声ファイルを読み込みしています
  • highWaterMarkで一度に読み込むサイズを1KBに抑えています
    • 大きいサイズを送信するとTranscribe側がエラーを返すことがあります

const audioStream = async function*部分

  • 音声ファイルのデータ片を所定のオブジェクト形式に変換しています

const command = new StartStreamTranscriptionCommand部分

  • Transcribeとのリアルタイムストリーム開始コマンドを作成しています
    • 日本語、PCM形式、8KHzであることを伝えています
    • 入力は前段で定義したストリームを指定しています
  • 処理が開始すると、入力ストリームから順次データを受け取り、処理する記述です

const client = new TranscribeStreamingClient部分

  • Transcribeとのリアルタイムストリーム開始リクエストクライアントを作成しています
    • パラメータ requestHandlerでセッションタイムアウトを5秒指定しています
      • これを指定しないと音声データ送信が終了しても5分ぐらい通信が切断されません
      • SDK内部でコネクションプールしていますが、データ終了時にクローズせず、プールしたままになっているようです
      • SDKはまだpreviewなので改善するかもしれません

const response = await client.send(command)部分

  • Transcribeとのリアルタイムストリーム開始リクエストしています
    • SDK v3では基本的に「コマンド作成」して、client.send()する記述になるようですね

const transcriptsStream = Readable.from(response.TranscriptResultStream)部分

  • Transcribeからのレスポンスを受け取るストリームを宣言しています

transcriptsStream.pipe(parseTranscribeStream).pipe(stdout)部分

  • ストリームをパイプで接続しています
    • Transcribeからのレスポンスを冒頭で記述したレスポンス解釈部へ
    • レスポンス解釈部のアウトプットを標準出力へ

まとめ

  • Node.jsの非同期ストリーム処理によってシンプルに実装できる印象です
    • そのぶん、どこで何が処理されているか理解しておかないとハマるかもしれません
  • この実装だと、HTTP/2 Streaming動作になると思います
    • 実は SDK v2で WebSockets Streamingも試していたのですが、途中でv3に気づいて移ってきました
    • あまり詳しくないのですが、HTTP/2ストリーミングだと送信ごとに毎回署名してるようなので効率は落ちるのでしょうかね :thinking:

[Node.js] 2021年版サービス アカウント認証コード with ドメイン全体の委任

$
0
0

はじめに

2020年10月に G Suite が Google Workspace にリブランドされました。
それとは全く関係ないですが Google Classroom や Google Chat などの Google Apis を Node.js 用クライアントである googleapisを使って呼び出す際に、サービスアカウントに対してドメイン全体の委任を行い OAuth 認証を行う必要があったのですが、情報を見つけるのに大分彷徨ってしまったり、見つけたコードの記法が少々古かったり、ポイントとなる subjectの指定方法がやや不適切だったりしたので備忘を兼ねてコードを残しておきます。

認証コード

一応 googleapis のバージョンは 67.0.0 です。古いバージョン(39.2.0とか)でも動きます。

const{google}=require('googleapis');constcredentials=require('./credentials.json');// サービスアカウント秘密鍵jsonファイル// ドメイン全体の委任で承認されている OAuth スコープの中から必要な分だけ指定するconstscopes=['https://www.googleapis.com/auth/classroom.courses.readonly'];// API 呼出し主体となるアカウントを指定。無指定だとパーミッションエラーになるconstclientOptions={subject:'great-teacher@my-domain.com'};asyncfunctionmain(){constauth=awaitgoogle.auth.getClient({credentials,scopes,clientOptions});constclassroom=google.classroom({version:'v1',auth});// api 単位ではなくグローバルオプションとして認証情報をセットする場合は下記のようにする// const classroom = google.classroom({ version: 'v1' });// google.options({ auth });constresponse=awaitclassroom.courses.list();console.log(JSON.stringify(response));}main().catch(err=>{console.error(err);process.exit(1);});

ポイント

getClient のパラメーターとして subjectを指定した clientOptionsを渡す点が重要です。
これを指定しないと 403 エラー (The caller does not have permission) が返ってきます。
逆に指定した場合はそのアカウントで認証して API を呼び出した場合と同じ結果が返ってきます。
例えば Classroom において教師A が課題1~10、教師B が課題11~20 を作成していた場合、
subjectに教師A のアカウントを指定して courses.courseWork.list()を実行すれば課題1~10が、
教師B のアカウントを指定して実行すれば課題11~20 が返ってきます。

また、ここでは requireを使って秘密鍵 Json ファイルをオブジェクトとして読み込んで credentialsパラメーターに渡していますが、下記のようにファイルパスを keyFileパラメーターに渡しても良いです。

constauth=awaitgoogle.auth.getClient({keyFile:'./credentials.json',scopes,clientOptions});

付録:サービスアカウント作成とドメイン全体の委任

一応この記事だけ見れば良いようにサービスアカウント作成と、そのアカウントに対するドメイン全体の委任の手順を記載しておきます。ドメイン全体の委任についてはGoogle Workspaceの管理者しか実施できず、説明するためのスクショを撮るのもままならなかったりしますので。

OAuth 同意画面の設定

API とサービス> OAuth 同意画面
ここで設定したアプリ名が Google 管理コンソールでドメイン全体の委任を設定した際に表示されます。

OAuth 同意画面 の設定手順

OAuth 同意画面

下記のとおり設定し「作成」または「保存して次へ」

  • UserType:目的に応じて選択
  • アプリ情報
    • アプリ名:適切な名前を入力
    • ユーザーサポートメール:プルダウンから選択
    • アプリのロゴ:空でOK。ある場合は指定
  • アプリのドメイン:全て空でOK。ある場合は指定
  • 承認済みドメイン:追加なしでOK
  • デベロッパーの連絡先情報
    • メールアドレス:適切に指定

oauth-001.png
oauth-002.png

スコープ

なにも設定せず「保存して次へ」
oauth-003.png

テストユーザー

なにも設定せず「保存して次へ」
oauth-004.png

サービスアカウントの準備

API とサービス> 認証情報

サービスアカウントの準備手順

サービスアカウント作成

+ 認証情報の作成から サービス アカウントを選択。
serviceaccount-001.png
下記のとおり設定して「完了」

  • サービス アカウント名:適当に(ここでは domain-delegation)
  • サービス アカウントID:自動設定
  • サービス アカウントの説明:空ないし任意の説明文

serviceaccount-002.png

ドメイン全体の委任を有効化

作成したサービスアカウントの編集ボタンをクリック。
serviceaccount-003.png

ドメイン全体の委任の表示をクリックして展開。
G Suite ドメイン全体の委任を有効にするにチェックを入れて「保存」
serviceaccount-004.png

これにより「OAuth 2.0 クライアント ID」が表示されたことを確認し、再度サービスアカウントの編集ボタンをクリック。
serviceaccount-005.png

鍵を追加プルダウンから 新しい鍵を作成をクリック。
キーのタイプとして「JSON」を選択して「作成」し、秘密鍵Jsonファイルをダウンロード。
このファイルがソースコード上で require('./credentials.json')により取り込んでいた json ファイル。
serviceaccount-006.png
serviceaccount-007.png
serviceaccount-008.png

このサービスアカウント用の OAuth クライアント ID は一覧のコピーボタンから取得する。
serviceaccount-009.png

ドメイン全体の委任の承認

Google 管理コンソール> セキュリティ

ドメイン全体の委任の承認手順

セキュリティ項目一番下の API の制御パネルをクリック。
workspace-001.png

API の制御項目の一番下の ドメイン全体の委任にある「ドメイン全体の委任を管理」をクリック。
workspace-002.png

API クライアント一覧にある「新しく追加」をクリック。
workspace-003.png

クライアント ID にサービスアカウントの OAuth クライアント ID、スコープに利用する Google APIs に必要なスコープを必要な数だけ入力して「承認」。
(カンマ区切りと書いてあるが、1 つ入力するたびにテキストボックスが増えていくので 1 つずつ入力すればよい)
workspace-004.png
workspace-005.png

おわりに

探し方が悪いのかサービスアカウント認証関連の公式ドキュメントを探しても subjectを指定しなきゃダメという情報を見つけられず、参考にさせて頂いた記事を見てようやく認証を通すことができました。
一応 subjectの指定方法は適切な形になっているはずですし、async/await 版でのサンプルにもなっていると思いますので参考になれば。

参考

【Node.js Sequalize】 Unable to find migration: エラーの解決法

$
0
0

マイグレーションの誤操作により、マイグレートをかけた時に、削除したマイグレーションファイルが引っかかってきて、下記の様なエラーが表示された場合の解決法。

ERROR: Unable to find migration: 20210105140820-create-user.js

一旦,migrationsディレクトリの中に表示されたファイルを作成する。

20210105140820-create-user.js
'use strict';module.exports={up:function(queryInterface,Sequelize){returnPromise.resolve()},down:function(queryInterface){returnPromise.resolve()}};

全てのマイグレーションをロールバックする。

npx sequelize-cli db:migrate:undo:all

先ほど作成したファイルを削除する。

マイグレートをかける。

npx sequelize-cli db:migrate --env development

以上

FirebaseのFunctionsで取り敢えずAPIを作ろう1

$
0
0

FirebaseのFunctionsでAPIを作ろう その1

GoogleのFirebaseってめちゃ便利ですよね,PHP派の私もいい加減あれこれ触ってます.
最近はNuxt使ってSSRアプリをHosting+Functionsにデプロイしたりしてますが
いい加減実用的なものを作るべき段になりました.
備忘録もかねて作業内容をつらつらします.

*今回はAPIまで行きません
*随所に偏見が入ってますが見逃してください.

~エミュレータの設定編

取り敢えずエミュレータの設定まで記事にします.
API的に動くところが一応見えます.

目的

APIの定義は非常に広いと思いますが,ここでは取り敢えずDBに接続してデータを入れたり貰ったりするサーバ側のなんかと定義します.
で,いい加減PHP以外でサーバレスで何とかしたいということで,FirebaseのFunctions使っていきます.
DBはGCPのCloud SQLのMySQL8系です.
今回はあんまり関係ないです.
そこまで進んだら続編出すなり続き書くなりします.

環境

OS:Windows10
エディタ(IDE):PHP Storm
因みにnpm派です.現代は一周回ってnpmです(偏見).yarnは捨てます.

前提

基本的に以下に沿って行きます.
https://firebase.google.com/docs/functions/get-started?hl=ja

Firebaseの設定

Firebaseのプロジェクトの作成とかです.
この辺は割愛します.
サイトからでもプロジェクトからでもいいです.
手順に沿って行くとFirestoreも使用するのでFunctionsとFirestoreも設定しておいてください.

ディレクトリ構成

取り敢えずプロジェクトのディレクトリを作ります.
できたら中に入って

npm install-g firebase-tools

特にpackage.jsonとか無いので見た目変化ないですがいいです.
で,

firebase login

この辺も経験ある人はいつも通り.
ログイン済みの人はいりません.

firebase init firestore

firebase init functions

因みに言語は「JavaScript」を選択.
申し訳ないけど「TypeScript」はめんどくさいだけです(偏見2).
複数人で開発するなら考えます.

デプロイファイル

デプロイする,つまり実際にアップロード?して使用するファイルは「functions」ディレクトリの下になります.
基本的には「functions/index.js」です.
こいつを手順に沿って書いていきます.
以下のようになります.

functions/index.js

constfunctions=require("firebase-functions");// The Firebase Admin SDK to access Cloud Firestore.constadmin=require('firebase-admin');admin.initializeApp();// このHTTPエンドポイントに渡されたテキストパラメーターを取得し、パス/ messages /:documentId / originalの下のCloudFirestoreに挿入しますexports.addMessage=functions.https.onRequest(async(req,res)=>{// テキストパラメータを取得します。constoriginal=req.query.text;// Firebase Admin SDKを使用して、新しいメッセージをCloudFirestoreにプッシュします。constwriteResult=awaitadmin.firestore().collection('messages').add({original:original});// メッセージの書き込みに成功したというメッセージを送り返しますres.json({result:`Message with ID: ${writeResult.id} added.`});});// / messages /:documentId / originalに追加された新しいメッセージをリッスンし、メッセージの大文字バージョンを/ messages /:documentId / uppercaseに作成しますexports.makeUppercase=functions.firestore.document('/messages/{documentId}').onCreate((snap,context)=>{// CloudFirestoreに書き込まれた内容の現在の値を取得します。constoriginal=snap.data().original;// `context.params`を使用してパラメータ` {documentId} `にアクセスしますfunctions.logger.log('Uppercasing',context.params.documentId,original);constuppercase=original.toUpperCase();// Cloud Firestoreへの書き込みなど、関数内で非同期タスクを実行する場合は、Promiseを返す必要があります。// Cloud Firestoreドキュメントに「大文字」フィールドを設定すると、Promiseが返されます。returnsnap.ref.set({uppercase},{merge:true});});

ざっと解説すると以下の二つの関数があります
・addMessage:多分HTTP GETして取得した値をFirestoreに突っ込みます
・makeUppercase:多分firestoreに突っ込まれた値をアッパーケースにしてFirestoreに突っ込みます

実行テストその1

さて,ローカル環境でテスト実行します.

firebase emulators:start

firebase emulators:start --only functions

いろんなサービスが入っている場合「--only functions」してあげないと他のサービスもエミュレートしようとして失敗して怒られます.

functions/に移動して

npm run serve

でもいいです.
「firebase emulators:start --only functions」が入ってます.

後,多分
http://localhost:5001/MY_PROJECT/us-central1/addMessage?text=uppercaseme
こんな感じでアクセスできます.
Firebaseのコンソールで確認するとFirestoreのmessagesに”uppercaseme”が入っているのが確認できます.

めでたしめでたし,と思っていたのか?

「addMessage」関数は動いてますが「makeUppercase」は動いていません.

https://firebase.google.com/docs/rules/emulator-setup
https://firebase.google.com/docs/emulator-suite/install_and_configure?hl=ja

この辺に書いてあります.
エミュレータの設定がいります.
で,Javaです.,,,Java?Javaナンデ?
なんで今どきJavaなんですか私はJavaを許しません(偏見3).

まあ,仕方なくJavaを入れます.
因みにこの段階でデプロイしてもちゃんと動くと思います.
でもテストせずにデプロイとか現実的にはあり得ないので,ちゃんと環境作ります.
個人的には自分のPCにJavaが入ってなくてびっくりです.
もうそういう時代っすよ.

Javaのインストールわからない方は以下のページを発見いたしましたのでご参照ください.
初学者の鬼門環境変数の何とやらです
https://masalib.hatenablog.com/entry/2020/11/28/131550#java%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB

エミュレータのインストール

firebase init emulators

これでインストール.
後で増やせばいいので一応FunctionsとFirestoreに絞るのが無難です.

再び以下にアクセス
http://localhost:5001/nexstimemanagementapi/us-central1/addMessage?text=uppercaseme

で,ちゃんと動いているかの確認ですが,
ちゃんとエミュレータが起動した後は本番のFirebaseには変化がありません.

http://localhost:4000
とかにローカルのエミュレータが起動しますのでそちらで確認してください.

以下のような感じ
image.png

めでたしめでたし

ちょと古いですが以下の記事なぞ参考になると思います.
https://qiita.com/HALU5071/items/e43729ac5b06b0506fbe

Repl.itでBotの機能を開発しまくったお話

$
0
0

:star:まずは皆様にご挨拶

皆様、初めまして!!:tada:
Repl.itでDiscordBotを開発している『姉だぁぁぁぁ』と申します。
私は、まだプログラミングを始めたばかりの学生で、このサイトを利用して投稿したりするのは本当に初めてですが、
良い記事の発行や、Botのプログラミング能力を高めたらと思い、このアカウントを作りました。よろしくお願いします。:bow:

では、本編に参りまっす!

:star:JSで覚醒した沢山の機能を作ってみる

:one:リアクションを使って、ページネーションを作成する。

ページネーションを早く、簡単に作成したい場合は下記のリンクを参照してください。
(InkoHXさん、ありがとうございます!)

リアクションを使って簡単にページネーションを作成する Discord.js 専用のパッケージ

↑このパッケージを使用することによって、Botのメッセージをリアクションのリアクションで、簡単にページネーションを作成する事ができます。

しかし、私は『できれば自分でページネーションを作成したい』と思ったので、数か月前にダウンロードしたソースコードを利用して、ようやくページネーションを作成することができました。
:star:▽▽▽そのソースコードは以下の通りです。▽▽▽

//ページネーションを作成client.on('message',asyncmessage=>{if(message.author.bot){return;}if(message.content===('!help')){constpages={//埋め込みでページネーションを作る時に形式を[Java]にしました。1:newdiscord.MessageEmbed().setTitle('(1/2)Title').setDescription("Helpコマンドっす").setAuthor(message.author.tag+'が実行したな!!',message.author.avatarURL()).setColor(16719653).setTimestamp(),2:newdiscord.MessageEmbed().setTitle('(2/2)Title').setDescription("EMBEDを作るのも https://leovoel.github.io/embed-visualizer/ を参照するといいと思う(多分)").setAuthor(message.author.tag+'が実行したな!!',message.author.avatarURL()).setColor(16719653).setTimestamp()};constoptions={limit:15*1000,min:1,max:2,page:1,pages:pages,};constawaitReactions=async(msg,m,options,filter)=>{awaitm.awaitReactions(filter,{max:1,time:360000,errors:["time"]}).then(async(collected)=>{//各リアクションをクリックした時の処理を作る↓↓↓constreaction=collected.first();if(reaction.emoji.name===""){awaitremoveReaction(m,msg,"");if(options.page!=options.min){options.page=options.page-1;awaitm.edit(options.pages[options.page]);}awaitawaitReactions(msg,m,options,filter);}elseif(reaction.emoji.name===""){awaitremoveReaction(m,msg,"");if(options.page!=options.max){options.page=options.page+1;awaitm.edit(options.pages[options.page]);}awaitawaitReactions(msg,m,options,filter);}else{awaitawaitReactions(msg,m,options,filter);}}).catch((e)=>console.log(e));};constremoveReaction=async(m,msg,emoji)=>{try{awaitm.reactions.cache.find((r)=>r.emoji.name==emoji).users.remove(msg.author.id);}catch(err){console.log(err);}};//メッセージを送信↓↓↓constm=awaitmessage.channel.send(options.pages[options.page]);//リアクションの並び順を作る↓↓↓awaitm.react("");awaitm.react("");//送信したメッセージにリアクション追加↓↓↓constfilter=(reaction,user)=>["",""].includes(reaction.emoji.name)&&user.id==message.author.id;awaitawaitReactions(message,m,options,filter);}});//ここまで

結構長いソースコードです…。
ですが、このソースコードを利用して任意のチャンネルにコマンドを実行すると、ちゃんと動いているんですよね…

実験画像
実験画像
この通りです♪

そんな感じですかね…
このソースコードを色んな風に使うと、様々な埋め込みをシンプルっぽくデザインすることができまっす。
リアクションが追加されない場合は、Botの権限の『リアクションの追加』をONにして下さい。


:two:YouTube検索機能を作成する。

:star:!youtube [検索ワード]でYouTubeにある動画を検索できるソースコードは、以下の通りです。

//ようつべ検索constyts=require('yt-search');//npmをインストールclient.on('message',asyncmessage=>{if(message.content.startsWith("!youtube")){if(message.author.bot){return;}constAKB=message.content.split("").slice(1).join("")//調べたい用語を入れなかった時のメッセージ↓↓↓if(!AKB)returnmessage.channel.send({embed:{color:16719653,description:'**調べたい用語を入れてね!**\n```使用例 : !youtube Discord```'}})//YouTubeで検索中の時のメッセージ↓↓↓constmsg=awaitmessage.channel.send({embed:{color:16719653,author:{name:"Youtube内で検索中なんだ!!ちょっとまてよぉぉ!!",},}});yts(AKB,function(err,r){constvideos=r.videosconstplaylists=r.playlists||r.listsconstchannels=r.channels||r.accounts//メッセージを編集して動画URLをうp↓↓↓msg.edit('✅動画が見つかったよぉぉぉ!!!',{embed:{color:16719653,timestamp:newDate(),author:{name:message.author.tag+'が実行したな!!',icon_url:message.author.avatarURL(),},title:videos[0].title,//動画のタイトルdescription:'**動画URL**\n> '+videos[0].url+'\n\n**  そうかそういう動画か**'}})})}});//ここまで

そんな感じですね。
埋め込みで動画URLとかを貼り付けるので、おしゃれにできるはずです。
わざわざYoutube.comに飛んで検索しなくて済むから、便利と言えば便利かも知れないね…
yt-searchは必ずインストールしないと、このソースコードは使えません!


:three:確認画面有りのKickコマンドを作る。

私が、Bot作る中で一番苦労したのは『確認画面が有るKickコマンド』を作る事でしたね…。
良いコマンドにしたくて色んなソースコードを組み合わせたものの、Runボタンを押したら…沢山の所からエラー吐いて来たので始末することにかなり時間が掛かりました。()
:star:▽▽▽そのソースコードは以下の通りです。▽▽▽

//Kickコマンドclient.on('message',asyncmessage=>{if(message.author.bot){return;}if(message.content.startsWith('!kick')&&message.guild){//コマンドを実行したユーザーの権限を確認↓↓↓if(!message.member.hasPermission('KICK_MEMBERS')){//Kickする権限が無ければメッセージを↓↓↓returnmessage.channel.send({embed:{color:16719653,description:'**Kickする権限ないよ!!**',}})}constuser=message.mentions.users.first();if(user){constmember=message.guild.member(user);//権限を持っている人がKickコマンドを実行したら確認画面を↓↓↓constmsg=awaitmessage.channel.send({embed:{color:16719653,timestamp:newDate(),author:{name:message.author.tag+'が実行したな!!',icon_url:message.author.avatarURL(),},description:'**本当にこれでいいのかよ!?**\n```diff\n+ [ok],[no]のどれかを送信してクレメンス\n+ ok → mentionしたメンバーをKick。\n+ no → mentionしたメンバーをKickせず、処理を停止するぜ。```'}});//回答による処理を作る↓↓↓constfilter=msg=>msg.author.id===message.author.idconstcollected=awaitmessage.channel.awaitMessages(filter,{max:1,time:300000})constresponse=collected.first()if(!response)returnmessage.channel.send('> **応答はやくしてよぉぉぉ!!!**')if(!['ok','no'].includes(response.content)){return;}//noを送信したら↓↓↓if(response.content==='no')returnmsg.edit({embed:{color:16719653,author:{name:'Kickの処理を止めたよ!!',},}});//okを送信したらKick!!↓↓↓if(response.content==='ok'){if(member){member.kick('監査ログに出されるやつ').then(()=>{msg.edit({embed:{color:16719653,timestamp:newDate(),author:{name:message.author.tag+'が実行したな!!',icon_url:message.author.avatarURL(),},description:'**キィィィィック!!!**\n**'+`${user.tag}`+'**をKickしたよ!!',}})})//エラーメッセージ↓↓↓.catch(err=>{msg.edit({embed:{color:16719653,description:'Kickできなかったよ!!権限確認してよぉぉ!!'}});console.error(err);});}else{//メンションされたユーザーがサーバーにいない時↓↓↓message.channel.send({embed:{color:16719653,description:'そいつここのサーバーにいないよぉぉ!!'}})};}}else{//対象を指定していない時↓↓↓message.channel.send({embed:{color:16719653,description:'**Kickしたいメンバーをメンションしてね!!**```使用例 : !#kick [@mention]```'}});}}});//ここまで

そんな感じですね。
これで、確認画面有りのKickコマンドが作れます。
確認画面の下の方のこれ↓↓↓

constfilter=msg=>msg.author.id===message.author.idconstcollected=awaitmessage.channel.awaitMessages(filter,{max:1,time:300000})constresponse=collected.first()if(!response)returnmessage.channel.send('> **応答はやくしてよぉぉぉ!!!**')if(!['ok','no'].includes(response.content)){return;}if(response.content==='no')returnmesssage.channel.send('なんでnoだにゃーん');if(response.content==='ok'){message.channel.send('んにゃ')}

結構色んなコマンドの所で使えるので、是非試してみて下さい!


:four:ステータスを変える方法

Botのステータス(カスタムステータス含む)を変えるには、下記のリンクを参照にするといいです。
(作成者様、ありがとうございます!)

Discord.js 例文 (v12.2.0)

:star:カスタムステータスの所のソースコードは、以下の通りです。

client.on('ready',message=>{console.log('準備完了っす!');client.user.setActivity('にゃーん',{type:"PLAYING"});/*
        typeの値:
            https://discord.js.org/#/docs/main/stable/class/ClientUser?scrollTo=setActivity
                'PLAYING': 〇〇 をプレイ中
                'STREAMING': 〇〇 を配信中
                'WATCHING': 〇〇 を視聴中
                'LISTENING': 〇〇 を再生中
        */});

カスタムステータスの所はこの通りに簡単に変えることができますが、『じゃあ、退席中等のステータスの所は?』と言いますと…それは、status値を使って変える事ができます。

status:"online"/*
                'online': オンライン
                'idle': 退席中
                'dnd': 邪魔しないで下さい
                'offline': オフライン
         ただしスマホステータスにしたい場合は別になる。
        */

これを入れればできると思います。
スマホステータスに変えたい場合は、クライアントを作るところをいじればできるはず。
:star:ソースコードは以下の通りです。

constclient=newdiscord.Client({ws:{properties:{$browser:"Discord iOS"}}});

これでしばらくすると、ステータスがスマホマークになります。

:star:最後に

結構沢山の所のねたばらしみたいな感じになってしまいましたが…私は、まだDiscord.jsのことをまだ完全に理解しているという訳ではないのでできれば大目に見てくれると幸いです。
そして、こんなjsのソースコードだらけの記事を最後まで見て下さり、誠にありがとうございました。

もし、最後まで見て「あれ?」と思う事がございましたら、この記事にコメントをするか、
Discordの[姉だぁぁぁぁ#1985]にDMを下さい。

できる限りの事はお答えします!
ありがとうございました。

Viewing all 8838 articles
Browse latest View live