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.8 kB

2 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. log.Info("start to get wechat access token")
  83. var result AccessTokenResponse
  84. _, err := client.R().
  85. SetQueryParam("grant_type", GRANT_TYPE).
  86. SetQueryParam("appid", setting.WechatAppId).
  87. SetQueryParam("secret", setting.WechatAppSecret).
  88. SetResult(&result).
  89. Get(setting.WechatApiHost + ACCESS_TOKEN_PATH)
  90. if err != nil {
  91. log.Error("get wechat access token failed,e=%v", err)
  92. return nil
  93. }
  94. log.Info("get wechat access token result=%v", result)
  95. return &result
  96. }
  97. //callQRCodeCreate call the wechat api to create qr-code,
  98. // api doc: https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
  99. func callQRCodeCreate(sceneStr string) (*QRCodeResponse, bool) {
  100. client := getWechatRestyClient()
  101. body := &QRCodeRequest{
  102. Action_name: ACTION_QR_STR_SCENE,
  103. Action_info: ActionInfo{Scene: Scene{Scene_str: sceneStr}},
  104. Expire_seconds: setting.WechatQRCodeExpireSeconds,
  105. }
  106. bodyJson, _ := json.Marshal(body)
  107. var result QRCodeResponse
  108. r, err := client.R().
  109. SetHeader("Content-Type", "application/json").
  110. SetQueryParam("access_token", GetWechatAccessToken()).
  111. SetBody(bodyJson).
  112. SetResult(&result).
  113. Post(setting.WechatApiHost + QR_CODE_PATH)
  114. if err != nil {
  115. log.Error("create QR code failed,e=%v", err)
  116. return nil, false
  117. }
  118. errCode := getErrorCodeFromResponse(r)
  119. if errCode == ERR_CODE_ACCESSTOKEN_EXPIRE || errCode == ERR_CODE_ACCESSTOKEN_INVALID {
  120. return nil, true
  121. }
  122. if result.Url == "" {
  123. return nil, false
  124. }
  125. log.Info("%v", r)
  126. return &result, false
  127. }
  128. //getMaterial
  129. // api doc: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Get_materials_list.html
  130. func getMaterial(mType string, offset, count int) (interface{}, bool) {
  131. client := getWechatRestyClient()
  132. body := &MaterialRequest{
  133. Type: mType,
  134. Offset: offset,
  135. Count: count,
  136. }
  137. bodyJson, _ := json.Marshal(body)
  138. r, err := client.R().
  139. SetHeader("Content-Type", "application/json").
  140. SetQueryParam("access_token", GetWechatAccessToken()).
  141. SetBody(bodyJson).
  142. Post(setting.WechatApiHost + GET_MATERIAL_PATH)
  143. if err != nil {
  144. log.Error("create QR code failed,e=%v", err)
  145. return nil, false
  146. }
  147. a := r.Body()
  148. resultMap := make(map[string]interface{}, 0)
  149. json.Unmarshal(a, &resultMap)
  150. errcode := resultMap["errcode"]
  151. if errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_EXPIRE) || errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_INVALID) {
  152. return nil, true
  153. }
  154. log.Info("%v", r)
  155. return &resultMap, false
  156. }
  157. func getErrorCodeFromResponse(r *resty.Response) int {
  158. a := r.Body()
  159. resultMap := make(map[string]interface{}, 0)
  160. json.Unmarshal(a, &resultMap)
  161. code := resultMap["errcode"]
  162. if code == nil {
  163. return -1
  164. }
  165. c, _ := strconv.Atoi(fmt.Sprint(code))
  166. return c
  167. }
  168. func sendTemplateMsg(req TemplateMsgRequest) (error, bool) {
  169. client := getWechatRestyClient()
  170. bodyJson, _ := json.Marshal(req)
  171. r, err := client.R().
  172. SetHeader("Content-Type", "application/json").
  173. SetQueryParam("access_token", GetWechatAccessToken()).
  174. SetBody(bodyJson).
  175. Post(setting.WechatApiHost + SEND_TEMPLATE_PATH)
  176. if err != nil {
  177. log.Error("sendTemplateMsg,e=%v", err)
  178. return nil, false
  179. }
  180. a := r.Body()
  181. resultMap := make(map[string]interface{}, 0)
  182. json.Unmarshal(a, &resultMap)
  183. errcode := resultMap["errcode"]
  184. log.Info("sendTemplateMsg,%v", r)
  185. if errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_EXPIRE) || errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_INVALID) {
  186. return nil, true
  187. }
  188. return nil, false
  189. }