Author: ptinsley Date: Tue Oct 24 15:43:23 2006 New Revision: 3183 Added: freeswitch/branches/ptinsley/voicemail/src/mod/applications/mod_voicemail/ (props changed) freeswitch/branches/ptinsley/voicemail/src/mod/applications/mod_voicemail/mod_voicemail.c Modified: freeswitch/branches/ptinsley/voicemail/src/include/switch_ivr.h freeswitch/branches/ptinsley/voicemail/src/switch_ivr.c Log: Getting my pre-cluecon code in a branch so i can get it up to date and finished up, pretty much ignore the voicemail stuff ATM, just me learning the modules way back Modified: freeswitch/branches/ptinsley/voicemail/src/include/switch_ivr.h ============================================================================== --- freeswitch/branches/ptinsley/voicemail/src/include/switch_ivr.h (original) +++ freeswitch/branches/ptinsley/voicemail/src/include/switch_ivr.h Tue Oct 24 15:43:23 2006 @@ -152,6 +152,48 @@ void *buf, unsigned int buflen); +/*! + \brief Function to evaluate an expression against a string + \param target The string to find a match in + \param expression The regular expression to run against the string + \return Boolean if a match was found or not +*/ +SWITCH_DECLARE(switch_status_t) switch_regex_match(char *target, char *expression); + +/*! + \brief Play a sound and gather digits with the number of retries specified if the user doesn't give digits in the set time + \param session the current session to play sound to and collect digits + \param min_digits the fewest digits allowed for the response to be valid + \param max_digits the max number of digits to accept + \param max_tries number of times to replay the sound and capture digits + \param timeout time to wait for input (this is per iteration, so total possible time = max_tries * (timeout + audio playback length) + \param valid_terminators for input that can include # or * (useful for variable length prompts) + \param audio_file file to play + \param bad_input_audio_file file to play if the input from the user was invalid + \param digit_buffer variable digits captured will be put back into (empty if capture failed) + \param digit_buffer_length length of the buffer for digits (should be the same or larger than max_digits) + \return switch status, used to note status of channel (will still return success if digit capture failed) + \note to test for digit capture failure look for \0 in the first position of the buffer +*/ +SWITCH_DECLARE(switch_status_t) switch_play_and_get_digits(switch_core_session_t *session, + unsigned int min_digits, + unsigned int max_digits, + unsigned int max_tries, + unsigned int timeout, + char* valid_terminators, + char* audio_file, + char* bad_input_audio_file, + void* digit_buffer, + unsigned int digit_buffer_length, + char* digits_regex); + +SWITCH_DECLARE(switch_status_t) switch_ivr_speak_text_handle(switch_core_session_t *session, + switch_speech_handle_t *sh, switch_codec_t *codec, + switch_timer_t *timer, switch_input_callback_function_t dtmf_callback, + char *text, void *buf, + unsigned int buflen); + + SWITCH_DECLARE(switch_status_t) switch_ivr_speak_text_handle(switch_core_session_t *session, switch_speech_handle_t *sh, Added: freeswitch/branches/ptinsley/voicemail/src/mod/applications/mod_voicemail/mod_voicemail.c ============================================================================== --- (empty file) +++ freeswitch/branches/ptinsley/voicemail/src/mod/applications/mod_voicemail/mod_voicemail.c Tue Oct 24 15:43:23 2006 @@ -0,0 +1,339 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com> + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Paul Tinsley <jackhammer@gmail.com> + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II <anthmct@yahoo.com> + * Paul Tinsley <jackhammer@gmail.com> + * + * + * mod_voicemail.c -- A basic voicemail system + * + */ +#include <switch.h> +#include <unistd.h> +#include <pcre.h> +#include <stdio.h> + +static const char modname[] = "mod_voicemail"; +static const char global_cf_name[] = "voicemail.conf"; + +typedef struct voicemail_user voicemail_user; +typedef struct voicemail_message voicemail_message; + +struct voicemail_user { + switch_core_session_t *session; + char mailbox[10]; + char password[10]; + int nopass; + int isvalid; + char context[25]; + char prompt_dir[1024]; +}; + +struct voicemail_message { + char message_id[40]; + int unread; + struct tm time_recieved; + voicemail_user *vm_user; +}; + +static switch_status_t _get_credentials(voicemail_user *vm_user); +static void _authenticate_user(voicemail_user *vm_user); + +/*! + \brief Function to populate a time struct with the current time details + \return tm structure (time.h) +*/ +struct tm *current_time() { + time_t now; + now = time(0); + + return localtime(&now); +} + +/*! + \brief Function to write out an xml description file of the details of a voicemail (date, CID, etc...) + \param voicemail_message to save the details of + \return Succes or failure of file write +*/ +#ifdef ASDF +static int save_message_detail_xml(voicemail_message *vm_message) { + +} +#endif + +static switch_status_t _get_credentials(voicemail_user *vm_user) { + switch_channel_t *channel; //used to hold the current channel in session + char input[10]; + + // Get the associated channel + channel = switch_core_session_get_channel(vm_user->session); + + //see if we already have a mailbox + if(!strlen(vm_user->mailbox)) { + //try to get the mailbox # from the user + switch_play_and_get_digits(vm_user->session, 3, 3, 3, 7000, "#", "/usr/local/freeswitch/voicemail/prompts/en-us/mailbox.gsm", "NULL", input, sizeof(input), "^13[68]$"); + + //see if we got a mailbox back + if(!strlen(input)) { + //say goodbye + return switch_channel_get_state(channel); + } else { + //save the input + strncpy(vm_user->mailbox, input, sizeof(vm_user->mailbox)); + } + } + + //See if we need the password + if(!vm_user->nopass) { + //try and get the password + switch_play_and_get_digits(vm_user->session, 1, 10, 3, 7000, "#", "/usr/local/freeswitch/voicemail/prompts/en-us/password.gsm", "NULL", input, sizeof(input), NULL); + + //see if we got the password + if(!strlen(input)) { + // say goodbye + return switch_channel_get_state(channel); + } else { + //save the input + strncpy(vm_user->password, input, sizeof(vm_user->password)); + } + } + + //if we get here we should be in good shape + return switch_channel_get_state(channel); +} + +static switch_status_t _main_menu(voicemail_user *vm_user) { + switch_channel_t *channel; //used to hold the channel variable + switch_status_t status; //used to hold the status variable + char input[10]; //used to hold the input from the user + + // Get the associated channel + channel = switch_core_session_get_channel(vm_user->session); + + //retrieve the current status + status = switch_channel_get_state(channel); + + //Make sure we are in good standing before we start + if(status != CS_EXECUTE) { + //Bail + return status; + } + + //Forever, muhahahahaha + while(1) { + //play the main menu and get user input (1 digit, 3 tries, 7 second timeout, 123789 allowed) + status = switch_play_and_get_digits(vm_user->session, 1, 1, 3, 7000, "", "/usr/local/freeswitch/voicemail/prompts/en-us/main-menu.wav", NULL, input, sizeof(input), "^[123789]$"); + + //Make sure everything is still copasetic + if(status != CS_EXECUTE) { + //Bail + return status; + } + + //Figure out where to go + switch(input[0]) { + case '1': { //press 1 to listen to your messages + break; + } + case '2': { //press 2 to change your current folder + break; + } + case '3': { //press 3 to send a message to another user + break; + } + case '7': { //Press 7 to change your greetings + break; + } + case '8': { //Press 8 to change your password + break; + } + case '9': { //Press 9 to logout + //User is done, bail + return status; + } + default: { + //User didn't respond in time, bail + return status; + } + } + } + + + //Stuff rolls down hill... + return switch_channel_get_state(channel); +} + +static void _authenticate_user(voicemail_user *vm_user) { + if(!strcmp(vm_user->mailbox, "136")) { + if(!strcmp(vm_user->password, "1234")) { + vm_user->isvalid = 1; + } + } +} + +static void login_function(switch_core_session_t *session, char *data) { + switch_channel_t *channel; + voicemail_user *vm_user; //Struct that holds details about the logged in user + switch_status_t status; + + //voicemail_user foo; + + //vm_user = &foo; + + //allocate our user struct + if(!(vm_user = switch_core_session_alloc(session, sizeof(*vm_user)))) { + //bad things + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "COULDN't Allocate\n"); + return; + } + + + // Parse the data coming from the dialplan + switch_xml_t parameters = switch_xml_parse_str(data, strlen(data)); + + //pull off all of the individual params + char *context = (char*) switch_xml_attr_soft(parameters, "context"); + + //setup the user + vm_user->isvalid = 0; + strncpy(vm_user->context, context, sizeof(vm_user->context)); + strncpy(vm_user->prompt_dir, "/usr/local/freeswitch/voicemail/prompts", sizeof(vm_user->context)); + vm_user->session = session; + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "STRUCT %s", vm_user->prompt_dir); + + + // Get the associated channel + channel = switch_core_session_get_channel(session); + + // Make sure somebody is home + assert(channel != NULL); + + //Answer the channel if we haven't already + switch_channel_answer(channel); + + int tries_left; + + for(tries_left = 3; tries_left > 0 && (switch_channel_get_state(channel) == CS_EXECUTE); tries_left--) { + //prompt the user for their credentials + status = _get_credentials(vm_user); + + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "STRUCT %s\n", vm_user->mailbox); + + //Make sure the channel is in good standing + if (status != SWITCH_STATUS_SUCCESS && status != CS_EXECUTE) { + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + return; + } + + //make sure we got what we needed. + if(!(strlen(vm_user->mailbox) && (strlen(vm_user->password) || vm_user->nopass))) { + //Can't go on without user info... + return; + } + + _authenticate_user(vm_user); + + if(vm_user->isvalid) { + break; + } + } + + if(!vm_user->isvalid) { + // say goodbye + switch_ivr_play_file(session, NULL, "/usr/local/freeswitch/voicemail/prompts/en-us/goodbye.gsm", NULL, NULL, NULL, 0); + return; + } + + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "WORKED\n"); + + //User is authenticated + status = _main_menu(vm_user); + + //If we get here we should be done + switch_ivr_play_file(session, NULL, "/usr/local/freeswitch/voicemail/prompts/en-us/goodbye.gsm", NULL, NULL, NULL, 0); + return; +} + + +/* +static switch_status_t my_on_hangup(switch_core_session_t *session) +{ + switch_channel_t *channel; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "I globally hooked to [%s] on the hangup event\n", switch_channel_get_name(channel)); + return SWITCH_STATUS_SUCCESS; + +} +*/ + +static const switch_state_handler_table_t state_handlers = { + /*.on_init */ NULL, + /*.on_ring */ NULL, + /*.on_execute */ NULL, + /*.on_hangup */ NULL, + /*.on_loopback */ NULL, + /*.on_transmit */ NULL +}; + +static const switch_application_interface_t voicemail_login_application_interface = { + /*.interface_name */ "voicemail_login", + /*.application_function */ login_function, + NULL, NULL, NULL, + /*.next*/ NULL +}; + +static const switch_loadable_module_interface_t mod_voicemail_module_interface = { + /*.module_name = */ modname, + /*.endpoint_interface = */ NULL, + /*.timer_interface = */ NULL, + /*.dialplan_interface = */ NULL, + /*.codec_interface = */ NULL, + /*.application_interface */ &voicemail_login_application_interface +}; + +SWITCH_MOD_DECLARE(switch_status_t) switch_module_load(const switch_loadable_module_interface_t **module_interface, char *filename) { + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = &mod_voicemail_module_interface; + + /* test global state handlers */ + switch_core_add_state_handler(&state_handlers); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +/* 'switch_module_runtime' will start up in a thread by itself just by having it exist + if it returns anything but SWITCH_STATUS_TERM it will be called again automaticly +*/ + + +//switch_status_t switch_module_runtime(void) Modified: freeswitch/branches/ptinsley/voicemail/src/switch_ivr.c ============================================================================== --- freeswitch/branches/ptinsley/voicemail/src/switch_ivr.c (original) +++ freeswitch/branches/ptinsley/voicemail/src/switch_ivr.c Tue Oct 24 15:43:23 2006 @@ -933,6 +933,172 @@ return status; } +SWITCH_DECLARE(switch_status_t) switch_regex_match(char *target, char *expression) { + const char* error = NULL; //Used to hold any errors + int error_offset = 0; //Holds the offset of an error + pcre* pcre_prepared = NULL; //Holds the compiled regex + int match_count = 0; //Number of times the regex was matched + int offset_vectors[2]; //not used, but has to exist or pcre won't even try to find a match + + //Compile the expression + pcre_prepared = pcre_compile(expression, 0, &error, &error_offset, NULL); + + //See if there was an error in the expression + if(error != NULL) { + //Clean up after ourselves + if(pcre_prepared) { + pcre_free(pcre_prepared); + pcre_prepared = NULL; + } + + //Note our error + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Regular Expression Error expression[%s] error[%s] location[%d]\n", expression, error, error_offset); + + //We definitely didn't match anything + return SWITCH_STATUS_FALSE; + } + + //So far so good, run the regex + match_count = pcre_exec(pcre_prepared, NULL, target, (int) strlen(target), 0, 0, offset_vectors, sizeof(offset_vectors) / sizeof(offset_vectors[0])); + + //Clean up + if(pcre_prepared) { + pcre_free(pcre_prepared); + pcre_prepared = NULL; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "number of matches: %d\n", match_count); + + //Was it a match made in heaven? + if(match_count > 0) { + return SWITCH_STATUS_SUCCESS; + } else { + return SWITCH_STATUS_FALSE; + } +} + +SWITCH_DECLARE(switch_status_t) switch_play_and_get_digits(switch_core_session_t *session, + unsigned int min_digits, + unsigned int max_digits, + unsigned int max_tries, + unsigned int timeout, + char* valid_terminators, + char* prompt_audio_file, + char* bad_input_audio_file, + void* digit_buffer, + unsigned int digit_buffer_length, + char* digits_regex) { + + char terminator; //used to hold terminator recieved from + switch_channel_t *channel; //the channel contained in session + switch_status_t status; //used to recieve state out of called functions + + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "switch_play_and_get_digits(session, %d, %d, %d, %d, %s, %s, %s, digit_buffer, %d, %s)\n", min_digits, max_digits, max_tries, timeout, valid_terminators, prompt_audio_file, bad_input_audio_file, digit_buffer_length, digits_regex); + + //Get the channel + channel = switch_core_session_get_channel(session); + + //Make sure somebody is home + assert(channel != NULL); + + //Answer the channel if it hasn't already been answered + switch_channel_answer(channel); + + //Start pestering the user for input + for(;(switch_channel_get_state(channel) == CS_EXECUTE) && max_tries > 0; max_tries--) { + //make the buffer so fresh and so clean clean + memset(digit_buffer, 0, digit_buffer_length); + + //Play the file + status = switch_ivr_play_file(session, NULL, prompt_audio_file, NULL, NULL, digit_buffer, digit_buffer_length); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "play gave up %s", digit_buffer); + + //Make sure we made it out alive + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + break; + } + + //we only get one digit out of playback, see if thats all we needed and what we got + if(max_digits == 1 && status == SWITCH_STATUS_BREAK) { + //Check the digit if we have a regex + if(digits_regex != NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Checking regex [%s] on [%s]\n", digits_regex, digit_buffer); + + //Make sure the digit is allowed + if(switch_regex_match(digit_buffer, digits_regex) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Match found!\n"); + //jobs done + break; + } else { + //See if a bad input prompt was specified, if so, play it + if(strlen(bad_input_audio_file) > 0) { + status = switch_ivr_play_file(session, NULL, bad_input_audio_file, NULL, NULL, NULL, 0); + + //Make sure we made it out alive + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + break; + } + } + } + } else { + //jobs done + break; + } + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Calling more digits try %d\n", max_tries); + + //Try to grab some more digits for the timeout period + status = switch_ivr_collect_digits_count(session, digit_buffer, digit_buffer_length, max_digits, valid_terminators, &terminator, timeout); + + //Make sure we made it out alive + if(status != SWITCH_STATUS_SUCCESS) { + //Bail + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + break; + } + + //see if we got enough + if(min_digits <= strlen(digit_buffer)) { + //See if we need to test a regex + if(digits_regex != NULL) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Checking regex [%s] on [%s]\n", digits_regex, digit_buffer); + //Test the regex + if(switch_regex_match(digit_buffer, digits_regex) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Match found!\n"); + //Jobs done + return switch_channel_get_state(channel); + } else { + //See if a bad input prompt was specified, if so, play it + if(strlen(bad_input_audio_file) > 0) { + status = switch_ivr_play_file(session, NULL, bad_input_audio_file, NULL, NULL, NULL, 0); + + //Make sure we made it out alive + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING); + break; + } + } + + } + } else { + //Jobs done + return switch_channel_get_state(channel); + } + } + } + + //if we got here, we got no digits or lost the channel + memset(digit_buffer, 0, sizeof(digit_buffer)); + return switch_channel_get_state(channel); +} + + + +