Source

rapidcall / rapidcall.pl

  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
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
#!/usr/bin/env perl
#
# rapidcall - Call people on the command line like a balla
#
# Copyright (c) 2007 J.A. Roberts Tunney
# J.A. Roberts Tunney <jtunney@lobstertech.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#

use strict;
use warnings;
use File::Copy;
use Manager;

my $VERBOSE = 0;
my $ASTLOC = (defined $ENV{CALL_ASTLOC}) ?
    $ENV{CALL_ASTLOC} : 'local:///var/spool/asterisk/outgoing';
my $CONTEXT = (defined $ENV{CALL_CONTEXT}) ?
    $ENV{CALL_CONTEXT} : 'default';
my $PHONE = (defined $ENV{CALL_PHONE}) ?
    $ENV{CALL_PHONE} : 'Zap/1';
my $CONTACTS = (defined $ENV{CALL_CONTACTS}) ?
    $ENV{CALL_CONTACTS} : $ENV{HOME} .'/.contacts';

while (@ARGV && $ARGV[0] =~ /^-/) {
    my $arg = shift @ARGV;
    $arg =~ /^--help$/           and usage();
    $arg =~ /^-h$/               and usage();
    $arg =~ /^--version$/        and version();
    $arg =~ /^-v$/               and $VERBOSE  = 1;
    $arg =~ /^-L(.*)$/           and $ASTLOC   = ($1) ? $1 : shift @ARGV;
    $arg =~ /^--location=?(.*)$/ and $ASTLOC   = ($1) ? $1 : shift @ARGV;
    $arg =~ /^-P(.*)$/           and $PHONE    = ($1) ? $1 : shift @ARGV;
    $arg =~ /^--phone=?(.*)$/    and $PHONE    = ($1) ? $1 : shift @ARGV;
    $arg =~ /^-C(.*)$/           and $CONTEXT  = ($1) ? $1 : shift @ARGV;
    $arg =~ /^--context=?(.*)$/  and $CONTEXT  = ($1) ? $1 : shift @ARGV;
    $arg =~ /^-F(.*)$/           and $CONTACTS = ($1) ? $1 : shift @ARGV;
    $arg =~ /^--contacts=?(.*)$/ and $CONTACTS = ($1) ? $1 : shift @ARGV;
}
@ARGV >= 1                       or  usage();
$ASTLOC =~ m!^local://(.+)$!i    and local_call($1, @ARGV);
$ASTLOC =~ m!^ami://(.+)$!i      and ami_call($1, @ARGV);
die "Asterisk location contains an unrecognized protocol\n";

sub usage
{
    my $error = shift;
    print STDERR "Error: $error\n" if defined $error;
    print "Type 'man rapidcall' for help\n";
    exit 1;
}

sub version
{
    print "rapidcall 0.1\n";
    exit 0;
}

sub load_contacts
{
    my %res = ();
    my $file = $CONTACTS;
    defined $file or usage("CALL_CONTACTS not defined");
    -f $file      or usage("contacts file does not exist");
    open F, "<$file" or die "Failed to open: $file ($!)\n";
    my $line = 0;
    foreach (<F>) {
        $line++;
        /^\s*$/ and next;
        /^\s*([^\s]+)\s*(.+?)\s*$/
            or print STDERR "Syntax error: $file:$line\n" and next;
        not defined $res{$1}
            or print STDERR "Ignoring duplicate: $file:$line\n" and next;
        $res{$1} = $2;
    }
    close F;
    return \%res;
}

# http://glennf.com/writing/hexadecimal.url.encoding.html
sub url_decode
{
    my $theURL = $_[0];
    $theURL =~ tr/+/ /;
    $theURL =~ s/%([a-fA-F0-9]{2,2})/chr(hex($1))/eg;
    $theURL =~ s/<!--(.|\n)*-->//g;
    return $theURL;
}

# we thank the pope for granting us this wish
# when friday comes, we'll all call rats fish
sub get_dest
{
    my $arg = shift;
    my $context;
    my $exten;

    if ($arg =~ /^(.+?)@(.+?)$/) {
        $context = $2;
        $arg = $1;
    } else {
        $context = $CONTEXT;
        defined $context or usage("CALL_CONTEXT not defined");
    }

    my $contacts = load_contacts();
    if (defined $contacts->{$arg}) {
        $exten = $contacts->{$arg};
    } else {
        $exten = $arg;
    }

    $context =~ /^[-\w]+$/i or usage("Invalid call context");
    $exten =~ s/[-()\s]//g;
    return ($context, $exten);
}

sub place_local_call
{
    my $spooldir = shift;
    my $context  = shift;
    my $exten    = shift;
    my $file     = "astcall_". rand(99999) .".call";
    my $temp     = "/tmp/". $file;
    my $spool    = $spooldir .'/'. $file;

    my $data =
        "Channel: $PHONE\n".
        "CallerID: $exten\n".
        "WaitTime: 15\n".
        "Context: $context\n".
        "Extension: $exten\n".
        "Priority: 1\n".
        "Set: CALLGEN=yes";
    foreach (@_) {
        /^[a-z][-_\w]+\s*=[^|\r\n]+$/i or die "Invalid variable definition\n";
        $data .= "Set: $_\n";
    }

    print "CALL FILE:\n$data\n" if $VERBOSE;
    open F, ">$temp" or die "Failed to open $temp ($!)\n";
    print F $data;
    close F;
    copy($temp, $spool) or die "Failed to move $temp to $spool ($!)\n";
}

sub local_call
{
    my $loc = shift;
    my $arg = shift;

    my $spooldir = url_decode($loc);
    $spooldir =~ s/\/$//;

    foreach my $dest (split m@,@, $arg) {
        my ($context, $exten) = get_dest($dest);
        place_local_call($spooldir, $context, $exten, @_);
    }

    exit 0;
}

sub manager_connect
{
    my ($host, $port, $user, $pass) = @_;

    my $man = Manager->new(
        Host         => $host,
        Port         => $port,
        Username     => $user,
        Secret       => $pass,
        Debug        => $VERBOSE,
        SystemEvents => 1,
        CallEvents   => 1,
        LogEvents    => 1);
    $man->connect;
    $man->login;

    return $man;
}

sub place_ami_call
{
    my $man     = shift;
    my $context = shift;
    my $exten   = shift;

    my $vars = 'CALLGEN=yes';
    foreach (@_) {
        /^[a-z][-_\w]+\s*=[^|\r\n]+$/i or die "Invalid variable definition\n";
        $vars .= "|$_";
    }

    my $res = $man->send_frame(Action   => "Originate",
                               Channel  => $PHONE,
                               Callerid => $exten,
                               Timeout  => 15000,
                               Context  => $context,
                               Exten    => $exten,
                               Priority => 1,
                               Variable => $vars);
    unless (defined $res->{Response} && $res->{Response} eq 'Success') {
        die "Failed to originate call via AMI\n";
    }
}

sub ami_call
{
    my $loc = shift;
    my $arg = shift;

    $loc =~ m/^(.+?):(.+?)\@([^:]+):?(\d*)$/i
        or die "Invalid ami location\n";
    my $user = url_decode($1);
    my $pass = url_decode($2);
    my $host = url_decode($3);
    my $port = ($4 eq "") ? 5038 : url_decode($4);
    my $man = manager_connect($host, $port, $user, $pass);

    foreach my $dest (split m@,@, $arg) {
        my ($context, $exten) = get_dest($dest);
        place_ami_call($man, $context, $exten, @_);
    }

    exit 0;
}

__END__

=head1 NAME

rapidcall - Call people on the command line like a balla

=head1 SYNOPSIS

rapidcall [OPTION]... <CONTACT>[@CONTEXT][,MULTIPLE]... [VAR1=x]...

=head1 DESCRIPTION

Do you loathe the agony of dialing numbers every time you want to make a call?
Does your SIP phone have a disasterous number directory?  Do you need a method
of generating calls more becoming of an elite creature of an the night such as
yourself?  Then look no further than the abyss of your black terminal...  This
program will allow you to easily generate calls to your fellow vampyres and
ghouls on the command line through your favorite Asterisk server.

Imagine how delectable the pleasure would be, of needing to call a fellow
mortal, for instance one possessing the admirable appellation of Lucretia.
What if you could call her by to Alt-Tab to a terminal window and typing
'rapidcall Luc', hitting tab to complete the name, and pressing enter.  Your
phone rings, and you are instantly connected to her.  Wouldn't that be
splendid?

This program offers a great deal of flexability in controlling Asterisk and
different methods of invokation.  The typical user will want to create a
.contacts file containing a list of names and phone numbers, configure their
.bashrc to set environment shortcut variables and enable bash completion.  The
user may then place calls to their friends by typing 'rapidcall
<contact_name>' on the command line.

In order for most setups to work, you need to have a functioning Asterisk PBX
configured either locally or remotely, and a phone that is connected to your
Asterisk server.  When calls are placed, Asterisk will be told to call your
phone.  Once you answer the phone, you will be connected with the called
party.

=head1 OPTIONS

=over 4

=item -C or --context

The Asterisk context that controls will accept phone number extensions.  Set
this to the same context your sip phones are programmed to use.  You may
override this by manually specifying a context with an @ symbol.  The default
value is 'default'.  Alternatively may be set with the environment variable
CALL_CONTEXT.

=item -F or --contacts

The location of the file containing your contact list.  You should set this to
'~/.contacts'.  In the contacts file, each line must contain a name with no
spaces, whitespace, and the extension that will be dialed in the CALL_CONTEXT
extension.  The default value is set to '~/.contacts'.  Alternatively may be
set with the environment variable CALL_CONTACTS.

=item -h or --help

Shows this information.

=item -L or --location

The 'location' or how to send calls to Asterisk.  The default value of
'local:///var/spool/asterisk/outgoing' generates calls by moving Asterisk call
files to the specified Asterisk spool directory on the local machine.  If you
want to generate calls on a remote machine, you can use the manager interface
by specifying connection parameters in the following format:
'ami://user:pass@host[:port]'.  If you do not wish to set this each time on
the command line, you may set the environment variable CALL_ASTLOC.

=item -P or --phone

The Asterisk context that controls will accept phone number extensions.  Set
this to the same context your sip phones are programmed to use.  You may
override this by manually specifying a context with an @ symbol.  The default
value is 'default'.  Alternatively may be set with the environment variable
CALL_PHONE.

=item -v

Verbose mode.  This will show raw data coming to and from the Asterisk
manager interface as well as the contents of generated call files.

=item --version

Show version.

=back

=head1 NOTES

=over 4

=item - sudo is needed if CALL_ASTLOC is set to local://

=item - You can put hyphens in your phone numbers

=back

=head1 EXAMPLES

=over 4

=item Example .bashrc Configuration with Shell Completion:

 # put this in your .bashrc.  Your may reload your .bashrc by
 # entering ". ~/.bashrc".
 export CALL_ASTLOC='local:///var/spool/asterisk/outgoing'
 export CALL_CONTEXT='internal'
 export CALL_PHONE='SIP/300'
 export CALL_CONTACTS=~/.contacts
 _rapidcall() {
     local contacts
     local cur
     contacts=$( awk '{print $1}' <$CALL_CONTACTS )
     cur=${COMP_WORDS[COMP_CWORD]}
     COMPREPLY=( $( compgen -W '$contacts' -- $cur) )
 }
 complete -F _rapidcall rapidcall

=item Example .contacts File:

 satan             1-203-666-0420
 lord_voldemort    1-666-148-3023
 king_solomon      1-893-750-9395
 peter_murphy      1-394-295-0034
 1800_suicide      1-800-784-2433

=item Example Invocations:

 # Call suicide hotline and set the channel variable 'CID' to
 # let my dialplan know to set a spoofed caller id
 rapidcall 1800_suicide CID=12036660420

 # Call Satan, but originate the call through the dialplan
 # context 'hell' instead of what we chose in our .bashrc
 rapidcall satan@hell

 # no .bashrc configuration required for this if you have
 # asterisk running on the local system
 rapidcall -P SIP/300 666-0420@internal

 # But hey!  If you thought all that was cool check this
 # out... If you have a MeetMe conference setup at conf|s|1 you
 # can invite yourself and two friends in with the following
 # command:
 rapidcall -P Local/s@conf 300,203-666-0420,203-999-1111

=item Example manager.conf File:

 [general]
 enabled = yes
 displaysystemname = yes
 port = 5038
 bindaddr = 0.0.0.0
 ;webenabled = yes
 ;httptimeout = 60

 ; ami://rapidcall:shamu@localhost
 [rapidcall]
 secret = shamu
 deny   = 0.0.0.0/0.0.0.0
 permit = 127.0.0.1/255.255.255.255
 read   = call
 write  = call

=back

=head1 AUTHOR

Written by J.A. Roberts Tunney <themador@lobsetrtech.com>

=head1 COPYRIGHT

Copyright (c) 2007 J.A. Roberts Tunney

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details.

=cut