僕の通っているオフィスでは社内の有志メンバーを募って仕出し弁当を注文していました。 毎朝10時ちょっと前までにFAXで注文しておけば、お昼休みまでにオフィスに届けてくれるという仕組みでした。 しかも、オフィスの前にやってくる弁当屋さんよりも、ちょっと安い!
しかし、毎朝注文を集計してFAXで注文するというのはなかなか大変。およそ人のやることではないということで自動化しました。
フローの設計
- 注文の受付は Google スプレッドシート。Sheets V4 API を使って注文数を取得する。
- HexaPDF で FAX する文章を生成
- Twilio Programmable FAX で送信
- Chatwork API で結果通知
今回は Ruby で実装しました。
Google スプレッドシートで注文管理
注文は Google スプレッドシートで管理していました。(横列方向に注文する人、縦列方向に注文日を記載)
Sheets V4 API を使い、毎日の注文数をこのようなスクリプトで集計していました。*1
require 'google/apis/sheets_v4' require_relative 'google_authorize.rb' service = Google::Apis::SheetsV4::SheetsService.new service.authorization = google_authorize t = Time.now sheet_name = sprintf('%04d-%d', t.year, t.mon) value_range = service.get_spreadsheet_values(SPREADSHEET_ID, "#{sheet_name}!A4:ZZ35", value_render_option: 'FORMATTED_VALUE') values = value_range.values header = values.shift order_info = Hash.new values.each do |row| day_header = row[0].scan(/^(\d+)日/)[0] if day_header.nil? next end day = day_header[0].to_i orders = row[1] price = row[2] members = Array.new (3..(row.length-1)).each do |i| if row[i] != '' members << header[i] end end order_info[day] = { :day => day, :day_formatted => row[0], :orders => orders.to_i, :price => price.to_i, :members => members } end today_item = order_info[t.day]
HexaPDF で注文 FAX を生成
お弁当屋さん規定のフォーマットを保存したPDFに、取得した注文数を書き込みます。この処理では HexaPDF というライブラリが使いやすかったです。
require 'hexapdf' def generate_order_pdf(month, day, count) dir = File.dirname(__FILE__) pdf = HexaPDF::Document.open("#{dir}/order.pdf") page = pdf.pages[0] canvas = page.canvas(type: :overlay) canvas.font('Helvetica', size: 18) canvas.text(month.to_s, at: [178, 341]) # 月 canvas.text(day.to_s, at: [225, 341]) # 日 canvas.font('Helvetica', size: 38) canvas.text(count.to_s, at: [105, 245]) # Aセット普通 canvas.text(count.to_s, at: [395, 245]) # 合計 return pdf end pdf = generate_order_pdf(t.mon, t.day, today_item[:orders])
いきなりファイルには書き込まず HexaPDF の PDF ドキュメントオブジェクトを返して次の処理で使っています。
Twilio Programmable FAX を使って送信
ここで、以前午前休電話で使った Twilio の FAX サービス Twilio Programmable FAX を使います。
- FAX 送受信が REST API ベースでできる。
- 送信は毎月 100 ページまで 0 円、101 ページ以降 1.5 円。これに電話番号代と電話代(Programmable Voiceの発着信料)が別途加算。
- 今回の用途だと 1 分で完了していたので、5.4 円/注文で済んだ。
しかし、この API、いきなり送信する FAX の PDF を POST して受け付けてくれるわけではなく、Twilio が HTTP GET でアクセスできる URL に PDF ファイルを置いておき、その URL を指定する必要があります。なので、先に作成した PDF オブジェクトを今回は Google Cloud Storage にアップロードしておくことにしました*2。
require 'google/cloud/storage' GCS_PROJECT_ID = 'xxxxxx' GCS_KEY_FILE = 'gcs_key.json' GCS_BUCKET = 'xxxxxx.appspot.com' storage_path = 'fax/' + Time.now.strftime('%Y%m%d%H%M%S') + '.pdf' storage = Google::Cloud::Storage.new(project: GCS_PROJECT_ID, keyfile: GCS_KEY_FILE) bucket = storage.bucket(GCS_BUCKET) storage_file = nil Tempfile.create() do |temp_file| pdf.write(temp_file) temp_file.seek(0) storage_file = bucket.create_file(temp_file, storage_path) storage_file.acl.public! end ## ここで FAX 送信処理をする # 送信が終わったらファイルを削除する storage_file.delete
ファイルをアップロードした後はいよいよ、Twilio Programmable Fax で FAX を送信します。
まず、twilio.fax.faxes.create
で FAX オブジェクトを作成し、その sid を取得します。以後、twilio.fax.faxes(sid).fetch
で定期的に FAX オブジェクトを取得して結果を確認します。
require 'twilio-ruby' TWILIO_ACCOUNT_SID = 'xxxxx' TWILIO_AUTH_TOKEN = 'xxxxx' TWILIO_FAX_FROM = '+8150xxxxxxxx' TWILIO_FAX_TO = '+81xxxxxxxxx' # 送信開始 twilio = Twilio::REST::Client.new(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) twilio_credential fax = twilio.fax.faxes.create(from: TWILIO_FAX_FROM, to: TWILIO_FAX_TO, media_url: "https://storage.googleapis.com/#{GCS_BUCKET}/#{storage_path}") fax_sid = fax.sid # 結果待ち while true do status = fax.status.downcase if status == 'delivered' chatwork.send("[info][title]FAX送信結果[/title]正常に終了しました[/info]") break elsif status == 'no-answer' chatwork.send("[info][title]FAX送信結果[/title]応答がありませんでした(puke)[/info]") break elsif status == 'busy' chatwork.send("[info][title]FAX送信結果[/title]電話中でした(puke)[/info]") break elsif status == 'failed' chatwork.send("[info][title]FAX送信結果[/title]失敗しました(puke)[/info]") break elsif status == 'canceled' chatwork.send("[info][title]FAX送信結果[/title]キャンセルされました(puke)[/info]") break end sleep 15 fax = twilio.fax.faxes(fax_sid).fetch end
送信結果の通知
送信結果は ChatWork に通知しました。以下のような簡易的なクライアントオブジェクトを作って使いました。
require 'faraday' CHATWORK_ROOM_ID = 'xxxxx' CHATWORK_TOKEN = 'xxxxx' class ChatworkClient def initialize(credential) @credential = credential @conn = Faraday::Connection.new(url: 'https://api.chatwork.com') do |builder| builder.use Faraday::Request::UrlEncoded builder.use Faraday::Response::Logger builder.use Faraday::Adapter::NetHttp end end def send(message) @conn.post do |request| request.url "/v2/rooms/#{CHATWORK_ROOM_ID}/messages" request.headers = { 'X-ChatWorkToken' => CHATWORK_TOKEN } request.body = { :body => message } end end end
完成
これらの要素を元に組み上げたスクリプトを用意して、毎朝 8:45 に FAX を送信することができるようになりました。
最後に
残念ながら、届くお弁当が冷たい上にレンジできない容器に入っているというところで利用率が下がってしまい、この仕組みはもう運用が終了してしまいました。
しかし Programmable FAX は簡単に FAX ができて面白く、安いので、今後また何かに使えれば良いなと思っています。