メール処理用 Bot Rounders について
Rounders という Ruby 製 Bot フレームワークを作りました
下記のようなイメージでメール処理を出来ます。
だいたい半年に一回くらいガッと開発してます。
※書いてあるのは試しに作った AWS の請求書を Slack にぶん投げる処理
require 'slack-ruby-client'
module Rounders
module Handlers
class Invoice < Rounders::Handlers::Handler
# メールをフィルタリングするAND条件、第二引数に渡したSymbolはメソッド名
on({
subject: 'Amazon Web Services Invoice Available',
from: 'aws-receivables-support@email.amazon.com'
}, :aws)
def aws(mail, matches)
pdf = mail.attachments.first
slack_client.files_upload(
channels: '#hogehoge',
file: UploadIO.new(StringIO.new(pdf.decoded), 'application/pdf', pdf.filename),
title: mail.subject,
as_user: true,
filename: pdf.filename,
)
rescue => e
Rounders.logger.error e
end
def slack_client
@client ||= Slack::Web::Client.new
end
end
end
end
類似のOSSとしては下記のものがあります。
使い方
gem install rounders
-
rounders new [ボット名]
こんな感じで自動生成されます - メールサーバの設定をconfig/initializers/mail.rbに記述
- dotenvが使えるので開発中は.env にメールサーバの情報を書くのがおすすめです。
-
bundle exec rounders new handler {ハンドラのクラス名}
で処理部分を生成し最初の例のように実装 -
bundle exec rounders start
で起動。--dotenvでdotenv有効化
で動かすことができます
動機
個人の感想というか経験ですが、
ラフにメールの処理を自動化したいと思った時に、こういう記述になりがちです。
# ※適当なコード
Mail.all.each do |mail|
if mail.subject == "期待する件名"
# なにがしかの処理
end
if mail.body == "期待するbody"
# なにがしかの処理
end
#延々と続く条件式・・・
end
もともと node.js で会社のメール処理自動化 bot を作っていて、
受注メールでラズパイから音を出したり、掃除当番をアナウンスしてたのですが、
案の定メール内容にマッチさせる箇所がカオスになった経験があり、
どうにかならないかな―と考えていました。
それからしばらく後、チャット bot フレームワークのRubotyで幾つかプラグインを作った際に、
bot likeなインターフェースでメールを受け取れれば楽なのでは!と思い実装するに至りました。
(なのでアーキテクチャなどがだいぶ Ruboty に似ています。あとはLita)
実際フレームワークというほど処理はしてなくて、ただ交通整理するだけのイメージですが、
Ruby におけるプラガブルなシステムの作り方や、Rails の Scaffold の勉強になったので作ってよかったと感じます。
名前の由来
FBFBFBFB....
開発時シュタゲを見ていて、ちょうど萌葉がまゆり撃ち抜くあたりだったので、
彼女等ラウンダーから取ってきました。
Gemを作成する前には、Rubygemsにて名前を調べておくという作業が必須になります。当たり前ですね。
その当たり前のことを怠り、
華麗なフルリネームを行うことになりました。
(魔女の宅急便のイメージでKikiとつけていました。)
実装での思い出深いこと
プラグインシステム
Rubotyのプラグインシステムにすごく魅力を感じ影響を受けたので、
メール処理は個々人の事情が出やすく、プラグインシステムの効果ないんじゃないという思いを黙殺しつつ、
Roundersもプラグインを書きやすい構成に実装しました。
当時のRubotyのプラグイン機構を真似しています。
Rubotyはinheritedのフックを用いて継承されたクラスを管理下に置きます。
Roundersも同様にinheritedのフックを前提に作られています。
ジェネレータ機能
機能を追加するときに記述するコード量を減らしたかったので、
ジェネレータ関連の機能を充実させました。
普通にThorを使うべきだと思います。
当初はThorを知らなかったため、mastacheなどを利用してましたが、
ユーザとの対話処理などを実装することを考えるとThorが良いかなという印象です。
Thorには最高の資料もあります
Config
プラグインを作る際、接続先など実行時に動的な設定が必要になることがあるかと思います。
その際はプラグインのクラスに.config で指定を行うと生成されるaplication.rbで実行時に値を与えることができます。
config の設定はすべての拡張可能なクラスで適用できます。
この機能はToppingという Gem で実装されています。
module Rounders
module Stores
class Conne < Rounders::Stores::Store
config :file, required: true, type: String
end
end
end
class Rounders::Application
# 名前空間を小文字にして'.'で区切ったパスでアクセスできる
config.stores.yaml.file = '設定したいファイルパス'
end
今後
- れーるずと組み合わせてなんかしてみたい
- メール系SaaSのwebhook受け取ったりしたい
- 返信機能つけたい