らんぼーのエンジニア日記

不定期にサクッと更新していきます

Docker環境でLINE botとYoutubeAPI使って動画検索サービスを作ってみた。

対象者

  • Dockerでアプリケーションを作りたい人
  • 外部APIを使って簡単に何かアプリケーションを作りたい人
  • LINE BOT or YoutubeAPIに興味がある人

完成イメージ

Docker

まずはRailsの環境構築をDockerで行います。

docker docsに記載されている、Railsの環境構築マニュアルを元に作成いたします。

何箇所か自分で変更した部分もあり、コメントを残しています。

Dockerfile

FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
# 文字化けしないように
ENV LANG=C.UTF-8
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

Dockerfileのコマンドを簡単に説明すると、

  • FROMはDockerfileで使用するイメージを指定します。
  • RUNは実行してほしい命令になります。
  • WORKDIRはディレクトリの指定
  • ENVは環境変数の設定
  • COPYは左辺にあるを右辺にコピー。(COPY Gemfile /myapp/Gemfileだったら、ローカルのGemfileをコンテナ上のmyapp/Gemfileにコピー)

他にもコマンドが気になる方は、下記記事が参考になります。

Dockerfileのコマンド - Qiita

Gemfile

source 'https://rubygems.org'
gem 'rails', '~>5'

GemfileをDockerfileと同じディレクトリに作成してください。

また、touch Gemfile.lockで一緒にGemfile.lockを作ってください(中身は空で大丈夫です)

次にdocker-compose.yamlを作ってください。ファイルの中身は以下のようになります。

docker-compose.yaml

version: '3'
# buildし直すのがめんどいので、bundleをvolumeで繋ぐ
volumes:
  bundle:
services:
  db:
    image: postgres # postgresのイメージを使ってDBを作成
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  app:
    build: . # 同じディレクトリに存在するDockerfileのイメージを使う
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

次に、以下のコマンドでRailsの新規プロジェクトを作成してください!

docker-compose run app rails new . --force --no-deps --database=postgresql

rails newをしたことでGemfileが変わったので、imageを作り直します!

先程、作成したGemfileとは中身が変わっているはずです。

docker-compose build

Railslocalhostで動いていますが、DBのコンテナを使用しているのでconfig/database.ymlを以下のように修正します。

default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test

変更したら、下記コマンドを実行してください。

docker-compose up
docker-compose run app rake db:create

これで、localhost:3000にアクセスしてみてください!

ちなみにopen http://localhost:3000とターミナル上で打てば、localhost:3000にアクセスしてくれます。

f:id:ramboo:20191222151604p:plain

LINE BOTYoutube API

まず、Gemfileにdotenv-railsline-bot-apiを追加します。

Gemfile

 # frozen_string_literal: true

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.7'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.4'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 3.11'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'mini_racer', platforms: :ruby

gem 'rubocop', '~> 0.77.0', require: false

# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: %i[mri mingw x64_mingw]
  gem 'pry-byebug'
  gem 'pry-doc'
  gem 'pry-rails'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'web-console', '>= 3.3.0'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of chromedriver to run system tests with Chrome
  gem 'chromedriver-helper'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
#----------------------追加---------------------------------
gem 'dotenv-rails', require: 'dotenv/rails-now' 
gem 'google-api-client', '~> 0.34'
gem 'line-bot-api'
#----------------------追加---------------------------------

この時、必ずdotenv-railsline-bot-apiより上になるようにGemfileに書いてください。 dotenv-rails環境変数をセットするGemより前に置かないと(line-bot-apigoogle-api-client)、環境変数を上手くセットできないからです。

routes.rbを作ってください

 Rails.application.routes.draw do
  post '/callback' => 'linebot#callback'
end

次にlinebot用のcontrollerを作ってください。

ザックリした流れとしては、line-botでテキストを受け取って、そのテキストでYoutubeAPIを叩き(そのテキストでYoutube内を検索し)、該当するYoutubeの動画をレスポンスで返すと言うのが流れです。

linebot_controller.rb

 # frozen_string_literal: true

class LinebotsController < ApplicationController
  # linebotを使うためにrequire
  require 'line/bot'
  # youtube apiを使うためにrequire
  require 'google/apis/youtube_v3'
  require 'active_support/all'

  # callbackアクションのCSRFトークン認証を無効
  protect_from_forgery except: [:callback]

  def callback
    body = request.body.read
    signature = request.env['HTTP_X_LINE_SIGNATURE']

    unless client.validate_signature(body, signature)
      halt 400, { 'Content-Type' => 'text/plain' }, 'Bad Request'
    end

    events = client.parse_events_from(body)
    events.each do |event|
      # 定数は下記参照
      # https://github.com/line/line-bot-sdk-ruby/blob/8963a4c277259b225a766269e9e53040e414b90f/lib/line/bot/event/message.rb#L18
      case event
      when Line::Bot::Event::Message
        case event.type
        # テキストが送られてきたケース
        when Line::Bot::Event::MessageType::Text
        # event.message['text']にユーザーが入力したテキストが入っている
          item_ids = find_videos(event.message['text'])
          start_word = {
            type: 'text',
            text: message_first_text(item_ids, event)
          }
          message = item_ids.map do |id|
            {
              type: 'text',
              # 動画のidを元にYoutubeの動画のリンクを作る
              text: "https://www.youtube.com/embed/#{id.video_id}"
            }
          end

          # 破壊的変更
          message.unshift(start_word)

        # 画像が送られてきたケース
        when Line::Bot::Event::MessageType::Image
          message = {
            type: 'text',
            text: '画像は送れません'
          }
        # スタンプが送られてきたケース
        when Line::Bot::Event::MessageType::Sticker
          message = {
            type: 'text',
            text: 'スタンプは対応していません'
          }
        # その他のケース
        else
          message = {
            type: 'text',
            text: 'テキストを入力してください'
          }
        end
        client.reply_message(event['replyToken'], message)
      end
    end

    'OK'
  end

  private

  # 送られてきたテキストがkeyword
  def find_videos(keyword)
    service = Google::Apis::YoutubeV3::YouTubeService.new
    service.key = ENV['API_KEY'] # 環境変数をセット
    opt = {
      q: keyword,
      type: 'video',
      max_results: 4 # 表示する動画の最大数
    }
    
    # そのkeywordでYoutubeの動画を検索
    results = service.list_searches(:snippet, opt)

    # 検索にヒットした動画のidを配列で返却
    results.items.map(&:id)
  end

  # ヒットしたものがあるかないかでメッセージを変更する
  def message_first_text(item_ids, event)
    if item_ids.blank? # 動画が見つからなかったケース
      "#{event.message['text']}」という検索ワードにヒットした動画は見つかりませんでした"
    else # 動画が見つかったケース
      "#{event.message['text']}」という検索ワードにヒットした動画が#{item_ids.count}件ありました!"
    end
  end

  # 呼ばれる度にインスタンスを生成しないようにメモ化
  def client
    @client ||= Line::Bot::Client.new do |config|
      config.channel_secret = ENV['LINE_CHANNEL_SECRET'] # 環境変数をセット
      config.channel_token = ENV['LINE_CHANNEL_TOKEN'] # 環境変数をセット
    end
  end
end

linebotのアクセスkeyの発行方法

【Rails】1時間ぐらいで簡単にLINEのBot開発をしよう-アンケート集計Bot基礎-【画像付き】 - Qiita

記事が凄く参考になったので、この記事に従ってLINE Developerにアクセスしてアカウントを作ってください。

記事どうり、Webhook送信: 利用するに変更です。

YoutubeAPIのアクセスkeyの発行方法

アクセスkeyは下記の記事どうりにやれば問題ないかなと思います!

qiita.com

harokuにデプロイ

heroku config:set LINE_CHANNEL_SECRET=ここに先程メモしたChannel Secretを貼り付ける
heroku config:set LINE_CHANNEL_TOKEN=ここに先程メモしたアクセストークンを貼り付ける
heroku config:set API_KEY=ここに先程はメモしたAPI_KEY(YoutubeAPI)を貼り付ける

そしてデプロイです。

デバッグ

LINEBOTは一回一回本番へデプロイしないと動作確認ができないです。

でも、それだと大変めんどくさいですよね?

ですので、Ngrokと言うオープンソースを使うといいです!

qiita.com

Ngrokの公式に行っていただいて、無料の会員登録をしてインストールして下さい。

./ngrok http -host-header="0.0.0.0:3000" 3000

でlocalhost3000にアクセスできるようにngrokを起動して、

あとは、上記のQiita記事のようにWebhookのURLを変更してあげれば大丈夫です!