添加文件发送接口

This commit is contained in:
eatMoreApple 2021-04-30 00:04:45 +08:00
parent 3c7fec07f3
commit e751e13afd
9 changed files with 1046 additions and 985 deletions

View File

@ -291,3 +291,29 @@ func TestRevokeMessage(t *testing.T) {
t.Error(err)
}
}
func TestSendFile(t *testing.T) {
bot := defaultBot(Desktop)
if err := bot.HotLogin(NewJsonFileHotReloadStorage("2.json"), true); err != nil {
t.Error(err)
return
}
self, err := bot.GetCurrentUser()
if err != nil {
t.Error(err)
return
}
fh, err := self.FileHelper()
if err != nil {
t.Error(err)
return
}
f, _ := os.Open("Taylor+Swift+-+Red")
defer f.Close()
msg, err := self.SendFileToFriend(fh, f)
if err != nil {
t.Error(err)
return
}
t.Log(msg.MsgId)
}

View File

@ -200,47 +200,64 @@ func (c *Caller) WebWxOplog(request *BaseRequest, remarkName, toUserName string)
return parseBaseResponseError(resp)
}
// 发送图片消息接口
func (c *Caller) WebWxSendImageMsg(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) {
func (c *Caller) UploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName, mediaType string) (*UploadResponse, error) {
// 首先尝试上传图片
sate, err := file.Stat()
if err != nil {
return nil, err
}
var resp *ReturnResponse
if sate.Size() <= chunkSize {
resp = NewReturnResponse(c.Client.WebWxUploadMedia(file, request, info, fromUserName, toUserName, "pic"))
} else {
resp = NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName, "pic"))
}
resp := NewReturnResponse(c.Client.WebWxUploadMediaByChunk(file, request, info, fromUserName, toUserName, mediaType))
// 无错误上传成功之后获取请求结果,判断结果是否正常
if resp.Err() != nil {
return nil, resp.Err()
}
defer resp.Body.Close()
var item struct {
BaseResponse BaseResponse
MediaId string
}
var item UploadResponse
if err = resp.ScanJSON(&item); err != nil {
return nil, err
if err := resp.ScanJSON(&item); err != nil {
return &item, err
}
if !item.BaseResponse.Ok() {
return nil, item.BaseResponse
return &item, item.BaseResponse
}
if len(item.MediaId) == 0 {
return nil, errors.New("upload failed")
return &item, errors.New("upload failed")
}
return &item, nil
}
// 发送图片消息接口
func (c *Caller) WebWxSendImageMsg(file *os.File, request *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) {
// 首先尝试上传图片
resp, err := c.UploadMedia(file, request, info, fromUserName, toUserName, "pic")
if err != nil {
return nil, err
}
// 构造新的图片类型的信息
msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, item.MediaId)
msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, resp.MediaId)
// 发送图片信息
resp = NewReturnResponse(c.Client.WebWxSendMsgImg(msg, request, info))
return c.WebWxSendAppMsg(msg, request, info)
}
func (c *Caller) WebWxSendFile(file *os.File, req *BaseRequest, info *LoginInfo, fromUserName, toUserName string) (*SentMessage, error) {
resp, err := c.UploadMedia(file, req, info, fromUserName, toUserName, "doc")
if err != nil {
return nil, err
}
// 构造新的图片类型的信息
msg := NewMediaSendMessage(ImageMessage, fromUserName, toUserName, resp.MediaId)
stat, _ := file.Stat()
appMsg := NewFileAppMessage(stat, resp.MediaId)
content, err := appMsg.XmlByte()
if err != nil {
return nil, err
}
msg.Content = string(content)
return c.WebWxSendAppMsg(msg, req, info)
}
// 发送媒体消息
func (c *Caller) WebWxSendAppMsg(msg *SendMessage, req *BaseRequest, info *LoginInfo) (*SentMessage, error) {
resp := NewReturnResponse(c.Client.WebWxSendMsgImg(msg, req, info))
sendSuccessMsg := &SentMessage{SendMessage: msg}
err = parseMessageResponseError(resp, sendSuccessMsg)
err := parseMessageResponseError(resp, sendSuccessMsg)
return sendSuccessMsg, err
}
@ -312,7 +329,7 @@ func parseMessageResponseError(resp *ReturnResponse, msg *SentMessage) error {
if !messageResp.BaseResponse.Ok() {
return messageResp.BaseResponse
}
//// 发送成功之后将msgId赋值给SendMessage
// 发送成功之后将msgId赋值给SendMessage
msg.MsgId = messageResp.MsgID
return nil
}

160
client.go
View File

@ -251,104 +251,12 @@ func (c *Client) WebWxGetHeadImg(headImageUrl string) (*http.Response, error) {
return c.Do(req)
}
// 上传文件
func (c *Client) WebWxUploadMedia(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName, mediaType string) (*http.Response, error) {
// 获取文件上传的类型
contentType, err := GetFileContentType(file)
if err != nil {
return nil, err
}
path, _ := url.Parse(c.webWxUpLoadMediaUrl)
params := url.Values{}
params.Add("f", "json")
path.RawQuery = params.Encode()
sate, err := file.Stat()
if err != nil {
return nil, err
}
cookies := c.Jar.Cookies(path)
webWxDataTicket := getWebWxDataTicket(cookies)
// 文件复位
if _, err = file.Seek(0, 0); err != nil {
return nil, err
}
buffer := bytes.Buffer{}
if _, err := buffer.ReadFrom(file); err != nil {
return nil, err
}
data := buffer.Bytes()
fileMd5 := fmt.Sprintf("%x", md5.Sum(data))
uploadMediaRequest := map[string]interface{}{
"UploadType": 2,
"BaseRequest": request,
"ClientMediaId": time.Now().Unix() * 1e4,
"TotalLen": sate.Size(),
"StartPos": 0,
"DataLen": sate.Size(),
"MediaType": 4,
"FromUserName": forUserName,
"ToUserName": toUserName,
"FileMd5": fileMd5,
}
uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest)
if err != nil {
return nil, err
}
content := map[string]interface{}{
"id": "WU_FILE_0",
"name": file.Name(),
"type": contentType,
"lastModifiedDate": sate.ModTime().Format(TimeFormat),
"size": sate.Size(),
"mediatype": mediaType,
"webwx_data_ticket": webWxDataTicket,
"pass_ticket": info.PassTicket,
}
body, err := ToBuffer(content)
if err != nil {
return nil, err
}
writer := multipart.NewWriter(body)
if err = writer.WriteField("uploadmediarequest", string(uploadMediaRequestByte)); err != nil {
return nil, err
}
if w, err := writer.CreateFormFile("filename", file.Name()); err != nil {
return nil, err
} else {
if _, err = w.Write(data); err != nil {
return nil, err
}
}
ct := writer.FormDataContentType()
if err = writer.Close(); err != nil {
return nil, err
}
req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Set("Content-Type", ct)
return c.Do(req)
}
func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName, mediaType string) (*http.Response, error) {
// 获取文件上传的类型
contentType, err := GetFileContentType(file)
if err != nil {
return nil, err
}
path, _ := url.Parse(c.webWxUpLoadMediaUrl)
params := url.Values{}
params.Add("f", "json")
path.RawQuery = params.Encode()
sate, err := file.Stat()
if err != nil {
return nil, err
}
cookies := c.Jar.Cookies(path)
webWxDataTicket := getWebWxDataTicket(cookies)
// 将文件的游标复原到原点
// 上面获取文件的类型的时候已经读取了512个字节
@ -363,6 +271,20 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
data := buffer.Bytes()
fileMd5 := fmt.Sprintf("%x", md5.Sum(data))
sate, err := file.Stat()
if err != nil {
return nil, err
}
path, _ := url.Parse(c.webWxUpLoadMediaUrl)
params := url.Values{}
params.Add("f", "json")
path.RawQuery = params.Encode()
cookies := c.Jar.Cookies(path)
webWxDataTicket := getWebWxDataTicket(cookies)
uploadMediaRequest := map[string]interface{}{
"UploadType": 2,
"BaseRequest": request,
@ -375,25 +297,25 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
"ToUserName": toUserName,
"FileMd5": fileMd5,
}
uploadMediaRequestByte, err := json.Marshal(uploadMediaRequest)
if err != nil {
return nil, err
}
chunks := sate.Size() / chunkSize
var chunks int64
if sate.Size() > chunkSize {
chunks = sate.Size() / chunkSize
if chunks*chunkSize < sate.Size() {
chunks++
}
} else {
chunks = 1
}
var resp *http.Response
for chunk := 0; int64(chunk) < chunks; chunk++ {
var isLastTime bool
if int64(chunk)+1 == chunks {
isLastTime = true
}
content := map[string]string{
"id": "WU_FILE_0",
"name": file.Name(),
@ -403,9 +325,23 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
"mediatype": mediaType,
"webwx_data_ticket": webWxDataTicket,
"pass_ticket": info.PassTicket,
"chunks": strconv.FormatInt(chunks, 10),
"chunk": strconv.Itoa(chunk),
}
if chunks > 1 {
content["chunks"] = strconv.FormatInt(chunks, 10)
}
// 分块上传
for chunk := 0; int64(chunk) < chunks; chunk++ {
var isLastTime bool
if int64(chunk)+1 == chunks {
isLastTime = true
}
if chunks > 1 {
content["chunk"] = strconv.Itoa(chunk)
}
var formBuffer bytes.Buffer
writer := multipart.NewWriter(&formBuffer)
@ -450,7 +386,7 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
}
}
}
// 将最后一次携带文件信息的response返回
return resp, err
}
@ -645,3 +581,21 @@ func (c *Client) WebWxRevokeMsg(msg *SentMessage, request *BaseRequest) (*http.R
req.Header.Set("Content-Type", jsonContentType)
return c.Do(req)
}
// 校验上传文件
func (c *Client) webWxCheckUpload(stat os.FileInfo, request *BaseRequest, fileMd5, fromUserName, toUserName string) (*http.Response, error) {
path, _ := url.Parse(c.webWxCheckUploadUrl)
content := map[string]interface{}{
"BaseRequest": request,
"FileMd5": fileMd5,
"FileName": stat.Name(),
"FileSize": stat.Size(),
"FileType": 7,
"FromUserName": fromUserName,
"ToUserName": toUserName,
}
body, _ := ToBuffer(content)
req, _ := http.NewRequest(http.MethodPost, path.String(), body)
req.Header.Add("Content-Type", jsonContentType)
return c.Do(req)
}

View File

@ -41,6 +41,7 @@ const (
webWxGetMediaNormalUrl = "https://file.wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia"
webWxUpdateChatRoomNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxupdatechatroom"
webWxRevokeMsgNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxrevokemsg"
webWxCheckUploadNormalUrl = "https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxcheckupload"
// Desktop urls
@ -65,6 +66,7 @@ const (
webWxGetMediaDesktopUrl = "https://file.wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmedia"
webWxUpdateChatRoomDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxupdatechatroom"
webWxRevokeMsgDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxrevokemsg"
webWxCheckUploadDesktopUrl = "https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxcheckupload"
jsonContentType = "application/json; charset=utf-8"
uosPatchClientVersion = "2.0.0"
@ -101,6 +103,15 @@ const (
FEMALE = 2
)
const chunkSize int64 = 512 * 1024
const (
// 分块上传时每次上传的文件的大小
chunkSize int64 = (1 << 20) / 2 // 0.5m
// 需要检测的文件大小
needCheckSize int64 = 25 << 20 // 20m
// 最大文件上传大小
maxFileUploadSize int64 = 50 << 20 // 50m
// 最大图片上传大小
maxImageUploadSize int64 = 20 << 20 // 20m
)
const TimeFormat = "Mon Jan 02 2006 15:04:05 GMT+0800 (中国标准时间)"

View File

@ -202,3 +202,8 @@ type MessageResponse struct {
LocalID string
MsgID string
}
type UploadResponse struct {
BaseResponse BaseResponse
MediaId string
}

View File

@ -471,3 +471,34 @@ type SentMessage struct {
func (s *SentMessage) Revoke() error {
return s.Self.RevokeMessage(s)
}
type FileAppMessage struct {
AppMsg xml.Name `xml:"appmsg"`
Type int `xml:"type"`
AppId string `xml:"appid,attr"` // wxeb7ec651dd0aefa9
SdkVer string `xml:"sdkver,attr"`
Title string `xml:"title"`
Des string `xml:"des"`
Action string `xml:"action"`
Content string `xml:"content"`
Url string `xml:"url"`
LowUrl string `xml:"lowurl"`
ExtInfo string `xml:"extinfo"`
AppAttach struct {
TotalLen int64 `xml:"totallen"`
AttachId string `xml:"attachid"`
FileExt string `xml:"fileext"`
} `xml:"appattach"`
}
func (f FileAppMessage) XmlByte() ([]byte, error) {
return xml.Marshal(f)
}
func NewFileAppMessage(stat os.FileInfo, attachId string) *FileAppMessage {
m := &FileAppMessage{AppId: "wxeb7ec651dd0aefa9", Title: stat.Name()}
m.AppAttach.AttachId = attachId
m.AppAttach.TotalLen = stat.Size()
m.AppAttach.FileExt = getFileExt(stat.Name())
return m
}

View File

@ -64,3 +64,11 @@ func GetFileContentType(file multipart.File) (string, error) {
}
return http.DetectContentType(data), nil
}
func getFileExt(name string) string {
results := strings.Split(name, ".")
if len(results) == 0 {
return "undefined"
}
return results[len(results)-1]
}

3
url.go
View File

@ -23,6 +23,7 @@ type UrlManager struct {
webWxGetMediaUrl string
webWxUpdateChatRoomUrl string
webWxRevokeMsg string
webWxCheckUploadUrl string
}
var (
@ -49,6 +50,7 @@ var (
webWxGetMediaUrl: webWxGetMediaDesktopUrl,
webWxUpdateChatRoomUrl: webWxUpdateChatRoomDesktopUrl,
webWxRevokeMsg: webWxRevokeMsgDesktopUrl,
webWxCheckUploadUrl: webWxCheckUploadDesktopUrl,
}
// 网页版
@ -74,6 +76,7 @@ var (
webWxGetMediaUrl: webWxGetMediaNormalUrl,
webWxUpdateChatRoomUrl: webWxUpdateChatRoomNormalUrl,
webWxRevokeMsg: webWxRevokeMsgNormalUrl,
webWxCheckUploadUrl: webWxCheckUploadNormalUrl,
}
)

View File

@ -242,6 +242,12 @@ func (s *Self) SendImageToFriend(friend *Friend, file *os.File) (*SentMessage, e
return s.Bot.Caller.WebWxSendImageMsg(file, req, info, s.UserName, friend.UserName)
}
func (s *Self) SendFileToFriend(friend *Friend, file *os.File) (*SentMessage, error) {
req := s.Bot.storage.Request
info := s.Bot.storage.LoginInfo
return s.Bot.Caller.WebWxSendFile(file, req, info, s.UserName, friend.UserName)
}
// 设置好友备注
// self.SetRemarkNameToFriend(friend, "remark") // or friend.SetRemarkName("remark")
func (s *Self) SetRemarkNameToFriend(friend *Friend, remarkName string) error {