%%%----------------------------------------------------------------------------- %%% @copyright (C) 2012-2019, 2600Hz %%% @doc Allow caller to use the account resource to call out. %%% %%%

Data options:

%%%
%%%
`pin'
%%%
Optional: PIN code to allow caller use this feature.
%%% %%%
`max_digits'
%%%
Maximum digits allowed when collecting destination number. Default is 15 digits.
%%% %%%
`retries'
%%%
Optional: Maximum number of retries to collect PIN and/or destination number. Default is 3.
%%% %%%
`interdigit'
%%%
Optional: How long to wait for the next DTMF, in milliseconds
%%%
%%% %%% @author James Aimonetti %%% @end %%%----------------------------------------------------------------------------- -module(cf_disa). %% some recursion causes loops in cf_data_usage %% -behaviour(gen_cf_action). -include("callflow.hrl"). -export([handle/2]). -define(DEFAULT_USE_ACCOUNT_CALLER_ID, kapps_config:get_is_true(?CF_CONFIG_CAT, <<"default_use_account_caller_id">>, 'true')). -define(DEFAULT_PIN_LENGTH, kapps_config:get_integer(?CF_CONFIG_CAT, <<"default_pin_length">>, 10)). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec handle(kz_json:object(), kapps_call:call()) -> 'ok'. handle(Data, Call) -> lager:info("starting DISA handler"), kapps_call_command:answer(Call), Pin = kz_json:get_value(<<"pin">>, Data, <<>>), Retries = kz_json:get_integer_value(<<"retries">>, Data, 3), Interdigit = kz_json:get_integer_value(<<"interdigit">>, Data, kapps_call_command:default_interdigit_timeout()), case try_collect_pin(Call, Pin, Retries, Interdigit) of 'allow' -> allow_dial(Data, Call, Retries, Interdigit); 'fail' -> cf_exe:stop(Call) end. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec try_collect_pin(kapps_call:call(), binary(), non_neg_integer(), pos_integer()) -> 'allow' | 'fail'. try_collect_pin(_Call, <<>>, _Retries, _Interdigit) -> lager:warning("no pin set on DISA object, permitting"), 'allow'; try_collect_pin(Call, _Pin, 0, _Interdigit) -> lager:info("retries for DISA pin exceeded"), _ = kapps_call_command:b_prompt(<<"disa-retries_exceeded">>, Call), 'fail'; try_collect_pin(Call, Pin, Retries, Interdigit) -> Prompt = <<"disa-enter_pin">>, NoopId = kapps_call_command:prompt(Prompt, Call), PinLength = erlang:max(?DEFAULT_PIN_LENGTH, byte_size(Pin)), lager:debug("collecting up to ~p digits for pin", [PinLength]), case kapps_call_command:collect_digits(PinLength ,kapps_call_command:default_collect_timeout() ,Interdigit ,NoopId ,Call ) of {'ok', Pin} -> lager:info("pin matches, permitting"), 'allow'; {'ok', _Digits} -> lager:info("caller entered bad pin: '~s'", [_Digits]), _ = kapps_call_command:b_prompt(<<"disa-invalid_pin">>, Call), try_collect_pin(Call, Pin, Retries - 1, Interdigit); {'error', 'channel_hungup'} -> lager:info("channel has hungup, we're done"), 'fail' end. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec allow_dial(kz_json:object(), kapps_call:call(), non_neg_integer(), pos_integer()) -> 'ok'. allow_dial(_, Call, 0, _Interdigit) -> lager:info("retries exceeded for finding a callflow"), cf_exe:continue(Call); allow_dial(Data, Call, Retries, Interdigit) -> _ = start_preconnect_audio(Data, Call), Number = collect_destination_number(Call, Data, Interdigit), lager:info("caller is trying to call '~s'", [Number]), Call1 = maybe_update_caller_id(Call, should_use_account_cid(Data)), maybe_route_to_callflow(Data, Call1, Retries, Interdigit, Number). maybe_route_to_callflow(Data, Call, Retries, Interdigit, Number) -> case cf_flow:lookup(Number, kapps_call:account_id(Call)) of {'ok', Flow, NoMatch} -> lager:info("callflow ~s satisfies request", [kz_doc:id(Flow)]), Updates = [{fun kapps_call:set_request/2 ,list_to_binary([Number, "@", kapps_call:request_realm(Call)]) } ,{fun kapps_call:set_to/2, list_to_binary([Number, "@", kapps_call:to_realm(Call)])} ], cf_exe:set_call(kapps_call:exec(Updates, Call)), maybe_restrict_call(Data, Call, Number, Flow); _ -> lager:info("failed to find a callflow to satisfy ~s", [Number]), _ = kapps_call_command:b_prompt(<<"disa-invalid_extension">>, Call), allow_dial(Data, Call, Retries - 1, Interdigit) end. %%------------------------------------------------------------------------------ %% @doc Check collect digits to be not empty, if empty collect again %% (e.g. if previous callflow crashed during collecting digits before receiving pound %% FreeSWITCH still thinks it's collecting for the previous callflow, and collect digits %% for this module will be resulted to an empty binary) %% @end %%------------------------------------------------------------------------------ -spec collect_destination_number(kapps_call:call(), kz_json:object(), pos_integer()) -> kz_term:ne_binary(). collect_destination_number(Call, Data, Interdigit) -> MaxDigits = kz_json:get_integer_value(<<"max_digits">>, Data, 15), Timeout = kapps_call_command:default_collect_timeout(), lager:debug("collecting max ~p digits for destination number", [MaxDigits]), try_collect_destination_number(Call, Interdigit, MaxDigits, Timeout). -spec try_collect_destination_number(kapps_call:call(), pos_integer(), pos_integer(), pos_integer()) -> kz_term:ne_binary(). try_collect_destination_number(Call, Interdigit, MaxDigits, Timeout) -> case kapps_call_command:collect_digits(MaxDigits, Timeout, Interdigit, Call) of {'ok', <<>>} -> try_collect_destination_number(Call, Interdigit, MaxDigits, Timeout); {'ok', Digits} -> knm_converters:normalize(Digits); {'error', _E} -> lager:info("caller hungup while collecting destination number"), cf_exe:stop(Call) end. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec maybe_restrict_call(kz_json:object(), kapps_call:call(), kz_term:ne_binary(), kz_json:object()) -> 'ok'. maybe_restrict_call(Data, Call, Number, Flow) -> case should_restrict_call(Data, Call, Number) of 'true' -> lager:info("disa is restricted from making this call, terminate", []), _ = kapps_call_command:answer(Call), _ = kapps_call_command:prompt(<<"cf-unauthorized_call">>, Call), _ = kapps_call_command:queued_hangup(Call), 'ok'; 'false' -> cf_exe:branch(kz_json:get_json_value(<<"flow">>, Flow), Call) end. -spec should_use_account_cid(kz_json:object()) -> boolean(). should_use_account_cid(Data) -> kz_json:is_true(<<"use_account_caller_id">>, Data, ?DEFAULT_USE_ACCOUNT_CALLER_ID). -spec maybe_update_caller_id(kapps_call:call(), boolean()) -> kapps_call:call(). maybe_update_caller_id(Call, 'true') -> use_account_cid(Call); maybe_update_caller_id(Call, 'false') -> keep_original_cid(Call). -spec start_preconnect_audio(kz_json:object(), kapps_call:call()) -> 'ok'. start_preconnect_audio(Data, Call) -> case kz_json:get_ne_binary_value(<<"preconnect_audio">>, Data, <<"dialtone">>) of <<"dialtone">> -> lager:debug("playing dialtone..."), play_dialtone(Call); <<"ringing">> -> lager:debug("playing ringing..."), play_ringing(Data, Call); _Else -> lager:debug("unknown preconnect audio type: ~p", [_Else]) end. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec play_dialtone(kapps_call:call()) -> 'ok'. play_dialtone(Call) -> Tone = kz_json:from_list([{<<"Frequencies">>, [<<"350">>, <<"440">>]} ,{<<"Duration-ON">>, <<"10000">>} ,{<<"Duration-OFF">>, <<"0">>} ]), kapps_call_command:tones([Tone], Call). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec play_ringing(kz_json:object(), kapps_call:call()) -> 'ok'. play_ringing(Data, Call) -> RingRepeatCount = kz_json:get_integer_value(<<"ring_repeat_count">>, Data, 1), Tone = kz_json:from_list([{<<"Frequencies">>, [<<"440">>, <<"480">>]} ,{<<"Duration-ON">>, <<"2000">>} ,{<<"Duration-OFF">>, <<"4000">>} ,{<<"Repeat">>, RingRepeatCount} ]), kapps_call_command:tones([Tone], Call). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec use_account_cid(kapps_call:call()) -> kapps_call:call(). use_account_cid(Call) -> AccountId = kapps_call:account_id(Call), {Number, Name} = kz_attributes:get_account_external_cid(Call), lager:info("setting the caller id to <~s> ~s from account ~s", [Name, Number, AccountId]), set_cid(Number, Name, Call). -spec keep_original_cid(kapps_call:call()) -> kapps_call:call(). keep_original_cid(Call) -> Number = kapps_call:caller_id_number(Call), Name = kapps_call:caller_id_name(Call), lager:info("keep the original caller id <~s> ~s", [Name, Number]), set_cid(Number, Name, Call). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec set_cid(kz_term:ne_binary(), kz_term:ne_binary(), kapps_call:call()) -> kapps_call:call(). set_cid(Number, Name, Call) -> Props = [{<<"Retain-CID">>, 'true'} ,{<<"Caller-ID-Number">>, Number} ,{<<"Caller-ID-Name">>, Name} ], Updates = [fun(C) -> kapps_call:set_caller_id_number(Number, C) end ,fun(C) -> kapps_call:set_caller_id_name(Name, C) end ,fun(C) -> kapps_call:set_custom_channel_vars(Props, C) end ], kapps_call:exec(Updates, Call). %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec should_restrict_call(kz_json:object(), kapps_call:call(), kz_term:ne_binary()) -> boolean(). should_restrict_call(Data, Call, Number) -> case kz_json:is_true(<<"enforce_call_restriction">>, Data, 'false') of 'false' -> 'false'; 'true' -> should_restrict_call_by_account(Call, Number) end. %%------------------------------------------------------------------------------ %% @doc %% @end %%------------------------------------------------------------------------------ -spec should_restrict_call_by_account(kapps_call:call(), kz_term:ne_binary()) -> boolean(). should_restrict_call_by_account(Call, Number) -> case kzd_accounts:fetch(kapps_call:account_id(Call)) of {'error', _} -> 'false'; {'ok', JObj} -> Classification = knm_converters:classify(Number), lager:info("classified number as ~p", [Classification]), <<"deny">> =:= kz_json:get_ne_binary_value([<<"call_restriction">>, Classification, <<"action">>], JObj) end.