[
  {
    "path": ".gitignore",
    "content": "*.pyc\n*.json\n*.jpg\nauth.py\n__pycache__\n*.log\n.idea"
  },
  {
    "path": "README.md",
    "content": "# 微信聊天机器人 - 个人账号版\n\n这是一个基于微信网页版接口的，个人账号聊天机器人。\n\n不同于微信公众号和订阅号，个人账号腾讯并没有提供 API 接口，所以只能模拟微信网页版的协议来做。由于使用未公开通信协议，故不能保证向后兼容性。\n\n* wechat-asyncio 是异步实现的。需要 Python 3.5。\n* wechat-draft 是很早的第一个可用的版本。只有两个线程。只需要 > Python 3。\n\n## 简单说明\n\n### 依赖\n\n申请一枚图灵机器人 API，新建 auth.py，并写入 \n\n    apikey = 'xxxxxxxxxxx'\n\n* pip3 install aiohttp\n\n### 快速开始\n\n目前使用起来还不友好，且登陆有一定失败率。\n\n    (terminal 1) python3 main.py \n\n运行后会在当前文件夹内下载二维码，根据终端提示做即可。\n\n1. 若 10s 内二维码下载失败则重来。\n2. 若 wx.log 提示 sync 失败则重来。\n\n### 参数调整\n\n在 config.py 中可以对各个时间间隔做调整。\n\n## Changelog\n\n### 2016.2.4\n\n改成了基于 asyncio/aiohttp 的异步框架。现在只有 5 个任务：sync, sendmsg, updategroupinfo, msgloop, monitor。\n\n### 2016.1.23\n\nwechat-robot.py 改用 Python3。原来的 urllib2/urllib 全部改为了 requests。\n\n\n## 参考\n\n前期参考了 [查看被删的微信好友](https://github.com/0x5e/wechat-deleted-friends)。\n\n也参考了 [Nodejs 版的微信聊天机器人](https://github.com/HalfdogStudio/wechat-user-bot)。\n"
  },
  {
    "path": "wechat-asyncio/HttpClient.py",
    "content": "# coding=utf-8\n\nimport logging\nimport asyncio\nimport aiohttp\nimport json\n\nlogger = logging.getLogger('wx')\n\n\nclass HttpClient:\n    def __init__(self, client):\n        if not isinstance(client, aiohttp.ClientSession):\n            raise TypeError('Please init with a aiohttp.ClientSession instance')\n        self.__client = client\n        self.__cookies = None\n\n    async def get(self, url, params=None):\n        try:\n            self.__cookies = self.__client.cookies\n            async with await self.__client.get(url, params=params) as r:\n                #assert r.status == 200\n                return await r.text()\n\n        except Exception:\n            logger.exception(\"Network Exception, url: %s, params: %s\" % (url, params))\n            return None\n\n    async def get_json(self, url, params=None):\n        try:\n            self.__cookies = self.__client.cookies\n            async with await self.__client.get(url, params=params) as r:\n                text = await r.text(encoding='utf-8')\n                return json.loads(text)\n\n        except Exception:\n            logger.exception(\"Network Exception, url: %s, params: %s\" % (url, params))\n            return None\n\n    async def get_json_timeout(self, url, params=None):\n        try:\n            self.__cookies = self.__client.cookies\n            with aiohttp.Timeout(2):\n                async with await self.__client.get(url, params=params) as r:\n                    text = await r.text(encoding='utf-8')\n                    return json.loads(text)\n\n        except Exception:\n            logger.exception(\"Network Exception, url: %s, params: %s\" % (url, params))\n            return None\n\n    async def post(self, url, data, params=None):\n        try:\n            async with await self.__client.post(url, params=params, data=data) as r:\n                #assert r.status == 200\n                return await r.text()\n\n        except Exception:\n            logger.exception(\"Network Exception, url: %s, params: %s\" % (url, params))\n            return None\n\n    async def post_json(self, url, data, params=None):\n        try:\n\n            async with await self.__client.post(url, params=params, data=data) as r:\n                #assert r.status == 200\n                text = await r.text(encoding='utf-8')\n                return json.loads(text)\n\n        except Exception:\n            logger.exception(\"Network Exception, url: %s, params: %s\" % (url, params))\n            return None\n\n    async def post_json_timeout(self, url, data, params=None):\n        try:\n\n            with aiohttp.Timeout(2):\n                async with await self.__client.post(url, params=params, data=data) as r:\n                    #assert r.status == 200\n                    text = await r.text(encoding='utf-8')\n                    return json.loads(text)\n\n        except Exception:\n            logger.exception(\"Network Exception, url: %s, params: %s\" % (url, params))\n            return None\n\n    async def downloadfile(self, url, data, filename):\n        try:\n            async with await self.__client.post(url, data=data) as r:\n                #assert r.status == 200\n                with open(filename, 'wb') as fd:\n                    while True:\n                        chunk = await r.content.read(512)\n                        if not chunk:\n                            break\n                        fd.write(chunk)\n                return True\n        except aiohttp.errors.DisconnectedError:\n            return False\n"
  },
  {
    "path": "wechat-asyncio/Monitor.py",
    "content": "# coding=utf-8\n\nimport logging\nimport asyncio\nfrom time import ctime\nimport config\n\nlogger = logging.getLogger('monitor')\n\nclass Monitor():\n    def __init__(self, wx):\n        self.wx = wx\n\n    async def monitor(self):\n        while True:\n            logger.info('Monitoring.......... ' + ctime())\n            logger.info('retcode %s,  selector %s' % (self.wx.retcode, self.wx.selector))\n            logger.info('recvqueue size: %s' % self.wx.recvqueue.qsize())\n            logger.info('sendqueue size: %s' % self.wx.sendqueue.qsize())\n            logger.info('updatequeue size: %s' % self.wx.updatequeue.qsize())\n            logger.info('Monitor end.......... ')\n\n            if self.wx.recvqueue.qsize() > 3:\n                while self.wx.recvqueue.qsize() > 1:\n                    try:\n                        self.wx.recvqueue.get_nowait()\n                    except:\n                        pass\n\n            if self.wx.sendqueue.qsize() > 3:\n                while self.wx.sendqueue.qsize() > 1:\n                    try:\n                        self.wx.sendqueue.get_nowait()\n                    except:\n                        pass\n\n            if self.wx.updatequeue.qsize() > 3:\n                while self.wx.updatequeue.qsize() > 1:\n                    try:\n                        self.wx.updatequeue.get_nowait()\n                    except:\n                        pass\n\n            await asyncio.sleep(config.monitor_interval)\n"
  },
  {
    "path": "wechat-asyncio/MsgHandler.py",
    "content": "# coding=utf-8\n\nimport asyncio\nimport re\n\nimport config\nimport logging\nlogger = logging.getLogger('monitor')\n\nclass MsgHandler:\n    def __init__(self, wx, robot):\n        self.wx = wx\n        self.robot = robot\n\n    async def __parsemsg(self):\n        msg = await self.wx.recvqueue.get()\n        # 自己从别的平台发的消息忽略\n        if msg['FromUserName'] == self.wx.My['UserName']:\n            return None\n        # 排除不是发给自己的消息\n        if msg['ToUserName'] != self.wx.My['UserName']:\n            return None\n        # 在黑名单里面\n        if msg['FromUserName'] in self.wx.blacklist:\n            return None\n\n        msginfo = {}\n        # 文字消息\n        if msg['MsgType'] == 1:\n            content = msg['Content']\n            fromsomeone_NickName = ''\n            ## 来自群消息\n            if msg['FromUserName'].find('@@') != -1:\n                fromsomeone = content[:content.find(':<br/>')]\n                groupname = msg['FromUserName']\n                if groupname not in self.wx.grouplist:\n                    await self.wx.updatequeue.put(groupname)\n                elif fromsomeone in self.wx.grouplist[groupname]:\n                    fromsomeone_NickName = self.wx.grouplist[groupname][fromsomeone]\n                    fromsomeone_NickName = '@' + fromsomeone_NickName + ' '\n                else:\n                    await self.wx.updatequeue.put(groupname)\n                # 去掉消息头部的来源信息\n                content = content[content.find('>')+1:]\n            # 普通消息\n            else:\n                fromsomeone_NickName = ''\n\n            # print (content)\n            if len(content)>1:\n                regx = re.compile(r'@.+?\\u2005')\n                content = regx.sub(' ', content)\n\n            msginfo['Content'] = content\n            msginfo['fromsomeone'] = fromsomeone_NickName\n            msginfo['FromUserName'] = msg['FromUserName']\n\n            return msginfo\n        else:\n            return None\n\n    async def msgloop(self):\n        while True:\n            msginfo = await self.__parsemsg()\n            if msginfo != None:\n                response = {}\n                answser = await self.robot.answser(msginfo)\n                response['Content'] = msginfo['fromsomeone'] + answser\n                response['user'] = msginfo['FromUserName']\n                await self.wx.sendqueue.put(response)\n\n                logger.info(msginfo['fromsomeone'] + ' say: ' + msginfo['Content'])\n                logger.info('Harry Potter say: ' + response['Content'])\n\n            await asyncio.sleep(config.msgloop_interval)\n"
  },
  {
    "path": "wechat-asyncio/RobotEngine.py",
    "content": "# coding=utf-8\n\nimport asyncio\nimport re\nfrom HttpClient import *\nimport RobotPredefinedAnswer\nimport random\n\nclass RobotEngine():\n    def __init__(self, client, apikey):\n        self.rbclient = HttpClient(client)\n        self.apikey = apikey\n        self.acc = 0\n        self.lasttext = ''\n        self.lastuser = ''\n\n    async def answser(self, msginfo):\n        content = msginfo['Content']\n        # 去掉英文，因为图灵机器人不支持\n        content = re.sub(r'[a-zA-Z]', '', content)\n        # 去掉两端空格，不然图灵api那边有问题\n        content = content.strip()\n        # 做一下字数限制\n        content = content[:50]\n        # 做完处理发现没有字符了\n        if content == '':\n            content = self.__randomanswer()\n\n        tuling_data = dict(\n            key=self.apikey,\n            info=content,\n            userid=msginfo['FromUserName'][2:32],  # 把userName的一部分截取,作为userid,这样对话有上下文功能.\n        )\n        tuling_url = 'http://www.tuling123.com/openapi/api'\n        # 使用post方法访问图灵,以免出现content没有经过urlencoded得不到正确结果.\n        dic = await self.rbclient.post_json_timeout(tuling_url, data=tuling_data)\n        if dic is not None:\n            text = dic['text']\n        else:\n            text = '网络异常。。。。。。。。。。。。'\n\n        # 做一下字数回复的限制\n        if len(text)>100:\n            text = text[:100]\n            text = text + '......'\n\n        # 对于不能回答的问题直接回复数数\n        if text.find('不明白你是什么意思，麻烦换一种说法') != -1:\n            text = str(self.acc)\n            self.acc = self.acc + 1\n        if text.find('不明白你说的什么意思') != -1:\n            text = str(self.acc)\n            self.acc = self.acc + 1\n        if text == self.lasttext and msginfo['FromUserName'] == self.lastuser:\n            text = self.__randomanswer()\n\n        self.lasttext = text\n        self.lastuser = msginfo['FromUserName']\n        return text\n\n    def __randomanswer(self):\n        diaglen = len(RobotPredefinedAnswer.dialoglist)\n        index = random.randint(0, diaglen-1)\n        return RobotPredefinedAnswer.dialoglist[index]\n"
  },
  {
    "path": "wechat-asyncio/RobotPredefinedAnswer.py",
    "content": "dialoglist = [\n    '公司下属出错了，没什么大事，我小心提醒一句，就说下次要注意啊，不然艾姆安格瑞',\n    '我妈给我介绍对象，看了照片，很美，我脱口而出，excited!' ,\n    '财务处通知我薪水涨了，我说，哎呀，你们给我搞的这个涨薪，excited!' ,\n    '吃到好吃的东西，不说delicious, 说excited',\n    '新同事长的美艳无比，自我介绍时候我忍不住连声赞叹excited' ,\n    '看完大圣归来，观后感就是excited' ,\n    '遇到任何高兴的事情，都用一棵塞体的表达心情' ,\n    '有了点积蓄开始买手表，先后买了三块，不打算再买了，遇到重大场合把三块都戴着以示严肃' ,\n    '以前在公交车被人踩了，都是叫哎呀！现在被踩脱口而出 艾姆安格瑞！' ,\n    '遇到工科生必问你们学不学engineer drawing，用不用鸭嘴笔' ,\n    '身为淮安人说普通话不自觉带扬州口音' ,\n    '把格利高里派克演的葛底斯堡演讲视频截下来，对着反复练习，每一次都觉得自己受到了灵魂洗礼，熟练背诵全文没一点问题' ,\n    '曾经花了一年的闲余时间听完了莎士比亚的戏剧全集音频，顺着翻完了世图出版的那本全集书，看书的动机就是因为他说中国人要熟悉莎士比亚和贝多芬' ,\n    '欢迎客人亲戚时候，都是套路：今天天气预报说有雨，二舅你来了立刻万里无云阳光灿烂' ,\n    '跟人提起未来，规划，梦想的时候，必然要提到，人的命运啊，就什么都不知道。个人奋斗当然很重要，但和历史行程也是分不开的' ,\n    '被人表扬时候表示谦虚，都是说我很惭愧，就做了这么点微小工作，谢谢大家' ,\n    '为了表示志向高洁理想远大，必然要念林则徐的两句诗' ,\n    '朋友问我下班去吃日料不，以前回好，现在回吼啊！' ,\n    '就会一句粤语，识得唔识得噶' ,\n    '形容和某人的深厚友谊一般用谈笑风生这个词' ,\n    '对唱歌好有文艺范的妹子充满好感' ,\n    '以前很反感说话中夹英，现在能夹就夹，不然体现不出膜法师的风度' ,\n    '这些就是比较少见的一些词语运用，类似图样什么的出现的场合太多了，就不一一列举了' ,\n    '它，就是化肥，“金坷垃”。' ,\n    '你家麦地2米以下藏着丰富的氮磷钾。' ,\n    '肥料掺了金坷垃，一袋能顶两袋撒' ,\n    '肥料掺了金坷垃，小麦亩产一千八。' ,\n    '美国英国法国都在用金坷垃......日本不给用......'\n]\n"
  },
  {
    "path": "wechat-asyncio/Wechat.py",
    "content": "# coding=utf-8\n\n\nfrom HttpClient import HttpClient\nimport aiohttp\nimport asyncio\nimport time\nimport re\nimport xml.dom.minidom\nimport json\nimport html\n\nimport config\nimport logging\n\nlogger = logging.getLogger('wx')\n\nclass Wechat():\n    def __init__(self, client):\n        self.__wxclient = HttpClient(client)\n        self.tip = 0\n        self.deviceId = 'e000701000000000'\n\n        self.recvqueue = asyncio.Queue()\n        self.sendqueue = asyncio.Queue()\n        self.blacklist = []\n        self.updatequeue = asyncio.Queue() # 更新群组信息的请求\n        self.grouplist = {} # 存储群组的联系人信息\n        # 给 monitor 用\n        self.retcode = '0'\n        self.selector = '0'\n\n    async def __getuuid(self):\n        logger.debug('Entering getuuid.')\n        url = 'https://login.weixin.qq.com/jslogin'\n        payload = {\n            'appid': 'wx782c26e4c19acffb',\n            'fun': 'new',\n            'lang': 'zh_CN',\n            '_': int(time.time()),\n        }\n\n        text = await self.__wxclient.post(url=url, data=payload)\n        if text == None:\n            return False\n        logger.info(text)\n\n        regx = r'window.QRLogin.code = (\\d+); window.QRLogin.uuid = \"(\\S+?)\"'\n        pm = re.search(regx, text)\n\n        code = pm.group(1)\n        uuid = pm.group(2)\n\n        self.uuid = uuid\n        if code == '200':\n            return True\n        else:\n            return False\n\n    async def __downloadQR(self):\n        logger.debug('Entering downloadQR.')\n        url = 'https://login.weixin.qq.com/qrcode/' + self.uuid\n        payload = {\n            't': 'webwx',\n            '_': int(time.time()),\n        }\n\n        su = await self.__wxclient.downloadfile(url, data=payload, filename='qrimage.jpg')\n        logger.info ('请扫描二维码')\n        print ('请扫描二维码')\n        return su\n\n    async def __waitforlogin(self):\n        logger.debug('Waiting for login.......')\n        url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (self.tip, self.uuid, int(time.time()))\n        text = await self.__wxclient.get(url)\n\n        regx = r'window.code=(\\d+);'\n        pm = re.search(regx, text)\n        code = pm.group(1)\n\n        if code == '201':\n            logger.info ('成功扫描,请在手机上点击确认以登录')\n            print ('成功扫描,请在手机上点击确认以登录')\n            self.tip = 0\n        elif code == '200':\n            logger.info ('正在登录。。。')\n            print ('正在登录。。。')\n            regx = r'window.redirect_uri=\"(\\S+?)\";'\n            pm = re.search(regx, text)\n            redirect_uri = pm.group(1) + '&fun=new'\n            self.redirect_uri  = redirect_uri\n            base_uri = redirect_uri[:redirect_uri.rfind('/')]\n            self.base_uri = base_uri\n\n            services = [\n                ('wx2.qq.com', 'webpush2.weixin.qq.com'),\n                ('qq.com', 'webpush.weixin.qq.com'),\n                ('web1.wechat.com', 'webpush1.wechat.com'),\n                ('web2.wechat.com', 'webpush2.wechat.com'),\n                ('wechat.com', 'webpush.wechat.com'),\n                ('web1.wechatapp.com', 'webpush1.wechatapp.com'),\n            ]\n\n            push_uri = base_uri\n            for (searchUrl, pushUrl) in services:\n                if base_uri.find(searchUrl) >= 0:\n                    push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl\n                    break\n            self.push_uri = push_uri\n        elif code == '408':\n            pass\n\n        return code\n\n\n    async def __checklogin(self):\n        logger.debug('Entering checklogin.')\n        text = await self.__wxclient.get(self.redirect_uri)\n\n        doc = xml.dom.minidom.parseString(text)\n        root = doc.documentElement\n\n        for node in root.childNodes:\n            if node.nodeName == 'skey':\n                skey = node.childNodes[0].data\n            elif node.nodeName == 'wxsid':\n                wxsid = node.childNodes[0].data\n            elif node.nodeName == 'wxuin':\n                wxuin = node.childNodes[0].data\n            elif node.nodeName == 'pass_ticket':\n                pass_ticket = node.childNodes[0].data\n\n        if not all((skey, wxsid, wxuin, pass_ticket)):\n            return False\n\n        BaseRequest = {\n            'Uin': int(wxuin),\n            'Sid': wxsid,\n            'Skey': skey,\n            'DeviceID': self.deviceId,\n        }\n        logger.debug('%s, %s, %s, %s', skey, wxsid, wxuin, pass_ticket)\n        self.skey = skey\n        self.wxsid = wxsid\n        self.wxuin = wxuin\n        self.pass_ticket = pass_ticket\n        self.BaseRequest = BaseRequest\n\n        return True\n\n\n    async def __responseState(self, func, BaseResponse):\n        ErrMsg = BaseResponse['ErrMsg']\n        Ret = BaseResponse['Ret']\n        logger.info('func: %s, Ret: %d, ErrMsg: %s' % (func, Ret, ErrMsg))\n        if Ret != 0:\n            return False\n        return True\n\n\n    async def __webwxinit(self):\n        logger.debug('Entering webwxinit.')\n        url = self.base_uri + \\\n            '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % (\n                self.pass_ticket, self.skey, int(time.time()))\n        payload = {\n            'BaseRequest' : self.BaseRequest\n        }\n\n        dic = await self.__wxclient.post_json(url=url, data=json.dumps(payload))\n\n        self.My = dic['User']\n        self.SyncKey = dic['SyncKey']\n        logger.debug('The new SyncKey is: %s' % self.SyncKey)\n\n        return await self.__responseState('webwxinit', dic['BaseResponse'])\n\n    async def __webwxgetcontact(self):\n        url = self.base_uri + \\\n        '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (\n            self.pass_ticket, self.skey, int(time.time()))\n\n        dic = await self.__wxclient.get_json(url)\n\n        SpecialUsers = [\"newsapp\", \"fmessage\", \"filehelper\", \"weibo\", \"qqmail\", \"tmessage\", \"qmessage\", \"qqsync\", \"floatbottle\", \"lbsapp\", \"shakeapp\", \"medianote\", \"qqfriend\", \"readerapp\", \"blogapp\", \"facebookapp\", \"masssendapp\",\n                    \"meishiapp\", \"feedsapp\", \"voip\", \"blogappweixin\", \"weixin\", \"brandsessionholder\", \"weixinreminder\", \"wxid_novlwrv3lqwv11\", \"gh_22b87fa7cb3c\", \"officialaccounts\", \"notification_messages\", \"wxitil\", \"userexperience_alarm\"]\n        self.blacklist += SpecialUsers\n\n        MemberList = {}\n        for member in dic['MemberList']:\n            if member['VerifyFlag'] & 8 != 0: # 公众号\n                continue\n            elif member['UserName'] in SpecialUsers:\n                continue\n            MemberList[member['UserName']] = {\n                'NickName' : member['NickName'] ,\n                'DisplayName' : member['DisplayName']\n            }\n\n        self.memberlist = MemberList\n        logger.info('You have %s friends.' % len(MemberList))\n\n\n\n    async def __login(self):\n        success = await self.__getuuid()\n        if not success:\n            logger.info ('获取 uuid 失败')\n            print ('获取 uuid 失败')\n        success = await self.__downloadQR()\n        if not success:\n            logger.info ('获取二维码失败')\n            print ('获取二维码失败')\n\n        while await self.__waitforlogin() != '200':\n            pass\n\n        success = await self.__checklogin()\n        if not success:\n            logger.info ('登陆失败')\n            print ('登陆失败')\n        logger.info ('登陆成功')\n        print ('登陆成功')\n        success = await self.__webwxinit()\n        if not success:\n            logger.info ('初始化失败')\n            print ('初始化失败')\n        logger.info ('初始化成功')\n        print ('初始化成功')\n\n        await self.__webwxgetcontact()\n\n    def __syncKey(self):\n        SyncKey = self.SyncKey\n        SyncKeyItems = ['%s_%s' % (item['Key'], item['Val'])\n                        for item in SyncKey['List']]\n        SyncKeyStr = '|'.join(SyncKeyItems)\n        return SyncKeyStr\n\n    async def __synccheck(self):\n        url = self.push_uri + '/synccheck?'\n        BaseRequest = self.BaseRequest\n        params = {\n            'skey': BaseRequest['Skey'] ,\n            'sid': BaseRequest['Sid'] ,\n            'uin': BaseRequest['Uin'] ,\n            'deviceId': BaseRequest['DeviceID'] ,\n            'synckey': self.__syncKey() ,\n            'r': int(time.time()*1000)\n        }\n        text = await self.__wxclient.get(url, params = params)\n        if text == None:\n            return ('1111', '1111')\n\n        regx = r'window.synccheck={retcode:\"(\\d+)\",selector:\"(\\d+)\"}'\n        pm = re.search(regx, text)\n\n        retcode = pm.group(1)\n        selector = pm.group(2)\n        logger.info('retcode: %s, selector: %s' % (retcode, selector))\n        return (retcode, selector)\n\n    async def __webwxsync(self):\n        url = self.base_uri + '/webwxsync?'\n        payload = {\n            'BaseRequest' : self.BaseRequest ,\n            'SyncKey' : self.SyncKey ,\n            'rr' : ~int(time.time())\n        }\n        params = {\n            'skey' : self.BaseRequest['Skey'] ,\n            'pass_ticket' : self.pass_ticket ,\n            'sid' : self.BaseRequest['Sid']\n        }\n\n        dic = await self.__wxclient.post_json(url, params=params, data=json.dumps(payload))\n        if dic == None:\n            return\n        # 更新 synckey\n        self.SyncKey = dic['SyncKey']\n\n        await self.__responseState('webwxsync', dic['BaseResponse'])\n\n        msglist = dic['AddMsgList']\n        for msg in msglist:\n            await self.recvqueue.put(msg)\n\n\n    async def __webwxsendmsg(self, content, user):\n        url = self.base_uri + \\\n            '/webwxsendmsg?pass_ticket=%s' % (self.pass_ticket)\n\n        msgid = int(time.time()*10000000)\n        msg = {\n            'ClientMsgId' : msgid ,\n            'Content' : content,\n            'FromUserName' : self.My['UserName'] ,\n            'LocalID' : msgid ,\n            'ToUserName' : user,\n            'Type' : 1\n        }\n        payload = {\n            'BaseRequest' : self.BaseRequest ,\n            'Msg' : msg\n        }\n        data = json.dumps(payload, ensure_ascii=False)\n        data = data.encode('utf-8')\n\n        text = await self.__wxclient.post(url, data=data)\n\n\n    async def __webwxbatchgetcontact(self, groupname):\n        url = self.base_uri + '/webwxbatchgetcontact?'\n        List = [{\n            'ChatRoomId' : '',\n            'UserName' : groupname\n        }]\n        payload = {\n            'BaseRequest': self.BaseRequest ,\n            'Count' : 1 ,\n            'List' : List\n        }\n        params = {\n            'lang' : 'zh_CN' ,\n            'type' : 'ex' ,\n            'pass_ticket' : self.pass_ticket ,\n            'r' : int(time.time())\n        }\n\n        dic = await self.__wxclient.post_json(url, params=params, data=json.dumps(payload))\n        if dic == None:\n            return\n        GroupMapUsers = {}\n        ContactList = dic['ContactList']\n        for contact in ContactList:\n            memberlist = contact['MemberList']\n            for member in memberlist:\n                # 默认 @群名片，没有群名片就 @昵称\n                nickname = member['NickName']\n                displayname = member['DisplayName']\n                AT = ''\n                if displayname == '':\n                    # 有些人的昵称会有表情 <span> 会表示成 &lt;span&gt;\n                    # 需要 html.unescape() 转义一下\n                    AT = html.unescape(nickname)\n                else:\n                    AT = html.unescape(displayname)\n                GroupMapUsers[member['UserName']] = AT\n\n        self.grouplist[groupname] = GroupMapUsers\n\n    async def sync(self):\n        await self.__login()\n        logger.info ('开始心跳噗通噗咚 咚咚咚！！！！')\n        print ('开始心跳噗通噗咚 咚咚咚！！！！')\n        logger.info('Begin to sync with wx server.....')\n        while True:\n            retcode, selector = await self.__synccheck()\n            if retcode != '0':\n                logger.info ('sync 失败')\n                print ('sync 失败')\n            if selector != '0':\n                await self.__webwxsync()\n\n            await asyncio.sleep(config.sync_interval)\n            self.retcode = retcode\n            self.selector = selector\n\n\n    async def sendmsg(self):\n        while True:\n            response = await self.sendqueue.get()\n            # 不要发的太频繁，在拿到 response 之后歇一秒\n            await asyncio.sleep(config.send_interval)\n            await self.__webwxsendmsg(response['Content'], response['user'])\n\n    async def updategroupinfo(self):\n        while True:\n            groupname = await self.updatequeue.get()\n\n            logger.info('更新群信息开始')\n            await self.__webwxbatchgetcontact(groupname)\n            await asyncio.sleep(config.updategroupinfo_interval)\n            logger.info('更新群信息结束')\n"
  },
  {
    "path": "wechat-asyncio/config.py",
    "content": "send_interval = 0.5\nsync_interval = 1\nupdategroupinfo_interval = 1\nmonitor_interval = 10\nmsgloop_interval = 1\n"
  },
  {
    "path": "wechat-asyncio/logger.conf",
    "content": "#logger.conf\n\n\n###############################################\n\n[loggers]\nkeys=root,wx,monitor\n\n[logger_root]\nlevel=DEBUG\nhandlers=hand01\n\n[logger_wx]\nhandlers=hand01\nqualname=wx\npropagate=0\n\n[logger_monitor]\nhandlers=hand02\nqualname=monitor\npropagate=0\n\n###############################################\n\n[handlers]\nkeys=hand01,hand02\n\n[handler_hand01]\nclass=handlers.RotatingFileHandler\nlevel=DEBUG\nformatter=form01\nargs=('wx.log', 'a', 10*1024*1024, 5)\n\n[handler_hand02]\nclass=handlers.RotatingFileHandler\nlevel=DEBUG\nformatter=form01\nargs=('monitor.log', 'a', 10*1024*1024, 5)\n\n###############################################\n\n[formatters]\nkeys=form01\n\n[formatter_form01]\nformat=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s\ndatefmt=%a, %d %b %Y %H:%M:%S\n"
  },
  {
    "path": "wechat-asyncio/main.py",
    "content": "# coding=utf-8\n\nimport aiohttp\nimport asyncio\n\nfrom Wechat import Wechat\nfrom MsgHandler import MsgHandler\nfrom RobotEngine import RobotEngine\nfrom Monitor import Monitor\nimport auth\n\nimport logging\nimport logging.config\n\nlogging.config.fileConfig(\"logger.conf\")\n\nwith aiohttp.ClientSession() as client, aiohttp.ClientSession() as rclient:\n    wx = Wechat(client)\n    robot = RobotEngine(rclient, auth.apikey)\n    msg = MsgHandler(wx, robot)\n    god = Monitor(wx)\n    tasks = [\n            wx.sync() ,\n            wx.sendmsg() ,\n            wx.updategroupinfo() ,\n            msg.msgloop() ,\n            god.monitor()\n            ]\n    asyncio.get_event_loop().run_until_complete(asyncio.wait(tasks))\n"
  },
  {
    "path": "wechat-draft/wechat-robot.py",
    "content": "# coding=utf-8\n\nimport os\nimport requests\nimport simplejson as json\nimport time\nimport re\nimport xml.dom.minidom\n# from collections import deque\nimport queue\nimport html\n\nimport threading\n# lock = threading.Lock()\n\nDEBUG = False\nLOG = True\n\ndeviceId = 'e000000000000000' \ng_info = {}\ng_info['tip'] = 0\ng_queue = queue.Queue()# []\n\nimport config\napikey = config.apikey\n\ndef getUUID():\n    global g_info\n\n    url = 'https://login.weixin.qq.com/jslogin'\n    params = {\n        'appid': 'wx782c26e4c19acffb',\n        'fun': 'new',\n        'lang': 'zh_CN',\n        '_': int(time.time()),   \n    }\n\n    r = requests.post(url, params)\n    if DEBUG:\n        print (r.text)\n    text = r.text\n    regx = r'window.QRLogin.code = (\\d+); window.QRLogin.uuid = \"(\\S+?)\"'\n    pm = re.search(regx, text)\n\n    code = pm.group(1)\n    uuid = pm.group(2)\n\n    g_info['uuid'] = uuid\n    if code == '200':\n        return True \n\n    return False\n\ndef showQRImage():\n    global g_info\n\n    uuid = g_info['uuid']\n    url = 'https://login.weixin.qq.com/qrcode/' + uuid\n    params = {\n        't': 'webwx',\n        '_': int(time.time()),\n    }\n\n    r = requests.post(url, params)\n    g_info['tip'] = 1\n\n    with open('qrcode.jpg', 'wb') as fd:\n        for chunk in r.iter_content(512):\n            fd.write(chunk)\n\n    print ('请扫描二维码。。。')\n\ndef waitForLogin():\n    global g_info\n\n    tip = g_info['tip']\n    uuid = g_info['uuid']\n    url = 'https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?tip=%s&uuid=%s&_=%s' % (\n        tip, uuid, int(time.time()))  \n\n    r = requests.get(url)\n    text = r.text\n    regx = r'window.code=(\\d+);'\n    pm = re.search(regx, text)\n    \n    code = pm.group(1)\n    if code == '201':\n        print ('成功扫描,请在手机上点击确认以登录')\n        g_info['tip'] = 0\n    elif code == '200':\n        print ('正在登录。。。')\n        regx = r'window.redirect_uri=\"(\\S+?)\";'\n        pm = re.search(regx, text)\n        redirect_uri = pm.group(1) + '&fun=new'\n        g_info['redirect_uri']  = redirect_uri\n        base_uri = redirect_uri[:redirect_uri.rfind('/')]\n        g_info['base_uri'] = base_uri\n\n        services = [\n            ('wx2.qq.com', 'webpush2.weixin.qq.com'),\n            ('qq.com', 'webpush.weixin.qq.com'),\n            ('web1.wechat.com', 'webpush1.wechat.com'),\n            ('web2.wechat.com', 'webpush2.wechat.com'),\n            ('wechat.com', 'webpush.wechat.com'),\n            ('web1.wechatapp.com', 'webpush1.wechatapp.com'),\n        ]\n\n        push_uri = base_uri\n        for (searchUrl, pushUrl) in services:\n            if base_uri.find(searchUrl) >= 0:\n                push_uri = 'https://%s/cgi-bin/mmwebwx-bin' % pushUrl\n                break  \n        g_info['push_uri'] = push_uri\n\n    elif code == '408':\n        pass\n\n    return code\n\ndef login():\n    global g_info\n\n    redirect_uri = g_info['redirect_uri']\n    r = requests.get(redirect_uri)\n\n\n    g_info['cookies'] = r.cookies\n\n    doc = xml.dom.minidom.parseString(r.text)\n    root = doc.documentElement\n\n    for node in root.childNodes:\n        if node.nodeName == 'skey':\n            skey = node.childNodes[0].data\n        elif node.nodeName == 'wxsid':\n            wxsid = node.childNodes[0].data\n        elif node.nodeName == 'wxuin':\n            wxuin = node.childNodes[0].data\n        elif node.nodeName == 'pass_ticket':\n            pass_ticket = node.childNodes[0].data   \n\n    if not all((skey, wxsid, wxuin, pass_ticket)):\n        return False\n\n    BaseRequest = {\n        'Uin': int(wxuin),\n        'Sid': wxsid,\n        'Skey': skey,\n        'DeviceID': deviceId,\n    }\n\n    g_info['skey'] = skey\n    g_info['wxsid'] = wxsid\n    g_info['wxuin'] = wxuin\n    g_info['pass_ticket'] = pass_ticket\n    g_info['BaseRequest'] = BaseRequest\n\n    if DEBUG:\n        print (skey, wxsid, wxuin)\n\n    return True\n\n\n\ndef responseState(func, BaseResponse):\n    ErrMsg = BaseResponse['ErrMsg']\n    Ret = BaseResponse['Ret']\n    if DEBUG:\n        print('func: %s, Ret: %d, ErrMsg: %s' % (func, Ret, ErrMsg))\n\n    if Ret != 0:\n        return False\n\n    return True\n\ndef webwxinit():\n    global g_info\n\n    base_uri = g_info['base_uri']\n    pass_ticket = g_info['pass_ticket']\n    skey = g_info['skey']\n    BaseRequest = g_info['BaseRequest']\n\n    url = base_uri + \\\n        '/webwxinit?pass_ticket=%s&skey=%s&r=%s' % (\n            pass_ticket, skey, int(time.time()))\n    params = {\n        'BaseRequest': BaseRequest\n    }\n\n    r = requests.post(url, json.dumps(params))\n    text = r.text\n\n    if DEBUG:\n        with open('webwxinit.json', 'wb') as fd:\n            for chunk in r.iter_content(512):\n                fd.write(chunk)\n\n    dic = json.loads(text)\n    ContactList = dic['ContactList']\n    My = dic['User']\n    SyncKey = dic['SyncKey']\n\n    g_info['ContactList'] = ContactList\n    g_info['My'] = My \n    g_info['SyncKey'] = SyncKey\n\n    state = responseState('webwxinit', dic['BaseResponse'])\n    return state\n\ndef webwxgetcontact():\n    global g_info\n\n    base_uri = g_info['base_uri']\n    pass_ticket = g_info['pass_ticket']\n    skey = g_info['skey']\n\n    url = base_uri + \\\n        '/webwxgetcontact?pass_ticket=%s&skey=%s&r=%s' % (\n            pass_ticket, skey, int(time.time()))\n    headers = {'Content-Type':'application/json; charset=UTF-8'}\n    r = requests.get(url,headers=headers,cookies=g_info['cookies'])\n    text = r.text\n\n    if DEBUG:\n        with open('webwxgetcontact.json', 'wb') as fd:\n            for chunk in r.iter_content(512):\n                fd.write(chunk)\n\n    \n    dic = json.loads(text, 'utf-8')\n    MemberList = dic['MemberList']\n    SpecialUsers = [\"newsapp\", \"fmessage\", \"filehelper\", \"weibo\", \"qqmail\", \"tmessage\", \"qmessage\", \"qqsync\", \"floatbottle\", \"lbsapp\", \"shakeapp\", \"medianote\", \"qqfriend\", \"readerapp\", \"blogapp\", \"facebookapp\", \"masssendapp\",\n                    \"meishiapp\", \"feedsapp\", \"voip\", \"blogappweixin\", \"weixin\", \"brandsessionholder\", \"weixinreminder\", \"wxid_novlwrv3lqwv11\", \"gh_22b87fa7cb3c\", \"officialaccounts\", \"notification_messages\", \"wxitil\", \"userexperience_alarm\"]\n    for i in range(len(MemberList) - 1, -1, -1):\n        Member = MemberList[i]\n        if Member['VerifyFlag'] & 8 != 0:  # 公众号/服务号\n            MemberList.remove(Member)\n        elif Member['UserName'] in SpecialUsers:  # 特殊账号\n            MemberList.remove(Member)   \n    \n    _MemberList = {}\n    for user in MemberList:\n        _MemberList[user['UserName']] = user['NickName']\n\n    g_info['MemberList'] = _MemberList\n\n\ndef syncKey():\n    global g_info\n\n    SyncKey = g_info['SyncKey']\n    SyncKeyItems = ['%s_%s' % (item['Key'], item['Val'])\n                    for item in SyncKey['List']]\n    SyncKeyStr = '|'.join(SyncKeyItems)\n    return SyncKeyStr\n\ndef syncCheck():\n    global g_info\n\n    push_uri = g_info['push_uri']\n    BaseRequest = g_info['BaseRequest']\n    url = push_uri + '/synccheck?'\n    params = {\n        'skey': BaseRequest['Skey'],\n        'sid': BaseRequest['Sid'],\n        'uin': BaseRequest['Uin'],\n        'deviceId': BaseRequest['DeviceID'],\n        'synckey': syncKey(),\n        'r': int(time.time()),\n    }\n\n    r = requests.get(url, params=params, cookies=g_info['cookies'], timeout=300)\n    text = r.text\n    regx = r'window.synccheck={retcode:\"(\\d+)\",selector:\"(\\d+)\"}'\n    pm = re.search(regx, text)    \n\n    retcode = pm.group(1)\n    selector = pm.group(2)\n\n    if DEBUG or LOG:\n        print (text)\n\n    return (retcode, selector)\n\n\ndef webwxsync():\n    global g_info\n\n    base_uri = g_info['base_uri']\n    BaseRequest = g_info['BaseRequest']\n    SyncKey = g_info['SyncKey']\n    pass_ticket = g_info['pass_ticket']\n    # url = base_uri + '/webwxsync?lang=zh_CN&skey=%s&sid=%s&pass_ticket=%s' % (\n    #     BaseRequest['Skey'], BaseRequest['Sid'], quote_plus(pass_ticket))\n    url = base_uri + '/webwxsync?'\n    params = {\n        'BaseRequest': BaseRequest,\n        'SyncKey': SyncKey,\n        'rr': ~int(time.time()),\n    } \n    url_params = {\n        # 'lang' : 'zh_CN' ,\n        'skey' : BaseRequest['Skey'] ,\n        'pass_ticket' : pass_ticket ,\n        'sid' : BaseRequest['Sid']\n    }\n    headers = {\n        'ContentType': 'application/json; charset=UTF-8'\n    }\n    r = requests.post(url, params=url_params, data=json.dumps(params), headers = headers, cookies=g_info['cookies'])\n\n    # if DEBUG:\n    #     global i \n    #     i = i + 1\n    #     with open('sync' +  str(i) + '.json', 'wb') as fd:\n    #         for chunk in r.iter_content(512):\n    #             fd.write(chunk)\n    r.encoding = 'utf-8'\n    dic = json.loads(r.text)\n    # print (dic)\n    # 更新 synckey\n    g_info['SyncKey'] = dic['SyncKey']\n    state = responseState('webwxsync', dic['BaseResponse'])\n\n    msg_list = dic['AddMsgList']\n    if DEBUG:\n        print (msg_list)\n\n    return (state, msg_list)\n\n\ndef getMsg(msg_list):\n    global g_info, g_queue\n\n    My = g_info['My']\n\n    for msg in msg_list:\n        if msg['MsgType'] != 1:\n            continue\n        if msg['FromUserName'] == My['UserName']:\n            continue\n        if msg['ToUserName'] == My['UserName']:\n            response = {}\n            content = msg['Content']\n\n            # 群消息中 :<br/> 之前是 UserName\n            if content.find(':<br/>') != -1:\n                fromsomeone = content[:content.find(':<br/>')]\n            else:\n                fromsomeone = ''\n\n            fromsomeone_NickName = ''\n            if msg['FromUserName'].find('@@') != -1:\n                # 如果是来自群，那就试着去 g_info[] 对应的群中找群成员列表\n                groupName = msg['FromUserName']\n                # 群还没有记录\n                if groupName not in g_info:\n                    g_info['Group_UserName_Req'] = msg['FromUserName']\n                # 在群列表中有了，因为可能群成员会变化，所以要再次找一遍\n                elif fromsomeone in g_info[groupName]:\n                    fromsomeone_NickName = g_info[groupName][fromsomeone]\n                    fromsomeone_NickName = '@' + fromsomeone_NickName + ' '\n                # 找不到，所以置标志位，会在另一个群中触发寻找行为\n                else:\n                    g_info['Group_UserName_Req'] = msg['FromUserName']\n            else:\n                fromsomeone_NickName = ''\n\n            response['fromsomeone'] = fromsomeone_NickName\n            response['Content'] = content[content.find('>')+1:]\n            response['FromUserName'] = msg['FromUserName']\n\n            # g_info['Group_UserName_Req'] = response['FromUserName']\n\n            # 不停地塞新消息\n            # lock.acquire()\n            # try:\n            #     g_queue.append(response)\n            # finally:\n            #     lock.release()\n            # print (response['Content'])\n\n            # test\n            if g_queue.qsize() > 5:\n                g_queue.get()\n                g_queue.get()\n            g_queue.put(response)\n\n    if LOG:\n        print ('getmsg queue: %s' % g_queue.qsize())\n\n\ndef webwxsendmsg(content, user):\n    global g_info\n\n    base_uri = g_info['base_uri']\n    pass_ticket = g_info['pass_ticket']\n    BaseRequest = g_info['BaseRequest']\n    My = g_info['My']\n\n    wxuin = BaseRequest['Uin']\n    wxsid = BaseRequest['Sid']\n    skey = BaseRequest['Skey']\n    deviceId = BaseRequest['DeviceID']\n\n    url = base_uri + \\\n    '/webwxsendmsg?pass_ticket=%s' % (pass_ticket)\n\n    msgid = int(time.time()*10000000)\n    msg = {\n        'ClientMsgId' : msgid ,\n        'Content' : content,\n        'FromUserName' : My['UserName'] ,\n        'LocalID' : msgid ,\n        'ToUserName' : user,\n        'Type' : 1\n    }\n    params = {\n        'BaseRequest' : BaseRequest,\n        'Msg' : msg\n    }\n    data = json.dumps(params, ensure_ascii=False)\n    # print (data)\n    data = data.encode('utf-8')\n    r = requests.post(url, data=data, cookies=g_info['cookies'])\n\n\ndef sendMsg():\n    global g_info, g_queue\n\n    MemberList = g_info['MemberList']\n    tuling_url = 'http://www.tuling123.com/openapi/api?key=' + apikey + '&info='\n\n    time.sleep(1)\n    if LOG:\n        print ('sendmsg queue: %s' % g_queue.qsize())\n    while g_queue.empty() is False:\n        # lock.acquire()\n        # try:\n        #     response = g_queue.popleft()\n        # finally:\n        #     lock.release()\n        response = g_queue.get()\n\n        content = response['Content']\n        from_user = response['FromUserName']\n        AT = response['fromsomeone']\n        tuling_url = tuling_url + content\n        try:\n            data = requests.get(tuling_url)\n            data = json.loads(data.text)\n            text = data['text']\n        except:\n            text = '网络异常。。。。。'          \n        webwxsendmsg(AT + text, from_user)\n        time.sleep(1)\n\n        if LOG:\n            print\n            print ('机器人收到回复：%s' % content)\n            print ('机器人的回复: %s' % text)\n            print\n\n\n\n\ndef webwxbatchgetcontact(UserName):\n    global g_info\n\n    base_uri = g_info['base_uri']\n    pass_ticket = g_info['pass_ticket']\n    BaseRequest = g_info['BaseRequest']\n\n    url = base_uri + '/webwxbatchgetcontact?'\n\n\n    List = [{\n        'ChatRoomId' : '',\n        'UserName' : UserName\n    }]\n    \n    post_params = {\n        'BaseRequest': BaseRequest ,\n        'Count' : 1 ,\n        'List' : List\n    } \n    url_params = {\n        'lang' : 'zh_CN' ,\n        'type' : 'ex' ,\n        'pass_ticket' : pass_ticket ,\n        'r' : int(time.time())\n    }\n\n    headers = {\n        'ContentType': 'application/json; charset=UTF-8'\n    }\n    r = requests.post(url, params=url_params, data=json.dumps(post_params), headers = headers, cookies=g_info['cookies'])\n\n    r.encoding = 'utf-8'\n    dic = json.loads(r.text)    \n\n\n    GroupMapUsers = {}\n    ContactList = dic['ContactList']\n    for contact in ContactList:\n        memberlist = contact['MemberList']\n        for member in memberlist:\n            # 默认 @群名片，没有群名片就 @昵称\n            nickname = member['NickName']\n            displayname = member['DisplayName']\n            AT = ''\n            if displayname == '':\n                # 有些人的昵称会有表情 <span> 会表示成 &lt;span&gt;\n                # 需要 html.unescape() 转义一下\n                AT = html.unescape(nickname)\n            else:\n                AT = html.unescape(displayname)\n            GroupMapUsers[member['UserName']] = AT\n\n            if DEBUG:\n                print (member['NickName'])\n\n    # 整群的成员列表消息记录\n    g_info[UserName] = GroupMapUsers\n\n\n\n\ndef getgroupinfo():\n    global g_info\n\n    if 'Group_UserName_Req' not in g_info:\n        return\n    if g_info['Group_UserName_Req'] == '0':\n        return\n\n    Group_UserName = g_info['Group_UserName_Req']\n    webwxbatchgetcontact(Group_UserName)\n\n    # 这个变量表示一次 获取群成员列表 请求。请求完毕置空\n    g_info['Group_UserName_Req'] = '0'\n\n    time.sleep(0.5)\n\n\n    \ndef heartBeatLoop():\n    while True:\n        retcode, selector = syncCheck()\n        if retcode != '0':\n            print ('sync 失败。。。')\n        if selector == '2':\n            state, msg_list = webwxsync()\n            getMsg(msg_list)\n            getgroupinfo()\n\n        time.sleep(1)\n\n\n\ndef main():\n    global g_info\n\n    if not getUUID():\n        print ('获取 uuid 失败')\n        return\n    print ('获取二维码图片中。。。')\n    showQRImage()\n    time.sleep(1)\n\n    while waitForLogin() != '200':\n        pass\n\n    if not login():\n        print ('登陆失败')\n        return\n\n    if not webwxinit():\n        print ('初始化失败')\n        return\n\n    print ('登陆')\n\n    print ('获取好友。。。。')\n    webwxgetcontact()\n\n    print ('开始心跳 噗咚噗通')\n    t1 = threading.Thread(target=heartBeatLoop)\n    t1.start()\n\n    MemberCount = len(g_info['MemberList'])\n    print ('这位同志啊，你有 %s 个好友' % MemberCount)\n\n    try:\n        while True:\n            sendMsg()\n            time.sleep(1.5)\n    except KeyboardInterrupt:\n        print ('bye bye ~')\n\nif __name__ == '__main__':\n    main()"
  }
]