File Coverage

blib/lib/Magrathea/API.pm
Criterion Covered Total %
statement 35 192 18.2
branch 0 86 0.0
condition 0 15 0.0
subroutine 12 33 36.3
pod 16 18 88.8
total 63 344 18.3


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