File Coverage

blib/lib/Magrathea/API.pm
Criterion Covered Total %
statement 35 184 19.0
branch 0 62 0.0
condition 0 15 0.0
subroutine 12 32 37.5
pod 15 17 88.2
total 62 310 20.0


line stmt bran cond sub pod time code
1             package Magrathea::API;
2              
3 1     1   67784 use strict;
  1         2  
  1         28  
4 1     1   9 use warnings;
  1         2  
  1         23  
5 1     1   13 use 5.10.0;
  1         3  
6              
7 1     1   439 use version 0.77; our $VERSION = qv('v1.5.1');
  1         1942  
  1         7  
8              
9 1     1   1084 use Net::Telnet;
  1         41934  
  1         50  
10 1     1   400 use Phone::Number;
  1         5875  
  1         31  
11 1     1   516 use Email::Address;
  1         26578  
  1         64  
12 1     1   491 use Magrathea::API::Status;
  1         54  
  1         5  
13 1     1   583 use Magrathea::API::Emergency;
  1         5  
  1         9  
14 1     1   134 use Attribute::Boolean;
  1         2  
  1         4  
15              
16 1     1   89 use Carp;
  1         2  
  1         48  
17 1     1   6 use Data::Dumper;
  1         2  
  1         2065  
18              
19             our @CARP_NOT = qw{ Net::Telnet };
20              
21             =encoding utf8
22              
23             =head2 NAME
24              
25             Magrathea::API - Easier access to the Magrathea NTS API
26              
27             =head2 VERSION
28              
29             Version 1.5.1
30              
31             =head2 SYNOPSIS
32              
33             use Magrathea::API;
34             my $mt = new Magrathea::API;
35             my $number = $mt->allocate('01792');
36             $mt->deactivate($number);
37             my @list = $mt->list('01792');
38             my @numbers = $mt->block_allocate('01792', 10);
39             $mt->fax2email($numbers[2], 'user@host.com');
40             $mt->divert($number[3], '+5716027171');
41             $emerg = $mt->emergency_info;
42              
43             =head2 DESCRIPTION
44              
45             This module implements most of the
46             L
47             in a simple format.
48              
49             =head2 EXPORT
50              
51             Nothing Exported.
52              
53             =cut
54              
55             #################################################################
56             ##
57             ## Local Prototyped Functions
58             ##
59             #################################################################
60              
61             sub catch(;$)
62             {
63 0     0 0   local $_ = $@;
64 0 0         return undef unless $_;
65 0           chomp;
66 0           my $re = shift;
67 0 0 0       return true if ref $re eq 'Regexp' and $_ =~ $re;
68 0           croak $_;
69             }
70              
71             #################################################################
72             ##
73             ## Private Instance Functions
74             ##
75             #################################################################
76              
77             sub sendline
78             {
79 0     0 0   my $self = shift;
80 0   0       my $message = shift // '';
81 0 0 0       say ">> $message" if $self->{params}{debug} && $message;
82 0 0         $self->{telnet}->print($message) if $message;
83 0           my $response = $self->{telnet}->getline;
84 0           chomp $response;
85 0           my ($val, $msg) = $response =~ /^(\d)\s+(.*)/;
86 0 0         croak qq(Unknown response: "$response") unless defined $val;
87 0 0         say "<<$val $msg" if $self->{params}{debug};
88 0 0         croak "$msg" unless $val == 0;
89 0           return $val, $msg;
90             }
91              
92             #################################################################
93             ##
94             ## Class Functions
95             ##
96             #################################################################
97              
98             =head2 MAIN API METHODS
99              
100             =head2 Constructor
101              
102             =head3 new
103              
104             This will create a new Magrathea object and open at telnet
105             session to the server. If authorisation fails, it will croak.
106              
107             my $mt = new Magrathea::API(
108             username => 'myuser',
109             password => 'mypass',
110             );
111              
112             =head4 Parameters:
113              
114             =over
115              
116             =item username
117              
118             =item password
119              
120             The username and password allocated by Magrathea.
121              
122             =item host
123              
124             Defaults to I but could be overridden.
125              
126             =item port
127              
128             Defaults to I<777>.
129              
130             =item timeout
131              
132             In seconds. Defaults to I<10>.
133              
134             =item debug
135              
136             If set to a true value, this will output the conversation between the API
137             and Magrathea's server. Be careful as this will also echo the username
138             and password.
139              
140             =back
141              
142             =cut
143              
144             sub new
145             {
146 0     0 1   my $class = shift;
147 0           my %defaults = (
148             host => 'api.magrathea-telecom.co.uk',
149             port => 777,
150             timeout => 10,
151             debug => false,
152             );
153 0           my %params = (%defaults, @_);
154             croak "Username & Password Required"
155 0 0 0       unless $params{username} && $params{password};
156             my $telnet = new Net::Telnet(
157             Host => $params{host},
158             Port => $params{port},
159             Timeout => $params{timeout},
160             Errmode => sub {
161 0     0     croak shift;
162             },
163 0           );
164 0           my $self = {
165             params => \%params,
166             telnet => $telnet,
167             };
168 0           bless $self, $class;
169 0           $self->sendline;
170 0           eval {
171 0           $self->auth(@params{qw(username password)});
172             };
173 0           catch;
174 0           return $self;
175             }
176              
177             #################################################################
178             ##
179             ## Instance Functions
180             ##
181             #################################################################
182              
183             =head2 Allocation Methods
184              
185             In all cases where C<$number> is passed, this may be a string
186             containing a number in National format (I<020 1234 5678>) or
187             in International format (I<+44 20 1234 5678>). Spaces are ignored.
188             Also, L objects may be passed.
189              
190             When a number is returned, it will always be in the for of a
191             L object.
192              
193             =head3 allocate
194              
195             Passed a prefix, this will allocate and activate a number. You do not need
196             to add the C<_> characters. If a number can be found, this routine
197             will return a L object. If no match is found, this
198             routine will return C. It will croak on any other error from
199             Magrathea.
200              
201             =cut
202              
203             sub allocate
204             {
205 0     0 1   my $self = shift;
206 0           my $number = shift;
207 0           $number = substr $number . '_' x 11, 0, 11;
208 0           for (my $tries = 0; $tries < 5; $tries++)
209             {
210 0           eval {
211 0           my $result = $self->allo($number);
212 0           ($number = $result) =~ s/\s.*$//;
213             };
214 0 0         return undef if catch qr/^No number found for allocation/;
215 0           eval {
216 0           $self->acti($number);
217             };
218 0 0         unless (catch qr/^Number not activated/) # $@ is ''
219             {
220 0           return new Phone::Number($number);
221             }
222             }
223 0           return undef; # Failed after 5 attempts.
224             }
225              
226             =head3 deactivate
227              
228             Passed a number as a string or a L, this deactivates
229             the number.
230              
231             =cut
232              
233             sub deactivate
234             {
235 0     0 1   my $self = shift;
236 0           my $number = new Phone::Number(shift);
237 0           $self->deac($number->packed);
238             }
239              
240             =head3 reactivate
241              
242             Reactivates a number that has previously been deactivated.
243              
244             =cut
245              
246             sub reactivate
247             {
248 0     0 1   my $self = shift;
249 0           my $number = new Phone::Number(shift);
250 0           $self->reac($number->packed);
251             }
252              
253             =head3 list
254              
255             This should be passed a prefix and possibly a quantity (defaulting
256             to 10. It will return a sorted random list of available numbers matching
257             the prefix. These are returned as an array (or an arrayref) of
258             L. None of the numbers is allocated by this method.
259              
260             If none are available, the method will return an empty array.
261              
262             =cut
263              
264             sub list
265             {
266 0     0 1   my $self = shift;
267 0           my $prefix = shift;
268 0   0       my $qty = shift // 10;
269 0           local $_;
270 0           my @results;
271 0           eval {
272 0           push @results, new Phone::Number($self->alist($prefix, $qty));
273             };
274 0 0         unless (catch qr/^No range found for allocation/) {
275 0           while (true) {
276 0           my $response = $self->{telnet}->getline;
277 0           chomp $response;
278 0           my ($val, $msg) = $response =~ /^(\d)\s+(.*)/;
279 0 0         say "<<$val $msg" if $self->{params}{debug};
280 0 0         last if $val != 0;
281 0           push @results, new Phone::Number($msg);
282             }
283 0           @results = sort { $a->plain cmp $b->plain } @results;
  0            
284             }
285 0 0         return wantarray ? @results : \@results;
286              
287             }
288              
289             =head2 Block Methods
290              
291             =head3 block_allocate
292              
293             This should be passed a prefix (without any C<_> characters) and an
294             optional block size (defaulting to 10). It will attempt to allocate
295             and activate a block of numbers.
296              
297             If a block can be found, this routine
298             should return an array or arrayref of L objects. Under odd
299             circumstances, it is possible that fewer than the requested quantity
300             of numbers will be returned;
301              
302             If no range is found is found, this routine will return C in scalar
303             context or an empty array in list context. It will croak
304             on any other error from Magrathea.
305              
306             =cut
307              
308             sub block_allocate
309             {
310 0     0 1   my $self = shift;
311 0           my $range = shift;
312 0   0       my $qty = shift // 10;
313 0           local $_;
314 0 0         croak "Block size must be a number" unless $qty =~ /^\d+$/;
315 0           my $alloc = eval {
316 0           $self->blkacti($range, $qty);
317             };
318 0 0         if (catch qr/^No range found for allocation/) {
319 0 0         return wantarray ? () : undef;
320             }
321 0           my ($first, $last) = split ' ', $alloc;
322 0           my @numbers;
323 0           while ($first le $last) {
324 0           push @numbers, new Phone::Number($first++);
325             }
326 0 0         return wantarray ? @numbers : \@numbers;
327             }
328              
329             =head3 block_info
330              
331             This should be passed a number (string or L)
332             to check whether that number is part of a block.
333              
334             If it is, the size of the block will be returned in scalar context;
335             In list context, the response will be an array of all the numbers
336             in that block.
337              
338             If it is not a block, this will return C or an empty
339             array.
340              
341             =cut
342              
343             sub block_info
344             {
345 0     0 1   my $self = shift;
346 0           my $number = new Phone::Number(shift);
347 0           my $block = eval {
348 0           $self->blkinfo($number->packed);
349             };
350 0 0         if (catch qr/^Account not ACTIve/) {
351 0 0         return wantarray ? () : undef;
352             }
353 0           my ($first, $qty) = split ' ', $block;
354 0 0         return 0 + $qty unless wantarray;
355 0           my @numbers;
356 0           for (; $qty > 0; $qty--) {
357 0           push @numbers, new Phone::Number($first++);
358             }
359 0           return @numbers;
360             }
361              
362             =head3 block_deactivate
363              
364             This should be passed the first number in a block. It will
365             deactivate and return the block of numbers.
366              
367             =cut
368              
369             sub block_deactivate
370             {
371 0     0 1   my $self = shift;
372 0           my $number = new Phone::Number(shift);
373 0           $self->blkdeac($number->packed);
374             }
375              
376             =head3 block_reactivate
377              
378             This should be passed the first number in a block. It will
379             reactivate the block and return the size of the block in scalar
380             context or an array of the numbers in list context.
381              
382             If the block is not available, this method will croak.
383              
384             In testing, this method has never worked correctly.
385              
386             =cut
387              
388             sub block_reactivate
389             {
390 0     0 1   my $self = shift;
391 0           my $number = new Phone::Number(shift);
392 0           $self->blkreac($number->packed);
393             }
394              
395             =head2 Service Methods
396              
397             =head3 fax2email
398              
399             Sets a number as a fax to email.
400              
401             $mt->fax2email($number, $email_address);
402              
403             =cut
404              
405             sub fax2email
406             {
407 0     0 1   my $self = shift;
408 0           my $number = new Phone::Number(shift);
409 0           my $email = shift;
410 0           my @email = parse Email::Address($email);
411 0 0         croak "One email address required" if @email != 1;
412 0           $self->set($number->packed, 1, "F:$email[0]");
413             }
414              
415             =head3 voice2email
416              
417             Sets a number as a voice to email.
418              
419             $mt->voice2email($number, $email_address);
420              
421             =cut
422              
423             sub voice2email
424             {
425 0     0 1   my $self = shift;
426 0           my $number = new Phone::Number(shift);
427 0           my $email = shift;
428 0           my @email = parse Email::Address($email);
429 0 0         croak "One email address required" if @email != 1;
430 0           $self->set($number->packed, 1, "V:$email[0]");
431             }
432              
433             =head3 sip
434              
435             $mt->sip($number, $host, [$username, [$inband]]);
436              
437             Passed a number and a host, will set an inbound sip link
438             to the international number (minus leading +) @ the host.
439             If username is defined, it will be used instead of the number.
440             If inband is true, it will force inband DTMF. The default is
441             RFC2833 DTMF.
442              
443             =cut
444              
445             sub sip
446             {
447 0     0 1   my $self = shift;
448 0           my $number = new Phone::Number(shift);
449 0           my ($host, $username, $inband) = @_;
450 0 0         croak "Domain required" unless $host;
451 0 0         $username = $number->plain unless $username;
452 0 0         my $sip = $inband ? "s" : "S";
453 0           $self->set($number->packed, 1, "$sip:$username\@$host");
454             }
455              
456             =head3 divert
457              
458             $mt->divert($number, $to_number);
459              
460             =cut
461              
462             sub divert
463             {
464 0     0 1   my $self = shift;
465 0           my $number = new Phone::Number(shift);
466 0           my $to = new Phone::Number(shift);
467 0           $self->set($number->packed, 1, $to->plain);
468             }
469              
470              
471             =head3 status
472              
473             Returns the status for a given number.
474              
475             my $status = $mt->status($number);
476             my @status = $mt->status($number);
477              
478             In scalar context, returns the first (and usually only) status as
479             a L object. In list context, returns up to
480             three statuses representing the three possible setups created with
481             ORDE.
482              
483             If the number is not allocated to us and activated, this routine
484             returns C in scalar context and an empty list in list context.
485              
486             The L object has the following calls:
487              
488             =over
489              
490             =item C<< $status->number >>
491              
492             A L object representing the number to which this
493             status refers.
494              
495             =item C<< $status->active >>
496              
497             Boolean.
498              
499             =item C<< $status->expiry >>
500              
501             The date this number expires in the form C.
502              
503             =item C<< $status->type >>
504              
505             One of sip, fax2email, voice2email, divert or unallocated.
506              
507             =item C<< $status->target >>
508              
509             The target email or phone number for this number;
510              
511             =item C<< $status->entry >>
512              
513             The entry number (1, 2 or 3) for this status;
514              
515             =back
516              
517             In addition, it overloads '""' to provide as tring comprising
518             the type and the target, separated by a space.
519              
520             =cut
521              
522             sub status
523             {
524 0     0 1   my $self = shift;
525 0           my $number = new Phone::Number(shift);
526 0           my $status = eval {
527 0           $self->stat($number->packed);
528             };
529 0 0         return wantarray ? () : undef if $@;
    0          
530 0           my @statuses = split /\|/, $status;
531 0           my @retval;
532 0           for my $i (0 .. 2)
533             {
534 0           my $stat = new Magrathea::API::Status($statuses[$i]);
535 0 0         return $stat unless wantarray;
536 0 0         next unless $stat;
537 0           $stat->entry($i + 1);
538 0           push @retval, $stat;
539             }
540 0           return @retval;
541             }
542              
543             =head2 Emergency Methods
544              
545             =head3 emergency_info
546              
547             Passed a phone number, this method returns a
548             L object with the current 999
549             information.
550              
551             Optionally it can be passed a second parameter which, if it
552             is a truthy value, will set the C flag.
553              
554             =cut
555              
556             sub emergency_info
557             {
558 0     0 1   my $self = shift;
559 0           my $number = new Phone::Number(shift);
560 0           my $ported = shift;
561 0           return new Magrathea::API::Emergency($self, $number, $ported);
562             }
563              
564             =head2 Low Level Methods
565              
566             All the Magrathea low level calls are available. These are
567             simply passed an array of strings which are joined to create
568             the command string. They return the raw response
569             on success (minus the leading 0) and die on failure. C<$@>
570             will contain the error.
571              
572             See the L.
573              
574             The functions are:
575              
576             =over
577              
578             =item auth
579              
580             This is called by L and should not be called directly.
581              
582             $mt->auth('username', 'password');
583              
584             =item quit
585              
586             This is called automatically upon the Magrathea::API object
587             going out of scope and should not be called directly.
588              
589             =item allo
590              
591             $mt->allo('0201235___');
592              
593             =item acti
594              
595             $mt->acti('02012345678');
596              
597             =item deac
598              
599             $mt->deac('02012345678');
600              
601             =item reac
602              
603             $mt->reac('02012345678');
604              
605             =item stat
606              
607             $mt->stat('02012345678');
608              
609             =item set
610              
611             $mt->set('02012345678 1 441189999999');
612             $mt->set('02012345678 1 F:fax@mydomain.com');
613             $mt->set('02012345678 1 V:voicemail@mydomain.com');
614             $mt->set('02012345678 1 S:username@sip.com');
615             $mt->set('02012345678 1 I:username:password@iaxhost.com');
616              
617             =item spin
618              
619             $mt->set('02012345678 [pin]');
620              
621             =item feat
622              
623             $mt->feat('02012345678 D');
624             $mt->feat('02012345678 J');
625              
626             =item orde
627              
628             $mt->orde('02012345678 1 0000');
629              
630             =item info
631              
632             $mt->info('02012345678 GEN Magrathea, 14 Shute End, RG40 1BJ');
633              
634             =back
635              
636             It will not usually be necessary to call these functions directly.
637              
638             =cut
639              
640             sub AUTOLOAD
641             {
642 0     0     my $self = shift;
643 0           my $commands = qr{^(?:
644             AUTH|QUIT|ALLO|ACTI|DEAC|REAC|STAT|SET|SPIN|FEAT|ORDE|INFO|ALIST|
645             BLKACTI|BLKINFO|BLKDEAC|BLKREAC
646             )$}x;
647 0           (my $name = our $AUTOLOAD) =~ s/.*://;
648 0           my $cmd = uc $name;
649 0 0         croak "Unknown Command: $name" unless $cmd =~ $commands;
650 0           return $self->sendline("$cmd @_");
651             }
652              
653             sub DESTROY
654             {
655 0     0     my $self = shift;
656 0           eval {
657 0           $self->quit;
658             };
659             }
660              
661             1;
662              
663             __END__