2019年10月17日木曜日

Nomad でも OCR

昨日の会社帰りは、約2カ月ぶりに開催された「のの会」に参加しました。


今回、私が担当したのは「iPad で OCR を試した事」のお話です。

既にこのブログでも何度も登場している NotesHTTPRequest や NotesJSONNavigator等の V10 で追加された機能が iPad でも動くことを、実機のデモを通してご紹介しました。

私が所有する iPad の OS は iPadOS にしています。昨日は ThinkPad Bluetooth ワイヤレス・トラックポイント・キーボード を使って、トラックポイントのマウス操作で HCL Nomad とそのアプリがグリグリ動く様子をご覧いただきました。

iPad でデモする時は、設定から「アクセシビリティ - タッチ - AssistiveTouch」をオンにすると、マウスカーソル的なマークがわかりやすく表示されるのがいいですね。

お話した内容をスライドにしていますので、共有いたしました。何かの参考になれば幸いです。





2019年10月11日金曜日

iPhone? iPad? @Platform の拡張について

先日 TestFlight で公開されました HCL Nomad 1.0.5 では iPhone でも使用できるということで、早速ダウンロード。

このバージョンは @Platform 関数が拡張され、iPhone と iPad を区別することができる、と説明されています。

これを試してみました。

まずは、ビューのアクションボタンに次の式を設定しました。

@Prompt([Ok];"Formula";@Platform([Specific]))

これを実行してみると、iPad と iPhone のどちらも "iOS" とだけ表示されます。

「これはバグではないか?」と思い、フィードバックしたところ、すぐさまメールで返事がありました。

回答によれば、@Platform([Specific]) の戻り値は次のようなテキストリストだというのです。

PrimaryOSNamefor example, iOS
PrimaryOSVersionNamefor example, 12.4
iOS Model Typeeither iPad or iPhone
Apple Hardware Identifierfor example, iPad8.4

さらに次の式を iPhone で実行すれば "Platform is iPhone" と表示されると。

@If(@Platform([Specific])="iPhone";@Prompt([Ok]; "Formula"; "Platform is iPhone");@Prompt([Ok]; "Formula"; "Platform is NOT iPhone")) 


よくヘルプを読んでみるとバグでもなんでもなく「文字列リストが返されます」と書いてありました... orz


気をとりなおして、先のボタンの式を次のように書き換えてみました。

@Prompt([Ok];"Formula";@Implode(@Platform([Specific]);@NewLine))

そして表示されたプロンプトがこちらです。
iPhone
iPad
Windows 10
最後の Windows 10 はおまけですが、テキストリストの3番目にある iOS Model Type で iPhone か iPad を識別できることがわかりました。

おかげで私の所有する端末が古い機種ということがバレバレです(笑)

V11 では GPS にも対応するとのことですし、まだまだ Nomad 対応アプリの開発を楽しめそうです。

2019年10月7日月曜日

NDS2019 トラブルチケットアプリの部分的裏話

NDS2019 は5都市を巡り、どの会場も盛況で幕を閉じました。

今年5回目の開催となる NDS ですが、私は今回が初参加でした。

その NDS へ参加することが決まったころ...

日本の IBM Champion でアプリを作り NDS2019 の参加者へのお土産にしよう!

とか

どうせなら iPad でも使えるアプリにしよう

さらには

ついでに CollabSphere の Beauty Contest に応募しよう!

といった話が持ち上がり、各地の NDS 最後のビアバッシュ中に、ケートリック田付様よりご説明いただいた「トラブルチケット」を作ることになりました。



アプリの概要はスライドをご覧ください。

このアプリで私が主に関わったのは Slack への投稿部分です。

開発は Domino Designer のバージョン 10.0.1 FP2 で行っていました。
IBM Domino Mobile Apps(以下 DMA)は 1.0.3 です。

当初の構想では、文書に添付された写真を文書のタイトルとともに Slack へ表示しようと考えていました。ところが、LotusScript では配列の制限があり、ちょっとしたサイズのバイナリデータを送ることができないことと、Base64 エンコードしたデータを Slack 側がサポートしていないことの2点で、写真の送信をあきらめました。Google Drive 等 Slack 以外のクラウドストレージへ送信し、そのURLを Slack で使用するといった代替案もあったのですが、複雑にしたくなかったのです。

さらに悩ましかったのは、投稿の内容が英数字だけなら Notes クライアントも iPad の DMA もうまくいくのに、ダブルバイト文字が入ると iPad では文字化けする問題が発生したことでした。

iPad の DMA から投稿自体はできるものの、Slack に表示される全角文字が "BAD+82" のような文字列になって表示されてしまう現象に遭遇したのです。

エンコードを変えてみたりなんとかできないかと工夫しようと試みましたが、どうにもうまくいきません。

万策尽きて完全にあきらめていたところ、NDS 東京会場の開催前日になり DMA 1.0.4 (このバージョンから名称が HCL Nomad へと変わりました)が TestFlight でダウンロード可能となりました。

なんとこの文字化け、1.0.4 で見事に解消していたのです!

日本語の文字化けは DMA のバグが原因であり 1.0.4 で Fix されていたのでした。

そのような訳で、NDS会場のデモでは iPad 上の HCL Nomad からの投稿後直後に、画面上部からピロンと Slack 通知が日本語で表示されたのでした。めでたしめでたし。


なお、今回作成しましたアプリは設計を公開した状態で、XPAGES.JP のWebサイトからダウンロードできるようになるようですりました。

改行を含む文字列を Slack へ投稿したり、notes:// で始まるリンクを送るコツなど、NDSのビアバッシュでほろ酔いの状態では到底話せないネタを、私の稚拙で汚いコードが語ってくれると思います。

公開されましたらこのページを更新してリンクを掲載したいと思います。
こちらからダウンロード可能です。

2019年9月28日土曜日

LotusScript で携帯電話へSMSを送信

NDS2019 も折り返し地点を回り、残り2か所となりました。

ご来場されたお客様より伺ったお話から「やってみたい」と思うことがあって、早速試したことがありますので、ここに残しておこうと思います。

現状の最新版 Notes/Domino V10 には HTTP Request を送信できる新機能があります。

9.0.1 までは Windows の HTTP Request を呼び出したり Java でプログラムを組んだりしていましたが、これらは iOS 上のクライアント(HCL Nomad) では使えません。

また Notes クライアントは Windows のほか、Mac、iOS (現在は iPad のみ) で動くクライアントがありますが、OS依存の部分など多少考慮しなければならない場合があるものの、LotusScript のコードはどのクライアントでも動くようです。

V10 の新機能を使うことで、より Web サービスとの連携が容易になりました。


さて、やってみたいこととは、携帯電話への SMS (Short Message Service)の送信です。

Twilio (トゥイリオ)というサービスをご存知でしょうか。

詳しくは上のリンクをご覧いただくとして、Twilio は SMS や自動音声通話といった、電話番号を用いたサービスと連携するための API を提供しています。

電話をかけて、文章を日本語で読み上げたり、音声ファイルを再生したりといったようなことが可能なようです。

今回はこの API を使い、私のスマートフォンへ SMS を送信してみます。

SMS を送信する場合は、場合にもよるかもしれませんが料金が発生します。

Twilio の API は Free Trial が可能です。
Sign up を済ませて初回ログインすると、表示された Console Dashboard には 500円分の TRIAL BALANCE が付与されていました。

この Dashboard には、テストに使用する発信用の電話番号(TRIAL NUMBER = Twilio電話番号)を取得できるボタンがあったり、API を使う際に必要となる「アカウント SID」と「AUTH TOKEN」もあります。

なお、私が TRIAL NUMBER を取得したところ +1 で始まる米国の電話番号が取得できました。

次のコードは、サンプルとして作成したエージェントです。

Dim ss As NotesSession
Const accountSid = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Const authToken = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Const phoneFrom = "+11231231234" '発信用電話番号(Twilio電話番号)

Sub Initialize
 Set ss = New NotesSession
 sendSMS "LotusScript から初めての  SMS 送信。ドキドキ...", "+819012345678"
End Sub

Function base64Encode( textString As String ) As String
 Dim stream As NotesStream 
 Dim db As NotesDatabase 
 Dim doc As NotesDocument 
 Dim body As NotesMIMEEntity
 
 Set stream = ss.CreateStream 
 Call stream.WriteText ( textString ) 
 
 Set db = ss.CurrentDatabase
 Set doc = db.CreateDocument
 Set body  = doc.CreateMIMEEntity
 
 Call body.SetContentFromText ( stream, "", ENC_NONE )
 Call body.EncodeContent ( ENC_BASE64 )
 
 base64encode = Replace( Replace( body.ContentAsText, Chr( 13 ), ""), Chr( 10 ), "" )
 
 Call stream.Close
End Function

Sub sendSMS( message As String, phoneTo As String )
 Dim req As NotesHTTPRequest
 Dim url$, body$, res As Variant

 url = "https://api.twilio.com/2010-04-01/Accounts/" & accountSid & "/Messages.json"

 body = |From=| & urlEncode( phoneFrom ) & _
 |&To=| & urlEncode( phoneTo ) & _
 |&Body=| & urlEncode( message )

 Set req = ss.Createhttprequest()
 req.Preferstrings = True
 req.Setheaderfield "Content-Type", "application/x-www-form-urlencoded"
 req.Setheaderfield "Accept", "application/json"
 req.Setheaderfield "Authorization", "Basic " & base64Encode( accountSid & ":" & authToken )
 res = req.Post( url, body )
End Sub

Function urlEncode( textString ) As String
 Dim notesmacro As String, res As Variant

 notesmacro = |@URLEncode( "Domino";"| & textString & |" )|
 res = Evaluate( notesmacro )
 urlEncode = res( 0 )
End Function

以下、上のコードを補足します。

2~4行目
実行に必要なアカウントSID、AUTH TOKEN、発信者番号(Twilio電話番号)を定数(Const)で設定していますが、実行時は Console Dashboard のもので置換します。
※本番で使う場合、認証情報のハードコーディングはお勧めしません

4行目、8行目
このサービスのすばらしい点のひとつは、対応する国が日本だけではないことです。ここでは私のスマホの電話番号を指定したいので、最初のゼロを除いた番号の頭に日本の番号を示す +81 を付けました。たとえば電話番号が 090-1234-5678 の場合、発信先番号へは +819012345678 と指定します。

36行目
API の URL ですが、最後を .json とすると、レスポンス(47行目)が JSON 形式で返ります。このコードではレスポンスをほぼ無視していますが、SMSが送信されない理由を調べたい場合はコードの追加が必要です。※ res を messagebox で表示するだけでも、レスポンスにあるエラーコード等がなんとなく見えます

38行目、39行目
From(発信者番号)と To(受信者番号)と Body(メッセージ)は、URLエンコードが必要です。

46行目
API は Basic 認証をサポートしています。認証情報として
アカウントSID + ":" + AUTH TOKEN
の文字列を Base64 エンコードした値を "Authorization" ヘッダーへ追加しています。

そして、エージェントを実行して、実際に iPhone で受信したところが下図です。




今回はトライアルのアカウントを使用したからでしょうか、メッセージの冒頭には "Sent from your Twilio trial account - " と追加されてしまいました。まあ、仕方ありませんね...

SMS の料金などを調べずに3回 API をコールしてしまいましたが、Dashboard を覗いてみると、まだ 500円以内に収まっていました。
もう少し楽しめそうです..

もし「私も試してみたい!」と思った方は、Qiita に「[最新版]Twilioのサインアップ」という記事がありますので、ご参考まで。

2019年9月26日木曜日

V11 新機能 テーマのカラーを変えてみた

V11ベータ1 では、Notesクライアントのテーマの色を notes.ini パラメータを追加することで変更できる機能が追加されていました。

notes.ini へセットするパラメータは次のとおりです。
CUSTOM_THEME_COLOR=<R>,<G>,<B>
<R>,<G>,<B> には、それぞれ赤、緑、青を示す数値をセットします。

V11ベータ1のデフォルトのテーマは、新たに追加された "Notes 11 Theme" です。
私には少々青が強いように感じていますが、これが HCL 社のカラーなのでしょうか...

さて、昔懐かしのロータス・カラーへ変更したい場合、
CUSTOM_THEME_COLOR=255,211,2
あたりの値をセットします。

すると、メールを開いたときの見た目は次のようになります。


notes.ini パラメータであれば、デスクトップポリシーを使ってクライアントへ設定を配布することができます。

そこで今回ご紹介した notes.ini パラメータをデスクトップポリシーを使って配布するための設定手順をスライドにまとめました。
説明がほとんどありませんがご容赦くださいませ。





2019年9月10日火曜日

V11 の DQL はさらに進化!全文索引の検索も可能に!

Notes/Domino の次期バージョンであるV11のベータプログラムが始まりました。

https://hcljapan.co.jp/software/blog/hcl-notes-domino-v11-beta-started

私もさっそくWindows版のNotesとDominoをダウンロードして、試しているところです。

このベータで私が最も注目している機能のひとつに「DQLの拡張」があります。

DQL は V10 の新機能として追加された、データベースの文書へアクセスするための新たな枠組みです。V10 では NSFサーチ(文書のサマリーフィールド)とビューを検索できるのみで、サマリーフィールドでもないしビューにも表示できない「リッチテキストフィールド」の内容は、検索できませんでした。

V11 ではこれが改善され、全文索引を検索するためのクエリーを書くことができようになりました。全文索引にはリッチテキストフィールドのテキストも含むので、リッチテキストの中も検索できるようになるようです。添付ファイルを全文索引に含める設定をしていれば、サポートされているファイル形式のファイルにあるテキストも検索できそうです。


DQL では、検索する際に使うもの(サマリーフィールド or ビュー or 全文索引)がクエリーの書き方で変わります。


クエリーの例をいくつか挙げます。


たとえば、件名が"Request"のメールを探したい場合、

Subject = 'request'

とすると、データベース内の Subject という名前のサマリーフィールドから値が一致するもの探します。※このとき Subject でソートできるビューがDQLで使える仕様になっていればビューから探す場合もあります

検索に「All」ビューを使いたい場合、明示的にビュー名を指定することも可能です。

'All'.Subject = 'request'

このようにビューを検索で使う場合、Subject 列(Subjectは列のプログラム名)が最も左にあるソートされた列である、または Subject 列にクリックソートが設定されていることが必要です。

テクてくで発表した資料で示したとおり、NSFサーチ(サマリーフィールド)よりもビューを使った検索のほうがパフォーマンスは良好でした。


ここまで(これはほんの一部です)が V10 の DQL で使えた構文です。


V10 までは、フィールドまたはビュー列と、指定した値とが完全に一致する場合のみヒットしましたが、V11 では全文索引を使ってこれまでより柔軟な検索ができるようになりそうです。

V11 以降の DQL で全文索引を使うには、クエリーに Contains を用います。

たとえば先の例を、全文索引を使うよう書き換える場合、

Subject Contains ('request')

となります。ただし V10 では Subject の値が Complicated request の場合はヒットしませんでしたが、Contains を使えばヒットします。検索バーから全文検索する場合と違い、DQL では = と Contains は同じではないので注意が必要です。

それから、全文索引を使う場合には、アスタリスク * やクエスチョンマーク ? といったワイルドカードが使えます。

先の例では、件名が [Request] や REQUEST: といった文字列はヒットしますが、 Req: や はヒットしません。
もし Request 以外にも Req: や といった文言も検索したい場合、

Subject Contains ('req*')

とします。

クエスチョンマークは任意の1文字を表す場合に使います。例えば、'r??t' とすると root や REST はヒットしますが request はヒットしません。

また、件名だけでなく、本文など他のフィールドも含めて検索させたい場合、

Contains ('req*')

のように、フィールド名を指定せずに Contains を用いることで、全文索引にあるすべてのフィールドを対象として探すことができます。


さらに、複数の文言のどれかを含む文書を探したい場合は

Contains ('req*', 'server', 'replica')

のように括弧の中にカンマ区切りで指定できます。
また、

Contains all ('req*', 'server', 'replica')

のように contains の直後に all を加えることで、複数の文言のすべてを含む文書を探すことができます。


なお、前回のエントリで話題にしました「読者フィールドを使って閲覧制限した文書が全文検索にヒットする」事象は、DQL で全文索引を用いる場合にも同様であることを本ベータでは確認しました。※NSF サーチやビューを使った場合はヒットしません


V11 の DQL ではこういった構文の拡張の他、パフォーマンスのさらなる向上や、DQL準拠のビューを自動作成するといったプランもあるようです。次のリンクを参照ください。

https://www.cwpcollaboration.com/uploads/1/0/2/7/102707030/domino_app_dev__cr_.pdf

まだ未検証ですが、パフォーマンスについて書かれた12ページのグラフを見ると、検索スピードが物凄く速くなるようです。

さらに V11 の先には、複数のデータベースを対象にしたクエリーの拡張も計画にあるようですね。

今後の DQL にも注目していきたいと思います。

2019年9月9日月曜日

閲覧制限した文書も検索にヒットするのです

Notesでは「読者フィールド」と呼ばれる特殊なフィールドを用意しておき、閲覧させたい人やグループ、あるいは特定のロール(を付与されたACLエントリ)を読者フィールドへ埋め込むことで、文書の閲覧を制御することができることはご存知のとおりです。

読者フィールドに自分のアカウントや自分が所属するグループ、あるいは自分に付与されたロールが無い場合、その文書を閲覧することができません。

しかしながら、自分が閲覧できる文書がデータベースの中に1件も無い場合でも、データベースのプロパティには、閲覧できない文書を含む文書数が表示されます。エンドユーザーは何か見えない文書があることがわかるわけです。


さて、NotesDatabase クラスの Search メソッドや FTSearch メソッドを使う場合、検索条件に一致すれば、閲覧できない文書もヒットします。

これらのメソッドでは NotesDocumentCollection クラスのインスタンスが返ります。

もし、自分が閲覧できない文書がヒットした場合、それを GetFirstDocument や GetNextDocument を使って NotesDocument クラスのインスタンスが作られるのでしょうか。

次のようなコードを書きました。
Dim ss as New NotesSession
Dim db As NotesDatabase
Dim dc As NotesDocumentCollection
Dim doc As NotesDocument
Dim Query As String

Query = |[Subject] contains "request"|
Set db = ss.CurrentDatabase
Set dc = db.FTSearch( Query, 0 )
Set doc = dc.GetFirstDocument
While not doc is Nothing
    set doc = dc.GetNextDocument( doc )
Wend

このコードを実行して閲覧できない文書が検索にヒットした場合、GetFirstDocument や GetNextDocument で取り出した NotesDocument は Nothing になりませんが、UniversalID プロパティや NoteID プロパティの値は "" となりました。また NotesDocument クラスの Items プロパティの値はヌルになるため、フィールドの値へアクセスすることもできません。

つまり、見えない文書が存在することは分かるけれども、その詳細にはアクセスできないのです。

ここで注意しなければならないのは、読者フィールドを使ったデータベースでは、閲覧できない文書がヒットすることも想定しなければならない、ということです。

実は、私が書いたコードで「結果がなんか変だよ」と指摘があり調べたら、この想定がされてなかった、ということがあったのを思い出しました。

皆様も読者フィールドによる閲覧制限機能を後から追加するような変更を行う場合は、ぜひともご注意ください。