まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:node

タイトル通りですが、「『じゃじゃじゃじゃーん』をリズムに変換する Node-RED 用ノード」を作ってみました。


ほとんどの人はこの時点でどんな処理をするノードか理解ができてないと思うので、その補足です。このノードを作るきっかけになったのはこちらのまとめサイトでした:
【超能力者】知恵袋「何の曲か分かりますか?てんてててん てんてんてて」→「戦場のメリークリスマスですね」「なんで分かるんだ!?」


要するに「じゃじゃじゃじゃーん」という日本語テキスト(音階なし、正確なリズムもわからない)だけを頼りに「交響曲第5番、『運命』」のように曲を探し出すという(超)能力を持った人がいて、こりゃすげー!と。

なるほど、面白そうだ。最終的にはこれと同じことをシステム化できないかなあ、と考えているわけですが、その開発の途中で「とりあえずは日本語テキストからリズムを取り出す」仕組みを作り、そこから少々脱線して「その仕組を Node-RED のノードにしてみた」のが今回紹介するノード: node-red-contrib-dotnsf-jajajajan です:
https://www.npmjs.com/package/node-red-contrib-dotnsf-jajajajan

2017092300


利用するにはまず Node-RED 環境を(ローカルでも IBM Bluemix でも)用意し、そこからこのノードを導入します。ローカルであれば以下のコマンドでインストールできます:
$ npm install node-red-contrib-dotnsf-jajajajan

IBM Bluemix の Node-RED の場合はメニューからパレット管理機能を使ってインストールします:
2017101401


パレットの管理画面において「ノードを追加」タブを選び、検索文字列に "jajaja" などと入力すると見つかります(私のミスで複数バージョンが登録されてしまいました。最新バージョンのものを指定して「ノードを追加」してください):
2017101402


この状態で Node-RED を起動すると機能カテゴリ内に音符アイコンの "jajajajan" というノードが追加されて、キャンバス上で使えるようになります:
2017092301


このノードの挙動を確認するために以下のようなシンプルなフローを作ってみます。入力カテゴリの inject ノードと、出力カテゴリの debug ノード、そしてこの jajajajan ノードを配置し、以下のように配線します:
2017092302


inject ノードをダブルクリックして、Payload の種類をデフォルトの timestamp から string に変更し、その値を上記で示したような日本語のリズムテキストにします。以下の例ではまさに『運命』の有名な部分である「じゃじゃじゃじゃーん、ララララーン」としています(「じゃ」でも「ラ」でも同じように処理することができることを確認するため、わざと別の表現にしています)。ここで指定したテキストが msg.payload として jajajajan ノードに渡されて処理されます。入力し終わったら「終了」ボタン:
2017092303


これでフローの準備はできました。動作を確認するため「デプロイ」します:
2017092304


デプロイできたら、画面右ペインを "debug" タブに切り替えたことを確認してから、inject ノードの左にあるボタン部分をクリックします。クリックしたタイミングで指定したテキスト(今回の例では「じゃじゃじゃじゃーん、ララララーン」)が msg.payload として jajajajan ノードに渡されます。
2017092305


クリックすると、不定のタイミングで debug タブに文字が表示されてゆくことが確認できます。このタイミングに関してはネットワーク依存があることと、Node.js 上で動く Node-RED が非同期処理を行うことから必ずしも厳密なタイミングではないのですが、なんとなく「じゃ」「じゃ」「じゃ」「じゃーん」「ラ」「ラ」「ラ」「ラーン」のリズムで1行ずつ表示されている(はず)です:
2017092306


出力されている内容を詳しく見てみると、"8" とか "2" とか数字が表示されています。実はこれは音符の長さを表していて、"8" は「8分音符」、"2" は「半音符(2分音符)」を意味して、その長さに合わせて debug 出力も制御されています。現在の仕様では "8" では 0.5 秒後、"2" では 2 秒後に次の音符が表示されるようにしています(なので "8" の次はすぐに表示されますが、"2" の次が表示されるまでには少し時間がかかります)。結果的になんとなく「じゃ」「じゃ」「じゃ」「じゃーん」「ラ」「ラ」「ラ」「ラーン」というリズムに合わせて1行ずつ表示されているように見える・・・のではないかと思っています(苦笑):
2017092307


試しに『メヌエット』の出だし部分である「タンタタタタタンタンタン」で試すとこんな感じです。最初の「タン」が4分音符で、そこから8分音符が4回続いて・・・というリズムで解釈されていることがわかります:
2017092308


と、またこんなくっだらないノードを作ってしまった。。どうしても使ってみたいという変わった人がいたら使ってください。ちなみにソースコードはこちらです(日本語処理の部分はかなりハードコードに依存しているので、他の言語用に移植するのが難しいかも・・):
https://github.com/dotnsf/node-red-contrib-dotnsf-jajajajan


脱線した話を元に戻すと、このノードで実現している仕組みを使ってテキストをリズムに変えて、取り出したリズムの情報をあらかじめ楽譜を元にリズムだけを取り出した楽曲のデータベースから曖昧検索して近いものを探す、という方法で冒頭で紹介したような超能力者みたいな仕組みをシステム化できないか、と考えています。こちらも諦めているわけではなく、ある程度は動くようになっている(楽譜そのものと、楽譜から主旋律を探す仕組みに苦戦している)ので、人前に出せるようになったら公開するかもしれません。まあ、そうそう簡単なものではないと分かっている上に自分が音楽や楽器が得意ではないので、まだ躓いてすらいない落とし穴が待っているかもしれませんが・・・

複数の楽器向けに書かれている楽譜から主旋律(というのかな?人間がよく知ってるパート)を探す方法を知っている人がいたら教えていただけるとうれしいです。

IBM Bluemix からも提供されている IBM の DBaaS サービスである dashDB に Node.js からアクセスする方法を紹介します。実際には dashDB だけでなく、DB2 のサービスやオンプレミスデータベースへも同様に応用できますが、今回は Bluemix 上の DB2/dashDB 関連サービスを例に紹介します:
2017063002


dashDB は行指向/列指向型のテーブルをどちらも作成することができるリレーショナル・データベースのサービスですが、そのデータベースシステムとしての実体は IBM DB2 です。というわけで、このライブラリを使ってアクセスします:
https://www.npmjs.com/package/ibm_db

2017063001


まず以下のコマンドを実行して ibm_db をインストールします(このコマンドだけで DB2 ODBC Driver ごとインストールされます):
$ npm install ibm_db


そして以下のようなコードを用意して dashDB にアクセスします:

(settings.js)
exports.db_host = 'dashdb-entry-yp-XXXXXXXX.services.dal.bluemix.net';
exports.db_port = 50000;
exports.db_name = 'BLUDB';
exports.db_username = 'dashNNNN';
exports.db_password = 'PASSWORD';

(sample.js)
var ibm_db = require( 'ibm_db' );
var settings = require( './settings' );

var db_str = "DATABASE=" + settings.db_name
  + ";HOSTNAME=" + settings.db_host
  + ";UID=" + settings.db_username
  + ";PWD=" + settings.db_password
  + ";PORT=" + settings.db_port
  + ";PROTOCOL=TCPIP";
var sql = "select OBJECTID, NAME from SAMPLES.GEO_CUSTOMER limit 10";

ibm_db.open( db_str, function( err, conn ){
  if( err ) return console.log( err );

  conn.query( sql, function( err, data ){
    if( err ) console.log( err );
    else console.log( data );

    conn.close( function(){
      console.log( 'done.' );
    });
  });
});

settings.js の中身はユーザー名やパスワードといった dashDB に接続するためのサービス資格情報です。IBM Bluemix の画面から取得できる値を使って、実際の値で書き換えて使ってください:

2017063003


アプリケーションの実体は sample.js です。今回の例ではシンプルに接続して、サンプルデータとして GEO_CUSTOMER テーブルから OBJECTID と NAME の値を 10 件だけ取得する、という SQL (青字部分)を実行しました。また settings.js で定義した情報を取り出して接続文字列(赤字部分)を生成しています。

node コマンドで sample.js を実行して、以下のような結果が表示されれば成功です:
$ node sample.js
[ { OBJECTID: 1322, NAME: 'Kami Labarbera' },
  { OBJECTID: 1323, NAME: 'Johnathon Tunney' },
     :
  { OBJECTID: 1587, NAME: 'Althea Alcazar' } ]
done.







 

Node.js の処理内で unzip を実現する方法を紹介します。アップロードなどで zip ファイルを受取って、それをダイナミックに展開して特定のファイルを取り出す、といった仕組みを Node.js で実現する場合に必要な実装の例です。

この仕組みを実現するために、node-unzip という便利なライブラリがあるので、これを使うことにします:
https://www.npmjs.com/package/unzip

2017070601


fs ライブラリと併用して、こんな感じで使います(zip ファイル内の全ファイルを展開する例):
var fs = require( 'fs' );
var unzip = require( 'unzip' );

  :
  :

fs.createReadStream( './uploads/archive.zip' )
    .pipe( unzip.Extract( { path: './tmp/' } ) );

特定のファイルだけ(以下の例では拡張子が ".xml" のものだけ)を展開する場合は以下のようにします:
var fs = require( 'fs' );
var unzip = require( 'unzip' );

  :
  :

fs.createReadStream( './uploads/archive.zip' )
    .pipe( unzip.Parse() )
    .on( 'entry', function( entry ){
      var filename = entry.path;  //. ファイル名
      var type = entry.type;  //. 'Directory' または 'File'
      var size = entry.size;   //. ファイルサイズ

      if( filename.toLowerCase().endsWith( ".xml" ) ){
//. ".xml" で終わるファイル名だった場合のみ展開 entry.pipe( fs.createWriteStream( './tmp/' + filename ) ); }else{ entry.autodrain(); } });

そもそもの元ファイルが zip 圧縮されていたり、大量のファイルデータをアップロードして登録したい場合などは、目的のファイルを zip して、1回でまとめてアップロードできると便利なのですが、この方法であれば受け取った zip を展開して・・・という処理が実現できます。


最近、ボット関連の API や SDK をよく見るわりに、自分では使う機会が少なかったので、今更ながら1つ作ってみました。

今回作ってみたのは「シェルボット」です。名前の通り、ボットでシェルっぽいインターフェースを実現しました。何言ってるかよくわからない人はこちらの画面を参照ください:
2017060901



ボットっぽい対話型インターフェースを使って、対話しているかのようにシェルコマンドを実行します。今回はインターフェースに独自の HTML ページを用意しましたが、ぶっちゃけ LINE などでも(Messaging API を使うなどすれば)応用できると思っています。

#「チャットボット」とは違うけど「ボット」ですっ!


ソースコードを github で公開しておきました。セキュリティ的な面はまだまだ改良の余地があると思っています(rm コマンド他は実行不可にしていますが、本当は他にも実行を制御するコマンドを考慮したほうがいいと思う):
https://github.com/dotnsf/shellbot


↑このソースコードはビジュアルフローエディタである Node-RED で使う前提のものです。(IBM Bluemix などを使って)Node-RED 環境を用意し、func-exec ノードを追加した上でキャンバスに shellbot_nodes.txt の内容をインポートして、ブラウザで /shell にアクセスするだけです(詳しくは README.md 参照)。

#あー、あと今のところ Linux/Unix 系 OS に導入する前提です。


実際に Node-RED 環境上にインポートした様子のスクリーンショットがこちらです。2本のシンプルな HTTP リクエスト処理が定義されているだけのもの(うち1つは HTML 画面定義)ですが、本当にこれだけでこのシェルボットが動きます:
2017060902


実は今まで「ボット」=「チャットボット」=「コンシェルジュ」的なイメージを持っていてハードルが高かったのですが、今回のアプリを作った結果、難しく考えずに「既存アプリケーションのインターフェースを対話型にする」という実装もアリかな、と思うようになりました。


IBM Bluemix(Cloud Foundry) のプラットフォームが現在持っている制約の1つが「IPアドレスによるアクセス制限」に関するものです。残念ながら現時点ではベースとなっている Cloud Foundry にこの機能がなく、IBM Bluemix でも実装されていません。

というわけで、現状この機能を実現するにはプラットフォーム側ではなくアプリケーション側で用意する必要があります。Node.js アプリケーションでこれを実現する方法の1つとして、Express-IpFilter があります:
https://www.npmjs.com/package/express-ipfilter

2017060601



名前の通りの機能です。Node.js の Express フレームワークの中で IP アドレス制限(許可/拒絶)を簡単に実現することができます。

Express-IpFIlter をインストールするには npm で以下を実行します:
$ npm install express-ipfilter
(実際には express のインストールも必要です)

例えば、以下のような Node.js + Express のシンプルなアプリケーションを例に IP アドレス制御をかける例を紹介します。まずアプリケーション(app.js)は以下のような内容のものを使います:
//. app.js

var express = require( 'express' );
var app = express();

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

アプリケーションの中でスタティックディレクトリを /public/ に指定しています。そこで /public/index.html というファイルを用意し、中身を以下のようなものにします:
<html>
<head>
<title>Hello</title>
</head>
<body>
<h1>はろーわーるど</h1>
</body>
</html>

これを普通に Node.js で実行します:
$ node app

3000 番ポートを listen するように指定しているので、このポートを指定して同一マシンからウェブブラウザでアクセスすると、スタティックディレクトリに用意した index.html を見ることができます:
2017060602


ではこのアプリにアクセス制御をかけてみます。まずは '127.0.0.1' からのアクセスを拒絶するようなフィルターをかけてみます。app.js の内容を以下のように変更します(赤字部分を追加):
//. app.js

var express = require( 'express' );
var ipfilter = require( 'express-ipfilter' ).IpFilter;
var app = express();

var ips = [ '127.0.0.1' ];
app.use( ipfilter( ips ) );

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

この例では ips という配列変数を用意し、その中に対象とする IP アドレスを文字列配列の形式で代入します。そして ipfilter を有効にしています。

この状態で app.js を起動し、先程と同じように同一マシンからウェブブラウザでアクセスすると以下のようになります:
2017060603


IP アドレス制御が有効になり、アクセスは拒否されました。

では今後は逆に '127.0.0.1' からのアクセスのみ許可するようなフィルタをかけてみます。app.js の内容を以下のように変更します(青字部分を追加):

//. app.js

var express = require( 'express' );
var ipfilter = require( 'express-ipfilter' ).IpFilter;
var app = express();

var ips = [ '127.0.0.1' ];
app.use( ipfilter( ips, { mode: 'allow' } ) );

app.use( express.static( __dirname + '/public/' ) );

app.listen( 3000 );

この状態で再度アプリケーションを起動し、同様にウェブブラウザでアクセスすると、今度は元のように表示されます(つまり IP Filter はデフォルトだと拒絶、'allow' モードを指定すると許可のフィルタをそれぞれ有効にします):
2017060604


これでアプリケーションレベルでの IP アドレス制限が実現できます。

このページのトップヘ