Пример #1
0
Файл: hub.py Проект: pyzh/robot
    def __init__(self,
                 qid,
                 pwd,
                 client=None,
                 debug=False,
                 handle_msg_image=True):
        self.handle_msg_image = handle_msg_image
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36")
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.http.debug = debug

        self.qid = qid
        self.__pwd = pwd
        self.client = client

        self.rc = random.randrange(0, 100)
        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)  # 消息id, 随机初始化
        self.daid = 164
        self.login_sig = None
        self.ptwebqq = None
        self.nickname = u"YouWillNeverGetIt"
        self.vfwebqq = None
        self.psessionid = None
        self.stop_poll = False

        # 检查是否验证码的回调
        self.ptui_checkVC = lambda *r: r

        # 是否需要验证码
        self.require_check = None
        self.require_check_time = None

        # 是否开始心跳和拉取消息
        self.poll_and_heart = None
        self.login_time = None
        self.hThread = None

        # 验证图片
        self.checkimg_path = tempfile.mktemp(".jpg")
        self._lock_path = tempfile.mktemp()
        self._wait_path = tempfile.mktemp()

        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)

        self.message_interval = 0.5  # 消息间隔
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量
        WebQQRequest.hub = self
        self.connecting = False
Пример #2
0
    def find_next(self):
        pprint('Start recover cookie')
        s = TornadoHTTPClient(force_instance = True)

        with open('cookie.txt') as f:
            cookie_str = f.read()

        s.set_global_headers({ 'Cookie': cookie_str })

        resp = yield s.get('http://ehire.51job.com/CommonPage/JobsPostNumbList.aspx')
        pprint('End   recover cookie')

        bs = BeautifulSoup(resp.body, "html.parser")
        pprint(bs.find('b', {'class': 'info_att'}).text)
Пример #3
0
    def __init__(self, qid, pwd, handler = None):
        self.qid = qid               # QQ 号
        self.__pwd = pwd             # QQ密码
        self.nickname = None         # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36")
        self.http.debug = TRACE
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903                                    # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)     # 消息id, 随机初始化

        self.require_check = False   # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.require_check_time = None  # 需要验证码的时间

        self.checkimg_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "check.jpg")

        self.friend_info = {}        # 初始化好友列表
        self.group_info = {}         # 初始化组列表
        self.group_sig = {}          # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {} # 初始化组成员列表
        self.mark_to_uin = {}        # 备注名->uin的映射

        self.daid = 164
        self.login_sig = None

        self.login_time = None       # 登录的时间
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0    # 剩余位发送的消息数量

        self.status_callback = None

        self.poll_stoped = True      # 获取消息是否停止
        self.hThread = None               # 心跳线程
        #self.base_header = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"}
        self.base_header = {"Referer":"http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=3"}
Пример #4
0
    def __init__(self, http=None):
        self.http = http or TornadoHTTPClient()

        if not http:
            self.http.set_user_agent(
                "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36"
            )
            self.http.debug = getattr(config, "TRACE", False)
            self.http.validate_cert = False
            self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})

        self.url = "http://www.simsimi.com/func/reqN"
        self.params = {
            "lc": "zh",
            "ft": 0.0,
            "fl": "http://www.simsimi.com/talk.htm"
        }
        self.ready = False

        self.fetch_kwargs = {}
        if config.SimSimi_Proxy:
            self.fetch_kwargs.update(proxy_host=config.SimSimi_Proxy[0],
                                     proxy_port=config.SimSimi_Proxy[1])

        self._setup_cookie()
Пример #5
0
 def __init__(self, http = None):
     self.http = http or TornadoHTTPClient()
     #self.http.debug=True
     self.result=[]
     self.statment=[]
     self.helpkeyword=['?',u'怎么',u'什么',u'鸭子','ee',u'好了','YY','yy',u'神马',u'啊',u'?',u'是么',u'依依',u'EE',u'BSD鸭子',u'能不能',u'多少',u'么']
     pass
Пример #6
0
    def __init__(self, qid, pwd):
        self.qid = qid  # QQ 号
        self.__pwd = pwd  # QQ密码
        self.nickname = None  # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36"
        )
        self.http.debug = DEBUG
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(1111111, 99999999)  # 消息id, 随机初始化

        self.require_check = False  # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.check_data = None  # 初始化检查时返回的数据
        self.blogin_data = None  # 初始化登录前返回的数据

        self.friend_info = {}  # 初始化好友列表
        self.group_info = {}  # 初始化组列表
        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {}  # 初始化组成员列表

        self.hb_time = int(time.time() * 1000)
        self.daid = 164
        self.login_sig = None

        self.login_time = None  # 登录的时间
        self.last_group_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量
        self.base_header = {
            "Referer":
            "https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"
        }
Пример #7
0
Файл: hub.py Проект: udbmnm/twqq
    def __init__(self, qid, pwd, client=None, debug=False):
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36"
        )
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.http.debug = debug

        self.qid = qid
        self.__pwd = pwd
        self.client = client

        self.rc = random.randrange(0, 100)
        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)  # 消息id, 随机初始化
        self.daid = 164
        self.login_sig = None
        self.ptwebqq = None
        self.nickname = u"YouWillNeverGetIt"
        self.vfwebqq = None
        self.psessionid = None
        self.stop_poll = False

        # 检查是否验证码的回调
        self.ptui_checkVC = lambda r, v, u: (r, v, u)

        # 是否需要验证码
        self.require_check = None
        self.require_check_time = None

        # 是否开始心跳和拉取消息
        self.poll_and_heart = None
        self.login_time = None
        self.hThread = None

        # 验证图片
        self.checkimg_path = tempfile.mktemp(".jpg")
        self._lock_path = tempfile.mktemp()
        self._wait_path = tempfile.mktemp()

        self.friend_info = {}  # 初始化好友列表
        self.group_info = {}  # 初始化组列表
        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {}  # 初始化组成员列表
        self.mark_to_uin = {}  # 备注名->uin的映射

        self.message_interval = 0.5  # 消息间隔
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量
        WebQQRequest.hub = self
        self.load_next_request(FirstRequest())
Пример #8
0
    def __init__(self, qid, pwd, handler=None):
        self.qid = qid  # QQ 号
        self.__pwd = pwd  # QQ密码
        self.nickname = None  # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36"
        )
        self.http.debug = TRACE
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)  # 消息id, 随机初始化

        self.require_check = False  # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.require_check_time = None  # 需要验证码的时间

        self.checkimg_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "check.jpg")

        self.friend_info = {}  # 初始化好友列表
        self.group_info = {}  # 初始化组列表
        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {}  # 初始化组成员列表
        self.mark_to_uin = {}  # 备注名->uin的映射

        self.daid = 164
        self.login_sig = None

        self.login_time = None  # 登录的时间
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量

        self.status_callback = None

        self.poll_stoped = True  # 获取消息是否停止
        self.hThread = None  # 心跳线程
        # self.base_header = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"}
        self.base_header = {"Referer": "http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=3"}
Пример #9
0
    def __init__(self, qid, pwd):
        self.qid = qid               # QQ 号
        self.__pwd = pwd             # QQ密码
        self.nickname = None         # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36")
        self.http.debug = TRACE
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903                                    # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(1111111, 99999999)     # 消息id, 随机初始化

        self.require_check = False   # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.check_data = None       # 初始化检查时返回的数据
        self.blogin_data = None      # 初始化登录前返回的数据

        self.friend_info = {}        # 初始化好友列表
        self.group_info = {}         # 初始化组列表
        self.group_sig = {}          # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {} # 初始化组成员列表

        self.hb_time = int(time.time() * 1000)
        self.daid = 164
        self.login_sig = None

        self.login_time = None       # 登录的时间
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0    # 剩余位发送的消息数量
        self.base_header = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"}

        self.last_heartbeat = None
Пример #10
0
    def __init__(self, http=None):
        self.http = http or TornadoHTTPClient()

        if not http:
            self.http.set_user_agent(
                "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36"
            )
            self.http.validate_cert = False
            self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})

        self.url = "rest://www.simsimi.com/func/reqN"
        self.params = {"lc": "ch", "ft": 0.0}
        self.ready = False

        self.fetch_kwargs = {}
        self.fetch_kwargs.update(proxy_host='192.168.13.19', proxy_port='7777')

        t = threading.Thread(target=self._setup_cookie())
        t.start()
Пример #11
0
class WebQQ(object):
    def __init__(self, qid, pwd):
        self.qid = qid  # QQ 号
        self.__pwd = pwd  # QQ密码
        self.nickname = None  # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36"
        )
        self.http.debug = DEBUG
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(1111111, 99999999)  # 消息id, 随机初始化

        self.require_check = False  # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.check_data = None  # 初始化检查时返回的数据
        self.blogin_data = None  # 初始化登录前返回的数据

        self.friend_info = {}  # 初始化好友列表
        self.group_info = {}  # 初始化组列表
        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {}  # 初始化组成员列表

        self.hb_time = int(time.time() * 1000)
        self.daid = 164
        self.login_sig = None

        self.login_time = None  # 登录的时间
        self.last_group_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量
        self.base_header = {
            "Referer":
            "https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"
        }

    def ptuiCB(self, scode, r, url, status, msg, nickname=None):
        """ 模拟JS登录之前的回调, 保存昵称 """
        if int(scode) == 0:
            logging.info("从Cookie中获取ptwebqq的值")
            self.ptwebqq = self.http.cookie['.qq.com']['/']['ptwebqq'].value
            self.logined = True
        elif int(scode) == 4:
            logging.error(msg)
            self.check()
        else:
            logging.error(u"server response: {0}".format(msg.decode('utf-8')))
            exit(2)

        if nickname:
            self.nickname = nickname

    def get_group_member_nick(self, gcode, uin):
        return self.group_members_info.get(gcode, {}).get(uin, {}).get("nick")

    def get_uptime(self):
        MIN = 60
        HOUR = 60 * MIN
        DAY = 24 * HOUR
        up_time = datetime.fromtimestamp(self.login_time).strftime("%H:%M:%S")

        now = time.time()
        sub = now - self.login_time

        days = int(sub / DAY)
        hours = int(sub / HOUR)
        mins = int(sub / MIN)

        if mins:
            num = mins
            unit = "min"

        if hours:
            num = hours
            unit = "hours" if hours > 1 else "hour"

        if days:
            num = days
            unit = "days" if days > 1 else "day"

        if not days and not mins and not hours:
            num = int(sub)
            unit = "sec"

        return "{0} up {1} {2}".format(up_time, num, unit)

    def get_login_sig(self):
        logging.info("获取 login_sig...")
        url = "https://ui.ptlogin2.qq.com/cgi-bin/login"
        params = [("daid", self.daid), ("target", "self"), ("style", 5),
                  ("mibao_css", "m_webqq"), ("appid", self.aid),
                  ("enable_qlogin", 0), ("no_verifyimg", 1),
                  ("s_url", "http://web2.qq.com/loginproxy.html"),
                  ("f_url", "loginerroralert"), ("strong_login", 1),
                  ("login_state", 10), ("t", "20130723001")]
        self.http.get(url, params, callback=self._get_login_sig)
        self.http.get("http://web2.qq.com")

    def _get_login_sig(self, resp):
        sigs = SIG_RE.findall(resp.body)
        if len(sigs) == 1:
            self.login_sig = sigs[0]
            logging.info(u"获取Login Sig: {0}".format(self.login_sig))
        else:
            logging.warn(u"没有获取到 Login Sig, 后续操作可能失败")
            self.login_sig = ""

        self.check()

    def check(self):
        """ 检查是否需要验证码
        url :
            https://ssl.ptlogin2.qq.com/check
        方法:   GET
        参数:
            {
                uin     // qq号
                appid   // 程序id 固定为1003903
                r       // 随机数
                u1      // http://web2.qq.com/loginproxy.html
                js_ver  // 10040
                js_type // 0
            }
        返回:
            ptui_checkVC('0','!PTH','\x00\x00\x00\x00\x64\x74\x8b\x05');
            第一个参数表示状态码, 0 不需要验证, 第二个为验证码, 第三个为uin
        """

        logging.info(u"检查是否需要验证码...")
        #url = "https://ssl.ptlogin2.qq.com/check"
        url = "http://check.ptlogin2.qq.com/check"
        params = {
            "uin": self.qid,
            "appid": self.aid,
            "u1": "http://web2.qq.com/loginproxy.html",
            "login_sig": self.login_sig,
            "js_ver": 10040,
            "js_type": 0,
            "r": random.random()
        }
        headers = {
            "Referer":
            "https://ui.ptlogin2.qq.com/cgi-bin/login?daid="
            "164&target=self&style=5&mibao_css=m_webqq&appid=1003903&"
            "enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb2.q"
            "q.com%2Floginproxy.html&f_url=loginerroralert&strong_log"
            "in=1&login_state=10&t=20130723001"
        }
        self.http.get(url,
                      params,
                      headers=headers,
                      callback=self.handle_verify)

        cookie_url = "http://www.simsimi.com/talk.htm?lc=ch"
        cookie_params = (("lc", "ch"), )
        headers = {"Referer": "http://www.simsimi.com/talk.htm"}
        self.http.get(cookie_url, cookie_params, headers=headers)

        headers = {"Referer": "http://www.simsimi.com/talk.htm?lc=ch"}
        self.http.get("http://www.simsimi.com/func/langInfo",
                      cookie_params,
                      headers=headers)

    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd

    def get_check_img(self, r, vcode, uin):
        """ 获取验证图片 """
        def callback(resp):
            path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                                "check.jpg")
            fp = open(path, 'wb')
            fp.write(resp.body)
            fp.close()
            if UPLOAD_CHECKIMG:
                res = upload_file("check.jpg", path)
                path = res.url
            print u"验证图片: {0}".format(path)
            check_code = ""
            while not check_code:
                check_code = raw_input("输入验证图片上的验证码: ")
            ccode = check_code.strip().lower()
            self.check_code = ccode
            pwd = self.handle_pwd(r, ccode.upper(), uin)
            self.before_login(pwd)

        url = "https://ssl.captcha.qq.com/getimage"
        params = [("aid", self.aid), ("r", random.random()), ("uin", self.qid)]
        self.http.get(url, params, callback=callback)

    def handle_verify(self, resp):
        ptui_checkVC = lambda r, v, u: (r, v, u)
        r, vcode, uin = eval(resp.body.strip().rstrip(";"))
        if int(r) == 0:
            logging.info("验证码检查完毕, 不需要验证码")
            password = self.handle_pwd(r, vcode, uin)
            self.before_login(password)
            self.check_code = vcode
        else:
            logging.warn("验证码检查完毕, 需要验证码")
            self.get_check_img(r, vcode, uin)
            self.require_check = True

    def before_login(self, password):
        """ 登录之前的操作
        url:
            https://ssl.ptlogin2.qq.com/login
        方法:   GET
        参数:
            {
                u       // qq号码
                p       // 经过处理的密码
                verifycode  // 验证码
                webqq_type  // 固定为10
                remember_uin    // 是否记住qq号, 传1 即可
                login2qq        // 登录qq, 传1
                aid             // appid 固定为 1003903
                u1              // 固定为 http://www.qq.com
                h               // 固定为1
                ptrediect       // 固定为0
                ptlang          // 固定为2052
                from_ui         // 固定为 1
                pttype          // 固定为1
                dumy            // 固定为空
                fp              // 固定为loginerroralert ( 重要)
                mibao_css       // 固定为 m_webqq
                t               // 固定为1
                g               // 固定为
                js_type         // 固定为0
                js_ver          // 固定为10021
        其他:
            如果check步骤验证了需要验证码, 需加上 Referer头 值为:
            https://ui.ptlogin2.qq.com/cgi-bin/login?target=self&style=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20130221001

        接口返回:
            ptuiCB('0','0','http://www.qq.com','0','登录成功!', 'nickname');
        先检查是否需要验证码,不需要验证码则首先执行一次登录
        然后获取Cookie里的ptwebqq保存在实例里,供后面的接口调用
        """
        url = "https://ssl.ptlogin2.qq.com/login"
        params = [("u", self.qid), ("p", password),
                  ("verifycode", self.check_code), ("webqq_type", 10),
                  ("remember_uin", 1), ("login2qq", 1), ("aid", self.aid),
                  ("u1", "http://www.qq.com/loginproxy.h"
                   "tml?login2qq=1&webqq_type=10"), ("h", 1),
                  ("action", 4 - 5 - 8246), ("ptredirect", 0),
                  ("ptlang", 2052), ("from_ui", 1), ("daid", self.daid),
                  ("pttype", 1), ("dumy", ""), ("fp", "loginerroralert"),
                  ("mibao_css", "m_webqq"), ("t", 1), ("g", 1), ("js_type", 0),
                  ("js_ver", 10040), ("login_sig", self.login_sig)]
        headers = {}
        if self.require_check:
            headers.update(Referer="https://ui.ptlogin2.qq.com/cgi-"
                           "bin/login?target=self&style=5&mibao_css=m_"
                           "webqq&appid=1003903&enable_qlogin=0&no_ver"
                           "ifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Fl"
                           "oginproxy.html&f_url=loginerroralert&stron"
                           "g_login=1&login_state=10&t=20130221001")
        logging.info("检查完毕, 开始登录前准备")
        self.http.get(url, params, headers=headers, callback=self.login0)

    def login0(self, resp):
        logging.info("开始登录前准备...")
        blogin_data = resp.body.decode("utf-8").strip().rstrip(";")
        eval("self." + blogin_data)

        location1 = re.findall(r'ptuiCB\(\'0\'\,\'0\'\,\'(.*)\'\,\'0\'\,',
                               blogin_data)[0]
        params = []
        header = {
            "Referer":
            "https://ui.ptlogin2.qq.com/cgi-bin/login?d"
            "aid=164&target=self&style=5&mibao_css=m_webqq&appid=1"
            "003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2"
            "F%2Fweb2.qq.com%2Floginproxy.html&f_url=loginerrorale"
            "rt&strong_login=1&login_state=10&t=20130723001"
        }
        self.http.get(location1,
                      params,
                      headers=header,
                      callback=self.get_location1)

    def get_location1(self, resp):
        if resp.code == 302:
            location2 = resp.headers.get("Location")
            params = []
            header = {}
            self.http.get(location2,
                          params,
                          headers=header,
                          callback=self.get_location1)
        else:
            logging.info("准备完毕, 开始登录")
            self.login()

    def login(self):
        """ 获取登录前的数据, 并进行登录
        url:
            http://d.web2.qq.com/channel/login2
        方法: POST
        参数:
            {
                r : {
                    status      // 登录后的状态 ("online")
                    ptwebqq     // 上次请求返回的cookie
                    passwd_sig  // 固定为空
                    clientid    // 随机的clientid
                    psessionid  // 传递 null
                }
                clientid    // 客户端id
                psessionid  // 传递null
            }
        其他:
            需加上 Referer和 Origin 头:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
            "Origin": "http://d.web2.qq.com"

        返回:
            {u'retcode': 0,
            u'result': {
                'status': 'online', 'index': 1075,
                'psessionid': '', u'user_state': 0, u'f': 0,
                u'uin': 1685359365, u'cip': 3673277226,
                u'vfwebqq': u'', u'port': 43332}}
            保存result中的psessionid和vfwebqq供后面接口调用
        """
        time.sleep(4)

        url = "http://d.web2.qq.com/channel/login2"
        params = [("r", '{"status":"online","ptwebqq":"%s","passwd_sig":"",'
                '"clientid":"%d","psessionid":null}'\
                % (self.ptwebqq, self.clientid)),
                ("clientid", self.clientid),
                ("psessionid", "null")
                ]

        headers = {"Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        logging.info("登录准备完毕, 开始登录")
        self.http.post(url,
                       params,
                       headers=headers,
                       callback=self.update_friend)

    def _hash(self):
        a = str(self.qid)
        e = self.ptwebqq
        l = len(e)
        # 将qq号码转换成整形列表
        b, k, d = 0, -1, 0
        for d in a:
            d = int(d)
            b += d
            b %= l
            f = 0
            if b + 4 > l:
                g = 4 + b - l
                for h in range(4):
                    f |= h < g and (ord(e[b + h]) & 255) << (3 - h) * 8 or (
                        ord(e[h - g]) & 255) << (3 - h) * 8
            else:
                for h in range(4):
                    f |= (ord(e[b + h]) & 255) << (3 - h) * 8
            k ^= f
        c = [k >> 24 & 255, k >> 16 & 255, k >> 8 & 255, k & 255]
        import string
        k = list(string.digits) + ['A', 'B', 'C', 'D', 'E', 'F']
        d = [k[b >> 4 & 15] + k[b & 15] for b in c]
        return ''.join(d)

    def update_friend(self, resp, login=True):
        """ 更新好友列表
        URL:
            http://s.web2.qq.com/api/get_user_friends2
        METHOD:
            POST
        PARAMS:
            {r:{"h":"hello", "vfwebqq":""}}
        HEADER:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """

        data = json.loads(resp.body)
        if login:
            if data.get("retcode") != 0:
                logging.error("登录失败")
                exit(2)
            self.vfwebqq = data.get("result", {}).get("vfwebqq")
            self.psessionid = data.get("result", {}).get("psessionid")

        url = "http://s.web2.qq.com/api/get_user_friends2"
        params = [("r",
                   json.dumps({
                       "h": "hello",
                       "hash": self._hash(),
                       "vfwebqq": self.vfwebqq
                   }))]
        headers = {
            "Referer":
            "http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1"
        }

        callback = self.update_friend

        if login:
            logging.info("登录成功")
            if not DEBUG:
                aw = ""
                while aw.lower() not in ["y", "yes", "n", "no"]:
                    aw = raw_input("是否将程序至于后台[y] ")
                    if not aw:
                        aw = "y"

                if aw in ["y", "yes"]:
                    run_daemon(self.http.post,
                               args=(url, params),
                               kwargs=dict(headers=headers,
                                           kwargs=dict(login=False),
                                           callback=callback))
                    return

            self.http.post(url,
                           params,
                           headers=headers,
                           callback=callback,
                           kwargs=dict(login=False))
        else:
            logging.info("加载好友信息")
            lst = data.get("result", {}).get("info", [])
            for info in lst:
                uin = info.get("uin")
                self.friend_info[uin] = info
            self.update_group()

            self.http.post(url,
                           params,
                           headers=self.base_header,
                           delay=300,
                           callback=callback)

    def update_group(self, resp=None):
        """ 获取组列表, 并获取组成员
        获取组列表:
            url:
                http://s.web2.qq.com/api/get_group_name_list_mask2
            method:
                POST
            params:
                {
                    r : {
                        vfwebqq     // 登录前返回的cookie值
                    }
                }

        """
        logging.info("获取群列表")
        url = "http://s.web2.qq.com/api/get_group_name_list_mask2"
        params = [
            ("r", '{"vfwebqq":"%s"}' % self.vfwebqq),
        ]
        headers = {"Origin": "http://s.web2.qq.com"}
        headers.update(self.base_header)
        self.http.post(url,
                       params,
                       headers=headers,
                       callback=self.group_members)

    def group_members(self, resp):
        """ 获取群列表, 获取群列表中的成员
        url: http://s.web2.qq.com/api/get_group_info_ext2
        method: GET
        params:
            {
                gcode           // 群代码
                vfwebqq         // 登录前的cookie值
                t               // int(time.time())
            }
        headers:
            "Referer":
            "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
        """
        logging.info("加载组成员信息")
        data = json.loads(resp.body)
        group_list = data.get("result", {}).get("gnamelist", [])
        if not group_list:
            self.heartbeat(0)
            self.poll()
        for i, group in enumerate(group_list):
            gcode = group.get("code")
            url = "http://s.web2.qq.com/api/get_group_info_ext2"
            params = [("gcode", gcode), ("vfwebqq", self.vfwebqq),
                      ("t", int(time.time()))]
            callback = self.do_group_members
            if i == len(group_list) - 1:
                kwargs = dict(gcode=gcode, last=True)
            else:
                kwargs = dict(gcode=gcode)

            self.http.get(url,
                          params,
                          headers=self.base_header,
                          callback=callback,
                          kwargs=kwargs)
            self.group_info[gcode] = group

    def do_group_members(self, resp, gcode, last=False):
        """ 获取群成员数据 """
        data = json.loads(resp.body)
        members = data.get("result", {}).get("minfo", [])
        self.group_members_info[gcode] = {}
        for m in members:
            uin = m.get("uin")
            self.group_members_info[gcode][uin] = m

        cards = data.get("result", {}).get("cards", [])

        for card in cards:
            uin = card.get("muin")
            group_name = card.get("card")
            self.group_members_info[gcode][uin]["nick"] = group_name

        if last and not self.poll_and_heart:
            logging.info("万事具备,开始拉取信息和心跳")
            self.poll()
            self.heartbeat(0)

    def poll(self):
        """ 建立长连接获取消息
        url:http://d.web2.qq.com/channel/poll2
        方法: POST
        参数:
            {
                r:{
                    clientid       // 客户端id
                    psessionid     // session id
                    key             // 固定为0
                    ids             // 固定为 []
                }
                clientid
                psessionid
            }

        头部:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=2"
        """
        if not self.poll_and_heart:
            self.poll_and_heart = True
        url = "http://d.web2.qq.com/channel/poll2"
        rdic = {
            "clientid": self.clientid,
            "psessionid": self.psessionid,
            "key": 0,
            "ids": []
        }
        params = [("r", json.dumps(rdic)), ("clientid", self.clientid),
                  ("psessionid", self.psessionid)]
        headers = {
            "Referer":
            "https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1",
            "Origin": "http://d.web2.qq.com"
        }

        self.http.post(url,
                       params,
                       headers=headers,
                       request_timeout=60.0,
                       connect_timeout=60.0,
                       callback=self.handle_msg)

    def handle_msg(self, resp):
        """ 处理消息 """
        self.poll()
        data = resp.body
        try:
            msg = json.loads(data)
            if msg.get("retcode") in [121, 103]:
                logging.error(u"登录失败")
                return
            logging.info(u"获取消息: {0!r}".format(msg))
            self.msg_dispatch.dispatch(msg)
        except ValueError:
            traceback.print_exc()
            logging.error(u"消息加载失败: %s", data)

    def heartbeat(self, delay=60):
        """ 开始心跳
        url:http://web.qq.com/web2/get_msg_tip
        方法: GET
        参数:
            {
                uin  // 固定为空
                tp   // 固定为1
                rc   // 固定为1
                id   // 固定位0
                lv   // 固定为2
                t    // 开始的心跳时间(int(time.time()) * 1000)
            }
        """
        logging.info("心跳..")
        self.login_time = time.time()

        if not self.poll_and_heart:
            self.poll_and_heart = True

        url = "http://web.qq.com/web2/get_msg_tip"
        params = [("uin", ""), ("tp", 1), ("id", 0), ("retype", 1),
                  ("rc", self.rc), ("lv", 3), ("t", int(self.hb_time * 1000))]
        self.rc += 1

        self.http.get(url, params, callback=self.hb_next, delay=delay)

    def hb_next(self, resp):
        """ 持续心跳 """
        self.heartbeat()

    def make_msg_content(self, content):
        """ 构造QQ消息的内容 """
        self.msg_id += 1
        return json.dumps([
            content,
            [
                "font", {
                    "name": "Monospace",
                    "size": 10,
                    "style": [0, 0, 0],
                    "color": "000000"
                }
            ]
        ])

    def get_sess_group_sig(self, to_uin, callback):
        """ 获取临时消息组签名
        URL: http://d.web2.qq.com/channel/get_c2cmsg_sig2
        METHOD: GET
        PARAMS:
            id   // 请求ID 固定为833193360
            to_uin   // 消息接受人uin( 消息的from_uin)
            service_type   // 固定为0
            clientid       // 客户端id
            psessionid     // session id
            t              // 当前时间秒1370671760656
        HEADERS:
        Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        url = "http://d.web2.qq.com/channel/get_c2cmsg_sig2"
        params = (("id", 833193360), ("to_uin", to_uin), ("service_type", 0),
                  ("clientid", self.clientid), ("psessionid", self.psessionid),
                  ("t", time.time()))

        def callback(resp):
            data = resp.body
            r = json.loads(data)
            result = r.get("result")
            group_sig = result.get("value")
            if r.get("retcode") != 0:
                logging.warn(u"加载临时消息签名失败: {0}".format(group_sig))
                return
            try:
                logging.info("加载临时消息签名 {0} for {1}".format(group_sig, to_uin))
            except UnicodeError:
                return
            self.group_sig[to_uin] = group_sig
            callback()

        self.http.get(url, params, callback=callback, headers=self.base_header)

    def send_sess_msg(self, to_uin, content):
        """ 发送临时消息
        URL:http://d.web2.qq.com/channel/send_sess_msg2
        METHOD: POST
        PARAMS:
            r:{
                to              // 消息接收人 uin
                group_sig       // 组签名
                face            // 固定为 564,
                content         // 发送内容
                msg_id          // 消息id
                service_type    // 固定为0,
                clientid        // 客户端id
                psessionid      // sessionid
                }
            clientid                // 客户端id
            psessionid              // sessionid
        Headers:
            self.base_header
        """
        group_sig = self.group_sig.get(to_uin)
        if not group_sig:
            callback = partial(self.send_sess_msg, to_uin, content)
            return self.get_sess_group_sig(to_uin, callback)

        logging.info(u"发送临时消息 {0} 到 {1}".format(content, to_uin))
        delay, n = self.get_delay(content)
        content = self.make_msg_content(content)
        url = "http://d.web2.qq.com/channel/send_sess_msg2"
        params = (("r",
                   json.dumps({
                       "to": to_uin,
                       "group_sig": group_sig,
                       "face": 564,
                       "content": content,
                       "msg_id": self.msg_id,
                       "service_type": 0,
                       "clientid": self.clientid,
                       "psessionid": self.psessionid
                   })), ("clientid", self.clientid), ("psessionid",
                                                      self.psessionid))

        def callback(resp):
            self.last_msg_numbers -= n

        self.http.post(url,
                       params,
                       headers=self.base_header,
                       callback=callback,
                       delay=delay)

    def send_buddy_msg(self, to_uin, content):
        """ 发送好友消息
        URL:
            http://d.web2.qq.com/channel/send_buddy_msg2

        METHOD:
            POST

        PARAMS:
            {
                "r":{
                    "to"            // 好友uin
                    "face"          // 固定为564
                    "content"       // 发送内容
                    "msg_id"        // 消息id, 每发一条递增
                    "clientid"      // 客户端id
                    "psessionid"    // sessionid
                    }
                "clientid":clientid,
                "psessionid": psessionid,
            }

        HEADERS:
            Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        logging.info(u"发送好友消息 {0} 给 {1}".format(content, to_uin))
        content = self.make_msg_content(content)

        url = "http://d.web2.qq.com/channel/send_buddy_msg2"

        r = {
            "to": to_uin,
            "face": 564,
            "content": content,
            "clientid": self.clientid,
            "msg_id": self.msg_id,
            "psessionid": self.psessionid
        }
        params = [("r", json.dumps(r)), ("clientid", self.clientid),
                  ("psessionid", self.psessionid)]
        headers = {"Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        delay, n = self.get_delay(content)

        def callback(resp):
            self.last_msg_numbers -= n

        self.http.post(url,
                       params,
                       headers=headers,
                       delay=delay,
                       callback=callback)

    def send_group_msg(self, group_uin, content):
        """ 发送群消息
        url:http://d.web2.qq.com/channel/send_qun_msg2
        方法: POST
        参数:
            {
                r:{
                    group_uin           // gid
                    content             // 发送内容
                    msg_id              // 消息id, 每次发送消息应该递增
                    clientid            // 客户端id
                    psessionid          // sessionid
                }
                clientid
                psessionid
            }
        """
        gid = self.group_info.get(group_uin, {}).get("gid")
        source = content
        content = self.make_msg_content(source)

        url = "http://d.web2.qq.com/channel/send_qun_msg2"
        r = {
            "group_uin": gid,
            "content": content,
            "msg_id": self.msg_id,
            "clientid": self.clientid,
            "psessionid": self.psessionid
        }
        params = [("r", json.dumps(r)), ("psessionid", self.psessionid),
                  ("clientid", self.clientid)]

        delay, n = self.get_delay(content)
        callback = self.send_group_msg_back

        self.http.post(url,
                       params,
                       headers=self.base_header,
                       callback=callback,
                       args=(source, group_uin, n),
                       delay=delay)

    def get_delay(self, content):
        MIN = MESSAGE_INTERVAL
        delay = 0

        if time.time() - self.last_group_msg_time < MIN or\
           self.last_msg_numbers > 0:
            delay = self.last_msg_numbers * MIN

        numbers = 1
        if self.last_msg_content == content:
            delay += 0.5
            self.last_msg_numbers += 1
            numbers = 2
        self.last_msg_numbers += 1
        self.last_msg_content = content
        if delay:
            logging.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递".format(
                delay, self.last_msg_numbers))
        return delay, numbers

    def send_group_msg_back(self, content, group_uin, n, resp):
        logging.info(u"发送群消息 {0} 到 {1}".format(content, group_uin))
        self.last_group_msg_time = time.time()
        if self.last_msg_numbers > 0:
            self.last_msg_numbers -= n

    def set_signature(self, signature, password, callback):
        """ 设置QQ签名,
        可以通过发送好友消息设置签名, 消息应按照如下格式:
            设置签名:[密码]|[签名内容]    // 密码和签名内容不能包含分割符
        url: http://s.web2.qq.com/api/set_long_nick2
        method: POST
        params:
                r : {
                    nlk         // 签名内容
                    vfwebqq     // 登录时获取的cookie值
                }
        headers:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """
        if password != Set_Password:
            return callback(u"你没有权限这么做")

        logging.info(u"设置QQ签名 {0}".format(signature))

        url = "http://s.web2.qq.com/api/set_long_nick2"
        params = (("r", json.dumps({
            "nlk": signature,
            "vfwebqq": self.vfwebqq
        })), )
        headers = {"Origin": "http://s.web2.qq.com"}
        headers.update(self.base_header)

        def callback(resp):
            data = resp.body
            print data
            result = json.loads(data).get("retcode")
            if result == 0:
                callback(u"设置成功")
            else:
                callback(u"设置失败")

        self.http.post(url, params, headers=headers, callback=callback)

    def run(self):
        self.get_login_sig()
        self.http.start()
Пример #12
0
    def find(self):
        pprint('Into tornadohttpclient')
        s = TornadoHTTPClient(force_instance = True)

        pprint('Start login')
        f = yield s.get('http://ehire.51job.com')
        soup = BeautifulSoup(f.body, "html.parser")
        hidAccessKey = soup.find('input', {'name': 'hidAccessKey'})['value']
        fksc = soup.find('input', {'name': 'fksc'})['value']
        hidEhireGuid = soup.find('input', {'name': 'hidEhireGuid'})['value']

        headers = {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Origin': 'http://ehire.51job.com',
            'Referer': 'http://ehire.51job.com/MainLogin.aspx',
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
        }
        data = {'ctmName': '大岂网络',
                'userName': '******',
                'password': '******',
                'oldAccessKey': hidAccessKey,
                'langtype': 'Lang=&Flag=1',
                'sc': fksc,
                'ec': hidEhireGuid,
                'isRememberMe': 'True'
                }
        res = yield s.post('https://ehirelogin.51job.com/Member/UserLogin.aspx', data=data, headers=headers)
        pprint('End login')

        pprint('Start force')
        try:
            soup = BeautifulSoup(res.body, "html.parser")
            viewState = soup.find('input', {'name': '__VIEWSTATE'})['value']
            partURL = soup.find('form', {'id': 'form1'})['action']
    
            URL = 'http://ehire.51job.com/Member/' + partURL
            headers = {
                'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Origin': 'http://ehire.51job.com',
                'Referer': URL,
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
            }
            data = {'__EVENTTARGET': 'gvOnLineUser',
                    '__EVENTARGUMENT': 'KickOut$0',
                    '__VIEWSTATE': viewState
                    }
            res = yield s.post(URL, data=data, headers=headers)
        except: pass # 有时不需要下线
        pprint('End force')

        pprint('Start fetch remain')
        resp = yield s.get('http://ehire.51job.com/CommonPage/JobsPostNumbList.aspx')
        pprint('End   fetch remain')

        bs = BeautifulSoup(resp.body, "html.parser")
        pprint(bs.find('b', {'class': 'info_att'}).text)

        pprint('###Cookie###')
        pprint(s.cookie)

        with open('cookie.txt', 'w') as f:
            f.write(s.cookie)
Пример #13
0
Файл: hub.py Проект: udbmnm/twqq
class RequestHub(object):

    """ 集成Request请求和保存请求值
    :param qid: qq号
    :param pwd: 密码
    :param client: ~twqq.client.Client instance
    """

    SIG_RE = re.compile(r'var g_login_sig=encodeURIComponent\("(.*?)"\);')

    def __init__(self, qid, pwd, client=None, debug=False):
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36"
        )
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.http.debug = debug

        self.qid = qid
        self.__pwd = pwd
        self.client = client

        self.rc = random.randrange(0, 100)
        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)  # 消息id, 随机初始化
        self.daid = 164
        self.login_sig = None
        self.ptwebqq = None
        self.nickname = u"YouWillNeverGetIt"
        self.vfwebqq = None
        self.psessionid = None
        self.stop_poll = False

        # 检查是否验证码的回调
        self.ptui_checkVC = lambda r, v, u: (r, v, u)

        # 是否需要验证码
        self.require_check = None
        self.require_check_time = None

        # 是否开始心跳和拉取消息
        self.poll_and_heart = None
        self.login_time = None
        self.hThread = None

        # 验证图片
        self.checkimg_path = tempfile.mktemp(".jpg")
        self._lock_path = tempfile.mktemp()
        self._wait_path = tempfile.mktemp()

        self.friend_info = {}  # 初始化好友列表
        self.group_info = {}  # 初始化组列表
        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {}  # 初始化组成员列表
        self.mark_to_uin = {}  # 备注名->uin的映射

        self.message_interval = 0.5  # 消息间隔
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量
        WebQQRequest.hub = self
        self.load_next_request(FirstRequest())

    def load_next_request(self, request):
        """ 加载下一个请求

        :param request: ~twqq.requests.WebQQRequest instance
        :rtype: ~twqq.requests.WebQQRequest instance
        """
        func = self.http.get if request.method == WebQQRequest.METHOD_GET else self.http.post

        if self.stop_poll and isinstance(request, PollMessageRequest):
            logger.info("检测Poll已停止, 此请求不处理: {0}".format(request))
            return

        kwargs = copy.deepcopy(request.kwargs)
        callback = request.callback if hasattr(request, "callback") and callable(request.callback) else None
        kwargs.update(callback=self.wrap(request, callback))
        kwargs.update(headers=request.headers)
        kwargs.update(delay=request.delay)
        logger.debug("KWARGS: {0}".format(kwargs))

        if request.ready:
            logger.debug("处理请求: {0}".format(request))
            with ExceptionStackContext(request.handle_exc):
                func(request.url, request.params, **kwargs)
        else:
            logger.debug("请求未就绪: {0}".format(request))

        return request

    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd

    def upload_file(self, filename, path):
        """ 上传文件

        :param filename: 文件名
        :param path: 文件路径
        """
        form = Form()
        filename = filename.encode("utf-8")
        form.add_file(fieldname="name", filename=filename, fileHandle=open(path))
        img_host = "http://dimg.vim-cn.com/"
        req = urllib2.Request(img_host)
        req.add_header("Content-Type", form.get_content_type())
        req.add_header("Content-Length", len(str(form)))
        req.add_header("User-Agent", "curl/python")
        req.add_data(str(form))
        return urllib2.urlopen(req)

    def lock(self):
        """ 当输入验证码时锁住
        """
        with open(self._lock_path, "w"):
            pass

    def unlock(self):
        """ 解锁
        """
        if os.path.exists(self._lock_path):
            os.remove(self._lock_path)

    def clean(self):
        """ 清除锁住和等待状态
        """
        self.unlock()
        self.unwait()

    def wait(self):
        """ 当没有验证是否需要验证码时等待
        """
        with open(self._wait_path, "w"):
            pass

    def unwait(self):
        """ 解除等待状态
        """
        if os.path.exists(self._wait_path):
            os.remove(self._wait_path)

    def is_lock(self):
        """ 检测是否被锁住
        """
        return os.path.exists(self._lock_path)

    def is_wait(self):
        """ 检测是否在等待生成验证码
        """
        return os.path.exists(self._wait_path)

    def _hash(self):
        """  获取好友列表时的Hash """
        return _hash.webqq_hash(self.qid, self.ptwebqq)

    def start_poll(self):
        """ 开始心跳和拉取信息
        """
        self.stop_poll = False
        if not self.poll_and_heart:
            self.login_time = time.time()
            logger.info("开始拉取信息")
            self.load_next_request(PollMessageRequest())
            self.poll_and_heart = True
            if self.hThread is None:
                logger.info("开始心跳")
                self.hThread = threading.Thread(target=self._heartbeat)
                self.hThread.setDaemon(True)
                self.hThread.start()

    def _heartbeat(self):
        """ 放入线程的产生心跳
        """
        assert not isinstance(threading.currentThread(), threading._MainThread)
        while 1:
            try:
                self.load_next_request(HeartbeatRequest())
            except:
                pass
            time.sleep(60)

    def make_msg_content(self, content, style):
        """ 构造QQ消息的内容

        :param content: 小心内容
        :type content: str
        :rtype: str
        """
        self.msg_id += 1
        return json.dumps([content, ["font", style]])

    def get_delay(self, content):
        """ 根据消息内容是否和上一条内容相同和未送出的消息数目产生延迟

        :param content: 消息内容
        :rtype: tuple(delay, number)
        """
        MIN = self.message_interval
        delay = 0
        sub = time.time() - self.last_msg_time
        if self.last_msg_numbers < 0:
            self.last_msg_numbers = 0

        # 不足最小间隔就补足最小间隔
        if sub < MIN:
            delay = MIN
            logger.debug(u"间隔 %s 小于 %s, 设置延迟为%s", sub, MIN, delay)

        # 如果间隔是已有消息间隔的2倍, 则清除已有消息数
        # print "sub", sub, "n:", self.last_msg_numbers
        if self.last_msg_numbers > 0 and sub / (MIN * self.last_msg_numbers) > 1:
            self.last_msg_numbers = 0

        # 如果还有消息未发送, 则加上他们的间隔
        if self.last_msg_numbers > 0:
            delay += MIN * self.last_msg_numbers
            logger.info(u"有%s条消息未发送, 延迟为 %s", self.last_msg_numbers, delay)

        n = 1
        # 如果这条消息和上条消息一致, 保险起见再加上一个最小间隔
        if self.last_msg_content == content and sub < MIN:
            delay += MIN
            self.last_msg_numbers += 1
            n = 2

        self.last_msg_numbers += 1
        self.last_msg_content = content

        if delay:
            logger.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递".format(delay, self.last_msg_numbers))
        # 返回消息累加个数, 在消息发送后减去相应的数目
        return delay, n

    def consume_delay(self, number):
        """ 消费延迟

        :param number: 消费的消息数目
        """
        self.last_msg_numbers -= number
        self.last_msg_time = time.time()

    def get_group_id(self, uin):
        """ 根据组uin获取组的id

        :param uin: 组的uin
        """
        return self.group_info.get(uin, {}).get("gid")

    def get_friend_name(self, uin):
        """ 获取好友名称

        :param uin: 好友uin
        """
        info = self.friend_info.get(uin, {})
        name = info.get("markname")
        if name is None:
            name = info.get("nick")
        return name

    def wrap(self, request, func=None):
        """ 装饰callback

        :param request: ~twqq.requests.WebQQRequest instance
        :param func: 回调函数
        """

        def _wrap(resp, *args, **kwargs):
            data = resp.body
            logger.debug(resp.headers)
            if resp.headers.get("Content-Type") == "application/json":
                data = json.loads(data) if data else {}
            else:
                try:
                    data = json.loads(data)
                except:
                    pass
            if func:
                func(resp, data, *args, **kwargs)

            funcs = self.client.request_handlers.get(check_request(request), [])
            for f in funcs:
                f(request, resp, data)

        return _wrap

    def handle_qq_msg_contents(self, contents):
        """ 处理QQ消息内容

        :param contents: 内容
        :type contents: list
        """
        content = ""
        for row in contents:
            if isinstance(row, (str, unicode)):
                content += row.replace(u"【提示:此用户正在使用Q+" u" Web:http://web.qq.com/】", "").replace(
                    u"【提示:此用户正在使用Q+" u" Web:http://web3.qq.com/】", ""
                )
        return content.replace("\r", "\n").replace("\r\n", "\n").replace("\n\n", "\n")

    def get_group_member_nick(self, gcode, uin):
        """ 根据组代码和用户uin获取群成员昵称

        :param gcode: 组代码
        :param uin: 群成员uin
        """
        return self.group_members_info.get(gcode, {}).get(uin, {}).get("nick")

    def dispatch(self, qq_source):
        """ 调度QQ消息

        :param qq_source: 源消息包
        """
        if self.stop_poll:
            logger.info("检测Poll已停止, 此消息不处理: {0}".format(qq_source))
            return

        if qq_source.get("retcode") == 0:
            messages = qq_source.get("result")
            logger.info(u"获取消息: {0}".format(messages))
            for m in messages:
                funcs = self.client.msg_handlers.get(m.get("poll_type"), [])
                [func(*func._args_func(self, m)) for func in funcs]

    def relogin(self):
        """ 被T出或获取登出时尝试重新登录
        """
        self.stop_poll = True
        self.poll_and_heart = None
        self.load_next_request(Login2Request(relogin=True))

    def send_sess_msg(self, qid, to_uin, content, style=const.DEFAULT_STYLE):
        """ 发送临时消息

        :param qid: 发送临时消息的qid
        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(SessMsgRequest(qid, to_uin, content, style))

    def send_group_msg(self, group_uin, content, style=const.DEFAULT_STYLE):
        """ 发送群消息

        :param group_uin: 组的uin
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(GroupMsgRequest(group_uin, content, style))

    def send_discu_msg(self, did, content, style=const.DEFAULT_STYLE):
        """ 发送讨论组消息

        :param did: 讨论组id
        :param content: 内容
        """
        return self.load_next_request(DiscuMsgRequest(did, content, style))

    def send_buddy_msg(self, to_uin, content, style=const.DEFAULT_STYLE):
        """ 发送好友消息

        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(BuddyMsgRequest(to_uin, content, style))

    def send_msg_with_markname(self, markname, content):
        """ 使用备注名发送消息

        :param markname: 备注名
        :param content: 消息内容
        :rtype: None or Request instance
        """
        uin = self.mark_to_uin.get(markname)
        if not uin:
            return
        return self.send_buddy_msg(uin, content)

    def accept_verify(self, uin, account, markname=""):
        """ 同意验证请求

        :param  uin: 请求人uin
        :param account: 请求人账号
        :param markname: 添加后的备注
        """
        return self.load_next_request(AcceptVerifyRequest(uin, account, markname))
Пример #14
0
class WebQQ(object):
    def __init__(self, qid, pwd, handler = None):
        self.qid = qid               # QQ 号
        self.__pwd = pwd             # QQ密码
        self.nickname = None         # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36")
        self.http.debug = TRACE
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903                                    # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)     # 消息id, 随机初始化

        self.require_check = False   # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.require_check_time = None  # 需要验证码的时间

        self.checkimg_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "check.jpg")

        self.friend_info = {}        # 初始化好友列表
        self.group_info = {}         # 初始化组列表
        self.group_sig = {}          # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {} # 初始化组成员列表
        self.mark_to_uin = {}        # 备注名->uin的映射

        self.daid = 164
        self.login_sig = None

        self.login_time = None       # 登录的时间
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0    # 剩余位发送的消息数量

        self.status_callback = None

        self.poll_stoped = True      # 获取消息是否停止
        self.hThread = None               # 心跳线程
        #self.base_header = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"}
        self.base_header = {"Referer":"http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=3"}


    def ptuiCB(self, scode, r, url, status, msg, nickname = None):
        """ 模拟JS登录之前的回调, 保存昵称 """
        if int(scode) == 0:
            logging.info("从Cookie中获取ptwebqq的值")
            old_value = self.ptwebqq
            try:
                self.ptwebqq = self.http.cookie['.qq.com']['/']['ptwebqq'].value
            except:
                logging.error("从Cookie中获取ptwebqq的值失败, 使用旧值尝试")
                self.ptwebqq = old_value
            self.logined = True
        elif int(scode) == 4:
            logging.error(msg)
            if self.status_callback:
                self.status_callback(False, msg)
            self.check()
        else:
            logging.error(u"server response: {0}".format(msg.decode('utf-8')))
            self.check()

        if nickname:
            self.nickname = nickname.decode('utf-8')


    def get_group_member_nick(self, gcode, uin):
        return self.group_members_info.get(gcode, {}).get(uin, {}).get("nick")

    def get_uptime(self):
        MIN = 60
        HOUR = 60 * MIN
        DAY = 24 * HOUR

        if not isinstance(self.login_time, (int, float)):
            self.login_time = time.time()

        up_time = datetime.fromtimestamp(self.login_time).strftime("%H:%M:%S")

        now = time.time()
        sub = now - self.login_time

        days = int(sub / DAY)
        hours = int(sub / HOUR)
        mins = int(sub / MIN)

        if mins:
            num = mins
            unit = "min"

        if hours:
            num = hours
            unit = "hours" if hours > 1 else "hour"

        if days:
            num = days
            unit = "days" if days > 1 else "day"

        if not days and not mins and not hours:
            num = int(sub)
            unit = "sec"

        return "{0} up {1} {2}".format(up_time, num, unit)


    def get_login_sig(self, handler = None):

        self.handler = handler # 启用HTTP_CHECKIMG的Handler
        self.stop_poll_heartbeat = False

        with open("wait", 'w'):
            pass

        logging.info("获取 login_sig...")
        url = "https://ui.ptlogin2.qq.com/cgi-bin/login"
        params = [("daid", self.daid), ("target", "self"), ("style", 5),
                  ("mibao_css", "m_webqq"), ("appid", self.aid),
                  ("enable_qlogin", 0), ("no_verifyimg", 1),
                  ("s_url", "http://web2.qq.com/loginproxy.html"),
                  ("f_url", "loginerroralert"),
                  ("strong_login", 1), ("login_state", 10),
                  ("t", "20130723001")]
        self.http.get(url, params, callback = self._get_login_sig)
        self.http.get("http://web2.qq.com")

    def _get_login_sig(self, resp):
        sigs = SIG_RE.findall(resp.body)
        if len(sigs) == 1:
            self.login_sig = sigs[0]
            logging.info(u"获取Login Sig: {0}".format(self.login_sig))
        else:
            logging.warn(u"没有获取到 Login Sig, 后续操作可能失败")
            self.login_sig = ""

        self.check()


    def clean(self):
        if os.path.exists("lock"):
            os.remove("lock")

        if os.path.exists("wait"):
            os.remove("wait")



    def check(self):
        """ 检查是否需要验证码
        url :
            https://ssl.ptlogin2.qq.com/check
        方法:   GET
        参数:
            {
                uin     // qq号
                appid   // 程序id 固定为1003903
                r       // 随机数
                u1      // http://web2.qq.com/loginproxy.html
                js_ver  // 10040
                js_type // 0
            }
        返回:
            ptui_checkVC('0','!PTH','\x00\x00\x00\x00\x64\x74\x8b\x05');
            第一个参数表示状态码, 0 不需要验证, 第二个为验证码, 第三个为uin
        """
        self.clean()
        self.poll_stoped = True   # 检查时停止轮询消息
        logging.info(u"检查是否需要验证码...")
        #url = "https://ssl.ptlogin2.qq.com/check"
        url = "http://check.ptlogin2.qq.com/check"
        params = {"uin":self.qid, "appid":self.aid,
                  "u1": "http://web2.qq.com/loginproxy.html",
                  "login_sig":self.login_sig, "js_ver":10040,
                  "js_type":0, "r" : random.random()}
        headers = {"Referer":"https://ui.ptlogin2.qq.com/cgi-bin/login?daid="
                   "164&target=self&style=5&mibao_css=m_webqq&appid=1003903&"
                   "enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb2.q"
                   "q.com%2Floginproxy.html&f_url=loginerroralert&strong_log"
                   "in=1&login_state=10&t=20130723001"}
        self.http.get(url, params, headers = headers, callback = self.handle_verify)


    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd


    def get_check_img(self, r, vcode, uin):
        """ 获取验证图片 """

        def callback(resp):
            path = self.checkimg_path
            fp = open(path, 'wb')
            fp.write(resp.body)
            fp.close()
            if UPLOAD_CHECKIMG:
                logging.info(u"正在上传验证码..")
                res = upload_file("check.jpg", path)
                path = res.read()
            check_code = ""
            if not HTTP_CHECKIMG:
                print u"验证图片: {0}".format(path)
                while not check_code:
                    check_code = raw_input("输入验证图片上的验证码: ")
                ccode = check_code.strip().lower()
                self.check_code = ccode
                pwd = self.handle_pwd(r, ccode.upper(), uin)
                self.before_login(pwd)
            else:
                if os.path.exists("wait"):
                    os.remove("wait")
                logging.info(u"请打开http://{0}:{1} 提交验证码"
                             .format(HTTP_LISTEN, HTTP_PORT))
                self.handler.r = r
                self.handler.uin = uin
                self.handler.next_callback = self.before_login
                self.require_check_time = time.time()
                if EMAIL_NOTICE:
                    if send_notice_email():
                        logging.info(u"已成功发送邮件提醒")
                    else:
                        logging.warn(u"发送邮件提醒失败")


        url = "https://ssl.captcha.qq.com/getimage"
        params = [("aid", self.aid), ("r", random.random()),
                ("uin", self.qid)]
        self.http.get(url, params, callback = callback)


    def handle_verify(self, resp):
        ptui_checkVC = lambda r, v, u: (r, v, u)
        data = resp.body
        if not data:
            self.check()   # 没有数据重新检查
            return

        r, vcode, uin = eval(data.strip().rstrip(";"))
        logging.info("R:{0} vcode:{1} uin:{2}".format(r, vcode, uin))
        if int(r) == 0:
            logging.info("验证码检查完毕, 不需要验证码")
            password = self.handle_pwd(r, vcode, uin)
            self.check_code = vcode
            self.clean()
            self.before_login(password)
        else:
            logging.warn("验证码检查完毕, 需要验证码")
            self.get_check_img(r, vcode, uin)
            self.require_check = True


    def before_login(self, password, callback = None):
        """ 登录之前的操作
        url:
            https://ssl.ptlogin2.qq.com/login
        方法:   GET
        参数:
            {
                u       // qq号码
                p       // 经过处理的密码
                verifycode  // 验证码
                webqq_type  // 固定为10
                remember_uin    // 是否记住qq号, 传1 即可
                login2qq        // 登录qq, 传1
                aid             // appid 固定为 1003903
                u1              // 固定为 http://www.qq.com
                h               // 固定为1
                ptrediect       // 固定为0
                ptlang          // 固定为2052
                from_ui         // 固定为 1
                pttype          // 固定为1
                dumy            // 固定为空
                fp              // 固定为loginerroralert ( 重要)
                mibao_css       // 固定为 m_webqq
                t               // 固定为1
                g               // 固定为
                js_type         // 固定为0
                js_ver          // 固定为10021
        其他:
            如果check步骤验证了需要验证码, 需加上 Referer头 值为:
            https://ui.ptlogin2.qq.com/cgi-bin/login?target=self&style=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20130221001

        接口返回:
            ptuiCB('0','0','http://www.qq.com','0','登录成功!', 'nickname');
        先检查是否需要验证码,不需要验证码则首先执行一次登录
        然后获取Cookie里的ptwebqq保存在实例里,供后面的接口调用
        """
        with open("lock", 'w'):
            pass

        if callback:
            self.status_callback = callback
        url = "https://ssl.ptlogin2.qq.com/login"
        params = [("u",self.qid), ("p",password), ("verifycode", self.check_code),
                  ("webqq_type",10), ("remember_uin", 1),("login2qq",1),
                  ("aid", self.aid), ("u1", "http://www.qq.com/loginproxy.h"
                                      "tml?login2qq=1&webqq_type=10"),
                  ("h", 1), ("action", 4-5-8246),
                  ("ptredirect", 0), ("ptlang", 2052), ("from_ui", 1),
                  ("daid", self.daid),
                  ("pttype", 1), ("dumy", ""), ("fp", "loginerroralert"),
                  ("mibao_css","m_webqq"), ("t",1), ("g",1), ("js_type",0),
                  ("js_ver", 10040), ("login_sig", self.login_sig)]
        headers = {}
        if self.require_check:
            headers.update(Referer =  "https://ui.ptlogin2.qq.com/cgi-"
                            "bin/login?target=self&style=5&mibao_css=m_"
                            "webqq&appid=1003903&enable_qlogin=0&no_ver"
                            "ifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Fl"
                            "oginproxy.html&f_url=loginerroralert&stron"
                            "g_login=1&login_state=10&t=20130221001")
        else:
            headers.update(Referfer = "https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&style=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20130903001")
        logging.info("检查完毕, 开始登录前准备")
        self.http.get(url, params, headers = headers, callback = self.login0)


    def login0(self, resp):
        logging.info("开始登录前准备...")
        blogin_data = resp.body.decode("utf-8").strip().rstrip(";")
        eval("self." + blogin_data)

        try:
            location1 = re.findall(r'ptuiCB\(\'0\'\,\'0\'\,\'(.*)\'\,\'0\'\,',
                                blogin_data)[0]
            params = []
            header = {"Referer": "https://ui.ptlogin2.qq.com/cgi-bin/login?d"
                    "aid=164&target=self&style=5&mibao_css=m_webqq&appid=1"
                    "003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2"
                    "F%2Fweb2.qq.com%2Floginproxy.html&f_url=loginerrorale"
                    "rt&strong_login=1&login_state=10&t=20130723001"}
            self.http.get(location1, params, headers = header,
                        callback = self.get_location1)
        except:
            pass


    def get_location1(self, resp):
        if os.path.exists(self.checkimg_path):
            os.remove(self.checkimg_path)

        if os.path.exists("lock"):
            os.remove("lock")

        logging.info("准备完毕, 开始登录")
        self.login()


    def login(self):
        """ 获取登录前的数据, 并进行登录
        url:
            http://d.web2.qq.com/channel/login2
        方法: POST
        参数:
            {
                r : {
                    status      // 登录后的状态 ("online")
                    ptwebqq     // 上次请求返回的cookie
                    passwd_sig  // 固定为空
                    clientid    // 随机的clientid
                    psessionid  // 传递 null
                }
                clientid    // 客户端id
                psessionid  // 传递null
            }
        其他:
            需加上 Referer和 Origin 头:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
            "Origin": "http://d.web2.qq.com"

        返回:
            {u'retcode': 0,
            u'result': {
                'status': 'online', 'index': 1075,
                'psessionid': '', u'user_state': 0, u'f': 0,
                u'uin': 1685359365, u'cip': 3673277226,
                u'vfwebqq': u'', u'port': 43332}}
            保存result中的psessionid和vfwebqq供后面接口调用
        """
        #time.sleep(4)

        url = "http://d.web2.qq.com/channel/login2"
        params = [("r", '{"status":"online","ptwebqq":"%s","passwd_sig":"",'
                '"clientid":"%d","psessionid":null}'\
                % (self.ptwebqq, self.clientid)),
                ("clientid", self.clientid),
                ("psessionid", "null")
                ]

        headers = { "Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        logging.info("登录准备完毕, 开始登录")
        self.http.post(url, params, headers = headers,
                              callback= self.login_back)

    def login_back(self, resp):
        self.require_check_time = None
        if not resp.body:
            logging.error(u"没有获取到数据, 登录失败")
            if self.status_callback:
                self.status_callback(False, "登录失败 没有数据返回")
            return self.check()

        data = json.loads(resp.body)

        if data.get("retcode") != 0:
            if self.status_callback:
                self.status_callback(False, "登录失败 {0}".format(data))

            logging.error("登录失败 {0!r}".format(data))
            return
        self.vfwebqq = data.get("result", {}).get("vfwebqq")
        self.psessionid = data.get("result", {}).get("psessionid")
        logging.info("登录成功")


        if not DEBUG and not HTTP_CHECKIMG:
            aw = ""
            while aw.lower() not in ["y", "yes", "n", "no"]:
                aw = raw_input("是否将程序至于后台[y] ")
                if not aw:
                    aw = "y"

            if aw in ["y", "yes"]:
                run_daemon(self.update_friend)
                return

        self.update_friend()

    def _hash(self):
        """  获取列表时的Hash """
        return _hash.webqq_hash(self.qid, self.ptwebqq)
        # l = len(e)
        # # 将qq号码转换成整形列表
        # b, k, d = 0, -1, 0
        # for d in a:
        #     d = int(d)
        #     b += d
        #     b %= l
        #     f = 0
        #     if b + 4 > l:
        #         g = 4 + b - l
        #         for h in range(4):
        #             f |= h < g and (
        #                 ord(e[b + h]) & 255) << (3 - h) * 8 or (
        #                     ord(e[h - g]) & 255) << (3 - h) * 8
        #     else:
        #         for h in range(4):
        #             f |= (ord(e[b + h]) & 255) << (3 - h) * 8
        #     k ^= f
        # c = [k >> 24 & 255, k >> 16 & 255, k >> 8 & 255, k & 255]
        # import string
        # k = list(string.digits) + ['A', 'B', 'C', 'D', 'E', 'F']
        # d = [k[b >> 4 & 15] + k[b & 15] for b in c]
        # return ''.join(d)


    def update_friend(self, resp = None, call_status = True):
        """ 更新好友列表
        URL:
            http://s.web2.qq.com/api/get_user_friends2
        METHOD:
            POST
        PARAMS:
            {r:{"h":"hello", "vfwebqq":""}}
        HEADER:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """

        url = "http://s.web2.qq.com/api/get_user_friends2"
        params = [("r", json.dumps({"h":"hello", "hash":self._hash(),
                                    "vfwebqq":self.vfwebqq}))]
        headers = {"Referer":
            "http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1"}

        callback = self.update_friend

        if resp is None:
            self.poll_stoped = False           # 可以开始轮询消息
            self.http.post(url, params, headers = headers, callback = callback)
        else:
            if not resp.body:
                if self.status_callback and call_status:
                    self.status_callback(False, u"更新好友信息失败")
                return
            data = json.loads(resp.body)
            if data.get("retcode") != 0 and call_status:
                self.status_callback(False, u"好友列表加载失败, 错误代码:{0}"
                                     .format(data.get("retcode")))
                return

            lst = data.get("result", {}).get("info", [])
            for info in lst:
                uin = info.get("uin")
                self.friend_info[uin] = info

            marknames = data.get("result", {}).get("marknames", [])
            [self.mark_to_uin.update({minfo.get("markname"): minfo.get("uin")})
             for minfo in marknames]

            logging.debug("加载好友信息 {0!r}".format(self.friend_info))
            logging.info(data)
            if self.status_callback and call_status:
                self.status_callback(True)
            self.update_group()

            self.http.post(url, params, headers = self.base_header,
                           delay = 3600, callback = callback,
                           kwargs = {"call_status":False})


    def update_group(self, resp = None):
        """ 获取组列表, 并获取组成员
        获取组列表:
            url:
                http://s.web2.qq.com/api/get_group_name_list_mask2
            method:
                POST
            params:
                {
                    r : {
                        vfwebqq     // 登录前返回的cookie值
                    }
                }

        """
        logging.info("获取群列表")
        url = "http://s.web2.qq.com/api/get_group_name_list_mask2"
        params = [("r", '{"vfwebqq":"%s"}' % self.vfwebqq),]
        headers = {"Origin": "http://s.web2.qq.com"}
        headers.update(self.base_header)
        self.http.post(url, params, headers = headers,
                              callback = self.group_members)


    def group_members(self, resp):
        """ 获取群列表, 获取群列表中的成员
        url: http://s.web2.qq.com/api/get_group_info_ext2
        method: GET
        params:
            {
                gcode           // 群代码
                vfwebqq         // 登录前的cookie值
                t               // int(time.time())
            }
        headers:
            "Referer":
            "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
        """
        logging.info("加载组成员信息")
        data = json.loads(resp.body)
        logging.debug(u"群信息 {0!r}".format(data))
        group_list = data.get("result", {}).get("gnamelist", [])
        logging.debug(u"群列表: {0!r}".format(group_list))
        if not group_list and not self.poll_and_heart:
            self.heartbeat(0)
            self.poll()

        for i, group in enumerate(group_list):
            gcode = group.get("code")
            url = "http://s.web2.qq.com/api/get_group_info_ext2"
            params = [("gcode", gcode),("vfwebqq", self.vfwebqq),
                      ("cb", "undefined"), ("t", int(time.time()))]

            kwargs = dict(gcode = gcode)

            if i == 0:
                kwargs.update(poll = True)

            self.http.get(url, params, headers = self.base_header,
                          callback = self.do_group_members, kwargs = kwargs,
                          delay = i * 300)

            self.group_info[gcode] = group


    def do_group_members(self, resp, gcode, poll = False):
        """ 获取群成员数据 """
        data = json.loads(resp.body)
        logging.debug(u"获取群成员信息 {0!r}".format(data))
        members = data.get("result", {}).get("minfo", [])
        self.group_members_info[gcode] = {}
        for m in members:
            uin = m.get("uin")
            self.group_members_info[gcode][uin] = m

        cards = data.get("result", {}).get("cards", [])

        for card in cards:
            uin = card.get("muin")
            group_name = card.get("card")
            self.group_members_info[gcode][uin]["nick"] = group_name

        logging.debug(u"群成员信息: {0!r}".format(self.group_members_info))


        if poll and not self.poll_and_heart:
            logging.info("开始拉取信息和心跳")
            self.login_time = time.time()
            self.poll()
            self.heartbeat(0)


    def poll(self):
        """ 建立长连接获取消息
        url:http://d.web2.qq.com/channel/poll2
        方法: POST
        参数:
            {
                r:{
                    clientid       // 客户端id
                    psessionid     // session id
                    key             // 固定为0
                    ids             // 固定为 []
                }
                clientid
                psessionid
            }

        头部:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=2"
        """
        if not self.poll_and_heart:
            self.poll_and_heart = True
        url = "http://d.web2.qq.com/channel/poll2"
        rdic = {"clientid": self.clientid, "psessionid": self.psessionid,
                "key": 0, "ids":[]}
        params = [("r", json.dumps(rdic)), ("clientid", self.clientid),
                ("psessionid", self.psessionid)]
        headers = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1",
                   "Origin":"http://d.web2.qq.com"}

        self.http.post(url, params, headers = headers, request_timeout = 60.0,
                       connect_timeout = 60.0, callback = self.handle_msg)


    def handle_msg(self, resp):
        """ 处理消息 """
        if self.poll_stoped:
            return
        self.poll()
        if not resp.body:
            return

        data = resp.body
        try:
            msg = json.loads(data)
            if msg.get("retcode") in [121, 100006]:
                logging.error(u"获取消息异常 {0!r}".format(data))
                exit()
            logging.info(u"获取消息: {0!r}".format(msg))
            self.msg_dispatch.dispatch(msg)
        except ValueError:
            if DEBUG:
                traceback.print_exc()
            logging.error(u"消息加载失败: %s", data)


    def heartbeat(self, delay = 60):
        """ 开始心跳
        url:http://web.qq.com/web2/get_msg_tip
        方法: GET
        参数:
            {
                uin  // 固定为空
                tp   // 固定为1
                rc   // 固定为1
                id   // 固定位0
                lv   // 固定为2
                t    // 开始的心跳时间(int(time.time()) * 1000)
            }
        """

        if not self.poll_and_heart:
            self.poll_and_heart = True

        self.hThread = threading.Thread(name="heartThead#1",
                                        target= self._heartbeat)
        self.hThread.setDaemon(True)
        self.hThread.start()


    def _heartbeat(self):
        i = self.rc
        url = "http://web.qq.com/web2/get_msg_tip"
        params = dict([("uin", ""), ("tp", 1), ("id", 0), ("retype", 1),
                       ("rc", i), ("lv", 3), ("t", int(time.time() * 1000))])

        def callback(resp):
            logging.info("心跳..")

        while True:
            try:
                self.http.get(url, params, callback = callback, connect_timeout = 1.0,
                              request_timeout = 1.0)
            except:
                pass
            i += 1
            params["rc"] = i
            time.sleep(60)


    def make_msg_content(self, content):
        """ 构造QQ消息的内容 """
        self.msg_id += 1
        return json.dumps([content, ["font", {"name":"Monospace", "size":10,
                                   "style":[0, 0, 0], "color":"000000"}]])


    def get_sess_group_sig(self, to_uin, callback):
        """ 获取临时消息组签名
        URL: http://d.web2.qq.com/channel/get_c2cmsg_sig2
        METHOD: GET
        PARAMS:
            id   // 请求ID 固定为833193360
            to_uin   // 消息接受人uin( 消息的from_uin)
            service_type   // 固定为0
            clientid       // 客户端id
            psessionid     // session id
            t              // 当前时间秒1370671760656
        HEADERS:
        Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        url = "http://d.web2.qq.com/channel/get_c2cmsg_sig2"
        params = (("id", 833193360), ("to_uin", to_uin), ("service_type", 0),
                  ("clientid", self.clientid), ("psessionid", self.psessionid),
                  ("t", time.time()))


        def callback(resp):
            data = resp.body
            r = json.loads(data)
            result = r.get("result")
            group_sig = result.get("value")
            if r.get("retcode") != 0:
                logging.warn(u"加载临时消息签名失败: {0}".format(group_sig))
                return
            try:
                logging.info("加载临时消息签名 {0} for {1}".format(group_sig, to_uin))
            except UnicodeError:
                return
            self.group_sig[to_uin] = group_sig
            callback()

        self.http.get(url, params, callback = callback, headers = self.base_header)


    def send_sess_msg(self, to_uin, content):
        """ 发送临时消息
        URL:http://d.web2.qq.com/channel/send_sess_msg2
        METHOD: POST
        PARAMS:
            r:{
                to              // 消息接收人 uin
                group_sig       // 组签名
                face            // 固定为 564,
                content         // 发送内容
                msg_id          // 消息id
                service_type    // 固定为0,
                clientid        // 客户端id
                psessionid      // sessionid
                }
            clientid                // 客户端id
            psessionid              // sessionid
        Headers:
            self.base_header
        """
        group_sig = self.group_sig.get(to_uin)
        if not group_sig:
            callback = partial(self.send_sess_msg, to_uin, content)
            return self.get_sess_group_sig(to_uin, callback)

        logging.info(u"发送临时消息 {0} 到 {1}".format(content, to_uin))
        delay, n = self.get_delay(content)
        content = self.make_msg_content(content)
        url = "http://d.web2.qq.com/channel/send_sess_msg2"
        params = (("r", json.dumps({"to":to_uin, "group_sig":group_sig,
                                    "face":564, "content":content,
                                    "msg_id": self.msg_id, "service_type":0,
                                    "clientid":self.clientid,
                                    "psessionid":self.psessionid})),
                  ("clientid", self.clientid), ("psessionid", self.psessionid))
        def callback(resp):
            self.last_msg_numbers -= n
            self.last_msg_time = time.time()

        self.http.post(url, params, headers = self.base_header,
                              callback = callback, delay = delay)


    def send_buddy_msg(self, to_uin, content, callback = None):
        """ 发送好友消息
        URL:
            http://d.web2.qq.com/channel/send_buddy_msg2

        METHOD:
            POST

        PARAMS:
            {
                "r":{
                    "to"            // 好友uin
                    "face"          // 固定为564
                    "content"       // 发送内容
                    "msg_id"        // 消息id, 每发一条递增
                    "clientid"      // 客户端id
                    "psessionid"    // sessionid
                    }
                "clientid":clientid,
                "psessionid": psessionid,
            }

        HEADERS:
            Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        content = self.make_msg_content(content)

        url = "http://d.web2.qq.com/channel/send_buddy_msg2"

        r = {"to":to_uin, "face":564, "content":content,
             "clientid":self.clientid, "msg_id": self.msg_id,
             "psessionid": self.psessionid}
        params = [("r",json.dumps(r)), ("clientid",self.clientid),
                  ("psessionid", self.psessionid)]
        headers = {"Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        delay, n = self.get_delay(content)
        def _callback(resp):
            logging.info(u"发送好友消息 {0} 给 {1} 成功".format(content, to_uin))
            if callback:
                callback(True)
            self.last_msg_numbers -= n
            self.last_msg_time = time.time()

        logging.info(u"发送好友消息 {0} 给 {1} ...".format(content, to_uin))
        self.http.post(url, params, headers = headers, delay = delay,
                              callback = _callback)


    def send_group_msg(self, group_uin, content):
        """ 发送群消息
        url:http://d.web2.qq.com/channel/send_qun_msg2
        方法: POST
        参数:
            {
                r:{
                    group_uin           // gid
                    content             // 发送内容
                    msg_id              // 消息id, 每次发送消息应该递增
                    clientid            // 客户端id
                    psessionid          // sessionid
                }
                clientid
                psessionid
            }
        """
        gid = self.group_info.get(group_uin, {}).get("gid")
        source = content
        content = self.make_msg_content(source)

        url = "http://d.web2.qq.com/channel/send_qun_msg2"
        r = {"group_uin": gid, "content": content,
            "msg_id": self.msg_id, "clientid": self.clientid,
            "psessionid": self.psessionid}
        params = [("r", json.dumps(r)), ("psessionid", self.psessionid),
                ("clientid", self.clientid)]
        print params

        delay, n = self.get_delay(content)
        callback = self.send_group_msg_back


        logging.info(u"发送群消息 {0} 到 {1}...".format(content, group_uin))
        headers = {"Origin": "http://d.web2.qq.com",
                   "Referer":"http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=2"}
        self.http.post(url, params, headers = headers,
                       callback = callback, args = (source, group_uin, n),
                       delay = delay)


    def get_delay(self, content):
        MIN = MESSAGE_INTERVAL
        delay = 0
        sub = time.time() - self.last_msg_time
        if self.last_msg_numbers < 0:
            self.last_msg_numbers = 0

        # 不足最小间隔就补足最小间隔
        if sub < MIN:
            delay = MIN
            logging.debug(u"间隔 %s 小于 %s, 设置延迟为%s", sub, MIN, delay)

        # 如果间隔是已有消息间隔的2倍, 则清除已有消息数
        #print "sub", sub, "n:", self.last_msg_numbers
        if self.last_msg_numbers > 0 and sub / (MIN * self.last_msg_numbers)> 1:
            self.last_msg_numbers = 0

        # 如果还有消息未发送, 则加上他们的间隔
        if self.last_msg_numbers > 0:
            delay += MIN * self.last_msg_numbers
            logging.info(u"有%s条消息未发送, 延迟为 %s", self.last_msg_numbers, delay)


        n = 1
        # 如果这条消息和上条消息一致, 保险起见再加上一个最小间隔
        if self.last_msg_content == content and sub < MIN:
            delay += MIN
            self.last_msg_numbers += 1
            n = 2

        self.last_msg_numbers += 1
        self.last_msg_content = content

        if delay:
            logging.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递"
                         .format(delay, self.last_msg_numbers))
        # 返回消息累加个数, 在消息发送后减去相应的数目
        return delay, n


    def send_group_msg_back(self, content, group_uin, n, resp):
        logging.info(u"发送群消息 {0} 到 {1} 成功".format(content, group_uin))
        self.last_msg_time = time.time()
        if self.last_msg_numbers > 0:
            self.last_msg_numbers -= n


    def set_signature(self, signature, password, callback):
        """ 设置QQ签名,
        可以通过发送好友消息设置签名, 消息应按照如下格式:
            设置签名:[密码]|[签名内容]    // 密码和签名内容不能包含分割符
        url: http://s.web2.qq.com/api/set_long_nick2
        method: POST
        params:
                r : {
                    nlk         // 签名内容
                    vfwebqq     // 登录时获取的cookie值
                }
        headers:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """
        if password != Set_Password:
            return callback(u"你没有权限这么做")

        logging.info(u"设置QQ签名 {0}".format(signature))

        url = "http://s.web2.qq.com/api/set_long_nick2"
        params = (("r", json.dumps({"nlk":signature, "vfwebqq":self.vfwebqq})),)
        headers = {"Origin":"http://s.web2.qq.com"}
        headers.update(self.base_header)

        def callback(resp):
            data = resp.body
            print data
            result = json.loads(data).get("retcode")
            if result == 0:
                callback(u"设置成功")
            else:
                callback(u"设置失败")
        self.http.post(url, params, headers = headers, callback = callback)


    def run(self):
        self.get_login_sig()
        self.http.start()

    def real_stop(self):
        self.http.stop()

    def stop(self):
        self.stop_poll = True

    def send_msg_with_markname(self, markname, message, callback):
        """ 使用备注发送消息
        """
        uin = self.mark_to_uin.get(markname)
        if not uin:
            return False

        self.send_buddy_msg(uin, message, callback)
        return True


    def accept_and_set_mark(self, uin, qq_num):
        """ 确认添加并更改备注
        """
        url = "http://s.web2.qq.com/api/allow_and_add2"
        params = [("r","{\"account\":%d, \"gid\":0, \"mname\":\"%d\","
                    " \"vfwebqq\":\"%s\"}" % (qq_num, qq_num, self.vfwebqq)),]
        headers = {"Origin":"http://s.web2.qq.com"}
        headers.update(self.base_header)

        def _callback(resp):
            data = json.loads(resp.body)
            logging.info(data)
            logging.info(params)
            if data.get("retcode") == 0:
                logging.info(u"添加 {0} 成功".format(qq_num))
                self.mark_to_uin[uin] = qq_num
            else:
                logging.info(u"添加 {0} 失败".format(qq_num))

        self.http.post(url, params, headers = headers, callback = _callback)
Пример #15
0
#        print 'is match'
        if type == "g" or type=="s" or type=="b":
            print 'search'
            if self.searcher.find(content):
                result=self.searcher.search(content)
                if result>=0 :
                    print 'result count:',result
                    
                    return True
        else:
            self.content = content
            return True
        return False
        pass
if __name__ == "__main__":
    c=TornadoHTTPClient()
    #c.start()
    robot=SmartRobotPlugin(None,None,None,None)
    #while True:
  
    if robot.is_match(111, 'ss?', 'g')==True:
        data=robot.get_result()
        print "data:",data,type(data)
    else:
        print 'no found'
    c=TornadoHTTPClient()
    s=Searcher(c)
    c.start()
    #s.baidu_search('ss')
    c.stop()
#     
Пример #16
0
 def setUp(self):
     self.http = TornadoHTTPClient()
     self.http.debug = True
Пример #17
0
 def setUp(self):
     super(TornadoHTTPClientCoroutineTest, self).setUp()
     self.http = TornadoHTTPClient()
     self.http.debug = True
Пример #18
0
class WebQQ(object):
    def __init__(self, qid, pwd, handler=None):
        self.qid = qid  # QQ 号
        self.__pwd = pwd  # QQ密码
        self.nickname = None  # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36"
        )
        self.http.debug = TRACE
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)  # 消息id, 随机初始化

        self.require_check = False  # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.require_check_time = None  # 需要验证码的时间

        self.checkimg_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "check.jpg")

        self.friend_info = {}  # 初始化好友列表
        self.group_info = {}  # 初始化组列表
        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {}  # 初始化组成员列表
        self.mark_to_uin = {}  # 备注名->uin的映射

        self.daid = 164
        self.login_sig = None

        self.login_time = None  # 登录的时间
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量

        self.status_callback = None

        self.poll_stoped = True  # 获取消息是否停止
        self.hThread = None  # 心跳线程
        # self.base_header = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"}
        self.base_header = {"Referer": "http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=3"}

    def ptuiCB(self, scode, r, url, status, msg, nickname=None):
        """ 模拟JS登录之前的回调, 保存昵称 """
        if int(scode) == 0:
            logging.info("从Cookie中获取ptwebqq的值")
            old_value = self.ptwebqq
            try:
                self.ptwebqq = self.http.cookie[".qq.com"]["/"]["ptwebqq"].value
            except:
                logging.error("从Cookie中获取ptwebqq的值失败, 使用旧值尝试")
                self.ptwebqq = old_value
            self.logined = True
        elif int(scode) == 4:
            logging.error(msg)
            if self.status_callback:
                self.status_callback(False, msg)
            self.check()
        else:
            logging.error(u"server response: {0}".format(msg.decode("utf-8")))
            self.check()

        if nickname:
            self.nickname = nickname.decode("utf-8")

    def get_group_member_nick(self, gcode, uin):
        return self.group_members_info.get(gcode, {}).get(uin, {}).get("nick")

    def get_uptime(self):
        MIN = 60
        HOUR = 60 * MIN
        DAY = 24 * HOUR

        if not isinstance(self.login_time, (int, float)):
            self.login_time = time.time()

        up_time = datetime.fromtimestamp(self.login_time).strftime("%H:%M:%S")

        now = time.time()
        sub = now - self.login_time

        days = int(sub / DAY)
        hours = int(sub / HOUR)
        mins = int(sub / MIN)

        if mins:
            num = mins
            unit = "min"

        if hours:
            num = hours
            unit = "hours" if hours > 1 else "hour"

        if days:
            num = days
            unit = "days" if days > 1 else "day"

        if not days and not mins and not hours:
            num = int(sub)
            unit = "sec"

        return "{0} up {1} {2}".format(up_time, num, unit)

    def get_login_sig(self, handler=None):

        self.handler = handler  # 启用HTTP_CHECKIMG的Handler
        self.stop_poll_heartbeat = False

        with open("wait", "w"):
            pass

        logging.info("获取 login_sig...")
        url = "https://ui.ptlogin2.qq.com/cgi-bin/login"
        params = [
            ("daid", self.daid),
            ("target", "self"),
            ("style", 5),
            ("mibao_css", "m_webqq"),
            ("appid", self.aid),
            ("enable_qlogin", 0),
            ("no_verifyimg", 1),
            ("s_url", "http://web2.qq.com/loginproxy.html"),
            ("f_url", "loginerroralert"),
            ("strong_login", 1),
            ("login_state", 10),
            ("t", "20130723001"),
        ]
        self.http.get(url, params, callback=self._get_login_sig)
        self.http.get("http://web2.qq.com")

    def _get_login_sig(self, resp):
        sigs = SIG_RE.findall(resp.body)
        if len(sigs) == 1:
            self.login_sig = sigs[0]
            logging.info(u"获取Login Sig: {0}".format(self.login_sig))
        else:
            logging.warn(u"没有获取到 Login Sig, 后续操作可能失败")
            self.login_sig = ""

        self.check()

    def clean(self):
        if os.path.exists("lock"):
            os.remove("lock")

        if os.path.exists("wait"):
            os.remove("wait")

    def check(self):
        """ 检查是否需要验证码
        url :
            https://ssl.ptlogin2.qq.com/check
        方法:   GET
        参数:
            {
                uin     // qq号
                appid   // 程序id 固定为1003903
                r       // 随机数
                u1      // http://web2.qq.com/loginproxy.html
                js_ver  // 10040
                js_type // 0
            }
        返回:
            ptui_checkVC('0','!PTH','\x00\x00\x00\x00\x64\x74\x8b\x05');
            第一个参数表示状态码, 0 不需要验证, 第二个为验证码, 第三个为uin
        """
        self.clean()
        self.poll_stoped = True  # 检查时停止轮询消息
        logging.info(u"检查是否需要验证码...")
        # url = "https://ssl.ptlogin2.qq.com/check"
        url = "http://check.ptlogin2.qq.com/check"
        params = {
            "uin": self.qid,
            "appid": self.aid,
            "u1": "http://web2.qq.com/loginproxy.html",
            "login_sig": self.login_sig,
            "js_ver": 10040,
            "js_type": 0,
            "r": random.random(),
        }
        headers = {
            "Referer": "https://ui.ptlogin2.qq.com/cgi-bin/login?daid="
            "164&target=self&style=5&mibao_css=m_webqq&appid=1003903&"
            "enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb2.q"
            "q.com%2Floginproxy.html&f_url=loginerroralert&strong_log"
            "in=1&login_state=10&t=20130723001"
        }
        self.http.get(url, params, headers=headers, callback=self.handle_verify)

    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd

    def get_check_img(self, r, vcode, uin):
        """ 获取验证图片 """

        def callback(resp):
            path = self.checkimg_path
            fp = open(path, "wb")
            fp.write(resp.body)
            fp.close()
            if UPLOAD_CHECKIMG:
                logging.info(u"正在上传验证码..")
                res = upload_file("check.jpg", path)
                path = res.read()
            check_code = ""
            if not HTTP_CHECKIMG:
                print u"验证图片: {0}".format(path)
                while not check_code:
                    check_code = raw_input("输入验证图片上的验证码: ")
                ccode = check_code.strip().lower()
                self.check_code = ccode
                pwd = self.handle_pwd(r, ccode.upper(), uin)
                self.before_login(pwd)
            else:
                if os.path.exists("wait"):
                    os.remove("wait")
                logging.info(u"请打开http://{0}:{1} 提交验证码".format(HTTP_LISTEN, HTTP_PORT))
                self.handler.r = r
                self.handler.uin = uin
                self.handler.next_callback = self.before_login
                self.require_check_time = time.time()
                if EMAIL_NOTICE:
                    if send_notice_email():
                        logging.info(u"已成功发送邮件提醒")
                    else:
                        logging.warn(u"发送邮件提醒失败")

        url = "https://ssl.captcha.qq.com/getimage"
        params = [("aid", self.aid), ("r", random.random()), ("uin", self.qid)]
        self.http.get(url, params, callback=callback)

    def handle_verify(self, resp):
        ptui_checkVC = lambda r, v, u: (r, v, u)
        data = resp.body
        if not data:
            self.check()  # 没有数据重新检查
            return

        r, vcode, uin = eval(data.strip().rstrip(";"))
        logging.info("R:{0} vcode:{1} uin:{2}".format(r, vcode, uin))
        if int(r) == 0:
            logging.info("验证码检查完毕, 不需要验证码")
            password = self.handle_pwd(r, vcode, uin)
            self.check_code = vcode
            self.clean()
            self.before_login(password)
        else:
            logging.warn("验证码检查完毕, 需要验证码")
            self.get_check_img(r, vcode, uin)
            self.require_check = True

    def before_login(self, password, callback=None):
        """ 登录之前的操作
        url:
            https://ssl.ptlogin2.qq.com/login
        方法:   GET
        参数:
            {
                u       // qq号码
                p       // 经过处理的密码
                verifycode  // 验证码
                webqq_type  // 固定为10
                remember_uin    // 是否记住qq号, 传1 即可
                login2qq        // 登录qq, 传1
                aid             // appid 固定为 1003903
                u1              // 固定为 http://www.qq.com
                h               // 固定为1
                ptrediect       // 固定为0
                ptlang          // 固定为2052
                from_ui         // 固定为 1
                pttype          // 固定为1
                dumy            // 固定为空
                fp              // 固定为loginerroralert ( 重要)
                mibao_css       // 固定为 m_webqq
                t               // 固定为1
                g               // 固定为
                js_type         // 固定为0
                js_ver          // 固定为10021
        其他:
            如果check步骤验证了需要验证码, 需加上 Referer头 值为:
            https://ui.ptlogin2.qq.com/cgi-bin/login?target=self&style=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20130221001

        接口返回:
            ptuiCB('0','0','http://www.qq.com','0','登录成功!', 'nickname');
        先检查是否需要验证码,不需要验证码则首先执行一次登录
        然后获取Cookie里的ptwebqq保存在实例里,供后面的接口调用
        """
        with open("lock", "w"):
            pass

        if callback:
            self.status_callback = callback
        url = "https://ssl.ptlogin2.qq.com/login"
        params = [
            ("u", self.qid),
            ("p", password),
            ("verifycode", self.check_code),
            ("webqq_type", 10),
            ("remember_uin", 1),
            ("login2qq", 1),
            ("aid", self.aid),
            ("u1", "http://www.qq.com/loginproxy.h" "tml?login2qq=1&webqq_type=10"),
            ("h", 1),
            ("action", 4 - 5 - 8246),
            ("ptredirect", 0),
            ("ptlang", 2052),
            ("from_ui", 1),
            ("daid", self.daid),
            ("pttype", 1),
            ("dumy", ""),
            ("fp", "loginerroralert"),
            ("mibao_css", "m_webqq"),
            ("t", 1),
            ("g", 1),
            ("js_type", 0),
            ("js_ver", 10040),
            ("login_sig", self.login_sig),
        ]
        headers = {}
        if self.require_check:
            headers.update(
                Referer="https://ui.ptlogin2.qq.com/cgi-"
                "bin/login?target=self&style=5&mibao_css=m_"
                "webqq&appid=1003903&enable_qlogin=0&no_ver"
                "ifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Fl"
                "oginproxy.html&f_url=loginerroralert&stron"
                "g_login=1&login_state=10&t=20130221001"
            )
        else:
            headers.update(
                Referfer="https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&style=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb2.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20130903001"
            )
        logging.info("检查完毕, 开始登录前准备")
        self.http.get(url, params, headers=headers, callback=self.login0)

    def login0(self, resp):
        logging.info("开始登录前准备...")
        blogin_data = resp.body.decode("utf-8").strip().rstrip(";")
        eval("self." + blogin_data)

        try:
            location1 = re.findall(r"ptuiCB\(\'0\'\,\'0\'\,\'(.*)\'\,\'0\'\,", blogin_data)[0]
            params = []
            header = {
                "Referer": "https://ui.ptlogin2.qq.com/cgi-bin/login?d"
                "aid=164&target=self&style=5&mibao_css=m_webqq&appid=1"
                "003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2"
                "F%2Fweb2.qq.com%2Floginproxy.html&f_url=loginerrorale"
                "rt&strong_login=1&login_state=10&t=20130723001"
            }
            self.http.get(location1, params, headers=header, callback=self.get_location1)
        except:
            pass

    def get_location1(self, resp):
        if os.path.exists(self.checkimg_path):
            os.remove(self.checkimg_path)

        if os.path.exists("lock"):
            os.remove("lock")

        logging.info("准备完毕, 开始登录")
        self.login()

    def login(self):
        """ 获取登录前的数据, 并进行登录
        url:
            http://d.web2.qq.com/channel/login2
        方法: POST
        参数:
            {
                r : {
                    status      // 登录后的状态 ("online")
                    ptwebqq     // 上次请求返回的cookie
                    passwd_sig  // 固定为空
                    clientid    // 随机的clientid
                    psessionid  // 传递 null
                }
                clientid    // 客户端id
                psessionid  // 传递null
            }
        其他:
            需加上 Referer和 Origin 头:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
            "Origin": "http://d.web2.qq.com"

        返回:
            {u'retcode': 0,
            u'result': {
                'status': 'online', 'index': 1075,
                'psessionid': '', u'user_state': 0, u'f': 0,
                u'uin': 1685359365, u'cip': 3673277226,
                u'vfwebqq': u'', u'port': 43332}}
            保存result中的psessionid和vfwebqq供后面接口调用
        """
        # time.sleep(4)

        url = "http://d.web2.qq.com/channel/login2"
        params = [
            (
                "r",
                '{"status":"online","ptwebqq":"%s","passwd_sig":"",'
                '"clientid":"%d","psessionid":null}' % (self.ptwebqq, self.clientid),
            ),
            ("clientid", self.clientid),
            ("psessionid", "null"),
        ]

        headers = {"Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        logging.info("登录准备完毕, 开始登录")
        self.http.post(url, params, headers=headers, callback=self.login_back)

    def login_back(self, resp):
        self.require_check_time = None
        if not resp.body:
            logging.error(u"没有获取到数据, 登录失败")
            if self.status_callback:
                self.status_callback(False, "登录失败 没有数据返回")
            return self.check()

        data = json.loads(resp.body)

        if data.get("retcode") != 0:
            if self.status_callback:
                self.status_callback(False, "登录失败 {0}".format(data))

            logging.error("登录失败 {0!r}".format(data))
            return
        self.vfwebqq = data.get("result", {}).get("vfwebqq")
        self.psessionid = data.get("result", {}).get("psessionid")
        logging.info("登录成功")

        if not DEBUG and not HTTP_CHECKIMG:
            aw = ""
            while aw.lower() not in ["y", "yes", "n", "no"]:
                aw = raw_input("是否将程序至于后台[y] ")
                if not aw:
                    aw = "y"

            if aw in ["y", "yes"]:
                run_daemon(self.update_friend)
                return

        self.update_friend()

    def _hash(self):
        """  获取列表时的Hash """
        return _hash.webqq_hash(self.qid, self.ptwebqq)
        # l = len(e)
        # # 将qq号码转换成整形列表
        # b, k, d = 0, -1, 0
        # for d in a:
        #     d = int(d)
        #     b += d
        #     b %= l
        #     f = 0
        #     if b + 4 > l:
        #         g = 4 + b - l
        #         for h in range(4):
        #             f |= h < g and (
        #                 ord(e[b + h]) & 255) << (3 - h) * 8 or (
        #                     ord(e[h - g]) & 255) << (3 - h) * 8
        #     else:
        #         for h in range(4):
        #             f |= (ord(e[b + h]) & 255) << (3 - h) * 8
        #     k ^= f
        # c = [k >> 24 & 255, k >> 16 & 255, k >> 8 & 255, k & 255]
        # import string
        # k = list(string.digits) + ['A', 'B', 'C', 'D', 'E', 'F']
        # d = [k[b >> 4 & 15] + k[b & 15] for b in c]
        # return ''.join(d)

    def update_friend(self, resp=None, call_status=True):
        """ 更新好友列表
        URL:
            http://s.web2.qq.com/api/get_user_friends2
        METHOD:
            POST
        PARAMS:
            {r:{"h":"hello", "vfwebqq":""}}
        HEADER:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """

        url = "http://s.web2.qq.com/api/get_user_friends2"
        params = [("r", json.dumps({"h": "hello", "hash": self._hash(), "vfwebqq": self.vfwebqq}))]
        headers = {"Referer": "http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1"}

        callback = self.update_friend

        if resp is None:
            self.poll_stoped = False  # 可以开始轮询消息
            self.http.post(url, params, headers=headers, callback=callback)
        else:
            if not resp.body:
                if self.status_callback and call_status:
                    self.status_callback(False, u"更新好友信息失败")
                return
            data = json.loads(resp.body)
            if data.get("retcode") != 0 and call_status:
                self.status_callback(False, u"好友列表加载失败, 错误代码:{0}".format(data.get("retcode")))
                return

            lst = data.get("result", {}).get("info", [])
            for info in lst:
                uin = info.get("uin")
                self.friend_info[uin] = info

            marknames = data.get("result", {}).get("marknames", [])
            [self.mark_to_uin.update({minfo.get("markname"): minfo.get("uin")}) for minfo in marknames]

            logging.debug("加载好友信息 {0!r}".format(self.friend_info))
            logging.info(data)
            if self.status_callback and call_status:
                self.status_callback(True)
            self.update_group()

            self.http.post(
                url, params, headers=self.base_header, delay=3600, callback=callback, kwargs={"call_status": False}
            )

    def update_group(self, resp=None):
        """ 获取组列表, 并获取组成员
        获取组列表:
            url:
                http://s.web2.qq.com/api/get_group_name_list_mask2
            method:
                POST
            params:
                {
                    r : {
                        vfwebqq     // 登录前返回的cookie值
                    }
                }

        """
        logging.info("获取群列表")
        url = "http://s.web2.qq.com/api/get_group_name_list_mask2"
        params = [("r", '{"vfwebqq":"%s"}' % self.vfwebqq)]
        headers = {"Origin": "http://s.web2.qq.com"}
        headers.update(self.base_header)
        self.http.post(url, params, headers=headers, callback=self.group_members)

    def group_members(self, resp):
        """ 获取群列表, 获取群列表中的成员
        url: http://s.web2.qq.com/api/get_group_info_ext2
        method: GET
        params:
            {
                gcode           // 群代码
                vfwebqq         // 登录前的cookie值
                t               // int(time.time())
            }
        headers:
            "Referer":
            "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
        """
        logging.info("加载组成员信息")
        data = json.loads(resp.body)
        logging.debug(u"群信息 {0!r}".format(data))
        group_list = data.get("result", {}).get("gnamelist", [])
        logging.debug(u"群列表: {0!r}".format(group_list))
        if not group_list and not self.poll_and_heart:
            self.heartbeat(0)
            self.poll()

        for i, group in enumerate(group_list):
            gcode = group.get("code")
            url = "http://s.web2.qq.com/api/get_group_info_ext2"
            params = [("gcode", gcode), ("vfwebqq", self.vfwebqq), ("cb", "undefined"), ("t", int(time.time()))]

            kwargs = dict(gcode=gcode)

            if i == 0:
                kwargs.update(poll=True)

            self.http.get(
                url, params, headers=self.base_header, callback=self.do_group_members, kwargs=kwargs, delay=i * 300
            )

            self.group_info[gcode] = group

    def do_group_members(self, resp, gcode, poll=False):
        """ 获取群成员数据 """
        data = json.loads(resp.body)
        logging.debug(u"获取群成员信息 {0!r}".format(data))
        members = data.get("result", {}).get("minfo", [])
        self.group_members_info[gcode] = {}
        for m in members:
            uin = m.get("uin")
            self.group_members_info[gcode][uin] = m

        cards = data.get("result", {}).get("cards", [])

        for card in cards:
            uin = card.get("muin")
            group_name = card.get("card")
            self.group_members_info[gcode][uin]["nick"] = group_name

        logging.debug(u"群成员信息: {0!r}".format(self.group_members_info))

        if poll and not self.poll_and_heart:
            logging.info("开始拉取信息和心跳")
            self.login_time = time.time()
            self.poll()
            self.heartbeat(0)

    def poll(self):
        """ 建立长连接获取消息
        url:http://d.web2.qq.com/channel/poll2
        方法: POST
        参数:
            {
                r:{
                    clientid       // 客户端id
                    psessionid     // session id
                    key             // 固定为0
                    ids             // 固定为 []
                }
                clientid
                psessionid
            }

        头部:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=2"
        """
        if not self.poll_and_heart:
            self.poll_and_heart = True
        url = "http://d.web2.qq.com/channel/poll2"
        rdic = {"clientid": self.clientid, "psessionid": self.psessionid, "key": 0, "ids": []}
        params = [("r", json.dumps(rdic)), ("clientid", self.clientid), ("psessionid", self.psessionid)]
        headers = {
            "Referer": "https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1",
            "Origin": "http://d.web2.qq.com",
        }

        self.http.post(
            url, params, headers=headers, request_timeout=60.0, connect_timeout=60.0, callback=self.handle_msg
        )

    def handle_msg(self, resp):
        """ 处理消息 """
        if self.poll_stoped:
            return
        self.poll()
        if not resp.body:
            return

        data = resp.body
        try:
            msg = json.loads(data)
            if msg.get("retcode") in [121, 100006]:
                logging.error(u"获取消息异常 {0!r}".format(data))
                exit()
            logging.info(u"获取消息: {0!r}".format(msg))
            self.msg_dispatch.dispatch(msg)
        except ValueError:
            if DEBUG:
                traceback.print_exc()
            logging.error(u"消息加载失败: %s", data)

    def heartbeat(self, delay=60):
        """ 开始心跳
        url:http://web.qq.com/web2/get_msg_tip
        方法: GET
        参数:
            {
                uin  // 固定为空
                tp   // 固定为1
                rc   // 固定为1
                id   // 固定位0
                lv   // 固定为2
                t    // 开始的心跳时间(int(time.time()) * 1000)
            }
        """

        if not self.poll_and_heart:
            self.poll_and_heart = True

        self.hThread = threading.Thread(name="heartThead#1", target=self._heartbeat)
        self.hThread.setDaemon(True)
        self.hThread.start()

    def _heartbeat(self):
        i = self.rc
        url = "http://web.qq.com/web2/get_msg_tip"
        params = dict(
            [("uin", ""), ("tp", 1), ("id", 0), ("retype", 1), ("rc", i), ("lv", 3), ("t", int(time.time() * 1000))]
        )

        def callback(resp):
            logging.info("心跳..")

        while True:
            try:
                self.http.get(url, params, callback=callback, connect_timeout=1.0, request_timeout=1.0)
            except:
                pass
            i += 1
            params["rc"] = i
            time.sleep(60)

    def make_msg_content(self, content):
        """ 构造QQ消息的内容 """
        self.msg_id += 1
        return json.dumps([content, ["font", {"name": "Monospace", "size": 10, "style": [0, 0, 0], "color": "000000"}]])

    def get_sess_group_sig(self, to_uin, callback):
        """ 获取临时消息组签名
        URL: http://d.web2.qq.com/channel/get_c2cmsg_sig2
        METHOD: GET
        PARAMS:
            id   // 请求ID 固定为833193360
            to_uin   // 消息接受人uin( 消息的from_uin)
            service_type   // 固定为0
            clientid       // 客户端id
            psessionid     // session id
            t              // 当前时间秒1370671760656
        HEADERS:
        Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        url = "http://d.web2.qq.com/channel/get_c2cmsg_sig2"
        params = (
            ("id", 833193360),
            ("to_uin", to_uin),
            ("service_type", 0),
            ("clientid", self.clientid),
            ("psessionid", self.psessionid),
            ("t", time.time()),
        )

        def callback(resp):
            data = resp.body
            r = json.loads(data)
            result = r.get("result")
            group_sig = result.get("value")
            if r.get("retcode") != 0:
                logging.warn(u"加载临时消息签名失败: {0}".format(group_sig))
                return
            try:
                logging.info("加载临时消息签名 {0} for {1}".format(group_sig, to_uin))
            except UnicodeError:
                return
            self.group_sig[to_uin] = group_sig
            callback()

        self.http.get(url, params, callback=callback, headers=self.base_header)

    def send_sess_msg(self, to_uin, content):
        """ 发送临时消息
        URL:http://d.web2.qq.com/channel/send_sess_msg2
        METHOD: POST
        PARAMS:
            r:{
                to              // 消息接收人 uin
                group_sig       // 组签名
                face            // 固定为 564,
                content         // 发送内容
                msg_id          // 消息id
                service_type    // 固定为0,
                clientid        // 客户端id
                psessionid      // sessionid
                }
            clientid                // 客户端id
            psessionid              // sessionid
        Headers:
            self.base_header
        """
        group_sig = self.group_sig.get(to_uin)
        if not group_sig:
            callback = partial(self.send_sess_msg, to_uin, content)
            return self.get_sess_group_sig(to_uin, callback)

        logging.info(u"发送临时消息 {0} 到 {1}".format(content, to_uin))
        delay, n = self.get_delay(content)
        content = self.make_msg_content(content)
        url = "http://d.web2.qq.com/channel/send_sess_msg2"
        params = (
            (
                "r",
                json.dumps(
                    {
                        "to": to_uin,
                        "group_sig": group_sig,
                        "face": 564,
                        "content": content,
                        "msg_id": self.msg_id,
                        "service_type": 0,
                        "clientid": self.clientid,
                        "psessionid": self.psessionid,
                    }
                ),
            ),
            ("clientid", self.clientid),
            ("psessionid", self.psessionid),
        )

        def callback(resp):
            self.last_msg_numbers -= n
            self.last_msg_time = time.time()

        self.http.post(url, params, headers=self.base_header, callback=callback, delay=delay)

    def send_buddy_msg(self, to_uin, content, callback=None):
        """ 发送好友消息
        URL:
            http://d.web2.qq.com/channel/send_buddy_msg2

        METHOD:
            POST

        PARAMS:
            {
                "r":{
                    "to"            // 好友uin
                    "face"          // 固定为564
                    "content"       // 发送内容
                    "msg_id"        // 消息id, 每发一条递增
                    "clientid"      // 客户端id
                    "psessionid"    // sessionid
                    }
                "clientid":clientid,
                "psessionid": psessionid,
            }

        HEADERS:
            Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        content = self.make_msg_content(content)

        url = "http://d.web2.qq.com/channel/send_buddy_msg2"

        r = {
            "to": to_uin,
            "face": 564,
            "content": content,
            "clientid": self.clientid,
            "msg_id": self.msg_id,
            "psessionid": self.psessionid,
        }
        params = [("r", json.dumps(r)), ("clientid", self.clientid), ("psessionid", self.psessionid)]
        headers = {"Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        delay, n = self.get_delay(content)

        def _callback(resp):
            logging.info(u"发送好友消息 {0} 给 {1} 成功".format(content, to_uin))
            if callback:
                callback(True)
            self.last_msg_numbers -= n
            self.last_msg_time = time.time()

        logging.info(u"发送好友消息 {0} 给 {1} ...".format(content, to_uin))
        self.http.post(url, params, headers=headers, delay=delay, callback=_callback)

    def send_group_msg(self, group_uin, content):
        """ 发送群消息
        url:http://d.web2.qq.com/channel/send_qun_msg2
        方法: POST
        参数:
            {
                r:{
                    group_uin           // gid
                    content             // 发送内容
                    msg_id              // 消息id, 每次发送消息应该递增
                    clientid            // 客户端id
                    psessionid          // sessionid
                }
                clientid
                psessionid
            }
        """
        gid = self.group_info.get(group_uin, {}).get("gid")
        source = content
        content = self.make_msg_content(source)

        url = "http://d.web2.qq.com/channel/send_qun_msg2"
        r = {
            "group_uin": gid,
            "content": content,
            "msg_id": self.msg_id,
            "clientid": self.clientid,
            "psessionid": self.psessionid,
        }
        params = [("r", json.dumps(r)), ("psessionid", self.psessionid), ("clientid", self.clientid)]
        print params

        delay, n = self.get_delay(content)
        callback = self.send_group_msg_back

        logging.info(u"发送群消息 {0} 到 {1}...".format(content, group_uin))
        headers = {
            "Origin": "http://d.web2.qq.com",
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=2",
        }
        self.http.post(url, params, headers=headers, callback=callback, args=(source, group_uin, n), delay=delay)

    def get_delay(self, content):
        MIN = MESSAGE_INTERVAL
        delay = 0
        sub = time.time() - self.last_msg_time
        if self.last_msg_numbers < 0:
            self.last_msg_numbers = 0

        # 不足最小间隔就补足最小间隔
        if sub < MIN:
            delay = MIN
            logging.debug(u"间隔 %s 小于 %s, 设置延迟为%s", sub, MIN, delay)

        # 如果间隔是已有消息间隔的2倍, 则清除已有消息数
        # print "sub", sub, "n:", self.last_msg_numbers
        if self.last_msg_numbers > 0 and sub / (MIN * self.last_msg_numbers) > 1:
            self.last_msg_numbers = 0

        # 如果还有消息未发送, 则加上他们的间隔
        if self.last_msg_numbers > 0:
            delay += MIN * self.last_msg_numbers
            logging.info(u"有%s条消息未发送, 延迟为 %s", self.last_msg_numbers, delay)

        n = 1
        # 如果这条消息和上条消息一致, 保险起见再加上一个最小间隔
        if self.last_msg_content == content and sub < MIN:
            delay += MIN
            self.last_msg_numbers += 1
            n = 2

        self.last_msg_numbers += 1
        self.last_msg_content = content

        if delay:
            logging.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递".format(delay, self.last_msg_numbers))
        # 返回消息累加个数, 在消息发送后减去相应的数目
        return delay, n

    def send_group_msg_back(self, content, group_uin, n, resp):
        logging.info(u"发送群消息 {0} 到 {1} 成功".format(content, group_uin))
        self.last_msg_time = time.time()
        if self.last_msg_numbers > 0:
            self.last_msg_numbers -= n

    def set_signature(self, signature, password, callback):
        """ 设置QQ签名,
        可以通过发送好友消息设置签名, 消息应按照如下格式:
            设置签名:[密码]|[签名内容]    // 密码和签名内容不能包含分割符
        url: http://s.web2.qq.com/api/set_long_nick2
        method: POST
        params:
                r : {
                    nlk         // 签名内容
                    vfwebqq     // 登录时获取的cookie值
                }
        headers:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """
        if password != Set_Password:
            return callback(u"你没有权限这么做")

        logging.info(u"设置QQ签名 {0}".format(signature))

        url = "http://s.web2.qq.com/api/set_long_nick2"
        params = (("r", json.dumps({"nlk": signature, "vfwebqq": self.vfwebqq})),)
        headers = {"Origin": "http://s.web2.qq.com"}
        headers.update(self.base_header)

        def callback(resp):
            data = resp.body
            print data
            result = json.loads(data).get("retcode")
            if result == 0:
                callback(u"设置成功")
            else:
                callback(u"设置失败")

        self.http.post(url, params, headers=headers, callback=callback)

    def run(self):
        self.get_login_sig()
        self.http.start()

    def real_stop(self):
        self.http.stop()

    def stop(self):
        self.stop_poll = True

    def send_msg_with_markname(self, markname, message, callback):
        """ 使用备注发送消息
        """
        uin = self.mark_to_uin.get(markname)
        if not uin:
            return False

        self.send_buddy_msg(uin, message, callback)
        return True

    def accept_and_set_mark(self, uin, qq_num):
        """ 确认添加并更改备注
        """
        url = "http://s.web2.qq.com/api/allow_and_add2"
        params = [("r", '{"account":%d, "gid":0, "mname":"%d",' ' "vfwebqq":"%s"}' % (qq_num, qq_num, self.vfwebqq))]
        headers = {"Origin": "http://s.web2.qq.com"}
        headers.update(self.base_header)

        def _callback(resp):
            data = json.loads(resp.body)
            logging.info(data)
            logging.info(params)
            if data.get("retcode") == 0:
                logging.info(u"添加 {0} 成功".format(qq_num))
                self.mark_to_uin[uin] = qq_num
            else:
                logging.info(u"添加 {0} 失败".format(qq_num))

        self.http.post(url, params, headers=headers, callback=_callback)
Пример #19
0
class TornadoHTTPClientCoroutineTest(testing.AsyncTestCase):
    def setUp(self):
        super(TornadoHTTPClientCoroutineTest, self).setUp()
        self.http = TornadoHTTPClient()
        self.http.debug = True

    def _callback(self, response):
        print(response.code)
        print("当前链接地址: ")
        print(response.effective_url)
        self.http.stop()

    @testing.gen_test
    def test_get(self):
        resp = yield self.http.get("http://www.linuxzen.com")
        self._callback(resp)

    @testing.gen_test
    def test_get_args(self):
        yield self.http.get("http://www.baidu.com/s", (("wd", "tornado"), ),
                            callback=self._callback)

    @testing.gen_test
    def test_post(self):
        params = [("vimcn", u"# 这是TornadoHTTPClient单元测试提交的".encode("utf-8"))]
        url = "http://p.vim-cn.com"

        def callback(response):
            print("打开此链接:", end=" ")
            print(response.effective_url)

        resp = yield self.http.post(url, params)
        callback(resp)

    @testing.gen_test
    def test_head(self):
        resp = yield self.http.head("http://linuxzen.com")
        self.assertEqual(resp.code, 200)

    @testing.gen_test
    def test_user_agent(self):
        user_agent =  "Mozilla/5.0 (X11; Linux x86_64)"\
                " AppleWebKit/537.11 (KHTML, like Gecko)"\
                " Chrome/23.0.1271.97 Safari/537.11"
        self.http.set_user_agent(user_agent)

        resp = yield self.http.get("http://www.linuxzen.com")
        self.assertEqual(resp.request.user_agent, user_agent)

    @testing.gen_test
    def test_header(self):
        headers = {"Origin": "http://www.linuxzen.com"}
        resp = yield self.http.get("http://www.linuxzen.com", headers=headers)
        self.assertEqual(resp.request.headers["Origin"], headers["Origin"])

    @testing.gen_test
    def test_cookie(self):
        yield self.http.get("http://www.baidu.com")
        print(self.http.cookie)

    @testing.gen_test
    def test_cookie_jar(self):
        yield self.http.get("http://www.baidu.com")
        print("cookie jar>>>>>>>>>>>>>>>>>>", end=" ")
        print(self.http.cookiejar)

    @testing.gen_test
    def test_upload_img(self):
        def callback(response):
            self.http.stop()

        resp = yield self.http.upload("http://dimg.vim-cn.com", "name",
                                      "img_test.png")
        print("打开图片链接", end=" ")
        print(resp.effective_url)
Пример #20
0
class RequestHub(object):

    """ 集成Request请求和保存请求值
    :param qid: qq号
    :param pwd: 密码
    :param client: ~twqq.client.Client instance
    """
    SIG_RE = re.compile(r'var g_login_sig=encodeURIComponent\("(.*?)"\);')

    def __init__(self, qid, pwd, client=None, debug=False):
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36")
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.http.debug = debug

        self.qid = qid
        self.__pwd = pwd
        self.client = client

        self.rc = random.randrange(0, 100)
        self.aid = 1003903                                    # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)     # 消息id, 随机初始化
        self.daid = 164
        self.login_sig = None
        self.ptwebqq = None
        self.nickname = u"YouWillNeverGetIt"
        self.vfwebqq = None
        self.psessionid = None
        self.stop_poll = False

        # 检查是否验证码的回调
        self.ptui_checkVC = lambda r, v, u: (r, v, u)

        # 是否需要验证码
        self.require_check = None
        self.require_check_time = None

        # 是否开始心跳和拉取消息
        self.poll_and_heart = None
        self.login_time = None
        self.hThread = None

        # 验证图片
        self.checkimg_path = tempfile.mktemp(".jpg")
        self._lock_path = tempfile.mktemp()
        self._wait_path = tempfile.mktemp()

        self.group_sig = {}          # 组签名映射, 用作发送临时消息(sess_message)

        self.message_interval = 0.5  # 消息间隔
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0    # 剩余位发送的消息数量
        WebQQRequest.hub = self
        self.connecting = False

    def connect(self):
        self.connecting = True
        self.load_next_request(FirstRequest())

    def load_next_request(self, request):
        """ 加载下一个请求

        :param request: ~twqq.requests.WebQQRequest instance
        :rtype: ~twqq.requests.WebQQRequest instance
        """
        func = self.http.get if request.method == WebQQRequest.METHOD_GET \
            else self.http.post

        if self.stop_poll and isinstance(request, PollMessageRequest):
            logger.info("检测Poll已停止, 此请求不处理: {0}".format(request))
            return

        kwargs = copy.deepcopy(request.kwargs)
        callback = request.callback if hasattr(request, "callback") and\
            callable(request.callback) else None
        kwargs.update(callback=self.wrap(request, callback))
        kwargs.update(headers=request.headers)
        kwargs.update(delay=request.delay)
        logger.debug("KWARGS: {0}".format(kwargs))

        if request.ready:
            logger.debug("处理请求: {0}".format(request))
            with ExceptionStackContext(request.handle_exc):
                func(request.url, request.params, **kwargs)
        else:
            logger.debug("请求未就绪: {0}".format(request))

        return request

    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd

    def upload_file(self, path):
        """ 上传文件

        :param path: 文件路径
        """
        img_host = "http://dimg.vim-cn.com/"
        curl, buff = self.generate_curl(img_host)
        curl.setopt(pycurl.POST, 1)
        curl.setopt(pycurl.HTTPPOST, [('name', (pycurl.FORM_FILE, path)), ])
        try:
            curl.perform()
            ret = buff.getvalue()
            curl.close()
            buff.close()
        except:
            logger.warn(u"上传图片错误", exc_info=True)
            return u"[图片获取失败]"
        return ret

    def generate_curl(self, url=None, headers=None):
        """ 生成一个curl, 返回 curl 实例和用于获取结果的 buffer
        """
        curl = pycurl.Curl()
        buff = StringIO()

        curl.setopt(pycurl.COOKIEFILE, "cookie")
        curl.setopt(pycurl.COOKIEJAR, "cookie_jar")
        curl.setopt(pycurl.SHARE, self.http._share)
        curl.setopt(pycurl.WRITEFUNCTION, buff.write)
        curl.setopt(pycurl.FOLLOWLOCATION, 1)
        curl.setopt(pycurl.MAXREDIRS, 5)
        curl.setopt(pycurl.TIMEOUT, 3)
        curl.setopt(pycurl.CONNECTTIMEOUT, 3)

        if url:
            curl.setopt(pycurl.URL, url)

        if headers:
            self.set_curl_headers(curl, headers)

        return curl, buff

    def set_curl_headers(self, curl, headers):
        """ 将一个字典设置为 curl 的头
        """
        h = []
        for key, val in headers.items():
            h.append("{0}: {1}".format(key, val))
        curl.setopt(pycurl.HTTPHEADER, h)

    def get_msg_img(self, from_uin, file_path):
        """ 获取聊天信息中的图片
        """
        url = "http://d.web2.qq.com/channel/get_offpic2"
        params = {"clientid": self.clientid, "f_uin": from_uin,
                  "file_path": file_path, "psessionid": self.psessionid}
        url = url + "?" + urllib.urlencode(params)
        headers = {}

        headers = {
            "User-Agent":
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36",
            "Referer":  "http://web2.qq.com/webqq.html"}
        curl, buff = self.generate_curl(url, headers)
        try:
            curl.perform()
        except:
            logger.warn(u"获取聊天图片错误", exc_info=True)
            return u"[图片获取失败]"
        body = buff.getvalue()
        curl.close()
        buff.close()

        path = tempfile.mktemp()
        with open(path, 'w') as f:
            f.write(body)
        return self.upload_file(path)

    def get_group_img(self, gid, from_uin, file_id, server, name, key,
                      _type=0):
        """ 获取群发送的图片
        """
        ip, port = server.split(":")
        url = "http://web2.qq.com/cgi-bin/get_group_pic"
        params = {"type": _type, "fid": file_id, "gid": gid, "pic": name,
                  "rip": ip, "rport": port, "uin": from_uin,
                  "vfwebqq": self.vfwebqq}
        url = url + "?" + urllib.urlencode(params)
        headers = {
            "User-Agent":
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36",
            "Referer":  "http://web2.qq.com/webqq.html"}
        curl, buff = self.generate_curl(url, headers)
        try:
            curl.perform()
        except:
            logger.warn(u"获取群聊天图片错误", exc_info=True)
            return u"[图片获取失败]"
        body = buff.getvalue()
        buff.close()
        curl.close()
        path = tempfile.mktemp()
        with open(path, 'w') as f:
            f.write(body)
        return self.upload_file(path)

    def set_friends(self, data):
        """ 存储好友信息
        """
        self._friends = objects.Friends(data)

    def get_friends(self):
        return self._friends if hasattr(self, "_friends") else None

    def set_groups(self, data):
        self._groups = objects.GroupList(data)

    def get_groups(self):
        return self._groups if hasattr(self, "_groups") else None

    def set_discu(self, data):
        self._discu = objects.DiscuList(data)

    def get_discu(self):
        return self._discu if hasattr(self, "_discu") else None

    def lock(self):
        """ 当输入验证码时锁住
        """
        with open(self._lock_path, 'w'):
            pass

    def get_account(self, uin, _type=1):
        """ 获取好友QQ号
        :param _type: 类型, 1 是好友, 4 是群
        """
        # self.load_next_request(QQNumberRequest())
        ret = self.get_friends().get_account(uin)
        if ret:
            return ret

        url = "http://s.web2.qq.com/api/get_friend_uin2"
        params = {"code": "", "t": time.time() * 1000, "tuin": uin,
                  "type": _type, "verifysession": "", "vfwebqq": self.vfwebqq}
        url = url + "?" + urllib.urlencode(params)
        headers = {
            "User-Agent":
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36",
            "Referer":  const.S_REFERER}
        curl, buff = self.generate_curl(url, headers)

        try:
            curl.perform()
            ret = buff.getvalue()
            buff.close()
            data = json.loads(ret)
            curl.close()
        except:
            logger.warn(u"获取QQ号时发生错误", exc_info=True)
            return

        if data.get("retcode") == 0:
            logger.info(u"获取QQ号码成功: {0!r}".format(data))
            ret = data.get("result")
            uin = ret.get("uin")
            account = ret.get("account")
            self.get_friends().set_account(uin, account)
            return account
        logger.warn(u"获取QQ号码失败: {0!r}".format(data))

    def unlock(self):
        """ 解锁
        """
        if os.path.exists(self._lock_path):
            os.remove(self._lock_path)

    def clean(self):
        """ 清除锁住和等待状态
        """
        self.unlock()
        self.unwait()

    def wait(self):
        """ 当没有验证是否需要验证码时等待
        """
        with open(self._wait_path, 'w'):
            pass

    def unwait(self):
        """ 解除等待状态
        """
        if os.path.exists(self._wait_path):
            os.remove(self._wait_path)

    def is_lock(self):
        """ 检测是否被锁住
        """
        return os.path.exists(self._lock_path)

    def is_wait(self):
        """ 检测是否在等待生成验证码
        """
        return os.path.exists(self._wait_path)

    def _hash(self):
        """  获取好友列表时的Hash """
        return _hash.webqq_hash(self.qid, self.ptwebqq)

    def start_poll(self):
        """ 开始心跳和拉取信息
        """
        self.stop_poll = False
        if not self.poll_and_heart:
            self.login_time = time.time()
            logger.info("开始拉取信息")
            self.load_next_request(PollMessageRequest())
            self.poll_and_heart = True
            if self.hThread is None:
                logger.info("开始心跳")
                self.hThread = threading.Thread(target=self._heartbeat)
                self.hThread.setDaemon(True)
                self.hThread.start()

    def _heartbeat(self):
        """ 放入线程的产生心跳
        """
        assert not isinstance(threading.currentThread(), threading._MainThread)
        while 1:
            try:
                self.load_next_request(HeartbeatRequest())
            except:
                pass
            time.sleep(60)

    def make_msg_content(self, content, style):
        """ 构造QQ消息的内容

        :param content: 小心内容
        :type content: str
        :rtype: str
        """
        self.msg_id += 1
        return json.dumps([content,
                           ["font", style]])

    def get_delay(self, content):
        """ 根据消息内容是否和上一条内容相同和未送出的消息数目产生延迟

        :param content: 消息内容
        :rtype: tuple(delay, number)
        """
        MIN = self.message_interval
        delay = 0
        sub = time.time() - self.last_msg_time
        if self.last_msg_numbers < 0:
            self.last_msg_numbers = 0

        # 不足最小间隔就补足最小间隔
        if sub < MIN:
            delay = MIN
            logger.debug(u"间隔 %s 小于 %s, 设置延迟为%s", sub, MIN, delay)

        # 如果间隔是已有消息间隔的2倍, 则清除已有消息数
        # print "sub", sub, "n:", self.last_msg_numbers
        if self.last_msg_numbers > 0 and\
                sub / (MIN * self.last_msg_numbers) > 1:
            self.last_msg_numbers = 0

        # 如果还有消息未发送, 则加上他们的间隔
        if self.last_msg_numbers > 0:
            delay += MIN * self.last_msg_numbers
            logger.info(u"有%s条消息未发送, 延迟为 %s",
                        self.last_msg_numbers, delay)

        n = 1
        # 如果这条消息和上条消息一致, 保险起见再加上一个最小间隔
        if self.last_msg_content == content and sub < MIN:
            delay += MIN
            self.last_msg_numbers += 1
            n = 2

        self.last_msg_numbers += 1
        self.last_msg_content = content

        if delay:
            logger.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递"
                        .format(delay, self.last_msg_numbers))
        # 返回消息累加个数, 在消息发送后减去相应的数目
        return delay, n

    def consume_delay(self, number):
        """ 消费延迟

        :param number: 消费的消息数目
        """
        self.last_msg_numbers -= number
        self.last_msg_time = time.time()

    def get_group_id(self, uin):
        """ 根据组uin获取组的id

        :param uin: 组的uin
        """
        return self.get_groups().get_gid(uin)

    def get_friend_name(self, uin):
        """ 获取好友名称

        :param uin: 好友uin
        """
        return self.get_friends().get_show_name()

    def wrap(self, request, func=None):
        """ 装饰callback

        :param request: ~twqq.requests.WebQQRequest instance
        :param func: 回调函数
        """
        def _wrap(resp, *args, **kwargs):
            data = resp.body
            logger.debug(resp.headers)
            if resp.headers.get("Content-Type") == "application/json":
                data = json.loads(data) if data else {}
            else:
                try:
                    data = json.loads(data)
                except:
                    pass
            if func:
                func(resp, data, *args, **kwargs)

            funcs = self.client.request_handlers.get(
                check_request(request), [])
            for f in funcs:
                f(request, resp, data)

        return _wrap

    def handle_qq_msg_contents(self, from_uin, contents, eid=None, _type=0):
        """ 处理QQ消息内容

        :param from_uin: 消息发送人uin
        :param contents: 内容
        :param eid: 扩展id(群gid, 讨论组did)
        :type contents: list

        """
        content = ""
        for row in contents:
            if isinstance(row, (list)) and len(row) == 2:
                info = row[1]
                if row[0] == "offpic":
                    file_path = info.get("file_path")
                    content += self.get_msg_img(from_uin, file_path)

                if row[0] == "cface":
                    name = info.get("name")
                    key = info.get("key")
                    file_id = info.get("file_id")
                    server = info.get("server")
                    content += self.get_group_img(eid, from_uin, file_id,
                                                  server, name, key, _type)

            if isinstance(row, (str, unicode)):
                content += row.replace(u"【提示:此用户正在使用Q+"
                                       u" Web:http://web.qq.com/】", "")\
                    .replace(u"【提示:此用户正在使用Q+"
                             u" Web:http://web3.qq.com/】", "")
        return content.replace("\r", "\n").replace("\r\n", "\n")\
            .replace("\n\n", "\n")

    def get_group_member_nick(self, gcode, uin):
        """ 根据组代码和用户uin获取群成员昵称

        :param gcode: 组代码
        :param uin: 群成员uin
        """
        return self.get_groups().get_member_nick(gcode, uin)

    def dispatch(self, qq_source):
        """ 调度QQ消息

        :param qq_source: 源消息包
        """
        if self.stop_poll:
            logger.info("检测Poll已停止, 此消息不处理: {0}".format(qq_source))
            return

        if qq_source.get("retcode") == 0:
            messages = qq_source.get("result")
            logger.info(u"获取消息: {0}".format(messages))
            for m in messages:
                poll_type = m.get("poll_type")
                if poll_type == "buddies_status_change":
                    self.get_friends().set_status(**m.get("value", {}))
                else:
                    funcs = self.client.msg_handlers.get(m.get("poll_type"),
                                                         [])
                    [func(*func._args_func(self, m)) for func in funcs]

    def recv_file(self, guid, lcid, to, callback):
        """ 接收文件

        :param guid: 文件名
        :param lcid: 会话id
        :param to_uin: 发送人uin
        :param callback:  回调, 接收两个参数, 分别是文件名和文件内容
        """
        self.load_next_request(FileRequest(guid, lcid, to, callback))

    def relogin(self):
        """ 被T出或获取登出时尝试重新登录
        """
        self.stop_poll = True
        self.poll_and_heart = None
        self.load_next_request(Login2Request(relogin=True))

    def disconnect(self):
        self.stop_poll = True
        self.poll_and_heart = None
        self.load_next_request(LogoutRequset())

    def send_sess_msg(self, qid, to_uin, content, style=const.DEFAULT_STYLE):
        """ 发送临时消息

        :param qid: 发送临时消息的qid
        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(SessMsgRequest(qid, to_uin, content,
                                                     style))

    def send_group_msg(self, group_uin, content, style=const.DEFAULT_STYLE):
        """ 发送群消息

        :param group_uin: 组的uin
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(GroupMsgRequest(group_uin, content,
                                                      style))

    def send_discu_msg(self, did, content, style=const.DEFAULT_STYLE):
        """ 发送讨论组消息

        :param did: 讨论组id
        :param content: 内容
        """
        return self.load_next_request(DiscuMsgRequest(did, content, style))

    def send_buddy_msg(self, to_uin, content, style=const.DEFAULT_STYLE):
        """ 发送好友消息

        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(BuddyMsgRequest(to_uin, content, style))

    def send_msg_with_markname(self, markname, content):
        """ 使用备注名发送消息

        :param markname: 备注名
        :param content: 消息内容
        :rtype: None or Request instance
        """
        uin = self.get_friends().get_uin_from_mark(markname)
        if not uin:
            return
        return self.send_buddy_msg(uin, content)

    def accept_verify(self, uin, account, markname=""):
        """ 同意验证请求

        :param  uin: 请求人uin
        :param account: 请求人账号
        :param markname: 添加后的备注
        """
        return self.load_next_request(AcceptVerifyRequest(uin, account,
                                                          markname))

    def refresh_friend_info(self):
        self.load_next_request(FriendListRequest(manual=True))

    def refresh_group_info(self, _id):
        """ 手动刷新某个群的信息

        :param _id: 对应群生成的唯一id
        """
        gcode, _type = objects.UniqueIds.get(int(_id))
        if gcode is None or _type is None:
            return False, u"没有找到对象"

        if _type != objects.UniqueIds.T_GRP:
            return False, u"该对象不是群"

        self.load_next_request(GroupMembersRequest(gcode))
        return True, self.get_groups().get_group_name(gcode)
Пример #21
0
        if (content.startswith("<") and content.endswith(">")) or\
           (content.startswith(u"《") and content.endswith(u"》")):
            self._name = content.strip("<").strip(">").strip(u"《")\
                    .strip(u"》")

            if not self._name.strip():
                return False

            if self.douban is None:
                self.douban = DoubanReader(self.http)
            return True
        return False


    def handle_message(self, callback):
        self.douban.search(self._name, callback)

if __name__ == "__main__":
    from tornadohttpclient import TornadoHTTPClient
    def cb(b):
        print b
    douban = DoubanReader(TornadoHTTPClient())
    douban.search(u"百年孤独", cb)
    douban.search(u"鸟哥的私房菜", cb)
    douban.search(u"论语", cb)
    douban.search(u"寒战", cb)
    douban.search(u"阿凡达", cb)
    douban.search(u"创战记", cb)
    douban.search(u"简单爱", cb)
    TornadoHTTPClient().start()
Пример #22
0
class RequestHub(object):
    """ 集成Request请求和保存请求值
    :param qid: qq号
    :param pwd: 密码
    :param client: ~twqq.client.Client instance
    """
    SIG_RE = re.compile(r'var g_login_sig=encodeURIComponent\("(.*?)"\);')
    def __init__(self, qid, pwd, client = None, debug = False):
        self.http = TornadoHTTPClient()
        self.http.set_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36")
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.http.debug = debug

        self.qid = qid
        self.__pwd = pwd
        self.client = client

        self.rc = random.randrange(0, 100)
        self.aid = 1003903                                    # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)     # 消息id, 随机初始化
        self.daid = 164
        self.login_sig = None
        self.ptwebqq = None
        self.nickname = None
        self.vfwebqq = None
        self.psessionid = None
        self.stop_poll = False

        # 检查是否验证码的回调
        self.ptui_checkVC = lambda r, v, u: (r, v, u)

        # 是否需要验证码
        self.require_check = None
        self.require_check_time = None

        # 是否开始心跳和拉取消息
        self.poll_and_heart = None
        self.login_time = None
        self.hThread = None

        # 验证图片
        self.checkimg_path = tempfile.mktemp(".jpg")
        self._lock_path = tempfile.mktemp()
        self._wait_path = tempfile.mktemp()

        self.friend_info = {}        # 初始化好友列表
        self.group_info = {}         # 初始化组列表
        self.group_sig = {}          # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {} # 初始化组成员列表
        self.mark_to_uin = {}        # 备注名->uin的映射

        self.message_interval = 0.5  # 消息间隔
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0    # 剩余位发送的消息数量
        WebQQRequest.hub = self
        self.load_next_request(FirstRequest())

    def load_next_request(self, request):
        """ 加载下一个请求

        :param request: ~twqq.requests.WebQQRequest instance
        :rtype: ~twqq.requests.WebQQRequest instance
        """
        func = self.http.get if request.method == WebQQRequest.METHOD_GET \
                else self.http.post

        if self.stop_poll and isinstance(request, PollMessageRequest):
            logger.info("检测Poll已停止, 此请求不处理: {0}".format(request))
            return

        kwargs = copy.deepcopy(request.kwargs)
        callback = request.callback if hasattr(request, "callback") and\
                callable(request.callback) else None
        kwargs.update(callback = self.wrap(request, callback))
        kwargs.update(headers = request.headers)
        kwargs.update(delay = request.delay)
        logger.debug("KWARGS: {0}".format(kwargs))

        if request.ready:
            logger.debug("处理请求: {0}".format(request))
            with ExceptionStackContext(request.handle_exc):
                func(request.url, request.params, **kwargs)
        else:
            logger.debug("请求未就绪: {0}".format(request))

        return request


    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd


    def upload_file(self, filename, path):
        """ 上传文件

        :param filename: 文件名
        :param path: 文件路径
        """
        form = Form()
        filename = filename.encode("utf-8")
        form.add_file(fieldname='name', filename=filename,
                        fileHandle=open(path))
        img_host = "http://dimg.vim-cn.com/"
        req = urllib2.Request(img_host)
        req.add_header("Content-Type", form.get_content_type())
        req.add_header("Content-Length", len(str(form)))
        req.add_header("User-Agent", "curl/python")
        req.add_data(str(form))
        return urllib2.urlopen(req)


    def lock(self):
        """ 当输入验证码时锁住
        """
        with open(self._lock_path, 'w'):
            pass


    def unlock(self):
        """ 解锁
        """
        if os.path.exists(self._lock_path):
            os.remove(self._lock_path)


    def clean(self):
        """ 清除锁住和等待状态
        """
        self.unlock()
        self.unwait()

    def wait(self):
        """ 当没有验证是否需要验证码时等待
        """
        with open(self._wait_path, 'w'):
            pass

    def unwait(self):
        """ 解除等待状态
        """
        if os.path.exists(self._wait_path):
            os.remove(self._wait_path)

    def is_lock(self):
        """ 检测是否被锁住
        """
        return os.path.exists(self._lock_path)

    def is_wait(self):
        """ 检测是否在等待生成验证码
        """
        return os.path.exists(self._wait_path)


    def _hash(self):
        """  获取好友列表时的Hash """
        return _hash.webqq_hash(self.qid, self.ptwebqq)


    def start_poll(self):
        """ 开始心跳和拉取信息
        """
        self.stop_poll = False
        if not self.poll_and_heart:
            self.login_time = time.time()
            logger.info("开始拉取信息")
            self.load_next_request(PollMessageRequest())
            self.poll_and_heart = True
            if self.hThread is None:
                logger.info("开始心跳")
                self.hThread = threading.Thread(target = self._heartbeat)
                self.hThread.setDaemon(True)
                self.hThread.start()


    def _heartbeat(self):
        """ 放入线程的产生心跳
        """
        assert not isinstance(threading.currentThread(), threading._MainThread)
        while 1:
            try:
                self.load_next_request(HeartbeatRequest())
            except:
                pass
            time.sleep(60)



    def make_msg_content(self, content):
        """ 构造QQ消息的内容

        :param content: 小心内容
        :type content: str
        :rtype: str
        """
        self.msg_id += 1
        return json.dumps([content, ["font", {"name":"Monospace", "size":10,
                                   "style":[0, 0, 0], "color":"000000"}]])


    def get_delay(self, content):
        """ 根据消息内容是否和上一条内容相同和未送出的消息数目产生延迟

        :param content: 消息内容
        :rtype: tuple(delay, number)
        """
        MIN = self.message_interval
        delay = 0
        sub = time.time() - self.last_msg_time
        if self.last_msg_numbers < 0:
            self.last_msg_numbers = 0

        # 不足最小间隔就补足最小间隔
        if sub < MIN:
            delay = MIN
            logger.debug(u"间隔 %s 小于 %s, 设置延迟为%s", sub, MIN, delay)

        # 如果间隔是已有消息间隔的2倍, 则清除已有消息数
        #print "sub", sub, "n:", self.last_msg_numbers
        if self.last_msg_numbers > 0 and sub / (MIN * self.last_msg_numbers)> 1:
            self.last_msg_numbers = 0

        # 如果还有消息未发送, 则加上他们的间隔
        if self.last_msg_numbers > 0:
            delay += MIN * self.last_msg_numbers
            logger.info(u"有%s条消息未发送, 延迟为 %s", self.last_msg_numbers, delay)


        n = 1
        # 如果这条消息和上条消息一致, 保险起见再加上一个最小间隔
        if self.last_msg_content == content and sub < MIN:
            delay += MIN
            self.last_msg_numbers += 1
            n = 2

        self.last_msg_numbers += 1
        self.last_msg_content = content

        if delay:
            logger.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递"
                         .format(delay, self.last_msg_numbers))
        # 返回消息累加个数, 在消息发送后减去相应的数目
        return delay, n

    def consume_delay(self, number):
        """ 消费延迟

        :param number: 消费的消息数目
        """
        self.last_msg_numbers -= number
        self.last_msg_time = time.time()


    def get_group_id(self, uin):
        """ 根据组uin获取组的id

        :param uin: 组的uin
        """
        return self.group_info.get(uin, {}).get("gid")


    def wrap(self, request, func = None):
        """ 装饰callback

        :param request: ~twqq.requests.WebQQRequest instance
        :param func: 回调函数
        """
        def _wrap(resp, *args, **kwargs):
            data = resp.body
            logger.debug(resp.headers)
            if resp.headers.get("Content-Type") == "application/json":
                data = json.loads(data) if data else {}
            else:
                try:
                    data = json.loads(data)
                except:
                    pass
            if func:
                func(resp, data, *args, **kwargs)

            funcs = self.client.request_handlers.get(check_request(request), [])
            for f in funcs:
                f(request, resp, data)

        return _wrap


    def handle_qq_msg_contents(self, contents):
        """ 处理QQ消息内容

        :param contents: 内容
        :type contents: list
        """
        content = ""
        for row in contents:
            if isinstance(row, (str, unicode)):
                content += row.replace(u"【提示:此用户正在使用Q+"
                                       u" Web:http://web.qq.com/】", "")\
                        .replace(u"【提示:此用户正在使用Q+"
                                       u" Web:http://web3.qq.com/】", "")
        return  content.replace("\r", "\n").replace("\r\n", "\n")\
                .replace("\n\n", "\n")


    def get_group_member_nick(self, gcode, uin):
        """ 根据组代码和用户uin获取群成员昵称

        :param gcode: 组代码
        :param uin: 群成员uin
        """
        return self.group_members_info.get(gcode, {}).get(uin, {}).get("nick")


    def dispatch(self, qq_source):
        """ 调度QQ消息

        :param qq_source: 源消息包
        """
        if self.stop_poll:
            logger.info("检测Poll已停止, 此消息不处理: {0}".format(qq_source))
            return

        if qq_source.get("retcode") == 0:
            messages = qq_source.get("result")
            for m in messages:
                funcs = self.client.msg_handlers.get(m.get("poll_type"), [])
                [func(*func._args_func(self, m)) for func in funcs]


    def relogin(self):
        """ 被T出或获取登出时尝试重新登录
        """
        self.stop_poll = True
        self.poll_and_heart = None
        self.load_next_request(Login2Request(relogin = True))


    def send_sess_msg(self, qid, to_uin, content):
        """ 发送临时消息

        :param qid: 发送临时消息的qid
        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(SessMsgRequest(qid, to_uin, content))


    def send_group_msg(self, group_uin, content):
        """ 发送群消息

        :param group_uin: 组的uin
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(GroupMsgRequest(group_uin, content))


    def send_buddy_msg(self, to_uin, content):
        """ 发送好友消息

        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(BuddyMsgRequest(to_uin, content))

    def send_msg_with_markname(self, markname, content):
        """ 使用备注名发送消息

        :param markname: 备注名
        :param content: 消息内容
        :rtype: None or Request instance
        """
        uin = self.mark_to_uin.get(markname)
        if not uin:
            return
        return self.send_buddy_msg(uin, content)

    def accept_verify(self, uin, account, markname = ""):
        """ 同意验证请求

        :param  uin: 请求人uin
        :param account: 请求人账号
        :param markname: 添加后的备注
        """
        return self.load_next_request(AcceptVerifyRequest(uin, account, markname))
Пример #23
0
#!/usr/bin/env python
#coding=utf8
from tornado import gen
from tornado.ioloop import IOLoop

from tornadohttpclient import TornadoHTTPClient
import json
# 实例化
http = TornadoHTTPClient()

@gen.coroutine
def get():
    headers = dict((("content-type","application/json"),("Accept-Charset","utf8"),("Authorization","token F017254EAF208C462C8D4ABCBC27F1DD7CB8DA65FF70B063CF16E90AA35BAE7FAF360C87D0617ED7723DC64FC8CA6A47438F5BED9FC60C303DF3AFAAE2C44A8FAE961E8999B43C820615D9489DA0AB178368A3F9CA7640CA755283A67E8D9CC13DA55AFD48606621E37C601A52E64C94530C92D468CF5A72DE68C0F7230103F400A855997086CE4E4E357BB887D0B08C6D5238EA6B5487E0231A10B38650FEE92D14264704D3FC1C80AC6675612E41C17A73D9283470A0C1FBB8C089106169AB6C179C296D073F09FDB1822646AE5B76D458CC433C1F4829AC2DACE160F851188951ED2BF00CA6E4AA53CFCC659C100F53669EB2C9F4DDAA5BBB1C5D8E98EFB720C94AA3") ))
    # 发出get请求
    # response = yield #http.post("http://ipsapro.isoftstone.com:8081/api/user/login",'{"strUser":"******","strPwd":"e10adc3949ba59abbe56e057f20f883ee10adc3949ba59abbe56e057f20f883e"}',headers=headers)

    response = yield http.post("http://ipsapro.isoftstone.com:8081/api/Department/GetDepartment",'',headers=headers);
    # print(response.body)
    fo = open("department.txt", "wb")
    #j=json.loads(response.body)
    #str_json=json.dumps(j['departments'],encoding="gbk",ensure_ascii=False)
    str_json=response.body
    fo.write(str_json)
IOLoop.instance().run_sync(get)
Пример #24
0
class TornadoHTTPClientTest(unittest.TestCase):
    def setUp(self):
        self.http = TornadoHTTPClient()
        self.http.debug = True

    def _callback(self, response):
        print(response.code)
        print("当前链接地址: ")
        print(response.effective_url)
        self.http.stop()

    def test_get(self):
        self.http.get("http://www.linuxzen.com", callback=self._callback)
        self.http.start()

    def test_get_args(self):
        self.http.get("http://www.baidu.com/s", (("wd", "tornado"), ),
                      callback=self._callback)

    def test_post(self):
        params = [("vimcn", u"# 这是TornadoHTTPClient单元测试提交的".encode("utf-8"))]
        url = "http://p.vim-cn.com"

        def callback(response):
            print("打开此链接:", end=" ")
            print(response.effective_url)
            self.http.stop()

        self.http.post(url, params, callback=callback)
        self.http.start()

    def test_head(self):
        def callback(response):
            pass

        self.http.head("http://linuxzen.com", callback=callback)

    def test_callback_args(self):
        def callback(times, response):
            print(response.code)
            print("当前链接地址: ")
            print(response.effective_url)
            print("当前请求次数", end=" ")
            print(times)
            if times == 9:
                self.http.stop()

        for i in range(10):
            self.http.get("http://www.linuxzen.com",
                          callback=callback,
                          args=(i, ))

        self.http.start()

    def test_user_agent(self):
        user_agent =  "Mozilla/5.0 (X11; Linux x86_64)"\
                " AppleWebKit/537.11 (KHTML, like Gecko)"\
                " Chrome/23.0.1271.97 Safari/537.11"
        self.http.set_user_agent(user_agent)

        def callback(response):
            #self.assertEqual(response.request.headers["User-Agent"], user_agent)
            self.http.stop()

        self.http.get("http://www.linuxzen.com", callback=callback)
        self.http.start()

    def test_header(self):
        headers = {"Origin": "http://www.linuxzen.com"}

        def callback(response):
            self.assertEqual(response.request.headers["Origin"],
                             headers.get("Origin"))
            self.http.stop()

        self.http.get("http://www.linuxzen.com", callback=callback)
        self.http.start()

    def test_cookie(self):
        def callback(response):
            print("cookie >>>>>>>>>>>>>>>>>>", end=" ")
            print(self.http.cookie)
            self.http.stop()

        self.http.get("http://www.baidu.com", callback=callback)
        self.http.start()

    def test_cookie_jar(self):
        def callback(response):
            print("cookie jar>>>>>>>>>>>>>>>>>>", end=" ")
            print(self.http.cookiejar)
            self.http.stop()

        self.http.get("http://www.baidu.com", callback=callback)
        self.http.start()

    def test_upload_img(self):
        def callback(response):
            print("打开图片链接", end=" ")
            print(response.effective_url)
            self.http.stop()

        self.http.upload("http://dimg.vim-cn.com",
                         "name",
                         "img_test.png",
                         callback=callback)
        self.http.start()
Пример #25
0
class TornadoHTTPClientCoroutineTest(testing.AsyncTestCase):
    def setUp(self):
        super(TornadoHTTPClientCoroutineTest, self).setUp()
        self.http = TornadoHTTPClient()
        self.http.debug = True

    def _callback(self, response):
        print(response.code)
        print("当前链接地址: ")
        print(response.effective_url)
        self.http.stop()

    @testing.gen_test
    def test_get(self):
        resp = yield self.http.get("http://www.linuxzen.com")
        self._callback(resp)

    @testing.gen_test
    def test_get_args(self):
        yield self.http.get("http://www.baidu.com/s", (("wd", "tornado"),),
                            callback=self._callback)

    @testing.gen_test
    def test_post(self):
        params = [("vimcn", u"# 这是TornadoHTTPClient单元测试提交的".encode("utf-8"))]
        url = "http://p.vim-cn.com"
        def callback(response):
            print("打开此链接:", end=" ")
            print(response.effective_url)

        resp = yield self.http.post(url, params)
        callback(resp)

    @testing.gen_test
    def test_head(self):
        resp = yield self.http.head("http://linuxzen.com")
        self.assertEqual(resp.code, 200)

    @testing.gen_test
    def test_user_agent(self):
        user_agent =  "Mozilla/5.0 (X11; Linux x86_64)"\
                " AppleWebKit/537.11 (KHTML, like Gecko)"\
                " Chrome/23.0.1271.97 Safari/537.11"
        self.http.set_user_agent(user_agent)

        resp = yield self.http.get("http://www.linuxzen.com")
        self.assertEqual(resp.request.user_agent, user_agent)

    @testing.gen_test
    def test_header(self):
        headers = {"Origin":"http://www.linuxzen.com"}
        resp = yield self.http.get("http://www.linuxzen.com", headers=headers)
        self.assertEqual(resp.request.headers["Origin"], headers["Origin"])

    @testing.gen_test
    def test_cookie(self):
        yield self.http.get("http://www.baidu.com")
        print(self.http.cookie)

    @testing.gen_test
    def test_cookie_jar(self):
        yield self.http.get("http://www.baidu.com")
        print("cookie jar>>>>>>>>>>>>>>>>>>", end=" ")
        print(self.http.cookiejar)

    @testing.gen_test
    def test_upload_img(self):
        def callback(response):
            self.http.stop()

        resp = yield self.http.upload("http://dimg.vim-cn.com", "name",
                                        "img_test.png")
        print("打开图片链接", end = " ")
        print(resp.effective_url)
Пример #26
0
 def setUp(self):
     self.http = TornadoHTTPClient()
     self.http.debug = True
Пример #27
0
class TornadoHTTPClientTest(unittest.TestCase):
    def setUp(self):
        self.http = TornadoHTTPClient()
        self.http.debug = True

    def _callback(self, response):
        print(response.code)
        print("当前链接地址: ")
        print(response.effective_url)
        self.http.stop()

    def test_get(self):
        self.http.get("http://www.linuxzen.com", callback=self._callback)
        self.http.start()

    def test_get_args(self):
        self.http.get("http://www.baidu.com/s", (("wd", "tornado"),),
                      callback=self._callback)

    def test_post(self):
        params = [("vimcn", u"# 这是TornadoHTTPClient单元测试提交的".encode("utf-8"))]
        url = "http://p.vim-cn.com"
        def callback(response):
            print("打开此链接:", end=" ")
            print(response.effective_url)
            self.http.stop()

        self.http.post(url, params, callback=callback)
        self.http.start()

    def test_head(self):
        def callback(response):
            pass

        self.http.head("http://linuxzen.com", callback=callback)

    def test_callback_args(self):
        def callback(times, response):
            print(response.code)
            print("当前链接地址: ")
            print(response.effective_url)
            print("当前请求次数", end=" ")
            print(times)
            if times == 9:
                self.http.stop()

        for i in range(10):
            self.http.get("http://www.linuxzen.com", callback=callback,
                          args=(i,))

        self.http.start()

    def test_user_agent(self):
        user_agent =  "Mozilla/5.0 (X11; Linux x86_64)"\
                " AppleWebKit/537.11 (KHTML, like Gecko)"\
                " Chrome/23.0.1271.97 Safari/537.11"
        self.http.set_user_agent(user_agent)

        def callback(response):
            #self.assertEqual(response.request.headers["User-Agent"], user_agent)
            self.http.stop()

        self.http.get("http://www.linuxzen.com", callback=callback)
        self.http.start()

    def test_header(self):
        headers = {"Origin":"http://www.linuxzen.com"}
        def callback(response):
            self.assertEqual(response.request.headers["Origin"],
                             headers.get("Origin"))
            self.http.stop()

        self.http.get("http://www.linuxzen.com", callback=callback)
        self.http.start()

    def test_cookie(self):
        def callback(response):
            print("cookie >>>>>>>>>>>>>>>>>>", end=" ")
            print(self.http.cookie)
            self.http.stop()

        self.http.get("http://www.baidu.com", callback=callback)
        self.http.start()

    def test_cookie_jar(self):
        def callback(response):
            print("cookie jar>>>>>>>>>>>>>>>>>>", end=" ")
            print(self.http.cookiejar)
            self.http.stop()

        self.http.get("http://www.baidu.com", callback=callback)
        self.http.start()

    def test_upload_img(self):
        def callback(response):
            print("打开图片链接", end = " ")
            print(response.effective_url)
            self.http.stop()

        self.http.upload("http://dimg.vim-cn.com", "name", "img_test.png",
                         callback=callback)
        self.http.start()
Пример #28
0
class Command(object):
    http = TornadoHTTPClient()
    _sim_try = {}
    simsimi_proxy = False

    def url_info(self, url, callback, isredirect=False):
        """ 获取url信息
        Arguments:
            `url`   -   链接
            `callback`  -   发送消息的回调
            `isredirect` -   是否是重定向
        """
        _url_info = partial(self._url_info,
                            callback=callback,
                            url=url,
                            isredirect=isredirect)
        self.http.get(url, callback=_url_info)

    def _url_info(self, resp, callback, url, isredirect=False):
        """ 读取url_info的回调 """
        meta_charset = re.compile(
            br'<meta\s+http-equiv="?content-type"?'
            '\s+content="?[^;]+;\s*charset=([^">]+'
            ')"?\s*/?>|<meta\s+charset="?([^">/"]+'
            ')"?\s*/?>', re.IGNORECASE)
        body = ""
        content = resp.body
        c_type = resp.headers.get("Context-Type", "text/html")
        if resp.code in [200]:
            if c_type == "text/html":
                charset = meta_charset.findall(content)
                logging.info("Found charset {0!r} in url {1}".format(
                    charset, url))
                if len(charset) == 1 and len(charset[0]) == 2:
                    charset = charset[0][0] if charset[0][0] else charset[0][1]
                else:
                    charset = ""

                if charset.lower().strip() == "gb2312":
                    charset = "gbk"

                if charset:
                    ucont = content.lower().decode(charset).encode(
                        "utf-8").decode("utf-8")
                else:
                    ucont = content.lower().decode("utf-8")
                parser = etree.HTML(ucont)
                title = parser.xpath(u"//title")
                title = title[0].text if len(title) >= 1 else None
                if title:
                    body += u"网页标题: " + title.replace("\r", "").replace(
                        "\n", "")
                if isredirect:
                    body += u"(重定向到:{0})".format(url)
        elif resp.code in [302, 301]:
            dst = resp.headers.get("Location")
            self.url_info(dst, callback, True)
        else:
            body = u"({0} {1} {2})".format(url, resp.code,
                                           httplib.responses[resp.code])

        if body:
            callback(body)

    def _eurl_info(self, errcode, errmsg, url, callback):
        """ 处理url_info错误 """
        body = u"({0} {1})".format(url, errmsg)
        callback(body)

    def py(self, code, callback):
        """ 执行Python代码
        Arguments:
            `code`      -   要执行的代码
            `callback`  -   发送消息的回调
        """
        url = "http://pythonec.appspot.com/run"
        #url = "http://localhost:8080/run"
        params = [("code", code.encode("utf-8"))]

        read_py = partial(self.read_py, callback=callback)
        self.http.post(url, params, callback=read_py)

    def read_py(self, resp, callback):
        """ 读取执行Python代码的返回 """
        data = resp.body
        try:
            result = json.loads(data)
            status = result.get("status")
            if status:
                content = u"OK: " + result.get("out")
            else:
                content = u"ERR: " + result.get("err")

        except ValueError:
            logging.warn(traceback.format_exc())
            content = u"我出错了, 没办法执行, 我正在改"
        callback(content)

    def shell(self, session, statement, callback):
        """ 实现Python Shell
        Arguments:
            `session`   -   区别用户的shell
            `statement` -   Python语句
            `callback`  -   发送结果的回调
        """
        if statement.strip() in ["cls", "clear"]:
            url = "http://pythonec.appspot.com/drop"
            params = [
                ("session", session),
            ]
        else:
            url = "http://pythonec.appspot.com/shell"
            #url = "http://localhost:8080/shell"
            params = [("session", session),
                      ("statement", statement.encode("utf-8"))]

        def read_shell(resp, callback):
            data = resp.body
            if not data:
                data = "OK"
            callback(data.decode("utf-8"))
            return

        callback = partial(read_shell, callback=callback)
        self.http.get(url, params, callback=callback)

    def paste(self, code, callback, typ="text"):
        """ 贴代码 """
        url = "http://paste.linuxzen.com"
        params = [("class", typ), ("code", code.encode("utf-8")),
                  ("paste", "ff")]

        callback = partial(self.read_paste, oldurl=url, callback=callback)
        self.http.post(url, params, callback=callback)

    def read_paste(self, resp, oldurl, callback):
        """ 读取贴代码结果, 并发送消息 """
        if resp.code == 302:
            url = resp.headers.get("Location")
        else:
            url = resp.effective_url
        if url != oldurl:
            content = url
            callback(content)

    def teach(self, say, response):
        url = "http://paste.linuxzen.com/bot/teach"
        params = (("say", say.encode("utf-8")), ("res",
                                                 response.encode("utf-8")))
        logging.info(u"Teach our bot {0}/{1}".format(say, response))
        self.http.get(url, params)

    def talk(self, say, callback):
        url = "http://paste.linuxzen.com/bot/talk"
        params = (("say", say.encode("utf-8")), )

        def callback(resp):
            data = resp.body
            r = json.loads(data)
            if r.get("status"):
                callback(r.get("response"))
            else:
                self.simsimi(say, callback)

        self.http.get(url, params, callback=callback)

    def simsimi(self, content, callback):
        """ simsimi 小黄鸡 """
        msg_url = "http://www.simsimi.com/func/req"
        msg_params = (("msg", content.encode("utf-8")), ("lc", "ch"))
        headers = {
            "Referer": "http://www.simsimi.com/talk.htm?lc=ch",
            "X-Requested-With": "XMLHttpRequest"
        }

        def read_simsimi(resp):
            result = resp.body
            if result:
                try:
                    response = json.loads(result)
                    res = response.get("response")

                    if is_black_msg(res):
                        return self.simsimi(content, callback)

                    if not res or (res and
                                   res.startswith("Unauthorized access!.")):
                        if not self._sim_try.has_key(content):
                            self._sim_try[content] = 0
                        if self._sim_try.get(content) < 10:
                            logging.warn(
                                "SimSimi error with response {0}".format(res))
                            self._sim_try[content] += 1
                            self.simsimi(content, callback)
                        else:
                            self._sim_try[content] = 0
                            callback(u"T^T ip被SimSimi封了, 无法应答")
                        return
                    else:
                        self._sim_try[content] = 0
                        callback(res)
                        self.teach(content, res)
                except ValueError:
                    logging.warn(traceback.format_exc())
                    logging.warn(
                        "SimSimi error with response {0}".format(result))
                    #self.simsimi(content, callback)
                    callback(u"呵呵")

        kw = {"headers": headers, "callback": read_simsimi}
        if SimSimi_Proxy:
            kw.update(proxy=SimSimi_Proxy)

        self.http.get(msg_url, msg_params, **kw)

    def cetr(self, source, callback, web=False):
        """ 调用有道接口进行英汉互译 """
        key = YOUDAO_KEY
        keyfrom = YOUDAO_KEYFROM
        source = source.encode("utf-8")
        url = "http://fanyi.youdao.com/openapi.do"
        params = [("keyfrom", keyfrom), ("key", key), ("type", "data"),
                  ("doctype", "json"), ("version", 1.1), ("q", source)]

        callback = partial(self.read_cetr, callback=callback, web=web)
        self.http.get(url, params, callback=callback)

    def read_cetr(self, resp, callback, web):
        """ 读取英汉翻译的结果 """
        """
        try:
            buf = StringIO(source)
            with gzip.GzipFile(mode = "rb", fileobj = buf) as gf:
                data = gf.read()
        except:
            logging.warn(traceback.format_exc())
            data = source
        """

        try:
            result = json.loads(resp.body)
        except ValueError:
            logging.warn(traceback.format_exc())
            body = u"error"
        else:
            errorCode = result.get("errorCode")
            if errorCode == 0:
                query = result.get("query")
                r = " ".join(result.get("translation"))
                basic = result.get("basic", {})
                body = u"{0}\n{1}".format(query, r)
                phonetic = basic.get("phonetic")
                if phonetic:
                    ps = phonetic.split(",")
                    if len(ps) == 2:
                        pstr = u"读音: 英 [{0}] 美 [{1}]".format(*ps)
                    else:
                        pstr = u"读音: {0}".format(*ps)
                    body += u"\n" + pstr

                exp = basic.get("explains")
                if exp:
                    body += u"\n其他释义:\n\t{0}".format(u"\n\t".join(exp))

                if web:
                    body += u"\n网络释义:\n"
                    web = result.get("web", [])
                    if web:
                        for w in web:
                            body += u"\t{0}\n".format(w.get("key"))
                            vs = u"\n\t\t".join(w.get("value"))
                            body += u"\t\t{0}\n".format(vs)

            if errorCode == 50:
                body = u"无效的有道key"

        if not body:
            body = u"没有结果"

        callback(body)

    def send_msg(self, msg, callback, nick=None):
        if len(msg) <= MAX_LENGTH:
            body = nick + msg if nick else msg
            callback(body)
        else:
            callback = partial(self.send_msg, callback=callback, nick=nick)
            self.paste(msg, callback)
Пример #29
0
class RequestHub(object):
    """ 集成Request请求和保存请求值
    :param qid: qq号
    :param pwd: 密码
    :param client: ~twqq.client.Client instance
    """
    SIG_RE = re.compile(r'var g_login_sig=encodeURIComponent\("(.*?)"\);')

    def __init__(self, qid, pwd, client=None, debug=False):
        self.http = TornadoHTTPClient()
        self.http.set_user_agent(
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36")
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.http.debug = debug

        self.qid = qid
        self.__pwd = pwd
        self.client = client

        self.rc = random.randrange(0, 100)
        self.aid = 1003903  # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(11111111, 99999999)  # 消息id, 随机初始化
        self.daid = 164
        self.login_sig = None
        self.ptwebqq = None
        self.nickname = u"YouWillNeverGetIt"
        self.vfwebqq = None
        self.psessionid = None
        self.stop_poll = False

        # 检查是否验证码的回调
        self.ptui_checkVC = lambda r, v, u: (r, v, u)

        # 是否需要验证码
        self.require_check = None
        self.require_check_time = None

        # 是否开始心跳和拉取消息
        self.poll_and_heart = None
        self.login_time = None
        self.hThread = None

        # 验证图片
        self.checkimg_path = tempfile.mktemp(".jpg")
        self._lock_path = tempfile.mktemp()
        self._wait_path = tempfile.mktemp()

        self.group_sig = {}  # 组签名映射, 用作发送临时消息(sess_message)

        self.message_interval = 0.5  # 消息间隔
        self.last_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0  # 剩余位发送的消息数量
        WebQQRequest.hub = self
        self.connecting = False

    def connect(self):
        self.connecting = True
        self.load_next_request(FirstRequest())

    def load_next_request(self, request):
        """ 加载下一个请求

        :param request: ~twqq.requests.WebQQRequest instance
        :rtype: ~twqq.requests.WebQQRequest instance
        """
        func = self.http.get if request.method == WebQQRequest.METHOD_GET \
            else self.http.post

        if self.stop_poll and isinstance(request, PollMessageRequest):
            logger.info("检测Poll已停止, 此请求不处理: {0}".format(request))
            return

        kwargs = copy.deepcopy(request.kwargs)
        callback = request.callback if hasattr(request, "callback") and\
            callable(request.callback) else None
        kwargs.update(callback=self.wrap(request, callback))
        kwargs.update(headers=request.headers)
        kwargs.update(delay=request.delay)
        logger.debug("KWARGS: {0}".format(kwargs))

        if request.ready:
            logger.debug("处理请求: {0}".format(request))
            with ExceptionStackContext(request.handle_exc):
                func(request.url, request.params, **kwargs)
        else:
            logger.debug("请求未就绪: {0}".format(request))

        return request

    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd

    def upload_file(self, path):
        """ 上传文件

        :param path: 文件路径
        """
        img_host = "http://dimg.vim-cn.com/"
        curl, buff = self.generate_curl(img_host)
        curl.setopt(pycurl.POST, 1)
        curl.setopt(pycurl.HTTPPOST, [
            ('name', (pycurl.FORM_FILE, path)),
        ])
        try:
            curl.perform()
            ret = buff.getvalue()
            curl.close()
            buff.close()
        except:
            logger.warn(u"上传图片错误", exc_info=True)
            return u"[图片获取失败]"
        return ret

    def generate_curl(self, url=None, headers=None):
        """ 生成一个curl, 返回 curl 实例和用于获取结果的 buffer
        """
        curl = pycurl.Curl()
        buff = StringIO()

        curl.setopt(pycurl.COOKIEFILE, "cookie")
        curl.setopt(pycurl.COOKIEJAR, "cookie_jar")
        curl.setopt(pycurl.SHARE, self.http._share)
        curl.setopt(pycurl.WRITEFUNCTION, buff.write)
        curl.setopt(pycurl.FOLLOWLOCATION, 1)
        curl.setopt(pycurl.MAXREDIRS, 5)
        curl.setopt(pycurl.TIMEOUT, 3)
        curl.setopt(pycurl.CONNECTTIMEOUT, 3)

        if url:
            curl.setopt(pycurl.URL, url)

        if headers:
            self.set_curl_headers(curl, headers)

        return curl, buff

    def set_curl_headers(self, curl, headers):
        """ 将一个字典设置为 curl 的头
        """
        h = []
        for key, val in headers.items():
            h.append("{0}: {1}".format(key, val))
        curl.setopt(pycurl.HTTPHEADER, h)

    def get_msg_img(self, from_uin, file_path):
        """ 获取聊天信息中的图片
        """
        url = "http://d.web2.qq.com/channel/get_offpic2"
        params = {
            "clientid": self.clientid,
            "f_uin": from_uin,
            "file_path": file_path,
            "psessionid": self.psessionid
        }
        url = url + "?" + urllib.urlencode(params)
        headers = {}

        headers = {
            "User-Agent":
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36",
            "Referer":
            "http://web2.qq.com/webqq.html"
        }
        curl, buff = self.generate_curl(url, headers)
        try:
            curl.perform()
        except:
            logger.warn(u"获取聊天图片错误", exc_info=True)
            return u"[图片获取失败]"
        body = buff.getvalue()
        curl.close()
        buff.close()

        path = tempfile.mktemp()
        with open(path, 'w') as f:
            f.write(body)
        return self.upload_file(path)

    def get_group_img(self,
                      gid,
                      from_uin,
                      file_id,
                      server,
                      name,
                      key,
                      _type=0):
        """ 获取群发送的图片
        """
        ip, port = server.split(":")
        url = "http://web2.qq.com/cgi-bin/get_group_pic"
        params = {
            "type": _type,
            "fid": file_id,
            "gid": gid,
            "pic": name,
            "rip": ip,
            "rport": port,
            "uin": from_uin,
            "vfwebqq": self.vfwebqq
        }
        url = url + "?" + urllib.urlencode(params)
        headers = {
            "User-Agent":
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36",
            "Referer":
            "http://web2.qq.com/webqq.html"
        }
        curl, buff = self.generate_curl(url, headers)
        try:
            curl.perform()
        except:
            logger.warn(u"获取群聊天图片错误", exc_info=True)
            return u"[图片获取失败]"
        body = buff.getvalue()
        buff.close()
        curl.close()
        path = tempfile.mktemp()
        with open(path, 'w') as f:
            f.write(body)
        return self.upload_file(path)

    def set_friends(self, data):
        """ 存储好友信息
        """
        self._friends = objects.Friends(data)

    def get_friends(self):
        return self._friends if hasattr(self, "_friends") else None

    def set_groups(self, data):
        self._groups = objects.GroupList(data)

    def get_groups(self):
        return self._groups if hasattr(self, "_groups") else None

    def set_discu(self, data):
        self._discu = objects.DiscuList(data)

    def get_discu(self):
        return self._discu if hasattr(self, "_discu") else None

    def lock(self):
        """ 当输入验证码时锁住
        """
        with open(self._lock_path, 'w'):
            pass

    def get_account(self, uin, _type=1):
        """ 获取好友QQ号
        :param _type: 类型, 1 是好友, 4 是群
        """
        # self.load_next_request(QQNumberRequest())
        ret = self.get_friends().get_account(uin)
        if ret:
            return ret

        url = "http://s.web2.qq.com/api/get_friend_uin2"
        params = {
            "code": "",
            "t": time.time() * 1000,
            "tuin": uin,
            "type": _type,
            "verifysession": "",
            "vfwebqq": self.vfwebqq
        }
        url = url + "?" + urllib.urlencode(params)
        headers = {
            "User-Agent":
            "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
            " (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 "
            "Chrome/28.0.1500.71 Safari/537.36",
            "Referer":
            const.S_REFERER
        }
        curl, buff = self.generate_curl(url, headers)

        try:
            curl.perform()
            ret = buff.getvalue()
            buff.close()
            data = json.loads(ret)
            curl.close()
        except:
            logger.warn(u"获取QQ号时发生错误", exc_info=True)
            return

        if data.get("retcode") == 0:
            logger.info(u"获取QQ号码成功: {0!r}".format(data))
            ret = data.get("result")
            uin = ret.get("uin")
            account = ret.get("account")
            self.get_friends().set_account(uin, account)
            return account
        logger.warn(u"获取QQ号码失败: {0!r}".format(data))

    def unlock(self):
        """ 解锁
        """
        if os.path.exists(self._lock_path):
            os.remove(self._lock_path)

    def clean(self):
        """ 清除锁住和等待状态
        """
        self.unlock()
        self.unwait()

    def wait(self):
        """ 当没有验证是否需要验证码时等待
        """
        with open(self._wait_path, 'w'):
            pass

    def unwait(self):
        """ 解除等待状态
        """
        if os.path.exists(self._wait_path):
            os.remove(self._wait_path)

    def is_lock(self):
        """ 检测是否被锁住
        """
        return os.path.exists(self._lock_path)

    def is_wait(self):
        """ 检测是否在等待生成验证码
        """
        return os.path.exists(self._wait_path)

    def _hash(self):
        """  获取好友列表时的Hash """
        return _hash.webqq_hash(self.qid, self.ptwebqq)

    def start_poll(self):
        """ 开始心跳和拉取信息
        """
        self.stop_poll = False
        if not self.poll_and_heart:
            self.login_time = time.time()
            logger.info("开始拉取信息")
            self.load_next_request(PollMessageRequest())
            self.poll_and_heart = True
            if self.hThread is None:
                logger.info("开始心跳")
                self.hThread = threading.Thread(target=self._heartbeat)
                self.hThread.setDaemon(True)
                self.hThread.start()

    def _heartbeat(self):
        """ 放入线程的产生心跳
        """
        assert not isinstance(threading.currentThread(), threading._MainThread)
        while 1:
            try:
                self.load_next_request(HeartbeatRequest())
            except:
                pass
            time.sleep(60)

    def make_msg_content(self, content, style):
        """ 构造QQ消息的内容

        :param content: 小心内容
        :type content: str
        :rtype: str
        """
        self.msg_id += 1
        return json.dumps([content, ["font", style]])

    def get_delay(self, content):
        """ 根据消息内容是否和上一条内容相同和未送出的消息数目产生延迟

        :param content: 消息内容
        :rtype: tuple(delay, number)
        """
        MIN = self.message_interval
        delay = 0
        sub = time.time() - self.last_msg_time
        if self.last_msg_numbers < 0:
            self.last_msg_numbers = 0

        # 不足最小间隔就补足最小间隔
        if sub < MIN:
            delay = MIN
            logger.debug(u"间隔 %s 小于 %s, 设置延迟为%s", sub, MIN, delay)

        # 如果间隔是已有消息间隔的2倍, 则清除已有消息数
        # print "sub", sub, "n:", self.last_msg_numbers
        if self.last_msg_numbers > 0 and\
                sub / (MIN * self.last_msg_numbers) > 1:
            self.last_msg_numbers = 0

        # 如果还有消息未发送, 则加上他们的间隔
        if self.last_msg_numbers > 0:
            delay += MIN * self.last_msg_numbers
            logger.info(u"有%s条消息未发送, 延迟为 %s", self.last_msg_numbers, delay)

        n = 1
        # 如果这条消息和上条消息一致, 保险起见再加上一个最小间隔
        if self.last_msg_content == content and sub < MIN:
            delay += MIN
            self.last_msg_numbers += 1
            n = 2

        self.last_msg_numbers += 1
        self.last_msg_content = content

        if delay:
            logger.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递".format(
                delay, self.last_msg_numbers))
        # 返回消息累加个数, 在消息发送后减去相应的数目
        return delay, n

    def consume_delay(self, number):
        """ 消费延迟

        :param number: 消费的消息数目
        """
        self.last_msg_numbers -= number
        self.last_msg_time = time.time()

    def get_group_id(self, uin):
        """ 根据组uin获取组的id

        :param uin: 组的uin
        """
        return self.get_groups().get_gid(uin)

    def get_friend_name(self, uin):
        """ 获取好友名称

        :param uin: 好友uin
        """
        return self.get_friends().get_show_name()

    def wrap(self, request, func=None):
        """ 装饰callback

        :param request: ~twqq.requests.WebQQRequest instance
        :param func: 回调函数
        """
        def _wrap(resp, *args, **kwargs):
            data = resp.body
            logger.debug(resp.headers)
            if resp.headers.get("Content-Type") == "application/json":
                data = json.loads(data) if data else {}
            else:
                try:
                    data = json.loads(data)
                except:
                    pass
            if func:
                func(resp, data, *args, **kwargs)

            funcs = self.client.request_handlers.get(check_request(request),
                                                     [])
            for f in funcs:
                f(request, resp, data)

        return _wrap

    def handle_qq_msg_contents(self, from_uin, contents, eid=None, _type=0):
        """ 处理QQ消息内容

        :param from_uin: 消息发送人uin
        :param contents: 内容
        :param eid: 扩展id(群gid, 讨论组did)
        :type contents: list

        """
        content = ""
        for row in contents:
            if isinstance(row, (list)) and len(row) == 2:
                info = row[1]
                if row[0] == "offpic":
                    file_path = info.get("file_path")
                    content += self.get_msg_img(from_uin, file_path)

                if row[0] == "cface":
                    name = info.get("name")
                    key = info.get("key")
                    file_id = info.get("file_id")
                    server = info.get("server")
                    content += self.get_group_img(eid, from_uin, file_id,
                                                  server, name, key, _type)

            if isinstance(row, (str, unicode)):
                content += row.replace(u"【提示:此用户正在使用Q+"
                                       u" Web:http://web.qq.com/】", "")\
                    .replace(u"【提示:此用户正在使用Q+"
                             u" Web:http://web3.qq.com/】", "")
        return content.replace("\r", "\n").replace("\r\n", "\n")\
            .replace("\n\n", "\n")

    def get_group_member_nick(self, gcode, uin):
        """ 根据组代码和用户uin获取群成员昵称

        :param gcode: 组代码
        :param uin: 群成员uin
        """
        return self.get_groups().get_member_nick(gcode, uin)

    def dispatch(self, qq_source):
        """ 调度QQ消息

        :param qq_source: 源消息包
        """
        if self.stop_poll:
            logger.info("检测Poll已停止, 此消息不处理: {0}".format(qq_source))
            return

        if qq_source.get("retcode") == 0:
            messages = qq_source.get("result")
            logger.info(u"获取消息: {0}".format(messages))
            for m in messages:
                poll_type = m.get("poll_type")
                if poll_type == "buddies_status_change":
                    self.get_friends().set_status(**m.get("value", {}))
                else:
                    funcs = self.client.msg_handlers.get(
                        m.get("poll_type"), [])
                    [func(*func._args_func(self, m)) for func in funcs]

    def recv_file(self, guid, lcid, to, callback):
        """ 接收文件

        :param guid: 文件名
        :param lcid: 会话id
        :param to_uin: 发送人uin
        :param callback:  回调, 接收两个参数, 分别是文件名和文件内容
        """
        self.load_next_request(FileRequest(guid, lcid, to, callback))

    def relogin(self):
        """ 被T出或获取登出时尝试重新登录
        """
        self.stop_poll = True
        self.poll_and_heart = None
        self.load_next_request(Login2Request(relogin=True))

    def disconnect(self):
        self.stop_poll = True
        self.poll_and_heart = None
        self.load_next_request(LogoutRequset())

    def send_sess_msg(self, qid, to_uin, content, style=const.DEFAULT_STYLE):
        """ 发送临时消息

        :param qid: 发送临时消息的qid
        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(
            SessMsgRequest(qid, to_uin, content, style))

    def send_group_msg(self, group_uin, content, style=const.DEFAULT_STYLE):
        """ 发送群消息

        :param group_uin: 组的uin
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(
            GroupMsgRequest(group_uin, content, style))

    def send_discu_msg(self, did, content, style=const.DEFAULT_STYLE):
        """ 发送讨论组消息

        :param did: 讨论组id
        :param content: 内容
        """
        return self.load_next_request(DiscuMsgRequest(did, content, style))

    def send_buddy_msg(self, to_uin, content, style=const.DEFAULT_STYLE):
        """ 发送好友消息

        :param to_uin: 消息接收人
        :param content: 消息内容
        :rtype: Request instance
        """
        return self.load_next_request(BuddyMsgRequest(to_uin, content, style))

    def send_msg_with_markname(self, markname, content):
        """ 使用备注名发送消息

        :param markname: 备注名
        :param content: 消息内容
        :rtype: None or Request instance
        """
        uin = self.get_friends().get_uin_from_mark(markname)
        if not uin:
            return
        return self.send_buddy_msg(uin, content)

    def accept_verify(self, uin, account, markname=""):
        """ 同意验证请求

        :param  uin: 请求人uin
        :param account: 请求人账号
        :param markname: 添加后的备注
        """
        return self.load_next_request(
            AcceptVerifyRequest(uin, account, markname))

    def refresh_friend_info(self):
        self.load_next_request(FriendListRequest(manual=True))

    def refresh_group_info(self, _id):
        """ 手动刷新某个群的信息

        :param _id: 对应群生成的唯一id
        """
        gcode, _type = objects.UniqueIds.get(int(_id))
        if gcode is None or _type is None:
            return False, u"没有找到对象"

        if _type != objects.UniqueIds.T_GRP:
            return False, u"该对象不是群"

        self.load_next_request(GroupMembersRequest(gcode))
        return True, self.get_groups().get_group_name(gcode)
Пример #30
0
class WebQQ(object):
    def __init__(self, qid, pwd):
        self.qid = qid               # QQ 号
        self.__pwd = pwd             # QQ密码
        self.nickname = None         # 初始化QQ昵称
        self.http = TornadoHTTPClient()
        self.http.set_user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/28.0.1500.71 Chrome/28.0.1500.71 Safari/537.36")
        self.http.debug = DEBUG
        self.http.validate_cert = False
        self.http.set_global_headers({"Accept-Charset": "UTF-8,*;q=0.5"})
        self.msg_dispatch = MessageDispatch(self)

        self.rc = random.randrange(0, 100)

        self.aid = 1003903                                    # aid 固定
        self.clientid = random.randrange(11111111, 99999999)  # 客户端id 随机固定
        self.msg_id = random.randrange(1111111, 99999999)     # 消息id, 随机初始化

        self.require_check = False   # 是否需要验证码
        self.poll_and_heart = False  # 开始拉取消息和心跳

        # 初始化WebQQ登录期间需要保存的数据
        self.check_code = None
        self.ptwebqq = None

        self.check_data = None       # 初始化检查时返回的数据
        self.blogin_data = None      # 初始化登录前返回的数据

        self.friend_info = {}        # 初始化好友列表
        self.group_info = {}         # 初始化组列表
        self.group_sig = {}          # 组签名映射, 用作发送临时消息(sess_message)
        self.group_members_info = {} # 初始化组成员列表

        self.hb_time = int(time.time() * 1000)
        self.daid = 164
        self.login_sig = None

        self.login_time = None       # 登录的时间
        self.last_group_msg_time = time.time()
        self.last_msg_content = None
        self.last_msg_numbers = 0    # 剩余位发送的消息数量
        self.base_header = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1"}


    def ptuiCB(self, scode, r, url, status, msg, nickname = None):
        """ 模拟JS登录之前的回调, 保存昵称 """
        if int(scode) == 0:
            logging.info("从Cookie中获取ptwebqq的值")
            self.ptwebqq = self.http.cookie['.qq.com']['/']['ptwebqq'].value
            self.logined = True
        elif int(scode) == 4:
            logging.error(msg)
            self.check()
        else:
            logging.error(u"server response: {0}".format(msg.decode('utf-8')))
            exit(2)

        if nickname:
            self.nickname = nickname


    def get_group_member_nick(self, gcode, uin):
        return self.group_members_info.get(gcode, {}).get(uin, {}).get("nick")

    def get_uptime(self):
        MIN = 60
        HOUR = 60 * MIN
        DAY = 24 * HOUR
        up_time = datetime.fromtimestamp(self.login_time).strftime("%H:%M:%S")

        now = time.time()
        sub = now - self.login_time

        days = int(sub / DAY)
        hours = int(sub / HOUR)
        mins = int(sub / MIN)

        if mins:
            num = mins
            unit = "min"

        if hours:
            num = hours
            unit = "hours" if hours > 1 else "hour"

        if days:
            num = days
            unit = "days" if days > 1 else "day"

        if not days and not mins and not hours:
            num = int(sub)
            unit = "sec"

        return "{0} up {1} {2}".format(up_time, num, unit)


    def get_login_sig(self):
        logging.info("获取 login_sig...")
        url = "https://ui.ptlogin2.qq.com/cgi-bin/login"
        params = [("daid", self.daid), ("target", "self"), ("style", 5),
                  ("mibao_css", "m_webqq"), ("appid", self.aid),
                  ("enable_qlogin", 0), ("no_verifyimg", 1),
                  ("s_url", "http://web2.qq.com/loginproxy.html"),
                  ("f_url", "loginerroralert"),
                  ("strong_login", 1), ("login_state", 10),
                  ("t", "20130723001")]
        self.http.get(url, params, callback = self._get_login_sig)
        self.http.get("http://web2.qq.com")

    def _get_login_sig(self, resp):
        sigs = SIG_RE.findall(resp.body)
        if len(sigs) == 1:
            self.login_sig = sigs[0]
            logging.info(u"获取Login Sig: {0}".format(self.login_sig))
        else:
            logging.warn(u"没有获取到 Login Sig, 后续操作可能失败")
            self.login_sig = ""

        self.check()



    def check(self):
        """ 检查是否需要验证码
        url :
            https://ssl.ptlogin2.qq.com/check
        方法:   GET
        参数:
            {
                uin     // qq号
                appid   // 程序id 固定为1003903
                r       // 随机数
                u1      // http://web2.qq.com/loginproxy.html
                js_ver  // 10040
                js_type // 0
            }
        返回:
            ptui_checkVC('0','!PTH','\x00\x00\x00\x00\x64\x74\x8b\x05');
            第一个参数表示状态码, 0 不需要验证, 第二个为验证码, 第三个为uin
        """

        logging.info(u"检查是否需要验证码...")
        #url = "https://ssl.ptlogin2.qq.com/check"
        url = "http://check.ptlogin2.qq.com/check"
        params = {"uin":self.qid, "appid":self.aid,
                  "u1": "http://web2.qq.com/loginproxy.html",
                  "login_sig":self.login_sig, "js_ver":10040,
                  "js_type":0, "r" : random.random()}
        headers = {"Referer":"https://ui.ptlogin2.qq.com/cgi-bin/login?daid="
                   "164&target=self&style=5&mibao_css=m_webqq&appid=1003903&"
                   "enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb2.q"
                   "q.com%2Floginproxy.html&f_url=loginerroralert&strong_log"
                   "in=1&login_state=10&t=20130723001"}
        self.http.get(url, params, headers = headers, callback = self.handle_verify)

        cookie_url = "http://www.simsimi.com/talk.htm?lc=ch"
        cookie_params = (("lc", "ch"),)
        headers = {"Referer": "http://www.simsimi.com/talk.htm"}
        self.http.get(cookie_url, cookie_params, headers = headers)

        headers = {"Referer": "http://www.simsimi.com/talk.htm?lc=ch"}
        self.http.get("http://www.simsimi.com/func/langInfo",
                             cookie_params, headers = headers)


    def handle_pwd(self, r, vcode, huin):
        """ 根据检查返回结果,调用回调生成密码和保存验证码 """
        pwd = md5(md5(self.__pwd).digest() + huin).hexdigest().upper()
        pwd = md5(pwd + vcode).hexdigest().upper()
        return pwd


    def get_check_img(self, r, vcode, uin):
        """ 获取验证图片 """

        def callback(resp):
            path = os.path.join(os.path.abspath(os.path.dirname(__file__)),
                                                "check.jpg")
            fp = open(path, 'wb')
            fp.write(resp.body)
            fp.close()
            if UPLOAD_CHECKIMG:
                res = upload_file("check.jpg", path)
                path = res.url
            print u"验证图片: {0}".format(path)
            check_code = ""
            while not check_code:
                check_code = raw_input("输入验证图片上的验证码: ")
            ccode = check_code.strip().lower()
            self.check_code = ccode
            pwd = self.handle_pwd(r, ccode.upper(), uin)
            self.before_login(pwd)

        url = "https://ssl.captcha.qq.com/getimage"
        params = [("aid", self.aid), ("r", random.random()),
                ("uin", self.qid)]
        self.http.get(url, params, callback = callback)


    def handle_verify(self, resp):
        ptui_checkVC = lambda r, v, u: (r, v, u)
        r, vcode, uin = eval(resp.body.strip().rstrip(";"))
        if int(r) == 0:
            logging.info("验证码检查完毕, 不需要验证码")
            password = self.handle_pwd(r, vcode, uin)
            self.before_login(password)
            self.check_code = vcode
        else:
            logging.warn("验证码检查完毕, 需要验证码")
            self.get_check_img(r, vcode, uin)
            self.require_check = True


    def before_login(self, password):
        """ 登录之前的操作
        url:
            https://ssl.ptlogin2.qq.com/login
        方法:   GET
        参数:
            {
                u       // qq号码
                p       // 经过处理的密码
                verifycode  // 验证码
                webqq_type  // 固定为10
                remember_uin    // 是否记住qq号, 传1 即可
                login2qq        // 登录qq, 传1
                aid             // appid 固定为 1003903
                u1              // 固定为 http://www.qq.com
                h               // 固定为1
                ptrediect       // 固定为0
                ptlang          // 固定为2052
                from_ui         // 固定为 1
                pttype          // 固定为1
                dumy            // 固定为空
                fp              // 固定为loginerroralert ( 重要)
                mibao_css       // 固定为 m_webqq
                t               // 固定为1
                g               // 固定为
                js_type         // 固定为0
                js_ver          // 固定为10021
        其他:
            如果check步骤验证了需要验证码, 需加上 Referer头 值为:
            https://ui.ptlogin2.qq.com/cgi-bin/login?target=self&style=5&mibao_css=m_webqq&appid=1003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Floginproxy.html&f_url=loginerroralert&strong_login=1&login_state=10&t=20130221001

        接口返回:
            ptuiCB('0','0','http://www.qq.com','0','登录成功!', 'nickname');
        先检查是否需要验证码,不需要验证码则首先执行一次登录
        然后获取Cookie里的ptwebqq保存在实例里,供后面的接口调用
        """
        url = "https://ssl.ptlogin2.qq.com/login"
        params = [("u",self.qid), ("p",password), ("verifycode", self.check_code),
                  ("webqq_type",10), ("remember_uin", 1),("login2qq",1),
                  ("aid", self.aid), ("u1", "http://www.qq.com/loginproxy.h"
                                      "tml?login2qq=1&webqq_type=10"),
                  ("h", 1), ("action", 4-5-8246),
                  ("ptredirect", 0), ("ptlang", 2052), ("from_ui", 1),
                  ("daid", self.daid),
                  ("pttype", 1), ("dumy", ""), ("fp", "loginerroralert"),
                  ("mibao_css","m_webqq"), ("t",1), ("g",1), ("js_type",0),
                  ("js_ver", 10040), ("login_sig", self.login_sig)]
        headers = {}
        if self.require_check:
            headers.update(Referer =  "https://ui.ptlogin2.qq.com/cgi-"
                            "bin/login?target=self&style=5&mibao_css=m_"
                            "webqq&appid=1003903&enable_qlogin=0&no_ver"
                            "ifyimg=1&s_url=http%3A%2F%2Fweb.qq.com%2Fl"
                            "oginproxy.html&f_url=loginerroralert&stron"
                            "g_login=1&login_state=10&t=20130221001")
        logging.info("检查完毕, 开始登录前准备")
        self.http.get(url, params, headers = headers, callback = self.login0)


    def login0(self, resp):
        logging.info("开始登录前准备...")
        blogin_data = resp.body.decode("utf-8").strip().rstrip(";")
        eval("self." + blogin_data)

        location1 = re.findall(r'ptuiCB\(\'0\'\,\'0\'\,\'(.*)\'\,\'0\'\,',
                               blogin_data)[0]
        params = []
        header = {"Referer": "https://ui.ptlogin2.qq.com/cgi-bin/login?d"
                  "aid=164&target=self&style=5&mibao_css=m_webqq&appid=1"
                  "003903&enable_qlogin=0&no_verifyimg=1&s_url=http%3A%2"
                  "F%2Fweb2.qq.com%2Floginproxy.html&f_url=loginerrorale"
                  "rt&strong_login=1&login_state=10&t=20130723001"}
        self.http.get(location1, params, headers = header,
                             callback = self.get_location1)

    def get_location1(self, resp):
        if resp.code == 302:
            location2 = resp.headers.get("Location")
            params = []
            header = {}
            self.http.get(location2, params, headers = header,
                                 callback = self.get_location1)
        else:
            logging.info("准备完毕, 开始登录")
            self.login()


    def login(self):
        """ 获取登录前的数据, 并进行登录
        url:
            http://d.web2.qq.com/channel/login2
        方法: POST
        参数:
            {
                r : {
                    status      // 登录后的状态 ("online")
                    ptwebqq     // 上次请求返回的cookie
                    passwd_sig  // 固定为空
                    clientid    // 随机的clientid
                    psessionid  // 传递 null
                }
                clientid    // 客户端id
                psessionid  // 传递null
            }
        其他:
            需加上 Referer和 Origin 头:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
            "Origin": "http://d.web2.qq.com"

        返回:
            {u'retcode': 0,
            u'result': {
                'status': 'online', 'index': 1075,
                'psessionid': '', u'user_state': 0, u'f': 0,
                u'uin': 1685359365, u'cip': 3673277226,
                u'vfwebqq': u'', u'port': 43332}}
            保存result中的psessionid和vfwebqq供后面接口调用
        """
        time.sleep(4)

        url = "http://d.web2.qq.com/channel/login2"
        params = [("r", '{"status":"online","ptwebqq":"%s","passwd_sig":"",'
                '"clientid":"%d","psessionid":null}'\
                % (self.ptwebqq, self.clientid)),
                ("clientid", self.clientid),
                ("psessionid", "null")
                ]

        headers = { "Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        logging.info("登录准备完毕, 开始登录")
        self.http.post(url, params, headers = headers,
                              callback= self.update_friend)

    def _hash(self):
        a = str(self.qid)
        e = self.ptwebqq
        l = len(e)
        # 将qq号码转换成整形列表
        b, k, d = 0, -1, 0
        for d in a:
            d = int(d)
            b += d
            b %= l
            f = 0
            if b + 4 > l:
                g = 4 + b - l
                for h in range(4):
                    f |= h < g and (
                        ord(e[b + h]) & 255) << (3 - h) * 8 or (
                            ord(e[h - g]) & 255) << (3 - h) * 8
            else:
                for h in range(4):
                    f |= (ord(e[b + h]) & 255) << (3 - h) * 8
            k ^= f
        c = [k >> 24 & 255, k >> 16 & 255, k >> 8 & 255, k & 255]
        import string
        k = list(string.digits) + ['A', 'B', 'C', 'D', 'E', 'F']
        d = [k[b >> 4 & 15] + k[b & 15] for b in c]
        return ''.join(d)


    def update_friend(self, resp, login = True):
        """ 更新好友列表
        URL:
            http://s.web2.qq.com/api/get_user_friends2
        METHOD:
            POST
        PARAMS:
            {r:{"h":"hello", "vfwebqq":""}}
        HEADER:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """

        data = json.loads(resp.body)
        if login:
            if data.get("retcode") != 0:
                logging.error("登录失败")
                exit(2)
            self.vfwebqq = data.get("result", {}).get("vfwebqq")
            self.psessionid = data.get("result", {}).get("psessionid")


        url = "http://s.web2.qq.com/api/get_user_friends2"
        params = [("r", json.dumps({"h":"hello", "hash":self._hash(),
                                    "vfwebqq":self.vfwebqq}))]
        headers = {"Referer":
            "http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1"}

        callback = self.update_friend

        if login:
            logging.info("登录成功")
            if not DEBUG:
                aw = ""
                while aw.lower() not in ["y", "yes", "n", "no"]:
                    aw = raw_input("是否将程序至于后台[y] ")
                    if not aw:
                        aw = "y"

                if aw in ["y", "yes"]:
                    run_daemon(self.http.post, args = (url, params),
                               kwargs = dict(headers = headers,
                                             kwargs = dict(login = False),
                                             callback = callback))
                    return

            self.http.post(url, params, headers = headers,
                           callback = callback, kwargs = dict(login = False))
        else:
            logging.info("加载好友信息")
            lst = data.get("result", {}).get("info", [])
            for info in lst:
                uin = info.get("uin")
                self.friend_info[uin] = info
            self.update_group()

            self.http.post(url, params, headers = self.base_header,
                                  delay = 300, callback = callback)


    def update_group(self, resp = None):
        """ 获取组列表, 并获取组成员
        获取组列表:
            url:
                http://s.web2.qq.com/api/get_group_name_list_mask2
            method:
                POST
            params:
                {
                    r : {
                        vfwebqq     // 登录前返回的cookie值
                    }
                }

        """
        logging.info("获取群列表")
        url = "http://s.web2.qq.com/api/get_group_name_list_mask2"
        params = [("r", '{"vfwebqq":"%s"}' % self.vfwebqq),]
        headers = {"Origin": "http://s.web2.qq.com"}
        headers.update(self.base_header)
        self.http.post(url, params, headers = headers,
                              callback = self.group_members)


    def group_members(self, resp):
        """ 获取群列表, 获取群列表中的成员
        url: http://s.web2.qq.com/api/get_group_info_ext2
        method: GET
        params:
            {
                gcode           // 群代码
                vfwebqq         // 登录前的cookie值
                t               // int(time.time())
            }
        headers:
            "Referer":
            "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3"
        """
        logging.info("加载组成员信息")
        data = json.loads(resp.body)
        group_list = data.get("result", {}).get("gnamelist", [])
        if not group_list:
            self.heartbeat(0)
            self.poll()
        for i, group in enumerate(group_list):
            gcode = group.get("code")
            url = "http://s.web2.qq.com/api/get_group_info_ext2"
            params = [("gcode", gcode),("vfwebqq", self.vfwebqq),
                    ("t", int(time.time()))]
            callback = self.do_group_members
            if i == len(group_list) -1 :
                kwargs = dict(gcode = gcode, last = True)
            else:
                kwargs = dict(gcode = gcode)

            self.http.get(url, params, headers = self.base_header,
                          callback = callback, kwargs = kwargs)
            self.group_info[gcode] = group


    def do_group_members(self, resp, gcode, last = False):
        """ 获取群成员数据 """
        data = json.loads(resp.body)
        members = data.get("result", {}).get("minfo", [])
        self.group_members_info[gcode] = {}
        for m in members:
            uin = m.get("uin")
            self.group_members_info[gcode][uin] = m

        cards = data.get("result", {}).get("cards", [])

        for card in cards:
            uin = card.get("muin")
            group_name = card.get("card")
            self.group_members_info[gcode][uin]["nick"] = group_name


        if last and not self.poll_and_heart:
            logging.info("万事具备,开始拉取信息和心跳")
            self.poll()
            self.heartbeat(0)


    def poll(self):
        """ 建立长连接获取消息
        url:http://d.web2.qq.com/channel/poll2
        方法: POST
        参数:
            {
                r:{
                    clientid       // 客户端id
                    psessionid     // session id
                    key             // 固定为0
                    ids             // 固定为 []
                }
                clientid
                psessionid
            }

        头部:
            "Referer": "http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=2"
        """
        if not self.poll_and_heart:
            self.poll_and_heart = True
        url = "http://d.web2.qq.com/channel/poll2"
        rdic = {"clientid": self.clientid, "psessionid": self.psessionid,
                "key": 0, "ids":[]}
        params = [("r", json.dumps(rdic)), ("clientid", self.clientid),
                ("psessionid", self.psessionid)]
        headers = {"Referer":"https://d.web2.qq.com/cfproxy.html?v=20110331002&callback=1",
                   "Origin":"http://d.web2.qq.com"}

        self.http.post(url, params, headers = headers, request_timeout = 60.0,
                       connect_timeout = 60.0, callback = self.handle_msg)


    def handle_msg(self, resp):
        """ 处理消息 """
        self.poll()
        data = resp.body
        try:
            msg = json.loads(data)
            if msg.get("retcode") in [121, 103]:
                logging.error(u"登录失败")
                return
            logging.info(u"获取消息: {0!r}".format(msg))
            self.msg_dispatch.dispatch(msg)
        except ValueError:
            traceback.print_exc()
            logging.error(u"消息加载失败: %s", data)


    def heartbeat(self, delay = 60):
        """ 开始心跳
        url:http://web.qq.com/web2/get_msg_tip
        方法: GET
        参数:
            {
                uin  // 固定为空
                tp   // 固定为1
                rc   // 固定为1
                id   // 固定位0
                lv   // 固定为2
                t    // 开始的心跳时间(int(time.time()) * 1000)
            }
        """
        logging.info("心跳..")
        self.login_time = time.time()

        if not self.poll_and_heart:
            self.poll_and_heart = True

        url = "http://web.qq.com/web2/get_msg_tip"
        params = [("uin", ""), ("tp", 1), ("id", 0), ("retype", 1),
                    ("rc", self.rc), ("lv", 3),
                ("t", int(self.hb_time * 1000))]
        self.rc += 1

        self.http.get(url, params, callback = self.hb_next, delay = delay)


    def hb_next(self, resp):
        """ 持续心跳 """
        self.heartbeat()


    def make_msg_content(self, content):
        """ 构造QQ消息的内容 """
        self.msg_id += 1
        return json.dumps([content, ["font", {"name":"Monospace", "size":10,
                                   "style":[0, 0, 0], "color":"000000"}]])


    def get_sess_group_sig(self, to_uin, callback):
        """ 获取临时消息组签名
        URL: http://d.web2.qq.com/channel/get_c2cmsg_sig2
        METHOD: GET
        PARAMS:
            id   // 请求ID 固定为833193360
            to_uin   // 消息接受人uin( 消息的from_uin)
            service_type   // 固定为0
            clientid       // 客户端id
            psessionid     // session id
            t              // 当前时间秒1370671760656
        HEADERS:
        Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        url = "http://d.web2.qq.com/channel/get_c2cmsg_sig2"
        params = (("id", 833193360), ("to_uin", to_uin), ("service_type", 0),
                  ("clientid", self.clientid), ("psessionid", self.psessionid),
                  ("t", time.time()))


        def callback(resp):
            data = resp.body
            r = json.loads(data)
            result = r.get("result")
            group_sig = result.get("value")
            if r.get("retcode") != 0:
                logging.warn(u"加载临时消息签名失败: {0}".format(group_sig))
                return
            try:
                logging.info("加载临时消息签名 {0} for {1}".format(group_sig, to_uin))
            except UnicodeError:
                return
            self.group_sig[to_uin] = group_sig
            callback()

        self.http.get(url, params, callback = callback, headers = self.base_header)


    def send_sess_msg(self, to_uin, content):
        """ 发送临时消息
        URL:http://d.web2.qq.com/channel/send_sess_msg2
        METHOD: POST
        PARAMS:
            r:{
                to              // 消息接收人 uin
                group_sig       // 组签名
                face            // 固定为 564,
                content         // 发送内容
                msg_id          // 消息id
                service_type    // 固定为0,
                clientid        // 客户端id
                psessionid      // sessionid
                }
            clientid                // 客户端id
            psessionid              // sessionid
        Headers:
            self.base_header
        """
        group_sig = self.group_sig.get(to_uin)
        if not group_sig:
            callback = partial(self.send_sess_msg, to_uin, content)
            return self.get_sess_group_sig(to_uin, callback)

        logging.info(u"发送临时消息 {0} 到 {1}".format(content, to_uin))
        delay, n = self.get_delay(content)
        content = self.make_msg_content(content)
        url = "http://d.web2.qq.com/channel/send_sess_msg2"
        params = (("r", json.dumps({"to":to_uin, "group_sig":group_sig,
                                    "face":564, "content":content,
                                    "msg_id": self.msg_id, "service_type":0,
                                    "clientid":self.clientid,
                                    "psessionid":self.psessionid})),
                  ("clientid", self.clientid), ("psessionid", self.psessionid))
        def callback(resp):
            self.last_msg_numbers -= n
        self.http.post(url, params, headers = self.base_header,
                              callback = callback, delay = delay)


    def send_buddy_msg(self, to_uin, content):
        """ 发送好友消息
        URL:
            http://d.web2.qq.com/channel/send_buddy_msg2

        METHOD:
            POST

        PARAMS:
            {
                "r":{
                    "to"            // 好友uin
                    "face"          // 固定为564
                    "content"       // 发送内容
                    "msg_id"        // 消息id, 每发一条递增
                    "clientid"      // 客户端id
                    "psessionid"    // sessionid
                    }
                "clientid":clientid,
                "psessionid": psessionid,
            }

        HEADERS:
            Referer:http://d.web2.qq.com/proxy.html?v=20110331002&callback=1&id=3
        """
        logging.info(u"发送好友消息 {0} 给 {1}".format(content, to_uin))
        content = self.make_msg_content(content)

        url = "http://d.web2.qq.com/channel/send_buddy_msg2"

        r = {"to":to_uin, "face":564, "content":content,
             "clientid":self.clientid, "msg_id": self.msg_id,
             "psessionid": self.psessionid}
        params = [("r",json.dumps(r)), ("clientid",self.clientid),
                  ("psessionid", self.psessionid)]
        headers = {"Origin": "http://d.web2.qq.com"}
        headers.update(self.base_header)
        delay, n = self.get_delay(content)
        def callback(resp):
            self.last_msg_numbers -= n

        self.http.post(url, params, headers = headers, delay = delay,
                              callback = callback)


    def send_group_msg(self, group_uin, content):
        """ 发送群消息
        url:http://d.web2.qq.com/channel/send_qun_msg2
        方法: POST
        参数:
            {
                r:{
                    group_uin           // gid
                    content             // 发送内容
                    msg_id              // 消息id, 每次发送消息应该递增
                    clientid            // 客户端id
                    psessionid          // sessionid
                }
                clientid
                psessionid
            }
        """
        gid = self.group_info.get(group_uin, {}).get("gid")
        source = content
        content = self.make_msg_content(source)

        url = "http://d.web2.qq.com/channel/send_qun_msg2"
        r = {"group_uin": gid, "content": content,
            "msg_id": self.msg_id, "clientid": self.clientid,
            "psessionid": self.psessionid}
        params = [("r", json.dumps(r)), ("psessionid", self.psessionid),
                ("clientid", self.clientid)]

        delay, n = self.get_delay(content)
        callback = self.send_group_msg_back


        self.http.post(url, params, headers = self.base_header,
                       callback = callback, args = (source, group_uin, n),
                       delay = delay)


    def get_delay(self, content):
        MIN = MESSAGE_INTERVAL
        delay = 0

        if time.time() - self.last_group_msg_time < MIN or\
           self.last_msg_numbers > 0:
            delay = self.last_msg_numbers * MIN

        numbers = 1
        if self.last_msg_content == content:
            delay += 0.5
            self.last_msg_numbers += 1
            numbers = 2
        self.last_msg_numbers += 1
        self.last_msg_content = content
        if delay:
            logging.info(u"有 {1} 个消息未投递将会在 {0} 秒后投递"
                         .format(delay, self.last_msg_numbers))
        return delay, numbers


    def send_group_msg_back(self, content, group_uin, n, resp):
        logging.info(u"发送群消息 {0} 到 {1}".format(content, group_uin))
        self.last_group_msg_time = time.time()
        if self.last_msg_numbers > 0:
            self.last_msg_numbers -= n


    def set_signature(self, signature, password, callback):
        """ 设置QQ签名,
        可以通过发送好友消息设置签名, 消息应按照如下格式:
            设置签名:[密码]|[签名内容]    // 密码和签名内容不能包含分割符
        url: http://s.web2.qq.com/api/set_long_nick2
        method: POST
        params:
                r : {
                    nlk         // 签名内容
                    vfwebqq     // 登录时获取的cookie值
                }
        headers:
            Referer:http://s.web2.qq.com/proxy.html?v=20110412001&callback=1&id=1
        """
        if password != Set_Password:
            return callback(u"你没有权限这么做")

        logging.info(u"设置QQ签名 {0}".format(signature))

        url = "http://s.web2.qq.com/api/set_long_nick2"
        params = (("r", json.dumps({"nlk":signature, "vfwebqq":self.vfwebqq})),)
        headers = {"Origin":"http://s.web2.qq.com"}
        headers.update(self.base_header)

        def callback(resp):
            data = resp.body
            print data
            result = json.loads(data).get("retcode")
            if result == 0:
                callback(u"设置成功")
            else:
                callback(u"设置失败")
        self.http.post(url, params, headers = headers, callback = callback)


    def run(self):
        self.get_login_sig()
        self.http.start()
Пример #31
0
 def setUp(self):
     super(TornadoHTTPClientCoroutineTest, self).setUp()
     self.http = TornadoHTTPClient()
     self.http.debug = True
Пример #32
0
 def __init__(self, message_bus):
     self._message_bus = message_bus  # 消息总线
     self._logger = get_logger()  # 日志
     self._http_stream = TornadoHTTPClient()
     self._honor = Honor()