lib/wechat/official_account/custom_message.ex

defmodule WeChat.CustomMessage do
  @moduledoc """
  消息管理 - 客服消息

  同时支持 公众号 & 小程序
  [官方文档](https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html){:target="_blank"}
  """
  import Jason.Helpers
  alias WeChat.{Card, Material, Publish}

  @doc_link "https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html"

  @type template_id :: String.t()
  @type title :: String.t()
  @type description :: String.t()
  @type url :: String.t()
  @type pic_url :: String.t()
  @type content :: String.t()

  @doc """
  客服消息接口 - 发送文本消息 -
  [官方文档](#{@doc_link}#7){:target="_blank"}

  发送文本消息时,支持插入跳小程序的文字链

  ```html
  文本内容<a href="http://www.qq.com" data-miniprogram-appid="appid" data-miniprogram-path="pages/index/index">点击跳小程序</a>
  ```

  说明:

  - `data-miniprogram-appid` 项,填写小程序 `appid`,则表示该链接跳小程序;
  - `data-miniprogram-path` 项,填写小程序路径,路径与 `app.json` 中保持一致,可带参数;
  - 对于不支持 `data-miniprogram-appid` 项的客户端版本,如果有 `herf` 项,则仍然保持跳 `href` 中的网页链接;
  - `data-miniprogram-appid` 对应的小程序必须与公众号有绑定关系。
  """
  @spec send_text(WeChat.client(), WeChat.openid(), content) :: WeChat.response()
  def send_text(client, openid, content) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "text", text: %{content: content})
    )
  end

  @doc """
  客服消息接口 - 发送文本消息 - by某个客服帐号 -
  [官方文档](#{@doc_link}#7){:target="_blank"}
  """
  @spec send_text(WeChat.client(), WeChat.openid(), content, WeChat.CustomService.kf_account()) ::
          WeChat.response()
  def send_text(client, openid, content, kf_account) do
    send_msg(
      client,
      json_map(
        touser: openid,
        msgtype: "text",
        text: %{content: content},
        customservice: %{kf_account: kf_account}
      )
    )
  end

  @doc """
  客服消息接口 - 发送图片消息 -
  [官方文档](#{@doc_link}#7){:target="_blank"}
  """
  @spec send_image(WeChat.client(), WeChat.openid(), WeChat.Material.media_id()) ::
          WeChat.response()
  def send_image(client, openid, media_id) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "image", image: %{media_id: media_id})
    )
  end

  @doc """
  客服消息接口 - 发送语音消息 -
  [官方文档](#{@doc_link}#7){:target="_blank"}
  """
  @spec send_voice(WeChat.client(), WeChat.openid(), WeChat.Material.media_id()) ::
          WeChat.response()
  def send_voice(client, openid, media_id) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "voice", voice: %{media_id: media_id})
    )
  end

  @doc """
  客服消息接口 - 发送视频消息 -
  [官方文档](#{@doc_link}#7){:target="_blank"}

  ## Example

  ```elixir
  #{inspect(__MODULE__)}.send_video(client, openid, {
    media_id:         "MEDIA_ID",
    thumb_media_id:   "MEDIA_ID",
    title:            "TITLE",
    description:      "DESCRIPTION"
  })
  ```
  """
  @spec send_video(WeChat.client(), WeChat.openid(), map) :: WeChat.response()
  def send_video(client, openid, map) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "video", video: map)
    )
  end

  @doc """
  客服消息接口 - 发送音乐消息 -
  [官方文档](#{@doc_link}#7){:target="_blank"}

  ## Example

  ```elixir
  #{inspect(__MODULE__)}.send_music(client, openid, {
    title:          "MUSIC_TITLE",
    description:    "MUSIC_DESCRIPTION",
    musicurl:       "MUSIC_URL",
    hqmusicurl:     "HQ_MUSIC_URL",
    thumb_media_id: "THUMB_MEDIA_ID"
  })
  ```
  """
  @spec send_music(WeChat.client(), WeChat.openid(), map) :: WeChat.response()
  def send_music(client, openid, map) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "music", music: map)
    )
  end

  @doc """
  客服消息接口 - 发送图文消息(点击跳转到外链) -
  [官方文档](#{@doc_link}#7){:target="_blank"}

  ## Example

  ```elixir
  #{inspect(__MODULE__)}.send_news(client, openid, {
    title:        "Happy Day",
    description:  "Is Really A Happy Day",
    url:          "URL",
    picurl:       "PIC_URL"
  })
  ```
  """
  @spec send_news(WeChat.client(), WeChat.openid(), title, description, url, pic_url) ::
          WeChat.response()
  def send_news(client, openid, title, description, url, pic_url) do
    send_msg(
      client,
      json_map(
        touser: openid,
        msgtype: "news",
        news: %{articles: [%{title: title, description: description, url: url, picurl: pic_url}]}
      )
    )
  end

  @doc """
  客服消息接口 - 发送图文消息(点击跳转到外链) -
  [官方文档](#{@doc_link}#7){:target="_blank"}
  """
  @spec send_news(WeChat.client(), WeChat.openid(), article :: map) :: WeChat.response()
  def send_news(client, openid, article) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "news", news: %{articles: [article]})
    )
  end

  @doc """
  客服消息接口 - 发送图文消息(点击跳转到图文消息页面) -
  [官方文档](#{@doc_link}#7){:target="_blank"}
  """
  @spec send_mp_news(WeChat.client(), WeChat.openid(), Material.media_id()) :: WeChat.response()
  def send_mp_news(client, openid, media_id) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "mpnews", mpnews: %{media_id: media_id})
    )
  end

  @doc """
  客服消息接口 - 发送图文消息 by ArticleID (点击跳转到图文消息页面) -
  [官方文档](#{@doc_link}#7){:target="_blank"}

  使用通过 “发布” 系列接口得到的 article_id
  """
  @spec send_article(WeChat.client(), WeChat.openid(), Publish.article_id()) :: WeChat.response()
  def send_article(client, openid, article_id) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "mpnewsarticle", mpnewsarticle: %{article_id: article_id})
    )
  end

  @doc """
  客服消息接口 - 发送菜单消息 -
  [官方文档](#{@doc_link}#7){:target="_blank"}

  ## Example

  ```elixir
  #{inspect(__MODULE__)}.send_menu(client, openid, {
    head_content: "您对本次服务是否满意呢?",
    list: [
      {
        id: "101",
        content: "满意"
      },
      {
        id: "102",
        content: "不满意"
      }
    ],
    tail_content: "欢迎再次光临"
  })
  ```
  """
  @spec send_menu(WeChat.client(), WeChat.openid(), map) :: WeChat.response()
  def send_menu(client, openid, map) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "msgmenu", msgmenu: map)
    )
  end

  @doc """
  客服消息接口 - 发送卡券 -
  [官方文档](#{@doc_link}#7){:target="_blank"}
  """
  @spec send_card(WeChat.client(), WeChat.openid(), Card.card_id()) :: WeChat.response()
  def send_card(client, openid, card_id) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "wxcard", wxcard: %{card_id: card_id})
    )
  end

  @doc """
  客服消息接口 - 发送小程序卡片(要求小程序与公众号已关联) -
  [官方文档](#{@doc_link}#7){:target="_blank"}

  ## Example

  ```elixir
  #{inspect(__MODULE__)}.send_mini_program_page(client, openid, {
    title:    "title",
    appid:    "appid",
    pagepath: "pagepath",
    thumb_media_id: "thumb_media_id"
  })
  ```
  """
  @spec send_mini_program_page(WeChat.client(), WeChat.openid(), map) :: WeChat.response()
  def send_mini_program_page(client, openid, map) do
    send_msg(
      client,
      json_map(touser: openid, msgtype: "miniprogrampage", miniprogrampage: map)
    )
  end

  @doc """
  客服消息接口 -
  [官方文档](#{@doc_link}#7){:target="_blank"}
  """
  def send_msg(client, body) do
    client.post("/cgi-bin/message/custom/send", body,
      query: [access_token: client.get_access_token()]
    )
  end

  @doc """
  客服输入状态 -
  [官方文档](#{@doc_link}#8){:target="_blank"}

  开发者可通过调用“客服输入状态”接口,返回客服当前输入状态给用户。

  此接口需要客服消息接口权限。

  - 如果不满足发送客服消息的触发条件,则无法下发输入状态。
  - 下发输入状态,需要客服之前30秒内跟用户有过消息交互。
  - 在输入状态中(持续15s),不可重复下发输入态。
  - 在输入状态中,如果向用户下发消息,会同时取消输入状态。
  """
  @spec typing(WeChat.client(), WeChat.openid(), is_typing :: boolean) :: WeChat.response()
  def typing(client, openid, is_typing \\ true) do
    command = if(is_typing, do: "Typing", else: "CancelTyping")

    client.post(
      "/cgi-bin/message/custom/typing",
      json_map(touser: openid, command: command),
      query: [access_token: client.get_access_token()]
    )
  end
end