-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.rb
More file actions
228 lines (185 loc) · 7.18 KB
/
server.rb
File metadata and controls
228 lines (185 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
require "rubygems"
require "twilio-ruby"
require "yaml"
require "sinatra"
$config = YAML::load(File.read("./config.yml"))
$validator = Twilio::Util::RequestValidator.new($config["twilio"]["token"])
$client = Twilio::REST::Client.new($config["twilio"]["sid"], $config["twilio"]["token"])
# General door handling
# Need to move this to a thread or unlink it from the initial Twilio response.
# Maybe use <Redirect>
def notify_arrival(data)
return if data["mode"] == "none"
$client.account.sms.messages.create(:from => $config["send_from"], :to => data["phone"], :body => data["message"])
end
def verify_call
#unless $validator.validate(request.url, params, request.env["HTTP_X_TWILIO_SIGNATURE"])
# return (Twilio::TwiML::Response.new {|r| r.Say("We're sorry, your call could not be verified.")}).text
#end
unless params[:From] == $config["require_from"]
return (Twilio::TwiML::Response.new {|r| r.Say("We're sorry, your call could not be verified.") }).text
end
nil
end
post "/" do
if ( data = verify_call )
return data
end
# Open the door since we're still automatically opening
if $config["auto_open"]["ends_at"] and $config["auto_open"]["ends_at"] > Time.now.utc.to_i
twiml = Twilio::TwiML::Response.new do |r|
# DTMF tones can't be arbitrarily played back in Twilio.
# http://www.dialabc.com/sound/generate/ will generate the DTMF sound easily
r.Play($config["open_file"])
r.Say("Welcome")
end
$config["auto_open"]["codes"].each do |code|
notify_arrival($config["codes"][code])
end
# Require a passcode
else
code_length = $config["codes"].keys.first.to_s.length
twiml = Twilio::TwiML::Response.new do |r|
r.Gather(:numDigits => code_length, :timeout => $config["timeout"], :finishOnKey => "#", :action => "/code") do |g|
g.Say("Please enter the #{code_length} digit code.")
end
end
end
return twiml.text
end
post "/code" do
if ( data = verify_call )
return data
end
if ( data = $config["codes"][params[:Digits].to_i] )
notify_arrival(data)
twiml = Twilio::TwiML::Response.new do |r|
r.Play($config["open_file"])
r.Say("Welcome")
end
else
twiml = Twilio::TwiML::Response.new {|r| r.say("Sorry, that code is incorrect.")}
end
twiml.text
end
# Admin
def flush_config
file = File.open("./config.yml", "w+")
file.write(YAML.dump($config))
file.close
end
def parse_number(number)
number = number.gsub(/[^0-9]/, "")
case number.length
when 10 then "+1#{number}"
when 11 then "+#{number}"
else nil
end
end
def format_phone(number)
number.gsub(/\+1([0-9]{3})([0-9]{3})([0-9]{4})/, "(\\1) \\2-\\3")
end
def verify_admin
#unless $validator.validate(request.url, params, request.env["HTTP_X_TWILIO_SIGNATURE"])
# return (Twilio::TwiML::Response.new {|r| r.SMS("Sorry, your request is invalid.")}).text
#end
nil
end
post "/sms" do
if ( data = verify_admin )
return data
end
twiml = nil
cmd, args = params[:Body].split(" ", 2)
cmd = cmd.downcase
args = args.trim if args
# Basic auth/deauth
if cmd == "authorize"
if $config["admin"]["password"] == args
$config["admin"]["authorized"].push(params[:From])
flush_config
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Authorized!")}
else
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Access Denied.")}
end
return twiml.text
elsif cmd == "unauthorize"
if $config["admin"]["password"] == args
$config["admin"]["authorized"].delete(params[:From])
flush_config
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Unauthorized this phone.")}
else
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Access Denied.")}
end
return twiml.text
end
# Anything after we need to actually be authorized to this
unless $config["admin"]["authorized"].include?(params[:From])
return (Twilio::TwiML::Response.new {|r| r.SMS("Access Denied.")}).text
end
# Automatically unlock anytime someone asks it to for a set time limit
if cmd == "unlock"
amount, unit, codes = args.split(" ", 3)
amount = amount.to_i
$config["auto_open"]["ends_at"] = Time.now.utc
$config["auto_open"]["codes"] = []
if unit =~ /^seconds{0,}$/i
$config["auto_open"]["ends_at"] += amount
elsif unit =~ /^minutes{0,}$/i
$config["auto_open"]["ends_at"] += amount * 60
elsif unit =~ /^hours{0,}$/i
$config["auto_open"]["ends_at"] += amount * 3600
elsif unit =~ /^days{0,}$/i
$config["auto_open"]["ends_at"] += amount * 86400
end
# Don't notify anyone on auto open
if codes.nil?
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Door will auto unlock for the next #{amount} #{unit}.")}
# Notify using the given codes
else
$config["auto_open"]["codes"] = codes.split(" ").map {|code| code.to_i}
phones = ($config["auto_open"]["codes"].map {|data| format_phone(data["phone"])}).uniq
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Door will auto unlock for the next #{amount} #{unit}, and notify #{phones.join(", ")}.")}
end
# Stop auto unlocking early
elsif cmd == "lock"
$config["auto_open"]["ends_at"] = nil
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Door will no longer auto open.")}
# Add a new code
elsif cmd == "add-code"
crt_length = $config["codes"].keys.first.to_s.length
code, mode, phone, message = args.split(" ", 4)
phone = params[:From] if phone == "me"
code, phone = code.to_i, parse_phone(phone)
if $config["codes"][code]
twiml = Twilio::TwiML::Response.new {|r| r.SMS("The code #{code} is already in use.")}
elsif code.to_s.length != crt_length
twiml = Twilio::TwiML::Response.new {|r| r.SMS("All codes must be exactly #{crt_length} digits long.")}
elsif mode != "text" and mode != "none"
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Invalid mode, must be either \"text\" or \"none\".")}
elsif phone.nil? or phone == ""
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Invalid phone, must be either 10 or 11 digits.")}
elsif mode != "none" and ( message.nil? or message == "" )
twiml = Twilio::TwiML::Response.new {|r| r.SMS("No message given when the unlock code is used.")}
# Added!
else
$config["codes"][code] = {"mode" => mode, "phone" => phone, "message" => message}
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Added new code #{code}, will #{mode == "none" && "not notify" || "#{mode} #{format_number(phone)}"} when someone arrives.")}
end
# Remove an added code
elsif cmd == "rm-code"
code = args.to_i
$config["codes"].delete(code)
twiml = Twilio::TwiML::Response.new {|r| r.SMS("Removed code #{code}.")}
# Help!
elsif cmd == "help"
twiml = Twilio::TwiML::Response.new do |r|
r.SMS("authorize <password>: Authorizes a new phone to use admin. unauthorize: Unauthorizes the phone.")
r.SMS("unlock <amount> <minutes/hours/days>: How long to auto unlock the door. lock: Ends an auto unlock early.")
r.SMS("add-code <code> <text/none> <phone> <message>: Adds a new passcode, as well as an optional notifier when the code is used. rm-code: Removes an added code.")
end
return twiml.text
end
flush_config
twiml.text
end