mirror of
https://github.com/silenceper/wechat.git
synced 2026-03-01 00:35:26 +08:00
381 lines
10 KiB
Go
381 lines
10 KiB
Go
package material
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/silenceper/wechat/v2/officialaccount/context"
|
|
"github.com/silenceper/wechat/v2/util"
|
|
)
|
|
|
|
const (
|
|
addNewsURL = "https://api.weixin.qq.com/cgi-bin/material/add_news"
|
|
updateNewsURL = "https://api.weixin.qq.com/cgi-bin/material/update_news"
|
|
addMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/add_material"
|
|
delMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/del_material"
|
|
getMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/get_material"
|
|
getMaterialCountURL = "https://api.weixin.qq.com/cgi-bin/material/get_materialcount"
|
|
batchGetMaterialURL = "https://api.weixin.qq.com/cgi-bin/material/batchget_material"
|
|
)
|
|
|
|
// PermanentMaterialType 永久素材类型
|
|
type PermanentMaterialType string
|
|
|
|
const (
|
|
// PermanentMaterialTypeImage 永久素材图片类型(image)
|
|
PermanentMaterialTypeImage PermanentMaterialType = "image"
|
|
// PermanentMaterialTypeVideo 永久素材视频类型(video)
|
|
PermanentMaterialTypeVideo PermanentMaterialType = "video"
|
|
// PermanentMaterialTypeVoice 永久素材语音类型(voice)
|
|
PermanentMaterialTypeVoice PermanentMaterialType = "voice"
|
|
// PermanentMaterialTypeNews 永久素材图文类型(news)
|
|
PermanentMaterialTypeNews PermanentMaterialType = "news"
|
|
)
|
|
|
|
// Material 素材管理
|
|
type Material struct {
|
|
*context.Context
|
|
}
|
|
|
|
// NewMaterial init
|
|
func NewMaterial(context *context.Context) *Material {
|
|
material := new(Material)
|
|
material.Context = context
|
|
return material
|
|
}
|
|
|
|
// Article 永久图文素材
|
|
type Article struct {
|
|
Title string `json:"title"`
|
|
ThumbMediaID string `json:"thumb_media_id"`
|
|
ThumbURL string `json:"thumb_url"`
|
|
Author string `json:"author"`
|
|
Digest string `json:"digest"`
|
|
ShowCoverPic int `json:"show_cover_pic"`
|
|
Content string `json:"content"`
|
|
ContentSourceURL string `json:"content_source_url"`
|
|
URL string `json:"url"`
|
|
DownURL string `json:"down_url"`
|
|
}
|
|
|
|
// GetNews 获取/下载永久素材
|
|
func (material *Material) GetNews(id string) ([]*Article, error) {
|
|
accessToken, err := material.GetAccessToken()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uri := fmt.Sprintf("%s?access_token=%s", getMaterialURL, accessToken)
|
|
|
|
var req struct {
|
|
MediaID string `json:"media_id"`
|
|
}
|
|
req.MediaID = id
|
|
responseBytes, err := util.PostJSON(uri, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var res struct {
|
|
NewsItem []*Article `json:"news_item"`
|
|
}
|
|
err = json.Unmarshal(responseBytes, &res)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res.NewsItem, nil
|
|
}
|
|
|
|
// reqArticles 永久性图文素材请求信息
|
|
type reqArticles struct {
|
|
Articles []*Article `json:"articles"`
|
|
}
|
|
|
|
// resArticles 永久性图文素材返回结果
|
|
type resArticles struct {
|
|
util.CommonError
|
|
|
|
MediaID string `json:"media_id"`
|
|
}
|
|
|
|
// AddNews 新增永久图文素材
|
|
func (material *Material) AddNews(articles []*Article) (mediaID string, err error) {
|
|
req := &reqArticles{articles}
|
|
|
|
var accessToken string
|
|
accessToken, err = material.GetAccessToken()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s?access_token=%s", addNewsURL, accessToken)
|
|
responseBytes, err := util.PostJSON(uri, req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var res resArticles
|
|
err = json.Unmarshal(responseBytes, &res)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if res.ErrCode != 0 {
|
|
return "", fmt.Errorf("errcode=%d,errmsg=%s", res.ErrCode, res.ErrMsg)
|
|
}
|
|
mediaID = res.MediaID
|
|
return
|
|
}
|
|
|
|
// reqUpdateArticle 更新永久性图文素材请求信息
|
|
type reqUpdateArticle struct {
|
|
MediaID string `json:"media_id"`
|
|
Index int64 `json:"index"`
|
|
Articles *Article `json:"articles"`
|
|
}
|
|
|
|
// UpdateNews 更新永久图文素材
|
|
func (material *Material) UpdateNews(article *Article, mediaID string, index int64) (err error) {
|
|
req := &reqUpdateArticle{mediaID, index, article}
|
|
|
|
var accessToken string
|
|
accessToken, err = material.GetAccessToken()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s?access_token=%s", updateNewsURL, accessToken)
|
|
var response []byte
|
|
response, err = util.PostJSON(uri, req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return util.DecodeWithCommonError(response, "UpdateNews")
|
|
}
|
|
|
|
// resAddMaterial 永久性素材上传返回的结果
|
|
type resAddMaterial struct {
|
|
util.CommonError
|
|
|
|
MediaID string `json:"media_id"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// AddMaterialFromReader 上传永久性素材(处理视频需要单独上传),从 io.Reader 中读取
|
|
func (material *Material) AddMaterialFromReader(mediaType MediaType, filename string, reader io.Reader) (mediaID string, url string, err error) {
|
|
if mediaType == MediaTypeVideo {
|
|
err = errors.New("永久视频素材上传使用 AddVideo 方法")
|
|
return
|
|
}
|
|
var accessToken string
|
|
accessToken, err = material.GetAccessToken()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s?access_token=%s&type=%s", addMaterialURL, accessToken, mediaType)
|
|
var response []byte
|
|
response, err = util.PostFileFromReader("media", filename, reader, uri)
|
|
if err != nil {
|
|
return
|
|
}
|
|
var resMaterial resAddMaterial
|
|
err = json.Unmarshal(response, &resMaterial)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if resMaterial.ErrCode != 0 {
|
|
err = fmt.Errorf("AddMaterial error : errcode=%v , errmsg=%v", resMaterial.ErrCode, resMaterial.ErrMsg)
|
|
return
|
|
}
|
|
mediaID = resMaterial.MediaID
|
|
url = resMaterial.URL
|
|
return
|
|
}
|
|
|
|
// AddMaterial 上传永久性素材(处理视频需要单独上传)
|
|
func (material *Material) AddMaterial(mediaType MediaType, filename string) (mediaID string, url string, err error) {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
return material.AddMaterialFromReader(mediaType, filename, f)
|
|
}
|
|
|
|
type reqVideo struct {
|
|
Title string `json:"title"`
|
|
Introduction string `json:"introduction"`
|
|
}
|
|
|
|
// AddVideoFromReader 永久视频素材文件上传,从 io.Reader 中读取
|
|
func (material *Material) AddVideoFromReader(filename string, reader io.Reader, title, introduction string) (mediaID string, url string, err error) {
|
|
var accessToken string
|
|
accessToken, err = material.GetAccessToken()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s?access_token=%s&type=video", addMaterialURL, accessToken)
|
|
|
|
videoDesc := &reqVideo{
|
|
Title: title,
|
|
Introduction: introduction,
|
|
}
|
|
var fieldValue []byte
|
|
fieldValue, err = json.Marshal(videoDesc)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
fields := []util.MultipartFormField{
|
|
{
|
|
IsFile: true,
|
|
Fieldname: "media",
|
|
Filename: filename,
|
|
FileReader: reader,
|
|
},
|
|
{
|
|
IsFile: false,
|
|
Fieldname: "description",
|
|
Value: fieldValue,
|
|
},
|
|
}
|
|
|
|
var response []byte
|
|
response, err = util.PostMultipartForm(fields, uri)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var resMaterial resAddMaterial
|
|
err = json.Unmarshal(response, &resMaterial)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if resMaterial.ErrCode != 0 {
|
|
err = fmt.Errorf("AddMaterial error : errcode=%v , errmsg=%v", resMaterial.ErrCode, resMaterial.ErrMsg)
|
|
return
|
|
}
|
|
mediaID = resMaterial.MediaID
|
|
url = resMaterial.URL
|
|
return
|
|
}
|
|
|
|
// AddVideo 永久视频素材文件上传
|
|
func (material *Material) AddVideo(filename, title, introduction string) (mediaID string, url string, err error) {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
return material.AddVideoFromReader(filename, f, title, introduction)
|
|
}
|
|
|
|
type reqDeleteMaterial struct {
|
|
MediaID string `json:"media_id"`
|
|
}
|
|
|
|
// DeleteMaterial 删除永久素材
|
|
func (material *Material) DeleteMaterial(mediaID string) error {
|
|
accessToken, err := material.GetAccessToken()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uri := fmt.Sprintf("%s?access_token=%s", delMaterialURL, accessToken)
|
|
response, err := util.PostJSON(uri, reqDeleteMaterial{mediaID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return util.DecodeWithCommonError(response, "DeleteMaterial")
|
|
}
|
|
|
|
// ArticleList 永久素材列表
|
|
type ArticleList struct {
|
|
util.CommonError
|
|
TotalCount int64 `json:"total_count"`
|
|
ItemCount int64 `json:"item_count"`
|
|
Item []ArticleListItem `json:"item"`
|
|
}
|
|
|
|
// ArticleListItem 用于 ArticleList 的 item 节点
|
|
type ArticleListItem struct {
|
|
MediaID string `json:"media_id"`
|
|
Content ArticleListContent `json:"content"`
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
UpdateTime int64 `json:"update_time"`
|
|
}
|
|
|
|
// ArticleListContent 用于 ArticleListItem 的 content 节点
|
|
type ArticleListContent struct {
|
|
NewsItem []Article `json:"news_item"`
|
|
UpdateTime int64 `json:"update_time"`
|
|
CreateTime int64 `json:"create_time"`
|
|
}
|
|
|
|
// reqBatchGetMaterial BatchGetMaterial 请求参数
|
|
type reqBatchGetMaterial struct {
|
|
Type PermanentMaterialType `json:"type"`
|
|
Count int64 `json:"count"`
|
|
Offset int64 `json:"offset"`
|
|
}
|
|
|
|
// BatchGetMaterial 批量获取永久素材
|
|
//
|
|
//reference:https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Get_materials_list.html
|
|
func (material *Material) BatchGetMaterial(permanentMaterialType PermanentMaterialType, offset, count int64) (list ArticleList, err error) {
|
|
var accessToken string
|
|
accessToken, err = material.GetAccessToken()
|
|
if err != nil {
|
|
return
|
|
}
|
|
uri := fmt.Sprintf("%s?access_token=%s", batchGetMaterialURL, accessToken)
|
|
|
|
req := reqBatchGetMaterial{
|
|
Type: permanentMaterialType,
|
|
Offset: offset,
|
|
Count: count,
|
|
}
|
|
|
|
var response []byte
|
|
response, err = util.PostJSON(uri, req)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = util.DecodeWithError(response, &list, "BatchGetMaterial")
|
|
return
|
|
}
|
|
|
|
// ResMaterialCount 素材总数
|
|
type ResMaterialCount struct {
|
|
util.CommonError
|
|
VoiceCount int64 `json:"voice_count"` // 语音总数量
|
|
VideoCount int64 `json:"video_count"` // 视频总数量
|
|
ImageCount int64 `json:"image_count"` // 图片总数量
|
|
NewsCount int64 `json:"news_count"` // 图文总数量
|
|
}
|
|
|
|
// GetMaterialCount 获取素材总数。
|
|
func (material *Material) GetMaterialCount() (res ResMaterialCount, err error) {
|
|
var accessToken string
|
|
accessToken, err = material.GetAccessToken()
|
|
if err != nil {
|
|
return
|
|
}
|
|
uri := fmt.Sprintf("%s?access_token=%s", getMaterialCountURL, accessToken)
|
|
var response []byte
|
|
response, err = util.HTTPGet(uri)
|
|
if err != nil {
|
|
return
|
|
}
|
|
err = util.DecodeWithError(response, &res, "GetMaterialCount")
|
|
return
|
|
}
|