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

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

2017年06月

facebook を使っていると、「今日は○○○さんの誕生日です!」というお知らせが届きますよね:
2017062101


ある程度以上の繋がりがあると、体感的にはほぼ毎日のようにこの機能を目にすることになります。もちろんちゃんと心の籠もったメッセージを作って、思い出の画像も用意して、・・・という人もいますが、中には「メッセージ考えるのも面倒くさい、そんなにお世話になってるわけでもないし、ってかこいつ誰だっけ?どんなメッセージを送るのがいいのかなあ」と頭を悩ませることもあります。そんなあなたの大切な時間を節約する Chrome プラグインを作ってみました。

このプラグインを Chrome にインストールして有効にすると、facebook の誕生日ページに移動して、そこに今日が誕生日でまだバースデーメッセージを送っていない人がいると、自動的に(あらかじめ用意した)メッセージを入力してくれます(自動投稿まではしません)。そのままで良ければ後は投稿するだけ、「この人にはちゃんとメッセージを書きたいなあ」という場合はそこから編集することもできます:
2017062100

(↑こんな感じ。自動的にバースデーメッセージが入力された状態になります)


プラグイン自体は github 上に公開しておきました:
https://github.com/dotnsf/fb_birthday_extension


上記ページから git clone するかダウンロード&展開して、ローカルシステム上に以下の3つのファイルが存在しているディレクトリを用意します:
2017062102


この中の script.js というファイルが拡張機能の本体となる部分です。中身は結構シンプルにこんな感じ:
var birthday_msg = 'ハッピーハッピーバースデー♪';

$(function(){
 $("#events_birthday_view div:eq(0) ul li ._42ef ._6a ._6b:eq(1) div div:eq(1) textarea").each(function(){
  var textarea = $(this);
  textarea.val( birthday_msg );
 });
});

1行目の birthday_msg 変数がバースデーメッセージです。必要に応じてこの内容を書き換えて使ってください。


では実際にインストールしてみます。Chrome を起動して拡張機能の画面(右上のメニューから「その他のツール」-「拡張機能」)に移動します:
2017062106


現在インストール済みの Chrome 拡張の一覧が表示されます。ここで「デベロッパーモード」が有効になっていることを確認し、「パッケージ化されていない拡張機能を読み込む」をクリックします:
2017062103


先程ダウンロードした3つの拡張用ファイルがあるディレクトリを指定します:
2017062104


すると "FB Birthdah" という名前の、この拡張機能が導入されます。「有効」にチェックを入れると、実際に facebook の誕生日ページで動くようになります。なお、導入後に script.js の中身を書き換えた場合は、この画面内の「リロード」をクリックすると、変更が反映されるようになります:
2017062105


これで上記で紹介したような心の籠もっていないバースデーメッセージを自動作成することができるようになっている、はず:
2017062101


※このプラグインはあくまで誕生日ページを参照した時に動作するもので、↓こんな感じのダイアログで出た場合は動いてくれません、あしからず:
20170621


自分が苦手なので、備忘録的に残しておきます。

まずやりたいことはこれです。RDB (今回は MySQL)のこんなテーブル(todos)があったとします。email の人の ToDo を管理するようなテーブルだと考えてください:
列名列型目的
idintプライマリキー、自動インクリメント
emailvarchar(50)メールアドレス
bodyvarchar(255)本文
createdlongレコードを作成した日時のタイムスタンプ


これだけであれば、以下の SQL でテーブルを定義するようなものだと思います:
create table todos(
  id int primary key auto_increment,
  email varchar(50),
  body varchar(255),
  created long
);

ここに運用上の条件を1つ加えます。1つの email に対しては最新の body のみを有効としたいのです。要するに同じ email に異なる body があっても(なくても)いいのですが、有効なものは最新の(created が最も大きな)body のみとして扱えるようにしたい、というものです。

例えばテーブルの中身がこのようになっているものとします(タイムスタンプの値がいいかげんですが、大小関係だけ分かればいいものとします):
idemailbodycreated
1aaa@xxx.jpToDo その110
2bbb@xxx.jpToDo その220
3aaa@xxx.jpToDo その330


全部で3件のレコードがありますが、email = aaa@xxx.jp のレコードが2件あります。この場合は created が最大である id=3 のレコードのみが有効と考えます(id=1 のレコードは削除されていても構いません)。

このような条件を付けた上で、「有効なレコードだけの一覧を取り出したい」のですが、どのようにデータベースを定義して運用すればいいでしょう?

まず email 列は実質的にユニークになるとも考えられます。が、テーブル定義時に UNIQUE 属性を付けてしまうと、INSERT 時にエラーとなってしまうので「まず同じ email のデータが存在しているかどうかを確認し、存在していなければ INSERT 、していれば UPDATE するような SQL を実行する」ことになります(こうすると実は UNIQUE 属性はあってもなくても同じと言えます)。INSERT 時の処理がちと面倒ですが、読み込み時にはシンプルに "select * from todos" だけで実現可能な方法といえます。

次に email はユニークとは考えずに、とにかくレコードは全て INSERT して、有効なレコードを取り出す時に工夫する、という方法もあります。これは SQL としては以下を実行すればよいことになります:
> select * from todos where ( email, created ) in (select email,max(created) as max_created from todos group by email);

これはこれで実現可能な方法の1つです。ただ上記の例とは逆に、INSERT は無条件に行いますが、読み込み時に比較的複雑な SQL が実行されることになります。また有効でない(使われることのない)レコードがテーブル内にずっと残ってしまうことになります。


ではこれらのいいとこ取りを考えてみます。つまり、以下のようなロジックです:
(1) INSERT は無条件に行い、
(2) INSERT 直後に有効でないレコードがあったら削除し、
(3) 検索はシンプルに "select * from todos" だけで行えるようにする


(1) の直後に (2) が出来れば、(1) も (3) もシンプルな処理になるので理想的とも言えます。で、前書きが長くなりましたが、今回はこの (2) を実現するのに苦労した話です(苦笑)。

まず、このケースでの「有効なレコード」を取り出す場合の SQL クエリーは上記のようにこのようになります:
> select * from todos where ( email, created ) in (select email,max(created) as max_created from todos group by email);

(結果)
idemailbodycreated
2bbb@xxx.jpToDo その220
3aaa@xxx.jpToDo その330

ということは、「有効でないレコード」(削除対象のレコード)はこの SQL で取り出せます:
> select * from todos where ( email, created ) not in (select email,max(created) as max_created from todos group by email);

(結果)
idemailbodycreated
1aaa@xxx.jpToDo その110

ここまでできれば後は単純に削除対象のレコードを削除すればよいので、サブクエリーを使ってこんな SQL を実行するだけ・・・
> delete from todos where id in ( select id from todos where ( email, created ) not in (select email,max(created) as max_created from todos group by email) );

と思ったのですが、これを実行すると以下のようなエラーが発生します:
ERROR 1093 (HY000): You can't specify target table 'todos' for update in FROM clause

これがサブクエリーのややこしい(というか、自分がよく分かっていなかった)所です。サブクエリーを使った時に意識する構文がいくつか存在しています:
https://dev.mysql.com/doc/refman/5.6/ja/subquery-errors.html

↑これの「サブクエリー内の誤って使用されているテーブル」に書かれた項目が今回のエラーの原因でした。サブクエリーの FROM で指定されるテーブルと、更新系のテーブルに同じものを使用することができない、というルールです。

これを解決するにはサブクエリー内の FROM 部分("todos" と書かれた部分)を更にサブクエリーにしてテンポラリテーブルにします。上記例だとこんな感じ:
> delete from todos where id in ( select id from (select * from todos) where ( email, created ) not in ( select email,max(created)
 as max_created from (select * from todos) group by email ) );

ただこれだと該当部分が2箇所あり、今度は複数のテンポラリテーブルは異なるエイリアスを持たなければならない、という制約に引っかかってエラーになってしまいます:
ERROR 1248 (42000): Every derived table must have its own alias

というわけで、最終的な答はこちら。テンポラリテーブルに異なるエイリアスを指定して完成です:
> delete from todos where id in ( select id from (select * from todos) as temp1 where ( email, created ) not in ( select email,max(created)
 as max_created from (select * from todos) as temp2 group by email ) );

こんな SQL 、何も見ずに一発でさらっと書ける人いるんかな・・・


最近、ボット関連の 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 アドレス制限が実現できます。

話題(?)のブロックチェーンを小型コンピュータであるラズベリーパイ(以下、「ラズパイ」)から操作する、ということに挑戦してみました。


まずは操作の対象となるブロックチェーン環境を用意します。今回は IBM のクラウドプラットフォームである IBM Bluemix から提供されているマネージドハイパーレジャー環境である IBM Blockchain サービスを使うことにします(2017/Jun/04 現在では HyperLedger Fabric v0.6 と v1.0 がベータ提供されています)。IBM Bluemix アカウントが必要になりますが、30日間無料試用も用意されているので、必要に応じてアカウントを取得してください。

IBM Bluemix にログイン後、米国南部データセンターになっていることを確認し、「サービスの作成」をクリックします:
2017060404


そして「アプリケーション・サービス」カテゴリ内にある "Blockchain" を探してクリック:
2017060405


利用する Blockchain の種類をプランから選択します。今回は無料の "Starter Developer plan(beta)" を選択します。ちなみにこのプランの場合、Hyperledger Fabric は v0.6 を使うことになり、4つのブロックチェーンピアが作成されます。最後に「作成」ボタンをクリック:
2017060406


しばらく待つと IBM Blockchain サービスが起動します。ここでダッシュボードに移動することもできますが、この後で利用する情報を先に取得しておきます。左ペインの「サービス資格情報」を選択:
2017060407


サービス資格情報から「資格情報の表示」をクリックして、表示される JSON テキストを全てコピーしておきます(この後、ラズパイ内で使います):
2017060408


改めて「管理」タブを選択し、「ダッシュボードを起動」ボタンをクリックして、ダッシュボード画面に切り替えておきましょう:
2017060409


IBM Blockchain のダッシュボード画面が表示されます。4つのピアが動いていますが、この時点ではまだチェーンコードは作られていないことが確認できます。これでブロックチェーン側の準備は完了です:
2017060401


では続けてラズパイ側の準備に取り掛かります。以下はラズパイ上での作業です(僕の検証では3Bを使いました)。ラズパイにはネットワークのセットアップが済んだ Raspbian Jessie を用意し、そこに Hyperledger Fabric Node SDK ごとサンプルコードを導入してデモアプリケーションを動かします。というわけでまずは Node.js と npm を用意します。まずはこのドキュメント内にあるように、Node.js v6.2.x ~ v6.10.x のバージョンの環境を用意します(以下は v6.10.3 の例です。v7.x は未対応):
$ sudo apt-get install -y nodejs npm
$ sudo npm cache clean
$ sudo npm install n -g
$ sudo n v6.10.3
$ node -v
v6.10.3  <- v6.10.3 の導入が完了

続けて npm を最新版(以下の例では v5.0.2)に更新します:
$ sudo npm update -g npm
$ sudo npm outdated -g
$ npm -v
v5.0.2  <- v5.0.2 の導入が完了

git でデモアプリをダウンロードし、必要なライブラリをインストールします:
$ git clone https://github.com/IBM-Blockchain/SDK-Demo.git
$ cd SDK-Demo
$ npm install

ダウンロードしたデモアプリの中(直下のディレクトリ)にある ServiceCredentials.json ファイルの中身を、上記でコピーしたサービス資格情報の JSON テキストの中身で全て書き換えます:
{
  "peers": [
    {
      "discovery_host": ******

      :
      :
"cert_path": "/certs/peer/cert.pem" }

これでラズパイ側の準備も完了しました。後は以下のコマンドを実行して、デモアプリを実行します:
$ node helloblockchain.js

以下のようにコマンドが次々を流れていきます。実際にはこの中でチェーンコードの初期化や、初期データ入力、データ変更などが行われています。最後に "Successfully ****" というメッセージがいくつか表示されていればコマンドは成功です:

2017060403


この最後のメッセージについて補足します。この helloblockchain.js ではチェーンコード初期化などの作業を行った後に「"a" という入れ物に 100 、"b" という入れ物に 200 のデータを保存」します(ここまでが初期化)。そして「"a" から "b" へ 10 移動」するようなトランザクションが実行されます(なのでここまでの処理が完了すると "a" に 90 、"b" に 210 入っている状態になるはずです)。最後に「"a" の値を取り出す」処理が実行されます。その辺りの様子が以下のメッセージから分かります("a" が 90 になっています):
  :
  :
Chaincode ID : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Successfully deployed chaincode: request={"fcn":"init","args":["a","100","b","200"],"chaincodePath":"chaincode","certificatePath":"/certs/peer/cert.pem"}, response={"uuid":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}

Successfully submitted chaincode invoke transaction: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"invoke","args":["a","b","10"]}, response={"uuid":"zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz"}

Successfully completed chaincode invoke transaction: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"invoke","args":["a","b","10"]}, response={"result":"Tx zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz complete"}
Custom event received, payload: "Event Counter is 1"


Successfully queried  chaincode function: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"query","args":["a"]}, value=90


※↑数値の移動は Submit して Complete しているのが別行で表示されているので、全部で4行表示されています。


なお初期化部分は最初の1回しか実行されないため、この helloblockchain.js を何度か実行すると、"a" の値が 10 ずつ減っていく様子がわかります:
$ node helloblockchain.js

  :
  :
Successfully queried  chaincode function: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"query","args":["a"]}, value=80

$ node helloblockchain.js

  :
  :
Successfully queried  chaincode function: request={"chaincodeID":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","fcn":"query","args":["a"]}, value=70


また、これらの処理を実行すると、ダッシュボード上からもチェーンコードが生成されて、実行されている様子が確認できます(初期状態だと画面更新が3分ごとなので、画面に反映されるまで少し時間がかかるかもしれません):
2017060402


まだ環境の一部がベータ版だったりする状態ではありますが、今やラズパイでもブロックチェーンが動くようになったんですねー。

このページのトップヘ