diff --git a/READEME.md b/READEME.md new file mode 100644 index 0000000..06a8873 --- /dev/null +++ b/READEME.md @@ -0,0 +1,51 @@ +# wxbot + +微信聊天记录自动存sqlite数据库,基于微信网页版。注意,Web版获取不到微信用户的微信ID,所得到的用户名/ID是仅当前会话有效的,因此只有根据备注名在数据库中识别用户比较靠谱。 + +项目fork自[@Urinx/WeixinBot](https://github.com/Urinx/WeixinBot),我只是加了个存到sqlite的壳,感谢老哥的网页版微信API。 + +## 数据表结构 +| 字段名 | 类型 | 说明 | +| -- | -- | -- | +| msg_id| TEXT | 消息ID | +|create_time | INTEGER | 创建时间,Unix时间戳 | +| msg_type | INTEGER | 消息类型,1-文本,3-图片,34-语音,43-视频,0-其它 +| content | TEXT | 消息的文本内容 +| from_id | TEXT | 消息发送者ID(不是微信ID,没啥用),如果是来自群的消息此字段为空 +| from_name | TEXT | 消息发送者的备注,没备注就昵称,如果是来自群的消息此字段为空 +| from_nickname | TEXT | 消息发送者的昵称,如果是来自群的消息此字段为空 +| to_id | TEXT | 接收者ID,如果是发送到群的消息此字段为空 +| to_name | TEXT | 接收者备注,没备注就昵称,如果是发送到群的消息此字段为空 +| to_nickname | TEXT | 接收者昵称,如果是发送到群的消息此字段为空 +| group_id | TEXT | 如果是群消息则为群的ID否则为空,照样,没啥用 +| group_name | TEXT | 如果是群消息则为群聊名称(应该吧🤣我不关注群消息),否则为空 + +消息里的图片等文件以`img_`,`voice_`等前缀 + 消息ID存储于命令行参数给定的数据目录中。 + +其它信息请参考[项目@Urinx/WeixinBot](https://github.com/Urinx/WeixinBot)。 + +## 使用 +### 克隆仓库并安装python依赖 +```shell +# 不适用win系统 +git clone https://github.com/mattuylee/wxbot.git +cd wxbot +python3 -m venv ./venv +source ./venv/bin/activate +pip3 install -r ./requirements.txt +``` + +### 运行 +`python ./main.py -f -d ` +其中`DB_FILE`是sqlite数据库文件路径,`DATA_DIR`是图片视频等数据的存储目录。 + +支持sqlcipher加密,程序启动会要求输入密码,如不需要加密可留空直接回车。注意,以后每次启动必须输入相同的密码。暂不支持修改密码,可自行安装sqlcipher打开数据库修改密码。 + +未加密数据库可直接通过sqlite3打开,加密数据库需要sqlcipher才能打开。程序只负责记录数据,没有读取能力。 + +### 其它 +关于表情包,如果是从表情商店里添加的表情包网页版微信是获取不到的,为了保护创作者的权益。这倒是可以理解。 + +如果发现无法登录先试试在浏览器登录网页版微信,有可能是腾讯为了“安全”限制了你的微信帐号在网页登录。 + +最后,祝腾讯早点倒闭。 diff --git a/README.md b/README.md deleted file mode 100644 index 4b237ee..0000000 --- a/README.md +++ /dev/null @@ -1,799 +0,0 @@ -# WeixinBot [![star this repo](http://github-svg-buttons.herokuapp.com/star.svg?user=Urinx&repo=WeixinBot&style=flat&background=1081C1)](http://github.com/Urinx/WeixinBot) [![fork this repo](http://github-svg-buttons.herokuapp.com/fork.svg?user=Urinx&repo=WeixinBot&style=flat&background=1081C1)](http://github.com/Urinx/WeixinBot/fork) ![python](https://img.shields.io/badge/python-2.7%20&%203.6-ff69b4.svg) - -网页版微信API,包含终端版微信及微信机器人 - -## Contents -* [Demo](#Demo) -* [Web Weixin Pipeline](#Web-Weixin-Pipeline) -* [Web Weixin API](#Web-Weixin-API) -* [Discussion Group](#Discussion-Group) -* [Recent Update](#Recent-Update) - -## Demo -为了确保能正常运行示例脚本,请安装所需的第三方包。 - -``` -pip install -r requirements.txt -``` - -注:下面演示的图片与功能可能不是最新的,具体请看源码。 - -
- -
- -按照操作指示在手机微信上扫描二维码然后登录,你可以选择是否开启自动回复模式。 - -![2](imgs/2.png) - -开启自动回复模式后,如果接收到的是文字消息就会自动回复,包括群消息。 - -![3](imgs/3.png) - -名片,链接,动画表情和地址位置消息。 - -![4](imgs/4.png) - -![5](imgs/5.png) - -网页版上有的功能目前基本上都能支持。 - -## Web Weixin Pipeline - -``` - +--------------+ +---------------+ +---------------+ - | | | | | | - | 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| -+-------------+ -``` - - -## Web Weixin API - -### 登录 - -| 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`,显示名称为`微信网页版`。 - -
- -
- -
- -| API | 绑定登陆(webwxpushloginurl) | -| --- | --------- | -| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxpushloginurl | -| method | GET | -| params | **uin**: xxx | - -返回数据(String): -``` -{'msg': 'all ok', 'uuid': 'xxx', 'ret': '0'} - -通过这种方式可以省掉扫二维码这步操作,更加方便 -``` -
- -| API | 生成二维码 | -| --- | --------- | -| url | https://login.weixin.qq.com/l/ `uuid` | -| method | GET | -
- -| API | 二维码扫描登录 | -| --- | --------- | -| url | https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login | -| method | GET | -| params | **tip**: 1 `未扫描` 0 `已扫描`
**uuid**: xxx
**_**: `时间戳` | - -返回数据(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"; -``` -
- -| 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 - -``` -
- -### 微信初始化 - -| API | webwxinit | -| --- | --------- | -| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?pass_ticket=xxx&skey=xxx&r=xxx | -| method | POST | -| data | JSON | -| header | ContentType: 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 -} -``` -
- -| 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": "" - }, - ... -} -``` -
- -### 获取联系人信息 - -| 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": "你好,我们是地球三体组织。在这里,你将感受到不一样的思维模式,以及颠覆常规的世界观。而我们的目标,就是以三体人的智慧,引领人类未来科学技术500年。", - "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 -} -``` -
- -| API | webwxbatchgetcontact | -| --- | --------- | -| 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: "" },
         ...
     ],
} | - -返回数据(JSON)同上 -

- -### 同步刷新 - -| API | synccheck | -| --- | --------- | -| protocol | https | -| host | webpush.weixin.qq.com
webpush.wx2.qq.com
webpush.wx8.qq.com
webpush.wx.qq.com
webpush.web2.wechat.com
webpush.web.wechat.com | -| path | /cgi-bin/mmwebwx-bin/synccheck | -| method | GET | -| data | URL Encode | -| params | **r**: `时间戳`
**sid**: xxx
**uin**: xxx
**skey**: xxx
**deviceid**: xxx
**synckey**: xxx
**_**: `时间戳` | - -返回数据(String): -``` -window.synccheck={retcode:"xxx",selector:"xxx"} - -retcode: - 0 正常 - 1100 失败/登出微信 -selector: - 0 正常 - 2 新的消息 - 7 进入/离开聊天界面 -``` -
- -| 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 | webwxrevokemsg | -| --- | ------------ | -| url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxrevokemsg | -| method | POST | -| data | JSON | -| header | ContentType: application/json; charset=UTF-8 | -| params | {
     BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx },
     SvrMsgId: msg_id,
     ToUserName: user_id,
     ClientMsgId: local_msg_id
} | - -返回数据(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`, `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 | 好友确认消息 | -| 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 ,现在可以开始聊天了。" - "如果陌生人主动添加你为朋友,请谨慎核实对方身份。" - "收到红包,请在手机上查看" -``` - - -## Discussion Group -如果你希望和 WeixinBot 的其他开发者交流,或者有什么问题和建议,欢迎大家加入微信群【Youth fed the dog】一起讨论。扫描下面的二维码添加机器人为好友,并回复【Aidog】获取入群链接。 - -
-join us -
- -注:这个不是群的二维码,是机器人拉你入群,记得回复机器人【Aidog】哦~ (secret code: Aidog) - -## Recent Update - -- association_login - 目前网页版微信已经可以脱离扫码,但是依然需要在客户端进行确认登录。 diff --git a/imgs/1.png b/imgs/1.png deleted file mode 100644 index bcde745..0000000 Binary files a/imgs/1.png and /dev/null differ diff --git a/imgs/2.png b/imgs/2.png deleted file mode 100644 index a15e0c4..0000000 Binary files a/imgs/2.png and /dev/null differ diff --git a/imgs/3.png b/imgs/3.png deleted file mode 100644 index 6974e66..0000000 Binary files a/imgs/3.png and /dev/null differ diff --git a/imgs/4.png b/imgs/4.png deleted file mode 100644 index 835565b..0000000 Binary files a/imgs/4.png and /dev/null differ diff --git a/imgs/5.png b/imgs/5.png deleted file mode 100644 index 2421c9f..0000000 Binary files a/imgs/5.png and /dev/null differ diff --git a/imgs/8.jpg b/imgs/8.jpg deleted file mode 100644 index 921a816..0000000 Binary files a/imgs/8.jpg and /dev/null differ diff --git a/imgs/groupQrcode.jpg b/imgs/groupQrcode.jpg deleted file mode 100644 index 556a7da..0000000 Binary files a/imgs/groupQrcode.jpg and /dev/null differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..5c2f617 --- /dev/null +++ b/main.py @@ -0,0 +1,180 @@ +import argparse +import getpass +import logging +import sys +import os +from weixin import WebWeixin +from sqlcipher3 import dbapi2 as sqlite + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("-f", "--db-file", required=True, help="数据库文件") + parser.add_argument("-d", "--data-dir",required=True, help="文件存储目录") + args = parser.parse_args() + return args + + +class WXBot(WebWeixin): + def getUserNickName(self, id): + if id == self.User['UserName']: + return self.User['NickName'] + for member in self.ContactList: + if member['UserName'] == id: + return member['NickName'] + return None + + def handleMsg(self, r): + for msg in r['AddMsgList']: + msgID = msg['MsgId'] + msgType = msg['MsgType'] + sql = "INSERT INTO msg (\ + msg_id,\ + create_time,\ + msg_type,\ + content,\ + from_id,\ + from_name,\ + from_nickname,\ + to_id,\ + to_name,\ + to_nickname,\ + group_id,\ + group_name\ + ) VALUES (\ + :msg_id,\ + :create_time,\ + :msg_type,\ + :content,\ + :from_id,\ + :from_name,\ + :from_nickname,\ + :to_id,\ + :to_name,\ + :to_nickname,\ + :group_id,\ + :group_name\ + )" + params = { + "msg_id": msgID, + "create_time": msg['CreateTime'], + "msg_type": msgType, + "content": None, + "from_id": msg['FromUserName'], + "from_name": self.getUserRemarkName(msg['FromUserName']), + "from_nickname": self.getUserNickName(msg['FromUserName']), + "to_id": msg['ToUserName'], + "to_name": self.getUserRemarkName(msg['ToUserName']), + "to_nickname": self.getUserNickName(msg['ToUserName']), + "group_id": None, + "group_name": None + } + # 过滤特殊帐号消息 + if msg['FromUserName'] in self.SpecialUsers or msg['ToUserName'] in self.SpecialUsers: + continue + if msgType == 1: + params['content'] = msg['Content'].replace('<', '<').replace('>', '>') + elif msgType == 3: + params['content'] = "[图片](%s)" % msgID + self.webwxgetmsgimg(msgID) + elif msgType == 34: + params['content'] = "[语音](%s)" % msgID + self.webwxgetvoice(msgID) + elif msgType == 43: + params['content'] = "[视频](%s)" % msgID + self.webwxgetvideo(msgID) + elif msgType == 62: + params['msg_type'] = 43 + params['content'] = "[小视频](%s)" % msgID + self.webwxgetvideo(msgID) + elif msgType == 42: + params['msg_type'] = 0 + params['content'] = "[名片](%s)" % msg['RecommendInfo']['NickName'] + elif msgType == 47: + if msg['HasProductId'] == 1: + # 有版权的表情包,无法在Web端查看 + params['msg_type'] = 0 + params['content'] = "[表情包图片]" + else: + params['msg_type'] = 3 + params['content'] = "[动画表情](%s)" % msgID + self.webwxgetmsgimg(msgID) + elif msgType == 49: + params['msg_type'] = 0 + params['content'] = "[链接:%s](%s)" % (msg['FileName'], msg['Url']) + elif (50, 51, 52, 53, 9999, 10000, 10002).index(msgType) >= 0: + continue + else: + params['msg_type'] = 0 + params['content'] = "" + continue + # 群消息 + if msg['FromUserName'][:2] == '@@': + if ":
" in content: + [people, content] = msg['Content'].split(':
', 1) + params['group_name'] = self.getUserRemarkName(msg['FromUserName']) + params['from_id'] = people + params['from_name'] = self.getUserRemarkName(people) + params['to_id'] = params['to_name'] = params['to_nickname'] = None + elif msg['ToUserName'][:2] == '@@': + params['group_name'] = self.getUserRemarkName(msg['ToUserName']) + params['from_id'] = self.User['UserName'] + params['from_name'] = self.User['NickName'] + params['to_id'] = params['to_name'] = params['to_nickname'] = None + conn.execute(sql, params) + conn.commit() + + +args = parse_args() +dataDir = args.data_dir +if not os.access(os.path.dirname(args.db_file), os.X_OK) and not os.access(args.db_file, os.W_OK): + print("无法创建或打开数据库文件") + exit() +elif not os.access(dataDir, os.X_OK): + print("数据目录不存在或权限不足") + exit() +conn = sqlite.connect(args.db_file) + +while True: + try: + passwd = getpass.getpass("输入数据库密码:") + if passwd != '': + conn.execute("PRAGMA KEY = %s" % passwd) + conn.execute("CREATE TABLE IF NOT EXISTS msg (\ + msg_id TEXT NOT NULL,\ + create_time INTEGER NOT NULL,\ + msg_type INTEGER NOT NULL,\ + content TEXT,\ + from_id TEXT,\ + from_name TEXT,\ + from_nickname TEXT,\ + to_id TEXT,\ + to_name TEXT,\ + to_nickname TEXT,\ + group_id TEXT,\ + group_name TEXT\ + );") + break + except sqlite.DatabaseError as e: + if 'file is not a database' in e.args: + print("密码错误,请重试") + else: + print("打开数据库失败", e) + except KeyboardInterrupt: + exit(1) + except Exception as e: + print("打开数据库失败", e) + exit(1) + + +if sys.stdout.encoding == 'cp936': + sys.stdout = UnicodeStreamFilter(sys.stdout) + +if __name__ == '__main__': + logger = logging.getLogger(__name__) + if not sys.platform.startswith('win'): + import coloredlogs + coloredlogs.install(level='INFO') + wxbot = WXBot() + wxbot.saveFolder = dataDir + wxbot.start() diff --git a/wxbot_demo_py3/requirements.txt b/requirements.txt similarity index 77% rename from wxbot_demo_py3/requirements.txt rename to requirements.txt index 1268a48..ed0735b 100644 --- a/wxbot_demo_py3/requirements.txt +++ b/requirements.txt @@ -8,3 +8,5 @@ six requests_toolbelt pyqrcode certifi +argparse +sqlcipher3-binary diff --git a/wxbot_demo_py3/weixin.py b/weixin.py similarity index 99% rename from wxbot_demo_py3/weixin.py rename to weixin.py index 09c438c..c1b6114 100755 --- a/wxbot_demo_py3/weixin.py +++ b/weixin.py @@ -23,7 +23,6 @@ from collections import defaultdict from urllib.parse import urlparse from lxml import html from socket import timeout as timeout_error -#import pdb # for media upload import mimetypes @@ -616,7 +615,7 @@ class WebWeixin(object): def webwxgetmsgimg(self, msgid): url = self.base_uri + \ '/webwxgetmsgimg?MsgID=%s&skey=%s' % (msgid, self.skey) - data = self._get(url) + data = self._get(url, 'webwxgetmsgimg') if data == '': return '' fn = 'img_' + msgid + '.jpg' @@ -1104,7 +1103,7 @@ class WebWeixin(object): request.add_header('Range', 'bytes=0-') try: response = urllib.request.urlopen(request, timeout=timeout) if timeout else urllib.request.urlopen(request) - if api == 'webwxgetvoice' or api == 'webwxgetvideo': + if api == 'webwxgetvoice' or api == 'webwxgetvideo' or api == 'webwxgetmsgimg': data = response.read() else: data = response.read().decode('utf-8') @@ -1128,7 +1127,7 @@ class WebWeixin(object): def _post(self, url: object, params: object, jsonfmt: object = True) -> object: if jsonfmt: data = (json.dumps(params)).encode() - + request = urllib.request.Request(url=url, data=data) request.add_header( 'ContentType', 'application/json; charset=UTF-8') @@ -1204,16 +1203,3 @@ class UnicodeStreamFilter: def flush(self): self.target.flush() - -if sys.stdout.encoding == 'cp936': - sys.stdout = UnicodeStreamFilter(sys.stdout) - - -if __name__ == '__main__': - logger = logging.getLogger(__name__) - if not sys.platform.startswith('win'): - import coloredlogs - coloredlogs.install(level='DEBUG') - - webwx = WebWeixin() - webwx.start() diff --git a/wxbot.pdman.json b/wxbot.pdman.json new file mode 100644 index 0000000..e2507c8 --- /dev/null +++ b/wxbot.pdman.json @@ -0,0 +1,367 @@ +{ + "modules": [ + { + "name": "core", + "chnname": "NOTA核心", + "entities": [ + { + "title": "msg", + "fields": [ + { + "name": "msg_id", + "type": "TEXT", + "remark": "", + "chnname": "消息ID", + "notNull": true, + "pk": false, + "autoIncrement": false + }, + { + "name": "create_time", + "type": "DATETIME", + "remark": "Unix时间戳", + "chnname": "创建时间", + "notNull": true, + "pk": false + }, + { + "name": "msg_type", + "type": "ENUM_INTEGER", + "remark": "0,不受支持的消息,以文本形式存储\n1,文本消息\n3,图片消息\n34,语音消息\n43,视频消息", + "chnname": "消息类型", + "notNull": true + }, + { + "name": "content", + "type": "TEXT", + "remark": "", + "chnname": "消息内容" + }, + { + "name": "from_id", + "type": "TEXT_ID", + "remark": "", + "chnname": "发送者ID" + }, + { + "name": "from_name", + "type": "TEXT", + "remark": "备注或昵称", + "chnname": "发送者" + }, + { + "name": "from_nickname", + "type": "TEXT", + "remark": "昵称", + "chnname": "发送者昵称" + }, + { + "name": "to_id", + "type": "TEXT_ID", + "remark": "", + "chnname": "接收者ID" + }, + { + "name": "to_name", + "type": "TEXT", + "remark": "", + "chnname": "接收者" + }, + { + "name": "to_nickname", + "type": "TEXT", + "remark": "", + "chnname": "接收者昵称" + }, + { + "name": "group_id", + "type": "TEXT_ID", + "remark": "", + "chnname": "群组ID" + }, + { + "name": "group_name", + "type": "TEXT", + "remark": "", + "chnname": "群组名称" + } + ], + "indexs": [], + "headers": [ + { + "fieldName": "chnname", + "relationNoShow": false + }, + { + "fieldName": "name", + "relationNoShow": false + }, + { + "fieldName": "type", + "relationNoShow": false + }, + { + "fieldName": "dataType", + "relationNoShow": true + }, + { + "fieldName": "remark", + "relationNoShow": true + }, + { + "fieldName": "pk", + "relationNoShow": false + }, + { + "fieldName": "notNull", + "relationNoShow": true + }, + { + "fieldName": "autoIncrement", + "relationNoShow": true + }, + { + "fieldName": "defaultValue", + "relationNoShow": true + }, + { + "fieldName": "relationNoShow", + "relationNoShow": true + }, + { + "fieldName": "uiHint", + "relationNoShow": true + } + ], + "chnname": "消息", + "remark": "" + } + ], + "graphCanvas": { + "nodes": [ + { + "shape": "table", + "title": "label", + "moduleName": false, + "x": 840, + "y": 640, + "id": "d140666b" + }, + { + "shape": "table", + "title": "article", + "moduleName": false, + "x": 180, + "y": 285.7142857142857, + "id": "59f92335" + }, + { + "shape": "table", + "title": "label_group", + "moduleName": false, + "x": 1200, + "y": 650, + "id": "b41dde04" + }, + { + "shape": "table", + "title": "msg", + "moduleName": false, + "x": 840, + "y": 460, + "id": "a3e008eb" + }, + { + "shape": "table", + "title": "user_role", + "moduleName": false, + "x": 840, + "y": 300, + "id": "9e383a0c" + }, + { + "shape": "table", + "title": "article_field", + "moduleName": false, + "x": 180, + "y": 570, + "id": "31a11be8" + }, + { + "shape": "table", + "title": "article_edition", + "moduleName": false, + "x": 520, + "y": 350, + "id": "ff924bfe" + } + ], + "edges": [] + }, + "associations": [] + } + ], + "dataTypeDomains": { + "datatype": [ + { + "name": "文本", + "code": "TEXT", + "apply": { + "Sqlite": { + "type": "TEXT" + } + } + }, + { + "name": "整数", + "code": "INTEGER", + "apply": { + "Sqlite": { + "type": "INTEGER" + } + } + }, + { + "name": "实数", + "code": "REAL", + "apply": { + "Sqlite": { + "type": "REAL" + } + } + }, + { + "name": "日期", + "code": "DATE", + "apply": { + "Sqlite": { + "type": "INTEGER" + } + } + }, + { + "name": "日期时间", + "code": "DATETIME", + "apply": { + "Sqlite": { + "type": "INTEGER" + } + } + }, + { + "name": "布尔值", + "code": "BOOLEAN", + "apply": { + "Sqlite": { + "type": "INTEGER" + } + } + }, + { + "name": "文本枚举", + "code": "ENUM_TEXT", + "apply": { + "Sqlite": { + "type": "TEXT" + } + } + }, + { + "name": "整数枚举", + "code": "ENUM_INTEGER", + "apply": { + "Sqlite": { + "type": "INTEGER" + } + } + }, + { + "name": "整数主键", + "apply": { + "Sqlite": { + "type": "INTEGER" + } + }, + "code": "INTEGER_PK" + }, + { + "name": "文本主键", + "code": "TEXT_PK", + "apply": { + "Sqlite": { + "type": "TEXT" + } + } + }, + { + "name": "整数唯一", + "code": "INTEGER_U", + "apply": { + "Sqlite": { + "type": "INTEGER UNIQUE" + } + } + }, + { + "name": "文本唯一", + "code": "TEXT_U", + "apply": { + "Sqlite": { + "type": "TEXT UNIQUE" + } + } + }, + { + "name": "整数标志符", + "code": "INTEGER_ID", + "apply": { + "Sqlite": { + "type": "INTEGER" + } + } + }, + { + "name": "文本标识符", + "code": "TEXT_ID", + "apply": { + "Sqlite": { + "type": "TEXT" + } + } + }, + { + "name": "二进制数据", + "code": "BLOB", + "apply": { + "Sqlite": { + "type": "BLOB" + } + } + } + ], + "database": [ + { + "code": "Sqlite", + "defaultDatabase": true, + "fileShow": true, + "createTableTemplate": "CREATE TABLE {{=it.entity.title}} (\n{{ pkList = [] ; }}\n{{~ it.entity.fields:field:index }}\n {{? field.pk }}{{ pkList.push(field) }}{{?}}\n {{=field.name}} {{=field.type}}{{= field.notNull ? ' NOT NULL' : '' }}{{= field.defaultValue ? it.func.join(' DEFAULT', field.defaultValue, ' ') : '' }}{{= index < it.entity.fields.length - 1 ? ',' : (pkList.length > 0 ? ',' : '') }}\n{{~}}\n{{? pkList.length > 0 }}\n PRIMARY KEY ({{~ pkList:pkField:i }}{{= pkField.name }}{{= pkField.autoIncrement ? ' AUTOINCREMENT' : '' }}{{= i < pkList.length - 1 ? ',' : '' }}{{~}})\n{{?}}\n){{=it.separator}}\n$blankline\n", + "deleteTableTemplate": "DROP TABLE {{=it.entity.title}}{{=it.separator}}", + "rebuildTableTemplate": "", + "createFieldTemplate": "ALTER TABLE {{=it.entity.title}} ADD {{=it.field.name}} {{=it.field.type}} {{? it.field.notNull}}NOT NULL{{?}} {{? it.field.defaultValue}}DEFAULT {{? null==it.field.defaultValue}}NULL{{??}}'{{=it.field.defaultValue}}'{{?}}{{?}} {{? it.field.pk}}PRIMARY KEY{{?}}{{=it.separator}}", + "updateFieldTemplate": "", + "deleteFieldTemplate": "", + "deleteIndexTemplate": "DROP INDEX {{=it.index.name}}{{=it.separator}}", + "createIndexTemplate": "CREATE{{? it.index.isUnique }} UNIQUE{{?}} INDEX {{=it.index.name}} ON {{=it.entity.title}} ({{=it.func.join(...it.index.fields,', ') }}){{=it.separator}}\n\n", + "updateTableComment": "" + } + ] + }, + "profile": { + "defaultFields": [], + "defaultFieldsType": "1", + "javaConfig": {}, + "sqlConfig": ";", + "dbs": [], + "wordTemplateConfig": "" + } +} \ No newline at end of file diff --git a/wxbot_project_py2.7/README.md b/wxbot_project_py2.7/README.md deleted file mode 100644 index 85814c2..0000000 --- a/wxbot_project_py2.7/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# wxbot_project_py2.7 - -目录结构: -```bash -. -├── README.md -├── config -│   ├── __init__.py -│   ├── config_manager.py -│   ├── constant.py -│   ├── log.py -│   ├── requirements.txt -│   └── wechat.conf.bak -├── db -│   ├── __init__.py -│   ├── mysql_db.py -│   └── sqlite_db.py -├── docker -│   ├── Dockerfile -│   └── README.md -├── flask_templates -│   ├── index.html -│   └── upload.html -├── wechat -│   ├── __init__.py -│   ├── utils.py -│   ├── wechat_apis.py -│   └── wechat.py -├── weixin_bot.py -└── wx_handler - ├── __init__.py - ├── bot.py - ├── sendgrid_mail.py - └── wechat_msg_processor.py -``` diff --git a/wxbot_project_py2.7/config/__init__.py b/wxbot_project_py2.7/config/__init__.py deleted file mode 100644 index 8cd6be0..0000000 --- a/wxbot_project_py2.7/config/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -from config_manager import ConfigManager -from constant import Constant -from log import Log \ No newline at end of file diff --git a/wxbot_project_py2.7/config/config_manager.py b/wxbot_project_py2.7/config/config_manager.py deleted file mode 100644 index 92a6fe8..0000000 --- a/wxbot_project_py2.7/config/config_manager.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from constant import Constant -#--------------------------------------------------- -import ConfigParser -import os -#=================================================== - - -class ConfigManager(object): - - def __init__(self): - self.config = Constant.WECHAT_CONFIG_FILE - self.cp = ConfigParser.ConfigParser() - self.cp.read(self.config) - - data_dir = self.get('setting', 'prefix') - upload_dir = self.getpath('uploaddir') - if not os.path.exists(data_dir): - os.makedirs(data_dir) - if not os.path.exists(upload_dir): - os.makedirs(upload_dir) - - def get(self, section, option): - return self.cp.get(section, option) - - def set(self, section, option, value): - self.cp.set(section, option, value) - self.cp.write(open(self.config, 'w')) - - def getpath(self, dir): - prefix = self.get('setting', 'prefix') - return prefix + self.get('setting', dir) - - def setup_database(self): - path = self.get('setting', 'prefix') - conf = [ - path + self.get('setting', 'uploaddir'), - path + self.get('setting', 'datadir'), - path + self.get('setting', 'logdir'), - ] - return conf - - def set_wechat_config(self, conf): - for [key, value] in conf.items(): - self.cp.set('wechat', key, value) - self.cp.write(open(self.config, 'w')) - - def get_wechat_config(self): - uin = self.cp.get('wechat', 'uin') - last_login = self.cp.get('wechat', 'last_login') - conf = [ - self.cp.get('wechat', 'uuid'), - self.cp.get('wechat', 'redirect_uri'), - int(uin if uin else 0), - self.cp.get('wechat', 'sid'), - self.cp.get('wechat', 'skey'), - self.cp.get('wechat', 'pass_ticket'), - self.cp.get('wechat', 'synckey'), - self.cp.get('wechat', 'device_id'), - float(last_login if last_login else 0), - ] - return conf - - def get_wechat_media_dir(self): - prefix = self.get('setting', 'prefix') - path = prefix + self.cp.get('setting', 'mediapath') - return { - 'webwxgeticon': path + '/icons', - 'webwxgetheadimg': path + '/headimgs', - 'webwxgetmsgimg': path + '/msgimgs', - 'webwxgetvideo': path + '/videos', - 'webwxgetvoice': path + '/voices', - '_showQRCodeImg': path + '/qrcodes', - } - - def get_pickle_files(self): - prefix = self.get('setting', 'prefix') - return { - 'User': prefix + self.get('setting', 'contact_user'), - 'MemberList': prefix + self.get('setting', 'contact_member_list'), - 'GroupList': prefix + self.get('setting', 'contact_group_list'), - 'GroupMemeberList': prefix + self.get('setting', 'contact_group_memeber_list'), - 'SpecialUsersList': prefix + self.get('setting', 'contact_special_users_list'), - } - - def get_cookie(self): - prefix = self.get('setting', 'prefix') - path = prefix + self.get('setting', 'cookie') - basedir = os.path.dirname(path) - if not os.path.exists(basedir): - os.makedirs(basedir) - return path - - def mysql(self): - mysql = { - 'host': self.get('mysql', 'host'), - 'port': self.cp.getint('mysql', 'port'), - 'user': self.get('mysql', 'user'), - 'passwd': self.get('mysql', 'passwd'), - 'database': self.get('mysql', 'database'), - } - return mysql diff --git a/wxbot_project_py2.7/config/constant.py b/wxbot_project_py2.7/config/constant.py deleted file mode 100644 index 30f3839..0000000 --- a/wxbot_project_py2.7/config/constant.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -import time - -class Constant(object): - """ - @brief All used constants are listed here - """ - - WECHAT_CONFIG_FILE = 'config/wechat.conf' - LOGGING_LOGGER_NAME = 'WeChat' - - QRCODE_BLACK = '\033[40m \033[0m' - QRCODE_WHITE = '\033[47m \033[0m' - - HTTP_HEADER_USERAGENT = [('User-agent', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.109 Safari/537.36')] - HTTP_HEADER_CONTENTTYPE = ['ContentType', 'application/json; charset=UTF-8'] - HTTP_HEADER_CONNECTION = ['Connection', 'keep-alive'] - HTTP_HEADER_REFERER = ['Referer', 'https://wx.qq.com/'] - HTTP_HEADER_RANGE = ['Range', 'bytes=0-'] - - REGEX_EMOJI = r'' - - SERVER_LOG_FORMAT = '%(asctime)s - %(pathname)s:%(lineno)d - %(name)s - %(levelname)s - %(message)s' - SERVER_UPLOAD_ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif']) - SERVER_PAGE_UPLOAD = 'upload.html' - SERVER_PAGE_INDEX = 'index.html' - - RUN_RESULT_SUCCESS = '成功 %ds\n' - RUN_RESULT_FAIL = '失败\n[*] 退出程序\n' - MAIN_RESTART = '[*] wait for restart' - LOG_MSG_FILE = 'WeChat-Msgs-%Y-%m-%d.json' - LOG_MSG_GROUP_LIST_FILE = 'group_list.json' - LOG_MSG_QUIT = '\n[*] Force quit.\n' - LOG_MSG_FAIL = '失败\n' - LOG_MSG_SUCCESS = '成功\n' - LOG_MSG_START = '[*] 微信网页版 ... 开动\n' - LOG_MSG_RECOVER = '[*] 从配置文件中恢复 ... ' - LOG_MSG_RECOVER_CONTACT = '[*] 从文件中恢复联系人数据 ... ' - LOG_MSG_TRY_INIT = '[*] 尝试初始化 ... ' - LOG_MSG_ASSOCIATION_LOGIN = '[*] 通过关联登录 ... ' - LOG_MSG_GET_UUID = '[*] 正在获取 uuid ... ' - LOG_MSG_GET_QRCODE = '[*] 正在获取二维码 ... 成功\n' - LOG_MSG_SCAN_QRCODE = '[*] 请使用微信扫描二维码以登录 ... \n' - LOG_MSG_CONFIRM_LOGIN = '[*] 请在手机上点击确认以登录 ... \n' - LOG_MSG_WAIT_LOGIN_ERR1 = '[登陆超时] \n' - LOG_MSG_WAIT_LOGIN_ERR2 = '[登陆异常] \n' - LOG_MSG_LOGIN = '[*] 正在登录 ... ' - LOG_MSG_INIT = '[*] 微信初始化 ... ' - LOG_MSG_STATUS_NOTIFY = '[*] 开启状态通知 ... ' - LOG_MSG_GET_CONTACT = '[*] 获取联系人 ... ' - LOG_MSG_CONTACT_COUNT = '[*] 应有 %s 个联系人,读取到联系人 %d 个\n' - LOG_MSG_OTHER_CONTACT_COUNT = '[*] 共有 %d 个群 | %d 个直接联系人 | %d 个特殊账号 | %d 公众号或服务号\n' - LOG_MSG_GET_GROUP_MEMBER = '[*] 拉取群聊成员 ... ' - LOG_MSG_SNAPSHOT = '[*] 保存配置 ... ' - LOG_MSG_LOGOUT = '[*] 你在手机上登出了微信\n' - LOG_MSG_LOGIN_OTHERWHERE = '[*] 你在其他地方登录了 WEB 版微信\n' - LOG_MSG_QUIT_ON_PHONE = '[*] 你在手机上主动退出了\n' - LOG_MSG_RUNTIME = '[*] Total run: %s\n' - LOG_MSG_KILL_PROCESS = 'kill %d' - LOG_MSG_NEW_MSG = '>>> %d 条新消息\n' - LOG_MSG_LOCATION = '[位置] %s' - LOG_MSG_PICTURE = '[图片] %s' - LOG_MSG_VOICE = '[语音] %s' - LOG_MSG_RECALL = '撤回了一条消息' - LOG_MSG_ADD_FRIEND = '%s 请求添加你为好友' - LOG_MSG_UNKNOWN_MSG = '[*] 该消息类型为: %d,内容: %s' - LOG_MSG_VIDEO = '[小视频] %s' - LOG_MSG_NOTIFY_PHONE = '[*] 提示手机网页版微信登录状态\n' - LOG_MSG_EMOTION = '[表情] %s' - LOG_MSG_NAME_CARD = ( - '[名片]\n' - '=========================\n' - '= 昵称: %s\n' - '= 微信号: %s\n' - '= 地区: %s %s\n' - '= 性别: %s\n' - '=========================' - ) - LOG_MSG_SEX_OPTION = ['未知', '男', '女'] - LOG_MSG_APP_LINK = ( - '[%s]\n' - '=========================\n' - '= 标题: %s\n' - '= 描述: %s\n' - '= 链接: %s\n' - '= 来自: %s\n' - '=========================' - ) - LOG_MSG_APP_LINK_TYPE = {5: '链接', 3: '音乐', 7: '微博'} - LOG_MSG_APP_IMG = ( - '[图片]\n' - '=========================\n' - '= 文件: %s\n' - '= 来自: %s\n' - '=========================' - ) - LOG_MSG_SYSTEM = '系统消息' - LOG_MSG_UNKNOWN_NAME = '未知_' - LOG_MSG_UNKNOWN_GROUP_NAME = '未知群_' - - TABLE_GROUP_MSG_LOG = 'WeChatRoomMessage' - TABLE_GROUP_MSG_LOG_COL = """ - MsgID text, - RoomOwnerID text, - RoomName text, - UserCount text, - FromUserName text, - ToUserName text, - AttrStatus text, - DisplayName text, - Name text, - MsgType text, - FaceMsg text, - TextMsg text, - ImageMsg text, - VideoMsg text, - SoundMsg text, - LinkMsg text, - NameCardMsg text, - LocationMsg text, - RecallMsgID text, - SysMsg text, - MsgTime text, - MsgTimestamp text - """ - - @staticmethod - def TABLE_GROUP_LIST(): - return 'WeChatRoom_' + time.strftime('%Y%m%d', time.localtime()) - - TABLE_GROUP_LIST_COL = """ - RoomName text, - RoomID text, - RoomOwnerID text, - UserCount text, - RoomIcon text - """ - - @staticmethod - def TABLE_GROUP_USER_LIST(): - return 'WeChatRoomMember_' + time.strftime('%Y%m%d', time.localtime()) - - TABLE_GROUP_USER_LIST_COL = """ - RoomID text, - MemberID text, - MemberNickName text, - MemberDisplayName text, - MemberAttrStatus text - """ - TABLE_RECORD_ENTER_GROUP = 'WeChatEnterGroupRecord' - TABLE_RECORD_ENTER_GROUP_COL = """ - MsgID text, - RoomName text, - FromUserName text, - ToUserName text, - Name text, - EnterTime text - """ - TABLE_RECORD_RENAME_GROUP = 'WeChatRenameGroupRecord' - TABLE_RECORD_RENAME_GROUP_COL = """ - MsgID text, - FromName text, - ToName text, - ModifyPeople text, - ModifyTime text - """ - - API_APPID = 'wx782c26e4c19acffb' - API_WXAPPID = 'wx299208e619de7026' # Weibo - # 'wxeb7ec651dd0aefa9' # Weixin - API_LANG = 'zh_CN' - API_USER_AGENT = ( - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) ' - 'AppleWebKit/537.36 (KHTML, like Gecko) ' - 'Chrome/48.0.2564.109 Safari/537.36' - ) - API_SPECIAL_USER = [ - 'newsapp', 'filehelper', 'weibo', 'qqmail', - 'fmessage', 'tmessage', 'qmessage', 'qqsync', - 'floatbottle', 'lbsapp', 'shakeapp', 'medianote', - 'qqfriend', 'readerapp', 'blogapp', 'facebookapp', - 'masssendapp', 'meishiapp', 'feedsapp', 'voip', - 'blogappweixin', 'brandsessionholder', 'weixin', - 'weixinreminder', 'officialaccounts', 'wxitil', - 'notification_messages', 'wxid_novlwrv3lqwv11', - 'gh_22b87fa7cb3c', 'userexperience_alarm', - ] - - EMOTICON = [ - '[Smile]', '[Grimace]', '[Drool]', '[Scowl]', '[CoolGuy]', '[Sob]', '[Shy]', - '[Silent]', '[Sleep]', '[Cry]', '[Awkward]', '[Angry]', '[Tongue]', '[Grin]', - '[Surprise]', '[Frown]', '[Ruthless]', '[Blush]', '[Scream]', '[Puke]', - '[Chuckle]', '[Joyful]', '[Slight]', '[Smug]', '[Hungry]', '[Drowsy]', '[Panic]', - '[Sweat]', '[Laugh]', '[Commando]', '[Determined]', '[Scold]', '[Shocked]', '[Shhh]', - '[Dizzy]', '[Tormented]', '[Toasted]', '[Skull]', '[Hammer]', '[Wave]', - '[Relief]', '[DigNose]', '[Clap]', '[Shame]', '[Trick]',' [Bah!L]','[Bah!R]', - '[Yawn]', '[Lookdown]', '[Wronged]', '[Puling]', '[Sly]', '[Kiss]', '[Uh-oh]', - '[Whimper]', '[Cleaver]', '[Melon]', '[Beer]', '[Basketball]', '[PingPong]', - '[Coffee]', '[Rice]', '[Pig]', '[Rose]', '[Wilt]', '[Lip]', '[Heart]', - '[BrokenHeart]', '[Cake]', '[Lightning]', '[Bomb]', '[Dagger]', '[Soccer]', '[Ladybug]', - '[Poop]', '[Moon]', '[Sun]', '[Gift]', '[Hug]', '[Strong]', - '[Weak]', '[Shake]', '[Victory]', '[Admire]', '[Beckon]', '[Fist]', '[Pinky]', - '[Love]', '[No]', '[OK]', '[InLove]', '[Blowkiss]', '[Waddle]', '[Tremble]', - '[Aaagh!]', '[Twirl]', '[Kotow]', '[Lookback]', '[Jump]', '[Give-in]', - u'\U0001f604', u'\U0001f637', u'\U0001f639', u'\U0001f61d', u'\U0001f632', u'\U0001f633', - u'\U0001f631', u'\U0001f64d', u'\U0001f609', u'\U0001f60c', u'\U0001f612', u'\U0001f47f', - u'\U0001f47b', u'\U0001f49d', u'\U0001f64f', u'\U0001f4aa', u'\U0001f4b5', u'\U0001f382', - u'\U0001f388', u'\U0001f4e6', - ] - BOT_ZHIHU_URL_LATEST = 'http://news-at.zhihu.com/api/4/news/latest' - BOT_ZHIHU_URL_DAILY = 'http://daily.zhihu.com/story/' - BOT_TULING_API_KEY = '55e7f30895a0a10535984bae5ad294d1' - BOT_TULING_API_URL = 'http://www.tuling123.com/openapi/api?key=%s&info=%s&userid=%s' - BOT_TULING_BOT_REPLY = u'麻烦说的清楚一点,我听不懂你在说什么' diff --git a/wxbot_project_py2.7/config/log.py b/wxbot_project_py2.7/config/log.py deleted file mode 100644 index eac4933..0000000 --- a/wxbot_project_py2.7/config/log.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from constant import Constant -from config import ConfigManager -#--------------------------------------------------- -import logging -import logging.config -#=================================================== - -cm = ConfigManager() - -logging.config.fileConfig(Constant.WECHAT_CONFIG_FILE) -# create logger -Log = logging.getLogger(Constant.LOGGING_LOGGER_NAME) - -# 'application' code -# Log.debug('debug message') -# Log.info('info message') -# Log.warn('warn message') -# Log.error('error message') -# Log.critical('critical message') diff --git a/wxbot_project_py2.7/config/requirements.txt b/wxbot_project_py2.7/config/requirements.txt deleted file mode 100644 index a0597d7..0000000 --- a/wxbot_project_py2.7/config/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -qrcode -flask -requests -requests_toolbelt -pymysql -sendgrid diff --git a/wxbot_project_py2.7/config/wechat.conf.bak b/wxbot_project_py2.7/config/wechat.conf.bak deleted file mode 100644 index 31ac260..0000000 --- a/wxbot_project_py2.7/config/wechat.conf.bak +++ /dev/null @@ -1,77 +0,0 @@ -[wechat] -host = wx.qq.com -uuid = -redirect_uri = -uin = -sid = -skey = -pass_ticket = -device_id = -last_login = - -[setting] -prefix = tmp_data/ -database = WeChat.db -datadir = Data/infos/ -logdir = Logs -mediapath = Data -uploaddir = Data/upload -qrcodedir = Data/qrcode -server_port = 8080 -cookie = Cookie/WeChat.cookie -contact_user = Pickle/User.pkl -contact_member_list = Pickle/MemberList.pkl -contact_group_list = Pickle/GroupList.pkl -contact_group_memeber_list = Pickle/GroupMemeberList.pkl -contact_special_users_list = Pickle/SpecialUsersList.pkl -server_mode = False -server_log_file = server.log -log_mode = False - -[mysql] -host = localhost -port = 3306 -user = root -passwd = root -database = wechat - -[sendgrid] -api_key = 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 -from_email = wxbot@wechat.com -to_email = xxx@example.com - -[loggers] -keys = root,WeChat - -[handlers] -keys = consoleHandler,fileHandler - -[formatters] -keys = simpleFormatter - -[logger_root] -level = DEBUG -handlers = consoleHandler - -[logger_WeChat] -level = DEBUG -handlers = fileHandler -qualname = WeChat -propagate = 0 - -[handler_consoleHandler] -class = StreamHandler -level = DEBUG -formatter = simpleFormatter -args = (sys.stdout,) - -[handler_fileHandler] -class = FileHandler -level = DEBUG -formatter = simpleFormatter -args = ('tmp_data/wechat.log',) - -[formatter_simpleFormatter] -format = %(asctime)s - %(name)s - %(levelname)s - %(message)s -datefmt = - diff --git a/wxbot_project_py2.7/db/__init__.py b/wxbot_project_py2.7/db/__init__.py deleted file mode 100644 index 5d5ff9a..0000000 --- a/wxbot_project_py2.7/db/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -from sqlite_db import SqliteDB -from mysql_db import MysqlDB \ No newline at end of file diff --git a/wxbot_project_py2.7/db/mysql_db.py b/wxbot_project_py2.7/db/mysql_db.py deleted file mode 100644 index 30df872..0000000 --- a/wxbot_project_py2.7/db/mysql_db.py +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from config import Log -#--------------------------------------------------- -import pymysql -import threading -import traceback -#=================================================== - -def array_join(arr, c): - t = '' - for a in arr: - t += "'%s'" % str(a).replace("'","\\\'") + c - return t[:-len(c)] - -class MysqlDB(object): - """ - 修改服务器上的配置文件/etc/my.cnf,在对应位置添加以下设置: - [client] - default-character-set = utf8mb4 - - [mysql] - default-character-set = utf8mb4 - - [mysqld] - character-set-client-handshake = FALSE - character-set-server = utf8mb4 - collation-server = utf8mb4_unicode_ci - init_connect='SET NAMES utf8mb4' - """ - - def __init__(self, conf): - self.conf = conf - config = { - 'host': conf['host'], - 'port': conf['port'], - 'user': conf['user'], - 'passwd': conf['passwd'], - 'charset':'utf8mb4', # 支持1-4个字节字符 - 'cursorclass': pymysql.cursors.DictCursor - } - self.conn = pymysql.connect(**config) - self.conn.autocommit(1) - # for thread-save - self.lock = threading.Lock() - - self.create_db(conf['database']) - self.conn.select_db(conf['database']) - - # cache table cols - self.table_cols = {} - for t in self.show_tables(): - self.table_cols[t] = self.get_table_column_name(t) - - def show_database(self): - c = self.conn.cursor() - sql = 'SHOW DATABASES' - Log.debug('DB -> %s' % sql) - c.execute(sql) - return [r['Database'] for r in c.fetchall()] - - def show_tables(self): - c = self.conn.cursor() - sql = 'SHOW TABLES' - Log.debug('DB -> %s' % sql) - c.execute(sql) - return [r['Tables_in_'+self.conf['database']] for r in c.fetchall()] - - def create_db(self, db_name): - """ - @brief Creates a database - @param db_name String - """ - if self.conf['database'] not in self.show_database(): - sql = 'CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' % db_name - Log.debug('DB -> %s' % sql) - self.execute(sql) - - def create_table(self, table, cols): - """ - @brief Creates a table in database - @param table String - @param cols String, the cols in table - """ - if table not in self.table_cols: - sql = 'CREATE TABLE IF NOT EXISTS %s(id int primary key auto_increment, %s) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' % (table, cols) - Log.debug('DB -> %s' % sql) - self.execute(sql) - self.table_cols[table] = ['id'] + [c.strip().split(' ')[0] for c in cols.split(',')] - - def delete_table(self, table): - """ - @brief Delete a table in database - @param table String - """ - if table in self.table_cols: - sql = "DROP TABLE IF EXISTS %s" % table - Log.debug('DB -> %s' % sql) - self.execute(sql) - self.table_cols.pop(table) - - def insert(self, table, value): - """ - @brief Insert a row in table - @param table String - @param value Tuple - """ - col_name = self.table_cols[table][1:] - sql = "INSERT INTO %s(%s) VALUES (%s)" % (table, str(','.join(col_name)), array_join(value, ',')) - Log.debug('DB -> %s' % sql) - self.execute(sql) - - def insertmany(self, table, values): - """ - @brief Insert many rows in table - @param table String - @param values Array of tuple - """ - col_name = self.table_cols[table][1:] - sql = 'INSERT INTO %s(%s) VALUES (%s)' % (table, ','.join(col_name), ','.join(['%s'] * len(values[0]))) - Log.debug('DB -> %s' % sql) - self.execute(sql, values) - - def select(self, table, field='', condition=''): - """ - @brief select all result from table - @param table String - @param field String - @param condition String - @return result Tuple - """ - sql = "SELECT * FROM %s" % table - if field and condition: - sql += " WHERE %s='%s'" % (field, condition) - Log.debug('DB -> %s' % sql) - return self.execute(sql) - - def get_table_column_name(self, table): - """ - @brief select all result from table - @param table String - @return result Array - """ - c = self.conn.cursor() - c.execute("SELECT * FROM %s" % table) - names = list(map(lambda x: x[0], c.description)) - return names - - def execute(self, sql, values=None): - """ - @brief execute sql commands, return result if it has - @param sql String - @param value Tuple - @return result Array - """ - c = self.conn.cursor() - self.lock.acquire() - hasReturn = sql.lstrip().upper().startswith("SELECT") - - result = [] - try: - if values: - c.executemany(sql, values) - else: - c.execute(sql) - - if hasReturn: - result = c.fetchall() - - except Exception, e: - Log.error(traceback.format_exc()) - self.conn.rollback() - finally: - self.lock.release() - - if hasReturn: - return result - - def delete(self, table, field='', condition=''): - """ - @brief execute sql commands, return result if it has - @param table String - @param field String - @param condition String - """ - sql = "DELETE FROM %s WHERE %s=%s" % (table, field, condition) - Log.debug('DB -> %s' % sql) - self.execute(sql) - - def close(self): - """ - @brief close connection to database - """ - Log.debug('DB -> close') - # 关闭数据库连接 - self.conn.close() - - \ No newline at end of file diff --git a/wxbot_project_py2.7/db/sqlite_db.py b/wxbot_project_py2.7/db/sqlite_db.py deleted file mode 100644 index b953721..0000000 --- a/wxbot_project_py2.7/db/sqlite_db.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from config import Log -#--------------------------------------------------- -import sqlite3 -import threading -import traceback -#=================================================== - - -def _dict_factory(cursor, row): - aDict = {} - for iField, field in enumerate (cursor.description): - aDict [field [0]] = row [iField] - return aDict - - -class SqliteDB(object): - - def __init__(self, db_file): - self.db_file = db_file - # self.conn = sqlite3.connect(db_file, check_same_thread=False) - # use 8-bit strings instead of unicode string - self.conn.text_factory = str - # not return a tuple but a dict with column name as key - self.conn.row_factory = _dict_factory - # for thread-save - self.lock = threading.Lock() - - def set_conn(self): - self._conn = sqlite3.connect(self.db_file,check_same_thread=False) - - @property - def conn(self): - try: - self._conn.execute('select 1;') - # check out conn - except (sqlite3.ProgrammingError,AttributeError): - # Cannot operate on a closed database - self.set_conn() - - finally: - return self._conn - - - def create_table(self, table, cols): - """ - @brief Creates a table in database - @param table String - @param cols String, the cols in table - """ - sql = "CREATE TABLE if not exists %s (%s);" % (table, cols) - Log.debug('DB -> %s' % sql) - self.execute(sql) - - def delete_table(self, table): - """ - @brief Delete a table in database - @param table String - """ - sql = "DROP TABLE if exists %s;" % table - Log.debug('DB -> %s' % sql) - self.execute(sql) - - def insert(self, table, value): - """ - @brief Insert a row in table - @param table String - @param value Tuple - """ - sql = ("INSERT INTO %s VALUES (" + ",".join(['?'] * len(value)) + ");") % table - Log.debug('DB -> %s' % sql) - self.execute(sql, value) - - def insertmany(self, table, values): - """ - @brief Insert many rows in table - @param table String - @param values Array of tuple - """ - c = self.conn.cursor() - self.lock.acquire() - n = len(values[0]) - sql = ("INSERT INTO %s VALUES (" + ",".join(['?'] * n) + ");") % table - Log.debug('DB -> %s' % sql) - - try: - c.executemany(sql, values) - except Exception, e: - Log.error(traceback.format_exc()) - finally: - self.lock.release() - - self.conn.commit() - - def select(self, table, field='', condition=''): - """ - @brief select all result from table - @param table String - @param field String - @param condition String - @return result Tuple - """ - result = [] - if field and condition: - cond = (condition,) - sql = "SELECT * FROM %s WHERE %s=?" % (table, field) - Log.debug('DB -> %s' % sql) - result = self.execute(sql, cond) - else: - sql = "SELECT * FROM %s" % table - Log.debug('DB -> %s' % sql) - result = self.execute(sql) - return result - - def update(self, table, dic, condition=''): - k_arr = [] - v_arr = [] - for (k, v) in dic.items(): - k_arr.append('%s=?' % k) - v_arr.append(v) - - sql = "UPDATE %s SET %s" % (table, ','.join(k_arr)) - if condition: - sql += " WHERE %s" % condition - - Log.debug('DB -> %s' % sql) - self.execute(sql, tuple(v_arr)) - - def get_table_column_name(self, table): - """ - @brief select all result from table - @param table String - @return result Array - """ - c = self.conn.cursor() - c.execute("SELECT * FROM %s" % table) - names = list(map(lambda x: x[0], c.description)) - return names - - def execute(self, sql, value=None): - """ - @brief execute sql commands, return result if it has - @param sql String - @param value Tuple - @return result Array - """ - c = self.conn.cursor() - self.lock.acquire() - hasReturn = sql.lstrip().upper().startswith("SELECT") - - try: - if value: - c.execute(sql, value) - else: - c.execute(sql) - - if hasReturn: - result = c.fetchall() - except Exception, e: - Log.error(traceback.format_exc()) - finally: - self.lock.release() - - self.conn.commit() - - if hasReturn: - return result - - def delete(self, table, field='', condition=''): - """ - @brief execute sql commands, return result if it has - @param table String - @param field String - @param condition String - """ - sql = "DELETE FROM %s WHERE %s=?" % (table, field) - Log.debug('DB -> %s' % sql) - cond = (condition,) - self.execute(sql, cond) - - def close(self): - """ - @brief close connection to database - """ - Log.debug('DB -> close') - self.conn.close() diff --git a/wxbot_project_py2.7/docker/Dockerfile b/wxbot_project_py2.7/docker/Dockerfile deleted file mode 100644 index 0bab668..0000000 --- a/wxbot_project_py2.7/docker/Dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -FROM ubuntu:16.04 -MAINTAINER Urinx - -RUN apt-get update && \ - apt-get install -y python \ - python-dev \ - python-pip && \ - apt-get clean && \ - apt-get autoclean && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -ADD weixin_bot.tar.gz / -WORKDIR /weixin_bot - -RUN pip install -r config/requirements.txt -EXPOSE 80 -ENTRYPOINT ["./weixin_bot.py"] -CMD [""] diff --git a/wxbot_project_py2.7/docker/README.md b/wxbot_project_py2.7/docker/README.md deleted file mode 100644 index 4072aca..0000000 --- a/wxbot_project_py2.7/docker/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# 本地构建 wechat_bot docker 镜像 - -拉下镜像: - -```bash -docker pull ubuntu:16.04 -``` - -打包本项目,将压缩包放到`docker`目录下: - -``` -tar -czf weixin_bot.tar.gz wxbot_project_py2.7/ -``` - -切换到`docker`目录,执行`build`命令: - -```bash -docker build -t wechat-bot . -``` - -导出镜像: - -```bash -docker save wechat-bot > wechat.tar -``` - -导入镜像: - -```bash -docker load < wechat.tar -``` - -运行: - -```bash -docker run -d -P --name xxx -v /src/data/dir:/Wechat_bot/test wechat-bot -``` - -删除镜像: - -```bash -docker rmi -f wechat-bot -``` - -查看log: - -```bash -docker log wechat-bot -``` diff --git a/wxbot_project_py2.7/flask_templates/index.html b/wxbot_project_py2.7/flask_templates/index.html deleted file mode 100644 index 4482b9f..0000000 --- a/wxbot_project_py2.7/flask_templates/index.html +++ /dev/null @@ -1,210 +0,0 @@ - - - - - WeChat Bot Server - - - - -
-
-	#####################
-	# WeChat Bot Server #
-	#####################
-
-
-## APIs
-
-- /qrcode
-  ------------------------
-  @brief      login qrcode
-  @method     get
-  @return     raw image
-  ------------------------
-
-- /runtime
-  -----------------------------
-  @brief      basic info
-  @method     get
-  @return     application/json
-              {
-                'ret': 0,
-                'runtime': '',
-                'total_size': '',
-                'db_size': '',
-                'msg_count': '',
-                'image_count': '',
-                'voice_count': '',
-                'video_count': '',
-              }
-  -----------------------------
-
-- /group_list
-  ------------------------
-  @brief      list groups
-  @method     get
-  ------------------------
-
-- /group_member_list/<g_id>
-  -----------------------------
-  @brief      list group member
-  @method     get
-  @param      g_id String
-  -----------------------------
-
-- /group_chat_log/<g_name>
-  -------------------------------
-  @brief      list group chat log
-  @method     get
-  @param      g_name String
-  -------------------------------
-
-- /upload
-  -------------------------------
-  @brief      upload a file
-  @method     get/post
-  @return     application/json
-              {
-                'ret': 0,
-                'msg': '',
-              }
-  -------------------------------
-
-- /send_msg/<to>/<msg>
-  -------------------------------------------
-  @brief      send message to user or gourp
-  @method     get
-  @param      to: String, user id or group id
-  @param      msg: String, words
-  @return     application/json
-              {
-                'ret': 0,
-              }
-  -------------------------------------------
-
-- /send_img/<to>/<img>
-  -------------------------------------------
-  @brief      send image to user or gourp
-  @method     get
-  @param      to: String, user id or group id
-  @param      img: String, image file name
-  @return     application/json
-              {
-                'ret': 0,
-              }
-  -------------------------------------------
-
-- /send_emot/<to>/<emot>
-  -------------------------------------------
-  @brief      send emotion to user or gourp
-  @method     get
-  @param      to: String, user id or group id
-  @param      emot: String, emotion file name
-  @return     application/json
-              {
-                'ret': 0,
-              }
-  -------------------------------------------
-
-- /send_file/<to>/<file>
-  -------------------------------------------
-  @brief      send emotion to user or gourp
-  @method     get
-  @param      to: String, user id or group id
-  @param      file: String, file name
-  @return     application/json
-              {
-                'ret': 0,
-              }
-  -------------------------------------------
-
-- /mass_send_msg
-  -------------------------------------------
-  @brief      send text to mass users or gourps
-  @method     post
-  @param      application/json
-              {
-                'to_list': [
-                  'group_id',
-                  ...
-                ],
-                'msg': '',
-              }
-  @return     application/json
-              {
-                'ret': 0,
-                'unsend_list': [],
-              }
-  -------------------------------------------
-
-- /mass_send_img
-  -------------------------------------------
-  @brief      send image to mass users or gourps
-  @method     post
-  @param      application/json
-              {
-                'to_list': [
-                  'group_id',
-                  ...
-                ],
-                'msg': '',
-              }
-  @return     application/json
-              {
-                'ret': 0,
-                'unsend_list': [],
-              }
-  -------------------------------------------
-
-- /mass_send_emot
-  -------------------------------------------
-  @brief      send emoticon to mass users or gourps
-  @method     post
-  @param      application/json
-              {
-                'to_list': [
-                  'group_id',
-                  ...
-                ],
-                'msg': '',
-              }
-  @return     application/json
-              {
-                'ret': 0,
-                'unsend_list': [],
-              }
-  -------------------------------------------
-
-- /mass_send_file
-  -------------------------------------------
-  @brief      send file to mass users or gourps
-  @method     post
-  @param      application/json
-              {
-                'to_list': [
-                  'group_id',
-                  ...
-                ],
-                'msg': '',
-              }
-  @return     application/json
-              {
-                'ret': 0,
-                'unsend_list': [],
-              }
-  -------------------------------------------
-
-
- - - - diff --git a/wxbot_project_py2.7/flask_templates/upload.html b/wxbot_project_py2.7/flask_templates/upload.html deleted file mode 100644 index ecb88ba..0000000 --- a/wxbot_project_py2.7/flask_templates/upload.html +++ /dev/null @@ -1,7 +0,0 @@ - -Upload new File -

Upload new File

-
-

- -

\ No newline at end of file diff --git a/wxbot_project_py2.7/wechat/__init__.py b/wxbot_project_py2.7/wechat/__init__.py deleted file mode 100644 index 1877c14..0000000 --- a/wxbot_project_py2.7/wechat/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -from wechat import WeChat \ No newline at end of file diff --git a/wxbot_project_py2.7/wechat/utils.py b/wxbot_project_py2.7/wechat/utils.py deleted file mode 100644 index 63c86bc..0000000 --- a/wxbot_project_py2.7/wechat/utils.py +++ /dev/null @@ -1,365 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from config import Log -from config import Constant -#--------------------------------------------------- -import qrcode -import re -import os -import sys -import json -import urllib -import urllib2 -import cookielib -import cPickle as pickle -import traceback -import time -import hashlib -#=================================================== - - -def _decode_data(data): - """ - @brief decode array or dict to utf-8 - @param data array or dict - @return utf-8 - """ - if isinstance(data, dict): - rv = {} - for key, value in data.iteritems(): - if isinstance(key, unicode): - key = key.encode('utf-8') - rv[key] = _decode_data(value) - return rv - elif isinstance(data, list): - rv = [] - for item in data: - item = _decode_data(item) - rv.append(item) - return rv - elif isinstance(data, unicode): - return data.encode('utf-8') - else: - return data - - -def str2qr_terminal(text): - """ - @brief convert string to qrcode matrix and outprint - @param text The string - """ - Log.debug(text) - qr = qrcode.QRCode() - qr.border = 1 - qr.add_data(text) - mat = qr.get_matrix() - print_qr(mat) - - -def str2qr_image(text, image_path): - """ - @brief convert string to qrcode image & save - @param text The string - @param image_path Save image to the path - """ - qr = qrcode.QRCode() - qr.border = 1 - qr.add_data(text) - qr.make(fit=True) - img = qr.make_image() - img.save(image_path) - - -def print_qr(mat): - for i in mat: - BLACK = Constant.QRCODE_BLACK - WHITE = Constant.QRCODE_WHITE - echo(''.join([BLACK if j else WHITE for j in i])+'\n') - - -def echo(str): - Log.info(str[:-1]) - sys.stdout.write(str) - sys.stdout.flush() - - -def run(str, func, *args): - t = time.time() - echo(str) - r = False - try: - r = func(*args) - except: - Log.error(traceback.format_exc()) - if r: - totalTime = int(time.time() - t) - echo(Constant.RUN_RESULT_SUCCESS % totalTime) - else: - echo(Constant.RUN_RESULT_FAIL) - exit() - - -def get(url, api=None): - """ - @brief http get request - @param url String - @param api wechat api - @return http response - """ - Log.debug('GET -> ' + url) - request = urllib2.Request(url=url) - request.add_header(*Constant.HTTP_HEADER_CONNECTION) - request.add_header(*Constant.HTTP_HEADER_REFERER) - if api in ['webwxgetvoice', 'webwxgetvideo']: - request.add_header(*Constant.HTTP_HEADER_RANGE) - - while True: - try: - response = urllib2.urlopen(request, timeout=30) - data = response.read() - response.close() - if api == None: - Log.debug(data) - return data - except (KeyboardInterrupt, SystemExit): - raise - except: - Log.error(traceback.format_exc()) - - time.sleep(1) - - -def post(url, params, jsonfmt=True): - """ - @brief http post request - @param url String - @param params Dict, post params - @param jsonfmt Bool, whether is json format - @return http response - """ - Log.debug('POST -> '+url) - Log.debug(params) - if jsonfmt: - request = urllib2.Request(url=url, data=json.dumps(params, ensure_ascii=False).encode('utf8')) - request.add_header(*Constant.HTTP_HEADER_CONTENTTYPE) - else: - request = urllib2.Request(url=url, data=urllib.urlencode(params)) - - while True: - try: - response = urllib2.urlopen(request, timeout=30) - data = response.read() - response.close() - - if jsonfmt: - Log.debug(data) - return json.loads(data, object_hook=_decode_data) - return data - except (KeyboardInterrupt, SystemExit): - raise - except: - Log.error(traceback.format_exc()) - - time.sleep(1) - - -def set_cookie(cookie_file): - """ - @brief Load cookie from file - @param cookie_file - @param user_agent - @return cookie, LWPCookieJar - """ - cookie = cookielib.LWPCookieJar(cookie_file) - try: - cookie.load(ignore_discard=True) - except: - Log.error(traceback.format_exc()) - opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) - opener.addheaders = Constant.HTTP_HEADER_USERAGENT - urllib2.install_opener(opener) - return cookie - - -def generate_file_name(filename): - """ - @brief generate file name - @return new file name - """ - i = filename.rfind('.') - ext = filename[i:] - tmp = filename + str(int(time.time())) - hash_md5 = hashlib.md5(tmp) - return hash_md5.hexdigest() + ext - - -def save_file(filename, data, dirName): - """ - @brief Saves raw data to file. - @param filename String - @param data Binary data - @param dirName String - @return file path - """ - Log.debug('save file: ' + filename) - fn = filename - if not os.path.exists(dirName): - os.makedirs(dirName) - fn = os.path.join(dirName, filename) - with open(fn, 'wb') as f: - f.write(data) - return fn - - -def save_json(filename, data, dirName, mode='w+'): - """ - @brief Saves dict to json file. - @param filename String - @param data Dict - @param dirName String - @return file path - """ - Log.debug('save json: ' + filename) - fn = filename - if not os.path.exists(dirName): - os.makedirs(dirName) - fn = os.path.join(dirName, filename) - with open(fn, mode) as f: - f.write(json.dumps(data, indent=4)+'\n') - return fn - - -def load_json(filepath): - Log.debug('load json: ' + filepath) - with open(filepath, 'r') as f: - return _decode_data(json.loads(f.read())) - - -def pickle_save(data, file): - """ - @brief Use pickle to save python object into file - @param data The pyhton data - @param file The file - """ - basedir = os.path.dirname(file) - if not os.path.exists(basedir): - os.makedirs(basedir) - with open(file, 'wb') as f: - pickle.dump(data, f, pickle.HIGHEST_PROTOCOL) - - -def pickle_load(file): - """ - @brief Use pickle to load python object from file - @param file The file - @return python data - """ - if os.path.isfile(file): - with open(file, 'rb') as f: - return pickle.load(f) - return None - - -def search_content(key, content, fmat='attr'): - """ - @brief Search content from xml or html format - @param key String - @param content String - @param fmat attr - xml - @return String - """ - if fmat == 'attr': - pm = re.search(key + '\s?=\s?"([^"<]+)"', content) - if pm: - return pm.group(1) - elif fmat == 'xml': - pm = re.search('<{0}>([^<]+)'.format(key), content) - if not pm: - pm = re.search( - '<{0}><\!\[CDATA\[(.*?)\]\]>'.format(key), content) - if pm: - return pm.group(1) - return 'unknown' - - -def is_str(s): - """ - @brief Determines if string. - @param s String - @return True if string, False otherwise. - """ - return isinstance(s, basestring) - - -def trans_coding(data): - """ - @brief Transform string to unicode - @param data String - @return unicode - """ - if not data: - return data - result = None - if type(data) == unicode: - result = data - elif type(data) == str: - result = data.decode('utf-8') - return result - - -def trans_emoji(text): - """ - @brief Transform emoji html text to unicode - @param text String - @return emoji unicode - """ - def _emoji(matched): - hex = matched.group(1) - return ('\\U%08x' % int(hex, 16)).decode('unicode-escape').encode('utf-8') - - replace_t = re.sub(Constant.REGEX_EMOJI, _emoji, text) - return replace_t - - -def auto_reload(mod): - """ - @brief reload modules - @param mod: the need reload modules - """ - try: - module = sys.modules[mod] - except: - Log.error(traceback.format_exc()) - return False - - filename = module.__file__ - # .pyc 修改时间不会变 - # 所以就用 .py 的修改时间 - if filename.endswith(".pyc"): - filename = filename.replace(".pyc", ".py") - mod_time = os.path.getmtime(filename) - if not "loadtime" in module.__dict__: - module.loadtime = 0 - - try: - if mod_time > module.loadtime: - reload(module) - else: - return False - except: - Log.error(traceback.format_exc()) - return False - - module.loadtime = mod_time - - echo('[*] load \'%s\' successful.\n' % mod) - return True - - -def split_array(arr, n): - for i in xrange(0, len(arr), n): - yield arr[i:i+n] - diff --git a/wxbot_project_py2.7/wechat/wechat.py b/wxbot_project_py2.7/wechat/wechat.py deleted file mode 100644 index 81b2001..0000000 --- a/wxbot_project_py2.7/wechat/wechat.py +++ /dev/null @@ -1,698 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from utils import * -from wechat_apis import WXAPI -from config import ConfigManager -from config import Constant -from config import Log -#--------------------------------------------------- -import json -import re -import sys -import os -import time -import random -from collections import defaultdict -from datetime import timedelta -import traceback -import Queue -import threading -#=================================================== - - -class WeChat(WXAPI): - - def __str__(self): - description = \ - "=========================\n" + \ - "[#] Web WeChat\n" + \ - "[#] UUID: " + self.uuid + "\n" + \ - "[#] Uin: " + str(self.uin) + "\n" + \ - "[#] Sid: " + self.sid + "\n" + \ - "[#] Skey: " + self.skey + "\n" + \ - "[#] DeviceId: " + self.device_id + "\n" + \ - "[#] PassTicket: " + self.pass_ticket + "\n" + \ - "[#] Run Time: " + self.get_run_time() + '\n' + \ - "=========================" - return description - - def __init__(self, host='wx.qq.com'): - super(WeChat, self).__init__(host) - - self.db = None - self.save_data_folder = '' # 保存图片,语音,小视频的文件夹 - self.last_login = 0 # 上次退出的时间 - self.time_out = 5 # 同步时间间隔(单位:秒) - self.msg_handler = None - self.start_time = time.time() - self.bot = None - - cm = ConfigManager() - self.save_data_folders = cm.get_wechat_media_dir() - self.cookie_file = cm.get_cookie() - self.pickle_file = cm.get_pickle_files() - self.log_mode = cm.get('setting', 'log_mode') == 'True' - self.exit_code = 0 - - def start(self): - echo(Constant.LOG_MSG_START) - run(Constant.LOG_MSG_RECOVER, self.recover) - - timeOut = time.time() - self.last_login - echo(Constant.LOG_MSG_TRY_INIT) - if self.webwxinit(): - echo(Constant.LOG_MSG_SUCCESS) - run(Constant.LOG_MSG_RECOVER_CONTACT, self.recover_contacts) - else: - echo(Constant.LOG_MSG_FAIL) - - while True: - # first try to login by uin without qrcode - echo(Constant.LOG_MSG_ASSOCIATION_LOGIN) - if self.association_login(): - echo(Constant.LOG_MSG_SUCCESS) - else: - echo(Constant.LOG_MSG_FAIL) - # scan qrcode to login - run(Constant.LOG_MSG_GET_UUID, self.getuuid) - echo(Constant.LOG_MSG_GET_QRCODE) - self.genqrcode() - echo(Constant.LOG_MSG_SCAN_QRCODE) - - if not self.waitforlogin(): - continue - echo(Constant.LOG_MSG_CONFIRM_LOGIN) - if not self.waitforlogin(0): - continue - break - - run(Constant.LOG_MSG_LOGIN, self.login) - run(Constant.LOG_MSG_INIT, self.webwxinit) - run(Constant.LOG_MSG_STATUS_NOTIFY, self.webwxstatusnotify) - run(Constant.LOG_MSG_GET_CONTACT, self.webwxgetcontact) - echo(Constant.LOG_MSG_CONTACT_COUNT % ( - self.MemberCount, len(self.MemberList) - )) - echo(Constant.LOG_MSG_OTHER_CONTACT_COUNT % ( - len(self.GroupList), len(self.ContactList), - len(self.SpecialUsersList), len(self.PublicUsersList) - )) - run(Constant.LOG_MSG_GET_GROUP_MEMBER, self.fetch_group_contacts) - - run(Constant.LOG_MSG_SNAPSHOT, self.snapshot) - - while True: - [retcode, selector] = self.synccheck() - Log.debug('retcode: %s, selector: %s' % (retcode, selector)) - self.exit_code = int(retcode) - - if retcode == '1100': - echo(Constant.LOG_MSG_LOGOUT) - break - if retcode == '1101': - echo(Constant.LOG_MSG_LOGIN_OTHERWHERE) - break - if retcode == '1102': - echo(Constant.LOG_MSG_QUIT_ON_PHONE) - break - elif retcode == '0': - if selector == '2': - r = self.webwxsync() - if r is not None: - try: - self.handle_msg(r) - except: - Log.error(traceback.format_exc()) - elif selector == '7': - r = self.webwxsync() - elif selector == '0': - time.sleep(self.time_out) - elif selector == '4': - # 保存群聊到通讯录 - # 修改群名称 - # 新增或删除联系人 - # 群聊成员数目变化 - r = self.webwxsync() - if r is not None: - try: - self.handle_mod(r) - except: - Log.error(traceback.format_exc()) - elif selector == '3' or selector == '6': - break - else: - r = self.webwxsync() - Log.debug('webwxsync: %s\n' % json.dumps(r)) - - # 执行定时任务 - if self.msg_handler: - self.msg_handler.check_schedule_task() - - # if self.bot: - # r = self.bot.time_schedule() - # if r: - # for g in self.GroupList: - # echo('[*] 推送 -> %s: %s' % (g['NickName'], r)) - # g_id = g['UserName'] - # self.webwxsendmsg(r, g_id) - - def get_run_time(self): - """ - @brief get how long this run - @return String - """ - totalTime = int(time.time() - self.start_time) - t = timedelta(seconds=totalTime) - return '%s Day %s' % (t.days, t) - - def stop(self): - """ - @brief Save some data and use shell to kill this process - """ - run(Constant.LOG_MSG_SNAPSHOT, self.snapshot) - echo(Constant.LOG_MSG_RUNTIME % self.get_run_time()) - # close database connect - self.db.close() - - def fetch_group_contacts(self): - """ - @brief Fetches all groups contacts. - @return Bool: whether operation succeed. - @note This function must be finished in 180s - """ - Log.debug('fetch_group_contacts') - # clean database - if self.msg_handler: - self.msg_handler.clean_db() - - # sqlite - # ---------------------------------------------------- - # group max_thread_num max_fetch_group_num time(s) - # 197 10 10 108 - # 197 10 15 95 - # 197 20 10 103 - # 197 10 20 55 - # 197 5 30 39 - # 197 4 50 35 - # ---------------------------------------------------- - # mysql - # ---------------------------------------------------- - # group max_thread_num max_fetch_group_num time(s) - # 197 4 50 20 - # ---------------------------------------------------- - - max_thread_num = 4 - max_fetch_group_num = 50 - group_list_queue = Queue.Queue() - - class GroupListThread(threading.Thread): - - def __init__(self, group_list_queue, wechat): - threading.Thread.__init__(self) - self.group_list_queue = group_list_queue - self.wechat = wechat - - def run(self): - while not self.group_list_queue.empty(): - g_list = self.group_list_queue.get() - gid_list = [] - g_dict = {} - for g in g_list: - gid = g['UserName'] - gid_list.append(gid) - g_dict[gid] = g - - group_member_list = self.wechat.webwxbatchgetcontact(gid_list) - - for member_list in group_member_list: - gid = member_list['UserName'] - g = g_dict[gid] - g['MemberCount'] = member_list['MemberCount'] - g['OwnerUin'] = member_list['OwnerUin'] - self.wechat.GroupMemeberList[gid] = member_list['MemberList'] - - # 如果使用 Mysql 则可以在多线程里操作数据库 - # 否则请注释下列代码在主线程里更新群列表 - # ----------------------------------- - # 处理群成员 - # if self.wechat.msg_handler: - # self.wechat.msg_handler.handle_group_member_list(gid, member_list['MemberList']) - # ----------------------------------- - - self.group_list_queue.task_done() - - for g_list in split_array(self.GroupList, max_fetch_group_num): - group_list_queue.put(g_list) - - for i in range(max_thread_num): - t = GroupListThread(group_list_queue, self) - t.setDaemon(True) - t.start() - - group_list_queue.join() - - if self.msg_handler: - # 处理群 - if self.GroupList: - self.msg_handler.handle_group_list(self.GroupList) - - # 这个是用 sqlite 来存储群列表,sqlite 对多线程的支持不太好 - # ---------------------------------------------------- - # 处理群成员 - for (gid, member_list) in self.GroupMemeberList.items(): - self.msg_handler.handle_group_member_list(gid, member_list) - # ---------------------------------------------------- - - return True - - def snapshot(self): - """ - @brief Save basic infos for next login. - @return Bool: whether operation succeed. - """ - try: - conf = { - 'uuid': self.uuid, - 'redirect_uri': self.redirect_uri, - 'uin': self.uin, - 'sid': self.sid, - 'skey': self.skey, - 'pass_ticket': self.pass_ticket, - 'synckey': self.synckey, - 'device_id': self.device_id, - 'last_login': time.time(), - } - cm = ConfigManager() - Log.debug('save wechat config') - cm.set_wechat_config(conf) - - # save cookie - Log.debug('save cookie') - if self.cookie: - self.cookie.save(ignore_discard=True) - - # save contacts - Log.debug('save contacts') - self.save_contacts() - except Exception, e: - Log.error(traceback.format_exc()) - return False - return True - - def recover(self): - """ - @brief Recover from snapshot data. - @return Bool: whether operation succeed. - """ - cm = ConfigManager() - [self.uuid, self.redirect_uri, self.uin, - self.sid, self.skey, self.pass_ticket, - self.synckey, device_id, self.last_login] = cm.get_wechat_config() - - if device_id: - self.device_id = device_id - - self.base_request = { - 'Uin': int(self.uin), - 'Sid': self.sid, - 'Skey': self.skey, - 'DeviceID': self.device_id, - } - - # set cookie - Log.debug('set cookie') - self.cookie = set_cookie(self.cookie_file) - - return True - - def save_contacts(self): - """ - @brief Save contacts. - """ - pickle_save(self.User, self.pickle_file['User']) - pickle_save(self.MemberList, self.pickle_file['MemberList']) - pickle_save(self.GroupList, self.pickle_file['GroupList']) - pickle_save(self.GroupMemeberList, self.pickle_file['GroupMemeberList']) - pickle_save(self.SpecialUsersList, self.pickle_file['SpecialUsersList']) - - def recover_contacts(self): - """ - @brief recover contacts. - @return Bool: whether operation succeed. - """ - try: - self.User = pickle_load(self.pickle_file['User']) - self.MemberList = pickle_load(self.pickle_file['MemberList']) - self.GroupList = pickle_load(self.pickle_file['GroupList']) - self.GroupMemeberList = pickle_load(self.pickle_file['GroupMemeberList']) - self.SpecialUsersList = pickle_load(self.pickle_file['SpecialUsersList']) - return True - except Exception, e: - Log.error(traceback.format_exc()) - return False - - def handle_mod(self, r): - # ModContactCount: 变更联系人或群聊成员数目 - # ModContactList: 变更联系人或群聊列表,或群名称改变 - Log.debug('handle modify') - self.handle_msg(r) - for m in r['ModContactList']: - if m['UserName'][:2] == '@@': - # group - in_list = False - g_id = m['UserName'] - for g in self.GroupList: - # group member change - if g_id == g['UserName']: - g['MemberCount'] = m['MemberCount'] - g['NickName'] = m['NickName'] - self.GroupMemeberList[g_id] = m['MemberList'] - in_list = True - if self.msg_handler: - self.msg_handler.handle_group_member_change(g_id, m['MemberList']) - break - if not in_list: - # a new group - self.GroupList.append(m) - self.GroupMemeberList[g_id] = m['MemberList'] - if self.msg_handler: - self.msg_handler.handle_group_list_change(m) - self.msg_handler.handle_group_member_change(g_id, m['MemberList']) - - elif m['UserName'][0] == '@': - # user - in_list = False - for u in self.MemberList: - u_id = m['UserName'] - if u_id == u['UserName']: - u = m - in_list = True - break - # if don't have then add it - if not in_list: - self.MemberList.append(m) - - def handle_msg(self, r): - """ - @brief Recover from snapshot data. - @param r Dict: message json - """ - Log.debug('handle message') - if self.msg_handler: - self.msg_handler.handle_wxsync(r) - - n = len(r['AddMsgList']) - if n == 0: - return - - if self.log_mode: - echo(Constant.LOG_MSG_NEW_MSG % n) - - for msg in r['AddMsgList']: - - msgType = msg['MsgType'] - msgId = msg['MsgId'] - content = msg['Content'].replace('<', '<').replace('>', '>') - raw_msg = None - - if msgType == self.wx_conf['MSGTYPE_TEXT']: - # 地理位置消息 - if content.find('pictype=location') != -1: - location = content.split('
')[1][:-1] - raw_msg = { - 'raw_msg': msg, - 'location': location, - 'log': Constant.LOG_MSG_LOCATION % location - } - # 普通文本消息 - else: - text = content.split(':
')[-1] - raw_msg = { - 'raw_msg': msg, - 'text': text, - 'log': text.replace('
', '\n') - } - elif msgType == self.wx_conf['MSGTYPE_IMAGE']: - data = self.webwxgetmsgimg(msgId) - fn = 'img_' + msgId + '.jpg' - dir = self.save_data_folders['webwxgetmsgimg'] - path = save_file(fn, data, dir) - raw_msg = {'raw_msg': msg, - 'image': path, - 'log': Constant.LOG_MSG_PICTURE % path} - elif msgType == self.wx_conf['MSGTYPE_VOICE']: - data = self.webwxgetvoice(msgId) - fn = 'voice_' + msgId + '.mp3' - dir = self.save_data_folders['webwxgetvoice'] - path = save_file(fn, data, dir) - raw_msg = {'raw_msg': msg, - 'voice': path, - 'log': Constant.LOG_MSG_VOICE % path} - elif msgType == self.wx_conf['MSGTYPE_SHARECARD']: - info = msg['RecommendInfo'] - card = Constant.LOG_MSG_NAME_CARD % ( - info['NickName'], - info['Alias'], - info['Province'], info['City'], - Constant.LOG_MSG_SEX_OPTION[info['Sex']] - ) - namecard = '%s %s %s %s %s' % ( - info['NickName'], info['Alias'], info['Province'], - info['City'], Constant.LOG_MSG_SEX_OPTION[info['Sex']] - ) - raw_msg = { - 'raw_msg': msg, - 'namecard': namecard, - 'log': card - } - elif msgType == self.wx_conf['MSGTYPE_EMOTICON']: - url = search_content('cdnurl', content) - raw_msg = {'raw_msg': msg, - 'emoticon': url, - 'log': Constant.LOG_MSG_EMOTION % url} - elif msgType == self.wx_conf['MSGTYPE_APP']: - card = '' - # 链接, 音乐, 微博 - if msg['AppMsgType'] in [ - self.wx_conf['APPMSGTYPE_AUDIO'], - self.wx_conf['APPMSGTYPE_URL'], - self.wx_conf['APPMSGTYPE_OPEN'] - ]: - card = Constant.LOG_MSG_APP_LINK % ( - Constant.LOG_MSG_APP_LINK_TYPE[msg['AppMsgType']], - msg['FileName'], - search_content('des', content, 'xml'), - msg['Url'], - search_content('appname', content, 'xml') - ) - raw_msg = { - 'raw_msg': msg, - 'link': msg['Url'], - 'log': card - } - # 图片 - elif msg['AppMsgType'] == self.wx_conf['APPMSGTYPE_IMG']: - data = self.webwxgetmsgimg(msgId) - fn = 'img_' + msgId + '.jpg' - dir = self.save_data_folders['webwxgetmsgimg'] - path = save_file(fn, data, dir) - card = Constant.LOG_MSG_APP_IMG % ( - path, - search_content('appname', content, 'xml') - ) - raw_msg = { - 'raw_msg': msg, - 'image': path, - 'log': card - } - else: - raw_msg = { - 'raw_msg': msg, - 'log': Constant.LOG_MSG_UNKNOWN_MSG % (msgType, content) - } - elif msgType == self.wx_conf['MSGTYPE_STATUSNOTIFY']: - Log.info(Constant.LOG_MSG_NOTIFY_PHONE) - elif msgType == self.wx_conf['MSGTYPE_MICROVIDEO']: - data = self.webwxgetvideo(msgId) - fn = 'video_' + msgId + '.mp4' - dir = self.save_data_folders['webwxgetvideo'] - path = save_file(fn, data, dir) - raw_msg = {'raw_msg': msg, - 'video': path, - 'log': Constant.LOG_MSG_VIDEO % path} - elif msgType == self.wx_conf['MSGTYPE_RECALLED']: - recall_id = search_content('msgid', content, 'xml') - text = Constant.LOG_MSG_RECALL - raw_msg = { - 'raw_msg': msg, - 'text': text, - 'recall_msg_id': recall_id, - 'log': text - } - elif msgType == self.wx_conf['MSGTYPE_SYS']: - raw_msg = { - 'raw_msg': msg, - 'sys_notif': content, - 'log': content - } - elif msgType == self.wx_conf['MSGTYPE_VERIFYMSG']: - name = search_content('fromnickname', content) - raw_msg = { - 'raw_msg': msg, - 'log': Constant.LOG_MSG_ADD_FRIEND % name - } - else: - raw_msg = { - 'raw_msg': msg, - 'log': Constant.LOG_MSG_UNKNOWN_MSG % (msgType, content) - } - - isGroupMsg = '@@' in msg['FromUserName']+msg['ToUserName'] - if self.msg_handler and raw_msg: - if isGroupMsg: - # handle group messages - g_msg = self.make_group_msg(raw_msg) - self.msg_handler.handle_group_msg(g_msg) - else: - # handle personal messages - self.msg_handler.handle_user_msg(raw_msg) - - if self.log_mode: - self.show_msg(raw_msg) - - def make_group_msg(self, msg): - """ - @brief Package the group message for storage. - @param msg Dict: raw msg - @return raw_msg Dict: packged msg - """ - Log.debug('make group message') - raw_msg = { - 'raw_msg': msg['raw_msg'], - 'msg_id': msg['raw_msg']['MsgId'], - 'group_owner_uin': '', - 'group_name': '', - 'group_count': '', - 'from_user_name': msg['raw_msg']['FromUserName'], - 'to_user_name': msg['raw_msg']['ToUserName'], - 'user_attrstatus': '', - 'user_display_name': '', - 'user_nickname': '', - 'msg_type': msg['raw_msg']['MsgType'], - 'text': '', - 'link': '', - 'image': '', - 'video': '', - 'voice': '', - 'emoticon': '', - 'namecard': '', - 'location': '', - 'recall_msg_id': '', - 'sys_notif': '', - 'time': '', - 'timestamp': '', - 'log': '', - } - content = msg['raw_msg']['Content'].replace( - '<', '<').replace('>', '>') - - group = None - src = None - - if msg['raw_msg']['FromUserName'][:2] == '@@': - # 接收到来自群的消息 - g_id = msg['raw_msg']['FromUserName'] - group = self.get_group_by_id(g_id) - - if re.search(":
", content, re.IGNORECASE): - u_id = content.split(':
')[0] - src = self.get_group_user_by_id(u_id, g_id) - - elif msg['raw_msg']['ToUserName'][:2] == '@@': - # 自己发给群的消息 - g_id = msg['raw_msg']['ToUserName'] - u_id = msg['raw_msg']['FromUserName'] - src = self.get_group_user_by_id(u_id, g_id) - group = self.get_group_by_id(g_id) - - if src: - raw_msg['user_attrstatus'] = src['AttrStatus'] - raw_msg['user_display_name'] = src['DisplayName'] - raw_msg['user_nickname'] = src['NickName'] - if group: - raw_msg['group_count'] = group['MemberCount'] - raw_msg['group_owner_uin'] = group['OwnerUin'] - raw_msg['group_name'] = group['ShowName'] - - raw_msg['timestamp'] = msg['raw_msg']['CreateTime'] - t = time.localtime(float(raw_msg['timestamp'])) - raw_msg['time'] = time.strftime("%Y-%m-%d %T", t) - - for key in [ - 'text', 'link', 'image', 'video', 'voice', - 'emoticon', 'namecard', 'location', 'log', - 'recall_msg_id', 'sys_notif' - ]: - if key in msg: - raw_msg[key] = msg[key] - - return raw_msg - - def show_msg(self, message): - """ - @brief Log the message to stdout - @param message Dict - """ - msg = message - src = None - dst = None - group = None - - if msg and msg['raw_msg']: - - content = msg['raw_msg']['Content'] - content = content.replace('<', '<').replace('>', '>') - msg_id = msg['raw_msg']['MsgId'] - - if msg['raw_msg']['FromUserName'][:2] == '@@': - # 接收到来自群的消息 - g_id = msg['raw_msg']['FromUserName'] - group = self.get_group_by_id(g_id) - - if re.search(":
", content, re.IGNORECASE): - u_id = content.split(':
')[0] - src = self.get_group_user_by_id(u_id, g_id) - dst = {'ShowName': 'GROUP'} - else: - u_id = msg['raw_msg']['ToUserName'] - src = {'ShowName': 'SYSTEM'} - dst = self.get_group_user_by_id(u_id, g_id) - elif msg['raw_msg']['ToUserName'][:2] == '@@': - # 自己发给群的消息 - g_id = msg['raw_msg']['ToUserName'] - u_id = msg['raw_msg']['FromUserName'] - group = self.get_group_by_id(g_id) - src = self.get_group_user_by_id(u_id, g_id) - dst = {'ShowName': 'GROUP'} - else: - # 非群聊消息 - src = self.get_user_by_id(msg['raw_msg']['FromUserName']) - dst = self.get_user_by_id(msg['raw_msg']['ToUserName']) - - if group: - echo('%s |%s| %s -> %s: %s\n' % ( - msg_id, - trans_emoji(group['ShowName']), - trans_emoji(src['ShowName']), - dst['ShowName'], - trans_emoji(msg['log']) - )) - else: - echo('%s %s -> %s: %s\n' % ( - msg_id, - trans_emoji(src['ShowName']), - trans_emoji(dst['ShowName']), - trans_emoji(msg['log']) - )) diff --git a/wxbot_project_py2.7/wechat/wechat_apis.py b/wxbot_project_py2.7/wechat/wechat_apis.py deleted file mode 100644 index fa75db5..0000000 --- a/wxbot_project_py2.7/wechat/wechat_apis.py +++ /dev/null @@ -1,973 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from utils import * -from config import Constant -from config import Log -#--------------------------------------------------- -import sys -import os -import cookielib -import random -import requests -import time -import xml.dom.minidom -# for media upload -import mimetypes -from requests_toolbelt.multipart.encoder import MultipartEncoder -#=================================================== - - -class WXAPI(object): - - def __init__(self, host): - self.wx_host = host - self.wx_filehost = '' - self.wx_conf = {} - # jsLogin时这个appid只能使用: wx782c26e4c19acffb - self.appid = Constant.API_APPID - self.uuid = '' - self.redirect_uri = '' - self.skey = '' - self.sid = '' - self.uin = '' - self.pass_ticket = '' - self.base_request = {} - self.synckey_dic = {} - self.synckey = '' - self.device_id = 'e' + repr(random.random())[2:17] - # device_id: 登录手机设备 - # web wechat 的格式为: e123456789012345 (e+15位随机数) - # mobile wechat 的格式为: A1234567890abcde (A+15位随机数字或字母) - self.user_agent = Constant.API_USER_AGENT - self.cookie = None - - self.conf_factory() - - self.User = [] # 登陆账号信息 - self.MemberList = [] # 好友+群聊+公众号+特殊账号 - self.MemberCount = 0 - self.ContactList = [] # 好友 - self.GroupList = [] # 群 - self.GroupMemeberList = {} # 群聊成员字典 - # "group_id": [ - # {member}, ... - # ] - self.PublicUsersList = [] # 公众号/服务号 - self.SpecialUsersList = [] # 特殊账号 - - self.media_count = 0 - - def conf_factory(self): - e = self.wx_host # wx.qq.com - t, o, n = "login.weixin.qq.com", "file.wx.qq.com", "webpush.weixin.qq.com" - - if e.find("wx2.qq.com") > -1: - t, o, n = "login.wx2.qq.com", "file.wx2.qq.com", "webpush.wx2.qq.com" - elif e.find("wx8.qq.com") > -1: - t, o, n = "login.wx8.qq.com", "file.wx8.qq.com", "webpush.wx8.qq.com" - elif e.find("qq.com") > -1: - t, o, n = "login.wx.qq.com", "file.wx.qq.com", "webpush.wx.qq.com" - elif e.find("web2.wechat.com") > -1: - t, o, n = "login.web2.wechat.com", "file.web2.wechat.com", "webpush.web2.wechat.com" - elif e.find("wechat.com") > -1: - t, o, n = "login.web.wechat.com", "file.web.wechat.com", "webpush.web.wechat.com" - - self.wx_filehost = o - conf = { - 'LANG': Constant.API_LANG, - 'SpecialUsers': Constant.API_SPECIAL_USER, - 'API_jsLogin': "https://" + t + "/jslogin", - 'API_qrcode': "https://login.weixin.qq.com/l/", - 'API_qrcode_img': "https://login.weixin.qq.com/qrcode/", - 'API_login': "https://" + t + "/cgi-bin/mmwebwx-bin/login", - 'API_synccheck': "https://" + n + "/cgi-bin/mmwebwx-bin/synccheck", - 'API_webwxdownloadmedia': "https://" + o + "/cgi-bin/mmwebwx-bin/webwxgetmedia", - 'API_webwxuploadmedia': "https://" + o + "/cgi-bin/mmwebwx-bin/webwxuploadmedia", - 'API_webwxpreview': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxpreview", - 'API_webwxinit': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxinit", - 'API_webwxgetcontact': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetcontact", - 'API_webwxsync': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsync", - 'API_webwxbatchgetcontact': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxbatchgetcontact", - 'API_webwxgeticon': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgeticon", - 'API_webwxsendmsg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendmsg", - 'API_webwxsendmsgimg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendmsgimg", - 'API_webwxsendmsgvedio': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendvideomsg", - 'API_webwxsendemoticon': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendemoticon", - 'API_webwxsendappmsg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendappmsg", - 'API_webwxgetheadimg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetheadimg", - 'API_webwxgetmsgimg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetmsgimg", - 'API_webwxgetmedia': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetmedia", - 'API_webwxgetvideo': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetvideo", - 'API_webwxlogout': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxlogout", - 'API_webwxgetvoice': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxgetvoice", - 'API_webwxupdatechatroom': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxupdatechatroom", - 'API_webwxcreatechatroom': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxcreatechatroom", - 'API_webwxstatusnotify': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxstatusnotify", - 'API_webwxcheckurl': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxcheckurl", - 'API_webwxverifyuser': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxverifyuser", - 'API_webwxfeedback': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsendfeedback", - 'API_webwxreport': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxstatreport", - 'API_webwxsearch': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxsearchcontact", - 'API_webwxoplog': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxoplog", - 'API_checkupload': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxcheckupload", - 'API_webwxrevokemsg': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxrevokemsg", - 'API_webwxpushloginurl': "https://" + e + "/cgi-bin/mmwebwx-bin/webwxpushloginurl", - 'CONTACTFLAG_CONTACT': 1, - 'CONTACTFLAG_CHATCONTACT': 2, - 'CONTACTFLAG_CHATROOMCONTACT': 4, - 'CONTACTFLAG_BLACKLISTCONTACT': 8, - 'CONTACTFLAG_DOMAINCONTACT': 16, - 'CONTACTFLAG_HIDECONTACT': 32, - 'CONTACTFLAG_FAVOURCONTACT': 64, - 'CONTACTFLAG_3RDAPPCONTACT': 128, - 'CONTACTFLAG_SNSBLACKLISTCONTACT': 256, - 'CONTACTFLAG_NOTIFYCLOSECONTACT': 512, - 'CONTACTFLAG_TOPCONTACT': 2048, - 'MSGTYPE_TEXT': 1, - 'MSGTYPE_IMAGE': 3, - 'MSGTYPE_VOICE': 34, - 'MSGTYPE_VIDEO': 43, - 'MSGTYPE_MICROVIDEO': 62, - 'MSGTYPE_EMOTICON': 47, - 'MSGTYPE_APP': 49, - 'MSGTYPE_VOIPMSG': 50, - 'MSGTYPE_VOIPNOTIFY': 52, - 'MSGTYPE_VOIPINVITE': 53, - 'MSGTYPE_LOCATION': 48, - 'MSGTYPE_STATUSNOTIFY': 51, - 'MSGTYPE_SYSNOTICE': 9999, - 'MSGTYPE_POSSIBLEFRIEND_MSG': 40, - 'MSGTYPE_VERIFYMSG': 37, - 'MSGTYPE_SHARECARD': 42, - 'MSGTYPE_SYS': 10000, - 'MSGTYPE_RECALLED': 10002, - 'APPMSGTYPE_TEXT': 1, - 'APPMSGTYPE_IMG': 2, - 'APPMSGTYPE_AUDIO': 3, - 'APPMSGTYPE_VIDEO': 4, - 'APPMSGTYPE_URL': 5, - 'APPMSGTYPE_ATTACH': 6, - 'APPMSGTYPE_OPEN': 7, - 'APPMSGTYPE_EMOJI': 8, - 'APPMSGTYPE_VOICE_REMIND': 9, - 'APPMSGTYPE_SCAN_GOOD': 10, - 'APPMSGTYPE_GOOD': 13, - 'APPMSGTYPE_EMOTION': 15, - 'APPMSGTYPE_CARD_TICKET': 16, - 'APPMSGTYPE_REALTIME_SHARE_LOCATION': 17, - 'APPMSGTYPE_TRANSFERS': 2e3, - 'APPMSGTYPE_RED_ENVELOPES': 2001, - 'APPMSGTYPE_READER_TYPE': 100001, - 'UPLOAD_MEDIA_TYPE_IMAGE': 1, - 'UPLOAD_MEDIA_TYPE_VIDEO': 2, - 'UPLOAD_MEDIA_TYPE_AUDIO': 3, - 'UPLOAD_MEDIA_TYPE_ATTACHMENT': 4, - } - self.wx_conf = conf - - def getuuid(self): - """ - @brief Gets the uuid just used for login. - @return Bool: whether operation succeed. - """ - url = self.wx_conf['API_jsLogin'] - params = { - 'appid': self.appid, - 'fun': 'new', - 'lang': self.wx_conf['LANG'], - '_': int(time.time()), - } - data = post(url, params, False) - regx = r'window.QRLogin.code = (\d+); window.QRLogin.uuid = "(\S+?)"' - pm = re.search(regx, data) - if pm: - code = pm.group(1) - self.uuid = pm.group(2) - return code == '200' - return False - - def genqrcode(self): - """ - @brief outprint the qrcode to stdout on macos/linux - or open image on windows - """ - if sys.platform.startswith('win'): - url = self.wx_conf['API_qrcode_img'] + self.uuid - params = { - 't': 'webwx', - '_': int(time.time()) - } - data = post(url, params, False) - if data == '': - return - qrcode_path = save_file('qrcode.jpg', data, './') - os.startfile(qrcode_path) - else: - str2qr_terminal(self.wx_conf['API_qrcode'] + self.uuid) - - def waitforlogin(self, tip=1): - """ - @brief wait for scaning qrcode to login - @param tip 1: wait for scan qrcode - 0: wait for confirm - @return Bool: whether operation succeed - """ - time.sleep(tip) - url = self.wx_conf['API_login'] + '?tip=%s&uuid=%s&_=%s' % ( - tip, self.uuid, int(time.time())) - data = get(url) - pm = re.search(r'window.code=(\d+);', data) - code = pm.group(1) - - if code == '201': - return True - elif code == '200': - pm = re.search(r'window.redirect_uri="(\S+?)";', data) - r_uri = pm.group(1) + '&fun=new' - self.redirect_uri = r_uri - self.wx_host = r_uri.split('://')[1].split('/')[0] - self.conf_factory() - return True - elif code == '408': - echo(Constant.LOG_MSG_WAIT_LOGIN_ERR1) - else: - echo(Constant.LOG_MSG_WAIT_LOGIN_ERR2) - return False - - def login(self): - """ - @brief login - redirect_uri 有效时间是从扫码成功后算起, - 大概是 300 秒,在此期间可以重新登录,但获取的联系人和群ID会改变 - @return Bool: whether operation succeed - """ - data = get(self.redirect_uri) - doc = xml.dom.minidom.parseString(data) - root = doc.documentElement - - for node in root.childNodes: - if node.nodeName == 'ret': - if node.childNodes[0].data != "0": - return False - elif node.nodeName == 'skey': - self.skey = node.childNodes[0].data - elif node.nodeName == 'wxsid': - self.sid = node.childNodes[0].data - elif node.nodeName == 'wxuin': - self.uin = node.childNodes[0].data - elif node.nodeName == 'pass_ticket': - self.pass_ticket = node.childNodes[0].data - - if '' in (self.skey, self.sid, self.uin, self.pass_ticket): - return False - - self.base_request = { - 'Uin': int(self.uin), - 'Sid': self.sid, - 'Skey': self.skey, - 'DeviceID': self.device_id, - } - - return True - - def webwxinit(self): - """ - @brief wechat initial - 掉线后 300 秒可以重新使用此 api 登录 - 获取的联系人和群ID保持不变 - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxinit'] + \ - '?pass_ticket=%s&skey=%s&r=%s' % ( - self.pass_ticket, self.skey, int(time.time()) - ) - params = { - 'BaseRequest': self.base_request - } - dic = post(url, params) - self.User = dic['User'] - self.make_synckey(dic) - - return dic['BaseResponse']['Ret'] == 0 - - def webwxstatusnotify(self): - """ - @brief notify the mobile phone, this not necessary - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxstatusnotify'] + \ - '?lang=%s&pass_ticket=%s' % ( - self.wx_conf['LANG'], self.pass_ticket - ) - params = { - 'BaseRequest': self.base_request, - "Code": 3, - "FromUserName": self.User['UserName'], - "ToUserName": self.User['UserName'], - "ClientMsgId": int(time.time()) - } - dic = post(url, params) - - return dic['BaseResponse']['Ret'] == 0 - - def webwxgetcontact(self): - """ - @brief get all contacts: people, group, public user, special user - @return Bool: whether operation succeed - """ - SpecialUsers = self.wx_conf['SpecialUsers'] - url = self.wx_conf['API_webwxgetcontact'] + \ - '?pass_ticket=%s&skey=%s&r=%s' % ( - self.pass_ticket, self.skey, int(time.time()) - ) - dic = post(url, {}) - - self.MemberCount = dic['MemberCount'] - self.MemberList = dic['MemberList'] - ContactList = self.MemberList[:] - GroupList = self.GroupList[:] - PublicUsersList = self.PublicUsersList[:] - SpecialUsersList = self.SpecialUsersList[:] - - for i in xrange(len(ContactList) - 1, -1, -1): - Contact = ContactList[i] - if Contact['VerifyFlag'] & 8 != 0: # 公众号/服务号 - ContactList.remove(Contact) - self.PublicUsersList.append(Contact) - elif Contact['UserName'] in SpecialUsers: # 特殊账号 - ContactList.remove(Contact) - self.SpecialUsersList.append(Contact) - elif Contact['UserName'].find('@@') != -1: # 群聊 - ContactList.remove(Contact) - self.GroupList.append(Contact) - elif Contact['UserName'] == self.User['UserName']: # 自己 - ContactList.remove(Contact) - self.ContactList = ContactList - - return True - - def webwxbatchgetcontact(self, gid_list): - """ - @brief get group contacts - @param gid_list, The list of group id - @return List, list of group contacts - """ - url = self.wx_conf['API_webwxbatchgetcontact'] + \ - '?type=ex&r=%s&pass_ticket=%s' % ( - int(time.time()), self.pass_ticket - ) - params = { - 'BaseRequest': self.base_request, - "Count": len(gid_list), - "List": [{"UserName": gid, "EncryChatRoomId": ""} for gid in gid_list] - } - dic = post(url, params) - return dic['ContactList'] - - def synccheck(self): - """ - @brief check whether there's a message - @return [retcode, selector] - retcode: 0 successful - 1100 logout - 1101 login otherwhere - selector: 0 nothing - 2 message - 6 unkonwn - 7 webwxsync - """ - params = { - 'r': int(time.time()), - 'sid': self.sid, - 'uin': self.uin, - 'skey': self.skey, - 'deviceid': self.device_id, - 'synckey': self.synckey, - '_': int(time.time()), - } - url = self.wx_conf['API_synccheck'] + '?' + urllib.urlencode(params) - data = get(url) - reg = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}' - pm = re.search(reg, data) - retcode = pm.group(1) - selector = pm.group(2) - return [retcode, selector] - - def webwxsync(self): - """ - @brief sync the messages - @return Dict{} - """ - url = self.wx_conf['API_webwxsync'] + \ - '?sid=%s&skey=%s&pass_ticket=%s' % ( - self.sid, self.skey, self.pass_ticket - ) - params = { - 'BaseRequest': self.base_request, - 'SyncKey': self.synckey_dic, - 'rr': ~int(time.time()) - } - dic = post(url, params) - - if dic['BaseResponse']['Ret'] == 0: - self.make_synckey(dic) - return dic - - def webwxgetmsgimg(self, msgid): - """ - @brief get image in message - @param msgid The id of message - @return binary data of image - """ - url = self.wx_conf['API_webwxgetmsgimg'] + \ - '?MsgID=%s&skey=%s' % (msgid, self.skey) - data = get(url, api='webwxgetmsgimg') - return data - - def webwxgetvoice(self, msgid): - """ - @brief get voice in message - @param msgid The id of message - @return binary data of voice - """ - url = self.wx_conf['API_webwxgetvoice'] + \ - '?msgid=%s&skey=%s' % (msgid, self.skey) - data = get(url, api='webwxgetvoice') - return data - - def webwxgetvideo(self, msgid): - """ - @brief get video in message - @param msgid The id of message - @return binary data of video - """ - url = self.wx_conf['API_webwxgetvideo'] + \ - '?msgid=%s&skey=%s' % (msgid, self.skey) - data = get(url, api='webwxgetvideo') - return data - - def webwxgeticon(self, id): - """ - @brief get user small icon - @param id String - @return binary data of icon - """ - url = self.wx_conf['API_webwxgeticon'] + \ - '?username=%s&skey=%s' % (id, self.skey) - data = get(url, api='webwxgeticon') - return data - - def webwxgetheadimg(self, id): - """ - @brief get user head image - @param id String - @return binary data of image - """ - url = self.wx_conf['API_webwxgetheadimg'] + \ - '?username=%s&skey=%s' % (id, self.skey) - data = get(url, api='webwxgetheadimg') - return data - - def webwxsendmsg(self, word, to='filehelper'): - """ - @brief send text message - @param word String - @param to User id - @return dic Dict - """ - url = self.wx_conf['API_webwxsendmsg'] + \ - '?pass_ticket=%s' % (self.pass_ticket) - clientMsgId = str(int(time.time() * 1000)) + \ - str(random.random())[:5].replace('.', '') - params = { - 'BaseRequest': self.base_request, - 'Msg': { - "Type": 1, - "Content": trans_coding(word), - "FromUserName": self.User['UserName'], - "ToUserName": to, - "LocalID": clientMsgId, - "ClientMsgId": clientMsgId - } - } - dic = post(url, params) - return dic - - def webwxuploadmedia(self, file_path): - """ - @brief upload image - @param file_path String - @return Dict: json - """ - url = self.wx_conf['API_webwxuploadmedia'] + '?f=json' - # 计数器 - self.media_count = self.media_count + 1 - fn = file_path - # mime_type: - # 'application/pdf' - # 'image/jpeg' - # 'image/png' - # ... - mime_type = mimetypes.guess_type(fn, strict=False)[0] - if not mime_type: - mime_type = 'text/plain' - # 文档格式 - # 微信服务器目前应该支持3种类型: - # pic 直接显示,包含图片,表情 - # video 不清楚 - # doc 显示为文件,包含PDF等 - media_type = 'pic' if mime_type.split('/')[0] == 'image' else 'doc' - time_format = "%a %b %d %Y %T GMT%z (%Z)" - last_modifie_date = time.strftime(time_format, time.localtime()) - file_size = os.path.getsize(fn) - pass_ticket = self.pass_ticket - client_media_id = str(int(time.time() * 1000)) + \ - str(random.random())[:5].replace('.', '') - - webwx_data_ticket = '' - for item in self.cookie: - if item.name == 'webwx_data_ticket': - webwx_data_ticket = item.value - break - if (webwx_data_ticket == ''): - Log.error("No Cookie\n") - return None - - uploadmediarequest = json.dumps({ - "BaseRequest": self.base_request, - "ClientMediaId": client_media_id, - "TotalLen": file_size, - "StartPos": 0, - "DataLen": file_size, - "MediaType": 4 - }, ensure_ascii=False).encode('utf8') - - multipart_encoder = MultipartEncoder( - fields={ - 'id': 'WU_FILE_' + str(self.media_count), - 'name': fn, - 'type': mime_type, - 'lastModifieDate': last_modifie_date, - 'size': str(file_size), - 'mediatype': media_type, - 'uploadmediarequest': uploadmediarequest, - 'webwx_data_ticket': webwx_data_ticket, - 'pass_ticket': pass_ticket, - 'filename': ( - fn, - open(fn, 'rb'), - mime_type.split('/')[1] - ) - }, - boundary=( - '-----------------------------' - '1575017231431605357584454111' - ) - ) - - headers = { - 'Host': self.wx_filehost, - 'User-Agent': self.user_agent, - 'Accept': ( - 'text/html,application/xhtml+xml,' - 'application/xml;q=0.9,*/*;q=0.8' - ), - 'Accept-Language': 'en-US,en;q=0.5', - 'Accept-Encoding': 'gzip, deflate', - 'Referer': 'https://' + self.wx_host, - 'Content-Type': multipart_encoder.content_type, - 'Origin': 'https://' + self.wx_host, - 'Connection': 'keep-alive', - 'Pragma': 'no-cache', - 'Cache-Control': 'no-cache', - } - - r = requests.post(url, data=multipart_encoder, headers=headers) - dic = json.loads(r.text) #修复无法发送Media消息BUG - if dic['BaseResponse']['Ret'] == 0: - return dic - return None - - def webwxsendmsgimg(self, user_id, media_id): - """ - @brief send image - @param user_id String - @param media_id String - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxsendmsgimg'] + \ - '?fun=async&f=json&pass_ticket=%s' % self.pass_ticket - clientMsgId = str(int(time.time() * 1000)) + \ - str(random.random())[:5].replace('.', '') - data_json = { - "BaseRequest": self.base_request, - "Msg": { - "Type": 3, - "MediaId": media_id, - "FromUserName": self.User['UserName'], - "ToUserName": user_id, - "LocalID": clientMsgId, - "ClientMsgId": clientMsgId - } - } - r = post(url, data_json) - return dic['BaseResponse']['Ret'] == 0 - - def webwxsendemoticon(self, user_id, media_id): - """ - @brief send image - @param user_id String - @param media_id String - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxsendemoticon'] + \ - '?fun=sys&f=json&pass_ticket=%s' % self.pass_ticket - clientMsgId = str(int(time.time() * 1000)) + \ - str(random.random())[:5].replace('.', '') - data_json = { - "BaseRequest": self.base_request, - "Msg": { - "Type": 47, - "EmojiFlag": 2, - "MediaId": media_id, - "FromUserName": self.User['UserName'], - "ToUserName": user_id, - "LocalID": clientMsgId, - "ClientMsgId": clientMsgId - } - } - r = post(url, data_json) - return dic['BaseResponse']['Ret'] == 0 - - def webwxsendappmsg(self, user_id, data): - """ - @brief send app msg - @param user_id String - @param data Dict - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxsendappmsg'] + \ - '?fun=sys&f=json&pass_ticket=%s' % self.pass_ticket - clientMsgId = str(int(time.time() * 1000)) + \ - str(random.random())[:5].replace('.', '') - content = ''.join([ - "" % data['appid'], # 可使用其它AppID - "%s" % data['title'], - "", - "", - "%d" % data['type'], - "", - "", - "", - "", - "%d" % data['totallen'], - "%s" % data['attachid'], - "%s" % data['fileext'], - "", - "", - "", - ]) - data_json = { - "BaseRequest": self.base_request, - "Msg": { - "Type": data['type'], - "Content": content, - "FromUserName": self.User['UserName'], - "ToUserName": user_id, - "LocalID": clientMsgId, - "ClientMsgId": clientMsgId - }, - "Scene": 0 - } - r = post(url, data_json) - return dic['BaseResponse']['Ret'] == 0 - - def webwxcreatechatroom(self, uid_arr): - """ - @brief create a chat group - @param uid_arr [String] - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxcreatechatroom'] + '?r=%s' % int(time.time()) - params = { - 'BaseRequest': self.base_request, - 'Topic': '', - 'MemberCount': len(uid_arr), - 'MemberList': [{'UserName': uid} for uid in uid_arr], - } - dic = post(url, params) - return dic['BaseResponse']['Ret'] == 0 - - def webwxupdatechatroom(self, add_arr, del_arr, invite_arr): - """ - @brief add/delete/invite member in chat group - @param add_arr [uid: String] - @param del_arr [uid: String] - @param invite_arr [uid: String] - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxupdatechatroom'] + '?r=%s' % int(time.time()) - params = { - 'BaseRequest': self.base_request, - 'ChatRoomName': '', - 'NewTopic': '', - 'AddMemberList': add_arr, - 'DelMemberList': del_arr, - 'InviteMemberList': invite_arr, - } - dic = post(url, params) - return dic['BaseResponse']['Ret'] == 0 - - def webwxrevokemsg(self, msgid, user_id, client_msgid): - """ - @brief revoke a message - @param msgid String - @param user_id String - @param client_msgid String - @return Bool: whether operation succeed - """ - url = self.wx_conf['API_webwxrevokemsg'] + '?r=%s' % int(time.time()) - params = { - 'BaseRequest': self.base_request, - 'SvrMsgId': msgid, - 'ToUserName': user_id, - 'ClientMsgId': client_msgid - } - dic = post(url, params) - return dic['BaseResponse']['Ret'] == 0 - - def webwxpushloginurl(self, uin): - """ - @brief push a login confirm alert to mobile device - @param uin String - @return dic Dict - """ - url = self.wx_conf['API_webwxpushloginurl'] + '?uin=%s' % uin - dic = eval(get(url)) - return dic - - def association_login(self): - """ - @brief login without scan qrcode - @return Bool: whether operation succeed - """ - if self.uin != '': - dic = self.webwxpushloginurl(self.uin) - if dic['ret'] == '0': - self.uuid = dic['uuid'] - return True - return False - - def send_text(self, user_id, text): - """ - @brief send text - @param user_id String - @param text String - @return Bool: whether operation succeed - """ - try: - dic = self.webwxsendmsg(text, user_id) - return dic['BaseResponse']['Ret'] == 0 - except: - return False - - def send_img(self, user_id, file_path): - """ - @brief send image - @param user_id String - @param file_path String - @return Bool: whether operation succeed - """ - response = self.webwxuploadmedia(file_path) - media_id = "" - if response is not None: - media_id = response['MediaId'] - return self.webwxsendmsgimg(user_id, media_id) - - def send_emot(self, user_id, file_path): - """ - @brief send emotion - @param user_id String - @param file_path String - @return Bool: whether operation succeed - """ - response = self.webwxuploadmedia(file_path) - media_id = "" - if response is not None: - media_id = response['MediaId'] - return self.webwxsendemoticon(user_id, media_id) - - def send_file(self, user_id, file_path): - """ - @brief send file - @param user_id String - @param file_path String - @return Bool: whether operation succeed - """ - title = file_path.split('/')[-1] - data = { - 'appid': Constant.API_WXAPPID, - 'title': title, - 'totallen': '', - 'attachid': '', - 'type': self.wx_conf['APPMSGTYPE_ATTACH'], - 'fileext': title.split('.')[-1], - } - - response = self.webwxuploadmedia(file_path) - if response is not None: - data['totallen'] = response['StartPos'] - data['attachid'] = response['MediaId'] - else: - Log.error('File upload error') - - return self.webwxsendappmsg(user_id, data) - - def make_synckey(self, dic): - self.synckey_dic = dic['SyncKey'] - - def foo(x): - return str(x['Key']) + '_' + str(x['Val']) - - # synckey for synccheck - self.synckey = '|'.join( - [foo(keyVal) for keyVal in self.synckey_dic['List']]) - - def revoke_msg(self, msgid, user_id, client_msgid): - """ - @brief revoke a message - @param msgid String - @param user_id String - @param client_msgid String - @return Bool: whether operation succeed - """ - return self.webwxrevokemsg(msgid, user_id, client_msgid) - - # ----------------------------------------------------- - # The following is getting user & group infomation apis - def get_user_by_id(self, user_id): - """ - @brief get all type of name by user id - @param user_id The id of user - @return Dict: { - 'UserName' # 微信动态ID - 'RemarkName' # 备注 - 'NickName' # 微信昵称 - 'ShowName' # Log显示用的 - } - """ - UnknownPeople = Constant.LOG_MSG_UNKNOWN_NAME + user_id - name = { - 'UserName': user_id, - 'RemarkName': '', - 'NickName': '', - 'ShowName': '', - } - name['ShowName'] = UnknownPeople - - # yourself - if user_id == self.User['UserName']: - name['RemarkName'] = self.User['RemarkName'] - name['NickName'] = self.User['NickName'] - name['ShowName'] = name['NickName'] - else: - # 联系人 - for member in self.MemberList: - if member['UserName'] == user_id: - r, n = member['RemarkName'], member['NickName'] - name['RemarkName'] = r - name['NickName'] = n - name['ShowName'] = r if r else n - break - # 特殊帐号 - for member in self.SpecialUsersList: - if member['UserName'] == user_id: - name['RemarkName'] = user_id - name['NickName'] = user_id - name['ShowName'] = user_id - break - - return name - - def get_group_user_by_id(self, user_id, group_id): - """ - @brief get group user by user id - @param user_id The id of user - @param group_id The id of group - @return Dict: { - 'UserName' # 微信动态ID - 'NickName' # 微信昵称 - 'DisplayName' # 群聊显示名称 - 'ShowName' # Log显示用的 - 'AttrStatus' # 群用户id - } - """ - UnknownPeople = Constant.LOG_MSG_UNKNOWN_NAME + user_id - name = { - 'UserName': user_id, - 'NickName': '', - 'DisplayName': '', - 'ShowName': '', - 'AttrStatus': '', - } - name['ShowName'] = UnknownPeople - - # 群友 - if group_id in self.GroupMemeberList: - for member in self.GroupMemeberList[group_id]: - if member['UserName'] == user_id: - n, d = member['NickName'], member['DisplayName'] - name['NickName'] = n - name['DisplayName'] = d - name['AttrStatus'] = member['AttrStatus'] - name['ShowName'] = d if d else n - break - - return name - - def get_group_by_id(self, group_id): - """ - @brief get basic info by group id - @param group_id The id of group - @return Dict: { - 'UserName' # 微信动态ID - 'NickName' # 微信昵称 - 'DisplayName' # 群聊显示名称 - 'ShowName' # Log显示用的 - 'OwnerUin' # 群主ID - 'MemberCount' # 群人数 - } - """ - UnknownGroup = Constant.LOG_MSG_UNKNOWN_GROUP_NAME + group_id - group = { - 'UserName': group_id, - 'NickName': '', - 'DisplayName': '', - 'ShowName': '', - 'OwnerUin': '', - 'MemberCount': '', - } - - for member in self.GroupList: - if member['UserName'] == group_id: - group['NickName'] = member['NickName'] - group['DisplayName'] = member.get('DisplayName', '') - group['ShowName'] = member.get('NickName', UnknownGroup) - group['OwnerUin'] = member.get('OwnerUin', '') - group['MemberCount'] = member['MemberCount'] - break - - return group - - def get_user_id(self, name): - """ - @brief Gets the user id. - @param name The user nickname or remarkname - @return The user id. - """ - for member in self.MemberList: - if name == member['RemarkName'] or name == member['NickName']: - return member['UserName'] - return None diff --git a/wxbot_project_py2.7/weixin_bot.py b/wxbot_project_py2.7/weixin_bot.py deleted file mode 100755 index ec7a53b..0000000 --- a/wxbot_project_py2.7/weixin_bot.py +++ /dev/null @@ -1,281 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from wechat import WeChat -from wechat.utils import * -from wx_handler import WeChatMsgProcessor -from wx_handler import Bot -from wx_handler import SGMail -from db import SqliteDB -from db import MysqlDB -from config import ConfigManager -from config import Constant -from config import Log -#--------------------------------------------------- -from flask import Flask, render_template, send_file, jsonify, request -import threading -import traceback -import os -import logging -import time -#=================================================== - - -cm = ConfigManager() -db = SqliteDB(cm.getpath('database')) -# db = MysqlDB(cm.mysql()) -wechat_msg_processor = WeChatMsgProcessor(db) -wechat = WeChat(cm.get('wechat', 'host')) -wechat.db = db -wechat.bot = Bot() -wechat.msg_handler = wechat_msg_processor -wechat_msg_processor.wechat = wechat - -PORT = int(cm.get('setting', 'server_port')) -app = Flask(__name__, template_folder='flask_templates') -app.config['UPLOAD_FOLDER'] = cm.getpath('uploaddir') - -logger = logging.getLogger('werkzeug') -log_format_str = Constant.SERVER_LOG_FORMAT -formatter = logging.Formatter(log_format_str) -flask_log_handler = logging.FileHandler(cm.getpath('server_log_file')) -flask_log_handler.setLevel(logging.INFO) -flask_log_handler.setFormatter(formatter) -logger.addHandler(flask_log_handler) -app.logger.addHandler(flask_log_handler) - -# sendgrid mail -sg_apikey = cm.get('sendgrid', 'api_key') -from_email = cm.get('sendgrid', 'from_email') -to_email = cm.get('sendgrid', 'to_email') -sg = SGMail(sg_apikey, from_email, to_email) - -@app.route('/') -def index(): - return render_template(Constant.SERVER_PAGE_INDEX) - - -@app.route('/qrcode') -def qrcode(): - qdir = cm.getpath('qrcodedir') - if not os.path.exists(qdir): - os.makedirs(qdir) - image_path = '%s/%s_%d.png' % (qdir, wechat.uuid, int(time.time()*100)) - s = wechat.wx_conf['API_qrcode'] + wechat.uuid - str2qr_image(s, image_path) - return send_file(image_path, mimetype='image/png') - - -@app.route("/group_list") -def group_list(): - """ - @brief list groups - """ - result = wechat.db.select(Constant.TABLE_GROUP_LIST()) - return jsonify({'count': len(result), 'group': result}) - - -@app.route('/group_member_list/') -def group_member_list(g_id): - """ - @brief list group member - @param g_id String - """ - result = wechat.db.select(Constant.TABLE_GROUP_USER_LIST(), 'RoomID', g_id) - return jsonify({'count': len(result), 'member': result}) - - -@app.route('/group_chat_log/') -def group_chat_log(g_name): - """ - @brief list group chat log - @param g_name String - """ - result = wechat.db.select(Constant.TABLE_GROUP_MSG_LOG, 'RoomName', g_name) - return jsonify({'count': len(result), 'chats': result}) - - -@app.route('/upload', methods=['GET', 'POST']) -def upload_file(): - if request.method == 'POST': - def allowed_file(filename): - return '.' in filename and \ - filename.rsplit('.', 1)[1] in Constant.SERVER_UPLOAD_ALLOWED_EXTENSIONS - - j = {'ret': 1, 'msg': ''} - - # check if the post request has the file part - if 'file' not in request.files: - j['msg'] = 'No file part' - return jsonify(j) - - # if user does not select file, browser also - # submit a empty part without filename - file = request.files['file'] - if file.filename == '': - j['msg'] = 'No selected file' - elif file and allowed_file(file.filename): - filename = generate_file_name(file.filename) - file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) - file.save(file_path) - j['ret'] = 0 - j['msg'] = filename - else: - j['msg'] = 'File type not support' - return jsonify(j) - else: - return render_template(Constant.SERVER_PAGE_UPLOAD) - - -@app.route('/send_msg//') -def send_msg(to, msg): - """ - @brief send message to user or gourp - @param to: String, user id or group id - @param msg: String, words - """ - return jsonify({'ret': 0 if wechat.send_text(to, msg) else 1}) - - -@app.route('/send_img//') -def send_img(to, img): - """ - @brief send image to user or gourp - @param to: String, user id or group id - @param img: String, image file name - """ - img_path = os.path.join(app.config['UPLOAD_FOLDER'], img) - return jsonify({'ret': 0 if wechat.send_img(to, img_path) else 1}) - - -@app.route('/send_emot//') -def send_emot(to, emot): - """ - @brief send emotion to user or gourp - @param to: String, user id or group id - @param emot: String, emotion file name - """ - emot_path = os.path.join(app.config['UPLOAD_FOLDER'], emot) - return jsonify({'ret': 0 if wechat.send_emot(to, emot_path) else 1}) - - -@app.route('/send_file//') -def send_file(to, file): - """ - @brief send file to user or gourp - @param to: String, user id or group id - @param file: String, file name - """ - file_path = os.path.join(app.config['UPLOAD_FOLDER'], file) - return jsonify({'ret': 0 if wechat.send_file(to, file_path) else 1}) - - -def mass_send(method, data, func): - j = {'ret': -1, 'unsend_list':[]} - if method == 'POST' and data: - to_list = data['to_list'] - msg = data['msg'] - media_type = data.get('media_type', '') - - if media_type in ['img', 'emot']: - file_path = os.path.join(app.config['UPLOAD_FOLDER'], msg) - response = wechat.webwxuploadmedia(file_path) - if response is not None: - msg = response['MediaId'] - elif media_type == 'file': - file_path = os.path.join(app.config['UPLOAD_FOLDER'], msg) - data = { - 'appid': Constant.API_WXAPPID, - 'title': msg, - 'totallen': '', - 'attachid': '', - 'type': wechat.wx_conf['APPMSGTYPE_ATTACH'], - 'fileext': msg.split('.')[-1], - } - response = wechat.webwxuploadmedia(file_path) - if response is not None: - data['totallen'] = response['StartPos'] - data['attachid'] = response['MediaId'] - else: - Log.error('File upload error') - msg = data - - for groups in split_array(to_list, 20): - for g in groups: - r = func(g, msg) - if not r: - j['unsend_list'].append(g) - time.sleep(1) - - j['ret'] = len(j['unsend_list']) - - return j - - -@app.route('/mass_send_msg/', methods=["GET", "POST"]) -def mass_send_msg(): - """ - @brief send text to mass users or gourps - """ - j = mass_send(request.method, request.json, wechat.send_text) - return jsonify(j) - - -@app.route('/mass_send_img', methods=["GET", "POST"]) -def mass_send_img(): - """ - @brief send iamge to mass users or gourps - """ - j = mass_send(request.method, request.json, wechat.webwxsendmsgimg) - return jsonify(j) - - -@app.route('/mass_send_emot', methods=["GET", "POST"]) -def mass_send_emot(): - """ - @brief send emoticon to mass users or gourps - """ - j = mass_send(request.method, request.json, wechat.webwxsendemoticon) - return jsonify(j) - - -@app.route('/mass_send_file', methods=["GET", "POST"]) -def mass_send_file(): - """ - @brief send file to mass users or gourps - """ - j = mass_send(request.method, request.json, wechat.webwxsendappmsg) - return jsonify(j) - - -def run_server(): - app.run(port=PORT) - -if cm.get('setting', 'server_mode') == 'True': - serverProcess = threading.Thread(target=run_server) - serverProcess.start() - -while True: - try: - wechat.start() - except KeyboardInterrupt: - echo(Constant.LOG_MSG_QUIT) - wechat.exit_code = 1 - else: - Log.error(traceback.format_exc()) - finally: - wechat.stop() - - # send a mail to tell the wxbot is failing - subject = 'wxbot stop message' - log_file = open(eval(cm.get('handler_fileHandler', 'args'))[0], 'r') - mail_content = '
' + str(wechat) + '\n\n-----\nLogs:\n-----\n\n' + ''.join(log_file.readlines()[-100:]) + '
' - sg.send_mail(subject, mail_content, 'text/html') - log_file.close() - - if wechat.exit_code == 0: - echo(Constant.MAIN_RESTART) - else: - # kill process - os.system(Constant.LOG_MSG_KILL_PROCESS % os.getpid()) diff --git a/wxbot_project_py2.7/wx_handler/__init__.py b/wxbot_project_py2.7/wx_handler/__init__.py deleted file mode 100644 index c13710e..0000000 --- a/wxbot_project_py2.7/wx_handler/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -from wechat_msg_processor import WeChatMsgProcessor -from bot import Bot -from sendgrid_mail import SGMail \ No newline at end of file diff --git a/wxbot_project_py2.7/wx_handler/bot.py b/wxbot_project_py2.7/wx_handler/bot.py deleted file mode 100644 index b3420bf..0000000 --- a/wxbot_project_py2.7/wx_handler/bot.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from wechat.utils import * -from config import Constant -#--------------------------------------------------- -import random, time, json -#=================================================== - - -class Bot(object): - - def __init__(self): - self.emoticons = Constant.EMOTICON - self.gifs = [] - self.last_time = time.time() - - def time_schedule(self): - r = '' - now = time.time() - if int(now - self.last_time) > 3600: - self.last_time = now - url_latest = Constant.BOT_ZHIHU_URL_LATEST - url_daily = Constant.BOT_ZHIHU_URL_DAILY - data = get(url_latest) - j = json.loads(data) - story = j['stories'][random.randint(0, len(j['stories'])-1)] - r = story['title'] + '\n' + url_daily + str(story['id']) - return r.encode('utf-8') - - def reply(self, text): - APIKEY = Constant.BOT_TULING_API_KEY - api_url = Constant.BOT_TULING_API_URL % (APIKEY, text, '12345678') - r = json.loads(get(api_url)) - if r.get('code') == 100000 and r.get('text') != Constant.BOT_TULING_BOT_REPLY: - p = random.randint(1, 10) - if p > 3: - return r['text'] - elif p > 1: - # send emoji - if random.randint(1, 10) > 5: - n = random.randint(0, len(self.emoticons)-1) - m = random.randint(1, 3) - reply = self.emoticons[n].encode('utf-8') * m - return reply - return '' diff --git a/wxbot_project_py2.7/wx_handler/sendgrid_mail.py b/wxbot_project_py2.7/wx_handler/sendgrid_mail.py deleted file mode 100644 index 58d4b39..0000000 --- a/wxbot_project_py2.7/wx_handler/sendgrid_mail.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -import sendgrid -from sendgrid.helpers.mail import * -#=================================================== - -class SGMail(object): - - def __init__(self, apikey, from_email, to_email): - self.sg = sendgrid.SendGridAPIClient(apikey=apikey) - self.from_email = Email(from_email) - self.to_email = Email(to_email) - - def send_mail(self, subject, plain_content, type='text/plain'): - content = Content(type, plain_content) - mail = Mail(self.from_email, subject, self.to_email, content) - response = self.sg.client.mail.send.post(request_body=mail.get()) - return response.status_code == 202 diff --git a/wxbot_project_py2.7/wx_handler/wechat_msg_processor.py b/wxbot_project_py2.7/wx_handler/wechat_msg_processor.py deleted file mode 100644 index 4faccb3..0000000 --- a/wxbot_project_py2.7/wx_handler/wechat_msg_processor.py +++ /dev/null @@ -1,252 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -#=================================================== -from wechat.utils import * -from config import ConfigManager -from config import Constant -from config import Log -#--------------------------------------------------- -import os -import time -import json -import re -#=================================================== - - -class WeChatMsgProcessor(object): - """ - Process fetched data - """ - - def __init__(self, db): - self.db = db - self.wechat = None # recieve `WeChat` class instance - # for call some wechat apis - - # read config - cm = ConfigManager() - [self.upload_dir, self.data_dir, self.log_dir] = cm.setup_database() - - def clean_db(self): - """ - @brief clean database, delete table & create table - """ - self.db.delete_table(Constant.TABLE_GROUP_LIST()) - self.db.delete_table(Constant.TABLE_GROUP_USER_LIST()) - self.db.create_table(Constant.TABLE_GROUP_MSG_LOG, Constant.TABLE_GROUP_MSG_LOG_COL) - self.db.create_table(Constant.TABLE_GROUP_LIST(), Constant.TABLE_GROUP_LIST_COL) - self.db.create_table(Constant.TABLE_GROUP_USER_LIST(), Constant.TABLE_GROUP_USER_LIST_COL) - self.db.create_table(Constant.TABLE_RECORD_ENTER_GROUP, Constant.TABLE_RECORD_ENTER_GROUP_COL) - self.db.create_table(Constant.TABLE_RECORD_RENAME_GROUP, Constant.TABLE_RECORD_RENAME_GROUP_COL) - - def handle_wxsync(self, msg): - """ - @brief Recieve webwxsync message, saved into json - @param msg Dict: webwxsync msg - """ - fn = time.strftime(Constant.LOG_MSG_FILE, time.localtime()) - save_json(fn, msg, self.log_dir, 'a+') - - def handle_group_list(self, group_list): - """ - @brief handle group list & saved in DB - @param group_list Array - """ - fn = Constant.LOG_MSG_GROUP_LIST_FILE - save_json(fn, group_list, self.data_dir) - cols = [( - g['NickName'], - g['UserName'], - g['OwnerUin'], - g['MemberCount'], - g['HeadImgUrl'] - ) for g in group_list] - self.db.insertmany(Constant.TABLE_GROUP_LIST(), cols) - - def handle_group_member_list(self, group_id, member_list): - """ - @brief handle group member list & saved in DB - @param member_list Array - """ - fn = group_id + '.json' - save_json(fn, member_list, self.data_dir) - cols = [( - group_id, - m['UserName'], - m['NickName'], - m['DisplayName'], - m['AttrStatus'] - ) for m in member_list] - self.db.insertmany(Constant.TABLE_GROUP_USER_LIST(), cols) - - def handle_group_list_change(self, new_group): - """ - @brief handle adding a new group & saved in DB - @param new_group Dict - """ - self.handle_group_list([new_group]) - - def handle_group_member_change(self, group_id, member_list): - """ - @brief handle group member changes & saved in DB - @param group_id Dict - @param member_list Dict - """ - self.db.delete(Constant.TABLE_GROUP_USER_LIST(), "RoomID", group_id) - self.handle_group_member_list(group_id, member_list) - - def handle_group_msg(self, msg): - """ - @brief Recieve group messages - @param msg Dict: packaged msg - """ - # rename media files - for k in ['image', 'video', 'voice']: - if msg[k]: - t = time.localtime(float(msg['timestamp'])) - time_str = time.strftime("%Y%m%d%H%M%S", t) - # format: 时间_消息ID_群名 - file_name = '/%s_%s_%s.' % (time_str, msg['msg_id'], msg['group_name']) - new_name = re.sub(r'\/\w+\_\d+\.', file_name, msg[k]) - Log.debug('rename file to %s' % new_name) - os.rename(msg[k], new_name) - msg[k] = new_name - - if msg['msg_type'] == 10000: - # record member enter in group - m = re.search(r'邀请(.+)加入了群聊', msg['sys_notif']) - if m: - name = m.group(1) - col_enter_group = ( - msg['msg_id'], - msg['group_name'], - msg['from_user_name'], - msg['to_user_name'], - name, - msg['time'], - ) - self.db.insert(Constant.TABLE_RECORD_ENTER_GROUP, col_enter_group) - - # record rename group - n = re.search(r'(.+)修改群名为“(.+)”', msg['sys_notif']) - if n: - people = n.group(1) - to_name = n.group(2) - col_rename_group = ( - msg['msg_id'], - msg['group_name'], - to_name, - people, - msg['time'], - ) - self.db.insert(Constant.TABLE_RECORD_RENAME_GROUP, col_rename_group) - - # upadte group in GroupList - for g in self.wechat.GroupList: - if g['UserName'] == msg['from_user_name']: - g['NickName'] = to_name - break - - # normal group message - col = ( - msg['msg_id'], - msg['group_owner_uin'], - msg['group_name'], - msg['group_count'], - msg['from_user_name'], - msg['to_user_name'], - msg['user_attrstatus'], - msg['user_display_name'], - msg['user_nickname'], - msg['msg_type'], - msg['emoticon'], - msg['text'], - msg['image'], - msg['video'], - msg['voice'], - msg['link'], - msg['namecard'], - msg['location'], - msg['recall_msg_id'], - msg['sys_notif'], - msg['time'], - msg['timestamp'] - ) - self.db.insert(Constant.TABLE_GROUP_MSG_LOG, col) - - text = msg['text'] - if text and text[0] == '@': - n = trans_coding(text).find(u'\u2005') - name = trans_coding(text)[1:n].encode('utf-8') - if name in [self.wechat.User['NickName'], self.wechat.User['RemarkName']]: - self.handle_command(trans_coding(text)[n+1:].encode('utf-8'), msg) - - def handle_user_msg(self, msg): - """ - @brief Recieve personal messages - @param msg Dict - """ - wechat = self.wechat - - text = trans_coding(msg['text']).encode('utf-8') - uid = msg['raw_msg']['FromUserName'] - - if text == 'test_revoke': # 撤回消息测试 - dic = wechat.webwxsendmsg('这条消息将被撤回', uid) - wechat.revoke_msg(dic['MsgID'], uid, dic['LocalID']) - elif text == 'reply': - wechat.send_text(uid, '自动回复') - - - def handle_command(self, cmd, msg): - """ - @brief handle msg of `@yourself cmd` - @param cmd String - @param msg Dict - """ - wechat = self.wechat - g_id = '' - for g in wechat.GroupList: - if g['NickName'] == msg['group_name']: - g_id = g['UserName'] - - cmd = cmd.strip() - if cmd == 'runtime': - wechat.send_text(g_id, wechat.get_run_time()) - elif cmd == 'test_sendimg': - wechat.send_img(g_id, 'test/emotion/7.gif') - elif cmd == 'test_sendfile': - wechat.send_file(g_id, 'test/Data/upload/shake.wav') - elif cmd == 'test_bot': - # reply bot - # --------- - if wechat.bot: - r = wechat.bot.reply(cmd) - if r: - wechat.send_text(g_id, r) - else: - pass - elif cmd == 'test_emot': - img_name = [ - '0.jpg', '1.jpeg', '2.gif', '3.jpg', '4.jpeg', - '5.gif', '6.gif', '7.gif', '8.jpg', '9.jpg' - ] - name = img_name[int(time.time()) % 10] - emot_path = os.path.join('test/emotion/', name) - wechat.send_emot(g_id, emot_path) - else: - pass - - def check_schedule_task(self): - # update group member list at 00:00 am every morning - t = time.localtime() - if t.tm_hour == 0 and t.tm_min <= 1: - # update group member - Log.debug('update group member list everyday') - self.db.delete_table(Constant.TABLE_GROUP_LIST()) - self.db.delete_table(Constant.TABLE_GROUP_USER_LIST()) - self.db.create_table(Constant.TABLE_GROUP_LIST(), Constant.TABLE_GROUP_LIST_COL) - self.db.create_table(Constant.TABLE_GROUP_USER_LIST(), Constant.TABLE_GROUP_USER_LIST_COL) - self.wechat.fetch_group_contacts() -