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サービスでは、当日会う時に直接電話で話せることに一定のニーズがあり、匿名電話機能でビジネス要件を満たせることがわかりました。