Translate

2017年4月26日水曜日

AIで長文の要約ができないか調べてみる


長文のテキストを短い文で要約するところをAIでできないか調べてみたら
以下の記事を見つけた。

Text summarization with TensorFlow
https://research.googleblog.com/2016/08/text-summarization-with-tensorflow.html

以下、翻訳した文章である。
いつもどおり参照する人は At your own riskでお願いします。

-------

TensorFlowを使ったテキスト要約


  • 2016年8月24日水曜日
  • 投稿者:Peter Liu、Xin Pan (ソフトウェアエンジニア、Google Brain Team)



毎日、人々はニュースストーリからソーシャルメディアポスト、検索結果まで、さまざまな情報源に依存しています。より長いテキストの正確な要約を自動的に提供できる機械学習モデルの開発は、大量の情報を圧縮形式で消化するのに役立ち、 "Google Brainチーム":https://research.google.com/teams/brain/ の長期目標となっています。

要約は、機械の興味深い読解力テストとしても役立ちます。 要約すると、機械学習モデルは、特に文書の長さが増すにつれて、文書を理解し、重要な情報、コンピュータにとって非常に挑戦的な課題を抽出することが可能です。

この研究を推進するため、 "TensorFlowモデルコード":https://github.com/tensorflow/models/tree/master/textsum をオープンソース化し、要約研究でよく使用されるデータセットである  "Annotated English Gigaword":https://catalog.ldc.upenn.edu/LDC2012T21 でニュース見出しを生成する作業を行っています。 また、ドキュメントのハイパーパラメータは、執筆時点で最も一般的に使用されている "メトリック":https://en.wikipedia.org/wiki/ROUGE_(metric) について、公開されている最先端のものよりも優れていると規定しています。 以下では、モデルによって生成されたサンプルも提供します。


抽象的および抽象的要約


抽出および抽象的な要約要約に対する1つのアプローチは、あるメトリック(例えば、逆文書頻度)によって興味深いとみなされる文書の部分を抽出し、それらを結合して要約を形成することです。このフレーバのアルゴリズムは、抽出集計と呼ばれます。

要約に対する1つのアプローチは、あるメトリック(例えば、逆文書頻度)によって興味深いとみなされる文書の部分を抽出し、それらを結合して要約を形成することです。 このフレーバのアルゴリズムは、抽出集計 (Extractive Summary) と呼ばれます。


原文:


Alice and Bob took the train to visit the zoo. They saw a baby giraffe, a lion, and a flock of colorful tropical birds.


アリスとボブは列車に乗って動物園を訪れました。彼らは、赤ん坊のキリン、ライオン、カラフルな熱帯鳥の群れを見ました。



抽出集計 (Extractive Summary) :


Alice and Bob visit the zoo. saw a flock of birds.


アリスとボブが動物園を訪問する。鳥の群れを見た。



上記では、元のテキストに 太字の単語 を抽出し、それらを連結して要約を作成しています。読んでわかるように、抽象的な制約により要約が不自然になったり、文法的に奇妙になることがあります。これとは別のアプローチは、人間がするようなシンプルに要約することであり、これは抽出制約を課さず、再評価を可能にすることです。これは抽象要約 (Abstractive Summary) と呼ばれます。


抽象要約 (Abstractive Summary) :


Alice and Bob visited the zoo and saw animals and birds.


アリスとボブは動物園を訪れ、動物や鳥を見た。



この例では、原文にはない単語を使用して、同様の量の単語でより多くの情報を保持しています。 良い抽象要約 (Abstractive Summary) が好まれるのは明らかですが、どのようにすればアルゴリズムにこれを始めるさせることができるのでしょうか?


TensorFlow モデルについて

短いテキストでは、 Smart-Reply for Inbox と同様の Sequence-to-Sequence 学習 と呼ばれる Deep Learning 技術を使用して、要約を学ぶことができます。特に、このようなモデルを訓練して、ニュース記事の見出しを作成することができます。この場合、モデルは記事テキストを読み取り、適切な見出しを書きだします。

モデルがどのような要約を生成するかを知るためには、以下のいくつかの例を見てください。最初の列はモデル入力であるニュース記事の最初の文を示し、2番目の列はモデルが書いた見出しを示しています。


入力:第1文モデルが書いた見出し
metro-goldwyn-mayer reported a third-quarter net loss of dlrs 16 million due mainly to the effect of accounting rules adopted this year (メトロ・ゴールドウィン・メイヤーは、主に今年採用された会計規則の影響により、第3四半期の純損失は1,600万ドル) mgm reports 16 million net loss on higher revenue (mgmはより高い収入で1600万の純損失を報告)
starting from july 1, the island province of hainan in southern china will implement strict market access control on all incoming livestock and animal products to prevent the possible spread of epidemic diseases (7月1日から、南中国の海南島は、流入する家畜や動物製品の厳しい市場アクセス管理を実施し、流行病の拡大を防ぎます) hainan to curb spread of diseases ( 海南は病気の広がりを抑制する)
australian wine exports hit a record 52.1 million liters worth 260 million dollars (143 million us) in september, the government statistics office reported on monday (オーストラリアのワイン輸出は9月に2億6100万ドル(1億4,300万米ドル)相当の5億2,100万リットルを記録した。政府統計局月曜日報告)australian wine exports hit record high in september (オーストラリアのワイン輸出は9月の最高記録をヒット)


研究の将来


我々は、ニュース見出しの性質上、記事の始めからちょうど少数の文章を読むことによって、モデルが良い見出しを生成できることを観察しました。この作業は概念実証のためのものですが、良い要約を作成するためには文書全体を読む必要があるため、より難しいデータセットを検討し始めました。これらのタスクでは、このモデルアーキテクチャによるゼロからのトレーニングは、我々が研究している他の技術と同様に機能しませんが、ベースラインとして機能します。 今回のリリースは、要約研究における他者のベースラインとしても役立つことを願っています。

------

ようは、

  • Googleでも長文の要約は初めているよ
  • やりかたには以下の2つがあるよ
  • 興味深いとみなされる文書の部分を抽出、結合する(Extractive Summary)
  • Sequence-to-Sequence ベース(Abstractive Summary)
  • 実際のアウトプットを載せたから確認してね※文章として後者のほうがまとも
  • ニュース見出しコーパスだとだいたい最初にサマリ書くから精度はあがっちゃうよね
  • 将来的にはほかの長文コーパスで全文を使ってより要約分らしい文章を抽出するモデルを検討するよ

ということらしい。

Google検索すると日本の方でいくつか
要約アルゴリズムだとかWebのフォームで実際に試せるサービスとか
提供しているところもあるけど、
アルゴリズム公開している情報 を読むと
いまのところ
この記事で言う
Extractive Summary
ばっかりのようだ。

バズワードで言えば
機械学習だけど、ディープラーニングではないアルゴリズム
たとえばTF-IDFベースだとかを併用したやつとか

まあでも、それで精度が出るのならそれでも良いのだけど...


ニュース記事だけで学習しちゃうと
機械学習で最初の方にばっかりattentionされたモデルになりかねないわけか..

このへんは TOEIC の Phase7 を人間がどう解いていたかを
モデル化しないといけないわけだけど..
とすると最初にニュースなのか広告なのか報告なのかメールなのか
種類分けをCNNなりRNNなりでやる必要があるだろうなあ..

でもなあそれだとせっかく Sequence-to-Sequence してるのに
なんとなくアレか..

しっかし、Googleはほんとに Sequence-to-Sequence 好きだねえ..

2017年4月11日火曜日

Dockerのバージョンが17.xになり、Ubuntu Serverへのインストールがまたちょこっと変わった



ひさしぶりにDockerサイトへ行ったら
Dockerのバージョンがかわっていた..

しかも昔Solarisとかがやったマイナーバージョンを
メジャーバージョンに繰り上げしやがった。

で、
今後は毎月リリースするらしい。
今は2017年3月だから17.3.xで
今月のDockercon2017あたりで17.4がでるらしい..

しかも有料版を docker-ee
無料版を docker-ce
としてaptリポジトリ管理されるらしい..


たしかにもうaptyumのパッケージ管理つかってやらないと
混乱してしまうよ..こっちも..

インストールは簡単になった、
Ubuntu Serverでsudoユーザでログインして、

# sudo apt-get -y install \
  apt-transport-https \
  ca-certificates \
  curl


proxy環境は vi .curlrc -x オプション使って

# sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
# sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# sudo apt-get update
# sudo apt-get -y install docker-ce
# sudo systemctl enable docker

sudoなしでdocker操作したいなら、

# sudo usermod -aG docker $USER
してから再ログインする。


Proxy環境の場合は

# sudo mkdir -p /etc/systemd/system/docker.service.d
# sudo vi /etc/systemd/system/docker.service.d/http-proxy.conf


[Service]
Environment="HTTP_PROXY=http://proxy.example.com:80/" "HTTPS_PROXY=http://proxy.example.com:80/" "NO_PROXY=localhost,127.0.0.1,"

# sudo systemctl daemon-reload

でOK。



でも、中身はあんまかわってなさそう..


有料版は
ノード別月額料金で
$75(CEのCertified版?)、
$120(DCサポート-イメージセキュリティスキャン)、
$200(DCサポート、イメージセキュリティスキャン有り
というケレン味もきっちりはいってる。

セキュリティ恐怖症の企業を狙い撃ちだ...


あたらしく
Docker Store
https://store.docker.com/
なるマーケットプレイスが立ち上がっていて
MSのイメージなんかもふくめDocker Certifiedイメージが..

ひょっとしたらofficalイメージはStoreで有料になったりするかもね..
Linuxは別として..

2017年4月10日月曜日

Hubot Document: Patterns を翻訳して、チャットボットでありがちな実装を確認する

Hubot Document: Scripting
https://hubot.github.com/docs/scripting/
を読めばHubot上で動作するチャットボットの書き方がわかるとおもい
前回の記事で翻訳してみた。

が、よくあるパターンサンプル集もあるとうれしい..

ということで
Hubot Documentation: Patterns
https://hubot.github.com/docs/patterns/
も引き続き翻訳してみた。



参照する場合は at your own risk でお願いします。
----------

Hubot Documentation: Patterns




一般的な Hubot 活用シナリオを扱うためのパターンを共有します。

Hubot インスタンスのリネーム


Hubot の名前を変更すると、元の名前に反応しなくなります。 ユーザに新しい名前を慣れさせるために、古い名前への発言に非推奨通知を追加することを選択できます。 パターンロジックは次のとおりです;

  • 古い名前で始まるすべてのメッセージをlisten
  • ユーザにreplyして、新しい名前について知らせる

この設定はとても簡単です:

  1. Hubot インスタンスの scripts/ ディレクトリに rename-hubot.coffee という バンドルされたスクリプトを作成
  2. 必要に応じて変更された次のコードを追加:
# Description:
#   Tell people hubot's new name if they use the old one
#
# Commands:
#   None
#
module.exports = (robot) ->
  robot.hear /^hubot:? (.+)/i, (res) ->
    response = "Sorry, I'm a diva and only respond to #{robot.name}"
    response += " or #{robot.alias}" if robot.alias
    res.reply response
    return


上記のパターンでは、ニーズに合わせて hubot リスナと応答メッセージの両方を変更します。

また、リスナは Hubot アダプタが処理する前にチャットプログラムに入力されたものではなく、実際に聞こえるものに基づいている必要があることに注意することが重要です。 たとえば、"HipChat アダプタ" は @hubothubot: に変換してから Hubot に渡します。


非推奨リスナまたは改名されたリスナ


スクリプトを削除したり、スクリプトのコマンドを変更した際に、変更内容をユーザに知らせることができます。 1つの方法は、チャットでそれらを伝えること、またはもはや存在しないコマンドを使用することによって変更を検出させることです。 別の方法は、Hubot に動作しないコマンドを使用したときに知らせることです。

このパターンは、上記の Hubot インスタンスパターンの名前変更に似ています:

  • 古いコマンドと一致するすべてのメッセージをlisten
  • ユーザに reply して、そのユーザが非推奨になったことを知らせる

セットアップ方法はこちら:

  1. deprecations.coffee という Hubot インスタンスの scripts/ ディレクトリにバンドルされたスクリプトを作成します
  2. 古いコマンドリスナをコピして、そのファイルに追加します。たとえば、何らかの理由で help コマンドの名前を変更する場合は、次のようにします:
# Description:
#   Tell users when they have used commands that are deprecated or renamed
#
# Commands:
#   None
#
module.exports = (robot) ->
  robot.respond /help\s*(.*)?$/i, (res) ->
    res.reply "That means nothing to me anymore. Perhaps you meant `docs` instead?"
    return


Proxy経由による HTTPリスエストのフォワーディング


多くの企業環境では、インターネットおよび/または保護されたリソースにアクセスするためにWeb Proxy が必要です。 一回限りの制御の場合、 robot.http で使用する "Agent" を指定できます。 しかし、これでは、ロボットが Proxy を指すために使用するすべてのスクリプトを変更する必要があります。 代わりに、グローバルレベルでAgentを指定し、デフォルトですべてのHTTP要求にAgentを使用させることができます。

node.jsによるHTTP要求とHTTPS要求に対する処理方法のために、プロトコルごとに異なるAgentを指定する必要があります。 ScopedHTTPClient は自動的に、各要求に対して正しい ProxyAgent を選択します。

  1. ProxyAgent をインストールします npm install proxy-agent
    Hubotインスタンス上の scripts/ ディレクトリに proxy.coffee と呼ばれる バンドルスクリプト を作成します
  2. 必要に応じて変更された次のコードを追加します:
proxy = require 'proxy-agent'
module.exports = (robot) ->
  robot.globalHttpOptions.httpAgent  = proxy('http://my-proxy-server.internal', false)
  robot.globalHttpOptions.httpsAgent = proxy('http://my-proxy-server.internal', true)


メッセージの動的マッチング


状況によっては、異なるメッセージ(例えば、factoids、JIRAプロジェクトなど)を動的に一致させたい場合があります。 常に一致する広範な正規表現を定義するのではなく、特定の条件が満たされたときだけ Hubot に一致するように指示することができます。

シンプルなロボットでは、これはListenerコールバックに条件を入れることとあまり変わりませんが、ミドルウェアを扱うときに大きな違いがあります。基本モデルでは、汎用正規表現の一致ごとにミドルウェアが実行されます 。 動的整合モデルでは、動的条件が一致した場合にのみミドルウェアが実行されます。

例えば、factoids ルックアップコマンドは次のように再実装できます:
module.exports = (robot) ->
  # Dynamically populated list of factoids
  facts =
    fact1: 'stuff'
    fact2: 'other stuff'

  robot.listen(
    # Matcher
    (message) ->
      match = message.match(/^~(.*)$/)
      # Only match if there is a matching factoid
      if match and match[1] in facts
        match[1]
      else
        false
    # Callback
    (response) ->
      fact = response.match
      res.reply "#{fact} is #{facts[fact]}"
  )



コマンドへのアクセス制限


Hubot のすばらしい機能の1つは、単一のチャットメッセージを使用して本番環境に変更を加える能力です。 ただし、チャットサービスにアクセスできる人は誰もが、プロダクションの変更を引き起こすことはできません。

特定のニーズに応じてアクセスを制限するさまざまなパターンがあります:

  • アクセスの2つのバケット:フルリストとホワイトリスト/ブラックリストで制限付き
  • すべてのコマンド(ロールベースのアクセス制御)に対する特定のアクセスルール
  • 特定の部屋のブラックリスト/ホワイトリストのコマンド

単純なリスナごとのアクセス


一部の組織では、ほぼすべての従業員に同じレベルのアクセス権が与えられており、限られた少数のユーザ(たとえば、新規採用者、請負業者など)のみが制限されています。 このモデルでは、すべてのリスナのセットを分割して、「通常のコマンド」から「パワーコマンド」を分離します。

リスナを分離したら、ホワイトリスト/ブラックリストのユーザとリスナの間でいくつかのトレードオフの決定をする必要があります。

ホワイトリスト作成とブラックリスト登録の重要な決定要素は、各カテゴリのユーザ数、どちらのカテゴリの変更頻度、組織が受け入れるセキュリティリスクのレベルです。

  • ユーザをホワイトリストに登録すると(ユーザX、Y、Zはパワーコマンドにアクセスでき、他のすべてのユーザは通常のコマンドにのみアクセスできます)、より安全なアクセス方法です(新しいユーザはパワーコマンドにデフォルトでアクセスできません) 新しい各ユーザを「承認済み」リストに追加する必要があります)。
  • ユーザをブラックリストに登録すると(ブラックリストに追加されるまで、新しいユーザは電源コマンドにデフォルトでアクセスできます)、ユーザをブラックリストに登録する(すべてのユーザは電源コマンドにアクセスできますが、通常のコマンドにしかアクセスできないユーザX、Y、Zは除きます) ブラックリストが小規模/めったに更新されていない場合はメンテナンスオーバーヘッドが大幅に低くなります。

リスナを選択的に許可するかどうかを決定する重要な要因は、各カテゴリのリスナ数、内部スクリプトと外部スクリプトの比率、組織が受け入れるセキュリティリスクのレベルです。

  • すべてのリスナは通常のコマンドと見なされるリスナA、B、Cを除いてすべてのリスナを選択的に許可しますが、新しいリスナはデフォルトで制限されています (おバカな/遊びのためのリスナは明示的に「通常」の状態にダウングレードする必要があります)。
  • リスナを選択的に制限する(リスナA、B、Cはパワーコマンドですが、それ以外のコマンドはすべて通常のコマンドです)、セキュリティ保護されていない方法です(新しいリスナはデフォルトで通常のカテゴリに入れられます 。保守オーバヘッドは低くなります(アクセスポリシのすべての遊びのための/文化的なスクリプトを変更/列挙する必要はありません)。

追加の考慮事項として、ほとんどのスクリプトは現在リスナIDを持っていないので、リスナIDを追加するために使用する外部スクリプトをPR(またはfork)する必要があります。 実際の変更は簡単ですが、多くのメンテナと調整することは時間がかかることがあります。

可能な4つのモデルのうちどれを選択するかを決めたら、承認ミドルウェアに接続するための適切なユーザとリスナのリストを作成する必要があります。

例:選択的に制限されたパワーコマンドにアクセスできるユーザのホワイトリスト
POWER_COMMANDS = [
  'deploy.web' # String that matches the listener ID
]

POWER_USERS = [
  'jdoe' # String that matches the user ID set by the adapter
]

module.exports = (robot) ->
  robot.listenerMiddleware (context, next, done) ->
    if context.listener.options.id in POWER_COMMANDS
      if context.response.message.user.id in POWER_USERS
        # User is allowed access to this command
        next()
      else
        # Restricted command, but user isn't in whitelist
        context.response.reply "I'm sorry, @#{context.response.message.user.name}, but you don't have access to do that."
        done()
    else
      # This is not a restricted command; allow everyone
      next()

ミドルウェアは、特定のメッセージ( robot.hear /.+/ を含む) に一致するすべてのリスナに対して実行されるので、リスナを分類するときにそれらを含めるようにしてください。


リスナごとの特定のアクセスルール


大規模な組織では、通常、アクセスのバイナリ分類が不十分で、より複雑なアクセスルールが必要です。

アクセスポリシの例:

  • 各開発チームは、リリースをカットしてサービスを展開することができます
  • Operations グループは、すべてのサービスを展開するためのアクセス権を持っています(ただし、カットリリースではありません)
  • フロントデスクはリリースをカットしたり、サービスを展開することはできません

このような複雑なポリシは現在、コードで直接実装するのが最善ですが、アクセス管理のための一般化されたフレームワークを構築するための "継続的な作業" があります。


ルームごとの特定のアクセスルール


さまざまな目的を果たす多数のチャットルームを持つ組織では、同一の hubot インスタンスを使用できるようにすることがしばしばですが、各ルームで異なるコマンドセットを使用できます。

一般化されたブラックリストソリューションに関する作業が 進行中 です。 ホワイトリストのソリューションも同様のアプローチをとることができます。

以上
----------

想像していたのは、もっとアプリアプリしてるサンプルコードだったのだけど..

Proxy経由でのアクセス方法があったから、
まあ
よしとしようか..



Hubot Document: Scripting を翻訳して、Hubotチャットボットの書き方を学習する

 Hubotフレームワークは、coffee scriptかJava Scriptで書かなければならない。
それにいわゆるプラグインのようにスクリプトを配置していく構造なので
Hubotが決めた書式に従ってコードを書かなくてはならない。

それにいろいろ予約語というか、
メタデータなどの扱いなんかもどうやればいいのか
しっておきたいし...

ということで本家サイトの

Hubot Documentation: Scripting
https://hubot.github.com/docs/scripting/

を翻訳することにした。
以下翻訳文ですが参照の場合は at your own riskでお願いします。
----------

Hubot Docmentation: Scripting


箱から取り出したばかりの Hubotは、それほど多くではありませんが拡張可能でスクリプト化可能なロボットのフレンズです。 コミュニティによって書かれ管理されている数百のスクリプト があり、簡単に自分のスクリプトを書くこともできます。 hubotの @scripts@ ディレクトリにカスタムスクリプトを作ったり、 コミュニティと共有するためのスクリプトパッケージを作る こともできますよ!


スクリプトの解剖学


hubot を作成すると、ジェネレータは scripts ディレクトリもあわせて作成します。 このディレクトリをのぞけは、スクリプトのサンプルを確認することができます。 スクリプトをスクリプトにするには、次のことが必要です:

  • hubot ロードパス(デフォルトは src/scripts および scripts )を通したディレクトリに配置
  • 拡張子 .coffee もしくは .js にする
  • 関数をエクスポートする

関数をエクスポートするとは、次のようにスクリプトを記述するという意味です:

module.exports = (robot) ->
  # コードをここに記述

パラメータ robot は、あなたのロボットのインスタンスをあらわしています。 これで、いくつかの素晴らしいスクリプトを開始することができきるようになりました。


hear respond


HuBot はチャットボットをつくるためのフレームワークなので、最も一般的なやりとりはメッセージに基づいています。 Hubot は、ルーム内で発言されたメッセージを聞いたり( hear )、直接そのメッセージに返信する( respond )ことができます。 どちらのメソッドも、正規表現とコールバック関数をパラメータとして取ります。 例えば:

module.exports = (robot) ->
  robot.hear /badger/i, (res) ->
    # コードをここに記述

  robot.respond /open the pod bay doors/i, (res) ->
    # コードをここに記述
robot.hear /badger/ コールバック関数は、メッセージのテキストが正規表現とマッチするたびに呼び出されます。 例えば、次のようなメッセージの場合呼び出されます:

  • Stop badgering the witness
  • badger me
  • what exactly is a badger anyways
robot.respond /open the pod bay doors/i コールバック関数は、ロボットの名前またはエイリアスの直前にあるメッセージのためだけに呼び出されます。 ロボットの名前が HAL でエイリアスが / の場合、このコールバック関数は次のメッセージでトリガされます:

  • hal open the pod bay doors
  • HAL: open the pod bay doors
  • @HAL open the pod bay doors
  • /open the pod bay doors

次のメッセージの場合は、呼び出されません:

  • HAL: please open the pod bay doors :respond はロボット名直後のテキストにバインドされているためです。
  • has anyone ever mentioned how lovely you are when you open the pod bay doors? :ロボット名がないためです。

send および reply


パラメータ res Response インスタンスです(歴史的にこのパラメータは msg でした。このように他のスクリプトで使用されることがあります)。 res を使用すると、書き込まれたチャットルームにメッセージを送信したり( send )、、任意のチャットルームにメッセージを送信したり( emote )(ただし、指定されたアダプタがサポートしている場合のみ)、メッセージを送信した人に返信( reply )することができます。 例えば:

module.exports = (robot) ->
  robot.hear /badger/i, (res) ->
    res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"

  robot.respond /open the pod bay doors/i, (res) ->
    res.reply "I'm afraid I can't let you do that."

  robot.hear /I like pie/i, (res) ->
    res.emote "makes a freshly baked pie"
robot.hear /badgers/ コールバック関数は、だれが発言したかに関係なく、指定された通りに正確にメッセージ"Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"を送信します。

もしユーザ Dave"HAL: open the pod bay doors"と発言したのであれば、 robot.respond /open bay doors/i コールバック関数からメッセージ"Dave: I'm afraid I can't let you do that."が送られます。


データのキャプチャリング


ここまでは静的な応答を扱っていましたが、もっと面白いもの、機能的には退屈なものもあります。 res.match には、着信メッセージを正規表現と照合( match )した結果が格納されています。 これは単に JavaScript関数 match() と同じで、最終的には式に一致する完全なテキストであるインデックス0を持つ配列になります。 取得グループを含めると、 res.match が作成されます。 たとえば、次のようなスクリプトを更新するとします:

  robot.respond /open the (.*) doors/i, (res) ->
    # コードをここに記述


Daveが"HAL: open the pod bay doors"と発言したならば、 res.match[0] は"open the pod bay doors"で、 res.match[1] は単に "pod bay" となります。これを使ってよりダイナミックなチャットボットを始めることができます:

  robot.respond /open the (.*) doors/i, (res) ->
    doorType = res.match[1]
    if doorType is "pod bay"
      res.reply "I'm afraid I can't let you do that."
    else
      res.reply "Opening #{doorType} doors"

HTTP通信の構築


Hubot は、サードパーティのAPIを統合して使用するために、あなたの代わりにHTTPコールを行うことができます。 これは、 robot.http で利用可能な node-scoped-http-client インスタンスを介して行うことができます。 最も単純なケースは次のようになります:


  robot.http("https://midnight-train")
    .get() (err, res, body) ->
      # コードをここに記述

POSTは次のように記述します:

  data = JSON.stringify({
    foo: 'bar'
  })
  robot.http("https://midnight-train")
    .header('Content-Type', 'application/json')
    .post(data) (err, res, body) ->
      # コードをここに記述


err は、処理中に発生したエラーをあらわします(発生した場合)。一般的にこれをチェックし、それに応じて処理したいと思うでしょう:

  robot.http("https://midnight-train")
    .get() (err, res, body) ->
      if err
        res.send "Encountered an error :( #{err}"
        return
      # (成功した場合の)コードをここに記述


res node http.ServerResponse インスタンスです。 node-scoped-http-client を使用する場合、ほとんどのメソッドは重要ではありませんが、興味があるのは statusCode getHeader です。 statusCode を使用してHTTPステータスコードをチェックします。通常 200 以外の場合は何か問題が発生したことを意味します。 レートリミットを確認するなど、ヘッダを表示するには getHeader を使用します。

  robot.http("https://midnight-train")
    .get() (err, res, body) ->
      # ここにエラーチェックコードを記述

      if res.statusCode isnt 200
        res.send "Request didn't come back HTTP 200 :("
        return

      rateLimitRemaining = parseInt res.getHeader('X-RateLimit-Limit') if res.getHeader('X-RateLimit-Limit')
      if rateLimitRemaining and rateLimitRemaining < 1
        res.send "Rate Limit hit, stop believing for awhile"

      # 残りのコードを記述

body はレスポンスボディを文字列として扱います。おそらく最も気になるものです:

  robot.http("https://midnight-train")
    .get() (err, res, body) ->
      # ここにエラーチェックコードを記述

      res.send "Got back #{body}"



JSON


APIと対話する際の最も簡単な方法は、余分な依存関係を必要としない JSON です。  robot.http を呼び出すときには、通常、 Accept ヘッダを設定して、APIにあなたが期待しているものを提供する必要があります。  body を取得したら、 JSON.parse で解析することができます:

  robot.http("https://midnight-train")
    .header('Accept', 'application/json')
    .get() (err, res, body) ->
      # ここにエラーチェックコードを記述

      data = JSON.parse body
      res.send "#{data.passenger} taking midnight train going #{data.destination}"

APIにエラーが発生し、JSON の代わりに通常のHTMLエラーをレンダリングしようとする場合など、非 JSON を戻すことは可能です。 安全な側になるためには、 Content-Type をチェックし、解析中にエラーをキャッチする必要があります。

  robot.http("https://midnight-train")
    .header('Accept', 'application/json')
    .get() (err, res, body) ->
      # ここに err および response ステータス確認コードを記述

      if response.getHeader('Content-Type') isnt 'application/json'
        res.send "Didn't get back JSON :("
        return

      data = null
      try
        data = JSON.parse body
      catch error
       res.send "Ran into an error parsing JSON :("
       return

      # コードを記述




XML


バンドルされたXML解析ライブラリがないため、XML APIは実装が難しくなります。 詳細については、このドキュメントの範囲を超えていますが、ここにいくつかのライブラリがあります:


スクリーンスクレイピング


APIがない場合は "スクリーンスクレイピング" が使えるの可能性が常にあります。 詳細についてはこのドキュメントの範囲を超えていますが、ここではいくつかのライブラリをチェックアウトしています:
  • cheerio (jQueryのおなじみの構文とAPI)
  • jsdom (W3C DOMのJavaScript実装)

高度な HTTP/HTTPS 設定


前述のように、hubot は node-scoped-http-client を使用して、HTTPおよびHTTPS要求を行うための単純なインタフェースを提供します。 その傘下では、nodeの組み込み http https ライブラリを使用していますが、最も一般的な種類の対話に簡単なDSLを提供しています。

http https のオプションをより直接的に制御する必要がある場合は、 http https に渡される node-scoped-http-client 上の第2引数を robot.http に渡します:

  options =
    # CAに対してサーバ証明書を検証しないでください、怖い!
    rejectUnauthorized: false
  robot.http("https://midnight-train", options)

さらに、 node-scoped-http-client があなたに合っていない場合は、 http https を直接使うことも、 request のような他のノードライブラリを使うこともできます。


ランダム


一般的なパターンは、コマンドをhearしたり、コマンドにrespondしたり、ランダムな面白い画像やテキスト行を可能な限り配列から送信することです。 JavaScriptやCoffeeScriptですぐにこれを行うのは面倒ですので、Hubot には便利なメソッドがあります:

lulz = ['lol', 'rofl', 'lmao']

res.send res.random lulz

トピック


アダプタがそれをサポートしている場合は、Hubot は部屋のトピック変更に反応することができます。
module.exports = (robot) ->
  robot.topic (res) ->
    res.send "#{res.message.text}? That's a Paddlin'"



入退室


アダプタがそれをサポートしている場合は、Hubot はユーザの入退室を確認することができます。

enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
leaveReplies = ['Are you still there?', 'Target lost', 'Searching']

module.exports = (robot) ->
  robot.enter (res) ->
    res.send res.random enterReplies
  robot.leave (res) ->
    res.send res.random leaveReplies


カスタムリスナ


上述のヘルパは、平均的なユーザが必要とする(聞く(hear)、応答する(respond)、入室する(enter)、退室する(leave)、トピック(topic))機能のほとんどをカバーしていますが、時にはリスナに対して非常に特殊なマッチングロジックを使用したいことがあるでしょう。 その場合、 listen を使用して正規表現の代わりにカスタムマッチ関数を指定することができます。

リスナがコールバックを実行する際に、match 関数は真理値を返却します。 次に match関数の返す真偽値が response.match としてコールバック関数に渡されます。

module.exports = (robot) ->
  robot.listen(
    (message) -> # マッチ関数
      # Steve の発言に時々返信する
      message.user.name is "Steve" and Math.random() > 0.8
    (response) -> # 標準リスナコールバック
      # 彼が存在することがどれほど幸せかをスティーブに知らせてください
      response.reply "HI STEVE! YOU'RE MY BEST FRIEND! (but only like #{response.match * 100}% of the time)"
  )

複雑なマッチ処理例については、デザインパターンのドキュメントを参照してください。


環境変数


Hubotは、 process.envを使用して、他のNodeプログラムと同じように、実行している環境にアクセスできます。 これは、スクリプトの実行方法を設定するのに使用できます。規約は接頭辞 HUBOT_を使用します。

answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING

module.exports = (robot) ->
  robot.respond /what is the answer to the ultimate question of life/, (res) ->
    res.send "#{answer}, but what is the question?"


スクリプトが定義されていない場合、スクリプトをロードできるか確認し、Hubot 開発者に定義方法を伝えたり、何かをデフォルトにするよう注意してください。 スクリプト作成者は、致命的なエラー(例えば、hubotが終了するかどうか)を判断して、それに依存するスクリプトを構成する必要があると説明します。可能であれば、それが理にかなっているときは、他の設定をせずにスクリプトを実行することを推奨します。

ここではデフォルトにすることができます:

answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING or 42

module.exports = (robot) ->
  robot.respond /what is the answer to the ultimate question of life/, (res) ->
    res.send "#{answer}, but what is the question?"

定義されていない場合は、ここで終了します:

 answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
unless answer?
  console.log "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
  process.exit(1)

module.exports = (robot) ->
  robot.respond /what is the answer to the ultimate question of life/, (res) ->
    res.send "#{answer}, but what is the question?"

最後に、robot.respondを更新してチェックします:

answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING

module.exports = (robot) ->
  robot.respond /what is the answer to the ultimate question of life/, (res) ->
    unless answer?
      res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
      return
    res.send "#{answer}, but what is the question?"

依存関係


Hubotは npm を使用してその依存関係を管理します。 パッケージを追加するには、パッケージを package.jsondependencies  に追加します。 たとえば、 lolimadeupthispackage 1.2.3 を追加したい場合は、次のようになります:

  "dependencies": {
    "hubot":         "2.5.5",
    "lolimadeupthispackage": "1.2.3"
  },

hubot-scripts からスクリプトを使う場合は、追加するスクリプトの Dependencies 文書を書き留めておいてください。 それらは package.json にコピー&ペーストできる形式でリストされていますが、有効なJSONにするために必要に応じてカンマを必ず追加してください。

タイムアウトとインターバル


Hubot は JavaScript のビルトイン setTimeout を使用してコードを遅延実行できます。 これはコールバックメソッドとそれを呼び出すまでに待つ時間をとります:

module.exports = (robot) ->
  robot.respond /you are a little slow/, (res) ->
    setTimeout () ->
      res.send "Who you calling 'slow'?"
    , 60 * 1000

さらに、Hubot は setInterval を使用してインターバルでコードを実行できます。 コールバックメソッドと、コール間の待機時間が必要です:

module.exports = (robot) ->
  annoyIntervalId = null

  robot.respond /annoy me/, (res) ->
    if annoyIntervalId
      res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
      return

    res.send "Hey, want to hear the most annoying sound in the world?"
    annoyIntervalId = setInterval () ->
      res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
    , 1000

  robot.respond /unannoy me/, (res) ->
    if annoyIntervalId
      res.send "GUYS, GUYS, GUYS!"
      clearInterval(annoyIntervalId) ->
      annoyIntervalId = null
    else
      res.send "Not annoying you right now, am I?"

HTTPリスナ


Hubot には、HTTP要求を処理するための Express Webフレームワークのサポートが含まれています。 環境変数 EXPRESS_PORT または PORT で指定されたポート(この順序で優先されます、デフォルトは8080)をlistenします。 Express アプリケーションのインスタンスは robot.router から入手できます。 EXPRESS_USEREXPRESS_PASSWORD を指定して、ユーザー名とパスワードで保護することができます。 EXPRESS_STATIC を設定することにより、自動的に静的ファイルを提供することができます。

この最も一般的な使い方は、プッシュ時のwebhooksによるサービスにHTTPエンドポイントを提供して、チャットに情報を表示することです。
module.exports = (robot) ->
  # 期待値:ルームがアダプタごとに異なる場合、数値ID、名前、トークン、またはその他の値である可能性があります。
  robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
    room   = req.params.room
    data   = if req.body.payload? then JSON.parse req.body.payload else req.body
    secret = data.secret

    robot.messageRoom room, "I have a secret: #{secret}"

    res.send 'OK'


それらを curl でテストしてください:後述の "エラー処理"節 も参照してください。

// JSONデータを送る場合、"Content-Type: application/json"を指定する必要があります
curl -X POST -H "Content-Type: application/json" -d '{"secret":"C-TECH Astronomy"}' http://127.0.0.1:8080/hubot/chatsecrets/general

// デフォルト(Content-Type: application/x-www-form-urlencoded)の場合、"payload=..."をセットする必要があります
curl -d 'payload=%7B%22secret%22%3A%22C-TECH+Astronomy%22%7D' http://127.0.0.1:8080/hubot/chatsecrets/general

すべてのエンドポイントURLは、(ロボットの名前に関係なく)リテラル文字列/hubotで始まる必要があります。 この一貫性により、ウェブフック(コピー可能なURL)を簡単に設定でき、URLが有効であることが保証されます(すべてのボット名がURLセーフではありません)。

イベント


Hubotはスクリプト間でデータを渡すために使用できるイベントに応答することもできます。 これは、node.js の EventEmitterrobot.emitrobot.on でカプセル化することによって行われます。

このためのユースケースの1つは、サービスとのやりとりを処理し、イベントが発生したときにイベントを発行するためのスクリプトを1つ持つことです。 たとえば、GitHubのポスト・コミット・フックからデータを受け取り、それが入ってきたときにコミットを発行し、そのコミットに対して別のスクリプトを実行させるスクリプトを作成できます。
# src/scripts/github-commits.coffee
module.exports = (robot) ->
  robot.router.post "/hubot/gh-commits", (req, res) ->
    robot.emit "commit", {
        user    : {}, #hubot user object
        repo    : 'https://github.com/github/hubot',
        hash  : '2e1951c089bd865839328592ff673d2f08153643'
    }

# src/scripts/heroku.coffee
module.exports = (robot) ->
  robot.on "commit", (commit) ->
    robot.send commit.user, "Will now deploy #{commit.hash} from #{commit.repo}!"
    #デプロイコードはここに記述

イベントを提供する場合は、データにHubot ユーザまたは hubot ルームオブジェクトを含めることを強くお勧めします。 これにより、hubot はチャット経由でユーザまたはルームに通知することができます。

エラー処理


完全なコードは存在しません、エラーと例外がかならず予想されます。 以前はキャッチされなかった例外がhubotインスタンスをクラッシュさせていました。そこで Hubot に uncaughtException ハンドラが追加されました。このハンドラは、スクリプトが例外に関する何かを行うためのフックする機能を提供します。

# src/scripts/does-not-compute.coffee
module.exports = (robot) ->
  robot.error (err, res) ->
    robot.logger.error "DOES NOT COMPUTE"

    if res?
      res.reply "DOES NOT COMPUTE"

ここで必要なことは何でも行うことができます、特に非同期コードでは、レスキューおよびロギングのエラーを予防する必要があります。 そうしないと、再帰的なエラーが発生し、何が起こっているのか分からないことがあります。

hoodの下では、エラーハンドラがそのイベントを消費する「エラー」イベントが発生しています。 uncaughtException ハンドラは、 技術的にプロセスを未知の状態のままにします 。 したがって、可能な限り、自分の例外を救済し、自分で放出する必要があります。 最初の引数は出力されたエラーで、2番目の引数はエラーを生成したオプションのメッセージです。

前のサンプルを使うと:

  robot.router.post '/hubot/chatsecrets/:room', (req, res) ->
    room = req.params.room
    data = null
    try
      data = JSON.parse req.body.payload
    catch err
      robot.emit 'error', err

    # 残りのコードをここに記述


  robot.hear /midnight train/i, (res)
    robot.http("https://midnight-train")
      .get() (err, res, body) ->
        if err
          res.reply "Had problems taking the midnight train"
          robot.emit 'error', err, res
          return
        # 残りのコードをここに記述


2番目の例では、ユーザがどのようなメッセージを表示するか考えておく必要があります。ユーザに返信するエラーハンドラがある場合は、カスタムメッセージを追加する必要はなく、 get() リクエストに提供されたエラーメッセージを返信することもできますが、もちろん例外レポートをどのように公開したいかによって異なります。

スクリプトの文書化


Hubotスクリプトは、ファイルの先頭にコメントを記述することができます。たとえば、次のようになります。

# Description:
#   <description of the scripts functionality>
#
# Dependencies:
#   "<module name>": "<module version>"
#
# Configuration:
#   LIST_OF_ENV_VARS_TO_SET
#
# Commands:
#   hubot <trigger> - <what the respond trigger does>
#   <trigger> - <what the hear trigger does>
#
# Notes:
#   &ltoptional notes required for the script>
#
# Author:
#   <github username of the original script author>

これらの中で最も重要でユーザが直面するのは Commands です。 読み込み時に、Hubot は各スクリプトの Commands セクションを見て、すべてのコマンドのリストを作成します。 含まれている help.coffee を使用すると、ユーザはすべてのコマンドまたは検索でヘルプを要求できます。 したがって、コマンドを文書化することで、ユーザはより多くの情報を発見することができます。

コマンドを文書化する際には、以下のベストプラクティスがあります:
  • 1行にとどめます。ヘルプコマンドがソートされるので、二番目の行が予期しない場所に挿入され、おそらく意味をなさなくなります。
  • hubot にが何か他の名前を付けられていても、Hubot を hubot として参照してください。自動的に正しい名前に置き換えられます。これにより、ドキュメントを更新しなくても簡単にスクリプトを共有できます。
  • robot.respond のドキュメントでは、常に接頭辞として hubot を付けます。 Hubot はこれをあなたのロボットの名前に自動的に置き換えます。ロボットの名前があればそれをエイリアスに置き換えます。
  • マニュアルページがどのように文書化されているか確認してください。特に角カッコは省略可能な部分を示し、任意の数の引数に対して '...'を指定します。

その他のセクションは、ボットの開発者、特定の依存関係、設定変数、および注意事項に関連しています。 hubot-scripts へ寄稿するには、スクリプトの起動と実行に関連するすべてのセクションが含まれている必要があります。

永続性


Hubot には、 robot.brain として公開されたインメモリの Key-Value ストアがあり、スクリプトでデータを格納および取得するために使うことができます。
robot.respond /have a soda/i, (res) ->
  # ソーダ数を取得する(数値に強制)
  sodasHad = robot.brain.get('totalSodas') * 1 or 0

  if sodasHad > 4
    res.reply "I'm too fizzy.."

  else
    res.reply 'Sure!'

    robot.brain.set 'totalSodas', sodasHad+1
robot.respond /sleep it off/i, (res) ->
  robot.brain.set 'totalSodas', 0
  msg.reply 'zzzzz'

スクリプトがユーザデータを検索する必要がある場合、 user.NameuserForIduserForFuzzyName 、および usersForFuzzyName というID、名前、または「あいまいな」一致で1つまたは複数のユーザを検索するためのrobot.brainのメソッドがあります。
module.exports = (robot) ->

  robot.respond /who is @?([\w .\-]+)\?*$/i, (res) ->
    name = res.match[1].trim()

    users = robot.brain.usersForFuzzyName(name)
    if users.length is 1
      user = users[0]
      # 何か面白いことをここに..

      res.send "#{name} is user - #{user}"

スクリプトのロード

主に3つのソースからスクリプトがロードされます。
  • scripts/ ディレクトリにある hubot インストールに バンドルされているすべてのスクリプト
    hubot-scripts.json で指定した hubot-scripts npm パッケージに含まれている コミュニティスクリプト
  • external-scripts.json で指定された外部の npm パッケージ からロードされるスクリプト

scripts/ ディレクトリから読み込まれたスクリプトは、アルファベット順にロードされるので、スクリプトの一貫したロード順序が期待できます。 例えば:
  • scripts/1-first.coffee
  • scripts/_second.coffee
  • scripts/third.coffee

スクリプトの共有


ロボットの友人の能力を拡張するためのスクリプトをいくつか作成したら、それらを世界と共有することを検討する必要があります。最低限、スクリプトをパッケージ化して Node.js パッケージレジストリ に提出すできです。以下のスクリプトを共有化するためのベストプラクティスについても検討してください。

スクリプトパッケージの作成


Hubot 用のスクリプトパッケージを作成することは非常に簡単です。最初に hobot yeoman ジェネレータをインストールします:

% npm install -g yo generator-hubot

hubot ジェネレータをインストールします。 Hubot スクリプトを作成することは新しいHubotを作成することに似ています。独自のhubotスクリプト用のディレクトリを作成し、それに新しい hubotスクリプト を生成します。 たとえば、 "my-awesome-script" という Hubot
スクリプトを作成する場合は、次のようにします:

% mkdir hubot-my-awesome-script
% cd hubot-my-awesome-script
% yo hubot:script


この時点で、スクリプトの著者、スクリプトの名前(ディレクトリ名で推測される)、簡単な説明、およびキーワード (少なくとも hubot 、このリストの hubot-scripts ) を見つけるための質問が表示されます。

git を使用している場合、生成されたディレクトリには .gitignore が含まれているため、すべてを初期化して追加できます:

% git init
% git add .
% git commit -m "Initial commit"


これで、準備が整った hubot スクリプトリポジトリが完成しました。 あらかじめ作成されている src/awesome-script.coffee ファイルを開いて、スクリプトを作成してください。 準備が整ったら、この文書 にしたがって npmjs に公開することができます!

おそらく、新しいスクリプト用の単体テストを書くことになります。 サンプルのテストスクリプトは、 test/awesome-script-test.coffee に書き込まれます。これは grunt で実行できます。 テストの詳細については、 "Hubot スクリプトのテスト"節を参照してください。

Listenerメタデータ


正規表現とコールバックに加え、 hear 関数と respond 関数もまた任意のメタデータを生成された Listener オブジェクトへ結びつけることができるオプション options Object を受け入れます。このメタデータにより、スクリプトパッケージを変更せずにスクリプトの動作を簡単に拡張することができます。

最も重要で最も一般的なメタデータキーは @id@ です。 すべてのリスナには一意の名前を付ける必要があります ( options.id ; デフォルトは null )。 名前はモジュール (たとえば 'my-module.my-listener')でスコープ化する必要があります。これらの名前は、他のスクリプトが個々のリスナに直接指定し、許可やレート制限のような追加機能を使ってそれらを拡張することを可能にします。

拡張に加えて、追加のメタデータキーを定義し、処理することができます。 詳細については、"Listener ミドルウェア" 節を参照してください。

前の例に戻ります:

module.exports = (robot) ->
  robot.respond /annoy me/, id:'annoyance.start', (msg)
    # 誰かを怒らせるコード

  robot.respond /unannoy me/, id:'annoyance.stop', (msg)
    # 迷惑をかけないようにするコード

これらのスコープ付き識別子を使用すると、次のような新しい動作を外部から指定できます:
  • 権限ポリシ: " annoyers グループの誰もが annoyance.* コマンドを許可する"
  • レート制限:"30分ごとに1回 annoyance.start の実行のみを許可する"

ミドルウェア


ミドルウェアには、Receive (受信)、Listener (リスナ)、Response (応答)の3種類があります。

Receive (受信)ミドルウェアは、リスナーがチェックされる前に1回だけ実行します。
Listener (リスナ)ミドルウェアは、メッセージに一致するすべてのリスナに対して実行します。
Response (応答)ミドルウェアは、メッセージに送信されるすべての応答に対して実行します。

プロセス実行およびAPI


Expressミドルウェアと同様に、Hubot はミドルウェアを定義順に実行します。各ミドルウェアは、( nextを呼ぶことで)チェーンを続行するか、( doneを呼ぶことで)チェーンを中断するかどちらかが可能です。すべてのミドルウェアが継続すると、リスナコールバックが実行され、 done が呼び出されます。ミドルウェアは done コールバックをラップして、(リスナコールバックが実行されたか、ミドルウェアのより深い部分が中断された後)プロセスの後半でコードを実行を許可します。

ミドルウェアは次のように呼び出されます:

context
  • 各ミドルウェアタイプのAPIを参照して、コンテキストが公開する内容を確認
next 
  • プロパティを持たないFunctionで、次のミドルウェアを継続/Listerコールバック実行
  • next が(done 関数か最終的に done がコールされる新たな関数のどちらかを提供するか)を指定する単一のオプション引数付きで呼び出される
  • もし引数が与えられなかったならば、提供された done が責任を負う

done
  • ミドルウェアの実行を中断し、一連の完了関数の実行を開始するために呼び出される追加のプロパティを持たないFunction引数無しで done が呼び出される

すべてのミドルウェアは、 context next 、および done の同一のAPIシグニチャを受け取ります。異なる種類のミドルウェアは、 context オブジェクト内で異なる情報を受け取ることがあります。 詳細は、各タイプのミドルウェアのAPIを参照してください。

エラーハンドリング


(イベントループを生成しない)同期ミドルウェアの場合、標準リスナの場合と同様に、自動的にエラーを検出してエラーイベントを生成します。 Hubot は自動的に最新の完了コールバックを呼び出してミドルウェアスタックを巻き戻します。非同期ミドルウェアは、独自の例外をキャッチし、エラーイベントを発行し、完了を呼び出す必要があります。 キャッチされない例外は、ミドルウェア完了コールバックのすべての実行を中断します。

Listener ミドルウェア


Listener ミドルウェアは、メッセージと一致するリスナと実行中のリスナとの間にロジックを挿入します。これにより、一致するスクリプトごとに実行される拡張機能を作成できます。例には、集中型の認可ポリシ、レート制限、ロギング、およびメトリックが含まれます。ミドルウェアは、他の hubot スクリプトと同様に実装されています。 hear メソッドや respondメソッドを使用する代わりに、ミドルウェアは listenerMiddleware を使用して登録されます。

Lister ミドルウェアの例

完全に機能する例は、 hubot-rate-limit にあります。

ミドルウェアのロギングコマンド実行の簡単な例:

module.exports = (robot) ->
  robot.listenerMiddleware (context, next, done) ->
    # ログコマンド
    robot.logger.info "#{context.response.message.user.name} asked me to #{context.response.message.text}"
    # ミドルウェアの実行を継続
    next()

この例では、リスナに一致するチャットメッセージごとにログメッセージが書き込まれます。

レート制限の決定を行うもっと複雑な例:

module.exports = (robot) ->
  # 最後の実行時にリスナIDをマップ
  lastExecutedTime = {}

  robot.listenerMiddleware (context, next, done) ->
    try
      # リスナが異なる最小期間を指定しない限り、デフォルトは1秒です
      minPeriodMs = context.listener.options?.rateLimits?.minPeriodMs? or 1000

      # コマンドが最近実行されたかどうかを確認
      if lastExecutedTime.hasOwnProperty(context.listener.options.id) and
         lastExecutedTime[context.listener.options.id] > Date.now() - minPeriodMs
        # コマンドがとても早く実行されています!
        done()
      else
        next ->
          lastExecutedTime[context.listener.options.id] = Date.now()
          done()
    catch err
      robot.emit('error', err, context.response)

この例では、ミドルウェアがリスナが最後の1,000msで実行されたかどうかを確認します。存在する場合、ミドルウェアの呼び出しはすぐに完了し、リスナのコールバックが呼び出されないようにします。リスナの実行が許可されている場合、ミドルウェアは完了したハンドラーをアタッチし、リスナーが実行を終了した時刻を記録できるようにします。

またこの例では、リスナ固有のメタデータを使用して非常に強力な拡張機能を作成する方法も示しています:スクリプト開発者がレート制限ミドルウェアに対してミドルウェアを追加しリスナオプションを設定するだけで異なるレートへのより簡単なレート制限コマンドを簡単に使うことができます。

module.exports = (robot) ->
  robot.hear /hello/, id: 'my-hello', rateLimits: {minPeriodMs: 10000}, (msg) ->
    # 10秒に1回以上実行されません
    msg.reply 'Why, hello there!'

Lister ミドルウェア API



リスナのミドルウェアコールバックには、 context next 、そして done という3つの引数があります。 next done については、 ミドルウェアAPIを参照してください。 リスナのミドルウェア・コンテキストには、次のフィールドがあります。
lister
  • options : リスナ定義時のオプションセットを含む単純なObject。 Listenerメタデータ節 を参照のこと。
  • その他全てのプロパティは内部で考慮される。

response
  • 標準レスポンスAPIのすべての部分がミドルウェアAPIに含まれている。 sendおよびreply節を参照のこと。
  • ミドルウェアは応答オブジェクトを追加情報でdecorate (ただしmodifyできない)ことが可能(例:ユーザのLDAPグループを用いて response.message.user にプロパティ追加)
  • 注:テキストメッセージ( response.message.text ) は、リスナミドルウェアで不変であるとみなされるべき

Receive ミドルウェア


Receive (受信) ミドルウェアは、リスナが実行される前に実行されます。 ID、metrics などを追加するように更新されていないコマンドをブラックリストに登録するのに適しています。

Receive ミドルウェアの例


このシンプルなミドルウェアは、 hear リスナを含む特定のユーザによる使用を禁止します。 ユーザが明示的にコマンドを実行しようとすると、エラーメッセージが返されます。

BLACKLISTED_USERS = [
  '12345' # 請負業者のユーザーIDのアクセスを制限
]

robot.receiveMiddleware (context, next, done) ->
  if context.response.message.user.id in BLACKLISTED_USERS
    # このメッセージをこれ以上処理しないこと。
    context.response.message.finish()

    # メッセージが 'hubot'またはエイリアスパターンで始まる場合、
    # このユーザは明示的にコマンドを実行しようとしていたため、
    # エラーメッセージで応答します。
    if context.response.message.text?.match(robot.respondPattern(''))
      context.response.reply "I'm sorry @#{context.response.message.user.name}, but I'm configured to ignore your commands."

    # これ以上ミドルウェアを実行しない。
    done()
  else
    next(done)

Receive ミドルウェア API


Receive ミドルウェアのコールバックは、 context next 、および done という3つの引数を受け取ります。  next done については、 "ミドルウェアAPI":節を参照してください。 Receive ミドルウェア context には、次のフィールドがあります:
response
  • このレスポンスオブジェクトには match プロパティーがない。これはまだ match するリスナがないためである。
  • ミドルウェアは、Receive オブジェクトを追加情報で装飾してもよい(例:ユーザの LDAP グループを用いて response.message.user にプロパティを追加)。
  • ミドルウェアは response.message オブジェクトを変更することがある。

Resonse ミドルウェア


Resonse (応答) ミドルウェアは、 hubot がチャットルームに送信するすべてのメッセージに対して実行されます。 メッセージのフォーマット、パスワードの漏洩、metrics などに役立ちます。

Resonse ミドルウェアの例


この簡単な例では、チャットルームに送信されるリンクの形式を、マークダウンリンク(たとえば " example ":https://example.com/ )から "Slack": でサポートされる形式 ( https://example.com|example ) に変更します。
module.exports = (robot) ->
  robot.responseMiddleware (context, next, done) ->
    return unless context.plaintext?
    context.strings = (string.replace(/\[([^\[\]]*?)\]\((https?:\/\/.*?)\)/, "<$2|$1>") for string in context.strings)
    next()

Resonse ミドルウェア API


Resonse ミドルウェアのコールバックは、 context 、 next 、そして done の3つの引数を受け取ります。  next と done については、 "ミドルウェアAPI":節を参照してください。 Resonse ミドルウェアcontextには、次のフィールドがあります:
response
  • このresponseオブジェクトは、ミドルウェアから新しいメッセージを送信するために使用可能である。 これらの新しい応答に対してミドルウェアが呼び出される。無限ループを作成しないように注意すること。

strings
  • チャットルームアダプタに送信される文字列の配列。 これらの編集や、 context.strings = ["new strings"] を使った置換が可能。

method
  • リスナが送信したresponseメッセージのタイプ( send reply emote topic など)を表す文字列。

plaintext
  • true または undefined 。 メッセージが通常の平文タイプ(送信や返信など)の場合、これは true に設定される。このプロパティは読み取り専用として扱う必要がある。

Hubot スクリプトのテスト


hubot-test-helperは、Hubot スクリプトの単体テストに適したフレームワークです ( hubot-test-helper を使用するには、 Promises をサポートする最近の Node バージョンが必要となります)。

Hubot インスタンスにパッケージをインストールします:

% npm install hubot-test-helper --save-dev


また、次のものをインストールする必要があります:
  • Mocha などの JavaScript テストフレームワーク
  • chai expect.js などのアサーションライブラリ

また、次のものもインストールしたくなるでしょう:
  • coffee-script (JavaScriptではなくCoffeeScriptでテストを書く場合)
  • Sinon.js のような mock ライブラリ(スクリプトがWebサービス呼び出しやその他の非同期アクションを実行する場合)

ここでは、 Hubot サンプルスクリプト の最初の2つのコマンドをテストするサンプルスクリプトを示します。 このスクリプトでは、 Mochachaicoffee-script 、そしてもちろん hubot-test-helper を使用しています:
test/example-test.coffee
Helper = require('hubot-test-helper')
chai = require 'chai'

expect = chai.expect

helper = new Helper('../scripts/example.coffee')

describe 'example script', ->
  beforeEach ->
    @room = helper.createRoom()

  afterEach ->
    @room.destroy()

  it 'doesn\'t need badgers', ->
    @room.user.say('alice', 'did someone call for a badger?').then =>
      expect(@room.messages).to.eql [
        ['alice', 'did someone call for a badger?']
        ['hubot', 'Badgers? BADGERS? WE DON\'T NEED NO STINKIN BADGERS']
      ]

  it 'won\'t open the pod bay doors', ->
    @room.user.say('bob', '@hubot open the pod bay doors').then =>
      expect(@room.messages).to.eql [
        ['bob', '@hubot open the pod bay doors']
        ['hubot', '@bob I\'m afraid I can\'t let you do that.']
      ]

  it 'will open the dutch doors', ->
    @room.user.say('bob', '@hubot open the dutch doors').then =>
      expect(@room.messages).to.eql [
        ['bob', '@hubot open the dutch doors']
        ['hubot', '@bob Opening dutch doors']
      ]
サンプル出力
% mocha --compilers "coffee:coffee-script/register" test/*.coffee


  example script
    ✓ doesn't need badgers
    ✓ won't open the pod bay doors
    ✓ will open the dutch doors


  3 passing (212ms)

以上
----------

この文書を読めば、

  • エラーハンドリングの仕方
  • ロードされる順番
  • コメントもHubothはチェックしている件(特にCommands)
  • タイムアウトやインターバルで遅い場合の応答も可能
  • 永続性ならNoSQL robot.brain
  • N回に1回実行するパターン
  • Listener、Receiveミドルウェア
  • hubot-test-helper およびテストフレームワークの活用によるテスト

といったところが理解できる。


p.s.
以下の記事も書きました。
よろしければ、どうぞ。

Hubot Document: Patterns を翻訳して、チャットボットでありがちな実装を確認する
https://fight-tsk.blogspot.jp/2017/04/hubot-document-patterns.html

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...