From 17cdf9e1fd71fba4049bef32cd45222ee0e52cc4 Mon Sep 17 00:00:00 2001 From: Marc Olivier Chouinard Date: Fri, 23 Dec 2011 19:45:18 -0500 Subject: [PATCH] mod_voicemail_ivr: BETA : An alternative voicemail IVR system. It require an API provider for voicemail access that is currently provided by mod_voicemail vm_fsdb API so it can be used simultaneously with mod_voicemail IVR. It currently try to resemble the normal FreeSwitch Voicemail experience. The goal is to expand upon that experience without affecting everyone inproduction user experience. --- build/modules.conf.in | 1 + conf/lang/en/en.xml | 1 + .../applications/mod_voicemail_ivr/Makefile | 3 + .../applications/mod_voicemail_ivr/config.c | 168 ++++ .../applications/mod_voicemail_ivr/config.h | 92 ++ src/mod/applications/mod_voicemail_ivr/ivr.c | 250 ++++++ src/mod/applications/mod_voicemail_ivr/ivr.h | 61 ++ src/mod/applications/mod_voicemail_ivr/menu.c | 841 ++++++++++++++++++ src/mod/applications/mod_voicemail_ivr/menu.h | 64 ++ .../mod_voicemail_ivr/mod_voicemail_ivr.c | 139 +++ .../applications/mod_voicemail_ivr/utils.c | 181 ++++ .../applications/mod_voicemail_ivr/utils.h | 47 + 12 files changed, 1848 insertions(+) create mode 100644 src/mod/applications/mod_voicemail_ivr/Makefile create mode 100644 src/mod/applications/mod_voicemail_ivr/config.c create mode 100644 src/mod/applications/mod_voicemail_ivr/config.h create mode 100644 src/mod/applications/mod_voicemail_ivr/ivr.c create mode 100644 src/mod/applications/mod_voicemail_ivr/ivr.h create mode 100644 src/mod/applications/mod_voicemail_ivr/menu.c create mode 100644 src/mod/applications/mod_voicemail_ivr/menu.h create mode 100644 src/mod/applications/mod_voicemail_ivr/mod_voicemail_ivr.c create mode 100644 src/mod/applications/mod_voicemail_ivr/utils.c create mode 100644 src/mod/applications/mod_voicemail_ivr/utils.h diff --git a/build/modules.conf.in b/build/modules.conf.in index bb8bc7169a..40d55c7fb8 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -15,6 +15,7 @@ applications/mod_hash #applications/mod_http_cache #applications/mod_redis applications/mod_voicemail +#applications/mod_voicemail_ivr #applications/mod_directory #applications/mod_lcr applications/mod_expr diff --git a/conf/lang/en/en.xml b/conf/lang/en/en.xml index 2ccd513823..33c55197c2 100644 --- a/conf/lang/en/en.xml +++ b/conf/lang/en/en.xml @@ -8,6 +8,7 @@ + diff --git a/src/mod/applications/mod_voicemail_ivr/Makefile b/src/mod/applications/mod_voicemail_ivr/Makefile new file mode 100644 index 0000000000..38f5aae0a7 --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/Makefile @@ -0,0 +1,3 @@ +BASE=../../../.. +LOCAL_OBJS=ivr.o utils.o config.o menu.o +include $(BASE)/build/modmake.rules diff --git a/src/mod/applications/mod_voicemail_ivr/config.c b/src/mod/applications/mod_voicemail_ivr/config.c new file mode 100644 index 0000000000..3ff8f943c7 --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/config.c @@ -0,0 +1,168 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * config.c -- VoiceMail IVR Config + * + */ +#include + +#include "config.h" + +const char *global_cf = "voicemail_ivr.conf"; + +void populate_profile_menu_event(vmivr_profile_t *profile, vmivr_menu_profile_t *menu) { + switch_xml_t cfg, xml, x_profiles, x_profile, x_keys, x_phrases, x_menus, x_menu; + + free_profile_menu_event(menu); + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + goto end; + } + if (!(x_profiles = switch_xml_child(cfg, "profiles"))) { + goto end; + } + + if ((x_profile = switch_xml_find_child(x_profiles, "profile", "name", profile->name))) { + if ((x_menus = switch_xml_child(x_profile, "menus"))) { + if ((x_menu = switch_xml_find_child(x_menus, "menu", "name", menu->name))) { + if ((x_keys = switch_xml_child(x_menu, "keys"))) { + switch_event_import_xml(switch_xml_child(x_keys, "key"), "dtmf", "action", &menu->event_keys_dtmf); + switch_event_import_xml(switch_xml_child(x_keys, "key"), "action", "dtmf", &menu->event_keys_action); + switch_event_import_xml(switch_xml_child(x_keys, "key"), "action", "variable", &menu->event_keys_varname); + } + if ((x_phrases = switch_xml_child(x_menu, "phrases"))) { + switch_event_import_xml(switch_xml_child(x_phrases, "phrase"), "name", "value", &menu->event_phrases); + } + } + } + } +end: + if (xml) + switch_xml_free(xml); + return; + +} + +void free_profile_menu_event(vmivr_menu_profile_t *menu) { + if (menu->event_keys_dtmf) { + switch_event_destroy(&menu->event_keys_dtmf); + } + if (menu->event_keys_action) { + switch_event_destroy(&menu->event_keys_action); + } + if (menu->event_keys_varname) { + switch_event_destroy(&menu->event_keys_varname); + } + + if (menu->event_phrases) { + switch_event_destroy(&menu->event_phrases); + } +} + +vmivr_profile_t *get_profile(switch_core_session_t *session, const char *profile_name) +{ + vmivr_profile_t *profile = NULL; + switch_xml_t cfg, xml, x_profiles, x_profile, x_apis, param; + + if (!(xml = switch_xml_open_cfg(global_cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Open of %s failed\n", global_cf); + return profile; + } + if (!(x_profiles = switch_xml_child(cfg, "profiles"))) { + goto end; + } + + if ((x_profile = switch_xml_find_child(x_profiles, "profile", "name", profile_name))) { + if (!(profile = switch_core_session_alloc(session, sizeof(vmivr_profile_t)))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n"); + goto end; + } + + profile->name = profile_name; + + profile->current_msg = 0; + profile->current_msg_uuid = NULL; + + profile->folder_name = VM_FOLDER_ROOT; + profile->folder_filter = VM_MSG_NOT_READ; + + /* TODO Make the following configurable */ + profile->api_profile = profile->name; + profile->menu_check_auth = "std_authenticate"; + profile->menu_check_main = "std_main_menu"; + profile->menu_check_terminate = "std_purge"; + + if ((x_apis = switch_xml_child(x_profile, "apis"))) { + int total_options = 0; + int total_invalid_options = 0; + for (param = switch_xml_child(x_apis, "api"); param; param = param->next) { + char *var, *val; + if ((var = (char *) switch_xml_attr_soft(param, "name")) && (val = (char *) switch_xml_attr_soft(param, "value"))) { + if (!strcasecmp(var, "msg_undelete") && !profile->api_msg_undelete) + profile->api_msg_undelete = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "msg_delete") && !profile->api_msg_delete) + profile->api_msg_delete = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "msg_list") && !profile->api_msg_list) + profile->api_msg_list = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "msg_count") && !profile->api_msg_count) + profile->api_msg_count = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "msg_save") && !profile->api_msg_save) + profile->api_msg_save = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "msg_purge") && !profile->api_msg_purge) + profile->api_msg_purge = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "msg_get") && !profile->api_msg_get) + profile->api_msg_get = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "msg_forward") && !profile->api_msg_forward) + profile->api_msg_forward = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "pref_greeting_set") && !profile->api_pref_greeting_set) + profile->api_pref_greeting_set = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "pref_recname_set") && !profile->api_pref_recname_set) + profile->api_pref_recname_set = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "pref_password_set") && !profile->api_pref_password_set) + profile->api_pref_password_set = switch_core_session_strdup(session, val); + else if (!strcasecmp(var, "auth_login") && !profile->api_auth_login) + profile->api_auth_login = switch_core_session_strdup(session, val); + else + total_invalid_options++; + total_options++; + } + } + if (total_options - total_invalid_options != 12) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing api definition for profile '%s'\n", profile_name); + profile = NULL; + } + } + + } + +end: + switch_xml_free(xml); + return profile; +} + diff --git a/src/mod/applications/mod_voicemail_ivr/config.h b/src/mod/applications/mod_voicemail_ivr/config.h new file mode 100644 index 0000000000..d463b9f521 --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/config.h @@ -0,0 +1,92 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * config.c -- VoiceMail IVR Config + * + */ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +extern const char *global_cf; + +#define VM_FOLDER_ROOT "inbox"; +#define VM_MSG_NOT_READ "not-read" +#define VM_MSG_SAVED "save" +#define VM_MSG_NEW "new" + +struct vmivr_profile { + const char *name; + + const char *domain; + const char *id; + + int current_msg; + const char *current_msg_uuid; + + const char *folder_name; + const char *folder_filter; + + const char *menu_check_auth; + const char *menu_check_main; + const char *menu_check_terminate; + + switch_bool_t authorized; + + const char *api_profile; + const char *api_auth_login; + const char *api_msg_delete; + const char *api_msg_undelete; + const char *api_msg_list; + const char *api_msg_count; + const char *api_msg_save; + const char *api_msg_purge; + const char *api_msg_get; + const char *api_msg_forward; + const char *api_pref_greeting_set; + const char *api_pref_recname_set; + const char *api_pref_password_set; + +}; +typedef struct vmivr_profile vmivr_profile_t; + +struct vmivr_menu_profile { + const char *name; + + switch_event_t *event_keys_action; + switch_event_t *event_keys_dtmf; + switch_event_t *event_keys_varname; + switch_event_t *event_phrases; +}; +typedef struct vmivr_menu_profile vmivr_menu_profile_t; + +vmivr_profile_t *get_profile(switch_core_session_t *session, const char *profile_name); + +void free_profile_menu_event(vmivr_menu_profile_t *menu); +void populate_profile_menu_event(vmivr_profile_t *profile, vmivr_menu_profile_t *menu); + +#endif /* _CONFIG_H_ */ diff --git a/src/mod/applications/mod_voicemail_ivr/ivr.c b/src/mod/applications/mod_voicemail_ivr/ivr.c new file mode 100644 index 0000000000..9637c63d31 --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/ivr.c @@ -0,0 +1,250 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * ivr.c -- VoiceMail IVR Engone + * + */ + +#include + +#include "ivr.h" + +int match_dtmf(switch_core_session_t *session, dtmf_ss_t *loc) { + switch_bool_t is_invalid[128] = { SWITCH_FALSE }; + int i; + loc->potentialMatch = NULL; + loc->completeMatch = NULL; + loc->potentialMatchCount = 0; + + for (i = 0; i < loc->dtmf_received; i++) { + int j; + loc->potentialMatchCount = 0; + for (j = 0; !zstr(loc->dtmf_accepted[j]) && j < 128; j++) { + switch_bool_t cMatch = SWITCH_FALSE; + char test[2] = { 0 }; + + if (is_invalid[j]) + continue; + + test[0] = loc->dtmf_stored[i]; + if (loc->dtmf_accepted[j][i] == 'N' && atoi(test) >= 2 && atoi(test) <= 9) + cMatch = SWITCH_TRUE; + if (loc->dtmf_accepted[j][i] == 'X' && atoi(test) >= 0 && atoi(test) <= 9) { + cMatch = SWITCH_TRUE; + } + if (i >= strlen(loc->dtmf_accepted[j]) - 1 && loc->dtmf_accepted[j][strlen(loc->dtmf_accepted[j])-1] == '.') + cMatch = SWITCH_TRUE; + if (loc->dtmf_accepted[j][i] == loc->dtmf_stored[i]) + cMatch = SWITCH_TRUE; + + if (cMatch == SWITCH_FALSE) { + is_invalid[j] = SWITCH_TRUE; + continue; + } + + if (i == strlen(loc->dtmf_accepted[j]) - 1 && loc->dtmf_accepted[j][strlen(loc->dtmf_accepted[j])-1] == '.') { + loc->completeMatch = loc->dtmf_accepted[j]; + } + if (i == loc->dtmf_received - 1 && loc->dtmf_received == strlen(loc->dtmf_accepted[j]) && loc->dtmf_accepted[j][strlen(loc->dtmf_accepted[j])-1] != '.') { + loc->completeMatch = loc->dtmf_accepted[j]; + continue; + } + loc->potentialMatchCount++; + } + } + + return 1; +} + +static switch_status_t cb_on_dtmf_ignore(switch_core_session_t *session, void *input, switch_input_type_t itype, void *buf, unsigned int buflen) +{ + switch (itype) { + case SWITCH_INPUT_TYPE_DTMF: + { + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_dtmf_t *dtmf = (switch_dtmf_t *) input; + switch_channel_queue_dtmf(channel, dtmf); + return SWITCH_STATUS_BREAK; + } + default: + break; + } + return SWITCH_STATUS_SUCCESS; +} + +static switch_status_t cb_on_dtmf(switch_core_session_t *session, void *input, switch_input_type_t itype, void *buf, unsigned int buflen) +{ + dtmf_ss_t *loc = (dtmf_ss_t*) buf; + + switch (itype) { + case SWITCH_INPUT_TYPE_DTMF: + { + switch_dtmf_t *dtmf = (switch_dtmf_t *) input; + switch_bool_t audio_was_stopped = loc->audio_stopped; + loc->audio_stopped = SWITCH_TRUE; + + if (loc->dtmf_received >= sizeof(loc->dtmf_stored)) { + loc->result = RES_BUFFER_OVERFLOW; + break; + } + if (!loc->terminate_key || dtmf->digit != loc->terminate_key) + loc->dtmf_stored[loc->dtmf_received++] = dtmf->digit; + + match_dtmf(session, loc); + + if (loc->terminate_key && dtmf->digit == loc->terminate_key && loc->result == RES_WAITFORMORE) { + if (loc->potentialMatchCount == 1 && loc->completeMatch != NULL) { + loc->result = RES_FOUND; + } else { + loc->result = RES_INVALID; + } + return SWITCH_STATUS_BREAK; + } else { + if (loc->potentialMatchCount == 0 && loc->completeMatch != NULL) { + loc->result = RES_FOUND; + return SWITCH_STATUS_BREAK; + } else if (loc->potentialMatchCount > 0) { + loc->result = RES_WAITFORMORE; + if (!audio_was_stopped) + return SWITCH_STATUS_BREAK; + } else { + loc->result = RES_INVALID; + return SWITCH_STATUS_BREAK; + } + } + } + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t captureMenuInitialize(dtmf_ss_t *loc, char **dtmf_accepted) { + int i; + + memset(loc, 0, sizeof(*loc)); + + for (i = 0; dtmf_accepted[i] && i < 16; i++) { + strncpy(loc->dtmf_accepted[i], dtmf_accepted[i], 128); + } + + return SWITCH_STATUS_SUCCESS; +} + +switch_status_t playbackBufferDTMF(switch_core_session_t *session, const char *macro_name, const char *data, switch_event_t *event, const char *lang, int timeout) { + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_channel_t *channel = switch_core_session_get_channel(session); + + if (switch_channel_ready(channel)) { + switch_input_args_t args = { 0 }; + + args.input_callback = cb_on_dtmf_ignore; + + if (macro_name) { + status = switch_ivr_phrase_macro_event(session, macro_name, data, event, lang, &args); + } + } else { + status = SWITCH_STATUS_BREAK; + } + + return status; +} + + +switch_status_t captureMenu(switch_core_session_t *session, dtmf_ss_t *loc, const char *macro_name, const char *data, switch_event_t *event, const char *lang, int timeout) { + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_channel_t *channel = switch_core_session_get_channel(session); + + if (switch_channel_ready(channel)) { + switch_input_args_t args = { 0 }; + + args.input_callback = cb_on_dtmf; + args.buf = loc; + + if (macro_name && loc->audio_stopped == SWITCH_FALSE && loc->result == RES_WAITFORMORE) { + status = switch_ivr_phrase_macro_event(session, macro_name, data, event, lang, &args); + } + + if (switch_channel_ready(channel) && (status == SWITCH_STATUS_SUCCESS || status == SWITCH_STATUS_BREAK) && timeout && loc->result == RES_WAITFORMORE) { + loc->audio_stopped = SWITCH_TRUE; + switch_ivr_collect_digits_callback(session, &args, timeout, 0); + if (loc->result == RES_WAITFORMORE) { + if (loc->potentialMatchCount == 1 && loc->completeMatch != NULL) { + loc->result = RES_FOUND; + } else { + loc->result = RES_TIMEOUT; + } + } + } + } else { + status = SWITCH_STATUS_BREAK; + } + + return status; +} + +switch_status_t captureMenuRecord(switch_core_session_t *session, dtmf_ss_t *loc, switch_event_t *event, const char *file_path, switch_file_handle_t *fh, int max_record_len) { + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_channel_t *channel = switch_core_session_get_channel(session); + + if (switch_channel_ready(channel)) { + switch_input_args_t args = { 0 }; + + args.input_callback = cb_on_dtmf; + args.buf = loc; + + if (loc->audio_stopped == SWITCH_FALSE && loc->result == RES_WAITFORMORE) { + loc->recorded_audio = SWITCH_TRUE; + switch_ivr_gentones(session, "%(1000, 0, 640)", 0, NULL); /* TODO Make this optional and configurable */ + status = switch_ivr_record_file(session, fh, file_path, &args, max_record_len); + + } + if (loc->result == RES_WAITFORMORE) { + loc->result = RES_TIMEOUT; + } + + } else { + status = SWITCH_STATUS_BREAK; + } + + return status; +} + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 + */ diff --git a/src/mod/applications/mod_voicemail_ivr/ivr.h b/src/mod/applications/mod_voicemail_ivr/ivr.h new file mode 100644 index 0000000000..520e41e34d --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/ivr.h @@ -0,0 +1,61 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * ivr.h -- VoiceMail IVR Engine + * + */ +struct dtmf_ss { + char dtmf_stored[128]; + int dtmf_received; + char dtmf_accepted[16][128]; + int result; + switch_bool_t audio_stopped; + switch_bool_t recorded_audio; + const char *potentialMatch; + int potentialMatchCount; + const char *completeMatch; + char terminate_key; +}; +typedef struct dtmf_ss dtmf_ss_t; + +#define RES_WAITFORMORE 0 +#define RES_FOUND 1 +#define RES_INVALID 3 +#define RES_TIMEOUT 4 +#define RES_BREAK 5 +#define RES_RECORD 6 +#define RES_BUFFER_OVERFLOW 99 + +#define MAX_DTMF_SIZE_OPTION 32 + +switch_status_t captureMenu(switch_core_session_t *session, dtmf_ss_t *loc, const char *macro_name, const char *data, switch_event_t *event, const char *lang, int timeout); +switch_status_t captureMenuRecord(switch_core_session_t *session, dtmf_ss_t *loc, switch_event_t *event, const char *file_path, switch_file_handle_t *fh, int max_record_len); +switch_status_t captureMenuInitialize(dtmf_ss_t *loc, char **dtmf_accepted); + +switch_status_t playbackBufferDTMF(switch_core_session_t *session, const char *macro_name, const char *data, switch_event_t *event, const char *lang, int timeout); + diff --git a/src/mod/applications/mod_voicemail_ivr/menu.c b/src/mod/applications/mod_voicemail_ivr/menu.c new file mode 100644 index 0000000000..3374824e42 --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/menu.c @@ -0,0 +1,841 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * menu.c -- VoiceMail Menu + * + */ +#include + +#include "ivr.h" +#include "menu.h" +#include "utils.h" +#include "config.h" + +/* List of available menu */ +vmivr_menu_function_t menu_list[] = { + {"std_authenticate", vmivr_menu_authenticate}, + {"std_main_menu", vmivr_menu_main}, + {"std_navigator", vmivr_menu_navigator}, + {"std_record_name", vmivr_menu_record_name}, + {"std_set_password", vmivr_menu_set_password}, + {"std_select_greeting_slot", vmivr_menu_select_greeting_slot}, + {"std_record_greeting_with_slot", vmivr_menu_record_greeting_with_slot}, + {"std_preference", vmivr_menu_preference}, + {"std_purge", vmivr_menu_purge}, + {"std_forward", vmivr_menu_forward}, + { NULL, NULL } +}; + +#define MAX_ATTEMPT 3 /* TODO Make these fields configurable */ +#define DEFAULT_IVR_TIMEOUT 3000 + +void vmivr_menu_purge(switch_core_session_t *session, vmivr_profile_t *profile) { + if (profile->id && profile->authorized) { + if (1==1 /* TODO make Purge email on exit optional ??? */) { + const char *cmd = switch_core_session_sprintf(session, "%s %s %s", profile->api_profile, profile->domain, profile->id); + vmivr_api_execute(session, profile->api_msg_purge, cmd); + } + } +} + +void vmivr_menu_main(switch_core_session_t *session, vmivr_profile_t *profile) { + switch_channel_t *channel = switch_core_session_get_channel(session); + vmivr_menu_profile_t menu = { "std_main_menu" }; + int retry; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + if (!menu.event_keys_dtmf || !menu.event_phrases) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing Menu Phrases and Keys\n"); + return; + } + + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0; retry--) { + dtmf_ss_t loc; + char *dtmfa[16] = { 0 }; + switch_event_t *phrase_params = NULL; + char *cmd = NULL; + + switch_event_create(&phrase_params, SWITCH_EVENT_REQUEST_PARAMS); + append_event_profile(phrase_params, profile, menu); + + populate_dtmfa_from_event(phrase_params, profile, menu, dtmfa); + + captureMenuInitialize(&loc, dtmfa); + + cmd = switch_core_session_sprintf(session, "json %s %s %s %s", profile->api_profile, profile->domain, profile->id, profile->folder_name); + jsonapi2event(session, phrase_params, profile->api_msg_count, cmd); + //initial_count_played = SWITCH_TRUE; + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "msg_count"), NULL, phrase_params, NULL, 0); + + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "menu_options"), NULL, phrase_params, NULL, DEFAULT_IVR_TIMEOUT); + + if (loc.result == RES_TIMEOUT) { + /* TODO Ask for the prompt Again IF retry != 0 */ + } else if (loc.result == RES_INVALID) { + /* TODO Say invalid option, and ask for the prompt again IF retry != 0 */ + } else if (loc.result == RES_FOUND) { /* Matching DTMF Key Pressed */ + const char *action = switch_event_get_header(menu.event_keys_dtmf, loc.dtmf_stored); + + /* Reset the try count */ + retry = MAX_ATTEMPT; + + if (action) { + if (!strncasecmp(action, "new_msg:", 8)) { + void (*fPtr)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(action+8); + profile->folder_filter = VM_MSG_NEW; + + if (fPtr) { + fPtr(session, profile); + } + } else if (!strncasecmp(action, "saved_msg:", 10)) { + void (*fPtr)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(action+10); + profile->folder_filter = VM_MSG_SAVED; + + if (fPtr) { + fPtr(session, profile); + } + } else if (!strcasecmp(action, "return")) { /* Return to the previous menu */ + retry = -1; + } else if (!strncasecmp(action, "menu:", 5)) { /* Sub Menu */ + void (*fPtr)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(action+5); + if (fPtr) { + fPtr(session, profile); + } + } + } + } + switch_event_destroy(&phrase_params); + + + } + free_profile_menu_event(&menu); +} + + +void vmivr_menu_navigator(switch_core_session_t *session, vmivr_profile_t *profile) { + switch_channel_t *channel = switch_core_session_get_channel(session); + switch_event_t *msg_list_params = NULL; + size_t msg_count = 0; + size_t current_msg = 1; + size_t next_msg = current_msg; + size_t previous_msg = current_msg; + char *cmd = NULL; + int retry; + + /* Different switch to control playback of phrases */ + switch_bool_t initial_count_played = SWITCH_FALSE; + switch_bool_t skip_header = SWITCH_FALSE; + switch_bool_t skip_playback = SWITCH_FALSE; + switch_bool_t msg_deleted = SWITCH_FALSE; + switch_bool_t msg_undeleted = SWITCH_FALSE; + switch_bool_t msg_saved = SWITCH_FALSE; + + vmivr_menu_profile_t menu = { "std_navigator" }; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + if (!menu.event_keys_dtmf || !menu.event_phrases) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing Menu Phrases or Keys\n"); + return; + } + + /* Get VoiceMail List And update msg count */ + cmd = switch_core_session_sprintf(session, "json %s %s %s %s %s", profile->api_profile, profile->domain, profile->id, profile->folder_name, profile->folder_filter); + msg_list_params = jsonapi2event(session, NULL, profile->api_msg_list, cmd); + if (msg_list_params) { + msg_count = atol(switch_event_get_header(msg_list_params,"VM-List-Count")); + if (msg_count == 0) { + goto done; + } + } else { + /* TODO error MSG */ + goto done; + } + + + /* TODO Add Detection of new message and notify the user */ + + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0; retry--) { + switch_core_session_message_t msg = { 0 }; + char cid_buf[1024] = ""; + dtmf_ss_t loc; + char *dtmfa[16] = { 0 }; + switch_event_t *phrase_params = NULL; + + switch_event_create(&phrase_params, SWITCH_EVENT_REQUEST_PARAMS); + + append_event_profile(phrase_params, profile, menu); + + populate_dtmfa_from_event(phrase_params, profile, menu, dtmfa); + + previous_msg = current_msg; + + /* Simple Protection to not go out of msg list scope */ + /* TODO: Add Prompt to notify they reached the begining or the end */ + if (next_msg == 0) { + next_msg = 1; + } else if (next_msg > msg_count) { + next_msg = msg_count; + } + + current_msg = next_msg; + + captureMenuInitialize(&loc, dtmfa); + + /* Prompt related to previous Message here */ + append_event_message(session, profile, phrase_params, msg_list_params, previous_msg); + if (msg_deleted) { + msg_deleted = SWITCH_FALSE; + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "ack"), "deleted", phrase_params, NULL, 0); + } + if (msg_undeleted) { + msg_undeleted = SWITCH_FALSE; + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "ack"), "undeleted", phrase_params, NULL, 0); + } + if (msg_saved) { + msg_saved = SWITCH_FALSE; + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "ack"), "saved", phrase_params, NULL, 0); + } + switch_event_del_header(phrase_params, "VM-Message-Flags"); + /* Prompt related the current message */ + append_event_message(session, profile, phrase_params, msg_list_params, current_msg); + + /* Used for extra control in phrases */ + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, "VM-List-Count", "%"SWITCH_SIZE_T_FMT, msg_count); + + /* Save in profile the current msg info for other menu processing AND restoration of our current position */ + switch_snprintf(cid_buf, sizeof(cid_buf), "%s|%s", switch_str_nil(switch_event_get_header(phrase_params, "VM-Message-Caller-Number")), switch_str_nil(switch_event_get_header(phrase_params, "VM-Message-Caller-Name"))); + + /* Display MSG CID/Name to caller */ + msg.from = __FILE__; + msg.string_arg = cid_buf; + msg.message_id = SWITCH_MESSAGE_INDICATE_DISPLAY; + switch_core_session_receive_message(session, &msg); + + profile->current_msg = current_msg; + profile->current_msg_uuid = switch_core_session_strdup(session, switch_event_get_header(phrase_params, "VM-Message-UUID")); + + /* TODO check if msg is gone (purged by another session, notify user and auto jump to next message or something) */ + if (!skip_header) { + if (!initial_count_played) { + cmd = switch_core_session_sprintf(session, "json %s %s %s", profile->api_profile, profile->domain, profile->id); + jsonapi2event(session, phrase_params, profile->api_msg_count, cmd); + initial_count_played = SWITCH_TRUE; + // TODO captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "msg_count"), NULL, phrase_params, NULL, 0); + } + if (msg_count > 0) { + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "say_msg_number"), NULL, phrase_params, NULL, 0); + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "say_date"), NULL, phrase_params, NULL, 0); + } + } + if (msg_count > 0 && !skip_playback) { + /* TODO Update the Read date of a message (When msg start, or when it listen compleatly ??? To be determined */ + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "play_message"), NULL, phrase_params, NULL, 0); + } + skip_header = SWITCH_FALSE; + skip_playback = SWITCH_FALSE; + + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "menu_options"), NULL, phrase_params, NULL, DEFAULT_IVR_TIMEOUT); + + if (loc.result == RES_TIMEOUT) { + /* TODO Ask for the prompt Again IF retry != 0 */ + } else if (loc.result == RES_INVALID) { + /* TODO Say invalid option, and ask for the prompt again IF retry != 0 */ + } else if (loc.result == RES_FOUND) { /* Matching DTMF Key Pressed */ + const char *action = switch_event_get_header(menu.event_keys_dtmf, loc.dtmf_stored); + + /* Reset the try count */ + retry = MAX_ATTEMPT; + + if (action) { + if (!strcasecmp(action, "skip_intro")) { /* Skip Header / Play the recording again */ + skip_header = SWITCH_TRUE; + } else if (!strcasecmp(action, "next_msg")) { /* Next Message */ + next_msg++; + if (next_msg > msg_count) { + //playbackBufferDTMF(session, switch_event_get_header(menu.event_phrases, "no_more_messages"), NULL, NULL, NULL, 0); + retry = -1; + } + + } else if (!strcasecmp(action, "prev_msg")) { /* Previous Message */ + next_msg--; + } else if (!strcasecmp(action, "delete_msg")) { /* Delete / Undelete Message */ + const char *msg_flags = switch_event_get_header(phrase_params, "VM-Message-Flags"); + if (!msg_flags || strncasecmp(msg_flags, "delete", 6)) { + cmd = switch_core_session_sprintf(session, "%s %s %s %s", profile->api_profile, profile->domain, profile->id, switch_event_get_header(phrase_params, "VM-Message-UUID")); + vmivr_api_execute(session, profile->api_msg_delete, cmd); + + msg_deleted = SWITCH_TRUE; + /* TODO Option for auto going to next message or just return to the menu (So user used to do 76 to delete and next message wont be confused) */ + //next_msg++; + skip_header = skip_playback = SWITCH_TRUE; + } else { + cmd = switch_core_session_sprintf(session, "%s %s %s %s", profile->api_profile, profile->domain, profile->id, switch_event_get_header(phrase_params, "VM-Message-UUID")); + vmivr_api_execute(session, profile->api_msg_undelete, cmd); + + msg_undeleted = SWITCH_TRUE; + } + } else if (!strcasecmp(action, "save_msg")) { /* Save Message */ + cmd = switch_core_session_sprintf(session, "%s %s %s %s", profile->api_profile, profile->domain, profile->id, switch_event_get_header(phrase_params, "VM-Message-UUID")); + vmivr_api_execute(session, profile->api_msg_save, cmd); + + msg_saved = SWITCH_TRUE; + } else if (!strcasecmp(action, "callback")) { /* CallBack caller */ + const char *cid_num = switch_event_get_header(phrase_params, "VM-Message-Caller-Number"); + if (cid_num) { + /* TODO add detection for private number */ + switch_core_session_execute_exten(session, cid_num, "XML", profile->domain); + } else { + /* TODO Some error msg that the msg doesn't contain a caller number */ + } + } else if (!strncasecmp(action, "menu:", 5)) { /* Sub Menu */ + void (*fPtr)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(action+5); + if (fPtr) { + fPtr(session, profile); + } + } else if (!strcasecmp(action, "return")) { /* Return */ + retry = -1; + } + } + } + + /* IF the API to get the message returned us a COPY of the file locally (temp file create from a DB or from a web server), delete it */ + if (switch_true(switch_event_get_header(phrase_params, "VM-Message-Private-Local-Copy"))) { + const char *file_path = switch_event_get_header(phrase_params, "VM-Message-File-Path"); + if (file_path && unlink(file_path) != 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "Failed to delete temp file [%s]\n", file_path); + } + } + switch_event_destroy(&phrase_params); + } +done: + switch_event_destroy(&msg_list_params); + + free_profile_menu_event(&menu); + + return; +} + +void vmivr_menu_forward(switch_core_session_t *session, vmivr_profile_t *profile) { + + vmivr_menu_profile_t menu = { "std_forward_ask_prepend" }; + switch_channel_t *channel = switch_core_session_get_channel(session); + const char *prepend_filepath = NULL; + int retry; + switch_bool_t forward_msg = SWITCH_FALSE; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + if (!menu.event_keys_dtmf || !menu.event_phrases) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing Menu Phrases and Keys\n"); + return; + } + + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0; retry--) { + dtmf_ss_t loc; + char *dtmfa[16] = { 0 }; + switch_event_t *phrase_params = NULL; + + switch_event_create(&phrase_params, SWITCH_EVENT_REQUEST_PARAMS); + append_event_profile(phrase_params, profile, menu); + + populate_dtmfa_from_event(phrase_params, profile, menu, dtmfa); + + captureMenuInitialize(&loc, dtmfa); + + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "menu_options"), NULL, phrase_params, NULL, DEFAULT_IVR_TIMEOUT); + + if (loc.result == RES_TIMEOUT) { + /* TODO Ask for the prompt Again IF retry != 0 */ + } else if (loc.result == RES_INVALID) { + /* TODO Say invalid option, and ask for the prompt again IF retry != 0 */ + } else if (loc.result == RES_FOUND) { /* Matching DTMF Key Pressed */ + const char *action = switch_event_get_header(menu.event_keys_dtmf, loc.dtmf_stored); + + /* Reset the try count */ + retry = MAX_ATTEMPT; + + if (action) { + if (!strcasecmp(action, "return")) { /* Return to the previous menu */ + retry = -1; + forward_msg = SWITCH_FALSE; + } else if (!strcasecmp(action, "prepend")) { /* Prepend record msg */ + vmivr_menu_profile_t sub_menu = { "std_record_message" }; + char *tmp_filepath = generate_random_file_name(session, "voicemail_ivr", "wav" /* TODO make it configurable */); + switch_status_t status; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &sub_menu); + + status = vmivr_menu_record(session, profile, sub_menu, tmp_filepath); + + if (status == SWITCH_STATUS_SUCCESS) { + //char *cmd = switch_core_session_sprintf(session, "%s %s %s %d %s", profile->api_profile, profile->domain, profile->id, gnum, tmp_filepath); + //char *str_num = switch_core_session_sprintf(session, "%d", gnum); + //vmivr_api_execute(session, profile->api_pref_greeting_set, cmd); + //playbackBufferDTMF(session, switch_event_get_header(menu.event_phrases, "selected_slot"), str_num, NULL, NULL, 0); + prepend_filepath = tmp_filepath; + retry = -1; + forward_msg = SWITCH_TRUE; + } else { + /* TODO Error Recording msg */ + } + free_profile_menu_event(&sub_menu); + + } else if (!strcasecmp(action, "forward")) { /* Forward without prepend msg */ + retry = -1; + forward_msg = SWITCH_TRUE; + } else if (!strncasecmp(action, "menu:", 5)) { /* Sub Menu */ + void (*fPtr)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(action+5); + if (fPtr) { + fPtr(session, profile); + } + } + } + } + switch_event_destroy(&phrase_params); + + + } + /* Ask Extension to Forward */ + if (forward_msg) { + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0; retry--) { + const char *id = NULL; + vmivr_menu_profile_t sub_menu = { "std_forward_ask_extension" }; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &sub_menu); + + id = vmivr_menu_get_input_set(session, profile, sub_menu, "X."); + if (id) { + const char *cmd = switch_core_session_sprintf(session, "%s %s %s %s %s %s %s%s%s", profile->api_profile, profile->domain, profile->id, profile->current_msg_uuid, profile->domain, id, prepend_filepath?" ":"", prepend_filepath?prepend_filepath:"" ); + if (vmivr_api_execute(session, profile->api_msg_forward, cmd) == SWITCH_STATUS_SUCCESS) { + playbackBufferDTMF(session, switch_event_get_header(sub_menu.event_phrases, "ack"), "saved", NULL, NULL, 0); + retry = -1; + } else { + playbackBufferDTMF(session, switch_event_get_header(sub_menu.event_phrases, "invalid_extension"), NULL, NULL, NULL, 0); + } + } else { + /* TODO Prompt about input not valid */ + } + free_profile_menu_event(&sub_menu); + /* TODO add Confirmation of the transfered number */ + } + /* TODO Ask if we want to transfer the msg to more person */ + + } + + free_profile_menu_event(&menu); +} + + +void vmivr_menu_record_name(switch_core_session_t *session, vmivr_profile_t *profile) { + switch_status_t status; + vmivr_menu_profile_t menu = { "std_record_name" }; + + char *tmp_filepath = generate_random_file_name(session, "voicemail_ivr", "wav" /* TODO make it configurable */); + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + status = vmivr_menu_record(session, profile, menu, tmp_filepath); + + if (status == SWITCH_STATUS_SUCCESS) { + char *cmd = switch_core_session_sprintf(session, "%s %s %s %s", profile->api_profile, profile->domain, profile->id, tmp_filepath); + vmivr_api_execute(session, profile->api_pref_recname_set, cmd); + } +} + +void vmivr_menu_set_password(switch_core_session_t *session, vmivr_profile_t *profile) { + char *password; + vmivr_menu_profile_t menu = { "std_set_password" }; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + password = vmivr_menu_get_input_set(session, profile, menu, "XXX." /* TODO Conf Min 3 Digit */); + + /* TODO Add Prompts to tell if password was set and if it was not */ + if (password) { + char *cmd = switch_core_session_sprintf(session, "%s %s %s %s", profile->api_profile, profile->domain, profile->id, password); + vmivr_api_execute(session, profile->api_pref_password_set, cmd); + + } + + free_profile_menu_event(&menu); +} + +void vmivr_menu_authenticate(switch_core_session_t *session, vmivr_profile_t *profile) { + switch_channel_t *channel = switch_core_session_get_channel(session); + vmivr_menu_profile_t menu = { "std_authenticate" }; + int retry; + const char *auth_var = NULL; + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + if (profile->id && (auth_var = switch_channel_get_variable(channel, "voicemail_authorized")) && switch_true(auth_var)) { + profile->authorized = SWITCH_TRUE; + } + + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0 && profile->authorized == SWITCH_FALSE; retry--) { + const char *id = profile->id, *password = NULL; + char *cmd = NULL; + + if (!id) { + vmivr_menu_profile_t sub_menu = { "std_authenticate_ask_user" }; + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &sub_menu); + + id = vmivr_menu_get_input_set(session, profile, sub_menu, "X." /* TODO Conf Min 3 Digit */); + free_profile_menu_event(&sub_menu); + } + if (!password) { + vmivr_menu_profile_t sub_menu = { "std_authenticate_ask_password" }; + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &sub_menu); + + password = vmivr_menu_get_input_set(session, profile, sub_menu, "X." /* TODO Conf Min 3 Digit */); + free_profile_menu_event(&sub_menu); + } + cmd = switch_core_session_sprintf(session, "%s %s %s %s", profile->api_profile, profile->domain, id, password); + + if (vmivr_api_execute(session, profile->api_auth_login, cmd) == SWITCH_STATUS_SUCCESS) { + profile->id = id; + profile->authorized = SWITCH_TRUE; + } else { + playbackBufferDTMF(session, switch_event_get_header(menu.event_phrases, "fail_auth"), NULL, NULL, NULL, 0); + } + } + free_profile_menu_event(&menu); +} + +void vmivr_menu_select_greeting_slot(switch_core_session_t *session, vmivr_profile_t *profile) { + vmivr_menu_profile_t menu = { "std_select_greeting_slot" }; + + const char *result; + int gnum = -1; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + result = vmivr_menu_get_input_set(session, profile, menu, "X"); + + if (result) + gnum = atoi(result); + if (gnum != -1) { + char * cmd = switch_core_session_sprintf(session, "%s %s %s %d", profile->api_profile, profile->domain, profile->id, gnum); + if (vmivr_api_execute(session, profile->api_pref_greeting_set, cmd) == SWITCH_STATUS_SUCCESS) { + char *str_num = switch_core_session_sprintf(session, "%d", gnum); + playbackBufferDTMF(session, switch_event_get_header(menu.event_phrases, "selected_slot"), str_num, NULL, NULL, 0); + } else { + playbackBufferDTMF(session, switch_event_get_header(menu.event_phrases, "invalid_slot"), NULL, NULL, NULL, 0); + } + } + free_profile_menu_event(&menu); +} + +void vmivr_menu_record_greeting_with_slot(switch_core_session_t *session, vmivr_profile_t *profile) { + + vmivr_menu_profile_t menu = { "std_record_greeting_with_slot" }; + + const char *result; + int gnum = -1; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + result = vmivr_menu_get_input_set(session, profile, menu, "X"); + + if (result) + gnum = atoi(result); + + /* If user entered 0, we don't accept it */ + if (gnum > 0) { + vmivr_menu_profile_t sub_menu = { "std_record_greeting" }; + char *tmp_filepath = generate_random_file_name(session, "voicemail_ivr", "wav" /* TODO make it configurable */); + switch_status_t status; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &sub_menu); + + status = vmivr_menu_record(session, profile, sub_menu, tmp_filepath); + + if (status == SWITCH_STATUS_SUCCESS) { + char *cmd = switch_core_session_sprintf(session, "%s %s %s %d %s", profile->api_profile, profile->domain, profile->id, gnum, tmp_filepath); + char *str_num = switch_core_session_sprintf(session, "%d", gnum); + vmivr_api_execute(session, profile->api_pref_greeting_set, cmd); + playbackBufferDTMF(session, switch_event_get_header(menu.event_phrases, "selected_slot"), str_num, NULL, NULL, 0); + } + free_profile_menu_event(&sub_menu); + + } + + free_profile_menu_event(&menu); + +} + +void vmivr_menu_preference(switch_core_session_t *session, vmivr_profile_t *profile) { + switch_channel_t *channel = switch_core_session_get_channel(session); + + int retry; + + vmivr_menu_profile_t menu = { "std_preference" }; + + /* Initialize Menu Configs */ + populate_profile_menu_event(profile, &menu); + + if (!menu.event_keys_dtmf || !menu.event_phrases) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing Menu Phrases and Keys\n"); + return; + } + + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0; retry--) { + dtmf_ss_t loc; + char *dtmfa[16] = { 0 }; + switch_event_t *phrase_params = NULL; + + switch_event_create(&phrase_params, SWITCH_EVENT_REQUEST_PARAMS); + append_event_profile(phrase_params, profile, menu); + + populate_dtmfa_from_event(phrase_params, profile, menu, dtmfa); + + captureMenuInitialize(&loc, dtmfa); + + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "menu_options"), NULL, phrase_params, NULL, DEFAULT_IVR_TIMEOUT); + + if (loc.result == RES_TIMEOUT) { + /* TODO Ask for the prompt Again IF retry != 0 */ + } else if (loc.result == RES_INVALID) { + /* TODO Say invalid option, and ask for the prompt again IF retry != 0 */ + } else if (loc.result == RES_FOUND) { /* Matching DTMF Key Pressed */ + const char *action = switch_event_get_header(menu.event_keys_dtmf, loc.dtmf_stored); + + /* Reset the try count */ + retry = MAX_ATTEMPT; + + if (action) { + if (!strcasecmp(action, "return")) { /* Return to the previous menu */ + retry = -1; + } else if (!strncasecmp(action, "menu:", 5)) { /* Sub Menu */ + void (*fPtr)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(action+5); + if (fPtr) { + fPtr(session, profile); + } + } + } + } + switch_event_destroy(&phrase_params); + } + + free_profile_menu_event(&menu); +} + +char *vmivr_menu_get_input_set(switch_core_session_t *session, vmivr_profile_t *profile, vmivr_menu_profile_t menu, const char *input_mask) { + char *result = NULL; + int retry; + const char *terminate_key = NULL; + switch_channel_t *channel = switch_core_session_get_channel(session); + + if (!menu.event_keys_dtmf || !menu.event_phrases) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing Menu Phrases and Keys : %s\n", menu.name); + return result; + } + + terminate_key = switch_event_get_header(menu.event_keys_action, "ivrengine:terminate_entry"); + + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0; retry--) { + dtmf_ss_t loc; + char *dtmfa[16] = { 0 }; + int i; + switch_event_t *phrase_params = NULL; + + switch_event_create(&phrase_params, SWITCH_EVENT_REQUEST_PARAMS); + append_event_profile(phrase_params, profile, menu); + + populate_dtmfa_from_event(phrase_params, profile, menu, dtmfa); + + /* Find the last entry and append this one to it */ + for (i=0; dtmfa[i] && i < 16; i++){ + } + dtmfa[i] = (char *) input_mask; + + captureMenuInitialize(&loc, dtmfa); + if (terminate_key) { + loc.terminate_key = terminate_key[0]; + } + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "instructions"), NULL, phrase_params, NULL, DEFAULT_IVR_TIMEOUT); + + if (loc.result == RES_TIMEOUT) { + /* TODO Ask for the prompt Again IF retry != 0 */ + } else if (loc.result == RES_INVALID) { + /* TODO Say invalid option, and ask for the prompt again IF retry != 0 */ + } else if (loc.result == RES_FOUND) { /* Matching DTMF Key Pressed */ + + /* Reset the try count */ + retry = MAX_ATTEMPT; + + if (!strncasecmp(loc.completeMatch, input_mask, 1)) { + result = switch_core_session_strdup(session, loc.dtmf_stored); + retry = -1; + + } + } + switch_event_destroy(&phrase_params); + } + + return result; +} + +switch_status_t vmivr_menu_record(switch_core_session_t *session, vmivr_profile_t *profile, vmivr_menu_profile_t menu, const char *file_name) { + switch_status_t status = SWITCH_STATUS_FALSE; + switch_channel_t *channel = switch_core_session_get_channel(session); + int retry; + + switch_bool_t record_prompt = SWITCH_TRUE; + switch_bool_t listen_recording = SWITCH_FALSE; + switch_bool_t play_instruction = SWITCH_TRUE; + + if (!menu.event_keys_dtmf || !menu.event_phrases) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Missing Menu Phrases and Keys\n"); + return status; + } + + for (retry = MAX_ATTEMPT; switch_channel_ready(channel) && retry > 0; retry--) { + dtmf_ss_t loc; + + char *dtmfa[16] = { 0 }; + switch_event_t *phrase_params = NULL; + switch_file_handle_t fh = { 0 }; + + /* TODO Make the following configurable */ + fh.thresh = 200; + fh.silence_hits = 4; + //fh.samplerate = 8000; + + + switch_event_create(&phrase_params, SWITCH_EVENT_REQUEST_PARAMS); + append_event_profile(phrase_params, profile, menu); + + populate_dtmfa_from_event(phrase_params, profile, menu, dtmfa); + + captureMenuInitialize(&loc, dtmfa); + if (record_prompt) { + if (play_instruction) { + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "instructions"), NULL, phrase_params, NULL, 0); + } + play_instruction = SWITCH_TRUE; + + captureMenuRecord(session, &loc, phrase_params, file_name, &fh, 30 /* TODO Make max recording configurable */); + } else { + if (listen_recording) { + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, "VM-Record-File-Path", "%s", file_name); + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "play_recording"), NULL, phrase_params, NULL, 0); + listen_recording = SWITCH_FALSE; + + } + captureMenu(session, &loc, switch_event_get_header(menu.event_phrases, "menu_options"), NULL, phrase_params, NULL, DEFAULT_IVR_TIMEOUT); + } + + if (loc.recorded_audio) { + /* Reset the try count */ + retry = MAX_ATTEMPT; + + /* TODO Check if message is too short */ + + record_prompt = SWITCH_FALSE; + + } else if (loc.result == RES_TIMEOUT) { + /* TODO Ask for the prompt Again IF retry != 0 */ + } else if (loc.result == RES_INVALID) { + /* TODO Say invalid option, and ask for the prompt again IF retry != 0 */ + } else if (loc.result == RES_FOUND) { /* Matching DTMF Key Pressed */ + const char *action = switch_event_get_header(menu.event_keys_dtmf, loc.dtmf_stored); + + /* Reset the try count */ + retry = MAX_ATTEMPT; + + if (action) { + if (!strcasecmp(action, "listen")) { /* Listen */ + listen_recording = SWITCH_TRUE; + + } else if (!strcasecmp(action, "save")) { + retry = -1; + /* TODO ALLOW SAVE ONLY IF FILE IS RECORDED AND HIGHER THAN MIN SIZE */ + status = SWITCH_STATUS_SUCCESS; + + } else if (!strcasecmp(action, "rerecord")) { + record_prompt = SWITCH_TRUE; + + } else if (!strcasecmp(action, "skip_instruction")) { /* Skip Recording Greeting */ + play_instruction = SWITCH_FALSE; + + } else if (!strncasecmp(action, "menu:", 5)) { /* Sub Menu */ + void (*fPtr)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(action+5); + if (fPtr) { + fPtr(session, profile); + } + } else if (!strcasecmp(action, "return")) { /* Return */ + retry = -1; + } + } + } + switch_event_destroy(&phrase_params); + } + return status; +} + + +void (*vmivr_get_menu_function(const char *menu_name))(switch_core_session_t *session, vmivr_profile_t *profile) { + int i = 0; + + if (menu_name) { + for (i=0; menu_list[i].name ; i++) { + if (!strcasecmp(menu_list[i].name, menu_name)) { + return menu_list[i].pt2Func; + } + } + } + return NULL; +} + + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 + */ diff --git a/src/mod/applications/mod_voicemail_ivr/menu.h b/src/mod/applications/mod_voicemail_ivr/menu.h new file mode 100644 index 0000000000..f7b7d64a2b --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/menu.h @@ -0,0 +1,64 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * menu.h -- VoiceMail IVR Menu Include + * + */ +#ifndef _MENU_H_ +#define _MENU_H_ + +#include "config.h" + +void vmivr_menu_purge(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_authenticate(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_main(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_navigator(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_record_name(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_set_password(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_select_greeting_slot(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_record_greeting_with_slot(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_preference(switch_core_session_t *session, vmivr_profile_t *profile); +void vmivr_menu_forward(switch_core_session_t *session, vmivr_profile_t *profile); + +switch_status_t vmivr_menu_record(switch_core_session_t *session, vmivr_profile_t *profile, vmivr_menu_profile_t menu, const char *file_name); +char *vmivr_menu_get_input_set(switch_core_session_t *session, vmivr_profile_t *profile, vmivr_menu_profile_t menu, const char *input_mask); + + +struct vmivr_menu_function { + const char *name; + void (*pt2Func)(switch_core_session_t *session, vmivr_profile_t *profile); + +}; +typedef struct vmivr_menu_function vmivr_menu_function_t; + +extern vmivr_menu_function_t menu_list[]; + +void (*vmivr_get_menu_function(const char *menu_name))(switch_core_session_t *session, vmivr_profile_t *profile); + +#endif /* _MENU_H_ */ + diff --git a/src/mod/applications/mod_voicemail_ivr/mod_voicemail_ivr.c b/src/mod/applications/mod_voicemail_ivr/mod_voicemail_ivr.c new file mode 100644 index 0000000000..a576b45536 --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/mod_voicemail_ivr.c @@ -0,0 +1,139 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * mod_voicemail_ivr.c -- VoiceMail IVR System + * + */ +#include + +#include "config.h" +#include "menu.h" + +/* Prototypes */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_voicemail_ivr_shutdown); +SWITCH_MODULE_RUNTIME_FUNCTION(mod_voicemail_ivr_runtime); +SWITCH_MODULE_LOAD_FUNCTION(mod_voicemail_ivr_load); + +/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) + * Defines a switch_loadable_module_function_table_t and a static const char[] modname + */ +SWITCH_MODULE_DEFINITION(mod_voicemail_ivr, mod_voicemail_ivr_load, mod_voicemail_ivr_shutdown, NULL); + + +#define VMIVR_DESC "voicemail_ivr" +#define VMIVR_USAGE " profile domain [id]" + +SWITCH_STANDARD_APP(voicemail_ivr_function) +{ + const char *id = NULL; + const char *domain = NULL; + const char *profile_name = NULL; + vmivr_profile_t *profile = NULL; + int argc = 0; + char *argv[6] = { 0 }; + char *mydata = NULL; + + if (!zstr(data)) { + mydata = switch_core_session_strdup(session, data); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argv[1]) + profile_name = argv[1]; + + if (argv[2]) + domain = argv[2]; + + if (!strcasecmp(argv[0], "check")) { + if (argv[3]) + id = argv[3]; + + if (domain && profile_name) { + profile = get_profile(session, profile_name); + + if (profile) { + void (*fPtrAuth)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(profile->menu_check_auth); + void (*fPtrMain)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(profile->menu_check_main); + void (*fPtrTerminate)(switch_core_session_t *session, vmivr_profile_t *profile) = vmivr_get_menu_function(profile->menu_check_terminate); + + profile->domain = domain; + profile->id = id; + + if (fPtrAuth && !profile->authorized) { + fPtrAuth(session, profile); + } + + if (fPtrMain && profile->authorized) { + fPtrMain(session, profile); + } + if (fPtrTerminate) { + fPtrTerminate(session, profile); + } + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Profile '%s' not found\n", profile_name); + } + } + } + return; +} + +/* Macro expands to: switch_status_t mod_voicemail_ivr_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */ +SWITCH_MODULE_LOAD_FUNCTION(mod_voicemail_ivr_load) +{ + switch_application_interface_t *app_interface; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + SWITCH_ADD_APP(app_interface, "voicemail_ivr", "voicemail_ivr", VMIVR_DESC, voicemail_ivr_function, VMIVR_USAGE, SAF_NONE); + + /* indicate that the module should continue to be loaded */ + return status; +} + +/* + Called when the system shuts down + Macro expands to: switch_status_t mod_voicemail_ivr_shutdown() */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_voicemail_ivr_shutdown) +{ + + return SWITCH_STATUS_SUCCESS; +} + + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 + */ diff --git a/src/mod/applications/mod_voicemail_ivr/utils.c b/src/mod/applications/mod_voicemail_ivr/utils.c new file mode 100644 index 0000000000..3dd6524e34 --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/utils.c @@ -0,0 +1,181 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * utils.c -- VoiceMail IVR / Different utility that might need to go into the core (after cleanup) + * + */ +#include + +#include "utils.h" + +switch_status_t vmivr_merge_media_files(const char** inputs, const char *output) { + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_file_handle_t fh_output = { 0 }; + int channels = 1; + int rate = 8000; /* TODO Make this configurable */ + int j = 0; + + if (switch_core_file_open(&fh_output, output, channels, rate, SWITCH_FILE_FLAG_WRITE | SWITCH_FILE_DATA_SHORT, NULL) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't open %s\n", output); + goto end; + } + + for (j = 0; inputs[j] != NULL && j < 128 && status == SWITCH_STATUS_SUCCESS; j++) { + switch_file_handle_t fh_input = { 0 }; + char buf[2048]; + switch_size_t len = sizeof(buf) / 2; + + if (switch_core_file_open(&fh_input, inputs[j], channels, rate, SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT, NULL) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't open %s\n", inputs[j]); + status = SWITCH_STATUS_GENERR; + break; + } + + while (switch_core_file_read(&fh_input, buf, &len) == SWITCH_STATUS_SUCCESS) { + if (switch_core_file_write(&fh_output, buf, &len) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Write error\n"); + status = SWITCH_STATUS_GENERR; + break; + } + } + + if (fh_input.file_interface) { + switch_core_file_close(&fh_input); + } + } + + if (fh_output.file_interface) { + switch_core_file_close(&fh_output); + } +end: + return status; +} + +switch_event_t *jsonapi2event(switch_core_session_t *session, switch_event_t *apply_event, const char *api, const char *data) { + switch_event_t *phrases_event = NULL; + switch_stream_handle_t stream = { 0 }; + SWITCH_STANDARD_STREAM(stream); + switch_api_execute(api, data, session, &stream); + switch_event_create_json(&phrases_event, (char *) stream.data); + switch_safe_free(stream.data); + + if (apply_event) { + switch_event_header_t *hp; + for (hp = phrases_event->headers; hp; hp = hp->next) { + if (!strncasecmp(hp->name, "VM-", 3)) { + switch_event_add_header(apply_event, SWITCH_STACK_BOTTOM, hp->name, "%s", hp->value); + } + } + switch_event_destroy(&phrases_event); + phrases_event = apply_event; + + } + + return phrases_event; +} + +char *generate_random_file_name(switch_core_session_t *session, const char *mod_name, char *file_extension) { + char rand_uuid[SWITCH_UUID_FORMATTED_LENGTH + 1] = ""; + switch_uuid_t srand_uuid; + + switch_uuid_get(&srand_uuid); + switch_uuid_format(rand_uuid, &srand_uuid); + + return switch_core_session_sprintf(session, "%s%s%s_%s.%s", SWITCH_GLOBAL_dirs.temp_dir, SWITCH_PATH_SEPARATOR, mod_name, rand_uuid, file_extension); + +} + +switch_status_t vmivr_api_execute(switch_core_session_t *session, const char *apiname, const char *arguments) { + switch_status_t status = SWITCH_STATUS_SUCCESS; + + switch_stream_handle_t stream = { 0 }; + + SWITCH_STANDARD_STREAM(stream); + switch_api_execute(apiname, arguments, session, &stream); + if (!strncasecmp(stream.data, "-ERR", 4)) { + status = SWITCH_STATUS_GENERR; + } + switch_safe_free(stream.data); + return status; +} + +void append_event_profile(switch_event_t *phrase_params, vmivr_profile_t *profile, vmivr_menu_profile_t menu) { + /* Used for some appending function */ + if (profile->name && profile->id && profile->domain) { + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, "VM-Profile", "%s", profile->name); + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, "VM-Account-ID", "%s", profile->id); + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, "VM-Account-Domain", "%s", profile->domain); + } +} + +void populate_dtmfa_from_event(switch_event_t *phrase_params, vmivr_profile_t *profile, vmivr_menu_profile_t menu, char **dtmfa) { + int i = 0; + if (menu.event_keys_dtmf) { + switch_event_header_t *hp; + + for (hp = menu.event_keys_dtmf->headers; hp; hp = hp->next) { + if (strlen(hp->name) < 3 && hp->value) { /* TODO This is a hack to discard default FS Events ! */ + const char *varphrasename = switch_event_get_header(menu.event_keys_varname, hp->value); + dtmfa[i++] = hp->name; + + if (varphrasename && !zstr(varphrasename)) { + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, varphrasename, "%s", hp->name); + } + } + } + } + +} + +void append_event_message(switch_core_session_t *session, vmivr_profile_t *profile, switch_event_t *phrase_params, switch_event_t *msg_list_event, size_t current_msg) { + + char *varname; + char *apicmd; + char *total_msg = NULL; + + if (!msg_list_event || !(total_msg = switch_event_get_header(msg_list_event, "VM-List-Count")) || current_msg > atoi(total_msg)) { + /* TODO Error MSG */ + return; + } + + varname = switch_mprintf("VM-List-Message-%" SWITCH_SIZE_T_FMT "-UUID", current_msg); + apicmd = switch_mprintf("json %s %s %s %s", profile->api_profile, profile->domain, profile->id, switch_event_get_header(msg_list_event, varname)); + + switch_safe_free(varname); + + jsonapi2event(session, phrase_params, profile->api_msg_get, apicmd); + + /* TODO Set these 2 header correctly */ + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, "VM-Message-Type", "%s", "new"); + switch_event_add_header(phrase_params, SWITCH_STACK_BOTTOM, "VM-Message-Number", "%"SWITCH_SIZE_T_FMT, current_msg); + + switch_event_add_header_string(phrase_params, SWITCH_STACK_BOTTOM, "VM-Message-Private-Local-Copy", "False"); + + switch_safe_free(apicmd); +} + diff --git a/src/mod/applications/mod_voicemail_ivr/utils.h b/src/mod/applications/mod_voicemail_ivr/utils.h new file mode 100644 index 0000000000..a56152659e --- /dev/null +++ b/src/mod/applications/mod_voicemail_ivr/utils.h @@ -0,0 +1,47 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2011, Anthony Minessale II + * + * 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 + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Marc Olivier Chouinard + * + * + * utils.c -- VoiceMail IVR / Different utility that might need to go into the core (after cleanup) + * + */ +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include "config.h" + +switch_status_t vmivr_merge_files(const char** inputs, const char *output); + +void append_event_message(switch_core_session_t *session, vmivr_profile_t *profile, switch_event_t *phrase_params, switch_event_t *msg_list_event, size_t current_msg); +void append_event_profile(switch_event_t *phrase_params, vmivr_profile_t *profile, vmivr_menu_profile_t menu); +char *generate_random_file_name(switch_core_session_t *session, const char *mod_name, char *file_extension); +switch_event_t *jsonapi2event(switch_core_session_t *session, switch_event_t *apply_event, const char *api, const char *data); +switch_status_t vmivr_merge_media_files(const char** inputs, const char *output); +switch_status_t vmivr_api_execute(switch_core_session_t *session, const char *apiname, const char *arguments); +void populate_dtmfa_from_event(switch_event_t *phrase_params, vmivr_profile_t *profile, vmivr_menu_profile_t menu, char **dtmfa); +#endif /* _UTIL_H_ */ +