File Coverage

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


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