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

【Node.js】Webpack + Express環境下でのpug(ejsでの可?)導入

$
0
0

pug導入前環境

Webpack + Express + React + Typescript

pug導入の理由

導入前はExpressでSSRをし,クライアントからのリクエストに対しres.send(html)で単純にhtmlスクリプトを送信するだけだった.これではデータベースから取得した値をクライアント側に渡すことができない.そのため,テンプレートエンジンであるpug(or ejs)を導入したかった.

やりたいこと

単純にルートにアクセスされた時にhtmlテンプレートに値を渡してレンダリングしたい.今回やりたいこととしては,レンダリングさえしてくれれば良いので,htmlとしてdistに出力されなくて良い(これをしようとするとWebpackでHtmlWebpackPluginが必要)

server.ts
importexpressfrom'express';constpostController=require('./controllers/postController');constapp=express();app.get('/',postController.doGetPostForSsr);//静的ファイルapp.use(express.static('dist'))app.listen(process.env.PORT||3000,function(){console.log('express app is started.');});
postController.ts
importexpressfrom'express';constPosts=require('../models/Posts');constssrController=require('./ssrController');module.exports={doGetPostForSsr:(req:express.Request,res:express.Response)=>{Posts.getPost().then((result)=>{constelements=ssrController.ssr(result);res.render("html",elements);}).catch((err)=>{console.log(err);})}}

エラー地獄

ただテンプレートエンジン導入するのに1日かかりました....webpack嫌いになりそうでしたけど,自分の力のなさですね,勉強になりました.
どこでどーゆーエラーが出たってのもストーリー立てて説明すると途方もないので,結論の導入方法だけ記述します.

pug導入

pug導入にはExpress側とWebpack側それぞれに設定が必要でした.

Express側pug導入

server.ts
importexpressfrom'express';constpostController=require('./controllers/postController');constapp=express();app.engine('pug',require('pug').__express);//追加app.set('view engine','pug');//追加app.get('/',postController.doGetPostForSsr);//静的ファイルapp.use(express.static('dist'))app.listen(process.env.PORT||3000,function(){console.log('express app is started.');});

これでExpressがpugを認識できるようになり,読み込んでくれるviewsディレクトリがルート直下に設定される?ぽいです.

Webpack側pug導入

npm install webpack-node-externals --save-dev
webpack.server.config.js
constnodeExternals=require('webpack-node-externals');module.exports={...externals:[nodeExternals()],...}

これがないとよくわかりませんが,buildは問題なく通りますがアクセス時にCannot find module "../lib/utils.js"ってエラーが出るんですよね.これは【webpack】Cannot find module "../lib/utils.js"を参考にしました.

おわりに

今回の目的はpugコンパイルからのhtmlの出力ではなく,単なるレンダリングでしたので,pug-loaderは使いませんでしたが,いろいろいじっていたところ,htmlの出力をする場合はhtml-webpack-plugin, pug, pug-loader使ってwebpackで設定したらできました.これは調べたらすぐ出てくるのでここに書く必要はないかと.
HTMLもわからない状態から独学を初めて1年と2ヶ月程経つのかな?最初は全てフルスクラッチで記述していたので,最近やっとフレームワークの恩恵とMVCモデルの素晴らしさを理解することができてきました.(最初はサーバーサイドでjs動くってのが衝撃的だったの覚えてますね笑)これからはちょっとはインフラの方も理解できたらなと思います〜.


Node.js + ibm_db で Db2 に接続してSELECT文を実行する

$
0
0

Node.jsでDb2にアクセスしてみました。
Db2導入済みのLinux環境に、Node.js/ibm_dbをインストールするところからメモを残します。

Node.jsからDb2にアクセスするための前提

  • Db2サーババージョンがV10.5.0.4以上であること
  • クライアントバージョンは制限なし

環境

項目バージョン
LinuxCentOS Linux 7 (Core)
npm6.14.4
ibm_db2.6.4
Db211.5.0

Node.js のセットアップ

1. Node.js インストール・イメージのダウンロード

Windowsにいったんダウンロードして、LinuxへWinSCPで転送。
https://nodejs.org/en/download/

2. ファイル展開

(権限の都合上、rootユーザで実行)
どのディレクトリに展開するかは環境ごとのルールに従うと良さそうですが
個人で使える環境なので、適当なところに展開。

cd /opt
tar xvzf /tmp/node-v12.16.2-linux-x64.tar.gz

3. シンボリックリンク作成

(権限の都合上、rootユーザで実行)

ln -s node-v12.16.2-linux-x64 node

4. パス設定

node、npm等のコマンドにパスを通すため、以下3つの環境変数をexportしておく。
都度やるのも面倒なので Node.jsアプリを動かすユーザの.bashrc に記述。

export NODE_HOME=/opt/node
export NODE_PATH=$NODE_HOME/lib/node_modules
export PATH=$NODE_HOME/bin:$PATH

5. 初期化

ibm_db をインストールする前に、カレントディレクトリにpackage.jsonを作成。
対話式コマンドで色々聞かれますが、他の方々のQiita等見ていると全部EnterでもOKらしいので、ここは素直に全Enter。

npm init

ibm_db ドライバーのインストール

インストール済みのドライバーを使わせたい場合にはIBM_DB_HOME環境変数でドライバーインストールパスを設定しておくと、CLIドライバのダウンロードは回避されてローカルのドライバが使われるようです。
IBM_DB_HOME環境変数を設定せず npm install コマンドを実行すると、最新バージョンのドライバが都度ダウンロードされます。

npm install ibm_db

標準出力:

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (build)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /database/config/db2inst1/node_modules/ibm_db/build/package.json:

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


Is this OK? (yes)
[db2inst1@7471dc6b3181 build]$
[db2inst1@7471dc6b3181 build]$
[db2inst1@7471dc6b3181 build]$ npm install ibm_db
npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142

> ibm_db@2.6.4 install /database/config/db2inst1/node_modules/ibm_db/build/node_modules/ibm_db
> node installer/driverInstall.js

platform =  linux , arch =  x64 , node.js version =  v12.16.2
make version = GNU Make 3.82
Downloading DB2 ODBC CLI Driver from https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/linuxx64_odbc_cli.tar.gz...

42.67% | 9011200 bytes downloaded out of 21119049 bytes.
100.00% | 21119049 bytes downloaded out of 21119049 bytes.

****************************************
You are downloading a package which includes the Node.js module for IBM DB2/Informix.  The module is licensed under the Apache License 2.0. The package also includes IBM ODBC and CLI Driver from IBM, which is automatically downloaded as the node module is installed on your system/device. The license agreement to the IBM ODBC and CLI Driver is available in undefined   Check for additional dependencies, which may come with their own license agreement(s). Your use of the components of the package and dependencies constitutes your acceptance of their respective license agreements. If you do not accept the terms of any license agreement(s), then delete the relevant component(s) from your device.
****************************************

Downloading and extraction of DB2 ODBC CLI Driver completed successfully ...

make: Entering directory `/database/config/db2inst1/node_modules/ibm_db/build/node_modules/ibm_db/build'
  CXX(target) Release/obj.target/odbc_bindings/src/odbc.o
  CXX(target) Release/obj.target/odbc_bindings/src/odbc_connection.o
  CXX(target) Release/obj.target/odbc_bindings/src/odbc_statement.o
  CXX(target) Release/obj.target/odbc_bindings/src/odbc_result.o
  SOLINK_MODULE(target) Release/obj.target/odbc_bindings.node
  COPY Release/odbc_bindings.node
make: Leaving directory `/database/config/db2inst1/node_modules/ibm_db/build/node_modules/ibm_db/build'

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN build@1.0.0 No description
npm WARN build@1.0.0 No repository field.

+ ibm_db@2.6.4
added 102 packages from 96 contributors and audited 184 packages in 73.863s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities

WARNING(WARN) が2つ出るのは、導入失敗というわけではなくて
npm init時にDescriptionなどちゃんと入力すれば出なくなります。(今回は全項目で空Enter)
次はこれを試してみたいなと思います---> Qiita「面倒なのでnpm initを実行しない」

(備忘録:IBM_DB_HOME環境変数を設定した状態では、このようなメッセージが出力される)

Downloading of clidriver skipped - build is in progress...

コード

Db2データベースに接続して、SELECTを行うプログラム。

接続情報(DBサーバホスト名、ポート番号、DB名、ユーザ名、パスワード)は
変更に対応しやすいよう外出しにします。
(※本来パスワードを平文ファイルに記述するべきではありませんがお試しということで。)

settings.js
exports.host='localhost';exports.port=50000;exports.dbname='TESTDB';exports.username='db2inst1';exports.password='xsw23edc';
test1.js
varibm_db=require('ibm_db');varsettings=require('./settings');vardb_con_str="DRIVER={DB2}"+";DATABASE="+settings.dbname+";HOSTNAME="+settings.host+";PORT="+settings.port+";PROTOCOL=TCPIP"+";UID="+settings.username+";PWD="+settings.password;varsql_str="select C1, C2 from T3";ibm_db.open(db_con_str,function(err,conn){if(err)returnconsole.log(err);conn.query(sql_str,function(err,data){if(err)console.log(err);console.log(data);conn.close(function(){console.log('done');});});});

実行結果

SELECTできました。

$ node test1.js
[
  { C1: 1, C2: 'a       ' },
  { C1: 2, C2: 'b       ' },
  { C1: 10001, C2: 'A10001  ' }
]
done
$

Node.js + ibm_db + Db2 で、プレースホルダを用いたSQLを実行する

$
0
0

プレースホルダを用いたSQLを使って、Db2にアクセスします。

SQLの条件指定を固定値(リテラル)で記述すると、指定される条件が変わるごとに新しいSQLとしてコンパイルされることになり、where条件だけが異なる同じSQLでパッケージ・キャッシュがあふれてしまいます。コンパイルにかかる時間ももったいない。
そうならないよう、可変となる値の部分だけ(場合によっては列名なども)、疑問符「?」で表したSQLで記述することができます。JDBCなどではパラメーターマーカーと呼ばれますが、Node.jsではプレースホルダと表現されるようです。

ここから先は、Db2を使う場合に、プレースホルダにどうやって値を渡すかのメモです。

プレースホルダーを用いたSQL実行例

ibm_dbのqueryでは、値を配列として渡します。

test2.js
varibm_db=require('ibm_db');varsettings=require('./settings');vardb_con_str="DRIVER={DB2}"+";DATABASE="+settings.dbname+";HOSTNAME="+settings.host+";PORT="+settings.port+";PROTOCOL=TCPIP"+";UID="+settings.username+";PWD="+settings.password;varsql_str="select C1, C2 from db2inst1.T3 where C1=?";varparam1=[1];  ←ココで、「C1=?」の「?」に与える値を記述ibm_db.open(db_con_str,function(err,conn){if(err)returnconsole.log(err);conn.query(sql_str,param1,function(err,data){if(err)console.log(err);console.log(data);conn.close(function(){console.log('done');});});});

実行結果

$ node test2.js
[ { C1: 1, C2: 'a       ' } ]
done

なお、今回指定した値1は、データベース上は数値列(Integer)ですが、配列には文字列として記述してもちゃんと動きます。

var sql_str = "select C1, C2 from db2inst1.T3 where C1=?";
var param1 = ["1"];

失敗ケース その1(数値として渡した場合)

他のDBで検証されている例を見ていると、ただの数値でも問題なさそうなのですが
ibm_db では失敗します。

抜粋(SQL/プレースホルダ)
varsql_str="select C1, C2 from T3 where C1=?";varparam1=1;

実行結果:

$ node test2.js
[Error: [IBM][CLI Driver] CLI0100E  Wrong number of parameters. SQLSTATE=07001] {
  error: '[node-ibm_db] SQL_ERROR',
  sqlcode: -99999,
  message: '[IBM][CLI Driver] CLI0100E  Wrong number of parameters. SQLSTATE=07001',
  state: '07001'
}
[]
done

失敗ケース その2(文字列として渡した場合)

文字列としてダブルクオートで囲むパターンも試しましたがうまくいかず。
※ "1" ではなく ["1"] と記述し、文字列配列として指定すれば成功します

抜粋(SQL/プレースホルダ)
varsql_str="select C1, C2 from T3 where C1=?";varparam1="1";

実行結果:

node test2.js
/database/config/db2inst1/node_modules/ibm_db/lib/odbc.js:600
        self.conn.query(query, params, cbQuery);
                  ^

TypeError: Argument 1 must be an Array.
    at /database/config/db2inst1/node_modules/ibm_db/lib/odbc.js:600:19
    at SimpleQueue.next (/database/config/db2inst1/node_modules/ibm_db/lib/simple-queue.js:34:5)
    at SimpleQueue.maybeNext (/database/config/db2inst1/node_modules/ibm_db/lib/simple-queue.js:22:10)
    at SimpleQueue.push (/database/config/db2inst1/node_modules/ibm_db/lib/simple-queue.js:15:8)
    at Database.query (/database/config/db2inst1/node_modules/ibm_db/lib/odbc.js:470:14)
    at /database/config/db2inst1/work/node/test2.js:19:8
    at /database/config/db2inst1/node_modules/ibm_db/lib/odbc.js:111:11
    at /database/config/db2inst1/node_modules/ibm_db/lib/odbc.js:333:11

Node.jsのモジュール解決プロセス(和訳)

$
0
0

Node.jsのrequireがどのようにモジュールを探すか、そのプロセスを説明した下記公式ドキュメントの和訳です。

内容に誤りがあればお教えください。

Node.jsのモジュール解決プロセス

require(X) をパス Y にあるモジュールで実行したとき、

  1. もし、 X がコアモジュールなら、
    • a. コアモジュールを返す
    • b. 終了
  2. もし、 X が "/" で始まるなら、
    • a. Y のパスをファイルシステムルートにセットしなおす
  3. もし、 X が "./"、"/"、"../"のどれかで始まるなら、
  4. LOAD_SELF_REFERENCE(X, dirname(Y))
  5. LOAD_NODE_MODULES(X, dirname(Y))
  6. 例外"not found"を投げる

LOAD_AS_FILE(X)

  1. もし、 X がファイルなら、 X をそのファイル拡張子の形式としてロードする。 終了
  2. もし、 X.js がファイルなら、 X.js をJavaScriptテキストとしてロードする。 終了
  3. もし、 X.json がファイルなら、 X.json をパースしてJavaScriptオブジェクトにする。 終了
  4. もし、 X.node がファイルなら、 X.node をバイナリアドオンとしてロードする。 終了

LOAD_INDEX(X)

  1. もし、 X/index.js がファイルなら、 X/index.js をJavaScriptテキストとしてロードする。 終了
  2. もし、 X/index.json がファイルなら、 X/index.json をパースしてJavaScriptオブジェクトにする。 終了
  3. もし、 X/index.node がファイルなら、 X/index.node をバイナリアドオンとしてロードする。 終了

LOAD_AS_DIRECTORY(X)

  1. もし、 X/package.json がファイルなら、
    • a. X/package.json をパースして、mainフィールドを探す。
    • b. もし、mainがfalsyな値なら、2に進む
    • c. let M = X + main
    • d. LOAD_AS_FILE(M)
    • e. LOAD_INDEX(M)
    • f. LOAD_INDEX(X) 非推奨
    • g. 例外"not found"を投げる
  2. LOAD_INDEX(X)

LOAD_NODE_MODULES(X, START)

  1. let DIRS = NODE_MODULES_PATHS(START)
  2. for each DIR in DIRS:

NODE_MODULES_PATHS(START)

1.letPARTS=pathsplit(START)2.letI=countofPARTS-13.letDIRS=[GLOBAL_FOLDERS]4.whileI>=0,a.ifPARTS[I]="node_modules"CONTINUEb.DIR=pathjoin(PARTS[0..I]+"node_modules")c.DIRS=DIRS+DIRd.letI=I-15.returnDIRS

これは、NODE_MODULES_PATHS('/root/a/b/c')を与えたときに、下記のリストを返す処理です。

['$HOME/.node_modules','$HOME/.node_libraries','$PREFIX/lib/node','/root/a/b/c/node_modules','/root/a/b/node_modules','/root/a/node_modules','/root/node_modules','/node_modules',]

LOAD_SELF_REFERENCE(X, START)

  1. STARTに最も近いパッケージのスコープを探す。
  2. もし、スコープが見つからなければ、return。
  3. もし、package.jsonに"exports"がなければ、return。
  4. もし、package.jsonの name が X で始まらないなら、"not found"例外を投げる。
  5. それ以外の場合は、このパッケージに相対的な X の残りの部分を、package.jsonのnameでLOAD_NODE_MODULESを介してロードされたかのようにロードする。

LOAD_PACKAGE_EXPORTS(DIR, X)

このプロセスは右記の規格に対応するものです→jkrems/proposal-pkg-exports: Proposal for Bare Module Specifier Resolution in node.js

  1. X を名前とサブパスの組み合わせとしての解釈を試みる。その名前は @scopeにスラッシュ(/)で始まるサブパスが続く形式を想定する。
  2. もし、 X このパターンにマッチしない、もしくは、DIR/名前/package.jsonがファイルでないなら、return。
  3. DIR/name/package.json をパースして、 "exports" フィールドを探す。
  4. もし、 "exports" が null か undefined なら、return。
  5. もし、 "exports" が object での場合、"." 始まりのキーがありつつ "." 始まりでないキーもあるなら、"invalid config"例外を投げる。
  6. もし、 "exports" が string または "." 始まりのキーが1つもない object なら、それの値を "." として扱う。
  7. もし、 サブパスが "." で "exports" が "." エントリーを持たないなら、return
  8. "exports" の中からサブパスで始まる最も長いキーを探す。
  9. もし、キーが見つからないなら、 "not found" 例外を投げる。
  10. let RESOLVED = fileURLToPath(PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name), exports[key], subpath.slice(key.length), ["node", "require"])) (これはESMリゾルバーで定義されたもの)
  11. もしキーが "/" で終わるなら:
  12. それ以外の場合は、
    • a. もし RESOLVED がファイルなら、そのファイル拡張子フォーマットでロードする。 終了
  13. "not found"例外を投げる

Node jsとESP32を使ってLEDをリアルタイムに制御する

$
0
0

はじめに

開発環境

node.js 12以降
npm 6以降
Arduino core for the ESP32 1.04以降

作るもの

スマホ等のブラウザからnode.jsを通してESP32にLEDの制御信号を送り,ESP32でリアルタイムにLEDを制御します。
またスマホからnode.jsサーバーにはwebsocketを、node.jsからESP32にはMQTTを用います。

対象読者

node.jsを触ったことのある人
ESP32にコードを書き込める人
フルカラーLEDを持っている人

node.js側の実装

まず以下のコマンドを実行する

$mkdir 好きな名前 
$cd さっき決めた名前
$npm init (質問に答える エンター連打でもヨシ!)

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

して以下のコマンドを実行ください

$npm install express mosca mqtt socket.io  -s

サーバーのコードを作成

index.jsを作成して以下のコードを書き込む

index.js
//各パッケージの読み込みconstmosca=require('mosca')varexpress=require('express');varapp=express();constserver=newmosca.Server()//mqttブローカーを定義varwebserver=require('http').createServer(app);//webサーバーを定義app.get("/",function(request,response){response.sendFile(__dirname+'/index.html');});webserver.listen(3000);//ポート3000でサーバーを開始vario=require('socket.io').listen(webserver);server.on('ready',()=>console.log('server started'))server.on('clientConnected',client=>console.log(`client connected: ${client.id}`))server.on('published',(packet)=>constmqtt=require('mqtt')constclient=mqtt.connect('mqtt://localhost')client.on('message',(topic,msg)=>{console.log(`topic: ${topic}, msg: ${msg}`)})io.on('connection',function(socket){socket.on('red',function(msg)//赤LEDの値が届いたらその値をESP32に送信する{console.log("red="+msg);client.publish('esp32/red',msg);});socket.on('blue',function(msg)//青LEDの値が届いたらその値をESP32に送信する{console.log("blue="+msg);client.publish('esp32/blue',msg);});socket.on('green',function(msg)//緑LEDの値が届いたらその値をESP32に送信する{console.log("green="+msg);client.publish('esp32/green',msg);});});

htmlファイルを作成する

index.html を作成して以下のコードを書き込む

index.html
<!DOCTYPE html><html><head><metacharset="utf-8"><metaname="viewport"content="width=device-width, initial-scale=1"><title>ESP32</title><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script><linkrel="stylesheet"href="//cdn.rawgit.com/milligram/milligram/master/dist/milligram.min.css"><style>body{max-width:100%;}.slider{width:300px;}</style></head><body><pid="redp">red=50</p><inputtype="range"value="50"min="0"max="100"step="1"id="redrange"class="slider"><pid="bluep">blue=50</p><inputtype="range"value="50"min="0"max="100"step="1"id="bluerange"class="slider"><pid="greenp">green=50</p><inputtype="range"value="50"min="0"max="100"step="1"id="greenrange"class="slider"><script>constsocket=io();varredrange=document.getElementById("redrange");varredp=document.getElementById("redp");varinterval=window.setInterval(sendcolor,25);varred=0,blue=0,green=0;varoldred=0,oldblue=0,oldgreen=0;redrange.oninput=function(){redp.innerHTML="red="+this.value;red=this.value}varbluerange=document.getElementById("bluerange");varbluep=document.getElementById("bluep");bluerange.oninput=function(){bluep.innerHTML="blue="+this.value;blue=this.value}vargreenrange=document.getElementById("greenrange");vargreenp=document.getElementById("greenp");greenrange.oninput=function(){greenp.innerHTML="green="+this.value;green=this.value}functionsendcolor(){if(red!=oldred){oldred=red;socket.emit('red',red);}if(blue!=oldblue){oldblue=blue;socket.emit('blue',blue);}if(green!=oldgreen){oldgreen=green;socket.emit('green',green);}}</script></body></html>

ESP32にコードを書き込む

まずhttps://github.com/knolleary/pubsubclient
よりPubSubClientライブラリを入れて以下のコードを書き込む

#include <WiFi.h>
#include <PubSubClient.h>
constcharssid[]="あなたの家のSSID";constcharpasswd[]="あなたの家のpasswd";constchar*mqtt_server="mqttブローカー(nodejsサーバー)のIPアドレス";WiFiClientespClient;PubSubClientclient(espClient);longlastMsg=0;charmsg[50];intvalue=0;intred=0,blue=0,green=0;constintledR=A18;constintledG=A4;constintledB=A5;voidsetup(){ledcSetup(0,12800,8);ledcAttachPin(ledR,0);ledcSetup(1,12800,8);ledcAttachPin(ledG,1);ledcSetup(2,12800,8);ledcAttachPin(ledB,2);Serial.begin(115200);connectWiFi();client.setServer(mqtt_server,1883);client.setCallback(callback);}voidloop(){if(WiFi.status()==WL_DISCONNECTED){connectWiFi();}if(!client.connected()){reconnect();}client.loop();//client.publish("esp32/temperature", "ddd");}voidconnectWiFi(){WiFi.begin(ssid,passwd);Serial.print("WiFi connecting...");while(WiFi.status()!=WL_CONNECTED){Serial.print(".");delay(100);}Serial.print(" connected. ");Serial.println(WiFi.localIP());}voidcallback(char*topic,byte*message,unsignedintlength){Serial.print("Message arrived on topic: ");Serial.print(topic);Serial.print(". Message: ");StringmessageTemp;for(inti=0;i<length;i++){Serial.print((char)message[i]);messageTemp+=(char)message[i];}Serial.println();if(String(topic)=="esp32/red"){red=messageTemp.toInt()*255/100;}if(String(topic)=="esp32/blue"){blue=messageTemp.toInt()*255/100;}if(String(topic)=="esp32/green"){green=messageTemp.toInt()*255/100;}ledcontrol();}voidreconnect(){// Loop until we're reconnectedwhile(!client.connected()){Serial.println("Connecting to MQTT...");StringclientId="ESP32-"+String(random(0xffff),HEX);if(client.connect(clientId.c_str())){Serial.println("connected");// Subscribeclient.subscribe("esp32/red");client.subscribe("esp32/blue");client.subscribe("esp32/green");}delay(1000);randomSeed(micros());}}voidledcontrol(){Serial.println(red);ledcWrite(0,red);ledcWrite(1,blue);ledcWrite(2,green);}

ESP32に配線をする

フルカラーLEDをそれぞれ32,33,25に接続する

起動する

まず以下のコマンドを実行する

$node index.js

次にESP32に電源を入れる
ブラウザから サーバーのIP:3000 にアクセスする

以上です。

終わりに

初めてのQiitaなので見るに堪えないところがあればご指摘お願いします。
また今日使ったコードはgithubにあげています。
https://github.com/iotConnecter/iotprototypes

参考サイト

https://wak-tech.com/archives/752
https://qiita.com/hilucky/items/0e394760a1445593cea5

在宅ワーク中に会議中サインをobnizとLINEBotで作ってみた deploy編

$
0
0

はじめに

この記事は前回在宅ワーク中に会議中だよサインをobnizとLINEBotで作ってみた - Qiitaのなかで外部Deployができなかったので前回のコードを元にHerokuにDeployしました。
deployとは別のところで一部未完成です。

概要

「今オンラインミーティング中!!!!ノックしないでー、開けないでー」みたいなことで気まずい思いをしたことが増えてきたので、いわゆる トイレの空き情報「空」 みたいなサイネージが欲しくて作りました。

LINEBotに「開けないで」といれると obnizのディスプレイに 『 × 』 を描画し
「終わったよ」 といれるとけしてくれます。
 

できたもの

(ngrokからHerokuにかわりましたが、よく考えると見た目は前回同じだった。。。)
IMG_20200415_214353-COLLAGE (2).jpg

おともだちになってね

使い方

  • (会議が始まったら)LINEBotに「開けないで」
    っていれると 私の部屋にあるobnizに 「 × 」マークが出ます

  • (会議が終わったら)LINEBotに「終わったよ」
    っていれると 私の部屋にあるobnizの 「 × 」マークが消えます

環境

Node.js v13.7.0
MacBook Pro macOS Mojave
Visual Studio Code 1.44.0
使用部材
obniz

node-canvasのために ➀

obnizのディスプレイに描画するために 「node-canvas」をつかいます。
文字を出すだけであれば
この子の取り扱いが厄介で、はまりまくりました。
node-canvasのインストールは、

「npm install canvas」

とするだけですが、その前に cairo がインストールされていなければなりません。
cairoのインストールの準備をします。

heroku buildpacks:set https://github.com/ddollar/heroku-buildpack-multi.git

.buildpacks ファイルを作ります。

cat << EOF > .buildpacks
https://github.com/mojodna/heroku-buildpack-cairo.git
https://github.com/heroku/heroku-buildpack-nodejs.git
EOF

このふたつをやったあとに

npm install canvas

しましょう!!!

コード

node.js
'use strict';// obniz呼び出しvarObniz=require('obniz');varobniz=newObniz("***");// Obniz_ID に自分のIDを入れますconstexpress=require('express');constline=require('@line/bot-sdk');constPORT=process.env.PORT||8080;letmatrix;const{createCanvas}=require('canvas')constcanvas=createCanvas(128,64);constctx=canvas.getContext('2d');constconfig={channelAccessToken:'***',channelSecret:'***'};constapp=express();app.get('/',(req,res)=>res.send('Hello LINE BOT!(GET)'));app.post('/webhook',line.middleware(config),(req,res)=>{console.log(req.body.events);Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});constclient=newline.Client(config);// obniz接続obniz.onconnect=asyncfunction(){obniz.display.clear();obniz.display.print("HELLO");}asyncfunctionhandleEvent(event){letlinemes=event.message.text;if(event.type!=='message'||event.message.type!=='text'){returnPromise.resolve(null);}letmes=event.message.text;if(event.message.text==='開けないで'){mes='表示を出すよ';//待ってねってメッセージだけ先に処理 obniz.display.clear();getAskObnizTemp(event.source.userId);}elseif(event.message.text==='終わったよ'){obniz.display.clear();mes="はーい"clearmes(event.source.userId);}else{obniz.display.clear();getlinemes(event.source.userId,linemes);}returnclient.replyMessage(event.replyToken,{type:'text',text:mes});}constgetlinemes=async(userId,linemes)=>{// ディスプレイをクリアにできないので黒い四角を書く// obniz.display.setColor('#000000');// obniz.display.rect(0, 0, 127, 127, true);ctx.fillStyle="white";ctx.font="20px sans-serif";ctx.fillText(linemes,0,60);obniz.display.clear();obniz.display.draw(ctx);obniz.display.print(linemes);awaitclient.pushMessage(userId,{type:'text',text:`${linemes}にしたよ`,});}constgetAskObnizTemp=async(userId)=>{ctx.strokeStyle='rgba(255,255,255,1)'ctx.beginPath()ctx.lineTo(0,0)ctx.lineTo(127,63)ctx.stroke()ctx.beginPath()ctx.lineTo(127,0)ctx.lineTo(0,63)ctx.stroke()obniz.display.draw(ctx);awaitclient.pushMessage(userId,{type:'text',text:"バツにしたよ",});}constclearmes=async(userId)=>{obniz.display.clear();awaitclient.pushMessage(userId,{type:'text',text:`けしたよ`,});}// app.listen(8080);app.listen(process.env.PORT||8080);console.log(`Server running at ${PORT}`);

node-canvasのために ②

上記コードをHerokuにデプロイします。(ちょっと普通じゃない)

git add --a
git commit -m "firstcommit" 
git push heroku master

ここでエラーのような怖い見た目とともに rejected とでてきます。

aaa.png

error とかでていますが、アプリケーションを実行させます。

heroku ps:scale web=1

アプリケーションにアクセスします

heroku open

スクリーンショット 2020-04-15 23.26.32.png
ブラウザが立ち上がってデプロイできました!!!!!!!!!!!!!!


動いていないところ

  1. Herokuにもっていくと、ngrokでは動いていた、 LINEBotに 入力した文字を ディスプレイに出す処理がごかなくなってしまいました。 (LINEの方にはおうむ返しが来るのですが)
node.js
constgetlinemes=async(userId,linemes)=>{// ディスプレイをクリアにできないので黒い四角を書くobniz.display.setColor('#000000');obniz.display.rect(0,0,127,127,true);ctx.fillStyle="white";ctx.font="20px sans-serif";ctx.fillText(linemes,0,60);obniz.display.clear();//drawもprintもきかないobniz.display.draw(ctx);obniz.display.print(linemes);awaitclient.pushMessage(userId,{type:'text',text:`${linemes}にしたよ`,});}

2.ディスプレイがクリアにできない
obniz.display.clear();がうごいてくれず、
obniz.display.print();も動かず。何が足りないのでしょう。

3.クリアにできないため黒い四角で塗りつぶす
白色で試してみたけど、なんか一瞬出たりしたりしたような。。。

参考サイト

heroku open
node-canvas をインストールしてみました - ふにょい日記
Automattic/node-canvas: Node canvas is a Cairo backed Canvas implementation for NodeJS.

感想

obniz難しい。

「うちでヨガしよう」ヨガポーズがあっているか判定してくれるLINE bot~Node.jsに読み込む~

$
0
0

概要

 コロナウイルの影響で外出自粛、在宅ワークをされている方が多いと思います。普段よりも運動量が減ってしまうので、何かできないかと考えてヨガを支援するLINE botを開発しています。

やりたいこと

 機能としては、お題のポーズ写真がbotから送られてくるので、そのポーズをとった自分の写真をbotに送ると正しいポーズか判定してくれます。

 まずはLINE botアカウントだけ作りました。
image.png

実装

 学習モデルの作成は次の2つの記事で解説しています。
Googleが提供しているサービス「Teachable Machine」でヨガポーズの学習モデルを作って遊んでみた
Teachable Machineで作成したモデルを使用する方法(webブラウザ編)

 この記事ではNode.jsにTeachable Machineのライブラリをインストールする方法を記載してます。

必要なライブラリは次のコマンドでインストールできます。

npmi@tensorflow/tfjs
npmi@teachablemachine/pose

で、次のようにrequireします。

consttmPose=require('@teachablemachine/pose');

この記事のメイン部分はおわりにします。

が、このライブラりを使うにあたり、node-fetchが必要なようで、インストールしてrequireしてもNot defineエラーが出てしまいます....。

学習モデルをネットからロードする処理になっていそうなので、ローカルに落としてくると改善されそうです。

破壊するかどうか

$
0
0

ほぼ備忘録。よく使うメソッドがArrayを破壊するかどうか
ググるときは日本語なら「配列 [メソッド名] 破壊的」など、英語なら"array [メソッド名] mutating"あたりがよい

破壊する

  • fill()
  • pop()
  • push()
  • reverse()
  • shift()
  • sort()
  • splice()
  • unshift()

破壊しない

  • concat()
  • entries()
  • every()
  • filter()
  • find()
  • forEach()
  • includes()
  • indexOf()
  • join()
  • keys()
  • map()
  • reduce()
  • some()

参考情報

  • 手元の環境はNode v12.16.1
  • Array - JavaScript | MDN:built-inメソッドの一覧、ブラウザ・Nodeのバージョンごとの対応状況も載ってる
  • Does it mutate?:サンプルコードなどをまとめたサイト

npm install でエラー。gyp: No Xcode or CLT version detected! 

$
0
0

npmを使ってみる

今回初めて、Node.jsを使うためにセットアップ!
と思っていたのですが、いきなりエラー続きで意味も分からず苦戦していたので共有します。
皆様が同じ目に合いませんように。

$ cd work/01-webpack 

まずは、フォルダを作成

$ npm init -y

そして、package.jsonファイルを作成を作成しました。
ここまでは、良かったのですが。。。
アプリケーションで使用するモジュールをインストールするために、webpackをインストールする時に

$ npm install webpack webpack-cli --save-dev

を行ったあと、エラーが。

No receipt for 'com.apple.pkg.CLTools_Executables' found at '/'.

No receipt for 'com.apple.pkg.DeveloperToolsCLILeo' found at '/'.

No receipt for 'com.apple.pkg.DeveloperToolsCLI' found at '/'.

gyp: No Xcode or CLT version detected! ←ココ
gyp ERR! configure error 
gyp ERR! stack Error: `gyp` failed with exit code: 1
gyp ERR! stack     at ChildProcess.onCpExit (/Users/mac/.nodebrew/node/v12.16.2/lib/node_modules/npm/node_modules/node-gyp/lib/configure.js:351:16)
gyp ERR! stack     at ChildProcess.emit (events.js:310:20)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (internal/child_process.js:275:12)
gyp ERR! System Darwin 19.3.0
gyp ERR! command "/Users/mac/.nodebrew/node/v12.16.2/bin/node" "/Users/mac/.nodebrew/node/v12.16.2/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"
gyp ERR! cwd /Users/mac/Desktop/work/01-webpack/node_modules/fsevents
gyp ERR! node -v v12.16.2
gyp ERR! node-gyp -v v5.1.0
gyp ERR! not ok 

そして気になった場所はココですね。

gyp: No Xcode or CLT version detected!

XcodeとCLTのバージョンがおかしい?

Xcodeは以前にインストールしていたので確認してみました。
すると、久しくみていなかったので、アップデートしていませんでした。
それが、原因かもと思いアップデート!!
そしてもう一度$ npm install webpack webpack-cli --save-dev

しかし、エラーは変わらず。。

※インストールしていない方はこれで

$ xcode-select --install

解決策

https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md#i-did-all-that-and-the-acid-test-still-does-not-pass--
上記のGitHubを参考に進めました(I did all that and the acid test still does not pass :-以下)

$ sudo rm -rf $(xcode-select -print-path)
$ sudo rm -rf /Library/Developer/CommandLineTools
$ xcode-select --install

この後もう一度

$ npm install webpack webpack-cli --save-dev

を行う。

無事エラーは出ず、進めることができました!

参考記事

https://qiita.com/dmrt/items/90ad12850c0ed29758a2

https://github.com/nodejs/node-gyp/blob/master/macOS_Catalina.md#i-did-all-that-and-the-acid-test-still-does-not-pass--

Macでnodebrewを使用したnodeのインストールと設定

$
0
0

(前手順)pkgで入れたnodeの削除 (入れてた場合)

こちらの記事を参考に削除を実施。

$ lsbom -f-l-s-pf /var/db/receipts/org.nodejs.node.pkg.bom \
| while read i;do
  sudo rm /usr/local/${i}done

lsbomコマンドとは

BOMファイルの内容を表示するコマンド。BOMファイルとは、
「Mac OS X Bill of Materials File」のことで、Apple社固有のファイル。
インストーラ(Installer.app)を使用するMacOS X標準のパッケージファイルに含まれている。

(参考:lsbom- BOMファイルの内容を表示する)

$ sudo rm-rf /usr/local/lib/node \
     /usr/local/lib/node_modules \
     /var/db/receipts/org.nodejs.*

1.nodebrewインストールと環境変数の設定

  • ターミナルから、brewでnodebrewをインストール
$ brew install nodebrew
  • 正常にインストールされたか確認
$ nodebrew -vnodebrew -v
  • bash_profileにnode関連のディレクトリパスを追加
    ~./配下を汚さないように、以下のように設定。
NODEBREW_HOME=/usr/local/var/nodebrew/current
export NODEBREW_HOME
export NODEBREW_ROOT=/usr/local/var/nodebrew

/usr/local/var/nodebrewディレクトリがない場合は作成。
/usr/local/var/nodebrew/currentは使用するバージョンのnodeディレクトリのシンボリックリンクとなる。

(/usr/local/var/nodebrew/currentをlsした例)

$ ls-l /usr/local/var/nodebrew/current
lrwxr-xr-x  1 hoge-pc  admin  35  6 12 15:24 /usr/local/var/nodebrew/current -> /usr/local/var/nodebrew/node/v11.9.0
  • bash_profileに追加後反映
$ source ~/.bash_profile

2.nodeのインストール

  • 以下のnodebrewコマンドを実行
# 最新版を入れる場合$ nodebrew install-binary latest

# バージョン指定で入れる場合$ nodebrew install-binary <バージョン>
# 例$ nodebrew install-binary v11.9.0
  • 正常にインストールされたか確認
$ nodebrew ls
  • 現在使用するnodeバージョンを設定する。
nodebrew use <バージョン名>
# 例$ nodebrew use v11.9.0

複数のバージョンをインストールして、バージョンを切り替える場合も
上記のuseコマンドを使用する。

  • 指定したnodeバージョンが使用可能な状態になっているか確認
$ node -v

(Tips) nodebrewコマンド

  • インストール可能なnodeのバージョンを知りたい場合
$ nodebrew ls-remote

GASの処理速度が遅いし制約が厳しいのでNode.jsに移行した(Webアプリ)

$
0
0

はじめに

弓道で用いるwebアプリを開発しているんですが、データの読み書きがクソ遅いので、
GoogleAppsScript+スプレッドシートからNode.js+Firebaseに移行しました。
(GoogleAppsScript Web APIからCloud Functions APIに変えたいうこと)

Google Apps ScriptもNode.jsも基本は一緒ですが...

これでかなりwebアプリの待機時間が短くなったので、記事にしようと思います。

私の使用用途は、スプレッドシートにユーザーやデータを保管し、それを読み書きする、つまりwebアプリのバックエンドとしての利用でGASの処理速度が遅いと感じたものであるので読む際は注意してください。

結果から

どれだけ早くなったか先に紹介しておきます。

的中を記録し、その月のデータをとってくる処理を例に紹介します。記録するデータは日付、中たった数、引いた本数、的中率の四つのデータです。月のデータは付けた日の月のデータ全てとってきます。

↓Google Apps Script + スプレッドシート
gas.gif
ちなみにタイムは2.5秒ほど
スクリーンショット 2020-04-16 18.25.04.png

↓Node.js + Firebase(Cloud Functions API使用)

ezgif.com-video-to-gif (1).gif

スクリーンショット 2020-04-16 18.36.14.png

タイムは驚異の0.4秒!!!!!!!!!!!

約2秒ほど早くなりました。ユーザビリティも右肩上がりでとどまることを知らないでしょう。

*上のフェッチしてきたgifの後半に表示されるデータを見ればわかりますが、ばらつきがある状態で行ったので正確ではないですがほぼ同じデータ量です。
*cloud Funcions APIを使用した方は、ある程度時間を置いてAPIアクセスした際には4秒くらいかかっていました。これは、一定時間おくとcloud Funcionsが寝ちゃうからです。詳細は別記事にしようかと思います。

GoogleAppsScript + スプレッドシートでの読み書きが遅いのはなぜか?

Webアプリのバックエンドとして使っていて、GoogleAppsScript+スプレッドシート(データベース)の読み書きが遅いのはなぜだろう。

1、APIの呼び出し回数が多い

Spreadsheetサービスでは、以下のようなメソッドなどを使ってスプレッドシートにアクセスをするたびにAPIが呼び出される。

・getActiveSpreadsheet()
・getSheetByName()
・getDataRange()
・getLastRow()
・getRange()
・getValue()
・setValue()

二重のfor文の中で
sheet.getRange(i,j).getValue();
なんかした日には終わりで、行数×列数分APIアクセス実行されるので、かなり時間がかかる。

なので、下のようにできるだけ呼び出すAPIを減らすために全てのセルのデータを配列として一回のアクセスでとるよう設計している。
sheet.getDataRange().getValues();

私のwebアプリで、部員全員のデータをとってくる処理があるんですが、この処理に二回のAPIの呼び出し✖️部員数30だったので計60回ほどAPIアクセスをしていました。60回とはいえ、体感3秒ほど待たされたのでユーザビリティがすこぶる悪いかったです。

2、読み書きするデータ量が多いと一回のAPIリクエストが遅い
一回のスプレッドシートの読み書き自体がおそらく長い。*ここで長いと言うのはNode.js+Firebaseと比較して

読み書きするデータ量にもよるだろうけど、パスワード、Eメール、その他ユーザーを管理するデータをスプレッドシートにデータベースとして管理するのには少し難があった。それもユーザー数が多いとなおさら。

膨大なユーザーを管理したりせず、個人で使うデータ量なら全然問題ない。

GoogleAppsScript(WebAPI)の制約

次に各制約について紹介する。
GoogleAppsScript(WebAPI)の制約は、

https://www.bugbugnow.net/2018/12/GoogelAppsScript-restriction.html

これをみればわかるが一日のURLフェッチ(APIアクセス)が20,000/日、さらには無料のGoogleアカウントでは、スクリプトの実行時間が6分と地味に少ない。

いや用途によっては十分なんだろうが、私の使用目的では、一日10回URLフェッチを行うとして、ユーザー数2000を超えると上限に達してしまう。もう少し大規模なSPAにしようと思ったら物足りない。

有料プランもあるがそちらも上限が決まっているので、ユーザーが増えて上限がきたら嫌なのでやはりwebアプリのバックエンドとして使うのは厳しいのかもしれない。そもそもそんな使い方する人はほとんどいないだろうが、個人開発をしている人なら同じような境遇にあった人もいそう。

Cloud Functions APIの制約

それに比べてCloud Functions APIの制約というと

https://firebase.google.com/docs/firestore/quotas?hl=ja

呼び出し
関数の呼び出し料金は定額制です。HTTP リクエストから呼び出される関数(HTTP 関数)、バックグラウンド関数、call API から行われる呼び出しなど、呼び出し元によって料金が変わることはありません。

月間呼び出し回数料金(100 万単位)
最初の 200 万回無料
200 万回を超えた分$0.40

米ドル以外の通貨でお支払いの場合は、Cloud Platform SKU に記載されている該当通貨の料金が適用されます。
呼び出し料金は 1 回あたり $0.0000004 の単価制で、関数の結果や実行時間に関係なく請求されます。ただし、毎月最初の 200 万回までは無料です。

だそう。ほー。無料枠がかなり多いw
一ユーザーが一日に10回APIアクセスするとして

10✖️30 = 300

毎月最初の 200 万回まで無料なので2000,000÷300 = 6,666人まで無料枠でいけるぞ!

一日10回APIアクセスを行うとして、ユーザー数2,000を超えると上限に達してしまうGASのAPIに大してCloud Functions APIは 6,666人までいける
断然、Cloud Functions APIの方がいい。さらに超えた分に対しては超えた分の請求なので都合がいい。上限を超えた際には一回のAPIアクセスをするのに約0.0004円ほどだし格安すぎる。

ユーザーが6万6666人にて毎月2,000万回APIアクセスするとしたら約8,000円ほど。
ガソリン代くらいでしょう。車持ってないけど。

gas+スプレッドシート からNode.js + Firebaseに移行

以上よりGoogle Apps Script + スプレッドシート ⇨ Node.js + Firebase に移行しました。

gasのコード全部で500行くらいだったのでNode.jsに書き換えるのも一週間くらいで終わりました。

↓冒頭で見せたアプリ内で使われる的中率を書き込みに行き、月のデータをフェッチする処理
gasのコードとNode.jsのコードの比較(一部)を紹介します。

gas
//的中率書き込みfunctionsubmitFetchData(e){varsheetID=e.parameter["id"];varsheetName=e.parameter["name"];vardate=e.parameter.p1;varhitArrow=e.parameter.p2;varallArrow=e.parameter.p3;varhitRate=hitArrow/allArrow*100;varyear=e.parameter["year"];varmonth=e.parameter["month"];//JSONオブジェクト格納用の入れ物varrowData={};//書込先スプレッドシートのIDを入力varsheet=SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);vararrayData=[[date,hitArrow,allArrow,hitRate]];varrows=arrayData.length;varcols=arrayData[0].length;sheet.insertRows(2,1);//シートに配列を書き込みsheet.getRange(2,1,1,cols).setValues(arrayData);varthisDate=year+month;varsheet=SpreadsheetApp.openById(sheetID).getSheetByName(sheetName);vardata=sheet.getDataRange().getValues();vararray=data.map(function(value,index){return(value.reduce(function(r,c,i){varresult=r;switch(i){case0:result.month=c;break;}returnresult;},{}))});array.shift();vardate=array.map(function(value,index){varthisDate={};varyear,month;year=value.month.split('/');thisDate.year=year[0];month=year[1].split('/');thisDate.month=month[0];returnthisDate;})vardateArray=date.map(function(value,index){returnvalue.year+value.month;});varfirstIndex=dateArray.indexOf(thisDate);varlastIndex=dateArray.lastIndexOf(thisDate);varnewData=sheet.getRange(Number(firstIndex+2),1,Number(lastIndex+2)-Number(firstIndex+2)+1,data[0].length).getValues();//オブジェクトに変換varnewArray=newData.map(function(value,index){return(value.reduce(function(r,c,i){varresult=r;switch(i){case0:result.date=c;break;case1:result.hit_arrow=c;break;case2:result.all_arrow=c;break;case3:result.hit_rate=c;break;}returnresult;},{}))});varoutput=ContentService.createTextOutput(JSON.stringify(newArray,null,2));output.setMimeType(ContentService.MimeType.TEXT);returnoutput;}
Node.js
//的中率書き込み&月のデータフェッチapp.get('/hitWrite',async(req,res)=>{console.log('-----hitWrite-----')constrec_name=req.query["rec_name"];constid=req.query["id"];constteam=req.query["team"];consthit_arrow=req.query["hit_arrow"];constall_arrow=req.query["all_arrow"];consthit_rate=hit_arrow/all_arrow*100;constdate=req.query["date"];constyear=req.query["year"];constmonth=req.query["month"];constarrayData={all_arrow:Number(all_arrow),date,hit_arrow:Number(hit_arrow),hit_rate:Number(hit_rate),id:Math.random().toString(32).substring(2)//ランダムID生成};letrecords;awaitdb.collection("college").doc(team).collection(id).doc("user_data").update({[rec_name]:admin.firestore.FieldValue.arrayUnion(arrayData)}).then(()=>{return(db.collection("college").doc(team).collection(id).doc("user_data").get().then((snapshot)=>{return(records=snapshot.data()[[rec_name]].filter((value)=>{if(value.date.split('/')[0]===year&&value.date.split('/')[1]===month){returnvalue;}}),res.json(JSON.stringify(records)))}).catch((err)=>{console.log(err)res.end();}))}).catch((err)=>{console.log(err)res.end();})})

かなりすっきりしました。

読みやすい、書きやすい、共同開発しやすくなりましたね。

おわりに

アプリの待機時間が短くなったので、Google Apps Script + スプレッドシート から Node.js + Firebase に移行してかなりよかったです。

ただ、金額的にも月にできるAPIアクセス数にも厳しさがあったGoogle Apps Scriptですが、スプレッドシートに書き込めれるので、部内でシートの共有なんかして直接記録を見たりできるのでこっちも便利だったりします。

ただ、ユーザー数が増えると厳しいのでやっぱり捨てざるをおえないってことでおさらばしないとけません。

このwebアプリのユーザー数は自分の大学のユーザーのみの30人なので、当分気にすることはないのですがね。ふふ

最後まで見ていただき、ありがとうございました。

Dockerコマンド ~学習メモ書き

$
0
0

前提

・導入メモになります。
・いろいろ試してみたが、やっぱりHyperVがいい

準備するもの

アプリケーション側

アプリケーション側はnodejs を使用します。
今回はnode の公式リポジトリを使用するための、Dockerfile

app-server/Dockerfile
FROMnode:5RUNnpm-ginstallredisENVNODE_PATHC:\nodejs\node_modulesENTRYPOINT["node","app.js"]
app-server/src/app.js
varredis=require('redis');varredis_client=redis.createClient(6379,"noderedis");varlisten_port=10080;require('http').createServer(function(request,response){redis_client.incr('counter',function(error,reply){response.writeHead(200,{'Content-Type':'text/plain'});response.end("You accessed here "+reply+" times.\n");});}).listen(listen_port,'0.0.0.0');console.log("Server is running on port "+listen_port+".");

Docker compose ファイルの作成

docker-compose.yml
nodeapp:
  build: "./app-server"container_name: "nodeapp"working_dir: "/usr/src/app"ports:
   -"10080:10080"volumes:
   -"$PWD/app-server/src:/usr/src/app"links:
   -"noderedis"noderedis:
  image: "redis:3"container_name: "noderedis"

コンテナが起動しているかどうかを調べる

$ docker ps

コンテナが停止しているかどうかを調べる

$ docker ps -a

コンテナ起動

$ docker-compose up 

コンテナ停止

$ docker stop *contenaID*

おまけ

コンテナ削除

$ docker rm*contenaID*
$ docker-compose kill

マウントできない時に疑うポイント

参考になったURL

https://qiita.com/jusotech10/items/cb8077efb9b7a74dfdcc

まとめ

もともとインストールしてるoracleの仮想マシーンとDocker toolbox の相性が合わない⇒ 一旦両方ともアンインストールして、入れ直すときに、仮想マシーンにチェックして入れ直すとうまくいく

Windowsのドッカーのツールボックスのマウントは、
C:¥Users直下とマウントされるみたいです。

お金に余裕あるなら、Windows10proを最初に買ってHyperVの仮想環境を使うのが手っ取り早いし起動も早い。。仕事上はproなので環境周りが楽でした。ダッシュボードでコンテナ管理が使えるので便利かと思います。

前に作ったVagrantfile環境の整理したら、オラクルもドッカーもアンインストールしてモジュール置くためのディレクトリのマウントに再挑戦してみようと思います。。

AWS lambda(Node.js)でオレオレ証明書(self-signed)を一時的に信頼してSSL通信を行う方法

$
0
0

はじめに

AWS lambdaのNode.js(https標準モジュール)で実装した、WEBサイトへhttpsのリクエストを投げる処理で、以下の2つのエラーが発生した際の対応についての記事です。
※急いでいる方、ソースコードだけ見たい方はここから見ればOKです

スクリーンショット 2020-04-16 22.24.09.png
スクリーンショット 2020-04-16 18.31.17.png

これ何

調べたところ、リクエスト先のWEBサーバから送出されているサーバ証明書に対してのnodejs内部での検証失敗のため生じたエラーでした。
2つのエラーの違いは、
* 自分自身が署名(オレオレ証明書(self-signed))
* サーバ証明書の発行元が信頼されていないか
👉2つのエラーは共に、Node.jsとして信頼1していないCA証明書にチェーンしていたため生じていました。

環境

  • AWS lambda
  • Node.js ランタイム12.x
  • 接続イメージ [AWS lambda]-->(WAN HTTP over SSL)-->[WEBサーバ]

対処案

3つ考えましたが、妥協点で私の環境では3つ目で実装しましたので以下にまとめます。

1.WEBサーバ側にちゃんとしたサーバ証明書2に変更してもらう

この対応ができたらこんな記事はいらない気もする。
自局署名の問題については、ネット上でも従前から議論されおり3、本記事をご覧になっている方でも既知のことと思います。WEBサーバ管理者に対しては、セキュリティの観点で懸念点を伝えてさしあげる程度にしました。

2.TLSハンドシェイクエラーを無視・無効にする

NODE_TLS_REJECT_UNAUTHORIZEDを環境変数に定義し、値を0とすれば検証自体がdisableになる模様。これは、curlでいうところの--insecureオプションと類似していますが、検証を全て無視するため有効期限やトラストアンカーとのチェーン等々を丸っとすっ飛ばす模様。(詳細は未検証のため割愛)
https://nodejs.org/api/cli.html#cli_node_tls_reject_unauthorized_value

3.オレオレ証明書(self-signed)を一時的に信頼する

公式ドキュメントに書いてありました。
https://nodejs.org/api/tls.html#tls_tls_connect_options_callback
具体的には、通信先のWEBサーバのオレオレ証明書(サーバ証明書自体)ないしは、サーバ証明書のissuerとなっている現状信頼されていない自局CA証明書(pem形式)を取得し、https.requestのoptionにcaを追加するというものです。以下にサンプルを用いて実装例を示しています。

※今回の実装ではpemファイルの読み込みを、諸般の事情によりlambdaのみで完結したかったため、環境変数に事前に設定して、そこから読み込ませるという方法を使っています。(S3に入れて読み込むという方法もありかと思います。)

3-1.pemファイルを1行化する

環境変数にpemを入れるため、改行を¥nに置換します。

[work@localhost]$ awk'NF {sub(/\r/, ""); printf "%s\\n",$0;}' cacert.pem
-----BEGIN CERTIFICATE-----\nAIIDQjCCAiHCEQCQzvg6BX4eF(中略)v2wk3xtME7i8Jb2aUQH9vFVYuXUN2\nUO+j8l1OA4p1ew0kCsit2HOn\n-----END CERTIFICATE-----\n[work@localhost]$

余談ですが、当初は、pem形式って改行まで含んで形式であるということを理解しておらず、改行を単純に削除して環境変数に入れていたためエラーが発生して、ここで地味に悩みました。ちなみに、pemの改行は、Windows形式(CR+LF)、Unix形式(LF)どちらで良い模様なのでCRを削除後に置換している。4

3-2.AWS lamda環境変数の設定

上コマンドで表示された内容を環境変数にCA_PEMとして保存します。
スクリーンショット 2020-04-16 14.43.23.png

3-3. 実際のスクリプト(sample)

consthttps=require('https');//ここで環境変数からcacertにPEMを読み込んでいるvarcacert=process.env['CA_PEM'].replace(/\\n/g,'\n');exports.handler=(event,context)=>{//caを追加してcacertを設定constoptions={protocol:'https:',host:'example.com',path:'/index.html',port:443,method:'GET',timeout:8000,ca:cacert,headers:{'User-Agent':'AWS-lambda',}};letreq=https.request(options,(res)=>{res.setEncoding('utf8');letbody='';res.on('data',(chunk)=>{body+=chunk;});res.on('end',()=>{console.log(body);});});req.end();}

3-4. エラー解消。取れました

Response:
null

Request ID:
"XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"

Function Logs:
START RequestId: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Version: $LATEST
2020-04-16T14:26:29.930Z    XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX INFO
<!DOCTYPE html>
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello</title>
</head>
<body>
    This is test page ;>
</body>
</html>

END RequestId: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
REPORT RequestId: XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Duration: 210.24 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 68 MB  

  1. Node.jsのSSL通信時のトラストアンカーってなんだろ?と思い調べた。https://github.com/nodejs/node/blob/bf7409e9740ce602b09e088aac70b7c817f5d27c/doc/guides/maintaining-root-certs.md読んでみると、このソースに書いてあるよとのこと。Mozilla NSSのトラストアンカーと合わせているみたいですね、定期的にメンテはされている模様。ここに自己署名をここに入れるのは影響が大きそうです。 

  2. Node.jsのトラストアンカー1に含まれるCAに直接または間接的に署名されているサーバ証明書のこと。 

  3. Qiitaですと、こちらの記事がとても参考になりました。オレオレ証明書を使いたがる人を例を用いて説得する 

  4. 出典:https://stackoverflow.com/questions/57870914/how-to-create-a-single-line-x509-certificate-that-can-be-parsed-by-openssl-comma 

新人に負けない本棚管理ページ その1

$
0
0

目次

  • 新人に負けない本棚管理ページ その1(ここ)
  • 新人に負けない本棚管理ページ その1(これから書くよ)

4月に新入社員が入ってきました

とうとう私も先輩になってしまいました。
つい1年前までピチピチの新人だったのですが、時の流れというのは無情なもので、
何も悪いことをしてないのに若葉マークを剥がされるんですよね。
まぁ自動車も同じですが。

今年の新人研修では「オフィスにある書籍の管理ページをWebで作る」というお題が出たそうです。
なので私も新免マークを貼りなおして(?)、サクサクっと作ってみたいと思います。

開発環境

開発OS     :Windows10
稼働OS     :Raspbian buster
サーバー    :Node.js
フレームワーク :Next.js(React)
データベース  :MariaDB
エディタ    :Visual Studio Code
SSHクライアント:Tera Term
おやつ     :ダイソーで買ったオーザックのり塩

構想

基本的な機能からまず作っていきます。
実用ではそうしないだろぉと思われるかもしれませんが、ひとまずは動くことを最優先します。

トップページ

「本のタイトル」「著者」「出版年月日」「貸出中かどうか」をテーブルで表示する

貸し出しページ

「借りる人」「メールアドレス」「パスワード」を入力して借りるボタンクリック。
「返却期限」を表示したい。

返却完了ページ

「メールアドレス」「パスワード」を入力して、貸し出しページで入力した値と一致したら返却できる。
ボタンクリックで返却完了。

(ここまで完成したら)会員情報で管理

セッションで社員情報を保持してイロイロ活用したい!!

(さらに...)借りた履歴ページ

「おまえ~JavaScriptの本ばっか借りてんじゃ~ん」
「おれはPHPの本ば多いわ~」
みたいな会話したいじゃん😆

次回予告

まずは環境構築です。

  • Node.js、Next.jsを用意してなにかページが表示できるようになるまで整えます
  • 次にMariaDBをインストールしてターミナルからクエリを実行できるようにします

Node.js(五):Express 実装

$
0
0

今回からはNodeでExpressを勉強しましょう、Expressは人気なのJSバックエンドモジュールと言うことです。先ずはインストールしてみよう。

実装の手順

Node.js立ち上げ

新たなフォルダを作って、エントリポイントとしてのapp.js追加して、vscode中のターミナルでポロジェクトにnode.jsをインストする。

xxx.xxx:my-first-express$npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (my-server) 
version: (1.0.0) 
description: 
entry point: (app.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
license: (ISC) 
About to write to /express/my-server/package.json:

{
  "name": "my-server",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) yes

そして、package.jsonは作成しました。

pachage.json
{"name":"my-server","version":"1.0.0","description":"","main":"app.js","scripts":{"test":"echo \"Error: no test specified\"&& exit 1"},"author":"","license":"ISC"}

Expressインストール

ターミナルでnpmでexpressをインストールします

xxx.xxx:my-first-express$npm install express
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN my-server@1.0.0 No description
npm WARN my-server@1.0.0 No repository field.

+ express@4.17.1
added 50 packages from 37 contributors and audited 126 packages in 2.902s
found 0 vulnerabilities

そして、package.json中で確認しましょう。

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

"dependencies"の意味は開発者使うパッケージ、ちなみに^4.17.1の"^"はマイナバージョンまでを使われる。

Experss導入

app.js
constexpress=require("express");//express moduleをリクワイアするconstapp=express();//expressオプジェットを呼ぶ//ポットとコールベック関数の内容を設定するapp.listen(3000,function(){console.log("Success to run Server!!")})

最後はターミナルでテストしよう

xxx.xxx:my-first-express$node app.js
Success to run Server!!

以上です。


【JavaScript】配列の一部だけを合計して新しい配列を作る

$
0
0

変換前の配列:array1 = [ 年, 月, 日, 人数1, 人数2, ... , 人数47 ];
変換後の配列:array2 = [ 年, 月, 日, 人数1~人数47の合計 ];

つまり、人数を合計したい。

letarray2=array1.slice(0,3).concat(array1.slice(3).reduce((a,x)=>{returna+x}));

Stripe | ReactとNodeでコネクト支払いさせる方法

$
0
0

やりたいこと

タイムチケットみたいなスキルシェアサービスで、一般ユーザーが、出品者の商品をクレカで購入して、出品者へお金を振り込むフリーを作りたい

考え方

流れは覚えてしまえば簡単

1 Reactでクレカ情報などを入力させる
2 入力情報を元にペイメントメソッドを作る (Stripeのyarnかnpmで出来る)
3 ペイメントメソッドをNodeへPOSTする
4 ペイメントメソッドと、その他の情報(金額とか、コネクトユーザーとか)をAPI叩き込む

以上です。

フロント側の具体的に実践

詳しくは書きません。外部リンクへ行ってください

まず、下記のような画面をReactで作ります。

ここここ読めばコピペで作れる
スクリーンショット 2020-04-17 11.47.30.png

で、フォームを全部入力して Pay $25をボタンを押すと、ペイメントメソッド(文字列のセキュアな暗号IDみたいなの)ができます

これをPOSTでNodeへ送ります。

バック側の具体的に実践

で、バックエンドのNodeで、ペイメントメソッド(文字列のセキュアな暗号IDみたいなの)を受け取ります。

そのペイメントメソッドを利用して、下記のようなAPIをNodeから叩き込みます

丸く囲った部分が、フロント側の決済情報から作ったペイメントメソッドを入れます

スクリーンショット 2020-04-17 11.50.09.png

そうすると、その決済情報に紐づいたコネクトの決済が行われ、コネクトユーザーへしっかり支払いが完了するはず

これはここからコピペできる

備考

動かしてない

WebSocketが切断された際に、自動的に再接続する方法

$
0
0

先日、WEBサイト制作者さん向けに、デザインデータや原稿などWEBサイト制作に必要なあらゆるデータを、チームで簡単に共有できるウェブサービスを公開したのですが、制作の際に得た知見をシリーズで共有していこうと思います。

▼ウェブサイト製作者のためのプラットフォームCrew'sHub
https://crewshub.net

WebSocket の自動再接続が行われない

Crew'sHubには、リアルタイムのチャット機能があり、Web Socketを利用しています。
また、データ管理画面でも、プロジェクトメンバーが編集したら即座に更新されるようになっており、やはりここでもWebSocketを利用しています。

開発中に遭遇した問題として、WebSocketが切断された際に、自動再接続がうまく行われないということがありました。

クライアント側では、Reconnecting WebSocketという、WebSocketがクローズされた際に自動的に再接続が行われるモジュールを採用しています。

reconnecting-websocket
https://www.npmjs.com/package/reconnecting-websocket

自動再接続されるはずのモジュールではありますが、ノートPCでスリープから復帰した際など、切断されたままの状態になってしまう不具合が度々発生しました。

そこで、SetTimeoutで定期的にWebSocketのreadyStateを確認して、コネクションが切断されていたら再接続するようにしてみたのですが、それもうまく機能しませんでした。どうもコネクションが切断されているにも関わらず、readyStateにそれが反映しないことがあるようです。

Ping Pong で接続状況を確認する

WebSocket.readyStateが使えないので、実際にサーバと定期的に通信を行って接続状況を確認する方法に切り替えました。

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

let pingPongTimer = null

const ws = new ReconnectingWebSocket(`wss://${process.env.API_HOST}/api/v1/`)

const checkConnection = () => {
  setTimeout(() => {
    ws.send('ping')
    pingPongTimer = setTimeout(() => {
      console.log('再接続を試みます')
      pingPongTimer = null
      ws.reconnect()
    }, 1000)
  }, 30000)
}

ws.onopen = () => {
  checkConnection()
  ...
}

ws.onmessage = ({ data }) => {
  if (data === 'pong') {
    if (pingPongTimer) {
      clearTimeout(pingPongTimer)
    }
    return checkConnection()
  }
  ...
}

以下のような処理が行われます。

  1. WebSocketがopneしたら、checkConnectionをコールします。
  2. checkConnectionは、呼ばれてから30秒後にサーバにpingを送信します。
  3. ping送信後1秒以内にpongが返って来なかったら再接続を試みます。
  4. ping送信後1秒以内にpongが返って来たら、checkConnectionを再度コールします。

サーバ側はNodeで構成されており、以下のように実装しています。(一部抜粋)

// ユーザーからのメッセージを処理する
ws.on('message', (message) => {
  // pingの処理
  if (message === 'ping') {
    return ws.send('pong')
  }
  ...
}

以上のような処理に変更してからは、コネクションが維持されるようになりました。
現在は、コネクションの確認を30秒ごとに行っていますが、目的によって間隔を調整すると良いと思います。

最後に

繰り返しになりますが、WEBサイト制作者さん向けのサービスをリリースしましたので、お気軽にお試しいただけると幸いです。(無料で使えます)
元々は自分たち向けに作ったシステムでして、WEBサイト再作時に大量の情報やファイルを管理・共有するのに疲れ果て、それを解消するために作ったウェブアプリです。なかなか評判が良かったので、一般向けに作り直してリリースしました。

▼ウェブサイト製作者のためのプラットフォームCrew'sHub
https://crewshub.net

nvm, Node.js, npmをUbuntuで最新にUpdate

$
0
0

Node.js環境を年に2回程度使うのですが、使い方を毎回忘れているので備忘録として残しておきます。

環境

種類システムなど備考
OSUbuntu18.04.01LTSWindowsから仮想で動かしています
Node.jsバージョン管理nvmもっといいのもありそうですが、たまにしか使わないので変えるのが面倒で調べていない
Node.jsパッケージ管理npmなんとなくnpm使っています

Update方法

nvm

最初はnvmのバージョンチェックします

nvm --version

GitHubの「Installing and Updating」にあるコマンドでnvmをアップデートします。最新のバージョン("v0.35.3"の部分)に注意してください。

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash

Node.js

nvm lsで現在インストールされているNode.jsのバージョンを確認します。
以下の例ではv8.10.0とv12.16.1がインストールされており、現在はv12.16.1を使っていることがわかります。そして、最新のltsとしてv12.16.2があることがわかります。

$ nvm ls
        v8.10.0
->     v12.16.1
default -> lts/*(-> N/A)
node -> stable (-> v12.16.1)(default)
stable -> 12.16 (-> v12.16.1)(default)
iojs -> N/A (default)
unstable -> N/A (default)
lts/* -> lts/erbium (-> N/A)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.20.0 (-> N/A)
lts/erbium -> v12.16.2 (-> N/A)

nvm ls-remoteでリモートに登録されているNode.jsのバージョンの一覧を出力します。長いので途中省略。

$ nvm ls-remote

省略

       v12.16.0   (LTS: Erbium)
->     v12.16.1   (LTS: Erbium)
       v12.16.2   (Latest LTS: Erbium)
        v13.0.0
        v13.0.1
        v13.1.0
        v13.2.0
        v13.3.0
        v13.4.0
        v13.5.0
        v13.6.0
        v13.7.0
        v13.8.0
        v13.9.0
       v13.10.0
       v13.10.1
       v13.11.0
       v13.12.0
       v13.13.0

最新のLTSであるv12.16.2をインストールします。

nvm install v12.16.2
Downloading and installing node v12.16.2...
Downloading https://nodejs.org/dist/v12.16.2/node-v12.16.2-linux-x64.tar.xz...
#=#=-  #       ######################################################## 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v12.16.2 (npm v6.14.4)

古いv12.16.1は使わないのでアンインストールします。

$ nvm uninstall v12.16.1
Uninstalled node v12.16.1

もう一度nvm lsで確認します。v12.16.1が消え、v12.16.2となっていることがわかります。

$ nvm ls
        v8.10.0
->     v12.16.2
default -> lts/*(-> v12.16.2)
node -> stable (-> v12.16.2)(default)
stable -> 12.16 (-> v12.16.2)(default)
iojs -> N/A (default)
unstable -> N/A (default)
lts/* -> lts/erbium (-> v12.16.2)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.20.0 (-> N/A)
lts/erbium -> v12.16.2

npm

npmを最新にアップデートします。

nvm install-latest-npm

試しにパッケージをグローバルにインストールします(-gオプションがグローバル)。

npm install-g @sap-cloud-sdk/cli

npm ls -gでインストール済みパッケージの一覧を見ます。ツリー形式で見れるのですが、多くて見にくいです。

npm ls

--depth=0オプションをつけると依存パッケージが出力されないので見やすいです。

npm ls--depth=0

おまけ

使用するNodeのバージョン変更

以下のコマンドで使用するNodeのバージョンを変更できます。

nvm use <version>

AiScriptを使ってみた

$
0
0

はじめに

AiScript(読み:あいすくりぷと)はMisskeyの開発者でもあるsyuiloさんが作成したプログラミング言語の一種です。

AiScript is a scripting language runing on JavaScript. Not altJS.

  • ホストから変数や関数を提供することが出来ます
  • 外の情報にはアクセス出来ないので安全にスクリプトを実行できます(サンドボックス環境)

ベースはTypeScript(JavaScript)で、Node.jsで動作します。

※この記事は2020年4月17日現在のものです。執筆時点では開発段階であるため、仕様変更がある場合がありますので予めご了承ください。

動作環境の構築

動作環境を構築するにはNode.js(LTS版で動作すると思います)が必要になります。
インストールしていない場合はインストールしておいてください。

  1. リポジトリをクローンします。: git clone https://github.com/syuilo/aiscript.git
  2. リポジトリのディレクトリに移動し、依存パッケージをインストールします。: cd aiscript; npm install
  3. AiScript環境をコンパイルをします。: npm run build

Hello Worldを表示する

node consoleでAiScriptのインタプリタを起動することができます。

起動したインタプリタで、下の関数を入力すると、Hello World!と表示されます。

print("Hello World!")

asciicast

ね、簡単でしょ。

インタプリタでは、返り値が表示されます。printは特に返すものはないため、nullとなります。

なお、printの関数は以下のようにしても同様に動作します。

<: "Hello World!"

インタプリタを抜けるにはCtrl-Cを押します。

注釈(コメント)

コメントはスラッシュ2文字で、改行するまで注釈扱いになります。

//
// これはコメントです。動作には影響しません

変数・定数

定数の定義は以下のようにして行います。

#constant = "This is a constant"
// #[定数名] = 代入する内容(項目:オブジェクト を参照)

変数の定義は以下のようにして行います。

$variable <- "This is a variable"
// $[変数名] <- 代入する内容(項目:オブジェクト を参照)

変数の内容を変更する場合は以下のようにして行います。

variable <- "It's changed variable"
// [変数名] <- 代入する内容(項目:オブジェクト を参照)

インタプリタ内であれば、定数や変数の中身を確認することができます。

interpreter
> #hoge = "hoge"
> hoge
< "hoge"

オブジェクト

AiScirptには以下のオブジェクトがあります。

種類リテラル例
文字列str"kawaii"
数値num42
真理値boolyes/no
配列arr["ai", "chan", "cute"]
オブジェクトobj{ foo: "bar"; a: 42; }
nullnull_
関数fn@(x) { x }

配列について

配列のインデックスは1から始まります。つまり、こうなります。

#array = ["one", "two", "three"]

print(array[1]) // one
print(array[2]) // two
print(array[3]) // three

print(array[0]) // [ERROR!]

演算

演算は以下のようにして書くことができます。

(1 + 1)

以上のスクリプトは糖衣構文であり、AiScriptでは以下のように解釈されます。

Core:add(1, 1)

以下のような演算があります。

関数演算子意味
Core:add+加算
Core:sub-減算
Core:mul*乗算
Core:div/除算
Core:mod%剰余
Core:eq=等しい
Core:and&かつ
Core:orまたは
Core:gt>大きい
Core:lt<小さい

※マークダウンのエスケープが上手く機能していないため、「|」は半角の|として扱ってください。

ブロック

ブロックは処理のまとまりを書ける役割があります。ブロックの最後に書かれた式が値として返されます。

#foo = {
  #a = 1
  #b = 2
  (a + b)
}

print(foo) // 3

なお、ブロック内の変数や定数はブロック外では使用できないみたいです(内部エラーが起きてるみたいなのでバグかもしれません)。

#foo = {
  #a = 1
  #b = 2
  (a + b)
}

print(a) // [ERROR]

条件分岐

条件分岐は以下のように書きます。

? (a = b) { // ? ([演算式]) { [条件が合う場合の処理] }
  <: "a is equal to b"
}

また、条件が一致しない場合の処理は以下のように書きます。

// ? ([演算式]) { [条件が合う場合の処理] } ... { [条件が合わない場合の処理] }
? (a = b) {
  <: "a is equal to b"
} ... {
  <: "a is not equal to b"
}

条件が一致しなかった場合、さらに条件を出す処理もできます。

// ? ([演算式1]) { [演算式1が合う場合の処理] } ...? ([演算式n]) { [演算式nが合う場合の処理] }
? (a = b) {
  <: "a is equal to b"
} ...? (a > b) {
  <: "a is grater than b"
} ... {
  <: "a is less than b"
}

条件分岐は式として扱えるため、ブロック内で値を返すこともできます。

<: ? (a = b) {
  "a is equal to b"
} ...? (a > b) {
  "a is grater than b"
} ... {
  "a is less than b"
}

繰り返し

繰り返し処理は以下のように書きます。

// ~ #[イテレータ名], [繰り返し回数] { [処理] }
~ #i, 100 {
  <: i
}
// 1~100までprintされます。

asciicast

なお、イテレーター名は省略可能です。

// ~ [繰り返し回数] { [処理] }
~ 100 {
  <: "hoge"
}

配列を使った繰り返し処理も行えます。

#hoge = ["a", "b", "c"]
// ~~ #[イテレーター名], [配列定数(or 変数)] { [処理] }
~~ #h, hoge {
  <: h
}

関数

関数を定義することもできます。以下のように書きます。

// @[関数名]([引数]) { [処理] }
@fun(x) {
  (x + 3)
}

// 関数を実行
fun(2) // 5

最後の式が返り値として扱われます。
途中で返り値を出す場合は<<を使用して値を返します。

@hoge(a) {
    ? (a = 2) {
        << (a * 2)
    }
    (a + 1)
}

hoge(2) // 4

テンプレート

文字列の中に変数や式を埋め込むことができます。以下のように書きます。

#hoge = "hoge"
<: `{hoge} is usually word used in programming`

スクリプトを実行

スクリプトを実行するには、test.isを、AiScriptと同じディレクトリに設置してスクリプトを作成します。

作成したら、npm startでスクリプトを実行することができます。


AiScriptはまだまだ開発段階であるため、
もしかしたら仕様が変わるかもしれません。
気が向いたらこの記事も変更するかもしれません。

また、このAiScriptは将来的にMisskeyに搭載される、とのことらしいので気になるところです。

Viewing all 8902 articles
Browse latest View live