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

client.go 5.7 kB

2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. package wechat
  2. import (
  3. "code.gitea.io/gitea/modules/log"
  4. "code.gitea.io/gitea/modules/setting"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/go-resty/resty/v2"
  8. "strconv"
  9. "time"
  10. )
  11. var (
  12. client *resty.Client
  13. )
  14. const (
  15. GRANT_TYPE = "client_credential"
  16. ACCESS_TOKEN_PATH = "/cgi-bin/token"
  17. QR_CODE_PATH = "/cgi-bin/qrcode/create"
  18. GET_MATERIAL_PATH = "/cgi-bin/material/batchget_material"
  19. SEND_TEMPLATE_PATH = "/cgi-bin/message/template/send"
  20. ACTION_QR_STR_SCENE = "QR_STR_SCENE"
  21. ERR_CODE_ACCESSTOKEN_EXPIRE = 42001
  22. ERR_CODE_ACCESSTOKEN_INVALID = 40001
  23. )
  24. type AccessTokenResponse struct {
  25. Access_token string
  26. Expires_in int
  27. }
  28. type QRCodeResponse struct {
  29. Ticket string `json:"ticket"`
  30. Expire_Seconds int `json:"expire_seconds"`
  31. Url string `json:"url"`
  32. }
  33. type QRCodeRequest struct {
  34. Action_name string `json:"action_name"`
  35. Action_info ActionInfo `json:"action_info"`
  36. Expire_seconds int `json:"expire_seconds"`
  37. }
  38. type MaterialRequest struct {
  39. Type string `json:"type"`
  40. Offset int `json:"offset"`
  41. Count int `json:"count"`
  42. }
  43. type TemplateMsgRequest struct {
  44. ToUser string `json:"touser"`
  45. TemplateId string `json:"template_id"`
  46. Url string `json:"url"`
  47. ClientMsgId string `json:"client_msg_id"`
  48. Data interface{} `json:"data"`
  49. }
  50. type TemplateValue struct {
  51. Value string `json:"value"`
  52. Color string `json:"color"`
  53. }
  54. type DefaultWechatTemplate struct {
  55. First TemplateValue `json:"first"`
  56. Keyword1 TemplateValue `json:"keyword1"`
  57. Keyword2 TemplateValue `json:"keyword2"`
  58. Keyword3 TemplateValue `json:"keyword3"`
  59. Keyword4 TemplateValue `json:"keyword4"`
  60. Remark TemplateValue `json:"remark"`
  61. }
  62. type ActionInfo struct {
  63. Scene Scene `json:"scene"`
  64. }
  65. type Scene struct {
  66. Scene_str string `json:"scene_str"`
  67. }
  68. type ErrorResponse struct {
  69. Errcode int
  70. Errmsg string
  71. }
  72. func getWechatRestyClient() *resty.Client {
  73. if client == nil {
  74. client = resty.New()
  75. client.SetTimeout(time.Duration(setting.WechatApiTimeoutSeconds) * time.Second)
  76. }
  77. return client
  78. }
  79. // api doc:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
  80. func callAccessToken() *AccessTokenResponse {
  81. client := getWechatRestyClient()
  82. var result AccessTokenResponse
  83. _, err := client.R().
  84. SetQueryParam("grant_type", GRANT_TYPE).
  85. SetQueryParam("appid", setting.WechatAppId).
  86. SetQueryParam("secret", setting.WechatAppSecret).
  87. SetResult(&result).
  88. Get(setting.WechatApiHost + ACCESS_TOKEN_PATH)
  89. if err != nil {
  90. log.Error("get wechat access token failed,e=%v", err)
  91. return nil
  92. }
  93. return &result
  94. }
  95. //callQRCodeCreate call the wechat api to create qr-code,
  96. // api doc: https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
  97. func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) {
  98. client := getWechatRestyClient()
  99. body := &QRCodeRequest{
  100. Action_name: ACTION_QR_STR_SCENE,
  101. Action_info: ActionInfo{Scene: Scene{Scene_str: sceneStr}},
  102. Expire_seconds: setting.WechatQRCodeExpireSeconds,
  103. }
  104. bodyJson, _ := json.Marshal(body)
  105. var result QRCodeResponse
  106. r, err := client.R().
  107. SetHeader("Content-Type", "application/json").
  108. SetQueryParam("access_token", GetWechatAccessToken()).
  109. SetBody(bodyJson).
  110. SetResult(&result).
  111. Post(setting.WechatApiHost + QR_CODE_PATH)
  112. if err != nil {
  113. log.Error("create QR code failed,e=%v", err)
  114. return nil, false
  115. }
  116. errCode := getErrorCodeFromResponse(r)
  117. if errCode == ERR_CODE_ACCESSTOKEN_EXPIRE || errCode == ERR_CODE_ACCESSTOKEN_INVALID {
  118. return nil, true
  119. }
  120. if result.Url == "" {
  121. return nil, false
  122. }
  123. log.Info("%v", r)
  124. return &result, false
  125. }
  126. //getMaterial
  127. // api doc: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Get_materials_list.html
  128. func getMaterial(mType string, offset, count int) (interface{}, bool) {
  129. client := getWechatRestyClient()
  130. body := &MaterialRequest{
  131. Type: mType,
  132. Offset: offset,
  133. Count: count,
  134. }
  135. bodyJson, _ := json.Marshal(body)
  136. r, err := client.R().
  137. SetHeader("Content-Type", "application/json").
  138. SetQueryParam("access_token", GetWechatAccessToken()).
  139. SetBody(bodyJson).
  140. Post(setting.WechatApiHost + GET_MATERIAL_PATH)
  141. if err != nil {
  142. log.Error("create QR code failed,e=%v", err)
  143. return nil, false
  144. }
  145. a := r.Body()
  146. resultMap := make(map[string]interface{}, 0)
  147. json.Unmarshal(a, &resultMap)
  148. errcode := resultMap["errcode"]
  149. if errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_EXPIRE) || errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_INVALID) {
  150. return nil, true
  151. }
  152. log.Info("%v", r)
  153. return &resultMap, false
  154. }
  155. func getErrorCodeFromResponse(r *resty.Response) int {
  156. a := r.Body()
  157. resultMap := make(map[string]interface{}, 0)
  158. json.Unmarshal(a, &resultMap)
  159. code := resultMap["errcode"]
  160. if code == nil {
  161. return -1
  162. }
  163. c, _ := strconv.Atoi(fmt.Sprint(code))
  164. return c
  165. }
  166. func sendTemplateMsg(req TemplateMsgRequest) (error, bool) {
  167. client := getWechatRestyClient()
  168. bodyJson, _ := json.Marshal(req)
  169. r, err := client.R().
  170. SetHeader("Content-Type", "application/json").
  171. SetQueryParam("access_token", GetWechatAccessToken()).
  172. SetBody(bodyJson).
  173. Post(setting.WechatApiHost + SEND_TEMPLATE_PATH)
  174. if err != nil {
  175. log.Error("sendTemplateMsg,e=%v", err)
  176. return nil, false
  177. }
  178. a := r.Body()
  179. resultMap := make(map[string]interface{}, 0)
  180. json.Unmarshal(a, &resultMap)
  181. errcode := resultMap["errcode"]
  182. log.Info("sendTemplateMsg,%v", r)
  183. if errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_EXPIRE) || errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_INVALID) {
  184. return nil, true
  185. }
  186. return nil, false
  187. }