Mobb 0.5 and Repp 0.4 out now
このエントリは Mobb/Repp Advent Calendar の二十五日目です
Mobb 0.5.0 out now
🎉
クリスマスなので、超急ぎでリリースしました。Ruby 2.6.0 も出たし。
Mobb 0.5.0では、Advent Calendar で予告していたいくつかの機能がリリースされます。
Mobbのメソッド呼び出しをチェーンする、 chain/trigger シンタックス
Mobb製のBotになにか処理をさせたが、何も反応を返したくないときはどうするのか
これらの機能の新規実装により、 chain/trigger, react_to_bot/include_myself, matched, say_nothing/silent, pass キーワードが新たにMobbに追加されました。
require 'mobb'
# chain/trigger
on 'hello' do
chain 'chain1', 'chain2'
'yo'
end
trigger 'chain1' do
chain 'chain3'
'yoyo'
end
trigger 'chain2' do
'yoyoyo'
end
trigger 'chain3' do
'yoyoyoyo'
end
# react_to_bot/include_myself
on /i'm (\w+)/, react_to_bot: true do |name|
"hello #{name}"
end
on /yo (\w+)/, react_to_bot: true, include_myself: true do |name|
"yo #{name}"
end
# matched
on /taks (?<task_name>\w+)/ do
"act #{matched[:task_name]}"
end
# say_nothing/silent
on /do (\w+)/ do |task|
say_nothing if task == 'slow_task'
"act #{task}"
end
on 'bad!', silent: true do
$stderr.puts("#{@env.user.name} is bad")
end
# pass
on 'yo' do
pass
'yo'
end
on 'yo' do
'yoyo'
end
また、次の機能は予告していましたが0.5.0には入りませんでした。
理由としては、実装そのものは概ね出来ているのですが、大きな機能追加が入りきちんとリリース前の検証が出来なかったからです。この機能は、検証が終わり次第リリースします。
Happy Mobb
25日間なんとかACを完走できました、これからもMobbをよろしくおねがいします。
Mobb/Repp Advent Calendar のネタが尽きたので、開発してて面倒な話をします
このエントリは、 Mobb/Repp Advent Calendar の二十四日目です
Mobb開発とRepp開発で困る依存性
Mobbの開発には、大きく分けて2つのケースがあります。Mobb単体の機能追加や修正で済む場合と、Repp側にも機能追加や修正が必要な場合です。
Mobb単体で終わる場合にはなにも困ることは無いのですが、Repp側との連携をしなくてはいけない場合は面倒なことが発生します。Repp側の機能追加や修正を、開発中のMobbにどうやって適用するかです。場合によっては、ReppとMobbのコードを行ったり来たりしながら修正を行う場合もあります。
Mobb/Repp Advent Calendar のネタが尽きたので、対応したいハンドラとか書いときます
このエントリは Mobb/Repp Advent Calendar の二十三日目です。
ネタが切れたので対応したいハンドラとか書いておきます
本当に作るかどうかはわかりませんが
-
Discord
-
LINE
-
IRC
-
ChatWork
-
他にも何かあれば教えてください
MobbアプリケーションをRack上で起動できるか?
このエントリは Mobb/Repp Advent Calendar の二十二日目です
Mobbアプリケーション is Rackアプリケーション?
結論から言うと、動きません。
試しにこんなアプリを書いて起動してみました。
app.rb
require 'mobb/base'
class MyApp < Mobb::Base
before do
p @env
end
end
config.ru
require './app'
run MyApp.new
起動
bundle exec rackup
MobbはRackのアプリケーションとほぼ互換なので、理屈の上では動きそうなものですが、動きませんでした。理由としては、Mobbがサービスからの情報を受け取ったときに処理するfilterやhandle_eventメソッドの中で使われている、process_eventメソッドにありました。
process_event の中身は次のようなメソッドです。
def process_event(pattern, conditions, block = nil, values = [])
res = pattern.match?(@env.body)
catch(:pass) do
conditions.each { |c| throw :pass unless c.bind(self).call }
case res
when ::Mobb::Matcher::Matched
block ? block[self, *(res.matched)] : yield(self, *(res.matched))
when TrueClass
block ? block[self] : yield(self)
else
nil
end
end
end
この中で、 @env.body を参照している箇所に問題がありました。Reppと違い、Rackの送ってくるenvオブジェクトには、bodyというメソッドが存在しないからです。
比較のために、Mobbが参考にしているSinatraのprocess_routeメソッドを見てみましょう。
def process_route(pattern, conditions, block = nil, values = [])
route = @request.path_info
route = '/' if route.empty? and not settings.empty_path_info?
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
return unless params = pattern.params(route)
params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
force_encoding(params)
original, @params = @params, @params.merge(params) if params.any?
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
if regexp_exists
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
values += captures
@params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty?
else
values += params.values.flatten
end
catch(:pass) do
conditions.each { |c| throw :pass if c.bind(self).call == false }
block ? block[self, values] : yield(self, values)
end
rescue
@env['sinatra.error.params'] = @params
raise
ensure
@params = original if original
end
最初に参照しているのが、 @request.path_info というメソッドで、これはどう考えてもHTTPに存在し、チャットボットに存在しない概念です。
残念ながら、MobbをRackで動かすという試みは、このRackとReppの微妙な世界観の違いで頓挫しました。
MobbをRackで動かせるべきか?
答えはNoです。MobbはSinatraを最大限にリスペクトしていますが、Sinatraの世界観とは違うものです。もちろん動かせれば面白いとは思いますが、MobbをRackに対応させる理由は全くありません。
Mobbを使った複数サービス間のゲートウェイを実現する方法
このエントリは、 Mobb/Repp Advent Calendar の二十一日目です
サービス間ゲートウェイ
Slackの発言をIRCに転送したり、HTTPアクセスを受け取ってSlackに投稿するIncomming Webhook のようなものをMobbで書きたい場合にはどうすれば良いでしょうか?
Mobbのロジックに書く
一つ目の答えは、Mobbアプリケーションのロジックに、転送先のサービスのクライアントを記述して、入力をすべてそちらに飛ばし、ブロックの戻り値はnilにして入力元のサービスには何も返さない方法です。多分これは一番直感的で楽だと思います。
Reppハンドラを書く
もう一つの手段は、専用のReppハンドラを用意してしまうことです。デフォルトのハンドラでは、入力と出力のソースが同じため、容易にゲートウェイの動作をさせることはできません。しかし、入力と出力を別々にもつReppハンドラを記述することはできます。
IoTしてますか? 難しいですよね? でもお手軽にWxBeacon2を使って室内環境監視ダッシュボードとか作れますよ?
このエントリは、 dwango Advent Calendar の二十一日目です
TL; DR
-
WxBeacon2を使って、簡易室内環境モニタを作ります
-
どこでも確認したいので、DBとフロントはWebに置きます
-
WxBeacon2 + Python + Fluentd + InfluxDB + Grafana + DockerCompose
お手軽に室内環境を監視したい
世の中IoTとかMakerとかいう言葉が流行り始めて数年が立ちましたが、Raspberry Piは買ったものの特に何を作るわけでもなく完全に腐らせている方は、私の他にも多いのではないでしょうか。電子工作の本とか買ってみて、いろいろなんかやろうとか思ってみはしたものの、本業や趣味のコーディングのほうが楽しくて、あまり真剣に向き合って来ませんでした。
ブレッドボードになんか刺したり、秋月や千石に行ってパーツを探したり、はんだ付けしたり、なんかかっこいいものを作って人にオォーって言われたかったりしたかった人生なんですが、まあそれはそれとして漠然と何かを作れる人に憧れがありました。そんなとき、技術書展5に出た際にブースを手伝ってくれた友人が、何やらカバンに奇妙な物体をつけているのを見ました。話を聞くと、どうやらそいつは気象系のセンサをいろいろ詰め込んだ便利なやつで、スマホアプリと接続して情報を見たり、BLEでPCとつなげたりもできるとのことでした。実際にスマホアプリを見てみると、気温や湿度、気圧に周囲の光量や騒音まで定期的に取得していました。それはなんだと聞くと、WxBeacon2だと言われました。
WxBeacon2
WxBeacon2とは、Weather News が販売している簡易気象観測器です。Weather News のアプリからポイントを貯めると貰えるらしいですが、まどろっこしいのでお金を払って買うことも出来ます。本体と消費税と送料込みで、5000円しないので、パパっと買ったほうが良いです。
WxBeacon2は、内容としてはオムロン製の2JCIE-BL01というIoTセンサのOEMです。2JCIEシリーズには、WxBeacon2と同型のバッグ型センサの他に、USBドングルの形をしたセンサや、PCB型のセンサも販売されています。ただ不思議なことに、日本や海外のどのセンサ通販サイトを見ても、USB型やBAG型は軒並み単価10000円を超え(しかもボリュームディスカウントも薄い)、PCB型に至っては売っているサイトすら見つけられません。そんな中、なぜかWeather News は半額以下の5000円未満でBAG型のOEM品を販売していて、ダントツで安く手に入れられるのです。なので、どうしてもUSB型が必要とかいう場合を除いて、Weather News で買うのが最も安く手に入れられる方法です。
そしてWxBeacon2もとい2JCIE-BL01は、なぜかGitHubに通信用のサンプルコードが置かれています。多分公式ではないと思うのですが(他にOmronのリポジトリも無いし、そもそもOrganizationもないので)、ここに載せられているサンプルプログラムだけで十分なので、こちらを参照します。
https://github.com/OmronMicroDevices/envsensor-observer-py
今回作りたいもの
Mobbのマッチングにどれもヒットしなかった場合のフック
このエントリは、 Mobb/Repp Advent Calendar の二十日目です
どれにもマッチしなかった
これは次のバージョンで実装される機能の話です。
Mobbはonキーワードでマッチングを登録し、サービスからの入力に対して一致チェックを行い、一致すればブロックを実行します。しかし、どこにもマッチしなかった入力は虚空に消えていきます。
Webフレームワークの場合、どこにもマッチしなかった場合には404が帰ります。そのため、404のページにはなにを表示するかといった設定ができます。しかし、チャットBotはWebのようにリクエストとレスポンスが対象的な世界ではないので、特にそういうものはありません。
しかしその一方で、どこにもマッチしなかった入力に対してなにか処理をしたいという需要はあると思います。それは、マッチングの対象ではない要素に対してなにかをしたい場合であったり(フィルタを使うという手もありますが)、すべての入力に対して一律なにか処理をしたりという場合です。
require 'mobb'
on 'Hi' do
'Yo'
end
on_unregistered do
# デバッグメッセージ
puts "#{@env.body} は登録されているパターンにマッチしませんでした"
end
on_unregistered キーワードは、すべてのマッチング処理の最後にチェックされ、どこにも一致しているものがない場合は必ずこのブロックを実行するというものです。うえのbotでは、マッチングのデバッグを行うために、一致しなかったすべての処理を出力しています。
Mobbにおけるマッチのパッシング
このエントリは Mobb/Repp Advent Calendar の十九日目です
マッチのパッシング
この機能は次のバージョンにおいて実装される予定です。
次のようなBotを作成し、「hello Mobb」というメッセージを送った場合、得られる結果は「Yo」です。
require 'mobb'
on /hello (\w+)/ do |name|
'Yo'
end
on 'hello Mobb' do
'Survival of the fittest'
end
これは、Mobbのパターンマッチは定義した順番にチェックされるので、最初の /hello (\w+)/ がすべての hello で始まるメッセージを吸収してしまい、次に定義されている ‘hello Mobb’ にマッチすることは決してありません。
この例は非常に極端な例ですが、特定のケースにおいてマッチングをパスしたいことは発生すると思われます。そのため、次のバージョンではpassキーワードが導入されます。
require 'mobb'
on /hello (\w+)/ do |name|
pass if name.start_with?('M')
'Yo'
end
on 'hello Mobb' do
'Survival of the fittest'
end
passキーワードは、呼び出されるとその場でブロックの評価を停止し、on/receive キーワードのマッチングを再開します。上のBotでは、nameがMで始まる場合は、 on /hello (\w+)/ のブロックを抜け、次の on ‘hello Mobb’ にマッチします。その結果、得られる返答は「Yo」ではなく「Survival of the fittest」になります。(もちろんこの例では、Mobb以外のMで始まる名前を送るとすべてのケースでなにも返答しなくなってしまいますが)
Next Mobb
年内リリースがんばります
Mobb+GitQueueでバージョン管理付きのTODO Botを作る
このエントリは Mobb/Repp Advent Calendar の十八日目です
Mobb + GitQueue = バージョン管理つきTODO bot
ちょうど一年くらい前、Gitをバックエンドとしたスタックを実装した話をしました。
http://tolarian-academy.net/task-manage-bot-with-git/
これを実際にMobbと組み合わせたBotを作成してみます。
require 'mobb'
require 'git_queue'
STORAGE = './storage'
set :service, 'slack'
set :name, 'YOUR BOT NAME'
helpers do
def stack
@stack ||= begin
GitQueue::Storage.create(STORAGE) unless File.exists?(STORAGE)
GitQueue::Queue.new(STORAGE)
end
end
end
set(:channel_filter) { |name| condition { @env.channel == name } }
on /add (.+)/, ignore_bot: true, reply_to_me: true, channel_filter: 'CXXXXXXX' do |task|
stack.push(task)
"#{task} を追加した"
end
on /now/, ignore_bot: true, reply_to_me: true, channel_filter: 'CXXXXXXX' do
task = stack.queue.first
"#{task} をやれ"
end
on /done/, ignore_bot: true, reply_to_me: true, channel_filter: 'CXXXXXXX' do
task = stack.pop
"#{task} が終わった"
end
# TODO: Fix Mobb capture bug
on /switch \d+\s+\d+/, ignore_bot: true, reply_to_me: true, channel_filter: 'CXXXXXXX' do
target = /(\d+)\s+(\d+)/.match(@env.body).captures
t1 = target[0].to_i
t2 = target[1].to_i
queue = stack.queue
return "そんなにタスクは無い" if queue.size <= t1 || queue.size <= t2
task1 = queue[t1]
task2 = queue[t2]
stack.switch(t1, t2)
"#{task1} と #{task2} を入れ替えた"
end
on /ls/, ignore_bot: true, reply_to_me: true, channel_filter: 'CXXXXXXX' do
"タスク一覧\n" + stack.queue.map { |t| "- #{t}" }.join("\n")
end
GitQueueというGemを使ってスタックを作り、それをMobb経由で操作しています。
まあまあ便利です。
Mobb製のBotになにか処理をさせたが、何も反応を返したくないときはどうするのか
このエントリは Mobb/Repp Advent Calendar の十七日目です
Botに何も発言をさせたくないとき
Mobbで作られたBotは、ブロックの戻り値の文字列をサービスに投稿します。しかし、ブロックを実行したあとに何も発言したくないときはどうすればいいのでしょうか? 答えは、nilを返せばいいのです。
require 'mobb'
on /.+/ do
File.write("#{Time.now}.txt, @env.body)
nil
end
このBotは、すべての発言に反応し、その発言をファイルに書き込み、何も投稿せずにそのまま処理を終了します。しかし、このnilを返す方法はやや分かりづらいとの指摘を受けたので、次のバージョンでは say_nothing キーワードと silent コンディションを用意します。
require 'mobb'
on /hello \w+/ do |name|
say_nothing if name != settings.name
"hello"
end
on /.+/, silent: true do
File.write("#{Time.now}.txt", @env.body)
end
say_nothing キーワードは、 say_nothing が呼び出された場合に、そのブロックがなんの値を返そうがサービスにポストを行いません。つまり、nilを返したときと同じ挙動をします。
silentはコンディションなので、現時点でも任意で追加可能ですが、Mobbのデフォルトに追加します。内容としては、ブロック実行後の戻り値を見て、何が入っていようがnilで上書きするコンディションを追加します。おそらく、実装としては次のようなコードになるのではないでしょうか?
require 'mobb'
helpers do
def silent(cond) do
dest_condition do |res|
res[0] = nil
end
end
end
on /.+/, silent: true do
"this string is not post to service"
end
次のバージョンのMobbにご期待下さい
以上です