Tuesday, January 8, 2013

Asterisk caller ID lookup

Background

I wanted a quick and simple solution to handling Caller ID lookups in Asterisk. I didn't want to use Google contacts integration because I'm using multiple Google accounts and I wanted to keep the contact list centralized. So what I did was whip up a quick Perl AGI script that could be called.

Notes and assumptions:

  • I'm running Asterisk 1.8 on DD-WRT using Optware.
  • Obviously, this is for a small home installation with a low call volume. It is not suitable for anything else.

The Perl Script (cidlookup.pl)

I placed this under /opt/var/lib/asterisk/agi-bin/cidlookup.pl. You'll need to have Asterisk::AGI package installed. The quickest way is to download from CPAN, and then unzip the asterisk directory into your Perl lib directory. The script does several things:
  1. Load existing address book and a list of unknown numbers into memory.
  2. Look up the caller number in the address book.
  3. If found, then return the caller's name.
  4. If not found, then look up the caller name using Open CNAM.
  5. If found, then use Open CNAM's result and update the address book
  6. If still not found, return the phone number as the name, and add the number to the list of unknown callers.
#!/opt/bin/perl -w
use strict;
use Asterisk::AGI;

# Globals
my $strCIDFile = '/opt/home/asterisk/callerid.csv';
my $strUnknownNums = '/opt/home/asterisk/unknownnums.txt';
my %hshCIDList = ();
my %hshUnknownList = ();

main();

sub main {
   my $blnAddCID = 0;

   # load lookup files into memory
   load_datafiles();
   
   # Extract Caller ID from AGI input
   my $objAGI = new Asterisk::AGI;
   my $strCallerID = $objAGI->get_variable("CALLERID(name)");
   
   # Match phone number
   if ($strCallerID =~ /\+(\d+)\@/) {
      my $strNumber = $1;
      my $strName = '';
      
      # Set caller ID number
      $objAGI->set_variable("CALLERID(number)",$strNumber);

      # Lookup name and set caller ID name if available, 
      # otherwise set caller ID to the number
      if ($strName = $hshCIDList{$strNumber}) {            # addr book
         $objAGI->set_variable("CALLERID(name)",$strName);
      } elsif ($strName = opencnam_lookup($strNumber) ) {  # open cnam
         $objAGI->set_variable("CALLERID(name)",$strName);
         $hshCIDList{$strNumber} = $strName;
         $blnAddCID = 1;
      } else {
         # Add number to unknown caller id list
         $hshUnknownList{$strNumber} = 1;
         # set caller id to number
         $objAGI->set_variable("CALLERID(name)",$strNumber);
      }
   } else {
      # error catch
      $objAGI->set_callerid('UNKNOWN');
   }
   update_datafiles($blnAddCID);
}

sub load_datafiles {
   # Read callerid lookup table into memory
   if (-e $strCIDFile) {
      open CIDFILE, $strCIDFile or die $!;
         while (my $strLine = ) {
            chomp $strLine;
            my ($intNumber, $strName) = split(',',$strLine);
            $hshCIDList{$intNumber} = $strName;   
         }
      close CIDFILE;
   }
   
   # Read unknown numbers into memory
   if (-e $strUnknownNums) {
      open UNKNOWNFILE, $strUnknownNums or die $!;
         while (my $strNum = ) {
            chomp $strNum;
            $hshUnknownList{$strNum} = 1;
         }
      close UNKNOWNFILE;
   }
}

sub update_datafiles {
   my $blnAddCID = $_[0];
   
   # Prune unknown numbers list
   open UNKNOWNFILE, "> $strUnknownNums" or die $!;
      foreach my $strNum (sort keys %hshUnknownList) {
         if ($hshCIDList{$strNum}) {
            delete $hshUnknownList{$strNum}
         } else {
            print UNKNOWNFILE "$strNum\n";
         }
      }
   close UNKNOWNFILE;
   
   # Update address book from open cnam
   if ($blnAddCID == 1) {
      open CIDFILE, "> $strCIDFile" or die $!;
         foreach my $strNum (sort keys %hshCIDList) {
            print CIDFILE $strNum . ',' . $hshCIDList{$strNum} . "\n";
         }
      close CIDFILE;
   }
}

# lookup open cnam in case not in personal address book
sub opencnam_lookup {
   my $strNum = $_[0];
   my $strRes = `curl -sk https://api.opencnam.com/v2/phone/$strNum`;
   
   if ($strRes !~ /no caller ID|throttle limit/i) {
      return sanitize($strRes)
   } else {
      return 0
   }
}

# sanitize caller id names from the Internet
sub sanitize {
   my $strToSanitize = $_[0];
   $strToSanitize =~ s/[^\w|^ ]//g; # keep alphanumeric and spaces
   $strToSanitize =~ s/ {2,}/ /g;   # shrink spaces
   $strToSanitize =~ s/^\s+//;      # ltrim
   $strToSanitize =~ s/\s+$//;      # rtrim
   return $strToSanitize;
}

The extensions.conf file

Call the script by using the AGI function. Be sure you have res_agi.so loaded in modules.conf.
; From Google
[google-in]
; Set caller ID
exten => s,1,NoOp(${CALLERID(dnid)})
 same => n,AGI(cidlookup.pl)
 same => n,Wait(1)
 same => n,Answer()
 same => n,SendDTMF(1)
 same => n,Dial(SIP/100,15)
 same => n,Hangup()

Tuesday, October 23, 2007

Working from home

First post, cool. I'm working from home today. I forgot my laptop so I had to go back home. So instead of wasting time driving back and forth, I figured that I would get actual work done and work from home. It's been pretty relaxing and I'm able to get a ton of stuff done without any distractions.

I went to Penn Camera and bought a new belt system from Think Tank photo. I've had enough of lugging my shoulder belt around. It has gotten so bad that I normally only take one lens and body with me. Leaving the other lenses at home is a waste. A lot of sports photographers us
e belt systems and I can see why. It is comfortable! I can carry all of my gear comfortably around my waist. The lower center of gravity alleviates all the pressure from my shoulders. I can get any relief that I can get, since I have bad shoulders from an old accident. Here's what I bought.

Speed Demon Bag

LC 75 Popdown

LC 50