├── .gitignore ├── README ├── demo.png ├── demo.py ├── requirements.txt └── weChat ├── __init__.py ├── base.py └── client.py /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | ._* 3 | .Spotlight-V100 4 | .Trashes 5 | Icon? 6 | ehthumbs.db 7 | Thumbs.db 8 | 9 | .DS_Store 10 | */.DS_Store 11 | **/.DS_Store 12 | 13 | .idea/ 14 | app/log/*.txt -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ## 该项目已经过期 2 | 3 | 由于微信早前已经更新规则,必须在用户主动发消息一定时间内回复才行,该接口已经不能用于主动推送消息。 4 | 5 | 如果需要主动推送重要消息,请参考官方消息模板:http://mp.weixin.qq.com/wiki/17/304c1885ea66dbedf7dc170d84999a9d.html 6 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vincenting/weChat-python-sdk/aec8cedbb878d596958cc04e5d286c09386d7ff1/demo.png -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 首先需要在微信公共平台申请帐号 mp.weixin.qq.com 4 | 之后才可以使用下事例 5 | """ 6 | __author__ = 'Vincent Ting' 7 | 8 | from weChat.client import Client 9 | 10 | client = Client(email='xx@gmail.com', password='yourPsw') 11 | 12 | #demo 推送文字信息,其中sendto为关注该帐号的某用户的fakeId 13 | 14 | client.sendTextMsg('xxxxxxx', 'Hello world') 15 | 16 | #demo 推送图片信息,其中sendto为关注该帐号的某用户的fakeId,img为图片的文件路径 17 | 18 | client.sendImgMsg('xxxxxxx', 'E:/demo.png') -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | poster -------------------------------------------------------------------------------- /weChat/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Vincent Ting' -------------------------------------------------------------------------------- /weChat/base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | __author__ = 'Vincent Ting' 4 | 5 | import cookielib 6 | import urllib2 7 | import urllib 8 | import json 9 | import poster 10 | import hashlib 11 | import time 12 | import re 13 | 14 | 15 | class BaseClient(object): 16 | def __init__(self, email=None, password=None): 17 | """ 18 | 登录公共平台服务器,如果失败将报客户端登录异常错误 19 | :param email: 20 | :param password: 21 | :raise: 22 | """ 23 | if not email or not password: 24 | raise ValueError 25 | self.setOpener() 26 | 27 | url_login = "https://mp.weixin.qq.com/cgi-bin/login?lang=zh_CN" 28 | m = hashlib.md5(password[0:16]) 29 | m.digest() 30 | password = m.hexdigest() 31 | body = (('username', email), ('pwd', password), ('imgcode', ''), ('f', 'json')) 32 | try: 33 | msg = json.loads(self.opener.open(url_login, urllib.urlencode(body), timeout=5).read()) 34 | except urllib2.URLError: 35 | raise ClientLoginException 36 | if msg['ErrCode'] not in (0, 65202): 37 | print msg 38 | raise ClientLoginException 39 | self.token = msg['ErrMsg'].split('=')[-1] 40 | time.sleep(1) 41 | 42 | def setOpener(self): 43 | """ 44 | 设置请求头部信息模拟浏览器 45 | """ 46 | self.opener = poster.streaminghttp.register_openers() 47 | self.opener.add_handler(urllib2.HTTPCookieProcessor(cookielib.CookieJar())) 48 | self.opener.addheaders = [('Accept', 'application/json, text/javascript, */*; q=0.01'), 49 | ('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8'), 50 | ('Referer', 'https://mp.weixin.qq.com/'), 51 | ('Cache-Control', 'max-age=0'), 52 | ('Connection', 'keep-alive'), 53 | ('Host', 'mp.weixin.qq.com'), 54 | ('Origin', 'mp.weixin.qq.com'), 55 | ('X-Requested-With', 'XMLHttpRequest'), 56 | ('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36')] 57 | 58 | def _sendMsg(self, sendTo, data): 59 | """ 60 | 基础发送信息的方法 61 | :param sendTo: 62 | :param data: 63 | :return: 64 | """ 65 | if type(sendTo) == type([]): 66 | for _sendTo in sendTo: 67 | self._sendMsg(_sendTo, data) 68 | return 69 | 70 | self.opener.addheaders += [('Referer', 'http://mp.weixin.qq.com/cgi-bin/singlemsgpage?fromfakeid={0}' 71 | '&msgid=&source=&count=20&t=wxm-singlechat&lang=zh_CN'.format(sendTo))] 72 | body = { 73 | 'error': 'false', 74 | 'token': self.token, 75 | 'tofakeid': sendTo, 76 | 'ajax': 1} 77 | body.update(data) 78 | try: 79 | msg = json.loads(self.opener.open("https://mp.weixin.qq.com/cgi-bin/singlesend?t=ajax-response&" 80 | "lang=zh_CN", urllib.urlencode(body), timeout=5).read())['msg'] 81 | except urllib2.URLError: 82 | time.sleep(1) 83 | return self._sendMsg(sendTo, data) 84 | print msg 85 | time.sleep(1) 86 | return msg 87 | 88 | def _uploadImg(self, img): 89 | """ 90 | 根据图片地址来上传图片,返回上传结果id 91 | :param img: 92 | :return: 93 | """ 94 | params = {'uploadfile': open(img, "rb")} 95 | data, headers = poster.encode.multipart_encode(params) 96 | request = urllib2.Request('http://mp.weixin.qq.com/cgi-bin/uploadmaterial?' 97 | 'cgi=uploadmaterial&type=2&token={0}&t=iframe-uploadfile&' 98 | 'lang=zh_CN&formId=file_from_{1}000'.format(self.token, int(time.time())), 99 | data, headers) 100 | result = urllib2.urlopen(request) 101 | find_id = re.compile("\d+") 102 | time.sleep(1) 103 | return find_id.findall(result.read())[-1] 104 | 105 | def _delImg(self, file_id): 106 | """ 107 | 根据图片ID来删除图片 108 | :param file_id: 109 | """ 110 | self.opener.open('http://mp.weixin.qq.com/cgi-bin/modifyfile?oper=del&lang=zh_CN&t=ajax-response', 111 | urllib.urlencode({'fileid': file_id, 112 | 'token': self.token, 113 | 'ajax': 1})) 114 | time.sleep(1) 115 | 116 | def _addAppMsg(self, title, content, file_id, digest='', sourceurl=''): 117 | """ 118 | 上传图文消息 119 | :param title: 120 | :param content: 121 | :param file_id: 122 | :param digest: 123 | :param sourceurl: 124 | :return: 125 | """ 126 | msg = json.loads(self.opener.open( 127 | 'http://mp.weixin.qq.com/cgi-bin/operate_appmsg?token={0}&lang=zh_CN&t=ajax-response&sub=create'.format( 128 | self.token), 129 | urllib.urlencode({'error': 'false', 130 | 'count': 1, 131 | 'AppMsgId': '', 132 | 'title0': title, 133 | 'digest0': digest, 134 | 'content0': content, 135 | 'fileid0': file_id, 136 | 'sourceurl0': sourceurl, 137 | 'token': self.token, 138 | 'ajax': 1})).read()) 139 | time.sleep(1) 140 | return msg['msg'] == 'OK' 141 | 142 | def _getAppMsgId(self): 143 | """ 144 | 获取最新上传id 145 | :return: 146 | """ 147 | msg = json.loads(self.opener.open( 148 | 'http://mp.weixin.qq.com/cgi-bin/operate_appmsg?token={0}&lang=zh_CN&sub=list&t=ajax-appmsgs-fileselect&type=10&pageIdx=0&pagesize=10&formid=file_from_{1}000&subtype=3'.format( 149 | self.token, int(time.time())), 150 | urllib.urlencode({'token': self.token, 151 | 'ajax': 1})).read()) 152 | time.sleep(1) 153 | return msg['List'][0]['appId'] 154 | 155 | def _delAppMsg(self, app_msg_id): 156 | """ 157 | 根据id删除图文 158 | :param app_msg_id: 159 | """ 160 | print self.opener.open('http://mp.weixin.qq.com/cgi-bin/operate_appmsg?sub=del&t=ajax-response', 161 | urllib.urlencode({'AppMsgId': app_msg_id, 162 | 'token': self.token, 163 | 'ajax': 1})).read() 164 | time.sleep(1) 165 | 166 | 167 | class ClientLoginException(Exception): 168 | pass 169 | -------------------------------------------------------------------------------- /weChat/client.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | __author__ = 'Vincent Ting' 3 | 4 | from base import BaseClient 5 | 6 | 7 | class Client(BaseClient): 8 | def sendTextMsg(self, sendTo, content): 9 | """ 10 | 给用户发送文字内容,成功返回True,使用时注意两次发送间隔,不能少于2s 11 | :param sendTo: 12 | :param content: 13 | :return: 14 | """ 15 | msg = self._sendMsg(sendTo, { 16 | 'type': 1, 17 | 'content': content 18 | }) 19 | return msg == 'ok' 20 | 21 | def sendImgMsg(self, sendTo, img): 22 | """ 23 | 主动推送图片信息 24 | :param sendTo: 25 | :param img:图片文件路径 26 | :return: 27 | """ 28 | file_id = self._uploadImg(img) 29 | msg = self._sendMsg(sendTo, { 30 | 'type': 2, 31 | 'content': '', 32 | 'fid': file_id, 33 | 'fileid': file_id 34 | }) 35 | self._delImg(file_id) 36 | return msg == 'ok' 37 | 38 | def sendAppMsg(self, sendTo, title, content, img, digest='', sourceurl=''): 39 | """ 40 | 主动推送图文 41 | :param sendTo: 42 | :param title: 标题 43 | :param content: 正文,允许html 44 | :param img: 45 | :param digest: 摘要 46 | :param sourceurl: 来源地址 47 | :return: 48 | """ 49 | file_id = self._uploadImg(img) 50 | self._addAppMsg(title, content, file_id, digest, sourceurl) 51 | app_msg_id = self._getAppMsgId() 52 | msg = self._sendMsg(sendTo, { 53 | 'type': 10, 54 | 'fid': app_msg_id, 55 | 'appmsgid': app_msg_id 56 | }) 57 | self._delImg(file_id) 58 | self._delAppMsg(app_msg_id) 59 | return msg == 'ok' --------------------------------------------------------------------------------