GitHub PullRequestReviewCommentEvent at 2014-05-18 05:54:27
Repository: kazoo
Actor: jamesaimonetti
Payload: {"comment":{"url":"https://api.github.com/repos/2600hz/kazoo/pulls/comments/12772305","id":12772305,"diff_hunk":"@@ -0,0 +1,258 @@\n+%%%-------------------------------------------------------------------\n+%%% @copyright (C) 2013-2014, 2600hz, INC\n+%%% @doc\n+%%% Eacesdrop\n+%%%\n+%%% data: {\n+%%%   \"user_id\":\"_user_id_\"\n+%%%   ,\"device_id\":\"_device_id_\"\n+%%% }\n+%%%\n+%%% One of the two - user_id, or device_id - must be defined on\n+%%% the data payload. Preference is given by most restrictive option set,\n+%%% so device_id is checked for first, then user_id.\n+%%%\n+%%% device_id will only connect to a channel of a specific device,\n+%%% user_id will only connect to channel on any of the user's devices*\n+%%%\n+%%% @end\n+%%% @contributors\n+%%%   SIPLABS LLC (Mikhail Rodionov)\n+%%%   SIPLABS LLC (Maksim Krzhemenevskiy)\n+%%%-------------------------------------------------------------------\n+-module(cf_eavesdrop).\n+\n+-include(\"../callflow.hrl\").\n+\n+-export([handle/2]).\n+\n+%%--------------------------------------------------------------------\n+%% @public\n+%% @doc\n+%% Entry point for this module sends an arbitrary response back to the\n+%% call originator.\n+%% @end\n+%%--------------------------------------------------------------------\n+-spec handle(wh_json:object(), whapps_call:call()) -> any().\n+handle(Data, Call) ->\n+    _ = case maybe_allowed_to_eavesdrop(Data, Call) of\n+            'true' ->\n+                case find_sip_endpoints(Data, Call) of\n+                    [] -> no_users(Call);\n+                    Usernames -> eavesdrop_channel(Usernames, Call)\n+                end;\n+            'false' -> no_permission_to_eavesdrop(Call)\n+        end,\n+    cf_exe:stop(Call).\n+\n+-spec maybe_allowed_to_eavesdrop(wh_json:object(), whapps_call:call()) -> boolean().\n+maybe_allowed_to_eavesdrop(Data, Call) ->\n+    case wh_json:get_value(<<\"approved_device_id\">>, Data) of\n+        'undefined' ->\n+            case wh_json:get_value(<<\"approved_user_id\">>, Data) of\n+                'undefined' ->\n+                    case wh_json:get_value(<<\"approved_group_id\">>, Data) of\n+                        'undefined' -> 'true'\n+                    end;\n+                UserId -> maybe_belongs_to_user(UserId, Call)\n+            end;\n+        DeviceId ->\n+            % Compare approved device_id with calling one\n+            DeviceId == whapps_call:authorizing_id(Call)\n+    end.\n+\n+-spec maybe_belongs_to_user(ne_binary(), whapps_call:call()) -> boolean().\n+maybe_belongs_to_user(UserId, Call) ->\n+    is_in_list(whapps_call:authorizing_id(Call), find_user_endpoints([UserId],[],Call)).\n+\n+-spec eavesdrop_channel(ne_binaries(), whapps_call:call()) -> 'ok'.\n+eavesdrop_channel(Usernames, Call) ->\n+    case find_channels(Usernames, Call) of\n+        [] -> no_channels(Call);\n+        Channels -> eavesdrop_a_channel(Channels, Call)\n+    end,\n+    'ok'.\n+\n+-spec eavesdrop_a_channel(wh_json:objects(), whapps_call:call()) -> 'ok'.\n+eavesdrop_a_channel(Channels, Call) ->\n+    MyUUID = whapps_call:call_id(Call),\n+    MyMediaServer = whapps_call:switch_nodename(Call),\n+\n+    lager:debug(\"looking for channels on my node ~s that aren't me\", [MyMediaServer]),\n+\n+    case sort_channels(Channels, MyUUID, MyMediaServer) of\n+        {[], []} ->\n+            lager:debug(\"no channels available to eavesdrop\"),\n+            no_channels(Call);\n+        {[], [RemoteUUID|_Remote]} ->\n+            lager:debug(\"no calls on my media server, trying ~s\", [RemoteUUID]),\n+            eavesdrop_call(RemoteUUID, Call);\n+        {[LocalUUID|_Cs], _} ->\n+            lager:debug(\"found a call (~s) on my media server\", [LocalUUID]),\n+            eavesdrop_call(LocalUUID, Call)\n+    end.\n+\n+-spec sort_channels(wh_json:objects(), ne_binary(), ne_binary()) ->\n+                           {ne_binaries(), ne_binaries()}.\n+-spec sort_channels(wh_json:objects(), ne_binary(), ne_binary(), {ne_binaries(), ne_binaries()}) ->\n+                           {ne_binaries(), ne_binaries()}.\n+sort_channels(Channels, MyUUID, MyMediaServer) ->\n+    sort_channels(Channels, MyUUID, MyMediaServer, {[], []}).\n+sort_channels([], _MyUUID, _MyMediaServer, Acc) -> Acc;\n+sort_channels([Channel|Channels], MyUUID, MyMediaServer, Acc) ->\n+    lager:debug(\"channel: c: ~s a: ~s n: ~s oleg: ~s\", [wh_json:get_value(<<\"uuid\">>, Channel)\n+                                                        ,wh_json:is_true(<<\"answered\">>, Channel)\n+                                                        ,wh_json:get_value(<<\"node\">>, Channel)\n+                                                        ,wh_json:get_value(<<\"other_leg\">>, Channel)\n+                                                       ]),\n+            maybe_add_leg(Channels, MyUUID, MyMediaServer, Acc, Channel).\n+\n+-spec maybe_add_leg(wh_json:objects(), ne_binary(), ne_binary(), {ne_binaries(), ne_binaries()}, wh_json:object()) ->\n+                                      {ne_binaries(), ne_binaries()}.\n+maybe_add_leg(Channels, MyUUID, MyMediaServer, {Local, Remote}=Acc, Channel) ->\n+    case wh_json:get_value(<<\"node\">>, Channel) of\n+        MyMediaServer ->\n+            case wh_json:get_value(<<\"uuid\">>, Channel) of\n+                MyUUID ->\n+                    sort_channels(Channels, MyUUID, MyMediaServer, Acc);\n+                _UUID ->\n+                    sort_channels(Channels, MyUUID, MyMediaServer, {maybe_add_other_leg(Channel, Local), Remote})\n+            end;\n+        _OtherMediaServer ->\n+            sort_channels(Channels, MyUUID, MyMediaServer, {Local, maybe_add_other_leg(Channel, Remote)})\n+    end.\n+\n+-spec maybe_add_other_leg(wh_json:object(), ne_binaries()) -> ne_binaries().\n+maybe_add_other_leg(Channel, Legs) ->\n+    case wh_json:get_value(<<\"other_leg\">>, Channel) of\n+        'undefined' -> Legs;\n+        Leg -> [Leg | Legs]\n+    end.\n+\n+-spec eavesdrop_call(ne_binary(), whapps_call:call()) -> 'ok'.\n+eavesdrop_call(UUID, Call) ->\n+    _ = whapps_call_command:send_command(eavesdrop_cmd(UUID), Call),\n+    case wait_for_eavesdrop(Call) of\n+        {'error', _E} ->\n+            lager:debug(\"failed to eavesdrop ~s: ~p\", [UUID, _E]);\n+        'ok' ->\n+            lager:debug(\"call is being eavesdropper\"),\n+            whapps_call_command:wait_for_hangup(),\n+            lager:debug(\"hangup recv\")\n+    end.\n+\n+-spec eavesdrop_cmd(ne_binary()) -> wh_proplist().\n+eavesdrop_cmd(TargetCallId) ->\n+    [{<<\"Application-Name\">>, <<\"eavesdrop\">>}\n+     ,{<<\"Target-Call-ID\">>, TargetCallId}\n+     ,{<<\"Enable-DTMF\">>, 'true'}\n+    ].\n+\n+-spec wait_for_eavesdrop(whapps_call:call()) ->\n+                             'ok' |\n+                             {'error', 'failed'} |\n+                             {'error', 'timeout'}.\n+wait_for_eavesdrop(Call) ->\n+    case whapps_call_command:receive_event(10000) of\n+        {'ok', Evt} ->\n+            eavesdrop_event(Call, wh_util:get_event_type(Evt), Evt);\n+        {'error', 'timeout'}=E ->\n+            lager:debug(\"timed out\"),\n+            E\n+    end.\n+\n+-spec eavesdrop_event(whapps_call:call(), {ne_binary(), ne_binary()}, wh_json:object()) ->\n+                          {'error', 'failed' | 'timeout'} |\n+                          'ok'.\n+eavesdrop_event(_Call, {<<\"error\">>, <<\"dialplan\">>}, Evt) ->\n+    lager:debug(\"error in dialplan: ~s\", [wh_json:get_value(<<\"Error-Message\">>, Evt)]),\n+    {'error', 'failed'};\n+eavesdrop_event(_Call, {<<\"call_event\">>,<<\"CHANNEL_BRIDGE\">>}, _Evt) ->\n+    lager:debug(\"channel bridged to ~s\", [wh_json:get_value(<<\"Other-Leg-Call-ID\">>, _Evt)]);\n+eavesdrop_event(Call, _Type, _Evt) ->\n+    lager:debug(\"unhandled evt ~p\", [_Type]),\n+    wait_for_eavesdrop(Call).\n+\n+-spec find_channels(ne_binaries(), whapps_call:call()) -> wh_json:objects().\n+find_channels(Usernames, Call) ->\n+    Realm = wh_util:get_account_realm(whapps_call:account_id(Call)),\n+    lager:debug(\"finding channels for realm ~s, usernames ~p\", [Realm, Usernames]),\n+    Req = [{<<\"Realm\">>, Realm}\n+           ,{<<\"Usernames\">>, Usernames}\n+           | wh_api:default_headers(?APP_NAME, ?APP_VERSION)\n+          ],\n+    case whapps_util:amqp_pool_request(Req\n+                                       ,fun wapi_call:publish_query_user_channels_req/1\n+                                       ,fun wapi_call:query_user_channels_resp_v/1\n+                                      )\n+    of\n+        {'ok', Resp} -> wh_json:get_value(<<\"Channels\">>, Resp, []);\n+        {'error', _E} ->\n+            lager:debug(\"failed to get channels: ~p\", [_E]),\n+            []\n+    end.\n+\n+-spec find_sip_endpoints(wh_json:object(), whapps_call:call()) ->\n+                                ne_binaries().\n+find_sip_endpoints(Data, Call) ->\n+    case wh_json:get_value(<<\"device_id\">>, Data) of\n+        'undefined' ->\n+            case wh_json:get_value(<<\"user_id\">>, Data) of\n+                UserId ->\n+                    sip_users_from_endpoints(\n+                      find_user_endpoints([UserId], [], Call), Call\n+                     )\n+            end;\n+        DeviceId ->\n+             sip_users_from_endpoints([DeviceId], Call)\n+    end.\n+\n+-spec sip_users_from_endpoints(ne_binaries(), whapps_call:call()) -> ne_binaries().\n+sip_users_from_endpoints(EndpointIds, Call) ->\n+    lists:foldl(fun(EndpointId, Acc) ->\n+                        case sip_user_of_endpoint(EndpointId, Call) of\n+                            'undefined' -> Acc;\n+                            Username -> [Username|Acc]\n+                        end\n+                end, [], EndpointIds).\n+\n+-spec sip_user_of_endpoint(ne_binary(), whapps_call:call()) -> api_binary().\n+sip_user_of_endpoint(EndpointId, Call) ->\n+    case cf_endpoint:get(EndpointId, Call) of\n+        {'error', _} -> 'undefined';\n+        {'ok', Endpoint} ->\n+            wh_json:get_value([<<\"sip\">>, <<\"username\">>], Endpoint)\n+    end.\n+\n+-spec find_user_endpoints(ne_binaries(), ne_binaries(), whapps_call:call()) ->\n+                                 ne_binaries().\n+find_user_endpoints([], DeviceIds, _) -> DeviceIds;\n+find_user_endpoints(UserIds, DeviceIds, Call) ->\n+    UserDeviceIds = cf_attributes:owned_by(UserIds, <<\"device\">>, Call),\n+    lists:merge(lists:sort(UserDeviceIds), DeviceIds).\n+\n+\n+-spec no_users(whapps_call:call()) -> any().\n+no_users(Call) ->\n+    whapps_call_command:answer(Call),\n+    whapps_call_command:b_play(<<\"system_media/pickup-no_users\">>, Call).\n+\n+-spec no_channels(whapps_call:call()) -> any().\n+no_channels(Call) ->\n+    whapps_call_command:answer(Call),\n+    whapps_call_command:b_play(<<\"system_media/pickup-no_channels\">>, Call).\n+\n+-spec no_permission_to_eavesdrop(whapps_call:call()) -> any().\n+%% TODO: please convert to system_media file (say is not consistent on deployments)\n+no_permission_to_eavesdrop(Call) ->\n+    whapps_call_command:answer(Call),\n+    whapps_call_command:b_say(<<\"you have no permission to eavesdrop this call\">>, Call).\n+\n+-spec is_in_list(api_binary(), ne_binaries()) -> boolean().","path":"applications/callflow/src/module/cf_eavesdrop.erl","position":251,"original_position":251,"commit_id":"444a0fe80c0ad2ee69064ac6f21fc9c8cd99fd51","original_commit_id":"444a0fe80c0ad2ee69064ac6f21fc9c8cd99fd51","user":{"login":"jamesaimonetti","id":160921,"avatar_url":"https://avatars.githubusercontent.com/u/160921?","gravatar_id":"4c3044bc0d96fdf0f4f28acafb319f0b","url":"https://api.github.com/users/jamesaimonetti","html_url":"https://github.com/jamesaimonetti","followers_url":"https://api.github.com/users/jamesaimonetti/followers","following_url":"https://api.github.com/users/jamesaimonetti/following{/other_user}","gists_url":"https://api.github.com/users/jamesaimonetti/gists{/gist_id}","starred_url":"https://api.github.com/users/jamesaimonetti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jamesaimonetti/subscriptions","organizations_url":"https://api.github.com/users/jamesaimonetti/orgs","repos_url":"https://api.github.com/users/jamesaimonetti/repos","events_url":"https://api.github.com/users/jamesaimonetti/events{/privacy}","received_events_url":"https://api.github.com/users/jamesaimonetti/received_events","type":"User","site_admin":false},"body":"Remove this function. No need for it","created_at":"2014-05-18T05:54:27Z","updated_at":"2014-05-18T05:54:27Z","html_url":"https://github.com/2600hz/kazoo/pull/337#discussion_r12772305","pull_request_url":"https://api.github.com/repos/2600hz/kazoo/pulls/337","_links":{"self":{"href":"https://api.github.com/repos/2600hz/kazoo/pulls/comments/12772305"},"html":{"href":"https://github.com/2600hz/kazoo/pull/337#discussion_r12772305"},"pull_request":{"href":"https://api.github.com/repos/2600hz/kazoo/pulls/337"}}}}