defmodule WeChat.Work.Agent do
@moduledoc "应用"
alias WeChat.Work
alias WeChat.ServerMessage.Encryptor
alias WeChat.Storage.Cache
@term_introduction_doc_link "#{WeChat.Utils.work_doc_link_prefix()}/90665"
@typedoc """
每个应用都有唯一的 agentid -
[官方文档](#{@term_introduction_doc_link}#agentid)
在管理后台->“应用与小程序”->“应用”,点进某个应用,即可看到 agentid
"""
@type agent_id :: integer | atom
@type agent_name :: atom | String.t()
@typedoc """
secret 是企业应用里面用于保障数据安全的“钥匙” -
[官方文档](#{@term_introduction_doc_link}#secret)
每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret务必不能泄漏。
目前 `secret` 有:
- 自建应用 `secret`
在管理后台->“应用与小程序”->“应用”->“自建”,点进某个应用,即可看到。
- 基础应用 `secret`
某些基础应用(如“审批”“打卡”应用),支持通过API进行操作。在管理后台->“应用与小程序”->“应用->”“基础”,点进某个应用,点开“API”小按钮,即可看到。
- 通讯录管理 `secret`
在“管理工具”-“通讯录同步”里面查看(需开启“API接口同步”);
- 客户联系管理 `secret`
在“客户联系”栏,点开“API”小按钮,即可看到。
"""
@type secret :: String.t()
@type options :: Keyword.t()
@type refresh_key :: :js_api_ticket | :agent_js_api_ticket
@type refresh_list :: [refresh_key]
@typedoc "应用配置"
@type t :: %__MODULE__{
id: agent_id,
name: agent_name,
secret: secret,
token: WeChat.token(),
encoding_aes_key: Encryptor.encoding_aes_key(),
aes_key: Encryptor.aes_key(),
refresh_list: [],
cache_id: Cache.cache_id()
}
@enforce_keys [:id]
defstruct [:name, :id, :secret, :token, :encoding_aes_key, :aes_key, :refresh_list, :cache_id]
@spec find_agent(Work.client(), Work.agent()) :: t | nil
def find_agent(client, id) when is_integer(id) do
Enum.find(client.agents(), &match?(%{id: ^id}, &1))
end
def find_agent(client, name) do
Enum.find(client.agents(), &match?(%{name: ^name}, &1))
end
@spec fetch_agent!(Work.client(), Work.agent()) :: t
def fetch_agent!(client, agent) do
if agent = find_agent(client, agent) do
agent
else
raise "missing #{inspect(agent)} for #{inspect(client)}, maybe it's a wrong name or id, maybe it not set in agents."
end
end
@spec fetch_agent_cache_id!(Work.client(), Work.agent()) :: Cache.cache_id()
def fetch_agent_cache_id!(client, agent) do
fetch_agent!(client, agent) |> Map.fetch!(:cache_id)
end
@spec name2id(Work.client(), agent_name) :: agent_id | nil
def name2id(client, name) do
with %{id: id} <- Enum.find(client.agents(), &match?(%{name: ^name}, &1)) do
id
end
end
@spec agent2id(Work.client(), Work.agent()) :: agent_id | nil
def agent2id(_client, id) when is_integer(id), do: id
def agent2id(client, name), do: name2id(client, name)
@doc "构建应用(agent)"
@spec agent(agent_id, options) :: t
def agent(id, options \\ []) do
struct(%__MODULE__{id: id, name: id}, options)
|> transfer_aes_key()
end
@doc "构建[通讯录]应用(agent)"
@spec contacts_agent(options) :: t
def contacts_agent(options \\ []) do
struct(%__MODULE__{id: :contacts, name: :contacts}, options)
|> transfer_aes_key()
end
@doc "构建[客户联系]应用(agent)"
@spec customer_agent(options) :: t
def customer_agent(options \\ []) do
struct(%__MODULE__{id: :customer, name: :customer}, options)
|> transfer_aes_key()
end
@doc "构建[微信客服]应用(agent)"
@spec kf_agent(options) :: t
def kf_agent(options \\ []) do
struct(%__MODULE__{id: :kf, name: :kf}, options)
|> transfer_aes_key()
end
def maybe_init_work_agents(client) do
with {:ok, configs} <- Application.fetch_env(:wechat, client),
agents <- client.agents(),
{:ok, ^agents} <- Keyword.fetch(configs, :agents),
true <- Enum.any?(agents, &match?(%{cache_id: nil}, &1)) do
agents
|> Enum.all?(&is_struct(&1, __MODULE__))
|> unless do
raise ArgumentError, "please set WeChat.Work.Agent struct for :agents option"
end
corp_id = client.appid()
Enum.map(agents, fn agent ->
Map.put(agent, :cache_id, "#{corp_id}_#{agent.id}")
end)
|> then(&Keyword.put(configs, :agents, &1))
|> then(&Application.put_env(:wechat, client, &1))
end
end
def append_agent(client, agent) when is_struct(agent, __MODULE__) do
with {_, nil} <- {:exist, find_agent(client, agent.id)},
{_, nil} <- {:exist, find_agent(client, agent.name)},
{:ok, configs} <- Application.fetch_env(:wechat, client),
{:ok, agents} <- Keyword.fetch(configs, :agents) do
agent =
unless agent.cache_id do
corp_id = client.appid()
Map.put(agent, :cache_id, "#{corp_id}_#{agent.id}")
else
agent
end
Keyword.put(configs, :agents, agents ++ [agent])
|> then(&Application.put_env(:wechat, client, &1))
else
{:exist, agent} -> {:error, {:already_exists, agent}}
config -> {:error, {:wrong_env_config, config}}
end
end
defp transfer_aes_key(agent) do
if is_nil(agent.aes_key) and agent.encoding_aes_key do
aes_key = Encryptor.aes_key(agent.encoding_aes_key)
%{agent | aes_key: aes_key}
else
agent
end
end
end