| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Net::SMS::BulkSMS; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 69747 | use 5.006; | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 40 |  | 
| 4 | 1 |  |  | 1 |  | 7 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 36 |  | 
| 5 | 1 |  |  | 1 |  | 5 | use warnings; | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 56 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | our $VERSION = '1.02'; | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 1 |  |  | 1 |  | 5 | use Carp; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 384 |  | 
| 10 | 1 |  |  | 1 |  | 2290 | use HTTP::Request::Common; | 
|  | 1 |  |  |  |  | 34567 |  | 
|  | 1 |  |  |  |  | 101 |  | 
| 11 | 1 |  |  | 1 |  | 1208 | use LWP::UserAgent; | 
|  | 1 |  |  |  |  | 26847 |  | 
|  | 1 |  |  |  |  | 44 |  | 
| 12 | 1 |  |  | 1 |  | 1043 | use MIME::Base64; | 
|  | 1 |  |  |  |  | 968 |  | 
|  | 1 |  |  |  |  | 96 |  | 
| 13 | 1 |  |  | 1 |  | 1108 | use POSIX qw(strftime); | 
|  | 1 |  |  |  |  | 7708 |  | 
|  | 1 |  |  |  |  | 9 |  | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | sub new | 
| 17 |  |  |  |  |  |  | { | 
| 18 | 0 |  |  | 0 | 1 |  | my $class = shift; | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 0 |  |  |  |  |  | my $this = { | 
| 21 |  |  |  |  |  |  | test				=> 0, | 
| 22 |  |  |  |  |  |  | test_form_url	=> "http://setyoururlhere/cgi-bin/form.pl/", | 
| 23 |  |  |  |  |  |  | username    	=> undef, | 
| 24 |  |  |  |  |  |  | password    	=> undef, | 
| 25 |  |  |  |  |  |  | max_recipients	=> 200, | 
| 26 |  |  |  |  |  |  | num_retries		=> 3, | 
| 27 |  |  |  |  |  |  | signature		=> "", | 
| 28 |  |  |  |  |  |  | signature_datetime	=> 1, | 
| 29 |  |  |  |  |  |  | sender			=> "BulksmsCoUk", | 
| 30 |  |  |  |  |  |  | url         	=> | 
| 31 |  |  |  |  |  |  | { | 
| 32 |  |  |  |  |  |  | base        	=> 'http://www.bulksms.co.uk:7512/eapi/1.0/', | 
| 33 |  |  |  |  |  |  | get_credits 	=> "get_credits.mc", | 
| 34 |  |  |  |  |  |  | send_sms    	=> "send_sms.mc", | 
| 35 |  |  |  |  |  |  | quote_sms   	=> "quote_sms.mc", | 
| 36 |  |  |  |  |  |  | get_report  	=> "get_report.mc", | 
| 37 |  |  |  |  |  |  | phonebook_public_add_member   => "phonebook/public_add_member", | 
| 38 |  |  |  |  |  |  | phonebook_public_remove_member => "phonebook/public_remove_member", | 
| 39 |  |  |  |  |  |  | }, | 
| 40 |  |  |  |  |  |  |  | 
| 41 |  |  |  |  |  |  | @_, | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | max_msglen		=> 160, | 
| 44 |  |  |  |  |  |  | max_senderlen	=> 11, | 
| 45 |  |  |  |  |  |  | max_length		=> 160, | 
| 46 |  |  |  |  |  |  | VERSION			=> "0.1", | 
| 47 |  |  |  |  |  |  | }; | 
| 48 | 0 |  |  |  |  |  | bless($this,$class); | 
| 49 | 0 |  |  |  |  |  | $this->init; | 
| 50 | 0 |  |  |  |  |  | $this; | 
| 51 |  |  |  |  |  |  | } | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | sub init | 
| 54 |  |  |  |  |  |  | { | 
| 55 | 0 |  |  | 0 | 0 |  | my $this = shift; | 
| 56 | 0 | 0 |  |  |  |  | if ( $this->{test} ) | 
| 57 |  |  |  |  |  |  | { | 
| 58 | 0 |  |  |  |  |  | $this->{url}->{base} = $this->{test_form_url}; | 
| 59 | 0 |  |  |  |  |  | $this->{username} = encode_base64("test"); | 
| 60 | 0 |  |  |  |  |  | $this->{password} = encode_base64("1234"); | 
| 61 |  |  |  |  |  |  | } | 
| 62 | 0 | 0 | 0 |  |  |  | confess "need auth info" unless $this->{password} && $this->{username}; | 
| 63 | 0 |  |  |  |  |  | $this->{username} = decode_base64($this->{username}); | 
| 64 | 0 |  |  |  |  |  | $this->{password} = decode_base64($this->{password}); | 
| 65 | 0 |  |  |  |  |  | 1; | 
| 66 |  |  |  |  |  |  | } | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | sub transaction | 
| 69 |  |  |  |  |  |  | { | 
| 70 | 0 |  |  | 0 | 0 |  | my $this = shift; | 
| 71 | 0 | 0 |  |  |  |  | ref($this) or confess; | 
| 72 | 0 |  |  |  |  |  | my %arg = ( txn => undef, username => $this->{username}, password => $this->{password}, @_ ); | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | # construct URL to call web API | 
| 75 | 0 |  |  |  |  |  | my $url = $this->{url}->{base}; | 
| 76 | 0 | 0 |  |  |  |  | confess unless $url; | 
| 77 | 0 | 0 |  |  |  |  | $url .= "/" unless $url =~ m/\/$/; | 
| 78 | 0 | 0 |  |  |  |  | confess "expected txn" unless $arg{txn}; | 
| 79 | 0 |  | 0 |  |  |  | $url .= $this->{url}->{$arg{txn}} || confess "suburl for transaction $arg{txn} not defined"; | 
| 80 | 0 |  |  |  |  |  | delete $arg{txn}; | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | # enforce 11 char limit on sender name/phone | 
| 83 | 0 | 0 |  |  |  |  | if ( defined $arg{sender} ) | 
| 84 |  |  |  |  |  |  | { | 
| 85 | 0 |  |  |  |  |  | my $count = length($arg{sender}); | 
| 86 | 0 |  | 0 |  |  |  | my $max_length = $this->{max_senderlen} || 11; | 
| 87 | 0 | 0 |  |  |  |  | return("Sender length $count too long (over $max_length characters)",0) if $count > $max_length; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # enforce 160 char limit on message length | 
| 91 | 0 | 0 |  |  |  |  | if ( defined $arg{message} ) | 
| 92 |  |  |  |  |  |  | { | 
| 93 | 0 |  |  |  |  |  | $arg{message} =~ s/^\s+//; # trim spaces leading and trailing | 
| 94 | 0 |  |  |  |  |  | $arg{message} =~ s/\s+$//; | 
| 95 | 0 |  |  |  |  |  | my $count = length($arg{message}); | 
| 96 | 0 |  | 0 |  |  |  | my $max_length = $this->{max_msglen} || 160; | 
| 97 | 0 | 0 |  |  |  |  | return("Message length $count too long (over $max_length characters including signature)",0) if $count > $max_length; | 
| 98 |  |  |  |  |  |  | } | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | # special check on phone number array argument if present | 
| 101 | 0 | 0 |  |  |  |  | if ( defined $arg{msisdn} ) | 
| 102 |  |  |  |  |  |  | { | 
| 103 |  |  |  |  |  |  | # strip white space | 
| 104 | 0 |  |  |  |  |  | my $s = $arg{msisdn}; | 
| 105 | 0 |  |  |  |  |  | $s =~ s/\s//g; | 
| 106 | 0 |  |  |  |  |  | $arg{msisdn} = $s; | 
| 107 |  |  |  |  |  |  | # split to individual phone numbers on comma | 
| 108 | 0 |  |  |  |  |  | my @telno = split(/\,/,$s); | 
| 109 | 0 |  |  |  |  |  | $s =~ s/\,//g; # strip commas and check for non-digits | 
| 110 | 0 | 0 |  |  |  |  | return ("Phone number string msisdn contains non-digits: $arg{msisdn}",0) if $s =~ m/\D/; | 
| 111 |  |  |  |  |  |  | # check for duplicate phone numbers | 
| 112 | 0 |  |  |  |  |  | my %h; | 
| 113 | 0 |  |  |  |  |  | for (@telno) { $h{$_}++; } | 
|  | 0 |  |  |  |  |  |  | 
| 114 | 0 |  |  |  |  |  | my @dupno; | 
| 115 | 0 |  |  |  |  |  | for ( keys %h ) | 
| 116 |  |  |  |  |  |  | { | 
| 117 | 0 | 0 |  |  |  |  | push(@dupno,$_) if $h{$_} > 1; | 
| 118 |  |  |  |  |  |  | } | 
| 119 | 0 | 0 |  |  |  |  | return("Phone number string msisdn contains duplicate phone numbers : ".join(",",@dupno)." in $arg{msisdn}",0) if scalar @dupno > 0; | 
| 120 |  |  |  |  |  |  | # check count of phone numbers | 
| 121 | 0 | 0 |  |  |  |  | return("Phone number string msisdn contains no phone numbers: $arg{msisdn}",0) if scalar @telno <= 0; | 
| 122 | 0 |  | 0 |  |  |  | my $max_recipients = $this->{max_recipients} || 0; | 
| 123 | 0 |  |  |  |  |  | my $count = scalar @telno; | 
| 124 | 0 | 0 | 0 |  |  |  | if ( $max_recipients && $count > $max_recipients ) | 
| 125 |  |  |  |  |  |  | { | 
| 126 | 0 |  |  |  |  |  | return("Cannot send more than $max_recipients messages at once ($count phone numbers in list)",0); | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  | } | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | # delete undefined parameters | 
| 131 | 0 |  |  |  |  |  | for ( keys %arg ) | 
| 132 |  |  |  |  |  |  | { | 
| 133 | 0 | 0 |  |  |  |  | delete $arg{$_} unless defined $arg{$_}; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 | 0 |  |  |  |  |  | my $req = POST $url, [ %arg ]; | 
| 137 |  |  |  |  |  |  |  | 
| 138 | 0 |  |  |  |  |  | my $ua = LWP::UserAgent->new; | 
| 139 | 0 |  |  |  |  |  | $ua->env_proxy(); # allow for loading of proxy settings from *_proxy env vars; see "perldoc LWP::UserAgent" for details | 
| 140 | 0 |  |  |  |  |  | my $res = $ua->request($req); | 
| 141 |  |  |  |  |  |  |  | 
| 142 | 0 | 0 |  |  |  |  | if ( $res->is_success ) # web request worked | 
|  |  | 0 |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | { | 
| 144 | 0 |  |  |  |  |  | return ($res->content, 1); | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  | elsif ( $res->is_error ) # web request failed | 
| 147 |  |  |  |  |  |  | { | 
| 148 | 0 |  |  |  |  |  | return ($res->code . ": " . $res->message, 0); | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  | else # redirect or information - should not happen, treat as failure | 
| 151 |  |  |  |  |  |  | { | 
| 152 |  |  |  |  |  |  | return ( | 
| 153 | 0 |  |  |  |  |  | ( "Code: " . $res->code . "\n" | 
| 154 |  |  |  |  |  |  | . "Message: " . $res->message . "\n" | 
| 155 |  |  |  |  |  |  | . "Content: " . $res->content . "\n" ) | 
| 156 |  |  |  |  |  |  | , 0); | 
| 157 |  |  |  |  |  |  | } | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | sub get_credits | 
| 161 |  |  |  |  |  |  | { | 
| 162 | 0 |  |  | 0 | 1 |  | $_[0]->transaction ( txn => "get_credits" ); | 
| 163 |  |  |  |  |  |  | } | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | # send_sms - send an SMS text message | 
| 166 |  |  |  |  |  |  | # | 
| 167 |  |  |  |  |  |  | # required parameters | 
| 168 |  |  |  |  |  |  | # 	message: max 160 chars, 280 for 8bit | 
| 169 |  |  |  |  |  |  | # 	msisdn: comma separated list of recipient phone numbers | 
| 170 |  |  |  |  |  |  | # | 
| 171 |  |  |  |  |  |  | # optional parameters | 
| 172 |  |  |  |  |  |  | # 	sender: sender id (if alphanumeric, max 11 characters). This facility has to be specifically enabled for your account on request. Alphanumeric sender id is a route-specific feature. | 
| 173 |  |  |  |  |  |  | # 	msg_class: currently 0 (flash SMS) or 2 (normal SMS), default 2 | 
| 174 |  |  |  |  |  |  | # 	dca: Data Coding Alphabet: 7bit,8bit or 16bit, default 7bit (normal text message). For 8bit (ringtones,logos) or 16bit (Unicode), a message containing only hexadecimal octets, to a maximum of 280 characters (140 octet pairs), must be submitted. Currently, concatenation is not supported for Unicode messages. 16-bit is a route-specific feature. | 
| 175 |  |  |  |  |  |  | # 	want_report: 0 or 1, default 0 | 
| 176 |  |  |  |  |  |  | # 	cost_route: 1 or 2, default 1 (future functionality - always use 1 for now, if used) | 
| 177 |  |  |  |  |  |  | # 	msg_id: a unique id generated by yourself, to reduce risk of duplicate submissions - future functionality, currently unused. | 
| 178 |  |  |  |  |  |  | # | 
| 179 |  |  |  |  |  |  | # returns | 
| 180 |  |  |  |  |  |  | #	 status_code|status_description|message_id (where message_id is optional, depending on the error) | 
| 181 |  |  |  |  |  |  | # | 
| 182 |  |  |  |  |  |  | # status codes | 
| 183 |  |  |  |  |  |  | # 	0: In progress (a normal message submission, with no error encountered so far). | 
| 184 |  |  |  |  |  |  | # 	22: Internal fatal error | 
| 185 |  |  |  |  |  |  | # 	23: Authentication failure | 
| 186 |  |  |  |  |  |  | # 	24: Data validation failed | 
| 187 |  |  |  |  |  |  | # 	25: You do not have sufficient credits | 
| 188 |  |  |  |  |  |  | # 	26: Upstream credits not available | 
| 189 |  |  |  |  |  |  | # 	27: You have exceeded your daily quota | 
| 190 |  |  |  |  |  |  | # 	28: Upstream quota exceeded | 
| 191 |  |  |  |  |  |  | # 	40: Temporarily unavailable | 
| 192 |  |  |  |  |  |  | # | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | sub base_send_sms | 
| 195 |  |  |  |  |  |  | { | 
| 196 | 0 |  |  | 0 | 0 |  | my $this = shift; | 
| 197 | 0 |  |  |  |  |  | my %arg = ( | 
| 198 |  |  |  |  |  |  | ## mandatory | 
| 199 |  |  |  |  |  |  | message		=> undef, | 
| 200 |  |  |  |  |  |  | msisdn		=> undef, | 
| 201 |  |  |  |  |  |  | ## optional | 
| 202 |  |  |  |  |  |  | sender		=> $this->{sender}, # sender id, alphanumeric, max 11 chars | 
| 203 |  |  |  |  |  |  | msg_class	=> "2", # 2 = normal SMS, 0 = flash SMS | 
| 204 |  |  |  |  |  |  | dca			=> "7bit", # data coding alphabet: 7bit (default), 8bit (ringtones,logos), 16bit (Unicode) | 
| 205 |  |  |  |  |  |  | want_report	=> 1, # 0 or 1 | 
| 206 |  |  |  |  |  |  | cost_route	=> 1, # 1 or 2, default 1, 2 not implemented | 
| 207 |  |  |  |  |  |  | #msg_id		=> our unique id | 
| 208 |  |  |  |  |  |  | ## user | 
| 209 |  |  |  |  |  |  | quote			=> 0, | 
| 210 |  |  |  |  |  |  | @_, | 
| 211 |  |  |  |  |  |  | ); | 
| 212 | 0 | 0 | 0 |  |  |  | confess unless $arg{message} && $arg{msisdn}; | 
| 213 | 0 | 0 |  |  |  |  | my $txn = $arg{quote} ? "quote_sms" : "send_sms"; | 
| 214 | 0 |  |  |  |  |  | $arg{message} .= $this->{signature}; | 
| 215 | 0 |  |  |  |  |  | my $datetime = strftime "%e-%b-%Y %H:%M", localtime(time); | 
| 216 | 0 | 0 |  |  |  |  | $arg{message} .= "\n$datetime" if $this->{signature_datetime}; | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | # call HTTP web request API interface at bulksms | 
| 219 |  |  |  |  |  |  | # on HTTP status code <> 200 we should try resending a few times | 
| 220 | 0 |  |  |  |  |  | my ($webmsg,$webcode); | 
| 221 | 0 |  | 0 |  |  |  | for (1..($this->{num_retries}||3)) | 
| 222 |  |  |  |  |  |  | { | 
| 223 | 0 |  |  |  |  |  | ($webmsg,$webcode) = $this->transaction ( | 
| 224 |  |  |  |  |  |  | txn			=> $txn, | 
| 225 |  |  |  |  |  |  | message		=> $arg{message}, | 
| 226 |  |  |  |  |  |  | msisdn		=> $arg{msisdn}, | 
| 227 |  |  |  |  |  |  | msg_class	=> $arg{msg_class}, | 
| 228 |  |  |  |  |  |  | dca			=> $arg{dca}, | 
| 229 |  |  |  |  |  |  | want_report	=> $arg{want_report}, | 
| 230 |  |  |  |  |  |  | cost_route	=> $arg{cost_route}, | 
| 231 |  |  |  |  |  |  | sender		=> $arg{sender}, | 
| 232 |  |  |  |  |  |  | ); | 
| 233 | 0 | 0 |  |  |  |  | return ($webmsg,1) if $webcode; # web request success | 
| 234 | 0 |  |  |  |  |  | select(undef,undef,undef,0.25); # wait 0.25 seconds | 
| 235 |  |  |  |  |  |  | } | 
| 236 | 0 |  |  |  |  |  | return ($webmsg,0); # web request failed | 
| 237 |  |  |  |  |  |  | } | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | sub send_sms | 
| 240 |  |  |  |  |  |  | { | 
| 241 | 0 |  |  | 0 | 1 |  | my $this = shift; | 
| 242 |  |  |  |  |  |  | # try and send, retry on temporary error (code 40) | 
| 243 |  |  |  |  |  |  | # possibly should retry on 26-28 or warn user about account/quota limit at caller's level | 
| 244 | 0 |  |  |  |  |  | my ($code,$desc,$msg_id); | 
| 245 | 0 |  | 0 |  |  |  | for (1..($this->{num_retries}||3)) | 
| 246 |  |  |  |  |  |  | { | 
| 247 | 0 |  |  |  |  |  | my ($webmsg,$webcode) = $this->base_send_sms ( @_ ); | 
| 248 | 0 | 0 |  |  |  |  | return ($webmsg,0) unless $webcode; # web request failed | 
| 249 | 0 |  |  |  |  |  | ($code,$desc,$msg_id) = split(/\|/,$webmsg,3); | 
| 250 | 0 |  | 0 |  |  |  | $desc ||= ""; | 
| 251 | 0 | 0 |  |  |  |  | return ("$msg_id",1) if $code eq "0"; # code 0 == in progress (successful) | 
| 252 | 0 | 0 |  |  |  |  | return ("$code: $desc",0) if $code ne "40"; # outright failure, except 40 which is temporary | 
| 253 | 0 |  |  |  |  |  | select(undef,undef,undef,0.25); # wait 0.25 seconds before retry | 
| 254 |  |  |  |  |  |  | } | 
| 255 | 0 |  |  |  |  |  | return ("$code: $desc",0); # failure | 
| 256 |  |  |  |  |  |  | } | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | sub quote_sms | 
| 259 |  |  |  |  |  |  | { | 
| 260 | 0 |  |  | 0 | 1 |  | my $this = shift; | 
| 261 | 0 |  |  |  |  |  | my ($webmsg,$webcode) = $this->base_send_sms ( @_, quote => 1 ); | 
| 262 | 0 | 0 |  |  |  |  | return ($webmsg,0) unless $webcode; # web request failed | 
| 263 | 0 |  |  |  |  |  | my ($code,$desc,$quote_total) = split(/\|/,$webmsg,3); | 
| 264 | 0 |  | 0 |  |  |  | $desc ||= ""; | 
| 265 | 0 | 0 |  |  |  |  | return ("$quote_total",1) if $code eq "1000"; # code 1000 == successful quotation | 
| 266 | 0 |  |  |  |  |  | return ("$code: $desc",0); # failure | 
| 267 |  |  |  |  |  |  | } | 
| 268 |  |  |  |  |  |  |  | 
| 269 |  |  |  |  |  |  | # get_report - report status of a sent SMS text message | 
| 270 |  |  |  |  |  |  | # | 
| 271 |  |  |  |  |  |  | # required parameters | 
| 272 |  |  |  |  |  |  | # 	msg_id: message id return by send_sms | 
| 273 |  |  |  |  |  |  | # optional parameter | 
| 274 |  |  |  |  |  |  | # 	msisdn: supply if querying the status of a single recipient. | 
| 275 |  |  |  |  |  |  | # | 
| 276 |  |  |  |  |  |  | # returns on failure | 
| 277 |  |  |  |  |  |  | # 	desc: "request_status_code: desc" formatted request status code and description | 
| 278 |  |  |  |  |  |  | # 	code: 0 | 
| 279 |  |  |  |  |  |  | # 	result: undefined | 
| 280 |  |  |  |  |  |  | # | 
| 281 |  |  |  |  |  |  | # 	possible request status codes | 
| 282 |  |  |  |  |  |  | # 		23: Authentication failure | 
| 283 |  |  |  |  |  |  | # 		24: Data validation failed | 
| 284 |  |  |  |  |  |  | # 		1001: Error - message not found (msg_id incorrect) | 
| 285 |  |  |  |  |  |  | # | 
| 286 |  |  |  |  |  |  | # returns on success | 
| 287 |  |  |  |  |  |  | # 	desc: "request status code: description", code 1000, description | 
| 288 |  |  |  |  |  |  | # 				"Results to follow\n" followed by one or more newline-separated items in the format: | 
| 289 |  |  |  |  |  |  | # 				"msisdn|status_code|status_description" | 
| 290 |  |  |  |  |  |  | # 	code: 1 | 
| 291 |  |  |  |  |  |  | # 	result: pointer to results hash indexed by msisdn (phone number), | 
| 292 |  |  |  |  |  |  | # 		each entry a pointer to hash of {code,desc} | 
| 293 |  |  |  |  |  |  | # | 
| 294 |  |  |  |  |  |  | # status codes and descriptions: | 
| 295 |  |  |  |  |  |  | # | 
| 296 |  |  |  |  |  |  | # 	0: In progress (a normal message submission, with no error encountered so far). | 
| 297 |  |  |  |  |  |  | # 	10: Delivered upstream | 
| 298 |  |  |  |  |  |  | # 	11: Delivered to mobile | 
| 299 |  |  |  |  |  |  | # 	22: Internal fatal error | 
| 300 |  |  |  |  |  |  | # 	23: Authentication failure | 
| 301 |  |  |  |  |  |  | # 	24: Data validation failed | 
| 302 |  |  |  |  |  |  | # 	25: You do not have sufficient credits | 
| 303 |  |  |  |  |  |  | # 	26: Upstream credits not available | 
| 304 |  |  |  |  |  |  | # 	27: You have exceeded your daily quota | 
| 305 |  |  |  |  |  |  | # 	28: Upstream quota exceeded | 
| 306 |  |  |  |  |  |  | # 	29: Message sending cancelled | 
| 307 |  |  |  |  |  |  | # 	30: Test complete (you should never see this) | 
| 308 |  |  |  |  |  |  | # 	40: Temporarily unavailable | 
| 309 |  |  |  |  |  |  | # 	50: Delivery failed - generic failure | 
| 310 |  |  |  |  |  |  | # 	51: Delivery to phone failed | 
| 311 |  |  |  |  |  |  | # 	52: Delivery to network failed | 
| 312 |  |  |  |  |  |  | # 	60: Transient upstream failure (transient) | 
| 313 |  |  |  |  |  |  | # 	61: Upstream status update (transient) | 
| 314 |  |  |  |  |  |  | # 	62: Upstream cancel failed (transient) | 
| 315 |  |  |  |  |  |  | # 	70: Unknown upstream status | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | sub get_report | 
| 318 |  |  |  |  |  |  | { | 
| 319 | 0 |  |  | 0 | 1 |  | my $this = shift; | 
| 320 | 0 |  |  |  |  |  | my %arg = ( msg_id => undef, msisdn => undef, @_ ); | 
| 321 | 0 | 0 |  |  |  |  | confess unless defined $arg{msg_id}; | 
| 322 | 0 |  |  |  |  |  | my ($webmsg,$webcode) = $this->transaction ( txn => "get_report", msg_id => $arg{msg_id}, msisdn => $arg{msisdn} ); | 
| 323 | 0 | 0 |  |  |  |  | return ($webmsg,0,undef) unless $webcode; # web request failed | 
| 324 |  |  |  |  |  |  | # check bulksms API return | 
| 325 | 0 |  |  |  |  |  | my ($code,$desc) = split(/\|/,$webmsg,2); | 
| 326 | 0 |  | 0 |  |  |  | $desc ||= ""; | 
| 327 | 0 | 0 |  |  |  |  | return ("$code: $desc",0,undef) if $code ne "1000"; # failure (1001,23,24) (code 1000 = success) | 
| 328 |  |  |  |  |  |  | # API returns on success a string containing | 
| 329 |  |  |  |  |  |  | #		1000|Results to follow\n Followed by one or more newline-separated items in the format: | 
| 330 |  |  |  |  |  |  | #		msisdn|status_code|status_description | 
| 331 |  |  |  |  |  |  | # build result hash from this | 
| 332 | 0 |  |  |  |  |  | my @l = split(/\n/,$desc); | 
| 333 | 0 |  |  |  |  |  | shift @l; # remove "Results to follow" first line | 
| 334 | 0 |  |  |  |  |  | my %h; | 
| 335 | 0 |  |  |  |  |  | for (@l) | 
| 336 |  |  |  |  |  |  | { | 
| 337 | 0 |  |  |  |  |  | my ($msisdn,$status_code,$status_desc) = split(/\|/,$_); | 
| 338 | 0 |  |  |  |  |  | $h{$msisdn} = { code => $status_code, desc => $status_desc }; | 
| 339 |  |  |  |  |  |  | } | 
| 340 | 0 |  |  |  |  |  | return ("$code: $desc",1,\%h); # success | 
| 341 |  |  |  |  |  |  | } | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | sub phonebook_public_add_member | 
| 344 |  |  |  |  |  |  | { | 
| 345 | 0 |  |  | 0 | 0 |  | confess "not implemented"; | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | sub phonebook_public_remove_member | 
| 349 |  |  |  |  |  |  | { | 
| 350 | 0 |  |  | 0 | 0 |  | confess "not implemented"; | 
| 351 |  |  |  |  |  |  | } | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | 1; | 
| 355 |  |  |  |  |  |  | __END__ |