メール処理用のRuby製botフレームワークを作った(作っている)

メール処理用 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

今後

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.