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サービスでは、当日会う時に直接電話で話せることに一定のニーズがあり、匿名電話機能でビジネス要件を満たせることがわかりました。
国内外にチームがあり、費用感や品質、スピード、様々なオーダーに対して、
柔軟にカスタマイズしてチームを作ります。
まずはお気軽にお見積もり依頼からご連絡ください。
お見積もり依頼はこちら