Last active
December 26, 2015 16:59
-
-
Save choplin/7183879 to your computer and use it in GitHub Desktop.
cli tool for calling using twilio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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