Compare commits

...

8 Commits

Author SHA1 Message Date
多吃点苹果
3e6fc41298
[fix]: 修改同步消息逻辑 (#236) 2023-02-05 11:23:05 +08:00
ford
3c7ac0cc75
【opt】优化好友公众号群组获取接口防止频繁发送网络请求 (#234)
Co-authored-by: wenyoufu <wenyoufu@jd.com>
2023-02-05 11:20:40 +08:00
多吃点苹果
99af4a2685
[refactor]: 添加 CookieGroup (#233) 2023-02-05 00:20:37 +08:00
多吃点苹果
0c57ab1ed5
更新 Group Display (#232) 2023-02-04 23:55:55 +08:00
多吃点苹果
a72c165c59
删除根据备注查找群组功能 (#231) 2023-02-04 12:04:36 +08:00
多吃点苹果
35a348f0af
[feat]: 添加最近联系人和公众号文章列表 (#230) 2023-02-04 11:59:17 +08:00
多吃点苹果
66c4bebd1f
提升上传文件性能 (#228) 2023-02-03 22:17:40 +08:00
多吃点苹果
fbfd691cb4
[style]: update User display (#227) 2023-02-03 17:57:58 +08:00
9 changed files with 105 additions and 52 deletions

11
bot.go
View File

@ -169,9 +169,10 @@ func (b *Bot) WebInit() error {
return err
}
// 设置当前的用户
b.self = &Self{bot: b, User: &resp.User}
b.self = &Self{bot: b, User: resp.User}
b.self.formatEmoji()
b.self.self = b.self
resp.ContactList.init(b.self)
b.Storage.Response = resp
// 通知手机客户端已经登录
@ -222,8 +223,8 @@ func (b *Bot) syncCheck() error {
if !resp.Success() {
return resp.Err()
}
// 如果Selector不为0则获取消息
if !resp.NorMal() {
switch resp.Selector {
case SelectorNewMsg:
messages, err := b.syncMessage()
if err != nil {
return err
@ -236,8 +237,12 @@ func (b *Bot) syncCheck() error {
// 默认同步调用
// 如果异步调用则需自行处理
// 如配合 openwechat.MessageMatchDispatcher 使用
// NOTE: 请确保 MessageHandler 不会阻塞,否则会导致收不到后续的消息
b.MessageHandler(message)
}
case SelectorModContact:
case SelectorAddOrDelContact:
case SelectorModChatRoom:
}
}
return err

View File

@ -314,6 +314,8 @@ func (c *Client) WebWxGetHeadImg(user *User) (*http.Response, error) {
return c.Do(req)
}
// WebWxUploadMediaByChunk 分块上传文件
// TODO 优化掉这个函数
func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, info *LoginInfo, forUserName, toUserName string) (*http.Response, error) {
// 获取文件上传的类型
contentType, err := GetFileContentType(file)
@ -358,7 +360,11 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
path.RawQuery = params.Encode()
cookies := c.Jar().Cookies(path)
webWxDataTicket := getWebWxDataTicket(cookies)
webWxDataTicket, err := getWebWxDataTicket(cookies)
if err != nil {
return nil, err
}
uploadMediaRequest := map[string]interface{}{
"UploadType": 2,
@ -410,16 +416,17 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
return nil, err
}
var chunkBuff = make([]byte, chunkSize)
var formBuffer = bytes.NewBuffer(nil)
// 分块上传
for chunk := 0; int64(chunk) < chunks; chunk++ {
isLastTime := int64(chunk)+1 == chunks
if chunks > 1 {
content["chunk"] = strconv.Itoa(chunk)
}
var formBuffer = bytes.NewBuffer(nil)
formBuffer.Reset()
writer := multipart.NewWriter(formBuffer)
@ -434,34 +441,33 @@ func (c *Client) WebWxUploadMediaByChunk(file *os.File, request *BaseRequest, in
}
w, err := writer.CreateFormFile("filename", file.Name())
if err != nil {
return nil, err
}
chunkData := make([]byte, chunkSize)
n, err := file.Read(chunkData)
n, err := file.Read(chunkBuff)
if err != nil && err != io.EOF {
return nil, err
}
if _, err = w.Write(chunkData[:n]); err != nil {
if _, err = w.Write(chunkBuff[:n]); err != nil {
return nil, err
}
ct := writer.FormDataContentType()
if err = writer.Close(); err != nil {
return nil, err
}
req, _ := http.NewRequest(http.MethodPost, path.String(), formBuffer)
req.Header.Set("Content-Type", ct)
// 发送数据
resp, err = c.Do(req)
if err != nil {
return nil, err
}
isLastTime := int64(chunk)+1 == chunks
// 如果不是最后一次, 解析有没有错误
if !isLastTime {
parser := MessageResponseParser{Reader: resp.Body}
@ -591,13 +597,18 @@ func (c *Client) WebWxGetVideo(msg *Message, info *LoginInfo) (*http.Response, e
// WebWxGetMedia 获取文件消息的文件响应
func (c *Client) WebWxGetMedia(msg *Message, info *LoginInfo) (*http.Response, error) {
path, _ := url.Parse(c.Domain.FileHost() + webwxgetmedia)
cookies := c.Jar().Cookies(path)
webWxDataTicket, err := getWebWxDataTicket(cookies)
if err != nil {
return nil, err
}
params := url.Values{}
params.Add("sender", msg.FromUserName)
params.Add("mediaid", msg.MediaId)
params.Add("encryfilename", msg.EncryFileName)
params.Add("fromuser", strconv.FormatInt(info.WxUin, 10))
params.Add("pass_ticket", info.PassTicket)
params.Add("webwx_data_ticket", getWebWxDataTicket(c.Jar().Cookies(path)))
params.Add("webwx_data_ticket", webWxDataTicket)
path.RawQuery = params.Encode()
req, _ := http.NewRequest(http.MethodGet, path.String(), nil)
req.Header.Add("Referer", c.Domain.BaseHost()+"/")

View File

@ -9,6 +9,7 @@ import (
)
// Jar is a struct which as same as cookiejar.Jar
// cookiejar.Jar's fields are private, so we can't use it directly
type Jar struct {
PsList cookiejar.PublicSuffixList
@ -57,3 +58,24 @@ type entry struct {
// equal Creation time. This simplifies testing.
seqNum uint64
}
// CookieGroup is a group of cookies
type CookieGroup []*http.Cookie
func (c CookieGroup) GetByName(cookieName string) (cookie *http.Cookie, exist bool) {
for _, cookie := range c {
if cookie.Name == cookieName {
return cookie, true
}
}
return nil, false
}
func getWebWxDataTicket(cookies []*http.Cookie) (string, error) {
cookieGroup := CookieGroup(cookies)
cookie, exist := cookieGroup.GetByName("webwx_data_ticket")
if !exist {
return "", ErrWebWxDataTicketNotFound
}
return cookie.Value, nil
}

View File

@ -57,9 +57,9 @@ type WebInitResponse struct {
SKey string
BaseResponse BaseResponse
SyncKey SyncKey
User User
MPSubscribeMsgList []MPSubscribeMsg
ContactList []User
User *User
MPSubscribeMsgList []*MPSubscribeMsg
ContactList Members
}
// MPSubscribeMsg 公众号的订阅信息
@ -68,12 +68,14 @@ type MPSubscribeMsg struct {
Time int64
UserName string
NickName string
MPArticleList []struct {
Title string
Cover string
Digest string
Url string
}
MPArticleList []*MPArticle
}
type MPArticle struct {
Title string
Cover string
Digest string
Url string
}
type UserDetailItem struct {

View File

@ -32,6 +32,9 @@ var (
// ErrLoginTimeout define login timeout error
ErrLoginTimeout = errors.New("login timeout")
// ErrWebWxDataTicketNotFound define webwx_data_ticket not found error
ErrWebWxDataTicketNotFound = errors.New("webwx_data_ticket not found")
)
// Error impl error interface

View File

@ -38,15 +38,6 @@ func GetRandomDeviceId() string {
return builder.String()
}
func getWebWxDataTicket(cookies []*http.Cookie) string {
for _, cookie := range cookies {
if cookie.Name == "webwx_data_ticket" {
return cookie.Value
}
}
return ""
}
// GetFileContentType 获取文件上传的类型
func GetFileContentType(file multipart.File) (string, error) {
data := make([]byte, 512)

View File

@ -10,7 +10,11 @@ type Friend struct{ *User }
// implement fmt.Stringer
func (f *Friend) String() string {
return fmt.Sprintf("<Friend:%s>", f.NickName)
display := f.NickName
if f.RemarkName != "" {
display = f.RemarkName
}
return fmt.Sprintf("<Friend:%s>", display)
}
// SetRemarkName 重命名当前好友
@ -300,11 +304,6 @@ func (g Groups) SearchByNickName(limit int, nickName string) (results Groups) {
return g.Search(limit, func(group *Group) bool { return group.NickName == nickName })
}
// SearchByRemarkName 根据备注查找群组
func (g Groups) SearchByRemarkName(limit int, remarkName string) (results Groups) {
return g.Search(limit, func(group *Group) bool { return group.RemarkName == remarkName })
}
// Search 根据自定义条件查找群组
func (g Groups) Search(limit int, searchFuncList ...func(group *Group) bool) (results Groups) {
return g.AsMembers().Search(limit, func(user *User) bool {
@ -445,11 +444,6 @@ func (g Groups) GetByUsername(username string) *Group {
return g.SearchByUserName(1, username).First()
}
// GetByRemarkName 根据remarkName查询一个Group
func (g Groups) GetByRemarkName(remarkName string) *Group {
return g.SearchByRemarkName(1, remarkName).First()
}
// GetByNickName 根据nickname查询一个Group
func (g Groups) GetByNickName(nickname string) *Group {
return g.SearchByNickName(1, nickname).First()

View File

@ -25,7 +25,11 @@ func (s SyncCheckResponse) Success() bool {
}
func (s SyncCheckResponse) NorMal() bool {
return s.Success() && s.Selector == "0"
return s.Success() && s.Selector == SelectorNormal
}
func (s SyncCheckResponse) HasNewMessage() bool {
return s.Success() && s.Selector == SelectorNewMsg
}
func (s SyncCheckResponse) Err() error {

33
user.go
View File

@ -58,14 +58,14 @@ type User struct {
// implement fmt.Stringer
func (u *User) String() string {
format := "User"
if u.IsFriend() {
if u.IsSelf() {
format = "Self"
} else if u.IsFriend() {
format = "Friend"
} else if u.IsGroup() {
format = "Group"
} else if u.IsMP() {
format = "MP"
} else if u.IsSelf() {
format = "Self"
}
return fmt.Sprintf("<%s:%s>", format, u.NickName)
}
@ -288,13 +288,18 @@ func (s *Self) FileHelper() *Friend {
}
return s.fileHelper
}
func (s *Self) ChkFrdGrpMpNil() bool {
return s.friends == nil && s.groups == nil && s.mps == nil
}
// Friends 获取所有的好友
func (s *Self) Friends(update ...bool) (Friends, error) {
if s.friends == nil || (len(update) > 0 && update[0]) {
if (len(update) > 0 && update[0]) || s.ChkFrdGrpMpNil() {
if _, err := s.Members(true); err != nil {
return nil, err
}
}
if s.friends == nil || (len(update) > 0 && update[0]) {
s.friends = s.members.Friends()
}
return s.friends, nil
@ -302,10 +307,14 @@ func (s *Self) Friends(update ...bool) (Friends, error) {
// Groups 获取所有的群组
func (s *Self) Groups(update ...bool) (Groups, error) {
if s.groups == nil || (len(update) > 0 && update[0]) {
if (len(update) > 0 && update[0]) || s.ChkFrdGrpMpNil() {
if _, err := s.Members(true); err != nil {
return nil, err
}
}
if s.groups == nil || (len(update) > 0 && update[0]) {
s.groups = s.members.Groups()
}
return s.groups, nil
@ -313,10 +322,12 @@ func (s *Self) Groups(update ...bool) (Groups, error) {
// Mps 获取所有的公众号
func (s *Self) Mps(update ...bool) (Mps, error) {
if s.mps == nil || (len(update) > 0 && update[0]) {
if (len(update) > 0 && update[0]) || s.ChkFrdGrpMpNil() {
if _, err := s.Members(true); err != nil {
return nil, err
}
}
if s.mps == nil || (len(update) > 0 && update[0]) {
s.mps = s.members.MPs()
}
return s.mps, nil
@ -668,6 +679,16 @@ func (s *Self) SendVideoToGroups(video io.Reader, delay time.Duration, groups ..
return s.sendVideoToMembers(video, delay, members...)
}
// ContactList 获取最近的联系人列表
func (s *Self) ContactList() Members {
return s.Bot().Storage.Response.ContactList
}
// MPSubscribeList 获取部分公众号文章列表
func (s *Self) MPSubscribeList() []*MPSubscribeMsg {
return s.Bot().Storage.Response.MPSubscribeMsgList
}
// Members 抽象的用户组
type Members []*User