#!/usr/bin/perl -w # # GSM phone toy # # actual functionality changes every time I edit it. handle with care. # use Device::SerialPort; use strict; use lib $ENV{HOME} . "/src/perl"; use GSM::SMS::PDU; use POSIX; use Data::Dumper; use Mail::Folder; use Mail::Folder::Mbox; use Mail::Internet; use Getopt::Long; use Text::CSV_XS; use Date::Parse; use MyVodafone; use Time::Local; my %msgnum; # tie this, maybe my ( $firstbill, $lastbill ) = ( "first", "" ); # hack if ( open( LAST, "<$ENV{HOME}/.vodafone/lastsms" )) { $msgnum{0} = ; chomp( $msgnum{0} ); print STDERR "Loaded date " . scalar( localtime( $msgnum{0} )) . "\n" if $ENV{DEBUG}; } # Load up login data my %config; if ( open( FILE, "<$ENV{HOME}/.vodafone/myvoda" )) { while (my $line = ) { chomp( $line ); next if $line =~ /^([;#].*|\s*)$/; # skip blanks + comments if ( $line =~ /^\[(.*)\]\s*$/ ) { next; } my ( $option, $value ) = split( /\s*=\s*/, $line, 2 ); if ( defined( $option ) and defined( $value )) { $config{$option} = $value; } } } package PhoneBookEntry; sub new { my ( $ref, $name, $num, $type ) = @_; my $entry = bless { 'name' => undef, 'number' => undef, 'type' => undef }, $ref; $entry->name( $name ); $entry->number( $num ); $entry->type( $type ); $entry; } sub name { my ( $ref, $name ) = @_; if ( defined( $name )) { # fixme Error check } $name ? $ref->{'name'} = $name : $ref->{'name'}; } sub number { my ( $ref, $num ) = @_; if ( defined( $num )) { if ( $num !~ /^[0-9\+]+$/ ) { $^W and warn "$num is an invalid phone number.\n"; return; } } $num ? $ref->{'number'} = $num : $ref->{'number'}; } my %types = ( "129" => "unknown", "161" => "national", "145" => "international" ); sub type { my ( $ref, $type ) = @_; if ( defined( $type )) { # 'set' operation if ( !grep /$type/, %types ) { $^W and warn "Unknown type $type\n"; $type = "unknown"; } else { $type = $type; } } else { $type = $ref->{ 'type' } || "unknown"; } # convert from number to word if necessary if ( defined( $types{ $type })) { $type = $types{ $type }; } # don't like unknowns if ( $type eq "unknown" and defined( $ref->{'number'} )) { $ref->{'number'} =~ /\+/ ? $type = "international" : $type = "national"; } $ref->{'type'} = $type; # every day is a "set" day :) $type; } package main; my $debug = $ENV{DEBUG}||0; my $vod = new MyVodafone( debug => $debug ); GetOptions( "debug!" => \$debug ) or die $!; my $port; my $LOCKFILE; my @PhoneBook; my %PhoneBook; my %Lockables; my %Lockable; my @PhoneBookSources; my ( $PowerSource, $BatteryLevel, $Status, $RSSI, $BER, $PhoneBookStorage ); my $reply; sub openphone { my $device = shift; my $origdev = $device; # for errors die "Please specify the port the phone is on!" unless $device; $device =~ s{^/dev/}{}; $device =~ m{/} && die "too many / in $origdev\n"; $LOCKFILE = "/var/tmp/LCK..$device"; # stat the lockfile, open it, check the process ID, check if # the process is still running, nuke the lockfile if it's not. if ( -f $LOCKFILE ) { if ( open( LOCKFILE, "<$LOCKFILE" )) { my $pid = ; chomp $pid; if ( kill 0, $pid ) { # process still running die "$origdev is locked by process $pid"; } close( LOCKFILE ); unlink( $LOCKFILE ); } else { die "Can't open lockfile for $origdev: $!"; } } $port = new Device::SerialPort( "/dev/$device", 1, $LOCKFILE ); if ( !$port ) { die "Failed to open port: $!\n"; } # now set up the port $port->baudrate( 115200 ); # actually virtual speed $port->parity( "none" ); $port->databits( 8 ); $port->stopbits( 1 ); $port->handshake( "rts" ); $port->alias( "phone" ); $port; } sub closephone { if ( ref( $port )) { $port->close; } unlink $LOCKFILE; undef $port; } # used for a number of things my %storage = ( 'DC' => 'ME dialed calls list', 'EN' => 'SIM (or ME) emergency number', 'FD' => 'SIM fixed dialing number phonebook', 'MC' => 'ME missed (unanswered received) calls list', 'ME' => 'ME phonebook', 'ON' => 'SIM (or ME) own numbers (MSISDNs) list', 'OW' => 'own telephone numbers', 'RC' => 'ME received calls list', 'SM' => 'SIM phonebook', 'TA' => 'TA (data card) phone book', 'MD' => 'last number redial memory', 'LD' => 'SIM last-dialing phonebook', 'MT' => 'combined ME and SIM phonebook', 'DC' => 'dialed calls list', 'RC' => 'received calls list', ); sub phonebooksource { my $source = shift; my $oldsource = askphone( "+CPBS?" ); my $reply; if ( !defined( $oldsource )) { $oldsource = "flarp"; # magic! $reply = "ERROR"; } if ( defined( $source )) { # upcase $source = uc( $source ); # requote $source =~ s/"//g; $source = qq("$source"); if ( $oldsource ne $source ) { $reply = askphone( "+CPBS=$source" ); if ( !defined( $oldsource )) { $^W and warn "Unable to switch phonebook to $source\n"; } else { $oldsource = $source; } } } defined( $oldsource ) and $oldsource =~ s/"//g; !defined( $storage{ $oldsource }) and $oldsource = undef; $oldsource; } sub getphonebook { my $verbose = shift; # How many entries? # AT+CPBR=? # +CPBR: (1-75),20,12 writeport( $port, "AT+CPBR=?\r" ); my ( $ok, $usage ) = checkreply( $port, "getting phonebook" ); my $max; if ( $ok ) { ( $max ) = $usage =~ m/^\+CPBR: \(1\-(\d+)\).+/m; $max or $ok = 0; } if ( !$ok ) { $^W and warn "Failed to query phonebook size.\n"; return 0; # zero entries in THIS phonebook... } else { print "($max entries)"; } # 1-n format only allows us to snag the first 20 entries, which is # kinda lame since the SH888 allows 75 entries... foreach my $i ( 1..$max ) { my $reply; writeport( $port, "AT+CPBR=$i\r" ); ( $ok, $reply ) = checkreply( $port, "getting phone entries" ); # DEBUG #$reply =~ s/\r/\\r/g; #$reply =~ s/\n/\\n/g; #print "$i: $reply\n"; # $reply format: # 29: AT+CPBR=29\r\r\n+CPBR: 29,"1745",129,"!INFO"\r\n\r\nOK\r\n # or, for a blank, # 35: AT+CPBR=35\r\r\nOK\r\n # Let's parse that! my ( $pos, $num, $type, $tag ) = $reply =~ m/^\+CPBR: ($i),"(.+?)",(\d+),"(.+)?"\r?$/m; # If there's something there, load it into the phonebook. if ( defined( $pos )) { $PhoneBook[ $pos ] = new PhoneBookEntry( $tag, $num, $type ); } else { $PhoneBook[ $i ] = undef; # make sure there's nothing there } print "." if $verbose; } return $max; } # putting a phonebook entry: # +CPBW=index,"number",type,"text" # +CPBW=,"number",type,"text" writes to first free location # I guess +CPBW=index trashes whatever's at # The doco says your ranges are (1-100),20,(128-255),18; my SIM says # (1-75),20,(128-255),12, which I'm guessing means you can have up to # 20 digits in a phone number and 12 characters in a name. The phone # itself says (1-99),20,(128-255),24. Might be worth investigating # before dinking with the phonebook. # Potentially useful stuff for autosensing the phone type: sub getinfo { my $Manufacturer = askphone( "+CGMI" ) || "Unknown manufacturer"; # Both my phones are ERICSSON my $Model = askphone( "+CGMM" ) || "Unknown model"; # My phones: # GA628 = 1050702 # SH888 = 1100801 my $Revision = askphone( "+CGMR" ) || "Unknown revision"; # My phones: # GA628: 9801141553,A,B,I,J,T2 # SH888: 990212 1544 CXC125131 } # AT+CFUN=0 will turn off the phone if the keylock is inactive... my $STATUS_UNKNOWN = 2; my @status = ( "Ready", "Unavailable", "Unknown", "Ringing", "Call in progress", "Asleep" ); my @rssi; # RSSI map: # 0 -> -113dBm or less # 1 - 30 -> -111 dBm to -53 dBm in twos # 31 -> 51 dBm or greater for my $i ( 0 .. 31 ) { $rssi[ $i ] = -113 + 2 * $i; } sub getstatus { # get various status stuff from the phone # battery level my $reply = askphone( "+CBC" ); if ( defined( $reply )) { ( $PowerSource, $BatteryLevel ) = $reply =~ m/(\d+),(\d+)/m; } else { $PowerSource = -1; $BatteryLevel = -1; } # phone status $reply = askphone( "+CPAS" ); $reply = $STATUS_UNKNOWN unless defined( $reply ); ( $Status ) = $reply =~ m/(\d+)/m; } # signal quality sub getsquelch { my $reply = askphone( "+CSQ" ); if ( defined( $reply )) { ( $RSSI, $BER ) = $reply=~ m/(\d+),(\d+)/m; } } sub getphonebooks { # Let's find out what our options are. $reply = askphone( "+CPBS=?" ); if ( defined( $reply )) { @PhoneBookSources = eval( $reply ); # evil! } else { @PhoneBookSources = (); # Wow. You're SO doomed. } # save this $PhoneBookStorage = phonebooksource; # if ( defined( $PhoneBookStorage )) { # Ask each phonebook for its limits. for my $p ( @PhoneBookSources ) { phonebooksource( $p ); $PhoneBook{ $p } = askphone( "+CPBW=?" ); } # restore original setting phonebooksource( $PhoneBookStorage ); # } } # Select terminal character set: +CSCS # When dialling, adding a ";" indicates that it's a voice call. # ATDL should be redial last # ATD=ME1 should dial entry 1 in the ME phonebook # ATD=SIM1 should dial entry 1 in the SIM # Can't say I've got ME or SIM to work. # SMS stuff # +CMGD=n delete message n # +CMGF=n change format. seems to be nailed to "PDU mode", n = 0. # +CMGL=n list messages # n = 0 received, unread # n = 1 received, read # n = 2 stored, unsent # n = 3 stored, sent # n = 4 all messages # +CMGR=n read message n # +CMGS=lenmsg| send message # len = "number of octest coded in the TP layer data unit". Eh. # msg = That pesky PDU format again # EOT (C-z) means send the message, ESC means cancel it. # +CMGW=len[,stat]msg store message [stat always = 2? it's optional] # +CMSS=n send message n from storage # +CMTI "unsolicited result" which I guess means it can happen at any time. # incoming mail, basically. # +CNMI=mode,mt,bm,ds,bfr configures CMTI hocus. # mode = 0 buffer codes in modem # 1 discard when modem is "reserved" # 2 buffer while reserved, flush when reservation ends. # mt = 0 No SMS-DELIVER # = 1 SMS-DELIVER # bm = 0 No CBM ??? # dt = 0 No SMS-STATUS-REPORTS # bfr = 0 mode 1, 2 flush to the computer # = 1 mode 1, 2 clear data # I'm confused now. # +CPMS="m1","m2" # Memory 1 storage is used to list, read and delete messages (+CMGL, # +CMGR and +CMGD) whilst memory 2 is used to write and send messages # (+CMGW and +CMSS). # +CSCA=num,type Service centre number & optional type. DON'T TOUCH. # +CSMS=mt,mo,bm SMS support. mt=incoming, mo=outgoing, bm=broadcast # Lockable items %Lockable = ( "PS" => "lock phone to SIM", "SC" => "lock SIM card", "AO" => "bar all outgoing calls", "OI" => "bar all outgoing int'l calls", "OX" => "bar all outgoing int'l call except to home", "AI" => "bar all incoming calls", "NT" => "bar all incoming calls from numbers not stored in TA memory", "NM" => "bar all incoming calls from numbers not stored in ME memory", "NS" => "bar all incoming calls from numbers not stored in SIM memory", "NA" => "bar all incoming calls from numbers not stored in any memory", "IR" => "bar all incoming calls when roaming abroad", "AB" => "all barring services", "AG" => "all outgoing barring services", "AC" => "all incoming barring services", "FD" => "SIM fixed dialing memory", "WNL" => "network lock", "CS" => "lock control surface (e.g. keyboard)", "P2" => "SIM PIN2", "PN" => "network personalization", "PU" => "network subset personalization", "PP" => "service provider personalization", "PC" => "corporate personalization" ); sub getlocks { my ( $reply ); $reply = askphone( "+CLCK=?" ); $reply = "()" unless defined( $reply ); # Stock up the hash for my $i ( eval( $reply )) { $Lockables{$i} = askphone( "+CLCK=\"$i\",2" ); } } sub resetphone { my ( $reply, $ok ); writeport( $port, "ATZ\r" ); ( $ok, $reply ) = checkreply( $port, "resetting phone" ); $reply ||= "unknown error"; if ( !$ok ) { closephone; # make sure to at least clean up. die "Failed to reset phone ($reply)\nStopped"; } } # Convenience routines, which should be more robust. sub askphone { my $string = shift; my $errorok = shift; $debug and print STDERR "Writing AT$string\n"; writeport( $port, "AT" . $string . "\r" ); my ( $ok, $reply ) = checkreply( $port, "doing $string", $errorok ); if ( $ok ) { $string = quotemeta( $string ); $debug and print STDERR "quotemeta: $string\n"; #$string =~ s/(\\\=?)\\\?/($1\\?)?/; # a little wizardry ;) $debug and print STDERR "raw reply: $reply\n"; # Unecho the thing $reply =~ s/^AT${string}\s*\n//s; $debug and print STDERR "unechoed: $reply\n"; # some commands present the answer as "+FOO: answer", and there can be a # few of them over multiple lines. $string =~ s/(\\[=?])+.*$//; $reply =~ s/\s*${string}:\s*/ /sg; # leave a space, though $reply =~ s/\s*\+CCFC:\s*/ /sg; # special case, cos I'm lazy. $debug and print STDERR "removing matching $string: $reply\n"; # nuke trailing OK $reply =~ s/(\r\n)*OK(\r\n)+//s; $reply =~ s/^\s+//s; $debug and print STDERR "Returning>>$reply\n"; } else { $reply = undef; } $reply; } sub writeport { my $p = shift; my $str = shift; my $len = length( $str ); # not used $p->write( $str ); while ( !($p->write_drain)[0] ) { } ; print STDERR ">>> $str\n" if $debug; } sub checkreply { my $p = shift; my $context = shift; my $errorok = shift; my $reply = ""; my $ok; my $now = time; while ( 1 ) { my $s = $p->input; print STDERR "<<< $s\n" if $debug && $s; $reply .= $s; last if ( $reply =~ m/^(OK|ERROR)\r?$/m ); # wait for answer if ( time > ( $now + 60)) { # don't wait more than 1 minute $^W and warn "Timed out in checkreply ($context)\n"; last } } $ok = ( $reply =~ m/^OK\r?$/m ) ? 1 : 0; if ( $errorok ) { $ok = 1; } if ( wantarray ) { return ( $ok, $reply ); } else { return $ok; } } # START OF CODE: WHERE IT ALL HAPPENS $| = 1; print STDERR "Opening phone on $ARGV[0]..." if $debug; openphone( $ARGV[0] ); #resetphone(); print STDERR "done\nDisabling quiet mode..." if $debug; askphone( "Q0" ); # make sure we're getting answers print STDERR "done\n" if $debug; #resetphone #fails on the GA628 # identify phone # print "Identifying phone..."; # getinfo; # printf "Mf./Mod./Rev.: $Manufacturer / $Model / $Revision\n"; # # check status # getstatus; # printf "Phone status: %s.\n", $status[ $Status ]; # printf "Battery: %s, %d%% charged.\n", # $PowerSource == 0 ? "in use" : "not in use", $BatteryLevel; # getsquelch; # if ( defined( $RSSI )) { # printf "Signal Quality: %d dBm (%g%%)", $rssi[ $RSSI ], ($RSSI * 100 / 31); # } # if ( defined( $BER )) { # $RSSI && print " with "; # printf( "BER %d", $BER ); # } # ( $RSSI || $BER ) and print "\n"; # getlocks; # for $item ( keys %Lockables ) { # printf( "%s: %s\n", $Lockable{$item}||$item, # $Lockables{ $item }||"[no reply]"); # } goto sms_dump; getphonebooks; for my $pb ( @PhoneBookSources ) { phonebooksource( $pb ); $pb =~ s/"//g; printf "Reading phonebook from %s ", $storage{ $pb } || $pb; my $max = getphonebook( 1 ); print "done.\n"; my ( $numw, $namew ); # Get the formatting widths if ( defined( $PhoneBook{ $pb } )) { ( $numw, $namew ) = $PhoneBook{ $pb } =~ /\(\d+\-\d+\),(\d+),\(\d+\-\d+\),(\d+)/; } $numw ||= 0; $namew ||= 0; for ( my $i = 0; $i < $max; $i++ ) { next unless defined( $PhoneBook[ $i ]); printf( "%-${namew}s|%${numw}s|(%s)\n", $PhoneBook[ $i ]->name||"Unknown", $PhoneBook[ $i ]->number||"", $PhoneBook[ $i ]->type||"" ); } } print "===============\n"; sms_dump: my $csv = new Text::CSV_XS; # find out my number! my $mynumber = askphone( "+CNUM" ); if ( $csv->parse( $mynumber )) { ( undef, $mynumber, undef ) = $csv->fields; } else { die "can't parse $mynumber\n"; } my $mbox = new Mail::Folder( 'mbox', $ENV{HOME} . '/Mail/phone', Create => 1 ); my $count = $mbox->qty(); my %messageid; for my $i ( 1..$count ) { my $msg = $mbox->get_message( $i ) or die $!; my $head = $msg->head(); my $msgid = $head->get( "Message-ID" ); chomp( $msgid ); $messageid{$msgid} = 1; } print STDERR "SMS DUMP\n" if $debug; # Get a list of all SMS messages #$reply = askphone( "+CSCS=\"UCS2\"" ); $reply = askphone( "+CMGF=1" ); for my $source ( "ME", "SM" ) { $reply = askphone( "+CPMS=\"$source\"" ); my @mboxes = ( "ALL" ); # ( "STO UNSENT", "STO SENT", "REC READ" ); for my $mb ( @mboxes ) { $reply = askphone( "+MMGL=\"$mb\"", "ok" ); my @msgs; while ( $reply ) { my ( $msg, undef, undef, $rest ) = $reply =~ /(\d+,"(REC READ|STO (UN)?SENT)","[^"]+")(.*)$/s; last unless $msg; push @msgs, $msg; $reply = $rest; } my @count = reverse map { s/^(\d+),.*$/$1/ ? $_ : "" } @msgs; for my $msg ( sort @count ) { if ( $msg !~ /\d+/ ) { print STDERR "WTF $msg\n"; next; } $reply = askphone( "+MMGR=$msg" ); my $pdu = new GSM::SMS::PDU; my $c = 1; if ( defined( $reply )) { $reply =~ s/STO SENT/SENT/s; $reply =~ s/REC READ/READ/s; $reply =~ s/STO UNSENT/UNSENT/s; print STDERR "Raw reply for $msg: $reply\n" if $debug; my @replies = split( /[\r\n]+/, $reply, 2 ); my ( $idx, $data ) = ( shift @replies, shift @replies ); my ( $type, $number, $timestamp ) = split( /,/, $idx, 3 ); if ( $csv->parse( $idx )) { ( $type, $number, $timestamp ) = $csv->fields; } if (!defined( $data )) { print STDERR "No data for message $msg\n" if $debug; next; } # stray newlines... this might not actually be right $data =~ s/\r\n//gs; my %msg; $msg{raw} = $data; $msg{number} = $msg; $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)", localtime( 0 )); $msg{zone} = 0; $number =~ s/"//g if $number; if ( $number =~ / / ) { print STDERR "Gaaah. We don't do multi-recipients\n"; $number =~ s/ .*$//; } # non-PDU version if ( $type =~ /READ|UNREAD/) { die "No timestamp in $idx" unless $timestamp; $msg{from} = $number; $msg{to} = $mynumber; $msg{body} = $data; $msg{length} = length( $msg{body} ); $msg{delete} = 1; my ( $y, $mt, $d, $h, $m, $s ) = $timestamp =~ m|(\d+)/(\d+)/(\d+),(\d+):(\d+):(\d+)|; $msg{unixtime} = timelocal( $s, $m, $h, $d, $mt - 1, $y ); $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)", localtime( $msg{unixtime} )); } elsif ( $type =~ /SENT/) { $msg{from} = $mynumber; $msg{to} = $number; $msg{body} = $data; $msg{length} = length( $msg{body} ); $msg{delete} = 0; $msg{unixtime} = 0; } elsif ( $type =~ /^\d+$/ ) { if ( $type <= 1 ) { my $decoded = $pdu->SMSDeliver( $data ); die "can't decode $data" unless $decoded; print STDERR Dumper( $decoded ) if $debug; $msg{from} = $decoded->{'TP-OA'}; $msg{to} = $mynumber; my ( $y, $mt, $d, $h, $m, $s, $z ) = $decoded->{'TP-SCTS'} =~ /^(..)(..)(..)(..)(..)(..)(..)$/; $y += 100; # stuuuuuuupid $mt--; $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)", $s, $m, $h, $d, $mt, $y ); $msg{unixtime} = mktime( $s, $m, $h, $d, $mt, $y ); $msg{zone} = $z||0; $msg{length} = $decoded->{'TP-UDL'}; $msg{body} = ( $decoded->{'TP-UD'}||"" ); $msg{delete} = 1; } else { my @decoded = $pdu->SMSSubmit_decode( $data ); # $VAR1 = [ # '+353862105113', # '11', # pdu type (flags, not important) # '00', # protocol identifier (should be 00) # undef, # user data header # 'When and where?^@' # ]; $msg{from} = $mynumber; $msg{to} = $decoded[0]; $msg{body} = $decoded[4]; $msg{length} = length( $msg{body} ); # need to pull message status for this, which we can't # do via the AT interface. $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S GMT", gmtime( 0 )); $msg{unixtime} = 0; $msg{zone} = 0; $msg{delete} = 0; } } # do the msgnum dance to try and get this stuff in sequence if ( $msg{unixtime} == 0 ) { # see if we have a date range to bracket our search my ( $less, $greater ) = ( 0, 0 ); for my $check ( sort keys %msgnum ) { if ( $check < $msg{number} ) { $less = $check; } elsif ( $check > $msg{number} ) { $greater = $check unless $greater; } } if ( $ENV{DEBUG}||0 ) { print STDERR "Date range: $msgnum{$less} to " . ( $msgnum{greater}||time) . "\n"; } if ( $msgnum{$less} ) { $vod->login( $config{number}, $config{password} ) unless $vod->{loggedin}; my $bits = get_sms_after( $msgnum{$less}, $msg{to} ); if ( defined( $bits->{date} )) { $msg{unixtime} = $bits->{date}; $msg{date} = strftime( "%a, %d %b %Y %H:%M:%S %z (%Z)", localtime( $msg{unixtime} )); } else { print "No date found for $msg{to} after " . scalar( localtime( $msgnum{$less} )) . ", bailing\n"; last; } } } $msgnum{$msg{number}} = $msg{unixtime}; $msg{body} =~ s/\x00$//; my $message = new Mail::Internet; $message->body( [ $msg{body} . "\n" ]); $message->head()->header_hashref( { "From" => $msg{from}, "To" => $msg{to}, "Date" => $msg{date}, "Content-Length" => $msg{length}, "X-Zone" => $msg{zone}, "X-Raw" => $msg{raw}, "Message-ID" => sprintf( "<%05d.SMS\@waider.ie>", $msg{number} ), "X-MsgNum" => $msg{number}, } ); if ( !defined( $messageid{sprintf( "<%05d.SMS\@waider.ie>", $msg{number})})) { $mbox->append_message( $message ); $messageid{sprintf( "<%05d.SMS\@waider.ie>", $msg{number})} = 1; } # decide to delete: # leading @ is mms indicator, maybe if ( $msg{length} == 0 or $msg{body} =~ /^@/ ) { $msg{delete} = 0; } if ( $msg{delete} and !$ENV{DEBUG}) { my $rep = askphone( "+CMGD=$msg", "ok" ); } } else { print STDERR "no reply to msg $msg\n" if $debug; } } } } $mbox->sync(); if ( !$ENV{DEBUG} and open( LAST, ">$ENV{HOME}/.vodafone/lastsms" )) { my @dates = sort { $a <=> $b } values( %msgnum ); print LAST $dates[-1] . "\n"; close( LAST ); } # don't do this. it'll happen automatically when the variable is destroyed. #$mbox->close(); END { closephone if $port; } sub get_sms_after { my $time = shift; my $number = shift; #my $intnumber = $number; die "no number???" unless $number; print STDERR "searching for $number after $time\n" if $ENV{DEBUG}; my $get = $firstbill; my $last = ""; while ( 1 ) { my ( $bill, $bdate ) = $vod->billing( $config{service}, $get ); # "can't get bill for $get" unless $bill; if ( !defined( $bill )) { if ( $get eq $firstbill and $get ne "unbilled" ) { $get = "unbilled"; next; } print STDERR "out of bills ($get)\n" if $ENV{DEBUG}; last; } $bdate =~ s@/@@; $firstbill = $bdate if $firstbill eq "first"; $last = $get; my $old = time; my $new = 0; for my $page ( @{$bill} ) { for my $item ( @{$page} ) { my $date = $item->{date}; print STDERR $item->{date} . " " . $item->{to} . " " . $item->{type} . "\n" if $ENV{DEBUG}; if (( $number eq "any number" or $item->{to} eq $number ) and $item->{type} eq "SMS" ) { if ( $date > $time ) { return $item; } } if ( $number =~ /^\+/ ) { my $intnum = $number; $intnum =~ s/\+353/0/; $intnum =~ s/\+1/001/; if ( $item->{to} eq $intnum and $item->{type} eq "SMS" ) { if ( $date > $time ) { return $item; } } } if ( $old > $date ) { $old = $date; } if ( $new < $date ) { $new = $date; } } } if ( $get eq $firstbill and $get ne "unbilled" ) { $get = "unbilled"; } elsif ( $get eq "unbilled" ) { last; } else { my ( $m, $y ) = ( substr( $bdate, 0, 2 ), substr( $bdate, 2 )); $m++; if ( $m > 12 ) { $m = 1; $y++; } $get = sprintf( "%02d/%02d", $m, $y ); } } if ( !$lastbill ) { $lastbill = $last; } return {}; }