├── .gitignore ├── FlaskApp ├── __init__.py ├── sae_py.py └── views.py ├── Handlers ├── __init__.py ├── api_handler.py └── weixin_handler.py ├── OnHomeward.jpg ├── README.md ├── Test.py ├── WeiXinCore ├── WeiXin.py ├── WeiXinMsg.py └── __init__.py ├── config.yaml ├── index.py └── index.wsgi /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | .idea/ 61 | tools.py 62 | -------------------------------------------------------------------------------- /FlaskApp/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | app = Flask(__name__) 3 | 4 | import FlaskApp.views 5 | from WeiXinCore.WeiXin import echo 6 | try: 7 | import sae 8 | import FlaskApp.sae_py 9 | except: 10 | print("no in sae.") -------------------------------------------------------------------------------- /FlaskApp/sae_py.py: -------------------------------------------------------------------------------- 1 | import MySQLdb 2 | import sae 3 | from sae.const import (MYSQL_HOST, MYSQL_HOST_S, 4 | MYSQL_PORT, MYSQL_USER, MYSQL_PASS, MYSQL_DB 5 | ) 6 | 7 | from flask import Flask, g, request 8 | from FlaskApp import app 9 | #app = Flask(__name__) 10 | #app.debug = True 11 | 12 | @app.before_request 13 | def before_request(): 14 | g.db = MySQLdb.connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, 15 | MYSQL_DB, port=int(MYSQL_PORT)) 16 | 17 | 18 | @app.teardown_request 19 | def teardown_request(exception): 20 | if hasattr(g, 'db'): g.db.close() 21 | 22 | # @app.route('/') 23 | # def hello(): 24 | # return "Hello, I'm LinHy!" 25 | 26 | @app.route('/douban', methods=['GET', 'POST']) 27 | def douban(): 28 | return str( dict(request.args)) 29 | 30 | 31 | 32 | 33 | @app.route('/fankui', methods=['GET', 'POST']) 34 | def greeting(): 35 | html = '' 36 | 37 | if request.method == 'POST': 38 | c = g.db.cursor() 39 | c.execute("insert into demo(text) values(%s)", (request.form['text'])) 40 | 41 | html += """ 42 |
43 |
44 |
45 |
46 | """ 47 | c = g.db.cursor() 48 | c.execute('select time,content from fankui') 49 | msgs = list(c.fetchall()) 50 | msgs.reverse() 51 | for row in msgs: 52 | #ltime = time.localtime(float(row[0])) 53 | #dt = time.strftime("%Y-%m-%d %H:%M:%S", ltime) 54 | #html += '

' + str(dt) + '<->' + row[-1] + '

' 55 | html += '

' + row[-1] + '

' 56 | return html -------------------------------------------------------------------------------- /FlaskApp/views.py: -------------------------------------------------------------------------------- 1 | from FlaskApp import app 2 | 3 | 4 | 5 | 6 | @app.route('/') 7 | def index(): 8 | return "Hello, World!" 9 | 10 | # @app.route('/wx', methods = ['GET', 'POST'] ) 11 | # def wx(): 12 | # from WeiXinCore.WeiXin import check_signature,echo 13 | # from flask import request 14 | # if not check_signature(request.args) and not app.debug: 15 | # return "" 16 | # return echo -------------------------------------------------------------------------------- /Handlers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lhysrc/weixin_python/af45232bee7485294cb9eb634ffc97cc1c91b983/Handlers/__init__.py -------------------------------------------------------------------------------- /Handlers/api_handler.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | import json 3 | import urllib2 4 | import zlib 5 | from urllib import * 6 | 7 | from WeiXinCore.WeiXinMsg import * 8 | 9 | 10 | def getJson(url): 11 | request = urllib2.Request(url) 12 | request.add_header('Accept-encoding', 'gzip') 13 | opener = urllib2.build_opener() 14 | response = opener.open(request) 15 | html = response.read()#.decode('gbk').encode('utf-8') 16 | gzipped = response.headers.get('Content-Encoding') 17 | if gzipped: 18 | html = zlib.decompress(html, 16+zlib.MAX_WBITS) 19 | return json.loads(html) 20 | #resp = urllib2.urlopen(url) 21 | #return json.loads(resp.read()) 22 | 23 | def youdao(text): 24 | url = 'http://fanyi.youdao.com/openapi.do?keyfrom=onHomeward&key=2010176806&type=data&doctype=json&version=1.1&q=%s' \ 25 | % quote_plus(text.encode('utf-8')) 26 | 27 | description = getJson(url)# json.loads(html) 28 | 29 | #resp = urllib2.urlopen(url) 30 | #description = json.loads(resp.read()) 31 | if description['errorCode'] is not 0: 32 | return u'**无法翻译**' 33 | return u' '.join(description['translation']) 34 | #result = [] 35 | #for k in description['subjects']: 36 | # item = {} 37 | # item["title"] = k['title']#.encode('gbk') 38 | # result.append(item) 39 | #return result 40 | 41 | 42 | 43 | def douban_dianying(text): 44 | url = 'http://api.douban.com/v2/movie/search?q=%s' % quote_plus(text.encode('utf-8')) 45 | description = getJson(url) 46 | 47 | if description['total'] is 0: 48 | return '' 49 | res = description['subjects'] 50 | items = [] 51 | for i in res: 52 | title = i['title'] 53 | rating = i['rating']['average'] 54 | picurl = i['images']['medium'] 55 | url = i['alt'] 56 | genres = '|'.join(i['genres']) 57 | items.append(NewsItem(u"%s (%s)" % (title,rating),genres,picurl,url)) 58 | return items[:5] 59 | -------------------------------------------------------------------------------- /Handlers/weixin_handler.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | 3 | from api_handler import * 4 | 5 | TOKEN = 'weixin' 6 | def onText(wxmsg): 7 | '''收到文本 8 | Content 文本消息内容''' 9 | inTxt = wxmsg.Content 10 | #return wxmsg.resp_text(wxmsg['FromUserName']) 11 | 12 | if inTxt.lower().startswith('fy'): 13 | return wxmsg.resp_text(youdao(inTxt[2:])) 14 | elif inTxt.startswith('?') or inTxt.startswith(u'?'): 15 | return wxmsg.resp_text(u'''通过发送以下列字符开头的消息可查询相关信息: 16 | fy 翻译(来自有道) 17 | dy 电影(来自豆瓣) 18 | 无前缀默认为查询电影''') 19 | 20 | # elif inTxt.startswith('!') or inTxt.startswith(u'!'): 21 | # c = g.db.cursor() 22 | # c.execute("insert into fankui(userid,time,content) values(%s,%s,%s)", \ 23 | # (wxmsg['FromUserName'],wxmsg['CreateTime'],inTxt[1:].encode('utf-8'))) 24 | # return wxmsg.resp_text(u'反馈已记录。') 25 | 26 | #c.execute('select content from fankui') 27 | #msgs = list(c.fetchall()) 28 | #msgs.reverse() 29 | #res = '' 30 | #for row in msgs: 31 | # res += row[-1] + ',' 32 | #return wxmsg.resp_text(res) 33 | 34 | 35 | 36 | else:# 37 | if inTxt.startswith('dy'): 38 | inTxt = inTxt[2:] 39 | newsItems = douban_dianying(inTxt) 40 | return wxmsg.resp_text(u'找不到') if not newsItems else wxmsg.resp_news(newsItems) 41 | 42 | #news1 = NewsItem(wxmsg.Content,youdao(wxmsg.Content),"","") 43 | #news2 = NewsItem("title2","yyyyyyyyyyy","picurl","url") 44 | #newsItems = [news1] 45 | #return wxmsg.resp_news(newsItems) 46 | 47 | def onImage(wxmsg): 48 | '''收到图片 49 | PicUrl 图片链接 50 | MediaId 图片消息媒体id,可以调用多媒体文件下载接口拉取数据。''' 51 | #return wxmsg.resp_music('Sorry','对不起,我还识别不了,来听首歌吧。',r'http://7s1r1i.com1.z0.glb.clouddn.com/小皮%20-%20村庄.mp3','') 52 | return wxmsg.resp_text(u'对不起,我还识别不了……') 53 | 54 | def onVoice(wxmsg): 55 | '''收到语音 56 | MediaId 语音消息媒体id,可以调用多媒体文件下载接口拉取数据。 57 | Format 语音格式,如amr,speex等 58 | Recognition为语音识别结果''' 59 | return wxmsg.resp_text(wxmsg.Recognition if wxmsg.Recognition is not 'None' else u"没听懂……") 60 | 61 | def onVideo(wxmsg): 62 | '''收到视频 63 | MediaId 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 64 | ThumbMediaId 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。''' 65 | return wxmsg.resp_text(u'对不起,我还识别不了……') 66 | 67 | def onShortVideo(wxmsg): 68 | '''收到小视频''' 69 | return wxmsg.resp_text(u'对不起,我还识别不了……') 70 | 71 | def onLocation(wxmsg): 72 | '''收到位置信息 73 | Location_X 地理位置维度 74 | Location_Y 地理位置经度 75 | Scale 地图缩放大小 76 | Label 地理位置信息''' 77 | txt = u"这是您所在位置:\nX:%s\nY:%s" % (wxmsg['Location_X'],wxmsg['Location_Y']) 78 | return wxmsg.resp_text(txt) 79 | 80 | def onLink(wxmsg): 81 | '''收到链接 82 | Title 消息标题 83 | Description 消息描述 84 | Url 消息链接''' 85 | return wxmsg.resp_text('url') 86 | 87 | def onSubscribe(wxmsg): 88 | '''关注''' 89 | return wxmsg.resp_text(u'感谢您的关注。你可以发“?”给我查看帮助。') 90 | 91 | def onUnsubscribe(wxmsg): 92 | '''取消关注''' 93 | return wxmsg.resp_text(u'oh,漏,你还没说为什么!') 94 | 95 | def onScan(wxmsg): 96 | '''扫描二维码''' 97 | return wxmsg.resp_text(wxmsg.self.Ticket) 98 | 99 | def onClick(wxmsg): 100 | '''点击菜单拉取消息时 101 | EventKey 事件KEY值,与自定义菜单接口中KEY值对应''' 102 | return wxmsg.resp_text('onClick') 103 | 104 | def onEventLocation(wxmsg): 105 | '''用户同意上报地理位置后,每次进入公众号会话时,都会在进入时上报地理位置 106 | 或在进入会话后每5秒上报一次地理位置,公众号可以在公众平台网站中修改以上设置。 107 | 上报地理位置时,微信会将上报地理位置事件推送到开发者填写的URL 108 | Latitude 地理位置纬度 109 | Longitude 地理位置经度 110 | Precision 地理位置精度''' 111 | return wxmsg.resp_text('xy') 112 | 113 | def onView(wxmsg): 114 | '''点击菜单跳转链接 115 | EventKey 事件KEY值,设置的跳转URL''' 116 | return wxmsg.resp_text('onView') 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /OnHomeward.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lhysrc/weixin_python/af45232bee7485294cb9eb634ffc97cc1c91b983/OnHomeward.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # weixin_python 2 | 微信公众号开发 3 | 基于python以及Flask 4 | ## 使用方法 5 | 6 | ### 修改weixin_handler文件: 7 | - 更改`\Handlers\weixin_handler.py`下的`TOKEN` 8 | - 实现`Handlers`下的`weixin_handler`相关方法 9 | 10 | ### 可直接上传至新浪sae使用 11 | - 修改`config.yaml`的`name`为自己的`appname` 12 | 13 | ## 尝试 14 | 可扫描关注此公众号了解功能。 15 | ![](OnHomeward.jpg) -------------------------------------------------------------------------------- /Test.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | 3 | 4 | from WeiXinCore.WeiXinMsg import * 5 | 6 | 7 | weixin = WeiXinMsg(u''' 8 | 9 | 10 | 11 | 1357290913 12 | 13 | 14 | 15 | 16 | 1234567890123456 17 | 18 | ''') 19 | 20 | def onEvent(wxmsg): 21 | print 'event' 22 | 23 | def onText(wxmsg): 24 | print 'text' 25 | 26 | def onImage(wxmsg): 27 | pass 28 | def onVoice(wxmsg): 29 | print 'voice' 30 | def onLocation(wxmsg): 31 | pass 32 | 33 | def onLink(wxmsg): 34 | pass 35 | Response = { 36 | "event":onEvent, 37 | "text":onText, 38 | "voice":onVoice, 39 | 40 | } 41 | 42 | print '\n'.join([k+':'+str(v) for k,v in weixin.j.items()]) 43 | 44 | print weixin['MsgType'] 45 | 46 | 47 | print weixin.j['MsgType'] 48 | 49 | 50 | Response[weixin.MsgType]() 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | # news1 = NewsItem("title1","wwwwwwwwwwwwwww","picurl1","url") 61 | # news2 = NewsItem("title2","wwwwwwwwwwwwwww","picurl2","url") 62 | # news3 = NewsItem("title3","wwwwwwwwwwwwwww","picurl3","url") 63 | 64 | # news_items = [news1,news2,news3] 65 | 66 | # print ''.join([str(i) for i in news_items]) 67 | 68 | -------------------------------------------------------------------------------- /WeiXinCore/WeiXin.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | #import time 3 | #import MySQLdb 4 | from flask import Flask, g, request, make_response 5 | import hashlib 6 | #import xml.etree.ElementTree as ET 7 | #import urllib2 8 | #import json 9 | 10 | 11 | 12 | from Handlers.weixin_handler import * 13 | #from index import app 14 | #from WeiXin.weixin_handler import * 15 | 16 | EventType ={ 17 | "subscribe":onSubscribe, 18 | "unsubscribe":onUnsubscribe, 19 | "scan":onScan, 20 | "location":onEventLocation, 21 | "click":onClick, 22 | "view":onView 23 | } 24 | #def onEvent(wxmsg): 25 | # return EventType[wxmsg.Event](wxmsg) if EventType.has_key(wxmsg.Event) else '' 26 | Response = { 27 | "event":lambda wxmsg:EventType[wxmsg.Event](wxmsg) \ 28 | if EventType.has_key(wxmsg.Event) else '', 29 | "text":onText, 30 | "voice":onVoice, 31 | "image":onImage, 32 | "video":onVideo, 33 | "shortvideo":onShortVideo, 34 | "location":onLocation, 35 | "link":onLink, 36 | } 37 | 38 | 39 | 40 | #print(youdao(u'who')) 41 | 42 | def check_signature(request_args): 43 | query = request.args 44 | signature = query.get('signature', '') 45 | timestamp = query.get('timestamp', '') 46 | nonce = query.get('nonce', '') 47 | #echostr = query.get('echostr', '') 48 | s = [timestamp, nonce, TOKEN] 49 | s.sort() 50 | s = ''.join(s) 51 | return hashlib.sha1(s).hexdigest() == signature 52 | 53 | from FlaskApp import app 54 | @app.route('/wx', methods = ['GET', 'POST'] ) 55 | def echo(): 56 | if not app.debug and not check_signature(request.args): 57 | #不在Debug模式下,则需要验证。 58 | return "" 59 | if request.method == 'GET': 60 | return make_response(request.args.get('echostr', '')) 61 | else: 62 | wxmsg = WeiXinMsg(request.data) 63 | respXml = Response[wxmsg.MsgType](wxmsg) if wxmsg.MsgType in Response else '' 64 | #return respXml 65 | response = make_response(respXml) 66 | response.content_type = 'application/xml' 67 | return response 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /WeiXinCore/WeiXinMsg.py: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | 3 | import xml.etree.ElementTree as ET 4 | import time 5 | 6 | 7 | class NewsItem(object): 8 | def __init__(self, title,desc,pic_url,url): 9 | self.title = title 10 | self.desc = desc 11 | self.pic_url = pic_url 12 | self.url = url 13 | def __str__(self): 14 | template = u''' 15 | <![CDATA[%s]]> 16 | 17 | 18 | 19 | ''' 20 | return template % (self.title,self.desc,self.pic_url,self.url) 21 | 22 | 23 | 24 | class WeiXinMsg(object): 25 | def __init__(self, xml_body=None): 26 | self.xml_body = xml_body#unicode(xml_body).encode("utf-8") 27 | root = ET.fromstring(self.xml_body) 28 | 29 | self.j={} 30 | for child in root: 31 | if child.tag == 'CreateTime': 32 | value = long(child.text) 33 | else: 34 | value = child.text 35 | self.j[child.tag] = value 36 | self.ToUserName = self.j['FromUserName'] 37 | self.FromUserName = self.j['ToUserName'] 38 | self.MsgType = self.j['MsgType'] 39 | 40 | self.MsgId = self.j['MsgId'] if self.j.has_key('MsgId') else '' 41 | self.Content = self.j['Content'] if self.j.has_key('Content') else '' 42 | self.PicUrl = self.j['PicUrl'] if self.j.has_key('PicUrl') else '' 43 | self.MediaId = self.j['MediaId'] if self.j.has_key('MediaId') else '' 44 | self.Recognition = self.j['Recognition'] if self.j.has_key('Recognition') else '' 45 | self.Format = self.j['Format'] if self.j.has_key('Format') else '' 46 | self.ThumbMediaId = self.j['ThumbMediaId'] if self.j.has_key('ThumbMediaId') else '' 47 | self.Location_X = self.j['Location_X'] if self.j.has_key('Location_X') else '' 48 | self.Location_Y = self.j['Location_Y'] if self.j.has_key('Location_Y') else '' 49 | self.Scale = self.j['Scale'] if self.j.has_key('Scale') else '' 50 | self.Label = self.j['Label'] if self.j.has_key('Label') else '' 51 | self.Title = self.j['Title'] if self.j.has_key('Title') else '' 52 | self.Description = self.j['Description'] if self.j.has_key('Description') else '' 53 | self.Url = self.j['Url'] if self.j.has_key('Url') else '' 54 | self.EventKey = self.j['EventKey'] if self.j.has_key('EventKey') else '' 55 | self.Event = self.j['Event'].lower() if self.j.has_key('Event') else '' 56 | self.Ticket = self.j['Ticket'].lower() if self.j.has_key('Ticket') else '' 57 | 58 | # # ToUserName 开发者微信号 59 | # # FromUserName 发送方帐号(一个OpenID) 60 | # # CreateTime 消息创建时间 (整型) 61 | # # MsgType 消息类型 62 | # # Content 文本消息内容 63 | # # MsgId 消息id,64位整型 64 | 65 | # # PicUrl 图片链接 66 | # # MediaId 图片消息媒体id,可以调用多媒体文件下载接口拉取数据。 67 | # # MediaId 语音消息媒体id,可以调用多媒体文件下载接口拉取数据。 68 | # # Format 语音格式,如amr,speex等 69 | # # MediaId 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 70 | # # ThumbMediaId 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 71 | # # MediaId 视频消息媒体id,可以调用多媒体文件下载接口拉取数据。 72 | # # ThumbMediaId 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据。 73 | # # Location_X 地理位置维度 74 | # # Location_Y 地理位置经度 75 | # # Scale 地图缩放大小 76 | # # Label 地理位置信息 77 | # # Title 消息标题 78 | # # Description 消息描述 79 | # # Url 消息链接 80 | 81 | 82 | def __getitem__(self,name): 83 | return self.j[name] if self.j.has_key(name) else '' 84 | 85 | 86 | def resp_text(self,text,funcFlag=0): 87 | template = u''' 88 | 89 | 90 | %s 91 | 92 | 93 | %s 94 | ''' 95 | return template % (self.ToUserName,self.FromUserName,int(time.time()),text,funcFlag) 96 | 97 | 98 | def resp_news(self,news_items,funcFlag=0): 99 | template=u''' 100 | 101 | 102 | %s 103 | 104 | %s 105 | 106 | %s 107 | 108 | %s 109 | ''' 110 | return template % (self.ToUserName,self.FromUserName,int(time.time()),len(news_items),\ 111 | (u''.join([unicode(i) for i in news_items])),funcFlag) 112 | 113 | 114 | 115 | def resp_music(self, title, desc, music_url, hq_music_url, funcFlag=0): 116 | '''回复音乐''' 117 | template=u''' 118 | 119 | 120 | %s 121 | 122 | 123 | <![CDATA[%s]]> 124 | 125 | 126 | 127 | 128 | %s 129 | ''' 130 | return template % (self.ToUserName, self.FromUserName, int(time.time()), \ 131 | title, desc, music_url, hq_music_url, funcFlag) 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /WeiXinCore/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lhysrc/weixin_python/af45232bee7485294cb9eb634ffc97cc1c91b983/WeiXinCore/__init__.py -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | name: linhy 2 | version: 2 -------------------------------------------------------------------------------- /index.py: -------------------------------------------------------------------------------- 1 | #import WeiXinCore.WeiXin 2 | import sys 3 | reload(sys) 4 | sys.setdefaultencoding('utf-8') 5 | from FlaskApp import app 6 | 7 | 8 | app.run(debug=True,port=5000) 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /index.wsgi: -------------------------------------------------------------------------------- 1 | #coding:utf-8 2 | #def application(environ, start_response): 3 | # start_response('200 ok', [('content-type', 'text/plain')]) 4 | # return ['Hello, SAE!'] 5 | 6 | from FlaskApp import app,sae_py 7 | 8 | import sys 9 | reload(sys) 10 | sys.setdefaultencoding('utf-8') 11 | 12 | 13 | 14 | import threading 15 | 16 | application = sae_py.sae.create_wsgi_app(app) 17 | #app.run() 18 | --------------------------------------------------------------------------------