diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 63c7f653..c6898131 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -18,6 +18,6 @@ > [您的内容] -[document]: https://github.com/soimort/you-get/wiki/FAQ -[issues]: https://github.com/soimort/you-get/issues +[document]: http://itchat.readthedocs.io/zh/latest/ +[issues]: https://github.com/littlecodersh/itchat/issues [itchatmp]: https://github.com/littlecodersh/itchatmp diff --git a/README.md b/README.md index 1742f914..75767d9b 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ itchat.run() 这是一个基于这一项目的[开源小机器人][robot-source-code],百闻不如一见,有兴趣可以尝试一下。 +由于好友数量实在增长过快,自动通过好友验证的功能演示暂时关闭。 + ![QRCode][robot-qr] ## 截屏 @@ -207,7 +209,7 @@ import itchat newInstance = itchat.new_instance() newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl') -@newInstance.msg_register(TEXT) +@newInstance.msg_register(itchat.content.TEXT) def reply(msg): return msg.text @@ -239,10 +241,6 @@ itchat.logout() ## 常见问题与解答 -Q: 为什么中文的文件没有办法上传? - -A: 这是由于`requests`的编码问题导致的。若需要支持中文文件传输,将[fields.py][fields.py-2](py3版本见[这里][fields.py-3])文件放入requests包的packages/urllib3下即可 - Q: 如何通过这个包将自己的微信号变为控制器? A: 有两种方式:发送、接受自己UserName的消息;发送接收文件传输助手(filehelper)的消息 @@ -271,13 +269,17 @@ A: 有些账号是天生无法给自己的账号发送信息的,建议使用`f [HanSon/vbot][HanSon-vbot]: 基于PHP7的微信个人号机器人,通过实现匿名函数可以方便地实现各种自定义的功能 +[yaphone/itchat4j][yaphone-itchat4j]: 用Java扩展个人微信号的能力 + +[kanjielu/jeeves][kanjielu-jeeves]: 使用springboot开发的微信机器人 + ## 问题和建议 如果有什么问题或者建议都可以在这个[Issue][issue#1]和我讨论 或者也可以在gitter上交流:[![Gitter][gitter-picture]][gitter] -当然也可以加入我们新建的QQ群讨论:549762872 +当然也可以加入我们新建的QQ群讨论:549762872, 205872856 [gitter-picture]: https://badges.gitter.im/littlecodersh/ItChat.svg [gitter]: https://gitter.im/littlecodersh/ItChat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge @@ -291,8 +293,6 @@ A: 有些账号是天生无法给自己的账号发送信息的,建议使用`f [robot-qr]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FQRCode2.jpg?imageView/2/w/400/ [robot-demo-file]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FScreenshots%2F%E5%BE%AE%E4%BF%A1%E8%8E%B7%E5%8F%96%E6%96%87%E4%BB%B6%E5%9B%BE%E7%89%87.png?imageView/2/w/300/ [robot-demo-login]: http://7xrip4.com1.z0.glb.clouddn.com/ItChat%2FScreenshots%2F%E7%99%BB%E5%BD%95%E7%95%8C%E9%9D%A2%E6%88%AA%E5%9B%BE.jpg?imageView/2/w/450/ -[fields.py-2]: https://gist.github.com/littlecodersh/9a0c5466f442d67d910f877744011705 -[fields.py-3]: https://gist.github.com/littlecodersh/e93532d5e7ddf0ec56c336499165c4dc [littlecodersh]: https://github.com/littlecodersh [tempdban]: https://github.com/tempdban [Chyroc]: https://github.com/Chyroc @@ -301,4 +301,6 @@ A: 有些账号是天生无法给自己的账号发送信息的,建议使用`f [zixia-wechaty]: https://github.com/zixia/wechaty [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin [HanSon-vbot]: https://github.com/hanson/vbot +[yaphone-itchat4j]: https://github.com/yaphone/itchat4j +[kanjielu-jeeves]: https://github.com/kanjielu/jeeves [issue#1]: https://github.com/littlecodersh/ItChat/issues/1 diff --git a/README.rst b/README.rst index 70a30772..47b6df45 100644 --- a/README.rst +++ b/README.rst @@ -206,7 +206,7 @@ You may use the following commands to open multi instance. newInstance = itchat.new_instance() newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl') - @newInstance.msg_register(TEXT) + @newInstance.msg_register(itchat.content.TEXT) def reply(msg): return msg['Text'] diff --git a/README_EN.md b/README_EN.md index 0d1b5b71..ab6df03d 100644 --- a/README_EN.md +++ b/README_EN.md @@ -10,7 +10,7 @@ A wechat robot can handle all the basic messages with only less than 30 lines of And it's similiar to itchatmp (api for wechat massive platform), learn once and get two tools. -Now Wechat is an important part of personal life, hopefully this repo can help you extend your personal wechat account's functionality and enbetter user's experience with wechat. +Now Wechat is an important part of personal life, hopefully this repo can help you extend your personal wechat account's functionality and better user's experience with wechat. ## Installation @@ -168,7 +168,7 @@ itchat.search_friends(wechatAccount='littlecodersh') itchat.search_friends(name='LittleCoder机器人', wechatAccount='littlecodersh') ``` -There are detailed information about searching and getting of massive platforms and chatrooms in document. +There is detailed information about searching and getting of massive platforms and chatrooms in document. ### Download and send attachments @@ -207,7 +207,7 @@ import itchat newInstance = itchat.new_instance() newInstance.auto_login(hotReload=True, statusStorageDir='newInstance.pkl') -@newInstance.msg_register(TEXT) +@newInstance.msg_register(itchat.content.TEXT) def reply(msg): return msg['Text'] @@ -239,10 +239,6 @@ If you exit through phone, exitCallback will also be called. ## FAQ -Q: Why I can't upload files whose name is not purely english? - -A: This is caused because of the encoding of `requests`, you may fix it by placing [fields.py][fields.py-2](py3 version is [here][fields.py-3]) in packages/urllib3 of requests. - Q: How to use this package to use my wechat as an monitor? A: There are two ways: communicate with your own account or with filehelper. @@ -263,9 +259,11 @@ A: Some account simply can't send messages to yourself, so use `filehelper` inst [liuwons/wxBot][liuwons-wxBot]: A wechat robot similiar to the robot branch -[zixia/wechaty][zixia-wechaty]: wechat for bot in Javascript(ES6), Personal Account Robot Framework/Library +[zixia/wechaty][zixia-wechaty]: Wechat for bot in Javascript(ES6), Personal Account Robot Framework/Library + +[sjdy521/Mojo-Weixin][Mojo-Weixin]: Wechat web api in Perl, available with HTTP requests -[sjdy521/Mojo-Weixin][Mojo-Weixin]: wechat web api in Perl, available with HTTP requests +[yaphone/itchat4j][yaphone-itchat4j]: Extend your wechat with java ## Comments @@ -291,4 +289,5 @@ Or you may also use [![Gitter][gitter-picture]][gitter] [liuwons-wxBot]: https://github.com/liuwons/wxBot [zixia-wechaty]: https://github.com/zixia/wechaty [Mojo-Weixin]: https://github.com/sjdy521/Mojo-Weixin +[yaphone-itchat4j]: https://github.com/yaphone/itchat4j [issue#1]: https://github.com/littlecodersh/ItChat/issues/1 diff --git a/docs/api.md b/docs/api.md index e861c00e..15187032 100644 --- a/docs/api.md +++ b/docs/api.md @@ -102,7 +102,7 @@ def start_receiving(self, finishCallback=None): def get_msg(self): ''' fetch messages for fetching - - method blocks for sometime util + - method blocks for sometime until - new messages are to be received - or anytime they like - synckey is updated with returned synccheckkey diff --git a/itchat/__init__.py b/itchat/__init__.py index dfc19acd..256fc721 100644 --- a/itchat/__init__.py +++ b/itchat/__init__.py @@ -51,6 +51,7 @@ def new_instance(): send_image = originInstance.send_image send_video = originInstance.send_video send = originInstance.send +revoke = originInstance.revoke # components.hotreload dump_login_status = originInstance.dump_login_status load_login_status = originInstance.load_login_status diff --git a/itchat/components/contact.py b/itchat/components/contact.py index 3026ba5c..3ae3c02a 100644 --- a/itchat/components/contact.py +++ b/itchat/components/contact.py @@ -1,12 +1,10 @@ -import os, time, re, io +import time, re, io import json, copy -import traceback, logging - -import requests +import logging from .. import config, utils from ..returnvalues import ReturnValue -from ..storage import contact_change, templates +from ..storage import contact_change from ..utils import update_info_dict logger = logging.getLogger('itchat') @@ -148,8 +146,9 @@ def update_local_chatrooms(core, l): del oldChatroom['MemberList'][i] # - update OwnerUin if oldChatroom.get('ChatRoomOwner') and oldChatroom.get('MemberList'): - oldChatroom['OwnerUin'] = utils.search_dict_list(oldChatroom['MemberList'], - 'UserName', oldChatroom['ChatRoomOwner']).get('Uin', 0) + owner = utils.search_dict_list(oldChatroom['MemberList'], + 'UserName', oldChatroom['ChatRoomOwner']) + oldChatroom['OwnerUin'] = (owner or {}).get('Uin', 0) # - update IsAdmin if 'OwnerUin' in oldChatroom and oldChatroom['OwnerUin'] != 0: oldChatroom['IsAdmin'] = \ @@ -406,7 +405,7 @@ def get_head_img(self, userName=None, chatroomUserName=None, picDir=None): 'Ret': -1001, }}) if 'EncryChatRoomId' in chatroom: params['chatroomid'] = chatroom['EncryChatRoomId'] - params['chatroomid'] = params['chatroomid'] or chatroom['UserName'] + params['chatroomid'] = params.get('chatroomid') or chatroom['UserName'] headers = { 'User-Agent' : config.USER_AGENT } r = self.s.get(url, params=params, stream=True, headers=headers) tempStorage = io.BytesIO() diff --git a/itchat/components/hotreload.py b/itchat/components/hotreload.py index d34d25ee..ae93f9be 100644 --- a/itchat/components/hotreload.py +++ b/itchat/components/hotreload.py @@ -1,5 +1,5 @@ import pickle, os -import logging, traceback +import logging import requests @@ -55,7 +55,10 @@ def load_login_status(self, fileDir, self.loginInfo['User'].core = self self.s.cookies = requests.utils.cookiejar_from_dict(j['cookies']) self.storageClass.loads(j['storage']) - msgList, contactList = self.get_msg() + try: + msgList, contactList = self.get_msg() + except: + msgList = contactList = None if (msgList or contactList) is None: self.logout() load_last_login_status(self.s, j['cookies']) diff --git a/itchat/components/login.py b/itchat/components/login.py index 7736427c..c1fb0391 100644 --- a/itchat/components/login.py +++ b/itchat/components/login.py @@ -1,8 +1,12 @@ -import os, sys, time, re, io +import os, time, re, io import threading import json, xml.dom.minidom -import copy, pickle, random +import random import traceback, logging +try: + from httplib import BadStatusLine +except ImportError: + from http.client import BadStatusLine import requests from pyqrcode import QRCode @@ -59,7 +63,8 @@ def login(self, enableCmdQR=False, picDir=None, qrCallback=None, break if isLoggedIn: break - logger.info('Log in time out, reloading QR code.') + elif self.isLogging: + logger.info('Log in time out, reloading QR code.') else: return # log in process is stopped by user logger.info('Loading the contact, this may take a little while.') @@ -110,11 +115,11 @@ def get_QR(self, uuid=None, enableCmdQR=False, picDir=None, qrCallback=None): if hasattr(qrCallback, '__call__'): qrCallback(uuid=uuid, status='0', qrcode=qrStorage.getvalue()) else: + with open(picDir, 'wb') as f: + f.write(qrStorage.getvalue()) if enableCmdQR: utils.print_cmd_qr(qrCode.text(1), enableCmdQR=enableCmdQR) else: - with open(picDir, 'wb') as f: - f.write(qrStorage.getvalue()) utils.print_qr(picDir) return qrStorage @@ -122,15 +127,17 @@ def check_login(self, uuid=None): uuid = uuid or self.uuid url = '%s/cgi-bin/mmwebwx-bin/login' % config.BASE_URL localTime = int(time.time()) - params = 'loginicon=true&uuid=%s&tip=0&r=%s&_=%s' % ( - uuid, localTime / 1579, localTime) + params = 'loginicon=true&uuid=%s&tip=1&r=%s&_=%s' % ( + uuid, int(-localTime / 1579), localTime) headers = { 'User-Agent' : config.USER_AGENT } r = self.s.get(url, params=params, headers=headers) regx = r'window.code=(\d+)' data = re.search(regx, r.text) if data and data.group(1) == '200': - process_login_info(self, r.text) - return '200' + if process_login_info(self, r.text): + return '200' + else: + return '400' elif data: return data.group(1) else: @@ -161,6 +168,7 @@ def process_login_info(core, loginContent): else: core.loginInfo['fileUrl'] = core.loginInfo['syncUrl'] = core.loginInfo['url'] core.loginInfo['deviceid'] = 'e' + repr(random.random())[2:17] + core.loginInfo['logintime'] = int(time.time() * 1e3) core.loginInfo['BaseRequest'] = {} for node in xml.dom.minidom.parseString(r.text).documentElement.childNodes: if node.nodeName == 'skey': @@ -171,14 +179,22 @@ def process_login_info(core, loginContent): core.loginInfo['wxuin'] = core.loginInfo['BaseRequest']['Uin'] = node.childNodes[0].data elif node.nodeName == 'pass_ticket': core.loginInfo['pass_ticket'] = core.loginInfo['BaseRequest']['DeviceID'] = node.childNodes[0].data + if not all([key in core.loginInfo for key in ('skey', 'wxsid', 'wxuin', 'pass_ticket')]): + logger.error('Your wechat account may be LIMITED to log in WEB wechat, error info:\n%s' % r.text) + core.isLogging = False + return False + return True def web_init(self): - url = '%s/webwxinit?r=%s' % (self.loginInfo['url'], int(time.time())) + url = '%s/webwxinit' % self.loginInfo['url'] + params = { + 'r': int(-time.time() / 1579), + 'pass_ticket': self.loginInfo['pass_ticket'], } data = { 'BaseRequest': self.loginInfo['BaseRequest'], } headers = { 'ContentType': 'application/json; charset=UTF-8', 'User-Agent' : config.USER_AGENT, } - r = self.s.post(url, data=json.dumps(data), headers=headers) + r = self.s.post(url, params=params, data=json.dumps(data), headers=headers) dic = json.loads(r.content.decode('utf-8', 'replace')) # deal with login info utils.emoji_formatter(dic['User'], 'NickName') @@ -191,19 +207,19 @@ def web_init(self): self.storageClass.userName = dic['User']['UserName'] self.storageClass.nickName = dic['User']['NickName'] # deal with contact list returned when init - contactList = dic.get('ContactList', []) - chatroomList, otherList = [], [] - for m in contactList: - if m['Sex'] != 0: - otherList.append(m) - elif '@@' in m['UserName']: + contactList = dic.get('ContactList', []) + chatroomList, otherList = [], [] + for m in contactList: + if m['Sex'] != 0: + otherList.append(m) + elif '@@' in m['UserName']: m['MemberList'] = [] # don't let dirty info pollute the list - chatroomList.append(m) - elif '@' in m['UserName']: - # mp will be dealt in update_local_friends as well - otherList.append(m) + chatroomList.append(m) + elif '@' in m['UserName']: + # mp will be dealt in update_local_friends as well + otherList.append(m) if chatroomList: - update_local_chatrooms(self, chatroomList) + update_local_chatrooms(self, chatroomList) if otherList: update_local_friends(self, otherList) return dic @@ -252,6 +268,8 @@ def maintain_loop(): self.msgList.put(chatroomMsg) update_local_friends(self, otherList) retryCount = 0 + except requests.exceptions.ReadTimeout: + pass except: retryCount += 1 logger.error(traceback.format_exc()) @@ -280,9 +298,23 @@ def sync_check(self): 'uin' : self.loginInfo['wxuin'], 'deviceid' : self.loginInfo['deviceid'], 'synckey' : self.loginInfo['synckey'], - '_' : int(time.time() * 1000),} + '_' : self.loginInfo['logintime'], } headers = { 'User-Agent' : config.USER_AGENT } - r = self.s.get(url, params=params, headers=headers) + self.loginInfo['logintime'] += 1 + try: + r = self.s.get(url, params=params, headers=headers, timeout=config.TIMEOUT) + except requests.exceptions.ConnectionError as e: + try: + if not isinstance(e.args[0].args[1], BadStatusLine): + raise + # will return a package with status '0 -' + # and value like: + # 6f:00:8a:9c:09:74:e4:d8:e0:14:bf:96:3a:56:a0:64:1b:a4:25:5d:12:f4:31:a5:30:f1:c6:48:5f:c3:75:6a:99:93 + # seems like status of typing, but before I make further achievement code will remain like this + return '2' + except: + raise + r.raise_for_status() regx = r'window.synccheck={retcode:"(\d+)",selector:"(\d+)"}' pm = re.search(regx, r.text) if pm is None or pm.group(1) != '0': @@ -301,10 +333,10 @@ def get_msg(self): headers = { 'ContentType': 'application/json; charset=UTF-8', 'User-Agent' : config.USER_AGENT } - r = self.s.post(url, data=json.dumps(data), headers=headers) + r = self.s.post(url, data=json.dumps(data), headers=headers, timeout=config.TIMEOUT) dic = json.loads(r.content.decode('utf-8', 'replace')) if dic['BaseResponse']['Ret'] != 0: return None, None - self.loginInfo['SyncKey'] = dic['SyncCheckKey'] + self.loginInfo['SyncKey'] = dic['SyncKey'] self.loginInfo['synckey'] = '|'.join(['%s_%s' % (item['Key'], item['Val']) for item in dic['SyncCheckKey']['List']]) return dic['AddMsgList'], dic['ModContactList'] diff --git a/itchat/components/messages.py b/itchat/components/messages.py index 8f00bab1..85c0ca2e 100644 --- a/itchat/components/messages.py +++ b/itchat/components/messages.py @@ -1,7 +1,7 @@ import os, time, re, io import json import mimetypes, hashlib -import traceback, logging +import logging from collections import OrderedDict import requests @@ -21,6 +21,7 @@ def load_messages(core): core.send_image = send_image core.send_video = send_video core.send = send + core.revoke = revoke def get_download_fn(core, url, msgId): def download_fn(downloadDir=None): @@ -46,7 +47,7 @@ def download_fn(downloadDir=None): def produce_msg(core, msgList): ''' for messages types * 40 msg, 43 videochat, 50 VOIPMSG, 52 voipnotifymsg - * 53 webwxvoipnotifymsg, 9999 sysnotice + * 53 webwxvoipnotifymsg, 9999 sysnotice ''' rl = [] srl = [40, 43, 50, 52, 53, 9999] @@ -88,7 +89,7 @@ def produce_msg(core, msgList): 'Type': 'Text', 'Text': m['Content'],} elif m['MsgType'] == 3 or m['MsgType'] == 47: # picture - download_fn = get_download_fn(core, + download_fn = get_download_fn(core, '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId']) msg = { 'Type' : 'Picture', @@ -140,7 +141,11 @@ def download_video(videoDir=None): 'FileName' : '%s.mp4' % time.strftime('%y%m%d-%H%M%S', time.localtime()), 'Text': download_video, } elif m['MsgType'] == 49: # sharing - if m['AppMsgType'] == 6: + if m['AppMsgType'] == 0: # chat history + msg = { + 'Type': 'Note', + 'Text': m['Content'], } + elif m['AppMsgType'] == 6: rawMsg = m cookiesList = {name:data for name,data in core.s.cookies.items()} def download_atta(attaDir=None): @@ -168,7 +173,7 @@ def download_atta(attaDir=None): 'Type': 'Attachment', 'Text': download_atta, } elif m['AppMsgType'] == 8: - download_fn = get_download_fn(core, + download_fn = get_download_fn(core, '%s/webwxgetmsgimg' % core.loginInfo['url'], m['NewMsgId']) msg = { 'Type' : 'Picture', @@ -238,7 +243,7 @@ def produce_group_chat(core, msg): member = utils.search_dict_list((chatroom or {}).get( 'MemberList') or [], 'UserName', actualUserName) if member is None: - chatroom = core.update_chatroom(msg['FromUserName']) + chatroom = core.update_chatroom(chatroomUserName) member = utils.search_dict_list((chatroom or {}).get( 'MemberList') or [], 'UserName', actualUserName) if member is None: @@ -266,7 +271,7 @@ def send_raw_msg(self, msgType, content, toUserName): 'ToUserName': (toUserName if toUserName else self.storageClass.userName), 'LocalID': int(time.time() * 1e4), 'ClientMsgId': int(time.time() * 1e4), - }, + }, 'Scene': 0, } headers = { 'ContentType': 'application/json; charset=UTF-8', 'User-Agent' : config.USER_AGENT } r = self.s.post(url, headers=headers, @@ -340,9 +345,10 @@ def upload_chunk_file(core, fileDir, fileSymbol, fileSize, # save it on server cookiesList = {name:data for name,data in core.s.cookies.items()} fileType = mimetypes.guess_type(fileDir)[0] or 'application/octet-stream' + fileName = utils.quote(os.path.basename(fileDir)) files = OrderedDict([ ('id', (None, 'WU_FILE_0')), - ('name', (None, os.path.basename(fileDir))), + ('name', (None, fileName)), ('type', (None, fileType)), ('lastModifiedDate', (None, time.strftime('%a %b %d %Y %H:%M:%S GMT+0800 (CST)'))), ('size', (None, str(fileSize))), @@ -352,13 +358,13 @@ def upload_chunk_file(core, fileDir, fileSymbol, fileSize, ('uploadmediarequest', (None, uploadMediaRequest)), ('webwx_data_ticket', (None, cookiesList['webwx_data_ticket'])), ('pass_ticket', (None, core.loginInfo['pass_ticket'])), - ('filename' , (os.path.basename(fileDir), file_.read(524288), 'application/octet-stream'))]) + ('filename' , (fileName, file_.read(524288), 'application/octet-stream'))]) if chunks == 1: del files['chunk']; del files['chunks'] else: files['chunk'], files['chunks'] = (None, str(chunk)), (None, str(chunks)) headers = { 'User-Agent' : config.USER_AGENT } - return requests.post(url, files=files, headers=headers) + return core.s.post(url, files=files, headers=headers, timeout=config.TIMEOUT) def send_file(self, fileDir, toUserName=None, mediaId=None, file_=None): logger.debug('Request to send a file(mediaId: %s) to %s: %s' % ( @@ -506,3 +512,17 @@ def send(self, msg, toUserName=None, mediaId=None): else: r = self.send_msg(msg, toUserName) return r + +def revoke(self, msgId, toUserName, localId=None): + url = '%s/webwxrevokemsg' % self.loginInfo['url'] + data = { + 'BaseRequest': self.loginInfo['BaseRequest'], + "ClientMsgId": localId or str(time.time() * 1e3), + "SvrMsgId": msgId, + "ToUserName": toUserName} + headers = { + 'ContentType': 'application/json; charset=UTF-8', + 'User-Agent' : config.USER_AGENT } + r = self.s.post(url, headers=headers, + data=json.dumps(data, ensure_ascii=False).encode('utf8')) + return ReturnValue(rawResponse=r) diff --git a/itchat/components/register.py b/itchat/components/register.py index 86a37fcc..079d3191 100644 --- a/itchat/components/register.py +++ b/itchat/components/register.py @@ -23,6 +23,7 @@ def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl', logger.info("You can't get access to internet or wechat domain, so exit.") sys.exit() self.useHotReload = hotReload + self.hotReloadDir = statusStorageDir if hotReload: if self.load_login_status(statusStorageDir, loginCallback=loginCallback, exitCallback=exitCallback): @@ -30,7 +31,6 @@ def auto_login(self, hotReload=False, statusStorageDir='itchat.pkl', self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback, loginCallback=loginCallback, exitCallback=exitCallback) self.dump_login_status(statusStorageDir) - self.hotReloadDir = statusStorageDir else: self.login(enableCmdQR=enableCmdQR, picDir=picDir, qrCallback=qrCallback, loginCallback=loginCallback, exitCallback=exitCallback) diff --git a/itchat/config.py b/itchat/config.py index 9a7d9b2d..36ca488b 100644 --- a/itchat/config.py +++ b/itchat/config.py @@ -1,9 +1,10 @@ import os, platform -VERSION = '1.3.5' +VERSION = '1.3.10' BASE_URL = 'https://login.weixin.qq.com' -OS = platform.system() #Windows, Linux, Darwin +OS = platform.system() # Windows, Linux, Darwin DIR = os.getcwd() DEFAULT_QR = 'QR.png' +TIMEOUT = (10, 60) USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' diff --git a/itchat/core.py b/itchat/core.py index ba384ced..52d6ae4f 100644 --- a/itchat/core.py +++ b/itchat/core.py @@ -1,8 +1,6 @@ -import logging - import requests -from . import config, storage, utils, log +from . import storage from .components import load_components class Core(object): @@ -127,7 +125,7 @@ def start_receiving(self, exitCallback=None, getReceivingFnOnly=False): def get_msg(self): ''' fetch messages for fetching - - method blocks for sometime util + - method blocks for sometime until - new messages are to be received - or anytime they like - synckey is updated with returned synccheckkey @@ -369,6 +367,15 @@ def send(self, msg, toUserName=None, mediaId=None): it is defined in components/messages.py ''' raise NotImplementedError() + def revoke(self, msgId, toUserName, localId=None): + ''' revoke message with its and msgId + for options + - msgId: message Id on server + - toUserName: 'UserName' key of friend dict + - localId: message Id at local (optional) + it is defined in components/messages.py + ''' + raise NotImplementedError() def dump_login_status(self, fileDir=None): ''' dump login status to a specific file for option diff --git a/itchat/returnvalues.py b/itchat/returnvalues.py index b9600bd6..f28c11d0 100644 --- a/itchat/returnvalues.py +++ b/itchat/returnvalues.py @@ -1,6 +1,4 @@ #coding=utf8 -import sys - TRANSLATE = 'Chinese' class ReturnValue(dict): diff --git a/itchat/storage/templates.py b/itchat/storage/templates.py index 70b8d9f3..2c23c3e4 100644 --- a/itchat/storage/templates.py +++ b/itchat/storage/templates.py @@ -13,7 +13,7 @@ def __getattr__(self, value): return self[keyName] except KeyError: raise AttributeError("'%s' object has no attribute '%s'" % ( - self.__class__.__name__.split('.')[-1], value)) + self.__class__.__name__.split('.')[-1], keyName)) def get(self, v, d=None): try: return self[v] @@ -299,10 +299,6 @@ def send(self, msg, mediaId=None): 'Ret': -1006, 'ErrMsg': '%s can not send message directly' % \ self.__class__.__name__, }, }) - def __deepcopy__(self, memo): - r = super(ChatroomMember, self).__deepcopy__(memo) - r.core = self.core - return r def __setstate__(self, state): super(ChatroomMember, self).__setstate__(state) self['MemberList'] = fakeContactList diff --git a/itchat/utils.py b/itchat/utils.py index 89456532..e217368c 100644 --- a/itchat/utils.py +++ b/itchat/utils.py @@ -4,6 +4,11 @@ from HTMLParser import HTMLParser except ImportError: from html.parser import HTMLParser +try: + from urllib import quote as _quote + quote = lambda n: _quote(n.encode('utf8', 'replace')) +except ImportError: + from urllib.parse import quote import requests