File Coverage

blib/lib/Net/SMS/2Way.pm
Criterion Covered Total %
statement 12 185 6.4
branch 0 88 0.0
condition 0 23 0.0
subroutine 4 14 28.5
pod 6 9 66.6
total 22 319 6.9


line stmt bran cond sub pod time code
1             package Net::SMS::2Way;
2              
3 1     1   21475 use 5.0;
  1         4  
  1         3229  
4 1     1   9 use strict;
  1         2  
  1         41  
5 1     1   1184 use LWP::UserAgent;
  1         50588  
  1         40  
6 1     1   9 use HTTP::Request;
  1         2  
  1         2773  
7              
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11              
12             our $VERSION = '0.08'; # 21 Jan. 2009
13              
14             our $urls = {
15             ZA => 'http://bulksms.2way.co.za:5567',
16             UK => 'http://www.bulksms.co.uk:5567',
17             ES => 'http://bulksms.com.es:5567',
18             DE => 'http://bulksms.de:5567',
19             US => 'http://usa.bulksms.com:5567',
20             INT => 'http://bulksms.vsms.net:5567'
21             };
22              
23             our $default_options = {
24             country => 'ZA',
25             quote => 0,
26             };
27              
28             our @bulksms_send_options = qw(
29             sender msg_class dca want_report routing_group source_id repliable strip_dup_recipients
30             stop_dup_id send_time send_time_unixtime scheduling_description test_always_succeed
31             test_always_fail allow_concat_text_sms oncat_text_sms_max_parts
32             );
33              
34             our $bulk_sms_send_defaults = {};
35              
36             our @mandatory_options = qw(username password);
37              
38             sub new
39             {
40 0     0 0   my $class = shift @_;
41 0           my $ref = shift @_;
42 0           my $error;
43            
44 0           $ref->{script} = $0;
45            
46             # Get settings from config file
47 0 0         my $cfg_ref = _parse_config($ref->{config}) if $ref->{config};
48            
49             # Merge settings from config so that the config file settings are overwritten
50 0           foreach my $key (keys(%$cfg_ref))
51             {
52 0 0         if ($ref->{$key} eq '')
53             {
54 0           $ref->{$key} = $cfg_ref->{$key};
55             }
56             }
57            
58             # Add defaults
59 0           foreach my $key (%$default_options)
60             {
61 0 0         if ($ref->{$key} eq '')
62             {
63 0           $ref->{$key} = $default_options->{$key};
64             }
65             }
66            
67             # Add BulkSMS defaults
68 0           foreach my $key (%$bulk_sms_send_defaults)
69             {
70 0 0         if ($ref->{$key} eq '')
71             {
72 0           $ref->{$key} = $bulk_sms_send_defaults->{$key};
73             }
74             }
75            
76             #Check mandatory options
77 0           foreach my $key (@mandatory_options)
78             {
79 0 0         if ($ref->{$key} eq '')
80             {
81 0           $error .= "Option '$key' does not have a value.\n";
82             }
83             }
84            
85             # Is there a proxy ?
86 0 0         $ENV{http_proxy} = $ref->{http_proxy} if ($ref->{http_proxy} ne '');
87            
88             # Wich base URL to use ?
89 0           $ref->{base_url} = $urls->{$ref->{country}};
90            
91 0 0         return 0 if $error;
92            
93 0           bless ( $ref, $class );
94             }
95              
96             sub send_batch
97             {
98 0     0 1   my $this = shift @_;
99 0           my $data = shift @_;
100 0           my %data = %$data;
101 0           my $csv_data = "msisdn,message\n";
102 0           my $recipient_count = 0;
103            
104 0           foreach my $number ( keys( %data ) )
105             {
106 0           $number =~ s/\D//g; # strip all non-digits
107 0 0         next if $number !~ /\d/;
108            
109 0 0 0       next if ($this->{sa_numbers_only} > 0 && $number !~ /^(27|0[78])/);
110              
111 0 0 0       if ($this->{sa_numbers_only} > 0 && $number =~ /^(27[78])/)
112             {
113             # SA cell prefixes as per http://en.wikipedia.org/wiki/Telephone_numbers_in_South_Africa
114 0           $number =~ s/^0(82|83|84|72|73|74|76|78|79)(\d+)/27$1$2/;
115             }
116            
117 0 0         if ($number eq '')
118             {
119 0           $this->{error} = "One of the recipient numbers is invalid: $number";
120 0           return 0;
121             }
122            
123 0           $csv_data .= "\"$number\",\"" . $data{$number} . "\"\n";
124 0           $recipient_count++;
125             }
126            
127 0 0         return 0 unless $recipient_count;
128            
129 0           my $url = $this->{base_url} . '/eapi/submission/send_batch/1/1.0';
130 0           my $args = { batch_data => $csv_data };
131 0           my @tmp;
132            
133 0 0         if (! (@tmp = $this->http_post($url, $args)) )
134             {
135 0           $this->send_to_log("WARN: Could not do http_post() for send_batch(): " . $this->{error});
136 0           return 0;
137             }
138              
139 0           my $retval = pop( @tmp );
140 0           my $log_mesg = "SMS batch sent. Results: $retval";
141              
142 0 0         $this->send_to_log( $log_mesg ) if $this->{verbose} > 0;
143            
144 0           return $retval;
145             }
146              
147             sub send_sms
148             {
149 0     0 1   my $this = shift @_;
150 0           my $message = shift @_;
151 0           my @recipients = @_;
152            
153             # un-comment at your convenience
154             #if (!$message) {
155             # $this->{error} = "Message is empty!\n";
156             # return 0;
157             #}
158              
159 0           foreach my $number (@recipients)
160             {
161 0           $number =~ s/\D//g; # strip all non-digits
162 0 0         next if $number !~ /\d/;
163            
164 0 0 0       next if ($this->{sa_numbers_only} > 0 && $number !~ /^(27|0[78])/);
165            
166 0 0 0       if ($this->{sa_numbers_only} > 0 && $number =~ /^(27[78])/)
167             {
168             # SA mobile prefixes as per http://en.wikipedia.org/wiki/Telephone_numbers_in_South_Africa
169 0           $number =~ s/^0(82|83|84|72|73|74|76|78|79)(\d+)/27$1$2/;
170             }
171            
172 0 0         if ($number eq '')
173             {
174 0           $this->{error} = "One of the recipient numbers is invalid: $number";
175 0           return 0;
176             }
177            
178             }
179            
180 0           my $args;
181            
182             # Extract all the BulkSMS options
183 0           foreach my $option (@bulksms_send_options)
184             {
185 0 0 0       if ( exists($this->{$option}) && $this->{$option} ne '' )
186             {
187 0           $args->{$option} = $this->{$option};
188             }
189             }
190            
191 0           $args->{msisdn} = join(',', @recipients);
192 0           $args->{message} = $message;
193            
194 0           my @tmp;
195 0           my $url = $this->{base_url} . '/eapi/submission/send_sms/2/2.0';
196              
197 0 0         if ( $this->{quote} > 0 )
198             {
199             # This is a hack to get a quote on much credits an SMS will cost
200 0           $url = $this->{base_url} . '/eapi/submission/quote_sms/2/2.0';
201             }
202              
203 0 0         if (! (@tmp = $this->http_post($url, $args)) )
204             {
205 0           $this->send_to_log("WARN: Could not do http_post() for send_sms(): " . $this->{error});
206 0           return 0;
207             }
208              
209 0 0         return pop( @tmp ) if $this->{quote} > 0; # ... for the quote_sms hack
210              
211 0           my $retval = pop( @tmp );
212 0           my $log_mesg = "SMS sent to " . join(',', @recipients) . ". Results: $retval";
213              
214 0 0         $this->send_to_log( $log_mesg ) if $this->{verbose} > 0;
215            
216 0           return $retval;
217             }
218              
219             sub quote_sms
220             {
221 0     0 1   my $this = shift @_;
222 0           my $message = shift @_;
223 0           my @recipients = @_;
224              
225 0           $this->{quote} = 1;
226              
227 0           my $quotation = $this->send_sms($message, @recipients);
228              
229 0           $this->{quote} = 0;
230              
231 0           return $quotation;
232             }
233              
234             sub get_credits
235             {
236 0     0 1   my $this = shift @_;
237 0           my $url = $this->{base_url} . '/eapi/user/get_credits/1/1.1';
238 0           my @tmp = $this->http_post($url);
239              
240 0   0       my ($status, $balance) = split /\|/, pop(@tmp)
241             || ($this->send_to_log("WARN: Could not do http_post() for get_credits(): " . $this->{error}) && return -1);
242              
243 0           return $balance;
244             }
245              
246             sub get_inbox
247             {
248 0     0 1   my $this = shift @_;
249 0           my $last_retrieved_id = shift @_;
250 0           my @tmp;
251              
252 0 0         if ( !defined($last_retrieved_id) )
253             {
254 0           $last_retrieved_id = 0;
255             }
256            
257 0           my $url = $this->{base_url} . '/eapi/reception/get_inbox/1/1.0';
258 0           my $args = {last_retrieved_id => $last_retrieved_id};
259              
260 0 0         if (! (@tmp = $this->http_post($url, $args)) )
261             {
262 0           $this->send_to_log("WARN: Could not do http_post() for get_inbox(): " . $this->{error});
263 0           return 0;
264             }
265              
266 0           my $end_of_headers_marker = 0;
267 0           my @results;
268              
269 0           foreach my $line (@tmp)
270             {
271 0 0         $end_of_headers_marker = 1 if $line =~ /^$/;
272 0 0         next if $line =~ /^$/;
273 0 0         push (@results, $line) if $end_of_headers_marker == 1;
274             }
275              
276 0           return @results;
277             }
278              
279             sub get_report
280             {
281 0     0 1   my $this = shift @_;
282 0           my $batch_id = shift @_;
283 0           my @tmp;
284            
285 0 0         if ( !$batch_id )
286             {
287 0           $this->{error} = "batch_id was not specified\n";
288 0           return 0;
289             }
290            
291 0           my $url = $this->{base_url} .= '/eapi/status_reports/get_report/2/2.0?';
292 0           my $args = {batch_id => $batch_id, optional_fields => 'body,completed_time,created_time,credits,origin_address,source_id'};
293              
294 0 0         if (! (@tmp = $this->http_post($url, $args)) )
295             {
296 0           $this->send_to_log( "WARN: Could not do http_post() for get_report(): " . $this->{error} );
297 0           return 0;
298             }
299              
300 0           my $end_of_headers_marker = 0;
301 0           my @results;
302              
303 0           foreach my $line (@tmp)
304             {
305 0 0         $end_of_headers_marker = 1 if $line =~ /^$/;
306 0 0         next if $line =~ /^$/;
307 0 0         push (@results, $line) if $end_of_headers_marker == 1;
308             }
309            
310 0           return @results;
311             }
312              
313             sub http_post
314             {
315 0     0 0   my $this = shift @_;
316 0           my $url = shift @_;
317 0           my $args = shift @_;
318            
319 0   0       my $timeout = $this->{timeout} || 30;
320            
321 0           my $uagent = LWP::UserAgent->new(timeout => $timeout);
322              
323 0 0 0       if( exists($this->{http_proxy}) && $this->{http_proxy} ne '' )
324             {
325 0           $uagent->proxy(['http'], $this->{http_proxy});
326             }
327            
328 0           my $request = HTTP::Request->new(POST => $url);
329 0           $request->content_type('application/x-www-form-urlencoded');
330            
331 0           my $content = 'username=' . $this->{username} . '&password=' . $this->{password};
332            
333 0           foreach my $arg (keys(%$args))
334             {
335 0           $content .= '&' . $arg . '=' . $args->{$arg};
336             }
337            
338 0           $request->content($content);
339            
340 0 0         $this->send_to_log("INFO: URL=$url content=$content") if $this->{verbose} > 0;
341            
342 0           my $response = $uagent->request($request);
343            
344 0 0         if ($response->is_success)
    0          
345             {
346 0           my @tmp = split( /\n/, $response->as_string );
347 0           return @tmp;
348             }
349             elsif ($response->is_error)
350             {
351 0           $this->{error} = $response->code . ':' . $response->message . "\n";
352 0           return 0;
353             }
354             else
355             {
356 0           $this->{error} = $response->code . ':' . $response->message . ':' . $response->content . "\n";
357 0           return 0;
358             }
359             }
360              
361             sub send_to_log
362             {
363 0     0 0   my $this = shift @_;
364 0           my $message = shift @_;
365            
366 0           chomp($message);
367            
368 0 0         if ($this->{logfile} == -1)
369             {
370 0           return 1;
371             }
372            
373 0 0         if ($this->{logfile} eq '')
374             {
375 0           $this->{logfile} = "$0.log";
376             }
377            
378 0 0         open (LGFH, ">>".$this->{logfile}) || die "ERROR: Could not open " . $this->{logfile} . ": $!\n";
379            
380 0           print LGFH scalar(localtime()) . " - $message - $0\n";
381            
382 0           close (LGFH);
383             }
384              
385             sub _parse_config
386             {
387 0     0     my $file = shift @_;
388 0           my $cfg_ref;
389            
390 0 0         open (CFG, $file) || die "ERROR: Could not open $file: $!\n";
391            
392 0           while ()
393             {
394 0           chomp;
395            
396 0 0         next if /^\s+$/; # Ignore lines with just whitespace...
397 0 0         next if /^$/; # blank lines...
398 0 0         next if /^#/; # and lines that start with a comment.
399            
400 0           s/#.*//; # Strip away all comments
401            
402 0           s/^\s+//; # Remove leading...
403 0           s/\s+$//; # ...and trailing whitespace
404            
405 0           s/\s*=\s*/=/;
406            
407 0           my ($var, $val) = split /=/;
408 0           $cfg_ref->{$var} = $val;
409             }
410            
411 0           close (CFG);
412            
413 0           return $cfg_ref;
414             }
415              
416             1;
417              
418             __END__