func uploadFromReader(clt *core.Client, mediaType, filename string, reader io.Reader) (info *MediaInfo, err error) { var incompleteURL = "https://api.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType + "&access_token=" var fields = []core.MultipartFormField{ { IsFile: true, Name: "media", FileName: filename, Value: reader, }, } var result struct { core.Error MediaInfo } if err = clt.PostMultipartForm(incompleteURL, fields, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } info = &result.MediaInfo return }
// 卡券投放, 创建二维码接口. func Create(clt *core.Client, para *CreateParameters) (info *QrcodeInfo, err error) { request := struct { ActionName string `json:"action_name"` ExpireSeconds int `json:"expire_seconds,omitempty"` ActionInfo struct { Card *CreateParameters `json:"card,omitempty"` } `json:"action_info"` }{ ActionName: "QR_CARD", ExpireSeconds: para.ExpireSeconds, } request.ActionInfo.Card = para var result struct { core.Error QrcodeInfo } incompleteURL := "https://api.weixin.qq.com/card/qrcode/create?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } info = &result.QrcodeInfo return }
// UploadThumbFromReader 上传多媒体缩略图. // NOTE: 参数 filename 不是文件路径, 是 multipart/form-data 里面 filename 的值. func UploadThumbFromReader(clt *core.Client, filename string, reader io.Reader) (info *MediaInfo, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/media/upload?type=thumb&access_token=" var fields = []core.MultipartFormField{ { IsFile: true, Name: "media", FileName: filename, Value: reader, }, } var result struct { core.Error MediaType string `json:"type"` MediaId string `json:"thumb_media_id"` CreatedAt int64 `json:"created_at"` } if err = clt.PostMultipartForm(incompleteURL, fields, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } info = &MediaInfo{ MediaType: result.MediaType, MediaId: result.MediaId, CreatedAt: result.CreatedAt, } return }
// 获取用户已领取卡券接口 // openid: 需要查询的用户openid // cardid: 卡券ID。不填写时默认查询当前appid下的卡券。 func GetCardList(clt *core.Client, openid, cardid string) (list []code.CardItemIdentifier, err error) { request := struct { OpenId string `json:"openid"` CardId string `json:"card_id,omitempty"` }{ OpenId: openid, CardId: cardid, } var result struct { core.Error CardList []code.CardItemIdentifier `json:"card_list"` } incompleteURL := "https://api.weixin.qq.com/card/user/getcardlist?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } list = result.CardList return }
func addFromReader(clt *core.Client, filename string, reader io.Reader, _type string) (info ImageInfo, err error) { var result struct { core.Error ImageInfo `json:"data"` } var incompleteURL string if _type != "" { incompleteURL = "https://api.weixin.qq.com/shakearound/material/add?type=" + url.QueryEscape(_type) + "&access_token=" } else { incompleteURL = "https://api.weixin.qq.com/shakearound/material/add?access_token=" } fields := []core.MultipartFormField{{ IsFile: true, Name: "media", FileName: filename, Value: reader, }} if err = clt.PostMultipartForm(incompleteURL, fields, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } info = result.ImageInfo return }
// Create 创建分组. func Create(clt *core.Client, name string) (group *Group, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/groups/create?access_token=" var request struct { Group struct { Name string `json:"name"` } `json:"group"` } request.Group.Name = name var result struct { core.Error Group `json:"group"` } if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } result.Group.UserCount = 0 group = &result.Group return }
// UploadImageFromReader 上传图片到微信服务器, 返回的图片url给其他场景使用, 比如图文消息, 卡卷, POI. // NOTE: 参数 filename 不是文件路径, 是 multipart/form-data 里面 filename 的值. func UploadImageFromReader(clt *core.Client, filename string, reader io.Reader) (url string, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=" var fields = []core.MultipartFormField{ { IsFile: true, Name: "media", FileName: filename, Value: reader, }, } var result struct { core.Error URL string `json:"url"` } if err = clt.PostMultipartForm(incompleteURL, fields, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } url = result.URL return }
// 以页面为维度的数据统计接口 func Page(clt *core.Client, pageId, beginDate, endDate int64) (data []StatisticsBase, err error) { request := struct { PageId int64 `json:"page_id"` BeginDate int64 `json:"begin_date"` EndDate int64 `json:"end_date"` }{ PageId: pageId, BeginDate: beginDate, EndDate: endDate, } var result struct { core.Error Data []StatisticsBase `json:"data"` } incompleteURL := "https://api.weixin.qq.com/shakearound/statistics/page?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } data = result.Data return }
// 以设备为维度的数据统计接口 func Device(clt *core.Client, deviceIdentifier *device.DeviceIdentifier, beginDate, endDate int64) (data []StatisticsBase, err error) { request := struct { DeviceIdentifier *device.DeviceIdentifier `json:"device_identifier,omitempty"` BeginDate int64 `json:"begin_date"` EndDate int64 `json:"end_date"` }{ DeviceIdentifier: deviceIdentifier, BeginDate: beginDate, EndDate: endDate, } var result struct { core.Error Data []StatisticsBase `json:"data"` } incompleteURL := "https://api.weixin.qq.com/shakearound/statistics/device?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } data = result.Data return }
// UploadThumbFromReader 上传多媒体缩略图 // NOTE: 参数 filename 不是文件路径, 是 multipart/form-data 里面 filename 的值. func UploadThumbFromReader(clt *core.Client, filename string, reader io.Reader) (mediaId, url string, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/material/add_material?type=thumb&access_token=" var fields = []core.MultipartFormField{ { IsFile: true, Name: "media", FileName: filename, Value: reader, }, } var result struct { core.Error MediaId string `json:"media_id"` URL string `json:"url"` } if err = clt.PostMultipartForm(incompleteURL, fields, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } mediaId = result.MediaId url = result.URL return }
// 获取图文素材列表. // offset: 从全部素材的该偏移位置开始返回, 0表示从第一个素材 // count: 返回素材的数量, 取值在1到20之间 func BatchGetNews(clt *core.Client, offset, count int) (rslt *BatchGetNewsResult, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=" if offset < 0 { err = fmt.Errorf("Incorrect offset: %d", offset) return } if count <= 0 { err = fmt.Errorf("Incorrect count: %d", count) return } var request = struct { MaterialType string `json:"type"` Offset int `json:"offset"` Count int `json:"count"` }{ MaterialType: MaterialTypeNews, Offset: offset, Count: count, } var result struct { core.Error BatchGetNewsResult } if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } rslt = &result.BatchGetNewsResult return }
// 数据统计 // shopId 按门店ID搜索,-1为总统计 // beginDate: 起始日期时间,格式yyyy-mm-dd,最长时间跨度为30天 // endDate: 结束日期时间戳,格式yyyy-mm-dd,最长时间跨度为30天 func List(clt *core.Client, shopId int64, beginDate, endDate string) (data []Statistics, err error) { request := struct { ShopId int64 `json:"shop_id"` BeginDate string `json:"begin_date"` EndDate string `json:"end_date"` }{ ShopId: shopId, BeginDate: beginDate, EndDate: endDate, } var result struct { core.Error Data []Statistics `json:"data"` } incompleteURL := "https://api.weixin.qq.com/bizwifi/statistics/list?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } data = result.Data return }
// 获取摇周边的设备及用户信息 // ticket: 摇周边业务的ticket,可在摇到的URL中得到,ticket生效时间为30分钟,每一次摇都会重新生成新的ticket // needPoi: 是否需要返回门店poi_id func GetShakeInfo(clt *core.Client, ticket string, needPoi bool) (info *Shakeinfo, err error) { request := struct { Ticket string `json:"ticket"` NeedPoi int `json:"need_poi,omitempty"` }{ Ticket: ticket, } if needPoi { request.NeedPoi = 1 } var result struct { core.Error Shakeinfo `json:"data"` } incompleteURL := "https://api.weixin.qq.com/shakearound/user/getshakeinfo?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } info = &result.Shakeinfo return }
// Get 获取客服聊天记录 func Get(clt *core.Client, request *GetRequest) (list []Record, err error) { const incompleteURL = "https://api.weixin.qq.com/customservice/msgrecord/getrecord?access_token=" if request.PageIndex < 1 { err = fmt.Errorf("Incorrect request.PageIndex: %d", request.PageIndex) return } if request.PageSize <= 0 { err = fmt.Errorf("Incorrect request.PageSize: %d", request.PageSize) return } var result struct { core.Error RecordList []Record `json:"recordlist"` } if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } list = result.RecordList return }
// CreateStrScenePermQrcode 创建永久二维码 // sceneStr: 场景值ID(字符串形式的ID), 字符串类型, 长度限制为1到64 func CreateStrScenePermQrcode(clt *core.Client, sceneStr string) (qrcode *PermQrcode, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" var request struct { ActionName string `json:"action_name"` ActionInfo struct { Scene struct { SceneStr string `json:"scene_str"` } `json:"scene"` } `json:"action_info"` } request.ActionName = "QR_LIMIT_STR_SCENE" request.ActionInfo.Scene.SceneStr = sceneStr var result struct { core.Error PermQrcode } if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } qrcode = &result.PermQrcode return }
// CreateTempQrcode 创建临时二维码. // sceneId: 场景值ID, 为32位非0整型 // expireSeconds: 二维码有效时间, 以秒为单位 func CreateTempQrcode(clt *core.Client, sceneId int32, expireSeconds int) (qrcode *TempQrcode, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" var request struct { ExpireSeconds int `json:"expire_seconds"` ActionName string `json:"action_name"` ActionInfo struct { Scene struct { SceneId int32 `json:"scene_id"` } `json:"scene"` } `json:"action_info"` } request.ExpireSeconds = expireSeconds request.ActionName = "QR_SCENE" request.ActionInfo.Scene.SceneId = sceneId var result struct { core.Error TempQrcode } if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } qrcode = &result.TempQrcode return }
func Get(clt *core.Client, shopId int64) (homepage *Homepage, err error) { request := struct { ShopId int64 `json:"shop_id"` }{ ShopId: shopId, } var result struct { core.Error Homepage `json:"data"` } incompleteURL := "https://api.weixin.qq.com/bizwifi/homepage/get?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } homepage = &result.Homepage return }
// Update 设置客服信息(增量更新, 不更新的可以留空). // account: 完整客服账号,格式为:账号前缀@公众号微信号 // nickname: 客服昵称,最长6个汉字或12个英文字符 // password: 客服账号登录密码 // isPasswordPlain: 标识 password 是否为明文格式, true 表示是明文密码, false 表示是密文密码. func Update(clt *core.Client, account, nickname, password string, isPasswordPlain bool) (err error) { const incompleteURL = "https://api.weixin.qq.com/customservice/kfaccount/update?access_token=" if isPasswordPlain && password != "" { md5Sum := md5.Sum([]byte(password)) password = hex.EncodeToString(md5Sum[:]) } request := struct { Account string `json:"kf_account"` Nickname string `json:"nickname,omitempty"` Password string `json:"password,omitempty"` }{ Account: account, Nickname: nickname, Password: password, } var result core.Error if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result return } return }
// 获取物料二维码 // shopId: 门店ID // imgId: 物料样式编号: // 0-二维码,可用于自由设计宣传材料; // 1-桌贴(二维码),100mm×100mm(宽×高),可直接张贴 func Get(clt *core.Client, shopId int64, imgId int) (qrcodeURL string, err error) { request := struct { ShopId int64 `json:"shop_id"` ImgId int `json:"img_id"` }{ ShopId: shopId, ImgId: imgId, } var result struct { core.Error Data struct { QrcodeURL string `json:"qrcode_url"` } `json:"data"` } incompleteURL := "https://api.weixin.qq.com/bizwifi/qrcode/get?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } qrcodeURL = result.Data.QrcodeURL return }
// 更改卡券信息接口. // sendCheck: 是否提交审核,false为修改后不会重新提审,true为修改字段后重新提审,该卡券的状态变为审核中。 func Update(clt *core.Client, cardId string, card *Card) (sendCheck bool, err error) { request := struct { CardId string `json:"card_id"` *Card }{ CardId: cardId, Card: card, } var result struct { core.Error SendCheck bool `json:"send_check"` } incompleteURL := "https://api.weixin.qq.com/card/update?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } sendCheck = result.SendCheck return }
// 批量查询设备统计数据接口 func DeviceList(clt *core.Client, date int64, pageIndex int) (rslt *DeviceListResult, err error) { request := struct { Date int64 `json:"date"` PageIndex int `json:"page_index"` }{ Date: date, PageIndex: pageIndex, } var result struct { core.Error DeviceListResult } incompleteURL := "https://api.weixin.qq.com/shakearound/statistics/devicelist?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } devices := result.DeviceListResult.Data.DeviceStatisticsList for i := 0; i < len(devices); i++ { devices[i].Ftime = result.DeviceListResult.Date } result.DeviceListResult.ItemCount = len(devices) rslt = &result.DeviceListResult return }
// 库存修改接口. // cardId: 卡券ID // increaseNum: 增加库存数量, 可以为负数 func ModifyStock(clt *core.Client, cardId string, increaseNum int) (err error) { request := struct { CardId string `json:"card_id"` IncreaseStockValue int `json:"increase_stock_value,omitempty"` ReduceStockValue int `json:"reduce_stock_value,omitempty"` }{ CardId: cardId, } switch { case increaseNum > 0: request.IncreaseStockValue = increaseNum case increaseNum < 0: request.ReduceStockValue = -increaseNum default: // increaseNum == 0 return } var result core.Error incompleteURL := "https://api.weixin.qq.com/card/modifystock?access_token=" if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result return } return }
// List 查询门店列表. // begin: 开始位置,0 即为从第一条开始查询 // limit: 返回数据条数,最大允许50,默认为20 func List(clt *core.Client, begin, limit int) (rslt *ListResult, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/poi/getpoilist?access_token=" if begin < 0 { err = fmt.Errorf("invalid begin: %d", begin) return } if limit <= 0 { err = fmt.Errorf("invalid limit: %d", limit) return } var request = struct { Begin int `json:"begin"` Limit int `json:"limit"` }{ Begin: begin, Limit: limit, } var result struct { core.Error ListResult } if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } result.ListResult.ItemCount = len(result.ListResult.List) rslt = &result.ListResult return }
// Get 获取用户基本信息. // 注意: // 1. 需要判断返回的 UserInfo.IsSubscriber 是等于 1 还是 0 // 2. lang 指定返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语, 默认为 zh_CN func Get(clt *core.Client, openId string, lang string) (info *UserInfo, err error) { switch lang { case "": lang = LanguageZhCN case LanguageZhCN, LanguageZhTW, LanguageEN: default: lang = LanguageZhCN } var incompleteURL = "https://api.weixin.qq.com/cgi-bin/user/info?openid=" + url.QueryEscape(openId) + "&lang=" + lang + "&access_token=" var result struct { core.Error UserInfo } if err = clt.GetJSON(incompleteURL, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } info = &result.UserInfo return }
// UploadVideo2 创建视频素材, 返回的素材一般用于群发消息. // mediaId: 通过 UploadVideo 上传视频文件得到 // title: 标题, 可以为空 // description: 描述, 可以为空 func UploadVideo2(clt *core.Client, mediaId, title, description string) (info *MediaInfo, err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/media/uploadvideo?access_token=" var request = struct { MediaId string `json:"media_id"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` }{ MediaId: mediaId, Title: title, Description: description, } var result struct { core.Error MediaInfo } if err = clt.PostJSON(incompleteURL, &request, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result.Error return } info = &result.MediaInfo return }
// DownloadToWriter 下载多媒体到 io.Writer. // 对于视频素材, 先通过 GetVideo 得到 Video 信息, 然后通过 Video.DownloadURL 来下载 func DownloadToWriter(clt *core.Client, mediaId string, writer io.Writer) (written int64, err error) { httpClient := clt.HttpClient if httpClient == nil { httpClient = http.DefaultClient } var request = struct { MediaId string `json:"media_id"` }{ MediaId: mediaId, } requestBodyBytes, err := json.Marshal(&request) if err != nil { return } var errorResult core.Error // 先读取 64bytes 内容来判断返回的是不是错误信息 // {"errcode":40007,"errmsg":"invalid media_id"} var buf = make([]byte, 64) token, err := clt.Token() if err != nil { return } hasRetried := false RETRY: finalURL := "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=" + url.QueryEscape(token) written, err = httpDownloadToWriter(httpClient, finalURL, requestBodyBytes, buf, writer, &errorResult) if err != nil { return } if written > 0 { return } switch errorResult.ErrCode { case core.ErrCodeOK: return // 基本不会出现 case core.ErrCodeInvalidCredential, core.ErrCodeAccessTokenExpired: retry.DebugPrintError(errorResult.ErrCode, errorResult.ErrMsg, token) if !hasRetried { hasRetried = true errorResult = core.Error{} if token, err = clt.RefreshToken(token); err != nil { return } retry.DebugPrintNewToken(token) goto RETRY } retry.DebugPrintFallthrough(token) fallthrough default: err = &errorResult return } }
// 创建自定义菜单. func Create(clt *core.Client, menu *Menu) (err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" var result core.Error if err = clt.PostJSON(incompleteURL, menu, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result return } return }
// Send 发送消息, msg 是经过 encoding/json.Marshal 得到的结果符合微信消息格式的任何数据结构. func Send(clt *core.Client, msg interface{}) (err error) { const incompleteURL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" var result core.Error if err = clt.PostJSON(incompleteURL, msg, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result return } return }
// 更新飞机票信息接口 func Checkin(clt *core.Client, para *CheckinParameters) (err error) { var result core.Error incompleteURL := "https://api.weixin.qq.com/card/boardingpass/checkin?access_token=" if err = clt.PostJSON(incompleteURL, para, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result return } return }
// 更新电影票 func UpdateUser(clt *core.Client, para *UpdateUserParameters) (err error) { var result core.Error incompleteURL := "https://api.weixin.qq.com/card/movieticket/updateuser?access_token=" if err = clt.PostJSON(incompleteURL, para, &result); err != nil { return } if result.ErrCode != core.ErrCodeOK { err = &result return } return }