├── imgs ├── info.png └── run.png ├── go.mod ├── .github └── ISSUE_TEMPLATE │ ├── -报告bug-bug-report.md │ └── -功能请求-feature-request.md ├── LICENSE ├── README.md ├── test ├── testChatRoom.go └── test.go ├── doc.md └── wechatapi.go /imgs/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NHPT/wechatapi/HEAD/imgs/info.png -------------------------------------------------------------------------------- /imgs/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NHPT/wechatapi/HEAD/imgs/run.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/NHPT/wechatapi 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/buger/jsonparser v1.1.1 7 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e 8 | ) 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-报告bug-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "【报告Bug】Bug report" 3 | about: "【告诉我Bug信息】Create a report to help us improve" 4 | title: "【Bug】" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **描述你遇到的问题** 11 | 12 | 13 | **复现步骤** 14 | 19 | 20 | **预期行为** 21 | 22 | 23 | **屏幕截图** 24 | 25 | 26 | **运行环境** 27 | - 操作系统 28 | 29 | - Go 版本 30 | 31 | - 软件版本 32 | 33 | 34 | **补充说明** 35 | 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/-功能请求-feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "【功能请求】Feature request" 3 | about: "【告诉我你建议的功能】Suggest an idea for this project" 4 | title: "【功能需求】" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **【您的功能请求是否与问题有关?请描述一下。】Is your feature request related to a problem? Please describe.** 11 | 【对问题的清晰简洁的描述。】A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **【描述您想要的解决方案】Describe the solution you'd like** 14 | 【告诉我们如何加入功能来满足你的需求】A clear and concise description of what you want to happen. 15 | 16 | **【备选方案】Describe alternatives you've considered** 17 | 【描述你认为也可以接受的备选方案】A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **【其他内容】Additional context** 20 | 【放一些截图和其他有助于理解我们你的需求的内容】Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 NHPT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 项目介绍 2 | 3 | 本项目是基于Web微信开发的个人微信API接口,支持扫码登录,过期重新登录,获取微信联系人信息,获取微信群及成员信息,接收消息,发送消息,邀请加群,移除群聊和退出登录等功能。可基于此API接口实现AI、WebHook、自动回复、智能客服等和个人微信的对接。 4 | 5 | ## 登录 6 | 7 | - 直接通过控制台输出的二维码扫码登录 8 | - 访问输出的链接扫码登录 9 | 10 | ## 获取联系人 11 | 12 | - 可获取所有联系人信息和数量 13 | - 也可以根据省份、城市、性别等条件获取符合条件的联系人信息和数量。 14 | - 可根据备注获取联系人信息和数量 15 | - 可根据昵称获取联系人信息和数量 16 | 17 | ## 获取群聊 18 | 19 | - 可获取所有群聊数量和名称,以及群成员信息 20 | 21 | ## 获取公众号 22 | 23 | - 可获取所有公众号数量和名称 24 | 25 | ## 接收消息 26 | 27 | - 接收所有消息,返回消息来源、消息类型、发送者、消息内容等 28 | - 根据来源筛选消息:群聊、公众号、联系人 29 | - 根据类型筛选消息:文本、图片、表情包等 30 | 31 | |**消息类型**|**识别**|**提取**| 32 | |-|-|-| 33 | |文本|√|√| 34 | |图片|√|√| 35 | |语音|√|×| 36 | |视频|√|×| 37 | |表情包|√|√| 38 | |公众号推文|√|√| 39 | |文件|√|×| 40 | |视频号消息|√|×| 41 | |拍一拍|√|√| 42 | 43 | 44 | ## 发送消息 45 | 46 | - 发送文本消息 47 | 48 | ## 邀请入群 49 | 50 | - 发送邀请入群的消息 51 | 52 | ## 移出群聊 53 | 54 | - 将指定用户移出指定群聊 55 | 56 | # 安装与使用 57 | 58 | ## 安装 59 | 60 | ```Bash 61 | go get github.com/NHPT/wechatapi 62 | ``` 63 | 64 | ## 使用示例 65 | 66 | 示例代码见[test.go](https://github.com/NHPT/wechatapi/blob/main/test/test.go) 67 | 68 | ![](https://github.com/NHPT/wechatapi/blob/main/imgs/run.png) 69 | 70 | ![](https://github.com/NHPT/wechatapi/blob/main/imgs/info.png) 71 | 72 | ## 详细文档 73 | 74 | 请查阅[文档](https://github.com/NHPT/wechatapi/blob/main/doc.md) 75 | 76 | # License 77 | 78 | [LICENSE](https://github.com/NHPT/wechatapi/blob/main/LICENSE) 79 | -------------------------------------------------------------------------------- /test/testChatRoom.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/NHPT/wechatapi" 9 | "github.com/buger/jsonparser" 10 | ) 11 | 12 | func main() { 13 | // 登录 14 | err := wechatapi.Login() 15 | fmt.Println(err, "登录信息") 16 | if err != nil { 17 | fmt.Println(err) 18 | return 19 | } 20 | // 获取自己的信息 21 | myself := wechatapi.GetMySelf() 22 | fmt.Println(myself, "自己的信息") 23 | if myself.UserName != "" { 24 | fmt.Println("用户名:", myself.UserName) 25 | fmt.Println("昵称:", myself.NickName) 26 | fmt.Println("头像:", myself.HeadImgUrl) 27 | fmt.Println("性别:", myself.Sex) 28 | fmt.Println("签名:", myself.Signature) 29 | } 30 | chatroomcount, chatroomlist := wechatapi.GetChatrooms() 31 | fmt.Println("群聊数量:", chatroomcount) 32 | // 解析群聊信息 33 | jsonparser.ArrayEach(chatroomlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 34 | if err != nil { 35 | return 36 | } 37 | chatroom, _ := jsonparser.GetString(value, "NickName") 38 | username, _ := jsonparser.GetString(value, "UserName") 39 | if chatroom == "你的群聊名" { 40 | fmt.Println(chatroom) 41 | fmt.Println(username) 42 | receiveMessages(myself.NickName, username) 43 | return 44 | } 45 | 46 | }) 47 | 48 | // 退出登录 49 | //wechatapi.Logout() 50 | } 51 | 52 | // 接收消息的函数 53 | func receiveMessages(username string, target string) { 54 | for { 55 | msg := wechatapi.GetMsg() 56 | // 格式化时间 57 | tm := time.Unix(msg.Createtime, 0) 58 | msgtime := tm.Format("01-02 15:04:05") 59 | // 返回1101说明退出登录 60 | if msg.Srctype == 1101 { 61 | fmt.Println("[!] 登录失效,请重新登录!") 62 | break 63 | } 64 | // 群消息 65 | if msg.Srctype == 1 { 66 | //if msg.Msgtype == 1 { 67 | // fmt.Println("[+]", msgtime, "收到群[", msg.Srcname, "]成员[", msg.Usernick, "]的文本消息:", msg.Msg) 68 | //} 69 | //if msg.Msgtype == 3 { 70 | // fmt.Println("[+]", msgtime, "收到群[", msg.Srcname, "]成员[", msg.Usernick, "]的图片消息:", msg.Msg) 71 | //} 72 | //if msg.Msgtype == 47 { 73 | // fmt.Println("[+]", msgtime, "收到群[", msg.Srcname, "]成员[", msg.Usernick, "]的表情包消息:", msg.Msg) 74 | //} 75 | //if msg.Msgtype == 49 { 76 | // fmt.Println("[+]", msgtime, "收到群[", msg.Srcname, "]成员[", msg.Usernick, "]转发的公众号消息:", msg.Msg) 77 | //} 78 | //if msg.Msgtype == 10000 { 79 | // fmt.Println("[+]", msgtime, "收到群[", msg.Srcname, "]成员[", msg.Usernick, "]的拍一拍消息:", msg.Msg) 80 | //} 81 | if msg.Srcname == "你的群聊名" { 82 | msg.Msg = strings.TrimPrefix(msg.Msg, "
") 83 | //匹配msg.Msg最开始的几个字符是否为@username 84 | myname := "@" + username 85 | if len(msg.Msg) < len(myname) { 86 | continue 87 | } 88 | fmt.Println("@" + username) 89 | fmt.Println(msg.Msg[:len(myname)]) 90 | fmt.Println(("@" + username) == msg.Msg[:len(myname)]) 91 | if strings.HasPrefix(msg.Msg, "@"+username) { 92 | fmt.Println("[+]", msgtime, "收到群[", msg.Srcname, "]成员[", msg.Usernick, "]的@消息:", msg.Msg) 93 | //将msg.Msg去掉最开始的@username 94 | msg.Msg = strings.TrimPrefix(msg.Msg, "@"+username+" ") 95 | //将msg.Msg去掉最开始的空格 96 | msg.Msg = strings.TrimPrefix(msg.Msg, " ") 97 | sendMessages(target, "@我干嘛") 98 | fmt.Println(msg.Msg) 99 | } else { 100 | fmt.Println(msg.Msg) 101 | 102 | } 103 | } 104 | } 105 | // 公众号消息 106 | if msg.Srctype == 2 { 107 | fmt.Println("[+]", msgtime, "收到公众号[", msg.Srcname, "]的消息:", msg.Msg) 108 | } 109 | //联系人消息 110 | if msg.Srctype == 3 { 111 | fmt.Println("[+]", msgtime, "收到联系人[", msg.Usernick, "]的消息:", msg.Msg) 112 | if strings.Contains(msg.Msg, "加群") || strings.Contains(msg.Msg, "进群") || strings.Contains(msg.Msg, "入群") || strings.Contains(msg.Msg, "社群") { 113 | err := wechatapi.JoinChatroom(msg.Fromuser, "Pentest技术交流") 114 | if err != nil { 115 | fmt.Println("[!] 邀请加入群聊错误") 116 | } 117 | _ = wechatapi.SendMsg("期待您的加入![握手]", msg.Fromuser) 118 | } 119 | } 120 | } 121 | } 122 | 123 | // 发送消息的线程 124 | func sendMessages(username string, message string) { 125 | // 示例:设定要发送的目标用户和消息内容 126 | targetUser := username // 替换为实际的用户名或用户ID 127 | // 发送消息 128 | err := wechatapi.SendMsg(message, targetUser) 129 | if err != nil { 130 | fmt.Println("[!] 发送消息错误:", err) 131 | } else { 132 | fmt.Println("[+] 消息已发送给", targetUser, "内容:", message) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import( 4 | "fmt" 5 | "github.com/NHPT/wechatapi" 6 | "github.com/buger/jsonparser" 7 | "time" 8 | "strings" 9 | ) 10 | func main() { 11 | // 登录 12 | err := wechatapi.Login() 13 | if err != nil { 14 | fmt.Println(err) 15 | return 16 | } 17 | // 获取自己的信息 18 | myself := wechatapi.GetMySelf() 19 | if myself.UserName != "" { 20 | fmt.Println("用户名:",myself.UserName) 21 | fmt.Println("昵称:",myself.NickName) 22 | fmt.Println("头像:",myself.HeadImgUrl) 23 | fmt.Println("性别:",myself.Sex) 24 | fmt.Println("签名:",myself.Signature) 25 | } 26 | // 获取所有联系人 27 | contactcount, contactlist := wechatapi.GetContactlist() 28 | fmt.Println("联系人数量:",contactcount) 29 | fmt.Println("联系人信息:",string(contactlist)) 30 | chatroomcount, chatroomlist := wechatapi.GetChatrooms() 31 | fmt.Println("群聊数量:",chatroomcount) 32 | // 解析群聊信息 33 | jsonparser.ArrayEach(chatroomlist,func(value []byte, dataType jsonparser.ValueType, offset int, err error){ 34 | if err != nil { 35 | return 36 | } 37 | chatroom,_ := jsonparser.GetString(value,"NickName") 38 | // 输出群昵称 39 | fmt.Println(chatroom) 40 | }) 41 | // 获取所有公众号 42 | offc, offclist := wechatapi.GetOfficiallist() 43 | fmt.Println("公众号数量:",offc) 44 | fmt.Println("公众号信息:",string(offclist)) 45 | // 根据备注获取联系人信息 46 | cb := wechatapi.GetContactByRemarkName("团团") 47 | if len(cb) > 0 { 48 | fmt.Println("联系人信息:",string(cb)) 49 | uname, _ := jsonparser.GetString(cb,"UserName") 50 | fmt.Println(uname) 51 | } 52 | // 根据备注获取联系人信息,不存在则返回空 53 | other := wechatapi.GetContactByRemarkName("zh") 54 | if len(other) > 0 { 55 | fmt.Println("联系人信息:",string(other)) 56 | } 57 | // 根据昵称获取联系人信息 58 | nc := wechatapi.GetContactByNickName("张三") 59 | if len(nc) > 0 { 60 | fmt.Println("联系人信息:",string(nc)) 61 | } 62 | // 获取北京的联系人 63 | ct,ctlist := wechatapi.GetContacts("北京","","") 64 | fmt.Println("北京联系人数量:",ct) 65 | fmt.Println(string(ctlist)) 66 | // 获取西安的男性联系人 67 | ct2,_ := wechatapi.GetContacts("","西安","1") 68 | fmt.Println("西安的男性数量:",ct2) 69 | // 获取所有男性联系人 70 | ct3,_ := wechatapi.GetContacts("","","1") 71 | fmt.Println("男性联系人数量:",ct3) 72 | // 获取所有女性联系人 73 | ct4,_ := wechatapi.GetContacts("","","2") 74 | fmt.Println("女性联系人数量:",ct4) 75 | // 接收消息 76 | for { 77 | msg := wechatapi.GetMsg() 78 | // 格式化时间 79 | tm := time.Unix(msg.Createtime, 0) 80 | msgtime := tm.Format("01-02 15:04:05") 81 | // 返回1101说明退出登录 82 | if msg.Srctype == 1101 { 83 | fmt.Println("[!] 登录失效,请重新登录!") 84 | break 85 | } 86 | // 群消息 87 | if msg.Srctype == 1 { 88 | // fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的消息:",msg.Msg) 89 | // 细化消息类型 90 | if msg.Msgtype == 1 { 91 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的文本消息:",msg.Msg) 92 | } 93 | if msg.Msgtype == 3 { 94 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的图片消息:",msg.Msg) 95 | } 96 | if msg.Msgtype == 47 { 97 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的表情包消息:",msg.Msg) 98 | } 99 | if msg.Msgtype == 49 { 100 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]转发的公众号消息:",msg.Msg) 101 | } 102 | if msg.Msgtype == 10000 { 103 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的拍一拍消息:",msg.Msg) 104 | } 105 | } 106 | // 公众号消息 107 | if msg.Srctype == 2 { 108 | fmt.Println("[+]",msgtime,"收到公众号[",msg.Srcname,"]的消息:",msg.Msg) 109 | } 110 | //联系人消息 111 | if msg.Srctype == 3 { 112 | fmt.Println("[+]",msgtime,"收到联系人[",msg.Usernick,"]的消息:",msg.Msg) 113 | // 自动邀请进群 114 | if strings.Contains(msg.Msg,"加群") || strings.Contains(msg.Msg,"进群") || strings.Contains(msg.Msg,"入群") || strings.Contains(msg.Msg,"社群"){ 115 | err := wechatapi.JoinChatroom(msg.Fromuser,"Pentest技术交流") 116 | if err!= nil { 117 | fmt.Println("[!] 邀请加入群聊错误") 118 | } 119 | _ = wechatapi.SendMsg("期待您的加入![握手]",msg.Fromuser) 120 | } 121 | } 122 | } 123 | // 退出登录 124 | //wechatapi.Logout() 125 | } 126 | -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | ## 目录 2 | 3 | - [结构体](https://github.com/NHPT/wechatapi/blob/main/doc.md#%E7%BB%93%E6%9E%84%E4%BD%93) 4 | - [全局变量](https://github.com/NHPT/wechatapi/blob/main/doc.md#%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F) 5 | - [函数](https://github.com/NHPT/wechatapi/blob/main/doc.md#%E5%87%BD%E6%95%B0) 6 | - [无需登录](https://github.com/NHPT/wechatapi/blob/main/doc.md#%E6%97%A0%E9%9C%80%E7%99%BB%E5%BD%95) 7 | - [PrintQRCode](https://github.com/NHPT/wechatapi/blob/main/doc.md#printqrcodeinfo-string-error) 8 | - [Login](https://github.com/NHPT/wechatapi/blob/main/doc.md#login-error) 9 | - [需要登录](https://github.com/NHPT/wechatapi/blob/main/doc.md#%E9%9C%80%E8%A6%81%E7%99%BB%E5%BD%95) 10 | - [GetMySelf](https://github.com/NHPT/wechatapi/blob/main/doc.md#getmyself-myself) 11 | - [GetChatrooms](https://github.com/NHPT/wechatapi/blob/main/doc.md#getchatrooms-int64-byte) 12 | - [GetContactlist](https://github.com/NHPT/wechatapi/blob/main/doc.md#getcontactlist-int64-byte) 13 | - [GetOfficiallist](https://github.com/NHPT/wechatapi/blob/main/doc.md#getofficiallist-int64-byte) 14 | - [GetContactByRemarkName](https://github.com/NHPT/wechatapi/blob/main/doc.md#getcontactbyremarknameremarkname-string-byte) 15 | - [GetContactByNickName](https://github.com/NHPT/wechatapi/blob/main/doc.md#getcontactbynicknamenickname-string-byte) 16 | - [GetContacts](https://github.com/NHPT/wechatapi/blob/main/doc.md#getcontactsargs-string-int64-byte) 17 | - [GetMsg](https://github.com/NHPT/wechatapi/blob/main/doc.md#getmsg-msgdata) 18 | - [SendMsg](https://github.com/NHPT/wechatapi/blob/main/doc.md#sendmsgmsg-string-tousername-string-error) 19 | - [JoinChatroom](https://github.com/NHPT/wechatapi/blob/main/doc.md#joinchatroominviteuser-string-chatroom-string-error) 20 | - [RmChatroom](https://github.com/NHPT/wechatapi/blob/main/doc.md#rmchatroomuser-string-chatroom-string-error) 21 | - [Logout](https://github.com/NHPT/wechatapi/blob/main/doc.md#logout) 22 | - [错误代码](https://github.com/NHPT/wechatapi/blob/main/doc.md#%E9%94%99%E8%AF%AF%E4%BB%A3%E7%A0%81) 23 | 24 | wechatapi是基于Web微信开发的适用于个人微信的API接口,该API接口中定义了一些结构体、全局变量和函数。 25 | 26 | ## 结构体 27 | 28 | wechatapi定义了3个结构体:`WechatData`、`MySelf`和`MsgData`结构体。 29 | 30 | `WechatData`结构体用来存放函数需要使用的数据,具体包含以下字段: 31 | 32 | - `Skey` 33 | - `Sid` 34 | - `Uin` 35 | - `PassTicket` 36 | - `Synckey` 37 | - `DeviceID` 38 | - `MsgID` 39 | - `Webwx_data_ticket` 40 | 41 | `MySelf`结构体用来存放自己的信息,具体包含以下字段: 42 | 43 | - `UserName`:用户名 44 | - `NickName`:昵称 45 | - `HeadImgUrl`:头像地址 46 | - `Sex`:性别 47 | - `Signature`:签名 48 | 49 | `MsgData`结构体用来存放消息,具体包含以下字段: 50 | 51 | - `Srctype`:消息来源 52 | - 0:表示无消息 53 | - 1:表示群消息 54 | - 2:表示公众号推送消息 55 | - 3:表示联系人消息 56 | - 1101:表示登录过期。 57 | - `Srcname`:来源名称 58 | - `Srctype=1`时表示群名称 59 | - `Srctype=2`时表示公众号名称 60 | - `Srctype=3`时为空。 61 | - `Msgtype`:消息类型 62 | - 1:表示文本消息 63 | - 3:表示图片消息 64 | - 34:表示语音消息 65 | - 43:表示视频消息 66 | - 47:表示表情包消息 67 | - 49:表示文件消息、视频号消息、转发的公众号消息和其它消息 68 | - 51:表示空消息 69 | - 10000:表示拍一拍消息 70 | - `Msg`:消息内容 71 | - `Fromuser`:发送者UserName 72 | - `Usernick`:发送者NickName 73 | - `Createtime`:创建时间,即发送时间 74 | 75 | ## 全局变量 76 | 77 | - `webwx`:`WechatData`结构体的实例。登录成功后存储后续函数使用的参数数据。 78 | - `myself` :`MySelf`结构体的实例。登录成功后存储自己的信息。 79 | - `chatrooms`:`[]byte`类型,登录成功后存储获取到的所有群聊的名称、用户名和成员数量,即: 80 | - `NickName` 81 | - `UserName` 82 | - `MemberCount` 83 | - `contactlist`:`[]byte`类型,登录成功后存储所有联系人信息,包括用户名、昵称、性别、城市等: 84 | - `UserName` 85 | - `NickName` 86 | - `HeadImgUrl` 87 | - `RemarkName` 88 | - `Sex` 89 | - `Signature` 90 | - `Province` 91 | - `City` 92 | - `SnsFlag` 93 | - `officiallist`:`[]byte`类型,登录成功后存储所有公众号信息,包括用户名、昵称、城市等: 94 | - `UserName` 95 | - `NickName` 96 | - `HeadImgUrl` 97 | - `Signature` 98 | - `Province` 99 | - `City` 100 | - `SnsFlag` 101 | 102 | ## 函数 103 | 104 | ### 无需登录 105 | 106 | #### PrintQRCode(info string) error 107 | 108 | 直接在终端输出二维码,依赖`github.com/skip2/go-qrcode`库。 109 | 110 | - 参数`info`为要在终端输出的二维码对应的字符串 111 | - 返回错误信息,如果发生错误则返回错误信息,否则返回`nil` 112 | 113 | 使用示例: 114 | 115 | ```Go 116 | package main 117 | import( 118 | "github.com/NHPT/wechatapi" 119 | ) 120 | func main() { 121 | wechatapi.PrintQRCode("Hello wechatapi") 122 | } 123 | ``` 124 | 125 | #### Login() error 126 | 127 | 函数获取二维码UUID,输出登录二维码,并检查是否登录成功,如果登录成功则解析返回数据到`webwx`结构体实例中,以备后续函数使用,否则返回错误信息,如果二维码过期则重新输出新的二维码。函数初始化了`webwx`变量的一部分字段。 128 | 129 | - 无参数 130 | - 返回错误信息 131 | 132 | 使用示例: 133 | 134 | ```Go 135 | package main 136 | import( 137 | "github.com/NHPT/wechatapi" 138 | "fmt" 139 | ) 140 | func main() { 141 | err := wechatapi.Login() 142 | if err != nil { 143 | fmt.Println(err.Error()) 144 | } 145 | fmt.Println("Login success.") 146 | } 147 | ``` 148 | 149 | ### 需要登录 150 | 151 | #### GetMySelf() MySelf 152 | 153 | 获取自己的信息,包括用户名、昵称、签名等信息。 154 | 155 | - 无参数 156 | - 返回`MySelf`结构体 157 | 158 | 使用示例: 159 | 160 | ```Go 161 | package main 162 | import( 163 | "github.com/NHPT/wechatapi" 164 | "fmt" 165 | ) 166 | func main() { 167 | err := wechatapi.Login() 168 | if err != nil { 169 | fmt.Println(err.Error()) 170 | } 171 | myself := wechatapi.GetMySelf() 172 | if myself.UserName != "" { 173 | fmt.Println("用户名:",myself.UserName) 174 | fmt.Println("昵称:",myself.NickName) 175 | fmt.Println("头像:",myself.HeadImgUrl) 176 | fmt.Println("性别:",myself.Sex) 177 | fmt.Println("签名:",myself.Signature) 178 | } 179 | } 180 | ``` 181 | 182 | #### GetChatrooms() (int64, []byte) 183 | 184 | 获取所有群聊信息。 185 | 186 | - 无参数 187 | - 返回所有群聊数量 188 | - 返回所有群聊信息的json数组,包括群聊名称、群聊UserName和群成员数量 189 | 190 | 使用示例: 191 | 192 | ```Go 193 | package main 194 | import( 195 | "github.com/NHPT/wechatapi" 196 | "github.com/buger/jsonparser" 197 | "fmt" 198 | ) 199 | func main() { 200 | err := wechatapi.Login() 201 | if err != nil { 202 | fmt.Println(err.Error()) 203 | } 204 | chatroomcount, chatroomlist := wechatapi.GetChatrooms() 205 | fmt.Println("群聊数量:",chatroomcount) 206 | // 解析群聊信息 207 | jsonparser.ArrayEach(chatroomlist,func(value []byte, dataType jsonparser.ValueType, offset int, err error){ 208 | if err != nil { 209 | return 210 | } 211 | chatroom,_ := jsonparser.GetString(value,"NickName") 212 | // 输出群昵称 213 | fmt.Println(chatroom) 214 | }) 215 | } 216 | ``` 217 | 218 | #### GetContactlist() (int64, []byte) 219 | 220 | 获取所有联系人信息。 221 | 222 | - 无参数 223 | - 返回所有联系人数量 224 | - 返回所有联系人信息的json数组,包含昵称、用户名、性别、城市等 225 | 226 | 使用示例: 227 | 228 | ```Go 229 | package main 230 | import( 231 | "github.com/NHPT/wechatapi" 232 | "fmt" 233 | ) 234 | func main() { 235 | err := wechatapi.Login() 236 | if err != nil { 237 | fmt.Println(err.Error()) 238 | } 239 | contactcount, contactlist := wechatapi.GetContactlist() 240 | fmt.Println("联系人数量:",contactcount) 241 | fmt.Println("联系人信息:",string(contactlist)) 242 | } 243 | ``` 244 | 245 | #### GetOfficiallist() (int64, []byte) 246 | 247 | 获取所有公众号信息。 248 | 249 | - 无参数 250 | - 返回所有公众号数量 251 | - 返回所有公众号信息的json数组 252 | 253 | 使用示例: 254 | 255 | ```Go 256 | package main 257 | import( 258 | "github.com/NHPT/wechatapi" 259 | "fmt" 260 | ) 261 | func main() { 262 | err := wechatapi.Login() 263 | if err != nil { 264 | fmt.Println(err.Error()) 265 | } 266 | offc, offclist := wechatapi.GetOfficiallist() 267 | fmt.Println("公众号数量:",offc) 268 | fmt.Println("公众号信息:",string(offclist)) 269 | } 270 | ``` 271 | 272 | #### GetContactByRemarkName(remarkname string) []byte 273 | 274 | 通过联系人备注获取联系人信息。 275 | 276 | - 参数`remarkname` 为联系人备注 277 | - 返回联系人信息的json数据 278 | 279 | 使用示例: 280 | 281 | ```Go 282 | package main 283 | import( 284 | "github.com/NHPT/wechatapi" 285 | "fmt" 286 | "github.com/buger/jsonparser" 287 | ) 288 | func main() { 289 | err := wechatapi.Login() 290 | if err != nil { 291 | fmt.Println(err.Error()) 292 | } 293 | // 根据备注获取联系人信息 294 | cb := wechatapi.GetContactByRemarkName("团团") 295 | if len(cb) > 0 { 296 | fmt.Println("联系人信息:",string(cb)) 297 | uname, _ := jsonparser.GetString(cb,"UserName") 298 | fmt.Println(uname) 299 | } 300 | // 根据备注获取联系人信息,不存在则返回空 301 | other := wechatapi.GetContactByRemarkName("zh") 302 | if len(other) > 0 { 303 | fmt.Println("联系人信息:",string(other)) 304 | } 305 | } 306 | ``` 307 | 308 | #### GetContactByNickName(nickname string) []byte 309 | 310 | 通过联系人昵称获取联系人信息。 311 | 312 | - 参数`nickname`为联系人昵称 313 | - 返回联系人信息的json数据 314 | 315 | 使用示例: 316 | 317 | ```Go 318 | package main 319 | import( 320 | "github.com/NHPT/wechatapi" 321 | "fmt" 322 | ) 323 | func main() { 324 | err := wechatapi.Login() 325 | if err != nil { 326 | fmt.Println(err.Error()) 327 | } 328 | nc := wechatapi.GetContactByNickName("张三") 329 | if len(nc) > 0 { 330 | fmt.Println("联系人信息:",string(nc)) 331 | } 332 | } 333 | ``` 334 | 335 | #### GetContacts(args ...string) (int64, []byte) 336 | 337 | 根据条件获取指定省份、城市、性别的联系人。 338 | 339 | - 第一个参数为省份 340 | - 第二个参数为城市 341 | - 第三个参数为性别 342 | - 返回联系人信息的json数组 343 | 344 | 使用示例: 345 | 346 | ```Go 347 | package main 348 | import( 349 | "github.com/NHPT/wechatapi" 350 | "fmt" 351 | ) 352 | func main() { 353 | err := wechatapi.Login() 354 | if err != nil { 355 | fmt.Println(err.Error()) 356 | } 357 | // 获取北京的联系人 358 | ct,ctlist := wechatapi.GetContacts("北京","","") 359 | fmt.Println("北京联系人数量:",ct) 360 | fmt.Println(string(ctlist)) 361 | // 获取西安的男性联系人 362 | ct2,_ := wechatapi.GetContacts("","西安","1") 363 | fmt.Println("西安的男性数量:",ct2) 364 | // 获取所有男性联系人 365 | ct3,_ := wechatapi.GetContacts("","","1") 366 | fmt.Println("男性联系人数量:",ct3) 367 | // 获取所有女性联系人 368 | ct4,_ := wechatapi.GetContacts("","","2") 369 | fmt.Println("女性联系人数量:",ct4) 370 | } 371 | ``` 372 | 373 | #### GetMsg() MsgData 374 | 375 | 接收联系人、群和公众号等消息。 376 | 377 | - 无参数 378 | - 返回`MsgData`结构体 379 | 380 | 使用示例: 381 | 382 | ```Go 383 | package main 384 | import( 385 | "github.com/NHPT/wechatapi" 386 | "time" 387 | "fmt" 388 | ) 389 | func main() { 390 | err := wechatapi.Login() 391 | if err != nil { 392 | fmt.Println(err.Error()) 393 | } 394 | for { 395 | msg := wechatapi.GetMsg() 396 | // 格式化时间 397 | tm := time.Unix(msg.Createtime, 0) 398 | msgtime := tm.Format("01-02 15:04:05") 399 | // 返回1101说明退出登录 400 | if msg.Srctype == 1101 { 401 | fmt.Println("[!] 登录失效,请重新登录!") 402 | break 403 | } 404 | // 群消息 405 | if msg.Srctype == 1 { 406 | // fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的消息:",msg.Msg) 407 | // 细化消息类型 408 | if msg.Msgtype == 1 { 409 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的文本消息:",msg.Msg) 410 | } 411 | if msg.Msgtype == 3 { 412 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的图片消息:",msg.Msg) 413 | } 414 | if msg.Msgtype == 47 { 415 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的表情包消息:",msg.Msg) 416 | } 417 | if msg.Msgtype == 49 { 418 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]转发的公众号消息:",msg.Msg) 419 | } 420 | if msg.Msgtype == 10000 { 421 | fmt.Println("[+]",msgtime,"收到群[",msg.Srcname,"]成员[",msg.Usernick,"]的拍一拍消息:",msg.Msg) 422 | } 423 | } 424 | // 公众号消息 425 | if msg.Srctype == 2 { 426 | fmt.Println("[+]",msgtime,"收到公众号[",msg.Srcname,"]的消息:",msg.Msg) 427 | } 428 | //联系人消息 429 | if msg.Srctype == 3 { 430 | fmt.Println("[+]",msgtime,"收到联系人[",msg.Usernick,"]的消息:",msg.Msg) 431 | } 432 | } 433 | } 434 | 435 | ``` 436 | 437 | #### SendMsg(msg string, toUserName string) error 438 | 439 | 发送消息。 440 | 441 | - 参数`msg`为`string`类型消息内容 442 | - 参数`toUserName `为`string`类型接收者UserName 443 | - 返回错误信息 444 | 445 | 使用示例: 446 | 447 | ```Go 448 | package main 449 | import( 450 | "github.com/NHPT/wechatapi" 451 | "fmt" 452 | "github.com/buger/jsonparser" 453 | ) 454 | func main() { 455 | err := wechatapi.Login() 456 | if err != nil { 457 | fmt.Println(err.Error()) 458 | } 459 | // 根据备注获取联系人信息 460 | cb := wechatapi.GetContactByRemarkName("团团") 461 | if len(cb) > 0 { 462 | fmt.Println("联系人信息:",string(cb)) 463 | uname, _ := jsonparser.GetString(cb,"UserName") 464 | // 发送消息 465 | _ = wechatapi.SendMsg("你好我有一个帽衫",uname) 466 | } 467 | } 468 | ``` 469 | 470 | #### JoinChatroom(inviteuser string, chatroom string) error 471 | 472 | 邀请联系人加入群聊。 473 | 474 | - 参数`inviteuser `为联系人UserName 475 | - 参数`chatroom `为群聊昵称 476 | - 返回错误信息 477 | 478 | 使用示例: 479 | 480 | ```Go 481 | package main 482 | import( 483 | "github.com/NHPT/wechatapi" 484 | "fmt" 485 | "github.com/buger/jsonparser" 486 | ) 487 | func main() { 488 | err := wechatapi.Login() 489 | if err != nil { 490 | fmt.Println(err.Error()) 491 | } 492 | // 根据备注获取联系人信息 493 | cb := wechatapi.GetContactByRemarkName("团团") 494 | if len(cb) > 0 { 495 | fmt.Println("联系人信息:",string(cb)) 496 | uname, _ := jsonparser.GetString(cb,"UserName") 497 | err := wechatapi.JoinChatroom(uname,"Pentest技术交流") 498 | if err!= nil { 499 | fmt.Println("[!] 邀请加入群聊错误") 500 | } 501 | } 502 | } 503 | 504 | ``` 505 | 506 | #### RmChatroom(user string, chatroom string) error 507 | 508 | 移除群聊中的某用户。 509 | 510 | - 参数`user`为待移除的用户 511 | - 参数`chatroom`为群聊昵称 512 | - 返回错误信息 513 | 514 | 使用示例: 515 | 516 | ```Go 517 | package main 518 | import( 519 | "github.com/NHPT/wechatapi" 520 | "fmt" 521 | "github.com/buger/jsonparser" 522 | ) 523 | func main() { 524 | err := wechatapi.Login() 525 | if err != nil { 526 | fmt.Println(err.Error()) 527 | } 528 | // 根据备注获取联系人信息 529 | cb := wechatapi.GetContactByRemarkName("团团") 530 | if len(cb) > 0 { 531 | fmt.Println("联系人信息:",string(cb)) 532 | uname, _ := jsonparser.GetString(cb,"UserName") 533 | err := wechatapi.RmChatroom(uname,"Pentest技术交流") 534 | if err!= nil { 535 | fmt.Println("[!] 移出群聊错误") 536 | } 537 | } 538 | } 539 | 540 | ``` 541 | 542 | #### Logout() 543 | 544 | 退出微信。 545 | 546 | - 无参数 547 | - 无返回 548 | 549 | 使用示例: 550 | 551 | ```Go 552 | package main 553 | import( 554 | "github.com/NHPT/wechatapi" 555 | "fmt" 556 | ) 557 | func main() { 558 | err := wechatapi.Login() 559 | if err != nil { 560 | fmt.Println(err.Error()) 561 | } 562 | wechatapi.Logout() 563 | fmt.Println("Wechat logged out.") 564 | } 565 | ``` 566 | 567 | ## 错误代码 568 | 569 | |**错误代码**|**说明**| 570 | |-|-| 571 | |E00001|网络错误| 572 | |E00002|读取响应错误| 573 | |E00003|登录二维码过期| 574 | |E00004|响应不存在window.redirect_uri| 575 | |E00005|响应不存在window.code| 576 | |E00006|微信账号未实名认证或未添加支付方式| 577 | |E00007|Json数据解析错误| 578 | |E00008|缺少必须参数| 579 | |E00009|Json序列化错误| 580 | |E00010|消息发送失败| 581 | |E00011|群聊不存在| 582 | -------------------------------------------------------------------------------- /wechatapi.go: -------------------------------------------------------------------------------- 1 | package wechatapi 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "html" 8 | "regexp" 9 | "time" 10 | "github.com/skip2/go-qrcode" 11 | "github.com/buger/jsonparser" 12 | "strings" 13 | "encoding/xml" 14 | "encoding/json" 15 | "math/rand" 16 | "strconv" 17 | ) 18 | 19 | type WechatData struct { 20 | Skey string `xml:"skey"` 21 | Sid string `xml:"wxsid"` 22 | Uin string `xml:"wxuin"` 23 | PassTicket string `xml:"pass_ticket"` 24 | Synckey []byte 25 | DeviceID string 26 | MsgID string 27 | Webwx_data_ticket string 28 | } 29 | 30 | type MySelf struct { 31 | UserName string `json:"UserName"` 32 | NickName string `json:"NickName"` 33 | HeadImgUrl string `json:"HeadImgUrl"` 34 | Sex int64 `json:"Sex"` 35 | Signature string `json:"Signature"` 36 | } 37 | 38 | type MsgData struct { 39 | Srctype int64 `json:"Srctype"` 40 | Srcname string `json:"Srcname"` 41 | Msgtype int64 `json:"Msgtype"` 42 | Msg string `json:"Msg"` 43 | Fromuser string `json:"Fromuser"` 44 | Usernick string `json:"Usernick"` 45 | Createtime int64 `json:"Createtime"` 46 | } 47 | 48 | var ( 49 | webwx WechatData 50 | myself MySelf 51 | chatrooms []byte 52 | contactlist []byte 53 | officiallist []byte 54 | wxurl = "https://wx.qq.com" 55 | webpushurl = "https://webpush.wx.qq.com" 56 | extspam = "Go8FCIkFEokFCggwMDAwMDAwMRAGGvAESySibk50w5Wb3uTl2c2h64jVVrV7"+ 57 | "gNs06GFlWplHQbY/5FfiO++1yH4ykCyNPWKXmco+wfQzK5R98D3so7rJ5LmGFvBLjGceleySrc3SOf2Pc1gVeh"+ 58 | "zJgODeS0lDL3/I/0S2SSE98YgKleq6Uqx6ndTy9yaL9qFxJL7eiA/R3SEfTaW1SBoSITIu+EEkXff+Pv8NHOk7"+ 59 | "N57rcGk1w0ZzRrQDkXTOXFN2iHYIzAAZPIOY45Lsh+A4slpgnDiaOvRtlQYCt97nmPLuTipOJ8Qc5pM7ZsOsAP"+ 60 | "PrCQL7nK0I7aPrFDF0q4ziUUKettzW8MrAaiVfmbD1/VkmLNVqqZVvBCtRblXb5FHmtS8FxnqCzYP4WFvz3T0T"+ 61 | "crOqwLX1M/DQvcHaGGw0B0y4bZMs7lVScGBFxMj3vbFi2SRKbKhaitxHfYHAOAa0X7/MSS0RNAjdwoyGHeOepX"+ 62 | "OKY+h3iHeqCvgOH6LOifdHf/1aaZNwSkGotYnYScW8Yx63LnSwba7+hESrtPa/huRmB9KWvMCKbDThL/nne14h"+ 63 | "nL277EDCSocPu3rOSYjuB9gKSOdVmWsj9Dxb/iZIe+S6AiG29Esm+/eUacSba0k8wn5HhHg9d4tIcixrxveflc"+ 64 | "8vi2/wNQGVFNsGO6tB5WF0xf/plngOvQ1/ivGV/C1Qpdhzznh0ExAVJ6dwzNg7qIEBaw+BzTJTUuRcPk92Sn6Q"+ 65 | "Dn2Pu3mpONaEumacjW4w6ipPnPw+g2TfywJjeEcpSZaP4Q3YV5HG8D6UjWA4GSkBKculWpdCMadx0usMomsSS/"+ 66 | "74QgpYqcPkmamB4nVv1JxczYITIqItIKjD35IGKAUwAA==" 67 | ) 68 | 69 | /** 获取自己的信息 70 | * @return MySelf 返回自己的信息 71 | */ 72 | func GetMySelf() MySelf { 73 | return myself 74 | } 75 | 76 | /** 获取所有群聊信息 77 | * @return int64 群聊数量 78 | * @return []string 返回所有群聊 79 | */ 80 | func GetChatrooms() (int64, []byte) { 81 | var count int64 = 0 82 | jsonparser.ArrayEach(chatrooms, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 83 | if err != nil { 84 | return 85 | } 86 | count++ 87 | }) 88 | return count, chatrooms 89 | } 90 | 91 | /** 根据群昵称获取群UserName 92 | * @param string 群昵称 93 | * @return string 群UserName 94 | */ 95 | func getChatroomName(nickname string) string { 96 | name := "" 97 | jsonparser.ArrayEach(chatrooms, func(value []byte, dataType jsonparser.ValueType, offset int, err error){ 98 | if err != nil { 99 | return 100 | } 101 | nick, _ := jsonparser.GetString(value,"NickName") 102 | if nickname == nick { 103 | name, _ = jsonparser.GetString(value,"UserName") 104 | } 105 | }) 106 | return name 107 | } 108 | 109 | /** 获取所有联系人信息 110 | * @return int64 联系人数量 111 | * @return []byte 返回所有联系人 112 | */ 113 | func GetContactlist() (int64, []byte) { 114 | var count int64 = 0 115 | jsonparser.ArrayEach(contactlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 116 | if err != nil { 117 | return 118 | } 119 | count++ 120 | }) 121 | return count, contactlist 122 | } 123 | 124 | /** 获取所有公众号信息 125 | * @return int64 公众号数量 126 | * @return []byte 返回所有公众号 127 | */ 128 | func GetOfficiallist() (int64, []byte) { 129 | var count int64 = 0 130 | jsonparser.ArrayEach(officiallist, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 131 | if err != nil { 132 | return 133 | } 134 | count++ 135 | }) 136 | return count, officiallist 137 | } 138 | 139 | /** 140 | * 直接在终端输出二维码 141 | * 使用github.com/skip2/go-qrcode库 142 | * @param info 要显示的字符串 143 | * @return error 错误信息 144 | */ 145 | func PrintQRCode(info string) error { 146 | // 生成QR码 147 | qr, err := qrcode.New(info, qrcode.Highest) 148 | if err != nil { 149 | return err 150 | } 151 | // 获取 QR 码图像 152 | size := 5 153 | img := qr.Image(size) 154 | 155 | // 遍历图像的每个像素点 156 | bounds := img.Bounds() 157 | for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 158 | for x := bounds.Min.X; x < bounds.Max.X; x++ { 159 | // 获取像素的颜色值 160 | r, g, b, _ := img.At(x, y).RGBA() 161 | _, _, _, alpha := img.At(x, y).RGBA() 162 | // 假设黑色像素为 QR 码的黑色部分,白色或透明像素为白色部分 163 | if alpha == 0xFFFF && (r == 0 && g == 0 && b == 0) { 164 | fmt.Print("██") 165 | } else { 166 | fmt.Print(" ") 167 | } 168 | } 169 | fmt.Println() 170 | } 171 | return nil 172 | } 173 | 174 | /** 175 | * 获取微信登录二维码UUID值,用于后续登录 176 | * @return string 返回UUID 177 | */ 178 | func getQRCodeuuid() string { 179 | // 请求微信登录页面获取响应中的值 180 | resp, err := http.Get(wxurl + "/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage%3Fmod%3Ddesktop&fun=new") 181 | if err != nil { 182 | return "" 183 | } 184 | defer resp.Body.Close() 185 | body, err := ioutil.ReadAll(resp.Body) 186 | if err != nil { 187 | return "" 188 | } 189 | re := regexp.MustCompile(`window\.QRLogin\.uuid\s*=\s*"([^"]+)"`) 190 | uuid := re.FindStringSubmatch(string(body)) 191 | if uuid == nil { 192 | return "" 193 | } 194 | return uuid[1] 195 | } 196 | 197 | /** 198 | * 检查是否登录,如果登录则返回 redirect_uri,否则返回错误信息 199 | * @param uuid 登录二维码UUID 200 | * @return string 返回 redirect_uri 201 | * @return error 错误信息 202 | */ 203 | func checkLogin(uuid string) (string, error) { 204 | p := time.Now().Unix() 205 | r := int(p * 11) 206 | loginURL := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=%s&tip=0&r=%d&_=%d", uuid, r, p) 207 | resp, err := http.Get(loginURL) 208 | if err != nil { 209 | return "", fmt.Errorf("E00001: Please check your network connection.") 210 | } 211 | defer resp.Body.Close() 212 | body, err := ioutil.ReadAll(resp.Body) 213 | if err != nil { 214 | return "", fmt.Errorf("E00002: Failed to read response body.") 215 | } 216 | codeRegex := regexp.MustCompile(`window\.code=(\d+);`) 217 | redirectURIRegex := regexp.MustCompile(`window\.redirect_uri\s*=\s*"([^"]+)";`) 218 | codeMatch := codeRegex.FindStringSubmatch(string(body)) 219 | if codeMatch != nil { 220 | // 二维码过期 221 | if codeMatch[1] == "400" { 222 | return "400", fmt.Errorf("E00003: The QR code has expired.") 223 | } 224 | // 登录成功 225 | if codeMatch[1] == "200" { 226 | redirectURIMatch := redirectURIRegex.FindStringSubmatch(string(body)) 227 | if redirectURIMatch == nil { 228 | return "", fmt.Errorf("E00004: window.redirect_uri not found.") 229 | } 230 | // 返回提取到的 redirect_uri 231 | return redirectURIMatch[1], nil 232 | } 233 | return "", fmt.Errorf("") 234 | }else { 235 | return "", fmt.Errorf("E00005: window.code is not exist") 236 | } 237 | } 238 | 239 | /** 240 | * 输出登录二维码,并检查登录结果,登录成功后初始化数据 241 | * @return error 错误信息 242 | */ 243 | func Login() error { 244 | uuid := getQRCodeuuid() 245 | if uuid == "" { 246 | return fmt.Errorf("[!] Cound not find uuid.") 247 | } else { 248 | fmt.Println("[*] 请扫描二维码登录微信") 249 | fmt.Println("[*] https://api.pwmqr.com/qrcode/create/?url=" + "https://login.weixin.qq.com/l/" + uuid) 250 | fmt.Println("[*] https://api.isoyu.com/qr/?m=1&e=L&p=20&url=" + "https://login.weixin.qq.com/l/" + uuid) 251 | PrintQRCode("https://login.weixin.qq.com/l/" + uuid) 252 | // 轮询检查是否已经扫码登录 253 | for { 254 | redirectURI, err := checkLogin(uuid) 255 | if err != nil { 256 | if redirectURI == "400" { 257 | uuid = getQRCodeuuid() 258 | fmt.Println("[*] 二维码过期,请重新扫描二维码登录微信") 259 | fmt.Println("[*] https://api.pwmqr.com/qrcode/create/?url=" + "https://login.weixin.qq.com/l/" + uuid) 260 | fmt.Println("[*] https://api.isoyu.com/qr/?m=1&e=L&p=20&url=" + "https://login.weixin.qq.com/l/" + uuid) 261 | PrintQRCode("https://login.weixin.qq.com/l/" + uuid) 262 | continue 263 | } 264 | if strings.Contains(err.Error(), "E00001") { 265 | fmt.Println("[!] " + err.Error()) 266 | return err 267 | } 268 | } else { 269 | fmt.Println("[+] 登录成功!") 270 | client := &http.Client{ 271 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 272 | // 返回错误以阻止自动跟随重定向 273 | return http.ErrUseLastResponse 274 | }, 275 | } 276 | req, _ := http.NewRequest("GET",redirectURI + "&fun=new&version=v2&mod=desktop",nil) 277 | //fmt.Println(redirectURI + "&fun=new&version=v2&mod=desktop") 278 | req.Header.Add("Referer","https://wx.qq.com/?target=t") 279 | req.Header.Add("client-version","2.0.0") 280 | req.Header.Add("extspam",extspam) 281 | resp, err := client.Do(req) 282 | if err != nil { 283 | fmt.Println("[!] E00001: Please check your network connection.") 284 | return err 285 | } 286 | defer resp.Body.Close() 287 | body, err := ioutil.ReadAll(resp.Body) 288 | if err != nil { 289 | fmt.Println("[!] E00002: Failed to read response body.") 290 | } 291 | cookies := resp.Header["Set-Cookie"] 292 | webwx_data_ticket := "" 293 | for _, cookie := range cookies { 294 | if strings.HasPrefix(cookie, "webwx_data_ticket="){ 295 | parts := strings.Split(cookie, ";") 296 | for _, part := range parts { 297 | part = strings.TrimSpace(part) 298 | if strings.HasPrefix(part, "webwx_data_ticket="){ 299 | webwx_data_ticket = part[len("webwx_data_ticket="):] 300 | break 301 | } 302 | } 303 | if webwx_data_ticket != "" { 304 | webwx.Webwx_data_ticket = webwx_data_ticket 305 | break 306 | } 307 | } 308 | } 309 | err = xml.Unmarshal([]byte(body), &webwx) 310 | if err != nil { 311 | fmt.Println("[!] E00006: You need real name authentication and WeChat payment activation.") 312 | } 313 | fmt.Println("[*] 正在初始化数据...") 314 | webInit() 315 | fmt.Println("[*] 初始化数据成功!") 316 | return nil 317 | } 318 | time.Sleep(2 * time.Second) 319 | } 320 | } 321 | } 322 | 323 | /** 324 | * 获取webInit数据,初始化数据 325 | */ 326 | func webInit() { 327 | r := time.Now().Unix() 328 | rand.Seed(time.Now().UnixNano()) 329 | const count = 15 330 | var randomString string 331 | for i := 0; i < count; i++ { 332 | randomDigit := rand.Intn(10) 333 | randomString += fmt.Sprint(randomDigit) 334 | } 335 | webwx.DeviceID = "e" + randomString 336 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxinit?r=%d&pass_ticket=%s", r, webwx.PassTicket) 337 | data := fmt.Sprintf(`{"BaseRequest":{"Uin":"%s","Sid":"%s","Skey":"%s","DeviceID":"%s"}}`, webwx.Uin, webwx.Sid, webwx.Skey, webwx.DeviceID) 338 | resp, err := http.Post(url, "application/json;charset=utf-8", strings.NewReader(data)) 339 | if err != nil { 340 | fmt.Println("[!] E00001: Please check your network connection.") 341 | return 342 | } 343 | defer resp.Body.Close() 344 | body, err := ioutil.ReadAll(resp.Body) 345 | if err != nil { 346 | fmt.Println("[!] E00002: Failed to read response body.") 347 | return 348 | } 349 | SyncKey, _, _, err := jsonparser.Get(body,"SyncKey") 350 | if err != nil { 351 | fmt.Println("[!] E00007: %s",err) 352 | return 353 | } 354 | webwx.Synckey = SyncKey 355 | err = setMySelf(body) 356 | if err != nil { 357 | fmt.Println(err) 358 | } 359 | fmt.Println("[*] 正在获取联系人,请稍后...") 360 | err = getAllContact() 361 | if err != nil { 362 | fmt.Println(err) 363 | } 364 | fmt.Println("[*] 获取联系人成功,正在获取微信群,请稍后...") 365 | _ = getStatusNotify() 366 | getChatRoomFromWebwxsync() 367 | fmt.Println("[*] 获取微信群成功") 368 | } 369 | 370 | /** 371 | * 获取自己的信息,并存入myself结构体实例 372 | * @param jsondata []byte 服务器返回的JSON数据 373 | * @return error 错误信息 374 | */ 375 | func setMySelf(jsondata []byte) error { 376 | username, err := jsonparser.GetString(jsondata, "User", "UserName") 377 | if err != nil { 378 | return fmt.Errorf("E00007: %s",err) 379 | } 380 | myself.UserName = username 381 | nickname, err := jsonparser.GetString(jsondata, "User", "NickName") 382 | if err != nil { 383 | return fmt.Errorf("E00007: %s",err) 384 | } 385 | myself.NickName = nickname 386 | headimgurl, err := jsonparser.GetString(jsondata, "User", "HeadImgUrl") 387 | if err != nil { 388 | return fmt.Errorf("E00007: %s",err) 389 | } 390 | myself.HeadImgUrl = headimgurl 391 | if value, err := jsonparser.GetInt(jsondata, "User", "Sex"); err == nil { 392 | myself.Sex = value 393 | } 394 | signature, err := jsonparser.GetString(jsondata, "User", "Signature") 395 | if err != nil { 396 | return fmt.Errorf("E00007: %s",err) 397 | } 398 | myself.Signature = signature 399 | return nil 400 | } 401 | 402 | /** 403 | * 获取微信状态通知 404 | * @return error 错误信息 405 | */ 406 | func getStatusNotify() error { 407 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxstatusnotify?pass_ticket=%s",webwx.PassTicket) 408 | data := fmt.Sprintf(`{"BaseRequest":{"Uin":%s,"Sid":"%s","Skey":"%s","DeviceID":"%s"},"Code":3,"FromUserName":"%s","ToUserName":"%s","ClientMsgId":"%d"}`,webwx.Uin,webwx.Sid,webwx.Skey,webwx.DeviceID,myself.UserName,myself.UserName,time.Now().Unix()) 409 | resp, err := http.Post(url, "application/json;charset=utf-8", strings.NewReader(data)) 410 | if err != nil { 411 | return fmt.Errorf("E00001: Please check your network connection.") 412 | } 413 | defer resp.Body.Close() 414 | body, err := ioutil.ReadAll(resp.Body) 415 | if err != nil { 416 | return fmt.Errorf("E00002: Failed to read response body.") 417 | } 418 | msgID, err := jsonparser.GetString(body,"MsgID") 419 | if err != nil { 420 | return fmt.Errorf("E00007: %s",err) 421 | } 422 | webwx.MsgID = msgID 423 | return nil 424 | } 425 | 426 | /** 427 | * 获取SyncKey 428 | * @param SyncKey []byte Json数据 429 | * @return string 返回格式化后的synckey数据 430 | * @return error 错误信息 431 | */ 432 | func getSyncKey(SyncKey []byte) (string, error) { 433 | SyncKeyList, _, _, err := jsonparser.Get(SyncKey,"List") 434 | if err != nil { 435 | return "", fmt.Errorf("E00007: %s",err) 436 | } 437 | // Initialize an empty string for the result 438 | result := "" 439 | 440 | // Iterate over the array elements 441 | jsonparser.ArrayEach(SyncKeyList, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 442 | if err != nil { 443 | return 444 | } 445 | 446 | // Use jsonparser to get the "Key" and "Val" values 447 | key, _ := jsonparser.GetInt(value, "Key") 448 | val, _ := jsonparser.GetInt(value, "Val") 449 | 450 | // Format the current element as "key_val" and add it to the result string 451 | if len(result) > 0 { 452 | result += "%7C" // Add separator (URL-encoded pipe symbol) 453 | } 454 | result += fmt.Sprintf("%d_%d", key, val) 455 | }) 456 | return result, nil 457 | } 458 | 459 | /** 通过联系人备注名获取联系人信息 460 | * @param remarkname string 联系人备注名 461 | * @return []byte 返回联系人信息 462 | */ 463 | func GetContactByRemarkName(remarkname string) []byte { 464 | var contact []byte 465 | jsonparser.ArrayEach(contactlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 466 | if err != nil { 467 | return 468 | } 469 | remarkName, _ := jsonparser.GetString(value, "RemarkName") 470 | if remarkName == remarkname { 471 | contact = value 472 | } 473 | }) 474 | return contact 475 | } 476 | 477 | /** 通过联系人昵称获取联系人信息 478 | * @param nickname string 联系人昵称 479 | * @return []byte 返回联系人信息 480 | */ 481 | func GetContactByNickName(nickname string) []byte { 482 | var contact []byte 483 | jsonparser.ArrayEach(contactlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 484 | if err != nil { 485 | return 486 | } 487 | nickName, _ := jsonparser.GetString(value, "NickName") 488 | if nickName == nickname { 489 | contact = value 490 | } 491 | }) 492 | return contact 493 | } 494 | 495 | /** 获取指定省份、城市、性别的联系人 496 | * @param args ...string 省份、城市、性别 497 | * @return int64 符合条件的联系人数量 498 | * @return []byte 符合条件的联系人信息 499 | */ 500 | func GetContacts(args ...string) (int64, []byte) { 501 | if len(args) != 3 { 502 | fmt.Println("[!] E00008: 3 parameters are required.") 503 | return 0,[]byte{} 504 | } 505 | var count int64 506 | var contactarray []map[string]interface{} 507 | var contactslist []byte 508 | jsonparser.ArrayEach(contactlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 509 | contact := make(map[string]interface{}) 510 | _ = json.Unmarshal(value, &contact) 511 | if args[0] != "" { 512 | Province, _ := jsonparser.GetString(value, "Province") 513 | if args[0] == Province { 514 | contactarray = append(contactarray,contact) 515 | count ++ 516 | } 517 | } 518 | if args[1] != "" { 519 | City, _ := jsonparser.GetString(value, "City") 520 | if args[1] == City { 521 | contactarray = append(contactarray,contact) 522 | count ++ 523 | } 524 | } 525 | if args[2] != "" { 526 | Sexint, _ := strconv.ParseInt(args[2], 10, 64) 527 | Sex, _ := jsonparser.GetInt(value, "Sex") 528 | if Sexint == Sex { 529 | contactarray = append(contactarray,contact) 530 | count ++ 531 | } 532 | } 533 | 534 | }) 535 | contactslist,_ = json.Marshal(contactarray) 536 | return count, contactslist 537 | } 538 | /** 539 | * 获取所有联系人,包括公众号 540 | * @return error 错误信息 541 | */ 542 | func getAllContact() error { 543 | var Seq, MemberCount int64 = 0,0 544 | var memberArray []map[string]interface{} 545 | for { 546 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket=%s&r=%d&seq=%d&skey=%s",webwx.PassTicket,time.Now().Unix(),Seq,webwx.Skey) 547 | req, err := http.NewRequest("GET",url,nil) 548 | if err != nil { 549 | return err 550 | } 551 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";wxsid="+webwx.Sid) 552 | client := &http.Client{} 553 | resp, err := client.Do(req) 554 | if err != nil { 555 | return fmt.Errorf("E00001: Please check your network connection.") 556 | } 557 | defer resp.Body.Close() 558 | body, err := ioutil.ReadAll(resp.Body) 559 | if err != nil { 560 | return fmt.Errorf("E00002: Failed to read response body.") 561 | } 562 | if value,err := jsonparser.GetInt(body, "Seq"); err == nil { 563 | Seq = value 564 | } 565 | if value,err := jsonparser.GetInt(body, "MemberCount"); err == nil { 566 | MemberCount += value 567 | } 568 | memberLists, _, _, err := jsonparser.Get(body,"MemberList") 569 | if err != nil { 570 | return fmt.Errorf("E00007: %s",err) 571 | } 572 | var temp []map[string]interface{} 573 | err = json.Unmarshal(memberLists, &temp) 574 | if err != nil { 575 | return err 576 | } 577 | memberArray = append(memberArray, temp...) 578 | if Seq == 0 { 579 | break 580 | } 581 | } 582 | memberList, err := json.Marshal(memberArray) 583 | if err != nil { 584 | return fmt.Errorf("E00009: error marshalling merged JSON array: %v", err) 585 | } 586 | // 保留指定字段 587 | var contactarray []map[string]interface{} 588 | var officialarray []map[string]interface{} 589 | jsonparser.ArrayEach(memberList, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 590 | if err != nil { 591 | fmt.Println("Error:", err) 592 | return 593 | } 594 | contact := make(map[string] interface{}) 595 | username, _ := jsonparser.GetString(value, "UserName") 596 | nickname, _ := jsonparser.GetString(value, "NickName") 597 | headimgurl, _ := jsonparser.GetString(value, "HeadImgUrl") 598 | remarkname, _ := jsonparser.GetString(value, "RemarkName") 599 | sex, _ := jsonparser.GetInt(value, "Sex") 600 | signature, _ := jsonparser.GetString(value, "Signature") 601 | province, _ := jsonparser.GetString(value, "Province") 602 | city, _ := jsonparser.GetString(value, "City") 603 | snsflag, _ := jsonparser.GetInt(value, "SnsFlag") 604 | contact["UserName"] = username 605 | contact["NickName"] = nickname 606 | contact["HeadImgUrl"] = headimgurl 607 | contact["RemarkName"] = remarkname 608 | contact["Sex"] = sex 609 | contact["Signature"] = signature 610 | contact["Province"] = province 611 | contact["City"] = city 612 | contact["SnsFlag"] = snsflag 613 | if snsflag == 0 { 614 | officialarray = append(officialarray,contact) 615 | officiallist,_ = json.Marshal(officialarray) 616 | } else { 617 | contactarray = append(contactarray,contact) 618 | contactlist,_ = json.Marshal(contactarray) 619 | } 620 | }) 621 | return nil 622 | } 623 | 624 | 625 | /** 626 | * 批量获取联系人 627 | * @param username ...string 用户名 628 | */ 629 | func getBatchContact(username ...string) []byte { 630 | count := int64(len(username)) 631 | users := make([]map[string]string, count) 632 | for i, user := range username { 633 | users[i] = map[string]string{ 634 | "UserName": user, 635 | "EncryChatRoomId": "", 636 | } 637 | } 638 | userList, err := json.Marshal(users) 639 | if err != nil { 640 | return []byte{} 641 | } 642 | 643 | r := time.Now().Unix() 644 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=%d&pass_ticket=%s",r, webwx.PassTicket) 645 | data := fmt.Sprintf( 646 | `{ 647 | "BaseRequest":{ 648 | "Uin":%s, 649 | "Sid":"%s", 650 | "Skey":"%s", 651 | "DeviceID":"%s" 652 | }, 653 | "Count":%d, 654 | "List":%s 655 | }`, 656 | webwx.Uin,webwx.Sid,webwx.Skey,webwx.DeviceID,count,string(userList)) 657 | req, err := http.NewRequest("POST",url,strings.NewReader(data)) 658 | if err != nil { 659 | return []byte{} 660 | } 661 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";wxsid="+webwx.Sid) 662 | client := &http.Client{} 663 | resp, err := client.Do(req) 664 | if err != nil { 665 | return []byte{} 666 | } 667 | defer resp.Body.Close() 668 | body, err := ioutil.ReadAll(resp.Body) 669 | if err != nil { 670 | return []byte{} 671 | } 672 | contactList, _, _, err := jsonparser.Get(body,"ContactList") 673 | if err != nil { 674 | return []byte{} 675 | } 676 | return contactList 677 | } 678 | 679 | 680 | /** 681 | * 检查消息 682 | * @return int64 返回retcode的值 683 | * @return int64 返回selector的值,为0表示无消息 684 | */ 685 | func syncCheck() (int64, int64) { 686 | r := time.Now().Unix() 687 | synckey, _ := getSyncKey(webwx.Synckey) 688 | url := fmt.Sprintf(webpushurl + "/cgi-bin/mmwebwx-bin/synccheck?r=%d&skey=%s&sid=%s&uin=%s&deviceid=%s&synckey=%s&_=%d",r,webwx.Skey,webwx.Sid,webwx.Uin,webwx.DeviceID,synckey,r) 689 | req, err := http.NewRequest("GET",url,nil) 690 | if err != nil { 691 | return 0, 0 692 | } 693 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";webwx_data_ticket="+webwx.Webwx_data_ticket) 694 | client := http.Client{} 695 | resp, err := client.Do(req) 696 | if err != nil { 697 | return 0, 0 698 | } 699 | defer resp.Body.Close() 700 | body, _ := ioutil.ReadAll(resp.Body) 701 | re1 := regexp.MustCompile(`retcode:"([^"]+)"`) 702 | re2 := regexp.MustCompile(`selector:"([^"]+)"`) 703 | retcode := re1.FindStringSubmatch(string(body)) 704 | if len(retcode) < 2 { 705 | return 0, 0 706 | } 707 | retcodeInt, _ := strconv.ParseInt(retcode[1], 10, 64) 708 | selector := re2.FindStringSubmatch(string(body)) 709 | if len(selector) < 2 { 710 | return 0, 0 711 | } 712 | selectorInt, _ := strconv.ParseInt(selector[1], 10, 64) 713 | //fmt.Println(retcodeInt, selectorInt) 714 | return retcodeInt, selectorInt 715 | } 716 | 717 | /** 检查用户名是否在联系人列表中 718 | * @param slice []byte 联系人列表 719 | * @param str string 要检查的用户名 720 | * @return bool 如果用户名在列表中,返回 true;否则返回 false 721 | */ 722 | func contains(slice []byte, str string) bool { 723 | res := false 724 | jsonparser.ArrayEach(slice, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 725 | if err != nil { 726 | return 727 | } 728 | username, _ := jsonparser.GetString(value, "UserName") 729 | if username == str { 730 | res = true 731 | } 732 | }) 733 | return res 734 | } 735 | 736 | /** 获取Webwxsync数据 737 | * @return []byte webwxsync的响应body 738 | * @return error 错误信息 739 | */ 740 | func getWebwxsync() ([]byte, error) { 741 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxsync?sid=%s&skey=%s&pass_ticket=%s",webwx.Sid, webwx.Skey, webwx.PassTicket) 742 | data := fmt.Sprintf( 743 | `{ 744 | "BaseRequest":{ 745 | "Uin":%s, 746 | "Sid":"%s", 747 | "Skey":"%s", 748 | "DeviceID":"%s" 749 | }, 750 | "SyncKey":%s, 751 | "rr":%s 752 | }`, 753 | webwx.Uin,webwx.Sid,webwx.Skey,webwx.DeviceID,webwx.Synckey,webwx.Uin) 754 | req, err := http.NewRequest("POST",url,strings.NewReader(data)) 755 | if err != nil { 756 | return []byte{}, err 757 | } 758 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";wxsid="+webwx.Sid) 759 | client := &http.Client{} 760 | resp, err := client.Do(req) 761 | if err != nil { 762 | return []byte{}, fmt.Errorf("E00001: Please check your network connection.") 763 | } 764 | defer resp.Body.Close() 765 | body, err := ioutil.ReadAll(resp.Body) 766 | if err != nil { 767 | return []byte{}, fmt.Errorf("E00002: Failed to read response body.") 768 | } 769 | // 更新Synckey 770 | SyncKey, _, _, err := jsonparser.Get(body,"SyncKey") 771 | if err != nil { 772 | return []byte{}, fmt.Errorf("E00007: %s",err) 773 | } 774 | webwx.Synckey = SyncKey 775 | return body, nil 776 | } 777 | 778 | /** 获取Webwxsync中的群聊 779 | */ 780 | func getChatRoomFromWebwxsync() { 781 | StatusNotifyUserName := "" 782 | for i := 0; i < 20; i++ { 783 | webwxsync, err := getWebwxsync() 784 | if err != nil { 785 | continue 786 | } 787 | AddMsgList, _, _, err := jsonparser.Get(webwxsync, "AddMsgList") 788 | if err != nil { 789 | continue 790 | } 791 | // 遍历 AddMsgList 数组中的每个元素 792 | jsonparser.ArrayEach(AddMsgList, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 793 | if err != nil { 794 | return 795 | } 796 | StatusNotifyUserName, _ = jsonparser.GetString(value, "StatusNotifyUserName") 797 | }) 798 | if StatusNotifyUserName != "" { 799 | break 800 | } 801 | } 802 | if StatusNotifyUserName == "" { 803 | fmt.Println("[!] 未获取到群聊") 804 | return 805 | } 806 | var chatroomarray []map[string]interface{} 807 | parts := strings.Split(StatusNotifyUserName, ",") 808 | for _, part := range parts { 809 | trimmedPart := strings.TrimSpace(part) 810 | if strings.HasPrefix(trimmedPart, "@@") && !contains(chatrooms, trimmedPart) { 811 | ContactList := getBatchContact(trimmedPart) 812 | tempnick := "" 813 | var tempcount int64 814 | jsonparser.ArrayEach(ContactList, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 815 | if err != nil { 816 | return 817 | } 818 | tempnick,_ = jsonparser.GetString(value,"NickName") 819 | tempcount,_ = jsonparser.GetInt(value,"MemberCount") 820 | }) 821 | data := make(map[string] interface{}) 822 | data["UserName"] = trimmedPart 823 | data["NickName"] = tempnick 824 | data["MemberCount"] = tempcount 825 | chatroomarray = append(chatroomarray, data) 826 | } 827 | } 828 | chatrooms,_ = json.Marshal(chatroomarray) 829 | } 830 | 831 | /** 832 | * 获取消息 833 | */ 834 | func GetMsg() MsgData { 835 | var createtime,srctype,msgtype int64 836 | fromuser := "" 837 | srcname := "" 838 | srcuser := "" 839 | msg := "" 840 | 841 | for { 842 | retcode, selector := syncCheck() 843 | if retcode != 0 { 844 | return MsgData{Srctype: retcode,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 845 | } 846 | if selector == 0 { 847 | continue 848 | } else { 849 | break 850 | } 851 | } 852 | body, err := getWebwxsync() 853 | if err != nil { 854 | fmt.Println("[!]",err) 855 | return MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 856 | } 857 | // 获取消息 858 | count, _ := jsonparser.GetInt(body,"AddMsgCount") 859 | if count == 0 { 860 | return MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 861 | } 862 | msglist, _, _, err := jsonparser.Get(body,"AddMsgList") 863 | jsonparser.ArrayEach(msglist, func(value []byte, dataType jsonparser.ValueType, offset int, err error){ 864 | if err != nil { 865 | return 866 | } 867 | fromuser, _ = jsonparser.GetString(value,"FromUserName") 868 | if fromuser == myself.UserName { 869 | return 870 | } 871 | createtime, _ = jsonparser.GetInt(value,"CreateTime") 872 | msgtype,_ = jsonparser.GetInt(value,"MsgType") 873 | if msgtype == 51 { 874 | return 875 | } 876 | content,_ := jsonparser.GetString(value,"Content") 877 | appmsgtype,_ := jsonparser.GetInt(value,"AppMsgType") 878 | // 群聊消息 879 | if strings.HasPrefix(fromuser, "@@") { 880 | srctype = 1 881 | user := strings.Split(content, ":") 882 | if len(user) < 2 { 883 | srctype = 0 884 | return 885 | } 886 | msg = user[1] 887 | chatroominfo := getBatchContact(fromuser) 888 | jsonparser.ArrayEach(chatroominfo, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 889 | if err != nil { 890 | return 891 | } 892 | srcname,_ = jsonparser.GetString(value,"NickName") 893 | memberlist,_ ,_ ,err := jsonparser.Get(value,"MemberList") 894 | if err != nil { 895 | fmt.Println(err) 896 | } 897 | jsonparser.ArrayEach(memberlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error) { 898 | if err != nil { 899 | return 900 | } 901 | membername,_ := jsonparser.GetString(value,"UserName") 902 | if membername == user[0] { 903 | srcuser,_ = jsonparser.GetString(value,"NickName") 904 | } 905 | }) 906 | }) 907 | switch msgtype { 908 | case 1: 909 | //fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的文本消息:",msg) 910 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 911 | case 3: 912 | msgid,_ := jsonparser.GetString(value,"MsgId") 913 | msg = wxurl + "/cgi-bin/mmwebwx-bin/webwxgetmsgimg?&MsgID=" + msgid + "&skey=" + webwx.Skey 914 | //fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的图片消息:",msg) 915 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 916 | //case 34: 917 | // fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的语音消息暂不支持获取") 918 | //case 43: 919 | // fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的视频消息暂不支持获取") 920 | case 47: 921 | re := regexp.MustCompile(`(?i)cdnurl\s*=\s*"(.*?)"`) 922 | match := re.FindStringSubmatch(content) 923 | if len(match) > 1 { 924 | msg = html.UnescapeString(html.UnescapeString(match[1])) 925 | //fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的表情包消息:",msg) 926 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 927 | } 928 | srctype = 0 929 | case 49: 930 | switch appmsgtype { 931 | case 5: 932 | title,_ := jsonparser.GetString(value,"FileName") 933 | msg,_ = jsonparser.GetString(value,"Url") 934 | msg = title + html.UnescapeString(msg) 935 | //fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的公众号消息:",title + msg) 936 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 937 | //case 6: 938 | // fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的文件消息,暂不支持获取") 939 | //case 51: 940 | // fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的视频号消息,暂不支持获取") 941 | default: 942 | //fmt.Println("[+]",msgtime,"群 [",srcname,"] 成员 [",srcuser,"] 的其它类型消息,暂不支持获取") 943 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 944 | } 945 | case 10000: 946 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 947 | default: 948 | //fmt.Println("[+] 收到其他消息:",string(content)) 949 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 950 | } 951 | } else { 952 | srcuser = getNicknameByUsername(fromuser,"contactlist") 953 | // 公众号消息 954 | if appmsgtype == 5 { 955 | srctype = 2 956 | srcname = getNicknameByUsername(fromuser,"officiallist") 957 | title,_ := jsonparser.GetString(value,"FileName") 958 | msg_url,_ := jsonparser.GetString(value,"Url") 959 | msg_url = html.UnescapeString(msg_url) 960 | msg = title + msg_url 961 | //fmt.Println("[+]",msgtime,"公众号 [",srcname,"] 的消息:",msg) 962 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 963 | } else {// 联系人消息 964 | srctype = 3 965 | remarkname := getRemarkNameByUsername(fromuser) 966 | if remarkname != "" { 967 | srcuser = remarkname 968 | } 969 | //content,_ := jsonparser.GetString(value,"Content") 970 | //msgtype,_ := jsonparser.GetInt(value,"MsgType") 971 | switch msgtype { 972 | case 1: 973 | msg = content 974 | //fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的文本消息:",content) 975 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 976 | case 3: 977 | msgid,_ := jsonparser.GetString(value,"MsgId") 978 | msg = wxurl + "/cgi-bin/mmwebwx-bin/webwxgetmsgimg?&MsgID=" + msgid + "&skey=" + webwx.Skey 979 | //fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的图片消息:",msg) 980 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 981 | //case 34: 982 | // fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的语音消息暂不支持获取") 983 | //case 43: 984 | // fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的视频消息暂不支持获取") 985 | case 47: 986 | re := regexp.MustCompile(`(?i)cdnurl\s*=\s*"(.*?)"`) 987 | match := re.FindStringSubmatch(content) 988 | if len(match) > 1 { 989 | msg = html.UnescapeString(html.UnescapeString(match[1])) 990 | //fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的表情包消息:",msg) 991 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 992 | } 993 | srctype = 0 994 | case 49: 995 | appmsgtype,_ := jsonparser.GetInt(value,"AppMsgType") 996 | switch appmsgtype { 997 | case 5: 998 | title,_ := jsonparser.GetString(value,"FileName") 999 | msg,_ = jsonparser.GetString(value,"Url") 1000 | msg = title + html.UnescapeString(msg) 1001 | //fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的公众号消息:",title + msg) 1002 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: title + msg,Createtime: createtime} 1003 | //case 6: 1004 | // fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的文件消息,暂不支持获取") 1005 | //case 51: 1006 | // fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的视频号消息,暂不支持获取") 1007 | default: 1008 | //fmt.Println("[+]",msgtime,"[ ",srcuser,"] 的其它类型消息,暂不支持获取") 1009 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 1010 | } 1011 | case 10000: 1012 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 1013 | default: 1014 | //fmt.Println("[+] 收到其他消息:",string(content),msgtype) 1015 | return //MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 1016 | } 1017 | } 1018 | } 1019 | }) 1020 | return MsgData{Srctype: srctype,Srcname: srcname,Msgtype: msgtype,Fromuser: fromuser,Usernick: srcuser,Msg: msg,Createtime: createtime} 1021 | } 1022 | 1023 | /** 根据用户名获取备注 1024 | * @param username string 用户名 1025 | * @return string 备注 1026 | */ 1027 | func getRemarkNameByUsername(username string) string { 1028 | remarkname := "" 1029 | jsonparser.ArrayEach(contactlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error){ 1030 | if err != nil { 1031 | return 1032 | } 1033 | uName, _ := jsonparser.GetString(value,"UserName") 1034 | if username == uName { 1035 | remarkname, _ = jsonparser.GetString(value,"RemarkName") 1036 | } 1037 | }) 1038 | return remarkname 1039 | } 1040 | 1041 | /** 根据用户名获取昵称 1042 | * @param username string 用户名 1043 | * @param source string 数据来源,contactlist或officiallist结构体实例 1044 | * @return string 昵称 1045 | */ 1046 | func getNicknameByUsername(username string, source string) string { 1047 | nickname := "" 1048 | if source == "contactlist" { 1049 | jsonparser.ArrayEach(contactlist, func(value []byte, dataType jsonparser.ValueType, offset int, err error){ 1050 | if err != nil { 1051 | return 1052 | } 1053 | uName, _ := jsonparser.GetString(value,"UserName") 1054 | if username == uName { 1055 | nick, _ := jsonparser.GetString(value,"NickName") 1056 | nickname = nick 1057 | } 1058 | }) 1059 | } 1060 | if source == "officiallist" { 1061 | jsonparser.ArrayEach(officiallist, func(value []byte, dataType jsonparser.ValueType, offset int, err error){ 1062 | if err != nil { 1063 | return 1064 | } 1065 | uName, _ := jsonparser.GetString(value,"UserName") 1066 | if username == uName { 1067 | nick, _ := jsonparser.GetString(value,"NickName") 1068 | nickname = nick 1069 | } 1070 | }) 1071 | } 1072 | return nickname 1073 | } 1074 | 1075 | /** 1076 | * 发送消息 1077 | * @param msg 消息内容 1078 | * @param toUserName 接收者 1079 | * @return error 错误信息 1080 | */ 1081 | func SendMsg(msg string, toUserName string) error { 1082 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=%s",webwx.PassTicket) 1083 | localid := time.Now().UnixNano() 1084 | data := fmt.Sprintf( 1085 | `{ 1086 | "BaseRequest":{ 1087 | "Uin":%s, 1088 | "Sid":"%s", 1089 | "Skey":"%s", 1090 | "DeviceID":"%s" 1091 | }, 1092 | "Msg":{ 1093 | "Type":1, 1094 | "Content":"%s", 1095 | "FromUserName":"%s", 1096 | "ToUserName":"%s", 1097 | "LocalID":"%s", 1098 | "ClientMsgId":"%s" 1099 | }, 1100 | "Scene":0 1101 | }`, 1102 | webwx.Uin,webwx.Sid,webwx.Skey,webwx.DeviceID,msg,myself.UserName,toUserName,localid,localid) 1103 | req, err := http.NewRequest("POST",url,strings.NewReader(data)) 1104 | if err != nil { 1105 | return err 1106 | } 1107 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";wxsid="+webwx.Sid) 1108 | client := &http.Client{} 1109 | resp, err := client.Do(req) 1110 | if err != nil { 1111 | return fmt.Errorf("E00001: Please check your network connection.") 1112 | } 1113 | defer resp.Body.Close() 1114 | body, err := ioutil.ReadAll(resp.Body) 1115 | if err != nil { 1116 | return fmt.Errorf("E00002: Failed to read response body.") 1117 | } 1118 | ret,_ := jsonparser.GetInt(body,"BaseResponse","Ret") 1119 | if ret != 0 { 1120 | return fmt.Errorf("E00010: Msg send failed.") 1121 | } 1122 | return nil 1123 | } 1124 | 1125 | /** 1126 | * 邀请联系人加入群聊 1127 | * @param inviteuser 被邀请联系人UserName 1128 | * @param chatroom 群聊名称 1129 | * @return error 1130 | */ 1131 | func JoinChatroom(inviteuser string, chatroom string) error { 1132 | chatroomname := getChatroomName(chatroom) 1133 | if chatroomname == "" { 1134 | return fmt.Errorf("E00011: Chatroom does not exist.") 1135 | } 1136 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxupdatechatroom?fun=invitemember&pass_ticket=%s",webwx.PassTicket) 1137 | data := fmt.Sprintf( 1138 | `{ 1139 | "InviteMemberList":"%s", 1140 | "ChatRoomName":"%s", 1141 | "BaseRequest":{ 1142 | "Uin":%s, 1143 | "Sid":"%s", 1144 | "Skey":"%s", 1145 | "DeviceID":"%s" 1146 | } 1147 | }`, 1148 | inviteuser,chatroomname,webwx.Uin,webwx.Sid,webwx.Skey,webwx.DeviceID) 1149 | req, err := http.NewRequest("POST",url,strings.NewReader(data)) 1150 | if err != nil { 1151 | return err 1152 | } 1153 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";wxsid="+webwx.Sid) 1154 | client := &http.Client{} 1155 | resp, err := client.Do(req) 1156 | if err != nil { 1157 | return fmt.Errorf("E00001: Please check your network connection.") 1158 | } 1159 | defer resp.Body.Close() 1160 | body, err := ioutil.ReadAll(resp.Body) 1161 | if err != nil { 1162 | return fmt.Errorf("E00002: Failed to read response body.") 1163 | } 1164 | ret,_ := jsonparser.GetInt(body,"BaseResponse","Ret") 1165 | if ret != 0 { 1166 | return fmt.Errorf("E00010: Msg send failed.") 1167 | } 1168 | return nil 1169 | } 1170 | 1171 | /** 1172 | * 移除群聊中的某用户 1173 | * @param user 待移除用户UserName 1174 | * @param chatroom 群聊 1175 | * @return error 错误信息 1176 | */ 1177 | func RmChatroom(user string, chatroom string) error { 1178 | chatroomname := getChatroomName(chatroom) 1179 | if chatroomname == "" { 1180 | return fmt.Errorf("E00011: Chatroom does not exist.") 1181 | } 1182 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxupdatechatroom?fun=delmember&pass_ticket=%s",webwx.PassTicket) 1183 | data := fmt.Sprintf( 1184 | `{ 1185 | "DelMemberList":"%s", 1186 | "ChatRoomName":"%s", 1187 | "BaseRequest":{ 1188 | "Uin":%s, 1189 | "Sid":"%s", 1190 | "Skey":"%s", 1191 | "DeviceID":"%s" 1192 | } 1193 | }`, 1194 | user,chatroomname,webwx.Uin,webwx.Sid,webwx.Skey,webwx.DeviceID) 1195 | req, err := http.NewRequest("POST",url,strings.NewReader(data)) 1196 | if err != nil { 1197 | return err 1198 | } 1199 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";wxsid="+webwx.Sid) 1200 | client := &http.Client{} 1201 | resp, err := client.Do(req) 1202 | if err != nil { 1203 | return fmt.Errorf("E00001: Please check your network connection.") 1204 | } 1205 | defer resp.Body.Close() 1206 | body, err := ioutil.ReadAll(resp.Body) 1207 | if err != nil { 1208 | return fmt.Errorf("E00002: Failed to read response body.") 1209 | } 1210 | ret,_ := jsonparser.GetInt(body,"BaseResponse","Ret") 1211 | if ret != 1 { 1212 | return fmt.Errorf("E00010: Msg send failed.") 1213 | } 1214 | return nil 1215 | } 1216 | 1217 | /** 1218 | * 退出微信 1219 | */ 1220 | func Logout() { 1221 | url := fmt.Sprintf(wxurl + "/cgi-bin/mmwebwx-bin/webwxlogout?redirect=1&type=0&skey=%s",webwx.Skey) 1222 | data := fmt.Sprintf(`sid=%s&uin=%s`,webwx.Sid,webwx.Uin) 1223 | req, err := http.NewRequest("POST",url,strings.NewReader(data)) 1224 | if err != nil { 1225 | fmt.Println(err.Error()) 1226 | } 1227 | req.Header.Add("Cookie","wxuin="+webwx.Uin+";wxsid="+webwx.Sid+";webwx_data_ticket="+webwx.Webwx_data_ticket) 1228 | client := &http.Client{} 1229 | resp, err := client.Do(req) 1230 | if err != nil { 1231 | fmt.Println("[!] E00001: Please check your network connection.") 1232 | } 1233 | defer resp.Body.Close() 1234 | fmt.Println("[+] 已退出微信.") 1235 | } 1236 | 1237 | /** 1238 | * 获取ChatSet 1239 | * @param jsondata []byte 从webInit获取的Json数据 1240 | * @return string 返回ChatSet 1241 | * @return error 错误信息 1242 | 1243 | func GetChatSet(jsondata []byte) (string, error) { 1244 | ChatSet, err := jsonparser.GetString(jsondata,"ChatSet") 1245 | if err != nil { 1246 | return "", fmt.Errorf("E00008: %s",err) 1247 | } 1248 | return ChatSet, nil 1249 | } 1250 | */ 1251 | 1252 | 1253 | /** 1254 | * 获取SKey 1255 | * @param jsondata []byte 从webInit获取的Json数据 1256 | * @return string 返回SKey 1257 | * @return error 错误信息 1258 | 1259 | func GetSKey(jsondata []byte) (string, error) { 1260 | Skey, err := jsonparser.GetString(jsondata,"SKey") 1261 | if err != nil { 1262 | return "", fmt.Errorf("E00008: %s",err) 1263 | } 1264 | return Skey, nil 1265 | } 1266 | */ 1267 | --------------------------------------------------------------------------------