File Coverage

blib/lib/Callerid.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             package Callerid;
2              
3 1     1   23615 use 5.006001;
  1         4  
  1         37  
4 1     1   5 use strict;
  1         2  
  1         92  
5 1     1   5 use warnings;
  1         6  
  1         36  
6 1     1   5 use Carp;
  1         2  
  1         229  
7              
8             require Exporter;
9              
10             our @ISA = qw(Exporter);
11              
12             # This allows declaration use Callerid ':all';
13             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
14             # will save memory.
15             our %EXPORT_TAGS = ( 'all' => [ qw(
16             parse_raw_cid_string format_phone_number
17             ) ] );
18              
19             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
20              
21             our @EXPORT = qw(
22            
23             );
24              
25             our $VERSION = '0.04';
26              
27             =pod
28              
29             =head1 NAME
30              
31             Callerid - Perl extension for interpreting raw caller ID information (a la AT#CID=2)
32              
33             =head1 SYNOPSIS
34              
35             use Callerid;
36             my($hex) = "8024010830...";
37              
38             # OO-style
39             my($cid) = new Callerid($hex);
40             print $cid->{name}; # prints callers name
41              
42             -or-
43              
44             # Procedural style
45             my(%cid) = Callerid::parse_raw_cid_string($hex);
46             print $cid{name}; # prints callers name
47            
48             # prints phone number pretty
49             print Callerid::format_phone_number($cid{number});
50              
51             =head1 DESCRIPTION
52              
53             The Callerid module aims to provide a quick and easy method (YMMV) of decoding
54             raw caller ID information as supplied by a modem.
55              
56             This module does not talk to modems. It also does not mangle input. If you
57             don't supply a hex string of the right format then you lose.
58              
59             =head2 Methods
60              
61             =head3 C<< $cid = Callerid->new() >>
62              
63             =head3 C<< $cid = Callerid->new($string_of_hex) >>
64              
65             =over 4
66              
67             Returns a newly created C<< Callerid >> object. If you supply it with a hex
68             string then (assuming it's not malformed) it will populate data fields in the
69             new C<< Callerid >> object appropriately.
70              
71             Currently the (public) fields provided are: name number hour minute month day.
72              
73             =back
74              
75             =head3 C<< $cid->parse_raw_cid_string($string_of_hex) >>
76              
77             =head3 C<< %info = Callerid::parse_raw_cid_string($string_of_hex) >>
78              
79             =over 4
80              
81             When called as an object method C<< parse_raw_cid_string() >> will fill the
82             objects data fields with appropriate information. When called as a class method
83             C<< parse_raw_cid_string() >> will return a hash with the same data fields.
84              
85             =back
86              
87             =head3 C<< $pretty_number = $cid->format_phone_number() >>
88              
89             =head3 C<< $pretty_number = Callerid::format_phone_number($number) >>
90              
91             =over 4
92              
93             When called as an object method, C<< format_phone_number() >> will return the
94             object's number field formatted pretty. When called as a class method,
95             C<< format_phone_number() >> will take a single argument and will do the same
96             thing.
97              
98             "Formatted pretty" means 7-digit phone numbers become ###-####, 10-digit numbers
99             become ###-###-####, 11-digit numbers become #-###-###-#### and everything else is passed through unchanged.
100              
101             =back
102              
103             =head2 EXPORT
104              
105             None by default.
106              
107             =head1 SAMPLE CODE
108              
109             use Callerid;
110              
111             # read in a list of raw caller ID codes
112             while(<>) {
113             chomp;
114             s/#.*$//; # remove comments
115             s/^\s*//; # remove leading spaces
116             s/\s*$//; # remove trailing spaces
117             next unless $_; # skip if there's nothing left
118              
119             my($cid);
120             eval {
121             $cid = new Callerid($_);
122             };
123              
124             if($@) {
125             warn "error parsing $_: $@";
126             } else {
127             printf "%s parses to name=%s number=%s date=%02d/%02d time=%02d:%02d\n",
128             $_,
129             $cid->{name},
130             $cid->format_phone_number(),
131             $cid->{month},
132             $cid->{day},
133             $cid->{hour},
134             $cid->{minute};
135             }
136             }
137              
138             =head1 SEE ALSO
139              
140             L to do I/O with a modem.
141              
142             Modem command set for putting modem into caller ID mode
143              
144             =head1 AUTHOR
145              
146             Mike Carr, Emcarr@pachogrande.comE
147              
148             =head1 COPYRIGHT AND LICENSE
149              
150             Copyright (C) 2004 by Mike Carr
151              
152             This library is free software; you can redistribute it and/or modify
153             it under the same terms as Perl itself, either Perl version 5.8.4 or,
154             at your option, any later version of Perl 5 you may have available.
155              
156             =cut
157              
158 1     1   884 use fields qw(_raw_cid_string name number hour minute month day);
  1         1727  
  1         5  
159              
160             sub new {
161             my Callerid $self = shift;
162             unless( ref $self ) {
163             $self = fields::new($self);
164             }
165             my($raw_cid_string) = shift;
166             if($raw_cid_string) {
167             eval {
168             my(%results);
169             my($href) = $self->parse_raw_cid_string($raw_cid_string);
170             if(ref $href) {
171             %results = %{ $href };
172             }
173             for my $field qw(name number hour minute month day) {
174             $self->{$field} = $results{$field} if($results{$field});
175             }
176             $self->{_raw_cid_string} = $raw_cid_string;
177             };
178             if($@) {
179             warn $@;
180             return $self->new();
181             } else {
182             return $self;
183             }
184             } else {
185             $self->{_raw_cid_string} = "";
186             for my $field qw(name number hour minute month day) {
187             $self->{$field} = "";
188             }
189             }
190             return $self;
191             }
192              
193             sub parse_raw_cid_string(;$$) {
194             my($_arg) = shift;
195             my($self);
196             my($c);
197             if(ref $_arg) {
198             $self = $_arg;
199             $c = shift;
200             } else {
201             $self = {};
202             $c = $_arg;
203             }
204              
205             unless($c) {
206             if($self->{_raw_cid_string}) {
207             $c = $self->{_raw_cid_string};
208             } else {
209             warn( __PACKAGE__ . "::parse_raw_cid_string() can't find a string to parse");
210             return { };
211             }
212             }
213            
214             chomp $c;
215            
216             unless($c =~ /^[0-9a-fA-F]*$/) {
217             croak(__PACKAGE__ . "::parse_raw_cid_string() can't find a valid string to parse");
218             }
219              
220            
221             my(@c) = split //, $c; # break each character of the line into the array @c
222             # die "nope" unless ($#c == 77);
223              
224             my($month, $day, $hour, $minute, $name, $number);
225             $month = (sprintf "%d", $c[9] . $c[11]) if($#c > 11);
226             $day = (sprintf "%d", $c[13] . $c[15]) if($#c > 15);
227             $hour = (sprintf "%d", $c[17] . $c[19]) if($#c > 19);
228             $minute = (sprintf "%d", $c[21] . $c[23]) if($#c > 23);
229             {{{ # name calculation
230             if($#c > 57) {
231             my $hex = join('', @c[28 .. 57]); # form a substring from the array
232             if($hex =~ /^(.*?)03/) {
233             $hex = $1;
234             }
235             my @parts = unpack("a2" x (length($hex)/2), $hex); # break the substring 0x00's
236             for my $p (@parts) { # go through the list of digits
237             # printf "%s becomes %c\n", $p, hex($p);
238             $name .= sprintf "%c", hex($p); # and convert each to a character
239             }
240             } else {
241             if($c =~ /..0401/) {
242             $name = "*PRIVATE";
243             $number = "";
244             } else {
245             $name = "ERROR"; warn "error parsing name, too short, yet not private";
246             }
247             }
248             }}}
249             {{{ # number calculation
250             if($c =~ /..0401/) {
251             $number = "";
252             } else {
253             for my $n qw(11 7) {
254             if($c =~ m/((3\d){$n})..$/) {
255             my($three_coded) = $1;
256             my(@three_coded) = split //, $three_coded;
257             my($toggle) = 1;
258             my(@number) = grep { $toggle = !($toggle) } @three_coded;
259             $number ||= join('', @number);
260             }
261             }
262            
263             unless($number) { warn("didn't parse number, doesn't match as private"); }
264             }
265             }}}
266              
267             # Reset all fields that we should be filling. aka "sanity checking"
268             for my $field qw(name number month day hour minute _raw_cid_string) {
269             $self->{$field} = "";
270             }
271              
272             $self->{name} = $name if $name;
273             $self->{number} = $number if($number || $name =~ /^\*PRIVATE$/);
274             $self->{month} = $month if $month;
275             $self->{day} = $day if $day;
276             $self->{hour} = $hour if $hour;
277             $self->{minute} = $minute if $minute;
278             $self->{_raw_cid_string} = $c;
279              
280             return $self;
281             }
282              
283             sub format_phone_number(;$$) {
284             my($_arg) = shift;
285             my($self);
286             my($number);
287              
288             if(ref $_arg) {
289             $self = $_arg;
290             if(@_) {
291             $number = shift;
292             } else {
293             $number = $self->{number};
294             }
295             } else {
296             $self = { };
297             $number = $_arg;
298             }
299              
300             if($number =~ /^(\d{3})(\d{4})$/) {
301             return qq($1-$2);
302             } elsif($number =~ /^(\d{3})(\d{3})(\d{4})$/) {
303             return qq($1-$2-$3);
304             } elsif($number =~ /^(1)(\d{3})(\d{3})(\d{4})$/) {
305             return qq($1-$2-$3-$4);
306             } else {
307             return $number;
308             }
309              
310             }
311              
312             1;
313              
314             # vim: set ts=2: