Difference between revisions of "Telephone scripts"

From Hackerspace ACKspace
Jump to: navigation, search
(added simple intercom script)
(added space state script)
Line 423: Line 423:
 
=== todo ===
 
=== todo ===
 
There is a auto_answer perl script that logs into the phone and changes the auto-answer setting: I might want to upload it here.
 
There is a auto_answer perl script that logs into the phone and changes the auto-answer setting: I might want to upload it here.
 +
 +
== Space state ==
 +
This script sets the spacestate variable according to what the webservice API returns.
 +
It then can be used in the dialplan to, for example, play a sound file or do call forwarding.
 +
Currently it is used to build a sound phrase for playing it as a greeting within our IVR
 +
 +
=== spacestate.js ===
 +
<pre>
 +
// Checks the space state API: if "open" is not 'true',
 +
// it's assumed closed and plays a sound file
 +
 +
if (session.ready())
 +
{
 +
    session.preAnswer();
 +
 +
    var state = fetchUrl( "https://ackspace.nl/status.php" );
 +
    if (state == false)
 +
        console_log( 3, "could not fetch space state");
 +
    else
 +
    {
 +
        // Remove newlines
 +
        state = state.replace( /[\r\n]/g,"");
 +
 +
        if( !state.match(  /.*"open"[ \t]*:[ \t]*true.*/i ) )
 +
        {
 +
            console_log(4, "Space seems to be closed, or could not parse result.\n");
 +
            session.setVariable("spacestate", "closed");
 +
        } else {
 +
            console_log(4, "Space is open!\n");
 +
            session.setVariable("spacestate", "open");
 +
        }
 +
    }
 +
}
 +
</pre>
 +
 +
=== dialplan and ivr ===
 +
The javascript is executed just before executing the IVR
 +
<action application="javascript" data="spacestate.js"/>
 +
<action application="ivr" data="ackspace_ivr"/>
 +
 +
and the corresponding IVR setting is:
 +
greet-long="phrase:ackspace_welcome:${spacestate}"
 +
 +
 +
 +
=== phrase ===
 +
This snippet is put under lang/nl/IVR/ackspace.xml and checks the given variable whether it is set to 'open'
 +
<pre><macro name="ackspace_welcome">
 +
    <input pattern="^(open)$">
 +
        <match>
 +
            <action function="play-file" data="ivr/welkom.wav"/>
 +
            <action function="sleep" data="300"/>
 +
            <action function="play-file" data="phrase:ackspace_welcome_short"/>
 +
        </match>
 +
        <nomatch>
 +
            <action function="play-file" data="ivr/welkom.wav"/>
 +
            <action function="sleep" data="300"/>
 +
            <action function="play-file" data="ivr/gesloten.wav"/>
 +
            <action function="sleep" data="2000"/>
 +
            <action function="play-file" data="phrase:ackspace_welcome_short"/>
 +
        </nomatch>
 +
    </input>
 +
</macro>
 +
</pre>

Revision as of 14:12, 23 June 2012

Number lookup

We make use of the mod_cidlookup for ease of use, but not all features are functional in our implementation. Every request is done via HTTP requests to a php script that will check if the number:

  • is an extension (and returns the name for that)
  • is stored in the local mySQL database, and return that
  • can be found online by using reversed number lookup websites for landlines
  • can be found online by using the national telecommunications authority database (opta.nl) for cell phones returning the associated cell provider
  • can be categorized by a more coarse lookup, like continent, country, region, town or number block owner, stored in a local array (~500 entries)

The setting for mod_cidlookup is:

<param name="url" value="http://webserviceprovider/lookup.php?number=${caller_id_number}"/>

lookup.php

This script returns some information about the caller, preferrably the name. It uses a local MySQL database and fetches info from some sites.

<?php
/*
 * Copyright (c) 2012, ACKspace foundation
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met: 
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution. 
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are those
 * of the authors and should not be interpreted as representing official policies, 
 * either expressed or implied, of the FreeBSD Project.
 */

define( "COUNTRY", "31" );
define( "REGION", "45" );
define( "PLUS", "00" );
define( "MYSQL_DB", "freeswitch_cidlookup" );

// Number dial plan, ~500 entries, shortened for wiki
$arrNumbers[1]['info'] = "(continent) Verenigde Staten";
$arrNumbers[1][4][4][1]['info'] = "Bermuda";
$arrNumbers[3]['info'] = "(continent) Europa";
$arrNumbers[3][1]['info'] = "(land) Nederland";
$arrNumbers[3][1][1][4]['info'] = "Testnetnummer van KPN Telecom";
$arrNumbers[3][1][4]['info'] = "(provincies) Oostelijk Noord-Brabant, Limburg";
$arrNumbers[3][1][4][5]['info'] = "(regio) Heerlen";
$arrNumbers[3][1][6]['info'] = "Mobiele nummers en Semafoondiensten";
$arrNumbers[3][1][6][1]['info'] = "Mobiele telefoon";
$arrNumbers[3][1][6][1][0]['info'] = "(GSM) KPN";
$arrNumbers[3][1][8][5]['info'] = "(type) Plaatsonafhankelijk/VoIP";
$arrNumbers[3][1][8][5][8][7]['info'] = "(VoIP) XS4ALL";

// Normalize the number
$arrNumberInfo = normalizeNumber( getVar( "number", true ) );

// Extension? try and get from dialplan
if ( $arrNumberInfo['type'] == "extension" )
{
    echo getExtension( $arrNumberInfo['local'] );
    exit;
}

if ( !function_exists( "mysql_connect" ))
{
    echo "ERROR";
    exit;
}

// Fetch the number from the database
if ( $strName = dbLookup( $arrNumberInfo ))
{
    echo $strName;
    exit;
}

// Nothing in the database?
// national number starting with 0[1-578]?
// Fetch number from website (check last get timestamp to prevent DoS
// put result in DB
if ( preg_match( "/^0[1-578].*/", $arrNumberInfo['national'] ) && $strName = fetchWebsiteResult( $arrNumberInfo['national'] ))
{
    echo $strName;

    // Add the name to the DB
    $arrNumberInfo['name'] = $strName;
    dbInsert( $arrNumberInfo );
    exit;
}
else if ( preg_match( "/^0[6].*/", $arrNumberInfo['national'] ) && $strName = fetchOptaResult( $arrNumberInfo['national'] ))
{
    // Number porting
    echo $strName;

    // Add the name to the DB, so we don't have to look it up anymore
    $arrNumberInfo['name'] = $strName;
    dbInsert( $arrNumberInfo );
    exit;
}

// Nothing on the website? Try and find the number in the array
if ( isset( $arrNumberInfo['international'] ))
{
    $strName = _getInfo( str_split( $arrNumberInfo['international'] ));
    echo $strName;
    exit;
}

/////////////////////////////////////////////////////////////////////
function fetchWebsiteResult( $_strNumber )
{

    if ( $strName = fetch_delefoondetective_nl( $_strNumber ))
        return $strName;

    if ( $strName = fetch_gevonden_cc( $_strNumber ))
        return $strName;

    if ( $strName = fetch_zoekenbel_nl( $_strNumber ))
        return $strName;

    if ( $strName = fetch_nummerzoeker_com( $_strNumber ))
        return $strName;

    if ( $strName = fetch_nummerid_com( $_strNumber ))
        return $strName;

    if ( $strName = fetch_gebeld_nl( $_strNumber ))
        return $strName;

    return false;
}

function fetchOptaResult( $_strNumber )
{
    $strPage = file_get_contents( "http://www.opta.nl/nl/nummers/nummers-zoeken/resultaat/?query=".$_strNumber."&page=1&portering=1" );

    if ( !preg_match( "/<strong>Huidige aanbieder<\/strong>.*?<p>(.*?)<\/p>/si", $strPage, $matches ))
        return false;

    return "(GSM) ".$matches[1];
}

function fetch_delefoondetective_nl( $_strNumber )
{
    $strPage = file_get_contents( "http://www.telefoondetective.nl/telefoonnummer/".$_strNumber."/" );

    if ( !preg_match( "/<div\sid=\"name\"><h\d>(.*?)<\/h\d><\/div>/i", $strPage, $matches ))
        return false;

    return $matches[1];
}

function fetch_gevonden_cc( $_strNumber )
{
    return false;
}

function fetch_zoekenbel_nl( $_strNumber )
{
    return false;
}

function fetch_nummerzoeker_com( $_strNumber )
{
    return false;
}


function fetch_nummerid_com( $_strNumber )
{
    return false;
}

function fetch_gebeld_nl( $_strNumber )
{
    return false;
}

function dbLookup( $_arrNumberInfo )
{
    if (!$db = mysql_connect( NULL, "username", "password" ))
        return false;

    // TODO: close db
    if (!mysql_select_db( MYSQL_DB, $db ))
        return false;

    // Prevent SQL injection on variables
    $country = mysql_real_escape_string( $_arrNumberInfo['country'] );
    $international = mysql_real_escape_string( $_arrNumberInfo['international'] );

    // Full number partial listing (experimental)
    $query = "SELECT name FROM telephone_names WHERE country_code=".$country." AND INSTR( '".$international."', number ) = 1 ORDER BY LENGTH(number), sortorder LIMIT 1";

    if (!$result = mysql_query( $query, $db ))
        return false;

    $row = mysql_fetch_row( $result );
    mysql_close( $db );
    return $row[0];
}


function dbInsert( $_arrNumberInfo )
{
    if (!$db = mysql_connect( NULL, "username", "password" ))
        return false;

    // TODO: close db
    if (!mysql_select_db( "freeswitch_cidlookup", $db ))
        return false;

    // Prevent SQL injection on variables
    $country = mysql_real_escape_string( $_arrNumberInfo['country'] );
    $international = mysql_real_escape_string( $_arrNumberInfo['international'] );
    $name = mysql_real_escape_string( $_arrNumberInfo['name'] );

    $query = "INSERT INTO telephone_names (country_code,number,name) VALUES (".$country.",'".$international."','".$name."')";

    if (!$result = mysql_query( $query, $db ))
    {
        echo mysql_error( $db );
        return false;
    }

    mysql_close( $db );

    return true;
}


function getExtension( $_strExtension )
{
    return false;
    //return "Ext. ".$_strExtension;
}

function normalizeNumber( $_strNumber )
{
    $arrInfo = array();

    if ( preg_match( "/^([19]\d+)/", $_strNumber, $matches ))
    {
        $arrInfo['local'] = $matches[1];
        $arrInfo['type'] = 'extension';
        return $arrInfo;
    }

    $_strNumber = preg_replace( "/^([2345678])/", COUNTRY.REGION.'$1', $_strNumber );

    // Add country on national dials
    $_strNumber = preg_replace( "/^(0)([^0].*)/", COUNTRY.'$2', $_strNumber );

    // Replace + and 00 international symbols before parsing (include space for URI conversion
    $_strNumber = preg_replace( "/^( |\+|00)/", '', $_strNumber );

    $arrInfo['country'] = intval( substr( $_strNumber, 0, 2 ));
    $arrInfo['international'] = $_strNumber;
    $arrInfo['type'] = 'international';

    // National number?
    if ( $arrInfo['country'] == intval( COUNTRY ))
    {
        // only works with countries of 2 digits; replaces it with a 0
        $arrInfo['national'] = "0".substr( $_strNumber, 2 );
        $arrInfo['type'] = 'national';
    }

    return $arrInfo;
};

function GetInfo( $_strNumber )
{
    global $arrNumbers;

    $_strNumber = preg_replace( "/^([2345678])/", COUNTRY.REGION.'$1', $_strNumber );

    // Add country on national dials
    $_strNumber = preg_replace( "/^(0)([^0].*)/", COUNTRY.'$2', $_strNumber );

    // Replace + and 00 international symbols before parsing
    $_strNumber = preg_replace( "/^(\+|00)/", '', $_strNumber );

    return _getInfo( str_split( $_strNumber ));
};

function _getInfo( $_arrNumber )
{
    global $arrNumbers;

    $arrInfo = array();

    $arrNumberInfo = $arrNumbers;
    $nIndent = 0;
    $strDigits = "";
    $strDetailedInfo = "";
    foreach ( $_arrNumber as $digit )
    {
        if ( isset( $arrNumberInfo[$digit] ) )
        {
            $strDigits .= $digit;
            $arrNumberInfo = $arrNumberInfo[$digit];
            if ( isset( $arrNumberInfo['info'] ) )
            {
                $arrInfo[] = str_repeat( "-", $nIndent ) . $strDigits . " " . $arrNumberInfo['info'];
                $strDigits = "";
                $strDetailedInfo = $arrNumberInfo['info'];
            }
            $nIndent++;
        } else {
            break;
        }
    }

    return $strDetailedInfo;
}

////////////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////////////
function getVar( $_strVarName, $_bAllowGet = false )
{
    // If _POST var is set, return _POST var,
    // else, if _GET var is set and is allowed, return _GET var
    // else, requested var not found: return NULL
    if ( isset( $_POST[ $_strVarName ] ) )
        return $_POST[ $_strVarName ];
    else if ( isset( $_GET[ $_strVarName ] ) && ($_bAllowGet == true) )
        return $_GET[ $_strVarName ];
    else
        return NULL;
}
?>

dialing out

This default dialplan snippet does a caller id lookup, and updates the callee name which will be visible on the local extension

<extension name="National_numbers">
    <condition field="destination_number" expression="^0([1-578]\d{8})$">
        <action application="set" data="effective_caller_id_number=${outbound_caller_id}"/>
        <action application="export" data="callee_id_name=${cidlookup(0031$1)}" />
        <action application="bridge" data="sofia/gateway/myLandLineProvider/31$1"/>
    </condition>
</extension>

incoming calls

This public dialplan snippet somewhat at the top sets the number (if any) first, checks if it has an international prefix, does a lookup for incoming calls and will set the name accordingly.

The second part will strip any leading + sign

<extension name="fix_cidnam" continue="true">
    <!--make sure the module is loaded, or else loading it will kill our call!-->
    <!-- Simple case: name=number or name is empty -->
    <!-- and number is a 10digit (excluding optional leading 1), in nanpa nxx-nxx-xxxx form -->
    <!-- will skipurl lookup if not a 10digit # (don't lookup INTL), and instead just query the SQL -->
    <condition field="${module_exists(mod_cidlookup)}" expression="true"/>
    <condition field="caller_id_name" expression="^${caller_id_number}$|^$"/>
    <condition field="caller_id_number" expression="^(\+|00)(\d+)$">
        <action application="cidlookup" data="00$2"/>
        <anti-action application="cidlookup" data="${caller_id_number}"/>
    </condition>
</extension>

<extension name="fix_cidnam_plus" continue="true">
    <!-- if the name starts with + followed by digits, strip the
         + and then pass the number -->
    <condition field="caller_id_name" expression="^\+(1[2-9]\d\d[2-9]\d{6})$">
        <action application="cidlookup" data="$1"/>
    </condition>
</extension>

todo

items stored in the database will not be updated anymore. The only way to refresh the number's information is to remove the entry manually which will cause a new lookup the next time that number is requested.

Simple intercom

dialplan snippet

Upon dialing *<number>, this snippet will create a conference for the caller, and will try for 5 seconds to add the callee as <number>_intercom which is an auto-answer line 2 setup on the cisco phones containing a sip image.

<extension name="extension-intercom">
    <condition field="destination_number" expression="^\*(1[09]\d\d)$" break="on-false">
        <action application="set" data="dialed_extension=$1"/>
        <action application="sleep" data="300"/>
    </condition>

    <condition>
        <action application="set" data="api_hangup_hook=conference 412 kick all"/>
        <action application="answer"/>
        <action application="export" data="sip_invite_params=intercom=true"/>
        <action application="export" data="sip_auto_answer=true"/>
        <action application="set" data="conference_auto_outcall_caller_id_name=$${effective_caller_id_name}"/>
        <action application="set" data="conference_auto_outcall_caller_id_number=$${effective_caller_id_number}"/>
        <action application="set" data="conference_auto_outcall_timeout=5"/>
        <action application="set" data="conference_auto_outcall_flags=mute"/>
        <action application="conference_set_auto_outcall" data="user/${dialed_extension}_intercom@${domain_name}"/>
        <action application="conference" data="412@intercom"/>

        <!-- Shouldn't be needed -->
        <action application="conference" data="412 kick all"/>
    </condition>
</extension>

todo

There is a auto_answer perl script that logs into the phone and changes the auto-answer setting: I might want to upload it here.

Space state

This script sets the spacestate variable according to what the webservice API returns. It then can be used in the dialplan to, for example, play a sound file or do call forwarding. Currently it is used to build a sound phrase for playing it as a greeting within our IVR

spacestate.js

// Checks the space state API: if "open" is not 'true',
// it's assumed closed and plays a sound file

if (session.ready())
{
    session.preAnswer();

    var state = fetchUrl( "https://ackspace.nl/status.php" );
    if (state == false)
        console_log( 3, "could not fetch space state");
    else
    {
        // Remove newlines
        state = state.replace( /[\r\n]/g,"");

        if( !state.match(  /.*"open"[ \t]*:[ \t]*true.*/i ) )
        {
            console_log(4, "Space seems to be closed, or could not parse result.\n");
            session.setVariable("spacestate", "closed");
        } else {
            console_log(4, "Space is open!\n");
            session.setVariable("spacestate", "open");
        }
    }
}

dialplan and ivr

The javascript is executed just before executing the IVR

<action application="javascript" data="spacestate.js"/>
<action application="ivr" data="ackspace_ivr"/>

and the corresponding IVR setting is:

greet-long="phrase:ackspace_welcome:${spacestate}"


phrase

This snippet is put under lang/nl/IVR/ackspace.xml and checks the given variable whether it is set to 'open'

<macro name="ackspace_welcome">
    <input pattern="^(open)$">
        <match>
            <action function="play-file" data="ivr/welkom.wav"/>
            <action function="sleep" data="300"/>
            <action function="play-file" data="phrase:ackspace_welcome_short"/>
        </match>
        <nomatch>
            <action function="play-file" data="ivr/welkom.wav"/>
            <action function="sleep" data="300"/>
            <action function="play-file" data="ivr/gesloten.wav"/>
            <action function="sleep" data="2000"/>
            <action function="play-file" data="phrase:ackspace_welcome_short"/>
        </nomatch>
    </input>
</macro>