附近小店
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

customer_service_message.go 6.9 KiB

3 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. package weapp
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "net/http"
  6. "strings"
  7. )
  8. const (
  9. apiSendMessage = "/cgi-bin/message/custom/send"
  10. apiSetTyping = "/cgi-bin/message/custom/typing"
  11. apiUploadTemplateMedia = "/cgi-bin/media/upload"
  12. apiGetTemplateMedia = "/cgi-bin/media/get"
  13. )
  14. // csMsgType 消息类型
  15. type csMsgType string
  16. // 所有消息类型
  17. const (
  18. csMsgTypeText csMsgType = "text" // 文本消息类型
  19. csMsgTypeLink = "link" // 图文链接消息类型
  20. csMsgTypeImage = "image" // 图片消息类型
  21. csMsgTypeMPCard = "miniprogrampage" // 小程序卡片消息类型
  22. )
  23. // csMessage 消息体
  24. type csMessage struct {
  25. Receiver string `json:"touser"` // user openID
  26. Type csMsgType `json:"msgtype"` // text | image | link | miniprogrampage
  27. Text CSMsgText `json:"text,omitempty"`
  28. Image CSMsgImage `json:"image,omitempty"`
  29. Link CSMsgLink `json:"link,omitempty"`
  30. MPCard CSMsgMPCard `json:"miniprogrampage,omitempty"`
  31. }
  32. // CSMsgText 接收的文本消息
  33. type CSMsgText struct {
  34. Content string `json:"content"`
  35. }
  36. // SendTo 发送文本消息
  37. //
  38. // openID 用户openID
  39. // token 微信 access_token
  40. func (msg CSMsgText) SendTo(openID, token string) (*CommonError, error) {
  41. params := csMessage{
  42. Receiver: openID,
  43. Type: csMsgTypeText,
  44. Text: msg,
  45. }
  46. return sendMessage(token, params)
  47. }
  48. // CSMsgImage 客服图片消息
  49. type CSMsgImage struct {
  50. MediaID string `json:"media_id"` // 发送的图片的媒体ID,通过 新增素材接口 上传图片文件获得。
  51. }
  52. // SendTo 发送图片消息
  53. //
  54. // openID 用户openID
  55. // token 微信 access_token
  56. func (msg CSMsgImage) SendTo(openID, token string) (*CommonError, error) {
  57. params := csMessage{
  58. Receiver: openID,
  59. Type: csMsgTypeImage,
  60. Image: msg,
  61. }
  62. return sendMessage(token, params)
  63. }
  64. // CSMsgLink 图文链接消息
  65. type CSMsgLink struct {
  66. Title string `json:"title"`
  67. Description string `json:"description"`
  68. URL string `json:"url"`
  69. ThumbURL string `json:"thumb_url"`
  70. }
  71. // SendTo 发送图文链接消息
  72. //
  73. // openID 用户openID
  74. // token 微信 access_token
  75. func (msg CSMsgLink) SendTo(openID, token string) (*CommonError, error) {
  76. params := csMessage{
  77. Receiver: openID,
  78. Type: csMsgTypeLink,
  79. Link: msg,
  80. }
  81. return sendMessage(token, params)
  82. }
  83. // CSMsgMPCard 接收的卡片消息
  84. type CSMsgMPCard struct {
  85. Title string `json:"title"` // 标题
  86. PagePath string `json:"pagepath"` // 小程序页面路径
  87. ThumbMediaID string `json:"thumb_media_id"` // 小程序消息卡片的封面, image 类型的 media_id,通过 新增素材接口 上传图片文件获得,建议大小为 520*416
  88. }
  89. // SendTo 发送卡片消息
  90. //
  91. // openID 用户openID
  92. // token 微信 access_token
  93. func (msg CSMsgMPCard) SendTo(openID, token string) (*CommonError, error) {
  94. params := csMessage{
  95. Receiver: openID,
  96. Type: "miniprogrampage",
  97. MPCard: msg,
  98. }
  99. return sendMessage(token, params)
  100. }
  101. // send 发送消息
  102. //
  103. // token 微信 access_token
  104. func sendMessage(token string, params interface{}) (*CommonError, error) {
  105. api := baseURL + apiSendMessage
  106. return doSendMessage(token, params, api)
  107. }
  108. func doSendMessage(token string, params interface{}, api string) (*CommonError, error) {
  109. url, err := tokenAPI(api, token)
  110. if err != nil {
  111. return nil, err
  112. }
  113. res := new(CommonError)
  114. if err := postJSON(url, params, res); err != nil {
  115. return nil, err
  116. }
  117. return res, nil
  118. }
  119. // SetTypingCommand 下发客服当前输入状态命令
  120. type SetTypingCommand = string
  121. // 所有下发客服当前输入状态命令
  122. const (
  123. SetTypingCommandTyping SetTypingCommand = "Typing" // 对用户下发"正在输入"状态
  124. SetTypingCommandCancelTyping = "CancelTyping" // 取消对用户的"正在输入"状态
  125. )
  126. // SetTyping 下发客服当前输入状态给用户。
  127. //
  128. // token 接口调用凭证
  129. // openID 用户的 OpenID
  130. // cmd 命令
  131. func SetTyping(token, openID string, cmd SetTypingCommand) (*CommonError, error) {
  132. api := baseURL + apiSetTyping
  133. return setTyping(token, openID, cmd, api)
  134. }
  135. func setTyping(token, openID string, cmd SetTypingCommand, api string) (*CommonError, error) {
  136. url, err := tokenAPI(api, token)
  137. if err != nil {
  138. return nil, err
  139. }
  140. params := requestParams{
  141. "touser": openID,
  142. "command": cmd,
  143. }
  144. res := new(CommonError)
  145. if err := postJSON(url, params, res); err != nil {
  146. return nil, err
  147. }
  148. return res, nil
  149. }
  150. // TempMediaType 文件类型
  151. type TempMediaType = string
  152. // 所有文件类型
  153. const (
  154. TempMediaTypeImage TempMediaType = "image" // 图片
  155. )
  156. // UploadTempMediaResponse 上传媒体文件返回
  157. type UploadTempMediaResponse struct {
  158. CommonError
  159. Type string `json:"type"` // 文件类型
  160. MediaID string `json:"media_id"` // 媒体文件上传后,获取标识,3天内有效。
  161. CreatedAt uint `json:"created_at"` // 媒体文件上传时间戳
  162. }
  163. // UploadTempMedia 把媒体文件上传到微信服务器。目前仅支持图片。用于发送客服消息或被动回复用户消息。
  164. //
  165. // token 接口调用凭证
  166. // mediaType 文件类型
  167. // medianame 媒体文件名
  168. func UploadTempMedia(token string, mediaType TempMediaType, medianame string) (*UploadTempMediaResponse, error) {
  169. api := baseURL + apiUploadTemplateMedia
  170. return uploadTempMedia(token, mediaType, medianame, api)
  171. }
  172. func uploadTempMedia(token string, mediaType TempMediaType, medianame, api string) (*UploadTempMediaResponse, error) {
  173. queries := requestQueries{
  174. "type": mediaType,
  175. "access_token": token,
  176. }
  177. url, err := encodeURL(api, queries)
  178. if err != nil {
  179. return nil, err
  180. }
  181. res := new(UploadTempMediaResponse)
  182. if err := postFormByFile(url, "media", medianame, res); err != nil {
  183. return nil, err
  184. }
  185. return res, nil
  186. }
  187. // GetTempMedia 获取客服消息内的临时素材。即下载临时的多媒体文件。目前小程序仅支持下载图片文件。
  188. //
  189. // token 接口调用凭证
  190. // mediaID 媒体文件 ID
  191. func GetTempMedia(token, mediaID string) (*http.Response, *CommonError, error) {
  192. api := baseURL + apiGetTemplateMedia
  193. return getTempMedia(token, mediaID, api)
  194. }
  195. func getTempMedia(token, mediaID, api string) (*http.Response, *CommonError, error) {
  196. queries := requestQueries{
  197. "access_token": token,
  198. "media_id": mediaID,
  199. }
  200. url, err := encodeURL(api, queries)
  201. if err != nil {
  202. return nil, nil, err
  203. }
  204. res, err := http.Get(url)
  205. if err != nil {
  206. return nil, nil, err
  207. }
  208. response := new(CommonError)
  209. switch header := res.Header.Get("Content-Type"); {
  210. case strings.HasPrefix(header, "application/json"): // 返回错误信息
  211. if err := json.NewDecoder(res.Body).Decode(response); err != nil {
  212. res.Body.Close()
  213. return nil, nil, err
  214. }
  215. return res, response, nil
  216. case strings.HasPrefix(header, "image"): // 返回文件 TODO: 应该确认一下
  217. return res, response, nil
  218. default:
  219. res.Body.Close()
  220. return nil, nil, errors.New("invalid response header: " + header)
  221. }
  222. }