Twilio APIを使って、ユーザー同士で匿名電話(Programmable Voice)

ビジネス要件

CtoCサービスにおいて、ビジネスモデルをマッチングの手数料としている仕組みを開発しました。
この場合、ユーザー間での「直接取引」を禁止するためのシステム実装を画策する必要が出てきます。

例えば、

・チャット機能で、文章を検閲し、直接取引を匂わすワードが含まれていた場合にリジェクトする
・電話番号やメアドをお互いに開示させない
・「直接取引」の提案が行われた場合、通報するシステム

など、さまざまな対策が必要です。
一方、LINEやメッセンジャーのアカウントの交換を禁止はしているにもかかわらず、大抵のアプリの独自のチャット機能は、大手のチャットツールの機能に大きく劣ることがあり、ユーザーからは「使いにくい」という不満も上がるでしょう。
さらにはCtoCで当日待ち合わせする際に、「チャットだと電話より遅い、不確か、ばっくれられないか心配」というユーザー心理も働きます。
LINEやメッセンジャーさせ交換しておけばすべて解決なのに、、、

こうした背景から、お互い匿名で(正確には特定の番号を経由して)、お互いの携帯電話番号を知らせることなく、電話できる機能を、TwilioAPIを使って実現しました。

実装解説

重要箇所を#NOTEで記載していきます。
まずは、Twilioのクライアント側のインタフェースを生成します。
TwilioのAPIのトークンなどの情報は、別サイトで解説があるため、割愛しています。
コードは最後にまとめます。

require 'twilio-ruby'
require "securerandom"

class TwilioTransfer
  def initialize(current_user = nil, from_phone_number = nil, to_phone_number = nil)
    @current_user = current_user
    # NOTE https://github.com/twilio/twilio-ruby こちらのgemを追加しましょう。
    # 以下の1行で、Twilioのクライアント側のインタフェースを生成します。
    @client = Twilio::REST::Client.new(ENV['TWILIO_SID'], ENV['TWILIO_AUTH_TOKEN'])
    @participants = Array.new
    # NOTE phonyはE164な国際電話番号を扱うためのgemです。Railsで使うならphony_railsというラッパーがあります。
    # ex) 「03-1234-5678」をnormalizeすると「+81312345678」となります。
    @phone_numbers = [
      PhonyRails.normalize_number(from_phone_number, country_code: 'JP'),
      PhonyRails.normalize_number(to_phone_number, country_code: 'JP')
    ]
  end
end

各メソッドについてはシンプルなので説明を割愛しますが、以下の流れが全てです。

cleanup Twilioのsessionをまず削除しています。無駄なsessionを保持しないように注意します。
create_session Twilioのsessionを作成します。
save_current_session Twilioのsessionを保存します。
set_participants Twilioのsessionと電話番号で参加者を作成します。


require 'twilio-ruby'
require "securerandom"
class TwilioTransfer
...省略
  def connect
    cleanup
    create_session
    save_current_session
    set_participants
    Rails.logger.info "Twilio: Now conneting with ... #{@participants}"
  end
...省略
end

手順

今回は、https://www.twilio.com/console/voice/dashboardのProgrammable Voiceを利用しました。

(1) 電話番号の購入


(2) 通話中

受話者に対して、Twilioで購入済みの電話番号から電話がかかってくれば、実装成功です。
スクリーンショットをご用意できなかったのですが、通話中は、利用中のsessionがTwilio管理画面で表示され、通話内容の録音データも得ることができます。

最終コード

Rubyのコードになりますが、最終的には次のようなClassを作成しました。
人によりですが、私は、/lib/twilio_transter.rbファイルを作成し、それを必要な箇所でcallするように実装していました。

require 'twilio-ruby'
require "securerandom"
class TwilioTransfer
  def initialize(current_user = nil, from_phone_number = nil, to_phone_number = nil)
    @current_user = current_user
    @client = Twilio::REST::Client.new(ENV['TWILIO_SID'], ENV['TWILIO_AUTH_TOKEN'])
    @participants = Array.new
    @phone_numbers = [
      PhonyRails.normalize_number(from_phone_number, country_code: 'JP'),
      PhonyRails.normalize_number(to_phone_number, country_code: 'JP')
    ]
  end

  def connect
    cleanup
    create_session
    save_current_session
    set_participants
    Rails.logger.info "Twilio: Now conneting with ... #{@participants}"
  end

  private

  def cleanup
    @current_user.twilio_sessions&.each do |twilio_session|
      begin
        delete_session(twilio_session.sid)
      rescue
        next
      end
    end
  end

  def delete_session(sid)
    @client.proxy
      .services(ENV['TWILIO_SERVICE_SID'])
      .sessions(sid)
      .delete

    twilio_session = TwilioSession.find_by(sid: sid)
    twilio_session.destroy!
  end

  def create_session
    @session = @client.proxy
      .services(ENV['TWILIO_SERVICE_SID'])
      .sessions
      .create(
        unique_name: "Session-#{Time.now}-#{SecureRandom.hex(8)}",
        mode: 'voice-only',
        ttl: Settings.twilio.ttl
      )
  end

  def save_current_session
    twilio_session = @current_user.twilio_sessions.build(sid: @session.sid)
    twilio_session.save!
  end

  def set_participants
    @phone_numbers.each do |phone_number|
      create_participant(phone_number)
    end
  end

  def create_participant(phone_number)
    @participants << @client.proxy
      .services(ENV['TWILIO_SERVICE_SID'])
      .sessions(@session.sid)
      .participants
      .create(
        identifier: phone_number,
        friendly_name: "Participant-#{phone_number}"
      )
    end
  end

twilio_transfer_controller.rbファイルを作成し、Twilioにconnectしています。

class TwilioTransferController < ApiApplicationController
  def connect
    twilio_transfer = TwilioTransfer.new(current_user, current_user.phone_number, @receive_user.phone_number)
    twilio_transfer.connect
    render_success

  rescue => e
    logger.info "== twilio error logger start ==\n#{e.message}\n== twilio error logger end =="
    render_error
  end

  private

  def render_success
    render json: {
      status: 'success',
      data: {
        tel: Settings.twilio.tel
      }
    }, status: 200
  end

  def render_error
    render json: {
      status: 'error',
      message: "エラーが発生し、現在電話ができないため、メッセージでやりとりお願い致します"
    }, status: :unprocessable_entity
  end
end

あとは適当なViewファイルからCallするなどすればOKです。
Settings.twilio.telには、Twilioで購入した電話番号が入ります。
1電話番号で同時接続できるconnectionの数に制限があるため、多数の利用が見込まれる場合、複数の電話番号を購入することが必要だと思います。

  = link_to connect_twilio_transfer_index_path, remote: true, data: { tel: Settings.twilio.tel } , class: 'tel-button' do
    i.fa.fa-phone 電話する

まとめ

今回は、Twilio APIを使って、ユーザー同士で匿名電話(Programmable Voice)を架けられる仕組みをご紹介しました。
今回利用した、Programmable Voiceでは、音声合成機能とオーディオ再生機能、音声通話の録音と保存、電話会議、音声文字化、大量の受信通話を管理できるなど、様々な使い方が可能です。
Programmable Voice
Twilio APIはこれ以外にも様々なAPIがあるため、是非試してみてください。

ちなみにこの機能は、結構な数のユーザーに利用されていました。
やはり連絡先交換を禁じられているCtoCサービスでは、当日会う時に直接電話で話せることに一定のニーズがあり、匿名電話機能でビジネス要件を満たせることがわかりました。