├── .gitignore ├── MANIFEST.in ├── PyWapFetion ├── AliveKeeper.py ├── Cache.py ├── Errors.py ├── Fetion.py └── __init__.py ├── README.md ├── example.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.cache 3 | test.py 4 | PyWapFetion.egg-info/ 5 | build/ 6 | dist/ 7 | *.zip 8 | *.tar 9 | *.tar.gz 10 | *.jpeg 11 | *.cookies 12 | 13 | #OS X 14 | .DS_Store -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /PyWapFetion/AliveKeeper.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from threading import Thread 3 | from time import sleep 4 | 5 | __all__ = ['AliveKeeper'] 6 | 7 | 8 | class AliveKeeper(Thread): 9 | def __init__(self, fetion, sleeptime=240, Daemon=True, start=True): 10 | self.fetion = fetion 11 | super(Thread, self).__init__() 12 | self.sleeptime = sleeptime 13 | self.setDaemon(Daemon) 14 | if start: 15 | self.start() 16 | 17 | def run(self): 18 | while '登陆' not in self.fetion.open('im/index/indexcenter.action'): 19 | sleep(self.sleeptime) 20 | -------------------------------------------------------------------------------- /PyWapFetion/Cache.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from __future__ import with_statement 3 | from marshal import dump, load 4 | 5 | __all__ = ['Cache'] 6 | 7 | 8 | class Cache(object): 9 | def __init__(self, path): 10 | self.path = path 11 | try: 12 | with open(path, 'rb') as f: 13 | self.dict = load(f) 14 | except: 15 | self.dict = {} 16 | 17 | __getitem__ = get = lambda self, k: self.dict.get(k) 18 | __setitem__ = lambda self, k, id: self.dict.__setitem__(k, id) 19 | __delitem__ = pop = lambda self, k: self.dict.pop(k, None) 20 | __del__ = save = lambda self: dump(self.dict, open(self.path, 'wb')) 21 | -------------------------------------------------------------------------------- /PyWapFetion/Errors.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | 4 | class FetionNotYourFriend(Exception): 5 | pass 6 | 7 | 8 | class FetionCsrfTokenFail(Exception): 9 | pass 10 | 11 | 12 | class FetionLoginFail(Exception): 13 | pass -------------------------------------------------------------------------------- /PyWapFetion/Fetion.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | import os 4 | import time 5 | import json 6 | from PyWapFetion.Errors import * 7 | from re import compile 8 | from PyWapFetion.Cache import Cache 9 | from gzip import GzipFile 10 | 11 | try: 12 | from http.cookiejar import MozillaCookieJar 13 | from urllib.request import Request, build_opener 14 | from urllib.request import HTTPHandler, HTTPCookieProcessor 15 | from urllib.parse import urlencode 16 | from io import StringIO 17 | except ImportError: 18 | # Python 2 19 | input = raw_input 20 | from cookielib import MozillaCookieJar 21 | from urllib2 import Request, build_opener, HTTPHandler, HTTPCookieProcessor 22 | from urllib import urlencode 23 | 24 | try: 25 | from cStringIO import StringIO 26 | except ImportError: 27 | from StringIO import StringIO 28 | IS_PY2 = True 29 | else: 30 | IS_PY2 = False 31 | 32 | idfinder = compile('touserid=(\d*)') 33 | idfinder2 = compile('name="internalid" value="(\d+)"') 34 | csrf_token = compile('') 35 | 36 | __all__ = ['Fetion'] 37 | 38 | 39 | class Fetion(object): 40 | def __init__(self, mobile, password=None, status='0', 41 | cachefile='Fetion.cache', cookiesfile=''): 42 | '''登录状态: 43 | 在线:400 隐身:0 忙碌:600 离开:100 44 | ''' 45 | if cachefile: 46 | self.cache = Cache(cachefile) 47 | 48 | if not cookiesfile: 49 | cookiesfile = '%s.cookies' % mobile 50 | 51 | cookiejar = MozillaCookieJar(filename=cookiesfile) 52 | if not os.path.isfile(cookiesfile): 53 | open(cookiesfile, 'w').write(MozillaCookieJar.header) 54 | 55 | cookiejar.load(filename=cookiesfile) 56 | 57 | cookie_processor = HTTPCookieProcessor(cookiejar) 58 | 59 | self.opener = build_opener(cookie_processor, 60 | HTTPHandler) 61 | self.mobile, self.password = mobile, password 62 | if not self.alive(): 63 | self._login() 64 | cookiejar.save() 65 | 66 | self.changestatus(status) 67 | 68 | def send2self(self, message, time=None): 69 | if time: 70 | htm = self.open('im/user/sendTimingMsgToMyselfs.action', 71 | {'msg': message, 'timing': time}) 72 | else: 73 | htm = self.open('im/user/sendMsgToMyselfs.action', 74 | {'msg': message}) 75 | return '成功' in htm 76 | 77 | def send(self, mobile, message, sm=False): 78 | if mobile == self.mobile: 79 | return self.send2self(message) 80 | return self.sendBYid(self.findid(mobile), message, sm) 81 | 82 | def addfriend(self, mobile, name='xx'): 83 | htm = self.open('im/user/insertfriendsubmit.action', 84 | {'nickname': name, 'number': mobile, 'type': '0'}) 85 | return '成功' in htm 86 | 87 | def alive(self): 88 | htm = self.open('im/index/indexcenter.action') 89 | return '心情' in htm or '正在登录' in htm 90 | 91 | def deletefriend(self, id): 92 | htm = self.open('im/user/deletefriendsubmit.action?touserid=%s' % id) 93 | return '删除好友成功!' in htm 94 | 95 | def changestatus(self, status='0'): 96 | url = 'im5/index/setLoginStatus.action?loginstatus=' + status 97 | for x in range(2): 98 | htm = self.open(url) 99 | return 'success' in htm 100 | 101 | def logout(self, *args): 102 | self.opener.open('http://f.10086.cn/im/index/logoutsubmit.action') 103 | 104 | __enter__ = lambda self: self 105 | __exit__ = __del__ = logout 106 | 107 | def _login(self): 108 | '''登录 109 | 若登录成功,返回True 110 | 若登录失败,抛出FetionLoginFail异常 111 | 注意:此函数可能需要从标准输入中读取验证码 112 | ''' 113 | data = { 114 | 'm': self.mobile, 115 | 'pass': self.password, 116 | } 117 | htm = self.open('/im5/login/loginHtml5.action', data) 118 | resp = json.loads(htm) 119 | if resp.get('checkCodeKey', 'false') == 'true': 120 | request = Request('http://f.10086.cn/im5/systemimage/verifycode%d.jpeg' % time.time()) 121 | img = self.opener.open(request).read() 122 | with open('verifycode.jpeg', 'wb') as verifycodefile: 123 | verifycodefile.write(img) 124 | 125 | captchacode = input('captchaCode:') 126 | data['captchaCode'] = captchacode 127 | htm = self.open('/im5/login/loginHtml5.action', data) 128 | resp = json.loads(htm) 129 | 130 | if resp['loginstate'] == '200': 131 | return True 132 | else: 133 | raise FetionLoginFail(resp['tip']) 134 | 135 | def sendBYid(self, id, message, sm=False): 136 | url = 'im/chat/sendShortMsg.action?touserid=%s' % id 137 | if sm: 138 | url = 'im/chat/sendMsg.action?touserid=%s' % id 139 | htm = self.open(url, 140 | {'msg': message, 'csrfToken': self._getcsrf(id)}) 141 | if '对方不是您的好友' in htm: 142 | raise FetionNotYourFriend 143 | return '成功' in htm 144 | 145 | def _getid(self, mobile): 146 | htm = self.open('im/index/searchOtherInfoList.action', 147 | {'searchText': mobile}) 148 | try: 149 | return idfinder.findall(htm)[0] 150 | except IndexError: 151 | try: 152 | return idfinder2.findall(htm)[0] 153 | except: 154 | return None 155 | except: 156 | return None 157 | 158 | def findid(self, mobile): 159 | if hasattr(self, 'cache'): 160 | id = self.cache[mobile] 161 | if not id: 162 | self.cache[mobile] = id = self._getid(mobile) 163 | return id 164 | return self._getid(mobile) 165 | 166 | def open(self, url, data=''): 167 | data = urlencode(data) 168 | if not IS_PY2: 169 | data = data.encode() 170 | 171 | request = Request('http://f.10086.cn/%s' % url, data=data) 172 | htm = self.opener.open(request).read() 173 | try: 174 | htm = GzipFile(fileobj=StringIO(htm)).read() 175 | finally: 176 | if IS_PY2: 177 | return htm 178 | else: 179 | return htm.decode() 180 | 181 | def _getcsrf(self, id=''): 182 | if hasattr(self, 'csrf'): 183 | return self.csrf 184 | url = ('im/chat/toinputMsg.action?touserid=%s&type=all' % id) 185 | htm = self.open(url) 186 | try: 187 | self.csrf = csrf_token.findall(htm)[0] 188 | return self.csrf 189 | except IndexError: 190 | print(htm) 191 | raise FetionCsrfTokenFail 192 | -------------------------------------------------------------------------------- /PyWapFetion/__init__.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | from __future__ import with_statement, absolute_import 3 | 4 | __name__ = 'PyWapFetion' 5 | __version__ = '0.9.6' 6 | __author__ = 'whtsky' 7 | __website__ = 'http://github.com/whtsky/PyWapFetion' 8 | __license__ = 'MIT' 9 | 10 | from PyWapFetion.Fetion import Fetion 11 | 12 | 13 | def send2self(mobile, password, message): 14 | with Fetion(mobile, password) as x: 15 | x.send2self(message) 16 | 17 | 18 | def send(mobile, password, to, message): 19 | with Fetion(mobile, password) as x: 20 | x.send(to, message) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyWapFeion 2 | ========== 3 | 4 | **[由于种种原因](http://whouz.com/PyWapFetion-已经停止维护了/), PyWapFetion 已经不再被维护了;希望接手此项目的人请与我( whtsky#gmail )联系,谢谢** 5 | 6 | PyWapFetion是一个飞信的Python模块,使用Wap飞信协议。 7 | 因为目前没有看到比较好的Python飞信模块(PyFetion虽然很强大,但是基于电脑客户端的协议,容易被各种验证码问题所困扰),所以自己动手写了一个。 8 | 9 | 安装 10 | --------------- 11 | 12 | 在终端下输入(*nix): `sudo pip install PyWapFetion` 或者 `sudo easy_install -U PyWapFetion` 13 | 或者把源码下载下来,运行:`python setup.py install` 14 | 15 | 使用 16 | --------------- 17 | 18 | from PyWapFetion import Fetion, send2self, send 19 | 20 | #快速发送: 21 | send2self('手机号', '密码', '信息') 22 | send('手机号', '密码', '接收方手机号', '信息') 23 | 24 | #---------------------------------------------------------------------- 25 | myfetion = Fetion('手机号', '密码') 26 | 27 | myfetion.changestatus('0') # 改变在线状态 28 | 29 | myfetion.send2self('发给自己的东西') 30 | myfetion.findid('输入手机号,返回飞信ID') 31 | myfetion.sendBYid('飞信ID', '消息') 32 | myfetion.send('手机号', '消息', sm=True) # 发送飞信信息 33 | #通过设定sm=True强制发送短信(sm=ShortMessage) 34 | myfetion.send('昵称', '消息') # 你也可以这么干 35 | myfetion.addfriend('手机号', '你的昵称(5字以内)') 36 | myfetion.send(['手机号1', '手机号2', '这就是传说中的群发'], '消息') 37 | # 成功返回True,失败返回False 38 | 39 | myfetion.send2self('这个是发给自己的定时短信', time='201111201120') 40 | '''发送定时短信。格式:年月日小时分钟 41 | 如:2011年11月20日11时14分:201111201144 42 | 2012年11月11日11时11分:201211111111 43 | 注意:时间允许范围:当前时刻向后10分钟-向后1年 44 | 如:当前时间:2011年11月20日 11:17 45 | 效时间范围是:2011年11月20日11:27分到2012年11月20日11:27分 46 | ''' 47 | 48 | myfetion.changeimpresa('改签名') 49 | myfetion.alive() # 保持在线,10分钟以上无操作会被判定为离线 50 | #如果你想要自动保持在线,那么: 51 | from PyWapFetion.AliveKeeper import AliveKeeper 52 | AliveKeeper(myfetion) 53 | 54 | myfetion.deletefriend('要删除的好友ID') 55 | myfetion.addblacklist('要拉黑的好友ID') 56 | myfetion.relieveblack('要解除拉黑的好友ID') 57 | 58 | myfetion.logout() 59 | # ----------------------------------------------------------------------- 60 | 61 | with Fetion('手机号', '密码') as f: # 其实你也可以用with,这样更方便一点 62 | f.send2self('xxxx') -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from __future__ import with_statement 4 | from PyWapFetion import Fetion, send2self, send 5 | # 仅作参考,详细了解请参考源码 6 | 7 | # 快速发送: 8 | send2self('手机号', '密码', '信息') 9 | send('手机号', '密码', '接收方手机号', '信息') 10 | 11 | #---------------------------------------------------------------------- 12 | myfetion = Fetion('手机号', '密码') 13 | 14 | myfetion.changestatus('0') # 改变在线状态 15 | 16 | myfetion.send2self('发给自己的东西') 17 | myfetion.findid('输入手机号,返回飞信ID') 18 | myfetion.sendBYid('飞信ID', '消息') 19 | myfetion.send('手机号', '消息', sm=True) # 发送飞信信息 20 | # 通过设定sm=True强制发送短信(sm=ShortMessage) 21 | myfetion.send('昵称', '消息') # 你也可以这么干 22 | myfetion.addfriend('手机号', '你的昵称(5字以内)') 23 | myfetion.send(['手机号1', '手机号2', '这就是传说中的群发'], '消息') 24 | # 成功返回True,失败返回False 25 | 26 | myfetion.send2self('这个是发给自己的定时短信', time='201111201120') 27 | '''发送定时短信。格式:年月日小时分钟 28 | 如:2011年11月20日11时14分:201111201144 29 | 2012年11月11日11时11分:201211111111 30 | 注意:时间允许范围:当前时刻向后10分钟-向后1年 31 | 如:当前时间:2011年11月20日 11:17 32 | 有效时间范围是:2011年11月20日11:27分到2012年11月20日11:27分 33 | ''' 34 | 35 | myfetion.changeimpresa('改签名') 36 | myfetion.alive() # 保持在线,10分钟以上无操作会被判定为离线 37 | # 如果你想要自动保持在线,那么: 38 | from PyWapFetion.AliveKeeper import AliveKeeper 39 | AliveKeeper(myfetion) 40 | 41 | myfetion.deletefriend('要删除的好友ID') 42 | myfetion.addblacklist('要拉黑的好友ID') 43 | myfetion.relieveblack('要解除拉黑的好友ID') 44 | 45 | myfetion.logout() 46 | # ----------------------------------------------------------------------- 47 | 48 | with Fetion('手机号', '密码') as f: # 其实你也可以用with,这样更方便一点 49 | f.send2self('xxxx') 50 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | release = sdist --formats=zip,gztar register upload -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #coding=utf-8 2 | 3 | import os 4 | import codecs 5 | 6 | from setuptools import setup 7 | import PyWapFetion 8 | 9 | readme = os.path.join(os.path.dirname(__file__), "README.md") 10 | 11 | setup( 12 | name=PyWapFetion.__name__, 13 | version=PyWapFetion.__version__, 14 | packages=['PyWapFetion'], 15 | keywords='library mobile fetion', 16 | author=PyWapFetion.__author__, 17 | author_email='whtsky@gmail.com', 18 | 19 | url=PyWapFetion.__website__, 20 | description='A simple python lib for WapFetion', 21 | long_description=codecs.open(readme, encoding='utf8').read(), 22 | license=PyWapFetion.__license__, 23 | classifiers=[ 24 | 'Development Status :: 4 - Beta', 25 | 'Environment :: Console', 26 | 'Intended Audience :: Developers', 27 | 'License :: OSI Approved :: MIT License', 28 | 'Natural Language :: English', 29 | 'Natural Language :: Chinese (Simplified)', 30 | 'Operating System :: OS Independent', 31 | 'Programming Language :: Python :: 2', 32 | 'Programming Language :: Python :: 2.5', 33 | 'Programming Language :: Python :: 2.6', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.2', 37 | 'Programming Language :: Python :: 3.3', 38 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries', 39 | 'Topic :: Utilities', 40 | 'Topic :: Software Development :: Libraries :: Python Modules', 41 | ], 42 | zip_safe=False, 43 | ) 44 | --------------------------------------------------------------------------------