|
@@ -18,7 +18,7 @@ import (
|
|
"path/filepath"
|
|
"path/filepath"
|
|
"regexp"
|
|
"regexp"
|
|
"sort"
|
|
"sort"
|
|
- "strings"
|
|
|
|
|
|
+ "sync/atomic"
|
|
"time"
|
|
"time"
|
|
)
|
|
)
|
|
|
|
|
|
@@ -27,7 +27,7 @@ const (
|
|
msgEvent = "event"
|
|
msgEvent = "event"
|
|
EventSubscribe = "subscribe"
|
|
EventSubscribe = "subscribe"
|
|
EventUnsubscribe = "unsubscribe"
|
|
EventUnsubscribe = "unsubscribe"
|
|
- EventScan = "scan"
|
|
|
|
|
|
+ EventScan = "SCAN"
|
|
EventView = "VIEW"
|
|
EventView = "VIEW"
|
|
EventClick = "CLICK"
|
|
EventClick = "CLICK"
|
|
EventLocation = "LOCATION"
|
|
EventLocation = "LOCATION"
|
|
@@ -78,6 +78,7 @@ const (
|
|
weixinHost = "https://api.weixin.qq.com/cgi-bin"
|
|
weixinHost = "https://api.weixin.qq.com/cgi-bin"
|
|
weixinQRScene = "https://api.weixin.qq.com/cgi-bin/qrcode"
|
|
weixinQRScene = "https://api.weixin.qq.com/cgi-bin/qrcode"
|
|
weixinShowQRScene = "https://mp.weixin.qq.com/cgi-bin/showqrcode"
|
|
weixinShowQRScene = "https://mp.weixin.qq.com/cgi-bin/showqrcode"
|
|
|
|
+ weixinMaterialURL = "https://api.weixin.qq.com/cgi-bin/material"
|
|
weixinShortURL = "https://api.weixin.qq.com/cgi-bin/shorturl"
|
|
weixinShortURL = "https://api.weixin.qq.com/cgi-bin/shorturl"
|
|
weixinUserInfo = "https://api.weixin.qq.com/cgi-bin/user/info"
|
|
weixinUserInfo = "https://api.weixin.qq.com/cgi-bin/user/info"
|
|
weixinFileURL = "http://file.api.weixin.qq.com/cgi-bin/media"
|
|
weixinFileURL = "http://file.api.weixin.qq.com/cgi-bin/media"
|
|
@@ -88,17 +89,19 @@ const (
|
|
// Max retry count
|
|
// Max retry count
|
|
retryMaxN = 3
|
|
retryMaxN = 3
|
|
// Reply format
|
|
// Reply format
|
|
- replyText = "<xml>%s<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>"
|
|
|
|
- replyImage = "<xml>%s<MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[%s]]></MediaId></Image></xml>"
|
|
|
|
- replyVoice = "<xml>%s<MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA[%s]]></MediaId></Voice></xml>"
|
|
|
|
- replyVideo = "<xml>%s<MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[%s]]></MediaId><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description></Video></xml>"
|
|
|
|
- replyMusic = "<xml>%s<MsgType><![CDATA[music]]></MsgType><Music><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description><MusicUrl><![CDATA[%s]]></MusicUrl><HQMusicUrl><![CDATA[%s]]></HQMusicUrl><ThumbMediaId><![CDATA[%s]]></ThumbMediaId></Music></xml>"
|
|
|
|
- replyNews = "<xml>%s<MsgType><![CDATA[news]]></MsgType><ArticleCount>%d</ArticleCount><Articles>%s</Articles></xml>"
|
|
|
|
- replyHeader = "<ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%d</CreateTime>"
|
|
|
|
- replyArticle = "<item><Title><![CDATA[%s]]></Title> <Description><![CDATA[%s]]></Description><PicUrl><![CDATA[%s]]></PicUrl><Url><![CDATA[%s]]></Url></item>"
|
|
|
|
- transferCustomerService = "<xml>" + replyHeader + "<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
|
|
|
|
- reply2CustomerService = "<xml>%s<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
|
|
|
|
-
|
|
|
|
|
|
+ replyText = "<xml>%s<MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>"
|
|
|
|
+ replyImage = "<xml>%s<MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[%s]]></MediaId></Image></xml>"
|
|
|
|
+ replyVoice = "<xml>%s<MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA[%s]]></MediaId></Voice></xml>"
|
|
|
|
+ replyVideo = "<xml>%s<MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[%s]]></MediaId><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description></Video></xml>"
|
|
|
|
+ replyMusic = "<xml>%s<MsgType><![CDATA[music]]></MsgType><Music><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description><MusicUrl><![CDATA[%s]]></MusicUrl><HQMusicUrl><![CDATA[%s]]></HQMusicUrl><ThumbMediaId><![CDATA[%s]]></ThumbMediaId></Music></xml>"
|
|
|
|
+ replyNews = "<xml>%s<MsgType><![CDATA[news]]></MsgType><ArticleCount>%d</ArticleCount><Articles>%s</Articles></xml>"
|
|
|
|
+ replyHeader = "<ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%d</CreateTime>"
|
|
|
|
+ replyArticle = "<item><Title><![CDATA[%s]]></Title> <Description><![CDATA[%s]]></Description><PicUrl><![CDATA[%s]]></PicUrl><Url><![CDATA[%s]]></Url></item>"
|
|
|
|
+ transferCustomerService = "<xml>" + replyHeader + "<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
|
|
|
|
+ transferCustomerService2 = "<xml>" + replyHeader + "<MsgType><![CDATA[transfer_customer_service]]></MsgType><TransInfo><KfAccount><![CDATA[%s]]></KfAccount></TransInfo></xml>"
|
|
|
|
+ reply2CustomerService = "<xml>%s<MsgType><![CDATA[transfer_customer_service]]></MsgType></xml>"
|
|
|
|
+ // Material request
|
|
|
|
+ requestMaterial = `{"type":"%s","offset":%d,"count":%d}`
|
|
// QR scene request
|
|
// QR scene request
|
|
requestQRScene = `{"expire_seconds":%d,"action_name":"QR_SCENE","action_info":{"scene":{"scene_id":%d}}}`
|
|
requestQRScene = `{"expire_seconds":%d,"action_name":"QR_SCENE","action_info":{"scene":{"scene_id":%d}}}`
|
|
requestQRLimitScene = `{"action_name":"QR_LIMIT_SCENE","action_info":{"scene":{"scene_id":%d}}}`
|
|
requestQRLimitScene = `{"action_name":"QR_LIMIT_SCENE","action_info":{"scene":{"scene_id":%d}}}`
|
|
@@ -200,6 +203,38 @@ type UserInfo struct {
|
|
GroupId int `json:"groupid,omitempty"`
|
|
GroupId int `json:"groupid,omitempty"`
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+type Material struct {
|
|
|
|
+ MediaId string `json:"media_id,omitempty"`
|
|
|
|
+ Name string `json:"name,omitempty"`
|
|
|
|
+ UpdateTime int64 `json:"update_time,omitempty"`
|
|
|
|
+ CreateTime int64 `json:"create_time,omitempty"`
|
|
|
|
+ Url string `json:"url,omitempty"`
|
|
|
|
+ Content struct {
|
|
|
|
+ NewsItem []struct {
|
|
|
|
+ Title string `json:"title,omitempty"`
|
|
|
|
+ ThumbMediaId string `json:"thumb_media_id,omitempty"`
|
|
|
|
+ ShowCoverPic int `json:"show_cover_pic,omitempty"`
|
|
|
|
+ Author string `json:"author,omitempty"`
|
|
|
|
+ Digest string `json:"digest,omitempty"`
|
|
|
|
+ Content string `json:"content,omitempty"`
|
|
|
|
+ Url string `json:"url,omitempty"`
|
|
|
|
+ ContentSourceUrl string `json:"content_source_url,omitempty"`
|
|
|
|
+ } `json:"news_item,omitempty"`
|
|
|
|
+ } `json:"content,omitempty"`
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type Materials struct {
|
|
|
|
+ TotalCount int `json:"total_count,omitempty"`
|
|
|
|
+ ItemCount int `json:"item_count,omitempty"`
|
|
|
|
+ Items []Material `json:"item,omitempty"`
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type TmplData map[string]TmplItem
|
|
|
|
+type TmplItem struct {
|
|
|
|
+ Value string `json:"value,omitempty"`
|
|
|
|
+ Color string `json:"color,omitempty"`
|
|
|
|
+}
|
|
|
|
+
|
|
// Use to output reply
|
|
// Use to output reply
|
|
type ResponseWriter interface {
|
|
type ResponseWriter interface {
|
|
// Get weixin
|
|
// Get weixin
|
|
@@ -208,12 +243,14 @@ type ResponseWriter interface {
|
|
// Reply message
|
|
// Reply message
|
|
ReplyOK()
|
|
ReplyOK()
|
|
ReplyText(text string)
|
|
ReplyText(text string)
|
|
- Reply2CustomerService()
|
|
|
|
ReplyImage(mediaId string)
|
|
ReplyImage(mediaId string)
|
|
ReplyVoice(mediaId string)
|
|
ReplyVoice(mediaId string)
|
|
ReplyVideo(mediaId string, title string, description string)
|
|
ReplyVideo(mediaId string, title string, description string)
|
|
ReplyMusic(music *Music)
|
|
ReplyMusic(music *Music)
|
|
ReplyNews(articles []Article)
|
|
ReplyNews(articles []Article)
|
|
|
|
+
|
|
|
|
+ Reply2CustomerService()
|
|
|
|
+
|
|
TransferCustomerService(serviceId string)
|
|
TransferCustomerService(serviceId string)
|
|
// Post message
|
|
// Post message
|
|
PostText(text string) error
|
|
PostText(text string) error
|
|
@@ -222,12 +259,14 @@ type ResponseWriter interface {
|
|
PostVideo(mediaId string, title string, description string) error
|
|
PostVideo(mediaId string, title string, description string) error
|
|
PostMusic(music *Music) error
|
|
PostMusic(music *Music) error
|
|
PostNews(articles []Article) error
|
|
PostNews(articles []Article) error
|
|
- PostTemplateMessage(templateid string, url string, data interface{}) (string, error)
|
|
|
|
|
|
+ PostTemplateMessage(templateid string, url string, data TmplData) (int32, error)
|
|
// Media operator
|
|
// Media operator
|
|
UploadMediaFromFile(mediaType string, filepath string) (string, error)
|
|
UploadMediaFromFile(mediaType string, filepath string) (string, error)
|
|
DownloadMediaToFile(mediaId string, filepath string) error
|
|
DownloadMediaToFile(mediaId string, filepath string) error
|
|
UploadMedia(mediaType string, filename string, reader io.Reader) (string, error)
|
|
UploadMedia(mediaType string, filename string, reader io.Reader) (string, error)
|
|
DownloadMedia(mediaId string, writer io.Writer) error
|
|
DownloadMedia(mediaId string, writer io.Writer) error
|
|
|
|
+ //
|
|
|
|
+ TransferCustomerService2(serviceId, kfaccount string)
|
|
GetUserBaseInfo(openid string) (map[string]interface{}, error)
|
|
GetUserBaseInfo(openid string) (map[string]interface{}, error)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -262,17 +301,16 @@ type jsApiTicket struct {
|
|
}
|
|
}
|
|
|
|
|
|
type Weixin struct {
|
|
type Weixin struct {
|
|
- token string
|
|
|
|
- routes []*route
|
|
|
|
- tokenChan chan accessToken
|
|
|
|
- ticketChan chan jsApiTicket
|
|
|
|
- userData interface{}
|
|
|
|
- appId string
|
|
|
|
- appSecret string
|
|
|
|
|
|
+ token string
|
|
|
|
+ routes []*route
|
|
|
|
+ tokenChan chan accessToken
|
|
|
|
+ ticketChan chan jsApiTicket
|
|
|
|
+ userData interface{}
|
|
|
|
+ appId string
|
|
|
|
+ appSecret string
|
|
|
|
+ refreshToken int32
|
|
}
|
|
}
|
|
|
|
|
|
-//
|
|
|
|
-
|
|
|
|
// Convert qr scene to url
|
|
// Convert qr scene to url
|
|
func (qr *QRScene) ToURL() string {
|
|
func (qr *QRScene) ToURL() string {
|
|
return (weixinShowQRScene + "?ticket=" + qr.Ticket)
|
|
return (weixinShowQRScene + "?ticket=" + qr.Ticket)
|
|
@@ -284,9 +322,10 @@ func New(token string, appid string, secret string) *Weixin {
|
|
wx.token = token
|
|
wx.token = token
|
|
wx.appId = appid
|
|
wx.appId = appid
|
|
wx.appSecret = secret
|
|
wx.appSecret = secret
|
|
|
|
+ wx.refreshToken = 0
|
|
if len(appid) > 0 && len(secret) > 0 {
|
|
if len(appid) > 0 && len(secret) > 0 {
|
|
wx.tokenChan = make(chan accessToken)
|
|
wx.tokenChan = make(chan accessToken)
|
|
- go createAccessToken(wx.tokenChan, appid, secret)
|
|
|
|
|
|
+ go wx.createAccessToken(wx.tokenChan, appid, secret)
|
|
wx.ticketChan = make(chan jsApiTicket)
|
|
wx.ticketChan = make(chan jsApiTicket)
|
|
go createJsApiTicket(wx.tokenChan, wx.ticketChan)
|
|
go createJsApiTicket(wx.tokenChan, wx.ticketChan)
|
|
}
|
|
}
|
|
@@ -307,6 +346,11 @@ func (wx *Weixin) GetAppSecret() string {
|
|
return wx.appSecret
|
|
return wx.appSecret
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (wx *Weixin) RefreshAccessToken() {
|
|
|
|
+ atomic.StoreInt32(&wx.refreshToken, 1)
|
|
|
|
+ <-wx.tokenChan
|
|
|
|
+}
|
|
|
|
+
|
|
// Register request callback.
|
|
// Register request callback.
|
|
func (wx *Weixin) HandleFunc(pattern string, handler HandlerFunc) {
|
|
func (wx *Weixin) HandleFunc(pattern string, handler HandlerFunc) {
|
|
regex, err := regexp.Compile(pattern)
|
|
regex, err := regexp.Compile(pattern)
|
|
@@ -333,28 +377,6 @@ func (wx *Weixin) PostText(touser string, text string) error {
|
|
return postMessage(wx.tokenChan, &msg)
|
|
return postMessage(wx.tokenChan, &msg)
|
|
}
|
|
}
|
|
|
|
|
|
-//Post custom message(消息结构体完全由开发人员自定义)
|
|
|
|
-func (wx *Weixin) PostTextCustom(url string, obj interface{}) error {
|
|
|
|
- data, err := json.Marshal(obj)
|
|
|
|
- if err != nil {
|
|
|
|
- return err
|
|
|
|
- }
|
|
|
|
- //log.Println("custom msg:", string(data))
|
|
|
|
- _, err = postRequest(url, wx.tokenChan, data)
|
|
|
|
- return err
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-//Post custom message(消息结构体完全由开发人员自定义)
|
|
|
|
-func (wx *Weixin) PostCustomMsg(url string, obj interface{}) (bs []byte, err error) {
|
|
|
|
- var data []byte
|
|
|
|
- data, err = json.Marshal(obj)
|
|
|
|
- if err != nil {
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
- bs, err = postRequest(url, wx.tokenChan, data)
|
|
|
|
- return
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
// Post image message
|
|
// Post image message
|
|
func (wx *Weixin) PostImage(touser string, mediaId string) error {
|
|
func (wx *Weixin) PostImage(touser string, mediaId string) error {
|
|
var msg struct {
|
|
var msg struct {
|
|
@@ -462,6 +484,20 @@ func (wx *Weixin) DownloadMedia(mediaId string, writer io.Writer) error {
|
|
return downloadMedia(wx.tokenChan, mediaId, writer)
|
|
return downloadMedia(wx.tokenChan, mediaId, writer)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+// Batch Get Material
|
|
|
|
+func (wx *Weixin) BatchGetMaterial(materialType string, offset int, count int) (*Materials, error) {
|
|
|
|
+ reply, err := postRequest(weixinMaterialURL+"/batchget_material?access_token=", wx.tokenChan,
|
|
|
|
+ []byte(fmt.Sprintf(requestMaterial, materialType, offset, count)))
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ var materials Materials
|
|
|
|
+ if err := json.Unmarshal(reply, &materials); err != nil {
|
|
|
|
+ return nil, err
|
|
|
|
+ }
|
|
|
|
+ return &materials, nil
|
|
|
|
+}
|
|
|
|
+
|
|
// Get ip list
|
|
// Get ip list
|
|
func (wx *Weixin) GetIpList() ([]string, error) {
|
|
func (wx *Weixin) GetIpList() ([]string, error) {
|
|
reply, err := sendGetRequest(weixinHost+"/getcallbackip?access_token=", wx.tokenChan)
|
|
reply, err := sendGetRequest(weixinHost+"/getcallbackip?access_token=", wx.tokenChan)
|
|
@@ -530,16 +566,12 @@ func (wx *Weixin) ShortURL(url string) (string, error) {
|
|
|
|
|
|
// Custom menu
|
|
// Custom menu
|
|
func (wx *Weixin) CreateMenu(menu *Menu) error {
|
|
func (wx *Weixin) CreateMenu(menu *Menu) error {
|
|
- data, err := json.Marshal(menu)
|
|
|
|
|
|
+ data, err := marshal(menu)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
- } else {
|
|
|
|
- tmp := string(data)
|
|
|
|
- tmp = strings.Replace(tmp, "\\u0026", "&", -1)
|
|
|
|
- log.Println("MenuJson", string(tmp))
|
|
|
|
- _, err := postRequest(weixinHost+"/menu/create?access_token=", wx.tokenChan, []byte(tmp))
|
|
|
|
- return err
|
|
|
|
}
|
|
}
|
|
|
|
+ _, err = postRequest(weixinHost+"/menu/create?access_token=", wx.tokenChan, data)
|
|
|
|
+ return err
|
|
}
|
|
}
|
|
|
|
|
|
func (wx *Weixin) GetMenu() (*Menu, error) {
|
|
func (wx *Weixin) GetMenu() (*Menu, error) {
|
|
@@ -599,12 +631,12 @@ func (wx *Weixin) AddTemplate(shortid string) (string, error) {
|
|
return templateId.Id, nil
|
|
return templateId.Id, nil
|
|
}
|
|
}
|
|
|
|
|
|
-func (wx *Weixin) PostTemplateMessage(touser string, templateid string, url string, data interface{}) (string, error) {
|
|
|
|
|
|
+func (wx *Weixin) PostTemplateMessage(touser string, templateid string, url string, data TmplData) (int32, error) {
|
|
var msg struct {
|
|
var msg struct {
|
|
- ToUser string `json:"touser"`
|
|
|
|
- TemplateId string `json:"template_id"`
|
|
|
|
- Url string `json:"url,omitempty"`
|
|
|
|
- Data interface{} `json:"data,omitempty"`
|
|
|
|
|
|
+ ToUser string `json:"touser"`
|
|
|
|
+ TemplateId string `json:"template_id"`
|
|
|
|
+ Url string `json:"url,omitempty"`
|
|
|
|
+ Data TmplData `json:"data,omitempty"`
|
|
}
|
|
}
|
|
msg.ToUser = touser
|
|
msg.ToUser = touser
|
|
msg.TemplateId = templateid
|
|
msg.TemplateId = templateid
|
|
@@ -612,17 +644,17 @@ func (wx *Weixin) PostTemplateMessage(touser string, templateid string, url stri
|
|
msg.Data = data
|
|
msg.Data = data
|
|
msgStr, err := marshal(msg)
|
|
msgStr, err := marshal(msg)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return "", err
|
|
|
|
|
|
+ return 0, err
|
|
}
|
|
}
|
|
reply, err := postRequest(weixinHost+"/message/template/send?access_token=", wx.tokenChan, msgStr)
|
|
reply, err := postRequest(weixinHost+"/message/template/send?access_token=", wx.tokenChan, msgStr)
|
|
if err != nil {
|
|
if err != nil {
|
|
- return "", err
|
|
|
|
|
|
+ return 0, err
|
|
}
|
|
}
|
|
var resp struct {
|
|
var resp struct {
|
|
- MsgId string `json:"msgid,omitempty"`
|
|
|
|
|
|
+ MsgId int32 `json:"msgid,omitempty"`
|
|
}
|
|
}
|
|
if err := json.Unmarshal(reply, &resp); err != nil {
|
|
if err := json.Unmarshal(reply, &resp); err != nil {
|
|
- return "", err
|
|
|
|
|
|
+ return 0, err
|
|
}
|
|
}
|
|
return resp.MsgId, nil
|
|
return resp.MsgId, nil
|
|
}
|
|
}
|
|
@@ -708,7 +740,6 @@ func (wx *Weixin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
log.Println("Weixin receive message failed:", err)
|
|
log.Println("Weixin receive message failed:", err)
|
|
http.Error(w, "", http.StatusBadRequest)
|
|
http.Error(w, "", http.StatusBadRequest)
|
|
} else {
|
|
} else {
|
|
- //log.Println("recive data:", string(data))
|
|
|
|
var msg Request
|
|
var msg Request
|
|
if err := xml.Unmarshal(data, &msg); err != nil {
|
|
if err := xml.Unmarshal(data, &msg); err != nil {
|
|
log.Println("Weixin parse message failed:", err)
|
|
log.Println("Weixin parse message failed:", err)
|
|
@@ -764,7 +795,6 @@ func checkSignature(t string, w http.ResponseWriter, r *http.Request) bool {
|
|
}
|
|
}
|
|
h := sha1.New()
|
|
h := sha1.New()
|
|
h.Write([]byte(str))
|
|
h.Write([]byte(str))
|
|
- //fmt.Printf("checksignature:%s %s \n %s %s \n", str, timestamp, signature, nonce)
|
|
|
|
return fmt.Sprintf("%x", h.Sum(nil)) == signature
|
|
return fmt.Sprintf("%x", h.Sum(nil)) == signature
|
|
}
|
|
}
|
|
|
|
|
|
@@ -812,11 +842,12 @@ func getJsApiTicket(c chan accessToken) (*jsApiTicket, error) {
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-func createAccessToken(c chan accessToken, appid string, secret string) {
|
|
|
|
|
|
+func (wx *Weixin) createAccessToken(c chan accessToken, appid string, secret string) {
|
|
token := accessToken{"", time.Now()}
|
|
token := accessToken{"", time.Now()}
|
|
c <- token
|
|
c <- token
|
|
for {
|
|
for {
|
|
- if time.Since(token.expires).Seconds() >= 0 {
|
|
|
|
|
|
+ swapped := atomic.CompareAndSwapInt32(&wx.refreshToken, 1, 0)
|
|
|
|
+ if swapped || time.Since(token.expires).Seconds() >= 0 {
|
|
var expires time.Duration
|
|
var expires time.Duration
|
|
token.token, expires = authAccessToken(appid, secret)
|
|
token.token, expires = authAccessToken(appid, secret)
|
|
token.expires = time.Now().Add(expires)
|
|
token.expires = time.Now().Add(expires)
|
|
@@ -843,7 +874,6 @@ func sendGetRequest(reqURL string, c chan accessToken) ([]byte, error) {
|
|
for i := 0; i < retryMaxN; i++ {
|
|
for i := 0; i < retryMaxN; i++ {
|
|
token := <-c
|
|
token := <-c
|
|
if time.Since(token.expires).Seconds() < 0 {
|
|
if time.Since(token.expires).Seconds() < 0 {
|
|
- log.Println("token:", token.token)
|
|
|
|
r, err := http.Get(reqURL + token.token)
|
|
r, err := http.Get(reqURL + token.token)
|
|
if err != nil {
|
|
if err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
@@ -856,15 +886,14 @@ func sendGetRequest(reqURL string, c chan accessToken) ([]byte, error) {
|
|
var result response
|
|
var result response
|
|
if err := json.Unmarshal(reply, &result); err != nil {
|
|
if err := json.Unmarshal(reply, &result); err != nil {
|
|
return nil, err
|
|
return nil, err
|
|
- } else {
|
|
|
|
- switch result.ErrorCode {
|
|
|
|
- case 0:
|
|
|
|
- return reply, nil
|
|
|
|
- case 42001: // access_token timeout and retry
|
|
|
|
- continue
|
|
|
|
- default:
|
|
|
|
- return nil, errors.New(fmt.Sprintf("WeiXin send get request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+ switch result.ErrorCode {
|
|
|
|
+ case 0:
|
|
|
|
+ return reply, nil
|
|
|
|
+ case 42001: // access_token timeout and retry
|
|
|
|
+ continue
|
|
|
|
+ default:
|
|
|
|
+ return nil, errors.New(fmt.Sprintf("WeiXin send get request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -877,29 +906,24 @@ func postRequest(reqURL string, c chan accessToken, data []byte) ([]byte, error)
|
|
if time.Since(token.expires).Seconds() < 0 {
|
|
if time.Since(token.expires).Seconds() < 0 {
|
|
r, err := http.Post(reqURL+token.token, "application/json; charset=utf-8", bytes.NewReader(data))
|
|
r, err := http.Post(reqURL+token.token, "application/json; charset=utf-8", bytes.NewReader(data))
|
|
if err != nil {
|
|
if err != nil {
|
|
- log.Println("err1", err.Error())
|
|
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
defer r.Body.Close()
|
|
defer r.Body.Close()
|
|
reply, err := ioutil.ReadAll(r.Body)
|
|
reply, err := ioutil.ReadAll(r.Body)
|
|
- log.Println("repl:", string(reply))
|
|
|
|
if err != nil {
|
|
if err != nil {
|
|
- log.Println("err2", err.Error())
|
|
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
var result response
|
|
var result response
|
|
if err := json.Unmarshal(reply, &result); err != nil {
|
|
if err := json.Unmarshal(reply, &result); err != nil {
|
|
- log.Println("err3", err.Error())
|
|
|
|
return nil, err
|
|
return nil, err
|
|
- } else {
|
|
|
|
- switch result.ErrorCode {
|
|
|
|
- case 0:
|
|
|
|
- return reply, nil
|
|
|
|
- case 42001: // access_token timeout and retry
|
|
|
|
- continue
|
|
|
|
- default:
|
|
|
|
- return nil, errors.New(fmt.Sprintf("WeiXin send post request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+ switch result.ErrorCode {
|
|
|
|
+ case 0:
|
|
|
|
+ return reply, nil
|
|
|
|
+ case 42001: // access_token timeout and retry
|
|
|
|
+ continue
|
|
|
|
+ default:
|
|
|
|
+ return nil, errors.New(fmt.Sprintf("WeiXin send post request reply[%d]: %s", result.ErrorCode, result.ErrorMessage))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -907,7 +931,7 @@ func postRequest(reqURL string, c chan accessToken, data []byte) ([]byte, error)
|
|
}
|
|
}
|
|
|
|
|
|
func postMessage(c chan accessToken, msg interface{}) error {
|
|
func postMessage(c chan accessToken, msg interface{}) error {
|
|
- data, err := json.Marshal(msg)
|
|
|
|
|
|
+ data, err := marshal(msg)
|
|
if err != nil {
|
|
if err != nil {
|
|
return err
|
|
return err
|
|
}
|
|
}
|
|
@@ -949,15 +973,14 @@ func uploadMedia(c chan accessToken, mediaType string, filename string, reader i
|
|
err = json.Unmarshal(reply, &result)
|
|
err = json.Unmarshal(reply, &result)
|
|
if err != nil {
|
|
if err != nil {
|
|
return "", err
|
|
return "", err
|
|
- } else {
|
|
|
|
- switch result.ErrorCode {
|
|
|
|
- case 0:
|
|
|
|
- return result.MediaId, nil
|
|
|
|
- case 42001: // access_token timeout and retry
|
|
|
|
- continue
|
|
|
|
- default:
|
|
|
|
- return "", errors.New(fmt.Sprintf("WeiXin upload[%d]: %s", result.ErrorCode, result.ErrorMessage))
|
|
|
|
- }
|
|
|
|
|
|
+ }
|
|
|
|
+ switch result.ErrorCode {
|
|
|
|
+ case 0:
|
|
|
|
+ return result.MediaId, nil
|
|
|
|
+ case 42001: // access_token timeout and retry
|
|
|
|
+ continue
|
|
|
|
+ default:
|
|
|
|
+ return "", errors.New(fmt.Sprintf("WeiXin upload[%d]: %s", result.ErrorCode, result.ErrorMessage))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1024,11 +1047,6 @@ func (w responseWriter) ReplyText(text string) {
|
|
msg := fmt.Sprintf(replyText, w.replyHeader(), text)
|
|
msg := fmt.Sprintf(replyText, w.replyHeader(), text)
|
|
w.writer.Write([]byte(msg))
|
|
w.writer.Write([]byte(msg))
|
|
}
|
|
}
|
|
-func (w responseWriter) Reply2CustomerService() {
|
|
|
|
- msg := fmt.Sprintf(reply2CustomerService, w.replyHeader(), "")
|
|
|
|
- //log.Println("repl msg:", msg)
|
|
|
|
- w.writer.Write([]byte(msg))
|
|
|
|
-}
|
|
|
|
|
|
|
|
// Reply image message
|
|
// Reply image message
|
|
func (w responseWriter) ReplyImage(mediaId string) {
|
|
func (w responseWriter) ReplyImage(mediaId string) {
|
|
@@ -1070,6 +1088,12 @@ func (w responseWriter) TransferCustomerService(serviceId string) {
|
|
w.writer.Write([]byte(msg))
|
|
w.writer.Write([]byte(msg))
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+func (w responseWriter) TransferCustomerService2(serviceId, kfaccount string) {
|
|
|
|
+ msg := fmt.Sprintf(transferCustomerService2, serviceId, w.fromUserName, time.Now().Unix(), kfaccount)
|
|
|
|
+ log.Println("send custom msg:::", msg)
|
|
|
|
+ w.writer.Write([]byte(msg))
|
|
|
|
+}
|
|
|
|
+
|
|
// Post text message
|
|
// Post text message
|
|
func (w responseWriter) PostText(text string) error {
|
|
func (w responseWriter) PostText(text string) error {
|
|
return w.wx.PostText(w.toUserName, text)
|
|
return w.wx.PostText(w.toUserName, text)
|
|
@@ -1101,7 +1125,7 @@ func (w responseWriter) PostNews(articles []Article) error {
|
|
}
|
|
}
|
|
|
|
|
|
// Post template message
|
|
// Post template message
|
|
-func (w responseWriter) PostTemplateMessage(templateid string, url string, data interface{}) (string, error) {
|
|
|
|
|
|
+func (w responseWriter) PostTemplateMessage(templateid string, url string, data TmplData) (int32, error) {
|
|
return w.wx.PostTemplateMessage(w.toUserName, templateid, url, data)
|
|
return w.wx.PostTemplateMessage(w.toUserName, templateid, url, data)
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1125,6 +1149,37 @@ func (w responseWriter) DownloadMedia(mediaId string, writer io.Writer) error {
|
|
return w.wx.DownloadMedia(mediaId, writer)
|
|
return w.wx.DownloadMedia(mediaId, writer)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+//
|
|
|
|
+func (wx *Weixin) PostCustomMsg(url string, obj interface{}) (bs []byte, err error) {
|
|
|
|
+ var data []byte
|
|
|
|
+ data, err = json.Marshal(obj)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ bs, err = postRequest(url, wx.tokenChan, data)
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//
|
|
|
|
+func (wx *Weixin) GetKfList() (ret map[string]interface{}) {
|
|
|
|
+ bs, err := sendGetRequest("https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=", wx.tokenChan)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ json.Unmarshal(bs, &ret)
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//
|
|
|
|
+func (wx *Weixin) GetOnlineKfList() (ret map[string]interface{}) {
|
|
|
|
+ bs, err := sendGetRequest("https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=", wx.tokenChan)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return nil
|
|
|
|
+ }
|
|
|
|
+ json.Unmarshal(bs, &ret)
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
func (rw responseWriter) GetUserBaseInfo(openid string) (map[string]interface{}, error) {
|
|
func (rw responseWriter) GetUserBaseInfo(openid string) (map[string]interface{}, error) {
|
|
return rw.wx.GetUserBaseInfo(openid)
|
|
return rw.wx.GetUserBaseInfo(openid)
|
|
}
|
|
}
|
|
@@ -1142,3 +1197,20 @@ func (w *Weixin) GetUserBaseInfo(openid string) (map[string]interface{}, error)
|
|
return nil, err
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+func (w responseWriter) Reply2CustomerService() {
|
|
|
|
+ msg := fmt.Sprintf(reply2CustomerService, w.replyHeader(), "")
|
|
|
|
+ //log.Println("repl msg:", msg)
|
|
|
|
+ w.writer.Write([]byte(msg))
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+//Post custom message(消息结构体完全由开发人员自定义)
|
|
|
|
+func (wx *Weixin) PostTextCustom(url string, obj interface{}) error {
|
|
|
|
+ data, err := json.Marshal(obj)
|
|
|
|
+ if err != nil {
|
|
|
|
+ return err
|
|
|
|
+ }
|
|
|
|
+ //log.Println("custom msg:", string(data))
|
|
|
|
+ _, err = postRequest(url, wx.tokenChan, data)
|
|
|
|
+ return err
|
|
|
|
+}
|