# wxBot **Repository Path**: fxh/wxBot ## Basic Information - **Project Name**: wxBot - **Description**: Python实现微信机器人 - **Primary Language**: Python - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 123 - **Created**: 2024-02-16 - **Last Updated**: 2024-02-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # WxBot ![python](https://img.shields.io/badge/python-3.5-origine.svg) ![DUB](https://img.shields.io/dub/l/vibe-d.svg?maxAge=2592000) 当时的我刚刚接触Python,急需要一个既不是太复杂,也不太难理解的项目练手,然后一个偶然的机会,在github上找到了类似的项目,所有就想着自己试试看,毕竟有比较成熟的项目作为参考,遇到问题也不会太卡项目进度,然而并没有那么顺利,貌似写代码都这样,一次次的修改最后成型,所以这是一个初学者的实践项目,所以不对的地方尽可以指出,所以不喜勿喷! ## 特别提醒 1. 在所有需要发送json的地方,请注意使用『json.dumps()』将字典转换成Json字符串,不然接口会返回空 2. 看源代码的朋友可能会发现,我对于`r`参数的理解更多的是随机整数,网上将`r`和`_`都做时间戳处理,但是13位时间戳还真不多见,可能是我见识短吧 3. 对于等待扫描接口中的`_`参数,我的理解是首次是用的时间戳,后面的请求是依次加一,并不是每次的时间戳 ## 执行流程 1. 打开首页,分配一个随机uuid, 2. 根据该uuid获取二维码图片。 3. 微信客户端扫描该图片,在客户端确认登录。 4. 浏览器不停的调用一个接口,如果返回登录成功,则调用登录接口 5. 此时可以获取联系人列表,可以发送消息。然后不断调用同步接口。 6. 如果同步接口有返回,则可以获取新消息,然后继续调用同步接口。 ```sh +--------------+ +---------------+ +---------------+ | | | | | | | Get UUID | | Get Contact | | Status Notify | | | | | | | +-------+------+ +-------^-------+ +-------^-------+ | | | | +-------+ +--------+ | | | +-------v------+ +-----+--+------+ +--------------+ | | | | | | | Get QRCode | | Weixin Init +------> Sync Check <----+ | | | | | | | +-------+------+ +-------^-------+ +-------+------+ | | | | | | | +-----------+ | | | +-------v------+ +-------+--------+ +-------v-------+ | | Confirm Login | | | | +------> Login +---------------> New Login Page | | Weixin Sync | | | | | | | | | +------+-------+ +----------------+ +---------------+ | | |QRCode Scaned| +-------------+ ``` ## WebWechat API ### 获取UUID(参考方法 getUUID) | API | 获取 UUID | | --- | --------- | | url | https://login.weixin.qq.com/jslogin | | method | POST | | data | URL Encode | | params | **appid**: `应用ID`
**fun**: new `应用类型`
**lang**: zh\_CN `语言`
**_**: `时间戳` | 返回数据(String): ``` window.QRLogin.code = 200; window.QRLogin.uuid = "xxx" ``` > 注:这里的appid就是在微信开放平台注册的应用的AppID。网页版微信有两个AppID,早期的是`wx782c26e4c19acffb`,在微信客户端上显示为应用名称为`Web微信`;现在用的是`wxeb7ec651dd0aefa9`,显示名称为`微信网页版`。 ### 显示二维码(参考方法 showQrCode) | API | 显示二维码 | | --- | --------- | | url | https://login.weixin.qq.com/qrcode/{uuid} | | method | POST | | params | **t** : webwx
**_** : 时间戳|
**注:**在代码中我们的二维码内容并不是`https://login.weixin.qq.com/qrcode/{uuid}`而是`https://login.weixin.qq.com/l/{uuid}` ### 等待登录(参考方法 waitForLogin)这里是微信确认登录 | API | 二维码扫描登录 | | --- | --------- | | url | https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login | | method | GET | | params | **tip** :网上解释=》【1:未扫描 0:已扫描】据我观察=》第一次tip为1,后面不管有没有扫描都变为0
**uuid** : 获取到的uuid
**_** : 时间戳(13位整型) | 返回数据(String): ``` window.code=xxx; xxx: 408 登陆超时 201 扫描成功 200 确认登录 当返回200时,还会有 window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=xxx&uuid=xxx&lang=xxx&scan=xxx"; ``` ### 登录获取Cookie(参考方法 login) | API | webwxnewloginpage | | --- | --------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage | | method | GET | | params | **ticket** : xxx
**uuid** : xxx
**lang** : zh_CN
**scan** : xxx
**fun** : new | 返回数据(XML): ``` 0 OK xxx xxx xxx xxx 1 ``` **核心数据点:**在这一步获取xml中的 `skey`, `wxsid`, `wxuin`, `pass_ticket` ### 微信初始化 | API | webwxinit | | --- | --------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?pass_ticket=xxx&skey=xxx&r=xxx | | method | POST | | data | JSON | | header | Content-Type: application/json; charset=UTF-8 | | params | {
     BaseRequest: {
         Uin: xxx,
         Sid: xxx,
         Skey: xxx,
         DeviceID: xxx,
     }
} | 返回数据(JSON): ``` { "BaseResponse": { "Ret": 0, "ErrMsg": "" }, "Count": 11, "ContactList": [...], "SyncKey": { "Count": 4, "List": [ { "Key": 1, "Val": 635705559 }, ... ] }, "User": { "Uin": xxx, "UserName": xxx, "NickName": xxx, "HeadImgUrl": xxx, "RemarkName": "", "PYInitial": "", "PYQuanPin": "", "RemarkPYInitial": "", "RemarkPYQuanPin": "", "HideInputBarFlag": 0, "StarFriend": 0, "Sex": 1, "Signature": "Apt-get install B", "AppAccountFlag": 0, "VerifyFlag": 0, "ContactFlag": 0, "WebWxPluginSwitch": 0, "HeadImgFlag": 1, "SnsFlag": 17 }, "ChatSet": xxx, "SKey": xxx, "ClientVersion": 369297683, "SystemTime": 1453124908, "GrayScale": 1, "InviteStartCount": 40, "MPSubscribeMsgCount": 2, "MPSubscribeMsgList": [...], "ClickReportInterval": 600000 } ``` 这一步中获取 `SyncKey`, `User` 后面的消息监听用。 ### 开启微信状态通知(参考方法 wxStatusNotify) | API | webwxstatusnotify | | --- | --------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify?lang=zh_CN&pass_ticket=xxx | | method | POST | | data | JSON | | header | ContentType: application/json; charset=UTF-8 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Code: 3,
     FromUserName: `自己ID`,
     ToUserName: `自己ID`,
     ClientMsgId: `时间戳`
} | 返回数据(JSON): ``` { "BaseResponse": { "Ret": 0, "ErrMsg": "" }, ... } ``` ### 获取联系人列表(参考方法 getContact) | API | webwxgetcontact | | --- | --------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin//webwxgetcontact?pass_ticket=xxx&skey=xxx&r=xxx | | method | POST | | data | JSON | | header | ContentType: application/json; charset=UTF-8 |
**注:**个人认为这个在做自动回复的机器人的时候没有获取的必要
返回数据(JSON): ``` { "BaseResponse": { "Ret": 0, "ErrMsg": "" }, "MemberCount": 334, "MemberList": [ { "Uin": 0, "UserName": xxx, "NickName": "Urinx", "HeadImgUrl": xxx, "ContactFlag": 3, "MemberCount": 0, "MemberList": [], "RemarkName": "", "HideInputBarFlag": 0, "Sex": 0, "Signature": "我是二蛋", "VerifyFlag": 8, "OwnerUin": 0, "PYInitial": "URINX", "PYQuanPin": "Urinx", "RemarkPYInitial": "", "RemarkPYQuanPin": "", "StarFriend": 0, "AppAccountFlag": 0, "Statues": 0, "AttrStatus": 0, "Province": "", "City": "", "Alias": "Urinxs", "SnsFlag": 0, "UniFriend": 0, "DisplayName": "", "ChatRoomId": 0, "KeyWord": "gh_", "EncryChatRoomId": "" }, ... ], "Seq": 0 } ``` ### 消息检查(参考方法 syncCheck) | API | synccheck | | --- | --------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=xxx&pass_ticket=xxx | | method | POST | | data | JSON | | header | ContentType: application/json; charset=UTF-8 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Count: `群数量`,
     List: [
         { UserName: `群ID`, EncryChatRoomId: "" },
         ...
     ],
} | 返回数据(String): ``` window.synccheck={retcode:"xxx",selector:"xxx"} retcode: 0 正常 1100 失败/登出微信 1101 在其他地方登录了微信 selector: 0 正常 2 新的消息 7 进入/离开聊天界面 ``` ### 获取最新消息(参考方法 webwxsync) | API | webwxsync | | --- | --------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=xxx&skey=xxx&pass_ticket=xxx | | method | POST | | data | JSON | | header | ContentType: application/json; charset=UTF-8 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     SyncKey: xxx,
     rr: `时间戳取反`
} | 返回数据(JSON): ``` { 'BaseResponse': {'ErrMsg': '', 'Ret': 0}, 'SyncKey': { 'Count': 7, 'List': [ {'Val': 636214192, 'Key': 1}, ... ] }, 'ContinueFlag': 0, 'AddMsgCount': 1, 'AddMsgList': [ { 'FromUserName': '', 'PlayLength': 0, 'RecommendInfo': {...}, 'Content': "", 'StatusNotifyUserName': '', 'StatusNotifyCode': 5, 'Status': 3, 'VoiceLength': 0, 'ToUserName': '', 'ForwardFlag': 0, 'AppMsgType': 0, 'AppInfo': {'Type': 0, 'AppID': ''}, 'Url': '', 'ImgStatus': 1, 'MsgType': 51, 'ImgHeight': 0, 'MediaId': '', 'FileName': '', 'FileSize': '', ... }, ... ], 'ModChatRoomMemberCount': 0, 'ModContactList': [], 'DelContactList': [], 'ModChatRoomMemberList': [], 'DelContactCount': 0, ... } ``` ### 消息接口 | API | webwxsendmsg | | --- | ------------ | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=xxx | | method | POST | | data | JSON | | header | ContentType: application/json; charset=UTF-8 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Msg: {
         Type: 1 `文字消息`,
         Content: `要发送的消息`,
         FromUserName: `自己ID`,
         ToUserName: `好友ID`,
         LocalID: `与clientMsgId相同`,
         ClientMsgId: `时间戳左移4位随后补上4位随机数`
     }
} | 返回数据(JSON): ``` { "BaseResponse": { "Ret": 0, "ErrMsg": "" }, ... } ``` #### 发送表情 | API | webwxsendmsgemotion | | --- | ------------ | | url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendemoticon?fun=sys&f=json&pass_ticket=xxx | | method | POST | | data | JSON | | header | ContentType: application/json; charset=UTF-8 | | params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     Msg: {
         Type: 47 `emoji消息`,
         EmojiFlag: 2,
         MediaId: `表情上传后的媒体ID`,
         FromUserName: `自己ID`,
         ToUserName: `好友ID`,
         LocalID: `与clientMsgId相同`,
         ClientMsgId: `时间戳左移4位随后补上4位随机数`
     }
} |
### 图片接口 | API | webwxgeticon | | --- | ------------ | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgeticon | | method | GET | | params | **seq**: `数字,可为空`
**username**: `ID`
**skey**: xxx |
| API | webwxgetheadimg | | --- | --------------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetheadimg | | method | GET | | params | **seq**: `数字,可为空`
**username**: `群ID`
**skey**: xxx |
| API | webwxgetmsgimg | | --- | --------------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetmsgimg | | method | GET | | params | **MsgID**: `消息ID`
**type**: slave `略缩图` or `为空时加载原图`
**skey**: xxx |
### 多媒体接口 | API | webwxgetvideo | | --- | --------------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetvideo | | method | GET | | params | **msgid**: `消息ID`
**skey**: xxx |
| API | webwxgetvoice | | --- | --------------- | | url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetvoice | | method | GET | | params | **msgid**: `消息ID`
**skey**: xxx |
### 账号类型 | 类型 | 说明 | | :--: | --- | | 个人账号 | 以`@`开头,例如:`@xxx` | | 群聊 | 以`@@`开头,例如:`@@xxx` | | 公众号/服务号 | 以`@`开头,但其`VerifyFlag` & 8 != 0

`VerifyFlag`:
         一般公众号/服务号:8
         微信自家的服务号:24
         微信官方账号`微信团队`:56 | | 特殊账号 | 像文件传输助手之类的账号,有特殊的ID,目前已知的有:
`filehelper`, `newsapp`, `fmessage`, `weibo`, `qqmail`, `fmessage`, `tmessage`, `qmessage`, `qqsync`, `floatbottle`, `lbsapp`, `shakeapp`, `medianote`, `qqfriend`, `readerapp`, `blogapp`, `facebookapp`, `masssendapp`, `meishiapp`, `feedsapp`, `voip`, `blogappweixin`, `weixin`, `brandsessionholder`, `weixinreminder`, `officialaccounts`, `notification_messages`, `wxitil`, `userexperience_alarm`, `notification_messages` |
### 消息类型 消息一般格式: ``` { "FromUserName": "", "ToUserName": "", "Content": "", "StatusNotifyUserName": "", "ImgWidth": 0, "PlayLength": 0, "RecommendInfo": {...}, "StatusNotifyCode": 4, "NewMsgId": "", "Status": 3, "VoiceLength": 0, "ForwardFlag": 0, "AppMsgType": 0, "Ticket": "", "AppInfo": {...}, "Url": "", "ImgStatus": 1, "MsgType": 1, "ImgHeight": 0, "MediaId": "", "MsgId": "", "FileName": "", "HasProductId": 0, "FileSize": "", "CreateTime": 1454602196, "SubMsgType": 0 } ```
| MsgType | 说明 | | ------- | --- | | 1 | 文本消息 | | 3 | 图片消息 | | 34 | 语音消息 | | 37 | VERIFYMSG | | 40 | POSSIBLEFRIEND_MSG | | 42 | 共享名片 | | 43 | 视频通话消息 | | 47 | 动画表情 | | 48 | 位置消息 | | 49 | 分享链接 | | 50 | VOIPMSG | | 51 | 微信初始化消息 | | 52 | VOIPNOTIFY | | 53 | VOIPINVITE | | 62 | 小视频 | | 9999 | SYSNOTICE | | 10000 | 系统消息 | | 10002 | 撤回消息 |
**微信初始化消息** ```html MsgType: 51 FromUserName: 自己ID ToUserName: 自己ID StatusNotifyUserName: 最近联系的联系人ID Content: // 最近联系的联系人 filehelper,xxx@chatroom,wxid_xxx,xxx,... // 朋友圈 MomentsUnreadMsgStatus 1454502365 // 未读的功能账号消息,群发助手,漂流瓶等 ``` **文本消息** ``` MsgType: 1 FromUserName: 发送方ID ToUserName: 接收方ID Content: 消息内容 ``` **图片消息** ```html MsgType: 3 FromUserName: 发送方ID ToUserName: 接收方ID MsgId: 用于获取图片 Content: ``` **小视频消息** ```html MsgType: 62 FromUserName: 发送方ID ToUserName: 接收方ID MsgId: 用于获取小视频 Content: ``` **地理位置消息** ``` MsgType: 1 FromUserName: 发送方ID ToUserName: 接收方ID Content: http://weixin.qq.com/cgi-bin/redirectforward?args=xxx // 属于文本消息,只不过内容是一个跳转到地图的链接 ``` **名片消息** ```js MsgType: 42 FromUserName: 发送方ID ToUserName: 接收方ID Content: RecommendInfo: { "UserName": "xxx", // ID "Province": "xxx", "City": "xxx", "Scene": 17, "QQNum": 0, "Content": "", "Alias": "xxx", // 微信号 "OpCode": 0, "Signature": "", "Ticket": "", "Sex": 0, // 1:男, 2:女 "NickName": "xxx", // 昵称 "AttrStatus": 4293221, "VerifyFlag": 0 } ``` **语音消息** ```html MsgType: 34 FromUserName: 发送方ID ToUserName: 接收方ID MsgId: 用于获取语音 Content: ``` **动画表情** ```html MsgType: 47 FromUserName: 发送方ID ToUserName: 接收方ID Content: ``` **普通链接或应用分享消息** ```html MsgType: 49 AppMsgType: 5 FromUserName: 发送方ID ToUserName: 接收方ID Url: 链接地址 FileName: 链接标题 Content: 5 ... ``` **音乐链接消息** ```html MsgType: 49 AppMsgType: 3 FromUserName: 发送方ID ToUserName: 接收方ID Url: 链接地址 FileName: 音乐名 AppInfo: // 分享链接的应用 { Type: 0, AppID: wx485a97c844086dc9 } Content: 3 0 0 http://ws.stream.qqmusic.qq.com/C100003i9hMt1bgui0.m4a?vkey=6867EF99F3684&guid=ffffffffc104ea2964a111cf3ff3edaf&fromtag=46 http://ws.stream.qqmusic.qq.com/C100003i9hMt1bgui0.m4a?vkey=6867EF99F3684&guid=ffffffffc104ea2964a111cf3ff3edaf&fromtag=46 0 http://imgcache.qq.com/music/photo/album/63/180_albumpic_143163_0.jpg 0 29 摇一摇搜歌 ``` **群消息** ``` MsgType: 1 FromUserName: @@xxx ToUserName: @xxx Content: @xxx:
xxx ``` **红包消息** ``` MsgType: 49 AppMsgType: 2001 FromUserName: 发送方ID ToUserName: 接收方ID Content: 未知 ``` 注:根据网页版的代码可以看到未来可能支持查看红包消息,但目前走的是系统消息,见下。 **系统消息** ``` MsgType: 10000 FromUserName: 发送方ID ToUserName: 自己ID Content: "你已添加了 xxx ,现在可以开始聊天了。" "如果陌生人主动添加你为朋友,请谨慎核实对方身份。" "收到红包,请在手机上查看" ``` ## 致谢 > 本项目受到以下项目的启发: > - [Urinx/WeixinBot](https://github.com/Urinx/WeixinBot) > - [liuwons/wxBot](https://github.com/zhaoxiang051405/wxBot) > - [littlecodersh/ItChat](https://github.com/littlecodersh/ItChat)