Created
December 3, 2013 15:10
-
-
Save choplin/7770799 to your computer and use it in GitHub Desktop.
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 | |
| 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 | |
| def self.example | |
| <<-TXT | |
| { | |
| "number" : "your twilio number", | |
| "message" : "please check a chat message", | |
| "account_sid" : "your account sid", | |
| "auth_token" : "your auth token", | |
| "targets" : { | |
| "foo" : "+818011112222", | |
| "bar" : "+819022223333" | |
| } | |
| } | |
| TXT | |
| 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('-f', '--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} | |
| opt.on('--config-example[=PATH]', "store example config file. if PATH is not specified, an example file will be stored at #{DEFAULT_CONFIG_PATH}") do |v| | |
| path = File.expand_path(v || DEFAULT_CONFIG_PATH) | |
| if File.exist?(path) | |
| $stderr.puts "already exists. #{path}" | |
| exit 1 | |
| end | |
| open(path, 'w') do |f| | |
| f.write Conf.example | |
| end | |
| puts "an example config file has been stored at #{path}" | |
| exit 0 | |
| end | |
| 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 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 | |
| def twiml(message) | |
| Twilio::TwiML::Response.new do |r| | |
| r.Say message | |
| end | |
| end | |
| end | |
| module Logging | |
| def self.logger | |
| @logger ||= create_logger | |
| end | |
| private | |
| def self.create_logger | |
| logger = Logger.new(STDOUT) | |
| logger.level = Logger::DEBUG | |
| 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