webエンジニアの日常

RubyやPython, JSなど、IT関連の記事を書いています

Lineにアップロードした画像をクラウドへ保存するBot作った

こんにちは、エンジニアのさもです。

タイトルにもありますが、Line(グループ)に投稿した画像をクラウド(S3)で保存し、webページで一覧を見れるようにするBot(とwebサービス)を作りました。

普段Lineで画像のやり取りをしているという方は試してみてはどうでしょうか?

スポンサーリンク

目次

背景

普段、息子の写真を僕の親や妻などと共有するのにLineで送りあったりしています。

ですが、Lineだと保存期間があったり、ちょっと前の画像みるのに上のほうへさかのぼっていたので、もう少し見やすくならないかなと、今回のBot&サービスを作ってみました。

やりたかったこと

実現したかった構成はこんな感じです。

f:id:s-uotani-zetakansu:20171226171712p:plain

インターフェース(これまでのやり方)を変えたくなかったので、いつもどおりLineにアップロードしたら自動的にS3へ保存してくれるようにしました。

画像の登録に成功すると、メッセージを返してくれます

f:id:s-uotani-zetakansu:20171226173916p:plain

画像の閲覧は専用のwebサイトで登録した月別でみれるようにしています(未実装)

Line側の準備

STEP1. Line developersへユーザー登録します。

LINE Developers

STEP2. 登録後、ログインしたら、プロバイダーを作成します。

f:id:s-uotani-zetakansu:20171226174405p:plain

プロバイダー名は何でもいいです。

STEP3. Botを作成します

作成したプロバイダを選択し、「Messaging API」から作成画面に進みます

f:id:s-uotani-zetakansu:20171226174758p:plain

分類などは適当でいいです。

STEP4. キーの取得と設定

作成後、APIを使うために、「Channel Secret」と「アクセストークン」を記録しておきます。

もしまだ表示されていない場合は、再発行してください。

Webhook送信 は有効にしておきます。

Botのグループトーク参加は利用するにしておきます。

Webhook URL はRails側の実装が終わってからで良いと思います。

自動応答や友達登録時メッセージは無効にしておくのが良いと思います。

S3の準備

やることは、IAM設定とバケットの作成のみですが、

ここは分かりやすいページがあったので丸投げしてしまいます。

qiita.com

Railsの準備

Railsでは、Lineからのpostの受け取りと、S3へのアップロードを行います。(実際には画像の一覧があるのですが、割愛します)

gem

以下のgemをgemfileに書いてbundleしてください

  • line-bot-api・・・LineBotを使うときに必要
  • carrierwave・・・画像アップロード
  • fog・・・・・・S3を使うときに必要

model

今回はPhotoモデルを作って行きます(あとから気づいたのですが、動画にも対応するので、モデル名は適切でないですが、このまま行きます)

カラムは、アップローダをマウントするimageカラムと画像か動画かを覚えておくcontent_typeカラムぐらいがあればいいと思います。

あと、誰が投稿したか分かるように、uploader_id, いつの写真か分かるようにphoto_dateを付け加えました。

今回は、年月毎にディレクトリを分けたいので、Photoモデルにディレクトリを返すメソッドを登録しておきます

class Photo < ActiveRecord::Base
  
  mount_uploader :image, ImageUploader
  
  belongs_to :uploader
    
  def upload_dir
    self.photo_date.strftime("%Y%m")
  end
    
end

投稿したユーザを表すUploaderモデルも作っておきます。

カラムは、line_id, token(webサイトへログイン用), nameです。tokenはwebサイトを見るために使っているので、無くてもいいです

uploader

rails g uploader Image でアップローダーを作成します

上を実行すると、新たにuploadersディレクトリが作られ、image_uploader.rbというファイルが作られます。

S3の設定のため、以下のように修正してください

class ImageUploader < CarrierWave::Uploader::Base

  if Rails.env.development? || Rails.env.test?
    storage :file
  else
    storage :fog
  end

  def store_dir
    "uploads/#{model.upload_dir}"
  end

  # ファイル名をランダム文字列にする
  def filename
    "#{secure_token}.#{file.extension}" if original_filename.present?
  end

  protected

  def secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
  end
end

ルーティング

コントローラ名は何でもいいですが、URLは/callbackとなるようにしておいてください

post '/callback' => 'webhook#callback'

controller

基本的には、ラインクライアントをインスタンス化し、

画像か動画であればPhotoモデルに登録、

登録が成功すれば、メッセージを返す。

という流れです。

途中で投稿者の登録が挟まっています。

require 'line/bot'

class WebhookController < ApplicationController
  before_action :set_line_client

  CHANNEL_SECRET = 'aaaaa' # 記録しておいたChannel Secret 本当はENVでやるのが望ましい
  CHANNEL_ACCESS_TOKEN = 'aaaaa' # 記録しておいたアクセストークン 本当はENVでやるのが望ましい

  def callback
    body = request.body.read
    
    unless @client.validate_signature(body, request.env['HTTP_X_LINE_SIGNATURE'])
      error 400 do 'Bad Request' end
    end

    event = @client.parse_events_from(body)[0]
    message = { text: nil }

    case event.type
    # 画像か動画なら登録する
    when "image", "video"
      temp = Tempfile.new("example").binmode.tap do |file|
        file.write @client.get_message_content(event.message['id']).body
      end
      if Photo.create(image: temp, uploader_id: find_or_create_uploader.id, photo_date: Time.zone.today, content_type: event.type)
        type_str = event.type == "image" ? "画像" : "動画"
        message = { type: 'text', text: "#{type_str}を登録しました。" }
      end
    end
  
    # Botを介してLineへメッセージ送信
    if message[:text].present?
      @client.reply_message(event['replyToken'], message)
    end

    # webからは何も返さない
    render :nothing => true, status: :ok
  end

  private

  # メッセージを取扱いやすくしてくれるclientをセット
  def set_line_client
    @client ||= Line::Bot::Client.new { |config|
      config.channel_secret = CHANNEL_SECRET
      config.channel_token = CHANNEL_ACCESS_TOKEN
    }
  end
  
  # Uploaderを見つけるor登録する
  def find_or_create_uploader
    line_id = params[:events][0][:source][:userId]
    uploader = Uploader.find_by(line_id: line_id)
    unless uploader
      uploader = Uploader.create(line_id: line_id, token: SecureRandom.uuid, name: line_name(line_id))
    end
    uploader
  end
  
  # プロフィールからLineの名前を取ってくる
  def line_name(line_id)
    response = @client.get_profile(line_id)
    case response
    when Net::HTTPSuccess then
      return JSON.parse(response.body)['displayName']
    end
  end

end

Railsはこんな感じです。

Herokuへデプロイ

ちょっと端折りますが、HerokuでRailsのプロジェクトを作り、メニューの「deploy」を参考にしながらデプロイします。

僕は開発環境にcloud9を使っているのですが、clod9からHerokuへデプロイする方法もそのうち記事に書きたいと思います。

引っかかるところはあまり無いと思いますが、Rubyのbuild packを追加するのを忘れないでください。

Line側の準備2

Herokuへデプロイしたら、メニューの「setting」からURLを確認し、

Botの設定のWebhook URL へpostするパスを入力します。上の例では、

https://なんとかかんとか.herokuapp.com/callback

です。

あとは利用したいグループへ招待するだけです!

まとめ

トークの中に埋もれていた画像が一覧でみやすくなりました!

Lineでも写真を一覧表示することは出来るのですが、保存期間が過ぎても保存しておきたい、月別で表示・ダウンロードしたいと思っていたのと、自分で作ったものだと自由にカスタマイズ出来るので今回のBot+サービスを作ってみました。

以上、エンジニアの皆様もお試しください!

読者登録はこちらからお願いします。