20201125のNode.jsに関する記事は15件です。

Node.js基礎 備忘録

リクエストの対処する仕組み

スレッドモデル(Apache)

リクエストが来た際にスレッドを作成して対応する
※スレッドは同時にひとつのリクエストしか処理できない。
新しいリクエストが来た際には、新しいリクエストを作成して対応する

問題

スレッドを作成するとメモリを消費するため、スレッドの作成限界がある。
リクエストが多い場合、スレッドが立ちあげれず、リクエストが待ち状態になる。

イベントループ(Node.js)

メインスレッドとバックスレッドの2つがあり、動作している。

メインスレッドには、処理待ちのキューがあり、リクエストが来た際には、キューに登録する。
※新しくスレッドは作成しない

キューをループで回し、バックグランドで動くスレッドに渡し、そちらで処理を実行していく。

プログラムを書く際は、ループをブロックしないように記述する。(Non-blocking)

時間がかかりそうな処理は、コールバック関数で実装する

メインで動くスレッドが、新規に作成されない為、シングルスレッドと呼ばれる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebSocket メモ

WebSocket とは

  • クライアントとリモートホストの間で双方向通信を可能とするプロトコル。
  • セキュリティモデルはウェブブラウザでよく用いられるオリジンをベースとしたモデル。
  • XMLHttpRequest やロングポーリングに頼らずにサーバとの双方向通信を必要とするブラウザアプリケーションのためのメカニズムを提供する。
  • クライアント・サーバ間の双方向のトラフィックのためにTCPコネクションを一つだけ張る。

プロトコルの概要

プロトコルはハンドシェイクデータ転送の2つの部分からなる。

ハンドシェイク

クライアント -> サーバ

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

サーバ -> クライアント

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

ハンドシェイクが成功すると、クライアントとサーバは1つ以上のフレームからなるメッセージをお互いに送出し合うことになる。

Node.js でシンプルな WebSocket 通信

Node.js では、ws というWebSocketライブラリを用いてシンプルな WebSocket 通信ができる(Socket.IO でも可能)。

サーバ側のコード

const WebSocket = require('ws')

const server = new WebSocket.Server({ port: 3000 })

server.on('connection', ws => {
  ws.send('connected!')

  ws.on('message', message => {
    console.log('received: %s', message)
  })
})

クライアント側のコード

const ws = require('ws')

const client = new ws('ws://localhost:3000')

client.on('open', () => {
  client.send('hello')
})

client.on('message', message => {
  console.log(message)
})

node server.js でWebSocketサーバを立ち上げた後 node client.js を実行すると、サーバ側では

received: hello

と出力され、クライアント側では

connected!

と出力される。

WebSocket でチャットアプリを作る

blessed というクールな CUI を作れるライブラリを使って、超シンプルなチャットアプリ(もどき)を作ってみる。

サーバ側

const WebSocket = require('ws')

const server = new WebSocket.Server({ port: 3000 })

const sockets = new Map()

const broadcast = message => {
  console.log(message)
  server.clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message)
    }
  })
}

server.on('connection', socket => {
  socket.on('message', message => {

    if (sockets.has(socket)) {
      const name = sockets.get(socket)
      broadcast(`${name}: ${message}`)
    } else {
      sockets.set(socket, message)
      broadcast(`${message} entered the room.`)
    }
  })

  socket.on('close', () => {
    const name = sockets.get(socket)
    if (name) {
      sockets.delete(socket)
      broadcast(`${name} left the room.`)
    }
  })
})

クライアント側

const ws = require('ws')
const blessed = require('blessed')

const screen = blessed.screen({ smartCSR: true })
screen.title = 'chat'

const chatList = blessed.list({ border: { type: 'line' } })

const textbox = blessed.textbox({
  height: '10%',
  bottom: 0,
  input: true,
  inputOnFocus: true,
  border: {
    type: 'line'
  }
})

const client = new ws('ws://localhost:3000')

client.on('open', async () => {
  textbox.setText('(Please enter your name)')
  textbox.focus()
  screen.render()
})

client.on('message', message => {
  chatList.insertLine(0, message)
  screen.render()
})

textbox.key(['enter'], () => {
  client.send(textbox.getText())
  textbox.clearValue()
  chatList.render()
  textbox.focus()
})


const EXIT_COMMANDS = ['escape', 'C-c']
const exit = () => process.exit(0)
screen.key(EXIT_COMMANDS, exit)
chatList.key(EXIT_COMMANDS, exit)
textbox.key(EXIT_COMMANDS, exit)


screen.append(chatList)
screen.append(textbox)
screen.render()

実際の動作

chat.gif

それっぽくは動く。

所感

  • blessed の API がよくわからない
  • ブラウザを UI としたチャットアプリを作ってみたい
  • 随時更新する

出典

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Express Generatorを用いてプロジェクとを立ち上げる。

1: Express Generatorをインストールする。

ターミナル.
npm install -g express-generator

2: Express Generatorでアプリケーションを作成する。

ターミナル.
express --view=ejs <アプリ名>

# --view=ejs テンプレートエンジンにejsを指定するオプション。
  • bin (アプリケーションを実行するためのwwwファイルが配置)
  • public (公開ディレクトリ images javascripts stylesheets配置)
  • routes (ルーティング)
  • views (テンプレートファイルを配置)
  • app.js (メインプログラム)
  • package.json (npmを管理)

が作成される。

3:パッケージをインストールする。

ターミナル.
npm install

package.jsonを元に必要なパッケージがインストールされる。

初期package.json{
  "name": "node_app",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",   //Expressでcookieを利用
    "debug": "~2.6.9",        //デバッガー
    "ejs": "~2.6.1",         //ejs テンプレートエンジン
    "express": "~4.16.1",         //expressそのもの
    "http-errors": "~1.6.3",      //エラー処理
    "morgan": "~1.9.1"            //ログ出力
  }
}

必ず使うことになるexpress-sessionをインストールする。

ターミナル.
npm install express-session

express-sessiionモジュールを読み込む。

app.js
const session = require('express-session'); 

express-sessionを設定し、appに組み込む

app.js
var session_opt = {
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 60 * 60 * 1000 }
};
app.use(session(session_opt));

//var app = express(); と ルーティング設定の間に記述

4: アプリケーションの起動

ターミナル.
node bin/www

起動スクリプトは作成されたbinディレクトリ内のwwwファイルに記述されている。

app.js(一部)
var express = require('express');    //expressの読み込み
var app = express();          //アプリケーションオブジェクトを作成し、appに格納
module.exports = app;          //appをエクスポート
bin/www.(一部)
var app = require('../app');   //app.jsを読み込み
var http = require('http');   //httpコアモジュールを読み込み

var port = normalizePort(process.env.PORT || '3000');  
app.set('port', port);                                 //環境変数で指定したポート、falseなら3000をportにセット

var server = http.createServer(app);   //httpサーバーを作成

server.listen(port);     //指定ポートで待機

package.jsonに

package.json
  "scripts": {
    "start": "node ./bin/www"
  }

スクリプト指定されているので

ターミナル.
npm start

でも可能。

VScodeの場合

pacage.josnがあるディレクトリを開いている場合、エクスプローラー欄に "NPMスクリプト"という蘭が表示される。
その配下のpackage.jsonから、"start"をクリックでも可能

5: 確認

http://localhost:3000/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Express Generatorを用いてプロジェクトを立ち上げる。

1: Express Generatorをインストールする。

ターミナル.
npm install -g express-generator

2: Express Generatorでアプリケーションを作成する。

ターミナル.
express --view=ejs <アプリ名>

# --view=ejs テンプレートエンジンにejsを指定するオプション。
  • bin (アプリケーションを実行するためのwwwファイルが配置)
  • public (公開ディレクトリ images javascripts stylesheets配置)
  • routes (ルーティング)
  • views (テンプレートファイルを配置)
  • app.js (メインプログラム)
  • package.json (npmを管理)

が作成される。

3:パッケージをインストールする。

ターミナル.
npm install

package.jsonを元に必要なパッケージがインストールされる。

初期package.json

{
  "name": "node_app",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",    //Expressでcookieを利用
    "debug": "~2.6.9",            //デバッガー
    "ejs": "~2.6.1",              //ejs テンプレートエンジン
    "express": "~4.16.1",         //expressそのもの
    "http-errors": "~1.6.3",      //エラー処理
    "morgan": "~1.9.1"            //ログ出力
  }
}

必ず使うことになるexpress-sessionをインストールする。

ターミナル.
npm install express-session

express-sessiionモジュールを読み込む。

app.js
const session = require('express-session'); 

express-sessionを設定し、appに組み込む

app.js
var session_opt = {
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: false,
  cookie: { maxAge: 60 * 60 * 1000 }
};
app.use(session(session_opt));

//var app = express(); と ルーティング設定の間に記述

4: アプリケーションの起動

ターミナル.
node bin/www

起動スクリプトは作成されたbinディレクトリ内のwwwファイルに記述されている。

app.js
(一部)
var express = require('express');    //expressの読み込み
var app = express();                 //アプリケーションオブジェクトを作成し、appに格納
module.exports = app;                //appをエクスポート
//bin/www

(一部)
var app = require('../app');   //app.jsを読み込み
var http = require('http');    //httpコアモジュールを読み込み

var port = normalizePort(process.env.PORT || '3000');  
app.set('port', port);                                 //環境変数で指定したポート、falseなら3000をportにセット

var server = http.createServer(app);    //httpサーバーを作成
//この処理により、クライアントがサーバにアクセスしたらapp.jsが読み込まれる。

server.listen(port);       //指定ポートで待機

package.jsonに

package.json
  "scripts": {
    "start": "node ./bin/www"
  }

スクリプト指定されているので

ターミナル.
npm start

でも可能。

VScodeの場合

pacage.josnがあるディレクトリを開いている場合、エクスプローラー欄に "NPMスクリプト"という蘭が表示される。
その配下のpackage.jsonから、"start"をクリックでも可能

5: 確認

http://localhost:3000/

6: 必要な設定、モジュールロード、モジュール組み込み(app.js)

app.js
//モジュールの読みこみ
var createError = require('http-errors');
//httpエラーの対処を行うたものもの。
var express = require('express');
//expressそのもの
var path = require('path');
//ファイルパスを扱うためのもの
var cookieParser = require('cookie-parser');
//クッキーのパースに関するもの
var logger = require('morgan');
//httpリクエストのログ出力にかんするもの。

var app = express();



// ビューエンジンの設定
app.set('views'), path.join(__dirname, 'views'));
//テンプレートファイルが保管される場所を設定
app.set('view engine', 'ejs');
//ejsをテンプレートエンジンに指定



//モジュールの組み込み
app.use(logger('dev'));
app.use(express.json());
//Body-Parserを基にExpressに組み込まれた機能。クライアントから送信されたデータを、req.body経由で会得、操作できる。
//リクエストオブジェクトを JSONオブジェクト として認識する。
app.use(express.urlencoded({extended: false }));
//Body-Parserを基にExpressに組み込まれた機能。クライアントから送信されたデータを、req.body経由で会得、操作できる。
//リクエストオブジェクトを 配列、文字列 として認識する。
app.use(cookieParser());
app.use(express.static(path,join(__dirname, 'public')));
//イメージ、CSS ファイル、JavaScript ファイルなどの静的ファイルを提供する.


//ルーティング
app.use('/', 'index.js');


// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

//モジュールエクスポート
module.exports = app;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EJSのif文でtrueの時だけ出力する。

やりたいこと

<% const person = { name: 'taro' };%>
<% if (person.name) { %><%= person.name %><% } %>
  • 上記のような値があったときだけ出力する記述をスマートに書きたい。
  • <%が多いので減らしたい。

結論

以下のように書くと完結に書くことができます。

<%= person.name ? person.name : '' %>

三項演算子を使用します。偽値の場合は空文字です。

試したこと

<%= if(person.name) { person.name } %> // エラー
<%= if(person.name) person.name %> // エラー
<% if(person.name) person.name %>  // 出力されない

偽値を省略できていないのが心残りですが、
結論の<%= person.name ? person.name : '' %>がシンプル。

他にいい方法があったら教えてください :bow_tone1:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

v8::Local<v8::String> と std::basic_string の相互変換

node.js C++ addons での文字列変換について
nan 等は使わずに V8 API (14.x) 向け

2020/11/27
コメントを参考に C++11 以降向けに修正しました

共通

windows は _UNICODE が定義されている必要があります

#define ISOLATE v8::Isolate::GetCurrent()

#if defined(_WIN32)
// windows
typedef wchar_t _char_t;
#else
// mac, linux
typedef char    _char_t;
#endif

typedef std::basic_string<_char_t> _string_t;

v8::Local<v8::String> → std::basic_string

_string_t to_string(const v8::Local<v8::String>& str)
{
#if defined(_WIN32)
    // windows
    _string_t buff(str->Length(), '\0');
    str->Write(ISOLATE, (uint16_t*)&buff[0], 0, -1, v8::String::NO_NULL_TERMINATION);
    return buff;
#else
    // mac, linux
    _string_t buff(str->Utf8Length(ISOLATE), '\0');
    str->WriteUtf8(ISOLATE, &buff[0], -1, nullptr, v8::String::NO_NULL_TERMINATION);
    return buff;
#endif
}

std::basic_string → v8::Local<v8::String>

v8::Local<v8::String> to_string(const _string_t& str)
{
#if defined(_WIN32)
    // windows
    return v8::String::NewFromTwoByte(ISOLATE, (uint16_t*)str.c_str()).ToLocalChecked();
#else
    // mac, linux
    return v8::String::NewFromUtf8(ISOLATE, str.c_str()).ToLocalChecked();
#endif
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Intl.DateTimeFormatを使った日付や時間の0詰め

本題

最近仕事でNode.jsとV8のチェンジログを追っていたところ、Intl.DateTimeFormatという新機能を見つけました。使うと、日付や時間の0詰めを外部ライブラリに頼らずにシュッと書けそうなので試してみました。

サンプルコードは以下のような感じです。

const date = new Date('2020-01-01');
const dateStr =new Intl.DateTimeFormat('jp', {
  year: 'numeric', month: '2-digit', day: '2-digit',
}).format(date);
console.log(dateStr); // 2020/01/01
const date2 = new Date('2020-01-01');
const dateStr2 = new Intl.DateTimeFormat('jp', options = {
  year: 'numeric', month: '2-digit', day: '2-digit', 
  hour: '2-digit', minute: '2-digit', second: '2-digit', 
}).format(date2);
console.log(dateStr2); // 2020/01/01 09:00:00

コンストラクタIntl.DateTimeFormatの第2引数にはオブジェクトで表示のオプションが渡せます。プロパティ値が2-digitなら0詰めした値numericならそのままの値でフォーマットします。第1引数にはロケールを渡すのですが、各国の主要な表示方法でフォーマットするようです。

これで以下のようなコードとはおさばらじゃ!!

const date3 = new Date('2020-01-01');
const month = ('0' + (date3.getMonth()+1)).slice(-2);

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsのvue-cursol-fxを使ってみる

最近は就活中で暇なんでちょこちょこVue.jsを触っています。Railsでのバックエンドばかりだったのでフロントの知識もなく、Vue用のプラグインでとりあえずそれっぽくしたいなと思いvue-cursol-fxを使ってみました。

githubのインストレーションを参考にしています。詳しくはREADME.mdを見てください。
https://github.com/LuXDAmore/vue-cursor-fx?ref=kabanoki.net

環境

  • Windowns 10
  • Node.js v14.15.1
  • @vue/cli 4.5.9

言い訳なんですがバーチャルボックスだとnodeのサーバー立ち上げた際ホットリロードが異常に遅い(約1分)ため仕方なくWindowsに直接Nodeをインストールして最近は遊んでいます。仮想環境でホットリロードがなんで遅いのかはいまだ原因分からないです。誰か教えて...

導入

まずはvue-cliでプロジェクトを作ります。

vue create sample_app

作成時の設定はデフォルトで、Vue2にしておきます。

sample_appのディレクトリに移動して、vue-cursor-fxを入れます。

cd sample_app
npm install --save @luxdamore/vue-cursor-fx

次にApp.vueにてimportしてコンポーネントを書き加えます

App.vue
<template>
  <div id="app">
    <cursor-fx color="#39B509" color-hover="#acf98e"></cursor-fx>
    <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Hello Vue"/>
  </div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue';
import { CursorFx } from '@luxdamore/vue-cursor-fx';
import '@luxdamore/vue-cursor-fx/dist/CursorFx.css';

export default {
  name: 'App',
  components: {
    HelloWorld,
    'cursor-fx': CursorFx,
  },
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

これでカーソルが表示されます。

2020-11-25_17h23_07.gif

オプションでポインタの形や大きさなど変えられます。

<cursor-fx color="#39B509" color-hover="#acf98e" shape='square'></cursor-fx>

2020-11-25_17h32_13.gif

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Node.js】非同期処理に慣れるためのハンズオン

Node.js Advent Calendar 4日目!
どうぞよろしくお願いいたします。m( )m

はじめに

Node.js で async await について書くんですが、独学で理解した気になっていた私は、いざ実装となったときに『あれれ?』ってなりました。
知識が経験として定着してなかったんですね。反省(=_=)

そこで、読んだ方が試せるようになるべく細かく書いてみました!
自身の復習と定着にもなりますしね~♪(*‘∀‘)

所用時間は事前準備で 15 分、実作業で 15 分の、合計 30分程度です。
Node.js の非同期処理にまだ慣れていない方は、是非ともやってみてください♪

やりたいこと

やりたいことは非同期処理の勉強!
…なんですけど、そうではなくてどんなプログラムを作るかですね。(;^ω^)

今回は非同期処理の練習なので API Request をして情報を取得し、情報リストを作成していきたいと思います。

誰でも試せるように無料でできる範囲にしたいので、LINE WORKS API のトーク Bot API 縛りでお送りしたいと思います(*´Д`)

トーク Bot API 縛りだと・・・今まで作成した Bot リストが作れそうですね。
Bot リスト自体は練習なのであんまり使い道がないですが、有料プランとかだとユーザデータやカレンダー情報などを API Request で取得してリスト化する!とかもできます。

リスト作りは汎用性が高いので練習には持ってこないんですよ~。(゚Д゚)

ロードマップ

こんな感じで進めて行こうと思います。

  1. API を利用するための Access Token を発行する
  2. 発行した Access Token を使用して BOT リストの照会 API を Request する(非同期処理
  3. さらに、Loop 処理で各 BOT の詳細情報照会 API をいっぺんに Request する
  4. Response を受け取ってくっつけて、リストを作る

この通りやりたいんだけど・・・!

ご存じの通り API Request はその性質上 Response を受け取るまで多少の時間がかかります。
しかし、Node.js は処理を同期させずに行うノンブロッキング I/O を利用しているので Response を待たずに次の処理を行います。
必要な情報が手に入っていないのに、次の手順へと進んでしまうのですね。
これはまずい(; ・`д・´)

そこで非同期処理再起処理の記述が必要になってきます。
これらを順番に学んでいきたいと思います。

開発環境

Windows 10
Node.js
Visual Studio Code
LINE WORKS

使用する API

API は LINE WORKS API を利用します。
無料で使える Free Plan でも Bot 関係の API は使用できるので、勉強や検証するには便利なのです。
宣伝です(`・ω・´)キリッ

事前準備

  1. Node.js のインストール
  2. Visual Studio Code のインストール
  3. LINE WORKS 利用登録
  4. LINE WORKS Developers で API 認証情報を取得

それぞれ解説しているサイトの URL を貼っておきます。
取り敢えずやり方だけ見たいって方は先に進んじゃってくださいませ。

各モジュールのインストール

事前準備がすべて終わったら Visual Studio Code(以後 VSC)を起動します。
[ファイル] - [フォルダを開く] で作業用フォルダを開きます。
作業用フォルダは何でも良いので、デスクトップに新しいフォルダを作成します。
ファイル名も何でも良いのですが、ここでは ApiRequestSample とでもしておきます。
1604543799.png

[ターミナル] - [新しいターミナル] でターミナルを起動して npm init -y します。
1604546930.png

package.json ファイルがフォルダに作成されたら、使用するモジュールをインストールしていきます。
今回使うのはこの4つ。

  • request
  • request-promise
  • promise
  • jsonwebtoken

ターミナルで順番にインストールしていきます。

> npm install request --save
> npm install request-promise --save
> npm install promise --save
> npm install jsonwebtoken --save

これで準備完了です。

ではでは、実際にコードを書いていきましょう!

1. API を利用するために Access Token を発行する

LINE WORKS API を利用するためは Access Token が必要です。
LINE WORKS 認証サーバーへの Token リクエスト API を使用して発行します。
事前準備の 4. で取得した LINE WORKS 認証情報を使いますので、ご用意を!

API Request で Token を取得するのですが、Response を待って次の処理を行う必要があるので、ここで非同期処理をする必要があります。

では、Node.js の非同期処理である async function を使っていきましょーヾ(´∀`)ノ

async function のお約束

非同期処理をしてくれる、とっても便利な関数。
今回、私が理解したお約束はこんな感じ。

  • async function では Promise オブジェクトを値を入れて return する。
  • Promise オブジェクトに値を入れるには resolve を使う。
  • async function がエラー値を throw した場合は reject を使う。

await を使って非同期処理!

await さんは async function が結果を返すまで処理を待ってくれる心の広いお方!
await さんとのお約束はこんな感じ。

  • await を頭につけて async function を使う。
  • await を使えるのは async function の中でだけ。
  • なので main プログラム内で await したいときは予め async function main() を宣言しておく。

まぁ、文字で読んでもイメージ沸きませんよね。( ゚Д゚)
なので、上記のことを踏まえてコードを見ていきましょう。

Access Token を発行するコード

使用するときは最初の CONSTS のところに事前準備の 4. で取得した LINE WORKS 認証情報を記入してくださいねー。

main.js
// LINE WORKS 認証情報
const CONSTS = {
    API_ID : "xxxxxxxx",
    CONSUMER_KEY : "xxxxxxxxxx",
    SERVER_ID : "xxxxxxxxxx",
    PRIVATEKEY : "xxxxxxxxxxxxxxxxxxxxxxyyzz" // 認証キー
}
// module
const request = require("request-promise");

// program start
main();

/**
 * 非同期処理にて Access Token を取得して表示
 */
async function main() {
    const token = await getServerToken();
    console.log(token);
}
/**
 * LINE WORKS の Server Token を取得
 * @return {object} Server Token
 */
async function getServerToken() {
    const jwt = require("jsonwebtoken");
    const iss = CONSTS.SERVER_ID;
    const iat = Math.floor(Date.now() / 1000);
    const exp = iat + 60 * 60; // 有効期間を 1時間に設定
    const cert = CONSTS.PRIVATEKEY;
    const options = {
        method: "POST",
        url: `https://auth.worksmobile.com/b/${CONSTS.API_ID}/server/token`,
        headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
        form: {
            grant_type: encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
            assertion: jwt.sign({ iss: iss, iat: iat, exp: exp }, cert, { algorithm: "RS256" }),
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            const result = JSON.parse(res);
            if (result.message) throw "Could not get token";
            resolve(result);
        }).catch((error) => {
            console.log(`Auth Error: ${error}`);
            reject(error);
        });
    });
}
> node .\main.js
{ access_token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  token_type: 'Bearer',
  expires_in: 86400 }

無事に取得して表示することができました!ヾ(´∀`)ノ
次は、取得した Access Token を使用して API を Request していきましょう。

2. 発行した Access Token を使用して BOT リストの照会 API を Request する

Access Token が手に入ったので API Request し放題ですね!( ゚Д゚)

今回の目的は、トーク BOT のリストを作ることなので、まずは トーク Bot リストの照会 APIBOT の簡易リストを取得します。

ちなみに、BOT 登録がないと当たり前のように空のリストが返ってくるので、実際に試してみたい方は以下の記事を参考に適当な BOT を複数登録しておいてください。
【LINE WORKS】トーク BOT の登録方法

API Request するときは非同期処理をしよう!

繰り返しになりますが、API Request はその性質上 Response を受け取るまで多少の時間がかかります。
しかし、Node.js はノンブロッキング I/O を利用しているので Response を待たずに次の処理を行います。
なので、これも非同期処理をしないとろくな結果になりません(・ω・)

ではでは、軽くおさらいしながらコードを書いていきましょー。

BOT の簡易リストを取得するコード

BOT の簡易リストを取得する関数、getBotList()非同期処理async function で宣言します。
main() の中で呼び出すときは await を頭につけて呼び出します。

main.js
// 前略
main();

async function main() {
    const token = await getServerToken();
    const bots = await getBotList(token);
    console.log(bots);
}
// getServerToken() の内容は省略
/**
 * LINE WORKS のトーク Bot リストを取得
 * @param {object} token LINE WORKS Server Token
 * @return {Promise object} トーク Bot リスト
 */
async function getBotList(token) {
    const options = {
        method: "GET",
        url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot`,
        headers: {
            "Content-Type": "application/JSON",
            "consumerKey": CONSTS.CONSUMER_KEY,
            "Authorization": `${token.token_type} ${token.access_token}`
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            const result = JSON.parse(res);
            resolve(result.bots);
        }).catch((error) => {
            console.log(`Bot List Request Error:\n${error}`);
            reject(error);
        });
    });
}
> node .\main.js
[ { botNo: xx6040,
    name: 'お問い合わせフォーム君',
    photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
    i18nNames: [],
    i18nPhotoUrls: [] },
  { botNo: xx6250,
    name: 'facebook_bot',
    photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
    i18nNames: [],
    i18nPhotoUrls: [] },
  { botNo: xx6261,
    name: 'LW_bot',
    photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
    i18nNames: [],
    i18nPhotoUrls: [] },
  { botNo: xx6682,
    name: 'GAS 連携 Bot',
    photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
    i18nNames: [],
    i18nPhotoUrls: [] }]

無事に Access Token を取得してから API を呼び出すことができました。
また、botList を取得してからコンソールに表示することもできました!

非同期処理、二回もやったので慣れてきましたね!ヾ(´∀`)ノ

asyncawait!(゚Д゚)ノ
asyncawait!(゚Д゚)ノ
呪文のように覚える。

もう非同期処理は完璧ですかね?
まだ不安ー!って方は、最後にもう1回やってみましょー!ヾ(´∀`)ノ

3. さらに、Loop 処理で各 BOT の詳細情報照会 API をいっぺんに Request する

先ほど取得した BOT 簡易リストの情報で、各 BOT の botNo がわかりました。
botNo がわかればトーク Bot 詳細情報の照会 API を利用して、各 BOT の詳細情報を照会できます。
つまり、リストアップされた BOT の数だけ API Request をするんですね。

API Request を Loop 処理する

繰り返しになりますが、API Request はその性質上 Response を受け取るまで多少の時間がかかります。
しかし、Node.js はノンブロッキング I/O を利用しているので Response を待たずに次の処理を行います。
Loop 処理なんかはその最たるもので、普通に forforEach で回そうものなら処理順がぐっちゃぐっちゃになります。( ;∀;)

と、言うわけで!もはや耳タコな非同期処理を使って綺麗なループ処理をやっていきましょー!(*‘∀‘)

非同期処理で Loop するコード

main.js
// 前略
main();

async function main() {
    const token = await getServerToken();
    const bots = await getBotList(token);
    let botLists = [];
    for(let i = 0; i < bots.length; i++){
        botLists.push(await getBotInfo(bots[i].botNo, token));
    }
    console.log(botLists);
}
// getServerToken() の内容は省略
// getBotList(token) の内容は省略

/**
 * LINE WORKS トーク Bot の詳細情報を加えたリストを作成
 * @param {object} bots LINE WORKS トーク Bot の botNo リスト
 * @param {object} token LINE WORKS Server Token
 * @return {Promise object} 詳細なトーク Bot リスト
 */
async function getBotInfo(botNo, token) {
    const options = {
        method: "GET",
        url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot/${botNo}`,
        headers: {
            "Content-Type": "application/JSON",
            "consumerKey": CONSTS.CONSUMER_KEY,
            "Authorization": `${token.token_type} ${token.access_token}`
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            let result = JSON.parse(res);
            result.botNo = botNo;
            resolve(result);
        }).catch((error) => {
            console.log(`Bot Info Request Error:\n${error}`);
            reject(error);
        });
    });
}
> node .\main.js
[ { name: 'お問い合わせフォーム君',
    photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
    useCallback: false,
    tenantId: xxxxxxxx,
    createdTime: 1544425101360,
    modifiedTime: 1544425101360,
    description: 'googleのフォームからのメッセージを受けてサンプル太郎に送信します。',
    managers: [ 'xxxx@yyy-zzz' ],
    submanagers: [],
    domainInfos: [ [Object] ],
    useGroupJoin: true,
    useDomainScope: false,
    i18nNames: [],
    i18nPhotoUrls: [],
    i18nDescriptions: [],
    botNo: xxxxxxxx },
// 中略
  { name: '湯婆婆',
    photoUrl: 'https://jp1-common.worksmobile.com/gateway/image/view?path=/bot_profile/xxxxxx.png',
    useCallback: true,
    callbackUrl: 'https://callback.com/',
    callbackEvents: [ 'text' ],
    tenantId: xxxxxxxx,
    createdTime: 1605684025704,
    modifiedTime: 1605684176017,
    description: '流行りの湯婆婆をBOTで作ってみました。',
    managers: [ 'xxxx@yyy-zzz' ],
    submanagers: [],
    domainInfos: [ [Object] ],
    useGroupJoin: false,
    useDomainScope: false,
    i18nNames: [],
    i18nPhotoUrls: [],
    i18nDescriptions: [],
    botNo: xxxxxxxx } ]

descriptioncallbackUrl など、Bot の詳細情報を取得することができましたね!

三回も非同期処理の練習をしたので、これでもうバッチリ OK 大丈夫!

右手に async! 左手に await
合体させて非同期処理!

はい、お粗末様でしたー( ゚Д゚)

コード全体

最後に、コード全体をさらしておきます。

main.js
// LINE WORKS 認証情報
const CONSTS = {
    API_ID : "xxxxxxxx",
    CONSUMER_KEY : "xxxxxxxxxx",
    SERVER_ID : "xxxxxxxxxx",
    PRIVATEKEY : "xxxxxxxxxxxxxxxxxxxxxxyyzz" // 認証キー
}
// module
const request = require("request-promise");

// main
main();

async function main() {
    const token = await getServerToken();
    const bots = await getBotLists(token);
    let botLists = [];
    for(let i = 0; i < bots.length; i++){
        botLists.push(await getBotInfo(bots[i].botNo, token));
    }
    console.log(botLists);
}
/**
 * LINE WORKS の Server Token を取得
 * @return {Promise object} Server Token
 */
async function getServerToken() {
    const jwt = require("jsonwebtoken");
    const iss = CONSTS.SERVER_ID;
    const iat = Math.floor(Date.now() / 1000);
    const exp = iat + 60 * 60; // 有効期間を 1時間に設定
    const cert = CONSTS.PRIVATEKEY;
    const options = {
        method: "POST",
        url: `https://auth.worksmobile.com/b/${CONSTS.API_ID}/server/token`,
        headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
        form: {
            grant_type: encodeURIComponent("urn:ietf:params:oauth:grant-type:jwt-bearer"),
            assertion: jwt.sign({ iss: iss, iat: iat, exp: exp }, cert, { algorithm: "RS256" }),
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            const result = JSON.parse(res);
            if (result.message) throw "Could not get token";
            resolve(result);
        }).catch((error) => {
            console.log(`Auth Error: ${error}`);
            reject(error);
        });
    });
}
/**
 * LINE WORKS のトーク Bot リストを取得
 * @param {object} token LINE WORKS Server Token
 * @return {Promise object} トーク Bot リスト
 */
async function getBotLists(token) {
    const options = {
        method: "GET",
        url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot`,
        headers: {
            "Content-Type": "application/JSON",
            "consumerKey": CONSTS.CONSUMER_KEY,
            "Authorization": `${token.token_type} ${token.access_token}`
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            const result = JSON.parse(res);
            resolve(result.bots);
        }).catch((error) => {
            console.log(`Bot List Request Error:\n${error}`);
            reject(error);
        });
    });
}
/**
 * LINE WORKS トーク Bot の詳細情報を加えたリストを作成
 * @param {object} bots LINE WORKS トーク Bot の botNo リスト
 * @param {object} token LINE WORKS Server Token
 * @return {Promise object} 詳細なトーク Bot リスト
 */
async function getBotInfo(botNo, token) {
    const options = {
        method: "GET",
        url: `https://apis.worksmobile.com/r/${CONSTS.API_ID}/message/v1/bot/${botNo}`,
        headers: {
            "Content-Type": "application/JSON",
            "consumerKey": CONSTS.CONSUMER_KEY,
            "Authorization": `${token.token_type} ${token.access_token}`
        }
    };
    return new Promise((resolve, reject) => {
        request(options).then((res) => {
            let result = JSON.parse(res);
            result.botNo = botNo;
            resolve(result);
        }).catch((error) => {
            console.log(`Bot Info Request Error:\n${error}`);
            reject(error);
        });
    });
}

おわりに

ここまでお付き合いいただきありがとうございました。

早いもので、独学で Node.js を勉強し始めてから 2年が経ちました。
非同期処理さんとも、何だかようやく仲良くなれた気がします♪

最近はソロ開発ばかりではなく先輩と組むこともあり、色々と吸収させてもらっています。
ほんと、良い先輩に恵まれた!
メキメキ成長して、恩返しをしたい所存であります。

次は何を書こうかな~。
ではまた!(^^)/

参考にさせていただきました。

LINE WORKS Developers
Windows10にVSCodeインストール

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js: Google Cloud Translation API の使い方 (Advanced)

advanced01.js
#! /usr/bin/node
//
//  advanced01.js
//
//                      Nov/25/2000
//
// ---------------------------------------------------------------
'use strict'

// ---------------------------------------------------------------
const projectId = "project-translation"
const location = 'global'
const text = "Es war einmal ein kleines Mädchen."

const {TranslationServiceClient} = require('@google-cloud/translate')

const translationClient = new TranslationServiceClient()

async function translateText() {
    const request = {
        parent: `projects/${projectId}/locations/${location}`,
        contents: [text],
        mimeType: 'text/plain',
        sourceLanguageCode: 'de',
        targetLanguageCode: 'ja',
    }

    try {
        const [response] = await translationClient.translateText(request)

        for (const translation of response.translations) {
            console.log(`Translation: ${translation.translatedText}`)
        }
        console.error ("*** 終了 ***")
    } catch (error) {
        console.error(error.details)
    }
}

// ---------------------------------------------------------------
console.error ("*** 開始 ***")
translateText()
// ---------------------------------------------------------------

実行方法

export GOOGLE_APPLICATION_CREDENTIALS=./***.json
#
./advanced01.js

実行結果

*** 開始 ***
Translation: 昔々、小さな女の子がいました。
*** 終了 ***
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node.js: Google Cloud Translation API の使い方 (Basic)

basic01.js
#! /usr/bin/node
//
//  basic01.js
//
//                      Nov/25/2000
//
// ---------------------------------------------------------------
'use strict'

async function translateText() {
    let [translations] = await translate.translate(text, target)
    translations = Array.isArray(translations) ? translations : [translations]
    console.log('Translations:')
    translations.forEach((translation, i) => {
        console.log(`${text[i]} => (${target}) ${translation}`)
        console.error ("*** 終了 ***")
    })
}

// ---------------------------------------------------------------
console.error ("*** 開始 ***")
const {Translate} = require('@google-cloud/translate').v2

const translate = new Translate()


const text = "Es war einmal ein kleines Mädchen."
const target = "ja"
translateText()

// ---------------------------------------------------------------

実行方法

export GOOGLE_APPLICATION_CREDENTIALS=./***.json
#
./basic01.js

実行結果

*** 開始 ***
Translations:
E => (ja) 昔々、小さな女の子がいました。
*** 終了 ***
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

alpine環境でyarn add cwebp-binする

やりたいこと

  • alpineベースのdockerコンテナにnodeのパッケージ cwebp-binをインストールする

やったこと

  • alpine linuxはOSレベルで画像処理ライブラリが欠けてるので追加でインストールを実施するべし
    • apk add make automake nasm g++ libc6-compat libjpeg-turbo-dev libpng-dev ...でいけるはず

そもそもの経緯

  1. まあまあデカめのプロダクトのCI設定の見直しをしていた
  2. ビルドにalpine環境を使えば高速化できるのでは?と思い至った
  3. やってみると yarnを実行したときのcwebp-binのインストールで何故かコケる

そもそもcwebp-binとは

https://github.com/imagemin/cwebp-bin
https://www.npmjs.com/package/cwebp-bin

WebP is a new image format that provides lossless and lossy compression for images on the web. WebP lossless images are 26% smaller in size compared to PNGs. WebP lossy images are 25-34% smaller in size compared to JPEG images at equivalent SSIM index.

DeepLによる翻訳

WebP は、ウェブ上の画像をロスレスかつロージーに圧縮する新しい画像フォーマットです。WebPロスレス画像はPNGと比較してサイズが26%小さくなります。WebPの可逆圧縮画像は、同等のSSIMインデックスのJPEG画像と比較して、サイズが25~34%小さくなります。

つまるところ、元画像をいい感じに圧縮しユーザーエンドへ届ける技術のひとつ。

エラー全文

使用するdocker imageは node:14.14.0-alpine とした。

docker-compose.yml
version: '3'
services:
  alpine:
    image:
      node:14.14.0-alpine
    container_name: 'alpine'
    tty: true
$ docker-compose up -d alpine
$ docker exec -it alpine sh
# ここからalpineコンテナの中
$ yarn add cwebp-bin
yarn add v1.22.5
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
error /node_modules/cwebp-bin: Command failed.
Exit code: 1
Command: node lib/install.js
Arguments: 
Directory: /node_modules/cwebp-bin
Output:
⚠ spawn /node_modules/cwebp-bin/vendor/cwebp ENOENT
  ⚠ cwebp pre-build test failed
  ℹ compiling from source
  ✖ Error: Command failed: /bin/sh -c ./configure --disable-shared --prefix="/node_modules/cwebp-bin/vendor" --bindir="/node_modules/cwebp-bin/vendor"
./config.guess: line 1049: objdump: not found
configure: error: in `/tmp/66cb2ebc-9291-412c-8f6a-6e830504eed0':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details

checking build system type... x86_64-pc-linux-musl
checking host system type... x86_64-pc-linux-musl
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... ./install-sh -c -d
checking for gawk... no
checking for mawk... no
checking for nawk... no
checking for awk... awk
checking whether make sets $(MAKE)... no
checking whether make supports nested variables... no
checking whether make supports the include directive... no
checking for gcc... no
checking for cc... no
checking for cl.exe... no

    at /node_modules/execa/index.js:231:11
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async Promise.all (index 0)
info Visit https://yarnpkg.com/en/docs/cli/add for documentation about this command.

objdump: not found :thinking:
なんだか低水準言語なアプローチをしている想像ができる。

対処

「cwebp-bin alpine」でググるとそれっぽいのがヒットしたので引用。

https://github.com/cyrilwanner/next-optimized-images/issues/12#issuecomment-378743176

First of all, image optimization is quite a heavy task, this is why imagemin (the underlying library) is using compiled binaries for it. But they are obviously not compiled for the alpine docker images and some libraries required for building them are missing because the goal of the alpine images is to have the smallest size possible.

DeepLによる翻訳

まず第一に、画像の最適化は非常に重い作業で、そのために imagemin (基礎となるライブラリ) はコンパイル済みのバイナリを使用しています。しかし、それらは明らかにアルプスのドッカーイメージ用にコンパイルされていませんし、アルプスのイメージの目標は可能な限り小さいサイズにすることですから、それらを構築するために必要ないくつかのライブラリが欠けています。

また、実際に動作を確認できたdockerfileも提示されている。

FROM node:alpine

RUN apk add --no-cache \
    autoconf \
    automake \
    bash \
    g++ \
    libc6-compat \
    libjpeg-turbo-dev \
    libpng-dev \
    make \
    nasm

# build your next.js app now..

つまり、alpineはOSレベルで画像処理ライブラルが欠けてるのできちんと追加でインストールしようね、ということらしい。
実際にひとつひとつインストールして確かめたところ、apk add make automake nasm g++の4つをインストールした時点でエラーは発生しなくなった。
(が、気持ち的には libc6-compat libjpeg-turbo-dev libpng-dev あたりもインストールしておいたほうがよさそうに思う)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

近すぎると小池都知事が笑顔で『密です。』と連呼してくれるデバイスを作ったら腹筋が崩壊しそうになったので、皆さんにも試して欲しい。

怒涛の「密です。」攻撃。

私は直接新型コロナと戦ってはいませんが、医療従事者の端くれとして、最前線で戦っている仲間のためにも、少しでも新型コロナ感染予防につながるデバイスを世の中に生み出したいと思い、こちらのデバイスを作りました。

挙動は以下の通りです。


穏やかに、しかし、穏やかに、小池都知事が「密です。」と連呼し、緊急事態を伝えてくださいます。
・・・。
ごめんなさい。
自分で作っておきながら、怒涛の「密です。」攻撃を受け、僕の腹筋は崩壊してしまいました。
この感動?をぜひ皆さん味わっていただきたく、記事を書くことにしました。

この感動から、皆さんが少しでも密であることを避けるように意識してくれれば幸いです。

実際の利用現場も想定した場合についても、最後に記載しております。

下準備

1) VScodeのインストール
VScodeのインストールついては、googleなどで他の記事を検索してください。
2) node.jsとnpmのインストール
Macでの環境作りは、こちらの別の記事にまとめてあります。
よかったら、参考にしてください。
Macにnode.js,npmのインストール方法
3) obnizの購入と設定
obniz - developer's consoleに進み、自分のアカウントを作成。
obnizに関してはobnizハンズオンを参考させていただきました。

*ちなみにobnizとはwi-fi経由で自身のPCやスマホから制御できる、マイコンボード(マイクロコンピューターが搭載され、センサーからの入力を受け、アクチュレーターやインジケーターに出力できるデバイス)です。
詳しくは、obniz公式ページをご確認ください。

開発環境の準備

1) socialDistanceというフォルダを作成
2) VSCodeで上記フォルダを開き、フォルダ内にmitsuDeath.jsを作成
3) VSCode内でターミナルを開き、フォルダをnpm管理できるように初期化

terminalコマンド
$ npm init -y

4) フォルダ内にobniz用のパッケージをnpmでインストール

terminalコマンド
$ npm i obniz

システムの概要

スクリーンショット 2020-11-25 12.41.00.png

LINE Notifyの設定

今回、密な状態になってしまったことを、LINE Notifyを利用して通知を出すようにします。

1) LINE notifyにログイン
https://notify-bot.line.me/ja/

2) ログイン後、マイページに進む

3) アクセストークンの発行に進む

4) トークン名を入力し、1:1を選択するとトークンが発行できる

アクセストークンの発行方法については、
LINE Notifyのnpmライブラリ作った
を参考にしてください。

mitsuDeath.jsのコード

mitsuDeath.js
'use strcit';

const axios = require('axios'); // axiosの呼び出しを追加
const qs = require('querystring'); // 送信するデータの整形
const Obniz = require('obniz');
const obniz = new Obniz('ご自身のobnizIDを入力'); 

const LINE_TOKEN = 'LINE notifyのアクセストークンを入力';

obniz.onconnect = async function () {
    //obnizの初期化
    obniz.display.clear();
    obniz.display.print('obniz Ready');

    const config = {
      url: 'https://notify-api.line.me/api/notify',
      method: 'post',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': `Bearer ${LINE_TOKEN}`
      },
      data: qs.stringify({
        message: `密です!`,
        imageFullsize :'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',
        imageThumbnail :'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',
        // type: 'image',
        // originalContentUrl: 'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',//ここのNetlifyに取り込んだ画像のURLを入力
        // previewImageUrl: 'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',//ここのNetlifyに取り込んだ画像のURLを入力
      })
    };
     // 超音波距離センサを利用
    const hcsr04 = obniz.wired('HC-SR04', {
        gnd: 3,
        echo: 2,
        trigger: 1,
        vcc: 0,
    });

    // LEDを利用
    const led = obniz.wired('LED', { anode: 10, cathode: 11 });

    // setIntervalで定期実行
    setInterval(async () => {
        // 距離を取得
        let distance = await hcsr04.measureWait();
        // 小数点以下がたくさんあるのでここでは整数にします
        distance = Math.floor(distance);
        // 距離をコンソールに表示
        console.log(distance + ' mm');
        // 距離をobnizディスプレイに表示
        obniz.display.clear();
        obniz.display.print(distance + ' mm');
        // 距離によって判定
        if (distance < 50.0) {
            // 50mm = 5cm 未満の場合 obnizディスプレイ表示
            obniz.display.clear();
            obniz.display.print('Too close!!');
            // LEDオン
            led.on();
            try {
                // configの設定を元に送信
                const response = await axios.request(config);
                // データ送信が成功するとレスポンスが来る
                console.log('レスポンスを受信しました:' + response.data);
                console.log('POSTに成功しました!');
                //await obniz.wait(10000);
              } catch (error) {
                // ネットワークに接続できてない、サーバーが落ちてる、URLが違うなど様々なエラー
                console.log('POSTに失敗しました……');
                console.error(error);
              }
        } else {
            // 50mmを超えるときは常にLEDオフ
            led.off();
        }
    }, 1000); // 1000ミリ秒 = 1秒ごとに実行

}

デバイス作り

今回用意したもの

1) ブレッドボード
2) 超音波測距センサ
3) LEDライト
4) ジャンパワイヤ x6

配線完成図

スクリーンショット 2020-11-25 12.42.57.png

「密です。」「密です。」「密です。」・・・

配線が完了したら、ターミナルに以下のコマンドを入力します。

terminalコマンド
$ node mitsuDeath.js

超音波測距センサに近づきすぎると、小池都知事から怒涛の「密です。」攻撃を受けるので注意してください。
ちなみに、近づくとLEDライトも点灯し、obniz上にも「Too close!!」と表示されます。

今後の課題

① 本当は、一度通知が出たら、しばらく通知しないように設定するつもりでしたが、現在の方が緊急事態感が伝わるので、現在のままにしました。
② obnizの電源の問題もありますが、首から吊り下げながら起動することができるようにすれば、例えば、会食の参加者全員にこのデバイスを配布し、LINE notifyを会食の参加者全員に通知するように設定することで、実際に使えるデバイスになると考えています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

近すぎると小池都知事が『密です。』と連呼するデバイスを作ったら腹筋が崩壊したので、皆さんにも試して欲しい。

怒涛の「密です。」攻撃。

私は直接新型コロナと戦ってはいませんが、医療従事者の端くれとして、最前線で戦っている仲間のためにも、少しでも新型コロナ感染予防につながるデバイスを世の中に生み出したいと思い、こちらのデバイスを作りました。

挙動は以下の通りです。


穏やかに、しかし、穏やかに、小池都知事が「密です。」と連呼し、緊急事態を伝えてくださいます。
・・・。
ごめんなさい。
自分で作っておきながら、怒涛の「密です。」攻撃を受け、僕の腹筋は崩壊してしまいました。
この感動?をぜひ皆さん味わっていただきたく、記事を書くことにしました。

この感動から、皆さんが少しでも密であることを避けるように意識してくれれば幸いです。

実際の利用現場も想定した場合についても、最後に記載しております。

下準備

1) VScodeのインストール
VScodeのインストールついては、googleなどで他の記事を検索してください。
2) node.jsとnpmのインストール
Macでの環境作りは、こちらの別の記事にまとめてあります。
よかったら、参考にしてください。
Macにnode.js,npmのインストール方法
3) obnizの購入と設定
obniz - developer's consoleに進み、自分のアカウントを作成。
obnizに関してはobnizハンズオンを参考させていただきました。

*ちなみにobnizとはwi-fi経由で自身のPCやスマホから制御できる、マイコンボード(マイクロコンピューターが搭載され、センサーからの入力を受け、アクチュレーターやインジケーターに出力できるデバイス)です。
詳しくは、obniz公式ページをご確認ください。

開発環境の準備

1) socialDistanceというフォルダを作成
2) VSCodeで上記フォルダを開き、フォルダ内にmitsuDeath.jsを作成
3) VSCode内でターミナルを開き、フォルダをnpm管理できるように初期化

terminalコマンド
$ npm init -y

4) フォルダ内にobniz用のパッケージをnpmでインストール

terminalコマンド
$ npm i obniz

システムの概要

スクリーンショット 2020-11-25 12.41.00.png

LINE Notifyの設定

今回、密な状態になってしまったことを、LINE Notifyを利用して通知を出すようにします。

1) LINE notifyにログイン
https://notify-bot.line.me/ja/

2) ログイン後、マイページに進む

3) アクセストークンの発行に進む

4) トークン名を入力し、1:1を選択するとトークンが発行できる

アクセストークンの発行方法については、
LINE Notifyのnpmライブラリ作った
を参考にしてください。

mitsuDeath.jsのコード

mitsuDeath.js
'use strcit';

const axios = require('axios'); // axiosの呼び出しを追加
const qs = require('querystring'); // 送信するデータの整形
const Obniz = require('obniz');
const obniz = new Obniz('ご自身のobnizIDを入力'); 

const LINE_TOKEN = 'LINE notifyのアクセストークンを入力';

obniz.onconnect = async function () {
    //obnizの初期化
    obniz.display.clear();
    obniz.display.print('obniz Ready');

    const config = {
      url: 'https://notify-api.line.me/api/notify',
      method: 'post',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Authorization': `Bearer ${LINE_TOKEN}`
      },
      data: qs.stringify({
        message: `密です!`,
        imageFullsize :'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',
        imageThumbnail :'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',
        // type: 'image',
        // originalContentUrl: 'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',//ここのNetlifyに取り込んだ画像のURLを入力
        // previewImageUrl: 'https://musing-jennings-8b8193.netlify.app/mitsu.jpg',//ここのNetlifyに取り込んだ画像のURLを入力
      })
    };
     // 超音波距離センサを利用
    const hcsr04 = obniz.wired('HC-SR04', {
        gnd: 3,
        echo: 2,
        trigger: 1,
        vcc: 0,
    });

    // LEDを利用
    const led = obniz.wired('LED', { anode: 10, cathode: 11 });

    // setIntervalで定期実行
    setInterval(async () => {
        // 距離を取得
        let distance = await hcsr04.measureWait();
        // 小数点以下がたくさんあるのでここでは整数にします
        distance = Math.floor(distance);
        // 距離をコンソールに表示
        console.log(distance + ' mm');
        // 距離をobnizディスプレイに表示
        obniz.display.clear();
        obniz.display.print(distance + ' mm');
        // 距離によって判定
        if (distance < 50.0) {
            // 50mm = 5cm 未満の場合 obnizディスプレイ表示
            obniz.display.clear();
            obniz.display.print('Too close!!');
            // LEDオン
            led.on();
            try {
                // configの設定を元に送信
                const response = await axios.request(config);
                // データ送信が成功するとレスポンスが来る
                console.log('レスポンスを受信しました:' + response.data);
                console.log('POSTに成功しました!');
                //await obniz.wait(10000);
              } catch (error) {
                // ネットワークに接続できてない、サーバーが落ちてる、URLが違うなど様々なエラー
                console.log('POSTに失敗しました……');
                console.error(error);
              }
        } else {
            // 50mmを超えるときは常にLEDオフ
            led.off();
        }
    }, 1000); // 1000ミリ秒 = 1秒ごとに実行

}

デバイス作り

今回用意したもの

1) ブレッドボード
2) 超音波測距センサ
3) LEDライト
4) ジャンパワイヤ x6

配線完成図

スクリーンショット 2020-11-25 12.42.57.png

「密です。」「密です。」「密です。」・・・

配線が完了したら、ターミナルに以下のコマンドを入力します。

terminalコマンド
$ node mitsuDeath.js

超音波測距センサに近づきすぎると、小池都知事から怒涛の「密です。」攻撃を受けるので注意してください。
ちなみに、近づくとLEDライトも点灯し、obniz上にも「Too close!!」と表示されます。

今後の課題

① 本当は、一度通知が出たら、しばらく通知しないように設定するつもりでしたが、現在の方が緊急事態感が伝わるので、現在のままにしました。
② obnizの電源の問題もありますが、首から吊り下げながら起動することができるようにすれば、例えば、会食の参加者全員にこのデバイスを配布し、LINE notifyを会食の参加者全員に通知するように設定することで、実際に使えるデバイスになると考えています。

その他の記事

誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 環境構築をしてみよう!

Homebrew をインストール

まずcreate-react-appをインストールするため、Homebrewをインストールします。以下のコードをターミナルに打ち込みましょう。

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"

次にインストールできたか確認します。

brew -v

バージョンが出てきたらインストール完了。

nodebrew をインストール

brew install nodebrew

数分待ちます。そして確認します。

nodebrew -v

node をインストール

nodebrew install stable

インストールしたバージョンの確認をします。

nodebrew ls

インストールしたバージョンが出てくるので、覚えときましょう。currentがnoneになっていると思うので、バーいジョンを指定してあげます。

nodebrew use インストールされたバージョンを入力(例 v12.16.1)

次にnodeを使うためパスを入力します。

echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.bash_profile

nodeのバージョンを確認。

node -v

npm 入っているか確認します。

npm -v

開発したいファイルに移動します。

cd (例 Projects)

移動したらreactのインストールです。

npx create-react-app (つくりたいファイル名)

インストールしたら、エディタで開きターミナルで

npm run build

インストール終わったら

npm start

をして環境構築完了です!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む