Hotmail,MSN.com,Live.com,Live.cn账号都可以用来登录MSN(也就是Windows Live Message),甚至用户可以用自己其他邮箱去注册MSN的账户。
但是不同的产品对于联系人的页面都不一样,如此,通过抓取网页导入联系人就变得异常麻烦。
不过,换一个思路,我们可以直接模拟MSN的客户端,来获取用户的联系人列表。
Python是一个很好很强大的工具,自然少不了连接MSN的库。这里我们选用Alberto Bertogli的msnlib。
到 http://blitiri.com.ar/p/msnlib/ ,下载安装msnlib。导入联系人的代码如下:
#coding:utf-8
import socket, select, time, msnlib, msncb
def void(s): pass
msnlib.debug = msncb.debug = void
def get_friend_list(email, password):
m = msnlib.msnd()
m.cb = msncb.cb()
m.email = email.strip()
m.pwd = password.strip()
m.encoding = 'utf-8'
try:
m.login()
m.sync()
except:
return
m.change_status('invisible')
begin_time = time.time()
users = set()
while True:
fds = m.pollable()
infd = fds[0]
outfd = fds[1]
fds = select.select(infd, outfd, [], 0)
for i in fds[0] + fds[1]:
try:
m.read(i)
except ('SocketError', socket.error), err:
if i != m:
m.close(i)
merge_users = users|set(m.users.keys())
if len(users) == len(merge_users):
end_time = time.time()
if len(users):
if end_time-begin_time >= 2:
break
elif end_time-begin_time >= 3:
break
time.sleep(0.05)
else:
users = merge_users
begin_time = time.time()
result = {}
for i in users:
nick = m.users[i].nick
i_lower = i.lower()
if nick.lower() == i_lower:
nick = nick.split("@", 1)[0]
result[i_lower] = nick
return result
if __name__ == "__main__":
result = get_friend_list(raw_input("email:"), raw_input("password:"))
if result is None:
print "email or password or network error , login failed"
else:
for k, v in result.iteritems():
print k, v.decode("utf-8", "ignore")
运行该脚本,输入用户名密码,输出内容如下
youngvleo@gmail.com youngvleo
tk@msn.cn tk
...
导入联系人往往是长达几十秒漫长的过程,为了不让它阻塞正常请求。我们将其纳入方便支持异步长连接的tornado框架下,作为一个远程调用的服务。
和往常一样,还是用ajax向来调用rpc。先实现一个从cookie获取用户id的基类
import tornado.web
from mysite.model.auth import UserSession
class BaseHandler(tornado.web.RequestHandler):
def get_user_id(self):
session = self.get_cookie("S")
user_id = UserSession.verify_user_id(session)
return user_id
然后开始写用户导入的模块
#coding:utf-8
import thread
import tornado.web
from mysite.model.auth import User, UserEmail
from mysite.model.consts import USER_STATE_IMPORT
from mysite.model.friend import is_friend
from get_friend_list import get_friend_list
from myrpc.base_handler import BaseHandler
import tornado.ioloop
def _async_get_friend_list(user_id, email, password, callback):
result = get_friend_list(email, password)
if result is None:
r = "N"
else:
not_friend = []
not_join = []
User.begin()
for email, name in result.iteritems():
email = email.strip()
user = User.by_email(email)
if user is None:
user_email = UserEmail.get(email=email)
if user_email:
print "!!!", email, user_email.id
else:
user_email = UserEmail(email=email)
user_email.save()
user = User(id=user_email.id, name=name, state=USER_STATE_IMPORT)
user.save()
not_join.append(user.id)
else:
if user.state == USER_STATE_IMPORT:
not_join.append(user.id)
elif not is_friend(user_id, user.id):
not_friend.append(user.id)
User.commit()
r = "%s-%s"%(
"_".join(map(str, not_friend)),
"_".join(map(str, not_join)),
)
#这样线程安全
tornado.ioloop.IOLoop.instance().add_callback(lambda: callback(r))
class ImportMsnHandler(BaseHandler):
@tornado.web.asynchronous
def get(self, email, password):
user_id = self.get_user_id()
if user_id:
thread.start_new_thread(
_async_get_friend_list,
(user_id, email, password, self.async_callback(self._on_get))
)
else:
self.finish('N')
def _on_get(self, r):
self.finish(r)
返回值 - 前的是已经注册,但是尚未成为好友的用户id,- 后的是尚未注册的用户id。
好了,到此为止MSN联系人导入算是写完了。全部代码见下面这个网址。
http://mayoufour.googlecode.com/svn/trunk/stdyun/myrpc/import_contact/import_msn/
最后配置一下Tornado的server,文件见 http://mayoufour.googlecode.com/svn/trunk/stdyun/tornado_server.py
#!/usr/bin/env python
#coding:utf-8
from myconf import config
config.DISABLE_LOCAL_CACHED = True
config.THREAD_SAFE = True
import logging
import os.path
import tornado.auth
import tornado.escape
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("host", default="0.0.0.0", help="run on the given port", type=int)
define("port", default=15050, help="run on the given port", type=int)
from myrpc.import_contact.import_msn import ImportMsnHandler
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/rpc/import/msn/([^/]+)/(.+)", ImportMsnHandler),
]
settings = {
"login_url":"/auth/login",
"xsrf_cookies": False,
"debug": False
}
tornado.web.Application.__init__(self, handlers, **settings)
def main(port=None):
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
port = port or options.port
http_server.listen(port)
print "port", port
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
import sys
if len(sys.argv) > 1 and sys.argv[1].isdigit():
port = int(sys.argv[1])
else:
port = 15050
main(port=port)
最后在nginx配置中加上url转发
upstream tornado {
server localhost:15050 weight=1;
}
server {
listen 80;
server_name stdyun.com;
charset utf-8;
location / {
proxy_pass http://stdyun;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
location ~ ^/rpc {
proxy_pass http://tornado;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
... ... ...
}
OK,导入MSN联系人的后台就算是写好了。
访问这里试试吧,不过要先登录哦:)
http://stdyun.com/rpc/import/msn/MSN账号名/密码
比如我的MSN账号返回值如下
10000268-10003385_10001307_10003386_10003387