File Coverage

blib/lib/SMS/CPAGateway.pm
Criterion Covered Total %
statement 12 78 15.3
branch 0 20 0.0
condition 0 6 0.0
subroutine 4 11 36.3
pod 1 1 100.0
total 17 116 14.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             # SMS::CPAGateway
3             #
4             # Copyright Eskild Hustvedt 2008, 2010
5             # for Portu Media & Communications
6             #
7             # This program is free software; you can redistribute it and/or
8             # modify it under the same terms as Perl itself.
9              
10             package SMS::CPAGateway;
11 1     1   31229 use Any::Moose;
  1         39913  
  1         7  
12 1     1   3786 use LWP::UserAgent;
  1         82524  
  1         36  
13 1     1   1127 use URI::Encode;
  1         29835  
  1         72  
14 1     1   12 use constant { true => 1, false => undef };
  1         2  
  1         1899  
15              
16             our $VERSION = 0.01;
17              
18             # - Public attributes -
19              
20             has 'servers' => (
21             is => 'rw',
22             isa => 'ArrayRef[Str]',
23             required => true,
24             );
25              
26             has 'fromNo' => (
27             isa => 'Int',
28             is => 'rw',
29             required => true,
30             );
31              
32             has 'price' => (
33             isa => 'Int',
34             is => 'rw',
35             required => true,
36             default => 0,
37             );
38              
39             has 'key' => (
40             isa => 'Str',
41             required => true,
42             is => 'rw',
43             );
44              
45             has 'id' => (
46             isa => 'Str',
47             is => 'rw',
48             builder => '_buildID',
49             lazy => 1,
50             );
51              
52             has 'errors'=> (
53             isa => 'ArrayRef[Str]',
54             is => 'rw',
55             default => sub { [] },
56             writer => '_writeErrors',
57             );
58              
59             has 'hadError' => (
60             isa => 'Bool',
61             is => 'rw',
62             default => false,
63             writer => '_setError',
64             );
65              
66             # - Public methods -
67              
68             # Purpose: Send an SMS
69             # Usage: object->send(RECIPIENT,MESSAGE,PRICE?);
70             # RECIPIENT is the recipient phone number, with +XX direction code.
71             # MESSAGE is the message.
72             # PRICE is the price
73             sub send
74             {
75 0     0 1   my $self = shift;
76 0           my $recipient = shift;
77 0           my $message = shift;
78 0           my $price = shift;
79 0 0         if (not $recipient =~ /^\+/)
80             {
81 0 0         if ($recipient =~ /^00/)
82             {
83 0           $recipient =~ s/^00/+/;
84             }
85             else
86             {
87 0           $self->_appendError('Recipient does not start with +');
88 0           return false;
89             }
90             }
91 0 0         if(not defined $price)
    0          
92             {
93 0           $price = $self->price;
94             }
95             elsif(not $price =~ /^\d+$/)
96             {
97 0           $self->_appendError('Illegal price value. "'.$price.'"');
98 0           return false;
99             }
100 0           $self->_recipient($recipient);
101 0           return $self->_SMSSend($message,$price);
102             }
103              
104             # --- INTERNAL METHODS AND ATTRIBUTES ---
105              
106             has '_recipient' => (
107             isa => 'Str',
108             is => 'rw',
109             );
110              
111             has '_prevID' => (
112             isa => 'Str',
113             is => 'rw',
114             );
115              
116             has '_ua' => (
117             isa => 'Object',
118             is => 'ro',
119             lazy => true,
120             default => sub { LWP::UserAgent->new() },
121             );
122              
123             has '_uriE' => (
124             isa => 'Object',
125             is => 'ro',
126             lazy => true,
127             default => sub { URI::Encode->new() },
128             );
129              
130             # Purpose: Construct a unique identifier
131             # Usage: id = this->_buildID();
132             sub _buildID
133             {
134 0     0     my $self = shift;
135 0           my $recipient = $self->_recipient;
136 0           $recipient =~ s/\+/00/;
137 0           my $id;
138             # Should be below 100 chars long, so loop until we get it right
139 0   0       while(!defined $id || length($id) > 99)
140             {
141 0           $id = $recipient.'-'.time().int(rand(10_000)).int(rand(100_000)).'-SMS::CPAGateway-'.$VERSION;
142             }
143 0           $self->_prevID($id);
144 0           return $id;
145             }
146              
147             # Purpose: Get our unique identifier
148             # Usage: id = self->_getID();
149             sub _getID
150             {
151 0     0     my $self = shift;
152 0 0 0       if(
153             (not defined $self->_prevID)
154             or
155             (not $self->_prevID eq $self->id)
156             )
157             {
158 0           return $self->id;
159             }
160 0           $self->id($self->_buildID());
161 0           return $self->id;
162             }
163              
164             sub _appendError
165             {
166 0     0     my $self = shift;
167 0           my $error = shift;
168 0           my $errorList = $self->errors;
169 0 0         if (ref($error))
170             {
171 0           $error = 'Error reply '.$error->{reply}.' when requesting URL '.$error->{URL}.' at '.time();
172             }
173 0           push(@{$errorList},$error);
  0            
174 0           $self->_writeErrors($errorList);
175 0           $self->_setError(true);
176             }
177              
178             # Purpose: Handle errors in requests
179             # Usage: my $ret = $self->_errHandler(URI,reply);
180             #
181             # This method verifies the content of reply and returns true or false.
182             # false meaning that sending failed and we should retry. Errors are
183             # added to the error list via $self->_appendError
184             sub _errHandler
185             {
186 0     0     my $self = shift;
187 0           my $URL = shift;
188 0           my $reply = shift;
189            
190 0           my $content = $reply->content;
191            
192 0 0         if ($content =~ /^\s*ok/i)
    0          
193             {
194             # All is well
195 0           return true;
196             }
197             elsif ($content =~ /^\s*err/i)
198             {
199 0           $self->_appendError( { URL => $URL, reply => $content } );
200 0           return false;
201             }
202             else
203             {
204             # Okay, this server isn't acting properly
205 0           $self->_appendError('The server returned an unknown reply, so I am assuming failure: '.$content);
206 0           return false;
207             }
208             }
209              
210             # Purpose: Construct the request URI and attempt to submit it to the servers
211             # Usage: $ret = $self->_SMSSend(message,$price);
212             sub _SMSSend
213             {
214 0     0     my $self = shift;
215 0           my $message = shift;
216 0           my $price = shift;
217             # Construct URI
218 0           my $URI = '?auth='.$self->_uriE->encode($self->key).
219             '&id='.$self->_uriE->encode($self->_getID).
220             '&from='.$self->_uriE->encode($self->fromNo).
221             '&to='.$self->_uriE->encode($self->_recipient).
222             '&type=text&data='.$self->_uriE->encode($message).
223             '&price='.$price;
224              
225 0           foreach my $host (@{$self->servers})
  0            
226             {
227 0 0         if ($self->_attemptSend($host,$URI))
228             {
229             # We sent it and all went well
230 0           return true;
231             }
232             # If not, we didn't send it and we ought to try the next server
233             }
234             # If we've gotten this far then all servers failed and we have to give up
235 0           $self->_appendError('All servers failed. Giving up.');
236 0           return false;
237             }
238              
239             # Purpose: Attempt to send an SMS
240             # Usage: $ret = $self->_attemptSend(host,URI);
241             sub _attemptSend
242             {
243 0     0     my $self = shift;
244 0           my $host = shift;
245 0           my $URI = shift;
246              
247 0           my $request = $host.'/'.$URI;
248              
249 0           my $reply = $self->_ua->get($request);
250 0 0         if ($reply->is_success())
251             {
252 0           return $self->_errHandler($request,$reply);
253             }
254             else
255             {
256 0           $self->_appendError('Server '.$host.' failed');
257 0           return false;
258             }
259             }
260              
261             __PACKAGE__->meta->make_immutable;
262             1;
263              
264             __END__