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

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

2018/11

LINE の新しい開発フレームワークである LIFF(LIne Front-end Framework)の存在を教えていただいたので、試しに使ってみました。

この LIFF 最大の特徴はスマホの LINE アプリ内で HTML5 の Web アプリケーションを動かすことができる、という点です。この Web アプリケーション内で LIFF の SDK を併用すると、アプリケーションからLINE にメッセージや画像(やスタンプ)を送ることも可能です。

この LIFF を使って指で画面にお絵かきをするような HTML5 アプリを作り、その絵を LINE アプリに送信する、というアプリケーションを作ってみました。LINE はスタンプを送信する文化がメジャーですが、その延長で自分でその場でお絵かきした画像を送る、という使い方を想定したアプリケーションです。ソースコードは github で公開したので、興味ある方は実際に試してみてください:
2018112200



なお、今回提供しているソースコードでは IBM Cloud の NoSQL マネージド・データベースである Cloudant を使っているので、IBM Cloud のアカウントも必要です。無料のライトアカウントもあるので IBM Cloud のアカウントをお持ちでない場合はあわせて取得してください。


【IBM Cloudant の準備】
今回紹介するアプリケーションでは IBM Cloundant のインスタンスが必要です。IBM Cloud にログインし、インスタンスを1つ作成して、あらかじめ画像格納用のデータベースを用意しておきます。なお無料のライトプランの場合、Cloudant には 1GB までのデータしか格納することができないという点をご了承ください。

まず IBM Cloud にログインし、「リソースの作成」から IBM Cloudant を追加します。IBM Cloudant は「データベース」カテゴリ内に存在しています:
2018112201


作成時のロケーションはどこでも構いません(下図では「シドニー」を使っています)。ただ認証方法は従来の方式が利用できる方("Use both legacy credentials and IAM")を選択しておく必要があります:
2018112202


また価格プランも任意で構いませんが、Lite プランの場合は無料です。ただし 1GB までしかデータを格納することはできません。最後に「作成」をクリックしてインスタンスを作成します:
2018112203


Cloudant インスタンス作成直後の画面です。まずここから目的の(画像格納用の)データベースを作成するため、Cloudant のダッシュボードに移動します。下図の緑のボタンをクリック:
2018112204


Cloudant のダッシュボード画面に移動しました。データベース一覧が表示されていますが、作成直後の場合は何も存在してません。ここで「Create Database」をクリックして、データベースを作成します:
2018112205


作成するデータベースの名前を指定します。デフォルトでは "doodledb" を使います(後述の settings.js 内で指定されている名称と一致している必要があります)ので、とりあえず doodledb と入力して「Create」します:
2018112206


doodledb データベースが作成され、doodledb データベース内の文書一覧画面に移動しました(当然中身はありません)。左上の "<" 印をクリックしてデータベース一覧に戻ります:
2018112207


データベース一覧に戻りました。先程とは変わって、"doodledb" が一覧に含まれているはずです。これでデータベースの準備ができました:
2018112208


次にこのデータベースへ接続するための情報を確認します。IBM Cloud の画面に戻り、「サービス資格情報」タブを選択します。サービス資格情報は(この時点では)存在していないはずなので、「新規資格情報」ボタンをクリックして作成します:
2018112201


設定項目は変更せずに「追加」します:
2018112202


資格情報が追加されると先程の画面内に「資格情報の表示」メニューが追加されているはずです。ここをクリックして内容を確認します:
2018112203


資格情報の内容が JSON テキストで表示されます。今回必要なのは "username" と "password" の値です。これらの情報を後で利用するので、メモしておくか、コピペできるようにしておきます:
2018112204


これで IBM Cloudant の準備はできました。


【ランタイム(Web アプリケーション・サーバー)の準備】
次に LIFF が参照する Web アプリケーションサーバーを作成します。このサンプルは Node.js 上で動作するサンプルですが、今回は IBM Cloud 上にアプリケーション・サーバーを作成することにします(これもライトプランの無料枠内で作成することができます)。

Cloudant の時と同様に IBM Cloud にログイン後にリソース作成で「コンピュート」カテゴリの「SDK for Node.js」を選択します:
2018112201


ロケーションは「ダラス」を選択します(するとドメインは "mybluemix.net" となります)。そして「アプリ名」にアプリケーション名称を指定します。下図の例では "linedoodle" というアプリケーション名称としており、この場合のエンドポイント URL は https://linedoodle.mybluemix.net/ となります。なおアプリ名は他で使われていないものを指定する必要があります。自分の名前や日付を指定するなどして、ユニークなアプリケーション名称を指定してください。最後に「作成」をクリックします:
2018112202


しばらく待つとアプリケーションサーバーが起動します。これで LIFF アプリからアクセスできるパブリッククラウド上にアプリケーションサーバーが用意できました:
2018112203


【LINE Developers の準備】
LINE の LIFF アプリを作成するには LINE Developers でのチャネル登録が必要です。LINE Developer において新規にチャネルを作成します:
2018112204


チャネルの種類は「Messaging API」を選択します:
2018112205


アプリ名とプラン(Developer Trial)を選択し、作成します:
2018112206


作成すると、このチャネルにアクセストークンが割り当てられます。「アクセストークン(ロングターム)」と書かれた項目の値をこの後で使うことになるのでメモするか、コピペできるようにしておきます:
2018112207


また、ここで LIFF アプリとしてのアプリ URL を作成しておきましょう。LIFF タブを選んで「追加」ボタンをクリックします:
2018112209


名称、サイズ(Full または Tall のいずれかを選択)、そしてエンドポイント URL(上述の Node.js アプリケーションのエンドポイント URL)を入力して「保存する」をクリックします:
2018112210


すると LIFF アプリが登録され、LIFF URL が確認できるようになります。この LIFF URL は実際に LINE からアプリケーションを起動する際に利用します:
2018112208


これで LIFF アプリの登録も完了しました。あと少し。


【アプリケーションの準備】
上記で作成したアプリケーション・サーバーに HTML5 アプリケーションをデプロイします。まずはソースコードを入手します。github のリポジトリからソースコードをダウンロードまたは git clone して入手します:
https://github.com/dotnsf/line_liff_doodle

ソースコードがダウンロードできたら settings.js ファイルを環境にあわせて編集して保存します:
exports.db_username = '(Cloudant の username)';
exports.db_password = '(Cloudant の password)';
exports.db_name = 'doodledb';
exports.app_port = 0;

exports.base_url = 'https://(アプリケーション名).(ドメイン)/';

exports.line_access_token = '(LINE Developer で取得したロングタームアクセストークン)';

  :
  :

最後にこのアプリを上記で作成したアプリケーションサーバーにデプロイします。IBM Cloud では cf コマンドを使ってデプロイするので、未導入の場合は cf コマンドをダウンロード&インストールしてください:
https://github.com/cloudfoundry/cli/releases


そして cf コマンドを使って IBM Cloud へ IBM ID でログインし、アプリケーションをプッシュ(デプロイ)します:
$ cd (ソースコードのあるフォルダ)
$ cf login -a https://api.ng.bluemix.net/ -u (IBM ID)
$ cf push (アプリケーション名)

ここまでの作業が全て成功すると LIFF アプリが IBM Cloud 上にデプロイされ、LINE のチャット上で LIFF URL をクリックすると、LINE 上で同アプリケーションを呼び出すことができるようになります。


【LINE で動作確認】
では実際に LINE を使って動作確認してみます。上述で取得した LIFF URL ("line://app/" で始める文字列)をコピー&ペーストするなどして LINE のメッセージとして表示させます:
2018112201


この LIFF URL 部分をタップすると画面下部から LIFF アプリである HTML5 の Web アプリケーションの画面が表れます。今回の Web アプリはお絵かきアプリとなっていて、指でキャンバス上をドラッグして絵を描くアプリとなっています。色やフォントの太さを変えたり、失敗したら "reset" ではじめからやり直すこともできます:
2018112202


たとえばこんな絵を描いてみました。書き終わったら "post" をタップします:
2018112203


描いた絵が LINE の会話内に画像として表れます。その場でステッカーを作って送るような感覚です:
2018112204


・・・というアプリを作ってみたのでした。いかがでしょう? ちなみに iPhone 版の LINE でのみ動作確認しています。Android 版 LINE でうまく動かなかったらごめんなさい。


以前から似たようなアプリを Twitter 向けに作ってみたりしていて、同じようなのを LINE 向けにも作れたらいいなあ、と思っていました。が、LINE にはクライアント側を操作できる API が提供されておらず作ることができずに諦めていました。 が、今回 LIFF の存在を教えていただいて、「これならできるかも・・」と期待してがんばってみたら、なんとか実現できた、という経緯です。アプリ実装の詳細についてはまた別の機会にでも。

ある意味で LINE のステッカービジネスを根底から否定(苦笑)するアプリなので、あまり LINE 受けはよくないかもしれません。(^^; でも教えていただいたおかげで実現することができました。この場をお借りしてお礼申し上げます。


そうそう。繰り返しになりますが、IBM Cloud の(無料の)ライトアカウントを使って作成した場合、データベースには 1GB ぶんの画像しか格納できません。定期的にデータを削除するとか、データベースを消して作り直すとかして対応してください。



(参考)
LIFF API リファレンス


IBM Watson をはじめとした IBM Cloud から提供されているサービスの認証方法に IAM が採用され始めています。以前に IBM Watson サービスで IAM を使う方法についてはこちらのブログエントリでも紹介しました:
IBM Watson のアプリケーションを IAM(API Key) 認証/認可に移行する


今回は IBM Cloudant を IAM で利用する方法を紹介します。なお 2018/11/22 時点で IBM Cloudant が IAM でないと認証できなくなるというアナウンスがされているわけではないことを書き添えておきます(つまり現時点では従来の認証方法と併用されており、すぐに移行作業が必要になるわけではありません)。


まずは IAM に対応した IBM Cloudant のサービスインスタンスを用意する必要があります。現在 IBM Cloudant のサービスインスタンスを作成すると IAM のみで利用するタイプか、従来の認証と IAM の両方が利用できるタイプかを選択して作成することができます。どちらでもいいのですが IAM 対応のインスタンスを用意します。

まず IBM Cloud のダッシュボードからリソースの追加を行い、データベースカテゴリ内の "Cloudant" を選択します:
2018110701


作成時の認証方法において "Use both legacy credentails and IAM(従来の方法と IAM)" を選択して作成します:
2018110702


これでどちらの方法でも認証できるインスタンスが作成できました:
2018110703


クレデンシャル情報を確認するため、サービス資格情報を参照します。資格情報が作成されていない場合はあらたに1つ作成します:
2018110704


作成した資格情報を選択して参照します:
2018110706


JSON テキスト内に "apikey" と書かれたキー文字列が含まれていることを確認します。この値は後ほど利用します:
2018110707


では IAM で IBM Cloudant を使う Node.js アプリケーションを作成してみます。ソースコードはこんな感じになります:
var fs = require( 'fs' );

//. https://www.npmjs.com/package/@cloudant/cloudant
var CloudantLib = require( '@cloudant/cloudant' );

var options = {
  url: "https://xxxxxx-bluemix.cloudant.com",    //. Cloudant URL
  plugins: {
    iamauth: {
      iamApiKey: "(上記 apikey 文字列)"
    }
  }
};
var cloudant = new CloudantLib( options );
var db = cloudant.db.use( "mydb" );

db.list( { include_docs: true }, function( err, body ){
  if( err ){
    console.log( JSON.stringify( err, null, 2 ) );
  }else{
    console.log( JSON.stringify( body, null, 2 ) );
  }
});

Cloudant へのアクセスには @cloudant/cloudant パッケージを利用しています。このパッケージが既に IAM 対応済みで、上記のように URL と apikey 文字列を指定することでデータベースに接続することができ、データベースインスタンス生成後は従来通りの各種関数が利用できるようになります。


以前にこのブログで「Node.js からエクセルシートを操作する」というタイトル&内容の記事を書いて紹介しました:
Node.js からエクセルシートを操作する


この記事の中では XLSX という npm のライブラリを用いてエクセルファイルからセルの中身を読みとったり、セルに値を代入して保存する、という手法を紹介しました。 XLSX は対応フォーマットも多く、使う機会も多いのではないかと思っていました。

が、その後いろいろ使っていく中で XLSX が苦手とするケースも出てきました。典型的な例としてはセルのフォーマットを保持したままエクセルファイルを更新したい場合です。

具体的にはこんなケースが考えられます。以下のようなシート一枚のエクセルファイル(template.xlsx)が存在していたと仮定します:
2018111301


同シート内の A1 セルと B1 セルは結合しています。加えて背景色が緑色で、フォントは太字、中央揃えに設定されています。

これらの条件を残しながら A1 セルの内容を別の文字列に更新したい、というのが今回の用件であるとします。もちろん人間がマニュアル操作で更新することはたやすいのですが、これを人手を介さずにプログラマティックに行いたい、というものです。


では、この処理を XLSX ライブラリを使って行ってみます。コード(test01.js)としてはこのような内容になります:
// test01.js

var XLSX = require( 'xlsx' );

var ts = ( new Date() ).getTime();
var str = "かきくけこ " + ts;


//. テンプレート
var templatefile = './template.xlsx';

//. 出力先
var xlsfile1 = './xlsx_' + ts + '.xlsx';

//. テンプレートから読み込み
var book1 = XLSX.readFile( templatefile );

//. 「Sheet1」シート
var sheet1 = book1.Sheets["Sheet1"];
sheet1["A1"] = { v: str, t:'s', w: str };
book1.Sheets["Sheet1"] = sheet1;

XLSX.writeFile( book1, xlsfile1, { type: 'xlsx' } );
console.log( 'saved into ' + xlsfile1 );

この例では template.xlsx を読み込んだあと、実行時のタイムスタンプ値を取得し、Sheet1 シートの A1 セルの内容を「あいうえお」から「かきくけこ(タイムスタンプ値)」に書き換えた上で xlsx_(タイムスタンプ値).xlsx というファイルで保存するようにしています。

このコードを Node.js で実行してみます:
$ node test01

実行した結果、xlsx_(タイムスタンプ値).xlsx というファイルができあがります。このファイルをエクセルで開いてみると、このような見た目になりました:

2018111302


A1 セルと B1 セルの結合の情報は保持していたのですが、背景色やフォントの太字の情報は消えていました。また中央揃えは無効になっていました。

このようにセルに定義されたフォーマットを保持したままエクセルファイルを更新しようとすると、XLSX ライブラリでは限界があるようでした。


で、これをどうすればよいか、というのが今ブログエントリのメインテーマです。結論としては XLSX ライブラリではなく、XLSX-populate という npm ライブラリを使うことで解決できそうでした:
2018111500


XLSX-populate ライブラリを使って上記のコードを書き換えたものがこちら(test02.js)です:
// test02.js

var fs = require( 'fs' );
var XlsxPopulate = require( 'xlsx-populate' );

var ts = ( new Date() ).getTime();
var str = "さしすせそ " + ts;

//. テンプレート
var templatefile = './template.xlsx';

//. 出力先
var xlsfile2 = './xlsx_populate_' + ts + '.xlsx';

//. テンプレートから読み込み
XlsxPopulate.fromFileAsync( templatefile ).then( book2 => {
  //. 「Sheet1」シート
  var sheet2 = book2.sheet(0);
  sheet2.cell( "A1" ).value( str );

  book2.toFileAsync( xlsfile2 ).then( result => {} );
  console.log( 'saved into ' + xlsfile2 );
});

先程の test01.js とほぼ同様の処理を行っています。違いは A1 セルに上書きする内容を「さしすせそ(タイムスタンプ値)」として、出力ファイル名は xlsx_populate_(タイムスタンプ値).xlsx としています。

そしてこのファイルを実行します:
$ node test02

結果はこちら:

2018111303


ちょっとわかりにくいのですが、A1 セルと B1 セルの結合はもちろん、背景色や太字フォント、中央揃えの情報は全て保持されたまま更新ができています:
2018111304


もちろん、XLSX populate が万能、というわけではないのですが、Node.js でファイルの更新処理を行う場合には向いているライブラリだと思います。


先日、IBM Watson のサービスが IAM (Identification & Access Management)およびリソースグループに対応した、というニュースがありました:
IBM Watson AI のサービスについて、IAM およびリソースグループが有効に


この対応により、IBM Watson API を使うサービスの認証方式が変更になります。現行の(この変更前に作ったものの)方式は 2019/10/31 まで有効ですが、それ以降は認証エラーになってしまいます。また 2018/11/01 以降に作成した Watson サービスインスタンスは新しい認証方式でないと接続できなくなっています。

要は Watson サービスを使って今動いているアプリケーションは1年以内にこの新しい認証/認可の方式に対応させる必要があり、また新しく作るアプリケーションに関しては新しい認証/認可方式を使って作成する必要がある、ということになります。この「Watson の新しい認証/認可方式を使ってアプリを作成」するための手順を紹介します。

【新しい認証/認可方式に対応した Watson サービスのインスタンス化】
まず、新しい認証/認可方式に対応した Watson サービスを用意する必要があります。今回は比較的多く使われていると思われる画像認識機能 Visual Recognition API を使って紹介しますが、基本的には全てのサービスで同様の方法を実装することになります。

2018/11/07 時点で、IBM Cloud 内で新しく Visual Recognition サービスを作成すると、新しい認証/認可方式に対応したインスタンスとなります。というわけで IBM Cloud にログインし、リソースの作成にて「AI」カテゴリの Visual Recognition を選択します:
2018110701


インスタンスの属性を選択します。以前はここで「ロケーション」に加え「組織」や「スペース」を選択していたのですが、新しいインスタンスではロケーションとリソースグループ(default のままで大丈夫です)を選択します:
2018110702


下にスクロールしてサービスのプランを選択します(下図では無料版の Lite を選択しています)。最後に右下の「作成」ボタンでインスタンスを作成します:
2018110703


作成したインスタンスの画面がこちらです。この画面で API KeyURL が確認できます(API Key は当初マスキング処理されています):
2018110704


クレデンシャル情報を表示させることで一時的に API Key を確認することができます。新しい認証/認可方式ではこの API Key を使ってサービス API を利用することになります:
2018110705



【新しい認証/認可方式に対応したアプリケーション】
次に、アプリケーション側をこの新しいサービスインスタンスに対応した形で作り直します。今回は Node.js と Watson Developer Cloud SDK を使う前提でアプリケーションを作る想定で以下を記述します。

新しい認証方式の Visual Recognition では以下のようなコードで認証し、インスタンスオブジェクトを作成します:
//. test01.js
var fs = require( 'fs' );

//. https://www.npmjs.com/package/watson-developer-cloud
var vr_v3 = require( 'watson-developer-cloud/visual-recognition/v3' );

var vr = new vr_v3({
  url: "https://gateway.watsonplatform.net/visual-recognition/api",
  version: '2018-03-19',
  iam_apikey: "(上記で取得した API Key)"
});

var filename = 'sample.png';
if( process.argv.length >= 3 ){
  filename = process.argv[2];
}

var params = {
  images_file: fs.createReadStream( filename )
};

vr.classify( params, function( err, result ){
  if( err ){
    console.log( err );
  }else{
    console.log( JSON.stringify( result, null, 2 ) );
  }
});

青字部分は API Key を取得した時に同時に表示されていた URL の値を url に指定しています。また赤字部分は取得した API Key 文字列を iam_apikey に指定しています。この形でインスタンスオブジェクト(上記コードだと vr 変数)を作成する必要がある、という点がこれまでと異なります。

なお、上記コードで作っているコマンドアプリ(test01.js)は
$ node test01 (画像ファイル名)

という形で指定して実行することを想定しています。(画像ファイル名)部分に画像認識を実行する画像のファイルパスを指定します(無指定の場合は同じフォルダにある sample.png に対して実行します)。

今回はいらすとや様の、この画像を sample.png として用意しました:
sample01


この画像に対して実行してみます。まず Watson Developer Cloud SDK を導入します:
$ npm install watson-developer-cloud

そして実行(実行結果は緑字):
$ node test01

{
  "images": [
    {
      "classifiers": [
        {
          "classifier_id": "default",
          "name": "default",
          "classes": [
            {
              "class": "first-aid kit",
              "score": 0.879,
              "type_hierarchy": "/kit of things/first-aid kit"
            },
            {
              "class": "kit of things",
              "score": 0.879
            },
            {
              "class": "dado (dice)",
              "score": 0.5
            },
            {
              "class": "figure",
              "score": 0.79
            },
            {
              "class": "jade green color",
              "score": 0.77
            },
            {
              "class": "emerald color",
              "score": 0.511
            }
          ]
        }
      ],
      "image": "sample.png"
    }
  ],
  "images_processed": 1,
  "custom_classes": 0
}

(結果はともかく)新しい認証/認可方式のコードで実行できました!


久しぶりの Java プログラミングの機会があり、これまた久しぶりにサーブレットを作りました。POST されたデータを受け取って、バイナリデータを生成して、Content-Type をつけてストリームで返す、というものです。

この POST データの受け取り方をどうするかで(少しだけ)迷ったのですが、変数もその型も数も不定で受け取るような仕様だったので、深く考えずに JSON データを受け取ることにしました。普段の Node.js の時は特殊な事情がない限り JSON で受け取ることにしているので、その延長というか「まあイマドキは JSON だよね」くらいに考えていました。

・・・が、これが意外と苦戦。 誰かの参考になれば・・・、と思って、以下実際に作ったサーブレットの実装を紹介します。

まず最初はクライアント(呼び出し)側は普通にこんな感じの実装にしました。jQuery の AJAX を使ってタイムスタンプ値を含む JSON オブジェクトをポストしています。これを受け取って処理するサーブレット(/postdata)を作るのが今回の目的となります:
  :

$.ajax({
  type: 'POST',
  url: './postdata',
  data: { timestamp: ( new Date() ).getTime(), body: 'ハローワールド' },
  success: function( result ){
    console.log( result );
  },
  error: function( err ){
    console.log( 'error' );
  }
});
:

そしてそのサーブレットのコード部分が以下です:
public class PostdataServlet extends HttpServlet {
	
  @Override
  protected void doPost( HttpServletRequest req, HttpServletResponse res )
throws ServletException, IOException {
req.setCharacterEncoding( "UTF-8" ); try{
//. JSON テキストを全部取り出す BufferedReader br = new BufferedReader( req.getReader() ); String jsonText = br.readLine(); jsonText = URLDecoder.decode( json, "UTF-8" ); //System.out.println( jsonText );
//. JSON オブジェクトに変換 JSONParser parser = new JSONParser(); JSONObject jsonObj = ( JSONObject )parser.parse( jsonText );
//. JSON オブジェクトから特性の属性を取り出す
String body = ( String )jsonObj.get( "body" );
: }catch( Exception e ){ e.printStackTrace(); } } }

要は req.getParameter() などを使って特定のパラメータ値を取り出すわけではなく、req.getReader() を使ってポストされてきた全データ(今回の場合は JSON オブジェクトをテキスト化したもの)を受け取る必要があります。そして受け取ったテキストデータを(上記の例であれば JSON-Simple ライブラリを使って)JSON オブジェクト化した上でサーブレット内で取り扱う、という手法です。


・・・単純に JSON データを扱いたかっただけなんだけど、こんなに面倒だったっけ?

 

このページのトップヘ