Skip to content

Instantly share code, notes, and snippets.

@choplin
Last active December 26, 2015 16:59
Show Gist options
  • Select an option

  • Save choplin/7183879 to your computer and use it in GitHub Desktop.

Select an option

Save choplin/7183879 to your computer and use it in GitHub Desktop.
cli tool for calling using twilio
#!/usr/bin/env ruby
require 'twilio-ruby'
require 'json'
require 'optparse'
require 'uri'
require 'logger'
module TwilioCall
class Conf
#{
# "number" : "your twilio number",
# "message" : "arbitrary message",
# "account_sid" : "your account sid",
# "auth_token" : "your auth token",
# "targets" : {
# "homura" : "+890000000000",
# "madoka" : "+890111111111"
# }
#}
attr_reader :number, :account_sid, :auth_token, :numbers, :message
def initialize(path)
conf = JSON.parse(IO.read(File.expand_path(path)))
%w(number account_sid auth_token targets message).each do |f|
raise %Q{invalid format of .twilio.conf. "#{f}" is required.} if conf[f].nil?
end
@number = conf['number']
@account_sid = conf['account_sid']
@auth_token = conf['auth_token']
@targets = conf['targets']
@message = conf['message']
end
def number_for_name(name)
number = @targets[name]
if number.nil?
$stderr.puts "cannot find a number for #{name}"
exit 1
end
number
end
end
class Args
attr_reader :name, :retry_num, :force_retry, :config_path
DEFAULT_RETRY_NUM = 3
DEFAULT_CONFIG_PATH = '~/.twilio.conf'
def initialize
@retry_num = DEFAULT_RETRY_NUM
@config_path = DEFAULT_CONFIG_PATH
end
def self.parse(argv)
ret = self.new
ret.instance_eval do |i|
script_name = File.basename($0)
banner = "Usage: #{script_name} NAME [options]"
opt = OptionParser.new(banner)
opt.on('-r NUM', 'calling retry number. default: 3'){|v| @retry_num = v}
opt.on('--force-retry', 'retry even if a call ends in success'){|v| @force_retry = true}
opt.on('-c PATH', 'config file path. default: ~/.twiliio.conf'){|v| @config_path = v}
remains = opt.parse(argv)
if remains.length != 1
$stderr.puts 'invalid number of argments'
exit 1
end
@name = remains[0]
end
ret
end
end
class Caller
SLEEP_TIME = 5
def initialize(opts={})
@client = Twilio::REST::Client.new opts[:account_sid], opts[:auth_token]
@number = opts[:number]
@to = opts[:to]
@message = opts[:message]
@retry_num = opts[:retry_num]
@logger = Logging.logger
@force_retry = opts[:force_retry]
end
def call
try(0)
end
private
def twiml(message)
response = Twilio::TwiML::Response.new do |r|
r.Say message, voice: 'woman', language: 'ja-jp'
end
end
def try(count)
if count == @retry_num
@logger.info "max number of retires exceeds. exit"
exit @force_retry ? 0 : 1
end
@logger.info "trying to call #{@to}. retry #{count + 1}."
call = @client.account.calls.create(
from: @number,
to: @to,
url: URI.escape("http://twimlets.com/echo?Twiml=#{twiml(@message).text}"),
ifMachine: 'Hangup'
)
status = wait_status(call)
if status == :completed
@logger.info "success"
try(count + 1) if @force_retry
elsif status == :failed
@logger.info "failed. status #{status}. exit"
exit 1
else
@logger.info "failed. status #{status}. continue"
try(count + 1)
end
end
def wait_status(call)
loop do
call.refresh
status = call.status
@logger.debug "status: #{status}"
case status
# canceled queued または ringing 中に、通話がキャンセルされました。
when 'canceled'
break :canceld
# completed 相手が応答し、通話が正常に終了しました。
when 'completed'
break :completed
# busy 相手からビジー信号を受信しました。
when 'busy'
break :busy
# failed 通話を接続できませんでした。通常は、ダイヤルした電話番号が存在しません。
when 'failed'
break :failed
# no-answer 相手が応答せず、通話が終了しました。
when 'no-answer'
break :no_answer
end
# queued 通話は発信待ち状態です。
# ringing 呼び出し中です。
# in-progress 相手が応答し、通話中です。
sleep SLEEP_TIME
end
end
end
module Logging
def self.logger
@logger ||= create_logger
end
private
def self.create_logger
logger = Logger.new(STDOUT)
logger
end
end
end
def main
args = TwilioCall::Args.parse(ARGV)
conf = TwilioCall::Conf.new(args.config_path)
to = conf.number_for_name(args.name)
c = TwilioCall::Caller.new(
account_sid: conf.account_sid,
auth_token: conf.auth_token,
number:conf.number,
to: to,
message: conf.message,
retry_num: args.retry_num,
force_retry: args.force_retry,
)
c.call
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment