Created
July 19, 2025 09:15
-
-
Save voughtdq/1799160e125593bb4f19160d07416165 to your computer and use it in GitHub Desktop.
Kazoo Elixir Port
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule Kazoo do | |
require Logger | |
# original from https://github.com/2600hz/kazoo/blob/4.3.142/applications/ecallmgr/src/mod_kazoo.erl | |
@timeout 3000 | |
@type freeswitch_node :: atom | |
@type freeswitch_section_binding :: | |
:config | :directory | :dialplan | :languages | :chatplan | :channels | |
@type freeswitch_event_binding :: atom | |
@type freeswitch_api_ok :: {:ok, binary()} | :ok | |
@type freeswitch_api_error :: {:error, :timeout | :exception | :badarg | binary()} | |
@type freeswitch_api_return :: freeswitch_api_ok() | freeswitch_api_error() | |
@doc "Returns the current mod_kazoo version running on FreeSWITCH." | |
@spec version(node :: freeswitch_node) :: freeswitch_api_return() | |
def version(node), do: call(node, :version) | |
@doc "Instructs FreeSWITCH to stop sending events." | |
@spec no_events(node :: freeswitch_node) :: freeswitch_api_return() | |
def no_events(node), do: call(node, :noevents) | |
@doc "Closes the connection to the FreeSWITCH node." | |
@spec close(node :: freeswitch_node) :: :ok | |
def close(node), do: call(node, :exit) | |
@doc "Returns the PID for the FreeSWITCH node." | |
@spec get_pid(node :: freeswitch_node) :: {:ok, pid} | |
def get_pid(node), do: call(node, :getpid) | |
@doc """ | |
Instructs the FreeSWITCH node to begin requesting configuration from | |
the calling process. | |
## Examples | |
iex> bind(:"[email protected]", :dialplan) | |
""" | |
@spec bind(node :: freeswitch_node, binding :: freeswitch_section_binding) :: | |
freeswitch_api_return() | |
def bind(node, binding), do: call(node, {:bind, binding}) | |
@doc """ | |
Reply to a configuration request sent from a FreeSWITCH node. | |
""" | |
@spec fetch_reply( | |
node :: freeswitch_node, | |
fetch_id :: String.t(), | |
section :: String.t(), | |
reply :: String.t() | |
) :: freeswitch_api_return() | |
def fetch_reply(node, fetch_id, section, reply), | |
do: cast(node, {:fetch_reply, section, fetch_id, reply}) | |
@doc """ | |
Instructs FreeSWITCH to start sending events. Returns a tuple with | |
the IP and port that we can listen on to consume the events. | |
""" | |
@spec subscribe_events(node :: freeswitch_node, bindings :: [freeswitch_event_binding]) :: | |
{:ok, :inet.ip(), :inet.port()} | |
def subscribe_events(node, bindings), do: call(node, {:event, List.wrap(bindings)}) | |
@doc """ | |
Call an API on FreeSWITCH. | |
""" | |
@spec api(node :: atom, cmd :: atom, args :: String.t()) :: freeswitch_api_return() | |
def api(node, cmd, args \\ ""), do: call(node, {:api, cmd, args}) | |
@doc """ | |
Calls an API command in a background thread, returning the job id. The caller is responsible for receiving the message | |
""" | |
def async_bgapi(node, cmd, args \\ "") do | |
call(node, {:bgapi, cmd, args}) | |
end | |
@doc """ | |
Calls an API command in a background thread. | |
""" | |
@spec bgapi(node :: atom, cmd :: atom, args :: String.t()) :: freeswitch_api_return() | |
def bgapi(node, cmd, args \\ "") do | |
parent = self() | |
spawn(fn -> | |
try do | |
call(node, {:bgapi, cmd, args}) | |
catch | |
e, r -> | |
_ = | |
Logger.info(fn -> | |
formatted = Exception.format(e, r) | |
{"exec bgapi #{to_string(cmd)} #{args} failed with #{formatted}", [node: node]} | |
end) | |
send(parent, {:api, {:error, :exception}}) | |
else | |
{:ok, job_id} = job_ok -> | |
send(parent, {:api, job_ok}) | |
receive do | |
{:bgok, ^job_id, _} = bg_ok -> send(parent, bg_ok) | |
{:bgerror, ^job_id, _} = bg_error -> send(parent, bg_error) | |
after | |
@timeout -> send(parent, {:bgerror, job_id, :timeout}) | |
end | |
{:error, reason} -> | |
send(parent, {:api, {:error, reason}}) | |
:timeout -> | |
send(parent, {:api, {:error, :timeout}}) | |
end | |
end) | |
receive do | |
{:api, result} -> result | |
end | |
end | |
def bgapi4(node, cmd, args, fun, params) do | |
parent = self() | |
spawn(fn -> | |
case call(node, {:bgapi4, cmd, args}) do | |
{:ok, "-ERR " <> reason} -> | |
send(parent, {:api, internal_fs_error(reason)}) | |
{:ok, job_id} = job_ok -> | |
send(parent, {:api, job_ok}) | |
receive do | |
{:bgok, ^job_id, reply} when is_function(fun, 3) -> | |
send(parent, fun.(:ok, reply, [job_id | params])) | |
{:bgerror, ^job_id, reply} when is_function(fun, 3) -> | |
send(parent, fun.(:error, reply, [job_id | params])) | |
{:bgok, ^job_id, reply, data} when is_function(fun, 4) -> | |
fun.(:ok, reply, data, [job_id | params]) | |
{:bgerror, ^job_id, reply, data} when is_function(fun, 4) -> | |
fun.(:error, reply, data, [job_id | params]) | |
end | |
{:error, _} = err -> | |
send(parent, {:api, err}) | |
:timeout -> | |
send(parent, {:api, {:error, :timeout}}) | |
end | |
end) | |
receive do | |
{:api, result} -> result | |
end | |
end | |
def json_api(node, cmd, args \\ nil, uuid \\ nil) do | |
json = | |
Jason.encode!(%{ | |
"command" => cmd, | |
"data" => args, | |
"uuid" => uuid | |
}) | |
case call(node, {:json_api, json}) do | |
{:ok, response} when is_binary(response) -> | |
{:ok, Jason.decode!(response)} | |
{:error, response} when is_binary(response) -> | |
{:error, Jason.decode!(response)} | |
other -> | |
other | |
end | |
end | |
@doc false | |
def call(node, args, timeout \\ @timeout) do | |
GenServer.call({:mod_kazoo, node}, args, timeout) | |
catch | |
:exit, {{:nodedown, node}, _} -> | |
{:error, {:nodedown, node}} | |
error, reason -> | |
Logger.warning(fn -> | |
{ | |
"Attempt to call #{inspect(args)} failed:\n" <> Exception.format(error, reason), | |
node: node | |
} | |
end) | |
{:error, :exception} | |
end | |
def cast(node, args), do: GenServer.cast({:mod_kazoo, node}, args) | |
def internal_fs_error(reason) do | |
error = String.replace(reason, "\n", "") | |
{:error, error} | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks again! I've been meaning to dive into
mod_kazoo
for a while now, but it's undocumented status discouraged, so I appreciate giving me an entry point:)