可可熊版飞信

090224增补:朗儿版本

朗儿 <[email protected]>
reply-to        [email protected]
to      python-cn`CPyUG`华蟒用户组 <[email protected]>
date    Tue, Feb 24, 2009 at 17:38
subject [CPyUG:79525] 发一个自己写的小程序,用于通过飞信接收发送邮件

[attachment:py_mail_sms.zip]

消息

<[email protected]>
to      zeuux-python <[email protected]>
date    Sat, Jan 3, 2009 at 10:00
subject [zeuux-python] Cocobear用纯Python实现飞信协议

Linux下使用飞信有很多方式,可以安装pidgin的插件,也可以安装其他客户端。 pidgin的飞信插件最新是v0.98,可以从sourceforge.net上下载到源代码

不过作者从10月6日好后好像再没有更新过,他最近在开发一个python的独立飞信客户端,等后期再技术回流给插件版本。

不过不是开源的 :< 作者封装了一个 .a 的二进制文件,公开接口函数。

可可熊

俺们的可可熊(http://cocobear.info),最近用纯Python实现了飞信协议,OPEN SOURCE :)

PyFetion.py

   1 #!/usr/bin/env python
   2 # -*- coding: utf-8 -*-
   3 #Using GPL v2
   4 #Author: [email protected]
   5 
   6 import urllib2
   7 import urllib
   8 import cookielib
   9 import sys,re
  10 import binascii
  11 import hashlib
  12 import socket
  13 
  14 from hashlib import md5
  15 from hashlib import sha1
  16 from uuid import uuid1
  17 
  18 
  19 FetionVer = "2008"
  20 #"SIPP" USED IN HTTP CONNECTION
  21 FetionSIPP= "SIPP"
  22 FetionNavURL = "nav.fetion.com.cn"
  23 FetionConfigURL = "http://nav.fetion.com.cn/nav/getsystemconfig.aspx"
  24 
  25 FetionConfigXML = """<config><user mobile-no="%s" /><client type="PC" version="3.2.0540" platform="W5.1" /><servers version="0" /><service-no version="0" /><parameters version="0" /><hints version="0" /><http-applications version="0" /><client-config version="0" /></config>"""
  26 
  27 FetionLoginXML = """<args><device type="PC" version="0" client-version="3.2.0540" /><caps value="simple-im;im-session;temp-group;personal-group" /><events value="contact;permission;system-message;personal-group" /><user-info attributes="all" /><presence><basic value="400" desc="" /></presence></args>"""
  28 
  29 debug = True
  30 
  31 class PyFetionException(Exception):
  32     """Base class for all exceptions raised by this module."""
  33 
  34 class PyFetionInfoError(PyFetionException):
  35     """Phone number or password incomplete"""
  36 
  37 class PyFetionResponseException(PyFetionException):
  38     """Base class for all exceptions that include SIPC/HTTP error code.
  39     """
  40     def __init__(self, code, msg):
  41         self.PyFetion_code = code
  42         self.PyFetion_error = msg
  43         self.args = (code, msg)
  44 
  45 class PyFetionAuthError(PyFetionResponseException):
  46     """Authentication error.
  47     Your password error, or your mobile NO. don't support fetion
  48     """
  49 class PyFetionRegisterError(PyFetionResponseException):
  50     """RegisterError.
  51     """
  52 class PyFetionSendError(PyFetionResponseException):
  53     """Send SMS error
  54     """
  55 
  56 class PyFetion():
  57 
  58     __config_data = ""
  59     __sipc_url    = ""
  60     __sipc_proxy  = ""
  61     __sid = ""
  62     
  63     mobile_no = ""
  64     passwd = ""
  65     login_type = ""
  66 
  67     def __init__(self,mobile_no,passwd,login_type="HTTP"):
  68         if not passwd or len(mobile_no) != 11:
  69             raise PyFetionInfoError(mobile_no,passwd)
  70 
  71         self.mobile_no = mobile_no
  72         self.passwd = passwd
  73         self.login_type = login_type
  74 
  75         self.__get_system_config()
  76         self.__set_system_config()
  77 
  78     def login(self):
  79         (self.__ssic,self.__domain) = self.__get_uri()
  80         try:
  81             self.__register(self.__ssic,self.__domain)
  82         except PyFetionRegisterError,e:
  83             print "Register Failed!"
  84             #这里使用一个status变量作为类的成员,每一种失败后都改变一下这个
  85             pass
  86     def get_offline_msg(self):
  87         self.__SIPC.get("")
  88 
  89     def add(self,who):
  90         self.__SIPC.get("INFO","AddBuddy",who)
  91         response = self.__SIPC.send()
  92         code = self.__SIPC.get_code(response)
  93         if code == 521:
  94             d_print("Aleady added.")
  95         elif code == 522:
  96             d_print("Mobile NO. Don't Have Fetion")
  97             self.__SIPC.get("INFO","AddMobileBuddy",who)
  98             response = self.__SIPC.send()
  99 
 100 
 101     def get_personal_info(self):
 102         self.__SIPC.get("INFO","GetPersonalInfo")
 103         self.__SIPC.send()
 104 
 105     def get_info(self,who):
 106         self.__SIPC.get("INFO","GetContactsInfo",who)
 107         response = self.__SIPC.send()
 108         return response
 109 
 110 
 111     def get_contact_list(self):
 112         self.__SIPC.get("INFO","GetContactList")
 113         response = self.__SIPC.send()
 114         return response
 115 
 116     def get_uri(self,who):
 117         if who == self.mobile_no:
 118             who = self.__uri
 119         if not who.startswith("sip"):
 120             l = self.get_contact_list()
 121             all = re.findall('uri="(.+?)" ',l)
 122             #Get uri from contact list, compare one by one
 123             #I can't get other more effect way
 124             for uri in all:
 125                 ret = self.get_info(uri)
 126                 no = re.findall('mobile-no="(.+?)" ',ret)
 127                 if no:
 128                     if no[0] == who:
 129                         d_print(('who',),locals())
 130                         who = uri
 131                         break
 132         return who
 133 
 134     def send_msg(self,to,msg,flag="SENDMSG"):
 135         self.__SIPC.get(flag,to,msg)
 136         response = self.__SIPC.send()
 137         code = self.__SIPC.get_code(response)
 138         if code == 280:
 139             d_print("Send sms/msg OK!")
 140         else:
 141             d_print(('code',),locals())
 142 
 143     def send_sms(self,msg,to=None,long=False):
 144         if not to:
 145             to = self.__uri
 146         else:
 147             to = self.get_uri(to)
 148         if long:
 149             self.send_msg(to,msg,"SENDCatSMS")
 150         else:
 151             self.send_msg(to,msg,"SENDSMS")
 152 
 153     def send_schedule_sms(self,msg,time,to=None):
 154         if not to:
 155             to = self.__uri
 156         else:
 157             to = self.get_uri(to)
 158 
 159         self.__SIPC.get("SSSetScheduleSms",msg,time,to)
 160         response = self.__SIPC.send()
 161         code = self.__SIPC.get_code(response)
 162         if code == 486:
 163             d_print("Busy Here")
 164             return None
 165         if code == 200:
 166             id = re.search('id="(\d+)"',response).group(1)
 167             d_print(('id',),locals(),"schedule_sms id")
 168             return id
 169 
 170     def __register(self,ssic,domain):
 171         self.__SIPC = SIPC(self.__sid,self.__domain,self.passwd,self.login_type,self.__http_tunnel,self.__ssic,self.__sipc_proxy)
 172         response = ""
 173         for step in range(1,3):
 174                 self.__SIPC.get("REG",step,response)
 175                 response = self.__SIPC.send()
 176 
 177         code = self.__SIPC.get_code(response)
 178         if code == 200:
 179             d_print("register successful.")
 180         else:
 181             raise PyFetionRegisterError(code,response)
 182 
 183     def __http_send(self,url,body="",exheaders="",login=False):
 184         headers = {
 185                    'User-Agent':'IIC2.0/PC 3.2.0540',
 186                   }
 187         headers.update(exheaders)
 188         request = urllib2.Request(url,headers=headers,data=body)
 189         try:
 190             conn = urllib2.urlopen(request)
 191         except urllib2.URLError, e:
 192             code = e.code
 193             msg = e.read()
 194             if code == 401 or code == 404:
 195                 if login:
 196                     d_print(('code','text'),locals())
 197                     raise PyFetionAuthError(code,msg)
 198             return -1
 199 
 200         return conn
 201 
 202 
 203     def __get_system_config(self):
 204         global FetionConfigURL
 205         global FetionConfigXML
 206         url = FetionConfigURL
 207         body = FetionConfigXML % self.mobile_no
 208         d_print(('url','body'),locals())
 209         self.__config_data = self.__http_send(url,body).read()
 210             
 211 
 212     def __set_system_config(self):
 213         sipc_url = re.search("<ssi-app-sign-in>(.*)</ssi-app-sign-in>",self.__config_data).group(1)
 214         sipc_proxy = re.search("<sipc-proxy>(.*)</sipc-proxy>",self.__config_data).group(1)
 215         http_tunnel = re.search("<http-tunnel>(.*)</http-tunnel>",self.__config_data).group(1)
 216         d_print(('sipc_url','sipc_proxy','http_tunnel'),locals())
 217         self.__sipc_url   = sipc_url
 218         self.__sipc_proxy = sipc_proxy
 219         self.__http_tunnel= http_tunnel
 220 
 221     def __get_uri(self):
 222         url = self.__sipc_url+"?mobileno="+self.mobile_no+"&pwd="+self.passwd
 223         d_print(('url',),locals())
 224         try:
 225             ret = self.__http_send(url,login=True)
 226         except PyFetionAuthError,e:
 227             d_print(('e',),locals())
 228             print "Your password error, or your mobile NO. don't support fetion"
 229             sys.exit(-1)
 230 
 231         header = str(ret.info())
 232         body   = ret.read()
 233         ssic = re.search("ssic=(.*);",header).group(1)
 234         sid  = re.search("sip:(.*)@",body).group(1)
 235         uri  = re.search('uri="(.*)" mobile-no',body).group(1)
 236         status = re.search('user-status="(\d+)"',body).group(1)
 237         domain = "fetion.com.cn"
 238 
 239         d_print(('ssic','sid','uri','status','domain'),locals(),"Get SID OK")
 240         self.__sid = sid
 241         self.__uri = uri
 242         return (ssic,domain)
 243 
 244 class SIPC():
 245 
 246     global FetionVer
 247     global FetionSIPP
 248     global FetionLoginXML
 249 
 250     header = ""
 251     body = ""
 252     content = ""
 253     code = ''
 254     ver  = "SIP-C/2.0"
 255     ID   = 1
 256     sid  = ""
 257     domain = ""
 258     passwd = ""
 259     __http_tunnel = ""
 260 
 261     def __init__(self,sid,domain,passwd,login_type,http_tunnel,ssic,sipc_proxy):
 262         self.sid = sid
 263         self.domain = domain
 264         self.passwd = passwd
 265         self.login_type = login_type
 266         self.domain = domain
 267         self.sid = sid
 268         self.__seq = 1
 269         self.__sipc_proxy = sipc_proxy
 270         if self.login_type == "HTTP":
 271             self.__http_tunnel = http_tunnel
 272             self.__ssic = ssic
 273             guid = str(uuid1())
 274             self.__exheaders = {
 275                  'Cookie':'ssic=%s' % self.__ssic,
 276                  'Content-Type':'application/oct-stream',
 277                  'Pragma':'xz4BBcV%s' % guid,
 278                  }
 279      
 280     def init(self,type):
 281         self.content = '%s %s %s\r\n' % (type,self.domain,self.ver)
 282         self.header = [('F',self.sid),
 283                        ('I',self.ID),
 284                        ('Q','1 %s' % type),
 285                       ]
 286 
 287     def send(self):
 288         content = self.content 
 289         d_print(('content',),locals())
 290         if self.login_type == "HTTP":
 291             #First time t SHOULD SET AS 'i'
 292             #Otherwise 405 code get
 293             if self.__seq == 1:
 294                 t = 'i'
 295             else:
 296                 t = 's'
 297             url = self.__http_tunnel+"?t=%s&i=%s" % (t,self.__seq)
 298             response = self.__http_send(url,content,self.__exheaders).read()
 299             self.__seq+=1
 300             response = self.__sendSIPP()
 301             #This line will enhance the probablity of success.
 302             #Sometimes it will return FetionSIPP twice.
 303             #Probably you need add more
 304             if response == FetionSIPP:
 305                 response = self.__sendSIPP()
 306         else:
 307             if self.__seq == 1:
 308                 self.__tcp_init()
 309             self.__tcp_send(content)
 310             response = self.__tcp_recv()
 311             d_print(('response',),locals())
 312             self.__seq+=1
 313 
 314         code = self.get_code(response)
 315         d_print(('code',),locals())
 316         return response
 317 
 318 
 319  
 320     def get_code(self,response):
 321         try:
 322             self.code =int(re.search("%s (\d{3})" % self.ver,response).group(1))
 323             self.msg  =re.search("%s \d{3} (.*)\r" % self.ver,response).group(1)
 324             d_print(('self.code','self.msg',),locals())
 325             return self.code
 326         except AttributeError,e:
 327             return None
 328  
 329     def get(self,cmd,arg,ret="",extra=""):
 330         body = ret
 331         if cmd == "REG":
 332             body = FetionLoginXML
 333             self.init('R')
 334             if arg == 1:
 335                 pass
 336             if arg == 2:
 337                 nonce = re.search('nonce="(.*)"',ret).group(1)
 338                 cnonce = self.__get_cnonce()
 339                 if FetionVer == "2008":
 340                     response=self.__get_response_sha1(nonce,cnonce)
 341                 elif FetionVer == "2006":
 342                     response=self.__get_response_md5(nonce,cnonce)
 343                 salt = self.__get_salt()
 344                 d_print(('nonce','cnonce','response','salt'),locals())
 345                 #If this step failed try to uncomment this lines
 346                 #del self.header[2]
 347                 #self.header.insert(2,('Q','2 R'))
 348                 if FetionVer == "2008":
 349                     self.header.insert(3,('A','Digest algorithm="SHA1-sess",response="%s",cnonce="%s",salt="%s"' % (response,cnonce,salt)))
 350                 elif FetionVer == "2006":
 351                     self.header.insert(3,('A','Digest response="%s",cnonce="%s"' % (response,cnonce)))
 352             #If register successful 200 code get 
 353             if arg == 3:
 354                 return self.code
 355 
 356         if cmd == "SENDMSG":
 357             self.init('M')
 358             self.header.insert(3,('T',arg))
 359             self.header.insert(4,('C','text/plain'))
 360             self.header.insert(5,('K','SaveHistory'))
 361         
 362         if cmd == "SENDSMS":
 363             self.init('M')
 364             self.header.append(('T',arg))
 365             self.header.append(('N','SendSMS'))
 366 
 367         if cmd == "SENDCatSMS":
 368             self.init('M')
 369             self.header.append(('T',arg))
 370             self.header.append(('N','SendCatSMS'))
 371 
 372         if cmd == "SSSetScheduleSms":
 373             self.init('S')
 374             self.header.insert(3,('N',cmd))
 375             body = '<args><schedule-sms send-time="%s"><message>%s</message><receivers><receiver uri="%s" /></receivers></schedule-sms></args>' % (ret,arg,extra)
 376         if cmd == "INFO":
 377             self.init('S')
 378             self.header.insert(3,('N',arg))
 379             if arg == "GetPersonalInfo":
 380                 body = '<args><personal attributes="all" /><services version="" attributes="all" /><config version="33" attributes="all" /><mobile-device attributes="all" /></args>'
 381             elif arg == "GetContactList":
 382                 body = '<args><contacts attributes="all"><buddies attributes="all" /></contacts></args>'
 383             elif arg == "GetContactsInfo":
 384                 body = '<args><contacts attributes="all"><contact uri="%s" /></contacts></args>' % ret
 385             elif arg == "AddBuddy":
 386                 body = '<args><contacts><buddies><buddy uri="tel:%s" buddy-lists="1" desc="This message is send by PyFetion" expose-mobile-no="1" expose-name="1" /></buddies></contacts></args>' % ret
 387             elif arg == "AddMobileBuddy":
 388                 body = '<args><contacts><mobile-buddies><mobile-buddy uri="tel:%s" buddy-lists="1" desc="THis message is send by PyFetion" invite="0" /></mobile-buddies></contacts></args>' % ret
 389 
 390 
 391         
 392         #general SIPC info
 393         self.header.append(('L',len(body)))
 394         for k in self.header:
 395             self.content = self.content + k[0] + ": " + str(k[1]) + "\r\n"
 396         self.content+="\r\n"
 397         self.content+= body
 398         if self.login_type == "HTTP":
 399             #IN TCP CONNECTION "SIPP" SHOULD NOT BEEN SEND
 400             self.content+= FetionSIPP
 401         return self.content
 402 
 403 
 404     def __sendSIPP(self):
 405         body = FetionSIPP
 406         url = self.__http_tunnel+"?t=s&i=%s" % self.__seq
 407         response = self.__http_send(url,body,self.__exheaders).read()
 408         d_print(('response',),locals())
 409         self.__seq+=1
 410         return response
 411 
 412     def __http_send(self,url,body="",exheaders="",login=False):
 413         headers = {
 414                    'User-Agent':'IIC2.0/PC 3.2.0540',
 415                   }
 416         headers.update(exheaders)
 417         request = urllib2.Request(url,headers=headers,data=body)
 418         try:
 419             conn = urllib2.urlopen(request)
 420         except urllib2.URLError, e:
 421             code = e.code
 422             msg = e.read()
 423             d_print(('code','text'),locals())
 424             if code == 401 or code == 404:
 425                 if login:
 426                     raise PyFetionAuthError(code,msg)
 427             return -1
 428 
 429         return conn
 430 
 431 
 432     def __tcp_init(self):
 433         try:
 434             self.__sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 435         except socket.error,e:
 436             s = None
 437             print e.read()
 438             #Should return -1 NOT just exit
 439             sys.exit(-1)
 440         (host,port) = tuple(self.__sipc_proxy.split(":"))
 441         port = int(port)
 442         try:
 443             self.__sock.connect((host,port))
 444         except socket.error,e:
 445             self.__sock.close()
 446             self.__sock = None
 447             print e.read()
 448             sys.exit(-1)
 449 
 450 
 451     def __tcp_send(self,msg):
 452         try:
 453             self.__sock.send(msg)
 454         except socket.error,e:
 455             self.__sock.close()
 456             print e.read()
 457             sys.exit(-1)
 458 
 459     def __tcp_recv(self):
 460         try:
 461             data = self.__sock.recv(4096)
 462         except socket.error,e:
 463             self.__sock.close()
 464             print e.read()
 465             sys.exit(-1)
 466         return data
 467 
 468 
 469 
 470     def __get_salt(self):
 471         return self.__hash_passwd()[:8]
 472 
 473     def __get_cnonce(self):
 474         return md5(str(uuid1())).hexdigest().upper()
 475 
 476     def __get_response_md5(self,nonce,cnonce):
 477         #nonce = "3D8348924962579418512B8B3966294E"
 478         #cnonce= "9E169DCA9CBD85F1D1A89A893E00917E"
 479         key = md5("%s:%s:%s" % (self.sid,self.domain,self.passwd)).digest()
 480         h1  = md5("%s:%s:%s" % (key,nonce,cnonce)).hexdigest().upper()
 481         h2  = md5("REGISTER:%s" % self.sid).hexdigest().upper()
 482         response  = md5("%s:%s:%s" % (h1,nonce,h2)).hexdigest().upper()
 483         #d_print(('nonce','cnonce','key','h1','h2','response'),locals())
 484         return response
 485 
 486     def __get_response_sha1(self,nonce,cnonce):
 487         #nonce = "3D8348924962579418512B8B3966294E"
 488         #cnonce= "9E169DCA9CBD85F1D1A89A893E00917E"
 489         hash_passwd = self.__hash_passwd()
 490         hash_passwd_str = binascii.unhexlify(hash_passwd[8:])
 491         key = sha1("%s:%s:%s" % (self.sid,self.domain,hash_passwd_str)).digest()
 492         h1  = md5("%s:%s:%s" % (key,nonce,cnonce)).hexdigest().upper()
 493         h2  = md5("REGISTER:%s" % self.sid).hexdigest().upper()
 494         response = md5("%s:%s:%s" % (h1,nonce,h2)).hexdigest().upper()
 495         return response
 496 
 497     def __hash_passwd(self):
 498         #salt = '%s%s%s%s' % (chr(0x77), chr(0x7A), chr(0x6D), chr(0x03))
 499         salt = 'wzm\x03'
 500         src  = salt+sha1(self.passwd).digest()
 501         return "777A6D03"+sha1(src).hexdigest().upper()
 502 
 503 
 504 def d_print(vars=(),namespace=[],msg=""):
 505     """if only sigle variable use like this ('var',)"""
 506     global debug
 507     if vars and not namespace and not msg:
 508         msg = vars
 509     if debug and vars and namespace:
 510         for var in vars:
 511             if var in namespace:
 512                 print "[PyFetion]:\033[0;31;48m%s\033[0m" % var,
 513                 print namespace[var]
 514     if debug and msg:
 515         print "[PyFetion]:\033[0;31;48m%s\033[0m" % msg
 516 
 517 
 518 def main(argv=None):
 519     try:
 520         phone = PyFetion("138888888","888888","TCP")
 521     except PyFetionInfoError,e:
 522         print "corrent your mobile NO. and password"
 523         return -1
 524     phone.login()
 525     #phone.get_offline_msg()
 526     #phone.add("138888888")
 527     #phone.get_info()
 528     #phone.get_contact_list()
 529     #phone.send_sms("Hello, ",long=True)
 530     s = "2008-12-31 02:39:00."
 531     for i in range(100,500):
 532         time = s + str(i)
 533         phone.send_schedule_sms("请注意,这个是定时短信",time)
 534     #time_format = "%Y-%m-%d %H:%M:%S"
 535     #time.strftime(time_format,time.gmtime())
 536     
 537 if __name__ == "__main__":
 538     sys.exit(main())
 539 


反馈

MiscItems/2009-01-03 (last edited 2009-12-25 07:16:31 by localhost)