last-resort 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +18 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +46 -0
- data/LICENSE +22 -0
- data/README.md +58 -0
- data/Rakefile +2 -0
- data/bin/last-resort +5 -0
- data/config/config.rb +35 -0
- data/last-resort.gemspec +25 -0
- data/lib/last-resort/config-lang.rb +74 -0
- data/lib/last-resort/contextio.rb +66 -0
- data/lib/last-resort/controller.rb +78 -0
- data/lib/last-resort/scheduler.rb +73 -0
- data/lib/last-resort/twilio.rb +70 -0
- data/lib/last-resort/version.rb +5 -0
- data/lib/last-resort/webhooks.rb +19 -0
- data/lib/last-resort.rb +25 -0
- metadata +145 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
last-resort (0.0.1)
|
5
|
+
activesupport (~> 3.2)
|
6
|
+
gli (~> 1.6)
|
7
|
+
oauth (~> 0.4)
|
8
|
+
sinatra (~> 1.3)
|
9
|
+
thor (~> 0.14)
|
10
|
+
twilio-ruby (~> 3.6)
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: http://rubygems.org/
|
14
|
+
specs:
|
15
|
+
activesupport (3.2.3)
|
16
|
+
i18n (~> 0.6)
|
17
|
+
multi_json (~> 1.0)
|
18
|
+
awesome_print (1.0.2)
|
19
|
+
builder (3.0.0)
|
20
|
+
gli (1.6.0)
|
21
|
+
i18n (0.6.0)
|
22
|
+
json (1.6.6)
|
23
|
+
jwt (0.1.4)
|
24
|
+
json (>= 1.2.4)
|
25
|
+
multi_json (1.3.1)
|
26
|
+
oauth (0.4.5)
|
27
|
+
rack (1.4.1)
|
28
|
+
rack-protection (1.2.0)
|
29
|
+
rack
|
30
|
+
sinatra (1.3.2)
|
31
|
+
rack (~> 1.3, >= 1.3.6)
|
32
|
+
rack-protection (~> 1.2)
|
33
|
+
tilt (~> 1.3, >= 1.3.3)
|
34
|
+
thor (0.14.6)
|
35
|
+
tilt (1.3.3)
|
36
|
+
twilio-ruby (3.6.0)
|
37
|
+
builder (>= 2.1.2)
|
38
|
+
jwt (>= 0.1.2)
|
39
|
+
multi_json (>= 1.0.3)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
awesome_print
|
46
|
+
last-resort!
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Scott Hyndman, Ian Ha, Victor Mota
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
Last Resort is a Ruby gem for monitoring critical emails sent by automated services ("Server down!",
|
2
|
+
"Resource limit matched!", etc.) and calling your phone to tell you about it. It can be deployed in a
|
3
|
+
reliable environment and perform up to 1500 emergency calls for free, using the free and trial tiers
|
4
|
+
available from [context.io](http://context.io), [twilio](http://twilio.com) and [heroku](http://heroku.com).
|
5
|
+
|
6
|
+
### Installation
|
7
|
+
|
8
|
+
```sh
|
9
|
+
$ gem install last-resort
|
10
|
+
```
|
11
|
+
|
12
|
+
### Getting started
|
13
|
+
|
14
|
+
```sh
|
15
|
+
$ last-resort new my-awesome-project
|
16
|
+
```
|
17
|
+
This will create a scheduling project with a sample `my-awesome-project/config.rb` file.
|
18
|
+
|
19
|
+
### Example config.rb file
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
configure :host => "",
|
23
|
+
:twilio_sid => "",
|
24
|
+
:twilio_auth_token => "",
|
25
|
+
:contextio_account => "",
|
26
|
+
:contextio_key => "",
|
27
|
+
:contextio_secret => ""
|
28
|
+
|
29
|
+
# DEFINE YOUR CONTACTS
|
30
|
+
|
31
|
+
contact :ian, ""
|
32
|
+
contact :scott, ""
|
33
|
+
contact :victor, ""
|
34
|
+
|
35
|
+
# DEFINE WHAT EMAILS YOU WANT TO WATCH FOR
|
36
|
+
|
37
|
+
match :subject => /server down/ # rackspace ping
|
38
|
+
match :subject => /resource limit reached/ # monit
|
39
|
+
|
40
|
+
# DEFINE WHO TO CALL AND WHEN
|
41
|
+
|
42
|
+
between 19..22, :on => [:wednesday, :thursday] do
|
43
|
+
call :victor
|
44
|
+
end
|
45
|
+
|
46
|
+
between :off_hours, :on => :weekdays do
|
47
|
+
call :scott
|
48
|
+
end
|
49
|
+
|
50
|
+
between :all_hours, :on => :weekends do
|
51
|
+
call [:ian, :scott, :victor]
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
### Credit
|
56
|
+
Victor Mota ([@vimota](http://www.twitter.com/vimota))
|
57
|
+
Scott Hyndman ([@scotthyndman](http://www.twitter.com/scotthyndman))
|
58
|
+
Ian Ha ([@ianpha](http://www.twitter.com/ianpha))
|
data/Rakefile
ADDED
data/bin/last-resort
ADDED
data/config/config.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
configure :host => "",
|
2
|
+
:twilio_sid => "",
|
3
|
+
:twilio_auth_token => "",
|
4
|
+
:contextio_account => "",
|
5
|
+
:contextio_key => "",
|
6
|
+
:contextio_secret => ""
|
7
|
+
|
8
|
+
|
9
|
+
# DEFINE YOUR CONTACTS
|
10
|
+
|
11
|
+
contact :ian, ""
|
12
|
+
contact :scott, ""
|
13
|
+
contact :victor, ""
|
14
|
+
|
15
|
+
|
16
|
+
# DEFINE WHAT EMAILS YOU WANT TO WATCH FOR
|
17
|
+
|
18
|
+
match :subject => /server down/ # rackspace ping
|
19
|
+
match :subject => /resource limit reached/ # monit
|
20
|
+
|
21
|
+
|
22
|
+
# DEFINE WHO TO CALL AND WHEN
|
23
|
+
|
24
|
+
between 19..22, :on => [:wednesday, :thursday] do
|
25
|
+
call :victor
|
26
|
+
end
|
27
|
+
|
28
|
+
between :off_hours, :on => :weekdays do
|
29
|
+
call :scott
|
30
|
+
end
|
31
|
+
|
32
|
+
between :all_hours, :on => :weekends do
|
33
|
+
call [:ian, :scott, :victor]
|
34
|
+
end
|
35
|
+
|
data/last-resort.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/last-resort/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Ian Ha", "Victor Mota", "Scott Hyndman"]
|
6
|
+
gem.email = ["ianha0@gmail.com", "vimota@gmail.com", "scotty.hyndman@gmail.com"]
|
7
|
+
gem.description = "Call your phone when critical emails arrive" # TODO Improve this
|
8
|
+
gem.summary = "Call your phone when critical emails arrive" # TODO Improve this
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "last-resort"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Last::Resort::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency "sinatra", "~> 1.3"
|
19
|
+
gem.add_dependency "twilio-ruby", "~> 3.6"
|
20
|
+
gem.add_dependency "activesupport", "~> 3.2"
|
21
|
+
gem.add_dependency "thor", "~> 0.14"
|
22
|
+
gem.add_dependency "oauth", "~> 0.4"
|
23
|
+
gem.add_dependency "gli", "~> 1.6"
|
24
|
+
gem.add_development_dependency "awesome_print"
|
25
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module LastResort
|
2
|
+
class Config
|
3
|
+
|
4
|
+
CONFIG_PATH = "#{PROJECT_ROOT}/config/config.rb"
|
5
|
+
|
6
|
+
attr_accessor :host,
|
7
|
+
:contacts,
|
8
|
+
:matchers,
|
9
|
+
:schedules,
|
10
|
+
:twilio_sid, :twilio_auth_token,
|
11
|
+
:contextio_account, :contextio_key, :contextio_secret
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@contacts = {}
|
15
|
+
@matchers = []
|
16
|
+
@schedules = []
|
17
|
+
run_config_in_context
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def run_config_in_context
|
23
|
+
source = open(CONFIG_PATH).read
|
24
|
+
self.instance_eval source
|
25
|
+
end
|
26
|
+
|
27
|
+
def configure params
|
28
|
+
@host = params[:host]
|
29
|
+
@twilio_sid = params[:twilio_sid]
|
30
|
+
@twilio_auth_token = params[:twilio_auth_token]
|
31
|
+
@contextio_key = params[:contextio_key]
|
32
|
+
@contextio_secret = params[:contextio_secret]
|
33
|
+
@contextio_account = params[:contextio_account]
|
34
|
+
end
|
35
|
+
|
36
|
+
def contact name, phone
|
37
|
+
@contacts[name] = { :name => name, :phone => scrub_phone(phone) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def match matcher
|
41
|
+
@matchers << matcher
|
42
|
+
end
|
43
|
+
|
44
|
+
def between hours, options
|
45
|
+
hours = hours.is_a?(Array) ? hours : [hours]
|
46
|
+
|
47
|
+
days = options[:on] || :everyday
|
48
|
+
days = days.is_a?(Array) ? days : [days]
|
49
|
+
|
50
|
+
@current_schedule = {
|
51
|
+
:hours => hours,
|
52
|
+
:days => days
|
53
|
+
}
|
54
|
+
|
55
|
+
yield
|
56
|
+
|
57
|
+
@schedules << @current_schedule
|
58
|
+
@current_schedule = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def call contacts
|
62
|
+
contacts = contacts.is_a?(Array) ? contacts : [contacts]
|
63
|
+
@current_schedule[:contacts] = contacts
|
64
|
+
end
|
65
|
+
|
66
|
+
def scrub_phone phone
|
67
|
+
phone.gsub!(/\D/, '')
|
68
|
+
phone = "+1#{phone}" unless phone.start_with? "+1"
|
69
|
+
phone
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
CONFIG = LastResort::Config.new
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
require 'json'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
module ContextIO
|
6
|
+
class Connection
|
7
|
+
VERSION = "2.0"
|
8
|
+
|
9
|
+
def initialize(key='', secret='', server='https://api.context.io')
|
10
|
+
@consumer = OAuth::Consumer.new(key, secret, {:site => server, :sheme => :header})
|
11
|
+
@token = OAuth::AccessToken.new @consumer
|
12
|
+
end
|
13
|
+
|
14
|
+
def createWebhook(accountId, parameters)
|
15
|
+
post accountId, 'webhooks', parameters
|
16
|
+
end
|
17
|
+
|
18
|
+
def listWebhooks(accountId)
|
19
|
+
get accountId, 'webhooks'
|
20
|
+
end
|
21
|
+
|
22
|
+
def deleteWebhook(accountId, webhook_id)
|
23
|
+
delete accountId, "webhooks/#{webhook_id}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def deleteAllWebhooks(accountId)
|
27
|
+
webhooks = listWebhooks(accountId)
|
28
|
+
webhooks.each do |webhook|
|
29
|
+
deleteWebhook accountId, webhook["webhook_id"]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def url(accountId, url, *args)
|
36
|
+
if args.empty?
|
37
|
+
"/#{VERSION}/accounts/#{accountId}/#{url}"
|
38
|
+
else
|
39
|
+
"/#{VERSION}/accounts/#{accountId}/#{url}?#{parametrize args}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def get(accountId, url, *args)
|
44
|
+
response_body = @token.get(url(accountId, url, *args), "Accept" => "application/json").body
|
45
|
+
JSON.parse(response_body)
|
46
|
+
end
|
47
|
+
|
48
|
+
def post(accountId, url, parameters)
|
49
|
+
@token.post(url(accountId, url), parameters)
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete(accountId, url, *args)
|
53
|
+
@token.delete(url(accountId, url, *args))
|
54
|
+
end
|
55
|
+
|
56
|
+
def parametrize(options)
|
57
|
+
URI.escape(
|
58
|
+
options.collect do |k, v|
|
59
|
+
v = v.to_i if k == :since
|
60
|
+
v = v.join(',') if v.instance_of?(Array)
|
61
|
+
k = k.to_s.gsub('_', '')
|
62
|
+
"#{k}=#{v}"
|
63
|
+
end.join('&'))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# The exception session keeps state between twilio and context-io webhooks. Currently, the system can only handle
|
2
|
+
# one call session at a time, although we plan to change that in future versions.
|
3
|
+
exception_session = nil
|
4
|
+
|
5
|
+
|
6
|
+
# ====== CONTEXT-IO TWILIO ENDPOINTS
|
7
|
+
|
8
|
+
post '/matched_email' do
|
9
|
+
return if LastResort::Scheduler.new.get_matching_schedule.nil?
|
10
|
+
|
11
|
+
contact_names = LastResort::Scheduler.new.get_matching_schedule[:contacts]
|
12
|
+
contacts = []
|
13
|
+
contact_names.each do |name|
|
14
|
+
contacts.push(LastResort::Contact.new(name.to_s, CONFIG.contacts[name][:phone]))
|
15
|
+
end
|
16
|
+
|
17
|
+
hookData = JSON.parse(request_body.read)
|
18
|
+
exception_session = LastResort::ExceptionSession.new(contacts, hookData["message_data"]["subject"])
|
19
|
+
exception_session.notify
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# ====== SINATRA TWILIO ENDPOINTS
|
24
|
+
|
25
|
+
# Service check method
|
26
|
+
get '/twilio' do
|
27
|
+
"twilio callbacks up and running!"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Performs a test call based on the user's configuration
|
31
|
+
get '/twilio/test' do
|
32
|
+
exception_session.notify
|
33
|
+
end
|
34
|
+
|
35
|
+
# Method invoked to determine how the machine should interact with the user.
|
36
|
+
post '/twilio/call' do
|
37
|
+
content_type 'text/xml'
|
38
|
+
puts "call with #{params.inspect}"
|
39
|
+
|
40
|
+
if params[:CallStatus] == 'no-answer'
|
41
|
+
return Twilio::TwiML::Response.new { |r| r.Hangup }.text
|
42
|
+
end
|
43
|
+
|
44
|
+
response = Twilio::TwiML::Response.new do |r|
|
45
|
+
r.Say "Hello #{exception_session.callee_name}. The following error has occured: #{exception_session.description}", :voice => 'man'
|
46
|
+
r.Gather :numDigits => 1, :action => "http://#{HOST}/twilio/gather_digits" do |d|
|
47
|
+
d.Say "Please enter 1 to handle this bug that you probably didn't even create or 0 or hangup to go back to spending quality time with your family."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
response.text
|
51
|
+
end
|
52
|
+
|
53
|
+
# Called when a user's call ends.
|
54
|
+
post '/twilio/status_callback' do
|
55
|
+
puts "status_callback with #{params.inspect}"
|
56
|
+
exception_session.call_next
|
57
|
+
end
|
58
|
+
|
59
|
+
# Callback to determine user input.
|
60
|
+
post '/twilio/gather_digits' do
|
61
|
+
puts "gather_digits with #{params.inspect}"
|
62
|
+
|
63
|
+
content_type 'text/xml'
|
64
|
+
digit = params[:Digits].to_i
|
65
|
+
|
66
|
+
case digit
|
67
|
+
when 1 # User handles call, so don't call anyone else
|
68
|
+
puts "User entered 1"
|
69
|
+
exception_session.end
|
70
|
+
response = Twilio::TwiML::Response.new do |r|
|
71
|
+
r.Say "Thank you for handling this exception. Goodbye.", :voice => 'man'
|
72
|
+
r.Hangup
|
73
|
+
end
|
74
|
+
return response.text
|
75
|
+
else # Hangup this call and go to the next person
|
76
|
+
return Twilio::TwiML::Response.new {|r| r.Hangup}.text
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
module LastResort
|
4
|
+
class Scheduler
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@config = CONFIG
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_matching_schedule
|
11
|
+
matched_schedule = @config.schedules.find do |schedule|
|
12
|
+
match?(schedule)
|
13
|
+
end
|
14
|
+
|
15
|
+
if matched_schedule.nil?
|
16
|
+
puts "No matched schedule"
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
puts "Schedule found -- calling #{matched_schedule[:contacts]}"
|
20
|
+
matched_schedule
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
def match? schedule
|
27
|
+
match_hours?(schedule[:hours]) and match_days?(schedule[:days])
|
28
|
+
end
|
29
|
+
|
30
|
+
def match_hours? hours, time_to_match = Time.now
|
31
|
+
expanded_hours = []
|
32
|
+
hours.each do |hour|
|
33
|
+
expanded_hours += expand_if_possible(hour)
|
34
|
+
end
|
35
|
+
|
36
|
+
expanded_hours.any? do |hour|
|
37
|
+
if hour.is_a? Range
|
38
|
+
hour.include? time_to_match.hour
|
39
|
+
elsif hour.is_a? Fixnum
|
40
|
+
hour == time_to_match.hour
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def match_days? days, time_to_match = Time.now
|
46
|
+
day_of_week = time_to_match.strftime("%A").downcase.to_sym
|
47
|
+
|
48
|
+
expanded_days = []
|
49
|
+
days.each do |day|
|
50
|
+
expanded_days += expand_if_possible(day)
|
51
|
+
end
|
52
|
+
|
53
|
+
expanded_days.any? do |day|
|
54
|
+
day == day_of_week
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def expand_if_possible symbol
|
59
|
+
case symbol
|
60
|
+
when :all_hours
|
61
|
+
[0..23]
|
62
|
+
when :off_hours
|
63
|
+
[0..8, 17..23]
|
64
|
+
when :weekdays
|
65
|
+
[:monday, :tuesday, :wednesday, :thursday, :friday]
|
66
|
+
when :weekends
|
67
|
+
[:saturday, :sunday]
|
68
|
+
else
|
69
|
+
[symbol]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'twilio-ruby'
|
2
|
+
|
3
|
+
# Sandbox code
|
4
|
+
ACCOUNT_SID = ""
|
5
|
+
AUTH_TOKEN = ""
|
6
|
+
FROM_NUMBER = ""
|
7
|
+
HOST = ""
|
8
|
+
|
9
|
+
module LastResort
|
10
|
+
|
11
|
+
class Contact
|
12
|
+
attr_accessor :name, :number
|
13
|
+
|
14
|
+
def initialize(name = "", number = "")
|
15
|
+
@name = name
|
16
|
+
@number = number
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ExceptionSession
|
21
|
+
|
22
|
+
# Array of strings representing numbers
|
23
|
+
attr_accessor :contacts, :client, :call, :index, :description, :handled
|
24
|
+
|
25
|
+
def initialize(contacts = [], description = "a general exception has occurred")
|
26
|
+
@contacts = contacts
|
27
|
+
@description = description
|
28
|
+
@client = Twilio::REST::Client.new ACCOUNT_SID, AUTH_TOKEN
|
29
|
+
@index = -1
|
30
|
+
@handled = false
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return false if call was not made
|
34
|
+
def call_next
|
35
|
+
@index += 1
|
36
|
+
|
37
|
+
return false if @contacts.empty? || @index >= @contacts.size || @handled
|
38
|
+
|
39
|
+
# Make the call
|
40
|
+
@call = @client.account.calls.create(
|
41
|
+
:from => FROM_NUMBER,
|
42
|
+
:to => @contacts[@index].number,
|
43
|
+
:url => "http://#{HOST}/twilio/call",
|
44
|
+
:status_callback => "http://#{HOST}/twilio/status_callback"
|
45
|
+
)
|
46
|
+
|
47
|
+
return true
|
48
|
+
end
|
49
|
+
|
50
|
+
# Called when someone in the queue has handled this
|
51
|
+
def end
|
52
|
+
@handled = true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Begin the notification cycle
|
56
|
+
def notify
|
57
|
+
self.call_next
|
58
|
+
end
|
59
|
+
|
60
|
+
# Name of the latest callee (latest call)
|
61
|
+
def callee_name
|
62
|
+
@contacts[@index].name
|
63
|
+
end
|
64
|
+
|
65
|
+
# Number of latest callee (latest call)
|
66
|
+
def callee_number
|
67
|
+
@contacts[@index].number
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module LastResort
|
2
|
+
class WebHookCreator
|
3
|
+
def self.create_hooks
|
4
|
+
contextio = ContextIO::Connection.new(CONFIG.contextio_key, CONFIG.contextio_secret)
|
5
|
+
|
6
|
+
# Delete everything
|
7
|
+
contextio.deleteAllWebhooks CONFIG.contextio_account
|
8
|
+
|
9
|
+
# then recreate based on the configuration
|
10
|
+
CONFIG.matchers.each do |matcher|
|
11
|
+
contextio.createWebhook CONFIG.contextio_account,
|
12
|
+
:callback_url => "http://#{CONFIG.host}/matched_email",
|
13
|
+
:failure_notif_url => "http://google.ca",
|
14
|
+
:filter_subject => matcher[:subject].source,
|
15
|
+
:sync_period => "immediate"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/last-resort.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
require "sinatra"
|
4
|
+
require "awesome_print"
|
5
|
+
|
6
|
+
FILE_DIR = File.expand_path(File.dirname(__FILE__))
|
7
|
+
PROJECT_ROOT = File.expand_path("#{FILE_DIR}/..")
|
8
|
+
|
9
|
+
# BRING IN DEPENDENCIES
|
10
|
+
|
11
|
+
$LOAD_PATH << FILE_DIR
|
12
|
+
Dir["#{FILE_DIR}/last-resort/*.rb"].each do |file_path|
|
13
|
+
require file_path
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# SINATRA BASICS
|
18
|
+
|
19
|
+
set :port, 80
|
20
|
+
|
21
|
+
get "/" do
|
22
|
+
"Last Resort server running"
|
23
|
+
end
|
24
|
+
|
25
|
+
LastResort::WebHookCreator.create_hooks
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: last-resort
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ian Ha
|
9
|
+
- Victor Mota
|
10
|
+
- Scott Hyndman
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2012-04-19 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: sinatra
|
18
|
+
requirement: &70344951605780 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '1.3'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *70344951605780
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: twilio-ruby
|
29
|
+
requirement: &70344951603920 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '3.6'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *70344951603920
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: activesupport
|
40
|
+
requirement: &70344951602680 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3.2'
|
46
|
+
type: :runtime
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *70344951602680
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: thor
|
51
|
+
requirement: &70344951600840 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ~>
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0.14'
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *70344951600840
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: oauth
|
62
|
+
requirement: &70344951595260 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.4'
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: *70344951595260
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: gli
|
73
|
+
requirement: &70344951593480 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ~>
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '1.6'
|
79
|
+
type: :runtime
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: *70344951593480
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: awesome_print
|
84
|
+
requirement: &70344951592620 !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: *70344951592620
|
93
|
+
description: Call your phone when critical emails arrive
|
94
|
+
email:
|
95
|
+
- ianha0@gmail.com
|
96
|
+
- vimota@gmail.com
|
97
|
+
- scotty.hyndman@gmail.com
|
98
|
+
executables:
|
99
|
+
- last-resort
|
100
|
+
extensions: []
|
101
|
+
extra_rdoc_files: []
|
102
|
+
files:
|
103
|
+
- .gitignore
|
104
|
+
- Gemfile
|
105
|
+
- Gemfile.lock
|
106
|
+
- LICENSE
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- bin/last-resort
|
110
|
+
- config/config.rb
|
111
|
+
- last-resort.gemspec
|
112
|
+
- lib/last-resort.rb
|
113
|
+
- lib/last-resort/config-lang.rb
|
114
|
+
- lib/last-resort/contextio.rb
|
115
|
+
- lib/last-resort/controller.rb
|
116
|
+
- lib/last-resort/scheduler.rb
|
117
|
+
- lib/last-resort/twilio.rb
|
118
|
+
- lib/last-resort/version.rb
|
119
|
+
- lib/last-resort/webhooks.rb
|
120
|
+
homepage: ''
|
121
|
+
licenses: []
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 1.8.10
|
141
|
+
signing_key:
|
142
|
+
specification_version: 3
|
143
|
+
summary: Call your phone when critical emails arrive
|
144
|
+
test_files: []
|
145
|
+
has_rdoc:
|