1 |
#!/usr/bin/perl |
2 |
|
3 |
use strict; |
4 |
use warnings; |
5 |
|
6 |
use Asterisk::AGI; |
7 |
use POSIX qw (strftime mktime); |
8 |
use Date::Parse; |
9 |
use File::stat; |
10 |
|
11 |
## |
12 |
# Functional TODO: |
13 |
# - Move all of the configuration to a separate file |
14 |
# - write a get_digits() like get_digit() |
15 |
# Cleanup/Optimisation TODO: |
16 |
|
17 |
my $TMPDIR="/var/spool/asterisk/tmp"; |
18 |
my $CALLDIR="/var/spool/asterisk/outgoing"; |
19 |
my $MSGDIR="/tmp"; |
20 |
|
21 |
sub get_meridian($) |
22 |
{ |
23 |
my ($AGI) = @_; |
24 |
my $rc; |
25 |
do { |
26 |
$rc = $AGI->get_data('1-for-am-2-for-pm', 7000, 1); |
27 |
} until ($rc == 1 || $rc == 2); |
28 |
return $rc; |
29 |
} |
30 |
|
31 |
sub valid_time($) |
32 |
{ |
33 |
my ($time) = @_; |
34 |
return ($time =~ /^((?:0?[0-9]|1[0-9]|2[0-3])[0-5][0-9])$/); |
35 |
} |
36 |
|
37 |
# Plays a list of audio files, allowing key-ahead for any of the expected |
38 |
# digits, returns the digit entered or the return code if none entered |
39 |
# TODO: loop if an invalid digit is entered? Or leave that up to the caller? |
40 |
sub get_digit($$$) |
41 |
{ |
42 |
my ($AGI, $parts, $valid_digits) = @_; |
43 |
my $rc; |
44 |
my $digit = ""; |
45 |
# stream each part, bail out early on valid digit entry |
46 |
foreach my $part (@{$parts}) { |
47 |
$rc = $AGI->stream_file($part, join("", @{$valid_digits})); |
48 |
if ($rc) { |
49 |
$digit .= ($rc - ord('0')); |
50 |
last; |
51 |
} |
52 |
} |
53 |
# We have our digit already, get out of here |
54 |
if (length($digit)) { |
55 |
return $digit; |
56 |
} else { |
57 |
$rc = $AGI->wait_for_digit(7000); |
58 |
if ($rc > 0) { |
59 |
$digit .= ($rc - ord('0')); |
60 |
return $digit; |
61 |
} else { |
62 |
return $rc; |
63 |
} |
64 |
} |
65 |
} |
66 |
|
67 |
sub get_wakeuptime($) |
68 |
{ |
69 |
my ($AGI) = @_; |
70 |
my $rc; |
71 |
my $digits = ""; |
72 |
my @parts = ('please-enter-the', 'time', 'for', 'your'); |
73 |
foreach my $part (@parts) { |
74 |
$rc = $AGI->stream_file($part, join("", 0..9)); |
75 |
if ($rc) { |
76 |
last; |
77 |
} |
78 |
} |
79 |
if ($rc) { |
80 |
# got a digit already by virtue of preempting things |
81 |
$digits .= ($rc - 48); |
82 |
loggit("early \$digits = $digits"); |
83 |
# read 2-3 digits |
84 |
LOOP: { |
85 |
do { |
86 |
$rc = $AGI->wait_for_digit(15000); |
87 |
loggit("\$rc = " . ($rc - 48)); |
88 |
last if ($rc == 0); |
89 |
$digits .= ($rc - 48); |
90 |
} until(length($digits) == 4); |
91 |
} |
92 |
loggit("Received individual (first early) $digits"); |
93 |
} else { |
94 |
# read 3-4 digits |
95 |
$digits = $AGI->get_data('wakeup-call', 15000, 4); |
96 |
} |
97 |
loggit("Received $digits"); |
98 |
if (length($digits) < 4) { |
99 |
# need to confirm am/pm |
100 |
my $meridian = get_meridian($AGI); |
101 |
if ($meridian == 1) { |
102 |
# prepend a zero |
103 |
$digits = "0" . $digits; |
104 |
} else { |
105 |
# convert to 24 hour time |
106 |
my ($first, $rest) = (substr($digits, 0, 1), substr($digits, 1)); |
107 |
$first = $first + 12; |
108 |
$digits = $first . $rest; |
109 |
} |
110 |
} |
111 |
loggit("Time entered: $digits"); |
112 |
return $digits; |
113 |
} |
114 |
|
115 |
sub say_wakeuptime($$$) |
116 |
{ |
117 |
my ($AGI, $wakeuptime, $tomorrow) = @_; |
118 |
my $pm = 0; |
119 |
|
120 |
if ($wakeuptime > 1159) { |
121 |
$wakeuptime -= 1200; |
122 |
$pm = 1; |
123 |
} |
124 |
|
125 |
if ($wakeuptime <= 59) { |
126 |
$wakeuptime += 1200; |
127 |
} |
128 |
|
129 |
if (length($wakeuptime) < 4) { |
130 |
$wakeuptime = "0" . $wakeuptime; |
131 |
} |
132 |
|
133 |
my ($hour, $min) = (substr($wakeuptime, 0, 2), substr($wakeuptime, 2, 2)); |
134 |
my ($hour1, $hour2) = (substr($hour, 0, 1), substr($hour, 1, 1)); |
135 |
my ($min1, $min2) = (substr($min, 0, 1), substr($min, 1, 1)); |
136 |
|
137 |
$AGI->stream_file('rqsted-wakeup-for'); |
138 |
|
139 |
if ($tomorrow) { |
140 |
$AGI->stream_file('tomorrow'); |
141 |
} else { |
142 |
$AGI->stream_file('today'); |
143 |
} |
144 |
|
145 |
$AGI->stream_file('at'); |
146 |
|
147 |
if ($hour1 == 0) { |
148 |
$AGI->stream_file("digits/$hour2"); |
149 |
} else { |
150 |
$AGI->stream_file("digits/$hour"); |
151 |
} |
152 |
|
153 |
if ($min == 0) { |
154 |
$AGI->stream_file('digits/oclock'); |
155 |
} else { |
156 |
if ($min1 == 0) { |
157 |
$AGI->stream_file('digits/oh'); |
158 |
$AGI->stream_file("digits/$min2"); |
159 |
} elsif ($min <= 20) { |
160 |
$AGI->stream_file("digits/$min"); |
161 |
} else { |
162 |
$min1 = int($min / 10) * 10; |
163 |
$AGI->stream_file("digits/$min1"); |
164 |
$min2 = $min % 10; |
165 |
if ($min2 > 0) { |
166 |
$AGI->stream_file("digits/$min2"); |
167 |
} |
168 |
} |
169 |
} |
170 |
if ($pm) { |
171 |
$AGI->stream_file('digits/p-m'); |
172 |
} else { |
173 |
$AGI->stream_file('digits/a-m'); |
174 |
} |
175 |
} |
176 |
|
177 |
sub say_wakeuptime_from_file($$) |
178 |
{ |
179 |
my ($AGI, $filename) = @_; |
180 |
|
181 |
# get time of wakeup call |
182 |
# should we assume the filename is correct, or the mtime? |
183 |
# mtime is more authoritative... |
184 |
my $stat = stat($CALLDIR . "/" . $filename); |
185 |
my $wakeuptime = strftime("%H%M", localtime($stat->mtime)); |
186 |
if ($wakeuptime <= strftime("%H%M", localtime())) { |
187 |
say_wakeuptime($AGI, $wakeuptime, 1); |
188 |
} else { |
189 |
say_wakeuptime($AGI, $wakeuptime, 0); |
190 |
} |
191 |
|
192 |
} |
193 |
|
194 |
sub cancel_wakeupcall($$) |
195 |
{ |
196 |
my ($AGI, $wakeupcall) = @_; |
197 |
if (-f $CALLDIR . "/" . $wakeupcall) { |
198 |
loggit("Unlinking ${CALLDIR}/${wakeupcall}"); |
199 |
unlink($CALLDIR . "/" . $wakeupcall); |
200 |
} |
201 |
if (-f $MSGDIR . "/" . $wakeupcall . ".wav") { |
202 |
loggit("Unlinking ${CALLDIR}/${wakeupcall}.wav"); |
203 |
unlink($MSGDIR . "/" . $wakeupcall . "wav"); |
204 |
} |
205 |
$AGI->stream_file('your'); |
206 |
$AGI->stream_file('wakeup-call'); |
207 |
$AGI->stream_file('has-been'); |
208 |
$AGI->stream_file('cancelled'); |
209 |
$AGI->stream_file('goodbye'); |
210 |
sleep(1); |
211 |
$AGI->hangup(); |
212 |
} |
213 |
|
214 |
sub record_wakeup_message($$) |
215 |
{ |
216 |
my ($AGI, $filename) = @_; |
217 |
my $rc; |
218 |
|
219 |
$AGI->stream_file('say-temp-msg-prs-pound'); |
220 |
$AGI->stream_file('beep'); |
221 |
$rc = $AGI->record_file($MSGDIR . "/" . $filename, "wav", '#', -1, 1); |
222 |
return $filename . ".wav"; |
223 |
} |
224 |
|
225 |
sub schedule_wakeupcall($$$) |
226 |
{ |
227 |
my ($AGI, $channel, $wakeuptime) = @_; |
228 |
my $rc; |
229 |
my $digit; |
230 |
my $message; |
231 |
my $filename; |
232 |
|
233 |
($filename = $channel) =~ s!/!.!g; |
234 |
$filename = $wakeuptime . "." . $filename . ".call"; |
235 |
loggit("\$filename = $filename"); |
236 |
|
237 |
# Ask to schedule message |
238 |
do { |
239 |
$digit = get_digit($AGI, ['to-leave-message-for', 'your', |
240 |
'wakeup-call', 'press-1', 'otherwise', 'press-0'], [0,1]); |
241 |
loggit("get_digit returned '$digit'"); |
242 |
} until ($digit == 0 || $digit == 1); |
243 |
if ($digit == 1) { |
244 |
# we need to record a message |
245 |
$message = record_wakeup_message($AGI, $filename); |
246 |
} |
247 |
# we're all good, schedule wakeup |
248 |
open(CALL, ">", $TMPDIR . "/" . $filename); |
249 |
print CALL "channel: $channel\n"; |
250 |
print CALL "maxretries: 3\n"; |
251 |
print CALL "retrytime: 60\n"; |
252 |
print CALL "waittime: 24\n"; |
253 |
print CALL "callerid: \"Wake Up\" <800>\n"; |
254 |
print CALL "application: AGI\n"; |
255 |
if ($message) { |
256 |
print CALL "data: wakeconfirm.agi|${message}\n"; |
257 |
} else { |
258 |
print CALL "data: wakeconfirm.agi\n"; |
259 |
} |
260 |
close(CALL); |
261 |
# if the current time is after the specified time, it will be |
262 |
# tomorrow |
263 |
my $now = strftime("%H%M", localtime()); |
264 |
my (undef, undef, undef, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(); |
265 |
my ($hour, $min) = (substr($wakeuptime, 0, 2), substr($wakeuptime, 2, 2)); |
266 |
my $time_t; |
267 |
my $tomorrow = 0; |
268 |
if ($now >= $wakeuptime) { |
269 |
$tomorrow = 1; |
270 |
$time_t = mktime(0, $min, $hour, $mday+1, $mon, $year, $wday, $yday, $isdst); |
271 |
loggit("Scheduling call for tomorrow ($time_t)"); |
272 |
utime($time_t, $time_t, $TMPDIR . "/" . $filename); |
273 |
} else { |
274 |
$tomorrow = 0; |
275 |
$time_t = mktime(0, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst); |
276 |
loggit("Scheduling call for today ($time_t)"); |
277 |
utime($time_t, $time_t, $TMPDIR . "/" . $filename); |
278 |
} |
279 |
rename($TMPDIR . "/" . $filename, $CALLDIR . "/" . $filename); |
280 |
say_wakeuptime($AGI, $wakeuptime, $tomorrow); |
281 |
$AGI->stream_file('goodbye'); |
282 |
sleep(1); |
283 |
$AGI->hangup(); |
284 |
} |
285 |
|
286 |
sub get_wakeupcall($) |
287 |
{ |
288 |
my ($channel) = @_; |
289 |
my $filename; |
290 |
($filename = $channel) =~ s!/!.!g; |
291 |
# Need to check for files ending in $extension in $CALLDIR |
292 |
opendir(CALLDIR, $CALLDIR); |
293 |
my @wakeupcalls = grep { /^\d{4}\.${filename}\.call$/ && -f "$CALLDIR/$_" } readdir(CALLDIR); |
294 |
closedir(CALLDIR); |
295 |
return \@wakeupcalls; |
296 |
} |
297 |
|
298 |
sub loggit($) |
299 |
{ |
300 |
my ($message) = @_; |
301 |
open(LOG, ">>", "/tmp/wakeup2.log"); |
302 |
print LOG scalar localtime(time) . ": " . $message . "\n"; |
303 |
close(LOG); |
304 |
} |
305 |
|
306 |
my $AGI = new Asterisk::AGI; |
307 |
my $rc; |
308 |
my $channel; |
309 |
my $existing_wakeup; |
310 |
|
311 |
my %input = $AGI->ReadParse(); |
312 |
$channel = (split("-", $input{'channel'}))[0]; |
313 |
|
314 |
$AGI->answer(); |
315 |
sleep(1); |
316 |
$rc = $AGI->stream_file('welcome'); |
317 |
$existing_wakeup = get_wakeupcall($channel); |
318 |
|
319 |
if (scalar @{$existing_wakeup}) { |
320 |
# Need to do stuff here |
321 |
# press 1 to schedule, 2 to cancel |
322 |
loggit("I think there's existing wakeup calls"); |
323 |
loggit(join(",", @{$existing_wakeup})); |
324 |
say_wakeuptime_from_file($AGI, $existing_wakeup->[0]); |
325 |
my $digit = ""; |
326 |
do { |
327 |
$digit = get_digit($AGI, ['for-wakeup-call', 'press-1', |
328 |
'to-cancel-wakeup', 'press-2'], [1..2]); |
329 |
} until ($digit == 1 || $digit == 2); |
330 |
loggit("Received $digit"); |
331 |
if ($digit == 1) { |
332 |
# schedule wakeupcall |
333 |
my $wakeuptime; |
334 |
do { |
335 |
$wakeuptime = get_wakeuptime($AGI); |
336 |
} until (valid_time($wakeuptime)); |
337 |
schedule_wakeupcall($AGI, $channel, $wakeuptime) |
338 |
} else { |
339 |
# cancel wakeupcall |
340 |
cancel_wakeupcall($AGI, $existing_wakeup->[0]); |
341 |
} |
342 |
|
343 |
} else { |
344 |
my $wakeuptime; |
345 |
do { |
346 |
$wakeuptime = get_wakeuptime($AGI); |
347 |
} until (valid_time($wakeuptime)); |
348 |
schedule_wakeupcall($AGI, $channel, $wakeuptime) |
349 |
} |